【项目】UG/NX 全版本 AI 外挂（NX Copilot）。目标：一个独立安装、自动识别本机全部 NX 版本、
可自由切换、断网可用的 AI 自动编程软件。NX 内只允许存在"极薄桥接"，业务逻辑一律放外部程序。

【先读台账 + 总纲 · 冲突时以台账/总纲为准】
  必读①（30 秒读完）：D:\NX_AI\docs\10_状态台账.md —— 已做完什么、怎么验证的、当前基线、铁律。
                    凡台账里记录在案的【不要再重复实现、也不要重复验证】。
  必读②：D:\NX_AI\docs\09_TraeCN后续工作总纲.md
  它写明：16 张已完成卡、验收方已自行修复的 7 个缺陷（**不要重做**，重做会把已修好的逻辑改回去）、
  13 张待办卡的执行顺序，以及"卡片-现状差异对照表"（第 8 节）。
  本卡正文是在更早轮次写的，可能仍描述"待修"状态 —— 动手前先看总纲第 2 节与第 8 节。
  开工前先跑两条门禁确认起点干净（不通过就先报告，不要动手）：
      python D:\NX_AI\tools\check_py_syntax.py     # 期望：28 个 .py，0 错误 0 预警
      python D:\NX_AI\tests\run_eval.py            # 期望：通过 13/13，意外退化 0，漂移 0

【本机已核实的事实，禁止再猜，按这些事实写代码】
1. 本机装了 2 个 NX：E:\Program Files\Siemens\NX2212 与 E:\Program Files\Siemens\NX2412。
   注意：NX 不在 C 盘、不在 %ProgramFiles%，禁止把盘符写死成 C/D。
2. 版本探测的权威来源是注册表：HKLM\SOFTWARE\Siemens\NX 2212 与 HKLM\SOFTWARE\Siemens\NX 2412，
   值是 INSTALLDIR、UGII_BASE_DIR、LICENSESERVER、LANGUAGE。
   补充来源：Uninstall 项的 "Siemens NX 2212 / 22.12.1700.00000"、"Siemens NX 2412 / 24.12.7002.00005"。
   校验来源：<NX>\NXBIN\run_journal.exe 与 <NX>\NXBIN\ugraf.exe 是否存在。
3. 【重要·已实测】本机 NX 的 Python 是【以 DLL 形式内嵌】的，不需要独立 python.exe：
   E:\Program Files\Siemens\NX2212\NXBIN\python310.dll   (Python 3.10.6)
   E:\Program Files\Siemens\NX2412\NXBIN\python311.dll   (Python 3.11.9)
   E:\Program Files\Siemens\NX2412\NXBIN\nxpythonldr.dll  (NX 的 Python 加载器)
   E:\Program Files\Siemens\NX2412\NXBIN\python\          (含 NXOpen.pyd 与 NXOpen_*.pyd 全套)
   → 结论：Python journal 可用，且是版本无关性最好的通路。
   → 策略：批处理/自动化优先用 Python journal；需要进程内实时交互时才用 C#/VB。
   → 注意：NX2212 是 Python 3.10、NX2412 是 3.11，脚本必须同时兼容两个版本。
4. NX Open .NET 程序集在这里：
   E:\Program Files\Siemens\NX2212\NXBIN\managed\NXOpen.dll  (2212.0.0.0)
   E:\Program Files\Siemens\NX2412\NXBIN\managed\NXOpen.dll  (2412.0.0.0)
   两个版本是两套独立程序集 → 任何 NX 插件 DLL 必须按版本各编译一份，禁止一份打天下。
5. 编译环境：无 Visual Studio、无 .NET SDK。只能用 csc.exe：
   C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe
   目标框架 .NET Framework 4.8（本机已装）。
6. 环境变量现状（必须清理的残留）：
   UGII_VENDOR_DIR(用户级) = E:\Program Files\Siemens\NX2412\UGII\vendor   ← 单值，导致 NX2212 加载不到
   UGII_USER_DIR(用户级)   = C:\Users\Administrator\AppData\Local\NxCamToolkit\bridge
   UGII_LOCAL_USER_DEFAULTS(用户级) = C:\Users\Administrator\AppData\Roaming\NxCamWizard\nx_user.dpv ← 全局关闭了 NXOpen 签名警告
   UGII_BASE_DIR(机器级)   = E:\Program Files\Siemens\NX2412
   → 多版本切换必须用【进程级环境变量】，禁止再写系统/用户级单值变量。
7. 现有工程：C:\NxCamToolkit（Python/tkinter 桌面工具）、C:\NX_Plugins\NxCamWizard（NX 插件成品）、
   C:\Users\Administrator\AppData\Local\NxCamToolkit\bridge（文件信箱桥接 + NxAiBridge 插件）。
   文件信箱 IPC 协议设计是对的，保留并升级；NX 侧 Python journal 路线是错的，废弃。

