@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,2265 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# .claude/scripts/hc-config.sh — task-46 Phase 3 hc-config 対話的 yml editor
|
|
3
|
+
#
|
|
4
|
+
# 目的:
|
|
5
|
+
# harness-config.yml の全 key を対話 menu / CLI args で安全に編集する。
|
|
6
|
+
# user は yml を直接編集せず、本 script 経由で:
|
|
7
|
+
# - 全 key 一覧表示 (--list)
|
|
8
|
+
# - 値取得 (--get <key>、env override 優先)
|
|
9
|
+
# - 値設定 (--set <key>=<value>、型 validation + atomic + backup)
|
|
10
|
+
# - feature toggle 一括 on/off (--feature <name>=<true|false>)
|
|
11
|
+
# - default 復元 (--reset <key> / --reset-all)
|
|
12
|
+
# - 差分表示 (--diff)
|
|
13
|
+
# - validation only (--validate)
|
|
14
|
+
# - 対話 menu (引数なし起動)
|
|
15
|
+
#
|
|
16
|
+
# 設計:
|
|
17
|
+
# - shebang `#!/usr/bin/env bash` + `set -uo pipefail` (file-top errexit 外し、
|
|
18
|
+
# feedback_set_e_in_sourced_libs 規範遵守)
|
|
19
|
+
# - 既存 `.claude/hooks/lib/config-loader.sh` を source して env 解決ロジックを再利用
|
|
20
|
+
# - atomic 操作: .bak.<ts>.<pid> backup → .tmp.<pid> write → python yaml validate (stdin) → mv
|
|
21
|
+
# - 値型 validation: bool / int / float / array / string / path / csv
|
|
22
|
+
# - 対話 menu: stdin から `q` / `5` / `0` で即終了 (smoke Case 7 対応)
|
|
23
|
+
#
|
|
24
|
+
# 制約:
|
|
25
|
+
# - yml は flat key: value のみ対応 (config-loader.sh と同じ制約)
|
|
26
|
+
# - 値内コメント許容 (parse 時に strip)
|
|
27
|
+
# - tilde 展開 (~/foo) は config-loader.sh の HC_<NAME> env で運用、yml 値は raw 保持
|
|
28
|
+
#
|
|
29
|
+
# iter 2 fixes:
|
|
30
|
+
# CRIT F-01: yaml.safe_load を stdin 経由 (path quoting 同時解決)
|
|
31
|
+
# CRIT F-02: Case 8 で真の rollback path 検証 (smoke 側)
|
|
32
|
+
# HIGH H-01 test-auto: backup を ts+pid suffix で衝突回避
|
|
33
|
+
# HIGH H-02 test-auto: --get defaults fallback 実装
|
|
34
|
+
# HIGH H-01 code-rev: awk -v 廃止 (ENVIRON 経由で escape corruption 回避)
|
|
35
|
+
# HIGH H-02 code-rev: string/path に minimal sanity check (改行 / 制御文字 / # 行頭禁止)
|
|
36
|
+
# HIGH F-04 tdd: --config の REPO_ROOT 配下 / /tmp/ guard + HC_ALLOW_EXTERNAL_CONFIG bypass
|
|
37
|
+
# MED M-2 harness: _get_default の tmp cleanup を EXIT trap で保証
|
|
38
|
+
# MED M-01 security: --get / --reset の key format validation (regex)
|
|
39
|
+
# MED M-02 security: .bak retention policy (最新 N=10 件保持)
|
|
40
|
+
# MED M-03 test-auto: process group kill (smoke 側)
|
|
41
|
+
# MED M-04 test-auto: env priority case (smoke 側)
|
|
42
|
+
# MED M-01 code-rev: shellcheck SC2222 dead case 除去 (review_iteration_max を *_max にマージ)
|
|
43
|
+
# MED M-03 code-rev: _make_backup cp 失敗を伝播
|
|
44
|
+
# MED M-05 code-rev: review_iteration_max int range check (1..10)
|
|
45
|
+
#
|
|
46
|
+
# iter 3 fixes:
|
|
47
|
+
# CRIT R-01 (tdd-guide): _atomic_write の python3 detection を多版本 loop 化
|
|
48
|
+
# macOS Homebrew で `python3 -c "import yaml"` が subprocess で exit 1 になり
|
|
49
|
+
# yaml.safe_load path 到達不能 → fallback 強化と共に
|
|
50
|
+
# python3.13 → 3.12 → 3.11 → python3 の順で PyYAML 利用可能 version を探索。
|
|
51
|
+
# HIGH (test-automator 中間コロン): fallback で
|
|
52
|
+
# mid-value colon / 不完全 [bracket / 不完全 {brace を reject。
|
|
53
|
+
# HIGH (code-rev UTF-8 false-reject): `tr -d '\11\40-\176'` を廃止し
|
|
54
|
+
# bash pattern match で C0 / DEL のみ reject (UTF-8 multibyte 受理)。
|
|
55
|
+
# HIGH (pr-test mid-value # silent data loss): _yml_get_raw の `${val%%#*}` で
|
|
56
|
+
# mid-value `#` が read-back 時 truncation。書込み時点で reject + HC_ALLOW_HASH_IN_VALUE
|
|
57
|
+
# bypass。
|
|
58
|
+
# MED M-NEW-01 (code-rev) + L-03 (security): HC_BAK_RETENTION_COUNT validation
|
|
59
|
+
# (0 / 負値 / 非数値 で全 backup 削除 = rollback 不能を回避、default 10 に fallback)。
|
|
60
|
+
#
|
|
61
|
+
# iter 4 fixes (本 commit、HIGH 3 件単一根原因 closure):
|
|
62
|
+
# HIGH H3-01 (test-auto) + H-NEW-01 (pr-test) + H-NEW-01 (tdd-guide) 共通指摘:
|
|
63
|
+
# iter 3 で HC_ALLOW_HASH_IN_VALUE=1 bypass を導入したが、bypass で書き込み成功した値は
|
|
64
|
+
# `_yml_get_raw` の `val="${val%%#*}"` で `#` 以降が unconditional truncate されるため
|
|
65
|
+
# read-back で silent data loss。bypass 使用者の URL fragment / git refspec 用途で破綻。
|
|
66
|
+
# 解法 (採用案: write 時 quote):
|
|
67
|
+
# 1. `_yml_set` で bypass + value に `#` 含む場合、yml に double-quote 形式
|
|
68
|
+
# (`key: "foo#bar"`) で保存。内部 `"` は `\"` に escape、内部 `\` は `\\` に escape。
|
|
69
|
+
# 2. `_yml_get_raw` で bypass + 前後 `"` 検出時、行末 comment strip を skip し
|
|
70
|
+
# quote 解除 + `\"` unescape のみ実施。
|
|
71
|
+
# 3. bypass 時 stderr に notice を 1 行出力 (operator visibility 確保)。
|
|
72
|
+
# smoke Case 19b 更新 + Case 19c (round-trip 検証) + Case 19d (yml file format 検証) 追加。
|
|
73
|
+
#
|
|
74
|
+
# iter 5 fixes (code-reviewer iter 4 検出 CRIT 1 + HIGH 2 closure、データ corruption 3 surface 解消):
|
|
75
|
+
# CRIT C-01 (再書込時 awk comment-preservation 干渉):
|
|
76
|
+
# `_yml_set` の awk dispatch で matched-line も `match($0, /#.*$/)` で comment 抽出 + 新値後置
|
|
77
|
+
# する構造のため、quoted literal (`docs_approved_dir: "foo#bar"`) を再 `--set baz#qux` すると
|
|
78
|
+
# `#bar"` を comment 誤検知 → 新値に ` #bar"` 再付加 → `docs_approved_dir: "baz#qux" #bar"`
|
|
79
|
+
# corrupt 状態。matched-line では comment preservation を skip し新値で完全置換する。
|
|
80
|
+
# HIGH H-01 (`\\` unescape 欠落):
|
|
81
|
+
# `_yml_set` で `\` → `\\` escape するが、`_yml_get_raw` の unescape は `\"` → `"` のみ。
|
|
82
|
+
# bypass で `a\b#c` set → yml に `"a\\b#c"` → read で `a\\b#c` 返却 (literal 二重 backslash)。
|
|
83
|
+
# `\\` → `\` unescape を `\"` → `"` unescape より先に実施 (順序: 先に `\\`、後に `\"`)。
|
|
84
|
+
# HIGH H-02 (read 時 env asymmetry):
|
|
85
|
+
# `_yml_get_raw` の quote 解除条件が `HC_ALLOW_HASH_IN_VALUE=1 ∧ val が "..." 形式` の両 AND
|
|
86
|
+
# 条件のため、yml に `"foo#bar"` 保存後 bypass env 外して `--get` すると quote 解除 path に
|
|
87
|
+
# 入らず `val="${val%%#*}"` で `"foo` 返却 (broken partial)。`--list` / `--diff` / `--validate` 等
|
|
88
|
+
# が corrupt 値を表示。read 側の quote 解除条件から `HC_ALLOW_HASH_IN_VALUE=1` を削除し、
|
|
89
|
+
# 前後 `"..."` 形式検出だけで quote 解除 + comment-strip-skip を発動。bypass env は write 側のみ
|
|
90
|
+
# (mid-`#` reject の bypass 用途) で gate する非対称設計に変更。
|
|
91
|
+
# smoke Case 19e (再書込 round-trip) + Case 19f (backslash round-trip + non-bypass read) 追加。
|
|
92
|
+
#
|
|
93
|
+
# Step 6 refactoring (task-46 Step 6): 全関数 < 50 LOC に分割
|
|
94
|
+
# 対象 6 関数を helper 分割:
|
|
95
|
+
# - _validate_string_sanity: _validate_str_newline_ctrl + _validate_str_leading_chars
|
|
96
|
+
# + _validate_str_mid_hash に 3 分割
|
|
97
|
+
# - _validate_value: _validate_bool + _validate_int + _validate_float に 3 分割
|
|
98
|
+
# - _atomic_write: _atomic_find_python_yaml + _atomic_yaml_validate_fallback に 2 分割
|
|
99
|
+
# - _yml_set: _yml_set_escape_value に 1 helper 抽出
|
|
100
|
+
# - cmd_interactive: _menu_opt_edit_key + _menu_opt_feature + _menu_opt_reviewer に 3 分割
|
|
101
|
+
# - main: _main_require_arg + _main_dispatch に 2 helper 抽出、inline config parse
|
|
102
|
+
#
|
|
103
|
+
# 起源:
|
|
104
|
+
# task-46 Step 2 (TDD GREEN) → iter 2 fix → iter 3 fix → iter 4 fix → iter 5 fix
|
|
105
|
+
# → Step 6 refactoring (behavior-preserving function split)
|
|
106
|
+
# 設計 draft: docs/draft/config-yml-phase3-hc-config-script.md
|
|
107
|
+
# smoke: .claude/tests/hc-config-script-smoke.sh (21 cases iter 5)
|
|
108
|
+
|
|
109
|
+
set -uo pipefail
|
|
110
|
+
|
|
111
|
+
# === 設定 / 解決 ===
|
|
112
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
113
|
+
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
|
114
|
+
DEFAULT_CONFIG="${REPO_ROOT}/.claude/harness-config.yml"
|
|
115
|
+
|
|
116
|
+
# task-48 Step 3: key metadata lib を source (description / effect / category)。
|
|
117
|
+
# 不在環境でも fallback で動くよう存在確認後に source、引き helper も guard する。
|
|
118
|
+
HC_METADATA_LIB="${SCRIPT_DIR}/lib/hc-config-metadata.sh"
|
|
119
|
+
if [ -f "$HC_METADATA_LIB" ]; then
|
|
120
|
+
# shellcheck disable=SC1090
|
|
121
|
+
source "$HC_METADATA_LIB"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# task-70 Step 7 (LOW-1 DRY): enforcement matrix parse lib を source。
|
|
125
|
+
# 不在でも壊れないよう存在確認後に source する。
|
|
126
|
+
HC_EM_PARSE_LIB="${SCRIPT_DIR}/lib/enforcement-matrix-parse.sh"
|
|
127
|
+
if [ -f "$HC_EM_PARSE_LIB" ]; then
|
|
128
|
+
# shellcheck disable=SC1090
|
|
129
|
+
source "$HC_EM_PARSE_LIB"
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# metadata 引き helper (lib 不在でも壊れないよう command -v guard)
|
|
133
|
+
# $1: key → description (不在なら空)
|
|
134
|
+
_meta_desc() {
|
|
135
|
+
if command -v hc_metadata_description >/dev/null 2>&1; then
|
|
136
|
+
hc_metadata_description "$1" 2>/dev/null || true
|
|
137
|
+
fi
|
|
138
|
+
}
|
|
139
|
+
# $1: key → effect (不在なら空)
|
|
140
|
+
_meta_effect() {
|
|
141
|
+
if command -v hc_metadata_effect >/dev/null 2>&1; then
|
|
142
|
+
hc_metadata_effect "$1" 2>/dev/null || true
|
|
143
|
+
fi
|
|
144
|
+
}
|
|
145
|
+
# $1: key → category (不在なら空)
|
|
146
|
+
_meta_category() {
|
|
147
|
+
if command -v hc_metadata_category >/dev/null 2>&1; then
|
|
148
|
+
hc_metadata_category "$1" 2>/dev/null || true
|
|
149
|
+
fi
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# category 一覧文字列を返す helper (SSoT — cmd_list / _tui_order_keys_by_category の両所から参照)
|
|
153
|
+
# task-69 Step 8 L2: categories string が 794 (cmd_list) と 1390 (_tui_order_keys_by_category) の
|
|
154
|
+
# 2 箇所に重複していたため、単一 helper に抽出。将来 category 追加時はここ 1 箇所のみ修正。
|
|
155
|
+
# stdout: スペース区切りの category 名列 (word-split で for cat in $(...); do が使える形式)
|
|
156
|
+
_hc_categories() {
|
|
157
|
+
printf '%s' "保護パス ファイル配置 state_dir Gate/Confidence feature_toggle reviewer_control harness_meta"
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# --config <path> 引数で test isolation 対応 (smoke Case 3-6)
|
|
161
|
+
CONFIG_PATH=""
|
|
162
|
+
|
|
163
|
+
# .bak retention (最新 N 件保持、それより古い bak は自動削除)
|
|
164
|
+
# iter 3 MED M-NEW-01 (code-rev) + L-03 (security):
|
|
165
|
+
# HC_BAK_RETENTION_COUNT=0 / 負値 / 非数値 で全 backup 削除 (rollback 不能) を防止。
|
|
166
|
+
# positive int (>= 1) のみ受理、それ以外は default 10 に fallback + stderr warn。
|
|
167
|
+
BAK_RETENTION_COUNT="${HC_BAK_RETENTION_COUNT:-10}"
|
|
168
|
+
if ! [[ "$BAK_RETENTION_COUNT" =~ ^[1-9][0-9]*$ ]]; then
|
|
169
|
+
printf 'hc-config: invalid HC_BAK_RETENTION_COUNT: %q (must be positive int >= 1), using default 10\n' \
|
|
170
|
+
"$BAK_RETENTION_COUNT" >&2
|
|
171
|
+
BAK_RETENTION_COUNT=10
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# === ユーティリティ ===
|
|
175
|
+
|
|
176
|
+
# stderr 出力
|
|
177
|
+
_err() {
|
|
178
|
+
printf 'hc-config: %s\n' "$*" >&2
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# stdout 出力
|
|
182
|
+
_out() {
|
|
183
|
+
printf '%s\n' "$*"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# key 名の format validation (security M-01)
|
|
187
|
+
# 受理: ^[a-z_][a-zA-Z0-9_]*$
|
|
188
|
+
_validate_key_format() {
|
|
189
|
+
local key="$1"
|
|
190
|
+
if [[ ! "$key" =~ ^[a-z_][a-zA-Z0-9_]*$ ]]; then
|
|
191
|
+
_err "invalid key format: '${key}' (must match ^[a-z_][a-zA-Z0-9_]*\$)"
|
|
192
|
+
return 1
|
|
193
|
+
fi
|
|
194
|
+
return 0
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
# 改行 / NUL / C0 制御文字 / DEL を reject (string sanity helper 1/3)
|
|
198
|
+
# iter 3 HIGH (code-rev UTF-8 false-reject) fix:
|
|
199
|
+
# bash pattern match で C0 (\x01-\x08, \x0b-\x1f) + DEL (\x7f) のみ reject、
|
|
200
|
+
# \x80-\xFF (UTF-8 multibyte) は touch しない。
|
|
201
|
+
_validate_str_newline_ctrl() {
|
|
202
|
+
local key="$1"
|
|
203
|
+
local val="$2"
|
|
204
|
+
# 改行 (LF / CR) → yml 構造破壊
|
|
205
|
+
case "$val" in
|
|
206
|
+
*$'\n'*|*$'\r'*)
|
|
207
|
+
_err "invalid value for ${key}: contains newline/CR"
|
|
208
|
+
return 1
|
|
209
|
+
;;
|
|
210
|
+
esac
|
|
211
|
+
# C0 制御文字 / DEL を reject
|
|
212
|
+
# 注意: bash で `$'\x00'` は empty string になり `*$'\x00'*` = `**` で全 match 誤爆するため、
|
|
213
|
+
# NUL pattern は意図的に除外 (どのみち bash variable に NUL は格納不可)。
|
|
214
|
+
case "$val" in
|
|
215
|
+
*$'\x01'*|*$'\x02'*|*$'\x03'*|*$'\x04'*|*$'\x05'*|*$'\x06'*|*$'\x07'*|*$'\x08'* \
|
|
216
|
+
|*$'\x0b'*|*$'\x0c'*|*$'\x0e'*|*$'\x0f'*|*$'\x10'*|*$'\x11'*|*$'\x12'*|*$'\x13'* \
|
|
217
|
+
|*$'\x14'*|*$'\x15'*|*$'\x16'*|*$'\x17'*|*$'\x18'*|*$'\x19'*|*$'\x1a'*|*$'\x1b'* \
|
|
218
|
+
|*$'\x1c'*|*$'\x1d'*|*$'\x1e'*|*$'\x1f'*|*$'\x7f'*)
|
|
219
|
+
_err "invalid value for ${key}: contains control characters"
|
|
220
|
+
return 1
|
|
221
|
+
;;
|
|
222
|
+
esac
|
|
223
|
+
return 0
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# 行頭 # / 行頭 : を reject (string sanity helper 2/3)
|
|
227
|
+
_validate_str_leading_chars() {
|
|
228
|
+
local key="$1"
|
|
229
|
+
local val="$2"
|
|
230
|
+
# 行頭 # (yaml comment と混同) — 値全体が # から始まる場合のみ reject
|
|
231
|
+
case "$val" in
|
|
232
|
+
\#*)
|
|
233
|
+
_err "invalid value for ${key}: starts with '#' (yaml comment confusion)"
|
|
234
|
+
return 1
|
|
235
|
+
;;
|
|
236
|
+
esac
|
|
237
|
+
# yaml-confusing leading `:` (例: ": unexpected" は yml syntax を破壊)
|
|
238
|
+
case "$val" in
|
|
239
|
+
:*)
|
|
240
|
+
_err "invalid value for ${key}: starts with ':' (yaml syntax confusion)"
|
|
241
|
+
return 1
|
|
242
|
+
;;
|
|
243
|
+
esac
|
|
244
|
+
return 0
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# mid-value # を reject (string sanity helper 3/3)
|
|
248
|
+
# iter 3 HIGH (pr-test mid-value # silent data loss) fix:
|
|
249
|
+
# mid-value `#` は _yml_get_raw の `val="${val%%#*}"` で read-back 時に truncation。
|
|
250
|
+
# URL #fragment 等の正規ユースケース向けに HC_ALLOW_HASH_IN_VALUE=1 bypass。
|
|
251
|
+
_validate_str_mid_hash() {
|
|
252
|
+
local key="$1"
|
|
253
|
+
local val="$2"
|
|
254
|
+
if [ "${HC_ALLOW_HASH_IN_VALUE:-0}" != "1" ]; then
|
|
255
|
+
case "$val" in
|
|
256
|
+
*\#*)
|
|
257
|
+
_err "invalid value for ${key}: contains '#' (yaml inline comment confusion, would be silently truncated on read-back)"
|
|
258
|
+
_err " use HC_ALLOW_HASH_IN_VALUE=1 to bypass (URL fragment etc.)"
|
|
259
|
+
return 1
|
|
260
|
+
;;
|
|
261
|
+
esac
|
|
262
|
+
fi
|
|
263
|
+
return 0
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# string / path 値の minimal sanity check (HIGH H-02 code-rev + iter 3 fixes)
|
|
267
|
+
# 改行 / NUL / 制御文字 / 行頭 # / 行頭 : / mid-value # を reject、UTF-8 multibyte は受理
|
|
268
|
+
_validate_string_sanity() {
|
|
269
|
+
local key="$1"
|
|
270
|
+
local val="$2"
|
|
271
|
+
_validate_str_newline_ctrl "$key" "$val" || return 1
|
|
272
|
+
_validate_str_leading_chars "$key" "$val" || return 1
|
|
273
|
+
_validate_str_mid_hash "$key" "$val" || return 1
|
|
274
|
+
return 0
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# yml から key の raw 値を取得 (コメント strip / quote strip / 配列 inline 保持)
|
|
278
|
+
# $1: yml path
|
|
279
|
+
# $2: key
|
|
280
|
+
# 戻り: stdout に値 (key 不在なら空 + return 1)
|
|
281
|
+
#
|
|
282
|
+
# iter 4 fix (HIGH 3 件単一根原因 closure、HC_ALLOW_HASH_IN_VALUE=1 round-trip 整合性):
|
|
283
|
+
# bypass 時に `_yml_set` が double-quote escape で value を保存するため、
|
|
284
|
+
# read 側も double-quoted literal を検出して quote 解除 + comment strip skip する。
|
|
285
|
+
# 通常 value (quote なし) は従来通り `${val%%#*}` で行末コメント strip。
|
|
286
|
+
#
|
|
287
|
+
# 検出条件 (両 AND):
|
|
288
|
+
# 1. HC_ALLOW_HASH_IN_VALUE=1
|
|
289
|
+
# 2. 前後 trim 後の val が `"` で始まり `"` で終わる (length >= 2)
|
|
290
|
+
# → comment strip skip + leading/trailing quote 除去 + `\"` → `"` unescape
|
|
291
|
+
_yml_get_raw() {
|
|
292
|
+
local yml="$1"
|
|
293
|
+
local key="$2"
|
|
294
|
+
local line val
|
|
295
|
+
line=$(grep -E "^${key}:" "$yml" 2>/dev/null | head -n 1) || true
|
|
296
|
+
if [ -z "$line" ]; then
|
|
297
|
+
return 1
|
|
298
|
+
fi
|
|
299
|
+
val="${line#"${key}":}"
|
|
300
|
+
# 前後空白 trim (quote 判定の前に実行、comment strip より前)
|
|
301
|
+
val="${val#"${val%%[![:space:]]*}"}"
|
|
302
|
+
val="${val%"${val##*[![:space:]]}"}"
|
|
303
|
+
|
|
304
|
+
# iter 5 fix (HIGH H-01 + H-02 closure):
|
|
305
|
+
# - HIGH H-02 解消: quote 解除条件から HC_ALLOW_HASH_IN_VALUE=1 を削除し、
|
|
306
|
+
# 前後 `"..."` 形式検出だけで quote 解除 + comment-strip-skip を発動。
|
|
307
|
+
# - HIGH H-01 解消: `\\` → `\` unescape を `\"` → `"` unescape より先に実施。
|
|
308
|
+
# (round-trip 整合性: `_yml_set` が `"foo#bar"` で書き込んだ値を `foo#bar` で復元)
|
|
309
|
+
if [ "${#val}" -ge 2 ] \
|
|
310
|
+
&& [ "${val:0:1}" = '"' ] \
|
|
311
|
+
&& [ "${val: -1}" = '"' ]; then
|
|
312
|
+
# leading/trailing double-quote を 1 文字ずつ除去
|
|
313
|
+
val="${val#\"}"
|
|
314
|
+
val="${val%\"}"
|
|
315
|
+
# unescape 順序: `\\` → `\` を先 (二段解釈防止)、`\"` → `"` を後
|
|
316
|
+
val="${val//\\\\/\\}"
|
|
317
|
+
val="${val//\\\"/\"}"
|
|
318
|
+
printf '%s' "$val"
|
|
319
|
+
return 0
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
# 通常 path: 行末コメント (#...) を strip
|
|
323
|
+
# (HC_ALLOW_HASH_IN_VALUE=1 でも non-quoted な既存 value (mid-`#` なし) は本 path)
|
|
324
|
+
val="${val%%#*}"
|
|
325
|
+
# comment strip 後の trailing 空白を再 trim
|
|
326
|
+
val="${val%"${val##*[![:space:]]}"}"
|
|
327
|
+
# 外側 quote strip (legacy single-quote 含む)
|
|
328
|
+
if [ "${#val}" -ge 2 ]; then
|
|
329
|
+
case "$val" in
|
|
330
|
+
\"*\") val="${val#\"}"; val="${val%\"}" ;;
|
|
331
|
+
\'*\') val="${val#\'}"; val="${val%\'}" ;;
|
|
332
|
+
esac
|
|
333
|
+
fi
|
|
334
|
+
printf '%s' "$val"
|
|
335
|
+
return 0
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
# yml から全 top-level key を抽出 (出現順保持)
|
|
339
|
+
# $1: yml path
|
|
340
|
+
_yml_list_keys() {
|
|
341
|
+
local yml="$1"
|
|
342
|
+
grep -E "^[a-z_][a-zA-Z0-9_]*:" "$yml" 2>/dev/null | sed -E 's/:.*$//' || true
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
# key → uppercase env name 変換 (config-loader.sh 同期)
|
|
346
|
+
_key_to_env() {
|
|
347
|
+
printf '%s' "$1" | tr '[:lower:]' '[:upper:]' | tr -c '[:alnum:]_' '_' | sed 's/_$//'
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
# 型推論 (key 名 + 値から推測)
|
|
351
|
+
# $1: key, $2: value
|
|
352
|
+
# 戻り: bool / int / float / array / path / string
|
|
353
|
+
_infer_type() {
|
|
354
|
+
local key="$1"
|
|
355
|
+
local val="$2"
|
|
356
|
+
# 1. 配列 [a, b, c]
|
|
357
|
+
case "$val" in
|
|
358
|
+
\[*\]) printf 'array'; return ;;
|
|
359
|
+
esac
|
|
360
|
+
# 2. bool (true / false)
|
|
361
|
+
case "$val" in
|
|
362
|
+
true|True|TRUE|false|False|FALSE) printf 'bool'; return ;;
|
|
363
|
+
esac
|
|
364
|
+
# 3. key 名から推論 (review_iteration_max を *_max にマージし SC2222 dead case 解消)
|
|
365
|
+
case "$key" in
|
|
366
|
+
*_threshold|*_ratio) printf 'float'; return ;;
|
|
367
|
+
*_max|*_count|*_days|*_hours|*_sec|*_limit|*_min_*|*_max_*) printf 'int'; return ;;
|
|
368
|
+
*_enabled|*_required) printf 'bool'; return ;;
|
|
369
|
+
*_path|*_dir|*_root|*_sound) printf 'path'; return ;;
|
|
370
|
+
esac
|
|
371
|
+
# 4. 値から推論
|
|
372
|
+
if [[ "$val" =~ ^[0-9]+$ ]]; then
|
|
373
|
+
printf 'int'; return
|
|
374
|
+
fi
|
|
375
|
+
if [[ "$val" =~ ^[0-9]+\.[0-9]+$ ]]; then
|
|
376
|
+
printf 'float'; return
|
|
377
|
+
fi
|
|
378
|
+
printf 'string'
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
# bool 型 validation (_validate_value helper 1/3)
|
|
382
|
+
_validate_bool() {
|
|
383
|
+
local key="$1"
|
|
384
|
+
local val="$2"
|
|
385
|
+
case "$val" in
|
|
386
|
+
true|True|TRUE|false|False|FALSE) return 0 ;;
|
|
387
|
+
*)
|
|
388
|
+
_err "invalid value for ${key} (expected: bool, got: '${val}')"
|
|
389
|
+
return 1
|
|
390
|
+
;;
|
|
391
|
+
esac
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
# int 型 validation (_validate_value helper 2/3)
|
|
395
|
+
_validate_int() {
|
|
396
|
+
local key="$1"
|
|
397
|
+
local val="$2"
|
|
398
|
+
if [[ "$val" =~ ^[0-9]+$ ]]; then
|
|
399
|
+
# review_iteration_max は 1..10 の range
|
|
400
|
+
case "$key" in
|
|
401
|
+
review_iteration_max)
|
|
402
|
+
if [ "$val" -lt 1 ] || [ "$val" -gt 10 ]; then
|
|
403
|
+
_err "invalid value for ${key} (expected: int 1-10, got: '${val}')"
|
|
404
|
+
return 1
|
|
405
|
+
fi
|
|
406
|
+
;;
|
|
407
|
+
esac
|
|
408
|
+
return 0
|
|
409
|
+
fi
|
|
410
|
+
_err "invalid value for ${key} (expected: int, got: '${val}')"
|
|
411
|
+
return 1
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
# float 型 validation (_validate_value helper 3/3)
|
|
415
|
+
_validate_float() {
|
|
416
|
+
local key="$1"
|
|
417
|
+
local val="$2"
|
|
418
|
+
if [[ "$val" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
|
|
419
|
+
# confidence_threshold / context_budget_threshold は 0.0-1.0
|
|
420
|
+
case "$key" in
|
|
421
|
+
confidence_threshold|context_budget_threshold|*_ratio)
|
|
422
|
+
if awk -v v="$val" 'BEGIN { if (v < 0.0 || v > 1.0) exit 1; exit 0 }'; then
|
|
423
|
+
return 0
|
|
424
|
+
else
|
|
425
|
+
_err "invalid value for ${key} (expected: float 0.0-1.0, got: '${val}')"
|
|
426
|
+
return 1
|
|
427
|
+
fi
|
|
428
|
+
;;
|
|
429
|
+
*)
|
|
430
|
+
return 0
|
|
431
|
+
;;
|
|
432
|
+
esac
|
|
433
|
+
fi
|
|
434
|
+
_err "invalid value for ${key} (expected: float, got: '${val}')"
|
|
435
|
+
return 1
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
# 値型 validation
|
|
439
|
+
# $1: key, $2: type, $3: value
|
|
440
|
+
# 戻り: 0 = valid / 1 = invalid (stderr に error)
|
|
441
|
+
_validate_value() {
|
|
442
|
+
local key="$1"
|
|
443
|
+
local type="$2"
|
|
444
|
+
local val="$3"
|
|
445
|
+
case "$type" in
|
|
446
|
+
bool)
|
|
447
|
+
_validate_bool "$key" "$val"
|
|
448
|
+
;;
|
|
449
|
+
int)
|
|
450
|
+
_validate_int "$key" "$val"
|
|
451
|
+
;;
|
|
452
|
+
float)
|
|
453
|
+
_validate_float "$key" "$val"
|
|
454
|
+
;;
|
|
455
|
+
array)
|
|
456
|
+
# [a, b, c] 形式のみ受け入れ
|
|
457
|
+
case "$val" in
|
|
458
|
+
\[*\]) return 0 ;;
|
|
459
|
+
*)
|
|
460
|
+
_err "invalid value for ${key} (expected: array '[a, b, c]', got: '${val}')"
|
|
461
|
+
return 1
|
|
462
|
+
;;
|
|
463
|
+
esac
|
|
464
|
+
;;
|
|
465
|
+
path|string)
|
|
466
|
+
# 空文字は path/string では許容 (空 path は default 復元等)
|
|
467
|
+
if [ -z "$val" ]; then
|
|
468
|
+
return 0
|
|
469
|
+
fi
|
|
470
|
+
_validate_string_sanity "$key" "$val" || return 1
|
|
471
|
+
# MEDIUM-1: default_preset は allowed-value 4 種のみ (string sanity 通過後に追加 check)
|
|
472
|
+
if [ "$key" = "default_preset" ]; then
|
|
473
|
+
_validate_default_preset "$val" || return 1
|
|
474
|
+
fi
|
|
475
|
+
# task-77 Step 1: mainline_integration_policy は enum 3 値のみ
|
|
476
|
+
if [ "$key" = "mainline_integration_policy" ]; then
|
|
477
|
+
_validate_mainline_integration_policy "$val" || return 1
|
|
478
|
+
fi
|
|
479
|
+
# task-77 Step 1: mainline_branch は charset 制限 (空白含む値を reject)
|
|
480
|
+
if [ "$key" = "mainline_branch" ]; then
|
|
481
|
+
_validate_mainline_branch "$val" || return 1
|
|
482
|
+
fi
|
|
483
|
+
;;
|
|
484
|
+
*)
|
|
485
|
+
# 不明な型でも sanity check は通す
|
|
486
|
+
_validate_string_sanity "$key" "$val"
|
|
487
|
+
;;
|
|
488
|
+
esac
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
# bak retention policy: 古い bak (最新 N 件超過) を削除 (security M-02)
|
|
492
|
+
# $1: yml path
|
|
493
|
+
_prune_old_backups() {
|
|
494
|
+
local yml="$1"
|
|
495
|
+
local base
|
|
496
|
+
base=$(basename "$yml")
|
|
497
|
+
local dir
|
|
498
|
+
dir=$(dirname "$yml")
|
|
499
|
+
# ls -t で新しい順、 N+1 件目以降を削除
|
|
500
|
+
# shellcheck disable=SC2012
|
|
501
|
+
ls -t "${dir}/${base}.bak."* 2>/dev/null \
|
|
502
|
+
| awk -v n="$BAK_RETENTION_COUNT" 'NR > n' \
|
|
503
|
+
| while IFS= read -r old; do
|
|
504
|
+
rm -f "$old"
|
|
505
|
+
done
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
# yml backup 作成 (HIGH H-01 test-auto: ts + pid suffix で衝突回避)
|
|
509
|
+
# $1: yml path
|
|
510
|
+
# 戻り: stdout に backup path, 失敗時 exit code != 0
|
|
511
|
+
_make_backup() {
|
|
512
|
+
local yml="$1"
|
|
513
|
+
local ts
|
|
514
|
+
ts=$(date +%Y%m%d-%H%M%S)
|
|
515
|
+
local bak="${yml}.bak.${ts}.$$"
|
|
516
|
+
# 同一 PID 内で立て続けに backup する場合の衝突回避 (sub-second collision):
|
|
517
|
+
# 既存なら nanosecond / counter suffix を試す
|
|
518
|
+
if [ -e "$bak" ]; then
|
|
519
|
+
local n=1
|
|
520
|
+
while [ -e "${bak}.${n}" ] && [ "$n" -lt 1000 ]; do
|
|
521
|
+
n=$((n + 1))
|
|
522
|
+
done
|
|
523
|
+
bak="${bak}.${n}"
|
|
524
|
+
fi
|
|
525
|
+
if ! cp "$yml" "$bak"; then
|
|
526
|
+
_err "backup failed: cp '${yml}' '${bak}'"
|
|
527
|
+
return 1
|
|
528
|
+
fi
|
|
529
|
+
# 古い backup を retention policy で prune (失敗は silent、本処理を block しない)
|
|
530
|
+
_prune_old_backups "$yml" 2>/dev/null || true
|
|
531
|
+
printf '%s' "$bak"
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
# PyYAML 利用可能な python3 実行可能ファイルを検出して stdout に出力
|
|
535
|
+
# 見つからない場合は空文字 + return 1
|
|
536
|
+
# _atomic_write helper 1/2
|
|
537
|
+
_atomic_find_python_yaml() {
|
|
538
|
+
local py
|
|
539
|
+
for py in python3.13 python3.12 python3.11 python3; do
|
|
540
|
+
if command -v "$py" >/dev/null 2>&1 \
|
|
541
|
+
&& "$py" -c "import yaml" >/dev/null 2>&1; then
|
|
542
|
+
printf '%s' "$py"
|
|
543
|
+
return 0
|
|
544
|
+
fi
|
|
545
|
+
done
|
|
546
|
+
return 1
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
# PyYAML 不在環境向け軽量 yaml 構造チェック (fallback)
|
|
550
|
+
# $1: orig yml path, $2: tmp yml path
|
|
551
|
+
# 戻り: 0 = OK / 1 = 構造異常 (tmp を呼び出し元で rm すること)
|
|
552
|
+
# _atomic_write helper 2/2
|
|
553
|
+
_atomic_yaml_validate_fallback() {
|
|
554
|
+
local yml="$1"
|
|
555
|
+
local tmp="$2"
|
|
556
|
+
local orig_count new_count
|
|
557
|
+
orig_count=$(grep -cE "^[a-z_][a-zA-Z0-9_]*:" "$yml" 2>/dev/null || printf '0')
|
|
558
|
+
new_count=$(grep -cE "^[a-z_][a-zA-Z0-9_]*:" "$tmp" 2>/dev/null || printf '0')
|
|
559
|
+
if [ "$new_count" -lt "$orig_count" ]; then
|
|
560
|
+
_err "yaml structure check: key count decreased (${orig_count} → ${new_count}), rolling back"
|
|
561
|
+
return 1
|
|
562
|
+
fi
|
|
563
|
+
# 行頭 `:` のみで始まる行 (= 値が ': xxx' で yml が壊れている兆候) を reject
|
|
564
|
+
if grep -qE "^[a-z_][a-zA-Z0-9_]*: : " "$tmp" 2>/dev/null; then
|
|
565
|
+
_err "yaml structure check: detected ': :' pattern (corrupted value), rolling back"
|
|
566
|
+
return 1
|
|
567
|
+
fi
|
|
568
|
+
# iter 3 HIGH: mid-value `: <text>` を reject
|
|
569
|
+
if grep -qE '^[a-z_][a-zA-Z0-9_]*:[[:space:]]+[^"'"'"'#[{[:space:]][^"'"'"'#]*:[[:space:]]' "$tmp" 2>/dev/null; then
|
|
570
|
+
_err "yaml structure check: mid-value colon detected (structural corruption), rolling back"
|
|
571
|
+
return 1
|
|
572
|
+
fi
|
|
573
|
+
# iter 3 HIGH: unclosed array bracket → reject
|
|
574
|
+
if grep -qE '^[a-z_][a-zA-Z0-9_]*:[[:space:]]*\[[^]]*$' "$tmp" 2>/dev/null; then
|
|
575
|
+
_err "yaml structure check: unclosed array bracket detected, rolling back"
|
|
576
|
+
return 1
|
|
577
|
+
fi
|
|
578
|
+
# iter 3 HIGH: unclosed object brace → reject
|
|
579
|
+
if grep -qE '^[a-z_][a-zA-Z0-9_]*:[[:space:]]*\{[^}]*$' "$tmp" 2>/dev/null; then
|
|
580
|
+
_err "yaml structure check: unclosed object brace detected, rolling back"
|
|
581
|
+
return 1
|
|
582
|
+
fi
|
|
583
|
+
return 0
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
# atomic yml 上書き
|
|
587
|
+
# $1: yml path
|
|
588
|
+
# $2: new content
|
|
589
|
+
#
|
|
590
|
+
# iter 2 fix: CRIT F-01 = yaml.safe_load を stdin 経由 (path quoting + alias 不在 python 対応)
|
|
591
|
+
#
|
|
592
|
+
# iter 3 fixes:
|
|
593
|
+
# CRIT R-01 (tdd-guide): python3 detection を多版本 loop 化
|
|
594
|
+
# HIGH (test-automator 中間コロン): fallback 強化
|
|
595
|
+
_atomic_write() {
|
|
596
|
+
local yml="$1"
|
|
597
|
+
local new_content="$2"
|
|
598
|
+
local tmp="${yml}.tmp.$$"
|
|
599
|
+
|
|
600
|
+
printf '%s' "$new_content" > "$tmp"
|
|
601
|
+
|
|
602
|
+
local py_with_yaml
|
|
603
|
+
if py_with_yaml=$(_atomic_find_python_yaml); then
|
|
604
|
+
# CRIT F-01 fix: stdin 経由で path quoting 不要、alias 不在 python でも安全
|
|
605
|
+
if ! "$py_with_yaml" -c "import yaml, sys; yaml.safe_load(sys.stdin.read())" < "$tmp" 2>/dev/null; then
|
|
606
|
+
_err "yaml syntax invalid after edit, rolling back"
|
|
607
|
+
rm -f "$tmp"
|
|
608
|
+
return 1
|
|
609
|
+
fi
|
|
610
|
+
else
|
|
611
|
+
# Fallback: 軽量 yaml syntax check (PyYAML 不在環境用)
|
|
612
|
+
if ! _atomic_yaml_validate_fallback "$yml" "$tmp"; then
|
|
613
|
+
rm -f "$tmp"
|
|
614
|
+
return 1
|
|
615
|
+
fi
|
|
616
|
+
fi
|
|
617
|
+
|
|
618
|
+
mv "$tmp" "$yml"
|
|
619
|
+
return 0
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
# HC_ALLOW_HASH_IN_VALUE=1 + value に `#` 含む場合の double-quote escape
|
|
623
|
+
# $1: new_val (raw)
|
|
624
|
+
# stdout: yml 書き込み用 write_val (escaped 済)
|
|
625
|
+
# 副作用: bypass 時 stderr に notice 出力
|
|
626
|
+
# _yml_set helper
|
|
627
|
+
_yml_set_escape_value() {
|
|
628
|
+
local new_val="$1"
|
|
629
|
+
if [ "${HC_ALLOW_HASH_IN_VALUE:-0}" = "1" ]; then
|
|
630
|
+
case "$new_val" in
|
|
631
|
+
*\#*)
|
|
632
|
+
# 内部 `"` は `\"` に escape、内部 `\` も `\\` に escape して二重 escape 衝突を回避
|
|
633
|
+
local escaped="${new_val//\\/\\\\}"
|
|
634
|
+
escaped="${escaped//\"/\\\"}"
|
|
635
|
+
printf '%s' "\"${escaped}\""
|
|
636
|
+
_err "note: HC_ALLOW_HASH_IN_VALUE=1 で # 含む値を yml に double-quote 保存します (round-trip 整合性確保のため)"
|
|
637
|
+
return 0
|
|
638
|
+
;;
|
|
639
|
+
esac
|
|
640
|
+
fi
|
|
641
|
+
printf '%s' "$new_val"
|
|
642
|
+
return 0
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
# yml の特定 key を新値で書き換える
|
|
646
|
+
# $1: yml path
|
|
647
|
+
# $2: key
|
|
648
|
+
# $3: new value (raw、yml 構文として valid な形)
|
|
649
|
+
# 戻り: 0 = success / 1 = key 不在 or atomic fail
|
|
650
|
+
#
|
|
651
|
+
# iter 4 fix (HC_ALLOW_HASH_IN_VALUE=1 round-trip 整合性):
|
|
652
|
+
# bypass 時 + value に `#` を含む場合は _yml_set_escape_value で double-quote escape して保存。
|
|
653
|
+
# iter 5 fix (CRIT C-01 closure):
|
|
654
|
+
# matched-line では comment preservation を skip し新値で完全置換する。
|
|
655
|
+
_yml_set() {
|
|
656
|
+
local yml="$1"
|
|
657
|
+
local key="$2"
|
|
658
|
+
local new_val="$3"
|
|
659
|
+
local content
|
|
660
|
+
# 既存 key 行の有無確認
|
|
661
|
+
if ! grep -qE "^${key}:" "$yml"; then
|
|
662
|
+
_err "key not found: ${key}"
|
|
663
|
+
return 1
|
|
664
|
+
fi
|
|
665
|
+
# backup (cp 失敗時は伝播、code-rev M-03)
|
|
666
|
+
if ! _make_backup "$yml" >/dev/null; then
|
|
667
|
+
return 1
|
|
668
|
+
fi
|
|
669
|
+
|
|
670
|
+
local write_val
|
|
671
|
+
write_val=$(_yml_set_escape_value "$new_val")
|
|
672
|
+
|
|
673
|
+
# 行頭コメント以外で `^key:` 行を新値で置換 (1 行のみ)
|
|
674
|
+
# HIGH H-01 code-rev fix: awk -v は backslash sequence を解釈するため ENVIRON 経由で渡す。
|
|
675
|
+
# iter 5 fix (CRIT C-01): matched-line は新値で完全置換 (元行の comment 保持 skip)。
|
|
676
|
+
content=$(KEY="$key" VAL="$write_val" awk '
|
|
677
|
+
BEGIN {
|
|
678
|
+
k = ENVIRON["KEY"]
|
|
679
|
+
v = ENVIRON["VAL"]
|
|
680
|
+
replaced = 0
|
|
681
|
+
}
|
|
682
|
+
{
|
|
683
|
+
if (!replaced && index($0, k ":") == 1) {
|
|
684
|
+
printf "%s: %s\n", k, v
|
|
685
|
+
replaced = 1
|
|
686
|
+
} else {
|
|
687
|
+
print $0
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
' "$yml")
|
|
691
|
+
|
|
692
|
+
_atomic_write "$yml" "$content"
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
# config-loader.sh を source して default 値を取得
|
|
696
|
+
# 注意: caller の env を汚染しないよう subshell 内で実行
|
|
697
|
+
# MED M-2 fix: tmp cleanup を EXIT trap で保証
|
|
698
|
+
_get_default() {
|
|
699
|
+
local key="$1"
|
|
700
|
+
local env_name
|
|
701
|
+
env_name=$(_key_to_env "$key")
|
|
702
|
+
(
|
|
703
|
+
# 一時的に HC_<env_name> を unset して config-loader.sh の default を取得
|
|
704
|
+
unset "HC_${env_name}"
|
|
705
|
+
# config-loader.sh は env > yml > defaults の順なので、別の tmp yml (空) を渡せば
|
|
706
|
+
# defaults が露出する。
|
|
707
|
+
local empty_yml
|
|
708
|
+
empty_yml=$(mktemp "/tmp/hc-config-empty.XXXXXX") || return 1
|
|
709
|
+
# EXIT trap で確実 cleanup (subshell 内のみ有効)
|
|
710
|
+
trap 'rm -f "$empty_yml"' EXIT
|
|
711
|
+
export HC_CONFIG_PATH="$empty_yml"
|
|
712
|
+
# shellcheck disable=SC1091
|
|
713
|
+
source "${REPO_ROOT}/.claude/hooks/lib/config-loader.sh" 2>/dev/null
|
|
714
|
+
eval "printf '%s' \"\${HC_${env_name}:-}\""
|
|
715
|
+
)
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
# 現在値を取得 (env > yml > defaults の優先順)
|
|
719
|
+
#
|
|
720
|
+
# 解決順:
|
|
721
|
+
# 1. caller env HC_<KEY> が set されているか (export 済) → その値
|
|
722
|
+
# 2. yml に key が存在 → yml の raw 値
|
|
723
|
+
# 3. config-loader.sh の defaults → empty yml で source して取得 (HIGH H-02 test-auto fix)
|
|
724
|
+
_get_current() {
|
|
725
|
+
local key="$1"
|
|
726
|
+
local env_name
|
|
727
|
+
env_name=$(_key_to_env "$key")
|
|
728
|
+
# Step 1: caller env が set されていれば優先
|
|
729
|
+
local env_val env_is_set
|
|
730
|
+
eval "env_is_set=\${HC_${env_name}+set}"
|
|
731
|
+
if [ "${env_is_set:-}" = "set" ]; then
|
|
732
|
+
eval "env_val=\$HC_${env_name}"
|
|
733
|
+
printf '%s' "$env_val"
|
|
734
|
+
return 0
|
|
735
|
+
fi
|
|
736
|
+
# Step 2: yml に存在すれば raw 値
|
|
737
|
+
local raw
|
|
738
|
+
if raw=$(_yml_get_raw "$CONFIG_PATH" "$key"); then
|
|
739
|
+
printf '%s' "$raw"
|
|
740
|
+
return 0
|
|
741
|
+
fi
|
|
742
|
+
# Step 3: config-loader.sh の defaults
|
|
743
|
+
_get_default "$key"
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
# --config <path> の path validation (HIGH F-04 fix: path traversal guard)
|
|
747
|
+
# REPO_ROOT 配下 or /tmp/ 配下 or HC_ALLOW_EXTERNAL_CONFIG=1 のみ受理
|
|
748
|
+
_validate_config_path() {
|
|
749
|
+
local path="$1"
|
|
750
|
+
# bypass env (test isolation 用)
|
|
751
|
+
if [ "${HC_ALLOW_EXTERNAL_CONFIG:-}" = "1" ]; then
|
|
752
|
+
return 0
|
|
753
|
+
fi
|
|
754
|
+
# path canonicalize
|
|
755
|
+
local canon
|
|
756
|
+
canon=$(cd "$(dirname "$path")" 2>/dev/null && pwd -P)/$(basename "$path")
|
|
757
|
+
if [ -z "$canon" ] || [ "$canon" = "/" ]; then
|
|
758
|
+
canon="$path"
|
|
759
|
+
fi
|
|
760
|
+
# REPO_ROOT 配下 or /tmp/ 配下
|
|
761
|
+
case "$canon" in
|
|
762
|
+
"${REPO_ROOT}"/*|/tmp/*|/private/tmp/*|/var/folders/*)
|
|
763
|
+
return 0
|
|
764
|
+
;;
|
|
765
|
+
*)
|
|
766
|
+
_err "config path outside REPO_ROOT / /tmp/: ${canon}"
|
|
767
|
+
_err "use HC_ALLOW_EXTERNAL_CONFIG=1 to bypass (test isolation only)"
|
|
768
|
+
return 1
|
|
769
|
+
;;
|
|
770
|
+
esac
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
# === コマンド実装 ===
|
|
774
|
+
|
|
775
|
+
# --list ヘッダー行を印字 (mode に応じてカラム数を変える)
|
|
776
|
+
# $1: show_default (0/1), $2: show_effect (0/1)
|
|
777
|
+
# cmd_list helper
|
|
778
|
+
_cmd_list_header() {
|
|
779
|
+
local show_default="$1"
|
|
780
|
+
local show_effect="$2"
|
|
781
|
+
# ui-designer M3: default mode は説明列を cut で truncate して行幅暴走を抑える
|
|
782
|
+
# (最長 key=42 文字のため KEY 列 44、説明列を 30 文字に丸めて狭端末でも読める程度に)。
|
|
783
|
+
# --verbose / --show-default は情報密度優先で広端末向けと割り切る (header に注記)。
|
|
784
|
+
if [ "$show_effect" -eq 1 ]; then
|
|
785
|
+
printf '%-48s %-18s %-18s %-7s %-44s %s\n' "KEY" "CURRENT" "DEFAULT" "TYPE" "説明" "変更効果"
|
|
786
|
+
printf '%s\n' "(--verbose は広端末向け: 全 6 列を truncate して表示。狭端末では --list 単体を推奨)"
|
|
787
|
+
elif [ "$show_default" -eq 1 ]; then
|
|
788
|
+
printf '%-48s %-22s %-22s %-7s %s\n' "KEY" "CURRENT" "DEFAULT" "TYPE" "説明"
|
|
789
|
+
printf '%s\n' "(--show-default は広端末向け: DEFAULT 列を追加。狭端末では --list 単体を推奨)"
|
|
790
|
+
else
|
|
791
|
+
printf '%-44s %-22s %-7s %s\n' "KEY" "CURRENT" "TYPE" "説明"
|
|
792
|
+
fi
|
|
793
|
+
printf '%s\n' "$(printf '%.0s-' {1..106})"
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
# --list: 全 key 一覧表示
|
|
797
|
+
#
|
|
798
|
+
# task-48 Step 3: key metadata (説明 / 変更効果) を列に追加 + category 別グルーピング。
|
|
799
|
+
# default : KEY / CURRENT / TYPE / 説明 (DEFAULT 列を説明に置換)
|
|
800
|
+
# --show-default: KEY / CURRENT / DEFAULT / TYPE / 説明 (DEFAULT 列復活)
|
|
801
|
+
# --verbose : KEY / CURRENT / DEFAULT / TYPE / 説明 / 変更効果 (6 列)
|
|
802
|
+
#
|
|
803
|
+
# 引数: $1 に "verbose" / "show-default" を渡すと該当 mode。空なら default mode。
|
|
804
|
+
cmd_list() {
|
|
805
|
+
local mode="${1:-}"
|
|
806
|
+
local show_default=0 show_effect=0
|
|
807
|
+
case "$mode" in
|
|
808
|
+
verbose) show_default=1; show_effect=1 ;;
|
|
809
|
+
show-default) show_default=1 ;;
|
|
810
|
+
esac
|
|
811
|
+
|
|
812
|
+
_cmd_list_header "$show_default" "$show_effect"
|
|
813
|
+
|
|
814
|
+
local keys
|
|
815
|
+
keys=$(_yml_list_keys "$CONFIG_PATH")
|
|
816
|
+
|
|
817
|
+
# category 別にグルーピングして出力 (metadata 不在時は単一グループ扱い)
|
|
818
|
+
# iter2 CRIT 同種 leak 予防: for/while 本体内の `local cat_keys` / `local key` / `local cat_count`
|
|
819
|
+
# 宣言を関数頭に集約し、ループ内は素の代入にして bash 3.2 cmdsubst stdout 漏洩を構造的に防ぐ。
|
|
820
|
+
# task-69 Step 8 L2: categories は _hc_categories() SSoT helper から取得 (重複 hardcode 撤廃)。
|
|
821
|
+
local categories
|
|
822
|
+
categories=$(_hc_categories)
|
|
823
|
+
local printed_any=0
|
|
824
|
+
local cat cat_keys key cat_count
|
|
825
|
+
for cat in $categories; do
|
|
826
|
+
cat_keys=""
|
|
827
|
+
while IFS= read -r key; do
|
|
828
|
+
[ -z "$key" ] && continue
|
|
829
|
+
if [ "$(_meta_category "$key")" = "$cat" ]; then
|
|
830
|
+
cat_keys="${cat_keys}${key}"$'\n'
|
|
831
|
+
fi
|
|
832
|
+
done <<< "$keys"
|
|
833
|
+
[ -z "$cat_keys" ] && continue
|
|
834
|
+
cat_count=$(printf '%s' "$cat_keys" | grep -c '.')
|
|
835
|
+
printf '\n=== %s (%s keys) ===\n' "$cat" "$cat_count"
|
|
836
|
+
printf '%s' "$cat_keys" | _cmd_list_rows "$show_default" "$show_effect"
|
|
837
|
+
printed_any=1
|
|
838
|
+
done
|
|
839
|
+
|
|
840
|
+
# task-69 Step 1: 未分類 section — どの category にも一致しない key (metadata 欠落 /
|
|
841
|
+
# category drift) を漏れなく出力して --list の key set を yml と完全一致させる。
|
|
842
|
+
# key parity smoke (hc-config-key-parity-smoke.sh) が yml==--list を機械強制する。
|
|
843
|
+
local uncat_keys uncat_count
|
|
844
|
+
uncat_keys=""
|
|
845
|
+
while IFS= read -r key; do
|
|
846
|
+
[ -z "$key" ] && continue
|
|
847
|
+
case " $categories " in
|
|
848
|
+
*" $(_meta_category "$key") "*) : ;; # いずれかの category に分類済
|
|
849
|
+
*) uncat_keys="${uncat_keys}${key}"$'\n' ;;
|
|
850
|
+
esac
|
|
851
|
+
done <<< "$keys"
|
|
852
|
+
if [ -n "$uncat_keys" ]; then
|
|
853
|
+
uncat_count=$(printf '%s' "$uncat_keys" | grep -c '.')
|
|
854
|
+
printf '\n=== 未分類 (%s keys) ===\n' "$uncat_count"
|
|
855
|
+
printf '%s' "$uncat_keys" | _cmd_list_rows "$show_default" "$show_effect"
|
|
856
|
+
printed_any=1
|
|
857
|
+
fi
|
|
858
|
+
|
|
859
|
+
# metadata 不在等で 1 件も出力されなかった場合のみ従来通り全 key を 1 グループで出力
|
|
860
|
+
if [ "$printed_any" -eq 0 ]; then
|
|
861
|
+
printf '%s\n' "$keys" | _cmd_list_rows "$show_default" "$show_effect"
|
|
862
|
+
fi
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
# cmd_list の行出力 helper (stdin に key 改行区切り)
|
|
866
|
+
# $1: show_default (0/1), $2: show_effect (0/1)
|
|
867
|
+
_cmd_list_rows() {
|
|
868
|
+
local show_default="$1"
|
|
869
|
+
local show_effect="$2"
|
|
870
|
+
# iter2 CRIT 同種 leak 予防: while 本体内の `local cur_disp` / `local def_disp` 宣言を
|
|
871
|
+
# 関数頭に集約し、ループ内は素の代入にして bash 3.2 cmdsubst stdout 漏洩を構造的に防ぐ。
|
|
872
|
+
local key cur def type raw desc effect cur_disp def_disp
|
|
873
|
+
while IFS= read -r key; do
|
|
874
|
+
[ -z "$key" ] && continue
|
|
875
|
+
raw=$(_yml_get_raw "$CONFIG_PATH" "$key" || printf '')
|
|
876
|
+
cur=$(_get_current "$key")
|
|
877
|
+
type=$(_infer_type "$key" "$raw")
|
|
878
|
+
desc=$(_meta_desc "$key")
|
|
879
|
+
cur_disp=$(printf '%s' "$cur" | tr '\n' ',' | sed 's/,$//' | cut -c1-24)
|
|
880
|
+
if [ "$show_effect" -eq 1 ]; then
|
|
881
|
+
def=$(_get_default "$key" 2>/dev/null || printf '')
|
|
882
|
+
effect=$(_meta_effect "$key")
|
|
883
|
+
def_disp=$(printf '%s' "$def" | tr '\n' ',' | sed 's/,$//' | cut -c1-16)
|
|
884
|
+
printf '%-48s %-18s %-18s %-7s %-44s %s\n' \
|
|
885
|
+
"$key" "$cur_disp" "$def_disp" "$type" "$(printf '%s' "$desc" | cut -c1-42)" "$effect"
|
|
886
|
+
elif [ "$show_default" -eq 1 ]; then
|
|
887
|
+
def=$(_get_default "$key" 2>/dev/null || printf '')
|
|
888
|
+
def_disp=$(printf '%s' "$def" | tr '\n' ',' | sed 's/,$//' | cut -c1-20)
|
|
889
|
+
printf '%-48s %-22s %-22s %-7s %s\n' \
|
|
890
|
+
"$key" "$cur_disp" "$def_disp" "$type" "$desc"
|
|
891
|
+
else
|
|
892
|
+
# ui-designer M3: default mode は KEY 44 + CURRENT 22 + TYPE 7 + 説明 (30 文字 truncate) に丸めて
|
|
893
|
+
# 行幅暴走を抑える (説明全文は --verbose / --show-default で確認)。
|
|
894
|
+
printf '%-44s %-22s %-7s %s\n' \
|
|
895
|
+
"$key" "$(printf '%s' "$cur_disp" | cut -c1-20)" "$type" "$(printf '%s' "$desc" | cut -c1-30)"
|
|
896
|
+
fi
|
|
897
|
+
done
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
# --get <key>: 値取得 (HIGH H-02 test-auto fix: defaults fallback 実装)
|
|
901
|
+
cmd_get() {
|
|
902
|
+
local key="$1"
|
|
903
|
+
if [ -z "$key" ]; then
|
|
904
|
+
_err "--get requires <key>"
|
|
905
|
+
return 1
|
|
906
|
+
fi
|
|
907
|
+
# key format validation (security M-01)
|
|
908
|
+
if ! _validate_key_format "$key"; then
|
|
909
|
+
return 1
|
|
910
|
+
fi
|
|
911
|
+
# 1) yml に存在 → _get_current で env > yml の順
|
|
912
|
+
if _yml_get_raw "$CONFIG_PATH" "$key" >/dev/null 2>&1; then
|
|
913
|
+
_get_current "$key"
|
|
914
|
+
printf '\n'
|
|
915
|
+
return 0
|
|
916
|
+
fi
|
|
917
|
+
# 2) yml に不在 → defaults fallback (defaults が空でなければ返す)
|
|
918
|
+
local def
|
|
919
|
+
def=$(_get_default "$key" 2>/dev/null || printf '')
|
|
920
|
+
if [ -n "$def" ]; then
|
|
921
|
+
printf '%s\n' "$def"
|
|
922
|
+
return 0
|
|
923
|
+
fi
|
|
924
|
+
# 3) defaults も空 → key not found
|
|
925
|
+
_err "key not found: ${key}"
|
|
926
|
+
return 1
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
# --set <key>=<value>: 値設定
|
|
930
|
+
cmd_set() {
|
|
931
|
+
local arg="$1"
|
|
932
|
+
if [[ ! "$arg" =~ ^[a-z_][a-zA-Z0-9_]*=.*$ ]]; then
|
|
933
|
+
_err "--set requires <key>=<value>"
|
|
934
|
+
return 1
|
|
935
|
+
fi
|
|
936
|
+
local key="${arg%%=*}"
|
|
937
|
+
local val="${arg#*=}"
|
|
938
|
+
if ! _validate_key_format "$key"; then
|
|
939
|
+
return 1
|
|
940
|
+
fi
|
|
941
|
+
if ! _yml_get_raw "$CONFIG_PATH" "$key" >/dev/null; then
|
|
942
|
+
_err "key not found: ${key}"
|
|
943
|
+
return 1
|
|
944
|
+
fi
|
|
945
|
+
local raw
|
|
946
|
+
raw=$(_yml_get_raw "$CONFIG_PATH" "$key" || printf '')
|
|
947
|
+
local type
|
|
948
|
+
type=$(_infer_type "$key" "$raw")
|
|
949
|
+
if ! _validate_value "$key" "$type" "$val"; then
|
|
950
|
+
return 1
|
|
951
|
+
fi
|
|
952
|
+
_yml_set "$CONFIG_PATH" "$key" "$val"
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
# --feature <name>=<true|false>: feature toggle shorthand
|
|
956
|
+
cmd_feature() {
|
|
957
|
+
local arg="$1"
|
|
958
|
+
if [[ ! "$arg" =~ ^[a-z_][a-zA-Z0-9_]*=.+$ ]]; then
|
|
959
|
+
_err "--feature requires <name>=<true|false>"
|
|
960
|
+
return 1
|
|
961
|
+
fi
|
|
962
|
+
local name="${arg%%=*}"
|
|
963
|
+
local val="${arg#*=}"
|
|
964
|
+
local key="feature_${name}_enabled"
|
|
965
|
+
cmd_set "${key}=${val}"
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
# --reset <key>: default 復元
|
|
969
|
+
cmd_reset() {
|
|
970
|
+
local key="$1"
|
|
971
|
+
if [ -z "$key" ]; then
|
|
972
|
+
_err "--reset requires <key>"
|
|
973
|
+
return 1
|
|
974
|
+
fi
|
|
975
|
+
# key format validation (security M-01)
|
|
976
|
+
if ! _validate_key_format "$key"; then
|
|
977
|
+
return 1
|
|
978
|
+
fi
|
|
979
|
+
# iter2 CRIT 同種 leak 予防: case 内 `local items` を関数頭に集約。
|
|
980
|
+
local def items
|
|
981
|
+
def=$(_get_default "$key")
|
|
982
|
+
if [ -z "$def" ]; then
|
|
983
|
+
_err "no default value found for ${key}"
|
|
984
|
+
return 1
|
|
985
|
+
fi
|
|
986
|
+
# array 値は yml inline 形式に再構成 (改行 → カンマ区切り [a, b, c])
|
|
987
|
+
case "$def" in
|
|
988
|
+
*$'\n'*)
|
|
989
|
+
items=$(printf '%s' "$def" | tr '\n' ',' | sed 's/,$//')
|
|
990
|
+
def="[${items}]"
|
|
991
|
+
;;
|
|
992
|
+
esac
|
|
993
|
+
cmd_set "${key}=${def}"
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
# --reset-all: 全 key を default に戻す
|
|
997
|
+
cmd_reset_all() {
|
|
998
|
+
local keys
|
|
999
|
+
keys=$(_yml_list_keys "$CONFIG_PATH")
|
|
1000
|
+
local key
|
|
1001
|
+
while IFS= read -r key; do
|
|
1002
|
+
[ -z "$key" ] && continue
|
|
1003
|
+
cmd_reset "$key" 2>/dev/null || true
|
|
1004
|
+
done <<< "$keys"
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
# --diff: 現在値と default の差分
|
|
1008
|
+
cmd_diff() {
|
|
1009
|
+
printf '%-50s %-30s %-30s\n' "KEY" "CURRENT" "DEFAULT"
|
|
1010
|
+
printf '%s\n' "$(printf '%.0s-' {1..120})"
|
|
1011
|
+
local keys
|
|
1012
|
+
keys=$(_yml_list_keys "$CONFIG_PATH")
|
|
1013
|
+
# iter2 CRIT 同種 leak 予防: while 本体内 `local cur_disp def_disp` を関数頭に集約。
|
|
1014
|
+
local key cur def cur_disp def_disp
|
|
1015
|
+
while IFS= read -r key; do
|
|
1016
|
+
[ -z "$key" ] && continue
|
|
1017
|
+
cur=$(_get_current "$key")
|
|
1018
|
+
def=$(_get_default "$key" 2>/dev/null || printf '')
|
|
1019
|
+
if [ "$cur" != "$def" ]; then
|
|
1020
|
+
cur_disp=$(printf '%s' "$cur" | tr '\n' ',' | cut -c1-28)
|
|
1021
|
+
def_disp=$(printf '%s' "$def" | tr '\n' ',' | cut -c1-28)
|
|
1022
|
+
printf '%-50s %-30s %-30s\n' "$key" "$cur_disp" "$def_disp"
|
|
1023
|
+
fi
|
|
1024
|
+
done <<< "$keys"
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
# === Enforcement matrix parse helpers (task-70 Phase 2) ===
|
|
1028
|
+
# enforcement_matrix は nested block のため flat parser (_yml_get_raw) では読めない。
|
|
1029
|
+
# task-70 Step 7 (LOW-1 DRY): awk ロジックは lib/enforcement-matrix-parse.sh に SSoT 化。
|
|
1030
|
+
# 以下 3 関数は CONFIG_PATH を引き渡す薄いラッパー (lib 不在時は直接 awk に fallback)。
|
|
1031
|
+
|
|
1032
|
+
# enforcement_matrix block の guard 名一覧。
|
|
1033
|
+
_em_guards() {
|
|
1034
|
+
if command -v em_guards >/dev/null 2>&1; then
|
|
1035
|
+
em_guards "$CONFIG_PATH"
|
|
1036
|
+
else
|
|
1037
|
+
awk '
|
|
1038
|
+
/^enforcement_matrix:[[:space:]]*$/ { in_m=1; next }
|
|
1039
|
+
in_m && /^[^[:space:]]/ { in_m=0 }
|
|
1040
|
+
in_m && /^ [a-z_][a-zA-Z0-9_]*:[[:space:]]*$/ {
|
|
1041
|
+
line=$0; sub(/^ /,"",line); sub(/:.*$/,"",line); print line
|
|
1042
|
+
}
|
|
1043
|
+
' "$CONFIG_PATH"
|
|
1044
|
+
fi
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
# 指定 guard の指定 field 値。$1=guard, $2=field
|
|
1048
|
+
_em_field() {
|
|
1049
|
+
if command -v em_field >/dev/null 2>&1; then
|
|
1050
|
+
em_field "$CONFIG_PATH" "$1" "$2"
|
|
1051
|
+
else
|
|
1052
|
+
awk -v g="$1" -v f="$2" '
|
|
1053
|
+
/^enforcement_matrix:[[:space:]]*$/ { in_m=1; next }
|
|
1054
|
+
in_m && /^[^[:space:]]/ { in_m=0 }
|
|
1055
|
+
in_m && $0 ~ "^ " g ":[[:space:]]*$" { in_g=1; next }
|
|
1056
|
+
in_m && in_g && /^ [a-z_]/ { in_g=0 }
|
|
1057
|
+
in_m && in_g && $0 ~ "^ " f ":" {
|
|
1058
|
+
line=$0; sub("^ " f ":[[:space:]]*","",line); print line; exit
|
|
1059
|
+
}
|
|
1060
|
+
' "$CONFIG_PATH"
|
|
1061
|
+
fi
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
# 指定 guard の disabled_reason に指定 preset の理由文字列を返す (空なら return 1)。
|
|
1065
|
+
# $1=guard, $2=preset
|
|
1066
|
+
_em_disabled_reason() {
|
|
1067
|
+
if command -v em_disabled_reason >/dev/null 2>&1; then
|
|
1068
|
+
em_disabled_reason "$CONFIG_PATH" "$1" "$2"
|
|
1069
|
+
else
|
|
1070
|
+
awk -v g="$1" -v p="$2" '
|
|
1071
|
+
/^enforcement_matrix:[[:space:]]*$/ { in_m=1; next }
|
|
1072
|
+
in_m && /^[^[:space:]]/ { in_m=0 }
|
|
1073
|
+
in_m && $0 ~ "^ " g ":[[:space:]]*$" { in_g=1; next }
|
|
1074
|
+
in_m && in_g && /^ [a-z_]/ { in_g=0; in_dr=0 }
|
|
1075
|
+
in_m && in_g && /^ disabled_reason:[[:space:]]*$/ { in_dr=1; next }
|
|
1076
|
+
in_m && in_g && /^ [a-z_]/ && !/^ disabled_reason:/ { in_dr=0 }
|
|
1077
|
+
in_m && in_g && in_dr && $0 ~ "^ " p ":" {
|
|
1078
|
+
val=$0; sub("^ " p ":[[:space:]]*","",val)
|
|
1079
|
+
gsub(/^[\"\x27]|[\"\x27][[:space:]]*$/,"",val)
|
|
1080
|
+
if (length(val) > 0) { print val; found=1 }
|
|
1081
|
+
}
|
|
1082
|
+
END { exit (found ? 0 : 1) }
|
|
1083
|
+
' "$CONFIG_PATH"
|
|
1084
|
+
fi
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
# --summary: 現 preset / 有効 guard / 無効 guard / docs mismatch 件数 (draft §4.2 出力案)
|
|
1088
|
+
cmd_summary() {
|
|
1089
|
+
local preset
|
|
1090
|
+
preset=$(_get_current default_preset 2>/dev/null || printf '')
|
|
1091
|
+
[ -z "$preset" ] && preset="harness-dev"
|
|
1092
|
+
|
|
1093
|
+
# MEDIUM-1: preset が allowed-value 4 種以外なら UNKNOWN warning を表示
|
|
1094
|
+
case "$preset" in
|
|
1095
|
+
advisory|team-default|strict|harness-dev) ;;
|
|
1096
|
+
*)
|
|
1097
|
+
printf 'WARNING: default_preset="%s" is not one of the known presets (advisory, team-default, strict, harness-dev)\n' \
|
|
1098
|
+
"$preset" >&2
|
|
1099
|
+
;;
|
|
1100
|
+
esac
|
|
1101
|
+
|
|
1102
|
+
printf 'preset: %s\n' "$preset"
|
|
1103
|
+
printf 'guards:\n'
|
|
1104
|
+
|
|
1105
|
+
local guards g fkey claim eff reason
|
|
1106
|
+
local enabled_count=0 disabled_count=0
|
|
1107
|
+
local undoc_mismatch=0 doc_exception=0
|
|
1108
|
+
guards="$(_em_guards)"
|
|
1109
|
+
while IFS= read -r g; do
|
|
1110
|
+
[ -z "$g" ] && continue
|
|
1111
|
+
fkey="$(_em_field "$g" feature_key)"
|
|
1112
|
+
claim="$(_em_field "$g" docs_claim)"
|
|
1113
|
+
eff="$(_get_current "$fkey" 2>/dev/null || printf '')"
|
|
1114
|
+
[ -z "$eff" ] && eff="$(_get_default "$fkey" 2>/dev/null || printf '')"
|
|
1115
|
+
|
|
1116
|
+
if [ "$eff" = "true" ]; then
|
|
1117
|
+
enabled_count=$((enabled_count + 1))
|
|
1118
|
+
printf ' %s: enabled (docs: %s)\n' "$g" "$claim"
|
|
1119
|
+
else
|
|
1120
|
+
disabled_count=$((disabled_count + 1))
|
|
1121
|
+
if [ "$claim" = "block" ]; then
|
|
1122
|
+
reason="$(_em_disabled_reason "$g" "$preset" 2>/dev/null || printf '')"
|
|
1123
|
+
if [ -n "$reason" ]; then
|
|
1124
|
+
doc_exception=$((doc_exception + 1))
|
|
1125
|
+
printf ' %s: disabled (docs: block, reason: %s)\n' "$g" "$reason"
|
|
1126
|
+
else
|
|
1127
|
+
undoc_mismatch=$((undoc_mismatch + 1))
|
|
1128
|
+
printf ' %s: disabled (docs: block, reason: UNDOCUMENTED MISMATCH)\n' "$g"
|
|
1129
|
+
fi
|
|
1130
|
+
else
|
|
1131
|
+
printf ' %s: disabled (docs: %s)\n' "$g" "$claim"
|
|
1132
|
+
fi
|
|
1133
|
+
fi
|
|
1134
|
+
done <<< "$guards"
|
|
1135
|
+
|
|
1136
|
+
printf 'warnings:\n'
|
|
1137
|
+
printf ' docs/config mismatch: %d documented exception(s), %d undocumented\n' \
|
|
1138
|
+
"$doc_exception" "$undoc_mismatch"
|
|
1139
|
+
printf 'totals: %d enabled, %d disabled\n' "$enabled_count" "$disabled_count"
|
|
1140
|
+
|
|
1141
|
+
# undocumented mismatch があれば非 0 で返す (CI / smoke から検出可能に)
|
|
1142
|
+
[ "$undoc_mismatch" -eq 0 ]
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
# default_preset の allowed-value set (MEDIUM-1: task-70 Step 7)
|
|
1146
|
+
# advisory / team-default / strict / harness-dev の 4 種のみ有効。
|
|
1147
|
+
# $1: preset 値
|
|
1148
|
+
# 戻り: 0 = 有効 / 1 = 無効 (stderr にエラー)
|
|
1149
|
+
_validate_default_preset() {
|
|
1150
|
+
local val="$1"
|
|
1151
|
+
case "$val" in
|
|
1152
|
+
advisory|team-default|strict|harness-dev) return 0 ;;
|
|
1153
|
+
*)
|
|
1154
|
+
_err "invalid value for default_preset: '${val}' (must be one of: advisory, team-default, strict, harness-dev)"
|
|
1155
|
+
return 1
|
|
1156
|
+
;;
|
|
1157
|
+
esac
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
# task-77 Step 1: mainline_integration_policy の enum validation (3 値のみ許可)。
|
|
1161
|
+
# draft git-integration-policy.md §3.1/§3.2 SSoT。bash 3.2 互換 case。
|
|
1162
|
+
_validate_mainline_integration_policy() {
|
|
1163
|
+
local val="$1"
|
|
1164
|
+
case "$val" in
|
|
1165
|
+
pr-required|local-merge|local-merge-push) return 0 ;;
|
|
1166
|
+
*)
|
|
1167
|
+
_err "invalid value for mainline_integration_policy: '${val}' (must be one of: pr-required, local-merge, local-merge-push)"
|
|
1168
|
+
return 1
|
|
1169
|
+
;;
|
|
1170
|
+
esac
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
# task-77 Step 1: mainline_branch の charset validation。
|
|
1174
|
+
# SSoT charset: ^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,99}$ (英数字始まり + 英数字/./_/ //-、
|
|
1175
|
+
# release/1.0 可、空白不可)。空白含む値は git-deny の token 分割を破壊するため reject
|
|
1176
|
+
# (draft §3.1 / code-arch M-2 / sec M-2)。
|
|
1177
|
+
_validate_mainline_branch() {
|
|
1178
|
+
local val="$1"
|
|
1179
|
+
if [[ "$val" =~ ^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,99}$ ]]; then
|
|
1180
|
+
return 0
|
|
1181
|
+
fi
|
|
1182
|
+
_err "invalid value for mainline_branch: '${val}' (must match ^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,99}\$, no spaces)"
|
|
1183
|
+
return 1
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
# --validate: 全 key の型 validation のみ
|
|
1187
|
+
cmd_validate() {
|
|
1188
|
+
local keys
|
|
1189
|
+
keys=$(_yml_list_keys "$CONFIG_PATH")
|
|
1190
|
+
local key raw type errors=0
|
|
1191
|
+
while IFS= read -r key; do
|
|
1192
|
+
[ -z "$key" ] && continue
|
|
1193
|
+
raw=$(_yml_get_raw "$CONFIG_PATH" "$key" || printf '')
|
|
1194
|
+
type=$(_infer_type "$key" "$raw")
|
|
1195
|
+
if ! _validate_value "$key" "$type" "$raw" 2>/dev/null; then
|
|
1196
|
+
_err "invalid: ${key} (type=${type}, value='${raw}')"
|
|
1197
|
+
errors=$((errors + 1))
|
|
1198
|
+
fi
|
|
1199
|
+
done <<< "$keys"
|
|
1200
|
+
if [ "$errors" -gt 0 ]; then
|
|
1201
|
+
_err "validation failed: ${errors} errors"
|
|
1202
|
+
return 1
|
|
1203
|
+
fi
|
|
1204
|
+
_out "validation OK: all keys valid"
|
|
1205
|
+
return 0
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
# === deprecated_keys table + migration (task-69 Step 4) ===
|
|
1209
|
+
#
|
|
1210
|
+
# 設計 SSoT: docs/draft/harness-review-remediation-plan.md §4.1「migration の扱い」。
|
|
1211
|
+
# key rename / delete は手作業にしない。deprecated_keys table を持ち、`--migrate` で:
|
|
1212
|
+
# 1. old key を検出する
|
|
1213
|
+
# 2. new key に値を移す (new key 不在なら追記、存在すれば既存値を尊重し old は削除のみ)
|
|
1214
|
+
# 3. 移行ログを出す
|
|
1215
|
+
# 4. deprecated key が残っていれば非0 exit (smoke fail trigger)
|
|
1216
|
+
#
|
|
1217
|
+
# table 形式 (1 行 1 entry、"old_key new_key" の space 区切り):
|
|
1218
|
+
# - delete 専用 (new key なし) は new_key 欄を "-" にする (値破棄 + old 削除のみ)。
|
|
1219
|
+
# - 現状は将来の rename 用 seed として 1 entry を置く。実 rename 発生時に追記する。
|
|
1220
|
+
#
|
|
1221
|
+
# bash 3.2 互換のため連想配列を使わず、改行区切り string を loop する。
|
|
1222
|
+
#
|
|
1223
|
+
# seed entry (`_deprecated_example_old -> _deprecated_example_new`) は将来の rename 用 placeholder。
|
|
1224
|
+
# 実 harness-config.yml には存在しない key 名 (`_` 始まり) のため、real config で `--migrate` を
|
|
1225
|
+
# 実行しても no-op (対象なし)。実際の key rename / delete 発生時は本 table に
|
|
1226
|
+
# "old_key new_key" (delete 専用は new_key を "-") 形式で 1 行追記する。
|
|
1227
|
+
_DEPRECATED_KEYS="\
|
|
1228
|
+
_deprecated_example_old _deprecated_example_new"
|
|
1229
|
+
|
|
1230
|
+
# deprecated table を "old -> new" 形式で 1 行ずつ出力 (smoke / operator 参照用)
|
|
1231
|
+
cmd_list_deprecated() {
|
|
1232
|
+
local line old new
|
|
1233
|
+
while IFS= read -r line; do
|
|
1234
|
+
[ -z "$line" ] && continue
|
|
1235
|
+
old=$(printf '%s' "$line" | awk '{print $1}')
|
|
1236
|
+
new=$(printf '%s' "$line" | awk '{print $2}')
|
|
1237
|
+
[ -z "$old" ] && continue
|
|
1238
|
+
printf '%s -> %s\n' "$old" "${new:--}"
|
|
1239
|
+
done <<EOF
|
|
1240
|
+
$(printf '%s\n' "$_DEPRECATED_KEYS")
|
|
1241
|
+
EOF
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
# old key の値を new key へ移行 (1 entry 分)
|
|
1245
|
+
# $1: yml path, $2: old_key, $3: new_key ("-" なら delete 専用)
|
|
1246
|
+
# 戻り: 0 = 移行 or 何もしなかった (old 不在) / 1 = 移行失敗 (old 残存)
|
|
1247
|
+
_migrate_one() {
|
|
1248
|
+
local yml="$1"
|
|
1249
|
+
local old_key="$2"
|
|
1250
|
+
local new_key="$3"
|
|
1251
|
+
# old key が yml に存在しなければ no-op
|
|
1252
|
+
if ! grep -qE "^${old_key}:" "$yml"; then
|
|
1253
|
+
return 0
|
|
1254
|
+
fi
|
|
1255
|
+
local old_val
|
|
1256
|
+
old_val=$(_yml_get_raw "$yml" "$old_key" || printf '')
|
|
1257
|
+
|
|
1258
|
+
if [ "$new_key" != "-" ] && [ -n "$new_key" ]; then
|
|
1259
|
+
if grep -qE "^${new_key}:" "$yml"; then
|
|
1260
|
+
# new key が既に存在 → 既存値を尊重し old を削除するのみ
|
|
1261
|
+
_out "migrate: ${old_key} -> ${new_key} (new key already present, keeping its value; removing old)"
|
|
1262
|
+
else
|
|
1263
|
+
# new key 行を old key 行の位置に rename (値も移す)
|
|
1264
|
+
# awk で old key 行を `new_key: old_val` に置換 (1 行のみ、comment は破棄)
|
|
1265
|
+
local content
|
|
1266
|
+
content=$(OLD="$old_key" NEW="$new_key" VAL="$old_val" awk '
|
|
1267
|
+
BEGIN { o = ENVIRON["OLD"]; n = ENVIRON["NEW"]; v = ENVIRON["VAL"]; done = 0 }
|
|
1268
|
+
{
|
|
1269
|
+
if (!done && index($0, o ":") == 1) {
|
|
1270
|
+
printf "%s: %s\n", n, v
|
|
1271
|
+
done = 1
|
|
1272
|
+
} else {
|
|
1273
|
+
print $0
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
' "$yml")
|
|
1277
|
+
if ! _atomic_write "$yml" "$content"; then
|
|
1278
|
+
_err "migrate: atomic write failed for ${old_key} -> ${new_key}"
|
|
1279
|
+
return 1
|
|
1280
|
+
fi
|
|
1281
|
+
_out "migrate: ${old_key} -> ${new_key} (value '${old_val}' moved)"
|
|
1282
|
+
fi
|
|
1283
|
+
fi
|
|
1284
|
+
|
|
1285
|
+
# delete 専用 (new key="-") もしくは new key 既存 path: old key 行を削除する。
|
|
1286
|
+
if grep -qE "^${old_key}:" "$yml"; then
|
|
1287
|
+
local content2
|
|
1288
|
+
content2=$(OLD="$old_key" awk '
|
|
1289
|
+
BEGIN { o = ENVIRON["OLD"] }
|
|
1290
|
+
{ if (index($0, o ":") == 1) next; print $0 }
|
|
1291
|
+
' "$yml")
|
|
1292
|
+
if ! _atomic_write "$yml" "$content2"; then
|
|
1293
|
+
_err "migrate: atomic write failed removing old key ${old_key}"
|
|
1294
|
+
return 1
|
|
1295
|
+
fi
|
|
1296
|
+
if [ "$new_key" = "-" ] || [ -z "$new_key" ]; then
|
|
1297
|
+
_out "migrate: ${old_key} removed (deprecated, no replacement)"
|
|
1298
|
+
fi
|
|
1299
|
+
fi
|
|
1300
|
+
|
|
1301
|
+
# 残存確認 (移行失敗検出)
|
|
1302
|
+
if grep -qE "^${old_key}:" "$yml"; then
|
|
1303
|
+
_err "migrate: deprecated key still present after migration: ${old_key}"
|
|
1304
|
+
return 1
|
|
1305
|
+
fi
|
|
1306
|
+
return 0
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
# --migrate: deprecated_keys table を走査して old key を new key へ移行
|
|
1310
|
+
# 戻り: 0 = 全 entry 移行完了 (or 対象なし) / 1 = いずれか残存 (smoke fail trigger)
|
|
1311
|
+
cmd_migrate() {
|
|
1312
|
+
local line old new migrated=0 errors=0
|
|
1313
|
+
while IFS= read -r line; do
|
|
1314
|
+
[ -z "$line" ] && continue
|
|
1315
|
+
old=$(printf '%s' "$line" | awk '{print $1}')
|
|
1316
|
+
new=$(printf '%s' "$line" | awk '{print $2}')
|
|
1317
|
+
[ -z "$old" ] && continue
|
|
1318
|
+
if grep -qE "^${old}:" "$CONFIG_PATH"; then
|
|
1319
|
+
if _migrate_one "$CONFIG_PATH" "$old" "$new"; then
|
|
1320
|
+
migrated=$((migrated + 1))
|
|
1321
|
+
else
|
|
1322
|
+
errors=$((errors + 1))
|
|
1323
|
+
fi
|
|
1324
|
+
fi
|
|
1325
|
+
done <<EOF
|
|
1326
|
+
$(printf '%s\n' "$_DEPRECATED_KEYS")
|
|
1327
|
+
EOF
|
|
1328
|
+
if [ "$errors" -gt 0 ]; then
|
|
1329
|
+
_err "migration failed: ${errors} deprecated key(s) still present"
|
|
1330
|
+
return 1
|
|
1331
|
+
fi
|
|
1332
|
+
if [ "$migrated" -eq 0 ]; then
|
|
1333
|
+
_out "no migration needed (no deprecated keys present)"
|
|
1334
|
+
else
|
|
1335
|
+
_out "migration complete: ${migrated} key(s) migrated"
|
|
1336
|
+
fi
|
|
1337
|
+
return 0
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
# --help
|
|
1341
|
+
cmd_help() {
|
|
1342
|
+
cat <<'EOF'
|
|
1343
|
+
hc-config — harness-config.yml interactive editor
|
|
1344
|
+
|
|
1345
|
+
USAGE:
|
|
1346
|
+
hc-config.sh 引数なし: 対話 menu 起動 (TTY=矢印キー TUI / 非TTY=番号選択)
|
|
1347
|
+
hc-config.sh interactive 明示起動: 対話 menu (上記と同等、task-61 draft DoD 経路)
|
|
1348
|
+
hc-config.sh --list 全 key 一覧表示 (KEY/CURRENT/TYPE/説明、category 別)
|
|
1349
|
+
hc-config.sh --list --verbose 全 key 一覧 + DEFAULT + 変更効果 (6 列)
|
|
1350
|
+
hc-config.sh --list --show-default 全 key 一覧 + DEFAULT 列
|
|
1351
|
+
hc-config.sh --get <key> key の現在値取得 (env override 優先)
|
|
1352
|
+
hc-config.sh --set <key>=<value> 値設定 (型 validation + backup + atomic)
|
|
1353
|
+
hc-config.sh --feature <name>=<bool> feature toggle 短縮 (feature_<name>_enabled の alias)
|
|
1354
|
+
hc-config.sh --reset <key> key を default 値に戻す
|
|
1355
|
+
hc-config.sh --reset-all 全 key を default に戻す
|
|
1356
|
+
hc-config.sh --diff 現在値と default の差分一覧
|
|
1357
|
+
hc-config.sh --summary 現 preset / 有効・無効 guard / docs mismatch を表示 (enforcement_matrix)
|
|
1358
|
+
hc-config.sh --validate 全 key の型 validation のみ実行
|
|
1359
|
+
hc-config.sh --migrate deprecated_keys table に従い old key を new key へ移行 (残存で非0 exit)
|
|
1360
|
+
hc-config.sh --list-deprecated deprecated_keys table を "old -> new" 形式で表示
|
|
1361
|
+
hc-config.sh --config <path> 編集対象 yml path を override (test isolation 用)
|
|
1362
|
+
hc-config.sh --help 本 help 表示
|
|
1363
|
+
|
|
1364
|
+
EXAMPLES:
|
|
1365
|
+
hc-config.sh --get feature_loop_mode_enforcement_enabled
|
|
1366
|
+
hc-config.sh --set review_iteration_max=3
|
|
1367
|
+
hc-config.sh --feature draft_flow_guard=false
|
|
1368
|
+
hc-config.sh --reset review_iteration_max
|
|
1369
|
+
|
|
1370
|
+
DESIGN:
|
|
1371
|
+
- atomic 操作: .bak.<ts>.<pid> backup + .tmp.<pid> write + python yaml validate (stdin) + mv
|
|
1372
|
+
- 値型 validation: bool / int / float / array / string / path (改行/制御文字/yaml syntax confusion を reject)
|
|
1373
|
+
- 環境変数 HC_<KEY> で yml 値を override 可能 (config-loader.sh 経由)
|
|
1374
|
+
- .bak retention: 最新 N=10 件保持 (HC_BAK_RETENTION_COUNT で override)
|
|
1375
|
+
- --config path は REPO_ROOT / /tmp/ 配下のみ (HC_ALLOW_EXTERNAL_CONFIG=1 で bypass)
|
|
1376
|
+
|
|
1377
|
+
REFERENCE:
|
|
1378
|
+
- smoke test: .claude/tests/hc-config-script-smoke.sh
|
|
1379
|
+
- config-loader: .claude/hooks/lib/config-loader.sh
|
|
1380
|
+
- 設計 draft: docs/draft/config-yml-phase3-hc-config-script.md
|
|
1381
|
+
EOF
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
# === 対話 menu helpers ===
|
|
1385
|
+
|
|
1386
|
+
# option 2: key 選択して編集 (cmd_interactive helper 1/3)
|
|
1387
|
+
_menu_opt_edit_key() {
|
|
1388
|
+
printf 'key name: '
|
|
1389
|
+
local k
|
|
1390
|
+
IFS= read -r k || return 0
|
|
1391
|
+
if [ -n "$k" ]; then
|
|
1392
|
+
printf 'current: '
|
|
1393
|
+
cmd_get "$k" 2>/dev/null || true
|
|
1394
|
+
printf 'new value (empty to skip): '
|
|
1395
|
+
local v
|
|
1396
|
+
IFS= read -r v || return 0
|
|
1397
|
+
if [ -n "$v" ]; then
|
|
1398
|
+
cmd_set "${k}=${v}"
|
|
1399
|
+
fi
|
|
1400
|
+
fi
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
# option 3: feature toggle 一括 on/off (cmd_interactive helper 2/3)
|
|
1404
|
+
_menu_opt_feature() {
|
|
1405
|
+
printf 'feature name (without "feature_" prefix and "_enabled" suffix): '
|
|
1406
|
+
local fn
|
|
1407
|
+
IFS= read -r fn || return 0
|
|
1408
|
+
if [ -n "$fn" ]; then
|
|
1409
|
+
printf 'enable? [true/false]: '
|
|
1410
|
+
local fv
|
|
1411
|
+
IFS= read -r fv || return 0
|
|
1412
|
+
if [ -n "$fv" ]; then
|
|
1413
|
+
cmd_feature "${fn}=${fv}"
|
|
1414
|
+
fi
|
|
1415
|
+
fi
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
# option 4: reviewer 設定 quick edit (cmd_interactive helper 3/3)
|
|
1419
|
+
_menu_opt_reviewer() {
|
|
1420
|
+
local keys
|
|
1421
|
+
keys=$(_yml_list_keys "$CONFIG_PATH" | grep '^review_' || true)
|
|
1422
|
+
if [ -z "$keys" ]; then
|
|
1423
|
+
_out "no review_* keys found"
|
|
1424
|
+
return 0
|
|
1425
|
+
fi
|
|
1426
|
+
local k
|
|
1427
|
+
while IFS= read -r k; do
|
|
1428
|
+
[ -z "$k" ] && continue
|
|
1429
|
+
local cur
|
|
1430
|
+
cur=$(_get_current "$k")
|
|
1431
|
+
printf '%-40s = %s\n' "$k" "$cur"
|
|
1432
|
+
done <<< "$keys"
|
|
1433
|
+
printf '\nkey to edit (empty to skip): '
|
|
1434
|
+
local edk
|
|
1435
|
+
IFS= read -r edk || return 0
|
|
1436
|
+
if [ -n "$edk" ]; then
|
|
1437
|
+
printf 'new value: '
|
|
1438
|
+
local edv
|
|
1439
|
+
IFS= read -r edv || return 0
|
|
1440
|
+
if [ -n "$edv" ]; then
|
|
1441
|
+
cmd_set "${edk}=${edv}"
|
|
1442
|
+
fi
|
|
1443
|
+
fi
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
# === 対話 menu (番号選択、TTY fallback path) ===
|
|
1447
|
+
#
|
|
1448
|
+
# task-48 Step 3: 従来の番号選択 menu を _cmd_interactive_numeric に抽出。
|
|
1449
|
+
# 非 TTY (Claude Code session / pipe / CI) では raw terminal が効かないため本 path に降格、
|
|
1450
|
+
# HC_HC_CONFIG_FORCE_NUMERIC=1 で TTY でも強制番号選択。
|
|
1451
|
+
|
|
1452
|
+
# 番号選択 menu のメニュー行と選択プロンプトを印字
|
|
1453
|
+
# _cmd_interactive_numeric helper
|
|
1454
|
+
_numeric_menu_print() {
|
|
1455
|
+
cat <<'EOF'
|
|
1456
|
+
|
|
1457
|
+
=== hc-config interactive menu ===
|
|
1458
|
+
|
|
1459
|
+
1) 全 key 一覧表示
|
|
1460
|
+
2) key 選択して編集
|
|
1461
|
+
3) feature toggle 一括 on/off
|
|
1462
|
+
4) reviewer 設定 quick edit (review_*)
|
|
1463
|
+
5) 終了
|
|
1464
|
+
|
|
1465
|
+
EOF
|
|
1466
|
+
printf 'choice [1-5/q]: '
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
_cmd_interactive_numeric() {
|
|
1470
|
+
# H5: 非 TTY fallback の UX 断絶を埋める案内 (1 行)。
|
|
1471
|
+
# pipe / CI / Claude Code session では raw terminal が効かないため番号選択に降格する。
|
|
1472
|
+
_out "(非 TTY 環境のため番号選択モード。TTY 起動で矢印キー TUI が使えます)"
|
|
1473
|
+
while true; do
|
|
1474
|
+
_numeric_menu_print
|
|
1475
|
+
local choice
|
|
1476
|
+
if ! IFS= read -r choice; then
|
|
1477
|
+
printf '\nbye.\n'
|
|
1478
|
+
return 0
|
|
1479
|
+
fi
|
|
1480
|
+
case "$choice" in
|
|
1481
|
+
1)
|
|
1482
|
+
cmd_list
|
|
1483
|
+
;;
|
|
1484
|
+
2)
|
|
1485
|
+
_menu_opt_edit_key
|
|
1486
|
+
;;
|
|
1487
|
+
3)
|
|
1488
|
+
_menu_opt_feature
|
|
1489
|
+
;;
|
|
1490
|
+
4)
|
|
1491
|
+
_menu_opt_reviewer
|
|
1492
|
+
;;
|
|
1493
|
+
5|q|Q|0|quit|exit)
|
|
1494
|
+
_out "bye. (smoke test: bash .claude/tests/hc-config-script-smoke.sh)"
|
|
1495
|
+
return 0
|
|
1496
|
+
;;
|
|
1497
|
+
*)
|
|
1498
|
+
_err "unknown choice: ${choice}"
|
|
1499
|
+
;;
|
|
1500
|
+
esac
|
|
1501
|
+
done
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
# === 矢印キー TUI (task-48 Step 3、TTY 必須) ===
|
|
1505
|
+
#
|
|
1506
|
+
# category 一覧 → key 一覧 (選択行を `>` + reverse video ハイライト) → 下部に effect panel。
|
|
1507
|
+
# ESC [A/[B/[C/[D を decode、Enter で決定、q で quit。
|
|
1508
|
+
# bash 3.2 互換: declare -g / ${var^^} を避け case / tr で代替。
|
|
1509
|
+
#
|
|
1510
|
+
# 注意: 本 path は TTY 環境でのみ呼ばれる (cmd_interactive の dispatch 参照)。
|
|
1511
|
+
# 自動 smoke は非 TTY pipe で番号選択に降格するため TUI 描画は手動検証 (Step 5)。
|
|
1512
|
+
|
|
1513
|
+
# ANSI escape sequence 定義
|
|
1514
|
+
_TUI_RESET=$'\033[0m'
|
|
1515
|
+
_TUI_REVERSE=$'\033[7m'
|
|
1516
|
+
_TUI_DIM=$'\033[2m'
|
|
1517
|
+
_TUI_BOLD=$'\033[1m'
|
|
1518
|
+
_TUI_CLEAR=$'\033[2J\033[H'
|
|
1519
|
+
|
|
1520
|
+
# task-60 Step 5 iter 2 H2 fix (DRY 解消): category 一覧の SSoT 定数。
|
|
1521
|
+
# `_tui_render_category_menu` / `_tui_render_key_menu` / `_cmd_interactive_tui_2tier` の
|
|
1522
|
+
# 3 関数で同じ array を local 宣言していたのを 1 箇所に集約。bash 3.2 では `declare -ar` 不可
|
|
1523
|
+
# かつ scope 外への array 露出を避けるため、`|` 区切り string で持ち、各関数で
|
|
1524
|
+
# `IFS='|' read -r -a cat_names <<< "$_TUI_CAT_NAMES_STR"` で展開する (bash 3.2 互換)。
|
|
1525
|
+
# 順序は draft §3.2 の category 順 (保護パス → ファイル配置 → state_dir → Gate/Confidence →
|
|
1526
|
+
# feature_toggle → reviewer_control → harness_meta [task-69 追加])。
|
|
1527
|
+
_TUI_CAT_NAMES_STR="保護パス|ファイル配置|state_dir|Gate/Confidence|feature_toggle|reviewer_control|harness_meta"
|
|
1528
|
+
|
|
1529
|
+
# 1 文字キー入力を読み取り、矢印キーは UP/DOWN/RIGHT/LEFT に正規化して stdout 出力
|
|
1530
|
+
# Enter → ENTER、q → QUIT、それ以外は raw 文字。
|
|
1531
|
+
#
|
|
1532
|
+
# iter1 C1 fix (bash 3.2 互換):
|
|
1533
|
+
# 旧実装の `read -rsn2 -t 0.01 rest` は bash 3.2 (macOS 標準) で
|
|
1534
|
+
# "invalid timeout specification" になり ESC sequence が全て QUIT に化けて
|
|
1535
|
+
# 矢印キー TUI が動作しなかった。小数 `-t` を完全に排除し、ESC sequence の
|
|
1536
|
+
# inter-byte 読取は端末側の min/time 設定 (`stty min 0 time 1`、_cmd_interactive_tui で
|
|
1537
|
+
# 一時設定) に委ねる。`read -rsn2 rest` (タイムアウト指定なし) は端末タイムアウトで
|
|
1538
|
+
# 「続きが来なければ空」を実現する。bash 3.2 / 5.x 両対応。
|
|
1539
|
+
# 呼び出し元 (_cmd_interactive_tui) は既に raw/cbreak mode (stty -icanon -echo min 1 time 0)。
|
|
1540
|
+
_tui_read_key() {
|
|
1541
|
+
local key rest
|
|
1542
|
+
IFS= read -rsn1 key 2>/dev/null || { printf 'QUIT'; return 0; }
|
|
1543
|
+
case "$key" in
|
|
1544
|
+
$'\x1b')
|
|
1545
|
+
# ESC sequence: 続く 2 文字を読む ([A 等)。
|
|
1546
|
+
# 端末を一時的に min 0 time 1 (0.1 秒 inter-byte timeout) にして
|
|
1547
|
+
# 単独 ESC でも block しないようにする。小数 read -t は使わない (bash 3.2 互換)。
|
|
1548
|
+
stty min 0 time 1 2>/dev/null || true
|
|
1549
|
+
IFS= read -rsn2 rest 2>/dev/null || rest=""
|
|
1550
|
+
stty min 1 time 0 2>/dev/null || true
|
|
1551
|
+
# task-60 Step 5 iter 4 真の fix (M-new-1、draft §3.1 仕様乖離解消):
|
|
1552
|
+
# 単独 ESC (rest="") は 'ESC' を返し、unknown sequence のみ 'QUIT' に fallback。
|
|
1553
|
+
# 呼出側 (`_cmd_interactive_tui_2tier` key_menu state) で ESC|LEFT → back、
|
|
1554
|
+
# QUIT (= q キー、L1216 で正規化) → 全終了の分離を実現する。
|
|
1555
|
+
# `_cmd_interactive_tui_flat` 側は ESC|QUIT 同義扱いで互換維持 (Case 11 baseline)。
|
|
1556
|
+
case "$rest" in
|
|
1557
|
+
'[A') printf 'UP' ;;
|
|
1558
|
+
'[B') printf 'DOWN' ;;
|
|
1559
|
+
'[C') printf 'RIGHT' ;;
|
|
1560
|
+
'[D') printf 'LEFT' ;;
|
|
1561
|
+
'') printf 'ESC' ;;
|
|
1562
|
+
*) printf 'QUIT' ;;
|
|
1563
|
+
esac
|
|
1564
|
+
;;
|
|
1565
|
+
''|$'\n'|$'\r') printf 'ENTER' ;;
|
|
1566
|
+
q|Q) printf 'QUIT' ;;
|
|
1567
|
+
*) printf '%s' "$key" ;;
|
|
1568
|
+
esac
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
# 全 key を category 順に並べ替えて改行区切りで stdout 出力 (H3: category グルーピング)
|
|
1572
|
+
# metadata category 順 (保護パス → ファイル配置 → state_dir → Gate/Confidence →
|
|
1573
|
+
# feature_toggle → reviewer_control → harness_meta) に並べ、未分類 key は末尾に回す。
|
|
1574
|
+
# metadata 不在時 / 全 key 未分類時は入力順そのまま (fallback)。
|
|
1575
|
+
# $1: 全 key 改行区切り (yml 出現順)
|
|
1576
|
+
_tui_order_keys_by_category() {
|
|
1577
|
+
local all_keys="$1"
|
|
1578
|
+
# task-69 Step 8 L2: _hc_categories() SSoT helper から取得 (cmd_list と共通化)
|
|
1579
|
+
local categories
|
|
1580
|
+
categories=$(_hc_categories)
|
|
1581
|
+
local ordered="" key cat seen_cat=0
|
|
1582
|
+
# iter2 CRIT C-iter2-1 fix (bash 3.2 local+command-substitution leak 予防):
|
|
1583
|
+
# while ループ本体内で `local kc` (代入なし宣言) → 直後に `kc=$(_meta_category)` する構造は
|
|
1584
|
+
# 特定の bash 3.2 ビルドで command-substitution 出力が関数 stdout に漏洩する。
|
|
1585
|
+
# `local kc` をループ外 (関数頭) で 1 回だけ宣言し、ループ内は素の代入にして漏洩を構造的に防ぐ。
|
|
1586
|
+
local kc
|
|
1587
|
+
# category 順に key を集める
|
|
1588
|
+
for cat in $categories; do
|
|
1589
|
+
while IFS= read -r key; do
|
|
1590
|
+
[ -z "$key" ] && continue
|
|
1591
|
+
if [ "$(_meta_category "$key")" = "$cat" ]; then
|
|
1592
|
+
ordered="${ordered}${key}"$'\n'
|
|
1593
|
+
seen_cat=1
|
|
1594
|
+
fi
|
|
1595
|
+
done <<< "$all_keys"
|
|
1596
|
+
done
|
|
1597
|
+
# 未分類 key (metadata 不在 / category 空) を末尾に追加
|
|
1598
|
+
while IFS= read -r key; do
|
|
1599
|
+
[ -z "$key" ] && continue
|
|
1600
|
+
kc=$(_meta_category "$key")
|
|
1601
|
+
case "$kc" in
|
|
1602
|
+
保護パス|ファイル配置|state_dir|Gate/Confidence|feature_toggle|reviewer_control|harness_meta) ;;
|
|
1603
|
+
*) ordered="${ordered}${key}"$'\n' ;;
|
|
1604
|
+
esac
|
|
1605
|
+
done <<< "$all_keys"
|
|
1606
|
+
# category 一致 0 件 (metadata 完全不在) なら入力順そのまま
|
|
1607
|
+
if [ "$seen_cat" -eq 0 ]; then
|
|
1608
|
+
printf '%s\n' "$all_keys"
|
|
1609
|
+
return 0
|
|
1610
|
+
fi
|
|
1611
|
+
printf '%s' "$ordered"
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
# key 一覧部分の描画 (category 境界区切り行 + 選択ハイライト)
|
|
1615
|
+
# $1: all_keys (改行区切り), $2: sel (0-based)
|
|
1616
|
+
# _tui_render helper 1/2
|
|
1617
|
+
_tui_render_key_list() {
|
|
1618
|
+
local all_keys="$1"
|
|
1619
|
+
local sel="$2"
|
|
1620
|
+
# iter2 CRIT C-iter2-2 fix (bash 3.2 local+command-substitution leak 予防):
|
|
1621
|
+
# `cat_count` `kc` を関数頭で 1 回だけ宣言し、ループ/subshell 内は素の代入にして漏洩を防ぐ。
|
|
1622
|
+
local idx=0 key prev_cat="" cur_cat cat_count kc
|
|
1623
|
+
while IFS= read -r key; do
|
|
1624
|
+
[ -z "$key" ] && continue
|
|
1625
|
+
cur_cat=$(_meta_category "$key")
|
|
1626
|
+
[ -z "$cur_cat" ] && cur_cat="(未分類)"
|
|
1627
|
+
if [ "$cur_cat" != "$prev_cat" ]; then
|
|
1628
|
+
cat_count=$(printf '%s\n' "$all_keys" | while IFS= read -r k; do
|
|
1629
|
+
[ -z "$k" ] && continue
|
|
1630
|
+
kc=$(_meta_category "$k"); [ -z "$kc" ] && kc="(未分類)"
|
|
1631
|
+
[ "$kc" = "$cur_cat" ] && printf 'x\n'
|
|
1632
|
+
done | grep -c .)
|
|
1633
|
+
printf '%s=== %s (%s keys) ===%s\n' "$_TUI_DIM" "$cur_cat" "$cat_count" "$_TUI_RESET"
|
|
1634
|
+
prev_cat="$cur_cat"
|
|
1635
|
+
fi
|
|
1636
|
+
if [ "$idx" -eq "$sel" ]; then
|
|
1637
|
+
printf '%s> %-46s%s\n' "$_TUI_REVERSE" "$key" "$_TUI_RESET"
|
|
1638
|
+
else
|
|
1639
|
+
printf ' %-46s\n' "$key"
|
|
1640
|
+
fi
|
|
1641
|
+
idx=$((idx + 1))
|
|
1642
|
+
done <<< "$all_keys"
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
# 選択中 key の effect panel を描画
|
|
1646
|
+
# $1: all_keys (改行区切り), $2: sel (0-based)
|
|
1647
|
+
# _tui_render helper 2/2
|
|
1648
|
+
# perf MED (defer): 毎 keypress で _get_default (mktemp + config-loader source) を呼ぶ。
|
|
1649
|
+
# キャッシュ化は別 task で実施 (behavior-preserving に defer)。
|
|
1650
|
+
_tui_render_effect_panel() {
|
|
1651
|
+
local all_keys="$1"
|
|
1652
|
+
local sel="$2"
|
|
1653
|
+
local cur_key
|
|
1654
|
+
cur_key=$(printf '%s' "$all_keys" | sed -n "$((sel + 1))p")
|
|
1655
|
+
[ -z "$cur_key" ] && return 0
|
|
1656
|
+
_tui_render_effect_panel_for_key "$cur_key"
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
# task-60 Step 5 iter 2 H3 fix (DRY 解消): 1 引数版 effect panel 描画 helper。
|
|
1660
|
+
# $1: 表示対象 key (空なら no-op)
|
|
1661
|
+
# 標準出力: 区切り行 + key/説明/型/現在値/default/変更効果 6 行 (`_tui_render_effect_panel` と同じ描画規約)。
|
|
1662
|
+
# Step 3 で `_tui_render_key_menu` 内に inline 完全再実装 (16 LOC) されていた effect panel を
|
|
1663
|
+
# 抽出し、`_tui_render_effect_panel` (all_keys+sel 2 引数) からも本 helper を呼ぶ形に揃えた。
|
|
1664
|
+
# bash 3.2 互換: local の単純宣言のみ。
|
|
1665
|
+
_tui_render_effect_panel_for_key() {
|
|
1666
|
+
local selected_key="$1"
|
|
1667
|
+
[ -z "$selected_key" ] && return 0
|
|
1668
|
+
local cur type raw desc effect def
|
|
1669
|
+
raw=$(_yml_get_raw "$CONFIG_PATH" "$selected_key" || printf '')
|
|
1670
|
+
cur=$(_get_current "$selected_key")
|
|
1671
|
+
def=$(_get_default "$selected_key" 2>/dev/null || printf '')
|
|
1672
|
+
type=$(_infer_type "$selected_key" "$raw")
|
|
1673
|
+
desc=$(_meta_desc "$selected_key")
|
|
1674
|
+
effect=$(_meta_effect "$selected_key")
|
|
1675
|
+
printf '\n%s---------------------------------------------------------------%s\n' "$_TUI_DIM" "$_TUI_RESET"
|
|
1676
|
+
printf '%skey%s : %s\n' "$_TUI_BOLD" "$_TUI_RESET" "$selected_key"
|
|
1677
|
+
printf '%s説明%s : %s\n' "$_TUI_BOLD" "$_TUI_RESET" "$desc"
|
|
1678
|
+
printf '%s型%s : %s\n' "$_TUI_BOLD" "$_TUI_RESET" "$type"
|
|
1679
|
+
printf '%s現在値%s : %s\n' "$_TUI_BOLD" "$_TUI_RESET" "$(printf '%s' "$cur" | tr '\n' ',')"
|
|
1680
|
+
printf '%sdefault%s : %s\n' "$_TUI_BOLD" "$_TUI_RESET" "$(printf '%s' "$def" | tr '\n' ',')"
|
|
1681
|
+
printf '%s変更効果%s: %s\n' "$_TUI_BOLD" "$_TUI_RESET" "$effect"
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
# task-60 Step 2 (2026-05-29): category 別 key 数を返す (lib 経由)
|
|
1685
|
+
# $1: category 名 (保護パス / ファイル配置 / state_dir / Gate/Confidence / feature_toggle / reviewer_control)
|
|
1686
|
+
# 戻り: stdout に key 数 (整数、lib 不在なら 0)
|
|
1687
|
+
#
|
|
1688
|
+
# `hc_metadata_keys_by_category` (.claude/scripts/lib/hc-config-metadata.sh) は category 完全一致の
|
|
1689
|
+
# key 群を改行区切りで返すため `grep -c .` で個数化する。lib 不在環境では guard で 0 を返す
|
|
1690
|
+
# (TUI 描画は壊さず "0 keys" 表示で degrade、_meta_* 系と同じ fallback 規律)。
|
|
1691
|
+
_meta_count_by_category() {
|
|
1692
|
+
local cat="$1"
|
|
1693
|
+
if command -v hc_metadata_keys_by_category >/dev/null 2>&1; then
|
|
1694
|
+
hc_metadata_keys_by_category "$cat" 2>/dev/null | grep -c '.' || printf '0'
|
|
1695
|
+
else
|
|
1696
|
+
printf '0'
|
|
1697
|
+
fi
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
# task-60 Step 2 (2026-05-29): 3-state machine の category_menu state 用 menu 描画
|
|
1701
|
+
# $1: 現在の category sel index (0-5、default 0)
|
|
1702
|
+
# 標準出力:
|
|
1703
|
+
# - 画面クリア + header (=== hc-config TUI === (↑/↓ 選択, Enter 決定, q 終了))
|
|
1704
|
+
# - 6 category 一覧 (保護パス / ファイル配置 / state_dir / Gate/Confidence /
|
|
1705
|
+
# feature_toggle / reviewer_control 順、draft §3.2 順拠)
|
|
1706
|
+
# - 選択行は "> " prefix + reverse video、非選択は " " prefix のみ
|
|
1707
|
+
# - 各 category の key count を "(N keys)" で表示 (_meta_count_by_category 経由)
|
|
1708
|
+
#
|
|
1709
|
+
# Step 4 で 3-state machine から呼出される。本 Step では関数定義のみ追加、呼出側は未配線。
|
|
1710
|
+
# bash 3.2 互換: index array (local -a) のみ使用、連想配列 / declare -g 不使用。
|
|
1711
|
+
_tui_render_category_menu() {
|
|
1712
|
+
local sel="${1:-0}"
|
|
1713
|
+
# task-60 Step 5 iter 2 H2 fix: cat_names は SSoT 定数 _TUI_CAT_NAMES_STR から展開 (DRY)。
|
|
1714
|
+
local -a cat_names
|
|
1715
|
+
IFS='|' read -r -a cat_names <<< "$_TUI_CAT_NAMES_STR"
|
|
1716
|
+
local i=0 cat count
|
|
1717
|
+
printf '%s' "$_TUI_CLEAR"
|
|
1718
|
+
printf '%s=== hc-config TUI ===%s (↑/↓ 選択, Enter 決定, q 終了)\n\n' "$_TUI_BOLD" "$_TUI_RESET"
|
|
1719
|
+
while [ "$i" -lt 6 ]; do
|
|
1720
|
+
cat="${cat_names[$i]}"
|
|
1721
|
+
count=$(_meta_count_by_category "$cat")
|
|
1722
|
+
if [ "$i" = "$sel" ]; then
|
|
1723
|
+
printf '%s> %-20s (%s keys)%s\n' "$_TUI_REVERSE" "$cat" "$count" "$_TUI_RESET"
|
|
1724
|
+
else
|
|
1725
|
+
printf ' %-20s (%s keys)\n' "$cat" "$count"
|
|
1726
|
+
fi
|
|
1727
|
+
i=$((i + 1))
|
|
1728
|
+
done
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
# task-60 Step 3 (2026-05-29): 3-state machine の key_menu state 用 menu 描画
|
|
1732
|
+
# $1: category index (0-5、`_tui_render_category_menu` の cat_names 順)
|
|
1733
|
+
# $2: 現在の key sel index (0-based、category 内の key 連番)
|
|
1734
|
+
# 標準出力:
|
|
1735
|
+
# - 画面クリア + header (=== hc-config TUI === category: <cat> (ESC: 戻る, q: 終了))
|
|
1736
|
+
# - category 配下の全 key を hc_metadata_keys_by_category 経由で取得し改行区切りで列挙
|
|
1737
|
+
# - 選択行は "> " prefix + reverse video、非選択は " " prefix
|
|
1738
|
+
# - 下部 effect panel (区切り行 + key/説明/型/現在値/default/変更効果) を _tui_render_effect_panel と同じ
|
|
1739
|
+
# 描画規約で inline 実装 (panel 関数は all_keys+sel 前提のため category scope 用に新規実装)
|
|
1740
|
+
#
|
|
1741
|
+
# Step 4 で 3-state machine の key_menu state から呼出される。本 Step では関数定義のみ追加、呼出側は未配線。
|
|
1742
|
+
# bash 3.2 互換: index array (local -a) のみ使用、連想配列 / declare -g 不使用。lib 不在時は (該当 key なし) で degrade。
|
|
1743
|
+
_tui_render_key_menu() {
|
|
1744
|
+
local cat_idx="${1:-0}"
|
|
1745
|
+
local key_sel="${2:-0}"
|
|
1746
|
+
# task-60 Step 5 iter 2 H2 fix: cat_names は SSoT 定数 _TUI_CAT_NAMES_STR から展開 (DRY)。
|
|
1747
|
+
local -a cat_names
|
|
1748
|
+
IFS='|' read -r -a cat_names <<< "$_TUI_CAT_NAMES_STR"
|
|
1749
|
+
local cat="${cat_names[$cat_idx]}"
|
|
1750
|
+
|
|
1751
|
+
printf '%s' "$_TUI_CLEAR"
|
|
1752
|
+
printf '%s=== hc-config TUI ===%s category: %s (ESC: 戻る, q: 終了)\n\n' "$_TUI_BOLD" "$_TUI_RESET" "$cat"
|
|
1753
|
+
|
|
1754
|
+
# category 配下 key 一覧取得 (lib 不在なら空)
|
|
1755
|
+
local keys=""
|
|
1756
|
+
if command -v hc_metadata_keys_by_category >/dev/null 2>&1; then
|
|
1757
|
+
keys=$(hc_metadata_keys_by_category "$cat" 2>/dev/null || true)
|
|
1758
|
+
fi
|
|
1759
|
+
if [ -z "$keys" ]; then
|
|
1760
|
+
printf ' (該当 key なし)\n'
|
|
1761
|
+
return 0
|
|
1762
|
+
fi
|
|
1763
|
+
|
|
1764
|
+
# key 一覧描画 (sel ハイライト) + 選択 key 捕捉
|
|
1765
|
+
local i=0 key selected_key=""
|
|
1766
|
+
while IFS= read -r key; do
|
|
1767
|
+
[ -z "$key" ] && continue
|
|
1768
|
+
if [ "$i" = "$key_sel" ]; then
|
|
1769
|
+
printf '%s> %-46s%s\n' "$_TUI_REVERSE" "$key" "$_TUI_RESET"
|
|
1770
|
+
selected_key="$key"
|
|
1771
|
+
else
|
|
1772
|
+
printf ' %-46s\n' "$key"
|
|
1773
|
+
fi
|
|
1774
|
+
i=$((i + 1))
|
|
1775
|
+
done <<< "$keys"
|
|
1776
|
+
|
|
1777
|
+
# task-60 Step 5 iter 2 H3 fix (DRY 解消): inline 完全再実装を
|
|
1778
|
+
# _tui_render_effect_panel_for_key (1 引数版 helper) 呼出 1 行に置換。
|
|
1779
|
+
_tui_render_effect_panel_for_key "$selected_key"
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
# key 一覧の選択画面 + effect panel を描画 (H3: category 境界に区切り行を挿入)
|
|
1783
|
+
# $1: category 順に並んだ全 key 改行区切り (1 行 1 key), $2: 選択 index (0-based、key のみ counts)
|
|
1784
|
+
# 区切り行 (=== <category> (N keys) ===) は選択対象外。sel は key の連番。
|
|
1785
|
+
_tui_render() {
|
|
1786
|
+
local all_keys="$1"
|
|
1787
|
+
local sel="$2"
|
|
1788
|
+
printf '%s' "$_TUI_CLEAR"
|
|
1789
|
+
printf '%s=== hc-config TUI ===%s (↑/↓ 選択, Enter 決定, q 終了)\n\n' "$_TUI_BOLD" "$_TUI_RESET"
|
|
1790
|
+
_tui_render_key_list "$all_keys" "$sel"
|
|
1791
|
+
_tui_render_effect_panel "$all_keys" "$sel"
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
# ENTER/RIGHT キーで key 選択 → 新値入力 → cmd_set を実行
|
|
1795
|
+
# $1: all_keys (改行区切り), $2: sel (0-based), $3: _stty_saved (canonical 復帰用)
|
|
1796
|
+
# 戻り: 0 = 処理完了 (raw mode は呼び出し元が復帰すること)
|
|
1797
|
+
# _cmd_interactive_tui helper
|
|
1798
|
+
_tui_handle_enter() {
|
|
1799
|
+
local all_keys="$1"
|
|
1800
|
+
local sel="$2"
|
|
1801
|
+
local stty_saved="$3"
|
|
1802
|
+
local cur_key
|
|
1803
|
+
cur_key=$(printf '%s' "$all_keys" | sed -n "$((sel + 1))p")
|
|
1804
|
+
[ -z "$cur_key" ] && return 0
|
|
1805
|
+
# H4 + C1: canonical mode に戻して行編集可能な入力を受ける
|
|
1806
|
+
[ -n "$stty_saved" ] && stty "$stty_saved" 2>/dev/null
|
|
1807
|
+
local effect
|
|
1808
|
+
effect=$(_meta_effect "$cur_key")
|
|
1809
|
+
# H4: 入力プロンプト直上に変更効果を再掲
|
|
1810
|
+
printf '%s\n\n%s変更効果:%s %s\n新値を入力 (空で skip): ' \
|
|
1811
|
+
"$_TUI_RESET" "$_TUI_BOLD" "$_TUI_RESET" "$effect"
|
|
1812
|
+
local newval
|
|
1813
|
+
IFS= read -r newval || newval=""
|
|
1814
|
+
if [ -n "$newval" ]; then
|
|
1815
|
+
printf '変更後の効果: %s\n続行? [y/N]: ' "$effect"
|
|
1816
|
+
local confirm
|
|
1817
|
+
IFS= read -r confirm || confirm=""
|
|
1818
|
+
case "$confirm" in
|
|
1819
|
+
y|Y|yes|YES)
|
|
1820
|
+
cmd_set "${cur_key}=${newval}" && _out "更新しました: ${cur_key}=${newval}"
|
|
1821
|
+
;;
|
|
1822
|
+
*)
|
|
1823
|
+
_out "skip しました"
|
|
1824
|
+
;;
|
|
1825
|
+
esac
|
|
1826
|
+
printf '(Enter で menu に戻る) '
|
|
1827
|
+
IFS= read -r _ || true
|
|
1828
|
+
fi
|
|
1829
|
+
return 0
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
# TUI 本体: 選択 → effect 確認 → 新値入力 → 確認 → cmd_set
|
|
1833
|
+
#
|
|
1834
|
+
# iter1 C1 fix (bash 3.2 互換 stty raw mode + trap 復元):
|
|
1835
|
+
# 矢印キーを 1 文字ずつ受け取るため端末を raw/cbreak mode に設定する。
|
|
1836
|
+
# 異常終了 (Ctrl-C / kill / pipe 断) でも端末が壊れないよう trap で復元 + 表示属性 reset。
|
|
1837
|
+
# trap EXIT は「関数 return」では発火しないため、正常 return 前にも明示復元する。
|
|
1838
|
+
# iter1 H4 fix (effect panel が Enter 後に消える):
|
|
1839
|
+
# ENTER で新値入力に入る際、入力プロンプト直上に「変更効果: <effect>」を 1 行再掲。
|
|
1840
|
+
# ENTER 時は canonical mode へ戻して行編集 (backspace 等) を効かせ、入力後 raw に復帰。
|
|
1841
|
+
#
|
|
1842
|
+
# task-60 Step 1 (2026-05-29): 旧 1 階層 flat 実装を `_cmd_interactive_tui_flat` に rename。
|
|
1843
|
+
# 新 `_cmd_interactive_tui` wrapper (本関数の直後) が `HC_HC_CONFIG_FLAT_NAVIGATION=true` env
|
|
1844
|
+
# による fallback switch を提供する。Step 4 で本 wrapper を 3-state machine (category_menu →
|
|
1845
|
+
# key_menu → effect_edit) に置換予定。Step 1 段階では env 有無に関わらず flat 動作を維持。
|
|
1846
|
+
_cmd_interactive_tui_flat() {
|
|
1847
|
+
local all_keys
|
|
1848
|
+
all_keys=$(_yml_list_keys "$CONFIG_PATH")
|
|
1849
|
+
# H3: category 順に並べ替え (区切り行表示は _tui_render が担当)
|
|
1850
|
+
all_keys=$(_tui_order_keys_by_category "$all_keys")
|
|
1851
|
+
local total
|
|
1852
|
+
total=$(printf '%s' "$all_keys" | grep -c '.')
|
|
1853
|
+
if [ "$total" -eq 0 ]; then
|
|
1854
|
+
_err "no keys found in ${CONFIG_PATH}"
|
|
1855
|
+
return 1
|
|
1856
|
+
fi
|
|
1857
|
+
|
|
1858
|
+
# C1: raw mode 設定 + trap 復元 (異常終了でも端末を壊さない)
|
|
1859
|
+
# qa/tdd 注記: Ctrl-C (INT) / SIGTERM (TERM) / 正常 EXIT 時の stty 復元を trap で保証する。
|
|
1860
|
+
# 非 TTY pipe では stty -g が空文字を返し raw mode 自体 no-op になるため smoke では再現不能。
|
|
1861
|
+
# 手動検証は Step 5 で実施済。
|
|
1862
|
+
local _stty_saved
|
|
1863
|
+
_stty_saved=$(stty -g 2>/dev/null) || _stty_saved=""
|
|
1864
|
+
# shellcheck disable=SC2064
|
|
1865
|
+
trap "[ -n \"$_stty_saved\" ] && stty \"$_stty_saved\" 2>/dev/null; printf '%s' \"$_TUI_RESET\"" EXIT INT TERM
|
|
1866
|
+
stty -icanon -echo min 1 time 0 2>/dev/null || true
|
|
1867
|
+
|
|
1868
|
+
local sel=0
|
|
1869
|
+
while true; do
|
|
1870
|
+
_tui_render "$all_keys" "$sel"
|
|
1871
|
+
local k
|
|
1872
|
+
k=$(_tui_read_key)
|
|
1873
|
+
case "$k" in
|
|
1874
|
+
UP) [ "$sel" -gt 0 ] && sel=$((sel - 1)) ;;
|
|
1875
|
+
DOWN) [ "$sel" -lt "$((total - 1))" ] && sel=$((sel + 1)) ;;
|
|
1876
|
+
QUIT|ESC)
|
|
1877
|
+
# 正常終了: canonical 復帰 + 表示属性 reset + trap 解除
|
|
1878
|
+
# task-60 Step 5 iter 4: `_tui_read_key` の戻り値に 'ESC' を追加 (M-new-1 真の fix)。
|
|
1879
|
+
# flat 実装は ESC = QUIT 同義扱いで Case 11 baseline 14/14 PASS を維持する
|
|
1880
|
+
# (旧挙動: 単独 ESC は QUIT に正規化されていた)。2tier 実装側で ESC vs QUIT 分離。
|
|
1881
|
+
[ -n "$_stty_saved" ] && stty "$_stty_saved" 2>/dev/null
|
|
1882
|
+
trap - EXIT INT TERM
|
|
1883
|
+
printf '%s\n' "$_TUI_RESET"; _out "bye."; return 0 ;;
|
|
1884
|
+
ENTER|RIGHT)
|
|
1885
|
+
_tui_handle_enter "$all_keys" "$sel" "$_stty_saved"
|
|
1886
|
+
# raw mode に復帰してループ継続
|
|
1887
|
+
stty -icanon -echo min 1 time 0 2>/dev/null || true
|
|
1888
|
+
;;
|
|
1889
|
+
*) : ;; # その他キーは無視 (区切り行は選択不可なので skip 不要)
|
|
1890
|
+
esac
|
|
1891
|
+
done
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
# task-60 Step 4 (2026-05-29): 3-state machine ループ (category_menu → key_menu → effect_edit)
|
|
1895
|
+
#
|
|
1896
|
+
# 2 階層 navigation 本体。Step 2 で追加した `_tui_render_category_menu` と Step 3 で追加した
|
|
1897
|
+
# `_tui_render_key_menu` を呼び出し、`_tui_read_key` (UP/DOWN/LEFT/RIGHT/ENTER/QUIT を返す抽象)
|
|
1898
|
+
# でキー入力を受ける。既存 flat 実装 (`_cmd_interactive_tui_flat`) と同じ raw mode + trap 復元
|
|
1899
|
+
# パターンを踏襲し、effect_edit state では `_tui_handle_enter` の入力 / 確認フローを inline 再現
|
|
1900
|
+
# (cmd_set "key=val" 形式必須、L1457 と同様)。
|
|
1901
|
+
#
|
|
1902
|
+
# state machine 遷移 (task-60 Step 5 iter 4 真の fix、draft §3.1 仕様完全準拠):
|
|
1903
|
+
# category_menu --ENTER--> key_menu
|
|
1904
|
+
# category_menu --q--> quit (全終了)
|
|
1905
|
+
# key_menu --ENTER--> effect_edit
|
|
1906
|
+
# key_menu --ESC/LEFT--> category_menu (back)
|
|
1907
|
+
# key_menu --q--> quit (全終了、draft §3.1 `key_menu --q--> quit` 通り)
|
|
1908
|
+
# effect_edit --完了/skip--> key_menu (自動戻り)
|
|
1909
|
+
#
|
|
1910
|
+
# sel 位置記憶 (bash 3.2 互換、scalar 7 var + eval 合成、連想配列 / declare -g 不使用):
|
|
1911
|
+
# _tui_cat_sel — category sel (0-5)
|
|
1912
|
+
# _tui_key_sel_0..5 — 各 category 配下の key sel (0-based、最終回戻り時に復元)
|
|
1913
|
+
_cmd_interactive_tui_2tier() {
|
|
1914
|
+
# sel 位置 (function-local、初期値 0、既存値は session 全体で持続させない)
|
|
1915
|
+
local _tui_cat_sel=0
|
|
1916
|
+
local _tui_key_sel_0=0
|
|
1917
|
+
local _tui_key_sel_1=0
|
|
1918
|
+
local _tui_key_sel_2=0
|
|
1919
|
+
local _tui_key_sel_3=0
|
|
1920
|
+
local _tui_key_sel_4=0
|
|
1921
|
+
local _tui_key_sel_5=0
|
|
1922
|
+
|
|
1923
|
+
# raw mode + trap 復元 (flat 実装 L1499-1503 と同じパターン)
|
|
1924
|
+
local _stty_saved
|
|
1925
|
+
_stty_saved=$(stty -g 2>/dev/null) || _stty_saved=""
|
|
1926
|
+
# shellcheck disable=SC2064
|
|
1927
|
+
trap "[ -n \"$_stty_saved\" ] && stty \"$_stty_saved\" 2>/dev/null; printf '%s' \"$_TUI_RESET\"" EXIT INT TERM
|
|
1928
|
+
stty -icanon -echo min 1 time 0 2>/dev/null || true
|
|
1929
|
+
|
|
1930
|
+
local state="category_menu"
|
|
1931
|
+
# task-60 Step 5 iter 2 H2 fix: cat_names は SSoT 定数 _TUI_CAT_NAMES_STR から展開 (DRY)。
|
|
1932
|
+
local -a cat_names
|
|
1933
|
+
IFS='|' read -r -a cat_names <<< "$_TUI_CAT_NAMES_STR"
|
|
1934
|
+
local k cur_key_sel key_max cat selected_key keys_list effect newval confirm
|
|
1935
|
+
|
|
1936
|
+
# task-60 Step 5 iter 2 H4 fix (eval safety): `eval "_tui_key_sel_${_tui_cat_sel}=..."` の
|
|
1937
|
+
# `_tui_cat_sel` が未定義 / 空文字 / 非数値に化けた場合、`eval "_tui_key_sel_=..."` で global
|
|
1938
|
+
# var `_tui_key_sel_` を生成し汚染するリスクがある (raw mode + trap race 限定で実害再現可)。
|
|
1939
|
+
# 関数頭で数値 sanitize して 0-5 範囲外なら 0 に reset する。state 入口でも再 sanitize する。
|
|
1940
|
+
case "$_tui_cat_sel" in
|
|
1941
|
+
''|*[!0-9]*) _tui_cat_sel=0 ;;
|
|
1942
|
+
esac
|
|
1943
|
+
if [ "$_tui_cat_sel" -lt 0 ] || [ "$_tui_cat_sel" -gt 5 ]; then
|
|
1944
|
+
_tui_cat_sel=0
|
|
1945
|
+
fi
|
|
1946
|
+
|
|
1947
|
+
while :; do
|
|
1948
|
+
case "$state" in
|
|
1949
|
+
category_menu)
|
|
1950
|
+
_tui_render_category_menu "$_tui_cat_sel"
|
|
1951
|
+
k=$(_tui_read_key)
|
|
1952
|
+
case "$k" in
|
|
1953
|
+
UP) [ "$_tui_cat_sel" -gt 0 ] && _tui_cat_sel=$((_tui_cat_sel - 1)) ;;
|
|
1954
|
+
DOWN) [ "$_tui_cat_sel" -lt 5 ] && _tui_cat_sel=$((_tui_cat_sel + 1)) ;;
|
|
1955
|
+
ENTER|RIGHT)
|
|
1956
|
+
state="key_menu"
|
|
1957
|
+
;;
|
|
1958
|
+
QUIT)
|
|
1959
|
+
[ -n "$_stty_saved" ] && stty "$_stty_saved" 2>/dev/null
|
|
1960
|
+
trap - EXIT INT TERM
|
|
1961
|
+
printf '%s\n' "$_TUI_RESET"
|
|
1962
|
+
_out "bye."
|
|
1963
|
+
return 0
|
|
1964
|
+
;;
|
|
1965
|
+
*) : ;;
|
|
1966
|
+
esac
|
|
1967
|
+
;;
|
|
1968
|
+
key_menu)
|
|
1969
|
+
# task-60 Step 5 iter 2 H4 fix (eval safety): state 入口で _tui_cat_sel を再 sanitize。
|
|
1970
|
+
case "$_tui_cat_sel" in
|
|
1971
|
+
''|*[!0-9]*) _tui_cat_sel=0 ;;
|
|
1972
|
+
esac
|
|
1973
|
+
if [ "$_tui_cat_sel" -lt 0 ] || [ "$_tui_cat_sel" -gt 5 ]; then
|
|
1974
|
+
_tui_cat_sel=0
|
|
1975
|
+
fi
|
|
1976
|
+
# 現 category の key sel と key 数を取得 (eval で変数名合成、bash 3.2 互換)
|
|
1977
|
+
eval "cur_key_sel=\${_tui_key_sel_${_tui_cat_sel}:-0}"
|
|
1978
|
+
cat="${cat_names[$_tui_cat_sel]}"
|
|
1979
|
+
key_max=$(_meta_count_by_category "$cat" 2>/dev/null || printf '0')
|
|
1980
|
+
# key_max が空 or 非数値 → 0 に fallback
|
|
1981
|
+
case "$key_max" in
|
|
1982
|
+
''|*[!0-9]*) key_max=0 ;;
|
|
1983
|
+
esac
|
|
1984
|
+
# sel が range 外なら 0 に reset
|
|
1985
|
+
if [ "$key_max" -eq 0 ]; then
|
|
1986
|
+
# 該当 key なし → 入力受付。
|
|
1987
|
+
# task-60 Step 5 iter 4 真の fix (M-new-1、draft §3.1 仕様準拠):
|
|
1988
|
+
# ESC|LEFT|ENTER|RIGHT|その他 → category_menu へ back
|
|
1989
|
+
# QUIT (q) のみ全終了 (draft §3.1 `key_menu --q--> quit` 通り)
|
|
1990
|
+
_tui_render_key_menu "$_tui_cat_sel" 0
|
|
1991
|
+
k=$(_tui_read_key)
|
|
1992
|
+
case "$k" in
|
|
1993
|
+
QUIT)
|
|
1994
|
+
[ -n "$_stty_saved" ] && stty "$_stty_saved" 2>/dev/null
|
|
1995
|
+
trap - EXIT INT TERM
|
|
1996
|
+
printf '%s\n' "$_TUI_RESET"
|
|
1997
|
+
_out "bye."
|
|
1998
|
+
return 0
|
|
1999
|
+
;;
|
|
2000
|
+
*)
|
|
2001
|
+
state="category_menu"
|
|
2002
|
+
continue
|
|
2003
|
+
;;
|
|
2004
|
+
esac
|
|
2005
|
+
fi
|
|
2006
|
+
if [ "$cur_key_sel" -ge "$key_max" ] || [ "$cur_key_sel" -lt 0 ]; then
|
|
2007
|
+
cur_key_sel=0
|
|
2008
|
+
eval "_tui_key_sel_${_tui_cat_sel}=0"
|
|
2009
|
+
fi
|
|
2010
|
+
|
|
2011
|
+
_tui_render_key_menu "$_tui_cat_sel" "$cur_key_sel"
|
|
2012
|
+
k=$(_tui_read_key)
|
|
2013
|
+
case "$k" in
|
|
2014
|
+
UP)
|
|
2015
|
+
[ "$cur_key_sel" -gt 0 ] && cur_key_sel=$((cur_key_sel - 1))
|
|
2016
|
+
eval "_tui_key_sel_${_tui_cat_sel}=\${cur_key_sel}"
|
|
2017
|
+
;;
|
|
2018
|
+
DOWN)
|
|
2019
|
+
[ "$cur_key_sel" -lt "$((key_max - 1))" ] && cur_key_sel=$((cur_key_sel + 1))
|
|
2020
|
+
eval "_tui_key_sel_${_tui_cat_sel}=\${cur_key_sel}"
|
|
2021
|
+
;;
|
|
2022
|
+
ESC|LEFT)
|
|
2023
|
+
# task-60 Step 5 iter 4 真の fix (M-new-1、draft §3.1 仕様乖離解消):
|
|
2024
|
+
# `_tui_read_key` が単独 ESC を 'ESC' で返すよう修正 (L1207-1219) されたため、
|
|
2025
|
+
# ESC|LEFT を back trigger として直接扱える (iter 2 fix の QUIT 経由 reroute は廃止)。
|
|
2026
|
+
# draft §3.1 `key_menu --ESC/LEFT--> category_menu` + DoD「key 一覧で ESC または
|
|
2027
|
+
# LEFT で category 一覧に戻る」を満たす。
|
|
2028
|
+
state="category_menu"
|
|
2029
|
+
;;
|
|
2030
|
+
ENTER|RIGHT)
|
|
2031
|
+
state="effect_edit"
|
|
2032
|
+
;;
|
|
2033
|
+
QUIT)
|
|
2034
|
+
# task-60 Step 5 iter 4 真の fix (M-new-1、draft §3.1 仕様乖離解消):
|
|
2035
|
+
# QUIT (= q キー、L1216 で正規化) を全終了として扱う (draft §3.1
|
|
2036
|
+
# `key_menu --q--> quit (全終了)` 通り)。iter 2 fix では `_tui_read_key` の戻り値
|
|
2037
|
+
# 仕様を維持するため QUIT を back に reroute していたが、iter 4 で `_tui_read_key`
|
|
2038
|
+
# が ESC を独立した戻り値として返すようになり ESC vs QUIT 分離が可能になった。
|
|
2039
|
+
[ -n "$_stty_saved" ] && stty "$_stty_saved" 2>/dev/null
|
|
2040
|
+
trap - EXIT INT TERM
|
|
2041
|
+
printf '%s\n' "$_TUI_RESET"
|
|
2042
|
+
_out "bye."
|
|
2043
|
+
return 0
|
|
2044
|
+
;;
|
|
2045
|
+
*) : ;;
|
|
2046
|
+
esac
|
|
2047
|
+
;;
|
|
2048
|
+
effect_edit)
|
|
2049
|
+
# task-60 Step 5 iter 2 H4 fix (eval safety): state 入口で _tui_cat_sel を再 sanitize。
|
|
2050
|
+
case "$_tui_cat_sel" in
|
|
2051
|
+
''|*[!0-9]*) _tui_cat_sel=0 ;;
|
|
2052
|
+
esac
|
|
2053
|
+
if [ "$_tui_cat_sel" -lt 0 ] || [ "$_tui_cat_sel" -gt 5 ]; then
|
|
2054
|
+
_tui_cat_sel=0
|
|
2055
|
+
fi
|
|
2056
|
+
# 選択 key 取得 (key_menu と同じ計算)
|
|
2057
|
+
eval "cur_key_sel=\${_tui_key_sel_${_tui_cat_sel}:-0}"
|
|
2058
|
+
cat="${cat_names[$_tui_cat_sel]}"
|
|
2059
|
+
selected_key=""
|
|
2060
|
+
if command -v hc_metadata_keys_by_category >/dev/null 2>&1; then
|
|
2061
|
+
keys_list=$(hc_metadata_keys_by_category "$cat" 2>/dev/null || true)
|
|
2062
|
+
selected_key=$(printf '%s\n' "$keys_list" | sed -n "$((cur_key_sel + 1))p")
|
|
2063
|
+
fi
|
|
2064
|
+
if [ -z "$selected_key" ]; then
|
|
2065
|
+
# key 取得失敗 → key_menu に戻る (description: lib 不在 or category 空)
|
|
2066
|
+
state="key_menu"
|
|
2067
|
+
continue
|
|
2068
|
+
fi
|
|
2069
|
+
|
|
2070
|
+
# canonical mode に戻して行編集可能な入力を受ける (flat _tui_handle_enter L1443 と同じパターン)
|
|
2071
|
+
[ -n "$_stty_saved" ] && stty "$_stty_saved" 2>/dev/null
|
|
2072
|
+
effect=$(_meta_effect "$selected_key" 2>/dev/null || printf '')
|
|
2073
|
+
printf '%s\n\n%s変更効果:%s %s\n新値を入力 (空で skip): ' \
|
|
2074
|
+
"$_TUI_RESET" "$_TUI_BOLD" "$_TUI_RESET" "$effect"
|
|
2075
|
+
newval=""
|
|
2076
|
+
IFS= read -r newval || newval=""
|
|
2077
|
+
if [ -n "$newval" ]; then
|
|
2078
|
+
printf '変更後の効果: %s\n続行? [y/N]: ' "$effect"
|
|
2079
|
+
confirm=""
|
|
2080
|
+
IFS= read -r confirm || confirm=""
|
|
2081
|
+
case "$confirm" in
|
|
2082
|
+
y|Y|yes|YES)
|
|
2083
|
+
cmd_set "${selected_key}=${newval}" && _out "更新しました: ${selected_key}=${newval}"
|
|
2084
|
+
;;
|
|
2085
|
+
*)
|
|
2086
|
+
_out "skip しました"
|
|
2087
|
+
;;
|
|
2088
|
+
esac
|
|
2089
|
+
printf '(Enter で menu に戻る) '
|
|
2090
|
+
IFS= read -r _ || true
|
|
2091
|
+
fi
|
|
2092
|
+
# raw mode に復帰して key_menu に戻る
|
|
2093
|
+
stty -icanon -echo min 1 time 0 2>/dev/null || true
|
|
2094
|
+
state="key_menu"
|
|
2095
|
+
;;
|
|
2096
|
+
esac
|
|
2097
|
+
done
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
# === task-60 Step 1: flat fallback wrapper ===
|
|
2101
|
+
#
|
|
2102
|
+
# `_cmd_interactive_tui` は cmd_interactive (TTY menu dispatcher) から呼ばれる entry point。
|
|
2103
|
+
# - HC_HC_CONFIG_FLAT_NAVIGATION=true → 明示的に旧 flat 実装 (`_cmd_interactive_tui_flat`) 起動
|
|
2104
|
+
# - 上記以外 → Step 4 で配線した `_cmd_interactive_tui_2tier` (3-state machine)
|
|
2105
|
+
#
|
|
2106
|
+
# Step 4 (2026-05-29) で TODO comment 部分を `_cmd_interactive_tui_2tier` 呼出に置換完了。
|
|
2107
|
+
_cmd_interactive_tui() {
|
|
2108
|
+
# task-60 Step 1: env fallback switch
|
|
2109
|
+
if [ "${HC_HC_CONFIG_FLAT_NAVIGATION:-}" = "true" ]; then
|
|
2110
|
+
_cmd_interactive_tui_flat
|
|
2111
|
+
return $?
|
|
2112
|
+
fi
|
|
2113
|
+
# task-60 Step 4: 3-state machine ループに配線
|
|
2114
|
+
_cmd_interactive_tui_2tier
|
|
2115
|
+
return $?
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
# === 対話 menu dispatcher (TTY check + fallback) ===
|
|
2119
|
+
#
|
|
2120
|
+
# task-48 Step 3: TTY なら矢印キー TUI、非 TTY (pipe / CI) or HC_HC_CONFIG_FORCE_NUMERIC=1 なら
|
|
2121
|
+
# 番号選択 menu に降格。
|
|
2122
|
+
#
|
|
2123
|
+
# task-61 Step 1 (2026-05-29): TTY 時の経路を Web UI default + legacy env switch に拡張。
|
|
2124
|
+
# - HC_HC_CONFIG_TUI_LEGACY=true → 明示的に task-60 TUI (`_cmd_interactive_tui`) 起動
|
|
2125
|
+
# - 上記以外 (default) → `_cmd_interactive_web` (Step 1 では placeholder + TUI 降格)
|
|
2126
|
+
# 非 TTY 経路は従来通り `_cmd_interactive_numeric`。
|
|
2127
|
+
# Step 2 で `_cmd_interactive_web` を hc-config-web-server.js 起動に置換予定。
|
|
2128
|
+
#
|
|
2129
|
+
# task-61 Step 5 iter 2 D (2026-05-29): yml feature toggle 経路を追加 (OR 結合)。
|
|
2130
|
+
# - HC_HC_CONFIG_TUI_LEGACY=true (legacy compat env、Step 1 起源、user 直接 set 用)
|
|
2131
|
+
# - HC_FEATURE_HC_CONFIG_TUI_LEGACY_ENABLED=true (yml feature_hc_config_tui_legacy_enabled 経由)
|
|
2132
|
+
# どちらかが true なら TUI fallback (両者 OR 結合、後方互換維持)。
|
|
2133
|
+
# 起源: Design Constraints 「機能 on/off は yml feature toggle で集中管理」 + Step 5 iter 1 qa-expert H-Q2。
|
|
2134
|
+
cmd_interactive() {
|
|
2135
|
+
if [ -t 0 ] && [ -t 1 ] && [ "${HC_HC_CONFIG_FORCE_NUMERIC:-}" != "1" ]; then
|
|
2136
|
+
if [ "${HC_HC_CONFIG_TUI_LEGACY:-}" = "true" ] || [ "${HC_FEATURE_HC_CONFIG_TUI_LEGACY_ENABLED:-}" = "true" ]; then
|
|
2137
|
+
_cmd_interactive_tui
|
|
2138
|
+
else
|
|
2139
|
+
_cmd_interactive_web
|
|
2140
|
+
fi
|
|
2141
|
+
else
|
|
2142
|
+
_cmd_interactive_numeric
|
|
2143
|
+
fi
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
# === task-61 Step 2: Web UI entry (Node.js HTTP server 起動) ===
|
|
2147
|
+
#
|
|
2148
|
+
# Node.js 標準 module のみで実装された hc-config-web-server.js を起動する。
|
|
2149
|
+
# node binary 不在 / server.js 不在の場合は task-60 TUI に降格 (graceful fallback)。
|
|
2150
|
+
# legacy env (`HC_HC_CONFIG_TUI_LEGACY=true`) は cmd_interactive 側で TUI 直起動経路を選択。
|
|
2151
|
+
#
|
|
2152
|
+
# 動作:
|
|
2153
|
+
# 1. node binary 探索 (`command -v node`)、不在なら WARN + TUI 降格
|
|
2154
|
+
# 2. server.js 存在確認、不在なら WARN + TUI 降格
|
|
2155
|
+
# 3. foreground で `node <server.js>` 起動 (Ctrl+C で graceful shutdown)
|
|
2156
|
+
# port 探索 / browser auto-open は server.js 側が担当
|
|
2157
|
+
_cmd_interactive_web() {
|
|
2158
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
2159
|
+
printf 'WARN: Node.js not installed. Falling back to TUI.\n' >&2
|
|
2160
|
+
printf ' Install Node.js or set HC_HC_CONFIG_TUI_LEGACY=true to silence.\n' >&2
|
|
2161
|
+
_cmd_interactive_tui
|
|
2162
|
+
return $?
|
|
2163
|
+
fi
|
|
2164
|
+
|
|
2165
|
+
local server_js="${SCRIPT_DIR}/lib/hc-config-web-server.js"
|
|
2166
|
+
if [ ! -f "$server_js" ]; then
|
|
2167
|
+
printf 'WARN: Web UI server not found at %s. Falling back to TUI.\n' "$server_js" >&2
|
|
2168
|
+
_cmd_interactive_tui
|
|
2169
|
+
return $?
|
|
2170
|
+
fi
|
|
2171
|
+
|
|
2172
|
+
node "$server_js" "$@"
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
# === arg parser ===
|
|
2176
|
+
|
|
2177
|
+
# cmd に引数 $2 が必要かチェック。不在なら error + return 1
|
|
2178
|
+
# $1: cmd (例: --get), $2: 引数値 (省略可)
|
|
2179
|
+
_main_require_arg() {
|
|
2180
|
+
local cmd="$1"
|
|
2181
|
+
local arg="${2:-}"
|
|
2182
|
+
if [ -z "$arg" ]; then
|
|
2183
|
+
_err "${cmd} requires an argument"
|
|
2184
|
+
return 1
|
|
2185
|
+
fi
|
|
2186
|
+
return 0
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
# CLI args dispatch
|
|
2190
|
+
# $@: --config 除外済み引数
|
|
2191
|
+
_main_dispatch() {
|
|
2192
|
+
local cmd="${1:-}"
|
|
2193
|
+
local arg="${2:-}"
|
|
2194
|
+
case "$cmd" in
|
|
2195
|
+
--list)
|
|
2196
|
+
# --list [--verbose|--show-default] (task-48 Step 3)
|
|
2197
|
+
case "$arg" in
|
|
2198
|
+
--verbose|-v) cmd_list "verbose" ;;
|
|
2199
|
+
--show-default) cmd_list "show-default" ;;
|
|
2200
|
+
""|*) cmd_list "" ;;
|
|
2201
|
+
esac
|
|
2202
|
+
;;
|
|
2203
|
+
--get) _main_require_arg "$cmd" "$arg" && cmd_get "$arg" ;;
|
|
2204
|
+
--set) _main_require_arg "$cmd" "$arg" && cmd_set "$arg" ;;
|
|
2205
|
+
--feature) _main_require_arg "$cmd" "$arg" && cmd_feature "$arg" ;;
|
|
2206
|
+
--reset) _main_require_arg "$cmd" "$arg" && cmd_reset "$arg" ;;
|
|
2207
|
+
--reset-all) cmd_reset_all ;;
|
|
2208
|
+
--diff) cmd_diff ;;
|
|
2209
|
+
--summary) cmd_summary ;;
|
|
2210
|
+
--validate) cmd_validate ;;
|
|
2211
|
+
--migrate) cmd_migrate ;;
|
|
2212
|
+
--list-deprecated) cmd_list_deprecated ;;
|
|
2213
|
+
--help|-h) cmd_help ;;
|
|
2214
|
+
interactive) cmd_interactive ;;
|
|
2215
|
+
*)
|
|
2216
|
+
_err "unknown command: ${cmd}"
|
|
2217
|
+
_err "run 'hc-config.sh --help' for usage"
|
|
2218
|
+
return 1
|
|
2219
|
+
;;
|
|
2220
|
+
esac
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
main() {
|
|
2224
|
+
# --config <path> を最初に処理 (他 cmd の前提)
|
|
2225
|
+
# インラインループで CONFIG_PATH を設定 (subshell 回避)
|
|
2226
|
+
local args=("$@")
|
|
2227
|
+
local new_args=()
|
|
2228
|
+
local i=0
|
|
2229
|
+
while [ "$i" -lt "${#args[@]}" ]; do
|
|
2230
|
+
case "${args[$i]}" in
|
|
2231
|
+
--config)
|
|
2232
|
+
i=$((i + 1))
|
|
2233
|
+
CONFIG_PATH="${args[$i]}"
|
|
2234
|
+
;;
|
|
2235
|
+
--config=*)
|
|
2236
|
+
CONFIG_PATH="${args[$i]#--config=}"
|
|
2237
|
+
;;
|
|
2238
|
+
*)
|
|
2239
|
+
new_args+=("${args[$i]}")
|
|
2240
|
+
;;
|
|
2241
|
+
esac
|
|
2242
|
+
i=$((i + 1))
|
|
2243
|
+
done
|
|
2244
|
+
|
|
2245
|
+
# default config path
|
|
2246
|
+
if [ -z "$CONFIG_PATH" ]; then
|
|
2247
|
+
CONFIG_PATH="$DEFAULT_CONFIG"
|
|
2248
|
+
fi
|
|
2249
|
+
if [ ! -f "$CONFIG_PATH" ]; then
|
|
2250
|
+
_err "config not found: ${CONFIG_PATH}"
|
|
2251
|
+
return 1
|
|
2252
|
+
fi
|
|
2253
|
+
if ! _validate_config_path "$CONFIG_PATH"; then
|
|
2254
|
+
return 1
|
|
2255
|
+
fi
|
|
2256
|
+
|
|
2257
|
+
if [ "${#new_args[@]}" -eq 0 ]; then
|
|
2258
|
+
cmd_interactive
|
|
2259
|
+
return $?
|
|
2260
|
+
fi
|
|
2261
|
+
|
|
2262
|
+
_main_dispatch "${new_args[@]}"
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
main "$@"
|