@tradejs/cli 3.1.29-beta.291 → 3.1.29-beta.292

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.
@@ -39,10 +39,161 @@ var import_chalk = __toESM(require("chalk"));
39
39
 
40
40
  // src/lib/coreResearch/io.ts
41
41
  var import_node_crypto = require("crypto");
42
- var import_node_fs = require("fs");
42
+ var import_node_fs2 = require("fs");
43
43
  var import_promises = __toESM(require("fs/promises"));
44
44
  var import_backtest = require("@tradejs/core/backtest");
45
+ var import_node_path2 = __toESM(require("path"));
46
+
47
+ // src/lib/coreResearch/trace.ts
48
+ var import_node_fs = require("fs");
49
+ var import_node_readline = __toESM(require("readline"));
45
50
  var import_node_path = __toESM(require("path"));
51
+ async function* readCoreResearchTrace(filePaths) {
52
+ for (const inputPath of filePaths) {
53
+ const filePath = import_node_path.default.resolve(inputPath);
54
+ const input = (0, import_node_fs.createReadStream)(filePath);
55
+ const lines = import_node_readline.default.createInterface({ input, crlfDelay: Infinity });
56
+ let index = -1;
57
+ try {
58
+ for await (const line of lines) {
59
+ index++;
60
+ if (!line.trim()) continue;
61
+ let event;
62
+ try {
63
+ event = JSON.parse(line);
64
+ } catch (error) {
65
+ throw new Error(
66
+ `${filePath}:${index + 1} contains invalid trace JSON: ${String(error)}`
67
+ );
68
+ }
69
+ yield event;
70
+ }
71
+ } finally {
72
+ lines.close();
73
+ input.destroy();
74
+ }
75
+ }
76
+ }
77
+ var summarizeCoreResearchTrace = async (filePaths = []) => {
78
+ const events = /* @__PURE__ */ Object.create(null);
79
+ const skipCounts = /* @__PURE__ */ Object.create(null);
80
+ const exitCodes = /* @__PURE__ */ Object.create(null);
81
+ const policyRejections = {};
82
+ const policyExamples = [];
83
+ for await (const event of readCoreResearchTrace(filePaths)) {
84
+ events[event.event] = (events[event.event] ?? 0) + 1;
85
+ if (event.event === "skip_summary") {
86
+ for (const [reason, count] of Object.entries(event.skipCounts)) {
87
+ skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
88
+ }
89
+ }
90
+ if (event.event === "position_exited" && event.exitCode) {
91
+ exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
92
+ }
93
+ if ("researchEntryDecision" in event && event.researchEntryDecision && !event.researchEntryDecision.allowed) {
94
+ const reason = event.researchEntryDecision.reason;
95
+ policyRejections[reason] = (policyRejections[reason] ?? 0) + 1;
96
+ if (policyExamples.length < 20)
97
+ policyExamples.push({
98
+ timestamp: event.timestamp,
99
+ symbol: event.symbol,
100
+ decision: event.researchEntryDecision
101
+ });
102
+ }
103
+ }
104
+ return {
105
+ events: Object.fromEntries(Object.entries(events).sort()),
106
+ skipCounts: Object.fromEntries(
107
+ Object.entries(skipCounts).sort(
108
+ ([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
109
+ )
110
+ ),
111
+ exitCodes: Object.fromEntries(
112
+ Object.entries(exitCodes).sort(
113
+ ([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
114
+ )
115
+ ),
116
+ ...policyExamples.length ? {
117
+ researchPolicy: {
118
+ rejections: policyRejections,
119
+ examples: policyExamples
120
+ }
121
+ } : {}
122
+ };
123
+ };
124
+
125
+ // src/lib/coreResearch/support.ts
126
+ async function assessCoreResearchSupport(spec, criteria) {
127
+ const symbols = new Set(spec.universe.symbols);
128
+ const blockMs = spec.robustness.clusterDays * 864e5;
129
+ const variants = [];
130
+ for (const variant of spec.variants) {
131
+ if (!variant.traceFiles?.length) continue;
132
+ const setups = /* @__PURE__ */ new Set();
133
+ const timestamps = /* @__PURE__ */ new Set();
134
+ const blocks = /* @__PURE__ */ new Map();
135
+ const seenSymbols = /* @__PURE__ */ new Set();
136
+ const runs = /* @__PURE__ */ new Set();
137
+ let fallbackRows = 0;
138
+ for await (const event of readCoreResearchTrace(variant.traceFiles)) {
139
+ if (variant.runId && event.backtestRunId !== variant.runId) continue;
140
+ runs.add(event.backtestRunId);
141
+ if (event.event !== "signal_emitted" && event.event !== "entry_rejected" || event.strategy !== spec.strategy || !symbols.has(event.symbol) || spec.hypothesis.target !== "ALL" && event.direction !== spec.hypothesis.target || !Number.isSafeInteger(event.timestamp) || event.timestamp < spec.window.start || event.timestamp >= spec.window.end)
142
+ continue;
143
+ timestamps.add(event.timestamp);
144
+ seenSymbols.add(event.symbol);
145
+ if (event.setupIdentitySource === "strategy-context" && event.setupIdentity) {
146
+ setups.add(
147
+ JSON.stringify([event.symbol, event.direction, event.setupIdentity])
148
+ );
149
+ } else fallbackRows++;
150
+ const block = Math.floor((event.timestamp - spec.window.start) / blockMs);
151
+ const times = blocks.get(block) ?? /* @__PURE__ */ new Set();
152
+ times.add(event.timestamp);
153
+ blocks.set(block, times);
154
+ }
155
+ if (runs.size > 1)
156
+ throw new Error(`Support traces mix runs for ${variant.id}`);
157
+ variants.push({
158
+ id: variant.id,
159
+ role: variant.role,
160
+ setups,
161
+ events: timestamps.size,
162
+ symbols: seenSymbols.size,
163
+ fallbackRows,
164
+ blocks: [...blocks].sort(([a], [b]) => a - b).map(([index, times]) => ({
165
+ start: spec.window.start + index * blockMs,
166
+ events: times.size
167
+ })),
168
+ passed: timestamps.size >= criteria.minimumEvents && blocks.size >= criteria.minimumBlocks
169
+ });
170
+ }
171
+ const control = variants.find((v) => v.role === "control");
172
+ const candidates = variants.filter((v) => v.role === "candidate");
173
+ const comparisons = control ? candidates.map((candidate) => ({
174
+ candidate: candidate.id,
175
+ commonSetups: [...candidate.setups].filter(
176
+ (id) => control.setups.has(id)
177
+ ).length
178
+ })) : [];
179
+ const missing = !control || criteria.minimumCommonSetups !== void 0 && candidates.length !== spec.variants.filter((v) => v.role === "candidate").length;
180
+ const insufficient = variants.some((v) => !v.passed) || comparisons.some(
181
+ (c) => c.commonSetups < (criteria.minimumCommonSetups ?? 0)
182
+ );
183
+ return {
184
+ schema: "tradejs-core-research-support/v1",
185
+ window: { start: spec.window.start, end: spec.window.end },
186
+ target: spec.hypothesis.target,
187
+ blockDays: spec.robustness.clusterDays,
188
+ criteria,
189
+ status: missing ? "missing-evidence" : insufficient ? "insufficient-support" : "supported",
190
+ variants: variants.map(({ setups, ...variant }) => ({
191
+ ...variant,
192
+ explicitSetups: setups.size
193
+ })),
194
+ comparisons
195
+ };
196
+ }
46
197
 
47
198
  // src/lib/coreResearch/types.ts
48
199
  var CORE_RESEARCH_SCHEMA = "tradejs-core-research/v1";
@@ -68,7 +219,7 @@ var sha256Text = (value) => (0, import_node_crypto.createHash)("sha256").update(
68
219
  var sha256Json = (value) => sha256Text(canonicalJson(value));
69
220
  var sha256File = async (filePath) => {
70
221
  const hash = (0, import_node_crypto.createHash)("sha256");
71
- const input = (0, import_node_fs.createReadStream)(filePath);
222
+ const input = (0, import_node_fs2.createReadStream)(filePath);
72
223
  for await (const chunk of input) hash.update(chunk);
73
224
  return hash.digest("hex");
74
225
  };
@@ -307,33 +458,49 @@ var validateCoreResearchSpec = (value) => {
307
458
  Boolean(spec.artifacts?.ledgerPath?.trim()),
308
459
  "artifacts.ledgerPath is required"
309
460
  );
461
+ if (spec.support !== void 0) {
462
+ assert(
463
+ spec.support && typeof spec.support === "object",
464
+ "support must be an object"
465
+ );
466
+ for (const [key, value2] of [
467
+ ["minimumEvents", spec.support.minimumEvents],
468
+ ["minimumBlocks", spec.support.minimumBlocks],
469
+ ...spec.support.minimumCommonSetups === void 0 ? [] : [["minimumCommonSetups", spec.support.minimumCommonSetups]]
470
+ ]) {
471
+ assert(
472
+ typeof value2 === "number" && Number.isSafeInteger(value2) && value2 > 0,
473
+ `support.${key} must be a positive integer`
474
+ );
475
+ }
476
+ }
310
477
  return spec;
311
478
  };
312
479
  var loadCoreResearchSpec = async (specPath) => {
313
- const resolvedPath = import_node_path.default.resolve(specPath);
480
+ const resolvedPath = import_node_path2.default.resolve(specPath);
314
481
  const parsed = JSON.parse(await import_promises.default.readFile(resolvedPath, "utf8"));
315
482
  const spec = validateCoreResearchSpec(parsed);
316
483
  return { resolvedPath, spec, specSha256: sha256Json(spec) };
317
484
  };
318
485
  var resolveCoreResearchPaths = (spec) => {
319
- const rootDir = import_node_path.default.resolve(spec.artifacts.rootDir);
320
- const researchDir = import_node_path.default.join(rootDir, spec.researchId);
486
+ const rootDir = import_node_path2.default.resolve(spec.artifacts.rootDir);
487
+ const researchDir = import_node_path2.default.join(rootDir, spec.researchId);
321
488
  return {
322
489
  rootDir,
323
490
  researchDir,
324
- ledgerPath: import_node_path.default.resolve(spec.artifacts.ledgerPath),
325
- specPath: import_node_path.default.join(researchDir, "spec.json"),
326
- manifestPath: import_node_path.default.join(researchDir, "manifest.json"),
327
- resultPath: import_node_path.default.join(researchDir, "result.json"),
328
- comparisonPath: import_node_path.default.join(researchDir, "comparison.json"),
329
- matchesPath: import_node_path.default.join(researchDir, "matches.csv"),
330
- tradesPath: import_node_path.default.join(researchDir, "trades.jsonl"),
331
- reportPath: import_node_path.default.join(researchDir, "report.html"),
332
- runLogDir: import_node_path.default.join(researchDir, "runs")
491
+ ledgerPath: import_node_path2.default.resolve(spec.artifacts.ledgerPath),
492
+ specPath: import_node_path2.default.join(researchDir, "spec.json"),
493
+ manifestPath: import_node_path2.default.join(researchDir, "manifest.json"),
494
+ resultPath: import_node_path2.default.join(researchDir, "result.json"),
495
+ comparisonPath: import_node_path2.default.join(researchDir, "comparison.json"),
496
+ matchesPath: import_node_path2.default.join(researchDir, "matches.csv"),
497
+ tradesPath: import_node_path2.default.join(researchDir, "trades.jsonl"),
498
+ reportPath: import_node_path2.default.join(researchDir, "report.html"),
499
+ runLogDir: import_node_path2.default.join(researchDir, "runs")
333
500
  };
334
501
  };
335
502
  var writeJsonAtomic = async (filePath, value) => {
336
- await import_promises.default.mkdir(import_node_path.default.dirname(filePath), { recursive: true });
503
+ await import_promises.default.mkdir(import_node_path2.default.dirname(filePath), { recursive: true });
337
504
  const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
338
505
  await import_promises.default.writeFile(temporaryPath, `${canonicalJson(value)}
339
506
  `, "utf8");
@@ -380,7 +547,7 @@ var verifyCoreResearchLedger = (records) => {
380
547
  };
381
548
  var appendCoreResearchLedger = async (params) => {
382
549
  const { ledgerPath } = params;
383
- await import_promises.default.mkdir(import_node_path.default.dirname(ledgerPath), { recursive: true });
550
+ await import_promises.default.mkdir(import_node_path2.default.dirname(ledgerPath), { recursive: true });
384
551
  const lockPath = `${ledgerPath}.lock`;
385
552
  let lock = null;
386
553
  try {
@@ -419,14 +586,14 @@ var appendCoreResearchLedger = async (params) => {
419
586
  var validateCoreResearchParentLineage = async (spec) => {
420
587
  if (spec.stage === "screen") return;
421
588
  const expectedParentStage = spec.stage === "isolated_long" ? "screen" : "isolated_long";
422
- const rootDir = import_node_path.default.resolve(spec.artifacts.rootDir);
589
+ const rootDir = import_node_path2.default.resolve(spec.artifacts.rootDir);
423
590
  const passedParentCandidateConfigShas = /* @__PURE__ */ new Set();
424
591
  for (const parentResearchId of spec.parentResearchIds ?? []) {
425
- const parentDir = import_node_path.default.join(rootDir, parentResearchId);
592
+ const parentDir = import_node_path2.default.join(rootDir, parentResearchId);
426
593
  const [parentSpec, parentManifest, parentResult] = await Promise.all([
427
- import_promises.default.readFile(import_node_path.default.join(parentDir, "spec.json"), "utf8").then((text) => JSON.parse(text)),
428
- import_promises.default.readFile(import_node_path.default.join(parentDir, "manifest.json"), "utf8").then((text) => JSON.parse(text)),
429
- import_promises.default.readFile(import_node_path.default.join(parentDir, "result.json"), "utf8").then((text) => JSON.parse(text))
594
+ import_promises.default.readFile(import_node_path2.default.join(parentDir, "spec.json"), "utf8").then((text) => JSON.parse(text)),
595
+ import_promises.default.readFile(import_node_path2.default.join(parentDir, "manifest.json"), "utf8").then((text) => JSON.parse(text)),
596
+ import_promises.default.readFile(import_node_path2.default.join(parentDir, "result.json"), "utf8").then((text) => JSON.parse(text))
430
597
  ]);
431
598
  validateCoreResearchSpec(parentSpec);
432
599
  if (parentManifest.status !== "completed") {
@@ -500,15 +667,46 @@ var prepareCoreResearch = async (spec) => {
500
667
  return { paths, specSha256, manifest: existingManifest };
501
668
  }
502
669
  }
670
+ const supportSources = spec.support ? await Promise.all(
671
+ [
672
+ ...new Set(
673
+ spec.variants.flatMap((variant) => variant.traceFiles ?? [])
674
+ )
675
+ ].map(async (file) => ({
676
+ file: import_node_path2.default.resolve(file),
677
+ sha256: await sha256File(file).catch((error) => {
678
+ if (error.code === "ENOENT") return null;
679
+ throw error;
680
+ })
681
+ }))
682
+ ) : [];
683
+ const missingSources = supportSources.filter(
684
+ (source) => source.sha256 === null
685
+ );
686
+ const support = spec.support ? missingSources.length ? {
687
+ schema: "tradejs-core-research-support/v1",
688
+ status: "missing-evidence",
689
+ criteria: spec.support
690
+ } : await assessCoreResearchSupport(spec, spec.support) : void 0;
691
+ const supportPath = import_node_path2.default.join(paths.researchDir, "support.json");
692
+ if (support) {
693
+ for (const source of supportSources) {
694
+ if (source.sha256 !== null && source.sha256 !== await sha256File(source.file))
695
+ throw new Error("Support trace changed during preparation");
696
+ }
697
+ await writeJsonAtomic(supportPath, { ...support, sources: supportSources });
698
+ }
503
699
  await writeJsonAtomic(paths.specPath, spec);
504
700
  const manifest = {
505
701
  schema: "tradejs-core-research-manifest/v1",
506
702
  researchId: spec.researchId,
507
703
  specSha256,
508
704
  status: "prepared",
705
+ ...support ? { support: support.status } : {},
509
706
  createdAt: spec.createdAt,
510
707
  artifactHashes: {
511
- "spec.json": await sha256File(paths.specPath)
708
+ "spec.json": await sha256File(paths.specPath),
709
+ ...support ? { "support.json": await sha256File(supportPath) } : {}
512
710
  }
513
711
  };
514
712
  await writeJsonAtomic(paths.manifestPath, manifest);
@@ -527,6 +725,24 @@ var prepareCoreResearch = async (spec) => {
527
725
  }
528
726
  return { paths, specSha256, manifest };
529
727
  };
728
+ var verifyPreparedSupport = async (prepared) => {
729
+ if (!prepared.manifest.support) return;
730
+ if (prepared.manifest.support !== "supported") {
731
+ throw new Error(
732
+ `Research preflight: ${prepared.manifest.support}; see support.json`
733
+ );
734
+ }
735
+ const file = import_node_path2.default.join(prepared.paths.researchDir, "support.json");
736
+ const content = await import_promises.default.readFile(file);
737
+ if ((0, import_node_crypto.createHash)("sha256").update(content).digest("hex") !== prepared.manifest.artifactHashes["support.json"]) {
738
+ throw new Error("Support report checksum mismatch");
739
+ }
740
+ const { sources } = JSON.parse(content.toString());
741
+ for (const source of sources) {
742
+ if (source.sha256 !== await sha256File(source.file))
743
+ throw new Error("Support trace changed after preparation");
744
+ }
745
+ };
530
746
  var verifyCoreResearchArtifacts = async (spec) => {
531
747
  const paths = resolveCoreResearchPaths(spec);
532
748
  const manifest = JSON.parse(
@@ -539,7 +755,7 @@ var verifyCoreResearchArtifacts = async (spec) => {
539
755
  manifest.artifactHashes
540
756
  )) {
541
757
  const actualHash = await sha256File(
542
- import_node_path.default.join(paths.researchDir, relativePath)
758
+ import_node_path2.default.join(paths.researchDir, relativePath)
543
759
  );
544
760
  if (actualHash !== expectedHash) {
545
761
  throw new Error(`Artifact hash mismatch: ${relativePath}`);
@@ -555,7 +771,7 @@ var verifyCoreResearchArtifacts = async (spec) => {
555
771
  };
556
772
  };
557
773
  var scanCoreResearchStages = async (rootDir) => {
558
- const resolvedRoot = import_node_path.default.resolve(rootDir);
774
+ const resolvedRoot = import_node_path2.default.resolve(rootDir);
559
775
  let entries = [];
560
776
  try {
561
777
  entries = await import_promises.default.readdir(resolvedRoot);
@@ -573,15 +789,15 @@ var scanCoreResearchStages = async (rootDir) => {
573
789
  for (const entry of entries.sort(
574
790
  (left, right) => left < right ? -1 : left > right ? 1 : 0
575
791
  )) {
576
- const researchDir = import_node_path.default.join(resolvedRoot, entry);
792
+ const researchDir = import_node_path2.default.join(resolvedRoot, entry);
577
793
  const stats = await import_promises.default.stat(researchDir);
578
794
  if (!stats.isDirectory()) continue;
579
795
  let spec;
580
796
  let manifest;
581
797
  try {
582
798
  [spec, manifest] = await Promise.all([
583
- import_promises.default.readFile(import_node_path.default.join(researchDir, "spec.json"), "utf8").then((text) => JSON.parse(text)),
584
- import_promises.default.readFile(import_node_path.default.join(researchDir, "manifest.json"), "utf8").then((text) => JSON.parse(text))
799
+ import_promises.default.readFile(import_node_path2.default.join(researchDir, "spec.json"), "utf8").then((text) => JSON.parse(text)),
800
+ import_promises.default.readFile(import_node_path2.default.join(researchDir, "manifest.json"), "utf8").then((text) => JSON.parse(text))
585
801
  ]);
586
802
  } catch (error) {
587
803
  if (error.code === "ENOENT") continue;
@@ -591,7 +807,7 @@ var scanCoreResearchStages = async (rootDir) => {
591
807
  if (manifest.specSha256 !== sha256Json(spec)) {
592
808
  throw new Error(`Stage index found a spec hash mismatch for ${entry}`);
593
809
  }
594
- const result = manifest.status === "completed" ? await import_promises.default.readFile(import_node_path.default.join(researchDir, "result.json"), "utf8").then((text) => JSON.parse(text)) : null;
810
+ const result = manifest.status === "completed" ? await import_promises.default.readFile(import_node_path2.default.join(researchDir, "result.json"), "utf8").then((text) => JSON.parse(text)) : null;
595
811
  familyByResearch.set(spec.researchId, spec.hypothesis.family);
596
812
  experiments.push({
597
813
  researchId: spec.researchId,
@@ -622,7 +838,7 @@ var scanCoreResearchStages = async (rootDir) => {
622
838
  return { experiments, familyByResearch };
623
839
  };
624
840
  var writeCoreResearchStageIndex = async (rootDir) => {
625
- const resolvedRoot = import_node_path.default.resolve(rootDir);
841
+ const resolvedRoot = import_node_path2.default.resolve(rootDir);
626
842
  const { experiments, familyByResearch } = await scanCoreResearchStages(resolvedRoot);
627
843
  const grouped = /* @__PURE__ */ new Map();
628
844
  for (const experiment of experiments) {
@@ -639,7 +855,7 @@ var writeCoreResearchStageIndex = async (rootDir) => {
639
855
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
640
856
  families: [...grouped.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([family, experiments2]) => ({ family, experiments: experiments2 }))
641
857
  };
642
- await writeJsonAtomic(import_node_path.default.join(resolvedRoot, "index.json"), index);
858
+ await writeJsonAtomic(import_node_path2.default.join(resolvedRoot, "index.json"), index);
643
859
  return index;
644
860
  };
645
861
 
@@ -651,10 +867,10 @@ var import_node_crypto5 = require("crypto");
651
867
  var import_coreResearch = require("@tradejs/infra/coreResearch");
652
868
 
653
869
  // src/lib/coreResearch/artifacts.ts
654
- var import_node_fs2 = require("fs");
870
+ var import_node_fs3 = require("fs");
655
871
  var import_node_events = require("events");
656
872
  var import_promises2 = __toESM(require("fs/promises"));
657
- var import_node_path2 = __toESM(require("path"));
873
+ var import_node_path3 = __toESM(require("path"));
658
874
  var csv = (value) => {
659
875
  const text = String(value ?? "");
660
876
  return /[",\n]/.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
@@ -667,8 +883,8 @@ var closeOutput = async (output) => {
667
883
  await (0, import_node_events.once)(output, "close");
668
884
  };
669
885
  var writeCoreResearchMatches = async (params) => {
670
- await import_promises2.default.mkdir(import_node_path2.default.dirname(params.filePath), { recursive: true });
671
- const output = (0, import_node_fs2.createWriteStream)(params.filePath, { encoding: "utf8" });
886
+ await import_promises2.default.mkdir(import_node_path3.default.dirname(params.filePath), { recursive: true });
887
+ const output = (0, import_node_fs3.createWriteStream)(params.filePath, { encoding: "utf8" });
672
888
  await writeLine(
673
889
  output,
674
890
  [
@@ -718,8 +934,8 @@ var writeCoreResearchMatches = async (params) => {
718
934
  await closeOutput(output);
719
935
  };
720
936
  var writeCoreResearchTrades = async (params) => {
721
- await import_promises2.default.mkdir(import_node_path2.default.dirname(params.filePath), { recursive: true });
722
- const output = (0, import_node_fs2.createWriteStream)(params.filePath, { encoding: "utf8" });
937
+ await import_promises2.default.mkdir(import_node_path3.default.dirname(params.filePath), { recursive: true });
938
+ const output = (0, import_node_fs3.createWriteStream)(params.filePath, { encoding: "utf8" });
723
939
  for (const variant of params.variants) {
724
940
  for (const trade of variant.trades) {
725
941
  await writeLine(
@@ -1452,9 +1668,9 @@ var compareCoreResearchVariants = (params) => {
1452
1668
 
1453
1669
  // src/lib/coreResearch/dataset.ts
1454
1670
  var import_node_crypto3 = require("crypto");
1455
- var import_node_fs3 = require("fs");
1456
- var import_node_path3 = __toESM(require("path"));
1457
- var import_node_readline = __toESM(require("readline"));
1671
+ var import_node_fs4 = require("fs");
1672
+ var import_node_path4 = __toESM(require("path"));
1673
+ var import_node_readline2 = __toESM(require("readline"));
1458
1674
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
1459
1675
  var finiteNumber = (value, field) => {
1460
1676
  const numeric = Number(value);
@@ -1664,11 +1880,11 @@ var readCoreResearchVariant = async (variant) => {
1664
1880
  const seen = /* @__PURE__ */ new Map();
1665
1881
  let duplicateRowsDropped = 0;
1666
1882
  for (const inputPath of variant.files) {
1667
- const filePath = import_node_path3.default.resolve(inputPath);
1668
- const input = (0, import_node_fs3.createReadStream)(filePath);
1883
+ const filePath = import_node_path4.default.resolve(inputPath);
1884
+ const input = (0, import_node_fs4.createReadStream)(filePath);
1669
1885
  const hash = (0, import_node_crypto3.createHash)("sha256");
1670
1886
  input.on("data", (chunk) => hash.update(chunk));
1671
- const lines = import_node_readline.default.createInterface({ input, crlfDelay: Infinity });
1887
+ const lines = import_node_readline2.default.createInterface({ input, crlfDelay: Infinity });
1672
1888
  const fileTrades = [];
1673
1889
  let lineNumber = 0;
1674
1890
  let rows = 0;
@@ -2156,79 +2372,6 @@ var applyCoreResearchRobustnessGuardrails = (params) => {
2156
2372
  }
2157
2373
  };
2158
2374
 
2159
- // src/lib/coreResearch/trace.ts
2160
- var import_node_fs4 = require("fs");
2161
- var import_node_readline2 = __toESM(require("readline"));
2162
- var import_node_path4 = __toESM(require("path"));
2163
- var summarizeCoreResearchTrace = async (filePaths = []) => {
2164
- const events = /* @__PURE__ */ Object.create(null);
2165
- const skipCounts = /* @__PURE__ */ Object.create(null);
2166
- const exitCodes = /* @__PURE__ */ Object.create(null);
2167
- const policyRejections = {};
2168
- const policyExamples = [];
2169
- for (const inputPath of filePaths) {
2170
- const filePath = import_node_path4.default.resolve(inputPath);
2171
- const input = (0, import_node_fs4.createReadStream)(filePath);
2172
- const lines = import_node_readline2.default.createInterface({ input, crlfDelay: Infinity });
2173
- let index = -1;
2174
- try {
2175
- for await (const line of lines) {
2176
- index++;
2177
- if (!line.trim()) continue;
2178
- let event;
2179
- try {
2180
- event = JSON.parse(line);
2181
- } catch (error) {
2182
- throw new Error(
2183
- `${filePath}:${index + 1} contains invalid trace JSON: ${String(error)}`
2184
- );
2185
- }
2186
- events[event.event] = (events[event.event] ?? 0) + 1;
2187
- if (event.event === "skip_summary") {
2188
- for (const [reason, count] of Object.entries(event.skipCounts)) {
2189
- skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
2190
- }
2191
- }
2192
- if (event.event === "position_exited" && event.exitCode) {
2193
- exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
2194
- }
2195
- if ("researchEntryDecision" in event && event.researchEntryDecision && !event.researchEntryDecision.allowed) {
2196
- const reason = event.researchEntryDecision.reason;
2197
- policyRejections[reason] = (policyRejections[reason] ?? 0) + 1;
2198
- if (policyExamples.length < 20)
2199
- policyExamples.push({
2200
- timestamp: event.timestamp,
2201
- symbol: event.symbol,
2202
- decision: event.researchEntryDecision
2203
- });
2204
- }
2205
- }
2206
- } finally {
2207
- lines.close();
2208
- input.destroy();
2209
- }
2210
- }
2211
- return {
2212
- events: Object.fromEntries(Object.entries(events).sort()),
2213
- skipCounts: Object.fromEntries(
2214
- Object.entries(skipCounts).sort(
2215
- ([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
2216
- )
2217
- ),
2218
- exitCodes: Object.fromEntries(
2219
- Object.entries(exitCodes).sort(
2220
- ([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
2221
- )
2222
- ),
2223
- ...policyExamples.length ? {
2224
- researchPolicy: {
2225
- rejections: policyRejections,
2226
- examples: policyExamples
2227
- }
2228
- } : {}
2229
- };
2230
- };
2231
-
2232
2375
  // src/lib/researchRoots.ts
2233
2376
  var import_node_path5 = __toESM(require("path"));
2234
2377
  var SOURCE_REPOSITORY_ROOT_ENV = "TRADEJS_SOURCE_REPOSITORY_ROOT";
@@ -2280,6 +2423,7 @@ var summarizeSupplementalFiles = async (params) => {
2280
2423
  var analyzeCoreResearchPrepared = async (spec, options = {}) => {
2281
2424
  const prepared = options.prepared ?? await prepareCoreResearch(spec);
2282
2425
  const { paths, specSha256 } = prepared;
2426
+ await verifyPreparedSupport(prepared);
2283
2427
  if (prepared.manifest.status === "completed") {
2284
2428
  throw new Error(
2285
2429
  `Research ${spec.researchId} is already completed and immutable; use verify or create a new researchId`
@@ -2550,6 +2694,7 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
2550
2694
  "utf8"
2551
2695
  );
2552
2696
  const artifactHashes = {
2697
+ ...prepared.manifest.artifactHashes,
2553
2698
  "spec.json": await sha256File(paths.specPath),
2554
2699
  "result.json": await sha256File(paths.resultPath),
2555
2700
  "comparison.json": await sha256File(paths.comparisonPath),
@@ -2840,6 +2985,7 @@ var runCoreResearch = async (params) => {
2840
2985
  const { spec } = params;
2841
2986
  const cwd = import_node_path6.default.resolve(params.cwd ?? process.cwd());
2842
2987
  const prepared = await prepareCoreResearch(spec);
2988
+ await verifyPreparedSupport(prepared);
2843
2989
  const resolvedVariants = [];
2844
2990
  for (const variant of spec.variants) {
2845
2991
  if (!variant.command?.length) {
@@ -3085,6 +3231,8 @@ var main = async () => {
3085
3231
  `Prepared ${loaded.spec.researchId}: ${result.paths.researchDir}`
3086
3232
  )
3087
3233
  );
3234
+ if (result.manifest.support)
3235
+ console.log(`Support: ${result.manifest.support} (support.json)`);
3088
3236
  return;
3089
3237
  }
3090
3238
  if (command === "analyze") {