AGENTS.md「当前任务与状态」历史明细(M35–R8.2)

⚠️ 这是归档材料,不作为设计参考。 AGENTS.md 的 §3 原本是一份任务流水:每完成一轮就往里加一段,涨到近 1000 行, 把「当前该干什么」整个埋掉了。2026-10-08 整段搬到这儿,§3 只留 「现在在哪、下一步做什么、怎么量」。 原文未做删改;里面的行号/章节引用已经漂移。 活着的硬约束在 AGENTS.md §1/§2;设计取舍在 docs/设计决策.md。


评测闭环 → 跨语言分发 → 包管理 → 语言收敛 1.0 → 发行版 → 排错体验补全 → 表达便利 → 发行改进 → 性能优化 → 开发者工具 → 包生态 → 语言功能 A/B/C 档 → 变长参数 → 值→文本统一 → Rust / JS 宿主补齐(含标准库同宽)→ 边界清单核实 → 模式匹配与枚举 → 发布工程与三平台 Release → 语法冻结 → 调试器)。 里程碑明细与各轮教训见 PROGRESS.md(末尾「里程碑明细 / 路线图历史归档」)。

桌面项 / Inno Setup 图标);类体校验统一到解析期(原先三执行器各不相同, 甚至 x += 5 静默算错);修掉「测试里写死开发机事实」这条根因, 把长期全红的 CI 修到四平台全绿。

(脱糖,三执行器 + 两宿主零改动)+ 位运算内建 6 个 + 字典.合并 + Node/Rust 宿主同步(顺手补上 Rust 一直缺的 字典.更新/弹出)+ 探针「无悬案」判据;三份路线图合并成一份。语法面就此冻结。

事件,于是让编译器在每条语句的首指令上打标(Instr.stmt_line),VM 主循环 只在标记处回调调试器。循环回边落在语句中部,所以循环头只停一次——与树遍历的 暂停点严格对齐,两个执行器的会话记录逐字相同(tests/test_m39_vm_debugger.py 逐项对拍)。C VM 当时判为暂不做(帧在 C 侧,要结构体 + 宿主回调)—— M40 已补齐(见下);

收尾引号独占一行时不补 NEWLINE(多行字符串整个特性不可用)、类体里的方法 定义行两个执行器都停不住、类变量求值顺序与源码顺序不一致、 导入 文本 会打断 f-string(最后一个尚未修,已列入路线图);

读仓库真数据(版本号 / 测试数 / 探针 / 里程碑 / 模块规模)生成一套介绍基石的 网页;三个执行器跑出来的产物逐字节相同。

增量分词(jishi.incremental,依据是 tokenizer.LineAnchor 记的七个状态值)。 实测每键分词成本:18001 行改末行 359.7ms → 8.5ms;1801 行 33.4 → 0.5ms。 parse 仍是全量的(如实标注在 docs/工具链.md);编辑范围按 LSP 默认的 UTF-16 码元**换算;版本乱序直接忽略。

可改)、Tab 补全(数据来自 ai.build_lang_spec(),不另抄一份)、多行编辑 判定交给真词法器。Windows 自实现逐键编辑 / POSIX 交给 readline / 非终端退回 input()。

「带 BOM 的文件直接崩」「格式化 <目录> 甩英文 traceback」四个真问题。

断点 / 单步(进·过·出)/ 看变量 / 看调用栈 / 查看 表达式;出错时先打 「出错那一帧的变量 + 调用栈」的现场,再打正式中文报错。 目前只支持树遍历执行器(字节码 VM / C VM 是栈机、没有语句边界事件, 要另做一套);钩子只读,带调试器跑与不带,程序输出逐字节相同。 用法与边界见 docs/调试器.md。

覆盖 initialize / launch / setBreakpoints / configurationDone / threads / stackTrace / scopes / variables / evaluate / continue / next / stepIn / stepOut / pause / disconnect 与 initialized、stopped、continued、output、 exited、terminated 六个事件。三执行器逐项一致(tests/test_m43_dap.py 34 项);唯一差异是 C 虚拟机不支持就地求值——明说原因并给替代方案,不给错值。

跑程序;暂停时脚本线程阻塞在命令队列上,所以主线程读栈与变量时执行状态 是静止的。stopped 事件必须在暂停判定一成立就发(编辑器收到就来问栈)。

没有自己写 DAP 传输层——语言服务那条路要自己写是因为要在编辑器外也能跑 同一份代码,调试这条路没有这个需求。

不必再走「F5 + 扩展宿主窗口」(从宿主窗口打开新文件夹会另开一个普通窗口, 那个窗口不加载开发中的扩展)。.vscodeignore 已排掉 99MB 的 node_modules。

与异常类型 9 个着色、关键字补 7 个、片段补 7 条; tests/test_m43_highlight.py 钉住「新增内建忘了同步高亮表就红」。

prepareRename / codeAction(报错里的修法建议→可点快速修复)/ signatureHelp(文档解析不了时走 token 兜底)/ workspace/symbol + 跨文件 跳转 / semanticTokens(关键字/标准库/用户符号/形参/属性五类)。

自己写 lsp-transport.js(约 150 行:分帧/请求应答/通知分发), 零运行时依赖(没引 vscode-languageclient,见 D39),11 项能力逐条接上。

传输代码**真的起一个 jishi lsp,把每项能力点一遍——脱离 VSCode 就能验 「点了有没有反应」。已进回归(tests/test_m42_vscode_client.py)。

① 重命名只做模块级符号(AST 不记形参位置,且没有作用域分析会误伤同名 局部变量 → 返回 null 让编辑器明确拒绝)② 跨文件跳转只在唯一命中时生效 ③ 工作区符号索引有封顶(400 文件 / 512KB / 跳过 .jishi 与隐藏目录)。

五路同宽(三执行器 + Node + Rust)。模块名是「容器」不是路线图里写的 「集合」——集合 是内建函数名,同名模块会把内建遮蔽(D35)。

三执行器与两宿主零改动(D36)。

与既有的 "abc".转整数());待办清单 的 捕获 异常 已收窄。

两趟遍历改一趟,整数循环 9.5x → 10.5x、函数调用 13.3x → 15.7x。

语句钩子 / 帧元信息 / 帧变量导出),三执行器会话记录逐字对拍 (tests/test_m40_cvm_debugger.py)。唯一没做的是 C VM 的就地求值 (C 侧没有「在任意帧上求表达式」的入口),会话明确说明而不是给错值。 顺带补上 C VM 一直缺的报错调用链(M17 给另两个执行器做了,C 侧漏了)。

哨兵;判定复用编译器的收集器,报错文案走单一来源 (runtime.unbound_local_error)。M39 那条 xfail(strict=True) 已转正常断言。

内部名从语言规格 / 补全里过滤掉(runtime.is_internal_builtin)。

口径、逐项数据与「别扭点清单」见 docs/真实项目.md。

AI 可用性」三个方向,M40–M45 顺序推进。 M42 / M43 已完成(语言服务六项能力 + VS Code 语言客户端;jishi dap 调试 适配器 + 扩展打包)。M44 / M45 已完成(机器可读语言事实 + 静态检查; AI 跑分表 + MCP 工具面 7 个)。方向三就此收齐。 M41 的复盘给 M42–M45 提供了新靶子:别扭点清单里 2 条其实是 「能力早就有、只是不知道」(数学.四舍五入、文本.左对齐、集合交并差) ——治它靠补全与机器可读事实,不靠加能力。

错误码表 / 静态检查码表 / 等价 Python 写法;新增 jishi 源码静态检查 (四类问题,真实语料零误报);作用域分析抽成 jishi/scope.py 供编辑器与命令行 共用。顺手修掉 E2008 重码(自定义异常改 E2009)。

