@topmindspace/topmind-writing-skills 0.8.49 → 0.8.51

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 (166) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.en.md +29 -17
  3. package/README.md +31 -19
  4. package/bin/topmind-writing-skills.js +16 -2
  5. package/lib/host-pack.cjs +35 -0
  6. package/lib/zip.cjs +18 -0
  7. package/package.json +6 -2
  8. package/runtime/article-renderer.d.mts +9 -0
  9. package/runtime/article-renderer.mjs +130 -0
  10. package/runtime/host-guide.md +7 -0
  11. package/topmind-briefs/README.en.md +11 -54
  12. package/topmind-briefs/README.md +13 -74
  13. package/topmind-briefs/SKILL.md +36 -166
  14. package/topmind-briefs/package.json +1 -1
  15. package/topmind-briefs/references/core-voice.md +63 -307
  16. package/topmind-briefs/references/creative-practice.md +112 -0
  17. package/topmind-briefs/references/de-ai-rules.md +22 -22
  18. package/topmind-briefs/references/emoji-system.md +11 -118
  19. package/topmind-briefs/references/examples.md +1 -1
  20. package/topmind-briefs/references/image-sourcing.md +13 -41
  21. package/topmind-briefs/references/information-design.md +40 -115
  22. package/topmind-briefs/references/platform-specs.md +17 -85
  23. package/topmind-briefs/references/research-mode.md +11 -23
  24. package/topmind-briefs/references/revision-workflow.md +34 -37
  25. package/topmind-briefs/references/social-writing-principles.md +24 -59
  26. package/topmind-briefs/references/x-delivery-contract.md +20 -126
  27. package/topmind-briefs/references/x-layout-patterns.md +12 -71
  28. package/topmind-briefs/references/x-post-format.md +17 -90
  29. package/topmind-briefs/scripts/audit_skill.py +8 -6
  30. package/topmind-briefs/scripts/de_ai_lint.py +19 -19
  31. package/topmind-briefs/scripts/format_text.py +24 -41
  32. package/topmind-briefs/scripts/lint_x_post.py +18 -50
  33. package/topmind-cover/README.en.md +11 -53
  34. package/topmind-cover/README.md +14 -50
  35. package/topmind-cover/SKILL.md +49 -184
  36. package/topmind-cover/package.json +1 -1
  37. package/topmind-cover/references/core-voice.md +63 -307
  38. package/topmind-cover/references/cover-combos.md +5 -3
  39. package/topmind-cover/references/cover-patterns.md +3 -1
  40. package/topmind-cover/references/cover-prompts.md +10 -8
  41. package/topmind-cover/references/cover-styles.md +10 -8
  42. package/topmind-cover/references/creative-practice.md +112 -0
  43. package/topmind-cover/references/design-system.md +15 -15
  44. package/topmind-cover/references/image-sourcing.md +13 -41
  45. package/topmind-cover/references/impact-language.md +15 -91
  46. package/topmind-cover/references/information-design.md +40 -115
  47. package/topmind-cover/references/md3-foundation.md +2 -0
  48. package/topmind-cover/references/scene-builder.md +53 -179
  49. package/topmind-cover/references/social-writing-principles.md +24 -59
  50. package/topmind-cover/references/typography-system.md +2 -0
  51. package/topmind-cover/scripts/audit_skill.py +8 -6
  52. package/topmind-cover/scripts/check-title.py +5 -5
  53. package/topmind-cover/scripts/de_ai_lint.py +19 -19
  54. package/topmind-cover/scripts/negative_tests.py +12 -12
  55. package/topmind-cover/scripts/title-engine.py +11 -6
  56. package/topmind-format/README.en.md +11 -30
  57. package/topmind-format/README.md +13 -47
  58. package/topmind-format/SKILL.md +29 -115
  59. package/topmind-format/package.json +1 -1
  60. package/topmind-format/references/core-voice.md +63 -307
  61. package/topmind-format/references/creative-practice.md +112 -0
  62. package/topmind-format/references/de-ai-rules.md +22 -22
  63. package/topmind-format/references/emoji-system.md +11 -118
  64. package/topmind-format/references/information-design.md +40 -115
  65. package/topmind-format/references/social-writing-principles.md +24 -59
  66. package/topmind-format/references/x-delivery-contract.md +20 -126
  67. package/topmind-format/references/x-layout-patterns.md +12 -71
  68. package/topmind-format/references/x-post-format.md +17 -90
  69. package/topmind-format/scripts/audit_skill.py +8 -6
  70. package/topmind-format/scripts/de_ai_lint.py +19 -19
  71. package/topmind-format/scripts/format_text.py +24 -41
  72. package/topmind-format/scripts/lint_x_post.py +18 -50
  73. package/topmind-format/scripts/negative_tests.py +6 -6
  74. package/topmind-poster/README.en.md +9 -44
  75. package/topmind-poster/README.md +12 -41
  76. package/topmind-poster/SKILL.md +30 -255
  77. package/topmind-poster/package.json +1 -1
  78. package/topmind-poster/references/creative-practice.md +112 -0
  79. package/topmind-poster/references/image-sourcing.md +13 -41
  80. package/topmind-poster/references/information-design.md +40 -115
  81. package/topmind-poster/references/social-writing-principles.md +24 -59
  82. package/topmind-poster/scripts/audit_skill.py +8 -6
  83. package/topmind-revise/README.en.md +10 -63
  84. package/topmind-revise/README.md +11 -58
  85. package/topmind-revise/SKILL.md +33 -139
  86. package/topmind-revise/package.json +1 -1
  87. package/topmind-revise/references/calibration-drill.md +5 -5
  88. package/topmind-revise/references/core-voice.md +63 -307
  89. package/topmind-revise/references/creative-practice.md +112 -0
  90. package/topmind-revise/references/de-ai-rules.md +22 -22
  91. package/topmind-revise/references/fact-check.md +4 -4
  92. package/topmind-revise/references/image-sourcing.md +13 -41
  93. package/topmind-revise/references/information-design.md +40 -115
  94. package/topmind-revise/references/report-template.md +20 -71
  95. package/topmind-revise/references/research-mode.md +11 -23
  96. package/topmind-revise/references/revision-workflow.md +34 -37
  97. package/topmind-revise/references/social-writing-principles.md +24 -59
  98. package/topmind-revise/scripts/audit_skill.py +8 -6
  99. package/topmind-revise/scripts/de_ai_lint.py +19 -19
  100. package/topmind-revise/scripts/lint_x_post.py +18 -50
  101. package/topmind-viral-posts/README.en.md +11 -72
  102. package/topmind-viral-posts/README.md +13 -82
  103. package/topmind-viral-posts/SKILL.md +33 -195
  104. package/topmind-viral-posts/package.json +1 -1
  105. package/topmind-viral-posts/references/core-voice.md +63 -307
  106. package/topmind-viral-posts/references/creative-practice.md +112 -0
  107. package/topmind-viral-posts/references/de-ai-rules.md +22 -22
  108. package/topmind-viral-posts/references/emoji-system.md +11 -118
  109. package/topmind-viral-posts/references/image-sourcing.md +13 -41
  110. package/topmind-viral-posts/references/information-design.md +40 -115
  111. package/topmind-viral-posts/references/patterns.md +4 -20
  112. package/topmind-viral-posts/references/research-mode.md +11 -23
  113. package/topmind-viral-posts/references/revision-workflow.md +34 -37
  114. package/topmind-viral-posts/references/social-writing-principles.md +24 -59
  115. package/topmind-viral-posts/references/tags-and-topics.md +13 -100
  116. package/topmind-viral-posts/references/x-delivery-contract.md +20 -126
  117. package/topmind-viral-posts/references/x-layout-patterns.md +12 -71
  118. package/topmind-viral-posts/references/x-post-format.md +17 -90
  119. package/topmind-viral-posts/scripts/audit_skill.py +8 -6
  120. package/topmind-viral-posts/scripts/de_ai_lint.py +19 -19
  121. package/topmind-viral-posts/scripts/format_text.py +24 -41
  122. package/topmind-viral-posts/scripts/lint_x_post.py +18 -50
  123. package/topmind-wechat-post/README.en.md +10 -115
  124. package/topmind-wechat-post/README.md +13 -114
  125. package/topmind-wechat-post/SKILL.md +36 -257
  126. package/topmind-wechat-post/package.json +1 -1
  127. package/topmind-wechat-post/references/core-voice.md +63 -307
  128. package/topmind-wechat-post/references/creative-practice.md +112 -0
  129. package/topmind-wechat-post/references/de-ai-rules.md +22 -22
  130. package/topmind-wechat-post/references/image-sourcing.md +13 -41
  131. package/topmind-wechat-post/references/information-design.md +40 -115
  132. package/topmind-wechat-post/references/platform-operations.md +173 -0
  133. package/topmind-wechat-post/references/research-mode.md +11 -23
  134. package/topmind-wechat-post/references/revision-workflow.md +34 -37
  135. package/topmind-wechat-post/references/social-writing-principles.md +24 -59
  136. package/topmind-wechat-post/references/typography-rules.md +16 -48
  137. package/topmind-wechat-post/references/workflow.md +1 -1
  138. package/topmind-wechat-post/references/workspace.md +21 -29
  139. package/topmind-wechat-post/references/writing-quality.md +15 -97
  140. package/topmind-wechat-post/references/x-delivery-contract.md +20 -126
  141. package/topmind-wechat-post/references/x-post-format.md +17 -90
  142. package/topmind-wechat-post/scripts/article_scaffold.py +11 -26
  143. package/topmind-wechat-post/scripts/audit_skill.py +8 -6
  144. package/topmind-wechat-post/scripts/de_ai_lint.py +19 -19
  145. package/topmind-wechat-post/scripts/lint_x_post.py +18 -50
  146. package/topmind-wechat-post/scripts/negative_tests.py +2 -2
  147. package/topmind-x-article/README.en.md +10 -113
  148. package/topmind-x-article/README.md +13 -105
  149. package/topmind-x-article/SKILL.md +39 -168
  150. package/topmind-x-article/package.json +1 -1
  151. package/topmind-x-article/references/core-voice.md +63 -307
  152. package/topmind-x-article/references/creative-practice.md +112 -0
  153. package/topmind-x-article/references/de-ai-rules.md +22 -22
  154. package/topmind-x-article/references/image-sourcing.md +13 -41
  155. package/topmind-x-article/references/information-design.md +40 -115
  156. package/topmind-x-article/references/platform-operations.md +34 -0
  157. package/topmind-x-article/references/research-mode.md +11 -23
  158. package/topmind-x-article/references/revision-workflow.md +34 -37
  159. package/topmind-x-article/references/social-writing-principles.md +24 -59
  160. package/topmind-x-article/references/workspace.md +21 -29
  161. package/topmind-x-article/references/x-delivery-contract.md +20 -126
  162. package/topmind-x-article/references/x-post-format.md +17 -90
  163. package/topmind-x-article/scripts/article_scaffold.py +11 -26
  164. package/topmind-x-article/scripts/audit_skill.py +8 -6
  165. package/topmind-x-article/scripts/de_ai_lint.py +19 -19
  166. package/topmind-x-article/scripts/lint_x_post.py +18 -50
