session-mechanism:两个看板定名(会话看板 / 项目看板)+ 包内代码一律相对路径
用户令(两句,同一轮):
①「会话技能中 能用相对路径的都要用相对路径」
②「把会话的 项目目标看板 改成 项目看板,
项目看板 和 会话技能的任务看板 都重新命名 会话看板、项目看板」
## 一、改名:项目管理界面 → 项目看板
* 目录:boards/项目管理界面/ → boards/项目看板/(会话看板不变)
* 包内引用:路径性(3 行)+ 展示名(6 处,含 argparse description、启动横幅、
错误日志前缀、模块 docstring、页面 <title> 与 .brand)
* HTML 标题:<title>项目管理 · 执行任务</title> → <title>项目看板 · 执行任务</title>
## 二、相对路径(新立第 11 条禁令)
判据=「这个根能不能从『我自己的位置』推出来」:
* 能(本包自己的目录)⇒ 从 __file__ 上跳推导
* 不能(工作区根 / 配置目录 / 文档库根)⇒ 读本包 env/roots.env
* 推不出来 ⇒ 如实报错 + 留痕(⛔ 不许静默扫空)
落点(共 4 处代码级硬编码,全部清掉):
* boards/项目看板/project-board.py:新增 _sm_load_roots();
技能根改 HERE.parent.parent;WS_ROOTS 从 DSH_AIROOT 推导;
推不出根时打警告 + 写 logs/project-board.err.log(原为静默扫出 0 个项目)
* scripts/selftest.py:生产区定位改为扫 DSH_AIROOT 下兄弟区(⛔ 不写死区名);
两处测试假路径去掉盘符
⛔ 文档/注释里的绝对路径保持原样(举例、历史取证照写 —— 那是证据)。
## 验收
* 换机场景实测(技能包拷到 /tmp、不带 env/roots.env):
无任何配置 ⇒ 如实报错;只给 DSH_AIROOT ⇒ 正常扫到 4 个项目
* selftest --filter 看板:PASS 110 / FAIL 0(报告型 1 条不计入)
* 项目看板服务:pid 40776 · HTTP 200 · <title>项目看板 · 执行任务</title>
· /api/projects 出数据 · /healthz ok
* py_compile 三份改动文件全过
This commit is contained in:
1 parent
52566c79f9
commit
fe22f4cebe
5 files changed
+121
-19
No files matched your search
+1
-1
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""项目管理界面的**启动器**(计划任务动作指向它,⛔ 不许直起 `project-board.py`)。
|
||||
"""项目看板的**启动器**(计划任务动作指向它,⛔ 不许直起 `project-board.py`)。
|
||||
|
||||
## 为什么必须有这一层(同族 P0-73/74)
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>项目管理 · 执行任务</title>
|
||||
<title>项目看板 · 执行任务</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0f1115; --panel:#171a21; --panel2:#1e222b; --line:#2a2f3a;
|
||||
@@ -249,7 +249,7 @@
|
||||
<body>
|
||||
<header>
|
||||
<div class="hrow">
|
||||
<div class="brand">项目管理</div>
|
||||
<div class="brand">项目看板</div>
|
||||
<select id="psel" aria-label="选择工作区" title="选择工作区"></select>
|
||||
<div class="spacer"></div>
|
||||
<span class="stamp" id="stamp">加载中…</span>
|
||||
+80
-9
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""项目管理界面 —— 管**本机所有工作区**的项目(照 dsh-task-board 的「项目 × 卡片」模式)。
|
||||
"""项目看板 —— 管**本机所有工作区**的项目(照 dsh-task-board 的「项目 × 卡片」模式)。
|
||||
|
||||
## 这是什么(用户原话)
|
||||
|
||||
@@ -64,18 +64,71 @@ from pathlib import Path
|
||||
HERE = Path(__file__).resolve().parent
|
||||
PAGE = HERE / "project-board.html"
|
||||
|
||||
# 🔴 扫哪些根目录找工作区(`;` 分隔;可用环境变量覆盖)
|
||||
WS_ROOTS = [Path(p) for p in (
|
||||
os.environ.get("DSH_PM_BOARD_ROOTS") or "E:/ProgramData/AIProject"
|
||||
).split(";") if p.strip()]
|
||||
|
||||
def _sm_load_roots():
|
||||
"""把本包 `env/roots.env` 注入进程 env(宿主已有的**不动** ⇒ `setdefault`)。
|
||||
|
||||
🔴🔴 2026-10-14(用户令:「会话技能中 能用相对路径的都要用相对路径」):
|
||||
本文件原先把两处根写死成 `E:/ProgramData/...` 绝对路径 ⇒ 换机 / 换用户目录就全错。
|
||||
正解分两半(判据=**这个根能不能从"我自己的位置"推出来**):
|
||||
· **能**(本包自己的目录,如 `scripts/`、`boards/`)⇒ 从 `__file__` 上跳推导,⛔ 不写死;
|
||||
· **不能**(工作区根 —— 技能包在配置目录下,跟工作区没有任何相对关系)
|
||||
⇒ 属"本机各不同的配置" ⇒ 读本包 `env/roots.env`(`install.py` 生成,不入库)。
|
||||
⚠️ 从 `boards/项目看板/` 到包根要上跳**两级**。
|
||||
"""
|
||||
import os as _os
|
||||
_here = _os.path.dirname(_os.path.abspath(__file__))
|
||||
for _up in range(5):
|
||||
_base = _os.path.normpath(_os.path.join(_here, *([".."] * _up)))
|
||||
for _rel in (("env", "roots.env"), ("roots.env",)):
|
||||
_p = _os.path.join(_base, *_rel)
|
||||
if _os.path.isfile(_p):
|
||||
try:
|
||||
with open(_p, encoding="utf-8") as _f:
|
||||
for _ln in _f:
|
||||
_ln = _ln.strip()
|
||||
if _ln and not _ln.startswith("#") and "=" in _ln:
|
||||
_k, _v = _ln.split("=", 1)
|
||||
_os.environ.setdefault(_k.strip(), _v.strip())
|
||||
except Exception:
|
||||
pass
|
||||
return _p
|
||||
return ""
|
||||
|
||||
|
||||
_sm_load_roots()
|
||||
|
||||
# 🔴 扫哪些根目录找工作区(`;` 分隔;可用环境变量覆盖)。
|
||||
# ⚠️ 2026-10-14:默认值原写死 `E:/ProgramData/AIProject` ⇒ 改为**按序推导**(全部相对,⛔ 无盘符):
|
||||
# ① 宿主 env / `env/roots.env` 的 `DSH_AIROOT`(工作区根的父目录 —— 唯一权威来源)
|
||||
# ② `DSH_PM_BOARD_SELF_WS`(本看板服务的那个工作区)的父目录
|
||||
# ③ 都取不到 ⇒ **空**,并由下面的 `_ws_roots()` 在真正要用时**如实报错**,
|
||||
# ⛔ 不做"兜底猜一个盘符"那种事(猜错 ⇒ 扫到别人的工作区,静默出错数据)。
|
||||
def _ws_roots() -> list:
|
||||
raw = (os.environ.get("DSH_PM_BOARD_ROOTS") or "").strip()
|
||||
if not raw:
|
||||
raw = (os.environ.get("DSH_AIROOT") or "").strip()
|
||||
if not raw:
|
||||
_self = (os.environ.get("DSH_PM_BOARD_SELF_WS") or "").strip()
|
||||
if _self:
|
||||
try:
|
||||
raw = str(Path(_self).resolve().parent)
|
||||
except Exception:
|
||||
raw = ""
|
||||
return [Path(p.strip()) for p in raw.split(";") if p.strip()]
|
||||
|
||||
|
||||
WS_ROOTS = _ws_roots()
|
||||
|
||||
# 技能自带的新建入口(⛔ 不自己另写一套建区逻辑 —— 同族"一条规则两份实现"必漂)
|
||||
# ⚠️ 2026-10-14:看板整包搬进 `boards/`(用户令「开个 boards 文件夹放两个看板」)⇒
|
||||
# **看板真身**与**机制脚本**不再同目录,⛔ 别再用一个常量指两处。
|
||||
# · `SKILL_BOARD_DIR` = 会话看板(`board.py::acc_is_pass` 的取用处)
|
||||
# · `SKILL_SCRIPTS` = 机制脚本(`init_workspace.py` 的取用处)
|
||||
_SKILL_ROOT = Path(os.environ.get("DSH_SKILL_ROOT")
|
||||
or r"E:/ProgramData/.workbuddy/skills/session-mechanism")
|
||||
# 🔴 技能根=**从本文件位置上跳两级**(`boards/项目看板/` → `boards/` → 包根)——
|
||||
# 纯相对,⛔ 不再写死盘符(2026-10-14 用户令「能用相对路径的都要用相对路径」)。
|
||||
# 宿主 env `DSH_SKILL_ROOT` 仍优先(部署方显式指定时以它为准)。
|
||||
_SKILL_ROOT = Path(os.environ.get("DSH_SKILL_ROOT") or HERE.parent.parent)
|
||||
SKILL_BOARD_DIR = Path(os.environ.get("DSH_SKILL_BOARD_DIR")
|
||||
or str(_SKILL_ROOT / "boards" / "会话看板"))
|
||||
SKILL_SCRIPTS = Path(os.environ.get("DSH_SKILL_SCRIPTS")
|
||||
@@ -258,6 +311,24 @@ def scan_workspaces() -> list:
|
||||
"""
|
||||
installed, skipped = [], []
|
||||
seen = set()
|
||||
if not WS_ROOTS:
|
||||
# 🔴 2026-10-14:默认值去掉了写死盘符 ⇒ "推不出来"成了一种真实可能
|
||||
# (换机 / 没跑过 `install.py` / `env/roots.env` 丢了)。
|
||||
# ⇒ **如实报错 + 留痕**,⛔ 不许静默扫出"0 个项目"
|
||||
# (那与"本机真的没有工作区"表现完全一样)。
|
||||
_say("⛔ 推不出工作区根 ⇒ 一个区都扫不到。请设 `DSH_AIROOT`(工作区根的父目录),"
|
||||
"或跑一次 `python install.py --apply --workspace <本区>`。")
|
||||
try:
|
||||
_d = _SKILL_ROOT / "logs"
|
||||
_d.mkdir(parents=True, exist_ok=True)
|
||||
with open(_d / "project-board.err.log", "a",
|
||||
encoding="utf-8", errors="replace") as _f:
|
||||
_f.write("[项目看板] ⛔ 推不出工作区根(`DSH_AIROOT` / "
|
||||
"`DSH_PM_BOARD_ROOTS` / `DSH_PM_BOARD_SELF_WS` 全空,"
|
||||
"且 `env/roots.env` 缺 `DSH_AIROOT`)\n")
|
||||
except Exception:
|
||||
pass
|
||||
return installed, skipped
|
||||
for root in WS_ROOTS:
|
||||
if not root.is_dir():
|
||||
continue
|
||||
@@ -717,7 +788,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description="项目管理界面(本机所有工作区 · 增删查)")
|
||||
ap = argparse.ArgumentParser(description="项目看板(本机所有工作区 · 增删查)")
|
||||
ap.add_argument("--serve", type=int, default=0, metavar="PORT",
|
||||
help="起 HTTP 服务(默认不启,只做一次扫描并打印摘要)")
|
||||
ap.add_argument("--once", action="store_true", help="扫一次并打印摘要")
|
||||
@@ -725,7 +796,7 @@ def main() -> int:
|
||||
|
||||
if a.serve:
|
||||
srv = ThreadingHTTPServer(("127.0.0.1", a.serve), Handler)
|
||||
_say("项目管理界面已起:http://127.0.0.1:%d/ (根目录 %s)"
|
||||
_say("项目看板已起:http://127.0.0.1:%d/ (根目录 %s)"
|
||||
% (a.serve, ", ".join(str(r) for r in WS_ROOTS)))
|
||||
try:
|
||||
srv.serve_forever()
|
||||
Reference in new issue
Block a user