@rasputin-ai/node 0.5.0-alpha.6 → 0.5.0-alpha.8

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 (29) hide show
  1. package/dist/auto-instrumentation/instrument-compiled-esm.d.ts +2 -0
  2. package/dist/auto-instrumentation/instrument-compiled-esm.d.ts.map +1 -1
  3. package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts +2 -1
  4. package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts.map +1 -1
  5. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts +1 -1
  6. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts.map +1 -1
  7. package/dist/auto-instrumentation/transform-source.d.ts +6 -0
  8. package/dist/auto-instrumentation/transform-source.d.ts.map +1 -1
  9. package/dist/derivation/analyze-function.d.ts +42 -0
  10. package/dist/derivation/analyze-function.d.ts.map +1 -0
  11. package/dist/derivation/build-dependency-graph.d.ts +21 -0
  12. package/dist/derivation/build-dependency-graph.d.ts.map +1 -0
  13. package/dist/derivation/derivation-ir.d.ts +33 -0
  14. package/dist/derivation/derivation-ir.d.ts.map +1 -0
  15. package/dist/derivation/derivation-proof.d.ts +14 -0
  16. package/dist/derivation/derivation-proof.d.ts.map +1 -0
  17. package/dist/derivation/emit-capture-plan.d.ts +8 -0
  18. package/dist/derivation/emit-capture-plan.d.ts.map +1 -0
  19. package/dist/execution-recorder/automatic-runtime.d.ts +3 -1
  20. package/dist/execution-recorder/automatic-runtime.d.ts.map +1 -1
  21. package/dist/execution-recorder/execution-recorder-types.d.ts +13 -2
  22. package/dist/execution-recorder/execution-recorder-types.d.ts.map +1 -1
  23. package/dist/execution-recorder/execution-recorder.d.ts.map +1 -1
  24. package/dist/index.js +169 -36
  25. package/dist/instrument/build.js +767 -71
  26. package/dist/instrument/bun.js +748 -66
  27. package/dist/instrument/node.js +748 -66
  28. package/dist/sdk-meta.d.ts +1 -1
  29. package/package.json +2 -2
@@ -46,7 +46,7 @@ var definitionFromEncodedPlan = (plan) => {
46
46
  };
47
47
 
48
48
  // src/auto-instrumentation/instrumentation-manifest-registry.ts
49
- var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v2";
49
+ var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v3";
50
50
  var registrySymbol = Symbol.for(INSTRUMENTATION_MANIFEST_SYMBOL);
51
51
  var getRegistry = () => {
52
52
  const existing = Reflect.get(globalThis, registrySymbol);
@@ -77,6 +77,7 @@ var snapshotInstrumentationManifest = (options = {}) => {
77
77
  const functions = [];
78
78
  const callSites = [];
79
79
  const destructures = [];
80
+ const derivationPlans = [];
80
81
  for (const delta of getRegistry().files.values()) {
81
82
  for (const fn of delta.functions) {
82
83
  const definition = definitionFromEncodedPlan(fn.plan);
@@ -128,6 +129,7 @@ var snapshotInstrumentationManifest = (options = {}) => {
128
129
  binding: destructure.binding
129
130
  });
130
131
  }
132
+ derivationPlans.push(...delta.derivationPlans ?? []);
131
133
  }
132
134
  const registry = getRegistry();
133
135
  if (registry.prebuilt && registry.files.size === 0) {
@@ -155,7 +157,8 @@ var snapshotInstrumentationManifest = (options = {}) => {
155
157
  } : {},
156
158
  functions,
157
159
  callSites,
158
- destructures
160
+ destructures,
161
+ derivationPlans
159
162
  };
160
163
  };
161
164
  var resetInstrumentationManifestRegistry = () => {
@@ -251,20 +254,655 @@ var createSourceMapLocationMapper = (generatedPath, source) => {
251
254
 
252
255
  // src/auto-instrumentation/transform-source.ts
253
256
  import { resolve as resolve4 } from "node:path";
254
- import ts2 from "typescript";
257
+ import ts3 from "typescript";
258
+
259
+ // src/derivation/analyze-function.ts
260
+ import ts from "typescript";
261
+
262
+ // src/derivation/build-dependency-graph.ts
263
+ var buildDependencyGraph = (functions, edges) => {
264
+ const names = new Set(functions.map((fn) => fn.name));
265
+ const localEdges = edges.filter((edge) => names.has(edge.calleeName));
266
+ const calleesByCaller = /* @__PURE__ */ new Map();
267
+ for (const edge of localEdges) {
268
+ const existing = calleesByCaller.get(edge.callerFunctionId) ?? [];
269
+ existing.push(edge.calleeName);
270
+ calleesByCaller.set(edge.callerFunctionId, existing);
271
+ }
272
+ return {
273
+ functions: functions.map((fn) => fn.functionId),
274
+ edges: localEdges,
275
+ calleesByCaller
276
+ };
277
+ };
278
+ var topologicalFunctionOrder = (functions, graph) => {
279
+ const idByName = new Map(functions.map((fn) => [fn.name, fn.functionId]));
280
+ const remainingCallees = new Map(
281
+ functions.map((fn) => {
282
+ const unique = new Set(
283
+ (graph.calleesByCaller.get(fn.functionId) ?? []).map((name) => idByName.get(name)).filter((id) => Boolean(id) && id !== fn.functionId)
284
+ );
285
+ return [fn.functionId, unique.size];
286
+ })
287
+ );
288
+ const callersOf = /* @__PURE__ */ new Map();
289
+ for (const fn of functions) {
290
+ for (const calleeName of graph.calleesByCaller.get(fn.functionId) ?? []) {
291
+ const calleeId = idByName.get(calleeName);
292
+ if (!calleeId || calleeId === fn.functionId) continue;
293
+ const callers = callersOf.get(calleeId) ?? [];
294
+ callers.push(fn.functionId);
295
+ callersOf.set(calleeId, callers);
296
+ }
297
+ }
298
+ const queue = functions.filter((fn) => (remainingCallees.get(fn.functionId) ?? 0) === 0).map((fn) => fn.functionId);
299
+ const ordered = [];
300
+ const queued = new Set(queue);
301
+ while (queue.length > 0) {
302
+ const current = queue.shift();
303
+ if (current === void 0) break;
304
+ ordered.push(current);
305
+ for (const caller of callersOf.get(current) ?? []) {
306
+ const next = (remainingCallees.get(caller) ?? 0) - 1;
307
+ remainingCallees.set(caller, next);
308
+ if (next === 0 && !queued.has(caller)) {
309
+ queued.add(caller);
310
+ queue.push(caller);
311
+ }
312
+ }
313
+ }
314
+ for (const fn of functions) {
315
+ if (!queued.has(fn.functionId)) ordered.push(fn.functionId);
316
+ }
317
+ return ordered;
318
+ };
319
+
320
+ // src/derivation/derivation-ir.ts
321
+ var DerivationIrBuilder = class {
322
+ expressions = [];
323
+ nextId = 0;
324
+ constValue(value) {
325
+ return this.add({ id: this.nextId++, kind: "const", value });
326
+ }
327
+ param(index) {
328
+ return this.add({ id: this.nextId++, kind: "param", index });
329
+ }
330
+ root(rootId) {
331
+ return this.add({ id: this.nextId++, kind: "root", rootId });
332
+ }
333
+ alias(from) {
334
+ return this.add({ id: this.nextId++, kind: "alias", from });
335
+ }
336
+ binary(op, left, right, guard) {
337
+ return this.add({ id: this.nextId++, kind: "binary", op, left, right, guard });
338
+ }
339
+ unary(op, operand) {
340
+ return this.add({ id: this.nextId++, kind: "unary", op, operand });
341
+ }
342
+ logical(op, left, right) {
343
+ return this.add({ id: this.nextId++, kind: "logical", op, left, right });
344
+ }
345
+ cond(test, consequent, alternate) {
346
+ return this.add({ id: this.nextId++, kind: "cond", test, consequent, alternate });
347
+ }
348
+ object(properties) {
349
+ return this.add({ id: this.nextId++, kind: "object", properties });
350
+ }
351
+ array(elements) {
352
+ return this.add({ id: this.nextId++, kind: "array", elements });
353
+ }
354
+ prop(object, key) {
355
+ return this.add({ id: this.nextId++, kind: "prop", object, key });
356
+ }
357
+ index(array, index) {
358
+ return this.add({ id: this.nextId++, kind: "index", array, index });
359
+ }
360
+ pureCall(calleeFunctionId, args2) {
361
+ return this.add({ id: this.nextId++, kind: "pure_call", calleeFunctionId, args: args2 });
362
+ }
363
+ template(parts) {
364
+ return this.add({ id: this.nextId++, kind: "template", parts });
365
+ }
366
+ add(expression) {
367
+ this.expressions.push(expression);
368
+ return expression.id;
369
+ }
370
+ };
371
+ var emptyPlan = (functionId) => ({
372
+ planId: `dp_${functionId}`,
373
+ functionId,
374
+ expressions: [],
375
+ variables: [],
376
+ captureBoundaries: [],
377
+ controlFacts: []
378
+ });
379
+
380
+ // src/derivation/derivation-proof.ts
381
+ var functionIsExact = (plan) => plan.fallbackReason === void 0 && plan.captureBoundaries.length === 0 && plan.controlFacts.length === 0;
382
+
383
+ // src/derivation/analyze-function.ts
384
+ var loc = (node, sourceFile) => {
385
+ const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
386
+ return { line: position.line + 1, column: position.character + 1 };
387
+ };
388
+ var lookup = (ctx, name) => {
389
+ for (let index = ctx.scopes.length - 1; index >= 0; index--) {
390
+ const binding = ctx.scopes[index]?.get(name);
391
+ if (binding) return binding;
392
+ }
393
+ return void 0;
394
+ };
395
+ var fallback = (ctx, reason) => {
396
+ ctx.fallback ??= reason;
397
+ return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "unknown" };
398
+ };
399
+ var capture = (ctx, node, reason) => {
400
+ const rootId = ctx.nextRootId.value++;
401
+ const position = loc(node, ctx.sourceFile);
402
+ const expressionId = ctx.ir.root(rootId);
403
+ ctx.captureWraps.push({
404
+ rootId,
405
+ reason,
406
+ start: node.getStart(ctx.sourceFile),
407
+ end: node.end,
408
+ line: position.line,
409
+ column: position.column,
410
+ expressionId
411
+ });
412
+ return { expressionId, shape: "unknown", captured: true };
413
+ };
414
+ var binaryOp = (kind) => {
415
+ switch (kind) {
416
+ case ts.SyntaxKind.PlusToken:
417
+ return "add";
418
+ case ts.SyntaxKind.MinusToken:
419
+ return "sub";
420
+ case ts.SyntaxKind.AsteriskToken:
421
+ return "mul";
422
+ case ts.SyntaxKind.SlashToken:
423
+ return "div";
424
+ case ts.SyntaxKind.PercentToken:
425
+ return "mod";
426
+ case ts.SyntaxKind.EqualsEqualsToken:
427
+ return "eq";
428
+ case ts.SyntaxKind.ExclamationEqualsToken:
429
+ return "neq";
430
+ case ts.SyntaxKind.EqualsEqualsEqualsToken:
431
+ return "seq";
432
+ case ts.SyntaxKind.ExclamationEqualsEqualsToken:
433
+ return "sneq";
434
+ case ts.SyntaxKind.LessThanToken:
435
+ return "lt";
436
+ case ts.SyntaxKind.LessThanEqualsToken:
437
+ return "lte";
438
+ case ts.SyntaxKind.GreaterThanToken:
439
+ return "gt";
440
+ case ts.SyntaxKind.GreaterThanEqualsToken:
441
+ return "gte";
442
+ default:
443
+ return void 0;
444
+ }
445
+ };
446
+ var isNumericBinaryOp = (op) => op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod";
447
+ var unwrapParens = (node) => {
448
+ while (ts.isParenthesizedExpression(node)) node = node.expression;
449
+ return node;
450
+ };
451
+ var numericLiteralValue = (node) => {
452
+ const value = Number(node.text);
453
+ return { type: "number", value };
454
+ };
455
+ var analyzeExpression = (ctx, node) => {
456
+ if (ctx.fallback) return fallback(ctx, ctx.fallback);
457
+ if (ts.isParenthesizedExpression(node)) return analyzeExpression(ctx, node.expression);
458
+ if (ts.isAsExpression(node) || ts.isNonNullExpression(node)) {
459
+ return analyzeExpression(ctx, node.expression);
460
+ }
461
+ if (node.kind === ts.SyntaxKind.TypeAssertionExpression) {
462
+ return analyzeExpression(ctx, node.expression);
463
+ }
464
+ if (ts.isSatisfiesExpression(node)) return analyzeExpression(ctx, node.expression);
465
+ if (node.kind === ts.SyntaxKind.NullKeyword) {
466
+ return { expressionId: ctx.ir.constValue({ type: "null" }), shape: "primitive" };
467
+ }
468
+ if (node.kind === ts.SyntaxKind.TrueKeyword) {
469
+ return {
470
+ expressionId: ctx.ir.constValue({ type: "boolean", value: true }),
471
+ shape: "primitive"
472
+ };
473
+ }
474
+ if (node.kind === ts.SyntaxKind.FalseKeyword) {
475
+ return {
476
+ expressionId: ctx.ir.constValue({ type: "boolean", value: false }),
477
+ shape: "primitive"
478
+ };
479
+ }
480
+ if (ts.isNumericLiteral(node)) {
481
+ return { expressionId: ctx.ir.constValue(numericLiteralValue(node)), shape: "primitive" };
482
+ }
483
+ if (ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand) && node.operator === ts.SyntaxKind.MinusToken) {
484
+ const value = -Number(node.operand.text);
485
+ return {
486
+ expressionId: ctx.ir.constValue(
487
+ Object.is(value, -0) ? { type: "number", value: 0, negativeZero: true } : { type: "number", value }
488
+ ),
489
+ shape: "primitive"
490
+ };
491
+ }
492
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
493
+ return {
494
+ expressionId: ctx.ir.constValue({ type: "string", value: node.text }),
495
+ shape: "primitive"
496
+ };
497
+ }
498
+ if (ts.isBigIntLiteral(node)) {
499
+ return {
500
+ expressionId: ctx.ir.constValue({ type: "bigint", value: node.text.replace(/n$/, "") }),
501
+ shape: "primitive"
502
+ };
503
+ }
504
+ if (ts.isIdentifier(node)) {
505
+ if (node.text === "undefined") {
506
+ return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "primitive" };
507
+ }
508
+ const binding = lookup(ctx, node.text);
509
+ if (!binding) return capture(ctx, node, "external_state");
510
+ if (binding.mutable) return capture(ctx, node, "mutable_object");
511
+ return {
512
+ expressionId: ctx.ir.alias(binding.expressionId),
513
+ shape: binding.shape
514
+ };
515
+ }
516
+ if (ts.isTemplateExpression(node)) {
517
+ const parts = [
518
+ { kind: "lit", value: node.head.text }
519
+ ];
520
+ for (const span of node.templateSpans) {
521
+ const nested = analyzeExpression(ctx, span.expression);
522
+ if (nested.shape !== "primitive" && !nested.captured) {
523
+ return capture(ctx, node, "unsupported_expression");
524
+ }
525
+ parts.push({ kind: "expr", expressionId: nested.expressionId });
526
+ parts.push({ kind: "lit", value: span.literal.text });
527
+ }
528
+ return { expressionId: ctx.ir.template(parts), shape: "primitive" };
529
+ }
530
+ if (ts.isArrayLiteralExpression(node)) {
531
+ if (node.elements.some((element) => ts.isSpreadElement(element))) {
532
+ return capture(ctx, node, "unsupported_expression");
533
+ }
534
+ const elements = node.elements.map((element) => {
535
+ if (ts.isOmittedExpression(element)) return { kind: "hole" };
536
+ return analyzeExpression(ctx, element).expressionId;
537
+ });
538
+ return { expressionId: ctx.ir.array(elements), shape: "array" };
539
+ }
540
+ if (ts.isObjectLiteralExpression(node)) {
541
+ const properties = [];
542
+ for (const property of node.properties) {
543
+ if (ts.isShorthandPropertyAssignment(property)) {
544
+ const nested = analyzeExpression(ctx, property.name);
545
+ if (nested.captured) {
546
+ const wrap = ctx.captureWraps.find(
547
+ (candidate) => candidate.expressionId === nested.expressionId
548
+ );
549
+ if (wrap) wrap.shorthandPropertyName = property.name.text;
550
+ }
551
+ properties.push({ key: property.name.text, value: nested.expressionId });
552
+ continue;
553
+ }
554
+ if (!ts.isPropertyAssignment(property)) {
555
+ return capture(ctx, node, "unsupported_expression");
556
+ }
557
+ if (ts.isPrivateIdentifier(property.name) || ts.isComputedPropertyName(property.name)) {
558
+ return capture(ctx, node, "unsupported_expression");
559
+ }
560
+ const key = ts.isIdentifier(property.name) ? property.name.text : ts.isStringLiteral(property.name) || ts.isNumericLiteral(property.name) ? property.name.text : void 0;
561
+ if (key === void 0) return capture(ctx, node, "unsupported_expression");
562
+ properties.push({ key, value: analyzeExpression(ctx, property.initializer).expressionId });
563
+ }
564
+ return { expressionId: ctx.ir.object(properties), shape: "object" };
565
+ }
566
+ if (ts.isPropertyAccessExpression(node)) {
567
+ if (node.questionDotToken) return capture(ctx, node, "unknown_property");
568
+ const object = analyzeExpression(ctx, node.expression);
569
+ if (object.shape === "object" && ts.isIdentifier(node.name)) {
570
+ return { expressionId: ctx.ir.prop(object.expressionId, node.name.text), shape: "unknown" };
571
+ }
572
+ return capture(ctx, node, "unknown_property");
573
+ }
574
+ if (ts.isElementAccessExpression(node)) {
575
+ if (node.questionDotToken) return capture(ctx, node, "unknown_property");
576
+ const array = analyzeExpression(ctx, node.expression);
577
+ if (array.shape === "array" && ts.isNumericLiteral(node.argumentExpression)) {
578
+ return {
579
+ expressionId: ctx.ir.index(array.expressionId, Number(node.argumentExpression.text)),
580
+ shape: "unknown"
581
+ };
582
+ }
583
+ return capture(ctx, node, "unknown_property");
584
+ }
585
+ if (ts.isCallExpression(node)) {
586
+ if (node.questionDotToken || node.typeArguments?.length) {
587
+ return capture(ctx, node, "unsupported_expression");
588
+ }
589
+ if (!ts.isIdentifier(node.expression)) return capture(ctx, node, "dynamic_callee");
590
+ const calleeName = node.expression.text;
591
+ if (lookup(ctx, calleeName)) return capture(ctx, node, "opaque_call");
592
+ const provenId = ctx.provenExact.get(calleeName);
593
+ ctx.callEdges.push({
594
+ callerFunctionId: ctx.functionId,
595
+ calleeName,
596
+ ...provenId ? { calleeFunctionId: provenId } : {}
597
+ });
598
+ if (!provenId) return capture(ctx, node, "opaque_call");
599
+ const args2 = node.arguments.map((argument) => {
600
+ if (ts.isSpreadElement(argument)) return void 0;
601
+ return analyzeExpression(ctx, argument).expressionId;
602
+ });
603
+ if (args2.some((argument) => argument === void 0))
604
+ return capture(ctx, node, "unsupported_expression");
605
+ return {
606
+ expressionId: ctx.ir.pureCall(
607
+ provenId,
608
+ args2.filter((id) => id !== void 0)
609
+ ),
610
+ shape: "unknown"
611
+ };
612
+ }
613
+ if (ts.isNewExpression(node) || ts.isAwaitExpression(node) || ts.isYieldExpression(node)) {
614
+ return capture(
615
+ ctx,
616
+ node,
617
+ node.kind === ts.SyntaxKind.AwaitExpression ? "nondeterministic" : "unsupported_expression"
618
+ );
619
+ }
620
+ if (ts.isConditionalExpression(node)) {
621
+ const test = analyzeExpression(ctx, node.condition);
622
+ const consequent = analyzeExpression(ctx, node.whenTrue);
623
+ const alternate = analyzeExpression(ctx, node.whenFalse);
624
+ return {
625
+ expressionId: ctx.ir.cond(test.expressionId, consequent.expressionId, alternate.expressionId),
626
+ shape: consequent.shape === alternate.shape ? consequent.shape : "unknown"
627
+ };
628
+ }
629
+ if (ts.isBinaryExpression(node)) {
630
+ if (node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
631
+ ctx.fallback ??= "mutable_state";
632
+ return capture(ctx, node, "mutable_object");
633
+ }
634
+ if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === ts.SyntaxKind.BarBarToken || node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
635
+ const left2 = analyzeExpression(ctx, node.left);
636
+ const right2 = analyzeExpression(ctx, node.right);
637
+ const op2 = node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ? "and" : node.operatorToken.kind === ts.SyntaxKind.BarBarToken ? "or" : "nullish";
638
+ return {
639
+ expressionId: ctx.ir.logical(op2, left2.expressionId, right2.expressionId),
640
+ shape: "unknown"
641
+ };
642
+ }
643
+ const op = binaryOp(node.operatorToken.kind);
644
+ if (!op) return capture(ctx, node, "unsupported_expression");
645
+ if (!ts.isExpression(node.left)) return capture(ctx, node, "unsupported_expression");
646
+ const left = analyzeExpression(ctx, node.left);
647
+ const right = analyzeExpression(ctx, node.right);
648
+ const numeric = isNumericBinaryOp(op);
649
+ const expressionId = ctx.ir.binary(
650
+ op,
651
+ left.expressionId,
652
+ right.expressionId,
653
+ numeric ? "numeric" : "primitive"
654
+ );
655
+ if (numeric && (left.captured || right.captured || left.shape !== "primitive" || right.shape !== "primitive")) {
656
+ ctx.hasCoercionGuard = true;
657
+ const fallbackRootId = ctx.nextRootId.value++;
658
+ ctx.guardWraps.push({
659
+ fallbackRootId,
660
+ start: node.getStart(ctx.sourceFile),
661
+ end: node.end,
662
+ op
663
+ });
664
+ }
665
+ return { expressionId, shape: "primitive" };
666
+ }
667
+ if (ts.isPrefixUnaryExpression(node)) {
668
+ const operand = analyzeExpression(ctx, node.operand);
669
+ if (node.operator === ts.SyntaxKind.ExclamationToken) {
670
+ return { expressionId: ctx.ir.unary("not", operand.expressionId), shape: "primitive" };
671
+ }
672
+ if (node.operator === ts.SyntaxKind.PlusToken || node.operator === ts.SyntaxKind.MinusToken) {
673
+ if (operand.captured || operand.shape !== "primitive") ctx.hasCoercionGuard = true;
674
+ return {
675
+ expressionId: ctx.ir.unary(
676
+ node.operator === ts.SyntaxKind.MinusToken ? "minus" : "plus",
677
+ operand.expressionId
678
+ ),
679
+ shape: "primitive"
680
+ };
681
+ }
682
+ return capture(ctx, node, "unsupported_expression");
683
+ }
684
+ if (ts.isTypeOfExpression(node)) {
685
+ const operand = unwrapParens(node.expression);
686
+ if (ts.isIdentifier(operand) && operand.text !== "undefined" && !lookup(ctx, operand.text)) {
687
+ return capture(ctx, node, "external_state");
688
+ }
689
+ const inner = analyzeExpression(ctx, node.expression);
690
+ return { expressionId: ctx.ir.unary("typeof", inner.expressionId), shape: "primitive" };
691
+ }
692
+ return capture(ctx, node, "unsupported_expression");
693
+ };
694
+ var bindIdentifier = (ctx, name, result2, declaration) => {
695
+ const current = ctx.scopes[ctx.scopes.length - 1];
696
+ current?.set(name.text, {
697
+ expressionId: result2.expressionId,
698
+ shape: result2.shape,
699
+ mutable: false
700
+ });
701
+ const position = loc(name, ctx.sourceFile);
702
+ const available = loc(declaration, ctx.sourceFile);
703
+ ctx.variables.push({
704
+ id: ctx.nextVariableId.value++,
705
+ name: name.text,
706
+ functionId: ctx.functionId,
707
+ scopeId: `${ctx.functionId}:${ctx.scopes.length}`,
708
+ line: position.line,
709
+ column: position.column,
710
+ assignmentId: ctx.nextAssignmentId.value++,
711
+ expressionId: result2.expressionId,
712
+ availableAtLine: available.line,
713
+ availableAtColumn: available.column
714
+ });
715
+ };
716
+ var analyzeStatement = (ctx, node) => {
717
+ if (ctx.fallback) return;
718
+ if (ts.isEmptyStatement(node) || ts.isDebuggerStatement(node)) return;
719
+ if (ts.isBlock(node)) {
720
+ ctx.scopes.push(/* @__PURE__ */ new Map());
721
+ for (const statement of node.statements) analyzeStatement(ctx, statement);
722
+ ctx.scopes.pop();
723
+ return;
724
+ }
725
+ if (ts.isReturnStatement(node)) {
726
+ if (!node.expression) {
727
+ ctx.returnExpressionId = ctx.ir.constValue({ type: "undefined" });
728
+ return;
729
+ }
730
+ ctx.returnExpressionId = analyzeExpression(ctx, node.expression).expressionId;
731
+ return;
732
+ }
733
+ if (ts.isVariableStatement(node)) {
734
+ if (node.declarationList.flags & ts.NodeFlags.Using) {
735
+ fallback(ctx, "side_effect");
736
+ return;
737
+ }
738
+ for (const declaration of node.declarationList.declarations) {
739
+ if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
740
+ fallback(ctx, "unsupported_expression");
741
+ return;
742
+ }
743
+ const result2 = analyzeExpression(ctx, declaration.initializer);
744
+ bindIdentifier(ctx, declaration.name, result2, declaration);
745
+ }
746
+ return;
747
+ }
748
+ if (ts.isExpressionStatement(node)) {
749
+ analyzeExpression(ctx, node.expression);
750
+ return;
751
+ }
752
+ if (ts.isThrowStatement(node)) {
753
+ ctx.hasThrow = true;
754
+ analyzeExpression(ctx, node.expression);
755
+ return;
756
+ }
757
+ if (ts.isIfStatement(node) || ts.isSwitchStatement(node) || ts.isTryStatement(node)) {
758
+ fallback(ctx, "unsupported_expression");
759
+ return;
760
+ }
761
+ if (ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node)) {
762
+ fallback(ctx, "has_loop");
763
+ return;
764
+ }
765
+ fallback(ctx, "unsupported_expression");
766
+ };
767
+ var bindParams = (ctx, node) => {
768
+ let index = 0;
769
+ for (const parameter of node.parameters) {
770
+ if (ts.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
771
+ if (!ts.isIdentifier(parameter.name) || parameter.dotDotDotToken) {
772
+ fallback(ctx, "unsupported_expression");
773
+ index++;
774
+ continue;
775
+ }
776
+ const expressionId = ctx.ir.param(index);
777
+ ctx.identifierParams.push(parameter.name.text);
778
+ bindIdentifier(ctx, parameter.name, { expressionId, shape: "unknown" }, parameter);
779
+ index++;
780
+ }
781
+ };
782
+ var analyzeOne = (node, functionId, name, sourceFile, provenExact) => {
783
+ const ctx = {
784
+ sourceFile,
785
+ functionId,
786
+ name,
787
+ ir: new DerivationIrBuilder(),
788
+ variables: [],
789
+ captureWraps: [],
790
+ guardWraps: [],
791
+ callEdges: [],
792
+ scopes: [/* @__PURE__ */ new Map()],
793
+ nextRootId: { value: 0 },
794
+ nextVariableId: { value: 0 },
795
+ nextAssignmentId: { value: 0 },
796
+ hasThrow: false,
797
+ hasCoercionGuard: false,
798
+ provenExact,
799
+ identifierParams: []
800
+ };
801
+ if (node.asteriskToken || node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)) {
802
+ ctx.fallback = "async";
803
+ } else {
804
+ bindParams(ctx, node);
805
+ if (ts.isBlock(node.body)) {
806
+ for (const statement of node.body.statements) analyzeStatement(ctx, statement);
807
+ } else {
808
+ ctx.returnExpressionId = analyzeExpression(ctx, node.body).expressionId;
809
+ }
810
+ }
811
+ const plan = {
812
+ ...emptyPlan(functionId),
813
+ expressions: ctx.ir.expressions,
814
+ variables: ctx.variables,
815
+ captureBoundaries: ctx.captureWraps.map((wrap) => ({
816
+ id: wrap.rootId,
817
+ reason: wrap.reason,
818
+ expressionId: wrap.expressionId,
819
+ line: wrap.line,
820
+ column: wrap.column
821
+ })),
822
+ controlFacts: [],
823
+ ...ctx.returnExpressionId === void 0 ? {} : { returnExpressionId: ctx.returnExpressionId },
824
+ ...ctx.fallback ? { fallbackReason: ctx.fallback } : {}
825
+ };
826
+ return {
827
+ functionId,
828
+ name,
829
+ node,
830
+ plan,
831
+ captureWraps: ctx.fallback ? [] : ctx.captureWraps,
832
+ guardWraps: ctx.fallback ? [] : ctx.guardWraps,
833
+ callEdges: ctx.callEdges,
834
+ hasThrow: ctx.hasThrow,
835
+ hasCoercionGuard: ctx.hasCoercionGuard
836
+ };
837
+ };
838
+ var analyzeModuleFunctions = (sourceFile, targets) => {
839
+ const nameCounts = /* @__PURE__ */ new Map();
840
+ for (const target of targets) {
841
+ nameCounts.set(target.name, (nameCounts.get(target.name) ?? 0) + 1);
842
+ }
843
+ const firstPass = targets.map(
844
+ (target) => analyzeOne(target.node, target.functionId, target.name, sourceFile, /* @__PURE__ */ new Map())
845
+ );
846
+ const graph = buildDependencyGraph(
847
+ firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
848
+ firstPass.flatMap((fn) => fn.callEdges)
849
+ );
850
+ const order = topologicalFunctionOrder(
851
+ firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
852
+ graph
853
+ );
854
+ const byId = new Map(targets.map((target) => [target.functionId, target]));
855
+ const provenExact = /* @__PURE__ */ new Map();
856
+ const analyzed = [];
857
+ for (const functionId of order) {
858
+ const target = byId.get(functionId);
859
+ if (!target) continue;
860
+ const result2 = analyzeOne(target.node, target.functionId, target.name, sourceFile, provenExact);
861
+ if (functionIsExact(result2.plan) && nameCounts.get(result2.name) === 1) {
862
+ provenExact.set(result2.name, result2.functionId);
863
+ }
864
+ analyzed.push(result2);
865
+ }
866
+ const remaining = targets.filter(
867
+ (target) => !analyzed.some((fn) => fn.functionId === target.functionId)
868
+ );
869
+ for (const target of remaining) {
870
+ analyzed.push(analyzeOne(target.node, target.functionId, target.name, sourceFile, provenExact));
871
+ }
872
+ return analyzed;
873
+ };
874
+
875
+ // src/derivation/emit-capture-plan.ts
876
+ var captureWrapEdits = (wraps, capName) => {
877
+ const edits = [];
878
+ let order = 0;
879
+ for (const wrap of wraps) {
880
+ edits.push({
881
+ position: wrap.start,
882
+ text: `${wrap.shorthandPropertyName ? `${wrap.shorthandPropertyName}:` : ""}${capName}(${wrap.rootId},`,
883
+ order: order++
884
+ });
885
+ edits.push({
886
+ position: wrap.end,
887
+ text: ")",
888
+ order: order++
889
+ });
890
+ }
891
+ return edits;
892
+ };
255
893
 
256
894
  // src/execution-recorder/automatic-runtime.ts
257
895
  var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
258
896
  var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
259
897
 
260
898
  // src/auto-instrumentation/collect-instrumentation-delta.ts
261
- import ts from "typescript";
899
+ import ts2 from "typescript";
262
900
  var paramBindingFromName = (name, sourceFile) => {
263
- if (ts.isIdentifier(name)) return { kind: "identifier", name: name.text };
264
- if (ts.isArrayBindingPattern(name)) {
901
+ if (ts2.isIdentifier(name)) return { kind: "identifier", name: name.text };
902
+ if (ts2.isArrayBindingPattern(name)) {
265
903
  const elements = name.elements.map(
266
904
  (element) => {
267
- if (ts.isOmittedExpression(element)) return { kind: "omitted" };
905
+ if (ts2.isOmittedExpression(element)) return { kind: "omitted" };
268
906
  const binding = paramBindingFromName(element.name, sourceFile);
269
907
  if (element.dotDotDotToken) return { kind: "rest", binding };
270
908
  return binding;
@@ -274,7 +912,7 @@ var paramBindingFromName = (name, sourceFile) => {
274
912
  }
275
913
  const properties = name.elements.map((element) => {
276
914
  const binding = paramBindingFromName(element.name, sourceFile);
277
- const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") : ts.isIdentifier(element.name) ? element.name.text : element.name.getText(sourceFile);
915
+ const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") : ts2.isIdentifier(element.name) ? element.name.text : element.name.getText(sourceFile);
278
916
  if (element.dotDotDotToken) {
279
917
  const rest = { kind: "rest", binding };
280
918
  return { key, binding: rest };
@@ -286,7 +924,7 @@ var paramBindingFromName = (name, sourceFile) => {
286
924
  var collectFunctionParams = (node, sourceFile) => {
287
925
  const params = [];
288
926
  for (const parameter of node.parameters) {
289
- if (ts.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
927
+ if (ts2.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
290
928
  const binding = paramBindingFromName(parameter.name, sourceFile);
291
929
  params.push(parameter.dotDotDotToken ? { kind: "rest", binding } : binding);
292
930
  }
@@ -327,18 +965,18 @@ var mappedLocation = (sourceFile, position, absolutePath, mapLocation) => {
327
965
  };
328
966
  var unwrapExpression = (expression) => {
329
967
  let current = expression;
330
- while (ts.isParenthesizedExpression(current) || ts.isNonNullExpression(current)) {
968
+ while (ts2.isParenthesizedExpression(current) || ts2.isNonNullExpression(current)) {
331
969
  current = current.expression;
332
970
  }
333
- if (ts.isAwaitExpression(current)) return unwrapExpression(current.expression);
971
+ if (ts2.isAwaitExpression(current)) return unwrapExpression(current.expression);
334
972
  return current;
335
973
  };
336
974
  var calleeFromExpression = (expression, sourceFile) => {
337
975
  const current = unwrapExpression(expression);
338
- if (ts.isIdentifier(current)) {
976
+ if (ts2.isIdentifier(current)) {
339
977
  return { calleeName: current.text, calleeText: current.text };
340
978
  }
341
- if (ts.isPropertyAccessExpression(current) && ts.isIdentifier(current.name)) {
979
+ if (ts2.isPropertyAccessExpression(current) && ts2.isIdentifier(current.name)) {
342
980
  return {
343
981
  calleeName: current.name.text,
344
982
  calleeText: current.getText(sourceFile).replace(/\s+/g, "")
@@ -348,9 +986,9 @@ var calleeFromExpression = (expression, sourceFile) => {
348
986
  };
349
987
  var resultBindingForCall = (call) => {
350
988
  let node = call.parent;
351
- if (ts.isAwaitExpression(node)) node = node.parent;
352
- if (!ts.isVariableDeclaration(node)) return null;
353
- if (!ts.isIdentifier(node.name)) return null;
989
+ if (ts2.isAwaitExpression(node)) node = node.parent;
990
+ if (!ts2.isVariableDeclaration(node)) return null;
991
+ if (!ts2.isIdentifier(node.name)) return null;
354
992
  const initializer = node.initializer;
355
993
  if (!initializer) return null;
356
994
  const inner = unwrapExpression(initializer);
@@ -360,18 +998,18 @@ var resultBindingForCall = (call) => {
360
998
  var argIdentifiersForCall = (call) => {
361
999
  const identifiers = [];
362
1000
  call.arguments?.forEach((argument, index) => {
363
- if (ts.isIdentifier(argument)) {
1001
+ if (ts2.isIdentifier(argument)) {
364
1002
  identifiers.push({ index, kind: "identifier", name: argument.text });
365
1003
  return;
366
1004
  }
367
- if (!ts.isObjectLiteralExpression(argument)) return;
1005
+ if (!ts2.isObjectLiteralExpression(argument)) return;
368
1006
  const properties = [];
369
1007
  for (const property of argument.properties) {
370
- if (ts.isShorthandPropertyAssignment(property)) {
1008
+ if (ts2.isShorthandPropertyAssignment(property)) {
371
1009
  properties.push({ key: property.name.text, name: property.name.text });
372
1010
  continue;
373
1011
  }
374
- if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name) && ts.isIdentifier(property.initializer)) {
1012
+ if (ts2.isPropertyAssignment(property) && ts2.isIdentifier(property.name) && ts2.isIdentifier(property.initializer)) {
375
1013
  properties.push({ key: property.name.text, name: property.initializer.text });
376
1014
  }
377
1015
  }
@@ -411,8 +1049,8 @@ var collectCallSite = (call, caller, context) => {
411
1049
  };
412
1050
  };
413
1051
  var collectParamDestructure = (declaration, caller, context) => {
414
- if (!declaration.initializer || !ts.isIdentifier(declaration.initializer)) return null;
415
- if (!ts.isObjectBindingPattern(declaration.name) && !ts.isArrayBindingPattern(declaration.name)) {
1052
+ if (!declaration.initializer || !ts2.isIdentifier(declaration.initializer)) return null;
1053
+ if (!ts2.isObjectBindingPattern(declaration.name) && !ts2.isArrayBindingPattern(declaration.name)) {
416
1054
  return null;
417
1055
  }
418
1056
  const sourceParam = declaration.initializer.text;
@@ -436,33 +1074,33 @@ var collectParamDestructure = (declaration, caller, context) => {
436
1074
 
437
1075
  // src/auto-instrumentation/transform-source.ts
438
1076
  var scriptKindFor = (filePath) => {
439
- if (/\.tsx$/i.test(filePath)) return ts2.ScriptKind.TSX;
440
- if (/\.jsx$/i.test(filePath)) return ts2.ScriptKind.JSX;
441
- if (/\.[cm]?ts$/i.test(filePath)) return ts2.ScriptKind.TS;
442
- return ts2.ScriptKind.JS;
1077
+ if (/\.tsx$/i.test(filePath)) return ts3.ScriptKind.TSX;
1078
+ if (/\.jsx$/i.test(filePath)) return ts3.ScriptKind.JSX;
1079
+ if (/\.[cm]?ts$/i.test(filePath)) return ts3.ScriptKind.TS;
1080
+ return ts3.ScriptKind.JS;
443
1081
  };
444
1082
  var functionName = (node, sourceFile) => {
445
1083
  if (node.name) return node.name.getText(sourceFile).replace(/^['"]|['"]$/g, "");
446
1084
  const parent = node.parent;
447
- if (ts2.isVariableDeclaration(parent) && ts2.isIdentifier(parent.name)) return parent.name.text;
448
- if (ts2.isPropertyAssignment(parent)) return parent.name.getText(sourceFile);
449
- if (ts2.isPropertyDeclaration(parent) && parent.name) return parent.name.getText(sourceFile);
1085
+ if (ts3.isVariableDeclaration(parent) && ts3.isIdentifier(parent.name)) return parent.name.text;
1086
+ if (ts3.isPropertyAssignment(parent)) return parent.name.getText(sourceFile);
1087
+ if (ts3.isPropertyDeclaration(parent) && parent.name) return parent.name.getText(sourceFile);
450
1088
  const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
451
1089
  return `anonymous@${location.line + 1}:${location.character + 1}`;
452
1090
  };
453
1091
  var functionLocationPosition = (node, sourceFile) => {
454
1092
  if (node.name) return node.name.getStart(sourceFile);
455
1093
  const parent = node.parent;
456
- if ((ts2.isVariableDeclaration(parent) || ts2.isPropertyAssignment(parent)) && parent.name) {
1094
+ if ((ts3.isVariableDeclaration(parent) || ts3.isPropertyAssignment(parent)) && parent.name) {
457
1095
  return parent.name.getStart(sourceFile);
458
1096
  }
459
1097
  return node.getStart(sourceFile);
460
1098
  };
461
1099
  var bindingValue = (name, sourceFile) => {
462
- if (ts2.isIdentifier(name)) return name.text;
463
- if (ts2.isArrayBindingPattern(name)) {
1100
+ if (ts3.isIdentifier(name)) return name.text;
1101
+ if (ts3.isArrayBindingPattern(name)) {
464
1102
  const values = name.elements.map((element) => {
465
- if (ts2.isOmittedExpression(element)) return "";
1103
+ if (ts3.isOmittedExpression(element)) return "";
466
1104
  const value = bindingValue(element.name, sourceFile);
467
1105
  return element.dotDotDotToken ? `...${value}` : value;
468
1106
  });
@@ -478,14 +1116,14 @@ var bindingValue = (name, sourceFile) => {
478
1116
  };
479
1117
  var arrowArguments = (node) => {
480
1118
  const values = node.parameters.flatMap((parameter) => {
481
- if (ts2.isIdentifier(parameter.name) && parameter.name.text === "this") return [];
1119
+ if (ts3.isIdentifier(parameter.name) && parameter.name.text === "this") return [];
482
1120
  const value = bindingValue(parameter.name, node.getSourceFile());
483
1121
  return [parameter.dotDotDotToken ? `...${value}` : value];
484
1122
  });
485
1123
  return `[${values.join(",")}]`;
486
1124
  };
487
1125
  var isSupportedFunction = (node) => {
488
- if (!ts2.isFunctionDeclaration(node) && !ts2.isFunctionExpression(node) && !ts2.isArrowFunction(node) && !ts2.isMethodDeclaration(node) && !ts2.isGetAccessorDeclaration(node) && !ts2.isSetAccessorDeclaration(node)) {
1126
+ if (!ts3.isFunctionDeclaration(node) && !ts3.isFunctionExpression(node) && !ts3.isArrowFunction(node) && !ts3.isMethodDeclaration(node) && !ts3.isGetAccessorDeclaration(node) && !ts3.isSetAccessorDeclaration(node)) {
489
1127
  return false;
490
1128
  }
491
1129
  if (!node.body || "asteriskToken" in node && node.asteriskToken) return false;
@@ -496,7 +1134,7 @@ var uniqueName = (source, base) => {
496
1134
  while (source.includes(name)) name += "_";
497
1135
  return name;
498
1136
  };
499
- var isDirectivePrologue = (statement) => ts2.isExpressionStatement(statement) && ts2.isStringLiteral(statement.expression);
1137
+ var isDirectivePrologue = (statement) => ts3.isExpressionStatement(statement) && ts3.isStringLiteral(statement.expression);
500
1138
  var insertPositionAfterPrologue = (source, sourceFile) => {
501
1139
  const shebangEnd = source.startsWith("#!") ? source.indexOf("\n") + 1 : 0;
502
1140
  let end = shebangEnd;
@@ -511,7 +1149,7 @@ var preambleInsert = (source, position, preamble) => {
511
1149
  const previous = source[position - 1];
512
1150
  return previous === ";" || previous === "\n" || previous === "\r" ? preamble : `;${preamble}`;
513
1151
  };
514
- var modulePreamble = (runName, plansName, plansJson) => `const ${plansName}=${plansJson};const ${runName}=((g,k)=>{const r=g[k]??(g[k]={runtimes:[],modules:[],register(p){const d={run:(i,a,c)=>c()};const m={plans:p,cell:d};this.modules.push(m);const n=this.runtimes.at(-1);if(n)n.bind(m);return(i,a,c)=>d.run(i,a,c)}});return r.register(${plansName})})(globalThis,Symbol.for(${JSON.stringify(AUTOMATIC_RUNTIME_SYMBOL)}));`;
1152
+ var modulePreamble = (runName, capName, plansName, plansJson) => `const ${plansName}=${plansJson};const ${runName}=((g,k)=>{const r=g[k]??(g[k]={runtimes:[],modules:[],captureRoot(i,v){return v},register(p){const d={run:(i,a,c)=>c()};const m={plans:p,cell:d};this.modules.push(m);const n=this.runtimes.at(-1);if(n)n.bind(m);return(i,a,c,l)=>d.run(i,a,c,l)}});return r.register(${plansName})})(globalThis,Symbol.for(${JSON.stringify(AUTOMATIC_RUNTIME_SYMBOL)}));const ${capName}=((g,k)=>{const r=g[k];return(i,v)=>typeof r?.captureRoot==='function'?r.captureRoot(i,v):v})(globalThis,Symbol.for(${JSON.stringify(AUTOMATIC_RUNTIME_SYMBOL)}));`;
515
1153
  var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
516
1154
  const start = mappedLocation(
517
1155
  sourceFile,
@@ -542,80 +1180,127 @@ var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
542
1180
  paramNames
543
1181
  };
544
1182
  };
1183
+ var canOmitCallValues = (analysis, analyzed) => {
1184
+ if (!functionIsExact(analysis.plan) || analysis.hasThrow || analysis.hasCoercionGuard) {
1185
+ return false;
1186
+ }
1187
+ const callers = analyzed.filter(
1188
+ (caller) => caller.callEdges.some((edge) => edge.calleeFunctionId === analysis.functionId)
1189
+ );
1190
+ if (callers.length === 0) return false;
1191
+ return callers.every((caller) => caller.plan.fallbackReason === void 0 && !caller.hasThrow);
1192
+ };
545
1193
  var transformSource = (source, options) => {
546
1194
  if (source.includes("@rasputin-ignore-file")) return void 0;
547
- const sourceFile = ts2.createSourceFile(
1195
+ const sourceFile = ts3.createSourceFile(
548
1196
  options.filePath,
549
1197
  source,
550
- ts2.ScriptTarget.Latest,
1198
+ ts3.ScriptTarget.Latest,
551
1199
  true,
552
1200
  scriptKindFor(options.filePath)
553
1201
  );
554
1202
  const runName = uniqueName(source, "__rasputinRun__");
1203
+ const capName = uniqueName(source, "__rasputinCap__");
555
1204
  const plansName = uniqueName(source, "__rasputinPlans__");
556
1205
  const absolutePath = resolve4(options.filePath);
1206
+ const derivationMode = options.derivationMode ?? "analysis";
557
1207
  const edits = [];
558
1208
  let order = 0;
559
1209
  const delta = {
560
1210
  functions: [],
561
1211
  callSites: [],
562
- destructures: []
1212
+ destructures: [],
1213
+ derivationPlans: []
563
1214
  };
564
1215
  const collectContext = {
565
1216
  sourceFile,
566
1217
  mapLocation: options.mapLocation,
567
1218
  absolutePath
568
1219
  };
1220
+ const pending = [];
569
1221
  const visit = (node, caller) => {
570
1222
  if (isSupportedFunction(node)) {
571
1223
  const collected = collectWrappedFunction(node, sourceFile, absolutePath, options.mapLocation);
572
1224
  if (!collected) {
573
- ts2.forEachChild(node, (child) => visit(child, caller));
1225
+ ts3.forEachChild(node, (child) => visit(child, caller));
574
1226
  return;
575
1227
  }
576
- const planIndex = delta.functions.length;
1228
+ pending.push({ node, collected });
577
1229
  delta.functions.push(collected);
578
- const args2 = ts2.isArrowFunction(node) ? arrowArguments(node) : "arguments";
579
- const asyncCallback = node.modifiers?.some(
580
- (modifier) => modifier.kind === ts2.SyntaxKind.AsyncKeyword
581
- ) ? "async " : "";
582
- if (ts2.isBlock(node.body)) {
583
- edits.push({
584
- position: node.body.getStart(sourceFile) + 1,
585
- text: `return ${runName}(${planIndex},${args2},${asyncCallback}()=>{`,
586
- order: order++
587
- });
588
- edits.push({ position: node.body.end - 1, text: "});", order: order++ });
589
- } else {
590
- edits.push({
591
- position: node.body.getStart(sourceFile),
592
- text: `${runName}(${planIndex},${args2},${asyncCallback}()=>(`,
593
- order: order++
594
- });
595
- edits.push({ position: node.body.end, text: "))", order: order++ });
596
- }
597
- ts2.forEachChild(node, (child) => visit(child, collected));
1230
+ ts3.forEachChild(node, (child) => visit(child, collected));
598
1231
  return;
599
1232
  }
600
- if (caller && (ts2.isCallExpression(node) || ts2.isNewExpression(node))) {
1233
+ if (caller && (ts3.isCallExpression(node) || ts3.isNewExpression(node))) {
601
1234
  const site = collectCallSite(node, caller, collectContext);
602
1235
  if (site) delta.callSites.push(site);
603
1236
  }
604
- if (caller && ts2.isVariableDeclaration(node)) {
1237
+ if (caller && ts3.isVariableDeclaration(node)) {
605
1238
  const destructure = collectParamDestructure(node, caller, collectContext);
606
1239
  if (destructure) delta.destructures.push(destructure);
607
1240
  }
608
- ts2.forEachChild(node, (child) => visit(child, caller));
1241
+ ts3.forEachChild(node, (child) => visit(child, caller));
609
1242
  };
610
1243
  visit(sourceFile);
611
- if (edits.length === 0) return void 0;
1244
+ if (pending.length === 0) return void 0;
1245
+ const analyzed = analyzeModuleFunctions(
1246
+ sourceFile,
1247
+ pending.map((entry) => ({
1248
+ functionId: entry.collected.functionId,
1249
+ name: entry.collected.name,
1250
+ node: entry.node
1251
+ }))
1252
+ );
1253
+ delta.derivationPlans = analyzed.map((fn) => fn.plan);
1254
+ const analyzedById = new Map(analyzed.map((fn) => [fn.functionId, fn]));
1255
+ for (const [planIndex, entry] of pending.entries()) {
1256
+ const analysis = analyzedById.get(entry.collected.functionId);
1257
+ const omitValues = derivationMode === "selective" && analysis && canOmitCallValues(analysis, analyzed);
1258
+ const valueFlag = omitValues ? ",1" : "";
1259
+ const args2 = ts3.isArrowFunction(entry.node) ? arrowArguments(entry.node) : "arguments";
1260
+ const asyncCallback = entry.node.modifiers?.some(
1261
+ (modifier) => modifier.kind === ts3.SyntaxKind.AsyncKeyword
1262
+ ) ? "async " : "";
1263
+ if (ts3.isBlock(entry.node.body)) {
1264
+ edits.push({
1265
+ position: entry.node.body.getStart(sourceFile) + 1,
1266
+ text: `return ${runName}(${planIndex},${args2},${asyncCallback}()=>{`,
1267
+ order: order++
1268
+ });
1269
+ edits.push({
1270
+ position: entry.node.body.end - 1,
1271
+ text: `}${valueFlag});`,
1272
+ order: order++
1273
+ });
1274
+ } else {
1275
+ edits.push({
1276
+ position: entry.node.body.getStart(sourceFile),
1277
+ text: `${runName}(${planIndex},${args2},${asyncCallback}()=>(`,
1278
+ order: order++
1279
+ });
1280
+ edits.push({
1281
+ position: entry.node.body.end,
1282
+ text: `)${valueFlag})`,
1283
+ order: order++
1284
+ });
1285
+ }
1286
+ if (analysis) {
1287
+ for (const edit of captureWrapEdits(analysis.captureWraps, capName)) {
1288
+ edits.push({ ...edit, order: order++ });
1289
+ }
1290
+ }
1291
+ }
612
1292
  const insertAt = insertPositionAfterPrologue(source, sourceFile);
613
1293
  edits.push({
614
1294
  position: insertAt,
615
1295
  text: preambleInsert(
616
1296
  source,
617
1297
  insertAt,
618
- modulePreamble(runName, plansName, JSON.stringify(delta.functions.map((fn) => fn.plan)))
1298
+ modulePreamble(
1299
+ runName,
1300
+ capName,
1301
+ plansName,
1302
+ JSON.stringify(delta.functions.map((fn) => fn.plan))
1303
+ )
619
1304
  ),
620
1305
  order: -1
621
1306
  });
@@ -667,7 +1352,8 @@ var instrumentCompiledDirectory = (options) => {
667
1352
  if (source.includes("__rasputinRun__") || source.includes("__rasputinPlans__")) continue;
668
1353
  const transformed = transformSource(source, {
669
1354
  filePath,
670
- mapLocation: createSourceMapLocationMapper(filePath, source)
1355
+ mapLocation: createSourceMapLocationMapper(filePath, source),
1356
+ ...options.derivationMode ? { derivationMode: options.derivationMode } : {}
671
1357
  });
672
1358
  if (!transformed) continue;
673
1359
  const outputPath = join2(outputDir, relative2(inputDir, filePath));
@@ -693,7 +1379,15 @@ var sdkPackageScopeRootFrom = (instrumentEntryUrl) => resolve6(dirname3(fileURLT
693
1379
 
694
1380
  // src/instrument/build.ts
695
1381
  var printUsage = () => {
696
- console.error("Usage: rasputin-instrument <input-dir> [--out <output-dir>] [--manifest <path>]");
1382
+ console.error(
1383
+ "Usage: rasputin-instrument <input-dir> [--out <output-dir>] [--manifest <path>] [--derivation-mode analysis|selective]"
1384
+ );
1385
+ };
1386
+ var parseDerivationMode = (value) => {
1387
+ if (value === void 0) return void 0;
1388
+ if (value === "analysis" || value === "selective") return value;
1389
+ console.error(`[rasputin] unknown --derivation-mode ${value} (expected analysis or selective)`);
1390
+ process.exit(1);
697
1391
  };
698
1392
  var parseArgs = (argv) => {
699
1393
  const inputDir = argv.find((argument) => !argument.startsWith("--"));
@@ -704,7 +1398,8 @@ var parseArgs = (argv) => {
704
1398
  return {
705
1399
  inputDir,
706
1400
  outputDir: option("--out"),
707
- manifestPath: option("--manifest")
1401
+ manifestPath: option("--manifest"),
1402
+ derivationMode: parseDerivationMode(option("--derivation-mode"))
708
1403
  };
709
1404
  };
710
1405
  var args = parseArgs(process.argv.slice(2));
@@ -716,7 +1411,8 @@ var result = instrumentCompiledDirectory({
716
1411
  inputDir: args.inputDir,
717
1412
  outputDir: args.outputDir,
718
1413
  manifestPath: args.manifestPath,
719
- excludedRoots: [sdkPackageScopeRootFrom(import.meta.url)]
1414
+ excludedRoots: [sdkPackageScopeRootFrom(import.meta.url)],
1415
+ ...args.derivationMode ? { derivationMode: args.derivationMode } : {}
720
1416
  });
721
1417
  console.info(
722
1418
  `[rasputin] instrumented ${result.transformedFiles.length} file${result.transformedFiles.length === 1 ? "" : "s"}`