@ccoalm/ccl-skills 0.3.0 → 0.5.0
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/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/classify_envelope.py +34 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_abort_leak_state_helpers.sh +148 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_classify_envelope.sh +28 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_review_json.sh +7 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate_abort_leak.sh +271 -34
- package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/references/public-data-acquisition.md +3 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/references/public-disclosure-channels.md +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +12 -12
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/dual-track-review-gate.md +8 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/eval-routing.md +7 -8
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +12 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +3 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/firing-point-placement.md +8 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/harness-patterns-and-eval.md +7 -6
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/recurring-anti-patterns-checklist.md +18 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +47 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md +28 -29
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh +165 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-health.rb +23 -10
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +303 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +222 -91
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +12 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_ci_checkout_ref_binding.sh +85 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_entrypoint_domain_scan_terms.sh +123 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_dateless_host.sh +6 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_verdict_differential.sh +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_round_attribution.sh +12 -12
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_self_adjudication.sh +455 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_source_refuted.sh +20 -20
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_liveness_predicate_gate.sh +288 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/SKILL.md +4 -4
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/deliverable-doc-genre-skeletons.md +133 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/doc-charter-first.md +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/figure-and-table-craft.md +318 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/AGENTS.md +46 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/doc-lint.py +246 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/figure-lint.py +1092 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/mutation_probe.sh +100 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/test_figure_and_doc_lint.sh +375 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/control.md +10 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/empty-header.md +6 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/fake-header.md +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/fenced-noise.md +14 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/fig-dangling.md +5 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/fig-orphan-captioned.md +11 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/fig-orphan.md +9 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/fig.png +0 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/imbalance.md +41 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/no-unit.md +8 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/should-be-chart.md +11 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/tables-only-clean.md +35 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/unfilled.md +7 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/wide-table.md +5 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/bad-viewbox.svg +9 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/blackmarker.svg +9 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/control.svg +12 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/crossings.svg +12 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/cvd-confusable.svg +9 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/decorative-line.svg +10 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/edge-no-arrow.svg +11 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/edge-vague.svg +12 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/figure-contract.json +21 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/figure-is-a-list.svg +12 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/flow-mixed.svg +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/low-contrast.svg +12 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/malformed.svg +1 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/no-aria.svg +9 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/no-group.svg +10 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/no-legend.svg +9 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/no-title.svg +12 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/no-viewbox.svg +9 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/offcontract-shape.svg +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/overflow.svg +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/transformed.svg +9 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/ungrouped-card.svg +12 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/svg/unlabeled-edge.svg +13 -0
- package/dist/assets/release.json +276 -31
- package/package.json +1 -1
package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/mutation_probe.sh
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 差分敏感度实测:逐谓词把其 code 字面量替换掉使其在结果中消失,
|
|
3
|
+
# 确认自测套件转红。绿的谓词 = 该谓词没有覆盖。
|
|
4
|
+
#
|
|
5
|
+
# 为什么必须"施加"而不是"断言":本目录实测踩过两种无效突变——
|
|
6
|
+
# ① 改的是 add() 的 severity 参数,而断言比较 code 集合,
|
|
7
|
+
# 改了 oracle 不观测的维度,全绿是假通过;
|
|
8
|
+
# ② 脚本崩溃时也没有 FAIL 行,只看 FAIL 行会把崩溃读成绿。
|
|
9
|
+
# 所以判据同时看退出码与 FAIL 行。
|
|
10
|
+
set -uo pipefail
|
|
11
|
+
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
12
|
+
work="$(mktemp -d)"; trap 'rm -rf "${work}"' EXIT
|
|
13
|
+
cp -R "${here}" "${work}/scripts"
|
|
14
|
+
cd "${work}/scripts" || exit 1
|
|
15
|
+
|
|
16
|
+
bash test_figure_and_doc_lint.sh >/dev/null 2>&1 || {
|
|
17
|
+
echo "BASELINE-RED: 未突变时套件就是红的,实验无效"; exit 1; }
|
|
18
|
+
echo "baseline: GREEN"
|
|
19
|
+
|
|
20
|
+
# 谓词清单从**源码派生**,不手写:手写清单会漏掉新增谓词,
|
|
21
|
+
# 而漏掉的那条就没有任何覆盖。独立评审实测抓到过——手写的 24 条里
|
|
22
|
+
# 漏了 PARSE / C4-VIEWBOX / C4-EDGE-VAGUE 三条。
|
|
23
|
+
extract_codes() { # <linter-file>
|
|
24
|
+
# 必须覆盖**所有发射路径与所有档位**。踩过两次分母偏小:
|
|
25
|
+
# ① 早先只认 add(...) 与错误元组,漏掉走 findings.append({'code': ...}) 的
|
|
26
|
+
# CONTRACT-MISSING / CONTRACT-INVALID;
|
|
27
|
+
# ② 档位只写了 ERROR|WARN,于是把一条谓词改成 INFO 就让它**静默退出分母**,
|
|
28
|
+
# 总数看起来不变、覆盖却少了一条。档位一律用 [A-Z]+ 匹配,不枚举。
|
|
29
|
+
python3 - "$1" <<'PYEOF'
|
|
30
|
+
import re, sys
|
|
31
|
+
src = open(sys.argv[1], encoding="utf8").read()
|
|
32
|
+
pats = [
|
|
33
|
+
r"""add\(\s*['"][A-Z]+['"]\s*,\s*['"]([A-Z0-9][A-Z0-9-]*)['"]""", # add('<档位>', 'CODE'
|
|
34
|
+
r"""\(\s*['"][A-Z]+['"]\s*,\s*['"]([A-Z0-9][A-Z0-9-]*)['"]""", # ('<档位>', 'CODE'
|
|
35
|
+
r"""['"]code['"]\s*:\s*['"]([A-Z0-9][A-Z0-9-]*)['"]""", # {'code': 'CODE'
|
|
36
|
+
]
|
|
37
|
+
codes = set()
|
|
38
|
+
for pat in pats:
|
|
39
|
+
codes |= set(re.findall(pat, src))
|
|
40
|
+
codes.discard("MUTANT-GONE")
|
|
41
|
+
for c in sorted(codes):
|
|
42
|
+
print(c)
|
|
43
|
+
PYEOF
|
|
44
|
+
}
|
|
45
|
+
codes_fig="$(extract_codes figure-lint.py)"
|
|
46
|
+
codes_doc="$(extract_codes doc-lint.py)"
|
|
47
|
+
echo "codes_from_source: figure=$(echo "${codes_fig}" | wc -w | tr -d ' ') doc=$(echo "${codes_doc}" | wc -w | tr -d ' ')"
|
|
48
|
+
|
|
49
|
+
pass=0; total=0; missed=""
|
|
50
|
+
probe() {
|
|
51
|
+
local file="$1" code="$2"
|
|
52
|
+
total=$((total + 1))
|
|
53
|
+
cp "${file}" "${file}.bak"
|
|
54
|
+
MUT_FILE="${file}" MUT_CODE="${code}" python3 -c '
|
|
55
|
+
import os
|
|
56
|
+
p = os.environ["MUT_FILE"]
|
|
57
|
+
s = open(p, encoding="utf8").read()
|
|
58
|
+
open(p, "w").write(s.replace("'"'"'" + os.environ["MUT_CODE"] + "'"'"'", "'"'"'MUTANT-GONE'"'"'"))
|
|
59
|
+
'
|
|
60
|
+
local out rc
|
|
61
|
+
out="$(bash test_figure_and_doc_lint.sh 2>&1)"; rc=$?
|
|
62
|
+
mv "${file}.bak" "${file}"
|
|
63
|
+
if [ "${rc}" -ne 0 ] && printf '%s' "${out}" | grep -q FAIL; then
|
|
64
|
+
pass=$((pass + 1)); printf ' %-28s RED ok\n' "${code}"
|
|
65
|
+
else
|
|
66
|
+
missed="${missed} ${code}"; printf ' %-28s GREEN NO-COVERAGE\n' "${code}"
|
|
67
|
+
fi
|
|
68
|
+
}
|
|
69
|
+
for c in ${codes_fig}; do probe figure-lint.py "${c}"; done
|
|
70
|
+
for c in ${codes_doc}; do probe doc-lint.py "${c}"; done
|
|
71
|
+
# 派生集合必须覆盖 linter 在自测语料上实际输出过的每一个 code。
|
|
72
|
+
# 不断言的话,一条新增谓词只要用了未被派生正则识别的发射路径,就会静默无覆盖。
|
|
73
|
+
emitted="$(
|
|
74
|
+
{ python3 figure-lint.py tests/svg/*.svg --json 2>/dev/null || true
|
|
75
|
+
python3 doc-lint.py tests/doc/*.md --json 2>/dev/null || true
|
|
76
|
+
} | python3 -c '
|
|
77
|
+
import json, sys
|
|
78
|
+
codes = set()
|
|
79
|
+
for chunk in sys.stdin.read().split("\n{"):
|
|
80
|
+
text = chunk if chunk.startswith("{") else "{" + chunk
|
|
81
|
+
try:
|
|
82
|
+
d = json.loads(text)
|
|
83
|
+
except Exception:
|
|
84
|
+
continue
|
|
85
|
+
for v in d.get("files", d).values(): # doc-lint 的 JSON 顶层就是文件映射,没有 files 键
|
|
86
|
+
codes |= {x["code"] for x in v["findings"]}
|
|
87
|
+
print(" ".join(sorted(codes)))
|
|
88
|
+
')"
|
|
89
|
+
# codes_* 是换行分隔的,必须先规范成单空格分隔再做包含匹配,
|
|
90
|
+
# 否则 case 模式永远匹配不上、把每一条都误报成缺口(本脚本实测踩过)。
|
|
91
|
+
derived=" $(printf '%s %s' "${codes_fig}" "${codes_doc}" | tr '\n' ' ' | tr -s ' ') "
|
|
92
|
+
for c in ${emitted}; do
|
|
93
|
+
case "${derived}" in
|
|
94
|
+
*" ${c} "*) ;;
|
|
95
|
+
*) echo "DERIVATION-GAP: ${c} 实际会被输出,却不在派生清单里——该谓词无突变覆盖"; missed="${missed} ${c}";;
|
|
96
|
+
esac
|
|
97
|
+
done
|
|
98
|
+
echo "differential_sensitivity=${pass}/${total}"
|
|
99
|
+
[ -n "${missed}" ] && { echo "uncovered:${missed}"; exit 1; }
|
|
100
|
+
echo "mutation_probe_ok"
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 逐谓词差分自测:每个 fixture 只应触发它自己的那一条,控制组必须干净。
|
|
3
|
+
#
|
|
4
|
+
# 这是 references/figure-and-table-craft.md §8 第 1、2 条的机械化:
|
|
5
|
+
# 1) 先证明检查器能报失败再信它的绿;
|
|
6
|
+
# 2) 改判据必须同步改 fixture——本测试就是"fixture 失效但仍通过"的防线。
|
|
7
|
+
#
|
|
8
|
+
# 已踩过的坑,改本文件前先读:
|
|
9
|
+
# - CJK 标点紧跟 $var 会被 bash 吞进变量名(踩过两次)——插值一律用 ${var}。
|
|
10
|
+
# - 改函数签名必须同步改调用点:曾把 ext 参数加进签名却漏改调用点,
|
|
11
|
+
# 第一条期望(控制组)被当成 ext 吃掉,control.svg 变脏仍全绿。
|
|
12
|
+
# 现在参数不足会直接判红,且期望表是**全量对应**:报告里多一个或少一个文件都判红,
|
|
13
|
+
# 所以意外文件(如通配到 figure-contract.json)不会再被无声忽略。
|
|
14
|
+
set -uo pipefail
|
|
15
|
+
|
|
16
|
+
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
17
|
+
fail=0
|
|
18
|
+
|
|
19
|
+
note() { printf '%s\n' "$*"; }
|
|
20
|
+
bad() { printf 'FAIL %s\n' "$*"; fail=1; }
|
|
21
|
+
|
|
22
|
+
# run_expect <linter> <fixture-dir> <ext> <expectation>...
|
|
23
|
+
# expectation 形如 "file=CODE[,CODE]";等号后为空表示该 fixture 必须干净。
|
|
24
|
+
run_expect() {
|
|
25
|
+
if [ "$#" -lt 4 ]; then
|
|
26
|
+
bad "run_expect 参数不足(需 linter dir ext expectation...),实得 $# 个"; return
|
|
27
|
+
fi
|
|
28
|
+
local linter="$1" dir="$2" ext="$3"; shift 3
|
|
29
|
+
local json
|
|
30
|
+
# 两个 linter 在有发现时都返回非 0(json 模式亦然),所以不能用退出码判执行失败;
|
|
31
|
+
# 判据是"输出能否解析成 JSON"。
|
|
32
|
+
json="$(python3 "${here}/${linter}" "${dir}"/*."${ext}" --json 2>/dev/null)"
|
|
33
|
+
printf '%s' "${json}" | python3 -c 'import json,sys; json.load(sys.stdin)' 2>/dev/null || {
|
|
34
|
+
bad "${linter} 未产出可解析 JSON(${dir})"; return; }
|
|
35
|
+
|
|
36
|
+
local result
|
|
37
|
+
result="$(printf '%s' "${json}" | LINT_EXPECT="$*" python3 -c '
|
|
38
|
+
import json, sys, os
|
|
39
|
+
d = json.load(sys.stdin)
|
|
40
|
+
files = d.get("files", d)
|
|
41
|
+
actual = {os.path.basename(k): sorted({x["code"] for x in v["findings"]}) for k, v in files.items()}
|
|
42
|
+
want = {}
|
|
43
|
+
for spec in os.environ["LINT_EXPECT"].split():
|
|
44
|
+
name, _, codes = spec.partition("=")
|
|
45
|
+
want[name] = sorted(c for c in codes.split(",") if c)
|
|
46
|
+
problems = []
|
|
47
|
+
for name in sorted(set(want) - set(actual)):
|
|
48
|
+
problems.append("期望表列了但报告里没有: " + name)
|
|
49
|
+
for name in sorted(set(actual) - set(want)):
|
|
50
|
+
problems.append("报告里出现但期望表未列(发现=%s): %s" % (actual[name] or [], name))
|
|
51
|
+
for name in sorted(set(want) & set(actual)):
|
|
52
|
+
if want[name] != actual[name]:
|
|
53
|
+
problems.append("%s 期望 %s 实得 %s" % (name, want[name] or [], actual[name] or []))
|
|
54
|
+
print("\n".join(problems))
|
|
55
|
+
')" || { bad "${linter} 结果比对失败"; return; }
|
|
56
|
+
if [ -n "${result}" ]; then
|
|
57
|
+
while IFS= read -r line; do
|
|
58
|
+
[ -n "${line}" ] && bad "${linter}: ${line}"
|
|
59
|
+
done <<<"${result}"
|
|
60
|
+
fi
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
note "== figure-lint 逐谓词差分 =="
|
|
64
|
+
run_expect figure-lint.py "${here}/tests/svg" svg \
|
|
65
|
+
'control.svg=' \
|
|
66
|
+
'no-title.svg=C4-TITLE' \
|
|
67
|
+
'no-legend.svg=C4-LEGEND' \
|
|
68
|
+
'low-contrast.svg=WCAG-143' \
|
|
69
|
+
'overflow.svg=SVG-OVERFLOW' \
|
|
70
|
+
'no-group.svg=GROUPING' \
|
|
71
|
+
'ungrouped-card.svg=GROUPING' \
|
|
72
|
+
'unlabeled-edge.svg=C4-EDGE-LABEL' \
|
|
73
|
+
'offcontract-shape.svg=CONTRACT-COLOR-TOKEN' \
|
|
74
|
+
'malformed.svg=PARSE' \
|
|
75
|
+
'bad-viewbox.svg=GEOMETRY' \
|
|
76
|
+
'no-viewbox.svg=C4-VIEWBOX' \
|
|
77
|
+
'edge-no-arrow.svg=C4-EDGE-DIRECTION' \
|
|
78
|
+
'edge-vague.svg=C4-EDGE-VAGUE' \
|
|
79
|
+
'transformed.svg=CONTRAST-UNSUPPORTED' \
|
|
80
|
+
'crossings.svg=GRAPH-CROSSINGS' \
|
|
81
|
+
'figure-is-a-list.svg=FIGURE-IS-A-LIST,GROUPING' \
|
|
82
|
+
'flow-mixed.svg=FLOW-DIRECTION-MIXED' \
|
|
83
|
+
'no-aria.svg=FIGURE-A11Y-STRUCTURE' \
|
|
84
|
+
'cvd-confusable.svg=' \
|
|
85
|
+
'decorative-line.svg=' \
|
|
86
|
+
'blackmarker.svg=CONTRACT-COLOR-TOKEN'
|
|
87
|
+
|
|
88
|
+
note "== doc-lint 逐谓词差分 =="
|
|
89
|
+
run_expect doc-lint.py "${here}/tests/doc" md \
|
|
90
|
+
'control.md=' \
|
|
91
|
+
'tables-only-clean.md=' \
|
|
92
|
+
'fake-header.md=WCAG-131-FAKE-HEADING' \
|
|
93
|
+
'no-unit.md=TABLE-NO-UNIT' \
|
|
94
|
+
'should-be-chart.md=ICD203-9-SHOULD-BE-CHART' \
|
|
95
|
+
'imbalance.md=CARRIER-IMBALANCE,ICD203-9-SHOULD-BE-CHART' \
|
|
96
|
+
'fig-dangling.md=FIG-REF-DANGLING' \
|
|
97
|
+
'fig-orphan.md=FIG-ORPHAN' \
|
|
98
|
+
'fig-orphan-captioned.md=FIG-ORPHAN' \
|
|
99
|
+
'empty-header.md=WCAG-131-TABLE' \
|
|
100
|
+
'wide-table.md=TABLE-WIDE' \
|
|
101
|
+
'unfilled.md=TABLE-UNFILLED' \
|
|
102
|
+
'fenced-noise.md='
|
|
103
|
+
|
|
104
|
+
# 无契约时应提示「重复出现的值该成为 token」——独立目录,避免污染契约测试
|
|
105
|
+
t3="$(mktemp -d)"
|
|
106
|
+
# 需要一张「多种颜色各重复多次」的图才触发;就地合成,不入仓
|
|
107
|
+
python3 - "${t3}/multi.svg" <<'SVGEOF'
|
|
108
|
+
import sys
|
|
109
|
+
cols = ["#0072B2", "#D55E00", "#009E73", "#CC79A7", "#E69F00"]
|
|
110
|
+
parts = ['<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 200" role="img" aria-label="many repeated colours"><title>many repeated colours</title><g>']
|
|
111
|
+
y = 5
|
|
112
|
+
for c in cols:
|
|
113
|
+
for _ in range(3):
|
|
114
|
+
parts.append(f'<rect x="10" y="{y}" width="12" height="8" fill="{c}"/>')
|
|
115
|
+
y += 10
|
|
116
|
+
parts.append("</g></svg>")
|
|
117
|
+
open(sys.argv[1], "w").write("".join(parts))
|
|
118
|
+
SVGEOF
|
|
119
|
+
out2="$(python3 "${here}/figure-lint.py" "${t3}"/*.svg 2>/dev/null)"; rc2=$?
|
|
120
|
+
case "${out2}" in *VALUE-SHOULD-BE-TOKEN*) ;; *) bad "无契约且多色重复时未报 VALUE-SHOULD-BE-TOKEN";; esac
|
|
121
|
+
[ "${rc2}" = 1 ] || bad "多色重复用例退出码应为 1(CONTRACT-MISSING 是 ERROR),实得 ${rc2}"
|
|
122
|
+
# 规则写的是「>2 次即应成 token」——单一颜色重复 3 次同样要报,否则规则与实现不一致
|
|
123
|
+
python3 - "${t3}/one.svg" <<'SVGEOF2'
|
|
124
|
+
import sys
|
|
125
|
+
parts = ['<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 200" role="img" aria-label="one repeated colour"><title>one repeated colour</title><g>']
|
|
126
|
+
y = 5
|
|
127
|
+
for _ in range(3):
|
|
128
|
+
parts.append(f'<rect x="10" y="{y}" width="12" height="8" fill="#0072B2"/>')
|
|
129
|
+
y += 10
|
|
130
|
+
parts.append("</g></svg>")
|
|
131
|
+
open(sys.argv[1], "w").write("".join(parts))
|
|
132
|
+
SVGEOF2
|
|
133
|
+
out2b="$(python3 "${here}/figure-lint.py" "${t3}/one.svg" 2>/dev/null)"; rc2b=$?
|
|
134
|
+
case "${out2b}" in *VALUE-SHOULD-BE-TOKEN*) ;; *) bad "单色重复 3 次未报 VALUE-SHOULD-BE-TOKEN(规则与实现不一致)";; esac
|
|
135
|
+
[ "${rc2b}" = 1 ] || bad "单色重复用例退出码应为 1(CONTRACT-MISSING 是 ERROR),实得 ${rc2b}"
|
|
136
|
+
rm -rf "${t3}"
|
|
137
|
+
|
|
138
|
+
# CVD 距离只在契约**显式声明语义色**时测量,且只报数不下判定
|
|
139
|
+
t3b="$(mktemp -d)"
|
|
140
|
+
cp "${here}/tests/svg/cvd-confusable.svg" "${t3b}/"
|
|
141
|
+
cat >"${t3b}/figure-contract.json" <<'JSON'
|
|
142
|
+
{ "canvas_ratios": [2.0], "font_scale": [14],
|
|
143
|
+
"color_tokens": {"ink":"#111827","warn":"#B54708","crit":"#B42318","ok":"#009E73","surface":"#FFFFFF"},
|
|
144
|
+
"semantic_colors": {"warning":"#B54708","critical":"#B42318","ok":"#009E73"} }
|
|
145
|
+
JSON
|
|
146
|
+
out3b="$(python3 "${here}/figure-lint.py" "${t3b}/cvd-confusable.svg" 2>/dev/null)"; rc3b=$?
|
|
147
|
+
case "${out3b}" in *CVD-DISTANCE*) ;; *) bad "契约声明语义色后未测量 CVD-DISTANCE";; esac
|
|
148
|
+
case "${out3b}" in *"测量值不是判定"*) ;; *) bad "CVD-DISTANCE 文案未声明这是测量值";; esac
|
|
149
|
+
# 「只报数不设阈」若让退出码变成非零,这句声明就被退出码当场推翻——必须是 INFO 档、退出码 0
|
|
150
|
+
[ "${rc3b}" = 0 ] || bad "只有 CVD-DISTANCE 测量时退出码应为 0(INFO 不阻断),实得 ${rc3b}"
|
|
151
|
+
case "${out3b}" in *INFO*) ;; *) bad "CVD-DISTANCE 未标为 INFO 档";; esac
|
|
152
|
+
# semantic_colors 非法必须报 CONTRACT-INVALID,不得崩溃、也不得静默丢成空集
|
|
153
|
+
for bad_sc in '["#B54708","#B42318"]' '"#B54708"' '{"a":"red","b":"#B42318"}' '{"a":"#123456","b":"#B42318"}'; do
|
|
154
|
+
cat >"${t3b}/figure-contract.json" <<JSON
|
|
155
|
+
{ "canvas_ratios": [2.0], "font_scale": [14],
|
|
156
|
+
"color_tokens": {"ink":"#111827","warn":"#B54708","crit":"#B42318","ok":"#009E73","surface":"#FFFFFF"},
|
|
157
|
+
"semantic_colors": ${bad_sc} }
|
|
158
|
+
JSON
|
|
159
|
+
o="$(python3 "${here}/figure-lint.py" "${t3b}/cvd-confusable.svg" 2>&1)"; r=$?
|
|
160
|
+
case "${o}" in *CONTRACT-INVALID*) ;; *) bad "非法 semantic_colors ${bad_sc} 未报 CONTRACT-INVALID";; esac
|
|
161
|
+
case "${o}" in *Traceback*) bad "非法 semantic_colors ${bad_sc} 让检查器崩溃";; esac
|
|
162
|
+
[ "${r}" = 1 ] || bad "非法 semantic_colors ${bad_sc} 应为 ERROR(退出码 1),实得 ${r}"
|
|
163
|
+
done
|
|
164
|
+
# 未声明语义色时不得凭渲染色臆断语义
|
|
165
|
+
out3c="$(python3 "${here}/figure-lint.py" "${here}/tests/svg/cvd-confusable.svg" 2>/dev/null || true)"
|
|
166
|
+
case "${out3c}" in *CVD-*) bad "契约未声明语义色却报了 CVD-*(把装饰色当语义色)";; esac
|
|
167
|
+
rm -rf "${t3b}"
|
|
168
|
+
|
|
169
|
+
# 主题维度:同一套写死的色在另一主题底上会失效;纯黑底另有提示。
|
|
170
|
+
t4="$(mktemp -d)"
|
|
171
|
+
cp "${here}/tests/svg/control.svg" "${t4}/"
|
|
172
|
+
cat >"${t4}/figure-contract.json" <<'JSON'
|
|
173
|
+
{ "canvas_ratios": [2.0], "font_scale": [13,14],
|
|
174
|
+
"color_tokens": {"ink":"#111827","primary":"#0072B2","critical":"#D55E00","ok":"#009E73","surface":"#FFFFFF"},
|
|
175
|
+
"themes": { "light": {"surface":"#FFFFFF"}, "dark": {"surface":"#000000"} } }
|
|
176
|
+
JSON
|
|
177
|
+
out4="$(python3 "${here}/figure-lint.py" "${t4}"/control.svg 2>/dev/null)"; rc4=$?
|
|
178
|
+
[ "${rc4}" = 2 ] || bad "主题用例退出码应为 2(仅 WARN),实得 ${rc4}"
|
|
179
|
+
case "${out4}" in *THEME-CONTRAST*) ;; *) bad "深色主题下未报 THEME-CONTRAST";; esac
|
|
180
|
+
case "${out4}" in *THEME-PURE-BLACK*) ;; *) bad "纯黑主题底未报 THEME-PURE-BLACK";; esac
|
|
181
|
+
# 主题底色不是合法 #RRGGBB 时必须报契约非法——否则该主题被静默跳过 = 假绿
|
|
182
|
+
cat >"${t4}/figure-contract.json" <<'JSON'
|
|
183
|
+
{ "canvas_ratios": [2.0], "font_scale": [13,14],
|
|
184
|
+
"color_tokens": {"ink":"#111827","primary":"#0072B2","critical":"#D55E00","ok":"#009E73","surface":"#FFFFFF"},
|
|
185
|
+
"themes": { "light": {"surface":"#FFFFFF"}, "dark": {"surface":"black"} } }
|
|
186
|
+
JSON
|
|
187
|
+
out4b="$(python3 "${here}/figure-lint.py" "${t4}"/control.svg 2>/dev/null)"; rc4b=$?
|
|
188
|
+
case "${out4b}" in *CONTRACT-INVALID*) ;; *) bad "themes.dark.surface 非十六进制未报 CONTRACT-INVALID(静默跳过=假绿)";; esac
|
|
189
|
+
[ "${rc4b}" = 1 ] || bad "契约非法应为 ERROR(退出码 1),实得 ${rc4b}"
|
|
190
|
+
cat >"${t4}/figure-contract.json" <<'JSON'
|
|
191
|
+
{ "canvas_ratios": [2.0], "font_scale": [13,14],
|
|
192
|
+
"color_tokens": {"ink":"#111827","primary":"#0072B2","critical":"#D55E00","ok":"#009E73","surface":"#FFFFFF"},
|
|
193
|
+
"themes": { "light": {"surface":"#FFFFFF"}, "dark": {"surface":[255]} } }
|
|
194
|
+
JSON
|
|
195
|
+
out4c="$(python3 "${here}/figure-lint.py" "${t4}"/control.svg 2>/dev/null || true)"
|
|
196
|
+
case "${out4c}" in *CONTRACT-INVALID*) ;; *) bad "themes.dark.surface 非字符串未报 CONTRACT-INVALID";; esac
|
|
197
|
+
|
|
198
|
+
# 底色解析不可信时,主题对比度不得凭全局底色判——必须显式报未判定
|
|
199
|
+
t4u="$(mktemp -d)"
|
|
200
|
+
cp "${here}/tests/svg/transformed.svg" "${t4u}/"
|
|
201
|
+
cat >"${t4u}/figure-contract.json" <<'JSON'
|
|
202
|
+
{ "canvas_ratios": [2.0], "font_scale": [13,14],
|
|
203
|
+
"color_tokens": {"ink":"#111827","primary":"#0072B2","surface":"#FFFFFF"},
|
|
204
|
+
"themes": { "light": {"surface":"#FFFFFF"}, "dark": {"surface":"#0B0F19"} } }
|
|
205
|
+
JSON
|
|
206
|
+
out4u="$(python3 "${here}/figure-lint.py" "${t4u}/transformed.svg" 2>/dev/null || true)"
|
|
207
|
+
case "${out4u}" in *THEME-UNASSESSED*) ;; *) bad "几何不可信时未报 THEME-UNASSESSED";; esac
|
|
208
|
+
case "${out4u}" in *THEME-CONTRAST*) bad "几何不可信时仍判 THEME-CONTRAST(拿不可信底色判出的假红)";; esac
|
|
209
|
+
rm -rf "${t4u}"
|
|
210
|
+
|
|
211
|
+
# fill="none" 的描边框不是底板:包住低对比文字的边框不得豁免 THEME-CONTRAST
|
|
212
|
+
t4s="$(mktemp -d)"
|
|
213
|
+
cat >"${t4s}/stroked.svg" <<'SVG'
|
|
214
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 200" role="img" aria-label="stroked box"><title>stroked box</title><g>
|
|
215
|
+
<rect x="0" y="0" width="400" height="200" fill="#FFFFFF"/>
|
|
216
|
+
<rect x="20" y="20" width="200" height="60" fill="none" stroke="#0072B2"/>
|
|
217
|
+
<text x="30" y="55" font-size="14" fill="#111827">深色小字</text>
|
|
218
|
+
</g></svg>
|
|
219
|
+
SVG
|
|
220
|
+
cat >"${t4s}/figure-contract.json" <<'JSON'
|
|
221
|
+
{ "canvas_ratios": [2.0], "font_scale": [14],
|
|
222
|
+
"color_tokens": {"ink":"#111827","primary":"#0072B2","surface":"#FFFFFF"},
|
|
223
|
+
"themes": { "light": {"surface":"#FFFFFF"}, "dark": {"surface":"#0B0F19"} } }
|
|
224
|
+
JSON
|
|
225
|
+
out4s="$(python3 "${here}/figure-lint.py" "${t4s}/stroked.svg" 2>/dev/null || true)"
|
|
226
|
+
case "${out4s}" in *THEME-CONTRAST*) ;; *) bad "fill=none 的描边框被当成底板,豁免了 THEME-CONTRAST";; esac
|
|
227
|
+
rm -rf "${t4s}"
|
|
228
|
+
|
|
229
|
+
# 两端都没箭头的无向连接不得按 d 的书写顺序造出方向
|
|
230
|
+
t5="$(mktemp -d)"
|
|
231
|
+
cat >"${t5}/undirected.svg" <<'SVG'
|
|
232
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 200" role="img" aria-label="undirected"><title>undirected</title>
|
|
233
|
+
<defs><marker id="a" refX="10" refY="6"><path d="M1 1L11 6L1 11Z" fill="#0072B2"/></marker></defs><g>
|
|
234
|
+
<rect x="0" y="0" width="400" height="200" fill="#FFFFFF"/>
|
|
235
|
+
<path d="M10 20L200 20" fill="none" stroke="#0072B2"/>
|
|
236
|
+
<path d="M200 60L10 60" fill="none" stroke="#0072B2"/>
|
|
237
|
+
<path d="M10 100L200 100" fill="none" stroke="#0072B2"/>
|
|
238
|
+
<path d="M200 140L10 140" fill="none" stroke="#0072B2"/>
|
|
239
|
+
</g></svg>
|
|
240
|
+
SVG
|
|
241
|
+
out5="$(python3 "${here}/figure-lint.py" "${t5}/undirected.svg" 2>/dev/null || true)"
|
|
242
|
+
case "${out5}" in *FLOW-DIRECTION-MIXED*) bad "无箭头的连接被按 d 书写顺序判成混合流向";; esac
|
|
243
|
+
rm -rf "${t5}"
|
|
244
|
+
|
|
245
|
+
# 主题相符时不得误报
|
|
246
|
+
cat >"${t4}/figure-contract.json" <<'JSON'
|
|
247
|
+
{ "canvas_ratios": [2.0], "font_scale": [13,14],
|
|
248
|
+
"color_tokens": {"ink":"#111827","primary":"#0072B2","critical":"#D55E00","ok":"#009E73","surface":"#FFFFFF"},
|
|
249
|
+
"themes": { "light": {"surface":"#FFFFFF"} } }
|
|
250
|
+
JSON
|
|
251
|
+
out5="$(python3 "${here}/figure-lint.py" "${t4}"/control.svg 2>/dev/null || true)"
|
|
252
|
+
case "${out5}" in *THEME-CONTRAST*) bad "单一浅色主题下误报 THEME-CONTRAST";; esac
|
|
253
|
+
rm -rf "${t4}"
|
|
254
|
+
|
|
255
|
+
# ---- 契约符合性:缺失 / 相符 / 不符 / 损坏 / 多目录 ----
|
|
256
|
+
note "== figure-lint 契约符合性 =="
|
|
257
|
+
tmp="$(mktemp -d)"; t2="$(mktemp -d)"
|
|
258
|
+
trap 'rm -rf "${tmp}" "${t2}"' EXIT
|
|
259
|
+
cp "${here}/tests/svg/control.svg" "${tmp}/"
|
|
260
|
+
|
|
261
|
+
out="$(python3 "${here}/figure-lint.py" "${tmp}" 2>/dev/null || true)"
|
|
262
|
+
case "${out}" in *CONTRACT-MISSING*) ;; *) bad "契约缺失时未报 CONTRACT-MISSING";; esac
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# 相符:取 control.svg 的真实取值,必须干净(写负例前先核对 fixture 真值,
|
|
266
|
+
# 否则会写出一份"其实相符"的契约让断言永远绿——本轮实际踩过)。
|
|
267
|
+
cat >"${tmp}/figure-contract.json" <<'JSON'
|
|
268
|
+
{ "canvas_ratios": [2.0], "font_scale": [13, 14],
|
|
269
|
+
"color_tokens": { "ink": "#111827", "primary": "#0072B2", "critical": "#D55E00",
|
|
270
|
+
"ok": "#009E73", "surface": "#FFFFFF" } }
|
|
271
|
+
JSON
|
|
272
|
+
out="$(python3 "${here}/figure-lint.py" "${tmp}" 2>/dev/null || true)"
|
|
273
|
+
case "${out}" in *CONTRACT-MISSING*) bad "契约存在时仍报 CONTRACT-MISSING";; esac
|
|
274
|
+
case "${out}" in
|
|
275
|
+
*CONTRACT-RATIO*|*CONTRACT-FONT-SCALE*|*CONTRACT-COLOR-TOKEN*)
|
|
276
|
+
bad "契约与 fixture 相符却报了偏离(假阳性)";;
|
|
277
|
+
esac
|
|
278
|
+
|
|
279
|
+
# 不符:三维各自断言。写成 OR(三选一命中即过)会让其余两维永久失去覆盖——
|
|
280
|
+
# 突变实测过:删掉 CONTRACT-RATIO 后套件仍绿,因为 FONT-SCALE 顶上了。
|
|
281
|
+
cat >"${tmp}/figure-contract.json" <<'JSON'
|
|
282
|
+
{ "canvas_ratios": [1.778], "font_scale": [99], "color_tokens": { "ink": "#000000" } }
|
|
283
|
+
JSON
|
|
284
|
+
out="$(python3 "${here}/figure-lint.py" "${tmp}" 2>/dev/null || true)"
|
|
285
|
+
for want in CONTRACT-RATIO CONTRACT-FONT-SCALE CONTRACT-COLOR-TOKEN; do
|
|
286
|
+
case "${out}" in
|
|
287
|
+
*"${want}"*) ;;
|
|
288
|
+
*) bad "契约不符时未报 ${want}——该维度的契约检查形同虚设";;
|
|
289
|
+
esac
|
|
290
|
+
done
|
|
291
|
+
|
|
292
|
+
# 损坏:语法错与类型非法都必须报 CONTRACT-INVALID 且不得崩溃。
|
|
293
|
+
printf '{ "canvas_ratios": [1.778,, }' >"${tmp}/figure-contract.json"
|
|
294
|
+
out="$(python3 "${here}/figure-lint.py" "${tmp}" 2>/dev/null || true)"
|
|
295
|
+
case "${out}" in *CONTRACT-INVALID*) ;; *) bad "契约语法损坏时未报 CONTRACT-INVALID";; esac
|
|
296
|
+
|
|
297
|
+
cat >"${tmp}/figure-contract.json" <<'JSON'
|
|
298
|
+
{ "canvas_ratios": "not-a-list", "font_scale": [ {"x": 1} ], "color_tokens": 42 }
|
|
299
|
+
JSON
|
|
300
|
+
out="$(python3 "${here}/figure-lint.py" "${tmp}" 2>/dev/null || true)"
|
|
301
|
+
case "${out}" in *CONTRACT-INVALID*) ;; *) bad "契约类型非法时未报 CONTRACT-INVALID(或已崩溃)";; esac
|
|
302
|
+
|
|
303
|
+
# 多目录:两棵树各有自己的契约,必须各按各的最近契约判,不能用第一棵的套第二棵。
|
|
304
|
+
mkdir -p "${t2}/a" "${t2}/b"
|
|
305
|
+
cp "${here}/tests/svg/control.svg" "${t2}/a/"
|
|
306
|
+
cp "${here}/tests/svg/control.svg" "${t2}/b/"
|
|
307
|
+
cat >"${t2}/a/figure-contract.json" <<'JSON'
|
|
308
|
+
{ "canvas_ratios": [2.0], "font_scale": [13, 14],
|
|
309
|
+
"color_tokens": { "ink": "#111827", "primary": "#0072B2", "critical": "#D55E00",
|
|
310
|
+
"ok": "#009E73", "surface": "#FFFFFF" } }
|
|
311
|
+
JSON
|
|
312
|
+
cat >"${t2}/b/figure-contract.json" <<'JSON'
|
|
313
|
+
{ "canvas_ratios": [1.5], "font_scale": [13, 14],
|
|
314
|
+
"color_tokens": { "ink": "#111827", "primary": "#0072B2", "critical": "#D55E00",
|
|
315
|
+
"ok": "#009E73", "surface": "#FFFFFF" } }
|
|
316
|
+
JSON
|
|
317
|
+
out="$(python3 "${here}/figure-lint.py" "${t2}/a/control.svg" "${t2}/b/control.svg" --json 2>/dev/null || true)"
|
|
318
|
+
printf '%s' "${out}" | python3 -c '
|
|
319
|
+
import json, sys, os
|
|
320
|
+
d = json.load(sys.stdin)
|
|
321
|
+
per = {os.path.basename(os.path.dirname(k)): sorted({x["code"] for x in v["findings"]})
|
|
322
|
+
for k, v in d["files"].items()}
|
|
323
|
+
sys.exit(0 if per.get("a") == [] and "CONTRACT-RATIO" in per.get("b", []) else 1)
|
|
324
|
+
' 2>/dev/null || bad "多目录时未按各自最近的契约判定(a 应干净、b 应报 CONTRACT-RATIO)"
|
|
325
|
+
|
|
326
|
+
# ---- 退出码契约回归 ----
|
|
327
|
+
# 两个 CLI 都声明「ERROR=1 / 仅 WARN=2 / 干净=0」,普通与 --json 模式一致。
|
|
328
|
+
# 此前套件全程忽略退出码(|| true),把 1 改成 0 也不会转红——契约无保护。
|
|
329
|
+
note "== 退出码契约(0 干净 / 2 仅 WARN / 1 有 ERROR)=="
|
|
330
|
+
assert_rc() { # <期望码> <说明> <命令...>
|
|
331
|
+
"${@:3}" >/dev/null 2>&1
|
|
332
|
+
local rc=$?
|
|
333
|
+
[ "${rc}" -eq "$1" ] || bad "退出码契约:$2 期望 $1 实得 ${rc}"
|
|
334
|
+
}
|
|
335
|
+
for mode in "" "--json"; do
|
|
336
|
+
label="${mode:-plain}"
|
|
337
|
+
assert_rc 0 "figure-lint 干净(${label})" python3 "${here}/figure-lint.py" "${here}/tests/svg/control.svg" ${mode}
|
|
338
|
+
assert_rc 1 "figure-lint 有 ERROR(${label})" python3 "${here}/figure-lint.py" "${here}/tests/svg/no-title.svg" ${mode}
|
|
339
|
+
assert_rc 2 "figure-lint 仅 WARN(${label})" python3 "${here}/figure-lint.py" "${here}/tests/svg/transformed.svg" ${mode}
|
|
340
|
+
assert_rc 0 "doc-lint 干净(${label})" python3 "${here}/doc-lint.py" "${here}/tests/doc/control.md" ${mode}
|
|
341
|
+
assert_rc 1 "doc-lint 有 ERROR(${label})" python3 "${here}/doc-lint.py" "${here}/tests/doc/fig-dangling.md" ${mode}
|
|
342
|
+
assert_rc 2 "doc-lint 仅 WARN(${label})" python3 "${here}/doc-lint.py" "${here}/tests/doc/no-unit.md" ${mode}
|
|
343
|
+
done
|
|
344
|
+
|
|
345
|
+
# ---- 读取失败不得中断整批 ----
|
|
346
|
+
# 非 UTF-8 fixture **不入仓**:提交进去会打断全仓扫描器(R0 审计实测 sed 报
|
|
347
|
+
# illegal byte sequence)。临时生成即可。
|
|
348
|
+
note "== 读取失败隔离 =="
|
|
349
|
+
python3 -c "open('${tmp}/non-utf8.svg','wb').write(b'<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 10 10\"><title>\xff\xfe</title></svg>')"
|
|
350
|
+
cp "${here}/tests/svg/control.svg" "${tmp}/ok.svg"
|
|
351
|
+
cp "${here}/tests/svg/figure-contract.json" "${tmp}/"
|
|
352
|
+
mixed="$(python3 "${here}/figure-lint.py" "${tmp}/non-utf8.svg" "${tmp}/ok.svg" --json 2>/dev/null)"
|
|
353
|
+
printf '%s' "${mixed}" | python3 -c '
|
|
354
|
+
import json, sys, os
|
|
355
|
+
d = json.load(sys.stdin)
|
|
356
|
+
per = {os.path.basename(k): sorted({x["code"] for x in v["findings"]}) for k, v in d["files"].items()}
|
|
357
|
+
ok = per.get("non-utf8.svg") == ["READ"] and per.get("ok.svg") == []
|
|
358
|
+
sys.exit(0 if ok else 1)
|
|
359
|
+
' 2>/dev/null || bad "坏文件未被隔离:期望 non-utf8.svg=[READ] 且 ok.svg 干净,实得 ${mixed}"
|
|
360
|
+
|
|
361
|
+
# doc-lint 同样:坏 .md 报 READ 并继续,不得静默判干净。
|
|
362
|
+
python3 -c "open('${tmp}/bad.md','wb').write(b'# t\n\xff\xfe\n')"
|
|
363
|
+
cp "${here}/tests/doc/control.md" "${tmp}/good.md"
|
|
364
|
+
dmixed="$(python3 "${here}/doc-lint.py" "${tmp}/bad.md" "${tmp}/good.md" --json 2>/dev/null)"
|
|
365
|
+
printf '%s' "${dmixed}" | python3 -c '
|
|
366
|
+
import json, sys, os
|
|
367
|
+
d = json.load(sys.stdin)
|
|
368
|
+
per = {os.path.basename(k): sorted({x["code"] for x in v["findings"]}) for k, v in d.get("files", d).items()}
|
|
369
|
+
sys.exit(0 if per.get("bad.md") == ["READ"] and per.get("good.md") == [] else 1)
|
|
370
|
+
' 2>/dev/null || bad "doc-lint 坏文件未被隔离:期望 bad.md=[READ] 且 good.md 干净,实得 ${dmixed}"
|
|
371
|
+
|
|
372
|
+
if [ "${fail}" -eq 0 ]; then
|
|
373
|
+
note "OK: figure-lint / doc-lint 逐谓词差分与契约五态全部符合预期"
|
|
374
|
+
fi
|
|
375
|
+
exit "${fail}"
|
package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/fig.png
ADDED
|
Binary file
|
package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/scripts/tests/doc/imbalance.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 载体错配
|
|
2
|
+
|
|
3
|
+
八张表、零图,且其中一张是量级对比表——那张该出图。
|
|
4
|
+
|
|
5
|
+
| 组 | 人数 |
|
|
6
|
+
|---|---|
|
|
7
|
+
| G1 | 7 |
|
|
8
|
+
| G2 | 14 |
|
|
9
|
+
| G3 | 21 |
|
|
10
|
+
| G4 | 28 |
|
|
11
|
+
| G5 | 35 |
|
|
12
|
+
| G6 | 42 |
|
|
13
|
+
| G7 | 49 |
|
|
14
|
+
|
|
15
|
+
| 键 | 含义 |
|
|
16
|
+
|---|---|
|
|
17
|
+
| k0 | 值0 |
|
|
18
|
+
|
|
19
|
+
| 键 | 含义 |
|
|
20
|
+
|---|---|
|
|
21
|
+
| k1 | 值1 |
|
|
22
|
+
|
|
23
|
+
| 键 | 含义 |
|
|
24
|
+
|---|---|
|
|
25
|
+
| k2 | 值2 |
|
|
26
|
+
|
|
27
|
+
| 键 | 含义 |
|
|
28
|
+
|---|---|
|
|
29
|
+
| k3 | 值3 |
|
|
30
|
+
|
|
31
|
+
| 键 | 含义 |
|
|
32
|
+
|---|---|
|
|
33
|
+
| k4 | 值4 |
|
|
34
|
+
|
|
35
|
+
| 键 | 含义 |
|
|
36
|
+
|---|---|
|
|
37
|
+
| k5 | 值5 |
|
|
38
|
+
|
|
39
|
+
| 键 | 含义 |
|
|
40
|
+
|---|---|
|
|
41
|
+
| k6 | 值6 |
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# 查询表文档
|
|
2
|
+
|
|
3
|
+
八张纯查询表,不含量级对比,不该被要求出图。
|
|
4
|
+
|
|
5
|
+
| 键 | 含义 |
|
|
6
|
+
|---|---|
|
|
7
|
+
| a | 甲 |
|
|
8
|
+
|
|
9
|
+
| 键 | 含义 |
|
|
10
|
+
|---|---|
|
|
11
|
+
| b | 乙 |
|
|
12
|
+
|
|
13
|
+
| 键 | 含义 |
|
|
14
|
+
|---|---|
|
|
15
|
+
| c | 丙 |
|
|
16
|
+
|
|
17
|
+
| 键 | 含义 |
|
|
18
|
+
|---|---|
|
|
19
|
+
| d | 丁 |
|
|
20
|
+
|
|
21
|
+
| 键 | 含义 |
|
|
22
|
+
|---|---|
|
|
23
|
+
| e | 戊 |
|
|
24
|
+
|
|
25
|
+
| 键 | 含义 |
|
|
26
|
+
|---|---|
|
|
27
|
+
| f | 己 |
|
|
28
|
+
|
|
29
|
+
| 键 | 含义 |
|
|
30
|
+
|---|---|
|
|
31
|
+
| g | 庚 |
|
|
32
|
+
|
|
33
|
+
| 键 | 含义 |
|
|
34
|
+
|---|---|
|
|
35
|
+
| h | 辛 |
|