@pmelab/gtd 63.0.0 → 64.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +12 -11
  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-Cem5i6Xl.mjs} +422 -2
  6. package/dist/chunks/{testkit-DwypA7cH.mjs → testkit-DfWZQCWQ.mjs} +1073 -328
  7. package/dist/flows.mjs +1 -1
  8. package/dist/gtd.bundle.mjs +4372 -1310
  9. package/dist/gtd.core.mjs +126 -118
  10. package/dist/steering.mjs +2 -2
  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 +22 -28
  17. package/src/cli/Output.ts +2 -1
  18. package/src/cli/Program.test.ts +55 -53
  19. package/src/cli/Program.ts +34 -34
  20. package/src/cli/ToolCommands.ts +3 -4
  21. package/src/edge/Edge.test.ts +38 -12
  22. package/src/edge/Edge.ts +53 -19
  23. package/src/emit/SteeringMode.test.ts +4 -2
  24. package/src/emit/SteeringMode.ts +31 -10
  25. package/src/flows/index.ts +1 -0
  26. package/src/flows/runtime.test.ts +2 -1
  27. package/src/flows/runtime.ts +8 -2
  28. package/src/gherkin/messages.test.ts +1 -1
  29. package/src/gherkin/module-config.feature +1 -1
  30. package/src/gherkin/testkit.feature +2 -2
  31. package/src/install/Version.ts +14 -13
  32. package/src/platform/Host.ts +75 -22
  33. package/src/platform/NodePinFile.ts +44 -0
  34. package/src/platform/NodeRuntime.ts +19 -0
  35. package/src/platform/NodeSync.ts +64 -0
  36. package/src/platform/PinFile.ts +0 -50
  37. package/src/platform/Sha.ts +121 -0
  38. package/src/platform/SyncIo.ts +35 -0
  39. package/src/platform/Workspace.test.ts +8 -3
  40. package/src/platform/Workspace.ts +82 -123
  41. package/src/platform/WorktreeState.test.ts +8 -4
  42. package/src/platform/WorktreeState.ts +21 -19
  43. package/src/platform/index.ts +18 -5
  44. package/src/replay/Format.test.ts +16 -13
  45. package/src/replay/Format.ts +4 -2
  46. package/src/replay/Replay.test.ts +25 -5
  47. package/src/replay/Replay.ts +10 -6
  48. package/src/replay/Trailers.test.ts +10 -10
  49. package/src/replay/Trailers.ts +23 -1
  50. package/src/replay/index.ts +1 -0
  51. package/src/steering/Declared.test.ts +218 -0
  52. package/src/steering/Declared.ts +607 -0
  53. package/src/steering/FORMATS.md +3 -0
  54. package/src/steering/index.ts +12 -0
  55. package/src/steering/internal.ts +1 -0
  56. package/src/step/planStart.test.ts +2 -2
  57. package/src/step/planStep.test.ts +4 -4
  58. package/src/testkit/TestRepo.test.ts +1 -1
  59. package/src/testkit/TestRepo.ts +4 -18
  60. package/src/twins/EmittedScriptRecognizer.ts +5 -3
  61. package/src/twins/EmittedScripts.test.ts +19 -0
  62. package/src/twins/Host.test.ts +151 -0
  63. package/src/twins/Layers.test.ts +2 -2
  64. package/src/twins/Layers.ts +44 -18
  65. package/src/twins/Path.test.ts +52 -0
  66. package/src/twins/SyncIo.test.ts +153 -0
  67. package/src/twins/SyncIo.ts +159 -0
  68. package/src/twins/cliIo.ts +8 -1
  69. package/src/twins/index.ts +5 -0
  70. package/src/workflow/SteeringFormats.ts +4 -0
  71. package/src/workflow/Workflow.ts +2 -0
  72. package/src/workflow/discovery.test.ts +6 -5
  73. package/src/workflow/discovery.ts +43 -32
  74. package/src/workflow/fingerprint.test.ts +23 -3
  75. package/src/workflow/fingerprint.ts +9 -11
  76. package/src/workflow/index.ts +5 -1
  77. package/src/workflow/load.test.ts +16 -6
  78. package/src/workflow/load.ts +107 -69
  79. package/src/workflow/pin.test.ts +3 -2
  80. 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 { F as headingSectionBodies, I as formatFinding, a as sections, c as openThreads, d as formatFromSpec, 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-Cem5i6Xl.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);
@@ -23598,6 +24289,8 @@ const replay = async (raw) => {
23598
24289
  const seededValidateCommand = (mode) => `gtd check ${mode} "$GTD_FILE"`;
23599
24290
  /** True when `command` is exactly `mode`'s seeded template (compared literally, not rendered) — distinguishes gtd's own seeding from a user override. */
23600
24291
  const isSeededValidateCommand = (mode, command) => command === seededValidateCommand(mode);
24292
+ /** The `validate:` command for a declared format: its spec travels on the command line, so `gtd check` loads no workflow to find it. */
24293
+ const declaredValidateCommand = (mode, spec) => `gtd check ${mode} "$GTD_FILE" --spec '${spec.replace(/'/g, "'\\''")}'`;
23601
24294
  //#endregion
23602
24295
  //#region src/workflow/compile.ts
23603
24296
  /**
@@ -23715,17 +24408,17 @@ const LEGACY_RC_FILES = [
23715
24408
  "gtd.config.yaml"
23716
24409
  ];
23717
24410
  /** 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);
24411
+ const legacyRcDirs = (path, root, home) => {
24412
+ const chain = walkUp(path, root, home);
23720
24413
  return chain.includes(home) ? chain : [...chain, home];
23721
24414
  };
23722
- const legacyRcFileAt = (path) => {
24415
+ const legacyRcFileAt = (io, path, uid) => {
23723
24416
  try {
23724
- const stat = statSync(path);
23725
- if (!stat.isFile()) return void 0;
24417
+ const stat = io.stat(path);
24418
+ if (stat?.type !== "file") return void 0;
23726
24419
  return {
23727
24420
  path,
23728
- owned: process.getuid === void 0 || stat.uid === process.getuid()
24421
+ owned: uid === void 0 || stat.uid === uid
23729
24422
  };
23730
24423
  } catch {
23731
24424
  return;
@@ -23739,30 +24432,33 @@ const legacyRcFileAt = (path) => {
23739
24432
  * shared implementation, not a second copy in `src/twins/Layers.ts` that
23740
24433
  * could silently diverge.
23741
24434
  */
23742
- const walkUp = (from, home) => {
24435
+ const walkUp = (path, from, home) => {
23743
24436
  const chain = [];
23744
24437
  let dir = from;
23745
24438
  while (true) {
23746
24439
  chain.push(dir);
23747
24440
  if (dir === home) break;
23748
- const parent = dirname(dir);
24441
+ const parent = path.dirname(dir);
23749
24442
  if (parent === dir) break;
23750
24443
  dir = parent;
23751
24444
  }
23752
24445
  return chain;
23753
24446
  };
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))
24447
+ const liveDiscovery = (io, path, uid) => ({
24448
+ legacyRcFiles: (root, home) => sync(() => legacyRcDirs(path, root, home).flatMap((dir) => LEGACY_RC_FILES.flatMap((name) => legacyRcFileAt(io, path.join(dir, name), uid) ?? []))),
24449
+ workflowModule: (root, home) => try_({
24450
+ try: () => {
24451
+ for (const dir of walkUp(path, root, home)) {
24452
+ const filepath = path.join(dir, WORKFLOW_MODULE);
24453
+ const source = io.readText(filepath);
24454
+ if (source !== void 0) return {
24455
+ filepath,
24456
+ source
24457
+ };
24458
+ }
24459
+ },
24460
+ catch: (e) => e instanceof Error ? e : new Error(String(e))
24461
+ })
23766
24462
  });
