@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.
- package/.github/workflows/npm-publish.yml +63 -0
- package/AGENTS.md +58 -0
- package/PERMISSION_MODES_PLAN.md +279 -0
- package/README.md +207 -0
- package/WHAT_YOU_GET.md +153 -0
- package/eval/README.md +72 -0
- package/eval/permission-cases.json +28 -0
- package/eval/permissions.mjs +103 -0
- package/extensions/ask-question.ts +111 -0
- package/extensions/background-tasks.ts +433 -0
- package/extensions/background-worker.ts +50 -0
- package/extensions/glob.ts +32 -0
- package/extensions/mode.ts +397 -0
- package/extensions/ui.ts +134 -0
- package/lib/bash-approvals.ts +113 -0
- package/lib/bash-policy.ts +164 -0
- package/lib/mode-policy.ts +174 -0
- package/lib/path-policy.ts +182 -0
- package/lib/permission-classifier.ts +128 -0
- package/lib/permission-config.ts +91 -0
- package/lib/permission-denials.ts +32 -0
- package/lib/permission-intent.ts +128 -0
- package/lib/permission-taxonomy.ts +15 -0
- package/lib/task-broker.ts +65 -0
- package/lib/task-checkout.ts +39 -0
- package/lib/task-git.ts +44 -0
- package/lib/task-patch.ts +106 -0
- package/lib/task-permissions.ts +51 -0
- package/lib/task-report.ts +62 -0
- package/lib/task-worktree-policy.ts +56 -0
- package/package.json +51 -0
- package/prompts/commit.md +7 -0
- package/prompts/init.md +66 -0
- package/prompts/summary.md +14 -0
- package/skills/claude-api/LICENSE.txt +202 -0
- package/skills/claude-api/SKILL.md +572 -0
- package/skills/claude-api/csharp/claude-api/README.md +362 -0
- package/skills/claude-api/csharp/claude-api/batches.md +14 -0
- package/skills/claude-api/csharp/claude-api/files-api.md +25 -0
- package/skills/claude-api/csharp/claude-api/streaming.md +28 -0
- package/skills/claude-api/csharp/claude-api/tool-use.md +164 -0
- package/skills/claude-api/curl/examples.md +256 -0
- package/skills/claude-api/curl/managed-agents.md +371 -0
- package/skills/claude-api/go/claude-api/README.md +188 -0
- package/skills/claude-api/go/claude-api/files-api.md +23 -0
- package/skills/claude-api/go/claude-api/streaming.md +43 -0
- package/skills/claude-api/go/claude-api/tool-use.md +220 -0
- package/skills/claude-api/go/managed-agents/README.md +564 -0
- package/skills/claude-api/java/claude-api/README.md +238 -0
- package/skills/claude-api/java/claude-api/files-api.md +27 -0
- package/skills/claude-api/java/claude-api/streaming.md +24 -0
- package/skills/claude-api/java/claude-api/tool-use.md +228 -0
- package/skills/claude-api/java/managed-agents/README.md +443 -0
- package/skills/claude-api/php/claude-api/README.md +174 -0
- package/skills/claude-api/php/claude-api/batches.md +15 -0
- package/skills/claude-api/php/claude-api/files-api.md +13 -0
- package/skills/claude-api/php/claude-api/streaming.md +27 -0
- package/skills/claude-api/php/claude-api/tool-use.md +253 -0
- package/skills/claude-api/php/managed-agents/README.md +443 -0
- package/skills/claude-api/python/claude-api/README.md +565 -0
- package/skills/claude-api/python/claude-api/batches.md +198 -0
- package/skills/claude-api/python/claude-api/files-api.md +170 -0
- package/skills/claude-api/python/claude-api/sdk-upgrade.md +286 -0
- package/skills/claude-api/python/claude-api/streaming.md +179 -0
- package/skills/claude-api/python/claude-api/tool-use.md +627 -0
- package/skills/claude-api/python/managed-agents/README.md +335 -0
- package/skills/claude-api/ruby/claude-api/README.md +134 -0
- package/skills/claude-api/ruby/claude-api/streaming.md +16 -0
- package/skills/claude-api/ruby/claude-api/tool-use.md +41 -0
- package/skills/claude-api/ruby/managed-agents/README.md +394 -0
- package/skills/claude-api/shared/admin-api.md +179 -0
- package/skills/claude-api/shared/agent-design.md +101 -0
- package/skills/claude-api/shared/anthropic-cli.md +269 -0
- package/skills/claude-api/shared/claude-platform-on-aws.md +59 -0
- package/skills/claude-api/shared/cost-optimization.md +233 -0
- package/skills/claude-api/shared/error-codes.md +262 -0
- package/skills/claude-api/shared/live-sources.md +166 -0
- package/skills/claude-api/shared/managed-agents-api-reference.md +455 -0
- package/skills/claude-api/shared/managed-agents-client-patterns.md +220 -0
- package/skills/claude-api/shared/managed-agents-core.md +359 -0
- package/skills/claude-api/shared/managed-agents-environments.md +226 -0
- package/skills/claude-api/shared/managed-agents-events.md +290 -0
- package/skills/claude-api/shared/managed-agents-memory.md +201 -0
- package/skills/claude-api/shared/managed-agents-multiagent.md +260 -0
- package/skills/claude-api/shared/managed-agents-onboarding.md +82 -0
- package/skills/claude-api/shared/managed-agents-outcomes.md +108 -0
- package/skills/claude-api/shared/managed-agents-overview.md +77 -0
- package/skills/claude-api/shared/managed-agents-scheduled-deployments.md +156 -0
- package/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +282 -0
- package/skills/claude-api/shared/managed-agents-tools.md +504 -0
- package/skills/claude-api/shared/managed-agents-webhooks.md +143 -0
- package/skills/claude-api/shared/model-migration.md +1872 -0
- package/skills/claude-api/shared/models.md +142 -0
- package/skills/claude-api/shared/platform-availability.md +53 -0
- package/skills/claude-api/shared/prompt-audit.md +223 -0
- package/skills/claude-api/shared/prompt-caching.md +292 -0
- package/skills/claude-api/shared/token-counting.md +56 -0
- package/skills/claude-api/shared/tool-use-concepts.md +526 -0
- package/skills/claude-api/typescript/claude-api/README.md +405 -0
- package/skills/claude-api/typescript/claude-api/batches.md +106 -0
- package/skills/claude-api/typescript/claude-api/files-api.md +98 -0
- package/skills/claude-api/typescript/claude-api/streaming.md +178 -0
- package/skills/claude-api/typescript/claude-api/tool-use.md +586 -0
- package/skills/claude-api/typescript/managed-agents/README.md +359 -0
- package/skills/code-review/SKILL.md +34 -0
- package/skills/code-review/agents/openai.yaml +3 -0
- package/skills/codebase-design/DEEPENING.md +37 -0
- package/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/skills/codebase-design/SKILL.md +40 -0
- package/skills/diagnosing-bugs/SKILL.md +38 -0
- package/skills/diagnosing-bugs/agents/openai.yaml +3 -0
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
- package/skills/docx/LICENSE.txt +30 -0
- package/skills/docx/SKILL.md +93 -0
- package/skills/docx/scripts/__init__.py +1 -0
- package/skills/docx/scripts/accept_changes.py +135 -0
- package/skills/docx/scripts/comment.py +368 -0
- package/skills/docx/scripts/merge_runs.py +310 -0
- package/skills/docx/scripts/office/helpers/__init__.py +111 -0
- package/skills/docx/scripts/office/helpers/pptx_chart.py +170 -0
- package/skills/docx/scripts/office/helpers/pptx_slide.py +60 -0
- package/skills/docx/scripts/office/helpers/pptx_theme.py +114 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/docx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/docx/scripts/office/soffice.py +192 -0
- package/skills/docx/scripts/office/validate.py +173 -0
- package/skills/docx/scripts/office/validators/__init__.py +15 -0
- package/skills/docx/scripts/office/validators/base.py +875 -0
- package/skills/docx/scripts/office/validators/docx.py +466 -0
- package/skills/docx/scripts/office/validators/pptx.py +441 -0
- package/skills/docx/scripts/office/validators/redlining.py +299 -0
- package/skills/docx/scripts/templates/comments.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/skills/docx/scripts/templates/people.xml +3 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +27 -0
- package/skills/grilling/SKILL.md +41 -0
- package/skills/lean-build/SKILL.md +22 -0
- package/skills/lean-build/agents/openai.yaml +4 -0
- package/skills/mcp-builder/LICENSE.txt +202 -0
- package/skills/mcp-builder/SKILL.md +235 -0
- package/skills/mcp-builder/reference/evaluation.md +602 -0
- package/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/skills/mcp-builder/scripts/connections.py +151 -0
- package/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/skills/migration/SKILL.md +17 -0
- package/skills/migration/agents/openai.yaml +4 -0
- package/skills/pdf/LICENSE.txt +30 -0
- package/skills/pdf/SKILL.md +27 -0
- package/skills/pdf/forms.md +294 -0
- package/skills/pdf/reference.md +612 -0
- package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
- package/skills/pdf/scripts/check_fillable_fields.py +11 -0
- package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
- package/skills/pdf/scripts/create_validation_image.py +37 -0
- package/skills/pdf/scripts/extract_form_field_info.py +122 -0
- package/skills/pdf/scripts/extract_form_structure.py +115 -0
- package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
- package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/skills/pptx/LICENSE.txt +30 -0
- package/skills/pptx/SKILL.md +240 -0
- package/skills/pptx/scripts/__init__.py +0 -0
- package/skills/pptx/scripts/add_slide.py +367 -0
- package/skills/pptx/scripts/clean.py +309 -0
- package/skills/pptx/scripts/office/helpers/__init__.py +111 -0
- package/skills/pptx/scripts/office/helpers/pptx_chart.py +170 -0
- package/skills/pptx/scripts/office/helpers/pptx_slide.py +60 -0
- package/skills/pptx/scripts/office/helpers/pptx_theme.py +114 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/pptx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/pptx/scripts/office/soffice.py +192 -0
- package/skills/pptx/scripts/office/validate.py +173 -0
- package/skills/pptx/scripts/office/validators/__init__.py +15 -0
- package/skills/pptx/scripts/office/validators/base.py +875 -0
- package/skills/pptx/scripts/office/validators/docx.py +466 -0
- package/skills/pptx/scripts/office/validators/pptx.py +441 -0
- package/skills/pptx/scripts/office/validators/redlining.py +299 -0
- package/skills/pptx/scripts/thumbnail.py +311 -0
- package/skills/safe-refactor/SKILL.md +16 -0
- package/skills/safe-refactor/agents/openai.yaml +4 -0
- package/skills/tdd/SKILL.md +18 -0
- package/skills/tdd/agents/openai.yaml +3 -0
- package/skills/tdd/mocking.md +59 -0
- package/skills/tdd/tests.md +77 -0
- package/skills/verify-and-stop/SKILL.md +16 -0
- package/skills/verify-and-stop/agents/openai.yaml +4 -0
- package/skills/webapp-testing/LICENSE.txt +202 -0
- package/skills/webapp-testing/SKILL.md +96 -0
- package/skills/webapp-testing/examples/console_logging.py +35 -0
- package/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/skills/webapp-testing/scripts/with_server.py +106 -0
- package/skills/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/skills/writing-for-agents/SKILL.md +28 -0
- package/skills/writing-for-agents/agents/openai.yaml +3 -0
- package/skills/xlsx/LICENSE.txt +30 -0
- package/skills/xlsx/SKILL.md +101 -0
- package/skills/xlsx/scripts/office/helpers/__init__.py +111 -0
- package/skills/xlsx/scripts/office/helpers/pptx_chart.py +170 -0
- package/skills/xlsx/scripts/office/helpers/pptx_slide.py +60 -0
- package/skills/xlsx/scripts/office/helpers/pptx_theme.py +114 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/xlsx/scripts/office/soffice.py +192 -0
- package/skills/xlsx/scripts/office/validate.py +173 -0
- package/skills/xlsx/scripts/office/validators/__init__.py +15 -0
- package/skills/xlsx/scripts/office/validators/base.py +875 -0
- package/skills/xlsx/scripts/office/validators/docx.py +466 -0
- package/skills/xlsx/scripts/office/validators/pptx.py +441 -0
- package/skills/xlsx/scripts/office/validators/redlining.py +299 -0
- package/skills/xlsx/scripts/recalc.py +308 -0
- package/skills-lock.json +359 -0
- package/tests/auto-permissions.test.mjs +315 -0
- package/tests/background-tasks.test.mjs +27 -0
- package/tests/background-worker.test.mjs +60 -0
- package/tests/bash-permissions.test.mjs +276 -0
- package/tests/glob.test.mjs +31 -0
- package/tests/mode.test.mjs +62 -0
- package/tests/permission-evaluation.test.mjs +70 -0
- package/tests/permission-foundation.test.mjs +194 -0
- package/tests/permission-policy.test.mjs +158 -0
- package/tests/runtime/README.md +51 -0
- package/tests/runtime/harness.mjs +190 -0
- package/tests/runtime/permissions.test.mjs +192 -0
- package/tests/runtime/tui.test.mjs +60 -0
- package/tests/skills-routing.test.mjs +51 -0
- package/tests/task-integration.test.mjs +324 -0
- package/tests/task-permissions.test.mjs +178 -0
- package/tests/task-worktree-policy.test.mjs +53 -0
- package/themes/orange.json +82 -0
- 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,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.
|