@pmelab/gtd 58.2.0 → 59.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +120 -395
  3. package/dist/chunks/{Schema-B3o6eRNU.mjs → testkit-07Z_ENch.mjs} +13503 -3460
  4. package/dist/chunks/{wire-C12dahrH.mjs → wire-BTUhRGXj.mjs} +1 -1
  5. package/dist/flows.mjs +1 -1
  6. package/dist/gtd.bundle.mjs +484 -497
  7. package/dist/testkit/gherkin.mjs +145 -65
  8. package/dist/testkit.mjs +1 -1
  9. package/dist/wire.mjs +1 -1
  10. package/package.json +3 -42
  11. package/skills/authoring/SKILL.md +34 -27
  12. package/src/cli/Cli.test.ts +148 -71
  13. package/src/cli/Cli.ts +384 -463
  14. package/src/cli/Help.test.ts +47 -0
  15. package/src/cli/Help.ts +70 -0
  16. package/src/cli/Program.test.ts +127 -33
  17. package/src/cli/Program.ts +70 -25
  18. package/src/edge/Edge.ts +4 -4
  19. package/src/emit/Emit.test.ts +1 -1
  20. package/src/emit/Emit.ts +1 -1
  21. package/src/flags/index.test.ts +0 -13
  22. package/src/flags/index.ts +1 -3
  23. package/src/flows/factory.ts +3 -1
  24. package/src/gherkin/index.ts +135 -71
  25. package/src/gherkin/messages.test.ts +90 -0
  26. package/src/gherkin/messages.ts +84 -0
  27. package/src/install/Install.test.ts +14 -11
  28. package/src/install/Install.ts +38 -15
  29. package/src/install/index.ts +1 -2
  30. package/src/replay/Replay.test.ts +1 -1
  31. package/src/replay/index.ts +1 -1
  32. package/src/step/planStart.ts +6 -1
  33. package/src/testkit/TestRepo.test.ts +47 -2
  34. package/src/testkit/TestRepo.ts +51 -6
  35. package/src/twins/EmittedScriptRecognizer.ts +5 -8
  36. package/src/twins/FlowScripts.ts +1 -1
  37. package/src/wire/Land.ts +1 -1
  38. package/src/workflow/index.ts +1 -0
  39. package/src/workflow/load.test.ts +138 -113
  40. package/src/workflow/load.ts +7 -7
  41. package/src/workflow/skills.test.ts +5 -5
  42. package/src/workflow/skills.ts +1 -1
  43. package/src/workflow/vars.test.ts +30 -3
  44. package/src/workflow/vars.ts +34 -2
  45. package/src/workflow-kit/index.ts +1 -1
  46. package/dist/chunks/reserved-lMYCaapY.mjs +0 -1990
  47. package/dist/chunks/review-Bfaror38.mjs +0 -427
  48. package/dist/chunks/tail-Bd7L940p.mjs +0 -355
  49. package/dist/chunks/testkit-JDtpNKyi.mjs +0 -10410
  50. package/dist/workflows/cleanup.mjs +0 -36
  51. package/dist/workflows/feature.mjs +0 -1018
  52. package/dist/workflows/fix.mjs +0 -41
  53. package/dist/workflows/review.mjs +0 -45
  54. package/dist/workflows/rollout.mjs +0 -48
  55. package/dist/workflows.mjs +0 -40651
  56. package/src/install/ConfigScaffold.ts +0 -22
  57. package/src/workflows/cleanup/cleanup-path.feature +0 -94
  58. package/src/workflows/cleanup/cleanup.feature +0 -315
  59. package/src/workflows/cleanup/cleanup.test.ts +0 -276
  60. package/src/workflows/cleanup/cleanup.ts +0 -52
  61. package/src/workflows/cleanup/index.ts +0 -6
  62. package/src/workflows/feature/acceptance-criteria-tier.feature +0 -25
  63. package/src/workflows/feature/acceptance-criteria.feature +0 -389
  64. package/src/workflows/feature/criteria-coverage.feature +0 -102
  65. package/src/workflows/feature/criteria.test.ts +0 -147
  66. package/src/workflows/feature/criteria.ts +0 -267
  67. package/src/workflows/feature/fast-path.feature +0 -93
  68. package/src/workflows/feature/feature.test.ts +0 -338
  69. package/src/workflows/feature/feature.ts +0 -156
  70. package/src/workflows/feature/green.ts +0 -19
  71. package/src/workflows/feature/index.ts +0 -18
  72. package/src/workflows/feature/lens-models.feature +0 -69
  73. package/src/workflows/feature/lens-review.feature +0 -566
  74. package/src/workflows/feature/naive-acceptance.feature +0 -147
  75. package/src/workflows/feature/naive-test.feature +0 -263
  76. package/src/workflows/feature/one-step-land.feature +0 -93
  77. package/src/workflows/feature/planning.ts +0 -41
  78. package/src/workflows/feature/refactor-absorbs.feature +0 -131
  79. package/src/workflows/feature/refactor-first.feature +0 -158
  80. package/src/workflows/feature/refactor-lens-models.feature +0 -79
  81. package/src/workflows/feature/refactor-rebuild.feature +0 -273
  82. package/src/workflows/feature/refactor-review.feature +0 -1528
  83. package/src/workflows/feature/refactor.test.ts +0 -1220
  84. package/src/workflows/feature/refactor.ts +0 -368
  85. package/src/workflows/feature/release-flag.feature +0 -167
  86. package/src/workflows/feature/release-flag.test.ts +0 -49
  87. package/src/workflows/feature/release-flag.ts +0 -46
  88. package/src/workflows/feature/review-gate.feature +0 -241
  89. package/src/workflows/feature/reviewer-walkthrough.feature +0 -142
  90. package/src/workflows/feature/risk-tier.feature +0 -351
  91. package/src/workflows/feature/seam-rejection.feature +0 -146
  92. package/src/workflows/feature/seam.feature +0 -297
  93. package/src/workflows/feature/seam.test.ts +0 -527
  94. package/src/workflows/feature/seam.ts +0 -426
  95. package/src/workflows/feature/standards.feature +0 -354
  96. package/src/workflows/feature/steps.test.ts +0 -65
  97. package/src/workflows/feature/steps.ts +0 -45
  98. package/src/workflows/feature/tier-pin.feature +0 -66
  99. package/src/workflows/feature/tier.test.ts +0 -166
  100. package/src/workflows/feature/tier.ts +0 -56
  101. package/src/workflows/fix/fix-lens-origin.feature +0 -114
  102. package/src/workflows/fix/fix-quality.feature +0 -212
  103. package/src/workflows/fix/fix.feature +0 -79
  104. package/src/workflows/fix/fix.test.ts +0 -74
  105. package/src/workflows/fix/fix.ts +0 -52
  106. package/src/workflows/fix/index.ts +0 -6
  107. package/src/workflows/index.ts +0 -47
  108. package/src/workflows/review/index.ts +0 -6
  109. package/src/workflows/review/review-door.feature +0 -199
  110. package/src/workflows/review/review.test.ts +0 -27
  111. package/src/workflows/review/review.ts +0 -60
  112. package/src/workflows/rollout/index.ts +0 -6
  113. package/src/workflows/rollout/rollout.feature +0 -510
  114. package/src/workflows/rollout/rollout.test.ts +0 -263
  115. package/src/workflows/rollout/rollout.ts +0 -82
  116. package/src/workflows/shared/__fixtures__/feature-collecting-prompt.txt +0 -98
  117. package/src/workflows/shared/access.test.ts +0 -62
  118. package/src/workflows/shared/access.ts +0 -24
  119. package/src/workflows/shared/config.fixture.ts +0 -12
  120. package/src/workflows/shared/fences.ts +0 -24
  121. package/src/workflows/shared/health.test.ts +0 -148
  122. package/src/workflows/shared/health.ts +0 -191
  123. package/src/workflows/shared/learn.test.ts +0 -140
  124. package/src/workflows/shared/learn.ts +0 -201
  125. package/src/workflows/shared/lenses.test.ts +0 -39
  126. package/src/workflows/shared/lenses.ts +0 -294
  127. package/src/workflows/shared/options.test.ts +0 -157
  128. package/src/workflows/shared/options.ts +0 -92
  129. package/src/workflows/shared/progress.feature +0 -124
  130. package/src/workflows/shared/prose.ts +0 -263
  131. package/src/workflows/shared/recheck.feature +0 -60
  132. package/src/workflows/shared/registry.test.ts +0 -345
  133. package/src/workflows/shared/registry.ts +0 -183
  134. package/src/workflows/shared/reserved.ts +0 -11
  135. package/src/workflows/shared/review.test.ts +0 -1062
  136. package/src/workflows/shared/review.ts +0 -446
  137. package/src/workflows/shared/skills.test.ts +0 -121
  138. package/src/workflows/shared/skills.ts +0 -49
  139. package/src/workflows/shared/standards.test.ts +0 -90
  140. package/src/workflows/shared/standards.ts +0 -156
  141. package/src/workflows/shared/steps.test.ts +0 -219
  142. package/src/workflows/shared/steps.ts +0 -201
  143. package/src/workflows/shared/tail.test.ts +0 -454
  144. package/src/workflows/shared/tail.ts +0 -264
  145. package/src/workflows/shared/text.fixture.ts +0 -128
  146. package/src/workflows/shared/text.test.ts +0 -1430
  147. package/src/workflows/shared/text.ts +0 -1700
  148. package/src/workflows/shared/threads.test.ts +0 -129
  149. package/src/workflows/shared/vars.ts +0 -47
  150. package/src/workflows/shared/workflows.steps.ts +0 -54
