@pmelab/gtd 30.0.0 → 30.1.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 +17 -8
- package/claude/hooks/register.tsx +5 -0
- package/dist/chunks/{reserved-iG86IR3I.mjs → reserved-DDiiAZ8U.mjs} +17 -7
- package/dist/chunks/{tail-DA2UuJNP.mjs → tail-ILPlELuy.mjs} +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +6 -5
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +2 -2
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +19 -8
- package/package.json +1 -1
- package/src/workflows/feature/feature.test.ts +41 -0
- package/src/workflows/feature/feature.ts +11 -6
- package/src/workflows/feature/flag-mode.feature +157 -0
- package/src/workflows/feature/seam.ts +2 -0
- package/src/workflows/shared/options.test.ts +12 -0
- package/src/workflows/shared/options.ts +17 -2
- package/src/workflows/shared/text.test.ts +16 -0
- package/src/workflows/shared/text.ts +13 -6
- package/src/workflows/shared/vars.ts +1 -0
package/README.md
CHANGED
|
@@ -422,14 +422,15 @@ noted in steps 3 and 4 below.
|
|
|
422
422
|
|
|
423
423
|
3. **You wait.** The agent first implements the requirements the quickest way
|
|
424
424
|
that works — the naive pass — and a judge reads where it changed code (see
|
|
425
|
-
[the naive pass](#the-naive-pass) below
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
425
|
+
[the naive pass](#the-naive-pass) below; with [flag mode](#the-naive-pass)
|
|
426
|
+
off, neither runs). Then the change is built test-first, checked against the
|
|
427
|
+
full suite (`testCommand`) and fixed until it passes — so keep that suite
|
|
428
|
+
fast; gtd has no lighter one. Scenario wording is not frozen: you sign the
|
|
429
|
+
scenarios off once, in the review's `## Scenarios` chunk, where any assertion
|
|
430
|
+
an existing scenario lost is marked `Weakened:`. After the build the quality
|
|
431
|
+
lap runs. Three points along the process (two with flag mode off) are judged
|
|
432
|
+
rather than always asking you outright — each stops and hands you a verdict
|
|
433
|
+
to make (`gtd judge answer`, or land with a clean tree to accept the
|
|
433
434
|
conservative default, which never skips work;
|
|
434
435
|
`gtd judge run --provider fixed --answers <path>` — or the
|
|
435
436
|
`GTD_JUDGE_ANSWERS` env var, inline JSON — answers one from a file, piped
|
|
@@ -595,6 +596,14 @@ out is `gtd abandon`. A process from 27.5.0 or later whose judge ruled
|
|
|
595
596
|
that slice. Details in
|
|
596
597
|
[the naive-pass path](docs/configuration.md#the-naive-pass-path).
|
|
597
598
|
|
|
599
|
+
**Flag mode (`flagMode`, on by default).** On, the implementation is asked for a
|
|
600
|
+
new release flag (none at a `low` risk tier) and the naive pass and its judge
|
|
601
|
+
run as above. A project that is not publicly released turns it off explicitly —
|
|
602
|
+
`feature({ flagMode: false })` in `gtd.config.ts`, it is never detected: then no
|
|
603
|
+
flag is asked for, no seam judge or refactor runs, and the implementation is the
|
|
604
|
+
naive version, straight through the health loop and the quality lap. See
|
|
605
|
+
[`flagMode`](docs/configuration.md#the-bundled-workflows-settings).
|
|
606
|
+
|
|
598
607
|
**Risk tier (flag `GTD_FLAG_RISK_TIER=1`, off by default).** Triage proposes a
|
|
599
608
|
risk tier, `low` or `high`, and the production signals to watch after merge.
|
|
600
609
|
Edit the first word under `## Risk Tier` at the design gate to change it; a
|
|
@@ -993,6 +993,11 @@ export const register: Register = (on) => {
|
|
|
993
993
|
return next(e)
|
|
994
994
|
})
|
|
995
995
|
|
|
996
|
+
// A worker's tool call hands settings hooks the MAIN session's
|
|
997
|
+
// `transcript_path`, marked only by `agent_id`/`agent_type`; its own
|
|
998
|
+
// transcript (`agent_transcript_path`) is named only at SubagentStop, too
|
|
999
|
+
// late to measure a running worker's context.
|
|
1000
|
+
//
|
|
996
1001
|
// The loop lands a turn when it ends; work left running in the background
|
|
997
1002
|
// would still be writing the tree gtd is committing.
|
|
998
1003
|
on("tool.call", async ($, e, next) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { accessFor, agent, answered, changesSince, check, codeThreads, defineWorkflow, displayPath, env, head, human, judge, numeric, quote, read, refuse, scope, skillsFor, start, vars } from "../flows.mjs";
|
|
2
2
|
import { escalation as escalation$1, gate as gate$1, healthy as healthy$1, lensScopes, lensesOf, removedAssertions } from "../workflow-kit.mjs";
|
|
3
|
-
import { c as String$, g as standardSchemaV1, h as optionalWith, l as Struct } from "./Schema-DO5ttJaI.mjs";
|
|
3
|
+
import { c as String$, g as standardSchemaV1, h as optionalWith, l as Struct, n as Boolean$ } from "./Schema-DO5ttJaI.mjs";
|
|
4
4
|
//#region src/workflows/shared/registry.ts
|
|
5
5
|
const FILL = "";
|
|
6
6
|
const commentLength = (rest) => {
|
|
@@ -132,6 +132,7 @@ const defaults = {
|
|
|
132
132
|
reviewSeverityMinP: "0.7",
|
|
133
133
|
refactorFlagPattern: "\\bGTD_FLAG_\\w+",
|
|
134
134
|
seamAllow: [...testFileGlobs, ".gtd/**"].join(", "),
|
|
135
|
+
flagMode: "true",
|
|
135
136
|
flag_risk_tier: "0",
|
|
136
137
|
qualityReviews: "correctness, owasp-security, ponytail-review, test-audit, conventions, spec-challenge",
|
|
137
138
|
refactorQualityReviews: "correctness, ponytail-review, conventions"
|
|
@@ -1172,18 +1173,20 @@ re-implement. It changed these files outside the expected set:
|
|
|
1172
1173
|
${listed(rejection.strays)}
|
|
1173
1174
|
Start over and choose a different approach; do not rebuild the same diff.
|
|
1174
1175
|
`}`;
|
|
1175
|
-
const implementBuildingPrompt = (reference, tier, amend, afterRefactor) => `${stateFileRules}
|
|
1176
|
+
const implementBuildingPrompt = (reference, tier, amend, afterRefactor, flagMode = true) => `${stateFileRules}
|
|
1176
1177
|
- Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
|
|
1177
1178
|
${afterRefactor === true ? `- A reviewed refactor has just landed and may already carry part of the change,
|
|
1178
1179
|
though never a release flag. Build only what is still missing, held to every
|
|
1179
1180
|
rule below; if nothing is, change nothing and finish your turn
|
|
1180
1181
|
` : ""}${amend === true ? `- The change already exists in the tree from an earlier lap; amend it to meet
|
|
1181
1182
|
the re-derived requirements instead of rewriting it
|
|
1182
|
-
` : `- Implement them properly, fitting the codebase's seams and conventions
|
|
1183
|
+
` : flagMode ? `- Implement them properly, fitting the codebase's seams and conventions
|
|
1184
|
+
` : `- Implement them the quickest way that works — the naive version. Do not
|
|
1185
|
+
restructure, abstract or refactor around the change
|
|
1183
1186
|
`}${reference === void 0 || amend === true ? "" : `- Reference only: \`git show ${reference}\` shows an earlier naive attempt.
|
|
1184
1187
|
Read it for ideas; never apply, cherry-pick or check it out — write
|
|
1185
1188
|
the change fresh
|
|
1186
|
-
`}${tier === "low" ? "- Add no release flag — this change is low risk tier" : amend === true ? "- Keep the release flag the earlier lap added, if it added one; otherwise put\n the behaviour behind a new release flag" : `- Put the new behaviour behind a new release flag, following the
|
|
1189
|
+
`}${!flagMode ? "- Add no release flag — this project ships without release flags" : tier === "low" ? "- Add no release flag — this change is low risk tier" : amend === true ? "- Keep the release flag the earlier lap added, if it added one; otherwise put\n the behaviour behind a new release flag" : `- Put the new behaviour behind a new release flag, following the
|
|
1187
1190
|
repository's flag conventions`}
|
|
1188
1191
|
- Write the tests first, then the code that makes them pass
|
|
1189
1192
|
- Leave \`.gtd/REQUIREMENTS.md\` in place and everything uncommitted, then
|
|
@@ -1420,12 +1423,19 @@ const ShippedOptions = Struct({
|
|
|
1420
1423
|
refactorQualityReviews: setting,
|
|
1421
1424
|
testCommand: setting,
|
|
1422
1425
|
plannerModel: setting,
|
|
1423
|
-
coderModel: setting
|
|
1426
|
+
coderModel: setting,
|
|
1427
|
+
flagMode: optionalWith(Boolean$, { exact: true })
|
|
1424
1428
|
});
|
|
1425
1429
|
const over = (base, options) => ({
|
|
1426
1430
|
...base,
|
|
1427
|
-
...Object.fromEntries(Object.entries(options).filter(([name]) => Object.hasOwn(base, name)))
|
|
1431
|
+
...Object.fromEntries(Object.entries(options).filter(([name]) => Object.hasOwn(base, name)).map(([name, value]) => [name, String(value)]))
|
|
1428
1432
|
});
|
|
1433
|
+
/** The `flagMode` setting, pinned at process start. Anything but true/false fails rather than guess a side. */
|
|
1434
|
+
const flagModeOn = () => {
|
|
1435
|
+
const value = vars.flagMode ?? defaults.flagMode;
|
|
1436
|
+
if (value !== "true" && value !== "false") refuse(`gtd: flagMode must be "true" or "false", got ${JSON.stringify(value)}`);
|
|
1437
|
+
return value === "true";
|
|
1438
|
+
};
|
|
1429
1439
|
/** A shipped flow as a factory: its options override the settings it declares, each in its own kind. */
|
|
1430
1440
|
const shipped = (flow, doors, base) => defineWorkflow(standardSchemaV1(ShippedOptions, {
|
|
1431
1441
|
onExcessProperty: "error",
|
|
@@ -1509,4 +1519,4 @@ const steering = {
|
|
|
1509
1519
|
[REVIEW]: "review"
|
|
1510
1520
|
};
|
|
1511
1521
|
//#endregion
|
|
1512
|
-
export {
|
|
1522
|
+
export { reviewerSystem as $, buildReviewCollectingPrompt as A, handOffAwaitReviewMessage as B, planner as C, agentWithSkills as D, reviewing as E, designGateAnswerMessage as F, refactorBuildingPrompt as G, implementBuildingPrompt as H, designSystem as I, refactorRules as J, refactorDroppedRefusal as K, designTriagePrompt as L, buildReviewTriageMessage as M, builderSystem as N, buildReviewAnswerQuestionsPrompt as O, deferNotesScript as P, reviewNotesCapture as Q, finisherSystem as R, fixRisks as S, reviewQuality as T, implementFixPrompt as U, idleMessage as V, naivePassPrompt as W, reviewEditsCapture as X, reviewEditNotesCapture as Y, reviewGateBlockedMessage as Z, REVIEW as _, flagModeOn as a, defaults as at, fixCheck as b, baseline as c, flagMentions as ct, guarded as d, registryEntry as dt, seamByHandMessage as et, healthy as f, requireFlag as ft, REQUIREMENTS as g, REFACTOR as h, skills as i, unwindFailure as it, buildReviewFixNitsPrompt as j, buildReviewAwaitAfterDeferMessage as k, escalation as l, flagReferences as lt, QUALITY as m, summary as n, startGateBlockedMessage as nt, flagShipped as o, envDefaults as ot, FEEDBACK as p, refactorFixPrompt as q, access as r, unwindFailedMessage as rt, shipped as s, testFileGlobs as st, steering as t, seamSteerMessage as tt, gate as u, flipProblems as ut, awaitReview as v, reviewMissing as w, fixQuality as x, coder as y, fixReviewNotesPrompt as z };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { read, refuse, removeScript, run, scope, showScript, vars } from "../flows.mjs";
|
|
2
2
|
import { fixFindings, lensesOf, qualityLap as qualityLap$1, reviewToHandOff } from "../workflow-kit.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { B as handOffAwaitReviewMessage, E as reviewing, S as fixRisks, T as reviewQuality, _ as REVIEW, b as fixCheck, d as guarded, f as healthy, l as escalation, m as QUALITY, v as awaitReview, w as reviewMissing, x as fixQuality } from "./reserved-DDiiAZ8U.mjs";
|
|
4
4
|
//#region src/workflows/shared/tail.ts
|
|
5
5
|
/** The reviewer's own `.gtd/REVIEW.md`, kept beside the human's notes so `fix` can tell them apart. */
|
|
6
6
|
const REVIEW_ORIG = ".gtd/REVIEW.orig.md";
|
|
@@ -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 { at as defaults, dt as registryEntry, ft as requireFlag, i as skills, lt as flagReferences, n as summary, o as flagShipped, ot as envDefaults, r as access, t as steering, y as coder } from "../chunks/reserved-DDiiAZ8U.mjs";
|
|
3
|
+
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-ILPlELuy.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 () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { answered, changes, changesSince, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, numeric, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope, slice, start, vars } from "../flows.mjs";
|
|
2
2
|
import { lensesOf, review as review$1 } from "../workflow-kit.mjs";
|
|
3
|
-
import { $ as
|
|
4
|
-
import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail } from "../chunks/tail-
|
|
3
|
+
import { $ as reviewerSystem, A as buildReviewCollectingPrompt, C as planner, D as agentWithSkills, E as reviewing, F as designGateAnswerMessage, G as refactorBuildingPrompt, H as implementBuildingPrompt, I as designSystem, J as refactorRules, K as refactorDroppedRefusal, L as designTriagePrompt, M as buildReviewTriageMessage, N as builderSystem, O as buildReviewAnswerQuestionsPrompt, P as deferNotesScript, Q as reviewNotesCapture, S as fixRisks, U as implementFixPrompt, V as idleMessage, W as naivePassPrompt, X as reviewEditsCapture, Y as reviewEditNotesCapture, _ as REVIEW, a as flagModeOn, at as defaults, b as fixCheck, d as guarded, et as seamByHandMessage, f as healthy, g as REQUIREMENTS, h as REFACTOR, i as skills, it as unwindFailure, j as buildReviewFixNitsPrompt, k as buildReviewAwaitAfterDeferMessage, n as summary, nt as startGateBlockedMessage, ot as envDefaults, p as FEEDBACK, q as refactorFixPrompt, r as access, rt as unwindFailedMessage, s as shipped, st as testFileGlobs, t as steering, tt as seamSteerMessage, u as gate, v as awaitReview, w as reviewMissing, y as coder } from "../chunks/reserved-DDiiAZ8U.mjs";
|
|
4
|
+
import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail } from "../chunks/tail-ILPlELuy.mjs";
|
|
5
5
|
//#region src/workflows/shared/fences.ts
|
|
6
6
|
const markFenced = (text) => {
|
|
7
7
|
let fence;
|
|
@@ -625,7 +625,7 @@ function implement(first, tier, amend) {
|
|
|
625
625
|
amend
|
|
626
626
|
};
|
|
627
627
|
return scope("implement", async () => {
|
|
628
|
-
await agentWithSkills("building", implementBuildingPrompt(options.reference, options.tier, options.amend, options.afterRefactor), {
|
|
628
|
+
await agentWithSkills("building", implementBuildingPrompt(options.reference, options.tier, options.amend, options.afterRefactor, flagModeOn()), {
|
|
629
629
|
label: "Implementing",
|
|
630
630
|
model: coder(),
|
|
631
631
|
system: builderSystem(),
|
|
@@ -706,10 +706,10 @@ const unwind = () => {
|
|
|
706
706
|
const commit = head();
|
|
707
707
|
return run("unwind", revertScript(commit, FEEDBACK, unwindFailure(commit)), { label: "Unwinding your input" });
|
|
708
708
|
};
|
|
709
|
-
/** A judged naive pass, then implement and review until a review round signs off; design must have run since `firstBase`. */
|
|
709
|
+
/** A judged naive pass (none with `flagMode` off), then implement and review until a review round signs off; design must have run since `firstBase`. */
|
|
710
710
|
const buildFeature = async (firstBase) => {
|
|
711
711
|
const tier = riskTierOn() ? recordRisk() : void 0;
|
|
712
|
-
const seam = await seamBuild(firstBase);
|
|
712
|
+
const seam = flagModeOn() ? await seamBuild(firstBase) : { base: firstBase };
|
|
713
713
|
await slice("feature", async () => {
|
|
714
714
|
await implement({
|
|
715
715
|
reference: seam.reference,
|
|
@@ -754,6 +754,7 @@ const ordinaryStart = async () => {
|
|
|
754
754
|
file: ".gtd/TODO.md"
|
|
755
755
|
});
|
|
756
756
|
refusePendingHandOff();
|
|
757
|
+
flagModeOn();
|
|
757
758
|
await unwind();
|
|
758
759
|
if (changes(".gtd/FEEDBACK.md").some((c) => c.status !== "deleted")) await human("unwind-failed", {
|
|
759
760
|
message: unwindFailedMessage(),
|
package/dist/workflows/fix.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { read, refuse, removeScript, run, scope, start } from "../flows.mjs";
|
|
2
2
|
import { d as reviewNotes, f as reviewRisks } from "../chunks/steering-DVILanVf.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-
|
|
3
|
+
import { D as agentWithSkills, R as finisherSystem, _ as REVIEW, at as defaults, c as baseline, i as skills, n as summary, ot as envDefaults, r as access, s as shipped, t as steering, y as coder, z as fixReviewNotesPrompt } from "../chunks/reserved-DDiiAZ8U.mjs";
|
|
4
|
+
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-ILPlELuy.mjs";
|
|
5
5
|
//#region src/workflows/fix/fix.ts
|
|
6
6
|
/**
|
|
7
7
|
* Fix every note a human left in a handed-off `.gtd/REVIEW.md` and every
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { start } from "../flows.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { Z as reviewGateBlockedMessage, at as defaults, i as skills, n as summary, ot as envDefaults, r as access, s as shipped, t as steering, u as gate } from "../chunks/reserved-DDiiAZ8U.mjs";
|
|
3
|
+
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-ILPlELuy.mjs";
|
|
4
4
|
//#region src/workflows/review/review.ts
|
|
5
5
|
/**
|
|
6
6
|
* Pure review of everything since `reviewBase`: a green gate, the quality
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { at as defaults, ct as flagMentions, ft as requireFlag, i as skills, n as summary, o as flagShipped, ot as envDefaults, p as FEEDBACK, r as access, t as steering, u as gate, ut as flipProblems, y as coder } from "../chunks/reserved-DDiiAZ8U.mjs";
|
|
3
3
|
//#region src/workflows/rollout/rollout.ts
|
|
4
4
|
const flippingPrompt = (name, registry) => `Roll out the release flag "${name}": in ${registry}, set \`defaultOn: true\` on its entry. Change nothing else — no other entry, no other field, no other file, no comments.`;
|
|
5
5
|
const suiteBlockedMessage = () => "The suite is red after the rollout. Fix it by hand — a rollout may only touch the flag's registry entry — then land.";
|
package/dist/workflows.mjs
CHANGED
|
@@ -16916,6 +16916,7 @@ const defaults = {
|
|
|
16916
16916
|
reviewSeverityMinP: "0.7",
|
|
16917
16917
|
refactorFlagPattern: "\\bGTD_FLAG_\\w+",
|
|
16918
16918
|
seamAllow: [...testFileGlobs, ".gtd/**"].join(", "),
|
|
16919
|
+
flagMode: "true",
|
|
16919
16920
|
flag_risk_tier: "0",
|
|
16920
16921
|
qualityReviews: "correctness, owasp-security, ponytail-review, test-audit, conventions, spec-challenge",
|
|
16921
16922
|
refactorQualityReviews: "correctness, ponytail-review, conventions"
|
|
@@ -17956,18 +17957,20 @@ re-implement. It changed these files outside the expected set:
|
|
|
17956
17957
|
${listed(rejection.strays)}
|
|
17957
17958
|
Start over and choose a different approach; do not rebuild the same diff.
|
|
17958
17959
|
`}`;
|
|
17959
|
-
const implementBuildingPrompt = (reference, tier, amend, afterRefactor) => `${stateFileRules}
|
|
17960
|
+
const implementBuildingPrompt = (reference, tier, amend, afterRefactor, flagMode = true) => `${stateFileRules}
|
|
17960
17961
|
- Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
|
|
17961
17962
|
${afterRefactor === true ? `- A reviewed refactor has just landed and may already carry part of the change,
|
|
17962
17963
|
though never a release flag. Build only what is still missing, held to every
|
|
17963
17964
|
rule below; if nothing is, change nothing and finish your turn
|
|
17964
17965
|
` : ""}${amend === true ? `- The change already exists in the tree from an earlier lap; amend it to meet
|
|
17965
17966
|
the re-derived requirements instead of rewriting it
|
|
17966
|
-
` : `- Implement them properly, fitting the codebase's seams and conventions
|
|
17967
|
+
` : flagMode ? `- Implement them properly, fitting the codebase's seams and conventions
|
|
17968
|
+
` : `- Implement them the quickest way that works — the naive version. Do not
|
|
17969
|
+
restructure, abstract or refactor around the change
|
|
17967
17970
|
`}${reference === void 0 || amend === true ? "" : `- Reference only: \`git show ${reference}\` shows an earlier naive attempt.
|
|
17968
17971
|
Read it for ideas; never apply, cherry-pick or check it out — write
|
|
17969
17972
|
the change fresh
|
|
17970
|
-
`}${tier === "low" ? "- Add no release flag — this change is low risk tier" : amend === true ? "- Keep the release flag the earlier lap added, if it added one; otherwise put\n the behaviour behind a new release flag" : `- Put the new behaviour behind a new release flag, following the
|
|
17973
|
+
`}${!flagMode ? "- Add no release flag — this project ships without release flags" : tier === "low" ? "- Add no release flag — this change is low risk tier" : amend === true ? "- Keep the release flag the earlier lap added, if it added one; otherwise put\n the behaviour behind a new release flag" : `- Put the new behaviour behind a new release flag, following the
|
|
17971
17974
|
repository's flag conventions`}
|
|
17972
17975
|
- Write the tests first, then the code that makes them pass
|
|
17973
17976
|
- Leave \`.gtd/REQUIREMENTS.md\` in place and everything uncommitted, then
|
|
@@ -37522,12 +37525,19 @@ const ShippedOptions = Struct({
|
|
|
37522
37525
|
refactorQualityReviews: setting,
|
|
37523
37526
|
testCommand: setting,
|
|
37524
37527
|
plannerModel: setting,
|
|
37525
|
-
coderModel: setting
|
|
37528
|
+
coderModel: setting,
|
|
37529
|
+
flagMode: optionalWith(Boolean$, { exact: true })
|
|
37526
37530
|
});
|
|
37527
37531
|
const over = (base, options) => ({
|
|
37528
37532
|
...base,
|
|
37529
|
-
...Object.fromEntries(Object.entries(options).filter(([name]) => Object.hasOwn(base, name)))
|
|
37533
|
+
...Object.fromEntries(Object.entries(options).filter(([name]) => Object.hasOwn(base, name)).map(([name, value]) => [name, String(value)]))
|
|
37530
37534
|
});
|
|
37535
|
+
/** The `flagMode` setting, pinned at process start. Anything but true/false fails rather than guess a side. */
|
|
37536
|
+
const flagModeOn = () => {
|
|
37537
|
+
const value = vars.flagMode ?? defaults.flagMode;
|
|
37538
|
+
if (value !== "true" && value !== "false") refuse(`gtd: flagMode must be "true" or "false", got ${JSON.stringify(value)}`);
|
|
37539
|
+
return value === "true";
|
|
37540
|
+
};
|
|
37531
37541
|
/** A shipped flow as a factory: its options override the settings it declares, each in its own kind. */
|
|
37532
37542
|
const shipped = (flow, doors, base) => defineWorkflow(standardSchemaV1(ShippedOptions, {
|
|
37533
37543
|
onExcessProperty: "error",
|
|
@@ -38292,7 +38302,7 @@ function implement(first, tier, amend) {
|
|
|
38292
38302
|
amend
|
|
38293
38303
|
};
|
|
38294
38304
|
return scope("implement", async () => {
|
|
38295
|
-
await agentWithSkills("building", implementBuildingPrompt(options.reference, options.tier, options.amend, options.afterRefactor), {
|
|
38305
|
+
await agentWithSkills("building", implementBuildingPrompt(options.reference, options.tier, options.amend, options.afterRefactor, flagModeOn()), {
|
|
38296
38306
|
label: "Implementing",
|
|
38297
38307
|
model: coder(),
|
|
38298
38308
|
system: builderSystem(),
|
|
@@ -38373,10 +38383,10 @@ const unwind = () => {
|
|
|
38373
38383
|
const commit = head$1();
|
|
38374
38384
|
return run("unwind", revertScript(commit, FEEDBACK, unwindFailure(commit)), { label: "Unwinding your input" });
|
|
38375
38385
|
};
|
|
38376
|
-
/** A judged naive pass, then implement and review until a review round signs off; design must have run since `firstBase`. */
|
|
38386
|
+
/** A judged naive pass (none with `flagMode` off), then implement and review until a review round signs off; design must have run since `firstBase`. */
|
|
38377
38387
|
const buildFeature = async (firstBase) => {
|
|
38378
38388
|
const tier = riskTierOn() ? recordRisk() : void 0;
|
|
38379
|
-
const seam = await seamBuild(firstBase);
|
|
38389
|
+
const seam = flagModeOn() ? await seamBuild(firstBase) : { base: firstBase };
|
|
38380
38390
|
await slice("feature", async () => {
|
|
38381
38391
|
await implement({
|
|
38382
38392
|
reference: seam.reference,
|
|
@@ -38421,6 +38431,7 @@ const ordinaryStart = async () => {
|
|
|
38421
38431
|
file: ".gtd/TODO.md"
|
|
38422
38432
|
});
|
|
38423
38433
|
refusePendingHandOff();
|
|
38434
|
+
flagModeOn();
|
|
38424
38435
|
await unwind();
|
|
38425
38436
|
if (changes(".gtd/FEEDBACK.md").some((c) => c.status !== "deleted")) await human("unwind-failed", {
|
|
38426
38437
|
message: unwindFailedMessage(),
|
package/package.json
CHANGED
|
@@ -397,3 +397,44 @@ describe("an in-flight process from before the architecture path was removed", (
|
|
|
397
397
|
await expect(repo.next()).rejects.toThrow("the workflow changed under this process")
|
|
398
398
|
})
|
|
399
399
|
})
|
|
400
|
+
|
|
401
|
+
describe("flagMode", () => {
|
|
402
|
+
const config = (workflow: string): string =>
|
|
403
|
+
[
|
|
404
|
+
'import { feature } from "@pmelab/gtd/workflows/feature"',
|
|
405
|
+
'export { summary, steering, skills, access } from "@pmelab/gtd/workflows/feature"',
|
|
406
|
+
`export default ${workflow}`,
|
|
407
|
+
"",
|
|
408
|
+
].join("\n")
|
|
409
|
+
|
|
410
|
+
const designed = async (workflow: string, env: Record<string, string> = {}) => {
|
|
411
|
+
const repo = testRepo({ workflow, env: { GTD_QUALITYREVIEWS: "", ...env } })
|
|
412
|
+
repo.write("src/calc.ts", CALC)
|
|
413
|
+
repo.commit("feat: add calculator")
|
|
414
|
+
repo.write("TODO.md", "# Add subtract\n")
|
|
415
|
+
return repo.drive({
|
|
416
|
+
unwind: () => undefined,
|
|
417
|
+
"start-gate.check": () => undefined,
|
|
418
|
+
"design.triage": (_, r) =>
|
|
419
|
+
r.write(".gtd/REQUIREMENTS.md", "# Requirements\n\n## Concerns\n\n### S\n\n- `s`\n"),
|
|
420
|
+
"design.gate.answer": () => undefined,
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
it("set on explicitly, runs the naive pass like the default", async () => {
|
|
425
|
+
expect((await designed(config("feature({ flagMode: true })"))).state).toBe("seam.naive")
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it("off through GTD_FLAGMODE=false, skips the naive pass and the seam judge", async () => {
|
|
429
|
+
const rest = await designed(CONFIG_SCAFFOLD, { GTD_FLAGMODE: "false" })
|
|
430
|
+
expect(rest.state).toBe("implement.building")
|
|
431
|
+
expect(rest.content).toContain("this project ships without release flags")
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
it("refuses a value other than true or false before the process starts", async () => {
|
|
435
|
+
const repo = testRepo({ workflow: CONFIG_SCAFFOLD, env: { GTD_FLAGMODE: "off" } })
|
|
436
|
+
repo.write("TODO.md", "# Add subtract\n")
|
|
437
|
+
await expect(repo.drive({})).rejects.toThrow('flagMode must be "true" or "false", got "off"')
|
|
438
|
+
expect(repo.subjects()).toEqual(["chore: initial commit"])
|
|
439
|
+
})
|
|
440
|
+
})
|
|
@@ -9,20 +9,21 @@ import {
|
|
|
9
9
|
start,
|
|
10
10
|
} from "../../flows/index.js"
|
|
11
11
|
import { gate } from "../shared/health.js"
|
|
12
|
-
import { shipped } from "../shared/options.js"
|
|
12
|
+
import { flagModeOn, shipped } from "../shared/options.js"
|
|
13
13
|
import { FEEDBACK } from "../shared/steps.js"
|
|
14
14
|
import { refusePendingHandOff } from "../shared/tail.js"
|
|
15
15
|
import * as t from "../shared/text.js"
|
|
16
16
|
import { design } from "./planning.js"
|
|
17
17
|
import { buildTail, isCode, reUnwind, type ReviewOutcome } from "./review.js"
|
|
18
|
-
import { implement, seamBuild } from "./seam.js"
|
|
18
|
+
import { implement, seamBuild, type SeamOutcome } from "./seam.js"
|
|
19
19
|
import { recordRisk, riskTierOn } from "./tier.js"
|
|
20
20
|
|
|
21
21
|
// The ordinary start. Any change to the tree starts a process: `idle` →
|
|
22
22
|
// `unwind` reverts the sketch (its intent survives in history) → a
|
|
23
23
|
// green-baseline gate → design → a judged naive pass (refactoring first when
|
|
24
|
-
// the judge asks) → implement → the quality lap
|
|
25
|
-
// off (the episode ends back at `idle`) or sends a
|
|
24
|
+
// the judge asks; with `flagMode` off, neither) → implement → the quality lap
|
|
25
|
+
// → human review, which signs off (the episode ends back at `idle`) or sends a
|
|
26
|
+
// design → implement lap.
|
|
26
27
|
|
|
27
28
|
/** Revert the sketch that started the process out of the working tree; its intent survives in history. */
|
|
28
29
|
export const unwind = (): Promise<void> => {
|
|
@@ -36,10 +37,12 @@ export { reUnwind }
|
|
|
36
37
|
|
|
37
38
|
// seam.naive / seam.judge run once: a feedback lap is design → implement on every verdict, so a
|
|
38
39
|
// seam a re-plan turns up is built unjudged and unrefactored.
|
|
39
|
-
/** A judged naive pass, then implement and review until a review round signs off; design must have run since `firstBase`. */
|
|
40
|
+
/** A judged naive pass (none with `flagMode` off), then implement and review until a review round signs off; design must have run since `firstBase`. */
|
|
40
41
|
export const buildFeature = async (firstBase: string): Promise<void> => {
|
|
41
42
|
const tier = riskTierOn() ? recordRisk() : undefined
|
|
42
|
-
const seam =
|
|
43
|
+
const seam: Omit<SeamOutcome, "reference"> & { reference?: string } = flagModeOn()
|
|
44
|
+
? await seamBuild(firstBase)
|
|
45
|
+
: { base: firstBase }
|
|
43
46
|
await slice("feature", async () => {
|
|
44
47
|
await implement({ reference: seam.reference, tier, afterRefactor: seam.refactored })
|
|
45
48
|
// An empty feature slice: the refactor carried the feature, so review the whole change.
|
|
@@ -75,6 +78,8 @@ export const afterTail = (outcome: ReviewOutcome): Promise<void> => featureFeedb
|
|
|
75
78
|
export const ordinaryStart = async (): Promise<void> => {
|
|
76
79
|
await human("idle", { message: t.idleMessage(), label: "Idle", file: ".gtd/TODO.md" })
|
|
77
80
|
refusePendingHandOff()
|
|
81
|
+
// Read right after idle, so a bad value refuses the landing that would pin it.
|
|
82
|
+
flagModeOn()
|
|
78
83
|
await unwind()
|
|
79
84
|
if (changes(FEEDBACK).some((c) => c.status !== "deleted")) {
|
|
80
85
|
await human("unwind-failed", {
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
@inmem
|
|
2
|
+
Feature: flag mode off builds a feature with no release flag, seam judge or refactor
|
|
3
|
+
|
|
4
|
+
`flagMode` is a factory option of the shipped workflows, on by default. Off,
|
|
5
|
+
the project ships without feature flags: no seam judge and no refactor run,
|
|
6
|
+
and the naive implementation goes straight through the health loop and the
|
|
7
|
+
quality lap.
|
|
8
|
+
|
|
9
|
+
Background:
|
|
10
|
+
Given a gtd config with:
|
|
11
|
+
"""
|
|
12
|
+
import { feature } from "@pmelab/gtd/workflows/feature"
|
|
13
|
+
export { summary, steering, skills, access } from "@pmelab/gtd/workflows/feature"
|
|
14
|
+
export default feature({ flagMode: false })
|
|
15
|
+
"""
|
|
16
|
+
And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
|
|
17
|
+
And a file "src/calc.ts" with:
|
|
18
|
+
"""
|
|
19
|
+
export const add = (a: number, b: number) => a + b
|
|
20
|
+
"""
|
|
21
|
+
And the working tree is committed as "feat: add calculator"
|
|
22
|
+
And a file "TODO.md" with:
|
|
23
|
+
"""
|
|
24
|
+
# Add subtract
|
|
25
|
+
|
|
26
|
+
subtract(a, b) returns the difference.
|
|
27
|
+
"""
|
|
28
|
+
And step "unwind" changes nothing
|
|
29
|
+
And step "start-gate.check" changes nothing
|
|
30
|
+
And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
31
|
+
"""
|
|
32
|
+
# Requirements
|
|
33
|
+
|
|
34
|
+
## Concerns
|
|
35
|
+
|
|
36
|
+
### Subtract
|
|
37
|
+
|
|
38
|
+
- `subtract(a, b)` returns `a - b`.
|
|
39
|
+
"""
|
|
40
|
+
And step "design.gate.answer" changes nothing
|
|
41
|
+
|
|
42
|
+
Scenario: flag mode off skips the seam judge and the refactor, and asks for no flag
|
|
43
|
+
When the process is driven
|
|
44
|
+
Then the process rests at "implement.building"
|
|
45
|
+
And the resting step's content contains "the quickest way that works"
|
|
46
|
+
And the resting step's content contains "Add no release flag — this project ships without release flags"
|
|
47
|
+
And the resting step's content does not contain "new release flag"
|
|
48
|
+
Given step "implement.building" writes "src/calc.ts" with:
|
|
49
|
+
"""
|
|
50
|
+
export const add = (a: number, b: number) => a + b
|
|
51
|
+
export const subtract = (a: number, b: number) => a - b
|
|
52
|
+
"""
|
|
53
|
+
And step "implement.health.check" changes nothing
|
|
54
|
+
And step "implement.closing" runs its script
|
|
55
|
+
And step "build.quality.correctness.reviewing" changes nothing
|
|
56
|
+
When the process is driven
|
|
57
|
+
Then the process rests at "build.review.reviewing"
|
|
58
|
+
And "src/calc.ts" contains "subtract"
|
|
59
|
+
And the commit subjects end with:
|
|
60
|
+
"""
|
|
61
|
+
chore: initial commit
|
|
62
|
+
feat: add calculator
|
|
63
|
+
gtd(human): idle → unwind
|
|
64
|
+
gtd(check): unwind → start-gate.check
|
|
65
|
+
gtd(check): start-gate.check → design.triage
|
|
66
|
+
gtd(agent): design.triage → design.gate.answer
|
|
67
|
+
gtd(human): design.gate.answer → implement.building
|
|
68
|
+
gtd(agent): implement.building → implement.health.check
|
|
69
|
+
gtd(check): implement.health.check → implement.closing
|
|
70
|
+
gtd(check): implement.closing → build.quality.correctness.reviewing
|
|
71
|
+
gtd(agent): build.quality.correctness.reviewing → build.review.reviewing
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
Scenario: flag mode off asks for no flag on a feedback lap either
|
|
75
|
+
Given step "implement.building" writes "src/calc.ts" with:
|
|
76
|
+
"""
|
|
77
|
+
export const add = (a: number, b: number) => a + b
|
|
78
|
+
export const subtract = (a: number, b: number) => a - b
|
|
79
|
+
"""
|
|
80
|
+
And step "implement.health.check" changes nothing
|
|
81
|
+
And step "implement.closing" runs its script
|
|
82
|
+
And step "build.quality.correctness.reviewing" changes nothing
|
|
83
|
+
And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
|
|
84
|
+
"""
|
|
85
|
+
# Review: abc1234
|
|
86
|
+
|
|
87
|
+
<!-- base: 0000000 -->
|
|
88
|
+
|
|
89
|
+
## calc
|
|
90
|
+
|
|
91
|
+
- [ ] ./src/calc.ts#2-2
|
|
92
|
+
new subtract function
|
|
93
|
+
"""
|
|
94
|
+
When the process is driven
|
|
95
|
+
Then the process rests at "build.review.await-review"
|
|
96
|
+
Given a file ".gtd/REVIEW.md" with:
|
|
97
|
+
"""
|
|
98
|
+
# Review: abc1234
|
|
99
|
+
|
|
100
|
+
<!-- base: 0000000 -->
|
|
101
|
+
|
|
102
|
+
## calc
|
|
103
|
+
|
|
104
|
+
- [ ] ./src/calc.ts#2-2
|
|
105
|
+
new subtract function[^why]
|
|
106
|
+
|
|
107
|
+
[^why]:
|
|
108
|
+
- H: also export multiply
|
|
109
|
+
"""
|
|
110
|
+
And step "build.review.collecting" writes ".gtd/REQUIREMENTS.md" with:
|
|
111
|
+
"""
|
|
112
|
+
# Requirements
|
|
113
|
+
|
|
114
|
+
## Concerns
|
|
115
|
+
|
|
116
|
+
### Multiply
|
|
117
|
+
|
|
118
|
+
- Export `multiply()`.
|
|
119
|
+
"""
|
|
120
|
+
And step "build.review.collecting" writes ".gtd/REVIEW.md" with:
|
|
121
|
+
"""
|
|
122
|
+
# Review: abc1234
|
|
123
|
+
|
|
124
|
+
<!-- base: 0000000 -->
|
|
125
|
+
|
|
126
|
+
## calc
|
|
127
|
+
|
|
128
|
+
- [ ] ./src/calc.ts#2-2
|
|
129
|
+
new subtract function[^why]
|
|
130
|
+
|
|
131
|
+
[^why]:
|
|
132
|
+
- H: also export multiply
|
|
133
|
+
- A: noted in the requirements.
|
|
134
|
+
"""
|
|
135
|
+
When gtd lands
|
|
136
|
+
And the process is driven
|
|
137
|
+
Then the process rests at "build.review.await-review"
|
|
138
|
+
Given a file ".gtd/REVIEW.md" with:
|
|
139
|
+
"""
|
|
140
|
+
# Review: abc1234
|
|
141
|
+
|
|
142
|
+
<!-- base: 0000000 -->
|
|
143
|
+
|
|
144
|
+
## calc
|
|
145
|
+
|
|
146
|
+
- [ ] ./src/calc.ts#2-2
|
|
147
|
+
new subtract function
|
|
148
|
+
"""
|
|
149
|
+
And step "build.review.closing" runs its script
|
|
150
|
+
And step "re-unwind" runs its script
|
|
151
|
+
And step "implement.building" is no longer scripted
|
|
152
|
+
When gtd lands
|
|
153
|
+
And the process is driven
|
|
154
|
+
Then the process rests at "implement.building"
|
|
155
|
+
And the resting step's content contains "Add no release flag — this project ships without release flags"
|
|
156
|
+
And the resting step's content does not contain "release flag the earlier lap added"
|
|
157
|
+
And no commit body contains "Gtd-Kept:"
|
|
@@ -17,6 +17,7 @@ import { coder, FEEDBACK, REQUIREMENTS } from "../shared/steps.js"
|
|
|
17
17
|
import * as t from "../shared/text.js"
|
|
18
18
|
import { defaults } from "../shared/vars.js"
|
|
19
19
|
import { refactorStrays, reviewedRefactor } from "./refactor.js"
|
|
20
|
+
import { flagModeOn } from "../shared/options.js"
|
|
20
21
|
import type { RiskTier } from "./tier.js"
|
|
21
22
|
|
|
22
23
|
/** Naive-pass retries after a `reject`; the next `reject` rests at `seam.steer`. */
|
|
@@ -71,6 +72,7 @@ export function implement(
|
|
|
71
72
|
options.tier,
|
|
72
73
|
options.amend,
|
|
73
74
|
options.afterRefactor,
|
|
75
|
+
flagModeOn(),
|
|
74
76
|
),
|
|
75
77
|
{
|
|
76
78
|
label: "Implementing",
|
|
@@ -30,6 +30,18 @@ describe("the shipped workflow factories", () => {
|
|
|
30
30
|
})
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
+
it("default flagMode on and lay a boolean factory option over it as a string", () => {
|
|
34
|
+
expect(defaults.flagMode).toBe("true")
|
|
35
|
+
expect(configure(feature({ flagMode: false }))).toMatchObject({
|
|
36
|
+
ok: true,
|
|
37
|
+
build: { defaults: { flagMode: "false" } },
|
|
38
|
+
})
|
|
39
|
+
expect(configure(review({ flagMode: true }))).toMatchObject({
|
|
40
|
+
ok: true,
|
|
41
|
+
build: { defaults: { flagMode: "true" } },
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
33
45
|
it("declare flag_risk_tier off by default; it is no factory option", () => {
|
|
34
46
|
expect(Object.keys(ShippedOptions.fields)).not.toContain("flag_risk_tier")
|
|
35
47
|
})
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
type WorkflowBase,
|
|
6
6
|
type WorkflowDoors,
|
|
7
7
|
type WorkflowFactory,
|
|
8
|
+
refuse,
|
|
9
|
+
vars,
|
|
8
10
|
} from "../../flows/index.js"
|
|
9
11
|
import { defaultFlagRegistry, defaults, envDefaults } from "./vars.js"
|
|
10
12
|
|
|
@@ -23,18 +25,31 @@ export const ShippedOptions = Schema.Struct({
|
|
|
23
25
|
testCommand: setting,
|
|
24
26
|
plannerModel: setting,
|
|
25
27
|
coderModel: setting,
|
|
28
|
+
flagMode: Schema.optionalWith(Schema.Boolean, { exact: true }),
|
|
26
29
|
})
|
|
27
30
|
|
|
28
31
|
export type ShippedOptions = Schema.Schema.Encoded<typeof ShippedOptions>
|
|
29
32
|
|
|
30
33
|
const over = (
|
|
31
34
|
base: Readonly<Record<string, string>>,
|
|
32
|
-
options: Readonly<Record<string, string>>,
|
|
35
|
+
options: Readonly<Record<string, string | boolean>>,
|
|
33
36
|
): Record<string, string> => ({
|
|
34
37
|
...base,
|
|
35
|
-
...Object.fromEntries(
|
|
38
|
+
...Object.fromEntries(
|
|
39
|
+
Object.entries(options)
|
|
40
|
+
.filter(([name]) => Object.hasOwn(base, name))
|
|
41
|
+
.map(([name, value]) => [name, String(value)]),
|
|
42
|
+
),
|
|
36
43
|
})
|
|
37
44
|
|
|
45
|
+
/** The `flagMode` setting, pinned at process start. Anything but true/false fails rather than guess a side. */
|
|
46
|
+
export const flagModeOn = (): boolean => {
|
|
47
|
+
const value = vars.flagMode ?? defaults.flagMode!
|
|
48
|
+
if (value !== "true" && value !== "false")
|
|
49
|
+
refuse(`gtd: flagMode must be "true" or "false", got ${JSON.stringify(value)}`)
|
|
50
|
+
return value === "true"
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
/** A shipped flow as a factory: its options override the settings it declares, each in its own kind. */
|
|
39
54
|
export const shipped = (
|
|
40
55
|
flow: Flow,
|
|
@@ -990,6 +990,22 @@ describe("the seam phase's texts", () => {
|
|
|
990
990
|
expect(prompt).toContain("Implement them properly")
|
|
991
991
|
})
|
|
992
992
|
|
|
993
|
+
it("flag mode off asks for the naive version and no release flag, whatever the tier or lap", () => {
|
|
994
|
+
for (const [tier, amend] of [
|
|
995
|
+
["high", false],
|
|
996
|
+
["low", false],
|
|
997
|
+
["high", true],
|
|
998
|
+
] as const) {
|
|
999
|
+
const prompt = renderText(() => implementBuildingPrompt(undefined, tier, amend, false, false))
|
|
1000
|
+
expect(prompt).toContain("Add no release flag — this project ships without release flags")
|
|
1001
|
+
expect(prompt).not.toContain("new release flag")
|
|
1002
|
+
expect(prompt).not.toContain("low risk tier")
|
|
1003
|
+
}
|
|
1004
|
+
expect(
|
|
1005
|
+
renderText(() => implementBuildingPrompt(undefined, "high", false, false, false)),
|
|
1006
|
+
).toContain("the quickest way that works")
|
|
1007
|
+
})
|
|
1008
|
+
|
|
993
1009
|
it("keeps a fix turn to the feedback file's rules", () => {
|
|
994
1010
|
expect(renderText(() => implementFixPrompt())).toContain(".gtd/FEEDBACK.md")
|
|
995
1011
|
})
|
|
@@ -1091,6 +1091,7 @@ export const implementBuildingPrompt = (
|
|
|
1091
1091
|
tier?: "low" | "high",
|
|
1092
1092
|
amend?: boolean,
|
|
1093
1093
|
afterRefactor?: boolean,
|
|
1094
|
+
flagMode = true,
|
|
1094
1095
|
): string =>
|
|
1095
1096
|
`${stateFileRules}
|
|
1096
1097
|
- Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
|
|
@@ -1106,7 +1107,11 @@ ${
|
|
|
1106
1107
|
? `- The change already exists in the tree from an earlier lap; amend it to meet
|
|
1107
1108
|
the re-derived requirements instead of rewriting it
|
|
1108
1109
|
`
|
|
1109
|
-
:
|
|
1110
|
+
: flagMode
|
|
1111
|
+
? `- Implement them properly, fitting the codebase's seams and conventions
|
|
1112
|
+
`
|
|
1113
|
+
: `- Implement them the quickest way that works — the naive version. Do not
|
|
1114
|
+
restructure, abstract or refactor around the change
|
|
1110
1115
|
`
|
|
1111
1116
|
}${
|
|
1112
1117
|
reference === undefined || amend === true
|
|
@@ -1116,11 +1121,13 @@ ${
|
|
|
1116
1121
|
the change fresh
|
|
1117
1122
|
`
|
|
1118
1123
|
}${
|
|
1119
|
-
|
|
1120
|
-
? "- Add no release flag — this
|
|
1121
|
-
:
|
|
1122
|
-
? "-
|
|
1123
|
-
:
|
|
1124
|
+
!flagMode
|
|
1125
|
+
? "- Add no release flag — this project ships without release flags"
|
|
1126
|
+
: tier === "low"
|
|
1127
|
+
? "- Add no release flag — this change is low risk tier"
|
|
1128
|
+
: amend === true
|
|
1129
|
+
? "- Keep the release flag the earlier lap added, if it added one; otherwise put\n the behaviour behind a new release flag"
|
|
1130
|
+
: `- Put the new behaviour behind a new release flag, following the
|
|
1124
1131
|
repository's flag conventions`
|
|
1125
1132
|
}
|
|
1126
1133
|
- Write the tests first, then the code that makes them pass
|
|
@@ -25,6 +25,7 @@ export const defaults: Readonly<Record<string, string>> = {
|
|
|
25
25
|
reviewSeverityMinP: "0.7",
|
|
26
26
|
refactorFlagPattern: "\\bGTD_FLAG_\\w+",
|
|
27
27
|
seamAllow: [...testFileGlobs, ".gtd/**"].join(", "),
|
|
28
|
+
flagMode: "true",
|
|
28
29
|
// GTD_FLAG_RISK_TIER, recorded at process start; only "1" is on.
|
|
29
30
|
flag_risk_tier: "0",
|
|
30
31
|
// Decides the turn count: one `build.quality.<lens>` scope per entry, each
|