Files
Mimikko-zeus ae208af6a9 Please enter the commit message for your changes. Lines starting
with '#' will be ignored, and an empty message aborts the commit.

On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
	new file:   .claude/skills/algorithmic-art/.openskills.json
	new file:   .claude/skills/algorithmic-art/LICENSE.txt
	new file:   .claude/skills/algorithmic-art/SKILL.md
	new file:   .claude/skills/algorithmic-art/templates/generator_template.js
	new file:   .claude/skills/algorithmic-art/templates/viewer.html
	new file:   .claude/skills/brand-guidelines/.openskills.json
	new file:   .claude/skills/brand-guidelines/LICENSE.txt
	new file:   .claude/skills/brand-guidelines/SKILL.md
	new file:   .claude/skills/canvas-design/.openskills.json
	new file:   .claude/skills/canvas-design/LICENSE.txt
	new file:   .claude/skills/canvas-design/SKILL.md
	new file:   .claude/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/DMMono-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/Gloock-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/Italiana-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/Jura-Light.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/Jura-Medium.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/Jura-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/Lora-Bold.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/Lora-Italic.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/Lora-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/Lora-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/Outfit-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/Tektur-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf
	new file:   .claude/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt
	new file:   .claude/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf
	new file:   .claude/skills/doc-coauthoring/.openskills.json
	new file:   .claude/skills/doc-coauthoring/SKILL.md
	new file:   .claude/skills/docx/.openskills.json
	new file:   .claude/skills/docx/LICENSE.txt
	new file:   .claude/skills/docx/SKILL.md
	new file:   .claude/skills/docx/scripts/__init__.py
	new file:   .claude/skills/docx/scripts/accept_changes.py
	new file:   .claude/skills/docx/scripts/comment.py
	new file:   .claude/skills/docx/scripts/office/helpers/__init__.py
	new file:   .claude/skills/docx/scripts/office/helpers/merge_runs.py
	new file:   .claude/skills/docx/scripts/office/helpers/simplify_redlines.py
	new file:   .claude/skills/docx/scripts/office/pack.py
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/mce/mc.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd
	new file:   .claude/skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd
	new file:   .claude/skills/docx/scripts/office/soffice.py
	new file:   .claude/skills/docx/scripts/office/unpack.py
	new file:   .claude/skills/docx/scripts/office/validate.py
	new file:   .claude/skills/docx/scripts/office/validators/__init__.py
	new file:   .claude/skills/docx/scripts/office/validators/base.py
	new file:   .claude/skills/docx/scripts/office/validators/docx.py
	new file:   .claude/skills/docx/scripts/office/validators/pptx.py
	new file:   .claude/skills/docx/scripts/office/validators/redlining.py
	new file:   .claude/skills/docx/scripts/templates/comments.xml
	new file:   .claude/skills/docx/scripts/templates/commentsExtended.xml
	new file:   .claude/skills/docx/scripts/templates/commentsExtensible.xml
	new file:   .claude/skills/docx/scripts/templates/commentsIds.xml
	new file:   .claude/skills/docx/scripts/templates/people.xml
	new file:   .claude/skills/frontend-design/.openskills.json
	new file:   .claude/skills/frontend-design/LICENSE.txt
	new file:   .claude/skills/frontend-design/SKILL.md
	new file:   .claude/skills/internal-comms/.openskills.json
	new file:   .claude/skills/internal-comms/LICENSE.txt
	new file:   .claude/skills/internal-comms/SKILL.md
	new file:   .claude/skills/internal-comms/examples/3p-updates.md
	new file:   .claude/skills/internal-comms/examples/company-newsletter.md
	new file:   .claude/skills/internal-comms/examples/faq-answers.md
	new file:   .claude/skills/internal-comms/examples/general-comms.md
	new file:   .claude/skills/mcp-builder/.openskills.json
	new file:   .claude/skills/mcp-builder/LICENSE.txt
	new file:   .claude/skills/mcp-builder/SKILL.md
	new file:   .claude/skills/mcp-builder/reference/evaluation.md
	new file:   .claude/skills/mcp-builder/reference/mcp_best_practices.md
	new file:   .claude/skills/mcp-builder/reference/node_mcp_server.md
	new file:   .claude/skills/mcp-builder/reference/python_mcp_server.md
	new file:   .claude/skills/mcp-builder/scripts/connections.py
	new file:   .claude/skills/mcp-builder/scripts/evaluation.py
	new file:   .claude/skills/mcp-builder/scripts/example_evaluation.xml
	new file:   .claude/skills/mcp-builder/scripts/requirements.txt
	new file:   .claude/skills/pdf/.openskills.json
	new file:   .claude/skills/pdf/LICENSE.txt
	new file:   .claude/skills/pdf/SKILL.md
	new file:   .claude/skills/pdf/forms.md
	new file:   .claude/skills/pdf/reference.md
	new file:   .claude/skills/pdf/scripts/check_bounding_boxes.py
	new file:   .claude/skills/pdf/scripts/check_fillable_fields.py
	new file:   .claude/skills/pdf/scripts/convert_pdf_to_images.py
	new file:   .claude/skills/pdf/scripts/create_validation_image.py
	new file:   .claude/skills/pdf/scripts/extract_form_field_info.py
	new file:   .claude/skills/pdf/scripts/extract_form_structure.py
	new file:   .claude/skills/pdf/scripts/fill_fillable_fields.py
	new file:   .claude/skills/pdf/scripts/fill_pdf_form_with_annotations.py
	new file:   .claude/skills/pptx/.openskills.json
	new file:   .claude/skills/pptx/LICENSE.txt
	new file:   .claude/skills/pptx/SKILL.md
	new file:   .claude/skills/pptx/editing.md
	new file:   .claude/skills/pptx/pptxgenjs.md
	new file:   .claude/skills/pptx/scripts/__init__.py
	new file:   .claude/skills/pptx/scripts/add_slide.py
	new file:   .claude/skills/pptx/scripts/clean.py
	new file:   .claude/skills/pptx/scripts/office/helpers/__init__.py
	new file:   .claude/skills/pptx/scripts/office/helpers/merge_runs.py
	new file:   .claude/skills/pptx/scripts/office/helpers/simplify_redlines.py
	new file:   .claude/skills/pptx/scripts/office/pack.py
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/mce/mc.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd
	new file:   .claude/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd
	new file:   .claude/skills/pptx/scripts/office/soffice.py
	new file:   .claude/skills/pptx/scripts/office/unpack.py
	new file:   .claude/skills/pptx/scripts/office/validate.py
	new file:   .claude/skills/pptx/scripts/office/validators/__init__.py
	new file:   .claude/skills/pptx/scripts/office/validators/base.py
	new file:   .claude/skills/pptx/scripts/office/validators/docx.py
	new file:   .claude/skills/pptx/scripts/office/validators/pptx.py
	new file:   .claude/skills/pptx/scripts/office/validators/redlining.py
	new file:   .claude/skills/pptx/scripts/thumbnail.py
	new file:   .claude/skills/skill-creator/.openskills.json
	new file:   .claude/skills/skill-creator/LICENSE.txt
	new file:   .claude/skills/skill-creator/SKILL.md
	new file:   .claude/skills/skill-creator/agents/analyzer.md
	new file:   .claude/skills/skill-creator/agents/comparator.md
	new file:   .claude/skills/skill-creator/agents/grader.md
	new file:   .claude/skills/skill-creator/assets/eval_review.html
	new file:   .claude/skills/skill-creator/eval-viewer/generate_review.py
	new file:   .claude/skills/skill-creator/eval-viewer/viewer.html
	new file:   .claude/skills/skill-creator/references/schemas.md
	new file:   .claude/skills/skill-creator/scripts/__init__.py
	new file:   .claude/skills/skill-creator/scripts/aggregate_benchmark.py
	new file:   .claude/skills/skill-creator/scripts/generate_report.py
	new file:   .claude/skills/skill-creator/scripts/improve_description.py
	new file:   .claude/skills/skill-creator/scripts/package_skill.py
	new file:   .claude/skills/skill-creator/scripts/quick_validate.py
	new file:   .claude/skills/skill-creator/scripts/run_eval.py
	new file:   .claude/skills/skill-creator/scripts/run_loop.py
	new file:   .claude/skills/skill-creator/scripts/utils.py
	new file:   .claude/skills/slack-gif-creator/.openskills.json
	new file:   .claude/skills/slack-gif-creator/LICENSE.txt
	new file:   .claude/skills/slack-gif-creator/SKILL.md
	new file:   .claude/skills/slack-gif-creator/core/easing.py
	new file:   .claude/skills/slack-gif-creator/core/frame_composer.py
	new file:   .claude/skills/slack-gif-creator/core/gif_builder.py
	new file:   .claude/skills/slack-gif-creator/core/validators.py
	new file:   .claude/skills/slack-gif-creator/requirements.txt
	new file:   .claude/skills/template/.openskills.json
	new file:   .claude/skills/template/SKILL.md
	new file:   .claude/skills/theme-factory/.openskills.json
	new file:   .claude/skills/theme-factory/LICENSE.txt
	new file:   .claude/skills/theme-factory/SKILL.md
	new file:   .claude/skills/theme-factory/theme-showcase.pdf
	new file:   .claude/skills/theme-factory/themes/arctic-frost.md
	new file:   .claude/skills/theme-factory/themes/botanical-garden.md
	new file:   .claude/skills/theme-factory/themes/desert-rose.md
	new file:   .claude/skills/theme-factory/themes/forest-canopy.md
	new file:   .claude/skills/theme-factory/themes/golden-hour.md
	new file:   .claude/skills/theme-factory/themes/midnight-galaxy.md
	new file:   .claude/skills/theme-factory/themes/modern-minimalist.md
	new file:   .claude/skills/theme-factory/themes/ocean-depths.md
	new file:   .claude/skills/theme-factory/themes/sunset-boulevard.md
	new file:   .claude/skills/theme-factory/themes/tech-innovation.md
	new file:   .claude/skills/web-artifacts-builder/.openskills.json
	new file:   .claude/skills/web-artifacts-builder/LICENSE.txt
	new file:   .claude/skills/web-artifacts-builder/SKILL.md
	new file:   .claude/skills/web-artifacts-builder/scripts/bundle-artifact.sh
	new file:   .claude/skills/web-artifacts-builder/scripts/init-artifact.sh
	new file:   .claude/skills/web-artifacts-builder/scripts/shadcn-components.tar.gz
	new file:   .claude/skills/webapp-testing/.openskills.json
	new file:   .claude/skills/webapp-testing/LICENSE.txt
	new file:   .claude/skills/webapp-testing/SKILL.md
	new file:   .claude/skills/webapp-testing/examples/console_logging.py
	new file:   .claude/skills/webapp-testing/examples/element_discovery.py
	new file:   .claude/skills/webapp-testing/examples/static_html_automation.py
	new file:   .claude/skills/webapp-testing/scripts/with_server.py
	new file:   .claude/skills/xlsx/.openskills.json
	new file:   .claude/skills/xlsx/LICENSE.txt
	new file:   .claude/skills/xlsx/SKILL.md
	new file:   .claude/skills/xlsx/scripts/office/helpers/__init__.py
	new file:   .claude/skills/xlsx/scripts/office/helpers/merge_runs.py
	new file:   .claude/skills/xlsx/scripts/office/helpers/simplify_redlines.py
	new file:   .claude/skills/xlsx/scripts/office/pack.py
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/mce/mc.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd
	new file:   .claude/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd
	new file:   .claude/skills/xlsx/scripts/office/soffice.py
	new file:   .claude/skills/xlsx/scripts/office/unpack.py
	new file:   .claude/skills/xlsx/scripts/office/validate.py
	new file:   .claude/skills/xlsx/scripts/office/validators/__init__.py
	new file:   .claude/skills/xlsx/scripts/office/validators/base.py
	new file:   .claude/skills/xlsx/scripts/office/validators/docx.py
	new file:   .claude/skills/xlsx/scripts/office/validators/pptx.py
	new file:   .claude/skills/xlsx/scripts/office/validators/redlining.py
	new file:   .claude/skills/xlsx/scripts/recalc.py
	new file:   .env.example
	new file:   .gitignore
	new file:   config/mcp.json
	new file:   config/models.json
	new file:   config/personalities.json
	new file:   docs/AGENTS.md
	new file:   docs/AI_IMPLEMENTATION.md
	new file:   docs/AI_INTEGRATION_COMPLETE.md
	new file:   docs/AI_QUICKSTART.md
	new file:   docs/AI_SUMMARY.md
	new file:   docs/CHANGELOG.md
	new file:   docs/CONFIG_GUIDE.md
	new file:   docs/FIXES.md
	new file:   docs/PROJECT_REFACTOR.md
	new file:   docs/README.md
	new file:   docs/README_INDEX.md
	new file:   examples/ai_example.py
	new file:   main.py
	new file:   pytest.ini
	new file:   requirements.txt
	new file:   scripts/migrate_to_vector_db.py
	new file:   skills/cmd_zip_skill/README.md
	new file:   skills/cmd_zip_skill/__init__.py
	new file:   skills/cmd_zip_skill/main.py
	new file:   skills/cmd_zip_skill/skill.json
	new file:   skills/cmd_zip_skill_1772465404375/README.md
	new file:   skills/cmd_zip_skill_1772465404375/__init__.py
	new file:   skills/cmd_zip_skill_1772465404375/main.py
	new file:   skills/cmd_zip_skill_1772465404375/skill.json
	new file:   skills/cmd_zip_skill_1772465434774/README.md
	new file:   skills/cmd_zip_skill_1772465434774/__init__.py
	new file:   skills/cmd_zip_skill_1772465434774/main.py
	new file:   skills/cmd_zip_skill_1772465434774/skill.json
	new file:   skills/cmd_zip_skill_1772465467809/README.md
	new file:   skills/cmd_zip_skill_1772465467809/__init__.py
	new file:   skills/cmd_zip_skill_1772465467809/main.py
	new file:   skills/cmd_zip_skill_1772465467809/skill.json
	new file:   skills/cmd_zip_skill_1772465652075/README.md
	new file:   skills/cmd_zip_skill_1772465652075/__init__.py
	new file:   skills/cmd_zip_skill_1772465652075/main.py
	new file:   skills/cmd_zip_skill_1772465652075/skill.json
	new file:   skills/cmd_zip_skill_1772465685352/README.md
	new file:   skills/cmd_zip_skill_1772465685352/__init__.py
	new file:   skills/cmd_zip_skill_1772465685352/main.py
	new file:   skills/cmd_zip_skill_1772465685352/skill.json
	new file:   skills/cmd_zip_skill_1772465936294/README.md
	new file:   skills/cmd_zip_skill_1772465936294/__init__.py
	new file:   skills/cmd_zip_skill_1772465936294/main.py
	new file:   skills/cmd_zip_skill_1772465936294/skill.json
	new file:   skills/cmd_zip_skill_1772465966322/README.md
	new file:   skills/cmd_zip_skill_1772465966322/__init__.py
	new file:   skills/cmd_zip_skill_1772465966322/main.py
	new file:   skills/cmd_zip_skill_1772465966322/skill.json
	new file:   skills/cmd_zip_skill_1772466071278/README.md
	new file:   skills/cmd_zip_skill_1772466071278/__init__.py
	new file:   skills/cmd_zip_skill_1772466071278/main.py
	new file:   skills/cmd_zip_skill_1772466071278/skill.json
	new file:   skills/skills_creator/README.md
	new file:   skills/skills_creator/__init__.py
	new file:   skills/skills_creator/main.py
	new file:   skills/skills_creator/skill.json
	new file:   src/__init__.py
	new file:   src/ai/__init__.py
	new file:   src/ai/base.py
	new file:   src/ai/client.py
	new file:   src/ai/docs/README.md
	new file:   src/ai/mcp/__init__.py
	new file:   src/ai/mcp/base.py
	new file:   src/ai/mcp/servers/__init__.py
	new file:   src/ai/mcp/servers/filesystem.py
	new file:   src/ai/memory.py
	new file:   src/ai/models/__init__.py
	new file:   src/ai/models/anthropic_model.py
	new file:   src/ai/models/openai_model.py
	new file:   src/ai/personality.py
	new file:   src/ai/skills/__init__.py
	new file:   src/ai/skills/base.py
	new file:   src/ai/task_manager.py
	new file:   src/ai/vector_store/__init__.py
	new file:   src/ai/vector_store/base.py
	new file:   src/ai/vector_store/chroma_store.py
	new file:   src/ai/vector_store/json_store.py
	new file:   src/core/__init__.py
	new file:   src/core/bot.py
	new file:   src/core/config.py
	new file:   src/handlers/__init__.py
	new file:   src/handlers/message_handler.py
	new file:   src/handlers/message_handler_ai.py
	new file:   src/utils/__init__.py
	new file:   src/utils/logger.py
	new file:   start.bat
	new file:   tests/test_ai.py
