@pmelab/gtd 45.2.0 → 47.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 +15 -30
- package/dist/chunks/{reserved-FWrjy7zo.mjs → reserved-DF1HD6jL.mjs} +42 -48
- package/dist/chunks/{review-Dhl4vvDe.mjs → review-Bv40kZu9.mjs} +9 -23
- package/dist/chunks/{tail-B-Es6Hwj.mjs → tail-D89fU_wf.mjs} +68 -39
- package/dist/gtd.bundle.mjs +5 -7
- package/dist/testkit.mjs +5 -7
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +26 -22
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +14 -4
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +270 -429
- package/package.json +1 -6
- package/src/install/ConfigScaffold.ts +1 -2
- package/src/install/Install.ts +4 -5
- package/src/workflows/cleanup/cleanup.test.ts +11 -9
- package/src/workflows/feature/feature.ts +38 -28
- package/src/workflows/feature/lens-review.feature +3 -1
- package/src/workflows/feature/slice-findings.feature +198 -0
- package/src/workflows/fix/fix-lens-origin.feature +101 -0
- package/src/workflows/fix/fix-quality.feature +131 -0
- package/src/workflows/index.ts +1 -3
- package/src/workflows/review/review.ts +15 -3
- package/src/workflows/shared/lenses.ts +30 -9
- package/src/workflows/shared/options.ts +1 -21
- package/src/workflows/shared/review.ts +5 -27
- package/src/workflows/shared/skills.test.ts +0 -2
- package/src/workflows/shared/skills.ts +0 -3
- package/src/workflows/shared/tail.ts +74 -57
- package/src/workflows/shared/text.ts +86 -33
- package/src/workflows/shared/vars.ts +0 -8
- package/dist/workflows/tests.mjs +0 -193
- package/src/workflows/feature/quality-unresolved.feature +0 -121
- package/src/workflows/tests/index.ts +0 -6
- package/src/workflows/tests/tests-options.feature +0 -52
- package/src/workflows/tests/tests.feature +0 -266
- package/src/workflows/tests/tests.test.ts +0 -51
- package/src/workflows/tests/tests.ts +0 -270
package/README.md
CHANGED
|
@@ -504,12 +504,12 @@ noted in steps 3 and 4 below.
|
|
|
504
504
|
unsure verdict (below `qualityTriageMinP`, `0.7`) is `design`; mechanical
|
|
505
505
|
findings the fix turns leave become notes too. This lap is where code quality
|
|
506
506
|
is looked at, and every round pays for it. It never replaces step 4 — your
|
|
507
|
-
review stays the final gate, and nothing here skips it.
|
|
508
|
-
[coarse
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
507
|
+
review stays the final gate, and nothing here skips it. Every
|
|
508
|
+
[coarse slice](docs/configuration.md#coarse-slices) runs the same triaged lap
|
|
509
|
+
and never rests for its findings: its notes are carried to the review after
|
|
510
|
+
the last slice, each naming its slice and lens, beside every slice's fixes.
|
|
511
|
+
The `fix` and `cleanup` doors triage their lap too, and hand its notes to
|
|
512
|
+
their own review.
|
|
513
513
|
|
|
514
514
|
A red suite that keeps failing past a few fix attempts escalates instead of
|
|
515
515
|
retrying forever: an agent turn reads the failing output and writes
|
|
@@ -741,21 +741,6 @@ hand or leave it for cleanup.
|
|
|
741
741
|
|
|
742
742
|
The delivery model these workflows implement: [Delivery model](SDLC.md).
|
|
743
743
|
|
|
744
|
-
### Reviving skipped tests
|
|
745
|
-
|
|
746
|
-
The opt-in `tests` workflow revisits tests nobody runs. Export `tests` from
|
|
747
|
-
`@pmelab/gtd/workflows/tests` and run `gtd door tests` (or
|
|
748
|
-
`gtd --workflow tests`). On a green baseline an agent sweeps the whole
|
|
749
|
-
repository — every skipped test plus the `test-audit` skill's findings — into
|
|
750
|
-
`.gtd/TESTS.md`, one section per test file. Each file is then rewritten in its
|
|
751
|
-
own turn until nothing in it is skipped and the suite is green. A test the agent
|
|
752
|
-
calls obsolete stays skipped and is proposed for deletion: the review in
|
|
753
|
-
`.gtd/REVIEW.md` gives each proposal `delete it`, `keep it skipped` or
|
|
754
|
-
`_your answer_`, and refuses sign-off until each holds one tick. An agent turn
|
|
755
|
-
that deletes a test file is refused. Skip detection is a factory option with
|
|
756
|
-
JavaScript/TypeScript and cucumber defaults; see
|
|
757
|
-
[Reviving skipped tests](docs/configuration.md#reviving-skipped-tests).
|
|
758
|
-
|
|
759
744
|
## Checking a range for stray files
|
|
760
745
|
|
|
761
746
|
```sh
|
|
@@ -894,15 +879,15 @@ approvals as `approvals` (`[]` when none), and `gtd history show` prints each as
|
|
|
894
879
|
gtd is one package, `@pmelab/gtd`, and every entrypoint shares its version and
|
|
895
880
|
[SemVer promise](https://github.com/pmelab/gtd/blob/main/COMPATIBILITY.md).
|
|
896
881
|
|
|
897
|
-
| Import
|
|
898
|
-
|
|
|
899
|
-
| `@pmelab/gtd/flows`
|
|
900
|
-
| `@pmelab/gtd/wire`
|
|
901
|
-
| `@pmelab/gtd/steering`
|
|
902
|
-
| `@pmelab/gtd/workflow-kit`
|
|
903
|
-
| `@pmelab/gtd/workflows`
|
|
904
|
-
| `@pmelab/gtd/workflows/feature`, `@pmelab/gtd/workflows/review`, `@pmelab/gtd/workflows/fix`, `@pmelab/gtd/workflows/rollout`, `@pmelab/gtd/workflows/cleanup
|
|
905
|
-
| `@pmelab/gtd/testkit`
|
|
882
|
+
| Import | What it is |
|
|
883
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
884
|
+
| `@pmelab/gtd/flows` | The flow API a workflow is written in: the steps (`agent`, `human`, `run`, …), `scope` and `slice` (named spans of a process), workflow factories, `defineConfig` and `FLOWS_PROTOCOL`. |
|
|
885
|
+
| `@pmelab/gtd/wire` | The driver protocol: `WIRE_SCHEMA`, the exit codes and the types of every `--json` document. |
|
|
886
|
+
| `@pmelab/gtd/steering` | The steering-file formats (`qa`, `review`) and the review-note readers. |
|
|
887
|
+
| `@pmelab/gtd/workflow-kit` | Policy-free building blocks to compose a workflow from: health checks, quality laps, scenario freezing, review tails. |
|
|
888
|
+
| `@pmelab/gtd/workflows` | Every shipped workflow and all its parts in one module; also resolvable as `@pmelab/gtd/workflow`. |
|
|
889
|
+
| `@pmelab/gtd/workflows/feature`, `@pmelab/gtd/workflows/review`, `@pmelab/gtd/workflows/fix`, `@pmelab/gtd/workflows/rollout`, `@pmelab/gtd/workflows/cleanup` | One shipped workflow each: its factory as the default export, its parts, and the reserved exports (`defaults`, `envDefaults`, `summary`, `steering`, `skills`, `access`). `gtd install` imports these. |
|
|
890
|
+
| `@pmelab/gtd/testkit` | Runs any workflow against the real engine, in an in-memory or a real repository. Needs `vitest` and `quickpickle`, optional peer dependencies. |
|
|
906
891
|
|
|
907
892
|
Types come from the TypeScript sources the package ships. Configs never need an
|
|
908
893
|
install for any of these but `./testkit`: gtd resolves a `gtd.config.ts`'s
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { accessFor, agent, answered, changesSince, check, codeThreads, defineWorkflow, displayPath, env, head, human, judge, mark, numeric, quote, read, refuse, scope, skillsFor, start, vars } from "../flows.mjs";
|
|
2
2
|
import { d as ANSWER_FIX, f as ANSWER_LEAD, p as ANSWER_WONT_FIX, v as FREE_TEXT_PLACEHOLDER } from "./steering-D7hWeMgE.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { gate as gate$1, healthy as healthy$1, lensScopes, lensesOf, removedAssertions } from "../workflow-kit.mjs";
|
|
4
4
|
import { c as String$, g as standardSchemaV1, h as optionalWith, l as Struct, n as Boolean$, s as Number$ } from "./Schema-C6fLlQzx.mjs";
|
|
5
5
|
//#region src/workflows/shared/registry.ts
|
|
6
6
|
const FILL = "";
|
|
@@ -226,11 +226,6 @@ const testFileGlobs = [
|
|
|
226
226
|
"**/__tests__/**"
|
|
227
227
|
];
|
|
228
228
|
const defaultFlagRegistry = "src/flags/index.ts";
|
|
229
|
-
/** The `tests` workflow's own process settings: where it looks for skipped tests, and how a skip reads. */
|
|
230
|
-
const testsDefaults = {
|
|
231
|
-
testFiles: testFileGlobs.join(", "),
|
|
232
|
-
skipPattern: "^\\s*(?:(?:describe|context|suite|it|test|specify)\\.(?:skip|todo)\\b|x(?:describe|context|it|test|specify)\\s*\\(|(?:@\\S+\\s+)*@(?:skip|ignore)(?:\\s|$))"
|
|
233
|
-
};
|
|
234
229
|
/** Process settings. */
|
|
235
230
|
const defaults = {
|
|
236
231
|
reviewBase: "",
|
|
@@ -549,6 +544,20 @@ const SUMMARY_TITLES = [
|
|
|
549
544
|
];
|
|
550
545
|
const unboxed = (body) => body.replace(/^(\s*(?:>\s*)*(?:[-*+]|\d+[.)])\s+)\[[ xX]\]\s?/gm, "$1");
|
|
551
546
|
const short = (hash) => hash.slice(0, 7);
|
|
547
|
+
/** `slice 2, lens tests`; empty when neither is known. */
|
|
548
|
+
const originOf = (f) => [...f.slice === void 0 ? [] : [`slice ${f.slice}`], ...f.lens === void 0 ? [] : [`lens ${f.lens}`]].join(", ");
|
|
549
|
+
const originLine = (f) => {
|
|
550
|
+
const origin = originOf(f);
|
|
551
|
+
return origin === "" ? "" : `_From ${origin}._\n\n`;
|
|
552
|
+
};
|
|
553
|
+
const withOrigin = (f, rest) => [rest, originOf(f)].filter((s) => s !== "").join(", ");
|
|
554
|
+
const noteBody = (n) => {
|
|
555
|
+
const body = unboxed(n.body) || "_No detail given._";
|
|
556
|
+
if (n.unresolved === true) return `_The fixer left this unresolved._\n\n${body}`;
|
|
557
|
+
if (n.unclear === true) return `_A fix turn removed this while renaming another finding — check whether it was fixed._\n\n${body}`;
|
|
558
|
+
return body;
|
|
559
|
+
};
|
|
560
|
+
const carriedOnly = (f) => f.slice === void 0 ? {} : f;
|
|
552
561
|
/**
|
|
553
562
|
* \`.gtd/REVIEW.md\` as gtd writes it: one chunk per note, each ending in its
|
|
554
563
|
* answer boxes, then the fixed and noise lists. Chunk titles are made unique —
|
|
@@ -563,7 +572,7 @@ const composedReview = (base, report) => {
|
|
|
563
572
|
return name;
|
|
564
573
|
};
|
|
565
574
|
const chunk = (title, body) => `## ${title}\n\n${body.trim()}`;
|
|
566
|
-
const answerBoxes =
|
|
575
|
+
const answerBoxes = `${ANSWER_LEAD}\n\n- [ ] ${ANSWER_FIX}\n- [ ] ${ANSWER_WONT_FIX}\n- [ ] ${FREE_TEXT_PLACEHOLDER}`;
|
|
567
576
|
const conflicts = (report.standards?.undecided ?? []).map((c) => chunk(unique(conflictTitle(c)), conflictBody(c)));
|
|
568
577
|
const added = report.standards?.added ?? [];
|
|
569
578
|
const notes = report.notes.map((n) => ({
|
|
@@ -575,25 +584,14 @@ const composedReview = (base, report) => {
|
|
|
575
584
|
...report.nitFix === void 0 ? [] : [chunk("Fixed from your notes", `Your nits and fix-as-suggested notes were worked on in ${short(report.nitFix)}; the notes you answered are left out — check the diff.`)],
|
|
576
585
|
...weakened.length === 0 ? [] : [chunk("Weakened scenarios", `These scenarios lost an assertion the review base had:\n\n${weakened.join("\n")}`)],
|
|
577
586
|
...conflicts,
|
|
578
|
-
...notes.map((n) => chunk(n.title, `${
|
|
579
|
-
...report.fixed.length === 0 ? [] : [chunk("Fixed automatically", report.fixed.map((f) =>
|
|
587
|
+
...notes.map((n) => chunk(n.title, `${originLine(carriedOnly(n))}${noteBody(n)}\n\n${answerBoxes}`)),
|
|
588
|
+
...report.fixed.length === 0 ? [] : [chunk("Fixed automatically", report.fixed.map((f) => fixedLine(f, carriedOnly(f))).join("\n"))],
|
|
580
589
|
...added.length === 0 ? [] : [chunk("Standards added", `Learned from your answers and kept in \`${STANDARDS}\`:\n\n${added.map((s) => `- ${s}`).join("\n")}`)],
|
|
581
|
-
...report.noise.length === 0 ? [] : [chunk("Dropped as noise", report.noise.map((f) =>
|
|
590
|
+
...report.noise.length === 0 ? [] : [chunk("Dropped as noise", report.noise.map((f) => noiseLine(f, carriedOnly(f))).join("\n"))]
|
|
582
591
|
];
|
|
583
592
|
const body = chunks.length > 0 ? chunks : [chunk("Nothing to note", "No quality lens raised anything for you — or none ran. Review the diff yourself.")];
|
|
584
593
|
return `# Review: ${short(base)}\n\n<!-- base: ${base} -->\n\n${body.join("\n\n")}\n`;
|
|
585
594
|
};
|
|
586
|
-
const qualityUnresolvedMessage = () => `The quality lap's fix turns hit their cap and \`.gtd/QUALITY.md\` still
|
|
587
|
-
holds findings. Nothing after this fixes them, so the process will not
|
|
588
|
-
go on with them open.
|
|
589
|
-
|
|
590
|
-
Fix what matters by hand, then delete or empty \`.gtd/QUALITY.md\` —
|
|
591
|
-
dropping a finding is your call to make.
|
|
592
|
-
|
|
593
|
-
What each change does next (then run \`gtd land\`):
|
|
594
|
-
- **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**).
|
|
595
|
-
- **Anything else** — rests here again.
|
|
596
|
-
`;
|
|
597
595
|
const startGateBlockedMessage = () => `The test baseline is red — gtd will not start new work on a broken suite.
|
|
598
596
|
\`.gtd/FEEDBACK.md\` holds the failing output.
|
|
599
597
|
|
|
@@ -1259,6 +1257,28 @@ const lapRange = {
|
|
|
1259
1257
|
feedback: "it starts at the previous review's boundary, so it covers only what is new since the previous review.",
|
|
1260
1258
|
refactor: "it covers the whole refactor since it started, on every round. A refactor may update an existing test's call sites only: give any test-file hunk its own chunk, and say so if it changes a test, an assertion or an expected value."
|
|
1261
1259
|
};
|
|
1260
|
+
const fixedLine = (f, origin = f) => `- ${f.title} (${withOrigin(origin, short(f.commit))})`;
|
|
1261
|
+
const noiseLine = (f, origin = f) => `- ${f.title} (${withOrigin(origin, `p=${f.p}`)})`;
|
|
1262
|
+
/**
|
|
1263
|
+
* `review` with the lap's notes appended as prose chunks — no pointer, no
|
|
1264
|
+
* answer boxes — then what it fixed and dropped. A title the review already
|
|
1265
|
+
* holds gets a numbered suffix, as notes are told apart by title.
|
|
1266
|
+
*/
|
|
1267
|
+
const withLapFindings = (review, { notes, fixed, noise }) => {
|
|
1268
|
+
const used = new Set(review.split("\n").filter((line) => line.startsWith("## ")).map((line) => line.slice(3).trim()));
|
|
1269
|
+
const unique = (title) => {
|
|
1270
|
+
let name = title;
|
|
1271
|
+
for (let n = 2; used.has(name); n++) name = `${title} (${n})`;
|
|
1272
|
+
used.add(name);
|
|
1273
|
+
return name;
|
|
1274
|
+
};
|
|
1275
|
+
const chunks = [
|
|
1276
|
+
...notes.map((n) => `## ${unique(n.title)}\n\n${originLine(n)}${noteBody(n)}`),
|
|
1277
|
+
...fixed.length === 0 ? [] : [`## ${unique("Fixed automatically")}\n\n${fixed.map((f) => fixedLine(f)).join("\n")}`],
|
|
1278
|
+
...noise.length === 0 ? [] : [`## ${unique("Dropped as noise")}\n\n${noise.map((f) => noiseLine(f)).join("\n")}`]
|
|
1279
|
+
];
|
|
1280
|
+
return chunks.length === 0 ? review : `${review.trimEnd()}\n\n${chunks.join("\n\n")}\n`;
|
|
1281
|
+
};
|
|
1262
1282
|
const buildReviewReviewingPrompt = (base, lap, carry, removed = []) => `${styleBlock}
|
|
1263
1283
|
|
|
1264
1284
|
${styleFormatContract}
|
|
@@ -1803,12 +1823,6 @@ const guardedSteps = (guard, describeGuard, rests = true, rules) => {
|
|
|
1803
1823
|
exhausted: guarded(steps.exhausted, rests ? guard : void 0)
|
|
1804
1824
|
};
|
|
1805
1825
|
};
|
|
1806
|
-
/**
|
|
1807
|
-
* Hand a red suite that repeated attempts could not fix to a person: an
|
|
1808
|
-
* agent writes `.gtd/ESCALATION.md`, a human narrows it. From the third round
|
|
1809
|
-
* on, the human is told the budget is spent instead.
|
|
1810
|
-
*/
|
|
1811
|
-
const escalation = (count, guard, guardRests = true, rules) => escalation$1(count, guardedSteps(guard, void 0, guardRests, rules));
|
|
1812
1826
|
/** "Is this red round the same failure as the last one?" — asked of two reports, or of nothing when one is empty. */
|
|
1813
1827
|
const retryQuestion = (comparable) => comparable ? {
|
|
1814
1828
|
id: "verdict",
|
|
@@ -1944,24 +1958,6 @@ const flagShipped = (flow, name) => defineWorkflow(standardSchemaV1(FlagOptions,
|
|
|
1944
1958
|
base: void 0
|
|
1945
1959
|
};
|
|
1946
1960
|
});
|
|
1947
|
-
const TestsOptions = Struct({
|
|
1948
|
-
...ShippedOptions.fields,
|
|
1949
|
-
testFiles: setting,
|
|
1950
|
-
skipPattern: setting
|
|
1951
|
-
});
|
|
1952
|
-
const testsShipped = (flow) => defineWorkflow(standardSchemaV1(TestsOptions, {
|
|
1953
|
-
onExcessProperty: "error",
|
|
1954
|
-
errors: "all"
|
|
1955
|
-
}), ({ contextLimit, ...options }) => ({
|
|
1956
|
-
flow: limited(flow, contextLimit),
|
|
1957
|
-
defaults: over({
|
|
1958
|
-
...defaults,
|
|
1959
|
-
...testsDefaults
|
|
1960
|
-
}, options),
|
|
1961
|
-
envDefaults: over(envDefaults, options),
|
|
1962
|
-
doors: { tests: {} },
|
|
1963
|
-
base: void 0
|
|
1964
|
-
}));
|
|
1965
1961
|
//#endregion
|
|
1966
1962
|
//#region src/workflows/shared/skills.ts
|
|
1967
1963
|
const skills = (vars) => {
|
|
@@ -1977,8 +1973,6 @@ const skills = (vars) => {
|
|
|
1977
1973
|
design: ["spec-driven-development", "planning-and-task-breakdown"],
|
|
1978
1974
|
notes: ["incremental-implementation", "code-simplification"],
|
|
1979
1975
|
feedback: ["test-driven-development", "incremental-implementation"],
|
|
1980
|
-
tests: ["test-audit", "test-driven-development"],
|
|
1981
|
-
"tests.health": ["debugging-and-error-recovery"],
|
|
1982
1976
|
...build,
|
|
1983
1977
|
...Object.fromEntries(Object.entries(build).map(([name, list]) => [`refactor.${name}`, list])),
|
|
1984
1978
|
refactor: ["ponytail"],
|
|
@@ -2011,4 +2005,4 @@ const steering = {
|
|
|
2011
2005
|
[REVIEW]: "review"
|
|
2012
2006
|
};
|
|
2013
2007
|
//#endregion
|
|
2014
|
-
export {
|
|
2008
|
+
export { naivePassPrompt as $, reviewQuality as A, isConflictTitle as At, coverageQuestion as B, collecting as C, defaults as Ct, fixRisks as D, conflictBody as Dt, fixQuality as E, STANDARDS as Et, builderSystem as F, flagMentions as Ft, designTriagePrompt as G, criterionLine as H, clearCriteriaMessage as I, flagReferences as It, handOffAwaitReviewMessage as J, finisherSystem as K, clearCriteriaQuestion as L, flipProblems as Lt, agentWithSkills as M, parseStandards as Mt, buildReviewCollectingPrompt as N, renderStandards as Nt, planner as O, conflictFor as Ot, buildReviewTriageMessage as P, standardKey as Pt, naiveApplyFailure as Q, composedReview as R, registryEntry as Rt, coder as S, withLapFindings as St, fixNits as T, testFileGlobs as Tt, designGateAnswerMessage as U, coveringPrompt as V, designSystem as W, implementBuildingPrompt as X, idleMessage as Y, implementFixPrompt as Z, REQUIREMENTS as _, slicingPrompt as _t, flagModeOn as a, refactorDroppedRefusal as at, answerReviewQuestions as b, unwindFailedMessage as bt, baseline as c, refactorRules as ct, healthy as d, reviewFeedbackFixPrompt as dt, normaliseCriteriaScript as et, FEEDBACK as f, reviewGateBlockedMessage as ft, REFACTOR_NOTE as g, seamSteerMessage as gt, REFACTOR as h, seamByHandMessage as ht, skills as i, refactorBuildingPrompt as it, reviewing as j, oneLine as jt, reviewMissing as k, conflictTitle as kt, gate as l, reviewEditNotesCapture as lt, QUALITY as m, reviewerSystem as mt, summary as n, qualityFindingsFile as nt, flagShipped as o, refactorFixPrompt as ot, HANDOFF as p, reviewNotesCapture as pt, fixReviewNotesPrompt as q, access as r, qualityTriageMessage as rt, shipped as s, refactorNotePrompt as st, steering as t, oversizedMessage as tt, guarded as u, reviewEditsCapture as ut, REVIEW as v, startGateBlockedMessage as vt, fixCheck as w, envDefaults as wt, awaitReview as x, unwindFailure as xt, SLICES as y, uncoveredMessage as yt, coverageMessage as z, requireFlag as zt };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { answered, changesSince, head, judge, numeric, read,
|
|
1
|
+
import { answered, changesSince, head, judge, numeric, read, requireRevert, restoreScript, run, showScript, vars, writeScript } from "../flows.mjs";
|
|
2
2
|
import { removedAssertions, review } from "../workflow-kit.mjs";
|
|
3
|
-
import { At as
|
|
4
|
-
import { i as isOptionalObject, n as closeReview, o as tail, s as NO_FINDINGS } from "./tail-
|
|
3
|
+
import { At as isConflictTitle, C as collecting, D as fixRisks, Dt as conflictBody, Et as STANDARDS, Mt as parseStandards, Nt as renderStandards, Ot as conflictFor, P as buildReviewTriageMessage, Pt as standardKey, R as composedReview, T as fixNits, b as answerReviewQuestions, d as healthy, jt as oneLine, k as reviewMissing, kt as conflictTitle, lt as reviewEditNotesCapture, pt as reviewNotesCapture, u as guarded, ut as reviewEditsCapture, v as REVIEW, w as fixCheck, x as awaitReview } from "./reserved-DF1HD6jL.mjs";
|
|
4
|
+
import { i as isOptionalObject, n as closeReview, o as tail, s as NO_FINDINGS } from "./tail-D89fU_wf.mjs";
|
|
5
5
|
//#region src/workflows/shared/learn.ts
|
|
6
6
|
const SUMMARY_MAX = 200;
|
|
7
7
|
const BANNER = /^_(?:The fixer left this unresolved\.|A fix turn removed this while renaming another finding — check whether it was fixed\.|No detail given\.)_\s*/;
|
|
@@ -276,7 +276,7 @@ const triage = (severities) => async (notes) => {
|
|
|
276
276
|
const isCode = (path) => path !== ".gtd" && !path.startsWith(".gtd/");
|
|
277
277
|
/**
|
|
278
278
|
* gtd writes `.gtd/REVIEW.md` from the quality lap's `report` — no reviewer
|
|
279
|
-
* turn
|
|
279
|
+
* turn. A re-review
|
|
280
280
|
* after fixed nits restores the doc as committed at `carry`, answers kept;
|
|
281
281
|
* a fresh one leaves out the notes the human already answered.
|
|
282
282
|
*/
|
|
@@ -289,7 +289,7 @@ const composing = (base, report, carry, nitFix, answered = /* @__PURE__ */ new S
|
|
|
289
289
|
added: standardsAddedSince(base),
|
|
290
290
|
undecided: parseStandards(read(".gtd/STANDARDS.md") ?? "").undecided
|
|
291
291
|
}
|
|
292
|
-
})
|
|
292
|
+
})) : showScript(carry, REVIEW, REVIEW), {
|
|
293
293
|
label: "Composing the review",
|
|
294
294
|
file: REVIEW,
|
|
295
295
|
base
|
|
@@ -303,7 +303,7 @@ const composing = (base, report, carry, nitFix, answered = /* @__PURE__ */ new S
|
|
|
303
303
|
* praise dropped.
|
|
304
304
|
*/
|
|
305
305
|
const humanReview = (base, escalations = { rounds: 0 }, options = {}) => {
|
|
306
|
-
const { guard, guardRests, fixAccess, rules
|
|
306
|
+
const { guard, guardRests, fixAccess, rules } = options;
|
|
307
307
|
const feedback = options.feedback ?? {
|
|
308
308
|
file: ".gtd/REQUIREMENTS.md",
|
|
309
309
|
collecting,
|
|
@@ -328,7 +328,7 @@ const humanReview = (base, escalations = { rounds: 0 }, options = {}) => {
|
|
|
328
328
|
guardRests,
|
|
329
329
|
rules
|
|
330
330
|
}),
|
|
331
|
-
awaitReview
|
|
331
|
+
awaitReview,
|
|
332
332
|
reviewMissing,
|
|
333
333
|
collecting: feedback.collecting,
|
|
334
334
|
triage: triage(severities),
|
|
@@ -371,10 +371,7 @@ function buildTail(first, second, ...rest) {
|
|
|
371
371
|
return tail((escalations, report) => humanReview(base, escalations, {
|
|
372
372
|
...form,
|
|
373
373
|
report
|
|
374
|
-
}),
|
|
375
|
-
...form,
|
|
376
|
-
triage: true
|
|
377
|
-
});
|
|
374
|
+
}), form);
|
|
378
375
|
}
|
|
379
376
|
const isOptionalBoolean = (value) => value == null || typeof value === "boolean";
|
|
380
377
|
const legacyForm = (fixFirst, base, green, options) => isOptionalBoolean(fixFirst) && isOptionalBoolean(green) && isOptionalObject(options) ? [base, {
|
|
@@ -388,17 +385,6 @@ const callForm = (first, second, [third, fourth, ...extra]) => {
|
|
|
388
385
|
if (typeof second === "string") return legacyForm(first, second, third, fourth);
|
|
389
386
|
if (typeof first === "string") return currentForm(first, second, third, fourth);
|
|
390
387
|
};
|
|
391
|
-
/** Build the concerns a review round collected into `.gtd/REQUIREMENTS.md`, then remove it. */
|
|
392
|
-
const fixFeedback = async () => {
|
|
393
|
-
await agentWithSkills("feedback.fixing", reviewFeedbackFixPrompt(), {
|
|
394
|
-
label: "Fixing your review feedback",
|
|
395
|
-
file: REQUIREMENTS,
|
|
396
|
-
model: coder(),
|
|
397
|
-
system: finisherSystem(),
|
|
398
|
-
allowEmpty: true
|
|
399
|
-
});
|
|
400
|
-
await run("feedback.closing", removeScript([REQUIREMENTS]), { label: "Closing the feedback" });
|
|
401
|
-
};
|
|
402
388
|
/**
|
|
403
389
|
* Undo the human's review-round code edits. A path changed since the human
|
|
404
390
|
* left it is not overwritten; requireRevert names it instead.
|
|
@@ -416,4 +402,4 @@ const reUnwind = async (feedback) => {
|
|
|
416
402
|
requireRevert(edited, base);
|
|
417
403
|
};
|
|
418
404
|
//#endregion
|
|
419
|
-
export { reUnwind as
|
|
405
|
+
export { reUnwind as i, humanReview as n, isCode as r, buildTail as t };
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import { answered, head, judge, mark, numeric, read, refuse, removeScript, run, scope, sectionBodies, showScript, vars, writeScript } from "../flows.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { lensEntriesOf, lensOf, qualityLap, reviewToHandOff } from "../workflow-kit.mjs";
|
|
3
|
+
import { A as reviewQuality, D as fixRisks, E as fixQuality, J as handOffAwaitReviewMessage, St as withLapFindings, d as healthy, j as reviewing, k as reviewMissing, m as QUALITY, nt as qualityFindingsFile, rt as qualityTriageMessage, u as guarded, v as REVIEW, w as fixCheck, x as awaitReview } from "./reserved-DF1HD6jL.mjs";
|
|
4
4
|
//#region src/workflows/shared/lenses.ts
|
|
5
5
|
const NO_FINDINGS = {
|
|
6
6
|
notes: [],
|
|
7
7
|
fixed: [],
|
|
8
8
|
noise: []
|
|
9
9
|
};
|
|
10
|
+
/** `reports` as one, each finding labelled with the slice it came from, in slice order. */
|
|
11
|
+
const joinReports = (reports) => {
|
|
12
|
+
const tag = (slice, fs) => fs.map((f) => ({
|
|
13
|
+
...f,
|
|
14
|
+
slice
|
|
15
|
+
}));
|
|
16
|
+
return {
|
|
17
|
+
notes: reports.flatMap((r) => tag(r.slice, r.report.notes)),
|
|
18
|
+
fixed: reports.flatMap((r) => tag(r.slice, r.report.fixed)),
|
|
19
|
+
noise: reports.flatMap((r) => tag(r.slice, r.report.noise))
|
|
20
|
+
};
|
|
21
|
+
};
|
|
10
22
|
const UNTITLED = "Quality findings";
|
|
11
23
|
const below = (section) => section.split("\n").slice(1).join("\n").trim();
|
|
12
24
|
const strayProse = (text, sections) => sections.reduce((rest, body) => rest.replace(body, "\n"), text).split("\n").filter((line) => !/^#\s/.test(line)).join("\n").trim();
|
|
@@ -86,7 +98,14 @@ const classify = async (findings) => {
|
|
|
86
98
|
*/
|
|
87
99
|
const triageFindings = async (options) => {
|
|
88
100
|
const { escalations, guard, guardRests, rules } = options;
|
|
89
|
-
const
|
|
101
|
+
const withLens = (f) => {
|
|
102
|
+
const lens = options.lensOf?.(f);
|
|
103
|
+
return lens === void 0 ? f : {
|
|
104
|
+
...f,
|
|
105
|
+
lens
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
const findings = findingsOf(read(".gtd/QUALITY.md") ?? "").map(withLens);
|
|
90
109
|
if (findings.length === 0) return NO_FINDINGS;
|
|
91
110
|
const { mechanical, noise, design } = await classify(findings);
|
|
92
111
|
const sorted = qualityFindingsFile(mechanical);
|
|
@@ -103,7 +122,10 @@ const triageFindings = async (options) => {
|
|
|
103
122
|
commit
|
|
104
123
|
})));
|
|
105
124
|
unclear.push(...turn.unclear);
|
|
106
|
-
open = turn.open
|
|
125
|
+
open = turn.open.map((f) => ({
|
|
126
|
+
...mechanical.find((m) => m.title === f.title),
|
|
127
|
+
...f
|
|
128
|
+
}));
|
|
107
129
|
}
|
|
108
130
|
if (mechanical.length > 0) await healthy(() => fixCheck(rules), {
|
|
109
131
|
escalations,
|
|
@@ -131,22 +153,15 @@ const triageFindings = async (options) => {
|
|
|
131
153
|
//#region src/workflows/shared/tail.ts
|
|
132
154
|
/** The reviewer's own `.gtd/REVIEW.md`, kept beside the human's notes so `fix` can tell them apart. */
|
|
133
155
|
const REVIEW_ORIG = ".gtd/REVIEW.orig.md";
|
|
134
|
-
|
|
135
|
-
* One review turn per lens over the whole change, each appending every
|
|
136
|
-
* finding to `.gtd/QUALITY.md`. Resolves `"findings"` when that file
|
|
137
|
-
* has any.
|
|
138
|
-
*/
|
|
139
|
-
const qualityLap = (guard, lenses, base) => qualityLap$1(lenses ?? lensEntriesOf(vars.qualityReviews ?? ""), guarded((entry) => {
|
|
156
|
+
const lensLap = (guard, lenses, base, seen) => qualityLap(lenses ?? lensEntriesOf(vars.qualityReviews ?? ""), guarded(async (entry) => {
|
|
140
157
|
const { lens, model } = lensOf(entry);
|
|
141
|
-
|
|
158
|
+
await reviewQuality(lens, base, model);
|
|
159
|
+
seen(lens, findingsOf(read(".gtd/QUALITY.md") ?? ""));
|
|
142
160
|
}, guard), QUALITY);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
cap: 3,
|
|
148
|
-
escalate: () => escalation(escalations, guard, guardRests, rules)
|
|
149
|
-
});
|
|
161
|
+
const closeQuality = async () => {
|
|
162
|
+
if (read(".gtd/QUALITY.md") === void 0) return;
|
|
163
|
+
await run("quality.closing", removeScript([QUALITY]), { label: "Clearing quality findings" });
|
|
164
|
+
};
|
|
150
165
|
/** `.gtd/REVIEW.md` removed at the end of a round at commit `round`. */
|
|
151
166
|
const closeReview = (round) => run("review.closing", removeScript([REVIEW, REVIEW_ORIG]), {
|
|
152
167
|
label: "Closing the review",
|
|
@@ -183,7 +198,7 @@ const runTail = (finish, options) => scope("build", async () => {
|
|
|
183
198
|
const guardRests = options.guardRests === void 0 ? true : options.guardRests;
|
|
184
199
|
const escalations = { rounds: 0 };
|
|
185
200
|
const fix = () => fixCheck(rules);
|
|
186
|
-
|
|
201
|
+
if (options.fixFirst) {
|
|
187
202
|
await guarded(fix, guard)();
|
|
188
203
|
await healthy(fix, {
|
|
189
204
|
fixesSoFar: 1,
|
|
@@ -192,38 +207,52 @@ const runTail = (finish, options) => scope("build", async () => {
|
|
|
192
207
|
guardRests,
|
|
193
208
|
rules
|
|
194
209
|
});
|
|
195
|
-
}
|
|
196
|
-
if (options.fixFirst) await repair();
|
|
197
|
-
else if (!options.green) await healthy(fix, {
|
|
210
|
+
} else if (!options.green) await healthy(fix, {
|
|
198
211
|
escalations,
|
|
199
212
|
guard,
|
|
200
213
|
guardRests,
|
|
201
214
|
rules
|
|
202
215
|
});
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
216
|
+
const byTitle = [];
|
|
217
|
+
const lap = await lensLap(guard, lenses, lensBase, (lens, findings) => {
|
|
218
|
+
const before = byTitle.map((f) => f.title);
|
|
219
|
+
for (const { title } of findings) {
|
|
220
|
+
const at = before.indexOf(title);
|
|
221
|
+
if (at >= 0) before.splice(at, 1);
|
|
222
|
+
else byTitle.push({
|
|
223
|
+
title,
|
|
224
|
+
lens
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
const lensOf = (f) => {
|
|
229
|
+
const at = byTitle.findIndex((e) => e.title === f.title);
|
|
230
|
+
return at < 0 ? void 0 : byTitle.splice(at, 1)[0].lens;
|
|
231
|
+
};
|
|
232
|
+
const report = lap === "findings" ? await triageFindings({
|
|
211
233
|
escalations,
|
|
212
234
|
guard,
|
|
213
235
|
guardRests,
|
|
214
|
-
rules
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
236
|
+
rules,
|
|
237
|
+
lensOf
|
|
238
|
+
}) : NO_FINDINGS;
|
|
239
|
+
await closeQuality();
|
|
240
|
+
return finish(escalations, report);
|
|
218
241
|
});
|
|
219
242
|
/**
|
|
220
243
|
* The fix workflow's human review since `base`: a sign-off closes
|
|
221
244
|
* `.gtd/REVIEW.md`; a note leaves it, with the reviewer's own version as
|
|
222
245
|
* `REVIEW_ORIG`, for the next `fix` to read — never a re-plan.
|
|
223
246
|
*/
|
|
224
|
-
const handOffReview = (base, escalations, guard) => reviewToHandOff(base, {
|
|
247
|
+
const handOffReview = (base, escalations, guard, report = NO_FINDINGS) => reviewToHandOff(base, {
|
|
225
248
|
file: REVIEW,
|
|
226
|
-
reviewing: guarded((b, carry) =>
|
|
249
|
+
reviewing: guarded(async (b, carry) => {
|
|
250
|
+
await reviewing(b, carry);
|
|
251
|
+
const review = read(REVIEW);
|
|
252
|
+
if (review === void 0) return;
|
|
253
|
+
const full = withLapFindings(review, report);
|
|
254
|
+
if (full !== review) await run("review.carrying", writeScript(REVIEW, full), { label: "Adding the quality lap's findings" });
|
|
255
|
+
}, guard),
|
|
227
256
|
fixRisks: guarded(fixRisks, guard),
|
|
228
257
|
keepGreen: () => healthy(fixCheck, {
|
|
229
258
|
escalations,
|
|
@@ -237,10 +266,10 @@ const handOffReview = (base, escalations, guard) => reviewToHandOff(base, {
|
|
|
237
266
|
unparsed: "gtd land: review-doc: your edit to .gtd/REVIEW.md contains no note fix can read — restore the deleted chunk, or write the note as a line under its entry.",
|
|
238
267
|
deleted: "gtd land: review-doc: .gtd/REVIEW.md was deleted — restore it, or leave a note to hand to fix."
|
|
239
268
|
});
|
|
240
|
-
/** The tail the fix workflow runs: ends at the hand-off review since `base
|
|
241
|
-
const handOffTail = (fixFirst, base, guard) => tail((escalations) => handOffReview(base, escalations, guard), {
|
|
269
|
+
/** The tail the fix workflow runs: ends at the hand-off review since `base`, which gets what the lap left. */
|
|
270
|
+
const handOffTail = (fixFirst, base, guard) => tail((escalations, report) => handOffReview(base, escalations, guard, report), {
|
|
242
271
|
fixFirst,
|
|
243
272
|
guard
|
|
244
273
|
});
|
|
245
274
|
//#endregion
|
|
246
|
-
export { refusePendingHandOff as a, isOptionalObject as i, closeReview as n, tail as o, handOffTail as r, NO_FINDINGS as s, REVIEW_ORIG as t };
|
|
275
|
+
export { refusePendingHandOff as a, joinReports as c, isOptionalObject as i, closeReview as n, tail as o, handOffTail as r, NO_FINDINGS as s, REVIEW_ORIG as t };
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -21606,11 +21606,10 @@ rather than starting the review; \`gtd-door fix\` on a GREEN suite is a no-op
|
|
|
21606
21606
|
straight back to \`idle\` — the exec'd \`gtd-build\` exits immediately on it.
|
|
21607
21607
|
\`gtd doors\` lists the doors a repository offers (those of the workflows its
|
|
21608
21608
|
config exports — the bundled \`fix\` and \`review [base]\`, plus the opt-in
|
|
21609
|
-
\`rollout <flag>\` (turn a release flag default-on)
|
|
21610
|
-
(remove it)
|
|
21611
|
-
|
|
21612
|
-
\`@pmelab/gtd/workflows/cleanup\`
|
|
21613
|
-
\`@pmelab/gtd/workflows/tests\` — plus any it declares). Refuses with
|
|
21609
|
+
\`rollout <flag>\` (turn a release flag default-on) and \`cleanup <flag>\`
|
|
21610
|
+
(remove it), which a config adds by exporting \`rollout\` from
|
|
21611
|
+
\`@pmelab/gtd/workflows/rollout\` and \`cleanup\` from
|
|
21612
|
+
\`@pmelab/gtd/workflows/cleanup\` — plus any it declares). Refuses with
|
|
21614
21613
|
\`usage: gtd-door <name> [args...]\` on stderr and exit \`2\` when the name is
|
|
21615
21614
|
missing — nothing was even attempted. Default path: \`~/.local/bin/gtd-door\`.
|
|
21616
21615
|
|
|
@@ -21723,8 +21722,7 @@ const CONFIG_SCAFFOLD = `// The workflows gtd runs here: gtd's shipped feature,
|
|
|
21723
21722
|
// its own doors: \`gtd door review [base]\` and \`gtd door fix\`. Export \`rollout\`
|
|
21724
21723
|
// from \`@pmelab/gtd/workflows/rollout\` and \`cleanup\` from
|
|
21725
21724
|
// \`@pmelab/gtd/workflows/cleanup\` to add \`gtd door rollout <flag>\` and
|
|
21726
|
-
// \`gtd door cleanup <flag
|
|
21727
|
-
// to add \`gtd door tests\`.
|
|
21725
|
+
// \`gtd door cleanup <flag>\`.
|
|
21728
21726
|
export {
|
|
21729
21727
|
feature as default,
|
|
21730
21728
|
feature,
|
package/dist/testkit.mjs
CHANGED
|
@@ -6643,11 +6643,10 @@ rather than starting the review; \`gtd-door fix\` on a GREEN suite is a no-op
|
|
|
6643
6643
|
straight back to \`idle\` — the exec'd \`gtd-build\` exits immediately on it.
|
|
6644
6644
|
\`gtd doors\` lists the doors a repository offers (those of the workflows its
|
|
6645
6645
|
config exports — the bundled \`fix\` and \`review [base]\`, plus the opt-in
|
|
6646
|
-
\`rollout <flag>\` (turn a release flag default-on)
|
|
6647
|
-
(remove it)
|
|
6648
|
-
|
|
6649
|
-
\`@pmelab/gtd/workflows/cleanup\`
|
|
6650
|
-
\`@pmelab/gtd/workflows/tests\` — plus any it declares). Refuses with
|
|
6646
|
+
\`rollout <flag>\` (turn a release flag default-on) and \`cleanup <flag>\`
|
|
6647
|
+
(remove it), which a config adds by exporting \`rollout\` from
|
|
6648
|
+
\`@pmelab/gtd/workflows/rollout\` and \`cleanup\` from
|
|
6649
|
+
\`@pmelab/gtd/workflows/cleanup\` — plus any it declares). Refuses with
|
|
6651
6650
|
\`usage: gtd-door <name> [args...]\` on stderr and exit \`2\` when the name is
|
|
6652
6651
|
missing — nothing was even attempted. Default path: \`~/.local/bin/gtd-door\`.
|
|
6653
6652
|
|
|
@@ -6760,8 +6759,7 @@ const CONFIG_SCAFFOLD = `// The workflows gtd runs here: gtd's shipped feature,
|
|
|
6760
6759
|
// its own doors: \`gtd door review [base]\` and \`gtd door fix\`. Export \`rollout\`
|
|
6761
6760
|
// from \`@pmelab/gtd/workflows/rollout\` and \`cleanup\` from
|
|
6762
6761
|
// \`@pmelab/gtd/workflows/cleanup\` to add \`gtd door rollout <flag>\` and
|
|
6763
|
-
// \`gtd door cleanup <flag
|
|
6764
|
-
// to add \`gtd door tests\`.
|
|
6762
|
+
// \`gtd door cleanup <flag>\`.
|
|
6765
6763
|
export {
|
|
6766
6764
|
feature as default,
|
|
6767
6765
|
feature,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { Ct as defaults, It as flagReferences, Rt as registryEntry, S as coder, i as skills, n as summary, o as flagShipped, r as access, t as steering, wt as envDefaults, zt as requireFlag } from "../chunks/reserved-DF1HD6jL.mjs";
|
|
3
|
+
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-D89fU_wf.mjs";
|
|
4
4
|
//#region src/workflows/cleanup/cleanup.ts
|
|
5
5
|
const removingPrompt = (name, registry) => `Clean up the release flag "${name}", which is default-on: delete its entry in ${registry}, and remove every \`GTD_FLAG_${name.toUpperCase()}\` check from the shipped sources. Keep the flag-on path; delete code only the off path reached, including tests that pin the off path.`;
|
|
6
6
|
const cleanupFlow = async () => {
|