Skip to content

Add plugin document-skills - #12

Closed
Hylouis233 wants to merge 34 commits into
hetaoBackend:mainfrom
Hylouis233:plugin/document-skills
Closed

Add plugin document-skills#12
Hylouis233 wants to merge 34 commits into
hetaoBackend:mainfrom
Hylouis233:plugin/document-skills

Conversation

@Hylouis233

Copy link
Copy Markdown
Contributor

What changes

New Plugin Hylouis233/document-skills - a Skill-only portable Agent Plugin bundling four original
workbench Skills: docx, pdf, pptx, xlsx.

All guidance in this Plugin was written originally for this contribution. It contains no content
from Anthropic's document skills or any other proprietary skill pack - the docx/pdf/pptx/xlsx
names describe the formats, and the patterns are built around standard open-source Python
libraries (python-docx, python-pptx, openpyxl, pypdf, ReportLab, PyMuPDF) and public format
knowledge.

User value

Users ask agents for document deliverables and get silent corruption: hand-edited ZIP XML that
no viewer opens, pasted values where spreadsheet formulas belong, screenshot-style PDFs with no
extractable text, decks whose text overflows every slide. The four Skills route each task to the
right tool, enforce the container contracts, and - critically - require re-opening and verifying
the artifact before handing it back.

Example prompt:

Use the document-skills plugin: open sales-2024.xlsx, add a sheet "Summary" with per-region
totals computed by formula, a bar chart of the top 5 products, and currency formatting.

Expected result: the agent loads the workbook with openpyxl, inspects sheets and headers, writes
real SUMIF/COUNTIF formulas, adds a native BarChart, applies number formats, and reports
the changed ranges plus the verification it ran.

Use the document-skills plugin: create a PDF one-pager "Q3 launch checklist" that fits exactly
one A4 page.

Expected result: generated with ReportLab flowables, measured, and verified with pypdf to be
exactly 1 page with extractable text.

Skill inventory (4 Skills, 13 reference files, 1333 lines):

  • docx - create (python-docx outline + styles + TOC field), edit (two-tier: structural edits
    vs. safe OOXML surgery), read (pandoc / python-docx), review (symptom-driven repair table),
    mandatory postcheck with optional soffice PDF smoke test.
  • pdf - one-tool-per-job routing: ReportLab for creation, PyMuPDF for extraction/inspection,
    pypdf for split/merge/rotate/watermark/encrypt/forms; text-first rule; page-geometry and
    overflow checks in postcheck.
  • pptx - seven workhorse slide patterns, narrow in-place editing with asserted shape matching,
    real chart parts over chart pictures, text-fit rules, slide-count and render verification.
  • xlsx - formulas-are-formulas contract, data_only caveats, typed values with explicit
    number formats, native charts bound to ranges, structural-edit formula audit, CSV/TSV route
    with a messy-data cleanup contract.

Plugin submission checklist

  • Plugin lives at plugins/<github-owner>/<plugin-name>.
  • plugin.json name matches the Plugin directory.
  • README.md includes a real example prompt and expected result.
  • LICENSE and plugin.json declare an open-source license (Apache-2.0).
  • Required executables, accounts, paid services, and supported platforms are disclosed
    (Python 3.9+ and the six libraries; optional LibreOffice/pandoc; Windows/macOS/Linux).
  • Network destinations and data handled by the plugin are disclosed (none - fully local).
  • No credentials, private endpoints, hidden telemetry, installers, symlinks, or native
    binaries are included.
  • Every scaffold TODO has been replaced.
  • Repository validation and portable contract tests pass (see evidence).

Network and data behavior

No network access, no credentials, no bundled executables. The Skills instruct the agent to use
the user's own installed Python libraries and operate only on files the user points at; temporary
artifacts go to the system temp directory. soffice/pandoc render checks are optional and
degrade gracefully when absent.

Evidence

npm run validate
OK   example hello-mcode
OK   example hello-mcode-mcp
OK   plugin Hylouis233/document-skills
Validated 1 hosted Plugin and all examples.

node --test
# tests 12
# pass 11
# fail 1

The one failing test (contributor can scaffold a hosted Skill plugin with one command,
test/hosted-plugins.test.mjs) is a pre-existing failure on unmodified main: the test asserts
a POSIX path separator while path.relative returns plugins\alice\hello-world on Windows.
Verified on a pristine shallow clone of this repository's main - same single failure, all
other 11 tests pass. No repository file outside plugins/Hylouis233/document-skills is touched
by this PR.

Manual evidence - the documented patterns were executed end to end on Windows (Python 3.13,
python-docx 1.2.0, python-pptx 1.0.2, openpyxl 3.1.5, pypdf 6.9.2, reportlab 4.4.10,
pymupdf 1.27.2, LibreOffice present):

XLSX ok | sheets: ['Sales'] | dims: A1:E5 | formulas: 4 [('E2','=C2*D2') ...] + native BarChart
DOCX ok | paragraphs: 3 | tables: 1 | table rows: 2 | PAGE field footer
PPTX ok | slides: 3 | title/table/chart slides rendered
PDF  ok | pages: 1 | mediabox: 595.3 x 841.9 | title text extractable
soffice report.docx -> exit 0 | pdf made: True
soffice deck.pptx    -> exit 0 | pdf made: True   (deck.pdf pages: 3 = slide count)

Every Python snippet embedded in the Skills was also parsed with ast.parse - zero syntax
errors. Staged files verified UTF-8 without BOM, LF line endings.

Four original workbench Skills (docx, pdf, pptx, xlsx) for creating, reading,
editing, and verifying Office and PDF documents with standard open-source
Python tooling.

- lives at plugins/Hylouis233/document-skills
- plugin.json + README + Apache-2.0 LICENSE + four Skills with references
- original guidance written for this plugin; no proprietary content reused
- no network, no credentials, no MCP server; scripts referenced are the
  user's own installed Python libraries (python-docx, python-pptx, openpyxl,
  pypdf, reportlab, pymupdf), not bundled
- npm run validate passes; documented patterns smoke-tested end to end
Copilot AI lite review requested due to automatic review settings August 14, 2026 19:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 26c09763c0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/extract.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/edit.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/edit.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/inspect.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/edit.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/SKILL.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pptx/references/edit.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pptx/references/create.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/SKILL.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/inspect.md Outdated

@hetaoBackend hetaoBackend left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

暂不合并。当前固定 commit 26c0976 仍有 17 个未解决且未 outdated 的 review threads,其中多项会让文档操作静默漏数据、生成损坏文件或让强制 postcheck 自身失败(PDF 只处理最后一页/未写出 watermark、DOCX 跨 run 替换与 OOXML repack、XLSX range styling/跨 sheet formula audit/固定 Summary sheet、PPTX 模糊匹配与格式保留等)。

请逐项修复或以可运行证据说明不成立,并 resolve 对应 thread;然后更新 end-to-end evidence。仓库 validator 与 ast.parse 只能证明包形状/语法正确,不能替代这些行为正确性检查。修复后我会对新 head 重新执行 npm run check 和文档片段验证。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2907c51b66

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/transform.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/extract.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pptx/references/edit.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/csv.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pptx/references/analyze.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pptx/references/analyze.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/create.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/SKILL.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/edit.md

@hetaoBackend hetaoBackend left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Hylouis233 最新 head 的 CI/CodeQL 已通过,且旧 review 中多数问题已修复;但当前仍有会让用户照抄失败或损坏文档的代码:

  1. PDF AcroForm 路径没有先把 reader pages 与 /AcroForm clone/append 到 writer,还把 reader page 传给 writer update;会报错或生成不完整文件。
  2. CMYK Pixmap 直接保存 PNG 会失败并中断抽取。
  3. PPTX table cell 的 .text = ... 会重建 text frame,丢 styled runs/hyperlink。
  4. CSV Sniffer 的返回 dialect 没传给 DictReader,分号 CSV 仍按逗号解析。
  5. PDF 尺寸验证用四坐标 mediabox tuple 对比 width/height 二元尺寸,无法正确验证 A4/Letter。
  6. grouped shape、继承字体、编号重启、openpyxl unsupported extensions 仍缺正确处理。

