@deslop/workbench 0.0.407 → 0.0.410

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 (38) hide show
  1. package/dist/client/assets/{-workbench-terminal-C3m5o50g.js → -workbench-terminal-Ne3Ca_Zm.js} +8 -8
  2. package/dist/client/assets/{agent-DWi_bAGy.js → agent-YDhqENQr.js} +1 -1
  3. package/dist/client/assets/agent-YvOEbSL4.js +2 -0
  4. package/dist/client/assets/{dialog-CpMhdP-y.js → dialog-O3wBc7X2.js} +2 -2
  5. package/dist/client/assets/diff-BcPEyFnc.js +223 -0
  6. package/dist/client/assets/diff-CZdOESVK.js +2 -0
  7. package/dist/client/assets/{external-link-Bw3Ptvfw.js → external-link-D8TqDHgo.js} +1 -1
  8. package/dist/client/assets/{fallbacks-f8E4aL2c.js → fallbacks-BYPj1x-T.js} +11 -11
  9. package/dist/client/assets/index-DFD32fIK.css +2 -0
  10. package/dist/client/assets/index-k88N8viu.js +10 -0
  11. package/dist/client/assets/{input-BVvU6Eiy.js → input-BHj016ES.js} +1 -1
  12. package/dist/client/assets/portless-DbuqdEMA.js +2 -0
  13. package/dist/client/assets/portless-rPZ2tatn.js +1 -0
  14. package/dist/client/assets/{resizable-CYsJ6Kg2.js → resizable-BlkgaK6V.js} +1 -1
  15. package/dist/client/assets/route-DKK79z62.js +2 -0
  16. package/dist/client/assets/route-DOc4X-Ya.js +45 -0
  17. package/dist/client/assets/{run-C8QYJmux.js → run-DVOYYIUX.js} +1 -1
  18. package/dist/client/assets/run-gggwaxnT.js +2 -0
  19. package/dist/client/assets/{sonner-CrB5PaMX.js → sonner-Bnl1Iat_.js} +1 -1
  20. package/dist/client/assets/state-Cxz4BVk7.js +2 -0
  21. package/dist/client/assets/terminal-Brvez29s.js +2 -0
  22. package/dist/client/assets/{terminal-Dj-VWVHI.js → terminal-CYX_Ixg-.js} +1 -1
  23. package/dist/client/assets/{triangle-alert-llGGn6BK.js → triangle-alert-Ceg_3GX9.js} +1 -1
  24. package/dist/client/index.html +11 -11
  25. package/dist/server.js +718 -784
  26. package/package.json +1 -1
  27. package/dist/client/assets/agent-BXm8Dnfe.js +0 -2
  28. package/dist/client/assets/diff-BDdajfz3.js +0 -2
  29. package/dist/client/assets/diff-mRZ3PeyD.js +0 -223
  30. package/dist/client/assets/index-D53pFEJY.js +0 -10
  31. package/dist/client/assets/index-YmJ_EbaF.css +0 -2
  32. package/dist/client/assets/portless-BYTEgoik.js +0 -1
  33. package/dist/client/assets/portless-Cke08ZHX.js +0 -2
  34. package/dist/client/assets/route-BzHtbTaZ.js +0 -2
  35. package/dist/client/assets/route-DD4r__Zm.js +0 -45
  36. package/dist/client/assets/run-CW5YhyWI.js +0 -2
  37. package/dist/client/assets/state-CvX-YRlC.js +0 -2
  38. package/dist/client/assets/terminal-DJzGIrES.js +0 -2
