chore(session-mechanism): 交接单目录改名(去 05- 前缀)+图模式触发词改为「图完成目标」

This commit is contained in:
admin committed 2026-10-20 15:49:38 +08:00
1 parent ce05207aa2
commit 5c862e4005
15 files changed
+62 -62

No files matched your search

+2 -2
View File
@@ -78,9 +78,9 @@ agent_created: true
`使用任务会话完成 X 目标` / `使用执行会话完成 X 目标` / `继续 X 目标` / `使用任务会话完成 X 目标` / `使用执行会话完成 X 目标` / `继续 X 目标` /
`用执行会话完成 X` / `派任务会话` / `建任务会话` / 任何"让我去把某个目标做完/接着做"的表述。 `用执行会话完成 X` / `派任务会话` / `建任务会话` / 任何"让我去把某个目标做完/接着做"的表述。
🔴🔴 **两句口令分流哪套机制(2026-10-19 用户令,逐字「完成复杂目标 走graph , 完成目标 走 loop」)**: 🔴🔴 **两句口令分流哪套机制(2026-10-19 用户令,逐字「图完成目标 走graph , 完成目标 走 loop」)**:
- `完成复杂目标:XXXX` ⇒ **走 graph(图模式)**:`graph/graphctl.py` 建图 → `check` → `tick` 派活 → `report` 回报 → `progress`;看板 20100 的「图模式」块会显示。 - `图完成目标:XXXX` ⇒ **走 graph(图模式)**:`graph/graphctl.py` 建图 → `check` → `tick` 派活 → `report` 回报 → `progress`;看板 20100 的「图模式」块会显示。
- `完成目标:XXXX`(含 `使用项目机制完成目标`)⇒ **走 loop(现有项目机制)**:`goalctl` 立目标 → `collabd` 建排期/拉执行会话 → 检查会话判据。 - `完成目标:XXXX`(含 `使用项目机制完成目标`)⇒ **走 loop(现有项目机制)**:`goalctl` 立目标 → `collabd` 建排期/拉执行会话 → 检查会话判据。
- 两条互不覆盖:要图就明说「复杂目标」;只说「完成目标」按 loop 走,⛔ 不擅自改道。 - 两条互不覆盖:要图就明说「复杂目标」;只说「完成目标」按 loop 走,⛔ 不擅自改道。
+1 -1
View File
@@ -27,5 +27,5 @@ COLLABD_PROD_CONFIG=<工作区根>/.workbuddy/collab/collabd.config.json
# ④ WorkBuddy 的配置目录(宿主库 workbuddy.db 在它下面) # ④ WorkBuddy 的配置目录(宿主库 workbuddy.db 在它下面)
CODEBUDDY_CONFIG_DIR=<WorkBuddy 配置目录>,例:<用户目录>/.workbuddy CODEBUDDY_CONFIG_DIR=<WorkBuddy 配置目录>,例:<用户目录>/.workbuddy
# ⑤ 文档库根(锁目录 `05-交接单/.locks` 在它下面) # ⑤ 文档库根(锁目录 `交接单/.locks` 在它下面)
DSH_DOCS_ROOT=<文档库根> DSH_DOCS_ROOT=<文档库根>
+4 -4
View File
@@ -228,7 +228,7 @@ def detect_docs_root(explicit: str | None) -> Path | None:
prev = read_roots().get("DSH_DOCS_ROOT") prev = read_roots().get("DSH_DOCS_ROOT")
if prev and Path(prev).is_dir(): if prev and Path(prev).is_dir():
return Path(prev) return Path(prev)
# 启发式:文档库必须同时具备「05-交接单」与「07-scripts」两个标志目录 # 启发式:文档库必须同时具备「交接单」与「07-scripts」两个标志目录
# ⚠️ 只看同名会误选到工作区里的 `dsh-server-docs/` 副本 ⇒ 两个条件都要满足。 # ⚠️ 只看同名会误选到工作区里的 `dsh-server-docs/` 副本 ⇒ 两个条件都要满足。
seeds = [Path.cwd(), *Path.cwd().parents] seeds = [Path.cwd(), *Path.cwd().parents]
for k in ("DSH_CODE_REPO",): for k in ("DSH_CODE_REPO",):
@@ -236,7 +236,7 @@ def detect_docs_root(explicit: str | None) -> Path | None:
seeds.insert(0, Path(os.environ[k])) seeds.insert(0, Path(os.environ[k]))
for s in seeds[:6]: for s in seeds[:6]:
for cand in (s / "dsh-server-docs", s.parent / "dsh-server-docs"): for cand in (s / "dsh-server-docs", s.parent / "dsh-server-docs"):
if (cand / "05-交接单").is_dir() and (cand / "07-scripts").is_dir(): if (cand / "交接单").is_dir() and (cand / "07-scripts").is_dir():
return cand.resolve() return cand.resolve()
return None return None
@@ -363,10 +363,10 @@ def write_roots(ws: Path, docs: Path | None, code: str | None, dry: bool) -> Non
# 🔴 2026-10-15 修(第 11 条禁令):解析不到文档库时**如实报错 + 留痕**。 # 🔴 2026-10-15 修(第 11 条禁令):解析不到文档库时**如实报错 + 留痕**。
# 原行为是**整行不生成、不报错、不留痕** ⇒ 锁类脚本读不到该键 ⇒ # 原行为是**整行不生成、不报错、不留痕** ⇒ 锁类脚本读不到该键 ⇒
# `handoff-guard.sh` 回落到**技能包自己的 `scripts/`** ⇒ 锁根落进技能包 # `handoff-guard.sh` 回落到**技能包自己的 `scripts/`** ⇒ 锁根落进技能包
# (D 盘那台实测已在 `<技能包>/scripts/05-交接单/.locks` 长出一把锁)。 # (D 盘那台实测已在 `<技能包>/scripts/交接单/.locks` 长出一把锁)。
# ⛔ 不许静默省略整行 —— 静默与"本机真没有文档库"表现完全一样,最难查。 # ⛔ 不许静默省略整行 —— 静默与"本机真没有文档库"表现完全一样,最难查。
log("⛔ 解析不到文档库根:`DSH_DOCS_ROOT` 未设、`roots.env` 里也没有、启发式也没找到" log("⛔ 解析不到文档库根:`DSH_DOCS_ROOT` 未设、`roots.env` 里也没有、启发式也没找到"
"「同层同时有 05-交接单 与 07-scripts」的目录 ⇒ **本次不写** `DSH_DOCS_ROOT`。" "「同层同时有 交接单 与 07-scripts」的目录 ⇒ **本次不写** `DSH_DOCS_ROOT`。"
"⚠️ 后果:锁类脚本(`lock/handoff-guard.sh` / `hooks/lock-guard-hook.py` / " "⚠️ 后果:锁类脚本(`lock/handoff-guard.sh` / `hooks/lock-guard-hook.py` / "
"`collabd.py` 的域锁)取不到锁根。" "`collabd.py` 的域锁)取不到锁根。"
"✅ 本区确实没有 dsh 文档库时 ⇒ 把**本工作区根**写进 `env/roots.env` 的 `DSH_DOCS_ROOT`。") "✅ 本区确实没有 dsh 文档库时 ⇒ 把**本工作区根**写进 `env/roots.env` 的 `DSH_DOCS_ROOT`。")
@@ -49,7 +49,7 @@
| 什么时候用 | 用户刚提需求、问"最优方案"、要在选项间选、要判"该不该做" | 决策已定,开始改码 / 验证 / 归档 | | 什么时候用 | 用户刚提需求、问"最优方案"、要在选项间选、要判"该不该做" | 决策已定,开始改码 / 验证 / 归档 |
| 产出 | **选项表 + 决策点 + 判定依据** | 代码改动 + 验证记录 + 档案 | | 产出 | **选项表 + 决策点 + 判定依据** | 代码改动 + 验证记录 + 档案 |
> 顺序:**先本技能定案 → 再 dsh-change-workflow 执行**。规划会话只做前者,执行会话只做后者(交接载体 = `dsh-server-docs/05-交接单/`)。 > 顺序:**先本技能定案 → 再 dsh-change-workflow 执行**。规划会话只做前者,执行会话只做后者(交接载体 = `dsh-server-docs/交接单/`)。
### 0.1 素材来源与复跑方式(**honest provenance,勿含糊**) ### 0.1 素材来源与复跑方式(**honest provenance,勿含糊**)
@@ -57,7 +57,7 @@
|---|---|---| |---|---|---|
| 改造档案 | `dsh-server-docs/04-调整方案/NN-*.md` | 每份含「触发 / 用户裁定 / 方案对比 / 事故踩坑」——**会话的结论层** | | 改造档案 | `dsh-server-docs/04-调整方案/NN-*.md` | 每份含「触发 / 用户裁定 / 方案对比 / 事故踩坑」——**会话的结论层** |
| 工作日志 | `.workbuddy/memory/YYYY-MM-DD.md` | 五天过程日志,含用户原话引用——**会话的过程层** | | 工作日志 | `.workbuddy/memory/YYYY-MM-DD.md` | 五天过程日志,含用户原话引用——**会话的过程层** |
| 现行事实层 | `BRIEF.md` / `INDEX.md` / `README.md` / `01-规范/06-工作台UI规范.md` / `05-交接单/README.md` | 约定与红线 | | 现行事实层 | `BRIEF.md` / `INDEX.md` / `README.md` / `01-规范/06-工作台UI规范.md` / `交接单/README.md` | 约定与红线 |
| **原始会话转录** | `~/.workbuddy/projects/<工作区目录名>/*.jsonl` | **用户真实发言的原始记录**(含被否决、被纠正的内容) | | **原始会话转录** | `~/.workbuddy/projects/<工作区目录名>/*.jsonl` | **用户真实发言的原始记录**(含被否决、被纠正的内容) |
**复跑命令(只读)**: **复跑命令(只读)**:
@@ -363,7 +363,7 @@ R7-边界② 的"只报告不动手"**只针对「别人的 / 归属不明」的
- **红线优先级高于本技能**:R1 不自动升级 dsh|R2 不改官方主程序与缓存|R3 client bundle 禁 `exports.default`|R4 不用真实账号做登录测试|**R5 权限只准收窄**|**R7 禁未经确认的批量/全仓写入**|**R8 中断在线用户须先知会** —— 任一条命中,**先停手**,本技能的效率论证不构成豁免。 - **红线优先级高于本技能**:R1 不自动升级 dsh|R2 不改官方主程序与缓存|R3 client bundle 禁 `exports.default`|R4 不用真实账号做登录测试|**R5 权限只准收窄**|**R7 禁未经确认的批量/全仓写入**|**R8 中断在线用户须先知会** —— 任一条命中,**先停手**,本技能的效率论证不构成豁免。
- **执行层**:决策定了之后走 `dsh-change-workflow`(六阶段 + 档案模板 + 并行调度协议)。 - **执行层**:决策定了之后走 `dsh-change-workflow`(六阶段 + 档案模板 + 并行调度协议)。
- **规划/执行分离**:本技能属**规划侧**;产出交接单(`dsh-server-docs/05-交接单/`,8 段必填),规划会话**不 ssh、不改码、不重启、不 scp**。 - **规划/执行分离**:本技能属**规划侧**;产出交接单(`dsh-server-docs/交接单/`,8 段必填),规划会话**不 ssh、不改码、不重启、不 scp**。
- **单一来源**:清单与状态 = `INDEX.md §二`;待办 = `01-规范/03-路线图与待办.md`;部署事实 = `DEPLOY-本部署.md`;UI 基线 = `01-规范/06-工作台UI规范.md`;现行事实 = `BRIEF.md`(首读)。 - **单一来源**:清单与状态 = `INDEX.md §二`;待办 = `01-规范/03-路线图与待办.md`;部署事实 = `DEPLOY-本部署.md`;UI 基线 = `01-规范/06-工作台UI规范.md`;现行事实 = `BRIEF.md`(首读)。
- **可复跑判定工具**:`docs-audit.py`(歧义/编号/悬空引用,退出码非 0 即需处理)、`docs-sync-check.sh`(双端对账)、`docs-manifest.py`(机读清单)、`handoff-guard.sh`(并发预检,推送前 `PUSH=1`)。 - **可复跑判定工具**:`docs-audit.py`(歧义/编号/悬空引用,退出码非 0 即需处理)、`docs-sync-check.sh`(双端对账)、`docs-manifest.py`(机读清单)、`handoff-guard.sh`(并发预检,推送前 `PUSH=1`)。
+1 -1
View File
@@ -753,7 +753,7 @@
读锁表判占用,被占 ⇒ **换域名**(⛔ 不是等),且**必须排除自己的锁**(否则锁主本人被自己挡住); 读锁表判占用,被占 ⇒ **换域名**(⛔ 不是等),且**必须排除自己的锁**(否则锁主本人被自己挡住);
锁表读不到 ⇒ **fail-safe 判全部不可派**(这正是用户那句「别的只能干等」的机制面)。 锁表读不到 ⇒ **fail-safe 判全部不可派**(这正是用户那句「别的只能干等」的机制面)。
- ⚠️ **顺带查出的既有不一致**:`handoff-guard.sh` 用 `scripts skills`, - ⚠️ **顺带查出的既有不一致**:`handoff-guard.sh` 用 `scripts skills`,
`lock-guard-hook.py:58` 用 `07-scripts 08-skills 05-交接单`。 `lock-guard-hook.py:58` 用 `07-scripts 08-skills 交接单`。
⇒ **工作区下的路径两边一致**(都先命中工作区名)⇒ 不影响工作区内的判据; ⇒ **工作区下的路径两边一致**(都先命中工作区名)⇒ 不影响工作区内的判据;
但**代码仓根目录下**的路径两边会算出不同键 ⇒ **已存在假绿风险**。`collabd.py --domain-status` 会报这个差异。 但**代码仓根目录下**的路径两边会算出不同键 ⇒ **已存在假绿风险**。`collabd.py --domain-status` 会报这个差异。
@@ -103,7 +103,7 @@
```text ```text
已登记自动接续:一次性 automation `<id>`,约 3~4 分钟后(08:37)自动开新会话,**不用你操作**; 已登记自动接续:一次性 automation `<id>`,约 3~4 分钟后(08:37)自动开新会话,**不用你操作**;
接续点 = 序 ④「443/TCP 兜底」的规划棒(出 `05-交接单/交接单_xxx_20260917.md`)。 接续点 = 序 ④「443/TCP 兜底」的规划棒(出 `交接单/交接单_xxx_20260917.md`)。
``` ```
### 3.1.1 ⛔ 排期两条铁律 ### 3.1.1 ⛔ 排期两条铁律
+4 -4
View File
@@ -3941,7 +3941,7 @@ def active_schedule_exists(name: str) -> bool:
# handoff-guard.sh : dsh-server-docs ai1net-dsh-server aliyun-dsh-server src poc web test # handoff-guard.sh : dsh-server-docs ai1net-dsh-server aliyun-dsh-server src poc web test
# docs scripts skills 交接单 # docs scripts skills 交接单
# lock-guard-hook.py: dsh-server-docs ai1net-dsh-server aliyun-dsh-server src poc web test # lock-guard-hook.py: dsh-server-docs ai1net-dsh-server aliyun-dsh-server src poc web test
# docs 07-scripts 08-skills 05-交接单 # docs 07-scripts 08-skills 交接单
# 差异:`scripts`/`skills` vs `07-scripts`/`08-skills`。⇒ 命中「第一个锚点段」时, # 差异:`scripts`/`skills` vs `07-scripts`/`08-skills`。⇒ 命中「第一个锚点段」时,
# **工作区下的路径两边结果一致**(都先命中工作区名)⇒ 本节的实际判据不受影响; # **工作区下的路径两边结果一致**(都先命中工作区名)⇒ 本节的实际判据不受影响;
# ⚠️ 但**代码仓根目录下的路径**(无工作区名那段)两边会算出不同键 ⇒ 已存在假绿风险。 # ⚠️ 但**代码仓根目录下的路径**(无工作区名那段)两边会算出不同键 ⇒ 已存在假绿风险。
@@ -3951,7 +3951,7 @@ def active_schedule_exists(name: str) -> bool:
_DS_ANCHORS = ("dsh-server-docs", "ai1net-dsh-server", "aliyun-dsh-server", _DS_ANCHORS = ("dsh-server-docs", "ai1net-dsh-server", "aliyun-dsh-server",
"src", "poc", "web", "test", "docs", "scripts", "skills", "交接单") "src", "poc", "web", "test", "docs", "scripts", "skills", "交接单")
# ⚠️ 另一份(`lock-guard-hook.py:83 _DOMAIN_SEGS`)—— 🔴 2026-10-03 10:0x **已对齐**: # ⚠️ 另一份(`lock-guard-hook.py:83 _DOMAIN_SEGS`)—— 🔴 2026-10-03 10:0x **已对齐**:
# 原值是 `07-scripts/08-skills/05-交接单`(带数字前缀的**旧仓目录名**)⇒ 与本表不同 # 原值是 `07-scripts/08-skills/交接单`(带数字前缀的**旧仓目录名**)⇒ 与本表不同
# ⇒ 那个文件里的 `domain_key()` 会跟抢锁侧算出**不同域键** ⇒ 域锁静默失效(假绿)。 # ⇒ 那个文件里的 `domain_key()` 会跟抢锁侧算出**不同域键** ⇒ 域锁静默失效(假绿)。
# ✅ 已改成 `scripts/skills/交接单`,与 `_DS_ANCHORS` 及 shell 侧**三处逐字一致**。 # ✅ 已改成 `scripts/skills/交接单`,与 `_DS_ANCHORS` 及 shell 侧**三处逐字一致**。
# ⛔ 本变量**不参与任何计算**,只保留作"另一侧读到什么"的对照;自检会逐字比三处。 # ⛔ 本变量**不参与任何计算**,只保留作"另一侧读到什么"的对照;自检会逐字比三处。
@@ -4019,7 +4019,7 @@ def ds_active_domain_locks() -> dict:
""" """
out = {} out = {}
_me = str(os.environ.get("DSH_SESSION_NAME") or "").strip() _me = str(os.environ.get("DSH_SESSION_NAME") or "").strip()
# 🔴🔴 锁根=文档库的 `05-交接单/.locks`。**键名必须与写侧逐字同源:`DSH_DOCS_ROOT`** # 🔴🔴 锁根=文档库的 `交接单/.locks`。**键名必须与写侧逐字同源:`DSH_DOCS_ROOT`**
# (写侧六处:`lock/handoff-guard.sh:46`/`lock/preflight-lock.sh:30`/`lock/handoff-status.py:40`/ # (写侧六处:`lock/handoff-guard.sh:46`/`lock/preflight-lock.sh:30`/`lock/handoff-status.py:40`/
# `lock/orphan-lock.py:203`/`hooks/lock-guard-hook.py:74`/`session-rules-check.py:107`) # `lock/orphan-lock.py:203`/`hooks/lock-guard-hook.py:74`/`session-rules-check.py:107`)
# 2026-10-15 修:原读 `DSH_LOCKROOT`/`DSH_DOCS_REPO` —— 这两个键**全包只此一处**读, # 2026-10-15 修:原读 `DSH_LOCKROOT`/`DSH_DOCS_REPO` —— 这两个键**全包只此一处**读,
@@ -4030,7 +4030,7 @@ def ds_active_domain_locks() -> dict:
_cands.append(os.environ["DSH_LOCKROOT"]) _cands.append(os.environ["DSH_LOCKROOT"])
_dr = (os.environ.get("DSH_DOCS_ROOT") or "").strip() # ← 与写侧同源 _dr = (os.environ.get("DSH_DOCS_ROOT") or "").strip() # ← 与写侧同源
if _dr: if _dr:
_cands.append(os.path.join(_dr, "05-交接单", ".locks")) _cands.append(os.path.join(_dr, "交接单", ".locks"))
root = next((c for c in _cands if os.path.isdir(c)), "") root = next((c for c in _cands if os.path.isdir(c)), "")
if not root: if not root:
# ⛔ 不再回落到"猜的盘符"(第 11 条禁令):如实报错 + 留痕。 # ⛔ 不再回落到"猜的盘符"(第 11 条禁令):如实报错 + 留痕。
@@ -6,7 +6,7 @@ lock-guard-hook.py — 「无锁不许改库」的强制钩子(WorkBuddy PreTo
──────────── ────────────
2026-09-12 实证:`handoff-guard.sh` 输出「无全局锁」被会话读成「环境干净,可以开工」 2026-09-12 实证:`handoff-guard.sh` 输出「无全局锁」被会话读成「环境干净,可以开工」
(正确读法是「你快去抢锁」),结果**两个会话同时改了本库**。措辞已在 guard 与 (正确读法是「你快去抢锁」),结果**两个会话同时改了本库**。措辞已在 guard 与
`05-交接单/README.md` 里补正,但**约定拦不住不看的人** —— 本脚本是强制层。 `交接单/README.md` 里补正,但**约定拦不住不看的人** —— 本脚本是强制层。
作用域(**刻意收窄:对其他项目零影响**) 作用域(**刻意收窄:对其他项目零影响**)
──────────── ────────────
@@ -17,7 +17,7 @@ lock-guard-hook.py — 「无锁不许改库」的强制钩子(WorkBuddy PreTo
判据 判据
──── ────
`<文档库根>/05-交接单/.exec-lock` 存在 = 有人持锁 → 放行(归属由台账 OWNER 承担); `<文档库根>/交接单/.exec-lock` 存在 = 有人持锁 → 放行(归属由台账 OWNER 承担);
不存在 = **deny**,并把可直接粘贴的抢锁命令回给 Agent。 不存在 = **deny**,并把可直接粘贴的抢锁命令回给 Agent。
为什么**不拦 Bash** 为什么**不拦 Bash**
@@ -91,9 +91,9 @@ if not DOCS_ROOT:
sys.exit(0) sys.exit(0)
# ⛔ 不留盘符:代码库=文档库的上一级(`<repo>/dsh-server-docs` 的约定) # ⛔ 不留盘符:代码库=文档库的上一级(`<repo>/dsh-server-docs` 的约定)
CODE_REPO = os.environ.get("DSH_CODE_REPO") or (os.path.dirname(DOCS_ROOT) if DOCS_ROOT else "") CODE_REPO = os.environ.get("DSH_CODE_REPO") or (os.path.dirname(DOCS_ROOT) if DOCS_ROOT else "")
LOCK_DIR = os.path.join(DOCS_ROOT, "05-交接单", ".exec-lock") LOCK_DIR = os.path.join(DOCS_ROOT, "交接单", ".exec-lock")
OWNER_FILE = os.path.join(LOCK_DIR, "OWNER") OWNER_FILE = os.path.join(LOCK_DIR, "OWNER")
LOCKS_ROOT = os.path.join(DOCS_ROOT, "05-交接单", ".locks") LOCKS_ROOT = os.path.join(DOCS_ROOT, "交接单", ".locks")
GUARD_CMD = 'bash 07-scripts/handoff-guard.sh --claim-exec "<你的会话名>"' GUARD_CMD = 'bash 07-scripts/handoff-guard.sh --claim-exec "<你的会话名>"'
GUARD_DOMAIN_CMD = 'bash 07-scripts/handoff-guard.sh --claim-exec "<你的会话名>" --domains <域...>' GUARD_DOMAIN_CMD = 'bash 07-scripts/handoff-guard.sh --claim-exec "<你的会话名>" --domains <域...>'
@@ -101,7 +101,7 @@ GUARD_DOMAIN_CMD = 'bash 07-scripts/handoff-guard.sh --claim-exec "<你的会话
# ⚠️ 顺序有意义:**先长后短**,与 handoff-guard.sh 的 _ANCHOR_SEGS 逐字一致。 # ⚠️ 顺序有意义:**先长后短**,与 handoff-guard.sh 的 _ANCHOR_SEGS 逐字一致。
# 两侧只要顺序或集合不同 ⇒ 同一文件算出不同域键 ⇒ 域锁静默失效(假绿)。 # 两侧只要顺序或集合不同 ⇒ 同一文件算出不同域键 ⇒ 域锁静默失效(假绿)。
# 🔴🔴 2026-10-03 10:0x **修一处真缺陷(不是文档漂移)**:本行原为 # 🔴🔴 2026-10-03 10:0x **修一处真缺陷(不是文档漂移)**:本行原为
# `… "docs", "07-scripts", "08-skills", "05-交接单"`, # `… "docs", "07-scripts", "08-skills", "交接单"`,
# 而真源 `handoff-guard.sh:66 _ANCHOR_SEGS` 是 `… "docs", "scripts", "skills", "交接单"`。 # 而真源 `handoff-guard.sh:66 _ANCHOR_SEGS` 是 `… "docs", "scripts", "skills", "交接单"`。
# ⚠️ 后果不是"看起来不一致":`domain_key()` **拿本表参与实际计算** ⇒ 同一个文件 # ⚠️ 后果不是"看起来不一致":`domain_key()` **拿本表参与实际计算** ⇒ 同一个文件
# 在 hook 侧与抢锁侧会算出**不同域键** ⇒ 两边各抢各的 ⇒ **域锁等于没有(假绿)**。 # 在 hook 侧与抢锁侧会算出**不同域键** ⇒ 两边各抢各的 ⇒ **域锁等于没有(假绿)**。
@@ -339,7 +339,7 @@ def main() -> None:
who = lock_owner() who = lock_owner()
msg = f"🔐 全局执行锁【已被占用】:{who} —— 但**域锁可绕开它**:不重叠的域仍能并行。" msg = f"🔐 全局执行锁【已被占用】:{who} —— 但**域锁可绕开它**:不重叠的域仍能并行。"
hook_log("SessionStart", f"旧全局锁被占 owner={who} source={src}") hook_log("SessionStart", f"旧全局锁被占 owner={who} source={src}")
elif os.path.isdir(DOCKS := os.path.join(DOCS_ROOT, "05-交接单", ".locks")) and os.listdir(DOCKS): elif os.path.isdir(DOCKS := os.path.join(DOCS_ROOT, "交接单", ".locks")) and os.listdir(DOCKS):
msg = "🔐 已有域锁在跑 —— 开工前先抢你要的域(不重叠即可并行):" + GUARD_DOMAIN_CMD msg = "🔐 已有域锁在跑 —— 开工前先抢你要的域(不重叠即可并行):" + GUARD_DOMAIN_CMD
hook_log("SessionStart", f"有域锁无全局锁 source={src}") hook_log("SessionStart", f"有域锁无全局锁 source={src}")
elif os.path.isdir(DOCS_ROOT): elif os.path.isdir(DOCS_ROOT):
@@ -160,11 +160,11 @@ TRIGGERS = (
# 🔴 2026-10-19 用户令(逐字「把skill 改成 完成目标也能触发」):光写「项目机制完成目标」不够 —— # 🔴 2026-10-19 用户令(逐字「把skill 改成 完成目标也能触发」):光写「项目机制完成目标」不够 ——
# 用户口语里会把机制名字省掉,只说「完成目标」;词表里必须有**裸「完成目标」**这一条, # 用户口语里会把机制名字省掉,只说「完成目标」;词表里必须有**裸「完成目标」**这一条,
# 否则那句同样是派活授权,却一条都命不中。 # 否则那句同样是派活授权,却一条都命不中。
# 🔴 2026-10-19 用户令(逐字「完成复杂目标 走graph , 完成目标 走 loop」): # 🔴 2026-10-19 用户令(逐字「图完成目标 走graph , 完成目标 走 loop」):
# **两条口令分工** —— `完成复杂目标` ⇒ 图模式 graph;`完成目标` ⇒ 现有 loop 机制。 # **两条口令分工** —— `图完成目标` ⇒ 图模式 graph;`完成目标` ⇒ 现有 loop 机制。
# ⚠️ 两句不是包含关系("完成复杂目标"里没有连续的"完成目标"子串),所以两词都要进表, # ⚠️ 两句不是包含关系("图完成目标"里没有连续的"完成目标"子串),所以两词都要进表,
# 分流按"先看复杂"判,⛔ 不许靠子串顺序碰运气。 # 分流按"先看复杂"判,⛔ 不许靠子串顺序碰运气。
'完成复杂目标', '复杂目标', '图完成目标', '复杂目标',
'完成目标', '完成目标',
'使用任务会话', '使用执行会话', '使用协作会话', '任务会话完成', '执行会话完成', '使用任务会话', '使用执行会话', '使用协作会话', '任务会话完成', '执行会话完成',
'继续完成目标', '继续目标', '继续完成之前', '继续之前的', '继续完成目标', '继续目标', '继续完成之前', '继续之前的',
@@ -188,7 +188,7 @@ _LOAD_RULES = ('按规则来', '按规则做', '按作业规则', '遵守规则'
# 命中"使用任务会话完成目标"的人,要的是**会话机制本体**(怎么建排期/怎么派活/ # 命中"使用任务会话完成目标"的人,要的是**会话机制本体**(怎么建排期/怎么派活/
# 自决策白名单在 `references/02-功能优先协作协议.md`),⛔ 不是决策方法论。 # 自决策白名单在 `references/02-功能优先协作协议.md`),⛔ 不是决策方法论。
_LOAD_SESSION = ('使用项目机制', '项目机制完成', '项目机制', _LOAD_SESSION = ('使用项目机制', '项目机制完成', '项目机制',
'完成复杂目标', '复杂目标', '完成目标', '图完成目标', '复杂目标', '完成目标',
'使用任务会话', '使用执行会话', '使用协作会话', '任务会话完成', '执行会话完成', '使用任务会话', '使用执行会话', '使用协作会话', '任务会话完成', '执行会话完成',
'继续完成目标', '继续目标', '继续完成之前', '继续之前的', '继续完成目标', '继续目标', '继续完成之前', '继续之前的',
'会话技能', '会话机制', '创建任务会话', '建任务会话', '会话技能', '会话机制', '创建任务会话', '建任务会话',
@@ -413,18 +413,18 @@ def main():
hit_rules = [w for w in hits if w in _LOAD_RULES] hit_rules = [w for w in hits if w in _LOAD_RULES]
if hit_session: if hit_session:
head = '检测到用户本轮**点名了会话机制 / 派活**(命中:%s)。' % '、'.join(hit_session) head = '检测到用户本轮**点名了会话机制 / 派活**(命中:%s)。' % '、'.join(hit_session)
# 🔴 2026-10-19 用户令(逐字「完成复杂目标 走graph , 完成目标 走 loop」): # 🔴 2026-10-19 用户令(逐字「图完成目标 走graph , 完成目标 走 loop」):
# 两条口令分流。**先判"复杂"**("完成复杂目标"里没有连续的"完成目标"子串,但仍按先后判更稳)。 # 两条口令分流。**先判"复杂"**("图完成目标"里没有连续的"完成目标"子串,但仍按先后判更稳)。
mode = '' mode = ''
if '复杂目标' in prompt: if '图完成目标' in prompt or '复杂目标' in prompt:
mode = ('🔴 **本轮走 graph(图模式)**:用户说「完成复杂目标」⇒ 用本包 `graph/graphctl.py`:\n' mode = ('🔴 **本轮走 graph(图模式)**:用户说「图完成目标」⇒ 用本包 `graph/graphctl.py`:\n'
' `init` 建图 → `check` 校验 → `tick`(算完就开)→ 执行方 `report` 回报 → `progress` 看进度;\n' ' `init` 建图 → `check` 校验 → `tick`(算完就开)→ 执行方 `report` 回报 → `progress` 看进度;\n'
' 看板 `http://127.0.0.1:20100/` 的「图模式」块会实时显示这张图。\n' ' 看板 `http://127.0.0.1:20100/` 的「图模式」块会实时显示这张图。\n'
' ⛔ 不走 loop 排期、⛔ 不写 `goal.json`/`tasks.json`;图只负责编排,节点上的活仍由会话去干。\n\n') ' ⛔ 不走 loop 排期、⛔ 不写 `goal.json`/`tasks.json`;图只负责编排,节点上的活仍由会话去干。\n\n')
elif '完成目标' in prompt: elif '完成目标' in prompt:
mode = ('🔴 **本轮走 loop(现有项目机制)**:用户说「完成目标」⇒ 按目标 + 台账 + 队列那套推进' mode = ('🔴 **本轮走 loop(现有项目机制)**:用户说「完成目标」⇒ 按目标 + 台账 + 队列那套推进'
'(`goalctl`/`collabd`/检查会话)。\n' '(`goalctl`/`collabd`/检查会话)。\n'
' ⛔ 不擅自改走 graph —— 要走图,用户会说「完成复杂目标」。\n\n') ' ⛔ 不擅自改走 graph —— 要走图,用户会说「图完成目标」。\n\n')
body = mode + ( body = mode + (
'⛔ **不要凭记忆代替、也不要以"我已经知道这套机制"为由跳过**:\n' '⛔ **不要凭记忆代替、也不要以"我已经知道这套机制"为由跳过**:\n'
' **先调用 Skill 工具加载 `session-mechanism`**(会话机制 + 多会话执行总入口 · 含**首屏第 0 步加载门槛**' ' **先调用 Skill 工具加载 `session-mechanism`**(会话机制 + 多会话执行总入口 · 含**首屏第 0 步加载门槛**'
+17 -17
View File
@@ -5,8 +5,8 @@
# 约定全部依赖“人记得做”。本脚本把判据变成**一条命令 + 退出码**,不靠记忆。 # 约定全部依赖“人记得做”。本脚本把判据变成**一条命令 + 退出码**,不靠记忆。
# #
# 关键设计:**mtime 只作提示、不作判定**(它分不清“谁改的”)。真正的判定来自四处: # 关键设计:**mtime 只作提示、不作判定**(它分不清“谁改的”)。真正的判定来自四处:
# ① 全局执行锁(05-交接单/.exec-lock)—— **一粗**:同一时刻只允许一个任务会话动「文档/代码/服务器」 # ① 全局执行锁(交接单/.exec-lock)—— **一粗**:同一时刻只允许一个任务会话动「文档/代码/服务器」
# ① 单级占用锁(05-交接单/.doing-<单号>)—— **一细**:这个单归谁做(供台账 / 接管使用) # ① 单级占用锁(交接单/.doing-<单号>)—— **一细**:这个单归谁做(供台账 / 接管使用)
# ② 越界改动(不在我声明清单里的未提交文件)—— 推送前检查,防“顺手重放别人的半成品” # ② 越界改动(不在我声明清单里的未提交文件)—— 推送前检查,防“顺手重放别人的半成品”
# ④ 双端一致性(docs-sync-check.sh)—— 推送前检查,防“幽灵文件”(推回了别人已移走的文件) # ④ 双端一致性(docs-sync-check.sh)—— 推送前检查,防“幽灵文件”(推回了别人已移走的文件)
# #
@@ -24,7 +24,7 @@
# # ⛔ 不带名字 ⇒ 拒绝释放(R9,2026-09-25 修) # # ⛔ 不带名字 ⇒ 拒绝释放(R9,2026-09-25 修)
# # 域锁侧:--claim-exec <会话> --domains <域…> / 同姿势 --release-exec <会话> # # 域锁侧:--claim-exec <会话> --domains <域…> / 同姿势 --release-exec <会话>
# bash 07-scripts/handoff-guard.sh # 看全局状态(信息模式) # bash 07-scripts/handoff-guard.sh # 看全局状态(信息模式)
# ME="exec-session-B" MINE="05-交接单/T03-*.md" bash 07-scripts/handoff-guard.sh T03 # 开工检查(严格) # ME="exec-session-B" MINE="交接单/T03-*.md" bash 07-scripts/handoff-guard.sh T03 # 开工检查(严格)
# ME="exec-session-B" MINE="..." PUSH=1 bash 07-scripts/handoff-guard.sh T03 # 推送前检查 # ME="exec-session-B" MINE="..." PUSH=1 bash 07-scripts/handoff-guard.sh T03 # 推送前检查
# #
# 环境变量:ME(我是谁 —— 用来判断锁是不是自己的;不设则一律按“别人的锁”处理) # 环境变量:ME(我是谁 —— 用来判断锁是不是自己的;不设则一律按“别人的锁”处理)
@@ -46,7 +46,7 @@ fi
# 🔴 2026-10-15 修(第 11 条禁令 + D 盘实测事故): # 🔴 2026-10-15 修(第 11 条禁令 + D 盘实测事故):
# 原写 `ROOT="${DSH_DOCS_ROOT:-$(cd "$_SM_HERE/.." && pwd)}"` —— 回落值是 # 原写 `ROOT="${DSH_DOCS_ROOT:-$(cd "$_SM_HERE/.." && pwd)}"` —— 回落值是
# `$_SM_HERE/..` = **本技能包自己的 `scripts/`** ⇒ 锁根落进技能包 # `$_SM_HERE/..` = **本技能包自己的 `scripts/`** ⇒ 锁根落进技能包
# (实测已在 `<技能包>/scripts/05-交接单/.locks` 长出一把锁,本该落文档库)。 # (实测已在 `<技能包>/scripts/交接单/.locks` 长出一把锁,本该落文档库)。
# ✅ 现行:取不到 ⇒ **如实报错 + 留痕 + 退出码 2**,⛔ 不回落到包内、⛔ 不猜路径。 # ✅ 现行:取不到 ⇒ **如实报错 + 留痕 + 退出码 2**,⛔ 不回落到包内、⛔ 不猜路径。
if [ -z "${DSH_DOCS_ROOT:-}" ]; then if [ -z "${DSH_DOCS_ROOT:-}" ]; then
_SM_ERR="${_SM_PKG}/logs/handoff-guard.err.log" _SM_ERR="${_SM_PKG}/logs/handoff-guard.err.log"
@@ -59,9 +59,9 @@ if [ -z "${DSH_DOCS_ROOT:-}" ]; then
fi fi
ROOT="$DSH_DOCS_ROOT" ROOT="$DSH_DOCS_ROOT"
cd "$ROOT" || exit 2 cd "$ROOT" || exit 2
LOCKDIR="$ROOT/05-交接单/.doing-" LOCKDIR="$ROOT/交接单/.doing-"
LOCKEXEC="$ROOT/05-交接单/.exec-lock" LOCKEXEC="$ROOT/交接单/.exec-lock"
MELOCK="$ROOT/05-交接单/.me-lock" MELOCK="$ROOT/交接单/.me-lock"
ME="${ME:-}" ME="${ME:-}"
# ══════════════════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════════════════
@@ -77,7 +77,7 @@ ME="${ME:-}"
# #
# 兼容:`.exec-lock` 分支**原样保留** ⇒ 未升级的会话照旧可用(hook 是启动时快照)。 # 兼容:`.exec-lock` 分支**原样保留** ⇒ 未升级的会话照旧可用(hook 是启动时快照)。
# ══════════════════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════════════════
LOCKSROOT="$ROOT/05-交接单/.locks" LOCKSROOT="$ROOT/交接单/.locks"
GATELOCK="$LOCKSROOT/.gate" # 秒级临界区:算交集时必须串行,否则两个会话可能同时判定"无冲突" GATELOCK="$LOCKSROOT/.gate" # 秒级临界区:算交集时必须串行,否则两个会话可能同时判定"无冲突"
# 域字符串规范化 ⇒ 域键。**必须与 lock-guard-hook.py 的 domain_key() 同规则**, # 域字符串规范化 ⇒ 域键。**必须与 lock-guard-hook.py 的 domain_key() 同规则**,
@@ -418,15 +418,15 @@ if [ "${1:-}" = "--claim" ]; then
[ -n "$T" ] || { echo "用法:--claim <单号> [占用者]" >&2; exit 2; } [ -n "$T" ] || { echo "用法:--claim <单号> [占用者]" >&2; exit 2; }
if mkdir "$LOCKDIR$T" 2>/dev/null; then if mkdir "$LOCKDIR$T" 2>/dev/null; then
printf '%s\n' "$OWNER" > "$LOCKDIR$T/OWNER" printf '%s\n' "$OWNER" > "$LOCKDIR$T/OWNER"
echo "✓ 已占位:05-交接单/.doing-$T($OWNER)—— 完工请 --release $T" echo "✓ 已占位:交接单/.doing-$T($OWNER)—— 完工请 --release $T"
exit 0 exit 0
fi fi
echo "✗ 占位失败:05-交接单/.doing-$T 已存在(占用者 $(cat "$LOCKDIR$T/OWNER" 2>/dev/null || echo '?')) → 停手" >&2 echo "✗ 占位失败:交接单/.doing-$T 已存在(占用者 $(cat "$LOCKDIR$T/OWNER" 2>/dev/null || echo '?')) → 停手" >&2
exit 1 exit 1
fi fi
if [ "${1:-}" = "--release" ]; then if [ "${1:-}" = "--release" ]; then
T="${2:-}"; [ -n "$T" ] || { echo "用法:--release <单号>" >&2; exit 2; } T="${2:-}"; [ -n "$T" ] || { echo "用法:--release <单号>" >&2; exit 2; }
rm -rf "$LOCKDIR$T" && echo "✓ 已释放:05-交接单/.doing-$T" || { echo "✗ 释放失败" >&2; exit 1; } rm -rf "$LOCKDIR$T" && echo "✓ 已释放:交接单/.doing-$T" || { echo "✗ 释放失败" >&2; exit 1; }
exit 0 exit 0
fi fi
@@ -451,7 +451,7 @@ in_mine() { [ -z "$MINE" ] && return 1; for p in $MINE; do case "$1" in $p) retu
# ── ① 占用锁(硬判定)──────────────────────────────────── # ── ① 占用锁(硬判定)────────────────────────────────────
echo echo
echo "【1】占用锁(05-交接单/.doing-*)" echo "【1】占用锁(交接单/.doing-*)"
shopt -s nullglob shopt -s nullglob
LOCKS=("$LOCKDIR"*) LOCKS=("$LOCKDIR"*)
if [ ${#LOCKS[@]} -eq 0 ]; then if [ ${#LOCKS[@]} -eq 0 ]; then
@@ -475,9 +475,9 @@ fi
if [ ${#LOCKS[@]} -gt 0 ]; then if [ ${#LOCKS[@]} -gt 0 ]; then
for L in "${LOCKS[@]}"; do for L in "${LOCKS[@]}"; do
n="$(basename "$L")"; T="${n#.doing-}" n="$(basename "$L")"; T="${n#.doing-}"
row="$(grep -m1 "^| \`$T-" 05-交接单/README.md 2>/dev/null || true)" row="$(grep -m1 "^| \`$T-" 交接单/README.md 2>/dev/null || true)"
if [ -z "$row" ]; then if [ -z "$row" ]; then
echo " ⚠ $T 有占用锁,但 \`05-交接单/README.md §一\` 里**没有这一行** → 补上(新增单)" echo " ⚠ $T 有占用锁,但 \`交接单/README.md §一\` 里**没有这一行** → 补上(新增单)"
VERDICT=1; HARD+=("①b $T 有锁但台账缺行") VERDICT=1; HARD+=("①b $T 有锁但台账缺行")
elif printf '%s' "$row" | grep -qE "执行中|已完成|已归档"; then elif printf '%s' "$row" | grep -qE "执行中|已完成|已归档"; then
echo " · $T 台账已标「执行中/已完成」✓" echo " · $T 台账已标「执行中/已完成」✓"
@@ -491,7 +491,7 @@ fi
# ── ①c 全局执行锁(2026-09-12 新增:同一时刻只允许一个任务会话)──── # ── ①c 全局执行锁(2026-09-12 新增:同一时刻只允许一个任务会话)────
echo echo
echo "【1c】全局执行锁(05-交接单/.exec-lock)" echo "【1c】全局执行锁(交接单/.exec-lock)"
if [ ! -d "$LOCKEXEC" ]; then if [ ! -d "$LOCKEXEC" ]; then
echo " ✓ 无全局锁 —— ⚠️ 这**不是「可以开工」,是「你快去抢」**:" echo " ✓ 无全局锁 —— ⚠️ 这**不是「可以开工」,是「你快去抢」**:"
echo " 开工前必须先:bash 07-scripts/handoff-guard.sh --claim-exec \"<你的会话名>\"" echo " 开工前必须先:bash 07-scripts/handoff-guard.sh --claim-exec \"<你的会话名>\""
@@ -544,7 +544,7 @@ fi
# ── ② 越界改动(推送前硬判定)──────────────────────────── # ── ② 越界改动(推送前硬判定)────────────────────────────
echo echo
echo "【2】未提交改动(含未跟踪)" echo "【2】未提交改动(含未跟踪)"
CHANGED="$(git -c core.quotepath=false status --short 2>/dev/null | awk '{print $NF}' | grep -vE '^05-交接单/\.(doing-|exec-lock)' || true)" CHANGED="$(git -c core.quotepath=false status --short 2>/dev/null | awk '{print $NF}' | grep -vE '^交接单/\.(doing-|exec-lock)' || true)"
OUTSIDE=(); INSIDE=() OUTSIDE=(); INSIDE=()
if [ -z "$CHANGED" ]; then if [ -z "$CHANGED" ]; then
echo " ✓ 工作区干净" echo " ✓ 工作区干净"
@@ -570,7 +570,7 @@ echo
echo "【3】近 $WINDOW 分钟被改动的文件(**仅提示**:mtime 分不清谁改的)" echo "【3】近 $WINDOW 分钟被改动的文件(**仅提示**:mtime 分不清谁改的)"
HOT="$(find . -type f -mmin "-$WINDOW" \ HOT="$(find . -type f -mmin "-$WINDOW" \
-not -path './.git/*' -not -path '*/node_modules/*' -not -name '*.bak*' \ -not -path './.git/*' -not -path '*/node_modules/*' -not -name '*.bak*' \
-not -path './05-交接单/.doing-*' -not -path './05-交接单/.exec-lock*' 2>/dev/null | sed 's|^\./||' | LC_ALL=C sort)" -not -path './交接单/.doing-*' -not -path './交接单/.exec-lock*' 2>/dev/null | sed 's|^\./||' | LC_ALL=C sort)"
if [ -z "$HOT" ]; then if [ -z "$HOT" ]; then
echo " ✓ 无(这块是「冷」的)" echo " ✓ 无(这块是「冷」的)"
else else
@@ -1,10 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""handoff-status.py — 一条命令看清 05-交接单 里哪些单子【待执行】/【已完成】/【无状态】。 """handoff-status.py — 一条命令看清 交接单 里哪些单子【待执行】/【已完成】/【无状态】。
为什么需要它:`05-交接单/交接单-已完成/` 那层目录摊平后,**"目录位置"不再表达状态**。 为什么需要它:`交接单/交接单-已完成/` 那层目录摊平后,**"目录位置"不再表达状态**。
状态改由**机读字段**承载(与布局解耦,摊平/搬家都不失效): 状态改由**机读字段**承载(与布局解耦,摊平/搬家都不失效):
① 单子头部 `- 状态:…` 行(模板已有)→ ② 回退取 `05-交接单/README.md` 台账表的「状态」列 ① 单子头部 `- 状态:…` 行(模板已有)→ ② 回退取 `交接单/README.md` 台账表的「状态」列
用法:python 07-scripts/handoff-status.py 用法:python 07-scripts/handoff-status.py
""" """
@@ -38,7 +38,7 @@ import os
import re import re
ROOT = os.environ.get("DSH_DOCS_ROOT") or os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ROOT = os.environ.get("DSH_DOCS_ROOT") or os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
D = os.path.join(ROOT, "05-交接单") D = os.path.join(ROOT, "交接单")
README = os.path.join(D, "README.md") README = os.path.join(D, "README.md")
HEAD_STATUS = re.compile(r"^[\s>*\-|#]*\*{0,2}状态\*{0,2}\s*[::]\s*(.+?)\s*$", re.M) HEAD_STATUS = re.compile(r"^[\s>*\-|#]*\*{0,2}状态\*{0,2}\s*[::]\s*(.+?)\s*$", re.M)
+1 -1
View File
@@ -7,7 +7,7 @@
# 文档冲突靠 git status / mtime 能看出来,**服务器态变更看不出来** # 文档冲突靠 git status / mtime 能看出来,**服务器态变更看不出来**
# (systemctl 不会告诉你 10 分钟前谁重启过)→ 只能靠显式锁。 # (systemctl 不会告诉你 10 分钟前谁重启过)→ 只能靠显式锁。
# #
# 与文档库锁的关系:`05-交接单/.doing-<单>` + `.exec-lock` 管「文档与单子」;本锁管「生产态」。 # 与文档库锁的关系:`交接单/.doing-<单>` + `.exec-lock` 管「文档与单子」;本锁管「生产态」。
# 顺序:**先抢全局执行锁 → 再占本锁**。 # 顺序:**先抢全局执行锁 → 再占本锁**。
# #
# 用法: # 用法:
@@ -205,7 +205,7 @@ def main() -> int:
root = Path(a.locks_root) root = Path(a.locks_root)
exec_lock = root / ".exec-lock" exec_lock = root / ".exec-lock"
elif docs and os.path.isdir(docs): elif docs and os.path.isdir(docs):
base = Path(docs) / "05-交接单" base = Path(docs) / "交接单"
root = base / ".locks" root = base / ".locks"
exec_lock = base / ".exec-lock" exec_lock = base / ".exec-lock"
else: else:
@@ -43,7 +43,7 @@ fi
DOCS_ROOT="$DSH_DOCS_ROOT" DOCS_ROOT="$DSH_DOCS_ROOT"
CODE_ROOT="${DSH_CODE_REPO:-${DOCS_ROOT%/*}}" # ⛔ 不留盘符:代码库=文档库的上一级(纯 bash 展开,⛔ 不调 dirname) CODE_ROOT="${DSH_CODE_REPO:-${DOCS_ROOT%/*}}" # ⛔ 不留盘符:代码库=文档库的上一级(纯 bash 展开,⛔ 不调 dirname)
WS_ROOT="${DSH_WS_ROOT:-$(pwd)}" # ⛔ 不留盘符:默认=当前工作目录(本文件内未被使用) WS_ROOT="${DSH_WS_ROOT:-$(pwd)}" # ⛔ 不留盘符:默认=当前工作目录(本文件内未被使用)
LOCKROOT="$DOCS_ROOT/05-交接单/.locks" LOCKROOT="$DOCS_ROOT/交接单/.locks"
# ── 机制层(⛔ 不受 architecture.md 的 src/** 分层管辖,但**全平台共用**)──────── # ── 机制层(⛔ 不受 architecture.md 的 src/** 分层管辖,但**全平台共用**)────────
# 判据:这些文件任一改动都会改变**所有会话**的行为 ⇒ 与层④同性质(全局影响)。 # 判据:这些文件任一改动都会改变**所有会话**的行为 ⇒ 与层④同性质(全局影响)。
@@ -537,19 +537,19 @@ def check_lockroot_in_pkg():
为什么是红线:`lock/handoff-guard.sh` 与 `lock/preflight-lock.sh` 的 `DOCS_ROOT` 为什么是红线:`lock/handoff-guard.sh` 与 `lock/preflight-lock.sh` 的 `DOCS_ROOT`
原先回落到 `$_SM_HERE/..`(=**技能包自己的 `scripts/`**)⇒ 取不到文档库根时, 原先回落到 `$_SM_HERE/..`(=**技能包自己的 `scripts/`**)⇒ 取不到文档库根时,
锁目录直接长在技能包里(另一环境实测已出现 `<技能包>/scripts/05-交接单/.locks/`)。 锁目录直接长在技能包里(另一环境实测已出现 `<技能包>/scripts/交接单/.locks/`)。
后果两条:① 污染技能包(它是只读发布物);② 换工作区后锁跟着错地方 ⇒ **域锁静默失效**。 后果两条:① 污染技能包(它是只读发布物);② 换工作区后锁跟着错地方 ⇒ **域锁静默失效**。
✅ 现行:两处回落实已改成「取不到就如实报错 + 留痕 + 退出码 2」; ✅ 现行:两处回落实已改成「取不到就如实报错 + 留痕 + 退出码 2」;
本条是**兜底判据** —— 万一将来又有人改回"包内回落",这里要能报出来。 本条是**兜底判据** —— 万一将来又有人改回"包内回落",这里要能报出来。
✅ 变异对照(2026-10-15 实测,⛔ 证明它抓得住,不是恒绿): ✅ 变异对照(2026-10-15 实测,⛔ 证明它抓得住,不是恒绿):
1. `mkdir -p <技能包>/scripts/05-交接单` 1. `mkdir -p <技能包>/scripts/交接单`
2. 跑 `python session-rules-check.py --ws <本区> --all` ⇒ 输出 2. 跑 `python session-rules-check.py --ws <本区> --all` ⇒ 输出
`✗ [A] 锁根落进了技能包 ⇒ … —— <技能包>/scripts/05-交接单` `✗ [A] 锁根落进了技能包 ⇒ … —— <技能包>/scripts/交接单`
3. `rmdir <技能包>/scripts/05-交接单` 复原 ⇒ 回到 `✓ [A] 锁根未落进技能包` 3. `rmdir <技能包>/scripts/交接单` 复原 ⇒ 回到 `✓ [A] 锁根未落进技能包`
⚠️ 为什么不把它做成 `selftest.py` 的自动化用例:那样必须**真在技能包里造目录**, ⚠️ 为什么不把它做成 `selftest.py` 的自动化用例:那样必须**真在技能包里造目录**,
用例中途被打断就会**把一个 `05-交接单` 永久留在包里**(不可逆污染)—— 用例中途被打断就会**把一个 `交接单` 永久留在包里**(不可逆污染)——
风险大于收益。⇒ 保留为上面这段**可手工复现**的步骤。 风险大于收益。⇒ 保留为上面这段**可手工复现**的步骤。
""" """
roots = [] roots = []
@@ -565,7 +565,7 @@ def check_lockroot_in_pkg():
roots.append(r) roots.append(r)
bad = [] bad = []
for r in roots: for r in roots:
for rel in (("05-交接单",), ("scripts", "05-交接单")): for rel in (("交接单",), ("scripts", "交接单")):
if os.path.isdir(os.path.join(r, *rel)): if os.path.isdir(os.path.join(r, *rel)):
bad.append("%s/%s" % (r, "/".join(rel))) bad.append("%s/%s" % (r, "/".join(rel)))
if bad: if bad: