@yuhan1124/draw-prompt 0.4.8 → 0.4.9

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 CHANGED
@@ -70,7 +70,8 @@ npx --yes --registry=https://registry.npmjs.org/ @yuhan1124/draw-prompt@latest i
70
70
  `install-skill` 默认复制 npm 包内的运行文件,避免软链到 npx 缓存导致后续路径失效。它只复制
71
71
  `SKILL.md`、CLI、references 和必要元数据;开发仓库里的 `tests/`、`tmp/`、`golden-cases.jsonl`、
72
72
  `visual-cases.jsonl` 不会进入 npm 包或安装目录。安装后可直接跑 `doctor`,它会检查
73
- 包文件、版本一致性、核心单图转化和一条真实长输入 `compose` 链路。
73
+ 包文件、版本一致性、核心单图转化、真实长输入 `compose` 链路,以及
74
+ `variants` / `series` / `adapt` 等主工作流 smoke。
74
75
 
75
76
  开发者本地调试也可以软链 repo:
76
77
 
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.8
11
+ version: 0.4.9
12
12
  openclaw:
13
13
  anyBins: ["uv", "python3"]
14
14
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuhan1124/draw-prompt",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "Convert natural-language image requests into high-quality gpt-image-2 prompts and Codex handoff blocks.",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -136,7 +136,7 @@ def ensure_home() -> None:
136
136
 
137
137
 
138
138
  SCHEMA_VERSION = 1
139
- COMPILER_VERSION = "0.4.8"
139
+ COMPILER_VERSION = "0.4.9"
140
140
 
141
141
 
142
142
  PACKAGED_SKILL_FILES = [
@@ -234,6 +234,34 @@ DOCTOR_COMPOSE_CASES = [
234
234
  }
235
235
  ]
236
236
 
237
+ DOCTOR_WORKFLOW_CASES = [
238
+ {
239
+ "id": "variants-style-envelope",
240
+ "kind": "variants",
241
+ "request": "小红书方图,主题 AI 生图 Prompt 三步法,标题 生图 Prompt 三步法",
242
+ "style_presets": ["premium", "flat-vector"],
243
+ "expect_count": 2,
244
+ "expect_required_text": ["AI 生图 Prompt 三步法", "生图 Prompt 三步法"],
245
+ "forbid_palette": ["小红书方图"],
246
+ },
247
+ {
248
+ "id": "series-consistent-posters",
249
+ "kind": "series",
250
+ "briefs": ["茶饮品牌春季主视觉,突出冷泡茶瓶", "同一品牌社媒方图,展示三种口味"],
251
+ "shared_style": "single coherent series style, same camera language, same palette discipline, same visual density",
252
+ "style_preset": "premium",
253
+ "expect_count": 2,
254
+ },
255
+ {
256
+ "id": "adapt-product-brand-and-selling-points",
257
+ "kind": "adapt",
258
+ "request": "电商产品详情首屏,品牌 ThermoFlow,展示实时测温、24小时保温、316不锈钢、Type-C 充电",
259
+ "style_preset": "premium-packshot",
260
+ "aspects": ["1:1", "3:4", "16:9"],
261
+ "expect_required_text": ["ThermoFlow", "实时测温", "24小时保温", "316不锈钢", "Type-C 充电"],
262
+ },
263
+ ]
264
+
237
265
 
238
266
  def package_root() -> Path:
239
267
  return Path(__file__).resolve().parent.parent
@@ -2574,6 +2602,7 @@ def extract_required_texts(request: str, explicit_texts: list[str]) -> list[str]
2574
2602
  r"(?:时间|地点)\s+([^,,、。;;\n]{2,40})",
2575
2603
  r"(?:核心卡片|主要按钮|主按钮|按钮)\s*(?:写上|写|显示|为|是|叫|[::])\s*([^,,。;;\n]{2,30})",
2576
2604
  r"(?:需要)?包含\s*([A-Za-z][A-Za-z0-9_-]{2,30})\s*字样",
