@spotpatch/dev-server 0.3.0 → 0.5.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.
package/dist/index.js CHANGED
@@ -204,7 +204,11 @@ function createAgentJobManager(options) {
204
204
  }
205
205
  };
206
206
  const applyChange = async (job, preparedChange) => {
207
- transition(job, "applying", "Applying validated changes to the project.");
207
+ transition(
208
+ job,
209
+ "applying",
210
+ job.applyMode === "trusted-auto" ? "Applying trusted change directly to the project." : "Applying validated changes to the project."
211
+ );
208
212
  try {
209
213
  await dependencies.applyChange(preparedChange);
210
214
  transition(job, "applied", "Changes were applied to local project files.");
@@ -243,11 +247,15 @@ function createAgentJobManager(options) {
243
247
  );
244
248
  }
245
249
  };
250
+ const execution = Object.freeze({
251
+ ...options.ai.execution,
252
+ applyMode: job.applyMode
253
+ });
246
254
  const preparedChange = await dependencies.executeChange({
247
255
  annotation: job.annotation,
248
256
  callbacks,
249
257
  credential: job.credential,
250
- execution: options.ai.execution,
258
+ execution,
251
259
  jobId: job.id,
252
260
  model: job.model,
253
261
  provider: job.provider,
@@ -734,7 +742,8 @@ import {
734
742
  DEFAULT_AGENT_LIMITS,
735
743
  MAX_ANNOTATION_TARGETS,
736
744
  SPOTPATCH_EDITOR_PREFERENCES,
737
- SPOTPATCH_LOCALE_PREFERENCES
745
+ SPOTPATCH_LOCALE_PREFERENCES,
746
+ DEFAULT_DATA_FLOW_LIMITS
738
747
  } from "@spotpatch/shared";
739
748
  import { z } from "zod";
740
749
  var DEFAULT_EXCLUDE = Object.freeze([
@@ -745,7 +754,7 @@ var DEFAULT_EXCLUDE = Object.freeze([
745
754
  /(?:^|\/)dist(?:\/|$)/,
746
755
  /(?:^|\/)coverage(?:\/|$)/
747
756
  ]);
748
- var DEFAULT_INCLUDE = Object.freeze([/(?:^|[/\\])src[/\\].+\.(?:jsx|tsx)$/]);
757
+ var DEFAULT_INCLUDE = Object.freeze([/(?:^|[/\\])src[/\\].+\.(?:js|jsx|ts|tsx)$/]);
749
758
  var DEFAULT_BUDGET = Object.freeze({
750
759
  totalCharacters: 16e3,
751
760
  domCharacters: 3e3,
@@ -766,7 +775,12 @@ var DEFAULT_OPTIONS = Object.freeze({
766
775
  debug: false,
767
776
  locale: "auto",
768
777
  maxTargets: 8,
769
- ai: false
778
+ ai: false,
779
+ dataFlow: Object.freeze({
780
+ enabled: false,
781
+ runtime: "dispatch",
782
+ limits: DEFAULT_DATA_FLOW_LIMITS
783
+ })
770
784
  });
771
785
  var PROFILE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
772
786
  var ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]{1,127}$/;
@@ -1058,6 +1072,36 @@ function assertPositiveBudget(budget) {
1058
1072
  }
1059
1073
  }
1060
1074
  }
1075
+ function resolveDataFlowOptions(options) {
1076
+ if (options === void 0 || options === false) {
1077
+ return DEFAULT_OPTIONS.dataFlow;
1078
+ }
1079
+ const candidate = options;
1080
+ if (typeof candidate !== "object" || candidate === null) {
1081
+ throw new RangeError("SpotPatch dataFlow configuration is invalid.");
1082
+ }
1083
+ const runtime = options.runtime ?? "dispatch";
1084
+ if (runtime !== "dispatch") {
1085
+ throw new RangeError("SpotPatch dataFlow runtime mode is invalid.");
1086
+ }
1087
+ return Object.freeze({
1088
+ enabled: true,
1089
+ runtime,
1090
+ limits: DEFAULT_DATA_FLOW_LIMITS
1091
+ });
1092
+ }
1093
+ function createRuntimeDataFlowConfig(options) {
1094
+ return Object.freeze({
1095
+ enabled: options.enabled,
1096
+ runtime: options.runtime,
1097
+ limits: Object.freeze({
1098
+ observationMaxEntries: options.limits.observationMaxEntries,
1099
+ observationMaxBytes: options.limits.observationMaxBytes,
1100
+ observationTtlMs: options.limits.observationTtlMs,
1101
+ reportMaxBytes: options.limits.reportMaxBytes
1102
+ })
1103
+ });
1104
+ }
1061
1105
  function resolveOptions(options = {}, environmentAi) {
1062
1106
  if (options.trustedFastMode !== void 0 && typeof options.trustedFastMode !== "boolean") {
1063
1107
  throw new RangeError("SpotPatch trustedFastMode must be a boolean.");
@@ -1093,7 +1137,8 @@ function resolveOptions(options = {}, environmentAi) {
1093
1137
  debug: options.debug ?? DEFAULT_OPTIONS.debug,
1094
1138
  locale,
1095
1139
  maxTargets,
1096
- ai: resolveAiOptions(options.ai ?? environmentAi)
1140
+ ai: resolveAiOptions(options.ai ?? environmentAi),
1141
+ dataFlow: resolveDataFlowOptions(options.dataFlow)
1097
1142
  };
1098
1143
  if (resolved.shortcut.trim().length === 0 || resolved.shortcut.length > 128 || resolved.shortcut.includes("\0")) {
1099
1144
  throw new RangeError("SpotPatch shortcut is invalid.");
@@ -1279,37 +1324,62 @@ function createSourceRegistry(options = {}) {
1279
1324
  const createId = options.createId ?? createRandomSourceId;
1280
1325
  const pathToId = /* @__PURE__ */ new Map();
1281
1326
  const idToPath = /* @__PURE__ */ new Map();
1327
+ const componentAnchors = /* @__PURE__ */ new Map();
1328
+ const componentIdsByPath = /* @__PURE__ */ new Map();
1329
+ function registerSourcePath(absolutePath) {
1330
+ const normalizedPath = normalizeAbsolutePath(absolutePath);
1331
+ const existingId = pathToId.get(normalizedPath);
1332
+ if (existingId !== void 0) {
1333
+ return existingId;
1334
+ }
1335
+ let fileId = createId();
1336
+ while (idToPath.has(fileId)) fileId = createId();
1337
+ pathToId.set(normalizedPath, fileId);
1338
+ idToPath.set(fileId, normalizedPath);
1339
+ return fileId;
1340
+ }
1282
1341
  return Object.freeze({
1283
1342
  register(absolutePath) {
1343
+ return registerSourcePath(absolutePath);
1344
+ },
1345
+ registerDataFlowComponents(absolutePath, sourceVersion, components) {
1284
1346
  const normalizedPath = normalizeAbsolutePath(absolutePath);
1285
- const existingId = pathToId.get(normalizedPath);
1286
- if (existingId !== void 0) {
1287
- return existingId;
1347
+ const previousIds = componentIdsByPath.get(normalizedPath);
1348
+ for (const componentSourceId of previousIds ?? []) {
1349
+ componentAnchors.delete(componentSourceId);
1288
1350
  }
1289
- let fileId = createId();
1290
- while (idToPath.has(fileId)) {
1291
- fileId = createId();
1351
+ const fileId = registerSourcePath(normalizedPath);
1352
+ const currentIds = /* @__PURE__ */ new Set();
1353
+ for (const component of components) {
1354
+ currentIds.add(component.componentSourceId);
1355
+ componentAnchors.set(
1356
+ component.componentSourceId,
1357
+ Object.freeze({ ...component, fileId, sourceVersion })
1358
+ );
1292
1359
  }
1293
- pathToId.set(normalizedPath, fileId);
1294
- idToPath.set(fileId, normalizedPath);
1295
- return fileId;
1360
+ componentIdsByPath.set(normalizedPath, currentIds);
1296
1361
  },
1297
1362
  resolve(fileId) {
1298
1363
  return idToPath.get(fileId);
1299
1364
  },
1365
+ resolveDataFlowComponent(componentSourceId) {
1366
+ return componentAnchors.get(componentSourceId);
1367
+ },
1300
1368
  clear() {
1301
1369
  pathToId.clear();
1302
1370
  idToPath.clear();
1371
+ componentAnchors.clear();
1372
+ componentIdsByPath.clear();
1303
1373
  }
1304
1374
  });
1305
1375
  }
1306
1376
 
1307
1377
  // src/server/middleware.ts
1308
1378
  import {
1309
- ERROR_CODES as ERROR_CODES9,
1379
+ ERROR_CODES as ERROR_CODES10,
1310
1380
  SPOTPATCH_API_BASE,
1311
1381
  SPOTPATCH_ENDPOINTS as SPOTPATCH_ENDPOINTS2,
1312
- SpotPatchError as SpotPatchError9,
1382
+ SpotPatchError as SpotPatchError10,
1313
1383
  openEditorRequestSchema,
1314
1384
  sourceContextRequestSchema
1315
1385
  } from "@spotpatch/shared";
@@ -2142,10 +2212,186 @@ function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES2) {
2142
2212
  }
2143
2213
  var launchConfiguredEditor = createEditorLauncher();
2144
2214
 
2215
+ // src/server/data-flow-http.ts
2216
+ import { createHash as createHash2 } from "crypto";
2217
+ import {
2218
+ createStaticDataFlowAnalyzer
2219
+ } from "@spotpatch/analyzer";
2220
+ import {
2221
+ DATA_FLOW_SCHEMA_VERSION,
2222
+ ERROR_CODES as ERROR_CODES7,
2223
+ SpotPatchError as SpotPatchError7,
2224
+ dataFlowComponentReportRequestSchema,
2225
+ dataFlowPageReportRequestSchema,
2226
+ limitDataFlowReportCollections
2227
+ } from "@spotpatch/shared";
2228
+ function envelopeBytes(report) {
2229
+ return Buffer.byteLength(JSON.stringify({ ok: true, data: report }), "utf8");
2230
+ }
2231
+ function limitDataFlowReportToBytes(report, maximumBytes) {
2232
+ const structurallyLimited = limitDataFlowReportCollections(report);
2233
+ if (envelopeBytes(structurallyLimited) <= maximumBytes) {
2234
+ return structurallyLimited;
2235
+ }
2236
+ let limited = limitDataFlowReportCollections(structurallyLimited, {
2237
+ forceTruncation: true,
2238
+ maximumDependencies: 0,
2239
+ truncatedBy: "bytes"
2240
+ });
2241
+ if (envelopeBytes(limited) > maximumBytes) {
2242
+ throw new SpotPatchError7(ERROR_CODES7.INTERNAL_ERROR);
2243
+ }
2244
+ for (let maximumDependencies = 1; maximumDependencies <= structurallyLimited.dependencies.length; maximumDependencies += 1) {
2245
+ const candidate = limitDataFlowReportCollections(structurallyLimited, {
2246
+ forceTruncation: true,
2247
+ maximumDependencies,
2248
+ truncatedBy: "bytes"
2249
+ });
2250
+ if (envelopeBytes(candidate) > maximumBytes) break;
2251
+ limited = candidate;
2252
+ }
2253
+ return limited;
2254
+ }
2255
+ function createDataFlowAnalyzer(options) {
2256
+ if (!options.options.dataFlow.enabled) return void 0;
2257
+ return createStaticDataFlowAnalyzer({
2258
+ root: options.root,
2259
+ registryEpoch: options.session.id,
2260
+ registerSource: (absolutePath) => options.registry.register(absolutePath),
2261
+ limits: options.options.dataFlow.limits
2262
+ });
2263
+ }
2264
+ async function analyzeTarget(request, analyzer, options) {
2265
+ const resolvedRequest = (() => {
2266
+ if ("componentSourceId" in request) {
2267
+ const anchor = options.registry.resolveDataFlowComponent(
2268
+ request.componentSourceId
2269
+ );
2270
+ if (anchor?.sourceVersion !== request.sourceVersion) {
2271
+ throw new SpotPatchError7(ERROR_CODES7.DATA_FLOW_SOURCE_STALE);
2272
+ }
2273
+ return anchor;
2274
+ }
2275
+ return request;
2276
+ })();
2277
+ const absolutePath = await resolveSourceFile({
2278
+ fileId: resolvedRequest.fileId,
2279
+ registry: options.registry,
2280
+ root: options.root
2281
+ });
2282
+ const report = analyzer.analyzeComponent({
2283
+ absolutePath,
2284
+ line: resolvedRequest.line,
2285
+ column: resolvedRequest.column
2286
+ });
2287
+ if (resolvedRequest.sourceVersion !== void 0 && resolvedRequest.sourceVersion !== report.component.source.sourceVersion) {
2288
+ throw new SpotPatchError7(ERROR_CODES7.DATA_FLOW_SOURCE_STALE);
2289
+ }
2290
+ return limitDataFlowReportToBytes(
2291
+ report,
2292
+ options.options.dataFlow.limits.reportMaxBytes
2293
+ );
2294
+ }
2295
+ function requireAnalyzer(analyzer) {
2296
+ if (analyzer === void 0) {
2297
+ throw new SpotPatchError7(ERROR_CODES7.DATA_FLOW_DISABLED);
2298
+ }
2299
+ return analyzer;
2300
+ }
2301
+ async function handleComponentDataFlowReport(request, analyzer, options) {
2302
+ const parsed = dataFlowComponentReportRequestSchema.safeParse(
2303
+ await readJsonRequestBody(
2304
+ request,
2305
+ options.options.dataFlow.limits.protocolRequestMaxBytes
2306
+ )
2307
+ );
2308
+ if (!parsed.success) {
2309
+ throw new SpotPatchError7(ERROR_CODES7.INVALID_REQUEST);
2310
+ }
2311
+ return analyzeTarget(parsed.data, requireAnalyzer(analyzer), options);
2312
+ }
2313
+ async function handlePageDataFlowReport(request, analyzer, options) {
2314
+ const parsed = dataFlowPageReportRequestSchema.safeParse(
2315
+ await readJsonRequestBody(
2316
+ request,
2317
+ options.options.dataFlow.limits.protocolRequestMaxBytes
2318
+ )
2319
+ );
2320
+ if (!parsed.success) {
2321
+ throw new SpotPatchError7(ERROR_CODES7.INVALID_REQUEST);
2322
+ }
2323
+ const activeAnalyzer = requireAnalyzer(analyzer);
2324
+ const componentReports = await Promise.all(
2325
+ parsed.data.targets.map((target) => analyzeTarget(target, activeAnalyzer, options))
2326
+ );
2327
+ const dependencies = new Map(
2328
+ componentReports.flatMap(
2329
+ (report2) => report2.dependencies.map((dependency) => [dependency.id, dependency])
2330
+ )
2331
+ );
2332
+ const evidence = new Map(
2333
+ componentReports.flatMap(
2334
+ (report2) => report2.evidence.map((entry) => [entry.id, entry])
2335
+ )
2336
+ );
2337
+ const diagnostics = new Map(
2338
+ componentReports.flatMap(
2339
+ (report2) => report2.diagnostics.map((entry) => [entry.id, entry])
2340
+ )
2341
+ );
2342
+ const analyzedVersions = new Set(
2343
+ componentReports.flatMap((report2) => report2.baseline.analyzedSourceVersions)
2344
+ );
2345
+ const reportId = `page_${createHash2("sha256").update(componentReports.map((report2) => report2.reportId).join("\0")).digest("base64url").slice(0, 22)}`;
2346
+ const complete = componentReports.every((report2) => report2.completeness.complete);
2347
+ const report = Object.freeze({
2348
+ schemaVersion: DATA_FLOW_SCHEMA_VERSION,
2349
+ reportId,
2350
+ baseline: Object.freeze({
2351
+ registryEpoch: options.session.id,
2352
+ analyzerVersion: componentReports[0]?.baseline.analyzerVersion ?? "unavailable",
2353
+ adapterSetHash: componentReports[0]?.baseline.adapterSetHash ?? "unavailable",
2354
+ analyzedSourceVersions: Object.freeze([...analyzedVersions].sort())
2355
+ }),
2356
+ capability: Object.freeze({
2357
+ enabled: true,
2358
+ staticAnalysis: complete ? "available" : "partial",
2359
+ runtimeObservation: "dispatch-only",
2360
+ responseShape: "consumed-fields-only",
2361
+ aiAssistance: "disabled",
2362
+ reasons: Object.freeze(
2363
+ componentReports.flatMap((report2) => report2.capability.reasons)
2364
+ )
2365
+ }),
2366
+ dependencies: Object.freeze([...dependencies.values()]),
2367
+ evidence: Object.freeze([...evidence.values()]),
2368
+ diagnostics: Object.freeze([...diagnostics.values()]),
2369
+ completeness: Object.freeze({
2370
+ complete,
2371
+ visitedModules: componentReports.reduce(
2372
+ (total, report2) => total + report2.completeness.visitedModules,
2373
+ 0
2374
+ ),
2375
+ visitedCallsites: componentReports.reduce(
2376
+ (total, report2) => total + report2.completeness.visitedCallsites,
2377
+ 0
2378
+ ),
2379
+ frontierCount: componentReports.reduce(
2380
+ (total, report2) => total + report2.completeness.frontierCount,
2381
+ 0
2382
+ )
2383
+ })
2384
+ });
2385
+ return limitDataFlowReportToBytes(
2386
+ report,
2387
+ options.options.dataFlow.limits.reportMaxBytes
2388
+ );
2389
+ }
2390
+
2145
2391
  // src/server/request-security.ts
2146
2392
  import { timingSafeEqual } from "crypto";
2147
2393
  import { isIP } from "net";
2148
- import { ERROR_CODES as ERROR_CODES7, SPOTPATCH_TOKEN_HEADER, SpotPatchError as SpotPatchError7 } from "@spotpatch/shared";
2394
+ import { ERROR_CODES as ERROR_CODES8, SPOTPATCH_TOKEN_HEADER, SpotPatchError as SpotPatchError8 } from "@spotpatch/shared";
2149
2395
  function getSingleHeader(request, name) {
2150
2396
  const value = request.headers[name.toLowerCase()];
2151
2397
  return Array.isArray(value) ? value[0] : value;
@@ -2192,32 +2438,32 @@ function parseOrigin(value) {
2192
2438
  function assertRequestAuthorized(request, options) {
2193
2439
  const actualToken = getSingleHeader(request, SPOTPATCH_TOKEN_HEADER);
2194
2440
  if (!tokensMatch(actualToken, options.sessionToken)) {
2195
- throw new SpotPatchError7(ERROR_CODES7.INVALID_TOKEN);
2441
+ throw new SpotPatchError8(ERROR_CODES8.INVALID_TOKEN);
2196
2442
  }
2197
2443
  const hostHeader = getSingleHeader(request, "host");
2198
2444
  const originHeader = getSingleHeader(request, "origin");
2199
2445
  const host = hostHeader === void 0 ? void 0 : parseHost(hostHeader);
2200
2446
  const origin = originHeader === void 0 ? void 0 : parseOrigin(originHeader);
2201
2447
  if (host === void 0 || origin === void 0) {
2202
- throw new SpotPatchError7(ERROR_CODES7.ORIGIN_NOT_ALLOWED);
2448
+ throw new SpotPatchError8(ERROR_CODES8.ORIGIN_NOT_ALLOWED);
2203
2449
  }
2204
2450
  const hostIsLoopback = isLoopbackHostname(host.hostname);
2205
2451
  const originIsLoopback = isLoopbackHostname(origin.hostname);
2206
2452
  if (!options.allowLan) {
2207
2453
  if (!hostIsLoopback || !originIsLoopback) {
2208
- throw new SpotPatchError7(ERROR_CODES7.ORIGIN_NOT_ALLOWED);
2454
+ throw new SpotPatchError8(ERROR_CODES8.ORIGIN_NOT_ALLOWED);
2209
2455
  }
2210
2456
  return;
2211
2457
  }
2212
2458
  if (!originIsLoopback && origin.host.toLowerCase() !== host.host.toLowerCase()) {
2213
- throw new SpotPatchError7(ERROR_CODES7.ORIGIN_NOT_ALLOWED);
2459
+ throw new SpotPatchError8(ERROR_CODES8.ORIGIN_NOT_ALLOWED);
2214
2460
  }
2215
2461
  }
2216
2462
 
2217
2463
  // src/server/runtime-bootstrap.ts
2218
2464
  import {
2219
- ERROR_CODES as ERROR_CODES8,
2220
- SpotPatchError as SpotPatchError8,
2465
+ ERROR_CODES as ERROR_CODES9,
2466
+ SpotPatchError as SpotPatchError9,
2221
2467
  runtimeBootstrapRequestSchema,
2222
2468
  runtimeConfigSchema
2223
2469
  } from "@spotpatch/shared";
@@ -2247,7 +2493,7 @@ function resolveRuntimeBootstrapOptions(options) {
2247
2493
  function assertRuntimeBootstrapRequest(request, expectedOrigin) {
2248
2494
  const contentType = getSingleHeader2(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
2249
2495
  if (request.method !== "POST" || contentType !== "application/json") {
2250
- throw new SpotPatchError8(ERROR_CODES8.INVALID_REQUEST);
2496
+ throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2251
2497
  }
2252
2498
  const host = getSingleHeader2(request, "host");
2253
2499
  let hostIsLoopback = false;
@@ -2259,7 +2505,7 @@ function assertRuntimeBootstrapRequest(request, expectedOrigin) {
2259
2505
  }
2260
2506
  }
2261
2507
  if (!hostIsLoopback || getSingleHeader2(request, "origin") !== expectedOrigin || getSingleHeader2(request, "sec-fetch-site") !== "same-origin") {
2262
- throw new SpotPatchError8(ERROR_CODES8.ORIGIN_NOT_ALLOWED);
2508
+ throw new SpotPatchError9(ERROR_CODES9.ORIGIN_NOT_ALLOWED);
2263
2509
  }
2264
2510
  }
2265
2511
  async function readRuntimeBootstrap(request, options) {
@@ -2268,81 +2514,87 @@ async function readRuntimeBootstrap(request, options) {
2268
2514
  await readJsonRequestBody(request)
2269
2515
  );
2270
2516
  if (!parsedBody.success) {
2271
- throw new SpotPatchError8(ERROR_CODES8.INVALID_REQUEST);
2517
+ throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2272
2518
  }
2273
2519
  return options.runtimeConfig;
2274
2520
  }
2275
2521
 
2276
2522
  // src/server/middleware.ts
2277
2523
  var STATUS_BY_ERROR = Object.freeze({
2278
- [ERROR_CODES9.INVALID_REQUEST]: 400,
2279
- [ERROR_CODES9.INVALID_TOKEN]: 401,
2280
- [ERROR_CODES9.ORIGIN_NOT_ALLOWED]: 403,
2281
- [ERROR_CODES9.SOURCE_NOT_FOUND]: 404,
2282
- [ERROR_CODES9.SOURCE_OUTSIDE_ROOT]: 403,
2283
- [ERROR_CODES9.SOURCE_TOO_LARGE]: 413,
2284
- [ERROR_CODES9.EDITOR_OPEN_FAILED]: 500,
2285
- [ERROR_CODES9.AI_DISABLED]: 404,
2286
- [ERROR_CODES9.PROVIDER_NOT_CONFIGURED]: 503,
2287
- [ERROR_CODES9.PROVIDER_AUTH_FAILED]: 502,
2288
- [ERROR_CODES9.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
2289
- [ERROR_CODES9.MODEL_NOT_ALLOWED]: 400,
2290
- [ERROR_CODES9.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
2291
- [ERROR_CODES9.PROVIDER_RATE_LIMITED]: 429,
2292
- [ERROR_CODES9.AGENT_BUSY]: 409,
2293
- [ERROR_CODES9.AGENT_LIMIT_EXCEEDED]: 413,
2294
- [ERROR_CODES9.AGENT_CANCELLED]: 409,
2295
- [ERROR_CODES9.WORKTREE_DIRTY]: 409,
2296
- [ERROR_CODES9.WORKTREE_NOT_REPOSITORY]: 409,
2297
- [ERROR_CODES9.WORKTREE_OPERATION_IN_PROGRESS]: 409,
2298
- [ERROR_CODES9.WORKTREE_CONFLICTED]: 409,
2299
- [ERROR_CODES9.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
2300
- [ERROR_CODES9.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
2301
- [ERROR_CODES9.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
2302
- [ERROR_CODES9.TOOL_DENIED]: 403,
2303
- [ERROR_CODES9.TOOL_INPUT_INVALID]: 422,
2304
- [ERROR_CODES9.TOOL_ARGUMENTS_INVALID]: 422,
2305
- [ERROR_CODES9.TOOL_CALL_ID_CONFLICT]: 422,
2306
- [ERROR_CODES9.TOOL_PATH_DENIED]: 403,
2307
- [ERROR_CODES9.PATCH_REJECTED]: 422,
2308
- [ERROR_CODES9.VALIDATION_FAILED]: 422,
2309
- [ERROR_CODES9.APPLY_CONFLICT]: 409,
2310
- [ERROR_CODES9.INTERNAL_ERROR]: 500
2524
+ [ERROR_CODES10.INVALID_REQUEST]: 400,
2525
+ [ERROR_CODES10.INVALID_TOKEN]: 401,
2526
+ [ERROR_CODES10.ORIGIN_NOT_ALLOWED]: 403,
2527
+ [ERROR_CODES10.SOURCE_NOT_FOUND]: 404,
2528
+ [ERROR_CODES10.SOURCE_OUTSIDE_ROOT]: 403,
2529
+ [ERROR_CODES10.SOURCE_TOO_LARGE]: 413,
2530
+ [ERROR_CODES10.EDITOR_OPEN_FAILED]: 500,
2531
+ [ERROR_CODES10.DATA_FLOW_DISABLED]: 404,
2532
+ [ERROR_CODES10.DATA_FLOW_SOURCE_STALE]: 409,
2533
+ [ERROR_CODES10.DATA_FLOW_ANALYSIS_CANCELLED]: 409,
2534
+ [ERROR_CODES10.AI_DISABLED]: 404,
2535
+ [ERROR_CODES10.PROVIDER_NOT_CONFIGURED]: 503,
2536
+ [ERROR_CODES10.PROVIDER_AUTH_FAILED]: 502,
2537
+ [ERROR_CODES10.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
2538
+ [ERROR_CODES10.MODEL_NOT_ALLOWED]: 400,
2539
+ [ERROR_CODES10.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
2540
+ [ERROR_CODES10.PROVIDER_RATE_LIMITED]: 429,
2541
+ [ERROR_CODES10.AGENT_BUSY]: 409,
2542
+ [ERROR_CODES10.AGENT_LIMIT_EXCEEDED]: 413,
2543
+ [ERROR_CODES10.AGENT_CANCELLED]: 409,
2544
+ [ERROR_CODES10.WORKTREE_DIRTY]: 409,
2545
+ [ERROR_CODES10.WORKTREE_NOT_REPOSITORY]: 409,
2546
+ [ERROR_CODES10.WORKTREE_OPERATION_IN_PROGRESS]: 409,
2547
+ [ERROR_CODES10.WORKTREE_CONFLICTED]: 409,
2548
+ [ERROR_CODES10.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
2549
+ [ERROR_CODES10.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
2550
+ [ERROR_CODES10.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
2551
+ [ERROR_CODES10.TOOL_DENIED]: 403,
2552
+ [ERROR_CODES10.TOOL_INPUT_INVALID]: 422,
2553
+ [ERROR_CODES10.TOOL_ARGUMENTS_INVALID]: 422,
2554
+ [ERROR_CODES10.TOOL_CALL_ID_CONFLICT]: 422,
2555
+ [ERROR_CODES10.TOOL_PATH_DENIED]: 403,
2556
+ [ERROR_CODES10.PATCH_REJECTED]: 422,
2557
+ [ERROR_CODES10.VALIDATION_FAILED]: 422,
2558
+ [ERROR_CODES10.APPLY_CONFLICT]: 409,
2559
+ [ERROR_CODES10.INTERNAL_ERROR]: 500
2311
2560
  });
2312
2561
  var PUBLIC_MESSAGES = Object.freeze({
2313
- [ERROR_CODES9.INVALID_REQUEST]: "The request is invalid.",
2314
- [ERROR_CODES9.INVALID_TOKEN]: "The session token is invalid.",
2315
- [ERROR_CODES9.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
2316
- [ERROR_CODES9.SOURCE_NOT_FOUND]: "The source file is unavailable.",
2317
- [ERROR_CODES9.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
2318
- [ERROR_CODES9.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
2319
- [ERROR_CODES9.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
2320
- [ERROR_CODES9.AI_DISABLED]: "AI execution is not enabled.",
2321
- [ERROR_CODES9.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
2322
- [ERROR_CODES9.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
2323
- [ERROR_CODES9.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
2324
- [ERROR_CODES9.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
2325
- [ERROR_CODES9.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
2326
- [ERROR_CODES9.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
2327
- [ERROR_CODES9.AGENT_BUSY]: "Another Agent job is already running.",
2328
- [ERROR_CODES9.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
2329
- [ERROR_CODES9.AGENT_CANCELLED]: "The Agent job was cancelled.",
2330
- [ERROR_CODES9.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
2331
- [ERROR_CODES9.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
2332
- [ERROR_CODES9.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
2333
- [ERROR_CODES9.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
2334
- [ERROR_CODES9.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
2335
- [ERROR_CODES9.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
2336
- [ERROR_CODES9.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
2337
- [ERROR_CODES9.TOOL_DENIED]: "The Agent tool request was denied.",
2338
- [ERROR_CODES9.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
2339
- [ERROR_CODES9.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
2340
- [ERROR_CODES9.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
2341
- [ERROR_CODES9.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
2342
- [ERROR_CODES9.PATCH_REJECTED]: "The proposed patch was rejected.",
2343
- [ERROR_CODES9.VALIDATION_FAILED]: "The proposed change failed validation.",
2344
- [ERROR_CODES9.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
2345
- [ERROR_CODES9.INTERNAL_ERROR]: "The request could not be completed."
2562
+ [ERROR_CODES10.INVALID_REQUEST]: "The request is invalid.",
2563
+ [ERROR_CODES10.INVALID_TOKEN]: "The session token is invalid.",
2564
+ [ERROR_CODES10.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
2565
+ [ERROR_CODES10.SOURCE_NOT_FOUND]: "The source file is unavailable.",
2566
+ [ERROR_CODES10.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
2567
+ [ERROR_CODES10.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
2568
+ [ERROR_CODES10.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
2569
+ [ERROR_CODES10.DATA_FLOW_DISABLED]: "Component data-flow analysis is not enabled.",
2570
+ [ERROR_CODES10.DATA_FLOW_SOURCE_STALE]: "The selected source version is stale.",
2571
+ [ERROR_CODES10.DATA_FLOW_ANALYSIS_CANCELLED]: "The data-flow analysis was cancelled.",
2572
+ [ERROR_CODES10.AI_DISABLED]: "AI execution is not enabled.",
2573
+ [ERROR_CODES10.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
2574
+ [ERROR_CODES10.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
2575
+ [ERROR_CODES10.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
2576
+ [ERROR_CODES10.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
2577
+ [ERROR_CODES10.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
2578
+ [ERROR_CODES10.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
2579
+ [ERROR_CODES10.AGENT_BUSY]: "Another Agent job is already running.",
2580
+ [ERROR_CODES10.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
2581
+ [ERROR_CODES10.AGENT_CANCELLED]: "The Agent job was cancelled.",
2582
+ [ERROR_CODES10.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
2583
+ [ERROR_CODES10.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
2584
+ [ERROR_CODES10.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
2585
+ [ERROR_CODES10.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
2586
+ [ERROR_CODES10.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
2587
+ [ERROR_CODES10.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
2588
+ [ERROR_CODES10.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
2589
+ [ERROR_CODES10.TOOL_DENIED]: "The Agent tool request was denied.",
2590
+ [ERROR_CODES10.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
2591
+ [ERROR_CODES10.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
2592
+ [ERROR_CODES10.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
2593
+ [ERROR_CODES10.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
2594
+ [ERROR_CODES10.PATCH_REJECTED]: "The proposed patch was rejected.",
2595
+ [ERROR_CODES10.VALIDATION_FAILED]: "The proposed change failed validation.",
2596
+ [ERROR_CODES10.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
2597
+ [ERROR_CODES10.INTERNAL_ERROR]: "The request could not be completed."
2346
2598
  });
2347
2599
  function writeJson(response, status, payload) {
2348
2600
  response.statusCode = status;
@@ -2351,11 +2603,11 @@ function writeJson(response, status, payload) {
2351
2603
  response.end(JSON.stringify(payload));
2352
2604
  }
2353
2605
  function asSpotPatchError(error) {
2354
- return error instanceof SpotPatchError9 ? error : new SpotPatchError9(ERROR_CODES9.INTERNAL_ERROR, void 0, { cause: error });
2606
+ return error instanceof SpotPatchError10 ? error : new SpotPatchError10(ERROR_CODES10.INTERNAL_ERROR, void 0, { cause: error });
2355
2607
  }
2356
2608
  function writeError(response, error, logger) {
2357
2609
  const normalized = asSpotPatchError(error);
2358
- if (normalized.code === ERROR_CODES9.INTERNAL_ERROR) {
2610
+ if (normalized.code === ERROR_CODES10.INTERNAL_ERROR) {
2359
2611
  logger?.warn("[spotpatch:server] Internal request failure.");
2360
2612
  }
2361
2613
  writeJson(response, STATUS_BY_ERROR[normalized.code], {
@@ -2378,7 +2630,7 @@ async function handleSourceContext(request, options) {
2378
2630
  await readJsonRequestBody(request)
2379
2631
  );
2380
2632
  if (!parsed.success) {
2381
- throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2633
+ throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2382
2634
  }
2383
2635
  return readSourceContext({
2384
2636
  request: parsed.data,
@@ -2391,7 +2643,7 @@ async function handleSourceContext(request, options) {
2391
2643
  async function handleOpenEditor(request, options) {
2392
2644
  const parsed = openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
2393
2645
  if (!parsed.success) {
2394
- throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2646
+ throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2395
2647
  }
2396
2648
  const body = parsed.data;
2397
2649
  const sourcePath = await resolveSourceFile({
@@ -2408,17 +2660,18 @@ async function handleOpenEditor(request, options) {
2408
2660
  options.logger?.warn(
2409
2661
  `[spotpatch:server] ${options.options.editor === "auto" ? "The detected editor" : options.options.editor} rejected an editor request.`
2410
2662
  );
2411
- throw new SpotPatchError9(ERROR_CODES9.EDITOR_OPEN_FAILED, void 0, {
2663
+ throw new SpotPatchError10(ERROR_CODES10.EDITOR_OPEN_FAILED, void 0, {
2412
2664
  cause: error
2413
2665
  });
2414
2666
  }
2415
2667
  }
2416
2668
  function createSpotPatchMiddleware(options) {
2417
2669
  const bootstrap = options.bootstrap === void 0 ? void 0 : resolveRuntimeBootstrapOptions(options.bootstrap);
2670
+ const dataFlowAnalyzer = createDataFlowAnalyzer(options);
2418
2671
  return (request, response, next) => {
2419
2672
  const path8 = requestPath(request);
2420
2673
  const agentRoute = matchAgentRequestPath(path8);
2421
- if (path8 !== SPOTPATCH_ENDPOINTS2.sourceContext && path8 !== SPOTPATCH_ENDPOINTS2.openEditor && agentRoute === void 0 && !path8.startsWith(`${SPOTPATCH_API_BASE}/`)) {
2674
+ if (path8 !== SPOTPATCH_ENDPOINTS2.sourceContext && path8 !== SPOTPATCH_ENDPOINTS2.openEditor && path8 !== SPOTPATCH_ENDPOINTS2.dataFlowComponentReport && path8 !== SPOTPATCH_ENDPOINTS2.dataFlowPageReport && agentRoute === void 0 && !path8.startsWith(`${SPOTPATCH_API_BASE}/`)) {
2422
2675
  next();
2423
2676
  return;
2424
2677
  }
@@ -2437,7 +2690,7 @@ function createSpotPatchMiddleware(options) {
2437
2690
  });
2438
2691
  if (path8 === SPOTPATCH_ENDPOINTS2.sourceContext) {
2439
2692
  if (request.method !== "POST") {
2440
- throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2693
+ throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2441
2694
  }
2442
2695
  const data = await handleSourceContext(request, options);
2443
2696
  writeJson(response, 200, { ok: true, data });
@@ -2445,14 +2698,34 @@ function createSpotPatchMiddleware(options) {
2445
2698
  }
2446
2699
  if (path8 === SPOTPATCH_ENDPOINTS2.openEditor) {
2447
2700
  if (request.method !== "POST") {
2448
- throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2701
+ throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2449
2702
  }
2450
2703
  const data = await handleOpenEditor(request, options);
2451
2704
  writeJson(response, 200, { ok: true, data });
2452
2705
  return;
2453
2706
  }
2707
+ if (path8 === SPOTPATCH_ENDPOINTS2.dataFlowComponentReport) {
2708
+ if (request.method !== "POST") {
2709
+ throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2710
+ }
2711
+ const data = await handleComponentDataFlowReport(
2712
+ request,
2713
+ dataFlowAnalyzer,
2714
+ options
2715
+ );
2716
+ writeJson(response, 200, { ok: true, data });
2717
+ return;
2718
+ }
2719
+ if (path8 === SPOTPATCH_ENDPOINTS2.dataFlowPageReport) {
2720
+ if (request.method !== "POST") {
2721
+ throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2722
+ }
2723
+ const data = await handlePageDataFlowReport(request, dataFlowAnalyzer, options);
2724
+ writeJson(response, 200, { ok: true, data });
2725
+ return;
2726
+ }
2454
2727
  if (agentRoute === void 0) {
2455
- throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2728
+ throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2456
2729
  }
2457
2730
  await handleAgentRequest(
2458
2731
  request,
@@ -2603,6 +2876,7 @@ var OPTION_KEYS = Object.freeze([
2603
2876
  "allowLan",
2604
2877
  "budget",
2605
2878
  "debug",
2879
+ "dataFlow",
2606
2880
  "editor",
2607
2881
  "enabled",
2608
2882
  "exclude",
@@ -2690,6 +2964,9 @@ function serializeResolvedSpotPatchOptions(options) {
2690
2964
  allowLan: options.allowLan,
2691
2965
  budget: options.budget,
2692
2966
  debug: options.debug,
2967
+ dataFlow: options.dataFlow.enabled ? Object.freeze({
2968
+ runtime: options.dataFlow.runtime
2969
+ }) : false,
2693
2970
  editor: options.editor,
2694
2971
  enabled: options.enabled,
2695
2972
  exclude: Object.freeze(options.exclude.map(serializeFilter)),
@@ -2732,6 +3009,15 @@ function parseBudget(value) {
2732
3009
  );
2733
3010
  return Object.freeze(budget);
2734
3011
  }
3012
+ function parseDataFlow(value) {
3013
+ if (value === false) return false;
3014
+ if (!isRecord2(value) || !hasExactKeys(value, ["runtime"]) || value.runtime !== "dispatch") {
3015
+ throw new TypeError("The SpotPatch data-flow transport is invalid.");
3016
+ }
3017
+ return Object.freeze({
3018
+ runtime: value.runtime
3019
+ });
3020
+ }
2735
3021
  function parseSerializedSpotPatchOptions(value) {
2736
3022
  if (!isRecord2(value) || !hasExactKeys(value, OPTION_KEYS)) {
2737
3023
  throw new TypeError("The SpotPatch options transport is invalid.");
@@ -2745,6 +3031,7 @@ function parseSerializedSpotPatchOptions(value) {
2745
3031
  allowLan: value.allowLan,
2746
3032
  budget: parseBudget(value.budget),
2747
3033
  debug: value.debug,
3034
+ dataFlow: parseDataFlow(value.dataFlow),
2748
3035
  editor: value.editor,
2749
3036
  enabled: value.enabled,
2750
3037
  exclude: parseFilterList(value.exclude),
@@ -2767,6 +3054,7 @@ export {
2767
3054
  createAgentJobManager,
2768
3055
  createIntegrationFileChange,
2769
3056
  createRuntimeAiConfig,
3057
+ createRuntimeDataFlowConfig,
2770
3058
  createSession,
2771
3059
  createSourceRegistrationService,
2772
3060
  createSourceRegistry,