claude-code-conductor 2.74.0__tar.gz → 2.76.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.74.0 → claude_code_conductor-2.76.0}/.claude/CLAUDE.md +16 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/statusline.py +6 -3
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/parallel-agents/SKILL.md +1 -1
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.gitignore +4 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/CHANGELOG.md +56 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/PKG-INFO +27 -1
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/README.md +26 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/__init__.py +1 -1
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/cli_init.py +74 -2
- claude_code_conductor-2.76.0/tests/conftest.py +57 -0
- claude_code_conductor-2.76.0/tests/hooks/test_statusline_reconfigure_guard.py +197 -0
- claude_code_conductor-2.76.0/tests/test_cli_init_symlink_guard.py +704 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_statusline.py +10 -1
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_stop_agent_memory_warn.py +5 -22
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_three_file_sync.py +9 -11
- claude_code_conductor-2.74.0/tests/conftest.py +0 -15
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/.gitignore +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/agents/architect.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/agents/code-reviewer.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/agents/design-critic.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/agents/developer.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/agents/doc-writer.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/agents/interviewer.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/agents/planner.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/agents/project-setup.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/agents/security-reviewer.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/agents/systematic-debugger.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/agents/tester.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/agents/wt_developer.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/agents/wt_systematic-debugger.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/agents/wt_tester.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/breaking-changes.txt +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/deletions.txt +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/docs/autonomous-mode-onboarding.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/docs/config-policy.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/docs/nul-boundary.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/docs/parallel-agents-setup.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/docs/platform-adapters.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/docs/settings.json.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/_hook_utils.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/check_agent_invocation.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/consolidate_memory.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/patterns_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/planner_check.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/post_tool.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/pre_compact.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/pre_tool.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/recall_autorebuild.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/recall_inject.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/report_contract_check.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/report_path_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/restore_session.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/select_tier.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/session_mode_watch.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/session_start.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/session_stop.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/session_utils.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/stop.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/tier_autoapply.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/tier_gap_check.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/hooks/worktree_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/memory/.gitkeep +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/memory/archive/.gitkeep +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/memory/sessions/.gitkeep +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/reports/.gitkeep +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/rules/judgment-principles.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/rules/promoted/index.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/settings.json +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/autonomous-mode/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/autonomous-mode/scripts/mode_line.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/brainstorm/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/codex-review/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/dev-workflow/references/c3-omission-declaration.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/dev-workflow/references/design-critic-rubric.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/dev-workflow/references/design-rubric.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/dev-workflow/references/interview-rubric.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/dev-workflow/references/reachability-map.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/dev-workflow/references/record-protocol.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/dev-workflow/scripts/detect_execution_verification.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/dev-workflow/scripts/record_agent_outcome.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/develop/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/doc/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/extract-lib/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/init-session/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/init-session/scripts/session_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/mcp-config/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/pattern-status/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/recall/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/review-phase/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/setup/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/setup/reference.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/start/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/skills/start/scripts/archive_reports.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/state/.gitkeep +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/state/setup_done.flag +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/.claude/tmp/.gitkeep +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/ARCHITECTURE.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/LICENSE +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/LICENSES/fastembed-LICENSE +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/LICENSES/fastembed-NOTICE +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/LICENSES/numpy-LICENSE +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/LICENSES/onnxruntime-LICENSE +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/hatch_build.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/pyproject.toml +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/__main__.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/_db_params.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/_excludes.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/_terminal.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/adapters.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/cli.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/cli_ask.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/cli_doctor.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/cli_list.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/cli_metrics.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/cli_plan.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/cli_recall.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/cli_run.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/cli_tier.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/cli_update.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/db.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/embedding.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/gitutil.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/mcp_server.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/migrate.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/migrations/001_initial.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/migrations/003_tier_cost.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/migrations/004_agent_outcomes.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/migrations/005_drop_agent_tier_bandit.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/migrations/006_review_decisions_severity.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/migrations/007_review_decisions_resolution.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/migrations/008_drop_agent_runs.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/migrations/README.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/migrations/__init__.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/paths.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/plan_validator.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/platforms.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/pricing.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/question.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/recall_chunker.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/recall_index.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/refgraph.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/src/c3/usage_ingester.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/__init__.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/_pre_compact_helpers.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/_sync_semantics.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/fixtures/usage/README.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/fixtures/usage/mainline.jsonl +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/__init__.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_check_agent_invocation.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_consolidate_memory.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_detect_execution_verification.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_hook_utils.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_planner_check.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_planner_check_dev.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_post_tool.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_pre_tool.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_recall_autorebuild.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_recall_inject.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_record_agent_outcome.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_record_review_decision.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_report_path_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_restore_session.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_review_hint_inject.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_select_tier.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_select_tier_escalation.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_session_start.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_session_stop.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_session_utils.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_settings_tier_autoapply_registration.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_similarity_boost.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_statusline.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_statusline_template_sync.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_sync_check.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_template_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_tier_autoapply.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_tier_gap_check.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/hooks/test_tier_security_fixes.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/__init__.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/_skill_helpers.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_c3_omission_declaration.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_e0_verification_exit.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_init_session_no_task_type.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_marker_compliance.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_parallel_agents_skill_tier.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_planner_lightweight.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_reachability_map.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_recall_skill.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_session_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_setup_templates.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_start_skill_new_flow.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_adapters.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_archive_reports.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_audit_review_decisions.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_check_deletions.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_ci_workflows.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_claude_gitignore.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_cli_ask.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_cli_doctor.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_cli_doctor_launcher.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_cli_entry.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_cli_init.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_cli_list.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_cli_metrics.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_cli_plan.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_cli_recall.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_cli_run.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_cli_tier.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_cli_update_breaking_changes.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_cli_update_deletions.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_db.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_db_connect_lint.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_docstring_consistency.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_embedding.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_excludes.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_extract_breaking_changes.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_gitutil.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_hooks_stdin_idiom.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_init_only_patterns.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_mcp_server_elicit.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_migrate.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_mode_line.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_no_bare_python_launcher.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_no_passonly_tests.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_nul_boundary_lint.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_paths.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_patterns_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_permission_handler_removal.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_plan_validator.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_pre_compact.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_precompact_additional.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_precompact_toctou_fixes.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_pricing.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_recall_chunker.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_recall_index.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_references_migration.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_refgraph.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_refgraph_query.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_refgraph_structural.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_report_contract_check.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_scripts_src_bootstrap.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_scripts_stdout_encoding.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_session_mode_watch.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_session_utils_additional.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_settings_hooks_registration.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_skill_no_builtin_conflict.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_start_skill_archive_contract.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_stop_additional.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_stop_hook.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_stop_precompact_fixes.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_sync_template_stop.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_template_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_usage_ingester.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_verify_wheel.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_worktree_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.76.0}/tests/test_wt_agent_sync.py +0 -0
|
@@ -14,6 +14,22 @@ C3 は、業務アプリケーション開発で製品としての品質を保
|
|
|
14
14
|
|
|
15
15
|
背景: 業務アプリは機密情報を扱い、作って終わりではなく保守・運用が続く。
|
|
16
16
|
|
|
17
|
+
## 根本解決の原則(監査・レビューを通すための対応をしない)
|
|
18
|
+
|
|
19
|
+
- 監査(C-3)・レビュー(E)・テストの指摘に対して、**指摘を消すためだけの対応(言い換え・周辺の付け足し・機構の差し替え)を行わない**
|
|
20
|
+
- 指摘の背後にある**根本問題を 1 文で書き**、それを解く手段を採る。根本問題を解いているかは、OS・ライブラリの一次資料(ソース・公式ドキュメント)と実測で裏取りしてから確定する
|
|
21
|
+
- **影響範囲が大きくても、コストがかかっても、根本解決できるものは必ず根本解決の手段を取る。** 同じコストを払うなら、修正範囲が広い根本解決の方が、後から積む機能への影響が少なくトータルコストが安い
|
|
22
|
+
- 監査・レビューを通すためだけの対応に払うコストは、開発で最も無駄なコストである。ゲートが緑になることは目的の達成を意味しない
|
|
23
|
+
- 対症療法しか取れない場合は、その理由(根本解決が構造的に不可能・現時点の情報では原因が確定しない等)を明記し、根本解決へ戻るトリガーをセッションファイルの残タスクに残す。「後で直す」をトリガーなしに先送りしない
|
|
24
|
+
|
|
25
|
+
判断の手順:
|
|
26
|
+
|
|
27
|
+
1. 指摘・欠陥の**根本問題を 1 文で書く**
|
|
28
|
+
2. 候補となる対策が、その根本問題を**解くのか・隠すのか**を分ける。隠す対策は採らない
|
|
29
|
+
3. 解く対策の修正範囲が大きい場合も、設計(architecture-report)と計画(plan-report)に載せて実施する。範囲の大きさを理由に縮小する裁定はユーザーが行う
|
|
30
|
+
4. 実装前に一次資料と再現実験で仮説を裏取りする。仮説が反証されたら実装せず再調査する
|
|
31
|
+
5. 是正後は「指摘が消えたか」ではなく「根本問題が解けたか」を実測で示す(Red → Green の反転・本番経路での再現条件の消失)
|
|
32
|
+
|
|
17
33
|
## 設計思想
|
|
18
34
|
|
|
19
35
|
- 対策・規約は「書きやすい場所」ではなく「効く場所」に置く。読み手(agent / skill)の Read 経路に届いていない規約は存在しないのと同じ
|
|
@@ -13,9 +13,12 @@ import threading
|
|
|
13
13
|
from datetime import datetime, timezone
|
|
14
14
|
from typing import Any
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
sys.
|
|
18
|
-
sys.
|
|
16
|
+
try:
|
|
17
|
+
sys.stdin.reconfigure(encoding='utf-8')
|
|
18
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
19
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
20
|
+
except AttributeError:
|
|
21
|
+
pass
|
|
19
22
|
|
|
20
23
|
MAX_INPUT = 64 * 1024 # 64 KB
|
|
21
24
|
|
|
@@ -157,7 +157,7 @@ wave を起動する前に `git status` が空(未コミット変更なし)
|
|
|
157
157
|
各 Agent ツール呼び出しに以下を指定:
|
|
158
158
|
|
|
159
159
|
- `subagent_type`: 上記マッピング表の値
|
|
160
|
-
- `model`: **親 Claude は `model:` を指定しない**。`wt_developer` タスクは起動時に PreToolUse hook(`.claude/hooks/tier_autoapply.py`)が `[tier-routing 推奨]`(developer 基準)の推奨 Tier を `model:` へ自動適用する(機械適用・親 Claude が model: を転記する必要はない)。この推奨 Tier の SSOT は `.claude/state/tier_selection.json` の `tier`(無ければ `suggested_model`)であり、kickoff の UserPromptSubmit で 1 度確定して以降 wave をまたいで安定する(`[tier-routing 推奨]` の表示テキストはその派生表示)。hook は実適用した model を `.claude/state/tier_autoapply.jsonl` に記録する(適用者=記録 SSOT)。並列 wave 内に複数の `wt_developer` が居る場合、**全 wt_developer は同一の推奨 Tier(単一 tier_selection.json.tier)で起動される。これは本 MVP の設計として明示的に許容する**(per-task complexity に応じて wt_developer ごとに tier を変える機能は本 MVP のスコープ外・フェーズ 3 以降)。推奨と異なる Tier を使いたい場合のみ `model:` を明示指定する(明示指定は hook
|
|
160
|
+
- `model`: **親 Claude は `model:` を指定しない**。`wt_developer` タスクは起動時に PreToolUse hook(`.claude/hooks/tier_autoapply.py`)が `[tier-routing 推奨]`(developer 基準)の推奨 Tier を `model:` へ自動適用する(機械適用・親 Claude が model: を転記する必要はない)。この推奨 Tier の SSOT は `.claude/state/tier_selection.json` の `tier`(無ければ `suggested_model`)であり、kickoff の UserPromptSubmit で 1 度確定して以降 wave をまたいで安定する(`[tier-routing 推奨]` の表示テキストはその派生表示)。hook は実適用した model を `.claude/state/tier_autoapply.jsonl` に記録する(適用者=記録 SSOT)。並列 wave 内に複数の `wt_developer` が居る場合、**全 wt_developer は同一の推奨 Tier(単一 tier_selection.json.tier)で起動される。これは本 MVP の設計として明示的に許容する**(per-task complexity に応じて wt_developer ごとに tier を変える機能は本 MVP のスコープ外・フェーズ 3 以降)。推奨と異なる Tier を使いたい場合のみ `model:` を明示指定する(明示指定は hook に尊重され上書きされない)。**上流環境変数の注意**: `CLAUDE_CODE_SUBAGENT_MODEL_FORCE`(Claude Code v2.1.257+)が設定されていると、ハーネスは per-spawn の `model:`(hook 注入・明示指定とも)と agent 定義の `model:` を無視して全サブエージェントを強制モデルで起動する。hook は注入と `tier_autoapply.jsonl` 記録を行うため、記録された tier と実適用モデルが乖離する。C3 は同変数を設定しない。tier-routing を使う環境ではこの変数を設定しないこと(`CLAUDE_CODE_SUBAGENT_MODEL` は既定値扱いで注入が優先されるため影響なし・v2.1.251+)。`wt_tester` は **test- タスク(Red)に限り機械適用対象**(`C3_TASK_ID` マーカーの `test-` プレフィックスで RED_APPLY_ROLES 注入・confirm- 等の非 test- タスクは対象外=frontmatter 任せ)。`wt_systematic-debugger` / `code-reviewer` / `security-reviewer` は **model: 指定対象外**(frontmatter/元 agent 任せ・機械適用対象外)。fork は model 上書き不可のため対象外。
|
|
161
161
|
- `isolation`: **`read_only: false` タスクのみ `"worktree"` を指定する。ただし `writes` が全て gitignored ファイル(実質 `.claude/reports/` のレポートのみ)のタスク(confirm- 系が典型)は git 的に「未変更」の worktree となるため Agent 完了時に auto-cleanup され、親が取り込む前に成果物が消失する(2026-07-26 実測)ため、worktree を使わず `isolation` を省略して main 直接経路・素の agent(読み替え: `wt_tester`→`tester` 等)で起動する。`read_only: true`(code-reviewer / security-reviewer)はソースを変更しないため worktree 不要。`isolation` を省略して main リポジトリで直接実行し、レポートを main の `.claude/reports/` に直接書かせる。**
|
|
162
162
|
> **R5 hook による機械強制**: 上記ルールに違反して `read_only: true` のレビュータスクに `isolation: "worktree"` を指定した場合、`.claude/hooks/check_agent_invocation.py`(PreToolUse Agent hook)が exit 2 でブロックする。詳細は `.claude/skills/dev-workflow/references/plan-design-guidelines.md` R5 参照。
|
|
163
163
|
- `run_in_background`: `true`
|
|
@@ -86,3 +86,7 @@ site/
|
|
|
86
86
|
# Personal Claude Code overrides (per-user / per-worktree, never committed)
|
|
87
87
|
# wheel からは _excludes.py / hatch_build.py で既に除外済み。git でも tracking しない方針。
|
|
88
88
|
.claude/settings.local.json
|
|
89
|
+
|
|
90
|
+
# agent 作業残骸 pytest_temp.ini の再発防止ガード(2026-05-02 混入・v0.4.2〔44be00f〕で削除+
|
|
91
|
+
# EXCLUDE_PATTERNS ガード化。本行はその git 側の対)
|
|
92
|
+
.claude/pytest_temp.ini
|
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.76.0] - 2026-09-04
|
|
4
|
+
|
|
5
|
+
### 追加
|
|
6
|
+
|
|
7
|
+
- `.claude/CLAUDE.md` に「根本解決の原則(監査・レビューを通すための対応をしない)」節を追加した。
|
|
8
|
+
監査(C-3)・レビュー(E)・テストの指摘に対し、指摘を消すためだけの対応(言い換え・周辺の付け足し・
|
|
9
|
+
機構の差し替え)を禁じ、根本問題を 1 文で書いてそれを解く手段を採る原則 5 項目と、判断の手順 5 段階
|
|
10
|
+
(根本問題の 1 文化 → 解く/隠すの仕分け → 修正範囲が大きくても設計・計画に載せる → 一次資料と再現実験で
|
|
11
|
+
裏取り → 根本問題が解けたことを実測で示す)を定めた。対症療法しか取れない場合は理由を明記し、
|
|
12
|
+
根本解決へ戻るトリガーをセッションファイルの残タスクに残す。是正ループの打切条件(3 連続で前提を疑う)は
|
|
13
|
+
dev-workflow E-3 を唯一の正本とし、本節には重複記述を置かない。利用先プロジェクトで運用して不足と
|
|
14
|
+
判明した規約を C3 本体へ取り込んだもの
|
|
15
|
+
|
|
16
|
+
### 変更
|
|
17
|
+
|
|
18
|
+
- `parallel-agents` skill の `model:` 指定規則と `docs/spec/02-hooks.md` の tier_autoapply 仕様表に、
|
|
19
|
+
Claude Code v2.1.257 で追加された環境変数 `CLAUDE_CODE_SUBAGENT_MODEL_FORCE` の注意を追記した。
|
|
20
|
+
同変数が設定されていると per-spawn の `model:`(hook 注入・明示指定とも)と agent 定義の `model:` が
|
|
21
|
+
ハーネス側で無視され、tier_autoapply が記録した tier と実適用モデルが乖離する。C3 は同変数を設定しない
|
|
22
|
+
(`CLAUDE_CODE_SUBAGENT_MODEL` は v2.1.251 以降既定値扱いで注入が優先されるため影響なし)
|
|
23
|
+
|
|
24
|
+
## [2.75.0] - 2026-08-16
|
|
25
|
+
|
|
26
|
+
### 追加
|
|
27
|
+
|
|
28
|
+
- **`c3 init` のテンプレート展開(`_copytree`)に symlink / NTFS ジャンクション防御を追加した**:
|
|
29
|
+
各 entry の realpath が「コピー元ルートの realpath + 相対パス」と完全一致することを検査し、
|
|
30
|
+
一致しない entry(symlink・ジャンクション・壊れたリンク)はコピーせず stderr へ 1 件 1 行の警告を
|
|
31
|
+
出してスキップする。`is_symlink()` 単体判定はジャンクションに `False` を返すため採用せず、
|
|
32
|
+
realpath 完全一致方式に一本化した。比較の両辺とも realpath 済みのため、コピー元ルート自体が
|
|
33
|
+
リンク経由(macOS の `/tmp` 等)でも全件スキップに縮退しない。加えて:
|
|
34
|
+
- entry 側の realpath が `OSError`(`ELOOP` 含む)を送出した場合は「リンク疑い」として
|
|
35
|
+
警告付きスキップに倒す(fail-soft・`c3 init` 全体は異常終了しない。ルート側の解決不能は
|
|
36
|
+
テンプレート根本の異常のため従来どおり例外で停止する)
|
|
37
|
+
- `shutil.copy2` 直前にリンク再検証を行い、検査とコピーの間の差し替え(TOCTOU)の窓を狭めた
|
|
38
|
+
(残余の窓は脅威モデル上許容・docstring に記録)
|
|
39
|
+
- 性質テスト `tests/test_cli_init_symlink_guard.py` を新設(symlink 系・ジャンクション実測・
|
|
40
|
+
ルートをリンク経由で渡す回帰ガード・例外系/再検証の検知テストを含む)。ジャンクション/リンク作成の
|
|
41
|
+
テストヘルパー `_make_link` は `tests/conftest.py` へ共有化した
|
|
42
|
+
- `.claude/hooks/statusline.py` の reconfigure 保護テスト
|
|
43
|
+
`tests/hooks/test_statusline_reconfigure_guard.py` を新設した(`reconfigure` 属性を持たない
|
|
44
|
+
ストリーム環境でも hook のロードがクラッシュしないこと)
|
|
45
|
+
|
|
46
|
+
### 変更
|
|
47
|
+
|
|
48
|
+
- `.claude/hooks/statusline.py` のストリーム reconfigure を canonical 順序(stdin→stdout→stderr)+
|
|
49
|
+
`try/except AttributeError` の確立イディオムへ統一した(配布 hook 22 本で唯一の無保護だった)
|
|
50
|
+
- `README.md` / `docs/getting-started.md` の前提条件に仮想環境(venv)の作成手順を追加した。
|
|
51
|
+
PEP 668(externally-managed-environment)環境で素の `pip install` が失敗する旨と、
|
|
52
|
+
隔離環境の但し書き 3 点(Claude Code を起動するシェルでも同じ venv を activate すること・
|
|
53
|
+
`pipx install claude-code-conductor` の代替・同一シェルでの `c3 doctor` による PATH 解決確認)を明記した
|
|
54
|
+
- root `.gitignore` に `.claude/pytest_temp.ini` のガード行を追加した(2026-05-02 に agent 作業残骸として
|
|
55
|
+
混入し v0.4.2 で削除+`EXCLUDE_PATTERNS` ガード化された経緯の git 側の対)。これに伴い
|
|
56
|
+
`tests/test_three_file_sync.py` の許容リスト A から同エントリを削除し、実効検査件数の下限を
|
|
57
|
+
16 / 16(遊びなし)へ締めた
|
|
58
|
+
|
|
3
59
|
## [2.74.0] - 2026-08-15
|
|
4
60
|
|
|
5
61
|
### 追加
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: claude-code-conductor
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.76.0
|
|
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
|
|
@@ -268,6 +268,30 @@ C3 の `.claude/` はプロジェクトコードに一切触れません。既
|
|
|
268
268
|
- [Claude Code](https://claude.ai/code) がインストール済みでログインしていること
|
|
269
269
|
- Python 3.10 以上がインストール済みであること
|
|
270
270
|
|
|
271
|
+
**仮想環境(venv)の準備(推奨)**
|
|
272
|
+
|
|
273
|
+
Python の導入方法によっては、素の `pip install` が PEP 668(externally-managed-environment)により拒否されます(Debian/Ubuntu の apt 版 Python、Homebrew の Python など)。仮想環境(venv)を作ってその中へインストールすれば、この制約を受けずに済みます。
|
|
274
|
+
|
|
275
|
+
Windows(PowerShell):
|
|
276
|
+
|
|
277
|
+
```powershell
|
|
278
|
+
python -m venv .venv
|
|
279
|
+
.\.venv\Scripts\Activate.ps1
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
macOS / Linux:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
python3 -m venv .venv
|
|
286
|
+
source .venv/bin/activate
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
venv に入れた場合の注意:
|
|
290
|
+
|
|
291
|
+
- C3 の hook は `c3 run` で起動されるため、**Claude Code を起動するシェルでも同じ venv を activate してください**。activate されていないシェルから Claude Code を起動すると `c3` が見つからず、hook が動作しません
|
|
292
|
+
- venv を使わずグローバルに使いたい場合は [pipx](https://pipx.pypa.io/) が代替になります: `pipx install claude-code-conductor`
|
|
293
|
+
- インストール後は、**Claude Code を起動するのと同じシェルで** `c3 doctor` を実行し、`c3` が PATH で解決されることを確認してください(`c3 doctor` が検査するのは実行中シェルの PATH です。別のシェルで実行しても、Claude Code の hook が起動される文脈は再現できません)
|
|
294
|
+
|
|
271
295
|
### 手順(推奨: PyPI から)
|
|
272
296
|
|
|
273
297
|
**1. C3 をインストールする**
|
|
@@ -276,6 +300,8 @@ C3 の `.claude/` はプロジェクトコードに一切触れません。既
|
|
|
276
300
|
pip install claude-code-conductor
|
|
277
301
|
```
|
|
278
302
|
|
|
303
|
+
> このコマンドが PEP 668(externally-managed-environment)で拒否される場合や、インストール後に hook が動作しない場合は、[前提条件](#前提条件)の venv 手順(activate するシェルの注意点を含む)を参照してください。
|
|
304
|
+
|
|
279
305
|
**2. プロジェクトに `.claude/` を展開する**
|
|
280
306
|
|
|
281
307
|
```bash
|
|
@@ -223,6 +223,30 @@ C3 の `.claude/` はプロジェクトコードに一切触れません。既
|
|
|
223
223
|
- [Claude Code](https://claude.ai/code) がインストール済みでログインしていること
|
|
224
224
|
- Python 3.10 以上がインストール済みであること
|
|
225
225
|
|
|
226
|
+
**仮想環境(venv)の準備(推奨)**
|
|
227
|
+
|
|
228
|
+
Python の導入方法によっては、素の `pip install` が PEP 668(externally-managed-environment)により拒否されます(Debian/Ubuntu の apt 版 Python、Homebrew の Python など)。仮想環境(venv)を作ってその中へインストールすれば、この制約を受けずに済みます。
|
|
229
|
+
|
|
230
|
+
Windows(PowerShell):
|
|
231
|
+
|
|
232
|
+
```powershell
|
|
233
|
+
python -m venv .venv
|
|
234
|
+
.\.venv\Scripts\Activate.ps1
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
macOS / Linux:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
python3 -m venv .venv
|
|
241
|
+
source .venv/bin/activate
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
venv に入れた場合の注意:
|
|
245
|
+
|
|
246
|
+
- C3 の hook は `c3 run` で起動されるため、**Claude Code を起動するシェルでも同じ venv を activate してください**。activate されていないシェルから Claude Code を起動すると `c3` が見つからず、hook が動作しません
|
|
247
|
+
- venv を使わずグローバルに使いたい場合は [pipx](https://pipx.pypa.io/) が代替になります: `pipx install claude-code-conductor`
|
|
248
|
+
- インストール後は、**Claude Code を起動するのと同じシェルで** `c3 doctor` を実行し、`c3` が PATH で解決されることを確認してください(`c3 doctor` が検査するのは実行中シェルの PATH です。別のシェルで実行しても、Claude Code の hook が起動される文脈は再現できません)
|
|
249
|
+
|
|
226
250
|
### 手順(推奨: PyPI から)
|
|
227
251
|
|
|
228
252
|
**1. C3 をインストールする**
|
|
@@ -231,6 +255,8 @@ C3 の `.claude/` はプロジェクトコードに一切触れません。既
|
|
|
231
255
|
pip install claude-code-conductor
|
|
232
256
|
```
|
|
233
257
|
|
|
258
|
+
> このコマンドが PEP 668(externally-managed-environment)で拒否される場合や、インストール後に hook が動作しない場合は、[前提条件](#前提条件)の venv 手順(activate するシェルの注意点を含む)を参照してください。
|
|
259
|
+
|
|
234
260
|
**2. プロジェクトに `.claude/` を展開する**
|
|
235
261
|
|
|
236
262
|
```bash
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
import os
|
|
6
7
|
import shutil
|
|
7
8
|
import sys
|
|
8
9
|
from collections.abc import Callable
|
|
@@ -181,28 +182,99 @@ def _do_git_init(target_root: Path) -> None:
|
|
|
181
182
|
)
|
|
182
183
|
|
|
183
184
|
|
|
184
|
-
def
|
|
185
|
+
def _is_link_entry(entry: Path, real_root: str, rel: str) -> bool:
|
|
186
|
+
"""FR-4 choke point: realpath 完全一致でリンク経由の entry を検出する。
|
|
187
|
+
|
|
188
|
+
``entry.is_symlink()`` は NTFS ディレクトリジャンクションに ``False`` を返し
|
|
189
|
+
素通りする(ADR-3・既知パターンの再発 5 回)ため採用しない。判定は
|
|
190
|
+
「entry の realpath」と「コピー元ルートの realpath + ルートからの相対パス」の
|
|
191
|
+
完全一致(``.claude/skills/start/scripts/archive_reports.py`` の
|
|
192
|
+
``_archive_dir_is_contained`` と同じ realpath 封じ込め方式)。
|
|
193
|
+
|
|
194
|
+
``real_root`` は**呼び出し側で解決済みの** コピー元ルートの realpath(文字列)を
|
|
195
|
+
受け取る契約(ADR-3 追補 2・``_copytree`` が再帰境界で 1 回だけ計算して伝播する)。
|
|
196
|
+
本関数は root 側の realpath を行わない。比較の両辺とも realpath 済みであることが
|
|
197
|
+
判定の前提であり、その担保は呼び出し側の責務になる。
|
|
198
|
+
|
|
199
|
+
entry 側の realpath は ``OSError``(ELOOP・EACCES 等)を送出しうる。解決できない
|
|
200
|
+
entry は安全側に倒して「リンク疑い(``True``)」として扱い、呼び出し側の
|
|
201
|
+
警告付きスキップに委ねる(fail-soft・ADR-3 追補 1)。
|
|
202
|
+
"""
|
|
203
|
+
# expected は「rel の各コンポーネントをリンクとして解決せず」文字列結合した
|
|
204
|
+
# 期待パス。os.path.realpath はここでは呼ばない(呼ぶと entry 側と同じ
|
|
205
|
+
# リンクを辿ってしまい、判定そのものが無効化される)。
|
|
206
|
+
expected = os.path.normpath(os.path.join(real_root, *rel.split("/")))
|
|
207
|
+
try:
|
|
208
|
+
actual = os.path.realpath(entry)
|
|
209
|
+
except OSError:
|
|
210
|
+
return True
|
|
211
|
+
return actual != expected
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _copytree(
|
|
215
|
+
src: Path, dst: Path, *, root: Path | None = None, real_root: str | None = None
|
|
216
|
+
) -> int:
|
|
185
217
|
"""Copy ``src`` -> ``dst`` recursively, skipping personal/working files.
|
|
186
218
|
|
|
187
219
|
``root`` defaults to ``src`` and represents the ``.claude/`` directory; the
|
|
188
220
|
relative path from ``root`` is what ``should_skip`` matches against.
|
|
189
221
|
Returns the number of regular files written.
|
|
222
|
+
|
|
223
|
+
``real_root`` は ``root`` の realpath(解決済み文字列)。``None`` の初回呼び出しで
|
|
224
|
+
1 回だけ計算し、再帰には計算済みの値を渡す(ADR-3 追補 2 の hoist。entry 総数 N 回の
|
|
225
|
+
重複計算を 1 回に落とす)。
|
|
226
|
+
|
|
227
|
+
root 側 realpath の失敗(``OSError``)は捕捉せず伝播させる。root が解決不能なのは
|
|
228
|
+
テンプレート根本の異常であり、全 entry を「リンク疑い」として警告付きスキップし
|
|
229
|
+
「0 件コピーの exit 0」を返すより、例外で止める方が利用者に正しく伝わるため
|
|
230
|
+
(per-entry の fail-soft はコピー継続に意味がある場合の設計であり、根本が解決不能なら
|
|
231
|
+
継続に意味がない・ADR-3 追補 1)。
|
|
232
|
+
|
|
233
|
+
リンク判定 ``_is_link_entry`` の適用位置はファイル経路の 2 箇所のみ:
|
|
234
|
+
(i) ``should_skip`` 前段の初回判定 (ii) ``shutil.copy2`` 直前の再検証。
|
|
235
|
+
ディレクトリ側に再検証は追加しない。ディレクトリ entry が判定後にリンクへ
|
|
236
|
+
差し替わっても、再帰先の per-entry 検査で全子 entry の realpath が
|
|
237
|
+
``real_root`` + rel と一致しなくなり内容は漏れないため(構造的再検証・ADR-3 追補 3)。
|
|
190
238
|
"""
|
|
191
239
|
if root is None:
|
|
192
240
|
root = src
|
|
241
|
+
if real_root is None:
|
|
242
|
+
# root 側の realpath はここで 1 回だけ。失敗は上記のとおり伝播させる。
|
|
243
|
+
real_root = os.path.realpath(root)
|
|
193
244
|
dst.mkdir(parents=True, exist_ok=True)
|
|
194
245
|
count = 0
|
|
195
246
|
for entry in src.iterdir():
|
|
196
247
|
rel = entry.relative_to(root).as_posix()
|
|
197
248
|
target = dst / entry.name
|
|
249
|
+
# FR-4: リンク経由の entry は is_dir()/is_file() 分岐より前にスキップする
|
|
250
|
+
# (両者ともリンクを暗黙に辿るため)。EXCLUDE 対象かどうかに関わらず
|
|
251
|
+
# 検査するため should_skip より前段に置く(架空の EXCLUDE リンクも警告する・
|
|
252
|
+
# architecture DC-GP-003 裁定)。
|
|
253
|
+
if _is_link_entry(entry, real_root, rel):
|
|
254
|
+
print(
|
|
255
|
+
f"c3 init: リンク(symlink/junction)疑いの entry をスキップしました: {rel}",
|
|
256
|
+
file=sys.stderr,
|
|
257
|
+
)
|
|
258
|
+
continue
|
|
198
259
|
if entry.is_dir():
|
|
199
|
-
count += _copytree(entry, target, root=root)
|
|
260
|
+
count += _copytree(entry, target, root=root, real_root=real_root)
|
|
200
261
|
# Drop directories that ended up empty (everything inside was skipped).
|
|
201
262
|
if not any(target.iterdir()):
|
|
202
263
|
target.rmdir()
|
|
203
264
|
elif entry.is_file():
|
|
204
265
|
if should_skip(rel):
|
|
205
266
|
continue
|
|
267
|
+
# 初回判定から copy2 到達までの間にリンクへ差し替わる TOCTOU を狭めるため、
|
|
268
|
+
# copy2 の直前で同じ choke point を再検証する(ADR-3 追補 3)。
|
|
269
|
+
# 残余リスク: 再検証と copy2 の間の窓は閉じない。窓を閉じる ``O_NOFOLLOW`` は
|
|
270
|
+
# POSIX 限定で Windows を含む配布対象では採用できないため不採用とし、
|
|
271
|
+
# 脅威モデル(ローカル開発者ツール)上この残余窓は許容する(ADR-3 追補 5)。
|
|
272
|
+
if _is_link_entry(entry, real_root, rel):
|
|
273
|
+
print(
|
|
274
|
+
f"c3 init: リンク(symlink/junction)疑いの entry をスキップしました: {rel}",
|
|
275
|
+
file=sys.stderr,
|
|
276
|
+
)
|
|
277
|
+
continue
|
|
206
278
|
shutil.copy2(entry, target)
|
|
207
279
|
count += 1
|
|
208
280
|
return count
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tests/conftest.py
|
|
3
|
+
|
|
4
|
+
pytest 共通セットアップ。
|
|
5
|
+
worktree の src/ を sys.path の最初に追加し、テストがworktree のコードを使用するようにする。
|
|
6
|
+
.claude/hooks/ も追加し、importlib 経由で stop.py / pre_compact.py を
|
|
7
|
+
ロードするテストが session_utils をインポートできるようにする。
|
|
8
|
+
|
|
9
|
+
リンク(symlink / junction)を実ファイルシステム上に作る共有ヘルパー ``_make_link``
|
|
10
|
+
もここに置く。利用側は ``from tests.conftest import _make_link`` の**絶対 import**
|
|
11
|
+
で参照する(tests/__init__.py が実在するパッケージ構成のため
|
|
12
|
+
``from conftest import ...`` は ModuleNotFoundError になる)。
|
|
13
|
+
"""
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
|
|
20
|
+
# Ensure worktree src is imported, not system-installed c3 package
|
|
21
|
+
worktree_root = Path(__file__).parent.parent
|
|
22
|
+
sys.path.insert(0, str(worktree_root / "src"))
|
|
23
|
+
sys.path.insert(1, str(worktree_root / ".claude" / "hooks"))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _make_link(target: Path, link: Path) -> None:
|
|
27
|
+
"""``link`` を ``target`` を指すリンクとして作る(OS 差を吸収する共有ヘルパー).
|
|
28
|
+
|
|
29
|
+
Windows は junction(``_winapi.CreateJunction``・管理者権限不要)、
|
|
30
|
+
POSIX は ``os.symlink`` を使う。作成に失敗した場合は skip せず fail させる
|
|
31
|
+
(skip すると「リンクを辿らない」という契約が全 OS で無検証になりうる)。
|
|
32
|
+
|
|
33
|
+
呼び出し側が知っておくべき OS 差(実測・2026-08-15 / Windows 11):
|
|
34
|
+
|
|
35
|
+
- junction は ``Path.is_symlink()`` が **False** を返し、``Path.is_dir()`` は
|
|
36
|
+
**True** を返す(=素朴な ``is_symlink()`` ガードでは弾けない)
|
|
37
|
+
- junction は**ディレクトリ専用**。ファイルへのリンクは
|
|
38
|
+
``os.symlink`` が必要で、非特権 Windows では ``WinError 1314``
|
|
39
|
+
(privilege not held) で失敗する
|
|
40
|
+
- 存在しないターゲットへの junction は ``FileNotFoundError`` で作れない
|
|
41
|
+
(dangling リンクのテストは POSIX 限定になる)
|
|
42
|
+
|
|
43
|
+
元は tests/test_stop_agent_memory_warn.py のローカルヘルパーだったものを、
|
|
44
|
+
tests/test_cli_init_symlink_guard.py と共有するため conftest へ移設した。
|
|
45
|
+
"""
|
|
46
|
+
if sys.platform == "win32":
|
|
47
|
+
try:
|
|
48
|
+
import _winapi
|
|
49
|
+
|
|
50
|
+
_winapi.CreateJunction(str(target), str(link))
|
|
51
|
+
except Exception as exc: # pragma: no cover - 環境不備時のみ
|
|
52
|
+
pytest.fail(f"junction の作成に失敗した(skip せず fail させる契約): {exc!r}")
|
|
53
|
+
else:
|
|
54
|
+
try:
|
|
55
|
+
os.symlink(str(target), str(link), target_is_directory=True)
|
|
56
|
+
except OSError as exc: # pragma: no cover - 環境不備時のみ
|
|
57
|
+
pytest.fail(f"symlink の作成に失敗した(skip せず fail させる契約): {exc!r}")
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""[FR-3] .claude/hooks/statusline.py の reconfigure 保護に関する性質テスト。
|
|
2
|
+
|
|
3
|
+
測るべき性質:
|
|
4
|
+
statusline.py はモジュールレベルで sys.stdout / sys.stderr / sys.stdin の
|
|
5
|
+
``reconfigure(encoding='utf-8')`` を呼ぶ。``reconfigure`` 属性を持たない
|
|
6
|
+
ストリームオブジェクトが差し込まれている環境(pytest の ``DontReadFromInput``、
|
|
7
|
+
パイプ・ラッパ経由で差し替えられた擬似ストリーム等)でも、
|
|
8
|
+
**モジュールのロードが AttributeError でクラッシュしてはならない**。
|
|
9
|
+
すなわち CLAUDE.md §9-3 の canonical idiom
|
|
10
|
+
(``try: ... except AttributeError: pass``)による保護が入っていること。
|
|
11
|
+
|
|
12
|
+
Red の正体:
|
|
13
|
+
無保護の現行実装(statusline.py:16-18)では、reconfigure を持たない
|
|
14
|
+
ストリーム注入下で exec_module が ``AttributeError`` を送出して落ちる。
|
|
15
|
+
保護が入ると緑に反転する。AttributeError は握り潰さず伝播させ、
|
|
16
|
+
pytest の traceback に実際の例外型・メッセージが出るようにしている
|
|
17
|
+
(Red が「正しい理由で赤い」ことを実出力で確認できるようにするため)。
|
|
18
|
+
|
|
19
|
+
意図的な設計上の制約(変更しないこと):
|
|
20
|
+
* モジュールのロードは ``importlib.util.spec_from_file_location`` +
|
|
21
|
+
``exec_module`` で **毎回再実行** する。``import statusline`` は
|
|
22
|
+
tests/conftest.py:15 の sys.path 挿入で成立してしまうが、sys.modules
|
|
23
|
+
キャッシュにより 2 回目以降モジュール本体が再実行されず「空の緑」になる。
|
|
24
|
+
前例は tests/hooks/test_statusline.py:38-53 の ``load_statusline()``。
|
|
25
|
+
* 注入するストリームは reconfigure 属性を持たない **実オブジェクト**
|
|
26
|
+
(``io.StringIO``)とする。``MagicMock`` は未定義属性を自動生成するため
|
|
27
|
+
「reconfigure を持つ」ことになり、無保護実装でも赤にならない。
|
|
28
|
+
* 3 ストリームの差し替えは ``monkeypatch.setattr(sys, ...)`` でテスト内に閉じ、
|
|
29
|
+
teardown で確実に復元する。ambient なストリームには依存しない
|
|
30
|
+
(tests/test_statusline.py:18-21 は sys.stdin をグローバル置換したまま
|
|
31
|
+
復元しないため、ambient に依存すると Red が Red にならない)。
|
|
32
|
+
* **reconfigure 呼び出しの「粒度」は敢えて検証しない。** CLAUDE.md §9-3 の
|
|
33
|
+
canonical idiom は 3 ストリームを 1 つの try ブロックで囲む形であり、
|
|
34
|
+
「1 本壊れても残り 2 本は reconfigure されること」を要求すると
|
|
35
|
+
canonical idiom を弾いてしまう。要求するのは
|
|
36
|
+
「クラッシュしないこと」+「対応ストリームには実際に適用されること」の 2 点のみ。
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
import importlib.util
|
|
40
|
+
import io
|
|
41
|
+
import itertools
|
|
42
|
+
import sys
|
|
43
|
+
import types
|
|
44
|
+
from pathlib import Path
|
|
45
|
+
|
|
46
|
+
import pytest
|
|
47
|
+
|
|
48
|
+
STATUSLINE_PATH = (
|
|
49
|
+
Path(__file__).parents[2]
|
|
50
|
+
/ ".claude"
|
|
51
|
+
/ "hooks"
|
|
52
|
+
/ "statusline.py"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
#: statusline.py がモジュールレベルで reconfigure を呼ぶ 3 ストリーム
|
|
56
|
+
STREAM_NAMES = ("stdin", "stdout", "stderr")
|
|
57
|
+
|
|
58
|
+
#: exec_module ごとにユニークなモジュール名を振り、キャッシュ経由の再利用を避ける
|
|
59
|
+
_load_counter = itertools.count()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _stream_without_reconfigure() -> io.StringIO:
|
|
63
|
+
"""``reconfigure`` を持たない実ストリームを返す。
|
|
64
|
+
|
|
65
|
+
テスト自体の有効性(この注入が無保護実装を確実に赤にすること)を
|
|
66
|
+
その場で表明しておく。将来 io.StringIO に reconfigure が生えたら、
|
|
67
|
+
サイレントに空の緑になるのではなくここで落ちる。
|
|
68
|
+
"""
|
|
69
|
+
stream = io.StringIO()
|
|
70
|
+
assert not hasattr(stream, "reconfigure"), (
|
|
71
|
+
"io.StringIO が reconfigure を持つ実装では、この注入は無保護実装を"
|
|
72
|
+
"赤にできない(テストの検出力が失われている)"
|
|
73
|
+
)
|
|
74
|
+
return stream
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _stream_with_reconfigure() -> io.TextIOWrapper:
|
|
78
|
+
"""``reconfigure`` を持ち、適用されたかを ``encoding`` で観測できる実ストリーム。
|
|
79
|
+
|
|
80
|
+
初期 encoding は utf-8 以外(latin-1)にしておく。こうすることで
|
|
81
|
+
「utf-8 になっている」ことが reconfigure が実際に呼ばれた証拠になる。
|
|
82
|
+
"""
|
|
83
|
+
stream = io.TextIOWrapper(io.BytesIO(), encoding="latin-1")
|
|
84
|
+
assert hasattr(stream, "reconfigure")
|
|
85
|
+
assert stream.encoding != "utf-8"
|
|
86
|
+
return stream
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _exec_statusline() -> types.ModuleType:
|
|
90
|
+
"""statusline.py を毎回新規に exec してモジュールオブジェクトを返す。
|
|
91
|
+
|
|
92
|
+
``__name__`` にユニーク名を与えるため ``if __name__ == '__main__'`` 配下の
|
|
93
|
+
``main()`` は起動しない(ロード自体は副作用を持たない)。
|
|
94
|
+
"""
|
|
95
|
+
name = f"_statusline_reconfigure_probe_{next(_load_counter)}"
|
|
96
|
+
spec = importlib.util.spec_from_file_location(name, STATUSLINE_PATH)
|
|
97
|
+
assert spec is not None and spec.loader is not None
|
|
98
|
+
mod = importlib.util.module_from_spec(spec)
|
|
99
|
+
# module_from_spec は sys.modules に登録しない = 毎回本体が再実行される
|
|
100
|
+
assert name not in sys.modules
|
|
101
|
+
spec.loader.exec_module(mod)
|
|
102
|
+
return mod
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _assert_module_fully_executed(mod: types.ModuleType) -> None:
|
|
106
|
+
"""モジュール本体が最後まで実行されたことを確認する。
|
|
107
|
+
|
|
108
|
+
``main`` は statusline.py:136 の最終 def。これが存在することで
|
|
109
|
+
「reconfigure 行で中断せず本体を最後まで通った」ことを担保する
|
|
110
|
+
(途中で例外を握り潰して打ち切る実装を空の緑にしないため)。
|
|
111
|
+
"""
|
|
112
|
+
assert mod.MAX_INPUT == 64 * 1024
|
|
113
|
+
assert callable(mod.main)
|
|
114
|
+
assert callable(mod.render_output)
|
|
115
|
+
assert callable(mod.pct_color)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# 本命の性質: reconfigure 非対応ストリーム下でもロードがクラッシュしない
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
def test_module_loads_when_all_three_streams_lack_reconfigure(monkeypatch):
|
|
123
|
+
"""3 ストリーム全てが reconfigure 非対応でも statusline のロードは成功する。
|
|
124
|
+
|
|
125
|
+
無保護実装では exec_module が AttributeError で落ちる(Red)。
|
|
126
|
+
try/except AttributeError 保護が入ると緑に反転する。
|
|
127
|
+
"""
|
|
128
|
+
for name in STREAM_NAMES:
|
|
129
|
+
monkeypatch.setattr(sys, name, _stream_without_reconfigure())
|
|
130
|
+
|
|
131
|
+
mod = _exec_statusline()
|
|
132
|
+
|
|
133
|
+
_assert_module_fully_executed(mod)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@pytest.mark.parametrize("broken", STREAM_NAMES)
|
|
137
|
+
def test_module_loads_when_single_stream_lacks_reconfigure(monkeypatch, broken):
|
|
138
|
+
"""3 ストリームのうち 1 本だけが reconfigure 非対応でもロードは成功する。
|
|
139
|
+
|
|
140
|
+
「どのストリームが欠けても落ちない」ことを 1 本ずつ独立に固定する。
|
|
141
|
+
残り 2 本は reconfigure 対応の実オブジェクトにしておくことで、
|
|
142
|
+
落ちる原因が対象ストリーム 1 本に限定されることを保証する。
|
|
143
|
+
"""
|
|
144
|
+
for name in STREAM_NAMES:
|
|
145
|
+
if name == broken:
|
|
146
|
+
monkeypatch.setattr(sys, name, _stream_without_reconfigure())
|
|
147
|
+
else:
|
|
148
|
+
monkeypatch.setattr(sys, name, _stream_with_reconfigure())
|
|
149
|
+
|
|
150
|
+
mod = _exec_statusline()
|
|
151
|
+
|
|
152
|
+
_assert_module_fully_executed(mod)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
# 反・空実装ガード: 保護を「reconfigure 呼び出しの削除」で通してはならない
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
def test_reconfigure_is_still_applied_when_streams_support_it(monkeypatch):
|
|
160
|
+
"""reconfigure 対応ストリームには utf-8 が実際に適用される。
|
|
161
|
+
|
|
162
|
+
本テストは保護導入前から緑であり、Red の一部ではない。
|
|
163
|
+
「reconfigure 行を丸ごと削除する」という空実装で FR-3 を通す退行を防ぐ
|
|
164
|
+
回帰ガードとして置く(CLAUDE.md §9-3 が reconfigure 自体を必須としており、
|
|
165
|
+
削除は cp1252 環境での UnicodeEncodeError クラッシュを復活させる)。
|
|
166
|
+
"""
|
|
167
|
+
streams = {}
|
|
168
|
+
for name in STREAM_NAMES:
|
|
169
|
+
stream = _stream_with_reconfigure()
|
|
170
|
+
monkeypatch.setattr(sys, name, stream)
|
|
171
|
+
streams[name] = stream
|
|
172
|
+
|
|
173
|
+
mod = _exec_statusline()
|
|
174
|
+
|
|
175
|
+
_assert_module_fully_executed(mod)
|
|
176
|
+
for name, stream in streams.items():
|
|
177
|
+
assert stream.encoding == "utf-8", (
|
|
178
|
+
f"sys.{name} が utf-8 に reconfigure されていない "
|
|
179
|
+
f"(encoding={stream.encoding!r})。reconfigure 呼び出しが"
|
|
180
|
+
"削除された可能性がある"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_broken_stream_injection_is_actually_detectable(monkeypatch):
|
|
185
|
+
"""テスト媒体の有効性: 注入したストリームが reconfigure を持たないこと。
|
|
186
|
+
|
|
187
|
+
本テストが緑である限り、上の 2 テストの Red は
|
|
188
|
+
「reconfigure 属性の不在」に由来すると言い切れる。
|
|
189
|
+
MagicMock 注入に差し替えられた場合はここが落ちる。
|
|
190
|
+
"""
|
|
191
|
+
for name in STREAM_NAMES:
|
|
192
|
+
monkeypatch.setattr(sys, name, _stream_without_reconfigure())
|
|
193
|
+
|
|
194
|
+
for name in STREAM_NAMES:
|
|
195
|
+
stream = getattr(sys, name)
|
|
196
|
+
assert isinstance(stream, io.StringIO)
|
|
197
|
+
assert not hasattr(stream, "reconfigure")
|