@taloswork/ose-shared 0.1.10 → 0.1.12

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 (126) hide show
  1. package/package.json +3 -3
  2. package/skills/document/SKILL.md +143 -215
  3. package/skills/document/art-directions/base.css +27 -2
  4. package/skills/document/art-directions/cinematic-depth.css +3 -1
  5. package/skills/document/art-directions/collage-paper.css +5 -1
  6. package/skills/document/art-directions/editorial-cut.css +5 -1
  7. package/skills/document/art-directions/geometric-pop.css +6 -0
  8. package/skills/document/art-directions/gradient-mesh.css +5 -1
  9. package/skills/document/art-directions/illustrated-doodle.css +5 -0
  10. package/skills/document/art-directions/luxury-ornament.css +4 -1
  11. package/skills/document/art-directions/maximalist-blocks.css +6 -0
  12. package/skills/document/art-directions/technical-grid.css +4 -0
  13. package/skills/document/assets/fonts/fonts.json +2 -2
  14. package/skills/document/assets/pdf-fonts/NotoSansSC-PDF-Bold.ttf +0 -0
  15. package/skills/document/assets/pdf-fonts/NotoSansSC-PDF-Regular.ttf +0 -0
  16. package/skills/document/assets/pdf-fonts/OFL.txt +93 -0
  17. package/skills/document/assets/pdf-fonts/README.md +35 -0
  18. package/skills/document/assets/tessdata/LICENSE-Apache-2.0.txt +202 -0
  19. package/skills/document/assets/tessdata/README.md +21 -0
  20. package/skills/document/assets/tessdata/chi_sim.traineddata.gz +0 -0
  21. package/skills/document/assets/tessdata/eng.traineddata.gz +0 -0
  22. package/skills/document/bin/deck-build.cjs +2311 -41
  23. package/skills/document/bin/deck-chart.cjs +762 -0
  24. package/skills/document/bin/deck-plan.cjs +802 -0
  25. package/skills/document/bin/deck-qa.cjs +984 -249
  26. package/skills/document/bin/deck-scenario.cjs +1346 -0
  27. package/skills/document/bin/doc-convert.cjs +130 -39
  28. package/skills/document/bin/doc-fill.cjs +73121 -170
  29. package/skills/document/bin/doc-gen.cjs +625 -68
  30. package/skills/document/bin/doc-run.cjs +162 -24
  31. package/skills/document/bin/doc-scan.cjs +13 -0
  32. package/skills/document/bin/doc-text.cjs +13 -0
  33. package/skills/document/bin/doc-validate.cjs +718 -23
  34. package/skills/document/bin/doc-xml.cjs +9 -5
  35. package/skills/document/bin/docx-annotate.cjs +95 -19
  36. package/skills/document/bin/fontkit.cjs +1 -0
  37. package/skills/document/bin/html2pptx.cjs +1372 -121
  38. package/skills/document/bin/ocr-worker.cjs +1 -0
  39. package/skills/document/bin/ocr.cjs +131 -40
  40. package/skills/document/bin/pdf-form.cjs +674 -177
  41. package/skills/document/bin/pdf-images.cjs +13 -0
  42. package/skills/document/bin/pdf-ops.cjs +634 -199
  43. package/skills/document/bin/pdf-render.cjs +19 -3
  44. package/skills/document/bin/pdf-tables.cjs +13 -0
  45. package/skills/document/bin/pptx-clone.cjs +3 -3
  46. package/skills/document/bin/pptx-slide.cjs +3 -3
  47. package/skills/document/bin/xlsx-query.cjs +192 -61
  48. package/skills/document/bin-reference.md +56 -33
  49. package/skills/document/content-pipeline.md +129 -242
  50. package/skills/document/deck-design.md +355 -638
  51. package/skills/document/layouts/README.md +46 -33
  52. package/skills/document/layouts/capacity.json +1 -0
  53. package/skills/document/layouts/slots.json +1 -1
  54. package/skills/document/layouts/toc-panel.tpl +1 -1
  55. package/skills/document/motion/README.md +3 -2
  56. package/skills/document/office.md +3 -3
  57. package/skills/document/packs/board-brief/backdrop.html +2 -0
  58. package/skills/document/packs/board-brief/pack.css +2 -1
  59. package/skills/document/packs/mckinsey-photo/backdrop.html +2 -0
  60. package/skills/document/packs/mckinsey-photo/pack.css +2 -1
  61. package/skills/document/packs/press-room/backdrop.html +2 -0
  62. package/skills/document/packs/press-room/pack.css +2 -1
  63. package/skills/document/packs/prismatic-3d/backdrop.html +2 -0
  64. package/skills/document/packs/prismatic-3d/pack.css +4 -1
  65. package/skills/document/pdf.md +53 -49
  66. package/skills/document/rewrite.md +241 -308
  67. package/skills/document/scenarios.json +1290 -0
  68. package/skills/document/scenarios.schema.json +103 -0
  69. package/skills/document/styles/README.md +15 -25
  70. package/skills/document/styles/_contract-defaults.css +25 -1
  71. package/skills/document/styles/anthropic/skin.css +6 -3
  72. package/skills/document/styles/apple/skin.css +4 -3
  73. package/skills/document/styles/bistro-warm/backdrop.html +2 -0
  74. package/skills/document/styles/bistro-warm/skin.css +4 -1
  75. package/skills/document/styles/black-gold/skin.css +6 -5
  76. package/skills/document/styles/bold-clash/backdrop.html +2 -0
  77. package/skills/document/styles/bold-clash/skin.css +2 -1
  78. package/skills/document/styles/branding/skin.css +4 -4
  79. package/skills/document/styles/campus-teal/backdrop.html +2 -0
  80. package/skills/document/styles/campus-teal/skin.css +2 -1
  81. package/skills/document/styles/cinereel/skin.css +7 -3
  82. package/skills/document/styles/clinic-care/backdrop.html +2 -0
  83. package/skills/document/styles/clinic-care/skin.css +2 -1
  84. package/skills/document/styles/cool/skin.css +6 -5
  85. package/skills/document/styles/dark-glass/backdrop.html +2 -0
  86. package/skills/document/styles/dark-glass/skin.css +2 -1
  87. package/skills/document/styles/gov-brief/backdrop.html +2 -0
  88. package/skills/document/styles/gov-brief/skin.css +2 -1
  89. package/skills/document/styles/grid-tech/backdrop.html +2 -0
  90. package/skills/document/styles/grid-tech/skin.css +2 -1
  91. package/skills/document/styles/hand-drawn/skin.css +3 -3
  92. package/skills/document/styles/ivory-gold/skin.css +7 -6
  93. package/skills/document/styles/legal-slate/backdrop.html +2 -0
  94. package/skills/document/styles/legal-slate/skin.css +2 -1
  95. package/skills/document/styles/magazine-ink/skin.css +4 -3
  96. package/skills/document/styles/mckinsey/skin.css +2 -1
  97. package/skills/document/styles/memphis/backdrop.html +2 -0
  98. package/skills/document/styles/memphis/skin.css +2 -1
  99. package/skills/document/styles/mono/skin.css +6 -2
  100. package/skills/document/styles/neon-cyber/backdrop.html +2 -0
  101. package/skills/document/styles/neon-cyber/skin.css +2 -1
  102. package/skills/document/styles/olive-gold/skin.css +7 -6
  103. package/skills/document/styles/organic-earth/backdrop.html +2 -0
  104. package/skills/document/styles/organic-earth/skin.css +2 -1
  105. package/skills/document/styles/pastel-saas/backdrop.html +2 -0
  106. package/skills/document/styles/pastel-saas/skin.css +2 -1
  107. package/skills/document/styles/portfolio-noir/backdrop.html +2 -0
  108. package/skills/document/styles/portfolio-noir/skin.css +2 -1
  109. package/skills/document/styles/retro-press/backdrop.html +2 -0
  110. package/skills/document/styles/retro-press/skin.css +2 -1
  111. package/skills/document/styles/sport-arena/backdrop.html +2 -0
  112. package/skills/document/styles/sport-arena/skin.css +4 -1
  113. package/skills/document/styles/swiss-grid/skin.css +10 -5
  114. package/skills/document/styles/wedding-blush/backdrop.html +2 -0
  115. package/skills/document/styles/wedding-blush/skin.css +4 -1
  116. package/skills/document/theme-system.md +85 -77
  117. package/skills/document/themes/README.md +7 -5
  118. package/skills/document/vision.md +8 -5
  119. package/skills/document/xlsx.md +15 -25
  120. package/skills/cozy-static-svg/SKILL.md +0 -76
  121. package/skills/cozy-static-svg/references/approved-preview.png +0 -0
  122. package/skills/cozy-static-svg/references/cross-platform.md +0 -73
  123. package/skills/cozy-static-svg/references/moon_keeps_watch.svg +0 -229
  124. package/skills/cozy-static-svg/references/style-anchor.md +0 -271
  125. package/skills/cozy-static-svg/scripts/artkit.py +0 -321
  126. package/skills/cozy-static-svg/scripts/to_png.py +0 -333
@@ -1,360 +1,293 @@
1
- # Generate / edit documents (L3)
1
+ # Generate, fill and edit documents (L3)
2
2
 
3
- ## Themes — the presentation layer for `doc-gen` DOCUMENTS (docx / pdf)
3
+ **Read only the sections your task needs** (Read with the offset/limit of the line range):
4
4
 
5
- > **Decks use skins, not themes.** For an html2pptx deck (路演/汇报/pitch), pick a
6
- > **skin** via `--style` (`styles/README.md`, the SKILL deck route) — NOT a
7
- > `themes/*.json`. The `themes/` + `--theme` system below is for `doc-gen`
8
- > DOCUMENTS (Word/PDF reports) — a separate, still-valid path. `doc-gen` makes no
9
- > decks at all; `html2pptx --theme` reads a theme's colors/fonts and its three
10
- > slide colors (`pptx{background,titleColor,textColor}`).
5
+ - New .docx / .pdf / .xlsx → **Generate** (lines 14–55); a formula in a workbook → also **Formulas** (lines 57–73); a particular look, theme or brand → also **Themes** (lines 75–125)
6
+ - Fill a `{{placeholder}}` .docx / .pptx / .xlsx template → **Fill a template** (lines 127–146)
7
+ - Change an existing .docx — text, structure, comments, tracked changes → **Edit an existing .docx** (lines 148–199)
8
+ - New content in an existing deck's look, or restructure a .pptx → **Template decks** (lines 201–271)
9
+ - Nothing above fits → **Custom scripts** (lines 273–293)
11
10
 
12
- Content (the spec) and presentation (the theme) are separate. A theme is a JSON
13
- file — template skills ship their own; this skill ships **12 built-ins** in `themes/`
14
- (theme = colors/fonts for `doc-gen` output; `themes/README.md` lists them with their
15
- best fit): `cool-tech` · `apple-keynote` · `mckinsey-consulting` · `anthropic-clay` ·
16
- `black-gold` · `hand-drawn` · `mono` · `warm-editorial` · `ivory-gold` · `cinereel` ·
17
- `magazine-ink` · `swiss-grid`. For a plain Word/PDF report, `mckinsey-consulting` (white · deep
18
- navy · structured tables) is a safe default; `mono` for a stark minimal look.
11
+ A PDF's forms, page ops and stamps are in pdf.md; a deck from scratch is the deck route
12
+ (SKILL.md) — `doc-gen` makes no decks. `<skill>` below is this skill's directory.
19
13
 
