@nguyenphp/antigravity-marketing 1.0.16 → 1.0.19
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/README.md +187 -74
- package/bin/index.js +4 -4
- package/package.json +4 -3
- package/templates/.agent/agents/backend-specialist.md +263 -0
- package/templates/.agent/agents/database-architect.md +226 -0
- package/templates/.agent/agents/debugger.md +225 -0
- package/templates/.agent/agents/devops-engineer.md +242 -0
- package/templates/.agent/agents/documentation-writer.md +104 -0
- package/templates/.agent/agents/explorer-agent.md +73 -0
- package/templates/.agent/agents/frontend-specialist.md +527 -0
- package/templates/.agent/agents/game-developer.md +162 -0
- package/templates/.agent/agents/mobile-developer.md +377 -0
- package/templates/.agent/agents/orchestrator.md +400 -0
- package/templates/.agent/agents/penetration-tester.md +188 -0
- package/templates/.agent/agents/performance-optimizer.md +187 -0
- package/templates/.agent/agents/project-planner.md +403 -0
- package/templates/.agent/agents/security-auditor.md +170 -0
- package/templates/.agent/agents/seo-specialist.md +111 -0
- package/templates/.agent/agents/test-engineer.md +158 -0
- package/templates/.agent/rules/GEMINI.md +248 -0
- package/templates/.agent/skills/analytics-marketing/SKILL.md +172 -324
- package/templates/.agent/skills/api-patterns/SKILL.md +81 -0
- package/templates/.agent/skills/api-patterns/api-style.md +42 -0
- package/templates/.agent/skills/api-patterns/auth.md +24 -0
- package/templates/.agent/skills/api-patterns/documentation.md +26 -0
- package/templates/.agent/skills/api-patterns/graphql.md +41 -0
- package/templates/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/templates/.agent/skills/api-patterns/response.md +37 -0
- package/templates/.agent/skills/api-patterns/rest.md +40 -0
- package/templates/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/templates/.agent/skills/api-patterns/security-testing.md +122 -0
- package/templates/.agent/skills/api-patterns/trpc.md +41 -0
- package/templates/.agent/skills/api-patterns/versioning.md +22 -0
- package/templates/.agent/skills/app-builder/SKILL.md +75 -0
- package/templates/.agent/skills/app-builder/agent-coordination.md +71 -0
- package/templates/.agent/skills/app-builder/feature-building.md +53 -0
- package/templates/.agent/skills/app-builder/project-detection.md +34 -0
- package/templates/.agent/skills/app-builder/scaffolding.md +118 -0
- package/templates/.agent/skills/app-builder/tech-stack.md +40 -0
- package/templates/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/templates/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/templates/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/templates/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/templates/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/templates/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/templates/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/templates/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/templates/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +82 -0
- package/templates/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +100 -0
- package/templates/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +106 -0
- package/templates/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +101 -0
- package/templates/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/templates/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +93 -0
- package/templates/.agent/skills/architecture/SKILL.md +55 -0
- package/templates/.agent/skills/architecture/context-discovery.md +43 -0
- package/templates/.agent/skills/architecture/examples.md +94 -0
- package/templates/.agent/skills/architecture/pattern-selection.md +68 -0
- package/templates/.agent/skills/architecture/patterns-reference.md +50 -0
- package/templates/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/templates/.agent/skills/banner-design/SKILL.md +192 -0
- package/templates/.agent/skills/banner-design/references/banner-sizes-and-styles.md +118 -0
- package/templates/.agent/skills/bash-linux/SKILL.md +199 -0
- package/templates/.agent/skills/behavioral-modes/SKILL.md +242 -0
- package/templates/.agent/skills/brainstorming/SKILL.md +163 -0
- package/templates/.agent/skills/brainstorming/dynamic-questioning.md +350 -0
- package/templates/.agent/skills/brand/SKILL.md +97 -0
- package/templates/.agent/skills/brand/references/approval-checklist.md +169 -0
- package/templates/.agent/skills/brand/references/asset-organization.md +157 -0
- package/templates/.agent/skills/brand/references/brand-guideline-template.md +140 -0
- package/templates/.agent/skills/brand/references/color-palette-management.md +186 -0
- package/templates/.agent/skills/brand/references/consistency-checklist.md +94 -0
- package/templates/.agent/skills/brand/references/logo-usage-rules.md +185 -0
- package/templates/.agent/skills/brand/references/messaging-framework.md +85 -0
- package/templates/.agent/skills/brand/references/typography-specifications.md +214 -0
- package/templates/.agent/skills/brand/references/update.md +118 -0
- package/templates/.agent/skills/brand/references/visual-identity.md +96 -0
- package/templates/.agent/skills/brand/references/voice-framework.md +88 -0
- package/templates/.agent/skills/brand/scripts/extract-colors.cjs +341 -0
- package/templates/.agent/skills/brand/scripts/inject-brand-context.cjs +349 -0
- package/templates/.agent/skills/brand/scripts/sync-brand-to-tokens.cjs +266 -0
- package/templates/.agent/skills/brand/scripts/validate-asset.cjs +387 -0
- package/templates/.agent/skills/brand/templates/brand-guidelines-starter.md +275 -0
- package/templates/.agent/skills/clean-code/SKILL.md +201 -0
- package/templates/.agent/skills/code-review-checklist/SKILL.md +109 -0
- package/templates/.agent/skills/copywriting/SKILL.md +250 -0
- package/templates/.agent/skills/database-design/SKILL.md +52 -0
- package/templates/.agent/skills/database-design/database-selection.md +43 -0
- package/templates/.agent/skills/database-design/indexing.md +39 -0
- package/templates/.agent/skills/database-design/migrations.md +48 -0
- package/templates/.agent/skills/database-design/optimization.md +36 -0
- package/templates/.agent/skills/database-design/orm-selection.md +30 -0
- package/templates/.agent/skills/database-design/schema-design.md +56 -0
- package/templates/.agent/skills/database-design/scripts/schema_validator.py +172 -0
- package/templates/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/templates/.agent/skills/docker-expert/SKILL.md +409 -0
- package/templates/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/templates/.agent/skills/frontend-design/color-system.md +311 -0
- package/templates/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/templates/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/templates/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/templates/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/templates/.agent/skills/frontend-design/typography-system.md +345 -0
- package/templates/.agent/skills/frontend-design/ux-psychology.md +541 -0
- package/templates/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/templates/.agent/skills/frontend-slides/SKILL.md +92 -0
- package/templates/.agent/skills/frontend-slides/STYLE_PRESETS.md +347 -0
- package/templates/.agent/skills/frontend-slides/animation-patterns.md +110 -0
- package/templates/.agent/skills/frontend-slides/examples/n8n-jupviec-automation.html +789 -0
- package/templates/.agent/skills/frontend-slides/examples/n8n-jupviec-automation.pptx +0 -0
- package/templates/.agent/skills/frontend-slides/html-template.md +347 -0
- package/templates/.agent/skills/frontend-slides/scripts/export-pptx.py +58 -0
- package/templates/.agent/skills/frontend-slides/scripts/extract-pptx.py +96 -0
- package/templates/.agent/skills/frontend-slides/viewport-base.css +153 -0
- package/templates/.agent/skills/game-development/2d-games/SKILL.md +119 -0
- package/templates/.agent/skills/game-development/3d-games/SKILL.md +135 -0
- package/templates/.agent/skills/game-development/SKILL.md +167 -0
- package/templates/.agent/skills/game-development/game-art/SKILL.md +185 -0
- package/templates/.agent/skills/game-development/game-audio/SKILL.md +190 -0
- package/templates/.agent/skills/game-development/game-design/SKILL.md +129 -0
- package/templates/.agent/skills/game-development/mobile-games/SKILL.md +108 -0
- package/templates/.agent/skills/game-development/multiplayer/SKILL.md +132 -0
- package/templates/.agent/skills/game-development/pc-games/SKILL.md +144 -0
- package/templates/.agent/skills/game-development/vr-ar/SKILL.md +123 -0
- package/templates/.agent/skills/game-development/web-games/SKILL.md +150 -0
- package/templates/.agent/skills/geo-fundamentals/SKILL.md +156 -0
- package/templates/.agent/skills/geo-fundamentals/scripts/geo_checker.py +289 -0
- package/templates/.agent/skills/growth-engine/SKILL.md +244 -0
- package/templates/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/templates/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/templates/.agent/skills/lint-and-validate/SKILL.md +45 -0
- package/templates/.agent/skills/lint-and-validate/scripts/lint_runner.py +172 -0
- package/templates/.agent/skills/lint-and-validate/scripts/type_coverage.py +173 -0
- package/templates/.agent/skills/marketing-report-expert/SKILL.md +70 -0
- package/templates/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/templates/.agent/skills/minimax-docx/LICENSE +21 -0
- package/templates/.agent/skills/minimax-docx/SKILL.md +274 -0
- package/templates/.agent/skills/minimax-docx/assets/styles/academic_styles.xml +250 -0
- package/templates/.agent/skills/minimax-docx/assets/styles/corporate_styles.xml +284 -0
- package/templates/.agent/skills/minimax-docx/assets/styles/default_styles.xml +449 -0
- package/templates/.agent/skills/minimax-docx/assets/xsd/aesthetic-rules.xsd +470 -0
- package/templates/.agent/skills/minimax-docx/assets/xsd/business-rules.xsd +130 -0
- package/templates/.agent/skills/minimax-docx/assets/xsd/common-types.xsd +159 -0
- package/templates/.agent/skills/minimax-docx/assets/xsd/wml-subset.xsd +589 -0
- package/templates/.agent/skills/minimax-docx/references/cjk_typography.md +357 -0
- package/templates/.agent/skills/minimax-docx/references/cjk_university_template_guide.md +184 -0
- package/templates/.agent/skills/minimax-docx/references/comments_guide.md +191 -0
- package/templates/.agent/skills/minimax-docx/references/design_good_bad_examples.md +829 -0
- package/templates/.agent/skills/minimax-docx/references/design_principles.md +819 -0
- package/templates/.agent/skills/minimax-docx/references/openxml_element_order.md +308 -0
- package/templates/.agent/skills/minimax-docx/references/openxml_encyclopedia_part1.md +4061 -0
- package/templates/.agent/skills/minimax-docx/references/openxml_encyclopedia_part2.md +2820 -0
- package/templates/.agent/skills/minimax-docx/references/openxml_encyclopedia_part3.md +3381 -0
- package/templates/.agent/skills/minimax-docx/references/openxml_namespaces.md +82 -0
- package/templates/.agent/skills/minimax-docx/references/openxml_units.md +72 -0
- package/templates/.agent/skills/minimax-docx/references/scenario_a_create.md +284 -0
- package/templates/.agent/skills/minimax-docx/references/scenario_b_edit_content.md +295 -0
- package/templates/.agent/skills/minimax-docx/references/scenario_c_apply_template.md +456 -0
- package/templates/.agent/skills/minimax-docx/references/track_changes_guide.md +200 -0
- package/templates/.agent/skills/minimax-docx/references/troubleshooting.md +506 -0
- package/templates/.agent/skills/minimax-docx/references/typography_guide.md +294 -0
- package/templates/.agent/skills/minimax-docx/references/xsd_validation_guide.md +158 -0
- package/templates/.agent/skills/minimax-docx/scripts/doc_to_docx.sh +40 -0
- package/templates/.agent/skills/minimax-docx/scripts/docx_preview.sh +37 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Cli/MiniMaxAIDocx.Cli.csproj +19 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Cli/Program.cs +18 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/AnalyzeCommand.cs +147 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/ApplyTemplateCommand.cs +322 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/CreateCommand.cs +324 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/DiffCommand.cs +155 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/EditContentCommand.cs +487 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/FixOrderCommand.cs +108 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/MergeRunsCommand.cs +122 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Commands/ValidateCommand.cs +107 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/MiniMaxAIDocx.Core.csproj +15 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/CommentSynchronizer.cs +169 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/ElementOrder.cs +80 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/NamespaceConstants.cs +42 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/RunMerger.cs +81 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/StyleAnalyzer.cs +81 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/TrackChangesHelper.cs +99 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/OpenXml/UnitConverter.cs +23 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples.cs +1832 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch1.cs +910 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch2.cs +999 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch3.cs +1048 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/AestheticRecipeSamples_Batch4.cs +1038 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/CharacterFormattingSamples.cs +1020 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/DocumentCreationSamples.cs +1121 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/FieldAndTocSamples.cs +624 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/FootnoteAndCommentSamples.cs +675 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/HeaderFooterSamples.cs +838 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ImageSamples.cs +917 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ListAndNumberingSamples.cs +826 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/ParagraphFormattingSamples.cs +1199 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/StyleSystemSamples.cs +1487 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/TableSamples.cs +1163 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Samples/TrackChangesSamples.cs +595 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/CjkHelper.cs +39 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/FontDefaults.cs +24 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Typography/PageSizes.cs +20 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/BusinessRuleValidator.cs +224 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/GateCheckValidator.cs +148 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/ValidationResult.cs +23 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.Core/Validation/XsdValidator.cs +69 -0
- package/templates/.agent/skills/minimax-docx/scripts/dotnet/MiniMaxAIDocx.slnx +4 -0
- package/templates/.agent/skills/minimax-docx/scripts/env_check.sh +196 -0
- package/templates/.agent/skills/minimax-docx/scripts/setup.ps1 +274 -0
- package/templates/.agent/skills/minimax-docx/scripts/setup.sh +504 -0
- package/templates/.agent/skills/minimax-multimodal-toolkit/SKILL.md +359 -0
- package/templates/.agent/skills/minimax-pdf/README.md +222 -0
- package/templates/.agent/skills/minimax-pdf/SKILL.md +201 -0
- package/templates/.agent/skills/minimax-pdf/design/design.md +381 -0
- package/templates/.agent/skills/minimax-pdf/scripts/cover.py +1579 -0
- package/templates/.agent/skills/minimax-pdf/scripts/fill_inspect.py +200 -0
- package/templates/.agent/skills/minimax-pdf/scripts/fill_write.py +242 -0
- package/templates/.agent/skills/minimax-pdf/scripts/make.sh +491 -0
- package/templates/.agent/skills/minimax-pdf/scripts/merge.py +112 -0
- package/templates/.agent/skills/minimax-pdf/scripts/palette.py +559 -0
- package/templates/.agent/skills/minimax-pdf/scripts/reformat_parse.py +374 -0
- package/templates/.agent/skills/minimax-pdf/scripts/render_body.py +1055 -0
- package/templates/.agent/skills/minimax-pdf/scripts/render_cover.cjs +111 -0
- package/templates/.agent/skills/minimax-xlsx/SKILL.md +138 -0
- package/templates/.agent/skills/minimax-xlsx/references/create.md +691 -0
- package/templates/.agent/skills/minimax-xlsx/references/edit.md +684 -0
- package/templates/.agent/skills/minimax-xlsx/references/fix.md +37 -0
- package/templates/.agent/skills/minimax-xlsx/references/format.md +768 -0
- package/templates/.agent/skills/minimax-xlsx/references/ooxml-cheatsheet.md +231 -0
- package/templates/.agent/skills/minimax-xlsx/references/read-analyze.md +97 -0
- package/templates/.agent/skills/minimax-xlsx/references/validate.md +772 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/formula_check.py +422 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/libreoffice_recalc.py +248 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/shared_strings_builder.py +163 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/style_audit.py +575 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_add_column.py +395 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_insert_row.py +274 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_pack.py +87 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_reader.py +362 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_shift_rows.py +396 -0
- package/templates/.agent/skills/minimax-xlsx/scripts/xlsx_unpack.py +130 -0
- package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/[Content_Types].xml +9 -0
- package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/_rels/.rels +6 -0
- package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/_rels/workbook.xml.rels +19 -0
- package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/sharedStrings.xml +33 -0
- package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/styles.xml +160 -0
- package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/workbook.xml +30 -0
- package/templates/.agent/skills/minimax-xlsx/templates/minimal_xlsx/xl/worksheets/sheet1.xml +70 -0
- package/templates/.agent/skills/mobile-design/SKILL.md +394 -0
- package/templates/.agent/skills/mobile-design/decision-trees.md +516 -0
- package/templates/.agent/skills/mobile-design/mobile-backend.md +491 -0
- package/templates/.agent/skills/mobile-design/mobile-color-system.md +420 -0
- package/templates/.agent/skills/mobile-design/mobile-debugging.md +122 -0
- package/templates/.agent/skills/mobile-design/mobile-design-thinking.md +357 -0
- package/templates/.agent/skills/mobile-design/mobile-navigation.md +458 -0
- package/templates/.agent/skills/mobile-design/mobile-performance.md +767 -0
- package/templates/.agent/skills/mobile-design/mobile-testing.md +356 -0
- package/templates/.agent/skills/mobile-design/mobile-typography.md +433 -0
- package/templates/.agent/skills/mobile-design/platform-android.md +666 -0
- package/templates/.agent/skills/mobile-design/platform-ios.md +561 -0
- package/templates/.agent/skills/mobile-design/scripts/mobile_audit.py +670 -0
- package/templates/.agent/skills/mobile-design/touch-psychology.md +537 -0
- package/templates/.agent/skills/nestjs-expert/SKILL.md +552 -0
- package/templates/.agent/skills/nextjs-best-practices/SKILL.md +203 -0
- package/templates/.agent/skills/nodejs-best-practices/SKILL.md +333 -0
- package/templates/.agent/skills/parallel-agents/SKILL.md +175 -0
- package/templates/.agent/skills/performance-profiling/SKILL.md +143 -0
- package/templates/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +76 -0
- package/templates/.agent/skills/plan-writing/SKILL.md +152 -0
- package/templates/.agent/skills/powershell-windows/SKILL.md +167 -0
- package/templates/.agent/skills/ppc-advertising/SKILL.md +183 -475
- package/templates/.agent/skills/pptx-generator/SKILL.md +249 -0
- package/templates/.agent/skills/pptx-generator/references/design-system.md +392 -0
- package/templates/.agent/skills/pptx-generator/references/editing.md +162 -0
- package/templates/.agent/skills/pptx-generator/references/pitfalls.md +112 -0
- package/templates/.agent/skills/pptx-generator/references/pptxgenjs.md +420 -0
- package/templates/.agent/skills/pptx-generator/references/slide-types.md +413 -0
- package/templates/.agent/skills/prisma-expert/SKILL.md +355 -0
- package/templates/.agent/skills/python-patterns/SKILL.md +441 -0
- package/templates/.agent/skills/react-patterns/SKILL.md +198 -0
- package/templates/.agent/skills/red-team-tactics/SKILL.md +199 -0
- package/templates/.agent/skills/remotion-best-practices/SKILL.md +45 -111
- package/templates/.agent/skills/remotion-best-practices/rules/3d.md +4 -4
- package/templates/.agent/skills/remotion-best-practices/rules/animations.md +5 -7
- package/templates/.agent/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
- package/templates/.agent/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
- package/templates/.agent/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +103 -0
- package/templates/.agent/skills/remotion-best-practices/rules/assets.md +78 -0
- package/templates/.agent/skills/remotion-best-practices/rules/audio-visualization.md +198 -0
- package/templates/.agent/skills/remotion-best-practices/rules/audio.md +1 -4
- package/templates/.agent/skills/remotion-best-practices/rules/calculate-metadata.md +47 -17
- package/templates/.agent/skills/remotion-best-practices/rules/can-decode.md +75 -0
- package/templates/.agent/skills/remotion-best-practices/rules/charts.md +80 -48
- package/templates/.agent/skills/remotion-best-practices/rules/compositions.md +22 -14
- package/templates/.agent/skills/remotion-best-practices/rules/display-captions.md +79 -21
- package/templates/.agent/skills/remotion-best-practices/rules/extract-frames.md +229 -0
- package/templates/.agent/skills/remotion-best-practices/rules/ffmpeg.md +38 -0
- package/templates/.agent/skills/remotion-best-practices/rules/fonts.md +96 -54
- package/templates/.agent/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
- package/templates/.agent/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
- package/templates/.agent/skills/remotion-best-practices/rules/get-video-duration.md +60 -0
- package/templates/.agent/skills/remotion-best-practices/rules/gifs.md +21 -18
- package/templates/.agent/skills/remotion-best-practices/rules/images.md +6 -2
- package/templates/.agent/skills/remotion-best-practices/rules/import-srt-captions.md +69 -0
- package/templates/.agent/skills/remotion-best-practices/rules/light-leaks.md +73 -0
- package/templates/.agent/skills/remotion-best-practices/rules/lottie.md +10 -7
- package/templates/.agent/skills/remotion-best-practices/rules/maps.md +412 -0
- package/templates/.agent/skills/remotion-best-practices/rules/measuring-dom-nodes.md +34 -0
- package/templates/.agent/skills/remotion-best-practices/rules/measuring-text.md +140 -0
- package/templates/.agent/skills/remotion-best-practices/rules/parameters.md +109 -0
- package/templates/.agent/skills/remotion-best-practices/rules/sequencing.md +13 -1
- package/templates/.agent/skills/remotion-best-practices/rules/sfx.md +26 -0
- package/templates/.agent/skills/remotion-best-practices/rules/subtitles.md +36 -0
- package/templates/.agent/skills/remotion-best-practices/rules/tailwind.md +11 -0
- package/templates/.agent/skills/remotion-best-practices/rules/text-animations.md +4 -115
- package/templates/.agent/skills/remotion-best-practices/rules/timing.md +19 -19
- package/templates/.agent/skills/remotion-best-practices/rules/transcribe-captions.md +70 -0
- package/templates/.agent/skills/remotion-best-practices/rules/transitions.md +117 -42
- package/templates/.agent/skills/remotion-best-practices/rules/transparent-videos.md +106 -0
- package/templates/.agent/skills/remotion-best-practices/rules/trimming.md +51 -0
- package/templates/.agent/skills/remotion-best-practices/rules/voiceover.md +99 -0
- package/templates/.agent/skills/seo-fundamentals/SKILL.md +83 -441
- package/templates/.agent/skills/seo-fundamentals/scripts/seo_checker.py +219 -0
- package/templates/.agent/skills/server-management/SKILL.md +161 -0
- package/templates/.agent/skills/systematic-debugging/SKILL.md +109 -0
- package/templates/.agent/skills/tdd-workflow/SKILL.md +149 -0
- package/templates/.agent/skills/testing-patterns/SKILL.md +178 -0
- package/templates/.agent/skills/testing-patterns/scripts/test_runner.py +219 -0
- package/templates/.agent/skills/tutorial-video-expert/SKILL.md +88 -0
- package/templates/.agent/skills/typescript-expert/SKILL.md +429 -0
- package/templates/.agent/skills/ui-ux-pro-max/SKILL.md +1 -1
- package/templates/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/prompts.csv +24 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/styles.csv +59 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/templates/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/templates/.agent/skills/ui-ux-pro-max/scripts/core.py +257 -0
- package/templates/.agent/skills/ui-ux-pro-max/scripts/design_system.py +487 -0
- package/templates/.agent/skills/ui-ux-pro-max/scripts/search.py +76 -0
- package/templates/.agent/skills/vision-analysis/SKILL.md +174 -0
- package/templates/.agent/skills/vue-expert/SKILL.md +374 -0
- package/templates/.agent/skills/vulnerability-scanner/SKILL.md +276 -0
- package/templates/.agent/skills/vulnerability-scanner/checklists.md +121 -0
- package/templates/.agent/skills/vulnerability-scanner/scripts/security_scan.py +458 -0
- package/templates/.agent/skills/webapp-testing/SKILL.md +187 -0
- package/templates/.agent/skills/webapp-testing/scripts/playwright_runner.py +173 -0
- package/templates/.agent/workflows/analyze.md +3 -0
- package/templates/.agent/workflows/brainstorm.md +113 -0
- package/templates/.agent/workflows/brand-report.md +44 -0
- package/templates/.agent/workflows/create.md +59 -0
- package/templates/.agent/workflows/debug.md +103 -0
- package/templates/.agent/workflows/deploy.md +176 -0
- package/templates/.agent/workflows/enhance.md +63 -0
- package/templates/.agent/workflows/orchestrate.md +237 -0
- package/templates/.agent/workflows/plan.md +89 -0
- package/templates/.agent/workflows/preview.md +80 -0
- package/templates/.agent/workflows/report.md +49 -0
- package/templates/.agent/workflows/status.md +86 -0
- package/templates/.agent/workflows/test.md +144 -0
- package/templates/.agent/workflows/ui-ux-pro-max.md +231 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""
|
|
4
|
+
formula_check.py — Static formula validator for xlsx files.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python3 formula_check.py <input.xlsx>
|
|
8
|
+
python3 formula_check.py <input.xlsx> --json # machine-readable output
|
|
9
|
+
python3 formula_check.py <input.xlsx> --report # standardized validation report (JSON)
|
|
10
|
+
python3 formula_check.py <input.xlsx> --report -o out # report to file
|
|
11
|
+
python3 formula_check.py <input.xlsx> --sheet Sales # limit to one sheet
|
|
12
|
+
python3 formula_check.py <input.xlsx> --summary # error counts only, no details
|
|
13
|
+
|
|
14
|
+
What it checks:
|
|
15
|
+
1. Error-value cells: <c t="e"><v>#REF!</v></c> — all 7 Excel error types
|
|
16
|
+
2. Broken cross-sheet references: formula references a sheet not in workbook.xml
|
|
17
|
+
3. Broken named-range references: formula references a name not in workbook.xml <definedNames>
|
|
18
|
+
4. Shared formula integrity: shared formula primary cell exists and has formula text
|
|
19
|
+
5. Missing <v> on t="e" cells (malformed XML)
|
|
20
|
+
|
|
21
|
+
Checks NOT performed (require dynamic recalculation):
|
|
22
|
+
- Runtime errors that only appear after formulas execute (#DIV/0! on empty denominator, etc.)
|
|
23
|
+
-> Use libreoffice_recalc.py + re-run formula_check.py for dynamic validation
|
|
24
|
+
|
|
25
|
+
Exit code:
|
|
26
|
+
0 — no errors found
|
|
27
|
+
1 — errors detected (or file cannot be opened)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import sys
|
|
31
|
+
import zipfile
|
|
32
|
+
import xml.etree.ElementTree as ET
|
|
33
|
+
import re
|
|
34
|
+
import json
|
|
35
|
+
|
|
36
|
+
# OOXML SpreadsheetML namespace
|
|
37
|
+
NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
38
|
+
NSP = f"{{{NS}}}"
|
|
39
|
+
|
|
40
|
+
# All 7 standard Excel formula error types
|
|
41
|
+
EXCEL_ERRORS = {"#REF!", "#DIV/0!", "#VALUE!", "#NAME?", "#NULL!", "#NUM!", "#N/A"}
|
|
42
|
+
|
|
43
|
+
# Excel built-in function names (subset of common ones) — used for #NAME? heuristic
|
|
44
|
+
# Full list: https://support.microsoft.com/en-us/office/excel-functions-alphabetical
|
|
45
|
+
_BUILTIN_FUNCTIONS = {
|
|
46
|
+
"ABS", "AND", "AVERAGE", "AVERAGEIF", "AVERAGEIFS", "CEILING", "CHOOSE",
|
|
47
|
+
"COUNTA", "COUNTIF", "COUNTIFS", "COUNT", "DATE", "EDATE", "EOMONTH",
|
|
48
|
+
"FALSE", "FILTER", "FIND", "FLOOR", "IF", "IFERROR", "IFNA", "IFS",
|
|
49
|
+
"INDEX", "INDIRECT", "INT", "IRR", "ISBLANK", "ISERROR", "ISNA", "ISNUMBER",
|
|
50
|
+
"LARGE", "LEFT", "LEN", "LOOKUP", "LOWER", "MATCH", "MAX", "MID", "MIN",
|
|
51
|
+
"MOD", "MONTH", "NETWORKDAYS", "NOT", "NOW", "NPV", "OFFSET", "OR",
|
|
52
|
+
"PMT", "PV", "RAND", "RANK", "RIGHT", "ROUND", "ROUNDDOWN", "ROUNDUP",
|
|
53
|
+
"ROW", "ROWS", "SEARCH", "SMALL", "SORT", "SQRT", "SUBSTITUTE", "SUM",
|
|
54
|
+
"SUMIF", "SUMIFS", "SUMPRODUCT", "TEXT", "TODAY", "TRANSPOSE", "TRIM",
|
|
55
|
+
"TRUE", "UNIQUE", "UPPER", "VALUE", "VLOOKUP", "HLOOKUP", "XLOOKUP",
|
|
56
|
+
"XMATCH", "XNPV", "XIRR", "YEAR", "YEARFRAC",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_sheet_names(z: zipfile.ZipFile) -> dict[str, str]:
|
|
61
|
+
"""Return dict of {r:id -> sheet_name} from workbook.xml."""
|
|
62
|
+
wb_xml = z.read("xl/workbook.xml")
|
|
63
|
+
wb = ET.fromstring(wb_xml)
|
|
64
|
+
rel_ns = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
65
|
+
sheets = {}
|
|
66
|
+
for sheet in wb.findall(f".//{NSP}sheet"):
|
|
67
|
+
name = sheet.get("name", "")
|
|
68
|
+
rid = sheet.get(f"{{{rel_ns}}}id", "")
|
|
69
|
+
sheets[rid] = name
|
|
70
|
+
return sheets
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_defined_names(z: zipfile.ZipFile) -> set[str]:
|
|
74
|
+
"""Return set of named ranges defined in workbook.xml <definedNames>."""
|
|
75
|
+
wb_xml = z.read("xl/workbook.xml")
|
|
76
|
+
wb = ET.fromstring(wb_xml)
|
|
77
|
+
names = set()
|
|
78
|
+
for dn in wb.findall(f".//{NSP}definedName"):
|
|
79
|
+
n = dn.get("name", "")
|
|
80
|
+
if n:
|
|
81
|
+
names.add(n)
|
|
82
|
+
return names
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_sheet_files(z: zipfile.ZipFile) -> dict[str, str]:
|
|
86
|
+
"""Return dict of {r:id -> xl/worksheets/sheetN.xml} from workbook.xml.rels."""
|
|
87
|
+
rels_xml = z.read("xl/_rels/workbook.xml.rels")
|
|
88
|
+
rels = ET.fromstring(rels_xml)
|
|
89
|
+
mapping = {}
|
|
90
|
+
for rel in rels:
|
|
91
|
+
rid = rel.get("Id", "")
|
|
92
|
+
target = rel.get("Target", "")
|
|
93
|
+
if "worksheets" in target:
|
|
94
|
+
# Target may be relative: "worksheets/sheet1.xml" -> "xl/worksheets/sheet1.xml"
|
|
95
|
+
if not target.startswith("xl/"):
|
|
96
|
+
target = "xl/" + target
|
|
97
|
+
mapping[rid] = target
|
|
98
|
+
return mapping
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def extract_sheet_refs(formula: str) -> list[str]:
|
|
102
|
+
"""
|
|
103
|
+
Extract all sheet names referenced in a formula string.
|
|
104
|
+
|
|
105
|
+
Handles:
|
|
106
|
+
- 'Sheet Name'!A1 (quoted, may contain spaces)
|
|
107
|
+
- SheetName!A1 (unquoted, no spaces)
|
|
108
|
+
|
|
109
|
+
Returns a list of sheet name strings (may contain duplicates if the same
|
|
110
|
+
sheet is referenced multiple times in one formula).
|
|
111
|
+
"""
|
|
112
|
+
refs = []
|
|
113
|
+
# Quoted sheet names: 'Sheet Name'!
|
|
114
|
+
for m in re.finditer(r"'([^']+)'!", formula):
|
|
115
|
+
refs.append(m.group(1))
|
|
116
|
+
# Unquoted sheet names: SheetName! (not preceded by a single quote)
|
|
117
|
+
for m in re.finditer(r"(?<!')([A-Za-z_\u4e00-\u9fff][A-Za-z0-9_.·\u4e00-\u9fff]*)!", formula):
|
|
118
|
+
refs.append(m.group(1))
|
|
119
|
+
return refs
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def extract_name_refs(formula: str) -> list[str]:
|
|
123
|
+
"""
|
|
124
|
+
Extract identifiers in a formula that could be named range references.
|
|
125
|
+
|
|
126
|
+
Heuristic: identifiers that:
|
|
127
|
+
- Are not preceded by a sheet reference (no "!" before them)
|
|
128
|
+
- Are not followed by "(" (which would make them function calls)
|
|
129
|
+
- Match the pattern of a name (letters/underscore start, alphanumeric/underscore body)
|
|
130
|
+
- Are not single-letter column references or row references
|
|
131
|
+
|
|
132
|
+
This is approximate. False positives are possible; false negatives are rare.
|
|
133
|
+
"""
|
|
134
|
+
names = []
|
|
135
|
+
# Remove quoted sheet references first to avoid false matches
|
|
136
|
+
formula_clean = re.sub(r"'[^']*'![A-Z$0-9:]+", "", formula)
|
|
137
|
+
formula_clean = re.sub(r"[A-Za-z_][A-Za-z0-9_.]*![A-Z$0-9:]+", "", formula_clean)
|
|
138
|
+
# Find identifiers not followed by "(" (not function calls)
|
|
139
|
+
for m in re.finditer(r"\b([A-Za-z_][A-Za-z0-9_]{2,})\b(?!\s*\()", formula_clean):
|
|
140
|
+
candidate = m.group(1)
|
|
141
|
+
# Exclude Excel cell references like A1, B10, AA100
|
|
142
|
+
if re.fullmatch(r"[A-Z]{1,3}[0-9]+", candidate):
|
|
143
|
+
continue
|
|
144
|
+
# Exclude built-in function names (they appear without parens sometimes in array formulas)
|
|
145
|
+
if candidate.upper() in _BUILTIN_FUNCTIONS:
|
|
146
|
+
continue
|
|
147
|
+
names.append(candidate)
|
|
148
|
+
return names
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def check(xlsx_path: str, sheet_filter: str | None = None) -> dict:
|
|
152
|
+
"""
|
|
153
|
+
Run all static checks on the given xlsx file.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
xlsx_path: path to the .xlsx file
|
|
157
|
+
sheet_filter: if provided, only check the sheet with this name
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
A dict with keys:
|
|
161
|
+
file, sheets_checked, formula_count, shared_formula_ranges,
|
|
162
|
+
error_count, errors
|
|
163
|
+
"""
|
|
164
|
+
results = {
|
|
165
|
+
"file": xlsx_path,
|
|
166
|
+
"sheets_checked": [],
|
|
167
|
+
"formula_count": 0,
|
|
168
|
+
"shared_formula_ranges": 0, # number of shared formula definitions
|
|
169
|
+
"error_count": 0,
|
|
170
|
+
"errors": [],
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
z = zipfile.ZipFile(xlsx_path, "r")
|
|
175
|
+
except (zipfile.BadZipFile, FileNotFoundError) as e:
|
|
176
|
+
results["errors"].append({"type": "file_error", "message": str(e)})
|
|
177
|
+
results["error_count"] = 1
|
|
178
|
+
return results
|
|
179
|
+
|
|
180
|
+
with z:
|
|
181
|
+
sheet_names = get_sheet_names(z)
|
|
182
|
+
sheet_files = get_sheet_files(z)
|
|
183
|
+
valid_sheet_names = set(sheet_names.values())
|
|
184
|
+
defined_names = get_defined_names(z)
|
|
185
|
+
|
|
186
|
+
for rid, sheet_name in sheet_names.items():
|
|
187
|
+
# Apply sheet filter if requested
|
|
188
|
+
if sheet_filter and sheet_name != sheet_filter:
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
ws_file = sheet_files.get(rid)
|
|
192
|
+
if not ws_file or ws_file not in z.namelist():
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
results["sheets_checked"].append(sheet_name)
|
|
196
|
+
ws_xml = z.read(ws_file)
|
|
197
|
+
ws = ET.fromstring(ws_xml)
|
|
198
|
+
|
|
199
|
+
# Track shared formula IDs seen on this sheet (si -> primary cell ref)
|
|
200
|
+
shared_primary: dict[str, str] = {}
|
|
201
|
+
|
|
202
|
+
for cell in ws.findall(f".//{NSP}c"):
|
|
203
|
+
cell_ref = cell.get("r", "?")
|
|
204
|
+
cell_type = cell.get("t", "n")
|
|
205
|
+
|
|
206
|
+
# ── Check 1: error-value cell ──────────────────────────────
|
|
207
|
+
if cell_type == "e":
|
|
208
|
+
v_elem = cell.find(f"{NSP}v")
|
|
209
|
+
if v_elem is None:
|
|
210
|
+
# Malformed: t="e" but no <v> — record as structural issue
|
|
211
|
+
results["errors"].append(
|
|
212
|
+
{
|
|
213
|
+
"type": "malformed_error_cell",
|
|
214
|
+
"sheet": sheet_name,
|
|
215
|
+
"cell": cell_ref,
|
|
216
|
+
"detail": "Cell has t='e' but no <v> child element",
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
results["error_count"] += 1
|
|
220
|
+
else:
|
|
221
|
+
error_val = v_elem.text or "#UNKNOWN"
|
|
222
|
+
f_elem = cell.find(f"{NSP}f")
|
|
223
|
+
results["errors"].append(
|
|
224
|
+
{
|
|
225
|
+
"type": "error_value",
|
|
226
|
+
"error": error_val,
|
|
227
|
+
"sheet": sheet_name,
|
|
228
|
+
"cell": cell_ref,
|
|
229
|
+
# Include formula text if present
|
|
230
|
+
"formula": f_elem.text if (f_elem is not None and f_elem.text) else None,
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
results["error_count"] += 1
|
|
234
|
+
|
|
235
|
+
# ── Check 2 & 3: formulas ──────────────────────────────────
|
|
236
|
+
f_elem = cell.find(f"{NSP}f")
|
|
237
|
+
if f_elem is None:
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
f_type = f_elem.get("t", "") # "shared", "array", or "" for normal
|
|
241
|
+
f_si = f_elem.get("si") # shared formula group ID
|
|
242
|
+
|
|
243
|
+
# Count formulas:
|
|
244
|
+
# - Normal formulas: always count
|
|
245
|
+
# - Shared formula PRIMARY (has text + ref attribute): count once
|
|
246
|
+
# - Shared formula CONSUMER (si only, no text): do NOT count separately
|
|
247
|
+
# (they are covered by the primary's ref range)
|
|
248
|
+
if f_type == "shared" and f_elem.text is None:
|
|
249
|
+
# Consumer cell: skip formula counting and cross-ref checks
|
|
250
|
+
# (the primary cell already covers this formula)
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
formula = f_elem.text or ""
|
|
254
|
+
|
|
255
|
+
if f_type == "shared" and f_elem.get("ref"):
|
|
256
|
+
results["shared_formula_ranges"] += 1
|
|
257
|
+
if f_si is not None:
|
|
258
|
+
shared_primary[f_si] = cell_ref
|
|
259
|
+
|
|
260
|
+
if formula:
|
|
261
|
+
results["formula_count"] += 1
|
|
262
|
+
|
|
263
|
+
# Check 2: cross-sheet references
|
|
264
|
+
for ref_sheet in extract_sheet_refs(formula):
|
|
265
|
+
if ref_sheet not in valid_sheet_names:
|
|
266
|
+
results["errors"].append(
|
|
267
|
+
{
|
|
268
|
+
"type": "broken_sheet_ref",
|
|
269
|
+
"sheet": sheet_name,
|
|
270
|
+
"cell": cell_ref,
|
|
271
|
+
"formula": formula,
|
|
272
|
+
"missing_sheet": ref_sheet,
|
|
273
|
+
"valid_sheets": sorted(valid_sheet_names),
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
results["error_count"] += 1
|
|
277
|
+
|
|
278
|
+
# Check 3: named range references
|
|
279
|
+
# Only flag if the name is not a built-in and not a sheet-prefixed ref
|
|
280
|
+
for name_ref in extract_name_refs(formula):
|
|
281
|
+
if name_ref not in defined_names:
|
|
282
|
+
results["errors"].append(
|
|
283
|
+
{
|
|
284
|
+
"type": "unknown_name_ref",
|
|
285
|
+
"sheet": sheet_name,
|
|
286
|
+
"cell": cell_ref,
|
|
287
|
+
"formula": formula,
|
|
288
|
+
"unknown_name": name_ref,
|
|
289
|
+
"defined_names": sorted(defined_names),
|
|
290
|
+
"note": "Heuristic check — verify manually if this is a false positive",
|
|
291
|
+
}
|
|
292
|
+
)
|
|
293
|
+
results["error_count"] += 1
|
|
294
|
+
|
|
295
|
+
return results
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def build_report(results: dict) -> dict:
|
|
299
|
+
"""
|
|
300
|
+
Transform raw check() output into a standardized validation report.
|
|
301
|
+
|
|
302
|
+
Usage:
|
|
303
|
+
python3 formula_check.py <input.xlsx> --report # JSON report to stdout
|
|
304
|
+
python3 formula_check.py <input.xlsx> --report -o out # JSON report to file
|
|
305
|
+
"""
|
|
306
|
+
from collections import Counter
|
|
307
|
+
|
|
308
|
+
errors = results.get("errors", [])
|
|
309
|
+
error_types = [e.get("error", e.get("type", "unknown")) for e in errors]
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
"status": "success" if results["error_count"] == 0 else "errors_found",
|
|
313
|
+
"file": results["file"],
|
|
314
|
+
"sheets_checked": results["sheets_checked"],
|
|
315
|
+
"total_formulas": results["formula_count"],
|
|
316
|
+
"total_errors": results["error_count"],
|
|
317
|
+
"shared_formula_ranges": results.get("shared_formula_ranges", 0),
|
|
318
|
+
"errors_by_type": dict(Counter(error_types)) if errors else {},
|
|
319
|
+
"errors": errors,
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def main() -> None:
|
|
324
|
+
use_json = "--json" in sys.argv
|
|
325
|
+
use_report = "--report" in sys.argv
|
|
326
|
+
summary_only = "--summary" in sys.argv
|
|
327
|
+
output_file = None
|
|
328
|
+
sheet_filter = None
|
|
329
|
+
args_clean = []
|
|
330
|
+
|
|
331
|
+
i = 1
|
|
332
|
+
while i < len(sys.argv):
|
|
333
|
+
arg = sys.argv[i]
|
|
334
|
+
if arg == "--sheet" and i + 1 < len(sys.argv):
|
|
335
|
+
sheet_filter = sys.argv[i + 1]
|
|
336
|
+
i += 2
|
|
337
|
+
elif arg == "-o" and i + 1 < len(sys.argv):
|
|
338
|
+
output_file = sys.argv[i + 1]
|
|
339
|
+
i += 2
|
|
340
|
+
elif arg.startswith("--"):
|
|
341
|
+
i += 1 # skip flags already handled
|
|
342
|
+
else:
|
|
343
|
+
args_clean.append(arg)
|
|
344
|
+
i += 1
|
|
345
|
+
|
|
346
|
+
if not args_clean:
|
|
347
|
+
print("Usage: formula_check.py <input.xlsx> [--json] [--report [-o FILE]] [--sheet NAME] [--summary]")
|
|
348
|
+
sys.exit(1)
|
|
349
|
+
|
|
350
|
+
results = check(args_clean[0], sheet_filter=sheet_filter)
|
|
351
|
+
|
|
352
|
+
if use_report:
|
|
353
|
+
report = build_report(results)
|
|
354
|
+
output = json.dumps(report, indent=2, ensure_ascii=False)
|
|
355
|
+
if output_file:
|
|
356
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
357
|
+
f.write(output + "\n")
|
|
358
|
+
else:
|
|
359
|
+
print(output)
|
|
360
|
+
sys.exit(1 if results["error_count"] > 0 else 0)
|
|
361
|
+
|
|
362
|
+
if use_json:
|
|
363
|
+
print(json.dumps(results, indent=2, ensure_ascii=False))
|
|
364
|
+
sys.exit(1 if results["error_count"] > 0 else 0)
|
|
365
|
+
|
|
366
|
+
# Human-readable output
|
|
367
|
+
sheets = ", ".join(results["sheets_checked"]) or "(none)"
|
|
368
|
+
if sheet_filter:
|
|
369
|
+
sheets = f"{sheet_filter} (filtered)"
|
|
370
|
+
|
|
371
|
+
print(f"File : {results['file']}")
|
|
372
|
+
print(f"Sheets : {sheets}")
|
|
373
|
+
print(f"Formulas checked : {results['formula_count']} distinct formula cells")
|
|
374
|
+
print(f"Shared formula ranges : {results['shared_formula_ranges']} ranges")
|
|
375
|
+
print(f"Errors found : {results['error_count']}")
|
|
376
|
+
|
|
377
|
+
if not summary_only and results["errors"]:
|
|
378
|
+
print("\n── Error Details ──")
|
|
379
|
+
for e in results["errors"]:
|
|
380
|
+
if e["type"] == "error_value":
|
|
381
|
+
formula_hint = f" (formula: {e['formula']})" if e.get("formula") else ""
|
|
382
|
+
print(f" [FAIL] [{e['sheet']}!{e['cell']}] contains {e['error']}{formula_hint}")
|
|
383
|
+
elif e["type"] == "broken_sheet_ref":
|
|
384
|
+
print(
|
|
385
|
+
f" [FAIL] [{e['sheet']}!{e['cell']}] references missing sheet "
|
|
386
|
+
f"'{e['missing_sheet']}'"
|
|
387
|
+
)
|
|
388
|
+
print(f" Formula: {e['formula']}")
|
|
389
|
+
print(f" Valid sheets: {e.get('valid_sheets', [])}")
|
|
390
|
+
elif e["type"] == "unknown_name_ref":
|
|
391
|
+
print(
|
|
392
|
+
f" [WARN] [{e['sheet']}!{e['cell']}] uses unknown name "
|
|
393
|
+
f"'{e['unknown_name']}' (heuristic — verify manually)"
|
|
394
|
+
)
|
|
395
|
+
print(f" Formula: {e['formula']}")
|
|
396
|
+
print(f" Defined names: {e.get('defined_names', [])}")
|
|
397
|
+
elif e["type"] == "malformed_error_cell":
|
|
398
|
+
print(f" [FAIL] [{e['sheet']}!{e['cell']}] malformed error cell: {e['detail']}")
|
|
399
|
+
elif e["type"] == "file_error":
|
|
400
|
+
print(f" [FAIL] File error: {e['message']}")
|
|
401
|
+
print()
|
|
402
|
+
|
|
403
|
+
if results["error_count"] == 0:
|
|
404
|
+
print("PASS — No formula errors detected")
|
|
405
|
+
else:
|
|
406
|
+
# Separate definitive failures from heuristic warnings
|
|
407
|
+
hard_errors = [e for e in results["errors"] if e["type"] != "unknown_name_ref"]
|
|
408
|
+
warnings = [e for e in results["errors"] if e["type"] == "unknown_name_ref"]
|
|
409
|
+
if hard_errors:
|
|
410
|
+
print(f"FAIL — {len(hard_errors)} error(s) must be fixed before delivery")
|
|
411
|
+
if warnings:
|
|
412
|
+
print(f"WARN — {len(warnings)} heuristic warning(s) require manual review")
|
|
413
|
+
sys.exit(1)
|
|
414
|
+
else:
|
|
415
|
+
# Only heuristic warnings — do not block delivery but alert
|
|
416
|
+
print(f"PASS with WARN — {len(warnings)} heuristic warning(s) require manual review")
|
|
417
|
+
# Exit 0: heuristic warnings alone do not block delivery
|
|
418
|
+
sys.exit(0)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
if __name__ == "__main__":
|
|
422
|
+
main()
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""
|
|
4
|
+
libreoffice_recalc.py — Tier 2 dynamic formula recalculation via LibreOffice headless.
|
|
5
|
+
|
|
6
|
+
Opens the xlsx file with the LibreOffice Calc engine, executes all formulas, writes
|
|
7
|
+
the computed values into the <v> cache elements, and saves the result. This is the
|
|
8
|
+
closest server-side equivalent of "open in Excel and save."
|
|
9
|
+
|
|
10
|
+
After recalculation, run formula_check.py on the output file to detect runtime errors
|
|
11
|
+
(#DIV/0!, #N/A, etc.) that only surface after actual computation.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
python3 libreoffice_recalc.py input.xlsx output.xlsx
|
|
15
|
+
python3 libreoffice_recalc.py input.xlsx output.xlsx --timeout 90
|
|
16
|
+
python3 libreoffice_recalc.py --check # check LibreOffice availability only
|
|
17
|
+
|
|
18
|
+
Exit codes:
|
|
19
|
+
0 — recalculation succeeded, output file written
|
|
20
|
+
2 — LibreOffice not found (Tier 2 unavailable — not a hard failure, note in report)
|
|
21
|
+
1 — LibreOffice found but recalculation failed (timeout, crash, bad file)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
import shutil
|
|
27
|
+
import os
|
|
28
|
+
import tempfile
|
|
29
|
+
import argparse
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ── LibreOffice discovery ───────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
def find_soffice() -> str | None:
|
|
35
|
+
"""
|
|
36
|
+
Locate the soffice (LibreOffice) binary.
|
|
37
|
+
|
|
38
|
+
Search order:
|
|
39
|
+
1. macOS application bundle (default install location)
|
|
40
|
+
2. PATH lookup for 'soffice'
|
|
41
|
+
3. PATH lookup for 'libreoffice' (common on Linux)
|
|
42
|
+
"""
|
|
43
|
+
candidates = [
|
|
44
|
+
"/Applications/LibreOffice.app/Contents/MacOS/soffice", # macOS
|
|
45
|
+
"soffice", # Linux / macOS if on PATH
|
|
46
|
+
"libreoffice", # alternative Linux name
|
|
47
|
+
]
|
|
48
|
+
for c in candidates:
|
|
49
|
+
# shutil.which handles PATH lookup; also check absolute paths directly
|
|
50
|
+
found = shutil.which(c)
|
|
51
|
+
if found:
|
|
52
|
+
return found
|
|
53
|
+
if os.path.isfile(c) and os.access(c, os.X_OK):
|
|
54
|
+
return c
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_libreoffice_version(soffice: str) -> str:
|
|
59
|
+
"""Return LibreOffice version string, or 'unknown' on failure."""
|
|
60
|
+
try:
|
|
61
|
+
result = subprocess.run(
|
|
62
|
+
[soffice, "--version"],
|
|
63
|
+
capture_output=True,
|
|
64
|
+
timeout=10,
|
|
65
|
+
)
|
|
66
|
+
return result.stdout.decode(errors="replace").strip()
|
|
67
|
+
except Exception:
|
|
68
|
+
return "unknown"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ── Recalculation ───────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
def recalculate(
|
|
74
|
+
input_path: str,
|
|
75
|
+
output_path: str,
|
|
76
|
+
timeout: int = 60,
|
|
77
|
+
) -> tuple[bool, str]:
|
|
78
|
+
"""
|
|
79
|
+
Run LibreOffice headless recalculation on input_path, write result to output_path.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
(success: bool, message: str)
|
|
83
|
+
|
|
84
|
+
The message explains what happened (success or failure reason).
|
|
85
|
+
"""
|
|
86
|
+
soffice = find_soffice()
|
|
87
|
+
if not soffice:
|
|
88
|
+
return False, (
|
|
89
|
+
"LibreOffice not found. Tier 2 validation is unavailable in this environment. "
|
|
90
|
+
"Install LibreOffice to enable dynamic formula recalculation.\n"
|
|
91
|
+
" macOS: brew install --cask libreoffice\n"
|
|
92
|
+
" Linux: sudo apt-get install -y libreoffice"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
version = get_libreoffice_version(soffice)
|
|
96
|
+
|
|
97
|
+
# Work on a copy in a temp directory to avoid side effects on the source file.
|
|
98
|
+
# LibreOffice writes the output using the same filename stem in --outdir.
|
|
99
|
+
with tempfile.TemporaryDirectory(prefix="xlsx_recalc_") as tmpdir:
|
|
100
|
+
tmp_input = os.path.join(tmpdir, os.path.basename(input_path))
|
|
101
|
+
shutil.copy(input_path, tmp_input)
|
|
102
|
+
|
|
103
|
+
cmd = [
|
|
104
|
+
soffice,
|
|
105
|
+
"--headless",
|
|
106
|
+
"--norestore", # do not attempt to restore crashed sessions
|
|
107
|
+
"--infilter=Calc MS Excel 2007 XML",
|
|
108
|
+
"--convert-to", "xlsx",
|
|
109
|
+
"--outdir", tmpdir,
|
|
110
|
+
tmp_input,
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
result = subprocess.run(
|
|
115
|
+
cmd,
|
|
116
|
+
capture_output=True,
|
|
117
|
+
timeout=timeout,
|
|
118
|
+
)
|
|
119
|
+
except subprocess.TimeoutExpired:
|
|
120
|
+
return False, (
|
|
121
|
+
f"LibreOffice timed out after {timeout}s. "
|
|
122
|
+
"The file may be too large or contain constructs that cause LibreOffice to hang. "
|
|
123
|
+
"Try increasing --timeout or simplify the file."
|
|
124
|
+
)
|
|
125
|
+
except FileNotFoundError:
|
|
126
|
+
return False, f"LibreOffice binary not executable: {soffice}"
|
|
127
|
+
|
|
128
|
+
if result.returncode != 0:
|
|
129
|
+
stderr = result.stderr.decode(errors="replace").strip()
|
|
130
|
+
stdout = result.stdout.decode(errors="replace").strip()
|
|
131
|
+
return False, (
|
|
132
|
+
f"LibreOffice exited with code {result.returncode}.\n"
|
|
133
|
+
f"stderr: {stderr}\n"
|
|
134
|
+
f"stdout: {stdout}"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# LibreOffice writes: <tmpdir>/<stem>.xlsx
|
|
138
|
+
stem = os.path.splitext(os.path.basename(tmp_input))[0]
|
|
139
|
+
tmp_output = os.path.join(tmpdir, stem + ".xlsx")
|
|
140
|
+
|
|
141
|
+
if not os.path.isfile(tmp_output):
|
|
142
|
+
# Try to find any .xlsx file in tmpdir (LibreOffice may behave differently)
|
|
143
|
+
xlsx_files = [f for f in os.listdir(tmpdir) if f.endswith(".xlsx") and f != os.path.basename(tmp_input)]
|
|
144
|
+
if xlsx_files:
|
|
145
|
+
tmp_output = os.path.join(tmpdir, xlsx_files[0])
|
|
146
|
+
else:
|
|
147
|
+
stdout = result.stdout.decode(errors="replace").strip()
|
|
148
|
+
return False, (
|
|
149
|
+
f"LibreOffice succeeded (exit 0) but output file not found in {tmpdir}.\n"
|
|
150
|
+
f"stdout: {stdout}\n"
|
|
151
|
+
f"Files in tmpdir: {os.listdir(tmpdir)}"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Copy recalculated file to final destination
|
|
155
|
+
os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
|
|
156
|
+
shutil.copy(tmp_output, output_path)
|
|
157
|
+
|
|
158
|
+
return True, f"Recalculation complete. LibreOffice {version}. Output: {output_path}"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# ── CLI ─────────────────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
def main() -> None:
|
|
164
|
+
parser = argparse.ArgumentParser(
|
|
165
|
+
description="LibreOffice headless formula recalculation for xlsx files.",
|
|
166
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
167
|
+
epilog="""
|
|
168
|
+
Examples:
|
|
169
|
+
# Basic recalculation
|
|
170
|
+
python3 libreoffice_recalc.py report.xlsx report_recalc.xlsx
|
|
171
|
+
|
|
172
|
+
# With extended timeout for large files
|
|
173
|
+
python3 libreoffice_recalc.py big_model.xlsx big_model_recalc.xlsx --timeout 120
|
|
174
|
+
|
|
175
|
+
# Check if LibreOffice is available (useful in CI)
|
|
176
|
+
python3 libreoffice_recalc.py --check
|
|
177
|
+
|
|
178
|
+
# Full validation pipeline
|
|
179
|
+
python3 libreoffice_recalc.py input.xlsx /tmp/recalc.xlsx && \\
|
|
180
|
+
python3 formula_check.py /tmp/recalc.xlsx
|
|
181
|
+
""",
|
|
182
|
+
)
|
|
183
|
+
parser.add_argument("input", nargs="?", help="Input xlsx file path")
|
|
184
|
+
parser.add_argument("output", nargs="?", help="Output xlsx file path (recalculated)")
|
|
185
|
+
parser.add_argument(
|
|
186
|
+
"--timeout",
|
|
187
|
+
type=int,
|
|
188
|
+
default=60,
|
|
189
|
+
metavar="SECONDS",
|
|
190
|
+
help="Maximum time to wait for LibreOffice (default: 60)",
|
|
191
|
+
)
|
|
192
|
+
parser.add_argument(
|
|
193
|
+
"--check",
|
|
194
|
+
action="store_true",
|
|
195
|
+
help="Only check if LibreOffice is available, then exit",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
args = parser.parse_args()
|
|
199
|
+
|
|
200
|
+
# ── --check mode ─────────────────────────────────────────────────────────
|
|
201
|
+
if args.check:
|
|
202
|
+
soffice = find_soffice()
|
|
203
|
+
if soffice:
|
|
204
|
+
version = get_libreoffice_version(soffice)
|
|
205
|
+
print(f"LibreOffice available: {soffice}")
|
|
206
|
+
print(f"Version: {version}")
|
|
207
|
+
sys.exit(0)
|
|
208
|
+
else:
|
|
209
|
+
print("LibreOffice NOT available.")
|
|
210
|
+
print("Tier 2 dynamic validation requires LibreOffice.")
|
|
211
|
+
print(" macOS: brew install --cask libreoffice")
|
|
212
|
+
print(" Linux: sudo apt-get install -y libreoffice")
|
|
213
|
+
sys.exit(2)
|
|
214
|
+
|
|
215
|
+
# ── Recalculation mode ────────────────────────────────────────────────────
|
|
216
|
+
if not args.input or not args.output:
|
|
217
|
+
parser.print_help()
|
|
218
|
+
sys.exit(1)
|
|
219
|
+
|
|
220
|
+
if not os.path.isfile(args.input):
|
|
221
|
+
print(f"ERROR: Input file not found: {args.input}")
|
|
222
|
+
sys.exit(1)
|
|
223
|
+
|
|
224
|
+
print(f"Input : {args.input}")
|
|
225
|
+
print(f"Output : {args.output}")
|
|
226
|
+
print(f"Timeout: {args.timeout}s")
|
|
227
|
+
print()
|
|
228
|
+
|
|
229
|
+
success, message = recalculate(args.input, args.output, timeout=args.timeout)
|
|
230
|
+
|
|
231
|
+
if success:
|
|
232
|
+
print(f"OK: {message}")
|
|
233
|
+
print()
|
|
234
|
+
print("Next step: run formula_check.py on the recalculated file to detect runtime errors:")
|
|
235
|
+
print(f" python3 formula_check.py {args.output}")
|
|
236
|
+
sys.exit(0)
|
|
237
|
+
else:
|
|
238
|
+
# Distinguish "not installed" (exit 2) from "failed" (exit 1)
|
|
239
|
+
if "not found" in message.lower() or "not available" in message.lower():
|
|
240
|
+
print(f"SKIP (Tier 2 unavailable): {message}")
|
|
241
|
+
sys.exit(2)
|
|
242
|
+
else:
|
|
243
|
+
print(f"ERROR: {message}")
|
|
244
|
+
sys.exit(1)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
if __name__ == "__main__":
|
|
248
|
+
main()
|