session-mechanism:两个看板整包搬进 boards/(页面与脚本同处一包)

用户令:「把这两个看板都纳入会话技能中,分两个看板文件夹,开个 boards 文件夹放两个看板」
「1 boards 2 A」(文件夹名 boards;本区部署与计划任务本轮一起改)

## 分层(新增第三层)
  scripts/  = 可执行程序(被计划任务/hook 调)
  assets/   = 静态素材(样式、模板)
  boards/   = 自带页面的看板程序(一个看板一个文件夹,页面+脚本同处一包)

## 动了什么
* boards/会话看板/(6 文件):board.py / board_ext.py / board-launch.py /
  board-launch.py.tpl / board.html / board-render-probe.js(从 scripts/ 与 assets/ 搬入)
* boards/项目管理界面/(3 文件):project-board.py / project-board.html /
  project-board-launch.py(从工作区 交付物/项目管理界面/ 复制入)

## 搬迁带出的真缺陷(已修,非补丁)
* board.py:「与 collabd.py 同目录」这个前提搬走后不成立 ⇒ 原来会**静默**落到
  本地抄的那份判据(自测「两侧共用同一实现」判 FAIL,但看板照跑、肉眼看不出来)。
  改成候选表(同目录 → 包内 scripts/ → DSH_SKILL_ROOT/scripts/),并在兜底分支**写 stderr 留痕**。
* board.py:CFG_P 同样按 HERE 找配置 ⇒ 与 collabd.py::load_cfg() 对齐查找顺序。
* selftest.py:一个 H 指两处的假设(board.py 与 collabd.py 同目录)拆成 H/B 两参数;
  board.html / board.py 全部改走 BOARD_DIR / board_py()。

## 顺带修一条既有 FAIL(与搬迁无关)
* 判据锚点没跟文档改:supervise-persistence.md 措辞在 053a6a6 已改成
  「排期按点拉起主会话这条路不存在」,判据仍锚旧句 ⇒ 恒红。判据改成锚档内现存那句。

## 验收
* selftest:--filter 看板 PASS 110 / FAIL 0(报告型 1 条不计入);
  全量 PASS 91 / FAIL 1(同一条报告型,实质 FAIL 0)。
* 会话看板 board.py --out ⇒ 50515 字节快照,无「回落本地判据」告警(证明真从 collabd.py 取)。
* 项目管理界面 --once ⇒ 4 个项目全列出;HTTP / 200 · 54102 字节(=技能包真身那份)·
  /api/projects 出数据 · /healthz ok。
This commit is contained in:
admin committed 2026-10-14 15:39:41 +08:00
1 parent 5d16c6df70
commit 52566c79f9
16 files changed
+2044 -92

No files matched your search

