#!/usr/bin/env python # -*- coding: utf-8 -*- """DSH 状态快照 —— 让 AI 用「1 次调用」代替「十几轮探索」。 用法: python state.py # 本地快照(快,无网络) python state.py --online # 追加:与 Gitea 远端比对基线(需 SSH) 设计原则(2026-09-16 立): ① 只输出「能决定下一步」的内容,目标 ≤ 22 行。**大输出本身就是成本。** ② 只读,永不写。执行它不需要抢锁。 ③ 每条状态带「判据」——让读者不必再自己取证。 ④ 默认不联网、不 SSH —— 那些是慢操作,要就显式加 --online。 """ import io, os, re, subprocess, sys, datetime REPO = r"D:/github/dsh_shenxian" DOCS = os.path.join(REPO, "dsh-server-docs") WS = r"E:/ProgramData/AIProject/aliyun-dsh-server" # 🔑 2026-09-21 加 `--ws `:让**其他工作区**用同一份脚本取**自己那条线**的状态。 # 用法 = python "<本脚本绝对路径>" --ws "<自己的工作区>"(本工作区无脚本时不必复制第二份)。 # ⛔ 跨工作区调用**不改变 cwds** —— 接续任务一律建在**自己的工作区**(归属三律,见技能 §0.6)。 _WS_CUSTOM = False for _i, _a in enumerate(sys.argv): if _a == "--ws" and _i + 1 < len(sys.argv): WS = sys.argv[_i + 1].replace("\\", "/").rstrip("/") _WS_CUSTOM = True LOGDIR = os.path.join(WS, ".workbuddy", "memory") def _norm(p): return os.path.normcase(os.path.normpath(p.replace("\\", "/"))) def _owner_ws(path): """读入口文件头部的**工作区归属声明**(2026-09-21 加,治「入口落错家」)。 规范写法(放在文件前 1.2 KB 内):`> 🔴 **工作区**:<该线自己的工作区绝对路径>` 返回 "" = 未声明 ⇒ 按**本工作区**处理(兼容旧文件,不报错)。 ⛔ 别的工作区复制来的副本会被识别成「外来线」并排除出 §2 / 口令 —— 否则本工作区会把**别线**当成自己的线,新会话被引到别人的待办上(2026-09-21 实测)。 """ try: head = io.open(path, encoding="utf-8", errors="replace", newline="").read()[:1200] except Exception: return "" m = re.search(r"(?:^|[\s>|*])工作区\**\s*[::]\s*" r"([A-Za-z]:[\\/][^\s|,,()()【】\[\]`]*)", head) return m.group(1).rstrip("/\\") if m else "" def _list_entries(): """全部「接续入口_*.md」,按 mtime 降序 —— ⛔ 不写死文件名(2026-09-16 晚加)。 多线并行时**只取最新一份**会让新会话走错线(这是他线的待办), 所以这里**列全**,由新会话按自己那份接续包 / 自动化 prompt 里的线名认领。 ⚠️ 2026-09-21 加归属校验:声明的工作区 ≠ 本工作区 ⇒ 标 `alien`(外来副本,应迁回+删)。 返回 4 元组 `(path, mtime, owner_ws, alien)`。 """ try: c = [os.path.join(WS, f) for f in os.listdir(WS) if f.startswith("接续入口_") and f.endswith(".md")] except Exception: c = [] c.sort(key=os.path.getmtime, reverse=True) out = [] for p in c: ow = _owner_ws(p) out.append((p, datetime.datetime.fromtimestamp(os.path.getmtime(p)).strftime("%m-%d %H:%M"), ow, bool(ow) and _norm(ow) != _norm(WS))) return out ENTRIES = _list_entries() LOCAL = [e for e in ENTRIES if not e[3]] # 属于本工作区的线(§2 展开 / 口令只用这些) ALIEN = [e for e in ENTRIES if e[3]] # 别的工作区的入口副本(应迁移 + 删除) ENTRY = ((LOCAL or ENTRIES)[0][0] if ENTRIES else os.path.join(WS, "接续入口_覆盖网络线_20260916.md")) LOCK = os.path.join(DOCS, "交接单", ".exec-lock") ONLINE = "--online" in sys.argv def sh(cmd, cwd=None, timeout=25): try: p = subprocess.run(["bash", "-lc", cmd], cwd=cwd, capture_output=True, timeout=timeout) return (p.stdout or b"").decode("utf-8", "replace").strip() except Exception as e: return "!%s" % type(e).__name__ def read(p): try: return io.open(p, encoding="utf-8", errors="replace", newline="").read() except Exception: return "" def line(k, v): print(" %-9s %s" % (k, v)) print("═══ DSH 状态快照 · %s ═══" % datetime.datetime.now().strftime("%Y-%m-%d %H:%M")) print() # ── 1. 执行锁(域锁 + 旧全局锁)──────────────────────────────── # ⚠️ `.exec-lock` 是**目录**(`handoff-guard.sh --claim-exec` 用 `mkdir` 原子创建,OWNER 写在里面), # **不是文件**。旧版按文件读 ⇒ 永远读空 ⇒ **恒报「空闲」** —— 也就是每个新会话读到的**第一个 # 信号就是错的**(2026-09-16 实测:本会话正持锁时 state.py 仍报空闲)。两种形态都兼容: # 目录读 `OWNER`,历史遗留的普通文件照读。 # # 2026-09-22 加:**域锁**(`交接单/.locks/*`)—— 不重叠的域可并行 ⇒ 开机第 0 步必须能看见 # 「谁占了哪些域」,否则新会话会误判"不能开工"而错失并行机会。 LOCKSROOT = os.path.join(DOCS, "交接单", ".locks") SID = os.environ.get("CODEBUDDY_SESSION_ID", "") s = "" if os.path.isdir(LOCK): s = read(os.path.join(LOCK, "OWNER")).strip() elif os.path.exists(LOCK): s = read(LOCK).strip() dom_locks = [] if os.path.isdir(LOCKSROOT): for _n in sorted(os.listdir(LOCKSROOT)): _d = os.path.join(LOCKSROOT, _n) if not os.path.isdir(_d): continue _own = read(os.path.join(_d, "OWNER")).strip() _who = (_own.splitlines() or [""])[0] _doms = " ".join(read(os.path.join(_d, "DOMAINS")).split()) if _n == ".publish": dom_locks.append(("发布锁", _who, "commit/push/scp 期间不可并行")) elif _n == ".migrations": for _m in sorted(os.listdir(_d)) if os.path.isdir(_d) else []: _md = os.path.join(_d, _m) if os.path.isdir(_md): dom_locks.append(("骨架", read(os.path.join(_md, "OWNER")).splitlines()[0] if read(os.path.join(_md, "OWNER")).strip() else "?", _m)) elif _n.startswith("."): continue else: _mine = bool(SID) and SID in _own dom_locks.append(("域锁" + ("(我)" if _mine else ""), _who, _doms)) if not s and not dom_locks: line("[锁]", "空闲 ✅ —— 「空闲」≠「可以开工」:先 --claim-exec 抢锁") elif dom_locks and not s: # 纯域锁形态(目标形态):并行的关键信号 —— 不重叠即可开工 line("[锁]", "🟢 域锁形态(%d 把)—— **不重叠的域可以并行**" % len(dom_locks)) for _k, _who, _x in dom_locks[:5]: line("", " · [%s] %s | %s" % (_k, _who, _x[:70])) line("", "⇒ 你要的域若与上面不重叠 ⇒ 可直接抢:--claim-exec \"<会话名>\" --domains <域...>") line("", "⇒ 重叠 ⇒ 停手或改做不重叠的域(⛔ 不得删锁,R9)") else: ls = [x.strip() for x in s.splitlines() if x.strip()] line("[锁]", "🔴 旧全局锁被占 —— %s" % (" / ".join(ls[:3]) if ls else s)) line("", "⇒ 该会话未升级到域锁 ⇒ 全局独占;等它 --release-exec(⛔ 不得删锁,R9)") for _k, _who, _x in dom_locks[:4]: line("", " · [%s] %s | %s" % (_k, _who, _x[:70])) # ── 2. git 基线 ──────────────────────────────────────────────── head = sh("git rev-parse --short HEAD", REPO) br = sh("git rev-parse --abbrev-ref HEAD", REPO) porcelain = sh("git status --porcelain", REPO) nmod = len([l for l in porcelain.splitlines() if l.strip()]) line("[git]", "分支 %s · HEAD %s" % (br or "?", head or "?")) line("", "工作区 %s" % ("干净 ✅" if nmod == 0 else "🟡 %d 处改动(未 commit)" % nmod)) if _WS_CUSTOM: line("", "⚠️ 本次 `--ws` 指向别的工作区 ⇒ [git] 段是平台代码仓 `%s` 的状态,与该工作区无关" % REPO) for l in porcelain.splitlines()[:8]: print(" %s" % l[:96]) if nmod > 8: print(" … 余 %d 处" % (nmod - 8)) # ── 3. 远端 / 镜像(--online)────────────────────────────────── if ONLINE: rem = sh('GIT_SSH_COMMAND="ssh -o BatchMode=yes -o ConnectTimeout=12 " ' 'git ls-remote origin refs/heads/master', REPO, 40) m = re.search(r"([0-9a-f]{7,40})\s+refs/heads/master", rem) if m: rsha = m.group(1)[:7] anc = sh("git merge-base --is-ancestor %s HEAD && echo YES || echo NO" % rsha, REPO) line("[远端]", "master=%s %s" % (rsha, "✅ 已同步" if rsha == head else ("可快进" if anc == "YES" else "🔴 已分叉"))) else: line("[远端]", "⚠️ ls-remote 取不到(本机 refs/remotes 写不进是已知的)") # ── 4. 接续入口 §2 待办(⚠️ 多线并行:只走你自己那条)────────── if len(LOCAL) > 1: line("[入口]", "⚠️ 检测到 **%d 条工作线** —— ⛔ 只走**你自己那条**" "(以你手上那份接续包 / 自动化 prompt 里的线名为准),别串线" % len(LOCAL)) for p0, mt0, ow0, al0 in ENTRIES: line("[入口]", "%s(mtime %s)%s" % ( os.path.basename(p0), mt0, " 🔴 **外来线**(属于 %s ⇒ 迁回该工作区并删本副本)" % ow0 if al0 else (" ← 最新" if p0 == ENTRY else ""))) if ALIEN: line("", "🔴 上面 %d 条是**别的工作区的入口副本**(归属三律:入口只允许一份、在本工作区)" "⇒ 内容迁回 %s,删掉这里的副本" % (len(ALIEN), ALIEN[0][2])) t = read(ENTRY) if t: print(" ——下面只展开最新的那条(%s)§2——" % os.path.basename(ENTRY)) sec = re.search(r"##\s*2\.[^\n]*\n(.*?)(?=\n##\s|\Z)", t, re.S) body = sec.group(1) if sec else "" items = re.findall(r"^\s*(?:\d+[.)]|[-*])\s+(.{6,120})", body, re.M) for i, it in enumerate(items[:6], 1): print(" %d) %s" % (i, it.replace("**", "").strip()[:104])) if not items: line("", "(§2 未解析出条目 —— 请直接读该文件)") # ── 5. 上次收口点 ───────────────────────────────────────────── today = os.path.join(LOGDIR, datetime.date.today().isoformat() + ".md") lg = read(today) if lg: # 优先找「接续点 / 收口」章节,其次取最后一个章节 best = None for m in re.finditer(r"^#{2,3}\s*(.+)$", lg, re.M): if ("接续" in m.group(1)) or ("收口" in m.group(1)): best = m if best is None: hs = list(re.finditer(r"^#{2,3}\s*(.+)$", lg, re.M)) best = hs[-1] if hs else None start = best.start() if best else 0 title = best.group(1).strip() if best else "(无章节)" nxt = re.search(r"^#{2,3}\s", lg[start + 4:], re.M) sec = lg[start:start + 4 + nxt.start()] if nxt else lg[start:] line("[收口]", "%s |今日日志 %d 字符" % (title[:64], len(lg))) # ⚠️ 日志是**多会话共用**的:多线并行时,最后写的那个不一定是「你那条线」的 _ti = [m.group(1).strip() for m in re.finditer(r"^#{2,3}\s*(.+)$", lg, re.M) if ("接续" in m.group(1)) or ("收口" in m.group(1))] if len(_ti) > 1: print(" ⚠️ 今日日志里有 %d 个接续点/收口章节(多线共用同一份日志)" "⇒ 只认**你那条线**的那一个:" % len(_ti)) for _x in _ti[-3:]: print(" · %s" % _x[:96]) print(" ⚠️ 下面是**日志原文摘录**,可能含已被后续推翻的结论" "(实测教训:曾摘出「定时自动化仍在烧钱」「rawUsage 为空」两条已勘误说法)") print(" ⇒ 当前事实以 `.workbuddy/memory/MEMORY.md` 状态层为准;该做什么看上面 [入口] 段。") keys = [l.strip() for l in sec.split("\n") if re.match(r"^\s*[-*]\s*\*\*", l) or re.match(r"^\s*\d+[.)]\s*\*\*", l)] for b in (keys[:5] or [l.strip() for l in sec.split("\n") if l.strip().startswith("-")][:3]): print(" %s" % b.replace("**", "").strip()[:104]) if best and best.group(1) != title: pass else: line("[收口]", "⚠️ 今日日志不存在:%s" % today) # ── 6. 下一步 ───────────────────────────────────────────────── print() print(" ⇒ 下一步:按**你那条线**(见下面「口令」)的 [入口] §2 第 1 条开工;" "⛔ 不要再 Glob/Grep 全库探索(省积分第一纪律)") print(" ⇒ 口令(复制给新会话):跑 `state.py`,按 **%s** 那段 §2 第 1 条开工" % os.path.basename(ENTRY).replace("接续入口_", "").replace(".md", "") .rsplit("_", 1)[0]) print(" ⇒ 动手前:bash \"%s/scripts/handoff-guard.sh\" --claim-exec \"<你的会话名>\"" % DOCS)