@pmelab/gtd 56.0.0 → 58.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 +29 -35
- package/dist/chunks/{reserved-B8EC-WE8.mjs → reserved-BzAFXuz_.mjs} +56 -131
- package/dist/chunks/{review-HdrX0FJ7.mjs → review-CVqRS0mc.mjs} +2 -2
- package/dist/chunks/{tail-BSLah2mA.mjs → tail-JQzB21Q3.mjs} +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +99 -170
- 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 +150 -297
- package/package.json +1 -1
- package/src/workflows/feature/acceptance-criteria.feature +32 -0
- package/src/workflows/feature/criteria-coverage.feature +0 -2
- package/src/workflows/feature/criteria.test.ts +1 -41
- package/src/workflows/feature/criteria.ts +12 -18
- package/src/workflows/feature/fast-path.feature +4 -1
- package/src/workflows/feature/feature.test.ts +3 -39
- package/src/workflows/feature/feature.ts +6 -8
- package/src/workflows/feature/lens-models.feature +0 -2
- package/src/workflows/feature/lens-review.feature +0 -2
- package/src/workflows/feature/naive-acceptance.feature +4 -0
- package/src/workflows/feature/naive-test.feature +263 -0
- package/src/workflows/feature/one-step-land.feature +4 -1
- package/src/workflows/feature/planning.ts +2 -0
- package/src/workflows/feature/refactor-absorbs.feature +4 -1
- package/src/workflows/feature/refactor-first.feature +4 -1
- package/src/workflows/feature/refactor-lens-models.feature +4 -0
- package/src/workflows/feature/refactor-rebuild.feature +4 -1
- package/src/workflows/feature/refactor-review.feature +20 -1
- package/src/workflows/feature/release-flag.feature +167 -0
- package/src/workflows/feature/release-flag.test.ts +49 -0
- package/src/workflows/feature/release-flag.ts +46 -0
- package/src/workflows/feature/review-gate.feature +1 -3
- package/src/workflows/feature/reviewer-walkthrough.feature +0 -2
- package/src/workflows/feature/risk-tier.feature +32 -0
- package/src/workflows/feature/seam-rejection.feature +8 -1
- package/src/workflows/feature/seam.feature +8 -1
- package/src/workflows/feature/seam.test.ts +21 -9
- package/src/workflows/feature/seam.ts +60 -14
- package/src/workflows/feature/standards.feature +0 -2
- package/src/workflows/shared/options.test.ts +3 -10
- package/src/workflows/shared/options.ts +5 -6
- package/src/workflows/shared/prose.ts +0 -6
- package/src/workflows/shared/skills.test.ts +0 -4
- package/src/workflows/shared/skills.ts +0 -5
- package/src/workflows/shared/steps.ts +1 -1
- package/src/workflows/shared/text.test.ts +58 -69
- package/src/workflows/shared/text.ts +79 -162
- package/src/workflows/shared/vars.ts +2 -4
- package/src/workflows/feature/flag-mode.feature +0 -88
- package/src/workflows/feature/hands-on-skip.feature +0 -316
- package/src/workflows/feature/prototype-rounds.feature +0 -156
- package/src/workflows/feature/prototype.feature +0 -428
- package/src/workflows/feature/prototype.test.ts +0 -43
- package/src/workflows/feature/prototype.ts +0 -198
|
@@ -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, start, vars, wrote } from "../flows.mjs";
|
|
1
|
+
import { agent, answered, applyScript, changes, changesSince, check, codeChanges, env, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireThreadsClosed, restoreScript, revertScript, run, scope, start, vars, wrote } from "../flows.mjs";
|
|
2
2
|
import { lensEntriesOf } from "../workflow-kit.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { a as refusePendingHandOff } from "../chunks/tail-
|
|
5
|
-
import { n as isCode$1, r as reUnwind, t as buildTail } from "../chunks/review-
|
|
3
|
+
import { B as designSystem, Ct as testFileGlobs, D as planner, F as clearCriteriaMessage, I as clearCriteriaQuestion, J as implementBuildingPrompt, L as criterionLine, M as buildReviewCollectingPrompt, P as builderSystem, Q as naiveTryMessage, R as designGateAnswerMessage, S as collecting, St as envDefaults, V as designTriagePrompt, X as naiveApplyFailure, Y as implementFixPrompt, Z as naivePassPrompt, _ as REQUIREMENTS, _t as unwindFailedMessage, at as refactorFixPrompt, d as healthy, et as normaliseCriteriaScript, f as FEEDBACK, g as REFACTOR_NOTE, gt as startGateBlockedMessage, h as REFACTOR, ht as seamSteerMessage, i as skills, it as refactorDroppedRefusal, j as agentWithSkills, l as gate, mt as seamByHandMessage, n as summary, o as naiveTestOn, ot as refactorNotePrompt, p as NAIVE_TEST, pt as reviewerSystem, q as idleMessage, r as access, rt as refactorBuildingPrompt, s as shipped, st as refactorRules, t as steering, vt as unwindFailure, w as fixNits, x as coder, xt as defaults, y as answerReviewQuestions, z as designImpactPrompt } from "../chunks/reserved-BzAFXuz_.mjs";
|
|
4
|
+
import { a as refusePendingHandOff } from "../chunks/tail-JQzB21Q3.mjs";
|
|
5
|
+
import { n as isCode$1, r as reUnwind, t as buildTail } from "../chunks/review-CVqRS0mc.mjs";
|
|
6
6
|
import { posix } from "node:path";
|
|
7
7
|
//#region src/workflows/shared/fences.ts
|
|
8
8
|
const markFenced = (text) => {
|
|
@@ -23,14 +23,50 @@ const HEADING = /^ {0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
|
|
|
23
23
|
/** A list item, its checkbox dropped: its text. */
|
|
24
24
|
const ITEM = /^ ?(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?(.*)$/;
|
|
25
25
|
//#endregion
|
|
26
|
+
//#region src/workflows/feature/release-flag.ts
|
|
27
|
+
const NAME = /^existing:\s*`?([A-Za-z_][\w.-]*)`?$/i;
|
|
28
|
+
const isFlagHeading = (h) => h !== null && h[1].length === 2 && /^release flag$/i.test(h[2].trim());
|
|
29
|
+
/** The first line under `## Release Flag`, `undefined` without the section. */
|
|
30
|
+
const requestLine = (text) => {
|
|
31
|
+
const lines = markFenced(text);
|
|
32
|
+
const at = lines.findIndex((l) => !l.fenced && isFlagHeading(HEADING.exec(l.line)));
|
|
33
|
+
if (at === -1) return void 0;
|
|
34
|
+
const body = lines.slice(at + 1);
|
|
35
|
+
const end = body.findIndex((l) => !l.fenced && HEADING.test(l.line));
|
|
36
|
+
return (end === -1 ? body : body.slice(0, end)).find((l) => l.line.trim() !== "")?.line.trim();
|
|
37
|
+
};
|
|
38
|
+
/** What `text` asks for, or why it cannot be read. No section asks for no flag. */
|
|
39
|
+
const releaseFlagOf = (text) => {
|
|
40
|
+
const line = requestLine(text);
|
|
41
|
+
if (line === void 0 || /^none$/i.test(line)) return { kind: "none" };
|
|
42
|
+
if (/^new$/i.test(line)) return { kind: "new" };
|
|
43
|
+
const name = NAME.exec(line)?.[1];
|
|
44
|
+
return name === void 0 ? { unreadable: line } : {
|
|
45
|
+
kind: "existing",
|
|
46
|
+
name
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
/** Refuse `file` when its `## Release Flag` section is neither `none`, `new` nor `existing: <NAME>`. */
|
|
50
|
+
const requireReleaseFlag = (file) => {
|
|
51
|
+
const request = releaseFlagOf(read(file) ?? "");
|
|
52
|
+
if (!("unreadable" in request)) return;
|
|
53
|
+
refuse(`gtd land: release-flag: ${file}'s "## Release Flag" reads ${JSON.stringify(request.unreadable)} — its first line must be \`new\`, \`existing: <NAME>\` or \`none\`, or leave the section out for no flag`);
|
|
54
|
+
};
|
|
55
|
+
/** What `.gtd/REQUIREMENTS.md` asks for now; a person's unreadable edit is refused. */
|
|
56
|
+
const requestedFlag = () => {
|
|
57
|
+
requireReleaseFlag(REQUIREMENTS);
|
|
58
|
+
return releaseFlagOf(read(".gtd/REQUIREMENTS.md") ?? "");
|
|
59
|
+
};
|
|
60
|
+
/** Whether the change goes behind a release flag: only then does the seam judge rule. */
|
|
61
|
+
const flagged = () => requestedFlag().kind !== "none";
|
|
62
|
+
//#endregion
|
|
26
63
|
//#region src/workflows/feature/criteria.ts
|
|
27
64
|
/** Below this the judge's `yes` is not trusted: a wrong skip builds against criteria nobody can check. */
|
|
28
65
|
const CLEAR_MIN_P = .8;
|
|
29
|
-
const ID = /^(AC-\d+)
|
|
66
|
+
const ID = /^(AC-\d+):\s*(.*)$/;
|
|
30
67
|
const criterion = (id, as = id[1]) => ({
|
|
31
68
|
id: as,
|
|
32
|
-
text: id[
|
|
33
|
-
...id[2] === void 0 ? {} : { noHandsOn: true }
|
|
69
|
+
text: id[2]
|
|
34
70
|
});
|
|
35
71
|
/** The list items under the first heading `title` matches (at `level` when given), up to the next heading; wrapped lines joined, fenced lines ignored. */
|
|
36
72
|
const listUnder = (text, title, level) => {
|
|
@@ -67,11 +103,6 @@ const criteriaOf = (text) => listUnder(text, /^acceptance criteria$/i, 2)?.map((
|
|
|
67
103
|
});
|
|
68
104
|
/** The ID'd criteria of `text`. */
|
|
69
105
|
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
|
-
const tagged = (criteriaOf(text) ?? []).filter((c) => c.noHandsOn).map((c) => c.id);
|
|
73
|
-
return criterionIds(text).filter((id) => !tagged.includes(id));
|
|
74
|
-
};
|
|
75
106
|
/** Items numbered `AC-1`…, unless every one already carries its own distinct ID. */
|
|
76
107
|
const numbered = (items) => {
|
|
77
108
|
const ids = items.map((item) => ID.exec(item));
|
|
@@ -120,14 +151,15 @@ const requireCriteria = (file) => {
|
|
|
120
151
|
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`);
|
|
121
152
|
};
|
|
122
153
|
const isImpactHeading = (h) => h !== null && h[1].length === 2 && /^impact$/i.test(h[2].trim());
|
|
154
|
+
const isImpactTurnHeading = (h) => h !== null && h[1].length === 2 && /^(impact|release flag)$/i.test(h[2].trim());
|
|
123
155
|
/** Whether `text` has an unfenced, level-2 `## Impact` heading. */
|
|
124
156
|
const hasImpact = (text) => markFenced(text).some((l) => !l.fenced && isImpactHeading(HEADING.exec(l.line)));
|
|
125
|
-
/** `text` without its `## Impact`
|
|
157
|
+
/** `text` without its `## Impact` and `## Release Flag` sections, whitespace collapsed. */
|
|
126
158
|
const withoutImpact = (text) => {
|
|
127
159
|
let inImpact = false;
|
|
128
160
|
return markFenced(text).filter((l) => {
|
|
129
161
|
const h = l.fenced ? null : HEADING.exec(l.line);
|
|
130
|
-
if (h !== null && h[1].length <= 2) inImpact =
|
|
162
|
+
if (h !== null && h[1].length <= 2) inImpact = isImpactTurnHeading(h);
|
|
131
163
|
return !inImpact;
|
|
132
164
|
}).map((l) => l.line).join(" ").replace(/\s+/g, " ").trim();
|
|
133
165
|
};
|
|
@@ -144,12 +176,12 @@ const requireImpact = (file) => {
|
|
|
144
176
|
if (hasImpact(text)) return;
|
|
145
177
|
refuse(`gtd land: impact: ${file} has no "## Impact" section — explore the codebase and add "## Impact" naming the units, public surfaces and surprises this change touches`);
|
|
146
178
|
};
|
|
147
|
-
/** Refuse a turn that changed `file` beyond its `## Impact`
|
|
179
|
+
/** Refuse a turn that changed `file` beyond its `## Impact` and `## Release Flag` sections. */
|
|
148
180
|
const requireImpactOnly = (file) => {
|
|
149
181
|
const change = changes().get(file);
|
|
150
182
|
if (change === void 0) return;
|
|
151
183
|
if (withoutImpact(change.before ?? "") === withoutImpact(change.after ?? "")) return;
|
|
152
|
-
refuse(`gtd land: impact-only: this turn may only add "## Impact" to ${file} — restore everything else`);
|
|
184
|
+
refuse(`gtd land: impact-only: this turn may only add "## Impact" (and "## Release Flag") to ${file} — restore everything else`);
|
|
153
185
|
};
|
|
154
186
|
const idNumber = (id) => Number(id.slice(3));
|
|
155
187
|
/** Why `listed` renumbers or reuses an earlier ID; `undefined` when it does neither. */
|
|
@@ -196,6 +228,7 @@ const impact = async () => {
|
|
|
196
228
|
requireCriteriaFrozen(REQUIREMENTS);
|
|
197
229
|
requireImpact(REQUIREMENTS);
|
|
198
230
|
requireImpactOnly(REQUIREMENTS);
|
|
231
|
+
requireReleaseFlag(REQUIREMENTS);
|
|
199
232
|
};
|
|
200
233
|
/**
|
|
201
234
|
* The skip path: when the sketch stated clear, checkable criteria, write them
|
|
@@ -299,6 +332,7 @@ const design = (base, guard) => scope("design", async () => {
|
|
|
299
332
|
requireReplies(REQUIREMENTS);
|
|
300
333
|
requireCriteria(REQUIREMENTS);
|
|
301
334
|
requireImpact(REQUIREMENTS);
|
|
335
|
+
requireReleaseFlag(REQUIREMENTS);
|
|
302
336
|
guard?.();
|
|
303
337
|
} while (await noteGate(REQUIREMENTS, answerProductQuestions));
|
|
304
338
|
});
|
|
@@ -314,147 +348,6 @@ const green = () => healthy(() => agentWithSkills("fix.suite.fixing", implementF
|
|
|
314
348
|
guardRests: false
|
|
315
349
|
});
|
|
316
350
|
//#endregion
|
|
317
|
-
//#region src/workflows/feature/prototype.ts
|
|
318
|
-
/** Only a `no` at this or above skips a criterion: no verdict, or an unsure one, sends it to the hands-on test. */
|
|
319
|
-
const UNOBSERVABLE_MIN_P = .5;
|
|
320
|
-
/** The `prototypeRounds` setting. Anything but a non-negative integer fails rather than guess a cap. */
|
|
321
|
-
const prototypeRounds = () => {
|
|
322
|
-
const value = vars.prototypeRounds ?? defaults.prototypeRounds;
|
|
323
|
-
if (!/^(0|[1-9]\d*)$/.test(value)) refuse(`gtd: prototypeRounds must be a non-negative integer, got ${JSON.stringify(value)}`);
|
|
324
|
-
return Number(value);
|
|
325
|
-
};
|
|
326
|
-
const CHUNK = /^(AC-\d+|Problem\b.*?)\s*:\s*(pass|fail|blocked)$/i;
|
|
327
|
-
const UNGUIDED = /^no testing instructions$/i;
|
|
328
|
-
/** The `## ` chunks of a prototype report; a heading in neither shape is prose, not an item. */
|
|
329
|
-
const reportOf = (text) => {
|
|
330
|
-
let unguided = false;
|
|
331
|
-
const items = [];
|
|
332
|
-
const malformed = [];
|
|
333
|
-
for (const { line, fenced } of markFenced(text)) {
|
|
334
|
-
const heading = fenced ? null : HEADING.exec(line);
|
|
335
|
-
if (heading?.[1] !== "##") continue;
|
|
336
|
-
if (UNGUIDED.test(heading[2])) unguided = true;
|
|
337
|
-
const chunk = CHUNK.exec(heading[2]);
|
|
338
|
-
if (chunk === null && /^(ac-\d|problem)/i.test(heading[2])) malformed.push(heading[2]);
|
|
339
|
-
if (chunk !== null) items.push({
|
|
340
|
-
subject: /^ac-/i.test(chunk[1]) ? chunk[1].toUpperCase() : chunk[1],
|
|
341
|
-
verdict: chunk[2].toLowerCase()
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
return {
|
|
345
|
-
unguided,
|
|
346
|
-
items,
|
|
347
|
-
malformed
|
|
348
|
-
};
|
|
349
|
-
};
|
|
350
|
-
/**
|
|
351
|
-
* The report the testing turn left, refused unless it is the only change and
|
|
352
|
-
* names each of `ids` once, with its open items: any other criterion is not
|
|
353
|
-
* this test's to fix.
|
|
354
|
-
*/
|
|
355
|
-
const requireReport = (ids, retest) => {
|
|
356
|
-
const others = changes().map((c) => c.path).filter((path) => path !== PROTOTYPE);
|
|
357
|
-
if (others.length > 0) refuse(`gtd land: prototype-report: test in a disposable place and change nothing but ${PROTOTYPE} — revert these:\n${others.map((path) => ` - ${path}`).join("\n")}`);
|
|
358
|
-
const text = read(PROTOTYPE);
|
|
359
|
-
if (text === void 0) return refuse(`gtd land: prototype-report: write ${PROTOTYPE}`);
|
|
360
|
-
const report = reportOf(text);
|
|
361
|
-
if (report.malformed.length > 0) refuse(`gtd land: prototype-report: head each chunk \`## <ID>: pass|fail|blocked\` or \`## Problem <title>: pass|fail|blocked\` — these miss it:\n${report.malformed.map((h) => ` - ${h}`).join("\n")}`);
|
|
362
|
-
if (report.unguided && report.items.length === 0 && !retest) return {
|
|
363
|
-
unguided: true,
|
|
364
|
-
open: []
|
|
365
|
-
};
|
|
366
|
-
const elsewhere = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "").filter((id) => !ids.includes(id));
|
|
367
|
-
const count = (id) => report.items.filter((i) => i.subject === id).length;
|
|
368
|
-
const wrong = ids.filter((id) => count(id) !== 1);
|
|
369
|
-
if (wrong.length > 0) refuse(`gtd land: prototype-report: ${PROTOTYPE} needs exactly one chunk headed \`## <ID>: pass|fail|blocked\` for ${wrong.join(", ")}`);
|
|
370
|
-
return {
|
|
371
|
-
unguided: false,
|
|
372
|
-
open: report.items.filter((i) => i.verdict !== "pass" && !elsewhere.includes(i.subject))
|
|
373
|
-
};
|
|
374
|
-
};
|
|
375
|
-
/**
|
|
376
|
-
* The criteria a person can observe by using the change, by the judge: those
|
|
377
|
-
* tagged `(no hands-on)` are never asked about.
|
|
378
|
-
*/
|
|
379
|
-
const observable = async () => {
|
|
380
|
-
const requirements = read(".gtd/REQUIREMENTS.md") ?? "";
|
|
381
|
-
const own = handsOnIds(requirements);
|
|
382
|
-
const criteria = (criteriaOf(requirements) ?? []).filter((c) => own.includes(c.id));
|
|
383
|
-
if (criteria.length === 0) return [];
|
|
384
|
-
const { answers } = await judge("observable", {
|
|
385
|
-
questions: criteria.map(observableQuestion),
|
|
386
|
-
evidence: { requirements },
|
|
387
|
-
message: observableMessage(),
|
|
388
|
-
label: "Judging which criteria to test by hand"
|
|
389
|
-
});
|
|
390
|
-
return own.filter((id) => !answered(answers[id], "no", UNOBSERVABLE_MIN_P));
|
|
391
|
-
};
|
|
392
|
-
const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
|
|
393
|
-
const testing = async (ids, retest) => {
|
|
394
|
-
await agentWithSkills("testing", prototypeTestingPrompt(criterionIds(read(".gtd/REQUIREMENTS.md") ?? "").filter((id) => !ids.includes(id)), retest), {
|
|
395
|
-
label: retest ? "Re-testing the change by hand" : "Testing the change by hand",
|
|
396
|
-
file: PROTOTYPE,
|
|
397
|
-
model: coder(),
|
|
398
|
-
system: testerSystem(),
|
|
399
|
-
allowEmpty: true
|
|
400
|
-
});
|
|
401
|
-
return requireReport(ids, retest);
|
|
402
|
-
};
|
|
403
|
-
const fixing = async () => {
|
|
404
|
-
await scope("fix", () => agentWithSkills("fixing", prototypeFixingPrompt(), {
|
|
405
|
-
label: "Fixing what the hands-on test found",
|
|
406
|
-
file: PROTOTYPE,
|
|
407
|
-
model: coder(),
|
|
408
|
-
system: builderSystem(),
|
|
409
|
-
allowEmpty: true
|
|
410
|
-
}));
|
|
411
|
-
if (changes().some((c) => c.path === ".gtd/PROTOTYPE.md")) refuse(`gtd land: prototype-fix: leave ${PROTOTYPE} as it is — the next round re-tests from it`);
|
|
412
|
-
frozen();
|
|
413
|
-
};
|
|
414
|
-
/** A person's rest; resolves `true` when they changed something to test again, `false` on a clean overrule. */
|
|
415
|
-
const askPerson = async (name, message, label) => {
|
|
416
|
-
await human(name, {
|
|
417
|
-
message,
|
|
418
|
-
label,
|
|
419
|
-
file: PROTOTYPE,
|
|
420
|
-
acceptClean: true
|
|
421
|
-
});
|
|
422
|
-
if (!changes().some((c) => c.path !== ".gtd/PROTOTYPE.md")) return false;
|
|
423
|
-
frozen();
|
|
424
|
-
return true;
|
|
425
|
-
};
|
|
426
|
-
/**
|
|
427
|
-
* Use the change by hand: a testing turn reports on every criterion a person
|
|
428
|
-
* can observe, a fix turn takes each failure, and the change is tested again —
|
|
429
|
-
* until the report is clean, or `prototypeRounds` fixes are spent and a person
|
|
430
|
-
* decides. A change with no such criterion is not tested by hand.
|
|
431
|
-
*/
|
|
432
|
-
const prototypeTest = async () => {
|
|
433
|
-
const cap = prototypeRounds();
|
|
434
|
-
if (cap === 0) return;
|
|
435
|
-
await scope("prototype", async () => {
|
|
436
|
-
const ids = await observable();
|
|
437
|
-
if (ids.length === 0) return;
|
|
438
|
-
let fixes = 0;
|
|
439
|
-
let retest = false;
|
|
440
|
-
for (;;) {
|
|
441
|
-
const { unguided, open } = await testing(ids, retest);
|
|
442
|
-
retest = !unguided;
|
|
443
|
-
if (unguided) {
|
|
444
|
-
if (!await askPerson("unguided", prototypeUnguidedMessage(), "No way to test by hand")) return;
|
|
445
|
-
} else if (open.length === 0) return;
|
|
446
|
-
else if (fixes < cap && open.some((i) => i.verdict === "fail")) {
|
|
447
|
-
fixes++;
|
|
448
|
-
await fixing();
|
|
449
|
-
} else {
|
|
450
|
-
if (!await askPerson("open", prototypeOpenMessage(open, fixes), "Hands-on test still failing")) return;
|
|
451
|
-
fixes = 0;
|
|
452
|
-
}
|
|
453
|
-
await green();
|
|
454
|
-
}
|
|
455
|
-
});
|
|
456
|
-
};
|
|
457
|
-
//#endregion
|
|
458
351
|
//#region src/workflows/feature/refactor.ts
|
|
459
352
|
const REFACTOR_PACKAGE = REFACTOR;
|
|
460
353
|
const isCode = (path) => path !== ".gtd" && !path.startsWith(".gtd/");
|
|
@@ -805,7 +698,7 @@ const applied = async (tip) => {
|
|
|
805
698
|
return !wrote(".gtd/FEEDBACK.md") && codeChanges().length > 0;
|
|
806
699
|
};
|
|
807
700
|
const build = async (options) => {
|
|
808
|
-
await agentWithSkills("building", implementBuildingPrompt(options.reference, options.
|
|
701
|
+
await agentWithSkills("building", implementBuildingPrompt(options.reference, options.amend, options.afterRefactor, requestedFlag(), options.rebuild), {
|
|
809
702
|
label: "Implementing",
|
|
810
703
|
model: coder(),
|
|
811
704
|
system: builderSystem(),
|
|
@@ -825,19 +718,14 @@ const implementing = (options) => scope("implement", async () => {
|
|
|
825
718
|
if (options.naive === void 0 || !await applied(options.naive)) await build(options);
|
|
826
719
|
else requireCriteriaFrozen(REQUIREMENTS);
|
|
827
720
|
await green();
|
|
828
|
-
await prototypeTest();
|
|
829
721
|
const ids = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
|
|
830
|
-
await run("closing", removeScript([
|
|
831
|
-
REQUIREMENTS,
|
|
832
|
-
REFACTOR_NOTE,
|
|
833
|
-
PROTOTYPE
|
|
834
|
-
]), { label: "Closing out the implementation" });
|
|
722
|
+
await run("closing", removeScript([REQUIREMENTS, REFACTOR_NOTE]), { label: "Closing out the implementation" });
|
|
835
723
|
return ids;
|
|
836
724
|
});
|
|
837
725
|
/** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
|
|
838
726
|
const steer = async (retry) => {
|
|
839
727
|
await human("steer", {
|
|
840
|
-
message: seamSteerMessage(retry,
|
|
728
|
+
message: seamSteerMessage(retry, flagged()),
|
|
841
729
|
label: "Steer or overrule the naive pass",
|
|
842
730
|
file: REQUIREMENTS,
|
|
843
731
|
acceptClean: true
|
|
@@ -899,6 +787,39 @@ const ruled = async (tip, again) => {
|
|
|
899
787
|
tip
|
|
900
788
|
} : next;
|
|
901
789
|
};
|
|
790
|
+
/** Back to `before`: every code path, and the notes file. `.gtd/` edits (the requirements) stay. */
|
|
791
|
+
const untryScript = (before) => {
|
|
792
|
+
const code = changesSince(before).filter((c) => isCode$1(c.path));
|
|
793
|
+
return restoreScript(head(), {
|
|
794
|
+
restore: code.filter((c) => c.status !== "added").map((c) => c.path),
|
|
795
|
+
remove: [...code.filter((c) => c.status === "added").map((c) => c.path), NAIVE_TEST]
|
|
796
|
+
}, before);
|
|
797
|
+
};
|
|
798
|
+
/**
|
|
799
|
+
* A person tries the green pass `tip` in the working tree. Resolves
|
|
800
|
+
* `undefined` on a clean landing (signed off), else what they sent back;
|
|
801
|
+
* either way the pass, their code edits and the notes file leave the tree.
|
|
802
|
+
*/
|
|
803
|
+
const tried = async (tip) => {
|
|
804
|
+
const before = head();
|
|
805
|
+
await run("applying", applyScript(tip.hash, FEEDBACK, naiveApplyFailure(tip.hash), [".gtd"]), { label: "Applying the naive pass to try it" });
|
|
806
|
+
await human("try", {
|
|
807
|
+
message: naiveTryMessage(flagged()),
|
|
808
|
+
label: "Try the naive pass",
|
|
809
|
+
file: NAIVE_TEST,
|
|
810
|
+
acceptClean: true
|
|
811
|
+
});
|
|
812
|
+
const landed = changes();
|
|
813
|
+
const edits = landed.some((c) => isCode$1(c.path)) ? head() : void 0;
|
|
814
|
+
const notes = read(".gtd/NAIVE-TEST.md")?.trim() || void 0;
|
|
815
|
+
if (landed.some((c) => c.path === ".gtd/REQUIREMENTS.md")) requireCriteria(REQUIREMENTS);
|
|
816
|
+
await run("restoring", untryScript(before), { label: "Taking the naive pass out again" });
|
|
817
|
+
return landed.length === 0 ? void 0 : {
|
|
818
|
+
tested: tip.hash,
|
|
819
|
+
notes,
|
|
820
|
+
edits
|
|
821
|
+
};
|
|
822
|
+
};
|
|
902
823
|
const decide = () => scope("seam", async () => {
|
|
903
824
|
const vetting = {
|
|
904
825
|
again: budget(),
|
|
@@ -908,7 +829,16 @@ const decide = () => scope("seam", async () => {
|
|
|
908
829
|
for (;;) {
|
|
909
830
|
await naivePass(retry);
|
|
910
831
|
const tip = kept("naive");
|
|
911
|
-
const
|
|
832
|
+
const verdict = await vetted(tip, vetting);
|
|
833
|
+
if (verdict === void 0 && naiveTestOn()) {
|
|
834
|
+
const sentBack = await tried(tip);
|
|
835
|
+
if (sentBack !== void 0) {
|
|
836
|
+
vetting.again = budget();
|
|
837
|
+
retry = sentBack;
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
const outcome = verdict ?? (flagged() ? await ruled(tip, vetting.again) : {
|
|
912
842
|
reference: tip.hash,
|
|
913
843
|
tip
|
|
914
844
|
});
|
|
@@ -977,7 +907,7 @@ const unwind = () => {
|
|
|
977
907
|
return run("unwind", revertScript(commit, FEEDBACK, unwindFailure(commit)), { label: "Unwinding your input" });
|
|
978
908
|
};
|
|
979
909
|
/**
|
|
980
|
-
* A naive pass (judged only
|
|
910
|
+
* A naive pass (judged only when a release flag is asked for), then implement and the
|
|
981
911
|
* quality lap, then the review; design must have run since `firstBase`.
|
|
982
912
|
*/
|
|
983
913
|
const buildFeature = async (firstBase) => {
|
|
@@ -1069,8 +999,7 @@ const ordinaryStart = async () => {
|
|
|
1069
999
|
file: ".gtd/TODO.md"
|
|
1070
1000
|
});
|
|
1071
1001
|
refusePendingHandOff();
|
|
1072
|
-
|
|
1073
|
-
prototypeRounds();
|
|
1002
|
+
naiveTestOn();
|
|
1074
1003
|
const stated = statedCriteria(changes(), head());
|
|
1075
1004
|
await unwind();
|
|
1076
1005
|
if (changes(".gtd/FEEDBACK.md").some((c) => c.status !== "deleted")) await human("unwind-failed", {
|
|
@@ -1086,4 +1015,4 @@ const feature = shipped(ordinaryStart);
|
|
|
1086
1015
|
//#region src/workflows/feature/index.ts
|
|
1087
1016
|
var feature_default = feature;
|
|
1088
1017
|
//#endregion
|
|
1089
|
-
export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, collecting, criteriaOf, criterionIds, feature_default as default, defaults, design, envDefaults, feature, fixNits,
|
|
1018
|
+
export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, collecting, criteriaOf, criterionIds, feature_default as default, defaults, design, envDefaults, feature, fixNits, hasImpact, holdsCallSites, implement, implementing, isTestFile, judgeSeam, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireCriteria, requireCriteriaFrozen, requireImpact, requireImpactOnly, requireNoNewFlags, requireNoTestFiles, requireStableIds, requireTestCallSitesOnly, requireWithin, reviewedRefactor, seamBuild, skills, skipDesign, 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 { b as reviewNotes, x as reviewRisks } from "../chunks/steering-CCNvngSH.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-
|
|
3
|
+
import { G as fixReviewNotesPrompt, St as envDefaults, W as finisherSystem, c as baseline, i as skills, j as agentWithSkills, n as summary, r as access, s as shipped, t as steering, v as REVIEW, x as coder, xt as defaults } from "../chunks/reserved-BzAFXuz_.mjs";
|
|
4
|
+
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-JQzB21Q3.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,7 +1,7 @@
|
|
|
1
1
|
import { read, refuse, removeScript, run, start } from "../flows.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { a as refusePendingHandOff } from "../chunks/tail-
|
|
4
|
-
import { r as reUnwind, t as buildTail } from "../chunks/review-
|
|
2
|
+
import { St as envDefaults, W as finisherSystem, _ as REQUIREMENTS, dt as reviewGateBlockedMessage, i as skills, j as agentWithSkills, l as gate, n as summary, r as access, s as shipped, t as steering, ut as reviewFeedbackFixPrompt, x as coder, xt as defaults } from "../chunks/reserved-BzAFXuz_.mjs";
|
|
3
|
+
import { a as refusePendingHandOff } from "../chunks/tail-JQzB21Q3.mjs";
|
|
4
|
+
import { r as reUnwind, t as buildTail } from "../chunks/review-CVqRS0mc.mjs";
|
|
5
5
|
//#region src/workflows/review/review.ts
|
|
6
6
|
const fixFeedback = async () => {
|
|
7
7
|
await agentWithSkills("feedback.fixing", reviewFeedbackFixPrompt(), {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { Ft as flipProblems, Lt as requireFlag, Nt as flagMentions, St as envDefaults, a as flagShipped, f as FEEDBACK, i as skills, l as gate, n as summary, r as access, t as steering, x as coder, xt as defaults } from "../chunks/reserved-BzAFXuz_.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.";
|