dsh-office-kit 0.4.1 → 0.4.3

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 (116) hide show
  1. package/assets/skills/excel-live-control/SKILL.md +120 -148
  2. package/assets/skills/excel-live-control/container_tools/live_excel.py +9 -0
  3. package/assets/skills/excel-live-control/domain_guidance/financial_models.md +42 -58
  4. package/assets/skills/excel-live-control/domain_guidance/healthcare.md +36 -42
  5. package/assets/skills/excel-live-control/domain_guidance/marketing_advertising.md +33 -34
  6. package/assets/skills/excel-live-control/domain_guidance/scientific_research.md +34 -36
  7. package/assets/skills/excel-live-control/references/office-automation.md +127 -153
  8. package/assets/skills/excel-live-control/references/read_only_qna.md +40 -45
  9. package/assets/skills/excel-live-control/style_guidelines.md +56 -64
  10. package/assets/skills/excel-live-control/workflows/create_workflows.md +40 -47
  11. package/assets/skills/excel-live-control/workflows/edit_workflows.md +37 -45
  12. package/assets/skills/office-documents/SKILL.md +24 -60
  13. package/assets/skills/office-documents/examples/end_to_end_smoke_test.md +5 -5
  14. package/assets/skills/office-documents/ooxml/comments.md +8 -8
  15. package/assets/skills/office-documents/ooxml/hyperlinks_and_fields.md +10 -10
  16. package/assets/skills/office-documents/ooxml/rels_and_content_types.md +7 -7
  17. package/assets/skills/office-documents/ooxml/tracked_changes.md +12 -12
  18. package/assets/skills/office-documents/references/docx-structure.md +11 -13
  19. package/assets/skills/office-documents/references/template-elicitation.md +11 -13
  20. package/assets/skills/office-documents/references/writing-quality.md +20 -20
  21. package/assets/skills/office-documents/tasks/accessibility_a11y.md +16 -22
  22. package/assets/skills/office-documents/tasks/captions_crossrefs.md +23 -26
  23. package/assets/skills/office-documents/tasks/clean_tracked_changes.md +17 -22
  24. package/assets/skills/office-documents/tasks/comments_manage.md +9 -11
  25. package/assets/skills/office-documents/tasks/compare_diff.md +10 -16
  26. package/assets/skills/office-documents/tasks/create_edit.md +14 -16
  27. package/assets/skills/office-documents/tasks/equations.md +17 -28
  28. package/assets/skills/office-documents/tasks/fields_update.md +14 -16
  29. package/assets/skills/office-documents/tasks/fixtures_edge_cases.md +19 -33
  30. package/assets/skills/office-documents/tasks/footnotes_endnotes.md +12 -14
  31. package/assets/skills/office-documents/tasks/forms_content_controls.md +16 -20
  32. package/assets/skills/office-documents/tasks/headings_numbering.md +17 -22
  33. package/assets/skills/office-documents/tasks/images_figures.md +15 -25
  34. package/assets/skills/office-documents/tasks/multi_doc_merge.md +13 -23
  35. package/assets/skills/office-documents/tasks/navigation_internal_links.md +18 -21
  36. package/assets/skills/office-documents/tasks/privacy_scrub_metadata.md +16 -22
  37. package/assets/skills/office-documents/tasks/protection_restrict_editing.md +19 -21
  38. package/assets/skills/office-documents/tasks/read_review.md +9 -20
  39. package/assets/skills/office-documents/tasks/redaction_anonymization.md +17 -18
  40. package/assets/skills/office-documents/tasks/sections_layout.md +16 -23
  41. package/assets/skills/office-documents/tasks/style_lint_normalize.md +18 -22
  42. package/assets/skills/office-documents/tasks/tables_spreadsheets.md +21 -31
  43. package/assets/skills/office-documents/tasks/templates_style_packs.md +17 -21
  44. package/assets/skills/office-documents/tasks/toc_workflow.md +10 -14
  45. package/assets/skills/office-documents/tasks/verify_render.md +11 -23
  46. package/assets/skills/office-documents/tasks/watermarks_background.md +11 -15
  47. package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +9 -9
  48. package/assets/skills/office-documents/troubleshooting/run_splitting.md +12 -12
  49. package/assets/skills/office-pdf/SKILL.md +36 -51
  50. package/assets/skills/office-pdf/references/form-fields.md +20 -20
  51. package/assets/skills/office-pdf/references/geometry.md +10 -10
  52. package/assets/skills/office-pdf/references/layout-quality.md +13 -13
  53. package/assets/skills/office-pdf/references/limits.md +12 -56
  54. package/assets/skills/office-pdf/tasks/annotations.md +5 -5
  55. package/assets/skills/office-pdf/tasks/create.md +6 -9
  56. package/assets/skills/office-pdf/tasks/edit-pages.md +14 -14
  57. package/assets/skills/office-pdf/tasks/forms.md +15 -15
  58. package/assets/skills/office-pdf/tasks/read.md +9 -9
  59. package/assets/skills/office-pdf/tasks/redact.md +18 -18
  60. package/assets/skills/office-pdf/tasks/stamp.md +10 -10
  61. package/assets/skills/office-presentations/SKILL.md +71 -91
  62. package/assets/skills/office-presentations/container_tools/render_slides.py +1 -1
  63. package/assets/skills/office-presentations/domain_guidance/finance.md +16 -16
  64. package/assets/skills/office-presentations/recipes/README.md +24 -35
  65. package/assets/skills/office-presentations/recipes/autolayout.md +12 -13
  66. package/assets/skills/office-presentations/recipes/connectors.md +11 -12
  67. package/assets/skills/office-presentations/recipes/images.md +11 -12
  68. package/assets/skills/office-presentations/recipes/master-and-layouts.md +18 -18
  69. package/assets/skills/office-presentations/recipes/native-charts.md +8 -8
  70. package/assets/skills/office-presentations/recipes/rich-text-runs.md +13 -13
  71. package/assets/skills/office-presentations/recipes/speaker-notes.md +7 -7
  72. package/assets/skills/office-presentations/recipes/tables.md +14 -14
  73. package/assets/skills/office-presentations/recipes/textbox-at-coordinate.md +9 -9
  74. package/assets/skills/office-presentations/recipes/theme-tokens.md +7 -8
  75. package/assets/skills/office-presentations/references/authoring-workflow.md +38 -43
  76. package/assets/skills/office-presentations/references/finalization.md +10 -12
  77. package/assets/skills/office-presentations/references/native-bullets.md +14 -18
  78. package/assets/skills/office-presentations/references/native-evidence.md +18 -32
  79. package/assets/skills/office-presentations/references/style-guidelines.md +27 -32
  80. package/assets/skills/office-presentations/references/templates.md +31 -60
  81. package/assets/skills/office-presentations/routing/google_slides.md +4 -4
  82. package/assets/skills/office-presentations/tasks/apply-annotations.md +17 -17
  83. package/assets/skills/office-presentations/tasks/charts-tables-notes.md +20 -23
  84. package/assets/skills/office-presentations/tasks/create-deck.md +19 -19
  85. package/assets/skills/office-presentations/tasks/edit-deck.md +14 -16
  86. package/assets/skills/office-presentations/tasks/inspect-and-verify.md +31 -34
  87. package/assets/skills/office-presentations/tasks/template-following.md +23 -28
  88. package/assets/skills/office-spreadsheets/SKILL.md +32 -39
  89. package/assets/skills/office-spreadsheets/recipes/charts.md +8 -26
  90. package/assets/skills/office-spreadsheets/recipes/conditional-formats.md +9 -26
  91. package/assets/skills/office-spreadsheets/recipes/data-tables.md +7 -23
  92. package/assets/skills/office-spreadsheets/recipes/named-ranges.md +8 -21
  93. package/assets/skills/office-spreadsheets/recipes/number-formats.md +7 -23
  94. package/assets/skills/office-spreadsheets/recipes/sparklines.md +7 -20
  95. package/assets/skills/office-spreadsheets/references/charts.md +15 -34
  96. package/assets/skills/office-spreadsheets/references/domain/financial-models.md +10 -25
  97. package/assets/skills/office-spreadsheets/references/domain/healthcare.md +8 -19
  98. package/assets/skills/office-spreadsheets/references/domain/marketing.md +8 -22
  99. package/assets/skills/office-spreadsheets/references/domain/research.md +8 -19
  100. package/assets/skills/office-spreadsheets/references/formula-patterns.md +20 -50
  101. package/assets/skills/office-spreadsheets/references/style-guidelines.md +19 -44
  102. package/assets/skills/office-spreadsheets/references/workbook-structure.md +12 -35
  103. package/assets/skills/office-spreadsheets/routing/google-sheets.md +11 -26
  104. package/assets/skills/office-spreadsheets/tasks/analyze-workbook.md +10 -25
  105. package/assets/skills/office-spreadsheets/tasks/annotations.md +12 -30
  106. package/assets/skills/office-spreadsheets/tasks/create-workbook.md +5 -5
  107. package/assets/skills/office-spreadsheets/tasks/diff-workbooks.md +2 -2
  108. package/assets/skills/office-spreadsheets/tasks/edit-workbook.md +5 -5
  109. package/assets/skills/office-spreadsheets/tasks/inspect-workbook.md +9 -9
  110. package/assets/skills/office-spreadsheets/tasks/recalc-and-check.md +10 -30
  111. package/assets/skills/shared/annotation-recipes.md +98 -120
  112. package/assets/skills/shared/common-workflow.md +70 -80
  113. package/dist/index.js +105 -29
  114. package/package.json +1 -1
  115. package/postinstall.mjs +58 -17
  116. package/python/office_cli.py +70 -7
