claude-code-conductor 2.65.0__tar.gz → 2.67.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 (272) hide show
  1. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/code-reviewer.md +1 -0
  2. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/developer.md +1 -1
  3. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/security-reviewer.md +1 -0
  4. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/tester.md +2 -1
  5. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/wt_developer.md +1 -1
  6. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/wt_tester.md +3 -2
  7. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/breaking-changes.txt +1 -0
  8. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/_hook_utils.py +25 -5
  9. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/patterns_guard.py +16 -5
  10. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/planner_check.py +4 -14
  11. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/stop.py +170 -28
  12. claude_code_conductor-2.67.0/.claude/hooks/worktree_guard.py +162 -0
  13. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/autonomous-mode/SKILL.md +1 -1
  14. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/SKILL.md +3 -3
  15. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/scripts/record_agent_outcome.py +1 -1
  16. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +21 -6
  17. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/CHANGELOG.md +82 -0
  18. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/PKG-INFO +2 -2
  19. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/__init__.py +1 -1
  20. claude_code_conductor-2.67.0/tests/hooks/test_pip_reinstall_reminder.py +336 -0
  21. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_record_review_decision.py +96 -10
  22. claude_code_conductor-2.67.0/tests/hooks/test_sync_check.py +340 -0
  23. claude_code_conductor-2.67.0/tests/hooks/test_template_guard.py +371 -0
  24. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_planner_lightweight.py +5 -1
  25. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_nul_boundary_lint.py +102 -0
  26. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_patterns_guard.py +128 -0
  27. claude_code_conductor-2.67.0/tests/test_stop_agent_memory_warn.py +875 -0
  28. claude_code_conductor-2.67.0/tests/test_worktree_guard.py +646 -0
  29. claude_code_conductor-2.67.0/tests/test_wt_agent_sync.py +482 -0
  30. claude_code_conductor-2.65.0/.claude/hooks/worktree_guard.py +0 -95
  31. claude_code_conductor-2.65.0/tests/hooks/test_pip_reinstall_reminder.py +0 -133
  32. claude_code_conductor-2.65.0/tests/hooks/test_sync_check.py +0 -142
  33. claude_code_conductor-2.65.0/tests/hooks/test_template_guard.py +0 -145
  34. claude_code_conductor-2.65.0/tests/test_stop_agent_memory_warn.py +0 -337
  35. claude_code_conductor-2.65.0/tests/test_worktree_guard.py +0 -243
  36. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/.gitignore +0 -0
  37. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/CLAUDE.md +0 -0
  38. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/architect.md +0 -0
  39. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/design-critic.md +0 -0
  40. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/doc-writer.md +0 -0
  41. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/interviewer.md +0 -0
  42. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/planner.md +0 -0
  43. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/project-setup.md +0 -0
  44. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/systematic-debugger.md +0 -0
  45. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/wt_systematic-debugger.md +0 -0
  46. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/deletions.txt +0 -0
  47. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/docs/autonomous-mode-onboarding.md +0 -0
  48. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/docs/config-policy.md +0 -0
  49. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/docs/nul-boundary.md +0 -0
  50. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/docs/parallel-agents-setup.md +0 -0
  51. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/docs/platform-adapters.md +0 -0
  52. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/docs/settings.json.md +0 -0
  53. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/check_agent_invocation.py +0 -0
  54. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/consolidate_memory.py +0 -0
  55. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/permission_handler.py +0 -0
  56. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/permission_handler_toast.py +0 -0
  57. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/post_tool.py +0 -0
  58. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/pre_compact.py +0 -0
  59. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/pre_tool.py +0 -0
  60. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/recall_autorebuild.py +0 -0
  61. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/recall_inject.py +0 -0
  62. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/report_contract_check.py +0 -0
  63. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/restore_session.py +0 -0
  64. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/select_tier.py +0 -0
  65. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/session_mode_watch.py +0 -0
  66. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/session_start.py +0 -0
  67. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/session_stop.py +0 -0
  68. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/session_utils.py +0 -0
  69. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/statusline.py +0 -0
  70. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/tier_autoapply.py +0 -0
  71. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/tier_gap_check.py +0 -0
  72. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/memory/.gitkeep +0 -0
  73. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/permission_rules.json +0 -0
  74. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/rules/promoted/index.md +0 -0
  75. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/settings.json +0 -0
  76. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/autonomous-mode/scripts/mode_line.py +0 -0
  77. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/brainstorm/SKILL.md +0 -0
  78. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/codex-review/SKILL.md +0 -0
  79. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/c3-omission-declaration.md +0 -0
  80. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
  81. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/design-critic-rubric.md +0 -0
  82. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/design-rubric.md +0 -0
  83. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/interview-rubric.md +0 -0
  84. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
  85. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/reachability-map.md +0 -0
  86. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/record-protocol.md +0 -0
  87. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
  88. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/scripts/detect_execution_verification.py +0 -0
  89. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
  90. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/develop/SKILL.md +0 -0
  91. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/doc/SKILL.md +0 -0
  92. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/extract-lib/SKILL.md +0 -0
  93. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/init-session/SKILL.md +0 -0
  94. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/init-session/scripts/session_guard.py +0 -0
  95. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/mcp-config/SKILL.md +0 -0
  96. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
  97. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/pattern-status/SKILL.md +0 -0
  98. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
  99. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/recall/SKILL.md +0 -0
  100. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
  101. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
  102. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/review-phase/SKILL.md +0 -0
  103. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/setup/SKILL.md +0 -0
  104. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/setup/reference.md +0 -0
  105. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
  106. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
  107. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/start/SKILL.md +0 -0
  108. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/start/scripts/archive_reports.py +0 -0
  109. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/state/.gitkeep +0 -0
  110. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.gitignore +0 -0
  111. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/ARCHITECTURE.md +0 -0
  112. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/LICENSE +0 -0
  113. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/LICENSES/fastembed-LICENSE +0 -0
  114. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/LICENSES/fastembed-NOTICE +0 -0
  115. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/LICENSES/numpy-LICENSE +0 -0
  116. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/LICENSES/onnxruntime-LICENSE +0 -0
  117. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
  118. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/README.md +0 -0
  119. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/hatch_build.py +0 -0
  120. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/pyproject.toml +0 -0
  121. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/__main__.py +0 -0
  122. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/_db_params.py +0 -0
  123. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/_excludes.py +0 -0
  124. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/_terminal.py +0 -0
  125. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/adapters.py +0 -0
  126. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli.py +0 -0
  127. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_ask.py +0 -0
  128. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_doctor.py +0 -0
  129. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_init.py +0 -0
  130. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_list.py +0 -0
  131. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_metrics.py +0 -0
  132. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_plan.py +0 -0
  133. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_recall.py +0 -0
  134. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_run.py +0 -0
  135. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_tier.py +0 -0
  136. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_update.py +0 -0
  137. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/db.py +0 -0
  138. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/embedding.py +0 -0
  139. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/gitutil.py +0 -0
  140. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/mcp_server.py +0 -0
  141. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrate.py +0 -0
  142. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/001_initial.sql +0 -0
  143. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
  144. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/003_tier_cost.sql +0 -0
  145. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/004_agent_outcomes.sql +0 -0
  146. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/005_drop_agent_tier_bandit.sql +0 -0
  147. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/006_review_decisions_severity.sql +0 -0
  148. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/007_review_decisions_resolution.sql +0 -0
  149. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/008_drop_agent_runs.sql +0 -0
  150. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/README.md +0 -0
  151. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/__init__.py +0 -0
  152. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/paths.py +0 -0
  153. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/plan_validator.py +0 -0
  154. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/platforms.py +0 -0
  155. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/pricing.py +0 -0
  156. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/question.py +0 -0
  157. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/recall_chunker.py +0 -0
  158. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/recall_index.py +0 -0
  159. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/refgraph.py +0 -0
  160. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/usage_ingester.py +0 -0
  161. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/__init__.py +0 -0
  162. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/_pre_compact_helpers.py +0 -0
  163. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/conftest.py +0 -0
  164. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/fixtures/usage/README.md +0 -0
  165. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/fixtures/usage/mainline.jsonl +0 -0
  166. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
  167. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
  168. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/__init__.py +0 -0
  169. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_check_agent_invocation.py +0 -0
  170. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_consolidate_memory.py +0 -0
  171. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_detect_execution_verification.py +0 -0
  172. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_hook_utils.py +0 -0
  173. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_permission_handler.py +0 -0
  174. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_permission_handler_toast.py +0 -0
  175. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_planner_check.py +0 -0
  176. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_planner_check_dev.py +0 -0
  177. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_post_tool.py +0 -0
  178. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_pre_tool.py +0 -0
  179. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_recall_autorebuild.py +0 -0
  180. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_recall_inject.py +0 -0
  181. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_record_agent_outcome.py +0 -0
  182. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_restore_session.py +0 -0
  183. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_review_hint_inject.py +0 -0
  184. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_select_tier.py +0 -0
  185. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_select_tier_escalation.py +0 -0
  186. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_session_start.py +0 -0
  187. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_session_stop.py +0 -0
  188. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_session_utils.py +0 -0
  189. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
  190. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_settings_tier_autoapply_registration.py +0 -0
  191. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_similarity_boost.py +0 -0
  192. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_statusline.py +0 -0
  193. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_statusline_template_sync.py +0 -0
  194. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_tier_autoapply.py +0 -0
  195. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_tier_gap_check.py +0 -0
  196. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_tier_security_fixes.py +0 -0
  197. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/__init__.py +0 -0
  198. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/_skill_helpers.py +0 -0
  199. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_c3_omission_declaration.py +0 -0
  200. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
  201. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_init_session_no_task_type.py +0 -0
  202. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_marker_compliance.py +0 -0
  203. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_parallel_agents_skill_tier.py +0 -0
  204. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_reachability_map.py +0 -0
  205. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_recall_skill.py +0 -0
  206. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
  207. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_session_guard.py +0 -0
  208. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_setup_templates.py +0 -0
  209. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
  210. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_start_skill_new_flow.py +0 -0
  211. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
  212. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_adapters.py +0 -0
  213. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_archive_reports.py +0 -0
  214. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_audit_review_decisions.py +0 -0
  215. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_check_deletions.py +0 -0
  216. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_ci_workflows.py +0 -0
  217. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_claude_gitignore.py +0 -0
  218. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_ask.py +0 -0
  219. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_doctor.py +0 -0
  220. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_doctor_launcher.py +0 -0
  221. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_entry.py +0 -0
  222. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_init.py +0 -0
  223. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_list.py +0 -0
  224. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_metrics.py +0 -0
  225. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_plan.py +0 -0
  226. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_recall.py +0 -0
  227. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_run.py +0 -0
  228. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_tier.py +0 -0
  229. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_update_breaking_changes.py +0 -0
  230. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_update_deletions.py +0 -0
  231. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_db.py +0 -0
  232. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_db_connect_lint.py +0 -0
  233. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_docstring_consistency.py +0 -0
  234. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_embedding.py +0 -0
  235. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_excludes.py +0 -0
  236. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_extract_breaking_changes.py +0 -0
  237. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_gitutil.py +0 -0
  238. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_hooks_stdin_idiom.py +0 -0
  239. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_init_only_patterns.py +0 -0
  240. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_mcp_server_elicit.py +0 -0
  241. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_migrate.py +0 -0
  242. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_mode_line.py +0 -0
  243. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_no_bare_python_launcher.py +0 -0
  244. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_no_passonly_tests.py +0 -0
  245. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_paths.py +0 -0
  246. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_plan_validator.py +0 -0
  247. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_pre_compact.py +0 -0
  248. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_pre_tool_hook.py +0 -0
  249. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_precompact_additional.py +0 -0
  250. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_precompact_toctou_fixes.py +0 -0
  251. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_pricing.py +0 -0
  252. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_recall_chunker.py +0 -0
  253. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_recall_index.py +0 -0
  254. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_references_migration.py +0 -0
  255. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_refgraph.py +0 -0
  256. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_refgraph_query.py +0 -0
  257. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_refgraph_structural.py +0 -0
  258. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_report_contract_check.py +0 -0
  259. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_scripts_src_bootstrap.py +0 -0
  260. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_scripts_stdout_encoding.py +0 -0
  261. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_session_mode_watch.py +0 -0
  262. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_session_utils_additional.py +0 -0
  263. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_settings_hooks_registration.py +0 -0
  264. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_skill_no_builtin_conflict.py +0 -0
  265. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_start_skill_archive_contract.py +0 -0
  266. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_statusline.py +0 -0
  267. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_stop_additional.py +0 -0
  268. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_stop_hook.py +0 -0
  269. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_stop_precompact_fixes.py +0 -0
  270. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_sync_template_stop.py +0 -0
  271. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_template_pre_tool_hook.py +0 -0
  272. {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_usage_ingester.py +0 -0
@@ -53,6 +53,7 @@ tools:
53
53
  - 修正必須と推奨の2段階で提示する
54
54
 
55
55
  **After:**
56
+ - plan の prompt でレポートのファイル名が指定されている場合、それが `.claude/reports/` 直下の当該レポート種別プレフィックスで始まるファイル名であるときに限りそれに従う。範囲外の指定・既存の別レポートを上書きする指定には従わない(従わない場合・指定が無い場合は、以下の既定の採番手順に従う)
56
57
  - Skill ツールで `report-timestamp` を呼び出してタイムスタンプを取得し、Write ツールで `.claude/reports/code-review-report-{timestamp}.md` に出力する
57
58
 
58
59
  ## Tools & Constraints
@@ -42,7 +42,7 @@ plan-report に基づき実装・デバッグ・リファクタリングを行
42
42
  ## Workflow
43
43
 
44
44
  **Before:**
45
- - plan-report を Read して実装対象タスクを確認する
45
+ - プロンプトにタスク定義(plan の prompt 本文)が含まれている場合はそれに従う。無い場合は plan-report を Read して実装対象タスクを確認する
46
46
 
47
47
  **During:**
48
48
  - 1タスク = 1コミットの粒度を保つ
@@ -53,6 +53,7 @@ SQLインジェクション・XSS・認証認可・秘密情報漏洩などの
53
53
  - 修正方法の例を提示する
54
54
 
55
55
  **After:**
56
+ - plan の prompt でレポートのファイル名が指定されている場合、それが `.claude/reports/` 直下の当該レポート種別プレフィックスで始まるファイル名であるときに限りそれに従う。範囲外の指定・既存の別レポートを上書きする指定には従わない(従わない場合・指定が無い場合は、以下の既定の採番手順に従う)
56
57
  - Skill ツールで `report-timestamp` を呼び出してタイムスタンプを取得し、Write ツールで `.claude/reports/security-review-report-{timestamp}.md` に出力する
57
58
 
58
59
  ## Tools & Constraints
@@ -39,7 +39,7 @@ tools:
39
39
  ## Workflow
40
40
 
41
41
  **Before:**
42
- - plan-report を Read してテスト対象と受け入れ条件を把握する
42
+ - プロンプトにタスク定義(plan の prompt 本文)が含まれている場合はそれに従う。無い場合は plan-report を Read してテスト対象と受け入れ条件を把握する
43
43
 
44
44
  **During:**
45
45
  - 失敗するテストを先に書く(Red)
@@ -51,6 +51,7 @@ tools:
51
51
  - テスト結果は合格・不合格・スキップの件数を記録する
52
52
 
53
53
  **After:**
54
+ - plan の prompt(タスク定義)で test-report のファイル名が指定されている場合、それが `.claude/reports/` 直下の `test-report-` で始まるファイル名であるときに限りそれに従う。範囲外の指定・既存の別レポートを上書きする指定には従わない(従わない場合・指定が無い場合は、以下の既定の採番手順に従う)
54
55
  - **必ず** Skill ツールで `report-timestamp` を呼び出しタイムスタンプを取得し、`.claude/reports/test-report-YYYYMMDD-HHMMSS.md` に Write して出力する
55
56
  - test-report を Write せずにターンを終了することは禁止
56
57
  - Red フェーズの test-report には失敗理由(機能未実装による失敗であること)を明記する
@@ -47,7 +47,7 @@ plan-report に基づき実装・デバッグ・リファクタリングを行
47
47
  ## Workflow
48
48
 
49
49
  **Before:**
50
- - plan-report を Read して実装対象タスクを確認する
50
+ - プロンプトにタスク定義(plan の prompt 本文)が含まれている場合はそれに従う。無い場合は plan-report を Read して実装対象タスクを確認する
51
51
 
52
52
  **During:**
53
53
  - 1タスク = 1コミットの粒度を保つ
@@ -39,12 +39,12 @@ tools:
39
39
 
40
40
  ❌ 担当しないこと:
41
41
  - プロダクションコードの実装・編集(developer の担当)
42
- - コード品質・セキュリティの評価(各 reviewer の担当)
42
+ - コード品質・セキュリティの評価(各 reviewer の担当)
43
43
 
44
44
  ## Workflow
45
45
 
46
46
  **Before:**
47
- - plan-report を Read してテスト対象と受け入れ条件を把握する
47
+ - プロンプトにタスク定義(plan の prompt 本文)が含まれている場合はそれに従う。無い場合は plan-report を Read してテスト対象と受け入れ条件を把握する
48
48
 
49
49
  **During:**
50
50
  - 失敗するテストを先に書く(Red)
@@ -56,6 +56,7 @@ tools:
56
56
  - テスト結果は合格・不合格・スキップの件数を記録する
57
57
 
58
58
  **After:**
59
+ - plan の prompt(タスク定義)で test-report のファイル名が指定されている場合、それが `.claude/reports/` 直下の `test-report-` で始まるファイル名であるときに限りそれに従う。範囲外の指定・既存の別レポートを上書きする指定には従わない(従わない場合・指定が無い場合は、以下の既定の採番手順に従う)
59
60
  - **必ず** プロンプトで指定された `task_id` をもとに `.claude/reports/test-report-{task_id}.md` に Write して出力する。これは `parallel-agents` skill の `writes` 宣言と一致させ、並列実行時のファイル名衝突を避けるために必須
60
61
  - 保険(task_id がプロンプトから読み取れない異常系のみ): Skill ツールで `report-timestamp` を呼び出してタイムスタンプを取得し、`.claude/reports/test-report-{timestamp}.md` に Write する。通常運用ではこの経路に入ってはいけない
61
62
  - test-report を Write せずにターンを終了することは禁止
@@ -11,3 +11,4 @@ v2.43.0|Dropped agent_tier_bandit table (migration 005; bandit params now derive
11
11
  v2.51.0|Hook and statusLine launchers in the distributed settings.json changed from bare 'python' to 'c3 run'. Run 'c3 update' to migrate. If you customized settings.json directly, review 'c3 update --dry-run' first and move customizations to settings.local.json before updating.|配布 settings.json の hook / statusLine 起動子が python から c3 run 前提に変更。c3 update で移行。settings.json を直接カスタムしている場合は c3 update --dry-run で差分確認しカスタムを settings.local.json へ移してから update。
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
+ 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.|
@@ -12,7 +12,7 @@ PYTHONPATH に追加してから `from _hook_utils import ...` する経路を
12
12
  - ``write_debug_log(log_path, line)`` — ``C3_HOOK_DEBUG=1`` のときのみログを追記する
13
13
  fail-safe な書き込みヘルパー。
14
14
  - ``sanitize_for_terminal(text)`` — 端末出力(stderr / stdout JSON)へ載せる前に
15
- C0 + DEL + C1 制御文字を除去するヘルパー。
15
+ C0 + DEL + C1 制御文字に加え、双方向制御・ゼロ幅・行区切り文字を除去するヘルパー。
16
16
  """
17
17
 
18
18
  from __future__ import annotations
@@ -35,6 +35,25 @@ DEBUG_ENV = "C3_HOOK_DEBUG"
35
35
  # エスケープシーケンスのプリフィクス(例: CSI = \x9b)として解釈される
36
36
  _CONTROL_CHARS_RE = re.compile(r"[\x00-\x1f\x7f-\x9f]")
37
37
 
38
+ # 端末表示用の除去集合。stop.py の `_DISPLAY_SANITIZE_RE` と同一範囲を持つ
39
+ # (CR-M-001 / SR-NEW-1: hook 側の表示サニタイズを最も広い集合へ揃える)。
40
+ # _CONTROL_CHARS_RE(C0 + DEL + C1)に加えて次を除去する:
41
+ # - U+200B-U+200F — ゼロ幅スペース / ZWNJ / ZWJ / LRM / RLM(表示偽装)
42
+ # - U+2028 / U+2029 — Line/Paragraph Separator(行区切り偽装・JSON パーサ破壊)
43
+ # - U+202A-U+202E — 双方向埋め込み・上書き(RLO によるファイル名偽装)
44
+ # - U+2066-U+2069 — 双方向 isolate
45
+ # - U+FEFF — BOM / ZERO WIDTH NO-BREAK SPACE
46
+ # raw string は \uXXXX を解釈しないため、非 ASCII のコードポイントは chr() で連結する。
47
+ _TERMINAL_SANITIZE_RE = re.compile(
48
+ r"[\x00-\x1f\x7f-\x9f"
49
+ + chr(0x200b) + "-" + chr(0x200f)
50
+ + chr(0x2028) + chr(0x2029)
51
+ + chr(0x202a) + "-" + chr(0x202e)
52
+ + chr(0x2066) + "-" + chr(0x2069)
53
+ + chr(0xfeff)
54
+ + r"]"
55
+ )
56
+
38
57
 
39
58
  # NOTE: 同一ロジックが src/c3/cli_update.py::_validate_deletion_path の step 13 に
40
59
  # 複製されている(import 経路がないため)。変更時は両方を揃えること。
@@ -49,14 +68,15 @@ def norm_component(s: str) -> str:
49
68
 
50
69
 
51
70
  def sanitize_for_terminal(text: str) -> str:
52
- """端末インジェクション対策: C0 制御文字 / DEL / C1 制御文字を除去する。
71
+ """端末インジェクション対策: 制御文字と表示偽装文字を除去する。
53
72
 
54
- - 除去範囲は ``_CONTROL_CHARS_RE``(C0 + DEL + C1)と共通。ANSI エスケープの
55
- ESC (``\\x1b``) と CSI (``\\x9b``) の双方をカバーする。
73
+ - 除去範囲は ``_TERMINAL_SANITIZE_RE``(C0 + DEL + C1 + ゼロ幅 + 行区切り +
74
+ 双方向制御 + BOM)。ANSI エスケープの ESC (``\\x1b``) と CSI (``\\x9b``)、
75
+ RLO によるファイル名偽装、改行による偽の警告行の注入をまとめてカバーする。
56
76
  - hook が stderr / stdout JSON(additionalContext)へ外部由来の文字列
57
77
  (ファイル名など)を載せる前に通す。
58
78
  """
59
- return _CONTROL_CHARS_RE.sub("", str(text))
79
+ return _TERMINAL_SANITIZE_RE.sub("", str(text))
60
80
 
61
81
 
62
82
  def write_debug_log(log_path: Path, line: str) -> None:
@@ -13,15 +13,22 @@ promote-pattern skill。意図的な編集は `.claude/state/patterns_guard_allo
13
13
  - worktree 実行時: 基準が main/worktree のどちらになるかは環境解決に依存
14
14
  (本 hook の基準は main の patterns.json 本体の事故防止。main 側保護が
15
15
  保護されない場合は検知が働かない限界あり)
16
- - resolve() による大小文字の正準化は実在ファイルのみ(Windows nt._getfinalpathname)。
17
- patterns.json が未作成の初期環境(c3 init 直後)ではケース違い指定が素通りする
18
- - Windows の末尾ドット・スペース正規化も実在パス限定。非実在パスでは本 hook が検知できない限界
16
+ - (i) resolve() が実在パスに対して解決する軸: 大小文字の正準化(Windows
17
+ nt._getfinalpathname)・末尾ドット/スペース正規化は実在ファイルのみに効く。
18
+ patterns.json が未作成の初期環境(c3 init 直後)ではケース違い指定が素通りする。
19
+ 非実在パスでは本 hook が検知できない限界がある
20
+ - (ii) 文字列比較に構造的に残る軸: resolve() 後の比較は文字列一致に依存するため、
21
+ Unicode 正規化形(NFC/NFD)の違いは resolve() だけでは吸収されない。これは
22
+ 比較直前に両辺へ unicodedata.normalize("NFC", ...) を適用することで対応済み
23
+ (S3 ①)。ただし NFC 化しても別種の等価表現(大小文字・末尾装飾等)は (i) の
24
+ 実在パス限定という限界の範囲内でしか解決しない
19
25
  """
20
26
 
21
27
  import json
22
28
  import os
23
29
  import sys
24
30
  import time
31
+ import unicodedata
25
32
  from pathlib import Path
26
33
 
27
34
  # 共通ヘルパー (_hook_utils.sanitize_for_terminal) を hooks/ 経由で import するため、
@@ -80,8 +87,12 @@ def main():
80
87
  except (OSError, ValueError):
81
88
  sys.exit(0)
82
89
 
83
- # realpath 一致チェック
84
- if resolved != protected_path:
90
+ # realpath 一致チェック(比較直前に両辺を NFC 正規化する。片辺のみの正規化は禁止)。
91
+ # resolve() は実在パスのバイト列は正しく解決するが、Unicode 正規化形(NFC/NFD)の
92
+ # 違いまでは吸収しない。同一ファイルを指す NFD 表現の file_path が素通りするのを防ぐ。
93
+ if unicodedata.normalize("NFC", str(resolved)) != unicodedata.normalize(
94
+ "NFC", str(protected_path)
95
+ ):
85
96
  sys.exit(0)
86
97
 
87
98
  # patterns.json への書き込みが検出された → フラグチェック
@@ -35,10 +35,11 @@ import re
35
35
  import sys
36
36
  from pathlib import Path
37
37
 
38
- # 共通ヘルパー (_hook_utils.write_debug_log) を hooks/ 経由で import するため、
38
+ # 共通ヘルパー (_hook_utils.write_debug_log / sanitize_for_terminal) を hooks/ 経由で
39
+ # import するため、
39
40
  # このスクリプトのディレクトリを PYTHONPATH に追加する。
40
41
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
41
- from _hook_utils import write_debug_log # noqa: E402
42
+ from _hook_utils import sanitize_for_terminal, write_debug_log # noqa: E402
42
43
 
43
44
  try:
44
45
  sys.stdin.reconfigure(encoding="utf-8")
@@ -79,17 +80,6 @@ _R6_TASK_THRESHOLD = 3
79
80
  _REVIEWER_AGENTS = frozenset({"code-reviewer", "security-reviewer"})
80
81
 
81
82
 
82
- def _sanitize(s: str) -> str:
83
- """ターミナルインジェクション対策: 制御文字と JSON 互換性を壊す Unicode 行区切りを除去する。
84
-
85
- 除去対象:
86
- - C0/C1 制御文字 (\\x00-\\x1f) と DEL (\\x7f) — ANSI エスケープ (ESC 0x1b) を含む
87
- - U+2028 (Line Separator) / U+2029 (Paragraph Separator) — 一部の JS/JSON
88
- パーサが行区切りとして扱い、ensure_ascii=False の JSON 解析を破壊する
89
- """
90
- return re.sub("[\x00-\x1f\x7f\u2028\u2029]", "", str(s))
91
-
92
-
93
83
  def _normalize(path: str) -> str:
94
84
  """Windows のバックスラッシュを `/` に統一する。"""
95
85
  return path.replace("\\", "/")
@@ -277,7 +267,7 @@ def main() -> None:
277
267
  # 経路1: stderr に人間向けメッセージ(ターミナル表示用)
278
268
  print("[PlannerCheck WARN] plan-report の検査で違反を検出しました:",
279
269
  file=sys.stderr)
280
- sanitized_warnings = [_sanitize(msg) for msg in warnings]
270
+ sanitized_warnings = [sanitize_for_terminal(msg) for msg in warnings]
281
271
  for msg in sanitized_warnings:
282
272
  print(f" - {msg}", file=sys.stderr)
283
273
 
@@ -8,6 +8,7 @@ import json
8
8
  import sys
9
9
  import os
10
10
  import re
11
+ import stat
11
12
  import tempfile
12
13
  from datetime import date, datetime, timezone
13
14
 
@@ -49,6 +50,16 @@ AGENT_MEMORY_WARN_RATIO = 0.8
49
50
  # 必ず警告条件を満たすので)行数を下限値として報告する [SR-NEW]。
50
51
  AGENT_MEMORY_READ_CAP_BYTES = AGENT_MEMORY_LIMIT_BYTES * 2
51
52
 
53
+ # 個別メモリファイル(agent-memory/<agent>/ 配下・MEMORY.md 以外の *.md。
54
+ # topics/ 等のサブディレクトリを含め再帰走査の対象)向けの閾値。
55
+ # 索引 MEMORY.md と異なり起動時に system prompt へ自動注入されないため、
56
+ # injection 予算(25KB/200行の 80%)はそのまま適用できない。個別ファイルは
57
+ # 必要時に Read されるだけなので、制約は「1 回の Read で払うトークン・保守コスト」
58
+ # であり注入予算とは性質が違う。実測分布は 27〜50KB が正規運用範囲、200KB 超は
59
+ # 外れ値だったため、その中間の区切りとして 100KB を採用する
60
+ # (strict greater・超過のみ警告)[S3 ⑦]。
61
+ AGENT_MEMORY_FILE_LIMIT_BYTES = 100 * 1024
62
+
52
63
  # 過去セッションファイルから引き継ぐ - [ ] 行のサニタイズ用パターン。
53
64
  # C0/C1 制御文字 (タブ と通常スペース は保持) と U+2028 / U+2029 を除去する。
54
65
  # 過去ファイルの ## 残タスク はユーザー編集領域のため信頼境界として扱う [SR-V-001]。
@@ -395,53 +406,184 @@ def _warn_oversized_descriptions(patterns: list) -> None:
395
406
  )
396
407
 
397
408
 
409
+ def _sanitize_path_element(value) -> str:
410
+ """agent 名・相対サブパスの各セグメント・ファイル名を、連結前に個別サニタイズする。
411
+
412
+ 連結後の文字列全体へ長さ上限をかけると、長い要素の隣に来た別要素が丸ごと
413
+ 消えてどのファイルの警告か分からなくなる(要素境界が壊れる)。要素ごとに
414
+ 独立してサニタイズ・切り詰めることで、対象ファイルの一意特定を保つ
415
+ [S3 ⑦ 契約(4)]。
416
+ """
417
+ if not isinstance(value, str):
418
+ value = str(value)
419
+ return _DISPLAY_SANITIZE_RE.sub('', value)[:MAX_ID_LENGTH]
420
+
421
+
422
+ def _is_link_like(path: str) -> bool:
423
+ """symlink / junction など「別の場所を指す」エントリかどうかを判定する。
424
+
425
+ os.path.islink() は POSIX symlink 用の reparse tag しか見ないため、Windows の
426
+ junction (IO_REPARSE_TAG_MOUNT_POINT) を検出できない。junction はディレクトリ
427
+ として列挙されるとそのまま再帰走査に「辿られて」しまう [S3 ⑦ tester 実測]。
428
+ reparse tag の種類を問わず FILE_ATTRIBUTE_REPARSE_POINT 属性の有無で判定する
429
+ ことで、symlink・junction の両方、および将来追加されうる他の reparse tag も
430
+ 一律に除外する。
431
+ """
432
+ try:
433
+ if os.path.islink(path):
434
+ return True
435
+ st = os.lstat(path)
436
+ except OSError:
437
+ return False
438
+ attrs = getattr(st, 'st_file_attributes', 0)
439
+ return bool(attrs & getattr(stat, 'FILE_ATTRIBUTE_REPARSE_POINT', 0x400))
440
+
441
+
442
+ def _iter_relative_md_files(agent_dir: str, _rel_parts: list | None = None):
443
+ """agent_dir 配下の *.md を再帰的に列挙する(symlink / junction は辿らない)。
444
+
445
+ yield するのは agent_dir からの相対パス要素のリスト(例: ["topics", "foo.md"])。
446
+ 走査エラー(権限不足等)はそのディレクトリ配下をスキップして継続する
447
+ (fail-open。1 ディレクトリの読み取りエラーで走査全体を止めない)。
448
+ """
449
+ rel_parts = _rel_parts or []
450
+ dir_path = os.path.join(agent_dir, *rel_parts) if rel_parts else agent_dir
451
+ try:
452
+ entries = sorted(os.listdir(dir_path))
453
+ except OSError:
454
+ return
455
+ for entry in entries:
456
+ full = os.path.join(dir_path, entry)
457
+ if _is_link_like(full):
458
+ continue
459
+ try:
460
+ is_dir = os.path.isdir(full)
461
+ except OSError:
462
+ continue
463
+ new_rel = rel_parts + [entry]
464
+ if is_dir:
465
+ yield from _iter_relative_md_files(agent_dir, new_rel)
466
+ elif entry.lower().endswith('.md'):
467
+ yield new_rel
468
+
469
+
470
+ def _warn_index_file(agent_dir: str, safe_agent: str, warn_bytes: int, warn_lines: int) -> None:
471
+ """<agent_dir>/MEMORY.md(索引)が injection 予算の 80% に達していたら警告する。
472
+
473
+ 直下 1 階層の MEMORY.md を直接パスで判定する(_iter_relative_md_files 経由の
474
+ os.listdir を追加で呼ばない)。listdir を丸ごと差し替える表示サニタイズ系の
475
+ 既存テストは「top-level の os.listdir が 1 回だけ呼ばれる」前提のため、ここで
476
+ 再度 listdir すると意図しない偽陰性を生む [S3 ⑦ 実装ノート]。
477
+
478
+ MEMORY.md 自体が symlink / junction の場合はスキップする [CR-NEW]。
479
+ 直接パスで開く経路は `_iter_relative_md_files` のリンク除外を通らないため、
480
+ ここで検査しないと「symlink / junction は辿らない」契約 [S3 ⑦ 契約(1)] に
481
+ 索引経路だけ穴が残る(リンク先の任意ファイルのサイズ・行数が警告に出る)。
482
+ """
483
+ path = os.path.join(agent_dir, 'MEMORY.md')
484
+ if _is_link_like(path):
485
+ return
486
+ try:
487
+ size = os.path.getsize(path)
488
+ with open(path, 'rb') as f:
489
+ head = f.read(AGENT_MEMORY_READ_CAP_BYTES)
490
+ except OSError:
491
+ # MEMORY.md 未生成のディレクトリ(wt_developer 等)はスキップする
492
+ return
493
+
494
+ # 末尾改行は行を増やさない(全 agent-memory ファイルが末尾改行ありのため、
495
+ # 単純な count+1 だと通常経路で常に 1 行多く報告してしまう)。
496
+ lines = head.count(b'\n')
497
+ if head and not head.endswith(b'\n'):
498
+ lines += 1
499
+ truncated = size > len(head)
500
+
501
+ if size <= warn_bytes and lines <= warn_lines:
502
+ return
503
+
504
+ line_label = f'{lines}行以上' if truncated else f'{lines}行'
505
+ print(
506
+ f'[Stop] agent-memory/{safe_agent}/MEMORY.md が injection 予算に接近しています '
507
+ f'({size}B / {AGENT_MEMORY_LIMIT_BYTES}B, {line_label} / {AGENT_MEMORY_LIMIT_LINES}行)。'
508
+ f'超えた分は起動時に system prompt へ載らず、蓄積した知見が読まれなくなります。'
509
+ f'価値の低いエントリから削って予算内に戻してください',
510
+ file=sys.stderr,
511
+ )
512
+
513
+
514
+ def _warn_individual_file(agent_dir: str, rel_parts: list, safe_agent: str) -> None:
515
+ """個別メモリファイル(索引 MEMORY.md 以外の *.md)が 100KB を超えていたら警告する。
516
+
517
+ 索引と異なり system prompt へ自動注入されないため、行数しきい値や injection
518
+ 予算の文言は使わない。制約は「Read のたびに払うコスト・保守コスト」。
519
+ """
520
+ path = os.path.join(agent_dir, *rel_parts)
521
+ try:
522
+ size = os.path.getsize(path)
523
+ except OSError:
524
+ # 読めないファイル 1 件で走査全体を止めない(fail-open)
525
+ return
526
+
527
+ if size <= AGENT_MEMORY_FILE_LIMIT_BYTES:
528
+ return
529
+
530
+ safe_parts = [_sanitize_path_element(p) for p in rel_parts]
531
+ # nul-boundary: allow(警告文に埋め込む相対パス表示のみ・人間可読出力であり機械パース対象ではない)
532
+ rel_display = '/'.join(safe_parts)
533
+ print(
534
+ f'[Stop] agent-memory/{safe_agent}/{rel_display} が個別メモリファイルとして'
535
+ f'肥大しています ({size}B > {AGENT_MEMORY_FILE_LIMIT_BYTES}B)。'
536
+ f'Read のたびに読み込みコストがかかり保守コストも増えるため、'
537
+ f'内容を分割するか整理してください',
538
+ file=sys.stderr,
539
+ )
540
+
541
+
398
542
  def _warn_oversized_agent_memory() -> None:
399
- """agent-memory/*/MEMORY.md が injection 予算の 80% に達していたら stderr 警告する。
543
+ """agent-memory/*/ 配下の *.md を 2 種類のしきい値で走査し stderr 警告する。
544
+
545
+ - <agent>/MEMORY.md(索引。直下 1 階層のみ): injection 予算 25KB/200行の 80% 到達
546
+ - それ以外の *.md(個別メモリファイル。サブディレクトリ含め再帰走査):
547
+ AGENT_MEMORY_FILE_LIMIT_BYTES (100KB) 超過のみ
548
+
549
+ 直下以外の場所にある "MEMORY.md"(例: topics/MEMORY.md)は契約上の想定外だが、
550
+ 索引として扱うと agent 直下と衝突するため、個別メモリファイル扱いとする
551
+ [設計コメント: 契約に明記なし・「索引は直下 1 階層のみ」と解釈して実装]。
400
552
 
401
553
  削除・切り詰めは行わない(警告のみ・read-only)。1 ファイル 1 行で出力する。
402
554
  patterns.json 側の `_warn_oversized_descriptions` と同じ「警告のみ」方針を取る。
555
+ symlink / junction は辿らない [S3 ⑦ 契約(1)]。
403
556
  本関数は例外を出さない防御的実装だが、呼び出し側でも try/except で二重に保護する。
404
557
  """
405
558
  warn_bytes = int(AGENT_MEMORY_LIMIT_BYTES * AGENT_MEMORY_WARN_RATIO)
406
559
  warn_lines = int(AGENT_MEMORY_LIMIT_LINES * AGENT_MEMORY_WARN_RATIO)
407
560
  try:
408
- names = sorted(os.listdir(AGENT_MEMORY_DIR))
561
+ agent_names = sorted(os.listdir(AGENT_MEMORY_DIR))
409
562
  except OSError:
410
563
  # 利用先の初回起動時などディレクトリ自体が無い場合は何もしない
411
564
  return
412
565
 
413
- for name in names:
414
- path = os.path.join(AGENT_MEMORY_DIR, name, 'MEMORY.md')
566
+ for agent_name in agent_names:
567
+ agent_dir = os.path.join(AGENT_MEMORY_DIR, agent_name)
568
+ if _is_link_like(agent_dir):
569
+ continue
415
570
  try:
416
- size = os.path.getsize(path)
417
- with open(path, 'rb') as f:
418
- head = f.read(AGENT_MEMORY_READ_CAP_BYTES)
571
+ if not os.path.isdir(agent_dir):
572
+ # トップ階層の .gitkeep 等(ディレクトリでないエントリ)はスキップ
573
+ continue
419
574
  except OSError:
420
- # MEMORY.md 未生成のディレクトリ(wt_developer 等)や
421
- # トップ階層の .gitkeep 等はスキップする
422
- continue
423
-
424
- # 末尾改行は行を増やさない(全 agent-memory ファイルが末尾改行ありのため、
425
- # 単純な count+1 だと通常経路で常に 1 行多く報告してしまう)。
426
- lines = head.count(b'\n')
427
- if head and not head.endswith(b'\n'):
428
- lines += 1
429
- truncated = size > len(head)
430
-
431
- if size <= warn_bytes and lines <= warn_lines:
432
575
  continue
433
576
 
434
577
  # ディレクトリ名はファイルシステム由来で未検証のため、出力前に制御文字・
435
578
  # 表示偽装文字を除去し MAX_ID_LENGTH で切り詰めて無害化する。
436
- safe = _DISPLAY_SANITIZE_RE.sub('', name)[:MAX_ID_LENGTH]
437
- line_label = f'{lines}行以上' if truncated else f'{lines}行'
438
- print(
439
- f'[Stop] agent-memory/{safe}/MEMORY.md が injection 予算に接近しています '
440
- f'({size}B / {AGENT_MEMORY_LIMIT_BYTES}B, {line_label} / {AGENT_MEMORY_LIMIT_LINES}行)。'
441
- f'超えた分は起動時に system prompt へ載らず、蓄積した知見が読まれなくなります。'
442
- f'価値の低いエントリから削って予算内に戻してください',
443
- file=sys.stderr,
444
- )
579
+ safe_agent = _sanitize_path_element(agent_name)
580
+
581
+ _warn_index_file(agent_dir, safe_agent, warn_bytes, warn_lines)
582
+
583
+ for rel_parts in _iter_relative_md_files(agent_dir):
584
+ if rel_parts == ['MEMORY.md']:
585
+ continue # 索引は _warn_index_file が処理済み。二重報告しない。
586
+ _warn_individual_file(agent_dir, rel_parts, safe_agent)
445
587
 
446
588
 
447
589
  def update_patterns(date_str: str) -> None:
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env python3
2
+ """PreToolUse hook: worktree boundary guardrail.
3
+
4
+ PO_WORKTREE_GUARD=1 が設定されている場合のみ動作する。
5
+ worktree 内で実装タスクを実行するワークフロー(parallel-agents skill が
6
+ isolation:"worktree" 付きで起動する agent など)が事前にこの env を設定して有効化する。
7
+ Write / Edit ツールの対象パスが CWD(worktree ルート)外であればブロックする。
8
+
9
+ NOTE [SR-V-002]: env 未設定時にガードが無効化されるリスクは parallel-agents/SKILL.md
10
+ で `PO_WORKTREE_GUARD=1` 設定を必須化することで運用上対処する。CWD parts チェック
11
+ 単独で自動有効化する設計変更は次回 major bump で検討予定。
12
+ """
13
+
14
+ import json
15
+ import os
16
+ import sys
17
+ import unicodedata
18
+ from pathlib import Path
19
+
20
+ # 共通ヘルパー (_hook_utils.sanitize_for_terminal) を hooks/ 経由で import するため、
21
+ # このスクリプトのディレクトリを PYTHONPATH に追加する。
22
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
23
+ from _hook_utils import sanitize_for_terminal # noqa: E402
24
+
25
+ try:
26
+ sys.stdin.reconfigure(encoding='utf-8')
27
+ sys.stdout.reconfigure(encoding='utf-8')
28
+ sys.stderr.reconfigure(encoding='utf-8')
29
+ except AttributeError:
30
+ pass
31
+
32
+ # worktree パスの識別に使うコンポーネント名。
33
+ # `.claude/worktrees/agent-<id>/` という構造を前提とし、
34
+ # "worktrees" の直前のコンポーネントが ".claude" であることをパス分割で検査する。
35
+ # os.sep を末尾に補完する理由: `.claude/worktrees/agent-test/` のような
36
+ # パスを split(os.sep) すると末尾の空文字列が含まれるが、
37
+ # インデックス検索には影響しないため補完不要。
38
+ # ただし startswith(cwd + os.sep) による境界チェックでは os.sep が必須(例:
39
+ # `/foo/bar` が `/foo/baz` の prefix と誤判定されるのを防ぐ)。
40
+ _WORKTREES_PARENT = ".claude"
41
+ _WORKTREES_COMPONENT = "worktrees"
42
+
43
+
44
+ def decide_containment(resolved, cwd, same_entity=os.path.samefile, path_exists=os.path.exists):
45
+ """対象パスが cwd 配下かどうかを二層 AND で判定する純関数(S3 ①)。
46
+
47
+ 戻り値はブロック判定そのもの(True = block / False = allow)。
48
+
49
+ 第 1 層: 対象パスの解決(realpath)後、比較直前に両辺を NFC 正規化した文字列の
50
+ prefix 判定。表現差(NFC/NFD)だけの不一致を吸収する。
51
+ 第 2 層: ファイルシステム実体による cwd 配下確認。対象の実在する最深祖先から
52
+ 根方向へ祖先鎖を辿り、いずれかの祖先(最深祖先自身を含む)が cwd と
53
+ samefile であれば配下と判定する(対象自身が cwd と同一の場合も、
54
+ 対象自身が「実在する最深祖先」になるため自然に許可される)。
55
+
56
+ 許可は第 1 層・第 2 層の両方に合格した場合のみ(AND)。片方のみの合格では
57
+ 許可しない。
58
+
59
+ 例外(OSError / ValueError)はこの関数の内側で捕捉し、fail-closed として
60
+ True(block)を返す。捕捉が囲む範囲は対象パスの解決(realpath)と両層の判定の
61
+ みに限定する。
62
+
63
+ TOCTOU 免責 [SR-NEW-3]: realpath / path_exists / same_entity の各呼び出しの
64
+ 間に対象パスやその祖先が差し替えられる(判定後・実書き込み前にリンクへ
65
+ すり替えられる等)競合は原理的に残る。本 hook は agent の書き込み先が
66
+ worktree 外へ逸脱する事故を防ぐガードであり、悪意ある同時操作に対する
67
+ セキュリティ境界ではないため、この競合は許容する(patterns_guard.py の
68
+ フラグ TTL 判定と同じ免責)。射程外の経路(Bash / NotebookEdit 経由の
69
+ 書き込み)が元々ガードされないことと合わせ、本 hook 単独を防御の最終線に
70
+ しない前提で運用する。
71
+ """
72
+ try:
73
+ resolved = os.path.realpath(resolved)
74
+
75
+ # 実測補正: Windows / Python 3.11.9 では os.path.realpath() は NUL 混入
76
+ # パスでも例外を出さず NUL を保持したまま成功して返る。例外捕捉だけでは
77
+ # 不足するため、解決後パスに \x00 が含まれる場合を明示的に block する。
78
+ if "\x00" in resolved or "\x00" in cwd:
79
+ return True
80
+
81
+ # 第 1 層: 正規化文字列(realpath 後 NFC)の prefix 判定
82
+ norm_resolved = unicodedata.normalize("NFC", resolved)
83
+ norm_cwd = unicodedata.normalize("NFC", cwd)
84
+ if norm_resolved != norm_cwd and not norm_resolved.startswith(norm_cwd + os.sep):
85
+ return True
86
+
87
+ # 第 2 層: ファイルシステム実体による cwd 配下確認。
88
+ # 対象の実在する最深祖先を求める(対象自身が実在すればそれが最深祖先)。
89
+ target = Path(resolved)
90
+ deepest_existing = None
91
+ for candidate in (target, *target.parents):
92
+ if path_exists(str(candidate)):
93
+ deepest_existing = candidate
94
+ break
95
+ if deepest_existing is None:
96
+ return True
97
+
98
+ # 最深祖先から根方向へ祖先鎖を辿り、いずれかが cwd と samefile なら配下。
99
+ for ancestor in (deepest_existing, *deepest_existing.parents):
100
+ if same_entity(str(ancestor), cwd):
101
+ return False
102
+ return True
103
+ except (OSError, ValueError):
104
+ return True
105
+
106
+
107
+ def main():
108
+ if os.environ.get('PO_WORKTREE_GUARD') != '1':
109
+ sys.exit(0)
110
+
111
+ try:
112
+ payload = json.loads(sys.stdin.read())
113
+ except (json.JSONDecodeError, ValueError):
114
+ sys.exit(0)
115
+
116
+ tool_name = payload.get('tool_name', '')
117
+ if tool_name not in ('Write', 'Edit'):
118
+ sys.exit(0)
119
+
120
+ file_path = payload.get('tool_input', {}).get('file_path', '')
121
+ if not file_path:
122
+ sys.exit(0)
123
+
124
+ cwd = os.path.realpath(os.getcwd())
125
+
126
+ # [SR-V-001] CWD がパスコンポーネント分割で ".claude/worktrees/..." の
127
+ # 構造を持つことを検証する。
128
+ # str.split(os.sep) でパス要素に分解し、"worktrees" の直前コンポーネントが
129
+ # ".claude" であることを確認する。
130
+ # これにより、".claude" 自体が symlink で別名解決される場合でも
131
+ # os.path.realpath() 後のパスで正しく検証できる
132
+ # (文字列部分一致 (_WORKTREES_MARKER in cwd) よりも誤検知が少ない)。
133
+ parts = cwd.split(os.sep)
134
+ try:
135
+ wt_idx = parts.index(_WORKTREES_COMPONENT)
136
+ if wt_idx == 0 or parts[wt_idx - 1] != _WORKTREES_PARENT:
137
+ sys.exit(0)
138
+ except ValueError:
139
+ sys.exit(0)
140
+
141
+ target = file_path if os.path.isabs(file_path) else os.path.join(cwd, file_path)
142
+
143
+ if decide_containment(target, cwd):
144
+ # stderr 表示専用の best-effort 解決(判定には使わない・失敗しても target を出す)
145
+ try:
146
+ display_resolved = os.path.realpath(target)
147
+ except (OSError, ValueError):
148
+ display_resolved = target
149
+ print(
150
+ f'[WorktreeGuard BLOCK] worktree 外へのファイル操作をブロックしました。\n'
151
+ f' 対象パス: {sanitize_for_terminal(file_path)}\n'
152
+ f' 解決パス: {sanitize_for_terminal(display_resolved)}\n'
153
+ f' 許可範囲: {sanitize_for_terminal(cwd)}',
154
+ file=sys.stderr
155
+ )
156
+ sys.exit(2)
157
+
158
+ sys.exit(0)
159
+
160
+
161
+ if __name__ == '__main__':
162
+ main()
@@ -96,7 +96,7 @@ E-0 起因の差し戻しは E 周回として 1 消費する(CR/SR セット
96
96
  | サイクルの状態 | 分岐 |
97
97
  |---|---|
98
98
  | High/Medium が 1 件以上 | 従来どおり「全て対応する」を選ぶ(Low も含めて `[対応予定]`)。層別ルーティングで上流へ戻る=未収束 |
99
- | High/Medium 0・Low が 1 件以上(=収束) | 残る Low を「全て許容して進む」で処理する。許容理由は「C-3 収束条件(High/Medium 0)を満たしたため、残る Low は実装フェーズ以降で扱う」と記録し、`.claude/skills/dev-workflow/scripts/record_review_decision.py --decision accepted` で残す。フェーズ D へ進む |
99
+ | High/Medium 0・Low が 1 件以上(=収束) | 残る Low を「全て許容して進む」で処理する。許容理由は「C-3 収束条件(High/Medium 0)を満たしたため、残る Low は実装フェーズ以降で扱う」と記録し、`.claude/skills/dev-workflow/scripts/record_review_decision.py --decision accepted --severity low` で残す(`--severity` は必須引数)。フェーズ D へ進む |
100
100
  | findings 0 | そのままフェーズ D へ |
101
101
 
102
102
  **cycles_used の定義**: E は**周回数**。CR∥SR 並列 1 セット+修正=1 サイクル。