@pmelab/gtd 63.0.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 (67) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +4 -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-DwypA7cH.mjs → testkit-zGjNeQNL.mjs} +1011 -313
  7. package/dist/flows.mjs +1 -1
  8. package/dist/gtd.bundle.mjs +3890 -1294
  9. package/dist/gtd.core.mjs +119 -118
  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 +9 -27
  17. package/src/cli/Output.ts +2 -1
  18. package/src/cli/Program.test.ts +55 -53
  19. package/src/cli/Program.ts +11 -30
  20. package/src/cli/ToolCommands.ts +3 -4
  21. package/src/edge/Edge.test.ts +38 -12
  22. package/src/edge/Edge.ts +30 -10
  23. package/src/emit/SteeringMode.test.ts +4 -2
  24. package/src/emit/SteeringMode.ts +10 -9
  25. package/src/gherkin/messages.test.ts +1 -1
  26. package/src/gherkin/module-config.feature +1 -1
  27. package/src/gherkin/testkit.feature +2 -2
  28. package/src/install/Version.ts +14 -13
  29. package/src/platform/Host.ts +75 -22
  30. package/src/platform/NodePinFile.ts +44 -0
  31. package/src/platform/NodeRuntime.ts +19 -0
  32. package/src/platform/NodeSync.ts +64 -0
  33. package/src/platform/PinFile.ts +0 -50
  34. package/src/platform/Sha.ts +121 -0
  35. package/src/platform/SyncIo.ts +35 -0
  36. package/src/platform/Workspace.test.ts +8 -3
  37. package/src/platform/Workspace.ts +82 -123
  38. package/src/platform/WorktreeState.test.ts +8 -4
  39. package/src/platform/WorktreeState.ts +21 -19
  40. package/src/platform/index.ts +18 -5
  41. package/src/replay/Format.test.ts +16 -13
  42. package/src/replay/Format.ts +4 -2
  43. package/src/replay/Replay.test.ts +25 -5
  44. package/src/replay/Replay.ts +10 -6
  45. package/src/replay/Trailers.test.ts +10 -10
  46. package/src/replay/Trailers.ts +23 -1
  47. package/src/replay/index.ts +1 -0
  48. package/src/step/planStart.test.ts +2 -2
  49. package/src/step/planStep.test.ts +4 -4
  50. package/src/testkit/TestRepo.test.ts +1 -1
  51. package/src/testkit/TestRepo.ts +4 -18
  52. package/src/twins/Host.test.ts +151 -0
  53. package/src/twins/Layers.test.ts +2 -2
  54. package/src/twins/Layers.ts +44 -18
  55. package/src/twins/Path.test.ts +52 -0
  56. package/src/twins/SyncIo.test.ts +153 -0
  57. package/src/twins/SyncIo.ts +159 -0
  58. package/src/twins/cliIo.ts +8 -1
  59. package/src/twins/index.ts +5 -0
  60. package/src/workflow/discovery.test.ts +6 -5
  61. package/src/workflow/discovery.ts +43 -32
  62. package/src/workflow/fingerprint.test.ts +23 -3
  63. package/src/workflow/fingerprint.ts +9 -11
  64. package/src/workflow/load.test.ts +16 -6
  65. package/src/workflow/load.ts +107 -69
  66. package/src/workflow/pin.test.ts +3 -2
  67. 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 > 3);
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 3 — 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;
@@ -23002,13 +23690,16 @@ const formatCommitMessage = (spec) => {
23002
23690
  ...trailer("Anchor", spec.anchor),
23003
23691
  ...trailer("Branch", spec.branch),
23004
23692
  ...trailer("Version", spec.version),
23005
- `Gtd-Format: 3`
23693
+ `Gtd-Format: 4`
23006
23694
  ];
23007
23695
  return `${formatSubject(spec.actor, spec.to, spec.from)}\n\n${lines.join("\n")}`;
23008
23696
  };
23009
23697
  //#endregion
23010
23698
  //#region src/replay/Replay.ts
23011
- 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
+ };
23012
23703
  const actorOfKind = (kind) => kind === "run" ? "check" : kind === "agent" ? "agent" : kind === "human" ? "human" : "judge";
