fix(cache): RENDERER_VERSION 7 —— #282 改变了 404 兜底正文(#282) v4.12.25 把 pydoc 404 兜底从 `pydoc3 -k` 扫描改成 FTS 模块索引,但当时没有 bump 版本号。not_found 行按 24h TTL 缓存且带正文,部署前写入的行会继续返回 扫描时代的旧正文(或扫描超时留下的空正文)——发布本身到不了它们。 按 CLAUDE.md「渲染输出变化必须 bump」补到 7,并写明这一版改了什么、旧行里 是什么。 Co-Authored-By: Claude code 2.1.311 with deepseek-v4.1-flash-1017 <noreply@taotoken.net>
perf(pydoc): 404 兜底不再跑 8–20s 的 pydoc3 -k 扫描(#282)
根因(已复现):`/pydoc/<非点号未知名字>` 的页面兜底会再跑一次 `pydoc3 -k`
关键词扫描。实测:
/pydoc/DefinitelyNotAModuleZzz123 → 404, 20.409s
/pydoc/sos.report.plugins.kafka → 200, 5.895s(含点号,跳过扫描)
扫描是「你是不是想找」的提示,不是存在性检查 —— 主查询 `pydoc3 <name>` 已经
失败过。按一天的爬虫流量量过:126 个不同的非点号名字,每个 8–20 秒,零结果。
最近的超时名字是 bzxmlrpc / bzmysql / Collection,全小写的猜测名同样吃满,
所以不能只按 CamelCase 过滤。
修法:给 `getPydocSearchPage()` 增加 `$allowShellScan`,页面兜底传 false,
改由已有的 FTS 模块索引(毫秒级)回答提示;搜索页保持扫描——用户在那儿确实
要搜索,关键词映射正是扫描擅长的。
没有扫描时,空结果按「索引里没有」处理(短 TTL),不冻结成 24h 的 404 ——
索引可能滞后,那不等于模块不存在(#247 的教训)。
ri 侧不需要改:页面兜底本来就没有第二次子进程(phpMan.php:607),剩下的
0.4–1.3s 是主查询本身。
测试:pydocShouldShellScan 的决策矩阵 + 调用点 tripwire + 「不扫描时 <2s」
(实测 0.012s)。全量 992 条断言、语料门全绿。
Co-Authored-By: Claude code 2.1.311 with deepseek-v4.1-flash-1017 <noreply@taotoken.net>
fix(release): 基线解析不出来时失败关闭,不再静默放行 上一版守卫在 PREV_RV 为空时(上一个 tag 早于 RENDERER_VERSION,或引号风格 不同)会与任何 HEAD_RV 都不等,于是静默放行 —— 一个失败开放的判据。 - 基线为空 → 中止,并提示用 RENDERER_VERSION_BASELINE=<n> 显式提供基线。 - sed 同时接受单引号与双引号,引号风格变化不会让基线失效。 - HEAD 侧解析不出来的检查保留。 实跑验证:合成仓库里「基线 tag 缺定义」→ 拒绝;带显式基线 → 放行。 真实 tag v4.12.24 与 HEAD 都解析为 6。全量 982 条断言全绿。 Co-Authored-By: Claude code 2.1.311 with deepseek-v4.1-flash-1017 <noreply@taotoken.net>
feat(release): 语料黄金变了就必须 bump RENDERER_VERSION(#255) v4.12.23 部署了两处渲染修复,线上却原样返回旧输出:页面缓存按 RENDERER_VERSION 失效,而那次没有 bump。语料门直接调渲染函数、绕过缓存, 所以它能证明"渲染器对了",证明不了"线上会变"。发布前置检查补上这个缺口: - 与上一个发布 tag 比较 test/corpus/golden:变了就要求 RENDERER_VERSION 的**解析数值**也变,否则中止发布。比较数值而不是 diff 行数 —— 删掉定义或 只改注释都会让行计数非零,守卫会被绕过(安全评审指出)。 - 触及渲染路径(format_*/source_*/web_header/footer/exec/tldr)但黄金没变时 只打提示,不中止:无谓的 bump 会让整库缓存失效、部署后瞬间全量重建, 正是 #259 那类 503 的触发条件。硬判据只认有证据的输出变化。 - 没有 src/config.php 的检出(测试夹具)跳过这条守卫。 测试:脚本正文 tripwire + 四条路径实跑(黄金变未 bump → 拒绝;渲染路径变未 bump → 放行并提示;已 bump → 放行;删掉定义 → 拒绝)。全量 981 条断言全绿。 Co-Authored-By: Claude code 2.1.311 with deepseek-v4.1-flash-1017 <noreply@taotoken.net>
fix(cache): RENDERER_VERSION 6 —— 渲染修复需要让旧缓存失效(#255 #281) v4.12.23 部署后线上仍在返回旧输出:页面缓存按 RENDERER_VERSION 失效, 而两次渲染修复(SGR 状态机、pydoc L2 模式门)只改了渲染代码,没有 bump。 命中缓存的 HTML 仍是 `<b>-f <u></b>ARCHIVE</u>`,markdown 仍是 `**-f _**ARCHIVE_`,perldoc 正文仍是 `### chomp`。 bump 到 6,让缓存行在首次读取时全部失效、按新渲染器重建。 Co-Authored-By: Claude code 2.1.311 with deepseek-v4.1-flash-1017 <noreply@taotoken.net>
fix(render): pydoc 的 L2 启发式只在 pydoc 模式生效(#281)
`detectL2IndentedPatterns()` 的两条 pydoc 规则(` class Name(` 与
` name(`)没有模式门,于是 perldoc/man 正文里恰好 4 空格缩进、以
`name(` 开头的行会被提升为 L2 小节:
chomp( LIST ) → ### chomp
nasty_break() is ... → ### nasty_break
perldoc 三页语料共 58 行受影响:正文被当成标题,TOC 里冒出假条目。
给两条规则加上 `$mode === "pydoc"` 的门;man 页不受影响(see-also 是 7 空格)。
语料门先红后绿:perldoc-perlfunc/perlvar/DBI 的黄金在 8.3 上重新生成,
差异仅为这些行从 `### xxx` 变回正文,已逐行核对。
Co-Authored-By: Claude code 2.1.311 with deepseek-v4.1-flash-1017 <noreply@taotoken.net>
test(corpus): 黄金/文本完整性/结构/JSON 四条检查(#255) Co-Authored-By: Claude Code <noreply@anthropic.com>
test(corpus): 采集六页真实语料与黄金输出(#255) Task 8 收尾:在生产机(man 2.12.0 / groff 1.23.0 / perl 5.38.2)冻结 六页真实工具输出(man-ls、man-tar、man-ssh_config、perldoc-perlfunc、 perldoc-perlvar、perldoc-DBI,共 922KB),黄金由生产 /usr/local/php83 (PHP 8.3.36)对冻结 raw 重渲染生成——raw 字节是唯一事实源,黄金只是 它的确定性投影。 - test/corpus/raw/:六页冻结输出;test/corpus/golden/:6×.md + 6×.fingerprint;test/corpus/VERSIONS.md:工具版本 + 每页字节数/sha256。 - test/corpus/normalize.php(Ruling 22):corpusNormalizeText() 把 邮箱改写为渲染器混淆后的 "user AT host" 形式,raw 与 HTML 两侧 token 才可比——内容保持归一化,不放松断言。 - test/unit/test_corpus_normalize.php:补 5 条邮箱归一化断言。 审阅:六页 .md 无 ESC/\x08/?(0lines)/空标题/超长行/控制字符; ad59aab、d2086fe 两轮修复后 markdown 无 _** 交错(man-tar 28 行、 man-ssh_config 4 行均呈 **_ 正确形式)。 门禁:本地 PHP 8.5 make test-corpus 六页 [OK] + [PASS] xhtml (6 页 dtd-valid);生产 PHP 8.3.36 check 六页 [OK],xhtml 因该机 无 xmllint 按设计 [SKIP](DTD 校验由本地对同样字节完成)。token multiset 六页 missing=0。make test-unit 949 passed, 0 failed。 Co-Authored-By: Claude code 2.1.311 with glm-5.3-flash <noreply@taotoken.net>
test(corpus): 语料清单、归一化库与结构指纹(#255) - test/corpus/pages.txt:6 页语料清单(tab 分隔,# 注释,空 section) - test/corpus/normalize.php:corpusReadManifest / corpusStripFormatting / corpusNormalizeText / corpusTokens / corpusMissingTokens / corpusTextFromHtml - test/corpus/fingerprint.php:jsonStructureFingerprint 从 structure_regression.php 原样移出,供后续 corpus 工具复用 - test/unit/test_corpus_normalize.php:21 断言;run_all.php 登记后 853 passed, 0 failed Co-Authored-By: Claude code 2.1.311 with deepseek-flash <noreply@taotoken.net>
test(corpus): 有清单但 raw 为空时必须失败,不再静默 SKIP(#255) Co-Authored-By: Claude Code <noreply@anthropic.com>