① tools/eval_ai.py AI 跑分表——45 题(语法 12 / 标准库 15 / 调试 10 / 重构 8)在 evals/tasks/*.json;模型只看语言规格,写完真跑基石判分 (输出逐字相同且退出码 0),跑错回灌中文报错最多改 3 轮。 题集先自检(--自检):调试题的缺陷代码必须真失败、重构题原始代码必须 真跑通——题目错了跑分表就是假的。限流按 3/6/12/24 秒退避重试。 ② MCP 工具面 3 → 7 个:check_source / lookup_error / lookup_stdlib / format_source,事实全从语言规格与既有实现取,不另抄一份。 ③ 顺带修掉树遍历复合下标赋值漏裸 KeyError (字典[新键] += 1 → 原来报「程序内部出现未预料的问题(KeyError)」, 现报 E2004 找不到这个键)——写调试题集时抓到的。

(不再含「PyPI 首次上传」——2026-09-22 用户决定放弃 PyPI,改自建分发。)

配置 / html / 对比 / 标识 + 既有模块补缺(迭代 +4、文本 +2、 时间 +1)——19 → 25 个模块、161 → 231 个函数;第二批补齐 Node / Rust 宿主,五引擎同宽(tests/test_m50_hosts.py 39 项逐字节对拍)。 验收落到真实项目:生成.jsh 的手写 转义()、排序.jsh 的手写计时都换掉了。

「三执行器、五引擎继续维持的代价是不是太大了?要不要优化架构?」 完整评估见 docs/架构评估.md(内部文档,不发布)。三条实测结论: ① 执行器与宿主都不砍——三执行器是最值钱的验证资产(对拍抓过 M47 内存泄漏、 M48 错误列号、M50 上下文管理器缺口);两个宿主是跨语言分发的落地形态。 ② 真正的重复是「标准库写三遍」:宿主层 17365 行里 11161 行是标准库 (Node 2282 + Rust 8879),同一份逻辑三种语言各写一遍。 ③ 纯逻辑库可以用基石写一遍、五引擎共用(POC 已验证):统计 用基石重写 68 行 → 编译成字节码 → Node / Rust 输出与官方 Python 版逐字节一致,零宿主代码。 代价是慢约 7 倍,但真实项目里标准库调用只有 4–26 次(耗时大头在循环体内), 实际影响 <10%。性能敏感的库(如排序类)留在宿主层——要分层,不一刀切。 ⚠️ 最危险的不是「贵」,是「错了不报警」:全仓没有「某模块必须五引擎都有」 的检测测试——M50 第一批的宿主缺失是手工测出来的。所以 M51 先止血。 ✅ M51 一致性止血已交付(2026-09-26):漂移检测测试(宿主自报家底) + 宿主命名参数缺口修复(五个引擎同一结果、映射不了就明确报错) + 顺带补齐 13 个静默缺失的函数。细节见 §2 的「宿主命名参数缺口」与 「漂移检测已建」两条。 ✅ M52 基石库层第一步已交付(2026-09-26):纯逻辑标准库只写一份 (jishi/stdlib-jishi/统计.jsh,就是基石源码),编译期展开成外壳函数, 于是五引擎共用同一份字节码、三执行器与两宿主零改动。 机制、走过的弯路、代价都写在 docs/设计决策.md D45 与 §2 的 「基石库层已建」。⚠️ 实测代价:比宿主层慢 25–38 倍 —— 热路径函数必须留在宿主层,这是 M53「性能分层」的存在理由。 ✅ M53 基石库层批量迁移已交付(2026-09-26):又搬了 容器(7)、 迭代(12)、参数(5)—— 加上 统计,基石库层共 4 模块 / 34 函数。 命名约定扩展 __X → 导出 X;性能分层第一例(迭代.求和 转发内建 总和)。 顺手抓到一个影响四个引擎的真 bug(编译器的 尝试/捕获 把栈搞脏, 详见 §2)并修复 + 加回归测试。 ✅ M54 把「五引擎的账」结清已交付(2026-09-27):抓到 4 个真 bug + 补上 1 个机制缺口 —— ① Rust 的闭包单元不共享(值语义的 Cell, 导致嵌套递归/闭包收函数回调全崩);② Rust 未绑定局部静默给 空; ③ C VM 的未绑定单元报错类型与另两个执行器不一致; ④ C VM CvmFunction.__del__ 的用后释放崩溃(崩在 GC 里); ⑤ 方法漂移检测(M51 那份只覆盖标准库函数名)→ 顺带补齐 Rust 文本.查找、Node 文件.位置/定位/刷新。详见 docs/设计决策.md D46。 ✅ M55 定下「字符是什么」已交付(2026-09-27):① 新增内建 序数(字符) / 字符(码点)(三处实现,五引擎逐字节一致); ② 🔴 修掉 Node 的「文本 = UTF-16 码元」真 bug —— 长度(&quot;😀&quot;) 曾给 2 (另两个给 1)、&quot;😀&quot;[0] 只拿到半个代理项、&quot;a😀b&quot;.查找(&quot;b&quot;) 给 3, 漏的正是 长度/单下标/查找/反转 四处(切片与遍历本来就对); ③ 新增 --dump-builtins(第三个「宿主自报家底」的口子)—— 建起来当场抓到 断言 / 是实例 在 Node 与 Rust 上根本没有, 断言错误 这个捕获类型也缺,都已补齐; ④ Rust 12 个内建参数个数不对会 panic、Python 侧漏英文原生错误 → 一并收口(文案与 Python 侧逐字对齐)。详见 docs/设计决策.md D47。 ✅ M56 第一批 · 内建体检已交付(2026-09-27):新建 tools/probe_builtins.py —— 给每个内建一条最小程序,五个引擎真跑 (正常用法 + 参数个数错 + Rust panic 扫描),不再只比名字。 一建起来就抓到 M55 漏掉的账:⚠️ M55 把 超 当成「编译器形式、宿主 不需要有它」而排除在比对之外(旧 BUILTIN_EXEMPT = {&quot;超&quot;})—— 判断错了:它是 LOAD_GLOBAL 超 + CALL 的真运行期内建, 于是 Node 与 Rust 上 超() 一直不可用(报「找不到名字「超」」), 而三个执行器全对、对拍又因豁免覆盖不到。已修:Node 补 JishiSuper, Rust 给 Class 补 base 字段(那边建类时展开合并基类方法表后 把基类指针丢了,而 超() 必须沿链找「定义类」)。 详见 docs/设计决策.md D48。教训:豁免必须能被复核, 否则它就是藏 bug 的地方(BUILTIN_EXEMPT 现为空,常量保留)。 ✅ M56 第二批已交付(2026-09-28):① Node 19 个内建补参数个数检查 (体检的 16 条 → 0;含 3 条曾是「静默成功」的 —— 文本() / 类型() / 退出上下文() 在 Node 上直接返回值)。⚠️ 根因是签名写成 (v) =&gt; …: 0 个实参时形参是 undefined、函数体照跑 —— 必须用 (...args) =&gt; 才拦得住。 顺带收口 Rust 4 处自己抄的文案(精确 少「但传了 N 个」、范围 没书名号、 两个上下文多空格)→ 参数个数错现在五引擎逐字一致。② Rust 异常对象模型: 捕获时改压带消息的异常值(以前只压类型名 → e.类型/e.消息 报「没有属性」)。 ③ jishi 错误 [码]:32 个码的「什么意思 / 常见成因 / 怎么改」 (码从 errors.py 自动抽,说明在 jishi/errcodes.py,测试钉住不许漂移)。 详见 docs/路线图.md §9.4.0b 与 docs/设计决策.md D48。 ✅ M56 第三批已交付(2026-09-28):第 3 / 4 / 5 条 —— ① 文件.位置/定位 统一成字符数(Python 原来给 tell() 的不透明字节 cookie; Rust 给字节偏移);② 换行符统一 \n(Python 原来在 Windows 上写 \r\n, 同一台机器上五引擎写出的文件字节不同 ⇒ 用 newline=&quot;\n&quot;); ③ 🔴 顺手抓到 Rust 的 文件.读(n) 按字节读 —— 非 ASCII 上把汉字切两半, 报「文件内容不是 UTF-8 文本」,而文件明明合法(误导性报错比不报错更耗时); ④ Rust 的报错终于有行列(原来全部没有;根因是 JishiError 缺字段 + 反序列化把指令的 line/col 丢了)+ 两宿主补错误码 (错误 E2001:不能除以零 + ← 第 3 行第 6 列;码能直接贴给 jishi 错误 &lt;码&gt;)。详见 docs/路线图.md §9.4.0c。 ⚠️ 错误码只映射能确定的 7 个(除零/类型/索引/键/值/文件/断言)—— 宿主的「异常」太笼统(找不到名字、属性不存在…都归它),宁可不给码也不瞎标。 ✅ M56 第四批已交付(2026-09-29)—— 🎉 M56 四批至此收完。 这一批给两宿主补上 hint(提示语 + 候选名): 找不到名字「长渡」 → 「提示:你是不是想写「长度」?」」。 ⚠️ 难点不在加字段,而在候选项必须三侧一样:Python 用 difflib, 而 Node / Rust 都没有 —— 各逐字复刻了一份 Ratcliff-Obershelp (不一致比不给候选更糟)。两个抄错就分叉的点: ① 最长公共块要用 difflib 的 DP 版(k &gt; bestsize 严格大于,并列取先遇到的); ② get_close_matches 收集的是 (分数, 名字) → 同分时按名字降序 (抄错这点,3000 组随机对拍里 103 组顺序不同)。 📌 得意之作的方法论:先在 Python 里把复刻版与 difflib 对拍 (4000 组 ratio + 3000 组候选全一致),确认算法钉死,再翻译成 JS / Rust。 ⚠️ Node 的字符串比较要按码点(cpCompare,JS 的 &lt; 是 UTF-16 码元序); Rust 的 UTF-8 字节序恰好与码点序一致,直接用 cmp。 ⚠️ 宁可少给,不要给错:prnit 跟池里谁都不像就不给提示行。 详见 docs/路线图.md §9.4.0d。 ✅ M49 · 自主分发已落地(2026-09-29):ICP 备案完成**(苏ICP备2026073372号), 官网与自建分发源上线 <https://基石.cn/>(源站=大陆轻量云 Nginx,前面 EdgeOne CDN)。

自建源那份索引的「下载地址」也指同一域名 ⇒ 索引 + 包全在境内(实测 0.11s)。 两份索引各自自洽,所以自建源挂了是整条退到 GitHub,不会半路断。

(443)⇒ 必然 521。判据是源站 access_log 里有没有那条请求(HTTP 有、HTTPS 没有)。 给源站装证书 + 开 443 后两条路都通(tools/nginx-jishi-tls.conf)。

--local-zip / --local-dir)+ jishi 医生 的索引可达性自检。

发行包由 tools/fetch_release_mirror.py 从 GitHub Releases 取(开发机拉得动、 服务器拉不稳)到 dist/release/,SHA256SUMS 逐个校验; ⚠️ deploy_push.sh 只在服务器缺文件时才传(109MB,不能每次都推)。

/ tools/nginx-jishi{,-tls,-locations}.conf。⚠️ 站点规则单独成片段, 80 与 443 两块 include 同一份(两处复制迟早漂)。 🔒 密钥与服务器地址只从环境变量读。

有 SSH 就优先 SSH(用户名 ubuntu、免密 sudo)。见 操作细节.md 附一/附三。

⇒ 本地构建 + scp(deploy_push.sh)才是可靠路径。

🚩 2026-09-30 转向:M57–M59 全部作废,改走 R 线「前端换 Rust」 (作者定:前端换 Rust + 桥接层 + 保留并增强 C VM + 单二进制解释器; R 线全程在 0.2.x 号段;架构变更完成后才发 0.3.0(进入 0.3.x 阶段))。

回答「值不值得」(全部同机实测),内部架构迁移路径(未公开) 回答「怎么做」(R0–R8 + 三条纪律 + 中途检查点 + 待拍板决策)。 决策与承接见 docs/路线图.md §十一。

Python 全套的 0.96 倍;启动(打包版)284.9 ms、Linux 包解开 102 MB (最大两块是 两份 30.6 MB 的 libpython);Rust 宿主二进制只有 2.63 MB; 前端跑 2 万行 2132 ms(编译占 1246 ms);Rust 侧 ≈7900 行可直接复用**。

(tokens / AST / 字节码 / 诊断)在 73 个语料上逐字冻结成基线 —— Rust 版每写一步就拿它比,一致才叫这一步做完。 工具 tools/conformance.py(freeze / check / list)、基线 tests/conformance/、 守门测试 tests/test_m57_conformance.py(17 项)、说明 docs/一致性夹具.md(内部)。 实测:冻结/比对各约 1.4 秒;产物 4.2 MB(gzip 0.3 MB); Python 3.10.9 与 3.13.14 产物逐项一致(73/73)。

① conformance.py 里前端抛非基石错误时拒绝冻结 —— 工具分不清「事实」与「事故」,「把当前状态升格为标准」的动作必须有这道闸门 (第一次试跑就把我写错的 dumps_json 冻成了 73 份「标准」); ② fetch_release_mirror.py 里清单没有该资产名时拒绝发布 —— 「跳过校验」与「校验通过」在 CI 里长得一模一样(它曾拿上一版清单静默跳过四条校验)。

带版本号的契约(docs/前端契约.md,内部),并给出能定位的尺子 —— tools/conformance.py compare --from &lt;目录&gt;(外地产物 vs 基线,差异定位到 路径.字段[下标] 并说出那个元素是什么);--stage 支持只比某一阶段。 不改行为,只定义接口 —— R2–R5 的验收方式由此固定。

Python 扩展 / 保留 Rust 树遍历参考执行器 / C VM 去向留 R6 / 新开 R 线 / R0 立刻做)—— 见内部路径文档 §八。

Python 扩展),tokenize() 在 JISHI_FRONTEND=rust 时走 Rust。

语料从 73 → 85(R2 补 12 个,见下;R3 又补 3 个 → 88)。

⚠️ 端到端天花板 = 4.23x(把 Token 交回 Python 有 46 ms 的地板)—— 「≥5 倍」这层接口够不着。 ✅ 2026-10-01 已拍板:这层验收线 = 「纯核心 ≥5x 且产物逐项一致」, 「端到端 ≥5x」移到 R5 —— 那时下游不再要 Python 对象,端到端才有意义。

位置);另有 CRLF / 孤立 \r / BOM 这类文件表达不了的输入,走 字符串级测试;并新增 tests/frontend_cases/(只给前端看:像超 i64 整数这种 C VM 有意拒绝、但前端必须处理的语料)。

conformance.py emit --frontend rust --out 目录 → compare --stage tokens。

+ 11 条报错逐字 + 8 条端到端 + 720 组随机对拍 + 三张表漂移 + 开关语义)。

移植 jishi/parser.py),parse_source() 在 JISHI_FRONTEND=rust 时走 Rust。

跑出来也一样)。compare --stage ast 与 --stage diagnostics 都全绿。

(天花板 4.73x);分词仍是 3.63x / 6.47x。

而 Python 入口原来只有 tokens + source_lines —— 后者是有损的载体 (incremental.split_lines 去掉末尾空行),反拼回去报错行号整体偏一位 (实测 9 个样例错 4 个)。于是新增 parse_source(源码) 作为生产入口, parse(tokens, lines, f) 保留给 LSP 增量分词,并加精确守卫 (拿 EOF 行号当标尺)——丢了内容就当场报错,不猜。

分段表,直接插进中文报错会把 Python 的 repr 泄给用户 → 两侧统一说 「插值字符串」(_tok_text / tok_text);② 类外 超() 那条错漏了 with_source 与下划线(它是解析之后补的检查,不走 _err)→ 已补齐, 报错的补全要认「一个出口」,别按「谁抛的」分岔。

(41_块首后空行 / 42_类外超 / 43_整行插值字符串,都被冻结进基线)。

R2 比过词法报错、R3 顺手比过语法报错,所以 R4 只剩「把比较面补全」这半边, 「执行期/编译期」那些要等 R5/R6 才有产物。

(渲染 = 用户真正看到的那段多行文本)。⚠️ 渲染 看着冗余,其实是兜底: 哪天给错误加了事实字段却忘了列进契约,两者的渲染立刻不一样 —— 而「字段没进契约」本身是看不出来的(R3 就栽在「漏了一个字段,比对还是绿的」)。

「这个码真的会出现」,这件事以前从来没人查过。第一次跑就抓到两笔: E0205「括号没有闭合」是死码(ParseUnclosedError 定义 + 说明都在, 全仓库没有任何触发点 —— 括号没闭合其实报 E0201); E0204 可达却一个语料都没有(八个形状能触发)。 「不可达」必须登记且经得起复核(verify_unreachable() 会静态复核 「是不是真没人引用」),登记过期就报红。 ✅ E0205 已拍板接上(2026-10-01):期望右括号而已到文件末尾 ⇒ E0205, 且报错位置指向那个没闭合的开括号(EOF 的位置是「末行第 1 列」、 连源码行都显示不出来,指着它没用)。⚠️ 只认「走到末尾都没等到」 —— 中途撞上别的 token([1, 2 后面换行写下一句)仍报 E0201 并说出「看到了什么」, 那时那条信息更有用,别为了把码归拢而把信息丢掉。 顺带修掉「到文件末尾把 Python 的 None 印进中文报错」(_tok_text 补 EOF 标签)。 现在:11 个可达码全部有语料钉住,0 死码。**

以前词法/语法各抄一份字段清单,而词法那份漏了「修正」—— 恰好当时 没有一个词法错误带 fix,所以怎么测都是绿的。 新建 tests/test_m61_frontend_errors.py 用字段表往返把这个缺口堵死。

两个前端在 92 个语料上 tokens / ast / diagnostics 三阶段全绿(R5.1 后 93)。

compiler.rs(jishi/compiler.py 1151 行的逐行移植)、jishilib.rs (基石库层展开)、serialize.rs(字节码 JSON + Python 的 repr(float))、 opcodes.rs、sha256.rs。

R5.1 后 93/93); 字节码逐字节相同(连 checksum —— 它是 payload 的 SHA-256, 差一个字节就不一样),另 3 万组随机源码对拍 0 分歧。

Python 对象 —— 「把对象交回 Python」正是 R2/R3 那堵天花板的来源, 文本没有这个地板。实测编译端到端 2035 → 379 ms(5.37x), 「端到端 ≥5x」这条 2026-10-01 从 R2 挪过来的验收线兑现了。 Python 侧两个入口:compile_to_json()(原生,给 --dump-bytecode 与夹具) 与 compile_source()(仍给真对象,VM/CVM 那条路要用)。

local_hints 不在字节码 JSON 里,而字节码执行器报错要用它 (「赋值前读局部」那句提示)。只给 JSON 的话,rust 前端跑 VM/CVM 时 那句话会静默消失 —— 两个前端就有了语义差异。有测试盯着。

opcode_table() 与 OP_NAMES 对拍)与 repr(float)(JSON 里的浮点文本, 拿 1 万多个值逐个比)。

① 编译器名字收集漏 Try 分支(赋值漏成全局 / 闭包捕获不到 / 块内定义函数类直接崩);② Python VM 的 BUILD_CLASS 用局部名 base 撞掉帧的栈基址(「函数里定义类」必炸);③ 树遍历给「捕获 … 为 名字」 偷偷开了子作用域(捕获体里的赋值出不去);④ 调用链挂载时机三处不一 (「显式 抛出」的错误两个引擎丢链)。 ⚠️ 它们在两个前端上一直是一致的 —— 只在三个执行器之间露出来, 所以一致性夹具全程绿也照样存在。

(含 error_cases,报错文本也是可观察行为)逐字节比三个执行器。 93 个语料 / 对拍 91 / 豁免 2 / 不一致 0(两个前端各一遍)。 改完任何引擎行为都跑它。

tools/benchmark.py 与 tools/check_engines.py 都接进了 Rust VM(第四个 执行器)。⚠️ Rust VM 那一列必须扣掉进程启动(实测空程序 8~10 ms), 否则量的是「进程启动快慢」而不是「VM 快慢」。

✅ R6.1 已修;②③④ ✅ R6.2/R6.3 已修(见下)(迭代器原来是「列表 + remove(0)」,改成快照反转 + 尾部 pop();n=20000 净耗时 2490 → 4.82 ms,翻倍倍率回到 ×2 线性); ⚠️ 零点要当场量:进程启动成本会漂(8 ms → 85 ms,刚编出来的 exe 被实时扫描),拿旧零点减新数据会把结论读反; ② 捕获 运行期错误 接不住 键错误/索引错误(语言规格明写「基类能抓子类」); ③ 报错时 stdout 一个字都不冲出来(崩了看不到跑到哪);④ 报错渲染缺 源码行/^/调用链(宿主设计,但核心二要求它必须解决 → 按 filename+行号 读源文件最省)。

⚠️ 不把源码塞进字节码,而是出错那一刻按 filename 读一次源文件; 读不到就退回「没有源码行」,不崩。

19 个常见运行期错误形状里 18 个四引擎不一致 —— 两类根因:① Python 侧 漏内部表示(list index out of range 英文、&#x27;乙&#x27; 键的 repr、 「([],)」 元组 repr)②宿主笼统化(找不到这个名字 等一律报「异常」, 连坐 e.类型 / 捕获 X / 错误码三处)。现已 0/19。

所以缺口③根本比不出来;改成「stdout + 用户看到的报错渲染」之后, 可见问题从 0 个变成 26 个。要比就比用户看到的那一份。

4 条「^ 落在哪一列」根本不用动格式 —— 登记里写的「宿主没有 AST」 是当时的猜测、不是事实:两边手里都有那两个位置(GET_ATTR @2:2 / CALL @2:5),差的是「报错时挑了哪一个」。修法是照 Python 的机制来: 让绑定方法值随身带取属性的位置。⚠️ 模块属性(数.开方)是裸 Builtin、两边都用调用点 —— 不能按「名字里有 .」一刀切。 1 条 local_hints 才真动格式:字节码升 v3,版本号四处同步 (有测试钉着)。 📌 教训:登记里写的「原因」是待验的假设 —— 真去修时必须把现象重量一遍, 否则会照着错的原因去做一件大得多、而且并不解决问题的事。 ⚠️ 现在登记只剩 3 条,全是「有意边界」(宿主是单二进制、没有 Python)。

树遍历/两宿主报调用点、VM/C VM 报函数定义处。🔴 根因不是一个,是三个 (分散在 Python / C 两种语言里):VM 的 call_function 把绑定位置写死成 firstlineno, col=1;C 侧 jsvm_call_function 里 bind_args(..., 0, 0) 把宿主传下来的调用点丢了;_cb_method_call 没记 _pending_call_site。 修法一律「照树遍历的机制来」:回流入口用调用点。 🔴 顺带:Rust 的用户方法调用静默丢 kwargs(&amp;[], &amp;[]);树遍历的 类构造参数错没有行列。 🔴 还有一个工具链级的(更阴):cvm/build.py 产出的 cvm/bin/jsvm.dll 与 wheel 预编译的 jishi/_native/jsvm.dll 是两份,而 _lib_path() 原 优先后者 —— 改了 C 源码并重建,跑的还是九天前那份、一句提示都没有。 已改成源码树优先(发布链不受影响)。📌 又一次「静默用错产物」。

提示,与 Python 逐字相同),做法与 Rust 一致(按 filename 现读源文件);

这个东西不能调用(E2006),不再笼统报「异常」「运行期错误」; 输入结束 补码 E2010;

(JS 数组自动补长 → 不报错还把列表撑长了)、[1].弹出(9) (给 undefined)、1 &lt; &quot;a&quot;(给 假);

「三个 Python + Rust」—— Node 的那些问题一个都抓不到。 拉进来的当天,探针从「0 不一致」变成 11 个不一致。 📌 与 R6.2 换判据是同一条教训:判据的视野有多宽,能看见的问题就有多少。

4 条「^ 落在哪一列」+ 1 条 local_hints 不在字节码里 + 3 条「宿主不支持 从 python 导入」),且机制上改成两个宿主都对齐了才算「登记过期」。

列[-1] 给最后一个元素,两个宿主报「索引错误」(语料里从没用过, 所以一直没发现)。定下来之前,这个形状不要进语料。

加东西」—— 树遍历 / Python VM / C VM / Node 都是活迭代器(与 Python 一致,会一直循环),只有 Rust VM 是快照。四个对一个是票数不是判据; ⚠️ 别把它写成测试也别进语料**(写了就等于让三个执行器死循环,pytest 会挂住)。

18.3x)、当循环倒计数(7.1x vs 37.7x) 外全面落后。但不是「Rust 不行」—— 现有 Rust VM 是 M27/M33 的兼容宿主,不是为核心重写过的。先量再说。

Vec&lt;Val&gt; 改成「保序 Vec&lt;Option&lt;Val&gt;&gt; + 哈希索引」——添加 去重 / 包含 / 移除 定位都 O(1),而顺序照旧(集合是有序的,规格里写着)。 ⚠️ 删除必须打墓碑:保序集合删中间元素若前移尾巴就是 O(n)(「删 n 个」仍 O(n²))—— 第一版就这么写的,n=16000 批量删除 1845ms;改成置 None + 空洞过半压缩一次(摊还 O(1))→ 37ms。压缩要同时重建 items 与 index。 🔴 顺带撞出三个既有分叉:① 真 == 1 只有 Rust 给「假」(Val::eq 少布尔分支,且键归一没跟着改 —— 「== 与哈希键是一对」,改一个必须改另一个; Node 侧一直是对的,注释里就写着「Python 里 True == 1」);② 「不能放进集合」 的报错三个说法;③ Node 的提示语不显示(方法表 catch 后重建错误只为补 位置,把 hint 丢了 —— ⚠️ 重建错误对象时要把字段显式带过来)。 📌 真正的教训:tests/cases/ 里一个集合语料都没有,所以集合从未进过 四阶段夹具与全语料体检 —— 这些分叉一个都露不出来(与 R6.4「Node 不进判据」 同类,缺的是语料)。先补 tests/cases/18_集合.jsh 再修代码。 ⏳ 新登记(待拍板):d[精确(&quot;1&quot;)] —— 三 Python 执行器报英文 unhashable type(内部类名泄漏),Rust/Node 允许。定下来之前不要进语料。

先量再改(把循环拆段、并把同一逻辑换写法量)发现 —— Rust 的 globals 是 HashMap&lt;String, Val&gt; 且每次 LOAD_GLOBAL 先 names[a].clone()(分配 String) 再哈希查找,而 **C VM 一直是 JVal *globals(按名字下标索引的数组)。 ins.a 就是 names 下标(已验证去重)→ 改 Vec&lt;Option&lt;Val&gt;&gt; + 内建加载时预填。 交错 A/B:当循环 3.42x、纯读写 2.32x、整数累加 2.11x。 🔴 顺手修既有 bug:compare 把所有数值比较都转 f64 → 2 62 &gt; 2 62 - 1 只有 Rust 给「假」(另四个给「真」)→ 整数对整数直接走 i128(既提速又修正确性)。 📌 语料里从没有大整数比较 → 补 tests/cases/19_整数比较.jsh。 ⚠️ benchmark 的口径坑:净耗时 < 启动成本时那次减法会放大误差 (「整数循环累加」显示 55ms,放大规模实测只有 4.2ms/20000 次)→ 这种格子打 ⚠。 📌 精确倍数只认「同机同时段交错 A/B + 足够大的规模」。 ⏳ 没做(如实记):函数调用密集(帧的 2~3 次堆分配)、Val 瘦身 —— 都是"另一件大事"。 🔴 全量回归抓到一处我改出来的回归:候选名池被我缩小(names 只装「本文件 出现过的名字」)→ 打印(长渡([1])) 的「你是不是想写「长度」?」没了;而我自建的 用例恰好避开了这个形状(它把「长度」写进了文件里)。修法照 C VM(pool = 全部内建 + 用户全局)。📌 用例要挑「会让口径分叉的数据」;单项验收绿 ≠ 没坏。 🔴 全量回归抓到一处我改出来的回归:候选名池被我缩小(names 只装「本文件 出现过的名字」)→ 打印(长渡([1])) 的「你是不是想写「长度」?」没了;而我自建的 用例恰好避开了这个形状(它把「长度」写进了文件里)。修法照 C VM(pool = 全部内建 + 用户全局)。📌 用例要挑「会让口径分叉的数据」;单项验收绿 ≠ 没坏。**

📌 先量(按参数个数拆场景):Rust 一参比零参多 350ns、三参多 595ns, 而 C VM 与参数个数无关 → 差在「每次调用的固定开销」。三处根因: ① instrs.clone() —— 每进一帧克隆整个指令数组(为绕开借用检查)→ 换持有方式:module: Rc&lt;Module&gt;,Rc::clone + 借引用(单这一项 1.41x); ② HashMap&lt;usize, Val&gt; 装参数(1~3 个参数)→ Vec&lt;Option&lt;Val&gt;&gt;,并省掉 中间的值表;③ 每帧两次堆分配 → 帧池。 ⚠️ 帧池最危险两处:locals_set 残留 → 读到上一帧的值(不报错的错答案); cells 复用 → 闭包看到别的函数的单元 → pop_frame 全清、bind_args 重置 (test_m74 钉住)。 ⚠️ 更正 D66 的错误估算:那儿说「instrs.clone() 只占 ~2%」,实际它是最主要的 一处。📌 「某处只占 x%」——不改掉量一遍就别写。 交错 A/B:斐波(24) 1.48x、一参 1.47x、三参 1.44x; 对 C VM 的差距 2.0~2.3x → 1.2~1.5x。 ⏳ 仍未做:Val 瘦身(要动表示、收益未量,独立一件)。

jishi-rs --walk AST.json —— 直接遍历 AST 求值、不经过字节码,与字节码 VM 互为参考实现(字节码 VM 的天生盲区:字节码生成错了它照着错的跑、"一切正常")。 📌 关键设计决策:吃 AST JSON,而不是让宿主自己 parse —— AST 已是契约产物 (两前端都产它),吃 JSON 就不必把 core 的 pyo3 依赖解耦进宿主 (查过:parser.rs/compiler.rs 其实不依赖 pyo3,只有 lib.rs 那 30 处是包装层), 也与 R5「产物优先给文本」同一思路。 ⚠️ 第一个切片只实现 8 种节点;未实现的节点明确报错,不静默给错值。 Val/binop/display/内建表是共用的(有意如此:要抓的是「编译器 vs 求值」, 不是两份 + 谁对)。 📌 新工具 tools/check_walk.py(进度仪表):语料 70 · 一致 7 · 还没实现 63 · 不一致 0 —— 「还没实现」那列就是下一步的清单。 ⏳ 没做完:① 控制流+字面量 ② 内建调用接上(✅ 见续五)③ 函数与闭包 ④ 类 ⑤ 异常与导入 ⑥ 报错渲染对齐。

用户拍板走「改签名」那条):内建签名 fn(&amp;[Val], &amp;mut VM) → &amp;mut dyn BuiltinHost, VM 与 Walker 各实现它 → 内建语义只有一份实现(new_builtins() 那张表), 互拍不再夹着「两份 + / 两份 打印」的噪音。 📌 接口是「先把用法列出来」再定的:数了一遍,内建只用到 5 个能力 (format_value / lookup_method / make_iter / call_value / output) → 接口就 5 个方法。⚠️ 加方法要「有意识地加」,有结构测试钉着这条。 ⚠️ 规模比想的大:lib.rs 只有 4 处,标准库有 217 处(23 个文件)—— 批量替换前先断言「所有 &amp;mut VM| 的处数 == 预期」(217 == 217), 形态不同就停下来看清楚(真用 vm 的只有 6 处)。 顺手把 format_value 的「容器递归 + 标量显示」抽成自由函数 format_value_with (两个执行器共用,只有「自定义对象怎么算文本」是宿主特有的)。 ⚠️ 树遍历侧做不到的两个能力明确报错(回调用户函数 / 自定义对象的「文本()」), 不静默给错值。 🔴 接上之后立刻撞出一处真问题:run_walk 把出错前已经打印的内容丢了 (返回 Result&lt;String,_&gt; 而不是元组)—— 字节码 VM 在 R6.2 就统一过 「出错前写进输出流的要吐出来」,于是 02_计算器 在空 stdin 下被判成不一致。 📌 判据扩大覆盖面的那一刻,就是旧实现露馅的那一刻(同 R6.4「把 Node 拉进判据」)。 📌 顺带把树遍历的「找不到名字」候选池补上内建名(长渡 → 提示「长度」—— 与上一轮字节码 VM 那个坑同源)。 ⏳ 顺带发现:字面量 真 / 假(前端编成 Num 且 value 是布尔)与 空(是 Name)还没接 —— 归下一步「字面量」。 📌 进度仪表:一致 7 → 11(还没实现 63 → 60,不一致 0)。 新增 tests/test_m76_walk_builtins.py(22 项)+ 语料 21_内建调用.jsh; 两条结构钉住:walk.rs 里不许有注册内建的动作、BuiltinHost 的方法数必须是 5。

补上 If(否则如果 = 多一条 branch + 单独的 orelse)/ While / For / Loop / Break / Continue / Pass / Compare(含链式)/ BoolOp / UnaryOp / List / Dict / Subscript(读)/ TargetList(解包)/ 复合赋值 += 那一族。 📌 三个「藏在别处」的字面量(照节点类型的直觉写一定翻车): ① 真/假 是 Num 且 value 是布尔(不是 Bool 节点); ② 空 是 Name(前端在编译期换成 none 常量,AST 里只剩个名字); ③ 中断 才是 break(不是 跳出 —— 我第一版对拍脚本就写错,结果两边 一起报「找不到名字」,看起来像通过)。 📌 两处「容易想当然」的语义:链式比较要短路(1 &gt; 2 &gt; d[&quot;键&quot;] 的第二节 求值根本不该发生);与/或 结果一定是真/假(1 或 2 给「真」不是 1)。 🔴 顺手修掉一个「不报错的错答案」:unary_neg 原来返回 Val、非数字一律给 空 → -「x」 在三个 Python 执行器上都报「不能取负号」,而 Rust 打印出「空」。 改成返回 Result 并报同一句话;-真 也对齐成 -1。 📌 共用也是共债:树遍历要调这个函数,才发现「共用的那一份」自己就是分叉源。 ⏳ 顺手量出 5 处既有分叉(都没修,登记待拍板):-「文本」 / -列表 (Node 给 NaN / -1,都是不报错的错答案)、-空(Python 那三个打的是 「None」 —— 内部表示泄漏)、循环 &quot;3&quot;(Rust 报错)、循环 3.7(Node 报错)。 ⚠️ 定下来之前这些形状不进语料(22_控制流.jsh 的文件头就写明了避开了哪些)。 📌 仪表:一致 11 → 25(还没实现 60 → 47、不一致 0)。 新增 tests/test_m77_walk_control.py(73 项)+ 语料 22_控制流.jsh; 三条结构/契约钉住:语义函数不许在 walk.rs 里重写、真/假/空 的节点类型、 链式比较短路。

补上 FuncDef / Return / Lambda + 词法作用域链(walk.rs 的 Scope)。 📌 两套做法、同一套语义:字节码用「编译器算好的槽位 + 单元表」,树遍历用 「读沿链找、写只写当前作用域」(= Python 侧 Environment)—— 能共用的是「判断」不是「存储」:形参绑定(含全部报错文案)抽成了 bind_params,两个执行器都调它。 ⚠️ Val 多了一个变体 Val::WalkFn(不能复用 Val::Func:那装的是字节码的 code_idx),但显示/类型/同一性/可调用判定必须与 Val::Func 完全一致。 顺带:内建现在能回调用户函数(映射/过滤/排序按/归约)。 🔴 最容易写错的一条:「赋值即局部」必须先判(在沿链找之前)—— 一个名字在本函数赋过值,读它就不许穿透到外层。顺序放反的症状是 静默拿到外层的值(不报错、答案错);我第一版就是这么写的,对拍一条就露了。 ⚠️ 配套的「扫本函数赋过值的名字」不能递归进嵌套的 FuncDef/Lambda 体。 另外两处:返回 要穿出循环(写成 _ =&gt; {} 会吞掉);默认值在定义处求值。 🔴 语料第一次「问类型」,C VM 的内裤就露出来了:打印(类型(加倍)) 在 C VM 上 打的是内部类名 CvmFunction(另四个都是「函数」)—— 根因是 runtime.type_name 的兜底手写清单漏了 CvmFunction(只有 UserFunction/ VmFunction)。它一直躺着,因为此前没有任何语料拿 类型() 问过函数。 已修 + 补五执行器测试。📌 又是「判据没覆盖到 = 没实现」。 📌 仪表:一致 25 → 39(还没实现 47 → 34、不一致 0)。 新增 tests/test_m78_walk_functions.py(46 项)+ 语料 23_函数与闭包.jsh; 两条结构钉住:walk.rs 不许自己拼形参错误文案(必须调 bind_params)、 「赋值即局部」的判定顺序。

ClassDef / Attr / Subscript 作赋值目标全通,顺带接上内建类型的方法 (列.追加(…) 那些)。📌 核心决定:不另造一套「类」 —— 树遍历产出的就是 宿主那个 Class(方法表里放 Val::WalkFn),于是 是实例 / 超() / get_attr / set_attr / display / type_label / identity_key 一行都不用改就能用。📌 加 10 行 pub(crate)、省下几百行重复语义 (而且省下的正是最容易悄悄漂的那种)。 📌 self 用「WalkFn + self_val」表达(字节码侧是 BoundMethod); ⚠️ 显示 / 类型 / 同一性三处必须跟着走(绑定方法仍明确报错 —— Python 里 a.方法 是 a.方法 为假)。 📌 顺带把内建方法表(248 行 / 73 分支)从 impl VM 搬成自由函数 (call_builtin_method(host: &amp;mut dyn BuiltinHost, …))—— 先量出它只依赖 一个宿主能力(call_value,4 处)才敢搬;self. → host.,逻辑一行没动。 ⚠️ 另外两处「差一点静默错」:遍历 自定义对象 必须走宿主的 make_iter (否则「定义了 迭代() 的对象」遍历不了);调用类 = 造实例要在 eval_call 里加分支(新建 甲(…) 只是个普通 Call)。 📌 仪表:一致 39 → 53(还没实现 34 → 21、不一致 0)。 新增 tests/test_m79_walk_classes.py(47 项)+ 语料 24_类.jsh; 结构钉住:walk.rs 不许另实现 get_attr/set_attr/call_builtin_method。 ⏳ 顺手记一笔(没改):新语料里写个叫 文本 的方法会被静态检查的 零误报闸门拦下(name.shadow)——「方法名遮蔽内建」算不算误报可以再议, 本轮先照闸门走(改名 介绍,文本() 钩子改在测试里覆盖)。 📌 仪表:一致 53 → 75(还没实现 2 → 0、不一致 0)—— ✅ 语料里出现过的节点类型全部实现了。 📌 顺手修一个真 bug:宿主把不存在的异常类型名 数值错误 当类型用了三处 → 捕获 值错误 在 Rust 宿主上接不住切片错误(其余四个都接得住)。 📌 共用了一处报错「头行」(error_head)——同一段源码两个宿主曾打出不同的字。 🔴 参考执行器抓到一个真 bug(登记,本轮不修):「捕获体里 返回」时四个 字节码宿主都不跑「最终」块,规范跑(根因在编译器 c_try)。登记在 tests/test_m80_walk_exceptions.py 的一条测试(会过期报红)+ tools/check_walk.py; ⚠️ 定下来之前这个形状不进语料。📌 这正是参考执行器存在的理由。 新增 tests/test_m80_walk_exceptions.py(46 项)+ 语料 25_异常与导入.jsh。

字节码 VM 逐字一致(源码行 / ^ / 调用链 / 提示,都走共用的 render_error_with)。check_walk.py 升级成「两个实现完整行为互拍」 (比 stdout + 报错渲染)。新增 tests/test_m81_walk_render.py(18 项)。 🔴 两个实现细节:① 错误对象补位置 —— 共用的 binop/get_item 等返回的错误 不带行列,在 eval/exec_stmt 分派层用「出错节点」位置补(⚠️ match arm 里的 ? 会提前 return 绕过补位置,得改成 .map(...));② 方法调用报错 位置用 BoundMethod 的「取属性」处(不是方法名)。 🔴 登记一个既有分叉(不修):方法调用 / 类实例化的调用链 —— Python 规范 没有方法帧(实例.方法() 走 UserFunction.__call__、不动 _call_stack), 字节码 VM 有;参考执行器站在规范这边。另量到 递归无深度限制(Python 有, 两个 Rust 宿主一个爆内存一个栈溢出)。登记在 tests/test_m81_walk_render.py。

下一步:实测定 C VM 去向。 ✅ R6 主体已开工(2026-10-03):第一步字典 O(n²) → O(n)(保序 Vec + 哈希索引;⚠️ 不能直接 HashMap&lt;Val,_&gt; —— Val 的 == 跨类型数值相等, 哈希键要单独规范化成 DictKey)。顺带修 Rust 字典 == 顺序相关、Node 字典 跨类型键两个分叉(docs/设计决策.md D64)。 ⏳ 主体继续:集合 O(n)→O(1)、整数循环/算术 vs C VM、Rust 树遍历参考执行器。 ⚠️ M49b 那个「选型决策」已随备案完成而结掉(方案 b:大陆服务器 + 域名)。 📌 (旧)M56 收完后,下一步:M49a 分发客户端收口 或 M57 性能(C VM 容器读侧, 先量再说)→ M58/M59。 📌 M49a · 分发客户端收口(可插队,与 ICP 备案无关,现在就能做)—— ⚠️ 先纠正一个旧判断:M49 的主体早就做完了 —— jishi 安装 &lt;包&gt; 默认走 https://raw.githubusercontent.com/…/packages/索引.json,实测能装通 (jishi 安装 基础库 → 已安装 1.0.0)。ICP 备案只挡「在中国大陆服务器上、 用域名对外提供下载」这一条路(腾讯云口径:与端口无关), 换香港镜像或只做客户端多源都不需要备案。 ① 多索引回退(默认 GitHub → 备用镜像 → 报错); ② 失败给中文可操作提示(换个镜像 / 用 --local-zip / 检查网络); ③ jishi 医生 加「索引可达性」自检。 → 再往后 M57 性能单独立项(C VM 容器读侧;先量,收益不够就如实写 「不做」)→ M58/M59 AI 可用性深化 + 文档示例。 ⚠️ 战略修正(2026-09-27 复核):~~「分发是『有没有人用』的闸门」~~ —— 现在已有三个入口(Marketplace 扩展已上架 / jishi 安装 能装 / 源码装),真正的闸门是「有没有人知道」,别再把它当阻塞项。 完整规划见 docs/路线图.md §九(含 §十「三条被改判的历史结论」)。 ⚠️ 时间.计时 那个「宿主对象模型」缺口仍未收(M51 记在 test_m51_consistency.py 的 KNOWN_GAPS):它要的是「标准库直接返回的 宿主对象能被上下文协议认出来」,与基石库层是两件事,别混在一起做。 ⚠️ 标准库不做 C / Rust 化(2026-09-25 实测判定,别再提): ① 161 个函数里 127 个是薄包装——底层 math / re / hashlib / json / zlib 本身就是 C 实现,「用 C 重写」等于把 C 换成 C; ② 一次调用的耗时里编组开销占 90%+:数学.开方 每次 5.69µs,其中 math.sqrt 本体只 0.071µs;即使函数体变成 0 也就能快 ~5%; ③ Rust 宿主已经用 Rust 重写了整套标准库(自写正则/SHA/inflate,见 rust/src/stdlib/),实测标准库密集 1932ms vs C VM 435ms——反而慢 4.4 倍; 顺带量到该宿主纯计算慢约 1000 倍(5 万次循环 11.7s vs C VM 10.7ms)—— 它是「最小可运行」,不是性能选项。 👉 真要提性能,靶子是「减少宿主回调次数」,不是「把标准库写成 C」 (同 §2 的结论)。

拿到了 kwargs 却只写 fn(...args)、Rust 的 Val::Builtin 签名里根本没放 关键字的位置 —— 于是 加密.摘要(&quot;abc&quot;, 算法=&quot;md5&quot;) 在两个宿主上都静默用 默认 sha256(Python 给 900150…、两宿主给 ba7816bf…),一声不响。 这是本项目最危险的一类 bug:错了不报警。

tools/gen_stdlib_sigs.py 从 jishi/stdlib/*.py 生成签名表 (node/stdlib_sigs.js + rust/src/stdlib_sigs.rs),两宿主按 「模块.函数」查表把命名参数映射回位置参数。 ⚠️ 键必须带模块前缀:全库有 19 组跨模块同名函数(解析 在 json / 参数 / 日期 都有),只按函数名查会歧义。

名字对不上 / 被给两次 / 缺必填 / 跳过了前面的可选参数(宿主按位置调用, 中间留空就得补 空 顶上,而 空 未必等价于「不给」—— 宁可报错也不猜)。

python tools/gen_stdlib_sigs.py;忘了会被 test_签名表与标准库源码同步 直接报红。

(concat!,零运行时开销)、Node 在 R.Module 构造函数里统一加前缀。 不要去几百处 new R.Builtin(...) / Val::Builtin(...) 手写前缀。

内建函数(打印 等)没有前缀 —— 所以宿主独有内建收到命名参数会走 「查不到签名表」那条路报错,而不是静默。<br> 仍然的边界:选项 形式的函数(纯变参)不在签名表里,命名调用会报错 (不是静默);数学.圆周率 这类常量**没纳入漂移检测(见下条)。

两宿主各加了 --dump-stdlib(由宿主自己报家底,比让测试去正则解析宿主源码 可靠得多:解析一改就崩,还会把注释里的名字也算进去)。测试断言三件事: ① 模块名 + 函数名对 Python 侧(jishi --lang-spec)全覆盖、不多不少; ② 两宿主彼此也对得上(防「只修了一边」);③ 签名表与源码同步。

点名「Node 缺:统计」;把 Node 的 mapKwargs 退回「静默丢 kwargs」→ 7 条报红(含 md5 vs sha256 那条)。检测真的会响,不是空壳。

(正弦/余弦/正切/对数/常用对数/角度转弧度)+ 文本(填充/截断)+ 迭代 (累积/组合/排列/笛卡尔积)共 12 个;Rust 补后两类共 6 个。三边逐字节一致。

还要支持上下文协议(用 … 为 t: 脱糖成 进入 / 退出),而两宿主的 ctxMethod 只认「Jishi 实例」和「内建容器」—— 标准库直接返的宿主对象 走不通。这不是补一个函数的事,是宿主对象模型的缺口。 归属 M52/M53:把 计时 改写成基石模块后它天然是 Jishi 实例, 五引擎都能跑,这类缺口会结构性地消失。缺口写在测试的 KNOWN_GAPS 里 —— 不掩盖,也不让它静默。

(--dump-stdlib 只报 Val::Builtin,常量一致性靠别的对拍测试); ② 同名函数的参数个数与顺序(本测试只比名字;参数个数一致性 留给 M52 的基石库层从根上解决)。

宿主加了 --lang-spec / --ai-card(由宿主自己报家底),与 jishi --lang-spec / jishi --ai-card 逐字节一致;另有一条不经 CLI 的 结构化对比(直接比 ai.build_lang_spec() + content_hash 的算法)。

python tools/gen_langdata_rs.py 一次性搬运(关键字 / 运算符 / 内建文档 / 标准库 229 条函数说明 / 错误码 / 等价 Python 写法)。改了 Python 侧元数据必须 重跑生成器再重编宿主,忘了会被 test_m90 当场报红 —— 这就是拍板①说的 「Rust 侧显式表 + 两侧各 --dump 自报家底来比」。

—— 键顺序也是内容的一部分;content_hash 还是「紧凑 + 键排序」的 sha256 前 8 位。所以 langspec.rs 自带一个保序 JSON 值 + 两个序列化器 (缩进版 / 排序紧凑版)。

全红」;oracle.jishi.cli._setup_io() 已经 reconfigure(newline=&quot;\n&quot;),所以走 CLI 那条路是干净的(自己写脚本比对时别忘了这一点)。

独立判据 + 计时 护栏);以及编辑器自带的 editors/vscode-jishi/verify-lsp.js(两边各跑一遍,各 25 项)。 JISHI_BIN=&lt;二进制&gt; JISHI_ARGS=&quot;lsp&quot; node editors/vscode-jishi/verify-lsp.js。

on_did_open 抛出、文档根本不注册(IncrementalDocument 在构造里就分词, 失败即抛出,dispatch 又把通知的异常吞掉)。想让「编辑器能打开一份有词法错的 文件」,那是先改 Python 侧**、再两边一起搬 —— 别只在 Rust 这边「顺手修好」, 那样报文就不一致了。判据里两个方向都钉着(语法错要能开 / 词法错不注册)。

快 5~6 倍(20003 行每次编辑 1224ms → 207ms,同机同时段实测)。少一份状态机 = 少一处会漂的地方。这条要重开得靠新实测数据。

对应 Python 的 scope.py)、rust/src/jsonw.rs(保序 JSON 写出器, --lang-spec 与 LSP 共用)。

与 rust/src/mcp.rs(jishi-rs mcp)。

(与 jishi &lt;文件&gt; 对齐),字节码要走 --load-bytecode。tests/ 与 tools/ 里 25 个文件、35 处调用点跟着补了开关。加新测试时别再直接喂字节码。

另有 10 个显式登记的跳过 —— 6 个读标准输入、3 个 从 python 导入 (单二进制的既定能力边界)、1 个 时间.计时(m51 的 KNOWN_GAPS)+ 非确定性 跑分输出;补掉一条就回去删一行)与 tests/test_m92_cli_rust.py / tests/test_m93_mcp_rust.py(对拍 + 不依赖 Python 的独立判据)。改 CLI/MCP 行为三处都要跑。

os error 231)——jishi-rs --mcp-spawn-probe 是复现入口,实测只有 stdin 方向有问题(stdout/stderr 换 null 都不影响)。run_script 因此用临时文件 当 stdin(--stdin 语义不变,只是不走管道),临时目录写不进去还会退到当前目录。

(死循环时 stdout 全空 + 退出码 1;0.5 秒的超时要跑十几秒 —— 泄漏的守护线程抢 GIL),所以「超时」那一格不进对拍,只钉宿主;② mcp_server 的 stdout 以前 没像 cli 那样 reconfigure(newline=&quot;\n&quot;),Windows 上吐 CRLF(已修)。

① 宿主把 值错误(&quot;x&quot;) 当立刻抛而不是构造异常(令 e = 值错误(&quot;x&quot;) 在宿主上直接崩,三个 Python 执行器都能跑);② TraceFrame 少 col (调用链里那一项恒为 null);③ 树遍历的「找不到名字」hint 给 &quot;&quot;、 另两个执行器给 null;④ 空源码行的报错渲染多印两行(Python 是 if self.source_line:,空串为假 → 连源码行带 ^ 一起跳过);⑤ --ast 少印 _super_ok(Python 侧是 setattr 动态挂的,排在 vars() 末尾); ⑥ 两个路径串联函数都没做归一:文件.路径拼接 要用 PurePosixPath (吃 ./空段、保留 ..、恒用 /)、路径.连接 要用 pathlib.Path (吃 ./空段、保留 ..、平台原生分隔符,且「只带根不带盘符的段要 保留已有盘符」:C:\a / /b → C:\b)。宿主以前是手拼 / PathBuf::push, 于是 文件.路径拼接(&quot;.&quot;, &quot;x&quot;) 给 ./x、路径.连接(&quot;.&quot;, &quot;x&quot;) 给 .\x —— 而前者还会渗进「找不到文件「X」,无法读取」这种用户可见的报错。 📌 判据在 test_m92(每组都是「对拍 + 写死的独立期望」两半)。

调用点数不准。 我按「25 个文件 / 35 处」收的工,全量回归又抓出 15 处 漏的 —— 它们藏在 _run([str(bc_p)]) 这类局部助手里,变量名不在我 grep 的模式里,check_engines / check_walk / check_cli 三条闸门一条都碰不到 (它们自己都用了新语义)。这种改动只能靠全量回归收口,不能靠清点。**

集体报红(那次的「235 failed」全是假的)。跑回归 / 量性能前先确认没有构建在后台跑。

rust/src/debugger.rs(调试会话)+ walk.rs 的调试钩子。

以及 build/_dap_cmp.py(开发期的对拍台,同样需要同步客户端)。

脚本线程跑程序」;Rust 的 Walker 不是 Send,所以是「读线程只做分帧 + 主线程既跑协议又跑程序」,暂停时由 PauseDriver 串行泵消息。

程序自由跑时收到的读请求,Python 当场就答、Rust 下一个语句边界才答 —— 那是时序差异(两边都合理)。用事件把两边卡在同一点,报文序列才可比。

变成 Option&lt;(i64, i64)&gt;(行 + 列)。顺带补齐了树遍历 trace 的 col (Python 的 Frame(name, line, col) 三个字段都在,宿主以前给 null)。

print 对每个实参各写一次、分隔符与结尾各写一次。拼接结果一样,但 DAP 把 每次 write 变成一条 output 事件 —— 条数不同报文就对不上。 加「会被 DAP 看见的输出」时,先想清楚写了几次。

jishi-rs 调试 的 --执行器 vm/cvm 明确拒绝(不静默降级)。

会话核心 R7.5 已就绪,这一轮补的是命令表(断点/取消断点/清空断点/变量/ 查看/栈/源码/帮助 + gdb 风格别名)与「读一行」的驱动(LineDriver —— PauseDriver 当初就是为它留的口子)。判据 tests/test_m95_debug_cli_rust.py (20 个场景逐字节对拍 + 独立判据 + stdin 路径)。

异常类型渲染成裸的 值错误,而 Python/Node 是 &lt;内建 打印&gt; / &lt;异常类型 值错误&gt;。旧测试没覆盖到(test_m32 的 _agree 只比 Python VM 与 Node,没算上 Rust)—— 是 变量 全部 的对拍照出来的。已补进 test_m32_boundaries.py::test_display_of_builtin_and_module(带 Rust 一起比)。

树遍历的内建在 Walker::builtins 另一张表 —— Walker::builtin_entries() 是纯显示用的合并(不往作用域里塞东西)。

jishi-rs 安装/卸载/列表/发布/索引/检查。

不依赖 Python 的独立判据 + file:// 本地镜像走完整条安装链)。

但 Python 侧的 检查 是包依赖冲突、源码检查叫 源码静态检查(别名 静态检查)。R7.6 把 检查 让回包管理,源码检查改用 源码静态检查 —— 否则等 Rust 顶上 jishi 这个名字时,脚本会静默走错命令。 调用点跟着改了 test_m86 / test_m87。

ZIP_DEFLATED、宿主用自己那份 stored 写出器,与 压缩.打包 同一条)。 所以 发布 / 索引 --checksum 的体积与 sha256 两边对不上(测试里归一化 这两处再比)。内容完全一样,stored 是合规 zip(拿 Python 的 zipfile 当裁判验过)。不影响安装。

Python 走 argparse(usage: … + error: …,换行跟终端宽度有关), 宿主打的是一行中文用法 —— 只对齐退出码 2;② 网络错误的具体文案 (Python URLError: &lt;urlopen error [WinError …]&gt; vs 宿主「发请求失败…」), 外层结构逐字一致。

(sha256_hex)、http.rs(fetch_bytes,顺带把 client::request 的返回 从 String 改成原始字节,文本调用方走新的 request_text)。

wasm + 退役 Python/JS,收尾后进 0.3.x。验收 体积 ≤5MB / 启动 ≤100ms (开工实测 4.60MB / 92ms —— 两条都擦线,要盯着别退;现有 Python 发行包 20MB)。

之后,jishi-frontend / jishi-ffi 零改动就能编到 wasm; 不需要 wasm-bindgen(裸 C ABI + 手写 shim 就够)。

tools/build_wasm.py。判据 tests/test_m97_wasm_playground.py: 12 个语料在 wasm 里的结果 JSON 与 jishi-rs --sandbox --json-result 逐字节一致 + 独立判据(写死期望 / 死循环被预算兜住 / 默认预算有限 / 无限递归被接住 / 体积)。

① std::time::Instant::now() 会 panic(time not implemented on this platform) —— 而 panic = &quot;abort&quot; 下它表现成一句英文 RuntimeError: unreachable, 看着完全不像时钟问题。⇒ sandbox.rs 的计时在 wasm 上恒 0, 真实耗时由 shim 用 performance.now() 量。 ② 没有线程 ⇒ sandbox 的「另起线程 + 超时」用不了(std::thread::spawn 直接 panic)⇒ 走 run_sandboxed_direct + 步数预算。

「一步」= 回跳一次(循环走完一圈),在跳转指令处判。 ⚠️ 这是量出来的决定:判断放在指令循环顶(每条指令都过一遍)实测 1.068x; 改成「只在回跳处判」还是 1.058x(同一轮里「同一份二进制的两份拷贝」只差 1.005x,所以不是噪声)。⇒ 干脆把整个功能关进特征:特征关掉时与加它之前 逐位同效(1.000x),只有 wasm 那条路付那 6%(换来「页面不冻住」)。

—— 根因是上面那个 Instant::now(),不是 profile。但现在它没被验证过, 所以 wasm/Cargo.toml 里暂时不写 [profile.release](1.47MB), 要瘦身得先单独量过再开。

所以兜不住没有回跳的失控(无限递归)—— 那种撞 wasm 栈上限, 由 shim 接住并翻成「程序跑飞了」,不是引擎给的错。

→ R8.4(退役 Python/Node + 改名 jishi)→ R8.5(收尾)。

不再内嵌 Python 源码、不再用 Pyodide:现在只引用 jishi-wasm.js(胶水) + jishi_wasm.wasm(引擎),页面 432KB → 7KB,不再从 CDN 拉约 10MB 的 Pyodide。 ⇒ 在线试玩与 jishi / 编辑器是同一份引擎(Rust 前端 + 字节码 VM + 原生标准库)。

引用 wasm 三件 / 体积量级);test_m97_wasm_playground.py 有一条端到端 —— 拿 DOM 桩把生成出来的那个页面在 Node 里真跑(真 shim + 真 wasm), 点「运行」比输出文本。📌 只比字符串「页面里有 JishiWasm.load」是糊不住错的 —— 渲染逻辑、错误分支、事件绑定全不在字符串里。

合法 JSON)以前是 errors 数组,与引擎的 error 对象不同形,页面得判两支 ⇒ 统一成引擎那份 {ok:false, error:{code,title,message,line,col,hint}}。

示例跑出正确输出。fetch 得到 + 实例化 + ABI 自检这条路是通的 (file:// 直接双击打不开 —— 浏览器不许读 wasm,页面里已写明改用本地服务器)。

(jishi/stdlib-jishi/*.jsh,目前有 统计),编译期展开进主程序, 五个引擎共用同一份字节码。完整设计见 docs/设计决策.md D45。

统计 = $造模块(&quot;统计&quot;, __基石库_统计__()),其中 __基石库_统计__ 是把模块源码整体包成的外壳函数(返回导出字典)。 三个执行器与两个跨语言宿主一个字都没改 —— 它们跑的只是普通字节码。

导入 X 为 _X(导出会排除下划线开头的名字);② 报错类型与 Python 侧 一致(对拍比类型);③ 热路径别放这里 —— 实测比宿主层慢 25–38 倍 (C VM 上 5.77µs → 159.67µs),原因是库代码内部每次调内建在 C VM 上都是 一次宿主回调。判据:只用列表/字典/文本/数字 + 现有内建能表达的才进这层 (编码 就因为要 UTF-8 编解码而搬不了 —— 基石没有 序数/字符 内建)。

Interpreter.run 两条路径各调一次(漏一条就是「产物里的库和实际跑的 库不是一份」);$造模块 是内部内建,三处都有 (runtime.new_builtins / node/runtime.js / rust/src/lib.rs)。

性能对比都靠它;tests/test_m52_jishilib.py 用它做「迁移没改行为」的对照。

异常冒到 cvm_bind._fail —— 那层会包成「内部错误(宿主回调)」, 用户看到的是 TypeError: &#x27;int&#x27; object is not iterable 这种天书。 M52 就是这么抓到 _cb_iter_new 里 _Iter(o) 的构造在 try 外面的。

延迟合并写;M47 C 侧原生字符串(拼接 2.94x / 比较 12.86x)+ AST 缓存; M48 方法调用融合(1.83x / 端到端 1.56x)。判定不做:M47 ② C 侧 JSON、 M48 ① 常量折叠与窥孔、M48 ③ 文件 I/O 快路径(都量过,见 docs/路线图.md)。 剩余短板:容器读侧(字典查找 0.96x)——要治它得让 C 侧真正持有容器内容 (别名 + 逃逸问题,属另一个量级的改造)。

官网 + 自建分发源上线 <https://基石.cn/>(源站=大陆轻量云 Nginx,前面 EdgeOne CDN)。 jishi 安装 改成自建源优先、GitHub 兜底;索引与包都在境内。 🔴 关键教训:HTTPS 521 的真因是「源站只听 80、而 CDN 对 HTTPS 走 HTTPS 回源」 —— 判据是源站 access_log 里有没有那条请求,别猜配置。 细节见 docs/路线图.md §9.3.6、docs/设计决策.md D52 与 .workbuddy/memory/操作细节.md 附一/附三。 ⚠️ 有 SSH 就优先 SSH(用户名 ubuntu、免密 sudo):云助手会拦一堆命令。 ⚠️ 本机到该服务器的 80/443 TCP 之前不通,2026-09-29 复测已通(22/80); 验收以「域名 + 源站日志」为准。

② 树遍历补齐「赋值即局部」。当前没有已知的未修分歧——tests/test_cvm.py 里那条 xfail 也按约定转成了正常断言。 下一批待办(M41 起)见 docs/路线图.md §3.6 与 docs/真实项目.md 的别扭点清单。