@pmelab/gtd 55.0.0 → 57.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 +72 -80
- package/dist/chunks/{reserved-DHafcW7t.mjs → reserved-X2RkNHin.mjs} +110 -318
- package/dist/chunks/{review-BJGKwlYv.mjs → review-v7sh5GF4.mjs} +30 -125
- package/dist/chunks/{steering-CLhu8C7q.mjs → steering-CCNvngSH.mjs} +256 -65
- package/dist/chunks/{tail-B9Hw2AFB.mjs → tail-S04KEewi.mjs} +144 -56
- package/dist/gtd.bundle.mjs +259 -22
- package/dist/steering.mjs +2 -2
- package/dist/testkit.mjs +6 -6
- package/dist/web/main.css +1 -1
- package/dist/web/main.js +551 -66
- package/dist/workflow-kit.mjs +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +63 -175
- package/dist/workflows/fix.mjs +3 -3
- package/dist/workflows/review.mjs +3 -3
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +603 -727
- package/package.json +1 -1
- package/skills/authoring/SKILL.md +3 -3
- package/src/cli/Cli.ts +5 -5
- package/src/edge/Edge.test.ts +1 -1
- package/src/steering/FORMATS.md +5 -1
- package/src/steering/SteeringFormats.test.ts +2 -2
- package/src/steering/contract.test.ts +1 -0
- package/src/steering/findings.test.ts +47 -0
- package/src/steering/findings.ts +350 -0
- package/src/steering/fixtures/findings/gate.md +34 -0
- package/src/steering/fixtures/findings/gate.read.json +87 -0
- package/src/steering/index.test.ts +2 -2
- package/src/steering/index.ts +4 -3
- package/src/steering/review.test.ts +1 -75
- package/src/steering/review.ts +1 -74
- package/src/workflow/compile.test.ts +1 -0
- package/src/workflows/feature/criteria-coverage.feature +1 -7
- package/src/workflows/feature/criteria.test.ts +1 -41
- package/src/workflows/feature/criteria.ts +2 -14
- package/src/workflows/feature/fast-path.feature +0 -1
- package/src/workflows/feature/feature.test.ts +1 -1
- package/src/workflows/feature/feature.ts +16 -25
- package/src/workflows/feature/flag-mode.feature +1 -1
- package/src/workflows/feature/lens-models.feature +0 -1
- package/src/workflows/feature/lens-review.feature +323 -172
- package/src/workflows/feature/naive-test.feature +258 -0
- package/src/workflows/feature/one-step-land.feature +0 -1
- package/src/workflows/feature/refactor-absorbs.feature +1 -8
- package/src/workflows/feature/refactor-first.feature +2 -11
- package/src/workflows/feature/refactor-rebuild.feature +4 -25
- package/src/workflows/feature/refactor-review.feature +33 -118
- package/src/workflows/feature/refactor.test.ts +0 -2
- package/src/workflows/feature/refactor.ts +0 -1
- package/src/workflows/feature/review-gate.feature +7 -24
- package/src/workflows/feature/reviewer-walkthrough.feature +16 -104
- package/src/workflows/feature/seam-rejection.feature +0 -1
- package/src/workflows/feature/seam.feature +0 -1
- package/src/workflows/feature/seam.test.ts +1 -2
- package/src/workflows/feature/seam.ts +56 -10
- package/src/workflows/feature/standards.feature +75 -35
- package/src/workflows/fix/fix-lens-origin.feature +20 -7
- package/src/workflows/fix/fix-quality.feature +102 -23
- package/src/workflows/review/review-door.feature +62 -59
- package/src/workflows/shared/learn.test.ts +0 -14
- package/src/workflows/shared/learn.ts +1 -6
- package/src/workflows/shared/lenses.ts +149 -43
- package/src/workflows/shared/options.ts +9 -1
- package/src/workflows/shared/prose.ts +0 -6
- package/src/workflows/shared/review.test.ts +27 -26
- package/src/workflows/shared/review.ts +29 -196
- 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/tail.ts +18 -9
- package/src/workflows/shared/text.test.ts +125 -329
- package/src/workflows/shared/text.ts +173 -471
- package/src/workflows/shared/vars.ts +2 -3
- 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
- package/src/workflows/feature/review-answers.feature +0 -324
- package/src/workflows/feature/severity-budget.feature +0 -116
- package/src/workflows/feature/severity-sorted-review.feature +0 -198
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { answered, head, judge, mark, numeric, read, refuse, removeScript, run, scope, sectionBodies, showScript, vars, writeScript } from "../flows.mjs";
|
|
1
|
+
import { answered, head, human, judge, mark, numeric, read, refuse, removeScript, run, scope, sectionBodies, showScript, vars, writeScript } from "../flows.mjs";
|
|
2
|
+
import { F as formatFinding, p as findingEntries, r as steeringFormatFor, x as reviewRisks } from "./steering-CCNvngSH.mjs";
|
|
2
3
|
import { lensEntriesOf, lensOf, qualityLap, reviewToHandOff } from "../workflow-kit.mjs";
|
|
3
|
-
import { A as
|
|
4
|
+
import { A as reviewQuality, D as fixRisks, E as fixQuality, U as findingGateFile, W as findingGateMessage, bt as withGuidance, d as guarded, f as healthy, h as QUALITY, j as reviewing, k as reviewMissing, nt as qualityFindingsFile, q as handOffAwaitReviewMessage, rt as qualityTriageMessage, w as fixCheck, x as awaitReview, xt as withSummary, y as REVIEW } from "./reserved-X2RkNHin.mjs";
|
|
4
5
|
//#region src/workflows/shared/lenses.ts
|
|
5
6
|
const NO_FINDINGS = {
|
|
6
|
-
notes: [],
|
|
7
7
|
fixed: [],
|
|
8
|
-
|
|
8
|
+
gateFixed: [],
|
|
9
|
+
dropped: [],
|
|
10
|
+
noise: [],
|
|
11
|
+
unresolved: []
|
|
9
12
|
};
|
|
10
13
|
const UNTITLED = "Quality findings";
|
|
11
14
|
const below = (section) => section.split("\n").slice(1).join("\n").trim();
|
|
@@ -78,31 +81,18 @@ const classify = async (findings) => {
|
|
|
78
81
|
design: of("design")
|
|
79
82
|
};
|
|
80
83
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
*/
|
|
87
|
-
const
|
|
88
|
-
const { escalations, guard, guardRests, rules } = options;
|
|
89
|
-
const withLens = (f) => {
|
|
90
|
-
const lens = options.lensOf?.(f);
|
|
91
|
-
return lens === void 0 ? f : {
|
|
92
|
-
...f,
|
|
93
|
-
lens
|
|
94
|
-
};
|
|
95
|
-
};
|
|
96
|
-
const findings = findingsOf(read(".gtd/QUALITY.md") ?? "").map(withLens);
|
|
97
|
-
if (findings.length === 0) return NO_FINDINGS;
|
|
98
|
-
const { mechanical, noise, design } = await classify(findings);
|
|
99
|
-
const sorted = qualityFindingsFile(mechanical);
|
|
100
|
-
if (read(".gtd/QUALITY.md") !== sorted) await run("quality.sorting", mechanical.length === 0 ? removeScript([QUALITY]) : writeScript(QUALITY, sorted), { label: "Setting findings aside for the review" });
|
|
84
|
+
const NOTHING_FIXED = {
|
|
85
|
+
fixed: [],
|
|
86
|
+
open: [],
|
|
87
|
+
unclear: []
|
|
88
|
+
};
|
|
89
|
+
/** Up to `FIX_CAP` fix turns over `.gtd/QUALITY.md`, which holds one chunk per finding in `findings`. */
|
|
90
|
+
const fixTurns = async (findings, options) => {
|
|
101
91
|
const fixed = [];
|
|
102
92
|
const unclear = [];
|
|
103
|
-
let open =
|
|
93
|
+
let open = findings;
|
|
104
94
|
for (let turns = 0; turns < 3 && open.length > 0; turns++) {
|
|
105
|
-
await guarded(() => fixQuality(rules), guard)();
|
|
95
|
+
await guarded(() => fixQuality(options.rules), options.guard)();
|
|
106
96
|
const turn = afterFix(open, findingsOf(read(".gtd/QUALITY.md") ?? ""));
|
|
107
97
|
const commit = head();
|
|
108
98
|
fixed.push(...turn.fixed.map((f) => ({
|
|
@@ -111,30 +101,120 @@ const triageFindings = async (options) => {
|
|
|
111
101
|
})));
|
|
112
102
|
unclear.push(...turn.unclear);
|
|
113
103
|
open = turn.open.map((f) => ({
|
|
114
|
-
...
|
|
104
|
+
...findings.find((m) => m.title === f.title),
|
|
115
105
|
...f
|
|
116
106
|
}));
|
|
117
107
|
}
|
|
118
|
-
|
|
108
|
+
const { escalations, guard, guardRests, rules } = options;
|
|
109
|
+
await healthy(() => fixCheck(rules), {
|
|
119
110
|
escalations,
|
|
120
111
|
guard,
|
|
121
112
|
guardRests,
|
|
122
113
|
rules
|
|
123
114
|
});
|
|
124
115
|
return {
|
|
125
|
-
|
|
116
|
+
fixed,
|
|
117
|
+
open,
|
|
118
|
+
unclear
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
/** Matches each entry the human left to the finding it was written from, by title, first unused first; `left` is what no entry claimed. */
|
|
122
|
+
const matcher = (findings) => {
|
|
123
|
+
const left = [...findings];
|
|
124
|
+
const of = (e) => {
|
|
125
|
+
const at = left.findIndex((f) => f.title === e.title);
|
|
126
|
+
return at < 0 ? void 0 : left.splice(at, 1)[0];
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
of,
|
|
130
|
+
left
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
const gateProblems = (text) => steeringFormatFor("findings").validate(text).map((f) => ` - ${formatFinding(QUALITY, f)}`);
|
|
134
|
+
/**
|
|
135
|
+
* The human gate over the open notes and the noise: a ticked finding is
|
|
136
|
+
* fixed — never re-planned — and the rest dropped. No notes and no noise, no gate.
|
|
137
|
+
*/
|
|
138
|
+
const findingGate = async (notes, noise, options) => {
|
|
139
|
+
if (notes.length + noise.length === 0) return NO_FINDINGS;
|
|
140
|
+
const written = findingGateFile(notes, noise);
|
|
141
|
+
await run("quality.gating", writeScript(QUALITY, written), { label: "Listing the findings for you" });
|
|
142
|
+
await human("quality.gate", {
|
|
143
|
+
message: findingGateMessage(),
|
|
144
|
+
label: "Deciding on the quality findings",
|
|
145
|
+
file: QUALITY,
|
|
146
|
+
mode: "findings",
|
|
147
|
+
acceptClean: true
|
|
148
|
+
});
|
|
149
|
+
const answered = read(".gtd/QUALITY.md") ?? written;
|
|
150
|
+
const problems = gateProblems(answered);
|
|
151
|
+
if (problems.length > 0) refuse(`gtd land: finding-gate: ${problems.length} problem(s) in ${QUALITY} — keep one \`- [ ] fix\` box per finding, ticked to fix it or unticked to drop it, and write guidance only under a ticked one:\n${problems.join("\n")}`);
|
|
152
|
+
const noteOf = matcher(notes);
|
|
153
|
+
const noiseOf = matcher(noise);
|
|
154
|
+
const decided = findingEntries(answered).map((e) => ({
|
|
155
|
+
e,
|
|
156
|
+
finding: (e.noise ? noiseOf.of(e) : noteOf.of(e)) ?? {
|
|
157
|
+
id: "",
|
|
158
|
+
title: e.title,
|
|
159
|
+
body: e.body
|
|
160
|
+
}
|
|
161
|
+
}));
|
|
162
|
+
const ticked = decided.filter(({ e }) => e.fix).map(({ e, finding }) => ({
|
|
163
|
+
...finding,
|
|
164
|
+
body: withGuidance(finding.body, e.guidance)
|
|
165
|
+
}));
|
|
166
|
+
let fixing = NOTHING_FIXED;
|
|
167
|
+
if (ticked.length > 0) {
|
|
168
|
+
await run("quality.picking", writeScript(QUALITY, qualityFindingsFile(ticked)), { label: "Keeping the findings you ticked" });
|
|
169
|
+
fixing = await fixTurns(ticked, options);
|
|
170
|
+
}
|
|
171
|
+
const unticked = (noisy) => decided.filter(({ e }) => !e.fix && e.noise === noisy).map(({ finding }) => finding);
|
|
172
|
+
return {
|
|
173
|
+
gateFixed: fixing.fixed,
|
|
174
|
+
dropped: [...unticked(false), ...noteOf.left],
|
|
175
|
+
noise: [...unticked(true), ...noiseOf.left],
|
|
176
|
+
unresolved: [...fixing.open, ...fixing.unclear.map((f) => ({
|
|
177
|
+
...f,
|
|
178
|
+
unclear: true
|
|
179
|
+
}))]
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* Judge each finding in `.gtd/QUALITY.md` and fix the mechanical ones for up
|
|
184
|
+
* to `FIX_CAP` turns; the rest — design, unresolved and noise — go to the
|
|
185
|
+
* finding gate. Leaves `.gtd/QUALITY.md` holding only what the last fixer
|
|
186
|
+
* left: the caller must remove it, or the next lap appends to stale findings.
|
|
187
|
+
*/
|
|
188
|
+
const triageFindings = async (options) => {
|
|
189
|
+
const withLens = (f) => {
|
|
190
|
+
const lens = options.lensOf?.(f);
|
|
191
|
+
return lens === void 0 ? f : {
|
|
192
|
+
...f,
|
|
193
|
+
lens
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
const findings = findingsOf(read(".gtd/QUALITY.md") ?? "").map(withLens);
|
|
197
|
+
if (findings.length === 0) return NO_FINDINGS;
|
|
198
|
+
const { mechanical, noise, design } = await classify(findings);
|
|
199
|
+
let auto = NOTHING_FIXED;
|
|
200
|
+
if (mechanical.length > 0) {
|
|
201
|
+
const sorted = qualityFindingsFile(mechanical);
|
|
202
|
+
if (read(".gtd/QUALITY.md") !== sorted) await run("quality.sorting", writeScript(QUALITY, sorted), { label: "Setting findings aside for the finding gate" });
|
|
203
|
+
auto = await fixTurns(mechanical, options);
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
...await findingGate([
|
|
126
207
|
...design,
|
|
127
|
-
...open.map((f) => ({
|
|
208
|
+
...auto.open.map((f) => ({
|
|
128
209
|
...f,
|
|
129
210
|
unresolved: true
|
|
130
211
|
})),
|
|
131
|
-
...unclear.map((f) => ({
|
|
212
|
+
...auto.unclear.map((f) => ({
|
|
132
213
|
...f,
|
|
133
214
|
unclear: true
|
|
134
215
|
}))
|
|
135
|
-
],
|
|
136
|
-
fixed
|
|
137
|
-
noise
|
|
216
|
+
], noise, options),
|
|
217
|
+
fixed: auto.fixed
|
|
138
218
|
};
|
|
139
219
|
};
|
|
140
220
|
//#endregion
|
|
@@ -236,28 +316,36 @@ const runTail = (finish, options) => scope("build", async () => {
|
|
|
236
316
|
* `.gtd/REVIEW.md`; a note leaves it, with the reviewer's own version as
|
|
237
317
|
* `REVIEW_ORIG`, for the next `fix` to read — never a re-plan.
|
|
238
318
|
*/
|
|
239
|
-
const handOffReview = (base, escalations, guard, report = NO_FINDINGS) =>
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
guard
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
319
|
+
const handOffReview = (base, escalations, guard, report = NO_FINDINGS) => {
|
|
320
|
+
let riskFixed = false;
|
|
321
|
+
return reviewToHandOff(base, {
|
|
322
|
+
file: REVIEW,
|
|
323
|
+
reviewing: guarded(async (b, carry) => {
|
|
324
|
+
const afterRiskFix = riskFixed;
|
|
325
|
+
riskFixed = false;
|
|
326
|
+
await reviewing(b, carry);
|
|
327
|
+
const review = read(REVIEW);
|
|
328
|
+
if (review === void 0 || !afterRiskFix && reviewRisks(review).length > 0) return;
|
|
329
|
+
const full = withSummary(review, { report });
|
|
330
|
+
if (full !== review) await run("review.carrying", writeScript(REVIEW, full), { label: "Adding the quality lap's summary" });
|
|
331
|
+
}, guard),
|
|
332
|
+
fixRisks: guarded((risks) => {
|
|
333
|
+
riskFixed = true;
|
|
334
|
+
return fixRisks(risks);
|
|
335
|
+
}, guard),
|
|
336
|
+
keepGreen: () => healthy(fixCheck, {
|
|
337
|
+
escalations,
|
|
338
|
+
guard
|
|
339
|
+
}),
|
|
340
|
+
awaitReview: (b) => awaitReview(b, handOffAwaitReviewMessage(b)),
|
|
341
|
+
reviewMissing,
|
|
342
|
+
close: closeReview,
|
|
343
|
+
handOff: (reviewed) => run("review.handing-off", showScript(reviewed, REVIEW, REVIEW_ORIG), { label: "Handing your notes to fix" }),
|
|
344
|
+
threadsPending: "gtd land: open-threads: a thread in .gtd/REVIEW.md or an `H:` code comment waits on the agent — nothing answers it here; delete it, or turn it into a note for fix.",
|
|
345
|
+
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.",
|
|
346
|
+
deleted: "gtd land: review-doc: .gtd/REVIEW.md was deleted — restore it, or leave a note to hand to fix."
|
|
347
|
+
});
|
|
348
|
+
};
|
|
261
349
|
/** The tail the fix workflow runs: ends at the hand-off review since `base`, which gets what the lap left. */
|
|
262
350
|
const handOffTail = (fixFirst, base, guard) => tail((escalations, report) => handOffReview(base, escalations, guard, report), {
|
|
263
351
|
fixFirst,
|