2605
+ r"(?:品牌名|品牌|brand)\s*(?:写上|写|显示|为|是|叫|[::])?\s*([A-Za-z][A-Za-z0-9_-]{2,30})\b",
2577
2606
  r"(?:名为|叫做|名称是|名字叫)\s*([A-Za-z][A-Za-z0-9_-]{2,30})\b",
2578
2607
  ]
2579
2608
  for pat in labeled_single_patterns:
@@ -2721,8 +2750,9 @@ def infer_style_anchors(request: str, override: str | None, profile: dict, prese
2721
2750
 
2722
2751
 
2723
2752
  def infer_palette_constraints(request: str) -> list[str]:
2724
- color_terms = [
2725
- "红", "橙", "", "绿", "", "蓝", "紫", "粉", "黑", "白", "灰", "金", "银", "棕",
2753
+ cjk_colors = "红橙黄绿青蓝紫粉黑白灰金银棕"
2754
+ false_color_tokens = ["小红书", "红书", "白皮书"]
2755
+ direct_color_terms = [
2726
2756
  "深色", "浅色", "渐变", "配色", "主色", "点缀", "背景",
2727
2757
  "red", "orange", "yellow", "green", "cyan", "blue", "purple", "pink", "black", "white",
2728
2758
  "gray", "grey", "gold", "silver", "brown", "gradient", "palette", "color", "colour",
@@ -2735,7 +2765,11 @@ def infer_palette_constraints(request: str) -> list[str]:
2735
2765
  if not fragment:
2736
2766
  continue
2737
2767
  lower = fragment.lower()
2738
- if not any(term in lower for term in color_terms):
2768
+ if any(token in fragment for token in false_color_tokens):
2769
+ continue
2770
+ has_direct_color = any(term in lower for term in direct_color_terms)
2771
+ has_cjk_color = re.search(rf"(?:[{cjk_colors}](?:色|系|调|主色|点缀|背景|渐变|不要太俗|高级)|(?:深|浅|亮|暗|磨砂|纯|主|背景|点缀|配色)[^,,。;;\n]{{0,12}}[{cjk_colors}]|黑白)", fragment)
2772
+ if not (has_direct_color or has_cjk_color):
2739
2773
  continue
2740
2774
  if len(fragment) > 72:
2741
2775
  fragment = fragment[:72].rstrip()
@@ -5916,6 +5950,78 @@ def doctor_check_compose_case(case: dict) -> dict:
5916
5950
  }
5917
5951
 
5918
5952
 
5953
+ def doctor_check_workflow_case(case: dict) -> dict:
5954
+ findings: list[dict] = []
5955
+ kind = case.get("kind")
5956
+ compiled_items: list[dict] = []
5957
+ if kind == "variants":
5958
+ request = str(case.get("request") or "")
5959
+ for preset in case.get("style_presets") or []:
5960
+ compiled_items.append(
5961
+ compile_visual_case(
5962
+ {"request": request, "style_preset": preset, "target": "raw", "tags": "doctor,variants"},
5963
+ target="raw",
5964
+ include_handoff=False,
5965
+ )
5966
+ )
5967
+ elif kind == "series":
5968
+ shared_style = str(case.get("shared_style") or "")
5969
+ for brief in case.get("briefs") or []:
5970
+ compiled_items.append(
5971
+ compile_visual_case(
5972
+ {"request": str(brief), "style": shared_style, "style_preset": case.get("style_preset"), "target": "raw", "tags": "doctor,series"},
5973
+ target="raw",
5974
+ include_handoff=False,
5975
+ )
5976
+ )
5977
+ elif kind == "adapt":
5978
+ request = str(case.get("request") or "")
5979
+ for aspect in case.get("aspects") or []:
5980
+ compiled_items.append(
5981
+ compile_visual_case(
5982
+ {"request": request, "aspect": aspect, "style_preset": case.get("style_preset"), "target": "raw", "tags": "doctor,adapt"},
5983
+ target="raw",
5984
+ include_handoff=False,
5985
+ )
5986
+ )
5987
+ else:
5988
+ doctor_add(findings, "error", "workflow.kind", f"未知工作流类型:{kind}")
5989
+
5990
+ for idx, item in enumerate(compiled_items, start=1):
5991
+ for lint in item["lint"]:
5992
+ findings.append({"severity": lint["severity"], "rule": f"workflow.{idx}.lint.{lint['rule']}", "message": lint["message"]})
5993
+ for intent in item["intent_check"]:
5994
+ findings.append({"severity": intent["severity"], "rule": f"workflow.{idx}.intent.{intent['rule']}", "message": intent["message"]})
5995
+
5996
+ if case.get("expect_count") and len(compiled_items) != int(case["expect_count"]):
5997
+ doctor_add(findings, "error", "workflow.count", f"期望 {case['expect_count']} 个输出,实际 {len(compiled_items)}")
5998
+ expected_texts = case.get("expect_required_text") or []
5999
+ for idx, item in enumerate(compiled_items, start=1):
6000
+ labels = set(item["spec"].get("required_text") or [])
6001
+ prompt = item["prompt"]
6002
+ for text_item in expected_texts:
6003
+ if text_item not in labels:
6004
+ doctor_add(findings, "error", "workflow.required_text_missing", f"第 {idx} 个输出缺少必显文字:{text_item}")
6005
+ elif f'"{text_item}"' not in prompt:
6006
+ doctor_add(findings, "error", "workflow.required_text_not_quoted", f"第 {idx} 个输出 Prompt 未逐字引用:{text_item}")
6007
+ forbidden_palette = case.get("forbid_palette") or []
6008
+ for idx, item in enumerate(compiled_items, start=1):
6009
+ palette = " ".join(str(part) for part in item["spec"].get("palette") or [])
6010
+ for forbidden in forbidden_palette:
6011
+ if forbidden in palette:
6012
+ doctor_add(findings, "error", "workflow.forbidden_palette", f"第 {idx} 个输出把非颜色词当成配色约束:{forbidden}")
6013
+ return {
6014
+ "name": f"workflow-{case['id']}",
6015
+ "pass": not has_lint_error(findings),
6016
+ "kind": kind,
6017
+ "count": len(compiled_items),
6018
+ "asset_types": [item["spec"].get("asset_type") for item in compiled_items],
6019
+ "prompt_digests": [item["prompt_digest"] for item in compiled_items],
6020
+ "required_text": [item["spec"].get("required_text") or [] for item in compiled_items],
6021
+ "findings": findings,
6022
+ }
6023
+
6024
+
5919
6025
  def cmd_doctor(args: argparse.Namespace) -> int:
5920
6026
  root = package_root()
5921
6027
  checks = [doctor_check_package(root)]
@@ -5929,6 +6035,11 @@ def cmd_doctor(args: argparse.Namespace) -> int:
5929
6035
  checks.append(doctor_check_compose_case(case))
5930
6036
  except Exception as exc:
5931
6037
  checks.append({"name": f"compose-{case.get('id', 'case')}", "pass": False, "findings": [{"severity": "error", "rule": "compose.exception", "message": str(exc)}]})
6038
+ for case in DOCTOR_WORKFLOW_CASES:
6039
+ try:
6040
+ checks.append(doctor_check_workflow_case(case))
6041
+ except Exception as exc:
6042
+ checks.append({"name": f"workflow-{case.get('id', 'case')}", "pass": False, "findings": [{"severity": "error", "rule": "workflow.exception", "message": str(exc)}]})
5932
6043
  passed = all(item["pass"] for item in checks)
5933
6044
  summary = {"pass": passed, "version": COMPILER_VERSION, "checks": len(checks), "failed": len([item for item in checks if not item["pass"]])}
5934
6045
  if args.json: