@spotpatch/dev-server 0.4.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
@@ -742,7 +742,8 @@ import {
742
742
  DEFAULT_AGENT_LIMITS,
743
743
  MAX_ANNOTATION_TARGETS,
744
744
  SPOTPATCH_EDITOR_PREFERENCES,
745
- SPOTPATCH_LOCALE_PREFERENCES
745
+ SPOTPATCH_LOCALE_PREFERENCES,
746
+ DEFAULT_DATA_FLOW_LIMITS
746
747
  } from "@spotpatch/shared";
747
748
  import { z } from "zod";
748
749
  var DEFAULT_EXCLUDE = Object.freeze([
@@ -753,7 +754,7 @@ var DEFAULT_EXCLUDE = Object.freeze([
753
754
  /(?:^|\/)dist(?:\/|$)/,
754
755
  /(?:^|\/)coverage(?:\/|$)/
755
756
  ]);
756
- var DEFAULT_INCLUDE = Object.freeze([/(?:^|[/\\])src[/\\].+\.(?:jsx|tsx)$/]);
757
+ var DEFAULT_INCLUDE = Object.freeze([/(?:^|[/\\])src[/\\].+\.(?:js|jsx|ts|tsx)$/]);
757
758
  var DEFAULT_BUDGET = Object.freeze({
758
759
  totalCharacters: 16e3,
759
760
  domCharacters: 3e3,
@@ -774,7 +775,12 @@ var DEFAULT_OPTIONS = Object.freeze({
774
775
  debug: false,
775
776
  locale: "auto",
776
777
  maxTargets: 8,
777
- ai: false
778
+ ai: false,
779
+ dataFlow: Object.freeze({
780
+ enabled: false,
781
+ runtime: "dispatch",
782
+ limits: DEFAULT_DATA_FLOW_LIMITS
783
+ })
778
784
  });
779
785
  var PROFILE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
780
786
  var ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]{1,127}$/;
@@ -1066,6 +1072,36 @@ function assertPositiveBudget(budget) {
1066
1072
  }
1067
1073
  }
1068
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
+ }
1069
1105
  function resolveOptions(options = {}, environmentAi) {
1070
1106
  if (options.trustedFastMode !== void 0 && typeof options.trustedFastMode !== "boolean") {
1071
1107
  throw new RangeError("SpotPatch trustedFastMode must be a boolean.");
@@ -1101,7 +1137,8 @@ function resolveOptions(options = {}, environmentAi) {
1101
1137
  debug: options.debug ?? DEFAULT_OPTIONS.debug,
1102
1138
  locale,
1103
1139
  maxTargets,
1104
- ai: resolveAiOptions(options.ai ?? environmentAi)
1140
+ ai: resolveAiOptions(options.ai ?? environmentAi),
1141
+ dataFlow: resolveDataFlowOptions(options.dataFlow)
1105
1142
  };
1106
1143
  if (resolved.shortcut.trim().length === 0 || resolved.shortcut.length > 128 || resolved.shortcut.includes("\0")) {
1107
1144
  throw new RangeError("SpotPatch shortcut is invalid.");
@@ -1287,37 +1324,62 @@ function createSourceRegistry(options = {}) {
1287
1324
  const createId = options.createId ?? createRandomSourceId;
1288
1325
  const pathToId = /* @__PURE__ */ new Map();
1289
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
+ }
1290
1341
  return Object.freeze({
1291
1342
  register(absolutePath) {
1343
+ return registerSourcePath(absolutePath);
1344
+ },
1345
+ registerDataFlowComponents(absolutePath, sourceVersion, components) {
1292
1346
  const normalizedPath = normalizeAbsolutePath(absolutePath);
1293
- const existingId = pathToId.get(normalizedPath);
1294
- if (existingId !== void 0) {
1295
- return existingId;
1347
+ const previousIds = componentIdsByPath.get(normalizedPath);
1348
+ for (const componentSourceId of previousIds ?? []) {
1349
+ componentAnchors.delete(componentSourceId);
1296
1350
  }
1297
- let fileId = createId();
1298
- while (idToPath.has(fileId)) {
1299
- 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
+ );
1300
1359
  }
1301
- pathToId.set(normalizedPath, fileId);
1302
- idToPath.set(fileId, normalizedPath);
1303
- return fileId;
1360
+ componentIdsByPath.set(normalizedPath, currentIds);
1304
1361
  },
1305
1362
  resolve(fileId) {
1306
1363
  return idToPath.get(fileId);
1307
1364
  },
1365
+ resolveDataFlowComponent(componentSourceId) {
1366
+ return componentAnchors.get(componentSourceId);
1367
+ },
1308
1368
  clear() {
1309
1369
  pathToId.clear();
1310
1370
  idToPath.clear();
1371
+ componentAnchors.clear();
1372
+ componentIdsByPath.clear();
1311
1373
  }
1312
1374
  });
1313
1375
  }
1314
1376
 
1315
1377
  // src/server/middleware.ts
1316
1378
  import {
1317
- ERROR_CODES as ERROR_CODES9,
1379
+ ERROR_CODES as ERROR_CODES10,
1318
1380
  SPOTPATCH_API_BASE,
1319
1381
  SPOTPATCH_ENDPOINTS as SPOTPATCH_ENDPOINTS2,
1320
- SpotPatchError as SpotPatchError9,
1382
+ SpotPatchError as SpotPatchError10,
1321
1383
  openEditorRequestSchema,
1322
1384
  sourceContextRequestSchema
1323
1385
  } from "@spotpatch/shared";
@@ -2150,10 +2212,186 @@ function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES2) {
2150
2212
  }
2151
2213
  var launchConfiguredEditor = createEditorLauncher();
2152
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
+
2153
2391
  // src/server/request-security.ts
2154
2392
  import { timingSafeEqual } from "crypto";
2155
2393
  import { isIP } from "net";
2156
- 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";
2157
2395
  function getSingleHeader(request, name) {
2158
2396
  const value = request.headers[name.toLowerCase()];
2159
2397
  return Array.isArray(value) ? value[0] : value;
@@ -2200,32 +2438,32 @@ function parseOrigin(value) {
2200
2438
  function assertRequestAuthorized(request, options) {
2201
2439
  const actualToken = getSingleHeader(request, SPOTPATCH_TOKEN_HEADER);
2202
2440
  if (!tokensMatch(actualToken, options.sessionToken)) {
2203
- throw new SpotPatchError7(ERROR_CODES7.INVALID_TOKEN);
2441
+ throw new SpotPatchError8(ERROR_CODES8.INVALID_TOKEN);
2204
2442
  }
2205
2443
  const hostHeader = getSingleHeader(request, "host");
2206
2444
  const originHeader = getSingleHeader(request, "origin");
2207
2445
  const host = hostHeader === void 0 ? void 0 : parseHost(hostHeader);
2208
2446
  const origin = originHeader === void 0 ? void 0 : parseOrigin(originHeader);
2209
2447
  if (host === void 0 || origin === void 0) {
2210
- throw new SpotPatchError7(ERROR_CODES7.ORIGIN_NOT_ALLOWED);
2448
+ throw new SpotPatchError8(ERROR_CODES8.ORIGIN_NOT_ALLOWED);
2211
2449
  }
2212
2450
  const hostIsLoopback = isLoopbackHostname(host.hostname);
2213
2451
  const originIsLoopback = isLoopbackHostname(origin.hostname);
2214
2452
  if (!options.allowLan) {
2215
2453
  if (!hostIsLoopback || !originIsLoopback) {
2216
- throw new SpotPatchError7(ERROR_CODES7.ORIGIN_NOT_ALLOWED);
2454
+ throw new SpotPatchError8(ERROR_CODES8.ORIGIN_NOT_ALLOWED);
2217
2455
  }
2218
2456
  return;
2219
2457
  }
2220
2458
  if (!originIsLoopback && origin.host.toLowerCase() !== host.host.toLowerCase()) {
2221
- throw new SpotPatchError7(ERROR_CODES7.ORIGIN_NOT_ALLOWED);
2459
+ throw new SpotPatchError8(ERROR_CODES8.ORIGIN_NOT_ALLOWED);
2222
2460
  }
2223
2461
  }
2224
2462
 
2225
2463
  // src/server/runtime-bootstrap.ts
2226
2464
  import {
2227
- ERROR_CODES as ERROR_CODES8,
2228
- SpotPatchError as SpotPatchError8,
2465
+ ERROR_CODES as ERROR_CODES9,
2466
+ SpotPatchError as SpotPatchError9,
2229
2467
  runtimeBootstrapRequestSchema,
2230
2468
  runtimeConfigSchema
2231
2469
  } from "@spotpatch/shared";
@@ -2255,7 +2493,7 @@ function resolveRuntimeBootstrapOptions(options) {
2255
2493
  function assertRuntimeBootstrapRequest(request, expectedOrigin) {
2256
2494
  const contentType = getSingleHeader2(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
2257
2495
  if (request.method !== "POST" || contentType !== "application/json") {
2258
- throw new SpotPatchError8(ERROR_CODES8.INVALID_REQUEST);
2496
+ throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2259
2497
  }
2260
2498
  const host = getSingleHeader2(request, "host");
2261
2499
  let hostIsLoopback = false;
@@ -2267,7 +2505,7 @@ function assertRuntimeBootstrapRequest(request, expectedOrigin) {
2267
2505
  }
2268
2506
  }
2269
2507
  if (!hostIsLoopback || getSingleHeader2(request, "origin") !== expectedOrigin || getSingleHeader2(request, "sec-fetch-site") !== "same-origin") {
2270
- throw new SpotPatchError8(ERROR_CODES8.ORIGIN_NOT_ALLOWED);
2508
+ throw new SpotPatchError9(ERROR_CODES9.ORIGIN_NOT_ALLOWED);
2271
2509
  }
2272
2510
  }
2273
2511
  async function readRuntimeBootstrap(request, options) {
@@ -2276,81 +2514,87 @@ async function readRuntimeBootstrap(request, options) {
2276
2514
  await readJsonRequestBody(request)
2277
2515
  );
2278
2516
  if (!parsedBody.success) {
2279
- throw new SpotPatchError8(ERROR_CODES8.INVALID_REQUEST);
2517
+ throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2280
2518
  }
2281
2519
  return options.runtimeConfig;
2282
2520
  }
2283
2521
 
2284
2522
  // src/server/middleware.ts
2285
2523
  var STATUS_BY_ERROR = Object.freeze({
2286
- [ERROR_CODES9.INVALID_REQUEST]: 400,
2287
- [ERROR_CODES9.INVALID_TOKEN]: 401,
2288
- [ERROR_CODES9.ORIGIN_NOT_ALLOWED]: 403,
2289
- [ERROR_CODES9.SOURCE_NOT_FOUND]: 404,
2290
- [ERROR_CODES9.SOURCE_OUTSIDE_ROOT]: 403,
2291
- [ERROR_CODES9.SOURCE_TOO_LARGE]: 413,
2292
- [ERROR_CODES9.EDITOR_OPEN_FAILED]: 500,
2293
- [ERROR_CODES9.AI_DISABLED]: 404,
2294
- [ERROR_CODES9.PROVIDER_NOT_CONFIGURED]: 503,
2295
- [ERROR_CODES9.PROVIDER_AUTH_FAILED]: 502,
2296
- [ERROR_CODES9.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
2297
- [ERROR_CODES9.MODEL_NOT_ALLOWED]: 400,
2298
- [ERROR_CODES9.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
2299
- [ERROR_CODES9.PROVIDER_RATE_LIMITED]: 429,
2300
- [ERROR_CODES9.AGENT_BUSY]: 409,
2301
- [ERROR_CODES9.AGENT_LIMIT_EXCEEDED]: 413,
2302
- [ERROR_CODES9.AGENT_CANCELLED]: 409,
2303
- [ERROR_CODES9.WORKTREE_DIRTY]: 409,
2304
- [ERROR_CODES9.WORKTREE_NOT_REPOSITORY]: 409,
2305
- [ERROR_CODES9.WORKTREE_OPERATION_IN_PROGRESS]: 409,
2306
- [ERROR_CODES9.WORKTREE_CONFLICTED]: 409,
2307
- [ERROR_CODES9.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
2308
- [ERROR_CODES9.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
2309
- [ERROR_CODES9.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
2310
- [ERROR_CODES9.TOOL_DENIED]: 403,
2311
- [ERROR_CODES9.TOOL_INPUT_INVALID]: 422,
2312
- [ERROR_CODES9.TOOL_ARGUMENTS_INVALID]: 422,
2313
- [ERROR_CODES9.TOOL_CALL_ID_CONFLICT]: 422,
2314
- [ERROR_CODES9.TOOL_PATH_DENIED]: 403,
2315
- [ERROR_CODES9.PATCH_REJECTED]: 422,
2316
- [ERROR_CODES9.VALIDATION_FAILED]: 422,
2317
- [ERROR_CODES9.APPLY_CONFLICT]: 409,
2318
- [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
2319
2560
  });
2320
2561
  var PUBLIC_MESSAGES = Object.freeze({
2321
- [ERROR_CODES9.INVALID_REQUEST]: "The request is invalid.",
2322
- [ERROR_CODES9.INVALID_TOKEN]: "The session token is invalid.",
2323
- [ERROR_CODES9.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
2324
- [ERROR_CODES9.SOURCE_NOT_FOUND]: "The source file is unavailable.",
2325
- [ERROR_CODES9.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
2326
- [ERROR_CODES9.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
2327
- [ERROR_CODES9.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
2328
- [ERROR_CODES9.AI_DISABLED]: "AI execution is not enabled.",
2329
- [ERROR_CODES9.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
2330
- [ERROR_CODES9.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
2331
- [ERROR_CODES9.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
2332
- [ERROR_CODES9.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
2333
- [ERROR_CODES9.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
2334
- [ERROR_CODES9.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
2335
- [ERROR_CODES9.AGENT_BUSY]: "Another Agent job is already running.",
2336
- [ERROR_CODES9.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
2337
- [ERROR_CODES9.AGENT_CANCELLED]: "The Agent job was cancelled.",
2338
- [ERROR_CODES9.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
2339
- [ERROR_CODES9.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
2340
- [ERROR_CODES9.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
2341
- [ERROR_CODES9.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
2342
- [ERROR_CODES9.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
2343
- [ERROR_CODES9.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
2344
- [ERROR_CODES9.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
2345
- [ERROR_CODES9.TOOL_DENIED]: "The Agent tool request was denied.",
2346
- [ERROR_CODES9.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
2347
- [ERROR_CODES9.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
2348
- [ERROR_CODES9.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
2349
- [ERROR_CODES9.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
2350
- [ERROR_CODES9.PATCH_REJECTED]: "The proposed patch was rejected.",
2351
- [ERROR_CODES9.VALIDATION_FAILED]: "The proposed change failed validation.",
2352
- [ERROR_CODES9.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
2353
- [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."
2354
2598
  });
2355
2599
  function writeJson(response, status, payload) {
2356
2600
  response.statusCode = status;
@@ -2359,11 +2603,11 @@ function writeJson(response, status, payload) {
2359
2603
  response.end(JSON.stringify(payload));
2360
2604
  }
2361
2605
  function asSpotPatchError(error) {
2362
- 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 });
2363
2607
  }
2364
2608
  function writeError(response, error, logger) {
2365
2609
  const normalized = asSpotPatchError(error);
2366
- if (normalized.code === ERROR_CODES9.INTERNAL_ERROR) {
2610
+ if (normalized.code === ERROR_CODES10.INTERNAL_ERROR) {
2367
2611
  logger?.warn("[spotpatch:server] Internal request failure.");
2368
2612
  }
2369
2613
  writeJson(response, STATUS_BY_ERROR[normalized.code], {
@@ -2386,7 +2630,7 @@ async function handleSourceContext(request, options) {
2386
2630
  await readJsonRequestBody(request)
2387
2631
  );
2388
2632
  if (!parsed.success) {
2389
- throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2633
+ throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2390
2634
  }
2391
2635
  return readSourceContext({
2392
2636
  request: parsed.data,
@@ -2399,7 +2643,7 @@ async function handleSourceContext(request, options) {
2399
2643
  async function handleOpenEditor(request, options) {
2400
2644
  const parsed = openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
2401
2645
  if (!parsed.success) {
2402
- throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2646
+ throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2403
2647
  }
2404
2648
  const body = parsed.data;
2405
2649
  const sourcePath = await resolveSourceFile({
@@ -2416,17 +2660,18 @@ async function handleOpenEditor(request, options) {
2416
2660
  options.logger?.warn(
2417
2661
  `[spotpatch:server] ${options.options.editor === "auto" ? "The detected editor" : options.options.editor} rejected an editor request.`
2418
2662
  );
2419
- throw new SpotPatchError9(ERROR_CODES9.EDITOR_OPEN_FAILED, void 0, {
2663
+ throw new SpotPatchError10(ERROR_CODES10.EDITOR_OPEN_FAILED, void 0, {
2420
2664
  cause: error
2421
2665
  });
2422
2666
  }
2423
2667
  }
2424
2668
  function createSpotPatchMiddleware(options) {
2425
2669
  const bootstrap = options.bootstrap === void 0 ? void 0 : resolveRuntimeBootstrapOptions(options.bootstrap);
2670
+ const dataFlowAnalyzer = createDataFlowAnalyzer(options);
2426
2671
  return (request, response, next) => {
2427
2672
  const path8 = requestPath(request);
2428
2673
  const agentRoute = matchAgentRequestPath(path8);
2429
- 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}/`)) {
2430
2675
  next();
2431
2676
  return;
2432
2677
  }
@@ -2445,7 +2690,7 @@ function createSpotPatchMiddleware(options) {
2445
2690
  });
2446
2691
  if (path8 === SPOTPATCH_ENDPOINTS2.sourceContext) {
2447
2692
  if (request.method !== "POST") {
2448
- throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2693
+ throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2449
2694
  }
2450
2695
  const data = await handleSourceContext(request, options);
2451
2696
  writeJson(response, 200, { ok: true, data });
@@ -2453,14 +2698,34 @@ function createSpotPatchMiddleware(options) {
2453
2698
  }
2454
2699
  if (path8 === SPOTPATCH_ENDPOINTS2.openEditor) {
2455
2700
  if (request.method !== "POST") {
2456
- throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2701
+ throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2457
2702
  }
2458
2703
  const data = await handleOpenEditor(request, options);
2459
2704
  writeJson(response, 200, { ok: true, data });
2460
2705
  return;
2461
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
+ }
2462
2727
  if (agentRoute === void 0) {
2463
- throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2728
+ throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
2464
2729
  }
2465
2730
  await handleAgentRequest(
2466
2731
  request,
@@ -2611,6 +2876,7 @@ var OPTION_KEYS = Object.freeze([
2611
2876
  "allowLan",
2612
2877
  "budget",
2613
2878
  "debug",
2879
+ "dataFlow",
2614
2880
  "editor",
2615
2881
  "enabled",
2616
2882
  "exclude",
@@ -2698,6 +2964,9 @@ function serializeResolvedSpotPatchOptions(options) {
2698
2964
  allowLan: options.allowLan,
2699
2965
  budget: options.budget,
2700
2966
  debug: options.debug,
2967
+ dataFlow: options.dataFlow.enabled ? Object.freeze({
2968
+ runtime: options.dataFlow.runtime
2969
+ }) : false,
2701
2970
  editor: options.editor,
2702
2971
  enabled: options.enabled,
2703
2972
  exclude: Object.freeze(options.exclude.map(serializeFilter)),
@@ -2740,6 +3009,15 @@ function parseBudget(value) {
2740
3009
  );
2741
3010
  return Object.freeze(budget);
2742
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
+ }
2743
3021
  function parseSerializedSpotPatchOptions(value) {
2744
3022
  if (!isRecord2(value) || !hasExactKeys(value, OPTION_KEYS)) {
2745
3023
  throw new TypeError("The SpotPatch options transport is invalid.");
@@ -2753,6 +3031,7 @@ function parseSerializedSpotPatchOptions(value) {
2753
3031
  allowLan: value.allowLan,
2754
3032
  budget: parseBudget(value.budget),
2755
3033
  debug: value.debug,
3034
+ dataFlow: parseDataFlow(value.dataFlow),
2756
3035
  editor: value.editor,
2757
3036
  enabled: value.enabled,
2758
3037
  exclude: parseFilterList(value.exclude),
@@ -2775,6 +3054,7 @@ export {
2775
3054
  createAgentJobManager,
2776
3055
  createIntegrationFileChange,
2777
3056
  createRuntimeAiConfig,
3057
+ createRuntimeDataFlowConfig,
2778
3058
  createSession,
2779
3059
  createSourceRegistrationService,
2780
3060
  createSourceRegistry,