nx-sk 0.12.0 → 0.15.0

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +137 -1
  2. package/README.md +55 -13
  3. package/assets/kv-source/SKILL.md +166 -0
  4. package/assets/kv-source/references/10-sync.md +155 -0
  5. package/assets/kv-source/references/20-intake.md +77 -0
  6. package/assets/nx-sk/SKILL.md +80 -1
  7. package/assets/nx-sk/references/10-sections-entries.md +34 -1
  8. package/assets/nx-sk/references/20-secrets.md +7 -1
  9. package/assets/nx-sk/references/40-files.md +2 -0
  10. package/assets/nx-sk/references/50-deploy.md +4 -2
  11. package/assets/sk-needdo/SKILL.md +152 -0
  12. package/assets/sk-needdo/references/10-inbox.md +64 -0
  13. package/assets/sk-needdo/references/20-schema.md +108 -0
  14. package/assets/sk-needdo/references/30-fill.md +105 -0
  15. package/assets/sk-needdo/references/40-pitfalls.md +68 -0
  16. package/assets/sk-needdo/references/50-example.md +140 -0
  17. package/assets/sk-pdf/SKILL.md +212 -0
  18. package/assets/sk-pdf/references/10-extract.md +69 -0
  19. package/assets/sk-pdf/references/20-sop-schema.md +108 -0
  20. package/assets/sk-pdf/references/30-pitfalls.md +89 -0
  21. package/assets/sk-pdf/scripts/_deps.py +33 -0
  22. package/assets/sk-pdf/scripts/pdf_extract.py +144 -0
  23. package/assets/sk-pdf/scripts/pdf_probe.py +84 -0
  24. package/assets/sk-thesis/SKILL.md +58 -0
  25. package/assets/sk-thesis/references/10-format.md +97 -0
  26. package/assets/sk-thesis/references/20-structure.md +82 -0
  27. package/assets/sk-thesis/references/30-pitfalls.md +91 -0
  28. package/assets/sk-thesis/scripts/make_thesis_template.py +987 -0
  29. package/assets/sk-thesis/templates//346/257/225/344/270/232/350/256/272/346/226/207/346/250/241/346/235/277-/350/256/276/350/256/241/345/274/200/345/217/221/347/261/273.docx +0 -0
  30. package/package.json +1 -1
  31. package/src/core/attachments.js +40 -15
  32. package/src/core/fields.js +189 -4
  33. package/src/core/render.js +36 -2
  34. package/src/core/source.js +190 -0
  35. package/src/core/store.js +0 -6
  36. package/src/core/vault.js +9 -2
  37. package/src/index.js +1 -2
  38. package/src/modules/entries/index.js +104 -0
  39. package/src/modules/entries/service.js +506 -8
  40. package/src/modules/entries/view.jsx +95 -14
  41. package/src/modules/export/index.js +9 -34
  42. package/src/modules/export/view.jsx +10 -7
  43. package/src/modules/files/service.js +3 -3
  44. package/src/modules/files/view.jsx +4 -4
  45. package/src/modules/sections/service.js +24 -2
  46. package/src/modules/sections/view.jsx +55 -4
  47. package/src/modules/settings/service.js +1 -4
  48. package/src/modules/settings/view.jsx +0 -31
  49. package/src/modules/transfer/service.js +20 -0
  50. package/src/runtime/cli.js +11 -4
  51. package/src/runtime/spec.js +13 -2
  52. package/src/web/frontend/components/fieldEditor.jsx +78 -4
  53. package/src/web/frontend/components/sourcePanel.jsx +359 -0
  54. package/src/web/frontend/styles.css +100 -0
  55. package/src/web/public/assets/{files-DA6DHa6W.js → files-D5FYZdp3.js} +1 -1
  56. package/src/web/public/assets/{index-DyvN6UVw.js → index-CK6204A2.js} +2 -2
  57. package/src/web/public/assets/{index-C0soQxz7.css → index-DtcEFmKx.css} +1 -1
  58. package/src/web/public/assets/view-BeNH5xFQ.js +1 -0
  59. package/src/web/public/assets/view-BkhydqIS.js +1 -0
  60. package/src/web/public/assets/view-DSR4LaGb.js +1 -0
  61. package/src/web/public/assets/view-DaUnjbX8.js +35 -0
  62. package/src/web/public/assets/view-NWwrB4dg.js +1 -0
  63. package/src/web/public/index.html +2 -2
  64. package/src/modules/export/service.js +0 -192
  65. package/src/web/frontend/components/readableExport.jsx +0 -159
  66. package/src/web/public/assets/view-2fmPzM-r.js +0 -38
  67. package/src/web/public/assets/view-B20jmlM1.js +0 -1
  68. package/src/web/public/assets/view-BEKkVrl1.js +0 -7
  69. package/src/web/public/assets/view-BiFJZZ7R.js +0 -1
  70. package/src/web/public/assets/view-DkM8Ts0E.js +0 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,143 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## 0.15.0 (2026-10-20)
