@yuhan1124/draw-prompt 0.4.10 → 0.4.11

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
@@ -135,7 +135,7 @@ overlay --image out.png --spec spec.json --out final.png # 按 overlay spec
135
135
  visual-check --image final.png --spec spec.json # 单图质量门:尺寸/画幅/亮度/对比度
136
136
  edit-check --reference ref.png --output edited.png # 改图质量门:主体保留 + 有效变化
137
137
  intent-check --request "原始需求" --prompt "生成prompt" # 检查是否新增/偏离用户未要求内容
138
- visual-regress references/visual-cases.jsonl # 开发仓库本地:多场景 prompt/成品图回归
138
+ visual-regress references/visual-cases.jsonl # 开发仓库本地:多场景 prompt/成品图回归,支持 expect_/forbid_ 断言
139
139
  lint --prompt "…" [--asset-type poster] [--text 必显文字] # 生图转化硬约束检查
140
140
  benchmark references/golden-cases.jsonl --runs 3 # 开发仓库本地:golden cases 稳定性基准
141
141
  revise --sample-id last --reason text_error # 按失败分类修订 Prompt
@@ -174,7 +174,7 @@ status # 数据 + 下游通道健康
174
174
  4. 只有当用户明确要求“文字必须绝对准确/可后处理”或出图反馈属于 `text_error` 时,才切到 `--strict-text` + `overlay` 两段式兜底。
175
175
  5. 用 `visual-check` 验证成品图尺寸、画幅、亮度、对比度和基础细节。
176
176
  6. 参考图改图用 `edit-check` 验证“主体保留 + 背景/目标确实变化”。
177
- 7. 模板或策略变动后,在开发仓库本地跑 `visual-regress references/visual-cases.jsonl`,确认多场景回归通过。
177
+ 7. 模板或策略变动后,在开发仓库本地跑 `visual-regress references/visual-cases.jsonl`,确认多场景回归通过;用 `expect_asset_type`、`expect_aspect`、`expect_required_text`、`forbid_required_text`、`expect_prompt_contains`、`forbid_prompt_contains` 把真实场景的产品意图固化成门禁。
178
178
 
179
179
  这条链路的默认目标不是替 gpt-image-2 重做排版引擎,而是减少跑偏、遗漏和廉价风格;
180
180
  两段式 overlay 只是文字极端稳定性兜底,不作为普通用户的默认体验。
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.10
11
+ version: 0.4.11
12
12
  openclaw:
13
13
  anyBins: ["uv", "python3"]
14
14
  ---
@@ -168,7 +168,7 @@ overlay --image out.png --spec spec.json --out final.png # 精确中文字/
168
168
  visual-check --image final.png --spec spec.json # 单图质量门
169
169
  edit-check --reference ref.png --output edited.png # 参考图改图质量门
170
170
  intent-check --request "原始需求" --prompt "生成prompt" # 意图保真检查
171
- visual-regress references/visual-cases.jsonl # 开发仓库本地:多场景回归
171
+ visual-regress references/visual-cases.jsonl # 开发仓库本地:多场景回归,支持 expect_/forbid_ 断言
172
172
  lint --prompt "…" [--asset-type poster] [--text 必显文字] # 生图转化硬约束检查
173
173
  benchmark references/golden-cases.jsonl --runs 3 # 开发仓库本地:golden cases 转化稳定性基准
174
174
  revise --sample-id last --reason text_error # 按失败分类修订 Prompt
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuhan1124/draw-prompt",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
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": {
@@ -138,7 +138,7 @@ def ensure_home() -> None:
138
138
 
139
139
 
140
140
  SCHEMA_VERSION = 1
141
- COMPILER_VERSION = "0.4.10"
141
+ COMPILER_VERSION = "0.4.11"
142
142
 
143
143
 
144
144
  PACKAGED_SKILL_FILES = [
@@ -2793,7 +2793,7 @@ def extract_structural_labels(request: str, asset_type: str) -> list[str]:
2793
2793
  value = re.split(stop_words, match.group(1), maxsplit=1, flags=re.IGNORECASE)[0]
2794
2794
  value = re.sub(r"[()()]", "、", value)
2795
2795
  value = re.sub(r"(?:进入|再到|到|输出|->|→)", "、", value)
2796
- for part_match in re.finditer(r"[^、,,;;/|]+", value):
2796
+ for part_match in re.finditer(r"[^、,,;;|]+", value):
2797
2797
  part = part_match.group(0).strip(" \t\n\r,,、::")
2798
2798
  part = re.sub(r"^(?:[^::]{0,12}(?:指标卡|卡片|列表|标题|模块|节点|步骤|栏目|分支))[::]", "", part)
2799
2799
  part = re.sub(r"^(?:和|与|及|以及|and|再|则)\s*", "", part, flags=re.IGNORECASE).strip()
@@ -4882,6 +4882,7 @@ def cmd_visual_regress(args: argparse.Namespace) -> int:
4882
4882
  results = []
4883
4883
  lint_errors = 0
4884
4884
  intent_errors = 0
4885
+ expectation_errors = 0
4885
4886
  visual_errors = 0
4886
4887
  missing_images = 0
4887
4888
  for idx, case in enumerate(cases, start=1):
@@ -4907,8 +4908,10 @@ def cmd_visual_regress(args: argparse.Namespace) -> int:
4907
4908
  compiled = visual_case_compile(case)
4908
4909
  lint = compiled["lint"]
4909
4910
  intent = compiled.get("intent_check", [])
4911
+ expectation_findings = visual_case_expectation_findings(case, compiled)
4910
4912
  lint_errors += sum(1 for item in lint if item.get("severity") == "error")
4911
4913
  intent_errors += sum(1 for item in intent if item.get("severity") == "error")
4914
+ expectation_errors += sum(1 for item in expectation_findings if item.get("severity") == "error")
4912
4915
  item = {
4913
4916
  "id": case_id,
4914
4917
  "scenario": case.get("scenario") or case.get("tool") or "convert",
@@ -4917,6 +4920,7 @@ def cmd_visual_regress(args: argparse.Namespace) -> int:
4917
4920
  "aspect": compiled["spec"]["aspect"],
4918
4921
  "lint": lint,
4919
4922
  "intent_check": intent,
4923
+ "expectation_findings": expectation_findings,
4920
4924
  "status": "compiled",
4921
4925
  }
4922
4926
  image = case.get("image") or case.get("output")
@@ -4951,15 +4955,20 @@ def cmd_visual_regress(args: argparse.Namespace) -> int:
4951
4955
  "cases": len(cases),
4952
4956
  "lint_error_count": lint_errors,
4953
4957
  "intent_error_count": intent_errors,
4958
+ "expectation_error_count": expectation_errors,
4954
4959
  "visual_error_count": visual_errors,
4955
4960
  "missing_image_count": missing_images,
4956
- "pass": lint_errors == 0 and intent_errors == 0 and visual_errors == 0 and missing_images == 0,
4961
+ "pass": lint_errors == 0 and intent_errors == 0 and expectation_errors == 0 and visual_errors == 0 and missing_images == 0,
4957
4962
  }
