@pmelab/gtd 9.14.1 → 10.1.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 +14 -9
- package/dist/gtd.bundle.mjs +300 -484
- 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
|
|
@@ -256,14 +256,19 @@ coding agent:
|
|
|
256
256
|
```
|
|
257
257
|
|
|
258
258
|
It prints a self-contained briefing that teaches the agent to set the project
|
|
259
|
-
up, build
|
|
259
|
+
up, build four commands for itself in whatever shell or runtime it runs, and ask
|
|
260
260
|
you what you want before it starts driving. You get one prompt to paste, not a
|
|
261
|
-
state name to choose. The
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
261
|
+
state name to choose. The four commands: **`gtd-build`** drives beats until the
|
|
262
|
+
process rests; **`gtd-edit`** opens the steering file the process is waiting on
|
|
263
|
+
right now — falling back to `.gtd/TODO.md` when the resting state declares none;
|
|
264
|
+
**`gtd-review <commitish>`** starts a review round over that commitish and
|
|
265
|
+
drives it; **`gtd-fix`** enters the fix process and drives it. The
|
|
266
|
+
`.gtd/TODO.md` fallback is also how you begin: on a clean repository the edit
|
|
267
|
+
command opens the empty `.gtd/TODO.md`, and whatever you write there is the
|
|
268
|
+
first sketch the whole process gets planned from. As a final step, if it finds
|
|
269
|
+
an LSP-capable editor, the briefing also offers to wire up live diagnostics and
|
|
270
|
+
review actions in it — asking first and naming the exact file, and merging
|
|
271
|
+
rather than overwriting your editor's config.
|
|
267
272
|
|
|
268
273
|
### The workflow it ships with
|
|
269
274
|
|
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);
|
|
@@ -206235,6 +205905,24 @@ f="$(gtd next --json=file)"
|
|
|
206235
205905
|
f="\${f:-.gtd/TODO.md}"
|
|
206236
205906
|
mkdir -p "$(dirname "$f")"
|
|
206237
205907
|
exec "\${EDITOR:-vi}" "$f"`;
|
|
205908
|
+
const REVIEW_COMMAND = `#!/usr/bin/env sh
|
|
205909
|
+
set -eu
|
|
205910
|
+
GTD_BUILD=~/.local/bin/gtd-build
|
|
205911
|
+
if [ -z "\${1:-}" ]; then
|
|
205912
|
+
echo "usage: gtd-review <commitish>" >&2
|
|
205913
|
+
exit 2
|
|
205914
|
+
fi
|
|
205915
|
+
cd "$(git rev-parse --show-toplevel)"
|
|
205916
|
+
script="$(gtd --entry review-gate.check --var reviewBase="$1")"
|
|
205917
|
+
sh -c "$script"
|
|
205918
|
+
exec "$GTD_BUILD"`;
|
|
205919
|
+
const FIX_COMMAND = `#!/usr/bin/env sh
|
|
205920
|
+
set -eu
|
|
205921
|
+
GTD_BUILD=~/.local/bin/gtd-build
|
|
205922
|
+
cd "$(git rev-parse --show-toplevel)"
|
|
205923
|
+
script="$(gtd --entry fix-precheck)"
|
|
205924
|
+
sh -c "$script"
|
|
205925
|
+
exec "$GTD_BUILD"`;
|
|
206238
205926
|
const HEADER = () => `gtd ${GTD_VERSION$1} — driver protocol\n\ngtd decides and prints; the driver executes. gtd never runs git, never runs\nyour agent, never runs a check script, and never runs a steering mode's own\nformat:/validate: command either — that pair is text in the script gtd next\n--json's validate field (or gtd validate) prints, for the driver to run\nitself ahead of gtd land.\n`;
|
|
206239
205927
|
const BEAT_PROTOCOL = `
|
|
206240
205928
|
## The beat protocol
|
|
@@ -206412,14 +206100,14 @@ since moved refuses loudly instead of corrupting anything. Scripts are
|
|
|
206412
206100
|
re-runnable and assert their own HEAD precondition — a driver needs no retry
|
|
206413
206101
|
logic beyond "ask gtd again".
|
|
206414
206102
|
`;
|
|
206415
|
-
const
|
|
206416
|
-
## Building the user's
|
|
206103
|
+
const commandSuite = () => `
|
|
206104
|
+
## Building the user's command suite: interview first, then adapt
|
|
206417
206105
|
|
|
206418
|
-
The obligations above are the contract; the
|
|
206419
|
-
EXAMPLE of it. Do not copy
|
|
206420
|
-
INVESTIGATE first, then INTERVIEW, then build a
|
|
206421
|
-
ONE conversation, not
|
|
206422
|
-
that is not set up yet cannot be driven, and the
|
|
206106
|
+
The obligations above are the contract; the four command bodies below are one
|
|
206107
|
+
WORKED EXAMPLE of it. Do not copy them blindly, and do not guess the user's
|
|
206108
|
+
setup — INVESTIGATE first, then INTERVIEW, then build a suite shaped by both.
|
|
206109
|
+
Hold ONE conversation, not four: the setup steps below come first, because a
|
|
206110
|
+
repo that is not set up yet cannot be driven, and the suite-shape questions
|
|
206423
206111
|
follow in the same numbered list.
|
|
206424
206112
|
|
|
206425
206113
|
1. **Investigate the repository and ask the user what they want before
|
|
@@ -206438,29 +206126,59 @@ follow in the same numbered list.
|
|
|
206438
206126
|
uncommitted config is a pending change, so starting a process without
|
|
206439
206127
|
committing it first turns the config into the very first thing the
|
|
206440
206128
|
process plans against. Commit it, THEN start driving.
|
|
206441
|
-
4. **Which agent should run the turns?**
|
|
206442
|
-
|
|
206443
|
-
|
|
206444
|
-
|
|
206445
|
-
|
|
206446
|
-
|
|
206129
|
+
4. **Which agent should run the turns?** Probe \`PATH\` for the known
|
|
206130
|
+
coding-agent CLIs — \`claude\`, \`codex\`, \`gemini\`, \`cursor-agent\`,
|
|
206131
|
+
\`aider\`, \`opencode\`, \`amp\` — show the user which are actually
|
|
206132
|
+
installed, and ask which one to drive with (default: \`claude\`). Accept
|
|
206133
|
+
a name that was not on the list; the probe is a convenience, not a
|
|
206134
|
+
restriction. The chosen CLI's own flags then replace the \`claude\` lines
|
|
206135
|
+
in the reference body below — its session flags (obligation 5) and its
|
|
206136
|
+
permission model (question 5).
|
|
206137
|
+
5. **Under which permission model, and should the workflow's model hints be
|
|
206138
|
+
honored at all?** Fully autonomous turns (e.g.
|
|
206447
206139
|
\`--dangerously-skip-permissions\`), a sandbox, or the agent's default
|
|
206448
|
-
prompting — their risk tolerance, their call. Also whether the
|
|
206449
|
-
own \`model\` hints
|
|
206450
|
-
|
|
206451
|
-
|
|
206452
|
-
|
|
206453
|
-
|
|
206454
|
-
|
|
206140
|
+
prompting — their risk tolerance, their call. Also ask whether the
|
|
206141
|
+
workflow's own \`model\` hints (resolved in question 6 below) should be
|
|
206142
|
+
honored (default: yes) — answering no means dropping \`--model\` entirely
|
|
206143
|
+
from every prompt line and writing no model exports, so every turn runs
|
|
206144
|
+
on the chosen CLI's own default tier.
|
|
206145
|
+
6. **If model hints are honored, resolve them to real model identifiers.**
|
|
206146
|
+
The bundled workflow ships \`plannerModel: smart\` and \`coderModel: base\`.
|
|
206147
|
+
**Those are opaque hints, not model names** — gtd never interprets them,
|
|
206148
|
+
and passing \`--model smart\` to a real CLI fails on the first prompt
|
|
206149
|
+
beat. Derive the chosen CLI's own available models at install time — its
|
|
206150
|
+
\`--help\`, its docs, its own config — never a hardcoded table (a vendor
|
|
206151
|
+
renames a model and a baked-in list goes stale the day it ships). **When
|
|
206152
|
+
the heavier/cheaper mapping is not obvious, ask**: list the CLI's models
|
|
206153
|
+
and let the user pick one per tier — the heavier tier for
|
|
206154
|
+
\`plannerModel\` (triage, design, review), the cheaper tier for
|
|
206155
|
+
\`coderModel\` (build, fix). Write the resolved names as
|
|
206156
|
+
\`GTD_PLANNERMODEL\`/\`GTD_CODERMODEL\` exports at the top of \`gtd-build\`,
|
|
206157
|
+
wrapped in \`# gtd-install: model exports\` / \`# gtd-install: end\`
|
|
206158
|
+
markers — NOT into \`.gtdrc\`. They stay per machine and are never
|
|
206159
|
+
committed, so one person's model choice binds nobody else on the repo.
|
|
206160
|
+
Two consequences follow, both worth stating to the user: \`gtd-review\`
|
|
206161
|
+
and \`gtd-fix\` inherit these exports only because they \`exec\`
|
|
206162
|
+
\`gtd-build\` — anything that drives beats without going through
|
|
206163
|
+
\`gtd-build\` sees the raw \`smart\`/\`base\` hints and fails; and \`GTD_*\` is
|
|
206164
|
+
the highest-precedence config layer, so these exports silently win over
|
|
206165
|
+
a \`plannerModel\`/\`coderModel\` a teammate later commits to \`.gtdrc\`.
|
|
206455
206166
|
7. **What should happen at the boundaries?** Where the log goes (obligation
|
|
206456
206167
|
4), and whether halting at a human gate should do anything richer than
|
|
206457
206168
|
print — desktop notification, terminal-multiplexer status, editor focus —
|
|
206458
206169
|
which belongs in their wrapper, never in gtd.
|
|
206459
|
-
8. **
|
|
206460
|
-
a
|
|
206461
|
-
|
|
206462
|
-
|
|
206463
|
-
|
|
206170
|
+
8. **How do they want to invoke it?** A command on PATH (default:
|
|
206171
|
+
\`~/.local/bin/gtd-build\`), a project task-runner entry, a CI job step —
|
|
206172
|
+
or no artifact at all: YOU drive the beats yourself, following the
|
|
206173
|
+
obligations directly. Pick the runtime to match: bash, their language of
|
|
206174
|
+
choice, anything that reads \`--json=<path>\` and spawns subprocesses.
|
|
206175
|
+
9. **Where should the suite live, which editor should \`gtd-edit\` spawn, and
|
|
206176
|
+
does anything need renaming?** Suggest \`~/.local/bin\` for all four —
|
|
206177
|
+
\`gtd-build\`, \`gtd-edit\`, \`gtd-review\`, \`gtd-fix\` — and \`$EDITOR\` (never
|
|
206178
|
+
a hardcoded editor) for the one that opens files. The interview can
|
|
206179
|
+
rename any of the four; whatever name is chosen for \`gtd-build\` is the
|
|
206180
|
+
RESOLVED path baked into \`GTD_BUILD\` in the other two bodies below —
|
|
206181
|
+
never the literal string \`gtd-build\`.
|
|
206464
206182
|
|
|
206465
206183
|
Then build it, and verify safely before the first real drive:
|
|
206466
206184
|
\`gtd next --json=<path>\`/\`gtd next\` are both pure reads — read/parse them,
|
|
@@ -206468,20 +206186,20 @@ check your kind dispatch against the table above, call them as often as you
|
|
|
206468
206186
|
like. Nothing happens until you run an emitted script.
|
|
206469
206187
|
|
|
206470
206188
|
The reference rendering in sh (no \`jq\`, no JSON parser at all — each
|
|
206471
|
-
\`--json=<path>\` call prints its one value directly
|
|
206472
|
-
|
|
206189
|
+
\`--json=<path>\` call prints its one value directly). Two of the four bodies
|
|
206190
|
+
below share one convention: \`GTD_BUILD\` is set once, at the top, to the
|
|
206191
|
+
suite's resolved \`gtd-build\` path.
|
|
206192
|
+
|
|
206193
|
+
### \`gtd-build\` — the driver loop
|
|
206194
|
+
|
|
206195
|
+
The \`claude\` lines are what interview answers 4 and 6 replace. Default path:
|
|
206196
|
+
\`~/.local/bin/gtd-build\`.
|
|
206473
206197
|
|
|
206474
206198
|
\`\`\`bash
|
|
206475
206199
|
${MINIMAL_DRIVER}
|
|
206476
206200
|
\`\`\`
|
|
206477
|
-
`;
|
|
206478
|
-
const editCommand = () => `
|
|
206479
|
-
## Building the user's edit command
|
|
206480
206201
|
|
|
206481
|
-
|
|
206482
|
-
a second artifact to the same interview (questions 8–9 above): an edit
|
|
206483
|
-
command that opens the steering file the process is resting at right now,
|
|
206484
|
-
suggested at \`~/.local/bin/gtd-edit\`.
|
|
206202
|
+
### \`gtd-edit\` — open the steering file the process is resting at
|
|
206485
206203
|
|
|
206486
206204
|
\`gtd next --json=file\` already reports the file for every state that
|
|
206487
206205
|
declares one — the edit command needs no new engine surface, just a read and
|
|
@@ -206501,12 +206219,115 @@ the first sketch the whole process gets planned from. The same command does
|
|
|
206501
206219
|
the same thing at every rest, whether or not a state declares a \`file:\`.
|
|
206502
206220
|
|
|
206503
206221
|
The edit command stops when the editor exits — it never drives the loop. It
|
|
206504
|
-
opens a file and returns; the human runs
|
|
206505
|
-
|
|
206222
|
+
opens a file and returns; the human runs \`gtd-build\` themselves when they
|
|
206223
|
+
are ready. Default path: \`~/.local/bin/gtd-edit\`.
|
|
206506
206224
|
|
|
206507
206225
|
\`\`\`bash
|
|
206508
206226
|
${EDIT_COMMAND}
|
|
206509
206227
|
\`\`\`
|
|
206228
|
+
|
|
206229
|
+
### \`gtd-review <commitish>\` — start a review round and drive it
|
|
206230
|
+
|
|
206231
|
+
Runs \`gtd --entry review-gate.check --var reviewBase=<commitish>\`, captured
|
|
206232
|
+
by command substitution — never a pipe: a pipeline reports only its LAST
|
|
206233
|
+
command's exit status, so \`gtd --entry ... | sh\` under \`set -e\` would sail
|
|
206234
|
+
past a refusal and \`exec\` a loop over a process that was never started. It
|
|
206235
|
+
runs the captured script, then \`exec\`s the suite's RESOLVED \`gtd-build\`
|
|
206236
|
+
path — never the literal string \`gtd-build\`. This is one invocation that
|
|
206237
|
+
carries the review all the way to its next human gate, not just a starter:
|
|
206238
|
+
\`gtd-review\` on a RED baseline hands off to \`gtd-build\`, which halts at the
|
|
206239
|
+
blocked gate and prints it, rather than starting the review. Refuses with
|
|
206240
|
+
\`usage: gtd-review <commitish>\` on stderr and exit \`2\` when the commitish is
|
|
206241
|
+
missing — nothing was even attempted. Default path: \`~/.local/bin/gtd-review\`.
|
|
206242
|
+
|
|
206243
|
+
\`\`\`bash
|
|
206244
|
+
${REVIEW_COMMAND}
|
|
206245
|
+
\`\`\`
|
|
206246
|
+
|
|
206247
|
+
### \`gtd-fix\` — enter the fix process and drive it
|
|
206248
|
+
|
|
206249
|
+
Runs \`gtd --entry fix-precheck\`, captured the same way, then \`exec\`s the
|
|
206250
|
+
suite's RESOLVED \`gtd-build\` path. \`gtd-fix\` on a GREEN suite is a no-op
|
|
206251
|
+
straight back to \`idle\` — the exec'd \`gtd-build\` exits immediately on it.
|
|
206252
|
+
Default path: \`~/.local/bin/gtd-fix\`.
|
|
206253
|
+
|
|
206254
|
+
\`\`\`bash
|
|
206255
|
+
${FIX_COMMAND}
|
|
206256
|
+
\`\`\`
|
|
206257
|
+
${REINSTALL}`;
|
|
206258
|
+
const REINSTALL = `
|
|
206259
|
+
## Re-installing: detect and adapt, don't blindly overwrite
|
|
206260
|
+
|
|
206261
|
+
A second install on a machine that already has a suite is not a fresh
|
|
206262
|
+
install — read each of the four suite paths before writing anything
|
|
206263
|
+
(\`gtd-build\`, \`gtd-edit\`, \`gtd-review\`, \`gtd-fix\`), and branch per path:
|
|
206264
|
+
|
|
206265
|
+
- Absent — install it, as in a fresh install.
|
|
206266
|
+
- Present and content-equal to what this gtd version would emit — say so,
|
|
206267
|
+
change nothing, and skip that command's interview questions entirely.
|
|
206268
|
+
- Present and different — show the difference, name the likely cause (a gtd
|
|
206269
|
+
upgrade, or the user's own edit), and ask before overwriting. Never
|
|
206270
|
+
silently replace a file the user may have customised.
|
|
206271
|
+
- Unreadable, or present as a directory instead of a file — report it and
|
|
206272
|
+
ask about it, the same as a differing file. Never overwritten.
|
|
206273
|
+
|
|
206274
|
+
Drift is detected by comparing CONTENT, never by parsing a version out of
|
|
206275
|
+
the installed file — an installed command carries no version marker to
|
|
206276
|
+
parse.
|
|
206277
|
+
|
|
206278
|
+
\`gtd-build\` needs one exemption before that comparison: the region between
|
|
206279
|
+
\`# gtd-install: model exports\` and \`# gtd-install: end\` gets stripped from
|
|
206280
|
+
the installed file first. A \`gtd-build\` differing only inside those
|
|
206281
|
+
markers is unchanged, and re-asks nothing — the resolved model names are
|
|
206282
|
+
per machine, so without this exemption every single re-install would
|
|
206283
|
+
report drift on the one command everybody has.
|
|
206284
|
+
|
|
206285
|
+
This check scopes to exactly the four suite paths above. \`gtd-loop\` is
|
|
206286
|
+
outside it entirely — never read, never diffed, never touched by it in any
|
|
206287
|
+
way — an existing \`gtd-loop\` survives untouched beside the new
|
|
206288
|
+
\`gtd-build\`, and cleaning it up is the human's own call.
|
|
206289
|
+
`;
|
|
206290
|
+
const EDITOR_INTEGRATION = `
|
|
206291
|
+
## Editor integration: offer it, don't bury it
|
|
206292
|
+
|
|
206293
|
+
\`gtd lsp\` runs a language server, started by \`gtd lsp\` itself, speaking LSP
|
|
206294
|
+
over \`stdio\`, applying to files under \`.gtd/\`. That is the whole integration
|
|
206295
|
+
contract, identical for every editor — this briefing names no specific editor
|
|
206296
|
+
and carries no per-editor config recipe.
|
|
206297
|
+
|
|
206298
|
+
Find the user's editor from their OWN shell configuration, never a guess:
|
|
206299
|
+
check \`$EDITOR\`/\`$VISUAL\` first, then look for an alias or export in their
|
|
206300
|
+
shell rc files (\`.zshrc\`, \`.bashrc\`, \`.config/fish/config.fish\`). Ask the
|
|
206301
|
+
user outright when that turns up nothing or turns up more than one. When the
|
|
206302
|
+
shell configuration names no editor and the user names none either, say
|
|
206303
|
+
nothing and move on — don't pitch editor integration to somebody with no
|
|
206304
|
+
editor to integrate.
|
|
206305
|
+
|
|
206306
|
+
Once an editor is identified, look ITS OWN LSP configuration format up
|
|
206307
|
+
yourself — there is no bundled recipe for it. If it is LSP-capable, offer to
|
|
206308
|
+
wire \`gtd lsp\` up with a brief explanation (three or four lines, not a copy
|
|
206309
|
+
of \`docs/setup.md\`): live diagnostics on \`.gtd/\` steering files, an outline
|
|
206310
|
+
of what is left to review, click-to-check review hunks, pick-an-option
|
|
206311
|
+
actions on open questions, and a \`gtd.openSteeringFile\` command that jumps
|
|
206312
|
+
to the current state's file. Two facts belong in that offer because both
|
|
206313
|
+
bite on a fresh repo: \`gtd lsp\` never creates \`.gtd/\`, so
|
|
206314
|
+
\`gtd.openSteeringFile\` may point at a path that does not exist yet before
|
|
206315
|
+
the first sketch; and \`gtd lsp\` needs no repository root — it is one of the
|
|
206316
|
+
commands that skips the root guard.
|
|
206317
|
+
|
|
206318
|
+
On yes, edit the editor's own config file yourself — never print a snippet
|
|
206319
|
+
and walk away. Integration works without the human touching anything. Three
|
|
206320
|
+
guards apply to that write, because it lands outside the repository in a
|
|
206321
|
+
file the user shares across every project:
|
|
206322
|
+
|
|
206323
|
+
- **Ask first, per editor, naming the exact file** about to change. Silence
|
|
206324
|
+
is not consent for a machine-wide config.
|
|
206325
|
+
- **Merge, never overwrite.** Read the existing config, add only the gtd
|
|
206326
|
+
language-server entry, and leave every other key byte-identical.
|
|
206327
|
+
- **Skip and report when the entry is already present** — a second install
|
|
206328
|
+
must not append a duplicate server registration. A malformed existing
|
|
206329
|
+
config (unparseable JSON, a TOML syntax error) is a stop-and-report, not a
|
|
206330
|
+
rewrite: name the file that could not be parsed and leave it untouched.
|
|
206510
206331
|
`;
|
|
206511
206332
|
const PREREQUISITES = `
|
|
206512
206333
|
## Prerequisites and portability
|
|
@@ -206523,7 +206344,7 @@ const PREREQUISITES = `
|
|
|
206523
206344
|
Any runtime works: the contract is \`--json\`/\`--json=<path>\` in, subprocesses
|
|
206524
206345
|
out.
|
|
206525
206346
|
`;
|
|
206526
|
-
const renderBriefing = () => HEADER() + BEAT_PROTOCOL + JSON_FIELD_REFERENCE + DRIVER_OBLIGATIONS + RECOVERY +
|
|
206347
|
+
const renderBriefing = () => HEADER() + BEAT_PROTOCOL + JSON_FIELD_REFERENCE + DRIVER_OBLIGATIONS + RECOVERY + commandSuite() + EDITOR_INTEGRATION + PREREQUISITES;
|
|
206527
206348
|
//#endregion
|
|
206528
206349
|
//#region src/Select.ts
|
|
206529
206350
|
/**
|
|
@@ -206654,7 +206475,6 @@ const runInitCommand = (out) => gen(function* () {
|
|
|
206654
206475
|
*/
|
|
206655
206476
|
const normalizeScriptNewline = (script) => script.length === 0 ? script : `${script.replace(/\n+$/, "")}\n`;
|
|
206656
206477
|
const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
206657
|
-
const headPreconditions = (currentCommit) => ({ expectedHead: currentCommit });
|
|
206658
206478
|
/** 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
206479
|
const isAttemptDecision = (decision) => decision.kind === "commit" && decision.attempt === true;
|
|
206660
206480
|
/** The subject the required script will produce. */
|
|
@@ -206665,7 +206485,7 @@ const previewSubject = (decision) => succeed$3(decision.subject);
|
|
|
206665
206485
|
* landing script — that's a driver contract now (`gtd next --json`'s
|
|
206666
206486
|
* `validate` field), not a gtd guarantee (see `resolveSelfValidateCommand`).
|
|
206667
206487
|
*/
|
|
206668
|
-
const buildRequiredScript = (rest, decision, cost, model) => succeed$3(emitScripts(
|
|
206488
|
+
const buildRequiredScript = (rest, decision, cost, model) => succeed$3(emitScripts(renderDecision(rest, decision, cost, model)).required);
|
|
206669
206489
|
/**
|
|
206670
206490
|
* Decide the one resulting transition (a commit) for `gtd land`
|
|
206671
206491
|
* without performing it, authenticating as `rest.actor`. Refusals fail the
|
|
@@ -206677,7 +206497,7 @@ const planLanding = (opts = {}) => gen(function* () {
|
|
|
206677
206497
|
const plan = yield* planStep(rest, opts);
|
|
206678
206498
|
if (plan.kind === "refusal") return yield* fail$2(new Error(plan.message));
|
|
206679
206499
|
if (plan.kind === "noop") {
|
|
206680
|
-
const required = emitScripts(
|
|
206500
|
+
const required = emitScripts([{
|
|
206681
206501
|
kind: "outcome",
|
|
206682
206502
|
command: noteOutcome(noopText(plan.state))
|
|
206683
206503
|
}]).required;
|
|
@@ -206814,7 +206634,7 @@ const runAbandonCommand = (out) => gen(function* () {
|
|
|
206814
206634
|
const initial = initialStateOf(def);
|
|
206815
206635
|
const run = yield* currentRun;
|
|
206816
206636
|
if (run.trace.length === 0) {
|
|
206817
|
-
const required = emitScripts(
|
|
206637
|
+
const required = emitScripts([{
|
|
206818
206638
|
kind: "outcome",
|
|
206819
206639
|
command: abandonNoopOutcome(initial)
|
|
206820
206640
|
}]).required;
|
|
@@ -206823,11 +206643,10 @@ const runAbandonCommand = (out) => gen(function* () {
|
|
|
206823
206643
|
}
|
|
206824
206644
|
const restState = run.trace[run.trace.length - 1].state;
|
|
206825
206645
|
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 = [
|
|
206646
|
+
const required = emitScripts([
|
|
206828
206647
|
{
|
|
206829
206648
|
kind: "gitWrite",
|
|
206830
|
-
command: updateRef(HISTORY_REF,
|
|
206649
|
+
command: updateRef(HISTORY_REF, yield* git.resolveRef("HEAD"))
|
|
206831
206650
|
},
|
|
206832
206651
|
{
|
|
206833
206652
|
kind: "gitWrite",
|
|
@@ -206837,8 +206656,7 @@ const runAbandonCommand = (out) => gen(function* () {
|
|
|
206837
206656
|
kind: "outcome",
|
|
206838
206657
|
command: abandonedOutcome(restState, run.startParentHash, initial)
|
|
206839
206658
|
}
|
|
206840
|
-
];
|
|
206841
|
-
const required = emitScripts(headPreconditions(tip), steps).required;
|
|
206659
|
+
]).required;
|
|
206842
206660
|
out.write(combinedScript(required, ""));
|
|
206843
206661
|
});
|
|
206844
206662
|
/**
|
|
@@ -206858,7 +206676,7 @@ const runRestoreCommand = (out) => gen(function* () {
|
|
|
206858
206676
|
const check = yield* restorability(git, headHash, tip);
|
|
206859
206677
|
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
206678
|
const after = yield* restAt(tip);
|
|
206861
|
-
const
|
|
206679
|
+
const required = emitScripts([
|
|
206862
206680
|
{
|
|
206863
206681
|
kind: "gitWrite",
|
|
206864
206682
|
command: hardResetTo(tip)
|
|
@@ -206871,8 +206689,7 @@ const runRestoreCommand = (out) => gen(function* () {
|
|
|
206871
206689
|
kind: "outcome",
|
|
206872
206690
|
command: restoredOutcome(tip, after.state)
|
|
206873
206691
|
}
|
|
206874
|
-
];
|
|
206875
|
-
const required = emitScripts(headPreconditions(headHash), steps).required;
|
|
206692
|
+
]).required;
|
|
206876
206693
|
out.write(combinedScript(required, ""));
|
|
206877
206694
|
});
|
|
206878
206695
|
/**
|
|
@@ -206996,18 +206813,17 @@ const resolveValidateScript = (rest) => gen(function* () {
|
|
|
206996
206813
|
if (file === void 0 || mode === void 0) return void 0;
|
|
206997
206814
|
const resolved = resolveSteeringMode(rest.def, mode);
|
|
206998
206815
|
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
206816
|
return {
|
|
207008
206817
|
file,
|
|
207009
206818
|
mode,
|
|
207010
|
-
script: emitScripts(
|
|
206819
|
+
script: emitScripts([
|
|
206820
|
+
...yield* modeContradictionSteps(resolved, mode, rest.context),
|
|
206821
|
+
{
|
|
206822
|
+
kind: "command",
|
|
206823
|
+
command: fileExistsGuard(file)
|
|
206824
|
+
},
|
|
206825
|
+
...yield* renderSteeringModeCommandSteps(resolved, file, rest.context)
|
|
206826
|
+
]).required
|
|
207011
206827
|
};
|
|
207012
206828
|
});
|
|
207013
206829
|
/** 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. */
|