claude-code-conductor 2.67.0__tar.gz → 2.69.0__tar.gz

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 (269) hide show
  1. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/breaking-changes.txt +1 -0
  2. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/_hook_utils.py +22 -0
  3. claude_code_conductor-2.69.0/.claude/hooks/report_contract_check.py +212 -0
  4. claude_code_conductor-2.69.0/.claude/hooks/report_path_guard.py +283 -0
  5. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/tier_autoapply.py +101 -37
  6. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/settings.json +5 -0
  7. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/ARCHITECTURE.md +2 -1
  8. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/CHANGELOG.md +68 -0
  9. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/PKG-INFO +1 -1
  10. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/__init__.py +1 -1
  11. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_hook_utils.py +96 -0
  12. claude_code_conductor-2.69.0/tests/hooks/test_report_path_guard.py +1126 -0
  13. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_tier_autoapply.py +648 -37
  14. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_tier_security_fixes.py +5 -0
  15. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_report_contract_check.py +390 -10
  16. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_settings_hooks_registration.py +91 -0
  17. claude_code_conductor-2.67.0/.claude/hooks/report_contract_check.py +0 -131
  18. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/.gitignore +0 -0
  19. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/CLAUDE.md +0 -0
  20. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/agents/architect.md +0 -0
  21. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/agents/code-reviewer.md +0 -0
  22. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/agents/design-critic.md +0 -0
  23. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/agents/developer.md +0 -0
  24. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/agents/doc-writer.md +0 -0
  25. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/agents/interviewer.md +0 -0
  26. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/agents/planner.md +0 -0
  27. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/agents/project-setup.md +0 -0
  28. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/agents/security-reviewer.md +0 -0
  29. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/agents/systematic-debugger.md +0 -0
  30. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/agents/tester.md +0 -0
  31. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/agents/wt_developer.md +0 -0
  32. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/agents/wt_systematic-debugger.md +0 -0
  33. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/agents/wt_tester.md +0 -0
  34. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/deletions.txt +0 -0
  35. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/docs/autonomous-mode-onboarding.md +0 -0
  36. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/docs/config-policy.md +0 -0
  37. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/docs/nul-boundary.md +0 -0
  38. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/docs/parallel-agents-setup.md +0 -0
  39. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/docs/platform-adapters.md +0 -0
  40. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/docs/settings.json.md +0 -0
  41. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/check_agent_invocation.py +0 -0
  42. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/consolidate_memory.py +0 -0
  43. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/patterns_guard.py +0 -0
  44. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/permission_handler.py +0 -0
  45. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/permission_handler_toast.py +0 -0
  46. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/planner_check.py +0 -0
  47. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/post_tool.py +0 -0
  48. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/pre_compact.py +0 -0
  49. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/pre_tool.py +0 -0
  50. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/recall_autorebuild.py +0 -0
  51. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/recall_inject.py +0 -0
  52. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/restore_session.py +0 -0
  53. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/select_tier.py +0 -0
  54. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/session_mode_watch.py +0 -0
  55. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/session_start.py +0 -0
  56. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/session_stop.py +0 -0
  57. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/session_utils.py +0 -0
  58. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/statusline.py +0 -0
  59. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/stop.py +0 -0
  60. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/tier_gap_check.py +0 -0
  61. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/hooks/worktree_guard.py +0 -0
  62. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/memory/.gitkeep +0 -0
  63. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/permission_rules.json +0 -0
  64. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/rules/promoted/index.md +0 -0
  65. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/autonomous-mode/SKILL.md +0 -0
  66. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/autonomous-mode/scripts/mode_line.py +0 -0
  67. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/brainstorm/SKILL.md +0 -0
  68. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/codex-review/SKILL.md +0 -0
  69. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
  70. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/dev-workflow/references/c3-omission-declaration.md +0 -0
  71. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
  72. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/dev-workflow/references/design-critic-rubric.md +0 -0
  73. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/dev-workflow/references/design-rubric.md +0 -0
  74. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/dev-workflow/references/interview-rubric.md +0 -0
  75. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
  76. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/dev-workflow/references/reachability-map.md +0 -0
  77. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/dev-workflow/references/record-protocol.md +0 -0
  78. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
  79. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/dev-workflow/scripts/detect_execution_verification.py +0 -0
  80. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/dev-workflow/scripts/record_agent_outcome.py +0 -0
  81. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
  82. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
  83. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/develop/SKILL.md +0 -0
  84. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/doc/SKILL.md +0 -0
  85. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/extract-lib/SKILL.md +0 -0
  86. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/init-session/SKILL.md +0 -0
  87. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/init-session/scripts/session_guard.py +0 -0
  88. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/mcp-config/SKILL.md +0 -0
  89. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
  90. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/pattern-status/SKILL.md +0 -0
  91. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
  92. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/recall/SKILL.md +0 -0
  93. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
  94. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
  95. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/review-phase/SKILL.md +0 -0
  96. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/setup/SKILL.md +0 -0
  97. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/setup/reference.md +0 -0
  98. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
  99. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
  100. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/start/SKILL.md +0 -0
  101. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/skills/start/scripts/archive_reports.py +0 -0
  102. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.claude/state/.gitkeep +0 -0
  103. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/.gitignore +0 -0
  104. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/LICENSE +0 -0
  105. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/LICENSES/fastembed-LICENSE +0 -0
  106. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/LICENSES/fastembed-NOTICE +0 -0
  107. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/LICENSES/numpy-LICENSE +0 -0
  108. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/LICENSES/onnxruntime-LICENSE +0 -0
  109. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
  110. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/README.md +0 -0
  111. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/hatch_build.py +0 -0
  112. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/pyproject.toml +0 -0
  113. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/__main__.py +0 -0
  114. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/_db_params.py +0 -0
  115. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/_excludes.py +0 -0
  116. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/_terminal.py +0 -0
  117. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/adapters.py +0 -0
  118. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/cli.py +0 -0
  119. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/cli_ask.py +0 -0
  120. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/cli_doctor.py +0 -0
  121. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/cli_init.py +0 -0
  122. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/cli_list.py +0 -0
  123. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/cli_metrics.py +0 -0
  124. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/cli_plan.py +0 -0
  125. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/cli_recall.py +0 -0
  126. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/cli_run.py +0 -0
  127. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/cli_tier.py +0 -0
  128. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/cli_update.py +0 -0
  129. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/db.py +0 -0
  130. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/embedding.py +0 -0
  131. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/gitutil.py +0 -0
  132. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/mcp_server.py +0 -0
  133. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/migrate.py +0 -0
  134. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/migrations/001_initial.sql +0 -0
  135. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
  136. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/migrations/003_tier_cost.sql +0 -0
  137. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/migrations/004_agent_outcomes.sql +0 -0
  138. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/migrations/005_drop_agent_tier_bandit.sql +0 -0
  139. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/migrations/006_review_decisions_severity.sql +0 -0
  140. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/migrations/007_review_decisions_resolution.sql +0 -0
  141. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/migrations/008_drop_agent_runs.sql +0 -0
  142. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/migrations/README.md +0 -0
  143. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/migrations/__init__.py +0 -0
  144. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/paths.py +0 -0
  145. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/plan_validator.py +0 -0
  146. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/platforms.py +0 -0
  147. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/pricing.py +0 -0
  148. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/question.py +0 -0
  149. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/recall_chunker.py +0 -0
  150. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/recall_index.py +0 -0
  151. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/refgraph.py +0 -0
  152. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/src/c3/usage_ingester.py +0 -0
  153. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/__init__.py +0 -0
  154. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/_pre_compact_helpers.py +0 -0
  155. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/conftest.py +0 -0
  156. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/fixtures/usage/README.md +0 -0
  157. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/fixtures/usage/mainline.jsonl +0 -0
  158. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
  159. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
  160. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/__init__.py +0 -0
  161. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_check_agent_invocation.py +0 -0
  162. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_consolidate_memory.py +0 -0
  163. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_detect_execution_verification.py +0 -0
  164. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_permission_handler.py +0 -0
  165. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_permission_handler_toast.py +0 -0
  166. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
  167. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_planner_check.py +0 -0
  168. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_planner_check_dev.py +0 -0
  169. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_post_tool.py +0 -0
  170. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_pre_tool.py +0 -0
  171. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_recall_autorebuild.py +0 -0
  172. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_recall_inject.py +0 -0
  173. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_record_agent_outcome.py +0 -0
  174. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_record_review_decision.py +0 -0
  175. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_restore_session.py +0 -0
  176. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_review_hint_inject.py +0 -0
  177. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_select_tier.py +0 -0
  178. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_select_tier_escalation.py +0 -0
  179. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_session_start.py +0 -0
  180. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_session_stop.py +0 -0
  181. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_session_utils.py +0 -0
  182. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
  183. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_settings_tier_autoapply_registration.py +0 -0
  184. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_similarity_boost.py +0 -0
  185. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_statusline.py +0 -0
  186. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_statusline_template_sync.py +0 -0
  187. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_sync_check.py +0 -0
  188. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_template_guard.py +0 -0
  189. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/hooks/test_tier_gap_check.py +0 -0
  190. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/__init__.py +0 -0
  191. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/_skill_helpers.py +0 -0
  192. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/test_c3_omission_declaration.py +0 -0
  193. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
  194. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/test_init_session_no_task_type.py +0 -0
  195. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/test_marker_compliance.py +0 -0
  196. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/test_parallel_agents_skill_tier.py +0 -0
  197. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/test_planner_lightweight.py +0 -0
  198. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/test_reachability_map.py +0 -0
  199. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/test_recall_skill.py +0 -0
  200. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
  201. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/test_session_guard.py +0 -0
  202. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/test_setup_templates.py +0 -0
  203. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
  204. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/test_start_skill_new_flow.py +0 -0
  205. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
  206. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_adapters.py +0 -0
  207. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_archive_reports.py +0 -0
  208. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_audit_review_decisions.py +0 -0
  209. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_check_deletions.py +0 -0
  210. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_ci_workflows.py +0 -0
  211. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_claude_gitignore.py +0 -0
  212. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_cli_ask.py +0 -0
  213. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_cli_doctor.py +0 -0
  214. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_cli_doctor_launcher.py +0 -0
  215. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_cli_entry.py +0 -0
  216. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_cli_init.py +0 -0
  217. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_cli_list.py +0 -0
  218. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_cli_metrics.py +0 -0
  219. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_cli_plan.py +0 -0
  220. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_cli_recall.py +0 -0
  221. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_cli_run.py +0 -0
  222. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_cli_tier.py +0 -0
  223. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_cli_update_breaking_changes.py +0 -0
  224. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_cli_update_deletions.py +0 -0
  225. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_db.py +0 -0
  226. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_db_connect_lint.py +0 -0
  227. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_docstring_consistency.py +0 -0
  228. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_embedding.py +0 -0
  229. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_excludes.py +0 -0
  230. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_extract_breaking_changes.py +0 -0
  231. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_gitutil.py +0 -0
  232. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_hooks_stdin_idiom.py +0 -0
  233. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_init_only_patterns.py +0 -0
  234. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_mcp_server_elicit.py +0 -0
  235. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_migrate.py +0 -0
  236. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_mode_line.py +0 -0
  237. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_no_bare_python_launcher.py +0 -0
  238. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_no_passonly_tests.py +0 -0
  239. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_nul_boundary_lint.py +0 -0
  240. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_paths.py +0 -0
  241. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_patterns_guard.py +0 -0
  242. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_plan_validator.py +0 -0
  243. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_pre_compact.py +0 -0
  244. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_pre_tool_hook.py +0 -0
  245. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_precompact_additional.py +0 -0
  246. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_precompact_toctou_fixes.py +0 -0
  247. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_pricing.py +0 -0
  248. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_recall_chunker.py +0 -0
  249. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_recall_index.py +0 -0
  250. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_references_migration.py +0 -0
  251. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_refgraph.py +0 -0
  252. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_refgraph_query.py +0 -0
  253. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_refgraph_structural.py +0 -0
  254. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_scripts_src_bootstrap.py +0 -0
  255. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_scripts_stdout_encoding.py +0 -0
  256. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_session_mode_watch.py +0 -0
  257. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_session_utils_additional.py +0 -0
  258. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_skill_no_builtin_conflict.py +0 -0
  259. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_start_skill_archive_contract.py +0 -0
  260. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_statusline.py +0 -0
  261. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_stop_additional.py +0 -0
  262. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_stop_agent_memory_warn.py +0 -0
  263. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_stop_hook.py +0 -0
  264. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_stop_precompact_fixes.py +0 -0
  265. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_sync_template_stop.py +0 -0
  266. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_template_pre_tool_hook.py +0 -0
  267. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_usage_ingester.py +0 -0
  268. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_worktree_guard.py +0 -0
  269. {claude_code_conductor-2.67.0 → claude_code_conductor-2.69.0}/tests/test_wt_agent_sync.py +0 -0
