修三处 v3 遗留: Iconify 不触发 / mkdir -p 误创目录 / 平台无知
- 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) <noreply@anthropic.com>
This commit is contained in:
parent
0971a500e7
commit
38fbee9d9e
|
|
@ -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 更稳
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ description: 生成 PowerPoint 演示文稿 (.pptx)。当用户要求做汇报 P
|
||||||
| 4 | 风格 | **现代简约** (白底 + 细线 + 留白) |
|
| 4 | 风格 | **现代简约** (白底 + 细线 + 留白) |
|
||||||
| 5 | 配色 | **商务红** `#C00000` `#E15554` `#FFC107` (见上"默认主题") |
|
| 5 | 配色 | **商务红** `#C00000` `#E15554` `#FFC107` (见上"默认主题") |
|
||||||
| 6 | 字体 | **微软雅黑 + Arial** |
|
| 6 | 字体 | **微软雅黑 + Arial** |
|
||||||
| 7 | 图标 | **MSO_SHAPE 几何形状** (无外部图片资源) |
|
| 7 | 图标 | **Iconify `tabler` 集** (描边商务图标,主色染色;`fetch_icon.py` 拉到 `assets/icons/` 缓存) |
|
||||||
| 8 | 图表 | 数据 ≥ 3 个点的页用 matplotlib 配图 |
|
| 8 | 图表 | 数据 ≥ 3 个点的页用 matplotlib 配图 |
|
||||||
|
|
||||||
把这 8 项写进 `spec_lock.md`,以表格形式给用户预览,问一句"按这个开干?"。**spec_lock 写定后不再改**,有冲突回头跟用户重新对齐。
|
把这 8 项写进 `spec_lock.md`,以表格形式给用户预览,问一句"按这个开干?"。**spec_lock 写定后不再改**,有冲突回头跟用户重新对齐。
|
||||||
|
|
@ -58,9 +58,10 @@ description: 生成 PowerPoint 演示文稿 (.pptx)。当用户要求做汇报 P
|
||||||
|
|
||||||
每页流程:
|
每页流程:
|
||||||
1. 读 `spec_lock.md` (即使刚读过)
|
1. 读 `spec_lock.md` (即使刚读过)
|
||||||
2. 写一个 `run_python` block,用 python-pptx 添加这一页 (载入已有 .pptx → append slide → save)
|
2. **图标先于版式**: 这一页要用什么概念图标? 先 `glob skills/ppt/assets/icons/` 看本地有没有,没有就 `python skills/ppt/scripts/fetch_icon.py <name> --set tabler --color C00000 --size 128 -o skills/ppt/assets/icons/...` 拉一个;`add_picture` 嵌入。**几何形状(圆点/徽章/装饰线)不算图标,走 layouts.md helper 即可**
|
||||||
3. 报这一页:版式、标题、要点条数、是否含图
|
3. 写一个 `run_python` block,用 python-pptx 添加这一页 (载入已有 .pptx → append slide → save)
|
||||||
4. 用户确认 / 微调后再下一页
|
4. 报这一页:版式、标题、要点条数、用了哪些图标
|
||||||
|
5. 用户确认 / 微调后再下一页
|
||||||
|
|
||||||
**为什么逐页?** 一次性出全 deck 中途改方向就要全推翻;逐页能让用户在第 2 页就发现问题。
|
**为什么逐页?** 一次性出全 deck 中途改方向就要全推翻;逐页能让用户在第 2 页就发现问题。
|
||||||
|
|
||||||
|
|
@ -103,6 +104,7 @@ python scripts/quality_check.py <output.pptx> --spec spec_lock.md
|
||||||
- **基于"场景判断"自行换配色**(见上"默认主题"违规清单)
|
- **基于"场景判断"自行换配色**(见上"默认主题"违规清单)
|
||||||
- **缺封面 / 缺尾页(Q&A)** —— 两端都是强制项,不算在正文页数预算内
|
- **缺封面 / 缺尾页(Q&A)** —— 两端都是强制项,不算在正文页数预算内
|
||||||
- **裸白纸版式** —— 所有版式起手都必须 `apply_brand(slide, kind)`,见 layouts.md
|
- **裸白纸版式** —— 所有版式起手都必须 `apply_brand(slide, kind)`,见 layouts.md
|
||||||
|
- **业务概念页只用几何形状** —— 比如"战略目标"页只摆圆点 bullet 没有 `target` 图标,视觉太单薄;按 §阶段二第 2 步先拉 Iconify 图标再做页
|
||||||
- 一个 `run_python` 出整 deck (中途改方向就要全推翻)
|
- 一个 `run_python` 出整 deck (中途改方向就要全推翻)
|
||||||
- 跑完不做 `quality_check.py` 就交付
|
- 跑完不做 `quality_check.py` 就交付
|
||||||
- 起名 `output.pptx` / `untitled.pptx` —— 务必按主题给文件名
|
- 起名 `output.pptx` / `untitled.pptx` —— 务必按主题给文件名
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
"""Shell 执行: subprocess 跑命令,有黑名单拦明显危险操作。"""
|
"""Shell 执行: subprocess 跑命令,有黑名单拦明显危险操作。"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
from .base import Tool
|
from .base import Tool
|
||||||
|
|
||||||
|
|
@ -32,12 +36,36 @@ class ShellTool(Tool):
|
||||||
"format c:",
|
"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:
|
def execute(self, command: str, timeout: int = 60) -> str:
|
||||||
normalized = command.lower()
|
normalized = command.lower()
|
||||||
for pat in self.BLOCKED_PATTERNS:
|
for pat in self.BLOCKED_PATTERNS:
|
||||||
if pat in normalized:
|
if pat in normalized:
|
||||||
return f"[Error] blocked dangerous command pattern: {pat!r}"
|
return f"[Error] blocked dangerous command pattern: {pat!r}"
|
||||||
|
|
||||||
|
command, note = self._windows_compat(command)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
command,
|
command,
|
||||||
|
|
@ -54,6 +82,9 @@ class ShellTool(Tool):
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
return f"[Error] {e}"
|
return f"[Error] {e}"
|
||||||
|
|
||||||
|
if note:
|
||||||
|
result.stdout = (result.stdout or "") + f"\n[note] {note}"
|
||||||
|
|
||||||
parts = []
|
parts = []
|
||||||
if result.stdout:
|
if result.stdout:
|
||||||
parts.append(f"[stdout]\n{result.stdout.rstrip()}")
|
parts.append(f"[stdout]\n{result.stdout.rstrip()}")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue