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