claude-code-conductor 2.54.0__tar.gz → 2.55.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.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/_hook_utils.py +25 -0
- claude_code_conductor-2.55.0/.claude/hooks/patterns_guard.py +126 -0
- claude_code_conductor-2.55.0/.claude/hooks/report_contract_check.py +130 -0
- claude_code_conductor-2.55.0/.claude/hooks/session_mode_watch.py +275 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/settings.json +20 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/promote-pattern/SKILL.md +7 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/ARCHITECTURE.md +4 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/CHANGELOG.md +18 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/PKG-INFO +21 -2
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/README.md +20 -1
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/__init__.py +1 -1
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_hook_utils.py +132 -0
- claude_code_conductor-2.55.0/tests/test_patterns_guard.py +598 -0
- claude_code_conductor-2.55.0/tests/test_report_contract_check.py +620 -0
- claude_code_conductor-2.55.0/tests/test_session_mode_watch.py +825 -0
- claude_code_conductor-2.55.0/tests/test_settings_hooks_registration.py +292 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/CLAUDE.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/agents/architect.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/agents/code-reviewer.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/agents/design-critic.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/agents/developer.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/agents/doc-writer.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/agents/interviewer.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/agents/planner.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/agents/project-setup.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/agents/security-reviewer.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/agents/systematic-debugger.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/agents/tester.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/agents/wt_developer.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/agents/wt_systematic-debugger.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/agents/wt_tester.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/breaking-changes.txt +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/deletions.txt +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/docs/autonomous-mode-onboarding.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/docs/config-policy.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/docs/nul-boundary.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/docs/parallel-agents-setup.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/docs/platform-adapters.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/docs/settings.json.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/check_agent_invocation.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/consolidate_memory.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/permission_handler.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/permission_handler_toast.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/planner_check.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/post_tool.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/pre_compact.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/pre_tool.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/recall_autorebuild.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/recall_inject.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/restore_session.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/select_tier.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/session_start.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/session_stop.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/session_utils.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/statusline.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/stop.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/tier_autoapply.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/tier_gap_check.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/hooks/worktree_guard.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/memory/.gitkeep +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/permission_rules.json +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/rules/promoted/index.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/autonomous-mode/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/autonomous-mode/scripts/mode_line.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/brainstorm/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/codex-review/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/dev-workflow/references/design-critic-rubric.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/dev-workflow/references/design-rubric.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/dev-workflow/references/interview-rubric.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/dev-workflow/scripts/record_agent_outcome.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/develop/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/doc/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/extract-lib/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/init-session/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/init-session/scripts/session_guard.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/mcp-config/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/pattern-status/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/recall/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/review-phase/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/setup/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/setup/reference.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/skills/start/SKILL.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.claude/state/.gitkeep +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/.gitignore +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/LICENSE +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/LICENSES/fastembed-LICENSE +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/LICENSES/fastembed-NOTICE +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/LICENSES/numpy-LICENSE +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/LICENSES/onnxruntime-LICENSE +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/hatch_build.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/pyproject.toml +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/__main__.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/_db_params.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/_excludes.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/_terminal.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/adapters.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/cli.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/cli_ask.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/cli_doctor.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/cli_init.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/cli_list.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/cli_metrics.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/cli_plan.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/cli_recall.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/cli_run.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/cli_tier.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/cli_update.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/db.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/embedding.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/gitutil.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/mcp_server.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/migrate.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/migrations/001_initial.sql +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/migrations/003_tier_cost.sql +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/migrations/004_agent_outcomes.sql +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/migrations/005_drop_agent_tier_bandit.sql +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/migrations/006_review_decisions_severity.sql +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/migrations/README.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/migrations/__init__.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/paths.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/plan_validator.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/platforms.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/pricing.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/question.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/recall_chunker.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/recall_index.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/src/c3/usage_ingester.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/__init__.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/_pre_compact_helpers.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/conftest.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/fixtures/usage/README.md +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/fixtures/usage/mainline.jsonl +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/__init__.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_check_agent_invocation.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_consolidate_memory.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_permission_handler.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_permission_handler_toast.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_planner_check.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_planner_check_dev.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_post_tool.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_pre_tool.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_recall_autorebuild.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_recall_inject.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_record_agent_outcome.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_record_review_decision.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_restore_session.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_review_hint_inject.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_select_tier.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_select_tier_escalation.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_session_start.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_session_stop.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_session_utils.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_settings_tier_autoapply_registration.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_similarity_boost.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_statusline.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_statusline_template_sync.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_sync_check.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_template_guard.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_tier_autoapply.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_tier_gap_check.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/hooks/test_tier_security_fixes.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/skills/__init__.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/skills/_skill_helpers.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/skills/test_init_session_no_task_type.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/skills/test_marker_compliance.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/skills/test_parallel_agents_skill_tier.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/skills/test_planner_lightweight.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/skills/test_recall_skill.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/skills/test_session_guard.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/skills/test_setup_templates.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/skills/test_start_skill_new_flow.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_adapters.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_check_deletions.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_cli_ask.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_cli_doctor.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_cli_doctor_launcher.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_cli_entry.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_cli_init.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_cli_list.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_cli_metrics.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_cli_plan.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_cli_recall.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_cli_run.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_cli_tier.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_cli_update_breaking_changes.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_cli_update_deletions.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_db.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_docstring_consistency.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_embedding.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_excludes.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_extract_breaking_changes.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_gitutil.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_mcp_server_elicit.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_migrate.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_mode_line.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_no_bare_python_launcher.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_nul_boundary_lint.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_paths.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_plan_validator.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_pre_compact.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_precompact_additional.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_precompact_toctou_fixes.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_pricing.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_recall_chunker.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_recall_index.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_references_migration.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_session_utils_additional.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_skill_no_builtin_conflict.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_statusline.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_stop_additional.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_stop_hook.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_stop_precompact_fixes.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_sync_template_stop.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_template_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_usage_ingester.py +0 -0
- {claude_code_conductor-2.54.0 → claude_code_conductor-2.55.0}/tests/test_worktree_guard.py +0 -0
|
@@ -7,8 +7,12 @@ PYTHONPATH に追加してから `from _hook_utils import ...` する経路を
|
|
|
7
7
|
|
|
8
8
|
## Exports
|
|
9
9
|
|
|
10
|
+
- ``norm_component(s)`` — パス成分を正規化する(`.lower().rstrip('. ')`)。
|
|
11
|
+
ディレクトリ比較・拡張子判定の際にケース非依存化・末尾ドット/スペース除去を行う。
|
|
10
12
|
- ``write_debug_log(log_path, line)`` — ``C3_HOOK_DEBUG=1`` のときのみログを追記する
|
|
11
13
|
fail-safe な書き込みヘルパー。
|
|
14
|
+
- ``sanitize_for_terminal(text)`` — 端末出力(stderr / stdout JSON)へ載せる前に
|
|
15
|
+
C0 + DEL + C1 制御文字を除去するヘルパー。
|
|
12
16
|
"""
|
|
13
17
|
|
|
14
18
|
from __future__ import annotations
|
|
@@ -32,6 +36,27 @@ DEBUG_ENV = "C3_HOOK_DEBUG"
|
|
|
32
36
|
_CONTROL_CHARS_RE = re.compile(r"[\x00-\x1f\x7f-\x9f]")
|
|
33
37
|
|
|
34
38
|
|
|
39
|
+
def norm_component(s: str) -> str:
|
|
40
|
+
"""パス成分を正規化する(ケース非依存化・末尾ドット/スペース除去)。
|
|
41
|
+
|
|
42
|
+
Windows・macOS の既定(ケース非依存 FS)では大文字・小文字の違い、
|
|
43
|
+
末尾のドットやスペースは同一実体を示す。複数ディレクトリ・拡張子判定の際に
|
|
44
|
+
`.lower().rstrip('. ')` で正規化してから比較する。
|
|
45
|
+
"""
|
|
46
|
+
return str(s).lower().rstrip('. ')
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def sanitize_for_terminal(text: str) -> str:
|
|
50
|
+
"""端末インジェクション対策: C0 制御文字 / DEL / C1 制御文字を除去する。
|
|
51
|
+
|
|
52
|
+
- 除去範囲は ``_CONTROL_CHARS_RE``(C0 + DEL + C1)と共通。ANSI エスケープの
|
|
53
|
+
ESC (``\\x1b``) と CSI (``\\x9b``) の双方をカバーする。
|
|
54
|
+
- hook が stderr / stdout JSON(additionalContext)へ外部由来の文字列
|
|
55
|
+
(ファイル名など)を載せる前に通す。
|
|
56
|
+
"""
|
|
57
|
+
return _CONTROL_CHARS_RE.sub("", str(text))
|
|
58
|
+
|
|
59
|
+
|
|
35
60
|
def write_debug_log(log_path: Path, line: str) -> None:
|
|
36
61
|
"""``C3_HOOK_DEBUG=1`` のとき、ログファイルに ``ISO8601 line`` を 1 行追記する。
|
|
37
62
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PreToolUse hook: patterns.json への直接編集を事故防止ガード(P1・配布対象)。
|
|
3
|
+
|
|
4
|
+
Write / Edit ツールで `.claude/memory/patterns.json` への書き込みを検出し、
|
|
5
|
+
許可フラグがなければブロックする(TTL 型・600 秒)。正規経路は session.tmp 経由の
|
|
6
|
+
promote-pattern skill。意図的な編集は `.claude/state/patterns_guard_allow.flag` で
|
|
7
|
+
一時許可できる。
|
|
8
|
+
|
|
9
|
+
## 射程の明記(設計の限界)
|
|
10
|
+
|
|
11
|
+
- 対象: Write / Edit ツール呼び出しのみ
|
|
12
|
+
- 対象外: Bash(python -c・sed 等)/ NotebookEdit 経由の書き込み
|
|
13
|
+
- worktree 実行時: 基準が main/worktree のどちらになるかは環境解決に依存
|
|
14
|
+
(本 hook の基準は main の patterns.json 本体の事故防止。main 側保護が
|
|
15
|
+
保護されない場合は検知が働かない限界あり)
|
|
16
|
+
- resolve() による大小文字の正準化は実在ファイルのみ(Windows nt._getfinalpathname)。
|
|
17
|
+
patterns.json が未作成の初期環境(c3 init 直後)ではケース違い指定が素通りする
|
|
18
|
+
- Windows の末尾ドット・スペース正規化も実在パス限定。非実在パスでは本 hook が検知できない限界
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
import time
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
# 共通ヘルパー (_hook_utils.sanitize_for_terminal) を hooks/ 経由で import するため、
|
|
28
|
+
# このスクリプトのディレクトリを PYTHONPATH に追加する。
|
|
29
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
30
|
+
from _hook_utils import sanitize_for_terminal # noqa: E402
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
34
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
35
|
+
except AttributeError:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
# 許可フラグの有効期間(秒)。判定式はこの定数を参照する。
|
|
39
|
+
TTL_SECONDS = 600
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main():
|
|
43
|
+
# bypass: 恒久 disable 環境変数の確認
|
|
44
|
+
if os.environ.get('C3_PATTERNS_GUARD_DISABLE') == '1':
|
|
45
|
+
sys.exit(0)
|
|
46
|
+
|
|
47
|
+
# JSON payload の読み込み(fail-open)
|
|
48
|
+
try:
|
|
49
|
+
payload = json.loads(sys.stdin.read())
|
|
50
|
+
except (json.JSONDecodeError, ValueError):
|
|
51
|
+
sys.exit(0)
|
|
52
|
+
|
|
53
|
+
# tool_name の確認(Write/Edit 以外は対象外)
|
|
54
|
+
tool_name = payload.get('tool_name', '')
|
|
55
|
+
if tool_name not in ('Write', 'Edit'):
|
|
56
|
+
sys.exit(0)
|
|
57
|
+
|
|
58
|
+
# file_path の抽出(キー欠落は fail-open)
|
|
59
|
+
file_path = payload.get('tool_input', {}).get('file_path', '')
|
|
60
|
+
if not file_path:
|
|
61
|
+
sys.exit(0)
|
|
62
|
+
|
|
63
|
+
# プロジェクトルートと保護対象パスの導出(Path(__file__) ベース)
|
|
64
|
+
hook_dir = Path(__file__).resolve().parent
|
|
65
|
+
project_root = hook_dir.parent.parent
|
|
66
|
+
flag_path = project_root / ".claude" / "state" / "patterns_guard_allow.flag"
|
|
67
|
+
|
|
68
|
+
# file_path と保護対象を同じ正規化(realpath)にそろえて比較する。
|
|
69
|
+
# NUL byte 混入など不正なパス文字列は OSError / ValueError になるため fail-open。
|
|
70
|
+
try:
|
|
71
|
+
protected_path = (
|
|
72
|
+
project_root / ".claude" / "memory" / "patterns.json"
|
|
73
|
+
).resolve()
|
|
74
|
+
if os.path.isabs(file_path):
|
|
75
|
+
resolved = Path(file_path).resolve()
|
|
76
|
+
else:
|
|
77
|
+
# 相対パスは CWD を基準に解決(worktree 実行時は worktree CWD)
|
|
78
|
+
resolved = (Path.cwd() / file_path).resolve()
|
|
79
|
+
except (OSError, ValueError):
|
|
80
|
+
sys.exit(0)
|
|
81
|
+
|
|
82
|
+
# realpath 一致チェック
|
|
83
|
+
if resolved != protected_path:
|
|
84
|
+
sys.exit(0)
|
|
85
|
+
|
|
86
|
+
# patterns.json への書き込みが検出された → フラグチェック
|
|
87
|
+
# フラグが存在し TTL 内なら allow(削除しない=複数操作可)
|
|
88
|
+
#
|
|
89
|
+
# TOCTOU 免責: exists() / getmtime() / unlink() の間にフラグが差し替えられる
|
|
90
|
+
# 競合は原理的に残る。本 hook は「事故防止ガード」であり悪意ある同時操作に対する
|
|
91
|
+
# セキュリティ境界ではないため、この競合は許容する。
|
|
92
|
+
if flag_path.exists():
|
|
93
|
+
mtime = os.path.getmtime(flag_path)
|
|
94
|
+
age = time.time() - mtime
|
|
95
|
+
if age <= TTL_SECONDS:
|
|
96
|
+
# TTL 内: 許可
|
|
97
|
+
sys.exit(0)
|
|
98
|
+
else:
|
|
99
|
+
# TTL 超過: フラグを削除して block
|
|
100
|
+
try:
|
|
101
|
+
flag_path.unlink()
|
|
102
|
+
except OSError:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
# ブロック: stderr と exit 2
|
|
106
|
+
flag_create_instruction = (
|
|
107
|
+
f".claude/state/patterns_guard_allow.flag を作成してください(空ファイルで可)。\n"
|
|
108
|
+
f" 例 Bash: touch {sanitize_for_terminal(str(flag_path))}\n"
|
|
109
|
+
f" 例 PowerShell: New-Item -ItemType File -Force "
|
|
110
|
+
f"{sanitize_for_terminal(str(flag_path))}"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
message = (
|
|
114
|
+
f"[PatternGuard BLOCK] patterns.json への直接編集がブロックされました。\n"
|
|
115
|
+
f"正規経路: session.tmp の「##残タスク」へ記載後、promote-pattern skill を実行してください\n"
|
|
116
|
+
f" または LLM が patterns.json を直接編集する場合は以下を実行:\n"
|
|
117
|
+
f"{flag_create_instruction}\n"
|
|
118
|
+
f"恒久 bypass: C3_PATTERNS_GUARD_DISABLE=1 環境変数を設定してください"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
print(message, file=sys.stderr)
|
|
122
|
+
sys.exit(2)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == '__main__':
|
|
126
|
+
main()
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PostToolUse hook: タイムスタンプ報告契約の検査(P2・配布対象)。
|
|
3
|
+
|
|
4
|
+
`.claude/reports/` 直下への Write で、以下 4 prefix のタイムスタンプ契約逸脱を warn する:
|
|
5
|
+
- requirements-report-
|
|
6
|
+
- architecture-report-
|
|
7
|
+
- plan-report-
|
|
8
|
+
- design-review-report-
|
|
9
|
+
|
|
10
|
+
判定は basename 全体のフルマッチ `^{prefix}\\d{8}-\\d{6}\\.md$` で行う
|
|
11
|
+
(prefix 一致と suffix 一致の独立 2 条件ではない。`plan-report-badname-extra-20260725-180252.md`
|
|
12
|
+
のように prefix と タイムスタンプの間へ任意文字列を挟む名前も逸脱として検出するため)。
|
|
13
|
+
不一致の場合は 2 経路で警告: stderr(人間向け)+ stdout JSON(LLM向け)。
|
|
14
|
+
|
|
15
|
+
## ディレクトリ判定とケース非依存化
|
|
16
|
+
|
|
17
|
+
reports ディレクトリの判定は `.claude/reports/` 直下かどうかを名前ベースで確認する。
|
|
18
|
+
Windows・macOS の既定(ケース非依存 FS)では `.CLAUDE` や `REPORTS` は `.claude` / `reports` と
|
|
19
|
+
同一実体の別名であり、名前比較は `.lower()` を使用してケース非依存で行う。
|
|
20
|
+
basename(prefix・タイムスタンプ・`.md`)の判定は厳密に行う(`re.ASCII` で Unicode 偽装を防止)。
|
|
21
|
+
|
|
22
|
+
## 出力契約(PostToolUse warn の標準形)
|
|
23
|
+
|
|
24
|
+
exit 0 でも stdout JSON が LLM コンテキストへ additionalContext として注入される。
|
|
25
|
+
stderr は人間にのみ届く。詳細は Claude Code 公式仕様を参照:
|
|
26
|
+
https://code.claude.com/docs/en/hooks
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import re
|
|
32
|
+
import sys
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
# 共通ヘルパー (_hook_utils.sanitize_for_terminal, norm_component) を hooks/ 経由で import するため、
|
|
36
|
+
# このスクリプトのディレクトリを PYTHONPATH に追加する。
|
|
37
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
38
|
+
from _hook_utils import norm_component, sanitize_for_terminal # noqa: E402
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
42
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
43
|
+
except AttributeError:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def main():
|
|
48
|
+
# JSON payload の読み込み(fail-open)
|
|
49
|
+
try:
|
|
50
|
+
payload = json.loads(sys.stdin.read())
|
|
51
|
+
except (json.JSONDecodeError, ValueError):
|
|
52
|
+
sys.exit(0)
|
|
53
|
+
|
|
54
|
+
# tool_name の確認(Write 以外は対象外=新規命名は Write でのみ発生)
|
|
55
|
+
tool_name = payload.get('tool_name', '')
|
|
56
|
+
if tool_name != 'Write':
|
|
57
|
+
sys.exit(0)
|
|
58
|
+
|
|
59
|
+
# file_path の抽出(キー欠落は fail-open)
|
|
60
|
+
file_path = payload.get('tool_input', {}).get('file_path', '')
|
|
61
|
+
if not file_path:
|
|
62
|
+
sys.exit(0)
|
|
63
|
+
|
|
64
|
+
# `.claude/reports/` 直下かどうかの判定
|
|
65
|
+
path_obj = Path(file_path)
|
|
66
|
+
basename = path_obj.name
|
|
67
|
+
parent = path_obj.parent
|
|
68
|
+
|
|
69
|
+
# 構造判定: 親が `reports` かつ祖父が `.claude`(= `.claude/reports/` 直下)。
|
|
70
|
+
# 上流に同名の `reports/` があっても誤アンカーしない(resolve は使わない)。
|
|
71
|
+
# ケース非依存 FS(Windows・macOS)では `.CLAUDE` や `REPORTS` は `.claude` / `reports` と
|
|
72
|
+
# 同一実体の別名のため、比較は norm_component() で正規化してから行う。
|
|
73
|
+
# Windows の末尾ドット・スペースも norm_component() が除去する。
|
|
74
|
+
# 例: `/foo/.claude/reports/plan-report-20260725-180252.md` → 対象
|
|
75
|
+
# 例: `/foo/.claude/reports/archive/plan-report-final.md` → 対象外(直下でない)
|
|
76
|
+
if norm_component(parent.name) != 'reports' or norm_component(parent.parent.name) != '.claude':
|
|
77
|
+
sys.exit(0)
|
|
78
|
+
|
|
79
|
+
# strict-4: 対象 prefix の確認
|
|
80
|
+
strict4_prefixes = [
|
|
81
|
+
'requirements-report-',
|
|
82
|
+
'architecture-report-',
|
|
83
|
+
'plan-report-',
|
|
84
|
+
'design-review-report-',
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
matched_prefix = None
|
|
88
|
+
for prefix in strict4_prefixes:
|
|
89
|
+
if basename.startswith(prefix):
|
|
90
|
+
matched_prefix = prefix
|
|
91
|
+
break
|
|
92
|
+
if matched_prefix is None:
|
|
93
|
+
sys.exit(0)
|
|
94
|
+
|
|
95
|
+
# basename 全体のフルマッチ確認(`{prefix}YYYYMMDD-HHMMSS.md`)
|
|
96
|
+
# re.ASCII を指定してタイムスタンプを ASCII 数字に限定し、全角数字による偽装を防止する。
|
|
97
|
+
contract_pattern = re.compile(re.escape(matched_prefix) + r'\d{8}-\d{6}\.md', re.ASCII)
|
|
98
|
+
if contract_pattern.fullmatch(basename):
|
|
99
|
+
# 正しい形式
|
|
100
|
+
sys.exit(0)
|
|
101
|
+
|
|
102
|
+
# 違反: 警告を出す(2 経路)
|
|
103
|
+
# stderr(人間向け)
|
|
104
|
+
print(
|
|
105
|
+
f'[ReportContract WARN] {sanitize_for_terminal(basename)} '
|
|
106
|
+
f'のファイル名形式が違反しています。'
|
|
107
|
+
f'期待形式: {{prefix}}YYYYMMDD-HHMMSS.md\n'
|
|
108
|
+
f'採番は report-timestamp skill を使用してください。',
|
|
109
|
+
file=sys.stderr
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# stdout JSON(LLM向け)
|
|
113
|
+
context = (
|
|
114
|
+
f'ファイル名: {sanitize_for_terminal(basename)}\n'
|
|
115
|
+
f'期待形式: {{prefix}}YYYYMMDD-HHMMSS.md\n'
|
|
116
|
+
f'採番方法: report-timestamp skill で取得したタイムスタンプを使用してください。'
|
|
117
|
+
)
|
|
118
|
+
output = {
|
|
119
|
+
'hookSpecificOutput': {
|
|
120
|
+
'hookEventName': 'PostToolUse',
|
|
121
|
+
'additionalContext': context
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
print(json.dumps(output), file=sys.stdout)
|
|
125
|
+
|
|
126
|
+
sys.exit(0)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
if __name__ == '__main__':
|
|
130
|
+
main()
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PostToolUse hook: session.tmp のモード行挿入・plan= 差し替えを監視(P3・配布対象)。
|
|
3
|
+
|
|
4
|
+
Edit ツールで `sessions/*.tmp` ファイルを検出し、`^モード: 自律` / `^モード: HITL` 行の
|
|
5
|
+
挿入、複数行にわたる plan= 差し替え、実効モード行の状態遷移を警告する。
|
|
6
|
+
削除(HITL 復帰)・cycles= 更新は沈黙。
|
|
7
|
+
|
|
8
|
+
## 対象パス判定とケース非依存化
|
|
9
|
+
|
|
10
|
+
ファイルパスが `sessions/` を含み `.tmp` で終わるかを判定する。
|
|
11
|
+
Windows・macOS の既定(ケース非依存 FS)では `Sessions` や `.TMP` は `sessions` / `.tmp` と
|
|
12
|
+
同一実体の別名であり、判定はメンバシップ検査・拡張子比較ともに `.lower()` でケース非依存で行う。
|
|
13
|
+
否定形の構造自体は変わらない(各成分に `.lower()` を適用するのみ)。
|
|
14
|
+
|
|
15
|
+
## 判定の SSOT(state transition table) — 複数モード行への拡張
|
|
16
|
+
|
|
17
|
+
実効モード行は「最初の `^モード: ` 行」(HITL 行を含む。init-session/autonomous-mode が
|
|
18
|
+
`grep -m1 '^モード: '` で読む意味論)。警告判定は以下 3 条件の OR:
|
|
19
|
+
1. (a) 挿入: old に `^モード: 自律` 行が 0 本、new に 1 本以上
|
|
20
|
+
2. (b) 新出値: new の有効 plan= 値集合に old の集合に無い値がある
|
|
21
|
+
3. (c) 実効行遷移: 各側の実効行(最初の `^モード: ` 行。HITL/行なしは「行なし」に写像)の
|
|
22
|
+
ペアに単一行の状態遷移表を適用し、表が警告と定める遷移に該当
|
|
23
|
+
|
|
24
|
+
検知種別は (c) が警告なら表の種別を採用、それ以外は (a) / (b) の順。
|
|
25
|
+
|
|
26
|
+
| old実効 | new実効 | 動作 |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| 行なし | 行あり(E/N) | 挿入として警告 |
|
|
29
|
+
| 行あり | 行なし | 沈黙(削除・HITL 復帰) |
|
|
30
|
+
| E | E(同値) | 沈黙(cycles= 更新等) |
|
|
31
|
+
| E | E(異値) | 差し替えとして警告 |
|
|
32
|
+
| N | E | 差し替えとして警告(無効→有効昇格) |
|
|
33
|
+
| E | N | 沈黙(有効→無効降格) |
|
|
34
|
+
| N | N | 沈黙 |
|
|
35
|
+
| 行なし | 行なし | 沈黙 |
|
|
36
|
+
|
|
37
|
+
## 出力契約
|
|
38
|
+
|
|
39
|
+
2 経路: stderr(人間向け)+ stdout JSON(LLM向け)。
|
|
40
|
+
exit 0 でも stdout JSON が LLM コンテキストへ注入される。
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
import json
|
|
44
|
+
import os
|
|
45
|
+
import re
|
|
46
|
+
import sys
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
|
|
49
|
+
# 共通ヘルパー (_hook_utils.sanitize_for_terminal, norm_component) を hooks/ 経由で import するため、
|
|
50
|
+
# このスクリプトのディレクトリを PYTHONPATH に追加する。
|
|
51
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
52
|
+
from _hook_utils import norm_component, sanitize_for_terminal # noqa: E402
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
56
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
57
|
+
except AttributeError:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _find_mode_lines(text: str) -> list[str]:
|
|
62
|
+
"""テキストから `^モード: 自律` にマッチする行を全て探す(複数行対応)。"""
|
|
63
|
+
lines = text.split('\n')
|
|
64
|
+
result = []
|
|
65
|
+
for line in lines:
|
|
66
|
+
if re.match(r'^モード: 自律', line):
|
|
67
|
+
result.append(line)
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _find_effective_mode_line(text: str) -> str | None:
|
|
72
|
+
"""
|
|
73
|
+
テキストから実効モード行を取得する。
|
|
74
|
+
|
|
75
|
+
実効モード行は「最初の `^モード: ` 行」(HITL を含む)。
|
|
76
|
+
init-session/autonomous-mode SKILL.md の `grep -m1 '^モード: '` に対応。
|
|
77
|
+
モード行なしなら None を返す。
|
|
78
|
+
"""
|
|
79
|
+
lines = text.split('\n')
|
|
80
|
+
for line in lines:
|
|
81
|
+
if re.match(r'^モード: ', line):
|
|
82
|
+
return line
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _extract_plan_value(mode_line: str) -> tuple[str, bool]:
|
|
87
|
+
"""
|
|
88
|
+
モード行から plan= の値を抽出する。
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
(value, is_extractable): value は抽出値、is_extractable は抽出成功の可否
|
|
92
|
+
- 正規行(E): value は plan= 値、is_extractable = True
|
|
93
|
+
- plan= 欠落(N): value = "", is_extractable = False
|
|
94
|
+
- unclosed quote(N): value = "", is_extractable = False
|
|
95
|
+
"""
|
|
96
|
+
# plan= を探す
|
|
97
|
+
match = re.search(r'plan=', mode_line)
|
|
98
|
+
if not match:
|
|
99
|
+
# plan= 欠落(N)
|
|
100
|
+
return "", False
|
|
101
|
+
|
|
102
|
+
rest = mode_line[match.end():]
|
|
103
|
+
|
|
104
|
+
# plan= 直後が引用符の場合
|
|
105
|
+
if rest.startswith('"'):
|
|
106
|
+
# 次の引用符を探す
|
|
107
|
+
close_quote_idx = rest.find('"', 1)
|
|
108
|
+
if close_quote_idx == -1:
|
|
109
|
+
# unclosed quote(N)
|
|
110
|
+
return "", False
|
|
111
|
+
# 引用符に囲まれた値(E)
|
|
112
|
+
value = rest[1:close_quote_idx]
|
|
113
|
+
return value, True
|
|
114
|
+
|
|
115
|
+
# 引用符なしの場合: \s+cycles= または行末まで
|
|
116
|
+
# SSOT(mode_line.py:89): \s+cycles= の終端
|
|
117
|
+
cycles_match = re.search(r'\s+cycles=', rest)
|
|
118
|
+
if cycles_match:
|
|
119
|
+
# 値は rest の開始から cycles= 前の空白前まで(strip)
|
|
120
|
+
value = rest[:cycles_match.start()].strip()
|
|
121
|
+
else:
|
|
122
|
+
# 行末まで(strip)
|
|
123
|
+
value = rest.strip()
|
|
124
|
+
|
|
125
|
+
return value, True
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def main():
|
|
129
|
+
# JSON payload の読み込み(fail-open)
|
|
130
|
+
try:
|
|
131
|
+
payload = json.loads(sys.stdin.read())
|
|
132
|
+
except (json.JSONDecodeError, ValueError):
|
|
133
|
+
sys.exit(0)
|
|
134
|
+
|
|
135
|
+
# tool_name の確認(Edit のみ・Write は対象外)
|
|
136
|
+
tool_name = payload.get('tool_name', '')
|
|
137
|
+
if tool_name != 'Edit':
|
|
138
|
+
sys.exit(0)
|
|
139
|
+
|
|
140
|
+
# file_path の抽出(キー欠落は fail-open)
|
|
141
|
+
file_path = payload.get('tool_input', {}).get('file_path', '')
|
|
142
|
+
if not file_path:
|
|
143
|
+
sys.exit(0)
|
|
144
|
+
|
|
145
|
+
# `sessions/*.tmp` パスかどうかの判定
|
|
146
|
+
path_obj = Path(file_path)
|
|
147
|
+
parts = path_obj.parts
|
|
148
|
+
basename = path_obj.name
|
|
149
|
+
|
|
150
|
+
# sessions を含むかつ .tmp で終わるかの確認(ケース非依存)
|
|
151
|
+
# ケース非依存 FS では Sessions や .TMP も対象に含める。
|
|
152
|
+
# パス成分は norm_component() で正規化してから比較(末尾ドット・スペース除去も含む)。
|
|
153
|
+
# basename の拡張子判定も norm_component() でファイル名全体を正規化してから endswith() を適用する。
|
|
154
|
+
if not any(norm_component(p) == 'sessions' for p in parts) or not norm_component(basename).endswith('.tmp'):
|
|
155
|
+
sys.exit(0)
|
|
156
|
+
|
|
157
|
+
# old_string / new_string の抽出(キー欠落は fail-open)
|
|
158
|
+
tool_input = payload.get('tool_input', {})
|
|
159
|
+
old_string = tool_input.get('old_string', '')
|
|
160
|
+
new_string = tool_input.get('new_string', '')
|
|
161
|
+
|
|
162
|
+
# モード行の検出 — F9 では全行走査
|
|
163
|
+
old_mode_lines = _find_mode_lines(old_string) # 自律行のみ
|
|
164
|
+
new_mode_lines = _find_mode_lines(new_string) # 自律行のみ
|
|
165
|
+
old_effective_line = _find_effective_mode_line(old_string) # 最初のモード: 行(HITL含む)
|
|
166
|
+
new_effective_line = _find_effective_mode_line(new_string) # 最初のモード: 行
|
|
167
|
+
|
|
168
|
+
# 3 条件を全て評価してから優先度で判定する(F10 修正)
|
|
169
|
+
# 優先順位: (c) 実効行遷移 > (a) 挿入 > (b) 新出値
|
|
170
|
+
should_warn_c = False
|
|
171
|
+
warn_kind_c = ''
|
|
172
|
+
should_warn_a = False
|
|
173
|
+
should_warn_b = False
|
|
174
|
+
|
|
175
|
+
# (c) 実効行遷移: 実効行ペアに単一行の状態遷移表を適用
|
|
176
|
+
# 実効行の状態を求める
|
|
177
|
+
old_eff_state = None
|
|
178
|
+
if old_effective_line is not None:
|
|
179
|
+
if re.match(r'^モード: 自律', old_effective_line):
|
|
180
|
+
# 自律行: E/N を判定
|
|
181
|
+
old_eff_state = 'E' if _extract_plan_value(old_effective_line)[1] else 'N'
|
|
182
|
+
# else: HITL 行は「行なし」に写像
|
|
183
|
+
|
|
184
|
+
new_eff_state = None
|
|
185
|
+
if new_effective_line is not None:
|
|
186
|
+
if re.match(r'^モード: 自律', new_effective_line):
|
|
187
|
+
# 自律行: E/N を判定
|
|
188
|
+
new_eff_state = 'E' if _extract_plan_value(new_effective_line)[1] else 'N'
|
|
189
|
+
# else: HITL 行は「行なし」に写像
|
|
190
|
+
|
|
191
|
+
# 状態遷移表に基づいて判定
|
|
192
|
+
if old_eff_state is None and new_eff_state is not None:
|
|
193
|
+
# 行なし → E/N: 挿入として警告
|
|
194
|
+
should_warn_c = True
|
|
195
|
+
warn_kind_c = '挿入'
|
|
196
|
+
elif old_eff_state == 'E' and new_eff_state == 'E':
|
|
197
|
+
# E → E: 値が異なれば警告
|
|
198
|
+
old_value, _ = _extract_plan_value(old_effective_line)
|
|
199
|
+
new_value, _ = _extract_plan_value(new_effective_line)
|
|
200
|
+
if old_value != new_value:
|
|
201
|
+
should_warn_c = True
|
|
202
|
+
warn_kind_c = '差し替え'
|
|
203
|
+
elif old_eff_state == 'N' and new_eff_state == 'E':
|
|
204
|
+
# N → E: 無効→有効昇格は警告
|
|
205
|
+
should_warn_c = True
|
|
206
|
+
warn_kind_c = '差し替え'
|
|
207
|
+
|
|
208
|
+
# (a) 挿入: old に自律行が 0 本、new に 1 本以上
|
|
209
|
+
if len(old_mode_lines) == 0 and len(new_mode_lines) >= 1:
|
|
210
|
+
should_warn_a = True
|
|
211
|
+
|
|
212
|
+
# (b) 新出値: new の有効 plan= 値集合に old に無い値
|
|
213
|
+
if len(new_mode_lines) > 0:
|
|
214
|
+
# old 側の有効値集合を収集
|
|
215
|
+
old_values = set()
|
|
216
|
+
for line in old_mode_lines:
|
|
217
|
+
value, is_extractable = _extract_plan_value(line)
|
|
218
|
+
if is_extractable:
|
|
219
|
+
old_values.add(value)
|
|
220
|
+
|
|
221
|
+
# new 側の有効値集合を収集
|
|
222
|
+
new_values = set()
|
|
223
|
+
for line in new_mode_lines:
|
|
224
|
+
value, is_extractable = _extract_plan_value(line)
|
|
225
|
+
if is_extractable:
|
|
226
|
+
new_values.add(value)
|
|
227
|
+
|
|
228
|
+
# new に old に無い値があるか
|
|
229
|
+
if new_values - old_values:
|
|
230
|
+
should_warn_b = True
|
|
231
|
+
|
|
232
|
+
# 優先度で判定: (c) > (a) > (b)
|
|
233
|
+
should_warn = False
|
|
234
|
+
warn_kind = ''
|
|
235
|
+
if should_warn_c:
|
|
236
|
+
should_warn = True
|
|
237
|
+
warn_kind = warn_kind_c
|
|
238
|
+
elif should_warn_a:
|
|
239
|
+
should_warn = True
|
|
240
|
+
warn_kind = '挿入'
|
|
241
|
+
elif should_warn_b:
|
|
242
|
+
should_warn = True
|
|
243
|
+
warn_kind = '差し替え'
|
|
244
|
+
|
|
245
|
+
if not should_warn:
|
|
246
|
+
# 沈黙
|
|
247
|
+
sys.exit(0)
|
|
248
|
+
|
|
249
|
+
# 警告を出す(2 経路)
|
|
250
|
+
# stderr(人間向け)
|
|
251
|
+
print(
|
|
252
|
+
f'[SessionModeWatch WARN] {sanitize_for_terminal(basename)} '
|
|
253
|
+
f'のモード行に {warn_kind} が検出されました。',
|
|
254
|
+
file=sys.stderr
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# stdout JSON(LLM向け)
|
|
258
|
+
context = (
|
|
259
|
+
f'ファイル: {sanitize_for_terminal(basename)}\n'
|
|
260
|
+
f'検知種別: {warn_kind}\n'
|
|
261
|
+
f'確認: 委任プラン承認直後の挿入か、承認済みプランへの変更かご確認ください。'
|
|
262
|
+
)
|
|
263
|
+
output = {
|
|
264
|
+
'hookSpecificOutput': {
|
|
265
|
+
'hookEventName': 'PostToolUse',
|
|
266
|
+
'additionalContext': context
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
print(json.dumps(output), file=sys.stdout)
|
|
270
|
+
|
|
271
|
+
sys.exit(0)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
if __name__ == '__main__':
|
|
275
|
+
main()
|
|
@@ -79,6 +79,11 @@
|
|
|
79
79
|
"type": "command",
|
|
80
80
|
"command": "c3",
|
|
81
81
|
"args": ["run", "${CLAUDE_PROJECT_DIR}/.claude/hooks/worktree_guard.py"]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"type": "command",
|
|
85
|
+
"command": "c3",
|
|
86
|
+
"args": ["run", "${CLAUDE_PROJECT_DIR}/.claude/hooks/patterns_guard.py"]
|
|
82
87
|
}
|
|
83
88
|
]
|
|
84
89
|
},
|
|
@@ -89,6 +94,11 @@
|
|
|
89
94
|
"type": "command",
|
|
90
95
|
"command": "c3",
|
|
91
96
|
"args": ["run", "${CLAUDE_PROJECT_DIR}/.claude/hooks/worktree_guard.py"]
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"type": "command",
|
|
100
|
+
"command": "c3",
|
|
101
|
+
"args": ["run", "${CLAUDE_PROJECT_DIR}/.claude/hooks/patterns_guard.py"]
|
|
92
102
|
}
|
|
93
103
|
]
|
|
94
104
|
},
|
|
@@ -121,6 +131,11 @@
|
|
|
121
131
|
"type": "command",
|
|
122
132
|
"command": "c3",
|
|
123
133
|
"args": ["run", "${CLAUDE_PROJECT_DIR}/.claude/hooks/planner_check.py"]
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"type": "command",
|
|
137
|
+
"command": "c3",
|
|
138
|
+
"args": ["run", "${CLAUDE_PROJECT_DIR}/.claude/hooks/report_contract_check.py"]
|
|
124
139
|
}
|
|
125
140
|
]
|
|
126
141
|
},
|
|
@@ -136,6 +151,11 @@
|
|
|
136
151
|
"type": "command",
|
|
137
152
|
"command": "c3",
|
|
138
153
|
"args": ["run", "${CLAUDE_PROJECT_DIR}/.claude/hooks/planner_check.py"]
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"type": "command",
|
|
157
|
+
"command": "c3",
|
|
158
|
+
"args": ["run", "${CLAUDE_PROJECT_DIR}/.claude/hooks/session_mode_watch.py"]
|
|
139
159
|
}
|
|
140
160
|
]
|
|
141
161
|
}
|
|
@@ -114,6 +114,13 @@ trust_score: {スコア}
|
|
|
114
114
|
|
|
115
115
|
## Step 6: patterns.json を更新する
|
|
116
116
|
|
|
117
|
+
**注意:** patterns.json への直接編集は patterns_guard.py hook でブロックされます。以下の順序で実行してください:
|
|
118
|
+
|
|
119
|
+
1. まず `.claude/state/patterns_guard_allow.flag` を作成します(空ファイルで可):
|
|
120
|
+
- Bash: `touch .claude/state/patterns_guard_allow.flag`
|
|
121
|
+
- PowerShell: `New-Item -ItemType File -Force .claude/state/patterns_guard_allow.flag`
|
|
122
|
+
2. その直後に patterns.json を Edit ツールで更新します(TTL 600 秒以内)
|
|
123
|
+
|
|
117
124
|
昇格したパターンの entry に以下を追加する:
|
|
118
125
|
|
|
119
126
|
rule の場合:
|
|
@@ -114,9 +114,13 @@ A ヒアリング → B 設計 → C 計画 → D 実装(TDD) → E レビュー
|
|
|
114
114
|
| `UserPromptSubmit` | (全て) | `recall_inject.py` | 過去類似情報を意味検索しコンテキスト注入([§4](#4-知能基盤)) |
|
|
115
115
|
| `PreToolUse` | `Bash` | `pre_tool.py` | 破壊的コマンド(`rm -rf` 等)ブロック・secret スキャン |
|
|
116
116
|
| `PreToolUse` | `Write` / `Edit` | `worktree_guard.py` | worktree 外への書き込み禁止(`PO_WORKTREE_GUARD=1` 設定時のみ作動。parallel-agents が並列実行時に有効化) |
|
|
117
|
+
| `PreToolUse` | `Write` / `Edit` | `patterns_guard.py` | patterns.json への直接編集を block(許可フラグ TTL 600 秒・C3_PATTERNS_GUARD_DISABLE=1 で無効化) |
|
|
117
118
|
| `PreToolUse` | `Agent` | `check_agent_invocation.py` | エージェント起動の検査 |
|
|
119
|
+
| `PreToolUse` | `Agent` | `tier_autoapply.py` | 推奨 Tier の `model:` 自動注入(既存登録の表への追随・乖離是正) |
|
|
118
120
|
| `PostToolUse` | `Write` / `Edit` | `post_tool.py` | skills 変更通知・品質パターン(TODO 等)スキャン |
|
|
119
121
|
| `PostToolUse` | `Write` / `Edit` | `planner_check.py` | plan-report の YAML/タイムスタンプ/reviewer 規約検査 |
|
|
122
|
+
| `PostToolUse` | `Write` | `report_contract_check.py` | reports 直下のタイムスタンプ契約 4 種の命名逸脱を warn(stderr + additionalContext) |
|
|
123
|
+
| `PostToolUse` | `Edit` | `session_mode_watch.py` | session.tmp のモード行挿入・plan= 差し替えを warn(stderr + additionalContext) |
|
|
120
124
|
| `PermissionRequest` | (全て) | `permission_handler.py` | `permission_rules.json` の `auto_allow` で自動承認([config-policy §2 レイヤー B](.claude/docs/config-policy.md)) |
|
|
121
125
|
| `PreCompact` | (全て) | `pre_compact.py` | compact 前にチェックポイントマーカー注入 |
|
|
122
126
|
| `Stop` | (全て) | `session_stop.py` | セッション終了処理のオーケストレータ |
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.55.0] - 2026-07-26
|
|
4
|
+
|
|
5
|
+
### 追加
|
|
6
|
+
|
|
7
|
+
- **`patterns_guard.py`(PreToolUse block・配布対象)**: `.claude/memory/patterns.json` への Write / Edit を exit 2 でブロックする事故防止ガード。過去の直接 Edit 起因の肥大 defect(98KB)の再発防止で、正規経路(session.tmp 経由で `stop.py` が取り込む promote-pattern フロー)は Python 直接 I/O のため影響を受けない。意図的な編集は `.claude/state/patterns_guard_allow.flag`(TTL 600 秒)で一時許可、緊急時は環境変数 `C3_PATTERNS_GUARD_DISABLE=1` で無効化できる。promote-pattern skill の Step 6 にフラグ発行手順を追記。docstring に射程の限界(Bash / NotebookEdit 経由は対象外・非実在パスのケース正準化不可)を明記
|
|
8
|
+
- **`report_contract_check.py`(PostToolUse warn・配布対象)**: `.claude/reports/` 直下への Write で、strict-4 prefix(requirements / architecture / plan / design-review)のタイムスタンプ契約逸脱を警告する。判定は basename 全体のフルマッチ `^{prefix}\d{8}-\d{6}\.md$`(`re.ASCII`・prefix とタイムスタンプの間に任意文字列を挟む名前も検出)。ディレクトリ判定は `.claude/reports/` 直下を名前ベース・ケース非依存(`norm_component`)で行う。警告は stderr(人間向け)+ stdout JSON `additionalContext`(LLM 向け)の 2 経路
|
|
9
|
+
- **`session_mode_watch.py`(PostToolUse warn・配布対象)**: `sessions/*.tmp` への Edit で autonomous-mode のモード行変更を監視する。警告条件は 3 条件の OR — (a) `^モード: 自律` 行の挿入 (b) 有効 plan= 値の新出 (c) 実効モード行(最初の `^モード: ` 行)の状態遷移表該当。削除(HITL 復帰)・cycles= 更新・有効→無効降格は沈黙。opt-in 不変則の事後検知層として機能する(モード行の検証 SSOT は従来どおり `mode_line.py`)
|
|
10
|
+
- **`_hook_utils.py` に共通ヘルパー追加**: `sanitize_for_terminal()`(C0/C1/DEL 制御文字除去・警告文へ埋め込む外部由来文字列の共通サニタイズ)と `norm_component()`(Windows 末尾ドット・スペース正規化+ケース非依存比較)を新設し、新 hook 3 本から共用
|
|
11
|
+
- **`.claude/settings.json` に hook 3 本を登録**: PreToolUse(Write|Edit)に patterns_guard、PostToolUse(Write)に report_contract_check、PostToolUse(Edit)に session_mode_watch。テスト +129 件(フルスイート 2152 → 2281 passed)
|
|
12
|
+
|
|
13
|
+
### 変更
|
|
14
|
+
|
|
15
|
+
- **ARCHITECTURE.md の hook 一覧を実態へ同期**: 新規 3 hook の行を追加し、tier_autoapply の記述乖離を是正
|
|
16
|
+
|
|
17
|
+
### 後方互換
|
|
18
|
+
|
|
19
|
+
- **破壊的変更なし**。patterns.json への直接 Write / Edit はもともと規約違反の経路(taxonomy.md の正規定義・配布元 CLAUDE.md §10)であり、正規経路の promote-pattern フロー・`stop.py` の機械管理は挙動不変。ブロックされた場合も警告文に許可フラグの発行手順が表示され、フラグ / 環境変数で意図的な編集は引き続き可能。P2 / P3 は警告のみ(exit 0)で既存ワークフローを止めない
|
|
20
|
+
|
|
3
21
|
## [2.54.0] - 2026-07-24
|
|
4
22
|
|
|
5
23
|
### 追加
|