claude-code-conductor 2.5.0__tar.gz → 2.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/consolidate_memory.py +25 -6
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/permission_handler.py +28 -9
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/post_tool.py +1 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/restore_session.py +16 -9
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/review_hint_inject.py +10 -2
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/select_tier.py +52 -15
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/session_utils.py +1 -2
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/stop.py +12 -2
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/settings.json +1 -0
- claude_code_conductor-2.6.1/.claude/skills/codex-review/SKILL.md +211 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/CHANGELOG.md +76 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/PKG-INFO +1 -1
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/__init__.py +1 -1
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/adapters.py +6 -1
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/cli_ask.py +15 -2
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/cli_tier.py +6 -27
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/db.py +55 -1
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/mcp_server.py +9 -1
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/question.py +6 -2
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_consolidate_memory.py +168 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_permission_handler.py +66 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_planner_check.py +96 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_select_tier.py +96 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_cli_ask.py +69 -0
- claude_code_conductor-2.6.1/tests/test_mcp_server_elicit.py +113 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/CLAUDE.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/agents/architect.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/agents/code-reviewer.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/agents/developer.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/agents/doc-writer.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/agents/interviewer.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/agents/planner.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/agents/project-setup.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/agents/security-reviewer.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/agents/systematic-debugger.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/agents/tester.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/agents/wt_developer.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/agents/wt_systematic-debugger.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/agents/wt_tester.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/docs/platform-adapters.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/docs/settings.json.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/pre_compact.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/pre_tool.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/record_review_decision.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/record_tier_outcome.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/schema.sql +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/session_start.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/session_stop.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/statusline.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/subagent_log.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/worktree_guard.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/memory/.gitkeep +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/permission_rules.json +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/rules/code-review-checklist.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/rules/promoted/index.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/rules/security-review-checklist.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/code-review/SKILL.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/dev-workflow/SKILL.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/develop/SKILL.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/doc/SKILL.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/extract-lib/SKILL.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/init-session/SKILL.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/mcp-config/SKILL.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/parallel-agents/SKILL.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/pattern-status/SKILL.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/promote-pattern/SKILL.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/report-timestamp/SKILL.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/report-timestamp/scripts/get_timestamp.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/setup/SKILL.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/start/SKILL.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/skills/task-routing/SKILL.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/state/.gitkeep +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.gitignore +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/LICENSE +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/README.md +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/hatch_build.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/pyproject.toml +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/__main__.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/_excludes.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/_terminal.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/cli.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/cli_doctor.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/cli_init.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/cli_list.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/cli_plan.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/cli_update.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/paths.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/plan_validator.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/src/c3/platforms.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/__init__.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/conftest.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/__init__.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_pip_reinstall_reminder.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_post_tool.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_pre_tool.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_record_tier_outcome.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_restore_session.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_review_hint_inject.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_select_tier_escalation.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_session_start.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_session_stop.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_session_utils.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_settings_local_absolute_paths.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_similarity_boost.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_statusline.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_statusline_template_sync.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_subagent_log.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_sync_check.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/hooks/test_template_guard.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/skills/__init__.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/skills/test_session_backlog_reconciliation.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/skills/test_start_skill_bugfix_flow.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/skills/test_start_skill_security_audit_phase.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/skills/test_task_routing_skill.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_adapters.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_cli_init.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_cli_list.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_cli_plan.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_cli_tier.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_docstring_consistency.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_excludes.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_paths.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_plan_validator.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_pre_compact.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_precompact_additional.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_precompact_toctou_fixes.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_session_utils_additional.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_statusline.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_stop_additional.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_stop_hook.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_stop_precompact_fixes.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_sync_template_stop.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_template_pre_tool_hook.py +0 -0
- {claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/tests/test_worktree_guard.py +0 -0
{claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/consolidate_memory.py
RENAMED
|
@@ -193,9 +193,12 @@ def build_summary_markdown(
|
|
|
193
193
|
"""集約結果の Markdown を組み立てる。"""
|
|
194
194
|
if today is None:
|
|
195
195
|
today = datetime.now(timezone.utc)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
196
|
+
elif not isinstance(today, datetime):
|
|
197
|
+
today = datetime.combine(today, datetime.min.time(), tzinfo=timezone.utc)
|
|
198
|
+
if today.tzinfo is None:
|
|
199
|
+
today = today.replace(tzinfo=timezone.utc)
|
|
200
|
+
today_str = today.date().isoformat()
|
|
201
|
+
start_str = (today.date() - timedelta(days=window_days - 1)).isoformat()
|
|
199
202
|
|
|
200
203
|
lines: list[str] = [
|
|
201
204
|
"# 集約サマリ",
|
|
@@ -527,8 +530,19 @@ def _atomic_write(output_path: str, payload: str) -> bool:
|
|
|
527
530
|
|
|
528
531
|
|
|
529
532
|
def _escape_for_xml(text: str) -> str:
|
|
530
|
-
"""XML
|
|
531
|
-
|
|
533
|
+
"""XML タグ境界突破を防ぐためタグ記号・引用符をエンティティに変換する。[SR-AI-001]
|
|
534
|
+
|
|
535
|
+
引用符 (" / ') もエスケープすることで、属性値混入による境界突破を防ぐ。
|
|
536
|
+
変換順: & を最初に変換してから他の文字を変換する(二重エスケープ防止)。
|
|
537
|
+
"""
|
|
538
|
+
return (
|
|
539
|
+
text
|
|
540
|
+
.replace("&", "&")
|
|
541
|
+
.replace("<", "<")
|
|
542
|
+
.replace(">", ">")
|
|
543
|
+
.replace('"', """)
|
|
544
|
+
.replace("'", "'")
|
|
545
|
+
)
|
|
532
546
|
|
|
533
547
|
|
|
534
548
|
def _build_llm_prompt(
|
|
@@ -649,8 +663,13 @@ def build_llm_summary_section(
|
|
|
649
663
|
)
|
|
650
664
|
|
|
651
665
|
try:
|
|
666
|
+
# セキュリティ設計 [SR-AI-001]:
|
|
667
|
+
# --dangerously-skip-permissions は全ツールへのフルアクセスを付与するため除去。
|
|
668
|
+
# LLM 要約生成はテキスト出力のみで十分なので --tools "" で全ツールを無効化する。
|
|
669
|
+
# これにより子 claude プロセスからのファイル読み書き・Bash 実行が完全にブロックされる。
|
|
670
|
+
# prompt は引数経由でのみ渡し、ファイル書き込みは親プロセス側で行う(職責分離)。
|
|
652
671
|
result = subprocess.run(
|
|
653
|
-
[claude_exe, "-p", prompt, "--
|
|
672
|
+
[claude_exe, "-p", prompt, "--tools", ""],
|
|
654
673
|
**run_kwargs,
|
|
655
674
|
)
|
|
656
675
|
except subprocess.TimeoutExpired:
|
{claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/permission_handler.py
RENAMED
|
@@ -11,6 +11,7 @@ import platform
|
|
|
11
11
|
import re
|
|
12
12
|
import subprocess
|
|
13
13
|
import sys
|
|
14
|
+
from urllib.parse import urlparse
|
|
14
15
|
|
|
15
16
|
try:
|
|
16
17
|
sys.stdin.reconfigure(encoding='utf-8')
|
|
@@ -24,13 +25,17 @@ _CLAUDE_DIR = os.path.dirname(_HOOKS_DIR)
|
|
|
24
25
|
RULES_PATH = os.path.join(_CLAUDE_DIR, 'permission_rules.json')
|
|
25
26
|
|
|
26
27
|
DEFAULT_RULES: dict = {'auto_allow': [], 'notify_on_auto': True}
|
|
28
|
+
_CREATE_NO_WINDOW = 0x08000000
|
|
29
|
+
# p_arg 付きパターンに対してシェル制御文字を含むコマンドの自動承認を防ぐ
|
|
30
|
+
_SHELL_INJECTION_RE = re.compile(r';|&&|\|\||`|\$\(')
|
|
27
31
|
|
|
28
32
|
|
|
29
33
|
def notify(message: str) -> None:
|
|
30
34
|
system = platform.system()
|
|
31
35
|
try:
|
|
32
36
|
if system == 'Darwin':
|
|
33
|
-
safe = message.replace('
|
|
37
|
+
safe = message.replace('\n', ' ').replace('\r', ' ')
|
|
38
|
+
safe = safe.replace('\\', '\\\\').replace('"', '\\"')
|
|
34
39
|
subprocess.run(
|
|
35
40
|
['osascript', '-e', f'display notification "{safe}" with title "Claude Code"'],
|
|
36
41
|
capture_output=True, timeout=5
|
|
@@ -41,21 +46,26 @@ def notify(message: str) -> None:
|
|
|
41
46
|
capture_output=True, timeout=5
|
|
42
47
|
)
|
|
43
48
|
elif system == 'Windows':
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
import base64
|
|
50
|
+
# メッセージを UTF-8 → Base64 に変換し、PowerShell スクリプト本文に
|
|
51
|
+
# 生のユーザーデータを含めない。Base64 文字列は英数字と +/= のみで
|
|
52
|
+
# PowerShell インジェクション ([SR-INJ-002]) が物理的に不可能。
|
|
53
|
+
msg_b64 = base64.b64encode(message.encode('utf-8')).decode('ascii')
|
|
54
|
+
ps_script = (
|
|
47
55
|
'Add-Type -AssemblyName System.Windows.Forms; '
|
|
48
56
|
'$n = New-Object System.Windows.Forms.NotifyIcon; '
|
|
49
57
|
'$n.Icon = [System.Drawing.SystemIcons]::Information; '
|
|
50
58
|
'$n.Visible = $true; '
|
|
51
|
-
f'$
|
|
59
|
+
f'$msg = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("{msg_b64}")); '
|
|
60
|
+
'$n.ShowBalloonTip(4000, \'Claude Code\', $msg, '
|
|
52
61
|
'[System.Windows.Forms.ToolTipIcon]::Info); '
|
|
53
62
|
'Start-Sleep -Milliseconds 4500; '
|
|
54
63
|
'$n.Dispose()'
|
|
55
64
|
)
|
|
65
|
+
encoded = base64.b64encode(ps_script.encode('utf-16-le')).decode('ascii')
|
|
56
66
|
subprocess.Popen(
|
|
57
|
-
['powershell', '-WindowStyle', 'Hidden', '-
|
|
58
|
-
creationflags=
|
|
67
|
+
['powershell', '-WindowStyle', 'Hidden', '-EncodedCommand', encoded],
|
|
68
|
+
creationflags=_CREATE_NO_WINDOW
|
|
59
69
|
)
|
|
60
70
|
except Exception as e:
|
|
61
71
|
print(f'[permission_handler] 通知エラー: {e}', file=sys.stderr)
|
|
@@ -97,14 +107,21 @@ def matches_pattern(tool_name: str, tool_input: dict, pattern: str) -> bool:
|
|
|
97
107
|
|
|
98
108
|
# ツール別に照合対象を決定
|
|
99
109
|
if tool_name == 'Bash':
|
|
100
|
-
|
|
110
|
+
command = tool_input.get('command', '')
|
|
111
|
+
if _SHELL_INJECTION_RE.search(command):
|
|
112
|
+
return False
|
|
113
|
+
subject = command
|
|
101
114
|
elif tool_name in ('Write', 'Edit', 'Read', 'Glob'):
|
|
102
115
|
subject = tool_input.get('file_path', tool_input.get('pattern', ''))
|
|
103
116
|
elif tool_name == 'WebFetch':
|
|
104
117
|
url = tool_input.get('url', '')
|
|
105
118
|
if p_arg.startswith('domain:'):
|
|
106
119
|
domain = p_arg[len('domain:'):]
|
|
107
|
-
|
|
120
|
+
try:
|
|
121
|
+
host = urlparse(url).hostname or ''
|
|
122
|
+
return host == domain or host.endswith('.' + domain)
|
|
123
|
+
except Exception:
|
|
124
|
+
return False
|
|
108
125
|
subject = url
|
|
109
126
|
else:
|
|
110
127
|
subject = str(tool_input)
|
|
@@ -132,6 +149,8 @@ def main() -> None:
|
|
|
132
149
|
|
|
133
150
|
tool_name = payload.get('tool_name', '')
|
|
134
151
|
tool_input = payload.get('tool_input', {})
|
|
152
|
+
if not isinstance(tool_input, dict):
|
|
153
|
+
tool_input = {}
|
|
135
154
|
rules = load_rules()
|
|
136
155
|
description = describe_tool(tool_name, tool_input)
|
|
137
156
|
|
|
@@ -43,6 +43,7 @@ _BINARY_SAMPLE_BYTES = 8 * 1024
|
|
|
43
43
|
# applicable_extensions が None なら全対象拡張子に適用。
|
|
44
44
|
_QUALITY_PATTERNS: list[tuple[str, "re.Pattern[str]", "frozenset[str] | None"]] = [
|
|
45
45
|
('console.log', re.compile(r'console\.log\('), frozenset({'.js', '.ts', '.tsx', '.jsx'})),
|
|
46
|
+
# Python の print() のみ対象。他言語の print は別パターン名で追加すること
|
|
46
47
|
('print', re.compile(r'^\s*print\('), frozenset({'.py'})),
|
|
47
48
|
('TODO', re.compile(r'\bTODO\b'), None),
|
|
48
49
|
('FIXME', re.compile(r'\bFIXME\b'), None),
|
{claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/restore_session.py
RENAMED
|
@@ -5,7 +5,6 @@ restore_session.py: SessionStart(compact) hook.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
-
import re
|
|
9
8
|
import sys
|
|
10
9
|
|
|
11
10
|
try:
|
|
@@ -20,6 +19,19 @@ _CLAUDE_DIR = os.path.dirname(_HOOKS_DIR)
|
|
|
20
19
|
SESSIONS_DIR = os.path.join(_CLAUDE_DIR, 'memory', 'sessions')
|
|
21
20
|
|
|
22
21
|
|
|
22
|
+
def _load_session_utils():
|
|
23
|
+
"""session_utils モジュールを動的にロードして返す(同階層)。"""
|
|
24
|
+
import importlib.util
|
|
25
|
+
|
|
26
|
+
util_path = os.path.join(_HOOKS_DIR, "session_utils.py")
|
|
27
|
+
spec = importlib.util.spec_from_file_location("session_utils", util_path)
|
|
28
|
+
if spec is None or spec.loader is None:
|
|
29
|
+
raise ImportError(f"session_utils が見つかりません: {util_path}")
|
|
30
|
+
module = importlib.util.module_from_spec(spec)
|
|
31
|
+
spec.loader.exec_module(module) # type: ignore[attr-defined]
|
|
32
|
+
return module
|
|
33
|
+
|
|
34
|
+
|
|
23
35
|
def find_latest_session() -> str | None:
|
|
24
36
|
if not os.path.isdir(SESSIONS_DIR):
|
|
25
37
|
return None
|
|
@@ -29,14 +41,6 @@ def find_latest_session() -> str | None:
|
|
|
29
41
|
return os.path.join(SESSIONS_DIR, max(files))
|
|
30
42
|
|
|
31
43
|
|
|
32
|
-
def extract_section(content: str, heading: str) -> str:
|
|
33
|
-
pattern = rf'## {re.escape(heading)}\n(.*?)(?=\n## |\n<!--|\Z)'
|
|
34
|
-
match = re.search(pattern, content, re.DOTALL)
|
|
35
|
-
if not match:
|
|
36
|
-
return ''
|
|
37
|
-
return match.group(1).strip()
|
|
38
|
-
|
|
39
|
-
|
|
40
44
|
def main():
|
|
41
45
|
path = find_latest_session()
|
|
42
46
|
if not path or not os.path.exists(path):
|
|
@@ -45,6 +49,9 @@ def main():
|
|
|
45
49
|
with open(path, 'r', encoding='utf-8') as f:
|
|
46
50
|
content = f.read()
|
|
47
51
|
|
|
52
|
+
session_utils = _load_session_utils()
|
|
53
|
+
extract_section = session_utils.extract_section
|
|
54
|
+
|
|
48
55
|
date_str = os.path.basename(path).replace('.tmp', '')
|
|
49
56
|
todos = extract_section(content, '残タスク')
|
|
50
57
|
successes = extract_section(content, 'うまくいったアプローチ')
|
{claude_code_conductor-2.5.0 → claude_code_conductor-2.6.1}/.claude/hooks/review_hint_inject.py
RENAMED
|
@@ -25,6 +25,7 @@ from __future__ import annotations
|
|
|
25
25
|
import os
|
|
26
26
|
import re
|
|
27
27
|
import sys
|
|
28
|
+
import tempfile
|
|
28
29
|
from datetime import datetime, timedelta, timezone
|
|
29
30
|
from pathlib import Path
|
|
30
31
|
|
|
@@ -164,17 +165,24 @@ def append_hints_to_report(
|
|
|
164
165
|
return False
|
|
165
166
|
|
|
166
167
|
new_text = text.rstrip() + "\n\n" + hint_section
|
|
168
|
+
fd, tmp_path = tempfile.mkstemp(prefix=".tmp_", dir=report_path.parent)
|
|
167
169
|
try:
|
|
168
|
-
|
|
170
|
+
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
171
|
+
f.write(new_text)
|
|
172
|
+
os.replace(tmp_path, report_path)
|
|
169
173
|
except OSError as exc:
|
|
170
174
|
print(f"[review_hint_inject] failed to write {report_path}: {exc}", file=sys.stderr)
|
|
175
|
+
try:
|
|
176
|
+
if os.path.exists(tmp_path):
|
|
177
|
+
os.unlink(tmp_path)
|
|
178
|
+
except OSError:
|
|
179
|
+
pass
|
|
171
180
|
return False
|
|
172
181
|
return True
|
|
173
182
|
|
|
174
183
|
|
|
175
184
|
def collect_decisions_for_report(report_text: str) -> dict[str, list[dict]]:
|
|
176
185
|
"""レポート内の checklist_id を全て抽出し、各 ID の過去判断を取得する。"""
|
|
177
|
-
_ensure_c3_db_path_in_sys_path()
|
|
178
186
|
try:
|
|
179
187
|
from c3 import db as c3_db # noqa: PLC0415
|
|
180
188
|
except ImportError as exc:
|
|
@@ -29,6 +29,7 @@ import hashlib
|
|
|
29
29
|
import json
|
|
30
30
|
import os
|
|
31
31
|
import random
|
|
32
|
+
import re
|
|
32
33
|
import sys
|
|
33
34
|
from pathlib import Path
|
|
34
35
|
|
|
@@ -40,8 +41,13 @@ except AttributeError:
|
|
|
40
41
|
pass
|
|
41
42
|
|
|
42
43
|
|
|
43
|
-
# 学習データ収集期の閾値(合計試行数がこの値未満なら uniform
|
|
44
|
-
LEARNING_THRESHOLD
|
|
44
|
+
# 学習データ収集期の閾値(合計試行数がこの値未満なら uniform 選択)。
|
|
45
|
+
# SSOT: c3.db.LEARNING_THRESHOLD から取得し、import 失敗時はフォールバック値 30 を使う(CR-M-002)。
|
|
46
|
+
try:
|
|
47
|
+
from c3 import db as _c3_db_const # type: ignore[import-not-found]
|
|
48
|
+
LEARNING_THRESHOLD: int = _c3_db_const.LEARNING_THRESHOLD
|
|
49
|
+
except ImportError:
|
|
50
|
+
LEARNING_THRESHOLD = 30
|
|
45
51
|
|
|
46
52
|
# 複雑度推定のキーワード
|
|
47
53
|
SIMPLE_KEYWORDS = frozenset({
|
|
@@ -68,6 +74,33 @@ _PROMPT_PREFIX_MAX = 200
|
|
|
68
74
|
SIMILARITY_STRONG_THRESHOLD = 0.8 # この値以上で complexity を上書き
|
|
69
75
|
SIMILARITY_WEAK_THRESHOLD = 0.6 # この値以上で信頼度補強のみ
|
|
70
76
|
|
|
77
|
+
# prompt 保存前のマスク処理: pre_tool.py の _SECRET_PATTERNS と同等のパターン。
|
|
78
|
+
# 検出した値を *** に置換してから保存することで二次漏洩を防ぐ。
|
|
79
|
+
# NOTE: ここで値をマスクすることで類似度推定の精度も若干下がる可能性があるが、
|
|
80
|
+
# セキュリティ優先として許容する(設計書に記載なし: SR-V-001 対応と判断)。
|
|
81
|
+
_MASK_PATTERNS: list[re.Pattern[str]] = [
|
|
82
|
+
re.compile(r'(password=)\S+', re.IGNORECASE),
|
|
83
|
+
re.compile(r'(api[_-]?key=)\S+', re.IGNORECASE),
|
|
84
|
+
re.compile(r'(Bearer\s+)[\w\-\.]+', re.IGNORECASE),
|
|
85
|
+
re.compile(r'(\btoken=)\S+', re.IGNORECASE),
|
|
86
|
+
re.compile(r'(\bsecret=)\S+', re.IGNORECASE),
|
|
87
|
+
re.compile(r'(aws_secret_access_key=)\S+', re.IGNORECASE),
|
|
88
|
+
re.compile(r'(-----BEGIN [A-Z ]*PRIVATE KEY-----)[\s\S]*?(-----END [A-Z ]*PRIVATE KEY-----)'),
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _mask_secrets(text: str) -> str:
|
|
93
|
+
"""秘密情報パターンにマッチする値部分を *** に置換して返す。
|
|
94
|
+
|
|
95
|
+
キー名やプレフィックスは残し、値のみを置換することで
|
|
96
|
+
「何が含まれていたか」は伝わらないようにする。
|
|
97
|
+
PEM ブロックは開始タグ〜終了タグ全体を *** に置換する。
|
|
98
|
+
"""
|
|
99
|
+
result = text
|
|
100
|
+
for pattern in _MASK_PATTERNS:
|
|
101
|
+
result = pattern.sub(lambda m: m.group(1) + "***", result)
|
|
102
|
+
return result
|
|
103
|
+
|
|
71
104
|
# prompt-history.jsonl の末尾から読む最大行数(パフォーマンス対策)
|
|
72
105
|
_PROMPT_HISTORY_SCAN_LINES = 1000
|
|
73
106
|
|
|
@@ -78,8 +111,10 @@ def _prompt_prefix_and_hash(prompt: str) -> tuple[str, str]:
|
|
|
78
111
|
"""prompt から (prefix, hash) を抽出する。
|
|
79
112
|
|
|
80
113
|
Phase 2-C: prefix 200 文字 + SHA256 先頭 16 文字(プライバシー対策)。
|
|
114
|
+
SR-V-001: prefix に含まれる秘密情報パターンは *** にマスクしてから保存する。
|
|
115
|
+
hash はマスク前の原文から計算する(一意性を保つため)。
|
|
81
116
|
"""
|
|
82
|
-
prefix = prompt[:_PROMPT_PREFIX_MAX]
|
|
117
|
+
prefix = _mask_secrets(prompt[:_PROMPT_PREFIX_MAX])
|
|
83
118
|
h = hashlib.sha256(prompt.encode("utf-8", errors="replace")).hexdigest()[:16]
|
|
84
119
|
return prefix, h
|
|
85
120
|
|
|
@@ -228,6 +263,17 @@ _ESCALATION_MAP: dict[str, str] = {
|
|
|
228
263
|
ESCALATION_THRESHOLD = 0.5
|
|
229
264
|
|
|
230
265
|
|
|
266
|
+
def _db_failure_rate(complexity: str, tier: str) -> tuple:
|
|
267
|
+
"""DB から failure rate を読み取るデフォルト実装。
|
|
268
|
+
|
|
269
|
+
c3_db のインポートに失敗した場合は ``(None, 0)`` を返す。
|
|
270
|
+
"""
|
|
271
|
+
c3_db = _load_c3_db_module()
|
|
272
|
+
if c3_db is None:
|
|
273
|
+
return None, 0
|
|
274
|
+
return c3_db.read_tier_failure_rate(complexity, tier)
|
|
275
|
+
|
|
276
|
+
|
|
231
277
|
def maybe_escalate(
|
|
232
278
|
complexity: str,
|
|
233
279
|
chosen_tier: str,
|
|
@@ -241,7 +287,7 @@ def maybe_escalate(
|
|
|
241
287
|
chosen_tier: select_tier が選んだ tier。
|
|
242
288
|
failure_rate_fn: テスト用に注入可能な
|
|
243
289
|
``(complexity, tier) -> (rate_or_None, sample_count)``。
|
|
244
|
-
省略時は :func:`
|
|
290
|
+
省略時は :func:`_db_failure_rate` を使う。
|
|
245
291
|
|
|
246
292
|
Returns:
|
|
247
293
|
``(effective_tier, escalation_reason)``。
|
|
@@ -251,17 +297,8 @@ def maybe_escalate(
|
|
|
251
297
|
if chosen_tier not in _ESCALATION_MAP:
|
|
252
298
|
return chosen_tier, None
|
|
253
299
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
c3_db = _load_c3_db_module()
|
|
257
|
-
if c3_db is None:
|
|
258
|
-
return chosen_tier, None
|
|
259
|
-
|
|
260
|
-
def _db_failure_rate(complexity: str, tier: str) -> tuple:
|
|
261
|
-
return c3_db.read_tier_failure_rate(complexity, tier)
|
|
262
|
-
failure_rate_fn = _db_failure_rate
|
|
263
|
-
|
|
264
|
-
rate, samples = failure_rate_fn(complexity, chosen_tier)
|
|
300
|
+
effective_fn = failure_rate_fn or _db_failure_rate
|
|
301
|
+
rate, samples = effective_fn(complexity, chosen_tier)
|
|
265
302
|
if rate is None or rate < ESCALATION_THRESHOLD:
|
|
266
303
|
return chosen_tier, None
|
|
267
304
|
|
|
@@ -73,8 +73,7 @@ def extract_section(content: str, heading: str) -> str:
|
|
|
73
73
|
セクション本文(前後の空白除去済み)、または空文字列。
|
|
74
74
|
|
|
75
75
|
Notes:
|
|
76
|
-
|
|
77
|
-
当面そのまま残す。新規コード(consolidate_memory.py 等)は本関数を使う。
|
|
76
|
+
新規コード(consolidate_memory.py 等)は本関数を使う。
|
|
78
77
|
"""
|
|
79
78
|
pattern = rf'## {re.escape(heading)}\n(.*?)(?=\n## |\n<!--|\Z)'
|
|
80
79
|
match = re.search(pattern, content, re.DOTALL)
|
|
@@ -141,10 +141,15 @@ def extract_session_patterns(date_str: str) -> list:
|
|
|
141
141
|
|
|
142
142
|
|
|
143
143
|
def _parse_session_date(date_str: str):
|
|
144
|
+
"""Parse a yyyymmdd date string and return a date object, or None if invalid.
|
|
145
|
+
|
|
146
|
+
Returns None (rather than a sentinel like date.min) so callers can explicitly
|
|
147
|
+
filter out unparseable entries instead of silently treating them as very old.
|
|
148
|
+
"""
|
|
144
149
|
try:
|
|
145
150
|
return datetime.strptime(date_str, '%Y%m%d').date()
|
|
146
151
|
except ValueError:
|
|
147
|
-
return
|
|
152
|
+
return None
|
|
148
153
|
|
|
149
154
|
|
|
150
155
|
def _build_sessions_by_date(sessions_dir: str) -> set[str]:
|
|
@@ -164,9 +169,14 @@ def count_sessions_since(registered_date_str: str, sessions_by_date: set[str] |
|
|
|
164
169
|
return 1
|
|
165
170
|
sessions_by_date = _build_sessions_by_date(SESSIONS_DIR)
|
|
166
171
|
registered = _parse_session_date(registered_date_str)
|
|
172
|
+
# If registered_date_str itself is unparseable, we cannot determine a baseline;
|
|
173
|
+
# fall back to counting all sessions rather than silently returning a wrong value.
|
|
174
|
+
if registered is None:
|
|
175
|
+
return max(len(sessions_by_date), 1)
|
|
167
176
|
count = sum(
|
|
168
177
|
1 for d in sessions_by_date
|
|
169
|
-
|
|
178
|
+
# Skip session entries whose date string is malformed (parsed as None).
|
|
179
|
+
if (parsed := _parse_session_date(d)) is not None and parsed >= registered
|
|
170
180
|
)
|
|
171
181
|
return max(count, 1)
|
|
172
182
|
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: codex-review
|
|
3
|
+
description: |
|
|
4
|
+
Codex CLI に .codex/agents/ のエージェント定義を読み込ませ、
|
|
5
|
+
code-reviewer または security-reviewer のペルソナとしてコードレビューを実行するスキル。
|
|
6
|
+
C3 Codex アダプター(.codex/ ディレクトリと AGENTS.md)がセットアップ済みの場合のみ有効。
|
|
7
|
+
通常の C3 code-reviewer / security-reviewer と同じレポート契約([CR-XX-NNN] / [SR-XX-NNN])を維持する。
|
|
8
|
+
|
|
9
|
+
【単一ファイルモード】特定ファイルを Codex でレビューする:
|
|
10
|
+
args: "code-reviewer src/path/to/file.py"
|
|
11
|
+
args: "security-reviewer src/path/to/file.py"
|
|
12
|
+
|
|
13
|
+
【ワークフローモード】git diff の変更全体を Codex でレビューする(通常ワークフローとの並走用):
|
|
14
|
+
args: "workflow code-reviewer"
|
|
15
|
+
args: "workflow security-reviewer"
|
|
16
|
+
|
|
17
|
+
呼び出しトリガー:
|
|
18
|
+
- 「Codex でレビューして」「Codex に code-reviewer をやらせて」
|
|
19
|
+
- 「codex-review」「/codex-review」
|
|
20
|
+
- 「Codex でセキュリティレビュー」
|
|
21
|
+
- 「Codex も並列でレビューさせて」「ワークフローで Codex レビュー」
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# codex-review
|
|
25
|
+
|
|
26
|
+
`.codex/agents/{reviewer_type}.toml` のエージェント定義を `codex exec` のプロンプトに埋め込み、
|
|
27
|
+
Codex 自身が code-reviewer / security-reviewer ペルソナとしてレビューを実行する。
|
|
28
|
+
|
|
29
|
+
2つのモードがある:
|
|
30
|
+
- **単一ファイルモード**: 指定ファイルを直接レビュー
|
|
31
|
+
- **ワークフローモード**: `git diff HEAD` の変更差分を対象にレビュー(通常ワークフローの Claude レビューと並走させる想定)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 前提確認
|
|
36
|
+
|
|
37
|
+
Glob で `.codex/agents/code-reviewer.toml` を確認する。
|
|
38
|
+
|
|
39
|
+
存在しない場合は以下を表示してスキルを終了する:
|
|
40
|
+
```
|
|
41
|
+
[codex-review] Codex アダプターがセットアップされていません。
|
|
42
|
+
先に `c3 init --platform codex` を実行してください。
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Step 1: モードとレビュー設定を確認する
|
|
48
|
+
|
|
49
|
+
args を解析する:
|
|
50
|
+
- `"workflow code-reviewer"` → ワークフローモード + code-reviewer
|
|
51
|
+
- `"workflow security-reviewer"` → ワークフローモード + security-reviewer
|
|
52
|
+
- `"code-reviewer src/path/file.py"` → 単一ファイルモード + code-reviewer
|
|
53
|
+
- `"security-reviewer src/path/file.py"` → 単一ファイルモード + security-reviewer
|
|
54
|
+
|
|
55
|
+
args が不十分な場合、AskUserQuestion でレビュー種別とモードを確認する:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"questions": [
|
|
60
|
+
{
|
|
61
|
+
"question": "実行するレビューの種類を選択してください",
|
|
62
|
+
"header": "レビュー種別",
|
|
63
|
+
"multiSelect": false,
|
|
64
|
+
"options": [
|
|
65
|
+
{ "label": "code-reviewer", "description": "品質・保守性・パフォーマンスをレビュー" },
|
|
66
|
+
{ "label": "security-reviewer", "description": "OWASP Top 10 基準でセキュリティ脆弱性をレビュー" }
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"question": "レビュー対象を選択してください",
|
|
71
|
+
"header": "対象",
|
|
72
|
+
"multiSelect": false,
|
|
73
|
+
"options": [
|
|
74
|
+
{ "label": "ワークフロー(git diff)", "description": "現在の変更差分全体をレビュー。通常ワークフローと並走させる場合はこちら" },
|
|
75
|
+
{ "label": "単一ファイル", "description": "特定のファイルを指定してレビュー" }
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
単一ファイルモードでファイルパスが未指定の場合:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"questions": [{
|
|
87
|
+
"question": "レビュー対象のファイルパスを入力してください(「その他」から入力)",
|
|
88
|
+
"header": "対象ファイル",
|
|
89
|
+
"multiSelect": false,
|
|
90
|
+
"options": [
|
|
91
|
+
{ "label": "その他(自由入力)", "description": "例: src/c3/cli_ask.py" }
|
|
92
|
+
]
|
|
93
|
+
}]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Step 2: レビュー対象のコンテンツを取得する
|
|
100
|
+
|
|
101
|
+
### ワークフローモードの場合
|
|
102
|
+
|
|
103
|
+
Bash で以下を実行する:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
git diff HEAD --stat
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
変更ファイルがない場合は `git diff HEAD~1 --stat` を試す。
|
|
110
|
+
それも空の場合は「変更差分が見つかりません。コミット済みの変更を対象にするには `git diff HEAD~1` が必要です」と表示して終了する。
|
|
111
|
+
|
|
112
|
+
続けて差分本体を取得する:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
git diff HEAD
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
差分が長い場合(目安 200 行超)は先頭 200 行に制限する:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
git diff HEAD | head -200
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
取得した内容を `{review_target}` として保持する。対象説明文は「git diff HEAD の変更差分」とする。
|
|
125
|
+
|
|
126
|
+
### 単一ファイルモードの場合
|
|
127
|
+
|
|
128
|
+
指定パスを Read してファイル内容を `{review_target}` として保持する。
|
|
129
|
+
対象説明文はファイルパスとする。
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Step 3: エージェント定義を Read する
|
|
134
|
+
|
|
135
|
+
`.codex/agents/{reviewer_type}.toml` を Read して `{agent_toml}` として保持する。
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Step 4: タイムスタンプを取得する
|
|
140
|
+
|
|
141
|
+
Skill ツールで `report-timestamp` を呼び出して `{timestamp}` を取得する。
|
|
142
|
+
|
|
143
|
+
レポートファイル名:
|
|
144
|
+
- code-reviewer: `code-review-report-{timestamp}.md`
|
|
145
|
+
- security-reviewer: `security-review-report-{timestamp}.md`
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Step 5: codex exec を実行する
|
|
150
|
+
|
|
151
|
+
Bash で以下を実行する(`--sandbox workspace-write`)。
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
codex exec "以下の定義に従ってエージェントとして動作してください。
|
|
155
|
+
|
|
156
|
+
=== エージェント定義(.codex/agents/{reviewer_type}.toml)===
|
|
157
|
+
{agent_toml}
|
|
158
|
+
=== エージェント定義ここまで ===
|
|
159
|
+
|
|
160
|
+
上記の定義に従い、以下のコードをレビューしてください。
|
|
161
|
+
ファイルシステムへのアクセスが必要な場合(チェックリストの参照など)は Read ツールを使用してください。
|
|
162
|
+
|
|
163
|
+
対象: {対象説明文}
|
|
164
|
+
|
|
165
|
+
{review_target}" --sandbox workspace-write 2>&1
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
出力を `{codex_output}` として保持する。
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Step 6: レポートを Write する
|
|
173
|
+
|
|
174
|
+
`.claude/reports/{report_filename}` に Write する:
|
|
175
|
+
|
|
176
|
+
```markdown
|
|
177
|
+
# {reviewer_type} Report (Codex)
|
|
178
|
+
|
|
179
|
+
**対象:** {対象説明文}
|
|
180
|
+
**実行エンジン:** Codex CLI (gpt-5.5) / ペルソナ: {reviewer_type}
|
|
181
|
+
**実行日時:** {timestamp}
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
{codex_output}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Step 7: 結果を表示してフォローアップを確認する
|
|
191
|
+
|
|
192
|
+
レポートの内容を表示し、保存先パスを伝える。
|
|
193
|
+
|
|
194
|
+
AskUserQuestion で確認する:
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"questions": [{
|
|
199
|
+
"question": "Codex レビュー結果を確認してください。次のアクションを選択してください。",
|
|
200
|
+
"header": "次のアクション",
|
|
201
|
+
"multiSelect": false,
|
|
202
|
+
"options": [
|
|
203
|
+
{ "label": "確認完了", "description": "レポートを確認した" },
|
|
204
|
+
{ "label": "別ファイルも続けてレビューする", "description": "Step 1 から再実行する" },
|
|
205
|
+
{ "label": "C3 レビューフローへ引き継ぐ", "description": "code-review スキルへ引き継いでフェーズ E を実行する" }
|
|
206
|
+
]
|
|
207
|
+
}]
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
「C3 レビューフローへ引き継ぐ」が選択された場合は Skill ツールで `code-review` を呼び出す。
|