+106 -58
View File
@@ -58,6 +58,28 @@ import time
from pathlib import Path
HERE = Path(__file__).resolve().parent
PKG = HERE.parent # 技能包根
BOARD_DIR = PKG / "boards" / "会话看板" # 2026-10-14:看板整包搬这里
# 🔴🔴 2026-10-14:看板搬进 `boards/会话看板/` 后,**它自己 `import collabd`**(同族"判据共用同一实现"),
# 而 `collabd.py` 仍在 `scripts/` ⇒ 两个目录都必须在 `sys.path` 上,
# 否则 `spec_from_file_location("bd_par", board_py())` 一执行就 `ModuleNotFoundError`(本机实测踩到)。
for _p in (str(BOARD_DIR), str(HERE)):
if _p not in sys.path:
sys.path.insert(0, _p)
def board_py() -> Path:
"""会话看板主程序的位置。🔴 2026-10-14 起在 `boards/会话看板/`(用户令「开个 boards 文件夹放两个看板」)。
⚠️ 保留旧位置的兜底 —— 部署方尚未同步时(各区副本)照旧能跑,⛔ 不让自测因搬家而假红。
"""
for p in (BOARD_DIR / "board.py", HERE / "board.py", HERE / "scripts" / "board.py"):
if p.is_file():
return p
return BOARD_DIR / "board.py"
WS = Path(os.environ.get("DSH_COLLAB_WS") or os.environ.get("DSH_WS_ROOT") or Path.cwd())
TEST_WS = WS / "tmp" / "selftest"
TIN = TEST_WS / "inbox"
@@ -320,7 +342,7 @@ def t_role_drift():
(2026-09-30 起就是"改一处必须改两处")⇒ 这里**真的把两边拉出来逐样本对账**,⛔ 不靠人盯。"""
m = imp()
import importlib.util
spec = importlib.util.spec_from_file_location("bd_selftest", HERE / "board.py")
spec = importlib.util.spec_from_file_location("bd_selftest", board_py())
bd = importlib.util.module_from_spec(spec)
spec.loader.exec_module(bd)
samples = ["[协作]-[唤醒机制]-日志事前叫停棒", "[唤醒]-[唤醒机制]-脉冲", "[主]-[唤醒机制]-机制线",
@@ -336,7 +358,7 @@ def t_role_drift():
"下载 open design", ""]
bad = [t for t in samples if m.parse_session_name(t)["role"] != bd._role_of_title(t)]
sa = (HERE / "collabd.py").read_text(encoding="utf-8")
sb = (HERE / "board.py").read_text(encoding="utf-8")
sb = (board_py()).read_text(encoding="utf-8")
return [
("两边角色判据逐样本一致(%d 条;不一致:%s)" % (len(samples), bad or "无"), not bad),
("🔴🔴 2026-10-02 两类改造:`[唤醒]`/`[跟进]` 两边**都判空**(已退役)—— ⛔ 判成活角色 ⇒ "
@@ -736,7 +758,7 @@ def t_acc_is_pass_chinese():
# 真因=**拿错了"真身"**:判定词表住在 `board.py`,collabd 只是 import 它。
# ⇒ 判据必须**去 board.py 里读**(这才是"三处同款"要守的那个源)。
_rows_ok = []
_bp = HERE / "board.py"
_bp = board_py()
if not _bp.is_file():
_bp = HERE / "scripts" / "board.py"
try:
@@ -756,9 +778,9 @@ def t_acc_is_pass_chinese():
# ⚠️ 2026-10-05 修:`HERE` = **本脚本所在目录(scripts/)**,
# 而 `board.html` 住在**技能根**下的 `assets/`(= `HERE.parent/assets`)
# ⇒ 原写 `HERE/"assets"` 必 `FileNotFoundError`(本轮实测就是这么红的)。
_html = HERE.parent / "assets" / "board.html"
_html = BOARD_DIR / "board.html"
if not _html.is_file():
_html = HERE / "assets" / "board.html"
_html = BOARD_DIR / "board.html"
try:
_hs = _html.read_text(encoding="utf-8")
_mw = re.search(r"var\s+ACC_PASS_WORDS\s*=\s*(\[[^\]]*\])", _hs)
@@ -2098,7 +2120,7 @@ def t_main_resolution():
@case("主会话:看板与投递**同一套判据**(⛔ 防两处漂移 —— 漂了就没人能发现换主会话)")
def t_main_same_rule():
a = (HERE / "collabd.py").read_text(encoding="utf-8")
b = (HERE / "board.py").read_text(encoding="utf-8")
b = (board_py()).read_text(encoding="utf-8")
pat = r'def _main_sid\(st: dict\) -> str:'
return [
("两处都定义了 _main_sid", re.search(pat, a) is not None and re.search(pat, b) is not None),
@@ -2290,9 +2312,15 @@ sys.dont_write_bytecode = True # ⛔ 别让测试自己往技能目录里落 _
def load(name, path):
spec = importlib.util.spec_from_file_location(name, path)
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m); return m
H = sys.argv[1]
# 🔴 2026-10-14:看板整包搬进 `boards/会话看板/` ⇒ **两个文件不再同目录**
# (机制脚本在 `scripts/`、看板在 `boards/<看板名>/`)⇒ ⛔ 别再用一个 `H` 指两处。
H = sys.argv[1] # scripts/(collabd.py 在这)
B = sys.argv[2] # boards/<看板名>/(board.py 在这)
cb = load("cb", H + "/collabd.py")
bd = load("bd", H + "/board.py")
# ⚠️ board.py 自己会去找 collabd.py;把它所在目录入 path,让同名相对导入也走得通。
if B not in sys.path:
sys.path.insert(0, B)
bd = load("bd", B + "/board.py")
cases = [
("main1", "任意标题(主会话)", "main1", "手机接入", True),
("aaa", "[协作]-[手机接入]-N10 复测", "main1", "手机接入", True),
@@ -2343,12 +2371,13 @@ for _nm, _s in (("collabd", _cbs), ("board", _bds)):
sig = str(inspect.signature(bd.in_project))
if "cwd" in sig:
bad.append("board.in_project 仍带 cwd 参数:%s" % sig)
src = open(H + "/board.py", encoding="utf-8").read()
src = open(B + "/board.py", encoding="utf-8").read()
if "project_scopes" in src:
bad.append("board.py 仍残留 cwd 版的 project_scopes()")
print(json.dumps({"bad": bad, "sig": sig}, ensure_ascii=False))
'''
p = subprocess.run([PY, "-c", code, str(HERE)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
p = subprocess.run([PY, "-c", code, str(HERE), str(BOARD_DIR)], stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True, env=_mk_env(), timeout=30, errors="replace")
try:
r = json.loads((p.stdout or "").strip().splitlines()[-1])
@@ -2367,7 +2396,7 @@ def t_board_styles():
② **组件里不许出现硬编码颜色** —— 颜色只许出现在令牌块(`--color-*` 等)里。
⚠️ 一旦有人在 `.card{}`/SVG 里写死 `#fff`,换风格就会**花**(这是最容易犯的)。
"""
p = HERE.parent / "assets" / "board.html"
p = BOARD_DIR / "board.html"
src = p.read_text(encoding="utf-8")
need = ['[data-style="archify-dark"]', '[data-style="archify-light"]',
':root:not([data-style]), :root[data-style="base"]', # base 跟随系统深色
@@ -2395,7 +2424,7 @@ def t_board_acc_summary():
os.environ["COLLABD_CONFIG"] = str(CFG)
os.environ["DSH_COLLAB_WS"] = str(TEST_WS)
import importlib.util
spec = importlib.util.spec_from_file_location("bd_selftest", HERE / "board.py")
spec = importlib.util.spec_from_file_location("bd_selftest", board_py())
b = importlib.util.module_from_spec(spec)
spec.loader.exec_module(b)
s_empty = b._acc_summary({})
@@ -2421,7 +2450,7 @@ def t_board_orphan_visible():
os.environ["COLLABD_CONFIG"] = str(CFG)
os.environ["DSH_COLLAB_WS"] = str(TEST_WS)
import importlib.util
spec = importlib.util.spec_from_file_location("bd_orphan", HERE / "board.py")
spec = importlib.util.spec_from_file_location("bd_orphan", board_py())
b = importlib.util.module_from_spec(spec)
spec.loader.exec_module(b)
@@ -2431,7 +2460,7 @@ def t_board_orphan_visible():
{"line": "old-ws", "kind": "legacy", "total": 2, "done": 2, "open": 0},
{"line": "old-ws2", "kind": "legacy", "total": 1, "done": 0, "open": 1}])
# ② 结构:看板真的会把它画出来 + 真的会点名(⛔ 只改数据不改渲染 = 没修)
H = (HERE.parent / "assets" / "board.html").read_text(encoding="utf-8")
H = (BOARD_DIR / "board.html").read_text(encoding="utf-8")
need = ["kind==='legacy'", "未归类", "不属任何当前任务类别",
"不在当前任务类别清单里", # 图外提示要点名
"旧值 · 不在当前任务类别清单"] # 台账表要标注
@@ -2462,11 +2491,11 @@ def t_board_unrecognized_visible():
os.environ["COLLABD_CONFIG"] = str(CFG)
os.environ["DSH_COLLAB_WS"] = str(TEST_WS)
import importlib.util
spec = importlib.util.spec_from_file_location("bd_unrec", HERE / "board.py")
spec = importlib.util.spec_from_file_location("bd_unrec", board_py())
b = importlib.util.module_from_spec(spec)
spec.loader.exec_module(b)
H = (HERE.parent / "assets" / "board.html").read_text(encoding="utf-8")
S = (HERE / "board.py").read_text(encoding="utf-8")
H = (BOARD_DIR / "board.html").read_text(encoding="utf-8")
S = (board_py()).read_text(encoding="utf-8")
need = [ # 看板真会画(⛔ 只算不画 = 没修)
"sessions_unrecognized", "未归入本项目", "会被漏管",
"值取 `goal.json` 的 `topics`(任务类别),⛔ 不是 `short` 简称",
@@ -2503,7 +2532,7 @@ def t_in_project_parity():
os.environ["COLLABD_CONFIG"] = str(CFG)
os.environ["DSH_COLLAB_WS"] = str(TEST_WS)
import importlib.util
spec = importlib.util.spec_from_file_location("bd_inproj", HERE / "board.py")
spec = importlib.util.spec_from_file_location("bd_inproj", board_py())
bd = importlib.util.module_from_spec(spec)
spec.loader.exec_module(bd)
m = imp()
@@ -2539,8 +2568,8 @@ def t_in_project_parity():
# 结果拿去跟 `a` 比的是**标题**不是**期望值** ⇒ 全表恒红。
# ⇒ 元教训:**对账类断言别用 `_` 当占位名**(一个函数里两个 `_` 必然屏蔽)。
_wrong = [(t, a, exp) for (t, exp), (_t, a, _b) in zip(cases, rows) if a != exp]
H = (HERE.parent / "assets" / "board.html").read_text(encoding="utf-8")
S = (HERE / "board.py").read_text(encoding="utf-8")
H = (BOARD_DIR / "board.html").read_text(encoding="utf-8")
S = (board_py()).read_text(encoding="utf-8")
return [
("两处 `in_project` 逐样本一致(%d 条;不一致:%s)" % (len(cases), _mismatch or "无"),
not _mismatch),
@@ -2572,7 +2601,7 @@ def t_session_retire():
os.environ["COLLABD_CONFIG"] = str(CFG)
os.environ["DSH_COLLAB_WS"] = str(TEST_WS)
import importlib.util
spec = importlib.util.spec_from_file_location("bd_retire", HERE / "board.py")
spec = importlib.util.spec_from_file_location("bd_retire", board_py())
b = importlib.util.module_from_spec(spec)
spec.loader.exec_module(b)
m = imp()
@@ -2650,7 +2679,7 @@ def t_board_topics_source():
os.environ["COLLABD_CONFIG"] = str(CFG)
os.environ["DSH_COLLAB_WS"] = str(TEST_WS)
import importlib.util
spec = importlib.util.spec_from_file_location("bd_tsrc", HERE / "board.py")
spec = importlib.util.spec_from_file_location("bd_tsrc", board_py())
b = importlib.util.module_from_spec(spec)
spec.loader.exec_module(b)
@@ -2659,7 +2688,7 @@ def t_board_topics_source():
s_fb = b._goal_topics_source({"short": "简"})
s_no = b._goal_topics_source({})
s_cand = b._goal_topics_source({"_topics候选": {"候选": ["丙"]}, "short": "简"})
H = (HERE.parent / "assets" / "board.html").read_text(encoding="utf-8")
H = (BOARD_DIR / "board.html").read_text(encoding="utf-8")
need = ["topics_source", "未声明", "调用时在对话里说明"]
miss = [n for n in need if n not in H]
return [("有 topics ⇒ declared(实得 %s)" % s_dec["kind"], s_dec["kind"] == "declared"),
@@ -2686,7 +2715,7 @@ def t_board_multi_goal():
os.environ["COLLABD_CONFIG"] = str(CFG)
os.environ["DSH_COLLAB_WS"] = str(TEST_WS)
import importlib.util
spec = importlib.util.spec_from_file_location("bd_mg", HERE / "board.py")
spec = importlib.util.spec_from_file_location("bd_mg", board_py())
b = importlib.util.module_from_spec(spec)
spec.loader.exec_module(b)
@@ -2733,7 +2762,7 @@ def t_board_multi_goal():
by = {str(g.get("key")): g for g in gs}
_ga = by.get("ga", {}) or {}
_gb = by.get("gb", {}) or {}
H = (HERE.parent / "assets" / "board.html").read_text(encoding="utf-8")
H = (BOARD_DIR / "board.html").read_text(encoding="utf-8")
old_top = {"goal", "project", "tasks", "sessions", "labor", "orphan", "queue",
"progress", "others_running", "front", "runtime", "notify", "warn", "meta"}
_miss = sorted(old_top - set(snap.keys()))
@@ -2850,7 +2879,7 @@ def t_board_multi_goal():
"COMSPEC": os.environ.get("COMSPEC", ""),
"COLLABD_CONFIG": str(_cf),
"COLLABD_NO_ENSURE": "1"} # ⛔ 测试环境⛔ 不许起常驻(见 `_mk_env` 的说明)
_sp8.run([_s8.executable, str(HERE / "board.py"), "--out", str(_in / "board.json")],
_sp8.run([_s8.executable, str(board_py()), "--out", str(_in / "board.json")],
capture_output=True, text=True, timeout=180, env=_env8,
encoding="utf-8", errors="replace")
_bj = _in / "board.json"
@@ -2882,7 +2911,7 @@ def t_board_multi_goal():
{"id": "t4", "short": "丙4", "title": "丙目标4", "lifecycle": "进行中",
"run": "active", "acceptance_state": {"A1": "过|x"}}, ensure_ascii=False),
encoding="utf-8")
_sp8.run([_s8.executable, str(HERE / "board.py"), "--out", str(_in / "board.json")],
_sp8.run([_s8.executable, str(board_py()), "--out", str(_in / "board.json")],
capture_output=True, text=True, timeout=180, env=_env8,
encoding="utf-8", errors="replace")
_d9 = _j8.loads((_in / "board.json").read_text(encoding="utf-8")) \
@@ -2905,7 +2934,7 @@ def t_board_multi_goal():
# 🔴 抓「干脆不排序」:夹具里文件出现次序**本就是时间倒序**(t0 居中、t2 最老在后加)
# ⇒ 排序与否**输出有差异** ⇒ 上面那条"升序"能抓到;此处再钉一条源码级保险
# (⛔ 这是源码级判据的正当用途:验"语句在不在")。
_bsrc = (HERE / "board.py").read_text(encoding="utf-8")
_bsrc = (board_py()).read_text(encoding="utf-8")
_R.append(("`blocks.sort(...)` 真在且按 `tab_created`:%s"
% ("tab_created" in _bsrc and "blocks.sort(" in _bsrc),
'blocks.sort(key=lambda b: (b.get("tab_created"' in _bsrc))
@@ -3182,8 +3211,14 @@ def t_no_schedule_as_supervisor():
"2. ⛔ **不许把排期当",
"3. ⛔ **不许在排期 prompt 里写",
"4. ⛔ **常驻真死时不许用排期兜底**"))),
("技能自己的文档**早写对了**「⛔ 排期代替不了载体」(判据据此钉住)",
"排期代替不了载体" in PP or "排期代替不了" in PP),
# 🔴🔴 2026-10-14 订正:该档同日改成**更硬的措辞** ——
# 「排期按点拉起主会话这条路不存在」(原来那句「排期代替不了载体」现只在 SKILL.md 里)。
# ⇒ 判据改成**锚档内现存的那句**(⛔ 别继续锚已搬走的旧措辞 ⇒ 恒红)。
# ⚠️ 这是本档第 N 次「文档改了、判据锚点没跟」⇒ 纪律:**锚点逐字抄原文,⛔ 不凭记忆**。
("技能自己的文档**早写对了**「⛔ 排期代替不了载体/排期拉起主会话这条路不存在」"
"(判据据此钉住;⛔ SKILL.md 与 references 任一在位即可)",
"排期代替不了载体" in PP or "排期代替不了" in PP
or "排期按点拉起主会话这条路不存在" in PP),
]
# 🔴 ② env_check 必须查工作区部署(⛔ 原来这一段完全不存在)
_ec = m.env_check(ws=str(HERE.parent.parent.parent), scope="workspace")
@@ -3300,7 +3335,7 @@ def t_board_layout():
① 每个 `.card/.hero` 的说明**只许放在 `.qtip` 里**(至少 4 个 `?`),⛔ 不许摊回版面;
② 被点名删除的三句**不得复活**(否则又变成"版面上一堆解释性小字")。
"""
src = (HERE.parent / "assets" / "board.html").read_text(encoding="utf-8")
src = (BOARD_DIR / "board.html").read_text(encoding="utf-8")
qh = src.count('class="qh"')
qtip = src.count('class="qtip"') + src.count('class="qtip" id=')
DELETED = ["一张看板只管", "只读旁路观测", "不按工作目录", "其他工作区另有在跑的会话",
@@ -3331,6 +3366,9 @@ def t_no_jargon():
③ 看板**回读不引用已废弃的项目键**(`front.shim` / `front.worker` / `device_ingress` / `overlay_node`)。
"""
SK = HERE.parent / "scripts"
# 🔴 2026-10-14:看板搬进 `boards/会话看板/` ⇒ 「哪个文件在哪」要分开给,⛔ 不再同目录。
# `SCRIPTS`=机制脚本目录(`collabd.py` 在这)|`BOARD_DIR`=看板目录(`board.py` 在这)。
SCRIPTS = SK
# ① 技能目录里不许有使用方的**结构性**产物(配置/启动器/编译产物)。
# ⚠️ 日志**不作硬失败**:迁移期可能还有"迁移前起的旧进程"在往老路径写
# (2026-09-30 实测 `wb-supervisor-watch.py --interval 10` 就是这种),
@@ -3342,8 +3380,10 @@ def t_no_jargon():
leak = [n for n in forbidden if (SK / n).exists()]
# ①b 源码级不变量:日志**不得**再挂在技能目录上(`LOG = HERE / …` 是旧写法)
logbad = []
for fn in ("collabd.py", "board.py", "guard.py"):
t = (SK / fn).read_text(encoding="utf-8")
for fn, _fp in (("collabd.py", SCRIPTS / "collabd.py"),
("board.py", BOARD_DIR / "board.py"),
("guard.py", SCRIPTS / "guard.py")):
t = _fp.read_text(encoding="utf-8")
for i, line in enumerate(t.splitlines(), 1):
s = line.strip()
if s.startswith("#"):
@@ -3366,9 +3406,11 @@ def t_no_jargon():
# ✅ 判「豁免有没有被滥用」:`_DS_ANCHORS_OTHER` 也豁免,但它是**对照表**(零计算),
# 改它只改体检输出 ⇒ 两条一起放行是安全的。
_anchor_exempt = re.compile(r"^_(DS_ANCHORS|DS_ANCHORS_OTHER)\s*=")
for fn in ("board.py", "collabd.py"):
# 🔴 2026-10-14:`board.py` 在 `boards/会话看板/`、`collabd.py` 在 `scripts/` ⇒ 逐个给全路径。
for fn, _fp in (("board.py", BOARD_DIR / "board.py"),
("collabd.py", SCRIPTS / "collabd.py")):
try:
src = (SK / fn).read_text(encoding="utf-8")
src = _fp.read_text(encoding="utf-8")
except Exception:
hits.append("%s:读不到" % fn)
continue
@@ -3381,7 +3423,7 @@ def t_no_jargon():
if proj_pat.search(line):
hits.append("%s:%d" % (fn, i))
# ③ 看板不许回读已废弃的项目键
html = (HERE.parent / "assets" / "board.html").read_text(encoding="utf-8")
html = (BOARD_DIR / "board.html").read_text(encoding="utf-8")
stale = [k for k in ("f.shim", "f.worker", "front.shim", "front.worker",
"f.device_ingress", "f.overlay_node") if k in html]
soft = [n for n in ("_collabd.log", "_guard-stdout.log", "tmp")
@@ -3594,7 +3636,7 @@ def t_all_sessions_idle_execution_judge():
✅ **统一后的唯一标准**(本用例守的就是这一条):
**「在执行」≡ `sessions.status=='working'`** —— 与看板那个格**同款**
(`assets/board.html:823` 逐字 `var mOn=!!(main&&main.status==='working');`
(`boards/会话看板/board.html:823` 逐字 `var mOn=!!(main&&main.status==='working');`
⇒ 亮 ≡ `status=='working'`)。
⛔ **本函数不再调 `_target_busy()`**(读宿主状态机日志那套是**第二套口径**)。
@@ -3695,7 +3737,7 @@ def t_check_agent_naming():
import importlib.util
import sqlite3
m = imp()
spec = importlib.util.spec_from_file_location("bd_chk", HERE / "board.py")
spec = importlib.util.spec_from_file_location("bd_chk", board_py())
b = importlib.util.module_from_spec(spec)
spec.loader.exec_module(b)
@@ -3753,7 +3795,7 @@ def t_check_agent_role_parity():
⇒ **对账当场抓到 2 条漂移**。⇒ 判据自己会指路,⛔ 不靠人盯。
"""
import importlib.util
_html = (HERE.parent / "assets" / "board.html").read_text(encoding="utf-8")
_html = (BOARD_DIR / "board.html").read_text(encoding="utf-8")
# 🔴🔴 2026-10-03 23:4x 改名后**不要在这里写死标签**(原来写死 `role==='任务会话'`)——
# 标签已改成「执行会话」⇒ 写死的那份**当场报红**(真判据,不是误报)。
# ✅ **改成从后端真值派生**:AST 抽 `board.py::_ROLE_LABEL['worker']` 那个字面量 ——
@@ -3764,7 +3806,7 @@ def t_check_agent_role_parity():
ROLE_FILTER = ("role==='%s' && String(x.title||'').indexOf('检查')<0"
% _ROLE_LABEL_WORKER)
m = imp()
spec = importlib.util.spec_from_file_location("bd_par", HERE / "board.py")
spec = importlib.util.spec_from_file_location("bd_par", board_py())
b = importlib.util.module_from_spec(spec)
spec.loader.exec_module(b)
@@ -4380,7 +4422,7 @@ def t_prog_online_judge():
import json as _j
import time as _tm
m = imp()
spec = _il.spec_from_file_location("bd_hb", HERE / "board.py")
spec = _il.spec_from_file_location("bd_hb", board_py())
b = _il.module_from_spec(spec)
spec.loader.exec_module(b)
@@ -4397,7 +4439,7 @@ def t_prog_online_judge():
dead = b._label_when_down(10.0, 999999, False)
unknown = b._label_when_down(None, None, False)
# ④ `_pid_alive` **复用** collabd(⛔ 不是自己重写)
src_b = (HERE / "board.py").read_text(encoding="utf-8")
src_b = (board_py()).read_text(encoding="utf-8")
reuses = ("_cb_shared._pid_alive" in src_b)
return [
("① 判据读**常驻心跳**(heartbeat_age_min 字段在位)", has_hb),
@@ -4505,8 +4547,8 @@ def t_tab_peer_workspace():
import re as _re
m = imp()
spec = _ins.getsource # 占位(下面直接读文本)
src_b = (HERE / "board.py").read_text(encoding="utf-8")
src_h = (HERE.parent / "assets" / "board.html").read_text(encoding="utf-8")
src_b = (board_py()).read_text(encoding="utf-8")
src_h = (BOARD_DIR / "board.html").read_text(encoding="utf-8")
# ① 函数在位
has_fn = "_peer_workspaces" in src_b
@@ -4548,7 +4590,7 @@ def t_tab_peer_workspace():
else:
os.environ["DSH_COLLAB_WS"] = str(ws_env)
_u = __import__("importlib.util", fromlist=["util"])
_sp = _u.spec_from_file_location(tag, HERE / "board.py")
_sp = _u.spec_from_file_location(tag, board_py())
_mm = _u.module_from_spec(_sp)
_sp.loader.exec_module(_mm)
return _mm
@@ -4725,7 +4767,7 @@ def t_peer_block_no_self_data():
🔴 **判据可证伪**:把 `_peer_tasks(_wr)` 换回本区的 `tasks` ⇒ ⑤ **必然报红**(两侧 tasks 变相同)。
"""
import tempfile
src = (HERE / "board.py").read_text(encoding="utf-8")
src = (board_py()).read_text(encoding="utf-8")
# ① peer 分支喂的是对方数据源
feeds_peer = ("_peer_tasks(_wr)" in src and "_peer_state(_wr)" in src
and "_peer_srows(srows, _wr)" in src)
@@ -4759,7 +4801,7 @@ def t_peer_block_no_self_data():
os.environ["COLLABD_CONFIG"] = str(cfg)
os.environ["DSH_COLLAB_WS"] = str(A)
_u = __import__("importlib.util", fromlist=["util"])
_sp = _u.spec_from_file_location("bd_peerblk", HERE / "board.py")
_sp = _u.spec_from_file_location("bd_peerblk", board_py())
bd = _u.module_from_spec(_sp)
_sp.loader.exec_module(bd)
try:
@@ -4819,7 +4861,7 @@ def t_view_takes_all_scoped_fields():
os.environ["COLLABD_CONFIG"] = str(_pcfg)
os.environ.pop("DSH_COLLAB_WS", None)
_u = __import__("importlib.util", fromlist=["util"])
_sp = _u.spec_from_file_location("bd_viewchk", HERE / "board.py")
_sp = _u.spec_from_file_location("bd_viewchk", board_py())
bd = _u.module_from_spec(_sp)
_sp.loader.exec_module(bd)
gs = (bd.build() or {}).get("goals") or []
@@ -4840,7 +4882,7 @@ def t_view_takes_all_scoped_fields():
!= json.dumps(b2.get(k), sort_keys=True, ensure_ascii=False)]
# 🔴 提取赋值清单前**先剥掉注释** —— ⛔ 否则"把某行注释掉"这种变异**照样匹配得到**
# (本轮实测踩到:注释掉 `v.sessions_checks=` 之后判据仍报绿 ⇒ 判据被注释骗了)。
_html = (HERE.parent / "assets" / "board.html").read_text(encoding="utf-8")
_html = (BOARD_DIR / "board.html").read_text(encoding="utf-8")
_html = _re2.sub(r"/\*.*?\*/", "", _html, flags=_re2.S)
_html = _re2.sub(r"//[^\n]*", "", _html)
_fn = _re2.search(r"function scopeView\(d,g\)\{.*?\n\}", _html, _re2.S)
@@ -4930,7 +4972,7 @@ def t_board_edges_land_on_boxes():
⇒ 那属于"把条件写复杂了",根治办法是**保持 `RX < hkX` 恒成立**(别靠加更多分支)。
"""
import re as _re3
src = (HERE.parent / "assets" / "board.html").read_text(encoding="utf-8")
src = (BOARD_DIR / "board.html").read_text(encoding="utf-8")
# 🔴🔴 2026-10-03 **又踩一次「朴素扫描被注释骗」**(同族坑,见 P0-36):
# ④ 那段注释里**逐字写着** `RXV=PX-40`/`RXH=PX+28` ⇒ 正则先命中注释
# ⇒ 抽出来是一整段散文 ⇒ `eval` 失败 ⇒ 判据整条失效。
@@ -5597,7 +5639,7 @@ def t_pkg_hygiene():
# 零引用文件:assets/ 下每个 .js,⛔ 排掉本身被引用的
dead = []
for p in list((HERE.parent / "assets").glob("*.js")):
for p in list((BOARD_DIR).glob("*.js")):
stem = p.stem
hit = 0
for q in HERE.parent.rglob("*"):
@@ -5704,7 +5746,7 @@ def t_warn_text_states_consequence():
凡"⚠/缺了/没读"类文案,**必须同时出现一个后果词**(会…/不会…/导致…)。
"""
import re as _re6
html = (HERE.parent / "assets" / "board.html").read_text(encoding="utf-8")
html = (BOARD_DIR / "board.html").read_text(encoding="utf-8")
# 剥掉注释 ⇒ 只查**真正渲染给用户看的文案**(注释里的原话留档不算)
code = _re6.sub(r"/\*.*?\*/", "", html, flags=_re6.S)
code = _re6.sub(r"(?m)^\s*//.*$", "", code)
@@ -5749,7 +5791,7 @@ def _worker_label_from_source() -> str:
"""
import ast as _ast
try:
t = _ast.parse((HERE / "board.py").read_text(encoding="utf-8"))
t = _ast.parse((board_py()).read_text(encoding="utf-8"))
except Exception:
return ""
for n in t.body:
@@ -6757,7 +6799,7 @@ def t_board_render_runs_clean():
· `p.*` —— 参数早已改名成 `d`,函数体里 6 处没跟着改(同族,一次改名的漏网)。
⇒ 都属于「**变量名与数据流向不符**」,⛔ 语法检查查不出(原版语法是过的)。
✅ 本判据的做法=**真跑**:用 `assets/board-render-probe.js` 把页面脚本放进
✅ 本判据的做法=**真跑**:用 `boards/会话看板/board-render-probe.js` 把页面脚本放进
`vm` 的**全局作用域**执行(⛔ 不能用 `new Function` —— 函数会关在局部,
取到 `undefined` ⇒ **假绿**),喂一份**合成快照**,真调 `renderProject()`,
看它抛不抛、`#proj` 真渲染了没。
@@ -6769,10 +6811,10 @@ def t_board_render_runs_clean():
import json as _json
import subprocess as _sp
import tempfile as _tmp_mod
probe = HERE.parent / "assets" / "board-render-probe.js"
html = HERE.parent / "assets" / "board.html"
probe = BOARD_DIR / "board-render-probe.js"
html = BOARD_DIR / "board.html"
res = []
res.append(("探针在位 `assets/board-render-probe.js`", probe.is_file()))
res.append(("探针在位 `boards/会话看板/board-render-probe.js`", probe.is_file()))
res.append(("探针路径**自定位**(⛔ 不写死作者机器)", "BOARD_HTML" in probe.read_text(encoding="utf-8")))
# 源码级:`acc` 必须**先声明后使用**(⛔ 不能只是"出现过")
@@ -6895,6 +6937,12 @@ def t_peer_main_resolved():
# ⚠️ 本用例要调的是 **`board.py`** 的函数(`_scan_ws_mains`/`_resolve_main`/`project_scope`),
# ⛔ **不是** `imp()` 装出来的 collabd 模块 —— 那两个模块都有同名函数,
# 拿错模块会 `AttributeError`(2026-10-05 本用例第一版就是这么红的,同族第 2 次)。
# 🔴 2026-10-14:看板搬进 `boards/会话看板/` ⇒ `import board` 前**必须**把该目录放进 `sys.path`
# (原来是靠"它在 scripts/ 里、而 scripts 本就在 path 上"蹭到的,搬完就不成立了)。
import sys as _sys
_bd = str(BOARD_DIR)
if _bd not in _sys.path:
_sys.path.insert(0, _bd)
import board as m
# ── 合成一条"对方工作区的主会话"(直接写测试库;⛔ 绝不碰生产库)
peer_root = str(TEST_WS / "peerws")
@@ -6937,7 +6985,7 @@ def t_peer_main_resolved():
# ⛔ **证明不了 `_goal_block()` 真把它传下去了** —— 实测变异 M3(删掉调用点的传参)
# **本条全绿**,而线上 peer 格照样「无主会话」。
# ⇒ 判据必须**静态咬住那条调用点**:`project_scope(...)` 在 peer 分支里必须带 `ws_root`。
_bs = (HERE / "board.py").read_text(encoding="utf-8")
_bs = (board_py()).read_text(encoding="utf-8")
res.append(("⑤ `_goal_block()` 的调用点**真把 `ws_root` 递给 `project_scope`**"
"(⛔ 只测函数体等于没测装配)",
"project_scope(g, ws_root)" in _bs))