package/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ ## [0.8.51] - 2026-10-26
2
+
3
+ - 新增独立宿主分发协议、可复现 ZIP、SHA256SUMS 和 latest.json;保留各技能独立版本与原 npx 安装方式。
4
+ - 新增无 IO 的 HTML/文本排版核心、八个写作动作、宿主能力说明、图片清单及格式诊断,由 Desktop/Obsidian 以固定快照消费。
5
+ - CLI 先完整复制到临时目录,再替换旧技能,复制或切换失败恢复旧版;不再先删除旧安装。
6
+ - 补充公众号/X 长文的宿主适配与原位兼容说明,加入包构建、排版、安全和安装回归。
7
+
8
+ ## [0.8.50] - 2026-10-26
9
+
10
+ ### 创作方式与参考
11
+
12
+ - 重构 8 个技能入口:按任务读取参考、看实际样张、按需探索不同方向;口吻、长度、emoji、开头结尾和视觉风格改为选项。
13
+ - 新增共享 `creative-practice.md`:同素材不同短文、段子与长文结构片段、真实参考阅读和匿名比较方法;事实、隐私、授权、文件恢复与输出完整性继续核对。
14
+ - 清理共享规则与局部经验中的固定判断 / 图片配额、分数定稿门槛、全局角色动作要求;平台操作细节移到按需参考。必要归因、近似值和不确定性保留正文。
15
+
16
+ ### 行为变化
17
+
18
+ - `format_text.py` 默认保留原文表情、链接和来源,`--strip-emoji` / `--separate-links` 显式选择转换;段子支持作者指定首尾表情。
19
+ - `lint_x_post.py` 默认允许文字列表、正文链接 / 来源、段子 emoji;链接分离检查仅在 `--separate-links` 启用。格式残留与缺失输入继续检查。
20
+ - `title-engine.py --check` 默认输出建议,`--strict` 主动选择模板门禁;无效 JSON 和空图层仍失败。
21
+ - 技能结构审计不再要求固定标题与禁用清单;移除把公开来源和致谢当隐私泄漏的封面测试,独立隐私扫描保留。
22
+
23
+ ### 验证与版本
24
+
25
+ - 增加 11 项 CLI 行为回归并接入 CI;更新旧用例预期,新增 12 个创作场景。用例结构检查不等于已执行模型评测或真实盲选。
26
+ - 根包 0.8.49 → 0.8.50;8 个技能各递增 patch,同步独立安装副本与中英文说明。提交并推送 v0.8.50 标签后,由 Release 工作流分发。
27
+
1
28
  ## [0.8.49] - 2026-10-24
2
29
 
3
30
  > 根包 0.8.48 → **0.8.49**(patch);技能版本:topmind-briefs 0.2.27 → 0.2.28、topmind-cover 0.4.16 → 0.4.17、topmind-viral-posts 0.3.19 → 0.3.20、topmind-wechat-post 0.3.19 → 0.3.20。其余技能不变。
package/README.en.md CHANGED
@@ -18,19 +18,31 @@ npx @topmindspace/topmind-writing-skills install topmind-viral-posts # install
18
18
  npx @topmindspace/topmind-writing-skills list # see all
