@yuhan1124/draw-prompt 0.4.6 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/SKILL.md +2 -1
- package/package.json +1 -1
- package/scripts/prompt_cli.py +228 -13
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ Prompt 的默认结构是三段:`User visual brief` 原始视觉需求、`Styl
|
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
npx --yes --registry=https://registry.npmjs.org/ @yuhan1124/draw-prompt@latest status
|
|
38
|
+
npx --yes --registry=https://registry.npmjs.org/ @yuhan1124/draw-prompt@latest doctor
|
|
38
39
|
npx --yes --registry=https://registry.npmjs.org/ @yuhan1124/draw-prompt@latest convert "茶饮新品海报,写冷泡系列"
|
|
39
40
|
npx --yes --registry=https://registry.npmjs.org/ @yuhan1124/draw-prompt@latest install-skill --target codex
|
|
40
41
|
```
|
|
@@ -116,6 +117,7 @@ Harness,作为下一次转化的辅助信号。
|
|
|
116
117
|
|
|
117
118
|
```
|
|
118
119
|
convert "自然语言画图需求" [--style-preset premium] [--strict-text] [--out p] # 默认 single-pass:需求 → Prompt / handoff
|
|
120
|
+
doctor # 安装后运行时自检:包文件、版本、核心场景转化
|
|
119
121
|
compose "长输入/文档内容" --max-images 6 # 长输入 → 多图视觉计划
|
|
120
122
|
variants "自然语言画图需求" --style-presets all # 同一输入 → 多风格 Prompt 组
|
|
121
123
|
styles --json # 查看内置风格预设
|
package/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ description: >-
|
|
|
8
8
|
画图的指令"、"优化我的出图 prompt"、"按我的风格生成 prompt",或在用 GPT Image 2 /
|
|
9
9
|
gpt-image-2 出图前需要一段精准提示词时,使用本 skill。
|
|
10
10
|
metadata:
|
|
11
|
-
version: 0.4.
|
|
11
|
+
version: 0.4.7
|
|
12
12
|
openclaw:
|
|
13
13
|
anyBins: ["uv", "python3"]
|
|
14
14
|
---
|
|
@@ -61,6 +61,7 @@ prompt」「给 Codex 出图的指令」「优化这条出图 prompt」等意图
|
|
|
61
61
|
|
|
62
62
|
2. **优先自动转化,并选对入口**。能直接交付时,按场景运行对应命令:
|
|
63
63
|
- 安装到 agent skill 目录:`prompt_cli.py install-skill --target codex|claude [--force]`
|
|
64
|
+
- 安装后自检:`prompt_cli.py doctor`
|
|
64
65
|
- 单图:`prompt_cli.py convert "<自然语言画图需求>" [--style-preset premium] [--strict-text] [--out <path>] [--record-pending]`
|
|
65
66
|
- 长输入整理成多张图:`prompt_cli.py compose "<长输入>" --max-images 6 [--style-preset corporate] [--strict-text]`
|
|
66
67
|
- 同一输入多风格探索:`prompt_cli.py variants "<自然语言画图需求>" --style-presets all`
|
package/package.json
CHANGED
package/scripts/prompt_cli.py
CHANGED
|
@@ -136,7 +136,7 @@ def ensure_home() -> None:
|
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
SCHEMA_VERSION = 1
|
|
139
|
-
COMPILER_VERSION = "0.4.
|
|
139
|
+
COMPILER_VERSION = "0.4.7"
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
PACKAGED_SKILL_FILES = [
|
|
@@ -154,6 +154,63 @@ PACKAGED_SKILL_FILES = [
|
|
|
154
154
|
"references/harness.md",
|
|
155
155
|
]
|
|
156
156
|
|
|
157
|
+
DOCTOR_CASES = [
|
|
158
|
+
{
|
|
159
|
+
"id": "social-plain-label",
|
|
160
|
+
"request": "小红书封面,主题 30分钟搭好AI知识库,副标题 从资料整理到智能问答,不要真人,1:1",
|
|
161
|
+
"asset_type": "poster",
|
|
162
|
+
"style_preset": "creator-economy",
|
|
163
|
+
"expect_asset_type": "poster",
|
|
164
|
+
"expect_template_id": "poster_social_cover",
|
|
165
|
+
"expect_aspect": "1:1",
|
|
166
|
+
"expect_required_text": ["30分钟搭好AI知识库", "从资料整理到智能问答"],
|
|
167
|
+
"forbid_prompt": ["price/offer block"],
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"id": "saas-dashboard",
|
|
171
|
+
"request": "SaaS 看板,需要包含通过率、失败数、平均评分、待修复,16:9",
|
|
172
|
+
"asset_type": "ui",
|
|
173
|
+
"style_preset": "clean-ui",
|
|
174
|
+
"expect_asset_type": "ui",
|
|
175
|
+
"expect_template_id": "ui_dashboard",
|
|
176
|
+
"expect_aspect": "16:9",
|
|
177
|
+
"expect_required_text": ["通过率", "失败数", "平均评分", "待修复"],
|
|
178
|
+
"forbid_prompt": ["chart panel", "data table"],
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"id": "event-poster",
|
|
182
|
+
"request": "设计一张 AI 产品发布会海报,4:5,标题 Prompt Craft 2026,副标题是 Build visual ideas faster,时间 2026.08.18,地点 Shanghai。",
|
|
183
|
+
"asset_type": "poster",
|
|
184
|
+
"style_preset": "event-poster",
|
|
185
|
+
"expect_asset_type": "poster",
|
|
186
|
+
"expect_template_id": "poster_event",
|
|
187
|
+
"expect_aspect": "4:5",
|
|
188
|
+
"expect_required_text": ["Prompt Craft 2026", "Build visual ideas faster", "2026.08.18", "Shanghai"],
|
|
189
|
+
"forbid_prompt": ["price/offer block"],
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"id": "architecture-diagram",
|
|
193
|
+
"request": "draw-prompt 产品架构图,16:9。需要展示用户输入、场景识别、风格预设、Prompt 编译器、意图保持检查、质量门、Image 2 出图、样本反馈学习。",
|
|
194
|
+
"asset_type": "diagram",
|
|
195
|
+
"style_preset": "systems-map",
|
|
196
|
+
"expect_asset_type": "diagram",
|
|
197
|
+
"expect_template_id": "diagram_system",
|
|
198
|
+
"expect_aspect": "16:9",
|
|
199
|
+
"expect_required_text": ["用户输入", "场景识别", "Prompt 编译器", "质量门", "Image 2 出图", "样本反馈学习"],
|
|
200
|
+
"forbid_prompt": ["academic system-architecture figure"],
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"id": "architecture-with-app-web",
|
|
204
|
+
"request": "画一张 16:9 中文技术架构图:AI 客服工单自动化系统。结构从左到右:用户入口(App、Web、企业微信)进入消息网关;再到意图识别、知识库检索、工单创建、人工坐席协同;底部有监控与质检层,包含响应时长、解决率、满意度、风险拦截;右侧输出自动回复、工单流转、运营报表。要求企业级、清晰、不要像宣传海报,中文标签要尽量清楚。",
|
|
205
|
+
"style_preset": "technical-blueprint",
|
|
206
|
+
"expect_asset_type": "diagram",
|
|
207
|
+
"expect_template_id": "diagram_system",
|
|
208
|
+
"expect_aspect": "16:9",
|
|
209
|
+
"expect_required_text": ["用户入口", "App", "Web", "企业微信", "消息网关", "意图识别", "知识库检索", "工单创建", "人工坐席协同", "响应时长", "解决率", "满意度", "风险拦截", "自动回复", "工单流转", "运营报表"],
|
|
210
|
+
"forbid_prompt": ["UI mockup", "dashboard workspace"],
|
|
211
|
+
},
|
|
212
|
+
]
|
|
213
|
+
|
|
157
214
|
|
|
158
215
|
def package_root() -> Path:
|
|
159
216
|
return Path(__file__).resolve().parent.parent
|
|
@@ -2378,9 +2435,9 @@ def route_asset_type(request: str, override: str | None = None) -> str:
|
|
|
2378
2435
|
lower = request.lower()
|
|
2379
2436
|
explicit_routes = [
|
|
2380
2437
|
("slide", ["powerpoint", "slide", "presentation", "ppt", "幻灯片", "演示文稿", "汇报单页"]),
|
|
2381
|
-
("ui", ["ui", "界面", "app", "dashboard", "仪表盘", "看板", "saas", "后台", "控制台", "网页", "mockup"]),
|
|
2382
2438
|
("diagram", ["架构图", "系统图", "流程架构", "architecture diagram", "system diagram"]),
|
|
2383
2439
|
("infographic", ["信息图", "infographic", "图解", "流程图", "时间线"]),
|
|
2440
|
+
("ui", ["ui", "界面", "app", "dashboard", "仪表盘", "看板", "saas", "后台", "控制台", "网页", "mockup"]),
|
|
2384
2441
|
("poster", ["海报", "poster", "banner", "主视觉", "kv", "封面"]),
|
|
2385
2442
|
("logo", ["logo", "品牌标识", "字标", "visual identity"]),
|
|
2386
2443
|
]
|
|
@@ -2452,7 +2509,7 @@ def extract_required_texts(request: str, explicit_texts: list[str]) -> list[str]
|
|
|
2452
2509
|
text = re.sub(r"(?:要)?(?:渲染)?(?:清晰|清楚|可读|明显)$", "", text)
|
|
2453
2510
|
text = re.sub(r"(?:这些)?元素$", "", text)
|
|
2454
2511
|
text = re.sub(r"(?:[一二三四五六七八九十\d]+个)?步骤$", "", text)
|
|
2455
|
-
if re.search(r"
|
|
2512
|
+
if re.search(r"箭头.*(?:关系|展示|输入|输出)|(?:关系|展示|输入|输出).*箭头|展示输入输出关系", text):
|
|
2456
2513
|
return ""
|
|
2457
2514
|
if re.fullmatch(r"[A-Za-z]{1,2}", text) and text.upper() not in {"AI", "UI"}:
|
|
2458
2515
|
return ""
|
|
@@ -2558,7 +2615,7 @@ def merge_texts(primary: list[str], extra: list[str]) -> list[str]:
|
|
|
2558
2615
|
text = re.sub(r"(?:这些)?元素$", "", text)
|
|
2559
2616
|
text = re.sub(r"(?:[一二三四五六七八九十\d]+个)?步骤$", "", text)
|
|
2560
2617
|
text = text.strip(" \t\n\r,,、。;;::.!?!?")
|
|
2561
|
-
if re.search(r"
|
|
2618
|
+
if re.search(r"箭头.*(?:关系|展示|输入|输出)|(?:关系|展示|输入|输出).*箭头|展示输入输出关系", text):
|
|
2562
2619
|
continue
|
|
2563
2620
|
if re.fullmatch(r"[A-Za-z]{1,2}", text) and text.upper() not in {"AI", "UI"}:
|
|
2564
2621
|
continue
|
|
@@ -2572,29 +2629,39 @@ def merge_texts(primary: list[str], extra: list[str]) -> list[str]:
|
|
|
2572
2629
|
|
|
2573
2630
|
|
|
2574
2631
|
def extract_structural_labels(request: str, asset_type: str) -> list[str]:
|
|
2575
|
-
if asset_type not in {"diagram", "infographic", "slide", "ui", "poster"}:
|
|
2632
|
+
if asset_type not in {"diagram", "infographic", "slide", "ui", "poster", "product"}:
|
|
2576
2633
|
return []
|
|
2577
2634
|
candidates: list[tuple[int, str]] = []
|
|
2578
|
-
list_intro = r"(?:需要)?(
|
|
2635
|
+
list_intro = r"(?:需要)?(?:展示|呈现|列出|包含|包括|含有|分为|覆盖|输出)"
|
|
2579
2636
|
stop_words = r"(?:\b16\s*:\s*9\b|\b9\s*:\s*16\b|\b3\s*:\s*4\b|\b1\s*:\s*1\b|适合|用于|画幅|aspect|高质量|高清|clean|corporate)"
|
|
2580
2637
|
patterns = [
|
|
2581
|
-
rf"
|
|
2582
|
-
rf"(
|
|
2583
|
-
rf"(
|
|
2638
|
+
rf"(?:结构(?:从左到右|从右到左|从上到下|从下到上)?|链路|流程)\s*[::]\s*([^。\n]{{2,240}})",
|
|
2639
|
+
rf"(?:数据|指标|核心数据|关键指标|卖点|亮点|功能点|特性|活动路径|路径)\s*(?:包括|包含|有|为)?[::\s]*([^。;;\n]{{2,180}})",
|
|
2640
|
+
rf"{list_intro}\s*(?:这些|以下|对应的)?(?:模块|部分|层|栏目|节点|入口|能力|场景|列表|指标卡|卡片|步骤|分支|数据|指标|卖点|亮点|功能点|特性|路径)?[::\s]*([^。;;\n]{{2,180}})",
|
|
2641
|
+
rf"(?:模块|节点|栏目|部分|层|入口|能力|场景|列表|指标卡|卡片|步骤|分支|数据|指标|卖点|亮点|功能点|特性|路径)\s*(?:包括|包含|有|为)[::\s]*([^。;;\n]{{2,180}})",
|
|
2642
|
+
rf"(?:模块|节点|栏目|部分|层|入口|能力|场景|列表|指标卡|卡片|步骤|分支|数据|指标|卖点|亮点|功能点|特性|路径)[^。;;\n::]{{0,16}}[::]([^。;;\n]{{2,180}})",
|
|
2584
2643
|
]
|
|
2585
2644
|
for pattern in patterns:
|
|
2586
2645
|
for match in re.finditer(pattern, request, flags=re.IGNORECASE):
|
|
2587
2646
|
value = re.split(stop_words, match.group(1), maxsplit=1, flags=re.IGNORECASE)[0]
|
|
2647
|
+
value = re.sub(r"[()()]", "、", value)
|
|
2648
|
+
value = re.sub(r"(?:进入|再到|到|输出|->|→)", "、", value)
|
|
2588
2649
|
for part_match in re.finditer(r"[^、,,;;/|]+", value):
|
|
2589
2650
|
part = part_match.group(0).strip(" \t\n\r,,、::")
|
|
2590
2651
|
part = re.sub(r"^(?:[^::]{0,12}(?:指标卡|卡片|列表|标题|模块|节点|步骤|栏目|分支))[::]", "", part)
|
|
2591
|
-
part = re.sub(r"^(?:和|与|及|以及|and)\s*", "", part, flags=re.IGNORECASE).strip()
|
|
2652
|
+
part = re.sub(r"^(?:和|与|及|以及|and|再|则)\s*", "", part, flags=re.IGNORECASE).strip()
|
|
2653
|
+
part = re.sub(r"^(?:底部有|顶部有|左侧有|右侧有|上方有|下方有)\s*", "", part)
|
|
2654
|
+
part = re.sub(r"^(?:包含|包括|含有)\s*", "", part)
|
|
2592
2655
|
part = re.sub(r"\s*(?:和|与|及|以及|and)$", "", part, flags=re.IGNORECASE).strip()
|
|
2593
2656
|
part = re.sub(r"(?:这些)?元素$", "", part)
|
|
2594
2657
|
part = re.sub(r"(?:[一二三四五六七八九十\d]+个)?步骤$", "", part)
|
|
2595
2658
|
part = part.strip(" \t\n\r,,、::")
|
|
2596
2659
|
if not part:
|
|
2597
2660
|
continue
|
|
2661
|
+
if re.search(r"箭头.*(?:关系|展示|输入|输出)|(?:关系|展示|输入|输出).*箭头|展示输入输出关系", part):
|
|
2662
|
+
continue
|
|
2663
|
+
if part in {"顶部", "底部", "左侧", "右侧", "上方", "下方", "中间", "顶部导航", "底部导航", "左侧导航", "右侧导航", "导航栏", "顶部栏", "状态栏", "箭头关系", "箭头", "关系"}:
|
|
2664
|
+
continue
|
|
2598
2665
|
if re.fullmatch(r"\d+(?:\s*:\s*\d+)?", part):
|
|
2599
2666
|
continue
|
|
2600
2667
|
if len(part) > 36:
|
|
@@ -2630,6 +2697,43 @@ def infer_style_anchors(request: str, override: str | None, profile: dict, prese
|
|
|
2630
2697
|
return anchors[:4]
|
|
2631
2698
|
|
|
2632
2699
|
|
|
2700
|
+
def infer_palette_constraints(request: str) -> list[str]:
|
|
2701
|
+
color_terms = [
|
|
2702
|
+
"红", "橙", "黄", "绿", "青", "蓝", "紫", "粉", "黑", "白", "灰", "金", "银", "棕",
|
|
2703
|
+
"深色", "浅色", "渐变", "配色", "主色", "点缀", "背景",
|
|
2704
|
+
"red", "orange", "yellow", "green", "cyan", "blue", "purple", "pink", "black", "white",
|
|
2705
|
+
"gray", "grey", "gold", "silver", "brown", "gradient", "palette", "color", "colour",
|
|
2706
|
+
]
|
|
2707
|
+
constraints: list[str] = []
|
|
2708
|
+
seen: set[str] = set()
|
|
2709
|
+
for sentence in re.split(r"[。;;\n]", request):
|
|
2710
|
+
for fragment in re.split(r"[,,]", sentence):
|
|
2711
|
+
fragment = fragment.strip(" \t\r\n。.;;,,")
|
|
2712
|
+
if not fragment:
|
|
2713
|
+
continue
|
|
2714
|
+
lower = fragment.lower()
|
|
2715
|
+
if not any(term in lower for term in color_terms):
|
|
2716
|
+
continue
|
|
2717
|
+
if len(fragment) > 72:
|
|
2718
|
+
fragment = fragment[:72].rstrip()
|
|
2719
|
+
if "红" in fragment and any(marker in fragment for marker in ["不要太俗", "别太俗", "不能太俗", "不俗", "高级"]):
|
|
2720
|
+
normalized = "use a restrained, mature red campaign accent palette; keep red visibly present without garish saturated red"
|
|
2721
|
+
if normalized not in seen:
|
|
2722
|
+
seen.add(normalized)
|
|
2723
|
+
constraints.append(normalized)
|
|
2724
|
+
if len(constraints) >= 3:
|
|
2725
|
+
return constraints
|
|
2726
|
+
continue
|
|
2727
|
+
key = re.sub(r"\s+", "", fragment)
|
|
2728
|
+
if key in seen:
|
|
2729
|
+
continue
|
|
2730
|
+
seen.add(key)
|
|
2731
|
+
constraints.append(f"preserve requested color direction: {fragment}")
|
|
2732
|
+
if len(constraints) >= 3:
|
|
2733
|
+
return constraints
|
|
2734
|
+
return constraints
|
|
2735
|
+
|
|
2736
|
+
|
|
2633
2737
|
def infer_negative(asset_type: str, texts: list[str], profile: dict, request: str = "", template_id: str = "") -> list[str]:
|
|
2634
2738
|
negative = ["avoid vague generic AI gloss", "avoid clutter", "avoid adding content not requested by the user"]
|
|
2635
2739
|
if texts:
|
|
@@ -3023,7 +3127,7 @@ def build_spec(args: argparse.Namespace) -> dict:
|
|
|
3023
3127
|
"style_anchors": infer_style_anchors(safe_request, getattr(args, "style", None), profile, style_preset),
|
|
3024
3128
|
"materials": split_csv(getattr(args, "materials", None)) or ["tactile, specific visible materials chosen for the subject"],
|
|
3025
3129
|
"lighting": getattr(args, "lighting", None) or "controlled, readable light with clear subject hierarchy",
|
|
3026
|
-
"palette": split_csv(getattr(args, "palette", None)) or ["restrained palette matched to the asset type"],
|
|
3130
|
+
"palette": split_csv(getattr(args, "palette", None)) or infer_palette_constraints(visual_request) or ["restrained palette matched to the asset type"],
|
|
3027
3131
|
"negative": negative,
|
|
3028
3132
|
"must_include": infer_must_include(asset_type, template_id, texts, bool(getattr(args, "strict_text", False))),
|
|
3029
3133
|
"must_avoid": negative,
|
|
@@ -3157,7 +3261,7 @@ def render_prompt(spec: dict) -> str:
|
|
|
3157
3261
|
if asset_type == "infographic":
|
|
3158
3262
|
return "\n".join(
|
|
3159
3263
|
[
|
|
3160
|
-
f"Create a {aspect}
|
|
3264
|
+
f"Create a {aspect} information graphic from the user visual brief below.",
|
|
3161
3265
|
brief_block,
|
|
3162
3266
|
style_quality_block(spec, label="infographic", extra=["Build a clear information hierarchy from the user's brief and preserve any named regions, section counts, and reading order."]),
|
|
3163
3267
|
f"Composition support: {spec.get('template_label', 'infographic')}; layout guidance: {layout}.",
|
|
@@ -5607,6 +5711,113 @@ def npm_registry_status() -> str:
|
|
|
5607
5711
|
return f"{registry}(建议 npx 加 --registry=https://registry.npmjs.org/)"
|
|
5608
5712
|
|
|
5609
5713
|
|
|
5714
|
+
def read_skill_version(path: Path) -> str:
|
|
5715
|
+
if not path.exists():
|
|
5716
|
+
return ""
|
|
5717
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
5718
|
+
match = re.match(r"\s*version:\s*([^\s]+)", line)
|
|
5719
|
+
if match:
|
|
5720
|
+
return match.group(1)
|
|
5721
|
+
return ""
|
|
5722
|
+
|
|
5723
|
+
|
|
5724
|
+
def doctor_add(findings: list[dict], severity: str, rule: str, message: str) -> None:
|
|
5725
|
+
findings.append({"severity": severity, "rule": rule, "message": message})
|
|
5726
|
+
|
|
5727
|
+
|
|
5728
|
+
def doctor_check_package(root: Path) -> dict:
|
|
5729
|
+
findings: list[dict] = []
|
|
5730
|
+
package_path = root / "package.json"
|
|
5731
|
+
package_version = ""
|
|
5732
|
+
if package_path.exists():
|
|
5733
|
+
try:
|
|
5734
|
+
package_version = str(json.loads(package_path.read_text(encoding="utf-8")).get("version") or "")
|
|
5735
|
+
except Exception as exc:
|
|
5736
|
+
doctor_add(findings, "error", "package.invalid_json", f"package.json 读取失败:{exc}")
|
|
5737
|
+
else:
|
|
5738
|
+
doctor_add(findings, "error", "package.missing", "缺少 package.json")
|
|
5739
|
+
skill_version = read_skill_version(root / "SKILL.md")
|
|
5740
|
+
if not skill_version:
|
|
5741
|
+
doctor_add(findings, "error", "skill.version_missing", "SKILL.md 缺少 metadata.version")
|
|
5742
|
+
if package_version and skill_version and package_version != skill_version:
|
|
5743
|
+
doctor_add(findings, "error", "version.package_skill_mismatch", f"package.json={package_version} SKILL.md={skill_version}")
|
|
5744
|
+
if package_version and package_version != COMPILER_VERSION:
|
|
5745
|
+
doctor_add(findings, "error", "version.compiler_mismatch", f"package.json={package_version} compiler={COMPILER_VERSION}")
|
|
5746
|
+
missing = [rel for rel in PACKAGED_SKILL_FILES if not (root / rel).exists()]
|
|
5747
|
+
for rel in missing:
|
|
5748
|
+
doctor_add(findings, "error", "package.file_missing", f"缺少运行文件:{rel}")
|
|
5749
|
+
style_count = len(available_style_presets(include_auto=False))
|
|
5750
|
+
if style_count < 400:
|
|
5751
|
+
doctor_add(findings, "error", "styles.too_few", f"内置风格数量过少:{style_count}")
|
|
5752
|
+
return {
|
|
5753
|
+
"name": "package",
|
|
5754
|
+
"pass": not has_lint_error(findings),
|
|
5755
|
+
"version": package_version or COMPILER_VERSION,
|
|
5756
|
+
"style_count": style_count,
|
|
5757
|
+
"missing_files": missing,
|
|
5758
|
+
"findings": findings,
|
|
5759
|
+
}
|
|
5760
|
+
|
|
5761
|
+
|
|
5762
|
+
def doctor_check_case(case: dict) -> dict:
|
|
5763
|
+
findings: list[dict] = []
|
|
5764
|
+
result = convert_for_benchmark(case)
|
|
5765
|
+
spec = result["spec"]
|
|
5766
|
+
prompt = result["prompt"]
|
|
5767
|
+
for item in result["lint"]:
|
|
5768
|
+
findings.append({"severity": item["severity"], "rule": f"lint.{item['rule']}", "message": item["message"]})
|
|
5769
|
+
for item in result["intent_check"]:
|
|
5770
|
+
findings.append({"severity": item["severity"], "rule": f"intent.{item['rule']}", "message": item["message"]})
|
|
5771
|
+
if case.get("expect_asset_type") and spec.get("asset_type") != case["expect_asset_type"]:
|
|
5772
|
+
doctor_add(findings, "error", "case.asset_type", f"期望 asset_type={case['expect_asset_type']},实际={spec.get('asset_type')}")
|
|
5773
|
+
if case.get("expect_template_id") and spec.get("template_id") != case["expect_template_id"]:
|
|
5774
|
+
doctor_add(findings, "error", "case.template_id", f"期望 template_id={case['expect_template_id']},实际={spec.get('template_id')}")
|
|
5775
|
+
if case.get("expect_aspect") and spec.get("aspect") != case["expect_aspect"]:
|
|
5776
|
+
doctor_add(findings, "error", "case.aspect", f"期望 aspect={case['expect_aspect']},实际={spec.get('aspect')}")
|
|
5777
|
+
labels = set(spec.get("required_text") or [])
|
|
5778
|
+
for text in case.get("expect_required_text") or []:
|
|
5779
|
+
if text not in labels:
|
|
5780
|
+
doctor_add(findings, "error", "case.required_text_missing", f"缺少必显文字:{text}")
|
|
5781
|
+
elif f'"{text}"' not in prompt:
|
|
5782
|
+
doctor_add(findings, "error", "case.required_text_not_quoted", f"Prompt 未逐字引用:{text}")
|
|
5783
|
+
prompt_lower = prompt.lower()
|
|
5784
|
+
for phrase in case.get("forbid_prompt") or []:
|
|
5785
|
+
if phrase.lower() in prompt_lower:
|
|
5786
|
+
doctor_add(findings, "error", "case.forbidden_prompt", f"Prompt 出现不应出现的片段:{phrase}")
|
|
5787
|
+
return {
|
|
5788
|
+
"name": case["id"],
|
|
5789
|
+
"pass": not has_lint_error(findings),
|
|
5790
|
+
"asset_type": spec.get("asset_type"),
|
|
5791
|
+
"template_id": spec.get("template_id"),
|
|
5792
|
+
"aspect": spec.get("aspect"),
|
|
5793
|
+
"prompt_digest": result["prompt_digest"],
|
|
5794
|
+
"required_text": spec.get("required_text") or [],
|
|
5795
|
+
"findings": findings,
|
|
5796
|
+
}
|
|
5797
|
+
|
|
5798
|
+
|
|
5799
|
+
def cmd_doctor(args: argparse.Namespace) -> int:
|
|
5800
|
+
root = package_root()
|
|
5801
|
+
checks = [doctor_check_package(root)]
|
|
5802
|
+
for case in DOCTOR_CASES:
|
|
5803
|
+
try:
|
|
5804
|
+
checks.append(doctor_check_case(case))
|
|
5805
|
+
except Exception as exc:
|
|
5806
|
+
checks.append({"name": case.get("id", "case"), "pass": False, "findings": [{"severity": "error", "rule": "case.exception", "message": str(exc)}]})
|
|
5807
|
+
passed = all(item["pass"] for item in checks)
|
|
5808
|
+
summary = {"pass": passed, "version": COMPILER_VERSION, "checks": len(checks), "failed": len([item for item in checks if not item["pass"]])}
|
|
5809
|
+
if args.json:
|
|
5810
|
+
print(json.dumps({"summary": summary, "checks": checks}, ensure_ascii=False, indent=2))
|
|
5811
|
+
else:
|
|
5812
|
+
print(f"doctor: version={COMPILER_VERSION} checks={summary['checks']} pass={passed}")
|
|
5813
|
+
for item in checks:
|
|
5814
|
+
status = "PASS" if item["pass"] else "FAIL"
|
|
5815
|
+
print(f"- {item['name']}: {status}")
|
|
5816
|
+
for finding in item.get("findings", []):
|
|
5817
|
+
print(f" [{finding['severity']}] {finding['rule']}: {finding['message']}")
|
|
5818
|
+
return 0 if passed else 1
|
|
5819
|
+
|
|
5820
|
+
|
|
5610
5821
|
def cmd_status(args: argparse.Namespace) -> int:
|
|
5611
5822
|
print("draw-prompt 环境检查")
|
|
5612
5823
|
print(f" 数据目录 : {data_home()} ({'存在' if data_home().exists() else '未创建'})")
|
|
@@ -5628,7 +5839,7 @@ def cmd_status(args: argparse.Namespace) -> int:
|
|
|
5628
5839
|
print(f" codex CLI : {which('codex') or '未找到'}")
|
|
5629
5840
|
plugin = Path.home() / ".claude" / "plugins" / "cache" / "codex-image-in-cc"
|
|
5630
5841
|
print(f" codex-image: {'已安装' if plugin.exists() else '未安装(可 /codex-image:generate 出图)'}")
|
|
5631
|
-
print(" 核心转化命令: convert / compose / variants / series / edit / brand / character / data-viz / rewrite / adapt")
|
|
5842
|
+
print(" 核心转化命令: doctor / convert / compose / variants / series / edit / brand / character / data-viz / rewrite / adapt")
|
|
5632
5843
|
print(" 稳定性命令 : overlay / visual-check / edit-check / visual-regress / lint / intent-check / benchmark / revise / styles")
|
|
5633
5844
|
return 0
|
|
5634
5845
|
|
|
@@ -5674,6 +5885,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
5674
5885
|
pis.add_argument("--json", action="store_true")
|
|
5675
5886
|
pis.set_defaults(func=cmd_install_skill)
|
|
5676
5887
|
|
|
5888
|
+
pdoc = sub.add_parser("doctor", help="安装后运行时自检:包文件、版本和核心场景转化")
|
|
5889
|
+
pdoc.add_argument("--json", action="store_true")
|
|
5890
|
+
pdoc.set_defaults(func=cmd_doctor)
|
|
5891
|
+
|
|
5677
5892
|
pc = sub.add_parser("convert", help="自然语言画图需求 -> 高质量生图 Prompt / handoff")
|
|
5678
5893
|
pc.add_argument("request_text", nargs="+", help="自然语言画图需求")
|
|
5679
5894
|
pc.add_argument("--asset-type", choices=sorted(ASSET_ROUTES.keys()), help="覆盖自动识别的资产类型")
|