@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,309 @@
1
+ """Remove unreferenced files from an unpacked PPTX directory.
2
+
3
+ Usage: python clean.py <unpacked_dir>
4
+
5
+ Example:
6
+ python clean.py unpacked/
7
+
8
+ This script removes:
9
+ - Orphaned slides (not in sldIdLst) and their relationships
10
+ - [trash] directory (unreferenced files)
11
+ - Orphaned .rels files for deleted resources
12
+ - Unreferenced media, embeddings, charts, diagrams, drawings, ink files
13
+ - Unreferenced theme files
14
+ - Unreferenced notes slides
15
+ - Content-Type overrides for deleted files
16
+ """
17
+
18
+ import posixpath
19
+ import re
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ import defusedxml.minidom
24
+
25
+ from office.helpers import SLIDE_REL_TYPE, opc_target, rels_source_part
26
+
27
+
28
+ def _slide_rids(pres_rels_path: Path, unpacked_dir: Path) -> dict[str, str]:
29
+ source_part = rels_source_part(pres_rels_path, unpacked_dir)
30
+ rels_dom = defusedxml.minidom.parse(str(pres_rels_path))
31
+
32
+ rids: dict[str, str] = {}
33
+ for rel in rels_dom.getElementsByTagName("Relationship"):
34
+ if rel.getAttribute("Type") != SLIDE_REL_TYPE:
35
+ continue
36
+ part = opc_target(
37
+ rel.getAttribute("Target"), source_part, rel.getAttribute("TargetMode")
38
+ )
39
+ if part is not None:
40
+ rids[rel.getAttribute("Id")] = part
41
+ return rids
42
+
43
+
44
+ def get_slides_in_sldidlst(unpacked_dir: Path) -> set[str]:
45
+ pres_path = unpacked_dir / "ppt" / "presentation.xml"
46
+ pres_rels_path = unpacked_dir / "ppt" / "_rels" / "presentation.xml.rels"
47
+
48
+ if not pres_path.exists() or not pres_rels_path.exists():
49
+ return set()
50
+
51
+ rid_to_slide = _slide_rids(pres_rels_path, unpacked_dir)
52
+
53
+ pres_content = pres_path.read_text(encoding="utf-8")
54
+ referenced_rids = set(re.findall(r'<p:sldId[^>]*r:id="([^"]+)"', pres_content))
55
+
56
+ return {
57
+ posixpath.basename(rid_to_slide[rid])
58
+ for rid in referenced_rids
59
+ if rid in rid_to_slide
60
+ }
61
+
62
+
63
+ class RefusedToClean(Exception):
64
+ """The package does not look the way a readable package should."""
65
+
66
+
67
+ def remove_orphaned_slides(unpacked_dir: Path) -> list[str]:
68
+ slides_dir = unpacked_dir / "ppt" / "slides"
69
+ slides_rels_dir = slides_dir / "_rels"
70
+ pres_rels_path = unpacked_dir / "ppt" / "_rels" / "presentation.xml.rels"
71
+
72
+ if not slides_dir.exists():
73
+ return []
74
+
75
+ referenced_slides = get_slides_in_sldidlst(unpacked_dir)
76
+ on_disk = sorted(slides_dir.glob("slide*.xml"))
77
+
78
+ if on_disk and not any(s.name in referenced_slides for s in on_disk):
79
+ listed = re.findall(
80
+ r'<p:sldId[^>]*r:id="([^"]+)"',
81
+ (unpacked_dir / "ppt" / "presentation.xml").read_text(encoding="utf-8")
82
+ if (unpacked_dir / "ppt" / "presentation.xml").exists()
83
+ else "",
84
+ )
85
+ if listed:
86
+ raise RefusedToClean(
87
+ f"<p:sldIdLst> lists {len(listed)} slide(s) and none of the "
88
+ f"{len(on_disk)} slide(s) on disk match any of them. Refusing to "
89
+ f"delete them all — this is a parse failure, not an empty deck."
90
+ )
91
+
92
+ removed = []
93
+
94
+ for slide_file in on_disk:
95
+ if slide_file.name not in referenced_slides:
96
+ rel_path = slide_file.relative_to(unpacked_dir)
97
+ slide_file.unlink()
98
+ removed.append(str(rel_path))
99
+
100
+ rels_file = slides_rels_dir / f"{slide_file.name}.rels"
101
+ if rels_file.exists():
102
+ rels_file.unlink()
103
+ removed.append(str(rels_file.relative_to(unpacked_dir)))
104
+
105
+ if removed and pres_rels_path.exists():
106
+ rels_dom = defusedxml.minidom.parse(str(pres_rels_path))
107
+ source_part = rels_source_part(pres_rels_path, unpacked_dir)
108
+ changed = False
109
+
110
+ for rel in list(rels_dom.getElementsByTagName("Relationship")):
111
+ if rel.getAttribute("Type") != SLIDE_REL_TYPE:
112
+ continue
113
+ part = opc_target(
114
+ rel.getAttribute("Target"), source_part, rel.getAttribute("TargetMode")
115
+ )
116
+ if part is None:
117
+ continue
118
+ if posixpath.basename(part) not in referenced_slides:
119
+ if rel.parentNode:
120
+ rel.parentNode.removeChild(rel)
121
+ changed = True
122
+
123
+ if changed:
124
+ with open(pres_rels_path, "wb") as f:
125
+ f.write(rels_dom.toxml(encoding="utf-8"))
126
+
127
+ return removed
128
+
129
+
130
+ def remove_trash_directory(unpacked_dir: Path) -> list[str]:
131
+ trash_dir = unpacked_dir / "[trash]"
132
+ removed = []
133
+
134
+ if trash_dir.exists() and trash_dir.is_dir():
135
+ for file_path in trash_dir.iterdir():
136
+ if file_path.is_file():
137
+ rel_path = file_path.relative_to(unpacked_dir)
138
+ removed.append(str(rel_path))
139
+ file_path.unlink()
140
+ trash_dir.rmdir()
141
+
142
+ return removed
143
+
144
+
145
+ def _referenced_by(rels_files, unpacked_dir: Path) -> set:
146
+ referenced = set()
147
+
148
+ for rels_file in rels_files:
149
+ source_part = rels_source_part(rels_file, unpacked_dir)
150
+ dom = defusedxml.minidom.parse(str(rels_file))
151
+ for rel in dom.getElementsByTagName("Relationship"):
152
+ part = opc_target(
153
+ rel.getAttribute("Target"), source_part, rel.getAttribute("TargetMode")
154
+ )
155
+ if part is not None:
156
+ referenced.add(Path(part))
157
+
158
+ return referenced
159
+
160
+
161
+ def remove_orphaned_rels_files(unpacked_dir: Path) -> list[str]:
162
+ resource_dirs = ["charts", "diagrams", "drawings"]
163
+ removed = []
164
+
165
+ for dir_name in resource_dirs:
166
+ rels_dir = unpacked_dir / "ppt" / dir_name / "_rels"
167
+ if not rels_dir.exists():
168
+ continue
169
+
170
+ for rels_file in rels_dir.glob("*.rels"):
171
+ resource_file = rels_dir.parent / rels_file.name.replace(".rels", "")
172
+ if not resource_file.exists():
173
+ rels_file.unlink()
174
+ removed.append(str(rels_file.relative_to(unpacked_dir)))
175
+
176
+ return removed
177
+
178
+
179
+ def get_referenced_files(unpacked_dir: Path) -> set:
180
+ return _referenced_by(sorted(unpacked_dir.rglob("*.rels")), unpacked_dir)
181
+
182
+
183
+ def remove_orphaned_files(unpacked_dir: Path, referenced: set) -> list[str]:
184
+ resource_dirs = ["media", "embeddings", "charts", "diagrams", "tags", "drawings", "ink"]
185
+ removed = []
186
+
187
+ for dir_name in resource_dirs:
188
+ dir_path = unpacked_dir / "ppt" / dir_name
189
+ if not dir_path.exists():
190
+ continue
191
+
192
+ for file_path in dir_path.glob("*"):
193
+ if not file_path.is_file():
194
+ continue
195
+ rel_path = file_path.relative_to(unpacked_dir)
196
+ if rel_path not in referenced:
197
+ file_path.unlink()
198
+ removed.append(str(rel_path))
199
+
200
+ theme_dir = unpacked_dir / "ppt" / "theme"
201
+ if theme_dir.exists():
202
+ for file_path in theme_dir.glob("theme*.xml"):
203
+ rel_path = file_path.relative_to(unpacked_dir)
204
+ if rel_path not in referenced:
205
+ file_path.unlink()
206
+ removed.append(str(rel_path))
207
+ theme_rels = theme_dir / "_rels" / f"{file_path.name}.rels"
208
+ if theme_rels.exists():
209
+ theme_rels.unlink()
210
+ removed.append(str(theme_rels.relative_to(unpacked_dir)))
211
+
212
+ notes_dir = unpacked_dir / "ppt" / "notesSlides"
213
+ if notes_dir.exists():
214
+ for file_path in notes_dir.glob("*.xml"):
215
+ if not file_path.is_file():
216
+ continue
217
+ rel_path = file_path.relative_to(unpacked_dir)
218
+ if rel_path not in referenced:
219
+ file_path.unlink()
220
+ removed.append(str(rel_path))
221
+
222
+ notes_rels_dir = notes_dir / "_rels"
223
+ if notes_rels_dir.exists():
224
+ for file_path in notes_rels_dir.glob("*.rels"):
225
+ notes_file = notes_dir / file_path.name.replace(".rels", "")
226
+ if not notes_file.exists():
227
+ file_path.unlink()
228
+ removed.append(str(file_path.relative_to(unpacked_dir)))
229
+
230
+ return removed
231
+
232
+
233
+ def update_content_types(unpacked_dir: Path, removed_files: list[str]) -> None:
234
+ ct_path = unpacked_dir / "[Content_Types].xml"
235
+ if not ct_path.exists():
236
+ return
237
+
238
+ dom = defusedxml.minidom.parse(str(ct_path))
239
+ changed = False
240
+
241
+ for override in list(dom.getElementsByTagName("Override")):
242
+ part_name = override.getAttribute("PartName").lstrip("/")
243
+ if part_name in removed_files:
244
+ if override.parentNode:
245
+ override.parentNode.removeChild(override)
246
+ changed = True
247
+
248
+ if changed:
249
+ with open(ct_path, "wb") as f:
250
+ f.write(dom.toxml(encoding="utf-8"))
251
+
252
+
253
+ def clean_unused_files(unpacked_dir: Path) -> list[str]:
254
+ all_removed = []
255
+
256
+ if list(unpacked_dir.rglob("*.rels")) and not get_referenced_files(unpacked_dir):
257
+ raise RefusedToClean(
258
+ "no relationship in this package names a part we can resolve. "
259
+ "Refusing to treat every file as unreferenced."
260
+ )
261
+
262
+ slides_removed = remove_orphaned_slides(unpacked_dir)
263
+ all_removed.extend(slides_removed)
264
+
265
+ trash_removed = remove_trash_directory(unpacked_dir)
266
+ all_removed.extend(trash_removed)
267
+
268
+ while True:
269
+ removed_rels = remove_orphaned_rels_files(unpacked_dir)
270
+ referenced = get_referenced_files(unpacked_dir)
271
+ removed_files = remove_orphaned_files(unpacked_dir, referenced)
272
+
273
+ total_removed = removed_rels + removed_files
274
+ if not total_removed:
275
+ break
276
+
277
+ all_removed.extend(total_removed)
278
+
279
+ if all_removed:
280
+ update_content_types(unpacked_dir, all_removed)
281
+
282
+ return all_removed
283
+
284
+
285
+ if __name__ == "__main__":
286
+ if len(sys.argv) != 2:
287
+ print("Usage: python clean.py <unpacked_dir>", file=sys.stderr)
288
+ print("Example: python clean.py unpacked/", file=sys.stderr)
289
+ sys.exit(1)
290
+
291
+ unpacked_dir = Path(sys.argv[1])
292
+
293
+ if not unpacked_dir.exists():
294
+ print(f"Error: {unpacked_dir} not found", file=sys.stderr)
295
+ sys.exit(1)
296
+
297
+ try:
298
+ removed = clean_unused_files(unpacked_dir)
299
+ except (RefusedToClean, ValueError) as e:
300
+ print(f"Error: {e}", file=sys.stderr)
301
+ print("Nothing was deleted.", file=sys.stderr)
302
+ sys.exit(1)
303
+
304
+ if removed:
305
+ print(f"Removed {len(removed)} unreferenced files:")
306
+ for f in removed:
307
+ print(f" {f}")
308
+ else:
309
+ print("No unreferenced files found")
@@ -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