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.
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/code-reviewer.md +1 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/developer.md +1 -1
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/security-reviewer.md +1 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/tester.md +2 -1
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/wt_developer.md +1 -1
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/wt_tester.md +3 -2
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/breaking-changes.txt +1 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/_hook_utils.py +25 -5
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/patterns_guard.py +16 -5
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/planner_check.py +4 -14
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/stop.py +170 -28
- claude_code_conductor-2.67.0/.claude/hooks/worktree_guard.py +162 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/autonomous-mode/SKILL.md +1 -1
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/SKILL.md +3 -3
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/scripts/record_agent_outcome.py +1 -1
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +21 -6
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/CHANGELOG.md +82 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/PKG-INFO +2 -2
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/__init__.py +1 -1
- claude_code_conductor-2.67.0/tests/hooks/test_pip_reinstall_reminder.py +336 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_record_review_decision.py +96 -10
- claude_code_conductor-2.67.0/tests/hooks/test_sync_check.py +340 -0
- claude_code_conductor-2.67.0/tests/hooks/test_template_guard.py +371 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_planner_lightweight.py +5 -1
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_nul_boundary_lint.py +102 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_patterns_guard.py +128 -0
- claude_code_conductor-2.67.0/tests/test_stop_agent_memory_warn.py +875 -0
- claude_code_conductor-2.67.0/tests/test_worktree_guard.py +646 -0
- claude_code_conductor-2.67.0/tests/test_wt_agent_sync.py +482 -0
- claude_code_conductor-2.65.0/.claude/hooks/worktree_guard.py +0 -95
- claude_code_conductor-2.65.0/tests/hooks/test_pip_reinstall_reminder.py +0 -133
- claude_code_conductor-2.65.0/tests/hooks/test_sync_check.py +0 -142
- claude_code_conductor-2.65.0/tests/hooks/test_template_guard.py +0 -145
- claude_code_conductor-2.65.0/tests/test_stop_agent_memory_warn.py +0 -337
- claude_code_conductor-2.65.0/tests/test_worktree_guard.py +0 -243
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/.gitignore +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/CLAUDE.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/architect.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/design-critic.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/doc-writer.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/interviewer.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/planner.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/project-setup.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/systematic-debugger.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/wt_systematic-debugger.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/deletions.txt +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/docs/autonomous-mode-onboarding.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/docs/config-policy.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/docs/nul-boundary.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/docs/parallel-agents-setup.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/docs/platform-adapters.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/docs/settings.json.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/check_agent_invocation.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/consolidate_memory.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/permission_handler.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/permission_handler_toast.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/post_tool.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/pre_compact.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/pre_tool.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/recall_autorebuild.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/recall_inject.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/report_contract_check.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/restore_session.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/select_tier.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/session_mode_watch.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/session_start.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/session_stop.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/session_utils.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/statusline.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/tier_autoapply.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/tier_gap_check.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/memory/.gitkeep +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/permission_rules.json +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/rules/promoted/index.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/settings.json +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/autonomous-mode/scripts/mode_line.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/brainstorm/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/codex-review/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/c3-omission-declaration.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/design-critic-rubric.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/design-rubric.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/interview-rubric.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/reachability-map.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/record-protocol.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/scripts/detect_execution_verification.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/develop/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/doc/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/extract-lib/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/init-session/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/init-session/scripts/session_guard.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/mcp-config/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/pattern-status/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/recall/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/review-phase/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/setup/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/setup/reference.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/start/SKILL.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/skills/start/scripts/archive_reports.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/state/.gitkeep +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.gitignore +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/ARCHITECTURE.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/LICENSE +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/LICENSES/fastembed-LICENSE +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/LICENSES/fastembed-NOTICE +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/LICENSES/numpy-LICENSE +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/LICENSES/onnxruntime-LICENSE +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/README.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/hatch_build.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/pyproject.toml +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/__main__.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/_db_params.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/_excludes.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/_terminal.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/adapters.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_ask.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_doctor.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_init.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_list.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_metrics.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_plan.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_recall.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_run.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_tier.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/cli_update.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/db.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/embedding.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/gitutil.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/mcp_server.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrate.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/001_initial.sql +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/003_tier_cost.sql +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/004_agent_outcomes.sql +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/005_drop_agent_tier_bandit.sql +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/006_review_decisions_severity.sql +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/007_review_decisions_resolution.sql +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/008_drop_agent_runs.sql +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/README.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/migrations/__init__.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/paths.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/plan_validator.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/platforms.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/pricing.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/question.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/recall_chunker.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/recall_index.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/refgraph.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/src/c3/usage_ingester.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/__init__.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/_pre_compact_helpers.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/conftest.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/fixtures/usage/README.md +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/fixtures/usage/mainline.jsonl +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/__init__.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_check_agent_invocation.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_consolidate_memory.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_detect_execution_verification.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_hook_utils.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_permission_handler.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_permission_handler_toast.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_planner_check.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_planner_check_dev.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_post_tool.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_pre_tool.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_recall_autorebuild.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_recall_inject.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_record_agent_outcome.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_restore_session.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_review_hint_inject.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_select_tier.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_select_tier_escalation.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_session_start.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_session_stop.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_session_utils.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_settings_tier_autoapply_registration.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_similarity_boost.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_statusline.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_statusline_template_sync.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_tier_autoapply.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_tier_gap_check.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/hooks/test_tier_security_fixes.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/__init__.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/_skill_helpers.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_c3_omission_declaration.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_init_session_no_task_type.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_marker_compliance.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_parallel_agents_skill_tier.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_reachability_map.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_recall_skill.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_session_guard.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_setup_templates.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_start_skill_new_flow.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_adapters.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_archive_reports.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_audit_review_decisions.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_check_deletions.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_ci_workflows.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_claude_gitignore.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_ask.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_doctor.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_doctor_launcher.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_entry.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_init.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_list.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_metrics.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_plan.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_recall.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_run.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_tier.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_update_breaking_changes.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_cli_update_deletions.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_db.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_db_connect_lint.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_docstring_consistency.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_embedding.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_excludes.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_extract_breaking_changes.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_gitutil.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_hooks_stdin_idiom.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_init_only_patterns.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_mcp_server_elicit.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_migrate.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_mode_line.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_no_bare_python_launcher.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_no_passonly_tests.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_paths.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_plan_validator.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_pre_compact.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_precompact_additional.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_precompact_toctou_fixes.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_pricing.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_recall_chunker.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_recall_index.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_references_migration.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_refgraph.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_refgraph_query.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_refgraph_structural.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_report_contract_check.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_scripts_src_bootstrap.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_scripts_stdout_encoding.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_session_mode_watch.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_session_utils_additional.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_settings_hooks_registration.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_skill_no_builtin_conflict.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_start_skill_archive_contract.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_statusline.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_stop_additional.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_stop_hook.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_stop_precompact_fixes.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_sync_template_stop.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_template_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/tests/test_usage_ingester.py +0 -0
{claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/code-reviewer.md
RENAMED
|
@@ -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
|
{claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/agents/security-reviewer.md
RENAMED
|
@@ -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 には失敗理由(機能未実装による失敗であること)を明記する
|
|
@@ -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
|
-
"""端末インジェクション対策:
|
|
71
|
+
"""端末インジェクション対策: 制御文字と表示偽装文字を除去する。
|
|
53
72
|
|
|
54
|
-
- 除去範囲は ``
|
|
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
|
|
79
|
+
return _TERMINAL_SANITIZE_RE.sub("", str(text))
|
|
60
80
|
|
|
61
81
|
|
|
62
82
|
def write_debug_log(log_path: Path, line: str) -> None:
|
{claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/patterns_guard.py
RENAMED
|
@@ -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()
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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 への書き込みが検出された → フラグチェック
|
{claude_code_conductor-2.65.0 → claude_code_conductor-2.67.0}/.claude/hooks/planner_check.py
RENAMED
|
@@ -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/ 経由で
|
|
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 = [
|
|
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/*/
|
|
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
|
-
|
|
561
|
+
agent_names = sorted(os.listdir(AGENT_MEMORY_DIR))
|
|
409
562
|
except OSError:
|
|
410
563
|
# 利用先の初回起動時などディレクトリ自体が無い場合は何もしない
|
|
411
564
|
return
|
|
412
565
|
|
|
413
|
-
for
|
|
414
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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`
|
|
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 サイクル。
|