请先修复当前有效 thread,并为每种格式最危险的 1–2 个 snippet 增加最小可运行 fixture 测试。绿灯目前只能证明包结构。

Review fixes (all fixture-verified in tests/):
- PDF: AcroForm route now clones pages + /AcroForm into the writer via
  append() and fills fields on writer pages; mediabox postcheck reduced to
  width/height pairs; CMYK/ICC pixmaps converted to RGB before PNG save
- PPTX: table cells edited at run level (cell.text rebuild proven lossy);
  inventory walker recurses into group shapes; font triage resolves
  inherited fonts with theme major/minor fallback
- XLSX: sniffed CSV dialect passed to DictReader; round_trip_losses()
  detects parts an openpyxl round trip drops before saving
- DOCX: numbering restart via cloned <w:num> + startOverride, rendered
  proof in fixture (restarts at 1 vs style-reuse continuing at 4)

Depth references (original content, no proprietary material):
- docx cjk.md: east-asian font slot, 字号 table, char-based indent,
  fixed line spacing, GB/T 9704 page geometry, font availability risks
- docx scenes.md: academic paper / resume / official document / contract
  skeletons with scene-specific verification
- xlsx formatting.md: conditional formatting rules, structured tables,
  honest pivot aggregation routes (openpyxl cannot create pivots)

tests/: one runnable fixture per format, 30 assertions, all passing
locally (pdf 9, pptx 10, xlsx 6, docx 5 incl. soffice-rendered proof)
@Hylouis233

Copy link
Copy Markdown
Contributor Author

Round-2 review feedback is addressed in f803513 (18 files, +954). Every item was fixed and covered by a runnable fixture, per the request for minimal tests of the most dangerous snippets per format:

Fixes

  1. PDF AcroForm: writer.append(reader) clones pages + /AcroForm before update_page_form_field_values, fields filled on writer pages.
  2. CMYK pixmaps converted via fitz.Pixmap(fitz.csRGB, pix) before PNG save.
  3. PPTX table cells edited at run level (fixture includes a negative control proving cell.text drops formatting).
  4. CSV: sniffed dialect is passed to DictReader (negative control shows the default reader merging semicolon headers).
  5. Postcheck mediabox reduced to (width, height) pairs from .width/.height.
  6. Grouped-shape walker in the inventory; inherited-font triage with theme major/minor fallback; numbering restart via cloned <w:num> + startOverride (rendered proof: restart at 1. vs style-reuse continuing at 4.); round_trip_losses() detects parts an openpyxl round trip would drop.

Fixturestests/, one self-contained script per format, 30 assertions total, all passing locally on Windows (pdf 9, pptx 10, xlsx 6, docx 5 incl. a LibreOffice-rendered numbering-restart proof): python tests/pdf_fixture.py etc. from a scratch directory.

Depth references added (original content): docx/references/cjk.md (east-asian font slot, 字号 table, char-based indent, GB/T 9704 geometry), docx/references/scenes.md (paper/resume/official-document/contract skeletons), xlsx/references/formatting.md (conditional formatting, structured tables, pivot-honesty routes).

All round-2 threads have been replied to and resolved/outdated. npm run validate OK; embedded python blocks all ast.parse clean after dedent (33 blocks).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f803513495

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/edit.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/scenes.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/cjk.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pptx/references/edit.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/cjk.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/formatting.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/read.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/create.md
- xlsx edit: round_trip_changes() now also detects extensions stripped from
  retained parts (x14/extLst markers), not just dropped archive members
- docx scenes: signature blocks get row-level cantSplit + keep_with_next on
  all rows (rendered fixture proves the table stays on one page)
- docx cjk: tofu postcheck switched to glyph-coverage (fontTools cmap) since
  text extraction cannot detect missing-glyph boxes; style snippet now
  defines font names before use (NameError fix)
- pptx edit: locator candidate collection recurses into groups with a
  stable nested path for the uniqueness assertion
- xlsx formatting: aggregation formulas build sheet refs from ws.title
  (quoting when needed) instead of hard-coded Data!
- xlsx read: profiles every sheet by default, not just sheetnames[0]
- pdf create: escape() rule for plain text into Paragraph; fixture shows
  unescaped markup silently swallows <...> runs

Fixtures extended to 44 assertions, all passing locally

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f0900859c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/formatting.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/formatting.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pptx/references/analyze.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/SKILL.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/SKILL.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/review.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/csv.md Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 64049ebb7c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/edit.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pptx/references/analyze.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/extract.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pptx/SKILL.md
Comment thread plugins/Hylouis233/document-skills/skills/pdf/SKILL.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pptx/references/analyze.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/edit.md
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/formatting.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/extract.md Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b671a22e7c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/Hylouis233/document-skills/README.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/read.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/edit.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/review.md Outdated
Comment thread plugins/Hylouis233/document-skills/README.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/extract.md
Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/transform.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/read.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/csv.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/edit.md Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12d9542c43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/transform.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/cjk.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/inspect.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pptx/references/analyze.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/transform.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pptx/references/analyze.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/edit.md Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f2afc97a6e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/extract.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/SKILL.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/read.md Outdated
Comment thread plugins/Hylouis233/document-skills/README.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/SKILL.md
- pdf extract: real table detection via page.find_tables() with span fallback
- pdf postcheck: interactive-only pages (AcroForm widgets) exempt from text gate
- docx read: unified block walker yields tables inside w:sdtContent
- README: drop the untagged-ReportLab accessibility claim, state the limitation
- xlsx: fullCalcOnLoad contract so manual-calc workbooks recalculate on open
- fixtures: table detection, widget exemption, sdt table walker, calc flags (all green)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 746e663d1b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/Hylouis233/document-skills/skills/pptx/references/analyze.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/cjk.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/edit.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/formatting.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/read.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/transform.md Outdated
- pptx analyze: sparse XY/bubble cache points keep their idx so x/y/bubble
  values pair correctly across blank points
- docx cjk: Hangul (jamo, compatibility jamo, extended-A, syllables) routes
  through the eastAsia font slot
- xlsx edit: extension detection matches namespace URIs and the local name
  extLst instead of arbitrary XML prefixes
- xlsx formatting: header-only sheets skip conditional formatting instead of
  building inverted ranges openpyxl rejects
- docx read: table extraction walks real w:tc elements and annotates
  gridSpan/vMerge instead of the merge-expanded row.cells view
- pdf transform: stamps are scaled and centered per destination page via
  merge_transformed_page; rotated pages flagged for visual verification
- fixtures extended for all six (with negative controls)
@Hylouis233

Copy link
Copy Markdown
Contributor Author

Cross-audit round complete. This pass re-verified every open review thread against the code, closed the five remaining items from the last round in 746e663 (PDF table detection via find_tables, interactive-only page exemption in the text postcheck, a unified DOCX block walker that reaches tables inside w:sdtContent, an honest statement that ReportLab output is not tagged PDF/UA, and the fullCalcOnLoad recalculation contract) and the six items from today's round in b6b0fdb (sparse XY/bubble cache indices, Hangul eastAsia routing, namespace-URI extension markers, header-only conditional-formatting guard, merged-cell spans in DOCX extraction, per-page stamp transforms). Every fix ships with a runnable fixture including negative controls; all four fixture scripts pass locally and npm run validate is green. All review threads are answered and resolved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6b0fdb7ea

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/Hylouis233/document-skills/skills/pdf/SKILL.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/read.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pptx/references/analyze.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/references/review.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/docx/SKILL.md
Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/extract.md
Comment thread plugins/Hylouis233/document-skills/skills/xlsx/references/read.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pdf/references/extract.md Outdated
Comment thread plugins/Hylouis233/document-skills/skills/pptx/references/analyze.md Outdated
- pdf postcheck: real overflow check - text blocks extracted through an
  enlarged clip rectangle and compared to the page box (plain extraction
  silently drops fully off-page text)
