@pmelab/gtd 24.4.0 → 26.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 -18
- package/dist/chunks/{reserved-B9pG93Fc.mjs → reserved-6pAgFGgB.mjs} +69 -125
- package/dist/gtd.bundle.mjs +28 -3
- package/dist/testkit.mjs +30 -5
- package/dist/workflow-kit.mjs +47 -90
- package/dist/workflows/feature.mjs +25 -47
- package/dist/workflows/fix.mjs +1 -1
- package/dist/workflows/review.mjs +1 -1
- package/dist/workflows.mjs +135 -261
- package/package.json +2 -2
- package/src/cli/Cli.test.ts +25 -0
- package/src/cli/Cli.ts +12 -1
- package/src/flags/index.test.ts +67 -0
- package/src/flags/index.ts +39 -0
- package/src/replay/Replay.ts +2 -1
- package/src/workflow/vars.test.ts +16 -0
- package/src/workflow/vars.ts +4 -1
- package/src/workflow-kit/scenarios.test.ts +116 -0
- package/src/workflow-kit/scenarios.ts +53 -113
- package/src/workflows/feature/feature.ts +2 -5
- package/src/workflows/feature/packages.test.ts +17 -34
- package/src/workflows/feature/packages.ts +13 -52
- package/src/workflows/feature/planning.ts +1 -1
- package/src/workflows/feature/review.test.ts +0 -47
- package/src/workflows/feature/review.ts +6 -10
- package/src/workflows/index.ts +0 -2
- package/src/workflows/shared/health.test.ts +1 -21
- package/src/workflows/shared/health.ts +4 -39
- package/src/workflows/shared/options.ts +0 -1
- package/src/workflows/shared/steps.ts +16 -11
- package/src/workflows/shared/tail.test.ts +1 -41
- package/src/workflows/shared/tail.ts +14 -30
- package/src/workflows/shared/text.fixture.ts +1 -0
- package/src/workflows/shared/text.test.ts +23 -41
- package/src/workflows/shared/text.ts +54 -74
- package/src/workflows/shared/vars.ts +0 -1
- package/src/workflows/shared/scenarios.test.ts +0 -256
- package/src/workflows/shared/scenarios.ts +0 -35
package/README.md
CHANGED
|
@@ -28,6 +28,8 @@ Or run without installing (prefix every `gtd` below with `npx`) — see
|
|
|
28
28
|
for the settings most projects tune. A process setting's value is committed to
|
|
29
29
|
Git history; keep secrets in an environment setting (see
|
|
30
30
|
[Settings](https://github.com/pmelab/gtd/blob/main/docs/configuration.md#settings)).
|
|
31
|
+
`GTD_FLAG_<NAME>` variables toggle experimental features — see
|
|
32
|
+
[Feature flags](https://github.com/pmelab/gtd/blob/main/docs/configuration.md#feature-flags).
|
|
31
33
|
|
|
32
34
|
Also install all three skill sources the bundled workflow names in its prompts —
|
|
33
35
|
none is optional. See
|
|
@@ -44,12 +46,6 @@ the read side to its agent. `read` is not a security boundary unless your driver
|
|
|
44
46
|
enforces it at the OS level. See
|
|
45
47
|
[Configuration](https://github.com/pmelab/gtd/blob/main/docs/configuration.md#file-access).
|
|
46
48
|
|
|
47
|
-
> **`fastTestCommand` is required, with no fallback to `testCommand`.** Set it
|
|
48
|
-
> (everything but e2e) as a workflow option in `gtd.config.ts`
|
|
49
|
-
> (`feature({ fastTestCommand: "npm run test:fast" })`) or as
|
|
50
|
-
> `GTD_FASTTESTCOMMAND`; gtd stops at the start gate, writing `.gtd/SETUP.md`,
|
|
51
|
-
> until it is.
|
|
52
|
-
|
|
53
49
|
> **A repository's `gtd.config.ts` is code, and gtd runs it.** A custom workflow
|
|
54
50
|
> is a TypeScript module, and every gtd command that looks at workflow state —
|
|
55
51
|
> `gtd next`, `gtd lsp`, `gtd door` and `gtd doors` included, not just
|
|
@@ -400,20 +396,21 @@ noted in steps 3 and 4 below.
|
|
|
400
396
|
questions and all. Every plan gets the technical pass: its document has four
|
|
401
397
|
sections, in order — `## Interfaces`, `## Call Stacks`, `## E2E Scenarios`,
|
|
402
398
|
`## Unit Tests` — behind a leading `## Open Questions` when there are any.
|
|
403
|
-
`## E2E Scenarios` is never empty: it
|
|
399
|
+
`## E2E Scenarios` is never empty: it names each user-visible behaviour in
|
|
400
|
+
plain words (the builder writes the scenario itself), or, when nothing
|
|
404
401
|
user-visible changes, the line `No e2e change.` with a one-line reason.
|
|
405
402
|
|
|
406
|
-
3. **You wait.** The work is split into
|
|
407
|
-
one starting from the unit tests it declares (a build turn
|
|
408
|
-
refused), checked against the
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
default, which never skips
|
|
416
|
-
`gtd judge run --provider fixed --answers <path>` — or the
|
|
403
|
+
3. **You wait.** The work is split into vertical slices and built one at a time,
|
|
404
|
+
each one starting from the unit and e2e tests it declares (a build turn
|
|
405
|
+
missing one is refused), checked against the full suite (`testCommand`) and
|
|
406
|
+
fixed until it passes before moving on — so keep that suite fast; gtd has no
|
|
407
|
+
lighter one. Scenario wording is not frozen: you sign the scenarios off once,
|
|
408
|
+
in the review's `## Scenarios` chunk, where any assertion an existing
|
|
409
|
+
scenario lost is marked `Weakened:`. After the last package the quality lap
|
|
410
|
+
runs. Two points along the process are judged rather than always asking you
|
|
411
|
+
outright — each stops and hands you a verdict to make (`gtd judge answer`, or
|
|
412
|
+
land with a clean tree to accept the conservative default, which never skips
|
|
413
|
+
work; `gtd judge run --provider fixed --answers <path>` — or the
|
|
417
414
|
`GTD_JUDGE_ANSWERS` env var, inline JSON — answers one from a file, piped
|
|
418
415
|
between `gtd judge --json` and `gtd judge answer`;
|
|
419
416
|
`gtd judge run --provider jev` asks TypeSafe's Jev instead, with the key in
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { accessFor, agent, answered, check, codeThreads, defineWorkflow, env, head, human, judge, numeric, quote, read, refuse, removeScript, run, scope, showScript, skillsFor, start, vars } from "../flows.mjs";
|
|
2
|
-
import { escalation as escalation$1, fixFindings, gate as gate$1, healthy as healthy$1,
|
|
1
|
+
import { accessFor, agent, answered, changesSince, check, codeThreads, defineWorkflow, env, head, human, judge, numeric, quote, read, refuse, removeScript, run, scope, showScript, skillsFor, start, vars } from "../flows.mjs";
|
|
2
|
+
import { escalation as escalation$1, fixFindings, gate as gate$1, healthy as healthy$1, lensScopes, lensesOf, qualityLap as qualityLap$1, removedAssertions, reviewToHandOff } from "../workflow-kit.mjs";
|
|
3
3
|
import { c as String$, g as standardSchemaV1, h as optionalWith, l as Struct } from "./Schema-B5yK7QrT.mjs";
|
|
4
|
-
import { dirname } from "node:path";
|
|
5
4
|
//#region src/workflows/shared/prose.ts
|
|
6
5
|
const skillsPreamble = `- Load whatever's listed here that your harness actually has installed,
|
|
7
6
|
skip anything it doesn't — silently, never stopping or asking about a
|
|
@@ -457,7 +456,8 @@ ${footnoteFoldIn}${codeThreadReplies()}
|
|
|
457
456
|
- Every open point here is TECHNICAL — triage already resolved
|
|
458
457
|
the product ones, one phase earlier. PERMISSIVE: answer it
|
|
459
458
|
yourself unless you genuinely cannot defend a default; a wrong
|
|
460
|
-
technical call is still caught by
|
|
459
|
+
technical call is still caught by each package's suite run and the
|
|
460
|
+
quality lap
|
|
461
461
|
- The narrow exception: a simplification that drops something
|
|
462
462
|
\`.gtd/REQUIREMENTS.md\` mentions is an open question, not a
|
|
463
463
|
silent default
|
|
@@ -469,11 +469,13 @@ ${footnoteFoldIn}${codeThreadReplies()}
|
|
|
469
469
|
only, as fenced TypeScript (or the repository's language)
|
|
470
470
|
3. \`## Call Stacks\` — seam level, entry point → module
|
|
471
471
|
boundary; one line per hop, never per line of code
|
|
472
|
-
4. \`## E2E Scenarios\` —
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
472
|
+
4. \`## E2E Scenarios\` — one line per user-visible behaviour,
|
|
473
|
+
\`- e2e: <path> — <behaviour> (covers <concern>)\`, in plain
|
|
474
|
+
words: the builder writes the scenario in the repository's
|
|
475
|
+
own e2e framework, so never spell out Gherkin here. With no
|
|
476
|
+
user-visible behaviour change, the literal line
|
|
477
|
+
\`No e2e change.\` plus a one-line reason — never empty or
|
|
478
|
+
missing
|
|
477
479
|
5. \`## Unit Tests\` — one \`### <interface>\` per entry of
|
|
478
480
|
\`## Interfaces\`, each test a line
|
|
479
481
|
\`- unit: <path> — <behaviour> (covers <concern>)\`;
|
|
@@ -499,35 +501,31 @@ const architectureDecomposePrompt = () => `${styleBlock}
|
|
|
499
501
|
${stateFileRules}
|
|
500
502
|
- The only state files this turn touches are the package files
|
|
501
503
|
under \`.gtd/packages/\` — no other files for notes or output.
|
|
502
|
-
Never delete \`.gtd/ARCHITECTURE.md\`:
|
|
503
|
-
|
|
504
|
+
Never delete \`.gtd/ARCHITECTURE.md\`: package files may
|
|
505
|
+
reference it, and it is removed with the last package
|
|
504
506
|
- Work from \`.gtd/ARCHITECTURE.md\` if you wrote it earlier this
|
|
505
507
|
conversation, otherwise read it (the converged technical
|
|
506
508
|
plan). It already lists the concerns, interfaces, e2e scenarios
|
|
507
509
|
and unit tests — this turn groups them into packages. A
|
|
508
510
|
\`## Merged Concerns\` heading there records merges, never a
|
|
509
511
|
concern of its own: write no package file for it
|
|
510
|
-
- A package is
|
|
511
|
-
green
|
|
512
|
-
|
|
513
|
-
same files stay in one package unless
|
|
514
|
-
an interface the earlier one creates.
|
|
515
|
-
|
|
516
|
-
interface-introducing packages first
|
|
512
|
+
- A package is one concern's slice, and each must leave the full
|
|
513
|
+
suite green — every package boundary costs a full run, so write
|
|
514
|
+
as few as the concerns allow. Keep the merge rule: concerns whose
|
|
515
|
+
footprints centre on the same files stay in one package unless
|
|
516
|
+
the later one only consumes an interface the earlier one creates.
|
|
517
|
+
Order them interface-introducing packages first
|
|
517
518
|
- Write the packages under \`.gtd/packages/\`, each carrying its
|
|
518
519
|
requirement(s) — both, independently, if merged — its tasks, and
|
|
519
520
|
each task's acceptance criteria as \`- [ ]\` checkboxes and
|
|
520
521
|
relevant paths:
|
|
521
|
-
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
- the last numbered package is the wiring package: CLI entry,
|
|
529
|
-
workflow composition, config. A red e2e after it is a bug, not
|
|
530
|
-
missing work
|
|
522
|
+
- \`01-…\` onward — one vertical slice each: the unit tests,
|
|
523
|
+
the code, the wiring (CLI entry, workflow composition, config)
|
|
524
|
+
and every \`## E2E Scenarios\` entry its concern covers, so the
|
|
525
|
+
slice ends with its scenarios passing. A requirement no test
|
|
526
|
+
covers (docs, README, deletions, config) is a task of the slice
|
|
527
|
+
it belongs to; only one no slice owns becomes a chore package,
|
|
528
|
+
which declares no tests
|
|
531
529
|
- Every package with tests has a \`## Tests\` section, one line per
|
|
532
530
|
declared test, copied from the architecture entries:
|
|
533
531
|
\`- unit: \\\`<path>\\\`\` or \`- e2e: \\\`<path>\\\`\`. Chore packages have
|
|
@@ -573,10 +571,10 @@ const packagesItemBuildingPrompt = (pkg) => `${stateFileRules}
|
|
|
573
571
|
- Write the tests the package declares under \`## Tests\` first,
|
|
574
572
|
then the code that makes them pass; the turn is refused unless
|
|
575
573
|
every declared test is in your diff
|
|
576
|
-
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
574
|
+
- A declared e2e test is a scenario in the repository's own e2e
|
|
575
|
+
framework for the behaviour its \`## E2E Scenarios\` entry names.
|
|
576
|
+
Word it to fit the existing steps — reuse and generalise them
|
|
577
|
+
rather than adding one-off ones
|
|
580
578
|
- Implement every task the package describes, no more, no less,
|
|
581
579
|
fanning independent ones out to parallel subagents where your
|
|
582
580
|
harness supports it; leave other package files untouched
|
|
@@ -630,22 +628,9 @@ Edit \`.gtd/ESCALATION.md\` with fresh instructions for the next fix
|
|
|
630
628
|
turn, or land untouched to give it one more attempt at the same
|
|
631
629
|
analysis.
|
|
632
630
|
`;
|
|
633
|
-
const
|
|
634
|
-
const list = (paths) => paths.length === 0 ? " (none)" : paths.map((path) => ` - ${path}`).join("\n");
|
|
635
|
-
return `- Read \`.gtd/ARCHITECTURE.md\` — the plan every package was built from
|
|
636
|
-
- A red scenario that is NEW in this plan likely means missing wiring
|
|
637
|
-
between packages; new scenarios:
|
|
638
|
-
${list(built.scenarios.added)}
|
|
639
|
-
- A red CHANGED or existing scenario or test is a regression a package
|
|
640
|
-
introduced; scenarios this plan changed:
|
|
641
|
-
${list(built.scenarios.changed)}
|
|
642
|
-
- Each package's commits, to bisect a break:
|
|
643
|
-
${built.ranges.map((r) => ` git log --oneline ${r.from}..${r.to} # ${r.pkg}`).join("\n")}
|
|
644
|
-
`;
|
|
645
|
-
};
|
|
646
|
-
const buildFixPrompt = (built) => `${stateFileRules}
|
|
631
|
+
const buildFixPrompt = () => `${stateFileRules}
|
|
647
632
|
${fixFeedbackPrompt}
|
|
648
|
-
|
|
633
|
+
- Leave everything uncommitted — do not commit
|
|
649
634
|
`;
|
|
650
635
|
const finisherSystem = () => `${finisherPersona}
|
|
651
636
|
|
|
@@ -966,7 +951,18 @@ actionability, and never dismiss a real note or edit as approval.
|
|
|
966
951
|
- If not: finish without writing anything — changing nothing is
|
|
967
952
|
the sign-off
|
|
968
953
|
`;
|
|
969
|
-
const
|
|
954
|
+
const scenariosPrompt = (removed) => `- When a scenario in a \`.feature\` file was added or changed since the
|
|
955
|
+
base, the first chunk is \`## Scenarios\`: one pointer per such
|
|
956
|
+
scenario, its note saying in plain words what behaviour it now pins
|
|
957
|
+
down — the human signs off the scenarios here, nowhere else
|
|
958
|
+
${removed.length === 0 ? "" : `- These assertions of scenarios that existed at the base are gone:
|
|
959
|
+
${removed.map(({ path, lines }) => lines.map((a) => ` ${path} — ${a.scenario} — ${a.line}\n`).join("")).join("")} Open the note of each scenario that lost one with \`Weakened:\`,
|
|
960
|
+
naming the lost line and whether \`.gtd/REQUIREMENTS.md\` asked
|
|
961
|
+
for it. A scenario that no longer exists has no pointer of its own:
|
|
962
|
+
put its \`Weakened:\` note on a pointer of the chunk covering its
|
|
963
|
+
removal
|
|
964
|
+
`}`;
|
|
965
|
+
const buildReviewReviewingPrompt = (base, carry, removed = []) => `${styleBlock}
|
|
970
966
|
|
|
971
967
|
${styleFormatContract}
|
|
972
968
|
|
|
@@ -1008,7 +1004,7 @@ review the changes:
|
|
|
1008
1004
|
A note sitting entirely on the line(s) beneath the pointer is
|
|
1009
1005
|
also valid. Either way, the note must never start with a bare \`./path\` token
|
|
1010
1006
|
— that parses as a second pointer, not a note
|
|
1011
|
-
No diff is given — read the changes yourself. The range runs
|
|
1007
|
+
${scenariosPrompt(removed)}No diff is given — read the changes yourself. The range runs
|
|
1012
1008
|
from \`${base}\` to the working tree (committed turns
|
|
1013
1009
|
plus anything pending); on a feedback round that's the previous
|
|
1014
1010
|
review's boundary, so it covers only what's new.
|
|
@@ -1055,20 +1051,6 @@ ${it.processCostByModel.map((m) => `- ${m.model}: ${m.cost}\n`).join("")}
|
|
|
1055
1051
|
Print the closing message and stop — this writes nothing itself.
|
|
1056
1052
|
`;
|
|
1057
1053
|
};
|
|
1058
|
-
const scenarioWordingMessage = (at, drifted) => `The e2e scenario wording was frozen when package 0 landed, and the last
|
|
1059
|
-
turn changed it. Removed lines are marked \`-\`, added ones \`+\`:
|
|
1060
|
-
|
|
1061
|
-
${drifted.map(({ path, lines }) => `${path}\n${lines.join("\n")}`).join("\n\n")}
|
|
1062
|
-
|
|
1063
|
-
To accept the change, land untouched.
|
|
1064
|
-
|
|
1065
|
-
To reject it, run
|
|
1066
|
-
|
|
1067
|
-
git checkout ${at} -- ${drifted.map(({ path }) => quote(path)).join(" ")}
|
|
1068
|
-
|
|
1069
|
-
and land. Restoring only some of the files is a partial accept: every file
|
|
1070
|
-
still differing from the frozen wording is accepted.
|
|
1071
|
-
`;
|
|
1072
1054
|
//#endregion
|
|
1073
1055
|
//#region src/workflows/shared/steps.ts
|
|
1074
1056
|
const FEEDBACK = ".gtd/FEEDBACK.md";
|
|
@@ -1079,7 +1061,7 @@ const REVIEW = ".gtd/REVIEW.md";
|
|
|
1079
1061
|
const QUALITY = ".gtd/QUALITY.md";
|
|
1080
1062
|
const planner = () => env.plannerModel ?? "";
|
|
1081
1063
|
const coder = () => env.coderModel ?? "";
|
|
1082
|
-
const fixCheck = (
|
|
1064
|
+
const fixCheck = () => agentWithSkills("fix", buildFixPrompt(), {
|
|
1083
1065
|
label: "Fixing the check",
|
|
1084
1066
|
file: FEEDBACK,
|
|
1085
1067
|
model: coder(),
|
|
@@ -1135,7 +1117,7 @@ const fixQuality = () => agentWithSkills("fix.quality.fixing", buildFixQualityPr
|
|
|
1135
1117
|
allowEmpty: true
|
|
1136
1118
|
});
|
|
1137
1119
|
/** Write `.gtd/REVIEW.md` over everything since `base`, carrying the answers of commit `carry` when given. */
|
|
1138
|
-
const reviewing = (base, carry) => agentWithSkills("review.reviewing", buildReviewReviewingPrompt(base, carry), {
|
|
1120
|
+
const reviewing = (base, carry) => agentWithSkills("review.reviewing", buildReviewReviewingPrompt(base, carry, removedAssertions(changesSince(base))), {
|
|
1139
1121
|
label: "Reviewing",
|
|
1140
1122
|
file: REVIEW,
|
|
1141
1123
|
mode: "review",
|
|
@@ -1164,25 +1146,11 @@ const reviewMissing = (round) => human("review.review-missing", {
|
|
|
1164
1146
|
message: buildReviewReviewMissingMessage(round),
|
|
1165
1147
|
label: "Nothing to review"
|
|
1166
1148
|
});
|
|
1167
|
-
//#endregion
|
|
1168
|
-
//#region src/workflows/shared/health.ts
|
|
1169
|
-
/** Written while `fastTestCommand` is unset; its presence rests the process at the check. */
|
|
1170
|
-
const SETUP = ".gtd/SETUP.md";
|
|
1171
|
-
const SETUP_TEXT = "The bundled workflow needs the `fastTestCommand` setting: the fast suite (everything but e2e).\nSet it as a workflow option in `gtd.config.ts` or as GTD_FASTTESTCOMMAND.";
|
|
1172
|
-
/** Shell lines for a check's preamble: unset (blank after trim) setting writes `SETUP` and skips the suite. */
|
|
1173
|
-
const fastSuiteGuard = () => (env.fastTestCommand ?? "").trim() === "" ? [
|
|
1174
|
-
`mkdir -p ${quote(dirname(SETUP))}`,
|
|
1175
|
-
`printf '%s\\n' ${quote(SETUP_TEXT)} > ${quote(SETUP)}`,
|
|
1176
|
-
`printf '%s\\n' ${quote(SETUP_TEXT)} >&2`,
|
|
1177
|
-
"exit 0"
|
|
1178
|
-
] : [`rm -f ${quote(SETUP)}`];
|
|
1179
|
-
const unconfigured = () => read(SETUP) !== void 0;
|
|
1180
1149
|
/** Run the suite as step `name`; resolves `true` when it passed. A failure is in `.gtd/FEEDBACK.md`. */
|
|
1181
|
-
const baseline = (name, label = "Checking the baseline") =>
|
|
1150
|
+
const baseline = (name, label = "Checking the baseline") => check(name, env.testCommand ?? "", {
|
|
1182
1151
|
report: FEEDBACK,
|
|
1183
|
-
label
|
|
1184
|
-
|
|
1185
|
-
}), unconfigured);
|
|
1152
|
+
label
|
|
1153
|
+
});
|
|
1186
1154
|
const escalationSteps = {
|
|
1187
1155
|
limit: 2,
|
|
1188
1156
|
describe: describeEscalation,
|
|
@@ -1220,22 +1188,18 @@ const sameFailure = async (previous, current) => {
|
|
|
1220
1188
|
});
|
|
1221
1189
|
return comparable && truncated.length === 0 && answered(answers.verdict, "identical", numeric(vars.judgeIdenticalMinP, Infinity));
|
|
1222
1190
|
};
|
|
1223
|
-
const runSuite = (options) => {
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
sweepOnGreen: [ESCALATION, ...options.sweepOnGreen ?? []],
|
|
1229
|
-
...fast ? { preamble: fastSuiteGuard() } : {}
|
|
1230
|
-
});
|
|
1231
|
-
};
|
|
1191
|
+
const runSuite = (options) => check("health.check", env.testCommand ?? "", {
|
|
1192
|
+
report: FEEDBACK,
|
|
1193
|
+
label: "Running checks",
|
|
1194
|
+
sweepOnGreen: [ESCALATION, ...options.sweepOnGreen ?? []]
|
|
1195
|
+
});
|
|
1232
1196
|
/**
|
|
1233
1197
|
* Loop until the suite is green: check, and on red run `fix` — escalating
|
|
1234
1198
|
* first once `FIX_CAP` fixes are spent, or a judge calls the failure
|
|
1235
1199
|
* identical to the previous round's.
|
|
1236
1200
|
*/
|
|
1237
1201
|
const healthy = (fix, options = {}) => healthy$1(fix, {
|
|
1238
|
-
check:
|
|
1202
|
+
check: () => runSuite(options),
|
|
1239
1203
|
report: FEEDBACK,
|
|
1240
1204
|
fixCap: 3,
|
|
1241
1205
|
fixesSoFar: options.fixesSoFar ?? 0,
|
|
@@ -1263,7 +1227,6 @@ const defaults = {
|
|
|
1263
1227
|
/** Environment settings. */
|
|
1264
1228
|
const envDefaults = {
|
|
1265
1229
|
testCommand: "npm test",
|
|
1266
|
-
fastTestCommand: "",
|
|
1267
1230
|
plannerModel: "smart",
|
|
1268
1231
|
coderModel: "base"
|
|
1269
1232
|
};
|
|
@@ -1278,7 +1241,6 @@ const ShippedOptions = Struct({
|
|
|
1278
1241
|
reviewSeverityMinP: setting,
|
|
1279
1242
|
qualityReviews: setting,
|
|
1280
1243
|
testCommand: setting,
|
|
1281
|
-
fastTestCommand: setting,
|
|
1282
1244
|
plannerModel: setting,
|
|
1283
1245
|
coderModel: setting
|
|
1284
1246
|
});
|
|
@@ -1298,21 +1260,6 @@ const shipped = (flow, doors, base) => defineWorkflow(standardSchemaV1(ShippedOp
|
|
|
1298
1260
|
base
|
|
1299
1261
|
}));
|
|
1300
1262
|
//#endregion
|
|
1301
|
-
//#region src/workflows/shared/scenarios.ts
|
|
1302
|
-
/** After a turn: stop at `scenario-wording` while wording drifted; accept moves the snapshot. */
|
|
1303
|
-
const holdWording = (frozen) => holdWording$1(frozen, (at, drifted) => human("scenario-wording", {
|
|
1304
|
-
message: scenarioWordingMessage(at, drifted),
|
|
1305
|
-
label: "Scenario wording changed",
|
|
1306
|
-
acceptClean: true,
|
|
1307
|
-
base: at
|
|
1308
|
-
}));
|
|
1309
|
-
/** `turn`, then `holdWording`; `within` names the scope the gate rests in. */
|
|
1310
|
-
const guarded = (frozen, turn, within) => async () => {
|
|
1311
|
-
await turn();
|
|
1312
|
-
if (within === void 0) await holdWording(frozen);
|
|
1313
|
-
else await scope(within, () => holdWording(frozen));
|
|
1314
|
-
};
|
|
1315
|
-
//#endregion
|
|
1316
1263
|
//#region src/workflows/shared/tail.ts
|
|
1317
1264
|
/** The reviewer's own `.gtd/REVIEW.md`, kept beside the human's notes so `fix` can tell them apart. */
|
|
1318
1265
|
const REVIEW_ORIG = ".gtd/REVIEW.orig.md";
|
|
@@ -1323,8 +1270,8 @@ const REVIEW_ORIG = ".gtd/REVIEW.orig.md";
|
|
|
1323
1270
|
*/
|
|
1324
1271
|
const qualityLap = () => qualityLap$1(lensesOf(vars.qualityReviews ?? ""), reviewQuality, QUALITY);
|
|
1325
1272
|
/** Resolves `true` once `.gtd/QUALITY.md` is resolved, `false` when the fix cap escalated instead. */
|
|
1326
|
-
const fixQualityFindings = (escalations
|
|
1327
|
-
fix:
|
|
1273
|
+
const fixQualityFindings = (escalations) => fixFindings({
|
|
1274
|
+
fix: fixQuality,
|
|
1328
1275
|
findings: QUALITY,
|
|
1329
1276
|
cap: 3,
|
|
1330
1277
|
escalate: () => escalation(escalations)
|
|
@@ -1340,27 +1287,24 @@ const refusePendingHandOff = () => {
|
|
|
1340
1287
|
};
|
|
1341
1288
|
/**
|
|
1342
1289
|
* The build tail: fix (when entered red), keep green, the quality lap, then
|
|
1343
|
-
* `finish` — the human review, as the calling workflow runs it.
|
|
1290
|
+
* `finish` — the human review, as the calling workflow runs it. `green`: the
|
|
1291
|
+
* caller just left the full suite green (the package loop), so no run opens it.
|
|
1344
1292
|
*/
|
|
1345
|
-
const tail = (fixFirst, finish,
|
|
1346
|
-
const frozen = built?.frozen;
|
|
1293
|
+
const tail = (fixFirst, finish, green = false) => scope("build", async () => {
|
|
1347
1294
|
const escalations = { rounds: 0 };
|
|
1348
|
-
const
|
|
1295
|
+
const fix = fixCheck;
|
|
1349
1296
|
const repair = async () => {
|
|
1350
|
-
await
|
|
1351
|
-
await healthy(
|
|
1297
|
+
await fix();
|
|
1298
|
+
await healthy(fix, {
|
|
1352
1299
|
fixesSoFar: 1,
|
|
1353
1300
|
escalations
|
|
1354
1301
|
});
|
|
1355
1302
|
};
|
|
1356
1303
|
if (fixFirst) await repair();
|
|
1357
|
-
else await healthy(
|
|
1358
|
-
|
|
1359
|
-
sweepOnGreen: [ARCHITECTURE]
|
|
1360
|
-
});
|
|
1361
|
-
if (await qualityLap() === "findings") if (await fixQualityFindings(escalations, frozen)) await healthy(guardedFix, { escalations });
|
|
1304
|
+
else if (!green) await healthy(fix, { escalations });
|
|
1305
|
+
if (await qualityLap() === "findings") if (await fixQualityFindings(escalations)) await healthy(fix, { escalations });
|
|
1362
1306
|
else await repair();
|
|
1363
|
-
return finish(escalations
|
|
1307
|
+
return finish(escalations);
|
|
1364
1308
|
});
|
|
1365
1309
|
/**
|
|
1366
1310
|
* The review and fix workflows' human review since `base`: a sign-off closes
|
|
@@ -1371,7 +1315,7 @@ const handOffReview = (base, escalations) => reviewToHandOff(base, {
|
|
|
1371
1315
|
file: REVIEW,
|
|
1372
1316
|
reviewing,
|
|
1373
1317
|
fixRisks,
|
|
1374
|
-
keepGreen: () => healthy(
|
|
1318
|
+
keepGreen: () => healthy(fixCheck, { escalations }),
|
|
1375
1319
|
awaitReview: (b) => awaitReview(b, handOffAwaitReviewMessage(b)),
|
|
1376
1320
|
reviewMissing,
|
|
1377
1321
|
close: closeReview,
|
|
@@ -1425,4 +1369,4 @@ const steering = {
|
|
|
1425
1369
|
[REVIEW]: "review"
|
|
1426
1370
|
};
|
|
1427
1371
|
//#endregion
|
|
1428
|
-
export {
|
|
1372
|
+
export { unwindFailedMessage as $, architectureDecomposePrompt as A, designSystem as B, fixRisks as C, agentWithSkills as D, reviewing as E, buildReviewFixNitsPrompt as F, packagesItemBuildingPrompt as G, finisherSystem as H, buildReviewTriageMessage as I, reviewEditsCapture as J, packagesItemFixSuitePrompt as K, builderSystem as L, buildReviewAnswerQuestionsPrompt as M, buildReviewAwaitAfterDeferMessage as N, architectSystem as O, buildReviewCollectingPrompt as P, startGateBlockedMessage as Q, deferNotesScript as R, fixCheck as S, reviewMissing as T, fixReviewNotesPrompt as U, designTriagePrompt as V, idleMessage as W, reviewNotesCapture as X, reviewGateBlockedMessage as Y, reviewerSystem as Z, FEEDBACK as _, REVIEW_ORIG as a, awaitReview as b, refusePendingHandOff as c, defaults as d, unwindFailure as et, envDefaults as f, ARCHITECTURE as g, healthy as h, skills as i, architectureGateAnswerMessage as j, architectureAuthorPrompt as k, tail as l, gate as m, summary as n, closeReview as o, baseline as p, reviewEditNotesCapture as q, access as r, handOffTail as s, steering as t, shipped as u, REQUIREMENTS as v, planner as w, coder as x, REVIEW as y, designGateAnswerMessage as z };
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -20957,6 +20957,26 @@ const restorability = (git, headHash, tipHash) => gen(function* () {
|
|
|
20957
20957
|
};
|
|
20958
20958
|
});
|
|
20959
20959
|
//#endregion
|
|
20960
|
+
//#region src/flags/index.ts
|
|
20961
|
+
const FLAGS$1 = [];
|
|
20962
|
+
const BOOL = {
|
|
20963
|
+
"": false,
|
|
20964
|
+
"0": false,
|
|
20965
|
+
false: false,
|
|
20966
|
+
"1": true,
|
|
20967
|
+
true: true
|
|
20968
|
+
};
|
|
20969
|
+
const resolveFlags = (env, registry = FLAGS$1) => {
|
|
20970
|
+
const out = {};
|
|
20971
|
+
for (const { name } of registry) {
|
|
20972
|
+
const variable = `GTD_FLAG_${name.toUpperCase()}`;
|
|
20973
|
+
const key = (env[variable] ?? "").toLowerCase();
|
|
20974
|
+
if (!Object.hasOwn(BOOL, key)) throw new Error(`${variable} must be 1, 0, true or false`);
|
|
20975
|
+
out[name] = BOOL[key];
|
|
20976
|
+
}
|
|
20977
|
+
return out;
|
|
20978
|
+
};
|
|
20979
|
+
//#endregion
|
|
20960
20980
|
//#region src/install/Version.ts
|
|
20961
20981
|
const _require = createRequire(import.meta.url);
|
|
20962
20982
|
const findPackageJson = (fromUrl) => {
|
|
@@ -37749,7 +37769,7 @@ const replay = async (input) => {
|
|
|
37749
37769
|
...c,
|
|
37750
37770
|
parsed: parseCommitMessage(c.message)
|
|
37751
37771
|
}));
|
|
37752
|
-
const treeAt = (hash) => hash === input.episode.base.hash ? input.episode.base.tree : commits.find((c) => c.hash === hash)?.tree;
|
|
37772
|
+
const treeAt = (hash) => hash === input.episode.base.hash ? input.episode.base.tree : commits.find((c) => c.hash === hash)?.tree ?? (hash === input.start ? input.startTree : void 0);
|
|
37753
37773
|
let cursor = 0;
|
|
37754
37774
|
let pendingUsed = false;
|
|
37755
37775
|
let position = input.episode.base;
|
|
@@ -38131,7 +38151,9 @@ const resolveVars = (workflowVars, entryVars, env) => {
|
|
|
38131
38151
|
...entryVars
|
|
38132
38152
|
};
|
|
38133
38153
|
for (const name of Object.keys(merged)) {
|
|
38134
|
-
const
|
|
38154
|
+
const key = PREFIX + name.toUpperCase();
|
|
38155
|
+
if (key.startsWith("GTD_FLAG_")) continue;
|
|
38156
|
+
const value = env[key];
|
|
38135
38157
|
if (value !== void 0) merged[name] = value;
|
|
38136
38158
|
}
|
|
38137
38159
|
return merged;
|
|
@@ -42051,7 +42073,10 @@ const runCli = (argv, io) => {
|
|
|
42051
42073
|
io.exit(2);
|
|
42052
42074
|
});
|
|
42053
42075
|
const out = bufferedArtifactOut(io);
|
|
42054
|
-
return
|
|
42076
|
+
return flatMap$4(Host, ({ env }) => try_({
|
|
42077
|
+
try: () => resolveFlags(env, io.flags),
|
|
42078
|
+
catch: (e) => new GtdError(e.message)
|
|
42079
|
+
})).pipe(flatMap$4(() => runCommand(plan.command, plan.json, out)), map$4(() => {
|
|
42055
42080
|
out.flush();
|
|
42056
42081
|
io.exit(0);
|
|
42057
42082
|
}), provide$1(io.layers(plan.verbose)), sandbox, catchAll((cause) => isInterruptedOnly(cause) ? failCause$2(cause) : report(io, plan.json.kind !== "off")(cause)));
|
package/dist/testkit.mjs
CHANGED
|
@@ -5,13 +5,13 @@ import { $ as makeSemaphore, $i as toJSON, $n as mapBoth, $r as none$1, $t as pr
|
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import * as Crypto from "node:crypto";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
-
import * as Path from "node:path";
|
|
9
|
-
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
10
8
|
import { PassThrough } from "node:stream";
|
|
11
9
|
import * as NFS from "node:fs";
|
|
12
10
|
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
13
11
|
import * as OS from "node:os";
|
|
14
12
|
import { homedir, tmpdir } from "node:os";
|
|
13
|
+
import * as Path from "node:path";
|
|
14
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
15
15
|
import * as ChildProcess from "node:child_process";
|
|
16
16
|
import { execFileSync, execSync, spawnSync } from "node:child_process";
|
|
17
17
|
import * as NodeUrl from "node:url";
|
|
@@ -6085,6 +6085,26 @@ const restorability = (git, headHash, tipHash) => gen(function* () {
|
|
|
6085
6085
|
};
|
|
6086
6086
|
});
|
|
6087
6087
|
//#endregion
|
|
6088
|
+
//#region src/flags/index.ts
|
|
6089
|
+
const FLAGS$1 = [];
|
|
6090
|
+
const BOOL = {
|
|
6091
|
+
"": false,
|
|
6092
|
+
"0": false,
|
|
6093
|
+
false: false,
|
|
6094
|
+
"1": true,
|
|
6095
|
+
true: true
|
|
6096
|
+
};
|
|
6097
|
+
const resolveFlags = (env, registry = FLAGS$1) => {
|
|
6098
|
+
const out = {};
|
|
6099
|
+
for (const { name } of registry) {
|
|
6100
|
+
const variable = `GTD_FLAG_${name.toUpperCase()}`;
|
|
6101
|
+
const key = (env[variable] ?? "").toLowerCase();
|
|
6102
|
+
if (!Object.hasOwn(BOOL, key)) throw new Error(`${variable} must be 1, 0, true or false`);
|
|
6103
|
+
out[name] = BOOL[key];
|
|
6104
|
+
}
|
|
6105
|
+
return out;
|
|
6106
|
+
};
|
|
6107
|
+
//#endregion
|
|
6088
6108
|
//#region src/install/Version.ts
|
|
6089
6109
|
const _require = createRequire(import.meta.url);
|
|
6090
6110
|
const findPackageJson = (fromUrl) => {
|
|
@@ -7027,7 +7047,7 @@ const replay = async (input) => {
|
|
|
7027
7047
|
...c,
|
|
7028
7048
|
parsed: parseCommitMessage(c.message)
|
|
7029
7049
|
}));
|
|
7030
|
-
const treeAt = (hash) => hash === input.episode.base.hash ? input.episode.base.tree : commits.find((c) => c.hash === hash)?.tree;
|
|
7050
|
+
const treeAt = (hash) => hash === input.episode.base.hash ? input.episode.base.tree : commits.find((c) => c.hash === hash)?.tree ?? (hash === input.start ? input.startTree : void 0);
|
|
7031
7051
|
let cursor = 0;
|
|
7032
7052
|
let pendingUsed = false;
|
|
7033
7053
|
let position = input.episode.base;
|
|
@@ -7408,7 +7428,9 @@ const resolveVars = (workflowVars, entryVars, env) => {
|
|
|
7408
7428
|
...entryVars
|
|
7409
7429
|
};
|
|
7410
7430
|
for (const name of Object.keys(merged)) {
|
|
7411
|
-
const
|
|
7431
|
+
const key = PREFIX + name.toUpperCase();
|
|
7432
|
+
if (key.startsWith("GTD_FLAG_")) continue;
|
|
7433
|
+
const value = env[key];
|
|
7412
7434
|
if (value !== void 0) merged[name] = value;
|
|
7413
7435
|
}
|
|
7414
7436
|
return merged;
|
|
@@ -11303,7 +11325,10 @@ const runCli = (argv, io) => {
|
|
|
11303
11325
|
io.exit(2);
|
|
11304
11326
|
});
|
|
11305
11327
|
const out = bufferedArtifactOut(io);
|
|
11306
|
-
return
|
|
11328
|
+
return flatMap$4(Host, ({ env }) => try_({
|
|
11329
|
+
try: () => resolveFlags(env, io.flags),
|
|
11330
|
+
catch: (e) => new GtdError(e.message)
|
|
11331
|
+
})).pipe(flatMap$4(() => runCommand$1(plan.command, plan.json, out)), map$4(() => {
|
|
11307
11332
|
out.flush();
|
|
11308
11333
|
io.exit(0);
|
|
11309
11334
|
}), provide$2(io.layers(plan.verbose)), sandbox, catchAll$1((cause) => isInterruptedOnly(cause) ? failCause$4(cause) : report(io, plan.json.kind !== "off")(cause)));
|