@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,22 @@
1
+ # Skill mechanics
2
+
3
+ The skill-specific branch of [`writing-for-agents`](SKILL.md): what changes when the document is a skill (frontmatter, the invocation choice, and router skills). Everything else about writing it is the universal reference in `SKILL.md`.
4
+
5
+ ## Invocation
6
+
7
+ Two choices, trading the two loads:
8
+
9
+ - A **model-invoked** skill keeps a `description`, so the agent can fire it autonomously, and other skills can reach it. You can still type its name: model-invocation always _includes_ user reach; a description only ever adds agent discovery, never removes the human's. The description is the skill's top-level context pointer, forced to stay loaded at all times: permanent context load in exchange for discoverability. A model-invoked skill whose content is all reference is also one home for shared reference: another skill can invoke it, so reference needed by several skills lives in one place. Mechanics: omit `disable-model-invocation`, and write a model-facing description carrying the trigger branches (the pointer-writing rules in `SKILL.md` apply in full).
10
+ - A **user-invoked** skill strips the description from the agent's reach: only the human typing its name can invoke it, and no other skill can. Zero context load, but it spends cognitive load: you are the index that must remember it exists. Mechanics: set `disable-model-invocation: true`; the `description` becomes human-facing: a one-line summary, trigger lists stripped.
11
+
12
+ Pick model-invocation only when the agent must reach the skill on its own, or another skill must. If it only ever fires by hand, make it user-invoked and pay no context load.
13
+
14
+ Shared reference that two user-invoked skills both need can live in neither: with no descriptions, neither can fire the other. Push it to a plain file outside the skill system: external reference any skill can point at.
15
+
16
+ ## Splitting by invocation
17
+
18
+ The invocation cut of splitting (the sequence cut lives in `SKILL.md`): split off a model-invoked skill when you have a distinct leading word that should trigger it on its own (a trigger word you actually use in your prompts), or another skill must reach it. You pay context load for the new always-loaded description, so that independent reach has to be worth it.
19
+
20
+ ## Router skills
21
+
22
+ When user-invoked skills multiply past what you can remember, that piled-up cognitive load is cured by a **router skill**: one user-invoked skill that names the others and when to reach for each, so the human has one skill to remember instead of many. It can only hint, never fire them: user-invoked skills have no description, so nothing but the human can reach them.
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: writing-for-agents
3
+ description: Create or edit skills, AGENTS.md, CLAUDE.md, and other instructions consumed by coding agents. Use when routing, context load, completion criteria, or instruction-file structure needs deliberate design.
4
+ ---
5
+
6
+ # Writing for Agents
7
+
8
+ Write instructions that change agent behavior with the least durable context and maintenance cost.
9
+
10
+ ## Structure
11
+
12
+ - Put the required action and completion condition first.
13
+ - Keep one behavior in one authoritative location. Link to stable reference material instead of copying volatile facts.
14
+ - Put universal steps in the main file; put rare branches and large reference material behind explicit pointers.
15
+ - Prefer observable instructions over generic advice such as “be thorough.”
16
+ - State positive target behavior. Use prohibitions only for hard safety boundaries.
17
+
18
+ ## Routing
19
+
20
+ A skill description is an always-loaded routing pointer. Give it one clear job and distinct trigger branches. Do not create another active skill for a synonym, output style, or small variation of an existing workflow.
21
+
22
+ Use model-invoked skills only when automatic discovery is worth their permanent context cost. Keep rare, manual, vendor-specific, or setup workflows outside the active skill directory.
23
+
24
+ ## Completion criteria
25
+
26
+ For each procedure, define what evidence proves completion, what must not change, and when to stop. Avoid prescribed tools or file paths unless they are stable requirements.
27
+
28
+ When editing a skill, preserve its unique recurring behavior, remove duplicated or contradictory rules, and add routing examples only where they disambiguate a nearby skill.
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "Writing for Agents"
3
+ short_description: "Write documents agents consume"
@@ -0,0 +1,30 @@
1
+ © 2025 Anthropic, PBC. All rights reserved.
2
+
3
+ LICENSE: Use of these materials (including all code, prompts, assets, files,
4
+ and other components of this Skill) is governed by your agreement with
5
+ Anthropic regarding use of Anthropic's services. If no separate agreement
6
+ exists, use is governed by Anthropic's Consumer Terms of Service or
7
+ Commercial Terms of Service, as applicable:
8
+ https://www.anthropic.com/legal/consumer-terms
9
+ https://www.anthropic.com/legal/commercial-terms
10
+ Your applicable agreement is referred to as the "Agreement." "Services" are
11
+ as defined in the Agreement.
12
+
13
+ ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the
14
+ contrary, users may not:
15
+
16
+ - Extract these materials from the Services or retain copies of these
17
+ materials outside the Services
18
+ - Reproduce or copy these materials, except for temporary copies created
19
+ automatically during authorized use of the Services
20
+ - Create derivative works based on these materials
21
+ - Distribute, sublicense, or transfer these materials to any third party
22
+ - Make, offer to sell, sell, or import any inventions embodied in these
23
+ materials
24
+ - Reverse engineer, decompile, or disassemble these materials
25
+
26
+ The receipt, viewing, or possession of these materials does not convey or
27
+ imply any license or right beyond those expressly granted above.
28
+
29
+ Anthropic retains all right, title, and interest in these materials,
30
+ including all copyrights, patents, and other intellectual property rights.
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: xlsx
3
+ description: "Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .xltx, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like \"the xlsx in my downloads\") — and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved."
4
+ license: Proprietary. LICENSE.txt has complete terms
5
+ ---
6
+
7
+ # XLSX creation, editing, and analysis
8
+
9
+ Read only the section for the requested task. Creation, model editing, and bulk conversion have different safety requirements.
10
+
11
+ | Task | Approach |
12
+ |---|---|
13
+ | **Create** or **edit** with formulas/formatting | `openpyxl` — see gotchas below |
14
+ | **Bulk data** in or out | `pandas` (`read_excel`, `to_excel`) |
15
+ | **Quick look** at a sheet | `markitdown file.xlsx` — `## SheetName` per sheet; reads `.xlsm` too. No cell coordinates, so don't plan edits from it |
16
+ | **Read** a model (formulas *and* values) | two `load_workbook` passes — see gotchas |
17
+
18
+ > `openpyxl`, `pandas`, and `markitdown` are preinstalled — do not run `pip install` first; write the script and import directly. Only if an import fails (or the `markitdown` command is missing): `pip install` the missing package.
19
+
20
+ > Script paths below are relative to this skill's directory.
21
+
22
+ ## Requirements for every output
23
+
24
+ - **Professional font** (Arial, Times New Roman) throughout, unless the user says otherwise.
25
+ - **Zero formula errors.** Never ship while `recalc.py` reports `errors_found`. If you think an error predates you, prove it: load the *original* with `data_only=True` and look at that cell. An error you introduced looks exactly like one you inherited.
26
+ - **Use formulas, never hardcoded results.** Write `sheet['B10'] = '=SUM(B2:B9)'`, not the Python-computed total. The sheet must recalculate when its inputs change.
27
+ - **Follow the user's spec literally.** Exact tab names, exact column headers, and the formula they spelled out. A redesign that computes something else fails, however elegant.
28
+ - **Document every assumption and hardcoded number** where the reader will see it — a cell comment, or an adjacent cell at a table's end. Cite a real source when one exists (`Source: Company 10-K, FY2024, Page 45, Revenue Note, [SEC EDGAR URL]`); when the number came from the user, say so plainly.
29
+ - **A workbook *you create* for someone to fill in** needs a short legend naming which cells to edit, and one example row of realistic values showing the expected format. Never add such a row to a file you were asked to edit.
30
+ - **Editing an existing file: match its conventions exactly.** They override every guideline here. Find its designated input cells first — a distinct font color, fill, or shading marks them — write only there, and leave every existing formula untouched.
31
+
32
+ ## Recalculate (mandatory whenever the file contains formulas)
33
+
34
+ openpyxl writes formulas as strings with **no cached values**. Until you recalculate, every
35
+ formula cell reads back as `None` to anything reading cached values — `pandas`,
36
+ `load_workbook(data_only=True)`, and most previewers.
37
+
38
+ ```bash
39
+ python scripts/recalc.py output.xlsx [timeout_seconds] # default 30
40
+ ```
41
+
42
+ LibreOffice computes every formula, the file is **rewritten in place**, and you get JSON:
43
+ `status` (`success` | `errors_found`), `total_formulas`, `total_errors`, and an
44
+ `error_summary` naming up to 100 cells per error type (`locations_truncated` says how many it
45
+ withheld — trust `total_errors`, not the length of the list). Fix what it names and run it
46
+ again. **JSON with an `error` key instead of a `status` means nothing was recalculated**, and
47
+ only that case exits non-zero — `errors_found` exits 0, so never treat a clean exit as a clean
48
+ workbook.
49
+
50
+ **A green recalc proves your formulas *evaluate*, not that they are *right*.** An off-by-one
51
+ range or a reference to the wrong row yields a clean, error-free file with wrong numbers.
52
+ Write 2–3 formulas first and check they pull the values you expect, before building out a grid.
53
+
54
+ **A workbook that links to another file loses those links** if you re-save it with openpyxl and
55
+ then recalculate. Such a formula reads `='[1]Returns Analysis'!$B$2` — the `[1]` is an index
56
+ into the workbook's external-reference list, naming a *separate file on disk*, not a sheet.
57
+ That file is rarely present here, so the cell's cached value is the only thing holding its
58
+ data. openpyxl strips that value on save; LibreOffice then has to resolve the reference for
59
+ real, fails, writes `#NAME?`, and deletes every link. `recalc.py` refuses to run in that state
60
+ — copy those cells' values out of the original before you save over them (`--force` overrides,
61
+ and accepts the loss).
62
+
63
+ ## Choosing formulas that survive verification
64
+
65
+ LibreOffice implements fewer functions than Excel, and one it cannot evaluate becomes a
66
+ literal `#NAME?` baked into the file you deliver.
67
+
68
+ - **Prefer Excel-2007-era functions** — `SUMIFS`, `INDEX`, `MATCH`, `IFERROR`, `SUMPRODUCT` — which need no prefix.
69
+ - **Six post-2007 functions work, but only with an `_xlfn.` prefix**, because openpyxl writes your formula into the XML verbatim and Excel stores post-2007 names prefixed (its UI hides the prefix): `_xlfn.TEXTJOIN`, `_xlfn.CONCAT`, `_xlfn.IFS`, `_xlfn.SWITCH`, `_xlfn.MAXIFS`, `_xlfn.MINIFS`. Written bare, each yields `#NAME?`.
70
+ - **Never use `XLOOKUP`, `XMATCH`, `SORT`, `FILTER`, `UNIQUE`, or `SEQUENCE`.** The runtime's LibreOffice cannot evaluate them under *any* prefix. Newer builds do evaluate them, but they are spilling array functions and an openpyxl-written file has no spill metadata, so only the top-left cell of the range gets a value — and `recalc.py` reports `total_errors: 0` on the truncated result. Use `INDEX`/`MATCH` for lookups, and sort, filter, and de-duplicate in Python before writing the cells.
71
+ - A formula LibreOffice could not parse is written back **lowercased** — a quick tell beside a `#NAME?`.
72
+
73
+ ## openpyxl gotchas
74
+
75
+ - **Reading a model takes two loads.** `data_only=True` yields cached values with the formulas gone; the default yields formula strings with no values. One pass cannot give you both.
76
+ - **`data_only=True` is destructive if you save.** That workbook has no formulas left, so saving replaces every one with a literal — permanently.
77
+ - **`data_only=True` on a file openpyxl just wrote returns `None` everywhere** — run `recalc.py` first. (A formula whose result is `""` also reads back as `None`.)
78
+ - **Merged cells: write the top-left anchor only.** Every other cell in the range is a `MergedCell` whose `.value` is read-only.
79
+ - **`.xlsm` loses its macros unless you pass `keep_vba=True`** to `load_workbook`.
80
+ - **A sheet name containing a space must be quoted** in a cross-sheet reference: `='Assumptions Inputs'!$B$5`. Unquoted, it evaluates to `#VALUE!`.
81
+
82
+ ## Financial models
83
+
84
+ Unless the user says otherwise, or the existing file already does something else.
85
+
86
+ **Color:** blue text (`0,0,255`) for hardcoded inputs and scenario levers · black for formulas ·
87
+ green (`0,128,0`) for links to another sheet · red (`255,0,0`) for links to another file ·
88
+ yellow fill (`255,255,0`) for key assumptions and cells the user should fill in.
89
+
90
+ **Numbers:** currency `$#,##0`, with the unit named in the header (`Revenue ($mm)`) · zeros
91
+ render as `-`, including in percentages (`$#,##0;($#,##0);-`) · negatives in parentheses ·
92
+ percentages `0.0%`, **stored as fractions** (`0.15` renders `15.0%`; storing `15` renders
93
+ `1500.0%`) · valuation multiples `0.0x` · years as text (`"2024"`, never `2,024`).
94
+
95
+ **Structure:** every assumption in its own labeled cell, referenced by the formulas that use it
96
+ (`=B5*(1+$B$6)`, never `=B5*1.05`) · formulas consistent across every projection period, since a
97
+ lone edited cell mid-row is the commonest silent error · guard denominators that can be zero.
98
+
99
+ ## Dependencies
100
+
101
+ `openpyxl`, `pandas`, `markitdown` (pip, preinstalled — install only if an import fails or the command is missing) · LibreOffice (`soffice`, auto-configured for sandboxed environments via `scripts/office/soffice.py`)
@@ -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
@@ -0,0 +1,114 @@
1
+ """Find masters sharing a theme part in the way PowerPoint refuses to open.
2
+
3
+ Reports only; the fix is to move <p:notesMasterIdLst> back to directly after
4
+ <p:sldIdLst> in ppt/presentation.xml.
5
+ """
6
+
7
+
8
+ from __future__ import annotations
9
+
10
+ import posixpath
11
+ import re
12
+ from typing import Mapping
13
+
14
+ from . import part_text
15
+
16
+ THEME_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"
17
+
18
+ _MASTER_RE = re.compile(
19
+ r"^ppt/(?P<group>slideMasters|notesMasters|handoutMasters)/"
20
+ r"(?:slide|notes|handout)Master(?P<num>\d+)\.xml$"
21
+ )
22
+ _GROUP_ORDER = {"slideMasters": 0, "notesMasters": 1, "handoutMasters": 2}
23
+
24
+ _RELATIONSHIP_RE = re.compile(
25
+ r"<Relationship\b[^>]*?(?:/>|>.*?</Relationship\s*>)", re.DOTALL
26
+ )
27
+
28
+
29
+ def _sort_key(name: str) -> tuple[int, int]:
30
+ m = _MASTER_RE.match(name)
31
+ assert m is not None
32
+ return (_GROUP_ORDER[m.group("group")], int(m.group("num")))
33
+
34
+
35
+ def _rels_path(part: str) -> str:
36
+ directory, base = posixpath.split(part)
37
+ return f"{directory}/_rels/{base}.rels"
38
+
39
+
40
+ def _resolve(rels_path: str, target: str) -> str:
41
+ if target.startswith("/"):
42
+ return target.lstrip("/")
43
+ part_dir = posixpath.dirname(posixpath.dirname(rels_path))
44
+ return posixpath.normpath(posixpath.join(part_dir, target))
45
+
46
+
47
+ def _theme_rel(files: Mapping[str, bytes], master: str):
48
+ rels_path = _rels_path(master)
49
+ rels = files.get(rels_path)
50
+ if rels is None:
51
+ return None
52
+ for element in _RELATIONSHIP_RE.findall(part_text(rels)):
53
+ if f'Type="{THEME_REL_TYPE}"' not in element:
54
+ continue
55
+ target = re.search(r'\bTarget="([^"]+)"', element)
56
+ if target is None:
57
+ continue
58
+ return rels_path, element, _resolve(rels_path, target.group(1))
59
+ return None
60
+
61
+
62
+ def _masters(files: Mapping[str, bytes]) -> list[str]:
63
+ return sorted((n for n in files if _MASTER_RE.match(n)), key=_sort_key)
64
+
65
+
66
+ _PRESENTATION = "ppt/presentation.xml"
67
+ _NOTES_MASTERS = "ppt/notesMasters/"
68
+ _IGNORABLE_RE = re.compile(r"<!--.*?-->|<\?.*?\?>", re.DOTALL)
69
+ _AFTER_SLDIDLST_RE = re.compile(
70
+ r"<p:sldIdLst\b(?:[^>]*/>|[^>]*>.*?</p:sldIdLst\s*>)\s*(<[^>\s/]+)", re.DOTALL
71
+ )
72
+
73
+
74
+ def _notes_master_share_is_inert(files: Mapping[str, bytes]) -> bool:
75
+ data = files.get(_PRESENTATION)
76
+ if data is None:
77
+ return False
78
+ match = _AFTER_SLDIDLST_RE.search(_IGNORABLE_RE.sub("", part_text(data)))
79
+ return match is not None and match.group(1) == "<p:notesMasterIdLst"
80
+
81
+
82
+ def _shares(files: Mapping[str, bytes]):
83
+ owner: dict[str, str] = {}
84
+ for master in _masters(files):
85
+ found = _theme_rel(files, master)
86
+ if found is None:
87
+ continue
88
+ rels_path, element, theme = found
89
+ if theme not in files:
90
+ continue
91
+ if theme in owner:
92
+ yield master, rels_path, element, theme, owner[theme]
93
+ else:
94
+ owner[theme] = master
95
+
96
+
97
+ def _is_inert(master: str, inert_notes: bool) -> bool:
98
+ return inert_notes and master.startswith(_NOTES_MASTERS)
99
+
100
+
101
+ def find_shared_master_themes(files: Mapping[str, bytes]) -> list[str]:
102
+ return [
103
+ f"{master} shares {theme} with {first}"
104
+ for master, _, _, theme, first in _shares(files)
105
+ ]
106
+
107
+
108
+ def live_shared_master_themes(files: Mapping[str, bytes]) -> list[str]:
109
+ inert_notes = _notes_master_share_is_inert(files)
110
+ return [
111
+ f"{master} shares {theme} with {first}"
112
+ for master, _, _, theme, first in _shares(files)
113
+ if not _is_inert(master, inert_notes)
114
+ ]