8
+
9
+ **原始资料袋:每个栏目都自带一个「乱入区」**
10
+
11
+ ### Added
12
+
13
+ - **毕业论文栏目模板(`section add --template thesis`)+ 随包 skill `sk-thesis`**:把信息工程学院
14
+ 2022 版撰写规范(设计开发类)做成 9 组 / 59 个字段——页面与字体硬指标、章节结构、图表与三线表、
15
+ 代码要求、参考文献(GB/T 7714-2015)、打印装订与日期红线,外加一份可直接勾选的「交稿自检」清单。
16
+ 配套 `assets/sk-thesis/templates/毕业论文模板-设计开发类.docx`:A4、页边距 2.5/2/2.5/2 cm、
17
+ 1.5 倍行距、宋体小四、标题黑体四号 / 黑体小四 / 宋体小四、三线表 1.5 磅 / 1 磅、目录域、
18
+ 分节页码(封面与目录不编、标题页起为 1),关键位置带 23 条格式批注。
19
+ `scripts/make_thesis_template.py` 可重新生成;`references/10-format` 是数值速查,
20
+ `20-structure` 是章节骨架与装订顺序,`30-pitfalls` 记录真实错法(一级标题加粗、
21
+ 图题「图 1」多空格、(1)用自动编号带悬挂缩进、批注未删导致 PDF 整页缩到 74%)。
22
+
23
+ - **原始资料袋(`source` 字段):每个栏目都自带一个的「乱入区」**——杂乱资料(邮件、截图、
24
+ PDF 摘录、手抄的表)先**原样**收进袋子,之后由 agent 或人按栏目字段字典整理成结构化字段。
25
+ 「先乱入、后整理」因此成立:没有这个袋子,用户只有把乱数据硬塞进结构化字段(把结构搞脏)
26
+ 或干脆不存(信息丢了)两个坏选择。
27
+ - **面板**:条目表单底部一条折叠条「原始资料 · N 条」,点开是右侧抽屉——左清单、中正文,
28
+ 文件拖进去即入袋(同时给了「选择文件」按钮与粘贴框:WCAG 2.2 AA 要求拖拽不能是唯一入口)。
29
+ 抽屉内焦点有可见指示器(2px 描边),关闭后焦点归还折叠条。
30
+ - **CLI**:`source list / get / add / update / remove / ensure`;`--file` 先进附件库、
31
+ **原件保留不搬走**(「加一条资料」不该搬用户本机的文件,那是 `file add` 的语义)。
32
+ - **双向同步**:整理完用 `source update --mapped 字段key` 给用上的资料打锚点,
33
+ 字段值也可回写进袋子留出处——「哪个字段的值来自哪条原始资料」永远反查得回来。
34
+ - 三条纪律写进代码与文档:**原文不改写**、**不确定就留空**、**敏感内容不进袋**(袋是明文的,
35
+ 密码走「密钥」栏目或 `sensitive` 字段);袋**不计完整度**(暂存区不是「该填的格子」)。
36
+ - 新栏目(模板或自定义)都自带这个字段;老栏目用 `nx-sk source ensure` 一次补齐。
37
+ - **`kv-source` skill**(随包分发,`nx-sk skill install kv-source`):讲透「原始资料 ↔ 字段」
38
+ 的双向同步、映射表纪律(把握不足就不写)、以及各种文件(PDF/截图/邮件/表格)怎么进袋。
39
+
40
+ ### Fixed
41
+
42
+ - **数组型 flag 的逗号没被切开**:`--unset a,b`、`--tags a,b` 会把整串当成**一个**值
43
+ (CLI 解析层把 array flag 收成数组,而强转那一步见数组就跳过了切分)——
44
+ `--unset a,b` 直接报「没有字段: a,b」。现在每个元素都再按逗号切一次。
45
+
46
+ ## 0.14.0 (2026-10-19)
47
+
48
+ **统一列表机制:跨栏目关键 key 台账 + 容器平台字段模板 + 结构清单**
49
+
50
+ ### Added
51
+
52
+ - **关键 key 台账(`nx-sk entry keys [--reveal]`)**:跨栏目聚合 链接 / 账号 / 密码 / 镜像 / token 等
53
+ 关键字段统一列表——不管是 job/school 还是自己手搓的「容器平台」栏目里的 accessUrl / account /
54
+ password 字段,全部进同一张台账。值默认打码(链接类保留原文便于点击;账号/密码/token 一律掩码);
55
+ `--reveal` 出明文(密文字段走解密)。给 agent 一句「把川农控制台链接和账号密码贴出来」就能直接拿到。
56
+ - **容器平台模板(`nx-sk section add --template platform`)**:一个平台(KubeSphere / Rancher / 自建 k8s…)
57
+ 一个条目,字段:基本信息(平台名/提供方/环境)/ 访问凭据(控制台链接/账号/密码[密文])/ 镜像与部署
58
+ (项目命名空间/仓库/镜像名/部署清单附件)/ 网络入口(应用入口链接)/ 备注。
59
+ 配合 `entry keys` 把分散在每个平台条目里的链接/账号/密码自动汇总成台账。
60
+ - **条目页左右栏可拖宽**:条目列表 ↔ 内容之间加了分隔把手(骑在原 14px 间隙上,间距节奏不变),
61
+ 拖动即调整左栏宽度(240–560px 夹紧),双击恢复默认 300px;宽度记在 `ui.listW`
62
+ (localStorage),跨会话保留。拖动过程只改本地 state,松手才落盘,不因 mousemove 重渲染表单。
63
+
64
+ - **条目定位面包屑:点击即复制,方便把「改哪个条目」一键贴给 agent**。条目列表每行悬浮出 ⧉
65
+ (hover 才显示,不打扰),右侧详情头的「栏目 › 条目 ⧉」整条可点;复制出的定位同时带
66
+ 人读路径(栏目「标题」(id) › 条目「标题」(id))与可直接执行的 CLI
67
+ (`entry get` / `entry update --section <栏目id> <条目id>`)——标题可能重名或被改,
68
+ 定位与命令一律用 id,agent 拿到就能精确动手,不用再猜「是哪一条」。
69
+
70
+ - **`entrylist` 字段类型:结构清单(变长行)**——一个字段的值是**行对象的数组**,行数不限;
71
+ 列(子字段)在字段定义的 `fields` 里声明。为「一个条目挂多组 链接/账号/密码」这类密集结构而生:
72
+ tags 存不了结构,拆成多个字段又不知道会有几组。
73
+ - 面板渲染成**密集迷你表**(列头 + 行输入框,直接增删行);「新增字段」选「结构清单(变长行)」
74
+ 预填 名称 / 链接 / 账号 / 密码(密文) 四列,列可增删、可勾「密文」。
75
+ - 敏感列(`sensitive: true`)与顶层敏感字段同一条边界:AES-256-GCM 密文落盘、默认读原文、
76
+ `--mask` 打码、打码占位原样回传保留原密文(想改密码却只拷了打码值会当场报错);
77
+ ZIP 导出前同样把清单里的密文解开。
78
+ - CLI 写入用 JSON:`--data '{"账号清单":[{"名称":"…","链接":"https://…"}]}'`(`--set` 认 JSON 串,
79
+ 空串 = 清空);没登记过的键直接写行数组会自动登记成 entrylist 并从行推断列;
80
+ `entry get` 里清单一行一条、带列标签;模板漂移把列定义也算进去(列变了 sync 会报)。
81
+
82
+ - **`sk-pdf` skill:把 PDF 转成图文 SOP 栏目**(随包分发,`nx-sk skill install sk-pdf`)。
83
+ 解决「照着一份 PDF 手册把流程沉淀进 nx-sk」这件事每次都要重新摸索的问题 ——
84
+ 流程、脚本、坑都固化下来了。
85
+ - 两个脚本(唯一第三方依赖 PyMuPDF,其余全是标准库):
86
+ `pdf_probe.py` 先探查(页数 / 每页图数 / 图片尺寸分布 / 是不是扫描件,
87
+ 图 ≤5 张就别走图文流程);`pdf_extract.py` 出**带图片占位符的 markdown** + 导出图片。
88
+ - **按版面坐标(y)排序拼图文**,不按页码也不按 xref 顺序 —— 这是最关键的一点:
89
+ 顺序错了文字全对而截图全错位,用户照着图文对不上完全做不下去。
90
+ - 图片编号 `页码_页内序号`(如 `006_1`),挑图时用编号对话不易错,事后可回溯来源。
91
+ - **技术栈(实测后写定,不是猜的)**:只用 **PyMuPDF** 一个第三方库,其余全是标准库;
92
+ Pillow / pypdf / pdfplumber 实测都不需要,不装(少一个依赖就少一处「换机器跑不起来」)。
93
+ 选 PyMuPDF 的唯一理由是它能给 `get_image_rects()`(图片在页面上的**摆放矩形**)——
94
+ 图文拼接靠的就是 y 坐标,别的库给不出位置就不适用。
95
+ 缺依赖时脚本会打印**可操作提示**(装什么、装到哪、Windows 上装完还报错多半是解释器指错了),
96
+ 而不是只丢一句 `No module named`。
97
+ - 栏目结构模板:简介 / **准备材料** / 入口与地址 / 操作手册 / 常见问题 / 自检清单,
98
+ 以及账号密码各自一个 `sensitive` 字段、正文只写「见〈某字段〉」。
99
+ - `30-pitfalls` 记的是真做一份 52 页手册时踩到的:51 张图只该留 18 张、
100
+ 占位符忘换附件 id、本地路径渲染不出、`file add` 默认移动原件、
101
+ 扫描件抽出来是空、md 图片曾被附件中心误判「未引用」。
102
+ - 已端到端验证:探查 → 抽图 48 张 → 上传 → 建栏目/字段/条目 → 写 md →
103
+ `file get` 的 `usedBy` 正确指向本条目、未引用数 = 0。
104
+ - 主 skill 补了 `md` 字段与图片引用的说明(此前完全没提,是缺口)。
105
+
106
+
107
+ ### Fixed
108
+
109
+ - **短明文打码**:原规则长度 ≤ 12 的值直接显示全明文——账号 14611 整条明文。现在
110
+ 账号/密码/token/secret/身份证/电话/银行等敏感类字段一律 `maskValue`(maskValue('14611')='**');
111
+ 链接/镜像等 URL 类仍明文。
112
+ - **关键 key 字段识别**:从文件名启发式改为 key/label 文本匹配(`account|password|token|...`),不再
113
+ 依赖文件名含数字(`sqlh` 等字母名字段也能正确入台账)。
114
+ - **md 正文里的图片在附件中心被显示成「未引用」**:`fileReferences` 只扫 `type: 'file'` 的字段,
115
+ 完全不看 `md` 字段正文里的 `![说明](f_xxx)` —— 而图片是内嵌在 markdown 里的正式引用。
116
+ 实际影响:一份 18 张截图的 SOP 容器平台栏目,全部 18 张图都被判成孤儿,
117
+ 「未引用」筛选会把它们捞出来,看着像附件丢了。
118
+ - 现在 `file` 与 `md` 两种引用都反查,并给出栏目 / 条目 / 字段的完整定位。
119
+ - `collectFileSections` 同步放开:只有 `md` 字段的栏目也出现在附件中心的栏目筛选里
120
+ (原先只有 `file` 字段的栏目才出现,按栏目筛选时根本找不到 md 图片所在栏目)。
121
+ - **顺带修掉一个更隐蔽的坑**:命中判定原先是 `findFileRecord(store, id) === rec`
122
+ (对象引用相等)。调用方拿到的通常是 `listFiles` 加工过的**副本**,引用不同 →
123
+ 静默判成「未引用」。改成按 id / 唯一文件名判定,副本传参也能命中。
124
+
125
+ ## [0.13.0] - 2026-10-18
126
+
127
+ > **BREAKING**:`nx-sk export run` / `export list`(可读导出 JSON / Markdown)已移除。
128
+ > 导出现在**只有 `nx-sk transfer` 一条路径**(`export` / `import` / `bundle-export` /
129
+ > `bundle-import` / `import-new`)。详见 [Removed](#removed)。
130
+
131
+ ### Removed
132
+
133
+ - **可读导出(JSON / Markdown)整体删除**:`nx-sk export run` / `export list`、
134
+ `POST /api/export`、`GET /api/exports`、`components/readableExport.jsx`、
135
+ `store.exports`(历次导出历史)与 `MAX_EXPORT_HISTORY` 全部移除。
136
+ 它和备份是同一类东西 —— 「往本机目录再写一份副本」,而 ZIP 导出已经把这件事做完了,
137
+ 两套口径并存只会漂移(谁也不知道该看哪一份)。
138
+ - 设置页移除:可读导出面板(格式 / 输出目录 / 凭据开关 / 历次导出 / 目录内文件),
139
+ 以及「默认导出格式」「默认导出目录」「导出默认带明文密钥」三项设置;
140
+ `SETTING_KEYS` 里的 `exportDir` / `exportFormat` / `includeSecretsInExport` 一并去掉。
141
+ - 栏目页「更多」里的「导出 JSON / Markdown」菜单项移除(ZIP 导出 / 导入不动)。
142
+ - **「导出导入」页与 transfer 五条命令完全不动** —— 那是本项目唯一的导出路径。
143
+
7
144
  ## [0.12.0] - 2026-10-18
8
145
 
9
146
  > **BREAKING**:`nx-sk backup *` 命令与 `.nxskbak` 格式已移除。改用 `nx-sk transfer export` /
@@ -92,7 +229,6 @@
92
229
  出来的包就是唯一回退手段。
93
230
  - 跨机敏感字段的重新加密逻辑迁到 `transfer`:导入后用**本机 vaultKey** 重加密落盘,
94
231
  与手填字段没有区别。
95
- - 面板里可读的 JSON / Markdown 导出移到**设置**页(人读副本,不参与导入)。
96
232
  - 导航名由「导出与备份」改为「导出导入」;旧书签 `#/backup` 自动落到「设置」。
97
233
  - 旧命令 `nx-sk backup export|import` 报「未知命令」,请改用 `transfer export` /
98
234
  `transfer import-new`(旧 `.nxskbak` 文件不再可导入)。
package/README.md CHANGED
@@ -76,14 +76,20 @@ ln -s "$(pwd)/assets/nx-sk" "$HOME/.workbuddy/skills/nx-sk"
76
76
 
77
77
  ## 从 0.11.x 升级
78
78
 
79
- **0.12.0 有不兼容变更:备份模块已删除。**
79
+ **导出只剩 `nx-sk transfer` 一条路径。** 备份(0.12.0 删)与可读导出 JSON / Markdown
80
+ (0.13.0 删)都是「往本机目录再写一份副本」,而 ZIP 导出已经覆盖了这两件事。
80
81
 
81
82
  | 原来 | 现在 |
82
83
  | --- | --- |
83
84
  | `nx-sk backup export --password 口令` | `nx-sk transfer export <栏目> --passphrase 口令 --out x.zip` |
84
85
  | `nx-sk backup import --file x.nxskbak` | `nx-sk transfer import-new --file x.zip` |
86
+ | `nx-sk export run --format both --out DIR` | `nx-sk transfer bundle-export --out x.zip`(ZIP,不是可读副本) |
87
+ | `nx-sk export list` | ——(已删;文件由你放到哪就在哪) |
85
88
  | 面板「导出与备份」 | 面板「导出导入」(栏目全选即整库) |
86
89
 
90
+ 仍然可用的命令:`transfer export` / `transfer import` / `transfer bundle-export` /
91
+ `transfer bundle-import` / `transfer import-new`。
92
+
87
93
  - **旧的 `.nxskbak` 文件不再可导入。** 升级前请先在 0.11.x 下导出一份
88
94
  (`nx-sk transfer bundle-export --out x.zip`),升级后用 `transfer import-new --file x.zip` 导回。
89
95
  - 破坏性删除(删栏目 / 条目 / 附件)**不再自动留快照** —— 删之前先导一份。
@@ -147,8 +153,6 @@ CLI 与 API 同源,skill 驱动 agent**。落到具体功能上:
147
153
  | `nx-sk file restore <id> [--to DIR] [--copy]` | **可逆**:移回原始位置并撤销台账;`--copy` 只导出副本 |
148
154
  | `nx-sk file content <id>` | 浏览器链接(内联预览 / `?download=1` 下载);CLI 打印物理路径 |
149
155
  | `nx-sk file open <id>` | 用系统默认程序打开原件 |
150
- | `nx-sk export run [--format json\|md\|both] [--section id] [--with-secrets] [--out DIR] [--dry-run]` | 整体导出 |
151
- | `nx-sk export list` | 历次导出与目录内文件 |
152
156
  | `nx-sk transfer export <栏目> [--entries 条目,条目] [--passphrase 口令] [--out 文件]` | 栏目/选中条目 + 引用附件打成 ZIP,可设口令加密(栏目页「更多」里同源下载) |
153
157
  | `nx-sk transfer import <栏目> [--file ZIP] [--on-conflict skip\|copy\|overwrite] [--dry-run]` | 预览/导入到指定栏目;附件按 sha256 复用 |
154
158
  | `nx-sk transfer import-new [--file ZIP] [--dry-run]` | **零配置**:按包内结构新建栏目(id 冲突加序号) |
@@ -240,13 +244,6 @@ nx-sk setting set --set 'navGroups=[]' # 清空 = 全部栏目恢复平铺
240
244
 
241
245
  分组只是**显示配置**(存在 `settings.navGroups`):不动栏目与条目的数据,删掉栏目时头部自动略过。
242
246
 
243
- 整体导出(落到本机目录的**人可读副本**,凭据默认含明文):
244
-
245
- ```bash
246
- nx-sk export run --format both --out D:/备份/nx-sk
247
- nx-sk export list
248
- ```
249
-
250
247
  ### 全局导出 / 导入(多栏目)
251
248
 
252
249
  **项目里只有一个导出导入模块,没有独立的「备份」模块。** 导出的包就是唯一回退手段:
@@ -318,20 +315,65 @@ export NX_SK_PASSPHRASE='你的长口令' # 之后所有密文都用它派
318
315
  ## 给 agent 用
319
316
 
320
317
  ```bash
321
- nx-sk skill install # 装到 ~/.claude/skills/nx-sk(三态,不静默覆盖)
318
+ nx-sk skill list # 内置 skill 清单
319
+ nx-sk skill install # 装到 ~/.claude/skills/nx-sk(三态,不静默覆盖)
322
320
  nx-sk skill get nx-sk --json # {skillName, ref, content, contentBytes, install}
323
321
  nx-sk skill get nx-sk 10-sections-entries # 裸名 → references/10-sections-entries.md
324
322
  ```
325
323
 
326
- 内置 skill 的 ref 路由表:
324
+ `nx-sk` 的 ref 路由表:
327
325
 
328
326
  | ref | 何时读 |
329
327
  | --- | --- |
330
328
  | `00-design` | 为什么这么分层、错误码与 `{status}` 的分工 |
331
329
  | `10-sections-entries` | 填/改信息的完整命令与 AI 补全流程 |
332
330
  | `20-secrets` | 密文机制、密钥来源、排查「解不开」 |
333
- | `30-export-backup` | 导出、快照与回滚 |
334
331
  | `40-files` | 附件库:导入(默认移动)与还原、`file` 字段与条目的链接、附件中心由栏目驱动 |
332
+ | `50-deploy` | 存/取 Dockerfile、compose、k8s 清单 |
333
+ | `60-transfer` | 栏目 ZIP:导出、预览、导入、冲突策略 |
334
+
335
+ ### sk-pdf:PDF → 图文 SOP 栏目
336
+
337
+ 把用户手册、操作指南转成 nx-sk 里的图文 SOP 栏目(抽正文、按版面挑截图、
338
+ 上传成附件、在 `md` 字段里 `![]()` 引用),带两个已验证的抽取脚本:
339
+
340
+ ```bash
341
+ nx-sk skill install sk-pdf
342
+ nx-sk skill get sk-pdf # 全文
343
+ nx-sk skill get sk-pdf 30-pitfalls # 踩过的坑
344
+
345
+ # 不装也能用(唯一第三方依赖是 PyMuPDF)
346
+ python assets/sk-pdf/scripts/pdf_probe.py book.pdf # 先探查:有没有图、是不是扫描件
347
+ python assets/sk-pdf/scripts/pdf_extract.py book.pdf --out /tmp/out
348
+ ```
349
+
350
+ | ref | 何时读 |
351
+ | --- | --- |
352
+ | `10-extract` | 为什么必须按版面坐标排序取图(顺序错 = 图全配错步骤) |
353
+ | `20-sop-schema` | 栏目六段结构(简介/准备材料/入口/手册/常见问题/自检)、账号怎么拆 sensitive 字段 |
354
+ | `30-pitfalls` | 占位符忘换 id、本地路径渲染不出、密码泄漏、md 图片被误判未引用… |
355
+
356
+ ### sk-thesis:毕业论文格式与结构
357
+
358
+ 按信息工程学院 2022 版规范(设计开发类)把格式做成**可查的字段**与**可用的 Word 模板**:
359
+ 页面 2.5/2/2.5/2 cm、1.5 倍行距、宋体小四、标题黑体四号/黑体小四/宋体小四、三线表 1.5·1 磅、
360
+ 目录域与分节页码(封面与目录不编、标题页起为 1)。
361
+
362
+ ```bash
363
+ nx-sk section add thesis --template thesis # 新建「毕业论文」栏目(9 组 / 59 字段)
364
+ nx-sk section get thesis # 全部硬指标 + 交稿自检清单
365
+ nx-sk skill get sk-thesis 10-format # 数值速查
366
+
367
+ # 重新生成模板(本机 managed venv 已装 python-docx)
368
+ ~/.workbuddy/binaries/python/envs/default/Scripts/python.exe \
369
+ assets/sk-thesis/scripts/make_thesis_template.py
370
+ ```
371
+
372
+ | ref | 何时读 |
373
+ | --- | --- |
374
+ | `10-format` | 页边距、字号、标题层级、图表、参考文献、页码的全部数值 |
375
+ | `20-structure` | 章节骨架、每章写什么、开题与论文的格式差异、装订顺序 |
376
+ | `30-pitfalls` | 真实错法:一级标题加粗、图题「图 1」多空格、(1)自动编号带悬挂缩进、批注没删 |
335
377
 
336
378
  ## 开发
337
379
 
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: kv-source
3
+ description: 在 nx-sk 的栏目里做「原始资料 ↔ 结构化字段」的双向同步——把杂乱资料(邮件、聊天记录、截图、PDF 摘录、手抄的表、网址)先原样收进条目的「原始资料」袋,再按栏目字段字典整理成结构化字段;也支持反向把已确认的字段值回写进袋子留痕。当用户说"把这些乱的整理进 nx-sk"、"这堆资料归到字段里"、"source 和字段同步一下"、"把邮件/截图/文档存下来并归档"、"原始资料整理成表"、"回写一下出处",或给来一堆没格式的内容却已经指定了栏目/条目时使用。触发词:source、原始资料、乱入、整理、同步、归档、乱数据、收件箱、字段同步、资料袋。不适用:用户已经说清要写哪个字段(直接 entry update 即可)、栏目结构本身还没定(先走 sk-needdo 定结构)、PDF 要转成图文 SOP(用 sk-pdf)。
4
+ ---
5
+
6
+ # kv-source · 原始资料 ↔ 字段 的双向同步
7
+
8
+ 一句话:**先原样收下,再按字典整理;整理完留痕,两头能对回去。**
9
+
10
+ 每个栏目都自带一个「原始资料」袋(`source` 字段)。它是**乱入区**:
11
+ 真实数据几乎从不是一次填对的——邮件、截图、PDF 摘录、一段手抄的表,全都先有个
12
+ 「还没整理」的阶段。没有这个袋子,用户只有两个坏选择:把乱数据硬塞进结构化字段
13
+ (把结构搞脏),或者干脆不存(信息丢了)。
14
+
15
+ ## 三条铁律
16
+
17
+ 1. **原文不改写。** 袋里的东西是**原始证据**:摘要是可以重写的,原文不能只剩摘要。
18
+ 整理的结果写进**字段**(或写进**新的一条**),不就地改写原件——否则「原始」这个词就没意义了。
19
+ 2. **不确定就留空。** 袋里的资料经常是半截的、含糊的、有歧义的。
20
+ 拿不准的值**不写进字段**(留空比写错好:写错会被当成事实传下去),把猜测写进该条的 `note`。
21
+ 3. **敏感内容不进袋。** 袋是**明文**的(不加密),密码/token 走「密钥」栏目或 `sensitive` 字段。
22
+ 往袋里贴明文密码 = 把凭据从密文降级成明文,这是不可逆的降级。
23
+
24
+ ## 冲突:以新的为准,旧的留痕
25
+
26
+ 同一件事有了新版本(又来一封邮件、密码改了)——**不要覆盖、不要删**:
27
+
28
+ - **同名即冲突**。时间新的那条是「当前版本」;旧的被标成「已被取代」但**仍留在袋里**
29
+ (原始证据要能翻回去看)。面板默认把旧的收起,点「已被取代的 N 条(留痕)」可展开。
30
+ - **时间以 `--at` 为准**:`--at 2026-10-19` 记的是**资料自身的时间**(邮件时间),
31
+ 缺省用入袋时间。所以「谁更新」看的是资料的时间,不是你什么时候加进去的。
32
+ - 名字不同但确实是同一件事(如「分配邮件」vs「李晶老师的邮件」):用
33
+ `--supersedes <旧id>` 显式指明——旧条同样被标成已取代。
34
+ - **字段也要跟着更新**:新资料覆盖字段里的旧值(`entry update --data`),
35
+ 旧资料留在袋里 ——「现在的值」与「它是怎么来的」同时成立。
36
+ - 手动标:`nx-sk source update <条目> --id <旧id> --superseded`。
37
+
38
+ ```bash
39
+ nx-sk source add <条目> --text "新密码…" --name "分配邮件" --at 2026-10-19
40
+ nx-sk source add <条目> --text "…" --name "李晶老师的邮件" --supersedes s_abc123
41
+ ```
42
+
43
+ ## 命令速查
44
+
45
+ ```bash
46
+ nx-sk source list # 各栏目的袋概况(多少条目有袋、袋里多少条)
47
+ nx-sk source get <条目> --json # 看某个条目的袋(item 明细 + 附件信息)
48
+ nx-sk source add <条目> --text "…" # 加一条文字(原样存)
49
+ nx-sk source add <条目> --file <路径> # 加一个文件(先进附件库,原件保留不搬走)
50
+ nx-sk source add <条目> --url <网址> # 加一个网址
51
+ nx-sk source update <条目> --id <itemId> --mapped 字段key # 标注「这条已整理到哪些字段」
52
+ nx-sk source remove <条目> --id <itemId> # 删一条(默认不动,删之前先确认)
53
+ nx-sk source ensure # 给缺 source 字段的栏目补上(KV 表除外)
54
+ ```
55
+
56
+ 带时间与取代关系的写法:
57
+
58
+ ```bash
59
+ nx-sk source add <条目> --text "…" --name "分配邮件" --at 2026-10-19 # 记资料自身的时间
60
+ nx-sk source add <条目> --text "…" --name "李晶老师的邮件" --supersedes s_abc # 显式取代旧的
61
+ nx-sk source update <条目> --id s_abc --superseded # 手动标「已被取代」
62
+ ```
63
+
64
+ ## 流程(五步)
65
+
66
+ ### 第 0 步:找到落点
67
+
68
+ ```bash
69
+ nx-sk source list # 哪个栏目有袋、哪些条目有袋
70
+ nx-sk entry list --section <栏目> # 找目标条目(同名用 id)
71
+ ```
72
+
73
+ 用户没指定条目时:**先问**(是新建一条,还是并进已有条目?)。
74
+ 并进已有条目前先 `nx-sk source get <条目>` 看一眼袋里已经有什么,避免重复收。
75
+
76
+ ### 第 1 步:原样收下(先别整理)
77
+
78
+ 一份资料一条 item。文字、文件、网址各是一个 kind:
79
+
80
+ ```bash
81
+ nx-sk source add <条目> --text "邮件原文照抄……" --name "李晶老师的分配邮件"
82
+ nx-sk source add <条目> --file "D:\截图\控制台.png" --name "控制台首屏"
83
+ nx-sk source add <条目> --url "https://qcloud.sicau.edu.cn/login"
84
+ ```
85
+
86
+ - `--file` **默认复制不搬走**原件(这是「加一条资料」,不是 `file add` 的搬运语义)。
87
+ - 一次要收很多份:面板里在「原始资料」抽屉中**多选文件**拖进去,或用 `--file` 重复调用。
88
+ - 文件分发细则(PDF 怎么拆、截图怎么批量进、表格怎么变成清单行)见 [[20-intake]]。
89
+
90
+ ### 第 2 步:读袋子
91
+
92
+ ```bash
93
+ nx-sk source get <条目> --json
94
+ ```
95
+
96
+ 返回的 `items[]` 形如 `{id, kind, name, text|fileId|url, addedAt, note, mapped}`。
97
+ `file` kind 的 item 要读内容就看 `files[fileId].path`(物理路径)或 `files[fileId].downloadUrl`。
98
+
99
+ ### 第 3 步:映射(source → 字段)
100
+
101
+ 先看**目标栏目的字段台账**(不要凭印象猜字段名):
102
+
103
+ ```bash
104
+ nx-sk entry fields --section <栏目> --json
105
+ ```
106
+
107
+ 逐条 item → 字段的映射表(心里列出来,或写进回复给用户过目):
108
+
109
+ | item | 内容 | 目标字段 | 把握 |
110
+ | --- | --- | --- | --- |
111
+ | 分配邮件 | VPN 账户 14611 / Lj198900@# | `vpnAccount` / `vpnPassword` | 高(原文直给)|
112
+ | 截图 | 控制台地址 | `accessUrl` | 中(OCR 有误风险)|
113
+
114
+ **把握不足的字段不写**——留空,并在该 item 的 `note` 里写清「疑似是什么、为什么没填」。
115
+
116
+ 写入(**先 dry-run**):
117
+
118
+ ```bash
119
+ nx-sk entry update <条目> --data @整理.json --dry-run # 先看会改什么
120
+ nx-sk entry update <条目> --data @整理.json # 确认后真写
121
+ ```
122
+
123
+ `整理.json` 形如 `{"vpnAccount":"14611","vpnPassword":"Lj198900@#"}`
124
+ (键用字段 key 或中文标签都行;敏感字段是 `sensitive` 的话值照写,加密由服务端做)。
125
+
126
+ ### 第 4 步:留痕(字段 → source,双向闭环)
127
+
128
+ **整理完给用上的 item 打锚点**,这样下一次同步知道「这条已经用过」,不会重复整理:
129
+
130
+ ```bash
131
+ nx-sk source update <条目> --id <itemId> --mapped vpnAccount,vpnPassword
132
+ ```
133
+
134
+ 反过来(**字段 → source**):把已确认的字段值也回写进袋子,出处留痕:
135
+
136
+ ```bash
137
+ nx-sk source add <条目> --text "14611" --name "VPN 账号(已确认)" --mapped vpnAccount --note "整理自 vpnAccount 字段"
138
+ ```
139
+
140
+ 这样「哪个字段的值是从哪条原始资料来的」永远能反查回来。
141
+
142
+ ### 第 5 步:自检
143
+
144
+ ```bash
145
+ nx-sk entry get <条目> --json # 字段值对不对
146
+ nx-sk source get <条目> --json # 袋里每条的 mapped 对不对
147
+ nx-sk section dump <栏目> --json # 完整度与未填清单(袋子不计入完整度)
148
+ ```
149
+
150
+ ## 面板里怎么用
151
+
152
+ 条目表单底部有一条**折叠条**「原始资料 · N 条」(暂存区不该在日常填表时占地方)。
153
+ 点开是右侧抽屉:左边清单、中间正文。
154
+
155
+ - 拖文件进左栏 → 自动上传并入袋
156
+ - 「+ 选择文件」按钮 → 同样的事(**拖拽不是唯一入口**:WCAG 2.2 AA 要求键盘/单指针也能完成)
157
+ - 粘贴框 + Ctrl+Enter → 加一条文字
158
+ - 左栏点一条 → 右边看内容;「移除」删掉这一条
159
+
160
+ ## 常见坑
161
+
162
+ - **同名条目**:一个栏目里两条同名时 `entry get` 会报 CONFLICT,用 id 定位。
163
+ - **袋里没有 source 字段**:老栏目是模板实例化时建的,source 字段是后加的 → `nx-sk source ensure` 补齐。
164
+ - **KV 栏目(密钥)没有袋**:它的形状就是一行一个键值对,加字段会散掉。密钥走 `nx-sk key set`。
165
+ - **`--mapped` 是数组**:可以 `--mapped a,b`(逗号分隔),也可以重复给。
166
+ - **别用 `entry update --set source=...` 手写整袋 JSON**:那是给程序用的口子,人用 `source add` 更安全(少写错一个括号就毁掉整袋)。
@@ -0,0 +1,155 @@
1
+ # 10 · 双向同步手册
2
+
3
+ > `kv-source` skill 的详细页。**什么时候读**:袋里有东西要整理(第 3 步)、
4
+ > 或要把字段值回写进袋(第 4 步)时。
5
+
6
+ ## 两种方向,先分清
7
+
8
+ | 方向 | 什么时候用 | 命令 | 结果落在哪 |
9
+ | --- | --- | --- | --- |
10
+ | **source → 字段** | 袋里有杂乱资料,要整理成结构化值 | `entry update --data` | 栏目的**字段** |
11
+ | **字段 → source** | 字段值已确认,想留出处/回溯 | `source add --mapped` | 袋里的**新一条** |
12
+ | **标锚点**(双向闭环) | 某条资料已经整理过了 | `source update --mapped` | 该 item 的 `mapped` |
13
+
14
+ **别把三件事混成一件**:
15
+ 「字段里已经填了值」不等于「袋里那条已经用掉」。
16
+ `mapped` 是**袋这一侧**的账,写在 item 自己身上——所以「哪些资料还没被整理过」永远查得出来。
17
+
18
+ ## 整理前的映射表
19
+
20
+ 一次整理一张表就够(写在回复里给用户过目也行):
21
+
22
+ | item(id) | 内容摘要 | 目标字段 | 把握 | 处理 |
23
+ | --- | --- | --- | --- | --- |
24
+ | s_abc | 「VPN 账户:14611」 | vpnAccount | 高 | 写 |
25
+ | s_abc | 「Lj198900@#」 | vpnPassword | 高 | 写(sensitive 字段自动加密)|
26
+ | s_def | 截图里的一串端口号 | registry | 低 | **不写**,note 里记「疑似端口,待确认」|
27
+
28
+ 「把握」这一列是纪律的核心:
29
+ - **高** = 原文直给,没有歧义 → 写
30
+ - **中** = 需要推断但只有一种解释 → 写,但在回复里点名说明
31
+ - **低** = 多种解释 / 缺上下文 / OCR 可能错 → **不写**,留空 + note 记疑点
32
+
33
+ **留空比写错好**:完整度会告诉你「这个字段还没填」,但错误的值会被当成事实一路传下去。
34
+
35
+ ## 写回的形状
36
+
37
+ `整理.json`(`--data @file.json`):
38
+
39
+ ```json
40
+ {
41
+ "vpnAccount": "14611",
42
+ "vpnPassword": "Lj198900@#",
43
+ "accessUrl": "https://qcloud.sicau.edu.cn/login"
44
+ }
45
+ ```
46
+
47
+ 键用 **field key** 或**中文标签**都认(与 `entry update --set` 同一套规矩)。
48
+ 一次只改要整理的字段——`entry update` 是 PATCH 语义,没出现在 JSON 里的字段不动。
49
+
50
+ **先 dry-run**:
51
+
52
+ ```bash
53
+ nx-sk entry update <条目> --data @整理.json --dry-run
54
+ ```
55
+
56
+ 返回 `wouldChange.fields` 是要写的字段 key、`newFields` 是这次顺带登记的新键
57
+ (字典里没有的键会自动登记,**注意看**——它常常意味着你写错了字段名)。
58
+
59
+ ## 回写(字段 → source)
60
+
61
+ 什么时候要回写:字段里的值是**从别处来的**(邮件、手抄、别人告诉你的),
62
+ 需要在袋里留一份带出处的副本。
63
+
64
+ ```bash
65
+ nx-sk source add <条目> --text "14611" --name "VPN 账号(李晶老师邮件)" \
66
+ --mapped vpnAccount --note "整理自 vpnAccount 字段"
67
+ ```
68
+
69
+ 回写出来的 item 长这样(`mapped` 让它和字段互相指认):
70
+
71
+ ```json
72
+ {
73
+ "id": "s_xyz",
74
+ "kind": "text",
75
+ "name": "VPN 账号(李晶老师邮件)",
76
+ "text": "14611",
77
+ "addedAt": "2026-10-20T…",
78
+ "note": "整理自 vpnAccount 字段",
79
+ "mapped": ["vpnAccount"]
80
+ }
81
+ ```
82
+
83
+ **别回写敏感值**:`sensitive` 字段的明文不该出现在袋里(袋是明文的)。
84
+ 密码/token 留在密文字段里,袋里只记「见〈某字段〉」。
85
+
86
+ ## 标锚点
87
+
88
+ ```bash
89
+ nx-sk source update <条目> --id s_abc --mapped vpnAccount,vpnPassword
90
+ ```
91
+
92
+ `--mapped` 接受逗号分隔。想清掉就传空:`--mapped ""`。
93
+
94
+ `source update` **不改正文**——它只改名字、备注、锚点。
95
+ 要改内容就删掉重加(`source remove` + `source add`):
96
+ 原文一旦被就地改写,「原始」这个词就不成立了。
97
+
98
+ ## 冲突:以新的为准,旧的留痕
99
+
100
+ 资料会**更新**:老师又发一封邮件说密码改了、同一个链接换了地址。这时候**不要覆盖、不要删**。
101
+
102
+ ### 规则
103
+
104
+ | 情形 | 判定 | 动作 |
105
+ | --- | --- | --- |
106
+ | 两条同名 | 冲突 | 时间新的 = 当前版本;旧的自动标「已被取代」,仍留在袋里 |
107
+ | 时间用哪个 | `at`(资料自身时间,如邮件时间)优先,缺省用入袋时间 | 加资料时用 `--at 2026-10-19` 写清 |
108
+ | 名字不同但同一件事 | 人工判定 | 新条加 `--supersedes <旧id>`,旧条同样被标取代 |
109
+ | 手动标记 | 任意 | `nx-sk source update <条目> --id <旧id> --superseded` |
110
+
111
+ ### 为什么「留痕」比「覆盖」重要
112
+
113
+ 密码改过三次之后,**「现在的密码」和「它为什么是这个」一样重要**。
114
+ 覆盖掉旧资料,就再也说不清「当时为什么发的那封邮件里写的是另一个值」——
115
+ 而这类问题(密码到底哪个是对的、哪个已失效)恰恰是**事后**才会冒出来的。
116
+ 所以:字段里放**当前值**,袋里留**全部版本**(旧的折叠但可查)。
117
+
118
+ ### 整理时怎么处理冲突
119
+
120
+ 1. `nx-sk source get <条目> --json` → 看 `items[]`,`superseded: true` 的是旧版。
121
+ 2. 写字段时用**当前版本**(没被取代的、时间最新的那条)的值。
122
+ 3. 写完给用上的 item 打锚点:`source update --id <id> --mapped 字段key`。
123
+ 4. 旧版**不用管**(它已经被自动标取代),但也别删。
124
+
125
+ ```bash
126
+ # 又来一封邮件(同名 + 新时间)→ 新的自动成为当前版本,旧的自动留痕
127
+ nx-sk source add <条目> --text "密码改成 NewPass2 了" --name "分配邮件" --at 2026-10-19
128
+ # 字段跟着更新
129
+ nx-sk entry update <条目> --data @更新.json --dry-run && nx-sk entry update <条目> --data @更新.json
130
+ # 给新条打锚点
131
+ nx-sk source update <条目> --id <新条id> --mapped vpnPassword
132
+ ```
133
+
134
+ ## 查「还有哪些没整理」
135
+
136
+ ```bash
137
+ nx-sk source get <条目> --json
138
+ ```
139
+
140
+ 然后看每个 item 有没有 `mapped`:
141
+
142
+ ```bash
143
+ nx-sk source get <条目> --json | node -e "
144
+ let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{
145
+ const j=JSON.parse(s);
146
+ const un=(j.items||[]).filter(i=>!(i.mapped||[]).length);
147
+ console.log('未整理:', un.length, un.map(i=>i.name||i.id).join('、'));
148
+ })"
149
+ ```
150
+
151
+ ## 一次性整理多个条目
152
+
153
+ 袋是**按条目**分的,所以「整理一批」= 对每个条目各走一遍第 2–4 步。
154
+ 批量写盘时**每个条目一次 `--dry-run`**(`entry update` 没有跨条目的批量模式):
155
+ 逐条 dry-run、逐条确认、逐条写。写错了也只错一条。