offerguard-mcp-server 0.3.0 → 0.4.0

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/README.md CHANGED
@@ -1,102 +1,118 @@
1
- # OfferGuard MCP Server
1
+ # OfferGuard MCP 0.4.0 复赛候选版
2
2
 
3
- OfferGuard MCP Server provides real MCP tools for internship offer risk auditing, compliance checks, seven-dimension scoring, and workflow routing.
3
+ 面向大学生实习选择的证据审计工具。使用官方 MCP SDK,支持 Streamable HTTP、旧式 SSE stdio。
4
4
 
5
- ## Tools
5
+ **当前是本地候选版。2026-09-09 已读取线上草稿并修改四个节点和 MCP 输入引用,但线上验收未通过:真实 MCP 调用返回 `MCP database operation failed`。本地0.4.0尚未确认部署,不能把本地通过写成平台上线完成。** 进度与备份范围见 `work/online-edit-20260909.md`。
6
6
 
7
- - `offerguard_audit`: all-in-one workflow tool for XFYun Agent workflow nodes. Input: `AGENT_USER_INPUT`.
8
- - `risk_term_matcher`: matches risky JD terms and returns evidence.
9
- - `internship_compliance_checker`: checks internship red lines such as unpaid trials, deposits, training loans, work hours, salary payment, and IP ownership.
10
- - `offer_score_calculator`: calculates seven-dimension scores, overall score, risk level, and route.
7
+ ## 本地复现
11
8
 
12
- ## Recommended XFYun Workflow Usage
9
+ 环境:Node.js >=20,npm;依赖版本由 package-lock.json 锁定。
13
10
 
14
- For XFYun workflow MCP nodes, prefer the single entry tool:
11
+ ```powershell
12
+ npm.cmd ci
13
+ npm.cmd test
14
+ npm.cmd run validate
15
+ ```
16
+
17
+ Linux/macOS 使用 `npm` 替代 `npm.cmd`。在 Windows PowerShell 运行 Windows npm,不在 WSL 中直接执行 `.cmd` 文件。
18
+
19
+ 本地启动 HTTP/SSE:
20
+
21
+ ```powershell
22
+ npm.cmd start
23
+ ```
24
+
25
+ 默认仅监听 `127.0.0.1:3000`。端点:`GET /health`、`/mcp`、`/sse`、`/messages`。本地地址不是星辰云端可访问的服务地址。
26
+
27
+ 本地 stdio 客户端配置:
15
28
 
16
29
  ```json
17
30
  {
18
- "tool": "offerguard_audit",
19
- "arguments": {
20
- "AGENT_USER_INPUT": "{{开始.AGENT_USER_INPUT}}"
31
+ "mcpServers": {
32
+ "OfferGuard": {
33
+ "command": "node",
34
+ "args": ["ABSOLUTE_PROJECT_PATH/src/stdio.js"]
35
+ }
21
36
  }
22
37
  }
23
38
  ```
24
39
 
25
- The tool returns structured JSON with these key fields:
26
-
27
- - `mcp_ok`
28
- - `route`
29
- - `risk_level`
30
- - `overall_score`
31
- - `route_marker`
32
- - `route_real_marker`
33
- - `risk_terms`
34
- - `compliance_red_lines`
35
- - `compliance_missing_items`
36
- - `dimensions`
37
- - `required_followup_questions`
38
- - `negotiation_cards`
39
- - `accept_conditions`
40
- - `mcp_audit_summary`
41
-
42
- If the workflow brancher needs a text marker, read either:
43
-
44
- - `ROUTE: reject`
45
- - `ROUTE_REAL: compare`
46
- - `ROUTE_REAL: negotiate`
47
-
48
- ## Local Run
49
-
50
- ```bash
51
- npm install
52
- npm test
53
- npm start
54
- ```
40
+ 0.4.0 是否已发布到 npm、是否被星辰托管,必须另行核验。不要仅凭本地版本号改线上启动命令。
55
41
 
56
- Streamable HTTP endpoint:
42
+ ## 四个真实工具
57
43
 
58
- ```text
59
- http://127.0.0.1:3000/mcp
60
- ```
44
+ | 工具 | 用途 |
45
+ | --- | --- |
46
+ | `offerguard_audit` | 独立来源审计、七维评分、追问、对比、状态更新和报告 |
47
+ | `risk_term_matcher` | 返回风险词及肯定、否定、未知、冲突的原文证据 |
48
+ | `internship_compliance_checker` | 收费、无薪等风险信号与关键信息缺口,不作法律定性 |
49
+ | `offer_score_calculator` | 从来源文本重算评分,不信任调用者传入的评分或风险摘要 |
61
50
 