@@ -50,7 +50,7 @@ export interface ConfigOperations {
50
50
  // prints `message` verbatim and would print a duplicated `detail` twice.
51
51
  const configError = (diagnostics: readonly Diagnostic[]): GtdError =>
52
52
  new GtdError(
53
- `gtd config:\n${diagnostics
53
+ `gtd: config:\n${diagnostics
54
54
  .filter((d) => d.severity === "error")
55
55
  .map((d) => ` - ${formatDiagnostic(d)}`)
56
56
  .join("\n")}`,
@@ -84,7 +84,7 @@ export const refuseLegacyRc: Effect.Effect<void, GtdError, Host | ConfigDiscover
84
84
  return yield* Effect.fail(
85
85
  new GtdError(
86
86
  [
87
- "gtd config:",
87
+ "gtd: config:",
88
88
  ...found.map((file) => ` - ${file}: no longer read — delete it`),
89
89
  " gtd 23 removed the .gtdrc files; move each key they set:",
90
90
  " vars → an option of the workflow factory in gtd.config.ts, or its `defaults` export",
@@ -115,7 +115,7 @@ export const load: Effect.Effect<
115
115
  const missing: Diagnostic = {
116
116
  severity: "error",
117
117
  path: [],
118
- message: `no workflows configured: no ${WORKFLOW_MODULE} here or above — run \`gtd install\` to scaffold one`,
118
+ message: `no workflows configured: no ${WORKFLOW_MODULE} here or above — gtd ships no workflow; \`gtd install\` says where to get one`,
119
119
  origin: host.root,
120
120
  }
121
121
  return yield* Effect.fail(configError([missing]))
@@ -129,7 +129,7 @@ export const load: Effect.Effect<
129
129
  try: () => loadWorkflows(module, shipped),
130
130
  catch: (e) =>
131
131
  new GtdError(
132
- `gtd config:\n - ${module.filepath}: ${e instanceof Error ? e.message : String(e)}`,
132
+ `gtd: config:\n - ${module.filepath}: ${e instanceof Error ? e.message : String(e)}`,
133
133
  ),
134
134
  })
