dsh-log-slicer 0.1.0 → 0.1.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # 变更记录
2
+
3
+ ## 0.1.1
4
+
5
+ 三处都是第一次真用(拿 2026-10-31 上午那段去切)时暴露出来的。
6
+
7
+ - **修**:`--kind work` 的三行头里,「原件」默认值从 `已移走` 改成 `(待填)`。工具并不知道那份原件在哪儿,替人写一个断言是越界。
8
+ - **修**:目录里如果某一卷的名字正好是「卷」,它会撞上表头首格,行被插到表头与分隔行中间,表格就坏了。现在表头与分隔行不参与"这是该卷的行"的判断。
9
+ - **增**:指路句识别。形如「我更新了第一轮」「你可以看看那个文件」「这方面内容我们就结束了」的句子,是他往文件上指,不是他的话。dry-run 会把它们列出来,并提示去原件抄真段落;若那段属三级,就不抄,写一句"按三级不抄在这里"。
10
+
11
+ ## 0.1.0
12
+
13
+ 第一版。取数 → 清洗 → 切片 → 渲染 → 登记,CLI 与插件两条入口共用 `src/pipeline.js`。
@@ -119,6 +119,13 @@ function main() {
119
119
  out.push(`# 图片 ${built.imageCount} 张:`);
120
120
  for (const img of built.images) out.push(`# ${img.mediaType || '?'} sha256:${img.sha256}`);
121
121
  }
122
+ if (built.pointerLines.length) {
123
+ out.push(`# 注意:有 ${built.pointerLines.length} 轮像"指路句"(往文件上指,不是原话)——`);
124
+ for (const p of built.pointerLines) {
125
+ out.push(`# 第 ${p.index + 1} 轮「${p.phrase}」:${p.text.replace(/\s+/g, ' ')}…`);
126
+ }
127
+ out.push('# 去那几份原件里抄真段落;若属三级,就不抄,写一句"按三级不抄在这里"。');
128
+ }
122
129
  out.push('', '===== 正文(将追加) =====', '', built.markdown);
123
130
 
124
131
  const row = indexRow(args.volume || (args.file ? path.basename(args.file, '.md') : '卷'), args.title, built.counts.you);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-log-slicer",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Slice one section out of a DeepSeek Harness session log: verbatim turns, images, catalog row, backups and a verification hash — one command, dry-run by default.",
5
5
  "keywords": [
6
6
  "dsh",
@@ -33,6 +33,7 @@
33
33
  "dsh.plugin.json",
34
34
  "README.md",
35
35
  "README.en.md",
36
+ "CHANGELOG.md",
36
37
  "LICENSE"
37
38
  ],
38
39
  "engines": {
package/src/pipeline.js CHANGED
@@ -8,7 +8,7 @@ import fs from 'node:fs';
8
8
  import { decodeFile, latestSessionLog } from './decode.js';
9
9
  import { turnsFromText } from './clean.js';
10
10
  import { sliceTurns, countTurns } from './slice.js';
11
- import { renderChat, renderWork, applyImageNotes } from './render.js';
11
+ import { renderChat, renderWork, applyImageNotes, findPointerLines } from './render.js';
12
12
 
13
13
  /**
14
14
  * @param {object} options
@@ -33,6 +33,7 @@ export function buildSection(options = {}) {
33
33
  kind === 'work' ? renderWork(segment, title, workHeader || {}) : renderChat(segment, title);
34
34
 
35
35
  const images = segment.flatMap((t) => t.images || []);
36
+ const pointerLines = findPointerLines(segment);
36
37
 
37
38
  return {
38
39
  log: log.path,
@@ -44,6 +45,7 @@ export function buildSection(options = {}) {
44
45
  counts,
45
46
  imageCount: images.length,
46
47
  images,
48
+ pointerLines,
47
49
  markdown,
48
50
  };
49
51
  }
package/src/register.js CHANGED
@@ -28,9 +28,19 @@ export function indexRow(volume, title, turns) {
28
28
  */
29
29
  export function insertIndexRow(markdown, row, volume) {
30
30
  const lines = String(markdown).split('\n');
31
+ const cells = (line) => line.split('|').map((c) => c.trim());
32
+ /** 表头与分隔行不参与"这是该卷的行"的判断——卷名叫「卷」时会撞上表头。 */
33
+ const isStructural = (line) => {
34
+ const t = line.trim();
35
+ if (!t.startsWith('|')) return true;
36
+ if (t.includes('---')) return true;
37
+ const c = cells(t);
38
+ return c[1] === '卷' && (c[2] === '节' || c[2] === '标题');
39
+ };
31
40
  let last = -1;
32
41
  for (let i = 0; i < lines.length; i++) {
33
42
  const t = lines[i].trim();
43
+ if (isStructural(t)) continue;
34
44
  if (t.startsWith(`| ${volume} |`) || t.startsWith(`|${volume}|`)) last = i;
35
45
  }
36
46
  if (last >= 0) {
package/src/render.js CHANGED
@@ -32,7 +32,7 @@ export function renderChat(segment, title) {
32
32
  return blocks.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
33
33
  }
34
34
 
35
- export function renderWork(segment, title, { what = '(待写)', line = '(待写)', why = '(待写)', origin = '已移走' } = {}) {
35
+ export function renderWork(segment, title, { what = '(待写)', line = '(待写)', why = '(待写)', origin = '(待填)' } = {}) {
36
36
  const out = [
37
37
  `## ${title}`,
38
38
  '',
@@ -54,6 +54,35 @@ export function renderWork(segment, title, { what = '(待写)', line = '(
54
54
  return out.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
55
55
  }
56
56
 
57
+ /**
58
+ * 他的话里有没有"指路句"("我更新了第一轮""你可以看看那个文件")。
59
+ *
60
+ * 这类句子不是原话,是他往文件上指;录进正本会让后来的人以为那儿就是内容。
61
+ * 这里只负责认出来、报给调用方,怎么处理(去原件抄、或按三级不抄)由人决定。
62
+ */
63
+ const POINTER_PATTERNS = [
64
+ /我(?:已经)?更新了第[一二三四五六七八九十\d]+轮/,
65
+ /你可以(?:看看|看一下|读一下|理解一下)/,
66
+ /这方面内容我们就(?:结束|到此)/,
67
+ /我(?:在)?工作区(?:更新|放)了/,
68
+ ];
69
+
70
+ export function findPointerLines(segment) {
71
+ const hits = [];
72
+ segment.forEach((t, i) => {
73
+ if (t.who !== 'user') return;
74
+ const body = tidy(t.text);
75
+ for (const re of POINTER_PATTERNS) {
76
+ const m = body.match(re);
77
+ if (m) {
78
+ hits.push({ index: i, phrase: m[0], text: body.slice(0, 60) });
79
+ break;
80
+ }
81
+ }
82
+ });
83
+ return hits;
84
+ }
85
+
57
86
  /** 把 `(图未落 …)` 占位替换成正式图注。 */
58
87
  export function applyImageNotes(markdown, mapping = {}) {
59
88
  let out = markdown;