fix(ppt,tools): svg 质量门末尾出 [GATE FAIL] 汇总行——失败签名从误标"Font issues"变诚实(bump 0.58.47)
工具失败面板 shell/exit 第1簇「Font issues x20」是误标:svg_quality_checker 按设计 exit-1(质量门),但 core/toolfail 取 [exit] 前尾行当签名,而尾行永远是 通用 tip「4. Font issues:…」→ 所有门失败(typography/alignment/spec_lock drift) 全塌成一条"Font issues",误导排查方向。多文件门也没有可靠尾行(最后文件可能只有 WARN)。 修在工具侧(toolfail 保持通用零改动): - svg_quality_checker.gate_verdict_line():errors>0 时在 main() 末尾(export 之后) 多打一行确定性汇总 [GATE FAIL] svg_quality_checker: N error file(s) / M total; top issues: … 作为最后一行 stdout,toolfail 现有取尾行逻辑自动拾取诚实签名, 模型也多一行有用汇总。 - _categorize_issue 加 spec_lock/typography 类,避免真错(typography px)落 "Other"。 - 补 GateVerdictLineTests(3):无 error 不出行 / [GATE FAIL] 打头带 top issues / 是最后一行 stdout。56 测试全绿。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ab55be4cde
commit
795161adcd
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。
|
||||
|
||||
最后更新:2026-07-20(文件预览背景撤回毛玻璃、改回半透明压暗遮罩,bump 0.58.46)
|
||||
最后更新:2026-07-20(工具失败面板巡检:salvage 复盘=保持 + svg 质量门签名诚实化,bump 0.58.47)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
### 2026-07
|
||||
|
||||
- **07-20 / 0.58.47 / 工具失败面板 6 簇巡检 + svg 质量门签名诚实化**:扫近7天失败聚集逐簇定根因。①**write/edit malformed(45/32)**=100% DeepSeek v4 wire 把并行 tool_call 的 arguments 拼成 `{...}{...}`,salvage(0.58.24)复盘提前到 07-20:命中率稳 ~85%(post-launch salvaged 252/malformed 44)、残差涉事 task 全在跑无终态失败=落非流式重试自愈,**决策保持现状不再写代码**(面板绝对数被上线前 07-13/14 尾巴撑高,要看 salvaged/malformed 比)。②**shell/exit "Font issues"(20)**=`svg_quality_checker` 质量门按设计 exit-1,但 `toolfail._classify` 取 `[exit]` 前尾行当签名,而尾行永远是通用 tip「4. Font issues:…」→ 所有门失败(typography/alignment/spec_lock drift)误标成 Font issues 误导排查。修:checker 在 errors>0 时末尾多打确定性 `gate_verdict_line()`「[GATE FAIL] svg_quality_checker: N error file(s) / M total; top issues: …」作为最后一行 stdout(放 main() 里 export 之后),toolfail 现有取尾行逻辑自动拾取诚实签名、toolfail 零改动;`_categorize_issue` 加 `spec_lock/typography` 类避免真错落 "Other"。补 3 测试(GateVerdictLineTests),39+3 全绿。③**run_python 预检(31)**=pysyntax 按设计拦中文正文硬拼进 .py(引号/缩进崩),非平台 bug=提示词治理待办。④**empty(15)**=主角换成 `glm.pro52`(11/15,非旧 opus48 案),需生产跑 narrated 探针定层(本机 import litellm 卡,task e7c2f50b 备用)。⑤**web_fetch(6)**=外部 403 拒抓为主,非平台锅。
|
||||
- **07-20 / 0.58.45 / glob 绝对路径崩溃(工具失败聚集 #2)**:巡检近7天工具失败聚集,`glob` 排第2(3次/3task/3用户、近24h 仍在发)。根因:`tools/fs.py` GlobTool 直接 `base.glob(pattern)`,模型传绝对 pattern(沙箱 cwd 就是 `/workspace`,自然写 `/workspace/**/*.py`)时 pathlib 抛 `NotImplementedError: Non-relative patterns are unsupported`,整个调用崩返 `[Error executing glob]`。改:绝对 pattern 先 `_split_abs_glob` 拆成「存在的静态前缀目录 + 相对 glob 串」(左起吃掉不含 `*?[` 的组件当 base)再匹配;补首字符 `/`\`\\` 判定(POSIX 前导斜杠在 Windows 宿主 `is_absolute()` 为 False,沙箱是 Linux,两处对齐)。顺手删掉原 `if "**"`/`else` 两分支代码完全相同的死重构。`/sandbox/tools/` 是 Dockerfile `COPY tools/` 拷贝,**需重建沙箱镜像**才生效。
|
||||
- **07-20 / 0.58.45 / PPT 页脚漂移治本(工具失败聚集 #1,A+C)**:`shell/exit` 排第1(近7天16条 exit-1、近24h 6条,9task/7用户),实测拆解 15/16 是**逐页手写绝对坐标偏离 `spec_lock.layout_grid` 锁定值 2–16px 的「想对齐没对齐」**(footer_y 近失 27行为最大头,margin_x/content_top 各7行,content_bottom 溢出12行),字体/spec_lock 只是 WARN 不 fail。关键发现:`executor-base.md §180-185` 早有「HARD—checker-enforced」的 snap 纪律却仍被漂移,加 prose 无用,治本须机械化。**A**:spec_lock_reference 新增 `## footer` 锁定片段——x/y 用 `margin_x`/`footer_y` 预填好、页码 `text-anchor="end"` 在 `canvas_w−margin_x`、留 `{PAGE}` 占位,executor 每页**逐字粘贴只换页码**,把 footer_y 这个跨页漂移第一大头从「每页手算」收敛成「一次编写」(executor-base 加对应硬规则,SKILL.md 同步);冒烟验证锁定片段过质检 errors=0、无 footer_y/margin_x 报错。**C**:`svg_quality_checker._ALIGN_TOL` 2→3px(2px 对手写实测过严),同步刷 SKILL/spec_lock_reference/executor-base 的「2–15px/2–12px」表述与测试值(margin drift 用例 60/63/66→60/64/68,30 测试全绿)。content_bottom 溢出(装太多项,另一性质)与 grid-snap 后处理(B)本轮未做。
|
||||
- **07-19 / 0.58.43**:文件预览遮罩改整屏、消除"上暗下亮"割裂——旧实现用 `#file-preview-modal { bottom: var(--preview-bottom-inset) }` 把遮罩顶到 chat-form 之上,好让预览时能边看边打字,代价是底部整条(输入区+左栏入口+右栏+状态)全不压暗,与上方压暗区形成一条硬边界的通亮带,很割裂。改为遮罩铺满整屏(`bottom`→`padding-bottom`,仍让居中卡片停在输入区之上不遮挡),底部随整屏统一压暗;仅 chat-form 由新 `body.fp-open` 类 `position:relative;z-index:95`(压过遮罩 z90,层叠上下文已核:`#app`/`#pane-mid` 均不建 context)+ 上向阴影,上浮成"悬浮活动条",继续可打字。预览时左栏/右栏/状态点击落到遮罩=关预览(符合"背景不可用"预期)。改 `web/static/dev.html` + `web/static/js/preview.js`(open/close 加/去 `fp-open`)。
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
|
||||
# 改版本只动这一行。
|
||||
__version__ = "0.58.46"
|
||||
__version__ = "0.58.47"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import re
|
|||
import json
|
||||
import html
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Tuple
|
||||
from typing import List, Dict, Tuple, Optional
|
||||
from collections import Counter, defaultdict
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
|
|
@ -1095,6 +1095,8 @@ class SVGQualityChecker:
|
|||
return 'Alignment/grid'
|
||||
elif error_msg.startswith('Geometry:'):
|
||||
return 'Geometry'
|
||||
elif 'spec_lock' in error_msg or 'typography' in error_msg:
|
||||
return 'spec_lock/typography'
|
||||
elif 'font' in error_msg.lower():
|
||||
return 'Font issues'
|
||||
else:
|
||||
|
|
@ -2325,6 +2327,26 @@ class SVGQualityChecker:
|
|||
"(<path>/<polygon>/<polyline>/<circle>/<image>) — verify dense content "
|
||||
"pages aren't just text + boxes.")
|
||||
|
||||
def gate_verdict_line(self) -> Optional[str]:
|
||||
"""One deterministic line summarizing the gate outcome, meant to be the
|
||||
LAST stdout line on failure.
|
||||
|
||||
Failure-cluster tooling (core/toolfail) signatures a non-zero shell exit
|
||||
by the last content line before ``[exit N]``. Without this, that line is
|
||||
always the generic trailing tip ``4. Font issues: ...`` — so every kind
|
||||
of quality-gate failure (typography px, alignment drift, spec_lock, ...)
|
||||
collapses onto one misleading "Font issues" cluster. This gives it a
|
||||
stable, honest signature instead. ``None`` when there are no errors.
|
||||
"""
|
||||
if self.summary['errors'] <= 0:
|
||||
return None
|
||||
top = sorted(self.issue_types.items(), key=lambda kv: (-kv[1], kv[0]))[:3]
|
||||
cats = ", ".join(k for k, _ in top) or "unspecified"
|
||||
return (
|
||||
f"[GATE FAIL] svg_quality_checker: {self.summary['errors']} error "
|
||||
f"file(s) / {self.summary['total']} total; top issues: {cats}"
|
||||
)
|
||||
|
||||
def print_summary(self):
|
||||
"""Print check summary"""
|
||||
print("=" * 80)
|
||||
|
|
@ -2576,6 +2598,12 @@ def main() -> None:
|
|||
output_file = sys.argv[idx + 1]
|
||||
checker.export_report(output_file)
|
||||
|
||||
# Deterministic gate verdict as the LAST stdout line on failure (after any
|
||||
# --export [REPORT] line) so core/toolfail signatures this cluster honestly.
|
||||
verdict = checker.gate_verdict_line()
|
||||
if verdict:
|
||||
print(verdict)
|
||||
|
||||
# Return exit code
|
||||
if checker.summary['errors'] > 0:
|
||||
sys.exit(1)
|
||||
|
|
|
|||
|
|
@ -331,5 +331,48 @@ class SpecContractTests(unittest.TestCase):
|
|||
self.assertFalse(any("CJK" in e for e in r["errors"]))
|
||||
|
||||
|
||||
class GateVerdictLineTests(unittest.TestCase):
|
||||
"""gate_verdict_line() 给 core/toolfail 一条稳定诚实的失败签名,
|
||||
替掉「[exit] 前最后一行永远是通用 tip → 全塌成 Font issues」的误标。"""
|
||||
|
||||
def _checker(self, body: str):
|
||||
with TemporaryDirectory() as tmp:
|
||||
page = _write_page(Path(tmp), "01_cover.svg", body)
|
||||
checker = SVGQualityChecker()
|
||||
checker.check_file(str(page))
|
||||
return checker
|
||||
|
||||
def test_none_when_no_errors(self):
|
||||
# 干净页面(无 error)不出 gate 行 —— exit 0 本就不进失败面板
|
||||
c = self._checker(_text(60, 100, "干净标题"))
|
||||
self.assertEqual(c.summary["errors"], 0)
|
||||
self.assertIsNone(c.gate_verdict_line())
|
||||
|
||||
def test_gate_line_on_error(self):
|
||||
# foreignObject 是 error;gate 行须以 [GATE FAIL] svg_quality_checker 打头,
|
||||
# 带文件计数与 top issues —— toolfail 归一后是诚实签名而非 "Font issues"
|
||||
c = self._checker('<foreignObject x="0" y="0" width="9" height="9"><b/></foreignObject>')
|
||||
self.assertGreater(c.summary["errors"], 0)
|
||||
line = c.gate_verdict_line()
|
||||
self.assertIsNotNone(line)
|
||||
self.assertTrue(line.startswith("[GATE FAIL] svg_quality_checker:"))
|
||||
self.assertIn("top issues:", line)
|
||||
self.assertIn("foreignObject", line)
|
||||
|
||||
def test_gate_line_is_last_stdout_line(self):
|
||||
# 契约:gate 行是 print_summary 之后的最后一行 stdout(toolfail 取尾行)
|
||||
import contextlib
|
||||
import io
|
||||
c = self._checker('<foreignObject x="0" y="0" width="9" height="9"><b/></foreignObject>')
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
c.print_summary()
|
||||
line = c.gate_verdict_line()
|
||||
if line:
|
||||
print(line)
|
||||
last = [ln for ln in buf.getvalue().splitlines() if ln.strip()][-1]
|
||||
self.assertTrue(last.startswith("[GATE FAIL] svg_quality_checker:"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Reference in New Issue