@pmelab/gtd 38.0.0 → 38.2.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.
Files changed (39) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +36 -3
  3. package/dist/chunks/{Schema-DO5ttJaI.mjs → Schema-C6fLlQzx.mjs} +1 -1
  4. package/dist/chunks/{reserved-sfMdy7v7.mjs → reserved-C3PgP7XB.mjs} +56 -12
  5. package/dist/chunks/{tail-Cgca5Gp-.mjs → tail-Bkxki2nK.mjs} +1 -1
  6. package/dist/chunks/{wire-CSsa3Wxd.mjs → wire-BJuZWR3C.mjs} +1 -1
  7. package/dist/gtd.bundle.mjs +292 -53
  8. package/dist/testkit.mjs +257 -21
  9. package/dist/wire.mjs +1 -1
  10. package/dist/workflows/cleanup.mjs +2 -2
  11. package/dist/workflows/feature.mjs +158 -42
  12. package/dist/workflows/fix.mjs +2 -2
  13. package/dist/workflows/review.mjs +2 -2
  14. package/dist/workflows/rollout.mjs +1 -1
  15. package/dist/workflows.mjs +230 -70
  16. package/package.json +1 -1
  17. package/src/cli/Cli.test.ts +15 -3
  18. package/src/cli/Cli.ts +61 -28
  19. package/src/cli/History.ts +1 -19
  20. package/src/cli/Metrics.test.ts +224 -0
  21. package/src/cli/Metrics.ts +310 -0
  22. package/src/cli/Output.ts +13 -0
  23. package/src/cli/Program.test.ts +3 -1
  24. package/src/cli/Program.ts +4 -0
  25. package/src/testkit/steps.ts +4 -0
  26. package/src/wire/ContextLimit.ts +1 -1
  27. package/src/workflows/feature/continuation-cap.feature +65 -0
  28. package/src/workflows/feature/continuation-rebuild.feature +88 -0
  29. package/src/workflows/feature/continuation-slices.feature +64 -0
  30. package/src/workflows/feature/continuation.feature +205 -0
  31. package/src/workflows/feature/criteria.ts +11 -0
  32. package/src/workflows/feature/refactor.ts +64 -30
  33. package/src/workflows/feature/seam.ts +102 -30
  34. package/src/workflows/shared/continuation.ts +92 -0
  35. package/src/workflows/shared/options.ts +2 -0
  36. package/src/workflows/shared/steps.ts +1 -0
  37. package/src/workflows/shared/text.test.ts +6 -2
  38. package/src/workflows/shared/text.ts +104 -8
  39. package/src/workflows/shared/vars.ts +5 -0
