@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,299 @@
1
+ """
2
+ Validator for tracked changes in Word documents.
3
+
4
+ Detects untracked edits in word/document.xml: text that differs from the
5
+ original without a <w:ins>/<w:del> wrapper recording it. The tracked changes
6
+ that are new relative to the original are undone, and the result is compared
7
+ against the original; whatever text still differs was edited without being
8
+ tracked.
9
+
10
+ Only the document body is compared. Headers, footers, footnotes and endnotes
11
+ are separate parts and are not checked.
12
+ """
13
+
14
+ import subprocess
15
+ import tempfile
16
+ import zipfile
17
+ from pathlib import Path
18
+
19
+ import defusedxml.ElementTree as ET
20
+ from defusedxml.common import DefusedXmlException
21
+
22
+ from helpers import rendered_text, safe_extract
23
+
24
+
25
+ class RedliningValidator:
26
+
27
+ def __init__(self, unpacked_dir, original_docx, verbose=False):
28
+ self.unpacked_dir = Path(unpacked_dir)
29
+ self.original_docx = Path(original_docx)
30
+ self.verbose = verbose
31
+ self.namespaces = {
32
+ "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
33
+ }
34
+
35
+ def repair(self) -> int:
36
+ return 0
37
+
38
+ def validate(self):
39
+ modified_file = self.unpacked_dir / "word" / "document.xml"
40
+ if not modified_file.exists():
41
+ print(f"FAILED - Modified document.xml not found at {modified_file}")
42
+ return False
43
+
44
+ with tempfile.TemporaryDirectory() as temp_dir:
45
+ temp_path = Path(temp_dir)
46
+
47
+ try:
48
+ with zipfile.ZipFile(self.original_docx, "r") as zip_ref:
49
+ safe_extract(zip_ref, temp_path)
50
+ except Exception as e:
51
+ print(f"FAILED - Error unpacking original docx: {e}")
52
+ return False
53
+
54
+ original_file = temp_path / "word" / "document.xml"
55
+ if not original_file.exists():
56
+ print(
57
+ f"FAILED - Original document.xml not found in {self.original_docx}"
58
+ )
59
+ return False
60
+
61
+ try:
62
+ modified_tree = ET.parse(modified_file)
63
+ modified_root = modified_tree.getroot()
64
+ original_tree = ET.parse(original_file)
65
+ original_root = original_tree.getroot()
66
+ except (ET.ParseError, DefusedXmlException) as e:
67
+ print(f"FAILED - Error parsing XML files: {e}")
68
+ return False
69
+
70
+ new_changes = self._new_tracked_changes(original_root, modified_root)
71
+ self._remove_tracked_changes(modified_root, new_changes)
72
+
73
+ modified_text = self._extract_text_content(modified_root)
74
+ original_text = self._extract_text_content(original_root)
75
+
76
+ if modified_text != original_text:
77
+ error_message = self._generate_detailed_diff(
78
+ original_text, modified_text
79
+ )
80
+ print(error_message)
81
+ return False
82
+
83
+ if self.verbose:
84
+ print(
85
+ f"PASSED - All {len(new_changes)} change(s) against the original "
86
+ "are properly tracked"
87
+ )
88
+ return True
89
+
90
+ def _tracked_change_elements(self, root):
91
+ ins_tag = f"{{{self.namespaces['w']}}}ins"
92
+ del_tag = f"{{{self.namespaces['w']}}}del"
93
+ return [elem for elem in root.iter() if elem.tag in (ins_tag, del_tag)]
94
+
95
+ def _rendered_text(self, elem):
96
+ preserve = elem.get("{http://www.w3.org/XML/1998/namespace}space") == "preserve"
97
+ return rendered_text(elem.text or "", preserve)
98
+
99
+ def _text_elements(self, elem):
100
+ w = self.namespaces["w"]
101
+ return [
102
+ node
103
+ for node in elem.iter()
104
+ if node.tag in (f"{{{w}}}t", f"{{{w}}}delText")
105
+ ]
106
+
107
+ def _tracked_change_key(self, elem):
108
+ w = self.namespaces["w"]
109
+ text = "".join(self._rendered_text(node) for node in self._text_elements(elem))
110
+ return (elem.tag, elem.get(f"{{{w}}}author"), elem.get(f"{{{w}}}date"), text)
111
+
112
+ def _new_tracked_changes(self, original_root, modified_root):
113
+ original = self._tracked_change_elements(original_root)
114
+ modified = self._tracked_change_elements(modified_root)
115
+
116
+ pool = {}
117
+ for elem in original:
118
+ pool.setdefault(self._tracked_change_key(elem), []).append(elem)
119
+
120
+ matched, leftover = set(), []
121
+ for elem in modified:
122
+ bucket = pool.get(self._tracked_change_key(elem))
123
+ if bucket:
124
+ matched.add(bucket.pop())
125
+ else:
126
+ leftover.append(elem)
127
+
128
+ def group(elem):
129
+ return self._tracked_change_key(elem)[:3]
130
+
131
+ def text_of(elems):
132
+ return "".join(self._tracked_change_key(e)[3] for e in elems)
133
+
134
+ unmatched_original = {}
135
+ for elem in original:
136
+ if elem not in matched:
137
+ unmatched_original.setdefault(group(elem), []).append(elem)
138
+
139
+ by_group = {}
140
+ for elem in leftover:
141
+ by_group.setdefault(group(elem), []).append(elem)
142
+
143
+ new = set()
144
+ for key, elems in by_group.items():
145
+ rebuilt = text_of(elems)
146
+ if rebuilt and rebuilt == text_of(unmatched_original.get(key, [])):
147
+ continue
148
+ new.update(elems)
149
+ return new
150
+
151
+ def _generate_detailed_diff(self, original_text, modified_text):
152
+ error_parts = [
153
+ "FAILED - Document text doesn't match after removing the tracked changes",
154
+ "",
155
+ "Likely causes:",
156
+ " 1. Modified text inside another author's <w:ins> or <w:del> tags",
157
+ " 2. Made edits without proper tracked changes",
158
+ " 3. Didn't nest <w:del> inside <w:ins> when deleting another's insertion",
159
+ " 4. Rewrote another author's <w:ins>/<w:del> and changed its text on",
160
+ " the way. A tracked change from the original is recognised by its",
161
+ " author, date and text; anything that doesn't reproduce one exactly",
162
+ " reads as new, and the text it carried is reported missing.",
163
+ "",
164
+ "For pre-redlined documents, use correct patterns:",
165
+ " - To reject another's INSERTION: Nest <w:del> inside their <w:ins>",
166
+ " - To reject PART of one: nest <w:del> around only the runs you reject.",
167
+ " Their <w:ins> may be split around it, so long as the pieces keep",
168
+ " their author and date and still spell out the same text.",
169
+ " - To restore another's DELETION: Add new <w:ins> AFTER their <w:del>",
170
+ "",
171
+ ]
172
+
173
+ git_diff = self._get_git_word_diff(original_text, modified_text)
174
+ if git_diff:
175
+ error_parts.extend(["Differences:", "============", git_diff])
176
+ else:
177
+ error_parts.append("Unable to generate word diff (git not available)")
178
+
179
+ return "\n".join(error_parts)
180
+
181
+ def _get_git_word_diff(self, original_text, modified_text):
182
+ try:
183
+ with tempfile.TemporaryDirectory() as temp_dir:
184
+ temp_path = Path(temp_dir)
185
+
186
+ original_file = temp_path / "original.txt"
187
+ modified_file = temp_path / "modified.txt"
188
+
189
+ original_file.write_text(original_text, encoding="utf-8")
190
+ modified_file.write_text(modified_text, encoding="utf-8")
191
+
192
+ result = subprocess.run(
193
+ [
194
+ "git",
195
+ "diff",
196
+ "--word-diff=plain",
197
+ "--word-diff-regex=.",
198
+ "-U0",
199
+ "--no-index",
200
+ str(original_file),
201
+ str(modified_file),
202
+ ],
203
+ capture_output=True,
204
+ text=True,
205
+ )
206
+
207
+ if result.stdout.strip():
208
+ lines = result.stdout.split("\n")
209
+ content_lines = []
210
+ in_content = False
211
+ for line in lines:
212
+ if line.startswith("@@"):
213
+ in_content = True
214
+ continue
215
+ if in_content and line.strip():
216
+ content_lines.append(line)
217
+
218
+ if content_lines:
219
+ return "\n".join(content_lines)
220
+
221
+ result = subprocess.run(
222
+ [
223
+ "git",
224
+ "diff",
225
+ "--word-diff=plain",
226
+ "-U0",
227
+ "--no-index",
228
+ str(original_file),
229
+ str(modified_file),
230
+ ],
231
+ capture_output=True,
232
+ text=True,
233
+ )
234
+
235
+ if result.stdout.strip():
236
+ lines = result.stdout.split("\n")
237
+ content_lines = []
238
+ in_content = False
239
+ for line in lines:
240
+ if line.startswith("@@"):
241
+ in_content = True
242
+ continue
243
+ if in_content and line.strip():
244
+ content_lines.append(line)
245
+ return "\n".join(content_lines)
246
+
247
+ except (subprocess.CalledProcessError, FileNotFoundError, Exception):
248
+ pass
249
+
250
+ return None
251
+
252
+ def _remove_tracked_changes(self, root, targets):
253
+ ins_tag = f"{{{self.namespaces['w']}}}ins"
254
+ del_tag = f"{{{self.namespaces['w']}}}del"
255
+
256
+ for parent in root.iter():
257
+ to_remove = []
258
+ for child in parent:
259
+ if child.tag == ins_tag and child in targets:
260
+ to_remove.append(child)
261
+ for elem in to_remove:
262
+ parent.remove(elem)
263
+
264
+ deltext_tag = f"{{{self.namespaces['w']}}}delText"
265
+ t_tag = f"{{{self.namespaces['w']}}}t"
266
+
267
+ for parent in root.iter():
268
+ to_process = []
269
+ for child in parent:
270
+ if child.tag == del_tag and child in targets:
271
+ to_process.append((child, list(parent).index(child)))
272
+
273
+ for del_elem, del_index in reversed(to_process):
274
+ for elem in del_elem.iter():
275
+ if elem.tag == deltext_tag:
276
+ elem.tag = t_tag
277
+
278
+ for child in reversed(list(del_elem)):
279
+ parent.insert(del_index, child)
280
+ parent.remove(del_elem)
281
+
282
+ def _extract_text_content(self, root):
283
+ p_tag = f"{{{self.namespaces['w']}}}p"
284
+ t_tag = f"{{{self.namespaces['w']}}}t"
285
+
286
+ paragraphs = []
287
+ for p_elem in root.findall(f".//{p_tag}"):
288
+ text_parts = []
289
+ for t_elem in p_elem.findall(f".//{t_tag}"):
290
+ text_parts.append(self._rendered_text(t_elem))
291
+ paragraph_text = "".join(text_parts)
292
+ if paragraph_text:
293
+ paragraphs.append(paragraph_text)
294
+
295
+ return "\n".join(paragraphs)
296
+
297
+
298
+ if __name__ == "__main__":
299
+ raise RuntimeError("This module should not be run directly.")
@@ -0,0 +1,311 @@
1
+ """Create thumbnail grids from PowerPoint presentation slides.
2
+
3
+ Creates a grid layout of slide thumbnails for quick visual analysis.
4
+ Labels each thumbnail with its XML filename (e.g., slide1.xml).
5
+ Hidden slides are shown with a placeholder pattern.
6
+
7
+ Usage:
8
+ python thumbnail.py input.pptx [output_prefix] [--cols N]
9
+
10
+ Examples:
11
+ python thumbnail.py presentation.pptx
12
+ # Creates: thumbnails.jpg
13
+
14
+ python thumbnail.py template.pptx grid --cols 4
15
+ # Creates: grid.jpg (or grid-1.jpg, grid-2.jpg for large decks)
16
+ """
17
+
18
+ import argparse
19
+ import posixpath
20
+ import subprocess
21
+ import sys
22
+ import tempfile
23
+ import zipfile
24
+ from pathlib import Path
25
+
26
+ import defusedxml.minidom
27
+ from defusedxml import ElementTree
28
+ from office.helpers import SLIDE_REL_TYPE, opc_target
29
+ from office.soffice import run_soffice
30
+ from PIL import Image, ImageDraw, ImageFont
31
+
32
+
33
+ THUMBNAIL_WIDTH = 300
34
+ CONVERSION_DPI = 100
35
+ MAX_COLS = 6
36
+ DEFAULT_COLS = 3
37
+ JPEG_QUALITY = 95
38
+ GRID_PADDING = 20
39
+ BORDER_WIDTH = 2
40
+ FONT_SIZE_RATIO = 0.10
41
+ LABEL_PADDING_RATIO = 0.4
42
+
43
+
44
+ def main():
45
+ parser = argparse.ArgumentParser(
46
+ description="Create thumbnail grids from PowerPoint slides."
47
+ )
48
+ parser.add_argument("input", help="Input PowerPoint file (.pptx)")
49
+ parser.add_argument(
50
+ "output_prefix",
51
+ nargs="?",
52
+ default="thumbnails",
53
+ help="Output prefix for image files (default: thumbnails)",
54
+ )
55
+ parser.add_argument(
56
+ "--cols",
57
+ type=int,
58
+ default=DEFAULT_COLS,
59
+ help=f"Number of columns (default: {DEFAULT_COLS}, max: {MAX_COLS})",
60
+ )
61
+
62
+ args = parser.parse_args()
63
+
64
+ cols = min(args.cols, MAX_COLS)
65
+ if args.cols > MAX_COLS:
66
+ print(f"Warning: Columns limited to {MAX_COLS}")
67
+
68
+ input_path = Path(args.input)
69
+ if not input_path.exists() or input_path.suffix.lower() != ".pptx":
70
+ print(f"Error: Invalid PowerPoint file: {args.input}", file=sys.stderr)
71
+ sys.exit(1)
72
+
73
+ output_path = Path(f"{args.output_prefix}.jpg")
74
+
75
+ try:
76
+ slide_info = get_slide_info(input_path)
77
+
78
+ with tempfile.TemporaryDirectory() as temp_dir:
79
+ temp_path = Path(temp_dir)
80
+ visible_images = convert_to_images(input_path, temp_path)
81
+
82
+ if not visible_images and not any(s["hidden"] for s in slide_info):
83
+ print("Error: No slides found", file=sys.stderr)
84
+ sys.exit(1)
85
+
86
+ slides = build_slide_list(slide_info, visible_images, temp_path)
87
+
88
+ grid_files = create_grids(slides, cols, THUMBNAIL_WIDTH, output_path)
89
+
90
+ print(f"Created {len(grid_files)} grid(s):")
91
+ for grid_file in grid_files:
92
+ print(f" {grid_file}")
93
+
94
+ except Exception as e:
95
+ print(f"Error: {e}", file=sys.stderr)
96
+ sys.exit(1)
97
+
98
+
99
+ def _is_hidden(zf: zipfile.ZipFile, part: str) -> bool:
100
+ try:
101
+ with zf.open(part) as f:
102
+ for _, root in ElementTree.iterparse(f, events=("start",)):
103
+ return root.get("show") in ("0", "false")
104
+ except (KeyError, ElementTree.ParseError):
105
+ return False
106
+ return False
107
+
108
+
109
+ def get_slide_info(pptx_path: Path) -> list[dict]:
110
+ with zipfile.ZipFile(pptx_path, "r") as zf:
111
+ rels_content = zf.read("ppt/_rels/presentation.xml.rels").decode("utf-8")
112
+ rels_dom = defusedxml.minidom.parseString(rels_content)
113
+
114
+ rid_to_part = {}
115
+ for rel in rels_dom.getElementsByTagName("Relationship"):
116
+ if rel.getAttribute("Type") != SLIDE_REL_TYPE:
117
+ continue
118
+ part = opc_target(
119
+ rel.getAttribute("Target"),
120
+ "ppt/presentation.xml",
121
+ rel.getAttribute("TargetMode"),
122
+ )
123
+ if part is not None:
124
+ rid_to_part[rel.getAttribute("Id")] = part
125
+
126
+ pres_content = zf.read("ppt/presentation.xml").decode("utf-8")
127
+ pres_dom = defusedxml.minidom.parseString(pres_content)
128
+
129
+ present = set(zf.namelist())
130
+
131
+ slides = []
132
+ for sld_id in pres_dom.getElementsByTagName("p:sldId"):
133
+ part = rid_to_part.get(sld_id.getAttribute("r:id"))
134
+ if part is not None and part in present:
135
+ slides.append(
136
+ {"name": posixpath.basename(part), "hidden": _is_hidden(zf, part)}
137
+ )
138
+
139
+ return slides
140
+
141
+
142
+ def build_slide_list(
143
+ slide_info: list[dict],
144
+ visible_images: list[Path],
145
+ temp_dir: Path,
146
+ ) -> list[tuple[Path, str]]:
147
+ visible_count = sum(1 for info in slide_info if not info["hidden"])
148
+ rendered_hidden = len(visible_images) == len(slide_info) != visible_count
149
+
150
+ if not rendered_hidden and visible_count != len(visible_images):
151
+ raise ValueError(
152
+ f"LibreOffice rendered {len(visible_images)} page(s) for {visible_count} "
153
+ f"visible slide(s) of {len(slide_info)}; thumbnails would be mislabeled"
154
+ )
155
+
156
+ if visible_images:
157
+ with Image.open(visible_images[0]) as img:
158
+ placeholder_size = img.size
159
+ else:
160
+ placeholder_size = (1920, 1080)
161
+
162
+ slides = []
163
+ visible_idx = 0
164
+
165
+ for info in slide_info:
166
+ if info["hidden"] and not rendered_hidden:
167
+ placeholder_path = temp_dir / f"hidden-{info['name']}.jpg"
168
+ placeholder_img = create_hidden_placeholder(placeholder_size)
169
+ placeholder_img.save(placeholder_path, "JPEG")
170
+ slides.append((placeholder_path, f"{info['name']} (hidden)"))
171
+ else:
172
+ label = f"{info['name']} (hidden)" if info["hidden"] else info["name"]
173
+ slides.append((visible_images[visible_idx], label))
174
+ visible_idx += 1
175
+
176
+ return slides
177
+
178
+
179
+ def create_hidden_placeholder(size: tuple[int, int]) -> Image.Image:
180
+ img = Image.new("RGB", size, color="#F0F0F0")
181
+ draw = ImageDraw.Draw(img)
182
+ line_width = max(5, min(size) // 100)
183
+ draw.line([(0, 0), size], fill="#CCCCCC", width=line_width)
184
+ draw.line([(size[0], 0), (0, size[1])], fill="#CCCCCC", width=line_width)
185
+ return img
186
+
187
+
188
+ def convert_to_images(pptx_path: Path, temp_dir: Path) -> list[Path]:
189
+ pdf_path = temp_dir / f"{pptx_path.stem}.pdf"
190
+
191
+ result = run_soffice(
192
+ ["--headless", "--convert-to", "pdf", "--outdir", str(temp_dir), str(pptx_path)],
193
+ capture_output=True,
194
+ text=True,
195
+ )
196
+ if result.returncode != 0 or not pdf_path.exists():
197
+ detail = (result.stderr or result.stdout or "").strip()
198
+ raise RuntimeError(f"PDF conversion failed: {detail}" if detail else "PDF conversion failed")
199
+
200
+ result = subprocess.run(
201
+ [
202
+ "pdftoppm",
203
+ "-jpeg",
204
+ "-r",
205
+ str(CONVERSION_DPI),
206
+ str(pdf_path),
207
+ str(temp_dir / "slide"),
208
+ ],
209
+ capture_output=True,
210
+ text=True,
211
+ )
212
+ if result.returncode != 0:
213
+ raise RuntimeError("Image conversion failed")
214
+
215
+ return sorted(temp_dir.glob("slide-*.jpg"))
216
+
217
+
218
+ def create_grids(
219
+ slides: list[tuple[Path, str]],
220
+ cols: int,
221
+ width: int,
222
+ output_path: Path,
223
+ ) -> list[str]:
224
+ max_per_grid = cols * (cols + 1)
225
+ grid_files = []
226
+
227
+ for chunk_idx, start_idx in enumerate(range(0, len(slides), max_per_grid)):
228
+ end_idx = min(start_idx + max_per_grid, len(slides))
229
+ chunk_slides = slides[start_idx:end_idx]
230
+
231
+ grid = create_grid(chunk_slides, cols, width)
232
+
233
+ if len(slides) <= max_per_grid:
234
+ grid_filename = output_path
235
+ else:
236
+ stem = output_path.stem
237
+ suffix = output_path.suffix
238
+ grid_filename = output_path.parent / f"{stem}-{chunk_idx + 1}{suffix}"
239
+
240
+ grid_filename.parent.mkdir(parents=True, exist_ok=True)
241
+ grid.save(str(grid_filename), quality=JPEG_QUALITY)
242
+ grid_files.append(str(grid_filename))
243
+
244
+ return grid_files
245
+
246
+
247
+ def create_grid(
248
+ slides: list[tuple[Path, str]],
249
+ cols: int,
250
+ width: int,
251
+ ) -> Image.Image:
252
+ font_size = int(width * FONT_SIZE_RATIO)
253
+ label_padding = int(font_size * LABEL_PADDING_RATIO)
254
+
255
+ with Image.open(slides[0][0]) as img:
256
+ aspect = img.height / img.width
257
+ height = int(width * aspect)
258
+
259
+ rows = (len(slides) + cols - 1) // cols
260
+ grid_w = cols * width + (cols + 1) * GRID_PADDING
261
+ grid_h = rows * (height + font_size + label_padding * 2) + (rows + 1) * GRID_PADDING
262
+
263
+ grid = Image.new("RGB", (grid_w, grid_h), "white")
264
+ draw = ImageDraw.Draw(grid)
265
+
266
+ try:
267
+ font = ImageFont.load_default(size=font_size)
268
+ except Exception:
269
+ font = ImageFont.load_default()
270
+
271
+ for i, (img_path, slide_name) in enumerate(slides):
272
+ row, col = i // cols, i % cols
273
+ x = col * width + (col + 1) * GRID_PADDING
274
+ y_base = (
275
+ row * (height + font_size + label_padding * 2) + (row + 1) * GRID_PADDING
276
+ )
277
+
278
+ label = slide_name
279
+ bbox = draw.textbbox((0, 0), label, font=font)
280
+ text_w = bbox[2] - bbox[0]
281
+ draw.text(
282
+ (x + (width - text_w) // 2, y_base + label_padding),
283
+ label,
284
+ fill="black",
285
+ font=font,
286
+ )
287
+
288
+ y_thumbnail = y_base + label_padding + font_size + label_padding
289
+
290
+ with Image.open(img_path) as img:
291
+ img.thumbnail((width, height), Image.Resampling.LANCZOS)
292
+ w, h = img.size
293
+ tx = x + (width - w) // 2
294
+ ty = y_thumbnail + (height - h) // 2
295
+ grid.paste(img, (tx, ty))
296
+
297
+ if BORDER_WIDTH > 0:
298
+ draw.rectangle(
299
+ [
300
+ (tx - BORDER_WIDTH, ty - BORDER_WIDTH),
301
+ (tx + w + BORDER_WIDTH - 1, ty + h + BORDER_WIDTH - 1),
302
+ ],
303
+ outline="gray",
304
+ width=BORDER_WIDTH,
305
+ )
306
+
307
+ return grid
308
+
309
+
310
+ if __name__ == "__main__":
311
+ main()
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: safe-refactor
3
+ description: Restructure code while preserving behavior. Use for extraction, consolidation, ownership moves, or cleanup where verification must bracket structural edits.
4
+ ---
5
+
6
+ # Safe refactor
7
+
8
+ Define behavior-preservation boundary and establish verification before structural edits.
9
+
10
+ - Keep feature changes outside refactor.
11
+ - Move one ownership boundary at a time.
12
+ - Preserve public interfaces, failure behavior, ordering, and compatibility unless explicitly scoped.
13
+ - Keep intermediate states buildable and testable.
14
+ - Avoid dependency or configuration growth without correctness need.
15
+
16
+ Run same proof after change. Stop when behavior matches and requested structure is achieved.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Safe Refactor"
3
+ short_description: "Preserve behavior through structural change"
4
+ default_prompt: "Use $safe-refactor to restructure this code while preserving behavior."
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: tdd
3
+ description: Use for test-first feature or bug work, explicit red-green-refactor requests, or when the user asks to design tests before implementation. Do not use merely because a change needs ordinary regression coverage.
4
+ ---
5
+
6
+ # Test-Driven Development
7
+
8
+ Use short vertical slices: one observable behavior, one failing test, the minimum implementation, then the next behavior.
9
+
10
+ 1. Read relevant project terminology, ADRs, and existing test conventions.
11
+ 2. Choose the highest practical public seam. Confirm the seam with the user only when it is consequential or ambiguous; otherwise state the choice and proceed.
12
+ 3. Write a test that fails for the requested behavior and derives expected values from the spec or a known-good example.
13
+ 4. Add only enough implementation to make that test pass.
14
+ 5. Repeat for the next behavior. Refactor separately once behavior is covered.
15
+
16
+ Tests should verify externally observable behavior, survive internal refactors, and avoid tautological expectations. Mock only true system boundaries; prefer real local implementations or test databases when practical.
17
+
18
+ Do not write a broad speculative test suite before learning from the first slice. Run focused tests after each slice and the relevant wider gate before completion.
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "TDD"
3
+ short_description: "Test-driven red-green-refactor"