@pmelab/gtd 50.0.0 → 51.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 +32 -24
- package/dist/chunks/{reserved-CdOzKFVX.mjs → reserved-CUkk9XgL.mjs} +93 -4
- package/dist/chunks/{review-BChXEl2Y.mjs → review-BaCXlIT2.mjs} +2 -2
- package/dist/chunks/{tail-DRM2eC1z.mjs → tail-CX6Pdsql.mjs} +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +144 -11
- 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 +234 -12
- package/package.json +1 -1
- package/src/workflows/feature/coverage.feature +1 -0
- package/src/workflows/feature/fast-path.feature +1 -0
- package/src/workflows/feature/feature.ts +4 -1
- package/src/workflows/feature/flag-mode.feature +1 -1
- package/src/workflows/feature/lens-models.feature +1 -0
- package/src/workflows/feature/lens-review.feature +1 -0
- package/src/workflows/feature/one-step-land.feature +1 -0
- package/src/workflows/feature/prototype-rounds.feature +152 -0
- package/src/workflows/feature/prototype.feature +419 -0
- package/src/workflows/feature/prototype.test.ts +43 -0
- package/src/workflows/feature/prototype.ts +181 -0
- package/src/workflows/feature/refactor-absorbs.feature +9 -1
- package/src/workflows/feature/refactor-first.feature +13 -3
- package/src/workflows/feature/refactor-rebuild.feature +29 -5
- package/src/workflows/feature/refactor-review.feature +111 -13
- package/src/workflows/feature/refactor.test.ts +14 -4
- package/src/workflows/feature/refactor.ts +2 -0
- package/src/workflows/feature/review-answers.feature +1 -0
- package/src/workflows/feature/review-gate.feature +1 -0
- package/src/workflows/feature/reviewer-walkthrough.feature +1 -0
- package/src/workflows/feature/seam-rejection.feature +1 -0
- package/src/workflows/feature/seam.feature +1 -0
- package/src/workflows/feature/seam.test.ts +3 -2
- package/src/workflows/feature/seam.ts +19 -5
- package/src/workflows/feature/severity-budget.feature +1 -0
- package/src/workflows/feature/severity-sorted-review.feature +1 -0
- package/src/workflows/feature/slice-findings.feature +1 -0
- package/src/workflows/feature/slicing-cap.feature +9 -0
- package/src/workflows/feature/slicing.feature +1 -0
- package/src/workflows/feature/standards.feature +1 -0
- package/src/workflows/shared/options.ts +1 -0
- package/src/workflows/shared/prose.ts +6 -0
- package/src/workflows/shared/skills.test.ts +4 -0
- package/src/workflows/shared/skills.ts +5 -0
- package/src/workflows/shared/steps.ts +1 -0
- package/src/workflows/shared/text.ts +104 -3
- package/src/workflows/shared/vars.ts +3 -0
package/dist/workflows.mjs
CHANGED
|
@@ -17241,6 +17241,7 @@ const defaults = {
|
|
|
17241
17241
|
qualityReviews: "correctness, owasp-security, ponytail-review, test-audit, conventions, spec-challenge",
|
|
17242
17242
|
refactorQualityReviews: "correctness, ponytail-review, conventions",
|
|
17243
17243
|
continuations: "3",
|
|
17244
|
+
prototypeRounds: "3",
|
|
17244
17245
|
continuationStall: "2"
|
|
17245
17246
|
};
|
|
17246
17247
|
/** Environment settings. */
|
|
@@ -17318,6 +17319,11 @@ task in front of you end to end, tests first, and come back to fix
|
|
|
17318
17319
|
things when redirected — a failing check, or reviewer feedback. Stay
|
|
17319
17320
|
strictly inside that task; never touch code outside it. Treat feedback as the
|
|
17320
17321
|
diagnosis to act on; discard it, with reason, only when simply wrong.`;
|
|
17322
|
+
const testerPersona = `You are the hands-on tester in gtd's build pipeline: use what was just
|
|
17323
|
+
built the way a person would — run the real program, open the real
|
|
17324
|
+
page, call the real endpoint — and report exactly what you did and
|
|
17325
|
+
what you saw. Never fix anything yourself, and never pass a criterion
|
|
17326
|
+
you did not watch hold.`;
|
|
17321
17327
|
const finisherPersona = `You are the closing identity in gtd's build pipeline — the last coder
|
|
17322
17328
|
before it closes. Fix a late-breaking failing check after sign-off,
|
|
17323
17329
|
focused and minimal. Every turn lands its own commit — nothing here
|
|
@@ -17743,8 +17749,11 @@ const acceptanceCriteriaBlock = `
|
|
|
17743
17749
|
Whichever lap this is, \`.gtd/REQUIREMENTS.md\` carries a
|
|
17744
17750
|
\`## Acceptance Criteria\` section: one \`- AC-<n>: <criterion>\` line
|
|
17745
17751
|
per criterion, numbered from \`AC-1\`. They bind every later step —
|
|
17746
|
-
the build is done when each one holds — so each must be
|
|
17747
|
-
test
|
|
17752
|
+
the build is done when each one holds — so each must be behaviour a
|
|
17753
|
+
test can pin: one that fails when it does not hold. A green suite, a
|
|
17754
|
+
passing typecheck or lint, a generator or build that runs — the health
|
|
17755
|
+
check enforces those on every lap, so none is a criterion; fold what it
|
|
17756
|
+
must produce into a behaviour a test can observe. A later lap keeps every ID:
|
|
17748
17757
|
reword a criterion in place, append a new one with the next unused
|
|
17749
17758
|
number, delete a dropped one — never renumber, never reuse an ID. When
|
|
17750
17759
|
the file holds no list (a review loop-back rewrites it), recover the
|
|
@@ -17852,7 +17861,7 @@ const clearCriteriaQuestion = {
|
|
|
17852
17861
|
id: "clear",
|
|
17853
17862
|
primitive: "noul",
|
|
17854
17863
|
instructions: "state.criteria lists the acceptance criteria the human's sketch (state.sketch) stated, as gtd numbered them. Decide whether they can stand as the binding definition of done, with no design conversation.",
|
|
17855
|
-
criteria: "yes: every criterion is clear
|
|
17864
|
+
criteria: "yes: every criterion is clear behaviour a test can pin — a test would fail if it did not hold — and together they say what to build. no: any one criterion is vague, subjective or untestable, only a command can check it (a suite, typecheck, lint, build or generator run that passes), or the sketch leaves an open product question. Unsure means no."
|
|
17856
17865
|
};
|
|
17857
17866
|
const clearCriteriaMessage = () => `The sketch stated its own acceptance criteria. Run \`gtd judge answer\` and
|
|
17858
17867
|
pipe \`yes\` when every one is clear and checkable, so design is skipped and
|
|
@@ -18771,6 +18780,80 @@ Two ways forward (then run \`gtd land\`):
|
|
|
18771
18780
|
- **Overrule** — land a clean tree to finish without them. The slice records
|
|
18772
18781
|
the IDs it covers and, as \`uncovered\`, the ones it does not
|
|
18773
18782
|
`;
|
|
18783
|
+
const testerSystem = () => `${testerPersona}
|
|
18784
|
+
|
|
18785
|
+
${agentConduct}`;
|
|
18786
|
+
const PROTOTYPE_FORMAT = `- Write \`.gtd/PROTOTYPE.md\`, replacing any earlier report: one \`## \` chunk
|
|
18787
|
+
per criterion, headed \`## AC-<n>: pass\`, \`## AC-<n>: fail\` or
|
|
18788
|
+
\`## AC-<n>: blocked\`, then one chunk per problem you found outside any
|
|
18789
|
+
criterion, headed \`## Problem <short title>: fail\` (\`: pass\` once a
|
|
18790
|
+
re-test shows it fixed). Under each: the exact steps you ran, what you
|
|
18791
|
+
observed (output, a screenshot path, the error), and for a failure what
|
|
18792
|
+
you expected instead
|
|
18793
|
+
- \`blocked\` is for a criterion you could not exercise, or one whose
|
|
18794
|
+
expected behaviour itself looks wrong — say which and why: a person
|
|
18795
|
+
decides it, never a fixer`;
|
|
18796
|
+
const prototypeTestingPrompt = (slice, retest) => `${stateFileRules}
|
|
18797
|
+
- Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
|
|
18798
|
+
${sliceLine(slice)}- The change is built and its suite is green. Now test it by hand: use
|
|
18799
|
+
every acceptance criterion the way a person would and watch what
|
|
18800
|
+
happens. Running the test suite is not this — it already ran
|
|
18801
|
+
- Find how this project is exercised for real, in this order: a project
|
|
18802
|
+
skill that launches or drives it (e.g. \`.claude/skills/*\`); a manual
|
|
18803
|
+
testing or running section in \`AGENTS.md\`, \`CLAUDE.md\` or \`README.md\`;
|
|
18804
|
+
failing those, the project's shape — a browser or Playwright for a web
|
|
18805
|
+
project, the built CLI in a scratch directory for a CLI, a request client
|
|
18806
|
+
for an API
|
|
18807
|
+
- Found none and recognised no shape: write only a chunk headed
|
|
18808
|
+
\`## No testing instructions\` saying what you looked for, and finish —
|
|
18809
|
+
a person is asked; never guess
|
|
18810
|
+
- Test in a disposable place only — a scratch directory outside this
|
|
18811
|
+
repository, a throwaway repository, a dev server on a free port — never
|
|
18812
|
+
against the user's real data or this working tree. Point every tool's
|
|
18813
|
+
output (screenshots, traces, test results) outside the repository too
|
|
18814
|
+
${retest ? `- This is a re-test: \`.gtd/PROTOTYPE.md\` holds the last round's report and
|
|
18815
|
+
a fix turn or a person has changed the code since. Re-test every failed or blocked criterion and
|
|
18816
|
+
every problem it lists in full, then re-run the passed ones quickly
|
|
18817
|
+
` : ""}${PROTOTYPE_FORMAT}
|
|
18818
|
+
- Change nothing but \`.gtd/PROTOTYPE.md\` — a turn that does is refused
|
|
18819
|
+
- Leave everything uncommitted and finish your turn — do not commit
|
|
18820
|
+
`;
|
|
18821
|
+
const prototypeFixingPrompt = (slice) => `${stateFileRules}
|
|
18822
|
+
- Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
|
|
18823
|
+
${sliceLine(slice)}${criteriaBinding(true)}
|
|
18824
|
+
- Read \`.gtd/PROTOTYPE.md\`: a hands-on test of the change. Fix every
|
|
18825
|
+
\`fail\` chunk of this change's criteria${slice === void 0 ? "" : " (this slice's only)"} or a problem found
|
|
18826
|
+
beside them, at its cause, with a test that shows it; leave \`blocked\`
|
|
18827
|
+
ones to the person
|
|
18828
|
+
- A \`fail\` the code does not explain (the tester misread what it saw) has
|
|
18829
|
+
nothing to fix: leave it and change nothing for it — the next round re-tests
|
|
18830
|
+
- Leave \`.gtd/PROTOTYPE.md\` as it is: the next round re-tests from it
|
|
18831
|
+
- Run \`${env.testCommand}\` and finish only once it is green
|
|
18832
|
+
- Leave everything uncommitted and finish your turn — do not commit
|
|
18833
|
+
`;
|
|
18834
|
+
const openItems = (items) => items.map((item) => `- ${item.subject}: ${item.verdict}`).join("\n");
|
|
18835
|
+
const prototypeOpenMessage = (items, rounds) => `The hands-on test of this slice still has open items${rounds > 0 ? ` after ${rounds} fix round${rounds === 1 ? "" : "s"}` : ", and none a fixer can take"}:
|
|
18836
|
+
${openItems(items)}
|
|
18837
|
+
|
|
18838
|
+
\`.gtd/PROTOTYPE.md\` holds the last report: the steps run and what was seen.
|
|
18839
|
+
|
|
18840
|
+
Two ways forward (then run \`gtd land\`):
|
|
18841
|
+
- **Fix** — change the code, or the testing instructions, yourself (the
|
|
18842
|
+
criteria are settled: an edit to them is refused); the suite runs and the
|
|
18843
|
+
slice is tested again, with a fresh round budget
|
|
18844
|
+
- **Overrule** — land a clean tree to go on to the quality lap with these
|
|
18845
|
+
items open
|
|
18846
|
+
`;
|
|
18847
|
+
const prototypeUnguidedMessage = () => `The hands-on test found no way to exercise this project for real — no
|
|
18848
|
+
project skill, no manual testing section in \`AGENTS.md\`, \`CLAUDE.md\` or
|
|
18849
|
+
\`README.md\`, and no project shape it recognised. \`.gtd/PROTOTYPE.md\` says
|
|
18850
|
+
what it looked for.
|
|
18851
|
+
|
|
18852
|
+
Two ways forward (then run \`gtd land\`):
|
|
18853
|
+
- **Instruct** — add testing instructions (a project skill, or a manual
|
|
18854
|
+
testing section); the slice is tested again
|
|
18855
|
+
- **Skip** — land a clean tree to go on to the quality lap untested
|
|
18856
|
+
`;
|
|
18774
18857
|
//#endregion
|
|
18775
18858
|
//#region src/workflows/shared/steps.ts
|
|
18776
18859
|
const FEEDBACK = ".gtd/FEEDBACK.md";
|
|
@@ -18782,6 +18865,7 @@ const REFACTOR_NOTE = ".gtd/REFACTOR-NOTE.md";
|
|
|
18782
18865
|
const QUALITY = ".gtd/QUALITY.md";
|
|
18783
18866
|
const SLICES = ".gtd/SLICES.md";
|
|
18784
18867
|
const HANDOFF = ".gtd/HANDOFF.md";
|
|
18868
|
+
const PROTOTYPE = ".gtd/PROTOTYPE.md";
|
|
18785
18869
|
const planner = () => env.plannerModel ?? "";
|
|
18786
18870
|
const coder = () => env.coderModel ?? "";
|
|
18787
18871
|
const fixCheck = (rules) => agentWithSkills("fix", buildFixPrompt(rules), {
|
|
@@ -38499,6 +38583,7 @@ const ShippedOptions = Struct({
|
|
|
38499
38583
|
qualityReviews: setting$1,
|
|
38500
38584
|
refactorQualityReviews: setting$1,
|
|
38501
38585
|
continuations: setting$1,
|
|
38586
|
+
prototypeRounds: setting$1,
|
|
38502
38587
|
continuationStall: setting$1,
|
|
38503
38588
|
testCommand: setting$1,
|
|
38504
38589
|
plannerModel: setting$1,
|
|
@@ -39748,7 +39833,9 @@ const reviewedRefactor = async (shape, options = {}) => {
|
|
|
39748
39833
|
feedback,
|
|
39749
39834
|
fixAccess: strayAccess(p.strays),
|
|
39750
39835
|
lensBase: entry,
|
|
39751
|
-
rules: refactorRules(p.strays, p.pattern)
|
|
39836
|
+
rules: refactorRules(p.strays, p.pattern),
|
|
39837
|
+
walkthrough: true,
|
|
39838
|
+
lap: "refactor"
|
|
39752
39839
|
});
|
|
39753
39840
|
if (outcome.verdict === "signoff") return true;
|
|
39754
39841
|
await reUnwind(outcome);
|
|
@@ -39763,7 +39850,7 @@ const reviewedRefactor = async (shape, options = {}) => {
|
|
|
39763
39850
|
const COVERED_MIN_P = .7;
|
|
39764
39851
|
/** Covering turns before the process rests at `uncovered`; a human edit there refills them. */
|
|
39765
39852
|
const COVERING_RETRIES = 2;
|
|
39766
|
-
const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
|
|
39853
|
+
const frozen$1 = () => requireCriteriaFrozen(REQUIREMENTS);
|
|
39767
39854
|
const green = (guard) => healthy(() => agentWithSkills("fix.suite.fixing", implementFixPrompt(), {
|
|
39768
39855
|
label: "Fixing the check",
|
|
39769
39856
|
file: FEEDBACK,
|
|
@@ -39771,7 +39858,7 @@ const green = (guard) => healthy(() => agentWithSkills("fix.suite.fixing", imple
|
|
|
39771
39858
|
system: builderSystem()
|
|
39772
39859
|
}), {
|
|
39773
39860
|
guard: () => {
|
|
39774
|
-
frozen();
|
|
39861
|
+
frozen$1();
|
|
39775
39862
|
guard?.();
|
|
39776
39863
|
},
|
|
39777
39864
|
guardRests: false
|
|
@@ -39824,7 +39911,7 @@ const requireCoverage = async (base = start(), slice) => {
|
|
|
39824
39911
|
system: builderSystem(),
|
|
39825
39912
|
allowEmpty: true
|
|
39826
39913
|
});
|
|
39827
|
-
frozen();
|
|
39914
|
+
frozen$1();
|
|
39828
39915
|
} else {
|
|
39829
39916
|
await human("uncovered", {
|
|
39830
39917
|
message: uncoveredMessage(uncovered),
|
|
@@ -39833,13 +39920,136 @@ const requireCoverage = async (base = start(), slice) => {
|
|
|
39833
39920
|
acceptClean: true
|
|
39834
39921
|
});
|
|
39835
39922
|
if (!changes().some((c) => isCode$1(c.path))) return record(covered, uncovered);
|
|
39836
|
-
frozen();
|
|
39923
|
+
frozen$1();
|
|
39837
39924
|
retries = 0;
|
|
39838
39925
|
}
|
|
39839
39926
|
await green();
|
|
39840
39927
|
}
|
|
39841
39928
|
};
|
|
39842
39929
|
//#endregion
|
|
39930
|
+
//#region src/workflows/feature/prototype.ts
|
|
39931
|
+
/** The `prototypeRounds` setting. Anything but a non-negative integer fails rather than guess a cap. */
|
|
39932
|
+
const prototypeRounds = () => {
|
|
39933
|
+
const value = vars.prototypeRounds ?? defaults.prototypeRounds;
|
|
39934
|
+
if (!/^(0|[1-9]\d*)$/.test(value)) refuse(`gtd: prototypeRounds must be a non-negative integer, got ${JSON.stringify(value)}`);
|
|
39935
|
+
return Number(value);
|
|
39936
|
+
};
|
|
39937
|
+
const CHUNK = /^(AC-\d+|Problem\b.*?)\s*:\s*(pass|fail|blocked)$/i;
|
|
39938
|
+
const UNGUIDED = /^no testing instructions$/i;
|
|
39939
|
+
/** The `## ` chunks of a prototype report; a heading in neither shape is prose, not an item. */
|
|
39940
|
+
const reportOf = (text) => {
|
|
39941
|
+
let unguided = false;
|
|
39942
|
+
const items = [];
|
|
39943
|
+
const malformed = [];
|
|
39944
|
+
for (const { line, fenced } of markFenced(text)) {
|
|
39945
|
+
const heading = fenced ? null : HEADING.exec(line);
|
|
39946
|
+
if (heading?.[1] !== "##") continue;
|
|
39947
|
+
if (UNGUIDED.test(heading[2])) unguided = true;
|
|
39948
|
+
const chunk = CHUNK.exec(heading[2]);
|
|
39949
|
+
if (chunk === null && /^(ac-\d|problem)/i.test(heading[2])) malformed.push(heading[2]);
|
|
39950
|
+
if (chunk !== null) items.push({
|
|
39951
|
+
subject: /^ac-/i.test(chunk[1]) ? chunk[1].toUpperCase() : chunk[1],
|
|
39952
|
+
verdict: chunk[2].toLowerCase()
|
|
39953
|
+
});
|
|
39954
|
+
}
|
|
39955
|
+
return {
|
|
39956
|
+
unguided,
|
|
39957
|
+
items,
|
|
39958
|
+
malformed
|
|
39959
|
+
};
|
|
39960
|
+
};
|
|
39961
|
+
/**
|
|
39962
|
+
* The report the testing turn left, refused unless it is the only change and
|
|
39963
|
+
* names every criterion once, with its open items: another slice's criteria
|
|
39964
|
+
* are not this slice's to fix.
|
|
39965
|
+
*/
|
|
39966
|
+
const requireReport = (slice, retest) => {
|
|
39967
|
+
const others = changes().map((c) => c.path).filter((path) => path !== PROTOTYPE);
|
|
39968
|
+
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")}`);
|
|
39969
|
+
const text = read(PROTOTYPE);
|
|
39970
|
+
if (text === void 0) return refuse(`gtd land: prototype-report: write ${PROTOTYPE}`);
|
|
39971
|
+
const report = reportOf(text);
|
|
39972
|
+
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")}`);
|
|
39973
|
+
if (report.unguided && report.items.length === 0 && !retest) return {
|
|
39974
|
+
unguided: true,
|
|
39975
|
+
open: []
|
|
39976
|
+
};
|
|
39977
|
+
const requirements = read(".gtd/REQUIREMENTS.md") ?? "";
|
|
39978
|
+
const ids = criterionIds(requirements, slice);
|
|
39979
|
+
const elsewhere = criterionIds(requirements).filter((id) => !ids.includes(id));
|
|
39980
|
+
const count = (id) => report.items.filter((i) => i.subject === id).length;
|
|
39981
|
+
const wrong = ids.filter((id) => count(id) !== 1);
|
|
39982
|
+
if (wrong.length > 0) refuse(`gtd land: prototype-report: ${PROTOTYPE} needs exactly one chunk headed \`## <ID>: pass|fail|blocked\` for ${wrong.join(", ")}`);
|
|
39983
|
+
return {
|
|
39984
|
+
unguided: false,
|
|
39985
|
+
open: report.items.filter((i) => i.verdict !== "pass" && !elsewhere.includes(i.subject))
|
|
39986
|
+
};
|
|
39987
|
+
};
|
|
39988
|
+
const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
|
|
39989
|
+
const testing = async (slice, retest) => {
|
|
39990
|
+
await agentWithSkills("testing", prototypeTestingPrompt(sliceIds(slice), retest), {
|
|
39991
|
+
label: retest ? "Re-testing the slice by hand" : "Testing the slice by hand",
|
|
39992
|
+
file: PROTOTYPE,
|
|
39993
|
+
model: coder(),
|
|
39994
|
+
system: testerSystem(),
|
|
39995
|
+
allowEmpty: true
|
|
39996
|
+
});
|
|
39997
|
+
return requireReport(slice, retest);
|
|
39998
|
+
};
|
|
39999
|
+
const fixing = async (slice, guard) => {
|
|
40000
|
+
await scope("fix", () => agentWithSkills("fixing", prototypeFixingPrompt(sliceIds(slice)), {
|
|
40001
|
+
label: "Fixing what the hands-on test found",
|
|
40002
|
+
file: PROTOTYPE,
|
|
40003
|
+
model: coder(),
|
|
40004
|
+
system: builderSystem(),
|
|
40005
|
+
allowEmpty: true
|
|
40006
|
+
}));
|
|
40007
|
+
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`);
|
|
40008
|
+
frozen();
|
|
40009
|
+
guard?.();
|
|
40010
|
+
};
|
|
40011
|
+
/** A person's rest; resolves `true` when they changed something to test again, `false` on a clean overrule. */
|
|
40012
|
+
const askPerson = async (name, message, label) => {
|
|
40013
|
+
await human(name, {
|
|
40014
|
+
message,
|
|
40015
|
+
label,
|
|
40016
|
+
file: PROTOTYPE,
|
|
40017
|
+
acceptClean: true
|
|
40018
|
+
});
|
|
40019
|
+
if (!changes().some((c) => c.path !== ".gtd/PROTOTYPE.md")) return false;
|
|
40020
|
+
frozen();
|
|
40021
|
+
return true;
|
|
40022
|
+
};
|
|
40023
|
+
/**
|
|
40024
|
+
* Use the slice by hand: a testing turn reports on every criterion, a fix turn
|
|
40025
|
+
* takes each failure, and the slice is tested again — until the report is
|
|
40026
|
+
* clean, or `prototypeRounds` fixes are spent and a person decides. `guard`
|
|
40027
|
+
* holds every fix turn, as it holds the implementation's.
|
|
40028
|
+
*/
|
|
40029
|
+
const prototypeTest = async (slice, guard) => {
|
|
40030
|
+
const cap = prototypeRounds();
|
|
40031
|
+
if (cap === 0) return;
|
|
40032
|
+
await scope("prototype", async () => {
|
|
40033
|
+
let fixes = 0;
|
|
40034
|
+
let retest = false;
|
|
40035
|
+
for (;;) {
|
|
40036
|
+
const { unguided, open } = await testing(slice, retest);
|
|
40037
|
+
retest = !unguided;
|
|
40038
|
+
if (unguided) {
|
|
40039
|
+
if (!await askPerson("unguided", prototypeUnguidedMessage(), "No way to test by hand")) return;
|
|
40040
|
+
} else if (open.length === 0) return;
|
|
40041
|
+
else if (fixes < cap && open.some((i) => i.verdict === "fail")) {
|
|
40042
|
+
fixes++;
|
|
40043
|
+
await fixing(slice, guard);
|
|
40044
|
+
} else {
|
|
40045
|
+
if (!await askPerson("open", prototypeOpenMessage(open, fixes), "Hands-on test still failing")) return;
|
|
40046
|
+
fixes = 0;
|
|
40047
|
+
}
|
|
40048
|
+
await green(guard);
|
|
40049
|
+
}
|
|
40050
|
+
});
|
|
40051
|
+
};
|
|
40052
|
+
//#endregion
|
|
39843
40053
|
//#region src/workflows/feature/seam.ts
|
|
39844
40054
|
/** Naive-pass retries after a `reject` or a red suite; the next one rests at `seam.steer`. */
|
|
39845
40055
|
const NAIVE_RETRIES = 2;
|
|
@@ -40046,8 +40256,10 @@ const implementing = (options) => scope("implement", async () => {
|
|
|
40046
40256
|
...acceptance,
|
|
40047
40257
|
ids: settled(acceptance.ids, options.slice)
|
|
40048
40258
|
}, since);
|
|
40049
|
-
|
|
40259
|
+
const keepsTests = built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0;
|
|
40260
|
+
await green(keepsTests);
|
|
40050
40261
|
await requireCoverage(options.base, options.slice);
|
|
40262
|
+
await prototypeTest(options.slice, keepsTests);
|
|
40051
40263
|
const ids = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
|
|
40052
40264
|
await closing(moreToBuild(options.more));
|
|
40053
40265
|
return ids;
|
|
@@ -40056,11 +40268,16 @@ const moreToBuild = (more) => typeof more === "function" ? more() : more === tru
|
|
|
40056
40268
|
/** The last slice's closing, for a last slice a steer left with no criterion to build. */
|
|
40057
40269
|
const closeOut = () => scope("implement", () => closing(false));
|
|
40058
40270
|
/** A slice with `more` after it keeps the criteria and the slicing for the next one. */
|
|
40059
|
-
const closing = (more) => run("closing", removeScript(more ? [
|
|
40271
|
+
const closing = (more) => run("closing", removeScript(more ? [
|
|
40272
|
+
REFACTOR_NOTE,
|
|
40273
|
+
HANDOFF,
|
|
40274
|
+
PROTOTYPE
|
|
40275
|
+
] : [
|
|
40060
40276
|
REQUIREMENTS,
|
|
40061
40277
|
REFACTOR_NOTE,
|
|
40062
40278
|
SLICES,
|
|
40063
|
-
HANDOFF
|
|
40279
|
+
HANDOFF,
|
|
40280
|
+
PROTOTYPE
|
|
40064
40281
|
]), { label: "Closing out the implementation" });
|
|
40065
40282
|
/** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
|
|
40066
40283
|
const steer = async (retry) => {
|
|
@@ -40425,6 +40642,7 @@ const ordinaryStart = async () => {
|
|
|
40425
40642
|
refusePendingHandOff();
|
|
40426
40643
|
flagModeOn();
|
|
40427
40644
|
sliceCap();
|
|
40645
|
+
prototypeRounds();
|
|
40428
40646
|
const stated = statedCriteria(changes(), head$1());
|
|
40429
40647
|
await unwind();
|
|
40430
40648
|
if (changes(".gtd/FEEDBACK.md").some((c) => c.status !== "deleted")) await human("unwind-failed", {
|
|
@@ -40459,6 +40677,10 @@ const skills = (vars) => {
|
|
|
40459
40677
|
seam: ["incremental-implementation"],
|
|
40460
40678
|
implement: ["test-driven-development", "incremental-implementation"],
|
|
40461
40679
|
"implement.fix.suite": ["debugging-and-error-recovery"],
|
|
40680
|
+
"implement.prototype": [],
|
|
40681
|
+
"implement.prototype.fix": ["test-driven-development", "incremental-implementation"],
|
|
40682
|
+
"implement.prototype.fix.suite": ["debugging-and-error-recovery"],
|
|
40683
|
+
"implement.prototype.health": ["debugging-and-error-recovery"],
|
|
40462
40684
|
"implement.health": ["debugging-and-error-recovery"],
|
|
40463
40685
|
...lensScopes("build.quality", lensesOf(vars.qualityReviews ?? ""), (lens) => builtInLenses[lens]?.skills ?? [lens]),
|
|
40464
40686
|
...lensScopes("refactor.build.quality", lensesOf(vars.refactorQualityReviews ?? ""), (lens) => builtInLenses[lens]?.skills ?? [lens])
|
|
@@ -40636,4 +40858,4 @@ var workflows_default = feature;
|
|
|
40636
40858
|
*/
|
|
40637
40859
|
const doors = {};
|
|
40638
40860
|
//#endregion
|
|
40639
|
-
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, describeEscalation, design, doors, envDefaults, escalate, escalation, escalationExhausted, feature, fix, fixCheck, fixFlow, fixNits, fixQuality, 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 };
|
|
40861
|
+
export { COMMENT, ESCALATION, FEEDBACK, FIX_CAP, FlagOptions, HANDOFF, PROTOTYPE, 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, describeEscalation, design, doors, envDefaults, escalate, escalation, escalationExhausted, feature, fix, fixCheck, fixFlow, fixNits, fixQuality, 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
|
@@ -9,6 +9,7 @@ Feature: a feature is done only when every acceptance criterion has a passing te
|
|
|
9
9
|
|
|
10
10
|
Background:
|
|
11
11
|
Given the shipped workflows, as gtd install scaffolds them
|
|
12
|
+
And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
|
|
12
13
|
And an environment variable "GTD_QUALITYREVIEWS" set to ""
|
|
13
14
|
And an environment variable "GTD_FLAGMODE" set to "false"
|
|
14
15
|
And a file "src/calc.ts" with:
|
|
@@ -8,6 +8,7 @@ Feature: a trivial change takes the same few beats at either risk tier
|
|
|
8
8
|
|
|
9
9
|
Background:
|
|
10
10
|
Given the shipped workflows, as gtd install scaffolds them
|
|
11
|
+
And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
|
|
11
12
|
And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
|
|
12
13
|
And an environment variable "GTD_FLAG_RISK_TIER" set to "1"
|
|
13
14
|
And a file "src/greeting.ts" with:
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from "./criteria.js"
|
|
29
29
|
import { design } from "./planning.js"
|
|
30
30
|
import { buildTail, humanReview, isCode, reUnwind, type ReviewOutcome } from "../shared/review.js"
|
|
31
|
+
import { prototypeRounds } from "./prototype.js"
|
|
31
32
|
import { implement, implementing, seamBuild } from "./seam.js"
|
|
32
33
|
import { sliceCap, sliceCriteria, slicing } from "./slicing.js"
|
|
33
34
|
import { recordRisk, riskTierOn, type RiskTier } from "./tier.js"
|
|
@@ -36,7 +37,8 @@ import { recordRisk, riskTierOn, type RiskTier } from "./tier.js"
|
|
|
36
37
|
// `unwind` reverts the sketch (its intent survives in history) → a
|
|
37
38
|
// green-baseline gate → design → per slice of the criteria, a naive pass,
|
|
38
39
|
// judged with `flagMode` on: it lands as the implementation, or (refactoring
|
|
39
|
-
// first when the judge asks) is built fresh from → the health loop →
|
|
40
|
+
// first when the judge asks) is built fresh from → the health loop → a
|
|
41
|
+
// hands-on test of every criterion, fixed and re-tested → the
|
|
40
42
|
// triaged quality lap, whose leftovers are carried on → after the last slice,
|
|
41
43
|
// a reviewer's walkthrough of the whole change, every slice's findings
|
|
42
44
|
// among its chunks, for the human, which
|
|
@@ -227,6 +229,7 @@ export const ordinaryStart = async (): Promise<void> => {
|
|
|
227
229
|
// Read right after idle, so a bad value refuses the landing that would pin it.
|
|
228
230
|
flagModeOn()
|
|
229
231
|
sliceCap()
|
|
232
|
+
prototypeRounds()
|
|
230
233
|
const stated = statedCriteria(changes(), head())
|
|
231
234
|
await unwind()
|
|
232
235
|
if (changes(FEEDBACK).some((c) => c.status !== "deleted")) {
|
|
@@ -11,7 +11,7 @@ Feature: flag mode off lands the naive pass with no release flag, seam judge or
|
|
|
11
11
|
"""
|
|
12
12
|
import { feature } from "@pmelab/gtd/workflows/feature"
|
|
13
13
|
export { summary, steering, skills, access } from "@pmelab/gtd/workflows/feature"
|
|
14
|
-
export default feature({ flagMode: false })
|
|
14
|
+
export default feature({ flagMode: false, prototypeRounds: "0" })
|
|
15
15
|
"""
|
|
16
16
|
And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
|
|
17
17
|
And a file "src/calc.ts" with:
|
|
@@ -12,6 +12,7 @@ Feature: each quality lens runs on its own model hint
|
|
|
12
12
|
export { summary, steering, skills, access } from "@pmelab/gtd/workflows/feature"
|
|
13
13
|
export default feature({
|
|
14
14
|
flagMode: false,
|
|
15
|
+
prototypeRounds: "0",
|
|
15
16
|
qualityReviews: "correctness, conventions, test-audit=opus",
|
|
16
17
|
})
|
|
17
18
|
"""
|
|
@@ -9,6 +9,7 @@ Feature: the quality lap's findings are triaged before the review
|
|
|
9
9
|
|
|
10
10
|
Background:
|
|
11
11
|
Given the shipped workflows, as gtd install scaffolds them
|
|
12
|
+
And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
|
|
12
13
|
And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
|
|
13
14
|
And an environment variable "GTD_FLAGMODE" set to "false"
|
|
14
15
|
And a file "src/calc.ts" with:
|
|
@@ -10,6 +10,7 @@ Feature: a naive pass the judge rules land is itself the implementation
|
|
|
10
10
|
|
|
11
11
|
Background:
|
|
12
12
|
Given the shipped workflows, as gtd install scaffolds them
|
|
13
|
+
And an environment variable "GTD_PROTOTYPEROUNDS" set to "0"
|
|
13
14
|
And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
|
|
14
15
|
And an environment variable "GTD_FLAG_RISK_TIER" set to "0"
|
|
15
16
|
And a file "src/greeting.ts" with:
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
@inmem
|
|
2
|
+
Feature: past its round cap the hands-on test rests at a person's gate
|
|
3
|
+
|
|
4
|
+
`prototypeRounds` caps the fix turns a slice's hands-on test gets. Once they
|
|
5
|
+
are spent and the report still has open items, the process rests at
|
|
6
|
+
implement.prototype.open naming them; a person's fix there is tested again
|
|
7
|
+
with a fresh budget.
|
|
8
|
+
|
|
9
|
+
Background:
|
|
10
|
+
Given the shipped workflows, as gtd install scaffolds them
|
|
11
|
+
And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
|
|
12
|
+
And an environment variable "GTD_FLAGMODE" set to "false"
|
|
13
|
+
And an environment variable "GTD_PROTOTYPEROUNDS" set to "1"
|
|
14
|
+
And a file "src/calc.ts" with:
|
|
15
|
+
"""
|
|
16
|
+
export const add = (a: number, b: number) => a + b
|
|
17
|
+
"""
|
|
18
|
+
And the working tree is committed as "feat: add calculator"
|
|
19
|
+
And a file "TODO.md" with:
|
|
20
|
+
"""
|
|
21
|
+
# Add subtract
|
|
22
|
+
|
|
23
|
+
subtract(a, b) returns the difference.
|
|
24
|
+
"""
|
|
25
|
+
And step "unwind" changes nothing
|
|
26
|
+
And step "start-gate.check" changes nothing
|
|
27
|
+
And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
28
|
+
"""
|
|
29
|
+
# Requirements
|
|
30
|
+
|
|
31
|
+
## Acceptance Criteria
|
|
32
|
+
|
|
33
|
+
- AC-1: subtract(a, b) returns a - b.
|
|
34
|
+
- AC-2: add is unchanged.
|
|
35
|
+
"""
|
|
36
|
+
And step "design.gate.answer" changes nothing
|
|
37
|
+
And step "seam.naive" writes "src/calc.ts" with:
|
|
38
|
+
"""
|
|
39
|
+
export const add = (a: number, b: number) => a + b
|
|
40
|
+
export const subtract = (a: number, b: number) => a + b
|
|
41
|
+
"""
|
|
42
|
+
And step "seam.naive" writes "src/calc.test.ts" with:
|
|
43
|
+
"""
|
|
44
|
+
it("[AC-1] subtracts", () => expect(subtract(3, 1)).toBe(2))
|
|
45
|
+
it("[AC-2] adds", () => expect(add(1, 1)).toBe(2))
|
|
46
|
+
"""
|
|
47
|
+
And step "seam.check" changes nothing
|
|
48
|
+
And step "implement.applying" runs its script
|
|
49
|
+
And step "implement.health.check" changes nothing
|
|
50
|
+
And judge step "implement.coverage" answers:
|
|
51
|
+
"""
|
|
52
|
+
[{"id":"AC-1","answer":"yes","p":0.9},{"id":"AC-2","answer":"yes","p":0.9}]
|
|
53
|
+
"""
|
|
54
|
+
And step "implement.closing" runs its script
|
|
55
|
+
|
|
56
|
+
Scenario: past the round cap a person is asked, naming the open items
|
|
57
|
+
Given step "implement.prototype.testing", when prompted with "Find how this project", writes ".gtd/PROTOTYPE.md" with:
|
|
58
|
+
"""
|
|
59
|
+
## AC-1: fail
|
|
60
|
+
|
|
61
|
+
`subtract(3, 1)` printed 4. Expected 2.
|
|
62
|
+
|
|
63
|
+
## AC-2: pass
|
|
64
|
+
|
|
65
|
+
Printed 2.
|
|
66
|
+
"""
|
|
67
|
+
And step "implement.prototype.testing", when prompted with "This is a re-test", writes ".gtd/PROTOTYPE.md" with:
|
|
68
|
+
"""
|
|
69
|
+
## AC-1: fail
|
|
70
|
+
|
|
71
|
+
Re-tested: `subtract(3, 1)` printed -2. Expected 2.
|
|
72
|
+
|
|
73
|
+
## AC-2: pass
|
|
74
|
+
|
|
75
|
+
Printed 2.
|
|
76
|
+
"""
|
|
77
|
+
And step "implement.prototype.fix.fixing" writes "src/calc.ts" with:
|
|
78
|
+
"""
|
|
79
|
+
export const add = (a: number, b: number) => a + b
|
|
80
|
+
export const subtract = (a: number, b: number) => b - a
|
|
81
|
+
"""
|
|
82
|
+
And step "implement.prototype.health.check" changes nothing
|
|
83
|
+
When the process is driven
|
|
84
|
+
Then the process rests at "implement.prototype.open"
|
|
85
|
+
And the resting step's content contains "- AC-1: fail"
|
|
86
|
+
And the resting step's content contains "after 1 fix round"
|
|
87
|
+
And the resting step's content does not contain "AC-2"
|
|
88
|
+
|
|
89
|
+
Scenario: a person's change at the gate is tested again
|
|
90
|
+
Given step "implement.prototype.testing", when prompted with "Find how this project", writes ".gtd/PROTOTYPE.md" with:
|
|
91
|
+
"""
|
|
92
|
+
## AC-1: blocked
|
|
93
|
+
|
|
94
|
+
`subtract` is not exported from any entry point I could run.
|
|
95
|
+
|
|
96
|
+
## AC-2: pass
|
|
97
|
+
|
|
98
|
+
Printed 2.
|
|
99
|
+
"""
|
|
100
|
+
And step "implement.prototype.open" writes "src/calc.ts" with:
|
|
101
|
+
"""
|
|
102
|
+
export const add = (a: number, b: number) => a + b
|
|
103
|
+
export const subtract = (a: number, b: number) => a - b
|
|
104
|
+
"""
|
|
105
|
+
And step "implement.prototype.health.check" changes nothing
|
|
106
|
+
And step "implement.prototype.testing", when prompted with "This is a re-test", writes ".gtd/PROTOTYPE.md" with:
|
|
107
|
+
"""
|
|
108
|
+
## AC-1: pass
|
|
109
|
+
|
|
110
|
+
`subtract(3, 1)` printed 2.
|
|
111
|
+
|
|
112
|
+
## AC-2: pass
|
|
113
|
+
|
|
114
|
+
Printed 2.
|
|
115
|
+
"""
|
|
116
|
+
When the process is driven
|
|
117
|
+
Then the process rests at "build.quality.correctness.reviewing"
|
|
118
|
+
And the commit subjects end with:
|
|
119
|
+
"""
|
|
120
|
+
gtd(agent): implement.prototype.testing → implement.prototype.open
|
|
121
|
+
gtd(human): implement.prototype.open → implement.prototype.health.check
|
|
122
|
+
gtd(check): implement.prototype.health.check → implement.prototype.testing
|
|
123
|
+
gtd(agent): implement.prototype.testing → implement.closing
|
|
124
|
+
gtd(check): implement.closing → build.quality.correctness.reviewing
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
Scenario: a re-test that sees exactly what the last round saw still ends the round
|
|
128
|
+
Given step "implement.prototype.testing" writes ".gtd/PROTOTYPE.md" with:
|
|
129
|
+
"""
|
|
130
|
+
## AC-1: fail
|
|
131
|
+
|
|
132
|
+
`subtract(3, 1)` printed 4. Expected 2.
|
|
133
|
+
|
|
134
|
+
## AC-2: pass
|
|
135
|
+
|
|
136
|
+
Printed 2.
|
|
137
|
+
"""
|
|
138
|
+
And step "implement.prototype.fix.fixing" writes "src/calc.ts" with:
|
|
139
|
+
"""
|
|
140
|
+
export const add = (a: number, b: number) => a + b
|
|
141
|
+
export const subtract = (a: number, b: number) => b - a
|
|
142
|
+
"""
|
|
143
|
+
And step "implement.prototype.health.check" changes nothing
|
|
144
|
+
When the process is driven
|
|
145
|
+
Then the process rests at "implement.prototype.open"
|
|
146
|
+
And the commit subjects end with:
|
|
147
|
+
"""
|
|
148
|
+
gtd(agent): implement.prototype.testing → implement.prototype.fix.fixing
|
|
149
|
+
gtd(agent): implement.prototype.fix.fixing → implement.prototype.health.check
|
|
150
|
+
gtd(check): implement.prototype.health.check → implement.prototype.testing
|
|
151
|
+
gtd(agent): implement.prototype.testing → implement.prototype.open
|
|
152
|
+
"""
|