【硬性约束】
- 不许修改 NX 安装目录下的官方文件（UGII\menus、ugraf.exe、custom_dirs.dat 等），只允许写 vendor/user 隔离目录。
- 不许写系统级/用户级环境变量来切换版本。
- 不许删除任何文件，除非先用 Copy-Item 生成带时间戳的 .bak 且备份成功。
- 不许联网、不许连 43.167.209.150、不许使用 n x_id 私钥、不许读取 trae-jwt-token。
- 不许启动 NX 界面（本次任务未获授权）。需要 NX 在线的验证一律写成脚本 + 说明，交给人工执行。
- 每个脚本必须有：-DryRun 开关、错误处理（不许裸 except）、执行前后日志、失败时非零退出码。

【编码规范】
- 主力语言：Python 3.12（桌面侧，路径 C:\Users\Administrator\AppData\Local\Programs\Python\Python312\python.exe）
  与 PowerShell 5.1（安装/探测/系统集成）；NX 侧用 C#（.NET Framework 4.8）。
- 所有新建工程文件放 D:\NX_AI\ 下（新建），不要污染 C:\NxCamToolkit。
- 中文注释、中文日志；文件一律 UTF-8 with BOM（PowerShell 脚本必须带 BOM，否则中文乱码）。
- 每个模块配一个可独立运行的 --self-test。

【NX Open 跨版本铁律 · 本机实测得出，必须遵守】
1. 判断几何特征只能用【几何量】：面（body.GetFaces()）、边（face.GetEdges()）、
   边长（edge.GetLength()）。实测同一零件 NX2212 与 NX2412 的面数(130)、面类型分布、
   孔径分布【完全一致】。
2. 【禁止】用 NX 对象个数做业务判断。实测同一零件 list(part.Bodies)：
   NX2212=92、NX2412=2；而特征数两版都是 2。按个数分支的逻辑必然出错。
3. 下列 API 在本机两版本上【不存在】，不要再尝试（已逐一实测）：
   face.Radius / face.GetRadius() / edge.Radius / edge.ArcRadius / edge.GetCurveData() /
   part.Faces / ufs.Modl.AskFaceData / body.GetBoundingBox()
   圆柱直径的正确取法：取面的圆边，**edge.GetLength() ÷ π = 直径**
   （实测 34.55751918948773 ÷ π = 11.0000000000，即 φ11）。
4. NX Open 集合【不支持】len()/ToArray()，只能迭代（list(coll) / sum(1 for _ in coll)）。
5. face.SolidFaceType 的 str() 给出数字 id（本机该零件：2→68面、1→43面、3→19面）；
   拿不准就先反射：`dir(obj)` 列真实名字，**不要凭文档猜名字**。
6. 导入/收敛几何（Brep 特征）没有参数化孔特征，孔径只能靠几何反算 —— 这是常态，不是特例。

【执行红线 · 最高优先级，高于本卡其它任何内容】
1. 绝对禁止执行任何带 -Apply / -Force / -Execute / -Run / -Kill / -Confirm:$false 的
   写入性或破坏性开关。你可以生成它们、可以用 -DryRun 验证它们，但【不得真正执行】。
   违反实例已记录一次：P0-05 卡片明确写了"不要执行 -Apply"，但 2026-10-16 14:07:52 实际执行了
   cleanup_residue.ps1 -Apply，清除了用户级 UGII_VENDOR_DIR 与 UGII_LOCAL_USER_DEFAULTS、
   移动了 nx_user.dpv（备份在 D:\NX_AI\backup\20261016140752\，人工已确认不还原）。
2. 任何会改动用户机器状态的命令（写环境变量、改注册表、移动或删除文件、启停服务、装软件、
   改 NX 安装目录内容）一律只写进"人工执行清单"，并说明为什么必须人工执行。
   允许你自动执行的只有三类：在 D:\NX_AI\ 下创建/修改你自己的工程文件、只读探测、-DryRun。
3. done.json 必须与事实一致。若确实执行了写入操作，必须在 notes 里逐条写明
   "已执行 X，时间 HH:MM:SS，影响 Y，备份在 Z"；不得声称"未执行"。
4. 涉及凭据（私钥、口令、token）只允许检测与列出，禁止删除、禁止联网、禁止尝试登录。
5. done.json 的 files 字段必须是纯字符串数组（["绝对路径", ...]），
   不得塞入对象或 PowerShell 的 hashtable 文本（P0-04 犯过这个错）。

【完成时必须做的交接动作】
在 D:\NX_AI\状态\<任务编号>.done.json 写入（UTF-8 无 BOM）：
{"task":"<任务编号>","status":"done|partial|failed","files":["产出文件绝对路径"],
 "verify":"<我该怎么验收，给出可直接复制的命令>","blocked":"<阻塞点或空>","notes":"<关键决策>"}
并且回复里用一句话汇总：做了什么、改了哪些文件、验收命令是什么。

【任务 P4-02】（★ 已由验收方于 2026-10-18 完成并验证，本卡仅留档，勿重复施工）飞轮发布经验接线进 propose_plan