@@ -12,3 +12,4 @@ v2.51.0|Hook and statusLine launchers in the distributed settings.json changed f
12
12
  v2.58.0|c3 init and c3 update now place .claude/.gitignore in the target project. It excludes only regenerable files (state/recall.*) and session-scoped files (state/*.flag, state/tier_selection.json, logs/, worktrees/, tmp/). Existing projects that already committed those must run 'git rm -r --cached .claude/state/recall.hnsw .claude/state/recall_meta.json .claude/logs .claude/worktrees' once and commit, because .gitignore has no effect on already-tracked files. If your root .gitignore excludes the whole .claude/state/ directory, git never descends into it and the negation patterns in .claude/.gitignore do not apply; change the root rule to .claude/state/* or remove it. See .claude/docs/config-policy.md section 1-2.|c3 init / c3 update が .claude/.gitignore を配置するようになりました。除外は再生成可能なもの(state/recall.*)とセッション一時のもの(state/*.flag・state/tier_selection.json・logs/・worktrees/・tmp/)のみです。既にこれらを commit している既存プロジェクトは、.gitignore が tracked ファイルに効かないため git rm -r --cached を一度実行してください。ルートの .gitignore で .claude/state/ をディレクトリごと除外している場合は否定パターンが効かないため、ワイルドカード形式へ変更するか削除してください。詳細は .claude/docs/config-policy.md §1-2 を参照。
13
13
  v2.60.0|c3 plan validate/waves now strictly validate po_plan_version: only "0.1" (parallel) or "sequential" (sequential TDD) quoted strings are accepted; unknown or non-string values fail with exit 2. Migrate existing plan-reports by setting po_plan_version: "0.1" or "sequential" (quoted).|po_plan_version の値を厳格検査("0.1"/"sequential" のみ受理・他は exit 2)。既存 plan-report は引用符付きの正値へ修正する。
14
14
  v2.66.0|record_review_decision.py: --severity is now a required argument (omitting it exits 2 and records nothing; out-of-vocabulary values still fail-safe to NULL with a warning). --finding now rejects empty or whitespace-only values (exit 2). Update any custom invocations to pass --severity with one of critical/high/medium/low. Shipped SKILL.md call sites and docstrings are already updated.|
15
+ v2.68.0|New PreToolUse Write hook report_path_guard.py is registered in the distributed settings.json. It blocks (exit 2) writes of report-prefixed filenames (requirements-report-, architecture-report-, plan-report-, design-review-report-, code-review-report-, security-review-report-, test-report-, debug-analysis-, debug-needed-) outside .claude/reports/, overwrites of existing timestamp-named reports, and non-timestamp naming for the strict 4 report types. Ordinary files whose names start with those prefixes can no longer be written outside .claude/reports/. Set C3_REPORT_GUARD_DISABLE=1 to bypass, or remove the hook registration from settings.json.|レポート 9 prefix 名ファイルの .claude/reports/ 外への Write・タイムスタンプ形式既存レポートの上書き・strict-4 の非タイムスタンプ命名を exit 2 で block する report_path_guard hook を配布 settings.json に登録。対象 prefix 名の一般ファイルも block されうる。回避は C3_REPORT_GUARD_DISABLE=1 または settings.json から登録を外す。
@@ -13,6 +13,11 @@ PYTHONPATH に追加してから `from _hook_utils import ...` する経路を
13
13
  fail-safe な書き込みヘルパー。
14
14
  - ``sanitize_for_terminal(text)`` — 端末出力(stderr / stdout JSON)へ載せる前に
15
15
  C0 + DEL + C1 制御文字に加え、双方向制御・ゼロ幅・行区切り文字を除去するヘルパー。
16
+ - ``STRICT4_PREFIXES`` — タイムスタンプ形式のみ許容する prefix の tuple。
17
+ ``timestamp_pattern()`` と組み合わせて使用(report_path_guard / report_contract_check)。
18
+ - ``timestamp_pattern(prefix)`` — ``{prefix}YYYYMMDD-HHMMSS.md`` のフルマッチ用
19
+ 正規表現(``re.compile`` オブジェクト)を返す。全角数字偽装防止のため ``re.ASCII``
20
+ を指定。
16
21
  """
17
22
 
18
23
  from __future__ import annotations
@@ -54,6 +59,15 @@ _TERMINAL_SANITIZE_RE = re.compile(
54
59
  + r"]"
55
60
  )
56
61
 
62
+ # strict-4: タイムスタンプ形式のみ許容する prefix の tuple。
63
+ # report_path_guard.py / report_contract_check.py が共有する。
64
+ STRICT4_PREFIXES = (
65
+ 'requirements-report-',
66
+ 'architecture-report-',
67
+ 'plan-report-',
68
+ 'design-review-report-',
69
+ )
70
+
57
71
 
58
72
  # NOTE: 同一ロジックが src/c3/cli_update.py::_validate_deletion_path の step 13 に
59
73
  # 複製されている(import 経路がないため)。変更時は両方を揃えること。
@@ -79,6 +93,14 @@ def sanitize_for_terminal(text: str) -> str:
79
93
  return _TERMINAL_SANITIZE_RE.sub("", str(text))
80
94
 
81
95
 
96
+ def timestamp_pattern(prefix: str) -> re.Pattern:
97
+ """``{prefix}YYYYMMDD-HHMMSS.md`` のフルマッチ用パターンを返す。
98
+
99
+ re.ASCII を指定してタイムスタンプを ASCII 数字に限定し、全角数字による偽装を防止する。
100
+ """
101
+ return re.compile(re.escape(prefix) + r'\d{8}-\d{6}\.md', re.ASCII)
102
+
103
+
82
104
  def write_debug_log(log_path: Path, line: str) -> None:
83
105
  """``C3_HOOK_DEBUG=1`` のとき、ログファイルに ``ISO8601 line`` を 1 行追記する。
84
106
 
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env python3
2
+ """PostToolUse hook: タイムスタンプ報告契約の検査(P2・配布対象)。
3
+
4
+ `.claude/reports/` 直下への Write で、以下 2 レジームのファイル名契約逸脱を warn する。
5
+
6
+ **strict-4(タイムスタンプ形式のみ許容)** — 以下 4 prefix:
7
+ - requirements-report-
8
+ - architecture-report-
9
+ - plan-report-
10
+ - design-review-report-
11
+
12
+ 判定は basename 全体のフルマッチ `^{prefix}\\d{8}-\\d{6}\\.md$` で行う
13
+ (prefix 一致と suffix 一致の独立 2 条件ではない。`plan-report-badname-extra-20260725-180252.md`
14
+ のように prefix と タイムスタンプの間へ任意文字列を挟む名前も逸脱として検出するため)。
15
+
16
+ **loose-3(タイムスタンプ形式 または task_id 形式のどちらかを許容=緩検査)** — 以下 3 prefix:
17
+ - code-review-report-
18
+ - security-review-report-
19
+ - test-report-
20
+
21
+ CR / SR / test レポートは「タイムスタンプ採番」と「task_id 採番(並列 wave のファイル名衝突
22
+ 回避)」の 2 レジームを持つため、どちらか一方を強制しない。以下 2 形式のいずれかに一致すれば
23
+ 沈黙し、どちらにも一致しなければ警告する:
24
+ - タイムスタンプ形式: `^{prefix}\\d{8}-\\d{6}\\.md$`
25
+ - task_id 形式: `^{prefix}[英数 . _ -]{1,200}\\.md$`
26
+
27
+ `debug-analysis-` / `debug-needed-` はどちらのレジームにも属さず警告対象外。
28
+ 不一致の場合は 2 経路で警告: stderr(人間向け)+ stdout JSON(LLM向け)。
29
+
30
+ ## ディレクトリ判定とケース非依存化
31
+
32
+ reports ディレクトリの判定は `.claude/reports/` 直下かどうかを名前ベースで確認する。
33
+ Windows・macOS の既定(ケース非依存 FS)では `.CLAUDE` や `REPORTS` は `.claude` / `reports` と
34
+ 同一実体の別名であり、名前比較は `.lower()` を使用してケース非依存で行う。
35
+ basename の判定は 2 レジームとも厳密に行う(`re.IGNORECASE` 不使用・`re.ASCII` で Unicode 偽装を
36
+ 防止)。strict-4 は prefix・タイムスタンプ・`.md` の厳密フルマッチ、loose-3 は
37
+ タイムスタンプ形式 または task_id 形式(許容文字は ASCII 英数と `.` `_` `-` のみ)の
38
+ どちらかへの厳密フルマッチであり、緩いのは「許容形式が 2 つある」点のみで
39
+ 各形式の照合自体は厳密である。
40
+
41
+ ## 出力契約(PostToolUse warn の標準形)
42
+
43
+ exit 0 でも stdout JSON が LLM コンテキストへ additionalContext として注入される。
44
+ stderr は人間にのみ届く。詳細は Claude Code 公式仕様を参照:
45
+ https://code.claude.com/docs/en/hooks
46
+ """
47
+
48
+ import json
49
+ import os
50
+ import re
51
+ import sys
52
+ from pathlib import Path
53
+
54
+ # 共通ヘルパー (_hook_utils.sanitize_for_terminal, norm_component, STRICT4_PREFIXES,
55
+ # timestamp_pattern) を hooks/ 経由で import するため、このスクリプトのディレクトリを
56
+ # PYTHONPATH に追加する。
57
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
58
+ from _hook_utils import ( # noqa: E402
59
+ norm_component,
60
+ sanitize_for_terminal,
61
+ STRICT4_PREFIXES,
62
+ timestamp_pattern,
63
+ )
64
+
65
+ try:
66
+ sys.stdin.reconfigure(encoding='utf-8')
67
+ sys.stdout.reconfigure(encoding='utf-8')
68
+ sys.stderr.reconfigure(encoding='utf-8')
69
+ except AttributeError:
70
+ pass
71
+
72
+ # strict-4: STRICT4_PREFIXES は _hook_utils から import。
73
+ # loose-3: タイムスタンプ形式 または task_id 形式のどちらかを許容する prefix。
74
+ # 注: タイムスタンプ形式(YYYYMMDD-HHMMSS)は task_id 形式の真部分集合であり、
75
+ # 判定としては task_id 形式 1 本で足りる(2 形式併記は利用者向けの説明のため)。
76
+ LOOSE3_PREFIXES = (
77
+ 'code-review-report-',
78
+ 'security-review-report-',
79
+ 'test-report-',
80
+ )
81
+
82
+ # task_id の許容文字集合と長さ上限(英数と `.` `_` `-` のみ 1〜200 文字)。
83
+ TASK_ID_MAX_LEN = 200
84
+
85
+
86
+ def task_id_pattern(prefix):
87
+ """`{prefix}{task_id}.md` のフルマッチ用パターンを返す。
88
+
89
+ task_id は ASCII 英数と `.` `_` `-` のみ 1〜TASK_ID_MAX_LEN 文字。
90
+ 文字集合をリテラルで ASCII に限定したうえで、`re.ASCII` も明示して意図を固定する。
91
+ """
92
+ return re.compile(
93
+ re.escape(prefix) + r'[A-Za-z0-9._-]{1,' + str(TASK_ID_MAX_LEN) + r'}\.md',
94
+ re.ASCII,
95
+ )
96
+
97
+
98
+ def emit_warning(basename, expected_format, stderr_hint, context_hint):
99
+ """契約逸脱を 2 経路(stderr = 人間向け / stdout JSON = LLM 向け)で警告する。
100
+
101
+ basename は外部由来のため、両経路とも sanitize_for_terminal() を通してから出力する。
102
+ strict-4 / loose-3 で期待形式・採番案内の文面のみが変わり、出力の骨格は共通。
103
+ """
104
+ safe_basename = sanitize_for_terminal(basename)
105
+
106
+ # stderr(人間向け)
107
+ print(
108
+ f'[ReportContract WARN] {safe_basename} '
109
+ f'のファイル名形式が違反しています。'
110
+ f'期待形式: {expected_format}\n'
111
+ f'{stderr_hint}',
112
+ file=sys.stderr
113
+ )
114
+
115
+ # stdout JSON(LLM向け)
116
+ context = (
117
+ f'ファイル名: {safe_basename}\n'
118
+ f'期待形式: {expected_format}\n'
119
+ f'採番方法: {context_hint}'
120
+ )
121
+ output = {
122
+ 'hookSpecificOutput': {
123
+ 'hookEventName': 'PostToolUse',
124
+ 'additionalContext': context
125
+ }
126
+ }
127
+ print(json.dumps(output), file=sys.stdout)
128
+
129
+
130
+ def main():
131
+ # JSON payload の読み込み(fail-open)
132
+ try:
133
+ payload = json.loads(sys.stdin.read())
134
+ except (json.JSONDecodeError, ValueError):
135
+ sys.exit(0)
136
+
137
+ # tool_name の確認(Write 以外は対象外=新規命名は Write でのみ発生)
138
+ tool_name = payload.get('tool_name', '')
139
+ if tool_name != 'Write':
140
+ sys.exit(0)
141
+
142
+ # file_path の抽出(キー欠落は fail-open)
143
+ file_path = payload.get('tool_input', {}).get('file_path', '')
144
+ if not file_path:
145
+ sys.exit(0)
146
+
147
+ # `.claude/reports/` 直下かどうかの判定
148
+ path_obj = Path(file_path)
149
+ basename = path_obj.name
150
+ parent = path_obj.parent
151
+
152
+ # 構造判定: 親が `reports` かつ祖父が `.claude`(= `.claude/reports/` 直下)。
153
+ # 上流に同名の `reports/` があっても誤アンカーしない(resolve は使わない)。
154
+ # ケース非依存 FS(Windows・macOS)では `.CLAUDE` や `REPORTS` は `.claude` / `reports` と
155
+ # 同一実体の別名のため、比較は norm_component() で正規化してから行う。
156
+ # Windows の末尾ドット・スペースも norm_component() が除去する。
157
+ # 例: `/foo/.claude/reports/plan-report-20260725-180252.md` → 対象
158
+ # 例: `/foo/.claude/reports/archive/plan-report-final.md` → 対象外(直下でない)
159
+ if norm_component(parent.name) != 'reports' or norm_component(parent.parent.name) != '.claude':
160
+ sys.exit(0)
161
+
162
+ # basename を正規化(ケース非依存化・末尾ドット/スペース除去)してから prefix 判定。
163
+ # report_path_guard.py と同じポリシーで統一。
164
+ # **重要**: prefix 判定は norm_basename で行うが、形式マッチング(拡張子検証含む)は
165
+ # 元の basename に対して行う。これにより大文字混じり prefix(Test-Report-)は検出
166
+ # しつつ、`.MD` 拡張子のような大文字混じりは厳密に reject できる。
167
+ norm_basename = norm_component(basename)
168
+
169
+ # strict-4: 対象 prefix の確認 → basename 全体のフルマッチ確認
170
+ # (`{prefix}YYYYMMDD-HHMMSS.md` のみ許容)
171
+ for prefix in STRICT4_PREFIXES:
172
+ if not norm_basename.startswith(prefix):
173
+ continue
174
+ # 形式マッチングは元の basename に対して行う(`.md` 拡張子の厳密化)
175
+ if timestamp_pattern(prefix).fullmatch(basename):
176
+ # 正しい形式
177
+ sys.exit(0)
178
+ emit_warning(
179
+ basename,
180
+ '{prefix}YYYYMMDD-HHMMSS.md',
181
+ '採番は report-timestamp skill を使用してください。',
182
+ 'report-timestamp skill で取得したタイムスタンプを使用してください。',
183
+ )
184
+ sys.exit(0)
185
+
186
+ # loose-3: 対象 prefix の確認 → 2 形式のいずれかへのフルマッチ確認
187
+ # (`{prefix}YYYYMMDD-HHMMSS.md` または `{prefix}{task_id}.md`)
188
+ for prefix in LOOSE3_PREFIXES:
189
+ if not norm_basename.startswith(prefix):
190
+ continue
191
+ # 形式マッチングは元の basename に対して行う(`.md` 拡張子の厳密化)
192
+ if (timestamp_pattern(prefix).fullmatch(basename)
193
+ or task_id_pattern(prefix).fullmatch(basename)):
194
+ # どちらかの正しい形式
195
+ sys.exit(0)
196
+ emit_warning(
197
+ basename,
198
+ '{prefix}YYYYMMDD-HHMMSS.md または {prefix}{task_id}.md'
199
+ f'(task_id は英数と . _ - のみ 1〜{TASK_ID_MAX_LEN} 文字)',
200
+ 'タイムスタンプ採番は report-timestamp skill を、'
201
+ 'task_id 採番は plan-report の task_id を使用してください。',
202
+ 'タイムスタンプ形式なら report-timestamp skill で取得した値を、'
203
+ 'task_id 形式なら plan-report の task_id を使用してください。',
204
+ )
205
+ sys.exit(0)
206
+
207
+ # どちらのレジームにも属さない prefix(debug 系・自由域)は対象外
208
+ sys.exit(0)
209
+
210
+
211
+ if __name__ == '__main__':
212
+ main()
@@ -0,0 +1,283 @@
1
+ #!/usr/bin/env python3
2
+ """PreToolUse hook: レポートの書き先パスを守るガード(P4・配布対象)。
3
+
4
+ Write ツールで作成されるレポートファイル(対象 9 prefix)について、
5
+ 書き先が `.claude/reports/` 直下であること・既存レポートを上書きしないこと・
6
+ strict-4 prefix がタイムスタンプ命名であることを検査し、逸脱を exit 2 でブロックする。
7
+
8
+ ## 発火条件
9
+
10
+ `tool_name` == "Write" かつ、basename を `_hook_utils.norm_component()`
11
+ (lower + 末尾ドット/スペース除去)で正規化した値が対象 9 prefix のいずれかで
12
+ 始まる場合のみ。非対象 basename・Write 以外は沈黙(exit 0・stderr 空)。
13
+
14
+ ## 判定入力の一本化
15
+
16
+ target = `file_path` が絶対ならそのまま、相対なら `os.getcwd()` と結合したパス。
17
+ 第 1 層・第 2 層とも同じ target を判定する(判定対象が経路によってブレないようにする)。
18
+
19
+ ## 検査 1: 封じ込め(二層)
20
+
21
+ - 第 1 層(名前判定): target の親が `reports`・祖父が `.claude`
22
+ (`norm_component` 正規化で比較。ケース非依存 FS の別名・末尾ドットを吸収する)
23
+ - 第 2 層(実体照合): `realpath(target)` の親ディレクトリが許可 root と
24
+ `os.path.samefile` で一致すること
25
+
26
+ 許可 root は 2 つ:
27
+ - root A = `realpath(os.getcwd())/.claude/reports`(一次)
28
+ - root B = `realpath($CLAUDE_PROJECT_DIR)/.claude/reports`(env が非空のときのみ)
29
+
30
+ env 未設定・空なら root A のみで判定する(縮退。root B が無いことを理由に block しない)。
31
+ 両層 AND で許可し、不成立・両 root 不在・親不在は block(fail-closed)。
32
+
33
+ ### 免責(脅威モデル外)
34
+
35
+ 許可 root ディレクトリ自体(`.claude/reports` そのもの)が外部へのリンクに
36
+ 差し替えられているケースは、expected(root A/B)と resolved(target の親)が
37
+ 同一実体になるため block しない。root そのものを差し替えられる権限を持つ相手は
38
+ 本 hook の防御対象ではなく、ここで block すると正規運用(reports を別ボリュームへ
39
+ 逃がす構成)を壊すだけになる。
40
+
41
+ ## 判定の分割(正規化 basename と元 basename)
42
+
43
+ report_contract_check.py と統一したポリシー: **prefix 一致判定は
44
+ `norm_component()` 正規化後の basename・形式(timestamp フルマッチ)判定は
45
+ 元の basename** に対して行う。これにより大文字混じり prefix(`Plan-Report-`)は
46
+ prefix 一致として検出しつつ、`.MD` のような大文字混じり拡張子は timestamp 形式として
47
+ 誤許可しない(ケース非依存 FS では正規名と同一実体になりうるため、この場合は
48
+ strict-4 なら検査 3 で block・非 strict-4 なら通常の新規作成として許可される)。
49
+
50
+ ## 検査 2: 上書き block
51
+
52
+ 元の basename が timestamp 形式(prefix + `\\d{8}-\\d{6}\\.md` の `re.ASCII`
53
+ フルマッチ)かつ書き込み先実体が既存 → block。task_id 形式(非 timestamp)の
54
+ 既存上書きは許可する(同一 task の再実行で更新する正規経路のため)。
55
+
56
+ ## 検査 3: strict-4 形式 block
57
+
58
+ strict-4 prefix(requirements / architecture / plan / design-review)に一致する
59
+ (正規化 basename が prefix で始まる)ファイルの、元の basename が timestamp 形式に
60
+ フルマッチしない場合は新規作成でも block。CR / SR / test / debug 系は
61
+ task_id 命名が正規のため非適用。
62
+
63
+ ## fail 方針
64
+
65
+ - 壊れた stdin JSON / payload 非オブジェクト / キー欠落 / 空 file_path は fail-open
66
+ (対象かどうかすら判定できない入力で他の Write を巻き込まない)
67
+ - 対象と判定した後のパス判定で例外を誘発する入力・NUL 混入は fail-closed(block)
68
+
69
+ ## bypass
70
+
71
+ 環境変数 `C3_REPORT_GUARD_DISABLE=1` で全検査をスキップする。
72
+ """
73
+
74
+ import json
75
+ import os
76
+ import re
77
+ import sys
78
+ from pathlib import Path
79
+
80
+ # 共通ヘルパー (_hook_utils.norm_component, sanitize_for_terminal, STRICT4_PREFIXES,
81
+ # timestamp_pattern) を hooks/ 経由で import するため、このスクリプトのディレクトリを
82
+ # PYTHONPATH に追加する。
83
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
84
+ from _hook_utils import ( # noqa: E402
85
+ norm_component,
86
+ sanitize_for_terminal,
87
+ STRICT4_PREFIXES,
88
+ timestamp_pattern,
89
+ )
90
+
91
+ try:
92
+ sys.stdin.reconfigure(encoding='utf-8')
93
+ sys.stdout.reconfigure(encoding='utf-8')
94
+ sys.stderr.reconfigure(encoding='utf-8')
95
+ except AttributeError:
96
+ pass
97
+
98
+ # 対象 9 prefix(正規化済み basename に対して startswith で判定する)
99
+ # strict-4 の先頭 4 件は _hook_utils.STRICT4_PREFIXES から導出し、二重管理を避ける。
100
+ TARGET_PREFIXES = STRICT4_PREFIXES + (
101
+ 'code-review-report-',
102
+ 'security-review-report-',
103
+ 'test-report-',
104
+ 'debug-analysis-',
105
+ 'debug-needed-',
106
+ )
107
+
108
+ REPORTS_DIR_NAME = 'reports'
109
+ CLAUDE_DIR_NAME = '.claude'
110
+ PROJECT_DIR_ENV = 'CLAUDE_PROJECT_DIR'
111
+ DISABLE_ENV = 'C3_REPORT_GUARD_DISABLE'
112
+
113
+ NUL = '\x00'
114
+
115
+
116
+ def _match_prefix(norm_basename):
117
+ """正規化済み basename が対象 9 prefix のどれで始まるかを返す(無ければ None)。"""
118
+ for prefix in TARGET_PREFIXES:
119
+ if norm_basename.startswith(prefix):
120
+ return prefix
121
+ return None
122
+
123
+
124
+ def _is_timestamp_name(basename, prefix):
125
+ """`{prefix}YYYYMMDD-HHMMSS.md` のフルマッチか。
126
+
127
+ _hook_utils.timestamp_pattern() へ委譲。**元の(正規化前の)basename** に対して
128
+ 判定する(report_contract_check.py と統一。prefix 一致は正規化後 basename で行い、
129
+ 形式判定は元の basename で行う分割により、`.MD` 等の大文字混じり拡張子を
130
+ timestamp 形式として誤許可しない)。
131
+ """
132
+ return timestamp_pattern(prefix).fullmatch(basename) is not None
133
+
134
+
135
+ def _allowed_roots():
136
+ """許可 root(root A / root B)の候補パスを返す。
137
+
138
+ root B は `CLAUDE_PROJECT_DIR` が非空のときのみ加える。存在確認はここでは行わず、
139
+ 照合側(samefile)に委ねる。
140
+ """
141
+ roots = [
142
+ os.path.join(
143
+ os.path.realpath(os.getcwd()), CLAUDE_DIR_NAME, REPORTS_DIR_NAME
144
+ )
145
+ ]
146
+ project_dir = os.environ.get(PROJECT_DIR_ENV, '')
147
+ if project_dir:
148
+ roots.append(
149
+ os.path.join(
150
+ os.path.realpath(project_dir), CLAUDE_DIR_NAME, REPORTS_DIR_NAME
151
+ )
152
+ )
153
+ return roots
154
+
155
+
156
+ def _is_contained(target):
157
+ """target が許可 root 直下に封じ込められているか(二層 AND)。"""
158
+ # 第 1 層: 名前判定(`.claude/reports/` 直下の形をしているか)。
159
+ # `..` を含む脱出パスは親の名前が `..` になるためここで落ちる。
160
+ parent = target.parent
161
+ if norm_component(parent.name) != REPORTS_DIR_NAME:
162
+ return False
163
+ if norm_component(parent.parent.name) != CLAUDE_DIR_NAME:
164
+ return False
165
+
166
+ # 第 2 層: 実体照合(リンク偽装で名前だけ揃えた経路をここで落とす)。
167
+ resolved_parent = os.path.dirname(os.path.realpath(str(target)))
168
+ if NUL in resolved_parent:
169
+ return False
170
+ for root in _allowed_roots():
171
+ try:
172
+ if os.path.samefile(resolved_parent, root):
173
+ return True
174
+ except OSError:
175
+ # root 不在・親不在は「一致しない」として扱う(fail-closed)。
176
+ continue
177
+ return False
178
+
179
+
180
+ def _block(kind_line, guidance):
181
+ """違反種別ごとのメッセージを stderr へ出して exit 2 する。"""
182
+ print(
183
+ f'[ReportPathGuard BLOCK] {kind_line}\n'
184
+ f'{guidance}\n'
185
+ f'恒久 bypass: {DISABLE_ENV}=1 環境変数を設定してください',
186
+ file=sys.stderr,
187
+ )
188
+ sys.exit(2)
189
+
190
+
191
+ def _block_containment(display_name):
192
+ _block(
193
+ f'封じ込め違反: {display_name} の書き先が許可された reports ディレクトリの外です。',
194
+ 'レポートは .claude/reports/ 直下に作成してください。\n'
195
+ '書き先パスをその形へ修正し、cwd がプロジェクトルート外なら'
196
+ 'プロジェクトルートへ cd してから再実行してください。',
197
+ )
198
+
199
+
200
+ def _block_overwrite(display_name):
201
+ _block(
202
+ f'上書き違反: {display_name} は既存のタイムスタンプ形式レポートです。',
203
+ '既存レポートの上書きは禁止です。'
204
+ 'report-timestamp skill で新しいタイムスタンプを再採番し、'
205
+ '別名のファイルとして作成してください。',
206
+ )
207
+
208
+
209
+ def _block_format(display_name, prefix):
210
+ _block(
211
+ f'形式違反: {display_name} は {prefix}YYYYMMDD-HHMMSS.md の形式ではありません。',
212
+ 'report-timestamp skill でタイムスタンプを再採番し、'
213
+ 'その値をファイル名に使用してください。',
214
+ )
215
+
216
+
217
+ def main():
218
+ # bypass: 恒久 disable 環境変数の確認(`1` 以外の値では無効化しない)
219
+ if os.environ.get(DISABLE_ENV) == '1':
220
+ sys.exit(0)
221
+
222
+ # JSON payload の読み込み(fail-open)
223
+ try:
224
+ payload = json.loads(sys.stdin.read())
225
+ except (json.JSONDecodeError, ValueError):
226
+ sys.exit(0)
227
+ if not isinstance(payload, dict):
228
+ sys.exit(0)
229
+
230
+ # tool_name の確認(Write 以外は対象外)
231
+ if payload.get('tool_name') != 'Write':
232
+ sys.exit(0)
233
+
234
+ # file_path の抽出(キー欠落・空文字は fail-open)
235
+ tool_input = payload.get('tool_input')
236
+ if not isinstance(tool_input, dict):
237
+ sys.exit(0)
238
+ file_path = tool_input.get('file_path')
239
+ if not isinstance(file_path, str) or not file_path:
240
+ sys.exit(0)
241
+
242
+ # 発火条件: 正規化 basename が対象 9 prefix で始まるか。
243
+ # ここまでは純粋な文字列操作であり、対象外なら一切副作用を出さずに抜ける。
244
+ raw_basename = os.path.basename(file_path)
245
+ norm_basename = norm_component(raw_basename)
246
+ prefix = _match_prefix(norm_basename)
247
+ if prefix is None:
248
+ sys.exit(0)
249
+
250
+ display_name = sanitize_for_terminal(raw_basename)
251
+
252
+ # ここから先はパス判定。例外を誘発する入力は fail-closed で block する。
253
+ try:
254
+ if os.path.isabs(file_path):
255
+ target = Path(file_path)
256
+ else:
257
+ target = Path(os.getcwd()) / file_path
258
+
259
+ # NUL 混入は realpath / samefile の挙動が環境依存になるため明示 block
260
+ if NUL in str(target):
261
+ _block_containment(display_name)
262
+
263
+ # 検査 1: 封じ込め
264
+ if not _is_contained(target):
265
+ _block_containment(display_name)
266
+
267
+ is_timestamp = _is_timestamp_name(raw_basename, prefix)
268
+
269
+ # 検査 2: 上書き block(timestamp 形式の既存のみ)
270
+ if is_timestamp and os.path.exists(str(target)):
271
+ _block_overwrite(display_name)
272
+
273
+ # 検査 3: strict-4 形式 block
274
+ if prefix in STRICT4_PREFIXES and not is_timestamp:
275
+ _block_format(display_name, prefix)
276
+ except (OSError, ValueError):
277
+ _block_containment(display_name)
278
+
279
+ sys.exit(0)
280
+
281
+
282
+ if __name__ == '__main__':
283
+ main()