23013
23704
  const memoryScopeOf = (name) => {
23014
23705
  const dot = name.lastIndexOf(".");
@@ -23213,7 +23904,7 @@ const replay = async (raw) => {
23213
23904
  if (!isAnchor(commit)) continue;
23214
23905
  position = commit;
23215
23906
  const to = commit.parsed.parsed?.to;
23216
- expectedNext = to === void 0 || to === UNPIN_STATE ? void 0 : {
23907
+ expectedNext = to === void 0 || namesUnpin(commit) ? void 0 : {
23217
23908
  name: to,
23218
23909
  hash: commit.hash
23219
23910
  };
@@ -23407,7 +24098,7 @@ const replay = async (raw) => {
23407
24098
  };
23408
24099
  const expectNextOf = (commit) => {
23409
24100
  const to = commit.parsed.parsed?.to;
23410
- if (to !== void 0 && to !== UNPIN_STATE) expectedNext = {
24101
+ if (to !== void 0 && !namesUnpin(commit)) expectedNext = {
23411
24102
  name: to,
23412
24103
  hash: commit.hash
23413
24104
  };
@@ -23453,7 +24144,7 @@ const replay = async (raw) => {
23453
24144
  return landPending(step, input.pending);
23454
24145
  };
23455
24146
  const requestFault = (request) => {
23456
- 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`;
23457
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)`;
23458
24149
  if (request.kind !== "agent") return void 0;
23459
24150
  const name = scoped(request.name);
@@ -23715,17 +24406,17 @@ const LEGACY_RC_FILES = [
23715
24406
  "gtd.config.yaml"
23716
24407
  ];
23717
24408
  /** The directories searched for a leftover rc file: the walk, plus `home` when the walk misses it. */
23718
- const legacyRcDirs = (root, home) => {
23719
- const chain = walkUp(root, home);
24409
+ const legacyRcDirs = (path, root, home) => {
24410
+ const chain = walkUp(path, root, home);
23720
24411
  return chain.includes(home) ? chain : [...chain, home];
23721
24412
  };
23722
- const legacyRcFileAt = (path) => {
24413
+ const legacyRcFileAt = (io, path, uid) => {
23723
24414
  try {
23724
- const stat = statSync(path);
23725
- if (!stat.isFile()) return void 0;
24415
+ const stat = io.stat(path);
24416
+ if (stat?.type !== "file") return void 0;
23726
24417
  return {
23727
24418
  path,
23728
- owned: process.getuid === void 0 || stat.uid === process.getuid()
24419
+ owned: uid === void 0 || stat.uid === uid
23729
24420
  };
23730
24421
  } catch {
23731
24422
  return;
@@ -23739,30 +24430,33 @@ const legacyRcFileAt = (path) => {
23739
24430
  * shared implementation, not a second copy in `src/twins/Layers.ts` that
23740
24431
  * could silently diverge.
23741
24432
  */
23742
- const walkUp = (from, home) => {
24433
+ const walkUp = (path, from, home) => {
23743
24434
  const chain = [];
23744
24435
  let dir = from;
23745
24436
  while (true) {
23746
24437
  chain.push(dir);
23747
24438
  if (dir === home) break;
23748
- const parent = dirname(dir);
24439
+ const parent = path.dirname(dir);
23749
24440
  if (parent === dir) break;
23750
24441
  dir = parent;
23751
24442
  }
23752
24443
  return chain;
23753
24444
  };
23754
- const legacyRcFiles = (root, home) => sync(() => legacyRcDirs(root, home).flatMap((dir) => LEGACY_RC_FILES.flatMap((name) => legacyRcFileAt(join(dir, name)) ?? [])));
23755
- const workflowModule = (root, home) => try_({
23756
- try: () => {
23757
- for (const dir of walkUp(root, home)) {
23758
- const filepath = join(dir, WORKFLOW_MODULE);
23759
- if (existsSync(filepath)) return {
23760
- filepath,
23761
- source: readFileSync(filepath, "utf8")
23762
- };
23763
- }
23764
- },
23765
- 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
+ })
23766
24460
  });
23767
24461
  /**
23768
24462
  * The discovery seam — a `Context.Service`, not part of `WorkflowFiles` (that
@@ -23775,31 +24469,29 @@ const workflowModule = (root, home) => try_({
23775
24469
  * each have a production and a fake layer behind one service.
23776
24470
  */
23777
24471
  var ConfigDiscovery = class ConfigDiscovery extends Service()("ConfigDiscovery") {
23778
- static Live = succeed$2(ConfigDiscovery, {
23779
- legacyRcFiles,
23780
- workflowModule
23781
- });
24472
+ static Live = effect(ConfigDiscovery, gen(function* () {
24473
+ return liveDiscovery(nodeSyncIo, yield* Path$1, (yield* Host).uid);
24474
+ }));
23782
24475
  };
23783
24476
  //#endregion
23784
24477
  //#region src/workflow/fingerprint.ts
23785
24478
  const HASH_LENGTH = 16;
23786
24479
  const CONTROL_CHARACTER = /[\x00-\x1f\x7f-\x9f]/;
23787
- const contentHash = (content) => createHash("sha256").update(content).digest("hex").slice(0, HASH_LENGTH);
23788
- const realOr = (path) => {
24480
+ const realOr = (io, path) => {
23789
24481
  try {
23790
- return realpathSync(path);
24482
+ return io.realPath(path);
23791
24483
  } catch {
23792
24484
  return path;
23793
24485
  }
23794
24486
  };
23795
- const configFingerprint = (files, root, read) => {
24487
+ const configFingerprint = (io, paths, files, root, read) => {
23796
24488
  const fingerprint = {};
23797
- const realRoot = realOr(root);
24489
+ const realRoot = realOr(io, root);
23798
24490
  for (const file of files) {
23799
- const path = relative(realRoot, realOr(file)).split(sep).join("/");
24491
+ const path = paths.relative(realRoot, realOr(io, file)).split(paths.sep).join("/");
23800
24492
  if (CONTROL_CHARACTER.test(path)) continue;
23801
24493
  const content = read(file);
23802
- if (content !== void 0) fingerprint[path] = contentHash(content);
24494
+ if (content !== void 0) fingerprint[path] = io.hash("sha256", content).slice(0, HASH_LENGTH);
23803
24495
  }
23804
24496
  return fingerprint;
23805
24497
  };
@@ -23893,19 +24585,20 @@ const configModule = gen(function* () {
23893
24585
  */
23894
24586
  const load = gen(function* () {
23895
24587
  const host = yield* Host;
24588
+ const paths = yield* Path$1;
23896
24589
  yield* refuseLegacyRc;
23897
24590
  const pinned = pinnedWorkflow();
23898
24591
  const module = pinned === void 0 ? yield* configModule : {
23899
- filepath: resolve(host.root, pinned.origin),
24592
+ filepath: paths.resolve(host.root, pinned.origin),
23900
24593
  source: ""
23901
24594
  };
23902
24595
  yield* (yield* Narrator).narrate(`config: ${pinned === void 0 ? "module" : "pinned module"} ${module.filepath}`);
23903
24596
  const shipped = pinned === void 0 ? yield* tryPromise({
23904
- try: shippedModules,
24597
+ try: () => shippedModules(paths),
23905
24598
  catch: (e) => new GtdError(`gtd: ${e instanceof Error ? e.message : String(e)}`)
23906
24599
  }) : void 0;
23907
24600
  const loaded = yield* tryPromise({
23908
- 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)),
23909
24602
  catch: (e) => new GtdError(`gtd: config:\n - ${module.filepath}: ${e instanceof Error ? e.message : String(e)}`)
23910
24603
  });
23911
24604
  const compiled = loaded.config;
@@ -23927,13 +24620,13 @@ const load = gen(function* () {
23927
24620
  initial
23928
24621
  });
23929
24622
  const names = [...loaded.catalogue.keys()].sort();
23930
- const resolve$3 = (name, entry) => ({
24623
+ const resolve = (name, entry) => ({
23931
24624
  name,
23932
24625
  def: define(entry),
23933
24626
  vars: { ...entry.defaults },
23934
24627
  env: { ...entry.envDefaults }
23935
24628
  });
23936
- const defaultWorkflow = resolve$3(loaded.default.name, loaded.default);
24629
+ const defaultWorkflow = resolve(loaded.default.name, loaded.default);
23937
24630
  return {
23938
24631
  workflow: defaultWorkflow.def,
23939
24632
  defaultWorkflow,
@@ -23941,41 +24634,41 @@ const load = gen(function* () {
23941
24634
  doors: loaded.doors,
23942
24635
  workflowNamed: (name) => {
23943
24636
  const entry = loaded.catalogue.get(name);
23944
- return entry === void 0 ? void 0 : resolve$3(name, entry);
24637
+ return entry === void 0 ? void 0 : resolve(name, entry);
23945
24638
  },
23946
24639
  ...compiled.ui !== void 0 ? { ui: compiled.ui } : {},
23947
24640
  warnings: diagnostics.filter((d) => d.severity === "warning"),
23948
- fingerprint: pinned?.fingerprint ?? configFingerprint(loaded.file.dependencyFiles, host.root, module.read ?? readFromDisk),
23949
- 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))
23950
24643
  };
23951
24644
  });
23952
24645
  const readFromDisk = (path) => {
23953
24646
  try {
23954
- return readFileSync(path, "utf8");
24647
+ return nodeSyncIo.readText(path);
23955
24648
  } catch {
23956
24649
  return;
23957
24650
  }
23958
24651
  };
23959
24652
  const PACKAGE = "@pmelab/gtd";
23960
24653
  const FLOWS = `${PACKAGE}/flows`;
23961
- const ownPackage = () => {
23962
- const start = dirname(fileURLToPath(import.meta.url));
23963
- for (let dir = start;; dir = dirname(dir)) {
23964
- const file = join(dir, "package.json");
23965
- if (existsSync(file)) {
23966
- 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);
23967
24660
  if (pkg.name === PACKAGE) return {
23968
24661
  root: dir,
23969
24662
  pkg
23970
24663
  };
23971
24664
  }
23972
- 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}`);
23973
24666
  }
23974
24667
  };
23975
24668
  let shippedMemo;
23976
- const shippedModules = () => shippedMemo ??= (async () => {
24669
+ const shippedModules = (paths) => shippedMemo ??= (async () => {
23977
24670
  const fromSource = import.meta.url.endsWith(".ts");
23978
- const files = subpathFiles(fromSource ? "source" : "default");
24671
+ const files = subpathFiles(paths, fromSource ? "source" : "default");
23979
24672
  const modules = { [FLOWS]: flows_exports };
23980
24673
  const missing = {};
23981
24674
  if (process.features.require_module && !fromSource) return {
@@ -23983,7 +24676,7 @@ const shippedModules = () => shippedMemo ??= (async () => {
23983
24676
  missing
23984
24677
  };
23985
24678
  for (const [id, path] of files) try {
23986
- modules[id] = await import(pathToFileURL(path).href);
24679
+ modules[id] = await import(fileUrl(paths, path));
23987
24680
  } catch {
23988
24681
  missing[id] = path;
23989
24682
  }
@@ -23993,13 +24686,13 @@ const shippedModules = () => shippedMemo ??= (async () => {
23993
24686
  };
23994
24687
  })();
23995
24688
  /** The file each subpath a config may import serves: all but `flows`, the engine's own, and the testkit's. */
23996
- const subpathFiles = (condition) => {
23997
- const { root, pkg } = ownPackage();
24689
+ const subpathFiles = (paths, condition) => {
24690
+ const { root, pkg } = ownPackage(paths);
23998
24691
  const files = /* @__PURE__ */ new Map();
23999
24692
  for (const [subpath, target] of Object.entries(pkg.exports)) {
24000
24693
  const file = typeof target === "string" ? target : target[condition];
24001
24694
  if (subpath === "./flows" || subpath.startsWith("./testkit") || file === void 0) continue;
24002
- files.set(`${PACKAGE}/${subpath.replace(/^\.\//, "")}`, join(root, file));
24695
+ files.set(`${PACKAGE}/${subpath.replace(/^\.\//, "")}`, paths.join(root, file));
24003
24696
  }
24004
24697
  return files;
24005
24698
  };
@@ -24193,16 +24886,16 @@ const shadowingDoors = (doors, catalogue, origin) => [...doors].filter(([name, d
24193
24886
  path: ["doors", name],
24194
24887
  origin
24195
24888
  }));
24196
- const moduleCandidates = (path) => [
24889
+ const moduleCandidates = (paths, path) => [
24197
24890
  path,
24198
24891
  path.replace(/\.js$/, ".ts"),
24199
24892
  path.replace(/\.mjs$/, ".mts"),
24200
24893
  `${path}.ts`,
24201
- join(path, "index.ts")
24894
+ paths.join(path, "index.ts")
24202
24895
  ];
24203
- const execute = (chunk, filepath, require) => {
24896
+ const execute = (paths, chunk, filepath, require) => {
24204
24897
  const cjs = { exports: {} };
24205
- chunk(cjs.exports, require, cjs, filepath, dirname(filepath));
24898
+ chunk(cjs.exports, require, cjs, filepath, paths.dirname(filepath));
24206
24899
  return cjs.exports;
24207
24900
  };
24208
24901
  /**
@@ -24214,40 +24907,40 @@ const execute = (chunk, filepath, require) => {
24214
24907
  * it, as Node would. `@pmelab/gtd/*` is the running gtd's own copy, and
24215
24908
  * `files` leaves out every package file.
24216
24909
  */
24217
- const evaluate = async (module, shipped) => {
24218
- const { code, files } = await bundleConfig(module, {
24910
+ const evaluate = async (paths, module, shipped) => {
24911
+ const { code, files } = await bundleConfig(paths, module, {
24219
24912
  kind: "evaluate",
24220
24913
  shipped
24221
24914
  });
24222
24915
  const fromConfig = createRequire(module.filepath);
24223
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 });
24224
- 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);
24225
24918
  return {
24226
- exports: execute(chunk, module.filepath, require),
24919
+ exports: execute(paths, chunk, module.filepath, require),
24227
24920
  files
24228
24921
  };
24229
24922
  };
24230
24923
  const pinnedWorkflow = () => globalThis[PIN_KEY];
24231
- const runPinned = (pinned, filepath, root) => ({
24232
- exports: execute(pinned.run, filepath, (id) => id === FLOWS ? flows_exports : createRequire(import.meta.url)(id)),
24233
- 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))
24234
24927
  });
24235
24928
  /**
24236
24929
  * The statement that registers `module`'s workflow for a pin: the config and
24237
24930
  * everything it imports — npm packages and gtd's own subpaths included — in
24238
24931
  * one function, so the engine runs it without a config, a package or rolldown.
24239
24932
  */
24240
- const pinnedWorkflowSource = async (module, root, version) => {
24241
- const gtd = realpathSync(ownPackage().root) + sep;
24242
- 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, {
24243
24936
  kind: "pin",
24244
- shipped: subpathFiles(import.meta.url.endsWith(".ts") ? "source" : "default")
24937
+ shipped: subpathFiles(paths, import.meta.url.endsWith(".ts") ? "source" : "default")
24245
24938
  });
24246
24939
  const own = (file) => file.startsWith(gtd) && !module.filepath.startsWith(gtd);
24247
24940
  const registered = {
24248
24941
  version,
24249
- origin: relative(root, module.filepath),
24250
- 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)
24251
24944
  };
24252
24945
  return `globalThis[Symbol.for(${JSON.stringify(PIN_KEY.description)})] = { ...${JSON.stringify(registered)}, run: function (exports, require, module, __filename, __dirname) {\n${code}\n} };\n`;
24253
24946
  };
@@ -24261,11 +24954,11 @@ const shippedImport = (source, mode) => {
24261
24954
  external: true
24262
24955
  } : void 0;
24263
24956
  };
24264
- const bundleConfig = async (module, mode) => {
24957
+ const bundleConfig = async (paths, module, mode) => {
24265
24958
  const { read } = module;
24266
24959
  const served = /* @__PURE__ */ new Set();
24267
24960
  const fromRead = (path) => {
24268
- const hit = moduleCandidates(path).find((candidate) => read?.(candidate) !== void 0);
24961
+ const hit = moduleCandidates(paths, path).find((candidate) => read?.(candidate) !== void 0);
24269
24962
  if (hit === void 0) return null;
24270
24963
  served.add(hit);
24271
24964
  return hit;
@@ -24279,18 +24972,18 @@ const bundleConfig = async (module, mode) => {
24279
24972
  if (importer === void 0) return source === module.filepath ? source : null;
24280
24973
  const shipped = shippedImport(source, mode);
24281
24974
  if (shipped !== void 0) return shipped;
24282
- if (/^\.\.?(\/|$)/.test(source)) return fromRead(join(dirname(importer), source));
24283
- if (!isPackage(source)) return null;
24975
+ if (/^\.\.?(\/|$)/.test(source)) return fromRead(paths.join(paths.dirname(importer), source));
24976
+ if (!isPackage(paths, source)) return null;
24284
24977
  const resolved = await this.resolve(source, importer, { skipSelf: true });
24285
- return mode.kind === "pin" ? resolved : packageImport(source, resolved);
24978
+ return mode.kind === "pin" ? resolved : packageImport(paths, source, resolved);
24286
24979
  },
24287
24980
  load(id) {
24288
- 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));
24289
24982
  if (id === module.filepath) return module.source;
24290
24983
  return served.has(id) ? read(id) : null;
24291
24984
  },
24292
24985
  transform(code, id) {
24293
- 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)));
24294
24987
  }
24295
24988
  }],
24296
24989
  external: (id) => isBuiltin(id),
@@ -24320,34 +25013,42 @@ const bundleConfig = async (module, mode) => {
24320
25013
  await bundle.close();
24321
25014
  }
24322
25015
  };
24323
- const isNativeAddon = (id) => {
25016
+ const isNativeAddon = (paths, id) => {
24324
25017
  if (id.endsWith(".node")) return true;
25018
+ const { sep } = paths;
24325
25019
  const at = id.lastIndexOf(`node_modules${sep}`);
24326
25020
  if (at < 0) return false;
24327
25021
  const rest = id.slice(at + `node_modules${sep}`.length).split(sep);
24328
- 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
+ }
24329
25028
  };
24330
- const nativeAddon = (path) => {
25029
+ const nativeAddon = (paths, path) => {
25030
+ const { sep } = paths;
24331
25031
  const at = path.lastIndexOf(`node_modules${sep}`);
24332
25032
  const rest = at < 0 ? [] : path.slice(at + `node_modules${sep}`.length).split(sep);
24333
25033
  const name = rest[0]?.startsWith("@") ? rest.slice(0, 2).join("/") : rest[0];
24334
25034
  return `${name === void 0 ? path : `package "${name}"`} loads a native addon (${path}), which a pin cannot carry`;
24335
25035
  };
24336
- 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 : {
24337
25037
  id: resolved?.id ?? source,
24338
25038
  external: true
24339
25039
  };
24340
- const isEsModule = (path) => {
25040
+ const isEsModule = (paths, path) => {
24341
25041
  if (/\.(mjs|[cm]?tsx?)$/.test(path)) return true;
24342
25042
  if (!/\.jsx?$/.test(path)) return false;
24343
- for (let dir = dirname(path); dirname(dir) !== dir; dir = dirname(dir)) {
24344
- const file = join(dir, "package.json");
24345
- 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";
24346
25046
  }
24347
25047
  return false;
24348
25048
  };
24349
25049
  const inPackage = (path) => /[\\/]node_modules[\\/]/.test(path);
24350
- 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;
24351
25052
  const bundleError = (e) => {
24352
25053
  const errors = e?.errors;
24353
25054
  if (errors === void 0) return e;
@@ -24604,11 +25305,15 @@ var EngineCore = class EngineCore extends Service()("EngineCore") {
24604
25305
  static layer = (path) => succeed$2(EngineCore, { source: suspend$2(() => {
24605
25306
  const file = path();
24606
25307
  return try_({
24607
- 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
+ },
24608
25313
  catch: () => new GtdError(`gtd bundle: no engine at ${file} — build gtd first`)
24609
25314
  });
24610
25315
  }) });
24611
- 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"))));
24612
25317
  };
24613
25318
  /**
24614
25319
  * A pin: one plain-text header line a launcher reads without unpacking, then
@@ -24617,11 +25322,12 @@ var EngineCore = class EngineCore extends Service()("EngineCore") {
24617
25322
  */
24618
25323
  const pin = (module, root, version) => gen(function* () {
24619
25324
  const core = yield* (yield* EngineCore).source;
25325
+ const path = yield* Path$1;
24620
25326
  const source = (yield* tryPromise({
24621
- try: () => pinnedWorkflowSource(module, root, version),
25327
+ try: () => pinnedWorkflowSource(path, module, root, version),
24622
25328
  catch: (e) => new GtdError(`gtd bundle: ${module.filepath}: ${e instanceof Error ? e.message : String(e)}`)
24623
25329
  })) + core.replace(/^#!.*\n/, "");
24624
- 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)]);
24625
25331
  });
24626
25332
  //#endregion
24627
25333
  //#region src/emit/GitScript.ts
@@ -25076,7 +25782,7 @@ const modeCommandSteps = (resolved, file) => {
25076
25782
  return steps;
25077
25783
  };
25078
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). */
25079
- 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`);
25080
25786
  /**
25081
25787
  * The contradiction round-trip/skip-notice steps for `resolved`'s `mode:`.
25082
25788
  * No `formatCommand` means nothing to round-trip: empty. Otherwise: a live
@@ -25089,8 +25795,7 @@ const modeContradictionSteps = (resolved) => gen(function* () {
25089
25795
  if (resolved.formatCommand === void 0) return [];
25090
25796
  const { capabilities } = resolved;
25091
25797
  if (capabilities.format !== void 0 && capabilities.liveValidate !== void 0) {
25092
- const { scratchDir } = yield* Host;
25093
- const samplePath = scratchSamplePath(scratchDir, resolved.mode);
25798
+ const samplePath = scratchSamplePath(yield* Path$1, yield* Host, resolved.mode);
25094
25799
  const formatCommand = withFileVar(resolved.formatCommand, samplePath);
25095
25800
  return [{
25096
25801
  kind: "command",
@@ -25490,7 +26195,7 @@ const subjectIsBoundary = (def, subject) => {
25490
26195
  const parsed = parseCommitMessage(subject).parsed;
25491
26196
  if (parsed === void 0 || !ACTORS.has(parsed.actor)) return true;
25492
26197
  if (parsed.actor === "human" && parsed.from === void 0) return false;
25493
- 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;
25494
26199
  return parsed.to === def.initial;
25495
26200
  };
25496
26201
  /** The last page index (walking newest→oldest, same direction as `locateEpisode`) whose subject is a boundary, or `undefined` if none in `page`. */
@@ -26249,15 +26954,16 @@ const summaryFor = (run) => gen(function* () {
26249
26954
  * repository cannot be read at a ref, so it is left out. A file only the old
26250
26955
  * config read needs no check: whichever file imported it differs too.
26251
26956
  */
26252
- const inRepoConfigPaths = (root, files) => {
26253
- const paths = /* @__PURE__ */ new Set();
26254
- for (const file of files) paths.add(relative(root, resolve(root, file)).split(sep).join("/"));
26255
- 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();
26256
26961
  };
26257
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}`);
26258
26963
  const differingConfigPaths = (git, workspace, root, config, hash) => gen(function* () {
26259
26964
  const differing = [];
26260
- 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);
26261
26967
  return differing;
26262
26968
  });
26263
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");
@@ -27037,12 +27743,11 @@ const runHistoryJudgesCommand = (refs, vars, jsonl, json, out) => gen(function*
27037
27743
  });
27038
27744
  //#endregion
27039
27745
  //#region src/cli/Program.ts
27040
- const selfGtd = () => {
27041
- const script = process.argv[1];
27042
- return script === void 0 ? ["gtd"] : [process.execPath, script];
27043
- };
27044
27746
  /** `needs: "none"` skips the repo-root guard — the server is keyed on file name, not workflow state. */
27045
- 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
+ });
27046
27751
  /**
27047
27752
  * Prints the briefing, emitted by the binary itself so it's always current.
27048
27753
  * gtd ships no workflow, so where no `gtd.config.ts` governs it says where to
@@ -27150,19 +27855,6 @@ const parseJudgeDocument = (rendered) => try_({
27150
27855
  try: () => JSON.parse(rendered),
27151
27856
  catch: () => /* @__PURE__ */ new Error("gtd judge answer: the resolved rest's \"judge:\" template did not render valid JSON")