package/dist/server.js CHANGED
@@ -4157,7 +4157,7 @@ const flatMap$6 = /*#__PURE__*/ dual(2, (self, f) => isNone(self) ? none() : f(s
4157
4157
  * @category filtering
4158
4158
  * @since 2.0.0
4159
4159
  */
4160
- const filter$6 = /*#__PURE__*/ dual(2, (self, predicate) => isNone(self) ? none() : predicate(self.value) ? some$1(self.value) : none());
4160
+ const filter$5 = /*#__PURE__*/ dual(2, (self, predicate) => isNone(self) ? none() : predicate(self.value) ? some$1(self.value) : none());
4161
4161
  //#endregion
4162
4162
  //#region ../../node_modules/.pnpm/effect@4.0.0-beta.74/node_modules/effect/dist/Context.js
4163
4163
  /**
@@ -5320,7 +5320,7 @@ const toEntries = /*#__PURE__*/ (/* @__PURE__ */ dual(2, (self, f) => {
5320
5320
  * @category guards
5321
5321
  * @since 2.0.0
5322
5322
  */
5323
- const has$6 = /*#__PURE__*/ dual(2, (self, key) => Object.hasOwn(self, key));
5323
+ const has$5 = /*#__PURE__*/ dual(2, (self, key) => Object.hasOwn(self, key));
5324
5324
  /**
5325
5325
  * Maps a record into another record by applying a transformation function to each of its values.
5326
5326
  *
@@ -5397,7 +5397,7 @@ const keys$3 = (self) => Object.keys(self);
5397
5397
  * @since 2.0.0
5398
5398
  */
5399
5399
  const isSubrecordBy = (equivalence) => dual(2, (self, that) => {
5400
- for (const key of keys$3(self)) if (!has$6(that, key) || !equivalence(self[key], that[key])) return false;
5400
+ for (const key of keys$3(self)) if (!has$5(that, key) || !equivalence(self[key], that[key])) return false;
5401
5401
  return true;
5402
5402
  });
5403
5403
  /**
@@ -5640,7 +5640,7 @@ const range$1 = (start, end) => start <= end ? makeBy(end - start + 1, (i) => st
5640
5640
  * @category constructors
5641
5641
  * @since 2.0.0
5642
5642
  */
5643
- const fromIterable$6 = (collection) => Array$1.isArray(collection) ? collection : Array$1.from(collection);
5643
+ const fromIterable$4 = (collection) => Array$1.isArray(collection) ? collection : Array$1.from(collection);
5644
5644
  /**
5645
5645
  * Normalizes a value that is either a single element or an array into an array.
5646
5646
  *
@@ -5726,7 +5726,7 @@ const append$1 = /*#__PURE__*/ dual(2, (self, last) => [...self, last]);
5726
5726
  * @category combining
5727
5727
  * @since 2.0.0
5728
5728
  */
5729
- const appendAll = /*#__PURE__*/ dual(2, (self, that) => fromIterable$6(self).concat(fromIterable$6(that)));
5729
+ const appendAll = /*#__PURE__*/ dual(2, (self, that) => fromIterable$4(self).concat(fromIterable$4(that)));
5730
5730
  Array$1.isArray;
5731
5731
  /**
5732
5732
  * Checks whether a mutable `Array` is empty, narrowing the type to `[]`.
@@ -6040,10 +6040,49 @@ const tailNonEmpty = (self) => self.slice(1);
6040
6040
  * @since 2.0.0
6041
6041
  */
6042
6042
  const take$3 = /*#__PURE__*/ dual(2, (self, n) => {
6043
- const input = fromIterable$6(self);
6043
+ const input = fromIterable$4(self);
6044
6044
  return input.slice(0, clamp$1(n, input));
6045
6045
  });
6046
6046
  /**
6047
+ * Takes elements from the start while the predicate holds, stopping at the
6048
+ * first element that fails.
6049
+ *
6050
+ * **When to use**
6051
+ *
6052
+ * Use to keep the leading elements of an iterable while each element satisfies
6053
+ * a predicate, returning the retained prefix as an array.
6054
+ *
6055
+ * **Details**
6056
+ *
6057
+ * - Supports refinements for type narrowing.
6058
+ * - The predicate receives `(element, index)`.
6059
+ *
6060
+ * **Example** (Taking while condition holds)
6061
+ *
6062
+ * ```ts
6063
+ * import { Array } from "effect"
6064
+ *
6065
+ * console.log(Array.takeWhile([1, 3, 2, 4, 1, 2], (x) => x < 4)) // [1, 3, 2]
6066
+ * ```
6067
+ *
6068
+ * @see {@link take} for keeping a fixed number of leading elements
6069
+ * @see {@link dropWhile} for removing the matching prefix and keeping the rest
6070
+ * @see {@link span} for splitting the matching prefix from the remaining elements
6071
+ *
6072
+ * @category getters
6073
+ * @since 2.0.0
6074
+ */
6075
+ const takeWhile = /*#__PURE__*/ dual(2, (self, predicate) => {
6076
+ let i = 0;
6077
+ const out = [];
6078
+ for (const a of self) {
6079
+ if (!predicate(a, i)) break;
6080
+ out.push(a);
6081
+ i++;
6082
+ }
6083
+ return out;
6084
+ });
6085
+ /**
6047
6086
  * Removes the first `n` elements, creating a new array.
6048
6087
  *
6049
6088
  * **When to use**
@@ -6072,7 +6111,7 @@ const take$3 = /*#__PURE__*/ dual(2, (self, n) => {
6072
6111
  * @since 2.0.0
6073
6112
  */
6074
6113
  const drop = /*#__PURE__*/ dual(2, (self, n) => {
6075
- const input = fromIterable$6(self);
6114
+ const input = fromIterable$4(self);
6076
6115
  return input.slice(clamp$1(n, input), input.length);
6077
6116
  });
6078
6117
  /**
@@ -6101,7 +6140,7 @@ const drop = /*#__PURE__*/ dual(2, (self, n) => {
6101
6140
  * @since 2.0.0
6102
6141
  */
6103
6142
  const dropRight = /*#__PURE__*/ dual(2, (self, n) => {
6104
- const input = fromIterable$6(self);
6143
+ const input = fromIterable$4(self);
6105
6144
  return input.slice(0, input.length - clamp$1(n, input));
6106
6145
  });
6107
6146
  /**
@@ -6163,7 +6202,7 @@ const findFirst$2 = findFirst$3;
6163
6202
  * @since 2.0.0
6164
6203
  */
6165
6204
  const findLast = /*#__PURE__*/ dual(2, (self, f) => {
6166
- const input = fromIterable$6(self);
6205
+ const input = fromIterable$4(self);
6167
6206
  for (let i = input.length - 1; i >= 0; i--) {
6168
6207
  const a = input[i];
6169
6208
  const o = f(a, i);
@@ -6299,8 +6338,8 @@ const contains = /*#__PURE__*/ containsWith(/*#__PURE__*/ asEquivalence());
6299
6338
  * @since 2.0.0
6300
6339
  */
6301
6340
  const unionWith = /*#__PURE__*/ dual(3, (self, that, isEquivalent) => {
6302
- const a = fromIterable$6(self);
6303
- const b = fromIterable$6(that);
6341
+ const a = fromIterable$4(self);
6342
+ const b = fromIterable$4(that);
6304
6343
  if (isReadonlyArrayNonEmpty(a)) {
6305
6344
  if (isReadonlyArrayNonEmpty(b)) return dedupeWith(isEquivalent)(appendAll(a, b));
6306
6345
  return a;
@@ -6326,7 +6365,7 @@ const unionWith = /*#__PURE__*/ dual(3, (self, that, isEquivalent) => {
6326
6365
  * @category elements
6327
6366
  * @since 2.0.0
6328
6367
  */
6329
- const union$1 = /*#__PURE__*/ dual(2, (self, that) => unionWith(self, that, asEquivalence()));
6368
+ const union$3 = /*#__PURE__*/ dual(2, (self, that) => unionWith(self, that, asEquivalence()));
6330
6369
  /**
6331
6370
  * Creates an empty array.
6332
6371
  *
@@ -6505,8 +6544,8 @@ const getSomes = (self) => {
6505
6544
  * @category filtering
6506
6545
  * @since 2.0.0
6507
6546
  */
6508
- const filter$5 = /*#__PURE__*/ dual(2, (self, predicate) => {
6509
- const as = fromIterable$6(self);
6547
+ const filter$4 = /*#__PURE__*/ dual(2, (self, predicate) => {
6548
+ const as = fromIterable$4(self);
6510
6549
  const out = [];
6511
6550
  for (let i = 0; i < as.length; i++) if (predicate(as[i], i)) out.push(as[i]);
6512
6551
  return out;
@@ -6536,7 +6575,7 @@ const filter$5 = /*#__PURE__*/ dual(2, (self, predicate) => {
6536
6575
  * @category folding
6537
6576
  * @since 2.0.0
6538
6577
  */
6539
- const reduce = /*#__PURE__*/ dual(3, (self, b, f) => fromIterable$6(self).reduce((b, a, i) => f(b, a, i), b));
6578
+ const reduce = /*#__PURE__*/ dual(3, (self, b, f) => fromIterable$4(self).reduce((b, a, i) => f(b, a, i), b));
6540
6579
  /**
6541
6580
  * Checks whether all elements satisfy the predicate. Supports refinements for
6542
6581
  * type narrowing.
@@ -6625,7 +6664,7 @@ const makeEquivalence = Array_;
6625
6664
  * @since 2.0.0
6626
6665
  */
6627
6666
  const dedupeWith = /*#__PURE__*/ dual(2, (self, isEquivalent) => {
6628
- const input = fromIterable$6(self);
6667
+ const input = fromIterable$4(self);
6629
6668
  if (isReadonlyArrayNonEmpty(input)) {
6630
6669
  const out = [headNonEmpty(input)];
6631
6670
  const rest = tailNonEmpty(input);
@@ -6674,7 +6713,7 @@ const dedupe = (self) => dedupeWith(self, asEquivalence());
6674
6713
  * @category folding
6675
6714
  * @since 2.0.0
6676
6715
  */
6677
- const join$4 = /*#__PURE__*/ dual(2, (self, sep) => fromIterable$6(self).join(sep));
6716
+ const join$4 = /*#__PURE__*/ dual(2, (self, sep) => fromIterable$4(self).join(sep));
6678
6717
  //#endregion
6679
6718
  //#region ../../node_modules/.pnpm/effect@4.0.0-beta.74/node_modules/effect/dist/Duration.js
6680
6719
  const TypeId$49 = "~effect/time/Duration";
@@ -7263,7 +7302,7 @@ const toPredicate = (self) => (input) => !isFailure$1(self(input));
7263
7302
  * @category constructors
7264
7303
  * @since 4.0.0
7265
7304
  */
7266
- const has$5 = (key) => (input) => input.has(key) ? succeed$7(input) : fail$7(input);
7305
+ const has$4 = (key) => (input) => input.has(key) ? succeed$7(input) : fail$7(input);
7267
7306
  /**
7268
7307
  * Composes two filters sequentially, feeding the output of the first into the second.
7269
7308
  *
@@ -7966,7 +8005,7 @@ const hasInterruptsOnly$1 = (self) => self.reasons.length > 0 && self.reasons.ev
7966
8005
  const causeCombine = /*#__PURE__*/ dual(2, (self, that) => {
7967
8006
  if (self.reasons.length === 0) return that;
7968
8007
  else if (that.reasons.length === 0) return self;
7969
- const newCause = new CauseImpl(union$1(self.reasons, that.reasons));
8008
+ const newCause = new CauseImpl(union$3(self.reasons, that.reasons));
7970
8009
  return equals$2(self, newCause) ? self : newCause;
7971
8010
  });
7972
8011
  /** @internal */
@@ -9161,7 +9200,7 @@ const forEach$2 = /*#__PURE__*/ dual((args) => typeof args[1] === "function", (i
9161
9200
  const concurrencyOption = options?.concurrency === "inherit" ? parent.getRef(CurrentConcurrency) : options?.concurrency ?? 1;
9162
9201
  const concurrency = concurrencyOption === "unbounded" ? Number.POSITIVE_INFINITY : Math.max(1, concurrencyOption);
9163
9202
  if (concurrency === 1) return forEachSequential(iterable, f, options);
9164
- const items = fromIterable$6(iterable);
9203
+ const items = fromIterable$4(iterable);
9165
9204
  let length = items.length;
9166
9205
  if (length === 0) return options?.discard ? void_$3 : succeed$6([]);
9167
9206
  const out = options?.discard ? void 0 : new Array(length);
@@ -19618,7 +19657,7 @@ function omit() {
19618
19657
  */
19619
19658
  function withDefault$1(defaultValue) {
19620
19659
  return new Getter((o) => {
19621
- const filtered = filter$6(o, isNotUndefined);
19660
+ const filtered = filter$5(o, isNotUndefined);
19622
19661
  return isSome(filtered) ? succeed$3(filtered) : mapEager(defaultValue, some$1);
19623
19662
  });
19624
19663
  }
@@ -21787,7 +21826,7 @@ function tuple(elements, checks = void 0) {
21787
21826
  return new Arrays(false, elements.map((e) => e.ast), [], void 0, checks);
21788
21827
  }
21789
21828
  /** @internal */
21790
- function union(members, mode, checks) {
21829
+ function union$2(members, mode, checks) {
21791
21830
  return new Union$2(members.map(getAST), mode, void 0, checks);
21792
21831
  }
21793
21832
  function getCandidateTypes(ast) {
@@ -23481,7 +23520,7 @@ const take$2 = (self) => {
23481
23520
  * @category mutations
23482
23521
  * @since 4.0.0
23483
23522
  */
23484
- const filter$4 = (self, f) => {
23523
+ const filter$3 = (self, f) => {
23485
23524
  const array = [];
23486
23525
  let chunk = self.head;
23487
23526
  while (chunk) {
@@ -23547,7 +23586,7 @@ const filter$4 = (self, f) => {
23547
23586
  * @category mutations
23548
23587
  * @since 4.0.0
23549
23588
  */
23550
- const remove$7 = (self, value) => filter$4(self, (v) => v !== value);
23589
+ const remove$7 = (self, value) => filter$3(self, (v) => v !== value);
23551
23590
  //#endregion
23552
23591
  //#region ../../node_modules/.pnpm/effect@4.0.0-beta.74/node_modules/effect/dist/MutableRef.js
23553
23592
  const TypeId$38 = "~effect/MutableRef";
@@ -24336,7 +24375,7 @@ var BoundedPubSubArb = class {
24336
24375
  if (this.replayBuffer) this.replayBuffer.offerAll(elements);
24337
24376
  return [];
24338
24377
  }
24339
- const chunk = fromIterable$6(elements);
24378
+ const chunk = fromIterable$4(elements);
24340
24379
  const n = chunk.length;
24341
24380
  const size = this.publisherIndex - this.subscribersIndex;
24342
24381
  const available = this.capacity - size;
@@ -24482,7 +24521,7 @@ var BoundedPubSubPow2 = class {
24482
24521
  if (this.replayBuffer) this.replayBuffer.offerAll(elements);
24483
24522
  return [];
24484
24523
  }
24485
- const chunk = fromIterable$6(elements);
24524
+ const chunk = fromIterable$4(elements);
24486
24525
  const n = chunk.length;
24487
24526
  const size = this.publisherIndex - this.subscribersIndex;
24488
24527
  const available = this.capacity - size;
@@ -24624,7 +24663,7 @@ var BoundedPubSubSingle = class {
24624
24663
  if (this.replayBuffer) this.replayBuffer.offerAll(elements);
24625
24664
  return [];
24626
24665
  }
24627
- const chunk = fromIterable$6(elements);
24666
+ const chunk = fromIterable$4(elements);
24628
24667
  if (chunk.length === 0) return chunk;
24629
24668
  if (this.publish(chunk[0])) return chunk.slice(1);
24630
24669
  else return chunk;
@@ -24958,7 +24997,7 @@ var BackPressureStrategy = class {
24958
24997
  }
24959
24998
  }
24960
24999
  removeUnsafe(deferred) {
24961
- filter$4(this.publishers, ([_, d]) => d !== deferred);
25000
+ filter$3(this.publishers, ([_, d]) => d !== deferred);
24962
25001
  }
24963
25002
  };
24964
25003
  /**
@@ -27252,47 +27291,6 @@ const repeat$1 = /*#__PURE__*/ dual(2, (self, schedule) => toStepWithMetadata(ty
27252
27291
  return loop;
27253
27292
  }), unwrap$3));
27254
27293
  /**
27255
- * Filters arrays of elements emitted by a channel, applying the filter
27256
- * to each element within the arrays and only emitting non-empty filtered arrays.
27257
- *
27258
- * **Example** (Filtering array output)
27259
- *
27260
- * ```ts
27261
- * import { Array, Channel } from "effect"
27262
- *
27263
- * const nonEmptyArrayPredicate = Array.isReadonlyArrayNonEmpty
27264
- *
27265
- * // Create a channel that outputs arrays of mixed data
27266
- * const arrayChannel = Channel.fromIterable([
27267
- * Array.make(1, 2, 3, 4, 5),
27268
- * Array.make(6, 7, 8, 9, 10),
27269
- * Array.make(11, 12, 13, 14, 15)
27270
- * ]).pipe(Channel.filter(nonEmptyArrayPredicate))
27271
- *
27272
- * // Filter arrays to keep only even numbers
27273
- * const evenArraysChannel = Channel.filterArray(arrayChannel, (n) => n % 2 === 0)
27274
- * // Outputs: [2, 4], [6, 8, 10], [12, 14]
27275
- * // Note: Only non-empty filtered arrays are emitted
27276
- *
27277
- * // Arrays that would become empty after filtering are discarded entirely
27278
- * const oddChannel = Channel.fromIterable([
27279
- * Array.make(1, 3, 5),
27280
- * Array.make(2, 4),
27281
- * Array.make(7, 9)
27282
- * ]).pipe(Channel.filter(nonEmptyArrayPredicate))
27283
- * const filteredOddChannel = Channel.filterArray(oddChannel, (n) => n % 2 === 0)
27284
- * // Outputs: [2, 4] (the arrays [1,3,5] and [7,9] are discarded)
27285
- * ```
27286
- *
27287
- * @category filtering
27288
- * @since 4.0.0
27289
- */
27290
- const filterArray = /*#__PURE__*/ dual(2, (self, predicate) => transformPull$1(self, (pull) => succeed$3(flatMap$2(pull, function loop(arr) {
27291
- const passes = [];
27292
- for (let i = 0; i < arr.length; i++) if (predicate(arr[i])) passes.push(arr[i]);
27293
- return isReadonlyArrayNonEmpty(passes) ? succeed$3(passes) : flatMap$2(pull, loop);
27294
- }))));
27295
- /**
27296
27294
  * Filters and maps each element inside emitted non-empty arrays using a
27297
27295
  * `Filter`.
27298
27296
  *
@@ -27787,6 +27785,35 @@ const provideService = /*#__PURE__*/ dual(3, (self, key, service) => fromTransfo
27787
27785
  */
27788
27786
  const provideServiceEffect = /*#__PURE__*/ dual(3, (self, key, service) => fromTransform$1((upstream, scope) => flatMap$2(service, (s) => toTransform(provideService(self, key, s))(upstream, scope))));
27789
27787
  /**
27788
+ * Runs a channel and discards all output elements, returning only the final result.
27789
+ *
27790
+ * **Example** (Draining channel output at runtime)
27791
+ *
27792
+ * ```ts
27793
+ * import { Channel, Data } from "effect"
27794
+ *
27795
+ * class DrainError extends Data.TaggedError("DrainError")<{
27796
+ * readonly stage: string
27797
+ * }> {}
27798
+ *
27799
+ * // Create a channel that outputs elements and completes with a result
27800
+ * const resultChannel = Channel.fromIterable([1, 2, 3])
27801
+ * const completedChannel = Channel.concatWith(
27802
+ * resultChannel,
27803
+ * () => Channel.succeed("completed")
27804
+ * )
27805
+ *
27806
+ * // Drain all elements and get only the final result
27807
+ * const drainEffect = Channel.runDrain(completedChannel)
27808
+ *
27809
+ * // Effect.runSync(drainEffect) // Returns: "completed"
27810
+ * ```
27811
+ *
27812
+ * @category execution
27813
+ * @since 2.0.0
27814
+ */
27815
+ const runDrain$1 = (self) => runWith(self, (pull) => forever(pull, { disableYield: true }));
27816
+ /**
27790
27817
  * Runs a channel and applies an effect to each output element.
27791
27818
  *
27792
27819
  * **Example** (Running effects for each output)
@@ -28268,7 +28295,7 @@ const getFromBucket = (self, bucket, key) => {
28268
28295
  * @category elements
28269
28296
  * @since 2.0.0
28270
28297
  */
28271
- const has$4 = /*#__PURE__*/ dual(2, (self, key) => isSome(get$7(self, key)));
28298
+ const has$3 = /*#__PURE__*/ dual(2, (self, key) => isSome(get$7(self, key)));
28272
28299
  /**
28273
28300
  * Sets a key-value pair in the MutableHashMap, mutating the map in place.
28274
28301
  * If the key already exists, its value is updated.
@@ -28655,7 +28682,7 @@ const get$6 = /*#__PURE__*/ dual(2, (self, key) => uninterruptibleMask((restore)
28655
28682
  const release = (self, key, entry) => withFiber((fiber) => {
28656
28683
  entry.refCount--;
28657
28684
  if (entry.refCount > 0) return void_$1;
28658
- else if (self.state._tag === "Closed" || !has$4(self.state.map, key) || isZero$1(entry.idleTimeToLive)) {
28685
+ else if (self.state._tag === "Closed" || !has$3(self.state.map, key) || isZero$1(entry.idleTimeToLive)) {
28659
28686
  if (self.state._tag === "Open") remove$6(self.state.map, key);
28660
28687
  return close(entry.scope, void_$2);
28661
28688
  } else if (!isFinite$2(entry.idleTimeToLive)) return void_$1;
@@ -29762,30 +29789,6 @@ const concat = /*#__PURE__*/ dual(2, (self, that) => flatten(fromArray([self, th
29762
29789
  */
29763
29790
  const merge$1 = /*#__PURE__*/ dual((args) => isStream(args[0]) && isStream(args[1]), (self, that, options) => fromChannel(merge$2(toChannel(self), toChannel(that), options)));
29764
29791
  /**
29765
- * Filters a stream to the elements that satisfy a predicate.
29766
- *
29767
- * **Example** (Filtering stream values)
29768
- *
29769
- * ```ts
29770
- * import { Console, Effect, Stream } from "effect"
29771
- *
29772
- * const program = Effect.gen(function*() {
29773
- * const stream = Stream.make(1, 2, 3, 4).pipe(
29774
- * Stream.filter((n) => n % 2 === 0)
29775
- * )
29776
- * const values = yield* Stream.runCollect(stream)
29777
- * yield* Console.log(values)
29778
- * })
29779
- *
29780
- * Effect.runPromise(program)
29781
- * // Output: [ 2, 4 ]
29782
- * ```
29783
- *
29784
- * @category filtering
29785
- * @since 2.0.0
29786
- */
29787
- const filter$3 = /*#__PURE__*/ dual(2, (self, predicate) => fromChannel(filterArray(toChannel(self), predicate)));
29788
- /**
29789
29792
  * Filters and maps stream elements in one pass using a `Filter`.
29790
29793
  *
29791
29794
  * **When to use**
@@ -30380,6 +30383,32 @@ const runForEach = /*#__PURE__*/ dual(2, (self, f) => runForEach$1(self.channel,
30380
30383
  */
30381
30384
  const runForEachArray = /*#__PURE__*/ dual(2, (self, f) => runForEach$1(self.channel, f));
30382
30385
  /**
30386
+ * Runs the stream for its effects, discarding emitted elements.
30387
+ *
30388
+ * **Example** (Draining a stream run)
30389
+ *
30390
+ * ```ts
30391
+ * import { Console, Effect, Stream } from "effect"
30392
+ *
30393
+ * const program = Effect.gen(function*() {
30394
+ * const stream = Stream.make(1, 2, 3).pipe(
30395
+ * Stream.mapEffect((n) => Console.log(`Processing: ${n}`))
30396
+ * )
30397
+ *
30398
+ * yield* Stream.runDrain(stream)
30399
+ * })
30400
+ *
30401
+ * Effect.runPromise(program)
30402
+ * // Processing: 1
30403
+ * // Processing: 2
30404
+ * // Processing: 3
30405
+ * ```
30406
+ *
30407
+ * @category destructors
30408
+ * @since 2.0.0
30409
+ */
30410
+ const runDrain = (self) => runDrain$1(self.channel);
30411
+ /**
30383
30412
  * Concatenates all emitted strings into a single string.
30384
30413
  *
30385
30414
  * **Example** (Joining strings from a stream)
@@ -31777,24 +31806,11 @@ const isHashMap = (u) => hasProperty(u, HashMapTypeId);
31777
31806
  /** @internal */
31778
31807
  const empty$9 = () => new HashMapImpl(false, 0, emptyNode, 0);
31779
31808
  /** @internal */
31780
- const fromIterable$4 = (entries) => {
31781
- let root = emptyNode;
31782
- let size = 0;
31783
- const added = { value: false };
31784
- for (const [key, value] of entries) {
31785
- const hash$1 = hash(key);
31786
- added.value = false;
31787
- root = root.set(NaN, 0, hash$1, key, value, added);
31788
- if (added.value) size++;
31789
- }
31790
- return new HashMapImpl(false, 0, root, size);
31791
- };
31792
- /** @internal */
31793
31809
  const get$3 = /*#__PURE__*/ dual(2, (self, key) => {
31794
31810
  return self._root.get(0, hash(key), key);
31795
31811
  });
31796
31812
  /** @internal */
31797
- const has$3 = /*#__PURE__*/ dual(2, (self, key) => {
31813
+ const has$2 = /*#__PURE__*/ dual(2, (self, key) => {
31798
31814
  return self._root.has(0, hash(key), key);
31799
31815
  });
31800
31816
  /** @internal */
@@ -31857,7 +31873,7 @@ const size$1 = (self) => self.size;
31857
31873
  /** @internal */
31858
31874
  const modifyAt = /*#__PURE__*/ dual(3, (self, key, f) => {
31859
31875
  const updated = f(get$3(self, key));
31860
- if (isNone(updated)) return has$3(self, key) ? remove$5(self, key) : self;
31876
+ if (isNone(updated)) return has$2(self, key) ? remove$5(self, key) : self;
31861
31877
  return set$4(self, key, updated.value);
31862
31878
  });
31863
31879
  /** @internal */
@@ -31980,24 +31996,6 @@ const filter$2 = /*#__PURE__*/ dual(2, (self, f) => {
31980
31996
  */
31981
31997
  const empty$8 = empty$9;
31982
31998
  /**
31983
- * Creates a new `HashMap` from an iterable collection of key/value pairs.
31984
- *
31985
- * **Example** (Creating a HashMap from an iterable)
31986
- *
31987
- * ```ts
31988
- * import { HashMap } from "effect"
31989
- *
31990
- * const entries = [["a", 1], ["b", 2], ["c", 3]] as const
31991
- * const map = HashMap.fromIterable(entries)
31992
- * console.log(HashMap.size(map)) // 3
31993
- * console.log(HashMap.get(map, "a")) // Option.some(1)
31994
- * ```
31995
- *
31996
- * @category constructors
31997
- * @since 2.0.0
31998
- */
31999
- const fromIterable$3 = fromIterable$4;
32000
- /**
32001
31999
  * Looks up the value for the specified key in the `HashMap` safely using the
32002
32000
  * internal hashing function.
32003
32001
  *
@@ -32021,28 +32019,6 @@ const fromIterable$3 = fromIterable$4;
32021
32019
  */
32022
32020
  const get$2 = get$3;
32023
32021
  /**
32024
- * Checks whether the specified key has an entry in the `HashMap`.
32025
- *
32026
- * **Example** (Checking for keys)
32027
- *
32028
- * ```ts
32029
- * import { HashMap } from "effect"
32030
- *
32031
- * const map = HashMap.make(["a", 1], ["b", 2])
32032
- *
32033
- * console.log(HashMap.has(map, "a")) // true
32034
- * console.log(HashMap.has(map, "c")) // false
32035
- *
32036
- * // Using pipe syntax
32037
- * const hasB = HashMap.has("b")(map)
32038
- * console.log(hasB) // true
32039
- * ```
32040
- *
32041
- * @category elements
32042
- * @since 2.0.0
32043
- */
32044
- const has$2 = has$3;
32045
- /**
32046
32022
  * Sets the specified key to the specified value using the internal hashing
32047
32023
  * function.
32048
32024
  *
@@ -32195,20 +32171,26 @@ const fromIterable$2 = (values) => {
32195
32171
  return makeImpl(map);
32196
32172
  };
32197
32173
  /** @internal */
32198
- const has$1 = (self, value) => has$3(keyMap(self), value);
32174
+ const has$1 = (self, value) => has$2(keyMap(self), value);
32199
32175
  /** @internal */
32200
32176
  const add$1 = (self, value) => {
32201
32177
  const map = keyMap(self);
32202
- return has$3(map, value) ? self : makeImpl(set$4(map, value, true));
32178
+ return has$2(map, value) ? self : makeImpl(set$4(map, value, true));
32203
32179
  };
32204
32180
  /** @internal */
32205
32181
  const remove$3 = (self, value) => {
32206
32182
  const map = keyMap(self);
32207
- return has$3(map, value) ? makeImpl(remove$5(map, value)) : self;
32183
+ return has$2(map, value) ? makeImpl(remove$5(map, value)) : self;
32208
32184
  };
32209
32185
  /** @internal */
32210
32186
  const size = (self) => size$1(keyMap(self));
32211
32187
  /** @internal */
32188
+ const union$1 = (self, that) => {
32189
+ let result = keyMap(self);
32190
+ for (const value of that) result = set$4(result, value, true);
32191
+ return makeImpl(result);
32192
+ };
32193
+ /** @internal */
32212
32194
  const every = (self, predicate) => {
32213
32195
  for (const value of self) if (!predicate(value)) return false;
32214
32196
  return true;
@@ -32382,6 +32364,26 @@ const has = /*#__PURE__*/ dual(2, has$1);
32382
32364
  * @since 2.0.0
32383
32365
  */
32384
32366
  const remove$2 = /*#__PURE__*/ dual(2, remove$3);
32367
+ /**
32368
+ * Creates the union of two HashSets.
32369
+ *
32370
+ * **Example** (Combining HashSets)
32371
+ *
32372
+ * ```ts
32373
+ * import { HashSet } from "effect"
32374
+ *
32375
+ * const set1 = HashSet.make("a", "b")
32376
+ * const set2 = HashSet.make("b", "c")
32377
+ * const combined = HashSet.union(set1, set2)
32378
+ *
32379
+ * console.log(Array.from(combined).sort()) // ["a", "b", "c"]
32380
+ * console.log(HashSet.size(combined)) // 3
32381
+ * ```
32382
+ *
32383
+ * @category combinators
32384
+ * @since 2.0.0
32385
+ */
32386
+ const union = /*#__PURE__*/ dual(2, union$1);
32385
32387
  //#endregion
32386
32388
  //#region ../../node_modules/.pnpm/effect@4.0.0-beta.74/node_modules/effect/dist/Struct.js
32387
32389
  /**
@@ -33517,7 +33519,7 @@ function makeUnion(ast, members) {
33517
33519
  members,
33518
33520
  mapMembers(f, options) {
33519
33521
  const members = f(this.members);
33520
- return makeUnion(union(members, this.ast.mode, options?.unsafePreserveChecks ? this.ast.checks : void 0), members);
33522
+ return makeUnion(union$2(members, this.ast.mode, options?.unsafePreserveChecks ? this.ast.checks : void 0), members);
33521
33523
  }
33522
33524
  });
33523
33525
  }
@@ -33546,7 +33548,7 @@ function makeUnion(ast, members) {
33546
33548
  * @since 3.10.0
33547
33549
  */
33548
33550
  function Union$1(members, options) {
33549
- return makeUnion(union(members, options?.mode ?? "anyOf", void 0), members);
33551
+ return makeUnion(union$2(members, options?.mode ?? "anyOf", void 0), members);
33550
33552
  }
33551
33553
  /**
33552
33554
  * Creates a union schema from an array of literal values.
@@ -33566,7 +33568,7 @@ function Union$1(members, options) {
33566
33568
  */
33567
33569
  function Literals(literals) {
33568
33570
  const members = literals.map(Literal$2);
33569
- return make$38(union(members, "anyOf", void 0), {
33571
+ return make$38(union$2(members, "anyOf", void 0), {
33570
33572
  literals,
33571
33573
  members,
33572
33574
  mapMembers(f) {
@@ -36730,7 +36732,7 @@ const fromInput = (input) => {
36730
36732
  return make$34(out);
36731
36733
  };
36732
36734
  const fromInputNested = (input) => {
36733
- const entries = typeof input[Symbol.iterator] === "function" ? fromIterable$6(input) : Object.entries(input);
36735
+ const entries = typeof input[Symbol.iterator] === "function" ? fromIterable$4(input) : Object.entries(input);
36734
36736
  const out = [];
36735
36737
  for (const [key, value] of entries) if (Array.isArray(value)) {
36736
36738
  for (let i = 0; i < value.length; i++) if (value[i] !== void 0) out.push([key, String(value[i])]);
@@ -38793,7 +38795,7 @@ const make$30 = () => acquireRelease(sync(() => makeUnsafe(/* @__PURE__ */ new S
38793
38795
  return interruptAll(fibers).pipe(into(set.deferred));
38794
38796
  }));
38795
38797
  const internalFiberId = -1;
38796
- const isInternalInterruption = /*#__PURE__*/ toPredicate(/*#__PURE__*/ compose(filterInterruptors, /*#__PURE__*/ has$5(internalFiberId)));
38798
+ const isInternalInterruption = /*#__PURE__*/ toPredicate(/*#__PURE__*/ compose(filterInterruptors, /*#__PURE__*/ has$4(internalFiberId)));
38797
38799
  /**
38798
38800
  * Adds an existing fiber to the `FiberSet` using a synchronous, unsafe
38799
38801
  * mutation.
@@ -50464,7 +50466,7 @@ const getMiddleware = (context) => {
50464
50466
  else {
50465
50467
  const middleware = /* @__PURE__ */ new Set();
50466
50468
  for (let i = maxLength - 1; i >= 0; i--) for (const arr of topLevel) if (i < arr.length) middleware.add(arr[i]);
50467
- arr = fromIterable$6(middleware).reverse();
50469
+ arr = fromIterable$4(middleware).reverse();
50468
50470
  }
50469
50471
  middlewareCache.set(context, arr);
50470
50472
  return arr;
@@ -54785,27 +54787,25 @@ const GitDiffStatus = Literals([
54785
54787
  "modified",
54786
54788
  "renamed"
54787
54789
  ]);
54788
- const GitDiffSegment = Struct({
54789
- filePath: String$3,
54790
- fingerprint: String$3,
54791
- id: String$3,
54792
- type: Literals(["commit", "worktree"])
54793
- });
54794
54790
  const GitDiff = Struct({
54791
+ changeHash: String$3,
54795
54792
  fileContent: optional(String$3),
54796
54793
  filePath: String$3,
54797
- patch: String$3,
54798
- segments: ArraySchema(GitDiffSegment),
54794
+ patch: optional(String$3),
54799
54795
  status: GitDiffStatus
54800
54796
  });
54801
54797
  const GitReviewChangesTarget = TaggedStruct("changes", {});
54798
+ const GitReviewLocalTarget = TaggedStruct("local", {});
54799
+ const GitReviewBranchTarget = TaggedStruct("branch", {});
54800
+ const GitReviewCommitTarget = TaggedStruct("commit", { hash: String$3 });
54802
54801
  const GitReviewTarget = Union$1([
54803
54802
  GitReviewChangesTarget,
54804
- TaggedStruct("local", {}),
54805
- TaggedStruct("branch", {}),
54806
- TaggedStruct("commit", { hash: String$3 })
54803
+ GitReviewLocalTarget,
54804
+ GitReviewBranchTarget,
54805
+ GitReviewCommitTarget
54807
54806
  ]);
54808
54807
  const GitCommit = Struct({
54808
+ checkpoint: Boolean$2,
54809
54809
  hash: String$3,
54810
54810
  shortHash: String$3,
54811
54811
  subject: String$3
@@ -54815,7 +54815,6 @@ const GitReviewMetadata = Struct({
54815
54815
  branchCommits: ArraySchema(GitCommit),
54816
54816
  dirty: Boolean$2,
54817
54817
  localCommits: ArraySchema(GitCommit),
54818
- prUrl: optional(String$3),
54819
54818
  unpushedCommits: Boolean$2,
54820
54819
  upstream: optional(Struct({
54821
54820
  ahead: Number$3,
@@ -54823,17 +54822,21 @@ const GitReviewMetadata = Struct({
54823
54822
  }))
54824
54823
  });
54825
54824
  const GitReviewMark = Struct({
54825
+ changeHash: String$3,
54826
+ filePath: String$3
54827
+ });
54828
+ const GitReviewCommentDraft = Struct({
54829
+ body: String$3,
54826
54830
  filePath: String$3,
54827
- fingerprint: String$3,
54828
- segmentId: String$3
54831
+ lineNumber: Number$3,
54832
+ side: optional(Literals(["additions", "deletions"]))
54829
54833
  });
54830
54834
  const GitReviewComment = Struct({
54831
54835
  body: String$3,
54832
54836
  filePath: String$3,
54833
54837
  lineNumber: Number$3,
54834
- resolved: Boolean$2,
54835
54838
  side: optional(Literals(["additions", "deletions"])),
54836
- source: optional(Literals(["local", "github"])),
54839
+ source: Literals(["local", "github"]),
54837
54840
  threadId: optional(String$3),
54838
54841
  url: optional(String$3)
54839
54842
  });
@@ -54867,48 +54870,20 @@ const GitProject = Struct({
54867
54870
  repository: GitRepository,
54868
54871
  worktrees: ArraySchema(GitWorktree)
54869
54872
  });
54870
- function gitReviewStateSaveComment(state, comment) {
54873
+ function gitReviewStateSaveComment(state, draft) {
54874
+ const comment = GitReviewComment.make({
54875
+ ...draft,
54876
+ source: "local"
54877
+ });
54871
54878
  return GitReviewState.make({
54872
- comments: append$1(filter$5(state.comments, (currentComment) => !equals$2({
54873
- filePath: currentComment.filePath,
54874
- lineNumber: currentComment.lineNumber,
54875
- side: currentComment.side ?? "additions",
54876
- source: currentComment.source ?? "local",
54877
- threadId: currentComment.threadId ?? ""
54878
- }, {
54879
- filePath: comment.filePath,
54880
- lineNumber: comment.lineNumber,
54881
- side: comment.side ?? "additions",
54882
- source: comment.source ?? "local",
54883
- threadId: comment.threadId ?? ""
54884
- })), GitReviewComment.make({
54885
- ...comment,
54886
- resolved: false,
54887
- source: "local",
54888
- threadId: void 0,
54889
- url: void 0
54890
- })),
54879
+ comments: append$1(filter$4(state.comments, (currentComment) => !sameCommentIdentity(currentComment, comment)), comment),
54891
54880
  marks: state.marks
54892
54881
  });
54893
54882
  }
54894
- function gitReviewStateResolveComment(state, input) {
54883
+ function gitReviewStateDeleteComments(state, comments) {
54884
+ const deletedThreadIds = pipe(comments, filter$4((comment) => comment.source === "github" && isString(comment.threadId)), map$7((comment) => comment.threadId ?? ""), fromIterable$1);
54895
54885
  return GitReviewState.make({
54896
- comments: map$7(state.comments, (comment) => equals$2({
54897
- filePath: comment.filePath,
54898
- lineNumber: comment.lineNumber,
54899
- side: comment.side ?? "additions",
54900
- source: comment.source ?? "local",
54901
- threadId: comment.threadId ?? ""
54902
- }, {
54903
- filePath: input.filePath,
54904
- lineNumber: input.lineNumber,
54905
- side: input.side ?? "additions",
54906
- source: "local",
54907
- threadId: ""
54908
- }) ? GitReviewComment.make({
54909
- ...comment,
54910
- resolved: true
54911
- }) : comment),
54886
+ comments: filter$4(state.comments, (comment) => comment.source === "github" && has(deletedThreadIds, comment.threadId ?? "") ? false : every$1(comments, (deletedComment) => !sameCommentIdentity(comment, deletedComment))),
54912
54887
  marks: state.marks
54913
54888
  });
54914
54889
  }
@@ -54916,14 +54891,29 @@ function gitReviewStateMark(state, marks) {
54916
54891
  const reviewed = fromIterable$1(marks);
54917
54892
  return GitReviewState.make({
54918
54893
  comments: state.comments,
54919
- marks: appendAll(filter$5(state.marks, (mark) => !has(reviewed, mark)), marks)
54894
+ marks: appendAll(filter$4(state.marks, (mark) => !has(reviewed, mark)), marks)
54920
54895
  });
54921
54896
  }
54922
54897
  function gitReviewStateUnmark(state, marks) {
54923
54898
  const reviewed = fromIterable$1(marks);
54924
54899
  return GitReviewState.make({
54925
54900
  comments: state.comments,
54926
- marks: filter$5(state.marks, (mark) => !has(reviewed, mark))
54901
+ marks: filter$4(state.marks, (mark) => !has(reviewed, mark))
54902
+ });
54903
+ }
54904
+ function sameCommentIdentity(left, right) {
54905
+ return equals$2({
54906
+ filePath: left.filePath,
54907
+ lineNumber: left.lineNumber,
54908
+ side: left.side ?? "additions",
54909
+ source: left.source,
54910
+ threadId: left.threadId ?? ""
54911
+ }, {
54912
+ filePath: right.filePath,
54913
+ lineNumber: right.lineNumber,
54914
+ side: right.side ?? "additions",
54915
+ source: right.source,
54916
+ threadId: right.threadId ?? ""
54927
54917
  });
54928
54918
  }
54929
54919
  //#endregion
@@ -55065,6 +55055,9 @@ var RpcContracts = class extends make$5(make$6("agents.create", {
55065
55055
  error: GitError,
55066
55056
  payload: CwdPayload,
55067
55057
  success: GitBranchesSnapshot
55058
+ }), make$6("projects.maintenance", {
55059
+ error: GitError,
55060
+ payload: CwdPayload
55068
55061
  }), make$6("review.metadata", {
55069
55062
  error: GitError,
55070
55063
  payload: CwdPayload,
@@ -55098,25 +55091,30 @@ var RpcContracts = class extends make$5(make$6("agents.create", {
55098
55091
  }), make$6("review.comments.save", {
55099
55092
  error: GitError,
55100
55093
  payload: Struct({
55101
- comment: GitReviewComment,
55094
+ comment: GitReviewCommentDraft,
55102
55095
  cwd: String$3
55103
55096
  })
55104
55097
  }), make$6("review.comments.resolve", {
55105
55098
  error: GitError,
55106
55099
  payload: Struct({
55107
- cwd: String$3,
55108
- filePath: String$3,
55109
- lineNumber: Number$3,
55110
- side: optional(Literals(["additions", "deletions"])),
55111
- threadId: optional(String$3)
55100
+ comments: ArraySchema(GitReviewComment),
55101
+ cwd: String$3
55112
55102
  })
55113
- }), make$6("publish.approve", {
55103
+ }), make$6("publish.checkpoint", {
55104
+ error: GitError,
55105
+ payload: CwdPayload
55106
+ }), make$6("publish.publish", {
55114
55107
  error: GitError,
55115
55108
  payload: Struct({
55116
55109
  cwd: String$3,
55117
55110
  message: String$3
55118
55111
  }),
55119
55112
  success: optional(GitPullRequest)
55113
+ }), make$6("publish.pullRequest", {
55114
+ error: GitError,
55115
+ payload: CwdPayload,
55116
+ stream: true,
55117
+ success: optional(GitPullRequest)
55120
55118
  }), make$6("publish.message.generate", {
55121
55119
  error: PublishDraftError,
55122
55120
  payload: CwdPayload,
@@ -55132,9 +55130,6 @@ var RpcContracts = class extends make$5(make$6("agents.create", {
55132
55130
  }), make$6("projects.deleteWorktree", {
55133
55131
  error: GitError,
55134
55132
  payload: CwdPayload
55135
- }), make$6("projects.fix", {
55136
- error: GitError,
55137
- payload: CwdPayload
55138
55133
  }), make$6("runs.portless", {
55139
55134
  error: TerminalError,
55140
55135
  payload: CwdPayload,
@@ -55286,7 +55281,7 @@ const ClaudeTokenLine = fromJsonString(Struct({
55286
55281
  const claudeJsonlFiles = fnUntraced(function* (root) {
55287
55282
  const fs = yield* FileSystem;
55288
55283
  if (!(yield* fs.exists(root))) return [];
55289
- return pipe(yield* fs.readDirectory(root, { recursive: true }), filter$5((entry) => endsWith(".jsonl")(entry)), map$7((entry) => join(root, entry)));
55284
+ return pipe(yield* fs.readDirectory(root, { recursive: true }), filter$4((entry) => endsWith(".jsonl")(entry)), map$7((entry) => join(root, entry)));
55290
55285
  });
55291
55286
  function claudeUsageTokens(input) {
55292
55287
  return {
@@ -55381,7 +55376,7 @@ const makeLayerClaudeUsage = fnUntraced(function* (_config) {
55381
55376
  });
55382
55377
  function claudeSubscriptionLabel(value) {
55383
55378
  if (!isString(value)) return;
55384
- return pipe(value, trim, split$1(/[\s_-]+/u), filter$5((token) => isNonEmpty$1(token) && toLowerCase(token) !== "default" && toLowerCase(token) !== "claude"), map$7((token) => /^\d+x$/u.test(toLowerCase(token)) ? toLowerCase(token) : capitalize(token)), (tokens) => isReadonlyArrayEmpty(tokens) ? void 0 : join$4(tokens, " "));
55379
+ return pipe(value, trim, split$1(/[\s_-]+/u), filter$4((token) => isNonEmpty$1(token) && toLowerCase(token) !== "default" && toLowerCase(token) !== "claude"), map$7((token) => /^\d+x$/u.test(toLowerCase(token)) ? toLowerCase(token) : capitalize(token)), (tokens) => isReadonlyArrayEmpty(tokens) ? void 0 : join$4(tokens, " "));
55385
55380
  }
55386
55381
  function claudeWindow(input) {
55387
55382
  return {
@@ -55435,7 +55430,7 @@ const CodexTokenLine = fromJsonString(Struct({
55435
55430
  const codexJsonlFiles = fnUntraced(function* (root) {
55436
55431
  const fs = yield* FileSystem;
55437
55432
  if (!(yield* fs.exists(root))) return [];
55438
- return pipe(yield* fs.readDirectory(root, { recursive: true }), filter$5((entry) => endsWith(".jsonl")(entry)), map$7((entry) => join(root, entry)));
55433
+ return pipe(yield* fs.readDirectory(root, { recursive: true }), filter$4((entry) => endsWith(".jsonl")(entry)), map$7((entry) => join(root, entry)));
55439
55434
  });
55440
55435
  function codexUsageTokens(input) {
55441
55436
  return {
@@ -55537,7 +55532,7 @@ const makeLayerCodexUsage = fnUntraced(function* (_config) {
55537
55532
  });
55538
55533
  function codexSubscriptionLabel(planType) {
55539
55534
  if (!isString(planType)) return;
55540
- return pipe(planType, trim, split$1(/[\s_-]+/u), filter$5((token) => isNonEmpty$1(token) && toLowerCase(token) !== "default"), map$7((token) => /^\d+x$/u.test(toLowerCase(token)) ? toLowerCase(token) : capitalize(token)), (tokens) => isReadonlyArrayEmpty(tokens) ? void 0 : join$4(tokens, " "));
55535
+ return pipe(planType, trim, split$1(/[\s_-]+/u), filter$4((token) => isNonEmpty$1(token) && toLowerCase(token) !== "default"), map$7((token) => /^\d+x$/u.test(toLowerCase(token)) ? toLowerCase(token) : capitalize(token)), (tokens) => isReadonlyArrayEmpty(tokens) ? void 0 : join$4(tokens, " "));
55541
55536
  }
55542
55537
  function codexWindow(input) {
55543
55538
  return {
@@ -254125,7 +254120,7 @@ const piMessagesFromPromptHistory = fnUntraced(function* (messages, input, curre
254125
254120
  })), when({ role: "assistant" }, (assistantMessage) => gen(function* () {
254126
254121
  const content = yield* piAssistantContentFromPromptParts(assistantMessage.content);
254127
254122
  if (isUndefined(content)) return yield* new AiError({ message: "Pi agent does not support assistant file or approval prompt parts" });
254128
- const toolResults = yield* piToolResultsFromPromptParts(filter$5(assistantMessage.content, (part) => part.type === "tool-result"));
254123
+ const toolResults = yield* piToolResultsFromPromptParts(filter$4(assistantMessage.content, (part) => part.type === "tool-result"));
254129
254124
  return [{
254130
254125
  api: "openai-codex-responses",
254131
254126
  content,
@@ -254326,8 +254321,8 @@ const makeLayerPi = fnUntraced(function* (config) {
254326
254321
  message: "agent prompt failed"
254327
254322
  }),
254328
254323
  try: () => {
254329
- const text = pipe(current.prompt.prompt, filter$5((part) => part.type === "text"), map$7((part) => part.text), join$4("\n\n"));
254330
- const images = filter$5(current.prompt.prompt, (part) => part.type === "image");
254324
+ const text = pipe(current.prompt.prompt, filter$4((part) => part.type === "text"), map$7((part) => part.text), join$4("\n\n"));
254325
+ const images = filter$4(current.prompt.prompt, (part) => part.type === "image");
254331
254326
  return current.session.prompt(text, { images });
254332
254327
  }
254333
254328
  }), catch_$2((error) => pipe(set$1(finished, true), andThen(setStatus("error")), andThen(offer(queue, makePart("error", { error: error.message }))), andThen(end$1(queue)))));
@@ -254396,7 +254391,7 @@ var GitCommand = class extends Service()("@deslop/git/service/GitCommand", { mak
254396
254391
  });
254397
254392
  return {
254398
254393
  lines: fn("GitCommand.lines")(function* (cwd, args) {
254399
- return pipe(yield* string(cwd, args), split$1(/\r?\n/u), filter$5(isNonEmpty$1));
254394
+ return pipe(yield* string(cwd, args), split$1(/\r?\n/u), filter$4(isNonEmpty$1));
254400
254395
  }),
254401
254396
  string,
254402
254397
  stringWithInput
@@ -254439,19 +254434,13 @@ const reviewExclusionPathspecs = [
254439
254434
  ":(exclude)plans/*.md",
254440
254435
  ":(exclude)**/plans/*.md"
254441
254436
  ];
254442
- function isReviewExcludedPath(filePath) {
254443
- const parts = split$1("/")(filePath);
254444
- const basename = parts.at(-1) ?? filePath;
254445
- if (filePath === "pnpm-lock.yaml") return true;
254446
- if (endsWith(".gen.ts")(basename)) return true;
254447
- for (const index of range$1(0, parts.length - 2)) {
254448
- const current = parts[index];
254449
- const next = parts[index + 1];
254450
- if (current === "components" && (next === "ui" || next === "svgs")) return true;
254451
- if (current === "plans" && index === parts.length - 2 && endsWith(".md")(basename)) return true;
254452
- }
254453
- return false;
254454
- }
254437
+ const reviewDiffFlags = [
254438
+ "--ignore-all-space",
254439
+ "--ignore-blank-lines",
254440
+ "--ignore-cr-at-eol"
254441
+ ];
254442
+ const checkpointCommit = { trailer: "Deslop-Checkpoint: true" };
254443
+ const emptyGitPullRequests = [];
254455
254444
  const GitHubPullRequestViewResponse = Struct({ url: optional(String$3) });
254456
254445
  const GitHubRepositoryResponse = Struct({
254457
254446
  name: String$3,
@@ -254469,9 +254458,6 @@ const GitHubReviewThreadsResponse = Struct({ data: optional(Struct({ repository:
254469
254458
  id: String$3,
254470
254459
  isResolved: Boolean$2
254471
254460
  })) })) })) })) })) });
254472
- const emptyGitPullRequests = [];
254473
- const emptyGitReviewCommentLists = [];
254474
- const emptyStrings = [];
254475
254461
  function normalizePublicPath(value) {
254476
254462
  return startsWith$1("/private/var/")(value) ? replace(/^\/private/u, "")(value) : value;
254477
254463
  }
@@ -254507,79 +254493,135 @@ function sameProjectSnapshot(left, right) {
254507
254493
  });
254508
254494
  }
254509
254495
  function repositoryProbeOnlyPermissionErrors(stderr) {
254510
- const lines = pipe(stderr, split$1(/\r?\n/u), filter$5(isNonEmpty$1));
254496
+ const lines = pipe(stderr, split$1(/\r?\n/u), filter$4(isNonEmpty$1));
254511
254497
  return !isReadonlyArrayEmpty(lines) && every$1(lines, (line) => repositoryProbePermissionError.test(line));
254512
254498
  }
254513
- function segmentsByFile(segments) {
254514
- return reduce(segments, empty$8(), (groups, segment) => set$3(groups, segment.filePath, pipe(get$2(groups, segment.filePath), getOrElse$1(() => empty$14()), append$1(segment))));
254499
+ function parseWorktreeFields(fields, current, records) {
254500
+ const field = fields[0];
254501
+ if (isUndefined(field)) return isNonEmpty$1(current.root) && current.hasHead ? append$1(records, current) : records;
254502
+ const rest = drop(fields, 1);
254503
+ if (startsWith$1("worktree ")(field)) return parseWorktreeFields(rest, {
254504
+ branch: "",
254505
+ hasHead: false,
254506
+ root: replace(/^worktree\s+/u, "")(field)
254507
+ }, isNonEmpty$1(current.root) && current.hasHead ? append$1(records, current) : records);
254508
+ if (startsWith$1("HEAD ")(field)) return parseWorktreeFields(rest, {
254509
+ ...current,
254510
+ hasHead: true
254511
+ }, records);
254512
+ if (startsWith$1("branch refs/heads/")(field)) return parseWorktreeFields(rest, {
254513
+ ...current,
254514
+ branch: replace(/^branch\s+refs\/heads\//u, "")(field)
254515
+ }, records);
254516
+ return parseWorktreeFields(rest, current, records);
254517
+ }
254518
+ function parseWorktreeRecords(output) {
254519
+ return parseWorktreeFields(split$1("\0")(output), {
254520
+ branch: "",
254521
+ hasHead: false,
254522
+ root: ""
254523
+ }, []);
254515
254524
  }
254516
- function diffFromPatchChunk(chunk, segments) {
254525
+ function isReviewExcludedPath(filePath) {
254526
+ const parts = split$1("/")(filePath);
254527
+ const basename = parts.at(-1) ?? filePath;
254528
+ if (filePath === "pnpm-lock.yaml") return true;
254529
+ if (endsWith(".gen.ts")(basename)) return true;
254530
+ for (const index of range$1(0, parts.length - 2)) {
254531
+ const current = parts[index];
254532
+ const next = parts[index + 1];
254533
+ if (current === "components" && (next === "ui" || next === "svgs")) return true;
254534
+ if (current === "plans" && index === parts.length - 2 && endsWith(".md")(basename)) return true;
254535
+ }
254536
+ return false;
254537
+ }
254538
+ function changeHash(value) {
254539
+ return createHash("sha256").update(value).digest("hex");
254540
+ }
254541
+ function patchPathspec() {
254542
+ return [
254543
+ "--",
254544
+ ".",
254545
+ ...reviewExclusionPathspecs
254546
+ ];
254547
+ }
254548
+ function diffFromPatchChunk(chunk) {
254517
254549
  const deleted = /^deleted file mode /mu.test(chunk);
254518
254550
  const filePath = (deleted ? chunk.match(/^--- a\/(.+)$/mu)?.[1] : void 0) ?? chunk.match(/^\+\+\+ b\/(.+)$/mu)?.[1] ?? chunk.match(/^--- a\/(.+)$/mu)?.[1] ?? chunk.match(/^diff --git a\/.+ b\/(.+)$/mu)?.[1] ?? "";
254519
254551
  const status = value$1(chunk).pipe(when((value) => /^new file mode /mu.test(value), () => "added"), when(() => deleted, () => "deleted"), when((value) => /^rename (from|to) /mu.test(value), () => "renamed"), orElse(() => "modified"));
254520
254552
  return GitDiff.make({
254553
+ changeHash: changeHash(chunk),
254521
254554
  filePath,
254522
254555
  patch: chunk,
254523
- segments: getOrElse$1(get$2(segments, filePath), () => empty$14()),
254524
254556
  status
254525
254557
  });
254526
254558
  }
254527
- function withDisplayedPatchSegments(diffs, id, type) {
254528
- return map$7(diffs, (diff) => GitDiff.make({
254529
- fileContent: diff.fileContent,
254530
- filePath: diff.filePath,
254531
- patch: diff.patch,
254532
- segments: [GitDiffSegment.make({
254533
- filePath: diff.filePath,
254534
- fingerprint: diff.patch,
254535
- id,
254536
- type
254537
- })],
254538
- status: diff.status
254539
- }));
254559
+ function diffsFromPatch(patch) {
254560
+ return pipe(patch.split(/(?=^diff --git )/mu), filter$4((chunk) => /^diff --git /u.test(chunk)), map$7(diffFromPatchChunk), filter$4((diff) => !isReviewExcludedPath(diff.filePath)));
254561
+ }
254562
+ function untrackedDiffFromContent(filePath, content) {
254563
+ const patch = `diff --git a/${filePath} b/${filePath}\nnew file mode 100644\n--- /dev/null\n+++ b/${filePath}\n@@ -0,0 +1,${length$1(split$1("\n")(content))} @@\n${pipe(split$1("\n")(content), map$7((line) => `+${line}`), join$4("\n"))}`;
254564
+ return GitDiff.make({
254565
+ changeHash: changeHash(patch),
254566
+ filePath,
254567
+ patch,
254568
+ status: "added"
254569
+ });
254570
+ }
254571
+ function sameDiffs(left, right) {
254572
+ return length$1(left) === length$1(right) && every$1(left, (leftDiff, index) => isNotUndefined(right[index]) && leftDiff.filePath === right[index].filePath && leftDiff.status === right[index].status && leftDiff.changeHash === right[index].changeHash && leftDiff.fileContent === right[index].fileContent);
254573
+ }
254574
+ function targetKey(target) {
254575
+ return target._tag === "commit" ? `commit:${target.hash}` : target._tag;
254576
+ }
254577
+ function targetFromKey(key) {
254578
+ if (startsWith$1("commit:")(key)) return GitReviewCommitTarget.make({ hash: replace(/^commit:/u, "")(key) });
254579
+ return value$1(key).pipe(when("local", () => GitReviewLocalTarget.make({})), when("branch", () => GitReviewBranchTarget.make({})), orElse(() => GitReviewChangesTarget.make({})));
254540
254580
  }
254541
- function commitFromLogLine(line) {
254542
- const parts = split$1("\0")(line);
254581
+ function commitFromRecord(record) {
254582
+ const parts = pipe(split$1("\0")(record), map$7(trim));
254543
254583
  return GitCommit.make({
254584
+ checkpoint: includes(checkpointCommit.trailer)(parts[3] ?? ""),
254544
254585
  hash: parts[0],
254545
254586
  shortHash: parts[1] ?? "",
254546
254587
  subject: parts[2] ?? ""
254547
254588
  });
254548
254589
  }
254549
- function parseWorktreeRecords(output) {
254550
- const parsed = reduce(split$1("\0")(output), {
254551
- current: {
254552
- branch: "",
254553
- hasHead: false,
254554
- root: ""
254555
- },
254556
- records: empty$14()
254557
- }, (state, field) => {
254558
- if (startsWith$1("worktree ")(field)) return {
254559
- current: {
254560
- branch: "",
254561
- hasHead: false,
254562
- root: replace(/^worktree\s+/u, "")(field)
254563
- },
254564
- records: isNonEmpty$1(state.current.root) && state.current.hasHead ? append$1(state.records, state.current) : state.records
254565
- };
254566
- if (startsWith$1("HEAD ")(field)) return {
254567
- ...state,
254568
- current: {
254569
- ...state.current,
254570
- hasHead: true
254571
- }
254572
- };
254573
- if (startsWith$1("branch refs/heads/")(field)) return {
254574
- ...state,
254575
- current: {
254576
- ...state.current,
254577
- branch: replace(/^branch\s+refs\/heads\//u, "")(field)
254578
- }
254579
- };
254580
- return state;
254581
- });
254582
- return isNonEmpty$1(parsed.current.root) && parsed.current.hasHead ? append$1(parsed.records, parsed.current) : parsed.records;
254590
+ function commitsFromRecords(output) {
254591
+ return pipe(output, split$1(""), map$7(trim), filter$4(isNonEmpty$1), map$7(commitFromRecord), filter$4((commit) => isNonEmpty$1(commit.hash)));
254592
+ }
254593
+ function publishTitleAndBody(message) {
254594
+ const lines = split$1(/\r?\n/)(trim(message));
254595
+ return {
254596
+ body: pipe(drop(lines, 1), join$4("\n"), trim),
254597
+ title: trim(lines[0])
254598
+ };
254599
+ }
254600
+ function gitHubString(spawner, cwd) {
254601
+ function* runGh(args) {
254602
+ yield* annotateCurrentSpan({
254603
+ command: args[0] ?? "gh",
254604
+ cwd
254605
+ });
254606
+ return yield* scoped$1(gen(function* () {
254607
+ const handle = yield* pipe(spawner.spawn(make$18("gh", args, {
254608
+ cwd,
254609
+ stderr: "pipe",
254610
+ stdout: "pipe"
254611
+ })), mapError$2((cause) => new GitError({ cause })));
254612
+ const output = yield* all({
254613
+ stderr: pipe(decodeText(handle.stderr), mkString, orElseSucceed(() => "")),
254614
+ stdout: pipe(decodeText(handle.stdout), mkString, orElseSucceed(() => ""))
254615
+ }, { concurrency: "unbounded" });
254616
+ const exitCode = yield* pipe(handle.exitCode, mapError$2((cause) => new GitError({ cause })));
254617
+ if (exitCode !== ExitCode(0)) return yield* new GitError({ cause: new Error(output.stderr || output.stdout || `gh ${join$4(" ")(args)} exited with ${exitCode}`) });
254618
+ return output.stdout;
254619
+ })).pipe(withSpan("gh.command", { attributes: {
254620
+ command: args[0] ?? "gh",
254621
+ cwd
254622
+ } }));
254623
+ }
254624
+ return fn("gh.string")(runGh);
254583
254625
  }
254584
254626
  var GitWorkspace = class extends Service()("@deslop/git/service/GitWorkspace", { make: gen(function* () {
254585
254627
  const git = yield* GitCommand;
@@ -254591,7 +254633,7 @@ var GitWorkspace = class extends Service()("@deslop/git/service/GitWorkspace", {
254591
254633
  const worktreeClean = fn("GitWorkspace.worktreeClean")(function* (cwd) {
254592
254634
  return yield* pipe(git.lines(cwd, ["status", "--porcelain"]), map$4(isReadonlyArrayEmpty));
254593
254635
  });
254594
- const fixProject = fn("GitWorkspace.fixProject")(function* (cwd) {
254636
+ const maintenanceProject = fn("GitWorkspace.maintenanceProject")(function* (cwd) {
254595
254637
  yield* annotateCurrentSpan({ cwd });
254596
254638
  const root = yield* sync(() => NFS.realpathSync.native(cwd));
254597
254639
  yield* pipe(git.string(cwd, ["worktree", "prune"]), asVoid);
@@ -254619,7 +254661,7 @@ var GitWorkspace = class extends Service()("@deslop/git/service/GitWorkspace", {
254619
254661
  "for-each-ref",
254620
254662
  "refs/heads",
254621
254663
  "--format=%(refname:short)%00%(upstream:short)%00%(upstream:track)%00%(worktreepath)"
254622
- ]), fn("GitWorkspace.fixBranch")(function* (branchLine) {
254664
+ ]), fn("GitWorkspace.maintenanceBranch")(function* (branchLine) {
254623
254665
  const fields = split$1("\0")(branchLine);
254624
254666
  const branch = fields[0];
254625
254667
  const upstream = fields[1] ?? "";
@@ -254721,17 +254763,17 @@ var GitWorkspace = class extends Service()("@deslop/git/service/GitWorkspace", {
254721
254763
  });
254722
254764
  const repositorySearchRoots = fn("GitWorkspace.repositorySearchRoots")(function* (root) {
254723
254765
  if (root !== home) return [root];
254724
- return yield* pipe(yield* pipe(fs.readDirectory(root), orElseSucceed(() => empty$14())), filter$5((entry) => !has(excludedHomeDiscoveryEntries, entry)), filter$5((entry) => !has(excludedDiscoveryEntries, entry)), filter$5((entry) => !startsWith$1(".")(entry)), forEach$1((entry) => gen(function* () {
254766
+ return yield* pipe(yield* pipe(fs.readDirectory(root), orElseSucceed(() => empty$14())), filter$4((entry) => !has(excludedHomeDiscoveryEntries, entry)), filter$4((entry) => !has(excludedDiscoveryEntries, entry)), filter$4((entry) => !startsWith$1(".")(entry)), forEach$1((entry) => gen(function* () {
254725
254767
  const directory = path.join(root, entry);
254726
254768
  return (yield* pipe(fs.stat(directory), orElseSucceed(() => {})))?.type === "Directory" ? directory : "";
254727
- })), map$4(filter$5(isNonEmpty$1)));
254769
+ })), map$4(filter$4(isNonEmpty$1)));
254728
254770
  });
254729
254771
  const repositoryRootsFromProbe = fn("GitWorkspace.repositoryRootsFromProbe")(function* (root) {
254730
254772
  const directRoot = yield* directRepositoryRoot(root);
254731
254773
  if (isSome(directRoot)) return [directRoot.value];
254732
254774
  const searchRoots = yield* repositorySearchRoots(root);
254733
254775
  if (isReadonlyArrayEmpty(searchRoots)) return getSomes([directRoot]);
254734
- return pipe(yield* repositoryProbeOutput(searchRoots), split$1(/\r?\n/u), filter$5(isNonEmpty$1), map$7((head) => normalizePublicPath(path.dirname(path.dirname(head)))), dedupe, sortWith(identity, String$7));
254776
+ return pipe(yield* repositoryProbeOutput(searchRoots), split$1(/\r?\n/u), filter$4(isNonEmpty$1), map$7((head) => normalizePublicPath(path.dirname(path.dirname(head)))), dedupe, sortWith(identity, String$7));
254735
254777
  });
254736
254778
  const repositoryFromRoot = fn("GitWorkspace.repositoryFromRoot")(function* (root) {
254737
254779
  return yield* pipe(all({
@@ -254800,11 +254842,11 @@ var GitWorkspace = class extends Service()("@deslop/git/service/GitWorkspace", {
254800
254842
  "for-each-ref",
254801
254843
  "--format=%(refname:short)",
254802
254844
  "refs/remotes"
254803
- ]), map$4((lines) => pipe(lines, filter$5((name) => !endsWith("/HEAD")(name)), map$7((name) => GitBranch.make({
254845
+ ]), map$4((lines) => pipe(lines, filter$4((name) => !endsWith("/HEAD")(name)), map$7((name) => GitBranch.make({
254804
254846
  name: pipe(split$1("/")(name), drop(1), join$4("/")),
254805
254847
  remote: split$1("/")(name)[0],
254806
254848
  type: "remote"
254807
- })), filter$5((branch) => isNonEmpty$1(branch.name)), appendAll(localBranches)))))),
254849
+ })), filter$4((branch) => isNonEmpty$1(branch.name)), appendAll(localBranches)))))),
254808
254850
  defaultBranch: yield* getDefaultBranch(cwd)
254809
254851
  });
254810
254852
  }),
@@ -254888,273 +254930,188 @@ var GitWorkspace = class extends Service()("@deslop/git/service/GitWorkspace", {
254888
254930
  }));
254889
254931
  yield* refreshProjects;
254890
254932
  }),
254891
- fix: fn("GitWorkspace.fix")(function* (cwd) {
254892
- yield* fixProject(cwd);
254893
- yield* refreshProjects;
254894
- }),
254895
254933
  listProjectsFrom,
254896
254934
  listRepositoriesFrom,
254897
254935
  listWorktrees,
254936
+ maintenance: fn("GitWorkspace.maintenance")(function* (cwd) {
254937
+ yield* maintenanceProject(cwd);
254938
+ yield* refreshProjects;
254939
+ }),
254898
254940
  projects,
254899
254941
  refreshProjects
254900
254942
  };
254901
254943
  }) }) {
254902
254944
  static layer = pipe(effect(this, this.make), provide$2(GitCommand.layer));
254903
254945
  };
254904
- var GitReview = class extends Service()("@deslop/git/service/GitReview", { make: fn("GitReview.make")(function* (config) {
254905
- const spawner = yield* ChildProcessSpawner;
254946
+ var GitChanges = class extends Service()("@deslop/git/service/GitChanges", { make: fn("GitChanges.make")(function* (config) {
254906
254947
  const git = yield* GitCommand;
254907
254948
  const fs = yield* FileSystem;
254908
254949
  const path = yield* Path$1;
254909
- const state = yield* make$10(GitReviewState.make({
254910
- comments: [],
254911
- marks: []
254912
- }));
254913
- const githubCommentsRef = yield* make$35(head(emptyGitReviewCommentLists));
254950
+ const refs = yield* make$35(empty$14());
254951
+ const fileContentCache = yield* make$35(empty$8());
254914
254952
  const hasWorktreeChanges = pipe(git.lines(config.cwd, ["status", "--porcelain"]), map$4((lines) => !isReadonlyArrayEmpty(lines)));
254915
- function diffsFromPatch(patch, segments) {
254916
- const groupedSegments = segmentsByFile(segments);
254917
- return pipe(patch.split(/(?=^diff --git )/mu), filter$5((chunk) => /^diff --git /u.test(chunk)), map$7((chunk) => diffFromPatchChunk(chunk, groupedSegments)));
254918
- }
254919
- const gitDiffs = fn("GitReview.gitDiffs")(function* (input) {
254920
- yield* annotateCurrentSpan({
254921
- cwd: config.cwd,
254922
- segmentCount: length$1(input.segments)
254923
- });
254924
- const diffs = pipe(diffsFromPatch(yield* git.string(config.cwd, [
254953
+ const currentBranch = pipe(git.string(config.cwd, ["branch", "--show-current"]), map$4(trim));
254954
+ const defaultBranchName = pipe(git.string(config.cwd, [
254955
+ "symbolic-ref",
254956
+ "--short",
254957
+ "refs/remotes/origin/HEAD"
254958
+ ]), map$4(flow(trim, replace(/^origin\//u, ""))), catchTag("GitError", () => pipe(git.string(config.cwd, [
254959
+ "rev-parse",
254960
+ "--verify",
254961
+ "main"
254962
+ ]), as$1("main"), catchTag("GitError", () => succeed$3("master")))));
254963
+ const branchBase = fn("GitChanges.branchBase")(function* (defaultBranch) {
254964
+ return yield* pipe([`origin/${defaultBranch}`, defaultBranch], findFirst((candidate) => pipe(git.string(config.cwd, [
254965
+ "rev-parse",
254966
+ "--verify",
254967
+ candidate
254968
+ ]), as$1(true), orElseSucceed(() => false))), map$4(getOrElse$1(() => "HEAD")));
254969
+ });
254970
+ const localBase = gen(function* () {
254971
+ const upstream = yield* pipe(git.string(config.cwd, [
254972
+ "rev-parse",
254973
+ "--abbrev-ref",
254974
+ "--symbolic-full-name",
254975
+ "@{u}"
254976
+ ]), map$4(trim), option$1);
254977
+ if (isSome(upstream)) return upstream.value;
254978
+ const base = yield* branchBase(yield* defaultBranchName);
254979
+ return yield* pipe(git.string(config.cwd, [
254980
+ "merge-base",
254981
+ base,
254982
+ "HEAD"
254983
+ ]), map$4(trim), catchTag("GitError", () => succeed$3(base)));
254984
+ });
254985
+ const branchDiffBase = gen(function* () {
254986
+ const base = yield* branchBase(yield* defaultBranchName);
254987
+ return yield* pipe(git.string(config.cwd, [
254988
+ "merge-base",
254989
+ base,
254990
+ "HEAD"
254991
+ ]), map$4(trim), catchTag("GitError", () => succeed$3(base)));
254992
+ });
254993
+ const gitDiffs = fn("GitChanges.gitDiffs")(function* (args) {
254994
+ return diffsFromPatch(yield* git.string(config.cwd, [
254925
254995
  "diff",
254926
- ...input.args,
254927
- "--ignore-all-space",
254928
- "--ignore-blank-lines",
254929
- "--ignore-cr-at-eol",
254996
+ ...args,
254997
+ ...reviewDiffFlags,
254930
254998
  "--patch",
254931
254999
  "--find-renames",
254932
255000
  "--no-ext-diff",
254933
- "--",
254934
- ".",
254935
- ...reviewExclusionPathspecs
254936
- ]), input.segments), filter$5((diff) => !isReviewExcludedPath(diff.filePath)));
254937
- yield* annotateCurrentSpan({ diffCount: length$1(diffs) });
254938
- return diffs;
255001
+ ...patchPathspec()
255002
+ ]));
254939
255003
  });
254940
- const commitDiffs = fn("GitReview.commitDiffs")(function* (hash) {
254941
- yield* annotateCurrentSpan({ cwd: config.cwd });
255004
+ const commitDiffs = fn("GitChanges.commitDiffs")(function* (hash) {
254942
255005
  const args = [
254943
255006
  "--root",
254944
255007
  "--patch",
254945
- "--ignore-all-space",
254946
- "--ignore-blank-lines",
254947
- "--ignore-cr-at-eol",
255008
+ ...reviewDiffFlags,
254948
255009
  "--find-renames",
254949
255010
  "--no-ext-diff"
254950
255011
  ];
254951
- const pathspec = [
254952
- "--",
254953
- ".",
254954
- ...reviewExclusionPathspecs
254955
- ];
254956
- const diffs = pipe(diffsFromPatch(yield* pipe(git.string(config.cwd, [
255012
+ const pathspec = patchPathspec();
255013
+ const parents = yield* pipe(git.string(config.cwd, [
255014
+ "rev-list",
255015
+ "--parents",
255016
+ "-n",
255017
+ "1",
255018
+ hash
255019
+ ]), map$4(flow(trim, split$1(/\s+/u))));
255020
+ return diffsFromPatch(yield* pipe(length$1(parents) === 3 ? git.string(config.cwd, [
255021
+ "show",
255022
+ "--remerge-diff",
255023
+ "--format=",
255024
+ ...drop(args, 1),
255025
+ hash,
255026
+ ...pathspec
255027
+ ]) : git.string(config.cwd, [
254957
255028
  "diff-tree",
254958
255029
  ...args,
254959
255030
  hash,
254960
255031
  ...pathspec
254961
255032
  ]), flatMap$2((output) => {
254962
255033
  if (/^diff --git /mu.test(output)) return succeed$3(output);
254963
- return pipe(git.string(config.cwd, [
254964
- "rev-list",
254965
- "--parents",
254966
- "-n",
254967
- "1",
254968
- hash
254969
- ]), map$4(flow(trim, split$1(/\s+/u))), flatMap$2((parents) => {
254970
- const parent = parents[1];
254971
- if (isUndefined(parent)) return succeed$3(output);
254972
- return git.string(config.cwd, [
254973
- "diff-tree",
254974
- ...args,
254975
- parent,
254976
- hash,
254977
- ...pathspec
254978
- ]);
254979
- }));
254980
- })), empty$14()), filter$5((diff) => !isReviewExcludedPath(diff.filePath)));
254981
- yield* annotateCurrentSpan({ diffCount: length$1(diffs) });
254982
- return diffs;
254983
- });
254984
- const untrackedDiffs = pipe(git.lines(config.cwd, [
254985
- "ls-files",
254986
- "--others",
254987
- "--exclude-standard"
254988
- ]), map$4(filter$5((filePath) => !isReviewExcludedPath(filePath))), flatMap$2((files) => forEach$1(files, (filePath) => pipe(fs.readFileString(path.join(config.cwd, filePath)), orElseSucceed(() => ""), map$4((content) => {
254989
- const patch = `diff --git a/${filePath} b/${filePath}\nnew file mode 100644\n--- /dev/null\n+++ b/${filePath}\n@@ -0,0 +1,${length$1(split$1("\n")(content))} @@\n${pipe(split$1("\n")(content), map$7((line) => `+${line}`), join$4("\n"))}`;
254990
- return GitDiff.make({
254991
- filePath,
254992
- patch,
254993
- segments: [GitDiffSegment.make({
254994
- filePath,
254995
- fingerprint: patch,
254996
- id: "HEAD->worktree",
254997
- type: "worktree"
254998
- })],
254999
- status: "added"
255000
- });
255001
- })), { concurrency: "unbounded" })));
255002
- const worktreeDiffs = gen(function* () {
255003
- if (isReadonlyArrayEmpty(yield* git.lines(config.cwd, ["status", "--porcelain"]))) return empty$14();
255004
- return yield* pipe(all([gitDiffs({
255005
- args: ["HEAD"],
255006
- segments: empty$14()
255007
- }), untrackedDiffs], { concurrency: "unbounded" }), map$4(([trackedDiffs, untracked]) => appendAll(map$7(trackedDiffs, (diff) => {
255008
- const segment = GitDiffSegment.make({
255009
- filePath: diff.filePath,
255010
- fingerprint: diff.patch,
255011
- id: "HEAD->worktree",
255012
- type: "worktree"
255013
- });
255014
- return GitDiff.make({
255015
- filePath: diff.filePath,
255016
- patch: diff.patch,
255017
- segments: [segment],
255018
- status: diff.status
255019
- });
255020
- }), untracked)));
255021
- }).pipe(withSpan("GitReview.worktreeDiffs", { attributes: { cwd: config.cwd } }));
255022
- const fileContent = fn("GitReview.fileContent")(function* (input) {
255023
- yield* annotateCurrentSpan({
255024
- cwd: config.cwd,
255025
- filePath: input.filePath,
255026
- target: input.target._tag
255027
- });
255028
- if (input.target._tag !== "commit") return yield* pipe(fs.readFileString(path.join(config.cwd, input.filePath)), orElseSucceed(() => ""));
255029
- return yield* pipe(git.string(config.cwd, ["show", `${input.target.hash}:${input.filePath}`]), orElseSucceed(() => ""));
255034
+ if (length$1(parents) === 3) return succeed$3(output);
255035
+ const parent = parents[1];
255036
+ if (isUndefined(parent)) return succeed$3(output);
255037
+ return git.string(config.cwd, [
255038
+ "diff-tree",
255039
+ ...args,
255040
+ parent,
255041
+ hash,
255042
+ ...pathspec
255043
+ ]);
255044
+ })));
255030
255045
  });
255031
- const withFileContent = fn("GitReview.withFileContent")(function* (input) {
255032
- const contentByFilePath = fromIterable$3(yield* forEach$1(input.diffs, (diff) => pipe(fileContent({
255033
- filePath: diff.filePath,
255034
- target: input.target
255035
- }), map$4((content) => [diff.filePath, content])), { concurrency: 1 }));
255036
- return map$7(input.diffs, (diff) => has$2(contentByFilePath, diff.filePath) ? GitDiff.make({
255037
- fileContent: getOrUndefined$2(get$2(contentByFilePath, diff.filePath)),
255038
- filePath: diff.filePath,
255039
- patch: diff.patch,
255040
- segments: diff.segments,
255041
- status: diff.status
255042
- }) : diff);
255046
+ const untrackedDiffs = gen(function* () {
255047
+ return yield* forEach$1(yield* pipe(git.lines(config.cwd, [
255048
+ "ls-files",
255049
+ "--others",
255050
+ "--exclude-standard"
255051
+ ]), map$4(filter$4((filePath) => !isReviewExcludedPath(filePath)))), (filePath) => pipe(fs.readFileString(path.join(config.cwd, filePath)), orElseSucceed(() => ""), map$4((content) => untrackedDiffFromContent(filePath, content))), { concurrency: 8 });
255043
255052
  });
255044
- const reviewDiffs = fn("GitReview.reviewDiffs")(function* (target) {
255045
- yield* annotateCurrentSpan({
255046
- cwd: config.cwd,
255047
- target: target._tag
255048
- });
255049
- const changesDiffs = gen(function* () {
255050
- return yield* withFileContent({
255051
- diffs: yield* worktreeDiffs,
255052
- target
255053
- });
255054
- });
255055
- const aggregateTargetDiffs = gen(function* () {
255056
- const base = target._tag === "local" ? yield* localBase : yield* branchDiffBase;
255057
- const diffsWithSegments = withDisplayedPatchSegments(yield* aggregateDiffs(base), `${base}->worktree`, "worktree");
255058
- yield* annotateCurrentSpan({ diffCount: length$1(diffsWithSegments) });
255059
- return yield* withFileContent({
255060
- diffs: diffsWithSegments,
255061
- target
255062
- });
255063
- });
255064
- return yield* value$1(target).pipe(when({ _tag: "changes" }, () => changesDiffs), when({ _tag: "commit" }, (commitTarget) => gen(function* () {
255065
- const id = `${commitTarget.hash}^->${commitTarget.hash}`;
255066
- const diffsWithSegments = withDisplayedPatchSegments(yield* commitDiffs(commitTarget.hash), id, "commit");
255067
- yield* annotateCurrentSpan({ diffCount: length$1(diffsWithSegments) });
255068
- return yield* withFileContent({
255069
- diffs: diffsWithSegments,
255070
- target
255071
- });
255072
- })), orElse(() => aggregateTargetDiffs));
255053
+ const fileContent = fn("GitChanges.fileContent")(function* (input) {
255054
+ if (input.diff.status === "deleted") return;
255055
+ if (input.target._tag === "commit") return yield* pipe(git.string(config.cwd, ["show", `${input.target.hash}:${input.diff.filePath}`]), orElseSucceed(() => ""));
255056
+ return yield* pipe(fs.readFileString(path.join(config.cwd, input.diff.filePath)), orElseSucceed(() => ""));
255073
255057
  });
255074
- const ghString = fn("gh.string")(function* (args) {
255075
- yield* annotateCurrentSpan({
255076
- command: args[0] ?? "gh",
255077
- cwd: config.cwd
255078
- });
255079
- return yield* scoped$1(gen(function* () {
255080
- const handle = yield* pipe(spawner.spawn(make$18("gh", args, {
255081
- cwd: config.cwd,
255082
- stderr: "pipe",
255083
- stdout: "pipe"
255084
- })), mapError$2((cause) => new GitError({ cause })));
255085
- const output = yield* all({
255086
- stderr: pipe(decodeText(handle.stderr), mkString, orElseSucceed(() => "")),
255087
- stdout: pipe(decodeText(handle.stdout), mkString, orElseSucceed(() => ""))
255088
- }, { concurrency: "unbounded" });
255089
- const exitCode = yield* pipe(handle.exitCode, mapError$2((cause) => new GitError({ cause })));
255090
- if (exitCode !== ExitCode(0)) return yield* new GitError({ cause: new Error(output.stderr || output.stdout || `gh ${join$4(" ")(args)} exited with ${exitCode}`) });
255091
- return output.stdout;
255092
- })).pipe(withSpan("gh.command", { attributes: {
255093
- command: args[0] ?? "gh",
255094
- cwd: config.cwd
255095
- } }));
255058
+ const withFileContent = fn("GitChanges.withFileContent")(function* (input) {
255059
+ return yield* forEach$1(input.diffs, (diff) => {
255060
+ if (input.target._tag !== "commit") return pipe(fileContent({
255061
+ diff,
255062
+ target: input.target
255063
+ }), map$4((content) => GitDiff.make({
255064
+ ...diff,
255065
+ fileContent: content
255066
+ })));
255067
+ const key = `${targetKey(input.target)}\u0000${diff.filePath}\u0000${diff.changeHash}`;
255068
+ return pipe(get$1(fileContentCache), flatMap$2((cache) => pipe(get$2(cache, key), match$6({
255069
+ onNone: () => pipe(fileContent({
255070
+ diff,
255071
+ target: input.target
255072
+ }), map$4((content) => GitDiff.make({
255073
+ ...diff,
255074
+ fileContent: content
255075
+ })), tap((readyDiff) => update$1(fileContentCache, (current) => set$3(current, key, readyDiff)))),
255076
+ onSome: succeed$3
255077
+ }))));
255078
+ }, { concurrency: 8 });
255096
255079
  });
255097
- const currentBranch = pipe(git.string(config.cwd, ["branch", "--show-current"]), map$4(trim));
255098
- const defaultBranchName = pipe(git.string(config.cwd, [
255099
- "symbolic-ref",
255100
- "--short",
255101
- "refs/remotes/origin/HEAD"
255102
- ]), map$4(flow(trim, replace(/^origin\//u, ""))), catchTag("GitError", () => pipe(git.string(config.cwd, [
255103
- "rev-parse",
255104
- "--verify",
255105
- "main"
255106
- ]), as$1("main"), catchTag("GitError", () => succeed$3("master")))));
255107
- const branchBase = fn("GitReview.branchBase")(function* (defaultBranch) {
255108
- yield* annotateCurrentSpan({
255109
- cwd: config.cwd,
255110
- defaultBranch
255080
+ const computeDiffs = fn("GitChanges.computeDiffs")(function* (target) {
255081
+ const trackedDiffs = yield* value$1(target).pipe(when({ _tag: "changes" }, () => gitDiffs(["HEAD"])), when({ _tag: "commit" }, (commit) => commitDiffs(commit.hash)), when({ _tag: "local" }, () => pipe(localBase, flatMap$2((base) => gitDiffs([base])))), orElse(() => pipe(branchDiffBase, flatMap$2((base) => gitDiffs([base])))));
255082
+ return yield* withFileContent({
255083
+ diffs: target._tag === "changes" && !(yield* hasWorktreeChanges) ? empty$14() : appendAll(trackedDiffs, target._tag === "commit" ? empty$14() : yield* untrackedDiffs),
255084
+ target
255111
255085
  });
255112
- return yield* pipe([`origin/${defaultBranch}`, defaultBranch], findFirst((candidate) => pipe(git.string(config.cwd, [
255113
- "rev-parse",
255114
- "--verify",
255115
- candidate
255116
- ]), as$1(true), orElseSucceed(() => false))), map$4(getOrElse$1(() => "HEAD")));
255117
255086
  });
255118
- const branchPrUrl = pipe(ghString([
255119
- "pr",
255120
- "view",
255121
- "--json",
255122
- "url",
255123
- "--jq",
255124
- ".url"
255125
- ]), map$4(trim), map$4((url) => head(isNonEmpty$1(url) ? [url] : [])), catchTag("GitError", () => succeed$3(head(emptyStrings))));
255126
- const commits = fn("GitReview.commits")(function* (base) {
255127
- yield* annotateCurrentSpan({ cwd: config.cwd });
255128
- const from = yield* pipe(git.string(config.cwd, [
255129
- "merge-base",
255130
- base,
255131
- "HEAD"
255132
- ]), map$4(trim), catchTag("GitError", () => succeed$3(base)));
255133
- return yield* pipe(git.lines(config.cwd, [
255087
+ const commitRecords = fn("GitChanges.commitRecords")(function* (range) {
255088
+ return commitsFromRecords(yield* git.string(config.cwd, [
255134
255089
  "log",
255135
255090
  "--max-count=80",
255136
- "--format=%H%x00%h%x00%s",
255137
- `${from}..HEAD`
255138
- ]), map$4(map$7(commitFromLogLine)));
255091
+ "--format=%H%x00%h%x00%s%x00%B%x1e",
255092
+ range
255093
+ ]));
255139
255094
  });
255140
- const commitsBetween = fn("GitReview.commitsBetween")(function* (from, to) {
255141
- yield* annotateCurrentSpan({ cwd: config.cwd });
255142
- return yield* pipe(git.lines(config.cwd, [
255095
+ const firstParentCommits = gen(function* () {
255096
+ return commitsFromRecords(yield* git.string(config.cwd, [
255143
255097
  "log",
255098
+ "--first-parent",
255144
255099
  "--max-count=80",
255145
- "--format=%H%x00%h%x00%s",
255146
- `${from}..${to}`
255147
- ]), map$4(map$7(commitFromLogLine)));
255100
+ "--format=%H%x00%h%x00%s%x00%B%x1e",
255101
+ "HEAD"
255102
+ ]));
255148
255103
  });
255149
- const firstParentCommits = pipe(git.lines(config.cwd, [
255150
- "log",
255151
- "--first-parent",
255152
- "--max-count=80",
255153
- "--format=%H%x00%h%x00%s",
255154
- "HEAD"
255155
- ]), map$4(map$7(commitFromLogLine)));
255156
- const hasPushableCommits = pipe(gen(function* () {
255157
- yield* annotateCurrentSpan({ cwd: config.cwd });
255104
+ const commitsBetween = fn("GitChanges.commitsBetween")(function* (from, to) {
255105
+ return yield* commitRecords(`${from}..${to}`);
255106
+ });
255107
+ const commits = fn("GitChanges.commits")(function* (base) {
255108
+ return yield* commitsBetween(yield* pipe(git.string(config.cwd, [
255109
+ "merge-base",
255110
+ base,
255111
+ "HEAD"
255112
+ ]), map$4(trim), catchTag("GitError", () => succeed$3(base))), "HEAD");
255113
+ });
255114
+ const pushableCommitCount = gen(function* () {
255158
255115
  const upstream = yield* pipe(git.string(config.cwd, [
255159
255116
  "rev-parse",
255160
255117
  "--abbrev-ref",
@@ -255177,16 +255134,15 @@ var GitReview = class extends Service()("@deslop/git/service/GitReview", { make:
255177
255134
  "--count",
255178
255135
  `${from}..HEAD`
255179
255136
  ]), map$4(flow(trim, parse$5, getOrElse$1(() => 0))));
255180
- }), map$4((count) => count > 0));
255137
+ });
255181
255138
  const upstreamCounts = gen(function* () {
255182
- const noUpstream = void 0;
255183
255139
  const upstream = yield* pipe(git.string(config.cwd, [
255184
255140
  "rev-parse",
255185
255141
  "--abbrev-ref",
255186
255142
  "--symbolic-full-name",
255187
255143
  "@{u}"
255188
255144
  ]), map$4(trim), option$1);
255189
- if (isNone(upstream)) return noUpstream;
255145
+ if (isNone(upstream)) return [];
255190
255146
  return yield* pipe(git.string(config.cwd, [
255191
255147
  "rev-list",
255192
255148
  "--left-right",
@@ -255194,43 +255150,72 @@ var GitReview = class extends Service()("@deslop/git/service/GitReview", { make:
255194
255150
  `${upstream.value}...HEAD`
255195
255151
  ]), map$4((output) => {
255196
255152
  const counts = pipe(output, trim, split$1(/\s+/u));
255197
- return {
255153
+ return [{
255198
255154
  ahead: getOrElse$1(parse$5(counts[1] ?? "0"), () => 0),
255199
255155
  behind: getOrElse$1(parse$5(counts[0]), () => 0)
255200
- };
255201
- }), catchTag("GitError", () => succeed$3(noUpstream)));
255202
- });
255203
- const localBase = gen(function* () {
255204
- const upstream = yield* pipe(git.string(config.cwd, [
255205
- "rev-parse",
255206
- "--abbrev-ref",
255207
- "--symbolic-full-name",
255208
- "@{u}"
255209
- ]), map$4(trim), option$1);
255210
- if (isSome(upstream)) return upstream.value;
255211
- const base = yield* branchBase(yield* defaultBranchName);
255212
- return yield* pipe(git.string(config.cwd, [
255213
- "merge-base",
255214
- base,
255215
- "HEAD"
255216
- ]), map$4(trim), catchTag("GitError", () => succeed$3(base)));
255156
+ }];
255157
+ }), catchTag("GitError", () => succeed$3([])));
255217
255158
  });
255218
- const branchDiffBase = gen(function* () {
255219
- const base = yield* branchBase(yield* defaultBranchName);
255220
- return yield* pipe(git.string(config.cwd, [
255221
- "merge-base",
255222
- base,
255223
- "HEAD"
255224
- ]), map$4(trim), catchTag("GitError", () => succeed$3(base)));
255159
+ const metadataSnapshot = gen(function* () {
255160
+ const branch = yield* currentBranch;
255161
+ const defaultBranch = yield* defaultBranchName;
255162
+ const branchBaseRef = yield* branchDiffBase;
255163
+ const localCommits = yield* commitsBetween(yield* localBase, "HEAD");
255164
+ const branchCommitCandidates = branch === defaultBranch ? yield* firstParentCommits : yield* commits(branchBaseRef);
255165
+ const localCommitHashes = pipe(localCommits, map$7((commit) => commit.hash), fromIterable$1);
255166
+ const branchCommits = filter$4(branchCommitCandidates, (commit) => !has(localCommitHashes, commit.hash));
255167
+ return GitReviewMetadata.make({
255168
+ branchCommits,
255169
+ dirty: yield* hasWorktreeChanges,
255170
+ localCommits,
255171
+ unpushedCommits: (yield* pushableCommitCount) > 0,
255172
+ upstream: pipe(yield* upstreamCounts, head, getOrUndefined$2)
255173
+ });
255225
255174
  });
255226
- const aggregateDiffs = fn("GitReview.aggregateDiffs")(function* (base) {
255227
- return appendAll(yield* gitDiffs({
255228
- args: [base],
255229
- segments: empty$14()
255230
- }), yield* untrackedDiffs);
255175
+ const metadata = yield* make$10(yield* metadataSnapshot);
255176
+ const worktreeChanges = yield* pipe(fs.watch(config.cwd), catch_(() => empty$10), debounce(millis(80)), map$2(() => void 0), share({
255177
+ capacity: 1,
255178
+ idleTimeToLive: seconds(30),
255179
+ replay: 0,
255180
+ strategy: "sliding"
255181
+ }));
255182
+ yield* forkScoped(pipe(worktreeChanges, mapEffect(() => pipe(metadataSnapshot, flatMap$2((next) => set(metadata, next)), ignore$4)), runDrain));
255183
+ const ensureDiffRef = fn("GitChanges.ensureDiffRef")(function* (target) {
255184
+ const key = targetKey(target);
255185
+ const existing = findFirst$2(yield* get$1(refs), (entry) => entry.key === key);
255186
+ if (isSome(existing)) return existing.value.ref;
255187
+ const ref = yield* make$10(yield* computeDiffs(target));
255188
+ yield* update$1(refs, (currentRefs) => append$1(currentRefs, {
255189
+ key,
255190
+ ref
255191
+ }));
255192
+ return ref;
255231
255193
  });
255232
- const prReviewComments = gen(function* () {
255233
- yield* annotateCurrentSpan({ cwd: config.cwd });
255194
+ yield* forkScoped(pipe(worktreeChanges, mapEffect(() => pipe(get$1(refs), flatMap$2((currentRefs) => forEach$1(currentRefs, (entry) => {
255195
+ const target = targetFromKey(entry.key);
255196
+ if (target._tag === "commit") return void_$1;
255197
+ return pipe(computeDiffs(target), flatMap$2((next) => update(entry.ref, (currentDiffs) => sameDiffs(currentDiffs, next) ? currentDiffs : next)), ignore$4);
255198
+ }, {
255199
+ concurrency: 2,
255200
+ discard: true
255201
+ })))), runDrain));
255202
+ yield* forkScoped(pipe(ensureDiffRef(GitReviewChangesTarget.make({})), ignore$4));
255203
+ return {
255204
+ diffs: ensureDiffRef,
255205
+ metadata
255206
+ };
255207
+ }) }) {
255208
+ static layer = flow(this.make, (layer) => pipe(effect(this, layer), provide$2(GitCommand.layer)));
255209
+ };
255210
+ var GitReview = class extends Service()("@deslop/git/service/GitReview", { make: fn("GitReview.make")(function* (config) {
255211
+ const spawner = yield* ChildProcessSpawner;
255212
+ const state = yield* make$10(GitReviewState.make({
255213
+ comments: [],
255214
+ marks: []
255215
+ }));
255216
+ const suppressedThreadIds = yield* make$35(empty$6());
255217
+ const ghString = gitHubString(spawner, config.cwd);
255218
+ yield* forkScoped(pipe(gen(function* () {
255234
255219
  const pr = yield* pipe(ghString([
255235
255220
  "pr",
255236
255221
  "view",
@@ -255251,160 +255236,61 @@ var GitReview = class extends Service()("@deslop/git/service/GitReview", { make:
255251
255236
  cause,
255252
255237
  message: "Failed to parse GitHub repository."
255253
255238
  })));
255254
- const response = yield* ghString([
255239
+ const response = yield* pipe(ghString([
255255
255240
  "api",
255256
255241
  "graphql",
255257
255242
  "-f",
255258
- `query=
255259
- query(\$owner: String!, \$name: String!, \$number: Int!) {
255260
- repository(owner: \$owner, name: \$name) {
255261
- pullRequest(number: \$number) {
255262
- reviewThreads(first: 100) {
255263
- nodes {
255264
- id
255265
- isResolved
255266
- diffSide
255267
- comments(first: 20) {
255268
- nodes {
255269
- body
255270
- line
255271
- originalLine
255272
- path
255273
- url
255274
- }
255275
- }
255276
- }
255277
- }
255278
- }
255279
- }
255280
- }`,
255243
+ `query=query(\$owner: String!, \$name: String!, \$number: Int!) { repository(owner: \$owner, name: \$name) { pullRequest(number: \$number) { reviewThreads(first: 100) { nodes { id isResolved diffSide comments(first: 20) { nodes { body line originalLine path url } } } } } } }`,
255281
255244
  "-f",
255282
255245
  `owner=${repository.owner.login}`,
255283
255246
  "-f",
255284
255247
  `name=${repository.name}`,
255285
255248
  "-F",
255286
255249
  `number=${pr}`
255287
- ]);
255288
- const threads = (yield* pipe(decodeUnknownEffect(fromJsonString(GitHubReviewThreadsResponse))(response), mapError$2((cause) => new GitError({
255250
+ ]), flatMap$2(decodeUnknownEffect(fromJsonString(GitHubReviewThreadsResponse))), mapError$2((cause) => new GitError({
255289
255251
  cause,
255290
255252
  message: "Failed to parse GitHub review threads."
255291
- })))).data?.repository?.pullRequest?.reviewThreads?.nodes ?? [];
255292
- yield* annotateCurrentSpan({ threadCount: length$1(threads) });
255293
- return pipe(threads, flatMap$5((thread) => map$7(thread.comments.nodes, (comment) => GitReviewComment.make({
255253
+ })));
255254
+ const suppressed = yield* get$1(suppressedThreadIds);
255255
+ return pipe(response.data?.repository?.pullRequest?.reviewThreads?.nodes ?? [], filter$4((thread) => thread.isResolved === false && !has(suppressed, thread.id)), flatMap$5((thread) => map$7(thread.comments.nodes, (comment) => GitReviewComment.make({
255294
255256
  body: comment.body,
255295
255257
  filePath: comment.path,
255296
255258
  lineNumber: comment.line ?? comment.originalLine ?? 1,
255297
- resolved: thread.isResolved,
255298
255259
  side: thread.diffSide === "LEFT" ? "deletions" : "additions",
255299
255260
  source: "github",
255300
255261
  threadId: thread.id,
255301
255262
  url: comment.url
255302
- }))));
255303
- }).pipe(withSpan("GitReview.reviewComments", { attributes: { cwd: config.cwd } }));
255304
- const githubComments = fnUntraced(function* () {
255305
- const cached = yield* get$1(githubCommentsRef);
255306
- if (isSome(cached)) return cached.value;
255307
- const comments = yield* pipe(prReviewComments, catchTag("GitError", () => succeed$3(empty$14())));
255308
- yield* set$1(githubCommentsRef, head([comments]));
255309
- return comments;
255310
- });
255311
- const reviewState = gen(function* () {
255312
- const current = yield* get(state);
255313
- const github = yield* githubComments();
255314
- return GitReviewState.make({
255315
- comments: pipe(appendAll(current.comments, github), filter$5((comment) => !isReviewExcludedPath(comment.filePath))),
255316
- marks: filter$5(current.marks, (mark) => !isReviewExcludedPath(mark.filePath))
255317
- });
255318
- });
255319
- const worktreeChanges = yield* pipe(fs.watch(config.cwd), catch_(() => empty$10), debounce(millis(50)), mapEffect(() => pipe(git.lines(config.cwd, ["status", "--porcelain"]), map$4((lines) => some(lines, (line) => {
255320
- const filePath = trim(slice(3)(line));
255321
- return isNonEmpty$1(filePath) && !isReviewExcludedPath(filePath);
255322
- })), orElseSucceed(() => true))), filter$3(identity), map$2(() => void 0), share({
255323
- capacity: 16,
255324
- idleTimeToLive: seconds(30),
255325
- replay: 0,
255326
- strategy: "sliding"
255327
- }));
255328
- const metadata = gen(function* () {
255329
- yield* annotateCurrentSpan({ cwd: config.cwd });
255330
- const branch = yield* currentBranch;
255331
- const defaultBranch = yield* defaultBranchName;
255332
- const branchBaseRef = yield* branchDiffBase;
255333
- const localCommits = yield* commitsBetween(yield* localBase, "HEAD");
255334
- const branchCommitCandidates = branch === defaultBranch ? yield* firstParentCommits : yield* commits(branchBaseRef);
255335
- const localCommitHashes = pipe(localCommits, map$7((commit) => commit.hash), fromIterable$1);
255336
- const branchCommits = filter$5(branchCommitCandidates, (commit) => !has(localCommitHashes, commit.hash));
255337
- return GitReviewMetadata.make({
255338
- branchCommits,
255339
- dirty: yield* hasWorktreeChanges,
255340
- localCommits,
255341
- prUrl: getOrUndefined$2(yield* branchPrUrl),
255342
- unpushedCommits: yield* hasPushableCommits,
255343
- upstream: yield* upstreamCounts
255344
- });
255345
- });
255263
+ }))), filter$4((comment) => !isReviewExcludedPath(comment.filePath)));
255264
+ }), catchTag("GitError", () => succeed$3(empty$14())), flatMap$2((comments) => update(state, (current) => GitReviewState.make({
255265
+ comments: appendAll(filter$4(current.comments, (comment) => comment.source !== "github"), comments),
255266
+ marks: current.marks
255267
+ })))));
255346
255268
  return {
255347
- commits,
255348
- mark: fn("GitReview.mark")(function* (marks) {
255349
- yield* annotateCurrentSpan({
255350
- cwd: config.cwd,
255351
- markCount: length$1(marks)
255352
- });
255353
- yield* update(state, (current) => gitReviewStateMark(current, marks));
255354
- }),
255355
- metadata,
255356
- resolveComment: fn("GitReview.resolveComment")(function* (input) {
255357
- yield* annotateCurrentSpan({
255358
- cwd: config.cwd,
255359
- filePath: input.filePath
255360
- });
255361
- yield* update(state, (current) => gitReviewStateResolveComment(current, input));
255362
- }),
255363
- resolveReviewThread: fn("GitReview.resolveReviewThread")(function* (threadId) {
255364
- yield* annotateCurrentSpan({
255365
- cwd: config.cwd,
255366
- threadId
255367
- });
255368
- yield* pipe(ghString([
255269
+ mark: (marks) => update(state, (current) => gitReviewStateMark(current, marks)),
255270
+ resolveComments: fn("GitReview.resolveComments")(function* (comments) {
255271
+ const threadIds = pipe(comments, filter$4((comment) => comment.source === "github" && isString(comment.threadId)), map$7((comment) => comment.threadId ?? ""), filter$4(isNonEmpty$1), dedupe);
255272
+ if (isReadonlyArrayEmpty(threadIds)) {
255273
+ yield* update(state, (current) => gitReviewStateDeleteComments(current, comments));
255274
+ return;
255275
+ }
255276
+ const query = `mutation($threadId: ID!) { resolveReviewThread(input: {threadId: $threadId}) { thread { id } } }`;
255277
+ if (!isReadonlyArrayEmpty(yield* pipe(threadIds, forEach$1((threadId) => pipe(ghString([
255369
255278
  "api",
255370
255279
  "graphql",
255371
255280
  "-f",
255372
- `query=
255373
- mutation(\$threadId: ID!) {
255374
- resolveReviewThread(input: {threadId: \$threadId}) {
255375
- thread {
255376
- id
255377
- }
255378
- }
255379
- }`,
255281
+ `query=${query}`,
255380
255282
  "-f",
255381
255283
  `threadId=${threadId}`
255382
- ]), asVoid);
255383
- yield* set$1(githubCommentsRef, head(emptyGitReviewCommentLists));
255284
+ ]), as$1(empty$14()), catchTag("GitError", (error) => succeed$3([error]))), { concurrency: 4 }), map$4(flatten$3)))) return yield* new GitError({ message: "One or more GitHub threads failed to resolve." });
255285
+ yield* update$1(suppressedThreadIds, (current) => union(current, fromIterable$1(threadIds)));
255286
+ yield* update(state, (current) => gitReviewStateDeleteComments(current, comments));
255384
255287
  }),
255385
- reviewDiffs,
255386
- reviewState,
255387
255288
  saveComment: fn("GitReview.saveComment")(function* (comment) {
255388
- yield* annotateCurrentSpan({
255389
- cwd: config.cwd,
255390
- filePath: comment.filePath
255391
- });
255289
+ if (isReviewExcludedPath(comment.filePath)) return;
255392
255290
  yield* update(state, (current) => gitReviewStateSaveComment(current, comment));
255393
255291
  }),
255394
- unmark: fn("GitReview.unmark")(function* (marks) {
255395
- yield* annotateCurrentSpan({
255396
- cwd: config.cwd,
255397
- markCount: length$1(marks)
255398
- });
255399
- yield* update(state, (current) => gitReviewStateUnmark(current, marks));
255400
- }),
255401
- watchReviewDiffs: (target) => {
255402
- const diffs = pipe(reviewDiffs(target), catchTag("GitError", () => succeed$3(empty$14())));
255403
- if (target._tag === "commit") return fromEffect(diffs);
255404
- return fromEffect(diffs).pipe(concat(pipe(worktreeChanges, mapEffect(() => diffs))), changesWith((left, right) => length$1(left) === length$1(right) && every$1(left, (leftDiff, index) => isNotUndefined(right[index]) && leftDiff.filePath === right[index].filePath && leftDiff.status === right[index].status && leftDiff.patch === right[index].patch)));
255405
- },
255406
- watchReviewMetadata: () => pipe(fromEffect(metadata), concat(pipe(worktreeChanges, mapEffect(() => metadata))), changes$1),
255407
- watchReviewState: () => pipe(changes(state), mapEffect(() => reviewState), changes$1)
255292
+ state,
255293
+ unmark: (marks) => update(state, (current) => gitReviewStateUnmark(current, marks))
255408
255294
  };
255409
255295
  }) }) {
255410
255296
  static layer = flow(this.make, (layer) => pipe(effect(this, layer), provide$2(GitCommand.layer)));
@@ -255412,29 +255298,7 @@ var GitReview = class extends Service()("@deslop/git/service/GitReview", { make:
255412
255298
  var GitPublish = class extends Service()("@deslop/git/service/GitPublish", { make: fn("GitPublish.make")(function* (config) {
255413
255299
  const spawner = yield* ChildProcessSpawner;
255414
255300
  const git = yield* GitCommand;
255415
- const ghString = fn("gh.string")(function* (args) {
255416
- yield* annotateCurrentSpan({
255417
- command: args[0] ?? "gh",
255418
- cwd: config.cwd
255419
- });
255420
- return yield* scoped$1(gen(function* () {
255421
- const handle = yield* pipe(spawner.spawn(make$18("gh", args, {
255422
- cwd: config.cwd,
255423
- stderr: "pipe",
255424
- stdout: "pipe"
255425
- })), mapError$2((cause) => new GitError({ cause })));
255426
- const output = yield* all({
255427
- stderr: pipe(decodeText(handle.stderr), mkString, orElseSucceed(() => "")),
255428
- stdout: pipe(decodeText(handle.stdout), mkString, orElseSucceed(() => ""))
255429
- }, { concurrency: "unbounded" });
255430
- const exitCode = yield* pipe(handle.exitCode, mapError$2((cause) => new GitError({ cause })));
255431
- if (exitCode !== ExitCode(0)) return yield* new GitError({ cause: new Error(output.stderr || output.stdout || `gh ${join$4(" ")(args)} exited with ${exitCode}`) });
255432
- return output.stdout;
255433
- })).pipe(withSpan("gh.command", { attributes: {
255434
- command: args[0] ?? "gh",
255435
- cwd: config.cwd
255436
- } }));
255437
- });
255301
+ const ghString = gitHubString(spawner, config.cwd);
255438
255302
  const hasWorktreeChanges = pipe(git.lines(config.cwd, ["status", "--porcelain"]), map$4((lines) => !isReadonlyArrayEmpty(lines)));
255439
255303
  const currentBranch = pipe(git.string(config.cwd, ["branch", "--show-current"]), map$4(trim));
255440
255304
  const defaultBranchName = pipe(git.string(config.cwd, [
@@ -255447,10 +255311,6 @@ var GitPublish = class extends Service()("@deslop/git/service/GitPublish", { mak
255447
255311
  "main"
255448
255312
  ]), as$1("main"), catchTag("GitError", () => succeed$3("master")))));
255449
255313
  const branchBase = fn("GitPublish.branchBase")(function* (defaultBranch) {
255450
- yield* annotateCurrentSpan({
255451
- cwd: config.cwd,
255452
- defaultBranch
255453
- });
255454
255314
  return yield* pipe([`origin/${defaultBranch}`, defaultBranch], findFirst((candidate) => pipe(git.string(config.cwd, [
255455
255315
  "rev-parse",
255456
255316
  "--verify",
@@ -255466,8 +255326,8 @@ var GitPublish = class extends Service()("@deslop/git/service/GitPublish", { mak
255466
255326
  cause,
255467
255327
  message: "Failed to parse current pull request."
255468
255328
  })))), flatMap$2((pr) => isNonEmpty$1(pr.url ?? "") ? succeed$3(head([GitPullRequest.make({ url: pr.url ?? "" })])) : succeed$3(head(emptyGitPullRequests))), catchTag("GitError", () => succeed$3(head(emptyGitPullRequests))));
255329
+ const pullRequest = yield* make$10(yield* currentPullRequest);
255469
255330
  const hasPushableCommits = pipe(gen(function* () {
255470
- yield* annotateCurrentSpan({ cwd: config.cwd });
255471
255331
  const upstream = yield* pipe(git.string(config.cwd, [
255472
255332
  "rev-parse",
255473
255333
  "--abbrev-ref",
@@ -255491,60 +255351,119 @@ var GitPublish = class extends Service()("@deslop/git/service/GitPublish", { mak
255491
255351
  `${from}..HEAD`
255492
255352
  ]), map$4(flow(trim, parse$5, getOrElse$1(() => 0))));
255493
255353
  }), map$4((count) => count > 0));
255494
- const createDraftPr = pipe(ghString([
255495
- "pr",
255496
- "create",
255497
- "--draft",
255498
- "--fill"
255499
- ]), map$4((output) => {
255500
- const url = output.match(/https?:\/\/\S+/u)?.[0] ?? trim(output);
255501
- return head(isNonEmpty$1(url) ? [GitPullRequest.make({ url })] : []);
255502
- }));
255503
- const commit = fn("GitPublish.commit")(function* (message) {
255504
- yield* annotateCurrentSpan({ cwd: config.cwd });
255505
- const dirty = yield* hasWorktreeChanges;
255506
- yield* annotateCurrentSpan({ dirty });
255507
- if (!dirty) return yield* new GitError({ message: "No changes to commit." });
255508
- if (isEmpty$1(trim(message))) return yield* new GitError({ message: "Commit message required." });
255509
- yield* pipe(git.string(config.cwd, ["add", "-A"]), asVoid, withSpan("GitPublish.stageAll"));
255354
+ const headCheckpointCommits = gen(function* () {
255355
+ return takeWhile(commitsFromRecords(yield* pipe(git.string(config.cwd, [
255356
+ "log",
255357
+ "--format=%H%x00%h%x00%s%x00%B%x1e",
255358
+ "HEAD"
255359
+ ]), catchTag("GitError", () => succeed$3("")))), (commit) => commit.checkpoint);
255360
+ });
255361
+ const headCommitMessage = pipe(git.string(config.cwd, [
255362
+ "log",
255363
+ "-1",
255364
+ "--format=%B",
255365
+ "HEAD"
255366
+ ]), map$4(trim), catchTag("GitError", () => succeed$3("")));
255367
+ const commitAll = fn("GitPublish.commitAll")(function* (message) {
255368
+ yield* pipe(git.string(config.cwd, ["add", "-A"]), asVoid);
255510
255369
  yield* pipe(git.stringWithInput(config.cwd, [
255511
255370
  "commit",
255512
255371
  "-F",
255513
255372
  "-"
255514
- ], message), asVoid, withSpan("GitPublish.createCommit"));
255373
+ ], message), asVoid);
255515
255374
  });
255516
255375
  const push = gen(function* () {
255517
- yield* annotateCurrentSpan({ cwd: config.cwd });
255518
- if (!(yield* hasPushableCommits)) return yield* new GitError({ message: "No unpushed commits." });
255376
+ if (!(yield* hasPushableCommits)) return;
255519
255377
  const branch = yield* currentBranch;
255520
- yield* annotateCurrentSpan({ branch });
255521
255378
  yield* pipe(git.string(config.cwd, [
255522
255379
  "push",
255523
255380
  "-u",
255524
255381
  "origin",
255525
255382
  `HEAD:${branch}`
255526
- ]), asVoid, withSpan("GitPublish.push", { attributes: {
255527
- branch,
255528
- cwd: config.cwd
255529
- } }));
255383
+ ]), asVoid);
255530
255384
  if (yield* hasPushableCommits) return yield* new GitError({ message: "Push completed but the branch still has unpushed commits." });
255531
255385
  });
255532
- const upsertDraftPullRequest = gen(function* () {
255533
- const branch = yield* currentBranch;
255534
- if (branch === (yield* defaultBranchName)) return head(emptyGitPullRequests);
255386
+ const upsertDraftPullRequest = fn("GitPublish.upsertDraftPullRequest")(function* (message) {
255387
+ if ((yield* currentBranch) === (yield* defaultBranchName)) return head(emptyGitPullRequests);
255388
+ const titleBody = publishTitleAndBody(message);
255389
+ if (isEmpty$1(titleBody.title)) return yield* new GitError({ message: "Publish message title required." });
255535
255390
  const existing = yield* currentPullRequest;
255536
- if (isSome(existing)) return existing;
255537
- return yield* pipe(createDraftPr, withSpan("GitPublish.createDraftPr", { attributes: {
255538
- branch,
255539
- cwd: config.cwd
255540
- } }));
255391
+ if (isSome(existing)) {
255392
+ yield* pipe(ghString([
255393
+ "pr",
255394
+ "edit",
255395
+ "--title",
255396
+ titleBody.title,
255397
+ "--body",
255398
+ titleBody.body
255399
+ ]), asVoid);
255400
+ return existing;
255401
+ }
255402
+ const output = yield* ghString([
255403
+ "pr",
255404
+ "create",
255405
+ "--draft",
255406
+ "--title",
255407
+ titleBody.title,
255408
+ "--body",
255409
+ titleBody.body
255410
+ ]);
255411
+ const url = output.match(/https?:\/\/\S+/u)?.[0] ?? trim(output);
255412
+ return head(isNonEmpty$1(url) ? [GitPullRequest.make({ url })] : []);
255541
255413
  });
255542
- return { approve: fn("GitPublish.approve")(function* (input) {
255543
- if (yield* hasWorktreeChanges) yield* commit(input.message);
255544
- if (!(yield* hasPushableCommits)) return yield* new GitError({ message: "No changes or unpushed commits to publish." });
255545
- yield* push;
255546
- return getOrUndefined$2(yield* upsertDraftPullRequest);
255547
- }) };
255414
+ return {
255415
+ checkpoint: gen(function* () {
255416
+ if (!(yield* hasWorktreeChanges)) return yield* new GitError({ message: "No changes to checkpoint." });
255417
+ yield* pipe(git.string(config.cwd, ["add", "-A"]), asVoid);
255418
+ yield* pipe(git.stringWithInput(config.cwd, [
255419
+ "commit",
255420
+ "-F",
255421
+ "-"
255422
+ ], `checkpoint\n\n${checkpointCommit.trailer}\n`), asVoid);
255423
+ }),
255424
+ publish: fn("GitPublish.publish")(function* (input) {
255425
+ const checkpoints = yield* headCheckpointCommits;
255426
+ const dirty = yield* hasWorktreeChanges;
255427
+ const inputMessage = trim(input.message);
255428
+ if (isEmpty$1(inputMessage) && (dirty || !isReadonlyArrayEmpty(checkpoints))) return yield* new GitError({ message: "Publish message required." });
255429
+ const message = isNonEmpty$1(inputMessage) ? inputMessage : yield* headCommitMessage;
255430
+ const committed = yield* gen(function* () {
255431
+ if (isReadonlyArrayEmpty(checkpoints)) {
255432
+ if (!dirty) return false;
255433
+ yield* commitAll(message);
255434
+ return true;
255435
+ }
255436
+ const oldest = pipe(checkpoints, last, getOrUndefined$2);
255437
+ if (isUndefined(oldest)) return yield* new GitError({ message: "Checkpoint state is invalid." });
255438
+ const backupRef = `refs/deslop/backups/${Date.now()}-${randomUUID()}`;
255439
+ yield* pipe(git.string(config.cwd, [
255440
+ "update-ref",
255441
+ backupRef,
255442
+ "HEAD"
255443
+ ]), asVoid);
255444
+ yield* pipe(git.string(config.cwd, [
255445
+ "reset",
255446
+ "--soft",
255447
+ `${oldest.hash}^`
255448
+ ]), asVoid);
255449
+ yield* pipe(commitAll(message), catchTag("GitError", (error) => pipe(git.string(config.cwd, [
255450
+ "reset",
255451
+ "--hard",
255452
+ backupRef
255453
+ ]), ignore$4, andThen(fail$3(error)))));
255454
+ return true;
255455
+ });
255456
+ const pushed = yield* pipe(hasPushableCommits, flatMap$2((shouldPush) => shouldPush ? pipe(push, as$1(true)) : succeed$3(false)));
255457
+ const pr = yield* pipe(upsertDraftPullRequest(message), catchTag("GitError", (error) => new GitError({
255458
+ cause: error,
255459
+ message: `${committed ? "Changes were committed. " : ""}${pushed ? "Changes were pushed. " : ""}PR update failed: ${error.message}`
255460
+ })));
255461
+ if (!committed && !pushed && isNone(pr)) return yield* new GitError({ message: "No changes, unpushed commits, or pull request to publish." });
255462
+ yield* set(pullRequest, pr);
255463
+ return getOrUndefined$2(pr);
255464
+ }),
255465
+ pullRequest
255466
+ };
255548
255467
  }) }) {
255549
255468
  static layer = flow(this.make, (layer) => pipe(effect(this, layer), provide$2(GitCommand.layer)));
255550
255469
  };
@@ -255703,7 +255622,7 @@ function commandAt(words, index) {
255703
255622
  };
255704
255623
  }
255705
255624
  function frameworkCommand(source) {
255706
- const words = pipe(split$1(/\s+/u)(source), filter$5(isNonEmpty$1));
255625
+ const words = pipe(split$1(/\s+/u)(source), filter$4(isNonEmpty$1));
255707
255626
  if (isReadonlyArrayEmpty(words)) return;
255708
255627
  const first = words[0];
255709
255628
  if (isUndefined(first)) return;
@@ -255776,7 +255695,7 @@ const discover = fnUntraced(function* (cwd, input) {
255776
255695
  "package.json",
255777
255696
  "**/package.json"
255778
255697
  ], { cwd }));
255779
- const packageManifests = yield* pipe(pipe(split$1("\n")(output), filter$5((packagePath) => packagePath === "package.json" || endsWith("/package.json")(packagePath))), forEach$1((packagePath) => pipe(fs.readFileString(path.join(cwd, packagePath)), flatMap$2((source) => try_({
255698
+ const packageManifests = yield* pipe(pipe(split$1("\n")(output), filter$4((packagePath) => packagePath === "package.json" || endsWith("/package.json")(packagePath))), forEach$1((packagePath) => pipe(fs.readFileString(path.join(cwd, packagePath)), flatMap$2((source) => try_({
255780
255699
  catch: identity,
255781
255700
  try: () => packageJson(source)
255782
255701
  })), match$1({
@@ -255940,7 +255859,7 @@ const proxyWebSocket = fnUntraced(function* (request, origin) {
255940
255859
  const hops = Number.parseInt(request.headers["x-portless-hops"] ?? "", 10);
255941
255860
  if ((Number.isFinite(hops) ? hops : 0) >= 5) return loopDetectedResponse(Number.isFinite(hops) ? hops : 0);
255942
255861
  const [pathname = "/", search = ""] = split$1(request.url, "?");
255943
- const protocols = pipe(fromUndefinedOr(request.headers["sec-websocket-protocol"]), map$9((header) => pipe(header, split$1(","), map$7(trim), filter$5(isNonEmpty$1))));
255862
+ const protocols = pipe(fromUndefinedOr(request.headers["sec-websocket-protocol"]), map$9((header) => pipe(header, split$1(","), map$7(trim), filter$4(isNonEmpty$1))));
255944
255863
  const inbound = yield* request.upgrade;
255945
255864
  const upstreamUrl = new URL(origin);
255946
255865
  upstreamUrl.protocol = upstreamUrl.protocol === "https:" ? "wss:" : "ws:";
@@ -256063,7 +255982,7 @@ var Portless = class Portless extends Service()("@deslop/portless/service/Portle
256063
255982
  const existing = get$2(state.ports, key);
256064
255983
  if (isSome(existing)) return existing.value;
256065
255984
  for (const candidatePort of range$1(4e3, 4999)) {
256066
- if (has(browserBlockedPorts, candidatePort) || contains(fromIterable$6(values(state.ports)), candidatePort)) continue;
255985
+ if (has(browserBlockedPorts, candidatePort) || contains(fromIterable$4(values(state.ports)), candidatePort)) continue;
256067
255986
  if (yield* portAvailable(candidatePort)) {
256068
255987
  state.ports = set$3(state.ports, key, candidatePort);
256069
255988
  return candidatePort;
@@ -256173,7 +256092,7 @@ function removePackageScripts(current, cwd) {
256173
256092
  return filter$1(current, (script) => script.cwd !== cwd);
256174
256093
  }
256175
256094
  function makeAgentSession(input) {
256176
- const labelCount = pipe(fromIterable$6(values(input.sessions)), filter$5((agentSession) => agentSession.cwd === input.cwd && agentSession.profileId === input.profile.id), length$1);
256095
+ const labelCount = pipe(fromIterable$4(values(input.sessions)), filter$4((agentSession) => agentSession.cwd === input.cwd && agentSession.profileId === input.profile.id), length$1);
256177
256096
  return {
256178
256097
  args: [...input.preparedCommand.args],
256179
256098
  command: input.preparedCommand.command,
@@ -256211,8 +256130,14 @@ const TerminalSessions = make$46({
256211
256130
  return get$10(yield* buildWithScope(Terminal.layer(config), yield* scope), Terminal);
256212
256131
  })
256213
256132
  });
256133
+ const GitChangesSessions = make$46({
256134
+ idleTimeToLive: seconds(30),
256135
+ lookup: fnUntraced(function* (cwd) {
256136
+ return get$10(yield* buildWithScope(GitChanges.layer({ cwd }), yield* scope), GitChanges);
256137
+ })
256138
+ });
256214
256139
  const GitReviewSessions = make$46({
256215
- idleTimeToLive: minutes(10),
256140
+ idleTimeToLive: infinity,
256216
256141
  lookup: fnUntraced(function* (cwd) {
256217
256142
  return get$10(yield* buildWithScope(GitReview.layer({ cwd }), yield* scope), GitReview);
256218
256143
  })
@@ -256275,6 +256200,7 @@ ${patches}`;
256275
256200
  const RpcHandlers = RpcContracts.toLayer(gen(function* () {
256276
256201
  const git = yield* GitWorkspace;
256277
256202
  const terminals = yield* TerminalSessions;
256203
+ const gitChanges = yield* GitChangesSessions;
256278
256204
  const gitReviews = yield* GitReviewSessions;
256279
256205
  const gitPublishes = yield* GitPublishSessions;
256280
256206
  const publishAgents = yield* PublishAgentSessions;
@@ -256392,7 +256318,7 @@ const RpcHandlers = RpcContracts.toLayer(gen(function* () {
256392
256318
  }), andThen(update$1(portlessScripts, (current) => removePortlessScript(current, script.script).current)), andThen(pipe(invalidate(portlessWorktrees, script.script.cwd), ignore$4)), andThen(pipe(invalidate(scriptWorktrees, script.script.cwd), ignore$4)), andThen(invalidateTerminal(input)), andThen(update$1(portlessStatusWatchers, (current) => remove$2(current, watcherKey)))) : void_$1))), forkDetach);
256393
256319
  });
256394
256320
  const currentAgentSessions = fnUntraced(function* (cwd) {
256395
- return pipe(fromIterable$6(values(yield* get(agents))), filter$5((session) => session.cwd === cwd));
256321
+ return pipe(fromIterable$4(values(yield* get(agents))), filter$4((session) => session.cwd === cwd));
256396
256322
  });
256397
256323
  const sidebarSnapshot = fnUntraced(function* () {
256398
256324
  const statuses = yield* get(runStatuses);
@@ -256503,18 +256429,28 @@ const RpcHandlers = RpcContracts.toLayer(gen(function* () {
256503
256429
  "projects.branches": (payload) => git.branches(payload.cwd),
256504
256430
  "projects.createWorktree": (payload) => git.createWorktree(payload),
256505
256431
  "projects.deleteWorktree": (payload) => pipe(portless.clear(payload.cwd), andThen(invalidate(portlessWorktrees, payload.cwd)), andThen(invalidate(scriptWorktrees, payload.cwd)), andThen(update$1(packageScripts, (current) => removePackageScripts(current, payload.cwd))), andThen(git.deleteWorktree(payload))),
256506
- "projects.fix": (payload) => git.fix(payload.cwd),
256507
- "publish.approve": (payload) => pipe(get$6(gitPublishes, payload.cwd), flatMap$2((publish) => publish.approve({ message: payload.message }))),
256432
+ "projects.maintenance": (payload) => git.maintenance(payload.cwd),
256433
+ "publish.checkpoint": (payload) => pipe(get$6(gitPublishes, payload.cwd), flatMap$2((publish) => publish.checkpoint), andThen(invalidate(gitChanges, payload.cwd))),
256508
256434
  "publish.message.generate": fn("WorkbenchRpc.publish.message.generate")(function* (payload) {
256509
256435
  return yield* scoped$1(gen(function* () {
256510
- const review = yield* get$6(gitReviews, payload.cwd);
256511
- const diffs = yield* review.reviewDiffs(GitReviewChangesTarget.make({}));
256512
- if (isReadonlyArrayEmpty(diffs)) return yield* new GitError({ message: "No current changes to summarize." });
256513
- const metadata = yield* review.metadata;
256436
+ const changes = yield* get$6(gitChanges, payload.cwd);
256437
+ const changesDiffs = yield* get(yield* changes.diffs(GitReviewChangesTarget.make({})));
256438
+ const metadata = yield* get(changes.metadata);
256439
+ const checkpointCommits = takeWhile(metadata.localCommits, (commit) => commit.checkpoint);
256440
+ const promptDiffs = map$7(yield* gen(function* () {
256441
+ if (!isReadonlyArrayEmpty(changesDiffs)) return changesDiffs;
256442
+ if (isReadonlyArrayEmpty(checkpointCommits)) return changesDiffs;
256443
+ return yield* get(yield* changes.diffs(GitReviewLocalTarget.make({})));
256444
+ }), (diff) => ({
256445
+ filePath: diff.filePath,
256446
+ patch: diff.patch ?? "",
256447
+ status: diff.status
256448
+ }));
256449
+ if (isReadonlyArrayEmpty(promptDiffs)) return yield* new GitError({ message: "No current changes to summarize." });
256514
256450
  const recentSubjects = pipe(appendAll(metadata.localCommits, metadata.branchCommits), take$3(10), map$7((commit) => commit.subject));
256515
256451
  const text = yield* pipe((yield* get$6(publishAgents, payload.cwd)).prompt({
256516
256452
  messages: [makeMessage("user", { content: [makePart$1("text", { text: draftCommitPrompt({
256517
- diffs,
256453
+ diffs: promptDiffs,
256518
256454
  recentSubjects
256519
256455
  }) })] })],
256520
256456
  model: "gpt-5.5",
@@ -256525,15 +256461,13 @@ const RpcHandlers = RpcContracts.toLayer(gen(function* () {
256525
256461
  return text;
256526
256462
  }));
256527
256463
  }),
256528
- "review.comments.resolve": (payload) => pipe(get$6(gitReviews, payload.cwd), flatMap$2((review) => isUndefined(payload.threadId) ? review.resolveComment({
256529
- filePath: payload.filePath,
256530
- lineNumber: payload.lineNumber,
256531
- side: payload.side
256532
- }) : review.resolveReviewThread(payload.threadId))),
256464
+ "publish.publish": (payload) => pipe(get$6(gitPublishes, payload.cwd), flatMap$2((publish) => publish.publish({ message: payload.message })), tap(() => invalidate(gitChanges, payload.cwd))),
256465
+ "publish.pullRequest": (payload) => unwrap$1(pipe(get$6(gitPublishes, payload.cwd), map$4((publish) => pipe(changes(publish.pullRequest), map$2(getOrUndefined$2))))),
256466
+ "review.comments.resolve": (payload) => pipe(get$6(gitReviews, payload.cwd), flatMap$2((review) => review.resolveComments(payload.comments))),
256533
256467
  "review.comments.save": (payload) => pipe(get$6(gitReviews, payload.cwd), flatMap$2((review) => review.saveComment(payload.comment))),
256534
- "review.diffs": (payload) => unwrap$1(pipe(get$6(gitReviews, payload.cwd), map$4((review) => review.watchReviewDiffs(payload.target)))),
256535
- "review.metadata": (payload) => unwrap$1(pipe(get$6(gitReviews, payload.cwd), map$4((review) => review.watchReviewMetadata()))),
256536
- "review.state": (payload) => unwrap$1(pipe(get$6(gitReviews, payload.cwd), map$4((review) => review.watchReviewState()))),
256468
+ "review.diffs": (payload) => unwrap$1(pipe(get$6(gitChanges, payload.cwd), flatMap$2((changes) => changes.diffs(payload.target)), map$4(changes))),
256469
+ "review.metadata": (payload) => unwrap$1(pipe(get$6(gitChanges, payload.cwd), map$4((changes$2) => changes(changes$2.metadata)))),
256470
+ "review.state": (payload) => unwrap$1(pipe(get$6(gitReviews, payload.cwd), map$4((review) => changes(review.state)))),
256537
256471
  "review.state.mark": (payload) => pipe(get$6(gitReviews, payload.cwd), flatMap$2((review) => review.mark(payload.marks))),
256538
256472
  "review.state.unmark": (payload) => pipe(get$6(gitReviews, payload.cwd), flatMap$2((review) => review.unmark(payload.marks))),
256539
256473
  "runs.portless": (payload) => get$6(portlessWorktrees, payload.cwd),
@@ -256571,7 +256505,7 @@ const RpcHandlers = RpcContracts.toLayer(gen(function* () {
256571
256505
  sessionId: input.sessionId
256572
256506
  });
256573
256507
  const session = pipe(yield* get$1(resolvedTerminals), get$2(statusKey), getOrUndefined$2);
256574
- if (!(isUndefined(session) ? false : some(fromIterable$6(yield* keys$1(terminals)), (current) => equals$2(TerminalSessionIdentity.make(current), TerminalSessionIdentity.make(session)))) || isUndefined(session)) return pipe(make$43(isUndefined(scriptKey) ? {
256508
+ if (!(isUndefined(session) ? false : some(fromIterable$4(yield* keys$1(terminals)), (current) => equals$2(TerminalSessionIdentity.make(current), TerminalSessionIdentity.make(session)))) || isUndefined(session)) return pipe(make$43(isUndefined(scriptKey) ? {
256575
256509
  state: "idle",
256576
256510
  title: ""
256577
256511
  } : pipe(yield* get(runStatuses), get$2(scriptKey), getOrElse$1(() => ({