Files
workbuddy_skills/dsh-local-env/references/dsh-env-bootstrap/resident-rules.py
T
admin e03465c398 按用户令提交:把此前未纳管的 9 个技能目录一并入库
用户令逐字:「E:/ProgramData/.workbuddy/skills  提交仓库是指的这里」——
即本目录就是仓库(2026-10-07 已在本目录建仓,见当日日志 §22),本轮把余下未纳管的 9 个技能一并提交。

本次入库(9 个技能,46 个文件):
1、`AI HOT`
2、`draw-ui`
3、`dsh-diagnose`
4、`dsh-knowledge`
5、`dsh-local-env`
6、`dsh-opensource-release`
7、`dsh-workflow`
8、`oil-motion`
9、`skills-security-check`

提交前核对:
· **凭据类扫描**(`*.env` / `*token*` / `*.key` / `*secret*` / `*.pem`)⇒ **零命中** ✓;
· 体积合计约 20 MB(`draw-ui` 12M + `oil-motion` 6.5M 是大头,形态为配图/素材 ——
  仓库 `.gitignore` 里明写「`assets/*.png` 是内容不是产物」⇒ 属刻意入库);
· 运行产物仍按既定规则排除(`__pycache__` / `logs/` / `tmp/` / `.venv/` / `*.egg-info` / `uv.lock` / `.workbuddy/`)。
2026-10-08 22:29:08 +08:00