19
19
  ```
20
20
 
21
- ## How it differs from "AI, write me a post"
21
+ ## Desktop and Obsidian integration
22
22
 
23
- - **Pick the type first.** 14 ways to write a duanzi (one-line-per-sentence post) and 9 content types for briefs, each with its own opening, structure and ending.
24
- - **Glanceable.** The X body keeps facts, numbers and the highlight. Verification notes, caliber notes, links and original images are delivered separately, so the body copies straight into X.
25
- - **De-AI with scripts.** Boilerplate phrases, newsreader tone, engagement-bait endings, fake first-hand claims and punctuation tics get flagged line by line; you decide what to change.
26
- - **Long-form starts with judgments.** The skeleton requires "my judgments" up front; material backs them up instead of piling up.
27
- - **Facts stay put.** Revision changes wording, never numbers, quotes or certainty; every edit cites a rule.
23
+ A reviewed release snapshot of [topmind-writing-skills](https://github.com/topmindspace/topmind-writing-skills) is bundled by default: revision, formatting, briefs, social posts, WeChat, X Articles, covers and posters. The writing repository keeps its own release cadence and version.
24
+
25
+ Open a Markdown document and choose **Writing & delivery** from Apps/status/commands in Desktop, or note menus/commands in Obsidian. Edit the source in the main editor, choose a delivery format and theme, then preview, copy or export beside it in `out/`. Existing article folders and WeChat drafts work in place.
26
+
27
+ Built-in HTML/text typography requires no Python. Preview and copy use the same output; changed sources or images require regeneration. Diagnostics identify missing images, diagram source, extended containers and table paste limitations. AI images, PNG rendering and publishing require their respective tools. Copying does not publish; wording scores do not decide editorial quality.
28
+
29
+ About & updates tracks the application, core skills, independent writing pack and integrations separately. Writing resources can update independently; compiled renderer code follows the host release. Managed packs are isolated, local modifications block replacement and failed installs preserve the previous version. Supported Desktop installs can download an update and explicitly restart; manual downloads remain available when signing or the installation channel prevents automatic updates.
30
+
31
+ ## More choices, concrete references
32
+
33
+ - Read actual references and visible specimens, then adapt their methods to the current material.
34
+ - Explore a few distinct directions when useful; deliver one finished result for a clear one-result request.
35
+ - Treat voice, length, emoji, openings and visual style as choices. Current user intent and author samples guide them.
36
+ - Preserve necessary attribution, approximation and uncertainty in public copy. Facts, privacy, authorization and usable output remain concrete boundaries.
37
+ - Linters offer context-sensitive signals, not a style score to maximize. Writing quality still needs real author or reader evaluation.
38
+
39
+ See the [practice and specimens](./shared/references/creative-practice.md) and [audit](./docs/creative-skills-audit.md).
28
40
 
29
41
  ## What it writes
30
42
 
31
43
  | Category | Skill | What it does | Say |
32
44
  |---|---|---|---|
33
- | Duanzi | [`topmind-viral-posts`](./topmind-viral-posts/) | One sentence per line, zero emoji, 14 patterns | "来条段子", "不懂就问", "写个群聊体" |
45
+ | Duanzi | [`topmind-viral-posts`](./topmind-viral-posts/) | X humor and conversational posts; 14 optional patterns | "来条段子", "不懂就问", "写个群聊体" |
34
46
  | Briefs | [`topmind-briefs`](./topmind-briefs/) | One thing, then stop: news, previews, launches, deals, rankings, papers, hands-on | "发个快讯", "来篇短的", "写条预告" |
35
47
  | Long-form · X | [`topmind-x-article`](./topmind-x-article/) | X Article: single-source Markdown → one-click copy HTML + preview image | "写 X 长文", "出一键复制版" |
36
48
  | Long-form · WeChat | [`topmind-wechat-post`](./topmind-wechat-post/) | WeChat articles: three quality gates, inline-image layout, publish checklist | "写公众号", "公众号排版" |
@@ -76,7 +88,7 @@ Each pattern has topics, hook, structure, ending, counter-examples and original
76
88
  | Hands-on | The result (broke, fast, pricey) | Self-roast or a real question |
77
89
  | One-line take | An insider's light judgment | The judgment |
78
90
 
79
- The first-post window is measured in minutes; if you're late, change the angle. Previews and launches are separate posts. See [topmind-briefs](./topmind-briefs/SKILL.md).
91
+ These are optional directions, not fixed openings or endings. Keep preview and launch states distinct; add a fresh angle when useful. See [topmind-briefs](./topmind-briefs/SKILL.md).
80
92
 
81
93
  ## Examples
82
94
 
@@ -112,7 +124,7 @@ Nova:我们也上线了,价格动了
112
124
  单任务成本两毛钱出头...
113
125
  ```
114
126
 
115
- Links, the official chart and caliber notes ("scores are vendor-reported") go to the author separately, not into the body.
127
+ Keep necessary vendor attribution and uncertainty in the copy. Detailed sources and charts can be listed separately; useful links may stay in the body.
116
128
 
117
129
  **De-AI · before → after**
118
130
 
@@ -140,21 +152,21 @@ Removed the newsreader hype, the "not just X but Y" construction and the uplifti
140
152
 
141
153
  More: [duanzi](./topmind-viral-posts/examples/) · [briefs](./topmind-briefs/examples/) · [format](./topmind-format/examples/) · [revise](./topmind-revise/examples/) · [X article](./topmind-x-article/examples/) · [WeChat](./topmind-wechat-post/examples/) · [poster](./topmind-poster/examples/) · [cover](./topmind-cover/assets/examples/)
142
154
 
143
- ## Long-form: judgments first, material second
155
+ ## Long-form: choose a structure that fits
144
156
 
145
157
  One folder per article, created by `article_scaffold.py`:
146
158
 
147
159
  ```text
148
160
  <title>/
149
161
  ├─ <title>.md the only source
150
- ├─ 骨架.md message, reader, my judgments (required), per-section form and figures
162
+ ├─ 骨架.md message, reader, main thread and evidence (optional), per-section form and figures
151
163
  ├─ 素材来源.md where every image and number came from (not published)
152
164
  ├─ images/ cover/ raw/自制图源/
153
165
  └─ out/ X HTML, WeChat HTML, 预览.png (rebuildable)
154
166
  ```
155
167
 
156
- - **My judgments**: 3–5, each with its basis and the likely pushback. A long section with no judgment gets a "material dump" hint.
157
- - **Own diagrams**: method, architecture and comparison pieces get at least one diagram of your own view.
168
+ - **Main thread**: claims and counterexamples for opinion, scenes for stories, tasks and steps for tutorials; no fixed number of judgments.
169
+ - **Diagrams**: originals and custom diagrams are options when they improve understanding; no figure quota.
158
170
  - **Preview**: render `out/预览.png` before delivery; tables X can't paste can become images.
159
171
 
160
172
  ## Rules: principles first
