feat: improve skill import and runtime delivery

This commit is contained in:
lofyer
2026-08-07 21:01:17 +08:00
parent 954b42ef55
commit 417a9fccb6
63 changed files with 6136 additions and 527 deletions
@@ -0,0 +1,431 @@
#!/usr/bin/env python3
"""Validate the shared product evidence manifest used by marketing skills."""
import argparse
import datetime as dt
import json
import re
import sys
from pathlib import Path
COLLECTIONS = (
"features",
"parameters",
"claims",
"use_cases",
"differentiators",
"limitations",
"evidence",
)
REQUIRED_FIELDS = {
"features": ("id", "name", "summary", "status"),
"parameters": ("id", "name", "value", "conditions", "disclosure"),
"claims": ("id", "text", "type", "status"),
"use_cases": ("id", "name", "audience", "problem", "workflow", "outcome"),
"differentiators": ("id", "statement", "comparison_scope"),
"limitations": ("id", "text", "applies_to"),
"evidence": (
"id",
"type",
"title",
"source",
"locator",
"verified_on",
"owner",
"disclosure",
),
}
ALLOWED = {
"product.maturity": {"released", "beta", "planned", "deprecated"},
"features.status": {"released", "beta", "planned", "deprecated"},
"claims.type": {"fact", "goal", "comparison"},
"claims.status": {"approved", "draft", "rejected"},
"disclosure": {"public", "restricted", "internal"},
}
OUTPUT_TYPES = {
"feature-catalog",
"technical-spec",
"presentation",
"technical-proposal",
"one-pager",
"whitepaper",
"tender-response",
"sales-demo",
"case-study",
"competitive-positioning",
}
DISCLOSURE_RANK = {"internal": 0, "restricted": 1, "public": 2}
PLACEHOLDERS = re.compile(
r"(?i)(?:\bTBD\b|\bTODO\b|待补充|待确认|placeholder|changeme)"
)
SECRET_QUERY = re.compile(r"(?i)(?:token|api[_-]?key|secret|password)=")
def issue(path, message):
return {"path": path, "message": message}
def is_nonempty_string(value):
return isinstance(value, str) and bool(value.strip())
def validate_record_shape(collection, index, record, errors):
path = f"{collection}[{index}]"
if not isinstance(record, dict):
errors.append(issue(path, "必须是对象"))
return False
for field in REQUIRED_FIELDS[collection]:
value = record.get(field)
if field in ("applies_to",):
if not isinstance(value, list) or not value:
errors.append(issue(f"{path}.{field}", "必须是非空数组"))
elif not is_nonempty_string(value):
errors.append(issue(f"{path}.{field}", "必须是非空字符串"))
return True
def evidence_disclosure_map(records):
return {
record.get("id"): record.get("disclosure")
for record in records["evidence"]
if isinstance(record, dict)
}
def validate_disclosure_chain(records, errors):
"""A record must not be more public than the evidence backing it."""
evidence_disclosure = evidence_disclosure_map(records)
for collection in ("parameters", "claims"):
for index, record in enumerate(records[collection]):
if not isinstance(record, dict):
continue
own = record.get("disclosure")
if own not in DISCLOSURE_RANK:
continue
for ref in record.get("evidence_ids", []) or []:
backing = evidence_disclosure.get(ref)
if backing not in DISCLOSURE_RANK:
continue
if DISCLOSURE_RANK[backing] < DISCLOSURE_RANK[own]:
errors.append(
issue(
f"{collection}[{index}].disclosure",
f"标记为 {own},但证据 {ref} 仅为 {backing}",
)
)
def validate_channel(records, channel, errors):
"""Reject claims cleared for delivery whose evidence is too restricted."""
target = DISCLOSURE_RANK[channel]
evidence_disclosure = evidence_disclosure_map(records)
for index, claim in enumerate(records["claims"]):
if not isinstance(claim, dict) or claim.get("status") != "approved":
continue
if not claim.get("allowed_outputs"):
continue
for ref in claim.get("evidence_ids", []) or []:
disclosure = evidence_disclosure.get(ref)
if disclosure not in DISCLOSURE_RANK:
continue
if DISCLOSURE_RANK[disclosure] < target:
errors.append(
issue(
f"claims[{index}].evidence_ids",
f"证据 {ref}{disclosure},不足以支撑 {channel} 渠道",
)
)
def validate_manifest(data, channel=None):
errors = []
warnings = []
if not isinstance(data, dict):
return [issue("$", "根节点必须是对象")], warnings
if data.get("schema_version") != 1:
errors.append(issue("schema_version", "当前仅支持整数 1"))
product = data.get("product")
if not isinstance(product, dict):
errors.append(issue("product", "必须是对象"))
product = {}
for field in ("name", "version", "category", "summary", "maturity"):
if not is_nonempty_string(product.get(field)):
errors.append(issue(f"product.{field}", "必须是非空字符串"))
if product.get("maturity") not in ALLOWED["product.maturity"]:
errors.append(
issue(
"product.maturity",
f"必须是 {sorted(ALLOWED['product.maturity'])} 之一",
)
)
records = {}
all_ids = {}
for collection in COLLECTIONS:
values = data.get(collection, [])
if not isinstance(values, list):
errors.append(issue(collection, "必须是数组"))
values = []
records[collection] = values
for index, record in enumerate(values):
if not validate_record_shape(collection, index, record, errors):
continue
record_id = record.get("id")
if is_nonempty_string(record_id):
if record_id in all_ids:
errors.append(
issue(
f"{collection}[{index}].id",
f"ID 与 {all_ids[record_id]} 重复:{record_id}",
)
)
else:
all_ids[record_id] = f"{collection}[{index}]"
evidence_ids = {
record.get("id")
for record in records["evidence"]
if isinstance(record, dict) and is_nonempty_string(record.get("id"))
}
feature_ids = {
record.get("id")
for record in records["features"]
if isinstance(record, dict) and is_nonempty_string(record.get("id"))
}
use_case_ids = {
record.get("id")
for record in records["use_cases"]
if isinstance(record, dict) and is_nonempty_string(record.get("id"))
}
for collection in COLLECTIONS:
for index, record in enumerate(records[collection]):
if not isinstance(record, dict):
continue
path = f"{collection}[{index}]"
refs = record.get("evidence_ids", [])
if refs is not None and not isinstance(refs, list):
errors.append(issue(f"{path}.evidence_ids", "必须是数组"))
refs = []
for ref in refs or []:
if ref not in evidence_ids:
errors.append(
issue(f"{path}.evidence_ids", f"引用不存在:{ref}")
)
for ref in record.get("use_case_ids", []) or []:
if ref not in use_case_ids:
errors.append(
issue(f"{path}.use_case_ids", f"引用不存在:{ref}")
)
for index, limitation in enumerate(records["limitations"]):
if not isinstance(limitation, dict):
continue
for ref in limitation.get("applies_to", []) or []:
if ref not in feature_ids and ref not in all_ids:
errors.append(
issue(
f"limitations[{index}].applies_to",
f"引用不存在:{ref}",
)
)
for index, feature in enumerate(records["features"]):
if not isinstance(feature, dict):
continue
status = feature.get("status")
if status not in ALLOWED["features.status"]:
errors.append(
issue(
f"features[{index}].status",
f"必须是 {sorted(ALLOWED['features.status'])} 之一",
)
)
if status in {"released", "beta"} and not feature.get("evidence_ids"):
warnings.append(
issue(
f"features[{index}].evidence_ids",
"已发布或 beta 功能缺少证据",
)
)
for index, parameter in enumerate(records["parameters"]):
if not isinstance(parameter, dict):
continue
disclosure = parameter.get("disclosure")
if disclosure not in ALLOWED["disclosure"]:
errors.append(
issue(
f"parameters[{index}].disclosure",
f"必须是 {sorted(ALLOWED['disclosure'])} 之一",
)
)
if not parameter.get("evidence_ids"):
warnings.append(
issue(
f"parameters[{index}].evidence_ids",
"技术参数缺少证据",
)
)
for index, claim in enumerate(records["claims"]):
if not isinstance(claim, dict):
continue
if claim.get("type") not in ALLOWED["claims.type"]:
errors.append(
issue(
f"claims[{index}].type",
f"必须是 {sorted(ALLOWED['claims.type'])} 之一",
)
)
if claim.get("status") not in ALLOWED["claims.status"]:
errors.append(
issue(
f"claims[{index}].status",
f"必须是 {sorted(ALLOWED['claims.status'])} 之一",
)
)
if (
claim.get("type") in {"fact", "comparison"}
and claim.get("status") == "approved"
and not claim.get("evidence_ids")
):
errors.append(
issue(
f"claims[{index}].evidence_ids",
"已批准的事实或比较主张必须有证据",
)
)
outputs = claim.get("allowed_outputs", [])
if outputs is not None and not isinstance(outputs, list):
errors.append(
issue(f"claims[{index}].allowed_outputs", "必须是数组")
)
for output in outputs or []:
if output not in OUTPUT_TYPES:
errors.append(
issue(
f"claims[{index}].allowed_outputs",
f"未知产物类型:{output}",
)
)
for index, evidence in enumerate(records["evidence"]):
if not isinstance(evidence, dict):
continue
disclosure = evidence.get("disclosure")
if disclosure not in ALLOWED["disclosure"]:
errors.append(
issue(
f"evidence[{index}].disclosure",
f"必须是 {sorted(ALLOWED['disclosure'])} 之一",
)
)
verified_on = evidence.get("verified_on")
if is_nonempty_string(verified_on):
try:
dt.date.fromisoformat(verified_on)
except ValueError:
errors.append(
issue(
f"evidence[{index}].verified_on",
"必须使用 YYYY-MM-DD",
)
)
source = evidence.get("source", "")
if is_nonempty_string(source) and SECRET_QUERY.search(source):
errors.append(
issue(
f"evidence[{index}].source",
"来源中疑似包含密钥或令牌参数",
)
)
for index, differentiator in enumerate(records["differentiators"]):
if isinstance(differentiator, dict) and not differentiator.get(
"evidence_ids"
):
warnings.append(
issue(
f"differentiators[{index}].evidence_ids",
"差异点缺少证据",
)
)
validate_disclosure_chain(records, errors)
if channel in DISCLOSURE_RANK:
validate_channel(records, channel, errors)
for path, value in walk_strings(data):
if PLACEHOLDERS.search(value):
warnings.append(issue(path, f"包含占位内容:{value[:60]}"))
if not evidence_ids:
warnings.append(issue("evidence", "没有任何证据记录"))
return errors, warnings
def walk_strings(value, path="$"):
if isinstance(value, dict):
for key, item in value.items():
yield from walk_strings(item, f"{path}.{key}")
elif isinstance(value, list):
for index, item in enumerate(value):
yield from walk_strings(item, f"{path}[{index}]")
elif isinstance(value, str):
yield path, value
def parse_args():
parser = argparse.ArgumentParser(description="验证产品事实与证据清单")
parser.add_argument("manifest", help="product-evidence.json")
parser.add_argument("--json", action="store_true", help="输出 JSON")
parser.add_argument(
"--strict",
action="store_true",
help="存在占位符、缺证据等警告时也失败",
)
parser.add_argument(
"--channel",
choices=sorted(DISCLOSURE_RANK),
help="目标发布渠道,校验主张引用证据的公开级别是否足够",
)
return parser.parse_args()
def main():
args = parse_args()
path = Path(args.manifest).expanduser().resolve()
with path.open(encoding="utf-8") as handle:
data = json.load(handle)
errors, warnings = validate_manifest(data, channel=args.channel)
report = {
"file": str(path),
"channel": args.channel,
"valid": not errors and (not args.strict or not warnings),
"errors": errors,
"warnings": warnings,
"stats": {
collection: len(data.get(collection, []))
if isinstance(data, dict) and isinstance(data.get(collection, []), list)
else 0
for collection in COLLECTIONS
},
}
if args.json:
print(json.dumps(report, ensure_ascii=False, indent=2))
else:
for level, items in (("错误", errors), ("警告", warnings)):
for item in items:
print(f"[{level}] {item['path']}{item['message']}")
print(
f"核验完成:错误 {len(errors)},警告 {len(warnings)}"
f"状态 {'通过' if report['valid'] else '失败'}"
)
return 0 if report["valid"] else 1
if __name__ == "__main__":
sys.exit(main())