@pmelab/gtd 27.5.0 → 27.7.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 (49) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +12 -2
  3. package/dist/chunks/{reserved-DM5W_LAt.mjs → reserved-Df8GeSHw.mjs} +37 -9
  4. package/dist/chunks/{tail-6gvLXXcg.mjs → tail-DmIfEAI4.mjs} +1 -1
  5. package/dist/flows.mjs +17 -1
  6. package/dist/gtd.bundle.mjs +56 -11
  7. package/dist/testkit.mjs +67 -26
  8. package/dist/workflows/cleanup.mjs +2 -2
  9. package/dist/workflows/feature.mjs +61 -10
  10. package/dist/workflows/fix.mjs +2 -2
  11. package/dist/workflows/review.mjs +2 -2
  12. package/dist/workflows/rollout.mjs +1 -1
  13. package/dist/workflows.mjs +110 -16
  14. package/package.json +1 -1
  15. package/src/cli/Cli.ts +2 -2
  16. package/src/edge/Edge.test.ts +111 -0
  17. package/src/edge/Edge.ts +15 -3
  18. package/src/flags/index.test.ts +13 -0
  19. package/src/flags/index.ts +1 -0
  20. package/src/flows/runtime.test.ts +42 -0
  21. package/src/flows/runtime.ts +36 -0
  22. package/src/replay/Replay.test.ts +43 -0
  23. package/src/replay/Replay.ts +10 -2
  24. package/src/replay/Trailers.test.ts +54 -0
  25. package/src/replay/Trailers.ts +15 -1
  26. package/src/testkit/EmittedScriptRecognizer.test.ts +41 -0
  27. package/src/testkit/EmittedScriptRecognizer.ts +16 -7
  28. package/src/testkit/TestRepo.test.ts +79 -1
  29. package/src/testkit/TestRepo.ts +17 -6
  30. package/src/testkit/steps.ts +44 -1
  31. package/src/testkit/testkit.feature +35 -0
  32. package/src/workflows/cleanup/cleanup-path.feature +94 -0
  33. package/src/workflows/cleanup/cleanup.feature +0 -29
  34. package/src/workflows/feature/fast-path.feature +114 -0
  35. package/src/workflows/feature/feature.ts +3 -1
  36. package/src/workflows/feature/one-step-land.feature +108 -0
  37. package/src/workflows/feature/refactor-first.feature +137 -0
  38. package/src/workflows/feature/risk-tier.feature +238 -0
  39. package/src/workflows/feature/seam-rejection.feature +146 -0
  40. package/src/workflows/feature/seam.feature +0 -67
  41. package/src/workflows/feature/seam.ts +5 -4
  42. package/src/workflows/feature/steps.ts +3 -2
  43. package/src/workflows/feature/tier.test.ts +164 -0
  44. package/src/workflows/feature/tier.ts +74 -0
  45. package/src/workflows/shared/options.test.ts +6 -1
  46. package/src/workflows/shared/text.test.ts +129 -0
  47. package/src/workflows/shared/text.ts +57 -9
  48. package/src/workflows/shared/vars.ts +2 -0
  49. package/src/workflows/shared/workflows.steps.ts +13 -4
package/dist/testkit.mjs CHANGED
@@ -6102,6 +6102,11 @@ const FLAGS$1 = [{
6102
6102
  owner: "pmelab",
6103
6103
  expiry: "2027-01-15",
6104
6104
  kind: "release"
6105
+ }, {
6106
+ name: "risk_tier",
6107
+ owner: "pmelab",
6108
+ expiry: "2027-01-15",
6109
+ kind: "release"
6105
6110
  }];