@@ -165,10 +177,10 @@ All skills share layered rules (single source `shared/references/`, copied into
165
177
  |---|---|---|
166
178
  | Principles | `social-writing-principles.md` | Know the message, lead with it, new info every line, form follows content, originals first, reader's side |
167
179
  | Information design | `information-design.md` | Skeleton first, content → form (list / table / figure / paragraph), length and density |
168
- | Voice | `core-voice.md` | Ten wording principles; V01–V40 show them in titles, openings, numbers, questions, replies, duanzi |
180
+ | Voice | `core-voice.md` | Contextual V01–V40 options for titles, openings, numbers, questions, replies and humor |
169
181
  | Review | `de-ai-rules.md` + `topmind-revise` | De-AI patterns (long-form + social S01–S10), rewrite boundaries, fidelity audit |
170
182
 
171
- The persona is **an ordinary person still learning**: talks about what they saw and tried, no lectern, says so when unsure.
183
+ The voice follows the current request, author samples, audience and genre; it is not one fixed persona.
172
184
 
173
185
  ### Scripts hint, people decide
174
186
 
@@ -182,8 +194,8 @@ python3 scripts/de_ai_lint.py article.md --genre long # long-form
182
194
 
183
195
  | Level | Meaning | Required |
184
196
  |---|---|---|
185
- | E error | Markdown leftovers; URLs / source lines / image notes in the body | Must be zero |
186
- | W warning | Boilerplate, engagement-bait endings, forced analogies, English template lines, punctuation tics, over-long replies… | Each needs a decision: fix or justify |
197
+ | E format issue | Likely marker leftovers and image placeholders; URL/source enforcement only with `--separate-links` | Check actual formatting and semantic exceptions |
198
+ | W editorial signal | Patterns, length, literal lists and inline links | Use context; valid choices need no written justification |
187
199
  | H hint / I1–I9 | Context words, long first line, caliber notes in the body, newsreader template; information design (long runs, list candidates, material dumps…) | For judgment only, never in `--strict` |
188
200
 
189
201
  If you have your own post data, `post_type_report.py` shows which openings, endings and types work for you and writes a habits summary for revision; the data stays on your machine.
package/README.md CHANGED
@@ -18,19 +18,31 @@ npx @topmindspace/topmind-writing-skills install topmind-viral-posts # 装一
18
18
  npx @topmindspace/topmind-writing-skills list # 看全部
19
19
  ```
20
20
 
21
- ## 它和「让 AI 随便写一条」有什么不一样
21
+ ## 在 Desktop 与 Obsidian 中使用
22
22
 
23
- - **先定类型,再动笔**:段子有 14 种写法,短文有 9 种内容类型,每种的首行、结构、收尾都不一样。
24
- - **一眼快餐**:X 正文只留事实、数字和亮点;查证过程、口径说明、链接、原图都另发,正文可以直接复制。
25
- - **去 AI 味有脚本**:套话、播报腔、套路互动尾巴、编出来的亲历、标点腔,脚本逐条标出来,人来决定改不改。
26
- - **长文先有判断**:骨架里必须先写「我的判断」,素材给判断作证,不写成资料汇编。
27
- - **事实不动**:改稿只改表达,不改数字、引语和确定性;每处改动都说得出依据。
23
+ 默认内置 [topmind-writing-skills](https://github.com/topmindspace/topmind-writing-skills) 的版本化快照:改稿、只排版、短文、互动短帖、公众号、X 长文、封面与长图。写作仓独立迭代和发布,宿主内置经过验证的版本。
24
+
25
+ 打开 Markdown,Desktop 从应用、状态栏或命令面板进入“写作与交付”;Obsidian 从命令面板或笔记菜单进入。源稿继续在主编辑器修改;选择格式和主题后生成预览,可复制正文或导出到源稿旁的 `out/`。旧 `公众号稿.md` 和文章目录可直接使用,不会被批量改名。
26
+
27
+ 内置 HTML/文本排版无需 Python,预览与复制同源;源稿或图片改变后需要重新生成。扩展容器、图表源码、缺图和平台表格粘贴限制会显示诊断。封面生图、PNG 和外部发布依赖实际工具,复制不等于已发布。审校不使用“AI 味分数”作为定稿门槛。
28
+
29
+ “关于与更新”分别展示 Desktop、核心技能、独立写作包和集成组件。写作资源可独立更新;编译进宿主的排版代码随宿主升级。受管包按身份隔离,覆盖本地定制会被阻止,失败保留旧版。Desktop 支持标准更新器下载后显式重启;签名、平台或渠道不支持时保留手动下载。
30
+
31
+ ## 加选项,让经验帮助创作
32
+
33
+ - **看过再借鉴**:实际阅读参考作品和可见样张,迁移角度、组织、节奏与设计关系。
34
+ - **按需探索**:方向未定时试几种真的不同的写法;明确要一稿就交完成稿,不固定多稿与审批回合。
35
+ - **偏好是选项**:口吻、长度、emoji、开头结尾和视觉风格跟任务与作者走,模板可以拆、混合或不用。
36
+ - **事实边界清楚**:正文保留必要归因、近似值和不确定性;隐私、授权、文件恢复与输出完整性仍明确核对。
37
+ - **工具帮助判断**:lint 提示回到语境,词面分数不作定稿门槛;真实质量通过作者或读者使用验证。
38
+
39
+ 完整方法与同素材样张见 [创作实践](./shared/references/creative-practice.md),整改范围与验证见 [审查报告](./docs/creative-skills-audit.md)。
28
40
 
29
41
  ## 能写什么
30
42
 
31
43
  | 类别 | 技能 | 用途 | 你可以这样说 |
32
44
  |---|---|---|---|
33
- | 段子 | [`topmind-viral-posts`](./topmind-viral-posts/) | 一句一行、零 emoji 的 X 短帖,14 种写法 | 「来条段子」「不懂就问」「写个群聊体」 |
45
+ | 段子 | [`topmind-viral-posts`](./topmind-viral-posts/) | X 段子与互动短帖,14 类参考写法 | 「来条段子」「不懂就问」「写个群聊体」 |
34
46
  | 短文 | [`topmind-briefs`](./topmind-briefs/) | 一件事讲完就停:快讯、预告、新品、羊毛、榜单、论文、实测 | 「发个快讯」「来篇短的」「写条预告」 |
35
47
  | 长文 · X | [`topmind-x-article`](./topmind-x-article/) | X Article:单源 Markdown → 一键复制 HTML + 图文预览 | 「写 X 长文」「出一键复制版」 |
36
48
  | 长文 · 公众号 | [`topmind-wechat-post`](./topmind-wechat-post/) | 公众号长文:质量三关、图片内嵌排版、发布清单 | 「写公众号」「公众号排版」 |
@@ -39,7 +51,7 @@ npx @topmindspace/topmind-writing-skills list # 看全
39
51
  | 封面 | [`topmind-cover`](./topmind-cover/) | X 和公众号封面:22 种风格、标题逐字核对 | 「做个封面」「头图」 |
40
52
  | 长图 | [`topmind-poster`](./topmind-poster/) | HTML → 高清长图 / 海报 / 信息图,文字数字逐字正确 | 「出张长图」「做个榜单图」 |
41
53
 
42
- **短文** = X 纯文本帖,1000 字以内都行;**长文** = X Article 或公众号长文;**段子** = 一句一行、行间空一行的短帖。
54
+ **短文** = X 纯文本帖,1000 字以内都行;**长文** = X Article 或公众号长文;**段子** = 以观察、反差或幽默推进的短帖,排版按节奏选择。
43
55
 
44
56
  ## 段子:14 种写法
45
57
 
@@ -78,7 +90,7 @@ npx @topmindspace/topmind-writing-skills list # 看全
78
90
  | 实测 | 结果一句(翻车、快、贵) | 自嘲或一个真问题 |
79
91
  | 观点一句话 | 一句内行的轻判断 | 判断本身 |
80
92
 
81
- 首发窗口以分钟计,晚了就换角度;预告和正式发布分开写。细则见 [topmind-briefs](./topmind-briefs/SKILL.md)。
93
+ 以下是方向示例,不限定标点或收尾;时效已过时可补新角度,预告与发布保留状态差别。细则见 [topmind-briefs](./topmind-briefs/SKILL.md)。
82
94
 
83
95
  ## 看看效果
84
96
 
@@ -130,7 +142,7 @@ Nova:我们也上线了,价格动了
130
142
  单任务成本两毛钱出头...
131
143
  ```
132
144
 
133
- 链接、官方评测图、「分数是厂商自测」这类口径提醒,都另发给作者,不进正文。
145
+ 正文保留“分数是厂商自测”等影响理解的归因与限定;详细来源、原图可另列,有用的链接也可随文。
134
146
 
135
147
  **去 AI 味 · 改前 → 改后**
136
148
 
@@ -158,21 +170,21 @@ Nova:我们也上线了,价格动了
158
170
 
159
171
  更多示例:[段子](./topmind-viral-posts/examples/) · [短文](./topmind-briefs/examples/) · [格式化](./topmind-format/examples/) · [改稿](./topmind-revise/examples/) · [X 长文](./topmind-x-article/examples/) · [公众号](./topmind-wechat-post/examples/) · [长图](./topmind-poster/examples/) · [封面](./topmind-cover/assets/examples/)
160
172
 
161
- ## 长文:先有判断,再有素材
173
+ ## 长文:结构按内容选择
162
174
 
163
175
  一篇长文一个文件夹,`article_scaffold.py` 一条命令建好:
164
176
 
165
177
  ```text
166
178
  <文章名>/
167
179
  ├─ <文章名>.md 唯一源稿
168
- ├─ 骨架.md 主要信息、读者、我的判断(必填)、每节要点 / 形式 / 图表计划
180
+ ├─ 骨架.md 主要信息、读者、主线与依据(按需)、每节要点 / 形式 / 图表计划
169
181
  ├─ 素材来源.md 每张图、每个数字的出处(不发布)
170
182
  ├─ images/ cover/ raw/自制图源/
171
183
  └─ out/ X 长文 HTML、公众号 HTML、预览.png(随时可重建)
172
184
  ```
173
185
 
174
- - **我的判断**:3–5 条,每条写依据和读者会怎么反驳;一节很长却没有判断,脚本会提示「素材罗列」。
175
- - **自制图**:方法论、架构、对比类文章至少一张,把自己的判断画出来。
186
+ - **主线**:观点稿可列判断与反例,故事可列场景,教程可列任务与步骤,不凑固定数量。
187
+ - **配图**:原图、自制结构图和纯文字按理解价值选择,不设图片配额。
176
188
  - **图文预览**:交付前生成 `out/预览.png` 先看一遍;X 粘贴不了的表格可以自动转成图。
177
189
 
178
190
  ## 写法规则:原则优先
@@ -183,10 +195,10 @@ Nova:我们也上线了,价格动了
183
195
  |---|---|---|
184
196
  | 写作原则 | `social-writing-principles.md` | 先想清楚传达什么、结论先行、每句有新信息、形式跟内容走、原图优先、站在读者那边 |
185
197
  | 信息设计 | `information-design.md` | 先写骨架、内容 → 形式(列表 / 表格 / 图 / 段落)、篇幅和密度 |
186
- | 措辞口吻 | `core-voice.md` | 十条措辞原则,V01–V40 是它们在标题、开头、数字、问句、回复、段子里的例子 |
198
+ | 措辞口吻 | `core-voice.md` | V01–V40 是标题、开头、数字、问句、回复、段子的场景选项 |
187
199
  | 审稿 | `de-ai-rules.md` + `topmind-revise` | 去 AI 味(长文模式 + 社交短帖 S01–S10)、改写边界、保真审计 |
188
200
 
189
- 人设是**还在学习的普通人**:讲自己看到、试过的,不站讲台,不确定就直说。
201
+ 声音跟当前任务、作者样本、读者与体裁走,不把所有作者固定成同一种人设。
190
202
 
191
203
  ### 脚本只提示,人来判断
192
204
 
@@ -200,9 +212,9 @@ python3 scripts/de_ai_lint.py 文章.md --genre long # 长文
200
212
 
201
213
  | 级别 | 含义 | 要求 |
202
214
  |---|---|---|
203
- | E 错误 | Markdown 残留;正文里有 URL / 来源行 / 配图说明 | 必须清零 |
204
- | W 警告 | AI 套话、套路互动尾巴、类比腔、英文模板句、标点腔、回复过长…… | 每条给结论:改,或写明保留理由 |
205
- | H 提示 / I1–I9 | 语境词、首行偏长、口径词进了正文、播报模板;信息设计(长段、可转列表、素材罗列……) | 只供判断,不进 `--strict` |
215
+ | E 格式问题 | 误带标记、图片占位;链接分离只在 `--separate-links` 启用 | 核对实际格式;特殊代码和引用按语义处理 |
216
+ | W 编辑提示 | 句式、长度、标点、文字列表、正文链接等现象 | 按语境取用,可保留,无需逐条报告 |
217
+ | H 提示 / I1–I9 | 语境词、首行偏长、归因与限定、播报模板;信息设计(长段、可转列表、素材罗列……) | 只供判断,不进 `--strict` |
206
218
 
207
219
  作者有往期帖子数据时,`post_type_report.py` 可以统计自己哪些开头、收尾、类型表现更好,生成一份习惯摘要给改稿用;数据只留在作者本机。
208
220
 
@@ -242,9 +242,23 @@ function install(skillId, targetRoot, { force = false, defaultTarget = false } =
242
242
  );
243
243
  }
