Files
dsh_ai1net_server/state.py
T
admin ce8e6ceed9 chore(工作区): 纳入版本控制基线(回收 411 MB 过程产物)
回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复:
- 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录)
- tmp/(32.4 M,按接续棒命名的过程临时区)
- .workbuddy/tmp/(39.5 M)
- 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物)
- tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留

入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与
接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、
.workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。

排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、
打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
2026-09-24 07:51:03 +08:00

256 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 <path>`:让**其他工作区**用同一份脚本取**自己那条线**的状态。
# 用法 = 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)