@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.
Files changed (822) hide show
  1. package/.claude/.stale-harness-state/last-check +0 -0
  2. package/.claude/CommonRules.md +121 -0
  3. package/.claude/agents/01-core-development/api-designer.md +237 -0
  4. package/.claude/agents/01-core-development/backend-developer.md +222 -0
  5. package/.claude/agents/01-core-development/design-bridge.md +127 -0
  6. package/.claude/agents/01-core-development/electron-pro.md +240 -0
  7. package/.claude/agents/01-core-development/frontend-developer.md +133 -0
  8. package/.claude/agents/01-core-development/fullstack-developer.md +235 -0
  9. package/.claude/agents/01-core-development/graphql-architect.md +238 -0
  10. package/.claude/agents/01-core-development/microservices-architect.md +239 -0
  11. package/.claude/agents/01-core-development/mobile-developer.md +283 -0
  12. package/.claude/agents/01-core-development/ui-designer.md +174 -0
  13. package/.claude/agents/01-core-development/websocket-engineer.md +150 -0
  14. package/.claude/agents/03-infrastructure/azure-infra-engineer.md +53 -0
  15. package/.claude/agents/03-infrastructure/cloud-architect.md +277 -0
  16. package/.claude/agents/03-infrastructure/database-administrator.md +287 -0
  17. package/.claude/agents/03-infrastructure/deployment-engineer.md +287 -0
  18. package/.claude/agents/03-infrastructure/devops-engineer.md +287 -0
  19. package/.claude/agents/03-infrastructure/devops-incident-responder.md +287 -0
  20. package/.claude/agents/03-infrastructure/docker-expert.md +278 -0
  21. package/.claude/agents/03-infrastructure/incident-responder.md +287 -0
  22. package/.claude/agents/03-infrastructure/kubernetes-specialist.md +287 -0
  23. package/.claude/agents/03-infrastructure/network-engineer.md +287 -0
  24. package/.claude/agents/03-infrastructure/platform-engineer.md +287 -0
  25. package/.claude/agents/03-infrastructure/security-engineer.md +277 -0
  26. package/.claude/agents/03-infrastructure/sre-engineer.md +287 -0
  27. package/.claude/agents/03-infrastructure/terraform-engineer.md +287 -0
  28. package/.claude/agents/03-infrastructure/terragrunt-expert.md +307 -0
  29. package/.claude/agents/03-infrastructure/windows-infra-admin.md +52 -0
  30. package/.claude/agents/04-quality-security/accessibility-tester.md +277 -0
  31. package/.claude/agents/04-quality-security/ad-security-reviewer.md +56 -0
  32. package/.claude/agents/04-quality-security/ai-writing-auditor.md +77 -0
  33. package/.claude/agents/04-quality-security/architect-reviewer.md +287 -0
  34. package/.claude/agents/04-quality-security/chaos-engineer.md +277 -0
  35. package/.claude/agents/04-quality-security/code-reviewer.md +287 -0
  36. package/.claude/agents/04-quality-security/compliance-auditor.md +277 -0
  37. package/.claude/agents/04-quality-security/debugger.md +287 -0
  38. package/.claude/agents/04-quality-security/error-detective.md +287 -0
  39. package/.claude/agents/04-quality-security/penetration-tester.md +287 -0
  40. package/.claude/agents/04-quality-security/performance-engineer.md +287 -0
  41. package/.claude/agents/04-quality-security/powershell-security-hardening.md +54 -0
  42. package/.claude/agents/04-quality-security/qa-expert.md +287 -0
  43. package/.claude/agents/04-quality-security/security-auditor.md +287 -0
  44. package/.claude/agents/04-quality-security/test-automator.md +287 -0
  45. package/.claude/agents/04-quality-security/ui-ux-tester.md +234 -0
  46. package/.claude/agents/06-developer-experience/build-engineer.md +286 -0
  47. package/.claude/agents/06-developer-experience/cli-developer.md +286 -0
  48. package/.claude/agents/06-developer-experience/dependency-manager.md +286 -0
  49. package/.claude/agents/06-developer-experience/documentation-engineer.md +276 -0
  50. package/.claude/agents/06-developer-experience/dx-optimizer.md +286 -0
  51. package/.claude/agents/06-developer-experience/git-workflow-manager.md +286 -0
  52. package/.claude/agents/06-developer-experience/legacy-modernizer.md +286 -0
  53. package/.claude/agents/06-developer-experience/mcp-developer.md +275 -0
  54. package/.claude/agents/06-developer-experience/powershell-module-architect.md +58 -0
  55. package/.claude/agents/06-developer-experience/powershell-ui-architect.md +135 -0
  56. package/.claude/agents/06-developer-experience/readme-generator.md +238 -0
  57. package/.claude/agents/06-developer-experience/refactoring-specialist.md +286 -0
  58. package/.claude/agents/06-developer-experience/slack-expert.md +232 -0
  59. package/.claude/agents/06-developer-experience/tooling-engineer.md +286 -0
  60. package/.claude/agents/09-meta-orchestration/agent-installer.md +97 -0
  61. package/.claude/agents/09-meta-orchestration/agent-organizer.md +287 -0
  62. package/.claude/agents/09-meta-orchestration/codebase-orchestrator.md +249 -0
  63. package/.claude/agents/09-meta-orchestration/context-manager.md +287 -0
  64. package/.claude/agents/09-meta-orchestration/error-coordinator.md +287 -0
  65. package/.claude/agents/09-meta-orchestration/it-ops-orchestrator.md +60 -0
  66. package/.claude/agents/09-meta-orchestration/knowledge-synthesizer.md +287 -0
  67. package/.claude/agents/09-meta-orchestration/multi-agent-coordinator.md +287 -0
  68. package/.claude/agents/09-meta-orchestration/performance-monitor.md +287 -0
  69. package/.claude/agents/09-meta-orchestration/task-distributor.md +287 -0
  70. package/.claude/agents/09-meta-orchestration/workflow-orchestrator.md +287 -0
  71. package/.claude/agents/10-research-analysis/competitive-analyst.md +287 -0
  72. package/.claude/agents/10-research-analysis/data-researcher.md +287 -0
  73. package/.claude/agents/10-research-analysis/market-researcher.md +287 -0
  74. package/.claude/agents/10-research-analysis/project-idea-validator.md +269 -0
  75. package/.claude/agents/10-research-analysis/research-analyst.md +287 -0
  76. package/.claude/agents/10-research-analysis/scientific-literature-researcher.md +151 -0
  77. package/.claude/agents/10-research-analysis/search-specialist.md +287 -0
  78. package/.claude/agents/10-research-analysis/trend-analyst.md +287 -0
  79. package/.claude/archive/README.md +47 -0
  80. package/.claude/archive/agents/02-language-specialists/angular-architect.md +287 -0
  81. package/.claude/archive/agents/02-language-specialists/cpp-pro.md +277 -0
  82. package/.claude/archive/agents/02-language-specialists/csharp-developer.md +287 -0
  83. package/.claude/archive/agents/02-language-specialists/django-developer.md +287 -0
  84. package/.claude/archive/agents/02-language-specialists/dotnet-core-expert.md +287 -0
  85. package/.claude/archive/agents/02-language-specialists/dotnet-framework-4.8-expert.md +306 -0
  86. package/.claude/archive/agents/02-language-specialists/elixir-expert.md +311 -0
  87. package/.claude/archive/agents/02-language-specialists/expo-react-native-expert.md +268 -0
  88. package/.claude/archive/agents/02-language-specialists/fastapi-developer.md +287 -0
  89. package/.claude/archive/agents/02-language-specialists/flutter-expert.md +287 -0
  90. package/.claude/archive/agents/02-language-specialists/golang-pro.md +277 -0
  91. package/.claude/archive/agents/02-language-specialists/java-architect.md +287 -0
  92. package/.claude/archive/agents/02-language-specialists/javascript-pro.md +277 -0
  93. package/.claude/archive/agents/02-language-specialists/kotlin-specialist.md +287 -0
  94. package/.claude/archive/agents/02-language-specialists/laravel-specialist.md +287 -0
  95. package/.claude/archive/agents/02-language-specialists/nextjs-developer.md +287 -0
  96. package/.claude/archive/agents/02-language-specialists/node-specialist.md +124 -0
  97. package/.claude/archive/agents/02-language-specialists/php-pro.md +287 -0
  98. package/.claude/archive/agents/02-language-specialists/powershell-5.1-expert.md +59 -0
  99. package/.claude/archive/agents/02-language-specialists/powershell-7-expert.md +57 -0
  100. package/.claude/archive/agents/02-language-specialists/python-pro.md +277 -0
  101. package/.claude/archive/agents/02-language-specialists/rails-expert.md +358 -0
  102. package/.claude/archive/agents/02-language-specialists/react-specialist.md +287 -0
  103. package/.claude/archive/agents/02-language-specialists/rust-engineer.md +287 -0
  104. package/.claude/archive/agents/02-language-specialists/spring-boot-engineer.md +287 -0
  105. package/.claude/archive/agents/02-language-specialists/sql-pro.md +287 -0
  106. package/.claude/archive/agents/02-language-specialists/swift-expert.md +287 -0
  107. package/.claude/archive/agents/02-language-specialists/symfony-specialist.md +354 -0
  108. package/.claude/archive/agents/02-language-specialists/typescript-pro.md +277 -0
  109. package/.claude/archive/agents/02-language-specialists/vue-expert.md +287 -0
  110. package/.claude/archive/agents/05-data-ai/ai-engineer.md +287 -0
  111. package/.claude/archive/agents/05-data-ai/data-analyst.md +277 -0
  112. package/.claude/archive/agents/05-data-ai/data-engineer.md +287 -0
  113. package/.claude/archive/agents/05-data-ai/data-scientist.md +287 -0
  114. package/.claude/archive/agents/05-data-ai/database-optimizer.md +287 -0
  115. package/.claude/archive/agents/05-data-ai/llm-architect.md +287 -0
  116. package/.claude/archive/agents/05-data-ai/machine-learning-engineer.md +277 -0
  117. package/.claude/archive/agents/05-data-ai/ml-engineer.md +287 -0
  118. package/.claude/archive/agents/05-data-ai/mlops-engineer.md +287 -0
  119. package/.claude/archive/agents/05-data-ai/nlp-engineer.md +287 -0
  120. package/.claude/archive/agents/05-data-ai/postgres-pro.md +287 -0
  121. package/.claude/archive/agents/05-data-ai/prompt-engineer.md +287 -0
  122. package/.claude/archive/agents/05-data-ai/reinforcement-learning-engineer.md +277 -0
  123. package/.claude/archive/agents/07-specialized-domains/api-documenter.md +277 -0
  124. package/.claude/archive/agents/07-specialized-domains/blockchain-developer.md +287 -0
  125. package/.claude/archive/agents/07-specialized-domains/embedded-systems.md +287 -0
  126. package/.claude/archive/agents/07-specialized-domains/fintech-engineer.md +287 -0
  127. package/.claude/archive/agents/07-specialized-domains/game-developer.md +287 -0
  128. package/.claude/archive/agents/07-specialized-domains/healthcare-admin.md +199 -0
  129. package/.claude/archive/agents/07-specialized-domains/iot-engineer.md +287 -0
  130. package/.claude/archive/agents/07-specialized-domains/m365-admin.md +48 -0
  131. package/.claude/archive/agents/07-specialized-domains/mobile-app-developer.md +287 -0
  132. package/.claude/archive/agents/07-specialized-domains/payment-integration.md +287 -0
  133. package/.claude/archive/agents/07-specialized-domains/quant-analyst.md +287 -0
  134. package/.claude/archive/agents/07-specialized-domains/risk-manager.md +287 -0
  135. package/.claude/archive/agents/07-specialized-domains/seo-specialist.md +184 -0
  136. package/.claude/archive/agents/08-business-product/business-analyst.md +287 -0
  137. package/.claude/archive/agents/08-business-product/content-marketer.md +287 -0
  138. package/.claude/archive/agents/08-business-product/customer-success-manager.md +287 -0
  139. package/.claude/archive/agents/08-business-product/legal-advisor.md +287 -0
  140. package/.claude/archive/agents/08-business-product/license-engineer.md +295 -0
  141. package/.claude/archive/agents/08-business-product/product-manager.md +287 -0
  142. package/.claude/archive/agents/08-business-product/project-manager.md +287 -0
  143. package/.claude/archive/agents/08-business-product/sales-engineer.md +287 -0
  144. package/.claude/archive/agents/08-business-product/scrum-master.md +287 -0
  145. package/.claude/archive/agents/08-business-product/technical-writer.md +287 -0
  146. package/.claude/archive/agents/08-business-product/ux-researcher.md +287 -0
  147. package/.claude/archive/agents/08-business-product/wordpress-master.md +316 -0
  148. package/.claude/archive/skills/competitive-ads-extractor/SKILL.md +293 -0
  149. package/.claude/archive/skills/developer-growth-analysis/SKILL.md +322 -0
  150. package/.claude/archive/skills/document-docx/LICENSE.txt +30 -0
  151. package/.claude/archive/skills/document-docx/SKILL.md +197 -0
  152. package/.claude/archive/skills/document-docx/docx-js.md +350 -0
  153. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  154. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  155. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  156. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  157. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  158. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  159. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  160. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  161. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  162. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  163. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  164. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  165. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  166. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  167. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  168. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  169. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  170. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  171. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  172. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  173. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  174. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  175. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  176. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  177. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  178. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  179. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  180. package/.claude/archive/skills/document-docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  181. package/.claude/archive/skills/document-docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  182. package/.claude/archive/skills/document-docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  183. package/.claude/archive/skills/document-docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  184. package/.claude/archive/skills/document-docx/ooxml/schemas/mce/mc.xsd +75 -0
  185. package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  186. package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  187. package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  188. package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  189. package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  190. package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  191. package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  192. package/.claude/archive/skills/document-docx/ooxml/scripts/pack.py +159 -0
  193. package/.claude/archive/skills/document-docx/ooxml/scripts/unpack.py +29 -0
  194. package/.claude/archive/skills/document-docx/ooxml/scripts/validate.py +69 -0
  195. package/.claude/archive/skills/document-docx/ooxml/scripts/validation/__init__.py +15 -0
  196. package/.claude/archive/skills/document-docx/ooxml/scripts/validation/base.py +951 -0
  197. package/.claude/archive/skills/document-docx/ooxml/scripts/validation/docx.py +274 -0
  198. package/.claude/archive/skills/document-docx/ooxml/scripts/validation/pptx.py +315 -0
  199. package/.claude/archive/skills/document-docx/ooxml/scripts/validation/redlining.py +279 -0
  200. package/.claude/archive/skills/document-docx/ooxml.md +610 -0
  201. package/.claude/archive/skills/document-docx/scripts/__init__.py +1 -0
  202. package/.claude/archive/skills/document-docx/scripts/document.py +1276 -0
  203. package/.claude/archive/skills/document-docx/scripts/templates/comments.xml +3 -0
  204. package/.claude/archive/skills/document-docx/scripts/templates/commentsExtended.xml +3 -0
  205. package/.claude/archive/skills/document-docx/scripts/templates/commentsExtensible.xml +3 -0
  206. package/.claude/archive/skills/document-docx/scripts/templates/commentsIds.xml +3 -0
  207. package/.claude/archive/skills/document-docx/scripts/templates/people.xml +3 -0
  208. package/.claude/archive/skills/document-docx/scripts/utilities.py +374 -0
  209. package/.claude/archive/skills/document-pdf/LICENSE.txt +30 -0
  210. package/.claude/archive/skills/document-pdf/SKILL.md +294 -0
  211. package/.claude/archive/skills/document-pdf/forms.md +205 -0
  212. package/.claude/archive/skills/document-pdf/reference.md +612 -0
  213. package/.claude/archive/skills/document-pdf/scripts/check_bounding_boxes.py +70 -0
  214. package/.claude/archive/skills/document-pdf/scripts/check_bounding_boxes_test.py +226 -0
  215. package/.claude/archive/skills/document-pdf/scripts/check_fillable_fields.py +12 -0
  216. package/.claude/archive/skills/document-pdf/scripts/convert_pdf_to_images.py +35 -0
  217. package/.claude/archive/skills/document-pdf/scripts/create_validation_image.py +41 -0
  218. package/.claude/archive/skills/document-pdf/scripts/extract_form_field_info.py +152 -0
  219. package/.claude/archive/skills/document-pdf/scripts/fill_fillable_fields.py +114 -0
  220. package/.claude/archive/skills/document-pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
  221. package/.claude/archive/skills/document-pptx/LICENSE.txt +30 -0
  222. package/.claude/archive/skills/document-pptx/SKILL.md +484 -0
  223. package/.claude/archive/skills/document-pptx/html2pptx.md +625 -0
  224. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  225. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  226. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  227. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  228. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  229. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  230. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  231. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  232. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  233. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  234. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  235. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  236. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  237. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  238. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  239. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  240. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  241. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  242. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  243. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  244. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  245. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  246. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  247. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  248. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  249. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  250. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  251. package/.claude/archive/skills/document-pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  252. package/.claude/archive/skills/document-pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  253. package/.claude/archive/skills/document-pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  254. package/.claude/archive/skills/document-pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  255. package/.claude/archive/skills/document-pptx/ooxml/schemas/mce/mc.xsd +75 -0
  256. package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  257. package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  258. package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  259. package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  260. package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  261. package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  262. package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  263. package/.claude/archive/skills/document-pptx/ooxml/scripts/pack.py +159 -0
  264. package/.claude/archive/skills/document-pptx/ooxml/scripts/unpack.py +29 -0
  265. package/.claude/archive/skills/document-pptx/ooxml/scripts/validate.py +69 -0
  266. package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/__init__.py +15 -0
  267. package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/base.py +951 -0
  268. package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/docx.py +274 -0
  269. package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/pptx.py +315 -0
  270. package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/redlining.py +279 -0
  271. package/.claude/archive/skills/document-pptx/ooxml.md +427 -0
  272. package/.claude/archive/skills/document-pptx/scripts/html2pptx.js +979 -0
  273. package/.claude/archive/skills/document-pptx/scripts/inventory.py +1020 -0
  274. package/.claude/archive/skills/document-pptx/scripts/rearrange.py +231 -0
  275. package/.claude/archive/skills/document-pptx/scripts/replace.py +385 -0
  276. package/.claude/archive/skills/document-pptx/scripts/thumbnail.py +450 -0
  277. package/.claude/archive/skills/document-xlsx/LICENSE.txt +30 -0
  278. package/.claude/archive/skills/document-xlsx/SKILL.md +289 -0
  279. package/.claude/archive/skills/document-xlsx/recalc.py +178 -0
  280. package/.claude/archive/skills/image-enhancer/SKILL.md +99 -0
  281. package/.claude/archive/skills/meeting-insights-analyzer/SKILL.md +327 -0
  282. package/.claude/archive/skills/slack-gif-creator/LICENSE.txt +202 -0
  283. package/.claude/archive/skills/slack-gif-creator/SKILL.md +646 -0
  284. package/.claude/archive/skills/slack-gif-creator/core/color_palettes.py +302 -0
  285. package/.claude/archive/skills/slack-gif-creator/core/easing.py +230 -0
  286. package/.claude/archive/skills/slack-gif-creator/core/frame_composer.py +469 -0
  287. package/.claude/archive/skills/slack-gif-creator/core/gif_builder.py +246 -0
  288. package/.claude/archive/skills/slack-gif-creator/core/typography.py +357 -0
  289. package/.claude/archive/skills/slack-gif-creator/core/validators.py +264 -0
  290. package/.claude/archive/skills/slack-gif-creator/core/visual_effects.py +494 -0
  291. package/.claude/archive/skills/slack-gif-creator/requirements.txt +4 -0
  292. package/.claude/archive/skills/slack-gif-creator/templates/bounce.py +106 -0
  293. package/.claude/archive/skills/slack-gif-creator/templates/explode.py +331 -0
  294. package/.claude/archive/skills/slack-gif-creator/templates/fade.py +329 -0
  295. package/.claude/archive/skills/slack-gif-creator/templates/flip.py +291 -0
  296. package/.claude/archive/skills/slack-gif-creator/templates/kaleidoscope.py +211 -0
  297. package/.claude/archive/skills/slack-gif-creator/templates/morph.py +329 -0
  298. package/.claude/archive/skills/slack-gif-creator/templates/move.py +293 -0
  299. package/.claude/archive/skills/slack-gif-creator/templates/pulse.py +268 -0
  300. package/.claude/archive/skills/slack-gif-creator/templates/shake.py +127 -0
  301. package/.claude/archive/skills/slack-gif-creator/templates/slide.py +291 -0
  302. package/.claude/archive/skills/slack-gif-creator/templates/spin.py +269 -0
  303. package/.claude/archive/skills/slack-gif-creator/templates/wiggle.py +300 -0
  304. package/.claude/archive/skills/slack-gif-creator/templates/zoom.py +312 -0
  305. package/.claude/archive/skills/twitter-algorithm-optimizer/SKILL.md +327 -0
  306. package/.claude/archive/skills/video-downloader/SKILL.md +99 -0
  307. package/.claude/archive/skills/video-downloader/scripts/download_video.py +145 -0
  308. package/.claude/bash-whitelist-requests/2026-05-28-grep-find-rg.md +68 -0
  309. package/.claude/bash-whitelist-requests/2026-06-01-readonly-filters.md +76 -0
  310. package/.claude/bash-whitelist.txt +124 -0
  311. package/.claude/commands/agent-introspect.md +89 -0
  312. package/.claude/commands/apply-rules.md +363 -0
  313. package/.claude/commands/approve-design.md +219 -0
  314. package/.claude/commands/approve-org-money.md +267 -0
  315. package/.claude/commands/build.md +234 -0
  316. package/.claude/commands/commit.md +97 -0
  317. package/.claude/commands/context-fetch.md +113 -0
  318. package/.claude/commands/create-tool.md +496 -0
  319. package/.claude/commands/design-review.md +138 -0
  320. package/.claude/commands/design.md +807 -0
  321. package/.claude/commands/discharge-byproduct.md +208 -0
  322. package/.claude/commands/doc-review.md +165 -0
  323. package/.claude/commands/document-pair.md +76 -0
  324. package/.claude/commands/error-triage.md +435 -0
  325. package/.claude/commands/eval.md +70 -0
  326. package/.claude/commands/evolve.md +49 -0
  327. package/.claude/commands/finish-task.md +105 -0
  328. package/.claude/commands/gan-build.md +91 -0
  329. package/.claude/commands/gan-design.md +82 -0
  330. package/.claude/commands/gate-bypass.md +77 -0
  331. package/.claude/commands/gate-clear.md +45 -0
  332. package/.claude/commands/gate-status.md +46 -0
  333. package/.claude/commands/harness-audit.md +151 -0
  334. package/.claude/commands/hearing.md +138 -0
  335. package/.claude/commands/impact-check.md +486 -0
  336. package/.claude/commands/init-tasks.md +49 -0
  337. package/.claude/commands/instinct-export.md +47 -0
  338. package/.claude/commands/instinct-import.md +41 -0
  339. package/.claude/commands/instinct-status.md +43 -0
  340. package/.claude/commands/investigate.md +547 -0
  341. package/.claude/commands/learn.md +55 -0
  342. package/.claude/commands/lint-rules.md +400 -0
  343. package/.claude/commands/mode.md +58 -0
  344. package/.claude/commands/modify-feature.md +209 -0
  345. package/.claude/commands/module-review.md +149 -0
  346. package/.claude/commands/move-section.md +67 -0
  347. package/.claude/commands/new-draft.md +67 -0
  348. package/.claude/commands/new-feature.md +286 -0
  349. package/.claude/commands/new-task.md +156 -0
  350. package/.claude/commands/notification.md +107 -0
  351. package/.claude/commands/pm-start.md +119 -0
  352. package/.claude/commands/projects.md +32 -0
  353. package/.claude/commands/promote.md +43 -0
  354. package/.claude/commands/rasis-report.md +1323 -0
  355. package/.claude/commands/release-note.md +130 -0
  356. package/.claude/commands/reply-watch.md +149 -0
  357. package/.claude/commands/requirement.md +352 -0
  358. package/.claude/commands/resume-state.md +187 -0
  359. package/.claude/commands/reviewpr.md +118 -0
  360. package/.claude/commands/save-state.md +100 -0
  361. package/.claude/commands/sentry-pr.md +157 -0
  362. package/.claude/commands/start-task.md +87 -0
  363. package/.claude/commands/system-review.md +147 -0
  364. package/.claude/commands/task-bypass.md +70 -0
  365. package/.claude/commands/task-estimate.md +100 -0
  366. package/.claude/commands/template-apply.md +89 -0
  367. package/.claude/commands/test-design.md +116 -0
  368. package/.claude/commands/transfer-mismatch.md +317 -0
  369. package/.claude/commands/verify.md +51 -0
  370. package/.claude/evals/grader-loop-mode-autonomy.sh +165 -0
  371. package/.claude/evals/grader-system-reminder-attention.sh +99 -0
  372. package/.claude/evals/loop-mode-autonomy.md +121 -0
  373. package/.claude/evals/loop-mode-autonomy.results.template.md +133 -0
  374. package/.claude/evals/system-reminder-attention.md +123 -0
  375. package/.claude/evals/system-reminder-attention.results.template.md +93 -0
  376. package/.claude/evals/system-reminder-attention.runner.md +353 -0
  377. package/.claude/harness-config.local.yml +48 -0
  378. package/.claude/harness-config.yml +534 -0
  379. package/.claude/hooks/agent-marker-clear.sh +43 -0
  380. package/.claude/hooks/agent-marker-set.sh +40 -0
  381. package/.claude/hooks/agent-router-suggest.sh +123 -0
  382. package/.claude/hooks/autonomous-action-guard.sh +242 -0
  383. package/.claude/hooks/byproduct-discharge-guard.sh +128 -0
  384. package/.claude/hooks/check-md-mermaid.sh +144 -0
  385. package/.claude/hooks/check-required-env.sh +95 -0
  386. package/.claude/hooks/check-serena-mcp.sh +123 -0
  387. package/.claude/hooks/confidence-gate.sh +139 -0
  388. package/.claude/hooks/context-budget.sh +233 -0
  389. package/.claude/hooks/delegation-guard.sh +99 -0
  390. package/.claude/hooks/dispatcher-manifest.tsv +38 -0
  391. package/.claude/hooks/draft-flow-guard.sh +304 -0
  392. package/.claude/hooks/failure-loop-detect.sh +139 -0
  393. package/.claude/hooks/gateguard.sh +209 -0
  394. package/.claude/hooks/improvement-proposal.sh +112 -0
  395. package/.claude/hooks/init-tasks-on-start.sh +34 -0
  396. package/.claude/hooks/lib/bypass-logger.sh +82 -0
  397. package/.claude/hooks/lib/confidence-gate/bypass.sh +48 -0
  398. package/.claude/hooks/lib/confidence-gate/extract.sh +99 -0
  399. package/.claude/hooks/lib/confidence-gate/major-agent-filter.sh +59 -0
  400. package/.claude/hooks/lib/confidence-gate/messages.sh +53 -0
  401. package/.claude/hooks/lib/config-loader.sh +784 -0
  402. package/.claude/hooks/lib/delegation-guard/bash-whitelist.sh +323 -0
  403. package/.claude/hooks/lib/delegation-guard/git-deny.sh +188 -0
  404. package/.claude/hooks/lib/delegation-guard/protected-paths.sh +105 -0
  405. package/.claude/hooks/lib/delegation-guard/subagent-detect.sh +40 -0
  406. package/.claude/hooks/lib/dispatcher-core.sh +454 -0
  407. package/.claude/hooks/lib/improvement-proposal/aggregate.py +466 -0
  408. package/.claude/hooks/lib/improvement-proposal/cache.sh +78 -0
  409. package/.claude/hooks/lib/mode-loader.sh +80 -0
  410. package/.claude/hooks/lib/next-actions-parser.sh +153 -0
  411. package/.claude/hooks/lib/project-root.sh +60 -0
  412. package/.claude/hooks/list-md-plan-first-reminder.sh +143 -0
  413. package/.claude/hooks/loop-auto-progress-reminder.sh +108 -0
  414. package/.claude/hooks/loop-confirmation-detector.sh +241 -0
  415. package/.claude/hooks/mode-asana-prompt.sh +61 -0
  416. package/.claude/hooks/mode-enforce.sh +57 -0
  417. package/.claude/hooks/mode-session-start.sh +93 -0
  418. package/.claude/hooks/next-actions-surface.sh +136 -0
  419. package/.claude/hooks/notification-dispatcher.sh +9 -0
  420. package/.claude/hooks/notify.sh +27 -0
  421. package/.claude/hooks/parallel-subagent-reminder.sh +469 -0
  422. package/.claude/hooks/post-tool-use-dispatcher.sh +9 -0
  423. package/.claude/hooks/pre-tool-use-dispatcher.sh +9 -0
  424. package/.claude/hooks/reviewer-count-guard.sh +313 -0
  425. package/.claude/hooks/session-help-surface.sh +192 -0
  426. package/.claude/hooks/session-start-dispatcher.sh +9 -0
  427. package/.claude/hooks/session-start-wrapper.sh +156 -0
  428. package/.claude/hooks/stale-harness-detect.sh +422 -0
  429. package/.claude/hooks/stop-dispatcher.sh +9 -0
  430. package/.claude/hooks/stop.sh +25 -0
  431. package/.claude/hooks/subagent-stop-dispatcher.sh +9 -0
  432. package/.claude/hooks/task-rule-guard.sh +317 -0
  433. package/.claude/hooks/tests/run-tests.sh +23 -0
  434. package/.claude/hooks/tests/test-agent-marker-warn.sh +86 -0
  435. package/.claude/hooks/tests/test-check-required-env.sh +138 -0
  436. package/.claude/hooks/tests/test-confidence-gate.sh +170 -0
  437. package/.claude/hooks/tests/test-config-env-override.sh +220 -0
  438. package/.claude/hooks/tests/test-gate-disable.sh +118 -0
  439. package/.claude/hooks/tests/test-improvement-proposal.sh +284 -0
  440. package/.claude/hooks/tool-call-slip-detector.sh +188 -0
  441. package/.claude/hooks/user-prompt-submit-dispatcher.sh +9 -0
  442. package/.claude/hooks/why-x5-reminder.sh +45 -0
  443. package/.claude/hooks/why-x5-violation-detect.sh +152 -0
  444. package/.claude/hooks/workflow-guard.sh +263 -0
  445. package/.claude/mode.yml +28 -0
  446. package/.claude/project-rules/development-process.md +8 -0
  447. package/.claude/project-rules/git-workflow.md +8 -0
  448. package/.claude/project-rules/modes.md +8 -0
  449. package/.claude/project-rules/self-improvement.md +8 -0
  450. package/.claude/project-rules/task-management.md +8 -0
  451. package/.claude/project-rules/why-x5-output.md +8 -0
  452. package/.claude/project-rules/workflow.md +8 -0
  453. package/.claude/rules/development-process.md +293 -0
  454. package/.claude/rules/git-workflow.md +71 -0
  455. package/.claude/rules/modes.md +189 -0
  456. package/.claude/rules/self-improvement.md +76 -0
  457. package/.claude/rules/task-management.md +261 -0
  458. package/.claude/rules/why-x5-output.md +97 -0
  459. package/.claude/rules/workflow.md +157 -0
  460. package/.claude/rules-details/README.md +67 -0
  461. package/.claude/rules-details/development-process/confidence-gate.md +22 -0
  462. package/.claude/rules-details/development-process/cross-repo-write.md +35 -0
  463. package/.claude/rules-details/development-process/delegation-requirements.md +158 -0
  464. package/.claude/rules-details/development-process/harness-sync.md +21 -0
  465. package/.claude/rules-details/development-process/origin.md +13 -0
  466. package/.claude/rules-details/development-process/parallelization-origin.md +22 -0
  467. package/.claude/rules-details/development-process/research-reuse.md +22 -0
  468. package/.claude/rules-details/development-process/staging-strategy.md +47 -0
  469. package/.claude/rules-details/modes/artifacts.md +34 -0
  470. package/.claude/rules-details/modes/compliance-items.md +120 -0
  471. package/.claude/rules-details/modes/five-layer-enforcement.md +46 -0
  472. package/.claude/rules-details/modes/mode-hooks.md +51 -0
  473. package/.claude/rules-details/modes/origin.md +17 -0
  474. package/.claude/rules-details/self-improvement/l4-mechanics.md +36 -0
  475. package/.claude/rules-details/self-improvement/origin.md +8 -0
  476. package/.claude/rules-details/self-improvement/related-skills.md +35 -0
  477. package/.claude/rules-details/self-improvement/when-to-use-layers.md +39 -0
  478. package/.claude/rules-details/task-management/hook-enforcement.md +25 -0
  479. package/.claude/rules-details/task-management/mandatory-reading.md +20 -0
  480. package/.claude/rules-details/task-management/origin.md +12 -0
  481. package/.claude/rules-details/task-management/parking-lot.md +26 -0
  482. package/.claude/rules-details/task-management/plan-first.md +44 -0
  483. package/.claude/rules-details/task-management/six-articles.md +68 -0
  484. package/.claude/rules-details/task-management/task-migration.md +16 -0
  485. package/.claude/rules-details/task-management/ui-detection.md +11 -0
  486. package/.claude/rules-details/why-x5-output/examples.md +41 -0
  487. package/.claude/rules-details/why-x5-output/feedback-memory.md +14 -0
  488. package/.claude/rules-details/why-x5-output/origin.md +10 -0
  489. package/.claude/rules-details/why-x5-output/v1-v10-history.md +19 -0
  490. package/.claude/rules-details/workflow/10-stage.md +43 -0
  491. package/.claude/rules-details/workflow/14-stage.md +52 -0
  492. package/.claude/rules-details/workflow/byproduct-discharge.md +39 -0
  493. package/.claude/rules-details/workflow/draft-flow-guard.md +31 -0
  494. package/.claude/rules-details/workflow/fan-out.md +70 -0
  495. package/.claude/rules-details/workflow/mece-20.md +36 -0
  496. package/.claude/rules-details/workflow/origin.md +14 -0
  497. package/.claude/rules-details/workflow/refactoring.md +48 -0
  498. package/.claude/rules-details/workflow/related-skills.md +22 -0
  499. package/.claude/rules-details/workflow/reviewer-prompt.md +100 -0
  500. package/.claude/rules-details/workflow/session-persistence.md +46 -0
  501. package/.claude/rules-details/workflow/workflow-guard.md +36 -0
  502. package/.claude/scripts/__pycache__/harness-audit.cpython-313.pyc +0 -0
  503. package/.claude/scripts/agent-stocktake.py +421 -0
  504. package/.claude/scripts/check-md-mermaid.mjs +138 -0
  505. package/.claude/scripts/generate-settings.sh +0 -0
  506. package/.claude/scripts/harness-audit.py +1547 -0
  507. package/.claude/scripts/hc-config.sh +2265 -0
  508. package/.claude/scripts/init-tasks.sh +117 -0
  509. package/.claude/scripts/lib/enforcement-matrix-parse.sh +81 -0
  510. package/.claude/scripts/lib/hc-config-metadata.sh +190 -0
  511. package/.claude/scripts/lib/hc-config-web-server.js +1528 -0
  512. package/.claude/scripts/lib/hc-config-web-ui/app.js +1054 -0
  513. package/.claude/scripts/lib/hc-config-web-ui/index.html +130 -0
  514. package/.claude/scripts/lib/hc-config-web-ui/style.css +522 -0
  515. package/.claude/scripts/new-task-helper.sh +432 -0
  516. package/.claude/scripts/observe-repair.sh +437 -0
  517. package/.claude/scripts/observe-rotate.sh +311 -0
  518. package/.claude/scripts/statusline.sh +239 -0
  519. package/.claude/settings.generated.preview.json +211 -0
  520. package/.claude/settings.json +215 -0
  521. package/.claude/settings.local.example.json +20 -0
  522. package/.claude/settings.local.json +36 -0
  523. package/.claude/skills/agent-introspection-debugging/SKILL.md +123 -0
  524. package/.claude/skills/agent-router/README.md +137 -0
  525. package/.claude/skills/agent-router/SKILL.md +74 -0
  526. package/.claude/skills/agent-router/dispatch-table.yml +352 -0
  527. package/.claude/skills/agent-router/router.py +1086 -0
  528. package/.claude/skills/agent-router/samples/representative_prompts.txt +24 -0
  529. package/.claude/skills/agent-router/tests/__init__.py +0 -0
  530. package/.claude/skills/agent-router/tests/test_router.py +762 -0
  531. package/.claude/skills/artifacts-builder/LICENSE.txt +202 -0
  532. package/.claude/skills/artifacts-builder/SKILL.md +74 -0
  533. package/.claude/skills/artifacts-builder/scripts/bundle-artifact.sh +54 -0
  534. package/.claude/skills/artifacts-builder/scripts/init-artifact.sh +322 -0
  535. package/.claude/skills/artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  536. package/.claude/skills/brand-guidelines/LICENSE.txt +202 -0
  537. package/.claude/skills/brand-guidelines/SKILL.md +73 -0
  538. package/.claude/skills/canvas-design/LICENSE.txt +202 -0
  539. package/.claude/skills/canvas-design/SKILL.md +130 -0
  540. package/.claude/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
  541. package/.claude/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  542. package/.claude/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  543. package/.claude/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
  544. package/.claude/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  545. package/.claude/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
  546. package/.claude/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
  547. package/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  548. package/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
  549. package/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  550. package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  551. package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  552. package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
  553. package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  554. package/.claude/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
  555. package/.claude/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
  556. package/.claude/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
  557. package/.claude/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
  558. package/.claude/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
  559. package/.claude/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
  560. package/.claude/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
  561. package/.claude/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
  562. package/.claude/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
  563. package/.claude/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  564. package/.claude/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
  565. package/.claude/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  566. package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  567. package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  568. package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  569. package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  570. package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  571. package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  572. package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  573. package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
  574. package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  575. package/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  576. package/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  577. package/.claude/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
  578. package/.claude/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
  579. package/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  580. package/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
  581. package/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  582. package/.claude/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
  583. package/.claude/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
  584. package/.claude/skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
  585. package/.claude/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
  586. package/.claude/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  587. package/.claude/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
  588. package/.claude/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  589. package/.claude/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
  590. package/.claude/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
  591. package/.claude/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
  592. package/.claude/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
  593. package/.claude/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
  594. package/.claude/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
  595. package/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
  596. package/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  597. package/.claude/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
  598. package/.claude/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
  599. package/.claude/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
  600. package/.claude/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  601. package/.claude/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
  602. package/.claude/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
  603. package/.claude/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  604. package/.claude/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  605. package/.claude/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
  606. package/.claude/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  607. package/.claude/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
  608. package/.claude/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  609. package/.claude/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  610. package/.claude/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
  611. package/.claude/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
  612. package/.claude/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
  613. package/.claude/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
  614. package/.claude/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
  615. package/.claude/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  616. package/.claude/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
  617. package/.claude/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
  618. package/.claude/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
  619. package/.claude/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
  620. package/.claude/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  621. package/.claude/skills/changelog-generator/SKILL.md +104 -0
  622. package/.claude/skills/check-md-mermaid/SKILL.md +62 -0
  623. package/.claude/skills/connect/SKILL.md +156 -0
  624. package/.claude/skills/connect-apps/SKILL.md +80 -0
  625. package/.claude/skills/content-research-writer/SKILL.md +538 -0
  626. package/.claude/skills/continuous-agent-loop/SKILL.md +187 -0
  627. package/.claude/skills/continuous-learning-v2/SKILL.md +238 -0
  628. package/.claude/skills/continuous-learning-v2/config.json +35 -0
  629. package/.claude/skills/continuous-learning-v2/hooks/observe.sh +333 -0
  630. package/.claude/skills/continuous-learning-v2/instinct-cli.py +406 -0
  631. package/.claude/skills/domain-name-brainstormer/SKILL.md +212 -0
  632. package/.claude/skills/eval-harness/SKILL.md +100 -0
  633. package/.claude/skills/eval-harness/swe-bench/README.md +80 -0
  634. package/.claude/skills/eval-harness/swe-bench/config.yml +29 -0
  635. package/.claude/skills/eval-harness/swe-bench/docker/Dockerfile +25 -0
  636. package/.claude/skills/eval-harness/swe-bench/docker/docker-compose.yml +18 -0
  637. package/.claude/skills/eval-harness/swe-bench/results/dry-run-2026-05-04.json +137 -0
  638. package/.claude/skills/eval-harness/swe-bench/results/dry-run-comparison-2026-05-04.md +112 -0
  639. package/.claude/skills/eval-harness/swe-bench/results/dry-run-improved-2026-05-04.json +165 -0
  640. package/.claude/skills/eval-harness/swe-bench/results/raw/astropy__astropy-12907.patch +12 -0
  641. package/.claude/skills/eval-harness/swe-bench/results/raw/astropy__astropy-12907.txt +322 -0
  642. package/.claude/skills/eval-harness/swe-bench/results/raw/astropy__astropy-12907.whole-file.txt +322 -0
  643. package/.claude/skills/eval-harness/swe-bench/runner.py +845 -0
  644. package/.claude/skills/eval-harness/swe-bench/scoring.py +298 -0
  645. package/.claude/skills/eval-harness/swe-bench/tasks/fetch_tasks.py +81 -0
  646. package/.claude/skills/eval-harness/swe-bench/tasks/lite-50.json +702 -0
  647. package/.claude/skills/file-organizer/SKILL.md +433 -0
  648. package/.claude/skills/gan-style-harness/SKILL.md +111 -0
  649. package/.claude/skills/gateguard/.gateguard.yml +47 -0
  650. package/.claude/skills/gateguard/SKILL.md +99 -0
  651. package/.claude/skills/internal-comms/LICENSE.txt +202 -0
  652. package/.claude/skills/internal-comms/SKILL.md +32 -0
  653. package/.claude/skills/internal-comms/examples/3p-updates.md +47 -0
  654. package/.claude/skills/internal-comms/examples/company-newsletter.md +65 -0
  655. package/.claude/skills/internal-comms/examples/faq-answers.md +30 -0
  656. package/.claude/skills/internal-comms/examples/general-comms.md +16 -0
  657. package/.claude/skills/invoice-organizer/SKILL.md +446 -0
  658. package/.claude/skills/karpathy-guidelines/SKILL.md +67 -0
  659. package/.claude/skills/langsmith-fetch/SKILL.md +485 -0
  660. package/.claude/skills/lead-research-assistant/SKILL.md +199 -0
  661. package/.claude/skills/mcp-builder/LICENSE.txt +202 -0
  662. package/.claude/skills/mcp-builder/SKILL.md +328 -0
  663. package/.claude/skills/mcp-builder/reference/evaluation.md +602 -0
  664. package/.claude/skills/mcp-builder/reference/mcp_best_practices.md +915 -0
  665. package/.claude/skills/mcp-builder/reference/node_mcp_server.md +916 -0
  666. package/.claude/skills/mcp-builder/reference/python_mcp_server.md +752 -0
  667. package/.claude/skills/mcp-builder/scripts/connections.py +151 -0
  668. package/.claude/skills/mcp-builder/scripts/evaluation.py +373 -0
  669. package/.claude/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  670. package/.claude/skills/mcp-builder/scripts/requirements.txt +2 -0
  671. package/.claude/skills/raffle-winner-picker/SKILL.md +159 -0
  672. package/.claude/skills/repo-map/README.md +125 -0
  673. package/.claude/skills/repo-map/SKILL.md +128 -0
  674. package/.claude/skills/repo-map/examples/sample-output.md +1194 -0
  675. package/.claude/skills/repo-map/repo-map.py +715 -0
  676. package/.claude/skills/salesforce-e2e-testing/SKILL.md +116 -0
  677. package/.claude/skills/salesforce-e2e-testing/catalog-template.md +161 -0
  678. package/.claude/skills/salesforce-e2e-testing/methodology.md +179 -0
  679. package/.claude/skills/salesforce-e2e-testing/observation-rules.md +280 -0
  680. package/.claude/skills/salesforce-e2e-testing/pattern-taxonomy.md +392 -0
  681. package/.claude/skills/salesforce-e2e-testing/procedure-template.md +376 -0
  682. package/.claude/skills/skill-creator/LICENSE.txt +202 -0
  683. package/.claude/skills/skill-creator/SKILL.md +209 -0
  684. package/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
  685. package/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
  686. package/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
  687. package/.claude/skills/skill-share/SKILL.md +80 -0
  688. package/.claude/skills/tailored-resume-generator/SKILL.md +345 -0
  689. package/.claude/skills/template-skill/SKILL.md +6 -0
  690. package/.claude/skills/theme-factory/LICENSE.txt +202 -0
  691. package/.claude/skills/theme-factory/SKILL.md +59 -0
  692. package/.claude/skills/theme-factory/theme-showcase.pdf +0 -0
  693. package/.claude/skills/theme-factory/themes/arctic-frost.md +19 -0
  694. package/.claude/skills/theme-factory/themes/botanical-garden.md +19 -0
  695. package/.claude/skills/theme-factory/themes/desert-rose.md +19 -0
  696. package/.claude/skills/theme-factory/themes/forest-canopy.md +19 -0
  697. package/.claude/skills/theme-factory/themes/golden-hour.md +19 -0
  698. package/.claude/skills/theme-factory/themes/midnight-galaxy.md +19 -0
  699. package/.claude/skills/theme-factory/themes/modern-minimalist.md +19 -0
  700. package/.claude/skills/theme-factory/themes/ocean-depths.md +19 -0
  701. package/.claude/skills/theme-factory/themes/sunset-boulevard.md +19 -0
  702. package/.claude/skills/theme-factory/themes/tech-innovation.md +19 -0
  703. package/.claude/skills/verification-loop/SKILL.md +129 -0
  704. package/.claude/skills/webapp-testing/LICENSE.txt +202 -0
  705. package/.claude/skills/webapp-testing/SKILL.md +96 -0
  706. package/.claude/skills/webapp-testing/examples/console_logging.py +35 -0
  707. package/.claude/skills/webapp-testing/examples/element_discovery.py +40 -0
  708. package/.claude/skills/webapp-testing/examples/static_html_automation.py +33 -0
  709. package/.claude/skills/webapp-testing/scripts/with_server.py +106 -0
  710. package/.claude/templates/docs/draft/_DRAFT_TEMPLATE.md +162 -0
  711. package/.claude/templates/docs/draft/_TEST_DESIGN_TEMPLATE.md +76 -0
  712. package/.claude/templates/docs/tasks/_TASK_TEMPLATE.md +276 -0
  713. package/.claude/templates/docs/tasks/list.md +80 -0
  714. package/.claude/templates/docs/tasks/parking-lot.md +82 -0
  715. package/.claude/templates/settings.user-level.json.template +306 -0
  716. package/.claude/tests/SMOKE-CLASSIFICATION.md +199 -0
  717. package/.claude/tests/action-space-count-smoke.sh +130 -0
  718. package/.claude/tests/agent-router-suggest-wiring-smoke.sh +188 -0
  719. package/.claude/tests/audit-followups-smoke.sh +158 -0
  720. package/.claude/tests/autonomous-action-guard-relaxation-smoke.sh +479 -0
  721. package/.claude/tests/autonomous-action-guard-smoke.sh +187 -0
  722. package/.claude/tests/check-serena-mcp-smoke.sh +156 -0
  723. package/.claude/tests/common-rules-import-smoke.sh +209 -0
  724. package/.claude/tests/confidence-gate-smoke.sh +220 -0
  725. package/.claude/tests/config-feature-toggles-smoke.sh +389 -0
  726. package/.claude/tests/context-budget-smoke.sh +222 -0
  727. package/.claude/tests/custom-pm-commands-smoke.sh +93 -0
  728. package/.claude/tests/delegation-guard-code-smoke.sh +244 -0
  729. package/.claude/tests/delegation-guard-deny-layers-smoke.sh +356 -0
  730. package/.claude/tests/delegation-guard-readonly-filter-smoke.sh +205 -0
  731. package/.claude/tests/delegation-guard-search-whitelist-smoke.sh +152 -0
  732. package/.claude/tests/delegation-guard-segment-smoke.sh +109 -0
  733. package/.claude/tests/dispatcher-blocker-invariance-smoke.sh +700 -0
  734. package/.claude/tests/dispatcher-core-smoke.sh +452 -0
  735. package/.claude/tests/dispatcher-merge-matrix-smoke.sh +825 -0
  736. package/.claude/tests/dispatcher-success-stdout-smoke.sh +290 -0
  737. package/.claude/tests/draft-flow-guard-approved-dir-smoke.sh +234 -0
  738. package/.claude/tests/draft-flow-guard-smoke.sh +194 -0
  739. package/.claude/tests/dual-mode-portability-smoke.sh +131 -0
  740. package/.claude/tests/effective-hook-matrix-smoke.sh +261 -0
  741. package/.claude/tests/enforcement-mismatch-smoke.sh +263 -0
  742. package/.claude/tests/fixtures/cascade-sample.jsonl +9 -0
  743. package/.claude/tests/fixtures/next-actions/case-clean.md +14 -0
  744. package/.claude/tests/fixtures/next-actions/case-with-red.md +16 -0
  745. package/.claude/tests/fixtures/next-actions/case-with-yellow-only.md +14 -0
  746. package/.claude/tests/fixtures/normal-broken-scatter.jsonl +5 -0
  747. package/.claude/tests/fixtures/task-71/blocker-baseline.tsv +24 -0
  748. package/.claude/tests/fixtures/task-71/settings-inventory.tsv +37 -0
  749. package/.claude/tests/fixtures/transcript-50pct.jsonl +2 -0
  750. package/.claude/tests/fixtures/transcript-60pct.jsonl +2 -0
  751. package/.claude/tests/fixtures/transcript-80pct.jsonl +2 -0
  752. package/.claude/tests/fixtures/transcript-95pct.jsonl +2 -0
  753. package/.claude/tests/fixtures/workflow-guard/case-2-mid.json +21 -0
  754. package/.claude/tests/fixtures/workflow-guard/case-3-blocked.json +33 -0
  755. package/.claude/tests/fixtures/workflow-guard/case-4-clean.json +27 -0
  756. package/.claude/tests/fixtures/workflow-guard/case-8-modify.json +23 -0
  757. package/.claude/tests/fixtures/workflow-guard/inputs/case-1.json +1 -0
  758. package/.claude/tests/fixtures/workflow-guard/inputs/case-2.json +1 -0
  759. package/.claude/tests/fixtures/workflow-guard/inputs/case-3.json +1 -0
  760. package/.claude/tests/fixtures/workflow-guard/inputs/case-4.json +1 -0
  761. package/.claude/tests/fixtures/workflow-guard/inputs/case-5.json +1 -0
  762. package/.claude/tests/fixtures/workflow-guard/inputs/case-6.json +1 -0
  763. package/.claude/tests/fixtures/workflow-guard/inputs/case-7.json +1 -0
  764. package/.claude/tests/fixtures/workflow-guard/inputs/case-8.json +1 -0
  765. package/.claude/tests/gateguard-smoke.sh +213 -0
  766. package/.claude/tests/git-deny-mainline-policy-smoke.sh +222 -0
  767. package/.claude/tests/harness-audit-c-batch-smoke.sh +270 -0
  768. package/.claude/tests/harness-audit-compare-smoke.sh +186 -0
  769. package/.claude/tests/harness-audit-pipeline-health-smoke.sh +326 -0
  770. package/.claude/tests/harness-config-local-smoke.sh +232 -0
  771. package/.claude/tests/hc-config-git-policy-smoke.sh +241 -0
  772. package/.claude/tests/hc-config-key-parity-smoke.sh +149 -0
  773. package/.claude/tests/hc-config-migration-smoke.sh +251 -0
  774. package/.claude/tests/hc-config-script-smoke.sh +1106 -0
  775. package/.claude/tests/hc-config-tui-smoke.sh +801 -0
  776. package/.claude/tests/hc-config-web-ui-smoke.sh +3224 -0
  777. package/.claude/tests/hook-cwd-robustness-smoke.sh +206 -0
  778. package/.claude/tests/hook-frequency-tweaks-smoke.sh +312 -0
  779. package/.claude/tests/improvement-proposal-cache-smoke.sh +238 -0
  780. package/.claude/tests/install-sh-overwrite-all-smoke.sh +274 -0
  781. package/.claude/tests/install-sh-regen-settings-smoke.sh +301 -0
  782. package/.claude/tests/install-sh-sync-drift-smoke.sh +285 -0
  783. package/.claude/tests/layer-b-context-isolation-smoke.sh +392 -0
  784. package/.claude/tests/list-md-plan-first-reminder-smoke.sh +313 -0
  785. package/.claude/tests/loop-auto-progress-smoke.sh +372 -0
  786. package/.claude/tests/loop-confirmation-detector-smoke.sh +674 -0
  787. package/.claude/tests/new-task-batch-update-smoke.sh +664 -0
  788. package/.claude/tests/next-actions-hooks-smoke.sh +283 -0
  789. package/.claude/tests/npx-cli-smoke.sh +696 -0
  790. package/.claude/tests/observe-flock-smoke.sh +223 -0
  791. package/.claude/tests/observe-jq-parse-smoke.sh +250 -0
  792. package/.claude/tests/observe-repair-smoke.sh +475 -0
  793. package/.claude/tests/observe-rotate-smoke.sh +428 -0
  794. package/.claude/tests/observe-subagent-stop-smoke.sh +476 -0
  795. package/.claude/tests/parallel-subagent-reminder-smoke.sh +918 -0
  796. package/.claude/tests/project-root-smoke.sh +140 -0
  797. package/.claude/tests/project-rules-protection-smoke.sh +199 -0
  798. package/.claude/tests/review-required-min-count-smoke.sh +286 -0
  799. package/.claude/tests/reviewer-count-guard-smoke.sh +490 -0
  800. package/.claude/tests/rule-architecture-smoke.sh +418 -0
  801. package/.claude/tests/rule-change-draft-flow-guard-smoke.sh +343 -0
  802. package/.claude/tests/run-all-smokes.sh +340 -0
  803. package/.claude/tests/session-help-surface-smoke.sh +224 -0
  804. package/.claude/tests/session-start-parallel-smoke.sh +165 -0
  805. package/.claude/tests/sessionstart-budget-smoke.sh +185 -0
  806. package/.claude/tests/sessionstart-footprint-smoke.sh +258 -0
  807. package/.claude/tests/settings-dispatcher-baseline-smoke.sh +709 -0
  808. package/.claude/tests/settings-generation-feature-pruning-smoke.sh +196 -0
  809. package/.claude/tests/stale-harness-detect-smoke.sh +974 -0
  810. package/.claude/tests/statusline-smoke.sh +180 -0
  811. package/.claude/tests/task-rule-guard-smoke.sh +656 -0
  812. package/.claude/tests/tool-call-slip-detector-smoke.sh +101 -0
  813. package/.claude/tests/wave-precheck-template-smoke.sh +159 -0
  814. package/.claude/tests/why-x5-violation-detect-smoke.sh +157 -0
  815. package/.claude/tests/workflow-guard-smoke.sh +266 -0
  816. package/CLAUDE.md +75 -0
  817. package/LICENSE +21 -0
  818. package/README.md +790 -0
  819. package/bin/cli.js +395 -0
  820. package/docs/INVENTORY.md +163 -0
  821. package/install.sh +769 -0
  822. 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 "$@"