fix(task-worktree): 联接列改三态点名——半联树不再被说成已联,adopt 没补齐不许报就绪
同一个谎有两处,根因是「联没联上」有两套判定:列表只取样 JUNCTION_SPECS 首项 (.venv),而 adopt 填两条。于是「有 .venv、缺 simulator/SDL2」的半联树被报成已联, adopt 遇主线缺 SDL2 走 skip、联接失败走 warn,回来照样打「现在可以跑」——宣布就绪。 改法是把判定收成一处: - 新增 junction_states() 三态(link / real 实体内容 / absent 真缺)与 junction_label(), plugin_trees() 改读三态;adopt_junctions() 也改读同一函数,消除两处逻辑分叉。 - 列表按缺的那一样分别说后果:缺 .venv 会**静默**落到机器级解释器(无 fontTools, 即 generated-fresh 假红的老根因);缺 simulator/SDL2 是模拟器当场报错(CMakeLists.txt 的 SDL2_DIR 是裸 set,没有环境变量逃生口)。实体目录单列齐(实体SDL2),不算共享。 - adopt 补完复算一次,仍缺就打「还不能算就绪」,不再打「现在可以跑」;锁归属与 锁命令照常打印。 - SKILL.md 同步四处措辞,并把「adopt 之前跑不了 Python」这条不准确的说法换成上面 两种真实失败方式(一条是静默错解释器、一条是响亮报错)。 验证:scratch 仓 16 条断言全 PASS(全联 / 只摘 SDL2 那条 → 缺 SDL2 且不得再出现已联 / 实体目录不被覆盖 / unadopt 只摘联接 / 主线缺 SDL2 时 skip + 不报就绪 / 已联的 .venv 不误报 / 两样都缺 → 缺 .venv+SDL2),同一半联态上新旧对照 = 缺 SDL2 vs 已联。 py_compile 通过;doc-links --all --sections-strict 与编号门禁绿;两文件无 BOM、无 U+FFFD、纯 LF、无行尾空格。改动只落在 .dsh/,不进体积门禁指纹面,也不需要 @build。
This commit is contained in:
@@ -447,20 +447,22 @@ ls "$WTROOT"/.state/locks/ 2>/dev/null # 若该 ID 名下还有 .lock,先按
|
||||
3. 联接与锁归属由本节命令接上(下面三步)。
|
||||
|
||||
**这棵树是不是插件的**:`git worktree list` 里路径含 `.dsh-worktrees/` 就是。`$TW list` 现在会在台账段
|
||||
下面另起一段列出它们,并标 `已联 / 未联`。**插件树不占 `MAX_CONCURRENT = 3` 名额**——回收不归本 skill,
|
||||
下面另起一段列出它们,联接列**点名缺哪一样**(`已联` / `缺 .venv` / `缺 SDL2` / `齐(实体SDL2)`)。**插件树不占
|
||||
`MAX_CONCURRENT = 3` 名额**——回收不归本 skill,
|
||||
硬计入只会让别人那棵关不掉的树把 `new` 挡死(`new` 会打印一行「另有 N 个插件工作树不计入上限」提醒并发代价)。
|
||||
|
||||
三步接上(`<name>` 可带 `worktree/` 前缀,也可多段如 `refactor/logging`):
|
||||
|
||||
@T3@bash
|
||||
"$PY" "$TW" plugin list # 一览:分支 / 脏文件数 / 联接已联·未联
|
||||
"$PY" "$TW" plugin list # 一览:分支 / 脏 / 缺哪个联接(缺项另起一行说后果)
|
||||
"$PY" "$TW" plugin adopt N21-dhara-isfree # 只补缺失的 .venv 与 simulator/SDL2 联接
|
||||
"$PY" "$TW" plugin unadopt N21-dhara-isfree # 只摘联接(os.rmdir),绝不动目标内容与树里文件
|
||||
@T3@
|
||||
|
||||
- **adopt 之前那棵树跑不了 Python**:`.venv` 不入库,新检出的树里没有它 → `.venv/Scripts/python.exe`
|
||||
找不到解释器,`@build`、`ci_local.py`、每个 `scripts/check_*.py` 全部起跑失败。这是插件树最常见的
|
||||
「莫名失败」,先 `plugin adopt` 再谈别的。
|
||||
- **两条联接坏了的样子完全不同**:缺 `.venv` → `ci_local.py` 的四级解释器回退**静默**落到机器级
|
||||
python(无 fontTools = `generated-fresh` 假红的老根因,AGENTS.md 记着);缺 `simulator/SDL2` →
|
||||
`build_sim.bat` 当场报错(`SDL2_DIR` 是裸 `set`,没有 env 逃生口)。所以列表只说「已联」不够——
|
||||
它按缺的那一样分别给后果,`adopt` 没补齐也只报「还不能算就绪」,不许说「现在可以跑」。
|
||||
- **adopt 只碰联接**:已联的跳过;`simulator/SDL2` 若已是实体目录只告警不动;`.dsh-worktrees/` 之外的路径
|
||||
一律 exit 2(实测 `plugin adopt ".."` 被拒)——这道闸防的是把主树或 `_wt` 目录当插件树遮蔽掉真实内容。
|
||||
- **锁归属现在认得出来**:`@build` 在插件树里自持自释时,持有者名从 cwd 反推成 `pw:<name>`(多段名压平成
|
||||
@@ -619,9 +621,10 @@ ls "$WTROOT"/.state/locks/ 2>/dev/null # 若该 ID 名下还有 .lock,先按
|
||||
`UNIQUE-UNMERGED` 归零(或全部已备份);保留树在报告里点名给出判据(`merge-base --is-ancestor` = MERGED)。
|
||||
- **E 第 6 步清悬挂台账**:该 json 已从 `_wt/.state/` 消失(`.state-bak` 在备份目录里)、`$TW list` 不再打出它、
|
||||
`$TW lock status` 两把锁仍空闲;全程未对该 ID 跑 `done` / `abort`。
|
||||
- **F 节桥接**:`plugin list` 的「联接」列在 `adopt` 后变 `已联`、`unadopt` 后回 `未联`;**adopt/unadopt 前后
|
||||
主树 `.venv` 与树内文件都还在**(`test -f .venv/Scripts/python.txt` 恒真);`plugin adopt ".."` 必须 exit 2;
|
||||
`plugin adopt` 打印的 `pw:<name>` 与 `keil_build.py` 从 cwd 反推的名字逐字相同;`done <插件树名>` 只 exit 2。
|
||||
- **F 节桥接**:`plugin list` 的「联接」列按实际三态点名(`已联` / `缺 .venv` / `缺 SDL2` /
|
||||
`齐(实体SDL2)`——旧实现只取样 `.venv`,半联树会说谎);**adopt/unadopt 前后主树 `.venv` 与树内文件都
|
||||
还在**;主线缺 SDL2 时 `adopt` 打 `[skip]` + 「还不能算就绪」而不打「现在可以跑」;`plugin adopt ".."`
|
||||
必须 exit 2;打印的 `pw:<name>` 与 `keil_build.py` 从 cwd 反推的名字逐字相同;`done <插件树名>` 只 exit 2。
|
||||
- D 节归因:测量前后 `git -C "$MAIN" status --porcelain` **逐行一致**(主树全程只读);
|
||||
`git worktree list` 里净室路径已消失;逐文件增量之和 **等于**观察到的涨幅(实测 +6+8+2 = +16),且每处增量
|
||||
都指到具体字面量类别(裸形 「字母 T 累进数字 4」)与属主;取数走 `import check_ids` 读 `ck.stats`,且门禁脚本自身 `git diff` 为空。
|
||||
|
||||
@@ -442,6 +442,35 @@ def plugin_dir_for(name: str) -> Path:
|
||||
return p
|
||||
|
||||
|
||||
def junction_states(wt: Path):
|
||||
"""每个共享联接的三态:link(目录联接)/ real(实体内容)/ absent(真缺)。
|
||||
|
||||
这是「联没联上」的**唯一**判定处——列表显示与 adopt 收尾都读它。早先列表只取样
|
||||
JUNCTION_SPECS 的第一项,于是「有 .venv、缺 simulator/SDL2」的半联树被报成已联,
|
||||
而 adopt 可以只成功一条(主线缺 SDL2 走 skip、联接失败走 warn)→ 说谎的正是这一列。
|
||||
"""
|
||||
st = {}
|
||||
for rel, _desc in JUNCTION_SPECS:
|
||||
link = wt / rel
|
||||
if is_reparse(link):
|
||||
st[rel] = "link"
|
||||
elif os.path.lexists(str(link)):
|
||||
st[rel] = "real"
|
||||
else:
|
||||
st[rel] = "absent"
|
||||
return st
|
||||
|
||||
|
||||
def junction_label(st) -> str:
|
||||
"""把三态压成一列短标签:缺什么都点名,实体内容也算「齐」但不算共享。"""
|
||||
tag = {rel: rel.rsplit("/", 1)[-1] for rel in st}
|
||||
absent = [tag[r] for r, s in st.items() if s == "absent"]
|
||||
real = [tag[r] for r, s in st.items() if s == "real"]
|
||||
if absent:
|
||||
return "缺 " + "+".join(absent)
|
||||
return "齐(实体" + "+".join(real) + ")" if real else "已联"
|
||||
|
||||
|
||||
def plugin_trees():
|
||||
""".dsh-worktrees/worktree/ 下的工作树(事实源 = git worktree list,不读插件 manifest)。
|
||||
|
||||
@@ -469,7 +498,7 @@ def plugin_trees():
|
||||
"path": str(p),
|
||||
"branch": ent.get("branch") or "(detached)",
|
||||
"dirty": dirty,
|
||||
"adopted": is_reparse(p / JUNCTION_SPECS[0][0]),
|
||||
"jn": junction_states(p),
|
||||
})
|
||||
return trees
|
||||
|
||||
@@ -482,25 +511,34 @@ def print_plugin_trees() -> int:
|
||||
print()
|
||||
print("DSH 插件工作树({} 下;**不在本 skill 台账** → 不占 {} 个名额、done/abort "
|
||||
"管不到,回收走 /worktree remove|prune):".format(PLUGIN_SUBDIR, MAX_CONCURRENT))
|
||||
print("{:<26} {:<34} {:>4} {:>6}".format("名称", "分支", "脏", "联接"))
|
||||
print("{:<26} {:<34} {:>4} {:>16}".format("名称", "分支", "脏", "联接"))
|
||||
for t in trees:
|
||||
print("{:<26} {:<34} {:>4} {:>6}".format(
|
||||
print("{:<26} {:<34} {:>4} {:>16}".format(
|
||||
t["name"], t["branch"], "-" if t["dirty"] < 0 else str(t["dirty"]),
|
||||
"已联" if t["adopted"] else "未联"))
|
||||
if any(not t["adopted"] for t in trees):
|
||||
print("未联的进去跑不了 Python:「plugin adopt <name>」只建共享联接,不碰树内容")
|
||||
junction_label(t["jn"])))
|
||||
def miss(rel):
|
||||
return any(t["jn"].get(rel) == "absent" for t in trees)
|
||||
if miss(".venv"):
|
||||
print(" 缺 .venv:进去跑 @build / ci_local.py 会**静默**落到机器级解释器(无 "
|
||||
"fontTools,generated-fresh 假红的老根因)")
|
||||
if miss("simulator/SDL2"):
|
||||
print(" 缺 simulator/SDL2:模拟器 build_sim.bat 直接报错——CMakeLists.txt 的 "
|
||||
"SDL2_DIR 是裸 set,没有环境变量逃生口")
|
||||
if miss(".venv") or miss("simulator/SDL2"):
|
||||
print(" 补法:「plugin adopt <name>」——只建缺失的联接,不碰树里任何文件")
|
||||
return len(trees)
|
||||
|
||||
|
||||
def adopt_junctions(wt: Path):
|
||||
"""只补**缺失**的共享联接(已联的跳过、实体目录绝不碰),返回 (已有, 本次新建)。"""
|
||||
already, made = [], []
|
||||
st = junction_states(wt)
|
||||
for rel, desc in JUNCTION_SPECS:
|
||||
link = wt / rel
|
||||
if is_reparse(link):
|
||||
if st[rel] == "link":
|
||||
already.append(rel)
|
||||
continue
|
||||
if os.path.lexists(str(link)):
|
||||
if st[rel] == "real":
|
||||
print("[warn] {} 已是实体目录/文件(不是联接)——不动它".format(rel))
|
||||
already.append(rel)
|
||||
continue
|
||||
@@ -537,10 +575,17 @@ def cmd_plugin(a) -> int:
|
||||
print("为插件树补共享联接:{}".format(wt))
|
||||
already, made = adopt_junctions(wt)
|
||||
owner = plugin_lock_owner(a.name)
|
||||
# 复算一次:adopt 里 skip/warn/失败都不算成功,「已有 N + 新建 M」两两相加可能对不上
|
||||
# 总数——就绪判定只能按**现在的实际状态**说,否则又是把没联上的说成联上了
|
||||
still = [r for r, s in junction_states(wt).items() if s == "absent"]
|
||||
print()
|
||||
print("[ok] 联接:已有 {} 个、本次新建 {} 个".format(len(already), len(made)))
|
||||
print("该目录现在可以跑 @build / ci_local.py / @test;build 锁由 keil_build.py "
|
||||
"自持自释,持有者名就是 {}".format(owner))
|
||||
if still:
|
||||
print("[warn] 仍缺 {}:主线里该路径不存在或联接失败——先补主线再复跑 adopt"
|
||||
"(这棵树还不能算就绪)".format("、".join(still)))
|
||||
else:
|
||||
print("该目录现在可以跑 @build / ci_local.py / @test")
|
||||
print("build 锁由 keil_build.py 自持自释,持有者名就是 {}".format(owner))
|
||||
print("上台架前:$TW lock acquire bench --task {} --note <说明>".format(owner))
|
||||
print("下台架后:$TW lock release bench --task {}".format(owner))
|
||||
print("回收仍归插件:/worktree remove {}(本 skill 不删插件树)".format(a.name))
|
||||
|
||||
Reference in New Issue
Block a user