Files

163 lines
7.0 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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""apply-reply-rules.py —— 把「回复排版与格式」核心块**配置到目标环境的规则文件**里
🔴 2026-10-06:本脚本**归位到 session-mechanism**(原先只在 agent-operating-rules 里,
而 03 号核心块却自称权威源却指着一个不在本包的注入器 ⇒ 权威声明分裂)。
权威源 = 本包 `references/03-回复排版-核心块.md`(**唯一**)。
它解决什么
──────────
用户 2026-10-02 令(逐字):
「**所有会话中**回复排版和格式要求和规则,也要**整合到会话技能中**,
使用时**配置到对应环境文件中**」
⇒ 三层分工(⛔ 别混):
· **技能**(本脚本所在包 `agent-operating-rules`)=**权威源**,跨工作区、跨机器。
· **环境文件**(`CODEBUDDY.md` / `AGENTS.md`)=该环境的**落地配置**,由本脚本生成。
· **每个会话**=由钩子 `reply-style-guard.py` **每轮**把核心块注入(优先用本环境的落地副本)。
为什么需要"注入"这一步(⛔ 不只是钩子就够)
────────────────────────────────────────────
钩子管的是"**每一轮**紧贴用户消息",而环境文件管的是"**这个环境自己的规则**":
· 换机 / 换工作区时,钩子可能还没装,但规则文件先被读到 ⇒ 规则不能只活在钩子里;
· 环境可能有**本地定稿**(如某工作区把骨架改过)⇒ 落地副本是它自己的那份,钩子优先读它。
安全设计
────────
1. **只动标记块**:只替换 `<!-- REPLY-CORE:BEGIN --> … <!-- REPLY-CORE:END -->` 之间;
块外(人工撰写的解释 / 别的规则)**一字不动**。
2. **默认不建块**:目标里没有标记块时**只报告**(rc=1),要建得显式加 `--init`
(追加到文末,不改既有内容)—— 避免与人工撰写的内容重复成两处。
3. **单向**:核心块内容由**技能**决定,⛔ 不许反过来把环境文件当源。
⇒ 本地确实要改措辞 ⇒ 改技能里那份,再对所有环境重跑本脚本。
4. **回读校验**:写完复读并断言块在、长度变化符合预期;异常 ⇒ 不写盘。
用法
────
<python> apply-reply-rules.py # 默认目标 = 当前目录的 CODEBUDDY.md(只报告)
<python> apply-reply-rules.py --ws <工作区> # 目标 = <工作区>/CODEBUDDY.md
<python> apply-reply-rules.py --target <文件> # 指定环境文件(AGENTS.md 等)
<python> apply-reply-rules.py --check # 只校验:块在不在、是否与技能一致
<python> apply-reply-rules.py --apply # 真写(需先抢锁 —— 环境文件通常属机制层)
<python> apply-reply-rules.py --apply --init # 目标没有标记块时,追加建块
退出码:0 = 一致/成功|1 = 有差异 / 需要人看|2 = 用法或环境异常
"""
import hashlib
import io
import os
import re
import shutil
import sys
import time
HERE = os.path.dirname(os.path.abspath(__file__))
SKILL = os.path.dirname(HERE) # = <技能库>/agent-operating-rules
# 🔴 2026-10-06 本包内版本:权威源就在本包 ⇒ 默认读 `references/03-回复排版-核心块.md`
CORE = os.path.join(SKILL, 'references', '03-回复排版-核心块.md')
BEGIN = '<!-- REPLY-CORE:BEGIN'
END = '<!-- REPLY-CORE:END -->'
BANNER = ('<!-- BEGIN reply-core (generated by session-mechanism/scripts/'
'apply-reply-rules.py · 勿手改块内;要改口径改技能里那份再重跑本脚本) -->')
def rd(p, nl=''):
try:
return io.open(p, encoding='utf-8', newline=nl).read()
except OSError:
return ''
def md5s(s):
return hashlib.md5(s.encode('utf-8')).hexdigest()[:12]
def core_text():
t = rd(CORE)
if not t:
raise SystemExit('ERROR: 缺权威源 %s' % CORE)
i = t.find(BEGIN)
if i < 0:
raise SystemExit('ERROR: 权威源里没有 %s 标记' % BEGIN)
i = t.find('\n', i)
j = t.find(END, i + 1)
return t[i + 1:j].strip()
def block(body, bare=False):
if bare:
return BEGIN + ' (由 agent-operating-rules 技能生成) -->\n' + body + '\n' + END + '\n'
return (BEGIN + ' (由 agent-operating-rules 技能生成 · 与技能内那份同源) -->\n'
+ body + '\n' + END + '\n')
def cur_block(t):
i = t.find(BEGIN)
if i < 0:
return ''
i = t.find('\n', i)
j = t.find(END, i + 1)
if i < 0 or j < 0:
return ''
return t[i + 1:j].strip()
def main(argv):
tgt, apply_, init_, chk = '', '--apply' in argv, '--init' in argv, '--check' in argv
if '--target' in argv:
tgt = argv[argv.index('--target') + 1]
elif '--ws' in argv:
tgt = os.path.join(argv[argv.index('--ws') + 1], 'CODEBUDDY.md')
else:
tgt = os.path.join(os.getcwd(), 'CODEBUDDY.md')
tgt = os.path.normpath(tgt.replace('\\', '/'))
body = core_text()
t = rd(tgt)
print('权威源:%s(核心块 %d 字符 / md5=%s)' % (CORE, len(body), md5s(body)))
print('目标 :%s(%s)' % (tgt, '存在' if t else '**不存在**'))
if not t:
print('⛔ 目标文件不存在 ⇒ 停手(本脚本只在既有规则文件里注入,⛔ 不凭空造规则文件)')
return 2
have = cur_block(t)
if have:
same = have == body
print('块状态:已存在,与技能%s(正文 md5 %s vs %s)'
% ('**一致** ✅' if same else '**不一致** ⚠️', md5s(have), md5s(body)))
if chk:
return 0 if same else 1
if same and not apply_:
print('⇒ 无需改动')
return 0
else:
print('块状态:**没有标记块**')
if chk:
return 1
if not init_:
print('⇒ 默认**不建块**(避免与人工撰写内容重复成两处)。要建:加 --init(追加到文末)')
return 1
if not apply_:
print('⇒ 将写入(--apply 才真写):%s' % ('追加标记块' if not have else '替换块内内容'))
return 0
b = tgt + '.bak-replycore-' + time.strftime('%Y%m%d-%H%M%S')
shutil.copy2(tgt, b)
if have:
new = re.sub(re.escape(BEGIN) + r'.*?' + re.escape(END) + r'\n?',
block(body), t, flags=re.S)
else:
new = t.rstrip('\n') + '\n\n' + BANNER + '\n' + block(body)
io.open(tgt, 'w', encoding='utf-8', newline='').write(new)
back = rd(tgt)
assert cur_block(back) == body, '回读不符 ⇒ 已中止(备份 %s)' % b
print('✓ 已写入并回读校验通过(备份 %s)' % os.path.basename(b))
print(' ⚠️ 环境文件通常属机制层 ⇒ 本轮若没抢到锁,写入动作本身即违规;请用 --check 先看状态。')
return 0
if __name__ == '__main__':
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
raise SystemExit(main(sys.argv[1:]))