chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
+ ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
This commit is contained in:
1 parent
c70d5d860e
commit
5ad755116e
173 files changed
+27632
No files matched your search
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 会话取证 · 逐次工具结果分类(沙箱拒绝 / 文件策略拒绝 / 命令错误 / OK)+ 审批事件时间线
|
||||
* 精确分类:每次 bash 调用的结果性质(沙箱拒绝 / 命令错误 / 成功)
|
||||
* 用法:node classify-bash.mjs <session.jsonl.zstd>
|
||||
*/
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { zstdDecompressSync } from 'node:zlib'
|
||||
|
||||
const raw = readFileSync(process.argv[2])
|
||||
const MAGIC = Buffer.from([0x28, 0xb5, 0x2f, 0xfd])
|
||||
const offs = []
|
||||
for (let i = 0; i + 4 <= raw.length; i++) if (raw.compare(MAGIC, 0, 4, i, i + 4) === 0) offs.push(i)
|
||||
let text = ''
|
||||
for (let f = 0; f < (offs.length ? offs : [0]).length; f++) {
|
||||
const s = offs[f], e = f + 1 < offs.length ? offs[f + 1] : raw.length
|
||||
try { text += zstdDecompressSync(raw.subarray(s, e)).toString('utf8') } catch {}
|
||||
}
|
||||
const evs = []
|
||||
for (const l of text.split('\n')) { if (!l.trim()) continue; try { evs.push(JSON.parse(l)) } catch {} }
|
||||
const t = (ms) => new Date(Number(ms)).toLocaleString('zh-CN', { hour12: false, timeZone: 'Asia/Shanghai' })
|
||||
|
||||
// tool/call:id → {name, args(截断)}
|
||||
const callInfo = new Map()
|
||||
for (const e of evs) {
|
||||
if (e.type !== 'tool/call') continue
|
||||
const s = JSON.stringify(e.data ?? {})
|
||||
const id = (s.match(/"callId":"([^"]+)"/) ?? [])[1]
|
||||
const name = (s.match(/"name":"([^"]+)"/) ?? [])[1]
|
||||
if (!id) continue
|
||||
const cmd = (s.match(/"command":"((?:[^"\\]|\\.)*)"/) ?? [])[1] ?? ''
|
||||
callInfo.set(id, { name, cmd: cmd.replace(/\\n/g, ' ').slice(0, 90) })
|
||||
}
|
||||
// tool/result:id → 文本
|
||||
const rows = []
|
||||
for (const e of evs) {
|
||||
if (e.type !== 'tool/result') continue
|
||||
const s = JSON.stringify(e.data ?? {})
|
||||
const id = (s.match(/"toolCallId":"([^"]+)"/) ?? [])[1]
|
||||
const info = callInfo.get(id) ?? { name: '?', cmd: '' }
|
||||
const txt = (e.data?.message?.content ?? []).map((c) => (c.content ?? []).map((x) => x.text ?? '').join('')).join('\n')
|
||||
const flat = txt.replace(/\s+/g, ' ').trim()
|
||||
let kind = 'OK'
|
||||
if (/sandbox mode .* is requested but no sandbox backend/.test(flat)) kind = '沙箱拒绝'
|
||||
else if (/file access denied under/.test(flat)) kind = '文件策略拒绝'
|
||||
else if (/^Error:|^error:|"error"|Command failed|exit code [1-9]/.test(flat)) kind = '命令错误'
|
||||
rows.push({ time: e.time, name: info.name, cmd: info.cmd, kind, head: flat.slice(0, 150) })
|
||||
}
|
||||
console.log('=== 逐次 tool/result 分类(共 %d)===', rows.length)
|
||||
for (const r of rows) {
|
||||
if (r.name !== 'bash' && r.kind === 'OK') continue
|
||||
console.log(' %s %-6s %-10s %s', t(r.time), r.name, r.kind, (r.cmd || r.head).slice(0, 110))
|
||||
}
|
||||
const byKind = {}
|
||||
for (const r of rows) byKind[r.kind] = (byKind[r.kind] ?? 0) + 1
|
||||
console.log('\n汇总:', JSON.stringify(byKind))
|
||||
const sb = rows.filter((r) => r.kind === '沙箱拒绝')
|
||||
console.log('\n沙箱拒绝时间线: %s → %s(共 %d 次)', t(sb[0]?.time), t(sb[sb.length - 1]?.time), sb.length)
|
||||
const after = sb.filter((r) => r.time > 1789116 * 1000).length
|
||||
console.log(' 其中 22:16 切档位之后:', after, '次')
|
||||
Reference in new issue
Block a user