62
- Legacy SSE endpoints:
51
+ ## 推荐输入
63
52
 
64
- ```text
65
- http://127.0.0.1:3000/sse
66
- http://127.0.0.1:3000/messages
53
+ ```json
54
+ {
55
+ "structured_input": {
56
+ "jd_text": "岗位原文,不包含学生偏好",
57
+ "student_profile": "专业、经历、薪资底线和到岗安排",
58
+ "hr_reply": "可选的 HR 补充原文"
59
+ },
60
+ "mode": "single_check"
61
+ }
67
62
  ```
68
63
 
69
- Health check:
64
+ `raw_input` 继续支持带标签的原始文本,例如 `JD:...\n学生:...\nHR回复:...`。无标签且无法可靠归属的文本保留为 `unattributed`,先追问,不自动认定企业事实。自然语言覆盖是有限的,应由工作流先分离原文来源。
70
65
 
71
- ```text
72
- http://127.0.0.1:3000/health
66
+ 如果星辰节点只能绑定一个字符串,用 `raw_input` 传 JSON 字符串:
67
+
68
+ ```json
69
+ {
70
+ "offerguard_schema": 1,
71
+ "user_message": "本轮原话",
72
+ "structured_input": {
73
+ "jd_text": "本轮提供的岗位原文",
74
+ "student_profile": "本轮提供的学生原文",
75
+ "hr_reply": "本轮提供的 HR 原文"
76
+ }
77
+ }
73
78
  ```
74
79
 
75
- ## NPM Usage
80
+ 同一输入不要同时放入重复的改写文案。各字段只填本轮实际来源,缺失字段省略,不填“无”代替未知。
76
81
 
77
- ```bash
78
- npx -y offerguard-mcp-server@0.3.0
79
- ```
82
+ ## 多轮与模式
80
83
 
81
- For MCP stdio clients:
84
+ 把工具返回的 `next_state` 原样保存到当前用户、当前会话、当前岗位的变量中。下一轮作为 `previous_state` 传回工具。服务自身不保存学生数据,也不会自动记住上一轮。
82
85
 
83
86
  ```json
84
87
  {
85
- "mcpServers": {
86
- "OfferGuard": {
87
- "command": "npx",
88
- "args": ["-y", "offerguard-mcp-server@0.3.0"]
89
- }
88
+ "raw_input": "HR回复:日薪180元/天,每月15日发放。",
89
+ "previous_state": {
90
+ "schema_version": 1,
91
+ "records": []
90
92
  }
91
93
  }
92
94
  ```
93
95
 
94
- ## Test Coverage
96
+ 上面的空数组仅说明格式,实际使用前一次 `next_state.records`。`changes` 展示状态或值的变化;矛盾原文同时保留,综合分暂停。确认矛盾后,目前需以已核实的完整资料开启新评估,不能静默删除旧证据。更换岗位传 `new_assessment: true`。
97
+
98
+ `mode` 可选:`single_check`、`negotiation`、`interview_prep`、`accept_conditions`、`full_report`。回复 1/2/3 分别对应追问、话术、接受条件。话术模式的可见报告不输出长篇评分。
99
+
100
+ 对比输入用 `structured_input.offers`,恰好两个不同 `id` 的 `{id,jd_text,hr_reply?}`,共同学生画像放在 `student_profile`。也支持明确按行标记 `A:...`、`B:...` 的原文。各 Offer 独立审计;资料不足、分数相同或均不满足推进条件时不强行推荐。对比后的单岗位追问必须选定对应 Offer 的 `next_state`。
101
+
102
+ ## 输出与失败
103
+
104
+ - `facts`:每项状态与原文;`source_id` 对应状态中的来源记录。
105
+ - `dimensions`、`overall_score`:未知项为 `null`;关键缺口或冲突存在时不生成综合分。
106
+ - `route`:`advance`、`verify`、`reject`、`followup` 或 `compare`。
107
+ - `mode` 与 `route` 独立。话术模式不代表岗位风险已经解除。
108
+ - `report_text`:可直接渲染的 Markdown 文本,不包代码块。`report_date` 来自运行时北京时间。
109
+ - `next_state`、`changes`:供工作流保存和更新;不要在报告或公共日志中暴露完整状态。
110
+ - `mcp_audit_summary`、`rule_version`:工具与规则审计信息;MCP 通信成功不等同于企业事实已独立核验。
111
+
112
+ 输入验证失败时工具返回 `isError: true` / `mcp_ok: false`;连接失败由客户端捕获。两种情况均须进入失败分支,停止生成评分报告。线上故障分支仍待验收。
113
+
114
+ ## 测试与交付
95
115
 
