claude-code-conductor 2.74.0__tar.gz → 2.75.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.75.0}/.claude/hooks/statusline.py +6 -3
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.gitignore +4 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/CHANGELOG.md +35 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/PKG-INFO +27 -1
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/README.md +26 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/__init__.py +1 -1
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_init.py +74 -2
- claude_code_conductor-2.75.0/tests/conftest.py +57 -0
- claude_code_conductor-2.75.0/tests/hooks/test_statusline_reconfigure_guard.py +197 -0
- claude_code_conductor-2.75.0/tests/test_cli_init_symlink_guard.py +704 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_statusline.py +10 -1
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_stop_agent_memory_warn.py +5 -22
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.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.75.0}/.claude/.gitignore +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/CLAUDE.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/architect.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/code-reviewer.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/design-critic.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/developer.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/doc-writer.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/interviewer.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/planner.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/project-setup.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/security-reviewer.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/systematic-debugger.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/tester.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/wt_developer.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/wt_systematic-debugger.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/agents/wt_tester.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/breaking-changes.txt +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/deletions.txt +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/docs/autonomous-mode-onboarding.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/docs/config-policy.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/docs/nul-boundary.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/docs/parallel-agents-setup.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/docs/platform-adapters.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/docs/settings.json.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/_hook_utils.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/check_agent_invocation.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/consolidate_memory.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/patterns_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/planner_check.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/post_tool.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/pre_compact.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/pre_tool.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/recall_autorebuild.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/recall_inject.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/report_contract_check.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/report_path_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/restore_session.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/select_tier.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/session_mode_watch.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/session_start.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/session_stop.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/session_utils.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/stop.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/tier_autoapply.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/tier_gap_check.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/hooks/worktree_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/memory/.gitkeep +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/memory/archive/.gitkeep +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/memory/sessions/.gitkeep +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/reports/.gitkeep +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/rules/judgment-principles.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/rules/promoted/index.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/settings.json +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/autonomous-mode/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/autonomous-mode/scripts/mode_line.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/brainstorm/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/codex-review/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/c3-omission-declaration.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/code-review-checklist.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/design-critic-rubric.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/design-rubric.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/interview-rubric.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/plan-design-guidelines.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/reachability-map.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/record-protocol.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/references/security-review-checklist.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/scripts/detect_execution_verification.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/scripts/record_agent_outcome.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/scripts/record_review_decision.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/dev-workflow/scripts/review_hint_inject.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/develop/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/doc/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/extract-lib/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/init-session/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/init-session/scripts/session_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/mcp-config/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/parallel-agents/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/pattern-status/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/promote-pattern/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/recall/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/report-timestamp/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/review-phase/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/setup/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/setup/reference.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/setup/templates/coding-standards-template.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/setup/templates/project-conventions-template.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/start/SKILL.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/skills/start/scripts/archive_reports.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/state/.gitkeep +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/state/setup_done.flag +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/.claude/tmp/.gitkeep +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/ARCHITECTURE.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/LICENSE +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/LICENSES/fastembed-LICENSE +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/LICENSES/fastembed-NOTICE +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/LICENSES/numpy-LICENSE +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/LICENSES/onnxruntime-LICENSE +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/LICENSES/paraphrase-multilingual-MiniLM-L12-v2-LICENSE +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/hatch_build.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/pyproject.toml +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/__main__.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/_db_params.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/_excludes.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/_terminal.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/adapters.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_ask.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_doctor.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_list.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_metrics.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_plan.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_recall.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_run.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_tier.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/cli_update.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/db.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/embedding.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/gitutil.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/mcp_server.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrate.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/001_initial.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/002_agent_cost_runs.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/003_tier_cost.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/004_agent_outcomes.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/005_drop_agent_tier_bandit.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/006_review_decisions_severity.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/007_review_decisions_resolution.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/008_drop_agent_runs.sql +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/README.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/migrations/__init__.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/paths.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/plan_validator.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/platforms.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/pricing.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/question.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/recall_chunker.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/recall_index.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/refgraph.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/src/c3/usage_ingester.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/__init__.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/_pre_compact_helpers.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/_sync_semantics.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/fixtures/usage/README.md +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/fixtures/usage/mainline.jsonl +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/fixtures/usage/subagents/agent-deadbeef.jsonl +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/fixtures/usage/subagents/agent-deadbeef.meta.json +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/__init__.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_check_agent_invocation.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_consolidate_memory.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_detect_execution_verification.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_hook_utils.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_planner_check.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_planner_check_dev.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_post_tool.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_pre_tool.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_recall_autorebuild.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_recall_inject.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_record_agent_outcome.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_record_review_decision.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_report_path_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_restore_session.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_review_hint_inject.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_select_tier.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_select_tier_escalation.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_session_start.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_session_stop.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_session_utils.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_settings_tier_autoapply_registration.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_similarity_boost.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_statusline.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_statusline_template_sync.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_sync_check.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_template_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_tier_autoapply.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_tier_gap_check.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/hooks/test_tier_security_fixes.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/__init__.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/_skill_helpers.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_c3_omission_declaration.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_dev_workflow_no_task_type.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_e0_verification_exit.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_init_session_no_task_type.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_marker_compliance.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_parallel_agents_skill_tier.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_planner_lightweight.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_reachability_map.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_recall_skill.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_session_backlog_reconciliation.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_session_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_setup_templates.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_start_skill_new_flow.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_adapters.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_archive_reports.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_audit_review_decisions.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_check_deletions.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_ci_workflows.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_claude_gitignore.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_ask.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_doctor.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_doctor_launcher.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_entry.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_init.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_list.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_metrics.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_plan.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_recall.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_run.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_tier.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_update_breaking_changes.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_cli_update_deletions.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_db.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_db_connect_lint.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_docstring_consistency.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_embedding.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_excludes.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_extract_breaking_changes.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_gitutil.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_hooks_stdin_idiom.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_init_only_patterns.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_mcp_server_elicit.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_migrate.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_mode_line.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_no_bare_python_launcher.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_no_passonly_tests.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_nul_boundary_lint.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_paths.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_patterns_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_permission_handler_removal.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_plan_validator.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_pre_compact.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_precompact_additional.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_precompact_toctou_fixes.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_pricing.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_recall_chunker.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_recall_index.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_references_migration.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_refgraph.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_refgraph_query.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_refgraph_structural.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_report_contract_check.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_scripts_src_bootstrap.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_scripts_stdout_encoding.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_session_mode_watch.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_session_utils_additional.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_settings_hooks_registration.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_skill_no_builtin_conflict.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_start_skill_archive_contract.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_stop_additional.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_stop_hook.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_stop_precompact_fixes.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_sync_template_stop.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_template_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_usage_ingester.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_verify_wheel.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_worktree_guard.py +0 -0
- {claude_code_conductor-2.74.0 → claude_code_conductor-2.75.0}/tests/test_wt_agent_sync.py +0 -0
|
@@ -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
|
|
|
@@ -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,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.75.0] - 2026-08-16
|
|
4
|
+
|
|
5
|
+
### 追加
|
|
6
|
+
|
|
7
|
+
- **`c3 init` のテンプレート展開(`_copytree`)に symlink / NTFS ジャンクション防御を追加した**:
|
|
8
|
+
各 entry の realpath が「コピー元ルートの realpath + 相対パス」と完全一致することを検査し、
|
|
9
|
+
一致しない entry(symlink・ジャンクション・壊れたリンク)はコピーせず stderr へ 1 件 1 行の警告を
|
|
10
|
+
出してスキップする。`is_symlink()` 単体判定はジャンクションに `False` を返すため採用せず、
|
|
11
|
+
realpath 完全一致方式に一本化した。比較の両辺とも realpath 済みのため、コピー元ルート自体が
|
|
12
|
+
リンク経由(macOS の `/tmp` 等)でも全件スキップに縮退しない。加えて:
|
|
13
|
+
- entry 側の realpath が `OSError`(`ELOOP` 含む)を送出した場合は「リンク疑い」として
|
|
14
|
+
警告付きスキップに倒す(fail-soft・`c3 init` 全体は異常終了しない。ルート側の解決不能は
|
|
15
|
+
テンプレート根本の異常のため従来どおり例外で停止する)
|
|
16
|
+
- `shutil.copy2` 直前にリンク再検証を行い、検査とコピーの間の差し替え(TOCTOU)の窓を狭めた
|
|
17
|
+
(残余の窓は脅威モデル上許容・docstring に記録)
|
|
18
|
+
- 性質テスト `tests/test_cli_init_symlink_guard.py` を新設(symlink 系・ジャンクション実測・
|
|
19
|
+
ルートをリンク経由で渡す回帰ガード・例外系/再検証の検知テストを含む)。ジャンクション/リンク作成の
|
|
20
|
+
テストヘルパー `_make_link` は `tests/conftest.py` へ共有化した
|
|
21
|
+
- `.claude/hooks/statusline.py` の reconfigure 保護テスト
|
|
22
|
+
`tests/hooks/test_statusline_reconfigure_guard.py` を新設した(`reconfigure` 属性を持たない
|
|
23
|
+
ストリーム環境でも hook のロードがクラッシュしないこと)
|
|
24
|
+
|
|
25
|
+
### 変更
|
|
26
|
+
|
|
27
|
+
- `.claude/hooks/statusline.py` のストリーム reconfigure を canonical 順序(stdin→stdout→stderr)+
|
|
28
|
+
`try/except AttributeError` の確立イディオムへ統一した(配布 hook 22 本で唯一の無保護だった)
|
|
29
|
+
- `README.md` / `docs/getting-started.md` の前提条件に仮想環境(venv)の作成手順を追加した。
|
|
30
|
+
PEP 668(externally-managed-environment)環境で素の `pip install` が失敗する旨と、
|
|
31
|
+
隔離環境の但し書き 3 点(Claude Code を起動するシェルでも同じ venv を activate すること・
|
|
32
|
+
`pipx install claude-code-conductor` の代替・同一シェルでの `c3 doctor` による PATH 解決確認)を明記した
|
|
33
|
+
- root `.gitignore` に `.claude/pytest_temp.ini` のガード行を追加した(2026-05-02 に agent 作業残骸として
|
|
34
|
+
混入し v0.4.2 で削除+`EXCLUDE_PATTERNS` ガード化された経緯の git 側の対)。これに伴い
|
|
35
|
+
`tests/test_three_file_sync.py` の許容リスト A から同エントリを削除し、実効検査件数の下限を
|
|
36
|
+
16 / 16(遊びなし)へ締めた
|
|
37
|
+
|
|
3
38
|
## [2.74.0] - 2026-08-15
|
|
4
39
|
|
|
5
40
|
### 追加
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: claude-code-conductor
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.75.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")
|