@tradejs/cli 3.1.0 → 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.
@@ -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
  });
@@ -5135,14 +5135,29 @@ var resolveRuntimeGateFingerprint = async ({
5135
5135
  gateFingerprintCache.set(cacheKey, result);
5136
5136
  return result;
5137
5137
  };
5138
- var buildRuntimeLineage = async ({
5138
+ async function buildRuntimeLineage({
5139
5139
  projectRoot,
5140
5140
  strategyName,
5141
5141
  compositionId,
5142
+ releaseVersion,
5143
+ strategyPackageVersion,
5144
+ runtimePackageVersion,
5142
5145
  config,
5143
5146
  runContext,
5144
5147
  env = process.env
5145
- }) => {
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
+ }
5146
5161
  const git = resolveGitLineage(projectRoot);
5147
5162
  const hyperliquidPerpUniverseFingerprint = typeof import_strategies3.getHyperliquidPerpUniverseSnapshot === "function" ? (0, import_strategies3.getHyperliquidPerpUniverseSnapshot)().fingerprint : await resolveSnapshotFingerprint(
5148
5163
  projectRoot,
@@ -5177,8 +5192,8 @@ var buildRuntimeLineage = async ({
5177
5192
  contextFingerprint: fingerprintRuntimeValue(context),
5178
5193
  maxLossValue: resolveRuntimeMaxLossValue(config)
5179
5194
  };
5180
- };
5181
- var runtimeLineageKey = (lineage) => [
5195
+ }
5196
+ var runtimeLineageKey = (lineage) => lineage.schemaVersion === 2 ? ["v2", lineage.releaseVersion].join(":") : [
5182
5197
  lineage.schemaVersion,
5183
5198
  lineage.compositionId ?? "unbound-composition",
5184
5199
  lineage.gitSha ?? "unknown",
@@ -5430,6 +5445,7 @@ var runHistoricalSignalsReplay = async ({
5430
5445
  userName: replayUserName,
5431
5446
  connectorName,
5432
5447
  runtimeConfigId: "config",
5448
+ runtimeLineage,
5433
5449
  config: buildReplayStrategyConfig({
5434
5450
  strategyConfig,
5435
5451
  interval
@@ -7210,6 +7226,7 @@ var buildRuntimeEvidenceLineageScopes = ({
7210
7226
  strategy,
7211
7227
  symbol,
7212
7228
  runtimeConfigId,
7229
+ runtimeReleaseVersion,
7213
7230
  timestamp,
7214
7231
  lineage
7215
7232
  }) => {
@@ -7222,6 +7239,7 @@ var buildRuntimeEvidenceLineageScopes = ({
7222
7239
  strategy,
7223
7240
  symbol,
7224
7241
  ...runtimeConfigId ? { runtimeConfigId } : {},
7242
+ ...runtimeReleaseVersion ? { runtimeReleaseVersion } : {},
7225
7243
  lineage,
7226
7244
  firstTimestamp: existing ? Math.min(existing.firstTimestamp, timestamp) : timestamp,
7227
7245
  lastTimestamp: existing ? Math.max(existing.lastTimestamp, timestamp) : timestamp
@@ -7232,6 +7250,7 @@ var buildRuntimeEvidenceLineageScopes = ({
7232
7250
  strategy: signal.strategy,
7233
7251
  symbol: signal.symbol,
7234
7252
  runtimeConfigId: signal.runtimeConfigId,
7253
+ runtimeReleaseVersion: signal.runtimeReleaseVersion,
7235
7254
  timestamp: signal.timestamp,
7236
7255
  lineage: signal.runtimeLineage
7237
7256
  });
@@ -7241,6 +7260,7 @@ var buildRuntimeEvidenceLineageScopes = ({
7241
7260
  strategy: evaluation.strategy,
7242
7261
  symbol: evaluation.symbol,
7243
7262
  runtimeConfigId: evaluation.runtimeConfigId,
7263
+ runtimeReleaseVersion: evaluation.runtimeReleaseVersion,
7244
7264
  timestamp: evaluation.timestamp,
7245
7265
  lineage: evaluation.runtimeLineage
7246
7266
  });
@@ -8551,13 +8571,26 @@ var renderLineageRows = (runtimeComparison) => {
8551
8571
  contexts: /* @__PURE__ */ new Set()
8552
8572
  };
8553
8573
  existing.scopes += 1;
8554
- existing.gitShas.add(record.lineage.gitSha ?? "unknown");
8555
- existing.dirty.add(
8556
- record.lineage.gitDirty == null ? "unknown" : String(record.lineage.gitDirty)
8557
- );
8558
- existing.gates.add(record.lineage.gateFingerprint);
8559
- existing.configs.add(record.lineage.configFingerprint);
8560
- 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
+ }
8561
8594
  grouped.set(record.strategy, existing);
8562
8595
  }
8563
8596
  const formatSet = (values) => {
@@ -4350,7 +4350,7 @@ var buildRuntimeModeStrategyConfig = ({
4350
4350
  ...strategyConfig,
4351
4351
  ENV: env,
4352
4352
  MAKE_ORDERS: makeOrders,
4353
- INTERVAL: interval,
4353
+ INTERVAL: strategyConfig.INTERVAL ?? interval,
4354
4354
  ...typeof recordRuntimeTrades === "boolean" ? { RECORD_RUNTIME_TRADES: recordRuntimeTrades } : {},
4355
4355
  ...aiReplayAnalyses?.length ? { AI_REPLAY_ANALYSES: aiReplayAnalyses } : {}
4356
4356
  });
@@ -5138,14 +5138,29 @@ var resolveRuntimeGateFingerprint = async ({
5138
5138
  gateFingerprintCache.set(cacheKey, result);
5139
5139
  return result;
5140
5140
  };
5141
- var buildRuntimeLineage = async ({
5141
+ async function buildRuntimeLineage({
5142
5142
  projectRoot,
5143
5143
  strategyName,
5144
5144
  compositionId,
5145
+ releaseVersion,
5146
+ strategyPackageVersion,
5147
+ runtimePackageVersion,
5145
5148
  config,
5146
5149
  runContext,
5147
5150
  env = process.env
5148
- }) => {
5151
+ }) {
5152
+ if (releaseVersion != null) {
5153
+ if (!Number.isSafeInteger(releaseVersion) || releaseVersion <= 0) {
5154
+ throw new Error(`Invalid runtime releaseVersion: ${releaseVersion}`);
5155
+ }
5156
+ return {
5157
+ schemaVersion: 2,
5158
+ releaseVersion,
5159
+ strategyPackageVersion,
5160
+ runtimePackageVersion,
5161
+ maxLossValue: resolveRuntimeMaxLossValue(config)
5162
+ };
5163
+ }
5149
5164
  const git = resolveGitLineage(projectRoot);
5150
5165
  const hyperliquidPerpUniverseFingerprint = typeof import_strategies3.getHyperliquidPerpUniverseSnapshot === "function" ? (0, import_strategies3.getHyperliquidPerpUniverseSnapshot)().fingerprint : await resolveSnapshotFingerprint(
5151
5166
  projectRoot,
@@ -5180,8 +5195,8 @@ var buildRuntimeLineage = async ({
5180
5195
  contextFingerprint: fingerprintRuntimeValue(context),
5181
5196
  maxLossValue: resolveRuntimeMaxLossValue(config)
5182
5197
  };
5183
- };
5184
- var runtimeLineageKey = (lineage) => [
5198
+ }
5199
+ var runtimeLineageKey = (lineage) => lineage.schemaVersion === 2 ? ["v2", lineage.releaseVersion].join(":") : [
5185
5200
  lineage.schemaVersion,
5186
5201
  lineage.compositionId ?? "unbound-composition",
5187
5202
  lineage.gitSha ?? "unknown",
@@ -5433,6 +5448,7 @@ var runHistoricalSignalsReplay = async ({
5433
5448
  userName: replayUserName,
5434
5449
  connectorName,
5435
5450
  runtimeConfigId: "config",
5451
+ runtimeLineage,
5436
5452
  config: buildReplayStrategyConfig({
5437
5453
  strategyConfig,
5438
5454
  interval
@@ -7213,6 +7229,7 @@ var buildRuntimeEvidenceLineageScopes = ({
7213
7229
  strategy,
7214
7230
  symbol,
7215
7231
  runtimeConfigId,
7232
+ runtimeReleaseVersion,
7216
7233
  timestamp,
7217
7234
  lineage
7218
7235
  }) => {
@@ -7225,6 +7242,7 @@ var buildRuntimeEvidenceLineageScopes = ({
7225
7242
  strategy,
7226
7243
  symbol,
7227
7244
  ...runtimeConfigId ? { runtimeConfigId } : {},
7245
+ ...runtimeReleaseVersion ? { runtimeReleaseVersion } : {},
7228
7246
  lineage,
7229
7247
  firstTimestamp: existing ? Math.min(existing.firstTimestamp, timestamp) : timestamp,
7230
7248
  lastTimestamp: existing ? Math.max(existing.lastTimestamp, timestamp) : timestamp
@@ -7235,6 +7253,7 @@ var buildRuntimeEvidenceLineageScopes = ({
7235
7253
  strategy: signal.strategy,
7236
7254
  symbol: signal.symbol,
7237
7255
  runtimeConfigId: signal.runtimeConfigId,
7256
+ runtimeReleaseVersion: signal.runtimeReleaseVersion,
7238
7257
  timestamp: signal.timestamp,
7239
7258
  lineage: signal.runtimeLineage
7240
7259
  });
@@ -7244,6 +7263,7 @@ var buildRuntimeEvidenceLineageScopes = ({
7244
7263
  strategy: evaluation.strategy,
7245
7264
  symbol: evaluation.symbol,
7246
7265
  runtimeConfigId: evaluation.runtimeConfigId,
7266
+ runtimeReleaseVersion: evaluation.runtimeReleaseVersion,
7247
7267
  timestamp: evaluation.timestamp,
7248
7268
  lineage: evaluation.runtimeLineage
7249
7269
  });
@@ -8554,13 +8574,26 @@ var renderLineageRows = (runtimeComparison) => {
8554
8574
  contexts: /* @__PURE__ */ new Set()
8555
8575
  };
8556
8576
  existing.scopes += 1;
8557
- existing.gitShas.add(record.lineage.gitSha ?? "unknown");
8558
- existing.dirty.add(
8559
- record.lineage.gitDirty == null ? "unknown" : String(record.lineage.gitDirty)
8560
- );
8561
- existing.gates.add(record.lineage.gateFingerprint);
8562
- existing.configs.add(record.lineage.configFingerprint);
8563
- existing.contexts.add(record.lineage.contextFingerprint);
8577
+ if (record.lineage.schemaVersion === 2) {
8578
+ existing.gitShas.add("release-versioned");
8579
+ existing.dirty.add("n/a");
8580
+ existing.gates.add("release-versioned");
8581
+ existing.configs.add(`v${record.lineage.releaseVersion}`);
8582
+ existing.contexts.add(
8583
+ [
8584
+ record.lineage.strategyPackageVersion ?? "strategy:unknown",
8585
+ record.lineage.runtimePackageVersion ?? "runtime:unknown"
8586
+ ].join("/")
8587
+ );
8588
+ } else {
8589
+ existing.gitShas.add(record.lineage.gitSha ?? "unknown");
8590
+ existing.dirty.add(
8591
+ record.lineage.gitDirty == null ? "unknown" : String(record.lineage.gitDirty)
8592
+ );
8593
+ existing.gates.add(record.lineage.gateFingerprint);
8594
+ existing.configs.add(record.lineage.configFingerprint);
8595
+ existing.contexts.add(record.lineage.contextFingerprint);
8596
+ }
8564
8597
  grouped.set(record.strategy, existing);
8565
8598
  }
8566
8599
  const formatSet = (values) => {
@@ -57,7 +57,7 @@ var import_chalk2 = __toESM(require("chalk"));
57
57
  var import_node_child_process = require("child_process");
58
58
  var import_promises = __toESM(require("fs/promises"));
59
59
  var import_node_os = __toESM(require("os"));
60
- var import_node_path = __toESM(require("path"));
60
+ var import_node_path2 = __toESM(require("path"));
61
61
  var import_constants = require("@tradejs/core/constants");
62
62
  var import_ai = require("@tradejs/infra/ai");
63
63
  var import_logger = require("@tradejs/infra/logger");
@@ -84,6 +84,26 @@ var BACKTEST_CLI_RUNTIME_CONFIG_KEYS = /* @__PURE__ */ new Set([
84
84
  "BACKTEST_ENTRY_DELAY_BARS"
85
85
  ]);
86
86
 
87
+ // src/lib/researchRoots.ts
88
+ var import_node_path = __toESM(require("path"));
89
+ var SOURCE_REPOSITORY_ROOT_ENV = "TRADEJS_SOURCE_REPOSITORY_ROOT";
90
+ var firstNonEmpty = (...values) => values.map((value) => String(value || "").trim()).find(Boolean);
91
+ var resolveResearchRoots = (options = {}) => {
92
+ const cwd = import_node_path.default.resolve(options.cwd || process.cwd());
93
+ const env = options.env || process.env;
94
+ const projectRoot2 = import_node_path.default.resolve(
95
+ firstNonEmpty(options.projectRoot, env.PROJECT_CWD, cwd) || cwd
96
+ );
97
+ const sourceRepositoryRoot2 = import_node_path.default.resolve(
98
+ firstNonEmpty(
99
+ options.sourceRepositoryRoot,
100
+ env[SOURCE_REPOSITORY_ROOT_ENV],
101
+ cwd
102
+ ) || cwd
103
+ );
104
+ return { projectRoot: projectRoot2, sourceRepositoryRoot: sourceRepositoryRoot2 };
105
+ };
106
+
87
107
  // src/lib/telegramReports.ts
88
108
  var import_cli = require("@tradejs/node/cli");
89
109
  var TELEGRAM_REPORT_CHUNK_LIMIT = 3900;
@@ -175,11 +195,11 @@ import_args.default.option(["O", "outDir"], "AI export output directory", "data/
175
195
  import_args.default.option(["A", "skipAgent"], "Skip direct research agent invocation", false);
176
196
  import_args.default.option(["j", "json"], "Print structured JSON summary", false);
177
197
  var flags = import_args.default.parse(process.argv);
178
- var projectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
198
+ var { projectRoot, sourceRepositoryRoot } = resolveResearchRoots();
179
199
  var dotenvPath = String(
180
- process.env.DOTENV_CONFIG_PATH || import_node_path.default.join(projectRoot, ".env")
181
- ).trim() || import_node_path.default.join(projectRoot, ".env");
182
- var getResearchAutoLockPath = (userName) => import_node_path.default.join(
200
+ process.env.DOTENV_CONFIG_PATH || import_node_path2.default.join(projectRoot, ".env")
201
+ ).trim() || import_node_path2.default.join(projectRoot, ".env");
202
+ var getResearchAutoLockPath = (userName) => import_node_path2.default.join(
183
203
  projectRoot,
184
204
  "data",
185
205
  "cache",
@@ -223,7 +243,7 @@ var acquireResearchAutoLock = async ({
223
243
  startedAt,
224
244
  argv: [...process.argv]
225
245
  };
226
- await import_promises.default.mkdir(import_node_path.default.dirname(lockPath), { recursive: true });
246
+ await import_promises.default.mkdir(import_node_path2.default.dirname(lockPath), { recursive: true });
227
247
  const writeLock = async () => {
228
248
  await import_promises.default.writeFile(lockPath, JSON.stringify(nextLock, null, 2), {
229
249
  flag: "wx"
@@ -360,7 +380,7 @@ var listMergedFiles = async (outDir, strategyName) => {
360
380
  try {
361
381
  const entries = await import_promises.default.readdir(outDir);
362
382
  const prefix = `ai-dataset-${(0, import_ai.toFileToken)(strategyName)}-merged-`;
363
- return entries.filter((name) => name.startsWith(prefix) && name.endsWith(".jsonl")).sort().map((name) => import_node_path.default.join(outDir, name));
383
+ return entries.filter((name) => name.startsWith(prefix) && name.endsWith(".jsonl")).sort().map((name) => import_node_path2.default.join(outDir, name));
364
384
  } catch {
365
385
  return [];
366
386
  }
@@ -480,6 +500,7 @@ var runCliCommand = async (params) => {
480
500
  const env = {
481
501
  ...process.env,
482
502
  PROJECT_CWD: projectRoot,
503
+ [SOURCE_REPOSITORY_ROOT_ENV]: sourceRepositoryRoot,
483
504
  DOTENV_CONFIG_PATH: dotenvPath,
484
505
  ...params.nodeHeapMb ? {
485
506
  NODE_OPTIONS: buildNodeOptions({
@@ -489,8 +510,8 @@ var runCliCommand = async (params) => {
489
510
  };
490
511
  return new Promise((resolve, reject) => {
491
512
  const child = (0, import_node_child_process.spawn)(
492
- "bash",
493
- ["./bin/run-cli-runtime.sh", params.command, ...params.args],
513
+ process.execPath,
514
+ [import_node_path2.default.resolve(process.argv[1]), params.command, ...params.args],
494
515
  {
495
516
  cwd: projectRoot,
496
517
  env,
@@ -672,8 +693,8 @@ var main = async () => {
672
693
  const jsonOutput = Boolean(flags.json);
673
694
  const skipAgent = Boolean(flags.skipAgent);
674
695
  const outDirArg = String(flags.outDir || "data/ai/export").trim() || "data/ai/export";
675
- const outDir = import_node_path.default.resolve(projectRoot, outDirArg);
676
- const cleanDirArg = import_node_path.default.relative(import_node_path.default.join(projectRoot, "data"), outDir).replace(/\\/g, "/");
696
+ const outDir = import_node_path2.default.resolve(projectRoot, outDirArg);
697
+ const cleanDirArg = import_node_path2.default.relative(import_node_path2.default.join(projectRoot, "data"), outDir).replace(/\\/g, "/");
677
698
  const target = await resolveTarget();
678
699
  const runId = `${Date.now()}-${(0, import_ai.toFileToken)(target.strategy)}`;
679
700
  const run = {