@pmelab/gtd 38.2.0 → 40.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 +66 -58
- package/dist/chunks/{reserved-C3PgP7XB.mjs → reserved-Qt5FM6Ia.mjs} +29 -10
- package/dist/chunks/{tail-Bkxki2nK.mjs → tail-tAHx_AHe.mjs} +1 -1
- package/dist/gtd.bundle.mjs +14 -5
- package/dist/testkit.mjs +14 -5
- package/dist/workflow-kit.mjs +12 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +98 -37
- 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 +136 -45
- package/package.json +1 -1
- package/skills/authoring/SKILL.md +3 -2
- package/src/cli/Cli.ts +4 -3
- package/src/cli/Metrics.test.ts +31 -0
- package/src/cli/Metrics.ts +8 -0
- package/src/workflow-kit/health.ts +15 -1
- package/src/workflows/cleanup/cleanup.test.ts +2 -0
- package/src/workflows/feature/coverage.feature +5 -5
- package/src/workflows/feature/criteria.test.ts +20 -1
- package/src/workflows/feature/criteria.ts +24 -0
- package/src/workflows/feature/fast-path.feature +4 -4
- package/src/workflows/feature/feature.ts +82 -22
- package/src/workflows/feature/flag-mode.feature +2 -2
- package/src/workflows/feature/one-step-land.feature +4 -4
- package/src/workflows/feature/planning.ts +3 -2
- package/src/workflows/feature/quality-unresolved.feature +6 -6
- package/src/workflows/feature/refactor-absorbs.feature +3 -3
- package/src/workflows/feature/refactor-first.feature +5 -5
- package/src/workflows/feature/refactor-rebuild.feature +2 -2
- package/src/workflows/feature/refactor-review.feature +8 -8
- package/src/workflows/feature/refactor.test.ts +4 -0
- package/src/workflows/feature/review-gate.feature +271 -0
- package/src/workflows/feature/seam-rejection.feature +4 -4
- package/src/workflows/feature/seam.feature +2 -2
- package/src/workflows/feature/seam.ts +17 -7
- package/src/workflows/feature/slicing.feature +63 -4
- package/src/workflows/rollout/rollout.feature +16 -0
- package/src/workflows/rollout/rollout.test.ts +2 -0
- package/src/workflows/shared/health.test.ts +2 -0
- package/src/workflows/shared/health.ts +34 -5
- package/src/workflows/shared/recheck.feature +60 -0
- package/src/workflows/shared/text.ts +7 -5
- package/src/workflows/review/review-door.feature +0 -121
|
@@ -1,7 +1,7 @@
|
|
|
1
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, slice, start, vars, wrote } from "../flows.mjs";
|
|
2
2
|
import { lensEntriesOf, review as review$1 } from "../workflow-kit.mjs";
|
|
3
|
-
import { $ as naiveApplyFailure, A as agentWithSkills, B as coverageQuestion, C as fixCheck, Ct as testFileGlobs, D as reviewMissing, E as planner, F as buildReviewTriageMessage, G as designSystem, H as criterionLine, I as builderSystem, K as designTriagePrompt, L as clearCriteriaMessage, M as buildReviewAwaitAfterDeferMessage, N as buildReviewCollectingPrompt, P as buildReviewFixNitsPrompt, Q as implementFixPrompt, R as clearCriteriaQuestion, S as coder, St as envDefaults, T as fixRisks, U as deferNotesScript, V as coveringPrompt, W as designGateAnswerMessage, X as idleMessage, Z as implementBuildingPrompt, _ as REFACTOR_NOTE, _t as startGateBlockedMessage, a as flagModeOn, at as refactorDroppedRefusal, b as SLICES, bt as unwindFailure, ct as refactorRules, d as guarded, et as naivePassPrompt, f as healthy, ft as reviewNotesCapture, g as REFACTOR, gt as slicingPrompt, h as QUALITY, ht as seamSteerMessage, i as skills, it as refactorBuildingPrompt, j as buildReviewAnswerQuestionsPrompt, k as reviewing, lt as reviewEditNotesCapture, m as HANDOFF, mt as seamByHandMessage, n as summary, nt as oversizedMessage, ot as refactorFixPrompt, p as FEEDBACK, pt as reviewerSystem, r as access, rt as qualityUnresolvedMessage, s as shipped, st as refactorNotePrompt, t as steering, tt as normaliseCriteriaScript, u as gate, ut as reviewEditsCapture, v as REQUIREMENTS, vt as uncoveredMessage, x as awaitReview, xt as defaults, y as REVIEW, yt as unwindFailedMessage, z as coverageMessage } from "../chunks/reserved-
|
|
4
|
-
import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail } from "../chunks/tail-
|
|
3
|
+
import { $ as naiveApplyFailure, A as agentWithSkills, B as coverageQuestion, C as fixCheck, Ct as testFileGlobs, D as reviewMissing, E as planner, F as buildReviewTriageMessage, G as designSystem, H as criterionLine, I as builderSystem, K as designTriagePrompt, L as clearCriteriaMessage, M as buildReviewAwaitAfterDeferMessage, N as buildReviewCollectingPrompt, P as buildReviewFixNitsPrompt, Q as implementFixPrompt, R as clearCriteriaQuestion, S as coder, St as envDefaults, T as fixRisks, U as deferNotesScript, V as coveringPrompt, W as designGateAnswerMessage, X as idleMessage, Z as implementBuildingPrompt, _ as REFACTOR_NOTE, _t as startGateBlockedMessage, a as flagModeOn, at as refactorDroppedRefusal, b as SLICES, bt as unwindFailure, ct as refactorRules, d as guarded, et as naivePassPrompt, f as healthy, ft as reviewNotesCapture, g as REFACTOR, gt as slicingPrompt, h as QUALITY, ht as seamSteerMessage, i as skills, it as refactorBuildingPrompt, j as buildReviewAnswerQuestionsPrompt, k as reviewing, lt as reviewEditNotesCapture, m as HANDOFF, mt as seamByHandMessage, n as summary, nt as oversizedMessage, ot as refactorFixPrompt, p as FEEDBACK, pt as reviewerSystem, r as access, rt as qualityUnresolvedMessage, s as shipped, st as refactorNotePrompt, t as steering, tt as normaliseCriteriaScript, u as gate, ut as reviewEditsCapture, v as REQUIREMENTS, vt as uncoveredMessage, x as awaitReview, xt as defaults, y as REVIEW, yt as unwindFailedMessage, z as coverageMessage } from "../chunks/reserved-Qt5FM6Ia.mjs";
|
|
4
|
+
import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail } from "../chunks/tail-tAHx_AHe.mjs";
|
|
5
5
|
import { posix } from "node:path";
|
|
6
6
|
//#region src/workflows/shared/fences.ts
|
|
7
7
|
const markFenced = (text) => {
|
|
@@ -112,6 +112,19 @@ const requireCriteria = (file) => {
|
|
|
112
112
|
if (criteria.length > 0 && unnamed.length === 0 && twice.length === 0) return;
|
|
113
113
|
refuse(`gtd land: acceptance-criteria: ${file} has ${criteria.length === 0 ? `no "## Acceptance Criteria" list` : unnamed.length > 0 ? `${unnamed.length} criterion without an ID:\n${unnamed.map((c) => ` - ${c.text}`).join("\n")}` : `duplicate IDs: ${[...new Set(twice)].join(", ")}`} — list every criterion under "## Acceptance Criteria" as "- AC-<n>: <checkable criterion>", each ID unique and never reused`);
|
|
114
114
|
};
|
|
115
|
+
const idNumber = (id) => Number(id.slice(3));
|
|
116
|
+
/** Why `listed` renumbers or reuses an earlier ID; `undefined` when it does neither. */
|
|
117
|
+
const unstableIds = (listed, ids) => {
|
|
118
|
+
const top = Math.max(0, ...ids.used.map(idNumber));
|
|
119
|
+
const reused = listed.filter((id) => !ids.closed.includes(id) && idNumber(id) <= top);
|
|
120
|
+
if (reused.length === 0) return void 0;
|
|
121
|
+
return `numbers ${reused.join(", ")} at or below AC-${top}, an ID already handed out — keep a built criterion's ID (reword it in place when the review changes it), and number new ones from AC-${top + 1}`;
|
|
122
|
+
};
|
|
123
|
+
/** Refuse criteria in `file` that renumber or reuse an earlier ID. */
|
|
124
|
+
const requireStableIds = (file, ids) => {
|
|
125
|
+
const why = unstableIds(criterionIds(read(file) ?? ""), ids);
|
|
126
|
+
if (why !== void 0) refuse(`gtd land: criteria-ids: ${file} ${why}`);
|
|
127
|
+
};
|
|
115
128
|
/** Refuse a turn that changed the acceptance criteria in `file`: after the design gate only another design lap may. */
|
|
116
129
|
const requireCriteriaFrozen = (file) => {
|
|
117
130
|
const change = changes().get(file);
|
|
@@ -267,13 +280,14 @@ const noteGate = async (file, answer) => {
|
|
|
267
280
|
if (!hasThreadFor("agent", file)) requireAnswers(file);
|
|
268
281
|
return true;
|
|
269
282
|
};
|
|
270
|
-
/** Triage the sketch since `base` into requirements, until no product question is open. */
|
|
271
|
-
const design = (base) => scope("design", async () => {
|
|
283
|
+
/** Triage the sketch since `base` into requirements, until no product question is open; `guard` holds every round's criteria. */
|
|
284
|
+
const design = (base, guard) => scope("design", async () => {
|
|
272
285
|
do {
|
|
273
286
|
await triage(base);
|
|
274
287
|
requireProgress(REQUIREMENTS);
|
|
275
288
|
requireReplies(REQUIREMENTS);
|
|
276
289
|
requireCriteria(REQUIREMENTS);
|
|
290
|
+
guard?.();
|
|
277
291
|
} while (await noteGate(REQUIREMENTS, answerProductQuestions));
|
|
278
292
|
});
|
|
279
293
|
//#endregion
|
|
@@ -1085,27 +1099,31 @@ const build = (options) => continuing(phase("build", requireCriteriaIfEdited), a
|
|
|
1085
1099
|
}
|
|
1086
1100
|
};
|
|
1087
1101
|
});
|
|
1088
|
-
function implement(first, tier, amend) {
|
|
1089
|
-
|
|
1102
|
+
async function implement(first, tier, amend) {
|
|
1103
|
+
await implementing(typeof first === "object" && first !== null ? first : {
|
|
1090
1104
|
reference: first,
|
|
1091
1105
|
tier,
|
|
1092
1106
|
amend
|
|
1093
|
-
};
|
|
1094
|
-
return scope("implement", async () => {
|
|
1095
|
-
const since = head();
|
|
1096
|
-
const acceptance = options.rebuild?.acceptance;
|
|
1097
|
-
const built = options.naive === void 0 || !await applied(options.naive);
|
|
1098
|
-
if (built) await build(options);
|
|
1099
|
-
else requireCriteriaFrozen(REQUIREMENTS);
|
|
1100
|
-
if (built && acceptance !== void 0) requireAcceptance({
|
|
1101
|
-
...acceptance,
|
|
1102
|
-
ids: settled(acceptance.ids, options.slice)
|
|
1103
|
-
}, since);
|
|
1104
|
-
await green(built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0);
|
|
1105
|
-
await requireCoverage(options.base, options.slice);
|
|
1106
|
-
await closing(options.more === true);
|
|
1107
1107
|
});
|
|
1108
1108
|
}
|
|
1109
|
+
/** `implement`, resolving with the criterion IDs the requirements listed when it closed. */
|
|
1110
|
+
const implementing = (options) => scope("implement", async () => {
|
|
1111
|
+
const since = head();
|
|
1112
|
+
const acceptance = options.rebuild?.acceptance;
|
|
1113
|
+
const built = options.naive === void 0 || !await applied(options.naive);
|
|
1114
|
+
if (built) await build(options);
|
|
1115
|
+
else requireCriteriaFrozen(REQUIREMENTS);
|
|
1116
|
+
if (built && acceptance !== void 0) requireAcceptance({
|
|
1117
|
+
...acceptance,
|
|
1118
|
+
ids: settled(acceptance.ids, options.slice)
|
|
1119
|
+
}, since);
|
|
1120
|
+
await green(built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0);
|
|
1121
|
+
await requireCoverage(options.base, options.slice);
|
|
1122
|
+
const ids = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
|
|
1123
|
+
await closing(moreToBuild(options.more));
|
|
1124
|
+
return ids;
|
|
1125
|
+
});
|
|
1126
|
+
const moreToBuild = (more) => typeof more === "function" ? more() : more === true;
|
|
1109
1127
|
/** The last slice's closing, for a last slice a steer left with no criterion to build. */
|
|
1110
1128
|
const closeOut = () => scope("implement", () => closing(false));
|
|
1111
1129
|
/** A slice with `more` after it keeps the criteria and the slicing for the next one. */
|
|
@@ -1340,32 +1358,37 @@ const unwind = () => {
|
|
|
1340
1358
|
const buildFeature = async (firstBase) => {
|
|
1341
1359
|
const tier = riskTierOn() ? recordRisk() : void 0;
|
|
1342
1360
|
const slices = await slicing();
|
|
1361
|
+
const handedOut = new Set(listedIds());
|
|
1343
1362
|
const one = slices.length === 1;
|
|
1363
|
+
const built = (i) => sliceIds(sliceCriteria(slices, i))?.length !== 0;
|
|
1344
1364
|
for (const i of slices.keys()) {
|
|
1345
|
-
|
|
1346
|
-
const criteria = sliceCriteria(slices, i);
|
|
1347
|
-
if (sliceIds(criteria)?.length === 0) {
|
|
1348
|
-
if (!more) await closeOut();
|
|
1349
|
-
continue;
|
|
1350
|
-
}
|
|
1365
|
+
if (!built(i)) continue;
|
|
1351
1366
|
const n = one ? "" : `-${i + 1}`;
|
|
1352
1367
|
await buildSlice(i === 0 ? firstBase : head(), {
|
|
1353
1368
|
tier,
|
|
1354
|
-
criteria,
|
|
1369
|
+
criteria: sliceCriteria(slices, i),
|
|
1355
1370
|
refactor: `refactor${n}`,
|
|
1356
1371
|
feature: `feature${n}`,
|
|
1357
|
-
more
|
|
1372
|
+
more: () => slices.some((_, j) => j > i && built(j)),
|
|
1373
|
+
reviewBase: one ? void 0 : firstBase,
|
|
1374
|
+
handedOut
|
|
1358
1375
|
});
|
|
1359
1376
|
}
|
|
1360
1377
|
};
|
|
1361
|
-
|
|
1378
|
+
const listedIds = () => criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
|
|
1379
|
+
const gather = (handedOut) => {
|
|
1380
|
+
for (const id of listedIds()) handedOut.add(id);
|
|
1381
|
+
return [...handedOut];
|
|
1382
|
+
};
|
|
1383
|
+
/** A naive pass (judged only with `flagMode` on), then implement and the quality lap; after the last slice, the review. */
|
|
1362
1384
|
const buildSlice = async (sliceBase, build) => {
|
|
1363
1385
|
const seam = await seamBuild(sliceBase, {
|
|
1364
1386
|
criteria: build.criteria,
|
|
1365
1387
|
refactor: build.refactor
|
|
1366
1388
|
});
|
|
1389
|
+
const used = gather(build.handedOut);
|
|
1367
1390
|
await slice(build.feature, async () => {
|
|
1368
|
-
await
|
|
1391
|
+
const closed = await implementing({
|
|
1369
1392
|
reference: seam.reference,
|
|
1370
1393
|
tier: build.tier,
|
|
1371
1394
|
afterRefactor: seam.refactored,
|
|
@@ -1375,14 +1398,23 @@ const buildSlice = async (sliceBase, build) => {
|
|
|
1375
1398
|
base: build.criteria === void 0 ? void 0 : sliceBase,
|
|
1376
1399
|
more: build.more
|
|
1377
1400
|
});
|
|
1401
|
+
const more = build.more();
|
|
1378
1402
|
const carried = seam.rebuild?.acceptance?.tests ?? [];
|
|
1379
1403
|
const base = seam.refactored === true && !changesSince(seam.base).some((c) => isCode$1(c.path) && !carried.includes(c.path)) ? sliceBase : seam.base;
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1404
|
+
if (more) {
|
|
1405
|
+
const guard = () => requireCriteriaFrozen(REQUIREMENTS);
|
|
1406
|
+
await tail(settleQuality, {
|
|
1407
|
+
green: true,
|
|
1408
|
+
lensBase: base,
|
|
1409
|
+
guard,
|
|
1410
|
+
guardRests: false
|
|
1411
|
+
});
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
const reviewBase = build.reviewBase ?? base;
|
|
1415
|
+
await featureFeedback(await reviewedTail(reviewBase, base), base, {
|
|
1416
|
+
closed,
|
|
1417
|
+
used
|
|
1386
1418
|
});
|
|
1387
1419
|
});
|
|
1388
1420
|
};
|
|
@@ -1398,6 +1430,35 @@ const settleQuality = async () => {
|
|
|
1398
1430
|
}
|
|
1399
1431
|
if (rested) await healthy(() => fixCheck());
|
|
1400
1432
|
};
|
|
1433
|
+
/** The quality lap, then the human review since `base`. */
|
|
1434
|
+
const reviewedTail = (base, lensBase, lap) => tail(async (escalations) => {
|
|
1435
|
+
await settleQuality();
|
|
1436
|
+
return review(base, escalations, { lap });
|
|
1437
|
+
}, {
|
|
1438
|
+
green: true,
|
|
1439
|
+
lensBase
|
|
1440
|
+
});
|
|
1441
|
+
const featureFeedback = async (first, lensBase, ids) => {
|
|
1442
|
+
let outcome = first;
|
|
1443
|
+
let closed = ids.closed;
|
|
1444
|
+
const handedOut = /* @__PURE__ */ new Set([...ids.used, ...ids.closed]);
|
|
1445
|
+
while (outcome.verdict === "feedback") {
|
|
1446
|
+
await reUnwind(outcome);
|
|
1447
|
+
const lap = {
|
|
1448
|
+
closed,
|
|
1449
|
+
used: [...handedOut]
|
|
1450
|
+
};
|
|
1451
|
+
await design(outcome.base, () => {
|
|
1452
|
+
requireStableIds(REQUIREMENTS, lap);
|
|
1453
|
+
gather(handedOut);
|
|
1454
|
+
});
|
|
1455
|
+
closed = await implementing({
|
|
1456
|
+
tier: riskTierOn() ? recordRisk() : void 0,
|
|
1457
|
+
amend: true
|
|
1458
|
+
});
|
|
1459
|
+
outcome = await reviewedTail(outcome.base, lensBase, "feedback");
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1401
1462
|
/** Design — skipped when the sketch's own criteria are judged clear — then `buildFeature`. */
|
|
1402
1463
|
const planAndBuild = async (firstBase, stated) => {
|
|
1403
1464
|
if (!await skipDesign(riskTierOn() ? void 0 : stated)) await design(firstBase);
|
|
@@ -1444,4 +1505,4 @@ const feature = shipped(ordinaryStart);
|
|
|
1444
1505
|
//#region src/workflows/feature/index.ts
|
|
1445
1506
|
var feature_default = feature;
|
|
1446
1507
|
//#endregion
|
|
1447
|
-
export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, closeOut, collecting, criteriaOf, criterionIds, feature_default as default, defaults, deferNotes, design, envDefaults, feature, fixNits, holdsCallSites, implement, isTestFile, judgeSeam, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireTestCallSitesOnly, requireWithin, reviewedRefactor, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, triage, unwind };
|
|
1508
|
+
export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, closeOut, collecting, criteriaOf, criterionIds, feature_default as default, defaults, deferNotes, design, envDefaults, feature, fixNits, holdsCallSites, implement, implementing, isTestFile, judgeSeam, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireStableIds, requireTestCallSitesOnly, requireWithin, reviewedRefactor, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, triage, unstableIds, 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 { A as agentWithSkills, J as fixReviewNotesPrompt, S as coder, St as envDefaults, c as baseline, i as skills, n as summary, q as finisherSystem, r as access, s as shipped, t as steering, xt as defaults, y as REVIEW } from "../chunks/reserved-
|
|
4
|
-
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-
|
|
3
|
+
import { A as agentWithSkills, J as fixReviewNotesPrompt, S as coder, St as envDefaults, c as baseline, i as skills, n as summary, q as finisherSystem, r as access, s as shipped, t as steering, xt as defaults, y as REVIEW } from "../chunks/reserved-Qt5FM6Ia.mjs";
|
|
4
|
+
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-tAHx_AHe.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 { St as envDefaults, dt as reviewGateBlockedMessage, i as skills, n as summary, r as access, s as shipped, t as steering, u as gate, xt as defaults } from "../chunks/reserved-
|
|
3
|
-
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { St as envDefaults, dt as reviewGateBlockedMessage, i as skills, n as summary, r as access, s as shipped, t as steering, u as gate, xt as defaults } from "../chunks/reserved-Qt5FM6Ia.mjs";
|
|
3
|
+
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-tAHx_AHe.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 { Et as flipProblems, Ot as requireFlag, S as coder, St 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 flagMentions, xt as defaults } from "../chunks/reserved-
|
|
2
|
+
import { Et as flipProblems, Ot as requireFlag, S as coder, St 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 flagMentions, xt as defaults } from "../chunks/reserved-Qt5FM6Ia.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.";
|
package/dist/workflows.mjs
CHANGED
|
@@ -451,10 +451,15 @@ const escalation$1 = async (count, steps) => {
|
|
|
451
451
|
};
|
|
452
452
|
const STAMP = /\n<!-- gtd check [0-9a-f]+ -->\n?$/;
|
|
453
453
|
const stripStamp = (text) => text.replace(STAMP, "\n");
|
|
454
|
+
/** `true` when the re-check passed, else the red re-check's report — the one the escalation describes. */
|
|
455
|
+
const recheckReport = async (health) => {
|
|
456
|
+
if (health.recheck === void 0) return void 0;
|
|
457
|
+
return await health.recheck() || stripStamp(read(health.report) ?? "");
|
|
458
|
+
};
|
|
454
459
|
/**
|
|
455
460
|
* Loop until the suite is green: check, and on red run `fix` — escalating
|
|
456
461
|
* first once `fixCap` fixes are spent, or `sameFailure` calls the failure
|
|
457
|
-
* identical to the previous round's.
|
|
462
|
+
* identical to the previous round's, unless `recheck` passes.
|
|
458
463
|
*/
|
|
459
464
|
const healthy$1 = async (fix, health) => {
|
|
460
465
|
let fixes = health.fixesSoFar ?? 0;
|
|
@@ -468,6 +473,12 @@ const healthy$1 = async (fix, health) => {
|
|
|
468
473
|
const stuck = previous !== void 0 && await health.sameFailure(previous, current);
|
|
469
474
|
previous = current;
|
|
470
475
|
if (stuck || fixes >= health.fixCap) {
|
|
476
|
+
const rechecked = await recheckReport(health);
|
|
477
|
+
if (rechecked === true) {
|
|
478
|
+
health.escalations.rounds = 0;
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
previous = rechecked ?? previous;
|
|
471
482
|
await escalation$1(health.escalations, health.escalation);
|
|
472
483
|
fixes = 0;
|
|
473
484
|
}
|
|
@@ -17222,14 +17233,14 @@ What each change does next (then run \`gtd land\`):
|
|
|
17222
17233
|
- **Continue** — having undone the sketch by hand, check the test baseline is green and start triage (**start-gate.check**).
|
|
17223
17234
|
`;
|
|
17224
17235
|
const qualityUnresolvedMessage = () => `The quality lap's fix turns hit their cap and \`.gtd/QUALITY.md\` still
|
|
17225
|
-
holds findings. Nothing
|
|
17226
|
-
|
|
17236
|
+
holds findings. Nothing after this fixes them, so the process will not
|
|
17237
|
+
go on with them open.
|
|
17227
17238
|
|
|
17228
17239
|
Fix what matters by hand, then delete or empty \`.gtd/QUALITY.md\` —
|
|
17229
17240
|
dropping a finding is your call to make.
|
|
17230
17241
|
|
|
17231
17242
|
What each change does next (then run \`gtd land\`):
|
|
17232
|
-
- **Delete or empty \`.gtd/QUALITY.md\`** — the suite runs on your edits, then the
|
|
17243
|
+
- **Delete or empty \`.gtd/QUALITY.md\`** — the suite runs on your edits, then the next slice is built, or after the last one you review the change (**build.review.reviewing**).
|
|
17233
17244
|
- **Anything else** — rests here again.
|
|
17234
17245
|
`;
|
|
17235
17246
|
const startGateBlockedMessage = () => `The test baseline is red — gtd will not start new work on a broken suite.
|
|
@@ -17267,8 +17278,10 @@ reword a criterion in place, append a new one with the next unused
|
|
|
17267
17278
|
number, delete a dropped one — never renumber, never reuse an ID. When
|
|
17268
17279
|
the file holds no list (a review loop-back rewrites it), recover the
|
|
17269
17280
|
last settled one from history (\`git log -p -- .gtd/REQUIREMENTS.md\`)
|
|
17270
|
-
and carry its IDs over
|
|
17271
|
-
|
|
17281
|
+
and carry its IDs over: after a review a built criterion keeps its ID,
|
|
17282
|
+
reworded in place when the review changes its behaviour, and new
|
|
17283
|
+
behaviour gets a new ID. Landing is refused while a criterion has no ID
|
|
17284
|
+
or two share one.
|
|
17272
17285
|
`;
|
|
17273
17286
|
const riskTierBlock = `
|
|
17274
17287
|
## Risk tier and production signals
|
|
@@ -18385,6 +18398,19 @@ const baseline = (name, label = "Checking the baseline") => check(name, env.test
|
|
|
18385
18398
|
report: FEEDBACK,
|
|
18386
18399
|
label
|
|
18387
18400
|
});
|
|
18401
|
+
/**
|
|
18402
|
+
* Re-run a red check as step `name` before a person sees it. Green records
|
|
18403
|
+
* `Gtd-Mark: flaky=<red>` on the re-check's landing and resolves `true`.
|
|
18404
|
+
*/
|
|
18405
|
+
const recheck = async (name, red, options = {}) => {
|
|
18406
|
+
const green = await check(name, env.testCommand ?? "", {
|
|
18407
|
+
...options,
|
|
18408
|
+
report: FEEDBACK,
|
|
18409
|
+
label: "Re-running checks"
|
|
18410
|
+
});
|
|
18411
|
+
if (green) mark("flaky", red);
|
|
18412
|
+
return green;
|
|
18413
|
+
};
|
|
18388
18414
|
const escalationSteps = (rules) => ({
|
|
18389
18415
|
limit: 2,
|
|
18390
18416
|
describe: () => describeEscalation(rules),
|
|
@@ -18435,15 +18461,17 @@ const sameFailure = async (previous, current) => {
|
|
|
18435
18461
|
});
|
|
18436
18462
|
return comparable && truncated.length === 0 && answered(answers.verdict, "identical", numeric(vars.judgeIdenticalMinP, Infinity));
|
|
18437
18463
|
};
|
|
18464
|
+
const healthSweep = (options) => [ESCALATION, ...options.sweepOnGreen ?? []];
|
|
18438
18465
|
const runSuite = (options) => check("health.check", env.testCommand ?? "", {
|
|
18439
18466
|
report: FEEDBACK,
|
|
18440
18467
|
label: "Running checks",
|
|
18441
|
-
sweepOnGreen:
|
|
18468
|
+
sweepOnGreen: healthSweep(options)
|
|
18442
18469
|
});
|
|
18443
18470
|
/**
|
|
18444
18471
|
* Loop until the suite is green: check, and on red run `fix` — escalating
|
|
18445
18472
|
* first once `FIX_CAP` fixes are spent, or a judge calls the failure
|
|
18446
|
-
* identical to the previous round's.
|
|
18473
|
+
* identical to the previous round's. An escalation re-runs the suite once
|
|
18474
|
+
* first; a green re-run ends the loop.
|
|
18447
18475
|
*/
|
|
18448
18476
|
const healthy = (fix, options = {}) => healthy$1(guarded(fix, options.guard), {
|
|
18449
18477
|
check: () => runSuite(options),
|
|
@@ -18452,14 +18480,16 @@ const healthy = (fix, options = {}) => healthy$1(guarded(fix, options.guard), {
|
|
|
18452
18480
|
fixesSoFar: options.fixesSoFar ?? 0,
|
|
18453
18481
|
sameFailure,
|
|
18454
18482
|
escalations: options.escalations ?? { rounds: 0 },
|
|
18483
|
+
recheck: () => recheck("health.recheck", "health.check", { sweepOnGreen: healthSweep(options) }),
|
|
18455
18484
|
escalation: guardedSteps(options.guard, options.describeGuard, options.guardRests, options.rules)
|
|
18456
18485
|
});
|
|
18457
18486
|
/**
|
|
18458
|
-
* Hold the process at `<name>.blocked` until the suite is green
|
|
18487
|
+
* Hold the process at `<name>.blocked` until the suite is green, re-running a
|
|
18488
|
+
* red check once as `<name>.recheck` before resting. `guard` runs
|
|
18459
18489
|
* after each `blocked` landing only — never after a check, whose refusal would
|
|
18460
18490
|
* re-run the same script forever.
|
|
18461
18491
|
*/
|
|
18462
|
-
const gate = (name, message, guard) => gate$1(name, () => baseline("check"), guarded(() => human("blocked", {
|
|
18492
|
+
const gate = (name, message, guard) => gate$1(name, async () => await baseline("check") || recheck("recheck", `${name}.check`), guarded(() => human("blocked", {
|
|
18463
18493
|
message,
|
|
18464
18494
|
label: "Baseline is red",
|
|
18465
18495
|
file: FEEDBACK
|
|
@@ -38067,6 +38097,19 @@ const requireCriteria = (file) => {
|
|
|
38067
38097
|
if (criteria.length > 0 && unnamed.length === 0 && twice.length === 0) return;
|
|
38068
38098
|
refuse(`gtd land: acceptance-criteria: ${file} has ${criteria.length === 0 ? `no "## Acceptance Criteria" list` : unnamed.length > 0 ? `${unnamed.length} criterion without an ID:\n${unnamed.map((c) => ` - ${c.text}`).join("\n")}` : `duplicate IDs: ${[...new Set(twice)].join(", ")}`} — list every criterion under "## Acceptance Criteria" as "- AC-<n>: <checkable criterion>", each ID unique and never reused`);
|
|
38069
38099
|
};
|
|
38100
|
+
const idNumber = (id) => Number(id.slice(3));
|
|
38101
|
+
/** Why `listed` renumbers or reuses an earlier ID; `undefined` when it does neither. */
|
|
38102
|
+
const unstableIds = (listed, ids) => {
|
|
38103
|
+
const top = Math.max(0, ...ids.used.map(idNumber));
|
|
38104
|
+
const reused = listed.filter((id) => !ids.closed.includes(id) && idNumber(id) <= top);
|
|
38105
|
+
if (reused.length === 0) return void 0;
|
|
38106
|
+
return `numbers ${reused.join(", ")} at or below AC-${top}, an ID already handed out — keep a built criterion's ID (reword it in place when the review changes it), and number new ones from AC-${top + 1}`;
|
|
38107
|
+
};
|
|
38108
|
+
/** Refuse criteria in `file` that renumber or reuse an earlier ID. */
|
|
38109
|
+
const requireStableIds = (file, ids) => {
|
|
38110
|
+
const why = unstableIds(criterionIds(read(file) ?? ""), ids);
|
|
38111
|
+
if (why !== void 0) refuse(`gtd land: criteria-ids: ${file} ${why}`);
|
|
38112
|
+
};
|
|
38070
38113
|
/** Refuse a turn that changed the acceptance criteria in `file`: after the design gate only another design lap may. */
|
|
38071
38114
|
const requireCriteriaFrozen = (file) => {
|
|
38072
38115
|
const change = changes().get(file);
|
|
@@ -38222,13 +38265,14 @@ const noteGate = async (file, answer) => {
|
|
|
38222
38265
|
if (!hasThreadFor("agent", file)) requireAnswers(file);
|
|
38223
38266
|
return true;
|
|
38224
38267
|
};
|
|
38225
|
-
/** Triage the sketch since `base` into requirements, until no product question is open. */
|
|
38226
|
-
const design = (base) => scope("design", async () => {
|
|
38268
|
+
/** Triage the sketch since `base` into requirements, until no product question is open; `guard` holds every round's criteria. */
|
|
38269
|
+
const design = (base, guard) => scope("design", async () => {
|
|
38227
38270
|
do {
|
|
38228
38271
|
await triage(base);
|
|
38229
38272
|
requireProgress(REQUIREMENTS);
|
|
38230
38273
|
requireReplies(REQUIREMENTS);
|
|
38231
38274
|
requireCriteria(REQUIREMENTS);
|
|
38275
|
+
guard?.();
|
|
38232
38276
|
} while (await noteGate(REQUIREMENTS, answerProductQuestions));
|
|
38233
38277
|
});
|
|
38234
38278
|
//#endregion
|
|
@@ -39040,27 +39084,31 @@ const build = (options) => continuing(phase("build", requireCriteriaIfEdited), a
|
|
|
39040
39084
|
}
|
|
39041
39085
|
};
|
|
39042
39086
|
});
|
|
39043
|
-
function implement(first, tier, amend) {
|
|
39044
|
-
|
|
39087
|
+
async function implement(first, tier, amend) {
|
|
39088
|
+
await implementing(typeof first === "object" && first !== null ? first : {
|
|
39045
39089
|
reference: first,
|
|
39046
39090
|
tier,
|
|
39047
39091
|
amend
|
|
39048
|
-
};
|
|
39049
|
-
return scope("implement", async () => {
|
|
39050
|
-
const since = head$1();
|
|
39051
|
-
const acceptance = options.rebuild?.acceptance;
|
|
39052
|
-
const built = options.naive === void 0 || !await applied(options.naive);
|
|
39053
|
-
if (built) await build(options);
|
|
39054
|
-
else requireCriteriaFrozen(REQUIREMENTS);
|
|
39055
|
-
if (built && acceptance !== void 0) requireAcceptance({
|
|
39056
|
-
...acceptance,
|
|
39057
|
-
ids: settled(acceptance.ids, options.slice)
|
|
39058
|
-
}, since);
|
|
39059
|
-
await green(built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0);
|
|
39060
|
-
await requireCoverage(options.base, options.slice);
|
|
39061
|
-
await closing(options.more === true);
|
|
39062
39092
|
});
|
|
39063
39093
|
}
|
|
39094
|
+
/** `implement`, resolving with the criterion IDs the requirements listed when it closed. */
|
|
39095
|
+
const implementing = (options) => scope("implement", async () => {
|
|
39096
|
+
const since = head$1();
|
|
39097
|
+
const acceptance = options.rebuild?.acceptance;
|
|
39098
|
+
const built = options.naive === void 0 || !await applied(options.naive);
|
|
39099
|
+
if (built) await build(options);
|
|
39100
|
+
else requireCriteriaFrozen(REQUIREMENTS);
|
|
39101
|
+
if (built && acceptance !== void 0) requireAcceptance({
|
|
39102
|
+
...acceptance,
|
|
39103
|
+
ids: settled(acceptance.ids, options.slice)
|
|
39104
|
+
}, since);
|
|
39105
|
+
await green(built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0);
|
|
39106
|
+
await requireCoverage(options.base, options.slice);
|
|
39107
|
+
const ids = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
|
|
39108
|
+
await closing(moreToBuild(options.more));
|
|
39109
|
+
return ids;
|
|
39110
|
+
});
|
|
39111
|
+
const moreToBuild = (more) => typeof more === "function" ? more() : more === true;
|
|
39064
39112
|
/** The last slice's closing, for a last slice a steer left with no criterion to build. */
|
|
39065
39113
|
const closeOut = () => scope("implement", () => closing(false));
|
|
39066
39114
|
/** A slice with `more` after it keeps the criteria and the slicing for the next one. */
|
|
@@ -39295,32 +39343,37 @@ const unwind = () => {
|
|
|
39295
39343
|
const buildFeature = async (firstBase) => {
|
|
39296
39344
|
const tier = riskTierOn() ? recordRisk() : void 0;
|
|
39297
39345
|
const slices = await slicing();
|
|
39346
|
+
const handedOut = new Set(listedIds());
|
|
39298
39347
|
const one = slices.length === 1;
|
|
39348
|
+
const built = (i) => sliceIds(sliceCriteria(slices, i))?.length !== 0;
|
|
39299
39349
|
for (const i of slices.keys()) {
|
|
39300
|
-
|
|
39301
|
-
const criteria = sliceCriteria(slices, i);
|
|
39302
|
-
if (sliceIds(criteria)?.length === 0) {
|
|
39303
|
-
if (!more) await closeOut();
|
|
39304
|
-
continue;
|
|
39305
|
-
}
|
|
39350
|
+
if (!built(i)) continue;
|
|
39306
39351
|
const n = one ? "" : `-${i + 1}`;
|
|
39307
39352
|
await buildSlice(i === 0 ? firstBase : head$1(), {
|
|
39308
39353
|
tier,
|
|
39309
|
-
criteria,
|
|
39354
|
+
criteria: sliceCriteria(slices, i),
|
|
39310
39355
|
refactor: `refactor${n}`,
|
|
39311
39356
|
feature: `feature${n}`,
|
|
39312
|
-
more
|
|
39357
|
+
more: () => slices.some((_, j) => j > i && built(j)),
|
|
39358
|
+
reviewBase: one ? void 0 : firstBase,
|
|
39359
|
+
handedOut
|
|
39313
39360
|
});
|
|
39314
39361
|
}
|
|
39315
39362
|
};
|
|
39316
|
-
|
|
39363
|
+
const listedIds = () => criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
|
|
39364
|
+
const gather = (handedOut) => {
|
|
39365
|
+
for (const id of listedIds()) handedOut.add(id);
|
|
39366
|
+
return [...handedOut];
|
|
39367
|
+
};
|
|
39368
|
+
/** A naive pass (judged only with `flagMode` on), then implement and the quality lap; after the last slice, the review. */
|
|
39317
39369
|
const buildSlice = async (sliceBase, build) => {
|
|
39318
39370
|
const seam = await seamBuild(sliceBase, {
|
|
39319
39371
|
criteria: build.criteria,
|
|
39320
39372
|
refactor: build.refactor
|
|
39321
39373
|
});
|
|
39374
|
+
const used = gather(build.handedOut);
|
|
39322
39375
|
await slice(build.feature, async () => {
|
|
39323
|
-
await
|
|
39376
|
+
const closed = await implementing({
|
|
39324
39377
|
reference: seam.reference,
|
|
39325
39378
|
tier: build.tier,
|
|
39326
39379
|
afterRefactor: seam.refactored,
|
|
@@ -39330,14 +39383,23 @@ const buildSlice = async (sliceBase, build) => {
|
|
|
39330
39383
|
base: build.criteria === void 0 ? void 0 : sliceBase,
|
|
39331
39384
|
more: build.more
|
|
39332
39385
|
});
|
|
39386
|
+
const more = build.more();
|
|
39333
39387
|
const carried = seam.rebuild?.acceptance?.tests ?? [];
|
|
39334
39388
|
const base = seam.refactored === true && !changesSince(seam.base).some((c) => isCode$1(c.path) && !carried.includes(c.path)) ? sliceBase : seam.base;
|
|
39335
|
-
|
|
39336
|
-
|
|
39337
|
-
|
|
39338
|
-
|
|
39339
|
-
|
|
39340
|
-
|
|
39389
|
+
if (more) {
|
|
39390
|
+
const guard = () => requireCriteriaFrozen(REQUIREMENTS);
|
|
39391
|
+
await tail(settleQuality, {
|
|
39392
|
+
green: true,
|
|
39393
|
+
lensBase: base,
|
|
39394
|
+
guard,
|
|
39395
|
+
guardRests: false
|
|
39396
|
+
});
|
|
39397
|
+
return;
|
|
39398
|
+
}
|
|
39399
|
+
const reviewBase = build.reviewBase ?? base;
|
|
39400
|
+
await featureFeedback(await reviewedTail(reviewBase, base), base, {
|
|
39401
|
+
closed,
|
|
39402
|
+
used
|
|
39341
39403
|
});
|
|
39342
39404
|
});
|
|
39343
39405
|
};
|
|
@@ -39353,6 +39415,35 @@ const settleQuality = async () => {
|
|
|
39353
39415
|
}
|
|
39354
39416
|
if (rested) await healthy(() => fixCheck());
|
|
39355
39417
|
};
|
|
39418
|
+
/** The quality lap, then the human review since `base`. */
|
|
39419
|
+
const reviewedTail = (base, lensBase, lap) => tail(async (escalations) => {
|
|
39420
|
+
await settleQuality();
|
|
39421
|
+
return review$1(base, escalations, { lap });
|
|
39422
|
+
}, {
|
|
39423
|
+
green: true,
|
|
39424
|
+
lensBase
|
|
39425
|
+
});
|
|
39426
|
+
const featureFeedback = async (first, lensBase, ids) => {
|
|
39427
|
+
let outcome = first;
|
|
39428
|
+
let closed = ids.closed;
|
|
39429
|
+
const handedOut = /* @__PURE__ */ new Set([...ids.used, ...ids.closed]);
|
|
39430
|
+
while (outcome.verdict === "feedback") {
|
|
39431
|
+
await reUnwind(outcome);
|
|
39432
|
+
const lap = {
|
|
39433
|
+
closed,
|
|
39434
|
+
used: [...handedOut]
|
|
39435
|
+
};
|
|
39436
|
+
await design(outcome.base, () => {
|
|
39437
|
+
requireStableIds(REQUIREMENTS, lap);
|
|
39438
|
+
gather(handedOut);
|
|
39439
|
+
});
|
|
39440
|
+
closed = await implementing({
|
|
39441
|
+
tier: riskTierOn() ? recordRisk() : void 0,
|
|
39442
|
+
amend: true
|
|
39443
|
+
});
|
|
39444
|
+
outcome = await reviewedTail(outcome.base, lensBase, "feedback");
|
|
39445
|
+
}
|
|
39446
|
+
};
|
|
39356
39447
|
/** Design — skipped when the sketch's own criteria are judged clear — then `buildFeature`. */
|
|
39357
39448
|
const planAndBuild = async (firstBase, stated) => {
|
|
39358
39449
|
if (!await skipDesign(riskTierOn() ? void 0 : stated)) await design(firstBase);
|
|
@@ -39578,4 +39669,4 @@ var workflows_default = feature;
|
|
|
39578
39669
|
*/
|
|
39579
39670
|
const doors = {};
|
|
39580
39671
|
//#endregion
|
|
39581
|
-
export { COMMENT, ESCALATION, FEEDBACK, FIX_CAP, FlagOptions, HANDOFF, QUALITY, REFACTOR, REFACTOR_NOTE, REFACTOR_PACKAGE, REQUIREMENTS, REVIEW, REVIEW_ORIG, SLICES, ShippedOptions, acceptanceFault, access, afterTail, agentWithSkills, answerProductQuestions, answerReviewQuestions, awaitReview, base, baseline, buildFeature, buildTail, builtInLenses, cleanup, cleanupFlow, closeOut, closeReview, coder, collecting, criteriaOf, criterionIds, workflows_default as default, defaults, deferNotes, describeEscalation, design, doors, envDefaults, escalate, escalation, escalationExhausted, feature, fix, fixCheck, fixFlow, fixNits, fixQuality, fixQualityFindings, fixReviewNotes, fixRisks, gate, guarded, handOffReview, handOffTail, healthy, holdsCallSites, implement, isTestFile, judgeSeam, lensEntriesOf, lensOf, lensesOf, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, planner, qualityLap, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireTestCallSitesOnly, requireWithin, retryQuestion, review, reviewFlow, reviewMissing, reviewQuality, reviewedRefactor, reviewing, rollout, rolloutFlow, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, tail, threads, triage, unwind };
|
|
39672
|
+
export { COMMENT, ESCALATION, FEEDBACK, FIX_CAP, FlagOptions, HANDOFF, QUALITY, REFACTOR, REFACTOR_NOTE, REFACTOR_PACKAGE, REQUIREMENTS, REVIEW, REVIEW_ORIG, SLICES, ShippedOptions, acceptanceFault, access, afterTail, agentWithSkills, answerProductQuestions, answerReviewQuestions, awaitReview, base, baseline, buildFeature, buildTail, builtInLenses, cleanup, cleanupFlow, closeOut, closeReview, coder, collecting, criteriaOf, criterionIds, workflows_default as default, defaults, deferNotes, describeEscalation, design, doors, envDefaults, escalate, escalation, escalationExhausted, feature, fix, fixCheck, fixFlow, fixNits, fixQuality, fixQualityFindings, fixReviewNotes, fixRisks, gate, guarded, handOffReview, handOffTail, healthy, holdsCallSites, implement, implementing, isTestFile, judgeSeam, lensEntriesOf, lensOf, lensesOf, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, planner, qualityLap, reUnwind, recheck, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireStableIds, requireTestCallSitesOnly, requireWithin, retryQuestion, review, reviewFlow, reviewMissing, reviewQuality, reviewedRefactor, reviewing, rollout, rolloutFlow, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, tail, threads, triage, unstableIds, unwind };
|
package/package.json
CHANGED
|
@@ -224,8 +224,9 @@ workflow.
|
|
|
224
224
|
|
|
225
225
|
In the bundled default, `planAndBuild` runs the design phase, then
|
|
226
226
|
`buildFeature`: the naive pass and its seam judge, then the implementation (the
|
|
227
|
-
pass itself on `land`)
|
|
228
|
-
|
|
227
|
+
pass itself on `land`), the quality lap and the human review (a review feedback
|
|
228
|
+
lap re-runs design inside `buildFeature` and does not pass it again). Add a
|
|
229
|
+
human sign-off before the first build:
|
|
229
230
|
|
|
230
231
|
```ts
|
|
231
232
|
const planAndBuild = async (firstBase: string): Promise<void> => {
|
package/src/cli/Cli.ts
CHANGED
|
@@ -838,9 +838,10 @@ const COMMAND_ROWS: readonly CommandRow[] = [
|
|
|
838
838
|
"Report how the kept processes went: slices whose naive pass",
|
|
839
839
|
"landed or was rebuilt (the refactor-first rate), continuations",
|
|
840
840
|
"per phase, Gtd-Cost by model and phase with the double work of",
|
|
841
|
-
"a rebuild,
|
|
842
|
-
"
|
|
843
|
-
"
|
|
841
|
+
"a rebuild, quality-lap findings per lens, and red checks that",
|
|
842
|
+
"passed on their re-check — per process and in total. Each <ref>",
|
|
843
|
+
"is read as by history show, but a branch name means every",
|
|
844
|
+
"process kept from it; none means every ref.",
|
|
844
845
|
"Reads commit subjects and trailers, never replays, so a",
|
|
845
846
|
"changed config is no refusal. --json prints { processes, total",
|
|
846
847
|
"} as one document; writes nothing",
|
package/src/cli/Metrics.test.ts
CHANGED
|
@@ -163,6 +163,37 @@ describe("processMetrics", () => {
|
|
|
163
163
|
})
|
|
164
164
|
})
|
|
165
165
|
|
|
166
|
+
describe("flaky re-checks", () => {
|
|
167
|
+
it("counts each flaky re-check under its own full step name", () => {
|
|
168
|
+
const m = processMetrics([
|
|
169
|
+
{
|
|
170
|
+
message:
|
|
171
|
+
"gtd(check): start-gate.recheck → design.triage\n\nGtd-Mark: flaky=start-gate.check",
|
|
172
|
+
findings: 0,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
message: "gtd(check): build.health.recheck → idle\n\nGtd-Mark: flaky=health.check",
|
|
176
|
+
findings: 0,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
message:
|
|
180
|
+
"gtd(check): implement.health.recheck → implement.closing\n\nGtd-Mark: flaky=health.check",
|
|
181
|
+
findings: 0,
|
|
182
|
+
},
|
|
183
|
+
{ message: "gtd(agent): seam.naive → seam.check\n\nGtd-Mark: covers=AC-1", findings: 0 },
|
|
184
|
+
{ message: "fix: hand-made\n\nGtd-Mark: flaky=x.check", findings: 0 },
|
|
185
|
+
])
|
|
186
|
+
expect(m.flaky).toEqual({
|
|
187
|
+
total: 3,
|
|
188
|
+
byStep: { "start-gate.recheck": 1, "build.health.recheck": 1, "implement.health.recheck": 1 },
|
|
189
|
+
})
|
|
190
|
+
expect(totalMetrics([m, m]).flaky).toEqual({
|
|
191
|
+
total: 6,
|
|
192
|
+
byStep: { "start-gate.recheck": 2, "build.health.recheck": 2, "implement.health.recheck": 2 },
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
166
197
|
describe("totalMetrics", () => {
|
|
167
198
|
it("adds processes and recomputes the rate over every decision", () => {
|
|
168
199
|
const a = processMetrics([
|