@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.
- package/dist/cli.js +10497 -9777
- package/dist/lib/coreResearch.js +261 -115
- package/dist/lib/researchVisuals.js +27 -6
- package/dist/lib/strategyResearch.js +202 -167
- package/dist/scripts/aiPocketSearch.js +3343 -3029
- package/dist/scripts/coreResearch.js +263 -115
- package/dist/scripts/replay.js +480 -111
- package/dist/scripts/replayRunner.js +480 -111
- package/dist/scripts/researchVisuals.js +27 -6
- package/dist/scripts/strategyResearch.js +204 -168
- package/package.json +7 -7
|
@@ -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
|
|
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,
|
|
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 =
|
|
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 =
|
|
320
|
-
const researchDir =
|
|
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:
|
|
325
|
-
specPath:
|
|
326
|
-
manifestPath:
|
|
327
|
-
resultPath:
|
|
328
|
-
comparisonPath:
|
|
329
|
-
matchesPath:
|
|
330
|
-
tradesPath:
|
|
331
|
-
reportPath:
|
|
332
|
-
runLogDir:
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
592
|
+
const parentDir = import_node_path2.default.join(rootDir, parentResearchId);
|
|
426
593
|
const [parentSpec, parentManifest, parentResult] = await Promise.all([
|
|
427
|
-
import_promises.default.readFile(
|
|
428
|
-
import_promises.default.readFile(
|
|
429
|
-
import_promises.default.readFile(
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
584
|
-
import_promises.default.readFile(
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
671
|
-
const output = (0,
|
|
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(
|
|
722
|
-
const output = (0,
|
|
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
|
|
1456
|
-
var
|
|
1457
|
-
var
|
|
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 =
|
|
1668
|
-
const input = (0,
|
|
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 =
|
|
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") {
|