135
135
  const compiled = loaded.config
@@ -885,7 +885,7 @@ const replayToFirstStep = (
885
885
  outcome.kind === "failed" || outcome.kind === "refused"
886
886
  ? outcome.message.replace(/^gtd: /, "")
887
887
  : "the default entry must begin at a step — that step is where a finished process waits"
888
- return Effect.fail(new GtdError(`gtd config:\n - ${loaded.file.origin}: ${problem}`))
888
+ return Effect.fail(new GtdError(`gtd: config:\n - ${loaded.file.origin}: ${problem}`))
889
889
  },
890
890
  )
891
891
 
@@ -912,7 +912,7 @@ const firstStep = (
912
912
  if (reads.length > 0) {
913
913
  return yield* Effect.fail(
914
914
  new GtdError(
915
- `gtd config:\n - ${loaded.file.origin}: the flow's first step on an ordinary start reads ${reads.join(" and ")} — that step is where a finished process waits, so it must not depend on a setting`,
915
+ `gtd: config:\n - ${loaded.file.origin}: the flow's first step on an ordinary start reads ${reads.join(" and ")} — that step is where a finished process waits, so it must not depend on a setting`,
916
916
  ),
917
917
  )
918
918
  }
@@ -923,7 +923,7 @@ const firstStep = (
923
923
  if (atHead === name) return name
924
924
  return yield* Effect.fail(
925
925
  new GtdError(
926
- `gtd config:\n - ${loaded.file.origin}: the flow's first step on an ordinary start depends on the repository's files ("${name}" without them, "${atHead}" at HEAD) — that step is where a finished process waits, so it must not change as files do`,
926
+ `gtd: config:\n - ${loaded.file.origin}: the flow's first step on an ordinary start depends on the repository's files ("${name}" without them, "${atHead}" at HEAD) — that step is where a finished process waits, so it must not change as files do`,
927
927
  ),
928
928
  )
929
929
  })