@@ -1,64 +1,54 @@
1
- # Tables and spreadsheets
1
+ # Tables and layout geometry
2
2
 
3
- **Goal.** A table that fits the page, repeats its header row across pages, and survives an edit without cells drifting out of alignment.
4
-
5
- A table's geometry is three things that must agree: the grid (`w:tblGrid`, one `w:gridCol` per column), the table width (`w:tblW`), and each cell width (`w:tcW`). When they disagree Word shows a table wider than the text block or columns that jump on the first edit.
3
+ Table geometry is three things that must agree: the grid (`w:tblGrid`, one `w:gridCol` per column), the table width (`w:tblW`) and each cell width (`w:tcW`). When they disagree, Word shows a table wider than the text block, or columns that jump on the first edit.
6
4
 
7
5
  ## Create
8
6
 
9
- ```bash
10
- "<python>" "<cli>" docx create --spec "<spec.json>" --out "<out.docx>"
11
- ```
7
+ Use `docx create --spec` with a table block:
12
8
 
13
9
  ```json
14
- {
15
- "type": "table",
16
- "headers": ["Region", "Revenue", "Growth"],
17
- "rows": [["EMEA", "4.1M", "12%"], ["APAC", "3.2M", "9%"]],
18
- "style": "Table Grid"
19
- }
10
+ { "type": "table", "headers": ["Region", "Q1"], "rows": [["North", "10"]], "style": "Table Grid" }
20
11
  ```
21
12
 
22
- Header cells are bold. For a document with the header repeating on every page, follow up with the a11y fix, which writes `w:tblHeader` on row 1:
13
+ Header cells are bold. To repeat the header row on every page:
23
14
 
24
15
  ```bash
25
- "<python>" "<skill>/scripts/a11y_audit.py" "<out.docx>" --fix_table_headers first_row --out "<out-fixed.docx>"
16
+ "<python>" "<skill>/scripts/a11y_audit.py" "<out.docx>" --fix_table_headers first_row --out "<out-fixed.docx"
26
17
  ```
27
18
 
19
+ That writes `w:tblHeader` on row 1.
20
+
28
21
  ## Inspect and fix geometry
29
22
 
30
23
  ```bash
31
24
  "<python>" "<skill>/scripts/table_geometry.py" "<file.docx>"
32
- "<python>" "<skill>/scripts/table_geometry.py" "<file.docx>" --fix --out "<file-fixed.docx>"
25
+ "<python>" "<skill>/scripts/table_geometry.py" "<file.docx>" --fix --out "<file-fixed.docx"
33
26
  ```
34
27
 
35
- The audit reports `tblW`, layout mode, every grid column in twips and the sum, plus the narrowest and widest cell. `--fix` writes a fixed-layout table at the text width (page width minus margins), rescales the grid proportionally and rewrites each `tcW` including `gridSpan`.
36
-
37
- Text width on Letter with 1in margins is 9360 twips — that is the number a full-width table should total.
28
+ The audit reports `tblW`, the layout mode, every grid column in twips with their sum, and the narrowest and widest cell. `--fix` writes a fixed-layout table at the text width (page width minus margins), rescales the grid proportionally and rewrites each `tcW`, including `gridSpan`. On Letter with 1in margins the text width is 9360 twips.
38
29
 
39
- ## Convert
30
+ ## Move data between formats
40
31
 
41
32
  ```bash
42
- "<python>" "<skill>/scripts/docx_table_to_csv.py" "<file.docx>" --table_index 0 --out "<table.csv>"
33
+ "<python>" "<skill>/scripts/docx_table_to_csv.py" "<file.docx>" --table_index 0 --out "<table.csv"
43
34
  "<python>" "<skill>/scripts/xlsx_to_docx_table.py" "<book.xlsx>" --sheet Revenue --out "<table.docx>" --title "Revenue by region"
44
35
  ```
45
36
 
46
- The first flattens a Word table (expanding horizontal spans, repeating vertically merged values) for data work. The second imports a worksheet as a Word table with a bold header row and `w:tblHeader`, which is the route for "put this spreadsheet in the report" — not a screenshot and not an embedded object.
37
+ `docx_table_to_csv.py` flattens the table, expands horizontal spans and repeats vertically merged values. `xlsx_to_docx_table.py` writes a bold header row plus `w:tblHeader`; it is the route for "put this spreadsheet in the report", not a screenshot or an embedded object.
47
38
 
48
39
  ## Design
49
40
 
50
- - Bold header row, light grey `#D9D9D9` borders, no shading inside the body.
51
- - Numeric columns right-aligned; text left-aligned. Thousands separators in the cells themselves.
52
- - Two or three columns of prose at most; a wide table wants landscape, and that means a section break (`tasks/sections_layout.md`), not a smaller font.
53
- - Caption above the table (`tasks/captions_crossrefs.md`), and keep it with the table.
54
- - Avoid vertical merges: they look fine in the file and confuse every downstream reader.
41
+ - Bold header row, light grey `#D9D9D9` borders, no shading in the body.
42
+ - Numeric right-aligned, text left-aligned, thousands separators inside the cells.
43
+ - Two or three prose columns at most. A wide table wants landscape through a section break (`tasks/sections_layout.md`), not a smaller font.
44
+ - Caption above the table (`tasks/captions_crossrefs.md`). Avoid vertical merges.
55
45
 
56
46
  ## Verify
57
47
 
58
- Render and look at the page the table lands on: nothing spills past the right margin, header row intact, no split row across pages, and the last column not truncated. A table that fits in the XML but overflows the page is a real defect, and only the render shows it.
48
+ Render the page: nothing past the right margin, header row intact, no row split across pages, last column not truncated. A table that fits in XML but overflows the page is a real defect that only the render shows.
59
49
 
60
50
  ## Pitfalls
61
51
 
62
- - A table inside a table cell inherits the cell width; check the outer table first.
63
- - `autofit` is a request, not a guarantee — fixed layout plus explicit widths is the only thing that renders the same everywhere.
64
- - Cell margins default to 108 twips left/right; that is padding, not missing width.
52
+ - A table inside a cell inherits the cell width, so check the outer table first.
53
+ - `autofit` is a request, not a guarantee.
54
+ - Cell margins default to 108 twips of padding.
@@ -1,53 +1,49 @@
1
1
  # Templates and style packs
2
2
 
3
- **Goal.** Make a new or existing document look like the house template, without copying the template's content into it.
4
-
5
- A `.dotx` is a `.docx` whose package is a template: `styles.xml` defines the look, `numbering.xml` defines lists, and the body is empty. Copying the body would import placeholder text; what you actually need is the styles part, the numbering definitions they reference, and the theme.
3
+ A `.dotx` is a `.docx` whose package is a template: `styles.xml` defines the look, `numbering.xml` defines lists, the body is empty. What you need is the styles part, the numbering definitions they reference, and the theme. Copying the body imports placeholder text.
6
4
 
7
5
  ## Ask before guessing
8
6
 
9
- Do not invent a house style. Get from the user: heading fonts and sizes, body font and size, accent colour, margins, table style, and whether a logo or cover page is required. `references/template-elicitation.md` has the question set that produces a spec.
7
+ Never invent a house style. Get heading fonts and sizes, body font and size, accent colour, margins, table style, and any logo or cover requirement. `references/template-elicitation.md` holds the question set.
10
8
 
11
- ## Apply a template
9
+ ## Apply
12
10
 
13
11
  ```bash
14
- "<python>" "<skill>/scripts/apply_template_styles.py" --template "<house.dotx>" --target "<draft.docx>" --out "<draft-styled.docx>"
12
+ "<python>" "<skill>/scripts/apply_template_styles.py" --template "<house.dotx>" --target "<draft.docx>" --out "<draft-styled.docx"
15
13
  ```
16
14
 
17
- The script merges the template's style definitions by `styleId` (replacing same-id definitions, appending missing ones, taking the template's `docDefaults`), and copies `numbering.xml` with its relationship and content-type override. The target's body is untouched, so the content survives and the look changes.
15
+ Merges style definitions by `styleId` (replacing same-id, appending missing, taking the template's `docDefaults`) and copies `numbering.xml` with its relationship and content-type override. The target's body is untouched.
18
16
 
19
- Then normalize so direct formatting does not override the template:
17
+ Then normalise so direct formatting does not override the imported styles:
20
18
 
21
19
  ```bash
22
- "<python>" "<skill>/scripts/style_normalize.py" "<draft-styled.docx>" --out "<draft-final.docx>" --clear_paragraph_format
20
+ "<python>" "<skill>/scripts/style_normalize.py" "<draft-styled.docx>" --out "<draft-final.docx" --clear_paragraph_format
23
21
  ```
24
22
 
25
23
  ## Restyle without a template
26
24
 
27
- When the user describes the style instead of supplying a file, set it on the styles rather than on runs:
25
+ Set values on styles, not on runs:
28
26
 
29
- ```bash
30
- "<python>" - <<'PY'
27
+ ```python
31
28
  from docx import Document
32
29
  from docx.shared import Pt, RGBColor
30
+
33
31
  d = Document(r"<draft.docx>")
34
- normal = d.styles["Normal"]
35
- normal.font.name = "Calibri"
36
- normal.font.size = Pt(11)
32
+ d.styles["Normal"].font.name = "Calibri"
33
+ d.styles["Normal"].font.size = Pt(11)
37
34
  for level, size in ((1, 18), (2, 14), (3, 12)):
38
35
  style = d.styles[f"Heading {level}"]
39
36
  style.font.size = Pt(size)
40
37
  style.font.color.rgb = RGBColor(0, 0, 0)
41
- d.save(r"<draft-styled.docx>")
42
- PY
38
+ d.save(r"<draft-restyled.docx>")
43
39
  ```
44
40
 
45
41
  ## Verify
46
42
 
47
- Render and compare against the template's own preview page by page: heading sizes, body leading, margins, table borders. A style page that looks right in a style list but wrong on the page usually means a direct-formatting run left over — run the lint.
43
+ Render and compare page by page with the template's own preview: heading sizes, body leading, margins, table borders. A style that looks right in the pane but wrong on the page usually has a leftover direct-formatting run — run the lint (`tasks/style_lint_normalize.md`).
48
44
 
49
45
  ## Pitfalls
50
46
 
51
- - A template applied over content that references missing styles produces the fallback look, not an error; run `style_lint.py` to find undefined references.
52
- - Themes (`word/theme/theme1.xml`) carry fonts and accent colours; a style that names a theme font changes when the theme does.
53
- - Keeping the template's `numbering.xml` without its `abstractNum` ids produces lists that all share one counter.
47
+ - A template applied over content that references missing styles produces the fallback look, not an error; run `style_lint.py`.
48
+ - Themes (`word/theme/theme1.xml`) carry fonts and accent colours, so theme-font styles change with the theme.
49
+ - Keeping the template's `numbering.xml` without its abstractNum ids makes lists share one counter.
@@ -1,26 +1,22 @@
1
1
  # Table of contents
2
2
 
3
- **Goal.** Put a navigable contents list at the front of the document, built from heading styles rather than typed by hand.
3
+ A TOC is a `TOC` field: Word fills it on recalculation and the file stores the last result. A hand-typed list looks identical, then goes wrong on the first edit.
4
4
 
5
- The TOC is a `TOC` field. Word fills it in when it recalculates; the file stores the last result. A hand-typed list looks identical in a screenshot and then goes wrong on the first edit.
6
-
7
- ## Insert
5
+ Insert:
8
6
 
9
7
  ```bash
10
8
  "<python>" "<skill>/scripts/insert_toc.py" "<file.docx>" --out "<file-toc.docx>" --levels 1-3
11
9
  ```
12
10
 
13
- The script looks for a placeholder paragraph containing `[[TOC]]` (override with `--placeholder`). If there is none it inserts the field at the start of the body. It writes `TOC \o "1-3" \h \z \u`, sets `w:updateFields`, and adds a short cached hint so the page is not blank before recalculation.
11
+ The script fills a placeholder paragraph containing `[[TOC]]` (override with `--placeholder`); with no placeholder it inserts the field at the start of the body. It writes `TOC \o "1-3" \h \z \u`, sets `w:updateFields`, and leaves a short cached hint so the page is not blank before recalculation.
14
12
 
15
- Check that every heading you want in the list uses a heading style: `heading_audit.py` finds headings formatted by hand, which the TOC will silently skip.
13
+ Every heading you want listed must use a heading style. `heading_audit.py` finds hand-formatted headings that the TOC silently skips.
16
14
 
17
15
  ## Rebuild after edits
18
16
 
19
- If headings changed, the cached list is stale. Either ask the user to update the field in Word (right-click → Update Field), or regenerate: strip the old field with `fields_materialize.py`? — no, that freezes it. Instead delete the field paragraphs and run `insert_toc.py` again. Freezing a TOC is almost never what a reader wants.
20
-
21
- ## Static contents list
17
+ The cached list goes stale after a structural edit. Ask the user to update the field in Word (right-click → Update Field), or delete the field paragraphs and re-run `insert_toc.py`. Do not freeze a TOC with `fields_materialize.py`.
22
18
 
23
- Sometimes the requirement really is a fixed list — a PDF export that will not be recalculated, or a template with a hand-built contents page. `internal_nav.py` builds one: a heading plus hyperlink entries to every heading, with optional back-links.
19
+ For a fixed PDF export or a hand-built template contents page, generate a static list with `internal_nav.py` instead:
24
20
 
25
21
  ```bash
26
22
  "<python>" "<skill>/scripts/internal_nav.py" "<file.docx>" --out "<file-nav.docx>" --levels 1 2
@@ -30,10 +26,10 @@ See `tasks/navigation_internal_links.md`.
30
26
 
31
27
  ## Verify
32
28
 
33
- Render the first pages and read them. A TOC that shows one line of grey hint text means the field never recalculated in that renderer; a TOC that shows the old headings means the document was rendered before the field updated. Say which one happened instead of claiming the TOC is correct.
29
+ Render the first pages. A line of grey hint text means the renderer never recalculated the field; old headings mean it rendered before the update. Say which happened instead of claiming the contents are correct.
34
30
 
35
31
  ## Pitfalls
36
32
 
37
- - TOC entries come from outline levels, not from text that merely looks like a heading.
38
- - A TOC inside a header or a text box does not work; it belongs in the body.
39
- - Level 4+ headings are excluded unless you widen `--levels`.
33
+ - TOC entries come from outline levels, not from text that looks like a heading.
34
+ - A TOC in a header or text box does not work — body only.
35
+ - Level 4 and deeper are excluded unless you widen `--levels`.
@@ -1,50 +1,38 @@
1
1
  # Visual verification
2
2
 
3
- **Goal.** See the pages before the user does. A document that parses and a document that looks right are different claims.
4
-
5
- ## Render
3
+ See the pages before the user does. A document that parses and a document that looks right are different claims.
6
4
 
7
5
  ```bash
8
6
  "<python>" "<cli>" docx render --input "<file.docx>" --out-dir "<render-dir>"
9
7
  ```
10
8
 
11
- Writes one PNG per page and prints their paths plus the renderer it used (`libreoffice-kit`); add `--pdf` to also keep a PDF. Use the same command through `render_docx.py` when you want the JSON report and page list:
9
+ Writes one PNG per page and prints the paths plus the renderer it used (`libreoffice-kit`); add `--pdf` to keep a PDF as well. For a JSON report and page list use `render_docx.py`:
12
10
 
13
11
  ```bash
14
12
  "<python>" "<skill>/render_docx.py" "<file.docx>" --output_dir "<render-dir>" --dpi 150
15
13
  ```
16
14
 
17
- ## Inspect
18
-
19
15
  Open every PNG with `read_image` and check, in this order:
20
16
 
21
17
  1. **Hierarchy** — does the page show the structure the user asked for, at a glance?
22
18
  2. **Fit** — nothing past the right margin, no clipped table column, no text over a watermark.
23
19
  3. **Breaks** — no heading alone at the bottom, no orphaned caption, no half-empty page before a section break.
24
- 4. **Text** — the words that were meant to change did change, and nothing else moved.
20
+ 4. **Text** — the intended words changed, and nothing else moved.
25
21
  5. **Glyphs** — dashes, quotes, CJK, currency symbols and maths render, not as boxes.
26
- 6. **Header and footer** — present, correct, not repeated on a first page that should not have it.
22
+ 6. **Header and footer** — present, correct, and not repeated on a first page that should not have one.
27
23
 
28
- ## When no renderer exists
24
+ Re-render after any script edit. Package-valid is not page-valid: a caption placed before the paragraph it describes, a table grown past the margin, a style merge that changed heading sizes — all are invisible in the XML and obvious in the image. To compare two revisions, `render_and_diff.py` writes a heatmapped difference page; read the diff images yourself, because a ratio is not a conclusion.
29
25
 
30
- The runtime reports `renderers.can_render.docx: false` because the DSH LibreOffice kit was not found. `render` then exits non-zero with a JSON error naming the missing renderer, and `render_docx.py` exits 3 with `reason: no_renderer`.
26
+ ## When no renderer exists
31
27
 
32
- In that state:
28
+ `office_status` reports `renderers.available: false` (with a `remedy`) because the DSH LibreOffice kit was not found. `render` then exits non-zero with a JSON error naming the missing renderer, and `render_docx.py` exits 3 with `reason: no_renderer`.
33
29
 
34
30
  - Verify structure with `docx read`, `docx_kit.py parts|text|xml` and the audit scripts.
35
- - Tell the user plainly that layout was not visually verified, and that the fix is a harness install that ships the kit (the plugin injects `DSH_LIBREOFFICE_KIT_CLI` when it loads), not installing Word/LibreOffice.
36
- - Do not claim a file "looks good" from XML evidence alone.
37
-
38
- ## After any script edit
39
-
40
- Re-render. Package-valid is not page-valid: a caption inserted before the paragraph it describes, a table grown past the margin, a style merge that changed heading sizes — all are invisible to the XML and obvious in the image.
41
-
42
- ## Comparing revisions
43
-
44
- `render_and_diff.py` renders two files and writes a heatmapped difference page. Read the diff images yourself; a ratio is not a conclusion.
31
+ - Say plainly that layout was not visually verified, and that the fix is a harness install shipping the kit (the plugin records its path in `config.json` when it loads), not installing Word or LibreOffice.
32
+ - Never claim a file "looks good" from XML evidence alone.
45
33
 
46
34
  ## Pitfalls
47
35
 
48
36
  - Different renderers paginate differently. Compare like with like, and do not promise Word-perfect pagination from a LibreOffice render.
49
- - A render fails silently into a blank page when a font is missing; a blank page is a finding, not a rendering bug. `missing_fonts` / `glyph_fallback` in the render JSON tells you substitution happened.
50
- - Render into a fresh directory; stale PNGs from an earlier run are the classic false pass.
37
+ - A missing font turns into a blank page: that is a finding, not a rendering bug. `missing_fonts` / `glyph_fallback` in the render JSON shows substitution happened.
38
+ - Render into a fresh directory: stale PNGs from an earlier run are the classic false pass.
@@ -1,35 +1,31 @@
1
- # Watermarks and background text
1
+ # Watermarks and background marks
2
2
 
3
- **Goal.** A diagonal DRAFT / CONFIDENTIAL mark behind the page content, and the ability to find and remove one that a template left behind.
4
-
5
- A Word watermark is not a document feature with its own element: it is a VML shape (`v:shape` with `v:textpath`) in a header part, positioned behind the text. That is why it disappears if the header is replaced, and why it can appear on some pages and not others when sections have their own headers.
3
+ A Word watermark is not its own element: it is a VML shape (`v:shape` with `v:textpath`) inside a header part, behind the text. That is why it disappears when the header is replaced and can differ per section.
6
4
 
7
5
  ## Add
8
6
 
9
7
  ```bash
10
- "<python>" "<skill>/scripts/watermark_add.py" "<file.docx>" --out "<file-draft.docx>" --text "DRAFT"
8
+ "<python>" "<skill>/scripts/watermark_add.py" "<file.docx>" --out "<file-draft.docx" --text "DRAFT"
11
9
  ```
12
10
 
13
- Writes the shape into the default header, light grey, rotated -45°, behind the body. The script refuses to add a second watermark when it finds one — remove the old one first, or you get two overlapping marks.
14
-
15
- For a per-page mark across a document with several sections, each section's header needs the shape; that is a loop over sections, not one insertion.
11
+ Writes the shape into the default header: light grey, rotated -45°, behind the body. The script refuses to add a second watermark when it finds one, so remove the old one first. For a mark on every page across sections, each section's header needs the shape — loop over the sections.
16
12
 
17
13
  ## Find and remove
18
14
 
19
15
  ```bash
20
16
  "<python>" "<skill>/scripts/watermark_audit_remove.py" "<file.docx>" --mode report
21
- "<python>" "<skill>/scripts/watermark_audit_remove.py" "<file.docx>" --mode remove --out "<file-clean.docx>"
17
+ "<python>" "<skill>/scripts/watermark_audit_remove.py" "<file.docx>" --mode remove --out "<file-clean.docx"
22
18
  ```
23
19
 
24
- The report lists the text of every VML text path and the part it lives in, and exits 1 when any watermark exists. `--contains` narrows to a substring, which is the case "the template says Company Confidential and it should not". Remove deletes the enclosing paragraph from the header.
20
+ The report lists every VML text path and the part it lives in, and exits 1 when any watermark exists. `--contains` narrows it to a substring, for the "template says Company Confidential" case. Remove deletes the enclosing paragraph from the header.
25
21
 
26
22
  ## Verify
27
23
 
28
- Render and read the pages: the mark sits behind the text (not over it), stays inside the page, and appears on every page the user expects. A watermark that vanishes on page two means that section has a different header.
24
+ Render and read the pages: the mark sits behind the text, inside the page, on every expected page. A watermark missing on page two means that section has a different header.
29
25
 
30
26
  ## Pitfalls
31
27
 
32
- - A watermark in a "different first page" header only shows on page one.
33
- - Removing the header paragraph can leave an empty header that adds vertical space; check the top margin on the rendered page.
34
- - An image watermark is an `v:imagedata` shape, not a text path — the report will not name it, so inspect the header XML when the visual mark is a logo.
35
- - Watermarks are not security. See `tasks/protection_restrict_editing.md` for what actually restricts editing.
28
+ - A watermark in a "different first page" header shows only on page one.
29
+ - Removing the header paragraph can leave an empty header that adds vertical space — check the top margin.
30
+ - An image watermark is `v:imagedata`, not a text path, so the report will not name it; inspect the header XML.
31
+ - A watermark is not security (`tasks/protection_restrict_editing.md`).
@@ -6,25 +6,25 @@
6
6
  "<python>" "<cli>" status
7
7
  ```
8
8
 
9
- Look at `renderers.kit` and `renderers.can_render`. `can_render.docx: false` means `docx render` will fail — not with a corrupt file, but with `the LibreOffice renderer is not available`. That is a setup problem, not a document problem.
9
+ Check `renderers.kit` and `renderers.can_render`. `can_render.docx: false` means `docx render` fails with `the LibreOffice renderer is not available` — a setup problem, not a document problem.
10
10
 
11
11
  ## What renders
12
12
 
13
- Rendering goes through the LibreOffice kit that DeepSeek Harness ships (`@deepseek-ai/libreoffice-kit`). On macOS and Windows it uses the kit's native engine; on Linux it uses the WASM engine. A separately installed Microsoft Office, WPS Office, or system LibreOffice is **not** used and does not make rendering work on its own.
13
+ Rendering goes through the LibreOffice kit DeepSeek Harness ships (`@deepseek-ai/libreoffice-kit`): the kit's native engine on macOS and Windows, the WASM engine on Linux. A separately installed Microsoft Office, WPS Office or system LibreOffice is **not** used and does not make rendering work.
14
14
 
15
- The plugin resolves the kit in this order:
15
+ The plugin records the kit's location when the harness loads it, and the helper reads that record before anything else:
16
16
 
17
- 1. `DSH_LIBREOFFICE_KIT_CLI` — the host half sets it (and `DSH_NODE`) when it loads, so every shell the agent spawns inherits it. That is what makes a profile install work, where the kit lives in the harness tree and not next to the plugin.
18
- 2. The harness entry point (`process.argv[1]`, e.g. `<dsh>/node_modules/@deepseek-ai/dsh/lib/bin.js`) → resolves the kit from the tree the harness itself loaded.
17
+ 1. `DSH_LIBREOFFICE_KIT_CLI` — an explicit override, also handy for a hand-run helper.
18
+ 2. `libreofficeKit` in `~/.dsh/office/config.json`. The host half writes it at load, resolving the kit from the harness entry point (`process.argv[1]`, e.g. `<dsh>/node_modules/@deepseek-ai/dsh/lib/bin.js`). This is what makes a profile install work, where the kit lives in the harness tree rather than next to the plugin. Configs that pre-0.5 versions wrote in the OS config directory are still read as fallbacks.
19
19
  3. Walking up from the plugin directory through `node_modules/@deepseek-ai/libreoffice-kit/lib/cli.js` — covers a checkout or a plugin-local dependency.
20
20
 
21
- Running the helper by hand outside the harness means setting `DSH_LIBREOFFICE_KIT_CLI` yourself; `DSH_NODE` overrides the node binary if `node` is not on `PATH`.
21
+ `DSH_OFFICE_CONFIG` points the helper at a different config file; `DSH_NODE` overrides the node binary when `node` is not on `PATH`.
22
22
 
23
23
  ## Common failures
24
24
 
25
25
  | Message | Cause | Fix |
26
26
  |---|---|---|
27
- | `the LibreOffice renderer is not available` | Kit not found | Run through the plugin (the host injects the path), or set `DSH_LIBREOFFICE_KIT_CLI` to the kit's `lib/cli.js` |
27
+ | `the LibreOffice renderer is not available` | Kit not found | Load the plugin through the harness (it records the path), or set `DSH_LIBREOFFICE_KIT_CLI` to the kit's `lib/cli.js` |
28
28
  | `cannot start the LibreOffice kit` / `EEXIST` | Interrupted install, or a stale staging directory | Re-run the plugin postinstall; delete leftover `.kit-render-*` directories |
29
29
  | `Unsupported LibreOfficeKit host` | Unsupported platform/arch (the kit ships Windows x64/arm64, macOS x64/arm64, Linux via WASM) | Render on a supported host |
30
30
  | `warning: N font(s) used by this file are not installed` | Font substitution — the render succeeded but that text may reflow or show boxes | Install the font or substitute it deliberately, then re-render |
@@ -33,7 +33,7 @@ Running the helper by hand outside the harness means setting `DSH_LIBREOFFICE_KI
33
33
 
34
34
  ## Working without a renderer
35
35
 
36
- Structure, text and metadata are all verifiable headlessly (`docx read`, the audit scripts, `docx_kit.py parts|text|xml`). Layout is not. In that state:
36
+ Structure, text and metadata are verifiable headlessly (`docx read`, the audit scripts, `docx_kit.py parts|text|xml`). Layout is not. In that state:
37
37
 
38
38
  1. Do the structural verification.
39
39
  2. Tell the user that page layout was not visually checked, and why.
@@ -43,4 +43,4 @@ Never describe a page you did not render.
43
43
 
44
44
  ## Keep renders disposable
45
45
 
46
- Render into a fresh temp directory each time (`--out-dir`). A stale PNG from a previous run reads as a pass and is the most common false confirmation.
46
+ Render into a fresh temp directory each time (`--out-dir`). A stale PNG from an earlier run reads as a pass — the most common false confirmation.
@@ -1,35 +1,35 @@
1
1
  # Splitting a long job
2
2
 
3
- A twenty-section document is not one command. Splitting it into bounded steps keeps each change verifiable and stops one failure from hiding inside a large edit.
3
+ A twenty-section document is not one command. Bounded steps keep each change verifiable and stop one failure hiding inside a large edit.
4
4
 
5
5
  ## One step per structural change
6
6
 
7
7
  1. **Plan** — the outline (headings and what each section must establish), written down before any file work.
8
- 2. **Skeleton** — create the file with the outline only: heading blocks, no body text.
9
- 3. **Content, in batches** — a batch per top-level section, appended with `append_blocks.py`.
10
- 4. **Numbers** — captions, cross-references, TOC once the structure is final, never while sections are still moving.
8
+ 2. **Skeleton** — the file with the outline only: heading blocks, no body text.
9
+ 3. **Content, in batches** — one batch per top-level section, appended with `append_blocks.py`.
10
+ 4. **Numbers** — captions, cross-references and TOC once the structure is final, never while sections are still moving.
11
11
  5. **Verify** — render, inspect, fix.
12
12
 
13
13
  ## Why this order
14
14
 
15
- - Captions and TOC are fields over structure; running them after every batch means renumbering noise and a TOC that points at deleted headings.
16
- - A batch of one section is small enough to redo when the layout is wrong.
17
- - The skeleton proves the heading hierarchy before the text makes it expensive to change.
15
+ - Captions and TOC are fields over structure; running them after every batch means renumbering noise and a TOC pointing at deleted headings.
16
+ - A one-section batch is small enough to redo when the layout is wrong.
17
+ - The skeleton proves the hierarchy before the text makes changing it expensive.
18
18
 
19
19
  ## Batch boundaries
20
20
 
21
- - One section per file: `section-01.docx`, `section-02.docx`, then `merge_docx_append.py`. This is the only sane way to draft in parallel or across sessions.
22
- - Keep a `manifest.json` listing section files in order. Merging is then mechanical and the order is not remembered from chat.
21
+ - One section per file — `section-01.docx`, `section-02.docx` — then `merge_docx_append.py`. This is the only sane way to draft in parallel or across sessions.
22
+ - Keep a `manifest.json` listing the section files in order, so merging is mechanical and the order is not remembered from chat.
23
23
  - Merge before numbering, not after.
24
24
 
25
25
  ## When a batch fails
26
26
 
27
- Do not patch a large edit inside a large document. Discard the failed batch's output, fix the batch, re-apply it to the previous good file. Keep the previous good file until the batch is verified.
27
+ Do not patch a large edit inside a large document. Discard the failed batch's output, fix the batch, re-apply it to the previous good file, and keep that good file until the batch is verified.
28
28
 
29
29
  ## Context hygiene
30
30
 
31
- - Long sessions lose the document design. Keep the plan and the design defaults (page size, font sizes, table borders) in a file in the workspace and re-read it, rather than trusting recollection.
32
- - When the user changes a decision mid-job (page size, heading style), record it at the point they said it, and apply it to batches already written.
31
+ - Long sessions lose the document design. Keep the plan and the design defaults (page size, font sizes, table borders) in a workspace file and re-read it instead of trusting recollection.
32
+ - When the user changes a decision mid-job (page size, heading style), record it where they said it and apply it to batches already written.
33
33
 
34
34
  ## Smoke test after the join
35
35
 
@@ -1,26 +1,30 @@
1
1
  ---
2
- description: Create, inspect, edit, redact, stamp, and render PDF files with the bundled Office helper CLI and self-contained Python scripts.
2
+ description: Create, extract, fill forms in, merge, split, redact, stamp, render, and verify PDF files with the bundled Office helper CLI and Python scripts.
3
3
  ---
4
4
 
5
5
  # PDF
6
6
 
7
- A PDF page is a content stream of text-showing and path-painting operators behind a page box. Every job in this pack either reads that stream (PyMuPDF, pdfplumber, pypdf), writes a new one (reportlab), or rasterises the result so you can look at it. Work from the mechanics outward: inspect first, choose the smallest operation that changes what the user asked for, then prove the change on a render. The helper CLI and the scripts are fast paths, not limits — each task file carries a runnable snippet for jobs they do not cover yet.
7
+ A PDF page is a content stream of text and path operators behind a page box. Every job here reads that stream (PyMuPDF, pdfplumber, pypdf), writes a new one (reportlab), or rasterises it to check the result. Inspect first, make the smallest change that satisfies the request, then prove it on a render.
8
8
 
9
- | User job | What actually happens | Fast path |
10
- | --- | --- | --- |
11
- | Look at a page, crop, or measure geometry | Rasterise the page box with PyMuPDF at a DPI, read page size, rotation, text, drawings, widgets | `scripts/render_pages.py`, `pdf render` |
12
- | Extract text or tables | pdfplumber reads characters and clusters table rules per page | `scripts/extract_text.py`, `pdf read` |
13
- | Inspect or fill an AcroForm | pypdf walks `/AcroForm` widgets, then rebuilds each `/AP` appearance from `/V` | `scripts/forms.py`, snippet in `tasks/forms.md` |
14
- | Remove a region (redaction) | PyMuPDF redaction annotations plus `apply_redactions` strip text and line art inside a rect | `scripts/redact_region.py`, snippet in `tasks/redact.md` |
15
- | Read or rewrite metadata | The document info dictionary; producer keys cannot change page content | `scripts/metadata.py` |
16
- | Merge, split, reorder, rotate, delete pages | Page-tree rearrangement; content is copied, not re-flowed | `scripts/page_ops.py`, `scripts/split_pages.py`, snippet in `tasks/edit-pages.md` |
17
- | Stamp a footer, header, or banner | reportlab draws an overlay page, pypdf merges it over the target pages | `scripts/stamp_overlay.py` |
18
- | Build a new document | reportlab draws primitives and text into a page | `pdf create`, `tasks/create.md` |
9
+ | User job | Fast path |
10
+ | --- | --- |
11
+ | Look at a page, crop, or measure geometry | `scripts/render_pages.py`, `pdf render` |
12
+ | Extract text, tables, or page images | `scripts/extract_text.py`, `pdf read`, `tasks/read.md` |
13
+ | Inspect or fill an AcroForm | `scripts/forms.py`, `tasks/forms.md` |
14
+ | Remove a region (redaction) | `scripts/redact_region.py`, `tasks/redact.md` |
15
+ | Read or rewrite metadata (info dictionary only, never page content) | `scripts/metadata.py`, `tasks/edit-pages.md` |
16
+ | Merge, split, reorder, rotate, delete pages | `scripts/page_ops.py`, `scripts/split_pages.py`, `tasks/edit-pages.md` |
17
+ | Stamp a footer, header, or label | `scripts/stamp_overlay.py`, `tasks/stamp.md` |
18
+ | Build a new document | `pdf create`, `tasks/create.md` |
19
+ | Resolve an `office-annotations` region | `tasks/annotations.md` |
20
+ | Decide what a PDF cannot do | `references/limits.md` |
21
+
22
+ Each task file explains the mechanism before giving the command. `references/geometry.md` covers page boxes and normalised regions, `references/layout-quality.md` is the pre-delivery visual checklist, and `references/form-fields.md` covers field flags, appearance streams, and flattening.
19
23
 
20
24
  ## Before any file work
21
25
 
22
26
  1. Call the `office_status` tool.
23
- 2. Read the `## Office runtime` JSON block at the end of this file and use its `python`, `cli`, `skill`, and `shared` values verbatim, quoted: `"<python>"`, `"<cli>"`, `"<skill>/scripts/<name>.py"`.
27
+ 2. Read the runtime block appended to this file at runtime; use its `python`, `cli`, `skill`, and `shared` values verbatim, quoted: `"<python>"`, `"<cli>"`, `"<skill>/scripts/<name>.py"`.
24
28
  3. If that block reports no working runtime, stop. Give the user the `node "..."` remedy command it names, then call `office_status` again. Never fabricate a PDF result you could not verify.
25
29
 
26
30
  Scripts under `"<skill>/scripts/"` are standalone argparse CLIs. Run `<python> <script> --help` before using one you have not called in this session.
@@ -29,49 +33,45 @@ Scripts under `"<skill>/scripts/"` are standalone argparse CLIs. Run `<python> <
29
33
 
30
34
  Decide the route before you type a command. Every route ends in the same render check.
31
35
 
32
- - **The file does not exist** → create it from a `pdf create` spec, or with reportlab when the layout does not fit the block model (`tasks/create.md`).
33
- - **The file exists and must change** → read it first (`pdf read`, and a render), then mutate it with the script named in the task file. The helper CLI has no edit verb: it creates, reads, renders, and reports status only; every change is a `scripts/*.py` call.
34
- - **A script covers the job** → run it, then verify its JSON report and its rendered pages. Scripts refuse to overwrite their input and exit non-zero on a failed self-check; treat both as hard stops.
36
+ - **No file yet** → create it from a `pdf create` spec, or with reportlab when the layout does not fit the block model (`tasks/create.md`).
37
+ - **Existing file to change** → read it first (`pdf read`, plus a render), then mutate it with the script its task file names. The CLI has no edit verb: it creates, reads, renders, and reports status only, so every change is a `scripts/*.py` call.
38
+ - **A script covers the job** → run it, then check its JSON report and its rendered pages. A refused overwrite or a failed self-check exits non-zero: that is a hard stop.
35
39
  - **A read/render verb covers it** → `pdf read`, `pdf render`, or `pdf status`, then verify in the task's own way. Never route a mutation through the CLI.
36
- - **Neither covers it** → run the runnable snippet in the matching `tasks/*.md` file, or adapt it. Do not invent a CLI verb, and do not tell the user the job is impossible because no verb exists.
37
- - **Choose the smallest operation** that satisfies the request: metadata change before rebuild, page move before re-creation, one-line patch before re-typing the page.
40
+ - **Neither covers it** → run or adapt the snippet in the matching `tasks/*.md` file. Do not invent a CLI verb, and do not call the job impossible because no verb exists.
41
+ - **Take the smallest operation**: metadata change before rebuild, page move before re-creation, one-line patch before re-typing the page.
38
42
 
39
43
  ## Failure handling
40
44
 
41
45
  | Symptom | Meaning | Next step |
42
46
  | --- | --- | --- |
43
- | `{"ok": false, ...}` and non-zero exit | Invalid argument, missing file, or a refused overwrite | Read the `error` field, fix what it names, re-run. Never strip a step to make it pass. |
44
- | `encrypted` in the error | The file needs a password | Ask the user for a decrypted copy; do not guess passwords or write one into a command. |
45
- | `the LibreOffice renderer is not available` | Media conversion for docx/pptx/xlsx is unavailable because the DSH LibreOffice kit was not found | PDF rendering still works (PyMuPDF); for a PDF this is harmless. For other formats report that appearance could not be checked. |
47
+ | `encrypted` in the error | The file needs a password | Ask the user for a decrypted copy. Do not guess passwords or write one into a command. |
46
48
  | `leftover_chars_in_rect` greater than 0 | The redaction rectangle missed text | Enlarge `--pad` or reconvert the region; re-render before delivering. |
47
- | `not_applied` is not empty | A form value did not persist after save | Treat as a failure: check the field name, its allowed states, and whether the widget has an appearance stream. |
48
- | `chars: 0` on a page that shows text | Scanned page, no text layer | Say so; follow `references/limits.md` for the OCR situation. Do not return an empty extraction as success. |
49
- | Render shows the change, `pdf read` does not | The edit landed in pixels only | Fix the content stream or note the limitation; a visible-only change usually means a cover-up, not an edit. |
49
+ | `not_applied` is not empty | A form value did not persist after save | Treat as a failure: check the field name, its allowed states, and the widget's appearance stream. |
50
+ | `chars: 0` on a page that shows text | Scanned page, no text layer | Say so; follow `references/limits.md`. Never return an empty extraction as success. |
51
+ | Render shows the change, `pdf read` does not | The edit landed in pixels only | Fix the content stream or note the limitation. A visible-only change is usually a cover-up, not an edit. |
52
+
53
+ General CLI failures, missing runtimes, and the `the LibreOffice renderer is not available` result are covered by `"<shared>/common-workflow.md"`; `pdf render` never needs LibreOffice.
50
54
 
51
55
  ## Quality rules
52
56
 
53
- - One heading scale, one body size, and one margin system per document (`references/layout-quality.md`).
54
- - Prove every structural change on a render of the changed page, not only on a text dump.
57
+ - One heading scale, one body size, one margin system per document (`references/layout-quality.md`).
58
+ - Prove every structural change on a render of the changed page, not on a text dump.
55
59
  - Keep page geometry consistent when merging or stamping: use each page's own box.
56
- - Do not claim redaction while the words are still extractable.
57
- - Preserve the user's original: write `<original>-edited.pdf`, never over it.
58
- - Mark deliberate shortcuts (a rebuild instead of a reflow, a stamped patch instead of a content edit) in the reply, so the user knows what kind of change they received.
59
- - Deliver with `present`, then one sentence about what changed and where the file is.
60
+ - Never claim redaction while the words are still extractable.
61
+ - Mark deliberate shortcuts (a rebuild instead of a reflow, a stamped patch instead of a content edit) in the reply.
60
62
 
61
63
  ## Delivery
62
64
 
63
- Always hand the finished file to the user with the `present` tool, then summarise in one sentence what changed. A bare path is not delivery: the chat transcript renders `.docx`/`.pptx`/`.xlsx`/`.pdf` links as "二进制文件,无法显示改动" and the user cannot open them.
65
+ Hand the finished file to the user with the `present` tool, then one sentence on what changed. A bare path is not delivery: the transcript renders `.docx`/`.pptx`/`.xlsx`/`.pdf` links as "二进制文件,无法显示改动", so the user cannot open them.
64
66
 
65
67
  - Creating a file: write it into the user's workspace, verify it, then `present`.
66
68
  - Editing an existing file: never overwrite the original. Write `<original>-edited.<ext>` (or a name the user asked for), verify it, `present` it, and state both paths.
67
69
 
68
70
  ## Annotations
69
71
 
70
- The user may reply with an `office-annotations` block: `[{ page, text, requirement, region: { left, top, width, height } }]`. `region` is normalised 0–1 against the rendered preview page the user drew on; `requirement` is free-form natural language. Treat it as a change request, not as editor state: locate what the region covers, apply the smallest edit that satisfies `requirement`, re-render, and confirm the region now looks right.
71
-
72
- When the payload cannot be mapped to a concrete object (the region is empty or the file has no geometry), do not guess silently — say what you could not locate.
72
+ An `office-annotations` reply is a change request, not editor state: `[{ page, text, requirement, region: { left, top, width, height } }]`. `region` is normalised 0–1 against the rendered preview page the user drew on; `requirement` is free-form. Locate what the region covers, apply the smallest edit that satisfies `requirement`, re-render, confirm the region looks right, and say what you could not locate rather than guessing.
73
73
 
74
- For PDF the `page` value is the 1-based page of the file itself, and `region × page.rect` is the real place to operate on: convert once with PyMuPDF and use the device rect directly. `text` is the words under the mark and wins over the region when they disagree; `imageName`, when present, is a screenshot of the marked area that you can open with `read_image`. Full resolution recipe: `tasks/annotations.md` and `"<shared>/annotation-recipes.md"`.
74
+ For PDF, `page` is the 1-based page of the file, and `region × page.rect` is the real rectangle: convert once with PyMuPDF and work in the device rect; `text` wins over the region when they disagree, and `imageName`, when present, is a screenshot of the marked area to open with `read_image`. Full recipe: `tasks/annotations.md`, payload shapes: `"<shared>/annotation-recipes.md"`.
75
75
 
76
76
  ## Verify
77
77
 
@@ -81,22 +81,7 @@ Render before you present, and again after every fix:
81
81
  "<python>" "<cli>" pdf render --input "<file.pdf>" --out-dir "<render-dir>" --dpi 110
82
82
  ```
83
83
 
84
- Open each PNG with `read_image`, and compare it with the same page rendered before the change. Look for text clipped at the page box, overlapping stamps or form fields, fonts replaced by boxes, rotated pages that now sit sideways, tables spilling past the margin, and scanned pages whose text is an image. Then run `"<python>" "<cli>" pdf read --input "<file.pdf>"` and confirm the text, page count, and form fields match what you edited. Two or three render/patch passes are normal; if a defect survives three, say what still looks wrong instead of calling it fixed.
85
-
86
- ## Tasks
87
-
88
- | Task | Read |
89
- | --- | --- |
90
- | Create a PDF from a spec or reportlab | `tasks/create.md` |
91
- | Extract text, tables, or page images | `tasks/read.md` |
92
- | Inspect, fill, or flatten an AcroForm | `tasks/forms.md` |
93
- | Remove a region of a page | `tasks/redact.md` |
94
- | Merge, split, reorder, rotate, delete pages | `tasks/edit-pages.md` |
95
- | Stamp a footer, header, or label | `tasks/stamp.md` |
96
- | Resolve an `office-annotations` region | `tasks/annotations.md` |
97
- | Decide what a PDF cannot do | `references/limits.md` |
98
-
99
- `references/geometry.md` explains page boxes and normalised regions, `references/layout-quality.md` is the pre-delivery visual checklist, `references/form-fields.md` covers field flags, appearance streams, and flattening.
84
+ Open each PNG with `read_image` and compare it with the pre-change render, working through `references/layout-quality.md`. Then run `"<python>" "<cli>" pdf read --input "<file.pdf>"` and confirm the text, page count, and form fields match your edit. Two or three passes are normal; past three, say what still looks wrong instead of calling it fixed.
100
85
 
101
86
  ## Shared workflow
102
87