245 lines
12 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 python3
# -*- coding: utf-8 -*-
"""resident-rules.py —— 「常驻规则」的可移植化:快照 / 校验 / 注入 / 环境自检
为什么需要它
────────────
项目常驻规则写在**工作区**的 `CODEBUDDY.md`(每会话自动注入 ⇒ 动作前必然生效)。
但它是**工作区文件**:换电脑 / 换工作区路径 ⇒ 规则直接消失,而"技能"才是**可移植层**。
本脚本 + 同目录 `references/常驻规则-快照.md` 把两者接起来:
CODEBUDDY.md(工作区·权威·动作前生效)
│ --snapshot(抽取 §1/§3/§4/§5/§6/§8 全文)
▼
references/常驻规则-快照.md(技能内·可移植·随技能走)
│ --check / --inject(到新环境的 CODEBUDDY.md)
▼
新环境的 CODEBUDDY.md
设计红线(避免造"第二真相源")
──────────────────────────────
1. **权威方向单向**:`CODEBUDDY.md` 是权威,快照是**它的副本**(`--snapshot` 生成,不手改)。
2. **默认只报不改**:`--check` 只报告差异(退出码 1)—— 与文档库「体检只报不改」同规。
3. **注入只动标记块**:`--inject` 只替换 `<!-- BEGIN resident-rules … --> … <!-- END -->` 之间的内容;
首次注入**不自动建块**(除非 `--init`),避免与人工撰写的内容重复成两处。
4. **区分环境无关 / 环境相关**:环境相关项(绝对路径 / IP / hooks 路径 / 工具位置)
注入后**必须按新环境核对** —— 脚本给「待核清单」,不假装能自动配好。
用法
────
python3 resident-rules.py --snapshot # 由 CODEBUDDY.md 重生成技能内快照
python3 resident-rules.py --check [--goal <CODEBUDDY.md>] # 校验目标环境(默认只报,rc=1 = 有差异)
python3 resident-rules.py --env-check # 环境自检:路径 / hooks / 锁脚本 / 工作区
python3 resident-rules.py --inject [--goal <CODEBUDDY.md>] [--init] # 注入(需已存在标记块)
退出码:0 = 一致/成功;1 = 有差异(未改任何文件);2 = 用法/环境异常
"""
import io
import json
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
# 🔴 2026-10-02 修路径推导(原 `SKILL = dirname(HERE)` 少算一层 ⇒ **快照一直写错目录**):
# 脚本实际位于 `<dsh-local-env>/references/dsh-env-bootstrap/`(`dsh-env-bootstrap` 已并入本包)
# ⇒ 原写法把 `SKILL` 算成 `<dsh-local-env>/references`,快照被写到
# `<...>/references/references/常驻规则-快照.md`(**没人读的幽灵路径**),
# 而真正的快照(脚本同目录那份)**从 2026-09-28 起再没更新过** ——
# 表现为「脚本报 ✓ 已生成」,但 CODEBUDDY.md 新加的 §📐 回复排版**进不去快照**(静默失效族)。
SKILL = os.path.dirname(os.path.dirname(HERE)) # 包根 = <dsh-local-env>
SNAP = os.path.join(HERE, '常驻规则-快照.md') # 与脚本同目录(快照真实所在)
DEFAULT_GOAL = r'E:/ProgramData/AIProject/ai1net-dsh-server/CODEBUDDY.md'
# 要随技能走的章节("动作前必须生效"的那些 + 事故级事实)
SECTIONS = ['## 1. 提问判据', '## 3. 红线', '## 4. 提交边界', '## 5. 规划与执行分离',
'## 6. 并发纪律', '## 8. ']
# ★骨架锚点:这几条丢了就等于规则没了 —— 校验时逐条查存在性(短语取自权威文件)
ANCHORS = [
# 🔴 2026-10-02 校正探针措辞(原 4 条**逐字探针**与 CODEBUDDY.md 现行措辞失配 ⇒ 每次 --check
# 都报 4 处假缺失:A2 / R7-边界 / U27 / L1)。真因=CODEBUDDY.md 在 10-01 被**压缩改写**
# (同一语义换了写法),而探针仍按旧版逐字比 ⇒ **假警报**:会让人以为"规则丢了"、
# 跑去"补"一遍 ⇒ 造出重复的第二份规则。探针**仍是逐字**(保持"能验原文还在"的价值),
# 只是跟上现行措辞;⛔ 不要改成宽松模糊匹配(那会让它恒真 ⇒ 变成空判据)。
('A1', '只问「超过现有判断方法边界」的问题'),
('A2', '禁用征询句收尾'), # 原「禁止用…」→ 现行 CODEBUDDY.md §1 用「禁用」
('A3', '四件或七件套必跑', '占位'), # 由下方 canary 覆盖
('R7', '禁未经确认的批量 / 全仓写入'),
('R7-边界', 'R7 只管「不是我的 lane」'), # 原「R7 只适用于…」→ 现行 §红线表用「只管」
('R9', '绝对禁止「人工删锁 / 接管」'),
('R11', '只做正向迭代'),
('U27', '要解决问题,不将就妥协'), # 原「目标不打折,路径取最小代价」= U27 的上游原话,
# CODEBUDDY.md 里落的是这句(同一判据的现行表述)
('L1', '抢到之前不要动文件'), # 原「先抢全局执行锁」→ 现行 §动作前三条的逐字
('L2', '锁的生命周期 = 任务的生命周期'),
# 🔴 2026-10-02 加:回复排版「强遵循」探针(用户令「所有会话…整合到会话技能中」)
('F1', '强遵循'),
('F2', '已完成的大类放最前'),
('F3', '**表格** / **长散文** / **碎标签堆叠**'),
]
MARK_BEGIN = '<!-- BEGIN resident-rules (generated by skills/dsh-env-bootstrap · 勿手改块内) -->'
MARK_END = '<!-- END resident-rules -->'
# 环境相关项:注入/换机后**必须按新环境核对**(不是"自动配好")
ENV_ITEMS = [
('工作区根', r'E:/ProgramData/AIProject/ai1net-dsh-server'),
('文档库', r'D:/github/dsh_shenxian/dsh-server-docs'),
('代码仓', r'D:/github/dsh_shenxian'),
('备份目录', r'/opt/dsh/backups'),
('服务器', 'bt-server(47.77.182.89,SSH **22**;别名里的 32022 已失效)'),
]
def rd(p):
try:
return io.open(p, encoding='utf-8', newline='').read()
except OSError:
return ''
def section_of(text, head):
"""取 `head` 章节到下一个同级 `## ` 之间的内容(含标题行)。"""
i = text.index(head)
m = re.search(r'^## ', text[i + len(head):], re.M)
return text[i:i + len(head) + (m.start() if m else len(text) - i - len(head))].rstrip() + '\n'
def snapshot(src):
t = rd(src)
if not t:
raise SystemExit('ERROR: 读不到源文件 %s' % src)
parts = []
for h in SECTIONS:
if h == '## 3. 红线':
h = '## 3. 红线 R1' # 实际标题含编号,用前缀匹配
i = t.find(h)
if i < 0:
continue
j = t.find('\n## 4.', i)
parts.append(t[i:j if j > 0 else len(t)].rstrip() + '\n')
continue
if t.find(h) < 0:
parts.append('<!-- 源文件里没有 `%s` 章节 -->\n' % h)
continue
parts.append(section_of(t, h))
body = '\n---\n\n'.join(parts)
head = ('# 常驻规则 · 可移植快照(由 `scripts/resident-rules.py --snapshot` 生成,勿手改)\n\n'
'> **这是「工作区 `CODEBUDDY.md` 关键章节」的副本**,随技能走 ⇒ 换电脑 / 换工作区也能带走。\n'
'> ⚠️ **权威方向单向**:`CODEBUDDY.md` 是权威,本文件是它的副本(重生成用 `--snapshot`)。\n'
'> ⚠️ **环境相关项**(绝对路径 / IP / hooks 路径 / 工具位置)注入后**必须按新环境核对**:见文末「待核清单」。\n\n'
'## 骨架锚点(校验用;丢了就等于规则没了)\n\n')
anch = '\n'.join('- `%s` — %s' % (k, v) for k, v, *_ in
[(a[0], a[1]) for a in ANCHORS]) + '\n'
tail = ('\n---\n\n## 待核清单(换环境后逐项核对,脚本不代改)\n\n'
+ '\n'.join('- [ ] **%s** %s' % (k, v) for k, v in ENV_ITEMS) + '\n')
out = head + anch + '\n---\n\n' + body + tail
os.makedirs(os.path.dirname(SNAP), exist_ok=True)
io.open(SNAP, 'w', encoding='utf-8', newline='\n').write(out)
return len(out), len(parts)
def check(goal):
t = rd(goal)
if not t:
print('ERROR: 读不到目标 %s' % goal)
return 2
snap = rd(SNAP)
if not snap:
print('ERROR: 缺少技能内快照(先跑 --snapshot)')
return 2
print('=== 常驻规则校验|目标 = %s ===' % goal)
bad = 0
for a in ANCHORS:
k, phrase = a[0], a[1]
if phrase.startswith('四件或七件套'):
ok = ('七件套' in t) or ('四件套' in t)
else:
ok = phrase in t
print(' %-10s %s %s' % (k, '✓' if ok else '✗ 缺失', phrase[:40]))
bad += 0 if ok else 1
sec_ok = sum(1 for h in ('## 1. 提问判据', '## 3. 红线', '## 4. 提交边界',
'## 5. 规划与执行分离', '## 6. 并发纪律') if h in t)
print(' 章节存在性:%d/5' % sec_ok)
bad += 5 - sec_ok
print('结论:%s' % ('✅ 关键规则齐备' if bad == 0 else '⚠️ 有 %d 处缺失/漂移 ⇒ 用 --inject(或人工补齐)' % bad))
return 1 if bad else 0
def env_check():
print('=== 环境自检(换机后逐项确认)===')
bad = 0
for k, v in ENV_ITEMS:
if k in ('服务器', '备份目录'):
print(' %-8s %-52s (需 ssh 侧核对)' % (k, v))
continue
ex = os.path.exists(v)
print(' %-8s %-52s %s' % (k, v, '✓ 存在' if ex else '✗ 不存在'))
bad += 0 if ex else 1
# hooks 里的绝对路径(工作区搬迁后最常见的坑)
st = r'E:/ProgramData/.workbuddy/settings.json'
if not os.path.exists(st):
st = os.path.expanduser('~/.workbuddy/settings.json')
try:
h = json.loads(rd(st)).get('hooks', {})
except Exception:
h = {}
cmds = [x.get('command', '') for v in h.values() for it in v for x in it.get('hooks', [])
if x.get('type') == 'command']
print(' hooks 命令 %d 条:' % len(cmds))
for c in cmds:
paths = re.findall(r'[A-Za-z]:/[^\s"]+?\.(?:py|sh|exe)', c)
miss = [q for q in paths if not os.path.exists(q.replace('/', os.sep))]
flag = '✗ 路径失效' if miss else '✓'
print(' %s %s' % (flag, c[:100]))
bad += 1 if miss else 0
return 1 if bad else 0
def inject(goal, init=False):
t = rd(goal)
if not t:
print('ERROR: 读不到目标 %s' % goal)
return 2
snap = rd(SNAP)
if not snap:
print('ERROR: 缺少技能内快照(先跑 --snapshot)')
return 2
body = snap.split('## 待核清单', 1)[0]
block = MARK_BEGIN + '\n' + body.strip() + '\n' + MARK_END + '\n'
if MARK_BEGIN in t:
new = re.sub(re.escape(MARK_BEGIN) + r'.*?' + re.escape(MARK_END) + r'\n?',
block, t, flags=re.S)
io.open(goal, 'w', encoding='utf-8', newline='').write(new)
print('✓ 已替换标记块内内容(块外人工内容未动)')
return 0
if not init:
print('⚠️ 目标里没有标记块 ⇒ **不自动注入**(避免与人工撰写的内容重复成两处)。\n'
' 要建立标记块并注入:加 --init(会**追加**到目标文末,不动既有内容)')
return 1
io.open(goal, 'w', encoding='utf-8', newline='').write(t.rstrip('\n') + '\n\n' + block)
print('✓ 已追加标记块(既有内容未动);建议随后人工去重并 `--check`')
return 0
def main(argv):
goal = DEFAULT_GOAL
if '--goal' in argv:
goal = argv[argv.index('--goal') + 1]
if '--snapshot' in argv:
n, k = snapshot(goal)
print('✓ 已由 %s 生成快照:%d 字符 / %d 个章节 → %s' % (goal, n, k, os.path.relpath(SNAP, SKILL)))
return 0
if '--env-check' in argv:
return env_check()
if '--inject' in argv:
return inject(goal, init=('--init' in argv))
if '--check' in argv:
return check(goal)
print(__doc__)
return 2
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))