@yuhan1124/draw-prompt 0.4.7 → 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 +3 -1
- package/SKILL.md +1 -1
- package/package.json +1 -1
- package/scripts/prompt_cli.py +242 -6
package/README.md
CHANGED
|
@@ -69,7 +69,9 @@ npx --yes --registry=https://registry.npmjs.org/ @yuhan1124/draw-prompt@latest i
|
|
|
69
69
|
|
|
70
70
|
`install-skill` 默认复制 npm 包内的运行文件,避免软链到 npx 缓存导致后续路径失效。它只复制
|
|
71
71
|
`SKILL.md`、CLI、references 和必要元数据;开发仓库里的 `tests/`、`tmp/`、`golden-cases.jsonl`、
|
|
72
|
-
`visual-cases.jsonl` 不会进入 npm
|
|
72
|
+
`visual-cases.jsonl` 不会进入 npm 包或安装目录。安装后可直接跑 `doctor`,它会检查
|
|
73
|
+
包文件、版本一致性、核心单图转化、真实长输入 `compose` 链路,以及
|
|
74
|
+
`variants` / `series` / `adapt` 等主工作流 smoke。
|
|
73
75
|
|
|
74
76
|
开发者本地调试也可以软链 repo:
|
|
75
77
|
|
package/SKILL.md
CHANGED
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.9"
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
PACKAGED_SKILL_FILES = [
|
|
@@ -211,6 +211,57 @@ DOCTOR_CASES = [
|
|
|
211
211
|
},
|
|
212
212
|
]
|
|
213
213
|
|
|
214
|
+
DOCTOR_COMPOSE_CASES = [
|
|
215
|
+
{
|
|
216
|
+
"id": "long-input-product-plan",
|
|
217
|
+
"request": (
|
|
218
|
+
"请把下面这段产品方案整理成 4 张图用于路演:我们要做一个 AI 生图 Prompt 转化 skill。"
|
|
219
|
+
"目标用户是经常把长文档、产品需求、运营数据、架构说明变成图片的研发和运营同学。"
|
|
220
|
+
"当前痛点是直接把长输入丢给模型容易跑偏,常见问题包括风格不稳定、中文标签遗漏、架构图被画成 UI、产品图漏卖点、活动战报颜色跑偏。"
|
|
221
|
+
"方案包括:场景识别、风格预设、意图保持检查、质量门、真实 Image 2 验证、样本反馈学习。"
|
|
222
|
+
"上线标准是安装后 doctor 通过、常见场景开箱高质量、NPM 公网安装可用。"
|
|
223
|
+
),
|
|
224
|
+
"max_images": 4,
|
|
225
|
+
"style_preset": "corporate",
|
|
226
|
+
"expect_count": 4,
|
|
227
|
+
"expect_no_asset_type": "illustration",
|
|
228
|
+
"expect_required_text_by_index": {
|
|
229
|
+
0: ["目标用户"],
|
|
230
|
+
1: ["当前痛点"],
|
|
231
|
+
2: ["方案", "场景识别", "风格预设", "质量门"],
|
|
232
|
+
3: ["上线标准", "NPM 公网安装可用"],
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
]
|
|
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
|
+
|
|
214
265
|
|
|
215
266
|
def package_root() -> Path:
|
|
216
267
|
return Path(__file__).resolve().parent.parent
|
|
@@ -2551,6 +2602,7 @@ def extract_required_texts(request: str, explicit_texts: list[str]) -> list[str]
|
|
|
2551
2602
|
r"(?:时间|地点)\s+([^,,、。;;\n]{2,40})",
|
|
2552
2603
|
r"(?:核心卡片|主要按钮|主按钮|按钮)\s*(?:写上|写|显示|为|是|叫|[::])\s*([^,,。;;\n]{2,30})",
|
|
2553
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",
|
|
2554
2606
|
r"(?:名为|叫做|名称是|名字叫)\s*([A-Za-z][A-Za-z0-9_-]{2,30})\b",
|
|
2555
2607
|
]
|
|
2556
2608
|
for pat in labeled_single_patterns:
|
|
@@ -2698,8 +2750,9 @@ def infer_style_anchors(request: str, override: str | None, profile: dict, prese
|
|
|
2698
2750
|
|
|
2699
2751
|
|
|
2700
2752
|
def infer_palette_constraints(request: str) -> list[str]:
|
|
2701
|
-
|
|
2702
|
-
|
|
2753
|
+
cjk_colors = "红橙黄绿青蓝紫粉黑白灰金银棕"
|
|
2754
|
+
false_color_tokens = ["小红书", "红书", "白皮书"]
|
|
2755
|
+
direct_color_terms = [
|
|
2703
2756
|
"深色", "浅色", "渐变", "配色", "主色", "点缀", "背景",
|
|
2704
2757
|
"red", "orange", "yellow", "green", "cyan", "blue", "purple", "pink", "black", "white",
|
|
2705
2758
|
"gray", "grey", "gold", "silver", "brown", "gradient", "palette", "color", "colour",
|
|
@@ -2712,7 +2765,11 @@ def infer_palette_constraints(request: str) -> list[str]:
|
|
|
2712
2765
|
if not fragment:
|
|
2713
2766
|
continue
|
|
2714
2767
|
lower = fragment.lower()
|
|
2715
|
-
if
|
|
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):
|
|
2716
2773
|
continue
|
|
2717
2774
|
if len(fragment) > 72:
|
|
2718
2775
|
fragment = fragment[:72].rstrip()
|
|
@@ -4887,6 +4944,30 @@ def split_document_sections(text: str, max_images: int) -> list[str]:
|
|
|
4887
4944
|
if buf:
|
|
4888
4945
|
chunks.append(" ".join(buf).strip())
|
|
4889
4946
|
|
|
4947
|
+
if len(chunks) <= 1:
|
|
4948
|
+
semantic_patterns = [
|
|
4949
|
+
r"(目标用户(?:是|:|:)[^。;;\n]+)",
|
|
4950
|
+
r"(用户场景(?:是|:|:)[^。;;\n]+)",
|
|
4951
|
+
r"(当前痛点(?:是|:|:)[^。;;\n]+)",
|
|
4952
|
+
r"(主要问题(?:是|:|:)[^。;;\n]+)",
|
|
4953
|
+
r"(方案(?:包括|是|:|:)[^。;;\n]+)",
|
|
4954
|
+
r"(核心能力(?:包括|是|:|:)[^。;;\n]+)",
|
|
4955
|
+
r"(上线标准(?:是|:|:)[^。;;\n]+)",
|
|
4956
|
+
r"(验收标准(?:是|:|:)[^。;;\n]+)",
|
|
4957
|
+
]
|
|
4958
|
+
semantic_matches: list[tuple[int, str]] = []
|
|
4959
|
+
seen_spans: set[tuple[int, int]] = set()
|
|
4960
|
+
for pattern in semantic_patterns:
|
|
4961
|
+
for match in re.finditer(pattern, text):
|
|
4962
|
+
span = match.span(1)
|
|
4963
|
+
if span in seen_spans:
|
|
4964
|
+
continue
|
|
4965
|
+
seen_spans.add(span)
|
|
4966
|
+
semantic_matches.append((span[0], match.group(1).strip()))
|
|
4967
|
+
semantic_chunks = [chunk for _, chunk in sorted(semantic_matches, key=lambda item: item[0])]
|
|
4968
|
+
if len(semantic_chunks) >= 2:
|
|
4969
|
+
chunks = semantic_chunks
|
|
4970
|
+
|
|
4890
4971
|
if len(chunks) <= 1:
|
|
4891
4972
|
sentences = [s.strip() for s in re.split(r"(?<=[。!?!?;;])\s*", text) if s.strip()]
|
|
4892
4973
|
chunks = []
|
|
@@ -4908,6 +4989,8 @@ def split_document_sections(text: str, max_images: int) -> list[str]:
|
|
|
4908
4989
|
|
|
4909
4990
|
def choose_compose_asset(chunk: str, index: int) -> str:
|
|
4910
4991
|
lower = chunk.lower()
|
|
4992
|
+
if re.search(r"^(?:目标用户|用户场景|当前痛点|主要问题|上线标准|验收标准)", chunk):
|
|
4993
|
+
return "infographic"
|
|
4911
4994
|
if any(k in lower for k in ["架构", "系统", "模块", "链路", "rag", "llm", "retriever", "pipeline"]):
|
|
4912
4995
|
return "diagram"
|
|
4913
4996
|
if any(k in lower for k in ["数据", "指标", "报表", "趋势", "占比", "转化率", "漏斗", "图表"]):
|
|
@@ -4922,7 +5005,9 @@ def choose_compose_asset(chunk: str, index: int) -> str:
|
|
|
4922
5005
|
return "character"
|
|
4923
5006
|
if index == 0 and any(k in lower for k in ["标题", "主题", "发布", "活动", "封面", "总结"]):
|
|
4924
5007
|
return "poster"
|
|
4925
|
-
|
|
5008
|
+
if any(k in lower for k in ["插画", "场景插图", "故事", "氛围图", "场景图", "scene illustration"]):
|
|
5009
|
+
return "illustration"
|
|
5010
|
+
return "infographic"
|
|
4926
5011
|
|
|
4927
5012
|
|
|
4928
5013
|
def infer_compose_style(text: str) -> str:
|
|
@@ -4946,6 +5031,13 @@ def extract_visual_labels(chunk: str, asset_type: str, limit: int = 5) -> list[s
|
|
|
4946
5031
|
|
|
4947
5032
|
for match in re.findall(r'"([^"\n]{1,28})"|“([^”\n]{1,28})”|「([^」\n]{1,28})」', chunk):
|
|
4948
5033
|
add(next((m for m in match if m), ""))
|
|
5034
|
+
for match in re.finditer(r"(目标用户|用户场景|当前痛点|主要问题|方案|核心能力|上线标准|验收标准)\s*(?:是|包括|包含|:|:)\s*([^。;;\n]{2,140})", chunk):
|
|
5035
|
+
add(match.group(1))
|
|
5036
|
+
value = match.group(2)
|
|
5037
|
+
for part in re.split(r"[、,,/|]", value):
|
|
5038
|
+
part = re.sub(r"^(?:和|与|及|以及|and)\s*", "", part.strip(), flags=re.IGNORECASE)
|
|
5039
|
+
if 1 < len(part) <= 18:
|
|
5040
|
+
add(part)
|
|
4949
5041
|
for match in re.findall(r"\b[A-Z][A-Za-z0-9_-]{1,20}\b", chunk):
|
|
4950
5042
|
add(match)
|
|
4951
5043
|
for match in re.findall(r"(?:标题|主题|模块|步骤|节点|页面)[::\s]*([^,。;;\n]{2,24})", chunk):
|
|
@@ -4964,7 +5056,7 @@ def compose_purpose(asset_type: str, index: int) -> str:
|
|
|
4964
5056
|
"ui": "界面概念图",
|
|
4965
5057
|
"product": "产品视觉图",
|
|
4966
5058
|
"character": "角色设定图",
|
|
4967
|
-
"illustration": "
|
|
5059
|
+
"illustration": "场景插画",
|
|
4968
5060
|
}
|
|
4969
5061
|
return f"{index}. {purpose_map.get(asset_type, '配图')}"
|
|
4970
5062
|
|
|
@@ -5796,6 +5888,140 @@ def doctor_check_case(case: dict) -> dict:
|
|
|
5796
5888
|
}
|
|
5797
5889
|
|
|
5798
5890
|
|
|
5891
|
+
def doctor_check_compose_case(case: dict) -> dict:
|
|
5892
|
+
findings: list[dict] = []
|
|
5893
|
+
text = str(case.get("request") or "")
|
|
5894
|
+
max_images = int(case.get("max_images") or 4)
|
|
5895
|
+
chunks = split_document_sections(text, max_images)
|
|
5896
|
+
shared_style = infer_compose_style(text)
|
|
5897
|
+
visual_plan: list[dict] = []
|
|
5898
|
+
for idx, chunk in enumerate(chunks, start=1):
|
|
5899
|
+
asset_type = choose_compose_asset(chunk, idx - 1)
|
|
5900
|
+
labels = extract_visual_labels(chunk, asset_type)
|
|
5901
|
+
purpose = compose_purpose(asset_type, idx)
|
|
5902
|
+
compiled = compile_visual_case(
|
|
5903
|
+
{
|
|
5904
|
+
"id": f"compose-{idx:02d}",
|
|
5905
|
+
"request": f"{purpose}。根据这段内容生成对应画面:{chunk}",
|
|
5906
|
+
"asset_type": asset_type,
|
|
5907
|
+
"style": shared_style,
|
|
5908
|
+
"style_preset": case.get("style_preset"),
|
|
5909
|
+
"text": labels if asset_type in {"diagram", "infographic", "ui"} else [],
|
|
5910
|
+
"target": "raw",
|
|
5911
|
+
"tags": "compose,long-input",
|
|
5912
|
+
},
|
|
5913
|
+
target="raw",
|
|
5914
|
+
include_handoff=False,
|
|
5915
|
+
)
|
|
5916
|
+
visual_plan.append({"chunk": chunk, "compiled": compiled})
|
|
5917
|
+
for item in compiled["lint"]:
|
|
5918
|
+
findings.append({"severity": item["severity"], "rule": f"compose.{idx}.lint.{item['rule']}", "message": item["message"]})
|
|
5919
|
+
for item in compiled["intent_check"]:
|
|
5920
|
+
findings.append({"severity": item["severity"], "rule": f"compose.{idx}.intent.{item['rule']}", "message": item["message"]})
|
|
5921
|
+
|
|
5922
|
+
if case.get("expect_count") and len(visual_plan) != int(case["expect_count"]):
|
|
5923
|
+
doctor_add(findings, "error", "compose.count", f"期望 {case['expect_count']} 张图,实际 {len(visual_plan)}")
|
|
5924
|
+
forbidden_asset = case.get("expect_no_asset_type")
|
|
5925
|
+
if forbidden_asset:
|
|
5926
|
+
for idx, item in enumerate(visual_plan, start=1):
|
|
5927
|
+
asset_type = item["compiled"]["spec"].get("asset_type")
|
|
5928
|
+
if asset_type == forbidden_asset:
|
|
5929
|
+
doctor_add(findings, "error", "compose.asset_type", f"第 {idx} 张图不应路由为 {forbidden_asset}")
|
|
5930
|
+
for raw_index, expected_texts in (case.get("expect_required_text_by_index") or {}).items():
|
|
5931
|
+
index = int(raw_index)
|
|
5932
|
+
if index >= len(visual_plan):
|
|
5933
|
+
doctor_add(findings, "error", "compose.index_missing", f"缺少第 {index + 1} 张图")
|
|
5934
|
+
continue
|
|
5935
|
+
labels = set(visual_plan[index]["compiled"]["spec"].get("required_text") or [])
|
|
5936
|
+
prompt = visual_plan[index]["compiled"]["prompt"]
|
|
5937
|
+
for text_item in expected_texts:
|
|
5938
|
+
if text_item not in labels:
|
|
5939
|
+
doctor_add(findings, "error", "compose.required_text_missing", f"第 {index + 1} 张图缺少必显文字:{text_item}")
|
|
5940
|
+
elif f'"{text_item}"' not in prompt:
|
|
5941
|
+
doctor_add(findings, "error", "compose.required_text_not_quoted", f"第 {index + 1} 张图 Prompt 未逐字引用:{text_item}")
|
|
5942
|
+
return {
|
|
5943
|
+
"name": f"compose-{case['id']}",
|
|
5944
|
+
"pass": not has_lint_error(findings),
|
|
5945
|
+
"count": len(visual_plan),
|
|
5946
|
+
"asset_types": [item["compiled"]["spec"].get("asset_type") for item in visual_plan],
|
|
5947
|
+
"prompt_digests": [item["compiled"]["prompt_digest"] for item in visual_plan],
|
|
5948
|
+
"required_text": [item["compiled"]["spec"].get("required_text") or [] for item in visual_plan],
|
|
5949
|
+
"findings": findings,
|
|
5950
|
+
}
|
|
5951
|
+
|
|
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
|
+
|
|
5799
6025
|
def cmd_doctor(args: argparse.Namespace) -> int:
|
|
5800
6026
|
root = package_root()
|
|
5801
6027
|
checks = [doctor_check_package(root)]
|
|
@@ -5804,6 +6030,16 @@ def cmd_doctor(args: argparse.Namespace) -> int:
|
|
|
5804
6030
|
checks.append(doctor_check_case(case))
|
|
5805
6031
|
except Exception as exc:
|
|
5806
6032
|
checks.append({"name": case.get("id", "case"), "pass": False, "findings": [{"severity": "error", "rule": "case.exception", "message": str(exc)}]})
|
|
6033
|
+
for case in DOCTOR_COMPOSE_CASES:
|
|
6034
|
+
try:
|
|
6035
|
+
checks.append(doctor_check_compose_case(case))
|
|
6036
|
+
except Exception as exc:
|
|
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)}]})
|
|
5807
6043
|
passed = all(item["pass"] for item in checks)
|
|
5808
6044
|
summary = {"pass": passed, "version": COMPILER_VERSION, "checks": len(checks), "failed": len([item for item in checks if not item["pass"]])}
|
|
5809
6045
|
if args.json:
|