@@ -17,24 +17,24 @@ describe("resolveScopeSkills", () => {
17
17
  const message = await failure(() => {
18
18
  throw new Error("boom")
19
19
  })
20
- expect(message).toContain(`gtd config:\n - ${FILE}: boom`)
20
+ expect(message).toContain(`gtd: config:\n - ${FILE}: boom`)
21
21
  })
22
22
 
23
23
  it("rejects a non-object return", async () => {
24
24
  expect(await failure(() => ["a"])).toContain(
25
- `gtd config:\n - ${FILE}: skills export must return an object of scope -> skill names`,
25
+ `gtd: config:\n - ${FILE}: skills export must return an object of scope -> skill names`,
26
26
  )
27
27
  })
28
28
 
29
29
  it("rejects a non-array value", async () => {
30
30
  expect(await failure(() => ({ a: "x" }))).toContain(
31
- `gtd config:\n - ${FILE}: skills export: "a" must be an array of skill names`,
31
+ `gtd: config:\n - ${FILE}: skills export: "a" must be an array of skill names`,
32
32
  )
33
33
  })
34
34
 
35
35
  it("rejects a non-string entry", async () => {
36
36
  expect(await failure(() => ({ a: ["x", 1] }))).toContain(
37
- `gtd config:\n - ${FILE}: skills export: "a" must be an array of skill names`,
37
+ `gtd: config:\n - ${FILE}: skills export: "a" must be an array of skill names`,
38
38
  )
39
39
  })
40
40
 
