@jpkme/diff-review 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -0
  3. package/bin/diff-review.mjs +175 -0
  4. package/package.json +53 -0
  5. package/schema.json +158 -0
  6. package/web/build/client/_app/immutable/assets/0.CLQiq3Ed.css +1 -0
  7. package/web/build/client/_app/immutable/assets/0.CLQiq3Ed.css.br +0 -0
  8. package/web/build/client/_app/immutable/assets/0.CLQiq3Ed.css.gz +0 -0
  9. package/web/build/client/_app/immutable/assets/2.CHpfuCyA.css +1 -0
  10. package/web/build/client/_app/immutable/assets/2.CHpfuCyA.css.br +0 -0
  11. package/web/build/client/_app/immutable/assets/2.CHpfuCyA.css.gz +0 -0
  12. package/web/build/client/_app/immutable/assets/3.BiNtcbA-.css +1 -0
  13. package/web/build/client/_app/immutable/assets/3.BiNtcbA-.css.br +0 -0
  14. package/web/build/client/_app/immutable/assets/3.BiNtcbA-.css.gz +0 -0
  15. package/web/build/client/_app/immutable/assets/ThemeToggle.DcT79vPK.css +1 -0
  16. package/web/build/client/_app/immutable/assets/ThemeToggle.DcT79vPK.css.br +0 -0
  17. package/web/build/client/_app/immutable/assets/ThemeToggle.DcT79vPK.css.gz +0 -0
  18. package/web/build/client/_app/immutable/chunks/BZfQ9INH.js +1 -0
  19. package/web/build/client/_app/immutable/chunks/BZfQ9INH.js.br +0 -0
  20. package/web/build/client/_app/immutable/chunks/BZfQ9INH.js.gz +0 -0
  21. package/web/build/client/_app/immutable/chunks/Bjy-W4x2.js +81 -0
  22. package/web/build/client/_app/immutable/chunks/Bjy-W4x2.js.br +0 -0
  23. package/web/build/client/_app/immutable/chunks/Bjy-W4x2.js.gz +0 -0
  24. package/web/build/client/_app/immutable/chunks/Czsj8nEn.js +114 -0
  25. package/web/build/client/_app/immutable/chunks/Czsj8nEn.js.br +0 -0
  26. package/web/build/client/_app/immutable/chunks/Czsj8nEn.js.gz +0 -0
  27. package/web/build/client/_app/immutable/chunks/DWi-qjc4.js +2 -0
  28. package/web/build/client/_app/immutable/chunks/DWi-qjc4.js.br +0 -0
  29. package/web/build/client/_app/immutable/chunks/DWi-qjc4.js.gz +0 -0
  30. package/web/build/client/_app/immutable/chunks/Dur-vdUf.js +3 -0
  31. package/web/build/client/_app/immutable/chunks/Dur-vdUf.js.br +0 -0
  32. package/web/build/client/_app/immutable/chunks/Dur-vdUf.js.gz +0 -0
  33. package/web/build/client/_app/immutable/chunks/tYeNsyIK.js +1 -0
  34. package/web/build/client/_app/immutable/chunks/tYeNsyIK.js.br +0 -0
  35. package/web/build/client/_app/immutable/chunks/tYeNsyIK.js.gz +0 -0
  36. package/web/build/client/_app/immutable/entry/app.BTcBsRpu.js +2 -0
  37. package/web/build/client/_app/immutable/entry/app.BTcBsRpu.js.br +0 -0
  38. package/web/build/client/_app/immutable/entry/app.BTcBsRpu.js.gz +0 -0
  39. package/web/build/client/_app/immutable/entry/start.kanpokEP.js +1 -0
  40. package/web/build/client/_app/immutable/entry/start.kanpokEP.js.br +1 -0
  41. package/web/build/client/_app/immutable/entry/start.kanpokEP.js.gz +0 -0
  42. package/web/build/client/_app/immutable/nodes/0.s5xmjXoS.js +1 -0
  43. package/web/build/client/_app/immutable/nodes/0.s5xmjXoS.js.br +1 -0
  44. package/web/build/client/_app/immutable/nodes/0.s5xmjXoS.js.gz +0 -0
  45. package/web/build/client/_app/immutable/nodes/1.CgR83Zma.js +1 -0
  46. package/web/build/client/_app/immutable/nodes/1.CgR83Zma.js.br +0 -0
  47. package/web/build/client/_app/immutable/nodes/1.CgR83Zma.js.gz +0 -0
  48. package/web/build/client/_app/immutable/nodes/2.CQ9hcOOu.js +66 -0
  49. package/web/build/client/_app/immutable/nodes/2.CQ9hcOOu.js.br +0 -0
  50. package/web/build/client/_app/immutable/nodes/2.CQ9hcOOu.js.gz +0 -0
  51. package/web/build/client/_app/immutable/nodes/3.DkvQklXV.js +170 -0
  52. package/web/build/client/_app/immutable/nodes/3.DkvQklXV.js.br +0 -0
  53. package/web/build/client/_app/immutable/nodes/3.DkvQklXV.js.gz +0 -0
  54. package/web/build/client/_app/version.json +1 -0
  55. package/web/build/client/_app/version.json.br +0 -0
  56. package/web/build/client/_app/version.json.gz +0 -0
  57. package/web/build/env.js +70 -0
  58. package/web/build/env.js.map +1 -0
  59. package/web/build/handler.js +16 -0
  60. package/web/build/handler.js.map +1 -0
  61. package/web/build/index.js +139 -0
  62. package/web/build/index.js.map +1 -0
  63. package/web/build/server/chunks/0-SseDy__O.js +11 -0
  64. package/web/build/server/chunks/0-SseDy__O.js.map +1 -0
  65. package/web/build/server/chunks/1-D4XIiB5g.js +7 -0
  66. package/web/build/server/chunks/1-D4XIiB5g.js.map +1 -0
  67. package/web/build/server/chunks/2-VwXkm01F.js +7 -0
  68. package/web/build/server/chunks/2-VwXkm01F.js.map +1 -0
  69. package/web/build/server/chunks/3-g6WScD1p.js +7 -0
  70. package/web/build/server/chunks/3-g6WScD1p.js.map +1 -0
  71. package/web/build/server/chunks/handler-UGld1DQG.js +1473 -0
  72. package/web/build/server/chunks/handler-UGld1DQG.js.map +1 -0
  73. package/web/build/server/chunks/hooks.server-DERFlOe1.js +14 -0
  74. package/web/build/server/chunks/hooks.server-DERFlOe1.js.map +1 -0
  75. package/web/build/server/chunks/index.js-A_Q670iW.js +11784 -0
  76. package/web/build/server/chunks/index.js-A_Q670iW.js.map +1 -0
  77. package/web/build/server/chunks/manifest.js-CbjlFtox.js +50 -0
  78. package/web/build/server/chunks/manifest.js-CbjlFtox.js.map +1 -0
  79. package/web/build/server/chunks/remote-1g4ayag-ipWXvkjB.js +2239 -0
  80. package/web/build/server/chunks/remote-1g4ayag-ipWXvkjB.js.map +1 -0
  81. package/web/build/shims.js +33 -0
  82. package/web/build/shims.js.map +1 -0
