@pmelab/gtd 62.1.0 → 64.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.
Files changed (68) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +8 -3
  3. package/dist/chunks/{flows-DENt0uWV.mjs → flows-B7ZRCe-B.mjs} +1 -1
  4. package/dist/chunks/{rolldown-runtime-8H4AJuhK.mjs → rolldown-runtime-D7D4PA-g.mjs} +0 -1
  5. package/dist/chunks/{steering-DF_BMadH.mjs → steering-DkA51qDa.mjs} +1 -1
  6. package/dist/chunks/{testkit-CPzWnl3P.mjs → testkit-zGjNeQNL.mjs} +1144 -345
  7. package/dist/flows.mjs +1 -1
  8. package/dist/gtd.bundle.mjs +4023 -1326
  9. package/dist/gtd.core.mjs +120 -119
  10. package/dist/steering.mjs +1 -1
  11. package/dist/testkit/gherkin.mjs +2 -2
  12. package/dist/testkit.mjs +2 -2
  13. package/dist/workflow-kit.mjs +2 -2
  14. package/package.json +1 -1
  15. package/src/cli/Cli.test.ts +5 -8
  16. package/src/cli/Cli.ts +10 -28
  17. package/src/cli/Output.ts +2 -1
  18. package/src/cli/Program.test.ts +55 -53
  19. package/src/cli/Program.ts +36 -45
  20. package/src/cli/ToolCommands.ts +3 -4
  21. package/src/edge/Edge.test.ts +38 -12
  22. package/src/edge/Edge.ts +98 -13
  23. package/src/edge/index.ts +1 -0
  24. package/src/emit/SteeringMode.test.ts +4 -2
  25. package/src/emit/SteeringMode.ts +10 -9
  26. package/src/gherkin/messages.test.ts +1 -1
  27. package/src/gherkin/module-config.feature +1 -1
  28. package/src/gherkin/testkit.feature +2 -2
  29. package/src/install/Version.ts +14 -13
  30. package/src/platform/Host.ts +75 -22
  31. package/src/platform/NodePinFile.ts +44 -0
  32. package/src/platform/NodeRuntime.ts +19 -0
  33. package/src/platform/NodeSync.ts +64 -0
  34. package/src/platform/PinFile.ts +0 -50
  35. package/src/platform/Sha.ts +121 -0
  36. package/src/platform/SyncIo.ts +35 -0
  37. package/src/platform/Workspace.test.ts +8 -3
  38. package/src/platform/Workspace.ts +82 -123
  39. package/src/platform/WorktreeState.test.ts +8 -4
  40. package/src/platform/WorktreeState.ts +21 -19
  41. package/src/platform/index.ts +18 -5
  42. package/src/replay/Format.test.ts +32 -10
  43. package/src/replay/Format.ts +5 -2
  44. package/src/replay/Replay.test.ts +110 -3
  45. package/src/replay/Replay.ts +120 -28
  46. package/src/replay/Trailers.test.ts +10 -10
  47. package/src/replay/Trailers.ts +32 -1
  48. package/src/replay/index.ts +2 -0
  49. package/src/step/planStart.test.ts +2 -2
  50. package/src/step/planStep.test.ts +4 -4
  51. package/src/testkit/TestRepo.test.ts +1 -1
  52. package/src/testkit/TestRepo.ts +4 -18
  53. package/src/twins/Host.test.ts +151 -0
  54. package/src/twins/Layers.test.ts +2 -2
  55. package/src/twins/Layers.ts +44 -18
  56. package/src/twins/Path.test.ts +52 -0
  57. package/src/twins/SyncIo.test.ts +153 -0
  58. package/src/twins/SyncIo.ts +159 -0
  59. package/src/twins/cliIo.ts +8 -1
  60. package/src/twins/index.ts +5 -0
  61. package/src/workflow/discovery.test.ts +6 -5
  62. package/src/workflow/discovery.ts +43 -32
  63. package/src/workflow/fingerprint.test.ts +23 -3
  64. package/src/workflow/fingerprint.ts +9 -11
  65. package/src/workflow/load.test.ts +16 -6
  66. package/src/workflow/load.ts +107 -69
  67. package/src/workflow/pin.test.ts +3 -2
  68. package/src/workflow/pin.ts +17 -11
@@ -1,23 +1,22 @@
1
- import { C as moveScript, D as revertScript, E as restoreScript, H as installContext, K as markedOptions, O as showScript, S as checkScript, T as removeScript, Y as protocolMismatch, i as configure, k as writeScript$1, n as configProblems, o as isFactoryWorkflow, p as numeric, s as answered, t as flows_exports, x as applyScript } from "./flows-DENt0uWV.mjs";
1
+ import { C as moveScript, D as revertScript, E as restoreScript, H as installContext, K as markedOptions, O as showScript, S as checkScript, T as removeScript, Y as protocolMismatch, i as configure, k as writeScript$1, n as configProblems, o as isFactoryWorkflow, p as numeric, s as answered, t as flows_exports, x as applyScript } from "./flows-B7ZRCe-B.mjs";
2
2
  import { M as renderFormat, O as ACCESS_REFUSAL, a as renderScriptJson, d as renderLandPlain, f as DEMAND_BRIEFING, g as statusOf, h as renderBeatJson, i as judgeJson, k as PIN_PATH, l as noopText, m as beatDocument, n as resolveSession, o as scriptFields, p as renderBeatPlain, r as selectPath, s as landFields, t as globMatches, u as renderLandJson, v as beatKindOf, y as demandOf } from "./wire-CPNSm_9N.mjs";
3
- import { M as headingSectionBodies, N as formatFinding, a as sections, c as openThreads, i as sectionBodies, l as steeringFormatFor, o as threads, r as openQuestions, s as BUILT_IN_MODE_NAMES, t as parseCodeThreads, u as unansweredQuestions } from "./steering-DF_BMadH.mjs";
3
+ import { M as headingSectionBodies, N as formatFinding, a as sections, c as openThreads, i as sectionBodies, l as steeringFormatFor, o as threads, r as openQuestions, s as BUILT_IN_MODE_NAMES, t as parseCodeThreads, u as unansweredQuestions } from "./steering-DkA51qDa.mjs";
4
4
  import { createRequire, isBuiltin } from "node:module";
5
5
  import * as Crypto from "node:crypto";
6
6
  import { createHash } from "node:crypto";