20
- ```
21
- node <skill>/bin/doc-gen.cjs docx out.docx spec.json --theme <skill>/themes/mckinsey-consulting.json
22
- ```
23
-
24
- **Topic-adaptive color (when the user hasn't named a theme/brand)**: pass
25
- `--topic "<subject>"` (or put `"topic"` in the spec). The engine picks a
26
- palette that FITS the subject — deterministically, by keyword then stable hash —
27
- instead of the same default every time (the #1 "AI-generated" tell). `--palette <id>` forces a
28
- named one; `doc-gen --list-palettes` prints the library. A `--theme` file or a
29
- saved/brand preference (step 1 below) still wins — use `--topic` for the "just
30
- make it look designed-for-this" case, not when the user has a look in mind.
31
-
32
- **Picking a theme with the user (preference → showcase → confirm → apply)**:
33
-
34
- 1. **Preference first**: check your memory for a saved deck-theme preference
35
- (记忆里的"deck 主题偏好") and `themes/custom/` for the user's own themes —
36
- a remembered/custom theme wins over guessing; use it and SAY so ("按你常用的
37
- X 主题,要换告诉我").
38
- 2. User named a style → match by 适用场景 in `themes/README.md`, apply directly.
39
- 3. User wants to choose (or nothing fits) → shortlist 2–4 by 适用场景. If they
40
- want to SEE them, render a faithful sample per candidate: a 1-page
41
- `doc-gen <docx|pdf> … --theme <themes/x.json>` → `doc-convert` to PDF →
42
- `pdf-render` the page to a PNG (for a document theme this print render IS the
43
- real look). A text shortlist by 适用场景 is fine too — don't over-render. Then
44
- ask with the **picker card**: `ask_user_question`
45
- type single_choice, one option per theme as
46
- `{ label: 主题名, description: 一句适用场景, imagePath: 样片PNG绝对路径 }` —
47
- the UI shows a compact thumbnail-card row and the user clicks one. Apply it.
48
- 4. **After an explicit confirmation, remember it**: save the choice to your
49
- agent memory as the user's deck-theme preference so the next deck starts
50
- from it — and update the memory when they switch.
51
-
52
- **User-created themes (`themes/custom/`)**: writing a new theme.json is the
53
- supported way to make a new "template" — invalid fields fall back with warnings
54
- on stderr (read them and fix), so iterate freely. When the user tweaks colors
55
- repeatedly, hands you brand colors/logo, or says "以后都用这个风格", offer to
56
- save it: write `<skill>/themes/custom/<descriptive-name>.json` (kebab-case,
57
- with a `bestFor` line). Files there are the USER's — resource updates never
58
- touch them — and step 1 above picks them up in every later session. A liked
59
- existing deck can seed one too: `pptx-theme ref.pptx <skill>/themes/custom/x.json`.
60
-
61
- **Mimic an EXISTING deck**: extract its palette/fonts into a theme first —
14
+ ## Generate — `bin/doc-gen`
62
15
 
63
16
  ```
64
- node <skill>/bin/pptx-theme.cjs ref.pptx /tmp/ref-theme.json
65
- node <skill>/bin/html2pptx.cjs deck.html out.pptx --theme /tmp/ref-theme.json
17
+ node <skill>/bin/doc-gen.cjs <docx|xlsx|pdf> <out> <spec.json> [--topic "<subject>" | --theme <file|name>]
66
18
  ```
67
19
 
68
- This captures colors/fonts (heuristic fallbacks print on
69
- stderr), NOT slide layouts — to reuse the deck's actual LAYOUTS, tokenize it into
70
- a fillable template instead (see "Reuse a user's UPLOADED deck" below:
71
- `pptx-tokenize` → `doc-fill`).
72
-
73
- Theme fields (all optional; invalid values default + warn on stderr, never fail):
74
- `bestFor` (one-line 适用场景, for the picker) · `fonts{heading,body}` ·
75
- `colors{primary,accent,secondary,text,muted,tableHeaderBg,tableBorder}`
76
- (`accent` defaults to `primary`; `secondary` defaults to `muted`) ·
77
- `page{size:A4|Letter,margin,header,footer}` (`margin` is a CSS length string such as `margin:"20mm"` — mm/cm/in/pt/px; a bare number is not accepted and falls back to the default margin, which changes the page count; `{title}` `{page}` `{pages}` interpolate;
78
- **page header/footer: prefer Latin/digits — CJK there renders blank in the PDF path**) ·
79
- `heading{h1..h4}{size,color,bold,spaceAfter}` (color takes hex or a palette key like `"primary"`) ·
80
- `body{size,lineHeight,justify}` · `cover{enabled,subtitle,date,logo}` (base64 logo; title moves onto the cover) ·
81
- `pptx{background,titleColor,textColor}` — read only by `html2pptx --theme`, as the
82
- slide background / title / body CSS colors (dark deck = dark `background` + light
83
- `titleColor`/`textColor`; docx/PDF stay white-paper regardless).
84
-
85
- Per format the theme becomes: **pdf** CSS + cover page + live page headers/footers;
86
- **docx** real document styles (Heading1–4, default font/size, table header shading)
87
- + header/footer parts + cover page. A spec may embed `"theme": {...}`;
88
- `--theme <file>` wins.
89
-
90
- ## Template decks — `bin/pptx-clone` (one styled slide per data item)
91
-
92
- When a template `.pptx` carries a styled slide with `{{token}}` text, clone it
93
- once per item — styling, images and master survive verbatim:
20
+ The spec is validated **strictly before rendering**: an unknown field or an empty body
21
+ exits 65 with `DOC_SPEC_INVALID` and the JSON path — correct that path and rerun rather
22
+ than delivering the previous output.
23
+
24
+ - **docx / pdf**: `{ "title"?, "paragraphs"?: ["…"], "blocks"?: [ … ] }` — at least one
25
+ non-empty paragraph or block; never `sections`. A title-only document must set
26
+ `"allowCoverOnly": true`. `blocks` is an ordered list of
27
+ - `{ "type": "heading", "text": "…", "level": 1..4 }`
28
+ - `{ "type": "paragraph", "text": "…", "bold"?, "italic"? }`
29
+ - `{ "type": "list", "items": ["…"], "ordered"?: true }`
30
+ - `{ "type": "table", "rows": [["h"],["a"]], "header"?: true, "columnWidths"?: [3000, …] }`
31
+ (DOCX widths in twips; default: full page width, equal columns)
32
+ - `{ "type": "image", "data": "<base64>", "imageType"?: "png", "width"?, "height"? }` (px, default 300×200)
33
+ - **xlsx**: `{ "sheets": { "Name": [["h1","h2"], ["a", 1]] } }` (row 1 = bold header), or per
34
+ sheet `{ "rows": [...], "header"?, "columnWidths"?: [n…], "numFmt"?: ["#,##0.00"…] }`. A
35
+ cell string starting with `=` is a **live formula** — then run **Formulas** below.
36
+ Fills, font colours, borders or merges need exceljs via doc-run (**Custom scripts**).
37
+ - The spec may also carry `"topic"`, `"palette"` or `"theme"` (same as the flags).
38
+
39
+ **A length limit** (正文 1000 字以内 / under 800 words) is kept in the spec, before
40
+ generating: the result's `textChars` (spaces and URLs not counted) and `cjkChars` count it
41
+ — over the limit, shorten the spec and generate once more. Do not extract text from the
42
+ output to count it.
43
+
44
+ **Look**: with no theme every document gets the same default — pass
45
+ `--topic "<subject>"` (a palette that fits the subject, deterministic) unless the user
46
+ named a look; **Themes** covers the rest.
47
+
48
+ **PDF** is always printed in Chromium from themed HTML — CJK line-breaking, mixed
49
+ Latin/中文 font fallback, tables, clickable links that wrap inside the page, the theme's
50
+ page size and margin. Inside Talos it uses the app's service (`[pdf (chromium)]`),
51
+ elsewhere a headless Chromium (`[pdf (headless chromium)]`); with neither it exits 69 and
52
+ writes nothing — tell the user (a .docx can be offered; never swap formats silently).
53
+
54
+ Anything past these shapes — an exotic layout, a styled workbook — is a doc-run script
55
+ (**Custom scripts**), not an edit of the bundled bin.
56
+
57
+ ## Formulas — the xlsx repair loop (mandatory)
58
+
59
+ Write live formulas (`"=SUM(A2:A9)"`), never precomputed values — the file must
60
+ recalculate when the user edits data. doc-gen and exceljs write formulas without cached
61
+ results, so close the loop:
62
+
63
+ 1. `node <skill>/bin/xlsx-recalc.cjs out.xlsx out.xlsx` — evaluates every formula and
64
+ caches the results.
65
+ 2. Its `errors` array must be **empty** — it exits 0 either way, so read it. A
66
+ `#DIV/0!`, `#REF!` or `#N/A` → fix the formula (guard divisors with IF, correct the
67
+ range) and recalc again. **Never deliver a spreadsheet with formula errors**, and never
68
+ "fix" one by replacing the formula with a hardcoded value.
69
+
70
+ `xlsx-recalc` (HyperFormula) covers ~400 functions — all common math/stat/text/date/lookup;
71
+ gaps are some exotic financial functions and dynamic-array spilling. `#NAME?` on a function
72
+ you know is valid Excel is that engine limit: say so and compute that value with
73
+ `xlsx-query` or a script instead of guessing.
74
+
75
+ ## Themes — the look of a doc-gen document (docx / pdf)
76
+
77
+ Decks use **skins** (`--style`, the deck route), not these themes. A theme is a JSON file
78
+ of colours/fonts/page setup; content (the spec) stays separate. This skill ships **12
79
+ built-ins** in `themes/` (`themes/README.md` gives each one's best fit): `cool-tech` ·
80
+ `apple-keynote` · `mckinsey-consulting` · `anthropic-clay` · `black-gold` · `hand-drawn` ·
81
+ `mono` · `warm-editorial` · `ivory-gold` · `cinereel` · `magazine-ink` · `swiss-grid`. For a
82
+ plain Word/PDF report `mckinsey-consulting` (white · deep navy · structured tables) is a
83
+ safe default; `mono` for a stark minimal look. Pass a built-in by path:
94
84
 
95
85
  ```
96
- node <skill>/bin/pptx-clone.cjs template.pptx 1 out.pptx replacements.json
97
- # replacements.json: { "{{region}}": "华东区", "{{owner}}": "Alice" }
86
+ node <skill>/bin/doc-gen.cjs docx out.docx spec.json --theme <skill>/themes/mckinsey-consulting.json
98
87
  ```
99
- The clone is appended at the deck's end; the template slide keeps its tokens for
100
- the next clone (chain the output for N items). Prints `{ newSlideNumber, replaced,
101
- missing, warnings }` — surface `missing` tokens instead of pretending they filled. To
102
- fill a whole deck ONCE (no repetition), use `doc-fill` on the .pptx instead.
103
-
104
- **Capacity warnings** (`CAPACITY {{token}}: … likely overflow` on stderr): the
105
- replacement text is visually wider than its slot (CJK chars count double).
106
- Fix by REWRITING the value shorter — never by shrinking the font (breaks the
107
- template's type hierarchy) and never by truncating with an ellipsis (looks
108
- worse than a slight overflow).
109
-
110
- **Slots ≠ item count.** When the template has more slots than you have content
111
- (e.g. a 4-person team page for a 3-person team), delete the WHOLE leftover
112
- group — picture + name + title together — via raw XML (`doc-xml unpack`, remove
113
- the group's `<p:sp>`/`<p:grpSp>` elements, repack). Emptying just the text
114
- leaves orphaned portraits/decoration. The inverse also holds: more items than
115
- slots means another cloned slide, not squeezed-in extra lines.
116
-
117
- **N data items, no leftover token slide (the standard combo):** clone N-1 times
118
- (one per item), then `doc-fill` the whole deck with the LAST item's values — that
119
- fills the original template slide (it becomes item N) plus any global tokens
120
- (`{{company}}`, `{{quarter}}` on cover/footer) in one pass. Finish with
121
- `doc-validate`.
122
-
123
- Alternatively clone N times and drop the template slide afterwards:
88
+
89
+ `--palette <id>` forces one palette (`doc-gen --list-palettes`); `--accent <#rrggbb>` or
90
+ `--style <deck style id>` keeps white paper with one brand colour (bin-reference.md
91
+ "Generate" has the precedence).
92
+
93
+ **Choosing with the user:** (1) a remembered document-theme preference or a theme the
94
+ user saved (`ls "$TALOS_DOC_STYLES_DIR/themes"`) wins — use it and say so ("按你常用的 X
95
+ 主题,要换告诉我"); (2) a named style → match by 适用场景 in `themes/README.md` and apply;
96
+ (3) they want to choose → shortlist 2–4 by 适用场景 (a text shortlist is fine). To show
97
+ them, render a 1-page sample per candidate (`doc-gen pdf … --theme <path>` — its preview
98
+ PNG is the real look) and ask with `ask_user_question` type `single_choice`, one option
99
+ per theme `{ label: 主题名, description: 一句适用场景, imagePath: 样片PNG绝对路径 }`;
100
+ (4) after an explicit confirmation, save the choice to memory as their document-theme
101
+ preference, and update it when they switch.
102
+
103
+ **User-created themes** are the supported way to make a new template: write a theme.json
104
+ in the working folder and iterate (invalid fields fall back with warnings on stderr —
105
+ read and fix them). When the user hands you brand colours/logo or says "以后都用这个风格",
106
+ offer to keep it: save it as `$TALOS_DOC_STYLES_DIR/themes/<descriptive-name>.json`
107
+ (kebab-case, with a `bestFor` line) — their own styles, kept across sessions and shared
108
+ by every expert — and apply it by name, `--theme <descriptive-name>`. Never write inside
109
+ the skill directory: it is an integrity-checked install, reinstalled (your file lost)
110
+ once it changes. **An existing deck's colours** seed one:
111
+ `pptx-theme.cjs ref.pptx ref-theme.json` → `--theme ref-theme.json` (palette/fonts only).
112
+
113
+ Theme fields (all optional; an invalid value falls back to the default and warns on
114
+ stderr, never fails): `bestFor` (one-line 适用场景) · `fonts{heading,body}` ·
115
+ `colors{primary,accent,secondary,text,muted,tableHeaderBg,tableBorder}` (`accent`
116
+ defaults to `primary`, `secondary` to `muted`) · `page{size:A4|Letter,margin,header,footer}`
117
+ (`margin` is a CSS length string such as `margin:"20mm"` — mm/cm/in/pt/px; a bare number is not accepted and falls back to the default margin, which changes the page count;
118
+ `{title}` `{page}` `{pages}` interpolate in header/footer; **prefer Latin/digits there —
119
+ CJK in a PDF header/footer renders blank**) · `heading{h1..h4}{size,color,bold,spaceAfter}`
120
+ (color: hex or a palette key like `"primary"`) · `body{size,lineHeight,justify}` ·
121
+ `cover{enabled,subtitle,date,logo}` (base64 logo; the title moves onto the cover) ·
122
+ `pptx{…}` (read only by the legacy `html2pptx --theme`; ignore for documents). A spec may
123
+ embed `"theme": {...}`; `--theme` wins. In a **pdf** the theme becomes CSS + cover +
124
+ live page headers/footers; in a **docx**, real styles (Heading1–4, default font, table
125
+ header shading) + header/footer parts + cover.
126
+
127
+ ## Fill a template — `bin/doc-fill` (keeps the template's styling)
128
+
129
+ When the user has (or wants) a styled **template** with `{{placeholder}}` tokens, this is
130
+ the lossless way to fill it — fonts, tables, headers and layout stay verbatim.
124
131
 
125
132
  ```
126
- node <skill>/bin/pptx-clone.cjs remove out.pptx 1 final.pptx
133
+ node <skill>/bin/doc-fill.cjs <template.docx|pptx|xlsx> <values.json> <out>
127
134
  ```
128
135
 
129
- Removal keeps every other slide untouched (parts are not renumbered — the
130
- deck order lives in sldIdLst). Always `doc-validate` the final deck.
136
+ - Values: a JSON object. `{{name}}` ← `name`; nested `{{a.b}}` ← `{"a":{"b":…}}`.
137
+ - Loops (.docx/.pptx): `{{#rows}} … {{col}} … {{/rows}}` over `{"rows":[{...}, ...]}`.
138
+ - Prints `{ ok, out, unfilled }`: `unfilled` lists tokens left blank — surface them, never
139
+ call the document complete. Tokens Word split across runs are still filled; a malformed
140
+ or unclosed tag exits 1 and writes nothing.
141
+ - **.xlsx**: tokens are filled cell by cell (`{{a}}`, `{{a.b}}`), each cell keeping its
142
+ style; a cell that is exactly one token takes the value's type, so pass numbers as
143
+ numbers. No loops — build repeated rows with `doc-gen xlsx` (or exceljs via doc-run).
144
+ - No template yet → generate a first version with `doc-gen`, then fill that.
131
145
 
132
- ## Reuse a user's UPLOADED deck as a template — `pptx-tokenize` → `doc-fill` (B2)
146
+ Then `doc-validate` and render-and-look (SKILL.md "After writing a file").
133
147
 
134
- **Trigger is the STYLE intent, not "a .pptx was uploaded."** Use this ONLY when the
135
- user explicitly wants their new deck to KEEP an existing deck's look (their exact
136
- layouts/master/brand) — i.e. they chose it as their **style** (the Q3 "复用这份 PPT
137
- 的风格" custom path). If the `.pptx` was handed in as **content** (they just want its
138
- text in a NEW deck), do NOT tokenize — extract its text (`.txt` sidecar / `doc-text`)
139
- and author fresh in the chosen skin. Same file, two intents; the user's answer to the
140
- style question (Q3), not the file type, decides.
148
+ ## Edit an existing .docx
141
149
 
142
- When the STYLE intent holds — new content in the deck's exact look (their layouts,
143
- master, brand — NOT one of our themes) — turn the deck into a fillable template
144
- instead of guessing a theme:
150
+ Pick the least lossy path, and tell the user which trade-off you took:
145
151
 
146
- 1. **Tokenize** — replace every content slot's text with a clean `{{token}}` (a
147
- single run, so docxtemplater never corrupts it), keeping styling/master/layout
148
- verbatim, and emit a catalog:
152
+ 1. **Change text** → `doc-xml replace` (cross-run, keeps formatting; body incl. tables,
153
+ headers/footers, footnotes/endnotes — not comments). One call per string, matched within
154
+ one paragraph; `replaced: 0` means the text is not there as written — copy the exact
155
+ wording from the sidecar.
156
+ ```
157
+ node <skill>/bin/doc-xml.cjs replace in.docx out.docx "旧措辞" "新措辞"
158
+ ```
159
+ 2. **`{{placeholders}}`** → **Fill a template** above.
160
+ 3. **Anything structural** (no targeted tool fits) → raw XML. A .docx is a zip of XML
161
+ parts that reference each other through `_rels/*.rels`, each listed in
162
+ `[Content_Types].xml` — add or rename a part and update both. Coalesce split runs
163
+ first so your find hits:
164
+ ```
165
+ node <skill>/bin/doc-xml.cjs merge-runs report.docx merged.docx
166
+ node <skill>/bin/doc-xml.cjs unpack merged.docx unpacked/ # XML pretty-printed
167
+ # … edit unpacked/word/document.xml …
168
+ node <skill>/bin/doc-xml.cjs pack unpacked/ report-edited.docx
169
+ node <skill>/bin/doc-validate.cjs report-edited.docx --strict # MUST pass before delivering
170
+ ```
171
+ 4. **Content-first rewrite** → regenerate with `doc-gen` from the edited text — the
172
+ original styling is lost.
173
+
174
+ ### Review: comments and tracked changes — `bin/docx-annotate`
175
+
176
+ - **Comments**: `docx-annotate.cjs comment in.docx out.docx comments.json` with
177
+ `[{ "author": "…", "text": "…", "anchor"?: "…", "replyTo"?: 0 }]`. `anchor` is a
178
+ substring of ONE paragraph (the first paragraph containing it gets the comment; no
179
+ anchor → the document's first paragraph). An anchor that matches nothing is NOT
180
+ written: stdout reports `written` and `skipped` (`[{ index, anchor }]`) and stderr warns;
181
+ add `--strict-anchors` to exit 1 on any skip (all unmatched always exits 1). Copy anchors
182
+ verbatim from the text. `replyTo` = index of another comment in the same file, shown as
183
+ a threaded reply (a reply to a skipped comment warns and is written top-level).
184
+ - **Suggest an addition**: `docx-annotate.cjs suggest in.docx out.docx "<author>" "<text>"`
185
+ appends ONE new paragraph at the end of the body (before the final section properties)
186
+ as a tracked insertion — it cannot target a position. Rejecting it removes the paragraph.
187
+ - **Tracked change in place** (replace or delete wording): raw XML (path 3 above) — wrap
188
+ the old run in `<w:del>` (its `<w:t>` becomes `<w:delText>`) and the new one in `<w:ins>`:
189
+ ```xml
190
+ <w:del w:id="9101" w:author="Claude" w:date="2026-01-01T00:00:00Z"><w:r><w:delText>旧</w:delText></w:r></w:del><w:ins w:id="9102" w:author="Claude" w:date="2026-01-01T00:00:00Z"><w:r><w:t>新</w:t></w:r></w:ins>
191
+ ```
192
+ (`w:id` unique in the document; keep the run's `<w:rPr>` in both to keep its format.)
193
+ - **After ANY redline edit**: `docx-annotate.cjs redline-check original.docx edited.docx`
194
+ rejects every tracked change and requires the original text back exactly — the proof
195
+ that nothing changed untracked. Exit 1 prints `firstDivergence`; redo that edit as a
196
+ tracked change.
197
+ - **Accept all**: `docx-annotate.cjs accept in.docx out.docx` keeps insertions, drops
198
+ deletions and format-change records, and prints the counts. A deleted paragraph mark
199
+ keeps both paragraphs (content is never lost).
200
+
201
+ ## Template decks — fill or restructure an existing .pptx
202
+
203
+ **Which tool**: keep an uploaded deck's LAYOUTS for new content → `pptx-tokenize` →
204
+ `doc-fill`; repeat one styled slide per data item → `pptx-clone`; add, duplicate, drop or
205
+ reorder slides → `pptx-slide`; only its COLOURS → `pptx-theme` into a user skin
206
+ (deck-design.md "Custom skins"). Always `doc-validate` the result, then render-and-look.
207
+
208
+ ### Reuse an uploaded deck — `pptx-tokenize` → `doc-fill`
209
+
210
+ Only when the user chose the deck as their **style** (the picker's "使用我的模板文件"); a
211
+ deck handed in as **content** is read for its text and authored fresh in the chosen skin.
212
+
213
+ 1. **Tokenize** — every content slot's text (placeholders and plain text boxes) becomes a
214
+ clean single-run `{{token}}`; styling/master/layouts stay verbatim:
149
215
  ```
150
216
  node <skill>/bin/pptx-tokenize.cjs user.pptx template.pptx schema.json
151
217
  ```
152
- `schema.json` lists each token: `{ slide, token, role (title/subtitle/body/
153
- text/pic/chart), type, multiline?, original, capacityChars }`. Plain text
154
- boxes with NO PowerPoint placeholder are tokenized too (role `text`) — most
155
- real decks position text in absolute boxes, not `<p:ph>` placeholders.
156
-
157
- 2. **Author `values.json` from the schema** — one key per token. **Fill EVERY
158
- token**: for slots you're keeping unchanged, pass their `original` back (an
159
- unfilled token renders EMPTY and would BLANK that text). `multiline:true`
160
- slots take a `\n`-joined string (renders as line breaks). Respect
161
- `capacityChars` — rewrite shorter rather than overflow (CJK counts double).
218
+ `schema.json` = `{ slides: [{ index, tokens: [{ token, role, type, multiline?,
219
+ original?, capacityChars?, rel? }] }] }` — `role` is title / subtitle / body / text /
220
+ pic / chart; `type` is `text`, `image` or `chart`.
221
+ 2. **Fill** — `values.json` has one key per `text` token (the key is the token name, no
222
+ braces). **Fill EVERY token**: a slot you keep gets its `original` back (an unfilled
223
+ token renders EMPTY). `multiline:true` takes a `\n`-joined string. Respect
224
+ `capacityChars` (CJK-width units; a Latin letter ≈ ½) — rewrite shorter rather than
225
+ overflow. `unfilled` in the output names tokens you missed — add them and refill.
162
226
  ```
163
227
  node <skill>/bin/doc-fill.cjs template.pptx values.json out.pptx
164
228
  ```
165
- `doc-fill` prints `{ unfilled }` — a non-empty `unfilled` means you missed
166
- tokens (they blanked); add those keys and refill.
167
-
168
- 3. **Swap images** — schema `type:"image"` tokens carry the slide + `rel`:
229
+ 3. **Images** — an `image` token carries its slide and `rel`:
169
230
  ```
170
231
  node <skill>/bin/pptx-set-image.cjs out.pptx <slide> <rel> new.png final.pptx
171
232
  ```
172
- The picture's frame (position/size) stays put — only the pixels change. If the
173
- new image's aspect ratio differs it STRETCHES; pre-crop it to the slot's ratio.
174
-
175
- 4. **Charts** keep their look but hold the SOURCE deck's embedded data (schema
176
- marks them `type:"chart"`, not a fill token) — edit the chart part separately,
177
- or tell the user the chart numbers are still from their file.
178
-
179
- 5. **`doc-validate`** the result, then render-and-look (doc-convert → PDF).
233
+ The frame stays put; a different aspect ratio STRETCHES — pre-crop to the slot's ratio.
234
+ 4. **Charts** keep the source deck's embedded data (`type:"chart"`, not a fill token) —
235
+ edit the chart part separately, or tell the user the numbers are still theirs.
180
236
 
181
- **Repeat one slide per data item?** After tokenizing you have a `{{token}}`
182
- template slide — the `pptx-clone` workflow above now applies (clone N times, fill
183
- each, drop the template slide).
184
-
185
- **Which path**: reuse their LAYOUTS → this (B2). Reuse only their COLORS in OUR
186
- layouts → `pptx-theme` (extract palette) + our families. Repeat a styled slide
187
- per data row → `pptx-clone`.
188
-
189
- All-Node generation (no Python): `docx`, `exceljs`, and Chromium for PDF; decks go
190
- through html2pptx. Same capability tier as the Anthropic docx/pptx/xlsx skills.
191
-
192
- ## Generate — `bin/doc-gen` (self-contained; no npm install, works offline)
237
+ ### One styled slide per data item — `pptx-clone`
193
238
 
194
239
  ```
195
- node <skill>/bin/doc-gen.cjs <docx|xlsx|pdf> <out> <spec.json>
240
+ node <skill>/bin/pptx-clone.cjs template.pptx 1 out.pptx replacements.json
241
+ # replacements.json: { "{{region}}": "华东区", "{{owner}}": "Alice" }
196
242
  ```
197
- Simple spec shapes:
198
- - **docx / pdf**: `{ "title"?: "...", "paragraphs": ["...", ...] }`
199
- - **xlsx**: `{ "sheets": { "SheetName": [["h1","h2"], ["a", 1], ...] } }` (row 1 = bold header)
200
-
201
- `doc-gen pptx` exits 64 and points at html2pptx: decks are laid out as HTML
202
- (deck-design.md), never generated from a slide spec.
203
-
204
- `doc-gen` validates these shapes **strictly before rendering**: unknown fields are
205
- errors, not ignored. For DOCX/PDF use `paragraphs` and/or `blocks`—never
206
- `sections`. A title-only artifact also fails unless it is intentional and the
207
- spec explicitly sets `"allowCoverOnly": true`; on `DOC_SPEC_INVALID`, correct
208
- the reported JSON path and rerun rather than delivering the previous output.
209
-
210
- Rich spec shapes (use these for real documents, not just prose):
211
- - **docx** `blocks`: ordered list of
212
- - `{ "type": "heading", "text": "...", "level": 1..4 }`
213
- - `{ "type": "paragraph", "text": "...", "bold"?, "italic"? }`
214
- - `{ "type": "list", "items": ["..."], "ordered"?: true }`
215
- - `{ "type": "table", "rows": [["h"],["a"]], "header"?: true, "columnWidths"?: [3000, ...] }` (DOCX widths are twips; defaults to full-page equal-width fixed table)`
216
- - `{ "type": "image", "data": "<base64>", "imageType"?: "png", "width"?, "height"? }`
217
- - **xlsx** per sheet, either the row array above OR
218
- `{ "rows": [...], "header"?, "columnWidths"?: [n...], "numFmt"?: ["#,##0.00"...] }`.
219
- A cell string starting with `=` is written as a **live formula** (e.g. `"=SUM(A2:A3)"`).
220
- Under the hood it uses `docx` / `exceljs`, and prints PDF in Chromium — the same
221
- tier as the Anthropic docx/pptx/xlsx skills, but all-Node (no Python /
222
- LibreOffice) and bundled into the script. For anything past these shapes, write a
223
- short Node script using those libraries directly (they are on the skill's path).
224
-
225
- **PDF rendering (always Chromium, one layout everywhere):** `doc-gen pdf`
226
- renders your spec as themed HTML and prints it in Chromium — professional CJK
227
- line-breaking, per-glyph font fallback (Latin + 中文 mixed just works),
228
- justification, tables, clickable links that wrap inside the page, and the
229
- theme's page size and margin. Inside Talos it goes through the app's service
230
- (`[pdf (chromium)]`); elsewhere it uses a headless Chromium
231
- (`[pdf (headless chromium)]`, needs Playwright). With neither it exits 69 and
232
- writes nothing — tell the user the PDF could not be produced. The spec's
233
- `blocks` (headings/lists/tables) are supported, not just `title`/`paragraphs`.
234
-
235
- ## Fill a template — `bin/doc-fill` (preserves styling; the best "edit" path)
236
-
237
- When the user has (or wants) a styled **template** with `{{placeholder}}` tokens,
238
- this is the lossless way to produce a filled document — the original fonts,
239
- tables, headers and layout are kept verbatim.
240
243
 
241
- ```
242
- node <skill>/bin/doc-fill.cjs <template.docx|pptx|xlsx> <values.json> <out>
243
- ```
244
- - Values: a JSON object. `{{name}}` ← `name`; nested `{{a.b}}` ← `{"a":{"b":…}}`.
245
- - Loops: `{{#rows}} … {{col}} … {{/rows}}` over `{"rows":[{...}, ...]}`.
246
- - Output prints `{ ok, out, unfilled }`; `unfilled` lists template tokens you left
247
- blank — surface them to the user rather than pretending the doc is complete.
248
- - Works across .docx/.pptx/.xlsx and correctly fills placeholders that Word split
249
- across runs (a naive find/replace misses those). Malformed/unclosed tags fail
250
- loudly — no half-rendered file.
244
+ The clone is appended at the end; the template slide keeps its tokens for the next clone
245
+ (chain the output for N items). Surface `missing` tokens instead of pretending they
246
+ filled. For N items with no leftover token slide: clone N-1 times, then `doc-fill` the
247
+ whole deck with the LAST item's values — that fills the template slide (it becomes item
248
+ N) plus global tokens (`{{company}}` on cover/footer) in one pass. Or clone N times and
249
+ drop the template: `pptx-clone.cjs remove out.pptx 1 final.pptx`. To fill a deck ONCE (no
250
+ repetition), `doc-fill` the .pptx directly.
251
251
 
252
- If the user has no template, generate a first version with `doc-gen`, then treat
253
- it as a template on subsequent fills.
252
+ - **CAPACITY warnings** (`CAPACITY {{token}}: … likely overflow` on stderr): rewrite the
253
+ value shorter — never shrink the font (breaks the type hierarchy), never truncate with
254
+ an ellipsis.
255
+ - **Slots ≠ item count**: more slots than content (a 4-person team page for 3 people) →
256
+ delete the WHOLE leftover group — picture + name + title — via raw XML (remove its
257
+ `<p:sp>`/`<p:grpSp>`); emptying just the text leaves orphaned portraits. More items
258
+ than slots → another cloned slide, not squeezed-in lines.
254
259
 
255
- ## Slide-lifecycle surgery — `bin/pptx-slide` (restructure an existing deck)
256
-
257
- Structural changes to an existing `.pptx` that need NO `{{token}}` template and
258
- preserve every slide's styling/master (the OOXML rels/Content_Types/sldIdLst
259
- bookkeeping is done for you — lossless):
260
+ ### Slide surgery — `pptx-slide`
260
261
 
261
262
  ```
262
263
  node <skill>/bin/pptx-slide.cjs add in.pptx out.pptx [--layout N] # append a blank slide (inherits a layout)
263
264
  node <skill>/bin/pptx-slide.cjs dup in.pptx 2 out.pptx # duplicate slide 2 (copy appended at end)
264
265
  node <skill>/bin/pptx-slide.cjs remove in.pptx 5 out.pptx # drop slide 5
265
- node <skill>/bin/pptx-slide.cjs reorder in.pptx 3,1,2 out.pptx # permute deck order (current 1-based positions)
266
+ node <skill>/bin/pptx-slide.cjs reorder in.pptx 3,1,2 out.pptx # new order, as current 1-based positions
266
267
  ```
267
268
 
268
- `add` binds the new slide to the LAST slide's layout unless `--layout N` picks
269
- one (so it inherits the deck's master/theme); its body is empty — fill it in
270
- PowerPoint or with a follow-up `doc-xml` edit. `dup` is `pptx-clone` with no
271
- replacements. `reorder` rewrites only `<p:sldIdLst>` (no parts move). This is the
272
- "edit any deck in place" toolkit for STRUCTURE; for in-slide TEXT edits use
273
- `doc-fill` (templated) or `doc-xml` (raw). Always `doc-validate` the output.
274
-
275
- ## Other edits (all lossy — pick per goal)
276
-
277
- Editing arbitrary content *in place while preserving layout* (no placeholders) is
278
- an unsolved problem. The realistic paths:
279
-
280
- 1. **Fill a template** — above; lossless when the doc is (or can be made) a
281
- `{{placeholder}}` template. Prefer this.
282
- 2. **Extract → rewrite → regenerate** — `doc-text`, edit the text, regenerate a
283
- fresh file. Loses original styling. Best for content-first edits.
284
- 3. **PDF page ops** — `bin/pdf-ops` merges/splits/rotates/watermarks existing PDFs
285
- without re-rendering (preserves every page verbatim). Additive/structural only.
286
- 4. **Overlay / annotate** — stamp marks on an existing PDF with `pdf-lib`.
287
- Preserves the original; additive only.
288
-
289
- State which trade-off you took so the user isn't surprised by lost formatting.
269
+ Lossless; rels/Content_Types/sldIdLst bookkeeping is done for you. `add` binds the last
270
+ slide's layout unless `--layout N`; its body is empty — fill it with `doc-xml`. In-slide
271
+ TEXT goes through `doc-fill` (templated) or `doc-xml` (raw).
290
272
 
291
273
  ## Custom scripts — `bin/doc-run` (the long-tail escape hatch)
292
274
 
293
- When the built-in verbs and spec blocks can't express what you need (an exotic
294
- layout, a bulk transformation, a bespoke workbook), **write a Node script and
295
- run it with `doc-run`** — the engine's pure-JS libraries are bundled inside it,
296
- so this works on ANY machine (packaged app, clean install, no node_modules):
275
+ When no verb or spec block expresses it (an exotic layout, a bulk transformation, a
276
+ styled workbook), write a CommonJS Node script and run it with `doc-run` — the engine's
277
+ pure-JS libraries are bundled inside it, so it works on any machine:
297
278
 
298
279
  ```
299
280
  node <skill>/bin/doc-run.cjs myscript.js [args…]
300
281
  node <skill>/bin/doc-run.cjs --list # the guaranteed module registry
301
282
  ```
302
283
 
303
- Inside the script, plain CommonJS `require()` of these names is guaranteed:
304
- `pptxgenjs` (non-deck pptx only — data tables, one-off shapes; every deck goes through html2pptx) · `docx` (Word) · `exceljs` (formatted workbooks: fills,
305
- fonts, borders, merges, formulas) · `xlsx` (SheetJS: reading and plain-data
306
- workbooks only — it writes no cell styles) · `pdf-lib` (PDF assembly) ·
307
- `jszip` / `pizzip` (OOXML zip surgery) · `hyperformula` (formula evaluation).
308
- An exceljs formula is written without a cached result; run
309
- `bin/xlsx-recalc.cjs` on the file when the values must show before Excel
310
- recalculates. Nothing else is guaranteed — do not
311
- `require` other packages, and never run `npm install`.
312
-
313
- Rules of the tier ladder (try in this order, don't skip levels). **NOTE — decks
314
- are the exception: a deck DEFAULTS to html2pptx (SKILL.md routing), not to
315
- doc-gen; the ladder below is the escalation order for the long-tail / non-deck
316
- custom case (exotic layouts, bulk transforms, bespoke workbooks).**
317
- 1. **Built-in verbs / spec blocks** (doc-gen, doc-fill, pptx-clone, docx-annotate) — stable, themed, tested.
318
- 2. **html2pptx** (decks — the default deck path; in-app or bundled-headless) — think in HTML/CSS layout, get native editable slides (see deck-design.md).
319
- 3. **`doc-run` custom script** — full library power, themed by reading the theme JSON yourself (see deck-design.md).
320
- 4. **Raw XML** (doc-xml unpack/pack) — when even the libraries can't express it.
321
-
322
- And the one unbreakable rule: **every produced file goes through
323
- `bin/doc-validate.cjs` before delivery — with `--strict` for anything from
324
- tiers 2–4** (custom code has no safety net except that gate; --strict is the
325
- full ECMA-376 schema check).
326
-
327
- ## Raw XML editing (advanced escape hatch)
328
-
329
- When no targeted tool (doc-fill / docx-annotate / pptx-clone) covers an edit:
330
-
331
- ```
332
- node <skill>/bin/doc-xml.cjs unpack report.docx unpacked/ # XML pretty-printed
333
- # … edit unpacked/word/document.xml …
334
- node <skill>/bin/doc-xml.cjs pack unpacked/ report-edited.docx
335
- node <skill>/bin/doc-validate.cjs report-edited.docx # MUST pass before delivering
336
- ```
284
+ `require()` of these is guaranteed: `docx` (Word) · `exceljs` (formatted workbooks:
285
+ fills, fonts, borders, merges, formulas — then **Formulas**) · `xlsx` (SheetJS: reading
286
+ and plain-data workbooks only — it writes no cell styles) · `pdf-lib` (PDF assembly) ·
287
+ `jszip` / `pizzip` (OOXML zip surgery) · `hyperformula` (formula evaluation) ·
288
+ `pptxgenjs` (non-deck pptx only — every deck goes through html2pptx). Nothing else: no
289
+ other package, never `npm install`. Theme the output by reading a theme JSON yourself.
337
290
 
338
- A .docx/.pptx/.xlsx is a zip of XML parts; parts reference each other through
339
- `_rels/*.rels` and every part needs a `[Content_Types].xml` entry — if you add
340
- or rename parts, update both, then let doc-validate catch what you missed.
341
- Text is often split across multiple `<w:r>` runs — for a .docx, run
342
- `doc-xml.cjs merge-runs in.docx merged.docx` FIRST to coalesce same-format
343
- fragments ("He"+"llo"→"Hello"), then unpack; your find/replace will hit.
344
-
345
- ## Review workflow (comments + tracked changes)
346
-
347
- - Comment threads: give a comment `"replyTo": <index-in-this-batch>` in
348
- comments.json and Word shows it as a reply; resolved-state/author-identity
349
- parts are written automatically.
350
- - `docx-annotate.cjs suggest` adds a tracked insertion the user can
351
- accept/reject in Word; `docx-annotate.cjs accept in.docx out.docx` accepts
352
- ALL tracked changes (insertions kept, deletions dropped, format-change
353
- records cleared) and prints the counts. A deleted *paragraph mark* keeps both
354
- paragraphs (documented limitation — content is never lost).
355
- - **After ANY redline edit** (suggest, or hand-written `<w:ins>`/`<w:del>` via
356
- doc-xml): run `docx-annotate.cjs redline-check original.docx edited.docx`.
357
- It rejects every tracked change and requires the result to equal the
358
- original text exactly — the mathematical proof that you changed nothing
359
- without tracking it. Non-zero exit prints the first diverging paragraph;
360
- redo that edit as a tracked change.
291
+ Order of escalation: built-in verbs → `doc-run` → raw XML (`doc-xml`). Custom code has no
292
+ safety net but the gate: **`doc-validate --strict` every file it produces** (the full
293
+ ECMA-376 schema check).