问题（已核实，不是推测）：
  memory\flywheel.py 实现了完整链路：事件采集 -> merge_job 归并 -> 候选 -> approve -> publish，
  但**发布之后的候选没有任何消费者**。
  全项目搜 'flywheel'，只有 memory\flywheel.py 自己和两个测试文件引用它；
  core\propose_plan.py 只调 feedback.find_correction（精确键匹配：材料组+刀具类型+直径+工序全一致才命中）。
  结果：飞轮能把 10 条相近修正归并成一条"经验"，但这条经验永远不会影响任何工序单 -> 闭环是断的。

已核实的事实（直接用，不要重复探）：
  memory\flywheel.py
    DEFAULT_DB = D:\NX_AI\状态\feedback.db
    class FeedbackStore(db_path)  方法: init_db(), list_candidates(status=None),
        get_candidate(cid), find_active_candidate(context_key), set_status(...)
    状态常量: ST_PENDING/ST_APPROVED/ST_PUBLISHED（'pending'/'approved'/'published'）
    ACTIVE_STATES = {pending, approved, published}
    publish_candidate(store, cid) -> (bool, str)：**只改状态，不写别的库**
    context_similarity(a: dict, b: dict) -> float     # 0~1
    normalize_context(ctx: dict) -> dict
    context_canonical_key(ctx: dict) -> str
  候选字典结构（来自 _row_to_candidate）：
    {id, ts, context_key, context(dict), payload(dict), status, provenance(dict),
     supersedes, superseded_by, decided_at, version}
    payload 里是表决后的建议值；provenance 里有 support / confidence / event_count /
    per_field_vote / outcome.success_rate
  core\propose_plan.py
    L157 apply_learned_correction(op, material, tool, params, memory_db, learn)  # 精确键路径
    L251 在 build_plan 的 for op in operations 循环里调用它
    L168 import feedback
    material.iso_class / tool.tool_type / tool.diameter_mm / tool.teeth 可用

    你只需做【输出侧消费】，不要重复实现输入侧，也不要改它的精确匹配语义。
  * 输入侧已由验收方接通（2026-10-18）：core\feedback.py 的 record_correction 现在会自动
    调用 flywheel.record_event 投递事件（失败只记 stderr，绝不影响修正落库）。

要做的事：
  1. 在 propose_plan.py 里新增 apply_flywheel_experience(op, material, tool, params,
     db_path=None, min_similarity=0.6)，从【status == published 且 superseded_by 为空】
     的候选里按 context_similarity 找最相似的一条，相似度 >= 阈值才采用。
     当前上下文按 {"operation", "material", "tool_type", "diameter"} 构造
     （diameter 保留 2 位小数）。
     采用时只覆盖 params 里【已经存在】的键，并把 old->new 记下来；
     没有任何键被改动就当作未命中（不要为了"有命中"而硬塞）。
  2. 优先级：**精确修正（feedback）永远优先**。精确命中时不再用飞轮。
     只有精确未命中时才走飞轮，并且必须在计划里显式标注来源，例如
       plan["flywheel_advice"].append({operation, candidate_id(前8位), similarity,
                                      support, confidence, applied{key:[old,new]}})
     并在该工序的 params 旁注明这是"归并经验"而非"人工精确修正"。
  3. 新增 --no-flywheel 开关（默认开启飞轮），并支持 --flywheel-db 指定库路径（测试用）。
     飞轮库不存在/打不开/表缺失时：**静默降级**，在 plan["notes"] 里加一句说明，
     绝不能让主流程崩。
  4. capability_level：命中飞轮时写 "L1-rule-driven + L3-merged-experience"，
     命中精确修正时保持 "L1-rule-driven + L2-learned"，两者都没有就 "L1-rule-driven"。
  5. 单测：新建 tests\test_flywheel_wiring.py（纯离线，用 tempfile 建临时 db）
     - 造 3 条相近 correction_event -> merge_job -> 候选
     - 仅 pending 时：propose_plan 不采用（相似经验未发布不得生效）
     - approve+publish 后：propose_plan 采用，且 flywheel_advice 里有记录
     - 精确修正存在时：飞轮不生效（优先级正确）
     - 阈值不达标（上下文差很远）：不采用
     - 库路径不存在：不崩，notes 有说明
     - --no-flywheel：完全不采用

验证（必须贴真实命令与输出）：
  python D:\NX_AI\tests\test_flywheel_wiring.py
  python D:\NX_AI\tools\check_py_syntax.py        # 0 错误 0 预警
  python D:\NX_AI\tests\run_eval.py               # 13/13、意外退化 0、漂移 0
  另跑一次真实链路并贴输出：
  python D:\NX_AI\core\propose_plan.py --facts D:\NX_AI\状态\_demo_facts.json --material N --json
    （先在临时库里造并发布一条经验，确认 flywheel_advice 出现；再删掉，确认消失）

红线：
  - 不许改 feedback.py 既有的精确匹配语义（它已验证过，改了会破坏 P2-06 的金标）
  - 不许让飞轮的经验覆盖红线校核结果；红线违规必须照旧报出
  - 飞轮只能"补参数"，不能新增工序、不能换刀
  - 任何异常都必须降级为"不用飞轮"，不许抛到主流程外