96
- `npm test` verifies:
116
+ `npm run validate` 运行真实协议及业务回归,生成 `outputs/retest-v040/validation.json`、`validation.md`、测试日志和合成演示输入。耗时数据只代表本地规则执行,不能写成线上响应时间。
97
117
 
98
- - Streamable HTTP initialization and `tools/list`.
99
- - stdio initialization and `tools/list`.
100
- - Calls to all four tools.
101
- - Low/medium/high-risk demo cases.
102
- - `offerguard_audit` compatibility with `AGENT_USER_INPUT`.
118
+ 评分公式与已知边界见 `docs/SCORING.md`。星辰配置步骤和待验收项见 `docs/WORKFLOW_INTEGRATION.md`。旧 V2.3 文档和旧 YAML 是历史资料,不应直接导入或作为复赛当前版本证据。
@@ -0,0 +1,42 @@
1
+ # 评分规则与边界
2
+
3
+ 规则版本:2026-09-evidence-1。所有分数是确定性规则估计,未使用真实录用结果校准;不是百分比概率、法律判断或行业排名。
4
+
5
+ ## 来源与状态
6
+
7
+ 只有 `jd` 和 `hr` 来源参与岗位事实匹配。`student` 只参与个人背景和偏好检查,`unattributed` 不作为公司事实。
8
+
9
+ 每个事实包含 `affirmed`、`negated`、`unknown` 或 `conflict`,并保留原文、来源类型和来源标识。来源表示由用户提供的材料类型,不表示平台已经核实 HR 身份或材料真伪。
10
+
11
+ 词典以句子和逗号分句识别常见否定、疑问与不确定表达。双重否定等不明确表达保守置为未知。不同来源的明确肯定与否定,或不同薪资金额,形成冲突;后续说法不会自动覆盖旧说法。
12
+
13
+ 无薪试岗、押金、培训贷仅作为重大风险信号。代码或作品归公司不自动判定违规;明确禁止展示时提示核实使用边界。
14
+
15
+ ## 七维公式
16
+
17
+ 以下公式仅在所需事实已知时执行。证据不足的维度为 null,不以默认分代替。
18
+
19
+ | 维度 | 已知时的计算 |
20
+ | --- | --- |
21
+ | 岗位匹配 | 40 + 45 × 学生匹配的岗位方向数 / 岗位方向数;岗位和学生文本独立比较 |
22
+ | 成长价值 | 导师明确存在40/明确不存在10 + 项目产出明确存在45/不存在10 |
23
+ | 薪资透明 | 金额已知且提及发放安排85;仅金额65;同时仍明确面议45 |
24
+ | 工作强度 | 工时已知:明确不加班85;未确定加班边界65;明确加班45;同时冲突于已识别学生时间偏好25 |
25
+ | 合规安全 | 明确重大风险0;明确不签协议20;协议明确75,不能理解为已经证明合法 |
26
+ | 转正清晰 | 标准明确存在80,明确不存在30;岗位不提供且学生不要求转正时不适用 |
27
+ | 简历增益 | 展示/证明材料明确存在40/不存在10 + 项目明确存在45/不存在10 |
28
+
29
+ 总分仅在岗位职责、学生背景、薪资底线及到岗信息、岗位城市、金额、协议、工时有依据、无冲突且所有适用维度可评估时,取适用维度均分并四舍五入。总分为空时仍可报告有证据的风险。
30
+
31
+ 明确重大风险优先进入 reject;矛盾证据进入 verify 并暂停总分;关键资料缺失进入 followup。资料齐全、无已命中风险、无低于50的适用维度、均分至少75且薪资未低于可比的个人底线,才进入 advance;否则 verify。
32
+
33
+ 目前薪资底线只比较同单位日薪或月薪,不擅自换算每月工作天数。方向匹配不是技能掌握水平测量。城市名称、工时表达和否定结构的识别覆盖有限;未识别时需要补充明确格式。
34
+
35
+ ## 已知限制
36
+
37
+ - 不抓取企业信用、招聘网站或市场薪酬;不得编造外部核验结果。
38
+ - 否定识别为有限规则,不是通用中文语义理解;反例测试只证明覆盖的样例。
39
+ - 学生只填写期望但未给出项目细节时,方向匹配仍可能粗糙;不能据此声称全面能力评估。
40
+ - HR 模糊承诺的真实性、法律适用关系、通勤成本及实际岗位成长仍需人工核实。
41
+ - 冲突解除暂采用完整核实材料开启新评估,未实现逐条签署确认。
42
+ - 线上知识库、会话变量和客户端错误分支未验证前,不宣称平台端闭环已完成。
@@ -0,0 +1,64 @@
1
+ # 星辰工作流接入记录与操作规范
2
+
3
+ 状态:待线上读取与实施。目标地址:
4
+ https://agent.xfyun.cn/agentbuilder/work_flow/621127/arrange?botId=5673753
5
+
6
+ 浏览器工具可以列出标签页,但读取该标签页反复超时。没有导出当前配置,没有修改线上节点,没有发布或提交审核。历史本地配置不能替代当前线上备份。
7
+
8
+ ## 先核对并备份
9
+
10
+ 1. 读取当前工作流版本、节点、连线、输入引用及 MCP 服务启动配置。通过平台导出功能保存当前配置,并记录版本与导出时间。
11
+ 2. 确认 MCP 是平台托管的包还是远程 URL,记录工具 `tools/list` 的真实 schema 与当前版本。不得因为本地存在0.4.0就认定线上已部署。
12
+ 3. 验证会话变量的生命周期、用户隔离及新对话清空行为。若平台无法持久保存当前会话状态,则暂停该部分接入并报告,不能用全局变量或 Prompt 假装记忆。
13
+
14
+ ## 目标数据流
15
+
16
+ 开始/当前会话状态 → 来源分离与任务识别 → 门禁分支 → 真实 MCP → 结果有效性分支 → 更新会话状态 → 展示报告。
17
+
18
+ 信息缺失时先问最多三个问题。若已有明确收费或无薪要求,即使资料不足也进入真实 MCP 审计,展示有证据的重大风险。知识库只在需解释规则或查证时检索,不能增加用户没提供的岗位事实。
19
+
20
+ 话术、反问与接受条件请求有状态时,直接调用同一 MCP 的相应 mode,无需重复长篇画像、专家报告和最终重写。新岗位清空旧状态。双 Offer 分开审计并要求选定一个 Offer 后继续追问。
21
+
22
+ ## 节点与变量绑定
23
+
24
+ | 输入/输出 | 绑定 |
25
+ | --- | --- |
26
+ | raw_input | 当前用户原话,或来源分离节点返回的 envelope_json 字符串 |
27
+ | structured_input | 仅本轮岗位原文、学生原文、HR原文;双Offer使用独立offers列表 |
28
+ | previous_state | 当前用户、当前会话、当前岗位保存的上轮 next_state |
29
+ | mode | single_check / negotiation / interview_prep / accept_conditions / full_report |
30
+ | new_assessment | 切换新岗位时 true |
31
+ | 下一轮状态 | MCP成功后保存 next_state;对比模式保存每个offers元素中的独立next_state |
32
+ | 可见输出 | 经有效性检查后的 report_text;不把整个result包装成代码块 |
33
+
34
+ 如果平台只能绑定字符串,使用 README 中 `offerguard_schema:1` JSON envelope。应由结构化序列化节点组合状态;不要让模型重写 previous_state。源码包含真实 MCP 对该 envelope 的验证。
35
+
36
+ ## 清理旧规则
37
+
38
+ - 删除把当前日期写死为六月的所有指令,统一使用 MCP report_date。
39
+ - 删除 demo_advance 指定82-90分、demo_compare必须推荐A等规则。
40
+ - 删除简短真实输入自动补造城市、薪资或背景的规则。演示按钮只发送完整、标注为合成的案例输入。
41
+ - 多专家分析如继续保留,只负责解释真实证据与未知项,不改工具分数、路线或来源。未经独立节点验证,不在材料中宣称真实专家并行。
42
+ - 比较 route 的精确字段值,不用包含通用“ROUTE_REAL”判断所有分支。
43
+
44
+ ## 失败分支
45
+
46
+ MCP连接失败、超时、CallToolResult.isError、mcp_ok不为true、结果为空或缺少当前模式所需report_text时,输出:
47
+
48
+ MCP 工具调用失败,暂不能生成 OfferGuard 风险报告。请稍后重试。
49
+
50
+ 不要保存失败状态,不输出上次报告作为本次成功结果,不调用模型重算风险作替代。用户-facing话术不暴露内部服务地址、密钥和堆栈。
51
+
52
+ ## 线上验收清单
53
+
54
+ - 本地候选包的安装/服务启动与 tools/list 中的0.4.0版本一致。
55
+ - 单岗位、缺信息、重大风险、不同专业、不同薪资底线、双Offer、谈判模式各运行一次。
56
+ - 三轮闭环:缺薪资 → HR补充薪资 → HR改口产生矛盾;查看真实请求、状态保存、changes和报告。
57
+ - 新会话及新岗位不携带其他岗位/用户资料。
58
+ - 停止测试服务或使用隔离测试环境制造连接失败,验证用户侧失败分支。
59
+ - 每次记录请求时间、各节点耗时、最终耗时、成功/失败及脱敏截图。不得将本地毫秒数据当成线上端到端时间。
60
+ - 通过后冻结工作流、知识库、规则、包版本及其校验值,再录最终视频与生成复赛材料。
61
+
62
+ ## 当前未完成
63
+
64
+ 线上备份、节点修改、状态变量绑定、服务升级、真实平台调用及故障分支验证均未完成。不能将本文件当成已执行记录。
package/package.json CHANGED
@@ -1,20 +1,25 @@
1
1
  {
2
2
  "name": "offerguard-mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "OfferGuard MCP Server for internship offer auditing, compliance checks, scoring, and workflow routing.",
7
7
  "scripts": {
8
8
  "start": "node src/server.js",
9
9
  "stdio": "node src/stdio.js",
10
- "test": "node test/mcp-protocol.test.js"
10
+ "test": "node --test test/decision-engine.test.js test/mcp-protocol.test.js",
11
+ "validate": "node scripts/validate-release.js"
11
12
  },
12
13
  "bin": {
13
14
  "offerguard-mcp-server": "src/stdio.js"
14
15
  },
15
16
  "files": [
16
17
  "src",
17
- "README.md"
18
+ "README.md",
19
+ "test",
20
+ "scripts",
21
+ "docs",
22
+ "workflow"
18
23
  ],
19
24
  "dependencies": {
20
25
  "@modelcontextprotocol/sdk": "^1.17.5",
@@ -0,0 +1,53 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { spawnSync } from "node:child_process";
5
+ import { createHash } from "node:crypto";
6
+ import { auditOfferGuardInput } from "../src/offerguard-tools.js";
7
+ import { TOOL_VERSION } from "../src/decision-engine.js";
8
+ import { CASES, GOOD_JD, PROFILE } from "../test/fixtures.js";
9
+
10
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
11
+ const directory = path.join(root, "outputs", "retest-v040");
12
+ fs.mkdirSync(directory, { recursive: true });
13
+ const testedAt = new Date().toISOString();
14
+ const started = performance.now();
15
+ const tested = spawnSync(process.execPath, ["--test", "--test-reporter=tap", "test/decision-engine.test.js", "test/mcp-protocol.test.js"], { cwd: root, encoding: "utf8", timeout: 60000, maxBuffer: 4 * 1024 * 1024 });
16
+ const log = `${tested.stdout ?? ""}\n${tested.stderr ?? ""}`;
17
+ fs.writeFileSync(path.join(directory, "tests.tap"), log);
18
+ const count = label => Number(log.match(new RegExp(`^# ${label} (\\d+)$`, "m"))?.[1] ?? 0);
19
+ const success = tested.status === 0 && count("tests") > 0 && count("fail") === 0;
20
+ const inputs = { ...CASES, compare: { structured_input: { student_profile: PROFILE, offers: [{ id: "A", jd_text: GOOD_JD }, { id: "B", jd_text: "北京后端实习生,须缴纳押金800元。" }] } } };
21
+ const samples = {};
22
+ const runs = [];
23
+ for (const [name, input] of Object.entries(inputs)) {
24
+ const r = auditOfferGuardInput(input);
25
+ samples[name] = { input, result: r };
26
+ // This measures local rule execution only, excluding MCP transport, models and the platform.
27
+ const times = Array.from({ length: 20 }, () => {
28
+ const t = performance.now(); auditOfferGuardInput(input); return performance.now() - t;
29
+ }).sort((a, b) => a - b);
30
+ runs.push({ name, samples: times.length, local_median_ms: +times[Math.floor(times.length / 2)].toFixed(3), local_p95_ms: +times[Math.ceil(times.length * 0.95) - 1].toFixed(3), route: r.route, score: r.overall_score });
31
+ }
32
+ const first = auditOfferGuardInput({ structured_input: { jd_text: GOOD_JD.replace("薪资180元/天,每月15日发放。", ""), student_profile: PROFILE } });
33
+ const secondInput = { raw_input: "HR回复:薪资180元/天,每月15日发放。", previous_state: first.next_state };
34
+ const second = auditOfferGuardInput(secondInput);
35
+ const thirdInput = { raw_input: "HR回复:薪资100元/天。", previous_state: second.next_state };
36
+ const third = auditOfferGuardInput(thirdInput);
37
+ samples.evidence_update = { first, second: { input: secondInput, result: second }, third: { input: thirdInput, result: third } };
38
+ const sourceFiles = ["package.json", "package-lock.json", "src/decision-engine.js", "src/offerguard-tools.js", "src/server.js", "src/stdio.js", "test/decision-engine.test.js", "test/mcp-protocol.test.js", "test/fixtures.js"];
39
+ const checksums = Object.fromEntries(sourceFiles.map(p => [p, fs.existsSync(path.join(root, p)) ? createHash("sha256").update(fs.readFileSync(path.join(root, p))).digest("hex") : null]));
40
+ const report = { release_status: "local_candidate_not_online_verified", version: TOOL_VERSION, tested_at_utc: testedAt, node: process.version,
41
+ test_result: { passed: success, total: count("tests"), pass: count("pass"), fail: count("fail"), exit_code: tested.status, error: tested.error?.message ?? null, elapsed_ms: Math.round(performance.now() - started) },
42
+ local_rule_timings: runs, reproducibility_lock_present: fs.existsSync(path.join(root, "package-lock.json")), online: { verified: false, timings: null, blocker: "Browser tab enumeration works but reading the XFYun workflow tab repeatedly timed out; no current online backup, mutation or validation performed." }, source_sha256: checksums };
43
+ fs.writeFileSync(path.join(directory, "validation.json"), JSON.stringify(report, null, 2));
44
+ fs.writeFileSync(path.join(directory, "demo-results.json"), JSON.stringify(samples, null, 2));
45
+ const markdown = ["# OfferGuard 0.4.0 本地候选版验证", `测试时间(UTC):${testedAt}`, `结果:${success ? "通过" : "失败"};${count("pass")}/${count("tests")};Node ${process.version}。`,
46
+ "真实协议覆盖:HTTP、SSE、stdio 的初始化、工具发现、四工具调用、状态更新、双 Offer 及输入错误;另有 HTTP 服务断开测试。", "线上状态:未验收。未读取当前配置,未修改或发布线上工作流。", "## 合成场景实测",
47
+ ["| 场景 | 路线 | 综合分 | 本地规则中位耗时(ms) |", "| --- | --- | --- | --- |", ...runs.map(r => `| ${r.name} | ${r.route} | ${r.score ?? "未评分"} | ${r.local_median_ms} |`)].join("\n"),
48
+ "这些耗时仅是本地规则函数执行,排除网络、MCP传输、模型与星辰平台耗时,不可作为线上响应时间。",
49
+ `三轮证据更新:${first.route}/${first.overall_score ?? "未评分"} → ${second.route}/${second.overall_score ?? "未评分"} → ${third.route}/${third.overall_score ?? "未评分"}。`,
50
+ "测试日志见 tests.tap,完整合成输入/输出见 demo-results.json,源码校验值见 validation.json。未使用真实学生数据,未测量现实场景准确率。", "当前候选版不得作为最终冻结版或已上线证明。"];
51
+ fs.writeFileSync(path.join(directory, "validation.md"), markdown.join("\n\n"));
52
+ console.log(JSON.stringify({ passed: success, tests: report.test_result, output: directory, online_verified: false }, null, 2));
53
+ if (!success) process.exitCode = 1;