27152
27857
  });
27153
- /**
27154
- * The first stdin-consuming command outside `src/lsp/Lsp.ts` (which reads LSP's
27155
- * own length-prefixed frames off `process.stdin`, not a one-shot read) —
27156
- * reads stdin to completion and decodes it as UTF-8 text, no framing.
27157
- */
27158
- const readStdin = () => tryPromise({
27159
- try: async () => {
27160
- const chunks = [];
27161
- for await (const chunk of process.stdin) chunks.push(chunk);
27162
- return Buffer.concat(chunks).toString("utf8");
27163
- },
27164
- catch: (e) => e instanceof Error ? e : new Error(String(e))
27165
- });
27166
27858
  /** A stdin that is no judgment is the caller's mistake, a usage error like `gtd judge answer`'s. */
27167
27859
  const judgmentShape = (input) => {
27168
27860
  let doc;
@@ -27179,7 +27871,7 @@ const runJudgeRunCommand = (command, out) => gen(function* () {
27179
27871
  const host = yield* Host;
27180
27872
  const tools = yield* ToolCommands;
27181
27873
  yield* refuseLegacyRc;
27182
- const input = yield* readStdin();
27874
+ const input = yield* host.stdin;
27183
27875
  yield* judgmentShape(input);
27184
27876
  const verdict = yield* tryPromise({
27185
27877
  try: () => tools.judge({
@@ -27227,7 +27919,7 @@ const runJudgeAnswerCommand = (json, out) => gen(function* () {
27227
27919
  const rendered = yield* renderRest(rest);
27228
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:"`));
27229
27921
  const ids = (yield* parseJudgeDocument(rendered.judge)).questions.map((q) => q.id);
27230
- const raw = yield* readStdin();
27922
+ const raw = yield* (yield* Host).stdin;
27231
27923
  const parsed = yield* try_({
27232
27924
  try: () => JSON.parse(raw),
27233
27925
  catch: () => new SelectorUsageError("gtd judge answer: stdin is not valid JSON")
@@ -29413,6 +30105,7 @@ const toError = (e) => e instanceof Error ? e : new Error(String(e));
29413
30105
  * one). Mirrors the guard the deleted `makeInMemoryFileSystem` carried.
29414
30106
  */
29415
30107
  const isGitDirKey = (key) => key.startsWith(".git/");
30108
+ const paths = runSync$1(provide(Path$1, layer$4));
29416
30109
  /**
29417
30110
  * `Workspace` for the in-memory tier. `readSync`/`read`/`write`/`committed`
29418
30111
  * take a repo-relative key ONLY (mirroring the live adapter's
@@ -29428,12 +30121,12 @@ const isGitDirKey = (key) => key.startsWith(".git/");
29428
30121
  */
29429
30122
  const makeInMemoryWorkspaceOps = (repo, root) => {
29430
30123
  const toKey = (path) => {
29431
- if (!isAbsolute(path)) return path;
30124
+ if (!paths.isAbsolute(path)) return path;
29432
30125
  const prefix = `${root}/`;
29433
30126
  return path === root ? "" : path.startsWith(prefix) ? path.slice(prefix.length) : path;
29434
30127
  };
29435
30128
  const assertRepoRelative = (path) => {
29436
- 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)`);
29437
30130
  return path;
29438
30131
  };
29439
30132
  const readAt = (key) => isGitDirKey(key) ? void 0 : repo.readFile(key);
@@ -29480,7 +30173,7 @@ const makeInMemoryConfigDiscovery = (repo, root) => {
29480
30173
  const workspace = makeInMemoryWorkspaceOps(repo, root);
29481
30174
  return succeed$2(ConfigDiscovery, {
29482
30175
  legacyRcFiles: (levelRoot, levelHome) => try_({
29483
- 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) => ({
29484
30177
  path,
29485
30178
  owned: true
29486
30179
  }))),
@@ -29488,8 +30181,8 @@ const makeInMemoryConfigDiscovery = (repo, root) => {
29488
30181
  }),
29489
30182
  workflowModule: (levelRoot, levelHome) => try_({
29490
30183
  try: () => {
29491
- for (const dir of walkUp(levelRoot, levelHome)) {
29492
- const filepath = join(dir, WORKFLOW_MODULE);
30184
+ for (const dir of walkUp(paths, levelRoot, levelHome)) {
30185
+ const filepath = paths.join(dir, WORKFLOW_MODULE);
29493
30186
  const source = workspace.atPath(filepath);
29494
30187
  if (source !== void 0) return {
29495
30188
  filepath,
@@ -29521,26 +30214,41 @@ const gitTestLayer = (repo, root = "/repo") => succeed$2(GitService, fakeGitOper
29521
30214
  * itself; explicit env wins.
29522
30215
  */
29523
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
+ });
29524
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. */
29525
30238
  function testLayers(repo, opts = {}) {
29526
30239
  const root = opts.root ?? "/repo";
29527
30240
  const home = opts.home ?? root;
29528
- return mergeAll(gitTestLayer(repo, root), succeed$2(Workspace, makeInMemoryWorkspaceOps(repo, root)), Host.layer({
29529
- root,
29530
- home,
29531
- env: {
29532
- ...ambientFlagEnv(),
29533
- ...opts.env
29534
- },
29535
- realPath: () => succeed$1(root)
29536
- }), 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, {
29537
30245
  ...missingToolCommands,
29538
30246
  ...opts.tools
29539
30247
  }), CommandRunner.layer(() => fail$4(/* @__PURE__ */ new Error("gtd ui: CommandRunner has no test double wired into testLayers()"))));
29540
30248
  }
29541
30249
  //#endregion
29542
30250
  //#region src/twins/cliIo.ts
29543
- const makeCapturingCliIo = (repo, env = {}, tools = {}) => {
30251
+ const makeCapturingCliIo = (repo, env = {}, tools = {}, stdin = "") => {
29544
30252
  let stdout = Buffer.alloc(0);
29545
30253
  let stderr = "";
29546
30254
  let exitCode = 0;
@@ -29559,7 +30267,8 @@ const makeCapturingCliIo = (repo, env = {}, tools = {}) => {
29559
30267
  env,
29560
30268
  narrate: (line) => stderr += line,
29561
30269
  verbose,
29562
- tools
30270
+ tools,
30271
+ stdin
29563
30272
  })
29564
30273
  },
29565
30274
  result: () => ({
@@ -30495,6 +31204,8 @@ const unrecognized = (block, output = "") => ({
30495
31204
  error: `${UNRECOGNIZED_PREFIX}${block.split("\n")[0]}`
30496
31205
  });
30497
31206
  Narrator.layer(() => {}, false);
31207
+ new TextEncoder();
31208
+ new TextDecoder("utf-8", { ignoreBOM: true });
30498
31209
  //#endregion
30499
31210
  //#region src/testkit/imports.ts
30500
31211
  const IDENTIFIER$1 = /^[A-Za-z_$][\w$]*$/;
@@ -30720,8 +31431,8 @@ const testRepo = (options) => {
30720
31431
  ...Object.fromEntries(Object.keys(process.env).filter((name) => name.startsWith("GTD_FLAG_")).map((name) => [name, void 0])),
30721
31432
  ...env
30722
31433
  } : env;
30723
- const gtd = async (...args) => {
30724
- const { io, result } = makeCapturingCliIo(repo, seenEnv());
31434
+ const gtdWithStdin = async (stdin, ...args) => {
31435
+ const { io, result } = makeCapturingCliIo(repo, seenEnv(), {}, stdin);
30725
31436
  if (typeof workflow !== "string") registry().set(id, workflow);
30726
31437
  for (const [specifier, module] of modules) registry().set(moduleKey(specifier), module);
30727
31438
  try {
@@ -30736,20 +31447,7 @@ const testRepo = (options) => {
30736
31447
  }
30737
31448
  return result();
30738
31449
  };
30739
- const withStdin = async (stdin, fn) => {
30740
- const saved = Object.getOwnPropertyDescriptor(process, "stdin");
30741
- const fake = new PassThrough();
30742
- Object.defineProperty(process, "stdin", {
30743
- value: fake,
30744
- configurable: true
30745
- });
30746
- fake.end(stdin);
30747
- try {
30748
- return await fn();
30749
- } finally {
30750
- if (saved) Object.defineProperty(process, "stdin", saved);
30751
- }
30752
- };
31450
+ const gtd = (...args) => gtdWithStdin("", ...args);
30753
31451
  const apply = (what, script) => {
30754
31452
  if (script.length === 0) return;
30755
31453
  const applied = applyEmittedScript(repo, commands, script);
@@ -30757,7 +31455,7 @@ const testRepo = (options) => {
30757
31455
  };
30758
31456
  /** Runs a write command, then the script its `--json=script` twin prints. */
30759
31457
  const runWrite = async (args, stdin, refused = (error) => error) => {
30760
- const call = (...extra) => stdin === void 0 ? gtd(...args, ...extra) : withStdin(stdin, () => gtd(...args, ...extra));
31458
+ const call = (...extra) => gtdWithStdin(stdin ?? "", ...args, ...extra);
30761
31459
  const reported = await call();
30762
31460
  if (reported.exitCode !== 0) throw refused(failed(`gtd ${args.join(" ")}`, reported));
30763
31461
  const script = await call("--json=script");