@preapexis/piexis 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/.github/workflows/npm-publish.yml +63 -0
  2. package/AGENTS.md +58 -0
  3. package/PERMISSION_MODES_PLAN.md +279 -0
  4. package/README.md +207 -0
  5. package/WHAT_YOU_GET.md +153 -0
  6. package/eval/README.md +72 -0
  7. package/eval/permission-cases.json +28 -0
  8. package/eval/permissions.mjs +103 -0
  9. package/extensions/ask-question.ts +111 -0
  10. package/extensions/background-tasks.ts +433 -0
  11. package/extensions/background-worker.ts +50 -0
  12. package/extensions/glob.ts +32 -0
  13. package/extensions/mode.ts +397 -0
  14. package/extensions/ui.ts +134 -0
  15. package/lib/bash-approvals.ts +113 -0
  16. package/lib/bash-policy.ts +164 -0
  17. package/lib/mode-policy.ts +174 -0
  18. package/lib/path-policy.ts +182 -0
  19. package/lib/permission-classifier.ts +128 -0
  20. package/lib/permission-config.ts +91 -0
  21. package/lib/permission-denials.ts +32 -0
  22. package/lib/permission-intent.ts +128 -0
  23. package/lib/permission-taxonomy.ts +15 -0
  24. package/lib/task-broker.ts +65 -0
  25. package/lib/task-checkout.ts +39 -0
  26. package/lib/task-git.ts +44 -0
  27. package/lib/task-patch.ts +106 -0
  28. package/lib/task-permissions.ts +51 -0
  29. package/lib/task-report.ts +62 -0
  30. package/lib/task-worktree-policy.ts +56 -0
  31. package/package.json +51 -0
  32. package/prompts/commit.md +7 -0
  33. package/prompts/init.md +66 -0
  34. package/prompts/summary.md +14 -0
  35. package/skills/claude-api/LICENSE.txt +202 -0
  36. package/skills/claude-api/SKILL.md +572 -0
  37. package/skills/claude-api/csharp/claude-api/README.md +362 -0
  38. package/skills/claude-api/csharp/claude-api/batches.md +14 -0
  39. package/skills/claude-api/csharp/claude-api/files-api.md +25 -0
  40. package/skills/claude-api/csharp/claude-api/streaming.md +28 -0
  41. package/skills/claude-api/csharp/claude-api/tool-use.md +164 -0
  42. package/skills/claude-api/curl/examples.md +256 -0
  43. package/skills/claude-api/curl/managed-agents.md +371 -0
  44. package/skills/claude-api/go/claude-api/README.md +188 -0
  45. package/skills/claude-api/go/claude-api/files-api.md +23 -0
  46. package/skills/claude-api/go/claude-api/streaming.md +43 -0
  47. package/skills/claude-api/go/claude-api/tool-use.md +220 -0
  48. package/skills/claude-api/go/managed-agents/README.md +564 -0
  49. package/skills/claude-api/java/claude-api/README.md +238 -0
  50. package/skills/claude-api/java/claude-api/files-api.md +27 -0
  51. package/skills/claude-api/java/claude-api/streaming.md +24 -0
  52. package/skills/claude-api/java/claude-api/tool-use.md +228 -0
  53. package/skills/claude-api/java/managed-agents/README.md +443 -0
  54. package/skills/claude-api/php/claude-api/README.md +174 -0
  55. package/skills/claude-api/php/claude-api/batches.md +15 -0
  56. package/skills/claude-api/php/claude-api/files-api.md +13 -0
  57. package/skills/claude-api/php/claude-api/streaming.md +27 -0
  58. package/skills/claude-api/php/claude-api/tool-use.md +253 -0
  59. package/skills/claude-api/php/managed-agents/README.md +443 -0
  60. package/skills/claude-api/python/claude-api/README.md +565 -0
  61. package/skills/claude-api/python/claude-api/batches.md +198 -0
  62. package/skills/claude-api/python/claude-api/files-api.md +170 -0
  63. package/skills/claude-api/python/claude-api/sdk-upgrade.md +286 -0
  64. package/skills/claude-api/python/claude-api/streaming.md +179 -0
  65. package/skills/claude-api/python/claude-api/tool-use.md +627 -0
  66. package/skills/claude-api/python/managed-agents/README.md +335 -0
  67. package/skills/claude-api/ruby/claude-api/README.md +134 -0
  68. package/skills/claude-api/ruby/claude-api/streaming.md +16 -0
  69. package/skills/claude-api/ruby/claude-api/tool-use.md +41 -0
  70. package/skills/claude-api/ruby/managed-agents/README.md +394 -0
  71. package/skills/claude-api/shared/admin-api.md +179 -0
  72. package/skills/claude-api/shared/agent-design.md +101 -0
  73. package/skills/claude-api/shared/anthropic-cli.md +269 -0
  74. package/skills/claude-api/shared/claude-platform-on-aws.md +59 -0
  75. package/skills/claude-api/shared/cost-optimization.md +233 -0
  76. package/skills/claude-api/shared/error-codes.md +262 -0
  77. package/skills/claude-api/shared/live-sources.md +166 -0
  78. package/skills/claude-api/shared/managed-agents-api-reference.md +455 -0
  79. package/skills/claude-api/shared/managed-agents-client-patterns.md +220 -0
  80. package/skills/claude-api/shared/managed-agents-core.md +359 -0
  81. package/skills/claude-api/shared/managed-agents-environments.md +226 -0
  82. package/skills/claude-api/shared/managed-agents-events.md +290 -0
  83. package/skills/claude-api/shared/managed-agents-memory.md +201 -0
  84. package/skills/claude-api/shared/managed-agents-multiagent.md +260 -0
  85. package/skills/claude-api/shared/managed-agents-onboarding.md +82 -0
  86. package/skills/claude-api/shared/managed-agents-outcomes.md +108 -0
  87. package/skills/claude-api/shared/managed-agents-overview.md +77 -0
  88. package/skills/claude-api/shared/managed-agents-scheduled-deployments.md +156 -0
  89. package/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +282 -0
  90. package/skills/claude-api/shared/managed-agents-tools.md +504 -0
  91. package/skills/claude-api/shared/managed-agents-webhooks.md +143 -0
  92. package/skills/claude-api/shared/model-migration.md +1872 -0
  93. package/skills/claude-api/shared/models.md +142 -0
  94. package/skills/claude-api/shared/platform-availability.md +53 -0
  95. package/skills/claude-api/shared/prompt-audit.md +223 -0
  96. package/skills/claude-api/shared/prompt-caching.md +292 -0
  97. package/skills/claude-api/shared/token-counting.md +56 -0
  98. package/skills/claude-api/shared/tool-use-concepts.md +526 -0
  99. package/skills/claude-api/typescript/claude-api/README.md +405 -0
  100. package/skills/claude-api/typescript/claude-api/batches.md +106 -0
  101. package/skills/claude-api/typescript/claude-api/files-api.md +98 -0
  102. package/skills/claude-api/typescript/claude-api/streaming.md +178 -0
  103. package/skills/claude-api/typescript/claude-api/tool-use.md +586 -0
  104. package/skills/claude-api/typescript/managed-agents/README.md +359 -0
  105. package/skills/code-review/SKILL.md +34 -0
  106. package/skills/code-review/agents/openai.yaml +3 -0
  107. package/skills/codebase-design/DEEPENING.md +37 -0
  108. package/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
  109. package/skills/codebase-design/SKILL.md +40 -0
  110. package/skills/diagnosing-bugs/SKILL.md +38 -0
  111. package/skills/diagnosing-bugs/agents/openai.yaml +3 -0
  112. package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
  113. package/skills/docx/LICENSE.txt +30 -0
  114. package/skills/docx/SKILL.md +93 -0
  115. package/skills/docx/scripts/__init__.py +1 -0
  116. package/skills/docx/scripts/accept_changes.py +135 -0
  117. package/skills/docx/scripts/comment.py +368 -0
  118. package/skills/docx/scripts/merge_runs.py +310 -0
  119. package/skills/docx/scripts/office/helpers/__init__.py +111 -0
  120. package/skills/docx/scripts/office/helpers/pptx_chart.py +170 -0
  121. package/skills/docx/scripts/office/helpers/pptx_slide.py +60 -0
  122. package/skills/docx/scripts/office/helpers/pptx_theme.py +114 -0
  123. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  124. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  125. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  126. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  127. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  128. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  129. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  130. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  131. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  132. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  133. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  134. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  135. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  136. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  137. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  138. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  139. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  140. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  141. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  142. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  143. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  144. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  145. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  146. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  147. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  148. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  149. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  150. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  151. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  152. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  153. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  154. package/skills/docx/scripts/office/schemas/mce/mc.xsd +75 -0
  155. package/skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  156. package/skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  157. package/skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  158. package/skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  159. package/skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  160. package/skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  161. package/skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  162. package/skills/docx/scripts/office/soffice.py +192 -0
  163. package/skills/docx/scripts/office/validate.py +173 -0
  164. package/skills/docx/scripts/office/validators/__init__.py +15 -0
  165. package/skills/docx/scripts/office/validators/base.py +875 -0
  166. package/skills/docx/scripts/office/validators/docx.py +466 -0
  167. package/skills/docx/scripts/office/validators/pptx.py +441 -0
  168. package/skills/docx/scripts/office/validators/redlining.py +299 -0
  169. package/skills/docx/scripts/templates/comments.xml +3 -0
  170. package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
  171. package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  172. package/skills/docx/scripts/templates/commentsIds.xml +3 -0
  173. package/skills/docx/scripts/templates/people.xml +3 -0
  174. package/skills/frontend-design/LICENSE.txt +177 -0
  175. package/skills/frontend-design/SKILL.md +27 -0
  176. package/skills/grilling/SKILL.md +41 -0
  177. package/skills/lean-build/SKILL.md +22 -0
  178. package/skills/lean-build/agents/openai.yaml +4 -0
  179. package/skills/mcp-builder/LICENSE.txt +202 -0
  180. package/skills/mcp-builder/SKILL.md +235 -0
  181. package/skills/mcp-builder/reference/evaluation.md +602 -0
  182. package/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
  183. package/skills/mcp-builder/reference/node_mcp_server.md +970 -0
  184. package/skills/mcp-builder/reference/python_mcp_server.md +719 -0
  185. package/skills/mcp-builder/scripts/connections.py +151 -0
  186. package/skills/mcp-builder/scripts/evaluation.py +373 -0
  187. package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  188. package/skills/mcp-builder/scripts/requirements.txt +2 -0
  189. package/skills/migration/SKILL.md +17 -0
  190. package/skills/migration/agents/openai.yaml +4 -0
  191. package/skills/pdf/LICENSE.txt +30 -0
  192. package/skills/pdf/SKILL.md +27 -0
  193. package/skills/pdf/forms.md +294 -0
  194. package/skills/pdf/reference.md +612 -0
  195. package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
  196. package/skills/pdf/scripts/check_fillable_fields.py +11 -0
  197. package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
  198. package/skills/pdf/scripts/create_validation_image.py +37 -0
  199. package/skills/pdf/scripts/extract_form_field_info.py +122 -0
  200. package/skills/pdf/scripts/extract_form_structure.py +115 -0
  201. package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
  202. package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  203. package/skills/pptx/LICENSE.txt +30 -0
  204. package/skills/pptx/SKILL.md +240 -0
  205. package/skills/pptx/scripts/__init__.py +0 -0
  206. package/skills/pptx/scripts/add_slide.py +367 -0
  207. package/skills/pptx/scripts/clean.py +309 -0
  208. package/skills/pptx/scripts/office/helpers/__init__.py +111 -0
  209. package/skills/pptx/scripts/office/helpers/pptx_chart.py +170 -0
  210. package/skills/pptx/scripts/office/helpers/pptx_slide.py +60 -0
  211. package/skills/pptx/scripts/office/helpers/pptx_theme.py +114 -0
  212. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  213. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  214. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  215. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  216. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  217. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  218. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  219. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  220. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  221. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  222. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  223. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  224. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  225. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  226. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  227. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  228. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  229. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  230. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  231. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  232. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  233. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  234. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  235. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  236. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  237. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  238. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  239. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  240. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  241. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  242. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  243. package/skills/pptx/scripts/office/schemas/mce/mc.xsd +75 -0
  244. package/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  245. package/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  246. package/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  247. package/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  248. package/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  249. package/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  250. package/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  251. package/skills/pptx/scripts/office/soffice.py +192 -0
  252. package/skills/pptx/scripts/office/validate.py +173 -0
  253. package/skills/pptx/scripts/office/validators/__init__.py +15 -0
  254. package/skills/pptx/scripts/office/validators/base.py +875 -0
  255. package/skills/pptx/scripts/office/validators/docx.py +466 -0
  256. package/skills/pptx/scripts/office/validators/pptx.py +441 -0
  257. package/skills/pptx/scripts/office/validators/redlining.py +299 -0
  258. package/skills/pptx/scripts/thumbnail.py +311 -0
  259. package/skills/safe-refactor/SKILL.md +16 -0
  260. package/skills/safe-refactor/agents/openai.yaml +4 -0
  261. package/skills/tdd/SKILL.md +18 -0
  262. package/skills/tdd/agents/openai.yaml +3 -0
  263. package/skills/tdd/mocking.md +59 -0
  264. package/skills/tdd/tests.md +77 -0
  265. package/skills/verify-and-stop/SKILL.md +16 -0
  266. package/skills/verify-and-stop/agents/openai.yaml +4 -0
  267. package/skills/webapp-testing/LICENSE.txt +202 -0
  268. package/skills/webapp-testing/SKILL.md +96 -0
  269. package/skills/webapp-testing/examples/console_logging.py +35 -0
  270. package/skills/webapp-testing/examples/element_discovery.py +40 -0
  271. package/skills/webapp-testing/examples/static_html_automation.py +33 -0
  272. package/skills/webapp-testing/scripts/with_server.py +106 -0
  273. package/skills/writing-for-agents/SKILL-MECHANICS.md +22 -0
  274. package/skills/writing-for-agents/SKILL.md +28 -0
  275. package/skills/writing-for-agents/agents/openai.yaml +3 -0
  276. package/skills/xlsx/LICENSE.txt +30 -0
  277. package/skills/xlsx/SKILL.md +101 -0
  278. package/skills/xlsx/scripts/office/helpers/__init__.py +111 -0
  279. package/skills/xlsx/scripts/office/helpers/pptx_chart.py +170 -0
  280. package/skills/xlsx/scripts/office/helpers/pptx_slide.py +60 -0
  281. package/skills/xlsx/scripts/office/helpers/pptx_theme.py +114 -0
  282. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  283. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  284. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  285. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  286. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  287. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  288. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  289. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  290. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  291. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  292. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  293. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  294. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  295. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  296. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  297. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  298. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  299. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  300. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  301. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  302. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  303. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  304. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  305. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  306. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  307. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  308. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  309. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  310. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  311. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  312. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  313. package/skills/xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
  314. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  315. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  316. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  317. package/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  318. package/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  319. package/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  320. package/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  321. package/skills/xlsx/scripts/office/soffice.py +192 -0
  322. package/skills/xlsx/scripts/office/validate.py +173 -0
  323. package/skills/xlsx/scripts/office/validators/__init__.py +15 -0
  324. package/skills/xlsx/scripts/office/validators/base.py +875 -0
  325. package/skills/xlsx/scripts/office/validators/docx.py +466 -0
  326. package/skills/xlsx/scripts/office/validators/pptx.py +441 -0
  327. package/skills/xlsx/scripts/office/validators/redlining.py +299 -0
  328. package/skills/xlsx/scripts/recalc.py +308 -0
  329. package/skills-lock.json +359 -0
  330. package/tests/auto-permissions.test.mjs +315 -0
  331. package/tests/background-tasks.test.mjs +27 -0
  332. package/tests/background-worker.test.mjs +60 -0
  333. package/tests/bash-permissions.test.mjs +276 -0
  334. package/tests/glob.test.mjs +31 -0
  335. package/tests/mode.test.mjs +62 -0
  336. package/tests/permission-evaluation.test.mjs +70 -0
  337. package/tests/permission-foundation.test.mjs +194 -0
  338. package/tests/permission-policy.test.mjs +158 -0
  339. package/tests/runtime/README.md +51 -0
  340. package/tests/runtime/harness.mjs +190 -0
  341. package/tests/runtime/permissions.test.mjs +192 -0
  342. package/tests/runtime/tui.test.mjs +60 -0
  343. package/tests/skills-routing.test.mjs +51 -0
  344. package/tests/task-integration.test.mjs +324 -0
  345. package/tests/task-permissions.test.mjs +178 -0
  346. package/tests/task-worktree-policy.test.mjs +53 -0
  347. package/themes/orange.json +82 -0
  348. package/tsconfig.json +13 -0