7
- import { PassThrough, finished } from "node:stream";
8
- import * as NodeChildProcess from "node:child_process";
9
- import { execFileSync } from "node:child_process";
10
- import * as NFS from "node:fs";
11
- import { existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
12
- import * as Path from "node:path";
13
- import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
14
7
  import * as OS from "node:os";
15
8
  import { homedir, tmpdir } from "node:os";
16
- import { gzipSync } from "node:zlib";
17
9
  import * as NodeUrl from "node:url";
18
- import { fileURLToPath, pathToFileURL } from "node:url";
10
+ import { fileURLToPath } from "node:url";
19
11
  import { stripVTControlCharacters } from "node:util";
20
12
  import { runInThisContext } from "node:vm";
13
+ import { gzipSync } from "node:zlib";
14
+ import * as NodeChildProcess from "node:child_process";
15
+ import { execFileSync } from "node:child_process";
16
+ import * as NFS from "node:fs";
17
+ import { lstatSync, mkdirSync, readFileSync, readlinkSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
18
+ import { PassThrough, finished } from "node:stream";
19
+ import * as Path from "node:path";
21
20
  import "vitest";
22
21
  //#region node_modules/effect/dist/Pipeable.js
23
22
  /**
@@ -3399,7 +3398,7 @@ const map$6 = /*#__PURE__*/ dual(2, (self, f) => isNone(self) ? none() : some(f(
3399
3398
  * @category sequencing
3400
3399
  * @since 2.0.0
3401
3400
  */
3402
- const flatMap$3 = /*#__PURE__*/ dual(2, (self, f) => isNone(self) ? none() : f(self.value));
3401
+ const flatMap$4 = /*#__PURE__*/ dual(2, (self, f) => isNone(self) ? none() : f(self.value));
3403
3402
  //#endregion
3404
3403
  //#region node_modules/effect/dist/Context.js
3405
3404
  /**
@@ -5636,7 +5635,7 @@ var FiberImpl = class {
5636
5635
  return;
5637
5636
  }
5638
5637
  const interruptChildren = fiberMiddleware.interruptChildren && fiberMiddleware.interruptChildren(this);
5639
- if (interruptChildren !== void 0) return this.evaluate(flatMap$1(interruptChildren, () => exit));
5638
+ if (interruptChildren !== void 0) return this.evaluate(flatMap$2(interruptChildren, () => exit));
5640
5639
  this._exit = exit;
5641
5640
  this.cache.runtimeMetrics?.recordFiberEnd(this.context, this._exit);
5642
5641
  if (this._parent) {
@@ -5671,7 +5670,7 @@ var FiberImpl = class {
5671
5670
  if (!yielding && !cache.preventYield && cache.scheduler.shouldYield(this)) {
5672
5671
  yielding = true;
5673
5672
  const prev = current;
5674
- current = flatMap$1(yieldNow, () => prev);
5673
+ current = flatMap$2(yieldNow, () => prev);
5675
5674
  }
5676
5675
  current = cache.tracerContext ? cache.tracerContext(current, this) : current[evaluate$1](this);
5677
5676
  if (current === Yield) {
@@ -5954,7 +5953,7 @@ const asyncFinalizer = /*#__PURE__*/ makePrimitive({
5954
5953
  }
5955
5954
  },
5956
5955
  [contE](cause, _fiber) {
5957
- return hasInterrupts(cause) ? flatMap$1(combineFinalizerCause(exitFailCause(cause), this[args]()), () => failCause$3(cause)) : failCause$3(cause);
5956
+ return hasInterrupts(cause) ? flatMap$2(combineFinalizerCause(exitFailCause(cause), this[args]()), () => failCause$3(cause)) : failCause$3(cause);
5958
5957
  }
5959
5958
  });
5960
5959
  /** @internal */
@@ -6015,7 +6014,7 @@ const fromIteratorEagerUnsafe = (evaluate) => {
6015
6014
  return suspend$3(() => {
6016
6015
  if (isFirstExecution) {
6017
6016
  isFirstExecution = false;
6018
- return flatMap$1(state.value, (value) => fromIteratorUnsafe(iterator, value));
6017
+ return flatMap$2(state.value, (value) => fromIteratorUnsafe(iterator, value));
6019
6018
  } else return suspend$3(() => fromIteratorUnsafe(evaluate()));
6020
6019
  });
6021
6020
  }
@@ -6138,13 +6137,13 @@ const raceAllFirst = (all, options) => withFiber$1((parent) => {
6138
6137
  /** @internal */
6139
6138
  const raceFirst$1 = /*#__PURE__*/ dual((args) => isEffect(args[1]), (self, that, options) => raceAllFirst([self, that], options));
6140
6139
  /** @internal */
6141
- const flatMap$1 = /*#__PURE__*/ dual(2, (self, f) => new ContImpl(self, andThenCont, f));
6140
+ const flatMap$2 = /*#__PURE__*/ dual(2, (self, f) => new ContImpl(self, andThenCont, f));
6142
6141
  /** @internal */
6143
6142
  const effectIsExit = (effect) => effect[ExitTypeId] !== void 0;
6144
6143
  /** @internal */
6145
6144
  const flatMapEager$1 = /*#__PURE__*/ dual(2, (self, f) => {
6146
6145
  if (effectIsExit(self)) return self._tag === "Success" ? f(self.value) : self;
6147
- return flatMap$1(self, f);
6146
+ return flatMap$2(self, f);
6148
6147
  });
6149
6148
  /** @internal */
6150
6149
  const map$2 = /*#__PURE__*/ dual(2, (self, f) => new ContImpl(self, mapCont, f));
@@ -6195,11 +6194,11 @@ const provideService$1 = function() {
6195
6194
  };
6196
6195
  const provideServiceImpl = (self, service, implementation) => updateContext(self, add$1(service, implementation));
6197
6196
  /** @internal */
6198
- const zipWith$1 = /*#__PURE__*/ dual((args) => isEffect(args[1]), (self, that, f, options) => options?.concurrent ? map$2(all$1([self, that], { concurrency: 2 }), ([a, a2]) => f(a, a2)) : flatMap$1(self, (a) => map$2(that, (a2) => f(a, a2))));
6197
+ const zipWith$1 = /*#__PURE__*/ dual((args) => isEffect(args[1]), (self, that, f, options) => options?.concurrent ? map$2(all$1([self, that], { concurrency: 2 }), ([a, a2]) => f(a, a2)) : flatMap$2(self, (a) => map$2(that, (a2) => f(a, a2))));
6199
6198
  /** @internal */
6200
6199
  const forever$1 = /*#__PURE__*/ dual((args) => isEffect(args[0]), (self, options) => whileLoop$1({
6201
6200
  while: constTrue,
6202
- body: constant(options?.disableYield ? self : flatMap$1(self, (_) => yieldNow)),
6201
+ body: constant(options?.disableYield ? self : flatMap$2(self, (_) => yieldNow)),
6203
6202
  step: constVoid
6204
6203
  }));
6205
6204
  /** @internal */
@@ -6474,7 +6473,7 @@ const scopedWith$1 = (f) => suspend$3(() => {
6474
6473
  return onExit$1(f(scope), (exit) => suspend$3(() => scopeCloseUnsafe(scope, exit) ?? void_$2));
6475
6474
  });
6476
6475
  /** @internal */
6477
- const acquireRelease$1 = (acquire, release, options) => contextWith((context) => uninterruptibleMask((restore) => flatMap$1(scope, (scope) => tap$1(options?.interruptible ? restore(acquire) : acquire, (a) => scopeAddFinalizerExit(scope, (exit) => provideContext(release(a, exit), context))))));
6476
+ const acquireRelease$1 = (acquire, release, options) => contextWith((context) => uninterruptibleMask((restore) => flatMap$2(scope, (scope) => tap$1(options?.interruptible ? restore(acquire) : acquire, (a) => scopeAddFinalizerExit(scope, (exit) => provideContext(release(a, exit), context))))));
6478
6477
  const OnExitImpl = /*#__PURE__*/ function() {
6479
6478
  const Proto = /*#__PURE__*/ makePrimitiveProto({
6480
6479
  op: "OnExit",
@@ -6496,7 +6495,7 @@ const OnExitImpl = /*#__PURE__*/ function() {
6496
6495
  } catch (defect) {
6497
6496
  eff = exitDie(defect);
6498
6497
  }
6499
- return eff ? flatMap$1(eff, (_) => exit) : exit;
6498
+ return eff ? flatMap$2(eff, (_) => exit) : exit;
6500
6499
  },
6501
6500
  [contE](cause, _, exit) {
6502
6501
  exit ??= exitFailCause(cause);
@@ -6506,7 +6505,7 @@ const OnExitImpl = /*#__PURE__*/ function() {
6506
6505
  } catch (defect) {
6507
6506
  eff = exitDie(defect);
6508
6507
  }
6509
- return eff ? flatMap$1(combineFinalizerCause(exit, eff), (_) => exit) : exit;
6508
+ return eff ? flatMap$2(combineFinalizerCause(exit, eff), (_) => exit) : exit;
6510
6509
  }
6511
6510
  });
6512
6511
  const OnExitImpl = function(effect, onExit, interruptible) {
@@ -6641,7 +6640,7 @@ const resolveConcurrency = (concurrency) => concurrency === "unbounded" ? Number
6641
6640
  const iterateEager = () => (options) => {
6642
6641
  const onItem = options.onItem;
6643
6642
  const step = options.step;
6644
- const resumeSequential = (state, items, index, end, effect) => flatMap$1(exit$1(effect), (itemExit) => step(state, items[index], itemExit, index) ?? runSequential(state, items, index + 1, end) ?? void_$2);
6643
+ const resumeSequential = (state, items, index, end, effect) => flatMap$2(exit$1(effect), (itemExit) => step(state, items[index], itemExit, index) ?? runSequential(state, items, index + 1, end) ?? void_$2);
6645
6644
  const runSequential = (state, items, index = 0, end = items.length) => {
6646
6645
  for (; index < end; index++) {
6647
6646
  const item = items[index];
@@ -6670,7 +6669,7 @@ const iterateConcurrentImpl = (options) => {
6670
6669
  const defect = exitDie(error);
6671
6670
  terminal = defect;
6672
6671
  done = true;
6673
- return fibers && fibers.size > 0 ? flatMap$1(uninterruptible(fiberInterruptAll(Array.from(fibers))), () => terminal ?? defect) : defect;
6672
+ return fibers && fibers.size > 0 ? flatMap$2(uninterruptible(fiberInterruptAll(Array.from(fibers))), () => terminal ?? defect) : defect;
6674
6673
  };
6675
6674
  const go = () => {
6676
6675
  let paused = false;
@@ -6694,7 +6693,7 @@ const iterateConcurrentImpl = (options) => {
6694
6693
  if (result) return cb(result);
6695
6694
  return suspend$3(() => {
6696
6695
  terminal ??= exitVoid;
6697
- return flatMap$1(fibers ? fiberInterruptAll(fibers) : void_$2, () => terminal?._tag === "Failure" ? terminal : void_$2);
6696
+ return flatMap$2(fibers ? fiberInterruptAll(fibers) : void_$2, () => terminal?._tag === "Failure" ? terminal : void_$2);
6698
6697
  });
6699
6698
  });
6700
6699
  else {
@@ -6793,7 +6792,7 @@ const forkIn$1 = /*#__PURE__*/ dual((args) => isEffect(args[0]), (self, scope, o
6793
6792
  return succeed$4(fiber);
6794
6793
  }));
6795
6794
  /** @internal */
6796
- const forkScoped$1 = /*#__PURE__*/ dual((args) => isEffect(args[0]), (self, options) => flatMap$1(scope, (scope) => forkIn$1(self, scope, options)));
6795
+ const forkScoped$1 = /*#__PURE__*/ dual((args) => isEffect(args[0]), (self, options) => flatMap$2(scope, (scope) => forkIn$1(self, scope, options)));
6797
6796
  /** @internal */
6798
6797
  const runForkWith = (context) => (effect, options) => {
6799
6798
  const fiber = new FiberImpl(options?.scheduler ? add$1(context, Scheduler, options.scheduler) : context, options?.uninterruptible !== true);
@@ -6843,6 +6842,17 @@ const runSyncExitWith = (context) => {
6843
6842
  };
6844
6843
  /** @internal */
6845
6844
  const runSyncExit$1 = /*#__PURE__*/ runSyncExitWith(/*#__PURE__*/ empty$3());
6845
+ /** @internal */
6846
+ const runSyncWith = (context) => {
6847
+ const runSyncExit = runSyncExitWith(context);
6848
+ return (effect) => {
6849
+ const exit = runSyncExit(effect);
6850
+ if (exit._tag === "Failure") throw causeSquash(exit.cause);
6851
+ return exit.value;
6852
+ };
6853
+ };
6854
+ /** @internal */
6855
+ const runSync$2 = /*#__PURE__*/ runSyncWith(/*#__PURE__*/ empty$3());
6846
6856
  const succeedTrue = /*#__PURE__*/ succeed$4(true);
6847
6857
  const succeedFalse = /*#__PURE__*/ succeed$4(false);
6848
6858
  var Latch = class {
@@ -6903,7 +6913,7 @@ var Latch = class {
6903
6913
  return true;
6904
6914
  }
6905
6915
  close = /*#__PURE__*/ sync$1(() => this.closeUnsafe());
6906
- whenOpen = (self) => flatMap$1(this.await, () => self);
6916
+ whenOpen = (self) => flatMap$2(this.await, () => self);
6907
6917
  isOpen() {
6908
6918
  return this._isOpen;
6909
6919
  }
@@ -8454,6 +8464,43 @@ const effectImpl = (service, effect) => effectContext(map$2(effect, (value) => m
8454
8464
  * @since 2.0.0
8455
8465
  */
8456
8466
  const effectContext = (effect) => fromBuildMemo((_, scope) => provide$2(effect, scope));
8467
+ const unwrapKey = /*#__PURE__*/ Service("effect/Layer/unwrap");
8468
+ /**
8469
+ * Unwraps a `Layer` from an `Effect`, flattening the nested structure.
8470
+ *
8471
+ * **When to use**
8472
+ *
8473
+ * Use when you have an `Effect` that produces a `Layer` and you want to
8474
+ * use that layer directly.
8475
+ *
8476
+ * **Details**
8477
+ *
8478
+ * The resulting Layer will have the combined error and dependency types from
8479
+ * both the outer Effect and the inner Layer.
8480
+ *
8481
+ * **Example** (Unwrapping an effectful layer)
8482
+ *
8483
+ * ```ts import.meta.vitest
8484
+ * import { Context, Effect, Layer } from "effect"
8485
+ *
8486
+ * class Database extends Context.Service<Database, {
8487
+ * readonly query: (sql: string) => Effect.Effect<string>
8488
+ * }>()("Database") {}
8489
+ *
8490
+ * const layerEffect = Effect.succeed(
8491
+ * Layer.succeed(Database, { query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) })
8492
+ * )
8493
+ *
8494
+ * const unwrappedLayer = Layer.unwrap(layerEffect)
8495
+ * const program = Database.use((database) => database.query("SELECT 1"))
8496
+ * Effect.runSync(Effect.provide(program, unwrappedLayer)) // => "result"
8497
+ * ```
8498
+ *
8499
+ * @stability stable
8500
+ * @category converting
8501
+ * @since 4.0.0
8502
+ */
8503
+ const unwrap$6 = (self) => flatMap$1(effect(unwrapKey)(self), get(unwrapKey));
8457
8504
  const mergeAllEffect = (layers, memoMap, scope) => {
8458
8505
  const parentScope = forkUnsafe(scope, "parallel");
8459
8506
  return forEach$2(layers, (layer) => layer.build(memoMap, forkUnsafe(parentScope, "sequential")), { concurrency: layers.length }).pipe(map$2((context) => mergeAll$1(...context)));
@@ -8508,7 +8555,7 @@ const mergeAllEffect = (layers, memoMap, scope) => {
8508
8555
  * @since 2.0.0
8509
8556
  */
8510
8557
  const mergeAll = (...layers) => fromBuildMemo((memoMap, scope) => mergeAllEffect(layers, memoMap, scope));
8511
- const provideWith = (self, that, f) => fromBuildMemo((memoMap, scope) => flatMap$1(Array.isArray(that) ? mergeAllEffect(that, memoMap, scope) : that.build(memoMap, scope), (context) => self.build(memoMap, scope).pipe(provideContext(context), map$2((merged) => f(merged, context)))));
8558
+ const provideWith = (self, that, f) => fromBuildMemo((memoMap, scope) => flatMap$2(Array.isArray(that) ? mergeAllEffect(that, memoMap, scope) : that.build(memoMap, scope), (context) => self.build(memoMap, scope).pipe(provideContext(context), map$2((merged) => f(merged, context)))));
8512
8559
  /**
8513
8560
  * Feeds the output services of the dependency layer into the requirements of
8514
8561
  * this layer, returning a layer that provides both sets of services.
@@ -8597,6 +8644,83 @@ const provideWith = (self, that, f) => fromBuildMemo((memoMap, scope) => flatMap
8597
8644
  * @since 2.0.0
8598
8645
  */
8599
8646
  const provideMerge = /*#__PURE__*/ dual(2, (self, that) => provideWith(self, that, (self, that) => merge$2(that, self)));
8647
+ /**
8648
+ * Constructs a layer dynamically based on the output of this layer.
8649
+ *
8650
+ * **Example** (Creating services from layer output)
8651
+ *
8652
+ * ```ts import.meta.vitest
8653
+ * import { Context, Effect, Layer } from "effect"
8654
+ *
8655
+ * class Config extends Context.Service<Config, {
8656
+ * readonly dbUrl: string
8657
+ * readonly logLevel: string
8658
+ * }>()("Config") {}
8659
+ *
8660
+ * class Database extends Context.Service<Database, {
8661
+ * readonly query: (sql: string) => Effect.Effect<string>
8662
+ * }>()("Database") {}
8663
+ *
8664
+ * class Logger extends Context.Service<Logger, {
8665
+ * readonly log: (msg: string) => Effect.Effect<void>
8666
+ * }>()("Logger") {}
8667
+ *
8668
+ * const logs: Array<string> = []
8669
+ *
8670
+ * // Base config layer
8671
+ * const configLayer = Layer.succeed(Config, {
8672
+ * dbUrl: "postgres://localhost:5432/mydb",
8673
+ * logLevel: "debug"
8674
+ * })
8675
+ *
8676
+ * // Dynamically create services based on config
8677
+ * const dynamicServiceLayer = configLayer.pipe(
8678
+ * Layer.flatMap((context) => {
8679
+ * const config = Context.get(context, Config)
8680
+ *
8681
+ * // Create database layer based on config
8682
+ * const dbLayer = Layer.succeed(Database, {
8683
+ * query: Effect.fn("Database.query")((sql: string) =>
8684
+ * Effect.succeed(
8685
+ * `Querying ${config.dbUrl}: ${sql}`
8686
+ * ))
8687
+ * })
8688
+ *
8689
+ * // Create logger layer based on config
8690
+ * const loggerLayer = Layer.succeed(Logger, {
8691
+ * log: Effect.fn("Logger.log")((msg: string) =>
8692
+ * config.logLevel === "debug"
8693
+ * ? Effect.sync(() => logs.push(`[DEBUG] ${msg}`))
8694
+ * : Effect.sync(() => logs.push(msg))
8695
+ * )
8696
+ * })
8697
+ *
8698
+ * // Return combined layer
8699
+ * return Layer.mergeAll(dbLayer, loggerLayer)
8700
+ * })
8701
+ * )
8702
+ *
8703
+ * // Use the dynamic services
8704
+ * const program = Effect.gen(function*() {
8705
+ * const database = yield* Database
8706
+ * const logger = yield* Logger
8707
+ *
8708
+ * yield* logger.log("Starting database query")
8709
+ * const result = yield* database.query("SELECT * FROM users")
8710
+ *
8711
+ * return result
8712
+ * }).pipe(
8713
+ * Effect.provide(dynamicServiceLayer)
8714
+ * )
8715
+ * Effect.runSync(program) // => "Querying postgres://localhost:5432/mydb: SELECT * FROM users"
8716
+ * logs // => ["[DEBUG] Starting database query"]
8717
+ * ```
8718
+ *
8719
+ * @stability stable
8720
+ * @category sequencing
8721
+ * @since 2.0.0
8722
+ */
8723
+ const flatMap$1 = /*#__PURE__*/ dual(2, (self, f) => fromBuild((memoMap, scope) => flatMap$2(self.build(memoMap, scope), (context) => f(context).build(memoMap, scope))));
8600
8724
  //#endregion
8601
8725
  //#region node_modules/effect/dist/internal/dateTime.js
8602
8726
  /** @internal */
@@ -8807,7 +8931,7 @@ const matchEffect$1 = /*#__PURE__*/ dual(2, (self, options) => matchCauseEffect$
8807
8931
  }));
8808
8932
  //#endregion
8809
8933
  //#region node_modules/effect/dist/internal/layer.js
8810
- const provideLayer = (self, layer, options) => scopedWith$1((scope) => flatMap$1(options?.local ? buildWithMemoMap(layer, makeMemoMapUnsafe(), scope) : buildWithScope(layer, scope), (context) => provideContext(self, context)));
8934
+ const provideLayer = (self, layer, options) => scopedWith$1((scope) => flatMap$2(options?.local ? buildWithMemoMap(layer, makeMemoMapUnsafe(), scope) : buildWithScope(layer, scope), (context) => provideContext(self, context)));
8811
8935
  /** @internal */
8812
8936
  const provide$1 = /*#__PURE__*/ dual((args) => isEffect(args[0]), (self, source, options) => isContext(source) ? provideContext(self, source) : provideLayer(self, Array.isArray(source) ? mergeAll(...source) : source, options));
8813
8937
  //#endregion
@@ -9681,7 +9805,7 @@ const withFiber = withFiber$1;
9681
9805
  * @category sequencing
9682
9806
  * @since 2.0.0
9683
9807
  */
9684
- const flatMap = flatMap$1;
9808
+ const flatMap = flatMap$2;
9685
9809
  /**
9686
9810
  * Runs this effect and then runs another effect, optionally using the first
9687
9811
  * effect's success value to choose the next effect.
@@ -11221,6 +11345,65 @@ const runFork = runFork$1;
11221
11345
  */
11222
11346
  const runPromise = runPromise$1;
11223
11347
  /**
11348
+ * Executes an effect synchronously and returns its success value.
11349
+ *
11350
+ * **When to use**
11351
+ *
11352
+ * Use when you need to execute an effect that is guaranteed to complete
11353
+ * synchronously.
11354
+ *
11355
+ * **Details**
11356
+ *
11357
+ * If the effect fails, dies, or is interrupted, `runSync` throws the squashed
11358
+ * cause instead of returning a value. Typed errors can be thrown directly,
11359
+ * including non-Error values. Asynchronous work throws `Cause.AsyncFiberError`.
11360
+ * Use `runSyncExit` when you want the failure captured as an `Exit`.
11361
+ *
11362
+ * **Example** (Running a synchronous effect)
11363
+ *
11364
+ * ```ts import.meta.vitest
11365
+ * import { Effect } from "effect"
11366
+ * const output: Array<unknown> = []
11367
+ *
11368
+ * const program = Effect.sync(() => {
11369
+ * void output.push("Hello, World!")
11370
+ * return 1
11371
+ * })
11372
+ *
11373
+ * const result = Effect.runSync(program)
11374
+ * void output.push(result)
11375
+ * output // => ["Hello, World!", 1]
11376
+ * ```
11377
+ *
11378
+ * **Example** (Throwing for failed or async effects)
11379
+ *
11380
+ * ```ts import.meta.vitest
11381
+ * import { Effect } from "effect"
11382
+ * const output: Array<unknown> = []
11383
+ *
11384
+ * try {
11385
+ * // Attempt to run an effect that fails
11386
+ * Effect.runSync(Effect.fail("my error"))
11387
+ * } catch (e) {
11388
+ * void output.push("failed effect")
11389
+ * }
11390
+ * try {
11391
+ * // Attempt to run an effect that involves async work
11392
+ * Effect.runSync(Effect.promise(() => Promise.resolve(1)))
11393
+ * } catch (e) {
11394
+ * void output.push("async effect")
11395
+ * }
11396
+ * output // => ["failed effect", "async effect"]
11397
+ * ```
11398
+ *
11399
+ * @see {@link runSyncExit} for a version that returns an `Exit` type instead of
11400
+ * throwing an error.
11401
+ * @stability stable
11402
+ * @category running
11403
+ * @since 2.0.0
11404
+ */
11405
+ const runSync$1 = runSync$2;
11406
+ /**
11224
11407
  * Runs an effect synchronously and captures the outcome safely as an `Exit` type, which
11225
11408
  * represents the outcome (success or failure) of the effect.
11226
11409
  *
@@ -13265,7 +13448,7 @@ const coerce = (value) => isRedacted(value) ? value : String(value);
13265
13448
  //#endregion
13266
13449
  //#region node_modules/effect/dist/internal/schema/annotations.js
13267
13450
  /** @internal */
13268
- function resolve$2(ast) {
13451
+ function resolve$3(ast) {
13269
13452
  return ast.checks ? ast.checks[ast.checks.length - 1].annotations : ast.annotations;
13270
13453
  }
13271
13454
  /** @internal */
@@ -13274,7 +13457,7 @@ const SENTINELS_ANNOTATION_KEY = "~sentinels";
13274
13457
  const CONSTRUCTOR_ANNOTATION_KEY = "~constructor";
13275
13458
  /** @internal */
13276
13459
  const getExpected = /*#__PURE__*/ memoize((ast) => {
13277
- const identifier = resolve$2(ast)?.identifier;
13460
+ const identifier = resolve$3(ast)?.identifier;
13278
13461
  if (typeof identifier === "string") return identifier;
13279
13462
  return ast.getExpected(getExpected);
13280
13463
  });
@@ -15993,8 +16176,8 @@ function compile$1(ast, compile, compileField, base, specialize) {
15993
16176
  /** @internal */
15994
16177
  const invalid = /*#__PURE__*/ Symbol();
15995
16178
  const cache = /*#__PURE__*/ new WeakMap();
15996
- const decodeChild = (ast) => resolve$1(ast).parser;
15997
- const makeChild = (ast) => resolve$1(ast).makeEffect;
16179
+ const decodeChild = (ast) => resolve$2(ast).parser;
16180
+ const makeChild = (ast) => resolve$2(ast).makeEffect;
15998
16181
  const makeField = (ast) => compileField(ast, makeChild);
15999
16182
  var InterpretedEntry = class {
16000
16183
  ast;
@@ -16012,7 +16195,7 @@ var InterpretedEntry = class {
16012
16195
  }
16013
16196
  };
16014
16197
  /** @internal */
16015
- function resolve$1(ast) {
16198
+ function resolve$2(ast) {
16016
16199
  const cached = cache.get(ast);
16017
16200
  if (cached !== void 0) return cached;
16018
16201
  const entry = new InterpretedEntry(ast);
@@ -16183,7 +16366,7 @@ function throwConstructorDefect(error) {
16183
16366
  }
16184
16367
  function makeConstructorSync(ast) {
16185
16368
  let entry;
16186
- return (input, options) => constructSync(entry ??= resolve$1(ast), input, options);
16369
+ return (input, options) => constructSync(entry ??= resolve$2(ast), input, options);
16187
16370
  }
16188
16371
  function constructSync(entry, input, options) {
16189
16372
  const parseOptions = options?.disableChecks ? options.parseOptions ? {
@@ -16202,8 +16385,8 @@ function constructSync(entry, input, options) {
16202
16385
  }
16203
16386
  return runSync(parserResult(entry.makeEffect(input, parseOptions), input), constructorDefectMessage);
16204
16387
  }
16205
- const normalCompiler = (ast) => resolve$1(ast).parser;
16206
- const constructorCompiler = (ast) => resolve$1(ast).makeEffect;
16388
+ const normalCompiler = (ast) => resolve$2(ast).parser;
16389
+ const constructorCompiler = (ast) => resolve$2(ast).makeEffect;
16207
16390
  //#endregion
16208
16391
  //#region node_modules/effect/dist/internal/schema/make.js
16209
16392
  /** @internal */
@@ -17627,7 +17810,7 @@ const splitSegments = (pattern) => {
17627
17810
  const drive = /^[A-Za-z]:/;
17628
17811
  const isDrive = (segment) => segment.length === 2 && drive.test(segment);
17629
17812
  const isRoot = (name) => name === "" || name.length === 3 && name[2] === "/" && drive.test(name);
17630
- const isAbsolute$1 = (path) => path.startsWith("/") || path[2] === "/" && drive.test(path);
17813
+ const isAbsolute = (path) => path.startsWith("/") || path[2] === "/" && drive.test(path);
17631
17814
  const normalize = (raw) => {
17632
17815
  const segments = [];
17633
17816
  let i = 0;
@@ -17970,7 +18153,7 @@ const needsDirectory = (compiled, states) => {
17970
18153
  }
17971
18154
  return false;
17972
18155
  };
17973
- const join$1 = (parent, name) => {
18156
+ const join = (parent, name) => {
17974
18157
  if (parent === "") return name === "" ? "/" : name;
17975
18158
  return parent.endsWith("/") ? parent + name : `${parent}/${name}`;
17976
18159
  };
@@ -17985,10 +18168,10 @@ const walk = /*#__PURE__*/ fnUntraced(function* (fs, compiled, options) {
17985
18168
  const root = options?.root ?? "";
17986
18169
  const excludes = compile(options?.exclude ?? [], true);
17987
18170
  const excludeNeedsDirectory = excludes.nodes.some((node) => node._tag === "End" && node.directoryOnly);
17988
- const toFsPath = (relative) => isAbsolute$1(relative) ? relative : relative === "" ? root === "" ? "." : root : join$1(root, relative);
18171
+ const toFsPath = (relative) => isAbsolute(relative) ? relative : relative === "" ? root === "" ? "." : root : join(root, relative);
17989
18172
  const isUnlisted = (value) => value === "." || value === ".." || isRoot(value);
17990
18173
  const probe = (parent, name, named) => {
17991
- const path = toFsPath(join$1(parent, name));
18174
+ const path = toFsPath(join(parent, name));
17992
18175
  return fs.stat(path).pipe(map((info) => info.type), catchReason("PlatformError", "NotFound", () => fs.readLink(path).pipe(as("SymbolicLink"), catchIf(isNotLink, () => succeed$1(void 0)))), catchIf(named ? isMissing : isIgnorable, () => succeed$1(void 0)), map((type) => type === void 0 ? void 0 : {
17993
18176
  name,
17994
18177
  type
@@ -18012,7 +18195,7 @@ const walk = /*#__PURE__*/ fnUntraced(function* (fs, compiled, options) {
18012
18195
  const result = step(compiled, states, name);
18013
18196
  const next = union(result);
18014
18197
  if (next.size === 0) return;
18015
- const relative = join$1(parent, name);
18198
+ const relative = join(parent, name);
18016
18199
  const nextExcluded = union(step(excludes, excluded, name));
18017
18200
  const descend = canDescend(compiled, next);
18018
18201
  const isDirectory = !descend && !needsDirectory(compiled, next) && !excludeNeedsDirectory ? false : type !== "Unknown" && type !== "SymbolicLink" ? type === "Directory" : yield* fs.stat(toFsPath(relative)).pipe(map((info) => info.type === "Directory"), catchIf(isIgnorable, () => succeed$1(false)));
@@ -19295,6 +19478,388 @@ const make$3 = (impl) => Path$1.of({
19295
19478
  return relative === ".." || relative.startsWith(`..${impl.sep}`) || impl.isAbsolute(relative) ? none() : some(candidate);
19296
19479
  }
19297
19480
  });
19481
+ /**
19482
+ * The following functions are adapted from the Node.js source code:
19483
+ * https://github.com/nodejs/node/blob/main/lib/internal/url.js
19484
+ *
19485
+ * The following license applies to these functions:
19486
+ * - MIT
19487
+ */
19488
+ function normalizeStringPosix(path, allowAboveRoot) {
19489
+ let res = "";
19490
+ let lastSegmentLength = 0;
19491
+ let lastSlash = -1;
19492
+ let dots = 0;
19493
+ let code;
19494
+ for (let i = 0; i <= path.length; ++i) {
19495
+ if (i < path.length) code = path.charCodeAt(i);
19496
+ else if (code === 47) break;
19497
+ else code = 47;
19498
+ if (code === 47) {
19499
+ if (lastSlash === i - 1 || dots === 1) {} else if (lastSlash !== i - 1 && dots === 2) {
19500
+ if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 || res.charCodeAt(res.length - 2) !== 46) {
19501
+ if (res.length > 2) {
19502
+ const lastSlashIndex = res.lastIndexOf("/");
19503
+ if (lastSlashIndex !== res.length - 1) {
19504
+ if (lastSlashIndex === -1) {
19505
+ res = "";
19506
+ lastSegmentLength = 0;
19507
+ } else {
19508
+ res = res.slice(0, lastSlashIndex);
19509
+ lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
19510
+ }
19511
+ lastSlash = i;
19512
+ dots = 0;
19513
+ continue;
19514
+ }
19515
+ } else if (res.length === 2 || res.length === 1) {
19516
+ res = "";
19517
+ lastSegmentLength = 0;
19518
+ lastSlash = i;
19519
+ dots = 0;
19520
+ continue;
19521
+ }
19522
+ }
19523
+ if (allowAboveRoot) {
19524
+ if (res.length > 0) res += "/..";
19525
+ else res = "..";
19526
+ lastSegmentLength = 2;
19527
+ }
19528
+ } else {
19529
+ if (res.length > 0) res += "/" + path.slice(lastSlash + 1, i);
19530
+ else res = path.slice(lastSlash + 1, i);
19531
+ lastSegmentLength = i - lastSlash - 1;
19532
+ }
19533
+ lastSlash = i;
19534
+ dots = 0;
19535
+ } else if (code === 46 && dots !== -1) ++dots;
19536
+ else dots = -1;
19537
+ }
19538
+ return res;
19539
+ }
19540
+ function _format(sep, pathObject) {
19541
+ const dir = pathObject.dir || pathObject.root;
19542
+ const base = pathObject.base || (pathObject.name || "") + (pathObject.ext || "");
19543
+ if (!dir) return base;
19544
+ if (dir === pathObject.root) return dir + base;
19545
+ return dir + sep + base;
19546
+ }
19547
+ function fromFileUrl(url) {
19548
+ if (url.protocol !== "file:") return fail$4(new BadArgument({
19549
+ module: "Path",
19550
+ method: "fromFileUrl",
19551
+ description: "URL must be of scheme file"
19552
+ }));
19553
+ else if (url.hostname !== "") return fail$4(new BadArgument({
19554
+ module: "Path",
19555
+ method: "fromFileUrl",
19556
+ description: "Invalid file URL host"
19557
+ }));
19558
+ const pathname = url.pathname;
19559
+ for (let n = 0; n < pathname.length; n++) if (pathname[n] === "%") {
19560
+ const third = pathname.codePointAt(n + 2) | 32;
19561
+ if (pathname[n + 1] === "2" && third === 102) return fail$4(new BadArgument({
19562
+ module: "Path",
19563
+ method: "fromFileUrl",
19564
+ description: "must not include encoded / characters"
19565
+ }));
19566
+ }
19567
+ return try_({
19568
+ try: () => decodeURIComponent(pathname),
19569
+ catch: (cause) => new BadArgument({
19570
+ module: "Path",
19571
+ method: "fromFileUrl",
19572
+ cause
19573
+ })
19574
+ });
19575
+ }
19576
+ const resolve$1 = function resolve() {
19577
+ let resolvedPath = "";
19578
+ let resolvedAbsolute = false;
19579
+ let cwd = void 0;
19580
+ for (let i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
19581
+ let path;
19582
+ if (i >= 0) path = arguments[i];
19583
+ else {
19584
+ const process = globalThis.process;
19585
+ if (cwd === void 0 && "process" in globalThis && typeof process === "object" && process !== null && typeof process.cwd === "function") cwd = process.cwd();
19586
+ path = cwd;
19587
+ }
19588
+ if (path.length === 0) continue;
19589
+ resolvedPath = path + "/" + resolvedPath;
19590
+ resolvedAbsolute = path.charCodeAt(0) === 47;
19591
+ }
19592
+ resolvedPath = normalizeStringPosix(resolvedPath, !resolvedAbsolute);
19593
+ if (resolvedAbsolute) if (resolvedPath.length > 0) return "/" + resolvedPath;
19594
+ else return "/";
19595
+ else if (resolvedPath.length > 0) return resolvedPath;
19596
+ else return ".";
19597
+ };
19598
+ const CHAR_FORWARD_SLASH = 47;
19599
+ function toFileUrl(filepath) {
19600
+ const outURL = new URL("file://");
19601
+ let resolved = resolve$1(filepath);
19602
+ if (filepath.charCodeAt(filepath.length - 1) === CHAR_FORWARD_SLASH && resolved[resolved.length - 1] !== "/") resolved += "/";
19603
+ outURL.pathname = encodePathChars(resolved);
19604
+ return succeed$1(outURL);
19605
+ }
19606
+ const percentRegExp = /%/g;
19607
+ const backslashRegExp = /\\/g;
19608
+ const newlineRegExp = /\n/g;
19609
+ const carriageReturnRegExp = /\r/g;
19610
+ const tabRegExp = /\t/g;
19611
+ function encodePathChars(filepath) {
19612
+ if (filepath.includes("%")) filepath = filepath.replace(percentRegExp, "%25");
19613
+ if (filepath.includes("\\")) filepath = filepath.replace(backslashRegExp, "%5C");
19614
+ if (filepath.includes("\n")) filepath = filepath.replace(newlineRegExp, "%0A");
19615
+ if (filepath.includes("\r")) filepath = filepath.replace(carriageReturnRegExp, "%0D");
19616
+ if (filepath.includes(" ")) filepath = filepath.replace(tabRegExp, "%09");
19617
+ return filepath;
19618
+ }
19619
+ const posixImpl = /*#__PURE__*/ make$3({
19620
+ resolve: resolve$1,
19621
+ normalize(path) {
19622
+ if (path.length === 0) return ".";
19623
+ const isAbsolute = path.charCodeAt(0) === 47;
19624
+ const trailingSeparator = path.charCodeAt(path.length - 1) === 47;
19625
+ path = normalizeStringPosix(path, !isAbsolute);
19626
+ if (path.length === 0 && !isAbsolute) path = ".";
19627
+ if (path.length > 0 && trailingSeparator) path += "/";
19628
+ if (isAbsolute) return "/" + path;
19629
+ return path;
19630
+ },
19631
+ isAbsolute(path) {
19632
+ return path.length > 0 && path.charCodeAt(0) === 47;
19633
+ },
19634
+ join() {
19635
+ if (arguments.length === 0) return ".";
19636
+ let joined;
19637
+ for (let i = 0; i < arguments.length; ++i) {
19638
+ const arg = arguments[i];
19639
+ if (arg.length > 0) if (joined === void 0) joined = arg;
19640
+ else joined += "/" + arg;
19641
+ }
19642
+ if (joined === void 0) return ".";
19643
+ return posixImpl.normalize(joined);
19644
+ },
19645
+ relative(from, to) {
19646
+ if (from === to) return "";
19647
+ from = posixImpl.resolve(from);
19648
+ to = posixImpl.resolve(to);
19649
+ if (from === to) return "";
19650
+ let fromStart = 1;
19651
+ for (; fromStart < from.length; ++fromStart) if (from.charCodeAt(fromStart) !== 47) break;
19652
+ const fromEnd = from.length;
19653
+ const fromLen = fromEnd - fromStart;
19654
+ let toStart = 1;
19655
+ for (; toStart < to.length; ++toStart) if (to.charCodeAt(toStart) !== 47) break;
19656
+ const toLen = to.length - toStart;
19657
+ const length = fromLen < toLen ? fromLen : toLen;
19658
+ let lastCommonSep = -1;
19659
+ let i = 0;
19660
+ for (; i <= length; ++i) {
19661
+ if (i === length) {
19662
+ if (toLen > length) {
19663
+ if (to.charCodeAt(toStart + i) === 47) return to.slice(toStart + i + 1);
19664
+ else if (i === 0) return to.slice(toStart + i);
19665
+ } else if (fromLen > length) {
19666
+ if (from.charCodeAt(fromStart + i) === 47) lastCommonSep = i;
19667
+ else if (i === 0) lastCommonSep = 0;
19668
+ }
19669
+ break;
19670
+ }
19671
+ const fromCode = from.charCodeAt(fromStart + i);
19672
+ if (fromCode !== to.charCodeAt(toStart + i)) break;
19673
+ else if (fromCode === 47) lastCommonSep = i;
19674
+ }
19675
+ let out = "";
19676
+ for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) if (i === fromEnd || from.charCodeAt(i) === 47) if (out.length === 0) out += "..";
19677
+ else out += "/..";
19678
+ if (out.length > 0) return out + to.slice(toStart + lastCommonSep);
19679
+ else {
19680
+ toStart += lastCommonSep;
19681
+ if (to.charCodeAt(toStart) === 47) ++toStart;
19682
+ return to.slice(toStart);
19683
+ }
19684
+ },
19685
+ dirname(path) {
19686
+ if (path.length === 0) return ".";
19687
+ let code = path.charCodeAt(0);
19688
+ const hasRoot = code === 47;
19689
+ let end = -1;
19690
+ let matchedSlash = true;
19691
+ for (let i = path.length - 1; i >= 1; --i) {
19692
+ code = path.charCodeAt(i);
19693
+ if (code === 47) {
19694
+ if (!matchedSlash) {
19695
+ end = i;
19696
+ break;
19697
+ }
19698
+ } else matchedSlash = false;
19699
+ }
19700
+ if (end === -1) return hasRoot ? "/" : ".";
19701
+ if (hasRoot && end === 1) return "//";
19702
+ return path.slice(0, end);
19703
+ },
19704
+ basename(path, ext) {
19705
+ let start = 0;
19706
+ let end = -1;
19707
+ let matchedSlash = true;
19708
+ let i;
19709
+ if (ext !== void 0 && ext.length > 0 && ext.length <= path.length) {
19710
+ if (ext.length === path.length && ext === path) return "";
19711
+ let extIdx = ext.length - 1;
19712
+ let firstNonSlashEnd = -1;
19713
+ for (i = path.length - 1; i >= 0; --i) {
19714
+ const code = path.charCodeAt(i);
19715
+ if (code === 47) {
19716
+ if (!matchedSlash) {
19717
+ start = i + 1;
19718
+ break;
19719
+ }
19720
+ } else {
19721
+ if (firstNonSlashEnd === -1) {
19722
+ matchedSlash = false;
19723
+ firstNonSlashEnd = i + 1;
19724
+ }
19725
+ if (extIdx >= 0) if (code === ext.charCodeAt(extIdx)) {
19726
+ if (--extIdx === -1) end = i;
19727
+ } else {
19728
+ extIdx = -1;
19729
+ end = firstNonSlashEnd;
19730
+ }
19731
+ }
19732
+ }
19733
+ if (start === end) end = firstNonSlashEnd;
19734
+ else if (end === -1) end = path.length;
19735
+ return path.slice(start, end);
19736
+ } else {
19737
+ for (i = path.length - 1; i >= 0; --i) if (path.charCodeAt(i) === 47) {
19738
+ if (!matchedSlash) {
19739
+ start = i + 1;
19740
+ break;
19741
+ }
19742
+ } else if (end === -1) {
19743
+ matchedSlash = false;
19744
+ end = i + 1;
19745
+ }
19746
+ if (end === -1) return "";
19747
+ return path.slice(start, end);
19748
+ }
19749
+ },
19750
+ extname(path) {
19751
+ let startDot = -1;
19752
+ let startPart = 0;
19753
+ let end = -1;
19754
+ let matchedSlash = true;
19755
+ let preDotState = 0;
19756
+ for (let i = path.length - 1; i >= 0; --i) {
19757
+ const code = path.charCodeAt(i);
19758
+ if (code === 47) {
19759
+ if (!matchedSlash) {
19760
+ startPart = i + 1;
19761
+ break;
19762
+ }
19763
+ continue;
19764
+ }
19765
+ if (end === -1) {
19766
+ matchedSlash = false;
19767
+ end = i + 1;
19768
+ }
19769
+ if (code === 46) {
19770
+ if (startDot === -1) startDot = i;
19771
+ else if (preDotState !== 1) preDotState = 1;
19772
+ } else if (startDot !== -1) preDotState = -1;
19773
+ }
19774
+ if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) return "";
19775
+ return path.slice(startDot, end);
19776
+ },
19777
+ format: function format(pathObject) {
19778
+ if (pathObject === null || typeof pathObject !== "object") throw new TypeError("The \"pathObject\" argument must be of type Object. Received type " + typeof pathObject);
19779
+ return _format("/", pathObject);
19780
+ },
19781
+ parse(path) {
19782
+ const ret = {
19783
+ root: "",
19784
+ dir: "",
19785
+ base: "",
19786
+ ext: "",
19787
+ name: ""
19788
+ };
19789
+ if (path.length === 0) return ret;
19790
+ let code = path.charCodeAt(0);
19791
+ const isAbsolute = code === 47;
19792
+ let start;
19793
+ if (isAbsolute) {
19794
+ ret.root = "/";
19795
+ start = 1;
19796
+ } else start = 0;
19797
+ let startDot = -1;
19798
+ let startPart = 0;
19799
+ let end = -1;
19800
+ let matchedSlash = true;
19801
+ let i = path.length - 1;
19802
+ let preDotState = 0;
19803
+ for (; i >= start; --i) {
19804
+ code = path.charCodeAt(i);
19805
+ if (code === 47) {
19806
+ if (!matchedSlash) {
19807
+ startPart = i + 1;
19808
+ break;
19809
+ }
19810
+ continue;
19811
+ }
19812
+ if (end === -1) {
19813
+ matchedSlash = false;
19814
+ end = i + 1;
19815
+ }
19816
+ if (code === 46) {
19817
+ if (startDot === -1) startDot = i;
19818
+ else if (preDotState !== 1) preDotState = 1;
19819
+ } else if (startDot !== -1) preDotState = -1;
19820
+ }
19821
+ if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
19822
+ if (end !== -1) if (startPart === 0 && isAbsolute) ret.base = ret.name = path.slice(1, end);
19823
+ else ret.base = ret.name = path.slice(startPart, end);
19824
+ } else {
19825
+ if (startPart === 0 && isAbsolute) {
19826
+ ret.name = path.slice(1, startDot);
19827
+ ret.base = path.slice(1, end);
19828
+ } else {
19829
+ ret.name = path.slice(startPart, startDot);
19830
+ ret.base = path.slice(startPart, end);
19831
+ }
19832
+ ret.ext = path.slice(startDot, end);
19833
+ }
19834
+ if (startPart > 0) ret.dir = path.slice(0, startPart - 1);
19835
+ else if (isAbsolute) ret.dir = "/";
19836
+ return ret;
19837
+ },
19838
+ sep: "/",
19839
+ fromFileUrl,
19840
+ toFileUrl,
19841
+ toNamespacedPath: identity
19842
+ });
19843
+ /**
19844
+ * Layer that provides the built-in POSIX `Path` implementation.
19845
+ *
19846
+ * **When to use**
19847
+ *
19848
+ * Use when you need an effect that requires the `Path` service to run with the
19849
+ * built-in POSIX path implementation.
19850
+ *
19851
+ * **Details**
19852
+ *
19853
+ * The layer provides a static service whose separator is `/` and whose
19854
+ * operations use POSIX path semantics.
19855
+ *
19856
+ * @see {@link Path} for accessing the `Path` service from an effect
19857
+ *
19858
+ * @stability unstable
19859
+ * @category layers
19860
+ * @since 4.0.0
19861
+ */
19862
+ const layer$4 = /*#__PURE__*/ succeed$2(Path$1)(posixImpl);
19298
19863
  //#endregion
19299
19864
  //#region node_modules/effect/dist/process/ChildProcessSpawner.js
19300
19865
  /**
@@ -19843,20 +20408,95 @@ const shellScript = (templates, expressions) => {
19843
20408
  return script;
19844
20409
  };
19845
20410
  //#endregion
20411
+ //#region src/platform/NodeRuntime.ts
20412
+ const gzip = (source) => gzipSync(source, { level: 9 });
20413
+ /** Compiles `source` as a script in this realm, reporting errors against `filename`. */
20414
+ const compileScript = (source, filename) => runInThisContext(source, { filename });
20415
+ //#endregion
20416
+ //#region src/platform/NodeSync.ts
20417
+ const MAX_BUFFER = 256 * 1024 * 1024;
20418
+ const isAbsent = (e) => {
20419
+ const code = e.code;
20420
+ return code === "ENOENT" || code === "ENOTDIR";
20421
+ };
20422
+ const orAbsent = (read) => {
20423
+ try {
20424
+ return read();
20425
+ } catch (e) {
20426
+ if (isAbsent(e)) return void 0;
20427
+ throw e;
20428
+ }
20429
+ };
20430
+ const infoOf = (stats) => ({
20431
+ type: stats.isSymbolicLink() ? "symlink" : stats.isDirectory() ? "directory" : stats.isFile() ? "file" : "other",
20432
+ uid: process.platform === "win32" ? void 0 : stats.uid
20433
+ });
20434
+ const nodeSyncIo = {
20435
+ readFile: (path) => orAbsent(() => readFileSync(path)),
20436
+ readText: (path) => orAbsent(() => readFileSync(path, "utf8")),
20437
+ writeText: (path, content) => writeFileSync(path, content, "utf8"),
20438
+ makeDirectory: (path) => {
20439
+ mkdirSync(path, { recursive: true });
20440
+ },
20441
+ rename: renameSync,
20442
+ stat: (path) => orAbsent(() => infoOf(statSync(path))),
20443
+ lstat: (path) => orAbsent(() => infoOf(lstatSync(path))),
20444
+ readLink: (path) => readlinkSync(path, "utf8"),
20445
+ realPath: (path) => realpathSync(path),
20446
+ git: (cwd, args) => execFileSync("git", args, {
20447
+ cwd,
20448
+ encoding: "utf8",
20449
+ maxBuffer: MAX_BUFFER
20450
+ }),
20451
+ gitBytes: (cwd, args) => execFileSync("git", args, {
20452
+ cwd,
20453
+ maxBuffer: MAX_BUFFER,
20454
+ stdio: [
20455
+ "ignore",
20456
+ "pipe",
20457
+ "ignore"
20458
+ ]
20459
+ }),
20460
+ hash: (algorithm, ...parts) => {
20461
+ const hash = createHash(algorithm);
20462
+ for (const part of parts) hash.update(part);
20463
+ return hash.digest("hex");
20464
+ }
20465
+ };
20466
+ //#endregion
19846
20467
  //#region src/platform/Host.ts
19847
20468
  const toError$3 = (e) => e instanceof Error ? e : new Error(String(e));
19848
- const makeHostOps = (opts) => ({
19849
- root: opts.root,
19850
- home: opts.home,
19851
- env: opts.env,
19852
- scratchDir: opts.scratchDir ?? (opts.env["TMPDIR"] !== void 0 && opts.env["TMPDIR"].length > 0 ? opts.env["TMPDIR"] : tmpdir()),
19853
- realPath: opts.realPath ?? ((path) => try_({
19854
- try: () => realpathSync(path),
19855
- catch: toError$3
19856
- }))
20469
+ /** `TMPDIR` when set and non-empty, else the host's own `fallback`. */
20470
+ const scratchDirOf = (env, fallback) => {
20471
+ const dir = env["TMPDIR"];
20472
+ return dir !== void 0 && dir.length > 0 ? dir : fallback;
20473
+ };
20474
+ const readAll = (stream) => tryPromise({
20475
+ try: async () => {
20476
+ const chunks = [];
20477
+ for await (const chunk of stream) chunks.push(Buffer.from(chunk));
20478
+ return Buffer.concat(chunks).toString("utf8");
20479
+ },
20480
+ catch: toError$3
19857
20481
  });
20482
+ const makeHostOps = (opts, proc) => {
20483
+ const script = proc.argv[1];
20484
+ return {
20485
+ scratchDir: scratchDirOf(opts.env, tmpdir()),
20486
+ realPath: (path) => try_({
20487
+ try: () => nodeSyncIo.realPath(path),
20488
+ catch: toError$3
20489
+ }),
20490
+ pid: proc.pid,
20491
+ uid: proc.getuid?.(),
20492
+ self: script === void 0 ? ["gtd"] : [proc.execPath, script],
20493
+ stdin: suspend$2(() => readAll(proc.stdin)),
20494
+ ...opts
20495
+ };
20496
+ };
20497
+ /** Every member not given in `opts` comes from Node: `proc`, its OS and its file system. */
19858
20498
  var Host = class Host extends Service()("Host") {
19859
- static layer = (opts) => succeed$2(Host, makeHostOps(opts));
20499
+ static layer = (opts, proc = process) => succeed$2(Host, makeHostOps(opts, proc));
19860
20500
  static Live = Host.layer({
19861
20501
  root: process.cwd(),
19862
20502
  home: homedir(),
@@ -20135,16 +20775,14 @@ var GitService = class GitService extends Service()("GitService") {
20135
20775
  //#endregion
20136
20776
  //#region src/platform/Workspace.ts
20137
20777
  const toError$2 = (e) => e instanceof Error ? e : new Error(String(e));
20138
- const MAX_BUFFER = 256 * 1024 * 1024;
20139
20778
  const HASH_CHUNK = 500;
20779
+ const encoder$2 = new TextEncoder();
20140
20780
  /** A full sha1 or sha256 object id — the only refs an immutable, disk-cached read is safe to key on. */
20141
20781
  const isObjectHash = (ref) => /^[0-9a-f]{40}$|^[0-9a-f]{64}$/.test(ref);
20142
20782
  const GTD_CACHE_SUBDIR = "gtd-cache";
20143
- const treeCachePath = (commonDir, hash) => join(commonDir, GTD_CACHE_SUBDIR, "trees", hash);
20144
- const blobCachePath = (commonDir, id) => join(commonDir, GTD_CACHE_SUBDIR, "blobs", id);
20145
- const readTextCache = (path) => {
20783
+ const readTextCache = (io, path) => {
20146
20784
  try {
20147
- return readFileSync(path, "utf8");
20785
+ return io.readText(path);
20148
20786
  } catch {
20149
20787
  return;
20150
20788
  }
@@ -20157,17 +20795,17 @@ const readTextCache = (path) => {
20157
20795
  * cache is a best-effort speedup, never a correctness dependency, and the
20158
20796
  * alternative — failing a gtd command over a cache write — is worse.
20159
20797
  */
20160
- const writeTextCacheAtomic = (path, content) => {
20798
+ const writeTextCacheAtomic = (io, paths, pid, path, content) => {
20161
20799
  try {
20162
- mkdirSync(dirname(path), { recursive: true });
20163
- const tmp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
20164
- writeFileSync(tmp, content, "utf8");
20165
- renameSync(tmp, path);
20800
+ io.makeDirectory(paths.dirname(path));
20801
+ const tmp = `${path}.tmp-${pid}-${Math.random().toString(36).slice(2)}`;
20802
+ io.writeText(tmp, content);
20803
+ io.rename(tmp, path);
20166
20804
  } catch {}
20167
20805
  };
20168
20806
  /** A commit's `path → blob id` map, serialized as JSON. `undefined` on ANY read failure — missing, truncated, unparseable — so the caller falls back to asking git. */
20169
- const readTreeCache = (commonDir, hash) => {
20170
- const raw = readTextCache(treeCachePath(commonDir, hash));
20807
+ const readTreeCache = (io, file) => {
20808
+ const raw = readTextCache(io, file);
20171
20809
  if (raw === void 0) return void 0;
20172
20810
  try {
20173
20811
  const parsed = JSON.parse(raw);
@@ -20182,7 +20820,7 @@ const readTreeCache = (commonDir, hash) => {
20182
20820
  return;
20183
20821
  }
20184
20822
  };
20185
- const writeTreeCache = (commonDir, hash, tree) => writeTextCacheAtomic(treeCachePath(commonDir, hash), JSON.stringify(Object.fromEntries(tree)));
20823
+ const writeTreeCache = (io, paths, pid, file, tree) => writeTextCacheAtomic(io, paths, pid, file, JSON.stringify(Object.fromEntries(tree)));
20186
20824
  /** Parses a `:`-prefixed raw-diff header token into its status letter and new blob id. */
20187
20825
  const parseRawDiffHeader = (header) => {
20188
20826
  const fields = header.slice(1).split(" ").filter((f) => f.length > 0);
@@ -20266,21 +20904,17 @@ const parseLsTree = (out) => {
20266
20904
  * failed read rather than from the key itself, so callers that cache must
20267
20905
  * check `ok` before doing so.
20268
20906
  */
20269
- const fetchTree = (root, ref) => {
20907
+ const fetchTree = (io, root, ref) => {
20270
20908
  try {
20271
20909
  return {
20272
20910
  ok: true,
20273
- entries: parseLsTree(execFileSync("git", [
20911
+ entries: parseLsTree(io.git(root, [
20274
20912
  "ls-tree",
20275
20913
  "-r",
20276
20914
  "-z",
20277
20915
  "--full-tree",
20278
20916
  ref
20279
- ], {
20280
- cwd: root,
20281
- encoding: "utf8",
20282
- maxBuffer: MAX_BUFFER
20283
- }))
20917
+ ]))
20284
20918
  };
20285
20919
  } catch {
20286
20920
  return {
@@ -20289,26 +20923,10 @@ const fetchTree = (root, ref) => {
20289
20923
  };
20290
20924
  }
20291
20925
  };
20292
- const assertRepoRelative = (path) => {
20293
- if (isAbsolute(path)) throw new Error(`Workspace: "${path}" is an absolute path — this member takes repo-relative paths only (use "atPath"/"writeAtPath" to read/write outside the repo)`);
20926
+ const assertRepoRelative = (paths, path) => {
20927
+ if (paths.isAbsolute(path)) throw new Error(`Workspace: "${path}" is an absolute path — this member takes repo-relative paths only (use "atPath"/"writeAtPath" to read/write outside the repo)`);
20294
20928
  return path;
20295
20929
  };
20296
- const readFileOrAbsent = (path) => {
20297
- try {
20298
- return readFileSync(path, "utf8");
20299
- } catch (e) {
20300
- if (e.code === "ENOENT") return void 0;
20301
- throw e;
20302
- }
20303
- };
20304
- const lstatOrAbsent = (path) => {
20305
- try {
20306
- return lstatSync(path);
20307
- } catch (e) {
20308
- if (e.code === "ENOENT") return void 0;
20309
- throw e;
20310
- }
20311
- };
20312
20930
  /**
20313
20931
  * Runs `git log --raw` for `base..head` (or all of `head`'s first-parent
20314
20932
  * history when `base` is `undefined`) and splits the output into one
@@ -20316,8 +20934,9 @@ const lstatOrAbsent = (path) => {
20316
20934
  * count, same hashes, in order — since the two disagreeing means the episode
20317
20935
  * bounds no longer match git's own history.
20318
20936
  */
20319
- const runRawLogReplay = (root, base, head, commits) => {
20320
- const chunks = execFileSync("git", [
20937
+ const runRawLogReplay = (io, root, base, head, commits) => {
20938
+ const range = base !== void 0 ? `${base}..${head}` : head;
20939
+ const chunks = io.git(root, [
20321
20940
  "log",
20322
20941
  "--first-parent",
20323
20942
  "--reverse",
@@ -20326,12 +20945,8 @@ const runRawLogReplay = (root, base, head, commits) => {
20326
20945
  "-r",
20327
20946
  "--no-abbrev",
20328
20947
  "--format=%x01%H",
20329
- base !== void 0 ? `${base}..${head}` : head
20330
- ], {
20331
- cwd: root,
20332
- encoding: "utf8",
20333
- maxBuffer: MAX_BUFFER
20334
- }).split("").filter((c) => c.length > 0);
20948
+ range
20949
+ ]).split("").filter((c) => c.length > 0);
20335
20950
  if (chunks.length !== commits.length) throw new Error(`gtd: episode reconstruction expected ${commits.length} commit(s) from git log, got ${chunks.length} — the episode bounds disagree with git's own history`);
20336
20951
  return chunks.map((chunk, i) => {
20337
20952
  const nul = chunk.indexOf("\0");
@@ -20357,8 +20972,11 @@ const foldRawDiffs = (baseTree, replay, rememberTree) => {
20357
20972
  }
20358
20973
  return result;
20359
20974
  };
20360
- const makeWorkspaceOps = (root, git, commonDir) => {
20361
- const resolveAny = (path) => isAbsolute(path) ? path : join(root, path);
20975
+ const makeWorkspaceOps = ({ root, pid }, git, commonDir, paths, io) => {
20976
+ const resolveAny = (path) => paths.isAbsolute(path) ? path : paths.join(root, path);
20977
+ const repoPath = (path) => paths.join(root, assertRepoRelative(paths, path));
20978
+ const treeCachePath = (dir, hash) => paths.join(dir, GTD_CACHE_SUBDIR, "trees", hash);
20979
+ const blobCachePath = (dir, id) => paths.join(dir, GTD_CACHE_SUBDIR, "blobs", id);
20362
20980
  const treeMemo = /* @__PURE__ */ new Map();
20363
20981
  const blobMemo = /* @__PURE__ */ new Map();
20364
20982
  /** A commit's tree, from the in-process memo or the on-disk cache — no subprocess. `undefined` on a cold miss. */
@@ -20366,15 +20984,15 @@ const makeWorkspaceOps = (root, git, commonDir) => {
20366
20984
  const memoHit = treeMemo.get(hash);
20367
20985
  if (memoHit !== void 0) return memoHit;
20368
20986
  if (commonDir === void 0) return void 0;
20369
- const cached = readTreeCache(commonDir, hash);
20987
+ const cached = readTreeCache(io, treeCachePath(commonDir, hash));
20370
20988
  if (cached !== void 0) treeMemo.set(hash, cached);
20371
20989
  return cached;
20372
20990
  };
20373
20991
  const rememberTree = (hash, tree) => {
20374
20992
  treeMemo.set(hash, tree);
20375
- if (commonDir !== void 0) writeTreeCache(commonDir, hash, tree);
20993
+ if (commonDir !== void 0) writeTreeCache(io, paths, pid, treeCachePath(commonDir, hash), tree);
20376
20994
  };
20377
- const readSync = (path) => readFileOrAbsent(join(root, assertRepoRelative(path)));
20995
+ const readSync = (path) => io.readText(repoPath(path));
20378
20996
  /**
20379
20997
  * A blob's content, keyed on its OWN id rather than a (path, ref) pair —
20380
20998
  * the same content read from two different commits costs one read, not
@@ -20386,7 +21004,7 @@ const makeWorkspaceOps = (root, git, commonDir) => {
20386
21004
  const memoHit = blobMemo.get(id);
20387
21005
  if (memoHit !== void 0) return memoHit;
20388
21006
  if (commonDir !== void 0) {
20389
- const cached = readTextCache(blobCachePath(commonDir, id));
21007
+ const cached = readTextCache(io, blobCachePath(commonDir, id));
20390
21008
  if (cached !== void 0) {
20391
21009
  blobMemo.set(id, cached);
20392
21010
  return cached;
@@ -20394,34 +21012,26 @@ const makeWorkspaceOps = (root, git, commonDir) => {
20394
21012
  }
20395
21013
  let content;
20396
21014
  try {
20397
- content = execFileSync("git", [
21015
+ content = io.git(root, [
20398
21016
  "cat-file",
20399
21017
  "-p",
20400
21018
  id
20401
- ], {
20402
- cwd: root,
20403
- encoding: "utf8",
20404
- maxBuffer: MAX_BUFFER
20405
- });
21019
+ ]);
20406
21020
  } catch {
20407
21021
  return;
20408
21022
  }
20409
21023
  blobMemo.set(id, content);
20410
- if (commonDir !== void 0) writeTextCacheAtomic(blobCachePath(commonDir, id), content);
21024
+ if (commonDir !== void 0) writeTextCacheAtomic(io, paths, pid, blobCachePath(commonDir, id), content);
20411
21025
  return content;
20412
21026
  };
20413
21027
  const readCommittedSync = (path, ref = "HEAD") => {
20414
- const relPath = assertRepoRelative(path);
21028
+ const relPath = assertRepoRelative(paths, path);
20415
21029
  if (isObjectHash(ref)) {
20416
21030
  const id = treeSync(ref).get(relPath);
20417
21031
  return id === void 0 ? void 0 : readBlobSync(id);
20418
21032
  }
20419
21033
  try {
20420
- return execFileSync("git", ["show", `${ref}:${relPath}`], {
20421
- cwd: root,
20422
- encoding: "utf8",
20423
- maxBuffer: MAX_BUFFER
20424
- });
21034
+ return io.git(root, ["show", `${ref}:${relPath}`]);
20425
21035
  } catch {
20426
21036
  return;
20427
21037
  }
@@ -20431,7 +21041,7 @@ const makeWorkspaceOps = (root, git, commonDir) => {
20431
21041
  const cached = peekTree(ref);
20432
21042
  if (cached !== void 0) return cached;
20433
21043
  }
20434
- const { ok, entries } = fetchTree(root, ref);
21044
+ const { ok, entries } = fetchTree(io, root, ref);
20435
21045
  if (!ok) {
20436
21046
  if (onFailure === "throw") throw new Error(`gtd: failed to read the tree at "${ref}" — git ls-tree did not succeed`);
20437
21047
  return entries;
@@ -20466,18 +21076,13 @@ const makeWorkspaceOps = (root, git, commonDir) => {
20466
21076
  return result;
20467
21077
  }
20468
21078
  const head = commits[commits.length - 1];
20469
- const replay = runRawLogReplay(root, base, head, commits);
21079
+ const replay = runRawLogReplay(io, root, base, head, commits);
20470
21080
  for (const [hash, tree] of foldRawDiffs(baseTree, replay, rememberTree)) result.set(hash, tree);
20471
21081
  return result;
20472
21082
  },
20473
21083
  catch: toError$2
20474
21084
  });
20475
- const gitSync = (args, input) => execFileSync("git", args, {
20476
- cwd: root,
20477
- encoding: "utf8",
20478
- maxBuffer: MAX_BUFFER,
20479
- ...input !== void 0 ? { input } : {}
20480
- });
21085
+ const gitSync = (args) => io.git(root, args);
20481
21086
  const worktreeSync = () => {
20482
21087
  const entries = new Map(treeSync("HEAD"));
20483
21088
  const status = gitSync([
@@ -20492,10 +21097,10 @@ const makeWorkspaceOps = (root, git, commonDir) => {
20492
21097
  for (const record of status.split("\0")) {
20493
21098
  if (record.length < 4) continue;
20494
21099
  const path = record.slice(3).replace(/\/$/, "");
20495
- const stat = lstatOrAbsent(join(root, path));
21100
+ const stat = io.lstat(paths.join(root, path));
20496
21101
  if (stat === void 0) entries.delete(path);
20497
- else if (stat.isSymbolicLink()) entries.set(path, blobId(readlinkSync(join(root, path), "utf8")));
20498
- else if (stat.isDirectory()) if (lstatOrAbsent(join(root, path, ".git")) === void 0) entries.delete(path);
21102
+ else if (stat.type === "symlink") entries.set(path, blobId(io.readLink(paths.join(root, path))));
21103
+ else if (stat.type === "directory") if (io.lstat(paths.join(root, path, ".git")) === void 0) entries.delete(path);
20499
21104
  else entries.set(path, gitlinkOf(path));
20500
21105
  else files.push(path);
20501
21106
  }
@@ -20513,10 +21118,7 @@ const makeWorkspaceOps = (root, git, commonDir) => {
20513
21118
  /** A submodule's (or embedded repository's) checked-out commit — what `git add` records for it. */
20514
21119
  const gitlinkOf = (path) => {
20515
21120
  try {
20516
- return execFileSync("git", ["rev-parse", "HEAD"], {
20517
- cwd: join(root, path),
20518
- encoding: "utf8"
20519
- }).trim();
21121
+ return io.git(paths.join(root, path), ["rev-parse", "HEAD"]).trim();
20520
21122
  } catch {
20521
21123
  return;
20522
21124
  }
@@ -20524,8 +21126,8 @@ const makeWorkspaceOps = (root, git, commonDir) => {
20524
21126
  let objectFormat;
20525
21127
  const blobId = (content) => {
20526
21128
  objectFormat ??= gitSync(["rev-parse", "--show-object-format"]).trim();
20527
- const bytes = Buffer.from(content, "utf8");
20528
- return createHash(objectFormat === "sha256" ? "sha256" : "sha1").update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
21129
+ const bytes = encoder$2.encode(content);
21130
+ return io.hash(objectFormat === "sha256" ? "sha256" : "sha1", `blob ${bytes.length}\0`, bytes);
20529
21131
  };
20530
21132
  return {
20531
21133
  treeSync,
@@ -20537,27 +21139,28 @@ const makeWorkspaceOps = (root, git, commonDir) => {
20537
21139
  catch: toError$2
20538
21140
  }),
20539
21141
  write: (path, content) => try_({
20540
- try: () => writeFileSync(join(root, assertRepoRelative(path)), content, "utf8"),
21142
+ try: () => io.writeText(repoPath(path), content),
20541
21143
  catch: toError$2
20542
21144
  }),
20543
21145
  committed: (path, ref = "HEAD") => try_({
20544
- try: () => assertRepoRelative(path),
21146
+ try: () => assertRepoRelative(paths, path),
20545
21147
  catch: toError$2
20546
21148
  }).pipe(flatMap((relPath) => git.readFileAtRef(ref, relPath).pipe(catch_(() => succeed$1(void 0))))),
20547
21149
  readCommittedSync,
20548
- atPath: (path) => readFileOrAbsent(resolveAny(path)),
21150
+ atPath: (path) => io.readText(resolveAny(path)),
20549
21151
  writeAtPath: (path, content) => try_({
20550
- try: () => writeFileSync(resolveAny(path), content, "utf8"),
21152
+ try: () => io.writeText(resolveAny(path), content),
20551
21153
  catch: toError$2
20552
21154
  })
20553
21155
  };
20554
21156
  };
20555
21157
  var Workspace = class Workspace extends Service()("Workspace") {
20556
21158
  static Live = effect(Workspace, gen(function* () {
20557
- const { root } = yield* Host;
21159
+ const host = yield* Host;
20558
21160
  const git = yield* GitService;
21161
+ const paths = yield* Path$1;
20559
21162
  const commonDir = yield* git.gitCommonDir().pipe(catch_(() => succeed$1(void 0)));
20560
- return makeWorkspaceOps(root, git, commonDir);
21163
+ return makeWorkspaceOps(host, git, commonDir, paths, nodeSyncIo);
20561
21164
  }));
20562
21165
  };
20563
21166
  //#endregion
@@ -21494,7 +22097,7 @@ const bigintToNumber = (value, field) => {
21494
22097
  if (!Number.isSafeInteger(number)) throw new RangeError(`${field} exceeds the safe integer range: ${value}`);
21495
22098
  return number;
21496
22099
  };
21497
- const bigintToNumberOption = (value) => flatMap$3(fromNullishOr(value), toNumber);
22100
+ const bigintToNumberOption = (value) => flatMap$4(fromNullishOr(value), toNumber);
21498
22101
  const toUint64 = (value) => BigInt.asUintN(64, value);
21499
22102
  const toUint64Option = (value) => map$6(fromNullishOr(value), toUint64);
21500
22103
  const positionToNumber = (position, method) => try_({
@@ -21848,7 +22451,7 @@ const watchNode = (path, info, options) => callback((queue) => acquireRelease(sy
21848
22451
  });
21849
22452
  return watcher;
21850
22453
  }), (watcher) => sync(() => watcher.close())).pipe(tap(() => sync(() => options?.onReady?.()))));
21851
- const watch = (backend, path, options) => all([stat(path), serviceOption(WatchBackend)]).pipe(map(([stat, callerBackend]) => orElse(callerBackend, () => backend).pipe(flatMap$3((_) => _.register(path, stat, options)), getOrElse(() => watchNode(path, stat, options)))), unwrap$2);
22454
+ const watch = (backend, path, options) => all([stat(path), serviceOption(WatchBackend)]).pipe(map(([stat, callerBackend]) => orElse(callerBackend, () => backend).pipe(flatMap$4((_) => _.register(path, stat, options)), getOrElse(() => watchNode(path, stat, options)))), unwrap$2);
21852
22455
  const writeFile = (path, data, options) => callback$1((resume, signal) => {
21853
22456
  try {
21854
22457
  NFS.writeFile(path, data, {
@@ -21956,11 +22559,12 @@ const GITDIR_POINTER = /^gitdir:\s*(.+)$/m;
21956
22559
  */
21957
22560
  const worktreeGitDir = gen(function* () {
21958
22561
  const { root } = yield* Host;
22562
+ const path = yield* Path$1;
21959
22563
  const content = yield* (yield* Workspace).read(".git").pipe(catch_(() => succeed$1(void 0)));
21960
22564
  const pointer = content === void 0 ? null : GITDIR_POINTER.exec(content);
21961
22565
  if (pointer === null) return ".git";
21962
22566
  const gitdir = pointer[1].trim();
21963
- return isAbsolute(gitdir) ? gitdir : join(root, gitdir);
22567
+ return path.isAbsolute(gitdir) ? gitdir : path.join(root, gitdir);
21964
22568
  });
21965
22569
  /**
21966
22570
  * The per-worktree loop log path `gtd next --json` reports as `log`.
@@ -21973,11 +22577,16 @@ const worktreeGitDir = gen(function* () {
21973
22577
  */
21974
22578
  const loopLogPath = gen(function* () {
21975
22579
  const { root, env } = yield* Host;
22580
+ const path = yield* Path$1;
21976
22581
  const override = env.GTD_LOOP_LOG;
21977
22582
  if (override !== void 0 && override !== "") return override;
21978
22583
  const gitDirEnv = env.GIT_DIR;
21979
- if (gitDirEnv !== void 0 && gitDirEnv !== "") return join(isAbsolute(gitDirEnv) ? gitDirEnv : join(root, gitDirEnv), "gtd-loop.log");
21980
- return join(yield* worktreeGitDir, "gtd-loop.log");
22584
+ if (gitDirEnv !== void 0 && gitDirEnv !== "") {
22585
+ const gitDir = path.isAbsolute(gitDirEnv) ? gitDirEnv : path.join(root, gitDirEnv);
22586
+ return path.join(gitDir, "gtd-loop.log");
22587
+ }
22588
+ const gitDir = yield* worktreeGitDir;
22589
+ return path.join(gitDir, "gtd-loop.log");
21981
22590
  });
21982
22591
  //#endregion
21983
22592
  //#region src/platform/RetainedHistory.ts
@@ -22001,6 +22610,73 @@ const restorability = (git, headHash, tipHash) => gen(function* () {
22001
22610
  reason: "HEAD has advanced past the retained tip — restoring would discard commits built on top of it"
22002
22611
  };
22003
22612
  });
22613
+ Uint32Array.from([
22614
+ 1116352408,
22615
+ 1899447441,
22616
+ 3049323471,
22617
+ 3921009573,
22618
+ 961987163,
22619
+ 1508970993,
22620
+ 2453635748,
22621
+ 2870763221,
22622
+ 3624381080,
22623
+ 310598401,
22624
+ 607225278,
22625
+ 1426881987,
22626
+ 1925078388,
22627
+ 2162078206,
22628
+ 2614888103,
22629
+ 3248222580,
22630
+ 3835390401,
22631
+ 4022224774,
22632
+ 264347078,
22633
+ 604807628,
22634
+ 770255983,
22635
+ 1249150122,
22636
+ 1555081692,
22637
+ 1996064986,
22638
+ 2554220882,
22639
+ 2821834349,
22640
+ 2952996808,
22641
+ 3210313671,
22642
+ 3336571891,
22643
+ 3584528711,
22644
+ 113926993,
22645
+ 338241895,
22646
+ 666307205,
22647
+ 773529912,
22648
+ 1294757372,
22649
+ 1396182291,
22650
+ 1695183700,
22651
+ 1986661051,
22652
+ 2177026350,
22653
+ 2456956037,
22654
+ 2730485921,
22655
+ 2820302411,
22656
+ 3259730800,
22657
+ 3345764771,
22658
+ 3516065817,
22659
+ 3600352804,
22660
+ 4094571909,
22661
+ 275423344,
22662
+ 430227734,
22663
+ 506948616,
22664
+ 659060556,
22665
+ 883997877,
22666
+ 958139571,
22667
+ 1322822218,
22668
+ 1537002063,
22669
+ 1747873779,
22670
+ 1955562222,
22671
+ 2024104815,
22672
+ 2227730452,
22673
+ 2361852424,
22674
+ 2428436474,
22675
+ 2756734187,
22676
+ 3204031479,
22677
+ 3329325298
22678
+ ]);
22679
+ new TextEncoder();
22004
22680
  //#endregion
22005
22681
  //#region src/flags/index.ts
22006
22682
  const FLAGS$1 = [];
@@ -22032,19 +22708,21 @@ const resolveFlags = (env, registry = FLAGS$1) => {
22032
22708
  const flagSettings = (flags) => Object.fromEntries(Object.entries(flags).map(([name, on]) => [`flag_${name}`, on ? "1" : "0"]));
22033
22709
  //#endregion
22034
22710
  //#region src/install/Version.ts
22035
- const _require = createRequire(import.meta.url);
22036
- const findPackageJson = (fromUrl) => {
22037
- let dir = dirname(fileURLToPath(fromUrl));
22711
+ const readPackageJson = (fromUrl) => {
22712
+ let dir = new URL(".", fromUrl);
22038
22713
  for (let i = 0; i < 5; i++) {
22039
- const candidate = join(dir, "package.json");
22040
- if (existsSync(candidate)) return candidate;
22041
- dir = dirname(dir);
22714
+ const candidate = new URL("package.json", dir);
22715
+ try {
22716
+ const source = nodeSyncIo.readText(fileURLToPath(candidate));
22717
+ if (source !== void 0) return source;
22718
+ } catch {}
22719
+ dir = new URL("..", dir);
22042
22720
  }
22043
22721
  throw new Error(`package.json not found by walking up from ${fromUrl}`);
22044
22722
  };
22045
22723
  /** The global a pin's workflow registers itself under, ahead of the engine in the same file. */
22046
22724
  const PIN_KEY = Symbol.for("@pmelab/gtd/pin");
22047
- const GTD_VERSION = globalThis[PIN_KEY]?.version ?? _require(findPackageJson(import.meta.url)).version;
22725
+ const GTD_VERSION = globalThis[PIN_KEY]?.version ?? JSON.parse(readPackageJson(import.meta.url)).version;
22048
22726
  //#endregion
22049
22727
  //#region src/install/Install.ts
22050
22728
  const WORKFLOWS_REPO = "https://github.com/pmelab/gtd-workflows";
@@ -22810,9 +23488,9 @@ const withoutPath = (tree, hidden) => {
22810
23488
  const OLDEST_READABLE_FORMAT = 1;
22811
23489
  const short$1 = (hash) => hash.slice(0, 7);
22812
23490
  const formatFault = (commits) => {
22813
- const foreign = commits.find((c) => c.format < OLDEST_READABLE_FORMAT || c.format > 2);
23491
+ const foreign = commits.find((c) => c.format < OLDEST_READABLE_FORMAT || c.format > 4);
22814
23492
  if (foreign === void 0) return void 0;
22815
- return `gtd: commit ${short$1(foreign.hash)} is written in history format ${foreign.format}, but this gtd reads formats ${OLDEST_READABLE_FORMAT} to 2 — run a gtd release that reads format ${foreign.format} to continue this process`;
23493
+ return `gtd: commit ${short$1(foreign.hash)} is written in history format ${foreign.format}, but this gtd reads formats ${OLDEST_READABLE_FORMAT} to 4 — run a gtd release that reads format ${foreign.format} to continue this process`;
22816
23494
  };
22817
23495
  //#endregion
22818
23496
  //#region src/replay/Trailers.ts
@@ -22849,11 +23527,21 @@ const parseStepId = (raw) => {
22849
23527
  occurrence: Number(match[2])
22850
23528
  };
22851
23529
  };
23530
+ /** Step names a workflow may not take: gtd's own beats live under it. */
23531
+ const ENGINE_PREFIX = "gtd:";
22852
23532
  /** What a pin and an unpin beat rest at, and their commits name — neither is a step of the workflow. */
22853
23533
  const PIN_STATES = {
23534
+ add: `${ENGINE_PREFIX}pin`,
23535
+ remove: `${ENGINE_PREFIX}unpin`
23536
+ };
23537
+ /** The names formats 2 and 3 gave the pin beats, before `gtd:` reserved them. */
23538
+ const LEGACY_PIN_STATES = {
22854
23539
  add: "pin",
22855
23540
  remove: "unpin"
22856
23541
  };
23542
+ const SCOPED_PIN_FORMAT = 4;
23543
+ /** The name a commit in `format` gives a pin beat; format 1 has none. */
23544
+ const pinStateIn = (format, change) => format >= SCOPED_PIN_FORMAT ? PIN_STATES[change] : format >= 2 ? LEGACY_PIN_STATES[change] : void 0;
22857
23545
  const isVerdict$1 = (value) => {
22858
23546
  if (typeof value !== "object" || value === null) return false;
22859
23547
  const v = value;
@@ -22943,6 +23631,9 @@ const TRAILER_READERS = {
22943
23631
  "Gtd-Pin": (value, into) => {
22944
23632
  if (value === "add" || value === "remove") into.pin ??= value;
22945
23633
  },
23634
+ "Gtd-Anchor": (value, into) => {
23635
+ if (HASH_RE.test(value)) into.anchor ??= value;
23636
+ },
22946
23637
  "Gtd-Format": (value, into) => {
22947
23638
  if (FORMAT_RE.test(value)) into.format ??= Number(value);
22948
23639
  },
@@ -22966,6 +23657,7 @@ const parseCommitMessage = (message) => {
22966
23657
  kept: void 0,
22967
23658
  approved: void 0,
22968
23659
  pin: void 0,
23660
+ anchor: void 0,
22969
23661
  judge: [],
22970
23662
  marks: [],
22971
23663
  vars: {},
@@ -22995,15 +23687,19 @@ const formatCommitMessage = (spec) => {
22995
23687
  ...(spec.marks ?? []).map((m) => `Gtd-Mark: ${m.name}=${m.value}`),
22996
23688
  ...trailer("Approved", spec.approved && encodeApproval(spec.approved)),
22997
23689
  ...trailer("Pin", spec.pin),
23690
+ ...trailer("Anchor", spec.anchor),
22998
23691
  ...trailer("Branch", spec.branch),
22999
23692
  ...trailer("Version", spec.version),
23000
- `Gtd-Format: 2`
23693
+ `Gtd-Format: 4`
23001
23694
  ];
23002
23695
  return `${formatSubject(spec.actor, spec.to, spec.from)}\n\n${lines.join("\n")}`;
23003
23696
  };
23004
23697
  //#endregion
23005
23698
  //#region src/replay/Replay.ts
23006
- const UNPIN_STATE = PIN_STATES.remove;
23699
+ const namesUnpin = (commit) => {
23700
+ const unpin = pinStateIn(commit.parsed.format, "remove");
23701
+ return unpin !== void 0 && commit.parsed.parsed?.to === unpin;
23702
+ };
23007
23703
  const actorOfKind = (kind) => kind === "run" ? "check" : kind === "agent" ? "agent" : kind === "human" ? "human" : "judge";
23008
23704
  const memoryScopeOf = (name) => {
23009
23705
  const dot = name.lastIndexOf(".");
@@ -23091,6 +23787,27 @@ const budgeted = (evidence, budgetBytes) => {
23091
23787
  };
23092
23788
  const sameSkills = (a, b) => (a?.length ?? 0) === (b?.length ?? 0) && (a ?? []).every((skill, i) => skill === b?.[i]);
23093
23789
  const samePersona = (a, b) => a.model === b.model && a.system === b.system && sameSkills(a.skills, b.skills) && sameAccess(a.access, b.access);
23790
+ const anchorOf = (parsed) => parsed.pin === "add" ? parsed.anchor : void 0;
23791
+ const isAnchor = (commit) => anchorOf(commit.parsed) !== void 0;
23792
+ /**
23793
+ * The commits every anchor sets aside — those between the commit it names and
23794
+ * itself — or a fault for an anchor naming no commit still read.
23795
+ */
23796
+ const setAsideBy = (base, all) => {
23797
+ const setAside = /* @__PURE__ */ new Set();
23798
+ if (all.every((c) => anchorOf(c.parsed) === void 0)) return { setAside };
23799
+ let kept = [];
23800
+ for (const commit of all) {
23801
+ const anchor = anchorOf(commit.parsed);
23802
+ if (anchor !== void 0) {
23803
+ const at = anchor === base ? -1 : kept.indexOf(anchor);
23804
+ if (anchor !== base && at === -1) return { fault: `gtd: commit ${short(commit.hash)} anchors the process at ${short(anchor)}, which is no commit replay reads in this episode` };
23805
+ for (const hash of kept.splice(at + 1)) setAside.add(hash);
23806
+ }
23807
+ kept.push(commit.hash);
23808
+ }
23809
+ return { setAside };
23810
+ };
23094
23811
  /**
23095
23812
  * Replay one episode: run its entry flow, answering each step from the
23096
23813
  * episode's commits in order, until a step has no commit left — the rest.
@@ -23164,19 +23881,39 @@ const withoutPin = (input) => {
23164
23881
  };
23165
23882
  const replay = async (raw) => {
23166
23883
  const input = withoutPin(raw);
23167
- const commits = input.episode.commits.map((c) => ({
23884
+ const all = input.episode.commits.map((c) => ({
23168
23885
  ...c,
23169
23886
  parsed: parseCommitMessage(c.message)
23170
23887
  }));
23888
+ const resolved = setAsideBy(input.episode.base.hash, all);
23889
+ if ("fault" in resolved) return {
23890
+ kind: "failed",
23891
+ message: resolved.fault
23892
+ };
23893
+ const setAside = resolved.setAside;
23894
+ const commits = all.filter((c) => !setAside.has(c.hash));
23895
+ const pinned = (raw.episode.commits.at(-1)?.tree ?? raw.episode.base.tree).paths().includes(PIN_PATH);
23171
23896
  const treeAt = (hash) => hash === input.episode.base.hash ? input.episode.base.tree : commits.find((c) => c.hash === hash)?.tree ?? (hash === input.start ? input.startTree : void 0);
23172
23897
  let cursor = 0;
23173
23898
  let pendingUsed = false;
23174
23899
  let position = input.episode.base;
23175
23900
  const skipPins = () => {
23176
- while (cursor < commits.length && commits[cursor].parsed.pin !== void 0) cursor++;
23901
+ while (cursor < commits.length && commits[cursor].parsed.pin !== void 0) {
23902
+ const commit = commits[cursor];
23903
+ cursor++;
23904
+ if (!isAnchor(commit)) continue;
23905
+ position = commit;
23906
+ const to = commit.parsed.parsed?.to;
23907
+ expectedNext = to === void 0 || namesUnpin(commit) ? void 0 : {
23908
+ name: to,
23909
+ hash: commit.hash
23910
+ };
23911
+ anchorPending = expectedNext !== void 0;
23912
+ }
23177
23913
  };
23178
23914
  let previousPosition = input.episode.base;
23179
23915
  let expectedNext;
23916
+ let anchorPending = false;
23180
23917
  const occurrences = /* @__PURE__ */ new Map();
23181
23918
  const personas = /* @__PURE__ */ new Map();
23182
23919
  const scopes = [];
@@ -23319,14 +24056,18 @@ const replay = async (raw) => {
23319
24056
  }
23320
24057
  return samePersona(seen, identity) ? void 0 : `gtd: "${step.name}" runs with a different model, system prompt, skills or file access than an earlier agent step in its conversation in memory scope "${step.memoryScope || "root"}" — a conversation has one identity`;
23321
24058
  };
23322
- const DIVERGED = "the workflow changed under this process; run `gtd abandon` to start over";
24059
+ const DIVERGED = pinned ? "the workflow changed under this process; run `gtd repin` to continue under it from there, or `gtd abandon` to start over" : "the workflow changed under this process; run `gtd abandon` to start over";
24060
+ const diverged = (message, reached) => ({
24061
+ kind: "divergence",
24062
+ message: `${message} — ${DIVERGED}`,
24063
+ at: position.hash,
24064
+ reached: reached?.name
24065
+ });
23323
24066
  const blockerAt = (step) => {
23324
24067
  const expected = expectedNext;
23325
24068
  expectedNext = void 0;
23326
- if (expected !== void 0 && expected.name !== step.name) return {
23327
- kind: "divergence",
23328
- message: `gtd: commit ${short(expected.hash)} names "${expected.name}" as the next step, but replay reached "${step.name}" — ${DIVERGED}`
23329
- };
24069
+ anchorPending = false;
24070
+ if (expected !== void 0 && expected.name !== step.name) return diverged(`gtd: commit ${short(expected.hash)} names "${expected.name}" as the next step, but replay reached "${step.name}"`, step);
23330
24071
  const error = unknownOptions(step) ?? approveConflict(step) ?? modeWithoutFile(step) ?? checkIdentity(step);
23331
24072
  return error === void 0 ? void 0 : {
23332
24073
  kind: "failed",
@@ -23337,33 +24078,30 @@ const replay = async (raw) => {
23337
24078
  if (step.request.kind !== "human" || step.request.options.approve !== true) return void 0;
23338
24079
  const approved = commit.parsed.approved;
23339
24080
  if (approved !== void 0 && formatStepId(approved.step) === expected) return void 0;
23340
- return {
23341
- kind: "divergence",
23342
- message: `gtd: commit ${short(commit.hash)} completes approval gate "${expected}" but records no Gtd-Approved for it — ${DIVERGED}`
23343
- };
24081
+ return diverged(`gtd: commit ${short(commit.hash)} completes approval gate "${expected}" but records no Gtd-Approved for it`, step);
23344
24082
  };
23345
24083
  const consumeCommit = (step, commit) => {
23346
24084
  const recorded = commit.parsed.step;
23347
24085
  const expected = formatStepId(step.id);
23348
24086
  if (recorded === void 0 || formatStepId(recorded) !== expected) {
23349
24087
  const what = recorded === void 0 ? "records no step" : `records step "${formatStepId(recorded)}"`;
23350
- return finish({
23351
- kind: "divergence",
23352
- message: `gtd: commit ${short(commit.hash)} ${what}, but replay expected "${expected}" — ${DIVERGED}`
23353
- });
24088
+ return finish(diverged(`gtd: commit ${short(commit.hash)} ${what}, but replay expected "${expected}"`, step));
23354
24089
  }
23355
24090
  const unapproved = missingApproval(step, commit, expected);
23356
24091
  if (unapproved !== void 0) return finish(unapproved);
23357
24092
  cursor++;
23358
24093
  advance(commit);
24094
+ expectNextOf(commit);
23359
24095
  skipPins();
23360
24096
  noteKept(step, commit);
24097
+ return replyFor(step.request, commit.parsed.judge);
24098
+ };
24099
+ const expectNextOf = (commit) => {
23361
24100
  const to = commit.parsed.parsed?.to;
23362
- if (to !== void 0 && to !== UNPIN_STATE) expectedNext = {
24101
+ if (to !== void 0 && !namesUnpin(commit)) expectedNext = {
23363
24102
  name: to,
23364
24103
  hash: commit.hash
23365
24104
  };
23366
- return replyFor(step.request, commit.parsed.judge);
23367
24105
  };
23368
24106
  const noteKept = (step, commit) => {
23369
24107
  if (commit.parsed.kept === void 0) return;
@@ -23406,7 +24144,7 @@ const replay = async (raw) => {
23406
24144
  return landPending(step, input.pending);
23407
24145
  };
23408
24146
  const requestFault = (request) => {
23409
- if (Object.values(PIN_STATES).includes(scoped(request.name))) return `gtd: step "${scoped(request.name)}": the name is reserved for the pin and unpin beats — rename the step`;
24147
+ if (scoped(request.name).startsWith("gtd:")) return `gtd: step "${scoped(request.name)}": the "${ENGINE_PREFIX}" prefix is reserved for gtd's own beats — rename the step`;
23410
24148
  if (request.kind === "run" && typeof request.body !== "string") return `gtd: step "${scoped(request.name)}": a run() body is a shell script string — decide in flow code, then render the script (see checkScript and friends)`;
23411
24149
  if (request.kind !== "agent") return void 0;
23412
24150
  const name = scoped(request.name);
@@ -23435,10 +24173,8 @@ const replay = async (raw) => {
23435
24173
  };
23436
24174
  const endOfFlow = (via) => {
23437
24175
  skipPins();
23438
- return cursor < commits.length ? {
23439
- kind: "divergence",
23440
- message: `gtd: the flow ended, but commit ${short(commits[cursor].hash)} continues the episode — ${DIVERGED}`
23441
- } : {
24176
+ if (anchorPending && cursor === commits.length) return diverged(`gtd: the flow ended, but commit ${short(expectedNext.hash)} continues the process at "${expectedNext.name}"`, void 0);
24177
+ return cursor < commits.length ? diverged(`gtd: the flow ended, but commit ${short(commits[cursor].hash)} continues the episode`, void 0) : {
23442
24178
  kind: "ended",
23443
24179
  via,
23444
24180
  trace,
@@ -23448,6 +24184,7 @@ const replay = async (raw) => {
23448
24184
  };
23449
24185
  const changesSince = (hash) => {
23450
24186
  const tree = treeAt(hash);
24187
+ if (setAside.has(hash)) throw new Error(`gtd: changesSince(${hash}): a repin set ${short(hash)} aside — pass a hash this run read from head() or start()`);
23451
24188
  if (tree === void 0) throw new Error(`gtd: changesSince(${hash}): ${hash} is not the episode base or one of its commits — pass a hash this run read from head() or start(), not one captured earlier, read from state, or from another branch`);
23452
24189
  return changesBetween(tree, position.tree);
23453
24190
  };
@@ -23669,17 +24406,17 @@ const LEGACY_RC_FILES = [
23669
24406
  "gtd.config.yaml"
23670
24407
  ];
23671
24408
  /** The directories searched for a leftover rc file: the walk, plus `home` when the walk misses it. */
23672
- const legacyRcDirs = (root, home) => {
23673
- const chain = walkUp(root, home);
24409
+ const legacyRcDirs = (path, root, home) => {
24410
+ const chain = walkUp(path, root, home);
23674
24411
  return chain.includes(home) ? chain : [...chain, home];
23675
24412
  };
23676
- const legacyRcFileAt = (path) => {
24413
+ const legacyRcFileAt = (io, path, uid) => {
23677
24414
  try {
23678
- const stat = statSync(path);
23679
- if (!stat.isFile()) return void 0;
24415
+ const stat = io.stat(path);
24416
+ if (stat?.type !== "file") return void 0;
23680
24417
  return {
23681
24418
  path,
23682
- owned: process.getuid === void 0 || stat.uid === process.getuid()
24419
+ owned: uid === void 0 || stat.uid === uid
23683
24420
  };
23684
24421
  } catch {
23685
24422
  return;
@@ -23693,30 +24430,33 @@ const legacyRcFileAt = (path) => {
23693
24430
  * shared implementation, not a second copy in `src/twins/Layers.ts` that
23694
24431
  * could silently diverge.
23695
24432
  */
23696
- const walkUp = (from, home) => {
24433
+ const walkUp = (path, from, home) => {
23697
24434
  const chain = [];
23698
24435
  let dir = from;
23699
24436
  while (true) {
23700
24437
  chain.push(dir);
23701
24438
  if (dir === home) break;
23702
- const parent = dirname(dir);
24439
+ const parent = path.dirname(dir);
23703
24440
  if (parent === dir) break;
23704
24441
  dir = parent;
23705
24442
  }
23706
24443
  return chain;
23707
24444
  };
23708
- const legacyRcFiles = (root, home) => sync(() => legacyRcDirs(root, home).flatMap((dir) => LEGACY_RC_FILES.flatMap((name) => legacyRcFileAt(join(dir, name)) ?? [])));
23709
- const workflowModule = (root, home) => try_({
23710
- try: () => {
23711
- for (const dir of walkUp(root, home)) {
23712
- const filepath = join(dir, WORKFLOW_MODULE);
23713
- if (existsSync(filepath)) return {
23714
- filepath,
23715
- source: readFileSync(filepath, "utf8")
23716
- };
23717
- }
23718
- },
23719
- catch: (e) => e instanceof Error ? e : new Error(String(e))
24445
+ const liveDiscovery = (io, path, uid) => ({
24446
+ legacyRcFiles: (root, home) => sync(() => legacyRcDirs(path, root, home).flatMap((dir) => LEGACY_RC_FILES.flatMap((name) => legacyRcFileAt(io, path.join(dir, name), uid) ?? []))),
24447
+ workflowModule: (root, home) => try_({
24448
+ try: () => {
24449
+ for (const dir of walkUp(path, root, home)) {
24450
+ const filepath = path.join(dir, WORKFLOW_MODULE);
24451
+ const source = io.readText(filepath);
24452
+ if (source !== void 0) return {
24453
+ filepath,
24454
+ source
24455
+ };
24456
+ }
24457
+ },
24458
+ catch: (e) => e instanceof Error ? e : new Error(String(e))
24459
+ })
23720
24460
  });
23721
24461
  /**
23722
24462
  * The discovery seam — a `Context.Service`, not part of `WorkflowFiles` (that
@@ -23729,31 +24469,29 @@ const workflowModule = (root, home) => try_({
23729
24469
  * each have a production and a fake layer behind one service.
23730
24470
  */
23731
24471
  var ConfigDiscovery = class ConfigDiscovery extends Service()("ConfigDiscovery") {
23732
- static Live = succeed$2(ConfigDiscovery, {
23733
- legacyRcFiles,
23734
- workflowModule
23735
- });
24472
+ static Live = effect(ConfigDiscovery, gen(function* () {
24473
+ return liveDiscovery(nodeSyncIo, yield* Path$1, (yield* Host).uid);
24474
+ }));
23736
24475
  };
23737
24476
  //#endregion
23738
24477
  //#region src/workflow/fingerprint.ts
23739
24478
  const HASH_LENGTH = 16;
23740
24479
  const CONTROL_CHARACTER = /[\x00-\x1f\x7f-\x9f]/;
23741
- const contentHash = (content) => createHash("sha256").update(content).digest("hex").slice(0, HASH_LENGTH);
23742
- const realOr = (path) => {
24480
+ const realOr = (io, path) => {
23743
24481
  try {
23744
- return realpathSync(path);
24482
+ return io.realPath(path);
23745
24483
  } catch {
23746
24484
  return path;
23747
24485
  }
23748
24486
  };
23749
- const configFingerprint = (files, root, read) => {
24487
+ const configFingerprint = (io, paths, files, root, read) => {
23750
24488
  const fingerprint = {};
23751
- const realRoot = realOr(root);
24489
+ const realRoot = realOr(io, root);
23752
24490
  for (const file of files) {
23753
- const path = relative(realRoot, realOr(file)).split(sep).join("/");
24491
+ const path = paths.relative(realRoot, realOr(io, file)).split(paths.sep).join("/");
23754
24492
  if (CONTROL_CHARACTER.test(path)) continue;
23755
24493
  const content = read(file);
23756
- if (content !== void 0) fingerprint[path] = contentHash(content);
24494
+ if (content !== void 0) fingerprint[path] = io.hash("sha256", content).slice(0, HASH_LENGTH);
23757
24495
  }
23758
24496
  return fingerprint;
23759
24497
  };
@@ -23847,19 +24585,20 @@ const configModule = gen(function* () {
23847
24585
  */
23848
24586
  const load = gen(function* () {
23849
24587
  const host = yield* Host;
24588
+ const paths = yield* Path$1;
23850
24589
  yield* refuseLegacyRc;
23851
24590
  const pinned = pinnedWorkflow();
23852
24591
  const module = pinned === void 0 ? yield* configModule : {
23853
- filepath: resolve(host.root, pinned.origin),
24592
+ filepath: paths.resolve(host.root, pinned.origin),
23854
24593
  source: ""
23855
24594
  };
23856
24595
  yield* (yield* Narrator).narrate(`config: ${pinned === void 0 ? "module" : "pinned module"} ${module.filepath}`);
23857
24596
  const shipped = pinned === void 0 ? yield* tryPromise({
23858
- try: shippedModules,
24597
+ try: () => shippedModules(paths),
23859
24598
  catch: (e) => new GtdError(`gtd: ${e instanceof Error ? e.message : String(e)}`)
23860
24599
  }) : void 0;
23861
24600
  const loaded = yield* tryPromise({
23862
- try: async () => loadWorkflows(module.filepath, shipped === void 0 ? runPinned(pinned, module.filepath, host.root) : await evaluate(module, shipped)),
24601
+ try: async () => loadWorkflows(module.filepath, shipped === void 0 ? runPinned(paths, pinned, module.filepath, host.root) : await evaluate(paths, module, shipped)),
23863
24602
  catch: (e) => new GtdError(`gtd: config:\n - ${module.filepath}: ${e instanceof Error ? e.message : String(e)}`)
23864
24603
  });
23865
24604
  const compiled = loaded.config;
@@ -23881,13 +24620,13 @@ const load = gen(function* () {
23881
24620
  initial
23882
24621
  });
23883
24622
  const names = [...loaded.catalogue.keys()].sort();
23884
- const resolve$3 = (name, entry) => ({
24623
+ const resolve = (name, entry) => ({
23885
24624
  name,
23886
24625
  def: define(entry),
23887
24626
  vars: { ...entry.defaults },
23888
24627
  env: { ...entry.envDefaults }
23889
24628
  });
23890
- const defaultWorkflow = resolve$3(loaded.default.name, loaded.default);
24629
+ const defaultWorkflow = resolve(loaded.default.name, loaded.default);
23891
24630
  return {
23892
24631
  workflow: defaultWorkflow.def,
23893
24632
  defaultWorkflow,
@@ -23895,41 +24634,41 @@ const load = gen(function* () {
23895
24634
  doors: loaded.doors,
23896
24635
  workflowNamed: (name) => {
23897
24636
  const entry = loaded.catalogue.get(name);
23898
- return entry === void 0 ? void 0 : resolve$3(name, entry);
24637
+ return entry === void 0 ? void 0 : resolve(name, entry);
23899
24638
  },
23900
24639
  ...compiled.ui !== void 0 ? { ui: compiled.ui } : {},
23901
24640
  warnings: diagnostics.filter((d) => d.severity === "warning"),
23902
- fingerprint: pinned?.fingerprint ?? configFingerprint(loaded.file.dependencyFiles, host.root, module.read ?? readFromDisk),
23903
- drift: pinned === void 0 ? [] : changedConfigFiles(pinned.fingerprint, configFingerprint(Object.keys(pinned.fingerprint).map((file) => resolve(host.root, file)), host.root, readFromDisk))
24641
+ fingerprint: pinned?.fingerprint ?? configFingerprint(nodeSyncIo, paths, loaded.file.dependencyFiles, host.root, module.read ?? readFromDisk),
24642
+ drift: pinned === void 0 ? [] : changedConfigFiles(pinned.fingerprint, configFingerprint(nodeSyncIo, paths, Object.keys(pinned.fingerprint).map((file) => paths.resolve(host.root, file)), host.root, readFromDisk))
23904
24643
  };
23905
24644
  });
23906
24645
  const readFromDisk = (path) => {
23907
24646
  try {
23908
- return readFileSync(path, "utf8");
24647
+ return nodeSyncIo.readText(path);
23909
24648
  } catch {
23910
24649
  return;
23911
24650
  }
23912
24651
  };
23913
24652
  const PACKAGE = "@pmelab/gtd";
23914
24653
  const FLOWS = `${PACKAGE}/flows`;
23915
- const ownPackage = () => {
23916
- const start = dirname(fileURLToPath(import.meta.url));
23917
- for (let dir = start;; dir = dirname(dir)) {
23918
- const file = join(dir, "package.json");
23919
- if (existsSync(file)) {
23920
- const pkg = JSON.parse(readFileSync(file, "utf8"));
24654
+ const ownPackage = (paths) => {
24655
+ const start = paths.dirname(runSync$1(paths.fromFileUrl(new URL(import.meta.url))));
24656
+ for (let dir = start;; dir = paths.dirname(dir)) {
24657
+ const source = readFromDisk(paths.join(dir, "package.json"));
24658
+ if (source !== void 0) {
24659
+ const pkg = JSON.parse(source);
23921
24660
  if (pkg.name === PACKAGE) return {
23922
24661
  root: dir,
23923
24662
  pkg
23924
24663
  };
23925
24664
  }
23926
- if (dirname(dir) === dir) throw new Error(`no ${PACKAGE} package.json above ${start}`);
24665
+ if (paths.dirname(dir) === dir) throw new Error(`no ${PACKAGE} package.json above ${start}`);
23927
24666
  }
23928
24667
  };
23929
24668
  let shippedMemo;
23930
- const shippedModules = () => shippedMemo ??= (async () => {
24669
+ const shippedModules = (paths) => shippedMemo ??= (async () => {
23931
24670
  const fromSource = import.meta.url.endsWith(".ts");
23932
- const files = subpathFiles(fromSource ? "source" : "default");
24671
+ const files = subpathFiles(paths, fromSource ? "source" : "default");
23933
24672
  const modules = { [FLOWS]: flows_exports };
23934
24673
  const missing = {};
23935
24674
  if (process.features.require_module && !fromSource) return {
@@ -23937,7 +24676,7 @@ const shippedModules = () => shippedMemo ??= (async () => {
23937
24676
  missing
23938
24677
  };
23939
24678
  for (const [id, path] of files) try {
23940
- modules[id] = await import(pathToFileURL(path).href);
24679
+ modules[id] = await import(fileUrl(paths, path));
23941
24680
  } catch {
23942
24681
  missing[id] = path;
23943
24682
  }
@@ -23947,13 +24686,13 @@ const shippedModules = () => shippedMemo ??= (async () => {
23947
24686
  };
23948
24687
  })();
23949
24688
  /** The file each subpath a config may import serves: all but `flows`, the engine's own, and the testkit's. */
23950
- const subpathFiles = (condition) => {
23951
- const { root, pkg } = ownPackage();
24689
+ const subpathFiles = (paths, condition) => {
24690
+ const { root, pkg } = ownPackage(paths);
23952
24691
  const files = /* @__PURE__ */ new Map();
23953
24692
  for (const [subpath, target] of Object.entries(pkg.exports)) {
23954
24693
  const file = typeof target === "string" ? target : target[condition];
23955
24694
  if (subpath === "./flows" || subpath.startsWith("./testkit") || file === void 0) continue;
23956
- files.set(`${PACKAGE}/${subpath.replace(/^\.\//, "")}`, join(root, file));
24695
+ files.set(`${PACKAGE}/${subpath.replace(/^\.\//, "")}`, paths.join(root, file));
23957
24696
  }
23958
24697
  return files;
23959
24698
  };
@@ -24147,16 +24886,16 @@ const shadowingDoors = (doors, catalogue, origin) => [...doors].filter(([name, d
24147
24886
  path: ["doors", name],
24148
24887
  origin
24149
24888
  }));
24150
- const moduleCandidates = (path) => [
24889
+ const moduleCandidates = (paths, path) => [
24151
24890
  path,
24152
24891
  path.replace(/\.js$/, ".ts"),
24153
24892
  path.replace(/\.mjs$/, ".mts"),
24154
24893
  `${path}.ts`,
24155
- join(path, "index.ts")
24894
+ paths.join(path, "index.ts")
24156
24895
  ];
24157
- const execute = (chunk, filepath, require) => {
24896
+ const execute = (paths, chunk, filepath, require) => {
24158
24897
  const cjs = { exports: {} };
24159
- chunk(cjs.exports, require, cjs, filepath, dirname(filepath));
24898
+ chunk(cjs.exports, require, cjs, filepath, paths.dirname(filepath));
24160
24899
  return cjs.exports;
24161
24900
  };
24162
24901
  /**
@@ -24168,40 +24907,40 @@ const execute = (chunk, filepath, require) => {
24168
24907
  * it, as Node would. `@pmelab/gtd/*` is the running gtd's own copy, and
24169
24908
  * `files` leaves out every package file.
24170
24909
  */
24171
- const evaluate = async (module, shipped) => {
24172
- const { code, files } = await bundleConfig(module, {
24910
+ const evaluate = async (paths, module, shipped) => {
24911
+ const { code, files } = await bundleConfig(paths, module, {
24173
24912
  kind: "evaluate",
24174
24913
  shipped
24175
24914
  });
24176
24915
  const fromConfig = createRequire(module.filepath);
24177
24916
  const require = Object.assign((id) => id in shipped.modules ? shipped.modules[id] : fromConfig(id in shipped.missing ? shipped.missing[id] : id), { resolve: fromConfig.resolve });
24178
- const chunk = runInThisContext(`(function (exports, require, module, __filename, __dirname) {${code}\n})`, { filename: module.filepath });
24917
+ const chunk = compileScript(`(function (exports, require, module, __filename, __dirname) {${code}\n})`, module.filepath);
24179
24918
  return {
24180
- exports: execute(chunk, module.filepath, require),
24919
+ exports: execute(paths, chunk, module.filepath, require),
24181
24920
  files
24182
24921
  };
24183
24922
  };
24184
24923
  const pinnedWorkflow = () => globalThis[PIN_KEY];
24185
- const runPinned = (pinned, filepath, root) => ({
24186
- exports: execute(pinned.run, filepath, (id) => id === FLOWS ? flows_exports : createRequire(import.meta.url)(id)),
24187
- files: Object.keys(pinned.fingerprint).map((file) => resolve(root, file))
24924
+ const runPinned = (paths, pinned, filepath, root) => ({
24925
+ exports: execute(paths, pinned.run, filepath, (id) => id === FLOWS ? flows_exports : createRequire(import.meta.url)(id)),
24926
+ files: Object.keys(pinned.fingerprint).map((file) => paths.resolve(root, file))
24188
24927
  });
24189
24928
  /**
24190
24929
  * The statement that registers `module`'s workflow for a pin: the config and
24191
24930
  * everything it imports — npm packages and gtd's own subpaths included — in
24192
24931
  * one function, so the engine runs it without a config, a package or rolldown.
24193
24932
  */
24194
- const pinnedWorkflowSource = async (module, root, version) => {
24195
- const gtd = realpathSync(ownPackage().root) + sep;
24196
- const { code, files } = await bundleConfig(module, {
24933
+ const pinnedWorkflowSource = async (paths, module, root, version) => {
24934
+ const gtd = nodeSyncIo.realPath(ownPackage(paths).root) + paths.sep;
24935
+ const { code, files } = await bundleConfig(paths, module, {
24197
24936
  kind: "pin",
24198
- shipped: subpathFiles(import.meta.url.endsWith(".ts") ? "source" : "default")
24937
+ shipped: subpathFiles(paths, import.meta.url.endsWith(".ts") ? "source" : "default")
24199
24938
  });
24200
24939
  const own = (file) => file.startsWith(gtd) && !module.filepath.startsWith(gtd);
24201
24940
  const registered = {
24202
24941
  version,
24203
- origin: relative(root, module.filepath),
24204
- fingerprint: configFingerprint(files.filter((file) => !own(file)), root, module.read ?? readFromDisk)
24942
+ origin: paths.relative(root, module.filepath),
24943
+ fingerprint: configFingerprint(nodeSyncIo, paths, files.filter((file) => !own(file)), root, module.read ?? readFromDisk)
24205
24944
  };
24206
24945
  return `globalThis[Symbol.for(${JSON.stringify(PIN_KEY.description)})] = { ...${JSON.stringify(registered)}, run: function (exports, require, module, __filename, __dirname) {\n${code}\n} };\n`;
24207
24946
  };
@@ -24215,11 +24954,11 @@ const shippedImport = (source, mode) => {
24215
24954
  external: true
24216
24955
  } : void 0;
24217
24956
  };
24218
- const bundleConfig = async (module, mode) => {
24957
+ const bundleConfig = async (paths, module, mode) => {
24219
24958
  const { read } = module;
24220
24959
  const served = /* @__PURE__ */ new Set();
24221
24960
  const fromRead = (path) => {
24222
- const hit = moduleCandidates(path).find((candidate) => read?.(candidate) !== void 0);
24961
+ const hit = moduleCandidates(paths, path).find((candidate) => read?.(candidate) !== void 0);
24223
24962
  if (hit === void 0) return null;
24224
24963
  served.add(hit);
24225
24964
  return hit;
@@ -24233,18 +24972,18 @@ const bundleConfig = async (module, mode) => {
24233
24972
  if (importer === void 0) return source === module.filepath ? source : null;
24234
24973
  const shipped = shippedImport(source, mode);
24235
24974
  if (shipped !== void 0) return shipped;
24236
- if (/^\.\.?(\/|$)/.test(source)) return fromRead(join(dirname(importer), source));
24237
- if (!isPackage(source)) return null;
24975
+ if (/^\.\.?(\/|$)/.test(source)) return fromRead(paths.join(paths.dirname(importer), source));
24976
+ if (!isPackage(paths, source)) return null;
24238
24977
  const resolved = await this.resolve(source, importer, { skipSelf: true });
24239
- return mode.kind === "pin" ? resolved : packageImport(source, resolved);
24978
+ return mode.kind === "pin" ? resolved : packageImport(paths, source, resolved);
24240
24979
  },
24241
24980
  load(id) {
24242
- if (mode.kind === "pin" && isNativeAddon(id)) throw new Error(nativeAddon(id));
24981
+ if (mode.kind === "pin" && isNativeAddon(paths, id)) throw new Error(nativeAddon(paths, id));
24243
24982
  if (id === module.filepath) return module.source;
24244
24983
  return served.has(id) ? read(id) : null;
24245
24984
  },
24246
24985
  transform(code, id) {
24247
- return code.replace(/\bimport\.meta\.(url|filename|dirname)\b/g, (_, key) => JSON.stringify(key === "url" ? pathToFileURL(id).href : key === "filename" ? id : dirname(id)));
24986
+ return code.replace(/\bimport\.meta\.(url|filename|dirname)\b/g, (_, key) => JSON.stringify(key === "url" ? fileUrl(paths, id) : key === "filename" ? id : paths.dirname(id)));
24248
24987
  }
24249
24988
  }],
24250
24989
  external: (id) => isBuiltin(id),
@@ -24274,34 +25013,42 @@ const bundleConfig = async (module, mode) => {
24274
25013
  await bundle.close();
24275
25014
  }
24276
25015
  };
24277
- const isNativeAddon = (id) => {
25016
+ const isNativeAddon = (paths, id) => {
24278
25017
  if (id.endsWith(".node")) return true;
25018
+ const { sep } = paths;
24279
25019
  const at = id.lastIndexOf(`node_modules${sep}`);
24280
25020
  if (at < 0) return false;
24281
25021
  const rest = id.slice(at + `node_modules${sep}`.length).split(sep);
24282
- return existsSync(join(id.slice(0, at) + ["node_modules", ...rest.slice(0, rest[0].startsWith("@") ? 2 : 1)].join(sep), "binding.gyp"));
25022
+ const root = id.slice(0, at) + ["node_modules", ...rest.slice(0, rest[0].startsWith("@") ? 2 : 1)].join(sep);
25023
+ try {
25024
+ return nodeSyncIo.stat(paths.join(root, "binding.gyp")) !== void 0;
25025
+ } catch {
25026
+ return false;
25027
+ }
24283
25028
  };
24284
- const nativeAddon = (path) => {
25029
+ const nativeAddon = (paths, path) => {
25030
+ const { sep } = paths;
24285
25031
  const at = path.lastIndexOf(`node_modules${sep}`);
24286
25032
  const rest = at < 0 ? [] : path.slice(at + `node_modules${sep}`.length).split(sep);
24287
25033
  const name = rest[0]?.startsWith("@") ? rest.slice(0, 2).join("/") : rest[0];
24288
25034
  return `${name === void 0 ? path : `package "${name}"`} loads a native addon (${path}), which a pin cannot carry`;
24289
25035
  };
24290
- const packageImport = (source, resolved) => resolved !== null && isEsModule(resolved.id) ? resolved.id : {
25036
+ const packageImport = (paths, source, resolved) => resolved !== null && isEsModule(paths, resolved.id) ? resolved.id : {
24291
25037
  id: resolved?.id ?? source,
24292
25038
  external: true
24293
25039
  };
24294
- const isEsModule = (path) => {
25040
+ const isEsModule = (paths, path) => {
24295
25041
  if (/\.(mjs|[cm]?tsx?)$/.test(path)) return true;
24296
25042
  if (!/\.jsx?$/.test(path)) return false;
24297
- for (let dir = dirname(path); dirname(dir) !== dir; dir = dirname(dir)) {
24298
- const file = join(dir, "package.json");
24299
- if (existsSync(file)) return JSON.parse(readFileSync(file, "utf8")).type === "module";
25043
+ for (let dir = paths.dirname(path); paths.dirname(dir) !== dir; dir = paths.dirname(dir)) {
25044
+ const source = readFromDisk(paths.join(dir, "package.json"));
25045
+ if (source !== void 0) return JSON.parse(source).type === "module";
24300
25046
  }
24301
25047
  return false;
24302
25048
  };
24303
25049
  const inPackage = (path) => /[\\/]node_modules[\\/]/.test(path);
24304
- const isPackage = (specifier) => !isAbsolute(specifier) && !isBuiltin(specifier);
25050
+ const isPackage = (paths, specifier) => !paths.isAbsolute(specifier) && !isBuiltin(specifier);
25051
+ const fileUrl = (paths, path) => runSync$1(paths.toFileUrl(path)).href;
24305
25052
  const bundleError = (e) => {
24306
25053
  const errors = e?.errors;
24307
25054
  if (errors === void 0) return e;
@@ -24558,11 +25305,15 @@ var EngineCore = class EngineCore extends Service()("EngineCore") {
24558
25305
  static layer = (path) => succeed$2(EngineCore, { source: suspend$2(() => {
24559
25306
  const file = path();
24560
25307
  return try_({
24561
- try: () => readFileSync(file, "utf8"),
25308
+ try: () => {
25309
+ const source = nodeSyncIo.readText(file);
25310
+ if (source === void 0) throw new Error("absent");
25311
+ return source;
25312
+ },
24562
25313
  catch: () => new GtdError(`gtd bundle: no engine at ${file} — build gtd first`)
24563
25314
  });
24564
25315
  }) });
24565
- static Live = EngineCore.layer(() => join(ownPackage().root, "dist", "gtd.core.mjs"));
25316
+ static Live = unwrap$6(map(Path$1, (path) => EngineCore.layer(() => path.join(ownPackage(path).root, "dist", "gtd.core.mjs"))));
24566
25317
  };
24567
25318
  /**
24568
25319
  * A pin: one plain-text header line a launcher reads without unpacking, then
@@ -24571,11 +25322,12 @@ var EngineCore = class EngineCore extends Service()("EngineCore") {
24571
25322
  */
24572
25323
  const pin = (module, root, version) => gen(function* () {
24573
25324
  const core = yield* (yield* EngineCore).source;
25325
+ const path = yield* Path$1;
24574
25326
  const source = (yield* tryPromise({
24575
- try: () => pinnedWorkflowSource(module, root, version),
25327
+ try: () => pinnedWorkflowSource(path, module, root, version),
24576
25328
  catch: (e) => new GtdError(`gtd bundle: ${module.filepath}: ${e instanceof Error ? e.message : String(e)}`)
24577
25329
  })) + core.replace(/^#!.*\n/, "");
24578
- return Buffer.concat([Buffer.from(`gtdpin 1 gtd ${version}\n`), gzipSync(source, { level: 9 })]);
25330
+ return Buffer.concat([Buffer.from(`gtdpin 1 gtd ${version}\n`), gzip(source)]);
24579
25331
  });
24580
25332
  //#endregion
24581
25333
  //#region src/emit/GitScript.ts
@@ -25030,7 +25782,7 @@ const modeCommandSteps = (resolved, file) => {
25030
25782
  return steps;
25031
25783
  };
25032
25784
  /** `<Host.scratchDir>/gtd-mode-sample-<mode>-<pid>.md` — an absolute literal baked in at emit time, never a shell variable. `<pid>` avoids collisions between concurrent `gtd` processes. Never a `/tmp` literal or `mktemp` (`tests/tooling/no-tmp-assumption.test.ts` scans for both). */
25033
- const scratchSamplePath = (scratchDir, mode) => join(scratchDir, `gtd-mode-sample-${mode}-${process.pid}.md`);
25785
+ const scratchSamplePath = (path, { scratchDir, pid }, mode) => path.join(scratchDir, `gtd-mode-sample-${mode}-${pid}.md`);
25034
25786
  /**
25035
25787
  * The contradiction round-trip/skip-notice steps for `resolved`'s `mode:`.
25036
25788
  * No `formatCommand` means nothing to round-trip: empty. Otherwise: a live
@@ -25043,8 +25795,7 @@ const modeContradictionSteps = (resolved) => gen(function* () {
25043
25795
  if (resolved.formatCommand === void 0) return [];
25044
25796
  const { capabilities } = resolved;
25045
25797
  if (capabilities.format !== void 0 && capabilities.liveValidate !== void 0) {
25046
- const { scratchDir } = yield* Host;
25047
- const samplePath = scratchSamplePath(scratchDir, resolved.mode);
25798
+ const samplePath = scratchSamplePath(yield* Path$1, yield* Host, resolved.mode);
25048
25799
  const formatCommand = withFileVar(resolved.formatCommand, samplePath);
25049
25800
  return [{
25050
25801
  kind: "command",
@@ -25378,11 +26129,21 @@ const approvalEntryOf = (hash, message) => message.approved === void 0 ? [] : [{
25378
26129
  email: message.approved.email,
25379
26130
  commit: hash
25380
26131
  }];
26132
+ const setAsideIn = (history, location) => {
26133
+ const anchored = setAsideBy(hashAt(history, location.baseIndex) ?? EMPTY_TREE$1, history.slice(location.baseIndex + 1).map((c) => ({
26134
+ hash: c.hash,
26135
+ parsed: parseCommitMessage(c.message)
26136
+ })));
26137
+ return "setAside" in anchored ? anchored.setAside : /* @__PURE__ */ new Set();
26138
+ };
25381
26139
  const buildRun = (history, location, closingHash) => {
25382
26140
  const allProcessCommits = history.slice(location.processStart);
25383
26141
  const allParsed = allProcessCommits.map((c) => parseCommitMessage(c.message));
25384
26142
  const processCommits = allProcessCommits.filter((_, i) => allParsed[i].pin !== "add");
25385
26143
  const parsed = allParsed.filter((m) => m.pin !== "add");
26144
+ const setAside = setAsideIn(history, location);
26145
+ const answering = parsed.filter((_, i) => !setAside.has(processCommits[i].hash));
26146
+ const answeringCommits = processCommits.filter((c) => !setAside.has(c.hash));
25386
26147
  const first = parsed[0];
25387
26148
  const startParentHash = hashAt(history, location.processStart - 1) ?? EMPTY_TREE$1;
25388
26149
  const head = history[history.length - 1];
@@ -25394,10 +26155,10 @@ const buildRun = (history, location, closingHash) => {
25394
26155
  diffBase: first?.reviewBase ?? startParentHash,
25395
26156
  trace: processCommits.map((c, i) => traceEntryOf(c.hash, parsed[i])),
25396
26157
  costEntries: parsed.flatMap((m, i) => costEntriesOf(processCommits[i].hash, m)),
25397
- judgeVerdicts: parsed.flatMap((m) => m.judge),
26158
+ judgeVerdicts: answering.flatMap((m) => m.judge),
25398
26159
  pinCommitted: allParsed.some((m) => m.pin === "add"),
25399
- marks: parsed.flatMap((m) => m.marks),
25400
- approvals: processCommits.flatMap((c, i) => approvalEntryOf(c.hash, parsed[i])),
26160
+ marks: answering.flatMap((m) => m.marks),
26161
+ approvals: answeringCommits.flatMap((c, i) => approvalEntryOf(c.hash, answering[i])),
25401
26162
  pinnedVars: { ...first?.vars },
25402
26163
  headTurn: headTurnOf(head),
25403
26164
  closingHash,
@@ -25434,7 +26195,7 @@ const subjectIsBoundary = (def, subject) => {
25434
26195
  const parsed = parseCommitMessage(subject).parsed;
25435
26196
  if (parsed === void 0 || !ACTORS.has(parsed.actor)) return true;
25436
26197
  if (parsed.actor === "human" && parsed.from === void 0) return false;
25437
- if (parsed.actor === "check" && parsed.from === PIN_STATES.add) return false;
26198
+ if (parsed.actor === "check" && (parsed.from === PIN_STATES.add || parsed.from === LEGACY_PIN_STATES.add)) return false;
25438
26199
  return parsed.to === def.initial;
25439
26200
  };
25440
26201
  /** The last page index (walking newest→oldest, same direction as `locateEpisode`) whose subject is a boundary, or `undefined` if none in `page`. */
@@ -25826,6 +26587,43 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
25826
26587
  };
25827
26588
  });
25828
26589
  const currentRest = restAt(void 0);
26590
+ /**
26591
+ * Where `gtd repin` anchors a pinned process the live workflow no longer
26592
+ * replays: the commit replay last stood on, and the step the live workflow
26593
+ * reaches from there on HEAD's tree — the unpin when the flow ends instead.
26594
+ * `undefined` when there is nothing to anchor: no divergence, or no pin.
26595
+ */
26596
+ const repinAnchor = gen(function* () {
26597
+ const git = yield* GitService;
26598
+ const config = yield* (yield* ConfigService).load;
26599
+ const workspace = yield* Workspace;
26600
+ const run = yield* computeProcessRun(git, config.workflow);
26601
+ const { def, setup } = yield* setupFor(config, run, workspace, yield* Host);
26602
+ const diverged = yield* promise(() => replayFor(setup));
26603
+ if (diverged.kind !== "divergence" || !headHasPin(workspace, run)) return void 0;
26604
+ const trial = {
26605
+ hash: "",
26606
+ message: formatCommitMessage({
26607
+ actor: "check",
26608
+ from: PIN_STATES.add,
26609
+ to: PIN_STATES.add,
26610
+ pin: "add",
26611
+ anchor: diverged.at
26612
+ }),
26613
+ tree: setup.episode.at(-1)?.tree ?? setup.base.tree
26614
+ };
26615
+ const outcome = yield* promise(() => replayFor({
26616
+ ...setup,
26617
+ episode: [...setup.episode, trial]
26618
+ }));
26619
+ if (outcome.kind !== "divergence") return yield* fail$4(replayError(outcome) ?? /* @__PURE__ */ new Error("gtd repin: replay found no step"));
26620
+ const to = outcome.reached ?? PIN_STATES.remove;
26621
+ if (run.workflow === void 0 && to === def.initial) return yield* fail$4(/* @__PURE__ */ new Error(`gtd repin: the live workflow continues this process at "${to}", the default workflow's first step, where a finished process waits — run \`gtd abandon\` to start over`));
26622
+ return {
26623
+ at: diverged.at,
26624
+ to
26625
+ };
26626
+ });
25829
26627
  /** The step the process rests at and the trace that reached it — at an unpin, the flow's last step. */
25830
26628
  const restingOn = (outcome, pin) => {
25831
26629
  if (outcome.kind === "rest") return {
@@ -26156,15 +26954,16 @@ const summaryFor = (run) => gen(function* () {
26156
26954
  * repository cannot be read at a ref, so it is left out. A file only the old
26157
26955
  * config read needs no check: whichever file imported it differs too.
26158
26956
  */
26159
- const inRepoConfigPaths = (root, files) => {
26160
- const paths = /* @__PURE__ */ new Set();
26161
- for (const file of files) paths.add(relative(root, resolve(root, file)).split(sep).join("/"));
26162
- return [...paths].filter((p) => p !== "" && !p.startsWith("..") && !isAbsolute(p)).sort();
26957
+ const inRepoConfigPaths = (path, root, files) => {
26958
+ const found = /* @__PURE__ */ new Set();
26959
+ for (const file of files) found.add(path.relative(root, path.resolve(root, file)).split(path.sep).join("/"));
26960
+ return [...found].filter((p) => p !== "" && !p.startsWith("..") && !path.isAbsolute(p)).sort();
26163
26961
  };
26164
26962
  const configRefusal = (command, tip, paths, reason = "A history ref replays under today's config, so it is unreadable after the config changes") => /* @__PURE__ */ new Error(`${command}: refused — config differs at ${tip.slice(0, 7)}: ${paths.join(", ")}. ${reason}`);
26165
26963
  const differingConfigPaths = (git, workspace, root, config, hash) => gen(function* () {
26166
26964
  const differing = [];
26167
- for (const path of inRepoConfigPaths(root, Object.keys(config.fingerprint))) if ((yield* orElseSucceed(git.readFileAtRef(hash, path), () => void 0)) !== workspace.readSync(path)) differing.push(path);
26965
+ const configPaths = inRepoConfigPaths(yield* Path$1, root, Object.keys(config.fingerprint));
26966
+ for (const path of configPaths) if ((yield* orElseSucceed(git.readFileAtRef(hash, path), () => void 0)) !== workspace.readSync(path)) differing.push(path);
26168
26967
  return differing;
26169
26968
  });
26170
26969
  const notClosedError = (hash, outcome) => outcome.kind === "rest" ? /* @__PURE__ */ new Error(`gtd history: ${hash.slice(0, 7)} does not close a process under today's config`) : replayError(outcome) ?? /* @__PURE__ */ new Error("gtd history: replay failed");
@@ -26944,12 +27743,11 @@ const runHistoryJudgesCommand = (refs, vars, jsonl, json, out) => gen(function*
26944
27743
  });
26945
27744
  //#endregion
26946
27745
  //#region src/cli/Program.ts
26947
- const selfGtd = () => {
26948
- const script = process.argv[1];
26949
- return script === void 0 ? ["gtd"] : [process.execPath, script];
26950
- };
26951
27746
  /** `needs: "none"` skips the repo-root guard — the server is keyed on file name, not workflow state. */
26952
- const runLspCommand = () => flatMap(ToolCommands, (tools) => tools.lsp(selfGtd()));
27747
+ const runLspCommand = () => gen(function* () {
27748
+ const { self } = yield* Host;
27749
+ return yield* (yield* ToolCommands).lsp(self);
27750
+ });
26953
27751
  /**
26954
27752
  * Prints the briefing, emitted by the binary itself so it's always current.
26955
27753
  * gtd ships no workflow, so where no `gtd.config.ts` governs it says where to
@@ -27057,19 +27855,6 @@ const parseJudgeDocument = (rendered) => try_({
27057
27855
  try: () => JSON.parse(rendered),
27058
27856
  catch: () => /* @__PURE__ */ new Error("gtd judge answer: the resolved rest's \"judge:\" template did not render valid JSON")
27059
27857
  });
27060
- /**
27061
- * The first stdin-consuming command outside `src/lsp/Lsp.ts` (which reads LSP's
27062
- * own length-prefixed frames off `process.stdin`, not a one-shot read) —
27063
- * reads stdin to completion and decodes it as UTF-8 text, no framing.
27064
- */
27065
- const readStdin = () => tryPromise({
27066
- try: async () => {
27067
- const chunks = [];
27068
- for await (const chunk of process.stdin) chunks.push(chunk);
27069
- return Buffer.concat(chunks).toString("utf8");
27070
- },
27071
- catch: (e) => e instanceof Error ? e : new Error(String(e))
27072
- });
27073
27858
  /** A stdin that is no judgment is the caller's mistake, a usage error like `gtd judge answer`'s. */
27074
27859
  const judgmentShape = (input) => {
27075
27860
  let doc;
@@ -27086,7 +27871,7 @@ const runJudgeRunCommand = (command, out) => gen(function* () {
27086
27871
  const host = yield* Host;
27087
27872
  const tools = yield* ToolCommands;
27088
27873
  yield* refuseLegacyRc;
27089
- const input = yield* readStdin();
27874
+ const input = yield* host.stdin;
27090
27875
  yield* judgmentShape(input);
27091
27876
  const verdict = yield* tryPromise({
27092
27877
  try: () => tools.judge({
@@ -27134,7 +27919,7 @@ const runJudgeAnswerCommand = (json, out) => gen(function* () {
27134
27919
  const rendered = yield* renderRest(rest);
27135
27920
  if (rendered.judge === void 0) return yield* fail$4(/* @__PURE__ */ new Error(`gtd judge answer: refused — the resolved rest ("${rest.state}") declares no "judge:"`));
27136
27921
  const ids = (yield* parseJudgeDocument(rendered.judge)).questions.map((q) => q.id);
27137
- const raw = yield* readStdin();
27922
+ const raw = yield* (yield* Host).stdin;
27138
27923
  const parsed = yield* try_({
27139
27924
  try: () => JSON.parse(raw),
27140
27925
  catch: () => new SelectorUsageError("gtd judge answer: stdin is not valid JSON")
@@ -27339,20 +28124,28 @@ const runRestoreCommand = (json, out) => gen(function* () {
27339
28124
  ]).required;
27340
28125
  yield* writeScript(json, out, combinedScript(required, ""));
27341
28126
  });
27342
- /**
27343
- * `gtd repin`: the pin beat's script and landing, on demand. It runs live,
27344
- * so reading the rest first refuses a live workflow that cannot replay the
27345
- * process before any pin is written.
27346
- */
27347
- const runRepinCommand = (json, out) => gen(function* () {
28127
+ /** The step an ordinary repin rests at — the one the process rests at now. */
28128
+ const repinTarget = gen(function* () {
27348
28129
  const rest = yield* currentRest;
27349
28130
  const refusal = rest.pin === "add" ? "the next beat pins the process already — run it (`gtd next --json=content`), then land" : rest.pin === "remove" ? "the flow has ended — its unpin beat is next, so there is nothing to repin" : noProcessUnderway(rest) ? "no gtd process is underway — nothing to repin" : void 0;
27350
28131
  if (refusal !== void 0) return yield* fail$4(/* @__PURE__ */ new Error(`gtd repin: ${refusal}`));
28132
+ return rest.step.name;
28133
+ });
28134
+ /**
28135
+ * `gtd repin`: the pin beat's script and landing, on demand. It runs live: a
28136
+ * pinned process the live workflow no longer replays is anchored where it
28137
+ * diverged, and any other failure to read the rest refuses before any pin is
28138
+ * written.
28139
+ */
28140
+ const runRepinCommand = (json, out) => gen(function* () {
28141
+ const anchor = yield* repinAnchor;
28142
+ const to = anchor?.to ?? (yield* repinTarget);
27351
28143
  const message = formatCommitMessage({
27352
28144
  actor: "check",
27353
28145
  from: PIN_STATES.add,
27354
- to: rest.step.name,
27355
- pin: "add"
28146
+ to,
28147
+ pin: "add",
28148
+ ...anchor !== void 0 ? { anchor: anchor.at } : {}
27356
28149
  });
27357
28150
  yield* writeScript(json, out, combinedScript(emitScripts([
27358
28151
  {
@@ -27365,7 +28158,7 @@ const runRepinCommand = (json, out) => gen(function* () {
27365
28158
  },
27366
28159
  {
27367
28160
  kind: "outcome",
27368
- command: transitionOutcome(PIN_STATES.add, rest.step.name)
28161
+ command: transitionOutcome(PIN_STATES.add, to)
27369
28162
  }
27370
28163
  ]).required, ""));
27371
28164
  });
@@ -28039,7 +28832,7 @@ With no <name>, list every workflow and door it can start. An unknown <name>, th
28039
28832
  arity: "none",
28040
28833
  group: "Start and stop",
28041
28834
  summary: "Pin the process to the live gtd and workflow",
28042
- details: `Switch the process underway to the live engine and workflow, the way out when the workflow needs a fix mid-process: the printed script runs the pin beat again, writing \`gtd bundle\`'s output to .gtd/process.gtdpin and committing that file alone. Always runs on the gtd on PATH, never on the pin. Refuses when no process is underway, when the next beat is a pin or unpin beat already, or when the live workflow does not replay the process.
28835
+ details: `Switch the process underway to the live engine and workflow, the way out when the workflow needs a fix mid-process: the printed script runs the pin beat again, writing \`gtd bundle\`'s output to .gtd/process.gtdpin and committing that file alone. Always runs on the gtd on PATH, never on the pin. When the live workflow no longer replays the process, the commit continues from the divergence: a Gtd-Anchor trailer names the last commit it replays, the later commits stay as files only, and the process rests at the step the live workflow reached there. Refuses when no process is underway, when the next beat is a pin or unpin beat already, when an unpinned process diverges, or when the live workflow would continue at the default workflow's first step.
28043
28836
 
28044
28837
  s=$(gtd repin --json=script) && sh -c "$s"`
28045
28838
  },
@@ -29312,6 +30105,7 @@ const toError = (e) => e instanceof Error ? e : new Error(String(e));
29312
30105
  * one). Mirrors the guard the deleted `makeInMemoryFileSystem` carried.
29313
30106
  */
29314
30107
  const isGitDirKey = (key) => key.startsWith(".git/");
30108
+ const paths = runSync$1(provide(Path$1, layer$4));
29315
30109
  /**
29316
30110
  * `Workspace` for the in-memory tier. `readSync`/`read`/`write`/`committed`
29317
30111
  * take a repo-relative key ONLY (mirroring the live adapter's
@@ -29327,12 +30121,12 @@ const isGitDirKey = (key) => key.startsWith(".git/");
29327
30121
  */
29328
30122
  const makeInMemoryWorkspaceOps = (repo, root) => {
29329
30123
  const toKey = (path) => {
29330
- if (!isAbsolute(path)) return path;
30124
+ if (!paths.isAbsolute(path)) return path;
29331
30125
  const prefix = `${root}/`;
29332
30126
  return path === root ? "" : path.startsWith(prefix) ? path.slice(prefix.length) : path;
29333
30127
  };
29334
30128
  const assertRepoRelative = (path) => {
29335
- if (isAbsolute(path)) throw new Error(`Workspace: "${path}" is an absolute path — this member takes repo-relative paths only (use "atPath" to read outside the repo)`);
30129
+ if (paths.isAbsolute(path)) throw new Error(`Workspace: "${path}" is an absolute path — this member takes repo-relative paths only (use "atPath" to read outside the repo)`);
29336
30130
  return path;
29337
30131
  };
29338
30132
  const readAt = (key) => isGitDirKey(key) ? void 0 : repo.readFile(key);
@@ -29379,7 +30173,7 @@ const makeInMemoryConfigDiscovery = (repo, root) => {
29379
30173
  const workspace = makeInMemoryWorkspaceOps(repo, root);
29380
30174
  return succeed$2(ConfigDiscovery, {
29381
30175
  legacyRcFiles: (levelRoot, levelHome) => try_({
29382
- try: () => legacyRcDirs(levelRoot, levelHome).flatMap((dir) => LEGACY_RC_FILES.map((name) => join(dir, name)).filter((filepath) => workspace.atPath(filepath) !== void 0).map((path) => ({
30176
+ try: () => legacyRcDirs(paths, levelRoot, levelHome).flatMap((dir) => LEGACY_RC_FILES.map((name) => paths.join(dir, name)).filter((filepath) => workspace.atPath(filepath) !== void 0).map((path) => ({
29383
30177
  path,
29384
30178
  owned: true
29385
30179
  }))),
@@ -29387,8 +30181,8 @@ const makeInMemoryConfigDiscovery = (repo, root) => {
29387
30181
  }),
29388
30182
  workflowModule: (levelRoot, levelHome) => try_({
29389
30183
  try: () => {
29390
- for (const dir of walkUp(levelRoot, levelHome)) {
29391
- const filepath = join(dir, WORKFLOW_MODULE);
30184
+ for (const dir of walkUp(paths, levelRoot, levelHome)) {
30185
+ const filepath = paths.join(dir, WORKFLOW_MODULE);
29392
30186
  const source = workspace.atPath(filepath);
29393
30187
  if (source !== void 0) return {
29394
30188
  filepath,
@@ -29420,26 +30214,41 @@ const gitTestLayer = (repo, root = "/repo") => succeed$2(GitService, fakeGitOper
29420
30214
  * itself; explicit env wins.
29421
30215
  */
29422
30216
  const ambientFlagEnv = (env = process.env) => Object.fromEntries(Object.entries(env).filter((e) => e[0].startsWith("GTD_FLAG_") && e[1] !== void 0));
30217
+ /** Where the in-memory host's scratch files go when `TMPDIR` is unset. */
30218
+ const IN_MEMORY_SCRATCH = "/scratch";
30219
+ const IN_MEMORY_PID = 4242;
30220
+ /**
30221
+ * The in-memory `Host`: nothing read off the real process or `node:os`.
30222
+ * `realPath` is a no-op (there is no real filesystem to resolve symlinks
30223
+ * against), so both sides of `assertRunningFromRepoRoot`'s comparison
30224
+ * collapse to `root`.
30225
+ */
30226
+ const inMemoryHost = (root, home, env, stdin = "") => ({
30227
+ root,
30228
+ home,
30229
+ env,
30230
+ scratchDir: scratchDirOf(env, IN_MEMORY_SCRATCH),
30231
+ realPath: () => succeed$1(root),
30232
+ pid: IN_MEMORY_PID,
30233
+ uid: void 0,
30234
+ self: ["gtd"],
30235
+ stdin: succeed$1(stdin)
30236
+ });
29423
30237
  /** Every layer `makeProgram` needs — `CommandRequirements`'s return type here IS the guarantee: a new port added there fails this function's typecheck instead of silently under-providing. */
29424
30238
  function testLayers(repo, opts = {}) {
29425
30239
  const root = opts.root ?? "/repo";
29426
30240
  const home = opts.home ?? root;
29427
- return mergeAll(gitTestLayer(repo, root), succeed$2(Workspace, makeInMemoryWorkspaceOps(repo, root)), Host.layer({
29428
- root,
29429
- home,
29430
- env: {
29431
- ...ambientFlagEnv(),
29432
- ...opts.env
29433
- },
29434
- realPath: () => succeed$1(root)
29435
- }), ConfigService.Live, makeInMemoryConfigDiscovery(repo, root), succeed$2(EngineCore, { source: succeed$1(IN_MEMORY_ENGINE) }), Narrator.layer(opts.narrate ?? (() => {}), opts.verbose ?? true), succeed$2(ToolCommands, {
30241
+ return mergeAll(gitTestLayer(repo, root), succeed$2(Workspace, makeInMemoryWorkspaceOps(repo, root)), succeed$2(Host, inMemoryHost(root, home, {
30242
+ ...ambientFlagEnv(),
30243
+ ...opts.env
30244
+ }, opts.stdin)), ConfigService.Live, makeInMemoryConfigDiscovery(repo, root), succeed$2(EngineCore, { source: succeed$1(IN_MEMORY_ENGINE) }), Narrator.layer(opts.narrate ?? (() => {}), opts.verbose ?? true), layer$4, succeed$2(ToolCommands, {
29436
30245
  ...missingToolCommands,
29437
30246
  ...opts.tools
29438
30247
  }), CommandRunner.layer(() => fail$4(/* @__PURE__ */ new Error("gtd ui: CommandRunner has no test double wired into testLayers()"))));
29439
30248
  }
29440
30249
  //#endregion
29441
30250
  //#region src/twins/cliIo.ts
29442
- const makeCapturingCliIo = (repo, env = {}, tools = {}) => {
30251
+ const makeCapturingCliIo = (repo, env = {}, tools = {}, stdin = "") => {
29443
30252
  let stdout = Buffer.alloc(0);
29444
30253
  let stderr = "";
29445
30254
  let exitCode = 0;
@@ -29458,7 +30267,8 @@ const makeCapturingCliIo = (repo, env = {}, tools = {}) => {
29458
30267
  env,
29459
30268
  narrate: (line) => stderr += line,
29460
30269
  verbose,
29461
- tools
30270
+ tools,
30271
+ stdin
29462
30272
  })
29463
30273
  },
29464
30274
  result: () => ({
@@ -30394,6 +31204,8 @@ const unrecognized = (block, output = "") => ({
30394
31204
  error: `${UNRECOGNIZED_PREFIX}${block.split("\n")[0]}`
30395
31205
  });
30396
31206
  Narrator.layer(() => {}, false);
31207
+ new TextEncoder();
31208
+ new TextDecoder("utf-8", { ignoreBOM: true });
30397
31209
  //#endregion
30398
31210
  //#region src/testkit/imports.ts
30399
31211
  const IDENTIFIER$1 = /^[A-Za-z_$][\w$]*$/;
@@ -30619,8 +31431,8 @@ const testRepo = (options) => {
30619
31431
  ...Object.fromEntries(Object.keys(process.env).filter((name) => name.startsWith("GTD_FLAG_")).map((name) => [name, void 0])),
30620
31432
  ...env
30621
31433
  } : env;
30622
- const gtd = async (...args) => {
30623
- const { io, result } = makeCapturingCliIo(repo, seenEnv());
31434
+ const gtdWithStdin = async (stdin, ...args) => {
31435
+ const { io, result } = makeCapturingCliIo(repo, seenEnv(), {}, stdin);
30624
31436
  if (typeof workflow !== "string") registry().set(id, workflow);
30625
31437
  for (const [specifier, module] of modules) registry().set(moduleKey(specifier), module);
30626
31438
  try {
@@ -30635,20 +31447,7 @@ const testRepo = (options) => {
30635
31447
  }
30636
31448
  return result();
30637
31449
  };
30638
- const withStdin = async (stdin, fn) => {
30639
- const saved = Object.getOwnPropertyDescriptor(process, "stdin");
30640
- const fake = new PassThrough();
30641
- Object.defineProperty(process, "stdin", {
30642
- value: fake,
30643
- configurable: true
30644
- });
30645
- fake.end(stdin);
30646
- try {
30647
- return await fn();
30648
- } finally {
30649
- if (saved) Object.defineProperty(process, "stdin", saved);
30650
- }
30651
- };
31450
+ const gtd = (...args) => gtdWithStdin("", ...args);
30652
31451
  const apply = (what, script) => {
30653
31452
  if (script.length === 0) return;
30654
31453
  const applied = applyEmittedScript(repo, commands, script);
@@ -30656,7 +31455,7 @@ const testRepo = (options) => {
30656
31455
  };
30657
31456
  /** Runs a write command, then the script its `--json=script` twin prints. */
30658
31457
  const runWrite = async (args, stdin, refused = (error) => error) => {
30659
- const call = (...extra) => stdin === void 0 ? gtd(...args, ...extra) : withStdin(stdin, () => gtd(...args, ...extra));
31458
+ const call = (...extra) => gtdWithStdin(stdin ?? "", ...args, ...extra);
30660
31459
  const reported = await call();
30661
31460
  if (reported.exitCode !== 0) throw refused(failed(`gtd ${args.join(" ")}`, reported));
30662
31461
  const script = await call("--json=script");