From 38fbee9d9eabaf8117cc25a7d036516e9574092d Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 6 May 2026 13:12:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E4=B8=89=E5=A4=84=20v3=20=E9=81=97?= =?UTF-8?q?=E7=95=99:=20Iconify=20=E4=B8=8D=E8=A7=A6=E5=8F=91=20/=20mkdir?= =?UTF-8?q?=20-p=20=E8=AF=AF=E5=88=9B=E7=9B=AE=E5=BD=95=20/=20=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E6=97=A0=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - skills/ppt/SKILL.md: 八条对齐第 7 项默认值从 "MSO_SHAPE 几何形状 (无外部图片资源)" 改成 "Iconify tabler 集 (描边商务图标, 主色染色, fetch_icon.py 缓存到 assets/icons/)"。 阶段二每页流程加一步"图标先于版式": 先 glob 本地, 没就 fetch, 再做页。 根因: v3 砍了 icons.md 里 MSO_SHAPE 当业务图标的部分, 但 SKILL.md 默认值没同步, 模型把它写进 spec_lock 后阶段二永远不会触发 Iconify 拉取 - tools/shell.py: Windows 下拦截 `mkdir -p X [Y...]`, 走 os.makedirs(exist_ok=True)。 根因: cmd.exe 的 mkdir 不识别 -p flag, 把 -p 当字面目录名创建 - prompts/system/general_v1.md: 加 "## 平台" 段, 提醒 Windows + cmd 环境下用 run_python os.makedirs 而非 shell mkdir -p。行为前置防御 + shell.py 工具层后置兜底 Co-Authored-By: Claude Opus 4.7 (1M context) --- prompts/system/general_v1.md | 6 ++++++ skills/ppt/SKILL.md | 10 ++++++---- tools/shell.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/prompts/system/general_v1.md b/prompts/system/general_v1.md index 5913351..efca6d1 100644 --- a/prompts/system/general_v1.md +++ b/prompts/system/general_v1.md @@ -23,3 +23,9 @@ ## 路径 默认工作目录在系统消息末尾,所有相对路径基于该目录。 + +## 平台 +当前是 Windows + cmd.exe。**避免用 unix-only flag**: +- 建目录用 `run_python` 的 `os.makedirs(path, exist_ok=True)`,**不要** `shell mkdir -p`(cmd 不识别 -p,会创建名为 '-p' 的字面目录;shell 工具已对此做兜底但仍以 run_python 为优先) +- 路径分隔符用 `/` 或 `\\`,Python 内部都识别;字符串 raw 路径用 `r"..."` +- shell 工具走的是 cmd,不是 bash,管道/重定向语义可能不同 —— 复杂逻辑用 run_python 更稳 diff --git a/skills/ppt/SKILL.md b/skills/ppt/SKILL.md index 6ae57ea..7a176a7 100644 --- a/skills/ppt/SKILL.md +++ b/skills/ppt/SKILL.md @@ -47,7 +47,7 @@ description: 生成 PowerPoint 演示文稿 (.pptx)。当用户要求做汇报 P | 4 | 风格 | **现代简约** (白底 + 细线 + 留白) | | 5 | 配色 | **商务红** `#C00000` `#E15554` `#FFC107` (见上"默认主题") | | 6 | 字体 | **微软雅黑 + Arial** | -| 7 | 图标 | **MSO_SHAPE 几何形状** (无外部图片资源) | +| 7 | 图标 | **Iconify `tabler` 集** (描边商务图标,主色染色;`fetch_icon.py` 拉到 `assets/icons/` 缓存) | | 8 | 图表 | 数据 ≥ 3 个点的页用 matplotlib 配图 | 把这 8 项写进 `spec_lock.md`,以表格形式给用户预览,问一句"按这个开干?"。**spec_lock 写定后不再改**,有冲突回头跟用户重新对齐。 @@ -58,9 +58,10 @@ description: 生成 PowerPoint 演示文稿 (.pptx)。当用户要求做汇报 P 每页流程: 1. 读 `spec_lock.md` (即使刚读过) -2. 写一个 `run_python` block,用 python-pptx 添加这一页 (载入已有 .pptx → append slide → save) -3. 报这一页:版式、标题、要点条数、是否含图 -4. 用户确认 / 微调后再下一页 +2. **图标先于版式**: 这一页要用什么概念图标? 先 `glob skills/ppt/assets/icons/` 看本地有没有,没有就 `python skills/ppt/scripts/fetch_icon.py --set tabler --color C00000 --size 128 -o skills/ppt/assets/icons/...` 拉一个;`add_picture` 嵌入。**几何形状(圆点/徽章/装饰线)不算图标,走 layouts.md helper 即可** +3. 写一个 `run_python` block,用 python-pptx 添加这一页 (载入已有 .pptx → append slide → save) +4. 报这一页:版式、标题、要点条数、用了哪些图标 +5. 用户确认 / 微调后再下一页 **为什么逐页?** 一次性出全 deck 中途改方向就要全推翻;逐页能让用户在第 2 页就发现问题。 @@ -103,6 +104,7 @@ python scripts/quality_check.py --spec spec_lock.md - **基于"场景判断"自行换配色**(见上"默认主题"违规清单) - **缺封面 / 缺尾页(Q&A)** —— 两端都是强制项,不算在正文页数预算内 - **裸白纸版式** —— 所有版式起手都必须 `apply_brand(slide, kind)`,见 layouts.md +- **业务概念页只用几何形状** —— 比如"战略目标"页只摆圆点 bullet 没有 `target` 图标,视觉太单薄;按 §阶段二第 2 步先拉 Iconify 图标再做页 - 一个 `run_python` 出整 deck (中途改方向就要全推翻) - 跑完不做 `quality_check.py` 就交付 - 起名 `output.pptx` / `untitled.pptx` —— 务必按主题给文件名 diff --git a/tools/shell.py b/tools/shell.py index 8ca0510..848a4ec 100644 --- a/tools/shell.py +++ b/tools/shell.py @@ -1,7 +1,11 @@ """Shell 执行: subprocess 跑命令,有黑名单拦明显危险操作。""" from __future__ import annotations +import os +import re +import shlex import subprocess +import sys from .base import Tool @@ -32,12 +36,36 @@ class ShellTool(Tool): "format c:", ) + # Windows cmd 不识别 unix flag,常见踩坑命令直接在工具层兜底 + _MKDIR_P_RE = re.compile(r"^\s*mkdir\s+-p\s+(.+?)\s*$") + + def _windows_compat(self, command: str) -> tuple[str, str | None]: + """Windows cmd 下把 unix 风格命令转译为可执行形式。 + + 返回 (转译后命令, 转译说明 or None)。无需转译时第二项为 None。 + """ + if sys.platform != "win32": + return command, None + m = self._MKDIR_P_RE.match(command) + if m: + paths = shlex.split(m.group(1), posix=False) + for p in paths: + p = p.strip('"').strip("'") + os.makedirs(p, exist_ok=True) + return ( + "echo [shell-tool] mkdir -p handled in-process (Windows cmd doesn't support -p)", + f"intercepted `mkdir -p`: created {paths} via os.makedirs", + ) + return command, None + def execute(self, command: str, timeout: int = 60) -> str: normalized = command.lower() for pat in self.BLOCKED_PATTERNS: if pat in normalized: return f"[Error] blocked dangerous command pattern: {pat!r}" + command, note = self._windows_compat(command) + try: result = subprocess.run( command, @@ -54,6 +82,9 @@ class ShellTool(Tool): except FileNotFoundError as e: return f"[Error] {e}" + if note: + result.stdout = (result.stdout or "") + f"\n[note] {note}" + parts = [] if result.stdout: parts.append(f"[stdout]\n{result.stdout.rstrip()}")