@pmelab/gtd 58.2.0 → 59.0.1
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 +120 -395
- package/dist/chunks/{Schema-B3o6eRNU.mjs → testkit-07Z_ENch.mjs} +13503 -3460
- package/dist/chunks/{wire-C12dahrH.mjs → wire-BTUhRGXj.mjs} +1 -1
- package/dist/flows.mjs +1 -1
- package/dist/gtd.bundle.mjs +484 -497
- package/dist/testkit/gherkin.mjs +145 -65
- package/dist/testkit.mjs +1 -1
- package/dist/wire.mjs +1 -1
- package/package.json +3 -42
- package/skills/authoring/SKILL.md +34 -27
- package/src/cli/Cli.test.ts +148 -71
- package/src/cli/Cli.ts +384 -463
- package/src/cli/Help.test.ts +47 -0
- package/src/cli/Help.ts +70 -0
- package/src/cli/Program.test.ts +127 -33
- package/src/cli/Program.ts +70 -25
- package/src/edge/Edge.ts +4 -4
- package/src/emit/Emit.test.ts +1 -1
- package/src/emit/Emit.ts +1 -1
- package/src/flags/index.test.ts +0 -13
- package/src/flags/index.ts +1 -3
- package/src/flows/factory.ts +3 -1
- package/src/gherkin/index.ts +135 -71
- package/src/gherkin/messages.test.ts +90 -0
- package/src/gherkin/messages.ts +84 -0
- package/src/install/Install.test.ts +14 -11
- package/src/install/Install.ts +38 -15
- package/src/install/index.ts +1 -2
- package/src/replay/Replay.test.ts +1 -1
- package/src/replay/index.ts +1 -1
- package/src/step/planStart.ts +6 -1
- package/src/testkit/TestRepo.test.ts +47 -2
- package/src/testkit/TestRepo.ts +51 -6
- package/src/twins/EmittedScriptRecognizer.ts +5 -8
- package/src/twins/FlowScripts.ts +1 -1
- package/src/wire/Land.ts +1 -1
- package/src/workflow/index.ts +1 -0
- package/src/workflow/load.test.ts +138 -113
- package/src/workflow/load.ts +7 -7
- package/src/workflow/skills.test.ts +5 -5
- package/src/workflow/skills.ts +1 -1
- package/src/workflow/vars.test.ts +30 -3
- package/src/workflow/vars.ts +34 -2
- package/src/workflow-kit/index.ts +1 -1
- package/dist/chunks/reserved-lMYCaapY.mjs +0 -1990
- package/dist/chunks/review-Bfaror38.mjs +0 -427
- package/dist/chunks/tail-Bd7L940p.mjs +0 -355
- package/dist/chunks/testkit-JDtpNKyi.mjs +0 -10410
- package/dist/workflows/cleanup.mjs +0 -36
- package/dist/workflows/feature.mjs +0 -1018
- package/dist/workflows/fix.mjs +0 -41
- package/dist/workflows/review.mjs +0 -45
- package/dist/workflows/rollout.mjs +0 -48
- package/dist/workflows.mjs +0 -40651
- package/src/install/ConfigScaffold.ts +0 -22
- package/src/workflows/cleanup/cleanup-path.feature +0 -94
- package/src/workflows/cleanup/cleanup.feature +0 -315
- package/src/workflows/cleanup/cleanup.test.ts +0 -276
- package/src/workflows/cleanup/cleanup.ts +0 -52
- package/src/workflows/cleanup/index.ts +0 -6
- package/src/workflows/feature/acceptance-criteria-tier.feature +0 -25
- package/src/workflows/feature/acceptance-criteria.feature +0 -389
- package/src/workflows/feature/criteria-coverage.feature +0 -102
- package/src/workflows/feature/criteria.test.ts +0 -147
- package/src/workflows/feature/criteria.ts +0 -267
- package/src/workflows/feature/fast-path.feature +0 -93
- package/src/workflows/feature/feature.test.ts +0 -338
- package/src/workflows/feature/feature.ts +0 -156
- package/src/workflows/feature/green.ts +0 -19
- package/src/workflows/feature/index.ts +0 -18
- package/src/workflows/feature/lens-models.feature +0 -69
- package/src/workflows/feature/lens-review.feature +0 -566
- package/src/workflows/feature/naive-acceptance.feature +0 -147
- package/src/workflows/feature/naive-test.feature +0 -263
- package/src/workflows/feature/one-step-land.feature +0 -93
- package/src/workflows/feature/planning.ts +0 -41
- package/src/workflows/feature/refactor-absorbs.feature +0 -131
- package/src/workflows/feature/refactor-first.feature +0 -158
- package/src/workflows/feature/refactor-lens-models.feature +0 -79
- package/src/workflows/feature/refactor-rebuild.feature +0 -273
- package/src/workflows/feature/refactor-review.feature +0 -1528
- package/src/workflows/feature/refactor.test.ts +0 -1220
- package/src/workflows/feature/refactor.ts +0 -368
- package/src/workflows/feature/release-flag.feature +0 -167
- package/src/workflows/feature/release-flag.test.ts +0 -49
- package/src/workflows/feature/release-flag.ts +0 -46
- package/src/workflows/feature/review-gate.feature +0 -241
- package/src/workflows/feature/reviewer-walkthrough.feature +0 -142
- package/src/workflows/feature/risk-tier.feature +0 -351
- package/src/workflows/feature/seam-rejection.feature +0 -146
- package/src/workflows/feature/seam.feature +0 -297
- package/src/workflows/feature/seam.test.ts +0 -527
- package/src/workflows/feature/seam.ts +0 -426
- package/src/workflows/feature/standards.feature +0 -354
- package/src/workflows/feature/steps.test.ts +0 -65
- package/src/workflows/feature/steps.ts +0 -45
- package/src/workflows/feature/tier-pin.feature +0 -66
- package/src/workflows/feature/tier.test.ts +0 -166
- package/src/workflows/feature/tier.ts +0 -56
- package/src/workflows/fix/fix-lens-origin.feature +0 -114
- package/src/workflows/fix/fix-quality.feature +0 -212
- package/src/workflows/fix/fix.feature +0 -79
- package/src/workflows/fix/fix.test.ts +0 -74
- package/src/workflows/fix/fix.ts +0 -52
- package/src/workflows/fix/index.ts +0 -6
- package/src/workflows/index.ts +0 -47
- package/src/workflows/review/index.ts +0 -6
- package/src/workflows/review/review-door.feature +0 -199
- package/src/workflows/review/review.test.ts +0 -27
- package/src/workflows/review/review.ts +0 -60
- package/src/workflows/rollout/index.ts +0 -6
- package/src/workflows/rollout/rollout.feature +0 -510
- package/src/workflows/rollout/rollout.test.ts +0 -263
- package/src/workflows/rollout/rollout.ts +0 -82
- package/src/workflows/shared/__fixtures__/feature-collecting-prompt.txt +0 -98
- package/src/workflows/shared/access.test.ts +0 -62
- package/src/workflows/shared/access.ts +0 -24
- package/src/workflows/shared/config.fixture.ts +0 -12
- package/src/workflows/shared/fences.ts +0 -24
- package/src/workflows/shared/health.test.ts +0 -148
- package/src/workflows/shared/health.ts +0 -191
- package/src/workflows/shared/learn.test.ts +0 -140
- package/src/workflows/shared/learn.ts +0 -201
- package/src/workflows/shared/lenses.test.ts +0 -39
- package/src/workflows/shared/lenses.ts +0 -294
- package/src/workflows/shared/options.test.ts +0 -157
- package/src/workflows/shared/options.ts +0 -92
- package/src/workflows/shared/progress.feature +0 -124
- package/src/workflows/shared/prose.ts +0 -263
- package/src/workflows/shared/recheck.feature +0 -60
- package/src/workflows/shared/registry.test.ts +0 -345
- package/src/workflows/shared/registry.ts +0 -183
- package/src/workflows/shared/reserved.ts +0 -11
- package/src/workflows/shared/review.test.ts +0 -1062
- package/src/workflows/shared/review.ts +0 -446
- package/src/workflows/shared/skills.test.ts +0 -121
- package/src/workflows/shared/skills.ts +0 -49
- package/src/workflows/shared/standards.test.ts +0 -90
- package/src/workflows/shared/standards.ts +0 -156
- package/src/workflows/shared/steps.test.ts +0 -219
- package/src/workflows/shared/steps.ts +0 -201
- package/src/workflows/shared/tail.test.ts +0 -454
- package/src/workflows/shared/tail.ts +0 -264
- package/src/workflows/shared/text.fixture.ts +0 -128
- package/src/workflows/shared/text.test.ts +0 -1430
- package/src/workflows/shared/text.ts +0 -1700
- package/src/workflows/shared/threads.test.ts +0 -129
- package/src/workflows/shared/vars.ts +0 -47
- package/src/workflows/shared/workflows.steps.ts +0 -54
package/dist/testkit/gherkin.mjs
CHANGED
|
@@ -1,9 +1,66 @@
|
|
|
1
|
-
import { n as testRepo, t as runsItsScript } from "../chunks/testkit-
|
|
1
|
+
import { n as testRepo, t as runsItsScript } from "../chunks/testkit-07Z_ENch.mjs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import assert from "node:assert";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { Given, QuickPickleWorld, Then, When, setWorldConstructor } from "quickpickle";
|
|
6
|
+
//#region src/gherkin/messages.ts
|
|
7
|
+
const CONTENT_LINES = 5;
|
|
8
|
+
const reason = (error) => error instanceof Error ? error.message : String(error);
|
|
9
|
+
const beatLines = async (repo) => {
|
|
10
|
+
try {
|
|
11
|
+
const beat = await repo.next();
|
|
12
|
+
const content = beat.content.trimEnd().split("\n");
|
|
13
|
+
const more = content.length - CONTENT_LINES;
|
|
14
|
+
return [
|
|
15
|
+
`step "${beat.state}" (${beat.kind}, ${beat.actor}${beat.idle ? ", idle" : ""})`,
|
|
16
|
+
"content:",
|
|
17
|
+
...content.slice(0, CONTENT_LINES).map((line) => ` | ${line}`),
|
|
18
|
+
...more > 0 ? [` | … ${more} more lines`] : []
|
|
19
|
+
];
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return [`unknown — gtd next failed: ${reason(error)}`];
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/** Where the process rests, for a failed outcome to end with. Never throws: it describes a failure. */
|
|
25
|
+
const restDescription = async (repo, neverUsed) => {
|
|
26
|
+
let lines;
|
|
27
|
+
try {
|
|
28
|
+
const r = repo();
|
|
29
|
+
lines = [
|
|
30
|
+
...await beatLines(r),
|
|
31
|
+
"last commits:",
|
|
32
|
+
...r.subjects().slice(-5).map((subject) => ` ${subject}`)
|
|
33
|
+
];
|
|
34
|
+
} catch (error) {
|
|
35
|
+
lines = [`unknown — ${reason(error)}`];
|
|
36
|
+
}
|
|
37
|
+
if (neverUsed.length > 0) lines.push(`actors never used: ${neverUsed.map((step) => `"${step}"`).join(", ")}`);
|
|
38
|
+
return ["where the process rests:", ...lines.map((line) => ` ${line}`)].join("\n");
|
|
39
|
+
};
|
|
40
|
+
/** Both sides of a failed `contains`: the text looked for, and what was there. */
|
|
41
|
+
const containsFailure = (where, text, content, expected) => content === void 0 ? `${where} does not exist${expected ? `, so it cannot contain "${text}"` : ""}` : `${where} ${expected ? "does not contain" : "contains"} "${text}"; it holds:\n${content}`;
|
|
42
|
+
/** What `attempt` was refused with; fails when it was not refused at all. */
|
|
43
|
+
const refusal = async (attempt, expectation) => {
|
|
44
|
+
try {
|
|
45
|
+
await attempt;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return error instanceof Error ? error.message : String(error);
|
|
48
|
+
}
|
|
49
|
+
assert.fail(`expected a refusal ${expectation}, but nothing was refused`);
|
|
50
|
+
};
|
|
51
|
+
const refusedWith = async (attempt, message) => {
|
|
52
|
+
const got = await refusal(attempt, `with "${message}"`);
|
|
53
|
+
if (!got.includes(message)) assert.fail(`expected a refusal with "${message}", but it was refused with:\n${got}`);
|
|
54
|
+
};
|
|
55
|
+
const assertContains = (where, text, content) => {
|
|
56
|
+
if (content === void 0 || !content.includes(text)) assert.fail(containsFailure(where, text, content, true));
|
|
57
|
+
};
|
|
58
|
+
const assertLacks = (where, text, content) => {
|
|
59
|
+
if (content === void 0 || content.includes(text)) assert.fail(containsFailure(where, text, content, false));
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
6
62
|
//#region src/gherkin/index.ts
|
|
63
|
+
const usedActors = /* @__PURE__ */ new WeakMap();
|
|
7
64
|
var TestkitWorld = class extends QuickPickleWorld {
|
|
8
65
|
constructor(context, info) {
|
|
9
66
|
super(context, info);
|
|
@@ -51,6 +108,8 @@ var TestkitWorld = class extends QuickPickleWorld {
|
|
|
51
108
|
}
|
|
52
109
|
async drive() {
|
|
53
110
|
const actors = {};
|
|
111
|
+
const used = usedActors.get(this) ?? /* @__PURE__ */ new Set();
|
|
112
|
+
usedActors.set(this, used);
|
|
54
113
|
for (const [step, actions] of this.actions) actors[step] = async (beat, repo) => {
|
|
55
114
|
for (const action of actions) await action(beat, repo);
|
|
56
115
|
};
|
|
@@ -67,12 +126,35 @@ var TestkitWorld = class extends QuickPickleWorld {
|
|
|
67
126
|
return hit[1];
|
|
68
127
|
};
|
|
69
128
|
}
|
|
70
|
-
|
|
129
|
+
const tracked = {};
|
|
130
|
+
for (const [step, actor] of Object.entries(actors)) tracked[step] = (beat, repo) => {
|
|
131
|
+
used.add(step);
|
|
132
|
+
return actor(beat, repo);
|
|
133
|
+
};
|
|
134
|
+
await this.repo().drive(tracked);
|
|
71
135
|
}
|
|
72
136
|
};
|
|
73
137
|
setWorldConstructor(TestkitWorld);
|
|
74
138
|
const withNewline = (content) => content.endsWith("\n") ? content : `${content}\n`;
|
|
75
139
|
const args = (list) => list.split(" ").filter((arg) => arg !== "");
|
|
140
|
+
const neverUsed = (world) => {
|
|
141
|
+
const used = usedActors.get(world) ?? /* @__PURE__ */ new Set();
|
|
142
|
+
return [.../* @__PURE__ */ new Set([
|
|
143
|
+
...world.actions.keys(),
|
|
144
|
+
...world.verdicts.keys(),
|
|
145
|
+
...world.conditionalVerdicts.keys()
|
|
146
|
+
])].filter((step) => !used.has(step));
|
|
147
|
+
};
|
|
148
|
+
/** An outcome step: a failure ends with where the process rests. */
|
|
149
|
+
const outcome = (check) => async (world, ...rest) => {
|
|
150
|
+
try {
|
|
151
|
+
await check(world, ...rest);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
154
|
+
failure.message += `\n\n${await restDescription(() => world.repo(), neverUsed(world))}`;
|
|
155
|
+
throw failure;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
76
158
|
Given("a gtd config with:", (world, source) => {
|
|
77
159
|
world.configure(withNewline(source));
|
|
78
160
|
});
|
|
@@ -197,87 +279,85 @@ When("gtd lands", async (world) => {
|
|
|
197
279
|
When("the process is driven", async (world) => {
|
|
198
280
|
await world.drive();
|
|
199
281
|
});
|
|
200
|
-
Then("opening door {string} is refused with {string}", async (world, door, message) => {
|
|
201
|
-
await
|
|
202
|
-
});
|
|
203
|
-
Then("starting workflow {string} with {string} is refused with {string}", async (world, workflow, list, message) => {
|
|
204
|
-
await
|
|
205
|
-
});
|
|
206
|
-
Then("landing is refused with {string}", async (world, message) => {
|
|
207
|
-
await
|
|
208
|
-
});
|
|
209
|
-
Then("driving the process is refused with {string}", async (world, message) => {
|
|
210
|
-
await
|
|
211
|
-
});
|
|
212
|
-
Then("driving the process is refused without {string}", async (world, message) => {
|
|
213
|
-
await
|
|
214
|
-
});
|
|
215
|
-
|
|
282
|
+
Then("opening door {string} is refused with {string}", outcome(async (world, door, message) => {
|
|
283
|
+
await refusedWith(world.repo().door(door), message);
|
|
284
|
+
}));
|
|
285
|
+
Then("starting workflow {string} with {string} is refused with {string}", outcome(async (world, workflow, list, message) => {
|
|
286
|
+
await refusedWith(world.repo().start(workflow, ...args(list)), message);
|
|
287
|
+
}));
|
|
288
|
+
Then("landing is refused with {string}", outcome(async (world, message) => {
|
|
289
|
+
await refusedWith(world.repo().land(), message);
|
|
290
|
+
}));
|
|
291
|
+
Then("driving the process is refused with {string}", outcome(async (world, message) => {
|
|
292
|
+
await refusedWith(world.drive(), message);
|
|
293
|
+
}));
|
|
294
|
+
Then("driving the process is refused without {string}", outcome(async (world, message) => {
|
|
295
|
+
const got = await refusal(world.drive(), `without "${message}"`);
|
|
296
|
+
if (got.includes(message)) assert.fail(`expected a refusal without "${message}", but it was refused with:\n${got}`);
|
|
297
|
+
}));
|
|
298
|
+
Then("the process rests at {string}", outcome(async (world, step) => {
|
|
216
299
|
assert.strictEqual((await world.repo().next()).state, step);
|
|
217
|
-
});
|
|
218
|
-
Then("the resting step's content contains {string}", async (world, text) => {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
})
|
|
222
|
-
Then("the resting step's content contains the hash of the commit {string}", async (world, subject) => {
|
|
300
|
+
}));
|
|
301
|
+
Then("the resting step's content contains {string}", outcome(async (world, text) => {
|
|
302
|
+
assertContains("the resting step's content", text, (await world.repo().next()).content);
|
|
303
|
+
}));
|
|
304
|
+
Then("the resting step's content contains the hash of the commit {string}", outcome(async (world, subject) => {
|
|
223
305
|
const repo = world.repo();
|
|
224
306
|
const hash = repo.hashes()[repo.subjects().lastIndexOf(subject)];
|
|
225
307
|
assert.ok(hash !== void 0, `no commit "${subject}"`);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
});
|
|
233
|
-
Then("the last commit subject is {string}", (world, subject) => {
|
|
308
|
+
assertContains("the resting step's content", hash, (await repo.next()).content);
|
|
309
|
+
}));
|
|
310
|
+
Then("the resting step's content does not contain {string}", outcome(async (world, text) => {
|
|
311
|
+
assertLacks("the resting step's content", text, (await world.repo().next()).content);
|
|
312
|
+
}));
|
|
313
|
+
Then("the last commit subject is {string}", outcome((world, subject) => {
|
|
234
314
|
assert.strictEqual(world.repo().subjects().at(-1), subject);
|
|
235
|
-
});
|
|
236
|
-
Then("the commit subjects end with:", (world, list) => {
|
|
315
|
+
}));
|
|
316
|
+
Then("the commit subjects end with:", outcome((world, list) => {
|
|
237
317
|
const expected = list.split("\n").filter((line) => line.trim() !== "");
|
|
238
318
|
assert.deepStrictEqual(world.repo().subjects().slice(-expected.length), expected);
|
|
239
|
-
});
|
|
319
|
+
}));
|
|
240
320
|
const outputOf = async (world, list) => {
|
|
241
321
|
const result = await world.repo().gtd(...args(list));
|
|
242
322
|
assert.strictEqual(result.exitCode, 0, result.stderr);
|
|
243
323
|
return result.stdout;
|
|
244
324
|
};
|
|
245
|
-
Then("the output of gtd {string} contains {string}", async (world, list, text) => {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
});
|
|
253
|
-
Then("the output lines of gtd {string} start with:", async (world, list, prefixes) => {
|
|
325
|
+
Then("the output of gtd {string} contains {string}", outcome(async (world, list, text) => {
|
|
326
|
+
assertContains(`the output of gtd ${list}`, text, await outputOf(world, list));
|
|
327
|
+
}));
|
|
328
|
+
Then("the output of gtd {string} does not contain {string}", outcome(async (world, list, text) => {
|
|
329
|
+
assertLacks(`the output of gtd ${list}`, text, await outputOf(world, list));
|
|
330
|
+
}));
|
|
331
|
+
Then("the output lines of gtd {string} start with:", outcome(async (world, list, prefixes) => {
|
|
254
332
|
const out = (await outputOf(world, list)).split("\n").filter((line) => line !== "");
|
|
255
333
|
const expected = prefixes.split("\n").filter((line) => line.trim() !== "");
|
|
256
|
-
|
|
257
|
-
out.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
});
|
|
265
|
-
|
|
334
|
+
const sides = `expected lines starting with:\n${expected.join("\n")}\ngot:\n${out.join("\n")}`;
|
|
335
|
+
assert.strictEqual(out.length, expected.length, sides);
|
|
336
|
+
out.forEach((line, i) => assert.ok(line.startsWith(expected[i]), sides));
|
|
337
|
+
}));
|
|
338
|
+
Then("{string} contains {string}", outcome((world, path, text) => {
|
|
339
|
+
assertContains(`"${path}"`, text, world.repo().read(path));
|
|
340
|
+
}));
|
|
341
|
+
Then("{string} contains:", outcome((world, path, text) => {
|
|
342
|
+
assertContains(`"${path}"`, text, world.repo().read(path));
|
|
343
|
+
}));
|
|
344
|
+
Then("{string} contains the short hash of the commit {string}", outcome((world, path, subject) => {
|
|
266
345
|
const repo = world.repo();
|
|
267
346
|
const hash = repo.hashes()[repo.subjects().lastIndexOf(subject)];
|
|
268
347
|
assert.ok(hash !== void 0, `no commit "${subject}"`);
|
|
269
|
-
|
|
270
|
-
});
|
|
271
|
-
Then("{string} contains {string} exactly once", (world, path, text) => {
|
|
348
|
+
assertContains(`"${path}"`, hash.slice(0, 7), repo.read(path));
|
|
349
|
+
}));
|
|
350
|
+
Then("{string} contains {string} exactly once", outcome((world, path, text) => {
|
|
272
351
|
const content = world.repo().read(path) ?? "";
|
|
273
|
-
|
|
274
|
-
});
|
|
275
|
-
|
|
352
|
+
const times = content.split(text).length - 1;
|
|
353
|
+
assert.strictEqual(times, 1, `"${path}" contains "${text}" ${times} times; it holds:\n${content}`);
|
|
354
|
+
}));
|
|
355
|
+
Then("{string} does not contain {string}", outcome((world, path, text) => {
|
|
356
|
+
assertLacks(`"${path}"`, text, world.repo().read(path));
|
|
357
|
+
}));
|
|
358
|
+
Then("{string} does not exist", outcome((world, path) => {
|
|
276
359
|
const content = world.repo().read(path);
|
|
277
|
-
assert.
|
|
278
|
-
});
|
|
279
|
-
Then("{string} does not exist", (world, path) => {
|
|
280
|
-
assert.strictEqual(world.repo().read(path), void 0);
|
|
281
|
-
});
|
|
360
|
+
assert.strictEqual(content, void 0, `"${path}" exists; it holds:\n${content}`);
|
|
361
|
+
}));
|
|
282
362
|
//#endregion
|
|
283
363
|
export { TestkitWorld };
|
package/dist/testkit.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as testRepo, r as InMemRepo, t as runsItsScript } from "./chunks/testkit-
|
|
1
|
+
import { n as testRepo, r as InMemRepo, t as runsItsScript } from "./chunks/testkit-07Z_ENch.mjs";
|
|
2
2
|
export { InMemRepo, runsItsScript, testRepo };
|
package/dist/wire.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as EXIT_SIGTERM, D as UNATTRIBUTED_MODEL, E as ACCESS_REFUSAL, O as WIRE_SCHEMA, S as EXIT_SIGINT, T as EXIT_USAGE_ERROR, _ as demandOf, a as landFields, b as EXIT_OK, c as renderLandJson, d as renderBeatPlain, f as beatDocument, g as beatKindOf, h as BEAT_KINDS, i as judgeJson, k as renderFormat, l as renderLandPlain, m as statusOf, n as resolveSession, o as landProseText, p as renderBeatJson, r as selectPath, s as noopText, t as globMatches, u as DEMAND_BRIEFING, v as stallDiagnosis, w as EXIT_STRAYS_FOUND, x as EXIT_RUNTIME_ERROR, y as EXIT_CODES } from "./chunks/wire-
|
|
1
|
+
import { C as EXIT_SIGTERM, D as UNATTRIBUTED_MODEL, E as ACCESS_REFUSAL, O as WIRE_SCHEMA, S as EXIT_SIGINT, T as EXIT_USAGE_ERROR, _ as demandOf, a as landFields, b as EXIT_OK, c as renderLandJson, d as renderBeatPlain, f as beatDocument, g as beatKindOf, h as BEAT_KINDS, i as judgeJson, k as renderFormat, l as renderLandPlain, m as statusOf, n as resolveSession, o as landProseText, p as renderBeatJson, r as selectPath, s as noopText, t as globMatches, u as DEMAND_BRIEFING, v as stallDiagnosis, w as EXIT_STRAYS_FOUND, x as EXIT_RUNTIME_ERROR, y as EXIT_CODES } from "./chunks/wire-BTUhRGXj.mjs";
|
|
2
2
|
export { ACCESS_REFUSAL, BEAT_KINDS, DEMAND_BRIEFING, EXIT_CODES, EXIT_OK, EXIT_RUNTIME_ERROR, EXIT_SIGINT, EXIT_SIGTERM, EXIT_STRAYS_FOUND, EXIT_USAGE_ERROR, UNATTRIBUTED_MODEL, WIRE_SCHEMA, beatDocument, beatKindOf, demandOf, globMatches, judgeJson, landFields, landProseText, noopText, renderBeatJson, renderBeatPlain, renderFormat, renderLandJson, renderLandPlain, resolveSession, selectPath, stallDiagnosis, statusOf };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pmelab/gtd",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "59.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
|
|
6
6
|
"bin": {
|
|
@@ -27,41 +27,6 @@
|
|
|
27
27
|
"source": "./src/workflow-kit/index.ts",
|
|
28
28
|
"default": "./dist/workflow-kit.mjs"
|
|
29
29
|
},
|
|
30
|
-
"./workflows": {
|
|
31
|
-
"types": "./src/workflows/index.ts",
|
|
32
|
-
"source": "./src/workflows/index.ts",
|
|
33
|
-
"default": "./dist/workflows.mjs"
|
|
34
|
-
},
|
|
35
|
-
"./workflows/feature": {
|
|
36
|
-
"types": "./src/workflows/feature/index.ts",
|
|
37
|
-
"source": "./src/workflows/feature/index.ts",
|
|
38
|
-
"default": "./dist/workflows/feature.mjs"
|
|
39
|
-
},
|
|
40
|
-
"./workflows/review": {
|
|
41
|
-
"types": "./src/workflows/review/index.ts",
|
|
42
|
-
"source": "./src/workflows/review/index.ts",
|
|
43
|
-
"default": "./dist/workflows/review.mjs"
|
|
44
|
-
},
|
|
45
|
-
"./workflows/fix": {
|
|
46
|
-
"types": "./src/workflows/fix/index.ts",
|
|
47
|
-
"source": "./src/workflows/fix/index.ts",
|
|
48
|
-
"default": "./dist/workflows/fix.mjs"
|
|
49
|
-
},
|
|
50
|
-
"./workflow": {
|
|
51
|
-
"types": "./src/workflows/index.ts",
|
|
52
|
-
"source": "./src/workflows/index.ts",
|
|
53
|
-
"default": "./dist/workflows.mjs"
|
|
54
|
-
},
|
|
55
|
-
"./workflows/rollout": {
|
|
56
|
-
"types": "./src/workflows/rollout/index.ts",
|
|
57
|
-
"source": "./src/workflows/rollout/index.ts",
|
|
58
|
-
"default": "./dist/workflows/rollout.mjs"
|
|
59
|
-
},
|
|
60
|
-
"./workflows/cleanup": {
|
|
61
|
-
"types": "./src/workflows/cleanup/index.ts",
|
|
62
|
-
"source": "./src/workflows/cleanup/index.ts",
|
|
63
|
-
"default": "./dist/workflows/cleanup.mjs"
|
|
64
|
-
},
|
|
65
30
|
"./testkit": {
|
|
66
31
|
"types": "./src/testkit/index.ts",
|
|
67
32
|
"source": "./src/testkit/index.ts",
|
|
@@ -76,7 +41,6 @@
|
|
|
76
41
|
"files": [
|
|
77
42
|
"dist/",
|
|
78
43
|
"src/flows/",
|
|
79
|
-
"src/workflows/",
|
|
80
44
|
"src/workflow-kit/",
|
|
81
45
|
"src/wire/",
|
|
82
46
|
"src/steering/",
|
|
@@ -119,7 +83,7 @@
|
|
|
119
83
|
"scripts": {
|
|
120
84
|
"prepare": "husky",
|
|
121
85
|
"dev": "node dev/run.mjs",
|
|
122
|
-
"build": "tsdown --filter web && npx @tailwindcss/cli -i src/web/styles.css -o dist/web/main.css --minify && node scripts/inline-web-client.mjs && tsdown --filter gtd && tsdown --filter
|
|
86
|
+
"build": "tsdown --filter web && npx @tailwindcss/cli -i src/web/styles.css -o dist/web/main.css --minify && node scripts/inline-web-client.mjs && tsdown --filter gtd && tsdown --filter entrypoints",
|
|
123
87
|
"postbuild": "node scripts/assert-no-test-doubles.mjs",
|
|
124
88
|
"watch": "tsdown --watch",
|
|
125
89
|
"bench:replay": "node scripts/bench-replay.mjs",
|
|
@@ -141,9 +105,7 @@
|
|
|
141
105
|
"analyze": "fallow --summary --quiet",
|
|
142
106
|
"test:web": "vitest run --project storybook",
|
|
143
107
|
"storybook": "storybook dev --host 0.0.0.0",
|
|
144
|
-
"coverage": "vitest run --project unit --coverage"
|
|
145
|
-
"eval": ": \"${GTD_EVALS_URL:?GTD_EVALS_URL is required — the OpenAI-compatible gateway every graded turn and the judge run through}\" && : \"${GTD_EVALS_KEY:?GTD_EVALS_KEY is required — the gateway bearer key}\" && npx turbo run build && node evals/eval.mjs",
|
|
146
|
-
"eval:baseline": "node evals/compare-baseline.mjs --record"
|
|
108
|
+
"coverage": "vitest run --project unit --coverage"
|
|
147
109
|
},
|
|
148
110
|
"lint-staged": {
|
|
149
111
|
"*": "oxfmt --no-error-on-unmatched-pattern --write"
|
|
@@ -184,7 +146,6 @@
|
|
|
184
146
|
"oxlint": "^1.74.0",
|
|
185
147
|
"playwright": "^1.62.1",
|
|
186
148
|
"prettier": "^3.8.1",
|
|
187
|
-
"promptfoo": "0.122.1",
|
|
188
149
|
"quickpickle": "1.11.2",
|
|
189
150
|
"react": "^19.2.0",
|
|
190
151
|
"react-dom": "^19.2.0",
|
|
@@ -29,20 +29,25 @@ what a driver does.
|
|
|
29
29
|
state (`gtd next`, `gtd lsp`, `gtd door` and `gtd doors` included). It is code
|
|
30
30
|
the user's repository runs; write it with the same care as a build script.
|
|
31
31
|
|
|
32
|
-
## Golden rule: start from
|
|
32
|
+
## Golden rule: start from an existing workflow, edit incrementally
|
|
33
33
|
|
|
34
34
|
Do **not** write a workflow from a blank page unless the user wants something
|
|
35
|
-
tiny. gtd ships
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
tiny. gtd ships no workflow; https://github.com/pmelab/gtd-workflows does. Its
|
|
36
|
+
`default` (feature, review, fix, rollout, cleanup) is used from a checkout
|
|
37
|
+
pinned on the machine — `bin/gtd-wf pin` there, then `bin/gtd-wf config default`
|
|
38
|
+
prints a `gtd.config.ts` importing its bundle by absolute path. Every phase and
|
|
39
|
+
single step that bundle is built from is a named export too, so a config can
|
|
40
|
+
import what it keeps and write only what changes. Import phases by name; **never
|
|
40
41
|
`export *` from it** — every exported function of your file would become a
|
|
41
42
|
startable workflow:
|
|
42
43
|
|
|
43
44
|
```ts
|
|
44
45
|
import { start } from "@pmelab/gtd/flows"
|
|
45
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
afterTail,
|
|
48
|
+
buildTail,
|
|
49
|
+
feature,
|
|
50
|
+
} from "/home/me/.local/share/gtd/workflows/dist/default.mjs"
|
|
46
51
|
|
|
47
52
|
export {
|
|
48
53
|
defaults,
|
|
@@ -51,9 +56,9 @@ export {
|
|
|
51
56
|
base,
|
|
52
57
|
steering,
|
|
53
58
|
skills,
|
|
54
|
-
} from "
|
|
59
|
+
} from "/home/me/.local/share/gtd/workflows/dist/default.mjs"
|
|
55
60
|
|
|
56
|
-
export default feature
|
|
61
|
+
export default feature()
|
|
57
62
|
|
|
58
63
|
export async function hotfix() {
|
|
59
64
|
return afterTail(await buildTail(start(), { fixFirst: true }))
|
|
@@ -62,21 +67,22 @@ export async function hotfix() {
|
|
|
62
67
|
export const doors = { hotfix: { workflow: "hotfix" } }
|
|
63
68
|
```
|
|
64
69
|
|
|
65
|
-
`gtd --workflow hotfix` (or `gtd door hotfix`) starts it.
|
|
70
|
+
`gtd --workflow hotfix` (or `gtd door hotfix`) starts it. Use the checkout's
|
|
71
|
+
real path; `gtd-wf status` shows it.
|
|
66
72
|
|
|
67
|
-
To change a phase itself, read its source in the
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
To change a phase itself, read its source in the checkout (`workflows/default/`)
|
|
74
|
+
and either write your own version in `gtd.config.ts`, reusing its single steps,
|
|
75
|
+
or copy the whole workflow in a clone of gtd-workflows
|
|
76
|
+
(`bin/gtd-wf new <name> --from default`) and change it there, with its tests.
|
|
71
77
|
|
|
72
78
|
There is no `extends`/merge: the innermost `gtd.config.ts` walking up from the
|
|
73
79
|
current directory is the whole workflow. If one already exists, read it and edit
|
|
74
80
|
it in place.
|
|
75
81
|
|
|
76
|
-
Prefer the
|
|
82
|
+
Prefer the workflow's own parts over re-implementing them — `healthy`,
|
|
77
83
|
`escalation`, `gate`, `design`, `seamBuild`, `implement`, `qualityLap`,
|
|
78
84
|
`buildTail`, and single steps like `triage` or `fixCheck`. Their full step names
|
|
79
|
-
are versioned API.
|
|
85
|
+
are that workflow's versioned API, not gtd's.
|
|
80
86
|
|
|
81
87
|
Make one small change, **verify it loads** (see "Verify"), then make the next. A
|
|
82
88
|
workflow that fails to load breaks every gtd command in the repository.
|
|
@@ -133,7 +139,7 @@ inside. Skills are declared per scope, never on `agent()`, and a driver
|
|
|
133
139
|
restricts each turn to its scope's list where it can. `refuse(message)` refuses
|
|
134
140
|
the pending landing — call it right after the step whose turn you reject.
|
|
135
141
|
`@pmelab/gtd/flows` exports `requireProgress(file)`, `requireAnswers(file)` and
|
|
136
|
-
`requireRevert(edited, base)
|
|
142
|
+
`requireRevert(edited, base)` among its ready-made checks.
|
|
137
143
|
|
|
138
144
|
## Names, commits and history
|
|
139
145
|
|
|
@@ -142,15 +148,15 @@ the pending landing — call it right after the step whose turn you reject.
|
|
|
142
148
|
the last dot). Rename a step and every process resting on it diverges.
|
|
143
149
|
- An episode ends when the flow returns or calls `restart()`; the next starts at
|
|
144
150
|
the flow's first step on an ordinary start — that step is where a finished
|
|
145
|
-
process waits (
|
|
151
|
+
process waits (gtd-workflows' default uses `human("idle", …)`).
|
|
146
152
|
- `gtd --workflow <name>` starts a process on the exported workflow `<name>` (an
|
|
147
153
|
unknown name is a usage error); `gtd door <name> [args]` starts one through a
|
|
148
154
|
`doors` entry `{ workflow, args?, vars? }` (`gtd doors` lists them). A
|
|
149
|
-
factory's build may return `doors` without `workflow`; exporting
|
|
150
|
-
`fix`, `review`, `rollout` and `cleanup` brings `fix`,
|
|
151
|
-
`rollout <flag>` and `cleanup <flag>`, and yours merge over
|
|
152
|
-
`export const base = (workflow, vars) => commitish | undefined` fixes
|
|
153
|
-
started process's diff base (blank = default-branch merge-base).
|
|
155
|
+
factory's build may return `doors` without `workflow`; exporting
|
|
156
|
+
gtd-workflows' `fix`, `review`, `rollout` and `cleanup` brings `fix`,
|
|
157
|
+
`review [base]`, `rollout <flag>` and `cleanup <flag>`, and yours merge over
|
|
158
|
+
them. An `export const base = (workflow, vars) => commitish | undefined` fixes
|
|
159
|
+
a started process's diff base (blank = default-branch merge-base).
|
|
154
160
|
`--var <name>=<value>` only pins process settings: names the workflow's
|
|
155
161
|
`defaults` declare — never an environment setting.
|
|
156
162
|
|
|
@@ -200,7 +206,8 @@ divergence later.
|
|
|
200
206
|
network or filesystem. Read the tree through the helpers; do IO inside a
|
|
201
207
|
`run()` body.
|
|
202
208
|
- Await only a step, `scope()`, or a function that steps. Anything else fails
|
|
203
|
-
with `the flow awaited something that is not a step`.
|
|
209
|
+
with `the flow awaited something that is not a step`. gtd does not flag a step
|
|
210
|
+
you forget to await — turn on a `no-floating-promises` lint rule.
|
|
204
211
|
- One call site per step name — wrap a reused helper in two different
|
|
205
212
|
`scope()`s.
|
|
206
213
|
- No `try`/`catch` around a step: `restart()` and refusals travel as exceptions.
|
|
@@ -222,7 +229,7 @@ workflow.
|
|
|
222
229
|
|
|
223
230
|
## Worked example: add an approval gate before building
|
|
224
231
|
|
|
225
|
-
In
|
|
232
|
+
In gtd-workflows' default, `planAndBuild` runs the design phase, then
|
|
226
233
|
`buildFeature`: the naive pass and its seam judge, then the implementation (the
|
|
227
234
|
pass itself on `land`), the quality lap and the human review (a review feedback
|
|
228
235
|
lap re-runs design inside `buildFeature` and does not pass it again). Add a
|
|
@@ -260,5 +267,5 @@ process before switching to the edited workflow.
|
|
|
260
267
|
- This skill is versioned in the gtd repository, not auto-installed. When gtd is
|
|
261
268
|
upgraded, re-copy it from the new version's `skills/authoring/SKILL.md`.
|
|
262
269
|
- Where this file and the code disagree, the code wins: the step API's own doc
|
|
263
|
-
comments in `@pmelab/gtd/flows`, and the
|
|
264
|
-
`
|
|
270
|
+
comments in `@pmelab/gtd/flows`, and the workflow's source in its own
|
|
271
|
+
repository (gtd-workflows' `workflows/default/`).
|