@pmelab/gtd 9.14.1 → 10.0.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/README.md CHANGED
@@ -108,8 +108,8 @@ commit everything with this message: gtd(human): idle → unwind
108
108
  (run `gtd land --json=script | sh` to get the landing script)
109
109
  ```
110
110
 
111
- You could run that commit by hand. Take the script instead — it adds the
112
- precondition checks and retries you would otherwise have to remember:
111
+ You could run that commit by hand. Take the script instead — it spells out the
112
+ exact message and reports what landed:
113
113
 
114
114
  ```bash
115
115
  gtd land --json=script | sh
@@ -6514,14 +6514,6 @@ const Equivalence$3 = (self, that) => matchWith(self, that, {
6514
6514
  * @since 2.0.0
6515
6515
  * @category math
6516
6516
  */
6517
- const times = /*#__PURE__*/ dual(2, (self, times) => match$7(self, {
6518
- onMillis: (millis) => make$42(millis * times),
6519
- onNanos: (nanos) => make$42(nanos * BigInt(times))
6520
- }));
6521
- /**
6522
- * @since 2.0.0
6523
- * @category math
6524
- */
6525
6517
  const sum = /*#__PURE__*/ dual(2, (self, that) => matchWith(self, that, {
6526
6518
  onMillis: (self, that) => make$42(self + that),
6527
6519
  onNanos: (self, that) => make$42(self + that)
@@ -8120,7 +8112,7 @@ const catchAll$1 = /*#__PURE__*/ dual(2, (self, f) => matchEffect$1(self, {
8120
8112
  onFailure: f,
8121
8113
  onSuccess: succeed$8
8122
8114
  }));
8123
- const catchIf$1 = /*#__PURE__*/ dual(3, (self, predicate, f) => catchAllCause$1(self, (cause) => {
8115
+ const catchIf = /*#__PURE__*/ dual(3, (self, predicate, f) => catchAllCause$1(self, (cause) => {
8124
8116
  const either = failureOrCause$1(cause);
8125
8117
  switch (either._tag) {
8126
8118
  case "Left": return predicate(either.left) ? f(either.left) : failCause$7(cause);
@@ -9869,10 +9861,6 @@ const defaultServicesWith = (f) => withFiberRuntime$1((fiber) => f(fiber.current
9869
9861
  const clockWith = (f) => defaultServicesWith((services) => f(services.unsafeMap.get(clockTag.key)));
9870
9862
  /** @internal */
9871
9863
  const currentTimeMillis$1 = /*#__PURE__*/ clockWith((clock) => clock.currentTimeMillis);
9872
- /** @internal */
9873
- const randomWith = (f) => defaultServicesWith((services) => f(services.unsafeMap.get(randomTag.key)));
9874
- /** @internal */
9875
- const next$1 = /*#__PURE__*/ randomWith((random) => random.next);
9876
9864
  //#endregion
9877
9865
  //#region node_modules/effect/dist/esm/Boolean.js
9878
9866
  /**
@@ -11367,7 +11355,7 @@ const catchTag$1 = /*#__PURE__*/ dual((args) => isEffect$1(args[0]), (self, ...a
11367
11355
  for (let i = 0; i < args.length - 1; i++) if (args[i] === tag) return true;
11368
11356
  return false;
11369
11357
  };
11370
- return catchIf$1(self, predicate, f);
11358
+ return catchIf(self, predicate, f);
11371
11359
  });
11372
11360
  const diffFiberRefs = (self) => summarized(self, fiberRefs, diff$2);
11373
11361
  const diffFiberRefsAndRuntimeFlags = (self) => summarized(self, zip$3(fiberRefs, runtimeFlags), ([refs, flags], [refsNew, flagsNew]) => [diff$2(refs, refsNew), diff$3(flags, flagsNew)]);
@@ -11879,7 +11867,7 @@ const fromIterable = (iterable) => {
11879
11867
  return new MetricBoundariesImpl(pipe(iterable, appendAll$2(of$1(Number.POSITIVE_INFINITY)), dedupe));
11880
11868
  };
11881
11869
  /** @internal */
11882
- const exponential$2 = (options) => pipe(makeBy(options.count - 1, (i) => options.start * Math.pow(options.factor, i)), unsafeFromArray, fromIterable);
11870
+ const exponential = (options) => pipe(makeBy(options.count - 1, (i) => options.start * Math.pow(options.factor, i)), unsafeFromArray, fromIterable);
11883
11871
  /** @internal */
11884
11872
  const MetricKeyTypeTypeId = /*#__PURE__*/ Symbol.for("effect/MetricKeyType");
11885
11873
  /** @internal */
@@ -12953,7 +12941,7 @@ const fiberSuccesses = /*#__PURE__*/ counter("effect_fiber_successes", { increme
12953
12941
  /** @internal */
12954
12942
  const fiberFailures = /*#__PURE__*/ counter("effect_fiber_failures", { incremental: true });
12955
12943
  /** @internal */
12956
- const fiberLifetimes = /*#__PURE__*/ tagged$1(/*#__PURE__*/ histogram("effect_fiber_lifetimes", /*#__PURE__*/ exponential$2({
12944
+ const fiberLifetimes = /*#__PURE__*/ tagged$1(/*#__PURE__*/ histogram("effect_fiber_lifetimes", /*#__PURE__*/ exponential({
12957
12945
  start: .5,
12958
12946
  factor: 2,
12959
12947
  count: 35
@@ -14396,7 +14384,7 @@ const isEmpty$3 = (self) => {
14396
14384
  return self.startMillis >= self.endMillis;
14397
14385
  };
14398
14386
  /** @internal */
14399
- const intersect$5 = /*#__PURE__*/ dual(2, (self, that) => {
14387
+ const intersect$4 = /*#__PURE__*/ dual(2, (self, that) => {
14400
14388
  const start = Math.max(self.startMillis, that.startMillis);
14401
14389
  const end = Math.min(self.endMillis, that.endMillis);
14402
14390
  return make$23(start, end);
@@ -14449,7 +14437,7 @@ const isEmpty$2 = isEmpty$3;
14449
14437
  * @since 2.0.0
14450
14438
  * @category ordering
14451
14439
  */
14452
- const intersect$4 = intersect$5;
14440
+ const intersect$3 = intersect$4;
14453
14441
  /**
14454
14442
  * Calculates the size of the `Interval` as the `Duration` from the start of the
14455
14443
  * interval to the end of the interval.
@@ -14476,14 +14464,14 @@ const make$21 = (intervals) => {
14476
14464
  };
14477
14465
  };
14478
14466
  /** @internal */
14479
- const intersect$3 = /*#__PURE__*/ dual(2, (self, that) => intersectLoop(self.intervals, that.intervals, empty$24()));
14467
+ const intersect$2 = /*#__PURE__*/ dual(2, (self, that) => intersectLoop(self.intervals, that.intervals, empty$24()));
14480
14468
  /** @internal */
14481
14469
  const intersectLoop = (_left, _right, _acc) => {
14482
14470
  let left = _left;
14483
14471
  let right = _right;
14484
14472
  let acc = _acc;
14485
14473
  while (isNonEmpty$4(left) && isNonEmpty$4(right)) {
14486
- const interval = pipe(headNonEmpty(left), intersect$4(headNonEmpty(right)));
14474
+ const interval = pipe(headNonEmpty(left), intersect$3(headNonEmpty(right)));
14487
14475
  const intervals = isEmpty$2(interval) ? acc : pipe(acc, prepend$1(interval));
14488
14476
  if (pipe(headNonEmpty(left), lessThan$3(headNonEmpty(right)))) left = tailNonEmpty(left);
14489
14477
  else right = tailNonEmpty(right);
@@ -14520,7 +14508,7 @@ const make$20 = make$21;
14520
14508
  * @since 2.0.0
14521
14509
  * @category utils
14522
14510
  */
14523
- const intersect$2 = intersect$3;
14511
+ const intersect$1 = intersect$2;
14524
14512
  /**
14525
14513
  * The start of the earliest interval in the specified `Intervals`.
14526
14514
  *
@@ -15886,15 +15874,6 @@ const uncapitalize = (self) => {
15886
15874
  * @since 2.0.0
15887
15875
  */
15888
15876
  const isNonEmpty$1 = (self) => self.length > 0;
15889
- //#endregion
15890
- //#region node_modules/effect/dist/esm/Random.js
15891
- /**
15892
- * Returns the next numeric value from the pseudo-random number generator.
15893
- *
15894
- * @since 2.0.0
15895
- * @category constructors
15896
- */
15897
- const next = next$1;
15898
15877
  /** @internal */
15899
15878
  const ScheduleTypeId = /*#__PURE__*/ Symbol.for("effect/Schedule");
15900
15879
  /** @internal */
@@ -16021,18 +16000,9 @@ const checkEffect = /*#__PURE__*/ dual(2, (self, test) => makeWithState(self.ini
16021
16000
  ]);
16022
16001
  })));
16023
16002
  /** @internal */
16024
- const delayedEffect = /*#__PURE__*/ dual(2, (self, f) => modifyDelayEffect(self, (_, delay) => f(delay)));
16025
- /** @internal */
16026
- const delayedSchedule = (schedule) => addDelay(schedule, (x) => x);
16027
- /** @internal */
16028
16003
  const driver = (self) => pipe(make$30([none$4(), self.initial]), map$8((ref) => new ScheduleDriverImpl(self, ref)));
16029
16004
  /** @internal */
16030
- const exponential$1 = (baseInput, factor = 2) => {
16031
- const base = decode(baseInput);
16032
- return delayedSchedule(map$5(forever, (i) => times(base, Math.pow(factor, i))));
16033
- };
16034
- /** @internal */
16035
- const intersect$1 = /*#__PURE__*/ dual(2, (self, that) => intersectWith(self, that, intersect$2));
16005
+ const intersect = /*#__PURE__*/ dual(2, (self, that) => intersectWith(self, that, intersect$1));
16036
16006
  /** @internal */
16037
16007
  const intersectWith = /*#__PURE__*/ dual(3, (self, that, f) => makeWithState([self.initial, that.initial], (now, input, state) => pipe(zipWith$2(self.step(now, input, state[0]), that.step(now, input, state[1]), (a, b) => [a, b]), flatMap$7(([[lState, out, lDecision], [rState, out2, rDecision]]) => {
16038
16008
  if (isContinue(lDecision) && isContinue(rDecision)) return intersectWithLoop(self, that, input, lState, out, lDecision.intervals, rState, out2, rDecision.intervals, f);
@@ -16068,23 +16038,6 @@ const intersectWithLoop = (self, that, input, lState, out, lInterval, rState, ou
16068
16038
  });
16069
16039
  };
16070
16040
  /** @internal */
16071
- const jittered$1 = (self) => jitteredWith(self, {
16072
- min: .8,
16073
- max: 1.2
16074
- });
16075
- /** @internal */
16076
- const jitteredWith = /*#__PURE__*/ dual(2, (self, options) => {
16077
- const { max, min } = Object.assign({
16078
- min: .8,
16079
- max: 1.2
16080
- }, options);
16081
- return delayedEffect(self, (duration) => map$8(next, (random) => {
16082
- const d = toMillis(duration);
16083
- const jittered = d * min * (1 - random) + d * max * random;
16084
- return millis(jittered);
16085
- }));
16086
- });
16087
- /** @internal */
16088
16041
  const map$5 = /*#__PURE__*/ dual(2, (self, f) => mapEffect(self, (out) => sync$2(() => f(out))));
16089
16042
  /** @internal */
16090
16043
  const mapEffect = /*#__PURE__*/ dual(2, (self, f) => makeWithState(self.initial, (now, input, state) => flatMap$7(self.step(now, input, state), ([state, out, decision]) => map$8(f(out), (out2) => [
@@ -16122,7 +16075,7 @@ const passthrough = (self) => makeWithState(self.initial, (now, input, state) =>
16122
16075
  decision
16123
16076
  ])));
16124
16077
  /** @internal */
16125
- const recurs$1 = (n) => whileOutput(forever, (out) => out < n);
16078
+ const recurs = (n) => whileOutput(forever, (out) => out < n);
16126
16079
  /** @internal */
16127
16080
  const spaced$1 = (duration) => addDelay(forever, () => duration);
16128
16081
  /** @internal */
@@ -16171,7 +16124,7 @@ const repeat_combined = /*#__PURE__*/ dual(2, (self, options) => {
16171
16124
  if (typeof applied === "boolean") return succeed$8(applied);
16172
16125
  return scheduleDefectWrap(applied);
16173
16126
  }) : withWhile;
16174
- const withTimes = options.times ? intersect$1(withUntil, recurs$1(options.times)).pipe(map$5((intersectionPair) => intersectionPair[0])) : withUntil;
16127
+ const withTimes = options.times ? intersect(withUntil, recurs(options.times)).pipe(map$5((intersectionPair) => intersectionPair[0])) : withUntil;
16175
16128
  return scheduleDefectRefail(repeat_Effect(self, withTimes));
16176
16129
  });
16177
16130
  /** @internal */
@@ -16207,7 +16160,7 @@ const fromRetryOptions = (options) => {
16207
16160
  if (typeof applied === "boolean") return succeed$8(applied);
16208
16161
  return scheduleDefectWrap(applied);
16209
16162
  }) : withWhile;
16210
- return options.times !== void 0 ? intersect$1(withUntil, recurs$1(options.times)) : withUntil;
16163
+ return options.times !== void 0 ? intersect(withUntil, recurs(options.times)) : withUntil;
16211
16164
  };
16212
16165
  /** @internal */
16213
16166
  const retryOrElse_Effect = /*#__PURE__*/ dual(3, (self, policy, orElse) => flatMap$7(driver(policy), (driver) => retryOrElse_EffectLoop(provideServiceEffect(self, CurrentIterationMetadata, get$4(driver.iterationMeta)), driver, (e, out) => provideServiceEffect(orElse(e, out), CurrentIterationMetadata, get$4(driver.iterationMeta)))));
@@ -17449,59 +17402,6 @@ const catchAll = catchAll$1;
17449
17402
  */
17450
17403
  const catchAllCause = catchAllCause$1;
17451
17404
  /**
17452
- * Recovers from specific errors based on a predicate.
17453
- *
17454
- * **When to Use**
17455
- *
17456
- * `catchIf` works similarly to {@link catchSome}, but it allows you to
17457
- * recover from errors by providing a predicate function. If the predicate
17458
- * matches the error, the recovery effect is applied. This function doesn't
17459
- * alter the error type, so the resulting effect still carries the original
17460
- * error type unless a user-defined type guard is used to narrow the type.
17461
- *
17462
- * **Example** (Catching Specific Errors with a Predicate)
17463
- *
17464
- * ```ts
17465
- * import { Effect, Random } from "effect"
17466
- *
17467
- * class HttpError {
17468
- * readonly _tag = "HttpError"
17469
- * }
17470
- *
17471
- * class ValidationError {
17472
- * readonly _tag = "ValidationError"
17473
- * }
17474
- *
17475
- * // ┌─── Effect<string, HttpError | ValidationError, never>
17476
- * // ▼
17477
- * const program = Effect.gen(function* () {
17478
- * const n1 = yield* Random.next
17479
- * const n2 = yield* Random.next
17480
- * if (n1 < 0.5) {
17481
- * yield* Effect.fail(new HttpError())
17482
- * }
17483
- * if (n2 < 0.5) {
17484
- * yield* Effect.fail(new ValidationError())
17485
- * }
17486
- * return "some result"
17487
- * })
17488
- *
17489
- * // ┌─── Effect<string, ValidationError, never>
17490
- * // ▼
17491
- * const recovered = program.pipe(
17492
- * Effect.catchIf(
17493
- * // Only handle HttpError errors
17494
- * (error) => error._tag === "HttpError",
17495
- * () => Effect.succeed("Recovering from HttpError")
17496
- * )
17497
- * )
17498
- * ```
17499
- *
17500
- * @since 2.0.0
17501
- * @category Error handling
17502
- */
17503
- const catchIf = catchIf$1;
17504
- /**
17505
17405
  * Catches and handles specific errors by their `_tag` field, which is used as a
17506
17406
  * discriminator.
17507
17407
  *
@@ -22675,79 +22575,6 @@ const updateContext = updateContext$1;
22675
22575
  //#endregion
22676
22576
  //#region node_modules/effect/dist/esm/Schedule.js
22677
22577
  /**
22678
- * Creates a schedule that recurs indefinitely with exponentially increasing
22679
- * delays.
22680
- *
22681
- * **Details**
22682
- *
22683
- * This schedule starts with an initial delay of `base` and increases the delay
22684
- * exponentially on each repetition using the formula `base * factor^n`, where
22685
- * `n` is the number of times the schedule has executed so far. If no `factor`
22686
- * is provided, it defaults to `2`, causing the delay to double after each
22687
- * execution.
22688
- *
22689
- * @since 2.0.0
22690
- * @category Constructors
22691
- */
22692
- const exponential = exponential$1;
22693
- /**
22694
- * Combines two schedules, continuing only if both schedules want to continue,
22695
- * using the longer delay.
22696
- *
22697
- * **Details**
22698
- *
22699
- * This function takes two schedules and creates a new schedule that only
22700
- * continues execution if both schedules allow it. The interval between
22701
- * recurrences is determined by the longer delay between the two schedules.
22702
- *
22703
- * The output of the resulting schedule is a tuple containing the outputs of
22704
- * both schedules. The input type is the intersection of both schedules' input
22705
- * types.
22706
- *
22707
- * This is useful when coordinating multiple scheduling conditions where
22708
- * execution should proceed only when both schedules permit it.
22709
- *
22710
- * @see {@link intersectWith} If you need to use a custom merge function.
22711
- *
22712
- * @since 2.0.0
22713
- * @category Composition
22714
- */
22715
- const intersect = intersect$1;
22716
- /**
22717
- * Returns a new schedule that randomly adjusts the interval size within a
22718
- * range.
22719
- *
22720
- * **Details**
22721
- *
22722
- * This function modifies a schedule so that its delay between executions is
22723
- * randomly varied within a range. By default, the delay is adjusted between
22724
- * `80%` (`0.8 * interval`) and `120%` (`1.2 * interval`) of the original
22725
- * interval size.
22726
- *
22727
- * This is useful for adding randomness to repeated executions, reducing
22728
- * contention in distributed systems, and avoiding synchronized execution
22729
- * patterns that can cause bottlenecks.
22730
- *
22731
- * @see {@link jitteredWith} If you need to specify custom min/max values.
22732
- *
22733
- * @since 2.0.0
22734
- * @category Timing & Delay
22735
- */
22736
- const jittered = jittered$1;
22737
- /**
22738
- * A schedule that recurs a fixed number of times before terminating.
22739
- *
22740
- * **Details**
22741
- *
22742
- * This schedule will continue executing until it has been stepped `n` times,
22743
- * after which it will stop. The output of the schedule is the current count of
22744
- * recurrences.
22745
- *
22746
- * @category Constructors
22747
- * @since 2.0.0
22748
- */
22749
- const recurs = recurs$1;
22750
- /**
22751
22578
  * Returns a schedule that recurs continuously, with each repetition
22752
22579
  * spaced by the specified `duration` from the last run.
22753
22580
  *
@@ -192377,7 +192204,7 @@ const ERRORS_MD_PATHS = /* @__PURE__ */ new Set([".gtd/ERRORS.md", "ERRORS.md"])
192377
192204
  * there. An unresolvable `ref` (an empty repo) yields an empty map, so every
192378
192205
  * candidate reads as "not at `ref`".
192379
192206
  */
192380
- const blobsAtRef = (exec, ref, paths) => exec("git", "ls-tree", "-r", "-z", ref, "--", ...paths).pipe(catchIf((e) => !isIndexLockError(e), () => succeed$3("")), map$4((out) => {
192207
+ const blobsAtRef = (exec, ref, paths) => exec("git", "ls-tree", "-r", "-z", ref, "--", ...paths).pipe(catchAll(() => succeed$3("")), map$4((out) => {
192381
192208
  const blobs = /* @__PURE__ */ new Map();
192382
192209
  for (const entry of splitNul(out)) {
192383
192210
  const tab = entry.indexOf(" ");
@@ -192403,14 +192230,14 @@ const blobsAtRef = (exec, ref, paths) => exec("git", "ls-tree", "-r", "-z", ref,
192403
192230
  * A symlink is the one residual inexactness: hash-object hashes the target's
192404
192231
  * content rather than the link text git stores, and reads as `M`, never `D`.
192405
192232
  */
192406
- const hashObjects = (exec, paths) => exec("git", "hash-object", "--", ...paths).pipe(map$4((out) => new Map(out.trim().split("\n").map((oid, i) => [paths[i] ?? "", oid.trim()]))), catchIf((e) => !isIndexLockError(e), () => succeed$3(/* @__PURE__ */ new Map())));
192233
+ const hashObjects = (exec, paths) => exec("git", "hash-object", "--", ...paths).pipe(map$4((out) => new Map(out.trim().split("\n").map((oid, i) => [paths[i] ?? "", oid.trim()]))), catchAll(() => succeed$3(/* @__PURE__ */ new Map())));
192407
192234
  /**
192408
192235
  * Classify every untracked path against `ref`'s tree, by CONTENT: not at
192409
192236
  * `ref` → `A`; different bytes → `M`; identical bytes → no entry. The latter
192410
- * two exist because "untracked" does not mean "new" — the review window's
192411
- * `git reset --mixed <base>` leaves every file the reviewed range ADDED
192412
- * untracked while it's still present at the window's saved head. "Different
192413
- * bytes" goes through `hashObjects`'s clean filters, matching how git itself
192237
+ * two exist because "untracked" does not mean "new": against a `ref` older
192238
+ * than HEAD, a path can be absent from the index yet already present in
192239
+ * `ref`'s tree with the same or different bytes. "Different bytes" goes
192240
+ * through `hashObjects`'s clean filters, matching how git itself
192414
192241
  * decides it, so an untouched `text=auto` file is never over-reported `M`.
192415
192242
  */
192416
192243
  const classifyUntracked = (exec, ref, untrackedPaths) => gen(function* () {
@@ -192430,34 +192257,12 @@ const classifyUntracked = (exec, ref, untrackedPaths) => gen(function* () {
192430
192257
  });
192431
192258
  });
192432
192259
  /**
192433
- * True when `error` is git's `index.lock` contention failure. gtd shares one
192434
- * worktree index with every git-aware tool the reviewer runs, each of which
192435
- * writes the index to refresh its stat cache whenever the review window's
192436
- * `git reset --mixed` wakes it — a pure "couldn't start" failure, so the
192437
- * losing command is safe to retry verbatim.
192438
- */
192439
- const isIndexLockError = (error) => /index\.lock[\s\S]*File exists|Another git process seems to be running/i.test(error.message);
192440
- const withIndexLockRetry = (eff) => retry(eff, {
192441
- schedule: intersect(recurs(6), exponential(millis(10), 2)).pipe(jittered),
192442
- while: (error) => isIndexLockError(error)
192443
- });
192444
- /**
192445
- * Wrap every operation of a `GitOperations` implementation in
192446
- * `withIndexLockRetry`. The ONE place the retry is applied — both
192447
- * `GitService.Live` and the in-memory test layer build their service through
192448
- * this. Never feed a `strictGitOperations` Proxy through this: `Object.keys`
192449
- * on the Proxy only sees the overrides, not the full port.
192450
- */
192451
- const withIndexLockRetries = (ops) => Object.fromEntries(Object.entries(ops).map(([name, fn]) => [name, (...args) => withIndexLockRetry(fn(...args))]));
192452
- /**
192453
192260
  * Run a command and return its stdout — FAILING on a non-zero exit code with
192454
192261
  * the command line and stderr in the error message. `Command.string` alone
192455
192262
  * silently ignores exit codes, which used to make gtd report success on
192456
192263
  * rejected commits and lose files whose quoted paths broke a swallowed
192457
192264
  * `git add`. Callers that expect a probe to fail handle it with an explicit
192458
- * `catchAll`; `index.lock` contention is retried transparently
192459
- * (`withIndexLockRetries`, applied once to the whole port) before any
192460
- * caller's `catchAll` sees it.
192265
+ * `catchAll`.
192461
192266
  */
192462
192267
  const run = (root, ...args) => scoped$3(gen(function* () {
192463
192268
  const process = yield* (yield* CommandExecutor).start(make$8(...args).pipe(workingDirectory(root), stdout("pipe"), stderr("pipe")));
@@ -192486,7 +192291,7 @@ const makeGitImpl = (executor, root) => {
192486
192291
  readFileAtRef: (ref, path) => exec("git", "show", `${ref}:${path}`),
192487
192292
  changedPaths: (base) => gen(function* () {
192488
192293
  const ref = base ?? "HEAD";
192489
- const nameStatusOut = yield* exec("git", "diff", "--name-status", "-z", ref).pipe(catchIf((e) => !isIndexLockError(e), () => succeed$3("")));
192294
+ const nameStatusOut = yield* exec("git", "diff", "--name-status", "-z", ref).pipe(catchAll(() => succeed$3("")));
192490
192295
  const trackedPaths = parseNameStatus(splitNul(nameStatusOut));
192491
192296
  const untrackedRaw = yield* exec("git", "ls-files", "--others", "--exclude-standard", "-z");
192492
192297
  const untrackedPaths = splitNul(untrackedRaw);
@@ -192554,7 +192359,7 @@ const makeLiveEffect = gen(function* () {
192554
192359
  return makeGitImpl(executor, root);
192555
192360
  });
192556
192361
  var GitService = class GitService extends Tag("GitService")() {
192557
- static Live = effect$1(GitService, makeLiveEffect.pipe(map$4(withIndexLockRetries)));
192362
+ static Live = effect$1(GitService, makeLiveEffect);
192558
192363
  };
192559
192364
  //#endregion
192560
192365
  //#region src/RepoFiles.ts
@@ -193200,146 +193005,13 @@ const updateRef = (ref, hash) => `git update-ref ${shellQuote(ref)} ${shellQuote
193200
193005
  /** `git update-ref -d <ref>` — idempotent: deleting a missing ref is already a no-op in real git. */
193201
193006
  const deleteRef = (ref) => `git update-ref -d ${shellQuote(ref)}`;
193202
193007
  //#endregion
193203
- //#region src/OutcomeScript.ts
193204
- /** The plain-text twin of `printfLine`: substitutes `args` into `fmt`'s `%s` placeholders, in order. */
193205
- const renderFormat = (fmt, ...args) => {
193206
- let i = 0;
193207
- return fmt.replace(/%s/g, () => args[i++] ?? "");
193208
- };
193209
- /**
193210
- * A `printf '<fmt>' <args...>` bash statement — `args` are already-valid bash
193211
- * tokens (a variable reference like `"$from"`), never re-quoted here: a value
193212
- * reaches the script as a printf ARGUMENT, never interpolated into the format
193213
- * itself, so a subject or state name containing `%` can never be read as a
193214
- * conversion spec.
193215
- */
193216
- const printfLine = (fmt, args) => `printf ${shellQuote(fmt)} ${args.join(" ")}`;
193217
- const FMT_ABANDON_NOOP = "no gtd process is underway (resting at \"%s\") — nothing to abandon\n";
193218
- const FMT_ABANDONED = "abandoned the process resting at \"%s\" — HEAD is back at %s (\"%s\"), resting at \"%s\".\nEverything the process produced is kept as uncommitted changes (`git status`); discard them with `git checkout -- . && git clean -fd .gtd` for a clean tree.\n";
193219
- const FMT_RESTORED = "restored the retained history — HEAD is back at %s (\"%s\"), resting at \"%s\". Resume with the loop, or `git reset` to any earlier turn to restart from there.\n";
193220
- /** `no gtd process is underway (resting at "<initial>") — nothing to abandon` — `gtd abandon`'s no-op plain-text line. */
193221
- const abandonNoopText = (initial) => renderFormat(FMT_ABANDON_NOOP, initial);
193222
- /**
193223
- * The bash preamble every outcome-carrying script includes, ONE block (no
193224
- * blank lines) so `assembleScript`'s blank-line-joined sections stay intact.
193225
- * The `${TERM:-}` form (never a bare `$TERM`) is deliberate: `TERM` is
193226
- * legitimately unset in plenty of real shells, and a bare reference would
193227
- * trip `set -u` in the embedding script.
193228
- */
193229
- const OUTCOME_PREAMBLE = [
193230
- "# gtd: human-facing outcome rendering (see src/OutcomeScript.ts)",
193231
- "if [ -t 1 ] && [ -z \"${NO_COLOR:-}\" ] && [ \"${TERM:-}\" != dumb ]; then",
193232
- " gtd_c_reset=$(printf '\\033[0m'); gtd_c_bold=$(printf '\\033[1m'); gtd_c_dim=$(printf '\\033[2m')",
193233
- " gtd_c_cyan=$(printf '\\033[36m'); gtd_c_green=$(printf '\\033[32m')",
193234
- " gtd_m_trans=\"➡️\"; gtd_m_commit=\"✅\"; gtd_m_ellipsis=\"…\"",
193235
- "else",
193236
- " gtd_c_reset=\"\"; gtd_c_bold=\"\"; gtd_c_dim=\"\"",
193237
- " gtd_c_cyan=\"\"; gtd_c_green=\"\"",
193238
- " gtd_m_trans=\"->\"; gtd_m_commit=\"[commit]\"; gtd_m_ellipsis=\"...\"",
193239
- "fi",
193240
- "gtd_files() {",
193241
- " gtd_files_sha=\"$1\"",
193242
- " gtd_files_shown=0",
193243
- " gtd_files_total=0",
193244
- " gtd_files_list=\"$(git diff-tree --no-commit-id --name-only -r --root \"$gtd_files_sha\" 2>/dev/null || true)\"",
193245
- " [ -z \"$gtd_files_list\" ] && { unset gtd_files_sha gtd_files_f gtd_files_shown gtd_files_total gtd_files_list; return 0; }",
193246
- " while IFS= read -r gtd_files_f; do [ -n \"$gtd_files_f\" ] && gtd_files_total=$((gtd_files_total + 1)); done <<EOF",
193247
- "$gtd_files_list",
193248
- "EOF",
193249
- " while IFS= read -r gtd_files_f; do",
193250
- " [ -z \"$gtd_files_f\" ] && continue",
193251
- " if [ \"$gtd_files_shown\" -ge 3 ]; then",
193252
- " printf ' %s%s (%d more)%s\\n' \"$gtd_c_dim\" \"$gtd_m_ellipsis\" \"$((gtd_files_total - 3))\" \"$gtd_c_reset\"",
193253
- " break",
193254
- " fi",
193255
- " printf ' %s%s%s\\n' \"$gtd_c_dim\" \"$gtd_files_f\" \"$gtd_c_reset\"",
193256
- " gtd_files_shown=$((gtd_files_shown + 1))",
193257
- " done <<EOF",
193258
- "$gtd_files_list",
193259
- "EOF",
193260
- " unset gtd_files_sha gtd_files_f gtd_files_shown gtd_files_total gtd_files_list",
193261
- "}",
193262
- "gtd_report_transition() {",
193263
- " gtd_rt_from=\"$1\"",
193264
- " gtd_rt_to=\"$2\"",
193265
- " printf '%s %s%s → %s%s\\n' \"$gtd_m_trans\" \"${gtd_c_bold}${gtd_c_cyan}\" \"$gtd_rt_from\" \"$gtd_rt_to\" \"$gtd_c_reset\"",
193266
- " gtd_files HEAD",
193267
- " unset gtd_rt_from gtd_rt_to",
193268
- "}",
193269
- "gtd_report_commit() {",
193270
- " gtd_rc_subject=\"$1\"",
193271
- " printf '%s %s%s%s\\n' \"$gtd_m_commit\" \"$gtd_c_green\" \"$gtd_rc_subject\" \"$gtd_c_reset\"",
193272
- " gtd_files HEAD",
193273
- " unset gtd_rc_subject",
193274
- "}",
193275
- "gtd_report_note() {",
193276
- " printf '%s\\n' \"$1\"",
193277
- "}",
193278
- "gtd_report_abandoned() {",
193279
- " gtd_ra_from=\"$1\"",
193280
- " gtd_ra_head=\"$2\"",
193281
- " gtd_ra_state=\"$3\"",
193282
- " gtd_ra_short=\"$(git rev-parse --short \"$gtd_ra_head\")\"",
193283
- " gtd_ra_subject=\"$(git log -1 --format=%s \"$gtd_ra_head\")\"",
193284
- ` ${printfLine(FMT_ABANDONED, [
193285
- "\"$gtd_ra_from\"",
193286
- "\"$gtd_ra_short\"",
193287
- "\"$gtd_ra_subject\"",
193288
- "\"$gtd_ra_state\""
193289
- ])}`,
193290
- " unset gtd_ra_from gtd_ra_head gtd_ra_state gtd_ra_short gtd_ra_subject",
193291
- "}",
193292
- "gtd_report_restored() {",
193293
- " gtd_rr_to=\"$1\"",
193294
- " gtd_rr_state=\"$2\"",
193295
- " gtd_rr_short=\"$(git rev-parse --short \"$gtd_rr_to\")\"",
193296
- " gtd_rr_subject=\"$(git log -1 --format=%s \"$gtd_rr_to\")\"",
193297
- ` ${printfLine(FMT_RESTORED, [
193298
- "\"$gtd_rr_short\"",
193299
- "\"$gtd_rr_subject\"",
193300
- "\"$gtd_rr_state\""
193301
- ])}`,
193302
- " unset gtd_rr_to gtd_rr_state gtd_rr_short gtd_rr_subject",
193303
- "}"
193304
- ].join("\n");
193305
- /** `gtd_report_transition <from> <to>` — a landed self-loop/target-change transition, with its changed-file rows. */
193306
- const transitionOutcome = (from, to) => `gtd_report_transition ${shellQuote(from)} ${shellQuote(to)}`;
193307
- /** `gtd_report_commit <subject>` — a bare capture (a self-loop commit), with its changed-file rows. */
193308
- const commitOutcome = (subject) => `gtd_report_commit ${shellQuote(subject)}`;
193309
- /** `gtd_report_note <text>` — one already-rendered plain line, no marker. */
193310
- const noteOutcome = (text) => `gtd_report_note ${shellQuote(text)}`;
193311
- /** `gtd_report_abandoned <from> <head> <state>` — resolves the post-hoc short hash/subject from `head` (a commitish) in-script. */
193312
- const abandonedOutcome = (from, head, state) => `gtd_report_abandoned ${shellQuote(from)} ${shellQuote(head)} ${shellQuote(state)}`;
193313
- /** `gtd abandon`'s no-op outcome — the same wording `abandonNoopText` renders, printed via `gtd_report_note`. */
193314
- const abandonNoopOutcome = (initial) => noteOutcome(abandonNoopText(initial));
193315
- /** `gtd_report_restored <to> <state>` — resolves the post-hoc short hash/subject from `to` (a commitish) in-script. */
193316
- const restoredOutcome = (to, state) => `gtd_report_restored ${shellQuote(to)} ${shellQuote(state)}`;
193317
- //#endregion
193318
193008
  //#region src/Emit.ts
193319
193009
  /**
193320
- * Closes the time-of-check/time-of-use gap between the CLI resolving
193321
- * `expectedHead` at decide time and the script running later in an unrelated
193322
- * process. A plain `[ ... ] || { ...; exit 1; }`, not `if`/`fi`, so it stays
193323
- * safe under `set -e` (an OR list's left side failing never trips `-e` on its
193324
- * own). Exported so `src/testing/EmittedScriptRecognizer.ts` can re-derive
193325
- * and string-compare this exact shape.
193326
- *
193327
- * The probe is `--verify --quiet`, not bare `git rev-parse HEAD` — load-
193328
- * bearing: against an unborn HEAD (no commits), the bare form prints the
193329
- * literal string `"HEAD"` and exits 128, so it could never match an
193330
- * `expectedHead === ""` comparison. `--verify --quiet` reads an unborn HEAD
193331
- * back as an empty string instead.
193332
- */
193333
- const headAssertion = (expectedHead) => {
193334
- const q = shellQuote(expectedHead);
193335
- return `[ "$(git rev-parse --verify --quiet HEAD 2>/dev/null)" = ${q} ] || { printf 'gtd: repository changed since this script was generated (expected HEAD %s) — re-run gtd\\n' ${q} >&2; exit 1; }`;
193336
- };
193337
- /**
193338
193010
  * `resolveValidateScript`'s guard: when the declared steering file is absent
193339
193011
  * (e.g. before the producing agent has written it at all), there's nothing
193340
193012
  * to format or validate, so the script exits 0 cleanly rather than running
193341
- * the mode's commands against a missing file. An OR list, not `if`, for the
193342
- * same `set -eu` safety `headAssertion` documents.
193013
+ * the mode's commands against a missing file. An OR list, not `if`: an OR
193014
+ * list's left side failing never trips `set -e` on its own.
193343
193015
  */
193344
193016
  const fileExistsGuard = (file) => `[ -f ${shellQuote(file)} ] || exit 0`;
193345
193017
  /**
@@ -193360,66 +193032,15 @@ const failurePromptWrapper = (command, prompt) => {
193360
193032
  `fi`
193361
193033
  ].join("\n");
193362
193034
  };
193363
- /**
193364
- * `gtd_retry` `eval`s one already-`shellQuote`d command, mirroring
193365
- * `src/Git.ts`'s `withIndexLockRetry`: retry only on the same two
193366
- * `isIndexLockError` substrings, jittered exponential backoff (~10ms
193367
- * doubling, 6 total attempts), propagating any other failure immediately.
193368
- * `awk` (not the shell — no fractional `sleep`, and no `$RANDOM` under POSIX
193369
- * `sh`/`dash`) computes both the fractional-second delay and its own jitter
193370
- * deterministically, so no external entropy source is needed. POSIX `sh` has
193371
- * no `local`, so every variable is `gtd_`-prefixed and `unset` before each
193372
- * `return` to avoid leaking into the rest of the assembled script.
193373
- */
193374
- const RETRY_HELPER = [
193375
- `gtd_retry() {`,
193376
- ` gtd_cmd=$1`,
193377
- ` gtd_attempt=1`,
193378
- ` gtd_delay_ms=10`,
193379
- ` while true; do`,
193380
- ` if gtd_out=$(eval "$gtd_cmd" 2>&1); then`,
193381
- ` [ -n "$gtd_out" ] && printf '%s\\n' "$gtd_out"`,
193382
- ` unset gtd_cmd gtd_attempt gtd_delay_ms gtd_out gtd_total_ms`,
193383
- ` return 0`,
193384
- ` fi`,
193385
- ` case "$gtd_out" in`,
193386
- ` *"index.lock"*|*"Another git process seems to be running"*) ;;`,
193387
- ` *)`,
193388
- ` printf '%s\\n' "$gtd_out" >&2`,
193389
- ` unset gtd_cmd gtd_attempt gtd_delay_ms gtd_out gtd_total_ms`,
193390
- ` return 1`,
193391
- ` ;;`,
193392
- ` esac`,
193393
- ` if [ "$gtd_attempt" -ge 6 ]; then`,
193394
- ` printf '%s\\n' "$gtd_out" >&2`,
193395
- ` unset gtd_cmd gtd_attempt gtd_delay_ms gtd_out gtd_total_ms`,
193396
- ` return 1`,
193397
- ` fi`,
193398
- ` gtd_total_ms=$(awk -v attempt="$gtd_attempt" -v ms="$gtd_delay_ms" 'BEGIN { jitter = (attempt * 2654435761) % ms + 1; printf "%.3f", (ms + jitter) / 1000 }')`,
193399
- ` sleep "$gtd_total_ms"`,
193400
- ` gtd_delay_ms=$(( gtd_delay_ms * 2 ))`,
193401
- ` gtd_attempt=$(( gtd_attempt + 1 ))`,
193402
- ` done`,
193403
- `}`
193404
- ].join("\n");
193405
193035
  const renderStep = (step) => {
193406
- if (step.kind === "gitWrite") return `gtd_retry ${shellQuote(step.command)}`;
193407
193036
  if (step.kind === "command" && step.onFailure !== void 0) return failurePromptWrapper(step.command, step.onFailure);
193408
193037
  return step.command;
193409
193038
  };
193410
- const assembleScript = (preconditions, steps) => {
193411
- if (steps.length === 0) return "";
193412
- const sections = ["set -eu"];
193413
- if (preconditions.expectedHead !== void 0) sections.push(headAssertion(preconditions.expectedHead));
193414
- if (steps.some((step) => step.kind === "gitWrite")) sections.push(RETRY_HELPER);
193415
- if (steps.some((step) => step.kind === "outcome")) sections.push(OUTCOME_PREAMBLE);
193416
- sections.push(...steps.map(renderStep));
193417
- return sections.join("\n\n");
193418
- };
193039
+ const assembleScript = (steps) => steps.length === 0 ? "" : ["set -eu", ...steps.map(renderStep)].join("\n\n");
193419
193040
  /** Build both halves from already-rendered command strings; an empty (or omitted) array produces the empty-string result, so a driver checking `if [ -n "$required" ]` never has to special-case a bare preamble. */
193420
- const emitScripts = (preconditions, required = [], optional = []) => ({
193421
- required: assembleScript(preconditions, required),
193422
- optional: assembleScript(preconditions, optional)
193041
+ const emitScripts = (required = [], optional = []) => ({
193042
+ required: assembleScript(required),
193043
+ optional: assembleScript(optional)
193423
193044
  });
193424
193045
  /** Exported so `src/testing/EmittedScriptRecognizer.ts` can recognize these by exact string comparison. `printf`, not `echo`, avoids shell-specific backslash-escape quirks. */
193425
193046
  const DID_NOT_RUN_COMMENT = "# gtd emitted this and did NOT run it — pipe it into `sh` to land the turn";
@@ -193444,6 +193065,66 @@ const combinedScript = (required, optional) => {
193444
193065
  ].join("\n");
193445
193066
  };
193446
193067
  //#endregion
193068
+ //#region src/OutcomeScript.ts
193069
+ /** The plain-text twin of `printfLine`: substitutes `args` into `fmt`'s `%s` placeholders, in order. */
193070
+ const renderFormat = (fmt, ...args) => {
193071
+ let i = 0;
193072
+ return fmt.replace(/%s/g, () => args[i++] ?? "");
193073
+ };
193074
+ /**
193075
+ * A `printf '<fmt>' <args...>` bash statement — `args` are already-valid bash
193076
+ * tokens (a `shellQuote`d literal, a `"$(git …)"` substitution), never
193077
+ * re-quoted here: a value reaches the script as a printf ARGUMENT, never
193078
+ * interpolated into the format itself, so a subject or state name containing
193079
+ * `%` can never be read as a conversion spec. Markers like `->` are arguments
193080
+ * too, keeping every format string free of a leading `-` that a shell's
193081
+ * `printf` could mistake for an option. A real newline in `fmt` is emitted as
193082
+ * the two-character `\n` escape `printf` itself expands, keeping every emitted
193083
+ * statement on one line.
193084
+ */
193085
+ const printfLine = (fmt, args) => `${OUTCOME_MARKER}\nprintf ${shellQuote(fmt.replace(/\n/g, "\\n"))} ${args.join(" ")}`;
193086
+ /**
193087
+ * Marks an emitted block as print-only. Load-bearing beyond documentation:
193088
+ * `src/testing/EmittedScriptRecognizer.ts` recognizes an outcome block by
193089
+ * this line, and a bare `printf` would be ambiguous with a workflow
193090
+ * `script:` command that happens to start the same way.
193091
+ */
193092
+ const OUTCOME_MARKER = "# gtd: outcome (print-only)";
193093
+ const FMT_ABANDON_NOOP = "no gtd process is underway (resting at \"%s\") — nothing to abandon\n";
193094
+ const FMT_ABANDONED = "abandoned the process resting at \"%s\" — HEAD is back at %s (\"%s\"), resting at \"%s\".\nEverything the process produced is kept as uncommitted changes (`git status`); discard them with `git checkout -- . && git clean -fd .gtd` for a clean tree.\n";
193095
+ const FMT_RESTORED = "restored the retained history — HEAD is back at %s (\"%s\"), resting at \"%s\". Resume with the loop, or `git reset` to any earlier turn to restart from there.\n";
193096
+ /** `no gtd process is underway (resting at "<initial>") — nothing to abandon` — `gtd abandon`'s no-op plain-text line. */
193097
+ const abandonNoopText = (initial) => renderFormat(FMT_ABANDON_NOOP, initial);
193098
+ /** A landed self-loop/target-change transition. */
193099
+ const transitionOutcome = (from, to) => printfLine("%s %s → %s\n", [
193100
+ "'->'",
193101
+ shellQuote(from),
193102
+ shellQuote(to)
193103
+ ]);
193104
+ /** A bare capture (a self-loop commit). */
193105
+ const commitOutcome = (subject) => printfLine("%s %s\n", ["'[commit]'", shellQuote(subject)]);
193106
+ /** One already-rendered plain line. */
193107
+ const noteOutcome = (text) => printfLine("%s\n", [shellQuote(text)]);
193108
+ /**
193109
+ * Resolves the post-hoc short hash/subject from `head` (a commitish) in-script
193110
+ * — this line runs AFTER the reset that made `head` the new HEAD, so neither
193111
+ * value is knowable when gtd generates it.
193112
+ */
193113
+ const abandonedOutcome = (from, head, state) => printfLine(FMT_ABANDONED, [
193114
+ shellQuote(from),
193115
+ `"$(git rev-parse --short ${shellQuote(head)})"`,
193116
+ `"$(git log -1 --format=%s ${shellQuote(head)})"`,
193117
+ shellQuote(state)
193118
+ ]);
193119
+ /** `gtd abandon`'s no-op outcome — the same wording `abandonNoopText` renders. */
193120
+ const abandonNoopOutcome = (initial) => noteOutcome(abandonNoopText(initial));
193121
+ /** Resolves the post-hoc short hash/subject from `to` (a commitish) in-script, for the same reason `abandonedOutcome` does. */
193122
+ const restoredOutcome = (to, state) => printfLine(FMT_RESTORED, [
193123
+ `"$(git rev-parse --short ${shellQuote(to)})"`,
193124
+ `"$(git log -1 --format=%s ${shellQuote(to)})"`,
193125
+ shellQuote(state)
193126
+ ]);
193127
+ //#endregion
193447
193128
  //#region src/Edge.ts
193448
193129
  const EMPTY_TREE$1 = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
193449
193130
  const subjectOf = (message) => (message.split("\n")[0] ?? "").trim();
@@ -193608,10 +193289,7 @@ const walkProcessBoundary = (history, initialState, includeClosingBoundary) => {
193608
193289
  * would pool across them). HEAD itself being such a boundary yields an EMPTY
193609
193290
  * run (`trace: []`).
193610
193291
  *
193611
- * `head` overrides the literal `HEAD` the walk would otherwise end at:
193612
- * `restAt`'s window-aware branch passes the saved-head hash here while a
193613
- * review window is open, so the trace still covers commits a real `git log`
193614
- * would miss with HEAD rewound to the review base.
193292
+ * `head` overrides the literal `HEAD` the walk would otherwise end at.
193615
193293
  *
193616
193294
  * `includeClosingBoundary` (default `false`) is `summaryRun`'s one-flag
193617
193295
  * difference from `currentRun`'s ordinary walk: when set AND the walk's very
@@ -193979,15 +193657,8 @@ const renderDecision = (rest, decision, cost, model) => {
193979
193657
  command: commitAll(withCostTrailer(decision.subject, cost, model))
193980
193658
  }, commitDecisionOutcome(decision)];
193981
193659
  };
193982
- /**
193983
- * The `EmitPreconditions` a step's/entry's assembled scripts assert against:
193984
- * `expectedHead` is the resolved HEAD hash the `Rest` snapshot was taken at.
193985
- */
193986
- const buildPreconditions = (rest) => ({ expectedHead: rest.context.currentCommit });
193987
193660
  /** The `EmittedScripts` a `"commit"` `StepPlan` carries alongside `perform` — built from `renderDecision`'s output. */
193988
- const buildStepScripts = (rest, decision, cost, model) => {
193989
- return emitScripts(buildPreconditions(rest), renderDecision(rest, decision, cost, model));
193990
- };
193661
+ const buildStepScripts = (rest, decision, cost, model) => emitScripts(renderDecision(rest, decision, cost, model));
193991
193662
  /**
193992
193663
  * A no-op is TERMINAL only at a `script` rest: gtd rendered the script, the
193993
193664
  * driver ran it, it left nothing any pattern claims, and re-running it can't
@@ -194095,17 +193766,16 @@ const planEntry = (rest, actor, entry) => gen(function* () {
194095
193766
  base = resolvedBase.right;
194096
193767
  }
194097
193768
  const subject = stateSubject(actor, entryState);
194098
- const message = withEntryTrailers(subject, {
194099
- ...base !== void 0 ? { base } : {},
194100
- vars: varOverrides
194101
- });
194102
193769
  return {
194103
193770
  kind: "entry",
194104
193771
  state: entryState,
194105
193772
  subject,
194106
- scripts: emitScripts(buildPreconditions(rest), [{
193773
+ scripts: emitScripts([{
194107
193774
  kind: "gitWrite",
194108
- command: commitAll(message)
193775
+ command: commitAll(withEntryTrailers(subject, {
193776
+ ...base !== void 0 ? { base } : {},
193777
+ vars: varOverrides
193778
+ }))
194109
193779
  }, {
194110
193780
  kind: "outcome",
194111
193781
  command: commitOutcome(subject)
@@ -205944,7 +205614,7 @@ const landFields = (input) => ({
205944
205614
  const renderLandJson = (fields) => JSON.stringify(fields) + "\n";
205945
205615
  const FMT_NOOP = "nothing to do at \"%s\"\n";
205946
205616
  const FMT_LAND_PROSE = "commit everything with this message: %s\n";
205947
- /** `nothing to do at "<state>"` — a no-op step's plain-text line, and the text a print-only script's `gtd_report_note` carries. */
205617
+ /** `nothing to do at "<state>"` — a no-op step's plain-text line, and the text a print-only script's own `printf` carries. */
205948
205618
  const noopText = (state) => renderFormat(FMT_NOOP, state);
205949
205619
  /** `commit everything with this message: <subject>` — plain `gtd land`'s own stdout at a pending diff (no script); `--json` keeps emitting the script itself, unaffected. */
205950
205620
  const landProseText = (subject) => renderFormat(FMT_LAND_PROSE, subject);
@@ -206654,7 +206324,6 @@ const runInitCommand = (out) => gen(function* () {
206654
206324
  */
206655
206325
  const normalizeScriptNewline = (script) => script.length === 0 ? script : `${script.replace(/\n+$/, "")}\n`;
206656
206326
  const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
206657
- const headPreconditions = (currentCommit) => ({ expectedHead: currentCommit });
206658
206327
  /** True for a `"commit"` decision that is an ATTEMPT (`PatternMachine.StepCommit.attempt`) — the one flag `enforceStepGuards` bypasses its own steps for (see its call site). */
206659
206328
  const isAttemptDecision = (decision) => decision.kind === "commit" && decision.attempt === true;
206660
206329
  /** The subject the required script will produce. */
@@ -206665,7 +206334,7 @@ const previewSubject = (decision) => succeed$3(decision.subject);
206665
206334
  * landing script — that's a driver contract now (`gtd next --json`'s
206666
206335
  * `validate` field), not a gtd guarantee (see `resolveSelfValidateCommand`).
206667
206336
  */
206668
- const buildRequiredScript = (rest, decision, cost, model) => succeed$3(emitScripts(headPreconditions(rest.context.currentCommit), renderDecision(rest, decision, cost, model)).required);
206337
+ const buildRequiredScript = (rest, decision, cost, model) => succeed$3(emitScripts(renderDecision(rest, decision, cost, model)).required);
206669
206338
  /**
206670
206339
  * Decide the one resulting transition (a commit) for `gtd land`
206671
206340
  * without performing it, authenticating as `rest.actor`. Refusals fail the
@@ -206677,7 +206346,7 @@ const planLanding = (opts = {}) => gen(function* () {
206677
206346
  const plan = yield* planStep(rest, opts);
206678
206347
  if (plan.kind === "refusal") return yield* fail$2(new Error(plan.message));
206679
206348
  if (plan.kind === "noop") {
206680
- const required = emitScripts({}, [{
206349
+ const required = emitScripts([{
206681
206350
  kind: "outcome",
206682
206351
  command: noteOutcome(noopText(plan.state))
206683
206352
  }]).required;
@@ -206814,7 +206483,7 @@ const runAbandonCommand = (out) => gen(function* () {
206814
206483
  const initial = initialStateOf(def);
206815
206484
  const run = yield* currentRun;
206816
206485
  if (run.trace.length === 0) {
206817
- const required = emitScripts({}, [{
206486
+ const required = emitScripts([{
206818
206487
  kind: "outcome",
206819
206488
  command: abandonNoopOutcome(initial)
206820
206489
  }]).required;
@@ -206823,11 +206492,10 @@ const runAbandonCommand = (out) => gen(function* () {
206823
206492
  }
206824
206493
  const restState = run.trace[run.trace.length - 1].state;
206825
206494
  if (run.startParentHash === EMPTY_TREE) return yield* fail$2(/* @__PURE__ */ new Error(`gtd abandon: the process underway (resting at "${restState}") starts at the repository's first commit — there is no earlier commit to rewind to`));
206826
- const tip = yield* git.resolveRef("HEAD");
206827
- const steps = [
206495
+ const required = emitScripts([
206828
206496
  {
206829
206497
  kind: "gitWrite",
206830
- command: updateRef(HISTORY_REF, tip)
206498
+ command: updateRef(HISTORY_REF, yield* git.resolveRef("HEAD"))
206831
206499
  },
206832
206500
  {
206833
206501
  kind: "gitWrite",
@@ -206837,8 +206505,7 @@ const runAbandonCommand = (out) => gen(function* () {
206837
206505
  kind: "outcome",
206838
206506
  command: abandonedOutcome(restState, run.startParentHash, initial)
206839
206507
  }
206840
- ];
206841
- const required = emitScripts(headPreconditions(tip), steps).required;
206508
+ ]).required;
206842
206509
  out.write(combinedScript(required, ""));
206843
206510
  });
206844
206511
  /**
@@ -206858,7 +206525,7 @@ const runRestoreCommand = (out) => gen(function* () {
206858
206525
  const check = yield* restorability(git, headHash, tip);
206859
206526
  if (!check.ok) return yield* fail$2(/* @__PURE__ */ new Error(`gtd restore: ${check.reason} — HEAD ${headHash.slice(0, 7)} is ahead of the retained tip ${tip.slice(0, 7)}.`));
206860
206527
  const after = yield* restAt(tip);
206861
- const steps = [
206528
+ const required = emitScripts([
206862
206529
  {
206863
206530
  kind: "gitWrite",
206864
206531
  command: hardResetTo(tip)
@@ -206871,8 +206538,7 @@ const runRestoreCommand = (out) => gen(function* () {
206871
206538
  kind: "outcome",
206872
206539
  command: restoredOutcome(tip, after.state)
206873
206540
  }
206874
- ];
206875
- const required = emitScripts(headPreconditions(headHash), steps).required;
206541
+ ]).required;
206876
206542
  out.write(combinedScript(required, ""));
206877
206543
  });
206878
206544
  /**
@@ -206996,18 +206662,17 @@ const resolveValidateScript = (rest) => gen(function* () {
206996
206662
  if (file === void 0 || mode === void 0) return void 0;
206997
206663
  const resolved = resolveSteeringMode(rest.def, mode);
206998
206664
  if (resolved === void 0) return yield* fail$2(new Error(unknownModeMessage(rest.def, rest.state, mode)));
206999
- const steps = [
207000
- ...yield* modeContradictionSteps(resolved, mode, rest.context),
207001
- {
207002
- kind: "command",
207003
- command: fileExistsGuard(file)
207004
- },
207005
- ...yield* renderSteeringModeCommandSteps(resolved, file, rest.context)
207006
- ];
207007
206665
  return {
207008
206666
  file,
207009
206667
  mode,
207010
- script: emitScripts(headPreconditions(rest.context.currentCommit), steps).required
206668
+ script: emitScripts([
206669
+ ...yield* modeContradictionSteps(resolved, mode, rest.context),
206670
+ {
206671
+ kind: "command",
206672
+ command: fileExistsGuard(file)
206673
+ },
206674
+ ...yield* renderSteeringModeCommandSteps(resolved, file, rest.context)
206675
+ ]).required
207011
206676
  };
207012
206677
  });
207013
206678
  /** The driver-facing `BeatKind` for a currently-resolved rest, the one computation `gatherBeatFields` reads — so a driver's `kind` field can never drift from what it assembled. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "9.14.1",
3
+ "version": "10.0.0",
4
4
  "private": false,
5
5
  "description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
6
6
  "bin": {