@@ -375,6 +375,7 @@ export const refactorBuildingPrompt = ({
375
375
  reference,
376
376
  feedback = false,
377
377
  note = false,
378
+ from,
378
379
  }: {
379
380
  pkg: string
380
381
  strays: readonly string[]
@@ -382,6 +383,8 @@ export const refactorBuildingPrompt = ({
382
383
  reference?: string | undefined
383
384
  feedback?: boolean | undefined
384
385
  note?: boolean | undefined
386
+ /** A continuation of a refactor turn that stopped at its context limit; `open` are its untouched paths. */
387
+ from?: Handoff | undefined
385
388
  }): string =>
386
389
  `${stateFileRules}
387
390
  - The package to implement is \`${pkg}\`; never delete it
@@ -410,8 +413,9 @@ ${
410
413
  refactor that touches none of it is wrong, however clean — never
411
414
  swap in an unrelated tidy-up
412
415
  `
413
- }- Leave everything uncommitted and finish your turn — do not commit
414
- `
416
+ }${handoffRule()}
417
+ - Leave everything uncommitted and finish your turn — do not commit
418
+ ${from === undefined ? "" : continuationLines(from)}`
415
419
 
416
420
  export const refactorFixPrompt = (strays: readonly string[], pattern: string): string =>
417
421
  `${stateFileRules}
@@ -1198,8 +1202,8 @@ export type NaiveRetry = SeamRejection | NaiveRed | NaiveFault
1198
1202
  const isRed = (retry: NaiveRetry): retry is NaiveRed => "red" in retry
1199
1203
  const isFault = (retry: NaiveRetry): retry is NaiveFault => "fault" in retry
1200
1204
 
1201
- const pickUp = (hash: string): string =>
1202
- `Pick it up — \`git diff --binary ${hash}^ ${hash} | git apply\``
1205
+ const pickUp = (hash: string, exclude?: string): string =>
1206
+ `Pick it up — \`git diff --binary --no-color --no-ext-diff --no-textconv ${hash}^ ${hash}${exclude === undefined ? "" : ` -- ${quote(":(top)")} ${quote(`:(top,exclude)${exclude}`)}`} | git apply\``
1203
1207
 
1204
1208
  const criteriaBinding = (refused: boolean): string =>
1205
1209
  `- Its \`## Acceptance Criteria\` (\`AC-<n>\`) bind this turn: re-read them from
@@ -1227,7 +1231,72 @@ ${listed(retry.strays)}
1227
1231
  Start over and choose a different approach; do not rebuild the same diff.
1228
1232
  `
1229
1233
 
1230
- export const naivePassPrompt = (retry?: NaiveRetry, slice?: readonly string[]): string =>
1234
+ /** A turn that stopped at its context limit: the snapshot it left, its handoff note, what it left open. */
1235
+ export interface Handoff {
1236
+ /** The commit holding the stopped turn's work: its kept tip, or the turn's own commit. */
1237
+ readonly snapshot: string
1238
+ readonly note: string
1239
+ readonly open: readonly string[]
1240
+ }
1241
+
1242
+ const quoted = (text: string): string =>
1243
+ text
1244
+ .trimEnd()
1245
+ .split("\n")
1246
+ .map((line) => (line === "" ? "" : ` > ${line}`))
1247
+ .join("\n")
1248
+
1249
+ /** Every turn a continuation may pick up tells its agent where the note goes. */
1250
+ const handoffRule = (): string =>
1251
+ `- Should you be told to stop at a context limit, write the handoff note to
1252
+ \`.gtd/HANDOFF.md\` — what you touched, what was next — and finish your turn:
1253
+ a fresh turn picks up from what you leave, broken or not. That file is the one
1254
+ exception to any path rule here; write it at no other time`
1255
+
1256
+ /** `pickUp`: how the snapshot gets into the tree — absent when it already is. */
1257
+ const continuationLines = (from: Handoff, pickUp?: string): string =>
1258
+ `
1259
+ Your previous turn stopped at its context limit; this fresh turn picks up from
1260
+ its snapshot (\`${from.snapshot}\`). ${pickUp === undefined ? "Its work is already in the tree." : `${pickUp}.`} Its handoff note:
1261
+
1262
+ ${quoted(from.note === "" ? "(empty)" : from.note)}
1263
+
1264
+ Still open: ${from.open.length === 0 ? "nothing the workflow can count — finish what the note says is next" : from.open.join(", ")}.
1265
+ ${pickUp === undefined ? "Delete `.gtd/HANDOFF.md` once you are done; rewrite it should you be stopped again.\n" : ""}`
1266
+
1267
+ export const oversizedMessage = (
1268
+ phase: string,
1269
+ from: Handoff,
1270
+ why: { readonly cap: number } | { readonly stall: number },
1271
+ narrowable = true,
1272
+ ): string =>
1273
+ `The ${phase} keeps outgrowing its context: ${
1274
+ "cap" in why
1275
+ ? why.cap === 0
1276
+ ? "continuations are off (`continuations` is 0) and the turn stopped at its context limit"
1277
+ : `${why.cap} continuation${why.cap === 1 ? "" : "s"} ran and the last still stopped at its context limit`
1278
+ : `the last ${why.stall} continuation${why.stall === 1 ? "" : "s"} closed nothing still open`
1279
+ }. The last snapshot is \`${from.snapshot}\`; still open: ${from.open.length === 0 ? "nothing countable" : from.open.join(", ")}.
1280
+
1281
+ Its handoff note:
1282
+
1283
+ ${quoted(from.note === "" ? "(empty)" : from.note)}
1284
+
1285
+ Land to go on: a fresh round of continuations picks up from that snapshot${
1286
+ narrowable
1287
+ ? ` — narrow
1288
+ \`.gtd/REQUIREMENTS.md\` first if the change is too big, or raise the context limit`
1289
+ : ` — raise
1290
+ the context limit first if the refactor is too big`
1291
+ }. \`gtd abandon\` gives the change up.
1292
+ `
1293
+
1294
+ /** `from`: a continuation of a pass that stopped at its context limit, in place of `retry`'s pick-up. */
1295
+ export const naivePassPrompt = (
1296
+ retry?: NaiveRetry,
1297
+ slice?: readonly string[],
1298
+ from?: Handoff,
1299
+ ): string =>
1231
1300
  `${stateFileRules}
1232
1301
  - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
1233
1302
  ${sliceLine(slice)}${criteriaBinding(false)}
@@ -1245,9 +1314,34 @@ ${sliceLine(slice)}${criteriaBinding(false)}
1245
1314
  - Run \`${env.testCommand}\` and finish only once it is green with every
1246
1315
  criterion's test passing: the next step runs it on your diff and sends a red
1247
1316
  pass back
1248
- - Leave \`.gtd/\` untouched
1317
+ - Leave \`.gtd/\` untouched, a handoff note excepted
1318
+ ${handoffRule()}
1249
1319
  - Leave everything uncommitted and finish your turn — do not commit
1250
- ${retry === undefined ? "" : naiveRetry(retry)}`
1320
+ ${
1321
+ from !== undefined
1322
+ ? `${continuationLines(from, pickUp(from.snapshot, ".gtd/HANDOFF.md"))}${
1323
+ retry === undefined
1324
+ ? ""
1325
+ : isFault(retry)
1326
+ ? `
1327
+ The pass this continues was a retry: its tests fell short, and still must not:
1328
+ ${retry.fault}
1329
+ `
1330
+ : isRed(retry)
1331
+ ? `
1332
+ The pass this continues was a retry after a red suite (\`.gtd/FEEDBACK.md\`
1333
+ holds the output): finish only once the suite is green.
1334
+ `
1335
+ : `
1336
+ The pass this continues was a retry after a \`reject\`: keep away from the
1337
+ approach that changed these files outside the expected set:
1338
+ ${listed(retry.strays)}
1339
+ `
1340
+ }`
1341
+ : retry === undefined
1342
+ ? ""
1343
+ : naiveRetry(retry)
1344
+ }`
1251
1345
 
1252
1346
  /** The naive pass's kept tip, its acceptance test files and the criterion IDs they name. */
1253
1347
  export interface Acceptance {
@@ -1340,6 +1434,7 @@ export const implementBuildingPrompt = (
1340
1434
  flagMode = true,
1341
1435
  rebuild: Rebuild = {},
1342
1436
  slice?: readonly string[],
1437
+ from?: Handoff,
1343
1438
  ): string =>
1344
1439
  `${stateFileRules}
1345
1440
  - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
@@ -1366,9 +1461,10 @@ ${afterRefactor === true ? afterRefactorLine(rebuild) : ""}${
1366
1461
  - Write the tests first, then the code that makes them pass. Name each
1367
1462
  criterion's ID in the test that covers it (\`[AC-3] …\`): once the suite is
1368
1463
  green a judge checks every ID has a passing test, and sends uncovered ones back
1464
+ ${handoffRule()}
1369
1465
  - Leave \`.gtd/REQUIREMENTS.md\` in place and everything uncommitted, then
1370
1466
  finish your turn
1371
- `
1467
+ ${from === undefined ? "" : continuationLines(from)}`
1372
1468
 
1373
1469
  export const implementFixPrompt = (): string =>
1374
1470
  `${stateFileRules}
@@ -34,6 +34,11 @@ export const defaults: Readonly<Record<string, string>> = {
34
34
  qualityReviews:
35
35
  "correctness, owasp-security, ponytail-review, test-audit, conventions, spec-challenge",
36
36
  refactorQualityReviews: "correctness, ponytail-review, conventions",
37
+ // Per phase (naive pass, refactor, build): continuations of a turn that
38
+ // stopped at its context limit before a person is asked, and how many in a
39
+ // row may close nothing still open.
40
+ continuations: "3",
41
+ continuationStall: "2",
37
42
  }
38
43
 
39
44
  /** Environment settings. */