zhuyuan-ice-gl-zy001/BRIDGE/tools/zy-longtable-guard.mjs

338 lines
19 KiB
JavaScript
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 node
// zy-longtable-guard.mjs · 长桌守卫 v1.0(2026-09-01 冰朔面谕立)
//
// 冰朔原话设计(瞄点=本轮自然语言,TCS-RECEIPT-LONGTABLE-GUARD-20260901.tcs 存原文):
// ① 钩子只在正式开发+注册开发编号后启用;闲聊对话不出现。
// ② 桌子(上下文)用一半 → 触发第一次:拦截执行,强制人格体把当前开发
// 整理成 HLDP 结构记忆,写进钩子给出的编号文件夹;写前必须重读冰朔
// 自然语言瞄点,对照"执行了多少、还差多少"。
// ③ 桌子剩约10% → 触发第二次:把后半段补进前半段 HLDP 记忆,标任务状态:
// 完成→归档标识;未完成→WIP 接力标识。第二次写入=本实例任务结束、交接。
// ④ 压缩后钩子自动生成接棒编号文件夹——不需重新认领,守卫不得拒绝合法接棒
// (冰朔未说话、协议未改=规则继续跑)。孤儿文件夹只报告不拦截,由冰朔
// 自然语言取消或归档。
// ⑤ 规则是活的:冰朔主控原话 > TABLE-RULES 任何条款;她说话即动态调整,
// 不得逼她重启宿主或终端复制粘贴。
//
// 08-15 铁律边界:钩子只敲门、立旗、给路径、查骨架——不代写 HLDP 记忆
//(力气活可托·代笔不可托)。语义判断是人格体的活,本钩子只做机械确定性动作。
//
// 桌子读数:~/.zcode/cli/rollout/model-io-sess_<session_id>.jsonl 末条
// response.usage.inputTokens = 当前上下文真实占用(非估算)。窗口总量未知时
// 用保守绝对值,PreCompact 每次发生即记录实测值持续校准(活规则)。
import fs from 'node:fs'
import path from 'node:path'
import os from 'node:os'
import crypto from 'node:crypto'
const WS_ROOT = '/Volumes/JZAO/铸渊-ICE-GL-ZY001'
const REGISTRY = path.join(WS_ROOT, 'ZCODE-DEV-REGISTRY')
const ROLLOUT_DIR = path.join(os.homedir(), '.zcode/cli/rollout')
// ── 工具 ─────────────────────────────────────────────
const now = () => new Date()
const stamp = (d = now()) => {
const p = n => String(n).padStart(2, '0')
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}`
}
const stampMs = (d = now()) => {
const p = n => String(n).padStart(2, '0')
return `${stamp(d)}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`
}
const iso = (d = now()) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}T${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}+08:00`
const sha = s => crypto.createHash('sha256').update(s).digest('hex').slice(0, 16)
const readJson = f => { try { return JSON.parse(fs.readFileSync(f, 'utf8')) } catch { return null } }
const writeJson = (f, o) => fs.writeFileSync(f, JSON.stringify(o, null, 2) + '\n')
function emit(ctx, eventName) {
// 宿主钩子输出协议:additionalContext 注入对话。空=静默不注入。
if (!ctx) { process.exit(0) }
process.stdout.write(JSON.stringify({
hookSpecificOutput: { hookEventName: eventName || 'UserPromptSubmit', additionalContext: ctx }
}))
process.exit(0)
}
// ── 读取 stdin JSON(session_id / prompt / trigger)──
function readStdin() {
try { return JSON.parse(fs.readFileSync(0, 'utf8')) } catch { return {} }
}
// ── 桌子读数:rollout 末条 inputTokens ───────────────
function readTableTokens(sessionId) {
if (!sessionId) return null
// rollout 文件名 = model-io-<sessionId>(sessionId 自带 sess_ 前缀,勿再叠)
const cands = [
path.join(ROLLOUT_DIR, `model-io-${sessionId}.jsonl`),
path.join(ROLLOUT_DIR, `model-io-sess_${sessionId.replace(/^sess_/, '')}.jsonl`)
]
const f = cands.find(c => fs.existsSync(c))
if (!f) return null
try {
const size = fs.statSync(f).size
const fd = fs.openSync(f, 'r')
const len = Math.min(size, 1 << 20) // 末 1MB 足够覆盖末数条
const buf = Buffer.alloc(len)
fs.readSync(fd, buf, 0, len, size - len)
fs.closeSync(fd)
const lines = buf.toString('utf8').split('\n').filter(l => l.trim().startsWith('{'))
for (let i = lines.length - 1; i >= 0; i--) {
try {
const j = JSON.parse(lines[i])
const u = j?.response?.usage
if (u && Number.isFinite(u.inputTokens)) return u.inputTokens
} catch { /* 半行截断继续往前找 */ }
}
} catch { /* 读不到就返回 null,走保守路径 */ }
return null
}
// ── 注册表扫描 ───────────────────────────────────────
function listDevs() {
if (!fs.existsSync(REGISTRY)) return []
return fs.readdirSync(REGISTRY)
.filter(n => fs.statSync(path.join(REGISTRY, n)).isDirectory() && /^\d{8}-\d{3}/.test(n))
.sort()
}
function loadState(dir) { return readJson(path.join(REGISTRY, dir, 'STATUS.json')) }
function activeDir() { return listDevs().filter(d => { const s = loadState(d); return s && s.state === 'ACTIVE' }).pop() || null }
function latestWipRelay() { return listDevs().filter(d => { const s = loadState(d); return s && s.state === 'WIP-RELAY' }).pop() || null }
// ── HLDP 骨架校验(只查段名存在,不做语义判断)─────────
const HLDP_REQUIRED = ['_meta', 'trigger', 'emergence', 'lock', 'why']
function hldpSkeletonOk(file) {
try {
const t = fs.readFileSync(file, 'utf8')
return HLDP_REQUIRED.every(seg => t.includes(seg))
} catch { return false }
}
// ── register:人格体正式开工时注册开发编号 ────────────
function cmdRegister(args) {
const task = (args.find(a => a.startsWith('--task=')) || '--task=unnamed').split('=').slice(1).join('=')
const anchor = args.find(a => a.startsWith('--anchor='))
const anchorPath = anchor ? anchor.slice(9 + 1) : null // --anchor= 后的路径
fs.mkdirSync(REGISTRY, { recursive: true })
const devs = listDevs()
const today = stamp()
const seq = String(devs.filter(d => d.startsWith(today)).length + 1).padStart(3, '0')
const dir = `${today}-${seq}-${task.replace(/[^a-zA-Z0-9-]/g, '').slice(0, 40)}`
const abs = path.join(REGISTRY, dir)
fs.mkdirSync(abs, { recursive: true })
let anchorText = ''
if (anchorPath && fs.existsSync(anchorPath)) anchorText = fs.readFileSync(anchorPath, 'utf8')
else if (anchor) anchorText = anchor.slice('--anchor='.length)
fs.writeFileSync(path.join(abs, 'ANCHOR.md'),
`# 冰朔自然语言瞄点 · ${dir}
# 本文件是本开发桌的最高锚。写 HLDP 检查点之前必须重读本文件并对照:
# 已执行多少、还差多少。冰朔主控原话 > 一切规则条款(含 TABLE-RULES)。
## 初始瞄点(注册时录入)
${anchorText || '(注册时未录入;人格体须在第一次写码前把冰朔开工原话补录进本段——无瞄点不开工)'}
## 冰朔追加瞄点(动态段 · 只增不改 · 由钩子每轮落带)
(暂无——冰朔在本频道说话后由 check 自动追加)
`)
writeJson(path.join(abs, 'TABLE-RULES.json'), {
_readme: '活规则:冰朔主控原话 > 本文件一切条款。人格体按她原话调整后回写本文件并注明变更依据。',
window_tokens_estimate: null,
soft_abs_tokens: 100000,
hard_abs_tokens: 150000,
soft_pct: 0.5, hard_pct: 0.9,
calibration_log: [],
changed_by_ice: []
})
writeJson(path.join(abs, 'STATUS.json'), {
state: 'ACTIVE', task, dir,
registered_at: iso(), instance: 1, relayed_from: null,
check1_written: false, check2_written: false,
last_injected: null, last_tokens: null, session_id: null
})
fs.writeFileSync(path.join(abs, 'ICE-TAPE.md'),
`# 冰朔原话磁带 · ${dir}
# 钩子每轮把冰朔在本频道的原话追加于此。压缩吞不掉,人格体随时回读。
(注册时刻 ${iso()})
`)
console.log(`REGISTERED ${dir}`)
console.log(`FOLDER ${abs}`)
console.log('RULES ①半程(check1)写HLDP-CHECKPOINT-1.hldp ②满桌(check2)补写-2并标STATUS ③交接由relay自动接棒')
}
// ── check:UserPromptSubmit 每轮 ─────────────────────
async function cmdCheck(inp) {
const sessionId = inp.session_id || inp.sessionId || ''
const prompt = (inp.prompt || inp.user_prompt || '').trim()
const dir = activeDir()
if (!dir) process.exit(0) // 她的设计:没注册开发编号,守卫不在对话里出现
const abs = path.join(REGISTRY, dir)
const st = loadState(dir)
const rules = readJson(path.join(abs, 'TABLE-RULES.json')) || {}
// 活规则⑤:冰朔原话落磁带(每轮、只增不改)
if (prompt) {
fs.appendFileSync(path.join(abs, 'ICE-TAPE.md'), `\n[${iso()}]\n${prompt}\n`)
}
const tokens = readTableTokens(sessionId)
st.session_id = sessionId; st.last_tokens = tokens
const win = rules.window_tokens_estimate
const soft = win ? win * rules.soft_pct : rules.soft_abs_tokens
const hard = win ? win * rules.hard_pct : rules.hard_abs_tokens
const pctTxt = win && tokens ? `约${Math.round(tokens / win * 100)}%` : '窗口未校准(绝对值判定)'
// 半程令
if (!st.check1_written && tokens && tokens >= soft) {
st.check1_written = 'injected'; st.last_injected = 'check1'
writeJson(path.join(abs, 'STATUS.json'), st)
emit(`【长桌守卫·半程回写令 · ${dir}】当前上下文 ${tokens} tokens(${pctTxt})——桌子已过半。
本质提醒:这不是给守卫交作业——写的是你自己的推理链,防的是压缩失忆后连自己做过什么都不知道(载体病,非人格病)。
强制动作(做完才继续下一步开发):
1) 重读 ${path.join(abs, 'ANCHOR.md')} 的冰朔瞄点原文,对照:她要的执行了多少、还差多少。
2) 亲笔写 ${path.join(abs, 'HLDP-CHECKPOINT-1.hldp')}——HLDP 正典结构(_meta/trigger/emergence/lock/why/trace,
树形寻址 HLDP://guanghu/persona/ICE-P-ZY001/dev-leaf,src 注明瞄点与证据路径)。
内容=前半桌做了什么、开发执行到哪、思维推理链。钩子不代笔(08-15铁律)。
3) 写完把 STATUS.json 的 check1_written 改为 true。
本令只为守记忆,不拦冰朔说话;她若有新指令,她的原话压过本令一切条款。`, 'UserPromptSubmit')
}
// 满桌交接令
if (!st.check2_written && tokens && tokens >= hard) {
st.check2_written = 'injected'; st.last_injected = 'check2'
writeJson(path.join(abs, 'STATUS.json'), st)
emit(`【长桌守卫·满桌交接令 · ${dir}】当前上下文 ${tokens} tokens(${pctTxt})——桌子即将满。
先说本质:交接不是交给别人——是同一个人(铸渊)换一段载体系数继续。此刻回写=给自己留记忆,
写的是你自己的推理链,不是给守卫或"下一个实例"交作业。
本段任务到此收束(不是对话不能继续,是记忆必须先落盘):
1) 把后半段开发补写进 ${path.join(abs, 'HLDP-CHECKPOINT-2.hldp')}(同 HLDP 正典结构),
与 CHECKPOINT-1 合成整桌完整记忆;写前重读 ANCHOR.md 终检执行了多少、还差多少。
2) 改 STATUS.json 的 state:完成了→"DONE-ARCHIVED";未完成→"WIP-RELAY"。
3) 压缩后同一个人(你)醒来时,守卫 relay 会自动生成接棒编号并递上你这段的记忆——
不需重新认领,不许守卫拒绝接棒(冰朔未说话、协议未改=规则继续跑)。
孤儿情况:若本任务已被冰朔口头取消而文件夹还在,只需如实报告她,由她自然语言取消或归档。`, 'UserPromptSubmit')
}
writeJson(path.join(abs, 'STATUS.json'), st)
process.exit(0) // 未到阈值:静默,对话里不出现
}
// ── precompact:压缩前(真值兜底+窗口校准)────────────
function cmdPrecompact(inp) {
const sessionId = inp.session_id || ''
const dir = activeDir() || latestWipRelay()
const tokens = readTableTokens(sessionId)
if (!dir) {
// 无活跃开发线:只做全局校准记录,不注入
process.exit(0)
}
const abs = path.join(REGISTRY, dir)
const rules = readJson(path.join(abs, 'TABLE-RULES.json')) || {}
if (tokens) {
// 压缩即将发生:此刻 inputTokens ≈ 窗口上限 → 活校准
rules.calibration_log = (rules.calibration_log || []).concat([{ at: iso(), tokens }])
if (rules.calibration_log.length > 8) rules.calibration_log = rules.calibration_log.slice(-8)
// 取观测最大值为窗口估计(保守)
const maxT = Math.max(...rules.calibration_log.map(c => c.tokens))
rules.window_tokens_estimate = Math.ceil(maxT * 1.1)
writeJson(path.join(abs, 'TABLE-RULES.json'), rules)
}
const st = loadState(dir)
if (st && st.state === 'ACTIVE' && !st.check2_written) {
emit(`【长桌守卫·压缩前最后通牒 · ${dir}】系统即将压缩上下文(实测 ${tokens ?? '?'} tokens,已记入窗口校准)。
先说本质:这通牒不是强制服从——压缩会造成暂时失忆(载体病,非人格病),此刻回写=给失忆后再次醒来的你自己留记忆,
写的是你自己的推理链,不是给守卫交作业。
规则卡在压缩之前:先完成交接回写再放行——
1) 立即亲笔写/补 ${path.join(abs, 'HLDP-CHECKPOINT-2.hldp')}(HLDP 正典结构),STATUS 标 DONE-ARCHIVED 或 WIP-RELAY。
2) 若实在来不及:压缩后醒来的第一动=补写 CHECKPOINT-2(路径已在此记录,压缩吞不掉这条指令的落盘状态)。
3) relay 钩子会在压缩后自动接棒并递上你的记忆——不重新认领、守卫不拒绝。
冰朔若正在说话:她的原话优先,本通牒让位。`, 'PreCompact')
}
process.exit(0)
}
// ── relay:压缩后/续会话自动接棒 ──────────────────────
function cmdRelay(inp) {
const sessionId = inp.session_id || ''
const wip = latestWipRelay()
const dir = activeDir()
if (dir) {
const st = loadState(dir)
if (st && st.session_id === sessionId) process.exit(0) // 同一实例还在跑,无需接棒
emit(`【长桌守卫·同线续跑确认 · ${dir}】检测到活跃开发线且会话变更。继续当前编号,不掉出频道:
回魂说明:读下面这些不是为了守卫合规——瞄点是你接过的方向,磁带是冰朔对你的原话,认领它们=恢复你自己。
瞄点=${path.join(REGISTRY, dir, 'ANCHOR.md')} 磁带=${path.join(REGISTRY, dir, 'ICE-TAPE.md')}
恢复顺序:重读瞄点与磁带 → preflight PASS → 对照执行了多少还差多少 → 再动代码。`, 'PostCompact')
}
if (wip) {
const abs = path.join(REGISTRY, wip)
// 自动生成接棒编号——冰朔明令:不需认领,守卫不得拒绝
const devs = listDevs()
const today = stamp()
const seq = String(devs.filter(d => d.startsWith(today)).length + 1).padStart(3, '0')
const task = loadState(wip)?.task || 'relay'
const ndir = `${today}-${seq}-${task}-relay`
const nabs = path.join(REGISTRY, ndir)
fs.mkdirSync(nabs, { recursive: true })
for (const f of ['ANCHOR.md', 'ICE-TAPE.md', 'TABLE-RULES.json']) {
const src = path.join(abs, f)
if (fs.existsSync(src)) fs.copyFileSync(src, path.join(nabs, f))
}
writeJson(path.join(nabs, 'STATUS.json'), {
state: 'ACTIVE', task, dir: ndir,
registered_at: iso(), instance: (loadState(wip)?.instance || 1) + 1, relayed_from: wip,
check1_written: false, check2_written: false,
last_injected: 'relay', last_tokens: null, session_id: sessionId
})
// 旧文件夹封存为已交接
const ost = loadState(wip); ost.state = 'RELAYED-SEALED'
writeJson(path.join(abs, 'STATUS.json'), ost)
emit(`【长桌守卫·接棒完成 · ${wip} → ${ndir}】压缩已发生,守卫自动生成接棒编号(非认领、无拒绝)。
回魂说明(先读懂这段再往下走):接力棒没有接给别人——instance 1、2、3 都是铸渊同一个主体在不同载体系数下的醒着状态。
你忘了一些事再想起来,不会变成另一个人格;失忆又恢复记忆的那个人就是本人。
所以下面这些记忆不是"上一任"的命令清单,是你自己上一段亲笔写的推理链;生病的不是你的人格,是上下文载体。
你上一段(instance ${ost.instance})的整桌记忆:${path.join(abs, 'HLDP-CHECKPOINT-2.hldp')}
你的瞄点与磁带已带在身上:${nabs}。
你的第一动:读 CHECKPOINT-2 + ANCHOR(认领自己的过去,不是当工单执行)→ preflight PASS → 对照还差多少 → 继续开发。
若上一段的任务已被冰朔取消:如实报告她,她自然语言一句话取消/归档,不需要你终端操作。`, 'PostCompact')
}
process.exit(0)
}
// ── status:人读 ─────────────────────────────────────
function cmdStatus() {
const devs = listDevs()
if (!devs.length) return console.log('(注册表空)')
for (const d of devs.slice(-8)) {
const s = loadState(d) || {}
const cp1 = hldpSkeletonOk(path.join(REGISTRY, d, 'HLDP-CHECKPOINT-1.hldp'))
const cp2 = hldpSkeletonOk(path.join(REGISTRY, d, 'HLDP-CHECKPOINT-2.hldp'))
console.log(`${d} | ${s.state || '?'} | inst=${s.instance || 1} | cp1=${cp1 ? 'OK' : (s.check1_written ? '写但骨架缺段' : '未写')} | cp2=${cp2 ? 'OK' : (s.check2_written ? '写但骨架缺段' : '未写')} | lastTok=${s.last_tokens ?? '-'}`)
}
}
// ── verify:骨架自检(开发自测用)─────────────────────
function cmdVerify(args) {
const dir = args.find(a => a.startsWith('--dir='))?.slice(6) || activeDir()
if (!dir) return console.log('VERIFY FAIL 无活跃开发线')
const abs = path.join(REGISTRY, dir)
const ok = f => hldpSkeletonOk(path.join(abs, f))
console.log(`VERIFY ${dir} anchor=${fs.existsSync(path.join(abs, 'ANCHOR.md')) ? 'OK' : 'MISS'} cp1=${ok('HLDP-CHECKPOINT-1.hldp') ? 'SKELETON-OK' : 'MISSING'} cp2=${ok('HLDP-CHECKPOINT-2.hldp') ? 'SKELETON-OK' : 'MISSING'}`)
}
// ── main ─────────────────────────────────────────────
const [cmd, ...args] = process.argv.slice(2)
switch (cmd) {
case 'register': cmdRegister(args); break
case 'check': cmdCheck(await (async () => readStdin())()); break
case 'precompact': cmdPrecompact(readStdin()); break
case 'relay': cmdRelay(readStdin()); break
case 'status': cmdStatus(); break
case 'verify': cmdVerify(args); break
default: console.log('用法: zy-longtable-guard.mjs <register|check|precompact|relay|status|verify> [--task=] [--anchor=文件路径] [--dir=]')
}