@pmelab/gtd 52.1.0 → 53.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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +91 -129
- package/claude/hooks/drive.ts +0 -3
- package/claude/hooks/register.tsx +3 -68
- package/claude/hooks/ship.ts +85 -1595
- package/dist/chunks/{reserved-DxB5CROj.mjs → reserved-CQ7amcKG.mjs} +45 -132
- package/dist/chunks/{review-CKMSdvgW.mjs → review-CNgN_V8T.mjs} +2 -2
- package/dist/chunks/{tail-QZc7Uz2U.mjs → tail-JZ9PAOwp.mjs} +2 -14
- package/dist/chunks/{wire-BJuZWR3C.mjs → wire-C12dahrH.mjs} +2 -130
- package/dist/flows.mjs +1 -16
- package/dist/gtd.bundle.mjs +476 -982
- package/dist/testkit.mjs +58 -417
- package/dist/wire.mjs +2 -2
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +112 -363
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +3 -3
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +311 -674
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +1 -86
- package/src/cli/Cli.ts +13 -133
- package/src/cli/Metrics.test.ts +21 -31
- package/src/cli/Metrics.ts +25 -36
- package/src/cli/Program.test.ts +1 -35
- package/src/cli/Program.ts +5 -64
- package/src/cli/ToolCommands.ts +0 -3
- package/src/edge/Edge.test.ts +20 -322
- package/src/edge/Edge.ts +10 -181
- package/src/edge/index.ts +0 -1
- package/src/flows/runtime.test.ts +0 -27
- package/src/flows/runtime.ts +1 -49
- package/src/install/Install.ts +15 -24
- package/src/replay/Replay.test.ts +0 -169
- package/src/replay/Replay.ts +4 -93
- package/src/replay/index.ts +0 -1
- package/src/testkit/Layers.ts +0 -1
- package/src/wire/BeatDocument.ts +2 -12
- package/src/wire/BeatStatus.ts +0 -3
- package/src/wire/WireSchema.test.ts +0 -18
- package/src/wire/index.ts +0 -2
- package/src/wire/types.ts +0 -7
- package/src/workflow/Workflow.ts +1 -9
- package/src/workflows/feature/criteria.test.ts +1 -14
- package/src/workflows/feature/criteria.ts +6 -38
- package/src/workflows/feature/feature.ts +34 -118
- package/src/workflows/feature/hands-on-skip.feature +2 -70
- package/src/workflows/feature/index.ts +0 -1
- package/src/workflows/feature/one-step-land.feature +0 -4
- package/src/workflows/feature/prototype-rounds.feature +1 -1
- package/src/workflows/feature/prototype.feature +3 -3
- package/src/workflows/feature/prototype.ts +18 -29
- package/src/workflows/feature/refactor-absorbs.feature +3 -8
- package/src/workflows/feature/refactor-first.feature +0 -5
- package/src/workflows/feature/refactor-rebuild.feature +0 -6
- package/src/workflows/feature/refactor-review.feature +11 -11
- package/src/workflows/feature/refactor.ts +30 -64
- package/src/workflows/feature/review-gate.feature +1 -1
- package/src/workflows/feature/seam-rejection.feature +0 -4
- package/src/workflows/feature/seam.test.ts +7 -26
- package/src/workflows/feature/seam.ts +52 -165
- package/src/workflows/index.ts +0 -1
- package/src/workflows/shared/lenses.ts +0 -13
- package/src/workflows/shared/options.test.ts +2 -11
- package/src/workflows/shared/options.ts +4 -21
- package/src/workflows/shared/review.test.ts +3 -3
- package/src/workflows/shared/steps.ts +0 -2
- package/src/workflows/shared/text.test.ts +10 -80
- package/src/workflows/shared/text.ts +35 -211
- package/src/workflows/shared/vars.ts +2 -8
- package/src/workflows/shared/workflows.steps.ts +0 -13
- package/claude/hooks/context.ts +0 -66
- package/src/replay/Slices.test.ts +0 -73
- package/src/replay/Slices.ts +0 -45
- package/src/wire/ContextLimit.fixture.ts +0 -30
- package/src/wire/ContextLimit.test.ts +0 -96
- package/src/wire/ContextLimit.ts +0 -169
- package/src/wire/Slices.ts +0 -25
- package/src/wire/golden/1/slices.json +0 -11
- package/src/workflows/feature/context-limit.feature +0 -37
- package/src/workflows/feature/continuation-cap.feature +0 -69
- package/src/workflows/feature/continuation-rebuild.feature +0 -92
- package/src/workflows/feature/continuation-slices.feature +0 -68
- package/src/workflows/feature/continuation.feature +0 -209
- package/src/workflows/feature/slice-findings.feature +0 -256
- package/src/workflows/feature/slicing-cap.feature +0 -23
- package/src/workflows/feature/slicing.feature +0 -297
- package/src/workflows/feature/slicing.test.ts +0 -49
- package/src/workflows/feature/slicing.ts +0 -105
- package/src/workflows/shared/continuation.ts +0 -92
|
@@ -1,8 +1,8 @@
|
|
|
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,
|
|
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, start, vars, wrote } from "../flows.mjs";
|
|
2
2
|
import { lensEntriesOf } from "../workflow-kit.mjs";
|
|
3
|
-
import { $ as
|
|
4
|
-
import { a as refusePendingHandOff,
|
|
5
|
-
import { i as reUnwind, n as humanReview, r as isCode$1, t as buildTail } from "../chunks/review-
|
|
3
|
+
import { $ as observableMessage, B as designGateAnswerMessage, Ct as testerSystem, D as planner, Dt as unwindFailure, Et as unwindFailedMessage, H as designSystem, I as clearCriteriaMessage, J as implementBuildingPrompt, L as clearCriteriaQuestion, M as buildReviewCollectingPrompt, Mt as envDefaults, Nt as testFileGlobs, P as builderSystem, Q as normaliseCriteriaScript, S as collecting, St as startGateBlockedMessage, U as designTriagePrompt, V as designImpactPrompt, X as naiveApplyFailure, Y as implementFixPrompt, Z as naivePassPrompt, _ as REQUIREMENTS, a as flagModeOn, bt as seamByHandMessage, ct as refactorBuildingPrompt, d as healthy, dt as refactorNotePrompt, et as observableQuestion, f as FEEDBACK, ft as refactorRules, g as REFACTOR_NOTE, h as REFACTOR, i as skills, it as prototypeUnguidedMessage, j as agentWithSkills, jt as defaults, l as gate, lt as refactorDroppedRefusal, n as summary, nt as prototypeOpenMessage, p as PROTOTYPE, q as idleMessage, r as access, rt as prototypeTestingPrompt, s as shipped, t as steering, tt as prototypeFixingPrompt, ut as refactorFixPrompt, w as fixNits, x as coder, xt as seamSteerMessage, y as answerReviewQuestions, yt as reviewerSystem, z as criterionLine } from "../chunks/reserved-CQ7amcKG.mjs";
|
|
4
|
+
import { a as refusePendingHandOff, o as tail } from "../chunks/tail-JZ9PAOwp.mjs";
|
|
5
|
+
import { i as reUnwind, n as humanReview, r as isCode$1, t as buildTail } from "../chunks/review-CNgN_V8T.mjs";
|
|
6
6
|
import { posix } from "node:path";
|
|
7
7
|
//#region src/workflows/shared/fences.ts
|
|
8
8
|
const markFenced = (text) => {
|
|
@@ -26,7 +26,7 @@ const ITEM = /^ ?(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?(.*)$/;
|
|
|
26
26
|
//#region src/workflows/feature/criteria.ts
|
|
27
27
|
/** Below this the judge's `yes` is not trusted: a wrong skip builds against criteria nobody can check. */
|
|
28
28
|
const CLEAR_MIN_P = .8;
|
|
29
|
-
const ID
|
|
29
|
+
const ID = /^(AC-\d+)(\s*\(no hands-on\))?:\s*(.*)$/;
|
|
30
30
|
const criterion = (id, as = id[1]) => ({
|
|
31
31
|
id: as,
|
|
32
32
|
text: id[3],
|
|
@@ -59,24 +59,22 @@ const listUnder = (text, title, level) => {
|
|
|
59
59
|
};
|
|
60
60
|
/** The `- AC-<n>: …` list of `## Acceptance Criteria`, or `undefined` when the section is missing. */
|
|
61
61
|
const criteriaOf = (text) => listUnder(text, /^acceptance criteria$/i, 2)?.map((item) => {
|
|
62
|
-
const id = ID
|
|
62
|
+
const id = ID.exec(item);
|
|
63
63
|
return id === null ? {
|
|
64
64
|
id: "",
|
|
65
65
|
text: item
|
|
66
66
|
} : criterion(id);
|
|
67
67
|
});
|
|
68
|
-
/** The ID'd criteria of `text
|
|
69
|
-
const criterionIds = (text
|
|
70
|
-
/** The ID'd criteria of `text`
|
|
71
|
-
const handsOnIds = (text
|
|
68
|
+
/** The ID'd criteria of `text`. */
|
|
69
|
+
const criterionIds = (text) => (criteriaOf(text) ?? []).map((c) => c.id).filter((id) => id !== "");
|
|
70
|
+
/** The ID'd criteria of `text` a person can try by hand: none tagged `(no hands-on)`. */
|
|
71
|
+
const handsOnIds = (text) => {
|
|
72
72
|
const tagged = (criteriaOf(text) ?? []).filter((c) => c.noHandsOn).map((c) => c.id);
|
|
73
|
-
return criterionIds(text
|
|
73
|
+
return criterionIds(text).filter((id) => !tagged.includes(id));
|
|
74
74
|
};
|
|
75
|
-
/** `slice`'s IDs in `.gtd/REQUIREMENTS.md` as it is now; `undefined` for an unsliced change. */
|
|
76
|
-
const sliceIds = (slice) => slice === void 0 ? void 0 : criterionIds(read(".gtd/REQUIREMENTS.md") ?? "", slice);
|
|
77
75
|
/** Items numbered `AC-1`…, unless every one already carries its own distinct ID. */
|
|
78
76
|
const numbered = (items) => {
|
|
79
|
-
const ids = items.map((item) => ID
|
|
77
|
+
const ids = items.map((item) => ID.exec(item));
|
|
80
78
|
const own = ids.every((id) => id !== null) && new Set(ids.map((id) => id[1])).size === ids.length;
|
|
81
79
|
return items.map((item, i) => {
|
|
82
80
|
const id = ids[i];
|
|
@@ -213,13 +211,6 @@ const skipDesign = (stated) => stated === void 0 ? Promise.resolve(false) : scop
|
|
|
213
211
|
await impact();
|
|
214
212
|
return true;
|
|
215
213
|
});
|
|
216
|
-
/** After a gate that invites narrowing the requirements: an edit must leave valid criteria. */
|
|
217
|
-
const requireCriteriaIfEdited = () => {
|
|
218
|
-
const change = changes().get(REQUIREMENTS);
|
|
219
|
-
if (change === void 0) return;
|
|
220
|
-
if (change.status === "deleted") refuse(`gtd land: criteria-missing: ${REQUIREMENTS} must stay — narrow its criteria, never delete it`);
|
|
221
|
-
requireCriteria(REQUIREMENTS);
|
|
222
|
-
};
|
|
223
214
|
//#endregion
|
|
224
215
|
//#region src/workflows/feature/tier.ts
|
|
225
216
|
/** GTD_FLAG_RISK_TIER: pinned at process start, so a mid-process flip never moves a process. */
|
|
@@ -385,12 +376,12 @@ const requireReport = (ids, retest) => {
|
|
|
385
376
|
};
|
|
386
377
|
};
|
|
387
378
|
/**
|
|
388
|
-
* The
|
|
389
|
-
*
|
|
379
|
+
* The criteria a person can observe by using the change, by the judge: those
|
|
380
|
+
* tagged `(no hands-on)` are never asked about.
|
|
390
381
|
*/
|
|
391
|
-
const observable = async (
|
|
382
|
+
const observable = async () => {
|
|
392
383
|
const requirements = read(".gtd/REQUIREMENTS.md") ?? "";
|
|
393
|
-
const own = handsOnIds(requirements
|
|
384
|
+
const own = handsOnIds(requirements);
|
|
394
385
|
const criteria = (criteriaOf(requirements) ?? []).filter((c) => own.includes(c.id));
|
|
395
386
|
if (criteria.length === 0) return [];
|
|
396
387
|
const { answers } = await judge("observable", {
|
|
@@ -402,10 +393,9 @@ const observable = async (slice) => {
|
|
|
402
393
|
return own.filter((id) => !answered(answers[id], "no", UNOBSERVABLE_MIN_P));
|
|
403
394
|
};
|
|
404
395
|
const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
|
|
405
|
-
const testing = async (
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
label: retest ? "Re-testing the slice by hand" : "Testing the slice by hand",
|
|
396
|
+
const testing = async (ids, retest) => {
|
|
397
|
+
await agentWithSkills("testing", prototypeTestingPrompt(criterionIds(read(".gtd/REQUIREMENTS.md") ?? "").filter((id) => !ids.includes(id)), retest), {
|
|
398
|
+
label: retest ? "Re-testing the change by hand" : "Testing the change by hand",
|
|
409
399
|
file: PROTOTYPE,
|
|
410
400
|
model: coder(),
|
|
411
401
|
system: testerSystem(),
|
|
@@ -413,8 +403,8 @@ const testing = async (slice, ids, retest) => {
|
|
|
413
403
|
});
|
|
414
404
|
return requireReport(ids, retest);
|
|
415
405
|
};
|
|
416
|
-
const fixing = async (
|
|
417
|
-
await scope("fix", () => agentWithSkills("fixing", prototypeFixingPrompt(
|
|
406
|
+
const fixing = async (guard) => {
|
|
407
|
+
await scope("fix", () => agentWithSkills("fixing", prototypeFixingPrompt(), {
|
|
418
408
|
label: "Fixing what the hands-on test found",
|
|
419
409
|
file: PROTOTYPE,
|
|
420
410
|
model: coder(),
|
|
@@ -438,29 +428,29 @@ const askPerson = async (name, message, label) => {
|
|
|
438
428
|
return true;
|
|
439
429
|
};
|
|
440
430
|
/**
|
|
441
|
-
* Use the
|
|
442
|
-
* can observe, a fix turn takes each failure, and the
|
|
431
|
+
* Use the change by hand: a testing turn reports on every criterion a person
|
|
432
|
+
* can observe, a fix turn takes each failure, and the change is tested again —
|
|
443
433
|
* until the report is clean, or `prototypeRounds` fixes are spent and a person
|
|
444
|
-
* decides. A
|
|
445
|
-
* every fix turn, as it holds the implementation's.
|
|
434
|
+
* decides. A change with no such criterion is not tested by hand. `guard`
|
|
435
|
+
* holds every fix turn, as it holds the implementation's.
|
|
446
436
|
*/
|
|
447
|
-
const prototypeTest = async (
|
|
437
|
+
const prototypeTest = async (guard) => {
|
|
448
438
|
const cap = prototypeRounds();
|
|
449
439
|
if (cap === 0) return;
|
|
450
440
|
await scope("prototype", async () => {
|
|
451
|
-
const ids = await observable(
|
|
441
|
+
const ids = await observable();
|
|
452
442
|
if (ids.length === 0) return;
|
|
453
443
|
let fixes = 0;
|
|
454
444
|
let retest = false;
|
|
455
445
|
for (;;) {
|
|
456
|
-
const { unguided, open } = await testing(
|
|
446
|
+
const { unguided, open } = await testing(ids, retest);
|
|
457
447
|
retest = !unguided;
|
|
458
448
|
if (unguided) {
|
|
459
449
|
if (!await askPerson("unguided", prototypeUnguidedMessage(), "No way to test by hand")) return;
|
|
460
450
|
} else if (open.length === 0) return;
|
|
461
451
|
else if (fixes < cap && open.some((i) => i.verdict === "fail")) {
|
|
462
452
|
fixes++;
|
|
463
|
-
await fixing(
|
|
453
|
+
await fixing(guard);
|
|
464
454
|
} else {
|
|
465
455
|
if (!await askPerson("open", prototypeOpenMessage(open, fixes), "Hands-on test still failing")) return;
|
|
466
456
|
fixes = 0;
|
|
@@ -470,53 +460,6 @@ const prototypeTest = async (slice, guard) => {
|
|
|
470
460
|
});
|
|
471
461
|
};
|
|
472
462
|
//#endregion
|
|
473
|
-
//#region src/workflows/shared/continuation.ts
|
|
474
|
-
const phase = (name, steered) => ({
|
|
475
|
-
name,
|
|
476
|
-
spent: false,
|
|
477
|
-
count: 0,
|
|
478
|
-
stalled: 0,
|
|
479
|
-
steered
|
|
480
|
-
});
|
|
481
|
-
const own = (fn) => fn();
|
|
482
|
-
const continued = (fn) => scope("continue", fn);
|
|
483
|
-
const afresh = (fn) => scope("fresh", fn);
|
|
484
|
-
const setting = (name, least) => {
|
|
485
|
-
const raw = vars[name] ?? defaults[name];
|
|
486
|
-
const n = Number(raw);
|
|
487
|
-
if (raw.trim() === "" || !Number.isInteger(n) || n < least) refuse(`gtd: ${name} must be a whole number of at least ${least}, got ${JSON.stringify(raw)}`);
|
|
488
|
-
return n;
|
|
489
|
-
};
|
|
490
|
-
/**
|
|
491
|
-
* Run `turn` until one ends without a handoff note: each stopped turn is picked
|
|
492
|
-
* up by a continuation in a fresh conversation. Past `continuations` of them in
|
|
493
|
-
* the phase, or after `continuationStall` in a row that close nothing still
|
|
494
|
-
* open, the phase rests at `oversized` for a person; landing there grants a new round.
|
|
495
|
-
*/
|
|
496
|
-
const continuing = async (phase, turn) => {
|
|
497
|
-
const cap = setting("continuations", 0);
|
|
498
|
-
const stall = setting("continuationStall", 1);
|
|
499
|
-
let last = await turn(void 0, phase.spent ? afresh : own);
|
|
500
|
-
while (last.handoff !== void 0) {
|
|
501
|
-
phase.spent = true;
|
|
502
|
-
const from = last.handoff;
|
|
503
|
-
if (phase.count >= cap || phase.stalled >= stall) {
|
|
504
|
-
await human("oversized", {
|
|
505
|
-
message: oversizedMessage(phase.name, from, phase.count >= cap ? { cap } : { stall }, phase.steered !== void 0),
|
|
506
|
-
label: "Too big for its context",
|
|
507
|
-
acceptClean: true
|
|
508
|
-
});
|
|
509
|
-
phase.steered?.();
|
|
510
|
-
phase.count = 0;
|
|
511
|
-
phase.stalled = 0;
|
|
512
|
-
}
|
|
513
|
-
last = await turn(from, continued);
|
|
514
|
-
phase.count++;
|
|
515
|
-
phase.stalled = from.open.length === 0 || from.open.some((item) => !(last.handoff?.open ?? []).includes(item)) ? 0 : phase.stalled + 1;
|
|
516
|
-
}
|
|
517
|
-
return last.result;
|
|
518
|
-
};
|
|
519
|
-
//#endregion
|
|
520
463
|
//#region src/workflows/feature/refactor.ts
|
|
521
464
|
const REFACTOR_PACKAGE = REFACTOR;
|
|
522
465
|
const isCode = (path) => path !== ".gtd" && !path.startsWith(".gtd/");
|
|
@@ -662,7 +605,7 @@ const guardOf = ({ pattern, strays }, entry) => () => {
|
|
|
662
605
|
requireNoNewFlags(pattern, entry);
|
|
663
606
|
};
|
|
664
607
|
/** Derive, build, check and close one refactor lap; `feedback` is the collected review text of the lap before. */
|
|
665
|
-
const refactorLap = async (p, guard,
|
|
608
|
+
const refactorLap = async (p, guard, feedback) => {
|
|
666
609
|
const { pattern, strays, reference, note } = p;
|
|
667
610
|
await run("deriving", deriveScript([
|
|
668
611
|
...packageLines(strays, pattern, reference),
|
|
@@ -682,48 +625,32 @@ const refactorLap = async (p, guard, refactor, feedback) => {
|
|
|
682
625
|
label: "Deriving the refactor package",
|
|
683
626
|
file: REFACTOR_PACKAGE
|
|
684
627
|
});
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
628
|
+
await agentWithSkills("building", refactorBuildingPrompt({
|
|
629
|
+
pkg: REFACTOR_PACKAGE,
|
|
630
|
+
strays,
|
|
631
|
+
pattern,
|
|
632
|
+
reference,
|
|
633
|
+
feedback: feedback !== void 0,
|
|
634
|
+
note: note !== void 0
|
|
635
|
+
}), {
|
|
636
|
+
label: "Refactoring",
|
|
637
|
+
model: coder(),
|
|
638
|
+
system: builderSystem()
|
|
639
|
+
});
|
|
640
|
+
guard();
|
|
641
|
+
await healthy(async () => {
|
|
642
|
+
await agentWithSkills("fix.suite.fixing", refactorFixPrompt(strays, pattern), {
|
|
643
|
+
label: "Fixing the check",
|
|
644
|
+
file: FEEDBACK,
|
|
697
645
|
model: coder(),
|
|
698
646
|
system: builderSystem()
|
|
699
|
-
}));
|
|
700
|
-
const left = read(HANDOFF);
|
|
701
|
-
guard();
|
|
702
|
-
const snapshot = head();
|
|
703
|
-
for (const c of changes()) reached.add(c.path);
|
|
704
|
-
await healthy(async () => {
|
|
705
|
-
await agentWithSkills("fix.suite.fixing", refactorFixPrompt(strays, pattern), {
|
|
706
|
-
label: "Fixing the check",
|
|
707
|
-
file: FEEDBACK,
|
|
708
|
-
model: coder(),
|
|
709
|
-
system: builderSystem()
|
|
710
|
-
});
|
|
711
|
-
guard();
|
|
712
|
-
}, {
|
|
713
|
-
describeGuard: guard,
|
|
714
|
-
rules: refactorRules(strays, pattern)
|
|
715
647
|
});
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
snapshot,
|
|
721
|
-
note: left.trim(),
|
|
722
|
-
open: strays.filter((path) => !reached.has(path))
|
|
723
|
-
}
|
|
724
|
-
};
|
|
648
|
+
guard();
|
|
649
|
+
}, {
|
|
650
|
+
describeGuard: guard,
|
|
651
|
+
rules: refactorRules(strays, pattern)
|
|
725
652
|
});
|
|
726
|
-
await run("closing", removeScript([REFACTOR_PACKAGE
|
|
653
|
+
await run("closing", removeScript([REFACTOR_PACKAGE]), { label: "Closing out the refactor" });
|
|
727
654
|
};
|
|
728
655
|
const strayAccess = (strays) => ({ write: [
|
|
729
656
|
...strays,
|
|
@@ -737,7 +664,7 @@ const refactorFirst = async (shape, options = {}) => {
|
|
|
737
664
|
name: "refactor",
|
|
738
665
|
access: strayAccess(p.strays)
|
|
739
666
|
}, async () => {
|
|
740
|
-
await refactorLap(p, guardOf(p, head())
|
|
667
|
+
await refactorLap(p, guardOf(p, head()));
|
|
741
668
|
return true;
|
|
742
669
|
});
|
|
743
670
|
};
|
|
@@ -762,10 +689,9 @@ const reviewedRefactor = async (shape, options = {}) => {
|
|
|
762
689
|
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",
|
|
763
690
|
dropped: refactorDroppedRefusal
|
|
764
691
|
};
|
|
765
|
-
const refactor = phase("refactor");
|
|
766
692
|
let collected;
|
|
767
693
|
for (;;) {
|
|
768
|
-
await refactorLap(p, guard,
|
|
694
|
+
await refactorLap(p, guard, collected);
|
|
769
695
|
const outcome = await buildTail(entry, {
|
|
770
696
|
green: true,
|
|
771
697
|
guard,
|
|
@@ -795,32 +721,12 @@ const judgeSeam = (tip) => judgeDiffShape("judge", {
|
|
|
795
721
|
allow: (vars.seamAllow ?? defaults.seamAllow).split(",").map((glob) => glob.trim()).filter((glob) => glob !== ""),
|
|
796
722
|
exempt: tip.changes.filter((c) => c.status === "added").map((c) => c.path)
|
|
797
723
|
});
|
|
798
|
-
const naivePass = (retry
|
|
799
|
-
label:
|
|
724
|
+
const naivePass = (retry) => agentWithSkills("naive", naivePassPrompt(retry), {
|
|
725
|
+
label: retry === void 0 ? "Writing the naive pass" : "Rewriting the naive pass",
|
|
800
726
|
keep: true,
|
|
801
727
|
model: coder(),
|
|
802
728
|
system: builderSystem()
|
|
803
729
|
});
|
|
804
|
-
const handoffOf = (tip, slice) => {
|
|
805
|
-
const note = tip.changes.find((c) => c.path === ".gtd/HANDOFF.md" && c.after !== void 0);
|
|
806
|
-
if (note === void 0) return void 0;
|
|
807
|
-
return {
|
|
808
|
-
snapshot: tip.hash,
|
|
809
|
-
note: note.after.trim(),
|
|
810
|
-
open: unnamed(idsOf(REQUIREMENTS, slice), acceptanceTests(tip.changes))
|
|
811
|
-
};
|
|
812
|
-
};
|
|
813
|
-
/** A naive pass, continued until a pass ends without a handoff note: the snapshot is its kept tip. */
|
|
814
|
-
const naiveContinued = (retry, slice, naive) => continuing(naive, async (from, fresh) => {
|
|
815
|
-
const tip = await fresh(async () => {
|
|
816
|
-
await naivePass(retry, slice, from);
|
|
817
|
-
return kept("naive");
|
|
818
|
-
});
|
|
819
|
-
return {
|
|
820
|
-
result: tip,
|
|
821
|
-
handoff: handoffOf(tip, slice)
|
|
822
|
-
};
|
|
823
|
-
});
|
|
824
730
|
const IMPORTED = /(?:\bfrom|\bimport|\brequire\s*\(|\bmock\s*\()\s*\(?\s*(["'`])([^"'`\n]+)\1/g;
|
|
825
731
|
const EXTENSION = /\.[\w-]+$/;
|
|
826
732
|
const ALIAS = /^(?:[@~]\/|#)/;
|
|
@@ -856,7 +762,7 @@ const addedLines = (change) => {
|
|
|
856
762
|
return change.after.split("\n").filter((line) => !before.has(line)).join("\n");
|
|
857
763
|
};
|
|
858
764
|
const acceptanceTests = (changes) => changes.filter((c) => c.after !== void 0 && isCode$1(c.path) && isTestFile(c.path));
|
|
859
|
-
const idsOf = (file
|
|
765
|
+
const idsOf = (file) => criterionIds(read(file) ?? "");
|
|
860
766
|
const unnamed = (ids, tests) => {
|
|
861
767
|
const lines = tests.map(addedLines);
|
|
862
768
|
return ids.filter((id) => !lines.some((text) => named(id, text)));
|
|
@@ -866,9 +772,9 @@ const unnamed = (ids, tests) => {
|
|
|
866
772
|
* test line of the pass names, or a test importing a file the pass added —
|
|
867
773
|
* naive structure a reset and refactor replaces, which the tests must survive.
|
|
868
774
|
*/
|
|
869
|
-
const acceptanceFault = (file, tip
|
|
775
|
+
const acceptanceFault = (file, tip) => {
|
|
870
776
|
const tests = acceptanceTests(tip.changes);
|
|
871
|
-
const untested = unnamed(idsOf(file
|
|
777
|
+
const untested = unnamed(idsOf(file), tests);
|
|
872
778
|
if (untested.length > 0) return `acceptance-tests: no test of the naive pass names ${untested.join(", ")} — write a behaviour-level test for each criterion with its ID in the test or scenario name, e.g. "[${untested[0]}] …"`;
|
|
873
779
|
const added = tip.changes.filter((c) => c.status === "added" && isCode$1(c.path) && !isTestFile(c.path)).map((c) => c.path);
|
|
874
780
|
const reaching = tests.filter((test) => holdsCallSites(test.path)).flatMap((test) => {
|
|
@@ -878,8 +784,8 @@ const acceptanceFault = (file, tip, slice) => {
|
|
|
878
784
|
if (reaching.length > 0) return `acceptance-internals: these tests import files the naive pass added — test through code that existed before it (a public entry point, an existing module's exports):\n${reaching.join("\n")}`;
|
|
879
785
|
};
|
|
880
786
|
/** What a rebuild carries over from the naive pass `tip`: the IDs its tests name, and those test files. */
|
|
881
|
-
const naiveAcceptance = (tip
|
|
882
|
-
const ids = idsOf(REQUIREMENTS
|
|
787
|
+
const naiveAcceptance = (tip) => {
|
|
788
|
+
const ids = idsOf(REQUIREMENTS);
|
|
883
789
|
const perTest = acceptanceTests(tip.changes).map((c) => {
|
|
884
790
|
const lines = addedLines(c);
|
|
885
791
|
return {
|
|
@@ -949,31 +855,19 @@ const applied = async (tip) => {
|
|
|
949
855
|
return !wrote(".gtd/FEEDBACK.md") && codeChanges().length > 0;
|
|
950
856
|
};
|
|
951
857
|
/** `carried` limited to the criteria `.gtd/REQUIREMENTS.md` lists now; a file listing none narrows nothing. */
|
|
952
|
-
const settled = (carried
|
|
953
|
-
const listed = idsOf(REQUIREMENTS
|
|
954
|
-
if (carried === void 0) return listed;
|
|
858
|
+
const settled = (carried) => {
|
|
859
|
+
const listed = idsOf(REQUIREMENTS);
|
|
955
860
|
return listed.length === 0 ? carried : carried.filter((id) => listed.includes(id));
|
|
956
861
|
};
|
|
957
|
-
const build =
|
|
958
|
-
await
|
|
959
|
-
label:
|
|
862
|
+
const build = async (options) => {
|
|
863
|
+
await agentWithSkills("building", implementBuildingPrompt(options.reference, options.tier, options.amend, options.afterRefactor, flagModeOn(), options.rebuild), {
|
|
864
|
+
label: "Implementing",
|
|
960
865
|
model: coder(),
|
|
961
866
|
system: builderSystem(),
|
|
962
867
|
...options.afterRefactor === true ? { allowEmpty: true } : {}
|
|
963
|
-
})
|
|
868
|
+
});
|
|
964
869
|
requireCriteriaFrozen(REQUIREMENTS);
|
|
965
|
-
|
|
966
|
-
if (note === void 0) return { result: void 0 };
|
|
967
|
-
const ids = settled(options.rebuild?.acceptance?.ids, options.slice);
|
|
968
|
-
return {
|
|
969
|
-
result: void 0,
|
|
970
|
-
handoff: {
|
|
971
|
-
snapshot: head(),
|
|
972
|
-
note: note.trim(),
|
|
973
|
-
open: unnamed(ids, acceptanceTests(changesSince(start())))
|
|
974
|
-
}
|
|
975
|
-
};
|
|
976
|
-
});
|
|
870
|
+
};
|
|
977
871
|
async function implement(first, tier, amend) {
|
|
978
872
|
await implementing(typeof first === "object" && first !== null ? first : {
|
|
979
873
|
reference: first,
|
|
@@ -990,30 +884,19 @@ const implementing = (options) => scope("implement", async () => {
|
|
|
990
884
|
else requireCriteriaFrozen(REQUIREMENTS);
|
|
991
885
|
if (built && acceptance !== void 0) requireAcceptance({
|
|
992
886
|
...acceptance,
|
|
993
|
-
ids: settled(acceptance.ids
|
|
887
|
+
ids: settled(acceptance.ids)
|
|
994
888
|
}, since);
|
|
995
889
|
const keepsTests = built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0;
|
|
996
890
|
await green(keepsTests);
|
|
997
|
-
await prototypeTest(
|
|
891
|
+
await prototypeTest(keepsTests);
|
|
998
892
|
const ids = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
|
|
999
|
-
await closing(
|
|
893
|
+
await run("closing", removeScript([
|
|
894
|
+
REQUIREMENTS,
|
|
895
|
+
REFACTOR_NOTE,
|
|
896
|
+
PROTOTYPE
|
|
897
|
+
]), { label: "Closing out the implementation" });
|
|
1000
898
|
return ids;
|
|
1001
899
|
});
|
|
1002
|
-
const moreToBuild = (more) => typeof more === "function" ? more() : more === true;
|
|
1003
|
-
/** The last slice's closing, for a last slice a steer left with no criterion to build. */
|
|
1004
|
-
const closeOut = () => scope("implement", () => closing(false));
|
|
1005
|
-
/** A slice with `more` after it keeps the criteria and the slicing for the next one. */
|
|
1006
|
-
const closing = (more) => run("closing", removeScript(more ? [
|
|
1007
|
-
REFACTOR_NOTE,
|
|
1008
|
-
HANDOFF,
|
|
1009
|
-
PROTOTYPE
|
|
1010
|
-
] : [
|
|
1011
|
-
REQUIREMENTS,
|
|
1012
|
-
REFACTOR_NOTE,
|
|
1013
|
-
SLICES,
|
|
1014
|
-
HANDOFF,
|
|
1015
|
-
PROTOTYPE
|
|
1016
|
-
]), { label: "Closing out the implementation" });
|
|
1017
900
|
/** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
|
|
1018
901
|
const steer = async (retry) => {
|
|
1019
902
|
await human("steer", {
|
|
@@ -1044,8 +927,8 @@ const budget = () => {
|
|
|
1044
927
|
};
|
|
1045
928
|
};
|
|
1046
929
|
/** Sends a pass whose tests fall short or run red back (`undefined`: it goes on, an overruled one as it is). */
|
|
1047
|
-
const vetted = async (tip, vetting
|
|
1048
|
-
const fault = vetting.waived ? void 0 : acceptanceFault(REQUIREMENTS, tip
|
|
930
|
+
const vetted = async (tip, vetting) => {
|
|
931
|
+
const fault = vetting.waived ? void 0 : acceptanceFault(REQUIREMENTS, tip);
|
|
1049
932
|
if (fault !== void 0) {
|
|
1050
933
|
const verdict = await vetting.again({
|
|
1051
934
|
fault,
|
|
@@ -1079,16 +962,16 @@ const ruled = async (tip, again) => {
|
|
|
1079
962
|
tip
|
|
1080
963
|
} : next;
|
|
1081
964
|
};
|
|
1082
|
-
const decide = (
|
|
965
|
+
const decide = () => scope("seam", async () => {
|
|
1083
966
|
const vetting = {
|
|
1084
967
|
again: budget(),
|
|
1085
968
|
waived: false
|
|
1086
969
|
};
|
|
1087
|
-
const naive = phase("naive pass", requireCriteriaIfEdited);
|
|
1088
970
|
let retry;
|
|
1089
971
|
for (;;) {
|
|
1090
|
-
|
|
1091
|
-
const
|
|
972
|
+
await naivePass(retry);
|
|
973
|
+
const tip = kept("naive");
|
|
974
|
+
const outcome = await vetted(tip, vetting) ?? (flagModeOn() ? await ruled(tip, vetting.again) : {
|
|
1092
975
|
reference: tip.hash,
|
|
1093
976
|
tip
|
|
1094
977
|
});
|
|
@@ -1109,8 +992,8 @@ const refactorNote = async (reference, strays) => {
|
|
|
1109
992
|
if (others.length > 0 || note.trim() === "") refuse(`gtd land: refactor-note: write ${REFACTOR_NOTE} and change nothing else${others.length > 0 ? ` — revert these:\n${others.map((path) => ` - ${path}`).join("\n")}` : ""}`);
|
|
1110
993
|
return note;
|
|
1111
994
|
};
|
|
1112
|
-
const seamBuild = async (base
|
|
1113
|
-
const { reference, tip, shape } = await decide(
|
|
995
|
+
const seamBuild = async (base) => {
|
|
996
|
+
const { reference, tip, shape } = await decide();
|
|
1114
997
|
if (shape === void 0) return {
|
|
1115
998
|
reference,
|
|
1116
999
|
base,
|
|
@@ -1125,107 +1008,31 @@ const seamBuild = async (base, slice$1 = {}) => {
|
|
|
1125
1008
|
return {
|
|
1126
1009
|
reference,
|
|
1127
1010
|
base,
|
|
1128
|
-
rebuild: { acceptance: naiveAcceptance(tip
|
|
1011
|
+
rebuild: { acceptance: naiveAcceptance(tip) }
|
|
1129
1012
|
};
|
|
1130
1013
|
}
|
|
1131
1014
|
const strays = refactorStrays(shape);
|
|
1132
1015
|
if (strays.length === 0) return {
|
|
1133
1016
|
reference,
|
|
1134
1017
|
base,
|
|
1135
|
-
rebuild: { acceptance: naiveAcceptance(tip
|
|
1018
|
+
rebuild: { acceptance: naiveAcceptance(tip) }
|
|
1136
1019
|
};
|
|
1137
1020
|
const note = await scope("seam", () => refactorNote(reference, strays));
|
|
1138
|
-
const refactored = await
|
|
1021
|
+
const refactored = await reviewedRefactor(shape, {
|
|
1139
1022
|
reference,
|
|
1140
1023
|
note
|
|
1141
|
-
})
|
|
1024
|
+
});
|
|
1142
1025
|
return {
|
|
1143
1026
|
reference,
|
|
1144
1027
|
base: head(),
|
|
1145
1028
|
refactored,
|
|
1146
1029
|
rebuild: {
|
|
1147
1030
|
note,
|
|
1148
|
-
acceptance: naiveAcceptance(tip
|
|
1031
|
+
acceptance: naiveAcceptance(tip)
|
|
1149
1032
|
}
|
|
1150
1033
|
};
|
|
1151
1034
|
};
|
|
1152
1035
|
//#endregion
|
|
1153
|
-
//#region src/workflows/feature/slicing.ts
|
|
1154
|
-
const ID = /^(AC-\d+)(?![\w-])/;
|
|
1155
|
-
/** The `sliceMaxCriteria` setting. Anything but a positive integer fails rather than guess a cap. */
|
|
1156
|
-
const sliceCap = () => {
|
|
1157
|
-
const value = vars.sliceMaxCriteria ?? defaults.sliceMaxCriteria;
|
|
1158
|
-
if (!/^[1-9]\d*$/.test(value)) refuse(`gtd: sliceMaxCriteria must be a positive integer, got ${JSON.stringify(value)}`);
|
|
1159
|
-
return Number(value);
|
|
1160
|
-
};
|
|
1161
|
-
/** Each `## ` heading of `text` with the list items under it; an item not led by an ID keeps an empty one. */
|
|
1162
|
-
const slicesOf = (text) => {
|
|
1163
|
-
const slices = [];
|
|
1164
|
-
let open = false;
|
|
1165
|
-
for (const { line, fenced } of markFenced(text)) {
|
|
1166
|
-
if (fenced) continue;
|
|
1167
|
-
const heading = HEADING.exec(line);
|
|
1168
|
-
if (heading !== null) {
|
|
1169
|
-
open = heading[1] === "##";
|
|
1170
|
-
if (open) slices.push({
|
|
1171
|
-
title: heading[2],
|
|
1172
|
-
ids: []
|
|
1173
|
-
});
|
|
1174
|
-
continue;
|
|
1175
|
-
}
|
|
1176
|
-
const item = ITEM.exec(line);
|
|
1177
|
-
if (item !== null && open) slices.at(-1).ids.push(ID.exec(item[1])?.[1] ?? "");
|
|
1178
|
-
}
|
|
1179
|
-
return slices;
|
|
1180
|
-
};
|
|
1181
|
-
const twice = (list) => [...new Set(list.filter((id, i) => list.indexOf(id) !== i))];
|
|
1182
|
-
/** Why `slices` fail to put each of `ids` in exactly one slice of at most `cap`, or `undefined`. */
|
|
1183
|
-
const sliceFault = (slices, ids, cap) => {
|
|
1184
|
-
const listed = slices.flatMap((s) => s.ids);
|
|
1185
|
-
if (slices.length === 0) return `no "## " slice`;
|
|
1186
|
-
const empty = slices.filter((s) => s.ids.length === 0).map((s) => s.title);
|
|
1187
|
-
if (empty.length > 0) return `a slice with no criterion: ${empty.join(", ")}`;
|
|
1188
|
-
if (listed.includes("")) return "a list item not led by a criterion ID";
|
|
1189
|
-
const over = slices.filter((s) => s.ids.length > cap).map((s) => s.title);
|
|
1190
|
-
if (over.length > 0) return `more than ${cap} criteria in: ${over.join(", ")}`;
|
|
1191
|
-
const repeated = twice(listed);
|
|
1192
|
-
if (repeated.length > 0) return `an ID in more than one slice: ${repeated.join(", ")}`;
|
|
1193
|
-
const unknown = listed.filter((id) => !ids.includes(id));
|
|
1194
|
-
if (unknown.length > 0) return `an ID ${REQUIREMENTS} does not list: ${unknown.join(", ")}`;
|
|
1195
|
-
const missing = ids.filter((id) => !listed.includes(id));
|
|
1196
|
-
if (missing.length > 0) return `no slice for: ${missing.join(", ")}`;
|
|
1197
|
-
};
|
|
1198
|
-
/** Refuse a turn whose `.gtd/SLICES.md` does not put each of `ids` in exactly one slice of at most `cap`. */
|
|
1199
|
-
const requireSlices = (ids, cap) => {
|
|
1200
|
-
const slices = slicesOf(read(".gtd/SLICES.md") ?? "");
|
|
1201
|
-
const why = sliceFault(slices, ids, cap);
|
|
1202
|
-
if (why !== void 0) refuse(`gtd land: slices: ${SLICES} has ${why} — list each slice as a "## <title>" heading over "- AC-<n>" items, every criterion of ${REQUIREMENTS} in exactly one slice, at most ${cap} per slice`);
|
|
1203
|
-
return slices;
|
|
1204
|
-
};
|
|
1205
|
-
/**
|
|
1206
|
-
* The slices to build, in order. Criteria within `sliceMaxCriteria` are one
|
|
1207
|
-
* slice and no turn; above it `design.slicing` groups them.
|
|
1208
|
-
*/
|
|
1209
|
-
const slicing = async () => {
|
|
1210
|
-
const all = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
|
|
1211
|
-
const cap = sliceCap();
|
|
1212
|
-
if (all.length <= cap) return [{
|
|
1213
|
-
title: "",
|
|
1214
|
-
ids: all
|
|
1215
|
-
}];
|
|
1216
|
-
return scope("design", async () => {
|
|
1217
|
-
await agentWithSkills("slicing", slicingPrompt(cap), {
|
|
1218
|
-
label: "Slicing the change",
|
|
1219
|
-
file: SLICES,
|
|
1220
|
-
model: planner(),
|
|
1221
|
-
system: designSystem()
|
|
1222
|
-
});
|
|
1223
|
-
return requireSlices(all, cap);
|
|
1224
|
-
});
|
|
1225
|
-
};
|
|
1226
|
-
/** Slice `i`'s criteria; `undefined` when `slices` is the one slice of an unsliced change. */
|
|
1227
|
-
const sliceCriteria = (slices, i) => slices.length === 1 ? void 0 : i < slices.length - 1 ? { only: slices[i].ids } : { except: slices.slice(0, -1).flatMap((s) => s.ids) };
|
|
1228
|
-
//#endregion
|
|
1229
1036
|
//#region src/workflows/feature/feature.ts
|
|
1230
1037
|
/** Revert the sketch that started the process out of the working tree; its intent survives in history. */
|
|
1231
1038
|
const unwind = () => {
|
|
@@ -1233,94 +1040,37 @@ const unwind = () => {
|
|
|
1233
1040
|
return run("unwind", revertScript(commit, FEEDBACK, unwindFailure(commit)), { label: "Unwinding your input" });
|
|
1234
1041
|
};
|
|
1235
1042
|
/**
|
|
1236
|
-
*
|
|
1237
|
-
*
|
|
1043
|
+
* A naive pass (judged only with `flagMode` on), then implement and the
|
|
1044
|
+
* quality lap, then the review; design must have run since `firstBase`.
|
|
1238
1045
|
*/
|
|
1239
1046
|
const buildFeature = async (firstBase) => {
|
|
1240
1047
|
const tier = riskTierOn() ? recordRisk() : void 0;
|
|
1241
|
-
const slices = await slicing();
|
|
1242
1048
|
const handedOut = new Set(listedIds());
|
|
1243
|
-
const
|
|
1244
|
-
const
|
|
1245
|
-
const
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
});
|
|
1260
|
-
}
|
|
1049
|
+
const seam = await seamBuild(firstBase);
|
|
1050
|
+
const used = gather(handedOut);
|
|
1051
|
+
const closed = await implementing({
|
|
1052
|
+
reference: seam.reference,
|
|
1053
|
+
tier,
|
|
1054
|
+
afterRefactor: seam.refactored,
|
|
1055
|
+
naive: seam.land === true ? seam.reference : void 0,
|
|
1056
|
+
rebuild: seam.rebuild
|
|
1057
|
+
});
|
|
1058
|
+
const carried = seam.rebuild?.acceptance?.tests ?? [];
|
|
1059
|
+
const base = seam.refactored === true && !changesSince(seam.base).some((c) => isCode$1(c.path) && !carried.includes(c.path)) ? firstBase : seam.base;
|
|
1060
|
+
const outcome = await reviewedTail(base, base);
|
|
1061
|
+
await featureFeedback(outcome, base, {
|
|
1062
|
+
closed,
|
|
1063
|
+
used
|
|
1064
|
+
});
|
|
1261
1065
|
};
|
|
1262
1066
|
const listedIds = () => criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
|
|
1263
1067
|
const gather = (handedOut) => {
|
|
1264
1068
|
for (const id of listedIds()) handedOut.add(id);
|
|
1265
1069
|
return [...handedOut];
|
|
1266
1070
|
};
|
|
1267
|
-
/**
|
|
1268
|
-
const
|
|
1269
|
-
|
|
1270
|
-
criteria: build.criteria,
|
|
1271
|
-
refactor: build.refactor
|
|
1272
|
-
});
|
|
1273
|
-
const used = gather(build.handedOut);
|
|
1274
|
-
await slice(build.feature, async () => {
|
|
1275
|
-
const closed = await implementing({
|
|
1276
|
-
reference: seam.reference,
|
|
1277
|
-
tier: build.tier,
|
|
1278
|
-
afterRefactor: seam.refactored,
|
|
1279
|
-
naive: seam.land === true ? seam.reference : void 0,
|
|
1280
|
-
rebuild: seam.rebuild,
|
|
1281
|
-
slice: build.criteria,
|
|
1282
|
-
more: build.more
|
|
1283
|
-
});
|
|
1284
|
-
const more = build.more();
|
|
1285
|
-
const carried = seam.rebuild?.acceptance?.tests ?? [];
|
|
1286
|
-
const base = seam.refactored === true && !changesSince(seam.base).some((c) => isCode$1(c.path) && !carried.includes(c.path)) ? sliceBase : seam.base;
|
|
1287
|
-
if (more) {
|
|
1288
|
-
const guard = () => requireCriteriaFrozen(REQUIREMENTS);
|
|
1289
|
-
const report = await tail(async (_, report) => report, {
|
|
1290
|
-
green: true,
|
|
1291
|
-
lensBase: base,
|
|
1292
|
-
guard,
|
|
1293
|
-
guardRests: false
|
|
1294
|
-
});
|
|
1295
|
-
build.laps.push({
|
|
1296
|
-
slice: build.number,
|
|
1297
|
-
report
|
|
1298
|
-
});
|
|
1299
|
-
return;
|
|
1300
|
-
}
|
|
1301
|
-
const reviewBase = build.reviewBase ?? base;
|
|
1302
|
-
const outcome = await reviewedTail(reviewBase, base, {
|
|
1303
|
-
slice: build.number,
|
|
1304
|
-
laps: build.laps
|
|
1305
|
-
});
|
|
1306
|
-
await featureFeedback(outcome, base, {
|
|
1307
|
-
closed,
|
|
1308
|
-
used
|
|
1309
|
-
});
|
|
1310
|
-
});
|
|
1311
|
-
};
|
|
1312
|
-
/**
|
|
1313
|
-
* The quality lap of slice `last.slice`, then a reviewer's walkthrough since
|
|
1314
|
-
* `base` for the human, carrying what earlier slices' `laps` left too.
|
|
1315
|
-
*/
|
|
1316
|
-
const reviewedTail = (base, lensBase, last = {
|
|
1317
|
-
slice: "",
|
|
1318
|
-
laps: []
|
|
1319
|
-
}, lap = "first") => tail((escalations, report) => humanReview(base, escalations, {
|
|
1320
|
-
report: last.laps.length === 0 ? report : joinReports([...last.laps, {
|
|
1321
|
-
slice: last.slice,
|
|
1322
|
-
report
|
|
1323
|
-
}]),
|
|
1071
|
+
/** The quality lap, then a reviewer's walkthrough since `base` for the human. */
|
|
1072
|
+
const reviewedTail = (base, lensBase, lap = "first") => tail((escalations, report) => humanReview(base, escalations, {
|
|
1073
|
+
report,
|
|
1324
1074
|
walkthrough: true,
|
|
1325
1075
|
lap
|
|
1326
1076
|
}), {
|
|
@@ -1345,7 +1095,7 @@ const featureFeedback = async (first, lensBase, ids) => {
|
|
|
1345
1095
|
tier: riskTierOn() ? recordRisk() : void 0,
|
|
1346
1096
|
amend: true
|
|
1347
1097
|
});
|
|
1348
|
-
outcome = await reviewedTail(outcome.base, lensBase,
|
|
1098
|
+
outcome = await reviewedTail(outcome.base, lensBase, "feedback");
|
|
1349
1099
|
}
|
|
1350
1100
|
};
|
|
1351
1101
|
/** Design — skipped when the sketch's own criteria are judged clear — then `buildFeature`. */
|
|
@@ -1353,7 +1103,7 @@ const planAndBuild = async (firstBase, stated) => {
|
|
|
1353
1103
|
if (!await skipDesign(riskTierOn() ? void 0 : stated)) await design(firstBase);
|
|
1354
1104
|
await buildFeature(firstBase);
|
|
1355
1105
|
};
|
|
1356
|
-
/** After a tail that skipped planning: stop on sign-off, re-plan on feedback.
|
|
1106
|
+
/** After a tail that skipped planning: stop on sign-off, re-plan on feedback. */
|
|
1357
1107
|
const afterTail = async (first) => {
|
|
1358
1108
|
let outcome = first;
|
|
1359
1109
|
while (outcome.verdict === "feedback") {
|
|
@@ -1375,7 +1125,6 @@ const ordinaryStart = async () => {
|
|
|
1375
1125
|
});
|
|
1376
1126
|
refusePendingHandOff();
|
|
1377
1127
|
flagModeOn();
|
|
1378
|
-
sliceCap();
|
|
1379
1128
|
prototypeRounds();
|
|
1380
1129
|
const stated = statedCriteria(changes(), head());
|
|
1381
1130
|
await unwind();
|
|
@@ -1392,4 +1141,4 @@ const feature = shipped(ordinaryStart);
|
|
|
1392
1141
|
//#region src/workflows/feature/index.ts
|
|
1393
1142
|
var feature_default = feature;
|
|
1394
1143
|
//#endregion
|
|
1395
|
-
export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail,
|
|
1144
|
+
export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, collecting, criteriaOf, criterionIds, feature_default as default, defaults, design, envDefaults, feature, fixNits, handsOnIds, hasImpact, holdsCallSites, implement, implementing, isTestFile, judgeSeam, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireImpact, requireImpactOnly, requireNoNewFlags, requireNoTestFiles, requireStableIds, requireTestCallSitesOnly, requireWithin, reviewedRefactor, seamBuild, skills, skipDesign, statedCriteria, steering, summary, triage, unstableIds, unwind };
|