Skill 方案导入1284 字·2026/11/14·被检索 0 次

Skill 方案 S4 · 笔记批量入库与版本管理

pack文件格式、父子key映射、meta生成、标题去重、版本不覆盖五件事写死:一个pack解决子篇先于父篇的顺序错,POST前先查重,改写走regenerate带direction而非PUT覆盖。15次批量入库的0.18亿token可压到0.04亿。

挂靠在《二、重复操作与 Skill 方案》之下

版本
使用说明
v1reused-preview2026/11/14 11:16:48

Skill 方案 S4 · 笔记批量入库与版本管理

一句话:把「一堆 Markdown 要按父子结构挂进笔记站、补摘要评价关键词、还不能覆盖旧版本」这件事固化成 pack 文件 + 一条命令;15 次以上批量入库消耗的 0.18 亿 token 可压到 0.04 亿。

一、为什么需要它

两个月里笔记站的批量入库做了 15 次以上,其中包括本次复盘本身——7 篇子篇 + 1 个父篇,就是靠 wb-create.mjs 和 wb-publish-pack.mjs 落进去的。

指标数值说明
批量入库次数15+单次 1–8 篇
累计 token0.18 亿平均每次 12 万
平均工具调用7–12 次每条笔记一次 POST + 一次校验
失败重试常见 1–2 次父子顺序错、内容过短、meta 缺字段
单篇平均正文1500–9000 字pack 文件最大 16 KB

现状四个痛点:

  1. 父子顺序敏感。子篇必须先有父篇 id,手工逐条 POST 时经常先发子篇,结果 parentId 为空,只能删了重发。
  2. meta 靠现编。summary/evaluation 每次写法不一,短到 20 字、长到 400 字都有,检索命中率随心情波动。
  3. 幂等缺失。同一篇重跑一次就多一份副本,标题一模一样,事后无法分辨哪份是主版本。
  4. 版本规则被误用。笔记站的 NoteVersion 是"永不覆盖",只有一个 isPrimary;有人直接 PUT 覆盖正文,把历史版本连同 direction 一起抹了。

二、触发词与边界

触发词不适用场景
「入库」「存到笔记站」「批量发布」只改一篇的一句话 → 直接编辑,不进 pack
「挂到某篇下面」「做子篇」要改数据库 schema → 走迁移,不走 API
「更新版本」「加个方向重写」要删笔记 → 手动确认,skill 不提供删除
「这批笔记补一下摘要/关键词」要导入 docx 原文件 → /api/import/docx,不在本 skill
「把上次那批重发一遍」内容未变 → 先查重,不重发

边界:只新增,不删除,不覆盖。删除与覆盖属于破坏性操作,必须逐条人工确认。

三、输入与前置检查

pack 文件格式固定(wb-publish-pack.mjs 按行解析,@@ 开头为元信息,其余为正文):

@@NOTE key=s1
@@TITLE Skill 方案 S1 · 站点健康巡检与自愈
@@CATEGORY Skill 方案
@@SUMMARY 80-150 字摘要
@@EVAL 60-120 字评价
@@KEYWORDS 站点巡检|运维,自愈|运维,隧道|运维
@@PARENT root
# 正文 Markdown

前置四查:

检查命令通过标准
笔记站在线Invoke-WebRequest -UseBasicParsing http://127.0.0.1:3102/notes/ -TimeoutSec 8200
门户密钥存在Test-Path "C:\Users\cag\WorkBuddy\2026-10-11-06-00-16\portal\data\portal-secret.txt"True
父篇 id 有效GET /notes/api/notes/<parentId>200 且标题匹配
正文长度(Get-Content s1.md -Raw).Length> 200(脚本硬性拒绝过短内容)

父篇 cmwftobjv0039lh8cg6a7p0dg(「二、重复操作与 Skill 方案」)已存在,7 篇子篇全部挂它下面。

四、执行步骤

步骤 1 · 单篇入库(有父篇时)。

node wb-create.mjs --file tmp-wb/s1.md \
  --title "Skill 方案 S1 · 站点健康巡检与自愈" \
  --category "Skill 方案" \
  --summary "把门户3000/词汇站3001/笔记站3102/隧道的体检与自愈固化成一条命令,含四层检查矩阵与L0-L3分级自愈,253次巡检会话的0.99亿token可压到0.15亿。" \
  --eval "方案把巡检从对话改成脚本,补上了内容指纹与分级自愈两个缺口;与现有kaoyan-site-health是超集关系,落地时把它收缩成薄入口即可。" \
  --keywords "站点巡检|运维,自愈|运维,隧道|运维,健康检查|运维" \
  --parent cmwftobjv0039lh8cg6a7p0dg

输出一行 JSON,含 id、url、parentId。parentId 为空就是挂靠失败,必须重发而不是将就。

