claude-code-conductor 2.55.0__tar.gz → 2.55.1__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.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/check_agent_invocation.py +4 -3
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/patterns_guard.py +1 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/planner_check.py +4 -3
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/post_tool.py +1 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/pre_compact.py +17 -4
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/pre_tool.py +6 -2
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/recall_autorebuild.py +3 -1
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/report_contract_check.py +1 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/session_mode_watch.py +1 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/tier_autoapply.py +5 -3
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/worktree_guard.py +6 -2
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/CHANGELOG.md +20 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/PKG-INFO +1 -1
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/__init__.py +1 -1
- claude_code_conductor-2.55.1/tests/test_hooks_stdin_idiom.py +357 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_session_mode_watch.py +142 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/CLAUDE.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/architect.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/code-reviewer.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/design-critic.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/developer.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/doc-writer.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/interviewer.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/planner.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/project-setup.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/security-reviewer.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/systematic-debugger.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/tester.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/wt_developer.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/wt_systematic-debugger.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/agents/wt_tester.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/breaking-changes.txt +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/deletions.txt +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/docs/autonomous-mode-onboarding.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/docs/config-policy.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/docs/nul-boundary.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/docs/parallel-agents-setup.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/docs/platform-adapters.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/docs/settings.json.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/_hook_utils.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/consolidate_memory.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/permission_handler.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/permission_handler_toast.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/recall_inject.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/restore_session.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/select_tier.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/session_start.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/session_stop.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/session_utils.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/statusline.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/stop.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/tier_gap_check.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/memory/.gitkeep +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/permission_rules.json +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/rules/promoted/index.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/settings.json +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/autonomous-mode/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/autonomous-mode/scripts/mode_line.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/brainstorm/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/codex-review/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/references/design-critic-rubric.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/references/design-rubric.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/references/interview-rubric.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/scripts/record_agent_outcome.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/develop/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/doc/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/extract-lib/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/init-session/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/init-session/scripts/session_guard.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/mcp-config/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/parallel-agents/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/pattern-status/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/promote-pattern/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/recall/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/report-timestamp/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/review-phase/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/setup/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/setup/reference.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/skills/start/SKILL.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/state/.gitkeep +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.gitignore +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/ARCHITECTURE.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/LICENSE +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/LICENSES/fastembed-LICENSE +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/LICENSES/fastembed-NOTICE +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/LICENSES/numpy-LICENSE +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/LICENSES/onnxruntime-LICENSE +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/README.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/hatch_build.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/pyproject.toml +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/__main__.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/_db_params.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/_excludes.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/_terminal.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/adapters.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_ask.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_doctor.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_init.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_list.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_metrics.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_plan.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_recall.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_run.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_tier.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/cli_update.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/db.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/embedding.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/gitutil.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/mcp_server.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrate.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/001_initial.sql +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/003_tier_cost.sql +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/004_agent_outcomes.sql +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/005_drop_agent_tier_bandit.sql +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/006_review_decisions_severity.sql +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/README.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/migrations/__init__.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/paths.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/plan_validator.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/platforms.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/pricing.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/question.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/recall_chunker.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/recall_index.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/src/c3/usage_ingester.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/__init__.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/_pre_compact_helpers.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/conftest.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/fixtures/usage/README.md +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/fixtures/usage/mainline.jsonl +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/__init__.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_check_agent_invocation.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_consolidate_memory.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_hook_utils.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_permission_handler.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_permission_handler_toast.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_planner_check.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_planner_check_dev.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_post_tool.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_pre_tool.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_recall_autorebuild.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_recall_inject.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_record_agent_outcome.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_record_review_decision.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_restore_session.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_review_hint_inject.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_select_tier.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_select_tier_escalation.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_session_start.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_session_stop.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_session_utils.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_settings_tier_autoapply_registration.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_similarity_boost.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_statusline.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_statusline_template_sync.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_sync_check.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_template_guard.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_tier_autoapply.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_tier_gap_check.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/hooks/test_tier_security_fixes.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/__init__.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/_skill_helpers.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_init_session_no_task_type.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_marker_compliance.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_parallel_agents_skill_tier.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_planner_lightweight.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_recall_skill.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_session_backlog_reconciliation.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_session_guard.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_setup_templates.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_start_skill_new_flow.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_adapters.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_check_deletions.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_ask.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_doctor.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_doctor_launcher.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_entry.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_init.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_list.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_metrics.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_plan.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_recall.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_run.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_tier.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_update_breaking_changes.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_cli_update_deletions.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_db.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_docstring_consistency.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_embedding.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_excludes.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_extract_breaking_changes.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_gitutil.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_mcp_server_elicit.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_migrate.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_mode_line.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_no_bare_python_launcher.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_nul_boundary_lint.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_paths.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_patterns_guard.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_plan_validator.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_pre_compact.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_precompact_additional.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_precompact_toctou_fixes.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_pricing.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_recall_chunker.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_recall_index.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_references_migration.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_report_contract_check.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_session_utils_additional.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_settings_hooks_registration.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_skill_no_builtin_conflict.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_statusline.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_stop_additional.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_stop_hook.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_stop_precompact_fixes.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_sync_template_stop.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_template_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_usage_ingester.py +0 -0
- {claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/tests/test_worktree_guard.py +0 -0
|
@@ -41,6 +41,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
41
41
|
from _hook_utils import write_debug_log # noqa: E402
|
|
42
42
|
|
|
43
43
|
try:
|
|
44
|
+
sys.stdin.reconfigure(encoding="utf-8")
|
|
44
45
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
45
46
|
sys.stderr.reconfigure(encoding="utf-8")
|
|
46
47
|
except AttributeError:
|
|
@@ -53,8 +54,8 @@ REVIEWER_TYPES = frozenset({"code-reviewer", "security-reviewer"})
|
|
|
53
54
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
54
55
|
DEBUG_LOG_PATH = PROJECT_ROOT / ".claude" / "tmp" / "agent_hook_debug.log"
|
|
55
56
|
|
|
56
|
-
# DoS
|
|
57
|
-
|
|
57
|
+
# DoS 防御の読み取り上限(文字数ベース)。hook はローカル前提だが多層防御として明示する。
|
|
58
|
+
_STDIN_READ_LIMIT_CHARS = 1 * 1024 * 1024 # 1M chars
|
|
58
59
|
|
|
59
60
|
|
|
60
61
|
def _escape_for_log(value: str) -> str:
|
|
@@ -64,7 +65,7 @@ def _escape_for_log(value: str) -> str:
|
|
|
64
65
|
|
|
65
66
|
def main() -> None:
|
|
66
67
|
try:
|
|
67
|
-
payload = json.loads(sys.stdin.read(
|
|
68
|
+
payload = json.loads(sys.stdin.read(_STDIN_READ_LIMIT_CHARS))
|
|
68
69
|
except (json.JSONDecodeError, ValueError):
|
|
69
70
|
sys.exit(0)
|
|
70
71
|
|
{claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/patterns_guard.py
RENAMED
|
@@ -30,6 +30,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
30
30
|
from _hook_utils import sanitize_for_terminal # noqa: E402
|
|
31
31
|
|
|
32
32
|
try:
|
|
33
|
+
sys.stdin.reconfigure(encoding='utf-8')
|
|
33
34
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
34
35
|
sys.stderr.reconfigure(encoding='utf-8')
|
|
35
36
|
except AttributeError:
|
{claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/planner_check.py
RENAMED
|
@@ -41,6 +41,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
41
41
|
from _hook_utils import write_debug_log # noqa: E402
|
|
42
42
|
|
|
43
43
|
try:
|
|
44
|
+
sys.stdin.reconfigure(encoding="utf-8")
|
|
44
45
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
45
46
|
sys.stderr.reconfigure(encoding="utf-8")
|
|
46
47
|
except AttributeError:
|
|
@@ -58,8 +59,8 @@ except ImportError:
|
|
|
58
59
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
59
60
|
DEBUG_LOG_PATH = PROJECT_ROOT / ".claude" / "tmp" / "planner_check_debug.log"
|
|
60
61
|
|
|
61
|
-
# DoS 防御の読み取り上限(hook
|
|
62
|
-
|
|
62
|
+
# DoS 防御の読み取り上限(stdin は文字数ベース)。hook はローカル前提だが多層防御として明示する。
|
|
63
|
+
_STDIN_READ_LIMIT_CHARS = 1 * 1024 * 1024 # 1M chars
|
|
63
64
|
_FILE_READ_LIMIT_BYTES = 512 * 1024 # 512 KiB
|
|
64
65
|
|
|
65
66
|
|
|
@@ -230,7 +231,7 @@ def _check_r6_reviewer_absence(tasks: list) -> list[str]:
|
|
|
230
231
|
|
|
231
232
|
def main() -> None:
|
|
232
233
|
try:
|
|
233
|
-
payload = json.loads(sys.stdin.read(
|
|
234
|
+
payload = json.loads(sys.stdin.read(_STDIN_READ_LIMIT_CHARS))
|
|
234
235
|
except (json.JSONDecodeError, ValueError):
|
|
235
236
|
sys.exit(0)
|
|
236
237
|
|
|
@@ -12,10 +12,23 @@ import re
|
|
|
12
12
|
import sys
|
|
13
13
|
from datetime import datetime, timedelta, timezone
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
sys.
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
try:
|
|
16
|
+
sys.stdin.reconfigure(encoding='utf-8')
|
|
17
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
18
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
19
|
+
except AttributeError:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
from session_utils import append_checkpoint, is_worktree, SESSIONS_DIR
|
|
23
|
+
# SESSION_JSON_MARKER は本ファイルのロジックでは未参照だが、過去レビュー(Code Medium-3a)の
|
|
24
|
+
# 契約として module-level 定数の存在を tests/test_stop_precompact_fixes.py が要求する再エクスポート。
|
|
25
|
+
# 未使用に見えても削除しないこと。
|
|
26
|
+
# `# noqa: F401` は flake8/ruff 固有の規約で生の pyflakes は解釈しないため、
|
|
27
|
+
# 代わりに __all__ へ列挙して re-export であることを明示する(pyflakes は
|
|
28
|
+
# __all__ 掲載名を使用済みとみなすため、警告が実際に抑制される)。
|
|
29
|
+
from session_utils import SESSION_JSON_MARKER
|
|
30
|
+
|
|
31
|
+
__all__ = ["SESSION_JSON_MARKER"]
|
|
19
32
|
|
|
20
33
|
|
|
21
34
|
# デバウンス窓幅(秒)。直近の PreCompact checkpoint からこの秒数以内の再実行は
|
|
@@ -7,8 +7,12 @@ import re
|
|
|
7
7
|
import shlex
|
|
8
8
|
import sys
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
sys.
|
|
10
|
+
try:
|
|
11
|
+
sys.stdin.reconfigure(encoding='utf-8')
|
|
12
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
13
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
14
|
+
except AttributeError:
|
|
15
|
+
pass
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
def _is_rm_rf(tokens: list[str], rm_idx: int) -> bool:
|
{claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/recall_autorebuild.py
RENAMED
|
@@ -23,8 +23,10 @@ import sys
|
|
|
23
23
|
import time
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
|
|
26
|
-
# CLAUDE.md §9-3: reconfigure stdout/stderr to UTF-8 on Windows.
|
|
26
|
+
# CLAUDE.md §9-3: reconfigure stdin/stdout/stderr to UTF-8 on Windows.
|
|
27
27
|
try:
|
|
28
|
+
if sys.stdin and hasattr(sys.stdin, "reconfigure"):
|
|
29
|
+
sys.stdin.reconfigure(encoding="utf-8")
|
|
28
30
|
if sys.stdout and hasattr(sys.stdout, "reconfigure"):
|
|
29
31
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
30
32
|
if sys.stderr and hasattr(sys.stderr, "reconfigure"):
|
{claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/report_contract_check.py
RENAMED
|
@@ -38,6 +38,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
38
38
|
from _hook_utils import norm_component, sanitize_for_terminal # noqa: E402
|
|
39
39
|
|
|
40
40
|
try:
|
|
41
|
+
sys.stdin.reconfigure(encoding='utf-8')
|
|
41
42
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
42
43
|
sys.stderr.reconfigure(encoding='utf-8')
|
|
43
44
|
except AttributeError:
|
{claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/session_mode_watch.py
RENAMED
|
@@ -52,6 +52,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
52
52
|
from _hook_utils import norm_component, sanitize_for_terminal # noqa: E402
|
|
53
53
|
|
|
54
54
|
try:
|
|
55
|
+
sys.stdin.reconfigure(encoding='utf-8')
|
|
55
56
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
56
57
|
sys.stderr.reconfigure(encoding='utf-8')
|
|
57
58
|
except AttributeError:
|
{claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/tier_autoapply.py
RENAMED
|
@@ -106,8 +106,10 @@ _VALID_TIERS = ("haiku", "sonnet", "opus")
|
|
|
106
106
|
|
|
107
107
|
KILL_SWITCH_ENV = "C3_TIER_AUTOAPPLY_DISABLE"
|
|
108
108
|
|
|
109
|
-
# DoS 防御の stdin
|
|
110
|
-
|
|
109
|
+
# DoS 防御の stdin 読み取り上限(文字数ベース・check_agent_invocation.py と同一)。
|
|
110
|
+
# sys.stdin はテキストストリームのため read(n) の n は文字数であってバイト数ではない
|
|
111
|
+
# (UTF-8 マルチバイト文字では実バイト量が最大 4 倍になり得る)。
|
|
112
|
+
_STDIN_READ_LIMIT_CHARS = 1 * 1024 * 1024 # 1M chars
|
|
111
113
|
|
|
112
114
|
# applied-state のローテーション閾値(§3-4)。
|
|
113
115
|
_MAX_JSONL_BYTES = 1 * 1024 * 1024 # 1 MB
|
|
@@ -371,7 +373,7 @@ def main() -> None:
|
|
|
371
373
|
sys.exit(0)
|
|
372
374
|
|
|
373
375
|
try:
|
|
374
|
-
payload = json.loads(sys.stdin.read(
|
|
376
|
+
payload = json.loads(sys.stdin.read(_STDIN_READ_LIMIT_CHARS))
|
|
375
377
|
except (json.JSONDecodeError, ValueError):
|
|
376
378
|
sys.exit(0)
|
|
377
379
|
if not isinstance(payload, dict):
|
{claude_code_conductor-2.55.0 → claude_code_conductor-2.55.1}/.claude/hooks/worktree_guard.py
RENAMED
|
@@ -16,8 +16,12 @@ import os
|
|
|
16
16
|
import re
|
|
17
17
|
import sys
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
sys.
|
|
19
|
+
try:
|
|
20
|
+
sys.stdin.reconfigure(encoding='utf-8')
|
|
21
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
22
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
23
|
+
except AttributeError:
|
|
24
|
+
pass
|
|
21
25
|
|
|
22
26
|
# worktree パスの識別に使うコンポーネント名。
|
|
23
27
|
# `.claude/worktrees/agent-<id>/` という構造を前提とし、
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.55.1] - 2026-07-26
|
|
4
|
+
|
|
5
|
+
### 修正
|
|
6
|
+
|
|
7
|
+
- **hook stdin cp932 defect の修正(stdin UTF-8 reconfigure 横断適用)**: v2.55.0 の `session_mode_watch.py` が実機の Edit 発火で沈黙する defect を修正。Windows ネイティブ Python では hook の stdin が cp932 既定でデコードされるため、ハーネスが送る UTF-8 JSON payload 中の日本語(`モード:` 行)が mojibake となり `^モード: 自律` 正規表現が不一致→モード行変更を一切検知できていなかった(実機発火スモークで確定・同一 payload の素パイプ再現と `PYTHONIOENCODING=utf-8` 対照実験で原因を実測特定)。stdin reconfigure が欠落していた 10 hook(新規 3 本 + 既存 7 本: session_mode_watch / patterns_guard / report_contract_check / check_agent_invocation / planner_check / post_tool / pre_tool / pre_compact / worktree_guard / recall_autorebuild)へ `sys.stdin.reconfigure(encoding='utf-8')` を追加し、既存 12 hook のイディオムへ統一(canonical 順序 stdin → stdout → stderr・素の呼び出し 3 ファイルは try/except AttributeError ラップ化=crash-to-fail-open リスクの軽減方向)
|
|
8
|
+
|
|
9
|
+
### 追加
|
|
10
|
+
|
|
11
|
+
- **cp932 実バイトリグレッションテスト**: `PYTHONIOENCODING=cp932` を強制した subprocess に `ensure_ascii=False` の生 UTF-8 payload をパイプし、Windows ネイティブ既定の欠陥環境を CI の Linux 上でも決定論的に再現するテストを `tests/test_session_mode_watch.py` に追加(ASCII エスケープ payload では defect を再現できないことが既存テスト素通しの真因だった)
|
|
12
|
+
- **静的イディオム検査 `tests/test_hooks_stdin_idiom.py` 新設**: `.claude/hooks/*.py` を走査し「`sys.stdin` を読む hook は stdin reconfigure 必須」を機械強制する。検出は純粋 AST 判定(reconfigure receiver 除外の 2 周走査)でコメント/docstring 内の文字列による偽陽性・誤検出なし。アンダースコア始まりファイルも検査対象
|
|
13
|
+
|
|
14
|
+
### 変更
|
|
15
|
+
|
|
16
|
+
- **`_STDIN_READ_LIMIT_BYTES` → `_STDIN_READ_LIMIT_CHARS` 改名**(check_agent_invocation / planner_check / tier_autoapply): `sys.stdin.read(n)` の n は文字数であり命名のバイト数示唆と実際の意味論の乖離を解消(読み取り方式は不変・挙動変更なし)
|
|
17
|
+
- **`pre_compact.py` の `SESSION_JSON_MARKER` 再エクスポートを `__all__` 明示化**: 過去レビュー(Code Medium-3a)の契約再エクスポートである旨のコメントを付し、生 pyflakes でも未使用 import 警告が出ない形へ整理
|
|
18
|
+
|
|
19
|
+
### 後方互換
|
|
20
|
+
|
|
21
|
+
- **破壊的変更なし**。修正は「stdin を UTF-8 として読む」の 1 点で、fail-open 挙動・payload 処理ロジック・ブロック/警告の判定条件は不変。修正前に誤って沈黙していた検知(session_mode_watch のモード行監視)が正しく発火するようになる方向のみの変更
|
|
22
|
+
|
|
3
23
|
## [2.55.0] - 2026-07-26
|
|
4
24
|
|
|
5
25
|
### 追加
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-code-conductor
|
|
3
|
-
Version: 2.55.
|
|
3
|
+
Version: 2.55.1
|
|
4
4
|
Summary: Multi-agent orchestration framework for Claude Code with Codex/Cursor adapters (C3)
|
|
5
5
|
Project-URL: Homepage, https://github.com/satoh-y-0323/claude-code-conductor
|
|
6
6
|
Project-URL: Repository, https://github.com/satoh-y-0323/claude-code-conductor
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
test_hooks_stdin_idiom.py: Static analysis test for stdin reconfigure idiom in hooks.
|
|
4
|
+
|
|
5
|
+
Tests that all .claude/hooks/*.py files that use sys.stdin also contain a
|
|
6
|
+
sys.stdin.reconfigure(encoding='utf-8') call (or equivalent reconfigure with
|
|
7
|
+
encoding parameter), following the idiom established in CLAUDE.md §9-3.
|
|
8
|
+
|
|
9
|
+
## Detection Strategy (AST-based)
|
|
10
|
+
|
|
11
|
+
1. **sys.stdin Usage Detection (AST)**:
|
|
12
|
+
- Walks the AST for ast.Attribute nodes matching sys -> stdin
|
|
13
|
+
(Name "sys" / Attribute "stdin").
|
|
14
|
+
- Detects patterns: sys.stdin.read(), sys.stdin.readline(), json.load(sys.stdin), etc.
|
|
15
|
+
- Excludes the single sys.stdin node that is the receiver of a
|
|
16
|
+
sys.stdin.reconfigure(...) call (i.e. the func chain of an ast.Call):
|
|
17
|
+
configuring the stream is setup, not "usage". Every other sys.stdin node
|
|
18
|
+
counts as usage.
|
|
19
|
+
- No raw-source regex is involved at any stage, so comments and docstrings
|
|
20
|
+
cannot influence the result (see section 3).
|
|
21
|
+
|
|
22
|
+
2. **Reconfigure Detection (AST)**:
|
|
23
|
+
- Walks the AST for an ast.Call whose func is the attribute chain
|
|
24
|
+
sys -> stdin -> reconfigure (Name "sys" / Attribute "stdin" /
|
|
25
|
+
Attribute "reconfigure") and which carries an `encoding=` keyword argument.
|
|
26
|
+
- Does NOT require try/except context (though production idiom uses it):
|
|
27
|
+
the visitor walks the whole tree regardless of control flow, so both the
|
|
28
|
+
`try/except AttributeError` idiom and the `if sys.stdin and hasattr(...)`
|
|
29
|
+
idiom are detected.
|
|
30
|
+
- Quote style of the encoding value is irrelevant, since the check inspects
|
|
31
|
+
the parsed keyword argument rather than raw source text.
|
|
32
|
+
|
|
33
|
+
3. **Comments and Docstrings**:
|
|
34
|
+
- Both detections are AST-based, so comments and docstrings are excluded from
|
|
35
|
+
the detection surface automatically: comments never enter the AST at all,
|
|
36
|
+
and a docstring is a plain string constant, not an ast.Call. A file that
|
|
37
|
+
merely *mentions* sys.stdin.reconfigure(encoding='utf-8') in a comment or
|
|
38
|
+
docstring is therefore correctly reported as "reconfigure missing".
|
|
39
|
+
|
|
40
|
+
## Known Limitations
|
|
41
|
+
|
|
42
|
+
- Does not validate that reconfigure is actually reached at runtime
|
|
43
|
+
(e.g., if it's in an if-block that never runs). Static detection is
|
|
44
|
+
best-effort; behavioral verification is in ADR-3 subprocess tests.
|
|
45
|
+
- A guard expression such as `if sys.stdin and hasattr(sys.stdin, 'reconfigure')`
|
|
46
|
+
counts as usage, because only the reconfigure call's own receiver node is
|
|
47
|
+
excluded. This is conservative-safe: a file carrying that guard also calls
|
|
48
|
+
reconfigure, so the idiom check still passes.
|
|
49
|
+
- Does not check if stdin.buffer or other alternatives are used
|
|
50
|
+
(acceptable per ADR-2: stdin.reconfigure is the standard idiom).
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
import ast
|
|
54
|
+
from pathlib import Path
|
|
55
|
+
|
|
56
|
+
import pytest
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_hooks_dir() -> Path:
|
|
60
|
+
"""Return the .claude/hooks directory path."""
|
|
61
|
+
project_root = Path(__file__).parent.parent
|
|
62
|
+
return project_root / ".claude" / "hooks"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _is_sys_stdin(node: ast.AST) -> bool:
|
|
66
|
+
"""Return True if node is the attribute expression ``sys.stdin``."""
|
|
67
|
+
return (
|
|
68
|
+
isinstance(node, ast.Attribute)
|
|
69
|
+
and node.attr == "stdin"
|
|
70
|
+
and isinstance(node.value, ast.Name)
|
|
71
|
+
and node.value.id == "sys"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def extract_stdin_refs(source: str, filename: str) -> bool:
|
|
76
|
+
"""
|
|
77
|
+
Check if source code uses sys.stdin.
|
|
78
|
+
|
|
79
|
+
Returns True if sys.stdin is referenced in a way that requires reading
|
|
80
|
+
(not just in reconfigure calls).
|
|
81
|
+
|
|
82
|
+
Detection is purely AST-based: comments and docstrings never contribute to
|
|
83
|
+
the result, because comments do not enter the AST at all and a docstring is
|
|
84
|
+
a plain string constant rather than an attribute expression.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
source: Python source code as string
|
|
88
|
+
filename: Name of file (for error reporting)
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
bool: True if sys.stdin usage is detected
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
tree = ast.parse(source)
|
|
95
|
+
except SyntaxError as e:
|
|
96
|
+
print(f" [SYNTAX ERROR] {filename}: {e}")
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
# sys.stdin nodes acting as the receiver of a sys.stdin.reconfigure(...)
|
|
100
|
+
# call. Configuring the stream is setup, not usage, so they are excluded.
|
|
101
|
+
# `tree` stays alive for the whole function, so node identities are stable.
|
|
102
|
+
reconfigure_receivers = set()
|
|
103
|
+
for node in ast.walk(tree):
|
|
104
|
+
if (
|
|
105
|
+
isinstance(node, ast.Call)
|
|
106
|
+
and isinstance(node.func, ast.Attribute)
|
|
107
|
+
and node.func.attr == "reconfigure"
|
|
108
|
+
and _is_sys_stdin(node.func.value)
|
|
109
|
+
):
|
|
110
|
+
reconfigure_receivers.add(id(node.func.value))
|
|
111
|
+
|
|
112
|
+
for node in ast.walk(tree):
|
|
113
|
+
if _is_sys_stdin(node) and id(node) not in reconfigure_receivers:
|
|
114
|
+
return True
|
|
115
|
+
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def has_stdin_reconfigure(source: str) -> bool:
|
|
120
|
+
"""
|
|
121
|
+
Check if source code contains sys.stdin.reconfigure(encoding=...).
|
|
122
|
+
|
|
123
|
+
Uses AST-based detection to avoid false positives from comments/docstrings.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
source: Python source code as string
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
bool: True if reconfigure call with encoding parameter found
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
tree = ast.parse(source)
|
|
133
|
+
except SyntaxError:
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
class ReconfigureVisitor(ast.NodeVisitor):
|
|
137
|
+
def __init__(self):
|
|
138
|
+
self.found_reconfigure = False
|
|
139
|
+
|
|
140
|
+
def visit_Call(self, node: ast.Call) -> None:
|
|
141
|
+
# Check for sys.stdin.reconfigure(...) pattern
|
|
142
|
+
if (
|
|
143
|
+
isinstance(node.func, ast.Attribute)
|
|
144
|
+
and node.func.attr == "reconfigure"
|
|
145
|
+
and isinstance(node.func.value, ast.Attribute)
|
|
146
|
+
and node.func.value.attr == "stdin"
|
|
147
|
+
and isinstance(node.func.value.value, ast.Name)
|
|
148
|
+
and node.func.value.value.id == "sys"
|
|
149
|
+
):
|
|
150
|
+
# Found sys.stdin.reconfigure call. Check for encoding keyword argument.
|
|
151
|
+
for keyword in node.keywords:
|
|
152
|
+
if keyword.arg == "encoding":
|
|
153
|
+
self.found_reconfigure = True
|
|
154
|
+
return
|
|
155
|
+
self.generic_visit(node)
|
|
156
|
+
|
|
157
|
+
visitor = ReconfigureVisitor()
|
|
158
|
+
visitor.visit(tree)
|
|
159
|
+
return visitor.found_reconfigure
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def check_hook_file(hook_path: Path) -> tuple[bool, str]:
|
|
163
|
+
"""
|
|
164
|
+
Check a single hook file for stdin reconfigure idiom.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
hook_path: Path to hook .py file
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
(passed, message): bool indicating pass/fail and explanatory message
|
|
171
|
+
"""
|
|
172
|
+
try:
|
|
173
|
+
source = hook_path.read_text(encoding="utf-8")
|
|
174
|
+
except Exception as e:
|
|
175
|
+
return False, f"Failed to read: {e}"
|
|
176
|
+
|
|
177
|
+
uses_stdin = extract_stdin_refs(source, hook_path.name)
|
|
178
|
+
has_reconfigure = has_stdin_reconfigure(source)
|
|
179
|
+
|
|
180
|
+
if uses_stdin and not has_reconfigure:
|
|
181
|
+
return False, "Uses sys.stdin but missing reconfigure(encoding=...)"
|
|
182
|
+
elif uses_stdin and has_reconfigure:
|
|
183
|
+
return True, "OK: uses sys.stdin and has reconfigure"
|
|
184
|
+
else:
|
|
185
|
+
return True, "OK: does not use sys.stdin"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_hooks_stdin_idiom():
|
|
189
|
+
"""
|
|
190
|
+
Test that all hooks using sys.stdin have stdin.reconfigure(encoding='utf-8').
|
|
191
|
+
"""
|
|
192
|
+
hooks_dir = get_hooks_dir()
|
|
193
|
+
|
|
194
|
+
if not hooks_dir.exists():
|
|
195
|
+
raise RuntimeError(f"Hooks directory not found: {hooks_dir}")
|
|
196
|
+
|
|
197
|
+
hook_files = sorted(hooks_dir.glob("*.py"))
|
|
198
|
+
|
|
199
|
+
if not hook_files:
|
|
200
|
+
raise RuntimeError(f"No .py files found in {hooks_dir}")
|
|
201
|
+
|
|
202
|
+
print(f"\n=== Scanning {len(hook_files)} hook files ===")
|
|
203
|
+
print(f"Directory: {hooks_dir}\n")
|
|
204
|
+
|
|
205
|
+
failures = []
|
|
206
|
+
passes = []
|
|
207
|
+
|
|
208
|
+
for hook_path in hook_files:
|
|
209
|
+
# Check all hook files including private modules (_hook_utils.py, etc.)
|
|
210
|
+
# using the same reconfigure detection logic.
|
|
211
|
+
passed, message = check_hook_file(hook_path)
|
|
212
|
+
|
|
213
|
+
status = "PASS" if passed else "FAIL"
|
|
214
|
+
print(f"[{status}] {hook_path.name}: {message}")
|
|
215
|
+
|
|
216
|
+
if not passed:
|
|
217
|
+
failures.append((hook_path.name, message))
|
|
218
|
+
else:
|
|
219
|
+
passes.append(hook_path.name)
|
|
220
|
+
|
|
221
|
+
print("\n=== Summary ===")
|
|
222
|
+
print(f"Passed: {len(passes)}")
|
|
223
|
+
print(f"Failed: {len(failures)}")
|
|
224
|
+
|
|
225
|
+
if failures:
|
|
226
|
+
print("\n=== Failed hooks ===")
|
|
227
|
+
for name, msg in failures:
|
|
228
|
+
print(f" {name}: {msg}")
|
|
229
|
+
|
|
230
|
+
# Construct assertion message with failing file names
|
|
231
|
+
failing_files = ", ".join(name for name, _ in failures)
|
|
232
|
+
raise AssertionError(
|
|
233
|
+
f"stdin idiom check failed for {len(failures)} hook(s): {failing_files}"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# 以下は has_stdin_reconfigure() / extract_stdin_refs() の AST ベース判定そのものを
|
|
238
|
+
# 対象にした単体テスト。上の test_hooks_stdin_idiom() は実 hook ファイルを走査するため、
|
|
239
|
+
# 「コメント/docstring 内の文字列を検出しない」という偽陽性排除の効果は(そのような hook が
|
|
240
|
+
# 存在しないため)検証されない。正規表現ベースへの回帰を検知する回帰防止テストとして分離する。
|
|
241
|
+
|
|
242
|
+
# コメント内にのみ reconfigure が現れる負例(実コードでは stdin を読むだけ)
|
|
243
|
+
_SRC_RECONFIGURE_IN_COMMENT_ONLY = """\
|
|
244
|
+
import sys
|
|
245
|
+
|
|
246
|
+
# sys.stdin.reconfigure(encoding='utf-8')
|
|
247
|
+
data = sys.stdin.read()
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
# docstring 内にのみ reconfigure が現れる負例
|
|
251
|
+
_SRC_RECONFIGURE_IN_DOCSTRING_ONLY = '''\
|
|
252
|
+
"""Hook that should call sys.stdin.reconfigure(encoding='utf-8') but does not."""
|
|
253
|
+
|
|
254
|
+
import sys
|
|
255
|
+
|
|
256
|
+
data = sys.stdin.read()
|
|
257
|
+
'''
|
|
258
|
+
|
|
259
|
+
# 本番 idiom その 1: try/except AttributeError
|
|
260
|
+
_SRC_RECONFIGURE_TRY_EXCEPT = """\
|
|
261
|
+
import sys
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
sys.stdin.reconfigure(encoding='utf-8')
|
|
265
|
+
except AttributeError:
|
|
266
|
+
pass
|
|
267
|
+
|
|
268
|
+
data = sys.stdin.read()
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
# 本番 idiom その 2: if hasattr(recall_autorebuild.py 系)
|
|
272
|
+
_SRC_RECONFIGURE_IF_HASATTR = """\
|
|
273
|
+
import sys
|
|
274
|
+
|
|
275
|
+
if sys.stdin and hasattr(sys.stdin, 'reconfigure'):
|
|
276
|
+
sys.stdin.reconfigure(encoding='utf-8')
|
|
277
|
+
|
|
278
|
+
data = sys.stdin.read()
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
# stdin を一切使わないソース
|
|
282
|
+
_SRC_NO_STDIN = """\
|
|
283
|
+
import sys
|
|
284
|
+
|
|
285
|
+
print(sys.argv)
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
# extract_stdin_refs の「実使用検出」用の正例。実コードで sys.stdin.read() を呼ぶ。
|
|
289
|
+
_SRC_REAL_STDIN_READ = """\
|
|
290
|
+
import sys
|
|
291
|
+
|
|
292
|
+
data = sys.stdin.read()
|
|
293
|
+
print(data)
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
# CR-r3 finding #1 の反証シナリオ: 実コードは reconfigure 呼び出しのみで stdin を読まず、
|
|
297
|
+
# コメントに「.reconfigure で終わらない」sys.stdin 言及がある負例。
|
|
298
|
+
# 旧実装(AST ゲート後に生ソース全体へ regex フォールバック)では usage あり(True)と
|
|
299
|
+
# 誤検出していた。純粋 AST 判定への回帰防止テスト。
|
|
300
|
+
_SRC_RECONFIGURE_ONLY_WITH_STDIN_COMMENT = """\
|
|
301
|
+
import sys
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
sys.stdin.reconfigure(encoding='utf-8')
|
|
305
|
+
except AttributeError:
|
|
306
|
+
pass
|
|
307
|
+
|
|
308
|
+
# NOTE: some tools call sys.stdin.read() directly instead
|
|
309
|
+
print("done")
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@pytest.mark.parametrize(
|
|
314
|
+
"source, expected",
|
|
315
|
+
[
|
|
316
|
+
pytest.param(
|
|
317
|
+
_SRC_RECONFIGURE_IN_COMMENT_ONLY, False, id="comment-only-is-not-detected"
|
|
318
|
+
),
|
|
319
|
+
pytest.param(
|
|
320
|
+
_SRC_RECONFIGURE_IN_DOCSTRING_ONLY,
|
|
321
|
+
False,
|
|
322
|
+
id="docstring-only-is-not-detected",
|
|
323
|
+
),
|
|
324
|
+
pytest.param(_SRC_RECONFIGURE_TRY_EXCEPT, True, id="try-except-idiom"),
|
|
325
|
+
pytest.param(_SRC_RECONFIGURE_IF_HASATTR, True, id="if-hasattr-idiom"),
|
|
326
|
+
],
|
|
327
|
+
)
|
|
328
|
+
def test_has_stdin_reconfigure_ignores_comments_and_docstrings(source, expected):
|
|
329
|
+
"""AST ベース判定がコメント/docstring 内の文字列を検出しないことを検証する。"""
|
|
330
|
+
assert has_stdin_reconfigure(source) is expected
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
@pytest.mark.parametrize(
|
|
334
|
+
"source, expected",
|
|
335
|
+
[
|
|
336
|
+
pytest.param(_SRC_NO_STDIN, False, id="no-stdin-usage"),
|
|
337
|
+
pytest.param(_SRC_REAL_STDIN_READ, True, id="real-stdin-read-usage"),
|
|
338
|
+
pytest.param(
|
|
339
|
+
_SRC_RECONFIGURE_ONLY_WITH_STDIN_COMMENT,
|
|
340
|
+
False,
|
|
341
|
+
id="reconfigure-only-with-stdin-comment-is-not-usage",
|
|
342
|
+
),
|
|
343
|
+
],
|
|
344
|
+
)
|
|
345
|
+
def test_extract_stdin_refs_detects_only_real_usage(source, expected):
|
|
346
|
+
"""extract_stdin_refs() が実コード上の stdin 使用のみを検出することを検証する。
|
|
347
|
+
|
|
348
|
+
- stdin を使わないソースでは False
|
|
349
|
+
- 実際に sys.stdin.read() を呼ぶソースでは True
|
|
350
|
+
- reconfigure 呼び出しのみ+コメント内の sys.stdin 言及では False
|
|
351
|
+
(純粋 AST 判定であり、コメントは検出面に入らない)
|
|
352
|
+
"""
|
|
353
|
+
assert extract_stdin_refs(source, "synthetic.py") is expected
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
if __name__ == "__main__":
|
|
357
|
+
test_hooks_stdin_idiom()
|