2026-03-03 01:23:23 +08:00

333 lines
13 KiB
Python

#!/usr/bin/env python3
"""Run the eval + improve loop until all pass or max iterations reached.
Combines run_eval.py and improve_description.py in a loop, tracking history
and returning the best description found. Supports train/test split to prevent
overfitting.
"""
import argparse
import json
import random
import sys
import tempfile
import time
import webbrowser
from pathlib import Path
import anthropic
from scripts.generate_report import generate_html
from scripts.improve_description import improve_description
from scripts.run_eval import find_project_root, run_eval
from scripts.utils import parse_skill_md
def split_eval_set(eval_set: list[dict], holdout: float, seed: int = 42) -> tuple[list[dict], list[dict]]:
"""Split eval set into train and test sets, stratified by should_trigger."""
random.seed(seed)
# Separate by should_trigger
trigger = [e for e in eval_set if e["should_trigger"]]
no_trigger = [e for e in eval_set if not e["should_trigger"]]
# Shuffle each group
random.shuffle(trigger)
random.shuffle(no_trigger)
# Calculate split points
n_trigger_test = max(1, int(len(trigger) * holdout))
n_no_trigger_test = max(1, int(len(no_trigger) * holdout))
# Split
test_set = trigger[:n_trigger_test] + no_trigger[:n_no_trigger_test]
train_set = trigger[n_trigger_test:] + no_trigger[n_no_trigger_test:]
return train_set, test_set
def run_loop(
eval_set: list[dict],
skill_path: Path,
description_override: str | None,
num_workers: int,
timeout: int,
max_iterations: int,
runs_per_query: int,
trigger_threshold: float,
holdout: float,
model: str,
verbose: bool,
live_report_path: Path | None = None,
log_dir: Path | None = None,
) -> dict:
"""Run the eval + improvement loop."""
project_root = find_project_root()
name, original_description, content = parse_skill_md(skill_path)
current_description = description_override or original_description
# Split into train/test if holdout > 0
if holdout > 0:
train_set, test_set = split_eval_set(eval_set, holdout)
if verbose:
print(f"Split: {len(train_set)} train, {len(test_set)} test (holdout={holdout})", file=sys.stderr)
else:
train_set = eval_set
test_set = []
client = anthropic.Anthropic()
history = []
exit_reason = "unknown"
for iteration in range(1, max_iterations + 1):
if verbose:
print(f"\n{'='*60}", file=sys.stderr)
print(f"Iteration {iteration}/{max_iterations}", file=sys.stderr)
print(f"Description: {current_description}", file=sys.stderr)
print(f"{'='*60}", file=sys.stderr)
# Evaluate train + test together in one batch for parallelism
all_queries = train_set + test_set
t0 = time.time()
all_results = run_eval(
eval_set=all_queries,
skill_name=name,
description=current_description,
num_workers=num_workers,
timeout=timeout,
project_root=project_root,
runs_per_query=runs_per_query,
trigger_threshold=trigger_threshold,
model=model,
)
eval_elapsed = time.time() - t0
# Split results back into train/test by matching queries
train_queries_set = {q["query"] for q in train_set}
train_result_list = [r for r in all_results["results"] if r["query"] in train_queries_set]
test_result_list = [r for r in all_results["results"] if r["query"] not in train_queries_set]
train_passed = sum(1 for r in train_result_list if r["pass"])
train_total = len(train_result_list)
train_summary = {"passed": train_passed, "failed": train_total - train_passed, "total": train_total}
train_results = {"results": train_result_list, "summary": train_summary}
if test_set:
test_passed = sum(1 for r in test_result_list if r["pass"])
test_total = len(test_result_list)
test_summary = {"passed": test_passed, "failed": test_total - test_passed, "total": test_total}
test_results = {"results": test_result_list, "summary": test_summary}
else:
test_results = None
test_summary = None
history.append({
"iteration": iteration,
"description": current_description,
"train_passed": train_summary["passed"],
"train_failed": train_summary["failed"],
"train_total": train_summary["total"],
"train_results": train_results["results"],
"test_passed": test_summary["passed"] if test_summary else None,
"test_failed": test_summary["failed"] if test_summary else None,
"test_total": test_summary["total"] if test_summary else None,
"test_results": test_results["results"] if test_results else None,
# For backward compat with report generator
"passed": train_summary["passed"],
"failed": train_summary["failed"],
"total": train_summary["total"],
"results": train_results["results"],
})
# Write live report if path provided
if live_report_path:
partial_output = {
"original_description": original_description,
"best_description": current_description,
"best_score": "in progress",
"iterations_run": len(history),
"holdout": holdout,
"train_size": len(train_set),
"test_size": len(test_set),
"history": history,
}
live_report_path.write_text(generate_html(partial_output, auto_refresh=True, skill_name=name))
if verbose:
def print_eval_stats(label, results, elapsed):
pos = [r for r in results if r["should_trigger"]]
neg = [r for r in results if not r["should_trigger"]]
tp = sum(r["triggers"] for r in pos)
pos_runs = sum(r["runs"] for r in pos)
fn = pos_runs - tp
fp = sum(r["triggers"] for r in neg)
neg_runs = sum(r["runs"] for r in neg)
tn = neg_runs - fp
total = tp + tn + fp + fn
precision = tp / (tp + fp) if (tp + fp) > 0 else 1.0
recall = tp / (tp + fn) if (tp + fn) > 0 else 1.0
accuracy = (tp + tn) / total if total > 0 else 0.0
print(f"{label}: {tp+tn}/{total} correct, precision={precision:.0%} recall={recall:.0%} accuracy={accuracy:.0%} ({elapsed:.1f}s)", file=sys.stderr)
for r in results:
status = "PASS" if r["pass"] else "FAIL"
rate_str = f"{r['triggers']}/{r['runs']}"
print(f" [{status}] rate={rate_str} expected={r['should_trigger']}: {r['query'][:60]}", file=sys.stderr)
print_eval_stats("Train", train_results["results"], eval_elapsed)
if test_summary:
print_eval_stats("Test ", test_results["results"], 0)
if train_summary["failed"] == 0:
exit_reason = f"all_passed (iteration {iteration})"
if verbose:
print(f"\nAll train queries passed on iteration {iteration}!", file=sys.stderr)
break
if iteration == max_iterations:
exit_reason = f"max_iterations ({max_iterations})"
if verbose:
print(f"\nMax iterations reached ({max_iterations}).", file=sys.stderr)
break
# Improve the description based on train results
if verbose:
print(f"\nImproving description...", file=sys.stderr)
t0 = time.time()
# Strip test scores from history so improvement model can't see them
blinded_history = [
{k: v for k, v in h.items() if not k.startswith("test_")}
for h in history
]
new_description = improve_description(
client=client,
skill_name=name,
skill_content=content,
current_description=current_description,
eval_results=train_results,
history=blinded_history,
model=model,
log_dir=log_dir,
iteration=iteration,
)
improve_elapsed = time.time() - t0
if verbose:
print(f"Proposed ({improve_elapsed:.1f}s): {new_description}", file=sys.stderr)
current_description = new_description
# Find the best iteration by TEST score (or train if no test set)
if test_set:
best = max(history, key=lambda h: h["test_passed"] or 0)
best_score = f"{best['test_passed']}/{best['test_total']}"
else:
best = max(history, key=lambda h: h["train_passed"])
best_score = f"{best['train_passed']}/{best['train_total']}"
if verbose:
print(f"\nExit reason: {exit_reason}", file=sys.stderr)
print(f"Best score: {best_score} (iteration {best['iteration']})", file=sys.stderr)
return {
"exit_reason": exit_reason,
"original_description": original_description,
"best_description": best["description"],
"best_score": best_score,
"best_train_score": f"{best['train_passed']}/{best['train_total']}",
"best_test_score": f"{best['test_passed']}/{best['test_total']}" if test_set else None,
"final_description": current_description,
"iterations_run": len(history),
"holdout": holdout,
"train_size": len(train_set),
"test_size": len(test_set),
"history": history,
}
def main():
parser = argparse.ArgumentParser(description="Run eval + improve loop")
parser.add_argument("--eval-set", required=True, help="Path to eval set JSON file")
parser.add_argument("--skill-path", required=True, help="Path to skill directory")
parser.add_argument("--description", default=None, help="Override starting description")
parser.add_argument("--num-workers", type=int, default=10, help="Number of parallel workers")
parser.add_argument("--timeout", type=int, default=30, help="Timeout per query in seconds")
parser.add_argument("--max-iterations", type=int, default=5, help="Max improvement iterations")
parser.add_argument("--runs-per-query", type=int, default=3, help="Number of runs per query")
parser.add_argument("--trigger-threshold", type=float, default=0.5, help="Trigger rate threshold")
parser.add_argument("--holdout", type=float, default=0.4, help="Fraction of eval set to hold out for testing (0 to disable)")
parser.add_argument("--model", required=True, help="Model for improvement")
parser.add_argument("--verbose", action="store_true", help="Print progress to stderr")
parser.add_argument("--report", default="auto", help="Generate HTML report at this path (default: 'auto' for temp file, 'none' to disable)")
parser.add_argument("--results-dir", default=None, help="Save all outputs (results.json, report.html, log.txt) to a timestamped subdirectory here")
args = parser.parse_args()
eval_set = json.loads(Path(args.eval_set).read_text())
skill_path = Path(args.skill_path)
if not (skill_path / "SKILL.md").exists():
print(f"Error: No SKILL.md found at {skill_path}", file=sys.stderr)
sys.exit(1)
name, _, _ = parse_skill_md(skill_path)
# Set up live report path
if args.report != "none":
if args.report == "auto":
timestamp = time.strftime("%Y%m%d_%H%M%S")
live_report_path = Path(tempfile.gettempdir()) / f"skill_description_report_{skill_path.name}_{timestamp}.html"
else:
live_report_path = Path(args.report)
# Open the report immediately so the user can watch
live_report_path.write_text("<html><body><h1>Starting optimization loop...</h1><meta http-equiv='refresh' content='5'></body></html>")
webbrowser.open(str(live_report_path))
else:
live_report_path = None
# Determine output directory (create before run_loop so logs can be written)
if args.results_dir:
timestamp = time.strftime("%Y-%m-%d_%H%M%S")
results_dir = Path(args.results_dir) / timestamp
results_dir.mkdir(parents=True, exist_ok=True)
else:
results_dir = None
log_dir = results_dir / "logs" if results_dir else None
output = run_loop(
eval_set=eval_set,
skill_path=skill_path,
description_override=args.description,
num_workers=args.num_workers,
timeout=args.timeout,
max_iterations=args.max_iterations,
runs_per_query=args.runs_per_query,
trigger_threshold=args.trigger_threshold,
holdout=args.holdout,
model=args.model,
verbose=args.verbose,
live_report_path=live_report_path,
log_dir=log_dir,
)
# Save JSON output
json_output = json.dumps(output, indent=2)
print(json_output)
if results_dir:
(results_dir / "results.json").write_text(json_output)
# Write final HTML report (without auto-refresh)
if live_report_path:
live_report_path.write_text(generate_html(output, auto_refresh=False, skill_name=name))
print(f"\nReport: {live_report_path}", file=sys.stderr)
if results_dir and live_report_path:
(results_dir / "report.html").write_text(generate_html(output, auto_refresh=False, skill_name=name))
if results_dir:
print(f"Results saved to: {results_dir}", file=sys.stderr)
if __name__ == "__main__":
main()