项目管理界面:改为跨工作区 + 增删落地(回收站走 SHFileOperationW)

用户口径:「项目管理管的是本机所有工作区的项目 不是单个工作区的,可以
通过项目管理工具 创建新目标(新工作区),管理目标」。

数据面
- ⛔ 不再拉「单一上游看板」(并非每个区都起着 board.py ⇒ 会静默漏区)
- ✅ 直读各区文件:goal.json / tasks.json / collabd-state.json / collabd.config.json
- 「装了机制」判据=有 collabd.config.json 或 goal.json;跳过的也带出来
  (skipped 列表,同族 P0「作用域不许静默排除」)

增删
- 增:POST /api/create → init_workspace.py(骨架+目标登记)
- 删(轻):POST /api/delete{mode:"goal"} ⇒ goal.json 改名归档(可逆)
- 删(重):POST /api/delete{mode:"ws"} ⇒ 送系统回收站(可逆)

回收站:本机 gio/trash-put/trash 全没有,PowerShell Add-Type 被安全策略
拦 ⇒ 改走纯 ctypes 调 Windows Shell API SHFileOperationW。两个坑:
  ① pFrom 必须手搓双 NUL 结尾缓冲(LPCWSTR 会在第一个 NUL 截断)
  ② 判据⛔ 不是 rc==0 —— 实测成功时 rc 恒为 2(Shell 脏状态)⇒ 假报错。
     真判据=原路径不存在 且 fAnyOperationsAborted 为假

其它修复
- init_workspace.py 子进程输出按 GBK 解码 ⇒ 中文乱码 ⇒ 显式 encoding=utf-8
- 起服务只许触发计划任务(裸 & 活不过工具调用边界,被 SIGTERM)

实测:列表 3 个项目 + 12 个未装机制目录;增删两种粒度 HTTP 端到端全通;
      _pmtest-* 五个测试目录已进回收站且可恢复($I 元数据逐条核对)。
This commit is contained in:
admin committed 2026-10-06 09:01:10 +08:00
1 parent 0cf981f419
commit 89f820a0f8
3 files changed
+688 -147

No files matched your search