@@ -0,0 +1,2239 @@
1
+ import { e as error, g as get_request_store, b as stringify_remote_arg, M as MUTATIVE_METHODS, w as with_request_store, c as create_remote_key, n as noop, H as HttpError, S as SvelteKitError, h as handle_error_and_jsonify, _ as __exportAll, i as init_remote_functions } from './index.js-A_Q670iW.js';
2
+ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ import { execFileSync, spawnSync } from 'node:child_process';
5
+ import { createHash } from 'node:crypto';
6
+ import { tmpdir, platform } from 'node:os';
7
+ import process from 'node:process';
8
+
9
+ //#region node_modules/@sveltejs/kit/src/runtime/app/server/remote/shared.js
10
+ /** @import { RequestEvent } from '@sveltejs/kit' */
11
+ /** @import { ServerHooks, MaybePromise, RequestState, RemoteInternals, RequestStore, RemoteLiveQueryUserFunctionReturnType } from 'types' */
12
+ /**
13
+ * @param {any} validate_or_fn
14
+ * @param {((arg?: any) => any) | undefined} [maybe_fn]
15
+ * @returns {(arg?: any) => MaybePromise<any>}
16
+ */
17
+ function create_validator(validate_or_fn, maybe_fn) {
18
+ if (!maybe_fn) return (arg) => {
19
+ if (arg !== void 0) error(400, "Bad Request");
20
+ };
21
+ if (validate_or_fn === "unchecked") return (arg) => arg;
22
+ if ("~standard" in validate_or_fn) return async (arg) => {
23
+ const { event, state } = get_request_store();
24
+ const result = await validate_or_fn["~standard"].validate(arg);
25
+ if (result.issues) error(400, await state.handleValidationError({
26
+ issues: result.issues,
27
+ event
28
+ }));
29
+ return result.value;
30
+ };
31
+ throw new Error("Invalid validator passed to remote function. Expected \"unchecked\" or a Standard Schema (https://standardschema.dev)");
32
+ }
33
+ /**
34
+ * In case of a single remote function call, just returns the result.
35
+ *
36
+ * In case of a full page reload, returns the response for a remote function call,
37
+ * either from the cache or by invoking the function.
38
+ * Also saves an uneval'ed version of the result for later HTML inlining for hydration.
39
+ *
40
+ * @template {MaybePromise<any>} T
41
+ * @param {RemoteInternals} internals
42
+ * @param {string} payload — the stringified raw argument (i.e. the cache key the client will use)
43
+ * @param {RequestState} state
44
+ * @param {() => Promise<T>} get_result
45
+ * @returns {Promise<T>}
46
+ */
47
+ async function get_response(internals, payload, state, get_result) {
48
+ await 0;
49
+ const cache = get_cache(internals, state);
50
+ if (!state.is_in_remote_query) get_implicit_lookup(internals, state)[payload] = get_result;
51
+ return cache[payload] ??= get_result();
52
+ }
53
+ /**
54
+ * @param {RequestEvent} event
55
+ * @param {RequestState} state
56
+ * @param {boolean} allow_cookies
57
+ * @returns {RequestStore}
58
+ */
59
+ function derive_remote_function_event(event, state, allow_cookies) {
60
+ return {
61
+ event: {
62
+ ...event,
63
+ setHeaders: () => {
64
+ throw new Error("setHeaders is not allowed in remote functions");
65
+ },
66
+ cookies: {
67
+ ...event.cookies,
68
+ set: (name, value, opts) => {
69
+ if (!allow_cookies) throw new Error("Cannot set cookies in `query` or `prerender` functions");
70
+ if (opts.path && !opts.path.startsWith("/")) throw new Error("Cookies set in remote functions must have an absolute path");
71
+ return event.cookies.set(name, value, opts);
72
+ },
73
+ delete: (name, opts) => {
74
+ if (!allow_cookies) throw new Error("Cannot delete cookies in `query` or `prerender` functions");
75
+ if (opts.path && !opts.path.startsWith("/")) throw new Error("Cookies deleted in remote functions must have an absolute path");
76
+ return event.cookies.delete(name, opts);
77
+ }
78
+ }
79
+ },
80
+ state: {
81
+ ...state,
82
+ is_in_remote_function: true
83
+ }
84
+ };
85
+ }
86
+ /**
87
+ * Like `with_event` but removes things from `event` you cannot see/call in remote functions, such as `setHeaders`.
88
+ * @template T
89
+ * @param {RequestEvent} event
90
+ * @param {RequestState} state
91
+ * @param {boolean} allow_cookies
92
+ * @param {() => any} get_input
93
+ * @param {(arg?: any) => T} fn
94
+ */
95
+ async function run_remote_function(event, state, allow_cookies, get_input, fn) {
96
+ const store = derive_remote_function_event(event, state, allow_cookies);
97
+ const input = await with_request_store(store, get_input);
98
+ return with_request_store(store, () => fn(input));
99
+ }
100
+ /**
101
+ * Like `with_event` but removes things from `event` you cannot see/call in remote functions, such as `setHeaders`.
102
+ * @template T
103
+ * @param {RequestEvent} event
104
+ * @param {RequestState} state
105
+ * @param {boolean} allow_cookies
106
+ * @param {() => any} get_input
107
+ * @param {(arg?: any) => RemoteLiveQueryUserFunctionReturnType<T>} fn
108
+ * @param {string} name
109
+ */
110
+ async function* run_remote_generator(event, state, allow_cookies, get_input, fn, name) {
111
+ const store = derive_remote_function_event(event, state, allow_cookies);
112
+ const input = await with_request_store(store, get_input);
113
+ const iterator = to_iterator(await with_request_store(store, () => fn(input)), name);
114
+ let done = false;
115
+ try {
116
+ while (true) {
117
+ const result = await with_request_store(store, () => iterator.next());
118
+ if (result.done) {
119
+ done = true;
120
+ return result.value;
121
+ }
122
+ yield result.value;
123
+ }
124
+ } finally {
125
+ if (!done && typeof iterator.return === "function") await with_request_store(store, () => iterator.return?.(void 0));
126
+ }
127
+ }
128
+ /**
129
+ * @template T
130
+ * @param {Awaited<RemoteLiveQueryUserFunctionReturnType<T>>} source
131
+ * @param {string} name
132
+ * @returns {Iterator<T> | AsyncIterator<T>}
133
+ */
134
+ function to_iterator(source, name) {
135
+ if ("next" in source && typeof source.next === "function") return source;
136
+ if (Symbol.asyncIterator in source && typeof source[Symbol.asyncIterator] === "function") return source[Symbol.asyncIterator]();
137
+ if (Symbol.iterator in source && typeof source[Symbol.iterator] === "function") return source[Symbol.iterator]();
138
+ throw new Error(`query.live '${name}' must return an Iterator, Iterable, AsyncIterator or AsyncIterable`);
139
+ }
140
+ /**
141
+ * Note that `state` is deliberately not optional: resources that capture the request
142
+ * state at creation must pass it explicitly, because reading it from the request store
143
+ * at call time is only equivalent on runtimes with `AsyncLocalStorage` support.
144
+ * Callers without a captured state (such as the module-level `form` instance getters)
145
+ * should pass `get_request_store().state` themselves.
146
+ * @param {RemoteInternals} internals
147
+ * @param {RequestState} state
148
+ */
149
+ function get_cache(internals, state) {
150
+ let cache = state.remote.data?.get(internals);
151
+ if (cache === void 0) {
152
+ cache = {};
153
+ (state.remote.data ??= /* @__PURE__ */ new Map()).set(internals, cache);
154
+ }
155
+ return cache;
156
+ }
157
+ /**
158
+ * @param {RemoteInternals} internals
159
+ * @param {RequestState} state
160
+ */
161
+ function get_implicit_lookup(internals, state) {
162
+ let cache = state.remote.implicit?.get(internals);
163
+ if (cache === void 0) {
164
+ cache = {};
165
+ (state.remote.implicit ??= /* @__PURE__ */ new Map()).set(internals, cache);
166
+ }
167
+ return cache;
168
+ }
169
+ //#endregion
170
+ //#region node_modules/@sveltejs/kit/src/runtime/app/server/remote/command.js
171
+ /** @import { RemoteCommand } from '@sveltejs/kit' */
172
+ /** @import { MaybePromise, RemoteCommandInternals } from 'types' */
173
+ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
174
+ /**
175
+ * Creates a remote command. When called from the browser, the function will be invoked on the server via a `fetch` call.
176
+ *
177
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#command) for full documentation.
178
+ *
179
+ * @template Output
180
+ * @overload
181
+ * @param {() => MaybePromise<Output>} fn
182
+ * @returns {RemoteCommand<void, Output>}
183
+ * @since 2.27
184
+ */
185
+ /**
186
+ * Creates a remote command. When called from the browser, the function will be invoked on the server via a `fetch` call.
187
+ *
188
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#command) for full documentation.
189
+ *
190
+ * @template Input
191
+ * @template Output
192
+ * @overload
193
+ * @param {'unchecked'} validate
194
+ * @param {(arg: Input) => MaybePromise<Output>} fn
195
+ * @returns {RemoteCommand<Input, Output>}
196
+ * @since 2.27
197
+ */
198
+ /**
199
+ * Creates a remote command. When called from the browser, the function will be invoked on the server via a `fetch` call.
200
+ *
201
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#command) for full documentation.
202
+ *
203
+ * @template {StandardSchemaV1} Schema
204
+ * @template Output
205
+ * @overload
206
+ * @param {Schema} validate
207
+ * @param {(arg: StandardSchemaV1.InferOutput<Schema>) => MaybePromise<Output>} fn
208
+ * @returns {RemoteCommand<StandardSchemaV1.InferInput<Schema>, Output>}
209
+ * @since 2.27
210
+ */
211
+ /**
212
+ * @template Input
213
+ * @template Output
214
+ * @param {any} validate_or_fn
215
+ * @param {(arg?: Input) => MaybePromise<Output>} [maybe_fn]
216
+ * @returns {RemoteCommand<Input, Output>}
217
+ * @since 2.27
218
+ */
219
+ /*@__NO_SIDE_EFFECTS__*/
220
+ function command(validate_or_fn, maybe_fn) {
221
+ /** @type {(arg?: Input) => MaybePromise<Output>} */
222
+ const fn = maybe_fn ?? validate_or_fn;
223
+ /** @type {(arg?: any) => MaybePromise<Input>} */
224
+ const validate = create_validator(validate_or_fn, maybe_fn);
225
+ /** @type {RemoteCommandInternals} */
226
+ const __ = {
227
+ type: "command",
228
+ id: "",
229
+ name: ""
230
+ };
231
+ /** @type {RemoteCommand<Input, Output> & { __: RemoteCommandInternals }} */
232
+ const wrapper = (arg) => {
233
+ const { event, state } = get_request_store();
234
+ if (!MUTATIVE_METHODS.includes(event.request.method)) throw new Error(`Cannot call a command (\`${__.name}(${maybe_fn ? "..." : ""})\`) from a ${event.request.method} handler`);
235
+ if (state.is_in_render) throw new Error(`Cannot call a command (\`${__.name}(${maybe_fn ? "..." : ""})\`) during server-side rendering`);
236
+ const promise = Promise.resolve(run_remote_function(event, state, true, () => validate(arg), fn));
237
+ promise.updates = () => {
238
+ throw new Error(`Cannot call '${__.name}(...).updates(...)' on the server`);
239
+ };
240
+ return promise;
241
+ };
242
+ Object.defineProperty(wrapper, "__", { value: __ });
243
+ Object.defineProperty(wrapper, "pending", { get: () => 0 });
244
+ return wrapper;
245
+ }
246
+ //#endregion
247
+ //#region node_modules/@sveltejs/kit/src/utils/shared-iterator.js
248
+ /**
249
+ * A pull-style async iterator that fans out a single stream of values to
250
+ * multiple `for await (...)` consumers. Each subscriber gets its own
251
+ * `AsyncGenerator` whose `.next()` resolves whenever a value is pushed via
252
+ * `push(value)`. Multiple consumers see the same values without each one
253
+ * driving an independent underlying source.
254
+ *
255
+ * Backpressure is **latest-wins**: if values arrive faster than a particular
256
+ * consumer drains its iterator, only the most-recently-pushed value is kept
257
+ * pending for that subscriber. Earlier undrained values are dropped. This is
258
+ * appropriate for live data streams (reactive state replication), not for
259
+ * event logs where every value must be delivered.
260
+ *
261
+ * Lifecycle hooks are exposed via the constructor:
262
+ *
263
+ * - `start()` is called when the subscriber count transitions
264
+ * from 0 to 1 (e.g. to start a pump pulling from a real source).
265
+ * - `stop()` is called when the subscriber count transitions
266
+ * from non-zero back to 0 (e.g. to tear down that pump).
267
+ *
268
+ * Either hook may be omitted.
269
+ *
270
+ * The owner is responsible for calling `push(value)` to broadcast values,
271
+ * `done()` to signal natural completion to all subscribers, and `fail(error)`
272
+ * to broadcast a terminal error. After `done()` or `fail()`, the iterator
273
+ * rejects further `subscribe()` calls with the terminal state appropriately.
274
+ *
275
+ * @template T
276
+ */
277
+ var SharedIterator = class {
278
+ /**
279
+ * @typedef {object} Subscriber
280
+ * @property {{ value: any } | null} pending
281
+ * @property {{ error: unknown } | null} pending_error
282
+ * @property {boolean} finished
283
+ * @property {((result: IteratorResult<any, void>) => void) | null} waiting_resolve
284
+ * @property {((reason: unknown) => void) | null} waiting_reject
285
+ */
286
+ /** @type {Set<Subscriber>} */
287
+ #subscribers = /* @__PURE__ */ new Set();
288
+ /** @type {((instance: SharedIterator<T>) => (() => void)) | undefined} */
289
+ #start = void 0;
290
+ /** @type {(() => void) | undefined} */
291
+ #stop = void 0;
292
+ /** Once `done()` or `fail()` has been broadcast, no new values are accepted. */
293
+ #closed = false;
294
+ /** @type {unknown} */
295
+ #terminal_error = void 0;
296
+ /** Whether `done()` or `fail()` has been broadcast. */
297
+ get closed() {
298
+ return this.#closed;
299
+ }
300
+ /**
301
+ * @param {(instance: SharedIterator<T>) => (() => void)} [start]
302
+ */
303
+ constructor(start) {
304
+ this.#start = start;
305
+ }
306
+ /** @param {T} value */
307
+ push(value) {
308
+ if (this.#closed) return;
309
+ for (const subscriber of this.#subscribers) if (subscriber.waiting_resolve) {
310
+ const resolve = subscriber.waiting_resolve;
311
+ subscriber.waiting_resolve = null;
312
+ subscriber.waiting_reject = null;
313
+ resolve({
314
+ value,
315
+ done: false
316
+ });
317
+ } else subscriber.pending = { value };
318
+ }
319
+ /**
320
+ * Signal natural completion to all current subscribers, and to any future
321
+ * subscriber (which will receive an immediately-done iterator).
322
+ */
323
+ done() {
324
+ if (this.#closed) return;
325
+ this.#closed = true;
326
+ for (const subscriber of this.#subscribers) {
327
+ subscriber.finished = true;
328
+ if (subscriber.waiting_resolve) {
329
+ const resolve = subscriber.waiting_resolve;
330
+ subscriber.waiting_resolve = null;
331
+ subscriber.waiting_reject = null;
332
+ resolve({
333
+ value: void 0,
334
+ done: true
335
+ });
336
+ }
337
+ }
338
+ this.#subscribers.clear();
339
+ }
340
+ /**
341
+ * Broadcast a terminal error. All current subscribers will reject their
342
+ * next `.next()` call with `error`. Future subscribers will also reject
343
+ * their first `.next()`.
344
+ *
345
+ * @param {unknown} error
346
+ */
347
+ fail(error) {
348
+ if (this.#closed) return;
349
+ this.#closed = true;
350
+ this.#terminal_error = error;
351
+ for (const subscriber of this.#subscribers) {
352
+ subscriber.finished = true;
353
+ if (subscriber.waiting_reject) {
354
+ const reject = subscriber.waiting_reject;
355
+ subscriber.waiting_resolve = null;
356
+ subscriber.waiting_reject = null;
357
+ reject(error);
358
+ } else subscriber.pending_error = { error };
359
+ }
360
+ this.#subscribers.clear();
361
+ }
362
+ /**
363
+ * Subscribe to the shared stream. Returns an `AsyncGenerator<T>` that
364
+ * yields every value pushed after this call (and, if `initial_value` is
365
+ * provided, that value as the first yield).
366
+ *
367
+ * @param {{ initial_value?: { value: T } }} [options]
368
+ * `initial_value` lets the caller seed the iterator with a synchronously-
369
+ * available current value before any new pushes arrive (e.g. the
370
+ * "last-seen value" of a reactive resource). Pass it wrapped in an
371
+ * object so `undefined` can be distinguished from "no initial value".
372
+ * @returns {AsyncGenerator<T, void, void>}
373
+ */
374
+ subscribe(options) {
375
+ /** @type {Subscriber} */
376
+ const subscriber = {
377
+ pending: options?.initial_value ? { value: options.initial_value.value } : null,
378
+ pending_error: this.#closed && this.#terminal_error !== void 0 ? { error: this.#terminal_error } : null,
379
+ finished: this.#closed && this.#terminal_error === void 0,
380
+ waiting_resolve: null,
381
+ waiting_reject: null
382
+ };
383
+ if (!subscriber.finished && subscriber.pending_error === null) this.#subscribers.add(subscriber);
384
+ if (!this.#closed) this.#stop ??= this.#start?.(this);
385
+ const unsubscribe = () => {
386
+ subscriber.finished = true;
387
+ if (this.#subscribers.delete(subscriber) && this.#subscribers.size === 0) this.#stop?.();
388
+ };
389
+ /** @type {AsyncGenerator<T, void, void>} */
390
+ const iterator = {
391
+ next() {
392
+ if (subscriber.pending_error) {
393
+ const { error } = subscriber.pending_error;
394
+ subscriber.pending_error = null;
395
+ unsubscribe();
396
+ return Promise.reject(error);
397
+ }
398
+ if (subscriber.pending) {
399
+ const { value } = subscriber.pending;
400
+ subscriber.pending = null;
401
+ return Promise.resolve({
402
+ value,
403
+ done: false
404
+ });
405
+ }
406
+ if (subscriber.finished) return Promise.resolve({
407
+ value: void 0,
408
+ done: true
409
+ });
410
+ return new Promise((resolve, reject) => {
411
+ subscriber.waiting_resolve = resolve;
412
+ subscriber.waiting_reject = reject;
413
+ });
414
+ },
415
+ return(value) {
416
+ unsubscribe();
417
+ if (subscriber.waiting_resolve) {
418
+ const resolve = subscriber.waiting_resolve;
419
+ subscriber.waiting_resolve = null;
420
+ subscriber.waiting_reject = null;
421
+ resolve({
422
+ value: void 0,
423
+ done: true
424
+ });
425
+ }
426
+ return Promise.resolve({
427
+ value,
428
+ done: true
429
+ });
430
+ },
431
+ throw(error) {
432
+ unsubscribe();
433
+ if (subscriber.waiting_reject) {
434
+ const reject = subscriber.waiting_reject;
435
+ subscriber.waiting_resolve = null;
436
+ subscriber.waiting_reject = null;
437
+ reject(error);
438
+ }
439
+ return Promise.reject(error);
440
+ },
441
+ [Symbol.asyncIterator]() {
442
+ return iterator;
443
+ }
444
+ };
445
+ return iterator;
446
+ }
447
+ };
448
+ //#endregion
449
+ //#region node_modules/@sveltejs/kit/src/runtime/app/server/remote/query.js
450
+ /** @import { RemoteLiveQuery, RemoteLiveQueryFunction, RemoteQuery, RemoteQueryFunction, RequestEvent } from '@sveltejs/kit' */
451
+ /** @import { RemoteInternals, MaybePromise, RequestState, RemoteQueryLiveInternals, RemoteQueryBatchInternals, RemoteQueryInternals, RemoteLiveQueryUserFunctionReturnType } from 'types' */
452
+ /** @import { StandardSchemaV1 } from '@standard-schema/spec' */
453
+ /**
454
+ * Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
455
+ *
456
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query) for full documentation.
457
+ *
458
+ * @template Output
459
+ * @overload
460
+ * @param {() => MaybePromise<Output>} fn
461
+ * @returns {RemoteQueryFunction<void, Output>}
462
+ * @since 2.27
463
+ */
464
+ /**
465
+ * Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
466
+ *
467
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query) for full documentation.
468
+ *
469
+ * @template Input
470
+ * @template Output
471
+ * @overload
472
+ * @param {'unchecked'} validate
473
+ * @param {(arg: Input) => MaybePromise<Output>} fn
474
+ * @returns {RemoteQueryFunction<Input, Output>}
475
+ * @since 2.27
476
+ */
477
+ /**
478
+ * Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
479
+ *
480
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query) for full documentation.
481
+ *
482
+ * @template {StandardSchemaV1} Schema
483
+ * @template Output
484
+ * @overload
485
+ * @param {Schema} schema
486
+ * @param {(arg: StandardSchemaV1.InferOutput<Schema>) => MaybePromise<Output>} fn
487
+ * @returns {RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, Output, StandardSchemaV1.InferOutput<Schema>>}
488
+ * @since 2.27
489
+ */
490
+ /**
491
+ * @template Input
492
+ * @template Output
493
+ * @param {any} validate_or_fn
494
+ * @param {(args?: Input) => MaybePromise<Output>} [maybe_fn]
495
+ * @returns {RemoteQueryFunction<Input, Output>}
496
+ * @since 2.27
497
+ */
498
+ /*@__NO_SIDE_EFFECTS__*/
499
+ function query(validate_or_fn, maybe_fn) {
500
+ /** @type {(arg?: Input) => Output} */
501
+ const fn = maybe_fn ?? validate_or_fn;
502
+ /** @type {(arg?: any) => MaybePromise<Input>} */
503
+ const validate = create_validator(validate_or_fn, maybe_fn);
504
+ /** @type {RemoteQueryInternals} */
505
+ const __ = {
506
+ type: "query",
507
+ id: "",
508
+ name: "",
509
+ validate,
510
+ bind(payload, validated_arg) {
511
+ const { event, state } = get_request_store();
512
+ return create_query_resource(__, payload, event, state, () => run_remote_function(event, {
513
+ ...state,
514
+ is_in_remote_query: true
515
+ }, false, () => validated_arg, fn));
516
+ }
517
+ };
518
+ /** @type {RemoteQueryFunction<Input, Output> & { __: RemoteQueryInternals }} */
519
+ const wrapper = (arg) => {
520
+ const { event, state } = get_request_store();
521
+ const payload = stringify_remote_arg(arg, state.transport);
522
+ return create_query_resource(__, payload, event, state, () => run_remote_function(event, {
523
+ ...state,
524
+ is_in_remote_query: true
525
+ }, false, () => validate(arg), fn));
526
+ };
527
+ Object.defineProperty(wrapper, "__", { value: __ });
528
+ return wrapper;
529
+ }
530
+ /**
531
+ * Creates a live remote query. When called from the browser, the function will be invoked on the server via a streaming `fetch` call.
532
+ *
533
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query.live) for full documentation.
534
+ *
535
+ * @template Output
536
+ * @overload
537
+ * @param {(arg: void) => RemoteLiveQueryUserFunctionReturnType<Output>} fn
538
+ * @returns {RemoteLiveQueryFunction<void, Output>}
539
+ */
540
+ /**
541
+ * @template Input
542
+ * @template Output
543
+ * @overload
544
+ * @param {'unchecked'} validate
545
+ * @param {(arg: Input) => RemoteLiveQueryUserFunctionReturnType<Output>} fn
546
+ * @returns {RemoteLiveQueryFunction<Input, Output>}
547
+ */
548
+ /**
549
+ * @template {StandardSchemaV1} Schema
550
+ * @template Output
551
+ * @overload
552
+ * @param {Schema} schema
553
+ * @param {(arg: StandardSchemaV1.InferOutput<Schema>) => RemoteLiveQueryUserFunctionReturnType<Output>} fn
554
+ * @returns {RemoteLiveQueryFunction<StandardSchemaV1.InferInput<Schema>, Output, StandardSchemaV1.InferOutput<Schema>>}
555
+ */
556
+ /**
557
+ * @template Input
558
+ * @template Output
559
+ * @param {any} validate_or_fn
560
+ * @param {(args: Input) => RemoteLiveQueryUserFunctionReturnType<Output>} [maybe_fn]
561
+ * @returns {RemoteLiveQueryFunction<Input, Output>}
562
+ */
563
+ /*@__NO_SIDE_EFFECTS__*/
564
+ function live(validate_or_fn, maybe_fn) {
565
+ /** @type {(arg: Input) => RemoteLiveQueryUserFunctionReturnType<Output>} */
566
+ const fn = maybe_fn ?? validate_or_fn;
567
+ /** @type {(arg?: any) => MaybePromise<Input>} */
568
+ const validate = create_validator(validate_or_fn, maybe_fn);
569
+ /**
570
+ * @param {any} event
571
+ * @param {any} state
572
+ * @param {any} get_input
573
+ */
574
+ const run = (event, state, get_input) => run_remote_generator(event, {
575
+ ...state,
576
+ is_in_remote_query: true
577
+ }, false, get_input, fn, __.name);
578
+ /** @type {RemoteQueryLiveInternals} */
579
+ const __ = {
580
+ type: "query_live",
581
+ id: "",
582
+ name: "",
583
+ run: (event, state, arg) => run(event, state, () => validate(arg)),
584
+ validate,
585
+ bind(payload, validated_arg) {
586
+ const { event, state } = get_request_store();
587
+ return create_live_query_resource(__, payload, event, state, () => run(event, state, () => validated_arg));
588
+ }
589
+ };
590
+ /** @type {RemoteLiveQueryFunction<Input, Output> & { __: RemoteQueryLiveInternals }} */
591
+ const wrapper = (arg) => {
592
+ const { event, state } = get_request_store();
593
+ const payload = stringify_remote_arg(arg, state.transport);
594
+ return create_live_query_resource(__, payload, event, state, () => run(event, state, () => validate(arg)));
595
+ };
596
+ Object.defineProperty(wrapper, "__", { value: __ });
597
+ return wrapper;
598
+ }
599
+ /**
600
+ * Creates a batch query function that collects multiple calls and executes them in a single request
601
+ *
602
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query.batch) for full documentation.
603
+ *
604
+ * @template Input
605
+ * @template Output
606
+ * @overload
607
+ * @param {'unchecked'} validate
608
+ * @param {(args: Input[]) => MaybePromise<(arg: Input, idx: number) => Output>} fn
609
+ * @returns {RemoteQueryFunction<Input, Output>}
610
+ * @since 2.35
611
+ */
612
+ /**
613
+ * Creates a batch query function that collects multiple calls and executes them in a single request
614
+ *
615
+ * See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query.batch) for full documentation.
616
+ *
617
+ * @template {StandardSchemaV1} Schema
618
+ * @template Output
619
+ * @overload
620
+ * @param {Schema} schema
621
+ * @param {(args: StandardSchemaV1.InferOutput<Schema>[]) => MaybePromise<(arg: StandardSchemaV1.InferOutput<Schema>, idx: number) => Output>} fn
622
+ * @returns {RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, Output, StandardSchemaV1.InferOutput<Schema>>}
623
+ * @since 2.35
624
+ */
625
+ /**
626
+ * @template Input
627
+ * @template Output
628
+ * @param {any} validate_or_fn
629
+ * @param {(args?: Input[]) => MaybePromise<(arg: Input, idx: number) => Output>} [maybe_fn]
630
+ * @returns {RemoteQueryFunction<Input, Output>}
631
+ * @since 2.35
632
+ */
633
+ /*@__NO_SIDE_EFFECTS__*/
634
+ function batch(validate_or_fn, maybe_fn) {
635
+ /** @type {(args?: Input[]) => MaybePromise<(arg: Input, idx: number) => Output>} */
636
+ const fn = maybe_fn ?? validate_or_fn;
637
+ /** @type {(arg?: any) => MaybePromise<Input>} */
638
+ const validate = create_validator(validate_or_fn, maybe_fn);
639
+ /**
640
+ * Enqueues a single call into the current batch (creating one if necessary)
641
+ * and returns a promise that resolves with the result for this entry.
642
+ *
643
+ * @param {string} payload — the stringified raw argument (cache key)
644
+ * @param {() => MaybePromise<any>} get_validated — produces the validated argument for this entry
645
+ * @returns {Promise<any>}
646
+ */
647
+ const enqueue = (payload, get_validated) => {
648
+ const { event, state } = get_request_store();
649
+ return new Promise((resolve, reject) => {
650
+ const batches = state.remote.batches ??= /* @__PURE__ */ new Map();
651
+ let batched = batches.get(__.id);
652
+ if (!batched) {
653
+ batched = /* @__PURE__ */ new Map();
654
+ batches.set(__.id, batched);
655
+ }
656
+ const entry = batched.get(payload);
657
+ if (entry) {
658
+ entry.resolvers.push({
659
+ resolve,
660
+ reject
661
+ });
662
+ return;
663
+ }
664
+ batched.set(payload, {
665
+ get_validated,
666
+ resolvers: [{
667
+ resolve,
668
+ reject
669
+ }]
670
+ });
671
+ if (batched.size > 1) return;
672
+ setTimeout(async () => {
673
+ batches.delete(__.id);
674
+ const entries = Array.from(batched.values());
675
+ try {
676
+ return await run_remote_function(event, {
677
+ ...state,
678
+ is_in_remote_query: true
679
+ }, false, async () => Promise.all(entries.map((entry) => entry.get_validated())), async (input) => {
680
+ const get_result = await fn(input);
681
+ for (let i = 0; i < entries.length; i++) try {
682
+ const result = get_result(input[i], i);
683
+ for (const resolver of entries[i].resolvers) resolver.resolve(result);
684
+ } catch (error) {
685
+ for (const resolver of entries[i].resolvers) resolver.reject(error);
686
+ }
687
+ });
688
+ } catch (error) {
689
+ for (const entry of batched.values()) for (const resolver of entry.resolvers) resolver.reject(error);
690
+ }
691
+ }, 0);
692
+ });
693
+ };
694
+ /** @type {RemoteQueryBatchInternals} */
695
+ const __ = {
696
+ type: "query_batch",
697
+ id: "",
698
+ name: "",
699
+ validate,
700
+ run: async (args, options) => {
701
+ const { event, state } = get_request_store();
702
+ return run_remote_function(event, {
703
+ ...state,
704
+ is_in_remote_query: true
705
+ }, false, async () => Promise.all(args.map(validate)), async (input) => {
706
+ const get_result = await fn(input);
707
+ return Promise.all(input.map(async (arg, i) => {
708
+ try {
709
+ return {
710
+ type: "result",
711
+ data: get_result(arg, i)
712
+ };
713
+ } catch (error) {
714
+ return {
715
+ type: "error",
716
+ error: await handle_error_and_jsonify(event, state, options, error),
717
+ status: error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500
718
+ };
719
+ }
720
+ }));
721
+ });
722
+ },
723
+ bind(payload, validated_arg) {
724
+ const { event, state } = get_request_store();
725
+ return create_query_resource(__, payload, event, state, () => enqueue(payload, () => validated_arg));
726
+ }
727
+ };
728
+ /** @type {RemoteQueryFunction<Input, Output> & { __: RemoteQueryBatchInternals }} */
729
+ const wrapper = (arg) => {
730
+ const { event, state } = get_request_store();
731
+ const payload = stringify_remote_arg(arg, state.transport);
732
+ return create_query_resource(__, payload, event, state, () => enqueue(payload, () => validate(arg)));
733
+ };
734
+ Object.defineProperty(wrapper, "__", { value: __ });
735
+ return wrapper;
736
+ }
737
+ /**
738
+ * Include this value in the returned payload...
739
+ * @param {RequestEvent} event
740
+ * @param {RequestState} state
741
+ * @param {RemoteInternals} internals
742
+ * @param {string} payload
743
+ * @param {() => Promise<any>} fn
744
+ */
745
+ function refresh(event, state, internals, payload, fn) {
746
+ if (!internals.id) return;
747
+ if (!event.isRemoteRequest) return;
748
+ const key = create_remote_key(internals.id, payload);
749
+ const promise = fn();
750
+ promise.catch(() => {});
751
+ (state.remote.explicit ??= /* @__PURE__ */ new Map()).set(key, {
752
+ internals,
753
+ promise
754
+ });
755
+ }
756
+ /**
757
+ * @param {RemoteInternals} __
758
+ * @param {string} payload — the stringified raw argument (i.e. the cache key the client will use)
759
+ * @param {RequestEvent} event
760
+ * @param {RequestState} state
761
+ * @param {() => Promise<any>} fn
762
+ * @returns {RemoteQuery<any>}
763
+ */
764
+ function create_query_resource(__, payload, event, state, fn) {
765
+ /** @type {Promise<any> | null} */
766
+ let promise = null;
767
+ const get_promise = () => {
768
+ return promise ??= get_response(__, payload, state, fn);
769
+ };
770
+ const populate_hydratable = () => {
771
+ if (__.id && state.is_in_render) get_promise().catch(noop);
772
+ };
773
+ return {
774
+ /** @type {Promise<any>['catch']} */
775
+ catch(onrejected) {
776
+ return get_promise().catch(onrejected);
777
+ },
778
+ get current() {
779
+ populate_hydratable();
780
+ },
781
+ get error() {
782
+ populate_hydratable();
783
+ },
784
+ /** @type {Promise<any>['finally']} */
785
+ finally(onfinally) {
786
+ return get_promise().finally(onfinally);
787
+ },
788
+ get loading() {
789
+ populate_hydratable();
790
+ return true;
791
+ },
792
+ get ready() {
793
+ populate_hydratable();
794
+ return false;
795
+ },
796
+ refresh() {
797
+ promise = null;
798
+ delete get_cache(__, state)[payload];
799
+ refresh(event, state, __, payload, get_promise);
800
+ return Promise.resolve();
801
+ },
802
+ /** @param {any} value */
803
+ set(value) {
804
+ const p = promise = Promise.resolve(value);
805
+ get_cache(__, state)[payload] = p;
806
+ refresh(event, state, __, payload, () => p);
807
+ },
808
+ run() {
809
+ throw new Error(`\`myQuery().run()\` has been removed — please replace it with \`myQuery()\`. See https://github.com/sveltejs/kit/pull/15779 for more details`);
810
+ },
811
+ /** @type {Promise<any>['then']} */
812
+ then(onfulfilled, onrejected) {
813
+ return get_promise().then(onfulfilled, onrejected);
814
+ },
815
+ withOverride() {
816
+ throw new Error(`Cannot call '${__.name}.withOverride()' on the server`);
817
+ },
818
+ get [Symbol.toStringTag]() {
819
+ return "QueryResource";
820
+ }
821
+ };
822
+ }
823
+ /**
824
+ * @param {RemoteQueryLiveInternals} __
825
+ * @param {string} payload — the stringified raw argument (i.e. the cache key the client will use)
826
+ * @param {RequestEvent} event
827
+ * @param {RequestState} state
828
+ * @param {() => AsyncGenerator<any, void, void>} get_generator
829
+ * @returns {RemoteLiveQuery<any>}
830
+ */
831
+ function create_live_query_resource(__, payload, event, state, get_generator) {
832
+ /** @type {Promise<any> | null} */
833
+ let promise = null;
834
+ const get_first_value = async () => {
835
+ for await (const value of get_generator()) return value;
836
+ throw new Error(`query.live '${__.name}' did not yield a value`);
837
+ };
838
+ const get_promise = () => {
839
+ return promise ??= get_response(__, payload, state, get_first_value);
840
+ };
841
+ const populate_hydratable = () => {
842
+ if (__.id && state.is_in_render) get_promise().catch(noop);
843
+ };
844
+ return {
845
+ /** @type {Promise<any>['catch']} */
846
+ catch(onrejected) {
847
+ return get_promise().catch(onrejected);
848
+ },
849
+ get current() {
850
+ populate_hydratable();
851
+ },
852
+ get error() {
853
+ populate_hydratable();
854
+ },
855
+ /** @type {Promise<any>['finally']} */
856
+ finally(onfinally) {
857
+ return get_promise().finally(onfinally);
858
+ },
859
+ get done() {
860
+ populate_hydratable();
861
+ return false;
862
+ },
863
+ get loading() {
864
+ populate_hydratable();
865
+ return true;
866
+ },
867
+ get ready() {
868
+ populate_hydratable();
869
+ return false;
870
+ },
871
+ get connected() {
872
+ populate_hydratable();
873
+ return false;
874
+ },
875
+ reconnect() {
876
+ promise = null;
877
+ delete get_cache(__, state)[payload];
878
+ refresh(event, state, __, payload, get_promise);
879
+ return Promise.resolve();
880
+ },
881
+ /** @ts-expect-error This method no longer exists */
882
+ run() {
883
+ throw new Error("`.run()` has been removed from live queries. Use `for await (const value of liveQuery())` instead.");
884
+ },
885
+ /** @type {Promise<any>['then']} */
886
+ then(onfulfilled, onrejected) {
887
+ return get_promise().then(onfulfilled, onrejected);
888
+ },
889
+ [Symbol.asyncIterator]() {
890
+ const key = create_remote_key(__.id, payload);
891
+ const cache = state.remote.live_iterators ??= /* @__PURE__ */ new Map();
892
+ let cached = cache.get(key);
893
+ if (!cached) {
894
+ cached = create_shared_live_iterator(event.request.signal, get_generator);
895
+ cache.set(key, cached);
896
+ }
897
+ return cached.subscribe();
898
+ },
899
+ get [Symbol.toStringTag]() {
900
+ return "LiveQueryResource";
901
+ }
902
+ };
903
+ }
904
+ /**
905
+ * Wraps a lazily-created live-query generator so that multiple `for await`
906
+ * consumers within the same request share one underlying iteration. The first
907
+ * subscriber starts the generator; values are broadcast to all subscribers
908
+ * via a `SharedIterator`. When the last subscriber unsubscribes, the generator
909
+ * is closed via `generator.return(undefined)`.
910
+ *
911
+ * If `signal` aborts (typically because the client has disconnected), the
912
+ * pump is torn down and any in-flight `next()` calls on consumer iterators
913
+ * resolve with `{ done: true }`, so suspended `for await` loops unwind
914
+ * cleanly rather than leaking.
915
+ *
916
+ * @param {AbortSignal} signal
917
+ * @param {() => AsyncGenerator<any, void, void>} get_generator
918
+ */
919
+ function create_shared_live_iterator(signal, get_generator) {
920
+ return new SharedIterator((instance) => {
921
+ if (signal.aborted) {
922
+ instance.done();
923
+ return noop;
924
+ }
925
+ const generator = get_generator();
926
+ let aborted = false;
927
+ const close = () => {
928
+ aborted = true;
929
+ generator.return().catch(noop);
930
+ };
931
+ signal.addEventListener("abort", () => (close(), instance.done()), { once: true });
932
+ (async () => {
933
+ try {
934
+ while (true) {
935
+ const result = await generator.next();
936
+ if (result.done) {
937
+ instance.done();
938
+ return;
939
+ }
940
+ instance.push(result.value);
941
+ }
942
+ } catch (error) {
943
+ if (!aborted) instance.fail(error);
944
+ } finally {
945
+ close();
946
+ }
947
+ })();
948
+ return close;
949
+ });
950
+ }
951
+ Object.defineProperty(query, "batch", {
952
+ value: batch,
953
+ enumerable: true
954
+ });
955
+ Object.defineProperty(query, "live", {
956
+ value: live,
957
+ enumerable: true
958
+ });
959
+
960
+ //#region ../lib/config.mjs
961
+ var DEFAULT_IGNORE = [
962
+ "**/package-lock.json",
963
+ "**/pnpm-lock.yaml",
964
+ "**/yarn.lock",
965
+ "**/bun.lock",
966
+ "**/bun.lockb",
967
+ "**/*.g.cs",
968
+ "**/*Snapshot.cs",
969
+ "**/*.Designer.cs",
970
+ "**/node_modules/**",
971
+ "**/.svelte-kit/**",
972
+ "**/dist/**",
973
+ "**/build/**",
974
+ "**/*.min.js",
975
+ "**/*.min.css",
976
+ "**/CODE_REVIEW_CORRECTNESS_FINDINGS.md"
977
+ ];
978
+ var STORE_DIFF_MAX = 4e5;
979
+ var GUIDELINE_FILES = [
980
+ "REVIEW.md",
981
+ "CLAUDE.md",
982
+ "AGENTS.md"
983
+ ];
984
+ function unquote(s) {
985
+ const t = String(s || "").trim();
986
+ if (t.startsWith("\"") && t.endsWith("\"") || t.startsWith("'") && t.endsWith("'")) return t.slice(1, -1);
987
+ return t;
988
+ }
989
+ /** Minimal YAML: `key: value` and `key:\n - item`. */
990
+ function parseSimpleYaml(text) {
991
+ const result = {};
992
+ let key = null;
993
+ for (const raw of String(text || "").split(/\r?\n/)) {
994
+ if (!raw.trim() || raw.trim().startsWith("#")) continue;
995
+ const list = raw.match(/^\s+-\s+(.*)$/);
996
+ if (list && key) {
997
+ if (!Array.isArray(result[key])) result[key] = [];
998
+ result[key].push(unquote(list[1]));
999
+ continue;
1000
+ }
1001
+ const kv = raw.match(/^([A-Za-z][\w]*)\s*:\s*(.*)$/);
1002
+ if (!kv) continue;
1003
+ key = kv[1];
1004
+ const v = kv[2].trim();
1005
+ result[key] = v === "" ? [] : unquote(v);
1006
+ }
1007
+ return result;
1008
+ }
1009
+ function globToRegExp(glob) {
1010
+ let g = String(glob || "").replace(/\\/g, "/");
1011
+ if (!g.includes("/")) g = `**/${g}`;
1012
+ let s = "";
1013
+ for (let i = 0; i < g.length; i++) {
1014
+ const c = g[i];
1015
+ if (c === "*" && g[i + 1] === "*") if (g[i + 2] === "/") {
1016
+ s += "(?:.*/)?";
1017
+ i += 2;
1018
+ } else {
1019
+ s += ".*";
1020
+ i += 1;
1021
+ }
1022
+ else if (c === "*") s += "[^/]*";
1023
+ else if (c === "?") s += "[^/]";
1024
+ else if ("+()[]{}|.^$".includes(c)) s += `\\${c}`;
1025
+ else s += c;
1026
+ }
1027
+ return new RegExp(`^${s}$`, "i");
1028
+ }
1029
+ function globMatch(filePath, pattern) {
1030
+ const norm = String(filePath || "").replace(/\\/g, "/");
1031
+ return globToRegExp(pattern).test(norm);
1032
+ }
1033
+ function isIgnored(filePath, patterns) {
1034
+ return (patterns || []).some((p) => globMatch(filePath, p));
1035
+ }
1036
+ function isTestPath(filePath) {
1037
+ const p = String(filePath || "").replace(/\\/g, "/");
1038
+ return /(^|\/)(__tests__|tests?|spec)s?(\/|$)/i.test(p) || /\.(test|spec)\.[^.]+$/i.test(p);
1039
+ }
1040
+ function readIfExists(path) {
1041
+ try {
1042
+ return existsSync(path) ? readFileSync(path, "utf8") : null;
1043
+ } catch {
1044
+ return null;
1045
+ }
1046
+ }
1047
+ function loadReviewConfig(repo) {
1048
+ const jsonText = readIfExists(join(repo, ".diff-review.json"));
1049
+ const yamlText = readIfExists(join(repo, ".diff-review.yml")) || readIfExists(join(repo, ".diff-review.yaml"));
1050
+ let extra = {};
1051
+ if (jsonText) try {
1052
+ extra = JSON.parse(jsonText);
1053
+ } catch {
1054
+ extra = {};
1055
+ }
1056
+ else if (yamlText) extra = parseSimpleYaml(yamlText);
1057
+ const extraIgnore = Array.isArray(extra.ignore) ? extra.ignore.map(String) : [];
1058
+ const rulesFile = extra.rulesFile ? String(extra.rulesFile) : "REVIEW.md";
1059
+ const rules = readIfExists(join(repo, rulesFile)) || "";
1060
+ const model = extra.model && extra.model !== "default" ? String(extra.model) : null;
1061
+ return {
1062
+ ignore: [...DEFAULT_IGNORE, ...extraIgnore],
1063
+ model,
1064
+ rulesFile,
1065
+ rules: rules.trim()
1066
+ };
1067
+ }
1068
+ /** CLAUDE.md / REVIEW.md / AGENTS.md from repo root and immediate parent dirs of touched files. */
1069
+ function collectGuidelines(repo, filePaths, { rulesFile = "REVIEW.md", maxChars = 8e3, maxTotalChars = 12e3, maxDepth = 1, skipNames = [] } = {}) {
1070
+ const skip = new Set(skipNames);
1071
+ const names = [.../* @__PURE__ */ new Set([rulesFile, ...GUIDELINE_FILES])].filter((name) => !skip.has(name));
1072
+ const seen = /* @__PURE__ */ new Set();
1073
+ const out = [];
1074
+ const addRel = (rel) => {
1075
+ const norm = String(rel || "").replace(/\\/g, "/").replace(/^\/+/, "");
1076
+ if (!norm || seen.has(norm)) return;
1077
+ const text = readIfExists(join(repo, ...norm.split("/")));
1078
+ if (!text || !text.trim()) return;
1079
+ seen.add(norm);
1080
+ out.push({
1081
+ path: norm,
1082
+ text: text.trim()
1083
+ });
1084
+ };
1085
+ for (const name of names) addRel(name);
1086
+ const depth = Math.max(1, Number(maxDepth) || 1);
1087
+ for (const filePath of filePaths || []) {
1088
+ const parts = String(filePath || "").replace(/\\/g, "/").split("/").filter(Boolean);
1089
+ const minLen = Math.max(1, parts.length - depth);
1090
+ for (let len = parts.length - 1; len >= minLen; len--) {
1091
+ const dir = parts.slice(0, len).join("/");
1092
+ for (const name of names) addRel(`${dir}/${name}`);
1093
+ }
1094
+ }
1095
+ let total = 0;
1096
+ const capped = [];
1097
+ for (const item of out) {
1098
+ if (total >= maxTotalChars) break;
1099
+ const room = maxTotalChars - total;
1100
+ const slice = item.text.slice(0, Math.min(maxChars, room));
1101
+ if (!slice) continue;
1102
+ capped.push({
1103
+ path: item.path,
1104
+ text: slice
1105
+ });
1106
+ total += slice.length;
1107
+ }
1108
+ return capped;
1109
+ }
1110
+ function capDiff(diff, max = STORE_DIFF_MAX) {
1111
+ const text = String(diff || "");
1112
+ if (text.length <= max) return {
1113
+ diff: text,
1114
+ diffChars: text.length,
1115
+ truncated: false
1116
+ };
1117
+ return {
1118
+ diff: `${text.slice(0, max)}\n\n… [diff recortado: se guardan ${max} de ${text.length} caracteres]`,
1119
+ diffChars: text.length,
1120
+ truncated: true
1121
+ };
1122
+ }
1123
+ //#endregion
1124
+ //#region ../lib/git.mjs
1125
+ function assertGitRepo(repo) {
1126
+ const raw = String(repo || "").trim();
1127
+ if (!raw) throw new Error("Indicá la ruta absoluta de un repositorio git.");
1128
+ if (!/[\\/]/.test(raw) && !/^[A-Za-z]:/.test(raw)) throw new Error(`"${raw}" no es una ruta. Pegá la ruta absoluta del repo (ej. C:\\tienda-argenta\\tienda-argenta-2).`);
1129
+ const resolved = resolve(raw);
1130
+ if (!resolved || resolved === ".") throw new Error("Indicá la ruta absoluta de un repositorio git.");
1131
+ if (!existsSync(resolved)) throw new Error(`No existe la carpeta: ${resolved}`);
1132
+ if (!existsSync(join(resolved, ".git"))) throw new Error(`No es un repositorio git (falta .git): ${resolved}`);
1133
+ return resolved;
1134
+ }
1135
+ function git(repo, args, { maxBuffer = 64 * 1024 * 1024 } = {}) {
1136
+ return execFileSync("git", args, {
1137
+ cwd: repo,
1138
+ encoding: "utf8",
1139
+ maxBuffer,
1140
+ windowsHide: true
1141
+ });
1142
+ }
1143
+ function commitLog(repo, mergeBase, branch, limit = 25) {
1144
+ try {
1145
+ return git(repo, [
1146
+ "log",
1147
+ "--oneline",
1148
+ "--no-merges",
1149
+ "-n",
1150
+ String(limit),
1151
+ `${mergeBase}..${branch}`
1152
+ ]).trim().split("\n").filter(Boolean);
1153
+ } catch {
1154
+ return [];
1155
+ }
1156
+ }
1157
+ var FILE_LINE_CACHE = /* @__PURE__ */ new Map();
1158
+ var FILE_LINE_CACHE_MAX = 48;
1159
+ function rememberFileLines(key, lines) {
1160
+ if (FILE_LINE_CACHE.has(key)) FILE_LINE_CACHE.delete(key);
1161
+ FILE_LINE_CACHE.set(key, lines);
1162
+ while (FILE_LINE_CACHE.size > FILE_LINE_CACHE_MAX) {
1163
+ const oldest = FILE_LINE_CACHE.keys().next().value;
1164
+ FILE_LINE_CACHE.delete(oldest);
1165
+ }
1166
+ }
1167
+ function showFileLines(repo, rev, path) {
1168
+ const resolved = assertGitRepo(repo);
1169
+ const safeRev = String(rev || "").trim();
1170
+ const safePath = String(path || "").replace(/\\/g, "/").trim();
1171
+ if (!safeRev || !/^[A-Za-z0-9._/\-]+$/.test(safeRev)) throw new Error("Rev inválido.");
1172
+ if (!safePath || safePath.startsWith("/") || safePath.includes("..") || safePath.includes("\0")) throw new Error("Ruta inválida.");
1173
+ const key = `${resolved}\0${safeRev}\0${safePath}`;
1174
+ if (FILE_LINE_CACHE.has(key)) return FILE_LINE_CACHE.get(key);
1175
+ let text;
1176
+ try {
1177
+ text = git(resolved, ["show", `${safeRev}:${safePath}`]);
1178
+ } catch {
1179
+ rememberFileLines(key, null);
1180
+ return null;
1181
+ }
1182
+ if (text.includes("\0")) {
1183
+ rememberFileLines(key, null);
1184
+ return null;
1185
+ }
1186
+ const raw = text.replace(/\r\n/g, "\n");
1187
+ const lines = raw.endsWith("\n") ? raw.slice(0, -1).split("\n") : raw.split("\n");
1188
+ rememberFileLines(key, lines);
1189
+ return lines;
1190
+ }
1191
+ function listBranches(repo) {
1192
+ const names = git(repo, [
1193
+ "for-each-ref",
1194
+ "--sort=-committerdate",
1195
+ "--format=%(refname:short)",
1196
+ "refs/heads",
1197
+ "refs/remotes"
1198
+ ]).split("\n").map((s) => s.trim()).filter(Boolean).map((name) => name.replace(/^origin\//, "")).filter((name) => name !== "HEAD" && !name.endsWith("/HEAD"));
1199
+ const unique = [...new Set(names)];
1200
+ const current = git(repo, [
1201
+ "rev-parse",
1202
+ "--abbrev-ref",
1203
+ "HEAD"
1204
+ ]).trim();
1205
+ const bases = unique.filter((n) => n === "develop" || n === "main" || n === "master");
1206
+ return {
1207
+ branches: unique,
1208
+ current,
1209
+ defaultBase: bases.includes("develop") ? "develop" : bases.includes("main") ? "main" : bases[0] || "develop"
1210
+ };
1211
+ }
1212
+ /** URL del remote (origin, o el primero disponible). Vacío si no hay. */
1213
+ function getRemoteUrl(repo, preferred = "origin") {
1214
+ try {
1215
+ const url = git(repo, [
1216
+ "remote",
1217
+ "get-url",
1218
+ preferred
1219
+ ]).trim();
1220
+ if (url) return url;
1221
+ } catch {}
1222
+ try {
1223
+ const remotes = git(repo, ["remote"]).split("\n").map((s) => s.trim()).filter(Boolean);
1224
+ if (!remotes.length) return "";
1225
+ return git(repo, [
1226
+ "remote",
1227
+ "get-url",
1228
+ remotes[0]
1229
+ ]).trim();
1230
+ } catch {
1231
+ return "";
1232
+ }
1233
+ }
1234
+ //#endregion
1235
+ //#region ../lib/extract.mjs
1236
+ function extractBranchDiff({ repo, branch, base = "develop" }) {
1237
+ if (!branch) throw new Error("Falta el branch a revisar.");
1238
+ const config = loadReviewConfig(repo);
1239
+ const mergeBase = git(repo, [
1240
+ "merge-base",
1241
+ base,
1242
+ branch
1243
+ ]).trim();
1244
+ const branchSha = git(repo, ["rev-parse", branch]).trim();
1245
+ const baseSha = git(repo, ["rev-parse", base]).trim();
1246
+ const nameStatus = git(repo, [
1247
+ "diff",
1248
+ mergeBase,
1249
+ branch,
1250
+ "--name-status"
1251
+ ]).trim().split("\n").filter(Boolean).map((line) => {
1252
+ const [status, ...rest] = line.split(" ");
1253
+ const path = rest[rest.length - 1];
1254
+ const oldPath = rest.length > 1 ? rest[0] : null;
1255
+ let changeType = "modified";
1256
+ if (status.startsWith("A")) changeType = "added";
1257
+ else if (status.startsWith("D")) changeType = "deleted";
1258
+ else if (status.startsWith("R")) changeType = "renamed";
1259
+ return {
1260
+ path,
1261
+ oldPath,
1262
+ changeType
1263
+ };
1264
+ });
1265
+ const ignored = [];
1266
+ const included = [];
1267
+ for (const f of nameStatus) if (isIgnored(f.path, config.ignore)) ignored.push(f.path);
1268
+ else included.push(f);
1269
+ const files = included.map((f) => {
1270
+ let raw = "";
1271
+ try {
1272
+ raw = git(repo, [
1273
+ "diff",
1274
+ mergeBase,
1275
+ branch,
1276
+ "--",
1277
+ f.path
1278
+ ]);
1279
+ const idx = raw.indexOf("\n@@");
1280
+ if (idx !== -1) raw = raw.slice(idx + 1);
1281
+ } catch {
1282
+ raw = "";
1283
+ }
1284
+ const capped = capDiff(raw);
1285
+ return {
1286
+ path: f.path,
1287
+ oldPath: f.oldPath || void 0,
1288
+ changeType: f.changeType,
1289
+ diff: capped.diff,
1290
+ diffChars: capped.diffChars,
1291
+ truncated: capped.truncated,
1292
+ modelChars: capped.diff.length,
1293
+ group: ""
1294
+ };
1295
+ });
1296
+ const shortstat = git(repo, [
1297
+ "diff",
1298
+ mergeBase,
1299
+ branch,
1300
+ "--shortstat"
1301
+ ]).trim();
1302
+ const insMatch = shortstat.match(/(\d+) insertion/);
1303
+ const delMatch = shortstat.match(/(\d+) deletion/);
1304
+ return {
1305
+ version: 2,
1306
+ repo,
1307
+ branch,
1308
+ baseBranch: base,
1309
+ mergeBase,
1310
+ branchSha,
1311
+ baseSha,
1312
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1313
+ intent: "",
1314
+ stats: {
1315
+ filesChanged: files.length,
1316
+ filesIgnored: ignored.length,
1317
+ filesTruncated: files.filter((f) => f.truncated).length,
1318
+ insertions: insMatch ? Number(insMatch[1]) : 0,
1319
+ deletions: delMatch ? Number(delMatch[1]) : 0
1320
+ },
1321
+ context: {
1322
+ commits: commitLog(repo, mergeBase, branch),
1323
+ rules: config.rules,
1324
+ rulesFile: config.rulesFile,
1325
+ ignored: ignored.slice(0, 40),
1326
+ testsTouched: files.filter((f) => isTestPath(f.path)).map((f) => f.path),
1327
+ testsMissing: files.filter((f) => !isTestPath(f.path)).length,
1328
+ guidelines: collectGuidelines(repo, files.map((f) => f.path), {
1329
+ rulesFile: config.rulesFile,
1330
+ skipNames: config.rules.trim() ? [config.rulesFile] : [],
1331
+ maxDepth: 1,
1332
+ maxChars: 8e3,
1333
+ maxTotalChars: 12e3
1334
+ })
1335
+ },
1336
+ files,
1337
+ groups: [],
1338
+ blocks: [],
1339
+ findings: [],
1340
+ skipped: []
1341
+ };
1342
+ }
1343
+ //#endregion
1344
+ //#region ../lib/remote-repo.mjs
1345
+ var CACHE_ROOT = join(tmpdir(), "diff-review-clones");
1346
+ var MAX_DEEPEN_ROUNDS = 8;
1347
+ var DEEPEN_BY = "50";
1348
+ function looksLikeGitUrl(value) {
1349
+ const v = String(value || "").trim();
1350
+ if (!v) return false;
1351
+ if (/^(https?:\/\/|git@|ssh:\/\/)/i.test(v)) return true;
1352
+ if (/\.git$/i.test(v) && !/^[A-Za-z]:/.test(v) && !v.startsWith("/") && !v.startsWith("\\")) return true;
1353
+ return false;
1354
+ }
1355
+ function normalizeGitUrl(url) {
1356
+ const raw = String(url || "").trim();
1357
+ if (!raw) throw new Error("Indicá la URL del repositorio.");
1358
+ if (!looksLikeGitUrl(raw)) throw new Error(`"${raw}" no parece una URL git. Usá https://…, git@… o ssh://…`);
1359
+ if (/^(file:|javascript:|data:)/i.test(raw)) throw new Error("Esquema de URL no permitido.");
1360
+ return raw.replace(/\/$/, "");
1361
+ }
1362
+ function cacheDirFor(url) {
1363
+ return join(CACHE_ROOT, createHash("sha1").update(url).digest("hex").slice(0, 16));
1364
+ }
1365
+ function runGit(args, { cwd, timeout = 300 * 1e3 } = {}) {
1366
+ return execFileSync("git", args, {
1367
+ cwd,
1368
+ encoding: "utf8",
1369
+ windowsHide: true,
1370
+ timeout,
1371
+ maxBuffer: 64 * 1024 * 1024
1372
+ });
1373
+ }
1374
+ /** Lista heads del remote sin clonar. */
1375
+ function listRemoteBranches(url) {
1376
+ const remote = normalizeGitUrl(url);
1377
+ let raw;
1378
+ try {
1379
+ raw = runGit([
1380
+ "ls-remote",
1381
+ "--heads",
1382
+ remote
1383
+ ]);
1384
+ } catch (err) {
1385
+ const stderr = String(
1386
+ /** @type {{ stderr?: string }} */
1387
+ err.stderr || err.message || err
1388
+ );
1389
+ throw new Error("No se pudo listar branches del remote: " + stderr.split("\n").find(Boolean));
1390
+ }
1391
+ const branches = [...new Set(raw.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
1392
+ return (line.split(" ")[1] || "").replace(/^refs\/heads\//, "");
1393
+ }).filter(Boolean))].sort((a, b) => a.localeCompare(b));
1394
+ if (!branches.length) throw new Error("El remote no expone branches (refs/heads).");
1395
+ return {
1396
+ branches,
1397
+ current: "",
1398
+ defaultBase: branches.includes("develop") ? "develop" : branches.includes("main") ? "main" : branches.includes("master") ? "master" : branches[0]
1399
+ };
1400
+ }
1401
+ function fetchRef(repo, ref) {
1402
+ const name = String(ref || "").trim();
1403
+ if (!name) return;
1404
+ try {
1405
+ runGit([
1406
+ "rev-parse",
1407
+ "--verify",
1408
+ name
1409
+ ], { cwd: repo });
1410
+ return;
1411
+ } catch {}
1412
+ runGit([
1413
+ "fetch",
1414
+ "--depth",
1415
+ DEEPEN_BY,
1416
+ "origin",
1417
+ `refs/heads/${name}:refs/heads/${name}`
1418
+ ], { cwd: repo });
1419
+ }
1420
+ function deepenUntilMergeBase(repo, base, branch) {
1421
+ for (let i = 0; i < MAX_DEEPEN_ROUNDS; i++) try {
1422
+ git(repo, [
1423
+ "merge-base",
1424
+ base,
1425
+ branch
1426
+ ]);
1427
+ return;
1428
+ } catch {
1429
+ runGit([
1430
+ "fetch",
1431
+ "--deepen",
1432
+ DEEPEN_BY,
1433
+ "origin",
1434
+ `refs/heads/${base}:refs/heads/${base}`,
1435
+ `refs/heads/${branch}:refs/heads/${branch}`
1436
+ ], { cwd: repo });
1437
+ }
1438
+ throw new Error(`No se encontró merge-base entre "${base}" y "${branch}" (historial shallow insuficiente). Probá con más historia o usá una carpeta local.`);
1439
+ }
1440
+ /**
1441
+ * Asegura un worktree local (cache en tmp) con branch y base disponibles para diff.
1442
+ * @returns {string} ruta absoluta del clone
1443
+ */
1444
+ function ensureRemoteWorktree(url, branch, base = "develop") {
1445
+ const remote = normalizeGitUrl(url);
1446
+ const b = String(branch || "").trim();
1447
+ const baseRef = String(base || "develop").trim() || "develop";
1448
+ if (!b) throw new Error("Falta el branch a revisar.");
1449
+ mkdirSync(CACHE_ROOT, { recursive: true });
1450
+ const dir = cacheDirFor(remote);
1451
+ if (!existsSync(join(dir, ".git"))) {
1452
+ if (existsSync(dir)) rmSync(dir, {
1453
+ recursive: true,
1454
+ force: true
1455
+ });
1456
+ try {
1457
+ runGit([
1458
+ "clone",
1459
+ "--filter=blob:none",
1460
+ "--depth",
1461
+ DEEPEN_BY,
1462
+ "--branch",
1463
+ b,
1464
+ remote,
1465
+ dir
1466
+ ], { timeout: 600 * 1e3 });
1467
+ } catch (err) {
1468
+ if (existsSync(dir)) rmSync(dir, {
1469
+ recursive: true,
1470
+ force: true
1471
+ });
1472
+ const stderr = String(
1473
+ /** @type {{ stderr?: string }} */
1474
+ err.stderr || err.message || err
1475
+ );
1476
+ throw new Error("No se pudo clonar el repo: " + stderr.split("\n").find(Boolean));
1477
+ }
1478
+ } else {
1479
+ try {
1480
+ runGit([
1481
+ "remote",
1482
+ "set-url",
1483
+ "origin",
1484
+ remote
1485
+ ], { cwd: dir });
1486
+ } catch {}
1487
+ fetchRef(dir, b);
1488
+ try {
1489
+ runGit([
1490
+ "checkout",
1491
+ "--force",
1492
+ b
1493
+ ], { cwd: dir });
1494
+ } catch {}
1495
+ }
1496
+ if (baseRef !== b) fetchRef(dir, baseRef);
1497
+ deepenUntilMergeBase(dir, baseRef, b);
1498
+ return dir;
1499
+ }
1500
+ //#endregion
1501
+ //#region ../lib/validate.mjs
1502
+ var GROUP_KINDS = [
1503
+ "feat",
1504
+ "fix",
1505
+ "refactor",
1506
+ "perf",
1507
+ "test",
1508
+ "chore",
1509
+ "docs",
1510
+ "infra"
1511
+ ];
1512
+ var BLOCK_OPS = [
1513
+ "add",
1514
+ "mod",
1515
+ "del",
1516
+ "rename"
1517
+ ];
1518
+ var WHY_SOURCES = [
1519
+ "code",
1520
+ "commit",
1521
+ "pr",
1522
+ "issue",
1523
+ "inferred"
1524
+ ];
1525
+ var FINDING_CLASSES = ["risk", "quality"];
1526
+ var FINDING_SEVERITIES = [
1527
+ "high",
1528
+ "med",
1529
+ "low",
1530
+ "nit"
1531
+ ];
1532
+ var FINDING_KINDS = [
1533
+ "bug",
1534
+ "race",
1535
+ "auth",
1536
+ "security",
1537
+ "api-break",
1538
+ "missing-test",
1539
+ "perf",
1540
+ "maintainability",
1541
+ "docs",
1542
+ "other"
1543
+ ];
1544
+ var SKIP_REASONS = [
1545
+ "generated",
1546
+ "lockfile",
1547
+ "format",
1548
+ "vendored",
1549
+ "binary",
1550
+ "trivial",
1551
+ "ignored"
1552
+ ];
1553
+ //#endregion
1554
+ //#region ../lib/review.mjs
1555
+ var MAX = {
1556
+ intent: 700,
1557
+ groupTitle: 100,
1558
+ groupIntent: 300,
1559
+ what: 240,
1560
+ why: 240,
1561
+ finding: 360
1562
+ };
1563
+ function text$1(value, max) {
1564
+ const raw = String(value ?? "").replace(/\s+/g, " ").trim();
1565
+ return raw.length > max ? `${raw.slice(0, max - 1).trimEnd()}…` : raw;
1566
+ }
1567
+ function oneOf(value, allowed, fallback) {
1568
+ const raw = String(value ?? "").trim().toLowerCase();
1569
+ return allowed.includes(raw) ? raw : fallback;
1570
+ }
1571
+ /**
1572
+ * Rango de líneas de un bloque. `L40-58` es post-imagen (lo que quedó);
1573
+ * `-L88-95` es pre-imagen, que es la única forma de señalar líneas borradas.
1574
+ */
1575
+ function parseLineRange(value) {
1576
+ const raw = String(value ?? "").trim();
1577
+ const match = /^(-)?L?(\d+)(?:\s*[-–]\s*L?(\d+))?$/i.exec(raw);
1578
+ if (!match) return null;
1579
+ const start = Number(match[2]);
1580
+ const end = match[3] ? Number(match[3]) : start;
1581
+ return {
1582
+ side: match[1] ? "old" : "new",
1583
+ start: Math.min(start, end),
1584
+ end: Math.max(start, end)
1585
+ };
1586
+ }
1587
+ function formatLineRange(range) {
1588
+ if (!range) return "";
1589
+ const prefix = range.side === "old" ? "-L" : "L";
1590
+ return range.start === range.end ? `${prefix}${range.start}` : `${prefix}${range.start}-${range.end}`;
1591
+ }
1592
+ function countHunks(files) {
1593
+ let total = 0;
1594
+ for (const file of files || []) for (const line of String(file.diff || "").split("\n")) if (line.startsWith("@@")) total += 1;
1595
+ return total;
1596
+ }
1597
+ function normalizeGroups(raw) {
1598
+ const seen = /* @__PURE__ */ new Set();
1599
+ const groups = [];
1600
+ for (const item of raw || []) {
1601
+ const id = String(item?.id || "").trim();
1602
+ if (!id || seen.has(id)) continue;
1603
+ seen.add(id);
1604
+ groups.push({
1605
+ id,
1606
+ kind: oneOf(item.kind, GROUP_KINDS, "chore"),
1607
+ title: text$1(item.title, MAX.groupTitle) || `${oneOf(item.kind, GROUP_KINDS, "chore")}: cambios`,
1608
+ intent: text$1(item.intent, MAX.groupIntent)
1609
+ });
1610
+ }
1611
+ return groups;
1612
+ }
1613
+ function normalizeBlocks(raw, { groupIds, knownPaths }) {
1614
+ const seen = /* @__PURE__ */ new Set();
1615
+ const blocks = [];
1616
+ for (const item of raw || []) {
1617
+ const id = String(item?.id || "").trim();
1618
+ const group = String(item?.group || "").trim();
1619
+ const file = String(item?.file || "").trim();
1620
+ if (!id || seen.has(id)) continue;
1621
+ if (!groupIds.has(group)) continue;
1622
+ if (knownPaths.size && !knownPaths.has(file)) continue;
1623
+ const range = parseLineRange(item.lines);
1624
+ seen.add(id);
1625
+ blocks.push({
1626
+ id,
1627
+ group,
1628
+ file,
1629
+ lines: range ? formatLineRange(range) : text$1(item.lines, 24),
1630
+ side: range?.side || "new",
1631
+ start: range?.start ?? 0,
1632
+ end: range?.end ?? 0,
1633
+ op: oneOf(item.op, BLOCK_OPS, "mod"),
1634
+ what: text$1(item.what, MAX.what),
1635
+ why: text$1(item.why, MAX.why),
1636
+ source: oneOf(item.source, WHY_SOURCES, "inferred")
1637
+ });
1638
+ }
1639
+ blocks.sort((a, b) => a.file.localeCompare(b.file) || a.start - b.start);
1640
+ return blocks;
1641
+ }
1642
+ function findingKey(finding) {
1643
+ return [
1644
+ finding.file,
1645
+ finding.line ?? "",
1646
+ finding.kind,
1647
+ finding.what
1648
+ ].join("|");
1649
+ }
1650
+ function normalizeFindings(raw, { blockIds, knownPaths }) {
1651
+ const seen = /* @__PURE__ */ new Set();
1652
+ const findings = [];
1653
+ for (const item of raw || []) {
1654
+ const file = String(item?.file || item?.path || "").trim();
1655
+ const what = text$1(item?.what || item?.title, MAX.finding);
1656
+ if (!file || !what) continue;
1657
+ if (knownPaths.size && !knownPaths.has(file)) continue;
1658
+ const cls = oneOf(item.class, FINDING_CLASSES, "risk");
1659
+ const severity = oneOf(item.severity, FINDING_SEVERITIES, "med");
1660
+ const block = String(item?.block || "").trim();
1661
+ const line = Number.isFinite(Number(item?.line)) ? Number(item.line) : null;
1662
+ const finding = {
1663
+ id: "",
1664
+ class: cls,
1665
+ severity,
1666
+ blocking: cls === "risk" && (item?.blocking === true || severity === "high"),
1667
+ kind: oneOf(item.kind, FINDING_KINDS, "other"),
1668
+ file,
1669
+ line,
1670
+ block: blockIds.has(block) ? block : "",
1671
+ what,
1672
+ fix: text$1(item.fix || item.detail, MAX.finding)
1673
+ };
1674
+ const key = findingKey(finding);
1675
+ if (seen.has(key)) continue;
1676
+ seen.add(key);
1677
+ findings.push(finding);
1678
+ }
1679
+ const rank = {
1680
+ high: 0,
1681
+ med: 1,
1682
+ low: 2,
1683
+ nit: 3
1684
+ };
1685
+ findings.sort((a, b) => Number(b.blocking) - Number(a.blocking) || (a.class === b.class ? 0 : a.class === "risk" ? -1 : 1) || rank[a.severity] - rank[b.severity] || a.file.localeCompare(b.file));
1686
+ findings.forEach((finding, i) => {
1687
+ finding.id = `f${i + 1}`;
1688
+ });
1689
+ return findings;
1690
+ }
1691
+ function normalizeSkipped(raw, ignored) {
1692
+ const byFile = /* @__PURE__ */ new Map();
1693
+ for (const path of ignored || []) byFile.set(String(path), {
1694
+ file: String(path),
1695
+ reason: "ignored"
1696
+ });
1697
+ for (const item of raw || []) {
1698
+ const file = String(item?.file || "").trim();
1699
+ if (!file) continue;
1700
+ byFile.set(file, {
1701
+ file,
1702
+ reason: oneOf(item.reason, SKIP_REASONS, "trivial")
1703
+ });
1704
+ }
1705
+ return [...byFile.values()];
1706
+ }
1707
+ /** Tema donde se dibuja el archivo: el que aporta más bloques, desempatando por orden de tema. */
1708
+ function dominantGroup(blocks, path, groupOrder) {
1709
+ const tally = /* @__PURE__ */ new Map();
1710
+ for (const block of blocks) {
1711
+ if (block.file !== path) continue;
1712
+ tally.set(block.group, (tally.get(block.group) || 0) + 1);
1713
+ }
1714
+ if (!tally.size) return "";
1715
+ return [...tally.entries()].sort((a, b) => b[1] - a[1] || groupOrder.indexOf(a[0]) - groupOrder.indexOf(b[0]))[0][0];
1716
+ }
1717
+ /**
1718
+ * Documento final: lo que dijo el modelo, saneado y cruzado contra los archivos
1719
+ * reales del diff. Todo lo que no cierra se descarta acá en vez de romper la UI.
1720
+ */
1721
+ function buildReview({ skeleton, payload, notes = [] }) {
1722
+ const files = skeleton.files || [];
1723
+ const knownPaths = new Set(files.map((f) => f.path));
1724
+ const groups = normalizeGroups(payload?.groups);
1725
+ const groupIds = new Set(groups.map((g) => g.id));
1726
+ const blocks = normalizeBlocks(payload?.blocks, {
1727
+ groupIds,
1728
+ knownPaths
1729
+ });
1730
+ const blockIds = new Set(blocks.map((b) => b.id));
1731
+ const findings = normalizeFindings(payload?.findings, {
1732
+ blockIds,
1733
+ knownPaths
1734
+ });
1735
+ const skipped = normalizeSkipped(payload?.skipped, skeleton.context?.ignored);
1736
+ const usedGroups = new Set(blocks.map((b) => b.group));
1737
+ const liveGroups = groups.filter((g) => usedGroups.has(g.id));
1738
+ const groupOrder = liveGroups.map((g) => g.id);
1739
+ const skippedFiles = new Set(skipped.map((s) => s.file));
1740
+ const orphans = files.filter((f) => !blocks.some((b) => b.file === f.path) && !skippedFiles.has(f.path));
1741
+ if (orphans.length) notes = [...notes, `${orphans.length} archivo(s) quedaron sin explicación del modelo.`];
1742
+ const decorated = files.map((file) => ({
1743
+ ...file,
1744
+ group: dominantGroup(blocks, file.path, groupOrder)
1745
+ }));
1746
+ return {
1747
+ ...skeleton,
1748
+ version: 2,
1749
+ intent: text$1(payload?.intent, MAX.intent),
1750
+ groups: liveGroups,
1751
+ blocks,
1752
+ findings,
1753
+ skipped,
1754
+ files: decorated,
1755
+ stats: {
1756
+ ...skeleton.stats,
1757
+ hunks: countHunks(files)
1758
+ },
1759
+ notes,
1760
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
1761
+ };
1762
+ }
1763
+ //#endregion
1764
+ //#region src/lib/server/pick-folder.ts
1765
+ /**
1766
+ * Diálogo nativo de carpetas.
1767
+ * Windows: PowerShell DPI-aware (exit 2 = canceló; 1 = error → fallback).
1768
+ * Otros: @bindrs/rfd; si falla, shell (VBS / osascript / zenity).
1769
+ */
1770
+ async function pickFolder() {
1771
+ if (platform() === "win32") try {
1772
+ return pickWindowsDpiAware();
1773
+ } catch (err) {
1774
+ console.warn("[diff-review] picker DPI-aware falló, probando rfd/fallback:", err instanceof Error ? err.message : err);
1775
+ }
1776
+ try {
1777
+ return pickWithRfdWorker();
1778
+ } catch (err) {
1779
+ console.warn("[diff-review] @bindrs/rfd no disponible, usando fallback:", err instanceof Error ? err.message : err);
1780
+ return pickFolderFallback();
1781
+ }
1782
+ }
1783
+ function resolveNodeBin() {
1784
+ if (process.execPath && /node(\.exe)?$/i.test(process.execPath)) return process.execPath;
1785
+ return "node";
1786
+ }
1787
+ function writeUtf8Bom(path, content) {
1788
+ writeFileSync(path, `\uFEFF${content}`, "utf8");
1789
+ }
1790
+ /**
1791
+ * IFileOpenDialog nativo (Vista+) + DPI Per-Monitor V2 ANTES de cualquier UI.
1792
+ * Exit: 0 ok, 2 canceló, otro error → fallback.
1793
+ */
1794
+ function pickWindowsDpiAware() {
1795
+ const dir = mkdtempSync(join(tmpdir(), "diff-review-pick-"));
1796
+ const script = join(dir, "pick.ps1");
1797
+ const outFile = join(dir, "path.txt");
1798
+ try {
1799
+ writeUtf8Bom(script, `
1800
+ $ErrorActionPreference = 'Stop'
1801
+
1802
+ # 1) DPI awareness ANTES de cargar WinForms / crear ventanas.
1803
+ Add-Type @'
1804
+ using System;
1805
+ using System.Runtime.InteropServices;
1806
+ public static class DiffReviewDpi {
1807
+ [DllImport("user32.dll")] public static extern IntPtr SetThreadDpiAwarenessContext(IntPtr dpiContext);
1808
+ [DllImport("shcore.dll")] public static extern int SetProcessDpiAwareness(int value);
1809
+ [DllImport("user32.dll")] public static extern bool SetProcessDPIAware();
1810
+ public static readonly IntPtr PerMonitorV2 = unchecked((IntPtr)(-4));
1811
+ public static void Enable() {
1812
+ try { SetThreadDpiAwarenessContext(PerMonitorV2); return; } catch { }
1813
+ try { SetProcessDpiAwareness(2); return; } catch { }
1814
+ try { SetProcessDPIAware(); } catch { }
1815
+ }
1816
+ }
1817
+ '@
1818
+ [DiffReviewDpi]::Enable()
1819
+
1820
+ # 2) IFileOpenDialog COM (mismo dialogo nitido que el Explorer).
1821
+ Add-Type @'
1822
+ using System;
1823
+ using System.Runtime.InteropServices;
1824
+
1825
+ [ComImport, Guid("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7")]
1826
+ internal class FileOpenDialogRCW { }
1827
+
1828
+ [ComImport, Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"),
1829
+ InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
1830
+ internal interface IFileDialog {
1831
+ [PreserveSig] int Show(IntPtr parent);
1832
+ void SetFileTypes(uint cFileTypes, IntPtr rgFilterSpec);
1833
+ void SetFileTypeIndex(uint iFileType);
1834
+ void GetFileTypeIndex(out uint piFileType);
1835
+ void Advise(IntPtr pfde, out uint pdwCookie);
1836
+ void Unadvise(uint dwCookie);
1837
+ void SetOptions(uint fos);
1838
+ void GetOptions(out uint pfos);
1839
+ void SetDefaultFolder(IShellItem psi);
1840
+ void SetFolder(IShellItem psi);
1841
+ void GetFolder(out IShellItem ppsi);
1842
+ void GetCurrentSelection(out IShellItem ppsi);
1843
+ void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName);
1844
+ void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);
1845
+ void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
1846
+ void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText);
1847
+ void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
1848
+ void GetResult(out IShellItem ppsi);
1849
+ void AddPlace(IShellItem psi, int fdap);
1850
+ void SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
1851
+ void Close(int hr);
1852
+ void SetClientGuid(ref Guid guid);
1853
+ void ClearClientData();
1854
+ void SetFilter(IntPtr pFilter);
1855
+ }
1856
+
1857
+ [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"),
1858
+ InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
1859
+ internal interface IShellItem {
1860
+ void BindToHandler(IntPtr pbc, ref Guid bhid, ref Guid riid, out IntPtr ppv);
1861
+ void GetParent(out IShellItem ppsi);
1862
+ void GetDisplayName(uint sigdnName, out IntPtr ppszName);
1863
+ void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
1864
+ void Compare(IShellItem psi, uint hint, out int piOrder);
1865
+ }
1866
+
1867
+ public static class DiffReviewFolderPicker {
1868
+ const uint FOS_PICKFOLDERS = 0x00000020;
1869
+ const uint FOS_FORCEFILESYSTEM = 0x00000040;
1870
+ const uint FOS_PATHMUSTEXIST = 0x00000800;
1871
+ const uint SIGDN_FILESYSPATH = 0x80058000;
1872
+
1873
+ [DllImport("ole32.dll")]
1874
+ static extern void CoTaskMemFree(IntPtr pv);
1875
+
1876
+ public static string Pick(string title) {
1877
+ var dialog = (IFileDialog)new FileOpenDialogRCW();
1878
+ uint options;
1879
+ dialog.GetOptions(out options);
1880
+ dialog.SetOptions(options | FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST);
1881
+ if (!string.IsNullOrEmpty(title)) dialog.SetTitle(title);
1882
+ int hr = dialog.Show(IntPtr.Zero);
1883
+ if (hr != 0) return null; // cancel / error
1884
+ IShellItem item;
1885
+ dialog.GetResult(out item);
1886
+ IntPtr psz;
1887
+ item.GetDisplayName(SIGDN_FILESYSPATH, out psz);
1888
+ string path = Marshal.PtrToStringUni(psz);
1889
+ CoTaskMemFree(psz);
1890
+ return path;
1891
+ }
1892
+ }
1893
+ '@
1894
+
1895
+ $path = [DiffReviewFolderPicker]::Pick('Elegi el repositorio git')
1896
+ if ([string]::IsNullOrWhiteSpace($path)) { exit 2 }
1897
+ [System.IO.File]::WriteAllText(${JSON.stringify(outFile)}, $path)
1898
+ exit 0
1899
+ `.trim() + "\r\n");
1900
+ const result = spawnSync("powershell.exe", [
1901
+ "-STA",
1902
+ "-NoProfile",
1903
+ "-ExecutionPolicy",
1904
+ "Bypass",
1905
+ "-File",
1906
+ script
1907
+ ], {
1908
+ encoding: "utf8",
1909
+ windowsHide: false,
1910
+ stdio: [
1911
+ "ignore",
1912
+ "pipe",
1913
+ "pipe"
1914
+ ],
1915
+ timeout: 600 * 1e3
1916
+ });
1917
+ if (result.status === 2) return null;
1918
+ if (result.status !== 0) {
1919
+ const err = (result.stderr || result.stdout || result.error?.message || `exit ${result.status}`).toString().trim();
1920
+ throw new Error(err || "powershell picker fallo");
1921
+ }
1922
+ if (!existsSync(outFile)) throw new Error("powershell picker no escribio la ruta");
1923
+ const path = readFileSync(outFile, "utf8").replace(/^\uFEFF/, "").trim();
1924
+ if (!path) throw new Error("powershell picker devolvio ruta vacia");
1925
+ return path;
1926
+ } finally {
1927
+ try {
1928
+ rmSync(dir, {
1929
+ recursive: true,
1930
+ force: true
1931
+ });
1932
+ } catch {}
1933
+ }
1934
+ }
1935
+ function pickWithRfdWorker() {
1936
+ const dir = mkdtempSync(join(tmpdir(), "diff-review-rfd-"));
1937
+ const worker = join(dir, "pick.mjs");
1938
+ const outFile = join(dir, "path.txt");
1939
+ try {
1940
+ writeFileSync(worker, `import { createRequire } from 'node:module';
1941
+ import { existsSync, writeFileSync } from 'node:fs';
1942
+ import { dirname, join } from 'node:path';
1943
+
1944
+ const require = createRequire(import.meta.url);
1945
+ const outFile = ${JSON.stringify(outFile)};
1946
+
1947
+ function findRfdIndex() {
1948
+ const seen = new Set();
1949
+ let dir = process.cwd();
1950
+ for (let i = 0; i < 16; i++) {
1951
+ if (seen.has(dir)) break;
1952
+ seen.add(dir);
1953
+ const indexJs = join(dir, 'node_modules', '@bindrs', 'rfd', 'index.js');
1954
+ if (existsSync(indexJs)) return indexJs;
1955
+ const parent = dirname(dir);
1956
+ if (parent === dir) break;
1957
+ dir = parent;
1958
+ }
1959
+ throw new Error('No se encontro @bindrs/rfd en node_modules');
1960
+ }
1961
+
1962
+ const { AsyncFileDialog } = require(findRfdIndex());
1963
+ const handle = await new AsyncFileDialog().setTitle('Elegi el repositorio git').pickFolder();
1964
+ if (!handle) process.exit(2);
1965
+ writeFileSync(outFile, handle.path(), 'utf8');
1966
+ `, "utf8");
1967
+ const result = spawnSync(resolveNodeBin(), [worker], {
1968
+ encoding: "utf8",
1969
+ windowsHide: false,
1970
+ stdio: [
1971
+ "ignore",
1972
+ "pipe",
1973
+ "pipe"
1974
+ ],
1975
+ timeout: 600 * 1e3,
1976
+ cwd: process.cwd(),
1977
+ env: process.env
1978
+ });
1979
+ if (result.status === 2 || result.status === 1) return null;
1980
+ if (result.status !== 0) {
1981
+ const err = (result.stderr || result.stdout || result.error?.message || `exit ${result.status}`).toString().trim();
1982
+ throw new Error(err || "worker rfd fallo");
1983
+ }
1984
+ if (!existsSync(outFile)) return null;
1985
+ return readFileSync(outFile, "utf8").replace(/^\uFEFF/, "").trim() || null;
1986
+ } finally {
1987
+ try {
1988
+ rmSync(dir, {
1989
+ recursive: true,
1990
+ force: true
1991
+ });
1992
+ } catch {}
1993
+ }
1994
+ }
1995
+ function pickFolderFallback() {
1996
+ const os = platform();
1997
+ if (os === "win32") return pickWindowsVbs();
1998
+ if (os === "darwin") return pickMac();
1999
+ return pickLinux();
2000
+ }
2001
+ function pickWindowsVbs() {
2002
+ const dir = mkdtempSync(join(tmpdir(), "diff-review-pick-"));
2003
+ const vbs = join(dir, "pick.vbs");
2004
+ const outFile = join(dir, "path.txt");
2005
+ try {
2006
+ writeFileSync(vbs, [
2007
+ "Set shell = CreateObject(\"Shell.Application\")",
2008
+ "Set folder = shell.BrowseForFolder(0, \"Elegi el repositorio git\", 0)",
2009
+ "If folder Is Nothing Then",
2010
+ " WScript.Quit 1",
2011
+ "End If",
2012
+ "Set fso = CreateObject(\"Scripting.FileSystemObject\")",
2013
+ `Set file = fso.CreateTextFile(${JSON.stringify(outFile)}, True, True)`,
2014
+ "file.Write folder.Self.Path",
2015
+ "file.Close"
2016
+ ].join("\r\n"), "utf8");
2017
+ const result = spawnSync("cscript.exe", [
2018
+ "//nologo",
2019
+ "//E:VBScript",
2020
+ vbs
2021
+ ], {
2022
+ encoding: "utf8",
2023
+ windowsHide: false,
2024
+ stdio: [
2025
+ "ignore",
2026
+ "ignore",
2027
+ "pipe"
2028
+ ],
2029
+ timeout: 600 * 1e3
2030
+ });
2031
+ if (result.status === 1) return null;
2032
+ if (result.status !== 0) {
2033
+ const err = (result.stderr || result.error?.message || `exit ${result.status}`).toString().trim();
2034
+ throw new Error(err || "cscript fallo");
2035
+ }
2036
+ if (!existsSync(outFile)) return null;
2037
+ const buf = readFileSync(outFile);
2038
+ return (buf.length >= 2 && buf[0] === 255 && buf[1] === 254 ? buf.subarray(2).toString("utf16le").trim() : buf.toString("utf8").replace(/^\uFEFF/, "").trim()) || null;
2039
+ } catch (err) {
2040
+ const msg = err instanceof Error ? err.message : String(err);
2041
+ if (/canceled|cancelled|abort/i.test(msg)) return null;
2042
+ throw new Error("No se pudo abrir el dialogo de carpetas: " + msg);
2043
+ } finally {
2044
+ try {
2045
+ rmSync(dir, {
2046
+ recursive: true,
2047
+ force: true
2048
+ });
2049
+ } catch {}
2050
+ }
2051
+ }
2052
+ function pickMac() {
2053
+ try {
2054
+ const out = execFileSync("osascript", ["-e", "POSIX path of (choose folder with prompt \"Elegi el repositorio git\")"], {
2055
+ encoding: "utf8",
2056
+ timeout: 600 * 1e3
2057
+ });
2058
+ return String(out || "").trim().replace(/\/$/, "");
2059
+ } catch (err) {
2060
+ if (err.status === 1 || /User canceled|-128/i.test(String(err))) return null;
2061
+ throw new Error("No se pudo abrir el dialogo de carpetas: " + (err instanceof Error ? err.message : String(err)));
2062
+ }
2063
+ }
2064
+ function pickLinux() {
2065
+ try {
2066
+ const out = execFileSync("zenity", [
2067
+ "--file-selection",
2068
+ "--directory",
2069
+ "--title=Elegi el repositorio git"
2070
+ ], {
2071
+ encoding: "utf8",
2072
+ timeout: 600 * 1e3
2073
+ });
2074
+ return String(out || "").trim() || null;
2075
+ } catch (zenityErr) {
2076
+ if (zenityErr.status === 1) return null;
2077
+ try {
2078
+ const out = execFileSync("kdialog", [
2079
+ "--getexistingdirectory",
2080
+ ".",
2081
+ "Elegi el repositorio git"
2082
+ ], {
2083
+ encoding: "utf8",
2084
+ timeout: 600 * 1e3
2085
+ });
2086
+ return String(out || "").trim() || null;
2087
+ } catch (kErr) {
2088
+ if (kErr.status === 1) return null;
2089
+ throw new Error("No hay dialogo de carpetas disponible. Instala zenity o kdialog, o pega la ruta a mano.");
2090
+ }
2091
+ }
2092
+ }
2093
+ //#endregion
2094
+ //#region src/lib/server/git-review.ts
2095
+ function describeGitError(err) {
2096
+ if (err && typeof err === "object") {
2097
+ const rec = err;
2098
+ const stderr = String(rec.stderr || "").trim().split("\n").find(Boolean);
2099
+ if (stderr) return stderr;
2100
+ if (rec.message) return rec.message;
2101
+ }
2102
+ return err instanceof Error ? err.message : String(err);
2103
+ }
2104
+ function resolveSource(source, repo) {
2105
+ if (source === "local" || source === "url") return source;
2106
+ return looksLikeGitUrl(repo) ? "url" : "local";
2107
+ }
2108
+ function loadRepoInfo(source, repo) {
2109
+ if (resolveSource(source, repo) === "url") {
2110
+ const url = normalizeGitUrl(repo);
2111
+ return {
2112
+ repo: url,
2113
+ source: "url",
2114
+ remoteUrl: url,
2115
+ ...listRemoteBranches(url)
2116
+ };
2117
+ }
2118
+ const resolved = assertGitRepo(repo);
2119
+ const listed = listBranches(resolved);
2120
+ return {
2121
+ repo: resolved,
2122
+ source: "local",
2123
+ remoteUrl: getRemoteUrl(resolved) || void 0,
2124
+ ...listed
2125
+ };
2126
+ }
2127
+ async function pickLocalFolder$1() {
2128
+ return pickFolder();
2129
+ }
2130
+ function resolveWorktree(source, repo, branch, base) {
2131
+ if (source === "url") return ensureRemoteWorktree(repo, branch, base);
2132
+ return assertGitRepo(repo);
2133
+ }
2134
+ function toReviewFile(file) {
2135
+ const changeType = file.changeType;
2136
+ const typed = changeType === "added" || changeType === "deleted" || changeType === "renamed" || changeType === "modified" ? changeType : "modified";
2137
+ return {
2138
+ path: file.path,
2139
+ oldPath: file.oldPath || void 0,
2140
+ changeType: typed,
2141
+ diff: String(file.diff ?? "").replaceAll("\0", ""),
2142
+ group: file.group ?? ""
2143
+ };
2144
+ }
2145
+ /**
2146
+ * Contenido del archivo en la punta del branch, para rellenar en el viewer las líneas
2147
+ * que el diff no trae. Devuelve null (en vez de reventar) cuando el repo no está a mano
2148
+ * o el archivo es binario: ahí el viewer apaga los botones de expandir.
2149
+ */
2150
+ function loadFileLines(input) {
2151
+ try {
2152
+ return showFileLines(resolveWorktree(resolveSource(input.source, input.repo), input.repo, input.branch, input.base || "develop"), input.branch, input.path);
2153
+ } catch (err) {
2154
+ console.error("[diff-review git] no se pudo leer", input.path, describeGitError(err));
2155
+ return null;
2156
+ }
2157
+ }
2158
+ function hydrateReviewPayload(input) {
2159
+ const skeleton = extractBranchDiff({
2160
+ repo: resolveWorktree(resolveSource(input.source, input.repo), input.repo, input.branch, input.base || "develop"),
2161
+ branch: input.branch,
2162
+ base: input.base || "develop"
2163
+ });
2164
+ const extraNotes = Array.isArray(input.payload?.notes) ? input.payload.notes.filter((n) => typeof n === "string") : [];
2165
+ const built = buildReview({
2166
+ skeleton,
2167
+ payload: input.payload,
2168
+ notes: extraNotes
2169
+ });
2170
+ const doc = {
2171
+ intent: built.intent,
2172
+ groups: built.groups,
2173
+ blocks: built.blocks,
2174
+ findings: built.findings,
2175
+ skipped: built.skipped,
2176
+ files: (built.files || []).map(toReviewFile),
2177
+ notes: built.notes || []
2178
+ };
2179
+ return JSON.parse(JSON.stringify(doc));
2180
+ }
2181
+ //#endregion
2182
+ //#region src/lib/report/git.remote.ts
2183
+ var git_remote_exports = /* @__PURE__ */ __exportAll({
2184
+ fileLines: () => fileLines,
2185
+ hydrateFromGit: () => hydrateFromGit,
2186
+ loadRepo: () => loadRepo,
2187
+ pickLocalFolder: () => pickLocalFolder
2188
+ });
2189
+ function fail(err) {
2190
+ const message = describeGitError(err) || "No se pudo hablar con git.";
2191
+ console.error("[diff-review git]", message, err);
2192
+ error(400, message);
2193
+ }
2194
+ var loadRepo = /* @__PURE__ */ query("unchecked", async (input) => {
2195
+ try {
2196
+ return loadRepoInfo(input?.source, String(input?.repo || "").trim());
2197
+ } catch (err) {
2198
+ fail(err);
2199
+ }
2200
+ });
2201
+ var pickLocalFolder = /* @__PURE__ */ command("unchecked", async (_input) => {
2202
+ try {
2203
+ return await pickLocalFolder$1();
2204
+ } catch (err) {
2205
+ fail(err);
2206
+ }
2207
+ });
2208
+ /** Líneas del archivo en el branch revisado, para expandir el contexto oculto de un diff. */
2209
+ var fileLines = /* @__PURE__ */ query("unchecked", async (input) => loadFileLines({
2210
+ repo: String(input?.repo || "").trim(),
2211
+ branch: String(input?.branch || "").trim(),
2212
+ base: String(input?.base || "develop").trim(),
2213
+ source: input?.source,
2214
+ path: String(input?.path || "").trim()
2215
+ }));
2216
+ var hydrateFromGit = /* @__PURE__ */ command("unchecked", async (input) => {
2217
+ try {
2218
+ return hydrateReviewPayload({
2219
+ repo: String(input?.repo || "").trim(),
2220
+ branch: String(input?.branch || "").trim(),
2221
+ base: String(input?.base || "develop").trim(),
2222
+ source: input?.source,
2223
+ payload: input?.payload
2224
+ });
2225
+ } catch (err) {
2226
+ fail(err);
2227
+ }
2228
+ });
2229
+ init_remote_functions(git_remote_exports, "src/lib/report/git.remote.ts", "1g4ayag");
2230
+ for (const [name, fn] of Object.entries(git_remote_exports)) {
2231
+ fn.__.id = "1g4ayag/" + name;
2232
+ fn.__.name = name;
2233
+ }
2234
+
2235
+ //#region \0sveltekit-remote:1g4ayag
2236
+ var _sveltekit_remote_1g4ayag_default = git_remote_exports;
2237
+
2238
+ export { _sveltekit_remote_1g4ayag_default as default };
2239
+ //# sourceMappingURL=remote-1g4ayag-ipWXvkjB.js.map