244
244
  upgradedFrom = cmp > 0 ? installedVer || 'unknown' : null;
245
- fs.rmSync(dest, { recursive: true, force: true });
246
245
  }
247
- copyDir(src, dest);
246
+ const stage = fs.mkdtempSync(path.join(targetRoot, `.${skillId}-stage-`));
247
+ let previous = null;
248
+ try {
249
+ copyDir(src, stage);
250
+ if (fs.existsSync(dest)) {
251
+ previous = fs.mkdtempSync(path.join(targetRoot, `.${skillId}-previous-`));
252
+ fs.rmdirSync(previous);
253
+ fs.renameSync(dest, previous);
254
+ }
255
+ try { fs.renameSync(stage, dest); }
256
+ catch (error) { if (previous) { fs.renameSync(previous, dest); previous = null; } throw error; }
257
+ if (previous) { try { fs.rmSync(previous, { recursive: true, force: true }); } catch { console.warn(`Previous copy retained at: ${previous}`); } }
258
+ } catch (error) {
259
+ fs.rmSync(stage, { recursive: true, force: true });
260
+ die(`Install failed; the previous installed skill was retained. ${error.message}`);
261
+ } finally { fs.rmSync(stage, { recursive: true, force: true }); }
248
262
  // 安装摘要三行:装到哪里 / 装了什么版本 / 下一步。
