@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 +2 -2
- package/dist/gtd.bundle.mjs +112 -447
- package/package.json +1 -1
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
|
|
112
|
-
|
|
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
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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()]))),
|
|
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"
|
|
192411
|
-
*
|
|
192412
|
-
*
|
|
192413
|
-
*
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
193342
|
-
*
|
|
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 = (
|
|
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 = (
|
|
193421
|
-
required: assembleScript(
|
|
193422
|
-
optional: assembleScript(
|
|
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(
|
|
193773
|
+
scripts: emitScripts([{
|
|
194107
193774
|
kind: "gitWrite",
|
|
194108
|
-
command: commitAll(
|
|
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 `
|
|
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(
|
|
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
|
|
206827
|
-
const steps = [
|
|
206495
|
+
const required = emitScripts([
|
|
206828
206496
|
{
|
|
206829
206497
|
kind: "gitWrite",
|
|
206830
|
-
command: updateRef(HISTORY_REF,
|
|
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
|
|
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(
|
|
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. */
|