-
Notifications
You must be signed in to change notification settings - Fork 0
feat(dashboard): factory-floor 板字段完善——关卡/谓词状态+漂移报警扩面(W5-C4 .github#227,ADR-0073) #255
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -160,6 +160,65 @@ def active_repos(): | |
|
|
||
| # ---------- 卡扫描(REST,真相源=issue label) ---------- | ||
|
|
||
| # @w5c4-board-pure-begin —— 板字段纯函数区(governance/tests/test-board-fields.sh | ||
| # 按标记对提取本块离线单测——不复制实现,防"测试测影子";标记对缺失=测试红) | ||
|
|
||
| # PR body 卡元数据行(入口协议 v1:body 必带 Card: Cloudbird-Software/<repo>#<n>) | ||
| CARD_REF_RE = re.compile(r"Card:\s*Cloudbird-Software/([A-Za-z0-9_.\-]+)#(\d+)") | ||
|
|
||
|
|
||
| def pr_refs_from_prs(prs): | ||
| """open PR 列表(含 body)→ {(卡 repo, 卡号): head_sha}——卡↔PR 关联唯一判据。""" | ||
| out = {} | ||
| for pr in prs or []: | ||
| m = CARD_REF_RE.search(str(pr.get("body") or "")) | ||
| if not m: | ||
| continue | ||
| out[(m.group(1), int(m.group(2)))] = pr.get("head", {}).get("sha") | ||
|
Comment on lines
+166
to
+177
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 验证 Line 174 接受正文中的任意子串。说明文字中的 将正则锚定为独立元数据行。检测重复 建议修改-CARD_REF_RE = re.compile(r"Card:\s*Cloudbird-Software/([A-Za-z0-9_.\-]+)#(\d+)")
+CARD_REF_RE = re.compile(
+ r"(?m)^\s*Card:\s*Cloudbird-Software/([A-Za-z0-9_.\-]+)#(\d+)\s*$"
+)🧰 Tools🪛 Ruff (0.16.1)[warning] 166-166: Comment contains ambiguous (RUF003) [warning] 166-166: Comment contains ambiguous (RUF003) [warning] 166-166: Comment contains ambiguous (RUF003) [warning] 171-171: Docstring contains ambiguous (RUF002) [warning] 171-171: Docstring contains ambiguous (RUF002) 🤖 Prompt for AI Agents |
||
| return out | ||
|
|
||
|
|
||
| # check-runs 结论 → 关卡状态文案(fail-closed 方向:任一失败即红,未完成不算绿) | ||
| _GATE_RED = ("failure", "timed_out", "startup_failure", "cancelled") | ||
|
|
||
|
|
||
| def gate_status_text(check_runs): | ||
| """head sha 的 check-runs → 绿/红/等待/无 CI(W5-C4 AC-3 字段:关卡状态)。 | ||
|
|
||
| 判序:红 > 等待 > 绿(一个失败即红,其余绿不遮红;completed 无 conclusion | ||
| 的畸形态归等待——不冒充绿);零 run=无 CI(区分"还没跑"与"跑挂了")。 | ||
| """ | ||
| runs = list(check_runs or []) | ||
| if not runs: | ||
| return "无 CI" | ||
| for r in runs: | ||
| if str(r.get("conclusion") or "").lower() in _GATE_RED: | ||
| return "红" | ||
| for r in runs: | ||
| if r.get("status") != "completed" or not r.get("conclusion"): | ||
| return "等待" | ||
| return "绿" | ||
|
Comment on lines
+197
to
+200
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 4. Non-success shown as green gate_status_text() 仅将少数 conclusion(failure/timed_out/startup_failure/cancelled)判为红,其余所有 completed 且有 conclusion 的 check-runs 都会落到“绿”,包括 action_required/neutral/skipped/stale 等非成功结论,违反注释所述 fail-closed 方向并可能误导关卡状态。结果是 CI 未真正成功时板上仍显示“绿”。 Agent Prompt
Comment on lines
+194
to
+200
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- relevant file outline ---'
ast-grep outline governance/board-sync.py --match 'def gate_status_text' --view expanded || true
printf '%s\n' '--- target ranges ---'
sed -n '150,215p' governance/board-sync.py
sed -n '260,315p' governance/board-sync.py
printf '%s\n' '--- related symbols ---'
rg -n -C 3 'gate_status_text|check-runs|check_runs|checkRuns|/check-runs|conclusion|per_page|page' governance/board-sync.py governance scripts .github 2>/dev/null || trueRepository: Cloudbird-Software/.github Length of output: 50382 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- api_get implementation and imports ---'
sed -n '1,95p' governance/board-sync.py
printf '%s\n' '--- focused call-site context ---'
sed -n '270,300p' governance/board-sync.py
printf '%s\n' '--- focused tests ---'
sed -n '45,70p' governance/tests/test-board-fields.sh
printf '%s\n' '--- read-only behavioral verifier ---'
python3 - <<'PY'
from pathlib import Path
import ast
path = Path("governance/board-sync.py")
source = path.read_text(encoding="utf-8")
tree = ast.parse(source, filename=str(path))
def find_function(name):
for node in tree.body:
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == name:
return node
raise SystemExit(f"missing function: {name}")
gate = find_function("gate_status_text")
enrich = find_function("enrich_cards")
gate_source = ast.get_source_segment(source, gate)
enrich_source = ast.get_source_segment(source, enrich)
print("gate_has_red_scan:", "_GATE_RED" in gate_source)
print("gate_has_completed_check:", 'r.get("status") != "completed"' in gate_source)
print("gate_returns_green_unconditionally_after_scans:", "return \"绿\"" in gate_source)
print("enrich_check_runs_call:", "/check-runs" in enrich_source)
print("enrich_check_runs_has_per_page:", "check-runs?per_page=" in enrich_source or "check-runs&per_page=" in enrich_source)
print("enrich_check_runs_has_page:", "check-runs?page=" in enrich_source or "check-runs&" in enrich_source and "page=" in enrich_source)
# Isolated equivalent of the function's deterministic branch behavior.
red = {"failure", "timed_out", "startup_failure", "cancelled"}
def observed(runs):
runs = list(runs or [])
if not runs:
return "无 CI"
for r in runs:
if str(r.get("conclusion") or "").lower() in red:
return "红"
for r in runs:
if r.get("status") != "completed" or not r.get("conclusion"):
return "等待"
return "绿"
cases = {
"all_success": [{"status": "completed", "conclusion": "success"}],
"unknown_completed": [{"status": "completed", "conclusion": "neutral"}],
"skipped_completed": [{"status": "completed", "conclusion": "skipped"}],
"mixed_success_unknown": [
{"status": "completed", "conclusion": "success"},
{"status": "completed", "conclusion": "action_required"},
],
}
for name, runs in cases.items():
print(name, "=>", observed(runs))
PYRepository: Cloudbird-Software/.github Length of output: 6827 按 仅当每个 run 的 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| # 漂移报警面=人工可在板上改的字段(宪法 §12:人工改动将被纠正+报警); | ||
| # 停留天数/卡号是每轮必变的派生刷新(日增/不变),进漂移面=每天误报淹没信号 | ||
| BOARD_DRIFT_FIELDS = ("State", "认领者", "AC 进度", "关卡状态", "谓词状态") | ||
|
|
||
|
|
||
| def board_drift_fields(have, want): | ||
| """板当前值 vs label/派生期望值 → 漂移字段名列表(空值与未设等价——GitHub | ||
| 空文本不落值,board-sync v1 同归一)。""" | ||
| drifted = [] | ||
| for k in BOARD_DRIFT_FIELDS: | ||
| hv, wv = have.get(k), want.get(k) | ||
| if hv is None and (wv == "" or wv is None): | ||
| continue | ||
| if hv != wv: | ||
| drifted.append(k) | ||
|
Comment on lines
+212
to
+217
|
||
| return drifted | ||
| # @w5c4-board-pure-end | ||
|
|
||
|
|
||
| def scan_cards(repos): | ||
| """全部 active 仓 open issue 且带 state:* 标签 → 卡列表(label 是唯一判据)。""" | ||
| cards = [] | ||
|
|
@@ -194,6 +253,52 @@ def scan_cards(repos): | |
| return cards | ||
|
|
||
|
|
||
| def load_predicate_pending(): | ||
| """谓词状态 pending 标注(policy/metrics.yaml board 节——W5-C2 信任门未落, | ||
| 字段占位≠造数)。policy 缺失→内置同值(板字段不因 policy 读取失败而缺席)。""" | ||
| try: | ||
| with open(os.path.join(DIR, "policy", "metrics.yaml"), encoding="utf-8") as f: | ||
| v = yaml.safe_load(f).get("board", {}).get("predicate_status_pending") | ||
| if v: | ||
| return str(v) | ||
| except Exception: | ||
| pass | ||
| return "pending(W5-C2)" | ||
|
Comment on lines
+264
to
+266
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win 记录谓词策略回退原因。 静态分析已在 Line 264 至 Line 265 报告 🧰 Tools🪛 Ruff (0.16.1)[error] 264-265: (S110) [warning] 264-264: Do not catch blind exception: (BLE001) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
|
|
||
| def enrich_cards(cards, repos): | ||
| """W5-C4 AC-3:卡补 关卡状态(卡 PR 的 head check-runs)与 谓词状态(pending)。 | ||
|
|
||
| 卡↔PR 关联=PR body 的 Card: 元数据行(入口协议 v1);无 PR 的卡=「无 PR」。 | ||
| check-runs 拉取失败→「未知」并 WARN(fail-closed 方向:未知≠绿)。 | ||
| """ | ||
| prs = [] | ||
| for repo in repos: | ||
| page = 1 | ||
| while True: | ||
| batch = api_get(f"/repos/{ORG}/{repo}/pulls?state=open&per_page=100&page={page}") | ||
| prs.extend(batch) | ||
| if len(batch) < 100: | ||
| break | ||
| page += 1 | ||
| refs = pr_refs_from_prs(prs) | ||
| predicate = load_predicate_pending() | ||
| for c in cards: | ||
| sha = refs.get((c["repo"], c["number"])) | ||
| if not sha: | ||
| c["gate_status"] = "无 PR" | ||
| else: | ||
| try: | ||
| cr = api_get(f"/repos/{ORG}/{c['repo']}/commits/{sha}/check-runs") | ||
| c["gate_status"] = gate_status_text(cr.get("check_runs") or []) | ||
| except Infra as e: | ||
| print(f"WARN gate-status {c['repo']}#{c['number']}: check-runs 拉取失败 {e}" | ||
| f"——关卡状态=未知(不冒充绿)") | ||
| c["gate_status"] = "未知" | ||
| c["predicate_status"] = predicate | ||
| return cards | ||
|
|
||
|
|
||
| # ---------- Project(v2) 幂等准备 ---------- | ||
|
|
||
| Q_ORG = """query($org:String!,$cur:String){ organization(login:$org){ | ||
|
|
@@ -229,6 +334,9 @@ def scan_cards(repos): | |
| # GitHub 保留名("Repo"/"Assignee" 会撞内建 Repository/Assignees → reserved 拒绝) | ||
| ("State", "SINGLE_SELECT"), ("仓", "TEXT"), ("认领者", "TEXT"), | ||
| ("卡号", "NUMBER"), ("停留天数", "NUMBER"), ("AC 进度", "TEXT"), | ||
| # W5-C4 AC-3(宪法 §12 字段清单):关卡状态=卡 PR head check-runs 投影; | ||
| # 谓词状态=硬谓词信任门(ADR-0071 W5-C2 进行中)——pending 标注不造数 | ||
| ("关卡状态", "TEXT"), ("谓词状态", "TEXT"), | ||
|
Comment on lines
+337
to
+339
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. Missing adr-#### in description This PR modifies files under governance/, but the PR description body (as provided) does not include an ADR-#### reference token. This violates the requirement to include an ADR reference in the PR description for governance/standards changes, reducing auditability of the rationale.
Comment on lines
+337
to
+339
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- changed files ---'
git diff --stat
printf '%s\n' '--- relevant governance references ---'
rg -n --hidden -S 'C1|ADR-NNNN|adr-required|owner-only|owner only|governance_change|ADR-0071' \
governance standards scripts .github CODEOWNERS profile docs 2>/dev/null | head -n 240
printf '%s\n' '--- target file context ---'
sed -n '320,350p' governance/board-sync.py
printf '%s\n' '--- repository metadata files ---'
git ls-files | rg '(^|/)(expected-state\.json|CODEOWNERS|CONTRIBUTING|README|.*ADR.*|.*governance.*)$' | head -n 160Repository: Cloudbird-Software/.github Length of output: 13936 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- C1 gate implementation ---'
sed -n '115,180p' .github/workflows/gate.yml
printf '%s\n' '--- owner-only declarations ---'
cat CODEOWNERS
printf '%s\n' '--- governance workflow scope ---'
sed -n '188,208p' governance/GOVERNANCE.yaml
printf '%s\n' '--- repository state ---'
git status --short --branch
git remote -vRepository: Cloudbird-Software/.github Length of output: 5789 在 PR 描述中补充有效的 ADR-NNNN 引用,并完成 owner-only review。 🧰 Tools🪛 Ruff (0.16.1)[warning] 337-337: Comment contains ambiguous (RUF003) [warning] 337-337: Comment contains ambiguous (RUF003) [warning] 337-337: Comment contains ambiguous (RUF003) [warning] 337-337: Comment contains ambiguous (RUF003) [warning] 338-338: Comment contains ambiguous (RUF003) [warning] 338-338: Comment contains ambiguous (RUF003) 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| ] | ||
|
|
||
|
|
||
|
|
@@ -346,7 +454,7 @@ def main(): | |
| state_names = {s["name"] for s in states} | ||
| repos = active_repos() | ||
| stats["repos"] = len(repos) | ||
| cards = scan_cards(repos) | ||
| cards = enrich_cards(scan_cards(repos), repos) | ||
| stats["cards"] = len(cards) | ||
| pid, purl = ensure_project() | ||
| if pid is None: # dry-run 且项目尚不存在——计划已打印,无从对账 | ||
|
|
@@ -378,17 +486,31 @@ def main(): | |
| stats["added"] += 1 | ||
| want = {"仓": c["repo"], "认领者": c["assignee"] or "", | ||
| "卡号": c["number"], "停留天数": c["days_idle"], | ||
| "AC 进度": c["ac_progress"]} | ||
| "AC 进度": c["ac_progress"], | ||
| "关卡状态": c["gate_status"], "谓词状态": c["predicate_status"]} | ||
| have = entry["fields"] | ||
| # State 先比对(漂移报警面 = 宪法 §12 人工改动将被纠正;仅对板上 | ||
| # 既有条目报警——新增条目无旧值,不算人工改动) | ||
| if have.get("State") != c["state"]: | ||
| if preexisting: | ||
| # 漂移报警面(宪法 §12 人工改动将被纠正+报警;W5-C4 AC-3 扩到全部 | ||
| # 人工可改字段——State 之外认领者/AC 进度/关卡状态/谓词状态同报); | ||
| # 仅对板上既有条目报警——新增条目无旧值,不算人工改动。 | ||
| # State 只在可写选项内参与比对(未知态写不进单选——比对了也纠正不了) | ||
| if preexisting: | ||
| drift_want = {**want} | ||
| if c["state"] in opt_ids: | ||
| drift_want["State"] = c["state"] | ||
| drifted = board_drift_fields(have, drift_want) | ||
| if "State" in drifted: | ||
| print(f"WARN board-drift {c['repo']}#{c['number']}: " | ||
| f"board={have.get('State')} label={c['state']}" | ||
| f"State board={have.get('State')} label={c['state']}" | ||
| f"(人工改动将被纠正,宪法 §12)") | ||
| for f in drifted: | ||
| if f != "State": | ||
| print(f"WARN board-drift {c['repo']}#{c['number']}: " | ||
| f"{f} board={have.get(f)!r} 期望={want[f]!r}" | ||
| f"(label/派生真源将被写回,宪法 §12)") | ||
| if drifted: | ||
| stats["warned"] += 1 | ||
| stats["corrected"] += 1 | ||
| # State 写入(单选;未知态兜底在下方) | ||
|
Comment on lines
+496
to
+513
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 将 State 写入移出 Line 496 的作用域包含 Line 514 至 Line 519。新建 Project item 时, 仅将漂移告警限制在 建议修改- # State 写入(单选;未知态兜底在下方)
- if c["state"] in opt_ids:
- set_field(pid, entry["item_id"], fields["State"],
- "singleSelectOptionId", opt_ids[c["state"]])
- else:
- print(...)
+ # State 写入(单选;未知态兜底在下方)
+ if c["state"] in opt_ids:
+ set_field(pid, entry["item_id"], fields["State"],
+ "singleSelectOptionId", opt_ids[c["state"]])
+ else:
+ print(...)🧰 Tools🪛 Ruff (0.16.1)[warning] 504-504: String contains ambiguous (RUF001) [warning] 504-504: String contains ambiguous (RUF001) [warning] 504-504: String contains ambiguous (RUF001) [warning] 509-509: String contains ambiguous (RUF001) [warning] 509-509: String contains ambiguous (RUF001) [warning] 509-509: String contains ambiguous (RUF001) [warning] 513-513: Comment contains ambiguous (RUF003) [warning] 513-513: Comment contains ambiguous (RUF003) [warning] 513-513: Comment contains ambiguous (RUF003) 🤖 Prompt for AI Agents |
||
| if c["state"] in opt_ids: | ||
| set_field(pid, entry["item_id"], fields["State"], | ||
| "singleSelectOptionId", opt_ids[c["state"]]) | ||
|
Comment on lines
+513
to
516
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3. New items skip state write main() 中 State 写入逻辑被缩进进 if preexisting: 块,导致新创建的 Project item(preexisting=False)不会写入 State 单选字段,板条目将缺失 State/无法正确投影。该问题会让新增卡在 factory-floor 板上长期处于空 State,破坏核心同步语义。 Agent Prompt
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -825,4 +825,4 @@ def main(): | |
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) | ||
| raise SystemExit(main()) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -306,4 +306,4 @@ def main(argv=None): | |
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) | ||
| raise SystemExit(main()) | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,97 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # test-board-fields.sh —— factory-floor 板字段完善自测(W5-C4 AC-3,ADR-0073 决策 5) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 从 board-sync.py 按 @w5c4-board-pure 标记对提取纯函数区(不复制实现——防"测试 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 测影子";标记对缺失=fail-closed 红),fixture 断言: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 卡↔PR 关联(Card: 元数据行解析)· 关卡状态四值映射(绿/红/等待/无 CI—— | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # fail-closed 方向:失败先于未完成,未知≠绿)· 漂移报警面(label/派生真源 vs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 板值;停留天数等例行刷新字段不进漂移面) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 用法:bash governance/tests/test-board-fields.sh(零网络零真实 gh) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| set -uo pipefail | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HERE="$(cd "$(dirname "$0")" && pwd)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GOV="$(cd "$HERE/.." && pwd)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PASS=0; FAIL=0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pass() { PASS=$((PASS+1)); echo "PASS $1"; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fail() { FAIL=$((FAIL+1)); echo "FAIL $1"; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PY="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for c in "${PYTHON:-}" python3 python py -3; do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [[ -n "$c" ]] || continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "$c" -c 'import sys, yaml; print("ok")' >/dev/null 2>&1 || continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PY="$c"; break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+19
to
+23
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [[ -n "$PY" ]] || { echo "::error::无可用 python(含 pyyaml)"; exit 2; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SRC="$GOV/board-sync.py" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [[ -f "$SRC" ]] || { echo "FATAL: board-sync.py 不存在"; exit 2; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| awk '/@w5c4-board-pure-begin/{f=1} f{print} /@w5c4-board-pure-end/{exit}' "$SRC" >"$TMP/pure_body.py" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ! grep -q '^def gate_status_text(' "$TMP/pure_body.py"; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "FATAL: 标记对内未找到板字段纯函数(提取失效——实现与测试脱钩)"; exit 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win 验证 end 标记存在并且顺序正确。 Line 30 在缺少 建议修复+begin_count=$(grep -c '`@w5c4-board-pure-begin`' "$SRC")
+end_count=$(grep -c '`@w5c4-board-pure-end`' "$SRC")
+if [[ $begin_count -ne 1 || $end_count -ne 1 ]]; then
+ echo "FATAL: 纯函数标记对缺失或重复"; exit 2
+fi
+
awk '/@w5c4-board-pure-begin/{f=1} f{print} /@w5c4-board-pure-end/{exit}' "$SRC" >"$TMP/pure_body.py"
+grep -q '`@w5c4-board-pure-end`' "$TMP/pure_body.py" ||
+ { echo "FATAL: 纯函数 end 标记顺序错误"; exit 2; }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { echo 'import re'; cat "$TMP/pure_body.py"; } >"$TMP/pure.py" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cat >>"$TMP/pure.py" <<'PYEOF' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # ==== 驱动断言 ==== | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| results = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def check(name, cond): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| results.append((name, bool(cond))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1) 卡↔PR 关联:Card: 元数据行解析(入口协议 v1);无元数据/无 body 不关联 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prs = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {"body": "动机…\n\nCard: Cloudbird-Software/.github#227\n", "head": {"sha": "aaa"}}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {"body": "Card: Cloudbird-Software/mutual#42", "head": {"sha": "bbb"}}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {"body": "无元数据行的 PR", "head": {"sha": "ccc"}}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {"body": None, "head": {"sha": "ddd"}}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| refs = pr_refs_from_prs(prs) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("卡↔PR 关联 2 条", refs == {(".github", 227): "aaa", ("mutual", 42): "bbb"}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("空输入零关联", pr_refs_from_prs([]) == {}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 2) 关卡状态映射(check-runs→绿/红/等待/无 CI;失败优先于未完成——未知≠绿) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ok = [{"status": "completed", "conclusion": "success"}, {"status": "completed", "conclusion": "success"}] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mix_fail = [{"status": "completed", "conclusion": "success"}, {"status": "completed", "conclusion": "failure"}] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mix_wait = [{"status": "completed", "conclusion": "success"}, {"status": "in_progress"}] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timed = [{"status": "completed", "conclusion": "timed_out"}] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| incomplete = [{"status": "completed"}] # completed 但无 conclusion(畸形态)——不算绿 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("全 success→绿", gate_status_text(ok) == "绿") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("任一 failure→红(其余绿不遮红)", gate_status_text(mix_fail) == "红") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("timed_out→红(fail-closed 方向)", gate_status_text(timed) == "红") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("in_progress→等待", gate_status_text(mix_wait) == "等待") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("completed 无 conclusion→等待(不冒充绿)", gate_status_text(incomplete) == "等待") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("空 check-runs→无 CI", gate_status_text([]) == "无 CI") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("None→无 CI(拉取失败调用方显式「未知」)", gate_status_text(None) == "无 CI") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+54
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 覆盖未知的完成结论。 Line 59-64 只覆盖缺失 建议修复 incomplete = [{"status": "completed"}] # completed 但无 conclusion(畸形态)——不算绿
+unknown = [{"status": "completed", "conclusion": "unrecognized"}]
check("全 success→绿", gate_status_text(ok) == "绿")
...
check("completed 无 conclusion→等待(不冒充绿)", gate_status_text(incomplete) == "等待")
+check("未知 conclusion→等待(未知≠绿)", gate_status_text(unknown) == "等待")📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 3) 漂移报警面:人工可改字段进面;例行派生刷新(停留天数/卡号)不进面 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| want = {"State": "in-progress", "认领者": "randypanding", "AC 进度": "2/4", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "关卡状态": "红", "谓词状态": "pending(W5-C2)", "停留天数": 3, "卡号": 227} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("全一致→零漂移", board_drift_fields(want, want) == []) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("State 漂移检出", board_drift_fields({**want, "State": "done"}, want) == ["State"]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("关卡状态漂移检出", board_drift_fields({**want, "关卡状态": "绿"}, want) == ["关卡状态"]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("认领者漂移检出", "认领者" in board_drift_fields({**want, "认领者": "someone"}, want)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| multi = board_drift_fields({**want, "State": "spec", "谓词状态": "ok"}, want) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("多字段漂移全列(State+谓词)", multi == ["State", "谓词状态"]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("停留天数例行刷新不进漂移面", board_drift_fields({**want, "停留天数": 4}, want) == []) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("板空值 vs 期望空串等价(GitHub 空文本不落值)", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| board_drift_fields({"认领者": None}, {"认领者": ""}) == []) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check("板有值 vs 期望空串=漂移", board_drift_fields({"认领者": "x"}, {"认领者": ""}) == ["认领者"]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bad = [n for n, ok in results if not ok] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for n, ok in results: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(("PASS " if ok else "FAIL ") + n) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise SystemExit(1 if bad else 0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PYEOF | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if "$PY" "$TMP/pure.py" >"$TMP/run.txt" 2>"$TMP/err.txt"; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sed 's/^/ /' "$TMP/run.txt" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pass "板字段纯函数 fixture 全过($(grep -c '^PASS' "$TMP/run.txt") 项)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sed 's/^/ /' "$TMP/run.txt" "$TMP/err.txt" 2>/dev/null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fail "板字段纯函数断言失败(详见上行)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "== test-board-fields: pass=$PASS fail=$FAIL ==" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [[ $FAIL -eq 0 ]] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
5. Duplicate card refs overwrite
🐞 Bug☼ ReliabilityAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools