@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
@@ -26,7 +26,6 @@ import { EXIT_CODES, EXIT_RUNTIME_ERROR, EXIT_USAGE_ERROR } from "../wire/index.
26
26
  import { Host } from "../platform/index.js"
27
27
  import { type CommandRequirements } from "./Cli.js"
28
28
  import { InMemRepo, makeCapturingCliIo, testLayers } from "../twins/index.js"
29
- import { CONFIG_SCAFFOLD } from "../install/index.js"
30
29
 
31
30
  const FLAG_NAMES = [
32
31
  "--json",
@@ -72,7 +71,8 @@ describe("parseArgv — unknown options", () => {
72
71
  expect(plan).toEqual({
73
72
  kind: "usage",
74
73
  stdout: "",
75
- message: "gtd: unknown option '--jsn' — see `gtd --help`",
74
+ message:
75
+ "gtd: unknown option '--jsn' — did you mean `--json`? Run `gtd help <command>` for its options",
76
76
  json: false,
77
77
  })
78
78
  })
@@ -370,17 +370,52 @@ describe("parseArgv — bare/unknown command under --json", () => {
370
370
  }
371
371
  })
372
372
 
373
- it("bare gtd's missing-command message (no --json) points at gtd install, not a bundled loop", () => {
373
+ it("bare gtd prints a short usage, not the overview, and points at gtd help and gtd install", () => {
374
374
  const plan = parseArgv(["node", "gtd.js"])
375
375
  expect(plan.kind).toBe("usage")
376
376
  if (plan.kind === "usage") {
377
377
  expect(plan.message).toContain("missing command")
378
- expect(plan.message).toContain("gtd decides and prints")
378
+ expect(plan.message).toContain("`gtd help`")
379
379
  expect(plan.message).toContain("gtd install")
380
- expect(plan.message).toContain("A complete minimal driver")
380
+ expect(plan.stdout).toMatch(/^Usage: gtd <command>/)
381
+ expect(plan.stdout.split("\n").length).toBeLessThan(10)
381
382
  }
382
383
  })
383
384
 
385
+ it("an unknown command that looks like a typo names the nearest command", () => {
386
+ expect(parseArgv(["node", "gtd.js", "nxt"])).toEqual({
387
+ kind: "usage",
388
+ stdout: "",
389
+ message:
390
+ "gtd: unknown command 'nxt' — did you mean `gtd next`? Run `gtd help` for every command",
391
+ json: false,
392
+ })
393
+ })
394
+
395
+ it("an unknown command nothing resembles points at gtd help only", () => {
396
+ const plan = parseArgv(["node", "gtd.js", "frobnicate"])
397
+ expect(plan.kind === "usage" && plan.message).toBe(
398
+ "gtd: unknown command 'frobnicate' — run `gtd help` for every command",
399
+ )
400
+ })
401
+
402
+ it("a mistyped history subcommand names the nearest one", () => {
403
+ const plan = parseArgv(["node", "gtd.js", "history", "shwo"])
404
+ expect(plan.kind === "usage" && plan.message).toContain("did you mean `gtd history show`?")
405
+ })
406
+
407
+ it("a bare --json followed by a word suggests the = form", () => {
408
+ const plan = parseArgv(["node", "gtd.js", "next", "--json", "kind"])
409
+ expect(plan.kind === "usage" && plan.message).toBe(
410
+ "gtd next: too many arguments — expected none, got: kind — to print one field, write --json=kind",
411
+ )
412
+ })
413
+
414
+ it("an unknown single-dash option is an unknown option, not an extra argument", () => {
415
+ const plan = parseArgv(["node", "gtd.js", "-j", "next"])
416
+ expect(plan.kind === "usage" && plan.message).toContain("unknown option '-j'")
417
+ })
418
+
384
419
  it("an unknown command under --json carries json:true, naming --json's own scope error over the subcommand", () => {
385
420
  const plan = parseArgv(["node", "gtd.js", "bogus", "--json"])
386
421
  expect(plan.kind).toBe("usage")
@@ -892,10 +927,8 @@ describe("parseArgv — doors", () => {
892
927
  })
893
928
  })
894
929
 
895
- it("`gtd door` with no name is a usage error", () => {
896
- const plan = parse("door")
897
- expect(plan.kind).toBe("usage")
898
- if (plan.kind === "usage") expect(plan.message).toContain("gtd door: missing name argument")
930
+ it("`gtd door` with no name parses, so the loaded config can list its doors", () => {
931
+ expect(parse("door")).toMatchObject({ kind: "command", command: { kind: "door", args: [] } })
899
932
  })
900
933
 
901
934
  it("`gtd doors` parses with every --json shape", () => {
@@ -1062,7 +1095,8 @@ describe("parseArgv — gtd strays", () => {
1062
1095
  const plan = parseArgv(["node", "gtd.js", "lsp", "--json"])
1063
1096
  if (plan.kind === "usage") expect(plan.message).toContain("`gtd strays`")
1064
1097
  expect(renderHelp()).toContain("strays <range> [glob...]")
1065
- expect(renderHelp()).toContain("gtd strays only")
1098
+ const help = parseArgv(["node", "gtd.js", "help", "strays"])
1099
+ expect(help.kind === "output" && help.stdout).toContain("--json[=<path>]")
1066
1100
  })
1067
1101
  })
1068
1102
 
@@ -1101,56 +1135,93 @@ describe("standaloneKinds / needsOf", () => {
1101
1135
  })
1102
1136
  })
1103
1137
 
1138
+ const helpFor = (...args: string[]): string => {
1139
+ const plan = parseArgv(["node", "gtd.js", ...args])
1140
+ if (plan.kind !== "output") throw new Error(`no help for ${args.join(" ")}`)
1141
+ return plan.stdout
1142
+ }
1143
+
1144
+ /** The command names the overview lists — ` name ` lines, as the docs pins read them. */
1145
+ const listedCommands = (): string[] =>
1146
+ [...renderHelp().matchAll(/^ {2}([a-z]+(?:-[a-z]+)?(?: [a-z]+)?)(?= )/gm)].map(([, n]) => n!)
1147
+
1104
1148
  describe("renderHelp", () => {
1105
- it("mentions every command and flag, and no removed surface", () => {
1149
+ it("groups the commands and mentions no removed surface", () => {
1106
1150
  const help = renderHelp()
1107
- expect(help).toContain("Usage")
1108
- expect(help).toContain("land")
1151
+ for (const heading of ["Core loop:", "Start and stop:", "Inspect:", "Driver plumbing:"]) {
1152
+ expect(help).toContain(`\n${heading}\n`)
1153
+ }
1109
1154
  expect(help).toContain("--workflow <name>")
1110
1155
  expect(help).toContain("--var")
1111
- expect(help).toContain("abandon")
1112
- expect(help).toContain("restore")
1113
- expect(help).toContain("next")
1114
- expect(help).toContain("validate")
1115
- expect(help).toContain("lsp")
1116
- expect(help).not.toContain("visualize")
1117
- expect(help).not.toMatch(/^ {2}init\b/m)
1118
- expect(help).not.toContain(".gtdrc")
1119
- expect(help).toMatch(/^ {2}ui\b/m)
1120
1156
  expect(help).toContain("check <mode> <file>")
1121
- expect(help).toContain("install")
1122
- expect(help).toContain("base ")
1123
- expect(help).toMatch(/^ {2}judge\b/m)
1124
- expect(help).toContain("judge answer")
1125
- expect(help).toContain("version")
1126
- expect(help).toContain("help")
1127
- expect(help).toContain("--json")
1128
- expect(help).toContain("--port")
1129
- expect(help).not.toContain("--no-open")
1130
- expect(help).toContain("--host")
1131
- expect(help).toContain("--self-signed")
1132
- expect(help).toContain("--dev")
1133
- expect(help).toContain("--cost")
1134
- expect(help).toContain("--model")
1135
- expect(help).toContain("--open-questions")
1136
- expect(help).toContain("--verbose")
1137
- expect(help).toContain("--version, -V")
1138
- expect(help).toContain("--help, -h")
1139
- expect(help).not.toContain("review <commitish>")
1140
- expect(help).not.toContain("format <file>")
1141
- expect(help).not.toContain("--debug")
1142
- expect(help).not.toContain("bin/gtd")
1143
- expect(help).not.toContain("(no command), loop")
1144
- expect(help).not.toContain("--dispatch")
1145
- expect(help).not.toContain("--if-resting")
1146
- expect(help).not.toMatch(/^ {2}serve\b/m)
1157
+ expect(help).toContain("door <name> [args...]")
1158
+ expect(help).toContain("-V, --version")
1159
+ expect(help).toContain("-h, --help")
1160
+ expect(help).toContain("Examples:")
1161
+ for (const removed of [
1162
+ "visualize",
1163
+ ".gtdrc",
1164
+ "--no-open",
1165
+ "--debug",
1166
+ "--dispatch",
1167
+ "bin/gtd",
1168
+ ]) {
1169
+ expect(help).not.toContain(removed)
1170
+ }
1171
+ expect(help).not.toMatch(/^ {2}(init|serve)\b/m)
1147
1172
  expect(help).toMatch(/\n$/)
1148
1173
  })
1149
1174
 
1150
- it("every flag row's name appears in the Options: block and vice versa", () => {
1151
- const help = renderHelp()
1152
- const optionsBlock = help.slice(help.indexOf("Options:"))
1153
- for (const name of FLAG_NAMES) expect(optionsBlock).toContain(name)
1175
+ it("keeps every line of every help text within 80 columns", () => {
1176
+ for (const text of [renderHelp(), ...listedCommands().map((name) => helpFor("help", name))]) {
1177
+ for (const line of text.split("\n")) expect(line.length, line).toBeLessThanOrEqual(80)
1178
+ }
1179
+ })
1180
+
1181
+ it("names no internals", () => {
1182
+ const all = [renderHelp(), ...listedCommands().map((name) => helpFor("help", name))].join("")
1183
+ for (const internal of ["it.processCost", "refs/worktree/gtd/history", "Effect Schema"]) {
1184
+ expect(all).not.toContain(internal)
1185
+ }
1186
+ })
1187
+
1188
+ it("every flag is in the help of a command it is valid for", () => {
1189
+ const all = [renderHelp(), ...listedCommands().map((name) => helpFor("help", name))].join("")
1190
+ for (const name of FLAG_NAMES) expect(all).toContain(name)
1191
+ })
1192
+
1193
+ it("gtd <cmd> --help, gtd <cmd> -h and gtd help <cmd> print that command's own help", () => {
1194
+ const help = helpFor("help", "land")
1195
+ expect(help).toMatch(/^Usage: gtd land \[options\]\n/)
1196
+ expect(help).toContain("--cost=<n>")
1197
+ expect(help).not.toContain("--port")
1198
+ expect(helpFor("land", "--help")).toBe(help)
1199
+ expect(helpFor("land", "-h")).toBe(help)
1200
+ })
1201
+
1202
+ it("a two-word command's help is its own", () => {
1203
+ expect(helpFor("judge", "run", "--help")).toMatch(/^Usage: gtd judge run \[options\]/)
1204
+ expect(helpFor("help", "history", "show")).toMatch(/^Usage: gtd history show <ref>/)
1205
+ })
1206
+
1207
+ it("a flag that means something else per command is described per command", () => {
1208
+ expect(helpFor("help", "land")).toContain("With --cost: the model")
1209
+ expect(helpFor("help", "judge", "run")).toContain("The claude model")
1210
+ })
1211
+
1212
+ it("--help after an unknown command still prints the overview", () => {
1213
+ expect(helpFor("bogus", "--help")).toBe(renderHelp())
1214
+ })
1215
+
1216
+ it("gtd help <unknown> is a usage error naming the nearest command", () => {
1217
+ const plan = parseArgv(["node", "gtd.js", "help", "lnad"])
1218
+ expect(plan).toEqual({
1219
+ kind: "usage",
1220
+ stdout: "",
1221
+ message:
1222
+ "gtd: help: no command 'lnad' — did you mean `gtd help land`? Run `gtd help` for every command",
1223
+ json: false,
1224
+ })
1154
1225
  })
1155
1226
 
1156
1227
  it("the docs/cli.md Commands fenced block equals renderHelp()", () => {
@@ -1160,6 +1231,15 @@ describe("renderHelp", () => {
1160
1231
  expect(match![1] + "\n").toBe(renderHelp())
1161
1232
  })
1162
1233
 
1234
+ it("docs/cli.md's Every command section is every command's help, in the overview's order", () => {
1235
+ const doc = readFileSync(resolve(import.meta.dirname, "../../docs/cli.md"), "utf8")
1236
+ const section = doc.slice(doc.indexOf("\n## Every command\n"))
1237
+ const documented = [...section.matchAll(/^### `gtd ([^`]+)`\n\n```\n([\s\S]*?)```\n/gm)].map(
1238
+ ([, name, help]) => [name, help],
1239
+ )
1240
+ expect(documented).toEqual(listedCommands().map((name) => [name, helpFor("help", name)]))
1241
+ })
1242
+
1163
1243
  it("README.md and COMPATIBILITY.md list exactly the commands", () => {
1164
1244
  const commands = new Set(
1165
1245
  [...renderHelp().matchAll(/^ {2}([a-z]+(?:-[a-z]+)?(?: [a-z]+)?)(?= )/gm)].map(
@@ -1291,8 +1371,9 @@ export const side = async () => {
1291
1371
  await agent("working", "w")
1292
1372
  }
1293
1373
 
1294
- export const defaults = { flag_risk_tier: "0" }
1374
+ export const defaults = { flag_beta: "0" }
1295
1375
  `
1376
+ const BETA = { name: "beta", owner: "pmelab", expiry: "2099-01-01", kind: "release" } as const
1296
1377
  const start = async (
1297
1378
  flags?: CliIo["flags"],
1298
1379
  env: Record<string, string> = {},
@@ -1307,27 +1388,23 @@ export const defaults = { flag_risk_tier: "0" }
1307
1388
  return result().stdout
1308
1389
  }
1309
1390
 
1310
- it("pins flag_risk_tier=1 with the flag on", async () => {
1311
- expect(await start(undefined, { GTD_FLAG_RISK_TIER: "1" })).toContain("flag_risk_tier=1")
1391
+ it("pins flag_beta=1 with the flag on", async () => {
1392
+ expect(await start([BETA], { GTD_FLAG_BETA: "1" })).toContain("flag_beta=1")
1312
1393
  })
1313
1394
 
1314
1395
  it("honours defaultOn with GTD_FLAG_* unset", async () => {
1315
- const out = await start(
1316
- [
1317
- {
1318
- name: "risk_tier",
1319
- owner: "pmelab",
1320
- expiry: "2099-01-01",
1321
- kind: "release",
1322
- defaultOn: true,
1323
- },
1324
- ],
1325
- { GTD_FLAG_RISK_TIER: "" },
1326
- )
1327
- expect(out).toContain("flag_risk_tier=1")
1396
+ const out = await start([{ ...BETA, defaultOn: true }], { GTD_FLAG_BETA: "" })
1397
+ expect(out).toContain("flag_beta=1")
1328
1398
  })
1329
1399
  })
1330
1400
 
1401
+ const IDLE_CONFIG = `import { human } from "@pmelab/gtd/flows"
1402
+
1403
+ export default async () => {
1404
+ await human("idle", { message: "hi" })
1405
+ }
1406
+ `
1407
+
1331
1408
  describe("runCli — exit codes", () => {
1332
1409
  it("output plan: exit is never called (success) — --help and --version still exit 0", async () => {
1333
1410
  const { io, captured } = capturingIo(throwingLayers)
@@ -1369,7 +1446,7 @@ describe("runCli — exit codes", () => {
1369
1446
  it("an unknown --json=<path> selector exits EXIT_USAGE_ERROR, not EXIT_RUNTIME_ERROR — Program.ts's SelectorUsageError mapped by report()", async () => {
1370
1447
  const repo = new InMemRepo()
1371
1448
  repo.writeFile("NOTE.md", "a note\n")
1372
- repo.writeFile("gtd.config.ts", CONFIG_SCAFFOLD)
1449
+ repo.writeFile("gtd.config.ts", IDLE_CONFIG)
1373
1450
  repo.seedCommit("gtd: init")
1374
1451
  const { io, captured } = capturingIo(() => testLayers(repo))
1375
1452
  await Effect.runPromise(runCli(["node", "gtd.js", "next", "--json=does.not.exist"], io))
@@ -1423,7 +1500,7 @@ describe("runCli — stdout stays byte-empty on every failing surface", () => {
1423
1500
  const { stdout, stderr, exitCode } = captured()
1424
1501
  expect(exitCode).toBe(EXIT_RUNTIME_ERROR)
1425
1502
  expect(stdout).toBe("")
1426
- expect(stderr).toContain("gtd requires a repository with at least one commit")
1503
+ expect(stderr).toContain("gtd: the repository has no commits yet")
1427
1504
  })
1428
1505
 
1429
1506
  it("runtime error: a handler's own Effect.fail (not the shared repo-root/commit guard) leaves stdout byte-empty", async () => {
@@ -1534,7 +1611,7 @@ describe("runCli — flush normalizes the artifact's trailing newline", () => {
1534
1611
  it("a plain-text write command's script (no trailing newline) reaches io.stdout with exactly one", async () => {
1535
1612
  const repo = new InMemRepo()
1536
1613
  repo.writeFile(".gitignore", "node_modules\n")
1537
- repo.writeFile("gtd.config.ts", CONFIG_SCAFFOLD)
1614
+ repo.writeFile("gtd.config.ts", IDLE_CONFIG)
1538
1615
  repo.seedCommit("chore: initial commit")
1539
1616
  const { io, captured } = capturingIo(() => testLayers(repo))
1540
1617
  await Effect.runPromise(runCli(["node", "gtd.js", "land"], io))