@@ -0,0 +1,310 @@
1
+ """Merge adjacent identically-formatted runs in a DOCX.
2
+
3
+ Word fragments paragraph text across many <w:r> elements (revision ids,
4
+ spell-check markers, editing history), which makes find-and-replace on
5
+ word/document.xml unreliable — the string you're looking for is split
6
+ across runs. This coalesces adjacent runs whose formatting (<w:rPr>) is
7
+ identical, strips rsid attributes and proofErr markers, and consolidates the
8
+ text elements — <w:t>, and <w:delText> for text inside a tracked deletion.
9
+
10
+ Rendering is unchanged. The text you search is what Word draws, which is not
11
+ always the bytes in the file: an element without xml:space="preserve" has its
12
+ edge whitespace trimmed before it reaches the page, so `<w:t>Hello </w:t>`
13
+ followed by `<w:t>world</w:t>` reads "Helloworld" and merges to exactly that.
14
+
15
+ Runs in two different <w:ins>/<w:del> wrappers are never merged: that would
16
+ rewrite tracked-change structure, collapsing separate revisions into one.
17
+
18
+ Only word/document.xml is processed (not headers, footers, or footnotes).
19
+
20
+ Usage:
21
+ python merge_runs.py unpacked/ # after unzip, before editing
22
+ python merge_runs.py document.docx # rewrite in place
23
+ python merge_runs.py document.docx -o out.docx
24
+ """
25
+
26
+
27
+ import argparse
28
+ import sys
29
+ import tempfile
30
+ import zipfile
31
+ from pathlib import Path
32
+
33
+ import defusedxml.minidom
34
+
35
+ from office.helpers import XML_SPACE, rendered_text, rezip, safe_extract
36
+
37
+ WORDML_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
38
+
39
+
40
+ def merge_runs(input_dir: str) -> tuple[int, str]:
41
+ doc_xml = Path(input_dir) / "word" / "document.xml"
42
+
43
+ if not doc_xml.exists():
44
+ return 0, f"Error: {doc_xml} not found"
45
+
46
+ try:
47
+ dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8"))
48
+ root = dom.documentElement
49
+ run_names = _run_tag_names(root)
50
+
51
+ _remove_elements(root, "proofErr")
52
+
53
+ runs = _find_runs(root, run_names)
54
+ _strip_rsid_attrs(runs)
55
+
56
+ merge_count = 0
57
+ for container in {run.parentNode for run in runs}:
58
+ merge_count += _merge_runs_in(container, run_names)
59
+
60
+ doc_xml.write_bytes(dom.toxml(encoding="UTF-8"))
61
+ return merge_count, f"Merged {merge_count} runs"
62
+
63
+ except Exception as e:
64
+ return 0, f"Error: {e}"
65
+
66
+
67
+
68
+
69
+ def _is_element(node, tag: str) -> bool:
70
+ name = node.localName or node.tagName
71
+ return name == tag or name.endswith(f":{tag}")
72
+
73
+
74
+ def _run_tag_names(root) -> set[str]:
75
+ names = set()
76
+ for attr in root.attributes.values():
77
+ if attr.value == WORDML_NS:
78
+ if attr.name == "xmlns":
79
+ names.add("r")
80
+ elif attr.name.startswith("xmlns:"):
81
+ names.add(attr.name.split(":", 1)[1] + ":r")
82
+ return names or {"w:r", "r"}
83
+
84
+
85
+ def _find_elements(root, tag: str) -> list:
86
+ results = []
87
+
88
+ def traverse(node):
89
+ if node.nodeType == node.ELEMENT_NODE:
90
+ if _is_element(node, tag):
91
+ results.append(node)
92
+ for child in node.childNodes:
93
+ traverse(child)
94
+
95
+ traverse(root)
96
+ return results
97
+
98
+
99
+ def _find_runs(root, run_names: set[str]) -> list:
100
+ return [e for e in _find_elements(root, "r") if _is_run(e, run_names)]
101
+
102
+
103
+ def _get_child(parent, tag: str):
104
+ return next(iter(_get_children(parent, tag)), None)
105
+
106
+
107
+ def _get_children(parent, tag: str) -> list:
108
+ return [
109
+ child
110
+ for child in parent.childNodes
111
+ if child.nodeType == child.ELEMENT_NODE and _is_element(child, tag)
112
+ ]
113
+
114
+
115
+ def _is_adjacent(elem1, elem2) -> bool:
116
+ node = elem1.nextSibling
117
+ while node:
118
+ if node == elem2:
119
+ return True
120
+ if node.nodeType == node.ELEMENT_NODE:
121
+ return False
122
+ if node.nodeType == node.TEXT_NODE and node.data.strip(XML_SPACE):
123
+ return False
124
+ node = node.nextSibling
125
+ return False
126
+
127
+
128
+
129
+
130
+ def _remove_elements(root, tag: str):
131
+ for elem in _find_elements(root, tag):
132
+ if elem.parentNode:
133
+ elem.parentNode.removeChild(elem)
134
+
135
+
136
+ def _strip_rsid_attrs(runs: list):
137
+ for run in runs:
138
+ for attr in list(run.attributes.values()):
139
+ if "rsid" in attr.name.lower():
140
+ run.removeAttribute(attr.name)
141
+
142
+
143
+
144
+
145
+ def _merge_runs_in(container, run_names: set[str]) -> int:
146
+ merge_count = 0
147
+ run = _first_child_run(container, run_names)
148
+
149
+ while run:
150
+ while True:
151
+ next_elem = _next_element_sibling(run)
152
+ if next_elem and _is_run(next_elem, run_names) and _can_merge(run, next_elem):
153
+ _merge_run_content(run, next_elem)
154
+ container.removeChild(next_elem)
155
+ merge_count += 1
156
+ else:
157
+ break
158
+
159
+ _consolidate_text(run)
160
+ run = _next_sibling_run(run, run_names)
161
+
162
+ return merge_count
163
+
164
+
165
+ def _first_child_run(container, run_names: set[str]):
166
+ for child in container.childNodes:
167
+ if child.nodeType == child.ELEMENT_NODE and _is_run(child, run_names):
168
+ return child
169
+ return None
170
+
171
+
172
+ def _next_element_sibling(node):
173
+ sibling = node.nextSibling
174
+ while sibling:
175
+ if sibling.nodeType == sibling.ELEMENT_NODE:
176
+ return sibling
177
+ sibling = sibling.nextSibling
178
+ return None
179
+
180
+
181
+ def _next_sibling_run(node, run_names: set[str]):
182
+ sibling = node.nextSibling
183
+ while sibling:
184
+ if sibling.nodeType == sibling.ELEMENT_NODE:
185
+ if _is_run(sibling, run_names):
186
+ return sibling
187
+ sibling = sibling.nextSibling
188
+ return None
189
+
190
+
191
+ def _is_run(node, run_names: set[str]) -> bool:
192
+ return node.tagName in run_names
193
+
194
+
195
+ def _can_merge(run1, run2) -> bool:
196
+ rpr1 = _get_child(run1, "rPr")
197
+ rpr2 = _get_child(run2, "rPr")
198
+
199
+ if (rpr1 is None) != (rpr2 is None):
200
+ return False
201
+ if rpr1 is None:
202
+ return True
203
+ return rpr1.toxml() == rpr2.toxml()
204
+
205
+
206
+ def _merge_run_content(target, source):
207
+ for child in list(source.childNodes):
208
+ if child.nodeType == child.ELEMENT_NODE:
209
+ name = child.localName or child.tagName
210
+ if name != "rPr" and not name.endswith(":rPr"):
211
+ target.appendChild(child)
212
+
213
+
214
+ def _element_text(elem) -> str:
215
+ return "".join(
216
+ child.data
217
+ for child in elem.childNodes
218
+ if child.nodeType in (child.TEXT_NODE, child.CDATA_SECTION_NODE)
219
+ )
220
+
221
+
222
+ def _has_preserve(elem) -> bool:
223
+ return elem.getAttribute("xml:space") == "preserve"
224
+
225
+
226
+ def _rendered_text(elem) -> str:
227
+ return rendered_text(_element_text(elem), _has_preserve(elem))
228
+
229
+
230
+ def _consolidate_text(run):
231
+ for tag in ("t", "delText"):
232
+ _consolidate_text_elements(run, tag)
233
+
234
+
235
+ def _consolidate_text_elements(run, tag: str):
236
+ t_elements = _get_children(run, tag)
237
+
238
+ for i in range(len(t_elements) - 1, 0, -1):
239
+ curr, prev = t_elements[i], t_elements[i - 1]
240
+
241
+ if _is_adjacent(prev, curr):
242
+ merged = _rendered_text(prev) + _rendered_text(curr)
243
+ had_preserve = _has_preserve(prev) or _has_preserve(curr)
244
+
245
+ new_text = run.ownerDocument.createTextNode(merged)
246
+ for node in list(prev.childNodes):
247
+ if node.nodeType in (node.TEXT_NODE, node.CDATA_SECTION_NODE):
248
+ prev.removeChild(node)
249
+ else:
250
+ run.insertBefore(node, curr)
251
+ prev.appendChild(new_text)
252
+ for node in list(curr.childNodes):
253
+ if node.nodeType not in (node.TEXT_NODE, node.CDATA_SECTION_NODE):
254
+ run.insertBefore(node, curr)
255
+
256
+ if merged != merged.strip(XML_SPACE) or had_preserve:
257
+ prev.setAttribute("xml:space", "preserve")
258
+ elif prev.hasAttribute("xml:space"):
259
+ prev.removeAttribute("xml:space")
260
+
261
+ run.removeChild(curr)
262
+
263
+
264
+
265
+
266
+ def _merge_or_die(path: Path) -> str:
267
+ _, msg = merge_runs(str(path))
268
+ if msg.startswith("Error"):
269
+ print(msg, file=sys.stderr)
270
+ sys.exit(1)
271
+ return msg
272
+
273
+
274
+ def main() -> None:
275
+ p = argparse.ArgumentParser(
276
+ description="Merge adjacent identically-formatted runs in a DOCX (directory or .docx file)."
277
+ )
278
+ p.add_argument("input", help="Unpacked DOCX directory OR a .docx/.dotx file")
279
+ p.add_argument(
280
+ "-o", "--output",
281
+ help="Output .docx path (only valid when input is a .docx; default: overwrite input)",
282
+ )
283
+ args = p.parse_args()
284
+
285
+ src = Path(args.input)
286
+
287
+ try:
288
+ if src.is_dir():
289
+ if args.output:
290
+ p.error("--output is only valid for .docx input; directory input is modified in place")
291
+ print(_merge_or_die(src))
292
+ elif src.is_file() and src.suffix.lower() in (".docx", ".dotx"):
293
+ out = Path(args.output) if args.output else src
294
+ with tempfile.TemporaryDirectory() as tmp:
295
+ tmp_path = Path(tmp)
296
+ with zipfile.ZipFile(src) as zf:
297
+ safe_extract(zf, tmp_path)
298
+ msg = _merge_or_die(tmp_path)
299
+ rezip(tmp_path, out)
300
+ print(f"{msg}; wrote {out}")
301
+ else:
302
+ print(f"Error: {src} is neither a directory nor a .docx/.dotx file", file=sys.stderr)
303
+ sys.exit(1)
304
+ except (OSError, ValueError, zipfile.BadZipFile) as e:
305
+ print(f"Error: {e}", file=sys.stderr)
306
+ sys.exit(1)
307
+
308
+
309
+ if __name__ == "__main__":
310
+ main()
@@ -0,0 +1,111 @@
1
+ import os
2
+ import posixpath
3
+ import re
4
+ import stat
5
+ import tempfile
6
+ import urllib.parse
7
+ import zipfile
8
+ from pathlib import Path
9
+
10
+ OOXML_FAMILY = {
11
+ ".docx": "docx",
12
+ ".dotx": "docx",
13
+ ".pptx": "pptx",
14
+ ".potx": "pptx",
15
+ ".xlsx": "xlsx",
16
+ ".xltx": "xlsx",
17
+ }
18
+
19
+ _SCHEME_RE = re.compile(r"^[A-Za-z][A-Za-z0-9+.\-]*:")
20
+
21
+ SLIDE_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"
22
+
23
+
24
+ def opc_target(target: str, source_part: str, target_mode: str = "") -> str | None:
25
+ if not target:
26
+ return None
27
+ if target_mode.lower() == "external":
28
+ return None
29
+ if _SCHEME_RE.match(target):
30
+ return None
31
+
32
+ target = urllib.parse.unquote(target)
33
+
34
+ if "\\" in target:
35
+ raise ValueError(f"relationship target is not a POSIX part name: {target!r}")
36
+
37
+ if target.startswith("/"):
38
+ joined = target.lstrip("/")
39
+ else:
40
+ joined = posixpath.join(posixpath.dirname(source_part), target)
41
+
42
+ parts: list[str] = []
43
+ for segment in posixpath.normpath(joined).split("/"):
44
+ if segment in ("", "."):
45
+ continue
46
+ if segment == "..":
47
+ if not parts:
48
+ raise ValueError(f"relationship target escapes the package: {target!r}")
49
+ parts.pop()
50
+ else:
51
+ parts.append(segment)
52
+
53
+ if not parts:
54
+ raise ValueError(f"relationship target resolves to nothing: {target!r}")
55
+ return "/".join(parts)
56
+
57
+
58
+ def rels_source_part(rels_file: Path, unpacked_dir: Path) -> str:
59
+ owner_dir = rels_file.parent.parent.relative_to(unpacked_dir)
60
+ return posixpath.join(owner_dir.as_posix(), rels_file.name[: -len(".rels")]).lstrip("./")
61
+
62
+
63
+ def part_text(data: bytes) -> str:
64
+ return data.decode("utf-8", "surrogateescape")
65
+
66
+
67
+ XML_SPACE = " \t\r\n"
68
+
69
+
70
+ def rendered_text(text: str, preserve: bool) -> str:
71
+ return text if preserve else text.strip(XML_SPACE)
72
+
73
+
74
+ def safe_extract(zf: zipfile.ZipFile, dest: Path) -> None:
75
+ dest = dest.resolve()
76
+ for m in zf.infolist():
77
+ if stat.S_ISLNK(m.external_attr >> 16):
78
+ raise ValueError(f"symlink archive entry not allowed: {m.filename!r}")
79
+ target = (dest / m.filename).resolve()
80
+ if not target.is_relative_to(dest):
81
+ raise ValueError(f"unsafe archive entry: {m.filename!r}")
82
+ zf.extract(m, dest)
83
+
84
+
85
+ def rezip(src_dir: Path, out_path: Path) -> None:
86
+ files = sorted(p for p in src_dir.rglob("*") if p.is_file())
87
+ ct = src_dir / "[Content_Types].xml"
88
+ fd, tmp_name = tempfile.mkstemp(
89
+ prefix=out_path.name + ".", suffix=".tmp", dir=out_path.parent
90
+ )
91
+ tmp_out = Path(tmp_name)
92
+ try:
93
+ with os.fdopen(fd, "wb") as fh:
94
+ with zipfile.ZipFile(fh, "w", zipfile.ZIP_DEFLATED) as zf:
95
+ if ct.exists():
96
+ zf.write(ct, ct.relative_to(src_dir), compress_type=zipfile.ZIP_STORED)
97
+ for f in files:
98
+ if f == ct:
99
+ continue
100
+ zf.write(f, f.relative_to(src_dir))
101
+ if out_path.exists():
102
+ mode = out_path.stat().st_mode & 0o777
103
+ else:
104
+ umask = os.umask(0)
105
+ os.umask(umask)
106
+ mode = 0o666 & ~umask
107
+ os.chmod(tmp_out, mode)
108
+ os.replace(tmp_out, out_path)
109
+ finally:
110
+ if tmp_out.exists():
111
+ tmp_out.unlink()
@@ -0,0 +1,170 @@
1
+ """Find chart XML that PowerPoint refuses but the schema accepts.
2
+
3
+ Detection only: for either fault more than one repair is valid, and only the
4
+ author knows which was meant.
5
+ """
6
+
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from typing import Mapping
12
+
13
+ from . import part_text
14
+
15
+
16
+ _CHART_PART_RE = re.compile(r"ppt/charts/chart\d+\.xml")
17
+
18
+ _GROUPING_RE = re.compile(r"""<c:grouping\b[^>]*?\bval=["'](\w+)["']""")
19
+ _DLBL_POS_RE = re.compile(r"""<c:dLblPos\b[^>]*?\bval=["'](\w+)["']""")
20
+
21
+ def _strip_ext_lst(text: str) -> str:
22
+ out, cursor = [], 0
23
+ for lo, hi in _ext_lst_spans(text):
24
+ out.append(text[cursor:lo])
25
+ cursor = hi
26
+ out.append(text[cursor:])
27
+ return "".join(out)
28
+
29
+ _BAR_GROUP_RE = re.compile(r"<c:(bar3DChart|barChart)\b[^>]*(?<!/)>.*?</c:\1\s*>", re.DOTALL)
30
+
31
+ STACKED_GROUPINGS = frozenset({"stacked", "percentStacked"})
32
+ ILLEGAL_ON_STACKED = frozenset({"outEnd"})
33
+ LEGAL_ON_STACKED = ("ctr", "inEnd", "inBase")
34
+
35
+
36
+ def _check_stacked_label_positions(part: str, xml: str) -> list[str]:
37
+ problems: list[str] = []
38
+ for match in _BAR_GROUP_RE.finditer(xml):
39
+ block = _strip_ext_lst(match.group(0))
40
+ group = match.group(1)
41
+
42
+ grouping = _GROUPING_RE.search(block)
43
+ if grouping is None or grouping.group(1) not in STACKED_GROUPINGS:
44
+ continue
45
+
46
+ bad = [p for p in _DLBL_POS_RE.findall(block) if p in ILLEGAL_ON_STACKED]
47
+ for pos in sorted(set(bad)):
48
+ problems.append(
49
+ f'{part}: {bad.count(pos)} data label(s) use dLblPos="{pos}" on a '
50
+ f"{grouping.group(1)} {group}; PowerPoint allows only "
51
+ f"{', '.join(LEGAL_ON_STACKED)} there"
52
+ )
53
+ return problems
54
+
55
+
56
+
57
+ _ANY_CHART_GROUP_RE = re.compile(r"<c:(\w+Chart)\b[^>]*(?<!/)>.*?</c:\1\s*>", re.DOTALL)
58
+
59
+ _AXID_RE = re.compile(
60
+ r"""\s*<c:axId\b[^>]*?\bval=["'](-?\d+)["']\s*(?:/>|>\s*</c:axId\s*>)"""
61
+ )
62
+
63
+ _AXIS_DECL_RE = re.compile(
64
+ r"""<c:(catAx|valAx|serAx|dateAx)\b[^>]*(?<!/)>\s*<c:axId\b[^>]*?\bval=["'](-?\d+)["']"""
65
+ )
66
+
67
+ AXID_LIMIT = {
68
+ "barChart": 2, "lineChart": 2, "areaChart": 2, "scatterChart": 2,
69
+ "bubbleChart": 2, "radarChart": 2, "stockChart": 2,
70
+ "bar3DChart": 3, "line3DChart": 3, "area3DChart": 3,
71
+ "surfaceChart": 3, "surface3DChart": 3,
72
+ }
73
+
74
+ AXID_MINIMUM = {
75
+ "barChart": 2, "lineChart": 2, "areaChart": 2, "scatterChart": 2,
76
+ "bubbleChart": 2, "radarChart": 2, "stockChart": 2,
77
+ "bar3DChart": 2, "area3DChart": 2, "surfaceChart": 2,
78
+ "line3DChart": 3, "surface3DChart": 3,
79
+ }
80
+
81
+
82
+ def _declared_axes(xml: str) -> dict[str, list[str]]:
83
+ axes: dict[str, list[str]] = {}
84
+ for kind, axid in _AXIS_DECL_RE.findall(xml):
85
+ axes.setdefault(kind, []).append(axid)
86
+ return axes
87
+
88
+
89
+ def _canonical_ids(axes: dict[str, list[str]], limit: int) -> list[str] | None:
90
+ category = axes.get("catAx", []) + axes.get("dateAx", [])
91
+ value = axes.get("valAx", [])
92
+ series = axes.get("serAx", [])
93
+ if len(category) != 1 or len(value) != 1 or len(series) > 1:
94
+ return None
95
+ ids = [category[0], value[0]]
96
+ if limit >= 3 and series:
97
+ ids.append(series[0])
98
+ return ids
99
+
100
+
101
+ def _undeclared_axes(kind: str, block: str, axes: dict[str, list[str]]) -> list[str] | None:
102
+ if kind not in AXID_LIMIT:
103
+ return None
104
+ ids = _AXID_RE.findall(block)
105
+ declared = {i for group in axes.values() for i in group}
106
+ if len([i for i in ids if i in declared]) >= 2:
107
+ return None
108
+ return ids
109
+
110
+
111
+ def _check_chart_axis_references(part: str, xml: str) -> list[str]:
112
+ axes = _declared_axes(xml)
113
+ problems: list[str] = []
114
+ declared = {i for group in axes.values() for i in group}
115
+ for match in _ANY_CHART_GROUP_RE.finditer(xml):
116
+ kind, block = match.group(1), match.group(0)
117
+ ids = _undeclared_axes(kind, block, axes)
118
+ if ids is None:
119
+ continue
120
+ if not ids:
121
+ problems.append(
122
+ f"{part}: <c:{kind}> declares no <c:axId> this part can resolve; a chart "
123
+ f"group needs {AXID_MINIMUM[kind]}, and PowerPoint discards one with fewer"
124
+ )
125
+ continue
126
+ dead = [i for i in ids if i not in declared]
127
+ canonical = _canonical_ids(axes, AXID_LIMIT[kind])
128
+ if canonical is not None and len(canonical) >= AXID_MINIMUM[kind]:
129
+ hint = f"Fix: point them at the axes this part declares ({', '.join(canonical)})"
130
+ else:
131
+ hint = ("Fix: the part declares several axes of a kind -- declare the "
132
+ "secondary axes the series expects, or drop them")
133
+ detail = (f"of which {', '.join(dead)} name no declared axis"
134
+ if dead else f"only {len(ids)} of which this part declares")
135
+ problems.append(
136
+ f"{part}: <c:{kind}> references axId {', '.join(ids)}, {detail}, "
137
+ f"leaving fewer than two live axes; PowerPoint discards the chart. {hint}"
138
+ )
139
+ return problems
140
+
141
+
142
+ def _ext_lst_spans(text: str) -> list[tuple[int, int]]:
143
+ spans: list[tuple[int, int]] = []
144
+ depth = 0
145
+ start = 0
146
+ for match in re.finditer(r"<(/?)c:extLst\b[^>]*?(/?)>", text):
147
+ closing, self_closing = match.group(1), match.group(2)
148
+ if self_closing:
149
+ continue
150
+ if closing:
151
+ depth -= 1
152
+ if depth == 0:
153
+ spans.append((start, match.end()))
154
+ else:
155
+ if depth == 0:
156
+ start = match.start()
157
+ depth += 1
158
+ return spans
159
+
160
+
161
+ CHART_CHECKS = (_check_stacked_label_positions, _check_chart_axis_references)
162
+
163
+
164
+ def find_chart_problems(files: Mapping[str, bytes]) -> list[str]:
165
+ problems: list[str] = []
166
+ for part in sorted(n for n in files if _CHART_PART_RE.fullmatch(n)):
167
+ xml = part_text(files[part])
168
+ for check in CHART_CHECKS:
169
+ problems.extend(check(part, xml))
170
+ return problems
@@ -0,0 +1,60 @@
1
+ """Pick the slide-XML schema errors PowerPoint refuses the file over.
2
+
3
+ A denylist over lxml's messages, so an unrecognised error class is a miss rather
4
+ than a false alarm.
5
+ """
6
+
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+
12
+ SLIDE_PART_RE = re.compile(
13
+ r"ppt/(slides|slideLayouts|slideMasters|notesSlides|notesMasters|handoutMasters)"
14
+ r"/[^/]+\.xml"
15
+ )
16
+
17
+ FATAL_SLIDE_ERRORS: tuple[tuple[re.Pattern[str], str], ...] = (
18
+ (
19
+ re.compile(r"\}tableStyleId': This element is not expected"),
20
+ "two <a:tableStyleId> in one <a:tblPr> (the schema allows one)",
21
+ ),
22
+ (
23
+ re.compile(r"\}srgbClr', attribute 'val'"),
24
+ "a colour that is not six hex digits",
25
+ ),
26
+ (
27
+ re.compile(r"\}txBody': Missing child element"),
28
+ "a <p:txBody> with no children",
29
+ ),
30
+ (
31
+ re.compile(r"\}miter', attribute 'lim'"),
32
+ 'a line join with lim="NaN"',
33
+ ),
34
+ (
35
+ re.compile(r"\}uLnTx': This element is not expected"),
36
+ "<a:uLnTx> in a position the schema forbids",
37
+ ),
38
+ (
39
+ re.compile(r"\}overrideClrMapping': This element is not expected"),
40
+ "<p:overrideClrMapping> in a position the schema forbids",
41
+ ),
42
+ (
43
+ re.compile(r"\}nvGrpSpPr': Missing child element"),
44
+ "a <p:nvGrpSpPr> with no children",
45
+ ),
46
+ )
47
+
48
+
49
+ def is_schema_verdict(error: str) -> bool:
50
+ return error.startswith("Element ")
51
+
52
+
53
+ def fatal_slide_errors(errors: set[str]) -> list[str]:
54
+ out = []
55
+ for error in sorted(errors):
56
+ for pattern, meaning in FATAL_SLIDE_ERRORS:
57
+ if pattern.search(error):
58
+ out.append(f"{meaning}: {error}")
59
+ break
60
+ return out