6106
6111
  const BOOL = {
6107
6112
  "0": false,
@@ -6931,6 +6936,7 @@ const isRecordableApproval = (approval) => {
6931
6936
  const back = decodeApproval(encodeApproval(approval));
6932
6937
  return back !== void 0 && encodeApproval(back) === encodeApproval(approval);
6933
6938
  };
6939
+ const MARK_RE = /^([a-z][a-z0-9-]*)=(.+)$/;
6934
6940
  const FORMAT_RE = /^[1-9][0-9]*$/;
6935
6941
  const HASH_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
6936
6942
  const TRAILER_READERS = {
@@ -6944,6 +6950,13 @@ const TRAILER_READERS = {
6944
6950
  const parsed = parseJson(value);
6945
6951
  if (isVerdict(parsed)) into.judge.push(parsed);
6946
6952
  },
6953
+ "Gtd-Mark": (value, into) => {
6954
+ const kv = MARK_RE.exec(value);
6955
+ if (kv !== null && !CONTROL_CHARACTER$1.test(value)) into.marks.push({
6956
+ name: kv[1],
6957
+ value: kv[2]
6958
+ });
6959
+ },
6947
6960
  "Gtd-Var": (value, into) => {
6948
6961
  const kv = VAR_RE.exec(value);
6949
6962
  if (kv !== null) into.vars[kv[1]] = kv[2];
@@ -6991,6 +7004,7 @@ const parseCommitMessage = (message) => {
6991
7004
  kept: void 0,
6992
7005
  approved: void 0,
6993
7006
  judge: [],
7007
+ marks: [],
6994
7008
  vars: {},
6995
7009
  config: {},
6996
7010
  cost: [],
@@ -7015,6 +7029,7 @@ const formatCommitMessage = (spec) => {
7015
7029
  for (const [path, hash] of sortedEntries(spec.config)) lines.push(`Gtd-Config: ${hash} ${encodePath(path)}`);
7016
7030
  if (spec.cost !== void 0) lines.push(`Gtd-Cost: ${spec.cost.cost}${spec.cost.model !== void 0 ? ` ${spec.cost.model}` : ""}`);
7017
7031
  for (const verdict of spec.judge ?? []) lines.push(`Gtd-Judge: ${JSON.stringify(verdict)}`);
7032
+ for (const m of spec.marks ?? []) lines.push(`Gtd-Mark: ${m.name}=${m.value}`);
7018
7033
  if (spec.approved !== void 0) lines.push(`Gtd-Approved: ${encodeApproval(spec.approved)}`);
7019
7034
  if (spec.branch !== void 0) lines.push(`Gtd-Branch: ${spec.branch}`);
7020
7035
  if (spec.version !== void 0) lines.push(`Gtd-Version: ${spec.version}`);
@@ -7148,6 +7163,7 @@ const replay = async (input) => {
7148
7163
  const trace = [];
7149
7164
  const keptTips = /* @__PURE__ */ new Map();
7150
7165
  let landed;
7166
+ const marks = [];
7151
7167
  let outcome;
7152
7168
  let signal;
7153
7169
  const settled = new Promise((resolve) => {
@@ -7353,7 +7369,8 @@ const replay = async (input) => {
7353
7369
  kind: "rest",
7354
7370
  rest: step,
7355
7371
  trace,
7356
- landed
7372
+ landed,
7373
+ marks
7357
7374
  });
7358
7375
  return landPending(step, input.pending);
7359
7376
  };
@@ -7391,7 +7408,8 @@ const replay = async (input) => {
7391
7408
  kind: "ended",
7392
7409
  via,
7393
7410
  trace,
7394
- landed
7411
+ landed,
7412
+ marks
7395
7413
  };
7396
7414
  const changesSince = (hash) => {
7397
7415
  const tree = treeAt(hash);
@@ -7449,6 +7467,12 @@ const replay = async (input) => {
7449
7467
  faults: findings.filter((f) => f.message.startsWith(`Code thread at ${c.path}:${t.line + 1}: `)).map((f) => f.message)
7450
7468
  }));
7451
7469
  }),
7470
+ mark: (name, value) => {
7471
+ if (pendingUsed) marks.push({
7472
+ name,
7473
+ value
7474
+ });
7475
+ },
7452
7476
  kept: (name) => {
7453
7477
  const tip = keptTips.get(scoped(name));
7454
7478
  return tip === void 0 ? void 0 : {
@@ -8664,6 +8688,7 @@ const buildRun = (history, location, closingHash) => {
8664
8688
  trace: processCommits.map((c, i) => traceEntryOf(c.hash, parsed[i])),
8665
8689
  costEntries: parsed.flatMap(costEntriesOf),
8666
8690
  judgeVerdicts: parsed.flatMap((m) => m.judge),
8691
+ marks: parsed.flatMap((m) => m.marks),
8667
8692
  approvals: processCommits.flatMap((c, i) => approvalEntryOf(c.hash, parsed[i])),
8668
8693
  pinnedVars: { ...first?.vars },
8669
8694
  pinnedConfig: pinnedConfigOf(history.slice(Math.max(location.baseIndex, 0))),
@@ -9265,7 +9290,7 @@ const approvedTrailer = (rest, approver) => isSome(approver) ? { approved: {
9265
9290
  step: rest.step.id,
9266
9291
  ...approver.value
9267
9292
  } } : {};
9268
- const landingTo = (rest, to, branch, approver) => {
9293
+ const landingTo = (rest, to, branch, approver, marks) => {
9269
9294
  if (isSettledNoop(rest, to)) return {
9270
9295
  kind: "noop",
9271
9296
  settled: true
@@ -9283,17 +9308,20 @@ const landingTo = (rest, to, branch, approver) => {
9283
9308
  from: rest.state,
9284
9309
  to,
9285
9310
  step: rest.step.id,
9311
+ ...marks.length > 0 && { marks },
9286
9312
  ...approvedTrailer(rest, approver),
9287
- ...starts ? {
9288
- vars: rest.vars,
9289
- config: rest.config
9290
- } : {},
9313
+ ...startTrailers(rest, starts),
9291
9314
  ...finishes ? closingTrailers(branch) : {}
9292
9315
  },
9293
9316
  ...finishes ? { keepHistory: processHistoryRef(rest.run.startHash) } : {},
9294
9317
  ...keptTurn(rest)
9295
9318
  };
9296
9319
  };
9320
+ const startTrailers = (rest, starts) => starts ? {
9321
+ vars: rest.vars,
9322
+ config: rest.config
9323
+ } : {};
9324
+ const outcomeMarks = (outcome) => outcome.kind === "rest" || outcome.kind === "ended" ? outcome.marks : [];
9297
9325
  /**
9298
9326
  * What landing the pending turn at `rest` does — a pure decision from a
9299
9327
  * replay with the working tree as the pending turn. The target step is
@@ -9317,7 +9345,7 @@ const decideLanding = (rest, verdicts, branch, approver) => gen(function* () {
9317
9345
  }));
9318
9346
  const to = yield* landingTarget(outcome, rest);
9319
9347
  if (typeof to !== "string") return to;
9320
- return landingTo(rest, to, branch, approver);
9348
+ return landingTo(rest, to, branch, approver, outcomeMarks(outcome));
9321
9349
  });
9322
9350
  /** Where landing the pending turn would leave the process, or `undefined` when it would land nothing. */
9323
9351
  const previewLanding = (rest) => decideLanding(rest, void 0, none(), none()).pipe(map$4((landing) => landing.kind === "commit" ? landing.to : void 0), catchAll$1(() => succeed$6(void 0)));
@@ -9354,6 +9382,7 @@ const summaryFor = (run) => gen(function* () {
9354
9382
  })),
9355
9383
  processCost: run.costEntries.reduce((sum, entry) => sum + entry.cost, 0),
9356
9384
  processCostByModel: costByModel(run.costEntries),
9385
+ marks: run.marks,
9357
9386
  ...settingsFor(resolved, run.pinnedVars, host.env, {}, {})
9358
9387
  });
9359
9388
  });
@@ -10796,7 +10825,7 @@ const FLAGS = [
10796
10825
  help: [
10797
10826
  "(gtd land, with --cost) tag that cost's model",
10798
10827
  "(gtd judge run, llm answerer) the claude model; omitted:",
10799
- "GTD_JUDGE_MODEL, then judge.json, then haiku"
10828
+ "GTD_JUDGE_MODEL, then judge.json, then sonnet"
10800
10829
  ]
10801
10830
  },
10802
10831
  {
@@ -11189,7 +11218,7 @@ const COMMAND_ROWS = [
11189
11218
  "--answers <path> or GTD_JUDGE_ANSWERS; questions the file",
11190
11219
  "does not cover are left out. --provider jev asks TypeSafe",
11191
11220
  "(TYPESAFE_API_KEY). --provider llm asks the `claude` CLI",
11192
- "on PATH (Claude Code only), model haiku unless --model.",
11221
+ "on PATH (Claude Code only), model sonnet unless --model.",
11193
11222
  "No --provider: GTD_JUDGE_PROVIDER, then ~/.config/gtd/",
11194
11223
  "judge.json, else jev when TYPESAFE_API_KEY is set and no",
11195
11224
  "model is given, else llm; --model likewise falls back to",
@@ -13047,18 +13076,22 @@ const recognizeGtdUncheck = (repo, block) => {
13047
13076
  * definition) keeps the `@inmem` tier green.
13048
13077
  */
13049
13078
  const recognizeDidNotRunComment = (block) => block === "# gtd emitted this and did NOT run it — pipe it into `sh` to land the turn" ? { kind: "noop" } : void 0;
13050
- /** `combinedScript`'s optional half. ALWAYS reports `noop` whatever the inner result, mirroring bash's `( … ) || <warning>`, where the subshell's exit status is swallowed and must never fail the outer script. */
13079
+ /** `combinedScript`'s optional half. Reports `noop` whatever the inner result, mirroring bash's `( … ) || <warning>`. Exception: under `git: false` any unrecognized inner block fails the outer script (git or not), so the in-memory twin knowingly diverges from `sh` rather than run git unseen. */
13051
13080
  const PRESENTATION_FAILURE_TEXT = `${extractQuotedTokens(PRESENTATION_FAILURE_WARNING)[0].replace(/\\n$/, "")}\n`;
13052
13081
  const PRESENTATION_SUBSHELL_PREFIX = `${PRESENTATION_ONLY_COMMENT}\n(\n`;
13053
13082
  const PRESENTATION_SUBSHELL_SUFFIX = `\n) || ${PRESENTATION_FAILURE_WARNING}`;
13054
- const recognizePresentationSubshell = (repo, commands, block) => {
13083
+ const recognizePresentationSubshell = (repo, commands, block, git) => {
13055
13084
  if (!block.startsWith(PRESENTATION_SUBSHELL_PREFIX) || !block.endsWith(PRESENTATION_SUBSHELL_SUFFIX)) return;
13056
13085
  const inner = block.slice(PRESENTATION_SUBSHELL_PREFIX.length, block.length - PRESENTATION_SUBSHELL_SUFFIX.length);
13057
- const result = applyEmittedScript(repo, commands, inner);
13086
+ const result = applyEmittedScript(repo, commands, inner, { git });
13058
13087
  if (result.ok) return {
13059
13088
  kind: "noop",
13060
13089
  output: result.output
13061
13090
  };
13091
+ if (!git && result.error?.startsWith(UNRECOGNIZED_PREFIX)) return {
13092
+ kind: "failed",
13093
+ error: `${result.output}${result.error}`
13094
+ };
13062
13095
  return {
13063
13096
  kind: "noop",
13064
13097
  output: `${result.output}${PRESENTATION_FAILURE_TEXT}`
@@ -13165,17 +13198,17 @@ const recognizeWriteLines = (repo, block) => {
13165
13198
  return { kind: "applied" };
13166
13199
  };
13167
13200
  /** Every recognizer `applyEmittedScript` tries, first match wins — `recognizeScriptedCommand` stays last since it's the exact-hit fallback for anything the rest don't claim. */
13168
- const recognizersFor = (repo, commands) => [
13201
+ const recognizersFor = (repo, commands, git) => [
13169
13202
  (block) => recognizeSetFlags(block),
13170
13203
  (block) => recognizeDidNotRunComment(block),
13171
13204
  (block) => recognizePrecondition(repo, block),
13172
13205
  (block) => recognizeFileExistsGuard(repo, block),
13173
13206
  (block) => recognizeBinaryGuard(commands, block),
13174
- (block) => recognizeGitBuilders(repo, block),
13207
+ (block) => git ? recognizeGitBuilders(repo, block) : void 0,
13175
13208
  (block) => recognizeDeferNotes(repo, block),
13176
13209
  (block) => recognizeWriteLines(repo, block),
13177
13210
  (block) => recognizeFailurePromptWrapper(repo, commands, block),
13178
- (block) => recognizePresentationSubshell(repo, commands, block),
13211
+ (block) => recognizePresentationSubshell(repo, commands, block, git),
13179
13212
  (block) => recognizeModeContradictionSkipNotice(block),
13180
13213
  (block) => recognizeModeContradictionCheck(repo, commands, block),
13181
13214
  (block) => recognizeGtdCheck(repo, block),
@@ -13198,7 +13231,7 @@ const recognizeBlock = (recognizers, block) => {
13198
13231
  * non-zero scripted-command exit — exactly like `set -euo pipefail` stops a
13199
13232
  * real script. No block after the failing one is applied.
13200
13233
  */
13201
- const applyEmittedScript = (repo, commands, script) => {
13234
+ const applyEmittedScript = (repo, commands, script, { git = true } = {}) => {
13202
13235
  const flow = applyFlowScript(repo, commands, script);
13203
13236
  if (flow !== void 0) return flow.ok ? {
13204
13237
  ok: true,
@@ -13210,10 +13243,10 @@ const applyEmittedScript = (repo, commands, script) => {
13210
13243
  output: flow.output,
13211
13244
  error: flow.error ?? ""
13212
13245
  };
13213
- return script.startsWith("#!") ? unrecognized(script) : applyBlocks(repo, commands, script);
13246
+ return script.startsWith("#!") ? unrecognized(script) : applyBlocks(repo, commands, script, git);
13214
13247
  };
13215
- const applyBlocks = (repo, commands, script) => {
13216
- const recognizers = recognizersFor(repo, commands);
13248
+ const applyBlocks = (repo, commands, script, git) => {
13249
+ const recognizers = recognizersFor(repo, commands, git);
13217
13250
  let output = "";
13218
13251
  for (const block of splitBlocks(script)) {
13219
13252
  const outcome = recognizeBlock(recognizers, block);
@@ -13237,11 +13270,12 @@ const failedAt = (outcome, output) => {
13237
13270
  error: outcome.error
13238
13271
  };
13239
13272
  };
13273
+ const UNRECOGNIZED_PREFIX = "unrecognized script block: ";
13240
13274
  const unrecognized = (block, output = "") => ({
13241
13275
  ok: false,
13242
13276
  status: 1,
13243
13277
  output,
13244
- error: `unrecognized script block: ${block.split("\n")[0]}`
13278
+ error: `${UNRECOGNIZED_PREFIX}${block.split("\n")[0]}`
13245
13279
  });
13246
13280
  //#endregion
13247
13281
  //#region src/testkit/TestRepo.ts
@@ -13293,10 +13327,17 @@ const LINES = [
13293
13327
  repo.writeFile(dest, content);
13294
13328
  }]
13295
13329
  ];
13296
- const runLine = (repo, line) => {
13297
- const known = LINES.find(([pattern]) => pattern.test(line));
13298
- if (known === void 0) throw new Error(`testkit: cannot run \`${line}\` in memory — give the step an actor instead`);
13299
- known[1](repo, words(line));
13330
+ const knownLine = (line) => LINES.find(([pattern]) => pattern.test(line));
13331
+ const cannotRun = (error) => /* @__PURE__ */ new Error(`testkit: cannot run script in memory — give the step an actor instead\n${error}`);
13332
+ const runLines = (repo, script) => {
13333
+ const lines = script.split("\n").map((line) => line.trim());
13334
+ const known = lines.map(knownLine);
13335
+ if (known.every(Boolean)) {
13336
+ known.forEach((k, i) => k[1](repo, words(lines[i])));
13337
+ return;
13338
+ }
13339
+ const applied = applyEmittedScript(repo, /* @__PURE__ */ new Map(), script, { git: false });
13340
+ if (!applied.ok) throw cannotRun(applied.error ?? "");
13300
13341
  };
13301
13342
  const failed = (what, result) => /* @__PURE__ */ new Error(`${what} exited ${result.exitCode}\n${result.stderr}${result.stdout}`);
13302
13343
  const testRepo = (options) => {
@@ -13378,7 +13419,7 @@ const testRepo = (options) => {
13378
13419
  show: (ref, path) => repo.fileAtRef(ref, path) ?? void 0,
13379
13420
  runScript: (script) => {
13380
13421
  const flow = applyFlowScript(repo, /* @__PURE__ */ new Map(), script);
13381
- if (flow === void 0) for (const line of script.split("\n")) runLine(repo, line.trim());
13422
+ if (flow === void 0) runLines(repo, script);
13382
13423
  else if (!flow.ok) throw new Error(`testkit: ${flow.error ?? ""}`);
13383
13424
  },
13384
13425
  gtd,
@@ -1,6 +1,6 @@
1
1
  import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
2
- import { a as flagShipped, b as coder, c as envDefaults, i as skills, n as summary, r as access, s as defaults, t as steering } from "../chunks/reserved-DM5W_LAt.mjs";
3
- import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-6gvLXXcg.mjs";
2
+ import { a as flagShipped, b as coder, c as envDefaults, i as skills, n as summary, r as access, s as defaults, t as steering } from "../chunks/reserved-Df8GeSHw.mjs";
3
+ import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-DmIfEAI4.mjs";
4
4
  import { i as requireFlag, r as registryEntry, t as flagReferences } from "../chunks/registry-CP3YuV9j.mjs";
5
5
  //#region src/workflows/cleanup/cleanup.ts
6
6
  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.`;
@@ -1,10 +1,60 @@
1
- import { answered, changes, changesSince, glob, hasThreadFor, head, human, judge, judgeDiffShape, kept, numeric, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope, seamGate, start, vars } from "../flows.mjs";
1
+ import { answered, changes, changesSince, glob, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, numeric, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope, seamGate, start, vars } from "../flows.mjs";
2
2
  import { review as review$1 } from "../workflow-kit.mjs";
3
- import { $ as refactorFixPrompt, A as architectureAuthorPrompt, B as designGateAnswerMessage, C as fixRisks, D as reviewing, F as buildReviewCollectingPrompt, H as designTriagePrompt, I as buildReviewFixNitsPrompt, J as implementFixPrompt, K as idleMessage, L as buildReviewTriageMessage, M as architectureGateAnswerMessage, N as buildReviewAnswerQuestionsPrompt, O as agentWithSkills, P as buildReviewAwaitAfterDeferMessage, Q as refactorBuildingPrompt, R as builderSystem, T as reviewMissing, V as designSystem, X as packagesItemBuildingPrompt, Y as naivePassPrompt, Z as packagesItemFixSuitePrompt, _ as REQUIREMENTS, at as seamByHandMessage, b as coder, c as envDefaults, ct as unwindFailedMessage, d as gate, et as reviewEditNotesCapture, h as FEEDBACK, i as skills, it as reviewerSystem, j as architectureDecomposePrompt, k as architectSystem, lt as unwindFailure, m as ARCHITECTURE, n as summary, o as shipped, ot as seamSteerMessage, p as healthy, q as implementBuildingPrompt, r as access, rt as reviewNotesCapture, s as defaults, st as startGateBlockedMessage, t as steering, tt as reviewEditsCapture, v as REVIEW, w as planner, x as fixCheck, y as awaitReview, z as deferNotesScript } from "../chunks/reserved-DM5W_LAt.mjs";
4
- import { a as tail, i as refusePendingHandOff, n as closeReview } from "../chunks/tail-6gvLXXcg.mjs";
3
+ import { $ as refactorFixPrompt, A as architectureAuthorPrompt, B as designGateAnswerMessage, C as fixRisks, D as reviewing, F as buildReviewCollectingPrompt, H as designTriagePrompt, I as buildReviewFixNitsPrompt, J as implementFixPrompt, K as idleMessage, L as buildReviewTriageMessage, M as architectureGateAnswerMessage, N as buildReviewAnswerQuestionsPrompt, O as agentWithSkills, P as buildReviewAwaitAfterDeferMessage, Q as refactorBuildingPrompt, R as builderSystem, T as reviewMissing, V as designSystem, X as packagesItemBuildingPrompt, Y as naivePassPrompt, Z as packagesItemFixSuitePrompt, _ as REQUIREMENTS, at as seamByHandMessage, b as coder, c as envDefaults, ct as unwindFailedMessage, d as gate, et as reviewEditNotesCapture, h as FEEDBACK, i as skills, it as reviewerSystem, j as architectureDecomposePrompt, k as architectSystem, lt as unwindFailure, m as ARCHITECTURE, n as summary, o as shipped, ot as seamSteerMessage, p as healthy, q as implementBuildingPrompt, r as access, rt as reviewNotesCapture, s as defaults, st as startGateBlockedMessage, t as steering, tt as reviewEditsCapture, v as REVIEW, w as planner, x as fixCheck, y as awaitReview, z as deferNotesScript } from "../chunks/reserved-Df8GeSHw.mjs";
4
+ import { a as tail, i as refusePendingHandOff, n as closeReview } from "../chunks/tail-DmIfEAI4.mjs";
5
+ //#region src/workflows/feature/tier.ts
6
+ /** GTD_FLAG_NAIVE_PASS, GTD_FLAG_RISK_TIER: both pinned at process start, so a mid-process flip never moves a process. */
7
+ const riskTierOn = () => vars.flag_naive_pass === "1" && vars.flag_risk_tier === "1";
8
+ const markFenced = (text) => {
9
+ let fence;
10
+ return text.split(/\r?\n/).map((line) => {
11
+ const run = /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1];
12
+ const fenced = fence !== void 0 || run !== void 0;
13
+ if (fence === void 0) fence = run;
14
+ else if (run !== void 0 && run[0] === fence[0] && run.length >= fence.length && /^ {0,3}(`+|~+)[ \t]*$/.test(line)) fence = void 0;
15
+ return {
16
+ line,
17
+ fenced
18
+ };
19
+ });
20
+ };
21
+ const sectionLines = (text, title) => {
22
+ if (text === void 0) return [];
23
+ const heading = new RegExp(`^##[ \\t]+${title}[ \\t]*$`, "i");
24
+ const lines = markFenced(text);
25
+ const start = lines.findIndex((l) => !l.fenced && heading.test(l.line));
26
+ if (start < 0) return [];
27
+ const rest = lines.slice(start + 1);
28
+ const end = rest.findIndex((l) => !l.fenced && /^#{1,2}[ \t]/.test(l.line));
29
+ return end < 0 ? rest : rest.slice(0, end);
30
+ };
31
+ /** Anything but a legible `low` is `high`: under-tiering is the risky direction. */
32
+ const readRiskTier = (requirements) => {
33
+ return (sectionLines(requirements, "Risk Tier").find((l) => !l.fenced && l.line.trim() !== "")?.line)?.trim().split(/\s+/)[0]?.replace(/[`*_]/g, "").toLowerCase() === "low" ? "low" : "high";
34
+ };
35
+ const CONTROL = /[\x00-\x1f\x7f-\x9f\u2028\u2029]/g;
36
+ const tidy = (text) => text.replace(/\s+/g, " ").replace(CONTROL, "").trim();
37
+ const productionSignals = (requirements) => {
38
+ const items = [];
39
+ for (const { line, fenced } of sectionLines(requirements, "Production Signals")) {
40
+ if (fenced) continue;
41
+ const top = /^[-*+][ \t]+(.*)$/.exec(line);
42
+ if (top !== null) items.push(top[1]);
43
+ }
44
+ return items.map(tidy).filter((item) => item !== "");
45
+ };
46
+ /** The tier is always marked, first, so the last `risk-tier` mark opens the current lap's marks. */
47
+ const recordRisk = () => {
48
+ const requirements = read(REQUIREMENTS);
49
+ const tier = readRiskTier(requirements);
50
+ mark("risk-tier", tier);
51
+ for (const signal of productionSignals(requirements)) mark("production-signal", signal);
52
+ return tier;
53
+ };
54
+ //#endregion
5
55
  //#region src/workflows/feature/steps.ts
6
56
  /** Turn the sketch since `base` into `.gtd/REQUIREMENTS.md`. */
7
- const triage = (base) => agentWithSkills("triage", designTriagePrompt(base), {
57
+ const triage = (base) => agentWithSkills("triage", designTriagePrompt(base, riskTierOn()), {
8
58
  label: "Triaging the change",
9
59
  file: REQUIREMENTS,
10
60
  mode: "qa",
@@ -24,7 +74,7 @@ const decompose = () => agentWithSkills("decompose.decomposing", architectureDec
24
74
  system: architectSystem()
25
75
  });
26
76
  const answerProductQuestions = () => human("gate.answer", {
27
- message: designGateAnswerMessage(),
77
+ message: designGateAnswerMessage(riskTierOn()),
28
78
  label: "Awaiting your product answers",
29
79
  file: REQUIREMENTS,
30
80
  mode: "qa",
@@ -473,8 +523,8 @@ const naivePass = (rejection) => agentWithSkills("naive", naivePassPrompt(reject
473
523
  model: coder(),
474
524
  system: builderSystem()
475
525
  });
476
- const implement = (reference) => scope("implement", async () => {
477
- await agentWithSkills("building", implementBuildingPrompt(reference), {
526
+ const implement = (reference, tier) => scope("implement", async () => {
527
+ await agentWithSkills("building", implementBuildingPrompt(reference, tier), {
478
528
  label: "Implementing",
479
529
  model: coder(),
480
530
  system: builderSystem()
@@ -521,14 +571,14 @@ const decide = () => scope("seam", async () => {
521
571
  retries = 0;
522
572
  }
523
573
  });
524
- const seamBuild = async () => {
574
+ const seamBuild = async (tier) => {
525
575
  const { reference, shape } = await decide();
526
576
  if (shape !== void 0 && !await refactorFirst(shape, { reference })) await scope("seam", () => human("by-hand", {
527
577
  message: seamByHandMessage(shape.strays, shape.unreadable, reference),
528
578
  label: "Reviewing unreadable files by hand",
529
579
  acceptClean: true
530
580
  }));
531
- await implement(reference);
581
+ await implement(reference, tier);
532
582
  };
533
583
  //#endregion
534
584
  //#region src/workflows/feature/feature.ts
@@ -558,7 +608,8 @@ const planAndBuild = async (firstBase) => {
558
608
  let base = firstBase;
559
609
  for (;;) {
560
610
  await design(base);
561
- if (vars.flag_naive_pass === "1") await seamBuild();
611
+ const tier = riskTierOn() ? recordRisk() : void 0;
612
+ if (vars.flag_naive_pass === "1") await seamBuild(tier);
562
613
  else {
563
614
  await architecture();
564
615
  await packages();
@@ -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 { O as agentWithSkills, U as finisherSystem, W as fixReviewNotesPrompt, b as coder, c as envDefaults, i as skills, l as baseline, n as summary, o as shipped, r as access, s as defaults, t as steering, v as REVIEW } from "../chunks/reserved-DM5W_LAt.mjs";
4
- import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-6gvLXXcg.mjs";
3
+ import { O as agentWithSkills, U as finisherSystem, W as fixReviewNotesPrompt, b as coder, c as envDefaults, i as skills, l as baseline, n as summary, o as shipped, r as access, s as defaults, t as steering, v as REVIEW } from "../chunks/reserved-Df8GeSHw.mjs";
4
+ import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-DmIfEAI4.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 { c as envDefaults, d as gate, i as skills, n as summary, nt as reviewGateBlockedMessage, o as shipped, r as access, s as defaults, t as steering } from "../chunks/reserved-DM5W_LAt.mjs";
3
- import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-6gvLXXcg.mjs";
2
+ import { c as envDefaults, d as gate, i as skills, n as summary, nt as reviewGateBlockedMessage, o as shipped, r as access, s as defaults, t as steering } from "../chunks/reserved-Df8GeSHw.mjs";
3
+ import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-DmIfEAI4.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, head, read, refuse, scope, vars } from "../flows.mjs";
2
- import { a as flagShipped, b as coder, c as envDefaults, d as gate, i as skills, n as summary, r as access, s as defaults, t as steering } from "../chunks/reserved-DM5W_LAt.mjs";
2
+ import { a as flagShipped, b as coder, c as envDefaults, d as gate, i as skills, n as summary, r as access, s as defaults, t as steering } from "../chunks/reserved-Df8GeSHw.mjs";
3
3
  import { i as requireFlag, n as flipProblems } from "../chunks/registry-CP3YuV9j.mjs";
4
4
  //#region src/workflows/rollout/rollout.ts
5
5
  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.`;