4958
4963
  output = {"summary": summary, "results": results}
4959
4964
  if args.json:
4960
4965
  print(json.dumps(output, ensure_ascii=False, indent=2))
4961
4966
  else:
4962
- print(f"visual-regress: cases={summary['cases']} pass={summary['pass']} lint_errors={lint_errors} intent_errors={intent_errors} visual_errors={visual_errors} missing_images={missing_images}")
4967
+ print(
4968
+ f"visual-regress: cases={summary['cases']} pass={summary['pass']} "
4969
+ f"lint_errors={lint_errors} intent_errors={intent_errors} expectation_errors={expectation_errors} "
4970
+ f"visual_errors={visual_errors} missing_images={missing_images}"
4971
+ )
4963
4972
  for item in results:
4964
4973
  print(f"- {item['id']}: {item['status']} asset={item.get('asset_type', '?')} digest={item.get('prompt_digest', '?')}")
4965
4974
  return 0 if summary["pass"] else 1
@@ -5025,6 +5034,71 @@ def compile_visual_case(
5025
5034
  }
5026
5035
 
5027
5036
 
5037
+ def case_list(value: object) -> list[str]:
5038
+ if value is None:
5039
+ return []
5040
+ if isinstance(value, list):
5041
+ return [str(item) for item in value if str(item).strip()]
5042
+ return [str(value)] if str(value).strip() else []
5043
+
5044
+
5045
+ def visual_case_expectation_findings(case: dict, compiled: dict) -> list[dict]:
5046
+ spec = compiled.get("spec") or {}
5047
+ prompt = str(compiled.get("prompt") or "")
5048
+ findings: list[dict] = []
5049
+
5050
+ expected_asset = case.get("expect_asset_type")
5051
+ if expected_asset and spec.get("asset_type") != expected_asset:
5052
+ findings.append(
5053
+ {
5054
+ "severity": "error",
5055
+ "rule": "case.asset_type_mismatch",
5056
+ "message": f"期望 asset_type={expected_asset},实际={spec.get('asset_type')}",
5057
+ }
5058
+ )
5059
+
5060
+ expected_aspect = case.get("expect_aspect")
5061
+ if expected_aspect and spec.get("aspect") != expected_aspect:
5062
+ findings.append(
5063
+ {
5064
+ "severity": "error",
5065
+ "rule": "case.aspect_mismatch",
5066
+ "message": f"期望 aspect={expected_aspect},实际={spec.get('aspect')}",
5067
+ }
5068
+ )
5069
+
5070
+ expected_template = case.get("expect_template_id")
5071
+ if expected_template and spec.get("template_id") != expected_template:
5072
+ findings.append(
5073
+ {
5074
+ "severity": "error",
5075
+ "rule": "case.template_mismatch",
5076
+ "message": f"期望 template_id={expected_template},实际={spec.get('template_id')}",
5077
+ }
5078
+ )
5079
+
5080
+ labels = set(spec.get("required_text") or [])
5081
+ for text in case_list(case.get("expect_required_text")):
5082
+ if text not in labels:
5083
+ findings.append({"severity": "error", "rule": "case.required_text_missing", "message": f"缺少必显文字:{text}"})
5084
+ elif f'"{text}"' not in prompt and not spec.get("strict_text"):
5085
+ findings.append({"severity": "error", "rule": "case.required_text_not_quoted", "message": f"Prompt 未逐字引用:{text}"})
5086
+
5087
+ for text in case_list(case.get("forbid_required_text")):
5088
+ if text in labels:
5089
+ findings.append({"severity": "error", "rule": "case.required_text_forbidden", "message": f"不应作为必显文字:{text}"})
5090
+
5091
+ for text in case_list(case.get("expect_prompt_contains")):
5092
+ if text not in prompt:
5093
+ findings.append({"severity": "error", "rule": "case.prompt_missing", "message": f"Prompt 缺少片段:{text}"})
5094
+
5095
+ for text in case_list(case.get("forbid_prompt_contains")):
5096
+ if text in prompt:
5097
+ findings.append({"severity": "error", "rule": "case.prompt_forbidden", "message": f"Prompt 不应包含片段:{text}"})
5098
+
5099
+ return findings
5100
+
5101
+
5028
5102
  def has_lint_error(items: list[dict]) -> bool:
5029
5103
  return any(item.get("severity") == "error" for item in items)
5030
5104