- docx read: table cells rebuilt per paragraph with visible tab/break
  markers instead of concatenated w:t text
- docx/pptx health checks: explicit require() raises so every archive
  limit survives python -O
- pptx analyze: run fonts resolve a:latin/a:ea/a:cs by the run's scripts
  before falling back to the Latin-only font.name; triage walks table
  cells (graphic frames have no text frame)
- docx SKILL: pandoc-only installs can serve read/extract requests
- pdf extract: inline content-stream images enumerated via dict image
  blocks alongside the XObject loop; coordinate spans position-sorted
  before emission
- xlsx read: implausible sheet <dimension> reset before streaming
- fixtures extended for the overflow check (with negative control), cell
  paragraph boundaries, dimension corruption, script faces, table triage

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98af2fc093

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


# Insert/delete does not adjust formulas or non-cell dependencies. Fail closed until every
# reported reference that can intersect the shifted region has an explicit rewrite plan.
references_before = non_cell_references(wb)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Audit cell formulas before inserting rows

When the workbook contains an ordinary cell formula that references rows at or below row 5, this gate allows the insertion because non_cell_references() deliberately excludes cell formulas; insert_rows(5) then moves cells without translating their formulas, producing stale ranges and incorrect results. The later loop only prints the already-modified formulas and has no pre-edit snapshot with which to identify or repair them, so inventory intersecting cell formulas before allowing the structural edit.

Useful? React with 👍 / 👎.

value_ws.reset_dimensions()
formula_ws.reset_dimensions()
print(f"--- {sheet_name} --- implausible dimension {declared!r}; reset, real extent:")
print(f"--- {sheet_name} --- dims:", value_ws.calculate_dimension())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Force dimension calculation after resetting bounds

When the revised bad-dimension branch runs, reset_dimensions() clears a read-only worksheet's cached max_row and max_column, so the immediate calculate_dimension() call raises ValueError: Worksheet is unsized, use calculate_dimension(force=True) instead of profiling the recovered data. Fresh evidence after the earlier dimension fix is this new unforced call on line 28; use calculate_dimension(force=True) or iterate first and report the discovered extent.

Useful? React with 👍 / 👎.

Comment on lines +264 to +268
if any(tag in ("Hans", "Hant", "Jpan", "Hang") for tag in tags) and "eastAsia" in declared:
return declared["eastAsia"]
if any(tag in ("Arab", "Hebr", "Deva") for tag in tags) and "complexScript" in declared:
return declared["complexScript"]
return declared.get("latin")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Report all faces used by mixed-script runs

When one run contains both Latin and CJK or complex-script text and declares separate faces, the new script-first branch returns only the East Asian or complex-script face and omits the Latin face that renders the other characters. Fresh evidence in the revised resolver is that it still returns one scalar face, so common text such as Q3 测试 yields only the CJK font and the promised effective-font inventory remains incomplete; return the distinct applicable faces per script instead.

Useful? React with 👍 / 👎.

# or a:cs must be resolved per the scripts present in its own text first.
def explicit_run_face(run, text):
"""The run's direct a:latin/a:ea/a:cs typeface chosen by its scripts (or None)."""
rPr = run._r.find(qn("a:rPr"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Import the helpers used by the font-triage snippet

When the font-triage code block is run independently, as the separate fenced example suggests, it fails on the first run with NameError: name 'qn' is not defined; it later also calls iter_shapes, which is defined only in the earlier content-inventory block. Import qn and include the recursive shape walker in this block so the advertised triage route can execute without first running an unrelated extraction snippet in the same interpreter.

Useful? React with 👍 / 👎.

Comment on lines +40 to +41
[[cell.text for cell in row.cells] for row in sh.table.rows]
for sh in shapes if sh.has_table

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve merged-cell spans in PPTX table inventories

When a slide table contains merged cells, this rectangular cell.text matrix does not record which cell is the merge origin or how many rows and columns it spans; python-pptx exposes continuation cells separately, so a multi-column heading is emitted as one populated cell plus indistinguishable empty cells. That loses table structure during the advertised extraction and can cause summaries or repurposed tables to assign headings to the wrong columns; include is_merge_origin, is_spanned, span_width, and span_height metadata.

Useful? React with 👍 / 👎.

@hetaoBackend hetaoBackend left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已复核当前 head 5b89545,暂不能合入。当前 head 仍有多项会导致文档内容遗漏、误改或验证失真的未解决问题,包括 DOCX 超链接/altChunk/表格网格处理、PPTX SmartArt 清点、PDF 旋转坐标与字体嵌入判断、XLSX 公式校验和 drawing anchor 迁移等。另一个门禁问题是仓库 npm check 并未执行本 PR 新增的 4 个 Python fixture;本地仅 PPTX fixture 可直接运行并通过,DOCX/PDF/XLSX 因依赖未安装无法形成独立验证。请先修复当前 review findings,并把四类 fixture 接入可重复的 CI 检查,再请求复核。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 88a3723709

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

temporary_path = output_path.with_suffix(output_path.suffix + ".tmp")
with temporary_path.open("w", newline="", encoding="utf-8") as output:
writer = csv.writer(output)
for formula_row, value_row in zip(formula_ws.iter_rows(), value_ws.iter_rows()):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset dimensions before streaming XLSX rows

When converting a workbook from a non-Excel producer whose <dimension> metadata is plausible but truncated, both read-only worksheets retain that declared bound, so iter_rows() silently stops before later populated cells. Because both streams truncate identically, zip() detects no mismatch and the resulting CSV is accepted despite missing rows or columns; call reset_dimensions() on both worksheets before streaming, as the workbook-read route does.

Useful? React with 👍 / 👎.

Comment on lines +118 to +119
if widget.get("/T") is not None:
return str(widget["/T"])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve fully qualified AcroForm field names

When a form uses a hierarchical field such as application.applicant_name, each /T contains only one partial name, but this function returns the first leaf it encounters instead of joining the parent chain. A caller using the fully qualified name exposed by get_fields() therefore finds no target page and raises KeyError even though the field is fillable; construct the qualified name from all ancestor /T values before comparing.

Useful? React with 👍 / 👎.

Comment on lines +225 to +226
assert not unresolved, f"font files not resolved per run: {unresolved}"
assert not missing, f"glyph missing from the run's effective font: {missing}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep CJK glyph checks active under optimized Python

When this mandatory glyph audit runs with python -O or PYTHONOPTIMIZE, both assertions are removed, so unresolved font files and missing glyphs are silently accepted and a document containing tofu can pass the documented postcheck. Use explicit conditionals that raise exceptions so the audit remains active in optimized environments.

Useful? React with 👍 / 👎.

Comment on lines +60 to +62
if r.is_encrypted:
if not password:
raise RuntimeError("set PDF_PASSWORD so the encrypted output can be postchecked")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow blank-user-password encrypted PDFs through postcheck

When an output PDF is permission-encrypted with an empty user password, as is common for files that open without a password but restrict editing or printing, is_encrypted remains true and this branch rejects it before attempting empty-password authentication. Try the empty password first and require PDF_PASSWORD only if that authentication fails, otherwise a valid, normally viewable output can never pass the mandatory postcheck.

Useful? React with 👍 / 👎.

for formula_cell, value_cell in zip(formula_row, value_row):
if formula_cell.data_type == "f" and value_cell.value is None:
missing_caches.append(formula_cell.coordinate)
writer.writerow([cell.value for cell in value_row])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Neutralize formula-like cells in CSV exports

When a worksheet contains literal text beginning with =, +, -, or @, this export writes the prefix unchanged, so opening the resulting CSV in Excel or LibreOffice interprets the cell as a formula rather than the original data and can execute attacker-controlled spreadsheet expressions. Apply CSV-injection neutralization for spreadsheet-targeted exports, or explicitly require a raw-data mode before preserving those prefixes.

Useful? React with 👍 / 👎.


red_fill = PatternFill(start_color="FFC7CE", end_color="FFC7CE", fill_type="solid")
red_font = Font(color="9C0006")
last = ws.max_row # real data boundary, not the column

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Derive conditional-format bounds from populated rows

When an existing worksheet has a stray styled or previously cleared cell far below its real data, ws.max_row reflects that cell rather than the populated boundary. The snippet can consequently attach several conditional-formatting rules to nearly a million blank rows, bloating the workbook and slowing spreadsheet viewers; scan backward for the last row containing actual data or use the source table's declared range before constructing these rules.

Useful? React with 👍 / 👎.

Comment on lines +39 to +40
infos = z.infolist()
names = {info.filename for info in infos}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound the number of DOCX archive members

When an untrusted DOCX contains hundreds of thousands of distinct zero-byte members, every current size and compression-ratio limit still passes because the total uncompressed size remains tiny. Materializing the complete central directory and iterating every member can then consume unbounded memory and CPU despite this being presented as a bounded health check; impose a compressed-file-size and member-count limit before processing the entries.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: beade83f43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

ph for ph in slide.placeholders
if ph.placeholder_format.type in types
]
assert len(matches) == 1, f"expected one placeholder of {types}, found {len(matches)}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Raise explicitly when placeholder selection is ambiguous

When this creation route runs with python -O or PYTHONOPTIMIZE and a supplied layout contains multiple matching placeholders, Python removes this assertion and matches[0] silently selects an arbitrary placeholder; with no match it instead fails later with an unhelpful IndexError. Use an explicit conditional exception so template ambiguity cannot populate the wrong placeholder.

Useful? React with 👍 / 👎.

annotations = list(page.annots() or ())
links = page.get_links()
print(page.number, page.rect, "text_len:", len(page.get_text()),
"images:", len(page.get_images()), "drawings:", len(drawings),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Count inline images before declaring a page blank

When a PDF page contains only inline images, page.get_images() reports no image because those images live directly in the content stream rather than the page's XObject resources. The documented blank-page predicate can therefore classify a visibly populated page as blank; include type-1 image blocks from page.get_text("dict") or use an equivalent rendered-content check.

Useful? React with 👍 / 👎.

)

with zipfile.ZipFile("input.pptx") as archive:
infos = archive.infolist()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound PPTX archive size and member count before inspection

When an untrusted PPTX contains hundreds of thousands of distinct zero-byte members, the per-entry, total-uncompressed-size, and compression-ratio limits all pass while infolist(), the name set, and the subsequent loop consume unbounded memory and CPU. Add a compressed-file-size limit before opening the archive and a member-count limit before processing its entries so this remains a genuinely bounded health check.

Useful? React with 👍 / 👎.

# extent so valid high-y portrait text is not flagged on a rotated page.
page_box = fitz.Rect(0, 0, page.cropbox.width, page.cropbox.height)
clip = fitz.Rect(
page_box.x0 - 2000, page_box.y0 - 2000,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Inspect images and drawings for PDF overflow

When a generated page contains an image or vector drawing that extends beyond the page box, this mandatory gate examines only text blocks, so the clipped graphical content passes even though shared rule 4 requires all overflowing content to be detected. Include image and drawing placement bounds in the check, or require a rendered visual comparison that can detect clipped non-text content.

Useful? React with 👍 / 👎.

yield from walk(paragraph._p)

def paragraph_text(paragraph):
return "".join(run.text for run in iter_paragraph_runs(paragraph))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve legacy symbol runs during DOCX extraction

When visible content is encoded as a <w:sym> run, such as a Wingdings checkbox or legacy mathematical symbol, Run.text does not expose that element and this concatenation silently drops it from the extracted paragraph. Inspect w:sym children and resolve their font/character codes, or explicitly report them as unreadable so summaries do not omit visible symbols.

Useful? React with 👍 / 👎.

return sorted(
(name, label)
for name in common_names
for label in before.get(name, set()) - after.get(name, set())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compare individual worksheet extensions across round trips

When a retained worksheet contains multiple extension records and openpyxl strips only one while another keeps the same extLst, x14 namespace, or markup-compatibility marker present, this set subtraction reports no change. The guard can therefore authorize an overwrite that loses an unsupported feature; inventory extension entries by URI and relevant child content rather than comparing only three coarse marker labels.

Useful? React with 👍 / 👎.

start = OxmlElement("w:startOverride")
start.set(qn("w:val"), "1")
override.append(start)
clone.append(override)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Replace an existing level override when restarting lists

When the selected base numbering instance already has a <w:lvlOverride w:ilvl="0">, cloning it and appending another override for the same level leaves two competing level-zero definitions. Word or LibreOffice may honor the inherited override rather than the new startOverride=1, so the second list can continue at the wrong number; replace or update the cloned level-zero override instead of blindly appending another one.

Useful? React with 👍 / 👎.

s = add_slide(title_only_layout)
s.shapes.title.text = "Regional service health"
rows, cols = 4, 3
tbl_shape = s.shapes.add_table(rows, cols, Inches(0.5), Inches(1.5), Inches(9), Inches(3.5))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Populate or remove the empty table rows

When this skeleton is run as written, it creates a four-row table but fills only the header, leaving nine body cells empty. The mandatory PPTX postcheck then reports those cell text frames as unexpected empties, so the documented creation route cannot pass its own verification; populate the three data rows or create only the header row until data is supplied.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 032a97a94c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +125 to +128
if overflow_doc.needs_pass:
if not password:
raise RuntimeError("set PDF_PASSWORD so the encrypted output can be overflow-checked")
overflow_doc.authenticate(password)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Authenticate the empty password in the overflow pass

For a permission-encrypted PDF with an empty user password, the earlier r.decrypt("") succeeds, but this independently opened PyMuPDF document still reports needs_pass; when PDF_PASSWORD is unset, this branch therefore rejects a valid PDF before checking overflow. Fresh evidence after the earlier blank-password repair is that only the pypdf reader was authenticated; authenticate overflow_doc with "" first, requiring the environment password only if that fails.

Useful? React with 👍 / 👎.

Comment on lines +69 to +74
for b in page.get_text("dict")["blocks"]:
if b["type"] != 1:
continue
ext = b.get("ext") or "png"
with open(f"img-p{page_number}-inline-{b['number']}.{ext}", "wb") as fh:
fh.write(b["image"])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Filter XObjects out of the inline-image pass

When a page contains an ordinary image XObject, page.get_text("dict") includes a type-1 block for that image as well as for inline images, so this loop exports it again under an inline filename after the get_images() loop already wrote it. This produces duplicate, misleading extraction results for normal image-bearing PDFs; restrict this pass to blocks whose image xref identifies inline content.

Useful? React with 👍 / 👎.

Comment on lines +63 to +68
rows = value_ws.iter_rows(values_only=True)
header = next(rows, None)
print("header:", header)
for i, row in enumerate(rows):
if i >= 5: break
print(row)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Start sampling at the discovered first row

When a worksheet's first populated row is below row 6, iter_rows() still begins at row 1 after dimensions are reset, so header and all five sample rows printed here are blank even though discover_dimension() already found the actual lower bound. The required first-pass profile consequently omits the sheet's real header and data; retain the discovered minimum row or skip leading empty rows before taking the header and samples.

Useful? React with 👍 / 👎.

Comment on lines +89 to +90
if formula_cell.data_type == "f" and value_cell.value is None:
missing_caches.append(formula_cell.coordinate)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Distinguish blank formula results from missing caches

When a valid formula such as =IF(A1="","",A1) has a cached empty-string result, openpyxl's data_only=True view also exposes that result as None. This predicate therefore treats a legitimate displayed blank as an absent cache and deletes the temporary CSV instead of exporting it; inspect the underlying cell XML/cache presence or otherwise distinguish cached blanks before rejecting the conversion.

Useful? React with 👍 / 👎.

Comment on lines +66 to +67
if x_source is None: # category/value chart
return {"name": series.name, "values": list(series.values)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle category series without numeric caches

When a category/value chart references workbook or external data but omits the optional <c:numCache>, accessing series.values through python-pptx dereferences the missing cache and aborts the entire slide inventory. The XY branch already reports unavailable caches explicitly; apply the same guarded extraction to category series so a valid uncached chart is reported as unavailable rather than preventing the rest of the deck from being analyzed.

Useful? React with 👍 / 👎.

if not condition:
raise ValueError(message)

page_count = len(r.pages)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce the requested PDF page count

When a task requests an exact page count, such as a one-page handout, this variable is never compared with an expected value, so an output with extra or missing pages can pass every mandatory check as long as the remaining pages contain text and use the expected geometry. Add an expected_page_count input and fail explicitly when len(r.pages) differs from it.

Useful? React with 👍 / 👎.

Comment on lines +45 to +46
print(page.number, page.rect, "text_len:", len(page.get_text()),
"images:", len(page.get_images()), "drawings:", len(drawings),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compare unrotated boxes when checking PDF page sizes

When identically sized pages use different /Rotate values, page.rect swaps width and height for the rotated pages, so the instructed consistency check reports mixed paper sizes even though every page has the same media geometry. This is common in scanned or intentionally mixed-orientation documents and can produce a false duplex-printing warning; compare unrotated media or crop boxes and report rotation separately.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4f50cecf3d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +8 to +11
if doc.needs_pass:
password = os.environ.get("PDF_PASSWORD")
if not password or not doc.authenticate(password):
raise RuntimeError("set PDF_PASSWORD to the correct password before extracting")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Authenticate blank passwords before rejecting extraction

When a permission-encrypted PDF uses an empty user password and PDF_PASSWORD is unset, this branch raises without attempting doc.authenticate(""), even though the file opens normally. The repaired postcheck and inspection paths already try the empty password first; apply the same sequence here so extraction supports these PDFs.

Useful? React with 👍 / 👎.

Comment on lines +198 to +200
check = PdfReader("filled.pdf")
value = str((check.get_fields() or {}).get("applicant_name", {}).get("/V", ""))
if value.strip("/") != "Ada Byron":

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reuse the selected field name in the round-trip check

When field_name is changed to any other simple or qualified key, the update can succeed but this verification still looks up the literal applicant_name, producing a false failure. The parent-chain matcher now supports qualified fields, but the final lookup must use field_name as well.

Useful? React with 👍 / 👎.

Comment on lines +31 to +34
if row_index is None or column_index is None:
continue
min_row = row_index if min_row is None else min(min_row, row_index)
min_column = column_index if min_column is None else min(min_column, column_index)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore empty styled cells when finding the first data row

When a read-only worksheet contains a styled-but-empty cell above its actual data, that cell still has row and column coordinates, so this code marks its row as populated even though its value is None. The new min_row=first_populated_row iterator then starts at that blank row and can still print a blank header and samples; only update the discovered bounds for cells containing real values or formulas.

Useful? React with 👍 / 👎.

Comment on lines +77 to +80
for formula_row, value_row in zip(formula_ws.iter_rows(), value_ws.iter_rows()):
for formula_cell, value_cell in zip(formula_row, value_row):
if formula_cell.data_type == "f" and value_cell.value is None:
missing_cache_count += 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Distinguish cached blank results in workbook profiling

When a formula has a valid cached empty-string result, the data_only=True cell is also None, so this profiler reports it as having no cached value. The CSV route now inspects worksheet XML to distinguish cached blanks from missing caches, but this independent read route still uses the ambiguous value alone and should apply the same cache-presence check.

Useful? React with 👍 / 👎.

Comment on lines +140 to +142
categories = [] if has_xy_values else [
[str(level) for level in label] for label in plot.categories.flattened_labels
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard category caches before flattening chart labels

When a category/value chart retains a worksheet formula but omits the optional category strCache or numCache, dereferencing plot.categories.flattened_labels can abort the entire slide inventory. The value-cache guard above now handles uncached series values, but category labels remain unguarded; report their cache as unavailable instead of preventing the rest of the deck from being analyzed.

Useful? React with 👍 / 👎.

Comment on lines +418 to +422
for shape in shapes:
if shape.has_text_frame:
yield shape.text_frame
if getattr(shape, "has_table", False):
for row in shape.table.rows:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Audit fonts used by charts and SmartArt

When a deck's custom font appears only in a chart title, chart label, or SmartArt node, this walker emits no candidate for it because chart and diagram graphic frames have neither an ordinary text frame nor a table. The triage can consequently report that it inspected effective fonts while missing visible text that may substitute on another machine; traverse chart text properties and SmartArt text runs as well, or explicitly report them as unresolved.

Useful? React with 👍 / 👎.

Comment on lines +70 to +72
mixing. Note: ReportLab output is not tagged PDF/UA — when the user needs an accessible
(screen-reader-ready) PDF, the pdf Skill says to report that limitation honestly instead of
claiming accessibility.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Put the PDF/UA limitation in the loaded PDF skill

When a user requests a screen-reader-ready PDF, the actual skills/pdf/SKILL.md contains no accessibility or tagging guard, despite this README claiming that the skill instructs the agent to report ReportLab's PDF/UA limitation. Because the runtime skill can therefore proceed with ordinary untagged ReportLab output without warning, add the limitation and refusal/fallback behavior to the PDF skill itself.

Useful? React with 👍 / 👎.

Comment on lines +140 to +142
files = sorted(
(path for path in src.rglob("*") if path.is_file() and path != content_types),
key=lambda path: path.relative_to(src).as_posix(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Repack DOCX files from a fresh extraction directory

When work/ already contains files from a previous document, extracting another DOCX there overwrites matching members but does not remove members absent from the new archive, and this rglob then repacks those stale files. The output can silently contain undeclared parts or confidential media from the earlier document; create a new empty temporary directory for every extraction or refuse to proceed when the destination is nonempty.

Useful? React with 👍 / 👎.

Comment on lines +130 to +134
def face_from_rpr(rpr, slot):
if rpr is None:
return None
rfonts = rpr.find(qn("w:rFonts"))
return None if rfonts is None else rfonts.get(qn("w:" + slot))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve direct theme font attributes before inherited faces

When a run declares w:asciiTheme, w:hAnsiTheme, or w:eastAsiaTheme instead of a literal face, this helper treats the direct formatting as absent and can fall through to a style-level font that Word does not actually use. The glyph audit may therefore pass against the wrong cmap; resolve the corresponding theme face or fail closed whenever a direct theme attribute is present.

Useful? React with 👍 / 👎.

Comment on lines +48 to +51
with open("output.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
rows = [["Region", "Units", "Note"], ["EU", 120, "=2+2"]]
writer.writerows([spreadsheet_csv_field(value) for value in row] for row in rows)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Select the requested delimiter when writing TSV files

When the requested output is .tsv, following this writing route with only the filename changed still uses csv.writer's comma delimiter, producing comma-separated content under a TSV extension. Since the skill advertises TSV support and routes it here, derive the delimiter from the requested format or pass delimiter="\t" explicitly for TSV output.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 943c278e7b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

print(f"{ws.title} dims:", ws.dimensions)
formulas = [
(c.coordinate, formula_text(c.value))
for row in ws.iter_rows() for c in row if c.data_type == "f"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound formula postcheck to populated cells

When an edited workbook retains a styled or previously cleared cell near the worksheet limits, this mandatory iter_rows() traverses the entire rectangle through max_row and max_column; for example, a sparse cell at XFD1048576 makes openpyxl attempt billions of cell visits and can exhaust memory before verification completes. Iterate the worksheet's populated cells or otherwise establish bounded real-data dimensions before collecting formulas.

Useful? React with 👍 / 👎.

Comment on lines +82 to +84
if cached_numeric_points(value_source) is None:
return {"name": series.name, "values": None, "cache_status": "unavailable"}
return {"name": series.name, "values": list(series.values)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard uncached chart series names

When a chart series title references a worksheet cell but omits the optional string cache, accessing series.name can dereference that missing cache and abort the entire deck inventory. Every return path in series_content() reads the name, including the branches intended to tolerate unavailable value caches, so resolve the series title defensively and report it as unavailable when its cache is absent.

Useful? React with 👍 / 👎.

Comment on lines +101 to +103
if number not in intentionally_raster_only_pages
and not text
and widget_count(r.pages[number - 1]) == 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore hidden widgets when exempting blank PDF pages

When an otherwise blank page contains only a hidden, invisible, or no-view AcroForm widget, widget_count() is nonzero and this condition exempts the page from the extractable-text requirement. Such metadata widgets are not usable visible page content, so an accidentally blank generated page can pass the mandatory postcheck; count only widgets whose annotation flags and appearance make them visible.

Useful? React with 👍 / 👎.

Comment on lines +141 to +142
with ZipFile(input_path) as archive:
archive.extractall(src)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate DOCX archives before extracting them

When Tier 2 is used on an untrusted or corrupted DOCX, this extractall() runs before any member-size, total-size, compression-ratio, or member-count checks, so a small archive bomb can exhaust disk space or CPU even though the separate review route contains a bounded health check. Run that bounded validation before extraction, or stream each member while enforcing equivalent limits.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6a148b2b0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +162 to +165
names = {info.filename for info in infos}
require(len(names) == len(infos), "duplicate archive member names are unsafe")
require("[Content_Types].xml" in names and "word/document.xml" in names,
"required DOCX package parts are missing")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject archive paths that collide after extraction

When an input contains both word/document.xml and a distinct name such as ../word/document.xml, this set considers them unique and the required-member check passes, but ZipFile.extractall() later normalizes both names to the same destination and silently lets the latter overwrite the canonical document part. Reject non-canonical member paths and collisions after path normalization before extraction so Tier 2 edits cannot operate on a different part than the one validated.

Useful? React with 👍 / 👎.

Comment on lines +58 to +61
return [
(int(point.get("idx")), float(value.text))
for point in points
if point.get("idx") is not None and (value := point.find(qn("c:v"))) is not None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve chart error values while reading numeric caches

When a valid chart cache contains an Excel error value such as #N/A (commonly used to suppress a plotted point), float(value.text) raises ValueError and aborts the inventory before later charts or slides are analyzed. Parse each point defensively and preserve error/blank markers, rather than requiring every <c:v> payload in a numeric cache to be convertible to a Python float.

Useful? React with 👍 / 👎.

Comment on lines +64 to +66
def cached_category_labels(plot):
"""Return flattened labels, or None when a formula has no category cache."""
category_nodes = plot._element.xpath("./c:ser[1]/c:cat")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Inventory category labels for each chart series

When two series in the same category plot reference different category ranges, this hard-coded ser[1] lookup and plot.categories access record only the first series' labels while the output lists values for every series. The resulting inventory can pair later series with the wrong categories and corrupt summaries or repurposed data; extract and associate the category cache per series, or explicitly reject plots whose series categories differ.

Useful? React with 👍 / 👎.

Comment on lines +123 to +126
def chart_axis_titles(chart):
"""Return titles for axes the chart actually exposes (pie charts have none)."""
titles = {}
for label, attribute in (("category", "category_axis"), ("value", "value_axis")):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include secondary chart-axis titles

When a combination chart uses a secondary category or value axis, the singular chart.category_axis and chart.value_axis properties expose only one axis of each type, so this inventory silently omits visible secondary-axis titles. That can remove essential units or scale context from a deck summary; traverse all axis elements and report their IDs, positions, and titles rather than returning only the primary pair.

Useful? React with 👍 / 👎.

Comment on lines +40 to +46
FORMULA_PREFIXES = ("=", "+", "-", "@")

def spreadsheet_csv_field(value, *, mode="safe"):
if mode not in {"safe", "raw"}:
raise ValueError("mode must be 'safe' or 'raw'")
if mode == "safe" and isinstance(value, str) and value.startswith(FORMULA_PREFIXES):
return "'" + value

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Neutralize control-prefixed spreadsheet formulas

When an untrusted string begins with a tab or carriage return followed by =, +, -, or @, the advertised safe mode leaves it unchanged even though spreadsheet applications can ignore those leading control characters and interpret the remainder as a formula. Include formula-triggering control prefixes in the check, or normalize and inspect leading control characters before serializing fields that are claimed to be safe to open.

Useful? React with 👍 / 👎.

Comment on lines +363 to +370
def character_tags(character):
codepoint = ord(character)
if 0x3040 <= codepoint <= 0x30FF:
return ["Jpan"]
if 0x1100 <= codepoint <= 0x11FF or 0xAC00 <= codepoint <= 0xD7AF:
return ["Hang"]
if 0x2F00 <= codepoint <= 0x9FFF:
return ["Hans", "Hant", "Jpan", "Hang"] # locale disambiguates Han

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Classify supplementary Han characters as East Asian

When slide text contains CJK Compatibility Ideographs or supplementary Han characters such as those in Extension B (U+20000 and above), character_tags() returns no East Asian tag because the Han range stops at U+9FFF. required_slots() consequently treats those glyphs as Latin and reports candidates from the wrong theme slot, so the font triage can miss the exact rare characters most likely to render as tofu; extend the Han ranges to cover compatibility and supplementary ideographs.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 88a48952a2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

labels = [node.text for node in root.iter(qn("a:t")) if node.text]
return {"name": shape.name, "status": "ok", "text": labels}

prs = Presentation("input.pptx")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate the PPTX package before opening it

When analyzing an untrusted or corrupted deck, Presentation("input.pptx") decompresses and parses package members before the bounded package check later in this document runs, so a ZIP bomb or oversized XML part can exhaust memory or CPU despite the advertised limits. Move the bounded validation ahead of this call and open the presentation only after it succeeds.

Useful? React with 👍 / 👎.

Comment on lines +339 to +342
chart_title = (
chart.chart_title.text_frame.text
if chart.has_title else ""
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse worksheet-backed chart titles from their cache

When a chart title uses the valid <c:tx><c:strRef> form instead of rich text, reading chart_title.text_frame invokes python-pptx's rich-text accessor rather than reading the cached referenced value, so the inventory can report an empty title and mutate the in-memory title choice. Parse literal/rich and strRef titles defensively, as chart_axis_text() already does for axis titles.

Useful? React with 👍 / 👎.

Comment on lines +48 to +50
for row in sheet.iter_rows():
for cell in row:
if cell.data_type == "f":

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound the structural formula scan to sparse cells

When an input workbook contains a styled or previously cleared cell near the worksheet limits, this iter_rows() traverses the full rectangle through max_row and max_column before an insertion can be audited; a cell at XFD1048576 can therefore make the edit route exhaust memory or hang. Iterate the worksheet's sparse populated-cell store or establish bounded real-data dimensions before scanning formulas.

Useful? React with 👍 / 👎.

Comment on lines +56 to +59
is_blank = not (
page.get_text().strip() or page.get_images() or image_blocks or drawings
or widgets or annotations or links
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Filter non-viewable annotations from blank-page inspection

When a page contains only a hidden, invisible, no-view, zero-area, or off-page widget/annotation, these truthy collections classify it as nonblank even though it has no visible or usable content. Fresh evidence beyond the earlier postcheck issue is that the postcheck now filters widget flags and rendered visibility, while this independent inspection route still counts every widget, annotation, and link; apply equivalent visibility filtering here.

Useful? React with 👍 / 👎.

Comment on lines +83 to +87
if node.tag == qn("w:t"):
pieces.append(node.text or "")
elif node.tag == qn("w:tab"):
pieces.append("<tab>")
elif node.tag in (qn("w:br"), qn("w:cr")):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve nonbreaking hyphens in table-cell extraction

When visible text in a table cell contains a <w:noBreakHyphen>, this cell-specific extractor handles only ordinary text, tabs, and breaks and silently drops the hyphen, potentially joining two words and corrupting extracted copy or summaries. Reuse run_text()/paragraph_text() for each cell paragraph or explicitly emit the nonbreaking-hyphen character here.

Useful? React with 👍 / 👎.

Comment on lines +197 to +200
clip = fitz.Rect(
page_box.x0 - 2000, page_box.y0 - 2000,
page_box.x1 + 2000, page_box.y1 + 2000,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove the finite search window from overflow detection

When content is positioned more than 2,000 points beyond a page edge—for example after a unit-conversion or absolute-positioning error—this finite clip excludes it from get_text("blocks"), so the mandatory overflow check can pass even though the content is completely off-page. Use PyMuPDF's unbounded/infinite clip or otherwise inspect all positioned text rather than imposing an arbitrary search margin.

Useful? React with 👍 / 👎.

Comment on lines +328 to +329
shapes = list(iter_shapes(slide.shapes)) # flattened; groups are common in template decks
text = [sh.text_frame.text for sh in shapes if sh.has_text_frame and sh.text_frame.text]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include inherited layout and master text in the inventory

When visible copy such as a legal disclaimer, confidentiality label, or persistent footer lives on the slide layout or master, walking only slide.shapes omits it because inherited shapes are not members of that collection. The resulting extraction and repurposed markdown can silently lose visible text; inventory applicable layout/master shapes as well, while avoiding placeholders overridden by the slide and honoring master-visibility settings.

Useful? React with 👍 / 👎.

Comment on lines +50 to +53
chart.title = "Revenue by product"
chart.y_axis.title = "Revenue"
data = Reference(ws, min_col=5, min_row=1, max_row=last) # includes header for series name
cats = Reference(ws, min_col=2, min_row=2, max_row=last)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Aggregate duplicate products before charting revenue

With the example data, Widget occurs in both the EU and US rows, but the chart binds directly to each transaction row while labeling itself “Revenue by product.” This produces two separate Widget bars rather than one product total and can misrepresent the requested comparison; aggregate revenue by product first or rename the chart to describe row-level sales.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review


P1 Badge Validate decks before the edit route parses them

When an edit is requested for an untrusted or corrupted deck, this call parses and decompresses the package before enforcing the archive/member/XML bounds defined in analyze.md, so a ZIP bomb or oversized XML part can exhaust resources before the agent reaches the edit. Fresh evidence beyond the earlier analysis-route finding is that analysis now uses open_validated_presentation(), while this independent edit route still opens the path directly; reuse that preflight here before constructing the Presentation.


page.get_text().strip() or page.get_images() or image_blocks or drawings
or widgets or annotations or links or interaction_visibility_unknown

P2 Badge Ignore unpainted image resources in blank-page checks

When a page retains an unused image XObject in its resource dictionary after editing, page.get_images() is nonempty even though no image is painted, so this predicate reports an otherwise blank page as nonblank. The type-1 image_blocks already detect displayed images; either rely on those or require each resource image to have a visible placement intersecting the page before using it as blank-page evidence.


shapes = list(iter_shapes(slide.shapes)) # flattened; groups are common in template decks
text = slide_text_content(slide)
tables = [
table_cells(sh.table)
for sh in shapes if sh.has_table

P2 Badge Exclude hidden shapes from non-text inventories

When a chart, table, picture, or SmartArt object has p:cNvPr hidden="1"—or is nested in a hidden group—this flattened list still feeds it into the non-text inventories even though the text walker correctly skips hidden shapes. The resulting summary can expose stale or confidential hidden data as visible slide content; propagate parent visibility through iter_shapes() and omit hidden shapes from every inventory.


x_cache = cached_numeric_points(x_source, include_count=True)
y_cache = cached_numeric_points(
getattr(series._element, "yVal", None), include_count=True
)

P2 Badge Apply the chart-point budget to XY series

When a valid scatter or bubble deck contains many series, each X/Y cache may contribute up to MAX_CHART_POINTS, but these calls neither charge the shared point_budget nor use the bounded fill path. Consequently the nominal 100,000-point deck limit can expand into millions of Python tuples and enormous output, potentially exhausting memory despite package validation; reserve each X, Y, and bubble cache's declared count from the shared budget before materializing its points.


try:
image = shape.image
except (AttributeError, ValueError):
return None

P2 Badge Report broken picture relationships instead of aborting

When a malformed deck contains a picture whose embed relationship is missing, shape.image raises KeyError, which is not caught here, so one broken picture aborts the entire inventory before later slides and the promised package triage can be reported. Catch missing-relationship errors and emit an explicit unreadable-picture record, as the SmartArt path already does.


if xref > 0:
try:
extracted = document.extract_font(xref)
embedded_bytes = len(extracted[3] or b"")
except (RuntimeError, ValueError):
embedded_bytes = 0
fonts.append({
"xref": xref,
"base_name": base_name,
"resource_name": resource_name,
"type": font_type,
"encoding": encoding,
"extension": extension,
"embedded": embedded_bytes > 0,
"embedded_bytes": embedded_bytes,

P2 Badge Treat Type3 glyph programs as embedded PDF content

When inspecting a PDF that uses a Type3 font, extract_font() can return no conventional font-file bytes because the glyph programs live in the PDF's /CharProcs streams, so this code labels the font as non-embedded even though it is self-contained and will not be substituted like a missing external face. Classify Type3 fonts separately or inspect their character procedures rather than deriving the embedded status solely from extracted font-file bytes.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 27b3edcc0a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return cached

input_path = "input.xlsx"
formula_wb = openpyxl.load_workbook(input_path, read_only=True, data_only=False)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preflight XLSX packages before loading them

When the read route receives an untrusted or corrupted workbook, load_workbook() parses and decompresses package parts such as shared strings and styles before any size, compression-ratio, member-count, or XML-part limit is enforced; read_only=True does not protect this initialization path. The XLSX routes contain no bounded package validator analogous to the DOCX/PPTX preflights, so a small ZIP bomb can exhaust memory or CPU before profiling begins.

Useful? React with 👍 / 👎.

"""Scan an untrusted read-only stream without relying on its <dimension>."""
worksheet.reset_dimensions()
min_row = min_column = max_row = max_column = None
for row in worksheet.iter_rows():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid rectangular expansion while discovering worksheet bounds

When a worksheet has a styled or cleared cell at an extreme coordinate such as XFD1048576, this unbounded iter_rows() must synthesize the rectangular gaps before lines 91–94 can discard the empty cell. Thus the newly added value filter fixes header selection but not the resource exhaustion: profiling can still generate billions of empty cells and hang before discovering the logical extent; scan worksheet XML or another sparse representation instead.

Useful? React with 👍 / 👎.

drawings = page.get_drawings()
widgets, annotations, links, interaction_visibility_unknown = viewable_interactives(page)
is_blank = not (
page.get_text().strip() or visible_images or drawings

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Filter drawings by rendered page visibility

When an otherwise blank page contains only a vector path that is fully off-page, clipped out, or has no visible paint, page.get_drawings() still returns a truthy record and this predicate reports the page as nonblank. The new visibility filtering covers images and interactive objects but not drawings, so generated blank pages can evade inspection; require a visible page intersection/rendered contribution for each drawing before counting it.

Useful? React with 👍 / 👎.

or 0xF900 <= codepoint <= 0xFAFF # CJK compatibility ideographs
or 0xFE30 <= codepoint <= 0xFE6F # CJK compatibility and small forms
or 0xFF00 <= codepoint <= 0xFFEF # fullwidth and halfwidth forms
or 0x20000 <= codepoint <= 0x3134F # CJK unified ideograph extensions

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Extend the DOCX font-slot range to newer Han blocks

When DOCX text contains a Han character at U+31350 or later, this cutoff classifies it as hAnsi rather than eastAsia, even though the PPTX audit in this same plugin already covers the newer supplementary Han range through U+33479. The glyph audit therefore resolves and checks the wrong effective font and can miss tofu for these rare characters; include the newer Han extensions in the East Asian slot mapping.

Useful? React with 👍 / 👎.

if shape.has_table:
for row_index, row in enumerate(shape.table.rows):
for column_index, cell in enumerate(row.cells):
yield f"{path}/table[{row_index},{column_index}]", cell.text_frame

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip covered slots when locating merged table text

When the requested text is in a merged PowerPoint table cell, row.cells exposes the merge-origin proxy at every covered grid position, so this loop adds the same underlying text frame to candidates multiple times and the uniqueness gate rejects an otherwise unambiguous edit. The analyzer and postcheck already distinguish cell.is_spanned; apply the same filter here so only the merge origin is treated as an editable target.

Useful? React with 👍 / 👎.

Comment on lines +41 to +42
else:
yield from iter_part_blocks(child, parent)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Select one markup-compatibility branch during DOCX extraction

When a DOCX contains mc:AlternateContent, this unconditional recursion visits both its mc:Choice and mc:Fallback branches even though a conforming consumer renders only one. Documents carrying fallback paragraphs, runs, or tables can therefore have mutually exclusive content duplicated or combined in the extracted text and summaries; select a supported Choice branch, otherwise use the Fallback or report the content as unresolved.

Useful? React with 👍 / 👎.

reference for reference in cell_formulas_before
if formula_may_intersect_rows(reference[1], reference[3], ws.title, 5)
]
unaudited_references = intersecting_formulas + non_cell_references(wb)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Inventory internal hyperlinks before shifting worksheet rows

When a workbook contains an internal hyperlink whose destination is at or below the inserted row, openpyxl does not rewrite the hyperlink location, but non_cell_references() omits cell hyperlinks entirely. The safety gate can therefore allow insert_rows(5) while leaving a link such as #'Data'!A10 pointing at the wrong record after that record moves to row 11; inventory internal hyperlink locations and require an explicit rewrite plan just like other non-cell dependencies.

Useful? React with 👍 / 👎.


def table_content(table):
rows = []
for row in table.rows:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Traverse table rows and cells wrapped by content controls

When a Word table uses row-level or cell-level structured document tags, table.rows and row._tr.tc_lst expose only direct <w:tr> and <w:tc> children, so rows or cells beneath <w:sdtContent> are silently omitted even though the extractor advertises content-control-aware traversal. Walk the table's physical XML through content-control wrappers before building the row and cell inventory.

Useful? React with 👍 / 👎.

Address the latest review round: add bounded XLSX/OPC package preflight with compression, member, materialization and range-token budgets; route all existing-package loads through validated snapshots; harden DOCX CJK/locator, PDF Type3 and PPTX merged-slot guidance; cover CSV formula neutralization, missing dimension scanning and internal hyperlink rewrites with real OPC fixtures.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5cd131415

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

})
return rows

doc = Document("input.docx")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preflight DOCX packages before structured reads

When this route receives an untrusted or corrupted DOCX, Document("input.docx") decompresses and parses package parts before any member-size, compression-ratio, or total-size bounds run. The bounded validator elsewhere in this plugin is used only by the raw-edit/review routes, so a small ZIP bomb can exhaust memory or CPU during an ordinary read or summary request; validate a private snapshot before passing that same handle to python-docx.

Useful? React with 👍 / 👎.

from openpyxl.utils.cell import coordinate_to_tuple, range_boundaries

approved_feature_loss = False # set True only after showing the inventory to the user
wb, dropped_parts, stripped_extensions = load_with_round_trip_audit("input.xlsx")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve rich text during the XLSX round-trip audit

When an input workbook contains rich-text cells, this call uses openpyxl's default rich_text=False, so loading and saving flattens their per-run formatting. The audit still reports no loss because the worksheet/shared-string parts remain present and no worksheet extension record disappears, allowing an unrelated edit to silently strip rich text throughout the workbook; load both audit and editable copies with rich_text=True or explicitly detect and gate this loss.

Useful? React with 👍 / 👎.

drawings, visible_drawings, drawing_visibility_unknown = viewable_drawings(page)
widgets, annotations, links, interaction_visibility_unknown = viewable_interactives(page)
is_blank = not (
page.get_text().strip() or visible_images or visible_drawings

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Filter non-rendered text from blank-page detection

When a page contains only text using invisible rendering mode, zero opacity, or clipping that prevents it from painting, page.get_text() can still return that text and this predicate classifies the visually blank page as nonblank. The image, drawing, and annotation branches already verify rendered visibility, so text needs an equivalent visibility/intersection check rather than treating every extractable string as visible content.

Useful? React with 👍 / 👎.

"formula output is not configured to recalculate in spreadsheet viewers",
)
for ws in wb.worksheets:
print(f"{ws.title} dims:", ws.dimensions)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Assert expected worksheet bounds in the postcheck

When an edit leaves an unintended value, formula, or styled cell outside the requested output range, this mandatory postcheck only prints ws.dimensions and still exits successfully. That contradicts the following requirement to confirm that the used range matches expectations and lets stale or accidentally appended content pass verification; accept expected bounds per sheet and fail when the reported dimensions differ.

Useful? React with 👍 / 👎.

require(info.file_size <= MAX_ENTRY, f"oversized part: {info.filename}")
ratio = info.file_size / max(info.compress_size, 1)
require(ratio <= MAX_COMPRESSION_RATIO, f"suspicious compression ratio: {info.filename}")
is_xml = info.filename.endswith((".xml", ".rels"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Identify DOCX XML parts from content-type declarations

When a DOCX contains an XML package part with a non-.xml suffix, an uppercase suffix, or an XML content-type override on another extension, this health check treats it as binary and never parses it. A malformed relationship target or custom XML part can therefore pass the advertised well-formedness check even though Word may repair or reject the document; parse [Content_Types].xml first and classify parts by their declared content type in addition to conventional suffixes.

Useful? React with 👍 / 👎.

@Hylouis233

Copy link
Copy Markdown
Contributor Author

本 PR 已迁移至 MiniMax-AI/MiniMax-Code-Plugins#6(head ece7467,内容一致)。上一轮反馈中要求的四类 Python fixture 已接入 CI(fixtures.test.mjs + actions/setup-python + requirements-fixtures.txt),后续 review 请在新 PR 进行。

@Hylouis233

Copy link
Copy Markdown
Contributor Author

关闭说明:本 PR 已完成迁移,后续在 MiniMax-AI/MiniMax-Code-Plugins#6 继续 review。当前 head ece7467 与新 PR 内容一致。

@Hylouis233 Hylouis233 closed this Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants