@tradejs/cli 3.0.1 → 3.1.1

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.
@@ -1001,28 +1001,37 @@ var import_types = require("@tradejs/types");
1001
1001
 
1002
1002
  // src/lib/strategyGateFingerprint.ts
1003
1003
  var import_node_crypto = require("crypto");
1004
+ var import_node_module = require("module");
1004
1005
  var import_promises = __toESM(require("fs/promises"));
1005
1006
  var import_node_path = __toESM(require("path"));
1006
- var gateFingerprintRelativePaths = (strategyName) => [
1007
- `packages/strategies/src/${strategyName}/adapters/ai.ts`,
1008
- `packages/strategies/src/${strategyName}/guardrails.ts`,
1009
- `packages/strategies/src/${strategyName}/pockets.ts`,
1010
- `packages/strategies/src/${strategyName}/config.ts`,
1011
- "packages/strategies/src/shared/localAiGate.ts",
1012
- "packages/node/src/ai.ts",
1013
- "packages/core/src/utils/strategyHelpers/signalBuilders.ts"
1007
+ var toPackageToken = (strategyName) => strategyName.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
1008
+ var getStrategyPackageName = (strategyName) => strategyName === "TrendLine" || strategyName === "ReverseTrendLine" ? "@tradejs/strategy-trend-line" : `@tradejs/strategy-${toPackageToken(strategyName)}`;
1009
+ var gateFingerprintSpecifiers = (strategyName) => [
1010
+ getStrategyPackageName(strategyName),
1011
+ "@tradejs/strategy-kit/ai-gate",
1012
+ "@tradejs/node/ai",
1013
+ "@tradejs/core/strategies"
1014
1014
  ];
1015
1015
  var resolveStrategyGateFingerprint = async ({
1016
1016
  projectRoot,
1017
1017
  strategyName,
1018
1018
  gitSha
1019
1019
  }) => {
1020
- const candidates = await Promise.all(
1021
- gateFingerprintRelativePaths(strategyName).map(async (relativePath) => {
1022
- const content = await import_promises.default.readFile(import_node_path.default.join(projectRoot, relativePath)).catch(() => null);
1023
- return content == null ? null : { relativePath, content };
1020
+ const projectManifestPath = import_node_path.default.join(projectRoot, "package.json");
1021
+ const projectExists = await import_promises.default.access(projectManifestPath).then(() => true).catch(() => false);
1022
+ const requireFromProject = projectExists ? (0, import_node_module.createRequire)(projectManifestPath) : null;
1023
+ const candidates = requireFromProject ? await Promise.all(
1024
+ gateFingerprintSpecifiers(strategyName).map(async (specifier) => {
1025
+ let resolvedPath;
1026
+ try {
1027
+ resolvedPath = requireFromProject.resolve(specifier);
1028
+ } catch {
1029
+ return null;
1030
+ }
1031
+ const content = await import_promises.default.readFile(resolvedPath).catch(() => null);
1032
+ return content == null ? null : { specifier, content };
1024
1033
  })
1025
- );
1034
+ ) : [];
1026
1035
  const sourceEntries = candidates.filter(
1027
1036
  (entry) => entry != null
1028
1037
  );
@@ -1030,7 +1039,7 @@ var resolveStrategyGateFingerprint = async ({
1030
1039
  hash.update(strategyName);
1031
1040
  if (sourceEntries.length) {
1032
1041
  for (const entry of sourceEntries) {
1033
- hash.update(entry.relativePath);
1042
+ hash.update(entry.specifier);
1034
1043
  hash.update(entry.content);
1035
1044
  }
1036
1045
  } else {
@@ -1038,7 +1047,7 @@ var resolveStrategyGateFingerprint = async ({
1038
1047
  }
1039
1048
  return {
1040
1049
  gateFingerprint: hash.digest("hex").slice(0, 16),
1041
- gateFingerprintFiles: sourceEntries.map((entry) => entry.relativePath)
1050
+ gateFingerprintFiles: sourceEntries.map((entry) => entry.specifier)
1042
1051
  };
1043
1052
  };
1044
1053
 
@@ -34,7 +34,7 @@ __export(coreResearch_exports, {
34
34
  });
35
35
  module.exports = __toCommonJS(coreResearch_exports);
36
36
  var import_promises5 = __toESM(require("fs/promises"));
37
- var import_node_path6 = __toESM(require("path"));
37
+ var import_node_path7 = __toESM(require("path"));
38
38
  var import_chalk = __toESM(require("chalk"));
39
39
 
40
40
  // src/lib/coreResearch/io.ts
@@ -641,7 +641,7 @@ var writeCoreResearchStageIndex = async (rootDir) => {
641
641
 
642
642
  // src/lib/coreResearch/orchestrator.ts
643
643
  var import_promises4 = __toESM(require("fs/promises"));
644
- var import_node_path5 = __toESM(require("path"));
644
+ var import_node_path6 = __toESM(require("path"));
645
645
  var import_node_child_process = require("child_process");
646
646
  var import_node_crypto5 = require("crypto");
647
647
  var import_coreResearch = require("@tradejs/infra/coreResearch");
@@ -2107,6 +2107,26 @@ var summarizeCoreResearchTrace = async (filePaths = []) => {
2107
2107
  };
2108
2108
  };
2109
2109
 
2110
+ // src/lib/researchRoots.ts
2111
+ var import_node_path5 = __toESM(require("path"));
2112
+ var SOURCE_REPOSITORY_ROOT_ENV = "TRADEJS_SOURCE_REPOSITORY_ROOT";
2113
+ var firstNonEmpty = (...values) => values.map((value) => String(value || "").trim()).find(Boolean);
2114
+ var resolveResearchRoots = (options = {}) => {
2115
+ const cwd = import_node_path5.default.resolve(options.cwd || process.cwd());
2116
+ const env = options.env || process.env;
2117
+ const projectRoot = import_node_path5.default.resolve(
2118
+ firstNonEmpty(options.projectRoot, env.PROJECT_CWD, cwd) || cwd
2119
+ );
2120
+ const sourceRepositoryRoot = import_node_path5.default.resolve(
2121
+ firstNonEmpty(
2122
+ options.sourceRepositoryRoot,
2123
+ env[SOURCE_REPOSITORY_ROOT_ENV],
2124
+ cwd
2125
+ ) || cwd
2126
+ );
2127
+ return { projectRoot, sourceRepositoryRoot };
2128
+ };
2129
+
2110
2130
  // src/lib/coreResearch/orchestrator.ts
2111
2131
  var summarizeSupplementalFiles = async (params) => {
2112
2132
  const loaded = await readCoreResearchVariant({
@@ -2299,7 +2319,7 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
2299
2319
  const primarySourceHashes = Object.fromEntries(
2300
2320
  analyses.flatMap(
2301
2321
  (analysis) => analysis.files.map((file) => [
2302
- import_node_path5.default.relative(paths.researchDir, file.path),
2322
+ import_node_path6.default.relative(paths.researchDir, file.path),
2303
2323
  file.sha256
2304
2324
  ])
2305
2325
  )
@@ -2318,8 +2338,8 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
2318
2338
  const supplementalSourceHashes = Object.fromEntries(
2319
2339
  await Promise.all(
2320
2340
  supplementalSourcePaths.map(async (filePath) => [
2321
- import_node_path5.default.relative(paths.researchDir, import_node_path5.default.resolve(filePath)),
2322
- await sha256File(import_node_path5.default.resolve(filePath))
2341
+ import_node_path6.default.relative(paths.researchDir, import_node_path6.default.resolve(filePath)),
2342
+ await sha256File(import_node_path6.default.resolve(filePath))
2323
2343
  ])
2324
2344
  )
2325
2345
  );
@@ -2396,7 +2416,7 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
2396
2416
  "trades.jsonl": await sha256File(paths.tradesPath),
2397
2417
  "report.html": await sha256File(paths.reportPath)
2398
2418
  };
2399
- const resolvedRunsPath = import_node_path5.default.join(paths.researchDir, "resolved-runs.json");
2419
+ const resolvedRunsPath = import_node_path6.default.join(paths.researchDir, "resolved-runs.json");
2400
2420
  try {
2401
2421
  artifactHashes["resolved-runs.json"] = await sha256File(resolvedRunsPath);
2402
2422
  } catch (error) {
@@ -2406,7 +2426,7 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
2406
2426
  for (const name of await import_promises4.default.readdir(paths.runLogDir)) {
2407
2427
  if (!name.endsWith(".log")) continue;
2408
2428
  artifactHashes[`runs/${name}`] = await sha256File(
2409
- import_node_path5.default.join(paths.runLogDir, name)
2429
+ import_node_path6.default.join(paths.runLogDir, name)
2410
2430
  );
2411
2431
  }
2412
2432
  } catch (error) {
@@ -2463,12 +2483,14 @@ var analyzeCoreResearch = async (spec, options = {}) => {
2463
2483
  }
2464
2484
  };
2465
2485
  var createCoreResearchSpecTemplate = (params) => {
2466
- const rootDir = "data/research/core";
2467
- const projectRoot = process.cwd();
2486
+ const rootDir = import_node_path6.default.relative(
2487
+ import_node_path6.default.resolve(params.projectRoot),
2488
+ import_node_path6.default.join(import_node_path6.default.resolve(params.projectRoot), "data", "research", "core")
2489
+ ).replace(/\\/g, "/");
2468
2490
  const lineage = (() => {
2469
2491
  try {
2470
2492
  const gitSha = (0, import_node_child_process.execFileSync)("git", ["rev-parse", "HEAD"], {
2471
- cwd: projectRoot,
2493
+ cwd: params.sourceRepositoryRoot,
2472
2494
  encoding: "utf8",
2473
2495
  stdio: ["ignore", "pipe", "ignore"]
2474
2496
  }).trim();
@@ -2476,13 +2498,23 @@ var createCoreResearchSpecTemplate = (params) => {
2476
2498
  "git",
2477
2499
  ["diff", "--binary", "--no-ext-diff", "HEAD"],
2478
2500
  {
2479
- cwd: projectRoot,
2501
+ cwd: params.sourceRepositoryRoot,
2480
2502
  encoding: "utf8",
2481
2503
  stdio: ["ignore", "pipe", "ignore"]
2482
2504
  }
2483
2505
  );
2506
+ const sourceRepository = (0, import_node_child_process.execFileSync)(
2507
+ "git",
2508
+ ["remote", "get-url", "origin"],
2509
+ {
2510
+ cwd: params.sourceRepositoryRoot,
2511
+ encoding: "utf8",
2512
+ stdio: ["ignore", "pipe", "ignore"]
2513
+ }
2514
+ ).trim();
2484
2515
  return {
2485
2516
  gitSha,
2517
+ ...sourceRepository ? { sourceRepository } : {},
2486
2518
  ...dirtyDiff ? {
2487
2519
  dirtyDiffSha256: (0, import_node_crypto5.createHash)("sha256").update(dirtyDiff).digest("hex")
2488
2520
  } : {}
@@ -2583,11 +2615,11 @@ var createCoreResearchSpecTemplate = (params) => {
2583
2615
  };
2584
2616
  var runCommand = async (params) => {
2585
2617
  if (!params.command.length) throw new Error("Cannot run an empty command");
2586
- await import_promises4.default.mkdir(import_node_path5.default.dirname(params.logPath), { recursive: true });
2618
+ await import_promises4.default.mkdir(import_node_path6.default.dirname(params.logPath), { recursive: true });
2587
2619
  const chunks = [];
2588
2620
  const child = (0, import_node_child_process.spawn)(params.command[0], params.command.slice(1), {
2589
2621
  cwd: params.cwd,
2590
- env: process.env,
2622
+ env: { ...process.env, ...params.env },
2591
2623
  stdio: ["ignore", "pipe", "pipe"]
2592
2624
  });
2593
2625
  const capture = (chunk) => {
@@ -2655,7 +2687,7 @@ var validateCoreResearchRunCommand = (params) => {
2655
2687
  };
2656
2688
  var runCoreResearch = async (params) => {
2657
2689
  const { spec } = params;
2658
- const cwd = import_node_path5.default.resolve(params.cwd ?? process.cwd());
2690
+ const cwd = import_node_path6.default.resolve(params.cwd ?? process.cwd());
2659
2691
  const prepared = await prepareCoreResearch(spec);
2660
2692
  const resolvedVariants = [];
2661
2693
  for (const variant of spec.variants) {
@@ -2673,7 +2705,7 @@ var runCoreResearch = async (params) => {
2673
2705
  artifactHashes: { variant: sha256Json(variant) }
2674
2706
  });
2675
2707
  let runId = "";
2676
- const backtestLogPath = import_node_path5.default.join(
2708
+ const backtestLogPath = import_node_path6.default.join(
2677
2709
  prepared.paths.runLogDir,
2678
2710
  `${variant.id}-backtest.log`
2679
2711
  );
@@ -2682,6 +2714,14 @@ var runCoreResearch = async (params) => {
2682
2714
  command: variant.command,
2683
2715
  cwd,
2684
2716
  logPath: backtestLogPath,
2717
+ env: {
2718
+ PROJECT_CWD: cwd,
2719
+ ...params.sourceRepositoryRoot ? {
2720
+ [SOURCE_REPOSITORY_ROOT_ENV]: import_node_path6.default.resolve(
2721
+ params.sourceRepositoryRoot
2722
+ )
2723
+ } : {}
2724
+ },
2685
2725
  onLine: (line) => {
2686
2726
  const match = line.match(
2687
2727
  /backtest run id:\s*([0-9]{12}-[a-f0-9]{8})/i
@@ -2692,12 +2732,12 @@ var runCoreResearch = async (params) => {
2692
2732
  if (!runId) {
2693
2733
  throw new Error(`Backtest for ${variant.id} did not report a run id`);
2694
2734
  }
2695
- const exportLogPath = import_node_path5.default.join(
2735
+ const exportLogPath = import_node_path6.default.join(
2696
2736
  prepared.paths.runLogDir,
2697
2737
  `${variant.id}-export.log`
2698
2738
  );
2699
2739
  const exportParts = [];
2700
- const cliPath = import_node_path5.default.resolve(process.argv[1]);
2740
+ const cliPath = import_node_path6.default.resolve(process.argv[1]);
2701
2741
  await runCommand({
2702
2742
  command: [
2703
2743
  process.execPath,
@@ -2714,9 +2754,17 @@ var runCoreResearch = async (params) => {
2714
2754
  ],
2715
2755
  cwd,
2716
2756
  logPath: exportLogPath,
2757
+ env: {
2758
+ PROJECT_CWD: cwd,
2759
+ ...params.sourceRepositoryRoot ? {
2760
+ [SOURCE_REPOSITORY_ROOT_ENV]: import_node_path6.default.resolve(
2761
+ params.sourceRepositoryRoot
2762
+ )
2763
+ } : {}
2764
+ },
2717
2765
  onLine: (line) => {
2718
2766
  const match = line.match(/^part\d+:\s+(.+)$/);
2719
- if (match) exportParts.push(import_node_path5.default.resolve(cwd, match[1].trim()));
2767
+ if (match) exportParts.push(import_node_path6.default.resolve(cwd, match[1].trim()));
2720
2768
  }
2721
2769
  });
2722
2770
  if (!exportParts.length) {
@@ -2727,7 +2775,7 @@ var runCoreResearch = async (params) => {
2727
2775
  const traceFiles = await (0, import_coreResearch.listCoreResearchTraceFiles)({
2728
2776
  strategyName: spec.strategy,
2729
2777
  runId,
2730
- outDir: import_node_path5.default.resolve(cwd, "data/research/core/trace")
2778
+ outDir: import_node_path6.default.resolve(cwd, "data/research/core/trace")
2731
2779
  });
2732
2780
  resolvedVariants.push({
2733
2781
  ...variant,
@@ -2747,7 +2795,7 @@ var runCoreResearch = async (params) => {
2747
2795
  ...Object.fromEntries(
2748
2796
  await Promise.all(
2749
2797
  exportParts.map(async (filePath) => [
2750
- import_node_path5.default.basename(filePath),
2798
+ import_node_path6.default.basename(filePath),
2751
2799
  await sha256File(filePath)
2752
2800
  ])
2753
2801
  )
@@ -2766,7 +2814,7 @@ var runCoreResearch = async (params) => {
2766
2814
  }
2767
2815
  }
2768
2816
  await writeJsonAtomic(
2769
- import_node_path5.default.join(prepared.paths.researchDir, "resolved-runs.json"),
2817
+ import_node_path6.default.join(prepared.paths.researchDir, "resolved-runs.json"),
2770
2818
  {
2771
2819
  schema: "tradejs-core-research-resolved-runs/v1",
2772
2820
  researchId: spec.researchId,
@@ -2795,11 +2843,11 @@ var parseTimestamp = (value, name) => {
2795
2843
  if (!Number.isFinite(numeric)) throw new Error(`${name} is invalid: ${text}`);
2796
2844
  return numeric;
2797
2845
  };
2798
- var readSymbols = async (argv) => {
2846
+ var readSymbols = async (argv, projectRoot) => {
2799
2847
  const symbolsFile = valueAfter(argv, "--symbolsFile");
2800
2848
  if (symbolsFile) {
2801
2849
  const parsed = JSON.parse(
2802
- await import_promises5.default.readFile(import_node_path6.default.resolve(symbolsFile), "utf8")
2850
+ await import_promises5.default.readFile(import_node_path7.default.resolve(projectRoot, symbolsFile), "utf8")
2803
2851
  );
2804
2852
  if (!Array.isArray(parsed) || parsed.some((symbol) => typeof symbol !== "string")) {
2805
2853
  throw new Error("--symbolsFile must contain a JSON string array");
@@ -2820,6 +2868,8 @@ var printUsage = () => {
2820
2868
  The run command executes only variants with an explicit command[] in the preregistered spec. Variants with files[] are analyzed without starting a backtest.`);
2821
2869
  };
2822
2870
  var main = async () => {
2871
+ const roots = resolveResearchRoots();
2872
+ process.chdir(roots.projectRoot);
2823
2873
  const argv = process.argv.slice(2);
2824
2874
  const command = argv[0];
2825
2875
  if (!command || !["init", "prepare", "analyze", "run", "verify", "index"].includes(command)) {
@@ -2828,28 +2878,32 @@ var main = async () => {
2828
2878
  return;
2829
2879
  }
2830
2880
  if (command === "index") {
2831
- const rootDir = import_node_path6.default.resolve(
2881
+ const rootDir = import_node_path7.default.resolve(
2882
+ roots.projectRoot,
2832
2883
  required(valueAfter(argv, "--root"), "--root")
2833
2884
  );
2834
2885
  const index = await writeCoreResearchStageIndex(rootDir);
2835
2886
  console.log(
2836
2887
  import_chalk.default.green(
2837
- `Core research index: ${import_node_path6.default.join(rootDir, "index.json")} (${index.families.length} families)`
2888
+ `Core research index: ${import_node_path7.default.join(rootDir, "index.json")} (${index.families.length} families)`
2838
2889
  )
2839
2890
  );
2840
2891
  return;
2841
2892
  }
2842
2893
  if (command === "init") {
2843
- const outputPath = import_node_path6.default.resolve(
2894
+ const outputPath = import_node_path7.default.resolve(
2895
+ roots.projectRoot,
2844
2896
  required(valueAfter(argv, "--out"), "--out")
2845
2897
  );
2846
2898
  const spec = createCoreResearchSpecTemplate({
2847
2899
  researchId: required(valueAfter(argv, "--researchId"), "--researchId"),
2848
2900
  strategy: required(valueAfter(argv, "--strategy"), "--strategy"),
2849
- symbols: await readSymbols(argv),
2901
+ symbols: await readSymbols(argv, roots.projectRoot),
2850
2902
  start: parseTimestamp(valueAfter(argv, "--start"), "--start"),
2851
2903
  end: parseTimestamp(valueAfter(argv, "--end"), "--end"),
2852
- outputPath
2904
+ outputPath,
2905
+ projectRoot: roots.projectRoot,
2906
+ sourceRepositoryRoot: roots.sourceRepositoryRoot
2853
2907
  });
2854
2908
  const { outputPath: _outputPath, ...persisted } = spec;
2855
2909
  await writeJsonAtomic(outputPath, persisted);
@@ -2861,7 +2915,10 @@ var main = async () => {
2861
2915
  );
2862
2916
  return;
2863
2917
  }
2864
- const specPath = required(valueAfter(argv, "--spec"), "--spec");
2918
+ const specPath = import_node_path7.default.resolve(
2919
+ roots.projectRoot,
2920
+ required(valueAfter(argv, "--spec"), "--spec")
2921
+ );
2865
2922
  const loaded = await loadCoreResearchSpec(specPath);
2866
2923
  if (command === "prepare") {
2867
2924
  const result = await prepareCoreResearch(loaded.spec);
@@ -2880,7 +2937,11 @@ var main = async () => {
2880
2937
  return;
2881
2938
  }
2882
2939
  if (command === "run") {
2883
- const analyzed = await runCoreResearch({ spec: loaded.spec });
2940
+ const analyzed = await runCoreResearch({
2941
+ spec: loaded.spec,
2942
+ cwd: roots.projectRoot,
2943
+ sourceRepositoryRoot: roots.sourceRepositoryRoot
2944
+ });
2884
2945
  console.log(
2885
2946
  import_chalk.default.green(`Core research report: ${analyzed.paths.reportPath}`)
2886
2947
  );
@@ -4347,7 +4347,7 @@ var buildRuntimeModeStrategyConfig = ({
4347
4347
  ...strategyConfig,
4348
4348
  ENV: env,
4349
4349
  MAKE_ORDERS: makeOrders,
4350
- INTERVAL: interval,
4350
+ INTERVAL: strategyConfig.INTERVAL ?? interval,
4351
4351
  ...typeof recordRuntimeTrades === "boolean" ? { RECORD_RUNTIME_TRADES: recordRuntimeTrades } : {},
4352
4352
  ...aiReplayAnalyses?.length ? { AI_REPLAY_ANALYSES: aiReplayAnalyses } : {}
4353
4353
  });
@@ -4910,28 +4910,37 @@ var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvid
4910
4910
 
4911
4911
  // src/lib/strategyGateFingerprint.ts
4912
4912
  var import_node_crypto = require("crypto");
4913
+ var import_node_module = require("module");
4913
4914
  var import_promises = __toESM(require("fs/promises"));
4914
4915
  var import_node_path = __toESM(require("path"));
4915
- var gateFingerprintRelativePaths = (strategyName) => [
4916
- `packages/strategies/src/${strategyName}/adapters/ai.ts`,
4917
- `packages/strategies/src/${strategyName}/guardrails.ts`,
4918
- `packages/strategies/src/${strategyName}/pockets.ts`,
4919
- `packages/strategies/src/${strategyName}/config.ts`,
4920
- "packages/strategies/src/shared/localAiGate.ts",
4921
- "packages/node/src/ai.ts",
4922
- "packages/core/src/utils/strategyHelpers/signalBuilders.ts"
4916
+ var toPackageToken = (strategyName) => strategyName.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
4917
+ var getStrategyPackageName = (strategyName) => strategyName === "TrendLine" || strategyName === "ReverseTrendLine" ? "@tradejs/strategy-trend-line" : `@tradejs/strategy-${toPackageToken(strategyName)}`;
4918
+ var gateFingerprintSpecifiers = (strategyName) => [
4919
+ getStrategyPackageName(strategyName),
4920
+ "@tradejs/strategy-kit/ai-gate",
4921
+ "@tradejs/node/ai",
4922
+ "@tradejs/core/strategies"
4923
4923
  ];
4924
4924
  var resolveStrategyGateFingerprint = async ({
4925
4925
  projectRoot,
4926
4926
  strategyName,
4927
4927
  gitSha
4928
4928
  }) => {
4929
- const candidates = await Promise.all(
4930
- gateFingerprintRelativePaths(strategyName).map(async (relativePath) => {
4931
- const content = await import_promises.default.readFile(import_node_path.default.join(projectRoot, relativePath)).catch(() => null);
4932
- return content == null ? null : { relativePath, content };
4929
+ const projectManifestPath = import_node_path.default.join(projectRoot, "package.json");
4930
+ const projectExists = await import_promises.default.access(projectManifestPath).then(() => true).catch(() => false);
4931
+ const requireFromProject = projectExists ? (0, import_node_module.createRequire)(projectManifestPath) : null;
4932
+ const candidates = requireFromProject ? await Promise.all(
4933
+ gateFingerprintSpecifiers(strategyName).map(async (specifier) => {
4934
+ let resolvedPath;
4935
+ try {
4936
+ resolvedPath = requireFromProject.resolve(specifier);
4937
+ } catch {
4938
+ return null;
4939
+ }
4940
+ const content = await import_promises.default.readFile(resolvedPath).catch(() => null);
4941
+ return content == null ? null : { specifier, content };
4933
4942
  })
4934
- );
4943
+ ) : [];
4935
4944
  const sourceEntries = candidates.filter(
4936
4945
  (entry) => entry != null
4937
4946
  );
@@ -4939,7 +4948,7 @@ var resolveStrategyGateFingerprint = async ({
4939
4948
  hash.update(strategyName);
4940
4949
  if (sourceEntries.length) {
4941
4950
  for (const entry of sourceEntries) {
4942
- hash.update(entry.relativePath);
4951
+ hash.update(entry.specifier);
4943
4952
  hash.update(entry.content);
4944
4953
  }
4945
4954
  } else {
@@ -4947,7 +4956,7 @@ var resolveStrategyGateFingerprint = async ({
4947
4956
  }
4948
4957
  return {
4949
4958
  gateFingerprint: hash.digest("hex").slice(0, 16),
4950
- gateFingerprintFiles: sourceEntries.map((entry) => entry.relativePath)
4959
+ gateFingerprintFiles: sourceEntries.map((entry) => entry.specifier)
4951
4960
  };
4952
4961
  };
4953
4962
 
@@ -5126,14 +5135,29 @@ var resolveRuntimeGateFingerprint = async ({
5126
5135
  gateFingerprintCache.set(cacheKey, result);
5127
5136
  return result;
5128
5137
  };
5129
- var buildRuntimeLineage = async ({
5138
+ async function buildRuntimeLineage({
5130
5139
  projectRoot,
5131
5140
  strategyName,
5132
5141
  compositionId,
5142
+ releaseVersion,
5143
+ strategyPackageVersion,
5144
+ runtimePackageVersion,
5133
5145
  config,
5134
5146
  runContext,
5135
5147
  env = process.env
5136
- }) => {
5148
+ }) {
5149
+ if (releaseVersion != null) {
5150
+ if (!Number.isSafeInteger(releaseVersion) || releaseVersion <= 0) {
5151
+ throw new Error(`Invalid runtime releaseVersion: ${releaseVersion}`);
5152
+ }
5153
+ return {
5154
+ schemaVersion: 2,
5155
+ releaseVersion,
5156
+ strategyPackageVersion,
5157
+ runtimePackageVersion,
5158
+ maxLossValue: resolveRuntimeMaxLossValue(config)
5159
+ };
5160
+ }
5137
5161
  const git = resolveGitLineage(projectRoot);
5138
5162
  const hyperliquidPerpUniverseFingerprint = typeof import_strategies3.getHyperliquidPerpUniverseSnapshot === "function" ? (0, import_strategies3.getHyperliquidPerpUniverseSnapshot)().fingerprint : await resolveSnapshotFingerprint(
5139
5163
  projectRoot,
@@ -5168,8 +5192,8 @@ var buildRuntimeLineage = async ({
5168
5192
  contextFingerprint: fingerprintRuntimeValue(context),
5169
5193
  maxLossValue: resolveRuntimeMaxLossValue(config)
5170
5194
  };
5171
- };
5172
- var runtimeLineageKey = (lineage) => [
5195
+ }
5196
+ var runtimeLineageKey = (lineage) => lineage.schemaVersion === 2 ? ["v2", lineage.releaseVersion].join(":") : [
5173
5197
  lineage.schemaVersion,
5174
5198
  lineage.compositionId ?? "unbound-composition",
5175
5199
  lineage.gitSha ?? "unknown",
@@ -5421,6 +5445,7 @@ var runHistoricalSignalsReplay = async ({
5421
5445
  userName: replayUserName,
5422
5446
  connectorName,
5423
5447
  runtimeConfigId: "config",
5448
+ runtimeLineage,
5424
5449
  config: buildReplayStrategyConfig({
5425
5450
  strategyConfig,
5426
5451
  interval
@@ -7201,6 +7226,7 @@ var buildRuntimeEvidenceLineageScopes = ({
7201
7226
  strategy,
7202
7227
  symbol,
7203
7228
  runtimeConfigId,
7229
+ runtimeReleaseVersion,
7204
7230
  timestamp,
7205
7231
  lineage
7206
7232
  }) => {
@@ -7213,6 +7239,7 @@ var buildRuntimeEvidenceLineageScopes = ({
7213
7239
  strategy,
7214
7240
  symbol,
7215
7241
  ...runtimeConfigId ? { runtimeConfigId } : {},
7242
+ ...runtimeReleaseVersion ? { runtimeReleaseVersion } : {},
7216
7243
  lineage,
7217
7244
  firstTimestamp: existing ? Math.min(existing.firstTimestamp, timestamp) : timestamp,
7218
7245
  lastTimestamp: existing ? Math.max(existing.lastTimestamp, timestamp) : timestamp
@@ -7223,6 +7250,7 @@ var buildRuntimeEvidenceLineageScopes = ({
7223
7250
  strategy: signal.strategy,
7224
7251
  symbol: signal.symbol,
7225
7252
  runtimeConfigId: signal.runtimeConfigId,
7253
+ runtimeReleaseVersion: signal.runtimeReleaseVersion,
7226
7254
  timestamp: signal.timestamp,
7227
7255
  lineage: signal.runtimeLineage
7228
7256
  });
@@ -7232,6 +7260,7 @@ var buildRuntimeEvidenceLineageScopes = ({
7232
7260
  strategy: evaluation.strategy,
7233
7261
  symbol: evaluation.symbol,
7234
7262
  runtimeConfigId: evaluation.runtimeConfigId,
7263
+ runtimeReleaseVersion: evaluation.runtimeReleaseVersion,
7235
7264
  timestamp: evaluation.timestamp,
7236
7265
  lineage: evaluation.runtimeLineage
7237
7266
  });
@@ -8542,13 +8571,26 @@ var renderLineageRows = (runtimeComparison) => {
8542
8571
  contexts: /* @__PURE__ */ new Set()
8543
8572
  };
8544
8573
  existing.scopes += 1;
8545
- existing.gitShas.add(record.lineage.gitSha ?? "unknown");
8546
- existing.dirty.add(
8547
- record.lineage.gitDirty == null ? "unknown" : String(record.lineage.gitDirty)
8548
- );
8549
- existing.gates.add(record.lineage.gateFingerprint);
8550
- existing.configs.add(record.lineage.configFingerprint);
8551
- existing.contexts.add(record.lineage.contextFingerprint);
8574
+ if (record.lineage.schemaVersion === 2) {
8575
+ existing.gitShas.add("release-versioned");
8576
+ existing.dirty.add("n/a");
8577
+ existing.gates.add("release-versioned");
8578
+ existing.configs.add(`v${record.lineage.releaseVersion}`);
8579
+ existing.contexts.add(
8580
+ [
8581
+ record.lineage.strategyPackageVersion ?? "strategy:unknown",
8582
+ record.lineage.runtimePackageVersion ?? "runtime:unknown"
8583
+ ].join("/")
8584
+ );
8585
+ } else {
8586
+ existing.gitShas.add(record.lineage.gitSha ?? "unknown");
8587
+ existing.dirty.add(
8588
+ record.lineage.gitDirty == null ? "unknown" : String(record.lineage.gitDirty)
8589
+ );
8590
+ existing.gates.add(record.lineage.gateFingerprint);
8591
+ existing.configs.add(record.lineage.configFingerprint);
8592
+ existing.contexts.add(record.lineage.contextFingerprint);
8593
+ }
8552
8594
  grouped.set(record.strategy, existing);
8553
8595
  }
8554
8596
  const formatSet = (values) => {