23767
24463
  /**
23768
24464
  * The discovery seam — a `Context.Service`, not part of `WorkflowFiles` (that
@@ -23775,31 +24471,29 @@ const workflowModule = (root, home) => try_({
23775
24471
  * each have a production and a fake layer behind one service.
23776
24472
  */
23777
24473
  var ConfigDiscovery = class ConfigDiscovery extends Service()("ConfigDiscovery") {
23778
- static Live = succeed$2(ConfigDiscovery, {
23779
- legacyRcFiles,
23780
- workflowModule
23781
- });
24474
+ static Live = effect(ConfigDiscovery, gen(function* () {
24475
+ return liveDiscovery(nodeSyncIo, yield* Path$1, (yield* Host).uid);
24476
+ }));
23782
24477
  };
23783
24478
  //#endregion
23784
24479
  //#region src/workflow/fingerprint.ts
23785
24480
  const HASH_LENGTH = 16;
23786
24481
  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) => {
24482
+ const realOr = (io, path) => {
23789
24483
  try {
23790
- return realpathSync(path);
24484
+ return io.realPath(path);
23791
24485
  } catch {
23792
24486
  return path;
23793
24487
  }
23794
24488
  };
23795
- const configFingerprint = (files, root, read) => {
24489
+ const configFingerprint = (io, paths, files, root, read) => {
23796
24490
  const fingerprint = {};
23797
- const realRoot = realOr(root);
24491
+ const realRoot = realOr(io, root);
23798
24492
  for (const file of files) {
23799
- const path = relative(realRoot, realOr(file)).split(sep).join("/");
24493
+ const path = paths.relative(realRoot, realOr(io, file)).split(paths.sep).join("/");
23800
24494
  if (CONTROL_CHARACTER.test(path)) continue;
23801
24495
  const content = read(file);
23802
- if (content !== void 0) fingerprint[path] = contentHash(content);
24496
+ if (content !== void 0) fingerprint[path] = io.hash("sha256", content).slice(0, HASH_LENGTH);
23803
24497
  }
23804
24498
  return fingerprint;
23805
24499
  };
@@ -23893,19 +24587,20 @@ const configModule = gen(function* () {
23893
24587
  */
23894
24588
  const load = gen(function* () {
23895
24589
  const host = yield* Host;
24590
+ const paths = yield* Path$1;
23896
24591
  yield* refuseLegacyRc;
23897
24592
  const pinned = pinnedWorkflow();
23898
24593
  const module = pinned === void 0 ? yield* configModule : {
23899
- filepath: resolve(host.root, pinned.origin),
24594
+ filepath: paths.resolve(host.root, pinned.origin),
23900
24595
  source: ""
23901
24596
  };
23902
24597
  yield* (yield* Narrator).narrate(`config: ${pinned === void 0 ? "module" : "pinned module"} ${module.filepath}`);
23903
24598
  const shipped = pinned === void 0 ? yield* tryPromise({
23904
- try: shippedModules,
24599
+ try: () => shippedModules(paths),
23905
24600
  catch: (e) => new GtdError(`gtd: ${e instanceof Error ? e.message : String(e)}`)
23906
24601
  }) : void 0;
23907
24602
  const loaded = yield* tryPromise({
23908
- try: async () => loadWorkflows(module.filepath, shipped === void 0 ? runPinned(pinned, module.filepath, host.root) : await evaluate(module, shipped)),
24603
+ try: async () => loadWorkflows(module.filepath, shipped === void 0 ? runPinned(paths, pinned, module.filepath, host.root) : await evaluate(paths, module, shipped)),
23909
24604
  catch: (e) => new GtdError(`gtd: config:\n - ${module.filepath}: ${e instanceof Error ? e.message : String(e)}`)
23910
24605
  });
23911
24606
  const compiled = loaded.config;
@@ -23927,13 +24622,13 @@ const load = gen(function* () {
23927
24622
  initial
23928
24623
  });
23929
24624
  const names = [...loaded.catalogue.keys()].sort();
23930
- const resolve$3 = (name, entry) => ({
24625
+ const resolve = (name, entry) => ({
23931
24626
  name,
23932
24627
  def: define(entry),
23933
24628
  vars: { ...entry.defaults },
23934
24629
  env: { ...entry.envDefaults }
23935
24630
  });
23936
- const defaultWorkflow = resolve$3(loaded.default.name, loaded.default);
24631
+ const defaultWorkflow = resolve(loaded.default.name, loaded.default);
23937
24632
  return {
23938
24633
  workflow: defaultWorkflow.def,
23939
24634
  defaultWorkflow,
@@ -23941,41 +24636,41 @@ const load = gen(function* () {
23941
24636
  doors: loaded.doors,
23942
24637
  workflowNamed: (name) => {
23943
24638
  const entry = loaded.catalogue.get(name);
23944
- return entry === void 0 ? void 0 : resolve$3(name, entry);
24639
+ return entry === void 0 ? void 0 : resolve(name, entry);
23945
24640
  },
23946
24641
  ...compiled.ui !== void 0 ? { ui: compiled.ui } : {},
23947
24642
  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))
24643
+ fingerprint: pinned?.fingerprint ?? configFingerprint(nodeSyncIo, paths, loaded.file.dependencyFiles, host.root, module.read ?? readFromDisk),
24644
+ 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
24645
  };
23951
24646
  });
23952
24647
  const readFromDisk = (path) => {
23953
24648
  try {
23954
- return readFileSync(path, "utf8");
24649
+ return nodeSyncIo.readText(path);
23955
24650
  } catch {
23956
24651
  return;
23957
24652
  }
23958
24653
  };
23959
24654
  const PACKAGE = "@pmelab/gtd";
23960
24655
  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"));
24656
+ const ownPackage = (paths) => {
24657
+ const start = paths.dirname(runSync$1(paths.fromFileUrl(new URL(import.meta.url))));
24658
+ for (let dir = start;; dir = paths.dirname(dir)) {
24659
+ const source = readFromDisk(paths.join(dir, "package.json"));
24660
+ if (source !== void 0) {
24661
+ const pkg = JSON.parse(source);
23967
24662
  if (pkg.name === PACKAGE) return {
23968
24663
  root: dir,
23969
24664
  pkg
23970
24665
  };
23971
24666
  }
23972
- if (dirname(dir) === dir) throw new Error(`no ${PACKAGE} package.json above ${start}`);
24667
+ if (paths.dirname(dir) === dir) throw new Error(`no ${PACKAGE} package.json above ${start}`);
23973
24668
  }
23974
24669
  };
23975
24670
  let shippedMemo;
23976
- const shippedModules = () => shippedMemo ??= (async () => {
24671
+ const shippedModules = (paths) => shippedMemo ??= (async () => {
23977
24672
  const fromSource = import.meta.url.endsWith(".ts");
23978
- const files = subpathFiles(fromSource ? "source" : "default");
24673
+ const files = subpathFiles(paths, fromSource ? "source" : "default");
23979
24674
  const modules = { [FLOWS]: flows_exports };
23980
24675
  const missing = {};
23981
24676
  if (process.features.require_module && !fromSource) return {
@@ -23983,7 +24678,7 @@ const shippedModules = () => shippedMemo ??= (async () => {
23983
24678
  missing
23984
24679
  };
23985
24680
  for (const [id, path] of files) try {
23986
- modules[id] = await import(pathToFileURL(path).href);
24681
+ modules[id] = await import(fileUrl(paths, path));
23987
24682
  } catch {
23988
24683
  missing[id] = path;
23989
24684
  }
@@ -23993,13 +24688,13 @@ const shippedModules = () => shippedMemo ??= (async () => {
23993
24688
  };
23994
24689
  })();
23995
24690
  /** 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();
24691
+ const subpathFiles = (paths, condition) => {
24692
+ const { root, pkg } = ownPackage(paths);
23998
24693
  const files = /* @__PURE__ */ new Map();
23999
24694
  for (const [subpath, target] of Object.entries(pkg.exports)) {
24000
24695
  const file = typeof target === "string" ? target : target[condition];
24001
24696
  if (subpath === "./flows" || subpath.startsWith("./testkit") || file === void 0) continue;
24002
- files.set(`${PACKAGE}/${subpath.replace(/^\.\//, "")}`, join(root, file));
24697
+ files.set(`${PACKAGE}/${subpath.replace(/^\.\//, "")}`, paths.join(root, file));
24003
24698
  }
24004
24699
  return files;
24005
24700
  };
@@ -24193,16 +24888,16 @@ const shadowingDoors = (doors, catalogue, origin) => [...doors].filter(([name, d
24193
24888
  path: ["doors", name],
24194
24889
  origin
24195
24890
  }));
24196
- const moduleCandidates = (path) => [
24891
+ const moduleCandidates = (paths, path) => [
24197
24892
  path,
24198
24893
  path.replace(/\.js$/, ".ts"),
24199
24894
  path.replace(/\.mjs$/, ".mts"),
24200
24895
  `${path}.ts`,
24201
- join(path, "index.ts")
24896
+ paths.join(path, "index.ts")
24202
24897
  ];
24203
- const execute = (chunk, filepath, require) => {
24898
+ const execute = (paths, chunk, filepath, require) => {
24204
24899
  const cjs = { exports: {} };
24205
- chunk(cjs.exports, require, cjs, filepath, dirname(filepath));
24900
+ chunk(cjs.exports, require, cjs, filepath, paths.dirname(filepath));
24206
24901
  return cjs.exports;
24207
24902
  };
24208
24903
  /**
@@ -24214,40 +24909,40 @@ const execute = (chunk, filepath, require) => {
24214
24909
  * it, as Node would. `@pmelab/gtd/*` is the running gtd's own copy, and
24215
24910
  * `files` leaves out every package file.
24216
24911
  */
24217
- const evaluate = async (module, shipped) => {
24218
- const { code, files } = await bundleConfig(module, {
24912
+ const evaluate = async (paths, module, shipped) => {
24913
+ const { code, files } = await bundleConfig(paths, module, {
24219
24914
  kind: "evaluate",
24220
24915
  shipped
24221
24916
  });
24222
24917
  const fromConfig = createRequire(module.filepath);
24223
24918
  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 });
24919
+ const chunk = compileScript(`(function (exports, require, module, __filename, __dirname) {${code}\n})`, module.filepath);
24225
24920
  return {
24226
- exports: execute(chunk, module.filepath, require),
24921
+ exports: execute(paths, chunk, module.filepath, require),
24227
24922
  files
24228
24923
  };
24229
24924
  };
24230
24925
  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))
24926
+ const runPinned = (paths, pinned, filepath, root) => ({
24927
+ exports: execute(paths, pinned.run, filepath, (id) => id === FLOWS ? flows_exports : createRequire(import.meta.url)(id)),
24928
+ files: Object.keys(pinned.fingerprint).map((file) => paths.resolve(root, file))
24234
24929
  });
24235
24930
  /**
24236
24931
  * The statement that registers `module`'s workflow for a pin: the config and
24237
24932
  * everything it imports — npm packages and gtd's own subpaths included — in
24238
24933
  * one function, so the engine runs it without a config, a package or rolldown.
24239
24934
  */
24240
- const pinnedWorkflowSource = async (module, root, version) => {
24241
- const gtd = realpathSync(ownPackage().root) + sep;
24242
- const { code, files } = await bundleConfig(module, {
24935
+ const pinnedWorkflowSource = async (paths, module, root, version) => {
24936
+ const gtd = nodeSyncIo.realPath(ownPackage(paths).root) + paths.sep;
24937
+ const { code, files } = await bundleConfig(paths, module, {
24243
24938
  kind: "pin",
24244
- shipped: subpathFiles(import.meta.url.endsWith(".ts") ? "source" : "default")
24939
+ shipped: subpathFiles(paths, import.meta.url.endsWith(".ts") ? "source" : "default")
24245
24940
  });
24246
24941
  const own = (file) => file.startsWith(gtd) && !module.filepath.startsWith(gtd);
24247
24942
  const registered = {
24248
24943
  version,
24249
- origin: relative(root, module.filepath),
24250
- fingerprint: configFingerprint(files.filter((file) => !own(file)), root, module.read ?? readFromDisk)
24944
+ origin: paths.relative(root, module.filepath),
24945
+ fingerprint: configFingerprint(nodeSyncIo, paths, files.filter((file) => !own(file)), root, module.read ?? readFromDisk)
24251
24946
  };
24252
24947
  return `globalThis[Symbol.for(${JSON.stringify(PIN_KEY.description)})] = { ...${JSON.stringify(registered)}, run: function (exports, require, module, __filename, __dirname) {\n${code}\n} };\n`;
24253
24948
  };
@@ -24261,11 +24956,11 @@ const shippedImport = (source, mode) => {
24261
24956
  external: true
24262
24957
  } : void 0;
24263
24958
  };
24264
- const bundleConfig = async (module, mode) => {
24959
+ const bundleConfig = async (paths, module, mode) => {
24265
24960
  const { read } = module;
24266
24961
  const served = /* @__PURE__ */ new Set();
24267
24962
  const fromRead = (path) => {
24268
- const hit = moduleCandidates(path).find((candidate) => read?.(candidate) !== void 0);
24963
+ const hit = moduleCandidates(paths, path).find((candidate) => read?.(candidate) !== void 0);
24269
24964
  if (hit === void 0) return null;
24270
24965
  served.add(hit);
24271
24966
  return hit;
@@ -24279,18 +24974,18 @@ const bundleConfig = async (module, mode) => {
24279
24974
  if (importer === void 0) return source === module.filepath ? source : null;
24280
24975
  const shipped = shippedImport(source, mode);
24281
24976
  if (shipped !== void 0) return shipped;
24282
- if (/^\.\.?(\/|$)/.test(source)) return fromRead(join(dirname(importer), source));
24283
- if (!isPackage(source)) return null;
24977
+ if (/^\.\.?(\/|$)/.test(source)) return fromRead(paths.join(paths.dirname(importer), source));
24978
+ if (!isPackage(paths, source)) return null;
24284
24979
  const resolved = await this.resolve(source, importer, { skipSelf: true });
24285
- return mode.kind === "pin" ? resolved : packageImport(source, resolved);
24980
+ return mode.kind === "pin" ? resolved : packageImport(paths, source, resolved);
24286
24981
  },
24287
24982
  load(id) {
24288
- if (mode.kind === "pin" && isNativeAddon(id)) throw new Error(nativeAddon(id));
24983
+ if (mode.kind === "pin" && isNativeAddon(paths, id)) throw new Error(nativeAddon(paths, id));
24289
24984
  if (id === module.filepath) return module.source;
24290
24985
  return served.has(id) ? read(id) : null;
24291
24986
  },
24292
24987
  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)));
24988
+ 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
24989
  }
24295
24990
  }],
24296
24991
  external: (id) => isBuiltin(id),
@@ -24320,34 +25015,42 @@ const bundleConfig = async (module, mode) => {
24320
25015
  await bundle.close();
24321
25016
  }
24322
25017
  };
24323
- const isNativeAddon = (id) => {
25018
+ const isNativeAddon = (paths, id) => {
24324
25019
  if (id.endsWith(".node")) return true;
25020
+ const { sep } = paths;
24325
25021
  const at = id.lastIndexOf(`node_modules${sep}`);
24326
25022
  if (at < 0) return false;
24327
25023
  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"));
25024
+ const root = id.slice(0, at) + ["node_modules", ...rest.slice(0, rest[0].startsWith("@") ? 2 : 1)].join(sep);
25025
+ try {
25026
+ return nodeSyncIo.stat(paths.join(root, "binding.gyp")) !== void 0;
25027
+ } catch {
25028
+ return false;
25029
+ }
24329
25030
  };
24330
- const nativeAddon = (path) => {
25031
+ const nativeAddon = (paths, path) => {
25032
+ const { sep } = paths;
24331
25033
  const at = path.lastIndexOf(`node_modules${sep}`);
24332
25034
  const rest = at < 0 ? [] : path.slice(at + `node_modules${sep}`.length).split(sep);
24333
25035
  const name = rest[0]?.startsWith("@") ? rest.slice(0, 2).join("/") : rest[0];
24334
25036
  return `${name === void 0 ? path : `package "${name}"`} loads a native addon (${path}), which a pin cannot carry`;
24335
25037
  };
24336
- const packageImport = (source, resolved) => resolved !== null && isEsModule(resolved.id) ? resolved.id : {
25038
+ const packageImport = (paths, source, resolved) => resolved !== null && isEsModule(paths, resolved.id) ? resolved.id : {
24337
25039
  id: resolved?.id ?? source,
24338
25040
  external: true
24339
25041
  };
24340
- const isEsModule = (path) => {
25042
+ const isEsModule = (paths, path) => {
24341
25043
  if (/\.(mjs|[cm]?tsx?)$/.test(path)) return true;
24342
25044
  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";
25045
+ for (let dir = paths.dirname(path); paths.dirname(dir) !== dir; dir = paths.dirname(dir)) {
25046
+ const source = readFromDisk(paths.join(dir, "package.json"));
25047
+ if (source !== void 0) return JSON.parse(source).type === "module";
24346
25048
  }
24347
25049
  return false;
24348
25050
  };
24349
25051
  const inPackage = (path) => /[\\/]node_modules[\\/]/.test(path);
24350
- const isPackage = (specifier) => !isAbsolute(specifier) && !isBuiltin(specifier);
25052
+ const isPackage = (paths, specifier) => !paths.isAbsolute(specifier) && !isBuiltin(specifier);
25053
+ const fileUrl = (paths, path) => runSync$1(paths.toFileUrl(path)).href;
24351
25054
  const bundleError = (e) => {
24352
25055
  const errors = e?.errors;
24353
25056
  if (errors === void 0) return e;
@@ -24604,11 +25307,15 @@ var EngineCore = class EngineCore extends Service()("EngineCore") {
24604
25307
  static layer = (path) => succeed$2(EngineCore, { source: suspend$2(() => {
24605
25308
  const file = path();
24606
25309
  return try_({
24607
- try: () => readFileSync(file, "utf8"),
25310
+ try: () => {
25311
+ const source = nodeSyncIo.readText(file);
25312
+ if (source === void 0) throw new Error("absent");
25313
+ return source;
25314
+ },
24608
25315
  catch: () => new GtdError(`gtd bundle: no engine at ${file} — build gtd first`)
24609
25316
  });
24610
25317
  }) });
24611
- static Live = EngineCore.layer(() => join(ownPackage().root, "dist", "gtd.core.mjs"));
25318
+ static Live = unwrap$6(map(Path$1, (path) => EngineCore.layer(() => path.join(ownPackage(path).root, "dist", "gtd.core.mjs"))));
24612
25319
  };
24613
25320
  /**
24614
25321
  * A pin: one plain-text header line a launcher reads without unpacking, then
@@ -24617,11 +25324,12 @@ var EngineCore = class EngineCore extends Service()("EngineCore") {
24617
25324
  */
24618
25325
  const pin = (module, root, version) => gen(function* () {
24619
25326
  const core = yield* (yield* EngineCore).source;
25327
+ const path = yield* Path$1;
24620
25328
  const source = (yield* tryPromise({
24621
- try: () => pinnedWorkflowSource(module, root, version),
25329
+ try: () => pinnedWorkflowSource(path, module, root, version),
24622
25330
  catch: (e) => new GtdError(`gtd bundle: ${module.filepath}: ${e instanceof Error ? e.message : String(e)}`)
24623
25331
  })) + core.replace(/^#!.*\n/, "");
24624
- return Buffer.concat([Buffer.from(`gtdpin 1 gtd ${version}\n`), gzipSync(source, { level: 9 })]);
25332
+ return Buffer.concat([Buffer.from(`gtdpin 1 gtd ${version}\n`), gzip(source)]);
24625
25333
  });
24626
25334
  //#endregion
24627
25335
  //#region src/emit/GitScript.ts
@@ -25017,8 +25725,27 @@ const capabilitiesFor = (mode, format, validate) => {
25017
25725
  * The `"unknown"` arm carries the whole refusal message, naming what IS known
25018
25726
  * when `def` is given — this is the one place that message is built, so it
25019
25727
  * can never drift from what actually resolved.
25728
+ *
25729
+ * `spec` is a declared format's: it alone decides, validating through
25730
+ * `gtd check --spec` with no `format:` half.
25020
25731
  */
25021
- const resolveMode = (def, state, mode) => {
25732
+ const resolveMode = (def, state, mode, spec) => {
25733
+ if (spec !== void 0) {
25734
+ const format = formatFromSpec(spec);
25735
+ return {
25736
+ kind: "resolved",
25737
+ mode,
25738
+ format,
25739
+ validate: {
25740
+ kind: "command",
25741
+ command: declaredValidateCommand(mode, spec)
25742
+ },
25743
+ capabilities: {
25744
+ format,
25745
+ liveValidate: format.validate
25746
+ }
25747
+ };
25748
+ }
25022
25749
  const declared = def?.modes?.[mode];
25023
25750
  const format = steeringFormatFor(mode);
25024
25751
  if (declared === void 0 && format === void 0) return {
@@ -25076,7 +25803,7 @@ const modeCommandSteps = (resolved, file) => {
25076
25803
  return steps;
25077
25804
  };
25078
25805
  /** `<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`);
25806
+ const scratchSamplePath = (path, { scratchDir, pid }, mode) => path.join(scratchDir, `gtd-mode-sample-${mode}-${pid}.md`);
25080
25807
  /**
25081
25808
  * The contradiction round-trip/skip-notice steps for `resolved`'s `mode:`.
25082
25809
  * No `formatCommand` means nothing to round-trip: empty. Otherwise: a live
@@ -25089,8 +25816,7 @@ const modeContradictionSteps = (resolved) => gen(function* () {
25089
25816
  if (resolved.formatCommand === void 0) return [];
25090
25817
  const { capabilities } = resolved;
25091
25818
  if (capabilities.format !== void 0 && capabilities.liveValidate !== void 0) {
25092
- const { scratchDir } = yield* Host;
25093
- const samplePath = scratchSamplePath(scratchDir, resolved.mode);
25819
+ const samplePath = scratchSamplePath(yield* Path$1, yield* Host, resolved.mode);
25094
25820
  const formatCommand = withFileVar(resolved.formatCommand, samplePath);
25095
25821
  return [{
25096
25822
  kind: "command",
@@ -25490,7 +26216,7 @@ const subjectIsBoundary = (def, subject) => {
25490
26216
  const parsed = parseCommitMessage(subject).parsed;
25491
26217
  if (parsed === void 0 || !ACTORS.has(parsed.actor)) return true;
25492
26218
  if (parsed.actor === "human" && parsed.from === void 0) return false;
25493
- if (parsed.actor === "check" && parsed.from === PIN_STATES.add) return false;
26219
+ if (parsed.actor === "check" && (parsed.from === PIN_STATES.add || parsed.from === LEGACY_PIN_STATES.add)) return false;
25494
26220
  return parsed.to === def.initial;
25495
26221
  };
25496
26222
  /** The last page index (walking newest→oldest, same direction as `locateEpisode`) whose subject is a boundary, or `undefined` if none in `page`. */
@@ -25687,7 +26413,10 @@ const commonOf = (step) => {
25687
26413
  actor: step.actor,
25688
26414
  ...optional("label", options.label),
25689
26415
  ...optional("file", options.file),
25690
- ...optional("mode", options.mode)
26416
+ ...typeof options.mode === "object" ? {
26417
+ mode: options.mode.name,
26418
+ modeSpec: options.mode.spec
26419
+ } : optional("mode", options.mode)
25691
26420
  };
25692
26421
  };
25693
26422
  const promptDef = (common, request, skills, access) => ({
@@ -25763,6 +26492,13 @@ const hintsOf = (def) => ({
25763
26492
  ...optional("skills", def.skills),
25764
26493
  ...optional("access", def.access)
25765
26494
  });
26495
+ /** A step's mode is a name the workflow knows, or a declared format named apart from all of them. */
26496
+ const modeFaultOf = (def, stepName, stepDef) => {
26497
+ if (stepDef.mode === void 0) return void 0;
26498
+ const known = def.modes[stepDef.mode] !== void 0;
26499
+ if (stepDef.modeSpec !== void 0) return known ? `gtd: config: step "${stepName}": declared format "${stepDef.mode}" has the name of a mode this workflow already knows — rename the format` : void 0;
26500
+ return known ? void 0 : `gtd: config: step "${stepName}": mode "${stepDef.mode}" is not a mode this workflow knows (${knownModes(def).join(", ")})`;
26501
+ };
25766
26502
  const normalizeStatus = (raw) => raw === "A" ? "A" : raw === "D" ? "D" : "M";
25767
26503
  /** Per-model token totals, highest-cost first (ties broken by model name). */
25768
26504
  const costByModel = (entries) => {
@@ -25854,7 +26590,8 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
25854
26590
  const { reached, step } = resting;
25855
26591
  const { stepDef, state, memory } = restFacts(pin, step, reached.trace, run);
25856
26592
  yield* (yield* Narrator).narrate(`rest resolved: ${state} (awaits ${stepDef.actor})`);
25857
- if (stepDef.mode !== void 0 && def.modes[stepDef.mode] === void 0) return yield* fail$4(/* @__PURE__ */ new Error(`gtd: config: step "${step.name}": mode "${stepDef.mode}" is not a mode this workflow knows (${knownModes(def).join(", ")})`));
26593
+ const modeFault = modeFaultOf(def, step.name, stepDef);
26594
+ if (modeFault !== void 0) return yield* fail$4(new Error(modeFault));
25858
26595
  const changes = ref === void 0 ? (yield* pendingChanges(git, stepDef.approve === true)).map((e) => ({
25859
26596
  status: normalizeStatus(e.status),
25860
26597
  path: e.path
@@ -26249,15 +26986,16 @@ const summaryFor = (run) => gen(function* () {
26249
26986
  * repository cannot be read at a ref, so it is left out. A file only the old
26250
26987
  * config read needs no check: whichever file imported it differs too.
26251
26988
  */
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();
26989
+ const inRepoConfigPaths = (path, root, files) => {
26990
+ const found = /* @__PURE__ */ new Set();
26991
+ for (const file of files) found.add(path.relative(root, path.resolve(root, file)).split(path.sep).join("/"));
26992
+ return [...found].filter((p) => p !== "" && !p.startsWith("..") && !path.isAbsolute(p)).sort();
26256
26993
  };
26257
26994
  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
26995
  const differingConfigPaths = (git, workspace, root, config, hash) => gen(function* () {
26259
26996
  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);
26997
+ const configPaths = inRepoConfigPaths(yield* Path$1, root, Object.keys(config.fingerprint));
26998
+ for (const path of configPaths) if ((yield* orElseSucceed(git.readFileAtRef(hash, path), () => void 0)) !== workspace.readSync(path)) differing.push(path);
26261
26999
  return differing;
26262
27000
  });
26263
27001
  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 +27775,11 @@ const runHistoryJudgesCommand = (refs, vars, jsonl, json, out) => gen(function*
27037
27775
  });
27038
27776
  //#endregion
27039
27777
  //#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
27778
  /** `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()));
27779
+ const runLspCommand = () => gen(function* () {
27780
+ const { self } = yield* Host;
27781
+ return yield* (yield* ToolCommands).lsp(self);
27782
+ });
27046
27783
  /**
27047
27784
  * Prints the briefing, emitted by the binary itself so it's always current.
27048
27785
  * gtd ships no workflow, so where no `gtd.config.ts` governs it says where to
@@ -27150,19 +27887,6 @@ const parseJudgeDocument = (rendered) => try_({
27150
27887
  try: () => JSON.parse(rendered),
27151
27888
  catch: () => /* @__PURE__ */ new Error("gtd judge answer: the resolved rest's \"judge:\" template did not render valid JSON")
27152
27889
  });
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
27890
  /** A stdin that is no judgment is the caller's mistake, a usage error like `gtd judge answer`'s. */
27167
27891
  const judgmentShape = (input) => {
27168
27892
  let doc;
@@ -27179,7 +27903,7 @@ const runJudgeRunCommand = (command, out) => gen(function* () {
27179
27903
  const host = yield* Host;
27180
27904
  const tools = yield* ToolCommands;
27181
27905
  yield* refuseLegacyRc;
27182
- const input = yield* readStdin();
27906
+ const input = yield* host.stdin;
27183
27907
  yield* judgmentShape(input);
27184
27908
  const verdict = yield* tryPromise({
27185
27909
  try: () => tools.judge({
@@ -27227,7 +27951,7 @@ const runJudgeAnswerCommand = (json, out) => gen(function* () {
27227
27951
  const rendered = yield* renderRest(rest);
27228
27952
  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
27953
  const ids = (yield* parseJudgeDocument(rendered.judge)).questions.map((q) => q.id);
27230
- const raw = yield* readStdin();
27954
+ const raw = yield* (yield* Host).stdin;
27231
27955
  const parsed = yield* try_({
27232
27956
  try: () => JSON.parse(raw),
27233
27957
  catch: () => new SelectorUsageError("gtd judge answer: stdin is not valid JSON")
@@ -27476,8 +28200,8 @@ const runRepinCommand = (json, out) => gen(function* () {
27476
28200
  * `gtd check <mode>` invocation instead, so there's always something concrete
27477
28201
  * to name.
27478
28202
  */
27479
- const selfValidateCommand = (def, state, mode, file) => {
27480
- const resolved = resolveMode(def, state, mode);
28203
+ const selfValidateCommand = (def, state, mode, file, spec) => {
28204
+ const resolved = resolveMode(def, state, mode, spec);
27481
28205
  const validate = resolved.kind === "resolved" ? resolved.validate : void 0;
27482
28206
  return commandForFile(validate?.kind === "command" ? validate.command : seededValidateCommand(mode), file);
27483
28207
  };
@@ -27495,7 +28219,7 @@ const resolveValidateScript = (rest) => gen(function* () {
27495
28219
  const file = rest.hints.file;
27496
28220
  const mode = rest.stepDef.mode;
27497
28221
  if (file === void 0 || mode === void 0) return void 0;
27498
- const resolved = resolveMode(rest.def, rest.state, mode);
28222
+ const resolved = resolveMode(rest.def, rest.state, mode, rest.stepDef.modeSpec);
27499
28223
  if (resolved.kind === "unknown") return yield* fail$4(new Error(resolved.message));
27500
28224
  const { script } = yield* validateScriptFor(resolved, file);
27501
28225
  return {
@@ -27526,7 +28250,7 @@ const runNextCommand = (json, out) => gen(function* () {
27526
28250
  if (json.kind === "document") out.write(renderBeatJson(fields));
27527
28251
  else if (json.kind === "select") yield* writeSelection(out, fields, json.path);
27528
28252
  else {
27529
- const validateCommand = emitsValidatablePrompt(rendered) ? selfValidateCommand(rest.def, rest.state, rendered.mode, rendered.file) : void 0;
28253
+ const validateCommand = emitsValidatablePrompt(rendered) ? selfValidateCommand(rest.def, rest.state, rendered.mode, rendered.file, rest.stepDef.modeSpec) : void 0;
27530
28254
  if (DEMAND_BRIEFING[fields.kind].suppressHeader) {
27531
28255
  const shown = renderBeatPlain({
27532
28256
  ...fields,
@@ -27553,8 +28277,8 @@ const runValidateCommand = (json, out) => gen(function* () {
27553
28277
  * Findings ride the failing Effect's message, so the shared refusal path
27554
28278
  * reports them on stderr.
27555
28279
  */
27556
- const runCheckCommand = (mode, file, openQuestions, openThreadsFlag) => gen(function* () {
27557
- const format = steeringFormatFor(mode);
28280
+ const runCheckCommand = (mode, file, openQuestions, openThreadsFlag, spec) => gen(function* () {
28281
+ const format = spec !== void 0 ? yield* declaredFormat(spec) : steeringFormatFor(mode);
27558
28282
  if (format === void 0) return yield* fail$4(new GtdUsageError(`gtd check: unknown mode "${mode}" — known modes: ${BUILT_IN_MODE_NAMES.join(", ")}`));
27559
28283
  if (openThreadsFlag) {
27560
28284
  if (openQuestions) return yield* fail$4(/* @__PURE__ */ new Error("gtd check: --open-threads and --open-questions are mutually exclusive"));
@@ -27573,6 +28297,10 @@ const runCheckCommand = (mode, file, openQuestions, openThreadsFlag) => gen(func
27573
28297
  if (errors.length === 0) return;
27574
28298
  return yield* fail$4(/* @__PURE__ */ new Error(`gtd check: ${file} is not valid under mode "${mode}" (${errors.length} finding(s)):\n` + errors.join("\n")));
27575
28299
  });
28300
+ const declaredFormat = (spec) => try_({
28301
+ try: () => formatFromSpec(spec),
28302
+ catch: (error) => new GtdUsageError(`gtd check: --spec is not a declared format: ${error.message}`)
28303
+ });
27576
28304
  const existingFile = (file) => flatMap(Workspace, (workspace) => {
27577
28305
  const content = workspace.atPath(file);
27578
28306
  return content === void 0 ? fail$4(/* @__PURE__ */ new Error(`gtd check: no such file: ${file}`)) : succeed$1(content);
@@ -27742,7 +28470,7 @@ const dispatchVoidCommand = (command, json, out) => {
27742
28470
  case "validate": return runValidateCommand(json, out);
27743
28471
  case "check":
27744
28472
  if (command.mode === void 0 || command.file === void 0) return runCodeThreadsCheckCommand();
27745
- return runCheckCommand(command.mode, command.file, command.openQuestions ?? false, command.openThreads ?? false);
28473
+ return runCheckCommand(command.mode, command.file, command.openQuestions ?? false, command.openThreads ?? false, command.spec);
27746
28474
  case "uncheck": return runUncheckCommand(command.file);
27747
28475
  case "guide": return runGuideCommand(out);
27748
28476
  case "bundle": return runBundleCommand(out);
@@ -28037,6 +28765,16 @@ const FLAGS = [
28037
28765
  valueHint: "",
28038
28766
  help: "Instead of <mode>'s findings, list the open threads in <file>; exits 1 when any are open. Alone, lists the open code-comment threads in the files the current process changed"
28039
28767
  },
28768
+ {
28769
+ name: "--spec",
28770
+ arity: 1,
28771
+ repeatable: false,
28772
+ scope: (kind) => kind === "check",
28773
+ decode: ([raw]) => succeed$5(raw ?? ""),
28774
+ scopeError: "gtd: --spec is only valid for `gtd check`",
28775
+ valueHint: "<json>",
28776
+ help: "Check against the declared format this spec describes, named <mode>, instead of a built-in one; validation scripts pass it for a step whose mode is a declared format"
28777
+ },
28040
28778
  {
28041
28779
  name: "--verbose",
28042
28780
  arity: 0,
@@ -28282,7 +29020,7 @@ The fixed answerer reads --answers and leaves out the questions it does not cove
28282
29020
  arity: { names: ["mode", "file"] },
28283
29021
  group: "Driver plumbing",
28284
29022
  summary: "Check a steering file's format",
28285
- details: `Run the steering format <mode> (qa, review or findings) over <file> and print each finding on a line of its own; exits 1 when there are any, or when <file> does not exist. An unknown <mode> is a usage error (exit 2). Reads no workflow and runs from any directory — emitted validation scripts call it.`
29023
+ details: `Run the steering format <mode> (qa, review or findings, or the declared format --spec describes) over <file> and print each finding on a line of its own; exits 1 when there are any, or when <file> does not exist. An unknown <mode> is a usage error (exit 2). Reads no workflow and runs from any directory — emitted validation scripts call it.`
28286
29024
  },
28287
29025
  {
28288
29026
  token: "uncheck",
@@ -28656,7 +29394,8 @@ const parseArgv = (argv) => {
28656
29394
  ...restPositionals[0] !== void 0 ? { mode: restPositionals[0] } : {},
28657
29395
  ...restPositionals[1] !== void 0 ? { file: restPositionals[1] } : {},
28658
29396
  ...bag["--open-questions"] !== void 0 ? { openQuestions: bag["--open-questions"] } : {},
28659
- ...bag["--open-threads"] !== void 0 ? { openThreads: bag["--open-threads"] } : {}
29397
+ ...bag["--open-threads"] !== void 0 ? { openThreads: bag["--open-threads"] } : {},
29398
+ ...bag["--spec"] !== void 0 ? { spec: bag["--spec"] } : {}
28660
29399
  },
28661
29400
  json,
28662
29401
  verbose
@@ -29413,6 +30152,7 @@ const toError = (e) => e instanceof Error ? e : new Error(String(e));
29413
30152
  * one). Mirrors the guard the deleted `makeInMemoryFileSystem` carried.
29414
30153
  */
29415
30154
  const isGitDirKey = (key) => key.startsWith(".git/");
30155
+ const paths = runSync$1(provide(Path$1, layer$4));
29416
30156
  /**
29417
30157
  * `Workspace` for the in-memory tier. `readSync`/`read`/`write`/`committed`
29418
30158
  * take a repo-relative key ONLY (mirroring the live adapter's
@@ -29428,12 +30168,12 @@ const isGitDirKey = (key) => key.startsWith(".git/");
29428
30168
  */
29429
30169
  const makeInMemoryWorkspaceOps = (repo, root) => {
29430
30170
  const toKey = (path) => {
29431
- if (!isAbsolute(path)) return path;
30171
+ if (!paths.isAbsolute(path)) return path;
29432
30172
  const prefix = `${root}/`;
29433
30173
  return path === root ? "" : path.startsWith(prefix) ? path.slice(prefix.length) : path;
29434
30174
  };
29435
30175
  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)`);
30176
+ 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
30177
  return path;
29438
30178
  };
29439
30179
  const readAt = (key) => isGitDirKey(key) ? void 0 : repo.readFile(key);
@@ -29480,7 +30220,7 @@ const makeInMemoryConfigDiscovery = (repo, root) => {
29480
30220
  const workspace = makeInMemoryWorkspaceOps(repo, root);
29481
30221
  return succeed$2(ConfigDiscovery, {
29482
30222
  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) => ({
30223
+ 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
30224
  path,
29485
30225
  owned: true
29486
30226
  }))),
@@ -29488,8 +30228,8 @@ const makeInMemoryConfigDiscovery = (repo, root) => {
29488
30228
  }),
29489
30229
  workflowModule: (levelRoot, levelHome) => try_({
29490
30230
  try: () => {
29491
- for (const dir of walkUp(levelRoot, levelHome)) {
29492
- const filepath = join(dir, WORKFLOW_MODULE);
30231
+ for (const dir of walkUp(paths, levelRoot, levelHome)) {
30232
+ const filepath = paths.join(dir, WORKFLOW_MODULE);
29493
30233
  const source = workspace.atPath(filepath);
29494
30234
  if (source !== void 0) return {
29495
30235
  filepath,
@@ -29521,26 +30261,41 @@ const gitTestLayer = (repo, root = "/repo") => succeed$2(GitService, fakeGitOper
29521
30261
  * itself; explicit env wins.
29522
30262
  */
29523
30263
  const ambientFlagEnv = (env = process.env) => Object.fromEntries(Object.entries(env).filter((e) => e[0].startsWith("GTD_FLAG_") && e[1] !== void 0));
30264
+ /** Where the in-memory host's scratch files go when `TMPDIR` is unset. */
30265
+ const IN_MEMORY_SCRATCH = "/scratch";
30266
+ const IN_MEMORY_PID = 4242;
30267
+ /**
30268
+ * The in-memory `Host`: nothing read off the real process or `node:os`.
30269
+ * `realPath` is a no-op (there is no real filesystem to resolve symlinks
30270
+ * against), so both sides of `assertRunningFromRepoRoot`'s comparison
30271
+ * collapse to `root`.
30272
+ */
30273
+ const inMemoryHost = (root, home, env, stdin = "") => ({
30274
+ root,
30275
+ home,
30276
+ env,
30277
+ scratchDir: scratchDirOf(env, IN_MEMORY_SCRATCH),
30278
+ realPath: () => succeed$1(root),
30279
+ pid: IN_MEMORY_PID,
30280
+ uid: void 0,
30281
+ self: ["gtd"],
30282
+ stdin: succeed$1(stdin)
30283
+ });
29524
30284
  /** 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
30285
  function testLayers(repo, opts = {}) {
29526
30286
  const root = opts.root ?? "/repo";
29527
30287
  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, {
30288
+ return mergeAll(gitTestLayer(repo, root), succeed$2(Workspace, makeInMemoryWorkspaceOps(repo, root)), succeed$2(Host, inMemoryHost(root, home, {
30289
+ ...ambientFlagEnv(),
30290
+ ...opts.env
30291
+ }, 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
30292
  ...missingToolCommands,
29538
30293
  ...opts.tools
29539
30294
  }), CommandRunner.layer(() => fail$4(/* @__PURE__ */ new Error("gtd ui: CommandRunner has no test double wired into testLayers()"))));
29540
30295
  }
29541
30296
  //#endregion
29542
30297
  //#region src/twins/cliIo.ts
29543
- const makeCapturingCliIo = (repo, env = {}, tools = {}) => {
30298
+ const makeCapturingCliIo = (repo, env = {}, tools = {}, stdin = "") => {
29544
30299
  let stdout = Buffer.alloc(0);
29545
30300
  let stderr = "";
29546
30301
  let exitCode = 0;
@@ -29559,7 +30314,8 @@ const makeCapturingCliIo = (repo, env = {}, tools = {}) => {
29559
30314
  env,
29560
30315
  narrate: (line) => stderr += line,
29561
30316
  verbose,
29562
- tools
30317
+ tools,
30318
+ stdin
29563
30319
  })
29564
30320
  },
29565
30321
  result: () => ({
@@ -30262,13 +31018,13 @@ const recognizeModeContradictionCheck = (repo, commands, block) => {
30262
31018
  return parsed === void 0 ? void 0 : simulateModeContradictionCheck(repo, commands, parsed);
30263
31019
  };
30264
31020
  const GTD_CHECK_RE = /^gtd check (\S+) (.+)$/;
30265
- /** `gtd check <mode> <file>` — a non-empty findings array fails the script, mirroring a real invocation's non-zero exit under `set -e`. */
31021
+ /** `gtd check <mode> <file> [--spec <json>]` — a non-empty findings array fails the script, mirroring a real invocation's non-zero exit under `set -e`. */
30266
31022
  const recognizeGtdCheck = (repo, block) => {
30267
31023
  const match = GTD_CHECK_RE.exec(block);
30268
31024
  if (!match) return void 0;
30269
31025
  const mode = match[1];
30270
- const [file] = extractQuotedTokens(match[2]);
30271
- const format = steeringFormatFor(mode);
31026
+ const [file, spec] = extractQuotedTokens(match[2]);
31027
+ const format = / --spec '/.test(match[2]) && spec !== void 0 ? formatFromSpec(spec) : steeringFormatFor(mode);
30272
31028
  if (format === void 0 || file === void 0) return {
30273
31029
  kind: "failed",
30274
31030
  error: `gtd check: unknown mode "${mode}" — known modes: ${BUILT_IN_MODE_NAMES.join(", ")}`
@@ -30495,6 +31251,8 @@ const unrecognized = (block, output = "") => ({
30495
31251
  error: `${UNRECOGNIZED_PREFIX}${block.split("\n")[0]}`
30496
31252
  });
30497
31253
  Narrator.layer(() => {}, false);
31254
+ new TextEncoder();
31255
+ new TextDecoder("utf-8", { ignoreBOM: true });
30498
31256
  //#endregion
30499
31257
  //#region src/testkit/imports.ts
30500
31258
  const IDENTIFIER$1 = /^[A-Za-z_$][\w$]*$/;
@@ -30720,8 +31478,8 @@ const testRepo = (options) => {
30720
31478
  ...Object.fromEntries(Object.keys(process.env).filter((name) => name.startsWith("GTD_FLAG_")).map((name) => [name, void 0])),
30721
31479
  ...env
30722
31480
  } : env;
30723
- const gtd = async (...args) => {
30724
- const { io, result } = makeCapturingCliIo(repo, seenEnv());
31481
+ const gtdWithStdin = async (stdin, ...args) => {
31482
+ const { io, result } = makeCapturingCliIo(repo, seenEnv(), {}, stdin);
30725
31483
  if (typeof workflow !== "string") registry().set(id, workflow);
30726
31484
  for (const [specifier, module] of modules) registry().set(moduleKey(specifier), module);
30727
31485
  try {
@@ -30736,20 +31494,7 @@ const testRepo = (options) => {
30736
31494
  }
30737
31495
  return result();
30738
31496
  };
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
- };
31497
+ const gtd = (...args) => gtdWithStdin("", ...args);
30753
31498
  const apply = (what, script) => {
30754
31499
  if (script.length === 0) return;
30755
31500
  const applied = applyEmittedScript(repo, commands, script);
@@ -30757,7 +31502,7 @@ const testRepo = (options) => {
30757
31502
  };
30758
31503
  /** Runs a write command, then the script its `--json=script` twin prints. */
30759
31504
  const runWrite = async (args, stdin, refused = (error) => error) => {
30760
- const call = (...extra) => stdin === void 0 ? gtd(...args, ...extra) : withStdin(stdin, () => gtd(...args, ...extra));
31505
+ const call = (...extra) => gtdWithStdin(stdin ?? "", ...args, ...extra);
30761
31506
  const reported = await call();
30762
31507
  if (reported.exitCode !== 0) throw refused(failed(`gtd ${args.join(" ")}`, reported));
30763
31508
  const script = await call("--json=script");