249
263
  const toNote = defaultTarget ? ' (default, auto-detected — no --to given)' : '';
250
264
  const skillVer = readSkillVersion(dest) || 'unknown';
@@ -0,0 +1,35 @@
1
+ /** Official portable host distribution. Independent from any host checkout. */
2
+ 'use strict';
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { createHash } = require('node:crypto');
6
+ const hash = value => createHash('sha256').update(value).digest('hex');
7
+ function buildHostPack(root) {
8
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
9
+ const ids = fs.readdirSync(root).filter(id => /^[a-z0-9-]+$/.test(id) && fs.existsSync(path.join(root, id, 'SKILL.md'))).sort();
10
+ const resources = {};
11
+ const files = {};
12
+ function walk(dir, relative) {
13
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
14
+ if (entry.name.startsWith('.') || ['node_modules', 'dist', '__pycache__'].includes(entry.name)) continue;
15
+ const rel = `${relative}/${entry.name}`;
16
+ if (entry.isSymbolicLink()) throw new Error(`Symlink in host pack: ${rel}`);
17
+ if (entry.isDirectory()) walk(path.join(dir, entry.name), rel);
18
+ else if (entry.isFile() && /\.(md|py|json|yaml|yml|css|html|txt|js|mjs|mts)$/i.test(entry.name)) {
19
+ const content = fs.readFileSync(path.join(dir, entry.name), 'utf8');
20
+ resources[rel] = content; files[rel] = hash(content);
21
+ }
22
+ }
23
+ }
24
+ for (const id of [...ids, 'runtime']) walk(path.join(root, id), id);
25
+ const skills = ids.map(id => {
26
+ const raw = resources[`${id}/SKILL.md`];
27
+ const meta = JSON.parse(fs.readFileSync(path.join(root, id, 'package.json'), 'utf8'));
28
+ if (!/^---\r?\n/u.test(raw) || !/^name:\s*/mu.test(raw)) throw new Error(`Invalid skill: ${id}`);
29
+ return { id, name: id, description: meta.description || id, version: meta.version, packId: 'topmind-writing-skills' };
30
+ });
31
+ const manifest = { schema_version: 1, name: 'topmind-writing-skills', version: pkg.version, license: pkg.license, repository: pkg.repository, entry_files: ids.map(id => `${id}/SKILL.md`), files, runtime: { api: 1, entry: 'runtime/article-renderer.mjs', update: 'host-release' } };
32
+ const digest = hash(JSON.stringify(manifest));
33
+ return { manifest: { ...manifest, digest }, snapshot: { id: manifest.name, version: pkg.version, source: 'https://github.com/topmindspace/topmind-writing-skills', license: pkg.license, digest, skills, resources } };
34
+ }
35
+ module.exports = { buildHostPack };
package/lib/zip.cjs ADDED
@@ -0,0 +1,18 @@
1
+ /** Deterministic portable ZIP writer. Fixed DOS timestamp; no host permissions or paths. */
2
+ const { deflateRawSync } = require('node:zlib');
3
+ const table = Array.from({ length: 256 }, (_, n) => { let c = n; for (let i = 0; i < 8; i++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; return c >>> 0; });
4
+ const crc32 = data => { let crc = 0xffffffff; for (const byte of data) crc = table[(crc ^ byte) & 255] ^ (crc >>> 8); return (crc ^ 0xffffffff) >>> 0; };
5
+ function createZip(files) {
6
+ const locals = [], directory = []; let offset = 0;
7
+ for (const [name, value] of Object.entries(files).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
8
+ const filename = Buffer.from(name); const data = Buffer.from(value); const compressed = deflateRawSync(data); const crc = crc32(data);
9
+ const local = Buffer.alloc(30); local.writeUInt32LE(0x04034b50); local.writeUInt16LE(20, 4); local.writeUInt16LE(0x800, 6); local.writeUInt16LE(8, 8); local.writeUInt16LE(33, 12); local.writeUInt32LE(crc, 14); local.writeUInt32LE(compressed.length, 18); local.writeUInt32LE(data.length, 22); local.writeUInt16LE(filename.length, 26);
10
+ locals.push(local, filename, compressed);
11
+ const central = Buffer.alloc(46); central.writeUInt32LE(0x02014b50); central.writeUInt16LE(20, 4); central.writeUInt16LE(20, 6); central.writeUInt16LE(0x800, 8); central.writeUInt16LE(8, 10); central.writeUInt16LE(33, 14); central.writeUInt32LE(crc, 16); central.writeUInt32LE(compressed.length, 20); central.writeUInt32LE(data.length, 24); central.writeUInt16LE(filename.length, 28); central.writeUInt32LE(offset, 42);
12
+ directory.push(central, filename); offset += local.length + filename.length + compressed.length;
13
+ }
14
+ const bytes = Buffer.concat(directory), end = Buffer.alloc(22); const count = Object.keys(files).length;
15
+ end.writeUInt32LE(0x06054b50); end.writeUInt16LE(count, 8); end.writeUInt16LE(count, 10); end.writeUInt32LE(bytes.length, 12); end.writeUInt32LE(offset, 16);
16
+ return Buffer.concat([...locals, bytes, end]);
17
+ }
18
+ module.exports = { createZip };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topmindspace/topmind-writing-skills",
3
- "version": "0.8.49",
3
+ "version": "0.8.51",
4
4
  "description": "TopMindSpace 写作技能合集 — 通过 npm / npx 安装技能(公众号长文、X 长文、干货短文、X 引流短帖、改稿与质检、封面图、长图海报)。只出稿与发布包,不代发。",
5
5
  "license": "MIT",
6
6
  "author": "TopMindspace",
@@ -30,6 +30,8 @@
30
30
  },
31
31
  "files": [
32
32
  "bin",
33
+ "lib",
34
+ "runtime",
33
35
  "README.md",
34
36
  "README.en.md",
35
37
  "LICENSE",
@@ -78,7 +80,9 @@
78
80
  "check": "node scripts/run_skill_gates.js check",
79
81
  "privacy": "python3 scripts/ci_privacy_scan.py",
80
82
  "audit": "node scripts/run_skill_gates.js audit",
81
- "package:skill": "node scripts/run_skill_gates.js package"
83
+ "package:skill": "node scripts/run_skill_gates.js package",
84
+ "test": "node --test tests/*.test.mjs",
85
+ "host:pack": "node scripts/build-host-pack.cjs"
82
86
  },
