@zltquant/zlt-mcp 0.7.2-beta.0 → 0.7.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zltquant/zlt-mcp",
3
- "version": "0.7.2-beta.0",
3
+ "version": "0.7.3",
4
4
  "description": "智量通 ZLT Quant MCP server(Windows x64;量化策略创建与本地回测)",
5
5
  "bin": {
6
6
  "zlt-mcp": "./zlt-mcp-proxy.bundle.cjs"
@@ -10609,11 +10609,59 @@ var {
10609
10609
  // src/cli-evidence.ts
10610
10610
  var fs = __toESM(require("node:fs"), 1);
10611
10611
  var path = __toESM(require("node:path"), 1);
10612
+ var READABLE_LINE_LIMIT = 1e3;
10613
+ function readableValue(value) {
10614
+ if (Array.isArray(value)) return value.map(readableValue);
10615
+ if (!value || typeof value !== "object") return value;
10616
+ const source = value;
10617
+ const result = /* @__PURE__ */ Object.create(null);
10618
+ for (const [key, entry] of Object.entries(source)) result[key] = readableValue(entry);
10619
+ if (source.type === "text" && typeof source.text === "string") {
10620
+ try {
10621
+ result.text = {
10622
+ displayNote: "Parsed from content[].text JSON. Exact original text remains in result.json.",
10623
+ parsedJson: readableValue(JSON.parse(source.text))
10624
+ };
10625
+ } catch {
10626
+ result.text = source.text;
10627
+ }
10628
+ }
10629
+ return result;
10630
+ }
10631
+ function takeChunk(value, limit) {
10632
+ let end = Math.min(limit, value.length);
10633
+ if (end < value.length && end > 0) {
10634
+ const code = value.charCodeAt(end - 1);
10635
+ if (code >= 55296 && code <= 56319) end -= 1;
10636
+ }
10637
+ return [value.slice(0, end), value.slice(end)];
10638
+ }
10639
+ function buildReadableEvidence(value) {
10640
+ const header = [
10641
+ "ZLT CLI readable evidence v1 - DISPLAY ONLY; result.json is the authoritative complete saved result.",
10642
+ "WRAPPING: | starts a rendered logical line; + continues the preceding line. Strip the first character and join + lines before interpretation.",
10643
+ "Embedded content[].text that is valid JSON is expanded under parsedJson; non-JSON text is preserved as text."
10644
+ ];
10645
+ const rendered = JSON.stringify(readableValue(value), null, 2).split("\n");
10646
+ const lines = [...header];
10647
+ for (const logical of rendered) {
10648
+ let remaining = logical;
10649
+ let marker = "|";
10650
+ do {
10651
+ const [chunk, rest] = takeChunk(remaining, READABLE_LINE_LIMIT - 1);
10652
+ lines.push(marker + chunk);
10653
+ marker = "+";
10654
+ remaining = rest;
10655
+ } while (remaining.length > 0);
10656
+ }
10657
+ return lines.join("\n") + "\n";
10658
+ }
10612
10659
  function evidenceDirectoryArg(args) {
10613
- if (!["call", "list"].includes(args[0])) return void 0;
10660
+ const authCommand = args[0] === "auth" && ["login", "status", "wait"].includes(args[1]);
10661
+ if (!["call", "list"].includes(args[0]) && !authCommand) return void 0;
10614
10662
  let directory;
10615
- const valueOptions = /* @__PURE__ */ new Set(["--args-json", "--args-file", "--output-json", "--tool", "--timeout-ms", "--max-total-timeout-ms"]);
10616
- for (let i = 1; i < args.length && args[i] !== "--"; i++) {
10663
+ const valueOptions = /* @__PURE__ */ new Set(["--args-json", "--args-file", "--output-json", "--tool", "--timeout-ms", "--max-total-timeout-ms", "--session"]);
10664
+ for (let i = authCommand ? 2 : 1; i < args.length && args[i] !== "--"; i++) {
10617
10665
  const arg = args[i];
10618
10666
  if (valueOptions.has(arg)) {
10619
10667
  i++;
@@ -10629,6 +10677,7 @@ function evidenceDirectoryArg(args) {
10629
10677
  }
10630
10678
  var CliEvidence = class {
10631
10679
  resultFile;
10680
+ readableFile;
10632
10681
  diagnosticsFile;
10633
10682
  executionFile;
10634
10683
  state;
@@ -10640,6 +10689,7 @@ var CliEvidence = class {
10640
10689
  const root2 = path.resolve(directory);
10641
10690
  fs.mkdirSync(root2);
10642
10691
  this.resultFile = path.join(root2, "result.json");
10692
+ this.readableFile = path.join(root2, "result.readable.txt");
10643
10693
  this.diagnosticsFile = path.join(root2, "diagnostics.log");
10644
10694
  this.executionFile = path.join(root2, "execution.json");
10645
10695
  this.state = {
@@ -10653,6 +10703,7 @@ var CliEvidence = class {
10653
10703
  business: "not_reported",
10654
10704
  exitCode: null,
10655
10705
  resultFile: this.resultFile,
10706
+ readableFile: this.readableFile,
10656
10707
  diagnosticsFile: this.diagnosticsFile,
10657
10708
  retry: "inspect_state_before_retry"
10658
10709
  };
@@ -10682,6 +10733,13 @@ var CliEvidence = class {
10682
10733
  checkDiagnostics() {
10683
10734
  if (this.diagnosticFailure) throw new Error(`Saving diagnostics failed: ${this.diagnosticFailure.message}`);
10684
10735
  }
10736
+ writeReadable(value) {
10737
+ fs.writeFileSync(this.readableFile, buildReadableEvidence(value), { encoding: "utf8", flag: "wx" });
10738
+ }
10739
+ writeResult(value) {
10740
+ fs.writeFileSync(this.resultFile, JSON.stringify(value) + "\n", { encoding: "utf8", flag: "wx" });
10741
+ this.writeReadable(value);
10742
+ }
10685
10743
  started(tool) {
10686
10744
  this.checkDiagnostics();
10687
10745
  this.state = { ...this.state, ...tool ? { tool } : {}, stage: "tool_call", toolExecution: "unknown" };
@@ -10730,7 +10788,7 @@ var CliEvidence = class {
10730
10788
  this.failed(stage, this.state.toolExecution, this.state.business);
10731
10789
  }
10732
10790
  receiptFields() {
10733
- return { executionFile: this.executionFile, diagnosticsFile: this.diagnosticsFile };
10791
+ return { executionFile: this.executionFile, diagnosticsFile: this.diagnosticsFile, readableFile: this.readableFile };
10734
10792
  }
10735
10793
  close() {
10736
10794
  if (process.stderr.write === this.teeWrite) process.stderr.write = this.originalWrite;
@@ -10766,9 +10824,12 @@ same MCP connection through mcp-wait. Progress can remain unchanged while data i
10766
10824
  prepared; read stderr and do not restart from a progress warning. If isTerminal=true,
10767
10825
  finalize that run without waiting again. Interrupted waits identify the runId,
10768
10826
  codePath, and attemptFence for inspection; never restart automatically.`;
10769
- var REFERENCING_TOOLS = /* @__PURE__ */ new Set(["mcp-get-cache-diff", "mcp-get-cloud-data-list"]);
10770
10827
  var REFERENCE_SOURCE_TOOL = "mcp-download-data";
10771
- var REFERENCE_PROPERTY = "exchange_id";
10828
+ var REFERENCED_PROPERTIES_BY_TOOL = /* @__PURE__ */ new Map([
10829
+ ["mcp-get-cache-diff", ["exchange_id"]],
10830
+ ["mcp-get-cloud-data-list", ["exchange_id"]],
10831
+ ["mcp-get-data-range", ["exchange_id", "data_type"]]
10832
+ ]);
10772
10833
  function record(value) {
10773
10834
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
10774
10835
  }
@@ -10884,17 +10945,22 @@ function buildArgumentExample(tool) {
10884
10945
  }
10885
10946
  function relatedParameterHelp(tool, tools) {
10886
10947
  const name = typeof tool.name === "string" ? tool.name : "";
10887
- if (!REFERENCING_TOOLS.has(name)) return void 0;
10888
- const referenced = schemaProperty(tool, REFERENCE_PROPERTY);
10889
- if (typeof referenced?.description !== "string" || !referenced.description.includes(REFERENCE_SOURCE_TOOL)) return void 0;
10948
+ const properties = REFERENCED_PROPERTIES_BY_TOOL.get(name);
10949
+ if (!properties) return void 0;
10890
10950
  const source = tools.find((candidate) => candidate.name === REFERENCE_SOURCE_TOOL);
10891
- const sourceProperty = source && schemaProperty(source, REFERENCE_PROPERTY);
10892
- if (typeof sourceProperty?.description !== "string" || sourceProperty.description.length === 0) return void 0;
10893
- return [{
10894
- sourceTool: REFERENCE_SOURCE_TOOL,
10895
- property: REFERENCE_PROPERTY,
10896
- description: sourceProperty.description
10897
- }];
10951
+ if (!source) return void 0;
10952
+ const related = [];
10953
+ for (const property of properties) {
10954
+ const referenced = schemaProperty(tool, property);
10955
+ const sourceProperty = schemaProperty(source, property);
10956
+ if (typeof referenced?.description !== "string" || !referenced.description.includes(REFERENCE_SOURCE_TOOL) || typeof sourceProperty?.description !== "string" || sourceProperty.description.length === 0) return void 0;
10957
+ related.push({
10958
+ sourceTool: REFERENCE_SOURCE_TOOL,
10959
+ property,
10960
+ description: sourceProperty.description
10961
+ });
10962
+ }
10963
+ return related;
10898
10964
  }
10899
10965
  function buildCliUsage(tool, tools) {
10900
10966
  const name = typeof tool.name === "string" ? tool.name : "";
@@ -10919,6 +10985,7 @@ function buildCliUsage(tool, tools) {
10919
10985
  mutuallyExclusiveWith: "--output-json",
10920
10986
  files: {
10921
10987
  resultJson: "result.json is exclusively reserved before execution. It is parseable only after a complete response is saved; existence alone does not prove a response was received, and disconnects or write failures can leave it empty or partial.",
10988
+ resultReadableTxt: "result.readable.txt is a display-only, line-bounded view that expands JSON inside content[].text without truncating content. result.json remains authoritative.",
10922
10989
  diagnosticsLog: "diagnostics.log captures CLI and child-MCP diagnostics while stderr remains live.",
10923
10990
  executionJson: "execution.json records state, toolExecution, business outcome, and the CLI exitCode."
10924
10991
  },
@@ -24893,6 +24960,17 @@ var MARKET_CODE_TO_NAME = {
24893
24960
  sj: "\u4E0A\u91D1\u6240(sj)",
24894
24961
  bs: "\u6E24\u5546(bs)"
24895
24962
  };
24963
+ var LEGACY_MARKET_ID_TO_CODE = {
24964
+ 29544: "sh",
24965
+ 29562: "sz",
24966
+ 31338: "zj",
24967
+ 31347: "zs",
24968
+ 25715: "ds",
24969
+ 29553: "sq",
24970
+ 26481: "gq",
24971
+ 29546: "sj",
24972
+ 25203: "bs"
24973
+ };
24896
24974
  var DC_SDK_CONFIG_SEGMENTS = ["python", "Lib", "site-packages", "zltsdk", "moudles", "dc_sdk.json"];
24897
24975
  var DCM_CONFIG_SEGMENTS = ["zltHost", "moudles", "dcm.json"];
24898
24976
  var dataRootsCache = /* @__PURE__ */ new Map();
@@ -24933,6 +25011,36 @@ function resolveDataRoots(clientRootDir) {
24933
25011
  return status;
24934
25012
  }
24935
25013
  var HQ_DATA_FAMILIES = /* @__PURE__ */ new Set(["dk", "mk", "tk"]);
25014
+ function requestedSeriesKey(series) {
25015
+ return JSON.stringify([series.exchangeId, series.dataType]);
25016
+ }
25017
+ function seriesIdentity(value) {
25018
+ if (!isRecord4(value)) return void 0;
25019
+ const exchangeRaw = value.exchange_id ?? value.exchangeid ?? value.exchange;
25020
+ const legacyExchange = finiteDataRangeNumber(exchangeRaw);
25021
+ const exchangeId = LEGACY_MARKET_ID_TO_CODE[legacyExchange] ?? String(exchangeRaw ?? "").trim().toLowerCase();
25022
+ let dataType = String(value.data_type ?? value.datatype ?? "").trim().toLowerCase();
25023
+ if (/^(dk|mk|tk)$/.test(dataType)) {
25024
+ const securityType = finiteDataRangeNumber(value.securitytype ?? value.sectype);
25025
+ if (securityType > 0 && securityType % 16 === 0) dataType += (securityType / 16).toString(16);
25026
+ }
25027
+ if (!dataType) return void 0;
25028
+ return { exchangeId, dataType };
25029
+ }
25030
+ function requestedSeriesFromParams(value) {
25031
+ if (!Array.isArray(value)) return [];
25032
+ const seen = /* @__PURE__ */ new Set();
25033
+ const series = [];
25034
+ for (const item of value) {
25035
+ const identity = seriesIdentity(item);
25036
+ if (!identity) continue;
25037
+ const key = requestedSeriesKey(identity);
25038
+ if (seen.has(key)) continue;
25039
+ seen.add(key);
25040
+ series.push(identity);
25041
+ }
25042
+ return series;
25043
+ }
24936
25044
  function hqSeriesFromResults(resultItems) {
24937
25045
  const series = [];
24938
25046
  for (const item of resultItems) {
@@ -24947,17 +25055,25 @@ function hqSeriesFromResults(resultItems) {
24947
25055
  }
24948
25056
  function engineHasAnyHqSeries(engineRoot, series) {
24949
25057
  if (!engineRoot || series.length === 0) return void 0;
24950
- for (const { exchangeId, dataType } of series) {
24951
- const dir = path19.join(engineRoot, "hqdata", "his", exchangeId, dataType);
24952
- try {
24953
- if (fs20.existsSync(dir) && fs20.readdirSync(dir).length > 0) return true;
24954
- } catch {
24955
- return void 0;
24956
- }
25058
+ for (const requested of series) {
25059
+ const present = engineHasHqSeries(engineRoot, requested);
25060
+ if (present === true) return true;
25061
+ if (present === void 0) return void 0;
24957
25062
  }
24958
25063
  return false;
24959
25064
  }
24960
- function formatDataRangeResult(rawResult, dataRoots) {
25065
+ function engineHasHqSeries(engineRoot, series) {
25066
+ if (!engineRoot || !series.exchangeId || !HQ_DATA_FAMILIES.has(splitDataTypeToken(series.dataType).family)) {
25067
+ return void 0;
25068
+ }
25069
+ const dir = path19.join(engineRoot, "hqdata", "his", series.exchangeId, series.dataType);
25070
+ try {
25071
+ return fs20.readdirSync(dir).length > 0;
25072
+ } catch (error2) {
25073
+ return isRecord4(error2) && error2.code === "ENOENT" ? false : void 0;
25074
+ }
25075
+ }
25076
+ function formatDataRangeResult(rawResult, dataRoots, requestedParams) {
24961
25077
  if (!isRecord4(rawResult)) return rawResult;
24962
25078
  const nestedStatus = isRecord4(rawResult.Data) ? rawResult.Data.coverageStatus : void 0;
24963
25079
  if (isDataRangeCoverageStatus(nestedStatus) || isDataRangeCoverageStatus(rawResult.coverageStatus)) {
@@ -24968,8 +25084,27 @@ function formatDataRangeResult(rawResult, dataRoots) {
24968
25084
  const resultItems = Array.isArray(data) ? data : isRecord4(data) && Array.isArray(data.results) ? data.results : [];
24969
25085
  const seriesOrder = [];
24970
25086
  const seriesByKey = /* @__PURE__ */ new Map();
25087
+ const returnedSeriesOrder = [];
25088
+ const returnedSeriesByKey = /* @__PURE__ */ new Map();
24971
25089
  for (const item of resultItems) {
24972
25090
  if (!isRecord4(item)) continue;
25091
+ const identity = seriesIdentity(item);
25092
+ let returnedEvidence;
25093
+ if (identity) {
25094
+ const identityKey = requestedSeriesKey(identity);
25095
+ returnedEvidence = returnedSeriesByKey.get(identityKey);
25096
+ if (!returnedEvidence) {
25097
+ returnedEvidence = {
25098
+ ...identity,
25099
+ hasIntervals: false,
25100
+ explicitMissing: hasExplicitMissingMarker(item)
25101
+ };
25102
+ returnedSeriesByKey.set(identityKey, returnedEvidence);
25103
+ returnedSeriesOrder.push(identityKey);
25104
+ } else if (hasExplicitMissingMarker(item)) {
25105
+ returnedEvidence.explicitMissing = true;
25106
+ }
25107
+ }
24973
25108
  const exchangeRaw = item.exchange_id ?? item.exchangeid ?? item.exchange;
24974
25109
  const datatypeRaw = item.data_type ?? item.datatype;
24975
25110
  const sectypeRaw = item.securitytype ?? item.sectype;
@@ -24992,6 +25127,7 @@ function formatDataRangeResult(rawResult, dataRoots) {
24992
25127
  const endDate = compactDataRangeDate(time3.end);
24993
25128
  if (!startDate || !endDate) continue;
24994
25129
  series.intervals.push([startDate, endDate]);
25130
+ if (returnedEvidence) returnedEvidence.hasIntervals = true;
24995
25131
  }
24996
25132
  }
24997
25133
  const ranges = [];
@@ -25003,8 +25139,58 @@ function formatDataRangeResult(rawResult, dataRoots) {
25003
25139
  }
25004
25140
  }
25005
25141
  const errNo = Number(rawResult.ErrNo);
25006
- if (Number.isFinite(errNo) && errNo !== 0) {
25007
- return withDataRangeInterpretation(rawResult, "unknown", true, "upstream-error", [], "report-error");
25142
+ const upstreamFailed = Number.isFinite(errNo) && errNo !== 0;
25143
+ const rootSplit = Boolean(dataRoots?.resolved && !dataRoots.agree);
25144
+ const explicitGlobalMissing = hasExplicitMissingMarker(data);
25145
+ const requestedSeries = requestedSeriesFromParams(requestedParams);
25146
+ const coverageOrder = [...requestedSeries.map(requestedSeriesKey)];
25147
+ const coverageByKey = new Map(requestedSeries.map((series) => [requestedSeriesKey(series), series]));
25148
+ for (const key of returnedSeriesOrder) {
25149
+ if (coverageByKey.has(key)) continue;
25150
+ const returned = returnedSeriesByKey.get(key);
25151
+ if (!returned) continue;
25152
+ coverageByKey.set(key, returned);
25153
+ coverageOrder.push(key);
25154
+ }
25155
+ const seriesCoverage = coverageOrder.map((key) => {
25156
+ const series = coverageByKey.get(key);
25157
+ const returned = returnedSeriesByKey.get(key);
25158
+ const identity = {
25159
+ ...series.exchangeId ? { exchange_id: series.exchangeId } : {},
25160
+ data_type: series.dataType
25161
+ };
25162
+ if (upstreamFailed) {
25163
+ return { ...identity, coverageStatus: "unknown", blocksBacktest: true, reasonCode: "upstream-error", nextAction: "report-error" };
25164
+ }
25165
+ if (rootSplit) {
25166
+ return { ...identity, coverageStatus: "unknown", blocksBacktest: true, reasonCode: "data-root-split", nextAction: "resolve-data-root-split" };
25167
+ }
25168
+ if (!returned) {
25169
+ if (explicitGlobalMissing) {
25170
+ return { ...identity, coverageStatus: "unknown", blocksBacktest: true, reasonCode: "explicit-missing", nextAction: "report-missing-data" };
25171
+ }
25172
+ return { ...identity, coverageStatus: "unknown", blocksBacktest: false, reasonCode: "upstream-omitted", nextAction: "recheck-coverage" };
25173
+ }
25174
+ if (returned.explicitMissing) {
25175
+ return { ...identity, coverageStatus: "missing", blocksBacktest: true, reasonCode: "explicit-missing", nextAction: "report-missing-data" };
25176
+ }
25177
+ if (explicitGlobalMissing) {
25178
+ return { ...identity, coverageStatus: "unknown", blocksBacktest: true, reasonCode: "explicit-missing", nextAction: "report-missing-data" };
25179
+ }
25180
+ if (returned.hasIntervals) {
25181
+ return { ...identity, coverageStatus: "available", blocksBacktest: false, reasonCode: "explicit-ranges", nextAction: "run-backtest" };
25182
+ }
25183
+ const family = splitDataTypeToken(series.dataType).family;
25184
+ if (dataRoots?.resolved && dataRoots.agree && HQ_DATA_FAMILIES.has(family)) {
25185
+ const present = engineHasHqSeries(dataRoots.engineRoot, series);
25186
+ if (present === false) {
25187
+ return { ...identity, coverageStatus: "missing", blocksBacktest: true, reasonCode: "no-local-files", nextAction: "download-and-recheck" };
25188
+ }
25189
+ }
25190
+ return { ...identity, coverageStatus: "unknown", blocksBacktest: false, reasonCode: "no-local-coverage", nextAction: "run-backtest" };
25191
+ });
25192
+ if (upstreamFailed) {
25193
+ return withDataRangeInterpretation(rawResult, "unknown", true, "upstream-error", [], "report-error", seriesCoverage);
25008
25194
  }
25009
25195
  if (dataRoots?.resolved && !dataRoots.agree) {
25010
25196
  return withDataRangeInterpretation(
@@ -25013,15 +25199,44 @@ function formatDataRangeResult(rawResult, dataRoots) {
25013
25199
  true,
25014
25200
  "data-root-split",
25015
25201
  ranges,
25016
- `\u6570\u636E\u6839\u4E0D\u4E00\u81F4\uFF1A\u4E0B\u8F7D\u5668\u5199\u5165 ${dataRoots.downloadRoot}\uFF0C\u800C\u7B56\u7565\u5F15\u64CE\u8BFB\u53D6 ${dataRoots.engineRoot}\u3002\u672C\u5730\u6587\u4EF6\u5373\u4F7F\u5B58\u5728\uFF0C\u56DE\u6D4B\u4E5F\u8BFB\u4E0D\u5230\uFF0C\u4F1A\u62A5\u300C\u672C\u5730\u6570\u636E\u4E0D\u8DB3\u300D\u3002\u4E0D\u8981\u636E\u6B64\u5F00\u59CB\u56DE\u6D4B\uFF1B\u8BF7\u5411\u7528\u6237\u62A5\u544A\u8FD9\u4E24\u4E2A\u8DEF\u5F84\uFF0C\u5E76\u8BF4\u660E\u9700\u8981\u4FEE\u590D\u6295\u7814\u6A21\u5757\u7684\u6570\u636E\u6839\u914D\u7F6E\uFF08dc_sdk.json \u7684 mdf.path \u4E0E dcm.json \u7684 download_home\uFF09\u3002`
25202
+ `\u6570\u636E\u6839\u4E0D\u4E00\u81F4\uFF1A\u4E0B\u8F7D\u5668\u5199\u5165 ${dataRoots.downloadRoot}\uFF0C\u800C\u7B56\u7565\u5F15\u64CE\u8BFB\u53D6 ${dataRoots.engineRoot}\u3002\u672C\u5730\u6587\u4EF6\u5373\u4F7F\u5B58\u5728\uFF0C\u56DE\u6D4B\u4E5F\u8BFB\u4E0D\u5230\uFF0C\u4F1A\u62A5\u300C\u672C\u5730\u6570\u636E\u4E0D\u8DB3\u300D\u3002\u4E0D\u8981\u636E\u6B64\u5F00\u59CB\u56DE\u6D4B\uFF1B\u8BF7\u5411\u7528\u6237\u62A5\u544A\u8FD9\u4E24\u4E2A\u8DEF\u5F84\uFF0C\u5E76\u8BF4\u660E\u9700\u8981\u4FEE\u590D\u6295\u7814\u6A21\u5757\u7684\u6570\u636E\u6839\u914D\u7F6E\uFF08dc_sdk.json \u7684 mdf.path \u4E0E dcm.json \u7684 download_home\uFF09\u3002`,
25203
+ seriesCoverage
25204
+ );
25205
+ }
25206
+ if (explicitGlobalMissing) {
25207
+ return withDataRangeInterpretation(rawResult, "missing", true, "explicit-missing", ranges, "report-missing-data", seriesCoverage);
25208
+ }
25209
+ const missingSeries = seriesCoverage.filter((series) => series.coverageStatus === "missing");
25210
+ if (missingSeries.length > 0) {
25211
+ const wanted = missingSeries.map((series) => `${series.exchange_id ? `${series.exchange_id}/` : ""}${series.data_type}`).join("\u3001");
25212
+ const { Data: _rawData, ...envelope } = rawResult;
25213
+ const source = ranges.length > 0 ? envelope : rawResult;
25214
+ return withDataRangeInterpretation(
25215
+ source,
25216
+ "missing",
25217
+ true,
25218
+ missingSeries.every((series) => series.reasonCode === "no-local-files") ? "no-local-files" : "series-missing",
25219
+ ranges,
25220
+ `\u672C\u5730\u7F3A\u5C11 ${wanted}\u3002\u5148\u7528 mcp-download-data \u4E0B\u8F7D\uFF0C\u518D\u7528 mcp-wait kind=download \u7B49\u5F85\u7EC8\u6001\uFF0C\u7136\u540E\u91CD\u65B0\u67E5\u8BE2\u8986\u76D6\u3002`,
25221
+ seriesCoverage
25017
25222
  );
25018
25223
  }
25019
- if (hasExplicitMissingMarker(data)) {
25020
- return withDataRangeInterpretation(rawResult, "missing", true, "explicit-missing", ranges, "report-missing-data");
25224
+ const incompleteSeries = seriesCoverage.some((series) => series.coverageStatus === "unknown");
25225
+ if (ranges.length > 0 && incompleteSeries) {
25226
+ const { Data: _rawData, ...withoutRawData } = rawResult;
25227
+ return withDataRangeInterpretation(
25228
+ withoutRawData,
25229
+ "unknown",
25230
+ false,
25231
+ "incomplete-series-coverage",
25232
+ ranges,
25233
+ "recheck-coverage",
25234
+ seriesCoverage
25235
+ );
25021
25236
  }
25022
25237
  if (ranges.length > 0) {
25023
25238
  const { Data: _rawData, ...withoutRawData } = rawResult;
25024
- return withDataRangeInterpretation(withoutRawData, "available", false, "explicit-ranges", ranges, "run-backtest");
25239
+ return withDataRangeInterpretation(withoutRawData, "available", false, "explicit-ranges", ranges, "run-backtest", seriesCoverage);
25025
25240
  }
25026
25241
  const reasonCode = isEmptyRecord(data) || hasResultCollection && resultItems.length === 0 ? "empty-summary" : hasResultCollection && resultItems.length > 0 ? "no-local-coverage" : "unrecognized-shape";
25027
25242
  if (reasonCode === "no-local-coverage" && dataRoots?.resolved) {
@@ -25034,11 +25249,13 @@ function formatDataRangeResult(rawResult, dataRoots) {
25034
25249
  true,
25035
25250
  "no-local-files",
25036
25251
  [],
25037
- `\u7B56\u7565\u5F15\u64CE\u7684\u6570\u636E\u6839 ${dataRoots.engineRoot} \u4E0B\u6CA1\u6709 ${wanted} \u7684\u4EFB\u4F55\u6587\u4EF6\uFF0C\u672C\u5730\u786E\u5B9E\u6CA1\u6709\u8FD9\u6279\u884C\u60C5\u3002\u5148\u7528 mcp-download-data \u4E0B\u8F7D\uFF0C\u7528 mcp-get-download-progress \u8F6E\u8BE2\u5230\u7EC8\u6001\uFF0C\u518D\u91CD\u65B0\u67E5\u8986\u76D6\uFF1B\u4E0D\u8981\u76F4\u63A5\u5F00\u59CB\u56DE\u6D4B\u3002`
25252
+ `\u7B56\u7565\u5F15\u64CE\u7684\u6570\u636E\u6839 ${dataRoots.engineRoot} \u4E0B\u6CA1\u6709 ${wanted} \u7684\u4EFB\u4F55\u6587\u4EF6\uFF0C\u672C\u5730\u786E\u5B9E\u6CA1\u6709\u8FD9\u6279\u884C\u60C5\u3002\u5148\u7528 mcp-download-data \u4E0B\u8F7D\uFF0C\u518D\u7528 mcp-wait kind=download \u7B49\u5F85\u7EC8\u6001\uFF0C\u7136\u540E\u91CD\u65B0\u67E5\u8986\u76D6\uFF1B\u4E0D\u8981\u76F4\u63A5\u5F00\u59CB\u56DE\u6D4B\u3002`,
25253
+ seriesCoverage
25038
25254
  );
25039
25255
  }
25040
25256
  }
25041
- return withDataRangeInterpretation(rawResult, "unknown", false, reasonCode, [], "run-backtest");
25257
+ const finalReasonCode = requestedSeries.length > 0 && seriesCoverage.some((series) => series.reasonCode === "upstream-omitted") ? "incomplete-series-coverage" : reasonCode;
25258
+ return withDataRangeInterpretation(rawResult, "unknown", false, finalReasonCode, [], "run-backtest", seriesCoverage);
25042
25259
  }
25043
25260
  function unionDataRangeIntervals(intervals) {
25044
25261
  const sorted = [...intervals].sort((a, b) => a[0] === b[0] ? a[1].localeCompare(b[1]) : a[0].localeCompare(b[0]));
@@ -25053,9 +25270,9 @@ function unionDataRangeIntervals(intervals) {
25053
25270
  }
25054
25271
  return merged;
25055
25272
  }
25056
- function withDataRangeInterpretation(rawResult, coverageStatus, blocksBacktest, reasonCode, ranges, nextAction) {
25273
+ function withDataRangeInterpretation(rawResult, coverageStatus, blocksBacktest, reasonCode, ranges, nextAction, seriesCoverage) {
25057
25274
  const { Data: upstreamData, ...envelope } = rawResult;
25058
- const data = { coverageStatus, blocksBacktest, reasonCode, nextAction, ranges };
25275
+ const data = { coverageStatus, blocksBacktest, reasonCode, nextAction, ranges, seriesCoverage };
25059
25276
  if (isRecord4(upstreamData)) {
25060
25277
  for (const [key, value] of Object.entries(upstreamData)) {
25061
25278
  if (!(key in data)) data[key] = value;
@@ -25358,7 +25575,7 @@ function compactDataRangeDate(value) {
25358
25575
 
25359
25576
  // src/tool-response.ts
25360
25577
  var identityRepairHint = (data) => data;
25361
- function handleIpcResponse(response, tool, repairHint = identityRepairHint, dataRoots) {
25578
+ function handleIpcResponse(response, tool, repairHint = identityRepairHint, dataRoots, requestedDataRanges) {
25362
25579
  const parsed = parseIpcRawResponse(response);
25363
25580
  if (!parsed.isSuccess) {
25364
25581
  return {
@@ -25367,18 +25584,18 @@ function handleIpcResponse(response, tool, repairHint = identityRepairHint, data
25367
25584
  };
25368
25585
  }
25369
25586
  const data = repairHint(parsed.data);
25370
- const text2 = typeof data === "string" ? applyResponseTransform(data, tool, dataRoots) : applyResponseTransform(JSON.stringify(data), tool, dataRoots);
25587
+ const text2 = typeof data === "string" ? applyResponseTransform(data, tool, dataRoots, requestedDataRanges) : applyResponseTransform(JSON.stringify(data), tool, dataRoots, requestedDataRanges);
25371
25588
  return {
25372
25589
  content: [{ type: "text", text: text2 }],
25373
25590
  isError: isIpcBusinessError(data) || void 0
25374
25591
  };
25375
25592
  }
25376
- function applyResponseTransform(text2, tool, dataRoots) {
25593
+ function applyResponseTransform(text2, tool, dataRoots, requestedDataRanges) {
25377
25594
  if (!tool?.responseTransform || !text2) return text2;
25378
25595
  try {
25379
25596
  const parsed = JSON.parse(text2);
25380
25597
  if (tool.responseTransform === "dataRange") {
25381
- return JSON.stringify(formatDataRangeResult(parsed, dataRoots));
25598
+ return JSON.stringify(formatDataRangeResult(parsed, dataRoots, requestedDataRanges));
25382
25599
  }
25383
25600
  if (tool.responseTransform === "downloadProgress") {
25384
25601
  return JSON.stringify(formatDownloadProgressResult(parsed));
@@ -28537,7 +28754,7 @@ async function startMcpServer() {
28537
28754
  }
28538
28755
  try {
28539
28756
  const response = await ipcClient.invoke(ipcUri, params, body, timeout);
28540
- const result = shapeToolResult(tool, toolArgs, handleIpcResponse(response, tool, withRuntimeRepairHint, dataRoots), session);
28757
+ const result = shapeToolResult(tool, toolArgs, handleIpcResponse(response, tool, withRuntimeRepairHint, dataRoots, toolArgs.params), session);
28541
28758
  if (name === "mcp-download-data") recordActiveDownload(result);
28542
28759
  return result;
28543
28760
  } catch (err) {
@@ -28545,7 +28762,7 @@ async function startMcpServer() {
28545
28762
  if (reconnectedClient) {
28546
28763
  try {
28547
28764
  const response = await reconnectedClient.invoke(ipcUri, params, body, timeout);
28548
- const result = shapeToolResult(tool, toolArgs, handleIpcResponse(response, tool, withRuntimeRepairHint, dataRoots), session);
28765
+ const result = shapeToolResult(tool, toolArgs, handleIpcResponse(response, tool, withRuntimeRepairHint, dataRoots, toolArgs.params), session);
28549
28766
  if (name === "mcp-download-data") recordActiveDownload(result);
28550
28767
  return result;
28551
28768
  } catch (retryErr) {
@@ -29220,6 +29437,7 @@ var path25 = __toESM(require("node:path"), 1);
29220
29437
  var import_node_crypto5 = require("node:crypto");
29221
29438
  var SOURCE = "zlt-mcp-device-login";
29222
29439
  var IGNORED_API_ENV = /* @__PURE__ */ new Set(["ZLTQUANT_API_KEY", "ZLTQUANT_API_BASE"]);
29440
+ var SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
29223
29441
  function authCliEnv(env) {
29224
29442
  return Object.fromEntries(
29225
29443
  Object.entries(env).filter(([key]) => !IGNORED_API_ENV.has(key.toUpperCase()))
@@ -29289,9 +29507,12 @@ function authErrorMessage(error2) {
29289
29507
  return "Authentication operation failed; check local credential configuration and storage permissions. No credential was returned.";
29290
29508
  }
29291
29509
  function sessionPath(id, e = process.env) {
29292
- if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id)) throw Error("Invalid authentication session");
29510
+ if (!SESSION_ID.test(id)) throw Error("Invalid authentication session");
29293
29511
  return path25.join(root(e), id + ".json");
29294
29512
  }
29513
+ function validateAuthSessionId(id) {
29514
+ if (!SESSION_ID.test(id)) throw Error("Invalid authentication session");
29515
+ }
29295
29516
  function drop(id, e = process.env) {
29296
29517
  fs26.rmSync(sessionPath(id, e), { force: true });
29297
29518
  }
@@ -29299,7 +29520,7 @@ function preflight(e) {
29299
29520
  if (e.ZLTQUANT_CREDENTIALS || readCredentials(e))
29300
29521
  throw Error("Authentication is already configured");
29301
29522
  }
29302
- async function finish(s, e, c, json) {
29523
+ async function finish(s, e, c, json, emit = true) {
29303
29524
  const grant = await c.poll(s, {
29304
29525
  maxDurationMs: s.expiresIn * 1e3
29305
29526
  });
@@ -29314,7 +29535,7 @@ async function finish(s, e, c, json) {
29314
29535
  apiKeyMask: a.apiKeyMask,
29315
29536
  apiBase: a.apiBase
29316
29537
  };
29317
- process.stdout.write(json ? JSON.stringify(o) + "\n" : `Signed in (${a.apiKeyMask}).
29538
+ if (emit) process.stdout.write(json ? JSON.stringify(o) + "\n" : `Signed in (${a.apiKeyMask}).
29318
29539
  `);
29319
29540
  return o;
29320
29541
  }
@@ -29332,10 +29553,10 @@ async function runAuthLogin(o = {}) {
29332
29553
  expiresIn: s.expiresIn,
29333
29554
  widget: buildShowWidgetPayload(s)
29334
29555
  };
29335
- process.stdout.write(JSON.stringify(out) + "\n");
29556
+ if (o.emit !== false) process.stdout.write(JSON.stringify(out) + "\n");
29336
29557
  return out;
29337
29558
  }
29338
- process.stdout.write(
29559
+ if (o.emit !== false) process.stdout.write(
29339
29560
  o.json ? JSON.stringify({
29340
29561
  status: "pending",
29341
29562
  verificationUri: s.verificationUri,
@@ -29343,7 +29564,7 @@ async function runAuthLogin(o = {}) {
29343
29564
  }) + "\n" : `Open ${s.verificationUri} and enter code ${s.userCode}.
29344
29565
  `
29345
29566
  );
29346
- return finish(s, e, c, o.json);
29567
+ return finish(s, e, c, o.json, o.emit !== false);
29347
29568
  }
29348
29569
  async function runAuthWait(id, o = {}) {
29349
29570
  const e = authCliEnv(o.env || process.env);
@@ -29357,7 +29578,7 @@ async function runAuthWait(id, o = {}) {
29357
29578
  }
29358
29579
  try {
29359
29580
  const x = load(id, e);
29360
- const result = await finish(x.authorization, e, o.client || client, o.json);
29581
+ const result = await finish(x.authorization, e, o.client || client, o.json, o.emit !== false);
29361
29582
  drop(id, e);
29362
29583
  return result;
29363
29584
  } catch (error2) {
@@ -29401,7 +29622,10 @@ if (process.argv.length <= 2) {
29401
29622
  let evidenceSetupError;
29402
29623
  try {
29403
29624
  const directory = evidenceDirectoryArg(process.argv.slice(2));
29404
- if (directory !== void 0) evidence = new CliEvidence(directory, resolveCliPackageProvenance(), process.argv[2]);
29625
+ if (directory !== void 0) {
29626
+ const command = process.argv[2] === "auth" ? `auth ${process.argv[3] ?? ""}`.trim() : process.argv[2];
29627
+ evidence = new CliEvidence(directory, resolveCliPackageProvenance(), command);
29628
+ }
29405
29629
  } catch (error2) {
29406
29630
  evidenceSetupError = error2;
29407
29631
  }
@@ -29422,34 +29646,127 @@ if (process.argv.length <= 2) {
29422
29646
  });
29423
29647
  program2.name(CLI_NAME).description("MCP proxy for ZLTHost IPC service").version(resolveCliVersion()).configureOutput({ outputError: writeCliArgumentError }).addHelpText("after", "\nDiscovery: list for names; list --tool <name> --json for one complete input schema.\nUse call --help for input, output and diagnostic handling.\nFor npx registry selection, put --registry before the package name; it is not a zlt-mcp option.");
29424
29648
  const auth = program2.command("auth").description("Manage local ZLT Quant authentication");
29425
- auth.command("login").description("Sign in with a device code").option("--json", "Emit a JSON descriptor and status").option("--widget", "Return a login card immediately; render it before running auth wait").addHelpText("after", "\nAgent flow: auth login --widget --json returns a local session ID and widget descriptor.\nInvoke the host show_widget tool with widget.arguments, or show the login URL/code.\nThen run auth wait --session <returned-id> --json. The descriptor alone does not render a widget.\nWithout --widget this command waits in the foreground. Never paste keys or private session files into chat.").action(async (options) => {
29649
+ auth.command("login").description("Sign in with a device code").option("--json", "Emit a JSON descriptor and status").option("--widget", "Return a login card immediately; render it before running auth wait").option("--evidence-dir <path>", "Create a new directory for the display-safe login descriptor and execution status; requires --widget").addHelpText("after", "\nAgent flow: auth login --widget --json returns a local session ID and widget descriptor.\nInvoke the host show_widget tool with widget.arguments, or show the login URL/code.\nThen run auth wait --session <returned-id> --json. The descriptor alone does not render a widget.\nWithout --widget this command waits in the foreground. Never paste keys or private session files into chat.").action(async (options) => {
29650
+ let toolExecution = "not_started";
29651
+ let stage = "arguments";
29652
+ let response;
29426
29653
  try {
29427
- await runAuthLogin(options);
29654
+ if (evidence && !options.widget) throw new Error("--evidence-dir requires --widget for auth login");
29655
+ evidence?.started();
29656
+ toolExecution = evidence ? "unknown" : "not_started";
29657
+ stage = "auth_login";
29658
+ const result = await runAuthLogin({ ...options, emit: !evidence });
29659
+ response = result;
29660
+ if (!evidence) return;
29661
+ toolExecution = "response_received";
29662
+ stage = "output_write";
29663
+ evidence.writeResult(result);
29664
+ stage = "output_emit";
29665
+ evidence.completed("not_reported", 0);
29666
+ process.stdout.write(JSON.stringify({
29667
+ ok: true,
29668
+ command: "auth login",
29669
+ resultFile: evidence.resultFile,
29670
+ cliProvenance: resolveCliPackageProvenance(),
29671
+ ...evidence.receiptFields()
29672
+ }) + "\n");
29428
29673
  } catch (error2) {
29429
- process.stderr.write(`Error: ${authErrorMessage(error2)}
29674
+ if (toolExecution === "response_received" && response !== void 0) {
29675
+ process.stdout.write(JSON.stringify(response) + "\n");
29676
+ }
29677
+ const message = error2 instanceof Error && error2.message === "--evidence-dir requires --widget for auth login" ? error2.message : authErrorMessage(error2);
29678
+ process.stderr.write(`Error: ${message}
29430
29679
  `);
29431
29680
  process.exitCode = 1;
29681
+ try {
29682
+ evidence?.failed(stage, toolExecution);
29683
+ } catch (saveError) {
29684
+ process.stderr.write(`Error: execution evidence could not be finalized: ${String(saveError)}
29685
+ `);
29686
+ }
29432
29687
  }
29433
29688
  });
29434
- auth.command("status").description("Show masked local authentication status").option("--json", "Emit JSON").action((options) => {
29689
+ auth.command("status").description("Show masked local authentication status").option("--json", "Emit JSON").option("--evidence-dir <path>", "Create a new directory for the masked status and execution state").action((options) => {
29690
+ let toolExecution = "not_started";
29691
+ let stage = "status_query";
29692
+ let response;
29435
29693
  try {
29694
+ evidence?.started();
29695
+ toolExecution = evidence ? "unknown" : "not_started";
29436
29696
  const status = authStatus();
29437
- process.stdout.write(options.json ? `${JSON.stringify(status)}
29697
+ response = status;
29698
+ if (!evidence) {
29699
+ process.stdout.write(options.json ? `${JSON.stringify(status)}
29438
29700
  ` : `${status.configured ? `Configured (${status.apiKeyMask})` : "Not configured"}
29439
29701
  `);
29702
+ return;
29703
+ }
29704
+ toolExecution = "response_received";
29705
+ stage = "output_write";
29706
+ evidence.writeResult(status);
29707
+ stage = "output_emit";
29708
+ evidence.completed("not_reported", 0);
29709
+ process.stdout.write(JSON.stringify({
29710
+ ok: true,
29711
+ command: "auth status",
29712
+ resultFile: evidence.resultFile,
29713
+ cliProvenance: resolveCliPackageProvenance(),
29714
+ ...evidence.receiptFields()
29715
+ }) + "\n");
29440
29716
  } catch (error2) {
29717
+ if (toolExecution === "response_received" && response !== void 0) {
29718
+ process.stdout.write(JSON.stringify(response) + "\n");
29719
+ }
29441
29720
  process.stderr.write(`Error: ${authErrorMessage(error2)}
29442
29721
  `);
29443
29722
  process.exitCode = 1;
29723
+ try {
29724
+ evidence?.failed(stage, toolExecution);
29725
+ } catch (saveError) {
29726
+ process.stderr.write(`Error: execution evidence could not be finalized: ${String(saveError)}
29727
+ `);
29728
+ }
29444
29729
  }
29445
29730
  });
29446
- auth.command("wait").description("Complete a saved device-code sign in").requiredOption("--session <id>", "Opaque local session ID from auth login --widget").option("--json", "Emit JSON").action(async (options) => {
29731
+ auth.command("wait").description("Complete a saved device-code sign in").requiredOption("--session <id>", "Opaque local session ID from auth login --widget").option("--json", "Emit JSON").option("--evidence-dir <path>", "Create a new directory for the display-safe wait receipt and execution state").action(async (options) => {
29732
+ let toolExecution = "not_started";
29733
+ let stage = "arguments";
29734
+ let business = "not_reported";
29735
+ let response;
29447
29736
  try {
29448
- await runAuthWait(options.session, options);
29737
+ if (evidence) validateAuthSessionId(options.session);
29738
+ evidence?.started();
29739
+ toolExecution = evidence ? "unknown" : "not_started";
29740
+ stage = "auth_wait";
29741
+ const result = await runAuthWait(options.session, { ...options, emit: !evidence });
29742
+ response = result;
29743
+ if (!evidence) return;
29744
+ toolExecution = "response_received";
29745
+ business = "success";
29746
+ stage = "output_write";
29747
+ evidence.writeResult(result);
29748
+ stage = "output_emit";
29749
+ evidence.completed("success", 0);
29750
+ process.stdout.write(JSON.stringify({
29751
+ ok: true,
29752
+ command: "auth wait",
29753
+ resultFile: evidence.resultFile,
29754
+ cliProvenance: resolveCliPackageProvenance(),
29755
+ ...evidence.receiptFields()
29756
+ }) + "\n");
29449
29757
  } catch (error2) {
29758
+ if (toolExecution === "response_received" && response !== void 0) {
29759
+ process.stdout.write(JSON.stringify(response) + "\n");
29760
+ }
29450
29761
  process.stderr.write(`Error: ${authErrorMessage(error2)}
29451
29762
  `);
29452
29763
  process.exitCode = 1;
29764
+ try {
29765
+ evidence?.failed(stage, toolExecution, business);
29766
+ } catch (saveError) {
29767
+ process.stderr.write(`Error: execution evidence could not be finalized: ${String(saveError)}
29768
+ `);
29769
+ }
29453
29770
  }
29454
29771
  });
29455
29772
  auth.command("logout").description("Remove only CLI-login-owned credentials locally; does not revoke the gateway key").option("--json", "Emit JSON").action((options) => {
@@ -29504,9 +29821,13 @@ if (process.argv.length <= 2) {
29504
29821
  const fd = outputFd;
29505
29822
  outputFd = void 0;
29506
29823
  fs27.closeSync(fd);
29824
+ if (evidence) {
29825
+ stage = "readable_write";
29826
+ evidence.writeReadable(complete);
29827
+ }
29507
29828
  } catch (error2) {
29508
29829
  process.stdout.write(serialized);
29509
- throw new Error(`Tool returned a result, but saving ${resultFile} failed; complete result is on stdout. Do not repeat the tool automatically. ${String(error2)}`);
29830
+ throw new Error(`Tool returned a result, but saving its evidence failed; complete result is on stdout and any saved result.json remains authoritative. Do not repeat the tool automatically. ${String(error2)}`);
29510
29831
  }
29511
29832
  stage = "output_emit";
29512
29833
  evidence?.completed(business, exitCode);
@@ -29563,6 +29884,7 @@ if (process.argv.length <= 2) {
29563
29884
  });
29564
29885
  program2.command("list").description("List MCP tools exposed by the bundled server").option("--json", "Emit the complete tools/list response as JSON").option("--tool <name>", "Select one exact tool name; combine with --json for its complete schema").option("--evidence-dir <path>", "Create a new directory for discovery, diagnostics and execution status").addHelpText("after", "\nReuse discovery within a task. Query one schema with list --tool <name> --json.\nUnknown names fail with exit code 1. Preserve stderr separately from JSON stdout.").action(async (options) => {
29565
29886
  let toolExecution = "not_started";
29887
+ let stage = "discovery";
29566
29888
  try {
29567
29889
  evidence?.started();
29568
29890
  toolExecution = "unknown";
@@ -29577,7 +29899,8 @@ if (process.argv.length <= 2) {
29577
29899
  const complete = withCliProvenance(result);
29578
29900
  const serialized = JSON.stringify(complete) + "\n";
29579
29901
  try {
29580
- fs27.writeFileSync(evidence.resultFile, serialized, { encoding: "utf8", flag: "wx" });
29902
+ stage = "output_write";
29903
+ evidence.writeResult(complete);
29581
29904
  evidence.completed("not_reported", 0);
29582
29905
  } catch (error2) {
29583
29906
  process.stdout.write(serialized);
@@ -29609,7 +29932,7 @@ if (process.argv.length <= 2) {
29609
29932
  `);
29610
29933
  process.exitCode = 1;
29611
29934
  try {
29612
- evidence?.failed("discovery", toolExecution);
29935
+ evidence?.failed(stage, toolExecution);
29613
29936
  } catch (saveError) {
29614
29937
  process.stderr.write(`Error: execution evidence could not be finalized: ${String(saveError)}
29615
29938
  `);