@@ -80,7 +80,7 @@ describe("resolveScopeAccess", () => {
80
80
 
81
81
  it("rejects a malformed export with the file", async () => {
82
82
  expect(message(await run({ access: () => ["a"] }))).toContain(
83
- `gtd config:\n - ${FILE}: access export must return an object`,
83
+ `gtd: config:\n - ${FILE}: access export must return an object`,
84
84
  )
85
85
  expect(message(await run({ access: () => ({ a: { write: "x" } }) }))).toContain(
86
86
  `${FILE}: access export: "a": access.write must be an array`,
@@ -7,7 +7,7 @@ import type { Diagnostic } from "./Diagnostic.js"
7
7
  import { failOnErrors } from "./load.js"
8
8
 
9
9
  const fail = (origin: string, message: string): GtdError =>
10
- new GtdError(`gtd config:\n - ${origin}: ${message}`)
10
+ new GtdError(`gtd: config:\n - ${origin}: ${message}`)
11
11
 
12
12
  const checkShape = (value: unknown): Record<string, readonly string[]> => {
13
13
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest"
2
- import { resolveStartVars, resolveVars } from "./vars.js"
2
+ import { ownFlagRefusal, 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) => {
@@ -30,9 +30,36 @@ describe("resolveStartVars", () => {
30
30
  })
31
31
  })
32
32
 
33
- it("GTD_FLAG_X never overrides, while GTD_<NAME> still does for other names", () => {
33
+ it("GTD_FLAG_X never overrides one of gtd's own flags, while GTD_<NAME> still does for other names", () => {
34
34
  expect(
35
- resolveStartVars({ flag_x: "0", a: "1" }, {}, {}, { GTD_FLAG_X: "1", GTD_A: "2" }),
35
+ resolveStartVars(
36
+ { flag_x: "0", a: "1" },
37
+ { flag_x: "0" },
38
+ {},
39
+ { GTD_FLAG_X: "1", GTD_A: "2" },
40
+ ),
36
41
  ).toEqual({ flag_x: "0", a: "2" })
37
42
  })
43
+
44
+ it("pins a workflow's own flag from GTD_FLAG_<NAME>, below --var", () => {
45
+ expect(resolveStartVars({ flag_own: "0" }, {}, {}, { GTD_FLAG_OWN: "true" })).toEqual({
46
+ flag_own: "1",
47
+ })
48
+ expect(
49
+ resolveStartVars({ flag_own: "0" }, {}, { flag_own: "0" }, { GTD_FLAG_OWN: "1" }),
50
+ ).toEqual({
51
+ flag_own: "0",
52
+ })
53
+ expect(resolveStartVars({ flag_own: "1" }, {}, {}, {})).toEqual({ flag_own: "1" })
54
+ })
55
+ })
56
+
57
+ describe("ownFlagRefusal", () => {
58
+ it("refuses a workflow's own flag set to anything but 1, 0, true or false", () => {
59
+ expect(ownFlagRefusal({ flag_own: "0" }, {}, { GTD_FLAG_OWN: "yes" })).toBe(
60
+ "GTD_FLAG_OWN must be 1, 0, true or false",
61
+ )
62
+ expect(ownFlagRefusal({ flag_own: "0" }, {}, { GTD_FLAG_OWN: "TRUE" })).toBeUndefined()
63
+ expect(ownFlagRefusal({ flag_x: "0" }, { flag_x: "1" }, { GTD_FLAG_X: "yes" })).toBeUndefined()
64
+ })
38
65
  })
@@ -29,10 +29,38 @@ export const resolveVars = (
29
29
  return merged
30
30
  }
31
31
 
32
+ const FLAG_VALUES: Readonly<Record<string, string>> = { "0": "0", false: "0", "1": "1", true: "1" }
33
+
34
+ const flagVariable = (setting: string): string =>
35
+ `GTD_FLAG_${setting.slice("flag_".length).toUpperCase()}`
36
+
37
+ /** The `flag_<name>` settings a workflow declares that gtd's own flags lack: the workflow's own release flags. */
38
+ const ownFlags = (
39
+ workflowVars: Readonly<Record<string, string>>,
40
+ flags: Readonly<Record<string, string>>,
41
+ ): string[] =>
42
+ Object.keys(workflowVars).filter(
43
+ (name) => name.startsWith("flag_") && !Object.hasOwn(flags, name),
44
+ )
45
+
46
+ /** Why a workflow's own release flag cannot start, if one is set to anything but 1, 0, true or false. */
47
+ export const ownFlagRefusal = (
48
+ workflowVars: Readonly<Record<string, string>>,
49
+ flags: Readonly<Record<string, string>>,
50
+ env: Readonly<Record<string, string | undefined>>,
51
+ ): string | undefined => {
52
+ const bad = ownFlags(workflowVars, flags).find((name) => {
53
+ const raw = env[flagVariable(name)] ?? ""
54
+ return raw !== "" && !Object.hasOwn(FLAG_VALUES, raw.toLowerCase())
55
+ })
56
+ return bad === undefined ? undefined : `${flagVariable(bad)} must be 1, 0, true or false`
57
+ }
58
+
32
59
  /**
33
60
  * `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.
61
+ * defaults and `--var`, for already-declared names only, and so does
62
+ * `GTD_FLAG_<NAME>` for a `flag_<name>` the workflow declares itself. A running
63
+ * process never calls this — it replays its pinned record.
36
64
  */
37
65
  export const resolveStartVars = (
38
66
  workflowVars: Readonly<Record<string, string>>,
@@ -43,6 +71,10 @@ export const resolveStartVars = (
43
71
  const declared = Object.fromEntries(
44
72
  Object.entries(flags).filter(([name]) => Object.hasOwn(workflowVars, name)),
45
73
  )
74
+ for (const name of ownFlags(workflowVars, flags)) {
75
+ const value = FLAG_VALUES[(env[flagVariable(name)] ?? "").toLowerCase()]
76
+ if (value !== undefined) declared[name] = value
77
+ }
46
78
  return resolveVars({ ...workflowVars, ...declared }, entryVars, env)
47
79
  }
48
80
 
@@ -1,4 +1,4 @@
1
- // Building blocks the shipped workflows share. Policy-free: every skill list,
1
+ // Building blocks workflows share. Policy-free: every skill list,
2
2
  // lens, prompt, step, path and threshold arrives as a parameter, so a new
3
3
  // workflow composes these without inheriting the shipped one's choices.
4
4
  export * from "./health.js"