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:
1 parent
5d16c6df70
commit
52566c79f9
16 files changed
+2044
-92
No files matched your search
@@ -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))
|
||||
|
||||
Reference in new issue
Block a user