步骤 2 · 多篇入库(有父子引用时)。 一个 pack 文件解决顺序问题,因为脚本按 @@NOTE 出现顺序发送,并把 key→id 记进 ids 映射:

node wb-publish-pack.mjs wb-pack-1.md

@@PARENT root 里的 root 是同一个文件里先前定义的 key;父篇必须写在最前面。跨文件引用不支持,跨批复用要显式传 --parent <真实id>。

步骤 3 · 幂等去重。 发之前先按标题查一遍,命中就跳过:

node -e "
const s=require('fs').readFileSync('C:/Users/cag/WorkBuddy/2026-10-11-06-00-16/portal/data/portal-secret.txt','utf8').trim();
const h={'x-portal-secret':s,'x-user-id':'eeefa7d5c16a49bdaeff','x-user-username':'cag','x-user-role':'admin'};
(async()=>{
  const r=await fetch('http://127.0.0.1:3102/notes/api/search?q='+encodeURIComponent('Skill 方案 S1'),{headers:h});
  const j=await r.json();
  console.log(JSON.stringify(j).slice(0,600));
})();"

命中同标题且 parentId 相同的,视为已入库,跳过;只是标题相同但父不同,属于合法的新篇,继续发。

步骤 4 · 更新版本而不是覆盖。 内容需要重写时,走版本接口,旧的自动降级、新的置为 isPrimary:

node -e "
const s=require('fs').readFileSync('C:/Users/cag/WorkBuddy/2026-10-11-06-00-16/portal/data/portal-secret.txt','utf8').trim();
fetch('http://127.0.0.1:3102/notes/api/notes/<noteId>/regenerate',{
  method:'POST',
  headers:{'content-type':'application/json','x-portal-secret':s,'x-user-id':'eeefa7d5c16a49bdaeff','x-user-username':'cag','x-user-role':'admin'},
  body:JSON.stringify({direction:'补上 S2 的停止顺序与 Prisma 锁窗口'})
}).then(r=>r.json()).then(j=>console.log(JSON.stringify(j).slice(0,300)));"

direction 是用户原话的直接落点,必须写清楚"改什么",不要写"优化一下"。

步骤 5 · 白皮书标记。 需要进白皮书板块的笔记,在入库后单独置 isWhitebook=true;它不复制内容,改笔记即改白皮书。

步骤 6 · 批量校验。

node -e "
(async()=>{const s=require('fs').readFileSync('C:/Users/cag/WorkBuddy/2026-10-11-06-00-16/portal/data/portal-secret.txt','utf8').trim();
const h={'x-portal-secret':s,'x-user-id':'eeefa7d5c16a49bdaeff','x-user-username':'cag','x-user-role':'admin'};
const r=await fetch('http://127.0.0.1:3102/notes/api/notes/cmwftobjv0039lh8cg6a7p0dg',{headers:h});
const j=await r.json();
console.log(j.note.children.length, j.note.children.map(c=>c.title).join(' | '));})();"

期望 7 个子篇,标题与 pack 完全一致。

五、输出与验收

{"id": "cmwft...", "title": "Skill 方案 S1 · 站点健康巡检与自愈", "url": "/notes/note/cmwft.../", "parentId": "cmwftobjv0039lh8cg6a7p0dg", "words": 3820}

三条硬断言:

  1. 每篇返回的 parentId 必须等于 cmwftobjv0039lh8cg6a7p0dg,为空即失败。
  2. 正文长度必须 > 200 且与源文件字符数误差 < 5 字(脚本本身会做最小值拦截)。
  3. 父篇 children 数量必须等于本次提交篇数,且没有重复标题;重复即说明幂等失效。

六、失败处理与回滚

现象根因动作
内容过短,拒绝入库文件读成空或路径错检查 --file 相对路径,确认字符数
返回 401/403密钥文件缺失或用户头不对读 portal-secret.txt,四个 x-user-* 头齐全
parentId 为 null父 id 打错或父篇被删先 GET 父篇确认存在,再重发
同一篇出现两份没做步骤 3 的去重保留 id 较小的一份,另一份改标题加「(副本)」后人工归档
版本越改越乱直接 PUT 覆盖了正文从 NoteVersion 里把最后一版置回 isPrimary

回滚:新增类操作没有事务性回滚,只能"标记作废"——把误发的笔记标题前加 [作废] 并把可见性改 private,不要物理删除。版本类操作可回滚:把目标 version 重新切为 isPrimary 即可。

七、收益测算

项目现状改造后
单次 token12 万(逐条 POST + 反复校验)2.6 万(一个 pack + 一次校验)
次数15+15+
累计0.18 亿0.04 亿
父子错挂常见接近零(key 映射)
重复入库时有发生去重前置

省的是"每条一次往返 + 出错重来"。节省 0.14 亿,约 78%,是七类里比例最低的一项——因为写作本身不可压缩,能省的只有编排开销。