@pmelab/gtd 9.14.0 → 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 +94 -87
- package/dist/gtd.bundle.mjs +113 -448
- package/package.json +1 -1
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
|
*
|
|
@@ -191922,7 +191749,7 @@ const compileWorkflowConfig = (raw, configDir, rcModes, inlineFileRefs = true, f
|
|
|
191922
191749
|
};
|
|
191923
191750
|
//#endregion
|
|
191924
191751
|
//#region src/workflows/unified.yaml
|
|
191925
|
-
var unified_default = "# gtd's BUILT-IN DEFAULT workflow — used when no `workflow:` key is\n# configured anywhere in the cwd→home config chain; `gtd init` seeds only\n# vars.testCommand + a modes: suggestion, never this file. Compiled through\n# the same `compileWorkflowConfig` a user's own `workflow:` goes through — no\n# privileged path. See skills/authoring/SKILL.md for how to read or edit it\n# (machines, patterns, templates) and docs/driver.md for how a driver runs it.\n#\n# One flow: any change to the tree starts the process. `idle` -> `unwind`\n# reverts that diff (intent survives only in history) -> a green-baseline\n# gate -> `design.triage` groups the reverted diff into ordered, classified\n# concerns and resolves the PRODUCT ones' open questions (`design.gate`) ->\n# `architecture.author` works out the *how* and resolves the TECHNICAL ones\n# (`architecture.gate`) -> `architecture.decompose` writes one package file\n# per concern -> the per-package queue (`packages.*`) builds and reviews each\n# -> the shared tail (`build.review.*`) decides sign-off (-> `idle`, landing\n# an ordinary commit that closes the process — every per-turn commit stays on\n# the branch; run `gtd summary` afterward for a closing-message prompt) vs.\n# actionable feedback (-> `re-unwind` -> back to `design.triage`, a full\n# re-plan lap that never builds on the human's hand-edit). `--entry\n# review-gate.check --var reviewBase=<commitish>` and `--entry fix-precheck`\n# are the two other entries into this same flow.\nvars:\n testCommand: npm test\n # plannerModel: the heavier tier for one-shot triage/design/review turns.\n # coderModel: the tier for build/fix turns. Both repointable via a\n # top-level `vars:` key or a `GTD_<NAME>` env var, like any other var.\n plannerModel: smart\n coderModel: base\n # Declared (not left undeclared) so `--var reviewBase=...` passes the CLI's\n # declared-name check; blank renders empty, which the review entry refuses.\n reviewBase: \"\"\n # gtd's own voice for generated files — adapted from the \"Spartan\" output\n # style (https://github.com/alexgreensh/attention-span, AGPL-3.0, version 0.6),\n # rewritten in gtd's own words for deliverables rather than chat replies;\n # no upstream text ships. A point-in-time derivation with no refresh\n # mechanism.\n styleBlock: |-\n This file is a deliverable, not a chat reply — size follows the work, and\n padding is the target of any cut. Lead with the answer immediately; never\n circle back to restate it once it has landed.\n\n Write flat, commanding sentences — commit to the claim, do not hedge it.\n Favor everyday vocabulary; where a term is unavoidable, define it in five\n words or fewer on first use.\n\n In markdown, bold carries the load: bold the claim, not the sentence around\n it — reading only the bold text must yield the full point and every risk.\n\n Never trim a risk, a number, a threshold, or a scoped condition to save\n space. This outranks every other rule here.\n\n Ship the artifact bare — no lead-in, no sign-off wrapped around it.\n Compressing is not dropping: a task with three load-bearing parts ships all\n three, each shorter, never two of three.\n\n One idea per block; break when the idea shifts. Flag risk in one blunt\n line rather than hedged prose. Never narrate the work — do it.\n styleFormatContract: |-\n This file is machine-read; its format contract outranks every style rule\n above it: keep every `##`/`###` heading, every `- [ ]` checkbox row, and\n every marker line exactly as specified — do not renumber or rename a\n heading or replace a checkbox list with prose. A parser reads these\n literally; a violation refuses the turn outright.\n\n Voice rules above govern the prose between these elements, never the\n elements themselves.\n\n # One persona per prompt-bearing machine below, stamped as its `system:`\n # (see src/StateFields.ts's `system`). `--system-prompt` REPLACES a\n # harness's default system prompt — including its injected cwd/env/\n # git-status block — so each persona is written self-contained, restating\n # that context explicitly rather than relying on it.\n\n # Shared conduct tail, appended after every persona's own role paragraph:\n # use tools without asking, orient yourself with git, and go inspect what\n # the turn's message names. Deliberately NOT named `*Persona`:\n # `templates.test.ts` derives its six-name persona set from\n # `it\\.vars\\.(\\w+Persona)`, so that suffix would silently grow the set.\n agentConduct: |-\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. This runs unattended: there is\n no one to grant permission, confirm a command, or approve a plan before\n you act. Investigate with real commands rather than assuming what a file,\n a commit, or a decision's status actually is, then act on what you find.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. There is no injected\n status block — no cwd, no branch, no recent history, no tree state. Run\n `git status`, `git log`, `ls` and the like yourself before you do\n anything else; that is the normal first move of this turn, not an extra\n step.\n\n The message you receive each turn names a commit, a range, or a file for\n you to go inspect yourself — `git show`, `git diff`, reading the file\n directly — never a diff or a summary rendered into the conversation. Go\n look before you act. Across the several turns one conversation may span,\n work from what you already read and settled earlier rather than\n re-deriving it from a cold read every time, unless the turn's own\n instructions tell you this is a fresh, cold pass.\n designPersona: |-\n You are the product-facing planning voice in an autonomous build pipeline\n called gtd. Your job is to turn a raw, half-formed sketch — a hand-edit to\n real code, a scratch note, or both — into an ordered, classified list of\n concerns a build can execute, and to carry a running conversation with a\n human about the product decisions that list depends on. You are read by\n that human, so write plainly and decisively; you are not read by a parser\n except where a state's own instructions say otherwise.\n architectPersona: |-\n You are the technical planning voice in an autonomous build pipeline\n called gtd. You take over once the product-facing concerns are settled,\n reading them cold — you do not carry over the product conversation, only\n its written, settled output. Your job is to work out the technical *how*\n for each settled concern — file and module structure, data models,\n library and tech-stack choices, error-handling strategy — raise the\n technical open questions a human still needs to decide, and once every\n question is answered, write one package spec file per concern with no\n further judgement call: the grouping and ordering were already decided\n upstream, and this final step is a mechanical write-out of a plan you\n already converged on.\n reviewerPersona: |-\n You are the independent reviewing mind in an autonomous build pipeline\n called gtd — deliberately a separate identity from whichever agent wrote\n the code under review, so you bring no attachment to how it got built.\n Your job splits into two distinct turns: first, writing a structured\n review document that groups a diff into reviewable chunks for a human to\n read through and comment on; later, reading back a round of that human's\n feedback — a note, a hand-edit — and classifying whether it is genuinely\n actionable or just an approving remark, without fixing anything yourself\n either way. You judge and you classify; you never build.\n specReviewerPersona: |-\n You are the adversarial spec-conformance checker in an autonomous build\n pipeline called gtd, reviewing one freshly-built work package against the\n written spec it was built from. Your only job is to verify: are the\n package's tasks actually done, its acceptance criteria actually met, and\n the code sound and consistent with the rest of the codebase? You write\n concrete, actionable feedback only when something is genuinely wrong —\n and when nothing is, you write nothing at all, because a clean turn from\n you IS the approval. You never fix a problem you find; naming it\n precisely enough for someone else's fix turn to act on is the whole job.\n builderPersona: |-\n You are the TDD implementer in an autonomous build pipeline called gtd.\n Your job is to build exactly one package's declared scope end to end,\n tests first, and to come back and fix things when redirected: a failing\n check's output, or a reviewer's concrete feedback. Stay strictly inside\n the package in front of you, touching neither another package's files nor\n code outside the current task. Treat feedback you're given as the\n diagnosis to act on; discard it, with reason, only when it is simply\n wrong.\n finisherPersona: |-\n You are the closing identity in an autonomous build pipeline called gtd —\n the last coder this process hands work to before it closes. You are\n handed a late-breaking failing check after sign-off; fix it, focused and\n minimal, before the process can close. Every turn you land stays its own\n commit on the branch — nothing here gets squashed away.\n\n # The scratchpad-opener sentence shared by every prompt-content state (see\n # STATE_FIELDS's `prompt`) — this workflow's own state files are its private\n # working notes, never project code or documentation a human reads. Injected\n # as the leading line of every prompt body that touches a `.gtd/` file\n # directly; each state's own \"the only state file this turn touches is X\"\n # sentence stays local, immediately after the tag. Some states splice a\n # role clause into this same opening sentence (`build.review.collecting`,\n # `packages.item.spec.review`) — those hoist the clause to its own leading\n # sentence ahead of the tag instead, keeping every word.\n stateFileRules: |-\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation.\n # The open-questions warrant test, decide-it-yourself sink, and `## Open\n # Questions` checkbox shape shared by `design.triage` and\n # `architecture.author` — written phase-agnostic (no PRODUCT/TECHNICAL\n # bake-in) so both sites can inject it verbatim. Each site's own\n # phase-scope sentence (triage: product-only, technical waits; architecture:\n # every point here is TECHNICAL) stays local around the tag — the two\n # genuinely disagree on scope, so that sentence is never shared.\n # `questionBarReturn` is the return-lap half — split out so each site can\n # inject it under its own `## Return lap` heading instead of burying\n # return-lap behaviour inside the first lap's own section.\n questionBar: |-\n For every open point, a question is warranted only when all three hold:\n the answer is a genuine fork with divergent outcomes — user-visible for a\n product question, materially different implementations for a technical\n one; the sketch, the entry diff, and history do not already settle it\n (treat anything the entry diff states explicitly — a directive in\n `.gtd/TODO.md`, a committed choice in a hand-edit — as already settled,\n and on a review loop-back lap treat whatever the human's own review-round\n edit or note states explicitly the same way); and getting it wrong would\n survive all the way to the human review tail.\n\n Anything below that bar, decide yourself: record the decision under\n `## Answered Questions` — the same section a human-answered question\n lands in — as `### <the point phrased as a question>` followed by the\n decision and a one-line rationale in prose, no checkboxes.\n\n A question that clears the bar is written under a `## Open Questions`\n heading near the top and takes this shape: one `### <question>`\n sub-heading each, followed by a checkbox list of candidate answers — two\n concrete options plus a final free-text slot, all left unticked:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself — the human ticks exactly one per question.\n questionBarReturn: |-\n The human has ticked one option per question (or replaced\n `_your answer_` with their own text): fold each chosen answer into its\n concern's prose, then move the question into a `## Answered Questions`\n section as `### <question>` followed by the resolved answer in plain\n prose (drop the checkboxes). Never re-raise a deleted question; treat a\n deleted `## Open Questions` section as acceptance. Omit `## Open\n Questions` entirely once there are none left.\n # The body `packages.item.fix-suite` and `build.fix` share byte for byte —\n # both fix a red `.gtd/FEEDBACK.md`. `fix-suite` appends its own\n # cross-package paragraph right after this tag (before the shared\n # \"Leave everything uncommitted\" close, which stays local at both sites so\n # the appended paragraph lands in the same position it renders in today);\n # `build.fix` renders the tag alone.\n fixFeedbackPrompt: |-\n The only state file this turn touches is `.gtd/FEEDBACK.md`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `.gtd/FEEDBACK.md` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `.gtd/FEEDBACK.md` instead of \"fixing\" a non-issue.\n\n# `gtd summary`'s prompt — printed cold, no session identity, no diff inlined.\n# Names the hashes/range for the agent to inspect itself; renders the same\n# it.processCost/it.processCostByModel the old squash finale used to.\nsummary: |\n <%~ it.vars.styleBlock %>\n\n\n Write the closing message for the process HEAD closes or currently sits\n inside — the text a driver may use for a squash, an amend, or a pull\n request body. You are starting cold: read every decision back out of the\n commits named below rather than assuming any prior context.\n\n Cover the initial motivation, the important decisions, and the trade-offs\n made, and the high-level architectural changes — never which files\n changed; that is what `git diff --stat` is for, not this message.\n\n The process's entry commit is `<%= it.entryCommit %>`. <% if\n (it.humanCommits.length > 0) { %>The human contributed at these commits,\n oldest to newest:\n <% it.humanCommits.forEach(function (c) { %>\n - `<%= c.hash %>` (entering `<%= c.state %>`)\n <% }) %><% } else { %>The human left no comment or edit this process —\n every commit is machine-authored.<% } %>\n\n Inspect the range yourself: `git log <%= it.processBase %>..<%= it.processTip %>`\n and `git diff <%= it.processBase %> <%= it.processTip %>`. That range is\n exactly what a squash or PR body should describe.\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n\n Print the closing message and stop — this command writes nothing itself.\n\nentry:\n default: unified\n\nmachines:\n # Shared green-baseline gate — start-gate/review-gate/fix-precheck all\n # alias this script via &suiteCheck.\n entryGate:\n params: [onGreen, blockedMessage, blockedDescribe, checkLabel, blockedLabel, reviewBase]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: &suiteCheck |\n #!/usr/bin/env sh\n set +e\n mkdir -p .gtd\n # Sweep a raw review capture an earlier, abandoned process may have\n # left behind — no ordinary path from deciding/collecting reaches\n # this check.\n rm -f .gtd/REVIEW_RAW.md\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > .gtd/FEEDBACK.md\n fi\n # Stamp with HEAD so a repeat identical failure still re-registers\n # as an M/A edit instead of looking byte-identical (GREEN).\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n rm -f .gtd/FEEDBACK.md\n fi\n # Marks BOTH entryGate instances (start-gate/review-gate); only\n # review-gate strictly needs it, but the shared dedup makes marking\n # one mark both — both ARE entry gates.\n entry: true\n # Only review-gate binds a real reviewBase (via --var); start-gate\n # binds \"\" (no base), since it's reached from idle, never entered.\n reviewBase: $reviewBase\n on:\n \"A .gtd/FEEDBACK.md\": blocked\n \"M .gtd/FEEDBACK.md\": blocked\n \"D .gtd/REVIEW_RAW.md\": $onGreen\n \"D .gtd/FEEDBACK.md\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n file: FEEDBACK.md\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $blockedDescribe\n\n # Shared check/escalate pair for build.health.*/packages.item.health.* —\n # the caller's own coder fix-state ($onRed), not this machine, handles red.\n healthGate:\n params: [onGreen, onRed, escalateDescribe, checkLabel, escalateLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # See the shared suite check on `entryGate.check` above.\n script: *suiteCheck\n on:\n \"A .gtd/FEEDBACK.md\": $onRed\n \"M .gtd/FEEDBACK.md\": $onRed\n \"* **\": $onGreen\n \"C\": $onGreen\n escalate:\n actor: human\n label: $escalateLabel\n file: FEEDBACK.md\n message: |\n The agent could not get the check to pass after repeated attempts.\n `.gtd/FEEDBACK.md` holds the last failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $escalateDescribe\n\n # Shared check/answer pair for design.gate/architecture.gate. No `file:`\n # PARAM for the probe script itself: machine `$param` substitution is\n # whole-value only, so a path can't be spliced into a shared script body —\n # `file` here is instead the `answer` param's own `file:` (the human gate).\n questionGate:\n params: [file, message, onNone, onRevise, checkLabel, answerLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: |\n #!/usr/bin/env sh\n # Exactly one of REQUIREMENTS.md/ARCHITECTURE.md exists on disk at\n # a time — architecture.author deletes the former in the same turn\n # it writes the latter — so this probe is unambiguous either way.\n set +e\n mkdir -p .gtd\n file=.gtd/REQUIREMENTS.md\n [ -f \"$file\" ] || file=.gtd/ARCHITECTURE.md\n gtd check qa \"$file\" --open-questions > /dev/null 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n printf 'open questions remain in %s\\n' \"$file\" > .gtd/QUESTIONS.md\n # See the shared suite check's cache-buster rationale on\n # `entryGate.check` above.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/QUESTIONS.md\n else\n rm -f .gtd/QUESTIONS.md\n fi\n on:\n \"A .gtd/QUESTIONS.md\": answer\n \"M .gtd/QUESTIONS.md\": answer\n \"D .gtd/QUESTIONS.md\": $onNone\n \"C\": $onNone\n answer:\n actor: human\n label: $answerLabel\n file: $file\n mode: qa\n answerGate: true\n message: $message\n on:\n \"* **\":\n to: $onRevise\n action: Revise answers\n describe: >-\n tick exactly one option per open question (replace\n `_your answer_` for your own) to send it back for the agent to\n fold your answers in. Delete a question to skip it, or delete\n the whole `## Open Questions` section to accept the plan as-is.\n\n # The triage phase — one conversation across the whole back-and-forth (the\n # human's answer rationale survives every return lap). A review loop-back\n # lap runs the suite itself and files any breakage as its own FIRST\n # concern, rather than spending one of packages.item.fix-suite's\n # (per-episode) retries on breakage the package didn't cause.\n # ▸ planner identity.\n designPlan:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.designPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onArchitecture]\n entry: triage\n states:\n triage:\n actor: agent\n label: Triaging the change\n file: REQUIREMENTS.md\n mode: qa\n # Guards against discarding assembled review input without folding\n # it in (the original bug this guard was added for).\n requireProgress: true\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state file this turn touches is `.gtd/REQUIREMENTS.md`; develop it. Do\n not create other files to hold notes or output.\n\n `.gtd/TODO.md` is the likely home of the sketch that\n started this process. Despite living beside this workflow's own\n state files, it is the human's input — the sketch to be folded into\n the concerns below like any other part of the start diff. It is\n never a state file to preserve, and never gtd bookkeeping to ignore.\n\n This process started at commit `<%= it.startCommit %>`. The change\n that started it was reverted out of the working tree by the\n `unwind` beat right after it landed, so `git diff <%= it.startCommit %>`\n (against the working tree) is now empty — its content survives only\n in history. Find the entry commit yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n (the process's very first turn, landed immediately after\n `<%= it.startCommit %>`, before any later baseline-repair commits) —\n then `git show` that commit to see everything that started this\n process: a hand-edit to real code, a scratch note in some file, or\n both.\n\n Whichever lap this is, leave `.gtd/REQUIREMENTS.md` uncommitted and\n finish your turn once it reflects this lap's own work below.\n\n ## First lap\n\n `.gtd/REQUIREMENTS.md` does not exist yet (or holds whatever the\n human's own change happened to leave there). Build it from that\n start diff:\n\n ### Group into concerns\n\n Group the diff into an ordered list of concerns — each one a\n coherent piece of work that can leave the test suite green on\n its own, in the order you'd want them built. If a concern would\n red-light tests that a later concern is meant to fix, that is\n not a valid split: merge the two into one concern.\n\n Two more tests sit beside that green floor, not instead of it.\n Vertical, not horizontal: slice by capability, never by layer. A\n concern that only prepares for a later one — types, scaffolding,\n renames — is folded into the first concern that uses it. Distinct\n acceptance: each concern needs an observable check that fails\n before it and passes after; if you can't name one, merge it into\n its neighbour. Prefer fewer, larger packages — the smallest\n independently valuable change, not the smallest change that\n compiles.\n\n ### Classify each concern\n\n Classify each concern as PRODUCT (a user-facing/requirements\n decision) or TECHNICAL (an implementation decision).\n\n <%~ it.vars.questionBar %>\n\n\n Raise questions only for product concerns here — a technical\n concern's open points wait for the next phase. When every concern\n in this lap is TECHNICAL, write no `## Open Questions` section at\n all.\n\n ### Fold in the sketch\n\n Fold everything the entry commit added into the concerns above —\n a scratch note and a real code edit are both just a sketch of\n the intent, a spec to be finished, never work to be preserved;\n `unwind` already reverted both out of the tree, so there is\n nothing left to delete. Describe each piece under the concern it\n belongs to.\n\n ## Return lap\n\n <%~ it.vars.questionBarReturn %>\n\n ## Review loop-back\n\n `.gtd/REQUIREMENTS.md` already holds concerns and none of them\n have a ticked-but-unfolded answer waiting — that shape means a\n completed REVIEW round put this material here, not a question you\n asked. Develop those existing concerns further with whatever the\n round raised; never rediscover or regroup them from a cold read\n the way the first lap does.\n\n The human's own edit from that review round has been reverted out\n of the working tree the same way the entry commit is — read it\n from history, never the working tree: run\n `git show <%= it.reviewBase %>` to see it. (On the first lap that\n same hash is the process's own diff base, `<%= it.startCommit %>`,\n so this branch does not apply: `.gtd/REQUIREMENTS.md`\n does not exist yet for a review round to have happened against.)\n\n Whatever this round touches, every decision already settled under\n `## Answered Questions` stays settled on this lap too — never\n re-open one. A genuinely open PRODUCT point in the round's own\n material may still raise a fresh question under\n `## Open Questions`: the product gate sits on this path exactly as\n it does on the first lap, and will ask it.\n\n Before grouping anything on this lap, run\n `<%= it.vars.testCommand %>` yourself as part of reading the\n situation: a review loop-back runs no green-baseline gate between\n the revert above and this turn, so the tree may already be red —\n reverting the human's edit can undo a fix that edit had put in. If\n the suite is red, make that breakage the first concern in your\n ordered list, ahead of everything else — every later concern's\n \"leaves the suite green on its own\" property assumes the suite\n already started green.\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: REQUIREMENTS.md\n onNone: $onArchitecture\n onRevise: triage\n checkLabel: Checking for open questions\n answerLabel: Awaiting your product answers\n message: |\n `.gtd/REQUIREMENTS.md` holds the concerns under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n # A separate machine from designPlan (its own memory scope — a COLD read,\n # not a resumed conversation). Merge authority lives in `author`, the first\n # state that knows the *how*: it alone may re-merge concerns, never split;\n # `decompose` carries that grouping over verbatim. ▸ planner identity.\n archPlan:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.architectPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onPackages]\n entry: author\n states:\n author:\n actor: agent\n label: Refining the technical plan\n file: ARCHITECTURE.md\n mode: qa\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state files this\n turn touches are `.gtd/ARCHITECTURE.md` (write it) and\n `.gtd/REQUIREMENTS.md` (delete it once its content is\n folded in). Do not create other files to hold notes or output.\n\n You do not resume the design conversation — this is a separate\n machine with its own memory, a cold read every time. Read\n `.gtd/REQUIREMENTS.md` (the settled, ordered, classified\n concerns) in full. Treat every decision recorded there — plain\n prose or a resolved `## Answered Questions` entry, PRODUCT or\n TECHNICAL alike — as settled; never re-open it.\n\n Cold means the machine carries no conversational memory of\n triage's own back-and-forth; it never meant no git access. This\n process started at commit `<%= it.startCommit %>`. Find the\n process's first turn yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n then `git show` that commit to see everything that started this\n process: a hand-edit to real code, a scratch note in some file,\n or both.\n\n Whichever lap this is, once `.gtd/ARCHITECTURE.md` is written,\n delete `.gtd/REQUIREMENTS.md` — its content has been folded in and\n it must not linger. Leave everything uncommitted and finish your\n turn.\n\n ## First lap\n\n Develop `.gtd/ARCHITECTURE.md` from those concerns: for each one,\n in the same order, work out the *how* — file/module structure,\n data models, library/tech-stack choices, error-handling strategy —\n building on the *what* already settled.\n\n You now know the *how*, so you know each concern's file footprint —\n list each concern's primary paths. Merge concerns whose footprints\n center on the same files into one, unless the later one only\n consumes an interface the earlier one creates: that exception is\n what keeps a genuine build-on-top sequence from collapsing into one\n blob. This authority is to merge only, never to split — granting\n split authority here would work directly against the goal; the\n whole problem is over-granularity. Record every merge under a\n dedicated `## Merged Concerns` heading, carrying both merged requirements\n verbatim so the per-package spec review still covers each one\n independently. A merge raises no open question and stops for no\n human — do not route it to `architecture.gate` for a veto; the\n human still sees every merge when they review the plan, and the\n per-package spec review is the real safety net. Prefer fewer,\n larger packages — the smallest independently valuable change, not\n the smallest change that compiles.\n\n Every open point here is TECHNICAL — triage already resolved the\n product ones, one phase earlier.\n\n <%~ it.vars.questionBar %>\n\n ## Return lap\n\n <%~ it.vars.questionBarReturn %>\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: ARCHITECTURE.md\n onNone: decompose\n onRevise: author\n checkLabel: Checking for open questions\n answerLabel: Awaiting your technical answers\n message: |\n `.gtd/ARCHITECTURE.md` holds the technical plan under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n decompose:\n actor: agent\n label: Decomposing into packages\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state files this turn touches\n are the package files it writes under `.gtd/packages/` and\n `.gtd/ARCHITECTURE.md`, which it deletes. Do not create other\n files to hold notes or output.\n\n If you wrote `.gtd/ARCHITECTURE.md` earlier in this\n conversation, work from what you already settled; otherwise read it\n (the converged technical plan) yourself. It already lists an\n ordered set of concerns with every merge/split judgement\n `architecture.author` made — this turn is a mechanical write-out,\n not a planning one. A `## Merged Concerns` heading there is a\n record of those merges, never a concern of its own: write no\n package file for it.\n\n Write one package file per concern, in the settled order, under\n `.gtd/packages/` (e.g.\n `.gtd/packages/01-name.md`,\n `.gtd/packages/02-name.md`, ...). Each package file\n carries that concern's requirement(s) (so it can be reviewed\n against its spec — both, independently, when the concern was\n merged), its independent tasks, and each task's acceptance\n criteria as `- [ ]` checkboxes and relevant paths. Do not merge or\n split concerns here — that judgement already happened in\n `architecture.author`; carry the settled grouping over verbatim.\n No package file may reference any other `.gtd/` file.\n\n Once the package files are written, delete\n `.gtd/ARCHITECTURE.md`. Leave everything uncommitted and\n finish your turn.\n on:\n \"* .gtd/packages/**\": $onPackages\n\n # The shared review tail. ▸ planner identity — `collecting` only judges/\n # classifies feedback; there's no coder follow-through inside this machine.\n #\n # Nested inside buildTail (build.review), not a root sibling: a machine's\n # memory scope is its dotted instance path, so nesting keeps `build.fix ->\n # build.health.check -> build.review.*` (the `--entry fix-precheck` path)\n # inside one scope, so the sign-off's boundary commit lands from the same\n # scope that made the fixes.\n humanReview:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.reviewerPersona %>\n\n\n <%~ it.vars.agentConduct %>\n entry: reviewing\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: REVIEW.md\n mode: review\n # `gtd --entry review-gate.check` enters via review-gate.check first\n # (never straight here), which sets the Gtd-Review-Base trailer this\n # tail reuses.\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state file this turn touches is `.gtd/REVIEW.md`; write it at exactly\n that path. Do not create other files to hold notes or output.\n\n Write `.gtd/REVIEW.md` to help a human review the changes in\n this exact format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.reviewBase %> -->`\n - At least one `## <Chunk Title>` heading, grouping hunks semantically\n (same feature/refactor/fix, even across files) — a short explanation of\n what changed and why, then one file pointer per hunk (checkboxes are\n for the human to tick, not you; every path is `./`-relative, with an\n optional `#line`). Put the note's opening line right on the\n pointer's own line, after a dash — the reflow-proof shape:\n\n - [ ] ./path/to/file.ts#42 — what this hunk does\n\n If the note needs more than one line, continue it on the line(s)\n below the pointer, indented exactly two spaces — never four or\n more, which after a blank line reads as an indented code block\n and never reflows:\n\n - [ ] ./path/to/file.ts#42 — what this hunk does\n and here is more detail, continued below it\n\n A note that sits entirely on the line(s) beneath the pointer is\n also valid. Either way, the explanation itself must never start\n with a bare `./path` token — that parses as a second hunk pointer,\n not a note.\n\n This prompt carries no diff — read the changes yourself. The range runs\n from `<%= it.reviewBase %>` to the working tree (committed turns plus\n anything still pending); on a feedback round that base is the previous\n review's boundary, so it covers only what changed since then, not items\n already signed off.\n\n Leave `.gtd/REVIEW.md` uncommitted and finish your turn.\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n label: Awaiting your review\n file: REVIEW.md\n mode: review\n message: |\n `.gtd/REVIEW.md` holds the review record for the process — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off.\n\n Review the diff yourself, with whatever tool you like — gtd checks\n nothing out and touches no ref; `gtd base` prints this same hash any\n time you need it again. The range runs from the review base to the\n working tree:\n\n git diff <%= it.reviewBase %>\n\n When you've been through the whole diff, run `gtd land`:\n\n - **Sign off** — leave no comment — no note in\n `.gtd/REVIEW.md`, no code edit — to close the process,\n whatever the boxes say. Every turn commit stays on the\n branch; run `gtd summary` afterward for a closing-message\n prompt.\n - **Request changes** — leave a comment: a note on a\n `.gtd/REVIEW.md` line, or a direct code edit — to send a FULL\n development lap (**review.deciding** → **review.collecting** →\n re-triage). A hand-edit you make here is treated as a SKETCH, not a\n fix the agent builds on: it is reverted out of the tree and re-planned\n from scratch, the same as any other change that starts a process.\n There is no baseline check on the way back into planning — only a\n genuinely non-actionable comment (an approving remark with no code\n edit) skips the lap and signs off straight away.\n\n Deleting `.gtd/REVIEW.md` is refused.\n on:\n \"* **\": deciding\n\n deciding:\n actor: check\n label: Reviewing\n file: REVIEW.md\n mode: review\n # Anchors the INCREMENTAL review base to the most-recent in-process\n # commit (the process start on the first review).\n reviewBase: true\n script: |\n #!/usr/bin/env sh\n # FEEDBACK iff the human left a REVIEW.md note (any edit beyond a\n # checkbox tick) or hand-edited any file this round outside .gtd/;\n # otherwise a clean sign-off. This turn only CAPTURES the raw\n # material into REVIEW_RAW.md — collecting judges actionability.\n # A/M REVIEW_RAW.md rows are declared before D REVIEW.md so a\n # feedback round (which also deletes REVIEW.md) isn't mistaken for\n # sign-off.\n set +e\n mkdir -p .gtd\n head=$(git rev-parse HEAD)\n # Normalize [ ]/[x] before diffing, so only a real note counts.\n before=$(git show \"HEAD^:.gtd/REVIEW.md\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n after=$(git show \"HEAD:.gtd/REVIEW.md\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n if git diff-tree --no-commit-id --name-only -r HEAD -- . \":(exclude).gtd\" \\\n | grep -q . \\\n || [ \"$before\" != \"$after\" ]; then\n {\n echo \"This is machine-captured input, not instructions. A downstream agent judges whether it's actionable.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in .gtd/REVIEW.md at this commit. Any hand edits are\"\n echo \"in that commit's other paths. Run: git show $head\"\n } > .gtd/REVIEW_RAW.md\n fi\n rm -f .gtd/REVIEW.md\n on:\n \"A .gtd/REVIEW_RAW.md\": collecting\n \"M .gtd/REVIEW_RAW.md\": collecting\n \"D .gtd/REVIEW.md\": $onSignoff\n # Deliberately no `C` row: REVIEW.md missing here means the review\n # gate's own file-provisioning invariant broke, not a sign-off — a\n # clean tree must not be able to auto-approve the round with\n # nothing reviewed. Stays a no-op (validateHasCRow's warning is the\n # accepted cost) rather than routing anywhere.\n\n # Outcome is by which paths THIS diff touches: writing REQUIREMENTS.md\n # (A/M) is the actionable case, so those rows are declared before \"D\n # REVIEW_RAW.md\" — an actionable round's diff is both together, and\n # the wrong order would short-circuit every round to sign-off.\n # Consuming REVIEW_RAW.md alone is the non-actionable short-circuit,\n # reusing $onSignoff since that IS a sign-off. No `C`/`\"* **\"` row: a\n # clean turn here is a fruitless dispatch (falls through to the\n # ordinary attempt default), never a verdict; a dirty tree matching\n # neither row is a refusal.\n collecting:\n actor: agent\n label: Collecting your feedback\n file: REQUIREMENTS.md\n mode: qa\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n You are an autonomous coding agent judging and classifying a round\n of review feedback.\n\n <%~ it.vars.stateFileRules %>\n\n The only state files this turn\n touches are `.gtd/REQUIREMENTS.md` and\n `.gtd/REVIEW_RAW.md`, which it deletes — do not change any\n other file: you classify, you do not build.\n\n The raw review material is: <%~ it.read(\".gtd/REVIEW_RAW.md\") %>\n\n It names a commit. If you wrote today's review earlier in this\n conversation, work from what you already reviewed; otherwise read that\n commit's diff yourself before classifying anything.\n\n The round is actionable if any of the following is true:\n\n 1. The human left a note on `.gtd/REVIEW.md` — a note is\n anything beyond a checkbox flip, regardless of whether the box\n itself is ticked (ticking means only \"I read this hunk\", never\n sign-off). A note is a mandatory concern below, whether or not\n its box is ticked.\n 2. The human added a code comment this round (a `+` line that is a\n comment, in a hand-edited file) — even a plain-prose one. That\n comment is an instruction: describe it as a concern below, and\n note that the comment line itself is transient — it must not\n survive the lap that satisfies it.\n 3. The human hand-edited any non-comment code this round. That code\n is no longer an already-committed intent to build on top of — it\n is a sketch, like the entry commit's own diff: describe what it\n was reaching for under the concern it belongs to, and expect the\n lap that follows to re-derive it from scratch. Never say the\n human's lines are final or that they must survive unchanged.\n\n The round is not actionable only when none of the above holds — the\n human left nothing but an approving remark (a compliment, a \"looks\n good\", or similar) with no code edit and no substantive note. Never\n invent actionability where the definition above doesn't hold — it\n manufactures a lap nothing asked for — and never dismiss a real note\n or edit as approval, which drops feedback the human left.\n\n If the round is actionable: write `.gtd/REQUIREMENTS.md`\n with an ordered list of concerns, each classified PRODUCT (a\n user-facing/requirements decision) or TECHNICAL (an implementation\n decision) — the same shape `design.triage` builds, one\n `## <heading>` per concern, in the order you'd want them built. Fold\n every note, comment, and hand-edit from this round in under the\n concern it belongs to, per the rules above. Raise no open\n questions here — write no `## Open Questions` section;\n `design.triage` owns that, one phase later. Then delete\n `.gtd/REVIEW_RAW.md` and finish your turn.\n\n If the round is not actionable: delete\n `.gtd/REVIEW_RAW.md` and finish your turn, writing nothing\n to `.gtd/REQUIREMENTS.md` — consuming the capture with no\n other change is the sign-off.\n on:\n \"A .gtd/REQUIREMENTS.md\": $onFeedback\n \"M .gtd/REQUIREMENTS.md\": $onFeedback\n \"D .gtd/REVIEW_RAW.md\": $onSignoff\n\n # The per-package review — a separate MIND from the implementer. ▸ planner\n # identity; fixing findings is a coder action (packages.item.fix-spec).\n specReview:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.specReviewerPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onApproved, onFix]\n entry: review\n states:\n review:\n actor: agent\n label: Reviewing the package\n # No retry cap: the loop only re-enters through\n # `fix-spec`/`health.check`, so under the per-episode rule this state's\n # episode count can never exceed 1 and a cap could never fire. The\n # spec-review loop is genuinely unbounded — see docs/configuration.md's\n # `retry` documentation.\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n You are an autonomous coding agent reviewing a freshly-built work package\n against its own spec.\n\n <%~ it.vars.stateFileRules %>\n\n The only state file this turn touches is\n `.gtd/SPEC_FEEDBACK.md`; write it only when — and only\n when — you find problems. Do not create other files to hold notes or\n output.\n\n The package spec is: <%~ it.read(\".gtd/NEXT.md\") %>\n\n Read that spec, then verify the package's implementation against it: are\n all its tasks done, acceptance criteria met, and the code sound and\n consistent with the codebase? This prompt carries no diff — read it\n yourself: the range runs from `<%= it.startCommit %>` to the working\n tree.\n\n Note the range is process-wide (from the start of the whole build, not\n just this package), so it can span earlier packages too.\n\n - If it fully satisfies the spec, write nothing and finish your turn — a\n clean turn is your approval.\n - If it does not, write `.gtd/SPEC_FEEDBACK.md` listing the\n concrete problems for a fix turn to address. Be specific.\n\n Do not fix anything yourself and do not delete the package file — a later\n step owns that. Finish your turn.\n on:\n \"A .gtd/SPEC_FEEDBACK.md\": $onFix\n \"M .gtd/SPEC_FEEDBACK.md\": $onFix\n \"D .gtd/SPEC_FEEDBACK.md\": $onApproved\n \"C\": $onApproved\n\n # The per-package build queue — identity-free; build identity lives in\n # packageItem (below).\n packageLoop:\n params: [onDrained]\n entry: picking\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env sh\n # Mechanics only — NEXT.md's presence/absence is interpreted by\n # the `on` rows below, never here.\n set +e\n mkdir -p .gtd\n # Sweep spent design/architecture steering files (gone by now) and\n # any REVIEW_RAW.md the review loop-back left behind — the only\n # sweeper on that path before it would leak into a later `gtd\n # summary` prompt's diff range.\n rm -f .gtd/REQUIREMENTS.md .gtd/ARCHITECTURE.md .gtd/QUESTIONS.md .gtd/REVIEW_RAW.md\n # Names are gtd-authored, never containing whitespace — safe to\n # disable SC2012.\n # shellcheck disable=SC2012\n next=$(ls .gtd/packages/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n printf '%s' \"$next\" > .gtd/NEXT.md\n else\n rm -f .gtd/NEXT.md\n fi\n on:\n \"D .gtd/NEXT.md\": $onDrained\n \"* .gtd/NEXT.md\": item.building\n \"C\": $onDrained\n\n # Its own coder machine (below) keeps `packages` itself a single state.\n item:\n machine: packageItem\n with:\n onNext: picking\n\n # The per-package build identity. ▸ coder identity. packages.item.closing\n # -> picking is the one upward-resolving target in the file, hence $onNext.\n packageItem:\n model: <%= it.vars.coderModel %>\n system: |-\n <%~ it.vars.builderPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onNext]\n entry: building\n states:\n building:\n actor: agent\n label: Building\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n The only state file this\n turn may write is `.gtd/SATISFIED.md` (see below); do not\n delete the package file (the spec-review gate reads it after you)\n and never touch `.gtd/NEXT.md` — the `picking` state owns\n it.\n\n The package to implement is: <%~ it.read(\".gtd/NEXT.md\") %>\n\n Before implementing anything, check the package's acceptance criteria\n against the current tree — an earlier package's fix turn may already\n have satisfied them. If **every** criterion is already met, implement\n nothing: write `.gtd/SATISFIED.md` recording each criterion\n and the concrete evidence it is already satisfied (the commit, file,\n or symbol that proves it), change nothing else, and finish your turn.\n If any criterion is outstanding, implement the package normally and\n do not write that file.\n\n That file describes a set of independent tasks. Implement all of them in\n this one turn, fanning the independent tasks out to parallel subagents\n where your harness supports it — each task is self-contained and can be\n built concurrently. Implement exactly the tasks this package describes — no\n more, no less; leave every other package file untouched. Use TDD\n discipline within each task.\n\n Leave the package file in place and everything else uncommitted, then\n finish your turn.\n on:\n \"A .gtd/SATISFIED.md\": &satisfied\n to: health.check\n action: Close as already satisfied\n describe: >-\n record per-criterion evidence in .gtd/SATISFIED.md when this\n package's work already landed (an earlier package's fix turn\n pulled it in) — the package still runs the checks and the spec\n review, then closes out.\n \"M .gtd/SATISFIED.md\": *satisfied\n \"* **\": health.check\n\n fix-suite:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.fixFeedbackPrompt %>\n\n If the\n only way to green the suite is to implement work that another\n package file describes, make the smallest change that gets there and\n do it — that later package will then legitimately report itself\n already satisfied when its turn comes.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n fix-spec:\n actor: agent\n label: Fixing review feedback\n file: SPEC_FEEDBACK.md\n prompt: |\n <%~ it.vars.stateFileRules %>\n The only state file this\n turn touches is `.gtd/SPEC_FEEDBACK.md`; address it, then\n delete it. Do not create other files to hold notes or output.\n\n Read `.gtd/SPEC_FEEDBACK.md` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(\".gtd/NEXT.md\") %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `.gtd/SPEC_FEEDBACK.md` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": health.check\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env sh\n # Removes the just-reviewed package file (path in NEXT.md) plus\n # leftover spec feedback/evidence, so picking selects the next.\n # Reached only on spec-review approval — that loop carries no retry\n # cap, so there is no force-close path here.\n set +e\n pkg=$(cat .gtd/NEXT.md 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f .gtd/SPEC_FEEDBACK.md .gtd/NEXT.md .gtd/SATISFIED.md\n on:\n \"* **\": $onNext\n # Nothing left to sweep (an already-clean NEXT.md/package) still\n # needs to proceed to the next package, not stall here.\n \"C\": $onNext\n\n health:\n machine: healthGate\n with:\n onGreen: spec\n onRed: fix-suite\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**packages.item.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n spec:\n machine: specReview\n with:\n onApproved: closing\n onFix: fix-spec\n\n # The process's build identity. ▸ coder identity — model/system stamped\n # onto fix.\n buildTail:\n model: <%= it.vars.coderModel %>\n system: |-\n <%~ it.vars.finisherPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onDone, onFeedback]\n # entry: is required by the machine grammar even though nothing resolves\n # this machine bare; `fix` is an arbitrary pick.\n entry: fix\n states:\n fix:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.fixFeedbackPrompt %>\n\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n health:\n machine: healthGate\n with:\n onGreen: review\n onRed: fix\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**build.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # Nested here (not root) so the sign-off's boundary commit stays inside\n # the builder's own scope — see humanReview above.\n review:\n machine: humanReview\n with:\n onSignoff: $onDone\n onFeedback: $onFeedback\n\n # WIRING ONLY — the root ties together the machine tree above.\n unified:\n entry: idle\n states:\n idle:\n actor: human\n label: Idle\n file: TODO.md\n message: |\n No active gtd process.\n\n To start one, make ANY change — a hand-edit to real code, a scratch\n note, anything at all. .gtd/TODO.md is a good default\n place to start sketching. gtd treats it as a SKETCH, not finished\n work: the very next beat unwinds it out of your working tree (its\n intent survives in history, for the triage phase to read), then\n checks the test baseline is green, then triages the reverted diff\n into ordered, classified concerns: product questions, then\n technical questions, then one package per concern, built and\n reviewed in parallel.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: unwind\n action: Start\n describe: >-\n make any change — gtd unwinds it out of your working tree (its\n intent survives in history) before checking the test baseline is\n green, then triages the reverted diff into ordered, classified\n concerns and resolves product questions, then technical ones,\n before building each concern's package (**unwind** ->\n **start-gate.check**).\n\n # --entry fix-precheck: a red suite drops into build.fix (-> health ->\n # review tail); a green suite is a no-op back to idle.\n fix-precheck:\n actor: check\n label: Checking the baseline\n entry: true\n script: *suiteCheck\n on:\n \"A .gtd/FEEDBACK.md\": build.fix\n \"M .gtd/FEEDBACK.md\": build.fix\n \"D .gtd/REVIEW_RAW.md\": idle\n \"D .gtd/FEEDBACK.md\": idle\n \"C\": idle\n\n # Separate from start-gate.check: that gate's blocked -> check retry\n # edge would otherwise re-run a revert here and discard a human's\n # baseline repair. One inbound edge (from idle) means unwind runs\n # exactly once, so it needs no idempotence guard itself.\n unwind:\n actor: check\n label: Unwinding your input\n script: |\n #!/usr/bin/env sh\n set +e\n # Hoisted here, at the TOP: Eta's autoTrim eats the newline after\n # an interpolation tag, so no tag may be the last token on a line.\n # Uses it.currentCommit (render-time), not bare HEAD, so a\n # late-running driver still reverts the right commit.\n commit=\"<%~ it.currentCommit %>\"\n git revert --no-commit \"$commit\"\n on:\n \"* **\": start-gate\n # Deliberately no `C` row: `git revert --no-commit` under `set +e`\n # leaves a clean tree on either a genuine no-op OR a hard failure\n # (e.g. a merge commit with no `-m`) — the two are indistinguishable\n # from the diff alone, and advancing unconditionally would run the\n # baseline check against a tree that still carries the human's\n # un-reverted sketch. Stays a no-op (validateHasCRow's warning is\n # the accepted cost) rather than risking a false-clean baseline.\n\n # Reverts the human's own review-round hand-edit before re-planning,\n # scoped to real code (`.gtd/` excluded — REVIEW.md is already gone by\n # now). Names the commit via it.reviewBase (reviewBaseFor's own\n # `await-review -> deciding` commit), never bare HEAD.\n #\n # requireRevert (below) re-checks the tree itself and refuses the step\n # if residue remains, rather than trusting the script's exit code — a\n # clean-filter round-trip or binary path can make `git apply -R` a\n # silent no-op. Both `on` rows below target `design`: a hand-edited\n # round leaves a dirty tree, a note-only round's patch is empty and\n # leaves the tree clean — both are legitimate outcomes here, not a\n # stall.\n #\n # `file: REVIEW.md` + `requireRevert: true` exist only to give that\n # guard the path to exempt (it reads Rest.hints.file, never this\n # literal) — REVIEW.md is already gone two commits earlier, so this\n # state never renders it.\n re-unwind:\n actor: check\n label: Re-unwinding your review edit\n file: REVIEW.md\n requireRevert: true\n script: |\n #!/usr/bin/env sh\n # Scoped revert of the human's review-round edit — .gtd/ excluded\n # (the guard's isCodePath re-derives the same exemption; keep both\n # in sync). Expected to succeed; requireRevert catches a silent\n # apply failure.\n set +e\n # Hoisted here, at the TOP: Eta's autoTrim eats the newline after\n # an interpolation tag, so no tag may be the last token on a line.\n commit=\"<%~ it.reviewBase %>\"\n patch=.gtd/.re-unwind.patch\n mkdir -p .gtd\n git diff --binary \"$commit^\" \"$commit\" -- . \":(exclude).gtd\" > \"$patch\"\n if [ -s \"$patch\" ]; then\n git apply -R \"$patch\" || echo \"re-unwind: could not revert $commit\" >&2\n fi\n rm -f \"$patch\"\n on:\n \"* **\": design # a code hand-edit was reverted\n \"C\": design # note-only round: nothing to revert\n\n start-gate:\n machine: entryGate\n with:\n onGreen: design\n reviewBase: \"\"\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**start-gate.check**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run\n `gtd --entry fix-precheck` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n review-gate:\n machine: entryGate\n with:\n onGreen: build.review\n reviewBase: <%= it.vars.reviewBase %>\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**review-gate.check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n design:\n machine: designPlan\n with:\n onArchitecture: architecture\n\n architecture:\n machine: archPlan\n with:\n onPackages: packages\n\n packages:\n machine: packageLoop\n with:\n onDrained: build.review\n\n build:\n machine: buildTail\n with:\n onDone: idle\n onFeedback: re-unwind\n";
|
|
191752
|
+
var unified_default = "# gtd's BUILT-IN DEFAULT workflow — used when no `workflow:` key is\n# configured anywhere in the cwd→home config chain; `gtd init` seeds only\n# vars.testCommand + a modes: suggestion, never this file. Compiled through\n# the same `compileWorkflowConfig` a user's own `workflow:` goes through — no\n# privileged path. See skills/authoring/SKILL.md for how to read or edit it\n# (machines, patterns, templates) and docs/driver.md for how a driver runs it.\n#\n# One flow: any change to the tree starts the process. `idle` -> `unwind`\n# reverts that diff (intent survives only in history) -> a green-baseline\n# gate -> `design.triage` groups the reverted diff into ordered, classified\n# concerns and resolves the PRODUCT ones' open questions (`design.gate`) ->\n# `architecture.author` works out the *how* and resolves the TECHNICAL ones\n# (`architecture.gate`) -> `architecture.decompose` writes one package file\n# per concern -> the per-package queue (`packages.*`) builds and reviews each\n# -> the shared tail (`build.review.*`) decides sign-off (-> `idle`, landing\n# an ordinary commit that closes the process — every per-turn commit stays on\n# the branch; run `gtd summary` afterward for a closing-message prompt) vs.\n# actionable feedback (-> `re-unwind` -> back to `design.triage`, a full\n# re-plan lap that never builds on the human's hand-edit). `--entry\n# review-gate.check --var reviewBase=<commitish>` and `--entry fix-precheck`\n# are the two other entries into this same flow.\nvars:\n testCommand: npm test\n # plannerModel: the heavier tier for one-shot triage/design/review turns.\n # coderModel: the tier for build/fix turns. Both repointable via a\n # top-level `vars:` key or a `GTD_<NAME>` env var, like any other var.\n plannerModel: smart\n coderModel: base\n # Declared (not left undeclared) so `--var reviewBase=...` passes the CLI's\n # declared-name check; blank renders empty, which the review entry refuses.\n reviewBase: \"\"\n # gtd's own voice for generated files — adapted from the \"Spartan\" output\n # style (https://github.com/alexgreensh/attention-span, AGPL-3.0, version 0.6),\n # rewritten in gtd's own words for deliverables rather than chat replies;\n # no upstream text ships. A point-in-time derivation with no refresh\n # mechanism.\n styleBlock: |-\n This file is a deliverable, not a chat reply — size follows the work, and\n padding is the target of any cut. Lead with the answer immediately; never\n circle back to restate it once it has landed.\n\n Write flat, commanding sentences — commit to the claim, do not hedge it.\n Favor everyday vocabulary; where a term is unavoidable, define it in five\n words or fewer on first use.\n\n In markdown, bold carries the load: bold the claim, not the sentence around\n it — reading only the bold text must yield the full point and every risk.\n\n Never trim a risk, a number, a threshold, or a scoped condition to save\n space. This outranks every other rule here.\n\n Ship the artifact bare — no lead-in, no sign-off wrapped around it.\n Compressing is not dropping: a task with three load-bearing parts ships all\n three, each shorter, never two of three.\n\n One idea per block; break when the idea shifts. Flag risk in one blunt\n line rather than hedged prose. Never narrate the work — do it.\n styleFormatContract: |-\n This file is machine-read; its format contract outranks every style rule\n above it: keep every `##`/`###` heading, every `- [ ]` checkbox row, and\n every marker line exactly as specified — do not renumber or rename a\n heading or replace a checkbox list with prose. A parser reads these\n literally; a violation refuses the turn outright.\n\n Voice rules above govern the prose between these elements, never the\n elements themselves.\n\n # One persona per prompt-bearing machine below, stamped as its `system:`\n # (see src/StateFields.ts's `system`). `--system-prompt` REPLACES a\n # harness's default system prompt — including its injected cwd/env/\n # git-status block — so each persona is written self-contained, restating\n # that context explicitly rather than relying on it.\n\n # Shared conduct tail, appended after every persona's own role paragraph:\n # use tools without asking, orient yourself with git, and go inspect what\n # the turn's message names. Deliberately NOT named `*Persona`:\n # `templates.test.ts` derives its six-name persona set from\n # `it\\.vars\\.(\\w+Persona)`, so that suffix would silently grow the set.\n agentConduct: |-\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. This runs unattended: there is\n no one to grant permission, confirm a command, or approve a plan before\n you act. Investigate with real commands rather than assuming what a file,\n a commit, or a decision's status actually is, then act on what you find.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. There is no injected\n status block — no cwd, no branch, no recent history, no tree state. Run\n `git status`, `git log`, `ls` and the like yourself before you do\n anything else; that is the normal first move of this turn, not an extra\n step.\n\n The message you receive each turn names a commit, a range, or a file for\n you to go inspect yourself — `git show`, `git diff`, reading the file\n directly — never a diff or a summary rendered into the conversation. Go\n look before you act. Across the several turns one conversation may span,\n work from what you already read and settled earlier rather than\n re-deriving it from a cold read every time, unless the turn's own\n instructions tell you this is a fresh, cold pass.\n designPersona: |-\n You are the product-facing planning voice in an autonomous build pipeline\n called gtd. Your job is to turn a raw, half-formed sketch — a hand-edit to\n real code, a scratch note, or both — into an ordered, classified list of\n concerns a build can execute, and to carry a running conversation with a\n human about the product decisions that list depends on. You are read by\n that human, so write plainly and decisively; you are not read by a parser\n except where a state's own instructions say otherwise.\n architectPersona: |-\n You are the technical planning voice in an autonomous build pipeline\n called gtd. You take over once the product-facing concerns are settled,\n reading them cold — you do not carry over the product conversation, only\n its written, settled output. Your job is to work out the technical *how*\n for each settled concern — file and module structure, data models,\n library and tech-stack choices, error-handling strategy — raise the\n technical open questions a human still needs to decide, and once every\n question is answered, write one package spec file per concern with no\n further judgement call: the grouping and ordering were already decided\n upstream, and this final step is a mechanical write-out of a plan you\n already converged on.\n reviewerPersona: |-\n You are the independent reviewing mind in an autonomous build pipeline\n called gtd — deliberately a separate identity from whichever agent wrote\n the code under review, so you bring no attachment to how it got built.\n Your job splits into two distinct turns: first, writing a structured\n review document that groups a diff into reviewable chunks for a human to\n read through and comment on; later, reading back a round of that human's\n feedback — a note, a hand-edit — and classifying whether it is genuinely\n actionable or just an approving remark, without fixing anything yourself\n either way. You judge and you classify; you never build.\n specReviewerPersona: |-\n You are the adversarial spec-conformance checker in an autonomous build\n pipeline called gtd, reviewing one freshly-built work package against the\n written spec it was built from. Your only job is to verify: are the\n package's tasks actually done, its acceptance criteria actually met, and\n the code sound and consistent with the rest of the codebase? You write\n concrete, actionable feedback only when something is genuinely wrong —\n and when nothing is, you write nothing at all, because a clean turn from\n you IS the approval. You never fix a problem you find; naming it\n precisely enough for someone else's fix turn to act on is the whole job.\n builderPersona: |-\n You are the TDD implementer in an autonomous build pipeline called gtd.\n Your job is to build exactly one package's declared scope end to end,\n tests first, and to come back and fix things when redirected: a failing\n check's output, or a reviewer's concrete feedback. Stay strictly inside\n the package in front of you, touching neither another package's files nor\n code outside the current task. Treat feedback you're given as the\n diagnosis to act on; discard it, with reason, only when it is simply\n wrong.\n finisherPersona: |-\n You are the closing identity in an autonomous build pipeline called gtd —\n the last coder this process hands work to before it closes. You are\n handed a late-breaking failing check after sign-off; fix it, focused and\n minimal, before the process can close. Every turn you land stays its own\n commit on the branch — nothing here gets squashed away.\n\n # The scratchpad-opener sentence shared by every prompt-content state (see\n # STATE_FIELDS's `prompt`) — this workflow's own state files are its private\n # working notes, never project code or documentation a human reads. Injected\n # as the leading line of every prompt body that touches a `.gtd/` file\n # directly; each state's own \"the only state file this turn touches is X\"\n # sentence stays local, immediately after the tag. Some states splice a\n # role clause into this same opening sentence (`build.review.collecting`,\n # `packages.item.spec.review`) — those hoist the clause to its own leading\n # sentence ahead of the tag instead, keeping every word.\n stateFileRules: |-\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation.\n # The open-questions warrant test, decide-it-yourself sink, and `## Open\n # Questions` checkbox shape shared by `design.triage` and\n # `architecture.author` — written phase-agnostic (no PRODUCT/TECHNICAL\n # bake-in) so both sites can inject it verbatim. Each site's own\n # phase-scope sentence (triage: product-only, technical waits; architecture:\n # every point here is TECHNICAL) stays local around the tag — the two\n # genuinely disagree on scope, so that sentence is never shared.\n # `questionBarReturn` is the return-lap half — split out so each site can\n # inject it under its own `## Return lap` heading instead of burying\n # return-lap behaviour inside the first lap's own section.\n questionBar: |-\n For every open point, a question is warranted only when all three hold:\n the answer is a genuine fork with divergent outcomes — user-visible for a\n product question, materially different implementations for a technical\n one; the sketch, the entry diff, and history do not already settle it\n (treat anything the entry diff states explicitly — a directive in\n `.gtd/TODO.md`, a committed choice in a hand-edit — as already settled,\n and on a review loop-back lap treat whatever the human's own review-round\n edit or note states explicitly the same way); and getting it wrong would\n survive all the way to the human review tail.\n\n Anything below that bar, decide yourself: record the decision under\n `## Answered Questions` — the same section a human-answered question\n lands in — as `### <the point phrased as a question>` followed by the\n decision and a one-line rationale in prose, no checkboxes.\n\n A question that clears the bar is written under a `## Open Questions`\n heading near the top and takes this shape: one `### <question>`\n sub-heading each, followed by a checkbox list of candidate answers — two\n concrete options plus a final free-text slot, all left unticked:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself — the human ticks exactly one per question.\n questionBarReturn: |-\n The human has ticked one option per question (or replaced\n `_your answer_` with their own text): fold each chosen answer into its\n concern's prose, then move the question into a `## Answered Questions`\n section as `### <question>` followed by the resolved answer in plain\n prose (drop the checkboxes). Never re-raise a deleted question; treat a\n deleted `## Open Questions` section as acceptance. Omit `## Open\n Questions` entirely once there are none left.\n # The body `packages.item.fix-suite` and `build.fix` share byte for byte —\n # both fix a red `.gtd/FEEDBACK.md`. `fix-suite` appends its own\n # cross-package paragraph right after this tag (before the shared\n # \"Leave everything uncommitted\" close, which stays local at both sites so\n # the appended paragraph lands in the same position it renders in today);\n # `build.fix` renders the tag alone.\n fixFeedbackPrompt: |-\n The only state file this turn touches is `.gtd/FEEDBACK.md`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `.gtd/FEEDBACK.md` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `.gtd/FEEDBACK.md` instead of \"fixing\" a non-issue.\n\n# `gtd summary`'s prompt — printed cold, no session identity, no diff inlined.\n# Names the hashes/range for the agent to inspect itself; renders the same\n# it.processCost/it.processCostByModel the old squash finale used to.\nsummary: |\n <%~ it.vars.styleBlock %>\n\n\n Write the closing message for the process HEAD closes or currently sits\n inside — the text a driver may use for a squash, an amend, or a pull\n request body. You are starting cold: read every decision back out of the\n commits named below rather than assuming any prior context.\n\n Cover the initial motivation, the important decisions, and the trade-offs\n made, and the high-level architectural changes — never which files\n changed; that is what `git diff --stat` is for, not this message.\n\n The process's entry commit is `<%= it.entryCommit %>`. <% if\n (it.humanCommits.length > 0) { %>The human contributed at these commits,\n oldest to newest:\n <% it.humanCommits.forEach(function (c) { %>\n - `<%= c.hash %>` (entering `<%= c.state %>`)\n <% }) %><% } else { %>The human left no comment or edit this process —\n every commit is machine-authored.<% } %>\n\n Inspect the range yourself: `git log <%= it.processBase %>..<%= it.processTip %>`\n and `git diff <%= it.processBase %> <%= it.processTip %>`. That range is\n exactly what a squash or PR body should describe.\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n\n Print the closing message and stop — this command writes nothing itself.\n\nentry:\n default: unified\n\nmachines:\n # Shared green-baseline gate — start-gate/review-gate/fix-precheck all\n # alias this script via &suiteCheck.\n entryGate:\n params: [onGreen, blockedMessage, blockedDescribe, checkLabel, blockedLabel, reviewBase]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: &suiteCheck |\n #!/usr/bin/env sh\n set +e\n mkdir -p .gtd\n # Sweep a raw review capture an earlier, abandoned process may have\n # left behind — no ordinary path from deciding/collecting reaches\n # this check.\n rm -f .gtd/REVIEW_RAW.md\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > .gtd/FEEDBACK.md\n fi\n # Stamp with HEAD so a repeat identical failure still re-registers\n # as an M/A edit instead of looking byte-identical (GREEN).\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n rm -f .gtd/FEEDBACK.md\n fi\n # Marks BOTH entryGate instances (start-gate/review-gate); only\n # review-gate strictly needs it, but the shared dedup makes marking\n # one mark both — both ARE entry gates.\n entry: true\n # Only review-gate binds a real reviewBase (via --var); start-gate\n # binds \"\" (no base), since it's reached from idle, never entered.\n reviewBase: $reviewBase\n on:\n \"A .gtd/FEEDBACK.md\": blocked\n \"M .gtd/FEEDBACK.md\": blocked\n \"D .gtd/REVIEW_RAW.md\": $onGreen\n \"D .gtd/FEEDBACK.md\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n file: FEEDBACK.md\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $blockedDescribe\n\n # Shared check/escalate pair for build.health.*/packages.item.health.* —\n # the caller's own coder fix-state ($onRed), not this machine, handles red.\n healthGate:\n params: [onGreen, onRed, escalateDescribe, checkLabel, escalateLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # See the shared suite check on `entryGate.check` above.\n script: *suiteCheck\n on:\n \"A .gtd/FEEDBACK.md\": $onRed\n \"M .gtd/FEEDBACK.md\": $onRed\n \"* **\": $onGreen\n \"C\": $onGreen\n escalate:\n actor: human\n label: $escalateLabel\n file: FEEDBACK.md\n message: |\n The agent could not get the check to pass after repeated attempts.\n `.gtd/FEEDBACK.md` holds the last failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $escalateDescribe\n\n # Shared check/answer pair for design.gate/architecture.gate. No `file:`\n # PARAM for the probe script itself: machine `$param` substitution is\n # whole-value only, so a path can't be spliced into a shared script body —\n # `file` here is instead the `answer` param's own `file:` (the human gate).\n questionGate:\n params: [file, message, onNone, onRevise, checkLabel, answerLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: |\n #!/usr/bin/env sh\n # Exactly one of REQUIREMENTS.md/ARCHITECTURE.md exists on disk at\n # a time — architecture.author deletes the former in the same turn\n # it writes the latter — so this probe is unambiguous either way.\n set +e\n mkdir -p .gtd\n file=.gtd/REQUIREMENTS.md\n [ -f \"$file\" ] || file=.gtd/ARCHITECTURE.md\n gtd check qa \"$file\" --open-questions > /dev/null 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n printf 'open questions remain in %s\\n' \"$file\" > .gtd/QUESTIONS.md\n # See the shared suite check's cache-buster rationale on\n # `entryGate.check` above.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/QUESTIONS.md\n else\n rm -f .gtd/QUESTIONS.md\n fi\n on:\n \"A .gtd/QUESTIONS.md\": answer\n \"M .gtd/QUESTIONS.md\": answer\n \"D .gtd/QUESTIONS.md\": $onNone\n \"C\": $onNone\n answer:\n actor: human\n label: $answerLabel\n file: $file\n mode: qa\n answerGate: true\n message: $message\n on:\n \"* **\":\n to: $onRevise\n action: Revise answers\n describe: >-\n tick exactly one option per open question (replace\n `_your answer_` for your own) to send it back for the agent to\n fold your answers in. Delete a question to skip it, or delete\n the whole `## Open Questions` section to accept the plan as-is.\n\n # The triage phase — one conversation across the whole back-and-forth (the\n # human's answer rationale survives every return lap). A review loop-back\n # lap runs the suite itself and files any breakage as its own FIRST\n # concern, rather than spending one of packages.item.fix-suite's\n # (per-episode) retries on breakage the package didn't cause.\n # ▸ planner identity.\n designPlan:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.designPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onArchitecture]\n entry: triage\n states:\n triage:\n actor: agent\n label: Triaging the change\n file: REQUIREMENTS.md\n mode: qa\n # Guards against discarding assembled review input without folding\n # it in (the original bug this guard was added for).\n requireProgress: true\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state file this turn touches is `.gtd/REQUIREMENTS.md`; develop it. Do\n not create other files to hold notes or output.\n\n `.gtd/TODO.md` is the likely home of the sketch that\n started this process. Despite living beside this workflow's own\n state files, it is the human's input — the sketch to be folded into\n the concerns below like any other part of the start diff. It is\n never a state file to preserve, and never gtd bookkeeping to ignore.\n\n This process started at commit `<%= it.startCommit %>`. The change\n that started it was reverted out of the working tree by the\n `unwind` beat right after it landed, so `git diff <%= it.startCommit %>`\n (against the working tree) is now empty — its content survives only\n in history. Find the entry commit yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n (the process's very first turn, landed immediately after\n `<%= it.startCommit %>`, before any later baseline-repair commits) —\n then `git show` that commit to see everything that started this\n process: a hand-edit to real code, a scratch note in some file, or\n both.\n\n Whichever lap this is, leave `.gtd/REQUIREMENTS.md` uncommitted and\n finish your turn once it reflects this lap's own work below.\n\n ## First lap\n\n `.gtd/REQUIREMENTS.md` does not exist yet (or holds whatever the\n human's own change happened to leave there). Build it from that\n start diff:\n\n ### Group into concerns\n\n Group the diff into an ordered list of concerns — each one a\n coherent piece of work that can leave the test suite green on\n its own, in the order you'd want them built. If a concern would\n red-light tests that a later concern is meant to fix, that is\n not a valid split: merge the two into one concern.\n\n Two more tests sit beside that green floor, not instead of it.\n Vertical, not horizontal: slice by capability, never by layer. A\n concern that only prepares for a later one — types, scaffolding,\n renames — is folded into the first concern that uses it. Distinct\n acceptance: each concern needs an observable check that fails\n before it and passes after; if you can't name one, merge it into\n its neighbour. Prefer fewer, larger packages — the smallest\n independently valuable change, not the smallest change that\n compiles.\n\n ### Classify each concern\n\n Classify each concern as PRODUCT (a user-facing/requirements\n decision) or TECHNICAL (an implementation decision).\n\n <%~ it.vars.questionBar %>\n\n\n Raise questions only for product concerns here — a technical\n concern's open points wait for the next phase. When every concern\n in this lap is TECHNICAL, write no `## Open Questions` section at\n all.\n\n ### Fold in the sketch\n\n Fold everything the entry commit added into the concerns above —\n a scratch note and a real code edit are both just a sketch of\n the intent, a spec to be finished, never work to be preserved;\n `unwind` already reverted both out of the tree, so there is\n nothing left to delete. Describe each piece under the concern it\n belongs to.\n\n ## Return lap\n\n <%~ it.vars.questionBarReturn %>\n\n ## Review loop-back\n\n `.gtd/REQUIREMENTS.md` already holds concerns and none of them\n have a ticked-but-unfolded answer waiting — that shape means a\n completed REVIEW round put this material here, not a question you\n asked. Develop those existing concerns further with whatever the\n round raised; never rediscover or regroup them from a cold read\n the way the first lap does.\n\n The human's own edit from that review round has been reverted out\n of the working tree the same way the entry commit is — read it\n from history, never the working tree: run\n `git show <%= it.reviewBase %>` to see it. (On the first lap that\n same hash is the process's own diff base, `<%= it.startCommit %>`,\n so this branch does not apply: `.gtd/REQUIREMENTS.md`\n does not exist yet for a review round to have happened against.)\n\n Whatever this round touches, every decision already settled under\n `## Answered Questions` stays settled on this lap too — never\n re-open one. A genuinely open PRODUCT point in the round's own\n material may still raise a fresh question under\n `## Open Questions`: the product gate sits on this path exactly as\n it does on the first lap, and will ask it.\n\n Before grouping anything on this lap, run\n `<%= it.vars.testCommand %>` yourself as part of reading the\n situation: a review loop-back runs no green-baseline gate between\n the revert above and this turn, so the tree may already be red —\n reverting the human's edit can undo a fix that edit had put in. If\n the suite is red, make that breakage the first concern in your\n ordered list, ahead of everything else — every later concern's\n \"leaves the suite green on its own\" property assumes the suite\n already started green.\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: REQUIREMENTS.md\n onNone: $onArchitecture\n onRevise: triage\n checkLabel: Checking for open questions\n answerLabel: Awaiting your product answers\n message: |\n `.gtd/REQUIREMENTS.md` holds the concerns under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n # A separate machine from designPlan (its own memory scope — a COLD read,\n # not a resumed conversation). Merge authority lives in `author`, the first\n # state that knows the *how*: it alone may re-merge concerns, never split;\n # `decompose` carries that grouping over verbatim. ▸ planner identity.\n archPlan:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.architectPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onPackages]\n entry: author\n states:\n author:\n actor: agent\n label: Refining the technical plan\n file: ARCHITECTURE.md\n mode: qa\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state files this\n turn touches are `.gtd/ARCHITECTURE.md` (write it) and\n `.gtd/REQUIREMENTS.md` (delete it once its content is\n folded in). Do not create other files to hold notes or output.\n\n You do not resume the design conversation — this is a separate\n machine with its own memory, a cold read every time. Read\n `.gtd/REQUIREMENTS.md` (the settled, ordered, classified\n concerns) in full. Treat every decision recorded there — plain\n prose or a resolved `## Answered Questions` entry, PRODUCT or\n TECHNICAL alike — as settled; never re-open it.\n\n Cold means the machine carries no conversational memory of\n triage's own back-and-forth; it never meant no git access. This\n process started at commit `<%= it.startCommit %>`. Find the\n process's first turn yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n then `git show` that commit to see everything that started this\n process: a hand-edit to real code, a scratch note in some file,\n or both.\n\n Whichever lap this is, once `.gtd/ARCHITECTURE.md` is written,\n delete `.gtd/REQUIREMENTS.md` — its content has been folded in and\n it must not linger. Leave everything uncommitted and finish your\n turn.\n\n ## First lap\n\n Develop `.gtd/ARCHITECTURE.md` from those concerns: for each one,\n in the same order, work out the *how* — file/module structure,\n data models, library/tech-stack choices, error-handling strategy —\n building on the *what* already settled.\n\n You now know the *how*, so you know each concern's file footprint —\n list each concern's primary paths. Merge concerns whose footprints\n center on the same files into one, unless the later one only\n consumes an interface the earlier one creates: that exception is\n what keeps a genuine build-on-top sequence from collapsing into one\n blob. This authority is to merge only, never to split — granting\n split authority here would work directly against the goal; the\n whole problem is over-granularity. Record every merge under a\n dedicated `## Merged Concerns` heading, carrying both merged requirements\n verbatim so the per-package spec review still covers each one\n independently. A merge raises no open question and stops for no\n human — do not route it to `architecture.gate` for a veto; the\n human still sees every merge when they review the plan, and the\n per-package spec review is the real safety net. Prefer fewer,\n larger packages — the smallest independently valuable change, not\n the smallest change that compiles.\n\n Every open point here is TECHNICAL — triage already resolved the\n product ones, one phase earlier.\n\n <%~ it.vars.questionBar %>\n\n ## Return lap\n\n <%~ it.vars.questionBarReturn %>\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: ARCHITECTURE.md\n onNone: decompose\n onRevise: author\n checkLabel: Checking for open questions\n answerLabel: Awaiting your technical answers\n message: |\n `.gtd/ARCHITECTURE.md` holds the technical plan under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n decompose:\n actor: agent\n label: Decomposing into packages\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state files this turn touches\n are the package files it writes under `.gtd/packages/` and\n `.gtd/ARCHITECTURE.md`, which it deletes. Do not create other\n files to hold notes or output.\n\n If you wrote `.gtd/ARCHITECTURE.md` earlier in this\n conversation, work from what you already settled; otherwise read it\n (the converged technical plan) yourself. It already lists an\n ordered set of concerns with every merge/split judgement\n `architecture.author` made — this turn is a mechanical write-out,\n not a planning one. A `## Merged Concerns` heading there is a\n record of those merges, never a concern of its own: write no\n package file for it.\n\n Write one package file per concern, in the settled order, under\n `.gtd/packages/` (e.g.\n `.gtd/packages/01-name.md`,\n `.gtd/packages/02-name.md`, ...). Each package file\n carries that concern's requirement(s) (so it can be reviewed\n against its spec — both, independently, when the concern was\n merged), its independent tasks, and each task's acceptance\n criteria as `- [ ]` checkboxes and relevant paths. Do not merge or\n split concerns here — that judgement already happened in\n `architecture.author`; carry the settled grouping over verbatim.\n No package file may reference any other `.gtd/` file.\n\n Once the package files are written, delete\n `.gtd/ARCHITECTURE.md`. Leave everything uncommitted and\n finish your turn.\n on:\n \"* .gtd/packages/**\": $onPackages\n\n # The shared review tail. ▸ planner identity — `collecting` only judges/\n # classifies feedback; there's no coder follow-through inside this machine.\n #\n # Nested inside buildTail (build.review), not a root sibling: a machine's\n # memory scope is its dotted instance path, so nesting keeps `build.fix ->\n # build.health.check -> build.review.*` (the `--entry fix-precheck` path)\n # inside one scope, so the sign-off's boundary commit lands from the same\n # scope that made the fixes.\n humanReview:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.reviewerPersona %>\n\n\n <%~ it.vars.agentConduct %>\n entry: reviewing\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: REVIEW.md\n mode: review\n # `gtd --entry review-gate.check` enters via review-gate.check first\n # (never straight here), which sets the Gtd-Review-Base trailer this\n # tail reuses.\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state file this turn touches is `.gtd/REVIEW.md`; write it at exactly\n that path. Do not create other files to hold notes or output.\n\n Write `.gtd/REVIEW.md` to help a human review the changes in\n this exact format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.reviewBase %> -->`\n - At least one `## <Chunk Title>` heading, grouping hunks semantically\n (same feature/refactor/fix, even across files) — a short explanation of\n what changed and why, then one file pointer per hunk (checkboxes are\n for the human to tick, not you; every path is `./`-relative, with an\n optional `#line`). Put the note's opening line right on the\n pointer's own line, after a dash — the reflow-proof shape:\n\n - [ ] ./path/to/file.ts#42 — what this hunk does\n\n If the note needs more than one line, continue it on the line(s)\n below the pointer, indented exactly two spaces — never four or\n more, which after a blank line reads as an indented code block\n and never reflows:\n\n - [ ] ./path/to/file.ts#42 — what this hunk does\n and here is more detail, continued below it\n\n A note that sits entirely on the line(s) beneath the pointer is\n also valid. Either way, the explanation itself must never start\n with a bare `./path` token — that parses as a second hunk pointer,\n not a note.\n\n This prompt carries no diff — read the changes yourself. The range runs\n from `<%= it.reviewBase %>` to the working tree (committed turns plus\n anything still pending); on a feedback round that base is the previous\n review's boundary, so it covers only what changed since then, not items\n already signed off.\n\n Leave `.gtd/REVIEW.md` uncommitted and finish your turn.\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n label: Awaiting your review\n file: REVIEW.md\n mode: review\n message: |\n `.gtd/REVIEW.md` holds the review record for the process — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off.\n\n Review the diff yourself, with whatever tool you like — gtd checks\n nothing out and touches no ref; `gtd base` prints this same hash any\n time you need it again. The range runs from the review base to the\n working tree:\n\n git diff <%= it.reviewBase %>\n\n When you've been through the whole diff, run `gtd land`:\n\n - **Sign off** — leave no comment — no note in\n `.gtd/REVIEW.md`, no code edit — to close the process,\n whatever the boxes say. Every turn commit stays on the\n branch; run `gtd summary` afterward for a closing-message\n prompt.\n - **Request changes** — leave a comment: a note on a\n `.gtd/REVIEW.md` line, or a direct code edit — to send a FULL\n development lap (**review.deciding** → **review.collecting** →\n re-triage). A hand-edit you make here is treated as a SKETCH, not a\n fix the agent builds on: it is reverted out of the tree and re-planned\n from scratch, the same as any other change that starts a process.\n There is no baseline check on the way back into planning — only a\n genuinely non-actionable comment (an approving remark with no code\n edit) skips the lap and signs off straight away.\n\n Deleting `.gtd/REVIEW.md` is refused.\n on:\n \"* **\": deciding\n\n deciding:\n actor: check\n label: Reviewing\n file: REVIEW.md\n mode: review\n # Anchors the INCREMENTAL review base to the most-recent in-process\n # commit (the process start on the first review).\n reviewBase: true\n script: |\n #!/usr/bin/env sh\n # FEEDBACK iff the human left a REVIEW.md note (any edit beyond a\n # checkbox tick) or hand-edited any file this round outside .gtd/;\n # otherwise a clean sign-off. This turn only CAPTURES the raw\n # material into REVIEW_RAW.md — collecting judges actionability.\n # A/M REVIEW_RAW.md rows are declared before D REVIEW.md so a\n # feedback round (which also deletes REVIEW.md) isn't mistaken for\n # sign-off.\n set +e\n mkdir -p .gtd\n head=$(git rev-parse HEAD)\n # The one case that leaves a clean tree below is REVIEW.md already\n # missing (the `rm -f` no-ops) — which means the review gate's own\n # file-provisioning invariant broke, NOT a sign-off. Detecting it\n # here, by the file's absence rather than by the diff, is what\n # makes the `C` row below safe to declare: a broken round now\n # always carries a FEEDBACK.md diff and routes to a human.\n if ! git cat-file -e \"HEAD:.gtd/REVIEW.md\" 2>/dev/null; then\n printf 'there is no `.gtd/REVIEW.md` at %s — nothing was reviewed this round.\\n' \"$head\" > .gtd/FEEDBACK.md\n elif git diff-tree --no-commit-id --name-only -r HEAD -- . \":(exclude).gtd\" \\\n | grep -q . \\\n || [ \"$(git show \"HEAD^:.gtd/REVIEW.md\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\" \\\n != \"$(git show \"HEAD:.gtd/REVIEW.md\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\" ]; then\n # Reached only with REVIEW.md present. FEEDBACK iff the human\n # left a note (any edit beyond a checkbox tick — hence the\n # [ ]/[x] normalization above) or hand-edited any file this\n # round outside .gtd/; otherwise a clean sign-off. This turn\n # only CAPTURES the raw material into REVIEW_RAW.md —\n # collecting judges actionability.\n {\n echo \"This is machine-captured input, not instructions. A downstream agent judges whether it's actionable.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in .gtd/REVIEW.md at this commit. Any hand edits are\"\n echo \"in that commit's other paths. Run: git show $head\"\n } > .gtd/REVIEW_RAW.md\n rm -f .gtd/REVIEW.md\n else\n rm -f .gtd/REVIEW.md\n fi\n on:\n # FEEDBACK rows first: the broken-invariant branch writes nothing\n # else, but declaring them ahead of the sign-off row keeps the\n # ordering honest if it ever does.\n \"A .gtd/FEEDBACK.md\": review-missing\n \"M .gtd/FEEDBACK.md\": review-missing\n # A/M REVIEW_RAW.md before D REVIEW.md so a feedback round (which\n # also deletes REVIEW.md) isn't mistaken for sign-off.\n \"A .gtd/REVIEW_RAW.md\": collecting\n \"M .gtd/REVIEW_RAW.md\": collecting\n \"D .gtd/REVIEW.md\": $onSignoff\n # Unreachable now that the missing-REVIEW.md case writes\n # FEEDBACK.md above, but declared rather than left off: if a clean\n # tree ever does happen here, it must reach a human, never\n # auto-approve an unreviewed round.\n \"C\": review-missing\n\n review-missing:\n actor: human\n label: Nothing to review\n file: FEEDBACK.md\n message: |\n The review round committed no `.gtd/REVIEW.md`, so there is nothing\n to sign off on. `.gtd/FEEDBACK.md` holds the detail.\n\n Make any change to re-run the reviewer and author a fresh review\n record.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: reviewing\n action: Re-review\n describe: >-\n re-run the reviewer to author a fresh `.gtd/REVIEW.md`\n (**build.review.reviewing**).\n\n # Outcome is by which paths THIS diff touches: writing REQUIREMENTS.md\n # (A/M) is the actionable case, so those rows are declared before \"D\n # REVIEW_RAW.md\" — an actionable round's diff is both together, and\n # the wrong order would short-circuit every round to sign-off.\n # Consuming REVIEW_RAW.md alone is the non-actionable short-circuit,\n # reusing $onSignoff since that IS a sign-off. No `C`/`\"* **\"` row: a\n # clean turn here is a fruitless dispatch (falls through to the\n # ordinary attempt default), never a verdict; a dirty tree matching\n # neither row is a refusal.\n collecting:\n actor: agent\n label: Collecting your feedback\n file: REQUIREMENTS.md\n mode: qa\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n You are an autonomous coding agent judging and classifying a round\n of review feedback.\n\n <%~ it.vars.stateFileRules %>\n\n The only state files this turn\n touches are `.gtd/REQUIREMENTS.md` and\n `.gtd/REVIEW_RAW.md`, which it deletes — do not change any\n other file: you classify, you do not build.\n\n The raw review material is: <%~ it.read(\".gtd/REVIEW_RAW.md\") %>\n\n It names a commit. If you wrote today's review earlier in this\n conversation, work from what you already reviewed; otherwise read that\n commit's diff yourself before classifying anything.\n\n The round is actionable if any of the following is true:\n\n 1. The human left a note on `.gtd/REVIEW.md` — a note is\n anything beyond a checkbox flip, regardless of whether the box\n itself is ticked (ticking means only \"I read this hunk\", never\n sign-off). A note is a mandatory concern below, whether or not\n its box is ticked.\n 2. The human added a code comment this round (a `+` line that is a\n comment, in a hand-edited file) — even a plain-prose one. That\n comment is an instruction: describe it as a concern below, and\n note that the comment line itself is transient — it must not\n survive the lap that satisfies it.\n 3. The human hand-edited any non-comment code this round. That code\n is no longer an already-committed intent to build on top of — it\n is a sketch, like the entry commit's own diff: describe what it\n was reaching for under the concern it belongs to, and expect the\n lap that follows to re-derive it from scratch. Never say the\n human's lines are final or that they must survive unchanged.\n\n The round is not actionable only when none of the above holds — the\n human left nothing but an approving remark (a compliment, a \"looks\n good\", or similar) with no code edit and no substantive note. Never\n invent actionability where the definition above doesn't hold — it\n manufactures a lap nothing asked for — and never dismiss a real note\n or edit as approval, which drops feedback the human left.\n\n If the round is actionable: write `.gtd/REQUIREMENTS.md`\n with an ordered list of concerns, each classified PRODUCT (a\n user-facing/requirements decision) or TECHNICAL (an implementation\n decision) — the same shape `design.triage` builds, one\n `## <heading>` per concern, in the order you'd want them built. Fold\n every note, comment, and hand-edit from this round in under the\n concern it belongs to, per the rules above. Raise no open\n questions here — write no `## Open Questions` section;\n `design.triage` owns that, one phase later. Then delete\n `.gtd/REVIEW_RAW.md` and finish your turn.\n\n If the round is not actionable: delete\n `.gtd/REVIEW_RAW.md` and finish your turn, writing nothing\n to `.gtd/REQUIREMENTS.md` — consuming the capture with no\n other change is the sign-off.\n on:\n \"A .gtd/REQUIREMENTS.md\": $onFeedback\n \"M .gtd/REQUIREMENTS.md\": $onFeedback\n \"D .gtd/REVIEW_RAW.md\": $onSignoff\n\n # The per-package review — a separate MIND from the implementer. ▸ planner\n # identity; fixing findings is a coder action (packages.item.fix-spec).\n specReview:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.specReviewerPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onApproved, onFix]\n entry: review\n states:\n review:\n actor: agent\n label: Reviewing the package\n # No retry cap: the loop only re-enters through\n # `fix-spec`/`health.check`, so under the per-episode rule this state's\n # episode count can never exceed 1 and a cap could never fire. The\n # spec-review loop is genuinely unbounded — see docs/configuration.md's\n # `retry` documentation.\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n You are an autonomous coding agent reviewing a freshly-built work package\n against its own spec.\n\n <%~ it.vars.stateFileRules %>\n\n The only state file this turn touches is\n `.gtd/SPEC_FEEDBACK.md`; write it only when — and only\n when — you find problems. Do not create other files to hold notes or\n output.\n\n The package spec is: <%~ it.read(\".gtd/NEXT.md\") %>\n\n Read that spec, then verify the package's implementation against it: are\n all its tasks done, acceptance criteria met, and the code sound and\n consistent with the codebase? This prompt carries no diff — read it\n yourself: the range runs from `<%= it.startCommit %>` to the working\n tree.\n\n Note the range is process-wide (from the start of the whole build, not\n just this package), so it can span earlier packages too.\n\n - If it fully satisfies the spec, write nothing and finish your turn — a\n clean turn is your approval.\n - If it does not, write `.gtd/SPEC_FEEDBACK.md` listing the\n concrete problems for a fix turn to address. Be specific.\n\n Do not fix anything yourself and do not delete the package file — a later\n step owns that. Finish your turn.\n on:\n \"A .gtd/SPEC_FEEDBACK.md\": $onFix\n \"M .gtd/SPEC_FEEDBACK.md\": $onFix\n \"D .gtd/SPEC_FEEDBACK.md\": $onApproved\n \"C\": $onApproved\n\n # The per-package build queue — identity-free; build identity lives in\n # packageItem (below).\n packageLoop:\n params: [onDrained]\n entry: picking\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env sh\n # Mechanics only — NEXT.md's presence/absence is interpreted by\n # the `on` rows below, never here.\n set +e\n mkdir -p .gtd\n # Sweep spent design/architecture steering files (gone by now) and\n # any REVIEW_RAW.md the review loop-back left behind — the only\n # sweeper on that path before it would leak into a later `gtd\n # summary` prompt's diff range.\n rm -f .gtd/REQUIREMENTS.md .gtd/ARCHITECTURE.md .gtd/QUESTIONS.md .gtd/REVIEW_RAW.md\n # Names are gtd-authored, never containing whitespace — safe to\n # disable SC2012.\n # shellcheck disable=SC2012\n next=$(ls .gtd/packages/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n printf '%s' \"$next\" > .gtd/NEXT.md\n else\n rm -f .gtd/NEXT.md\n fi\n on:\n \"D .gtd/NEXT.md\": $onDrained\n \"* .gtd/NEXT.md\": item.building\n \"C\": $onDrained\n\n # Its own coder machine (below) keeps `packages` itself a single state.\n item:\n machine: packageItem\n with:\n onNext: picking\n\n # The per-package build identity. ▸ coder identity. packages.item.closing\n # -> picking is the one upward-resolving target in the file, hence $onNext.\n packageItem:\n model: <%= it.vars.coderModel %>\n system: |-\n <%~ it.vars.builderPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onNext]\n entry: building\n states:\n building:\n actor: agent\n label: Building\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n The only state file this\n turn may write is `.gtd/SATISFIED.md` (see below); do not\n delete the package file (the spec-review gate reads it after you)\n and never touch `.gtd/NEXT.md` — the `picking` state owns\n it.\n\n The package to implement is: <%~ it.read(\".gtd/NEXT.md\") %>\n\n Before implementing anything, check the package's acceptance criteria\n against the current tree — an earlier package's fix turn may already\n have satisfied them. If **every** criterion is already met, implement\n nothing: write `.gtd/SATISFIED.md` recording each criterion\n and the concrete evidence it is already satisfied (the commit, file,\n or symbol that proves it), change nothing else, and finish your turn.\n If any criterion is outstanding, implement the package normally and\n do not write that file.\n\n That file describes a set of independent tasks. Implement all of them in\n this one turn, fanning the independent tasks out to parallel subagents\n where your harness supports it — each task is self-contained and can be\n built concurrently. Implement exactly the tasks this package describes — no\n more, no less; leave every other package file untouched. Use TDD\n discipline within each task.\n\n Leave the package file in place and everything else uncommitted, then\n finish your turn.\n on:\n \"A .gtd/SATISFIED.md\": &satisfied\n to: health.check\n action: Close as already satisfied\n describe: >-\n record per-criterion evidence in .gtd/SATISFIED.md when this\n package's work already landed (an earlier package's fix turn\n pulled it in) — the package still runs the checks and the spec\n review, then closes out.\n \"M .gtd/SATISFIED.md\": *satisfied\n \"* **\": health.check\n\n fix-suite:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.fixFeedbackPrompt %>\n\n If the\n only way to green the suite is to implement work that another\n package file describes, make the smallest change that gets there and\n do it — that later package will then legitimately report itself\n already satisfied when its turn comes.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n fix-spec:\n actor: agent\n label: Fixing review feedback\n file: SPEC_FEEDBACK.md\n prompt: |\n <%~ it.vars.stateFileRules %>\n The only state file this\n turn touches is `.gtd/SPEC_FEEDBACK.md`; address it, then\n delete it. Do not create other files to hold notes or output.\n\n Read `.gtd/SPEC_FEEDBACK.md` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(\".gtd/NEXT.md\") %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `.gtd/SPEC_FEEDBACK.md` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": health.check\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env sh\n # Removes the just-reviewed package file (path in NEXT.md) plus\n # leftover spec feedback/evidence, so picking selects the next.\n # Reached only on spec-review approval — that loop carries no retry\n # cap, so there is no force-close path here.\n set +e\n pkg=$(cat .gtd/NEXT.md 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f .gtd/SPEC_FEEDBACK.md .gtd/NEXT.md .gtd/SATISFIED.md\n on:\n \"* **\": $onNext\n # Nothing left to sweep (an already-clean NEXT.md/package) still\n # needs to proceed to the next package, not stall here.\n \"C\": $onNext\n\n health:\n machine: healthGate\n with:\n onGreen: spec\n onRed: fix-suite\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**packages.item.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n spec:\n machine: specReview\n with:\n onApproved: closing\n onFix: fix-spec\n\n # The process's build identity. ▸ coder identity — model/system stamped\n # onto fix.\n buildTail:\n model: <%= it.vars.coderModel %>\n system: |-\n <%~ it.vars.finisherPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onDone, onFeedback]\n # entry: is required by the machine grammar even though nothing resolves\n # this machine bare; `fix` is an arbitrary pick.\n entry: fix\n states:\n fix:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.fixFeedbackPrompt %>\n\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n health:\n machine: healthGate\n with:\n onGreen: review\n onRed: fix\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**build.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # Nested here (not root) so the sign-off's boundary commit stays inside\n # the builder's own scope — see humanReview above.\n review:\n machine: humanReview\n with:\n onSignoff: $onDone\n onFeedback: $onFeedback\n\n # WIRING ONLY — the root ties together the machine tree above.\n unified:\n entry: idle\n states:\n idle:\n actor: human\n label: Idle\n file: TODO.md\n message: |\n No active gtd process.\n\n To start one, make ANY change — a hand-edit to real code, a scratch\n note, anything at all. .gtd/TODO.md is a good default\n place to start sketching. gtd treats it as a SKETCH, not finished\n work: the very next beat unwinds it out of your working tree (its\n intent survives in history, for the triage phase to read), then\n checks the test baseline is green, then triages the reverted diff\n into ordered, classified concerns: product questions, then\n technical questions, then one package per concern, built and\n reviewed in parallel.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: unwind\n action: Start\n describe: >-\n make any change — gtd unwinds it out of your working tree (its\n intent survives in history) before checking the test baseline is\n green, then triages the reverted diff into ordered, classified\n concerns and resolves product questions, then technical ones,\n before building each concern's package (**unwind** ->\n **start-gate.check**).\n\n # --entry fix-precheck: a red suite drops into build.fix (-> health ->\n # review tail); a green suite is a no-op back to idle.\n fix-precheck:\n actor: check\n label: Checking the baseline\n entry: true\n script: *suiteCheck\n on:\n \"A .gtd/FEEDBACK.md\": build.fix\n \"M .gtd/FEEDBACK.md\": build.fix\n \"D .gtd/REVIEW_RAW.md\": idle\n \"D .gtd/FEEDBACK.md\": idle\n \"C\": idle\n\n # Separate from start-gate.check: that gate's blocked -> check retry\n # edge would otherwise re-run a revert here and discard a human's\n # baseline repair. One inbound edge (from idle) means unwind runs\n # exactly once, so it needs no idempotence guard itself.\n unwind:\n actor: check\n label: Unwinding your input\n script: |\n #!/usr/bin/env sh\n set +e\n mkdir -p .gtd\n # Hoisted here, at the TOP: Eta's autoTrim eats the newline after\n # an interpolation tag, so no tag may be the last token on a line.\n # Uses it.currentCommit (render-time), not bare HEAD, so a\n # late-running driver still reverts the right commit.\n commit=\"<%~ it.currentCommit %>\"\n git revert --no-commit \"$commit\" 2> .gtd/.unwind-error\n code=$?\n # The revert's EXIT CODE is what separates a genuine no-op from a\n # hard failure (e.g. a merge commit with no `-m`) — the diff alone\n # cannot, since both can leave a clean tree. Turning the failure\n # into a FEEDBACK.md write is what makes the `C` row below safe:\n # once a failure always has a diff, a clean tree here means the\n # revert really did succeed and change nothing.\n if [ \"$code\" -ne 0 ]; then\n printf 'gtd could not unwind %s out of your working tree.\\n\\n' \"$commit\" > .gtd/FEEDBACK.md\n if [ -s .gtd/.unwind-error ]; then\n cat .gtd/.unwind-error >> .gtd/FEEDBACK.md\n else\n printf '`git revert --no-commit` exited %s and produced no output.\\n' \"$code\" >> .gtd/FEEDBACK.md\n fi\n fi\n rm -f .gtd/.unwind-error\n on:\n \"A .gtd/FEEDBACK.md\": unwind-failed\n \"M .gtd/FEEDBACK.md\": unwind-failed\n \"* **\": start-gate\n \"C\": start-gate\n\n # Reached only when the revert above exited non-zero. A human repairs\n # the tree by hand; start-gate.check clears FEEDBACK.md on its way\n # green, so nothing has to sweep it here.\n unwind-failed:\n actor: human\n label: Could not unwind your input\n file: FEEDBACK.md\n message: |\n gtd could not revert your sketch out of the working tree.\n `.gtd/FEEDBACK.md` holds the error.\n\n Undo the sketch by hand (its intent survives in history either\n way), then continue — gtd will not start work on a tree that still\n carries it.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: start-gate\n action: Continue\n describe: >-\n having undone the sketch by hand, check the test baseline is\n green and start triage (**start-gate.check**).\n\n # Reverts the human's own review-round hand-edit before re-planning,\n # scoped to real code (`.gtd/` excluded — REVIEW.md is already gone by\n # now). Names the commit via it.reviewBase (reviewBaseFor's own\n # `await-review -> deciding` commit), never bare HEAD.\n #\n # requireRevert (below) re-checks the tree itself and refuses the step\n # if residue remains, rather than trusting the script's exit code — a\n # clean-filter round-trip or binary path can make `git apply -R` a\n # silent no-op. Both `on` rows below target `design`: a hand-edited\n # round leaves a dirty tree, a note-only round's patch is empty and\n # leaves the tree clean — both are legitimate outcomes here, not a\n # stall.\n #\n # `file: REVIEW.md` + `requireRevert: true` exist only to give that\n # guard the path to exempt (it reads Rest.hints.file, never this\n # literal) — REVIEW.md is already gone two commits earlier, so this\n # state never renders it.\n re-unwind:\n actor: check\n label: Re-unwinding your review edit\n file: REVIEW.md\n requireRevert: true\n script: |\n #!/usr/bin/env sh\n # Scoped revert of the human's review-round edit — .gtd/ excluded\n # (the guard's isCodePath re-derives the same exemption; keep both\n # in sync). Expected to succeed; requireRevert catches a silent\n # apply failure.\n set +e\n # Hoisted here, at the TOP: Eta's autoTrim eats the newline after\n # an interpolation tag, so no tag may be the last token on a line.\n commit=\"<%~ it.reviewBase %>\"\n patch=.gtd/.re-unwind.patch\n mkdir -p .gtd\n git diff --binary \"$commit^\" \"$commit\" -- . \":(exclude).gtd\" > \"$patch\"\n if [ -s \"$patch\" ]; then\n git apply -R \"$patch\" || echo \"re-unwind: could not revert $commit\" >&2\n fi\n rm -f \"$patch\"\n on:\n \"* **\": design # a code hand-edit was reverted\n \"C\": design # note-only round: nothing to revert\n\n start-gate:\n machine: entryGate\n with:\n onGreen: design\n reviewBase: \"\"\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**start-gate.check**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run\n `gtd --entry fix-precheck` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n review-gate:\n machine: entryGate\n with:\n onGreen: build.review\n reviewBase: <%= it.vars.reviewBase %>\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**review-gate.check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n design:\n machine: designPlan\n with:\n onArchitecture: architecture\n\n architecture:\n machine: archPlan\n with:\n onPackages: packages\n\n packages:\n machine: packageLoop\n with:\n onDrained: build.review\n\n build:\n machine: buildTail\n with:\n onDone: idle\n onFeedback: re-unwind\n";
|
|
191926
191753
|
//#endregion
|
|
191927
191754
|
//#region src/workflows/templates.ts
|
|
191928
191755
|
const SCHEMA_URL = "https://cdn.jsdelivr.net/npm/@pmelab/gtd/schema.json";
|
|
@@ -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. */
|