@pmelab/gtd 27.3.0 → 27.5.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 +22 -1
- package/dist/chunks/{Schema-CyImoHTj.mjs → Schema-DO5ttJaI.mjs} +1 -1
- package/dist/chunks/{reserved-BPdVZkgv.mjs → reserved-DM5W_LAt.mjs} +64 -3
- package/dist/chunks/{tail-iLAMoigl.mjs → tail-6gvLXXcg.mjs} +1 -1
- package/dist/chunks/{wire-BY1S-obW.mjs → wire-C12dahrH.mjs} +6 -4
- package/dist/flows.mjs +27 -2
- package/dist/gtd.bundle.mjs +218 -32
- package/dist/testkit.mjs +358 -37
- package/dist/wire.mjs +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +140 -64
- 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 +346 -107
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +49 -1
- package/src/cli/Cli.ts +25 -8
- package/src/cli/History.ts +5 -0
- package/src/cli/Program.ts +1 -0
- package/src/edge/Edge.test.ts +205 -1
- package/src/edge/Edge.ts +105 -12
- package/src/emit/GitScript.test.ts +39 -3
- package/src/emit/GitScript.ts +25 -1
- package/src/emit/index.ts +2 -0
- package/src/flags/index.test.ts +14 -1
- package/src/flags/index.ts +14 -3
- package/src/flows/helpers.ts +34 -2
- package/src/flows/runtime.ts +7 -0
- package/src/install/Install.ts +2 -1
- package/src/platform/Git.test.ts +25 -1
- package/src/platform/Git.ts +30 -0
- package/src/platform/index.ts +1 -0
- package/src/replay/Format.test.ts +16 -1
- package/src/replay/Replay.test.ts +91 -0
- package/src/replay/Replay.ts +22 -2
- package/src/replay/Trailers.test.ts +110 -1
- package/src/replay/Trailers.ts +37 -1
- package/src/replay/index.ts +2 -0
- package/src/replay/judgeDiffShape.test.ts +43 -0
- package/src/replay/seamGate.test.ts +62 -0
- package/src/step/LandStep.ts +1 -0
- package/src/step/planStart.ts +3 -2
- package/src/step/planStep.test.ts +32 -0
- package/src/step/planStep.ts +3 -1
- package/src/testkit/EmittedScriptRecognizer.test.ts +27 -0
- package/src/testkit/EmittedScriptRecognizer.ts +12 -0
- package/src/testkit/FakeGitOperations.ts +5 -0
- package/src/testkit/GitTiers.ts +158 -0
- package/src/testkit/InMemRepo.ts +38 -3
- package/src/testkit/TestRepo.ts +3 -0
- package/src/testkit/steps.ts +18 -0
- package/src/wire/BeatDocument.test.ts +25 -0
- package/src/wire/BeatDocument.ts +4 -2
- package/src/wire/BeatPlain.test.ts +1 -0
- package/src/wire/BeatStatus.test.ts +2 -0
- package/src/wire/BeatStatus.ts +13 -2
- package/src/wire/WireSchema.test.ts +10 -0
- package/src/wire/types.ts +8 -0
- package/src/workflow/Workflow.ts +1 -0
- package/src/workflow/index.ts +8 -1
- package/src/workflow/vars.test.ts +23 -1
- package/src/workflow/vars.ts +24 -0
- package/src/workflows/feature/feature.ts +9 -2
- package/src/workflows/feature/index.ts +1 -0
- package/src/workflows/feature/seam-pin.feature +52 -0
- package/src/workflows/feature/seam.feature +387 -0
- package/src/workflows/feature/seam.test.ts +88 -0
- package/src/workflows/feature/seam.ts +110 -0
- package/src/workflows/index.ts +1 -0
- package/src/workflows/shared/options.test.ts +12 -1
- package/src/workflows/shared/options.ts +1 -0
- package/src/workflows/shared/skills.test.ts +20 -2
- package/src/workflows/shared/skills.ts +6 -0
- package/src/workflows/shared/text.test.ts +59 -0
- package/src/workflows/shared/text.ts +86 -0
- package/src/workflows/shared/vars.ts +4 -0
- package/src/workflows/shared/workflows.steps.ts +39 -1
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type StatusChange,
|
|
11
11
|
DEMAND_BRIEFING,
|
|
12
12
|
renderBeatPlain,
|
|
13
|
+
type ApprovalEntry,
|
|
13
14
|
type ModelCost,
|
|
14
15
|
type RenderedDemandSource,
|
|
15
16
|
WIRE_SCHEMA,
|
|
@@ -34,6 +35,7 @@ interface DocumentInput {
|
|
|
34
35
|
readonly next?: NextMatch | null
|
|
35
36
|
readonly cost?: number
|
|
36
37
|
readonly costByModel?: readonly ModelCost[]
|
|
38
|
+
readonly approvals?: readonly ApprovalEntry[]
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
/** `demandOf`'s own input, isolated so `documentFor` stays a flat two-call body — `session`/`validate` are only spread in when the caller actually passed one, since `demandOf`'s `prompt` variant forbids an explicit `undefined`. */
|
|
@@ -61,6 +63,7 @@ const statusInput = (
|
|
|
61
63
|
next: input.next ?? null,
|
|
62
64
|
cost: input.cost ?? 0,
|
|
63
65
|
costByModel: input.costByModel ?? [],
|
|
66
|
+
approvals: input.approvals ?? [],
|
|
64
67
|
})
|
|
65
68
|
|
|
66
69
|
/** Builds one full `BeatDocument` in one call, mirroring the pre-split `beatFields` call shape so every golden below reads the same as before this package. */
|
|
@@ -87,6 +90,7 @@ describe("beatDocument / renderBeatJson", () => {
|
|
|
87
90
|
workflow: "feature",
|
|
88
91
|
changes: [],
|
|
89
92
|
next: null,
|
|
93
|
+
approvals: [],
|
|
90
94
|
})
|
|
91
95
|
})
|
|
92
96
|
|
|
@@ -267,9 +271,29 @@ describe("beatDocument / renderBeatJson", () => {
|
|
|
267
271
|
"judge",
|
|
268
272
|
"skills",
|
|
269
273
|
"access",
|
|
274
|
+
"approvals",
|
|
270
275
|
])
|
|
271
276
|
})
|
|
272
277
|
|
|
278
|
+
it("emits approvals always, [] when none, at every kind", () => {
|
|
279
|
+
for (const kind of BEAT_KINDS) {
|
|
280
|
+
const parsed = JSON.parse(renderJsonLine({ kind })) as Record<string, unknown>
|
|
281
|
+
expect(parsed.approvals).toEqual([])
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it("carries approvals verbatim, in the order given", () => {
|
|
286
|
+
const approvals = [
|
|
287
|
+
{ step: "gate#1", name: "Ada", email: "ada@x.io", commit: "a".repeat(40) },
|
|
288
|
+
{ step: "gate#2", name: "Bo", email: "bo@x.io", commit: "b".repeat(40) },
|
|
289
|
+
]
|
|
290
|
+
const parsed = JSON.parse(renderJsonLine({ kind: "message", approvals })) as Record<
|
|
291
|
+
string,
|
|
292
|
+
unknown
|
|
293
|
+
>
|
|
294
|
+
expect(parsed.approvals).toEqual(approvals)
|
|
295
|
+
})
|
|
296
|
+
|
|
273
297
|
it("emits access at kind prompt, null per side when unrestricted", () => {
|
|
274
298
|
const bare = JSON.parse(renderJsonLine({ kind: "prompt" })) as Record<string, unknown>
|
|
275
299
|
expect(bare.access).toEqual({ read: null, write: null })
|
|
@@ -377,6 +401,7 @@ describe("golden bytes", () => {
|
|
|
377
401
|
workflow: "feature",
|
|
378
402
|
changes: [],
|
|
379
403
|
next: null,
|
|
404
|
+
approvals: [],
|
|
380
405
|
}) + "\n",
|
|
381
406
|
)
|
|
382
407
|
})
|
package/src/wire/BeatDocument.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BeatKind, Demand, DemandSession } from "./Demand.js"
|
|
2
2
|
import type { BeatStatus, NextMatch, StatusChange } from "./BeatStatus.js"
|
|
3
|
-
import type { Actor, ModelCost, StateMode, StateName, StepAccess } from "./types.js"
|
|
3
|
+
import type { Actor, ApprovalEntry, ModelCost, StateMode, StateName, StepAccess } from "./types.js"
|
|
4
4
|
import { WIRE_SCHEMA } from "./constants.js"
|
|
5
5
|
|
|
6
6
|
/** `gtd next --json`'s `next` key — `null` when landing would commit nothing. */
|
|
@@ -72,10 +72,11 @@ export interface BeatDocument {
|
|
|
72
72
|
readonly judge: string | undefined
|
|
73
73
|
readonly skills: readonly string[] | undefined
|
|
74
74
|
readonly access: StepAccess | undefined
|
|
75
|
+
readonly approvals: readonly ApprovalEntry[]
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
/**
|
|
78
|
-
* Flatten a `Demand` plus its `BeatStatus` into the single
|
|
79
|
+
* Flatten a `Demand` plus its `BeatStatus` into the single 25-key document
|
|
79
80
|
* `gtd next --json` emits — the ONLY place the two are joined. `cost`/
|
|
80
81
|
* `costByModel` are omitted together, exactly when no cost was recorded
|
|
81
82
|
* (`cost <= 0`).
|
|
@@ -108,6 +109,7 @@ export const beatDocument = (demand: Demand, status: BeatStatus): BeatDocument =
|
|
|
108
109
|
judge: status.judge,
|
|
109
110
|
skills,
|
|
110
111
|
access,
|
|
112
|
+
approvals: status.approvals,
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
|
|
@@ -19,6 +19,7 @@ const baseInput = (overrides: Partial<RenderedDemandSource> = {}) => ({
|
|
|
19
19
|
next: null,
|
|
20
20
|
cost: 0,
|
|
21
21
|
costByModel: [],
|
|
22
|
+
approvals: [],
|
|
22
23
|
})
|
|
23
24
|
|
|
24
25
|
describe("statusOf", () => {
|
|
@@ -47,6 +48,7 @@ describe("statusOf", () => {
|
|
|
47
48
|
next: { target: "idle" },
|
|
48
49
|
cost: 12,
|
|
49
50
|
costByModel: [{ model: "opus", cost: 12 }],
|
|
51
|
+
approvals: [],
|
|
50
52
|
})
|
|
51
53
|
expect(status.workflow).toBe("feature")
|
|
52
54
|
expect(status.idle).toBe(true)
|
package/src/wire/BeatStatus.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ApprovalEntry,
|
|
3
|
+
Actor,
|
|
4
|
+
ModelCost,
|
|
5
|
+
RenderedDemandSource,
|
|
6
|
+
StateMode,
|
|
7
|
+
StateName,
|
|
8
|
+
} from "./types.js"
|
|
2
9
|
|
|
3
10
|
/** One pending change — `gtd next --json`'s `changes` entries. */
|
|
4
11
|
export interface StatusChange {
|
|
@@ -39,6 +46,7 @@ export interface BeatStatus {
|
|
|
39
46
|
readonly cost: number
|
|
40
47
|
readonly costByModel: readonly ModelCost[]
|
|
41
48
|
readonly judge: string | undefined
|
|
49
|
+
readonly approvals: readonly ApprovalEntry[]
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
/**
|
|
@@ -57,8 +65,10 @@ export const statusOf = (input: {
|
|
|
57
65
|
readonly next: NextMatch | null
|
|
58
66
|
readonly cost: number
|
|
59
67
|
readonly costByModel: readonly ModelCost[]
|
|
68
|
+
readonly approvals: readonly ApprovalEntry[]
|
|
60
69
|
}): BeatStatus => {
|
|
61
|
-
const { rendered, idle, initial, workflow, log, changes, next, cost, costByModel } =
|
|
70
|
+
const { rendered, idle, initial, workflow, log, changes, next, cost, costByModel, approvals } =
|
|
71
|
+
input
|
|
62
72
|
return {
|
|
63
73
|
idle,
|
|
64
74
|
initial,
|
|
@@ -77,5 +87,6 @@ export const statusOf = (input: {
|
|
|
77
87
|
cost,
|
|
78
88
|
costByModel,
|
|
79
89
|
judge: rendered.judge,
|
|
90
|
+
approvals,
|
|
80
91
|
}
|
|
81
92
|
}
|
|
@@ -86,6 +86,7 @@ const beat = (kind: BeatKind): string => {
|
|
|
86
86
|
next: { target: "idle" },
|
|
87
87
|
cost: 12,
|
|
88
88
|
costByModel: [{ model: "opus", cost: 12 }],
|
|
89
|
+
approvals: [{ step: "gate#1", name: "Ada", email: "ada@x.io", commit: "a".repeat(40) }],
|
|
89
90
|
})
|
|
90
91
|
return renderBeatJson(beatDocument(demand, status))
|
|
91
92
|
}
|
|
@@ -129,6 +130,15 @@ describe(`wire schema ${WIRE_SCHEMA}`, () => {
|
|
|
129
130
|
})
|
|
130
131
|
}
|
|
131
132
|
|
|
133
|
+
it("approvals is an additive field: present on every beat, WIRE_SCHEMA unchanged", () => {
|
|
134
|
+
expect(WIRE_SCHEMA).toBe(1)
|
|
135
|
+
for (const kind of BEAT_KINDS) {
|
|
136
|
+
expect(JSON.parse(documents[`beat-${kind}.json`]!)).toHaveProperty("approvals", [
|
|
137
|
+
{ step: "gate#1", name: "Ada", email: "ada@x.io", commit: "a".repeat(40) },
|
|
138
|
+
])
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
132
142
|
describe("the shape check", () => {
|
|
133
143
|
const land = JSON.parse(documents["land.json"]!) as Record<string, unknown>
|
|
134
144
|
const { subject, ...rest } = land
|
package/src/wire/types.ts
CHANGED
|
@@ -30,6 +30,14 @@ export interface StepAccess {
|
|
|
30
30
|
readonly write: readonly string[] | null
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** One approval commit of the process: `step` is the formatted `<name>#<n>`, `commit` the approving commit's hash. */
|
|
34
|
+
export interface ApprovalEntry {
|
|
35
|
+
readonly step: string
|
|
36
|
+
readonly name: string
|
|
37
|
+
readonly email: string
|
|
38
|
+
readonly commit: string
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
export interface RenderedDemandSource {
|
|
34
42
|
readonly state: StateName
|
|
35
43
|
readonly actor: Actor
|
package/src/workflow/Workflow.ts
CHANGED
package/src/workflow/index.ts
CHANGED
|
@@ -13,7 +13,14 @@ export {
|
|
|
13
13
|
walkUp,
|
|
14
14
|
} from "./discovery.js"
|
|
15
15
|
export { formatDiagnostic } from "./Diagnostic.js"
|
|
16
|
-
export {
|
|
16
|
+
export {
|
|
17
|
+
resolveVars,
|
|
18
|
+
resolveStartVars,
|
|
19
|
+
StartFlags,
|
|
20
|
+
multilineSetting,
|
|
21
|
+
isSettingName,
|
|
22
|
+
SETTING_NAME_RULE,
|
|
23
|
+
} from "./vars.js"
|
|
17
24
|
export { resolveScopeAccess, resolveScopeSkills } from "./skills.js"
|
|
18
25
|
export { seededValidateCommand, isSeededValidateCommand } from "./SteeringFormats.js"
|
|
19
26
|
export {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
-
import { resolveVars } from "./vars.js"
|
|
2
|
+
import { resolveStartVars, resolveVars } from "./vars.js"
|
|
3
3
|
|
|
4
4
|
describe("resolveVars — GTD_FLAG_* belongs to feature flags", () => {
|
|
5
5
|
it.each(["flag_x", "FLAG_X", "Flag_x"])("%s keeps its default with GTD_FLAG_X set", (name) => {
|
|
@@ -14,3 +14,25 @@ describe("resolveVars — GTD_FLAG_* belongs to feature flags", () => {
|
|
|
14
14
|
expect(resolveVars({ [name]: "off" }, {}, { [key]: "on" })).toEqual({ [name]: "on" })
|
|
15
15
|
})
|
|
16
16
|
})
|
|
17
|
+
|
|
18
|
+
describe("resolveStartVars", () => {
|
|
19
|
+
it("a declared flag_x takes the StartFlags value", () => {
|
|
20
|
+
expect(resolveStartVars({ flag_x: "0" }, { flag_x: "1" }, {}, {})).toEqual({ flag_x: "1" })
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it("never introduces an undeclared flag", () => {
|
|
24
|
+
expect(resolveStartVars({ a: "1" }, { flag_x: "1" }, {}, {})).toEqual({ a: "1" })
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it("--var flag_x beats the flag", () => {
|
|
28
|
+
expect(resolveStartVars({ flag_x: "0" }, { flag_x: "1" }, { flag_x: "0" }, {})).toEqual({
|
|
29
|
+
flag_x: "0",
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("GTD_FLAG_X never overrides, while GTD_<NAME> still does for other names", () => {
|
|
34
|
+
expect(
|
|
35
|
+
resolveStartVars({ flag_x: "0", a: "1" }, {}, {}, { GTD_FLAG_X: "1", GTD_A: "2" }),
|
|
36
|
+
).toEqual({ flag_x: "0", a: "2" })
|
|
37
|
+
})
|
|
38
|
+
})
|
package/src/workflow/vars.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import { Context } from "effect"
|
|
2
|
+
|
|
1
3
|
const PREFIX = "GTD_"
|
|
2
4
|
|
|
5
|
+
/** The live release flags as `flag_<name>` settings, provided by the CLI; absent means no flags. */
|
|
6
|
+
export class StartFlags extends Context.Reference<StartFlags>()("StartFlags", {
|
|
7
|
+
defaultValue: (): Readonly<Record<string, string>> => ({}),
|
|
8
|
+
}) {}
|
|
9
|
+
|
|
3
10
|
/**
|
|
4
11
|
* One kind of setting, later wins: the workflow's own defaults, `--var`
|
|
5
12
|
* overrides (process settings only; environment settings pass `{}`), then
|
|
@@ -22,6 +29,23 @@ export const resolveVars = (
|
|
|
22
29
|
return merged
|
|
23
30
|
}
|
|
24
31
|
|
|
32
|
+
/**
|
|
33
|
+
* `resolveVars` for a process being STARTED: `flags` land between the workflow
|
|
34
|
+
* defaults and `--var`, for already-declared names only. A running process never
|
|
35
|
+
* calls this — it replays its pinned record.
|
|
36
|
+
*/
|
|
37
|
+
export const resolveStartVars = (
|
|
38
|
+
workflowVars: Readonly<Record<string, string>>,
|
|
39
|
+
flags: Readonly<Record<string, string>>,
|
|
40
|
+
entryVars: Readonly<Record<string, string>>,
|
|
41
|
+
env: Readonly<Record<string, string | undefined>>,
|
|
42
|
+
): Record<string, string> => {
|
|
43
|
+
const declared = Object.fromEntries(
|
|
44
|
+
Object.entries(flags).filter(([name]) => Object.hasOwn(workflowVars, name)),
|
|
45
|
+
)
|
|
46
|
+
return resolveVars({ ...workflowVars, ...declared }, entryVars, env)
|
|
47
|
+
}
|
|
48
|
+
|
|
25
49
|
/** The first process setting whose value spans lines — a `Gtd-Var:` trailer cannot carry it. */
|
|
26
50
|
export const multilineSetting = (vars: Readonly<Record<string, string>>): string | undefined =>
|
|
27
51
|
Object.keys(vars)
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
revertScript,
|
|
8
8
|
run,
|
|
9
9
|
start,
|
|
10
|
+
vars,
|
|
10
11
|
} from "../../flows/index.js"
|
|
11
12
|
import { gate } from "../shared/health.js"
|
|
12
13
|
import { shipped } from "../shared/options.js"
|
|
@@ -16,6 +17,7 @@ import * as t from "../shared/text.js"
|
|
|
16
17
|
import { packages } from "./packages.js"
|
|
17
18
|
import { architecture, design } from "./planning.js"
|
|
18
19
|
import { buildTail, type ReviewOutcome } from "./review.js"
|
|
20
|
+
import { seamBuild } from "./seam.js"
|
|
19
21
|
|
|
20
22
|
// The ordinary start. Any change to the tree starts a process: `idle` →
|
|
21
23
|
// `unwind` reverts the sketch (its intent survives in history) → a
|
|
@@ -54,8 +56,13 @@ export const planAndBuild = async (firstBase: string): Promise<void> => {
|
|
|
54
56
|
let base = firstBase
|
|
55
57
|
for (;;) {
|
|
56
58
|
await design(base)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
// GTD_FLAG_NAIVE_PASS: the record is pinned at the process start, so a mid-process flip never moves a process.
|
|
60
|
+
if (vars.flag_naive_pass === "1") {
|
|
61
|
+
await seamBuild()
|
|
62
|
+
} else {
|
|
63
|
+
await architecture()
|
|
64
|
+
await packages()
|
|
65
|
+
}
|
|
59
66
|
const outcome = await buildTail(false, base, true)
|
|
60
67
|
if (outcome.verdict === "signoff") return
|
|
61
68
|
await reUnwind(outcome)
|
|
@@ -4,6 +4,7 @@ export * from "./steps.js"
|
|
|
4
4
|
export * from "./planning.js"
|
|
5
5
|
export * from "./packages.js"
|
|
6
6
|
export * from "./refactor.js"
|
|
7
|
+
export * from "./seam.js"
|
|
7
8
|
export { buildTail, type ReviewOutcome } from "./review.js"
|
|
8
9
|
export * from "./feature.js"
|
|
9
10
|
export * from "../shared/reserved.js"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
@inmem
|
|
2
|
+
Feature: a process keeps the path it started on when GTD_FLAG_NAIVE_PASS changes
|
|
3
|
+
|
|
4
|
+
The flag is recorded in the opening commit, so flipping it mid-process never
|
|
5
|
+
moves a process between the architecture path and the naive-pass path.
|
|
6
|
+
|
|
7
|
+
Background:
|
|
8
|
+
Given the shipped workflows, as gtd install scaffolds them
|
|
9
|
+
And an environment variable "GTD_QUALITYREVIEWS" set to ""
|
|
10
|
+
And a file "src/calc.ts" with:
|
|
11
|
+
"""
|
|
12
|
+
export const add = (a: number, b: number) => a + b
|
|
13
|
+
"""
|
|
14
|
+
And the working tree is committed as "feat: add calculator"
|
|
15
|
+
And a file "TODO.md" with:
|
|
16
|
+
"""
|
|
17
|
+
# Add subtract
|
|
18
|
+
|
|
19
|
+
subtract(a, b) returns the difference.
|
|
20
|
+
"""
|
|
21
|
+
And step "unwind" changes nothing
|
|
22
|
+
And step "start-gate.check" changes nothing
|
|
23
|
+
And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
|
|
24
|
+
"""
|
|
25
|
+
# Requirements
|
|
26
|
+
|
|
27
|
+
## Concerns
|
|
28
|
+
|
|
29
|
+
### Subtract
|
|
30
|
+
|
|
31
|
+
- `subtract(a, b)` returns `a - b`.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
Scenario: a process started with the flag off stays on architecture after the flag turns on
|
|
35
|
+
Given the ambient environment variable "GTD_FLAG_NAIVE_PASS" is "0"
|
|
36
|
+
When the process is driven
|
|
37
|
+
Then the process rests at "design.gate.answer"
|
|
38
|
+
Given the ambient environment variable "GTD_FLAG_NAIVE_PASS" is "1"
|
|
39
|
+
And step "design.gate.answer" changes nothing
|
|
40
|
+
When the process is driven
|
|
41
|
+
Then the process rests at "architecture.author"
|
|
42
|
+
And the commit "gtd(human): idle → unwind" has a body containing "Gtd-Var: flag_naive_pass=0"
|
|
43
|
+
|
|
44
|
+
Scenario: a process started with the flag on stays on the naive pass after the flag turns off
|
|
45
|
+
Given the ambient environment variable "GTD_FLAG_NAIVE_PASS" is "1"
|
|
46
|
+
When the process is driven
|
|
47
|
+
Then the process rests at "design.gate.answer"
|
|
48
|
+
Given the ambient environment variable "GTD_FLAG_NAIVE_PASS" is "0"
|
|
49
|
+
And step "design.gate.answer" changes nothing
|
|
50
|
+
When the process is driven
|
|
51
|
+
Then the process rests at "seam.naive"
|
|
52
|
+
And the commit "gtd(human): idle → unwind" has a body containing "Gtd-Var: flag_naive_pass=1"
|