+62
View File
@@ -369,3 +369,65 @@ vibe-product 上一条会话(10-06 08:15~08:20)已诊断出两条缺陷并**
⚠️ 这属「**影响面超出本平台/跨工作区状态变更**」⇒ **先报用户,⛔ 不擅自动手**。
⚠️ 另:vibe-product 常驻主循环跑的是**旧代码**(09-29 启动,配置改动需重启才生效)——
本次修的是**技能源**,其工作区副本要同步+重启才生效(这一步同样未做)。
## 41、项目管理界面改「跨工作区」+ 增删落地(用户:「项目管理管的是本机所有工作区的项目 不是单个工作区的」)
### 41.1 用户口径(逐字)
> `项目管理管的是本机所有工作区的项目 不是单个工作区的,可以通过项目管理工具 创建新目标(新工作区),管理目标`
外加三条 AskUserQuestion 选定:① 项目范围=**只列「装了机制」的工作区**;② 建新目标=**填表建目录+登记目标**;
③ 写权限=**允许增删,改状态交给机制**(承接上一轮 `增删查(状态不需要改 让会话机制自己处理)`)。
### 41.2 数据面(🔴 关键设计:直读各区文件,⛔ 不拉各区看板)
**真因**:并非每个区都起着 `board.py`(本机 3 个装机制的区里只有 1 个在看板跑)⇒ 拉看板会**静默漏掉没起看板的区**。
✅ 改直读:`<区>/tmp/supervise-inbox/goal.json`(项目)+ `tasks.json`(卡片)+ `collabd-state.json`(队列)
+ `<区>/.workbuddy/collab/collabd.config.json`(判「装没装机制」)。
**「装了机制」判据**:① 有 `collabd.config.json` 或 ② 有 `goal.json`(两条任一)。
🔴 跳过的**也要带出来**(`skipped` 列表)—— 同族 P0「作用域不许静默排除」,⛔ 不静默丢。
### 41.3 新增能力
| 能力 | 实现 | 实测 |
|---|---|---|
| **查** | `GET /api/projects` | 3 个项目(本机协作 / vibe-product / agent-product 空壳)+ 12 个未装机制目录 |
| **增** | `POST /api/create` → `init_workspace.py` | `ok=True rc=0`,骨架+目标登记齐全 |
| **删(轻)** | `POST /api/delete {mode:"goal"}` | `goal.json` → `goal.json.archived-<时间戳>`(可逆) |
| **删(重)** | `POST /api/delete {mode:"ws"}` | **送系统回收站**(可逆) |
### 41.4 🔴🔴 本机回收站:`gio` 不存在,改走 `SHFileOperationW`
**症状**:`mode="ws"` 原写 `gio trash` ⇒ `gio: command not found`(PortableGit 里 `gio`/`trash-put`/`trash` **一个都没有**);
改用 PowerShell `[Microsoft.VisualBasic.FileIO.FileSystem]` ⇒ **被安全策略拦**("Add-Type compiles and loads .NET code at runtime")。
**✅ 正解**:纯 `ctypes` 调 Windows Shell API `SHFileOperationW`(不编译代码、不碰 .NET、不弹 UAC)。
**三个坑**(都踩过):
1. 🔴 `pFrom` **必须手搓双 `\0` 结尾缓冲** —— `LPCWSTR` 会在**第一个 `\0` 截断** ⇒ 字段类型必须写 `c_void_p`,
用 `ctypes.create_unicode_buffer(abspath, len(abspath)+2)` 再 `cast` 塞进去。⛔ 不能直接赋 `str`。
2. 🔴🔴 **判据不是 `rc == 0`**:送回收站**成功**(目录消失、`$I` 元数据可解出原路径),返回值却**恒为 `rc=2`**
(`ERROR_FILE_NOT_FOUND`,Shell 内部探测残留的 `GetLastError`)⇒ 拿 `rc` 当判据会**假报错**
(用户看到"失败"但东西已进回收站)。✅ 真判据只有两条:**① 原路径不存在了 ② `fAnyOperationsAborted` 为假**。
3. ⚠️ 回收站 `$I` 元数据:`v2` 格式、路径是 **UTF-16LE**、起点 **offset 24**(⛔ 不是 26;开头 4 字节是长度前缀)。
验证可恢复性时按此解析。
**实测取证**:`_pmtest-{verify,trash,wsdel,wsdel2,http}` 全部送进 `E:/$RECYCLE.BIN/S-1-5-21-…-500/`,
共 **29+ 条 `$I` 元数据**,逐条解出的原路径与送入路径一致 ⇒ **可恢复**。
### 41.5 另一个已修缺陷:子进程中文乱码
`init_workspace.py` 输出 **UTF-8**(开头 `reconfigure("utf-8")`),而 `subprocess.run` 不给 `encoding`
时按**本机默认编码(中文 Windows=GBK)**解码 ⇒ 中文全变乱码。✅ 修:`encoding="utf-8"` + `PYTHONIOENCODING=utf-8`。
### 41.6 起服务:只许触发计划任务
`(python project-board.py --serve 20100 &)` **活不过工具调用边界**(被 SIGTERM)。
✅ `Start-ScheduledTask -TaskName 'dsh-project-board-keepalive'`(动作走 `pythonw.exe` + `project-board-launch.py`,
⛔ 不直起业务脚本 —— 同族 P0-73/74)。重启后 PID `64312` → `10408`,`/healthz` HTTP 200。
### 41.7 挂账
- 🔴 **vibe-product 是否收工** —— 仍待用户拍板(见 §40.8,未动)。
- 🔴 技能仓历史分叉未决(§36,仍待拍板)。