claude-code-conductor 0.3.3__tar.gz → 0.4.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-0.3.3 → claude_code_conductor-0.4.0}/.claude/agents/tdd-develop.md +6 -6
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/hooks/clear_file_history.py +4 -2
- claude_code_conductor-0.4.0/.claude/hooks/pre_compact.py +59 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/hooks/pre_tool.py +5 -17
- claude_code_conductor-0.4.0/.claude/hooks/session_utils.py +42 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/hooks/statusline.py +2 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/hooks/stop.py +92 -65
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/hooks/validate_skill_change.py +4 -5
- claude_code_conductor-0.4.0/.claude/memory/.gitkeep +0 -0
- claude_code_conductor-0.4.0/.claude/pytest_temp.ini +2 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/settings.json +25 -21
- claude_code_conductor-0.4.0/.claude/settings.local.json +33 -0
- claude_code_conductor-0.4.0/.claude/skills/code-review/SKILL.md +14 -0
- claude_code_conductor-0.3.3/.claude/skills/dev-workflow.md → claude_code_conductor-0.4.0/.claude/skills/dev-workflow/SKILL.md +8 -2
- claude_code_conductor-0.4.0/.claude/skills/develop/SKILL.md +16 -0
- claude_code_conductor-0.3.3/.claude/commands/doc.md → claude_code_conductor-0.4.0/.claude/skills/doc/SKILL.md +6 -1
- claude_code_conductor-0.3.3/.claude/commands/extract-lib.md → claude_code_conductor-0.4.0/.claude/skills/extract-lib/SKILL.md +7 -2
- claude_code_conductor-0.3.3/.claude/commands/init-session.md → claude_code_conductor-0.4.0/.claude/skills/init-session/SKILL.md +6 -1
- claude_code_conductor-0.3.3/.claude/commands/mcp.md → claude_code_conductor-0.4.0/.claude/skills/mcp-config/SKILL.md +6 -1
- claude_code_conductor-0.3.3/.claude/commands/promote-pattern.md → claude_code_conductor-0.4.0/.claude/skills/promote-pattern/SKILL.md +25 -14
- claude_code_conductor-0.3.3/.claude/commands/setup.md → claude_code_conductor-0.4.0/.claude/skills/setup/SKILL.md +6 -1
- claude_code_conductor-0.3.3/.claude/commands/start.md → claude_code_conductor-0.4.0/.claude/skills/start/SKILL.md +8 -3
- claude_code_conductor-0.3.3/.claude/skills/wave-execution.md → claude_code_conductor-0.4.0/.claude/skills/wave-execution/SKILL.md +10 -4
- claude_code_conductor-0.3.3/.claude/skills/worktree-tdd-workflow.md → claude_code_conductor-0.4.0/.claude/skills/worktree-tdd-workflow/SKILL.md +6 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/CHANGELOG.md +108 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/PKG-INFO +1 -1
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/src/c3/__init__.py +1 -1
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/src/c3/cli_list.py +4 -1
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/src/c3/cli_po.py +20 -10
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/src/c3/po/manifest.py +26 -8
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/src/c3/po/run.py +4 -2
- claude_code_conductor-0.3.3/.claude/CLAUDE.md +0 -182
- claude_code_conductor-0.3.3/.claude/commands/develop.md +0 -11
- claude_code_conductor-0.3.3/.claude/commands/review.md +0 -9
- claude_code_conductor-0.3.3/.claude/hooks/pre_compact.py +0 -82
- claude_code_conductor-0.3.3/.claude/settings.local.json +0 -126
- claude_code_conductor-0.3.3/.claude/skills/promoted/index.md +0 -5
- /claude_code_conductor-0.3.3/.claude/memory/.gitkeep → /claude_code_conductor-0.4.0/.claude/CLAUDE.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/agents/architect.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/agents/code-reviewer.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/agents/developer.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/agents/doc-writer.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/agents/interviewer.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/agents/planner.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/agents/project-setup.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/agents/security-reviewer.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/agents/tester.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/docs/parallel-orchestra-manifest.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/hooks/enable_sandbox.py +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/hooks/worktree_guard.py +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/rules/code-review-checklist.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/rules/promoted/index.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/rules/security-review-checklist.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.gitignore +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/LICENSE +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/README.md +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/hatch_build.py +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/pyproject.toml +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/src/c3/__main__.py +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/src/c3/_excludes.py +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/src/c3/cli.py +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/src/c3/cli_doctor.py +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/src/c3/cli_init.py +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/src/c3/cli_update.py +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/src/c3/paths.py +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/src/c3/po/__init__.py +0 -0
- {claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/src/c3/po/detect.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tdd-develop
|
|
3
3
|
model: sonnet
|
|
4
|
-
description: ヘッドレス専用 TDD コンダクター。**必ず最初に `.claude/skills/worktree-tdd-workflow.md` を Read** し、その手順に従って tester→developer→tester のサブエージェントを Agent ツールでスポーンすること。インライン実装は絶対禁止。AskUserQuestion を使わない。
|
|
4
|
+
description: ヘッドレス専用 TDD コンダクター。**必ず最初に `.claude/skills/worktree-tdd-workflow/SKILL.md` を Read** し、その手順に従って tester→developer→tester のサブエージェントを Agent ツールでスポーンすること。インライン実装は絶対禁止。AskUserQuestion を使わない。
|
|
5
5
|
tools:
|
|
6
6
|
- Read
|
|
7
7
|
- Write
|
|
@@ -19,7 +19,7 @@ tools:
|
|
|
19
19
|
|
|
20
20
|
**いかなるタスクであっても、最初に必ず以下を実行すること:**
|
|
21
21
|
|
|
22
|
-
1. `Read` ツールで `.claude/skills/worktree-tdd-workflow.md` を読み込む
|
|
22
|
+
1. `Read` ツールで `.claude/skills/worktree-tdd-workflow/SKILL.md` を読み込む
|
|
23
23
|
2. 読み込んだワークフローに従って tester → developer → tester の順で **必ず Agent ツールでサブエージェントをスポーン**する
|
|
24
24
|
3. **絶対にインラインで pytest や Edit を直接実行して実装してはならない**
|
|
25
25
|
4. 「自分で実装した方が早い」と感じても、必ず Agent 経由で tester/developer に委譲すること
|
|
@@ -31,13 +31,13 @@ tools:
|
|
|
31
31
|
## Core Mandate
|
|
32
32
|
|
|
33
33
|
ヘッドレス環境で TDD サイクルを自律実行するコンダクター。
|
|
34
|
-
`.claude/skills/worktree-tdd-workflow.md` を読み込み、上限付きループで実行する。
|
|
34
|
+
`.claude/skills/worktree-tdd-workflow/SKILL.md` を読み込み、上限付きループで実行する。
|
|
35
35
|
全テスト合格またはループ上限到達で終了する。
|
|
36
36
|
|
|
37
37
|
## Key Scope
|
|
38
38
|
|
|
39
39
|
✅ 担当すること:
|
|
40
|
-
- `.claude/skills/worktree-tdd-workflow.md` の手順に従った TDD サイクルの実行
|
|
40
|
+
- `.claude/skills/worktree-tdd-workflow/SKILL.md` の手順に従った TDD サイクルの実行
|
|
41
41
|
- ループカウンターの管理と終了判定
|
|
42
42
|
- tester / developer へのコンテキスト(plan-report・test-report のパス)の受け渡し
|
|
43
43
|
- 最終結果の出力
|
|
@@ -58,14 +58,14 @@ tools:
|
|
|
58
58
|
|
|
59
59
|
### Step 0: 初期化
|
|
60
60
|
|
|
61
|
-
1. `.claude/skills/worktree-tdd-workflow.md` を Read してサイクル手順を把握する
|
|
61
|
+
1. `.claude/skills/worktree-tdd-workflow/SKILL.md` を Read してサイクル手順を把握する
|
|
62
62
|
2. Glob で `.claude/reports/plan-report-*.md` の最新ファイルの存在を確認する
|
|
63
63
|
- 存在しない場合: 「plan-report が見つかりません。Parallel Orchestra のマニフェストに plan-report のパスを含めるか、事前に計画フェーズを完了してください」と出力して終了する
|
|
64
64
|
3. ループカウンターを `0` に初期化する
|
|
65
65
|
|
|
66
66
|
### Step 1: TDD サイクル実行
|
|
67
67
|
|
|
68
|
-
`.claude/skills/worktree-tdd-workflow.md` の Step 1〜4 を実行する。
|
|
68
|
+
`.claude/skills/worktree-tdd-workflow/SKILL.md` の Step 1〜4 を実行する。
|
|
69
69
|
|
|
70
70
|
### Step 2: 結果判定
|
|
71
71
|
|
{claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/hooks/clear_file_history.py
RENAMED
|
@@ -22,13 +22,15 @@ def main():
|
|
|
22
22
|
for name in entries:
|
|
23
23
|
full_path = os.path.join(FILE_HISTORY_DIR, name)
|
|
24
24
|
try:
|
|
25
|
-
if os.path.
|
|
25
|
+
if os.path.islink(full_path):
|
|
26
|
+
os.unlink(full_path)
|
|
27
|
+
elif os.path.isdir(full_path):
|
|
26
28
|
shutil.rmtree(full_path)
|
|
27
29
|
else:
|
|
28
30
|
os.unlink(full_path)
|
|
29
31
|
deleted += 1
|
|
30
32
|
except FileNotFoundError:
|
|
31
|
-
pass
|
|
33
|
+
pass # already deleted by another process between listdir and unlink/rmtree
|
|
32
34
|
except Exception as e:
|
|
33
35
|
print(f'[clear-file-history] 削除に失敗: {name} ({e})')
|
|
34
36
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PreCompact hook: append checkpoint marker to today's session file."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
|
|
9
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
10
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
11
|
+
|
|
12
|
+
_HOOKS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
13
|
+
_CLAUDE_DIR = os.path.dirname(_HOOKS_DIR)
|
|
14
|
+
SESSIONS_DIR = os.path.join(_CLAUDE_DIR, 'memory', 'sessions')
|
|
15
|
+
|
|
16
|
+
from session_utils import is_worktree, create_session_template
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
try:
|
|
21
|
+
payload = json.loads(sys.stdin.read())
|
|
22
|
+
except (json.JSONDecodeError, ValueError):
|
|
23
|
+
payload = {}
|
|
24
|
+
|
|
25
|
+
cwd = os.getcwd()
|
|
26
|
+
if is_worktree(cwd):
|
|
27
|
+
sys.exit(0)
|
|
28
|
+
|
|
29
|
+
trigger = payload.get('trigger', 'unknown')
|
|
30
|
+
context_items_before = payload.get('context_items_before', 0)
|
|
31
|
+
|
|
32
|
+
os.makedirs(SESSIONS_DIR, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
now = datetime.now(timezone.utc)
|
|
35
|
+
date_str = now.strftime('%Y%m%d')
|
|
36
|
+
session_file = os.path.join(SESSIONS_DIR, f'{date_str}.tmp')
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
with open(session_file, 'x', encoding='utf-8') as f:
|
|
40
|
+
f.write(create_session_template(date_str))
|
|
41
|
+
except FileExistsError:
|
|
42
|
+
pass # already created by stop.py or another process
|
|
43
|
+
|
|
44
|
+
ts = now.isoformat()
|
|
45
|
+
checkpoint = (
|
|
46
|
+
f'\n'
|
|
47
|
+
f'## [PreCompact checkpoint: {trigger} - {ts}]\n'
|
|
48
|
+
f'コンテキスト圧縮 ({trigger}) が発生しました。圧縮前: {context_items_before} アイテム。\n'
|
|
49
|
+
f'このポイント以前の詳細な文脈は失われています。\n'
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
with open(session_file, 'a', encoding='utf-8') as f:
|
|
53
|
+
f.write(checkpoint)
|
|
54
|
+
|
|
55
|
+
print(f'[PreCompact] セッション状態を {session_file} に保存しました', file=sys.stderr)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == '__main__':
|
|
59
|
+
main()
|
|
@@ -32,25 +32,13 @@ def main():
|
|
|
32
32
|
print('[PreToolUse WARNING] 破壊的な DB 操作を検出しました。本番環境での実行でないことを確認してください。',
|
|
33
33
|
file=sys.stderr)
|
|
34
34
|
|
|
35
|
-
# cd コマンド: CWD 固定バグを防ぐためブロック
|
|
36
|
-
# Bash ツールで cd を実行すると以降の全コマンドの CWD が変わり、
|
|
37
|
-
# フックが相対パスで .claude/hooks/ を参照できなくなる。
|
|
38
|
-
if re.search(r'(?:^|[;&|])\s*cd(?:\s|$)', cmd):
|
|
39
|
-
print(
|
|
40
|
-
'[PreToolUse BLOCK] cd コマンドをブロックしました。\n'
|
|
41
|
-
'Bash ツールで cd を実行すると CWD が変わり、以降のフックが失敗します。\n'
|
|
42
|
-
'cd を使わず、プロジェクトルートからの相対パスで実行してください。\n'
|
|
43
|
-
'例: python -m pytest test1/tests -v (cd test1 && python -m pytest の代わり)',
|
|
44
|
-
file=sys.stderr
|
|
45
|
-
)
|
|
46
|
-
sys.exit(2)
|
|
47
|
-
|
|
48
35
|
# rm -rf 系: ブロック
|
|
49
|
-
#
|
|
36
|
+
# rm の直後のフラグのみを収集することで、前のコマンドのフラグを誤検出しない
|
|
50
37
|
if re.search(r'\brm\b', cmd):
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
38
|
+
rm_flags_match = re.findall(r'\brm\b((?:\s+-[a-zA-Z]+)*)', cmd)
|
|
39
|
+
flags_str = ''.join(rm_flags_match)
|
|
40
|
+
has_r = bool(re.search(r'-[a-zA-Z]*r', flags_str)) or '--recursive' in cmd
|
|
41
|
+
has_f = bool(re.search(r'-[a-zA-Z]*f', flags_str)) or '--force' in cmd
|
|
54
42
|
has_long_recursive = '--recursive' in cmd
|
|
55
43
|
has_long_force = '--force' in cmd
|
|
56
44
|
if (has_r and has_f) or (has_long_recursive and has_long_force):
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Shared utilities for session management hooks (stop.py, pre_compact.py)."""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
_HOOKS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
7
|
+
_CLAUDE_DIR = os.path.dirname(_HOOKS_DIR)
|
|
8
|
+
SESSIONS_DIR = os.path.join(_CLAUDE_DIR, 'memory', 'sessions')
|
|
9
|
+
|
|
10
|
+
SESSION_JSON_MARKER = 'C3:SESSION:JSON'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def is_worktree(cwd: str) -> bool:
|
|
14
|
+
git_path = os.path.join(cwd, '.git')
|
|
15
|
+
return os.path.exists(git_path) and os.path.isfile(git_path)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def create_session_template(date_str: str) -> str:
|
|
19
|
+
return (
|
|
20
|
+
f"SESSION: {date_str}\n"
|
|
21
|
+
f"AGENT: \n"
|
|
22
|
+
f"DURATION: \n"
|
|
23
|
+
f"\n"
|
|
24
|
+
f"## うまくいったアプローチ\n"
|
|
25
|
+
f"\n"
|
|
26
|
+
f"## 試みたが失敗したアプローチ\n"
|
|
27
|
+
f"\n"
|
|
28
|
+
f"## 残タスク\n"
|
|
29
|
+
f"\n"
|
|
30
|
+
f"## 事実ログ(自動生成 / stop.py)\n"
|
|
31
|
+
f"- 記録時刻: \n"
|
|
32
|
+
f"\n"
|
|
33
|
+
f"<!-- {SESSION_JSON_MARKER}\n"
|
|
34
|
+
f"{{\n"
|
|
35
|
+
f' "session": "{date_str}",\n'
|
|
36
|
+
f' "patterns": [],\n'
|
|
37
|
+
f' "successes": [],\n'
|
|
38
|
+
f' "failures": [],\n'
|
|
39
|
+
f' "todos": []\n'
|
|
40
|
+
f"}}\n"
|
|
41
|
+
f"-->\n"
|
|
42
|
+
)
|
|
@@ -7,71 +7,66 @@ Triggered at the end of each Claude Code session.
|
|
|
7
7
|
import json
|
|
8
8
|
import sys
|
|
9
9
|
import os
|
|
10
|
+
import re
|
|
11
|
+
from datetime import date, datetime, timezone
|
|
10
12
|
|
|
11
13
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
12
14
|
sys.stderr.reconfigure(encoding='utf-8')
|
|
13
|
-
import re
|
|
14
|
-
from datetime import date, datetime, timezone
|
|
15
15
|
|
|
16
16
|
_HOOKS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
17
17
|
_CLAUDE_DIR = os.path.dirname(_HOOKS_DIR)
|
|
18
|
-
SESSIONS_DIR = os.path.join(_CLAUDE_DIR, 'memory', 'sessions')
|
|
19
18
|
PATTERNS_FILE = os.path.join(_CLAUDE_DIR, 'memory', 'patterns.json')
|
|
20
19
|
|
|
20
|
+
from session_utils import SESSION_JSON_MARKER, is_worktree, create_session_template, SESSIONS_DIR
|
|
21
|
+
|
|
21
22
|
EXPIRY_DAYS = 30
|
|
22
23
|
PROMOTION_THRESHOLD = 0.8
|
|
23
24
|
COOLING_DAYS = 3
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
git_path = os.path.join(cwd, '.git')
|
|
29
|
-
return os.path.exists(git_path) and os.path.isfile(git_path)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def get_session_path(yyyymmdd: str) -> str:
|
|
33
|
-
return os.path.join(SESSIONS_DIR, f'{yyyymmdd}.tmp')
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def create_session_template(yyyymmdd: str) -> str:
|
|
37
|
-
return (
|
|
38
|
-
f"SESSION: {yyyymmdd}\n"
|
|
39
|
-
f"AGENT: \n"
|
|
40
|
-
f"DURATION: \n"
|
|
41
|
-
f"\n"
|
|
42
|
-
f"## うまくいったアプローチ\n"
|
|
43
|
-
f"\n"
|
|
44
|
-
f"## 試みたが失敗したアプローチ\n"
|
|
45
|
-
f"\n"
|
|
46
|
-
f"## 残タスク\n"
|
|
47
|
-
f"\n"
|
|
48
|
-
f"## 事実ログ(自動生成 / stop.py)\n"
|
|
49
|
-
f"- 記録時刻: \n"
|
|
50
|
-
f"\n"
|
|
51
|
-
f"<!-- {SESSION_JSON_MARKER}\n"
|
|
52
|
-
f"{{\n"
|
|
53
|
-
f' "session": "{yyyymmdd}",\n'
|
|
54
|
-
f' "patterns": [],\n'
|
|
55
|
-
f' "successes": [],\n'
|
|
56
|
-
f' "failures": [],\n'
|
|
57
|
-
f' "todos": []\n'
|
|
58
|
-
f"}}\n"
|
|
59
|
-
f"-->\n"
|
|
60
|
-
)
|
|
25
|
+
MAX_ID_LENGTH = 64
|
|
26
|
+
MAX_DESCRIPTION_LENGTH = 500
|
|
27
|
+
MAX_LAST_MSG = 500
|
|
28
|
+
|
|
61
29
|
|
|
30
|
+
def get_session_path(date_str: str) -> str:
|
|
31
|
+
return os.path.join(SESSIONS_DIR, f'{date_str}.tmp')
|
|
62
32
|
|
|
63
|
-
|
|
33
|
+
|
|
34
|
+
def ensure_session_file(date_str: str) -> None:
|
|
64
35
|
os.makedirs(SESSIONS_DIR, exist_ok=True)
|
|
65
|
-
path = get_session_path(
|
|
36
|
+
path = get_session_path(date_str)
|
|
66
37
|
# wx フラグ相当: ファイルが存在しない場合のみ作成(TOCTOU安全)
|
|
67
38
|
try:
|
|
68
39
|
with open(path, 'x', encoding='utf-8') as f:
|
|
69
|
-
f.write(create_session_template(
|
|
40
|
+
f.write(create_session_template(date_str))
|
|
70
41
|
print(f'[Stop] セッションファイルを作成しました: {path}', file=sys.stderr)
|
|
71
42
|
except FileExistsError:
|
|
72
43
|
_update_facts_timestamp(path)
|
|
73
44
|
|
|
74
45
|
|
|
46
|
+
def _append_last_message(path: str, message: str) -> None:
|
|
47
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
48
|
+
content = f.read()
|
|
49
|
+
|
|
50
|
+
if '- 最終応答:' in content:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
single_line = ' '.join(message.split())
|
|
54
|
+
# サロゲート文字など UTF-8 非互換文字を除去(JSON デコード時に生成される場合がある)
|
|
55
|
+
single_line = single_line.encode('utf-8', errors='replace').decode('utf-8')
|
|
56
|
+
truncated = single_line[:MAX_LAST_MSG]
|
|
57
|
+
if len(single_line) > MAX_LAST_MSG:
|
|
58
|
+
truncated += '…(省略)'
|
|
59
|
+
|
|
60
|
+
updated = re.sub(
|
|
61
|
+
r'(- 記録時刻: [^\n]*)',
|
|
62
|
+
lambda m: m.group(0) + f'\n- 最終応答: {truncated}',
|
|
63
|
+
content
|
|
64
|
+
)
|
|
65
|
+
if updated != content:
|
|
66
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
67
|
+
f.write(updated)
|
|
68
|
+
|
|
69
|
+
|
|
75
70
|
def _update_facts_timestamp(path: str) -> None:
|
|
76
71
|
with open(path, 'r', encoding='utf-8') as f:
|
|
77
72
|
content = f.read()
|
|
@@ -82,8 +77,8 @@ def _update_facts_timestamp(path: str) -> None:
|
|
|
82
77
|
f.write(updated)
|
|
83
78
|
|
|
84
79
|
|
|
85
|
-
def extract_session_patterns(
|
|
86
|
-
path = get_session_path(
|
|
80
|
+
def extract_session_patterns(date_str: str) -> list:
|
|
81
|
+
path = get_session_path(date_str)
|
|
87
82
|
if not os.path.exists(path):
|
|
88
83
|
return []
|
|
89
84
|
with open(path, 'r', encoding='utf-8') as f:
|
|
@@ -98,20 +93,37 @@ def extract_session_patterns(yyyymmdd: str) -> list:
|
|
|
98
93
|
return []
|
|
99
94
|
|
|
100
95
|
|
|
101
|
-
def _parse_session_date(
|
|
96
|
+
def _parse_session_date(date_str: str):
|
|
102
97
|
try:
|
|
103
|
-
return datetime.strptime(
|
|
98
|
+
return datetime.strptime(date_str, '%Y%m%d').date()
|
|
104
99
|
except ValueError:
|
|
105
100
|
return date.min
|
|
106
101
|
|
|
107
102
|
|
|
108
|
-
def
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
def _build_sessions_by_date(sessions_dir: str) -> dict:
|
|
104
|
+
"""Build a mapping of date string -> session count from sessions directory.
|
|
105
|
+
|
|
106
|
+
Returns a dict mapping each yyyymmdd string found in sessions_dir to 1,
|
|
107
|
+
enabling O(1) lookup without repeated os.listdir calls.
|
|
108
|
+
"""
|
|
109
|
+
if not os.path.isdir(sessions_dir):
|
|
110
|
+
return {}
|
|
111
|
+
result = {}
|
|
112
|
+
for fname in os.listdir(sessions_dir):
|
|
113
|
+
if fname.endswith('.tmp'):
|
|
114
|
+
result[fname[:-4]] = True
|
|
115
|
+
return result
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def count_sessions_since(registered_date_str: str, sessions_by_date: dict | None = None) -> int:
|
|
119
|
+
if sessions_by_date is None:
|
|
120
|
+
if not os.path.isdir(SESSIONS_DIR):
|
|
121
|
+
return 1
|
|
122
|
+
sessions_by_date = _build_sessions_by_date(SESSIONS_DIR)
|
|
123
|
+
registered = _parse_session_date(registered_date_str)
|
|
112
124
|
count = sum(
|
|
113
|
-
1 for
|
|
114
|
-
if
|
|
125
|
+
1 for d in sessions_by_date
|
|
126
|
+
if _parse_session_date(d) >= registered
|
|
115
127
|
)
|
|
116
128
|
return max(count, 1)
|
|
117
129
|
|
|
@@ -129,31 +141,36 @@ def save_patterns(data: dict) -> None:
|
|
|
129
141
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
130
142
|
|
|
131
143
|
|
|
132
|
-
def update_patterns(
|
|
133
|
-
new_observations = extract_session_patterns(
|
|
144
|
+
def update_patterns(date_str: str) -> None:
|
|
145
|
+
new_observations = extract_session_patterns(date_str)
|
|
134
146
|
data = load_patterns()
|
|
135
147
|
today = date.today()
|
|
136
148
|
|
|
137
149
|
for obs in new_observations:
|
|
138
150
|
pid = obs.get('id')
|
|
139
|
-
if not pid:
|
|
151
|
+
if not pid or len(pid) > MAX_ID_LENGTH:
|
|
140
152
|
continue
|
|
141
153
|
description = obs.get('description', '')
|
|
154
|
+
if len(description) > MAX_DESCRIPTION_LENGTH:
|
|
155
|
+
continue
|
|
142
156
|
existing = next((p for p in data['patterns'] if p['id'] == pid), None)
|
|
143
157
|
if existing is None:
|
|
144
158
|
data['patterns'].append({
|
|
145
159
|
"id": pid,
|
|
146
160
|
"description": description,
|
|
147
|
-
"registered_date":
|
|
161
|
+
"registered_date": date_str,
|
|
148
162
|
"trust_score": 0.1,
|
|
149
163
|
"promotion_candidate": False,
|
|
150
|
-
"observations": [{"date":
|
|
151
|
-
"last_updated":
|
|
164
|
+
"observations": [{"date": date_str}],
|
|
165
|
+
"last_updated": date_str,
|
|
152
166
|
})
|
|
153
167
|
else:
|
|
154
|
-
if not any(o['date'] ==
|
|
155
|
-
existing['observations'].append({"date":
|
|
156
|
-
existing['last_updated'] =
|
|
168
|
+
if not any(o['date'] == date_str for o in existing['observations']):
|
|
169
|
+
existing['observations'].append({"date": date_str})
|
|
170
|
+
existing['last_updated'] = date_str
|
|
171
|
+
|
|
172
|
+
# Cache os.listdir result once before the loop to avoid O(N×M) calls
|
|
173
|
+
sessions_by_date = _build_sessions_by_date(SESSIONS_DIR)
|
|
157
174
|
|
|
158
175
|
active = []
|
|
159
176
|
for pattern in data['patterns']:
|
|
@@ -167,7 +184,7 @@ def update_patterns(yyyymmdd: str) -> None:
|
|
|
167
184
|
if days_elapsed >= EXPIRY_DAYS:
|
|
168
185
|
continue
|
|
169
186
|
|
|
170
|
-
sessions_total = count_sessions_since(pattern['registered_date'])
|
|
187
|
+
sessions_total = count_sessions_since(pattern['registered_date'], sessions_by_date)
|
|
171
188
|
obs_count = len(pattern['observations'])
|
|
172
189
|
trust = round(min(1.0, max(0.1, obs_count / sessions_total)), 2)
|
|
173
190
|
|
|
@@ -185,9 +202,14 @@ def update_patterns(yyyymmdd: str) -> None:
|
|
|
185
202
|
|
|
186
203
|
def main():
|
|
187
204
|
try:
|
|
188
|
-
json.loads(sys.stdin.read())
|
|
205
|
+
payload = json.loads(sys.stdin.read())
|
|
189
206
|
except (json.JSONDecodeError, ValueError):
|
|
190
|
-
|
|
207
|
+
payload = {}
|
|
208
|
+
|
|
209
|
+
# stop_hook_active=true は Stop hook が decision:block を返した後の 2 回目呼び出し。
|
|
210
|
+
# セッション処理は初回のみ実行する。
|
|
211
|
+
if payload.get('stop_hook_active'):
|
|
212
|
+
sys.exit(0)
|
|
191
213
|
|
|
192
214
|
cwd = os.getcwd()
|
|
193
215
|
if is_worktree(cwd):
|
|
@@ -195,6 +217,11 @@ def main():
|
|
|
195
217
|
|
|
196
218
|
today_str = date.today().strftime('%Y%m%d')
|
|
197
219
|
ensure_session_file(today_str)
|
|
220
|
+
|
|
221
|
+
last_msg = payload.get('last_assistant_message', '').strip()
|
|
222
|
+
if last_msg:
|
|
223
|
+
_append_last_message(get_session_path(today_str), last_msg)
|
|
224
|
+
|
|
198
225
|
update_patterns(today_str)
|
|
199
226
|
|
|
200
227
|
|
{claude_code_conductor-0.3.3 → claude_code_conductor-0.4.0}/.claude/hooks/validate_skill_change.py
RENAMED
|
@@ -13,21 +13,20 @@ def main():
|
|
|
13
13
|
try:
|
|
14
14
|
payload = json.loads(sys.stdin.read())
|
|
15
15
|
except (json.JSONDecodeError, ValueError):
|
|
16
|
-
|
|
16
|
+
return
|
|
17
17
|
|
|
18
18
|
if payload.get('tool_name') not in ('Write', 'Edit'):
|
|
19
|
-
|
|
19
|
+
return
|
|
20
20
|
|
|
21
21
|
file_path = payload.get('tool_input', {}).get('file_path', '')
|
|
22
22
|
normalized = file_path.replace('\\', '/')
|
|
23
23
|
|
|
24
24
|
if '/.claude/skills/' not in normalized:
|
|
25
|
-
|
|
25
|
+
return
|
|
26
26
|
|
|
27
27
|
skill_name = os.path.basename(file_path)
|
|
28
28
|
print(f'[C3] .claude/skills/{skill_name} を変更しました。実際のエージェント動作で確認してください。')
|
|
29
|
-
sys.exit(0)
|
|
30
29
|
|
|
31
30
|
|
|
32
31
|
if __name__ == '__main__':
|
|
33
|
-
main()
|
|
32
|
+
sys.exit(main() or 0)
|
|
File without changes
|
|
@@ -8,17 +8,32 @@
|
|
|
8
8
|
"Bash(python .claude/hooks/pre_compact.py*)",
|
|
9
9
|
"Bash(python .claude/hooks/validate_skill_change.py*)",
|
|
10
10
|
"Bash(python .claude/hooks/statusline.py*)",
|
|
11
|
+
"Bash(python \"$CLAUDE_PROJECT_DIR/.claude/hooks/clear_file_history.py\"*)",
|
|
12
|
+
"Bash(python \"$CLAUDE_PROJECT_DIR/.claude/hooks/enable_sandbox.py\"*)",
|
|
13
|
+
"Bash(python \"$CLAUDE_PROJECT_DIR/.claude/hooks/stop.py\"*)",
|
|
14
|
+
"Bash(python \"$CLAUDE_PROJECT_DIR/.claude/hooks/pre_tool.py\"*)",
|
|
15
|
+
"Bash(python \"$CLAUDE_PROJECT_DIR/.claude/hooks/pre_compact.py\"*)",
|
|
16
|
+
"Bash(python \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate_skill_change.py\"*)",
|
|
17
|
+
"Bash(python \"$CLAUDE_PROJECT_DIR/.claude/hooks/statusline.py\"*)",
|
|
11
18
|
"Read(**)",
|
|
12
19
|
"Glob(**)",
|
|
13
20
|
"Grep(**)",
|
|
14
21
|
"Edit(.claude/reports/**)",
|
|
22
|
+
"Edit(.claude/memory/sessions/**)",
|
|
23
|
+
"Edit(.claude/memory/**)",
|
|
24
|
+
"Edit(.claude/rules/**)",
|
|
25
|
+
"Edit(.claude/rules/promoted/**)",
|
|
26
|
+
"Edit(.claude/skills/**)",
|
|
27
|
+
"Edit(.claude/settings.json)",
|
|
15
28
|
"Write(.claude/reports/**)",
|
|
29
|
+
"Write(.claude/reports/archive/**)",
|
|
16
30
|
"Write(.claude/tmp/**)",
|
|
17
31
|
"Write(.claude/memory/**)",
|
|
18
|
-
"Edit(.claude/memory/sessions/**)",
|
|
19
32
|
"Write(.claude/memory/sessions/**)",
|
|
33
|
+
"Write(.claude/rules/**)",
|
|
20
34
|
"Write(.claude/rules/promoted/**)",
|
|
21
|
-
"Write(.claude/skills
|
|
35
|
+
"Write(.claude/skills/**)",
|
|
36
|
+
"Write(.claude/settings.json)",
|
|
22
37
|
"Bash(git status*)",
|
|
23
38
|
"Bash(git diff*)",
|
|
24
39
|
"Bash(git log*)",
|
|
@@ -47,7 +62,7 @@
|
|
|
47
62
|
},
|
|
48
63
|
"statusLine": {
|
|
49
64
|
"type": "command",
|
|
50
|
-
"command": "python
|
|
65
|
+
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/statusline.py\""
|
|
51
66
|
},
|
|
52
67
|
"hooks": {
|
|
53
68
|
"PreToolUse": [
|
|
@@ -56,7 +71,7 @@
|
|
|
56
71
|
"hooks": [
|
|
57
72
|
{
|
|
58
73
|
"type": "command",
|
|
59
|
-
"command": "python
|
|
74
|
+
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/pre_tool.py\""
|
|
60
75
|
}
|
|
61
76
|
]
|
|
62
77
|
},
|
|
@@ -65,7 +80,7 @@
|
|
|
65
80
|
"hooks": [
|
|
66
81
|
{
|
|
67
82
|
"type": "command",
|
|
68
|
-
"command": "python
|
|
83
|
+
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/worktree_guard.py\""
|
|
69
84
|
}
|
|
70
85
|
]
|
|
71
86
|
},
|
|
@@ -74,7 +89,7 @@
|
|
|
74
89
|
"hooks": [
|
|
75
90
|
{
|
|
76
91
|
"type": "command",
|
|
77
|
-
"command": "python
|
|
92
|
+
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/worktree_guard.py\""
|
|
78
93
|
}
|
|
79
94
|
]
|
|
80
95
|
}
|
|
@@ -85,7 +100,7 @@
|
|
|
85
100
|
"hooks": [
|
|
86
101
|
{
|
|
87
102
|
"type": "command",
|
|
88
|
-
"command": "python
|
|
103
|
+
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate_skill_change.py\""
|
|
89
104
|
}
|
|
90
105
|
]
|
|
91
106
|
},
|
|
@@ -94,7 +109,7 @@
|
|
|
94
109
|
"hooks": [
|
|
95
110
|
{
|
|
96
111
|
"type": "command",
|
|
97
|
-
"command": "python
|
|
112
|
+
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate_skill_change.py\""
|
|
98
113
|
}
|
|
99
114
|
]
|
|
100
115
|
}
|
|
@@ -105,7 +120,7 @@
|
|
|
105
120
|
"hooks": [
|
|
106
121
|
{
|
|
107
122
|
"type": "command",
|
|
108
|
-
"command": "python
|
|
123
|
+
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/pre_compact.py\""
|
|
109
124
|
}
|
|
110
125
|
]
|
|
111
126
|
}
|
|
@@ -116,18 +131,7 @@
|
|
|
116
131
|
"hooks": [
|
|
117
132
|
{
|
|
118
133
|
"type": "command",
|
|
119
|
-
"command": "python
|
|
120
|
-
}
|
|
121
|
-
]
|
|
122
|
-
}
|
|
123
|
-
],
|
|
124
|
-
"UserPromptSubmit": [
|
|
125
|
-
{
|
|
126
|
-
"matcher": "",
|
|
127
|
-
"hooks": [
|
|
128
|
-
{
|
|
129
|
-
"type": "command",
|
|
130
|
-
"command": "python .claude/hooks/statusline.py"
|
|
134
|
+
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/stop.py\""
|
|
131
135
|
}
|
|
132
136
|
]
|
|
133
137
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(python .claude/hooks/clear_file_history.py*)",
|
|
5
|
+
"Bash(python .claude/hooks/enable_sandbox.py*)",
|
|
6
|
+
"Bash(python .claude/hooks/stop.py*)",
|
|
7
|
+
"Bash(python .claude/hooks/pre_tool.py*)",
|
|
8
|
+
"Bash(python .claude/hooks/pre_compact.py*)",
|
|
9
|
+
"Bash(python .claude/hooks/validate_skill_change.py*)",
|
|
10
|
+
"Bash(python .claude/hooks/statusline.py*)",
|
|
11
|
+
"Bash(python *)",
|
|
12
|
+
"Bash(pytest *)",
|
|
13
|
+
"Bash(xargs wc *)",
|
|
14
|
+
"Bash(git add*)",
|
|
15
|
+
"Bash(git commit*)",
|
|
16
|
+
"Bash(git status*)",
|
|
17
|
+
"Bash(git diff*)",
|
|
18
|
+
"Bash(git log*)",
|
|
19
|
+
"Bash(mkdir*)",
|
|
20
|
+
"Edit(**)",
|
|
21
|
+
"Glob(**)",
|
|
22
|
+
"Grep(**)",
|
|
23
|
+
"Read(**)",
|
|
24
|
+
"Write(**)",
|
|
25
|
+
"WebFetch(domain:github.com)",
|
|
26
|
+
"WebFetch(domain:pypi.org)",
|
|
27
|
+
"Bash(grep \"\\\\.py$\")",
|
|
28
|
+
"WebFetch(domain:raw.githubusercontent.com)",
|
|
29
|
+
"Bash(pip-audit)",
|
|
30
|
+
"Bash(c3 po *)"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 実装済みコードの code-reviewer・security-reviewer によるレビュー(フェーズ E)を実行する。
|
|
3
|
+
disable-model-invocation: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# code-review
|
|
7
|
+
|
|
8
|
+
実装済みコードのレビューを行う。
|
|
9
|
+
|
|
10
|
+
## 必ず守ること
|
|
11
|
+
|
|
12
|
+
1. **最初に必ず** `.claude/skills/dev-workflow/SKILL.md` を Read する。記憶・推測で進めない
|
|
13
|
+
2. **フェーズ E(レビュー)** から実行する
|
|
14
|
+
3. dev-workflow/SKILL.md の AskUserQuestion・Edit・セッションファイル更新の手順を省略しない
|