dsh-log-contract 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dsh-log-contract.mjs +8 -4
- package/lib/index.js +1 -1
- package/lib/repair.js +59 -2
- package/package.json +1 -1
package/bin/dsh-log-contract.mjs
CHANGED
|
@@ -23,11 +23,14 @@ const USAGE = `dsh-log-contract —— 日志契约守护(DSH session log cont
|
|
|
23
23
|
--json 输出机器可读 JSON 报告
|
|
24
24
|
--max-details N 每条违规最多列 N 个缺失 seq(默认 8,--json 忽略)
|
|
25
25
|
|
|
26
|
-
dsh-log-contract fix <session-log> [--remove-markers] [--apply] [--backup-dir DIR] [--json]
|
|
26
|
+
dsh-log-contract fix <session-log> [--remove-markers] [--neutralize] [--apply] [--backup-dir DIR] [--json]
|
|
27
27
|
诊断 + 修复(2026-08 事故固化方案)。先做严格 seq 连续扫描 + 契约体检
|
|
28
28
|
(含 W1/W2 wire 级悬空 tool 检查),再按需修复:
|
|
29
29
|
--remove-markers 移除 retrace/message-editor marker 并全量重编号
|
|
30
30
|
(用于大范围遮蔽历史 / marker 漏盖 tool/result)
|
|
31
|
+
--neutralize 原地中和 turn-null marker(type→retrace/marker +
|
|
32
|
+
ignorable:true,删 surfaceOp/sourceEventSeqs,seq/行数不变)
|
|
33
|
+
—— token-meter 不再刷屏,会话驻留也安全(2026-08-30 事故)
|
|
31
34
|
--apply 备份后落盘(.zstd 走官方帧格式重建:帧1=header、
|
|
32
35
|
帧2=其余、带 checksum、单个结尾换行)
|
|
33
36
|
不传 --apply 为干跑(只报告)。
|
|
@@ -168,6 +171,7 @@ function cmdContracts() {
|
|
|
168
171
|
function cmdFix(args) {
|
|
169
172
|
const json = args.includes('--json');
|
|
170
173
|
const removeMarkers = args.includes('--remove-markers');
|
|
174
|
+
const neutralize = args.includes('--neutralize');
|
|
171
175
|
const dropFailedTurns = args.includes('--drop-failed-turns');
|
|
172
176
|
const trimIdx = args.indexOf('--trim-last');
|
|
173
177
|
const trimLast = trimIdx >= 0 && args[trimIdx + 1] ? Number(args[trimIdx + 1]) : undefined;
|
|
@@ -179,7 +183,7 @@ function cmdFix(args) {
|
|
|
179
183
|
const file = args.find((a) => !a.startsWith('-'));
|
|
180
184
|
if (!file) fail(USAGE);
|
|
181
185
|
|
|
182
|
-
const result = repairSession(file, { removeMarkers, dropFailedTurns, trimLast, compactLast, apply, backupDir });
|
|
186
|
+
const result = repairSession(file, { removeMarkers, neutralize, dropFailedTurns, trimLast, compactLast, apply, backupDir });
|
|
183
187
|
if (json) {
|
|
184
188
|
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
185
189
|
process.exit(result.ok ? 0 : 1);
|
|
@@ -188,7 +192,7 @@ function cmdFix(args) {
|
|
|
188
192
|
process.stdout.write(`\n🔧 dsh-log-contract fix —— ${file}\n`);
|
|
189
193
|
process.stdout.write(` 诊断:${result.issues.length === 0 ? '无问题' : result.issues.map((i) => `[${i.kind}] ${i.detail}`).join('\n ')}\n`);
|
|
190
194
|
if (result.applied) {
|
|
191
|
-
process.stdout.write(` 已应用修复:移除 ${result.removed} 项,重编号 ${result.renumbered}
|
|
195
|
+
process.stdout.write(` 已应用修复:移除 ${result.removed} 项,重编号 ${result.renumbered} 行,中和 ${result.neutralized} 个 turn-null marker(seq ${(result.neutralizedSeqs ?? []).join(',')})\n`);
|
|
192
196
|
process.stdout.write(` 备份:${result.backupPath}\n`);
|
|
193
197
|
process.stdout.write(` 修复后体检:error ${result.check.summary?.bySeverity?.error ?? '?'} | surface ${result.check.summary?.surfaceNodes ?? '?'} 节点\n`);
|
|
194
198
|
} else if (apply && !result.ok) {
|
|
@@ -196,7 +200,7 @@ function cmdFix(args) {
|
|
|
196
200
|
} else if (apply) {
|
|
197
201
|
process.stdout.write(' (--apply 且无问题——无内容可修)\n');
|
|
198
202
|
} else {
|
|
199
|
-
process.stdout.write(` (干跑模式:${result.removed} 项可移除、${result.renumbered}
|
|
203
|
+
process.stdout.write(` (干跑模式:${result.removed} 项可移除、${result.renumbered} 行待重编号、${result.neutralized} 个 turn-null marker 可中和;加 --apply 落盘,--remove-markers / --neutralize / --drop-failed-turns / --trim-last N 启用于对应修复)\n`);
|
|
200
204
|
}
|
|
201
205
|
process.stdout.write('\n');
|
|
202
206
|
process.exit(result.ok ? 0 : 1);
|
package/lib/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
export { loadSessionLog, tailSeq } from './log-reader.js';
|
|
8
8
|
export { validateSessionLog } from './validate.js';
|
|
9
9
|
export { createPreWriter, preWriterFromLog } from './prewrite.js';
|
|
10
|
-
export { repairSession, strictScanText, removeMarkersText, dropFailedTurnsText, trimLastMessagesText, compactLastMessagesText, rebuildZstdText } from './repair.js';
|
|
10
|
+
export { repairSession, strictScanText, removeMarkersText, neutralizeMarkersText, dropFailedTurnsText, trimLastMessagesText, compactLastMessagesText, rebuildZstdText } from './repair.js';
|
|
11
11
|
export { CONTRACT_RULES, LAYER, SEVERITY, ruleById } from './contracts.js';
|
|
12
12
|
export { tokenMeterViolations } from './checks.js';
|
|
13
13
|
export { auditToolCalls, extractText, extractToolOutputs, indexToolCalls, toolCommandOf } from './archaeology.js';
|
package/lib/repair.js
CHANGED
|
@@ -119,6 +119,50 @@ export function strictScanText(text) {
|
|
|
119
119
|
return { failures, count };
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* 原地中和 turn-null marker(2026-08-30 事故升级:单 marker 会让 token-meter
|
|
124
|
+
* 监听器在每条事件上抛错刷屏、压垮 host 事件循环 → 全会话连锁锁定)。
|
|
125
|
+
*
|
|
126
|
+
* 机制(自检单 §3.3):把 turn-null `assistant/message` replace marker 改写为
|
|
127
|
+
* `retrace/marker` + `ignorable: true`(dsh-session lib/index.js:1203/1209 认可
|
|
128
|
+
* envelope 级 ignorable),**删除 surfaceOp/sourceEventSeqs,不动行数/seq/时间**。
|
|
129
|
+
* 改写后该事件不再属于 SURFACE_EVENT_TYPES(dsh-session:219-223)→ foldSurface
|
|
130
|
+
* 跳过、token-meter 不做 step 配对(不再抛错刷屏);会话驻留时也安全(seq 不变,
|
|
131
|
+
* 退出 flush 不撞车)。代价:被遮蔽的旧消息回到 surface(编辑外观回退为原文)。
|
|
132
|
+
*
|
|
133
|
+
* @param {string} text - JSONL 全文(含 header 行)。
|
|
134
|
+
* @returns {{ text: string, neutralized: number, seqs: Array<number> }}
|
|
135
|
+
*/
|
|
136
|
+
export function neutralizeMarkersText(text) {
|
|
137
|
+
const parts = text.split('\n');
|
|
138
|
+
const seqs = [];
|
|
139
|
+
let neutralized = 0;
|
|
140
|
+
for (let i = 0; i < parts.length; i++) {
|
|
141
|
+
const raw = parts[i];
|
|
142
|
+
if (!raw.trim()) continue;
|
|
143
|
+
let v;
|
|
144
|
+
try {
|
|
145
|
+
v = JSON.parse(raw);
|
|
146
|
+
} catch {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (!v || typeof v !== 'object' || v.type !== 'assistant/message') continue;
|
|
150
|
+
// 只中和 turn-null 的 retrace marker(data.turn/step 为 null 且 id 带 marker 前缀)
|
|
151
|
+
if (v.data?.turn != null || v.data?.step != null) continue;
|
|
152
|
+
const id = v.data?.message?.id;
|
|
153
|
+
if (typeof id !== 'string' || !MARKER_PREFIXES.some((p) => id.startsWith(`${p}-`))) continue;
|
|
154
|
+
const seq = v.seq;
|
|
155
|
+
delete v.surfaceOp;
|
|
156
|
+
delete v.sourceEventSeqs;
|
|
157
|
+
v.type = 'retrace/marker';
|
|
158
|
+
v.ignorable = true;
|
|
159
|
+
parts[i] = JSON.stringify(v);
|
|
160
|
+
seqs.push(seq);
|
|
161
|
+
neutralized++;
|
|
162
|
+
}
|
|
163
|
+
return { text: parts.join('\n'), neutralized, seqs };
|
|
164
|
+
}
|
|
165
|
+
|
|
122
166
|
/**
|
|
123
167
|
* 移除 retrace/message-editor marker 并全量重编号。
|
|
124
168
|
* @param {string} text - JSONL 全文。
|
|
@@ -555,11 +599,11 @@ export function rebuildZstdText(text) {
|
|
|
555
599
|
* @param {string} file - .jsonl 或 .jsonl.zstd 路径。
|
|
556
600
|
* @param {{
|
|
557
601
|
* removeMarkers?: boolean, dropFailedTurns?: boolean, trimLast?: number, compactLast?: number,
|
|
558
|
-
* apply?: boolean, backupDir?: string
|
|
602
|
+
* neutralize?: boolean, apply?: boolean, backupDir?: string
|
|
559
603
|
* }} opts
|
|
560
604
|
* @returns {{
|
|
561
605
|
* file, ok, issues: Array<{kind:string, detail:string}>,
|
|
562
|
-
* removed, renumbered, backupPath, applied,
|
|
606
|
+
* removed, renumbered, neutralized, neutralizedSeqs: Array<number>, backupPath, applied,
|
|
563
607
|
* check: { ok, summary }
|
|
564
608
|
* }}
|
|
565
609
|
*/
|
|
@@ -610,6 +654,17 @@ export function repairSession(file, opts = {}) {
|
|
|
610
654
|
const r = removeMarkersText(plain);
|
|
611
655
|
applyFix('markers', r, `移除 ${r.removed} 个 retrace/message-editor marker(重编号 ${r.renumbered} 行)`);
|
|
612
656
|
}
|
|
657
|
+
let neutralized = 0;
|
|
658
|
+
const neutralizedSeqs = [];
|
|
659
|
+
if (opts.neutralize) {
|
|
660
|
+
const r = neutralizeMarkersText(plain);
|
|
661
|
+
neutralized = r.neutralized;
|
|
662
|
+
neutralizedSeqs.push(...r.seqs);
|
|
663
|
+
if (r.neutralized > 0) {
|
|
664
|
+
issues.push({ kind: 'neutralize', detail: `原地中和 ${r.neutralized} 个 turn-null marker(type→retrace/marker + ignorable:true,删除 surfaceOp/sourceEventSeqs,seq/行数不变)→ token-meter 不再刷屏` });
|
|
665
|
+
plain = r.text;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
613
668
|
if (typeof opts.trimLast === 'number') {
|
|
614
669
|
const r = trimLastMessagesText(plain, opts.trimLast);
|
|
615
670
|
applyFix('trim', r, `裁剪到最近 ${r.kept} 条消息(丢弃 ${r.removed} 行,重编号 ${r.renumbered} 行)`);
|
|
@@ -652,6 +707,8 @@ export function repairSession(file, opts = {}) {
|
|
|
652
707
|
issues,
|
|
653
708
|
removed,
|
|
654
709
|
renumbered,
|
|
710
|
+
neutralized,
|
|
711
|
+
neutralizedSeqs,
|
|
655
712
|
backupPath,
|
|
656
713
|
applied,
|
|
657
714
|
check,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-log-contract",
|
|
3
3
|
"description": "日志契约守护 — DSH session log contract guard: offline health check (CLI) + pre-write validation for DeepSeek Harness session logs",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"bin": {
|