83
87
  "publishConfig": {
84
88
  "access": "public"
@@ -0,0 +1,9 @@
1
+ export type WritingPlatform = 'wechat' | 'x' | 'text';
2
+ export type WritingTheme = 'minimal-ink' | 'tech-blue' | 'newsprint' | 'graphite';
3
+ export const WRITING_THEMES: Record<WritingTheme, { ink: string; accent: string; paper: string; muted: string }>;
4
+ export const WRITING_ACTIONS: ReadonlyArray<{ id: string; zh: string; en: string; prompt: string; promptEn: string }>;
5
+ export function stripFrontmatter(value: string): string;
6
+ export interface ArticleDelivery { platform: WritingPlatform; theme: WritingTheme; html: string; body: string; text: string; checklist: string; images: Array<{ source: string; alt: string; status: string }>; diagnostics: Array<{ code: string; source?: string }> }
7
+ export function renderArticle(input: { markdown: string; platform?: WritingPlatform; theme?: WritingTheme; title?: string; images?: Record<string, string> }): ArticleDelivery;
8
+ export function resolveArticleImage(sourcePath: string, reference: string): string;
9
+ export function prepareArticle(input: { sourcePath: string; markdown: string; platform?: WritingPlatform; theme?: WritingTheme; title?: string }, readImage: (relative: string) => Promise<string>): Promise<ArticleDelivery>;
@@ -0,0 +1,130 @@
1
+ /** Portable host renderer. Canonical source: topmind-writing-skills. No IO or executable HTML. */
2
+ export const WRITING_THEMES = Object.freeze({
3
+ 'minimal-ink': { ink: '#252525', accent: '#955247', paper: '#ffffff', muted: '#666666' },
4
+ 'tech-blue': { ink: '#172033', accent: '#0369a1', paper: '#ffffff', muted: '#536174' },
5
+ newsprint: { ink: '#24211c', accent: '#80502a', paper: '#fffdf8', muted: '#655f55' },
6
+ graphite: { ink: '#222222', accent: '#444444', paper: '#ffffff', muted: '#666666' },
7
+ });
8
+ export const WRITING_ACTIONS = Object.freeze([
9
+ { id: 'topmind-revise', zh: '改稿', en: 'Revise', prompt: '按 topmind-revise 审校当前内容,保留事实、数字、归因和作者原意。' },
10
+ { id: 'topmind-format', zh: '只排版', en: 'Format', prompt: '按 topmind-format 只调整当前内容的排版,不改写文字。' },
11
+ { id: 'topmind-briefs', zh: '短文', en: 'Brief', prompt: '按 topmind-briefs,基于当前材料写一篇信息清楚的短文。' },
12
+ { id: 'topmind-viral-posts', zh: '互动短帖', en: 'Social post', prompt: '按 topmind-viral-posts,基于当前材料写一条互动短帖。' },
13
+ { id: 'topmind-wechat-post', zh: '公众号', en: 'WeChat', prompt: '按 topmind-wechat-post,基于当前材料完成公众号稿,只出稿不代发。' },
14
+ { id: 'topmind-x-article', zh: 'X 长文', en: 'X Article', prompt: '按 topmind-x-article,基于当前材料完成 X 长文,只出稿不代发。' },
15
+ { id: 'topmind-cover', zh: '封面', en: 'Cover', prompt: '按 topmind-cover,为当前文章制作封面;先核对实际生图能力,缺少工具时说明。' },
16
+ { id: 'topmind-poster', zh: '长图', en: 'Poster', prompt: '按 topmind-poster,为当前内容制作信息长图;先核对实际渲染能力,缺少工具时保留 HTML 并说明。' },
17
+ ].map(action => ({ ...action, promptEn: `Use ${action.id} for the current document. Preserve source language, facts and author intent. Use only available tools; explain missing image or rendering capabilities. Draft and deliver only; do not publish.` })));
18
+ const esc = value => String(value).replace(/[&<>"']/gu, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);
19
+ export function stripFrontmatter(value) {
20
+ return String(value || '').replace(/^\uFEFF/u, '').replace(/\r\n?/gu, '\n').replace(/^---\n[\s\S]*?\n---(?:\n|$)/u, '');
21
+ }
22
+ export function resolveArticleImage(sourcePath, reference) {
23
+ if (!sourcePath || /[\\:]|^\//u.test(sourcePath) || /[\\:]|^\//u.test(reference)) throw new Error('Invalid workspace image path');
24
+ const parts = sourcePath.split('/').slice(0, -1);
25
+ for (const segment of reference.split('/')) {
26
+ if (!segment || segment === '.') continue;
27
+ if (segment === '..') { if (!parts.length) throw new Error('Image escapes workspace'); parts.pop(); }
28
+ else parts.push(segment);
29
+ }
30
+ return parts.join('/');
31
+ }
32
+ export async function prepareArticle(input, readImage) {
33
+ const images = Object.create(null);
34
+ for (const match of String(input.markdown).matchAll(/!\[[^\]]*\]\(([^\s)]+)(?:\s+"[^"]*")?\)/gu)) {
35
+ const source = match[1];
36
+ if (/^(https?:|data:)/iu.test(source) || Object.hasOwn(images, source)) continue;
37
+ try { images[source] = await readImage(resolveArticleImage(input.sourcePath, source)); }
38
+ catch { images[source] = ''; }
39
+ }
40
+ return renderArticle({ ...input, images });
41
+ }
42
+ function safeUrl(value, image = false) {
43
+ const url = String(value || '').trim();
44
+ if (/[\u0000-\u0020\u007f<>"']/u.test(url)) return null;
45
+ if (/^https?:\/\//iu.test(url)) return url;
46
+ if (!image && /^(mailto:|#)/iu.test(url)) return url;
47
+ if (image && /^data:image\/(png|jpe?g|gif|webp);base64,[a-z0-9+/=]+$/iu.test(url)) return url;
48
+ return null;
49
+ }
50
+ /** Render Markdown without interpreting raw HTML. Local image bytes must be supplied by the host. */
51
+ export function renderArticle({ markdown = '', platform = 'wechat', theme = 'minimal-ink', title = '', images = {} } = {}) {
52
+ if (!['wechat', 'x', 'text'].includes(platform)) throw new Error('Unsupported delivery format');
53
+ if (!Object.hasOwn(WRITING_THEMES, theme)) throw new Error('Unknown writing theme');
54
+ const palette = WRITING_THEMES[theme];
55
+ const source = stripFrontmatter(markdown);
56
+ const diagnostics = [];
57
+ const imageList = [];
58
+ const footnotes = [];
59
+ const inline = raw => {
60
+ const stash = [];
61
+ const token = html => { stash.push(html); return `\u0001${stash.length - 1}\u0002`; };
62
+ // Remove control characters so a source cannot impersonate a stash token.
63
+ let value = String(raw).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/gu, '');
64
+ value = value.replace(/`([^`]+)`/gu, (_, code) => token(`<code style="font-family:monospace;background:#f1f1f1;padding:2px 4px;">${esc(code)}</code>`));
65
+ value = value.replace(/!\[([^\]]*)\]\(([^\s)]+)(?:\s+"[^"]*")?\)/gu, (_, alt, src) => {
66
+ const supplied = Object.hasOwn(images, src) ? images[src] : src;
67
+ const url = safeUrl(supplied, true);
68
+ const status = !url ? 'missing' : /^data:/iu.test(url) ? 'embedded' : 'remote';
69
+ imageList.push({ source: src, alt, status });
70
+ if (!url) { diagnostics.push({ code: 'image-unavailable', source: src }); return token(`<span style="color:${palette.muted};">[${esc(alt || src)}]</span>`); }
71
+ return token(`<img src="${esc(url)}" alt="${esc(alt)}" style="max-width:100%;height:auto;display:block;margin:18px auto;" />`);
72
+ });
73
+ value = value.replace(/\[([^\]]+)\]\(([^\s)]+)(?:\s+"[^"]*")?\)/gu, (_, label, rawUrl) => {
74
+ const url = safeUrl(rawUrl);
75
+ if (!url) { diagnostics.push({ code: 'unsafe-link' }); return token(esc(label)); }
76
+ if (platform === 'wechat' && /^https?:/iu.test(url)) {
77
+ footnotes.push(url);
78
+ return token(`${esc(label)}<sup>[${footnotes.length}]</sup>`);
79
+ }
80
+ return token(`<a href="${esc(url)}" style="color:${palette.accent};">${esc(label)}</a>`);
81
+ });
82
+ value = esc(value).replace(/\*\*([^*]+)\*\*/gu, '<strong>$1</strong>').replace(/__([^_]+)__/gu, '<strong>$1</strong>').replace(/(?<!\*)\*([^*]+)\*(?!\*)/gu, '<em>$1</em>').replace(/~~([^~]+)~~/gu, '<s>$1</s>');
83
+ return value.replace(/\u0001(\d+)\u0002/gu, (_, n) => stash[Number(n)] || '');
84
+ };
85
+ const lines = source.split('\n');
86
+ const out = [];
87
+ let paragraph = [];
88
+ const flush = () => { if (paragraph.length) out.push(`<p style="margin:0 0 18px;line-height:1.85;">${inline(paragraph.join('\n')).replace(/\n/gu, '<br />')}</p>`); paragraph = []; };
89
+ for (let i = 0; i < lines.length; i++) {
90
+ const line = lines[i];
91
+ if (/^\s*```/u.test(line)) {
92
+ flush(); const language = line.trim().slice(3); const code = [];
93
+ while (++i < lines.length && !/^\s*```/u.test(lines[i])) code.push(lines[i]);
94
+ if (i === lines.length) diagnostics.push({ code: 'unclosed-code-fence' });
95
+ out.push(`<section style="font-family:monospace;white-space:pre-wrap;overflow-wrap:anywhere;background:#f4f4f4;padding:14px;margin:18px 0;">${esc(code.join('\n'))}</section>`);
96
+ if (language === 'mermaid') diagnostics.push({ code: 'diagram-source-only' });
97
+ } else if (/^#{1,6}\s/u.test(line)) {
98
+ flush(); const [, marks, text] = line.match(/^(#{1,6})\s+(.*)$/u);
99
+ out.push(`<h${Math.max(2, marks.length)} style="font-size:${marks.length < 3 ? 22 : 18}px;line-height:1.5;margin:28px 0 14px;color:${palette.accent};">${inline(text)}</h${Math.max(2, marks.length)}>`);
100
+ } else if (/^\s*([-*_])(?:\s*\1){2,}\s*$/u.test(line)) {
101
+ flush(); out.push('<hr style="border:0;border-top:1px solid #dddddd;margin:26px 0;" />');
102
+ } else if (/^\s*>/u.test(line)) {
103
+ flush(); const quote = [line.replace(/^\s*>\s?/u, '')];
104
+ while (i + 1 < lines.length && /^\s*>/u.test(lines[i + 1])) quote.push(lines[++i].replace(/^\s*>\s?/u, ''));
105
+ out.push(`<blockquote style="margin:18px 0;padding:10px 16px;border-left:3px solid ${palette.accent};color:${palette.muted};">${inline(quote.join('\n')).replace(/\n/gu, '<br />')}</blockquote>`);
106
+ } else if (/^\s*(?:[-+*]|\d+[.)])\s+/u.test(line)) {
107
+ flush(); const ordered = /^\s*\d/u.test(line); const tag = ordered ? 'ol' : 'ul'; const items = [line];
108
+ const re = ordered ? /^\s*\d+[.)]\s+/u : /^\s*[-+*]\s+/u;
109
+ while (i + 1 < lines.length && re.test(lines[i + 1])) items.push(lines[++i]);
110
+ out.push(`<${tag} style="padding-left:24px;margin:16px 0;">${items.map(item => `<li style="margin:8px 0;line-height:1.8;">${inline(item.replace(re, ''))}</li>`).join('')}</${tag}>`);
111
+ } else if (line.includes('|') && i + 1 < lines.length && /^\s*\|?\s*:?-{3,}/u.test(lines[i + 1])) {
112
+ flush(); const cells = row => row.trim().replace(/^\||\|$/gu, '').split('|').map(c => c.trim());
113
+ const head = cells(line); i++; const rows = [];
114
+ while (i + 1 < lines.length && lines[i + 1].trim() && lines[i + 1].includes('|')) rows.push(cells(lines[++i]));
115
+ const row = (values, tag) => `<tr>${values.map(cell => `<${tag} style="border:1px solid #dddddd;padding:8px;text-align:left;">${inline(cell)}</${tag}>`).join('')}</tr>`;
116
+ out.push(`<table style="border-collapse:collapse;width:100%;font-size:14px;margin:20px 0;"><thead>${row(head, 'th')}</thead><tbody>${rows.map(r => row(r, 'td')).join('')}</tbody></table>`);
117
+ if (platform === 'x') diagnostics.push({ code: 'table-paste-check' });
118
+ } else if (/^:::/u.test(line)) {
119
+ flush(); diagnostics.push({ code: 'container-as-text' }); paragraph.push(line);
120
+ } else if (!line.trim()) flush();
121
+ else paragraph.push(line);
122
+ }
123
+ flush();
124
+ if (footnotes.length) out.push(`<section style="font-size:13px;color:${palette.muted};margin-top:28px;">${footnotes.map((url, i) => `<p>[${i + 1}] ${esc(url)}</p>`).join('')}</section>`);
125
+ const text = source.trim();
126
+ const body = `<section style="color:${palette.ink};background:${palette.paper};font-family:-apple-system,BlinkMacSystemFont,'PingFang SC',sans-serif;font-size:16px;line-height:1.85;overflow-wrap:anywhere;">${out.join('\n')}</section>`;
127
+ const html = `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data: https:; style-src 'unsafe-inline';"><title>${esc(title || '文章预览')}</title></head><body style="max-width:720px;margin:0 auto;padding:24px;">${body}</body></html>`;
128
+ const checklist = ['# 图片交付清单', '', '按正文出现顺序核对;内嵌不代表目标平台已托管或发布。', '', ...imageList.map((item, i) => `${i + 1}. ${item.status} · ${item.source.replace(/[\r\n]/gu, ' ')}`)].join('\n');
129
+ return { platform, theme, html, body, text, images: imageList, diagnostics, checklist };
130
+ }
@@ -0,0 +1,7 @@
1
+ # Topmind 内置写作能力
2
+
3
+ 这是独立写作包的宿主快照。正文和参考按技能来源读取,不混用其他包的同名资源。
4
+
5
+ 宿主提供 `render_article`:从工作区源稿生成公众号、X 长文或文本的确定性排版结果;需要持久交付时再使用宿主文件写入工具,遵守原有写闸。也可在“写作与交付”界面预览、复制和导出。不要求用户照抄 Python 命令,不把脚本文本可读当成可执行权限。
6
+
7
+ 排版不改原稿;主题、表格粘贴、缺图、扩展容器和图表限制按工具真实诊断说明。封面、海报、PNG、外部检索和发布依实际工具能力执行,不虚构成功。旧文章直接使用,不为兼容批量改名。审校基于事实与作者意图,不以措辞分数定稿。