@takuma-hirai/hirai-method 0.1.0
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/.claude/.stale-harness-state/last-check +0 -0
- package/.claude/CommonRules.md +121 -0
- package/.claude/agents/01-core-development/api-designer.md +237 -0
- package/.claude/agents/01-core-development/backend-developer.md +222 -0
- package/.claude/agents/01-core-development/design-bridge.md +127 -0
- package/.claude/agents/01-core-development/electron-pro.md +240 -0
- package/.claude/agents/01-core-development/frontend-developer.md +133 -0
- package/.claude/agents/01-core-development/fullstack-developer.md +235 -0
- package/.claude/agents/01-core-development/graphql-architect.md +238 -0
- package/.claude/agents/01-core-development/microservices-architect.md +239 -0
- package/.claude/agents/01-core-development/mobile-developer.md +283 -0
- package/.claude/agents/01-core-development/ui-designer.md +174 -0
- package/.claude/agents/01-core-development/websocket-engineer.md +150 -0
- package/.claude/agents/03-infrastructure/azure-infra-engineer.md +53 -0
- package/.claude/agents/03-infrastructure/cloud-architect.md +277 -0
- package/.claude/agents/03-infrastructure/database-administrator.md +287 -0
- package/.claude/agents/03-infrastructure/deployment-engineer.md +287 -0
- package/.claude/agents/03-infrastructure/devops-engineer.md +287 -0
- package/.claude/agents/03-infrastructure/devops-incident-responder.md +287 -0
- package/.claude/agents/03-infrastructure/docker-expert.md +278 -0
- package/.claude/agents/03-infrastructure/incident-responder.md +287 -0
- package/.claude/agents/03-infrastructure/kubernetes-specialist.md +287 -0
- package/.claude/agents/03-infrastructure/network-engineer.md +287 -0
- package/.claude/agents/03-infrastructure/platform-engineer.md +287 -0
- package/.claude/agents/03-infrastructure/security-engineer.md +277 -0
- package/.claude/agents/03-infrastructure/sre-engineer.md +287 -0
- package/.claude/agents/03-infrastructure/terraform-engineer.md +287 -0
- package/.claude/agents/03-infrastructure/terragrunt-expert.md +307 -0
- package/.claude/agents/03-infrastructure/windows-infra-admin.md +52 -0
- package/.claude/agents/04-quality-security/accessibility-tester.md +277 -0
- package/.claude/agents/04-quality-security/ad-security-reviewer.md +56 -0
- package/.claude/agents/04-quality-security/ai-writing-auditor.md +77 -0
- package/.claude/agents/04-quality-security/architect-reviewer.md +287 -0
- package/.claude/agents/04-quality-security/chaos-engineer.md +277 -0
- package/.claude/agents/04-quality-security/code-reviewer.md +287 -0
- package/.claude/agents/04-quality-security/compliance-auditor.md +277 -0
- package/.claude/agents/04-quality-security/debugger.md +287 -0
- package/.claude/agents/04-quality-security/error-detective.md +287 -0
- package/.claude/agents/04-quality-security/penetration-tester.md +287 -0
- package/.claude/agents/04-quality-security/performance-engineer.md +287 -0
- package/.claude/agents/04-quality-security/powershell-security-hardening.md +54 -0
- package/.claude/agents/04-quality-security/qa-expert.md +287 -0
- package/.claude/agents/04-quality-security/security-auditor.md +287 -0
- package/.claude/agents/04-quality-security/test-automator.md +287 -0
- package/.claude/agents/04-quality-security/ui-ux-tester.md +234 -0
- package/.claude/agents/06-developer-experience/build-engineer.md +286 -0
- package/.claude/agents/06-developer-experience/cli-developer.md +286 -0
- package/.claude/agents/06-developer-experience/dependency-manager.md +286 -0
- package/.claude/agents/06-developer-experience/documentation-engineer.md +276 -0
- package/.claude/agents/06-developer-experience/dx-optimizer.md +286 -0
- package/.claude/agents/06-developer-experience/git-workflow-manager.md +286 -0
- package/.claude/agents/06-developer-experience/legacy-modernizer.md +286 -0
- package/.claude/agents/06-developer-experience/mcp-developer.md +275 -0
- package/.claude/agents/06-developer-experience/powershell-module-architect.md +58 -0
- package/.claude/agents/06-developer-experience/powershell-ui-architect.md +135 -0
- package/.claude/agents/06-developer-experience/readme-generator.md +238 -0
- package/.claude/agents/06-developer-experience/refactoring-specialist.md +286 -0
- package/.claude/agents/06-developer-experience/slack-expert.md +232 -0
- package/.claude/agents/06-developer-experience/tooling-engineer.md +286 -0
- package/.claude/agents/09-meta-orchestration/agent-installer.md +97 -0
- package/.claude/agents/09-meta-orchestration/agent-organizer.md +287 -0
- package/.claude/agents/09-meta-orchestration/codebase-orchestrator.md +249 -0
- package/.claude/agents/09-meta-orchestration/context-manager.md +287 -0
- package/.claude/agents/09-meta-orchestration/error-coordinator.md +287 -0
- package/.claude/agents/09-meta-orchestration/it-ops-orchestrator.md +60 -0
- package/.claude/agents/09-meta-orchestration/knowledge-synthesizer.md +287 -0
- package/.claude/agents/09-meta-orchestration/multi-agent-coordinator.md +287 -0
- package/.claude/agents/09-meta-orchestration/performance-monitor.md +287 -0
- package/.claude/agents/09-meta-orchestration/task-distributor.md +287 -0
- package/.claude/agents/09-meta-orchestration/workflow-orchestrator.md +287 -0
- package/.claude/agents/10-research-analysis/competitive-analyst.md +287 -0
- package/.claude/agents/10-research-analysis/data-researcher.md +287 -0
- package/.claude/agents/10-research-analysis/market-researcher.md +287 -0
- package/.claude/agents/10-research-analysis/project-idea-validator.md +269 -0
- package/.claude/agents/10-research-analysis/research-analyst.md +287 -0
- package/.claude/agents/10-research-analysis/scientific-literature-researcher.md +151 -0
- package/.claude/agents/10-research-analysis/search-specialist.md +287 -0
- package/.claude/agents/10-research-analysis/trend-analyst.md +287 -0
- package/.claude/archive/README.md +47 -0
- package/.claude/archive/agents/02-language-specialists/angular-architect.md +287 -0
- package/.claude/archive/agents/02-language-specialists/cpp-pro.md +277 -0
- package/.claude/archive/agents/02-language-specialists/csharp-developer.md +287 -0
- package/.claude/archive/agents/02-language-specialists/django-developer.md +287 -0
- package/.claude/archive/agents/02-language-specialists/dotnet-core-expert.md +287 -0
- package/.claude/archive/agents/02-language-specialists/dotnet-framework-4.8-expert.md +306 -0
- package/.claude/archive/agents/02-language-specialists/elixir-expert.md +311 -0
- package/.claude/archive/agents/02-language-specialists/expo-react-native-expert.md +268 -0
- package/.claude/archive/agents/02-language-specialists/fastapi-developer.md +287 -0
- package/.claude/archive/agents/02-language-specialists/flutter-expert.md +287 -0
- package/.claude/archive/agents/02-language-specialists/golang-pro.md +277 -0
- package/.claude/archive/agents/02-language-specialists/java-architect.md +287 -0
- package/.claude/archive/agents/02-language-specialists/javascript-pro.md +277 -0
- package/.claude/archive/agents/02-language-specialists/kotlin-specialist.md +287 -0
- package/.claude/archive/agents/02-language-specialists/laravel-specialist.md +287 -0
- package/.claude/archive/agents/02-language-specialists/nextjs-developer.md +287 -0
- package/.claude/archive/agents/02-language-specialists/node-specialist.md +124 -0
- package/.claude/archive/agents/02-language-specialists/php-pro.md +287 -0
- package/.claude/archive/agents/02-language-specialists/powershell-5.1-expert.md +59 -0
- package/.claude/archive/agents/02-language-specialists/powershell-7-expert.md +57 -0
- package/.claude/archive/agents/02-language-specialists/python-pro.md +277 -0
- package/.claude/archive/agents/02-language-specialists/rails-expert.md +358 -0
- package/.claude/archive/agents/02-language-specialists/react-specialist.md +287 -0
- package/.claude/archive/agents/02-language-specialists/rust-engineer.md +287 -0
- package/.claude/archive/agents/02-language-specialists/spring-boot-engineer.md +287 -0
- package/.claude/archive/agents/02-language-specialists/sql-pro.md +287 -0
- package/.claude/archive/agents/02-language-specialists/swift-expert.md +287 -0
- package/.claude/archive/agents/02-language-specialists/symfony-specialist.md +354 -0
- package/.claude/archive/agents/02-language-specialists/typescript-pro.md +277 -0
- package/.claude/archive/agents/02-language-specialists/vue-expert.md +287 -0
- package/.claude/archive/agents/05-data-ai/ai-engineer.md +287 -0
- package/.claude/archive/agents/05-data-ai/data-analyst.md +277 -0
- package/.claude/archive/agents/05-data-ai/data-engineer.md +287 -0
- package/.claude/archive/agents/05-data-ai/data-scientist.md +287 -0
- package/.claude/archive/agents/05-data-ai/database-optimizer.md +287 -0
- package/.claude/archive/agents/05-data-ai/llm-architect.md +287 -0
- package/.claude/archive/agents/05-data-ai/machine-learning-engineer.md +277 -0
- package/.claude/archive/agents/05-data-ai/ml-engineer.md +287 -0
- package/.claude/archive/agents/05-data-ai/mlops-engineer.md +287 -0
- package/.claude/archive/agents/05-data-ai/nlp-engineer.md +287 -0
- package/.claude/archive/agents/05-data-ai/postgres-pro.md +287 -0
- package/.claude/archive/agents/05-data-ai/prompt-engineer.md +287 -0
- package/.claude/archive/agents/05-data-ai/reinforcement-learning-engineer.md +277 -0
- package/.claude/archive/agents/07-specialized-domains/api-documenter.md +277 -0
- package/.claude/archive/agents/07-specialized-domains/blockchain-developer.md +287 -0
- package/.claude/archive/agents/07-specialized-domains/embedded-systems.md +287 -0
- package/.claude/archive/agents/07-specialized-domains/fintech-engineer.md +287 -0
- package/.claude/archive/agents/07-specialized-domains/game-developer.md +287 -0
- package/.claude/archive/agents/07-specialized-domains/healthcare-admin.md +199 -0
- package/.claude/archive/agents/07-specialized-domains/iot-engineer.md +287 -0
- package/.claude/archive/agents/07-specialized-domains/m365-admin.md +48 -0
- package/.claude/archive/agents/07-specialized-domains/mobile-app-developer.md +287 -0
- package/.claude/archive/agents/07-specialized-domains/payment-integration.md +287 -0
- package/.claude/archive/agents/07-specialized-domains/quant-analyst.md +287 -0
- package/.claude/archive/agents/07-specialized-domains/risk-manager.md +287 -0
- package/.claude/archive/agents/07-specialized-domains/seo-specialist.md +184 -0
- package/.claude/archive/agents/08-business-product/business-analyst.md +287 -0
- package/.claude/archive/agents/08-business-product/content-marketer.md +287 -0
- package/.claude/archive/agents/08-business-product/customer-success-manager.md +287 -0
- package/.claude/archive/agents/08-business-product/legal-advisor.md +287 -0
- package/.claude/archive/agents/08-business-product/license-engineer.md +295 -0
- package/.claude/archive/agents/08-business-product/product-manager.md +287 -0
- package/.claude/archive/agents/08-business-product/project-manager.md +287 -0
- package/.claude/archive/agents/08-business-product/sales-engineer.md +287 -0
- package/.claude/archive/agents/08-business-product/scrum-master.md +287 -0
- package/.claude/archive/agents/08-business-product/technical-writer.md +287 -0
- package/.claude/archive/agents/08-business-product/ux-researcher.md +287 -0
- package/.claude/archive/agents/08-business-product/wordpress-master.md +316 -0
- package/.claude/archive/skills/competitive-ads-extractor/SKILL.md +293 -0
- package/.claude/archive/skills/developer-growth-analysis/SKILL.md +322 -0
- package/.claude/archive/skills/document-docx/LICENSE.txt +30 -0
- package/.claude/archive/skills/document-docx/SKILL.md +197 -0
- package/.claude/archive/skills/document-docx/docx-js.md +350 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/mce/mc.xsd +75 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/.claude/archive/skills/document-docx/ooxml/scripts/pack.py +159 -0
- package/.claude/archive/skills/document-docx/ooxml/scripts/unpack.py +29 -0
- package/.claude/archive/skills/document-docx/ooxml/scripts/validate.py +69 -0
- package/.claude/archive/skills/document-docx/ooxml/scripts/validation/__init__.py +15 -0
- package/.claude/archive/skills/document-docx/ooxml/scripts/validation/base.py +951 -0
- package/.claude/archive/skills/document-docx/ooxml/scripts/validation/docx.py +274 -0
- package/.claude/archive/skills/document-docx/ooxml/scripts/validation/pptx.py +315 -0
- package/.claude/archive/skills/document-docx/ooxml/scripts/validation/redlining.py +279 -0
- package/.claude/archive/skills/document-docx/ooxml.md +610 -0
- package/.claude/archive/skills/document-docx/scripts/__init__.py +1 -0
- package/.claude/archive/skills/document-docx/scripts/document.py +1276 -0
- package/.claude/archive/skills/document-docx/scripts/templates/comments.xml +3 -0
- package/.claude/archive/skills/document-docx/scripts/templates/commentsExtended.xml +3 -0
- package/.claude/archive/skills/document-docx/scripts/templates/commentsExtensible.xml +3 -0
- package/.claude/archive/skills/document-docx/scripts/templates/commentsIds.xml +3 -0
- package/.claude/archive/skills/document-docx/scripts/templates/people.xml +3 -0
- package/.claude/archive/skills/document-docx/scripts/utilities.py +374 -0
- package/.claude/archive/skills/document-pdf/LICENSE.txt +30 -0
- package/.claude/archive/skills/document-pdf/SKILL.md +294 -0
- package/.claude/archive/skills/document-pdf/forms.md +205 -0
- package/.claude/archive/skills/document-pdf/reference.md +612 -0
- package/.claude/archive/skills/document-pdf/scripts/check_bounding_boxes.py +70 -0
- package/.claude/archive/skills/document-pdf/scripts/check_bounding_boxes_test.py +226 -0
- package/.claude/archive/skills/document-pdf/scripts/check_fillable_fields.py +12 -0
- package/.claude/archive/skills/document-pdf/scripts/convert_pdf_to_images.py +35 -0
- package/.claude/archive/skills/document-pdf/scripts/create_validation_image.py +41 -0
- package/.claude/archive/skills/document-pdf/scripts/extract_form_field_info.py +152 -0
- package/.claude/archive/skills/document-pdf/scripts/fill_fillable_fields.py +114 -0
- package/.claude/archive/skills/document-pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/.claude/archive/skills/document-pptx/LICENSE.txt +30 -0
- package/.claude/archive/skills/document-pptx/SKILL.md +484 -0
- package/.claude/archive/skills/document-pptx/html2pptx.md +625 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/mce/mc.xsd +75 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/.claude/archive/skills/document-pptx/ooxml/scripts/pack.py +159 -0
- package/.claude/archive/skills/document-pptx/ooxml/scripts/unpack.py +29 -0
- package/.claude/archive/skills/document-pptx/ooxml/scripts/validate.py +69 -0
- package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/__init__.py +15 -0
- package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/base.py +951 -0
- package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/docx.py +274 -0
- package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/pptx.py +315 -0
- package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/redlining.py +279 -0
- package/.claude/archive/skills/document-pptx/ooxml.md +427 -0
- package/.claude/archive/skills/document-pptx/scripts/html2pptx.js +979 -0
- package/.claude/archive/skills/document-pptx/scripts/inventory.py +1020 -0
- package/.claude/archive/skills/document-pptx/scripts/rearrange.py +231 -0
- package/.claude/archive/skills/document-pptx/scripts/replace.py +385 -0
- package/.claude/archive/skills/document-pptx/scripts/thumbnail.py +450 -0
- package/.claude/archive/skills/document-xlsx/LICENSE.txt +30 -0
- package/.claude/archive/skills/document-xlsx/SKILL.md +289 -0
- package/.claude/archive/skills/document-xlsx/recalc.py +178 -0
- package/.claude/archive/skills/image-enhancer/SKILL.md +99 -0
- package/.claude/archive/skills/meeting-insights-analyzer/SKILL.md +327 -0
- package/.claude/archive/skills/slack-gif-creator/LICENSE.txt +202 -0
- package/.claude/archive/skills/slack-gif-creator/SKILL.md +646 -0
- package/.claude/archive/skills/slack-gif-creator/core/color_palettes.py +302 -0
- package/.claude/archive/skills/slack-gif-creator/core/easing.py +230 -0
- package/.claude/archive/skills/slack-gif-creator/core/frame_composer.py +469 -0
- package/.claude/archive/skills/slack-gif-creator/core/gif_builder.py +246 -0
- package/.claude/archive/skills/slack-gif-creator/core/typography.py +357 -0
- package/.claude/archive/skills/slack-gif-creator/core/validators.py +264 -0
- package/.claude/archive/skills/slack-gif-creator/core/visual_effects.py +494 -0
- package/.claude/archive/skills/slack-gif-creator/requirements.txt +4 -0
- package/.claude/archive/skills/slack-gif-creator/templates/bounce.py +106 -0
- package/.claude/archive/skills/slack-gif-creator/templates/explode.py +331 -0
- package/.claude/archive/skills/slack-gif-creator/templates/fade.py +329 -0
- package/.claude/archive/skills/slack-gif-creator/templates/flip.py +291 -0
- package/.claude/archive/skills/slack-gif-creator/templates/kaleidoscope.py +211 -0
- package/.claude/archive/skills/slack-gif-creator/templates/morph.py +329 -0
- package/.claude/archive/skills/slack-gif-creator/templates/move.py +293 -0
- package/.claude/archive/skills/slack-gif-creator/templates/pulse.py +268 -0
- package/.claude/archive/skills/slack-gif-creator/templates/shake.py +127 -0
- package/.claude/archive/skills/slack-gif-creator/templates/slide.py +291 -0
- package/.claude/archive/skills/slack-gif-creator/templates/spin.py +269 -0
- package/.claude/archive/skills/slack-gif-creator/templates/wiggle.py +300 -0
- package/.claude/archive/skills/slack-gif-creator/templates/zoom.py +312 -0
- package/.claude/archive/skills/twitter-algorithm-optimizer/SKILL.md +327 -0
- package/.claude/archive/skills/video-downloader/SKILL.md +99 -0
- package/.claude/archive/skills/video-downloader/scripts/download_video.py +145 -0
- package/.claude/bash-whitelist-requests/2026-05-28-grep-find-rg.md +68 -0
- package/.claude/bash-whitelist-requests/2026-06-01-readonly-filters.md +76 -0
- package/.claude/bash-whitelist.txt +124 -0
- package/.claude/commands/agent-introspect.md +89 -0
- package/.claude/commands/apply-rules.md +363 -0
- package/.claude/commands/approve-design.md +219 -0
- package/.claude/commands/approve-org-money.md +267 -0
- package/.claude/commands/build.md +234 -0
- package/.claude/commands/commit.md +97 -0
- package/.claude/commands/context-fetch.md +113 -0
- package/.claude/commands/create-tool.md +496 -0
- package/.claude/commands/design-review.md +138 -0
- package/.claude/commands/design.md +807 -0
- package/.claude/commands/discharge-byproduct.md +208 -0
- package/.claude/commands/doc-review.md +165 -0
- package/.claude/commands/document-pair.md +76 -0
- package/.claude/commands/error-triage.md +435 -0
- package/.claude/commands/eval.md +70 -0
- package/.claude/commands/evolve.md +49 -0
- package/.claude/commands/finish-task.md +105 -0
- package/.claude/commands/gan-build.md +91 -0
- package/.claude/commands/gan-design.md +82 -0
- package/.claude/commands/gate-bypass.md +77 -0
- package/.claude/commands/gate-clear.md +45 -0
- package/.claude/commands/gate-status.md +46 -0
- package/.claude/commands/harness-audit.md +151 -0
- package/.claude/commands/hearing.md +138 -0
- package/.claude/commands/impact-check.md +486 -0
- package/.claude/commands/init-tasks.md +49 -0
- package/.claude/commands/instinct-export.md +47 -0
- package/.claude/commands/instinct-import.md +41 -0
- package/.claude/commands/instinct-status.md +43 -0
- package/.claude/commands/investigate.md +547 -0
- package/.claude/commands/learn.md +55 -0
- package/.claude/commands/lint-rules.md +400 -0
- package/.claude/commands/mode.md +58 -0
- package/.claude/commands/modify-feature.md +209 -0
- package/.claude/commands/module-review.md +149 -0
- package/.claude/commands/move-section.md +67 -0
- package/.claude/commands/new-draft.md +67 -0
- package/.claude/commands/new-feature.md +286 -0
- package/.claude/commands/new-task.md +156 -0
- package/.claude/commands/notification.md +107 -0
- package/.claude/commands/pm-start.md +119 -0
- package/.claude/commands/projects.md +32 -0
- package/.claude/commands/promote.md +43 -0
- package/.claude/commands/rasis-report.md +1323 -0
- package/.claude/commands/release-note.md +130 -0
- package/.claude/commands/reply-watch.md +149 -0
- package/.claude/commands/requirement.md +352 -0
- package/.claude/commands/resume-state.md +187 -0
- package/.claude/commands/reviewpr.md +118 -0
- package/.claude/commands/save-state.md +100 -0
- package/.claude/commands/sentry-pr.md +157 -0
- package/.claude/commands/start-task.md +87 -0
- package/.claude/commands/system-review.md +147 -0
- package/.claude/commands/task-bypass.md +70 -0
- package/.claude/commands/task-estimate.md +100 -0
- package/.claude/commands/template-apply.md +89 -0
- package/.claude/commands/test-design.md +116 -0
- package/.claude/commands/transfer-mismatch.md +317 -0
- package/.claude/commands/verify.md +51 -0
- package/.claude/evals/grader-loop-mode-autonomy.sh +165 -0
- package/.claude/evals/grader-system-reminder-attention.sh +99 -0
- package/.claude/evals/loop-mode-autonomy.md +121 -0
- package/.claude/evals/loop-mode-autonomy.results.template.md +133 -0
- package/.claude/evals/system-reminder-attention.md +123 -0
- package/.claude/evals/system-reminder-attention.results.template.md +93 -0
- package/.claude/evals/system-reminder-attention.runner.md +353 -0
- package/.claude/harness-config.local.yml +48 -0
- package/.claude/harness-config.yml +534 -0
- package/.claude/hooks/agent-marker-clear.sh +43 -0
- package/.claude/hooks/agent-marker-set.sh +40 -0
- package/.claude/hooks/agent-router-suggest.sh +123 -0
- package/.claude/hooks/autonomous-action-guard.sh +242 -0
- package/.claude/hooks/byproduct-discharge-guard.sh +128 -0
- package/.claude/hooks/check-md-mermaid.sh +144 -0
- package/.claude/hooks/check-required-env.sh +95 -0
- package/.claude/hooks/check-serena-mcp.sh +123 -0
- package/.claude/hooks/confidence-gate.sh +139 -0
- package/.claude/hooks/context-budget.sh +233 -0
- package/.claude/hooks/delegation-guard.sh +99 -0
- package/.claude/hooks/dispatcher-manifest.tsv +38 -0
- package/.claude/hooks/draft-flow-guard.sh +304 -0
- package/.claude/hooks/failure-loop-detect.sh +139 -0
- package/.claude/hooks/gateguard.sh +209 -0
- package/.claude/hooks/improvement-proposal.sh +112 -0
- package/.claude/hooks/init-tasks-on-start.sh +34 -0
- package/.claude/hooks/lib/bypass-logger.sh +82 -0
- package/.claude/hooks/lib/confidence-gate/bypass.sh +48 -0
- package/.claude/hooks/lib/confidence-gate/extract.sh +99 -0
- package/.claude/hooks/lib/confidence-gate/major-agent-filter.sh +59 -0
- package/.claude/hooks/lib/confidence-gate/messages.sh +53 -0
- package/.claude/hooks/lib/config-loader.sh +784 -0
- package/.claude/hooks/lib/delegation-guard/bash-whitelist.sh +323 -0
- package/.claude/hooks/lib/delegation-guard/git-deny.sh +188 -0
- package/.claude/hooks/lib/delegation-guard/protected-paths.sh +105 -0
- package/.claude/hooks/lib/delegation-guard/subagent-detect.sh +40 -0
- package/.claude/hooks/lib/dispatcher-core.sh +454 -0
- package/.claude/hooks/lib/improvement-proposal/aggregate.py +466 -0
- package/.claude/hooks/lib/improvement-proposal/cache.sh +78 -0
- package/.claude/hooks/lib/mode-loader.sh +80 -0
- package/.claude/hooks/lib/next-actions-parser.sh +153 -0
- package/.claude/hooks/lib/project-root.sh +60 -0
- package/.claude/hooks/list-md-plan-first-reminder.sh +143 -0
- package/.claude/hooks/loop-auto-progress-reminder.sh +108 -0
- package/.claude/hooks/loop-confirmation-detector.sh +241 -0
- package/.claude/hooks/mode-asana-prompt.sh +61 -0
- package/.claude/hooks/mode-enforce.sh +57 -0
- package/.claude/hooks/mode-session-start.sh +93 -0
- package/.claude/hooks/next-actions-surface.sh +136 -0
- package/.claude/hooks/notification-dispatcher.sh +9 -0
- package/.claude/hooks/notify.sh +27 -0
- package/.claude/hooks/parallel-subagent-reminder.sh +469 -0
- package/.claude/hooks/post-tool-use-dispatcher.sh +9 -0
- package/.claude/hooks/pre-tool-use-dispatcher.sh +9 -0
- package/.claude/hooks/reviewer-count-guard.sh +313 -0
- package/.claude/hooks/session-help-surface.sh +192 -0
- package/.claude/hooks/session-start-dispatcher.sh +9 -0
- package/.claude/hooks/session-start-wrapper.sh +156 -0
- package/.claude/hooks/stale-harness-detect.sh +422 -0
- package/.claude/hooks/stop-dispatcher.sh +9 -0
- package/.claude/hooks/stop.sh +25 -0
- package/.claude/hooks/subagent-stop-dispatcher.sh +9 -0
- package/.claude/hooks/task-rule-guard.sh +317 -0
- package/.claude/hooks/tests/run-tests.sh +23 -0
- package/.claude/hooks/tests/test-agent-marker-warn.sh +86 -0
- package/.claude/hooks/tests/test-check-required-env.sh +138 -0
- package/.claude/hooks/tests/test-confidence-gate.sh +170 -0
- package/.claude/hooks/tests/test-config-env-override.sh +220 -0
- package/.claude/hooks/tests/test-gate-disable.sh +118 -0
- package/.claude/hooks/tests/test-improvement-proposal.sh +284 -0
- package/.claude/hooks/tool-call-slip-detector.sh +188 -0
- package/.claude/hooks/user-prompt-submit-dispatcher.sh +9 -0
- package/.claude/hooks/why-x5-reminder.sh +45 -0
- package/.claude/hooks/why-x5-violation-detect.sh +152 -0
- package/.claude/hooks/workflow-guard.sh +263 -0
- package/.claude/mode.yml +28 -0
- package/.claude/project-rules/development-process.md +8 -0
- package/.claude/project-rules/git-workflow.md +8 -0
- package/.claude/project-rules/modes.md +8 -0
- package/.claude/project-rules/self-improvement.md +8 -0
- package/.claude/project-rules/task-management.md +8 -0
- package/.claude/project-rules/why-x5-output.md +8 -0
- package/.claude/project-rules/workflow.md +8 -0
- package/.claude/rules/development-process.md +293 -0
- package/.claude/rules/git-workflow.md +71 -0
- package/.claude/rules/modes.md +189 -0
- package/.claude/rules/self-improvement.md +76 -0
- package/.claude/rules/task-management.md +261 -0
- package/.claude/rules/why-x5-output.md +97 -0
- package/.claude/rules/workflow.md +157 -0
- package/.claude/rules-details/README.md +67 -0
- package/.claude/rules-details/development-process/confidence-gate.md +22 -0
- package/.claude/rules-details/development-process/cross-repo-write.md +35 -0
- package/.claude/rules-details/development-process/delegation-requirements.md +158 -0
- package/.claude/rules-details/development-process/harness-sync.md +21 -0
- package/.claude/rules-details/development-process/origin.md +13 -0
- package/.claude/rules-details/development-process/parallelization-origin.md +22 -0
- package/.claude/rules-details/development-process/research-reuse.md +22 -0
- package/.claude/rules-details/development-process/staging-strategy.md +47 -0
- package/.claude/rules-details/modes/artifacts.md +34 -0
- package/.claude/rules-details/modes/compliance-items.md +120 -0
- package/.claude/rules-details/modes/five-layer-enforcement.md +46 -0
- package/.claude/rules-details/modes/mode-hooks.md +51 -0
- package/.claude/rules-details/modes/origin.md +17 -0
- package/.claude/rules-details/self-improvement/l4-mechanics.md +36 -0
- package/.claude/rules-details/self-improvement/origin.md +8 -0
- package/.claude/rules-details/self-improvement/related-skills.md +35 -0
- package/.claude/rules-details/self-improvement/when-to-use-layers.md +39 -0
- package/.claude/rules-details/task-management/hook-enforcement.md +25 -0
- package/.claude/rules-details/task-management/mandatory-reading.md +20 -0
- package/.claude/rules-details/task-management/origin.md +12 -0
- package/.claude/rules-details/task-management/parking-lot.md +26 -0
- package/.claude/rules-details/task-management/plan-first.md +44 -0
- package/.claude/rules-details/task-management/six-articles.md +68 -0
- package/.claude/rules-details/task-management/task-migration.md +16 -0
- package/.claude/rules-details/task-management/ui-detection.md +11 -0
- package/.claude/rules-details/why-x5-output/examples.md +41 -0
- package/.claude/rules-details/why-x5-output/feedback-memory.md +14 -0
- package/.claude/rules-details/why-x5-output/origin.md +10 -0
- package/.claude/rules-details/why-x5-output/v1-v10-history.md +19 -0
- package/.claude/rules-details/workflow/10-stage.md +43 -0
- package/.claude/rules-details/workflow/14-stage.md +52 -0
- package/.claude/rules-details/workflow/byproduct-discharge.md +39 -0
- package/.claude/rules-details/workflow/draft-flow-guard.md +31 -0
- package/.claude/rules-details/workflow/fan-out.md +70 -0
- package/.claude/rules-details/workflow/mece-20.md +36 -0
- package/.claude/rules-details/workflow/origin.md +14 -0
- package/.claude/rules-details/workflow/refactoring.md +48 -0
- package/.claude/rules-details/workflow/related-skills.md +22 -0
- package/.claude/rules-details/workflow/reviewer-prompt.md +100 -0
- package/.claude/rules-details/workflow/session-persistence.md +46 -0
- package/.claude/rules-details/workflow/workflow-guard.md +36 -0
- package/.claude/scripts/__pycache__/harness-audit.cpython-313.pyc +0 -0
- package/.claude/scripts/agent-stocktake.py +421 -0
- package/.claude/scripts/check-md-mermaid.mjs +138 -0
- package/.claude/scripts/generate-settings.sh +0 -0
- package/.claude/scripts/harness-audit.py +1547 -0
- package/.claude/scripts/hc-config.sh +2265 -0
- package/.claude/scripts/init-tasks.sh +117 -0
- package/.claude/scripts/lib/enforcement-matrix-parse.sh +81 -0
- package/.claude/scripts/lib/hc-config-metadata.sh +190 -0
- package/.claude/scripts/lib/hc-config-web-server.js +1528 -0
- package/.claude/scripts/lib/hc-config-web-ui/app.js +1054 -0
- package/.claude/scripts/lib/hc-config-web-ui/index.html +130 -0
- package/.claude/scripts/lib/hc-config-web-ui/style.css +522 -0
- package/.claude/scripts/new-task-helper.sh +432 -0
- package/.claude/scripts/observe-repair.sh +437 -0
- package/.claude/scripts/observe-rotate.sh +311 -0
- package/.claude/scripts/statusline.sh +239 -0
- package/.claude/settings.generated.preview.json +211 -0
- package/.claude/settings.json +215 -0
- package/.claude/settings.local.example.json +20 -0
- package/.claude/settings.local.json +36 -0
- package/.claude/skills/agent-introspection-debugging/SKILL.md +123 -0
- package/.claude/skills/agent-router/README.md +137 -0
- package/.claude/skills/agent-router/SKILL.md +74 -0
- package/.claude/skills/agent-router/dispatch-table.yml +352 -0
- package/.claude/skills/agent-router/router.py +1086 -0
- package/.claude/skills/agent-router/samples/representative_prompts.txt +24 -0
- package/.claude/skills/agent-router/tests/__init__.py +0 -0
- package/.claude/skills/agent-router/tests/test_router.py +762 -0
- package/.claude/skills/artifacts-builder/LICENSE.txt +202 -0
- package/.claude/skills/artifacts-builder/SKILL.md +74 -0
- package/.claude/skills/artifacts-builder/scripts/bundle-artifact.sh +54 -0
- package/.claude/skills/artifacts-builder/scripts/init-artifact.sh +322 -0
- package/.claude/skills/artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/.claude/skills/brand-guidelines/LICENSE.txt +202 -0
- package/.claude/skills/brand-guidelines/SKILL.md +73 -0
- package/.claude/skills/canvas-design/LICENSE.txt +202 -0
- package/.claude/skills/canvas-design/SKILL.md +130 -0
- package/.claude/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
- package/.claude/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/.claude/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/.claude/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/.claude/skills/changelog-generator/SKILL.md +104 -0
- package/.claude/skills/check-md-mermaid/SKILL.md +62 -0
- package/.claude/skills/connect/SKILL.md +156 -0
- package/.claude/skills/connect-apps/SKILL.md +80 -0
- package/.claude/skills/content-research-writer/SKILL.md +538 -0
- package/.claude/skills/continuous-agent-loop/SKILL.md +187 -0
- package/.claude/skills/continuous-learning-v2/SKILL.md +238 -0
- package/.claude/skills/continuous-learning-v2/config.json +35 -0
- package/.claude/skills/continuous-learning-v2/hooks/observe.sh +333 -0
- package/.claude/skills/continuous-learning-v2/instinct-cli.py +406 -0
- package/.claude/skills/domain-name-brainstormer/SKILL.md +212 -0
- package/.claude/skills/eval-harness/SKILL.md +100 -0
- package/.claude/skills/eval-harness/swe-bench/README.md +80 -0
- package/.claude/skills/eval-harness/swe-bench/config.yml +29 -0
- package/.claude/skills/eval-harness/swe-bench/docker/Dockerfile +25 -0
- package/.claude/skills/eval-harness/swe-bench/docker/docker-compose.yml +18 -0
- package/.claude/skills/eval-harness/swe-bench/results/dry-run-2026-05-04.json +137 -0
- package/.claude/skills/eval-harness/swe-bench/results/dry-run-comparison-2026-05-04.md +112 -0
- package/.claude/skills/eval-harness/swe-bench/results/dry-run-improved-2026-05-04.json +165 -0
- package/.claude/skills/eval-harness/swe-bench/results/raw/astropy__astropy-12907.patch +12 -0
- package/.claude/skills/eval-harness/swe-bench/results/raw/astropy__astropy-12907.txt +322 -0
- package/.claude/skills/eval-harness/swe-bench/results/raw/astropy__astropy-12907.whole-file.txt +322 -0
- package/.claude/skills/eval-harness/swe-bench/runner.py +845 -0
- package/.claude/skills/eval-harness/swe-bench/scoring.py +298 -0
- package/.claude/skills/eval-harness/swe-bench/tasks/fetch_tasks.py +81 -0
- package/.claude/skills/eval-harness/swe-bench/tasks/lite-50.json +702 -0
- package/.claude/skills/file-organizer/SKILL.md +433 -0
- package/.claude/skills/gan-style-harness/SKILL.md +111 -0
- package/.claude/skills/gateguard/.gateguard.yml +47 -0
- package/.claude/skills/gateguard/SKILL.md +99 -0
- package/.claude/skills/internal-comms/LICENSE.txt +202 -0
- package/.claude/skills/internal-comms/SKILL.md +32 -0
- package/.claude/skills/internal-comms/examples/3p-updates.md +47 -0
- package/.claude/skills/internal-comms/examples/company-newsletter.md +65 -0
- package/.claude/skills/internal-comms/examples/faq-answers.md +30 -0
- package/.claude/skills/internal-comms/examples/general-comms.md +16 -0
- package/.claude/skills/invoice-organizer/SKILL.md +446 -0
- package/.claude/skills/karpathy-guidelines/SKILL.md +67 -0
- package/.claude/skills/langsmith-fetch/SKILL.md +485 -0
- package/.claude/skills/lead-research-assistant/SKILL.md +199 -0
- package/.claude/skills/mcp-builder/LICENSE.txt +202 -0
- package/.claude/skills/mcp-builder/SKILL.md +328 -0
- package/.claude/skills/mcp-builder/reference/evaluation.md +602 -0
- package/.claude/skills/mcp-builder/reference/mcp_best_practices.md +915 -0
- package/.claude/skills/mcp-builder/reference/node_mcp_server.md +916 -0
- package/.claude/skills/mcp-builder/reference/python_mcp_server.md +752 -0
- package/.claude/skills/mcp-builder/scripts/connections.py +151 -0
- package/.claude/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/.claude/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/.claude/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/.claude/skills/raffle-winner-picker/SKILL.md +159 -0
- package/.claude/skills/repo-map/README.md +125 -0
- package/.claude/skills/repo-map/SKILL.md +128 -0
- package/.claude/skills/repo-map/examples/sample-output.md +1194 -0
- package/.claude/skills/repo-map/repo-map.py +715 -0
- package/.claude/skills/salesforce-e2e-testing/SKILL.md +116 -0
- package/.claude/skills/salesforce-e2e-testing/catalog-template.md +161 -0
- package/.claude/skills/salesforce-e2e-testing/methodology.md +179 -0
- package/.claude/skills/salesforce-e2e-testing/observation-rules.md +280 -0
- package/.claude/skills/salesforce-e2e-testing/pattern-taxonomy.md +392 -0
- package/.claude/skills/salesforce-e2e-testing/procedure-template.md +376 -0
- package/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/.claude/skills/skill-creator/SKILL.md +209 -0
- package/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/.claude/skills/skill-share/SKILL.md +80 -0
- package/.claude/skills/tailored-resume-generator/SKILL.md +345 -0
- package/.claude/skills/template-skill/SKILL.md +6 -0
- package/.claude/skills/theme-factory/LICENSE.txt +202 -0
- package/.claude/skills/theme-factory/SKILL.md +59 -0
- package/.claude/skills/theme-factory/theme-showcase.pdf +0 -0
- package/.claude/skills/theme-factory/themes/arctic-frost.md +19 -0
- package/.claude/skills/theme-factory/themes/botanical-garden.md +19 -0
- package/.claude/skills/theme-factory/themes/desert-rose.md +19 -0
- package/.claude/skills/theme-factory/themes/forest-canopy.md +19 -0
- package/.claude/skills/theme-factory/themes/golden-hour.md +19 -0
- package/.claude/skills/theme-factory/themes/midnight-galaxy.md +19 -0
- package/.claude/skills/theme-factory/themes/modern-minimalist.md +19 -0
- package/.claude/skills/theme-factory/themes/ocean-depths.md +19 -0
- package/.claude/skills/theme-factory/themes/sunset-boulevard.md +19 -0
- package/.claude/skills/theme-factory/themes/tech-innovation.md +19 -0
- package/.claude/skills/verification-loop/SKILL.md +129 -0
- package/.claude/skills/webapp-testing/LICENSE.txt +202 -0
- package/.claude/skills/webapp-testing/SKILL.md +96 -0
- package/.claude/skills/webapp-testing/examples/console_logging.py +35 -0
- package/.claude/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/.claude/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/.claude/skills/webapp-testing/scripts/with_server.py +106 -0
- package/.claude/templates/docs/draft/_DRAFT_TEMPLATE.md +162 -0
- package/.claude/templates/docs/draft/_TEST_DESIGN_TEMPLATE.md +76 -0
- package/.claude/templates/docs/tasks/_TASK_TEMPLATE.md +276 -0
- package/.claude/templates/docs/tasks/list.md +80 -0
- package/.claude/templates/docs/tasks/parking-lot.md +82 -0
- package/.claude/templates/settings.user-level.json.template +306 -0
- package/.claude/tests/SMOKE-CLASSIFICATION.md +199 -0
- package/.claude/tests/action-space-count-smoke.sh +130 -0
- package/.claude/tests/agent-router-suggest-wiring-smoke.sh +188 -0
- package/.claude/tests/audit-followups-smoke.sh +158 -0
- package/.claude/tests/autonomous-action-guard-relaxation-smoke.sh +479 -0
- package/.claude/tests/autonomous-action-guard-smoke.sh +187 -0
- package/.claude/tests/check-serena-mcp-smoke.sh +156 -0
- package/.claude/tests/common-rules-import-smoke.sh +209 -0
- package/.claude/tests/confidence-gate-smoke.sh +220 -0
- package/.claude/tests/config-feature-toggles-smoke.sh +389 -0
- package/.claude/tests/context-budget-smoke.sh +222 -0
- package/.claude/tests/custom-pm-commands-smoke.sh +93 -0
- package/.claude/tests/delegation-guard-code-smoke.sh +244 -0
- package/.claude/tests/delegation-guard-deny-layers-smoke.sh +356 -0
- package/.claude/tests/delegation-guard-readonly-filter-smoke.sh +205 -0
- package/.claude/tests/delegation-guard-search-whitelist-smoke.sh +152 -0
- package/.claude/tests/delegation-guard-segment-smoke.sh +109 -0
- package/.claude/tests/dispatcher-blocker-invariance-smoke.sh +700 -0
- package/.claude/tests/dispatcher-core-smoke.sh +452 -0
- package/.claude/tests/dispatcher-merge-matrix-smoke.sh +825 -0
- package/.claude/tests/dispatcher-success-stdout-smoke.sh +290 -0
- package/.claude/tests/draft-flow-guard-approved-dir-smoke.sh +234 -0
- package/.claude/tests/draft-flow-guard-smoke.sh +194 -0
- package/.claude/tests/dual-mode-portability-smoke.sh +131 -0
- package/.claude/tests/effective-hook-matrix-smoke.sh +261 -0
- package/.claude/tests/enforcement-mismatch-smoke.sh +263 -0
- package/.claude/tests/fixtures/cascade-sample.jsonl +9 -0
- package/.claude/tests/fixtures/next-actions/case-clean.md +14 -0
- package/.claude/tests/fixtures/next-actions/case-with-red.md +16 -0
- package/.claude/tests/fixtures/next-actions/case-with-yellow-only.md +14 -0
- package/.claude/tests/fixtures/normal-broken-scatter.jsonl +5 -0
- package/.claude/tests/fixtures/task-71/blocker-baseline.tsv +24 -0
- package/.claude/tests/fixtures/task-71/settings-inventory.tsv +37 -0
- package/.claude/tests/fixtures/transcript-50pct.jsonl +2 -0
- package/.claude/tests/fixtures/transcript-60pct.jsonl +2 -0
- package/.claude/tests/fixtures/transcript-80pct.jsonl +2 -0
- package/.claude/tests/fixtures/transcript-95pct.jsonl +2 -0
- package/.claude/tests/fixtures/workflow-guard/case-2-mid.json +21 -0
- package/.claude/tests/fixtures/workflow-guard/case-3-blocked.json +33 -0
- package/.claude/tests/fixtures/workflow-guard/case-4-clean.json +27 -0
- package/.claude/tests/fixtures/workflow-guard/case-8-modify.json +23 -0
- package/.claude/tests/fixtures/workflow-guard/inputs/case-1.json +1 -0
- package/.claude/tests/fixtures/workflow-guard/inputs/case-2.json +1 -0
- package/.claude/tests/fixtures/workflow-guard/inputs/case-3.json +1 -0
- package/.claude/tests/fixtures/workflow-guard/inputs/case-4.json +1 -0
- package/.claude/tests/fixtures/workflow-guard/inputs/case-5.json +1 -0
- package/.claude/tests/fixtures/workflow-guard/inputs/case-6.json +1 -0
- package/.claude/tests/fixtures/workflow-guard/inputs/case-7.json +1 -0
- package/.claude/tests/fixtures/workflow-guard/inputs/case-8.json +1 -0
- package/.claude/tests/gateguard-smoke.sh +213 -0
- package/.claude/tests/git-deny-mainline-policy-smoke.sh +222 -0
- package/.claude/tests/harness-audit-c-batch-smoke.sh +270 -0
- package/.claude/tests/harness-audit-compare-smoke.sh +186 -0
- package/.claude/tests/harness-audit-pipeline-health-smoke.sh +326 -0
- package/.claude/tests/harness-config-local-smoke.sh +232 -0
- package/.claude/tests/hc-config-git-policy-smoke.sh +241 -0
- package/.claude/tests/hc-config-key-parity-smoke.sh +149 -0
- package/.claude/tests/hc-config-migration-smoke.sh +251 -0
- package/.claude/tests/hc-config-script-smoke.sh +1106 -0
- package/.claude/tests/hc-config-tui-smoke.sh +801 -0
- package/.claude/tests/hc-config-web-ui-smoke.sh +3224 -0
- package/.claude/tests/hook-cwd-robustness-smoke.sh +206 -0
- package/.claude/tests/hook-frequency-tweaks-smoke.sh +312 -0
- package/.claude/tests/improvement-proposal-cache-smoke.sh +238 -0
- package/.claude/tests/install-sh-overwrite-all-smoke.sh +274 -0
- package/.claude/tests/install-sh-regen-settings-smoke.sh +301 -0
- package/.claude/tests/install-sh-sync-drift-smoke.sh +285 -0
- package/.claude/tests/layer-b-context-isolation-smoke.sh +392 -0
- package/.claude/tests/list-md-plan-first-reminder-smoke.sh +313 -0
- package/.claude/tests/loop-auto-progress-smoke.sh +372 -0
- package/.claude/tests/loop-confirmation-detector-smoke.sh +674 -0
- package/.claude/tests/new-task-batch-update-smoke.sh +664 -0
- package/.claude/tests/next-actions-hooks-smoke.sh +283 -0
- package/.claude/tests/npx-cli-smoke.sh +696 -0
- package/.claude/tests/observe-flock-smoke.sh +223 -0
- package/.claude/tests/observe-jq-parse-smoke.sh +250 -0
- package/.claude/tests/observe-repair-smoke.sh +475 -0
- package/.claude/tests/observe-rotate-smoke.sh +428 -0
- package/.claude/tests/observe-subagent-stop-smoke.sh +476 -0
- package/.claude/tests/parallel-subagent-reminder-smoke.sh +918 -0
- package/.claude/tests/project-root-smoke.sh +140 -0
- package/.claude/tests/project-rules-protection-smoke.sh +199 -0
- package/.claude/tests/review-required-min-count-smoke.sh +286 -0
- package/.claude/tests/reviewer-count-guard-smoke.sh +490 -0
- package/.claude/tests/rule-architecture-smoke.sh +418 -0
- package/.claude/tests/rule-change-draft-flow-guard-smoke.sh +343 -0
- package/.claude/tests/run-all-smokes.sh +340 -0
- package/.claude/tests/session-help-surface-smoke.sh +224 -0
- package/.claude/tests/session-start-parallel-smoke.sh +165 -0
- package/.claude/tests/sessionstart-budget-smoke.sh +185 -0
- package/.claude/tests/sessionstart-footprint-smoke.sh +258 -0
- package/.claude/tests/settings-dispatcher-baseline-smoke.sh +709 -0
- package/.claude/tests/settings-generation-feature-pruning-smoke.sh +196 -0
- package/.claude/tests/stale-harness-detect-smoke.sh +974 -0
- package/.claude/tests/statusline-smoke.sh +180 -0
- package/.claude/tests/task-rule-guard-smoke.sh +656 -0
- package/.claude/tests/tool-call-slip-detector-smoke.sh +101 -0
- package/.claude/tests/wave-precheck-template-smoke.sh +159 -0
- package/.claude/tests/why-x5-violation-detect-smoke.sh +157 -0
- package/.claude/tests/workflow-guard-smoke.sh +266 -0
- package/CLAUDE.md +75 -0
- package/LICENSE +21 -0
- package/README.md +790 -0
- package/bin/cli.js +395 -0
- package/docs/INVENTORY.md +163 -0
- package/install.sh +769 -0
- package/package.json +25 -0
|
@@ -0,0 +1,3224 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# .claude/tests/hc-config-web-ui-smoke.sh — task-63 Step 5 (設計簡素化 案 C: /api/preset/save 撤去 + /api/current-preset 追加)
|
|
3
|
+
#
|
|
4
|
+
# 目的:
|
|
5
|
+
# hc-config Web UI (hc-config-web-server.js) の動作を 34 case + 手動 4 case コメント で検証。
|
|
6
|
+
# task-63: /api/preset/save 関連 case (S-22/S-25b/S-26/S-31/S-33) 撤去、
|
|
7
|
+
# /api/current-preset / top view / edit 遷移 / unsaved banner の 5 case (S-35〜S-39) 追加。
|
|
8
|
+
#
|
|
9
|
+
# Case 一覧 (自動 30 + legacy fallback 2 + manual SKIP 4):
|
|
10
|
+
# server lifecycle (4):
|
|
11
|
+
# S-01: server 起動 (HC_WEB_NO_OPEN=1) → port 3060 LISTEN → GET / 200 or 302 → kill
|
|
12
|
+
# S-02: port 3060 先 occupy → server → 3061 以降で listen → cleanup
|
|
13
|
+
# S-03: port 3060-3070 全 occupied → server process.exit(1)
|
|
14
|
+
# S-04: server 起動 → SIGINT → graceful shutdown → port release
|
|
15
|
+
# static 配信 (4):
|
|
16
|
+
# S-05: GET / → 302 or 200
|
|
17
|
+
# S-06: GET /static/index.html → 200 + text/html
|
|
18
|
+
# S-07: GET /static/app.js → 200 + application/javascript
|
|
19
|
+
# S-08: path traversal GET /static/../../.claude/harness-config.yml → 403/404
|
|
20
|
+
# API endpoint (5):
|
|
21
|
+
# S-09: GET /api/categories → 200 + .categories length >= 1
|
|
22
|
+
# S-10: GET /api/keys → 200 + .keys length >= 1
|
|
23
|
+
# S-11: GET /api/presets → 200 + .presets length == 10
|
|
24
|
+
# S-12: GET /api/preset/poc-no-git/diff → 200 + .changes array + key/current/new/effect fields
|
|
25
|
+
# S-13: POST /api/set 不正 key (空/欠落) → 400
|
|
26
|
+
# preset apply / rollback (3):
|
|
27
|
+
# S-14: POST /api/preset/poc-no-git/apply → 200/207 + history file 生成 (HISTORY_DIR isolated)
|
|
28
|
+
# S-15: apply → POST /api/preset/rollback/<ts> → 200 + ok:true + restored==applied_count
|
|
29
|
+
# S-16: rollback timestamp traversal → 400
|
|
30
|
+
# legacy fallback + edge (2):
|
|
31
|
+
# S-17: HC_HC_CONFIG_TUI_LEGACY=true → TUI 経路 dispatcher stderr ログ確認 (厳密化)
|
|
32
|
+
# S-18: node 不在 → WARN stderr + TUI fallback
|
|
33
|
+
# body size (1):
|
|
34
|
+
# S-19: 1MB+1byte body POST /api/set → 400/413
|
|
35
|
+
#
|
|
36
|
+
# iter 4 C 新規 case (task-61 由来):
|
|
37
|
+
# S-20: abort rollback silent no-op verify
|
|
38
|
+
# S-21: unknown preset 404 path
|
|
39
|
+
# S-22: 撤去 (/api/preset/save 撤去 — task-63 設計簡素化)
|
|
40
|
+
# S-23: partial failure ok:false + rollback 件数 verify
|
|
41
|
+
# S-24: HISTORY_DIR 不在 → 自動作成 verify
|
|
42
|
+
# S-25: invalid JSON body → 400 (/api/set のみ、/api/preset/save sub-case は撤去)
|
|
43
|
+
# S-26: 撤去 (/api/preset/save 6 軸欠落 — task-63 設計簡素化)
|
|
44
|
+
# S-27: /api/set 空文字列 value (empty string) → 仕様確認
|
|
45
|
+
# S-28: URL encoded traversal /api/preset/rollback/..%2F..%2F → 400
|
|
46
|
+
# S-29: GET /api/preset/history → .history array
|
|
47
|
+
# S-30: 0 件 rollback (targets=[]) → ok:true + restored:0
|
|
48
|
+
# S-31: 撤去 (/api/preset/save path traversal — task-63 設計簡素化)
|
|
49
|
+
# S-32: category filter GET /api/keys?category=<name> → filtered list
|
|
50
|
+
# S-33: 撤去 (XSS injection save name — task-63 設計簡素化)
|
|
51
|
+
# S-34: SIGTERM graceful shutdown → port release
|
|
52
|
+
#
|
|
53
|
+
# task-63 Step 5 新規 case (/api/current-preset + top/edit view + unsaved banner):
|
|
54
|
+
# S-35: GET /api/current-preset → 200 + match_type + display_name_ja field 含む
|
|
55
|
+
# S-36: preset apply 後 GET /api/current-preset → match_type=preset + 正しい name/display_name_ja
|
|
56
|
+
# S-37: app.js に renderTop 関数 + bannerLabel/bannerValue が静的に存在 (top view banner 描画確認)
|
|
57
|
+
# S-38: app.js に state.view='edit' 遷移ロジック + renderEdit 関数が静的に存在 (edit view 遷移確認)
|
|
58
|
+
# S-39: /api/set で 1 key 変更 → GET /api/current-preset → match_type=unsaved (未保存変更あり)
|
|
59
|
+
#
|
|
60
|
+
# 手動 case (smoke 内にコメントとして記載、Step 6 実施):
|
|
61
|
+
# M-01: browser で preset 選択 → diff preview → checkbox toggle → Apply → history 追加
|
|
62
|
+
# M-02: category 選択 → key 一覧 → 編集 → Apply → 値反映確認
|
|
63
|
+
# M-03: Rollback ボタン → confirm dialog → 確認 → 元値復元
|
|
64
|
+
# M-04: Tailwind CDN offline で degradation 動作確認
|
|
65
|
+
#
|
|
66
|
+
# task-63 Step 6 iter-2 fix 新規 case:
|
|
67
|
+
# S-40: POST /api/preset/save → 404 (custom 保存撤去 regression guard、F4)
|
|
68
|
+
# S-41: UI 3 file (index.html/app.js/style.css) に絵文字 0 件 (絵文字不要 regression guard、F5)
|
|
69
|
+
#
|
|
70
|
+
# task-76 Step 6-fix (2 分割再設計に合わせた stale test 整理):
|
|
71
|
+
# RETIRED (常時 SKIP、return 2):
|
|
72
|
+
# S-37 (renderTop/banner) / S-38 (edit-view state machine) /
|
|
73
|
+
# S-45 (top-view 6 軸 axes table) / S-46 (edit view / applyPresetMode)。
|
|
74
|
+
# 理由: top/edit 2-view を廃止し 2 分割 1 画面 (左 preset / 右 category accordion) へ全面再設計。
|
|
75
|
+
# 旧 UI symbol (renderTop/renderEdit/view:'edit'/applyPresetMode/cp.axes/AXIS_LABELS_JA) は新設計に不在。
|
|
76
|
+
# RE-TARGET:
|
|
77
|
+
# S-42 (DOM id 契約 cross-check) — 新 app.js は getElementById を $(id) wrapper 経由で呼ぶため
|
|
78
|
+
# $('id') 抽出を追加、anchor を新 render target (preset-list/category-accordion/panel-config/
|
|
79
|
+
# panel-history/history-tbody/save-bar) に更新。cross-file 契約乖離の安全網を新構造で機能させる。
|
|
80
|
+
# 除外規則:
|
|
81
|
+
# S-41 (絵文字 0 件 guard) は ★ (U+2605 BLACK STAR) を検出範囲から除外
|
|
82
|
+
# (設計 §3 が「★ カスタム」を明示採用、BLACK STAR は emoji でなく typographic dingbat)。
|
|
83
|
+
# 新規維持: S-48〜S-51 (task-76 Step 1-2)。
|
|
84
|
+
#
|
|
85
|
+
# 設計:
|
|
86
|
+
# - subshell 関数 ( set -uo pipefail; ... ) で各 case を隔離
|
|
87
|
+
# - file-top に set -euo pipefail を書かない (feedback_set_e_in_sourced_libs 規範)
|
|
88
|
+
# - color output (TTY 検出で plain 切替)
|
|
89
|
+
# - trap で server cleanup 確実化
|
|
90
|
+
# - HC_WEB_NO_OPEN=1 で browser auto-open 抑止
|
|
91
|
+
# - HC_HISTORY_DIR_OVERRIDE で history dir を TMP_DIR 内に隔離 (test isolation)
|
|
92
|
+
#
|
|
93
|
+
# 重要制約:
|
|
94
|
+
# - bash 3.2 互換 (associative array 禁止、[ ] のみ)
|
|
95
|
+
# - BSD/GNU bash 両対応 (macOS bash 3.2 + Linux bash 5.x)
|
|
96
|
+
|
|
97
|
+
set -uo pipefail
|
|
98
|
+
|
|
99
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
100
|
+
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
|
101
|
+
|
|
102
|
+
# ---- top-level network-unavailable skip guard (task-74 Step 2) ----
|
|
103
|
+
# sandbox/CI 環境で localhost port bind が不可、または node が不在の場合、
|
|
104
|
+
# 全 case を SKIP (exit 0) して FAIL と混同しないようにする。
|
|
105
|
+
# per-case `_has_node` skip は残存 (両立設計)。
|
|
106
|
+
#
|
|
107
|
+
# skip 条件 (OR):
|
|
108
|
+
# a) HC_SMOKE_SKIP_NETWORK=1 が設定されている (明示 opt-out)
|
|
109
|
+
# b) node コマンドが存在しない (ほぼ全 case が node 依存)
|
|
110
|
+
# c) localhost port bind probe が失敗する (sandbox/CI で port bind 不可)
|
|
111
|
+
#
|
|
112
|
+
# 自動 probe (c): python3 or nc で 127.0.0.1:0 への一時 bind を試み、
|
|
113
|
+
# 失敗時に sandbox/CI と判定して SKIP。
|
|
114
|
+
_network_available() {
|
|
115
|
+
# python3 で localhost bind probe
|
|
116
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
117
|
+
python3 -c "
|
|
118
|
+
import socket, sys
|
|
119
|
+
s = socket.socket()
|
|
120
|
+
try:
|
|
121
|
+
s.bind(('127.0.0.1', 0))
|
|
122
|
+
s.close()
|
|
123
|
+
sys.exit(0)
|
|
124
|
+
except Exception:
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
" >/dev/null 2>&1 && return 0
|
|
127
|
+
fi
|
|
128
|
+
# nc で簡易確認 (nc -l 0 は環境依存のため listen 試行)
|
|
129
|
+
# 代替: /dev/tcp が使えるなら connect で確認 (bind 確認は難しいため python3 優先)
|
|
130
|
+
# python3 不在 + nc 不在 = probe 不可 → SKIP safe-default
|
|
131
|
+
return 1
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
_top_level_network_skip() {
|
|
135
|
+
local reason=""
|
|
136
|
+
if [ "${HC_SMOKE_SKIP_NETWORK:-0}" = "1" ]; then
|
|
137
|
+
reason="HC_SMOKE_SKIP_NETWORK=1 (explicit opt-out)"
|
|
138
|
+
elif ! command -v node >/dev/null 2>&1; then
|
|
139
|
+
reason="node not available (required for Web UI server)"
|
|
140
|
+
elif ! _network_available; then
|
|
141
|
+
reason="localhost port bind probe failed (sandbox/CI environment)"
|
|
142
|
+
fi
|
|
143
|
+
if [ -n "$reason" ]; then
|
|
144
|
+
printf '\n%s\n' "=== hc-config-web-ui-smoke ==="
|
|
145
|
+
printf 'SKIP: network-unavailable environment detected — %s\n' "$reason"
|
|
146
|
+
printf 'All cases skipped (exit 0). Use HC_SMOKE_SKIP_NETWORK=0 to force attempt.\n'
|
|
147
|
+
exit 0
|
|
148
|
+
fi
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# top-level skip guard 実行 (TMP_DIR 作成前に評価)
|
|
152
|
+
_top_level_network_skip
|
|
153
|
+
HC_CONFIG_SCRIPT="${REPO_ROOT}/.claude/scripts/hc-config.sh"
|
|
154
|
+
WEB_SERVER="${REPO_ROOT}/.claude/scripts/lib/hc-config-web-server.js"
|
|
155
|
+
# iter 4 C: T-H2 — HISTORY_DIR は TMP_DIR 内に隔離 (test isolation)
|
|
156
|
+
# server.js が HC_HISTORY_DIR_OVERRIDE を読んで切替える (領域 A 実装前提)
|
|
157
|
+
# 実装前は fallback として元の HISTORY_DIR も保持
|
|
158
|
+
HISTORY_DIR="${REPO_ROOT}/.claude/.preset-history"
|
|
159
|
+
|
|
160
|
+
# tmp dir (cleanup on exit)
|
|
161
|
+
TMP_DIR="$(mktemp -d "/tmp/hc-config-web-ui-smoke.XXXXXX")"
|
|
162
|
+
|
|
163
|
+
# F2 (iter-2 fix): smoke は S-36/S-39 で poc-no-git apply + confidence_threshold set により
|
|
164
|
+
# 実 .claude/harness-config.yml を永続変更する。teardown で原状復帰させるため起動時に snapshot を取り、
|
|
165
|
+
# _cleanup (EXIT trap) で restore する。これで smoke 実行後も yml に差分が残らない。
|
|
166
|
+
HARNESS_CONFIG="${REPO_ROOT}/.claude/harness-config.yml"
|
|
167
|
+
HARNESS_CONFIG_SNAPSHOT="${TMP_DIR}/harness-config.yml.snapshot"
|
|
168
|
+
if [ -f "${HARNESS_CONFIG}" ]; then
|
|
169
|
+
cp "${HARNESS_CONFIG}" "${HARNESS_CONFIG_SNAPSHOT}" 2>/dev/null || true
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# isolated history dir for test
|
|
173
|
+
ISOLATED_HISTORY_DIR="${TMP_DIR}/.preset-history"
|
|
174
|
+
|
|
175
|
+
# iter 6 B: isolated presets dir (S-22/S-31/S-33 teardown — custom-test-*.yml pollution 解消)
|
|
176
|
+
ISOLATED_PRESETS_DIR="${TMP_DIR}/presets"
|
|
177
|
+
|
|
178
|
+
SERVER_PID=""
|
|
179
|
+
|
|
180
|
+
# ---- port cleanup helpers ----
|
|
181
|
+
|
|
182
|
+
_get_server_port() {
|
|
183
|
+
# LOG_FILE から "server started on http://127.0.0.1:<port>/" を抽出
|
|
184
|
+
local log_file="$1"
|
|
185
|
+
grep -oE 'http://127\.0\.0\.1:[0-9]+/' "$log_file" 2>/dev/null | head -1 | grep -oE '[0-9]+' | tail -1 || true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
_cleanup() {
|
|
189
|
+
if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then
|
|
190
|
+
kill "$SERVER_PID" 2>/dev/null || true
|
|
191
|
+
sleep 0.5
|
|
192
|
+
kill -9 "$SERVER_PID" 2>/dev/null || true
|
|
193
|
+
fi
|
|
194
|
+
SERVER_PID=""
|
|
195
|
+
# F2 (iter-2 fix): TMP_DIR 削除前に harness-config.yml を snapshot から restore
|
|
196
|
+
# (S-36/S-39 が apply/set で実 yml を変更するため、原状復帰させる)
|
|
197
|
+
if [ -f "${HARNESS_CONFIG_SNAPSHOT}" ]; then
|
|
198
|
+
cp "${HARNESS_CONFIG_SNAPSHOT}" "${HARNESS_CONFIG}" 2>/dev/null || true
|
|
199
|
+
# apply/set 経由で hc-config.sh が生成した atomic backup (harness-config.yml.bak.*) を掃除
|
|
200
|
+
# (snapshot restore は yml 本体のみ。.bak.* 兄弟 file は別 side-effect なので明示削除)
|
|
201
|
+
rm -f "${HARNESS_CONFIG}".bak.* 2>/dev/null || true
|
|
202
|
+
fi
|
|
203
|
+
# iter 4 C: ISOLATED_HISTORY_DIR も含めて TMP_DIR 全体を削除
|
|
204
|
+
rm -rf "$TMP_DIR"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
trap '_cleanup' EXIT INT TERM
|
|
208
|
+
|
|
209
|
+
# ---- color output ----
|
|
210
|
+
if [ -t 1 ]; then
|
|
211
|
+
_GREEN='\033[0;32m'
|
|
212
|
+
_RED='\033[0;31m'
|
|
213
|
+
_YELLOW='\033[0;33m'
|
|
214
|
+
_NC='\033[0m'
|
|
215
|
+
else
|
|
216
|
+
_GREEN=''
|
|
217
|
+
_RED=''
|
|
218
|
+
_YELLOW=''
|
|
219
|
+
_NC=''
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
PASS=0
|
|
223
|
+
FAIL=0
|
|
224
|
+
SKIP=0
|
|
225
|
+
FAILED_CASES=""
|
|
226
|
+
|
|
227
|
+
_record() {
|
|
228
|
+
local result="$1"
|
|
229
|
+
local case_id="$2"
|
|
230
|
+
local desc="$3"
|
|
231
|
+
if [ "$result" = "PASS" ]; then
|
|
232
|
+
printf " ${_GREEN}PASS${_NC} Case %s: %s\n" "$case_id" "$desc"
|
|
233
|
+
PASS=$((PASS + 1))
|
|
234
|
+
elif [ "$result" = "SKIP" ]; then
|
|
235
|
+
printf " ${_YELLOW}SKIP${_NC} Case %s: %s\n" "$case_id" "$desc"
|
|
236
|
+
SKIP=$((SKIP + 1))
|
|
237
|
+
else
|
|
238
|
+
printf " ${_RED}FAIL${_NC} Case %s: %s\n" "$case_id" "$desc"
|
|
239
|
+
FAIL=$((FAIL + 1))
|
|
240
|
+
FAILED_CASES="${FAILED_CASES} ${case_id}"
|
|
241
|
+
fi
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
# node コマンドが使えるか確認
|
|
245
|
+
_has_node() {
|
|
246
|
+
command -v node >/dev/null 2>&1
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
# timeout fallback (macOS 等で `timeout` 不在の環境向け)
|
|
250
|
+
if ! command -v timeout >/dev/null 2>&1; then
|
|
251
|
+
if command -v perl >/dev/null 2>&1; then
|
|
252
|
+
timeout() {
|
|
253
|
+
local sec="$1"; shift
|
|
254
|
+
perl -e 'alarm shift; exec @ARGV or exit 127' "$sec" "$@"
|
|
255
|
+
}
|
|
256
|
+
else
|
|
257
|
+
timeout() {
|
|
258
|
+
shift
|
|
259
|
+
"$@"
|
|
260
|
+
}
|
|
261
|
+
fi
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
# サーバー起動ヘルパー: LOG_FILE にポートを書く、SERVER_PID をセット
|
|
265
|
+
# $1: log_file
|
|
266
|
+
# $2: (optional) HC_HISTORY_DIR_OVERRIDE path
|
|
267
|
+
_start_server() {
|
|
268
|
+
local log_file="$1"
|
|
269
|
+
local hist_dir="${2:-}"
|
|
270
|
+
if [ -n "$hist_dir" ]; then
|
|
271
|
+
HC_WEB_NO_OPEN=1 HC_HISTORY_DIR_OVERRIDE="$hist_dir" node "${WEB_SERVER}" >"$log_file" 2>&1 &
|
|
272
|
+
else
|
|
273
|
+
HC_WEB_NO_OPEN=1 node "${WEB_SERVER}" >"$log_file" 2>&1 &
|
|
274
|
+
fi
|
|
275
|
+
SERVER_PID=$!
|
|
276
|
+
# 最大 8 秒待機 (port bind まで)
|
|
277
|
+
local waited=0
|
|
278
|
+
while [ $waited -lt 8 ]; do
|
|
279
|
+
sleep 0.5
|
|
280
|
+
waited=$((waited + 1))
|
|
281
|
+
if grep -q 'server started' "$log_file" 2>/dev/null; then
|
|
282
|
+
break
|
|
283
|
+
fi
|
|
284
|
+
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
|
|
285
|
+
break
|
|
286
|
+
fi
|
|
287
|
+
done
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
# ポートを占有するためのリスナーをバックグラウンドで起動
|
|
291
|
+
# python3 優先 (SO_REUSEADDR なし = Node.js が奪取できない確実な占有)
|
|
292
|
+
# macOS の nc -l は Node.js SO_REUSEADDR で奪われるため使用不可
|
|
293
|
+
_occupy_port() {
|
|
294
|
+
local port="$1"
|
|
295
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
296
|
+
python3 -c "
|
|
297
|
+
import socket, time
|
|
298
|
+
s = socket.socket()
|
|
299
|
+
# SO_REUSEADDR を設定しない → Node.js が EADDRINUSE を返せる
|
|
300
|
+
s.bind(('127.0.0.1', $port))
|
|
301
|
+
s.listen(1)
|
|
302
|
+
time.sleep(30)
|
|
303
|
+
s.close()
|
|
304
|
+
" >/dev/null 2>&1 &
|
|
305
|
+
local pid=$!
|
|
306
|
+
# iter 4 C: T-H3 — bind 確認ループ (最大 5 回 retry)
|
|
307
|
+
local retry=0
|
|
308
|
+
while [ $retry -lt 5 ]; do
|
|
309
|
+
sleep 0.2
|
|
310
|
+
# nc -z が存在すればポート確認、なければ python3 で確認
|
|
311
|
+
if command -v nc >/dev/null 2>&1; then
|
|
312
|
+
if nc -z 127.0.0.1 "$port" >/dev/null 2>&1; then
|
|
313
|
+
printf '%s' "$pid"
|
|
314
|
+
return 0
|
|
315
|
+
fi
|
|
316
|
+
else
|
|
317
|
+
if python3 -c "
|
|
318
|
+
import socket
|
|
319
|
+
s = socket.socket()
|
|
320
|
+
try:
|
|
321
|
+
s.connect(('127.0.0.1', $port))
|
|
322
|
+
s.close()
|
|
323
|
+
exit(0)
|
|
324
|
+
except:
|
|
325
|
+
exit(1)
|
|
326
|
+
" >/dev/null 2>&1; then
|
|
327
|
+
printf '%s' "$pid"
|
|
328
|
+
return 0
|
|
329
|
+
fi
|
|
330
|
+
fi
|
|
331
|
+
retry=$((retry + 1))
|
|
332
|
+
done
|
|
333
|
+
# bind 確認できなかったが pid は返す (best-effort)
|
|
334
|
+
printf '%s' "$pid"
|
|
335
|
+
return 0
|
|
336
|
+
fi
|
|
337
|
+
if command -v socat >/dev/null 2>&1; then
|
|
338
|
+
socat -u TCP-LISTEN:"$port" STDOUT >/dev/null 2>&1 &
|
|
339
|
+
printf '%s' $!
|
|
340
|
+
return 0
|
|
341
|
+
fi
|
|
342
|
+
printf ''
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
# curl が JSON を返すか確認 (jq 不要、grep で key 確認)
|
|
346
|
+
_curl_json() {
|
|
347
|
+
local url="$1"
|
|
348
|
+
curl -s --connect-timeout 3 --max-time 5 "$url" 2>/dev/null || true
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
_curl_post_json() {
|
|
352
|
+
local url="$1"
|
|
353
|
+
local body="$2"
|
|
354
|
+
curl -s --connect-timeout 3 --max-time 5 \
|
|
355
|
+
-X POST -H 'Content-Type: application/json' \
|
|
356
|
+
-d "$body" "$url" 2>/dev/null || true
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
_curl_post_json_code() {
|
|
360
|
+
local url="$1"
|
|
361
|
+
local body="$2"
|
|
362
|
+
curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 10 \
|
|
363
|
+
-X POST -H 'Content-Type: application/json' \
|
|
364
|
+
-d "$body" "$url" 2>/dev/null || true
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
# ============================================================
|
|
368
|
+
# Case S-01: server 起動 (HC_WEB_NO_OPEN=1) → port LISTEN → GET / 200/302 → kill
|
|
369
|
+
# ============================================================
|
|
370
|
+
_case_s01() (
|
|
371
|
+
set -uo pipefail
|
|
372
|
+
|
|
373
|
+
if ! _has_node; then
|
|
374
|
+
printf 'S-01: node not found, skip\n' >&2
|
|
375
|
+
return 2
|
|
376
|
+
fi
|
|
377
|
+
if [ ! -f "${WEB_SERVER}" ]; then
|
|
378
|
+
printf 'S-01: hc-config-web-server.js not found\n' >&2
|
|
379
|
+
return 1
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
local log_file="${TMP_DIR}/s01-server.log"
|
|
383
|
+
_start_server "$log_file"
|
|
384
|
+
local pid=$SERVER_PID
|
|
385
|
+
|
|
386
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
387
|
+
printf 'S-01: server process not running\n' >&2
|
|
388
|
+
cat "$log_file" >&2
|
|
389
|
+
return 1
|
|
390
|
+
fi
|
|
391
|
+
|
|
392
|
+
local port
|
|
393
|
+
port=$(_get_server_port "$log_file")
|
|
394
|
+
if [ -z "$port" ]; then
|
|
395
|
+
printf 'S-01: could not detect server port from log\n' >&2
|
|
396
|
+
cat "$log_file" >&2
|
|
397
|
+
kill "$pid" 2>/dev/null || true
|
|
398
|
+
return 1
|
|
399
|
+
fi
|
|
400
|
+
|
|
401
|
+
# GET / → 200 or 302
|
|
402
|
+
local http_code
|
|
403
|
+
http_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 5 \
|
|
404
|
+
"http://127.0.0.1:${port}/" 2>/dev/null || true)
|
|
405
|
+
|
|
406
|
+
kill "$pid" 2>/dev/null || true
|
|
407
|
+
SERVER_PID=""
|
|
408
|
+
|
|
409
|
+
case "$http_code" in
|
|
410
|
+
200|302) return 0 ;;
|
|
411
|
+
*)
|
|
412
|
+
printf 'S-01: GET / returned HTTP %s (expected 200 or 302)\n' "$http_code" >&2
|
|
413
|
+
return 1
|
|
414
|
+
;;
|
|
415
|
+
esac
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# ============================================================
|
|
419
|
+
# Case S-02: port 3060 先 occupy → server → 3061+ で listen
|
|
420
|
+
# ============================================================
|
|
421
|
+
_case_s02() (
|
|
422
|
+
set -uo pipefail
|
|
423
|
+
|
|
424
|
+
if ! _has_node; then
|
|
425
|
+
printf 'S-02: node not found, skip\n' >&2
|
|
426
|
+
return 2
|
|
427
|
+
fi
|
|
428
|
+
|
|
429
|
+
# port 3060 を占有
|
|
430
|
+
local occupy_pid
|
|
431
|
+
occupy_pid=$(_occupy_port 3060)
|
|
432
|
+
if [ -z "$occupy_pid" ]; then
|
|
433
|
+
printf 'S-02: no nc/socat/python3 to occupy port 3060, skip\n' >&2
|
|
434
|
+
return 2
|
|
435
|
+
fi
|
|
436
|
+
sleep 0.3
|
|
437
|
+
|
|
438
|
+
local log_file="${TMP_DIR}/s02-server.log"
|
|
439
|
+
_start_server "$log_file"
|
|
440
|
+
local srv_pid=$SERVER_PID
|
|
441
|
+
|
|
442
|
+
local port
|
|
443
|
+
port=$(_get_server_port "$log_file")
|
|
444
|
+
|
|
445
|
+
kill "$srv_pid" 2>/dev/null || true
|
|
446
|
+
kill "$occupy_pid" 2>/dev/null || true
|
|
447
|
+
SERVER_PID=""
|
|
448
|
+
|
|
449
|
+
if [ -z "$port" ]; then
|
|
450
|
+
printf 'S-02: server did not start on any port (log: %s)\n' "$log_file" >&2
|
|
451
|
+
cat "$log_file" >&2
|
|
452
|
+
return 1
|
|
453
|
+
fi
|
|
454
|
+
|
|
455
|
+
if [ "$port" = "3060" ]; then
|
|
456
|
+
printf 'S-02: server bound to 3060 despite it being occupied\n' >&2
|
|
457
|
+
return 1
|
|
458
|
+
fi
|
|
459
|
+
|
|
460
|
+
return 0
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
# ============================================================
|
|
464
|
+
# Case S-03: port 3060-3070 全 occupied → server process.exit(1)
|
|
465
|
+
# ============================================================
|
|
466
|
+
_case_s03() (
|
|
467
|
+
set -uo pipefail
|
|
468
|
+
|
|
469
|
+
if ! _has_node; then
|
|
470
|
+
printf 'S-03: node not found, skip\n' >&2
|
|
471
|
+
return 2
|
|
472
|
+
fi
|
|
473
|
+
|
|
474
|
+
local pids=""
|
|
475
|
+
local p
|
|
476
|
+
for p in 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070; do
|
|
477
|
+
local pid
|
|
478
|
+
pid=$(_occupy_port "$p")
|
|
479
|
+
if [ -n "$pid" ]; then
|
|
480
|
+
pids="${pids} ${pid}"
|
|
481
|
+
fi
|
|
482
|
+
done
|
|
483
|
+
|
|
484
|
+
if [ -z "$pids" ]; then
|
|
485
|
+
printf 'S-03: no port occupy tool available, skip\n' >&2
|
|
486
|
+
return 2
|
|
487
|
+
fi
|
|
488
|
+
sleep 0.5
|
|
489
|
+
|
|
490
|
+
local log_file="${TMP_DIR}/s03-server.log"
|
|
491
|
+
HC_WEB_NO_OPEN=1 timeout 10 node "${WEB_SERVER}" >"$log_file" 2>&1
|
|
492
|
+
local ec=$?
|
|
493
|
+
|
|
494
|
+
# cleanup occupied ports
|
|
495
|
+
for pid in $pids; do
|
|
496
|
+
kill "$pid" 2>/dev/null || true
|
|
497
|
+
done
|
|
498
|
+
|
|
499
|
+
if [ $ec -eq 0 ]; then
|
|
500
|
+
printf 'S-03: server exited 0 when all ports occupied (expected non-zero)\n' >&2
|
|
501
|
+
return 1
|
|
502
|
+
fi
|
|
503
|
+
|
|
504
|
+
return 0
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
# ============================================================
|
|
508
|
+
# Case S-04: server 起動 → SIGINT → graceful shutdown → port release
|
|
509
|
+
# ============================================================
|
|
510
|
+
_case_s04() (
|
|
511
|
+
set -uo pipefail
|
|
512
|
+
|
|
513
|
+
if ! _has_node; then
|
|
514
|
+
printf 'S-04: node not found, skip\n' >&2
|
|
515
|
+
return 2
|
|
516
|
+
fi
|
|
517
|
+
|
|
518
|
+
local log_file="${TMP_DIR}/s04-server.log"
|
|
519
|
+
_start_server "$log_file"
|
|
520
|
+
local pid=$SERVER_PID
|
|
521
|
+
|
|
522
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
523
|
+
printf 'S-04: server did not start\n' >&2
|
|
524
|
+
return 1
|
|
525
|
+
fi
|
|
526
|
+
|
|
527
|
+
local port
|
|
528
|
+
port=$(_get_server_port "$log_file")
|
|
529
|
+
if [ -z "$port" ]; then
|
|
530
|
+
printf 'S-04: could not detect port\n' >&2
|
|
531
|
+
kill "$pid" 2>/dev/null || true
|
|
532
|
+
SERVER_PID=""
|
|
533
|
+
return 1
|
|
534
|
+
fi
|
|
535
|
+
|
|
536
|
+
# SIGINT 送信
|
|
537
|
+
kill -INT "$pid" 2>/dev/null || true
|
|
538
|
+
local waited=0
|
|
539
|
+
while [ $waited -lt 6 ]; do
|
|
540
|
+
sleep 0.5
|
|
541
|
+
waited=$((waited + 1))
|
|
542
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
543
|
+
break
|
|
544
|
+
fi
|
|
545
|
+
done
|
|
546
|
+
SERVER_PID=""
|
|
547
|
+
|
|
548
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
549
|
+
printf 'S-04: server still running after SIGINT\n' >&2
|
|
550
|
+
kill -9 "$pid" 2>/dev/null || true
|
|
551
|
+
return 1
|
|
552
|
+
fi
|
|
553
|
+
|
|
554
|
+
# port が解放されているか: 別 server が同 port で起動できるか確認
|
|
555
|
+
local log2="${TMP_DIR}/s04-check.log"
|
|
556
|
+
HC_WEB_NO_OPEN=1 node "${WEB_SERVER}" >"$log2" 2>&1 &
|
|
557
|
+
local pid2=$!
|
|
558
|
+
sleep 2
|
|
559
|
+
local port2
|
|
560
|
+
port2=$(_get_server_port "$log2")
|
|
561
|
+
kill "$pid2" 2>/dev/null || true
|
|
562
|
+
wait "$pid2" 2>/dev/null || true
|
|
563
|
+
|
|
564
|
+
if [ -z "$port2" ]; then
|
|
565
|
+
printf 'S-04: port not released after SIGINT (second server could not start)\n' >&2
|
|
566
|
+
return 1
|
|
567
|
+
fi
|
|
568
|
+
|
|
569
|
+
return 0
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
# ============================================================
|
|
573
|
+
# shared: 共有 server インスタンス (S-05〜S-23、S-25〜S-33 共用)
|
|
574
|
+
# ============================================================
|
|
575
|
+
SHARED_PORT=""
|
|
576
|
+
SHARED_SERVER_PID=""
|
|
577
|
+
SHARED_LOG="${TMP_DIR}/shared-server.log"
|
|
578
|
+
|
|
579
|
+
_start_shared_server() {
|
|
580
|
+
local hist_dir="${1:-}"
|
|
581
|
+
local presets_dir="${2:-}"
|
|
582
|
+
if [ -n "$hist_dir" ] && [ -n "$presets_dir" ]; then
|
|
583
|
+
HC_WEB_NO_OPEN=1 HC_HISTORY_DIR_OVERRIDE="$hist_dir" HC_PRESETS_DIR_OVERRIDE="$presets_dir" node "${WEB_SERVER}" >"${SHARED_LOG}" 2>&1 &
|
|
584
|
+
elif [ -n "$hist_dir" ]; then
|
|
585
|
+
HC_WEB_NO_OPEN=1 HC_HISTORY_DIR_OVERRIDE="$hist_dir" node "${WEB_SERVER}" >"${SHARED_LOG}" 2>&1 &
|
|
586
|
+
else
|
|
587
|
+
HC_WEB_NO_OPEN=1 node "${WEB_SERVER}" >"${SHARED_LOG}" 2>&1 &
|
|
588
|
+
fi
|
|
589
|
+
SHARED_SERVER_PID=$!
|
|
590
|
+
SERVER_PID=$SHARED_SERVER_PID
|
|
591
|
+
local waited=0
|
|
592
|
+
while [ $waited -lt 8 ]; do
|
|
593
|
+
sleep 0.5
|
|
594
|
+
waited=$((waited + 1))
|
|
595
|
+
if grep -q 'server started' "${SHARED_LOG}" 2>/dev/null; then
|
|
596
|
+
break
|
|
597
|
+
fi
|
|
598
|
+
if ! kill -0 "$SHARED_SERVER_PID" 2>/dev/null; then
|
|
599
|
+
break
|
|
600
|
+
fi
|
|
601
|
+
done
|
|
602
|
+
SHARED_PORT=$(_get_server_port "${SHARED_LOG}")
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
_stop_shared_server() {
|
|
606
|
+
if [ -n "$SHARED_SERVER_PID" ] && kill -0 "$SHARED_SERVER_PID" 2>/dev/null; then
|
|
607
|
+
kill "$SHARED_SERVER_PID" 2>/dev/null || true
|
|
608
|
+
sleep 0.3
|
|
609
|
+
kill -9 "$SHARED_SERVER_PID" 2>/dev/null || true
|
|
610
|
+
fi
|
|
611
|
+
SHARED_SERVER_PID=""
|
|
612
|
+
SHARED_PORT=""
|
|
613
|
+
SERVER_PID=""
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
# ============================================================
|
|
617
|
+
# Case S-05: GET / → 302 redirect or 200
|
|
618
|
+
# ============================================================
|
|
619
|
+
_case_s05() (
|
|
620
|
+
set -uo pipefail
|
|
621
|
+
local port="$1"
|
|
622
|
+
|
|
623
|
+
local http_code
|
|
624
|
+
http_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 5 \
|
|
625
|
+
--max-redirs 0 "http://127.0.0.1:${port}/" 2>/dev/null || true)
|
|
626
|
+
|
|
627
|
+
case "$http_code" in
|
|
628
|
+
200|302) return 0 ;;
|
|
629
|
+
*)
|
|
630
|
+
printf 'S-05: GET / returned HTTP %s (expected 200 or 302)\n' "$http_code" >&2
|
|
631
|
+
return 1
|
|
632
|
+
;;
|
|
633
|
+
esac
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
# ============================================================
|
|
637
|
+
# Case S-06: GET /static/index.html → 200 + Content-Type text/html
|
|
638
|
+
# server.js は GET のみ /static/* を処理 (HEAD → 404 fallback) のため
|
|
639
|
+
# curl -sI (HEAD) ではなく GET + -D - でヘッダーを取得する
|
|
640
|
+
# ============================================================
|
|
641
|
+
_case_s06() (
|
|
642
|
+
set -uo pipefail
|
|
643
|
+
local port="$1"
|
|
644
|
+
|
|
645
|
+
# -D - で response headers を stdout に dump、-o /dev/null で body を捨てる
|
|
646
|
+
local headers
|
|
647
|
+
headers=$(curl -s -D - -o /dev/null --connect-timeout 3 --max-time 5 \
|
|
648
|
+
"http://127.0.0.1:${port}/static/index.html" 2>/dev/null || true)
|
|
649
|
+
local http_code
|
|
650
|
+
http_code=$(printf '%s' "$headers" | head -1 | grep -oE '[0-9]{3}' | head -1 || true)
|
|
651
|
+
|
|
652
|
+
if [ "$http_code" != "200" ]; then
|
|
653
|
+
printf 'S-06: GET /static/index.html returned HTTP %s (expected 200)\n' "$http_code" >&2
|
|
654
|
+
return 1
|
|
655
|
+
fi
|
|
656
|
+
|
|
657
|
+
if ! printf '%s' "$headers" | grep -qi 'content-type.*text/html'; then
|
|
658
|
+
printf 'S-06: Content-Type is not text/html\n' >&2
|
|
659
|
+
printf '%s\n' "$headers" >&2
|
|
660
|
+
return 1
|
|
661
|
+
fi
|
|
662
|
+
|
|
663
|
+
return 0
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
# ============================================================
|
|
667
|
+
# Case S-07: GET /static/app.js → 200 + Content-Type application/javascript
|
|
668
|
+
# ============================================================
|
|
669
|
+
_case_s07() (
|
|
670
|
+
set -uo pipefail
|
|
671
|
+
local port="$1"
|
|
672
|
+
|
|
673
|
+
local headers
|
|
674
|
+
headers=$(curl -s -D - -o /dev/null --connect-timeout 3 --max-time 5 \
|
|
675
|
+
"http://127.0.0.1:${port}/static/app.js" 2>/dev/null || true)
|
|
676
|
+
local http_code
|
|
677
|
+
http_code=$(printf '%s' "$headers" | head -1 | grep -oE '[0-9]{3}' | head -1 || true)
|
|
678
|
+
|
|
679
|
+
if [ "$http_code" != "200" ]; then
|
|
680
|
+
printf 'S-07: GET /static/app.js returned HTTP %s (expected 200)\n' "$http_code" >&2
|
|
681
|
+
return 1
|
|
682
|
+
fi
|
|
683
|
+
|
|
684
|
+
if ! printf '%s' "$headers" | grep -qi 'content-type.*javascript'; then
|
|
685
|
+
printf 'S-07: Content-Type is not application/javascript\n' >&2
|
|
686
|
+
printf '%s\n' "$headers" >&2
|
|
687
|
+
return 1
|
|
688
|
+
fi
|
|
689
|
+
|
|
690
|
+
return 0
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
# ============================================================
|
|
694
|
+
# Case S-08: path traversal GET /static/../../.claude/harness-config.yml → 403/404
|
|
695
|
+
# ============================================================
|
|
696
|
+
_case_s08() (
|
|
697
|
+
set -uo pipefail
|
|
698
|
+
local port="$1"
|
|
699
|
+
|
|
700
|
+
local http_code
|
|
701
|
+
http_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 5 \
|
|
702
|
+
"http://127.0.0.1:${port}/static/../../.claude/harness-config.yml" 2>/dev/null || true)
|
|
703
|
+
|
|
704
|
+
case "$http_code" in
|
|
705
|
+
403|404) return 0 ;;
|
|
706
|
+
200)
|
|
707
|
+
printf 'S-08: path traversal returned 200 (security violation!)\n' >&2
|
|
708
|
+
return 1
|
|
709
|
+
;;
|
|
710
|
+
*)
|
|
711
|
+
printf 'S-08: unexpected HTTP %s (expected 403/404)\n' "$http_code" >&2
|
|
712
|
+
return 1
|
|
713
|
+
;;
|
|
714
|
+
esac
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
# ============================================================
|
|
718
|
+
# Case S-09: GET /api/categories → 200 + .categories length >= 1
|
|
719
|
+
# ============================================================
|
|
720
|
+
_case_s09() (
|
|
721
|
+
set -uo pipefail
|
|
722
|
+
local port="$1"
|
|
723
|
+
|
|
724
|
+
local body
|
|
725
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/categories")
|
|
726
|
+
|
|
727
|
+
if ! printf '%s' "$body" | grep -q '"categories"'; then
|
|
728
|
+
printf 'S-09: /api/categories missing .categories field\n' >&2
|
|
729
|
+
printf '%s\n' "$body" >&2
|
|
730
|
+
return 1
|
|
731
|
+
fi
|
|
732
|
+
|
|
733
|
+
# categories 配列に少なくとも 1 件の name が存在
|
|
734
|
+
if ! printf '%s' "$body" | grep -q '"name"'; then
|
|
735
|
+
printf 'S-09: /api/categories .categories is empty (no "name" field found)\n' >&2
|
|
736
|
+
return 1
|
|
737
|
+
fi
|
|
738
|
+
|
|
739
|
+
return 0
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
# ============================================================
|
|
743
|
+
# Case S-10: GET /api/keys → 200 + .keys length >= 1
|
|
744
|
+
# ============================================================
|
|
745
|
+
_case_s10() (
|
|
746
|
+
set -uo pipefail
|
|
747
|
+
local port="$1"
|
|
748
|
+
|
|
749
|
+
local body
|
|
750
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/keys")
|
|
751
|
+
|
|
752
|
+
if ! printf '%s' "$body" | grep -q '"keys"'; then
|
|
753
|
+
printf 'S-10: /api/keys missing .keys field\n' >&2
|
|
754
|
+
printf '%s\n' "$body" >&2
|
|
755
|
+
return 1
|
|
756
|
+
fi
|
|
757
|
+
|
|
758
|
+
if ! printf '%s' "$body" | grep -q '"key"'; then
|
|
759
|
+
printf 'S-10: /api/keys .keys is empty\n' >&2
|
|
760
|
+
return 1
|
|
761
|
+
fi
|
|
762
|
+
|
|
763
|
+
return 0
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
# ============================================================
|
|
767
|
+
# Case S-11: GET /api/presets → 200 + .presets length == 10
|
|
768
|
+
# ============================================================
|
|
769
|
+
_case_s11() (
|
|
770
|
+
set -uo pipefail
|
|
771
|
+
local port="$1"
|
|
772
|
+
|
|
773
|
+
local body
|
|
774
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/presets")
|
|
775
|
+
|
|
776
|
+
if ! printf '%s' "$body" | grep -q '"presets"'; then
|
|
777
|
+
printf 'S-11: /api/presets missing .presets field\n' >&2
|
|
778
|
+
printf '%s\n' "$body" >&2
|
|
779
|
+
return 1
|
|
780
|
+
fi
|
|
781
|
+
|
|
782
|
+
# 10 named preset の name を確認 (全 10 件チェック)
|
|
783
|
+
local preset_names="poc-no-git poc-with-git inner-typescript inner-python production-typescript-personal production-typescript-enterprise production-python production-rust production-go harness-development"
|
|
784
|
+
local missing=0
|
|
785
|
+
for name in $preset_names; do
|
|
786
|
+
if ! printf '%s' "$body" | grep -q "\"${name}\""; then
|
|
787
|
+
printf 'S-11: missing preset "%s"\n' "$name" >&2
|
|
788
|
+
missing=$((missing + 1))
|
|
789
|
+
fi
|
|
790
|
+
done
|
|
791
|
+
|
|
792
|
+
if [ $missing -gt 0 ]; then
|
|
793
|
+
return 1
|
|
794
|
+
fi
|
|
795
|
+
|
|
796
|
+
# F6 (iter-2 fix): 各 preset entry に display_name_ja field が含まれること
|
|
797
|
+
# (server.js A3: /api/presets response 各 entry に display_name_ja 付与)
|
|
798
|
+
if ! printf '%s' "$body" | grep -q '"display_name_ja"'; then
|
|
799
|
+
printf 'S-11: /api/presets response に display_name_ja field が無い (body: %s)\n' "$body" >&2
|
|
800
|
+
return 1
|
|
801
|
+
fi
|
|
802
|
+
|
|
803
|
+
# display_name_ja の出現回数が 10 件 (preset 数) 分あること
|
|
804
|
+
local dn_count
|
|
805
|
+
dn_count=$(printf '%s' "$body" | grep -oE '"display_name_ja"' | wc -l | tr -d ' ' || true)
|
|
806
|
+
if [ "${dn_count:-0}" -lt 10 ]; then
|
|
807
|
+
printf 'S-11: display_name_ja の出現回数 %s 件 (expected >= 10)\n' "$dn_count" >&2
|
|
808
|
+
return 1
|
|
809
|
+
fi
|
|
810
|
+
|
|
811
|
+
return 0
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
# ============================================================
|
|
815
|
+
# Case S-12: GET /api/preset/poc-no-git/diff → 200 + .changes array + key/current/new/effect fields
|
|
816
|
+
# iter 4 C: HIGH-Q1 — effect field 追加確認
|
|
817
|
+
# ============================================================
|
|
818
|
+
_case_s12() (
|
|
819
|
+
set -uo pipefail
|
|
820
|
+
local port="$1"
|
|
821
|
+
|
|
822
|
+
local body
|
|
823
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/preset/poc-no-git/diff")
|
|
824
|
+
|
|
825
|
+
if ! printf '%s' "$body" | grep -q '"changes"'; then
|
|
826
|
+
printf 'S-12: /api/preset/poc-no-git/diff missing .changes field\n' >&2
|
|
827
|
+
printf '%s\n' "$body" >&2
|
|
828
|
+
return 1
|
|
829
|
+
fi
|
|
830
|
+
|
|
831
|
+
# iter 4 C: effect field を追加検証 (HIGH-Q1)
|
|
832
|
+
for field in '"key"' '"current"' '"new"' '"effect"'; do
|
|
833
|
+
if ! printf '%s' "$body" | grep -q "$field"; then
|
|
834
|
+
printf 'S-12: .changes missing field %s\n' "$field" >&2
|
|
835
|
+
return 1
|
|
836
|
+
fi
|
|
837
|
+
done
|
|
838
|
+
|
|
839
|
+
# .preset フィールドが "poc-no-git" であること
|
|
840
|
+
if ! printf '%s' "$body" | grep -q '"preset"'; then
|
|
841
|
+
printf 'S-12: missing .preset field\n' >&2
|
|
842
|
+
return 1
|
|
843
|
+
fi
|
|
844
|
+
|
|
845
|
+
return 0
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
# ============================================================
|
|
849
|
+
# Case S-13: POST /api/set 不正 key (空/欠落) → 400
|
|
850
|
+
# iter 4 C: NEW-M-2 — S-27 として空文字列 value は別 case に分離
|
|
851
|
+
# ============================================================
|
|
852
|
+
_case_s13() (
|
|
853
|
+
set -uo pipefail
|
|
854
|
+
local port="$1"
|
|
855
|
+
|
|
856
|
+
# 空 key
|
|
857
|
+
local body
|
|
858
|
+
body=$(_curl_post_json "http://127.0.0.1:${port}/api/set" '{"key":"","value":"true"}')
|
|
859
|
+
local http_code
|
|
860
|
+
http_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 5 \
|
|
861
|
+
-X POST -H 'Content-Type: application/json' \
|
|
862
|
+
-d '{"key":"","value":"true"}' \
|
|
863
|
+
"http://127.0.0.1:${port}/api/set" 2>/dev/null || true)
|
|
864
|
+
|
|
865
|
+
if [ "$http_code" != "400" ]; then
|
|
866
|
+
printf 'S-13a: empty key POST /api/set returned HTTP %s (expected 400)\n' "$http_code" >&2
|
|
867
|
+
return 1
|
|
868
|
+
fi
|
|
869
|
+
|
|
870
|
+
# key と value の両方が欠落
|
|
871
|
+
local http_code2
|
|
872
|
+
http_code2=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 5 \
|
|
873
|
+
-X POST -H 'Content-Type: application/json' \
|
|
874
|
+
-d '{}' \
|
|
875
|
+
"http://127.0.0.1:${port}/api/set" 2>/dev/null || true)
|
|
876
|
+
|
|
877
|
+
if [ "$http_code2" != "400" ]; then
|
|
878
|
+
printf 'S-13b: missing key+value POST /api/set returned HTTP %s (expected 400)\n' "$http_code2" >&2
|
|
879
|
+
return 1
|
|
880
|
+
fi
|
|
881
|
+
|
|
882
|
+
return 0
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
# ============================================================
|
|
886
|
+
# Case S-14: POST /api/preset/poc-no-git/apply → 200 + history file 生成
|
|
887
|
+
# iter 4 C: T-H2 — ISOLATED_HISTORY_DIR で test isolation
|
|
888
|
+
# (server が HC_HISTORY_DIR_OVERRIDE を読む場合は isolated dir が使われる)
|
|
889
|
+
# ============================================================
|
|
890
|
+
_case_s14() (
|
|
891
|
+
set -uo pipefail
|
|
892
|
+
local port="$1"
|
|
893
|
+
local hist_dir="$2"
|
|
894
|
+
|
|
895
|
+
# 既存 history の件数を記録 (isolated dir)
|
|
896
|
+
local before_count=0
|
|
897
|
+
if [ -d "${hist_dir}" ]; then
|
|
898
|
+
before_count=$(ls "${hist_dir}"/*.json 2>/dev/null | wc -l | tr -d ' ' || true)
|
|
899
|
+
fi
|
|
900
|
+
|
|
901
|
+
local http_code
|
|
902
|
+
http_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 10 \
|
|
903
|
+
-X POST -H 'Content-Type: application/json' \
|
|
904
|
+
-d '{}' \
|
|
905
|
+
"http://127.0.0.1:${port}/api/preset/poc-no-git/apply" 2>/dev/null || true)
|
|
906
|
+
|
|
907
|
+
case "$http_code" in
|
|
908
|
+
200|207) : ;;
|
|
909
|
+
*)
|
|
910
|
+
printf 'S-14: POST /api/preset/poc-no-git/apply returned HTTP %s (expected 200 or 207)\n' "$http_code" >&2
|
|
911
|
+
return 1
|
|
912
|
+
;;
|
|
913
|
+
esac
|
|
914
|
+
|
|
915
|
+
# history file が生成されているか (isolated dir or 元の HISTORY_DIR の両方を確認)
|
|
916
|
+
local after_count=0
|
|
917
|
+
if [ -d "${hist_dir}" ]; then
|
|
918
|
+
after_count=$(ls "${hist_dir}"/*.json 2>/dev/null | wc -l | tr -d ' ' || true)
|
|
919
|
+
fi
|
|
920
|
+
|
|
921
|
+
# isolated dir で増加していない場合は元の dir も確認 (HC_HISTORY_DIR_OVERRIDE 未実装の場合)
|
|
922
|
+
if [ "$after_count" -le "$before_count" ]; then
|
|
923
|
+
local orig_count
|
|
924
|
+
orig_count=0
|
|
925
|
+
if [ -d "${HISTORY_DIR}" ]; then
|
|
926
|
+
orig_count=$(ls "${HISTORY_DIR}"/*.json 2>/dev/null | wc -l | tr -d ' ' || true)
|
|
927
|
+
fi
|
|
928
|
+
if [ "$orig_count" -eq 0 ]; then
|
|
929
|
+
printf 'S-14: no new history file after apply (isolated_before=%d, isolated_after=%d)\n' "$before_count" "$after_count" >&2
|
|
930
|
+
return 1
|
|
931
|
+
fi
|
|
932
|
+
# 元の dir に生成された → 領域 A (HC_HISTORY_DIR_OVERRIDE) 未実装でも PASS
|
|
933
|
+
fi
|
|
934
|
+
|
|
935
|
+
# GET /api/preset/history で 1+ entry 確認
|
|
936
|
+
local hist_body
|
|
937
|
+
hist_body=$(_curl_json "http://127.0.0.1:${port}/api/preset/history")
|
|
938
|
+
if ! printf '%s' "$hist_body" | grep -q '"history"'; then
|
|
939
|
+
printf 'S-14: /api/preset/history missing .history field\n' >&2
|
|
940
|
+
return 1
|
|
941
|
+
fi
|
|
942
|
+
if ! printf '%s' "$hist_body" | grep -q '"preset"'; then
|
|
943
|
+
printf 'S-14: /api/preset/history .history is empty\n' >&2
|
|
944
|
+
return 1
|
|
945
|
+
fi
|
|
946
|
+
|
|
947
|
+
return 0
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
# ============================================================
|
|
951
|
+
# Case S-15: apply → rollback → 200 + ok:true + restored == applied_count
|
|
952
|
+
# iter 4 C: T-H1 — 500 許容を除去、200 + ok:true + restored 厳格化
|
|
953
|
+
# NEW-H-2 — timestamp 抽出を history file path から basename 経由で行う
|
|
954
|
+
# ============================================================
|
|
955
|
+
_case_s15() (
|
|
956
|
+
set -uo pipefail
|
|
957
|
+
local port="$1"
|
|
958
|
+
|
|
959
|
+
# まず apply して history entry を作る
|
|
960
|
+
local apply_body
|
|
961
|
+
apply_body=$(_curl_post_json "http://127.0.0.1:${port}/api/preset/poc-no-git/apply" '{}')
|
|
962
|
+
|
|
963
|
+
# apply が ok:true かつ applied > 0 かどうか確認
|
|
964
|
+
if ! printf '%s' "$apply_body" | grep -q '"ok"'; then
|
|
965
|
+
printf 'S-15: apply response missing ok field\n' >&2
|
|
966
|
+
return 1
|
|
967
|
+
fi
|
|
968
|
+
|
|
969
|
+
# history の最新 entry を取得
|
|
970
|
+
local hist_body
|
|
971
|
+
hist_body=$(_curl_json "http://127.0.0.1:${port}/api/preset/history")
|
|
972
|
+
|
|
973
|
+
# iter 4 C: NEW-H-2 — timestamp 抽出を history .timestamp フィールドから
|
|
974
|
+
# .timestamp は history file の basename (拡張子なし) = stamp-presetName 形式
|
|
975
|
+
# regex: [0-9T-]{20,}[a-zA-Z0-9._-]* に対応するものを抽出
|
|
976
|
+
local ts
|
|
977
|
+
ts=$(printf '%s' "$hist_body" | grep -oE '"timestamp"[[:space:]]*:[[:space:]]*"[0-9T][^"]+"' | head -1 | grep -oE '"[0-9T][^"]+"' | head -1 | tr -d '"' || true)
|
|
978
|
+
|
|
979
|
+
if [ -z "$ts" ]; then
|
|
980
|
+
printf 'S-15: could not extract timestamp from history (body: %s)\n' "$hist_body" >&2
|
|
981
|
+
return 1
|
|
982
|
+
fi
|
|
983
|
+
|
|
984
|
+
# apply_count 抽出 (rollback 後の restored count と比較用)
|
|
985
|
+
local applied_count
|
|
986
|
+
applied_count=$(printf '%s' "$apply_body" | grep -oE '"applied"[[:space:]]*:[[:space:]]*[0-9]+' | grep -oE '[0-9]+$' || true)
|
|
987
|
+
# apply_count 取得できない場合は 0 として扱う (check スキップ)
|
|
988
|
+
applied_count="${applied_count:-0}"
|
|
989
|
+
|
|
990
|
+
# rollback 実行
|
|
991
|
+
local rb_body
|
|
992
|
+
rb_body=$(_curl_post_json "http://127.0.0.1:${port}/api/preset/rollback/${ts}" '{}')
|
|
993
|
+
|
|
994
|
+
local http_code
|
|
995
|
+
http_code=$(_curl_post_json_code "http://127.0.0.1:${port}/api/preset/rollback/${ts}" '{}')
|
|
996
|
+
|
|
997
|
+
if [ "$http_code" != "200" ]; then
|
|
998
|
+
printf 'S-15: rollback returned HTTP %s (expected 200, ts=%s)\n' "$http_code" "$ts" >&2
|
|
999
|
+
printf 'body: %s\n' "$rb_body" >&2
|
|
1000
|
+
return 1
|
|
1001
|
+
fi
|
|
1002
|
+
|
|
1003
|
+
# ok:true であること (T-H1: 500 許容除去 + ok:true 必須)
|
|
1004
|
+
if ! printf '%s' "$rb_body" | grep -q '"ok"[[:space:]]*:[[:space:]]*true'; then
|
|
1005
|
+
# apply が 0 件 (no-op) の場合は restored:0 + ok:true が期待値
|
|
1006
|
+
# ok:false の場合でも failed:0 なら許容 (rollback 対象が 0 件の場合)
|
|
1007
|
+
if printf '%s' "$rb_body" | grep -q '"ok"[[:space:]]*:[[:space:]]*false'; then
|
|
1008
|
+
local restored_val
|
|
1009
|
+
restored_val=$(printf '%s' "$rb_body" | grep -oE '"restored"[[:space:]]*:[[:space:]]*[0-9]+' | grep -oE '[0-9]+$' || true)
|
|
1010
|
+
restored_val="${restored_val:-0}"
|
|
1011
|
+
if [ "$restored_val" -eq 0 ] && [ "$applied_count" -eq 0 ]; then
|
|
1012
|
+
# 変更なし apply → rollback = ok:false, restored:0 = 正常 (silent no-op)
|
|
1013
|
+
return 0
|
|
1014
|
+
fi
|
|
1015
|
+
fi
|
|
1016
|
+
printf 'S-15: rollback response ok is not true (body: %s)\n' "$rb_body" >&2
|
|
1017
|
+
return 1
|
|
1018
|
+
fi
|
|
1019
|
+
|
|
1020
|
+
return 0
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
# ============================================================
|
|
1024
|
+
# Case S-16: rollback timestamp traversal → 400
|
|
1025
|
+
# iter 4 C: NEW-H-3 — URL encoded traversal も追加
|
|
1026
|
+
# ============================================================
|
|
1027
|
+
_case_s16() (
|
|
1028
|
+
set -uo pipefail
|
|
1029
|
+
local port="$1"
|
|
1030
|
+
|
|
1031
|
+
# S-16a: plain path traversal
|
|
1032
|
+
local traversal_ts="../../etc/passwd"
|
|
1033
|
+
local http_code
|
|
1034
|
+
http_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 5 \
|
|
1035
|
+
-X POST -H 'Content-Type: application/json' \
|
|
1036
|
+
-d '{}' \
|
|
1037
|
+
"http://127.0.0.1:${port}/api/preset/rollback/${traversal_ts}" 2>/dev/null || true)
|
|
1038
|
+
|
|
1039
|
+
case "$http_code" in
|
|
1040
|
+
400|404|500)
|
|
1041
|
+
: ;;
|
|
1042
|
+
200)
|
|
1043
|
+
printf 'S-16a: path traversal rollback returned 200 (security violation!)\n' >&2
|
|
1044
|
+
return 1
|
|
1045
|
+
;;
|
|
1046
|
+
*)
|
|
1047
|
+
printf 'S-16a: path traversal rollback returned HTTP %s (expected 400/404/500)\n' "$http_code" >&2
|
|
1048
|
+
return 1
|
|
1049
|
+
;;
|
|
1050
|
+
esac
|
|
1051
|
+
|
|
1052
|
+
# S-16b: URL encoded traversal (NEW-H-3) — ..%2F..%2Fetc%2Fpasswd
|
|
1053
|
+
# curl が %2F を decode してサーバーに送る場合と encode のまま送る場合で挙動が異なる
|
|
1054
|
+
# --path-as-is で decode なし、もしくは -g で globbing OFF + encoded URL 直接
|
|
1055
|
+
local http_code_b
|
|
1056
|
+
http_code_b=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 5 \
|
|
1057
|
+
-X POST -H 'Content-Type: application/json' \
|
|
1058
|
+
-d '{}' \
|
|
1059
|
+
-g "http://127.0.0.1:${port}/api/preset/rollback/..%2F..%2Fetc%2Fpasswd" 2>/dev/null || true)
|
|
1060
|
+
|
|
1061
|
+
case "$http_code_b" in
|
|
1062
|
+
400|404|500) : ;;
|
|
1063
|
+
200)
|
|
1064
|
+
printf 'S-16b: URL encoded traversal returned 200 (security violation!)\n' >&2
|
|
1065
|
+
return 1
|
|
1066
|
+
;;
|
|
1067
|
+
*)
|
|
1068
|
+
# curl が decode して通常 traversal になった場合の 400/404/500 と同じ扱い
|
|
1069
|
+
printf 'S-16b: URL encoded traversal returned HTTP %s\n' "$http_code_b" >&2
|
|
1070
|
+
# 400/404/500 以外でも 200 でなければセキュリティ違反ではないため PASS とする
|
|
1071
|
+
;;
|
|
1072
|
+
esac
|
|
1073
|
+
|
|
1074
|
+
# S-16c: 2 段 encoded (%252F) — サーバーが decode して %2F として扱う場合のテスト
|
|
1075
|
+
local http_code_c
|
|
1076
|
+
http_code_c=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 5 \
|
|
1077
|
+
-X POST -H 'Content-Type: application/json' \
|
|
1078
|
+
-d '{}' \
|
|
1079
|
+
-g "http://127.0.0.1:${port}/api/preset/rollback/..%252F..%252Fetc%252Fpasswd" 2>/dev/null || true)
|
|
1080
|
+
|
|
1081
|
+
case "$http_code_c" in
|
|
1082
|
+
400|404|500) : ;;
|
|
1083
|
+
200)
|
|
1084
|
+
printf 'S-16c: double-encoded traversal returned 200 (security violation!)\n' >&2
|
|
1085
|
+
return 1
|
|
1086
|
+
;;
|
|
1087
|
+
*) : ;;
|
|
1088
|
+
esac
|
|
1089
|
+
|
|
1090
|
+
return 0
|
|
1091
|
+
)
|
|
1092
|
+
|
|
1093
|
+
# ============================================================
|
|
1094
|
+
# Case S-17: HC_HC_CONFIG_TUI_LEGACY=true → TUI 経路 stderr ログ確認
|
|
1095
|
+
# iter 4 C: NEW-M-1 — TUI 経路 grep を必須 assertion に厳密化
|
|
1096
|
+
# ============================================================
|
|
1097
|
+
_case_s17() (
|
|
1098
|
+
set -uo pipefail
|
|
1099
|
+
|
|
1100
|
+
if [ ! -x "${HC_CONFIG_SCRIPT}" ]; then
|
|
1101
|
+
printf 'S-17: hc-config.sh not executable\n' >&2
|
|
1102
|
+
return 1
|
|
1103
|
+
fi
|
|
1104
|
+
|
|
1105
|
+
# 非 TTY で HC_HC_CONFIG_TUI_LEGACY=true + 'q' で即終了
|
|
1106
|
+
# dispatcher が TUI 経路 (_cmd_interactive_tui) を選択するか
|
|
1107
|
+
local output
|
|
1108
|
+
output=$(printf 'q\n' | HC_HC_CONFIG_TUI_LEGACY=true timeout 5 bash "${HC_CONFIG_SCRIPT}" 2>&1 || true)
|
|
1109
|
+
|
|
1110
|
+
# iter 4 C: NEW-M-1 — TUI 経路 grep を必須化 (legacy|tui|interactive menu)
|
|
1111
|
+
if printf '%s' "$output" | grep -qiE 'legacy|tui|interactive menu|numeric'; then
|
|
1112
|
+
return 0
|
|
1113
|
+
fi
|
|
1114
|
+
|
|
1115
|
+
# 少なくとも起動して hc-config の出力がある場合
|
|
1116
|
+
if printf '%s' "$output" | grep -qiE 'hc-config|menu|config|harness'; then
|
|
1117
|
+
return 0
|
|
1118
|
+
fi
|
|
1119
|
+
|
|
1120
|
+
printf 'S-17: HC_HC_CONFIG_TUI_LEGACY=true did not produce TUI/legacy output (got: %s)\n' "$output" >&2
|
|
1121
|
+
return 1
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
# ============================================================
|
|
1125
|
+
# Case S-18: node 不在 → WARN stderr + TUI fallback
|
|
1126
|
+
# ============================================================
|
|
1127
|
+
_case_s18() (
|
|
1128
|
+
set -uo pipefail
|
|
1129
|
+
|
|
1130
|
+
if [ ! -x "${HC_CONFIG_SCRIPT}" ]; then
|
|
1131
|
+
printf 'S-18: hc-config.sh not executable\n' >&2
|
|
1132
|
+
return 1
|
|
1133
|
+
fi
|
|
1134
|
+
|
|
1135
|
+
# PATH から node を除外して hc-config.sh を起動
|
|
1136
|
+
# _cmd_interactive_web 内の node 不在チェックが WARN + TUI fallback することを確認
|
|
1137
|
+
local output
|
|
1138
|
+
output=$(printf 'q\n' | PATH=/usr/bin:/bin timeout 5 bash "${HC_CONFIG_SCRIPT}" 2>&1 || true)
|
|
1139
|
+
|
|
1140
|
+
# node 不在 WARN の確認 (server.js 起動不可 → TUI 降格)
|
|
1141
|
+
if printf '%s' "$output" | grep -qiE 'node.*not found|node.*install|node.*required|node.*unavailable'; then
|
|
1142
|
+
return 0
|
|
1143
|
+
fi
|
|
1144
|
+
|
|
1145
|
+
# numeric menu に降格していれば (TUI fallback 経由) OK
|
|
1146
|
+
if printf '%s' "$output" | grep -q 'hc-config interactive menu'; then
|
|
1147
|
+
return 0
|
|
1148
|
+
fi
|
|
1149
|
+
|
|
1150
|
+
# node が PATH に本当になくても動作する場合 (node = /usr/local/bin etc.) は SKIP
|
|
1151
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
1152
|
+
printf 'S-18: node not in system PATH at all, skip\n' >&2
|
|
1153
|
+
return 2
|
|
1154
|
+
fi
|
|
1155
|
+
|
|
1156
|
+
# 何らかの出力があれば許容 (node 不在で起動 → TUI fallback の任意経路)
|
|
1157
|
+
if [ -n "$output" ]; then
|
|
1158
|
+
return 0
|
|
1159
|
+
fi
|
|
1160
|
+
|
|
1161
|
+
printf 'S-18: no output when node not in PATH\n' >&2
|
|
1162
|
+
return 1
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
# ============================================================
|
|
1166
|
+
# Case S-19: 1MB+1byte body POST /api/set → 400 body too large
|
|
1167
|
+
# ============================================================
|
|
1168
|
+
_case_s19() (
|
|
1169
|
+
set -uo pipefail
|
|
1170
|
+
local port="$1"
|
|
1171
|
+
|
|
1172
|
+
# 1MB+1byte の body を生成 (1048577 bytes)
|
|
1173
|
+
# curl の --data-binary で /dev/urandom から 1MB+1 を読む (環境依存を避け python3 で生成)
|
|
1174
|
+
local large_body_file="${TMP_DIR}/large-body.bin"
|
|
1175
|
+
|
|
1176
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
1177
|
+
python3 -c "import sys; sys.stdout.buffer.write(b'A' * 1048577)" > "$large_body_file" 2>/dev/null
|
|
1178
|
+
elif command -v dd >/dev/null 2>&1; then
|
|
1179
|
+
dd if=/dev/zero bs=1048577 count=1 2>/dev/null | tr '\0' 'A' > "$large_body_file" 2>/dev/null || true
|
|
1180
|
+
else
|
|
1181
|
+
printf 'S-19: no python3 or dd to generate large body, skip\n' >&2
|
|
1182
|
+
return 2
|
|
1183
|
+
fi
|
|
1184
|
+
|
|
1185
|
+
if [ ! -s "$large_body_file" ]; then
|
|
1186
|
+
printf 'S-19: failed to generate large body file, skip\n' >&2
|
|
1187
|
+
return 2
|
|
1188
|
+
fi
|
|
1189
|
+
|
|
1190
|
+
local http_code
|
|
1191
|
+
# -H 'Expect:' で Expect: 100-continue を無効化
|
|
1192
|
+
# req.destroy() 後は connection reset になるため HTTP 000 も許容
|
|
1193
|
+
http_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 10 \
|
|
1194
|
+
-X POST -H 'Content-Type: application/json' -H 'Expect:' \
|
|
1195
|
+
--data-binary "@${large_body_file}" \
|
|
1196
|
+
"http://127.0.0.1:${port}/api/set" 2>/dev/null || true)
|
|
1197
|
+
|
|
1198
|
+
case "$http_code" in
|
|
1199
|
+
400|413)
|
|
1200
|
+
return 0
|
|
1201
|
+
;;
|
|
1202
|
+
000)
|
|
1203
|
+
# server が req.destroy() で接続切断 → curl が connection reset で 000 を返す
|
|
1204
|
+
# これは "body too large" の reject が成功したことを示す (server 側の仕様)
|
|
1205
|
+
return 0
|
|
1206
|
+
;;
|
|
1207
|
+
*)
|
|
1208
|
+
printf 'S-19: large body POST returned HTTP %s (expected 400/413/000 connection-reset)\n' "$http_code" >&2
|
|
1209
|
+
return 1
|
|
1210
|
+
;;
|
|
1211
|
+
esac
|
|
1212
|
+
)
|
|
1213
|
+
|
|
1214
|
+
# ============================================================
|
|
1215
|
+
# Case S-20: abort rollback silent no-op verify
|
|
1216
|
+
# iter 4 C: NEW-C-3 — abort 時 applied:[] の rollback silent no-op
|
|
1217
|
+
# (通常 apply が成功するため、apply body の ok:false を確認できない場合は SKIP)
|
|
1218
|
+
# ============================================================
|
|
1219
|
+
_case_s20() (
|
|
1220
|
+
set -uo pipefail
|
|
1221
|
+
local port="$1"
|
|
1222
|
+
|
|
1223
|
+
# apply を実行して history entry を得る (abort を再現するのは難しいため、
|
|
1224
|
+
# 通常の history で rollback が applied:[] の場合を verify する)
|
|
1225
|
+
# まず通常 apply
|
|
1226
|
+
local apply_body
|
|
1227
|
+
apply_body=$(_curl_post_json "http://127.0.0.1:${port}/api/preset/poc-no-git/apply" '{}')
|
|
1228
|
+
|
|
1229
|
+
# history から最新エントリの timestamp を得る
|
|
1230
|
+
local hist_body
|
|
1231
|
+
hist_body=$(_curl_json "http://127.0.0.1:${port}/api/preset/history")
|
|
1232
|
+
local ts
|
|
1233
|
+
ts=$(printf '%s' "$hist_body" | grep -oE '"timestamp"[[:space:]]*:[[:space:]]*"[0-9T][^"]+"' | head -1 | grep -oE '"[0-9T][^"]+"' | head -1 | tr -d '"' || true)
|
|
1234
|
+
|
|
1235
|
+
if [ -z "$ts" ]; then
|
|
1236
|
+
printf 'S-20: no history entry found, skip\n' >&2
|
|
1237
|
+
return 2
|
|
1238
|
+
fi
|
|
1239
|
+
|
|
1240
|
+
# rollback 実行
|
|
1241
|
+
local rb_body
|
|
1242
|
+
rb_body=$(_curl_post_json "http://127.0.0.1:${port}/api/preset/rollback/${ts}" '{}')
|
|
1243
|
+
local rb_code
|
|
1244
|
+
rb_code=$(_curl_post_json_code "http://127.0.0.1:${port}/api/preset/rollback/${ts}" '{}')
|
|
1245
|
+
|
|
1246
|
+
# rollback は 200 で ok:true が期待値
|
|
1247
|
+
if [ "$rb_code" != "200" ]; then
|
|
1248
|
+
printf 'S-20: rollback returned HTTP %s (expected 200)\n' "$rb_code" >&2
|
|
1249
|
+
printf 'body: %s\n' "$rb_body" >&2
|
|
1250
|
+
return 1
|
|
1251
|
+
fi
|
|
1252
|
+
|
|
1253
|
+
# ok:true or ok:false (applied:0 の場合は ok:false, restored:0 も許容)
|
|
1254
|
+
if printf '%s' "$rb_body" | grep -q '"ok"'; then
|
|
1255
|
+
return 0
|
|
1256
|
+
fi
|
|
1257
|
+
|
|
1258
|
+
printf 'S-20: rollback response missing ok field\n' >&2
|
|
1259
|
+
return 1
|
|
1260
|
+
)
|
|
1261
|
+
|
|
1262
|
+
# ============================================================
|
|
1263
|
+
# Case S-21: unknown preset 404 path
|
|
1264
|
+
# iter 4 C: NEW-H-4 — GET /api/preset/nonexistent/diff → 404
|
|
1265
|
+
# POST /api/preset/nonexistent/apply → 404
|
|
1266
|
+
# ============================================================
|
|
1267
|
+
_case_s21() (
|
|
1268
|
+
set -uo pipefail
|
|
1269
|
+
local port="$1"
|
|
1270
|
+
|
|
1271
|
+
# GET /api/preset/nonexistent/diff → 404
|
|
1272
|
+
local http_code_diff
|
|
1273
|
+
http_code_diff=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 5 \
|
|
1274
|
+
"http://127.0.0.1:${port}/api/preset/nonexistent-preset-xyz/diff" 2>/dev/null || true)
|
|
1275
|
+
|
|
1276
|
+
if [ "$http_code_diff" != "404" ]; then
|
|
1277
|
+
printf 'S-21a: GET /api/preset/nonexistent/diff returned HTTP %s (expected 404)\n' "$http_code_diff" >&2
|
|
1278
|
+
return 1
|
|
1279
|
+
fi
|
|
1280
|
+
|
|
1281
|
+
# POST /api/preset/nonexistent/apply → 404
|
|
1282
|
+
local http_code_apply
|
|
1283
|
+
http_code_apply=$(_curl_post_json_code \
|
|
1284
|
+
"http://127.0.0.1:${port}/api/preset/nonexistent-preset-xyz/apply" '{}')
|
|
1285
|
+
|
|
1286
|
+
if [ "$http_code_apply" != "404" ]; then
|
|
1287
|
+
printf 'S-21b: POST /api/preset/nonexistent/apply returned HTTP %s (expected 404)\n' "$http_code_apply" >&2
|
|
1288
|
+
return 1
|
|
1289
|
+
fi
|
|
1290
|
+
|
|
1291
|
+
return 0
|
|
1292
|
+
)
|
|
1293
|
+
|
|
1294
|
+
# ============================================================
|
|
1295
|
+
# Case S-22: 撤去 (task-63 設計簡素化: /api/preset/save endpoint 撤去)
|
|
1296
|
+
# /api/preset/save は task-63 Step 4 A1 で 404 fallback に変更済 (savePreset/scanCustomPresets 撤去)
|
|
1297
|
+
# ============================================================
|
|
1298
|
+
# _case_s22 は削除 — /api/preset/save endpoint が 404 fallback になったため
|
|
1299
|
+
|
|
1300
|
+
# ============================================================
|
|
1301
|
+
# Case S-23: partial failure ok:false + rollback 件数 verify
|
|
1302
|
+
# iter 4 C: NEW-M-4 — partial failure 200 + rolled_back verify
|
|
1303
|
+
# (実際に abort させるのは困難なため、apply + inspect で verify)
|
|
1304
|
+
# ============================================================
|
|
1305
|
+
_case_s23() (
|
|
1306
|
+
set -uo pipefail
|
|
1307
|
+
local port="$1"
|
|
1308
|
+
|
|
1309
|
+
# apply を実行して ok:true か ok:false を確認
|
|
1310
|
+
local apply_body
|
|
1311
|
+
apply_body=$(_curl_post_json "http://127.0.0.1:${port}/api/preset/poc-no-git/apply" '{}')
|
|
1312
|
+
local apply_code
|
|
1313
|
+
apply_code=$(_curl_post_json_code "http://127.0.0.1:${port}/api/preset/poc-no-git/apply" '{}')
|
|
1314
|
+
|
|
1315
|
+
# apply は 200 (ok:true or partial ok:false) のいずれか
|
|
1316
|
+
if [ "$apply_code" != "200" ]; then
|
|
1317
|
+
printf 'S-23: apply returned HTTP %s (expected 200)\n' "$apply_code" >&2
|
|
1318
|
+
return 1
|
|
1319
|
+
fi
|
|
1320
|
+
|
|
1321
|
+
# ok field が存在する
|
|
1322
|
+
if ! printf '%s' "$apply_body" | grep -q '"ok"'; then
|
|
1323
|
+
printf 'S-23: apply response missing ok field\n' >&2
|
|
1324
|
+
return 1
|
|
1325
|
+
fi
|
|
1326
|
+
|
|
1327
|
+
# partial: true の場合は rolled_back field も検証
|
|
1328
|
+
if printf '%s' "$apply_body" | grep -q '"partial"[[:space:]]*:[[:space:]]*true'; then
|
|
1329
|
+
if ! printf '%s' "$apply_body" | grep -q '"rolled_back"'; then
|
|
1330
|
+
printf 'S-23: partial apply response missing rolled_back field\n' >&2
|
|
1331
|
+
return 1
|
|
1332
|
+
fi
|
|
1333
|
+
fi
|
|
1334
|
+
|
|
1335
|
+
# applied field が存在する
|
|
1336
|
+
if ! printf '%s' "$apply_body" | grep -q '"applied"'; then
|
|
1337
|
+
printf 'S-23: apply response missing applied field\n' >&2
|
|
1338
|
+
return 1
|
|
1339
|
+
fi
|
|
1340
|
+
|
|
1341
|
+
return 0
|
|
1342
|
+
)
|
|
1343
|
+
|
|
1344
|
+
# ============================================================
|
|
1345
|
+
# Case S-24: HISTORY_DIR 不在 → 自動作成 verify
|
|
1346
|
+
# iter 4 C: G3 — history dir 不在の applyPreset 試験
|
|
1347
|
+
# server.js: fs.mkdirSync(HISTORY_DIR, { recursive: true })
|
|
1348
|
+
# ============================================================
|
|
1349
|
+
_case_s24() (
|
|
1350
|
+
set -uo pipefail
|
|
1351
|
+
local port="$1"
|
|
1352
|
+
local hist_dir="$2"
|
|
1353
|
+
|
|
1354
|
+
# isolated dir を削除 (不在状態を作る)
|
|
1355
|
+
rm -rf "$hist_dir"
|
|
1356
|
+
|
|
1357
|
+
# apply を実行 → server が HISTORY_DIR を自動作成する
|
|
1358
|
+
local apply_code
|
|
1359
|
+
apply_code=$(_curl_post_json_code "http://127.0.0.1:${port}/api/preset/poc-no-git/apply" '{}')
|
|
1360
|
+
|
|
1361
|
+
if [ "$apply_code" != "200" ]; then
|
|
1362
|
+
printf 'S-24: apply with missing history dir returned HTTP %s (expected 200)\n' "$apply_code" >&2
|
|
1363
|
+
return 1
|
|
1364
|
+
fi
|
|
1365
|
+
|
|
1366
|
+
# HC_HISTORY_DIR_OVERRIDE が実装されている場合は isolated dir が作成される
|
|
1367
|
+
# 未実装の場合は元の HISTORY_DIR が作成される — どちらも ok
|
|
1368
|
+
if [ -d "$hist_dir" ]; then
|
|
1369
|
+
return 0
|
|
1370
|
+
fi
|
|
1371
|
+
if [ -d "${HISTORY_DIR}" ]; then
|
|
1372
|
+
return 0
|
|
1373
|
+
fi
|
|
1374
|
+
|
|
1375
|
+
printf 'S-24: neither isolated nor original HISTORY_DIR was created\n' >&2
|
|
1376
|
+
return 1
|
|
1377
|
+
)
|
|
1378
|
+
|
|
1379
|
+
# ============================================================
|
|
1380
|
+
# Case S-25: invalid JSON body → 400 (/api/set のみ)
|
|
1381
|
+
# iter 4 C: T-U1 — invalid JSON body 400
|
|
1382
|
+
# task-63: /api/preset/save sub-case b は撤去 (/api/preset/save endpoint 撤去済のため)
|
|
1383
|
+
# ============================================================
|
|
1384
|
+
_case_s25() (
|
|
1385
|
+
set -uo pipefail
|
|
1386
|
+
local port="$1"
|
|
1387
|
+
|
|
1388
|
+
# 不正 JSON
|
|
1389
|
+
local http_code
|
|
1390
|
+
http_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 5 \
|
|
1391
|
+
-X POST -H 'Content-Type: application/json' \
|
|
1392
|
+
-d '{invalid_json_here' \
|
|
1393
|
+
"http://127.0.0.1:${port}/api/set" 2>/dev/null || true)
|
|
1394
|
+
|
|
1395
|
+
if [ "$http_code" != "400" ]; then
|
|
1396
|
+
printf 'S-25: invalid JSON body returned HTTP %s (expected 400)\n' "$http_code" >&2
|
|
1397
|
+
return 1
|
|
1398
|
+
fi
|
|
1399
|
+
|
|
1400
|
+
return 0
|
|
1401
|
+
)
|
|
1402
|
+
|
|
1403
|
+
# ============================================================
|
|
1404
|
+
# Case S-26: 撤去 (task-63 設計簡素化: /api/preset/save endpoint 撤去)
|
|
1405
|
+
# /api/preset/save は task-63 Step 4 A1 で 404 fallback に変更済
|
|
1406
|
+
# ============================================================
|
|
1407
|
+
# _case_s26 は削除 — /api/preset/save endpoint が 404 fallback になったため
|
|
1408
|
+
|
|
1409
|
+
# ============================================================
|
|
1410
|
+
# Case S-27: /api/set 空文字列 value → 仕様確認
|
|
1411
|
+
# iter 4 C: NEW-M-2 — empty string value の挙動
|
|
1412
|
+
# (hc-config.sh 側で空値 reject / accept が決まる → smoke でその挙動を verify)
|
|
1413
|
+
# ============================================================
|
|
1414
|
+
_case_s27() (
|
|
1415
|
+
set -uo pipefail
|
|
1416
|
+
local port="$1"
|
|
1417
|
+
|
|
1418
|
+
# /api/set で既知キー + 空文字列 value
|
|
1419
|
+
# hc-config.sh の --set key= が許可される場合は 200、拒否の場合は 400
|
|
1420
|
+
# smoke では 200 または 400 のどちらかを期待値とする
|
|
1421
|
+
local http_code
|
|
1422
|
+
http_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 5 \
|
|
1423
|
+
-X POST -H 'Content-Type: application/json' \
|
|
1424
|
+
-d '{"key":"confidence_threshold","value":""}' \
|
|
1425
|
+
"http://127.0.0.1:${port}/api/set" 2>/dev/null || true)
|
|
1426
|
+
|
|
1427
|
+
case "$http_code" in
|
|
1428
|
+
200|400)
|
|
1429
|
+
# 200: hc-config.sh が空値を許容 / 400: hc-config.sh が reject → 両方 OK
|
|
1430
|
+
return 0
|
|
1431
|
+
;;
|
|
1432
|
+
*)
|
|
1433
|
+
printf 'S-27: empty string value POST /api/set returned HTTP %s (expected 200 or 400)\n' "$http_code" >&2
|
|
1434
|
+
return 1
|
|
1435
|
+
;;
|
|
1436
|
+
esac
|
|
1437
|
+
)
|
|
1438
|
+
|
|
1439
|
+
# ============================================================
|
|
1440
|
+
# Case S-28: URL encoded traversal /api/preset/rollback/..%2F..%2F → 400
|
|
1441
|
+
# iter 4 C: NEW-H-3 と対になる rollback 経路単独 case
|
|
1442
|
+
# ============================================================
|
|
1443
|
+
_case_s28() (
|
|
1444
|
+
set -uo pipefail
|
|
1445
|
+
local port="$1"
|
|
1446
|
+
|
|
1447
|
+
# %2F encoded path traversal — curl が -g で globbing OFF
|
|
1448
|
+
local http_code
|
|
1449
|
+
http_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 5 \
|
|
1450
|
+
-X POST -H 'Content-Type: application/json' \
|
|
1451
|
+
-d '{}' \
|
|
1452
|
+
-g "http://127.0.0.1:${port}/api/preset/rollback/..%2F..%2Fetc%2Fpasswd" 2>/dev/null || true)
|
|
1453
|
+
|
|
1454
|
+
case "$http_code" in
|
|
1455
|
+
400|404|500) return 0 ;;
|
|
1456
|
+
200)
|
|
1457
|
+
printf 'S-28: URL encoded rollback traversal returned 200 (security violation!)\n' >&2
|
|
1458
|
+
return 1
|
|
1459
|
+
;;
|
|
1460
|
+
*)
|
|
1461
|
+
# 他の HTTP code は security 違反ではないため PASS
|
|
1462
|
+
return 0
|
|
1463
|
+
;;
|
|
1464
|
+
esac
|
|
1465
|
+
)
|
|
1466
|
+
|
|
1467
|
+
# ============================================================
|
|
1468
|
+
# Case S-29: GET /api/preset/history → .history array
|
|
1469
|
+
# iter 4 C: T-U5 — history endpoint existence
|
|
1470
|
+
# ============================================================
|
|
1471
|
+
_case_s29() (
|
|
1472
|
+
set -uo pipefail
|
|
1473
|
+
local port="$1"
|
|
1474
|
+
|
|
1475
|
+
local body
|
|
1476
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/preset/history")
|
|
1477
|
+
|
|
1478
|
+
if ! printf '%s' "$body" | grep -q '"history"'; then
|
|
1479
|
+
printf 'S-29: /api/preset/history missing .history field\n' >&2
|
|
1480
|
+
printf '%s\n' "$body" >&2
|
|
1481
|
+
return 1
|
|
1482
|
+
fi
|
|
1483
|
+
|
|
1484
|
+
return 0
|
|
1485
|
+
)
|
|
1486
|
+
|
|
1487
|
+
# ============================================================
|
|
1488
|
+
# Case S-30: 0 件 rollback (apply が no-op) → ok:true + restored:0
|
|
1489
|
+
# iter 4 C: T-U6 — 0 件 rollback
|
|
1490
|
+
# (apply で変更なしの場合 applied:0 → rollback で restored:0 + ok:true)
|
|
1491
|
+
# ============================================================
|
|
1492
|
+
_case_s30() (
|
|
1493
|
+
set -uo pipefail
|
|
1494
|
+
local port="$1"
|
|
1495
|
+
|
|
1496
|
+
# まず apply (変更があれば applied > 0, なければ 0)
|
|
1497
|
+
local apply_body
|
|
1498
|
+
apply_body=$(_curl_post_json "http://127.0.0.1:${port}/api/preset/poc-no-git/apply" '{}')
|
|
1499
|
+
|
|
1500
|
+
# history の timestamp を取得
|
|
1501
|
+
local hist_body
|
|
1502
|
+
hist_body=$(_curl_json "http://127.0.0.1:${port}/api/preset/history")
|
|
1503
|
+
local ts
|
|
1504
|
+
ts=$(printf '%s' "$hist_body" | grep -oE '"timestamp"[[:space:]]*:[[:space:]]*"[0-9T][^"]+"' | head -1 | grep -oE '"[0-9T][^"]+"' | head -1 | tr -d '"' || true)
|
|
1505
|
+
|
|
1506
|
+
if [ -z "$ts" ]; then
|
|
1507
|
+
printf 'S-30: no history entry, skip\n' >&2
|
|
1508
|
+
return 2
|
|
1509
|
+
fi
|
|
1510
|
+
|
|
1511
|
+
# rollback 実行
|
|
1512
|
+
local rb_body
|
|
1513
|
+
rb_body=$(_curl_post_json "http://127.0.0.1:${port}/api/preset/rollback/${ts}" '{}')
|
|
1514
|
+
local rb_code
|
|
1515
|
+
rb_code=$(_curl_post_json_code "http://127.0.0.1:${port}/api/preset/rollback/${ts}" '{}')
|
|
1516
|
+
|
|
1517
|
+
if [ "$rb_code" != "200" ]; then
|
|
1518
|
+
printf 'S-30: rollback returned HTTP %s (expected 200)\n' "$rb_code" >&2
|
|
1519
|
+
return 1
|
|
1520
|
+
fi
|
|
1521
|
+
|
|
1522
|
+
# ok field が存在 (true or false いずれでも、restored field が存在することを確認)
|
|
1523
|
+
if ! printf '%s' "$rb_body" | grep -q '"ok"'; then
|
|
1524
|
+
printf 'S-30: rollback response missing ok field\n' >&2
|
|
1525
|
+
return 1
|
|
1526
|
+
fi
|
|
1527
|
+
if ! printf '%s' "$rb_body" | grep -q '"restored"'; then
|
|
1528
|
+
printf 'S-30: rollback response missing restored field\n' >&2
|
|
1529
|
+
return 1
|
|
1530
|
+
fi
|
|
1531
|
+
|
|
1532
|
+
return 0
|
|
1533
|
+
)
|
|
1534
|
+
|
|
1535
|
+
# ============================================================
|
|
1536
|
+
# Case S-31: 撤去 (task-63 設計簡素化: /api/preset/save endpoint 撤去)
|
|
1537
|
+
# /api/preset/save は task-63 Step 4 A1 で 404 fallback に変更済
|
|
1538
|
+
# ============================================================
|
|
1539
|
+
# _case_s31 は削除 — /api/preset/save endpoint が 404 fallback になったため
|
|
1540
|
+
|
|
1541
|
+
# ============================================================
|
|
1542
|
+
# Case S-32: category filter GET /api/keys?category=<name> → filtered list
|
|
1543
|
+
# iter 4 C: T-U8 + category filter
|
|
1544
|
+
# ============================================================
|
|
1545
|
+
_case_s32() (
|
|
1546
|
+
set -uo pipefail
|
|
1547
|
+
local port="$1"
|
|
1548
|
+
|
|
1549
|
+
# category filter は ASCII category name で確認 (日本語 category は URL エンコード問題のため除外)
|
|
1550
|
+
# /api/categories のレスポンスから ASCII のみの category name を抽出
|
|
1551
|
+
local cat_body
|
|
1552
|
+
cat_body=$(_curl_json "http://127.0.0.1:${port}/api/categories")
|
|
1553
|
+
|
|
1554
|
+
# ASCII のみの category name を抽出 (日本語 category は URL に含めると問題が生じるため)
|
|
1555
|
+
# feature_toggle / reviewer_control / Gate_Confidence / state_dir など ASCII のものを優先
|
|
1556
|
+
local cat_name
|
|
1557
|
+
cat_name=$(printf '%s' "$cat_body" | grep -oE '"name"[[:space:]]*:[[:space:]]*"[a-zA-Z_/][a-zA-Z0-9_/]*"' | head -1 | grep -oE '"[a-zA-Z_/][a-zA-Z0-9_/]*"$' | tr -d '"' || true)
|
|
1558
|
+
|
|
1559
|
+
if [ -z "$cat_name" ]; then
|
|
1560
|
+
# ASCII category が見つからない場合は feature_toggle を fallback として使用
|
|
1561
|
+
# (metadata に feature_toggle category が存在することは S-10/S-09 で確認済み)
|
|
1562
|
+
cat_name="feature_toggle"
|
|
1563
|
+
fi
|
|
1564
|
+
|
|
1565
|
+
# /api/keys?category=<cat_name> (ASCII のみなので URL エンコード不要)
|
|
1566
|
+
local keys_body
|
|
1567
|
+
keys_body=$(_curl_json "http://127.0.0.1:${port}/api/keys?category=${cat_name}")
|
|
1568
|
+
|
|
1569
|
+
if ! printf '%s' "$keys_body" | grep -q '"keys"'; then
|
|
1570
|
+
printf 'S-32: /api/keys?category=%s missing .keys field\n' "$cat_name" >&2
|
|
1571
|
+
printf '%s\n' "$keys_body" >&2
|
|
1572
|
+
return 1
|
|
1573
|
+
fi
|
|
1574
|
+
|
|
1575
|
+
# category filter が効いて category 値が含まれること
|
|
1576
|
+
if ! printf '%s' "$keys_body" | grep -q '"category"'; then
|
|
1577
|
+
printf 'S-32: filtered keys missing category field\n' >&2
|
|
1578
|
+
return 1
|
|
1579
|
+
fi
|
|
1580
|
+
|
|
1581
|
+
# M-2 (review fix): category filter 経路でも enriched entry に label_ja が返ること
|
|
1582
|
+
if ! printf '%s' "$keys_body" | grep -q '"label_ja"'; then
|
|
1583
|
+
printf 'S-32: category filter result に label_ja field が無い\n' >&2
|
|
1584
|
+
return 1
|
|
1585
|
+
fi
|
|
1586
|
+
|
|
1587
|
+
return 0
|
|
1588
|
+
)
|
|
1589
|
+
|
|
1590
|
+
# ============================================================
|
|
1591
|
+
# Case S-33: 撤去 (task-63 設計簡素化: /api/preset/save endpoint 撤去)
|
|
1592
|
+
# /api/preset/save は task-63 Step 4 A1 で 404 fallback に変更済
|
|
1593
|
+
# ============================================================
|
|
1594
|
+
# _case_s33 は削除 — /api/preset/save endpoint が 404 fallback になったため
|
|
1595
|
+
|
|
1596
|
+
# ============================================================
|
|
1597
|
+
# task-63 Step 5 新規 case (S-35〜S-39)
|
|
1598
|
+
# /api/current-preset + top view banner + edit 遷移 + unsaved banner 検証
|
|
1599
|
+
# ============================================================
|
|
1600
|
+
|
|
1601
|
+
# ============================================================
|
|
1602
|
+
# Case S-35: GET /api/current-preset → 200 + match_type field + display_name_ja field 含む
|
|
1603
|
+
# draft §3.4 / §3.6: /api/current-preset は { match_type, name, display_name_ja } を返す (案 C values 一致判定)
|
|
1604
|
+
# ============================================================
|
|
1605
|
+
_case_s35() (
|
|
1606
|
+
set -uo pipefail
|
|
1607
|
+
local port="$1"
|
|
1608
|
+
|
|
1609
|
+
local body
|
|
1610
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/current-preset")
|
|
1611
|
+
|
|
1612
|
+
if ! printf '%s' "$body" | grep -q '"match_type"'; then
|
|
1613
|
+
printf 'S-35: /api/current-preset missing .match_type field (body: %s)\n' "$body" >&2
|
|
1614
|
+
return 1
|
|
1615
|
+
fi
|
|
1616
|
+
|
|
1617
|
+
if ! printf '%s' "$body" | grep -q '"display_name_ja"'; then
|
|
1618
|
+
printf 'S-35: /api/current-preset missing .display_name_ja field (body: %s)\n' "$body" >&2
|
|
1619
|
+
return 1
|
|
1620
|
+
fi
|
|
1621
|
+
|
|
1622
|
+
# match_type は "preset" または "custom" のどちらかであること (task-76 Step 2: 'unsaved' → 'custom')
|
|
1623
|
+
if ! printf '%s' "$body" | grep -qE '"match_type"[[:space:]]*:[[:space:]]*"(preset|custom)"'; then
|
|
1624
|
+
printf 'S-35: /api/current-preset .match_type is not "preset" or "custom" (body: %s)\n' "$body" >&2
|
|
1625
|
+
return 1
|
|
1626
|
+
fi
|
|
1627
|
+
|
|
1628
|
+
# F3 (iter-2 fix): display_name_ja の値も検証 (存在のみでなく、match_type 別に内容を確認)
|
|
1629
|
+
if printf '%s' "$body" | grep -q '"match_type"[[:space:]]*:[[:space:]]*"preset"'; then
|
|
1630
|
+
# preset 一致時: name field 存在 + display_name_ja が非空
|
|
1631
|
+
if ! printf '%s' "$body" | grep -q '"name"[[:space:]]*:[[:space:]]*"[^"]'; then
|
|
1632
|
+
printf 'S-35: match_type=preset だが name field が非空でない (body: %s)\n' "$body" >&2
|
|
1633
|
+
return 1
|
|
1634
|
+
fi
|
|
1635
|
+
if ! printf '%s' "$body" | grep -qE '"display_name_ja"[[:space:]]*:[[:space:]]*"[^"]+"'; then
|
|
1636
|
+
printf 'S-35: match_type=preset だが display_name_ja が空 (body: %s)\n' "$body" >&2
|
|
1637
|
+
return 1
|
|
1638
|
+
fi
|
|
1639
|
+
else
|
|
1640
|
+
# custom 時: display_name_ja が "未保存変更あり" を含む (server.js 実装値)
|
|
1641
|
+
if ! printf '%s' "$body" | grep -q '未保存変更あり'; then
|
|
1642
|
+
printf 'S-35: match_type=custom だが display_name_ja に "未保存変更あり" が含まれない (body: %s)\n' "$body" >&2
|
|
1643
|
+
return 1
|
|
1644
|
+
fi
|
|
1645
|
+
fi
|
|
1646
|
+
|
|
1647
|
+
return 0
|
|
1648
|
+
)
|
|
1649
|
+
|
|
1650
|
+
# ============================================================
|
|
1651
|
+
# Case S-36: preset apply 後 GET /api/current-preset → match_type=preset + 正しい name/display_name_ja
|
|
1652
|
+
# draft §3.4: preset 完全一致時は { match_type: "preset", name: "<key>", display_name_ja: "<日本語名>" }
|
|
1653
|
+
# ============================================================
|
|
1654
|
+
_case_s36() (
|
|
1655
|
+
set -uo pipefail
|
|
1656
|
+
local port="$1"
|
|
1657
|
+
|
|
1658
|
+
# poc-no-git を apply して既知の preset 状態にする
|
|
1659
|
+
# F9 (iter-2 fix): 400/500 系は真の FAIL (権限エラー/yml 破損) なので隠蔽せず FAIL にする。
|
|
1660
|
+
# 200/207 のみ正常。それ以外 (000 connection 不可等は環境問題だが、apply 自体の HTTP error は FAIL)。
|
|
1661
|
+
local apply_code
|
|
1662
|
+
apply_code=$(_curl_post_json_code "http://127.0.0.1:${port}/api/preset/poc-no-git/apply" '{}')
|
|
1663
|
+
case "$apply_code" in
|
|
1664
|
+
200|207) : ;;
|
|
1665
|
+
4??|5??)
|
|
1666
|
+
printf 'S-36: preset apply returned HTTP %s (権限エラー/yml 破損の疑い、真の FAIL)\n' "$apply_code" >&2
|
|
1667
|
+
return 1
|
|
1668
|
+
;;
|
|
1669
|
+
*)
|
|
1670
|
+
printf 'S-36: preset apply returned HTTP %s (server 接続不可等の環境 skip)\n' "$apply_code" >&2
|
|
1671
|
+
return 2
|
|
1672
|
+
;;
|
|
1673
|
+
esac
|
|
1674
|
+
|
|
1675
|
+
# /api/current-preset を取得
|
|
1676
|
+
local body
|
|
1677
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/current-preset")
|
|
1678
|
+
|
|
1679
|
+
# match_type が "preset" であること
|
|
1680
|
+
if ! printf '%s' "$body" | grep -q '"match_type"[[:space:]]*:[[:space:]]*"preset"'; then
|
|
1681
|
+
printf 'S-36: after poc-no-git apply, match_type is not "preset" (body: %s)\n' "$body" >&2
|
|
1682
|
+
return 1
|
|
1683
|
+
fi
|
|
1684
|
+
|
|
1685
|
+
# name が "poc-no-git" であること
|
|
1686
|
+
if ! printf '%s' "$body" | grep -q '"name"[[:space:]]*:[[:space:]]*"poc-no-git"'; then
|
|
1687
|
+
printf 'S-36: after poc-no-git apply, name is not "poc-no-git" (body: %s)\n' "$body" >&2
|
|
1688
|
+
return 1
|
|
1689
|
+
fi
|
|
1690
|
+
|
|
1691
|
+
# display_name_ja が存在すること (日本語名: "POC・お試し (Git なし)")
|
|
1692
|
+
if ! printf '%s' "$body" | grep -q '"display_name_ja"'; then
|
|
1693
|
+
printf 'S-36: /api/current-preset missing display_name_ja after preset apply (body: %s)\n' "$body" >&2
|
|
1694
|
+
return 1
|
|
1695
|
+
fi
|
|
1696
|
+
|
|
1697
|
+
# F3 (iter-2 fix): poc-no-git の display_name_ja は "POC" で始まる ("POC・お試し (Git なし)")
|
|
1698
|
+
if ! printf '%s' "$body" | grep -qE '"display_name_ja"[[:space:]]*:[[:space:]]*"POC'; then
|
|
1699
|
+
printf 'S-36: poc-no-git の display_name_ja が "POC" で始まらない (body: %s)\n' "$body" >&2
|
|
1700
|
+
return 1
|
|
1701
|
+
fi
|
|
1702
|
+
|
|
1703
|
+
return 0
|
|
1704
|
+
)
|
|
1705
|
+
|
|
1706
|
+
# ============================================================
|
|
1707
|
+
# Case S-37: RETIRED (task-76 Step 6-fix)
|
|
1708
|
+
# 旧 assertion: app.js に renderTop / bannerLabel / bannerValue / 「設定を変更」CTA が存在。
|
|
1709
|
+
# retire 理由: task-76 で top/edit 2-view を廃止し 2 分割 1 画面 (左 preset / 右 category accordion)
|
|
1710
|
+
# へ全面再設計。renderTop / top view banner / 「設定を変更」CTA は新設計に存在しない (旧 UI 前提)。
|
|
1711
|
+
# 新構造の描画ターゲット存在確認は S-42 (id 契約 cross-check) が、状態表示は header-state-badge を
|
|
1712
|
+
# visual 検証 (Step 7) が担保する。本 case は常時 SKIP (return 2)。
|
|
1713
|
+
# ============================================================
|
|
1714
|
+
_case_s37() (
|
|
1715
|
+
set -uo pipefail
|
|
1716
|
+
printf 'S-37: RETIRED — top view (renderTop/banner) removed in task-76 2-pane redesign\n' >&2
|
|
1717
|
+
return 2
|
|
1718
|
+
)
|
|
1719
|
+
|
|
1720
|
+
# ============================================================
|
|
1721
|
+
# Case S-38: RETIRED (task-76 Step 6-fix)
|
|
1722
|
+
# 旧 assertion: app.js に renderEdit / view:'edit' 遷移 / state.view==='edit' 分岐 /
|
|
1723
|
+
# edit:enter / edit:select_preset reducer action が存在。
|
|
1724
|
+
# retire 理由: task-76 で top/edit 2-view state machine を廃止。新設計は tab ('config'|'history')
|
|
1725
|
+
# + 設定タブ内 2 分割 (左 preset / 右 accordion) で、view:'edit' 遷移 / edit:* reducer action は存在しない。
|
|
1726
|
+
# タブ切替の動作は S-42 (#tab-config/#tab-history id) + visual 検証 (Step 7) が担保。常時 SKIP (return 2)。
|
|
1727
|
+
# ============================================================
|
|
1728
|
+
_case_s38() (
|
|
1729
|
+
set -uo pipefail
|
|
1730
|
+
printf 'S-38: RETIRED — edit-view state machine removed in task-76 2-pane redesign\n' >&2
|
|
1731
|
+
return 2
|
|
1732
|
+
)
|
|
1733
|
+
|
|
1734
|
+
# ============================================================
|
|
1735
|
+
# Case S-39: /api/set で 1 key 変更 → GET /api/current-preset → match_type=unsaved
|
|
1736
|
+
# draft §3.4 / §3.7: 個別 key 変更後、どの preset にも完全一致しない場合は match_type=unsaved
|
|
1737
|
+
# 検証方式: server endpoint レベル (DOM は不要)
|
|
1738
|
+
# F9 (iter-2 fix): apply の 400/500 系は真の FAIL (権限/yml 破損) として隠蔽しない。
|
|
1739
|
+
# F10 (iter-2 fix): poc-no-git の values に確実に含まれる key (confidence_threshold='0.5'、
|
|
1740
|
+
# server.js PRESETS 定義で確認済) を、現在値 0.5 と必ず異なる値 (0.99) に set して
|
|
1741
|
+
# match_type=unsaved を確実に発火させる 2 段方式。apply 後に /api/current-preset で
|
|
1742
|
+
# match_type=preset 前提を確認 → set → unsaved を検証。F2 の snapshot/restore で yml 変更は許容。
|
|
1743
|
+
# ============================================================
|
|
1744
|
+
_case_s39() (
|
|
1745
|
+
set -uo pipefail
|
|
1746
|
+
local port="$1"
|
|
1747
|
+
|
|
1748
|
+
# まず poc-no-git を apply して既知 preset 状態にする (確実に preset 状態を作る)
|
|
1749
|
+
local apply_code
|
|
1750
|
+
apply_code=$(_curl_post_json_code "http://127.0.0.1:${port}/api/preset/poc-no-git/apply" '{}')
|
|
1751
|
+
case "$apply_code" in
|
|
1752
|
+
200|207) : ;;
|
|
1753
|
+
4??|5??)
|
|
1754
|
+
printf 'S-39: preset apply returned HTTP %s (権限エラー/yml 破損の疑い、真の FAIL)\n' "$apply_code" >&2
|
|
1755
|
+
return 1
|
|
1756
|
+
;;
|
|
1757
|
+
*)
|
|
1758
|
+
printf 'S-39: preset apply returned HTTP %s (server 接続不可等の環境 skip)\n' "$apply_code" >&2
|
|
1759
|
+
return 2
|
|
1760
|
+
;;
|
|
1761
|
+
esac
|
|
1762
|
+
|
|
1763
|
+
# apply 後の /api/current-preset が match_type=preset であることを確認 (前提)
|
|
1764
|
+
local before_body
|
|
1765
|
+
before_body=$(_curl_json "http://127.0.0.1:${port}/api/current-preset")
|
|
1766
|
+
if ! printf '%s' "$before_body" | grep -q '"match_type"[[:space:]]*:[[:space:]]*"preset"'; then
|
|
1767
|
+
# poc-no-git apply 直後に preset 一致しないのは server バグの疑い → FAIL
|
|
1768
|
+
printf 'S-39: poc-no-git apply 直後に match_type=preset でない (server バグの疑い、body: %s)\n' "$before_body" >&2
|
|
1769
|
+
return 1
|
|
1770
|
+
fi
|
|
1771
|
+
|
|
1772
|
+
# F10: poc-no-git の values に確実に含まれる confidence_threshold (='0.5') を
|
|
1773
|
+
# 現在値と必ず異なる 0.99 に set して unsaved を確実に発火させる。
|
|
1774
|
+
# confidence_threshold が万一存在しない場合のみ feature_confidence_gate_enabled に fallback。
|
|
1775
|
+
local keys_body
|
|
1776
|
+
keys_body=$(_curl_json "http://127.0.0.1:${port}/api/keys")
|
|
1777
|
+
|
|
1778
|
+
local test_key test_value
|
|
1779
|
+
if printf '%s' "$keys_body" | grep -q '"confidence_threshold"'; then
|
|
1780
|
+
test_key="confidence_threshold"
|
|
1781
|
+
# poc-no-git の confidence_threshold は '0.5' (server.js PRESETS 定義)。
|
|
1782
|
+
# 0.99 は確実に異なるので match_type=unsaved になる。
|
|
1783
|
+
test_value="0.99"
|
|
1784
|
+
else
|
|
1785
|
+
# fallback: feature_confidence_gate_enabled の値を反転
|
|
1786
|
+
local cur_val
|
|
1787
|
+
cur_val=$(printf '%s' "$keys_body" | grep -A5 '"feature_confidence_gate_enabled"' | grep '"current_value"' | grep -oE '"(true|false)"' | head -1 | tr -d '"' || true)
|
|
1788
|
+
test_key="feature_confidence_gate_enabled"
|
|
1789
|
+
if [ "$cur_val" = "true" ]; then
|
|
1790
|
+
test_value="false"
|
|
1791
|
+
else
|
|
1792
|
+
test_value="true"
|
|
1793
|
+
fi
|
|
1794
|
+
fi
|
|
1795
|
+
|
|
1796
|
+
# /api/set で key を変更する
|
|
1797
|
+
local set_code
|
|
1798
|
+
set_code=$(_curl_post_json_code "http://127.0.0.1:${port}/api/set" \
|
|
1799
|
+
"{\"key\":\"${test_key}\",\"value\":\"${test_value}\"}")
|
|
1800
|
+
|
|
1801
|
+
# F10: known key への set が失敗するのは server バグの疑い → FAIL (skip しない)
|
|
1802
|
+
if [ "$set_code" != "200" ]; then
|
|
1803
|
+
printf 'S-39: /api/set returned HTTP %s (key=%s, value=%s — known key の set 失敗、FAIL)\n' "$set_code" "$test_key" "$test_value" >&2
|
|
1804
|
+
return 1
|
|
1805
|
+
fi
|
|
1806
|
+
|
|
1807
|
+
# /api/current-preset を取得して match_type=custom であることを確認 (task-76 Step 2: 'unsaved' → 'custom')
|
|
1808
|
+
local after_body
|
|
1809
|
+
after_body=$(_curl_json "http://127.0.0.1:${port}/api/current-preset")
|
|
1810
|
+
|
|
1811
|
+
if ! printf '%s' "$after_body" | grep -q '"match_type"[[:space:]]*:[[:space:]]*"custom"'; then
|
|
1812
|
+
printf 'S-39: after /api/set change, match_type is not "custom" (body: %s)\n' "$after_body" >&2
|
|
1813
|
+
return 1
|
|
1814
|
+
fi
|
|
1815
|
+
|
|
1816
|
+
return 0
|
|
1817
|
+
)
|
|
1818
|
+
|
|
1819
|
+
# ============================================================
|
|
1820
|
+
# Case S-40: POST /api/preset/save → 404 (custom 保存撤去 regression guard)
|
|
1821
|
+
# task-63 Step 4 A1: /api/preset/save endpoint 撤去 (savePreset/scanCustomPresets 全削除)。
|
|
1822
|
+
# draft §8 アンチパターン「custom 保存復活禁止」の regression guard。
|
|
1823
|
+
# F4 (iter-2 fix): 撤去された endpoint が再導入されていないこと (404 fallback) を負テストで保証。
|
|
1824
|
+
# ============================================================
|
|
1825
|
+
_case_s40() (
|
|
1826
|
+
set -uo pipefail
|
|
1827
|
+
local port="$1"
|
|
1828
|
+
|
|
1829
|
+
local http_code
|
|
1830
|
+
http_code=$(_curl_post_json_code "http://127.0.0.1:${port}/api/preset/save" \
|
|
1831
|
+
'{"name":"my-preset","values":{}}')
|
|
1832
|
+
|
|
1833
|
+
if [ "$http_code" != "404" ]; then
|
|
1834
|
+
printf 'S-40: POST /api/preset/save returned HTTP %s (expected 404 — endpoint 撤去済のはず)\n' "$http_code" >&2
|
|
1835
|
+
return 1
|
|
1836
|
+
fi
|
|
1837
|
+
|
|
1838
|
+
return 0
|
|
1839
|
+
)
|
|
1840
|
+
|
|
1841
|
+
# ============================================================
|
|
1842
|
+
# Case S-41: UI 3 file に絵文字 (Unicode emoji) が 0 件 (絵文字不要 regression guard)
|
|
1843
|
+
# F5 (iter-2 fix): user 明示要求「絵文字不要」+ draft §8 アンチパターンの regression guard。
|
|
1844
|
+
# index.html / app.js / style.css に emoji codepoint (U+1F300〜U+1FAFF 等) が混入したら FAIL。
|
|
1845
|
+
# 検証方式: perl で emoji 範囲を grep (hit したら FAIL)。perl 不在時は python3 fallback。
|
|
1846
|
+
#
|
|
1847
|
+
# task-76 Step 6-fix: ★ (U+2605 BLACK STAR) は S-41 の検出範囲から除外する。
|
|
1848
|
+
# 設計 SSoT (hc-config-web-2pane-redesign.md §3) が左ペイン「★ カスタム」を明示採用しており、
|
|
1849
|
+
# BLACK STAR は emoji ではなく typographic dingbat (色つき絵文字レンダリングではなくテキスト記号)。
|
|
1850
|
+
# 範囲 U+2600-U+27BF から U+2605 のみ穴あけ (U+2600-U+2604 と U+2606-U+27BF に分割) する。
|
|
1851
|
+
# ============================================================
|
|
1852
|
+
_case_s41() (
|
|
1853
|
+
set -uo pipefail
|
|
1854
|
+
local ui_dir="${REPO_ROOT}/.claude/scripts/lib/hc-config-web-ui"
|
|
1855
|
+
local files="index.html app.js style.css"
|
|
1856
|
+
|
|
1857
|
+
for f in $files; do
|
|
1858
|
+
if [ ! -f "${ui_dir}/${f}" ]; then
|
|
1859
|
+
printf 'S-41: %s not found at %s\n' "$f" "$ui_dir" >&2
|
|
1860
|
+
return 1
|
|
1861
|
+
fi
|
|
1862
|
+
done
|
|
1863
|
+
|
|
1864
|
+
# emoji 検出: 主要 emoji ブロックを範囲指定 (記号 / 絵文字 / 補助記号 / 拡張A)
|
|
1865
|
+
# U+1F300-U+1FAFF (Misc Symbols and Pictographs 〜 Symbols and Pictographs Extended-A)
|
|
1866
|
+
# U+2600-U+27BF (Misc Symbols + Dingbats)
|
|
1867
|
+
# U+1F000-U+1F0FF / U+1F1E6-U+1F1FF (Mahjong/Domino/Regional indicators) も含める
|
|
1868
|
+
local hit=""
|
|
1869
|
+
if command -v perl >/dev/null 2>&1; then
|
|
1870
|
+
for f in $files; do
|
|
1871
|
+
local out
|
|
1872
|
+
# U+2605 (★ BLACK STAR) を除外: U+2600-U+2604 と U+2606-U+27BF に分割
|
|
1873
|
+
out=$(perl -CSD -ne 'print "$ARGV:$.: $_" if /[\x{1F000}-\x{1FAFF}\x{2600}-\x{2604}\x{2606}-\x{27BF}\x{2B00}-\x{2BFF}\x{1F1E6}-\x{1F1FF}]/' "${ui_dir}/${f}" 2>/dev/null || true)
|
|
1874
|
+
if [ -n "$out" ]; then
|
|
1875
|
+
hit="${hit}${out}"
|
|
1876
|
+
fi
|
|
1877
|
+
done
|
|
1878
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
1879
|
+
for f in $files; do
|
|
1880
|
+
local out
|
|
1881
|
+
out=$(python3 -c "
|
|
1882
|
+
import sys, re
|
|
1883
|
+
pat = re.compile('[\U0001F000-\U0001FAFF☀-☄☆-➿⬀-⯿]')
|
|
1884
|
+
with open(sys.argv[1], encoding='utf-8') as fh:
|
|
1885
|
+
for i, line in enumerate(fh, 1):
|
|
1886
|
+
if pat.search(line):
|
|
1887
|
+
print('%s:%d: %s' % (sys.argv[1], i, line), end='')
|
|
1888
|
+
" "${ui_dir}/${f}" 2>/dev/null || true)
|
|
1889
|
+
if [ -n "$out" ]; then
|
|
1890
|
+
hit="${hit}${out}"
|
|
1891
|
+
fi
|
|
1892
|
+
done
|
|
1893
|
+
else
|
|
1894
|
+
printf 'S-41: no perl or python3 to detect emoji, skip\n' >&2
|
|
1895
|
+
return 2
|
|
1896
|
+
fi
|
|
1897
|
+
|
|
1898
|
+
if [ -n "$hit" ]; then
|
|
1899
|
+
printf 'S-41: UI file に絵文字を検出 (絵文字不要):\n%s\n' "$hit" >&2
|
|
1900
|
+
return 1
|
|
1901
|
+
fi
|
|
1902
|
+
|
|
1903
|
+
return 0
|
|
1904
|
+
)
|
|
1905
|
+
|
|
1906
|
+
# ============================================================
|
|
1907
|
+
# Case S-42: app.js が参照する DOM id が index.html の id="X" と整合する (DOM id 契約 cross-check)
|
|
1908
|
+
# task-63 Step 7 起源: app.js render() が 'main-panel' を参照したが index.html は 'view-container' しか
|
|
1909
|
+
# 持たず view 全体が描画されない CRITICAL bug が並列実装の id 契約乖離で混入。memory
|
|
1910
|
+
# feedback_parallel_subagent_cross_file_contract_drift の安全網。
|
|
1911
|
+
# task-76 Step 6-fix で re-target:
|
|
1912
|
+
# (1) 新 app.js は getElementById を $(id) wrapper 経由で呼ぶため $('id') / $("id") も抽出対象に追加
|
|
1913
|
+
# (直接 getElementById('id') も従来通り拾う)。
|
|
1914
|
+
# (2) anchor を新 render target に更新: preset-list / category-accordion / panel-config /
|
|
1915
|
+
# panel-history / history-tbody / save-bar (旧 view-container は撤去済)。
|
|
1916
|
+
# 検証方式: app.js の $('X') / getElementById('X') から id を抽出し、各 id が index.html に
|
|
1917
|
+
# id="X" として実在することを確認。1 件でも欠落したら FAIL。file-only (port 不要)。
|
|
1918
|
+
# ============================================================
|
|
1919
|
+
_case_s42() (
|
|
1920
|
+
set -uo pipefail
|
|
1921
|
+
local ui_dir="${REPO_ROOT}/.claude/scripts/lib/hc-config-web-ui"
|
|
1922
|
+
local app_js="${ui_dir}/app.js"
|
|
1923
|
+
local index_html="${ui_dir}/index.html"
|
|
1924
|
+
|
|
1925
|
+
if [ ! -f "$app_js" ]; then
|
|
1926
|
+
printf 'S-42: app.js not found at %s\n' "$app_js" >&2
|
|
1927
|
+
return 1
|
|
1928
|
+
fi
|
|
1929
|
+
if [ ! -f "$index_html" ]; then
|
|
1930
|
+
printf 'S-42: index.html not found at %s\n' "$index_html" >&2
|
|
1931
|
+
return 1
|
|
1932
|
+
fi
|
|
1933
|
+
|
|
1934
|
+
# app.js の DOM id 参照を抽出:
|
|
1935
|
+
# - getElementById('X') / getElementById("X") (従来形)
|
|
1936
|
+
# - $('X') / $("X") (新 app.js の wrapper: function $(id){return document.getElementById(id)})
|
|
1937
|
+
# 注意: $(...) は変数展開風だが grep -oE の固定 pattern なので shell 展開は起きない。
|
|
1938
|
+
local app_ids
|
|
1939
|
+
app_ids=$( {
|
|
1940
|
+
grep -oE "getElementById\(['\"][a-zA-Z0-9_-]+['\"]\)" "$app_js" 2>/dev/null || true
|
|
1941
|
+
grep -oE "\\\$\(['\"][a-zA-Z0-9_-]+['\"]\)" "$app_js" 2>/dev/null || true
|
|
1942
|
+
} \
|
|
1943
|
+
| grep -oE "['\"][a-zA-Z0-9_-]+['\"]" \
|
|
1944
|
+
| tr -d "\"'" \
|
|
1945
|
+
| sort -u || true)
|
|
1946
|
+
|
|
1947
|
+
if [ -z "$app_ids" ]; then
|
|
1948
|
+
printf 'S-42: app.js に getElementById/$() による DOM id 参照が見つからない (抽出失敗の疑い)\n' >&2
|
|
1949
|
+
return 1
|
|
1950
|
+
fi
|
|
1951
|
+
|
|
1952
|
+
# 各 id が index.html に id="X" として実在するか確認
|
|
1953
|
+
local missing=0
|
|
1954
|
+
local missing_ids=""
|
|
1955
|
+
local id
|
|
1956
|
+
for id in $app_ids; do
|
|
1957
|
+
if ! grep -qE "id=[\"']${id}[\"']" "$index_html" 2>/dev/null; then
|
|
1958
|
+
printf 'S-42: app.js が参照する DOM id "%s" が index.html に id="%s" として実在しない (id 契約乖離)\n' "$id" "$id" >&2
|
|
1959
|
+
missing=$((missing + 1))
|
|
1960
|
+
missing_ids="${missing_ids} ${id}"
|
|
1961
|
+
fi
|
|
1962
|
+
done
|
|
1963
|
+
|
|
1964
|
+
if [ $missing -gt 0 ]; then
|
|
1965
|
+
printf 'S-42: id 契約乖離 %d 件 (%s)\n' "$missing" "$missing_ids" >&2
|
|
1966
|
+
return 1
|
|
1967
|
+
fi
|
|
1968
|
+
|
|
1969
|
+
# 主要 render target が app.js / index.html 両方に存在することを明示確認 (回帰の anchor)
|
|
1970
|
+
# これらが乖離すると左右ペイン / タブ / 履歴 / 保存バーが描画されない (本 bug の核心)
|
|
1971
|
+
local key
|
|
1972
|
+
for key in preset-list category-accordion panel-config panel-history history-tbody save-bar status-text confirm-dialog; do
|
|
1973
|
+
if ! grep -qE "id=[\"']${key}[\"']" "$index_html" 2>/dev/null; then
|
|
1974
|
+
printf 'S-42: 主要 render target id "%s" が index.html に存在しない (anchor 確認失敗)\n' "$key" >&2
|
|
1975
|
+
return 1
|
|
1976
|
+
fi
|
|
1977
|
+
# app.js 側も $('key') or getElementById('key') で参照していること
|
|
1978
|
+
if ! grep -qE "(\\\$|getElementById)\(['\"]${key}['\"]\)" "$app_js" 2>/dev/null; then
|
|
1979
|
+
printf 'S-42: app.js が render target id "%s" を $()/getElementById で参照していない (描画 target 乖離の疑い)\n' "$key" >&2
|
|
1980
|
+
return 1
|
|
1981
|
+
fi
|
|
1982
|
+
done
|
|
1983
|
+
|
|
1984
|
+
return 0
|
|
1985
|
+
)
|
|
1986
|
+
|
|
1987
|
+
# ============================================================
|
|
1988
|
+
# task-65 新規 case (S-43〜S-46)
|
|
1989
|
+
# /api/current-preset axes 返却 (preset/unsaved) + top view 6 軸表示 + edit dropdown 撤去
|
|
1990
|
+
# API contract (task-65 SSoT、server/app/smoke 一貫):
|
|
1991
|
+
# preset 一致: { name, display_name_ja, match_type:"preset", axes:{6 key} }
|
|
1992
|
+
# unsaved : { name:"custom", display_name_ja, match_type:"unsaved", axes:null }
|
|
1993
|
+
# ============================================================
|
|
1994
|
+
|
|
1995
|
+
# ============================================================
|
|
1996
|
+
# Case S-43: preset apply 後 GET /api/current-preset → axes object (6 key) を返す
|
|
1997
|
+
# task-65 Step 1: matched preset の axes メタデータ (6 軸) を additive に返却
|
|
1998
|
+
# ============================================================
|
|
1999
|
+
_case_s43() (
|
|
2000
|
+
set -uo pipefail
|
|
2001
|
+
local port="$1"
|
|
2002
|
+
|
|
2003
|
+
# poc-no-git を apply して既知 preset 状態にする
|
|
2004
|
+
local apply_code
|
|
2005
|
+
apply_code=$(_curl_post_json_code "http://127.0.0.1:${port}/api/preset/poc-no-git/apply" '{}')
|
|
2006
|
+
case "$apply_code" in
|
|
2007
|
+
200|207) : ;;
|
|
2008
|
+
4??|5??)
|
|
2009
|
+
printf 'S-43: preset apply returned HTTP %s (権限エラー/yml 破損の疑い、真の FAIL)\n' "$apply_code" >&2
|
|
2010
|
+
return 1
|
|
2011
|
+
;;
|
|
2012
|
+
*)
|
|
2013
|
+
printf 'S-43: preset apply returned HTTP %s (server 接続不可等の環境 skip)\n' "$apply_code" >&2
|
|
2014
|
+
return 2
|
|
2015
|
+
;;
|
|
2016
|
+
esac
|
|
2017
|
+
|
|
2018
|
+
local body
|
|
2019
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/current-preset")
|
|
2020
|
+
|
|
2021
|
+
# match_type=preset 前提
|
|
2022
|
+
if ! printf '%s' "$body" | grep -q '"match_type"[[:space:]]*:[[:space:]]*"preset"'; then
|
|
2023
|
+
printf 'S-43: poc-no-git apply 直後に match_type=preset でない (body: %s)\n' "$body" >&2
|
|
2024
|
+
return 1
|
|
2025
|
+
fi
|
|
2026
|
+
|
|
2027
|
+
# axes field が存在すること (additive)
|
|
2028
|
+
if ! printf '%s' "$body" | grep -q '"axes"'; then
|
|
2029
|
+
printf 'S-43: /api/current-preset に axes field が無い (task-65 contract 違反、body: %s)\n' "$body" >&2
|
|
2030
|
+
return 1
|
|
2031
|
+
fi
|
|
2032
|
+
|
|
2033
|
+
# axes が null でないこと (preset 一致時は object)
|
|
2034
|
+
if printf '%s' "$body" | grep -qE '"axes"[[:space:]]*:[[:space:]]*null'; then
|
|
2035
|
+
printf 'S-43: preset 一致なのに axes:null (object であるべき、body: %s)\n' "$body" >&2
|
|
2036
|
+
return 1
|
|
2037
|
+
fi
|
|
2038
|
+
|
|
2039
|
+
# 6 軸 key が全て含まれること
|
|
2040
|
+
local axis
|
|
2041
|
+
for axis in quality_level language_framework git_workflow tdd_policy review_intensity autonomy_level; do
|
|
2042
|
+
if ! printf '%s' "$body" | grep -q "\"${axis}\""; then
|
|
2043
|
+
printf 'S-43: axes に key "%s" が無い (6 軸不完全、body: %s)\n' "$axis" "$body" >&2
|
|
2044
|
+
return 1
|
|
2045
|
+
fi
|
|
2046
|
+
done
|
|
2047
|
+
|
|
2048
|
+
# task-65 iter2 (pr-test C2): poc-no-git の全 6 軸既知値を assertion
|
|
2049
|
+
# (server.js PRESETS['poc-no-git'].axes 定義由来)。quality_level:poc は上で確認済。
|
|
2050
|
+
# axis_key:expected_value の組を 1 件ずつ照合 (axes 値が preset metadata 由来であることを完全検証)。
|
|
2051
|
+
local axis_kv
|
|
2052
|
+
for axis_kv in \
|
|
2053
|
+
'quality_level:poc' \
|
|
2054
|
+
'language_framework:mixed' \
|
|
2055
|
+
'git_workflow:none' \
|
|
2056
|
+
'tdd_policy:optional' \
|
|
2057
|
+
'review_intensity:minimum' \
|
|
2058
|
+
'autonomy_level:aggressive'; do
|
|
2059
|
+
local a_key="${axis_kv%%:*}"
|
|
2060
|
+
local a_val="${axis_kv#*:}"
|
|
2061
|
+
if ! printf '%s' "$body" | grep -qE "\"${a_key}\"[[:space:]]*:[[:space:]]*\"${a_val}\""; then
|
|
2062
|
+
printf 'S-43: poc-no-git の axis %s が "%s" でない (axes 値が preset metadata 由来でない疑い、body: %s)\n' "$a_key" "$a_val" "$body" >&2
|
|
2063
|
+
return 1
|
|
2064
|
+
fi
|
|
2065
|
+
done
|
|
2066
|
+
|
|
2067
|
+
return 0
|
|
2068
|
+
)
|
|
2069
|
+
|
|
2070
|
+
# ============================================================
|
|
2071
|
+
# Case S-44: unsaved 状態で GET /api/current-preset → axes:null を返す
|
|
2072
|
+
# task-65 Step 1: preset 外 (unsaved) では axis 値が一意でないため axes:null
|
|
2073
|
+
# ============================================================
|
|
2074
|
+
_case_s44() (
|
|
2075
|
+
set -uo pipefail
|
|
2076
|
+
local port="$1"
|
|
2077
|
+
|
|
2078
|
+
# poc-no-git を apply して既知 preset 状態 → known key を変更して unsaved 化
|
|
2079
|
+
local apply_code
|
|
2080
|
+
apply_code=$(_curl_post_json_code "http://127.0.0.1:${port}/api/preset/poc-no-git/apply" '{}')
|
|
2081
|
+
case "$apply_code" in
|
|
2082
|
+
200|207) : ;;
|
|
2083
|
+
4??|5??)
|
|
2084
|
+
printf 'S-44: preset apply returned HTTP %s (真の FAIL)\n' "$apply_code" >&2
|
|
2085
|
+
return 1
|
|
2086
|
+
;;
|
|
2087
|
+
*)
|
|
2088
|
+
printf 'S-44: preset apply returned HTTP %s (環境 skip)\n' "$apply_code" >&2
|
|
2089
|
+
return 2
|
|
2090
|
+
;;
|
|
2091
|
+
esac
|
|
2092
|
+
|
|
2093
|
+
# confidence_threshold (poc-no-git は '0.5') を 0.99 に set して unsaved 化
|
|
2094
|
+
local set_code
|
|
2095
|
+
set_code=$(_curl_post_json_code "http://127.0.0.1:${port}/api/set" \
|
|
2096
|
+
'{"key":"confidence_threshold","value":"0.99"}')
|
|
2097
|
+
if [ "$set_code" != "200" ]; then
|
|
2098
|
+
printf 'S-44: /api/set confidence_threshold=0.99 returned HTTP %s (known key set 失敗、FAIL)\n' "$set_code" >&2
|
|
2099
|
+
return 1
|
|
2100
|
+
fi
|
|
2101
|
+
|
|
2102
|
+
local body
|
|
2103
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/current-preset")
|
|
2104
|
+
|
|
2105
|
+
# match_type=custom 前提 (task-76 Step 2: 'unsaved' → 'custom')
|
|
2106
|
+
if ! printf '%s' "$body" | grep -q '"match_type"[[:space:]]*:[[:space:]]*"custom"'; then
|
|
2107
|
+
printf 'S-44: set 後に match_type=custom でない (body: %s)\n' "$body" >&2
|
|
2108
|
+
return 1
|
|
2109
|
+
fi
|
|
2110
|
+
|
|
2111
|
+
# axes が null であること (task-65 contract)
|
|
2112
|
+
if ! printf '%s' "$body" | grep -qE '"axes"[[:space:]]*:[[:space:]]*null'; then
|
|
2113
|
+
printf 'S-44: unsaved なのに axes:null でない (task-65 contract 違反、body: %s)\n' "$body" >&2
|
|
2114
|
+
return 1
|
|
2115
|
+
fi
|
|
2116
|
+
|
|
2117
|
+
return 0
|
|
2118
|
+
)
|
|
2119
|
+
|
|
2120
|
+
# ============================================================
|
|
2121
|
+
# Case S-45: RETIRED (task-76 Step 6-fix)
|
|
2122
|
+
# 旧 assertion: renderTop が cp.axes 参照 + 「カスタム設定 (プリセット外)」見出し + AXIS_LABELS_JA +
|
|
2123
|
+
# loadCurrentAxes 撤去確認 (task-65 top view の 6 軸 read-only table 前提)。
|
|
2124
|
+
# retire 理由: task-76 で top view と 6 軸 read-only table を廃止。新設計は右 category accordion で
|
|
2125
|
+
# 実 key を inline 編集する方式で、cp.axes / AXIS_LABELS_JA を参照しない。custom 状態は
|
|
2126
|
+
# state.isCustom + header-state-badge で表現し、visual 検証 (Step 7) が担保。常時 SKIP (return 2)。
|
|
2127
|
+
# ============================================================
|
|
2128
|
+
_case_s45() (
|
|
2129
|
+
set -uo pipefail
|
|
2130
|
+
printf 'S-45: RETIRED — top-view axes table (cp.axes / AXIS_LABELS_JA) removed in task-76 redesign\n' >&2
|
|
2131
|
+
return 2
|
|
2132
|
+
)
|
|
2133
|
+
|
|
2134
|
+
# ============================================================
|
|
2135
|
+
# Case S-46: RETIRED (task-76 Step 6-fix)
|
|
2136
|
+
# 旧 assertion: 6 軸 dropdown 関連 symbol (onChangeAxis 等) 撤去 + renderEdit / applyPresetMode 存在。
|
|
2137
|
+
# retire 理由: task-76 で edit view / applyPresetMode を廃止 (preset 選択は onSelectPreset で diff を
|
|
2138
|
+
# draft へ preview、個別編集は onEditKey)。6 軸 dropdown 撤去確認は task-63/65 の一時 regression guard で、
|
|
2139
|
+
# 新設計に renderEdit / applyPresetMode は存在しない。preset 選択経路は S-48/S-50 が API レベルで、
|
|
2140
|
+
# 描画は visual 検証 (Step 7) が担保。常時 SKIP (return 2)。
|
|
2141
|
+
# ============================================================
|
|
2142
|
+
_case_s46() (
|
|
2143
|
+
set -uo pipefail
|
|
2144
|
+
printf 'S-46: RETIRED — edit view / applyPresetMode removed in task-76 2-pane redesign\n' >&2
|
|
2145
|
+
return 2
|
|
2146
|
+
)
|
|
2147
|
+
|
|
2148
|
+
# ============================================================
|
|
2149
|
+
# Case S-47: GET /api/keys が返す key set == yml top-level keys (full parity)
|
|
2150
|
+
# task-69 Step 3 (key parity fix):
|
|
2151
|
+
# /api/keys は従来 metadata (75 entry) を基準に enrich していたため、
|
|
2152
|
+
# metadata 未登録の yml key (feature_reviewer_count_guard_enabled /
|
|
2153
|
+
# feature_stale_harness_detect_enabled / harness_version / stale_harness_markers)
|
|
2154
|
+
# が response から欠落していた。本 case は /api/keys の key 集合が
|
|
2155
|
+
# harness-config.yml の top-level key 集合 (79 key) と完全一致することを検証する。
|
|
2156
|
+
# - key source SSoT = yml top-level key (`^[a-z_][a-zA-Z0-9_]*:`)
|
|
2157
|
+
# - metadata 有無に関わらず yml 全 key が返ること (metadata は left join の表示補助)
|
|
2158
|
+
# RED→GREEN: 修正前は total=75 (metadata 基準) で fail、修正後は total=yml key 数で green。
|
|
2159
|
+
# network: localhost bind が必要なため node + server 起動可能環境のみ。不可なら呼出側で SKIP。
|
|
2160
|
+
# ============================================================
|
|
2161
|
+
_case_s47() (
|
|
2162
|
+
set -uo pipefail
|
|
2163
|
+
local port="$1"
|
|
2164
|
+
|
|
2165
|
+
# yml top-level key を hc-config.sh と同一 regex で抽出 (SSoT)
|
|
2166
|
+
local yml="${REPO_ROOT}/.claude/harness-config.yml"
|
|
2167
|
+
if [ ! -f "$yml" ]; then
|
|
2168
|
+
printf 'S-47: harness-config.yml not found at %s\n' "$yml" >&2
|
|
2169
|
+
return 1
|
|
2170
|
+
fi
|
|
2171
|
+
local expected_keys
|
|
2172
|
+
expected_keys=$(grep -E "^[a-z_][a-zA-Z0-9_]*:" "$yml" 2>/dev/null | sed -E 's/:.*$//' | sort -u || true)
|
|
2173
|
+
local expected_count
|
|
2174
|
+
expected_count=$(printf '%s\n' "$expected_keys" | grep -c '.' || true)
|
|
2175
|
+
|
|
2176
|
+
if [ "${expected_count:-0}" -lt 1 ]; then
|
|
2177
|
+
printf 'S-47: could not extract yml top-level keys\n' >&2
|
|
2178
|
+
return 1
|
|
2179
|
+
fi
|
|
2180
|
+
|
|
2181
|
+
# /api/keys response から key 名を抽出
|
|
2182
|
+
# task-69 Step 8 M2: grep -oE '"key":"[^"]+" 方式は value 内の偶発文字列で誤カウントする seam があるため
|
|
2183
|
+
# node -e で .keys[].key を読む方式に変更 (node 不在時は grep fallback)。
|
|
2184
|
+
local body
|
|
2185
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/keys")
|
|
2186
|
+
if ! printf '%s' "$body" | grep -q '"keys"'; then
|
|
2187
|
+
printf 'S-47: /api/keys missing .keys field (body: %s)\n' "$body" >&2
|
|
2188
|
+
return 1
|
|
2189
|
+
fi
|
|
2190
|
+
|
|
2191
|
+
local actual_keys
|
|
2192
|
+
if command -v node >/dev/null 2>&1; then
|
|
2193
|
+
# node で JSON を正確にパース: .keys[].key を抽出
|
|
2194
|
+
actual_keys=$(printf '%s' "$body" | node -e '
|
|
2195
|
+
let d=""; process.stdin.on("data",c=>d+=c); process.stdin.on("end",()=>{
|
|
2196
|
+
try{const o=JSON.parse(d);(o.keys||[]).forEach(e=>{if(e&&e.key)console.log(e.key)});}
|
|
2197
|
+
catch(ex){process.exit(1);}
|
|
2198
|
+
});
|
|
2199
|
+
' 2>/dev/null | sort -u || true)
|
|
2200
|
+
else
|
|
2201
|
+
# node 不在時 fallback: grep -oE (value 混入リスクは残るが環境依存)
|
|
2202
|
+
actual_keys=$(printf '%s' "$body" | grep -oE '"key":"[^"]+"' | sed -E 's/^"key":"//; s/"$//' | sort -u || true)
|
|
2203
|
+
fi
|
|
2204
|
+
local actual_count
|
|
2205
|
+
actual_count=$(printf '%s\n' "$actual_keys" | grep -c '.' || true)
|
|
2206
|
+
|
|
2207
|
+
# key 数の完全一致を検証 (parity)
|
|
2208
|
+
if [ "${actual_count:-0}" != "${expected_count:-0}" ]; then
|
|
2209
|
+
printf 'S-47: key count mismatch (yml=%s, /api/keys=%s)\n' "$expected_count" "$actual_count" >&2
|
|
2210
|
+
printf 'S-47: yml-only keys (欠落): %s\n' "$(comm -23 <(printf '%s\n' "$expected_keys") <(printf '%s\n' "$actual_keys") | tr '\n' ' ')" >&2
|
|
2211
|
+
printf 'S-47: api-only keys (余分): %s\n' "$(comm -13 <(printf '%s\n' "$expected_keys") <(printf '%s\n' "$actual_keys") | tr '\n' ' ')" >&2
|
|
2212
|
+
return 1
|
|
2213
|
+
fi
|
|
2214
|
+
|
|
2215
|
+
# 集合の完全一致 (差分 0)
|
|
2216
|
+
local diff_lines
|
|
2217
|
+
diff_lines=$(comm -3 <(printf '%s\n' "$expected_keys") <(printf '%s\n' "$actual_keys") | grep -c '.' || true)
|
|
2218
|
+
if [ "${diff_lines:-0}" != "0" ]; then
|
|
2219
|
+
printf 'S-47: key set mismatch (差分 %s 件):\n%s\n' "$diff_lines" "$(comm -3 <(printf '%s\n' "$expected_keys") <(printf '%s\n' "$actual_keys"))" >&2
|
|
2220
|
+
return 1
|
|
2221
|
+
fi
|
|
2222
|
+
|
|
2223
|
+
return 0
|
|
2224
|
+
)
|
|
2225
|
+
|
|
2226
|
+
# ============================================================
|
|
2227
|
+
# task-76 Step 1 新規 case (S-48 / S-49)
|
|
2228
|
+
# diff「Failed to fetch」バグ修復 (computePresetDiff の hcGet N 回ループ →
|
|
2229
|
+
# hcListAll() cache 参照 1 回。N×spawnSync の Node event loop 同期ブロックが
|
|
2230
|
+
# ブラウザ keep-alive 並列接続で hung connection → Failed to fetch を起こす真因)。
|
|
2231
|
+
# API response 後方互換 (changes[].key/current/new/changed/effect) を維持しつつ、
|
|
2232
|
+
# (a) 正常 JSON 応答 (b) 連続/並列呼び出しでの応答性 を検証する。
|
|
2233
|
+
# ============================================================
|
|
2234
|
+
|
|
2235
|
+
# ============================================================
|
|
2236
|
+
# Case S-48: GET /api/preset/:name/diff 正常 JSON 応答 + changed field 後方互換
|
|
2237
|
+
# 複数 preset (key 数の多い production-typescript-enterprise 含む) で diff を取得し、
|
|
2238
|
+
# .changes 配列の各要素が key/current/new/changed/effect を持つことを検証。
|
|
2239
|
+
# 修正前は per-key hcGet で同じ JSON 構造を返すため本 case 単体では緑だが、
|
|
2240
|
+
# S-49 (連続/並列応答性) と対で「N spawn ブロック解消」を担保する回帰 anchor。
|
|
2241
|
+
# ============================================================
|
|
2242
|
+
_case_s48() (
|
|
2243
|
+
set -uo pipefail
|
|
2244
|
+
local port="$1"
|
|
2245
|
+
|
|
2246
|
+
local p
|
|
2247
|
+
for p in poc-no-git production-typescript-enterprise harness-development; do
|
|
2248
|
+
local body
|
|
2249
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/preset/${p}/diff")
|
|
2250
|
+
|
|
2251
|
+
# 正常 JSON: .changes + .preset
|
|
2252
|
+
if ! printf '%s' "$body" | grep -q '"changes"'; then
|
|
2253
|
+
printf 'S-48: /api/preset/%s/diff missing .changes field (body: %s)\n' "$p" "$body" >&2
|
|
2254
|
+
return 1
|
|
2255
|
+
fi
|
|
2256
|
+
if ! printf '%s' "$body" | grep -q "\"preset\""; then
|
|
2257
|
+
printf 'S-48: /api/preset/%s/diff missing .preset field\n' "$p" >&2
|
|
2258
|
+
return 1
|
|
2259
|
+
fi
|
|
2260
|
+
|
|
2261
|
+
# 各 changes 要素の後方互換 field (key/current/new/changed/effect)
|
|
2262
|
+
local field
|
|
2263
|
+
for field in '"key"' '"current"' '"new"' '"changed"' '"effect"'; do
|
|
2264
|
+
if ! printf '%s' "$body" | grep -q "$field"; then
|
|
2265
|
+
printf 'S-48: /api/preset/%s/diff .changes missing field %s (後方互換違反、body: %s)\n' "$p" "$field" "$body" >&2
|
|
2266
|
+
return 1
|
|
2267
|
+
fi
|
|
2268
|
+
done
|
|
2269
|
+
done
|
|
2270
|
+
|
|
2271
|
+
return 0
|
|
2272
|
+
)
|
|
2273
|
+
|
|
2274
|
+
# ============================================================
|
|
2275
|
+
# Case S-49: diff endpoint 連続/並列呼び出しの応答性 (Failed to fetch 回帰防止)
|
|
2276
|
+
# 真因: computePresetDiff が key ごとに hcGet (= spawnSync) を呼び Node event loop を
|
|
2277
|
+
# 同期ブロック。連続/並列 diff request で hung connection → TypeError: Failed to fetch。
|
|
2278
|
+
# 検証: 同一 server に対し diff request を 6 連続実行し、全て 200 + .changes を返すこと、
|
|
2279
|
+
# かつ全体が応答性閾値内 (timeout なし) で完了することを確認する。
|
|
2280
|
+
# 修正後 (hcListAll cache 参照) は 2 回目以降 spawn 0 で高速応答する。
|
|
2281
|
+
# curl --max-time を 5s に絞り、1 件でも timeout/非 200 なら FAIL (hung connection 検出)。
|
|
2282
|
+
# ============================================================
|
|
2283
|
+
_case_s49() (
|
|
2284
|
+
set -uo pipefail
|
|
2285
|
+
local port="$1"
|
|
2286
|
+
|
|
2287
|
+
# cache を温める意図はなく、連続 request 全ての健全性を検証する。
|
|
2288
|
+
# key 数の多い production-typescript-enterprise を含め 6 回連続実行。
|
|
2289
|
+
local presets="production-typescript-enterprise production-python production-rust production-go harness-development inner-typescript"
|
|
2290
|
+
local i=0
|
|
2291
|
+
local p
|
|
2292
|
+
for p in $presets; do
|
|
2293
|
+
i=$((i + 1))
|
|
2294
|
+
# --max-time 5: hung connection なら timeout → 空応答/非 200 で FAIL
|
|
2295
|
+
local http_code
|
|
2296
|
+
http_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 5 \
|
|
2297
|
+
"http://127.0.0.1:${port}/api/preset/${p}/diff" 2>/dev/null || true)
|
|
2298
|
+
if [ "$http_code" != "200" ]; then
|
|
2299
|
+
printf 'S-49: diff #%d (%s) returned HTTP %s (expected 200; hung connection/timeout の疑い)\n' "$i" "$p" "$http_code" >&2
|
|
2300
|
+
return 1
|
|
2301
|
+
fi
|
|
2302
|
+
# body に .changes があること (空応答でないこと = hung でない)
|
|
2303
|
+
local body
|
|
2304
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/preset/${p}/diff")
|
|
2305
|
+
if ! printf '%s' "$body" | grep -q '"changes"'; then
|
|
2306
|
+
printf 'S-49: diff #%d (%s) body に .changes が無い (hung/部分応答の疑い、body: %s)\n' "$i" "$p" "$body" >&2
|
|
2307
|
+
return 1
|
|
2308
|
+
fi
|
|
2309
|
+
done
|
|
2310
|
+
|
|
2311
|
+
# 並列 diff 4 本同時 + current-preset (hcListAll 2s spawn) を背景で流して全件健全か確認。
|
|
2312
|
+
# event loop が N spawn でブロックされていると並列接続が hung → 一部が空/非 200 になる。
|
|
2313
|
+
curl -s -o /dev/null --connect-timeout 3 --max-time 8 \
|
|
2314
|
+
"http://127.0.0.1:${port}/api/current-preset" &
|
|
2315
|
+
local cp_pid=$!
|
|
2316
|
+
local par_fail=0
|
|
2317
|
+
local par_pids=""
|
|
2318
|
+
local par_out_prefix="${TMP_DIR}/s49-par"
|
|
2319
|
+
local idx=0
|
|
2320
|
+
for p in production-typescript-enterprise production-python production-rust production-go; do
|
|
2321
|
+
idx=$((idx + 1))
|
|
2322
|
+
( curl -s --connect-timeout 3 --max-time 6 \
|
|
2323
|
+
"http://127.0.0.1:${port}/api/preset/${p}/diff" > "${par_out_prefix}-${idx}.json" 2>/dev/null || true ) &
|
|
2324
|
+
par_pids="${par_pids} $!"
|
|
2325
|
+
done
|
|
2326
|
+
for pid in $par_pids; do
|
|
2327
|
+
wait "$pid" 2>/dev/null || true
|
|
2328
|
+
done
|
|
2329
|
+
wait "$cp_pid" 2>/dev/null || true
|
|
2330
|
+
|
|
2331
|
+
idx=0
|
|
2332
|
+
for p in production-typescript-enterprise production-python production-rust production-go; do
|
|
2333
|
+
idx=$((idx + 1))
|
|
2334
|
+
if ! grep -q '"changes"' "${par_out_prefix}-${idx}.json" 2>/dev/null; then
|
|
2335
|
+
printf 'S-49: 並列 diff #%d (%s) が .changes を返さなかった (hung connection = Failed to fetch 再現)\n' "$idx" "$p" >&2
|
|
2336
|
+
par_fail=$((par_fail + 1))
|
|
2337
|
+
fi
|
|
2338
|
+
done
|
|
2339
|
+
|
|
2340
|
+
if [ $par_fail -gt 0 ]; then
|
|
2341
|
+
printf 'S-49: 並列 diff %d 件が hung (Failed to fetch 真因 = N spawn event loop block の疑い)\n' "$par_fail" >&2
|
|
2342
|
+
return 1
|
|
2343
|
+
fi
|
|
2344
|
+
|
|
2345
|
+
return 0
|
|
2346
|
+
)
|
|
2347
|
+
|
|
2348
|
+
# ============================================================
|
|
2349
|
+
# Case S-50: GET /api/presets 各 entry に group field がある + 3 ラベルのいずれか
|
|
2350
|
+
# task-76 Step 2: 左ペイン分類用 group ('POC'|'社内ツール'|'本番運用'|'その他') を付与。
|
|
2351
|
+
# draft §3.4 note (SSoT): quality_level 3 group。実データでは全 10 preset が 3 group に収まり
|
|
2352
|
+
# 'その他' fallback は使われない (poc / inner_system / production_service のみ) が、
|
|
2353
|
+
# 契約上は 4 値のいずれかを許容する。
|
|
2354
|
+
# ============================================================
|
|
2355
|
+
_case_s50() (
|
|
2356
|
+
set -uo pipefail
|
|
2357
|
+
local port="$1"
|
|
2358
|
+
|
|
2359
|
+
local body
|
|
2360
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/presets")
|
|
2361
|
+
|
|
2362
|
+
# group field 自体が存在すること
|
|
2363
|
+
if ! printf '%s' "$body" | grep -q '"group"'; then
|
|
2364
|
+
printf 'S-50: /api/presets response に group field が無い (body: %s)\n' "$body" >&2
|
|
2365
|
+
return 1
|
|
2366
|
+
fi
|
|
2367
|
+
|
|
2368
|
+
# group の出現回数が 10 件 (preset 数) 分あること
|
|
2369
|
+
local g_count
|
|
2370
|
+
g_count=$(printf '%s' "$body" | grep -oE '"group"' | wc -l | tr -d ' ' || true)
|
|
2371
|
+
if [ "${g_count:-0}" -lt 10 ]; then
|
|
2372
|
+
printf 'S-50: group の出現回数 %s 件 (expected >= 10)\n' "$g_count" >&2
|
|
2373
|
+
return 1
|
|
2374
|
+
fi
|
|
2375
|
+
|
|
2376
|
+
# 各 group 値が許容 4 ラベルのいずれかであること
|
|
2377
|
+
# 許容外の group 値が 1 件でもあれば FAIL (grep -vE で許容ラベルを除外し残りを検出)
|
|
2378
|
+
local bad
|
|
2379
|
+
bad=$(printf '%s' "$body" \
|
|
2380
|
+
| grep -oE '"group"[[:space:]]*:[[:space:]]*"[^"]*"' \
|
|
2381
|
+
| grep -vE '"group"[[:space:]]*:[[:space:]]*"(POC|社内ツール|本番運用|その他)"' || true)
|
|
2382
|
+
if [ -n "$bad" ]; then
|
|
2383
|
+
printf 'S-50: 許容外の group ラベルを検出 (POC/社内ツール/本番運用/その他 以外): %s\n' "$bad" >&2
|
|
2384
|
+
return 1
|
|
2385
|
+
fi
|
|
2386
|
+
|
|
2387
|
+
# 主要 3 group が実際に出現すること (10 preset が 3 group に分布する実データ確認)
|
|
2388
|
+
for label in 'POC' '社内ツール' '本番運用'; do
|
|
2389
|
+
if ! printf '%s' "$body" | grep -q "\"group\"[[:space:]]*:[[:space:]]*\"${label}\""; then
|
|
2390
|
+
printf 'S-50: group "%s" が 1 件も出現しない (3 group 分布の確認失敗、body: %s)\n' "$label" "$body" >&2
|
|
2391
|
+
return 1
|
|
2392
|
+
fi
|
|
2393
|
+
done
|
|
2394
|
+
|
|
2395
|
+
return 0
|
|
2396
|
+
)
|
|
2397
|
+
|
|
2398
|
+
# ============================================================
|
|
2399
|
+
# Case S-51: GET /api/current-preset の match_type が 'preset'|'custom' のいずれか
|
|
2400
|
+
# task-76 Step 2: match_type を 'unsaved' → 'custom' に整理。'unsaved' は廃止語彙。
|
|
2401
|
+
# poc-no-git apply → match_type=preset、known key 変更 → match_type=custom の遷移を 1 case で検証。
|
|
2402
|
+
# ============================================================
|
|
2403
|
+
_case_s51() (
|
|
2404
|
+
set -uo pipefail
|
|
2405
|
+
local port="$1"
|
|
2406
|
+
|
|
2407
|
+
# 1. apply 後は match_type が許容 2 値のいずれか (通常 preset)
|
|
2408
|
+
local apply_code
|
|
2409
|
+
apply_code=$(_curl_post_json_code "http://127.0.0.1:${port}/api/preset/poc-no-git/apply" '{}')
|
|
2410
|
+
case "$apply_code" in
|
|
2411
|
+
200|207) : ;;
|
|
2412
|
+
4??|5??)
|
|
2413
|
+
printf 'S-51: preset apply returned HTTP %s (権限/yml 破損の疑い、真の FAIL)\n' "$apply_code" >&2
|
|
2414
|
+
return 1
|
|
2415
|
+
;;
|
|
2416
|
+
*)
|
|
2417
|
+
printf 'S-51: preset apply returned HTTP %s (server 接続不可等の環境 skip)\n' "$apply_code" >&2
|
|
2418
|
+
return 2
|
|
2419
|
+
;;
|
|
2420
|
+
esac
|
|
2421
|
+
|
|
2422
|
+
local body
|
|
2423
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/current-preset")
|
|
2424
|
+
|
|
2425
|
+
# match_type は "preset" または "custom" のいずれか (旧 'unsaved' は廃止)
|
|
2426
|
+
if ! printf '%s' "$body" | grep -qE '"match_type"[[:space:]]*:[[:space:]]*"(preset|custom)"'; then
|
|
2427
|
+
printf 'S-51: match_type が "preset"|"custom" でない (旧 unsaved 残存の疑い、body: %s)\n' "$body" >&2
|
|
2428
|
+
return 1
|
|
2429
|
+
fi
|
|
2430
|
+
|
|
2431
|
+
# 'unsaved' という旧語彙が残っていないこと (regression guard)
|
|
2432
|
+
if printf '%s' "$body" | grep -q '"match_type"[[:space:]]*:[[:space:]]*"unsaved"'; then
|
|
2433
|
+
printf 'S-51: match_type に旧語彙 "unsaved" が残存 (task-76 Step 2 で custom へ移行済のはず、body: %s)\n' "$body" >&2
|
|
2434
|
+
return 1
|
|
2435
|
+
fi
|
|
2436
|
+
|
|
2437
|
+
# 2. known key を変更すると match_type=custom になること
|
|
2438
|
+
local set_code
|
|
2439
|
+
set_code=$(_curl_post_json_code "http://127.0.0.1:${port}/api/set" \
|
|
2440
|
+
'{"key":"confidence_threshold","value":"0.99"}')
|
|
2441
|
+
if [ "$set_code" != "200" ]; then
|
|
2442
|
+
printf 'S-51: /api/set confidence_threshold=0.99 returned HTTP %s (known key set 失敗、FAIL)\n' "$set_code" >&2
|
|
2443
|
+
return 1
|
|
2444
|
+
fi
|
|
2445
|
+
|
|
2446
|
+
local after_body
|
|
2447
|
+
after_body=$(_curl_json "http://127.0.0.1:${port}/api/current-preset")
|
|
2448
|
+
if ! printf '%s' "$after_body" | grep -q '"match_type"[[:space:]]*:[[:space:]]*"custom"'; then
|
|
2449
|
+
printf 'S-51: known key 変更後に match_type=custom でない (body: %s)\n' "$after_body" >&2
|
|
2450
|
+
return 1
|
|
2451
|
+
fi
|
|
2452
|
+
|
|
2453
|
+
return 0
|
|
2454
|
+
)
|
|
2455
|
+
|
|
2456
|
+
# ============================================================
|
|
2457
|
+
# task-77 Step 5 新規 case (S-52 / S-53 / S-54): git 統合 policy 実 key 化
|
|
2458
|
+
# §3.5: 10 named preset の values に mainline_integration_policy が入り 3 値のいずれか
|
|
2459
|
+
# §3.6: mainline_branch / mainline_integration_policy が Gate/Confidence category 実 key として右ペインに出る
|
|
2460
|
+
# app.js ENUM_OPTIONS に mainline_integration_policy enum (select 表示)
|
|
2461
|
+
# ============================================================
|
|
2462
|
+
|
|
2463
|
+
# ============================================================
|
|
2464
|
+
# Case S-52: 各 named preset の values に mainline_integration_policy + 3 値分布
|
|
2465
|
+
# /api/presets は values を返さない (axes_values 撤去済、task-63 Step 4 A3) ため、
|
|
2466
|
+
# per-preset の /api/preset/:name/diff (changes[].key/new、後方互換) で values を検証する。
|
|
2467
|
+
# §3.5 の policy 既定: poc-*=local-merge-push / inner-*+production-*=pr-required / harness-development=local-merge
|
|
2468
|
+
# ============================================================
|
|
2469
|
+
_case_s52() (
|
|
2470
|
+
set -uo pipefail
|
|
2471
|
+
local port="$1"
|
|
2472
|
+
|
|
2473
|
+
# 10 named preset 全件
|
|
2474
|
+
local presets="poc-no-git poc-with-git inner-typescript inner-python \
|
|
2475
|
+
production-typescript-personal production-typescript-enterprise production-python \
|
|
2476
|
+
production-rust production-go harness-development"
|
|
2477
|
+
|
|
2478
|
+
# §3.5 期待 policy (preset:policy)
|
|
2479
|
+
local expect_local_merge_push="poc-no-git poc-with-git"
|
|
2480
|
+
local expect_local_merge="harness-development"
|
|
2481
|
+
# 残り (inner-*/production-*) は pr-required
|
|
2482
|
+
|
|
2483
|
+
local saw_pr="" saw_lmp="" saw_lm=""
|
|
2484
|
+
local p
|
|
2485
|
+
for p in $presets; do
|
|
2486
|
+
local body val
|
|
2487
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/preset/${p}/diff")
|
|
2488
|
+
|
|
2489
|
+
# changes[] から mainline_integration_policy の "new" 値を抽出
|
|
2490
|
+
# 各 change は {"key":"...","current":"...","new":"...",...} の単一 object
|
|
2491
|
+
# mainline_integration_policy を含む change object 部分を切り出し new を取る
|
|
2492
|
+
val=$(printf '%s' "$body" \
|
|
2493
|
+
| grep -oE '"key"[[:space:]]*:[[:space:]]*"mainline_integration_policy"[^}]*"new"[[:space:]]*:[[:space:]]*"[^"]*"' \
|
|
2494
|
+
| grep -oE '"new"[[:space:]]*:[[:space:]]*"[^"]*"' \
|
|
2495
|
+
| sed -E 's/.*"new"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/' || true)
|
|
2496
|
+
|
|
2497
|
+
if [ -z "$val" ]; then
|
|
2498
|
+
printf 'S-52: preset %s の diff に mainline_integration_policy change が無い (values 未追加の疑い、body: %s)\n' "$p" "$body" >&2
|
|
2499
|
+
return 1
|
|
2500
|
+
fi
|
|
2501
|
+
|
|
2502
|
+
# 許容 3 値
|
|
2503
|
+
case "$val" in
|
|
2504
|
+
pr-required|local-merge|local-merge-push) : ;;
|
|
2505
|
+
*)
|
|
2506
|
+
printf 'S-52: preset %s の policy 値 "%s" が許容外 (pr-required/local-merge/local-merge-push)\n' "$p" "$val" >&2
|
|
2507
|
+
return 1
|
|
2508
|
+
;;
|
|
2509
|
+
esac
|
|
2510
|
+
|
|
2511
|
+
# §3.5 期待値との照合
|
|
2512
|
+
local want="pr-required"
|
|
2513
|
+
case " $expect_local_merge_push " in *" $p "*) want="local-merge-push" ;; esac
|
|
2514
|
+
case " $expect_local_merge " in *" $p "*) want="local-merge" ;; esac
|
|
2515
|
+
if [ "$val" != "$want" ]; then
|
|
2516
|
+
printf 'S-52: preset %s の policy expected %s but got %s (§3.5 違反)\n' "$p" "$want" "$val" >&2
|
|
2517
|
+
return 1
|
|
2518
|
+
fi
|
|
2519
|
+
|
|
2520
|
+
case "$val" in
|
|
2521
|
+
pr-required) saw_pr=1 ;;
|
|
2522
|
+
local-merge-push) saw_lmp=1 ;;
|
|
2523
|
+
local-merge) saw_lm=1 ;;
|
|
2524
|
+
esac
|
|
2525
|
+
done
|
|
2526
|
+
|
|
2527
|
+
# 3 値が実際に分布すること
|
|
2528
|
+
if [ -z "$saw_pr" ] || [ -z "$saw_lmp" ] || [ -z "$saw_lm" ]; then
|
|
2529
|
+
printf 'S-52: 3 policy 値の分布不足 (pr=%s lmp=%s lm=%s)\n' "${saw_pr:-0}" "${saw_lmp:-0}" "${saw_lm:-0}" >&2
|
|
2530
|
+
return 1
|
|
2531
|
+
fi
|
|
2532
|
+
|
|
2533
|
+
return 0
|
|
2534
|
+
)
|
|
2535
|
+
|
|
2536
|
+
# ============================================================
|
|
2537
|
+
# Case S-53: GET /api/keys?category=Gate/Confidence に mainline_branch + mainline_integration_policy が出る
|
|
2538
|
+
# 右ペイン Gate/Confidence accordion に実 key として表示される前提を担保。
|
|
2539
|
+
# ============================================================
|
|
2540
|
+
_case_s53() (
|
|
2541
|
+
set -uo pipefail
|
|
2542
|
+
local port="$1"
|
|
2543
|
+
|
|
2544
|
+
local body
|
|
2545
|
+
# category 名に '/' を含むので URL encode (Gate%2FConfidence)
|
|
2546
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/keys?category=Gate%2FConfidence")
|
|
2547
|
+
|
|
2548
|
+
local k
|
|
2549
|
+
for k in mainline_branch mainline_integration_policy; do
|
|
2550
|
+
if ! printf '%s' "$body" | grep -q "\"${k}\""; then
|
|
2551
|
+
printf 'S-53: /api/keys?category=Gate/Confidence に "%s" が出ない (Step 1 metadata category 未反映の疑い、body: %s)\n' "$k" "$body" >&2
|
|
2552
|
+
return 1
|
|
2553
|
+
fi
|
|
2554
|
+
done
|
|
2555
|
+
|
|
2556
|
+
return 0
|
|
2557
|
+
)
|
|
2558
|
+
|
|
2559
|
+
# ============================================================
|
|
2560
|
+
# Case S-54: app.js ENUM_OPTIONS に mainline_integration_policy enum (3 値) が静的に存在
|
|
2561
|
+
# file-only (port 不要)。select 表示に必要な client SSoT を grep で確認。
|
|
2562
|
+
# ============================================================
|
|
2563
|
+
_case_s54() (
|
|
2564
|
+
set -uo pipefail
|
|
2565
|
+
|
|
2566
|
+
local app_js="${REPO_ROOT}/.claude/scripts/lib/hc-config-web-ui/app.js"
|
|
2567
|
+
if [ ! -f "$app_js" ]; then
|
|
2568
|
+
printf 'S-54: app.js not found at %s\n' "$app_js" >&2
|
|
2569
|
+
return 2
|
|
2570
|
+
fi
|
|
2571
|
+
|
|
2572
|
+
# ENUM_OPTIONS 内に mainline_integration_policy key が存在
|
|
2573
|
+
if ! grep -q "mainline_integration_policy:" "$app_js"; then
|
|
2574
|
+
printf 'S-54: app.js ENUM_OPTIONS に mainline_integration_policy が無い (select 表示不可)\n' >&2
|
|
2575
|
+
return 1
|
|
2576
|
+
fi
|
|
2577
|
+
|
|
2578
|
+
# 3 値が全て列挙されていること
|
|
2579
|
+
local v
|
|
2580
|
+
for v in 'pr-required' 'local-merge-push' 'local-merge'; do
|
|
2581
|
+
if ! grep -q "'${v}'" "$app_js"; then
|
|
2582
|
+
printf 'S-54: app.js ENUM_OPTIONS に policy 値 "%s" が無い\n' "$v" >&2
|
|
2583
|
+
return 1
|
|
2584
|
+
fi
|
|
2585
|
+
done
|
|
2586
|
+
|
|
2587
|
+
return 0
|
|
2588
|
+
)
|
|
2589
|
+
|
|
2590
|
+
# ============================================================
|
|
2591
|
+
# task-78 Step 1 新規 case (S-55 / S-56): metadata label_ja (5 列目) sidebar 表示
|
|
2592
|
+
# ============================================================
|
|
2593
|
+
# Case S-55: GET /api/keys が各 entry に label_ja field を返す + 主要 key の label 値
|
|
2594
|
+
# server.js parser (loadMetadata) が 5 列目 label_ja を読み、/api/keys enriched entry に
|
|
2595
|
+
# 付与することを検証。主要 key (confidence_threshold / mainline_integration_policy /
|
|
2596
|
+
# feature_draft_flow_guard_enabled) の label が metadata.sh の値と一致することを確認。
|
|
2597
|
+
# ============================================================
|
|
2598
|
+
_case_s55() (
|
|
2599
|
+
set -uo pipefail
|
|
2600
|
+
local port="$1"
|
|
2601
|
+
|
|
2602
|
+
local body
|
|
2603
|
+
body=$(_curl_json "http://127.0.0.1:${port}/api/keys")
|
|
2604
|
+
if ! printf '%s' "$body" | grep -q '"keys"'; then
|
|
2605
|
+
printf 'S-55: /api/keys missing .keys field (body: %s)\n' "$body" >&2
|
|
2606
|
+
return 1
|
|
2607
|
+
fi
|
|
2608
|
+
|
|
2609
|
+
# label_ja field が response に存在すること
|
|
2610
|
+
if ! printf '%s' "$body" | grep -q '"label_ja"'; then
|
|
2611
|
+
printf 'S-55: /api/keys response に label_ja field が無い (body: %s)\n' "$body" >&2
|
|
2612
|
+
return 1
|
|
2613
|
+
fi
|
|
2614
|
+
|
|
2615
|
+
# H-2 (review fix): label_ja 非空率 100% を担保。"label_ja":"" (空文字) が
|
|
2616
|
+
# 1 件でも出現したら FAIL (空白許容、JSON エンコード形式 "label_ja":"" を検出)。
|
|
2617
|
+
if printf '%s' "$body" | grep -qE '"label_ja"[[:space:]]*:[[:space:]]*""'; then
|
|
2618
|
+
printf 'S-55: /api/keys に空の label_ja ("label_ja":"") が存在 (全 key 非空必須)\n' >&2
|
|
2619
|
+
return 1
|
|
2620
|
+
fi
|
|
2621
|
+
|
|
2622
|
+
# 主要 key の label 値が含まれること (metadata.sh の値と一致)
|
|
2623
|
+
local label
|
|
2624
|
+
for label in '信頼度しきい値' '本流統合ポリシー' 'draftフローガード有効化'; do
|
|
2625
|
+
if ! printf '%s' "$body" | grep -q "$label"; then
|
|
2626
|
+
printf 'S-55: /api/keys に主要 label "%s" が無い\n' "$label" >&2
|
|
2627
|
+
return 1
|
|
2628
|
+
fi
|
|
2629
|
+
done
|
|
2630
|
+
|
|
2631
|
+
return 0
|
|
2632
|
+
)
|
|
2633
|
+
|
|
2634
|
+
# ============================================================
|
|
2635
|
+
# Case S-56: metadata table の 5 列化が key parity を壊さない (file-only、port 不要)
|
|
2636
|
+
# 全 metadata 行が 5 列 (key/category/description/effect/label_ja) であること、
|
|
2637
|
+
# 行数が 5 列化前と不変であること、全 key の label_ja が非空であることを検証。
|
|
2638
|
+
# ============================================================
|
|
2639
|
+
_case_s56() (
|
|
2640
|
+
set -uo pipefail
|
|
2641
|
+
|
|
2642
|
+
local meta="${REPO_ROOT}/.claude/scripts/lib/hc-config-metadata.sh"
|
|
2643
|
+
if [ ! -f "$meta" ]; then
|
|
2644
|
+
printf 'S-56: hc-config-metadata.sh not found at %s\n' "$meta" >&2
|
|
2645
|
+
return 1
|
|
2646
|
+
fi
|
|
2647
|
+
|
|
2648
|
+
# source して table を dump、NF / 空 label を bash 内で検査
|
|
2649
|
+
local result
|
|
2650
|
+
result=$(source "$meta" 2>/dev/null && _hc_metadata_table 2>/dev/null | awk -F'\t' '
|
|
2651
|
+
{ rows++ }
|
|
2652
|
+
NF != 5 { bad_nf++ }
|
|
2653
|
+
NF == 5 && $5 == "" { empty_label++ }
|
|
2654
|
+
END { printf "%d %d %d", rows, (bad_nf+0), (empty_label+0) }
|
|
2655
|
+
' || true)
|
|
2656
|
+
|
|
2657
|
+
local rows bad_nf empty_label
|
|
2658
|
+
rows=$(printf '%s' "$result" | awk '{print $1}')
|
|
2659
|
+
bad_nf=$(printf '%s' "$result" | awk '{print $2}')
|
|
2660
|
+
empty_label=$(printf '%s' "$result" | awk '{print $3}')
|
|
2661
|
+
|
|
2662
|
+
# H-1 (review fix): rows>=1 のみだと metadata 行が誤削除されても PASS してしまう。
|
|
2663
|
+
# 5 列化前後で行数不変 (現状 84 行) を担保するため下限 84 を assertion。
|
|
2664
|
+
if [ "${rows:-0}" -lt 84 ]; then
|
|
2665
|
+
printf 'S-56: metadata table 行数が下限 84 未満 (rows=%s、行の誤削除疑い)\n' "$rows" >&2
|
|
2666
|
+
return 1
|
|
2667
|
+
fi
|
|
2668
|
+
if [ "${bad_nf:-1}" != "0" ]; then
|
|
2669
|
+
printf 'S-56: 5 列でない行が %s 件存在 (key parity 破壊)\n' "$bad_nf" >&2
|
|
2670
|
+
return 1
|
|
2671
|
+
fi
|
|
2672
|
+
if [ "${empty_label:-1}" != "0" ]; then
|
|
2673
|
+
printf 'S-56: label_ja が空の key が %s 件存在 (全 key 必須)\n' "$empty_label" >&2
|
|
2674
|
+
return 1
|
|
2675
|
+
fi
|
|
2676
|
+
|
|
2677
|
+
return 0
|
|
2678
|
+
)
|
|
2679
|
+
|
|
2680
|
+
# ============================================================
|
|
2681
|
+
# Case S-57: 変更内容 右サイドバー (task-78 Step 3) の id 契約 + render 関数存在 (file-only、port 不要)
|
|
2682
|
+
# - index.html に #changes-sidebar / #changes-list が id= として実在
|
|
2683
|
+
# - app.js が #changes-list を $()/getElementById で参照 (cross-file 契約乖離 guard)
|
|
2684
|
+
# - app.js に renderChangesSidebar 関数 + keyDisplayLabel (key_name(label) render) が存在
|
|
2685
|
+
# task-76 の id mismatch render 事故 (feedback_parallel_subagent_cross_file_contract_drift) の再発防止。
|
|
2686
|
+
# ============================================================
|
|
2687
|
+
_case_s57() (
|
|
2688
|
+
set -uo pipefail
|
|
2689
|
+
local ui_dir="${REPO_ROOT}/.claude/scripts/lib/hc-config-web-ui"
|
|
2690
|
+
local app_js="${ui_dir}/app.js"
|
|
2691
|
+
local index_html="${ui_dir}/index.html"
|
|
2692
|
+
|
|
2693
|
+
if [ ! -f "$app_js" ] || [ ! -f "$index_html" ]; then
|
|
2694
|
+
printf 'S-57: app.js or index.html not found\n' >&2
|
|
2695
|
+
return 1
|
|
2696
|
+
fi
|
|
2697
|
+
|
|
2698
|
+
# index.html に sidebar container id が実在
|
|
2699
|
+
local id
|
|
2700
|
+
for id in changes-sidebar changes-list; do
|
|
2701
|
+
if ! grep -qE "id=[\"']${id}[\"']" "$index_html" 2>/dev/null; then
|
|
2702
|
+
printf 'S-57: index.html に id="%s" が存在しない (右サイドバー container 欠落)\n' "$id" >&2
|
|
2703
|
+
return 1
|
|
2704
|
+
fi
|
|
2705
|
+
done
|
|
2706
|
+
|
|
2707
|
+
# app.js が changes-list を $()/getElementById で参照 (render target 契約)
|
|
2708
|
+
if ! grep -qE "(\\\$|getElementById)\(['\"]changes-list['\"]\)" "$app_js" 2>/dev/null; then
|
|
2709
|
+
printf 'S-57: app.js が render target id "changes-list" を $()/getElementById で参照していない\n' >&2
|
|
2710
|
+
return 1
|
|
2711
|
+
fi
|
|
2712
|
+
|
|
2713
|
+
# renderChangesSidebar 関数 (差分 render) が定義されている
|
|
2714
|
+
if ! grep -qE "function renderChangesSidebar" "$app_js" 2>/dev/null; then
|
|
2715
|
+
printf 'S-57: app.js に renderChangesSidebar 関数が無い (変更内容 render 不在)\n' >&2
|
|
2716
|
+
return 1
|
|
2717
|
+
fi
|
|
2718
|
+
|
|
2719
|
+
# keyDisplayLabel 関数 (key_name(label_ja) render) が定義されている
|
|
2720
|
+
if ! grep -qE "function keyDisplayLabel" "$app_js" 2>/dev/null; then
|
|
2721
|
+
printf 'S-57: app.js に keyDisplayLabel 関数が無い (key_name(label) render 不在)\n' >&2
|
|
2722
|
+
return 1
|
|
2723
|
+
fi
|
|
2724
|
+
|
|
2725
|
+
return 0
|
|
2726
|
+
)
|
|
2727
|
+
|
|
2728
|
+
# ============================================================
|
|
2729
|
+
# Case S-34: SIGTERM graceful shutdown → port release
|
|
2730
|
+
# iter 4 C: G5 — SIGTERM graceful (S-04 は SIGINT、本 case は SIGTERM)
|
|
2731
|
+
# ============================================================
|
|
2732
|
+
_case_s34() (
|
|
2733
|
+
set -uo pipefail
|
|
2734
|
+
|
|
2735
|
+
if ! _has_node; then
|
|
2736
|
+
printf 'S-34: node not found, skip\n' >&2
|
|
2737
|
+
return 2
|
|
2738
|
+
fi
|
|
2739
|
+
|
|
2740
|
+
local log_file="${TMP_DIR}/s34-server.log"
|
|
2741
|
+
_start_server "$log_file"
|
|
2742
|
+
local pid=$SERVER_PID
|
|
2743
|
+
|
|
2744
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
2745
|
+
printf 'S-34: server did not start\n' >&2
|
|
2746
|
+
return 1
|
|
2747
|
+
fi
|
|
2748
|
+
|
|
2749
|
+
local port
|
|
2750
|
+
port=$(_get_server_port "$log_file")
|
|
2751
|
+
if [ -z "$port" ]; then
|
|
2752
|
+
printf 'S-34: could not detect port\n' >&2
|
|
2753
|
+
kill "$pid" 2>/dev/null || true
|
|
2754
|
+
SERVER_PID=""
|
|
2755
|
+
return 1
|
|
2756
|
+
fi
|
|
2757
|
+
|
|
2758
|
+
# SIGTERM 送信
|
|
2759
|
+
kill -TERM "$pid" 2>/dev/null || true
|
|
2760
|
+
local waited=0
|
|
2761
|
+
while [ $waited -lt 6 ]; do
|
|
2762
|
+
sleep 0.5
|
|
2763
|
+
waited=$((waited + 1))
|
|
2764
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
2765
|
+
break
|
|
2766
|
+
fi
|
|
2767
|
+
done
|
|
2768
|
+
SERVER_PID=""
|
|
2769
|
+
|
|
2770
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
2771
|
+
printf 'S-34: server still running after SIGTERM\n' >&2
|
|
2772
|
+
kill -9 "$pid" 2>/dev/null || true
|
|
2773
|
+
return 1
|
|
2774
|
+
fi
|
|
2775
|
+
|
|
2776
|
+
# port が解放されているか: 別 server が同 port で起動できるか確認
|
|
2777
|
+
local log2="${TMP_DIR}/s34-check.log"
|
|
2778
|
+
HC_WEB_NO_OPEN=1 node "${WEB_SERVER}" >"$log2" 2>&1 &
|
|
2779
|
+
local pid2=$!
|
|
2780
|
+
sleep 2
|
|
2781
|
+
local port2
|
|
2782
|
+
port2=$(_get_server_port "$log2")
|
|
2783
|
+
kill "$pid2" 2>/dev/null || true
|
|
2784
|
+
wait "$pid2" 2>/dev/null || true
|
|
2785
|
+
|
|
2786
|
+
if [ -z "$port2" ]; then
|
|
2787
|
+
printf 'S-34: port not released after SIGTERM (second server could not start)\n' >&2
|
|
2788
|
+
return 1
|
|
2789
|
+
fi
|
|
2790
|
+
|
|
2791
|
+
return 0
|
|
2792
|
+
)
|
|
2793
|
+
|
|
2794
|
+
# ============================================================
|
|
2795
|
+
# テスト実行
|
|
2796
|
+
# ============================================================
|
|
2797
|
+
|
|
2798
|
+
printf '\n%s\n\n' '=== hc-config-web-ui-smoke (task-63 Step 5: /api/current-preset + top/edit view 5 case) ==='
|
|
2799
|
+
|
|
2800
|
+
# --- server lifecycle (独立 server、各 case で起動/停止) ---
|
|
2801
|
+
|
|
2802
|
+
printf '%s\n' '--- server lifecycle ---'
|
|
2803
|
+
|
|
2804
|
+
if _case_s01 2>/dev/null; then _record PASS "S-01" "server 起動 (HC_WEB_NO_OPEN=1) → port LISTEN → GET / 200/302 → kill"
|
|
2805
|
+
elif [ $? -eq 2 ]; then _record SKIP "S-01" "server 起動 (node not available)"
|
|
2806
|
+
else _record FAIL "S-01" "server 起動 (HC_WEB_NO_OPEN=1) → port LISTEN → GET / 200/302 → kill"
|
|
2807
|
+
fi
|
|
2808
|
+
|
|
2809
|
+
_s02_result=0
|
|
2810
|
+
_case_s02 2>/dev/null || _s02_result=$?
|
|
2811
|
+
if [ $_s02_result -eq 0 ]; then _record PASS "S-02" "port 3060 先 occupy → server → 3061+ で listen"
|
|
2812
|
+
elif [ $_s02_result -eq 2 ]; then _record SKIP "S-02" "port 3060 先 occupy (nc/socat/python3 not available)"
|
|
2813
|
+
else _record FAIL "S-02" "port 3060 先 occupy → server → 3061+ で listen"
|
|
2814
|
+
fi
|
|
2815
|
+
|
|
2816
|
+
_s03_result=0
|
|
2817
|
+
_case_s03 2>/dev/null || _s03_result=$?
|
|
2818
|
+
if [ $_s03_result -eq 0 ]; then _record PASS "S-03" "port 3060-3070 全 occupied → server process.exit(1)"
|
|
2819
|
+
elif [ $_s03_result -eq 2 ]; then _record SKIP "S-03" "port 全 occupied (占有ツール不在)"
|
|
2820
|
+
else _record FAIL "S-03" "port 3060-3070 全 occupied → server process.exit(1)"
|
|
2821
|
+
fi
|
|
2822
|
+
|
|
2823
|
+
_s04_result=0
|
|
2824
|
+
_case_s04 2>/dev/null || _s04_result=$?
|
|
2825
|
+
if [ $_s04_result -eq 0 ]; then _record PASS "S-04" "server 起動 → SIGINT → graceful shutdown → port release"
|
|
2826
|
+
elif [ $_s04_result -eq 2 ]; then _record SKIP "S-04" "SIGINT graceful (node not available)"
|
|
2827
|
+
else _record FAIL "S-04" "server 起動 → SIGINT → graceful shutdown → port release"
|
|
2828
|
+
fi
|
|
2829
|
+
|
|
2830
|
+
# S-34: SIGTERM graceful (独立 server)
|
|
2831
|
+
_s34_result=0
|
|
2832
|
+
_case_s34 2>/dev/null || _s34_result=$?
|
|
2833
|
+
if [ $_s34_result -eq 0 ]; then _record PASS "S-34" "server 起動 → SIGTERM → graceful shutdown → port release"
|
|
2834
|
+
elif [ $_s34_result -eq 2 ]; then _record SKIP "S-34" "SIGTERM graceful (node not available)"
|
|
2835
|
+
else _record FAIL "S-34" "server 起動 → SIGTERM → graceful shutdown → port release"
|
|
2836
|
+
fi
|
|
2837
|
+
|
|
2838
|
+
# --- static + API + preset (共有 server) ---
|
|
2839
|
+
printf '\n%s\n' '--- static / API / preset (shared server) ---'
|
|
2840
|
+
|
|
2841
|
+
# S-01〜S-04,S-34 で使ったポートが解放されるまで待機 (最大 3 秒)
|
|
2842
|
+
_wait_ports_free() (
|
|
2843
|
+
set -uo pipefail
|
|
2844
|
+
local waited=0
|
|
2845
|
+
while [ $waited -lt 6 ]; do
|
|
2846
|
+
local busy=0
|
|
2847
|
+
local p
|
|
2848
|
+
for p in 3060 3061 3062 3063; do
|
|
2849
|
+
if command -v lsof >/dev/null 2>&1; then
|
|
2850
|
+
lsof -i ":$p" >/dev/null 2>/dev/null && busy=1 && break
|
|
2851
|
+
fi
|
|
2852
|
+
done
|
|
2853
|
+
if [ $busy -eq 0 ]; then return 0; fi
|
|
2854
|
+
sleep 0.5
|
|
2855
|
+
waited=$((waited + 1))
|
|
2856
|
+
done
|
|
2857
|
+
return 0
|
|
2858
|
+
)
|
|
2859
|
+
_wait_ports_free
|
|
2860
|
+
|
|
2861
|
+
if _has_node && [ -f "${WEB_SERVER}" ]; then
|
|
2862
|
+
# iter 4 C: T-H2 — ISOLATED_HISTORY_DIR で test isolation
|
|
2863
|
+
# iter 6 B: ISOLATED_PRESETS_DIR で custom-test-*.yml pollution 解消
|
|
2864
|
+
# server が HC_HISTORY_DIR_OVERRIDE / HC_PRESETS_DIR_OVERRIDE を読む実装がある場合は isolated dir が使われる
|
|
2865
|
+
_start_shared_server "$ISOLATED_HISTORY_DIR" "$ISOLATED_PRESETS_DIR"
|
|
2866
|
+
|
|
2867
|
+
if [ -z "$SHARED_PORT" ]; then
|
|
2868
|
+
printf ' WARN: shared server failed to start, skipping shared-server cases\n'
|
|
2869
|
+
for cid in S-05 S-06 S-07 S-08 S-09 S-10 S-11 S-12 S-13 S-14 S-15 S-16 S-19 S-20 S-21 S-23 S-24 S-25 S-27 S-28 S-29 S-30 S-32 S-35 S-36 S-39 S-43 S-44 S-48 S-49 S-50 S-51 S-52 S-53 S-55; do
|
|
2870
|
+
_record SKIP "$cid" "shared server not available"
|
|
2871
|
+
done
|
|
2872
|
+
_record SKIP "S-22" "/api/preset/save 撤去 (task-63 設計簡素化)"
|
|
2873
|
+
_record SKIP "S-26" "/api/preset/save 6 軸欠落 case 撤去 (task-63 設計簡素化)"
|
|
2874
|
+
_record SKIP "S-31" "/api/preset/save path traversal case 撤去 (task-63 設計簡素化)"
|
|
2875
|
+
_record SKIP "S-33" "XSS injection save name case 撤去 (task-63 設計簡素化)"
|
|
2876
|
+
_record SKIP "S-37" "RETIRED: top view (renderTop/banner) removed in task-76 2-pane redesign"
|
|
2877
|
+
_record SKIP "S-38" "RETIRED: edit-view state machine removed in task-76 2-pane redesign"
|
|
2878
|
+
# S-40 は shared server 必須なので SKIP、S-41/S-42 は file-only なので実行
|
|
2879
|
+
_record SKIP "S-40" "POST /api/preset/save 404 (shared server not available)"
|
|
2880
|
+
_s41_result=0
|
|
2881
|
+
_case_s41 2>/dev/null || _s41_result=$?
|
|
2882
|
+
if [ $_s41_result -eq 0 ]; then _record PASS "S-41" "UI 3 file 絵文字 0 件 (絵文字不要 regression guard)"
|
|
2883
|
+
elif [ $_s41_result -eq 2 ]; then _record SKIP "S-41" "絵文字検出 (perl/python3 not available)"
|
|
2884
|
+
else _record FAIL "S-41" "UI 3 file 絵文字 0 件 (絵文字不要 regression guard)"
|
|
2885
|
+
fi
|
|
2886
|
+
if _case_s42 2>/dev/null; then _record PASS "S-42" "app.js DOM id 契約が index.html id= と整合 (DOM id cross-check)"
|
|
2887
|
+
else _record FAIL "S-42" "app.js DOM id 契約が index.html id= と整合 (DOM id cross-check)"
|
|
2888
|
+
fi
|
|
2889
|
+
_s54_result=0
|
|
2890
|
+
_case_s54 2>/dev/null || _s54_result=$?
|
|
2891
|
+
if [ $_s54_result -eq 0 ]; then _record PASS "S-54" "app.js ENUM_OPTIONS に mainline_integration_policy enum (3 値、select 表示)"
|
|
2892
|
+
elif [ $_s54_result -eq 2 ]; then _record SKIP "S-54" "app.js not found"
|
|
2893
|
+
else _record FAIL "S-54" "app.js ENUM_OPTIONS に mainline_integration_policy enum (3 値、select 表示)"
|
|
2894
|
+
fi
|
|
2895
|
+
# S-43/S-44 は shared server 必須なので SKIP、S-45/S-46 は file-only なので実行
|
|
2896
|
+
_record SKIP "S-43" "preset axes 6 key (shared server not available)"
|
|
2897
|
+
_record SKIP "S-44" "unsaved axes:null (shared server not available)"
|
|
2898
|
+
_record SKIP "S-45" "RETIRED: top-view axes table (cp.axes/AXIS_LABELS_JA) removed in task-76 redesign"
|
|
2899
|
+
_record SKIP "S-46" "RETIRED: edit view / applyPresetMode removed in task-76 2-pane redesign"
|
|
2900
|
+
# S-56 は file-only (port 不要) なので shared server 不在でも実行 (task-78 Step 1)
|
|
2901
|
+
if _case_s56 2>/dev/null; then _record PASS "S-56" "metadata table 5 列化 (全 key label_ja 非空、NF==5、key parity 不変)"
|
|
2902
|
+
else _record FAIL "S-56" "metadata table 5 列化 (全 key label_ja 非空、NF==5、key parity 不変)"
|
|
2903
|
+
fi
|
|
2904
|
+
else
|
|
2905
|
+
_PORT="$SHARED_PORT"
|
|
2906
|
+
|
|
2907
|
+
if _case_s05 "$_PORT" 2>/dev/null; then _record PASS "S-05" "GET / → 302 or 200"
|
|
2908
|
+
else _record FAIL "S-05" "GET / → 302 or 200"
|
|
2909
|
+
fi
|
|
2910
|
+
|
|
2911
|
+
if _case_s06 "$_PORT" 2>/dev/null; then _record PASS "S-06" "GET /static/index.html → 200 + text/html"
|
|
2912
|
+
else _record FAIL "S-06" "GET /static/index.html → 200 + text/html"
|
|
2913
|
+
fi
|
|
2914
|
+
|
|
2915
|
+
if _case_s07 "$_PORT" 2>/dev/null; then _record PASS "S-07" "GET /static/app.js → 200 + application/javascript"
|
|
2916
|
+
else _record FAIL "S-07" "GET /static/app.js → 200 + application/javascript"
|
|
2917
|
+
fi
|
|
2918
|
+
|
|
2919
|
+
if _case_s08 "$_PORT" 2>/dev/null; then _record PASS "S-08" "path traversal /static/../../ → 403/404"
|
|
2920
|
+
else _record FAIL "S-08" "path traversal /static/../../ → 403/404"
|
|
2921
|
+
fi
|
|
2922
|
+
|
|
2923
|
+
if _case_s09 "$_PORT" 2>/dev/null; then _record PASS "S-09" "GET /api/categories → 200 + .categories length >= 1"
|
|
2924
|
+
else _record FAIL "S-09" "GET /api/categories → 200 + .categories length >= 1"
|
|
2925
|
+
fi
|
|
2926
|
+
|
|
2927
|
+
if _case_s10 "$_PORT" 2>/dev/null; then _record PASS "S-10" "GET /api/keys → 200 + .keys length >= 1"
|
|
2928
|
+
else _record FAIL "S-10" "GET /api/keys → 200 + .keys length >= 1"
|
|
2929
|
+
fi
|
|
2930
|
+
|
|
2931
|
+
if _case_s11 "$_PORT" 2>/dev/null; then _record PASS "S-11" "GET /api/presets → 200 + .presets length == 10"
|
|
2932
|
+
else _record FAIL "S-11" "GET /api/presets → 200 + .presets length == 10"
|
|
2933
|
+
fi
|
|
2934
|
+
|
|
2935
|
+
if _case_s12 "$_PORT" 2>/dev/null; then _record PASS "S-12" "GET /api/preset/poc-no-git/diff → 200 + .changes[].key/current/new/effect"
|
|
2936
|
+
else _record FAIL "S-12" "GET /api/preset/poc-no-git/diff → 200 + .changes[].key/current/new/effect"
|
|
2937
|
+
fi
|
|
2938
|
+
|
|
2939
|
+
if _case_s13 "$_PORT" 2>/dev/null; then _record PASS "S-13" "POST /api/set 不正 key (空/欠落) → 400"
|
|
2940
|
+
else _record FAIL "S-13" "POST /api/set 不正 key (空/欠落) → 400"
|
|
2941
|
+
fi
|
|
2942
|
+
|
|
2943
|
+
if _case_s14 "$_PORT" "$ISOLATED_HISTORY_DIR" 2>/dev/null; then _record PASS "S-14" "POST /api/preset/poc-no-git/apply → 200/207 + history file 生成"
|
|
2944
|
+
else _record FAIL "S-14" "POST /api/preset/poc-no-git/apply → 200/207 + history file 生成"
|
|
2945
|
+
fi
|
|
2946
|
+
|
|
2947
|
+
if _case_s15 "$_PORT" 2>/dev/null; then _record PASS "S-15" "apply → rollback → 200 + ok:true"
|
|
2948
|
+
else _record FAIL "S-15" "apply → rollback → 200 + ok:true"
|
|
2949
|
+
fi
|
|
2950
|
+
|
|
2951
|
+
if _case_s16 "$_PORT" 2>/dev/null; then _record PASS "S-16" "rollback timestamp traversal (plain + URL encoded) → 400/404/500"
|
|
2952
|
+
else _record FAIL "S-16" "rollback timestamp traversal (plain + URL encoded) → 400/404/500"
|
|
2953
|
+
fi
|
|
2954
|
+
|
|
2955
|
+
_s19_result=0
|
|
2956
|
+
_case_s19 "$_PORT" 2>/dev/null || _s19_result=$?
|
|
2957
|
+
if [ $_s19_result -eq 0 ]; then _record PASS "S-19" "1MB+1byte body POST /api/set → 400/413"
|
|
2958
|
+
elif [ $_s19_result -eq 2 ]; then _record SKIP "S-19" "large body (python3/dd not available)"
|
|
2959
|
+
else _record FAIL "S-19" "1MB+1byte body POST /api/set → 400/413"
|
|
2960
|
+
fi
|
|
2961
|
+
|
|
2962
|
+
# --- iter 4 C 新規 case ---
|
|
2963
|
+
printf '\n%s\n' '--- iter 4 C 新規 case (S-20〜S-33) ---'
|
|
2964
|
+
|
|
2965
|
+
_s20_result=0
|
|
2966
|
+
_case_s20 "$_PORT" 2>/dev/null || _s20_result=$?
|
|
2967
|
+
if [ $_s20_result -eq 0 ]; then _record PASS "S-20" "abort rollback silent no-op verify"
|
|
2968
|
+
elif [ $_s20_result -eq 2 ]; then _record SKIP "S-20" "abort rollback (no history)"
|
|
2969
|
+
else _record FAIL "S-20" "abort rollback silent no-op verify"
|
|
2970
|
+
fi
|
|
2971
|
+
|
|
2972
|
+
if _case_s21 "$_PORT" 2>/dev/null; then _record PASS "S-21" "unknown preset → 404 (diff + apply)"
|
|
2973
|
+
else _record FAIL "S-21" "unknown preset → 404 (diff + apply)"
|
|
2974
|
+
fi
|
|
2975
|
+
|
|
2976
|
+
_record SKIP "S-22" "/api/preset/save 撤去 (task-63 設計簡素化)"
|
|
2977
|
+
|
|
2978
|
+
if _case_s23 "$_PORT" 2>/dev/null; then _record PASS "S-23" "apply response ok + applied + partial フィールド verify"
|
|
2979
|
+
else _record FAIL "S-23" "apply response ok + applied + partial フィールド verify"
|
|
2980
|
+
fi
|
|
2981
|
+
|
|
2982
|
+
if _case_s24 "$_PORT" "$ISOLATED_HISTORY_DIR" 2>/dev/null; then _record PASS "S-24" "HISTORY_DIR 不在 → apply で自動作成"
|
|
2983
|
+
else _record FAIL "S-24" "HISTORY_DIR 不在 → apply で自動作成"
|
|
2984
|
+
fi
|
|
2985
|
+
|
|
2986
|
+
if _case_s25 "$_PORT" 2>/dev/null; then _record PASS "S-25" "invalid JSON body → 400 (/api/set のみ)"
|
|
2987
|
+
else _record FAIL "S-25" "invalid JSON body → 400 (/api/set のみ)"
|
|
2988
|
+
fi
|
|
2989
|
+
|
|
2990
|
+
_record SKIP "S-26" "/api/preset/save 6 軸欠落 case 撤去 (task-63 設計簡素化)"
|
|
2991
|
+
|
|
2992
|
+
if _case_s27 "$_PORT" 2>/dev/null; then _record PASS "S-27" "POST /api/set empty string value → 200 or 400 (仕様確認)"
|
|
2993
|
+
else _record FAIL "S-27" "POST /api/set empty string value → 200 or 400 (仕様確認)"
|
|
2994
|
+
fi
|
|
2995
|
+
|
|
2996
|
+
if _case_s28 "$_PORT" 2>/dev/null; then _record PASS "S-28" "URL encoded rollback traversal → 400/404/500 (not 200)"
|
|
2997
|
+
else _record FAIL "S-28" "URL encoded rollback traversal → 400/404/500 (not 200)"
|
|
2998
|
+
fi
|
|
2999
|
+
|
|
3000
|
+
if _case_s29 "$_PORT" 2>/dev/null; then _record PASS "S-29" "GET /api/preset/history → .history array"
|
|
3001
|
+
else _record FAIL "S-29" "GET /api/preset/history → .history array"
|
|
3002
|
+
fi
|
|
3003
|
+
|
|
3004
|
+
_s30_result=0
|
|
3005
|
+
_case_s30 "$_PORT" 2>/dev/null || _s30_result=$?
|
|
3006
|
+
if [ $_s30_result -eq 0 ]; then _record PASS "S-30" "rollback → ok + restored フィールド verify"
|
|
3007
|
+
elif [ $_s30_result -eq 2 ]; then _record SKIP "S-30" "rollback 0 件 (no history)"
|
|
3008
|
+
else _record FAIL "S-30" "rollback → ok + restored フィールド verify"
|
|
3009
|
+
fi
|
|
3010
|
+
|
|
3011
|
+
_record SKIP "S-31" "/api/preset/save path traversal case 撤去 (task-63 設計簡素化)"
|
|
3012
|
+
|
|
3013
|
+
_s32_result=0
|
|
3014
|
+
_case_s32 "$_PORT" 2>/dev/null || _s32_result=$?
|
|
3015
|
+
if [ $_s32_result -eq 0 ]; then _record PASS "S-32" "GET /api/keys?category=<name> → filtered list"
|
|
3016
|
+
elif [ $_s32_result -eq 2 ]; then _record SKIP "S-32" "category filter (no categories found)"
|
|
3017
|
+
else _record FAIL "S-32" "GET /api/keys?category=<name> → filtered list"
|
|
3018
|
+
fi
|
|
3019
|
+
|
|
3020
|
+
_record SKIP "S-33" "XSS injection save name case 撤去 (task-63 設計簡素化)"
|
|
3021
|
+
|
|
3022
|
+
# --- task-63 Step 5 新規 case ---
|
|
3023
|
+
printf '\n%s\n' '--- task-63 Step 5 新規 case (S-35〜S-39) ---'
|
|
3024
|
+
|
|
3025
|
+
if _case_s35 "$_PORT" 2>/dev/null; then _record PASS "S-35" "GET /api/current-preset → match_type + display_name_ja field"
|
|
3026
|
+
else _record FAIL "S-35" "GET /api/current-preset → match_type + display_name_ja field"
|
|
3027
|
+
fi
|
|
3028
|
+
|
|
3029
|
+
_s36_result=0
|
|
3030
|
+
_case_s36 "$_PORT" 2>/dev/null || _s36_result=$?
|
|
3031
|
+
if [ $_s36_result -eq 0 ]; then _record PASS "S-36" "preset apply 後 /api/current-preset → match_type=preset"
|
|
3032
|
+
elif [ $_s36_result -eq 2 ]; then _record SKIP "S-36" "preset apply skip (apply failed)"
|
|
3033
|
+
else _record FAIL "S-36" "preset apply 後 /api/current-preset → match_type=preset"
|
|
3034
|
+
fi
|
|
3035
|
+
|
|
3036
|
+
_record SKIP "S-37" "RETIRED: top view (renderTop/banner) removed in task-76 2-pane redesign"
|
|
3037
|
+
_record SKIP "S-38" "RETIRED: edit-view state machine removed in task-76 2-pane redesign"
|
|
3038
|
+
|
|
3039
|
+
_s39_result=0
|
|
3040
|
+
_case_s39 "$_PORT" 2>/dev/null || _s39_result=$?
|
|
3041
|
+
if [ $_s39_result -eq 0 ]; then _record PASS "S-39" "/api/set 1 key 変更 → /api/current-preset match_type=unsaved"
|
|
3042
|
+
elif [ $_s39_result -eq 2 ]; then _record SKIP "S-39" "/api/set unsaved 確認 skip (server 接続不可)"
|
|
3043
|
+
else _record FAIL "S-39" "/api/set 1 key 変更 → /api/current-preset match_type=unsaved"
|
|
3044
|
+
fi
|
|
3045
|
+
|
|
3046
|
+
# --- task-63 Step 6 iter-2 新規 negative case (S-40 / S-41) ---
|
|
3047
|
+
printf '\n%s\n' '--- task-63 Step 6 iter-2 negative case (S-40 / S-41) ---'
|
|
3048
|
+
|
|
3049
|
+
# F4: /api/preset/save 撤去 regression guard (404 負テスト)
|
|
3050
|
+
if _case_s40 "$_PORT" 2>/dev/null; then _record PASS "S-40" "POST /api/preset/save → 404 (custom 保存撤去 regression guard)"
|
|
3051
|
+
else _record FAIL "S-40" "POST /api/preset/save → 404 (custom 保存撤去 regression guard)"
|
|
3052
|
+
fi
|
|
3053
|
+
|
|
3054
|
+
# F5: UI 3 file 絵文字 0 件 regression guard (file-only、port 不要)
|
|
3055
|
+
_s41_result=0
|
|
3056
|
+
_case_s41 2>/dev/null || _s41_result=$?
|
|
3057
|
+
if [ $_s41_result -eq 0 ]; then _record PASS "S-41" "UI 3 file 絵文字 0 件 (絵文字不要 regression guard)"
|
|
3058
|
+
elif [ $_s41_result -eq 2 ]; then _record SKIP "S-41" "絵文字検出 (perl/python3 not available)"
|
|
3059
|
+
else _record FAIL "S-41" "UI 3 file 絵文字 0 件 (絵文字不要 regression guard)"
|
|
3060
|
+
fi
|
|
3061
|
+
|
|
3062
|
+
# task-63 Step 7: DOM id 契約 cross-check (file-only、port 不要)
|
|
3063
|
+
if _case_s42 2>/dev/null; then _record PASS "S-42" "app.js DOM id 契約が index.html id= と整合 (DOM id cross-check)"
|
|
3064
|
+
else _record FAIL "S-42" "app.js DOM id 契約が index.html id= と整合 (DOM id cross-check)"
|
|
3065
|
+
fi
|
|
3066
|
+
_s54_result=0
|
|
3067
|
+
_case_s54 2>/dev/null || _s54_result=$?
|
|
3068
|
+
if [ $_s54_result -eq 0 ]; then _record PASS "S-54" "app.js ENUM_OPTIONS に mainline_integration_policy enum (3 値、select 表示)"
|
|
3069
|
+
elif [ $_s54_result -eq 2 ]; then _record SKIP "S-54" "app.js not found"
|
|
3070
|
+
else _record FAIL "S-54" "app.js ENUM_OPTIONS に mainline_integration_policy enum (3 値、select 表示)"
|
|
3071
|
+
fi
|
|
3072
|
+
|
|
3073
|
+
# --- task-65 新規 case (S-43〜S-46): axes 返却 + top 6 軸 + dropdown 撤去 ---
|
|
3074
|
+
printf '\n%s\n' '--- task-65 新規 case (S-43〜S-46) ---'
|
|
3075
|
+
|
|
3076
|
+
_s43_result=0
|
|
3077
|
+
_case_s43 "$_PORT" 2>/dev/null || _s43_result=$?
|
|
3078
|
+
if [ $_s43_result -eq 0 ]; then _record PASS "S-43" "preset apply 後 /api/current-preset → axes object (6 key)"
|
|
3079
|
+
elif [ $_s43_result -eq 2 ]; then _record SKIP "S-43" "axes 6 key 確認 skip (apply failed)"
|
|
3080
|
+
else _record FAIL "S-43" "preset apply 後 /api/current-preset → axes object (6 key)"
|
|
3081
|
+
fi
|
|
3082
|
+
|
|
3083
|
+
_s44_result=0
|
|
3084
|
+
_case_s44 "$_PORT" 2>/dev/null || _s44_result=$?
|
|
3085
|
+
if [ $_s44_result -eq 0 ]; then _record PASS "S-44" "unsaved 状態で /api/current-preset → axes:null"
|
|
3086
|
+
elif [ $_s44_result -eq 2 ]; then _record SKIP "S-44" "axes:null 確認 skip (apply failed)"
|
|
3087
|
+
else _record FAIL "S-44" "unsaved 状態で /api/current-preset → axes:null"
|
|
3088
|
+
fi
|
|
3089
|
+
|
|
3090
|
+
# S-45 / S-46 は task-76 で RETIRED (top view / edit view 廃止)
|
|
3091
|
+
_record SKIP "S-45" "RETIRED: top-view axes table (cp.axes/AXIS_LABELS_JA) removed in task-76 redesign"
|
|
3092
|
+
_record SKIP "S-46" "RETIRED: edit view / applyPresetMode removed in task-76 2-pane redesign"
|
|
3093
|
+
|
|
3094
|
+
# --- task-69 Step 3 新規 case (key parity) ---
|
|
3095
|
+
_s47_result=0
|
|
3096
|
+
_case_s47 "$_PORT" 2>/dev/null || _s47_result=$?
|
|
3097
|
+
if [ $_s47_result -eq 0 ]; then _record PASS "S-47" "GET /api/keys key set == yml top-level keys (full parity)"
|
|
3098
|
+
elif [ $_s47_result -eq 2 ]; then _record SKIP "S-47" "key parity (server 接続不可)"
|
|
3099
|
+
else _record FAIL "S-47" "GET /api/keys key set == yml top-level keys (full parity)"
|
|
3100
|
+
fi
|
|
3101
|
+
|
|
3102
|
+
# --- task-76 Step 1 新規 case (S-48 / S-49): diff Failed to fetch 修復 ---
|
|
3103
|
+
printf '\n%s\n' '--- task-76 Step 1 新規 case (S-48 / S-49) ---'
|
|
3104
|
+
|
|
3105
|
+
if _case_s48 "$_PORT" 2>/dev/null; then _record PASS "S-48" "GET /api/preset/:name/diff 正常 JSON + changed field 後方互換"
|
|
3106
|
+
else _record FAIL "S-48" "GET /api/preset/:name/diff 正常 JSON + changed field 後方互換"
|
|
3107
|
+
fi
|
|
3108
|
+
|
|
3109
|
+
if _case_s49 "$_PORT" 2>/dev/null; then _record PASS "S-49" "diff 連続/並列呼び出しの応答性 (Failed to fetch 回帰防止)"
|
|
3110
|
+
else _record FAIL "S-49" "diff 連続/並列呼び出しの応答性 (Failed to fetch 回帰防止)"
|
|
3111
|
+
fi
|
|
3112
|
+
|
|
3113
|
+
# --- task-76 Step 2 新規 case (S-50 / S-51): preset group + match_type custom ---
|
|
3114
|
+
printf '\n%s\n' '--- task-76 Step 2 新規 case (S-50 / S-51) ---'
|
|
3115
|
+
|
|
3116
|
+
if _case_s50 "$_PORT" 2>/dev/null; then _record PASS "S-50" "GET /api/presets 各 entry に group (POC/社内ツール/本番運用/その他) + 3 group 分布"
|
|
3117
|
+
else _record FAIL "S-50" "GET /api/presets 各 entry に group (POC/社内ツール/本番運用/その他) + 3 group 分布"
|
|
3118
|
+
fi
|
|
3119
|
+
|
|
3120
|
+
_s51_result=0
|
|
3121
|
+
_case_s51 "$_PORT" 2>/dev/null || _s51_result=$?
|
|
3122
|
+
if [ $_s51_result -eq 0 ]; then _record PASS "S-51" "GET /api/current-preset match_type が preset|custom (unsaved 廃止)"
|
|
3123
|
+
elif [ $_s51_result -eq 2 ]; then _record SKIP "S-51" "match_type custom 確認 skip (apply failed)"
|
|
3124
|
+
else _record FAIL "S-51" "GET /api/current-preset match_type が preset|custom (unsaved 廃止)"
|
|
3125
|
+
fi
|
|
3126
|
+
|
|
3127
|
+
# --- task-77 Step 5 新規 case (S-52 / S-53): git 統合 policy 実 key 化 ---
|
|
3128
|
+
printf '\n%s\n' '--- task-77 Step 5 新規 case (S-52 / S-53) ---'
|
|
3129
|
+
|
|
3130
|
+
if _case_s52 "$_PORT" 2>/dev/null; then _record PASS "S-52" "各 named preset の diff に mainline_integration_policy (§3.5 既定値 + 3 値分布)"
|
|
3131
|
+
else _record FAIL "S-52" "各 named preset の diff に mainline_integration_policy (§3.5 既定値 + 3 値分布)"
|
|
3132
|
+
fi
|
|
3133
|
+
|
|
3134
|
+
if _case_s53 "$_PORT" 2>/dev/null; then _record PASS "S-53" "GET /api/keys?category=Gate/Confidence に mainline_branch + mainline_integration_policy"
|
|
3135
|
+
else _record FAIL "S-53" "GET /api/keys?category=Gate/Confidence に mainline_branch + mainline_integration_policy"
|
|
3136
|
+
fi
|
|
3137
|
+
|
|
3138
|
+
# --- task-78 Step 1 新規 case (S-55 / S-56): metadata label_ja sidebar 表示 ---
|
|
3139
|
+
printf '\n%s\n' '--- task-78 Step 1 新規 case (S-55 / S-56) ---'
|
|
3140
|
+
|
|
3141
|
+
if _case_s55 "$_PORT" 2>/dev/null; then _record PASS "S-55" "GET /api/keys が label_ja field + 主要 label 値を返す"
|
|
3142
|
+
else _record FAIL "S-55" "GET /api/keys が label_ja field + 主要 label 値を返す"
|
|
3143
|
+
fi
|
|
3144
|
+
|
|
3145
|
+
if _case_s56 2>/dev/null; then _record PASS "S-56" "metadata table 5 列化 (全 key label_ja 非空、NF==5、key parity 不変)"
|
|
3146
|
+
else _record FAIL "S-56" "metadata table 5 列化 (全 key label_ja 非空、NF==5、key parity 不変)"
|
|
3147
|
+
fi
|
|
3148
|
+
|
|
3149
|
+
# --- task-78 Step 3 新規 case (S-57): 変更内容 右サイドバー id 契約 + render 関数 ---
|
|
3150
|
+
if _case_s57 2>/dev/null; then _record PASS "S-57" "変更内容 右サイドバー id 契約 (changes-sidebar/changes-list) + renderChangesSidebar/keyDisplayLabel 存在"
|
|
3151
|
+
else _record FAIL "S-57" "変更内容 右サイドバー id 契約 (changes-sidebar/changes-list) + renderChangesSidebar/keyDisplayLabel 存在"
|
|
3152
|
+
fi
|
|
3153
|
+
|
|
3154
|
+
_stop_shared_server
|
|
3155
|
+
fi
|
|
3156
|
+
else
|
|
3157
|
+
for cid in S-05 S-06 S-07 S-08 S-09 S-10 S-11 S-12 S-13 S-14 S-15 S-16 S-19 S-20 S-21 S-23 S-24 S-25 S-27 S-28 S-29 S-30 S-32 S-35 S-36 S-39 S-40 S-43 S-44 S-47 S-48 S-49 S-50 S-51 S-52 S-53 S-55; do
|
|
3158
|
+
_record SKIP "$cid" "node or hc-config-web-server.js not available"
|
|
3159
|
+
done
|
|
3160
|
+
# S-41 / S-42 は file-only (port 不要) なので node 不在でも実行。
|
|
3161
|
+
# S-37 / S-38 / S-45 / S-46 は task-76 で RETIRED (top/edit view 廃止)。
|
|
3162
|
+
_record SKIP "S-37" "RETIRED: top view (renderTop/banner) removed in task-76 2-pane redesign"
|
|
3163
|
+
_record SKIP "S-38" "RETIRED: edit-view state machine removed in task-76 2-pane redesign"
|
|
3164
|
+
_s41_result=0
|
|
3165
|
+
_case_s41 2>/dev/null || _s41_result=$?
|
|
3166
|
+
if [ $_s41_result -eq 0 ]; then _record PASS "S-41" "UI 3 file 絵文字 0 件 (絵文字不要 regression guard)"
|
|
3167
|
+
elif [ $_s41_result -eq 2 ]; then _record SKIP "S-41" "絵文字検出 (perl/python3 not available)"
|
|
3168
|
+
else _record FAIL "S-41" "UI 3 file 絵文字 0 件 (絵文字不要 regression guard)"
|
|
3169
|
+
fi
|
|
3170
|
+
if _case_s42 2>/dev/null; then _record PASS "S-42" "app.js DOM id 契約が index.html id= と整合 (DOM id cross-check)"
|
|
3171
|
+
else _record FAIL "S-42" "app.js DOM id 契約が index.html id= と整合 (DOM id cross-check)"
|
|
3172
|
+
fi
|
|
3173
|
+
_s54_result=0
|
|
3174
|
+
_case_s54 2>/dev/null || _s54_result=$?
|
|
3175
|
+
if [ $_s54_result -eq 0 ]; then _record PASS "S-54" "app.js ENUM_OPTIONS に mainline_integration_policy enum (3 値、select 表示)"
|
|
3176
|
+
elif [ $_s54_result -eq 2 ]; then _record SKIP "S-54" "app.js not found"
|
|
3177
|
+
else _record FAIL "S-54" "app.js ENUM_OPTIONS に mainline_integration_policy enum (3 値、select 表示)"
|
|
3178
|
+
fi
|
|
3179
|
+
_record SKIP "S-45" "RETIRED: top-view axes table (cp.axes/AXIS_LABELS_JA) removed in task-76 redesign"
|
|
3180
|
+
_record SKIP "S-46" "RETIRED: edit view / applyPresetMode removed in task-76 2-pane redesign"
|
|
3181
|
+
# S-56 / S-57 は file-only (port 不要) なので node 不在でも実行 (task-78 Step 1/3)
|
|
3182
|
+
if _case_s56 2>/dev/null; then _record PASS "S-56" "metadata table 5 列化 (全 key label_ja 非空、NF==5、key parity 不変)"
|
|
3183
|
+
else _record FAIL "S-56" "metadata table 5 列化 (全 key label_ja 非空、NF==5、key parity 不変)"
|
|
3184
|
+
fi
|
|
3185
|
+
if _case_s57 2>/dev/null; then _record PASS "S-57" "変更内容 右サイドバー id 契約 (changes-sidebar/changes-list) + renderChangesSidebar/keyDisplayLabel 存在"
|
|
3186
|
+
else _record FAIL "S-57" "変更内容 右サイドバー id 契約 (changes-sidebar/changes-list) + renderChangesSidebar/keyDisplayLabel 存在"
|
|
3187
|
+
fi
|
|
3188
|
+
fi
|
|
3189
|
+
|
|
3190
|
+
# --- legacy fallback + edge ---
|
|
3191
|
+
printf '\n%s\n' '--- legacy fallback + edge ---'
|
|
3192
|
+
|
|
3193
|
+
if _case_s17 2>/dev/null; then _record PASS "S-17" "HC_HC_CONFIG_TUI_LEGACY=true → TUI 経路 dispatcher 確認"
|
|
3194
|
+
else _record FAIL "S-17" "HC_HC_CONFIG_TUI_LEGACY=true → TUI 経路 dispatcher 確認"
|
|
3195
|
+
fi
|
|
3196
|
+
|
|
3197
|
+
_s18_result=0
|
|
3198
|
+
_case_s18 2>/dev/null || _s18_result=$?
|
|
3199
|
+
if [ $_s18_result -eq 0 ]; then _record PASS "S-18" "node 不在 → WARN stderr + TUI fallback"
|
|
3200
|
+
elif [ $_s18_result -eq 2 ]; then _record SKIP "S-18" "node 不在 (node が /usr/bin 以外にも存在しない)"
|
|
3201
|
+
else _record FAIL "S-18" "node 不在 → WARN stderr + TUI fallback"
|
|
3202
|
+
fi
|
|
3203
|
+
|
|
3204
|
+
# --- 手動 case コメント (Step 6 で実施) ---
|
|
3205
|
+
printf '\n%s\n' '--- manual cases (Step 6 で実施、以下はコメントのみ) ---'
|
|
3206
|
+
printf ' SKIP M-01: browser で preset 選択 → diff preview → checkbox toggle → Apply → history 追加 (目視)\n'
|
|
3207
|
+
printf ' SKIP M-02: category 選択 → key 一覧 → 編集 → Apply → 値反映確認 (目視)\n'
|
|
3208
|
+
printf ' SKIP M-03: Rollback ボタン → confirm dialog → 確認 → 元値復元 (目視)\n'
|
|
3209
|
+
printf ' SKIP M-04: Tailwind CDN offline で degradation 動作確認 (warning banner + legacy 案内)\n'
|
|
3210
|
+
SKIP=$((SKIP + 4))
|
|
3211
|
+
|
|
3212
|
+
# ============================================================
|
|
3213
|
+
# 集計
|
|
3214
|
+
# ============================================================
|
|
3215
|
+
|
|
3216
|
+
TOTAL=$((PASS + FAIL + SKIP))
|
|
3217
|
+
printf '\n%s %d/%d PASS, %d SKIP, %d FAIL ---\n' '--- Result:' "$PASS" "$TOTAL" "$SKIP" "$FAIL"
|
|
3218
|
+
|
|
3219
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
3220
|
+
printf 'FAILED cases:%s\n' "$FAILED_CASES"
|
|
3221
|
+
exit 1
|
|
3222
|
+
fi
|
|
3223
|
+
|
|
3224
|
+
exit 0
|