Files
admin 19101acd65 init: workbuddy_skills 重建,仅收录 session-mechanism
- 按用户指示清空原有 25 技能内容,只提交 session-mechanism(57 文件)
- 附 .gitignore(产物 + 本机凭据)
- 令牌明文已脱敏(历史 .neodata_token 与 pitfalls 引用均不入库)
- 本提交为孤儿提交(父提交为空),历史自此重新开始
2026-10-05 14:13:24 +08:00

114 lines
5.2 KiB
Python
Raw Permalink 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.
# -*- coding: utf-8 -*-
"""deploy_code —— 把**程序文件**复制到某个工作区,让那个工作区**跑自己的一份**。
🔴 为什么需要它(用户 2026-10-03 定案):
「**跨工作区使用会话协作技能,除了看板共用,其余都是独立的,包括程序和相关文件**」
⇒ 改口径之前,**所有工作区跑的都是技能目录里同一份 `collabd.py`**(靠 `COLLABD_CONFIG` 区分工作区)。
⚠️ 那种形态下:改一份代码 ⇒ **所有工作区同时变了**;某个区的代码坏了 ⇒ 别的区一起坏。
⇒ 现在改成:**技能目录=源(唯一真身)**,每个工作区 `.workbuddy/collab/` 下有**自己的副本**。
✅ 判据(能不能证明"真的独立"):
① 副本文件在位且 **md5 与源一致**(本脚本打印);
② 心跳里的 **`argv0`** 指向**该工作区的副本路径**(由 `collabd.py` 写进心跳)——
⛔ 只看"文件在"不够:进程可能还在跑旧的那份(改完不重启=没生效,P0-17 同族)。
用法:
python deploy_code.py --ws E:/ProgramData/AIProject/<工作区> # 部署默认清单
python deploy_code.py --ws <工作区> --dry-run # 只报要做什么
python deploy_code.py --ws <工作区> --only collabd.py # 只部一个
⚠️ **改完代码要重跑本命令**(各区副本不会自动跟着变)——
这是"独立"的代价:⛔ 一处改动要分发 N 处 ⇒ 改完请顺手跑一遍,别等某个区行为不对再来查。
"""
from __future__ import annotations
import argparse
import hashlib
import os
import shutil
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
# 🔴 各工作区默认需要的**独立程序**(⛔ 不含 `board.py` —— 看板是**共用一份**的)
# 🔴🔴 2026-10-05 补:**管理入口也必须进清单** —— 原先只有 `collabd.py`/`goalctl.py`,
# 结果 `collabctl.py`(用户双击 `.bat` 调的那个)**从不被分发** ⇒ 各区副本停留在旧版
# ⇒ 「看着改了、其实各区没吃到」= P0-57 同族再次发生(本轮实测:新副本 23176 B/旧副本 16982 B)。
# ⛔ 判据:**凡是被计划任务、`.bat`、hook 直接执行的本包脚本,都在此清单内**。
DEFAULT_FILES = [
"collabd.py", # 常驻本体(各区独立跑)
"goalctl.py", # 目标控制
"collabctl.py", # 🔴 管理入口(一键开关 .bat 调它)
"guard.py", # 守护判据
"session-rules-check.py", # 命名/角色一致性闸门(hook 调)
"init_workspace.py", # 工作区初始化
"supervise-launch.py", # 🔴 常驻启动器(计划任务载体指向它 ⇒ 进程内设 CODEBUDDY_CONFIG_DIR)
]
def md5(p: Path) -> str:
try:
return hashlib.md5(p.read_bytes()).hexdigest()
except Exception:
return "?"
def main() -> int:
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
ap = argparse.ArgumentParser()
ap.add_argument("--ws", required=True, help="工作区绝对路径")
ap.add_argument("--only", default="", help="只部这一个文件名(可多次用逗号分隔)")
ap.add_argument("--dry-run", action="store_true")
a = ap.parse_args()
ws = Path(a.ws).resolve()
if not ws.is_dir():
print("✗ 工作区不存在:%s" % ws)
return 1
dst_dir = ws / ".workbuddy" / "collab"
files = [x.strip() for x in (a.only.split(",") if a.only else DEFAULT_FILES) if x.strip()]
print("工作区:%s" % ws)
print("副本目录:%s" % dst_dir)
bad = 0
for name in files:
src = HERE / name
if not src.is_file():
print(" ✗ 源里没有 %s" % name)
bad += 1
continue
dst = dst_dir / name
if a.dry_run:
print(" · [试运行] %s → %s(源 md5=%s)" % (name, dst, md5(src)[:8]))
continue
dst_dir.mkdir(parents=True, exist_ok=True)
# 🔴 覆盖前先留一份带时间戳的备份(⛔ 免得新版在这台机器上跑不通时没退路)
if dst.is_file() and md5(dst) != md5(src):
bak = dst.with_suffix(dst.suffix + ".bak-%s" % time_stamp())
try:
shutil.copy2(str(dst), str(bak))
print(" · 旧副本已备份:%s" % bak.name)
except Exception as e:
print(" ⚠ 备份失败(继续):%r" % e)
shutil.copy2(str(src), str(dst))
same = md5(src) == md5(dst)
print(" %s %s md5=%s%s" % ("✓" if same else "✗", name, md5(dst)[:8],
"" if same else "(⚠ 与源不一致)"))
if not same:
bad += 1
if not a.dry_run:
print("\n%s 下一步:让该工作区的常驻程序**重启**才会用上副本"
"(⛔ 改文件不重启=没生效)——判据=心跳里的 `argv0` 指向副本路径。" % ws.name)
return 0 if bad == 0 else 1
def time_stamp() -> str:
import time
return time.strftime("%Y%m%d-%H%M%S")
if __name__ == "__main__":
sys.exit(main())