@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,1528 @@
1
+ #!/usr/bin/env node
2
+ // .claude/scripts/lib/hc-config-web-server.js — task-63 Step 4 領域 A (設計簡素化、案 C values 完全一致判定)
3
+ //
4
+ // task-63 Step 4 領域 A 修正項目 (4 件、user 確定要求「プリセット名保存不要」2026-05-29):
5
+ // A1. 撤去: /api/preset/save endpoint + savePreset() + scanCustomPresets() + isTestPollutionName() +
6
+ // TEST_POLLUTION_PATTERNS const + /api/presets / /api/preset/:name/diff / /api/preset/:name/apply
7
+ // の custom preset merge ロジック (~80-100 LOC 削減)
8
+ // A2. 案 C 採用: getCurrentPreset() を values field 完全一致判定 (subset) に書換、
9
+ // axesEqual() → valuesSubsetEqual() に rename + logic 変更、PRESETS axes 6 軸照合は廃止
10
+ // A3. response 簡素化: /api/presets から axes_values 撤去、/api/current-preset は { name, display_name_ja, match_type }
11
+ // A4. module.exports 整理: savePreset / scanCustomPresets / isTestPollutionName / axesEqual 撤去、
12
+ // getCurrentPreset / valuesSubsetEqual 追加
13
+ //
14
+ // task-63 Step 1 修正項目 (継承、UX 再設計の API 基盤):
15
+ // 1. PRESETS 全 10 件に display_name_ja field 追加 (draft §3.1 mapping、日本語表示専用、絵文字なし)
16
+ // 2. /api/presets response 各 entry に display_name_ja field 追加
17
+ // 3. /api/current-preset 新規 endpoint (現在 yml 全 key 値 vs PRESETS values 完全一致 subset 判定)
18
+ // 4. router に /api/current-preset GET 分岐追加
19
+ // 5. module.exports に getCurrentPreset / valuesSubsetEqual 追加
20
+ //
21
+ // iter 6 A 修正項目 (5 件 = HIGH×2 + MED×2 + 補強):
22
+ // item 1 (HIGH 3-rev): HC_HISTORY_DIR_OVERRIDE + HC_PRESETS_DIR_OVERRIDE env override (test isolation)
23
+ // item 2 (HIGH 2-rev / MED-N3 実 HIGH): savePreset で PRESETS in-memory 追加 + /api/presets で
24
+ // PRESETS_DIR/custom-*.yml dynamic scan (§6 DoD 完全達成)
25
+ // item 3 (MED code-rev): HISTORY_MAX_FILES NaN / 負値 / 0 safe fallback 1000
26
+ // item 4 (MED code-rev): /api/value/:key + /api/set で hcGet/hcSet に {} 明示 (DI seam)
27
+ // (UI 側 item 5 + style.css item 6 は app.js / style.css に別途実装)
28
+ //
29
+ // 目的:
30
+ // hc-config.sh interactive (TTY 経路 default) の Web UI 化。
31
+ // Node.js 標準 module のみ (http / child_process / fs / path / os / url / net)、npm dep 0。
32
+ //
33
+ // 設計:
34
+ // - localhost (127.0.0.1) only bind、外部 access 禁止
35
+ // - port 3060-3070 自動 detect (listen error event 監視で EADDRINUSE 試行、race 排除)
36
+ // - 全ての yml 書込は hc-config.sh CLI 経由 (atomic backup + type validation を再利用)
37
+ // - 10 named preset を server.js 内 hardcode (draft §3.4 AI 推奨)
38
+ // - SIGINT graceful shutdown
39
+ // - applyPreset は 2-phase atomic (snapshot → apply、N 番目失敗で全件 rollback)
40
+ // - rollback timestamp は path.basename + regex で path traversal 防止
41
+ // - loadMetadata は process-lifetime cache、/api/set 成功で invalidate
42
+ // - module.exports に unit test seam (callHcConfig / loadMetadata / readJsonBody / serveStatic)
43
+ //
44
+ // iter 4 A 修正項目 (10 件):
45
+ // NEW-C-1 (CRIT): hcListAll() で /api/keys を 74 spawn → 1 spawn に集約 (Node event loop 370s block 解消)
46
+ // NF-13 (HIGH): history stamp に pid + counter 追加 (同 ms 衝突 silent data loss 防止)
47
+ // NF-2/9 (HIGH): applyPreset history write 失敗時 snapshot から rollback 実行 (silent data loss 防止)
48
+ // NF-8 (HIGH): handleRequest catch で internal abs path leak 防止 (REPO_ROOT → <repo> sanitize、stack trace 除外)
49
+ // HIGH-Q3 (MED) : savePreset で invalidateMetadataCache() 追加 (一貫性確保)
50
+ // NF-12 (MED) : NEW-C-1 統合で解消 (本項目は no-op 補強のみ)
51
+ // NF-3 (MED) : rollback restore 順序 LIFO 化 (apply 順 → 逆順、依存 key 不整合最小化)
52
+ // NF-6 (MED) : listHistory cleanup 機構追加 (1000 件超で古い順削除、HC_HISTORY_MAX_FILES env 上書き可)
53
+ // NF-10 (MED) : callHcConfig で timeout 判定 + timedOut flag 追加 (UI 側で "timeout 5s 超過" 明示)
54
+ // 設計乖離 : savePreset name regex を ^[a-z0-9][a-z0-9-]{2,48}$ (3-49 char、app.js と統一)、axes 6 軸完全必須
55
+ //
56
+ // API endpoint:
57
+ // GET / → redirect /static/index.html
58
+ // GET /static/* → static file serve (index.html / app.js / style.css)
59
+ // GET /api/keys → 74 key + metadata (現在値含む、bulk hcListAll = 1 spawn)
60
+ // GET /api/categories → 6 category 一覧
61
+ // GET /api/current-preset → 現在 yml 全 key 値 vs PRESETS values 完全一致 subset 判定 (task-63 Step 4 案 C)
62
+ // GET /api/value/:key → 単一 key 現在値
63
+ // POST /api/set → {key,value} → hc-config.sh --set
64
+ // GET /api/presets → 10 preset 一覧 (display_name_ja + use_case + affected_key_count)
65
+ // GET /api/preset/:name/diff → preset 適用差分 (effect 列含む)
66
+ // POST /api/preset/:name/apply → batch hc-config.sh --set + history 保存 (atomic)
67
+ // GET /api/preset/history → 適用履歴一覧
68
+ // POST /api/preset/rollback/:ts → history rollback (chain 修復 + traversal 防止)
69
+ //
70
+ // task-63 Step 4 撤去 (user 確定要求「プリセット名保存不要」):
71
+ // POST /api/preset/save (404 fallback で応答、savePreset/scanCustomPresets/isTestPollutionName 全て撤去)
72
+ //
73
+ // 起源: docs/draft/hc-config-web-ui.md §3.1 §3.2 §3.4 §6 / docs/tasks/task-61-hc-config-web-ui.md Step 5
74
+
75
+ 'use strict'
76
+
77
+ const http = require('http')
78
+ const { spawnSync } = require('child_process')
79
+ const fs = require('fs')
80
+ const path = require('path')
81
+ const os = require('os')
82
+
83
+ // ============================================================
84
+ // 定数 / パス解決
85
+ // ============================================================
86
+
87
+ const SCRIPT_DIR = __dirname // .claude/scripts/lib
88
+ const HC_CONFIG_SCRIPT = path.resolve(SCRIPT_DIR, '..', 'hc-config.sh')
89
+ const REPO_ROOT = path.resolve(SCRIPT_DIR, '..', '..', '..')
90
+ const STATIC_DIR = path.join(SCRIPT_DIR, 'hc-config-web-ui')
91
+ // iter 6 A item 1 (HIGH 3-rev): env override で test isolation を可能化
92
+ // smoke が HC_HISTORY_DIR_OVERRIDE を渡しても server.js が読まなかった bug
93
+ // (server boot で常に repo root の .claude/.preset-history を参照)
94
+ const HISTORY_DIR = process.env.HC_HISTORY_DIR_OVERRIDE || path.join(REPO_ROOT, '.claude', '.preset-history')
95
+ // task-63 Step 4 A1: PRESETS_DIR は撤去済 (custom preset 保存機能廃止)
96
+ // env override (HC_PRESETS_DIR_OVERRIDE) は smoke regression 互換のため定義のみ維持 (実利用なし)
97
+
98
+ // task-69 Step 3: harness-config.yml path (key parity の SSoT)。
99
+ // /api/keys の key 集合は metadata ではなく yml top-level key を基準にする (hc-config.sh _yml_list_keys と同一)。
100
+ const HARNESS_CONFIG_PATH = path.join(REPO_ROOT, '.claude', 'harness-config.yml')
101
+
102
+ const PORT_MIN = 3060
103
+ const PORT_MAX = 3070
104
+ const HOST = '127.0.0.1'
105
+ const HC_SUBPROCESS_TIMEOUT_MS = 5000
106
+
107
+ // NF-6: history file 最大保持件数 (超過は古い順 cleanup)
108
+ // iter 6 A item 3 (MED): NaN / 負値 / 0 は安全 fallback 1000
109
+ const _historyMaxParsed = parseInt(process.env.HC_HISTORY_MAX_FILES || '1000', 10)
110
+ const HISTORY_MAX_FILES = Number.isFinite(_historyMaxParsed) && _historyMaxParsed > 0 ? _historyMaxParsed : 1000
111
+
112
+ // rollback timestamp validation (M-P3): ISO-8601 風 + preset name 区切り
113
+ // 実 history file 名 stem は `2026-05-29T12-34-56-789Z-poc-no-git` 形式 (toISOString の `:` / `.` を `-` 置換)
114
+ // NF-13 後の format: `<ISO-stamp>-<pid>-<counter>-<preset>.json` or `<ISO-stamp>-<pid>-<counter>-rollback-of-<src>.json`
115
+ // 後段 path.basename も併用して path traversal を 2 重に防ぐ。
116
+ // regex は旧 format (pid/counter 無し) も後方互換で受理 (英数 + `-` + `_` + `.` 許容)。
117
+ const ROLLBACK_TS_REGEX = /^[0-9TZ-]+[a-z0-9._-]+$/i
118
+
119
+ const MIME_TYPES = {
120
+ '.html': 'text/html; charset=utf-8',
121
+ '.js': 'application/javascript; charset=utf-8',
122
+ '.css': 'text/css; charset=utf-8',
123
+ '.json': 'application/json; charset=utf-8',
124
+ '.svg': 'image/svg+xml',
125
+ '.png': 'image/png',
126
+ '.ico': 'image/x-icon',
127
+ }
128
+
129
+ // ============================================================
130
+ // 6 軸 + 10 named preset hardcode (draft §3.4)
131
+ // ============================================================
132
+
133
+ // task-76 Step 2: preset を左ペインで group 分けするための quality_level → 日本語ラベル写像。
134
+ // draft §3.4 note (SSoT): 実在軸 quality_level で 3 group に分割 (POC / 社内ツール / 本番運用)。
135
+ // PRESETS には enforcement level (advisory/team-default/strict/harness-dev) との対応 field が無いため、
136
+ // level による section 分けは不採用。代わりに各 preset.axes.quality_level を本 lookup で日本語 group 名へ写像する。
137
+ // fallback 'その他': quality_level 不在 / 上記 3 値以外。なお harness-development preset は
138
+ // axes.quality_level='inner_system' なので「社内ツール」group に入る (実データ確認済、2026-06-03)。
139
+ const QUALITY_LEVEL_GROUP_JA = {
140
+ poc: 'POC',
141
+ inner_system: '社内ツール',
142
+ production_service: '本番運用',
143
+ }
144
+ const PRESET_GROUP_FALLBACK = 'その他'
145
+
146
+ // preset の quality_level から日本語 group ラベルを解決する (左ペイン分類用)
147
+ function resolvePresetGroup(preset) {
148
+ const ql = preset && preset.axes ? preset.axes.quality_level : undefined
149
+ return QUALITY_LEVEL_GROUP_JA[ql] || PRESET_GROUP_FALLBACK
150
+ }
151
+
152
+ // task-77 Step 5 §3.6 (SSoT、display-only 明示): 以下 6 軸 (quality_level / language_framework /
153
+ // git_workflow / tdd_policy / review_intensity / autonomy_level) は **preset の分類表示専用ラベル**であり、
154
+ // harness-config.yml の実 key ではない。axis 値 (例 git_workflow='none'/'unrestricted') は git 制御を含む
155
+ // いかなる behavior にも一切影響しない。
156
+ // 実際の git 統合制御は `mainline_integration_policy` (実 key、右ペイン Gate/Confidence accordion で編集可、
157
+ // 3 値 pr-required/local-merge/local-merge-push) が担う。git_workflow axis label を見て「保護なし」等と
158
+ // 誤認しないこと (security L-1)。6 軸全体の去就 (撤去 or 恒久ラベル化) は別 task で判断 (draft §3.6)。
159
+ const PRESET_AXES = [
160
+ { key: 'quality_level', values: ['poc', 'inner_system', 'production_service'] },
161
+ { key: 'language_framework', values: ['mixed', 'typescript', 'python', 'rust', 'go'] },
162
+ { key: 'git_workflow', values: ['none', 'unrestricted', 'main_protected', 'main_stg_protected'] },
163
+ { key: 'tdd_policy', values: ['optional', 'recommended', 'mandatory'] },
164
+ { key: 'review_intensity', values: ['minimum', 'standard', 'strict'] },
165
+ { key: 'autonomy_level', values: ['aggressive', 'moderate', 'conservative'] },
166
+ ]
167
+
168
+ const PRESETS = {
169
+ 'poc-no-git': {
170
+ display_name_ja: 'POC・お試し (Git なし)',
171
+ axes: { quality_level: 'poc', language_framework: 'mixed', git_workflow: 'none', tdd_policy: 'optional', review_intensity: 'minimum', autonomy_level: 'aggressive' },
172
+ use_case: '実験 / 一時試作 (1 日以内、捨てる予定)',
173
+ values: {
174
+ confidence_threshold: '0.5',
175
+ confidence_required: 'false',
176
+ review_required_design: 'false',
177
+ review_required_test: 'false',
178
+ review_required_module: 'false',
179
+ review_required_system: 'false',
180
+ review_required_security: 'false',
181
+ review_min_count_design: '1',
182
+ review_min_count_test: '1',
183
+ review_iteration_max: '2',
184
+ feature_loop_mode_enforcement_enabled: 'true',
185
+ feature_gateguard_enabled: 'false',
186
+ feature_workflow_enforcement_enabled: 'false',
187
+ feature_draft_flow_guard_enabled: 'false',
188
+ feature_task_rule_guard_enabled: 'false',
189
+ mainline_integration_policy: 'local-merge-push',
190
+ },
191
+ },
192
+ 'poc-with-git': {
193
+ display_name_ja: 'POC・お試し (Git あり)',
194
+ axes: { quality_level: 'poc', language_framework: 'mixed', git_workflow: 'unrestricted', tdd_policy: 'optional', review_intensity: 'minimum', autonomy_level: 'aggressive' },
195
+ use_case: '個人 spike / 軽量 POC (Git 管理あり)',
196
+ values: {
197
+ confidence_threshold: '0.5',
198
+ confidence_required: 'false',
199
+ review_required_design: 'false',
200
+ review_required_test: 'false',
201
+ review_required_module: 'false',
202
+ review_required_system: 'false',
203
+ review_min_count_design: '1',
204
+ review_min_count_test: '2',
205
+ review_iteration_max: '2',
206
+ feature_gateguard_enabled: 'false',
207
+ feature_workflow_enforcement_enabled: 'false',
208
+ mainline_integration_policy: 'local-merge-push',
209
+ },
210
+ },
211
+ 'inner-typescript': {
212
+ display_name_ja: '社内ツール (TypeScript)',
213
+ axes: { quality_level: 'inner_system', language_framework: 'typescript', git_workflow: 'main_protected', tdd_policy: 'recommended', review_intensity: 'standard', autonomy_level: 'moderate' },
214
+ use_case: '内部 tool TypeScript (社内利用、main protected)',
215
+ values: {
216
+ confidence_threshold: '0.6',
217
+ confidence_required: 'true',
218
+ review_required_design: 'true',
219
+ review_required_test: 'true',
220
+ review_required_module: 'true',
221
+ review_required_system: 'false',
222
+ review_min_count_design: '3',
223
+ review_min_count_test: '3',
224
+ review_min_count_module: '2',
225
+ review_iteration_max: '3',
226
+ feature_gateguard_enabled: 'true',
227
+ feature_workflow_enforcement_enabled: 'true',
228
+ mainline_integration_policy: 'pr-required',
229
+ },
230
+ },
231
+ 'inner-python': {
232
+ display_name_ja: '社内ツール (Python)',
233
+ axes: { quality_level: 'inner_system', language_framework: 'python', git_workflow: 'main_protected', tdd_policy: 'recommended', review_intensity: 'standard', autonomy_level: 'moderate' },
234
+ use_case: '内部 tool Python (社内利用、main protected)',
235
+ values: {
236
+ confidence_threshold: '0.6',
237
+ confidence_required: 'true',
238
+ review_required_design: 'true',
239
+ review_required_test: 'true',
240
+ review_required_module: 'true',
241
+ review_required_system: 'false',
242
+ review_min_count_design: '3',
243
+ review_min_count_test: '3',
244
+ review_min_count_module: '2',
245
+ review_iteration_max: '3',
246
+ feature_gateguard_enabled: 'true',
247
+ feature_workflow_enforcement_enabled: 'true',
248
+ mainline_integration_policy: 'pr-required',
249
+ },
250
+ },
251
+ 'production-typescript-personal': {
252
+ display_name_ja: '本番運用・個人 (TypeScript)',
253
+ axes: { quality_level: 'production_service', language_framework: 'typescript', git_workflow: 'main_stg_protected', tdd_policy: 'mandatory', review_intensity: 'standard', autonomy_level: 'moderate' },
254
+ use_case: '個人 production (classlab 等、main/stg protected)',
255
+ values: {
256
+ confidence_threshold: '0.6',
257
+ confidence_required: 'true',
258
+ review_required_design: 'true',
259
+ review_required_test: 'true',
260
+ review_required_module: 'true',
261
+ review_required_system: 'true',
262
+ review_required_security: 'false',
263
+ review_min_count_design: '3',
264
+ review_min_count_test: '5',
265
+ review_min_count_module: '2',
266
+ review_min_count_system: '2',
267
+ review_iteration_max: '5',
268
+ feature_gateguard_enabled: 'true',
269
+ feature_workflow_enforcement_enabled: 'true',
270
+ feature_confidence_gate_enabled: 'true',
271
+ mainline_integration_policy: 'pr-required',
272
+ },
273
+ },
274
+ 'production-typescript-enterprise': {
275
+ display_name_ja: '本番運用・企業 (TypeScript)',
276
+ axes: { quality_level: 'production_service', language_framework: 'typescript', git_workflow: 'main_stg_protected', tdd_policy: 'mandatory', review_intensity: 'strict', autonomy_level: 'conservative' },
277
+ use_case: '企業 production TypeScript (strict review + conservative autonomy)',
278
+ values: {
279
+ confidence_threshold: '0.7',
280
+ confidence_required: 'true',
281
+ review_required_design: 'true',
282
+ review_required_test: 'true',
283
+ review_required_module: 'true',
284
+ review_required_system: 'true',
285
+ review_required_security: 'true',
286
+ review_min_count_design: '5',
287
+ review_min_count_test: '7',
288
+ review_min_count_module: '3',
289
+ review_min_count_system: '3',
290
+ review_min_count_security: '2',
291
+ review_iteration_max: '5',
292
+ feature_gateguard_enabled: 'true',
293
+ feature_workflow_enforcement_enabled: 'true',
294
+ feature_confidence_gate_enabled: 'true',
295
+ feature_autonomous_action_guard_enabled: 'true',
296
+ mainline_integration_policy: 'pr-required',
297
+ },
298
+ },
299
+ 'production-python': {
300
+ display_name_ja: '本番運用 (Python)',
301
+ axes: { quality_level: 'production_service', language_framework: 'python', git_workflow: 'main_stg_protected', tdd_policy: 'mandatory', review_intensity: 'strict', autonomy_level: 'conservative' },
302
+ use_case: '企業 production Python (strict + conservative)',
303
+ values: {
304
+ confidence_threshold: '0.7',
305
+ confidence_required: 'true',
306
+ review_required_design: 'true',
307
+ review_required_test: 'true',
308
+ review_required_module: 'true',
309
+ review_required_system: 'true',
310
+ review_required_security: 'true',
311
+ review_min_count_design: '5',
312
+ review_min_count_test: '7',
313
+ review_min_count_module: '3',
314
+ review_min_count_system: '3',
315
+ review_iteration_max: '5',
316
+ feature_gateguard_enabled: 'true',
317
+ feature_workflow_enforcement_enabled: 'true',
318
+ feature_autonomous_action_guard_enabled: 'true',
319
+ mainline_integration_policy: 'pr-required',
320
+ },
321
+ },
322
+ 'production-rust': {
323
+ display_name_ja: '本番運用 (Rust)',
324
+ axes: { quality_level: 'production_service', language_framework: 'rust', git_workflow: 'main_stg_protected', tdd_policy: 'mandatory', review_intensity: 'strict', autonomy_level: 'conservative' },
325
+ use_case: '企業 production Rust (strict + conservative)',
326
+ values: {
327
+ confidence_threshold: '0.7',
328
+ confidence_required: 'true',
329
+ review_required_design: 'true',
330
+ review_required_test: 'true',
331
+ review_required_module: 'true',
332
+ review_required_system: 'true',
333
+ review_required_security: 'true',
334
+ review_min_count_design: '5',
335
+ review_min_count_test: '7',
336
+ review_min_count_module: '3',
337
+ review_min_count_system: '3',
338
+ review_iteration_max: '5',
339
+ feature_gateguard_enabled: 'true',
340
+ feature_workflow_enforcement_enabled: 'true',
341
+ feature_autonomous_action_guard_enabled: 'true',
342
+ mainline_integration_policy: 'pr-required',
343
+ },
344
+ },
345
+ 'production-go': {
346
+ display_name_ja: '本番運用 (Go)',
347
+ axes: { quality_level: 'production_service', language_framework: 'go', git_workflow: 'main_stg_protected', tdd_policy: 'mandatory', review_intensity: 'strict', autonomy_level: 'conservative' },
348
+ use_case: '企業 production Go (strict + conservative)',
349
+ values: {
350
+ confidence_threshold: '0.7',
351
+ confidence_required: 'true',
352
+ review_required_design: 'true',
353
+ review_required_test: 'true',
354
+ review_required_module: 'true',
355
+ review_required_system: 'true',
356
+ review_required_security: 'true',
357
+ review_min_count_design: '5',
358
+ review_min_count_test: '7',
359
+ review_min_count_module: '3',
360
+ review_min_count_system: '3',
361
+ review_iteration_max: '5',
362
+ feature_gateguard_enabled: 'true',
363
+ feature_workflow_enforcement_enabled: 'true',
364
+ feature_autonomous_action_guard_enabled: 'true',
365
+ mainline_integration_policy: 'pr-required',
366
+ },
367
+ },
368
+ 'harness-development': {
369
+ display_name_ja: 'ハーネス開発専用',
370
+ axes: { quality_level: 'inner_system', language_framework: 'mixed', git_workflow: 'main_protected', tdd_policy: 'recommended', review_intensity: 'strict', autonomy_level: 'moderate' },
371
+ use_case: 'hirai-method 自体の開発 (dogfooding)',
372
+ values: {
373
+ confidence_threshold: '0.6',
374
+ confidence_required: 'true',
375
+ review_required_design: 'true',
376
+ review_required_test: 'true',
377
+ review_required_module: 'true',
378
+ review_required_system: 'true',
379
+ review_min_count_design: '3',
380
+ review_min_count_test: '5',
381
+ review_min_count_module: '2',
382
+ review_min_count_system: '2',
383
+ review_iteration_max: '5',
384
+ feature_gateguard_enabled: 'true',
385
+ feature_workflow_enforcement_enabled: 'true',
386
+ feature_confidence_gate_enabled: 'true',
387
+ mainline_integration_policy: 'local-merge',
388
+ },
389
+ },
390
+ }
391
+
392
+ // ============================================================
393
+ // bash subprocess (hc-config.sh CLI 呼出) — DI 化 (C-unit-seam)
394
+ // ============================================================
395
+
396
+ // overrides.spawnFn: テスト用 DI seam (default は spawnSync)
397
+ // NF-10: timeout 時 timedOut フラグを返し、UI 側で "5s 超過" を明示できるようにする
398
+ function callHcConfig(args, overrides) {
399
+ overrides = overrides || {}
400
+ const spawnFn = overrides.spawnFn || spawnSync
401
+ const result = spawnFn('bash', [HC_CONFIG_SCRIPT, ...args], {
402
+ encoding: 'utf8',
403
+ timeout: HC_SUBPROCESS_TIMEOUT_MS,
404
+ cwd: REPO_ROOT,
405
+ })
406
+ // NF-10: timeout 判定
407
+ // spawnSync timeout: result.signal === 'SIGTERM' || error.code === 'ETIMEDOUT'
408
+ // exitCode === null (signal kill) も timeout 候補
409
+ const timedOut =
410
+ !!(result.error && (result.error.code === 'ETIMEDOUT' || result.error.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER')) ||
411
+ result.signal === 'SIGTERM' ||
412
+ result.status === null
413
+ return {
414
+ exitCode: result.status === null ? -1 : result.status,
415
+ stdout: result.stdout || '',
416
+ stderr: result.stderr || '',
417
+ error: result.error ? String(result.error.message) : null,
418
+ timedOut,
419
+ }
420
+ }
421
+
422
+ function hcGet(key, overrides) {
423
+ const r = callHcConfig(['--get', key], overrides)
424
+ if (r.exitCode !== 0) return null
425
+ return r.stdout.replace(/\n$/, '')
426
+ }
427
+
428
+ function hcSet(key, value, overrides) {
429
+ return callHcConfig(['--set', `${key}=${value}`], overrides)
430
+ }
431
+
432
+ // ============================================================
433
+ // NEW-C-1 fix: hcListAll() で全 key=value を 1 spawnSync で一括取得
434
+ // ============================================================
435
+ //
436
+ // 旧経路: /api/keys は 74 個別 hcGet (=74 spawnSync) を直列実行 → Node event loop ~370s block
437
+ // 新経路: bash subprocess を 1 回起動し、その内部で metadata.sh の key 一覧を取得後、
438
+ // 各 key について `hc-config.sh --get` を bash subprocess 内部 loop で呼び TSV 出力。
439
+ // Node spawnSync は 1 回のみで、内部 bash の child process spawn は Node event loop を block しない。
440
+ //
441
+ // 出力 format: <key>\t<value>\n (改行終端、空 value は <key>\t\n)
442
+ // 失敗 key は entry なし (Node 側で undefined → null として扱う)
443
+ //
444
+ // overrides.spawnFn: テスト用 DI seam
445
+
446
+ let _keysValueCache = null
447
+
448
+ function invalidateKeysValueCache() {
449
+ _keysValueCache = null
450
+ }
451
+
452
+ // ============================================================
453
+ // task-69 Step 3: yml top-level key 一覧 (key parity の SSoT)
454
+ // ============================================================
455
+ //
456
+ // /api/keys の key 集合は metadata (表示補助) ではなく harness-config.yml の
457
+ // top-level key を基準にする。hc-config.sh の _yml_list_keys (`^[a-z_][a-zA-Z0-9_]*:`)
458
+ // と同一の regex で抽出し、CLI / Web / TUI の key 集合を一致させる。
459
+ // - 旧 /api/keys は metadata entry 数を基準に enrich していたため、metadata 未登録 yml key が欠落していた。
460
+ // - 本 helper は yml を直接 read するため metadata 登録有無に依存しない (= metadata に無くても拾う)。
461
+ // overrides.ymlPath: テスト用 path 差替 seam (default は HARNESS_CONFIG_PATH)
462
+ //
463
+ // 戻り値: yml top-level key の配列 (出現順保持、重複除去)。read 失敗時は []。
464
+ function listYamlKeys(overrides) {
465
+ overrides = overrides || {}
466
+ const ymlPath = overrides.ymlPath || HARNESS_CONFIG_PATH
467
+ let content
468
+ try {
469
+ content = fs.readFileSync(ymlPath, 'utf8')
470
+ } catch (e) {
471
+ return []
472
+ }
473
+ const keys = []
474
+ const seen = Object.create(null)
475
+ for (const line of content.split('\n')) {
476
+ // hc-config.sh _yml_list_keys と同一: 行頭 [a-z_] 始まり + 識別子 + ':'
477
+ const m = line.match(/^([a-z_][a-zA-Z0-9_]*):/)
478
+ if (!m) continue
479
+ const key = m[1]
480
+ if (seen[key]) continue
481
+ seen[key] = true
482
+ keys.push(key)
483
+ }
484
+ return keys
485
+ }
486
+
487
+ function hcListAll(overrides) {
488
+ overrides = overrides || {}
489
+ if (_keysValueCache && !overrides.bypassCache) return _keysValueCache
490
+ const spawnFn = overrides.spawnFn || spawnSync
491
+ // bash one-shot:
492
+ // 1. metadata.sh source して全 key 一覧を取得 (hc_metadata_table | cut -f1)
493
+ // 2. 各 key を hc-config.sh --get で取得し TSV 出力
494
+ // - bash 3.2 互換 (associative array 不使用)
495
+ // - エラー key は silent skip (空行ではなく entry 自体省略)
496
+ const script = `
497
+ set -u
498
+ META="${path.join(SCRIPT_DIR, 'hc-config-metadata.sh').replace(/"/g, '\\"')}"
499
+ HCSCRIPT="${HC_CONFIG_SCRIPT.replace(/"/g, '\\"')}"
500
+ # shellcheck disable=SC1090
501
+ source "$META" 2>/dev/null || exit 1
502
+ keys=$(_hc_metadata_table 2>/dev/null | awk -F'\\t' 'NF>0 && $1!=""{print $1}')
503
+ [ -z "$keys" ] && exit 0
504
+ while IFS= read -r k; do
505
+ [ -z "$k" ] && continue
506
+ v=$(bash "$HCSCRIPT" --get "$k" 2>/dev/null) || continue
507
+ # trailing newline は cmd_get で常に付与されるので除去
508
+ v=\${v%$'\\n'}
509
+ printf '%s\\t%s\\n' "$k" "$v"
510
+ done <<< "$keys"
511
+ `
512
+ const r = spawnFn('bash', ['-c', script], {
513
+ encoding: 'utf8',
514
+ timeout: HC_SUBPROCESS_TIMEOUT_MS * 6, // 74 key 内部 fork ぶん拡張 (30s)
515
+ cwd: REPO_ROOT,
516
+ })
517
+ if (r.status !== 0 && !r.stdout) return {}
518
+ const lines = (r.stdout || '').split('\n').filter((l) => l.length > 0)
519
+ const map = {}
520
+ for (const line of lines) {
521
+ const idx = line.indexOf('\t')
522
+ if (idx === -1) continue
523
+ const key = line.slice(0, idx)
524
+ const value = line.slice(idx + 1)
525
+ map[key] = value
526
+ }
527
+ if (!overrides.bypassCache) {
528
+ _keysValueCache = map
529
+ }
530
+ return map
531
+ }
532
+
533
+ // ============================================================
534
+ // metadata cache (H-1: process-lifetime cache、/api/set で invalidate)
535
+ // ============================================================
536
+
537
+ let _metadataCache = null
538
+
539
+ function invalidateMetadataCache() {
540
+ _metadataCache = null
541
+ // NEW-C-1: keys-value cache も同期 invalidate (一貫性保証)
542
+ invalidateKeysValueCache()
543
+ }
544
+
545
+ // _hc_metadata_table 全体を一括取得 (74 行 TSV) — bash 経由で一度だけ source して dump
546
+ // overrides.spawnFn: テスト用 DI seam
547
+ // overrides.bypassCache: cache を無視して fresh load (テスト用)
548
+ function loadMetadata(overrides) {
549
+ overrides = overrides || {}
550
+ if (_metadataCache && !overrides.bypassCache) return _metadataCache
551
+ const spawnFn = overrides.spawnFn || spawnSync
552
+ const script = `source "${path.join(SCRIPT_DIR, 'hc-config-metadata.sh')}" && _hc_metadata_table`
553
+ const r = spawnFn('bash', ['-c', script], {
554
+ encoding: 'utf8',
555
+ timeout: HC_SUBPROCESS_TIMEOUT_MS,
556
+ })
557
+ if (r.status !== 0) return []
558
+ const lines = (r.stdout || '').split('\n').filter((l) => l.length > 0)
559
+ const result = lines.map((line) => {
560
+ const parts = line.split('\t')
561
+ return {
562
+ key: parts[0] || '',
563
+ category: parts[1] || '',
564
+ description: parts[2] || '',
565
+ effect: parts[3] || '',
566
+ // task-78 Step 1: 5 列目 label_ja (短い日本語ラベル、欠落は空 fallback で後方互換)
567
+ label_ja: parts[4] || '',
568
+ }
569
+ })
570
+ if (!overrides.bypassCache) {
571
+ _metadataCache = result
572
+ }
573
+ return result
574
+ }
575
+
576
+ // known key set (H-11: /api/set whitelist 検証)
577
+ function getKnownKeys(overrides) {
578
+ const md = loadMetadata(overrides)
579
+ return new Set(md.map((m) => m.key).filter((k) => k.length > 0))
580
+ }
581
+
582
+ // effect lookup helper (draft §3 diff table effect 列)
583
+ function getEffectMap(overrides) {
584
+ const md = loadMetadata(overrides)
585
+ const map = {}
586
+ for (const m of md) {
587
+ if (m.key) map[m.key] = m.effect || ''
588
+ }
589
+ return map
590
+ }
591
+
592
+ // ============================================================
593
+ // NF-13: history stamp 生成 (pid + counter で同 ms 衝突回避)
594
+ // ============================================================
595
+
596
+ let _historyStampCounter = 0
597
+
598
+ function nextHistoryStamp() {
599
+ _historyStampCounter = (_historyStampCounter + 1) % 1000000
600
+ const iso = new Date().toISOString().replace(/[:.]/g, '-')
601
+ return `${iso}-${process.pid}-${_historyStampCounter}`
602
+ }
603
+
604
+ // ============================================================
605
+ // NF-6: history file cleanup (最大 HISTORY_MAX_FILES 件、古い順削除)
606
+ // ============================================================
607
+
608
+ function cleanupHistoryFiles() {
609
+ try {
610
+ if (!fs.existsSync(HISTORY_DIR)) return
611
+ const files = fs
612
+ .readdirSync(HISTORY_DIR)
613
+ .filter((f) => f.endsWith('.json'))
614
+ .sort() // 名前順 (= 時系列順、ISO stamp prefix のため)
615
+ if (files.length <= HISTORY_MAX_FILES) return
616
+ const excess = files.length - HISTORY_MAX_FILES
617
+ for (let i = 0; i < excess; i++) {
618
+ try {
619
+ fs.unlinkSync(path.join(HISTORY_DIR, files[i]))
620
+ } catch (_) {
621
+ // cleanup 失敗は silent skip (本流 apply に影響させない)
622
+ }
623
+ }
624
+ } catch (_) {
625
+ // dir 読み取り失敗も silent (本流に影響させない)
626
+ }
627
+ }
628
+
629
+ // ============================================================
630
+ // preset diff 計算 / apply (atomic) / rollback (chain repair)
631
+ // ============================================================
632
+
633
+ // H-12 + draft §3: diff の changed 判定は現在値取得成功 + 値差分の AND
634
+ // 現在値が取得できない (key 不在) なら changed: false に降格 (apply 対象から除外)
635
+ // effect: metadata から lookup
636
+ // task-76 Step 1 (diff「Failed to fetch」修復): key ごとの hcGet ループ
637
+ // (= N 回の spawnSync で Node event loop を同期ブロック、ブラウザ keep-alive
638
+ // 並列接続で hung connection → TypeError: Failed to fetch) を、hcListAll() で
639
+ // 全 key 値を 1 回だけ取得し cache 参照する形に変更。これで 2 回目以降の diff
640
+ // request の event loop ブロックが 0ms 化する。key 不在は null フォールバック
641
+ // (旧 hcGet 失敗時と同義: current='<unknown>' + changed:false + error 注記)。
642
+ // API response 構造 (changes 配列の各要素 key/current/new/changed/effect) は不変。
643
+ function computePresetDiff(presetName, overrides) {
644
+ const preset = PRESETS[presetName]
645
+ if (!preset) return null
646
+ const effectMap = getEffectMap(overrides)
647
+ // 全 key 値を 1 spawn で一括取得 (cache hit で 0 spawn)。key ごとの hcGet を排除。
648
+ const allValues = hcListAll(overrides)
649
+ const changes = []
650
+ for (const [key, newVal] of Object.entries(preset.values)) {
651
+ const currentVal = Object.prototype.hasOwnProperty.call(allValues, key) ? allValues[key] : null
652
+ const effect = effectMap[key] || ''
653
+ if (currentVal === null || currentVal === undefined) {
654
+ // 現在値取得不可 (key 不在) → changed: false に降格 + error 注記 (UI 側で skip 表示)
655
+ changes.push({
656
+ key,
657
+ current: '<unknown>',
658
+ new: String(newVal),
659
+ changed: false,
660
+ effect,
661
+ error: 'hc-config --get failed (skipped)',
662
+ })
663
+ continue
664
+ }
665
+ const changed = String(currentVal) !== String(newVal)
666
+ changes.push({
667
+ key,
668
+ current: String(currentVal),
669
+ new: String(newVal),
670
+ changed,
671
+ effect,
672
+ })
673
+ }
674
+ return { preset: presetName, axes: preset.axes, use_case: preset.use_case, changes }
675
+ }
676
+
677
+ // C-atomic: 2-phase atomic apply
678
+ // phase 1: 全変更対象 key の現在値を snapshot
679
+ // phase 2: hcSet 順次実行、N 番目失敗で snapshot から rollback
680
+ //
681
+ // NF-2/9 fix: history write 失敗時、yml 適用済みを snapshot から rollback (silent data loss 防止)
682
+ // NF-13 fix: history stamp に pid + counter 追加 (同 ms 衝突回避)
683
+ function applyPreset(presetName, skipKeys, overrides) {
684
+ const diff = computePresetDiff(presetName, overrides)
685
+ if (!diff) return { ok: false, error: 'unknown preset' }
686
+ const skipSet = new Set(skipKeys || [])
687
+
688
+ // phase 1: snapshot 対象 key 抽出 + 現在値確定
689
+ const targets = []
690
+ const snapshot = {}
691
+ for (const change of diff.changes) {
692
+ if (!change.changed) continue
693
+ if (skipSet.has(change.key)) continue
694
+ targets.push(change)
695
+ snapshot[change.key] = change.current
696
+ }
697
+
698
+ // phase 2: 順次 apply、失敗で全件 rollback
699
+ const applied = []
700
+ let aborted = null
701
+ for (const change of targets) {
702
+ const r = hcSet(change.key, change.new, overrides)
703
+ if (r.exitCode === 0) {
704
+ applied.push({ key: change.key, from: change.current, to: change.new })
705
+ } else {
706
+ aborted = { key: change.key, from: change.current, to: change.new, stderr: r.stderr }
707
+ break
708
+ }
709
+ }
710
+
711
+ // C-atomic phase 2 abort 時の rollback (snapshot 復元)
712
+ // NF-3: rollback restore 順序を LIFO (apply 逆順) で実行 (依存 key 不整合最小化)
713
+ const rolledBack = []
714
+ const rollbackFailed = []
715
+ if (aborted) {
716
+ const reverseApplied = applied.slice().reverse()
717
+ for (const a of reverseApplied) {
718
+ const r = hcSet(a.key, a.from, overrides)
719
+ if (r.exitCode === 0) {
720
+ rolledBack.push(a.key)
721
+ } else {
722
+ rollbackFailed.push({ key: a.key, target: a.from, stderr: r.stderr })
723
+ }
724
+ }
725
+ }
726
+
727
+ // history 保存 (snapshot 込み、rollback chain 用)
728
+ try {
729
+ if (!fs.existsSync(HISTORY_DIR)) {
730
+ fs.mkdirSync(HISTORY_DIR, { recursive: true })
731
+ }
732
+ } catch (e) {
733
+ // NF-2/9: history dir create 失敗 → snapshot rollback 実行 (apply 済 yml を元に戻す)
734
+ const dirRecover = rollbackAppliedFromSnapshot(applied, snapshot, overrides)
735
+ return {
736
+ ok: false,
737
+ error: 'history dir create failed: ' + e.message,
738
+ applied: 0,
739
+ failed: aborted ? 1 : 0,
740
+ rolled_back_on_history_failure: dirRecover.recovered,
741
+ rollback_failed_on_history_failure: dirRecover.failed,
742
+ }
743
+ }
744
+ // NF-13: pid + counter で stamp unique 化
745
+ const stamp = nextHistoryStamp()
746
+ const histFile = path.join(HISTORY_DIR, `${stamp}-${presetName}.json`)
747
+ const histPayload = {
748
+ preset: presetName,
749
+ applied_at: new Date().toISOString(),
750
+ type: 'apply',
751
+ axes: diff.axes,
752
+ use_case: diff.use_case,
753
+ snapshot, // C-atomic: rollback / chain 用に全対象 key の元値
754
+ targets: targets.map((t) => ({ key: t.key, from: t.current, to: t.new })),
755
+ applied: aborted ? [] : applied, // abort 時は実質 0 件 (rollback 済)
756
+ failed: aborted ? [aborted] : [],
757
+ rolled_back: rolledBack,
758
+ rollback_failed: rollbackFailed,
759
+ skipped: Array.from(skipSet),
760
+ }
761
+ try {
762
+ // C-atomic write: tmp → rename で history file 自体の atomic 保証
763
+ const tmpFile = `${histFile}.tmp`
764
+ fs.writeFileSync(tmpFile, JSON.stringify(histPayload, null, 2))
765
+ fs.renameSync(tmpFile, histFile)
766
+ } catch (e) {
767
+ // NF-2/9: history write 失敗 → snapshot から apply 済 yml を rollback
768
+ // abort 経由なら applied は既に rollback 済なので no-op
769
+ // abort なし (= 全 key 成功) なら applied から snapshot 復元
770
+ const writeRecover = rollbackAppliedFromSnapshot(applied, snapshot, overrides)
771
+ return {
772
+ ok: false,
773
+ error: 'history_write_failed',
774
+ detail: e.message,
775
+ applied: 0,
776
+ failed: aborted ? 1 : 0,
777
+ rolled_back_on_history_failure: writeRecover.recovered,
778
+ rollback_failed_on_history_failure: writeRecover.failed,
779
+ message:
780
+ writeRecover.failed.length === 0
781
+ ? 'history write failed, yml was rolled back to original state'
782
+ : `history write failed and partial rollback occurred (${writeRecover.failed.length} keys failed to restore)`,
783
+ }
784
+ }
785
+
786
+ // NF-6: history write 成功後に cleanup (古い分削除、本流に影響させない)
787
+ cleanupHistoryFiles()
788
+
789
+ // H-7: partial failure は ok:false だが 200 OK (router 側で判定)
790
+ if (aborted) {
791
+ return {
792
+ ok: false,
793
+ partial: true,
794
+ applied: 0,
795
+ failed: 1,
796
+ rolled_back: rolledBack.length,
797
+ rollback_failed: rollbackFailed.length,
798
+ skipped: skipSet.size,
799
+ history_file: path.basename(histFile),
800
+ aborted_at: aborted,
801
+ message: `apply aborted at key=${aborted.key}, ${rolledBack.length} keys rolled back`,
802
+ }
803
+ }
804
+ return {
805
+ ok: true,
806
+ applied: applied.length,
807
+ failed: 0,
808
+ skipped: skipSet.size,
809
+ history_file: path.basename(histFile),
810
+ }
811
+ }
812
+
813
+ // NF-2/9 helper: history dir create / write 失敗時、apply 済 yml を snapshot から復元
814
+ // LIFO 順 (apply 逆順) で hcSet を呼び元値に戻す
815
+ // 返却: { recovered: [key, ...], failed: [{ key, target, stderr }, ...] }
816
+ function rollbackAppliedFromSnapshot(applied, snapshot, overrides) {
817
+ const recovered = []
818
+ const failed = []
819
+ const reverseApplied = applied.slice().reverse()
820
+ for (const a of reverseApplied) {
821
+ const original = snapshot[a.key]
822
+ if (original === undefined) {
823
+ // snapshot 不在 = 復元不可 (発生条件理論上なし、防御的に記録)
824
+ failed.push({ key: a.key, target: '<unknown>', stderr: 'snapshot entry missing' })
825
+ continue
826
+ }
827
+ const r = hcSet(a.key, original, overrides)
828
+ if (r.exitCode === 0) {
829
+ recovered.push(a.key)
830
+ } else {
831
+ failed.push({ key: a.key, target: original, stderr: r.stderr })
832
+ }
833
+ }
834
+ return { recovered, failed }
835
+ }
836
+
837
+ function listHistory() {
838
+ if (!fs.existsSync(HISTORY_DIR)) return []
839
+ const files = fs.readdirSync(HISTORY_DIR).filter((f) => f.endsWith('.json')).sort().reverse()
840
+ return files.map((f) => {
841
+ try {
842
+ const content = JSON.parse(fs.readFileSync(path.join(HISTORY_DIR, f), 'utf8'))
843
+ return {
844
+ timestamp: f.replace(/\.json$/, ''),
845
+ preset: content.preset,
846
+ type: content.type || 'apply',
847
+ applied_at: content.applied_at,
848
+ applied_count: (content.applied || []).length,
849
+ failed_count: (content.failed || []).length,
850
+ rolled_back_count: (content.rolled_back || []).length,
851
+ }
852
+ } catch (e) {
853
+ return { timestamp: f.replace(/\.json$/, ''), error: String(e.message) }
854
+ }
855
+ })
856
+ }
857
+
858
+ // C-rollback-chain: rollback 実行時に rollback 前の現在値を snapshot として
859
+ // 新規 history entry に保存 (type: 'rollback')。これにより rollback chain で
860
+ // 何度でも戻れる構造を確保。
861
+ // C-traversal: timestamp は path.basename + regex 二重防御。
862
+ // NF-3: rollback 内 restore も LIFO 順 (applied 逆順) で実行 (依存 key 不整合最小化)
863
+ // NF-13: chain history stamp も pid + counter で unique 化
864
+ function rollbackHistory(timestamp, overrides) {
865
+ // C-traversal: path traversal 防止
866
+ if (!ROLLBACK_TS_REGEX.test(timestamp)) {
867
+ return { ok: false, error: 'invalid timestamp format' }
868
+ }
869
+ const safeTs = path.basename(timestamp)
870
+ if (safeTs !== timestamp || safeTs.length === 0) {
871
+ return { ok: false, error: 'invalid timestamp (basename mismatch)' }
872
+ }
873
+
874
+ const histFile = path.join(HISTORY_DIR, `${safeTs}.json`)
875
+ // 念のため normalize して HISTORY_DIR 内に閉じ込め確認
876
+ const normalized = path.normalize(histFile)
877
+ if (!normalized.startsWith(HISTORY_DIR + path.sep)) {
878
+ return { ok: false, error: 'path traversal detected' }
879
+ }
880
+ if (!fs.existsSync(normalized)) return { ok: false, error: 'history not found' }
881
+
882
+ let payload
883
+ try {
884
+ payload = JSON.parse(fs.readFileSync(normalized, 'utf8'))
885
+ } catch (e) {
886
+ return { ok: false, error: 'history parse failed: ' + e.message }
887
+ }
888
+
889
+ // rollback 前の現在値を snapshot (chain 修復用)
890
+ const appliedTargets = payload.applied || []
891
+ const rollbackPreSnapshot = {}
892
+ for (const change of appliedTargets) {
893
+ const cur = hcGet(change.key, overrides)
894
+ rollbackPreSnapshot[change.key] = cur === null ? '<unknown>' : cur
895
+ }
896
+
897
+ // NF-3: LIFO 順 (apply 逆順) で restore
898
+ const targets = appliedTargets.slice().reverse()
899
+ const restored = []
900
+ const failed = []
901
+ for (const change of targets) {
902
+ const r = hcSet(change.key, change.from, overrides)
903
+ if (r.exitCode === 0) restored.push({ key: change.key, restored_to: change.from })
904
+ else failed.push({ key: change.key, target: change.from, stderr: r.stderr })
905
+ }
906
+
907
+ // chain 修復: rollback 自体を新規 history entry として記録
908
+ try {
909
+ if (!fs.existsSync(HISTORY_DIR)) {
910
+ fs.mkdirSync(HISTORY_DIR, { recursive: true })
911
+ }
912
+ // NF-13: chain stamp も pid + counter で unique 化
913
+ const stamp = nextHistoryStamp()
914
+ const chainFile = path.join(HISTORY_DIR, `${stamp}-rollback-of-${safeTs}.json`)
915
+ const chainPayload = {
916
+ preset: payload.preset || '<unknown>',
917
+ applied_at: new Date().toISOString(),
918
+ type: 'rollback',
919
+ rolled_back_from: safeTs,
920
+ snapshot: rollbackPreSnapshot, // rollback 前の値 (= chain 用)
921
+ // applied フィールドは「rollback chain で更に戻る場合の target」となるよう
922
+ // 「rollback 前値 → rollback 後値」を逆向きで記録 (rollback の rollback で
923
+ // 元 apply 直後状態に復帰可能)
924
+ applied: Object.entries(rollbackPreSnapshot).map(([k, v]) => {
925
+ // rollback で k は change.from (元の preset 適用前値) に戻った
926
+ const original = appliedTargets.find((t) => t.key === k)
927
+ return {
928
+ key: k,
929
+ from: v, // rollback 直前 = preset apply 後の状態
930
+ to: original ? original.from : v, // rollback 直後 = preset apply 前の状態
931
+ }
932
+ }),
933
+ failed,
934
+ restored,
935
+ }
936
+ const tmpFile = `${chainFile}.tmp`
937
+ fs.writeFileSync(tmpFile, JSON.stringify(chainPayload, null, 2))
938
+ fs.renameSync(tmpFile, chainFile)
939
+ // NF-6: chain write 後も cleanup
940
+ cleanupHistoryFiles()
941
+ return {
942
+ ok: failed.length === 0,
943
+ restored: restored.length,
944
+ failed: failed.length,
945
+ failures: failed,
946
+ chain_history_file: path.basename(chainFile),
947
+ }
948
+ } catch (e) {
949
+ // chain 書込失敗でも restore 結果は返す (degraded)
950
+ return {
951
+ ok: failed.length === 0,
952
+ restored: restored.length,
953
+ failed: failed.length,
954
+ failures: failed,
955
+ chain_history_error: String(e.message),
956
+ }
957
+ }
958
+ }
959
+
960
+ // ============================================================
961
+ // task-63 Step 4 案 C: getCurrentPreset (values field 完全一致 subset 判定)
962
+ // ============================================================
963
+ //
964
+ // 仕様 (task-63 Step 4 spec、user 確定要求「プリセット名保存不要」2026-05-29):
965
+ // 1. 現在 yml の全 key 値を hcListAll() で取得 (1 spawn、cache hit で 0 spawn)
966
+ // 2. PRESETS (built-in 10 件) の各 preset values と current yml を subset 一致判定
967
+ // - 各 preset の values 全 entry について current yml と一致するかを確認
968
+ // - preset values は yml の部分集合 (subset)、yml の他 key は無視
969
+ // 3. 完全一致 (subset): { name: <preset key>, display_name_ja: <日本語名>, match_type: 'preset' }
970
+ // 4. 不一致: { name: 'custom', display_name_ja: '未保存変更あり', match_type: 'custom' }
971
+ // (task-76 Step 2: match_type 'unsaved' → 'custom' に整理。2 分割 UI の「★ カスタム」表示と語彙統一)
972
+ //
973
+ // task-65 (案 A): axes field を additive に復活。matched preset の axes メタデータ (6 軸) を返す。
974
+ // 既存 field (name / display_name_ja / match_type) は不変 (後方互換)。
975
+ // preset 一致時: axes = matched preset の axes object (6 key)。
976
+ // custom 時: axes = null (preset 外では axis 値が一意でないため。UI 側でカスタム表示に切替)。
977
+ // (task-63 Step 4 A3 で撤去した axes 返却を、6 軸 read-only 表示の data contract gap 解消のため復活)
978
+ //
979
+ // overrides.spawnFn: DI seam (テスト用)
980
+ function getCurrentPreset(overrides) {
981
+ overrides = overrides || {}
982
+ // 1. 現在 yml 全 key 値を取得 (hcListAll 経由、cache hit で 0 spawn)
983
+ const allValues = hcListAll(overrides)
984
+
985
+ // 2. PRESETS (built-in only、custom-* は廃止) を順次照合
986
+ for (const [key, p] of Object.entries(PRESETS)) {
987
+ if (valuesSubsetEqual(p.values, allValues)) {
988
+ return {
989
+ name: key,
990
+ display_name_ja: p.display_name_ja || key,
991
+ match_type: 'preset',
992
+ // task-65 (iter2 M1): 空 axes object が all-`—` table に退化する原 bug 再発を防ぐため、
993
+ // axes が空 / 不在なら null を返す (UI 側でカスタム表示に切替、6 軸 table を描かない)。
994
+ axes: (p.axes && Object.keys(p.axes).length) ? p.axes : null,
995
+ }
996
+ }
997
+ }
998
+
999
+ // 3. 一致なし (custom): axes は preset 外で一意でない → null (UI 側でカスタム表示)
1000
+ // task-76 Step 2: match_type を 'unsaved' → 'custom' に整理 (2 分割 UI で「★ カスタム」表示と語彙統一)。
1001
+ // 内部 client (app.js) のみが参照するため後方互換上の影響なし。
1002
+ return { name: 'custom', display_name_ja: '未保存変更あり', match_type: 'custom', axes: null }
1003
+ }
1004
+
1005
+ // task-63 Step 4 A2: values subset 完全一致判定
1006
+ // presetValues の全 key について currentValues に同 key + 同 value が存在するかを check。
1007
+ // currentValues に preset 不在 key があっても OK (subset 関係のみ確認)。
1008
+ // string 比較、yml 形式差異吸収のため trim。
1009
+ function valuesSubsetEqual(presetValues, currentValues) {
1010
+ if (!presetValues || typeof presetValues !== 'object') return false
1011
+ if (!currentValues || typeof currentValues !== 'object') return false
1012
+ for (const [k, pv] of Object.entries(presetValues)) {
1013
+ if (!Object.prototype.hasOwnProperty.call(currentValues, k)) return false
1014
+ const cv = currentValues[k]
1015
+ if (cv === undefined || cv === null) return false
1016
+ if (String(pv).trim() !== String(cv).trim()) return false
1017
+ }
1018
+ return true
1019
+ }
1020
+
1021
+ // ============================================================
1022
+ // HTTP helpers
1023
+ // ============================================================
1024
+
1025
+ // L-2: Buffer.from で生成し Content-Length は buffer.length を使う
1026
+ function sendJson(res, statusCode, payload) {
1027
+ const buf = Buffer.from(JSON.stringify(payload), 'utf8')
1028
+ res.writeHead(statusCode, {
1029
+ 'Content-Type': 'application/json; charset=utf-8',
1030
+ 'Content-Length': buf.length,
1031
+ })
1032
+ res.end(buf)
1033
+ }
1034
+
1035
+ function sendText(res, statusCode, text, contentType) {
1036
+ const buf = Buffer.from(text, 'utf8')
1037
+ res.writeHead(statusCode, {
1038
+ 'Content-Type': contentType || 'text/plain; charset=utf-8',
1039
+ 'Content-Length': buf.length,
1040
+ })
1041
+ res.end(buf)
1042
+ }
1043
+
1044
+ // H-3: destroyed flag で reject 二重呼び出し防止
1045
+ function readJsonBody(req) {
1046
+ return new Promise((resolve, reject) => {
1047
+ const chunks = []
1048
+ let total = 0
1049
+ let settled = false
1050
+ const safeReject = (err) => {
1051
+ if (settled) return
1052
+ settled = true
1053
+ reject(err)
1054
+ }
1055
+ const safeResolve = (val) => {
1056
+ if (settled) return
1057
+ settled = true
1058
+ resolve(val)
1059
+ }
1060
+ req.on('data', (c) => {
1061
+ if (settled) return
1062
+ chunks.push(c)
1063
+ total += c.length
1064
+ if (total > 1024 * 1024) {
1065
+ safeReject(new Error('body too large'))
1066
+ try { req.destroy() } catch (_) { /* ignore */ }
1067
+ }
1068
+ })
1069
+ req.on('end', () => {
1070
+ if (settled) return
1071
+ try {
1072
+ const body = Buffer.concat(chunks).toString('utf8')
1073
+ safeResolve(body.length === 0 ? {} : JSON.parse(body))
1074
+ } catch (e) {
1075
+ safeReject(e)
1076
+ }
1077
+ })
1078
+ req.on('error', safeReject)
1079
+ req.on('close', () => {
1080
+ if (!settled) safeReject(new Error('connection closed'))
1081
+ })
1082
+ })
1083
+ }
1084
+
1085
+ function serveStatic(req, res, relativePath) {
1086
+ // path traversal 防止
1087
+ const safe = path.normalize(relativePath).replace(/^(\.\.[\\/])+/, '')
1088
+ const full = path.join(STATIC_DIR, safe)
1089
+ if (!full.startsWith(STATIC_DIR)) {
1090
+ sendText(res, 403, 'forbidden')
1091
+ return
1092
+ }
1093
+ if (!fs.existsSync(full) || !fs.statSync(full).isFile()) {
1094
+ sendText(res, 404, 'not found')
1095
+ return
1096
+ }
1097
+ const ext = path.extname(full).toLowerCase()
1098
+ const mime = MIME_TYPES[ext] || 'application/octet-stream'
1099
+ const content = fs.readFileSync(full)
1100
+ res.writeHead(200, { 'Content-Type': mime, 'Content-Length': content.length })
1101
+ res.end(content)
1102
+ }
1103
+
1104
+ // NF-8 helper: error message から internal abs path leak を除外
1105
+ // REPO_ROOT を <repo> に置換 + HOME を <home> に置換
1106
+ function sanitizeErrorMessage(message) {
1107
+ if (!message) return ''
1108
+ let s = String(message)
1109
+ // REPO_ROOT 置換 (最も特異)
1110
+ if (REPO_ROOT && REPO_ROOT.length > 0) {
1111
+ s = s.split(REPO_ROOT).join('<repo>')
1112
+ }
1113
+ const home = os.homedir()
1114
+ if (home && home.length > 0) {
1115
+ s = s.split(home).join('<home>')
1116
+ }
1117
+ return s
1118
+ }
1119
+
1120
+ // ============================================================
1121
+ // router (L-3: url.parse → new URL)
1122
+ // ============================================================
1123
+
1124
+ async function handleRequest(req, res) {
1125
+ // L-3: deprecated url.parse → WHATWG URL
1126
+ const reqUrl = new URL(req.url, `http://${HOST}`)
1127
+ const pathname = reqUrl.pathname || '/'
1128
+
1129
+ // GET / → redirect
1130
+ if (req.method === 'GET' && pathname === '/') {
1131
+ res.writeHead(302, { Location: '/static/index.html' })
1132
+ res.end()
1133
+ return
1134
+ }
1135
+
1136
+ // GET /static/*
1137
+ if (req.method === 'GET' && pathname.startsWith('/static/')) {
1138
+ serveStatic(req, res, pathname.slice('/static/'.length))
1139
+ return
1140
+ }
1141
+
1142
+ // GET /api/current-preset (task-63 Step 4 案 C、values subset 完全一致判定)
1143
+ // 現在 yml 全 key 値 vs PRESETS (built-in 10) values を subset 完全一致判定し、
1144
+ // { name, display_name_ja, match_type, axes } を返却。
1145
+ // task-65 (案 A) で axes を additive に復活: matched preset の 6 軸メタデータを返す
1146
+ // (top view の 6 軸 read-only table 描画用)。custom 時は axes=null。
1147
+ // - 一致: name=<preset key> + display_name_ja=preset 日本語名 + match_type='preset' + axes=preset の 6 軸 object
1148
+ // - 不一致: name='custom' + display_name_ja='未保存変更あり' + match_type='custom' + axes=null (task-76 Step 2)
1149
+ if (req.method === 'GET' && pathname === '/api/current-preset') {
1150
+ const result = getCurrentPreset()
1151
+ sendJson(res, 200, result)
1152
+ return
1153
+ }
1154
+
1155
+ // GET /api/categories
1156
+ if (req.method === 'GET' && pathname === '/api/categories') {
1157
+ const metadata = loadMetadata()
1158
+ const counts = {}
1159
+ for (const m of metadata) {
1160
+ counts[m.category] = (counts[m.category] || 0) + 1
1161
+ }
1162
+ // LOW fix 1 (case-03): category name が空文字列 / 'undefined' の場合は表示上の不整合を防ぐため
1163
+ // name field として返す (client 側で fallback 表示する)。filter はしない (key 件数は正確に保つ)。
1164
+ const categories = Object.entries(counts).map(([name, count]) => ({ name: name || '', key_count: count }))
1165
+ sendJson(res, 200, { categories })
1166
+ return
1167
+ }
1168
+
1169
+ // GET /api/keys
1170
+ // task-69 Step 3 (key parity fix): key 集合の SSoT を metadata から yml top-level key に変更。
1171
+ // 旧実装は loadMetadata() (metadata entry 数) を基準に enrich していたため、metadata 未登録の yml key
1172
+ // (feature_reviewer_count_guard_enabled / feature_stale_harness_detect_enabled /
1173
+ // harness_version / stale_harness_markers) が response から欠落していた。
1174
+ // 新実装は listYamlKeys() (yml top-level key) を基準に Object.entries 相当の loop を回し、
1175
+ // metadataMap で left join (metadata 不在 key は category/description/effect を空文字で返す)。
1176
+ // NEW-C-1 (維持): hcListAll() で全 key 値を 1 spawnSync で取得 (74 → 1)、cache hit で spawn 0 件。
1177
+ if (req.method === 'GET' && pathname === '/api/keys') {
1178
+ const metadata = loadMetadata()
1179
+ // metadata を key → entry の map に変換 (left join 用、表示補助情報)
1180
+ const metadataMap = Object.create(null)
1181
+ for (const m of metadata) {
1182
+ if (m.key) metadataMap[m.key] = m
1183
+ }
1184
+ const allValues = hcListAll()
1185
+ const categoryFilter = reqUrl.searchParams.get('category')
1186
+ // key 集合の SSoT = yml top-level key (metadata 登録有無に依存しない)
1187
+ const ymlKeys = listYamlKeys()
1188
+ const enriched = []
1189
+ for (const key of ymlKeys) {
1190
+ const meta = metadataMap[key]
1191
+ const category = meta ? meta.category || '' : ''
1192
+ // category filter は yml 基準 key の (metadata 由来) category に対して適用
1193
+ if (categoryFilter && category !== categoryFilter) continue
1194
+ const value = Object.prototype.hasOwnProperty.call(allValues, key) ? allValues[key] : null
1195
+ enriched.push({
1196
+ key,
1197
+ category,
1198
+ description: meta ? meta.description || '' : '',
1199
+ effect: meta ? meta.effect || '' : '',
1200
+ // task-78 Step 1: 短い日本語ラベル (sidebar 表示用、metadata 不在 key は空)
1201
+ label_ja: meta ? meta.label_ja || '' : '',
1202
+ current_value: value,
1203
+ })
1204
+ }
1205
+ sendJson(res, 200, { keys: enriched, total: enriched.length })
1206
+ return
1207
+ }
1208
+
1209
+ // GET /api/value/:key (個別 key、single hcGet 維持)
1210
+ // iter 6 A item 4 (MED): DI seam を明示 (integration test で spawnFn 差替可能化)
1211
+ if (req.method === 'GET' && pathname.startsWith('/api/value/')) {
1212
+ const key = decodeURIComponent(pathname.slice('/api/value/'.length))
1213
+ const current = hcGet(key, {})
1214
+ if (current === null) {
1215
+ sendJson(res, 404, { error: 'key not found or get failed', key })
1216
+ return
1217
+ }
1218
+ sendJson(res, 200, { key, current_value: current })
1219
+ return
1220
+ }
1221
+
1222
+ // POST /api/set (H-11: known key whitelist + cache invalidate)
1223
+ if (req.method === 'POST' && pathname === '/api/set') {
1224
+ let body
1225
+ try {
1226
+ body = await readJsonBody(req)
1227
+ } catch (e) {
1228
+ sendJson(res, 400, { error: 'invalid JSON body', detail: sanitizeErrorMessage(e.message) })
1229
+ return
1230
+ }
1231
+ if (!body.key || body.value === undefined) {
1232
+ sendJson(res, 400, { error: 'key and value required' })
1233
+ return
1234
+ }
1235
+ // H-11: whitelist 検証
1236
+ const known = getKnownKeys()
1237
+ if (!known.has(body.key)) {
1238
+ sendJson(res, 400, { error: 'unknown key', key: body.key })
1239
+ return
1240
+ }
1241
+ // iter 6 A item 4 (MED): DI seam を明示 (integration test で spawnFn 差替可能化)
1242
+ const r = hcSet(body.key, String(body.value), {})
1243
+ if (r.exitCode !== 0) {
1244
+ // NF-10: timeout 明示
1245
+ const errPayload = {
1246
+ error: 'hc-config --set failed',
1247
+ stderr: sanitizeErrorMessage(r.stderr),
1248
+ exit_code: r.exitCode,
1249
+ }
1250
+ if (r.timedOut) errPayload.timed_out = true
1251
+ sendJson(res, 400, errPayload)
1252
+ return
1253
+ }
1254
+ // H-1: /api/set 成功で metadata cache invalidate
1255
+ // (metadata 自体は変わらないが、enriched current_value が古くなるため
1256
+ // /api/keys 経由 path でも fresh load させる)
1257
+ // NEW-C-1: keysValueCache も同期 invalidate (invalidateMetadataCache 経由)
1258
+ invalidateMetadataCache()
1259
+ sendJson(res, 200, { ok: true, key: body.key, value: body.value })
1260
+ return
1261
+ }
1262
+
1263
+ // GET /api/presets (task-63 Step 4 A3: axes_values 撤去、display_name_ja + use_case + affected_key_count のみ)
1264
+ // custom preset 機能は task-63 Step 4 A1 で撤去済 (scanCustomPresets 呼出なし、PRESETS は built-in 10 件固定)
1265
+ if (req.method === 'GET' && pathname === '/api/presets') {
1266
+ const list = Object.entries(PRESETS).map(([name, p]) => ({
1267
+ name,
1268
+ display_name_ja: p.display_name_ja || name,
1269
+ use_case: p.use_case,
1270
+ affected_key_count: Object.keys(p.values).length,
1271
+ // task-76 Step 2: 左ペイン group 分類 (quality_level → 'POC'|'社内ツール'|'本番運用'|'その他')
1272
+ group: resolvePresetGroup(p),
1273
+ }))
1274
+ sendJson(res, 200, { presets: list, axes_schema: PRESET_AXES })
1275
+ return
1276
+ }
1277
+
1278
+ // GET /api/preset/:name/diff (task-63 Step 4 A1: custom preset merge ロジック撤去、built-in 10 件のみ)
1279
+ const diffMatch = pathname.match(/^\/api\/preset\/([^/]+)\/diff$/)
1280
+ if (req.method === 'GET' && diffMatch) {
1281
+ const name = decodeURIComponent(diffMatch[1])
1282
+ const diff = computePresetDiff(name)
1283
+ if (!diff) {
1284
+ sendJson(res, 404, { error: 'unknown preset', preset: name })
1285
+ return
1286
+ }
1287
+ sendJson(res, 200, diff)
1288
+ return
1289
+ }
1290
+
1291
+ // POST /api/preset/:name/apply (H-7: partial failure は 200 OK + body.ok:false)
1292
+ // task-63 Step 4 A1: custom preset merge ロジック撤去 (built-in 10 件のみ)
1293
+ const applyMatch = pathname.match(/^\/api\/preset\/([^/]+)\/apply$/)
1294
+ if (req.method === 'POST' && applyMatch) {
1295
+ const name = decodeURIComponent(applyMatch[1])
1296
+ let body
1297
+ try {
1298
+ body = await readJsonBody(req)
1299
+ } catch (e) {
1300
+ sendJson(res, 400, { error: 'invalid JSON body', detail: sanitizeErrorMessage(e.message) })
1301
+ return
1302
+ }
1303
+ const skipKeys = Array.isArray(body.skip_keys) ? body.skip_keys : []
1304
+ const result = applyPreset(name, skipKeys)
1305
+ if (result.error === 'unknown preset') {
1306
+ sendJson(res, 404, result)
1307
+ return
1308
+ }
1309
+ // H-7: result.ok=false でも部分失敗は 200 (body で client が判定)
1310
+ // 完全失敗 (preset 不存在 / history dir エラー等の致命的) のみ 5xx
1311
+ if (!result.ok && !result.partial && result.error) {
1312
+ // NF-8: history_write_failed は client に正常に伝えるため 500 ではなく 200 で返却
1313
+ // (yml は rollback 済、UI 側で「history 不在で復旧不可」を表示できる)
1314
+ if (result.error === 'history_write_failed') {
1315
+ // cache invalidate (yml が rollback されたが念のため)
1316
+ invalidateMetadataCache()
1317
+ sendJson(res, 200, result)
1318
+ return
1319
+ }
1320
+ sendJson(res, 500, result)
1321
+ return
1322
+ }
1323
+ // H-1: apply 成功で cache invalidate
1324
+ if (result.ok || result.partial) {
1325
+ invalidateMetadataCache()
1326
+ }
1327
+ sendJson(res, 200, result)
1328
+ return
1329
+ }
1330
+
1331
+ // task-63 Step 4 A1: POST /api/preset/save endpoint 撤去 (user 確定要求「プリセット名保存不要」2026-05-29)
1332
+ // 404 fallback で応答 (本 endpoint への request は handleRequest 末尾で 404 not found を返す)
1333
+
1334
+ // GET /api/preset/history
1335
+ if (req.method === 'GET' && pathname === '/api/preset/history') {
1336
+ sendJson(res, 200, { history: listHistory() })
1337
+ return
1338
+ }
1339
+
1340
+ // POST /api/preset/rollback/:timestamp (C-traversal + C-rollback-chain)
1341
+ const rollbackMatch = pathname.match(/^\/api\/preset\/rollback\/(.+)$/)
1342
+ if (req.method === 'POST' && rollbackMatch) {
1343
+ const ts = decodeURIComponent(rollbackMatch[1])
1344
+ // C-traversal: 早期 validation (rollbackHistory 内部でも二重検証)
1345
+ if (!ROLLBACK_TS_REGEX.test(ts)) {
1346
+ sendJson(res, 400, { error: 'invalid timestamp format' })
1347
+ return
1348
+ }
1349
+ const result = rollbackHistory(ts)
1350
+ if (result.error === 'history not found') {
1351
+ sendJson(res, 404, result)
1352
+ return
1353
+ }
1354
+ if (result.error === 'invalid timestamp format' || result.error === 'invalid timestamp (basename mismatch)' || result.error === 'path traversal detected') {
1355
+ sendJson(res, 400, result)
1356
+ return
1357
+ }
1358
+ if (!result.ok && result.error) {
1359
+ sendJson(res, 500, result)
1360
+ return
1361
+ }
1362
+ // chain 書込成功時に cache invalidate (rollback で yml 値変更されたため)
1363
+ invalidateMetadataCache()
1364
+ sendJson(res, 200, result)
1365
+ return
1366
+ }
1367
+
1368
+ // 404 fallback
1369
+ sendJson(res, 404, { error: 'not found', path: pathname, method: req.method })
1370
+ }
1371
+
1372
+ // ============================================================
1373
+ // port 探索 + listen (H-4: error event 監視で TOCTOU 排除)
1374
+ // ============================================================
1375
+
1376
+ function tryListen(server, port) {
1377
+ return new Promise((resolve, reject) => {
1378
+ let settled = false
1379
+ const onError = (err) => {
1380
+ if (settled) return
1381
+ settled = true
1382
+ server.removeListener('listening', onListen)
1383
+ reject(err)
1384
+ }
1385
+ const onListen = () => {
1386
+ if (settled) return
1387
+ settled = true
1388
+ server.removeListener('error', onError)
1389
+ resolve(port)
1390
+ }
1391
+ server.once('error', onError)
1392
+ server.once('listening', onListen)
1393
+ server.listen(port, HOST)
1394
+ })
1395
+ }
1396
+
1397
+ async function findPortAndListen(server) {
1398
+ for (let port = PORT_MIN; port <= PORT_MAX; port++) {
1399
+ try {
1400
+ await tryListen(server, port)
1401
+ return port
1402
+ } catch (err) {
1403
+ if (err && (err.code === 'EADDRINUSE' || err.code === 'EACCES')) continue
1404
+ throw err
1405
+ }
1406
+ }
1407
+ throw new Error(`no free port in ${PORT_MIN}-${PORT_MAX}`)
1408
+ }
1409
+
1410
+ // ============================================================
1411
+ // browser auto-open
1412
+ // ============================================================
1413
+
1414
+ function openBrowser(targetUrl) {
1415
+ const platform = os.platform()
1416
+ let cmd, args
1417
+ if (platform === 'darwin') { cmd = 'open'; args = [targetUrl] }
1418
+ else if (platform === 'linux') { cmd = 'xdg-open'; args = [targetUrl] }
1419
+ else if (platform === 'win32') { cmd = 'cmd'; args = ['/c', 'start', '', targetUrl] }
1420
+ else {
1421
+ console.log(`Open this URL manually: ${targetUrl}`)
1422
+ return
1423
+ }
1424
+ const r = spawnSync(cmd, args, { timeout: 3000, stdio: 'ignore' })
1425
+ if (r.status !== 0 || r.error) {
1426
+ console.log(`Browser auto-open failed. Open this URL manually: ${targetUrl}`)
1427
+ }
1428
+ }
1429
+
1430
+ // ============================================================
1431
+ // main
1432
+ // ============================================================
1433
+
1434
+ async function main() {
1435
+ // M-P2: 起動時 REPO_ROOT 解決を stderr に明示出力
1436
+ console.error(`[hc-config-web-server] REPO_ROOT=${REPO_ROOT}`)
1437
+ console.error(`[hc-config-web-server] HC_CONFIG_SCRIPT=${HC_CONFIG_SCRIPT}`)
1438
+ if (!fs.existsSync(HC_CONFIG_SCRIPT)) {
1439
+ console.error(`[hc-config-web-server] WARN: HC_CONFIG_SCRIPT not found at resolved path`)
1440
+ }
1441
+
1442
+ const server = http.createServer((req, res) => {
1443
+ handleRequest(req, res).catch((err) => {
1444
+ // NF-8: internal abs path leak 防止 (sanitize + stack trace 除外)
1445
+ console.error('handler error:', err && err.stack ? err.stack : err)
1446
+ if (!res.headersSent) {
1447
+ sendJson(res, 500, {
1448
+ error: 'internal error',
1449
+ detail: sanitizeErrorMessage(err && err.message ? err.message : String(err)),
1450
+ })
1451
+ }
1452
+ })
1453
+ })
1454
+
1455
+ let port
1456
+ try {
1457
+ port = await findPortAndListen(server)
1458
+ } catch (err) {
1459
+ console.error(`Failed to start server: ${err.message}`)
1460
+ process.exit(1)
1461
+ }
1462
+
1463
+ const fullUrl = `http://${HOST}:${port}/`
1464
+ console.log(`hc-config Web UI server started on ${fullUrl}`)
1465
+ console.log(` Press Ctrl+C to stop.`)
1466
+
1467
+ // CLI flag で auto-open 無効化
1468
+ const noOpen = process.argv.includes('--no-open') || process.env.HC_WEB_NO_OPEN === '1'
1469
+ if (!noOpen) {
1470
+ openBrowser(fullUrl)
1471
+ }
1472
+
1473
+ const shutdown = (signal) => {
1474
+ console.log(`\nReceived ${signal}, shutting down.`)
1475
+ server.close(() => {
1476
+ console.log('bye.')
1477
+ process.exit(0)
1478
+ })
1479
+ // 強制終了 fallback
1480
+ setTimeout(() => process.exit(0), 2000).unref()
1481
+ }
1482
+ process.on('SIGINT', () => shutdown('SIGINT'))
1483
+ process.on('SIGTERM', () => shutdown('SIGTERM'))
1484
+ }
1485
+
1486
+ if (require.main === module) {
1487
+ main().catch((err) => {
1488
+ console.error('fatal:', err)
1489
+ process.exit(1)
1490
+ })
1491
+ }
1492
+
1493
+ // C-unit-seam: unit テスト用 seam (callHcConfig / loadMetadata / readJsonBody / serveStatic 追加)
1494
+ // iter 4 A 追加 seam: hcListAll / invalidateKeysValueCache / nextHistoryStamp / cleanupHistoryFiles /
1495
+ // rollbackAppliedFromSnapshot / sanitizeErrorMessage
1496
+ // task-63 Step 4 A4: savePreset / scanCustomPresets / isTestPollutionName / axesEqual 撤去、
1497
+ // getCurrentPreset / valuesSubsetEqual を保持 (axesEqual → valuesSubsetEqual に rename)
1498
+ module.exports = {
1499
+ PRESETS,
1500
+ PRESET_AXES,
1501
+ resolvePresetGroup, // task-76 Step 2 (group 写像 test seam)
1502
+ ROLLBACK_TS_REGEX,
1503
+ HISTORY_MAX_FILES,
1504
+ callHcConfig,
1505
+ hcGet,
1506
+ hcSet,
1507
+ hcListAll,
1508
+ invalidateKeysValueCache,
1509
+ loadMetadata,
1510
+ invalidateMetadataCache,
1511
+ getKnownKeys,
1512
+ getEffectMap,
1513
+ computePresetDiff,
1514
+ applyPreset,
1515
+ rollbackAppliedFromSnapshot,
1516
+ getCurrentPreset, // task-63 Step 4 案 C
1517
+ valuesSubsetEqual, // task-63 Step 4 案 C (helper、test seam)
1518
+ listHistory,
1519
+ rollbackHistory,
1520
+ readJsonBody,
1521
+ serveStatic,
1522
+ sendJson,
1523
+ sendText,
1524
+ sanitizeErrorMessage,
1525
+ nextHistoryStamp,
1526
+ cleanupHistoryFiles,
1527
+ handleRequest,
1528
+ }