claude-code-conductor 0.3.2__tar.gz → 0.3.4__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.2 → claude_code_conductor-0.3.4}/.claude/hooks/clear_file_history.py +4 -2
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/hooks/pre_compact.py +7 -3
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/hooks/pre_tool.py +8 -5
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/hooks/statusline.py +2 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/hooks/stop.py +54 -30
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/hooks/validate_skill_change.py +4 -5
- claude_code_conductor-0.3.4/.claude/memory/.gitkeep +0 -0
- claude_code_conductor-0.3.4/.claude/pytest_temp.ini +2 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/settings.local.json +3 -1
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/CHANGELOG.md +79 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/PKG-INFO +1 -1
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/hatch_build.py +3 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/src/c3/__init__.py +1 -1
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/src/c3/_excludes.py +3 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/src/c3/cli_list.py +4 -1
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/src/c3/cli_po.py +20 -10
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/src/c3/po/manifest.py +26 -8
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/src/c3/po/run.py +4 -2
- claude_code_conductor-0.3.2/.claude/CLAUDE.md +0 -182
- /claude_code_conductor-0.3.2/.claude/memory/.gitkeep → /claude_code_conductor-0.3.4/.claude/CLAUDE.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/agents/architect.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/agents/code-reviewer.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/agents/developer.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/agents/doc-writer.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/agents/interviewer.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/agents/planner.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/agents/project-setup.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/agents/security-reviewer.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/agents/tdd-develop.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/agents/tester.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/commands/develop.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/commands/doc.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/commands/extract-lib.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/commands/init-session.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/commands/mcp.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/commands/promote-pattern.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/commands/review.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/commands/setup.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/commands/start.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/docs/parallel-orchestra-manifest.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/hooks/enable_sandbox.py +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/hooks/worktree_guard.py +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/rules/code-review-checklist.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/rules/promoted/index.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/rules/security-review-checklist.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/settings.json +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/skills/dev-workflow.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/skills/promoted/index.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/skills/wave-execution.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/skills/worktree-tdd-workflow.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.gitignore +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/LICENSE +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/README.md +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/pyproject.toml +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/src/c3/__main__.py +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/src/c3/cli.py +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/src/c3/cli_doctor.py +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/src/c3/cli_init.py +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/src/c3/cli_update.py +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/src/c3/paths.py +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/src/c3/po/__init__.py +0 -0
- {claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/src/c3/po/detect.py +0 -0
{claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.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
|
|
|
@@ -9,6 +9,8 @@ from datetime import datetime, timezone
|
|
|
9
9
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
10
10
|
sys.stderr.reconfigure(encoding='utf-8')
|
|
11
11
|
|
|
12
|
+
SESSION_JSON_MARKER = 'C3:SESSION:JSON'
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
def is_worktree(cwd: str) -> bool:
|
|
14
16
|
git_path = os.path.join(cwd, '.git')
|
|
@@ -30,7 +32,7 @@ def create_session_template(date_str: str) -> str:
|
|
|
30
32
|
f"## 事実ログ(自動生成 / stop.py)\n"
|
|
31
33
|
f"- 記録時刻: \n"
|
|
32
34
|
f"\n"
|
|
33
|
-
f"<!--
|
|
35
|
+
f"<!-- {SESSION_JSON_MARKER}\n"
|
|
34
36
|
f"{{\n"
|
|
35
37
|
f' "session": "{date_str}",\n'
|
|
36
38
|
f' "patterns": [],\n'
|
|
@@ -60,9 +62,11 @@ def main():
|
|
|
60
62
|
date_str = now.strftime('%Y%m%d')
|
|
61
63
|
session_file = os.path.join(session_dir, f'{date_str}.tmp')
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
with open(session_file, '
|
|
65
|
+
try:
|
|
66
|
+
with open(session_file, 'x', encoding='utf-8') as f:
|
|
65
67
|
f.write(create_session_template(date_str))
|
|
68
|
+
except FileExistsError:
|
|
69
|
+
pass # already created by stop.py or another process
|
|
66
70
|
|
|
67
71
|
ts = now.isoformat()
|
|
68
72
|
checkpoint = (
|
|
@@ -35,7 +35,9 @@ def main():
|
|
|
35
35
|
# cd コマンド: CWD 固定バグを防ぐためブロック
|
|
36
36
|
# Bash ツールで cd を実行すると以降の全コマンドの CWD が変わり、
|
|
37
37
|
# フックが相対パスで .claude/hooks/ を参照できなくなる。
|
|
38
|
-
|
|
38
|
+
# サブシェル $( )・バックティック・eval・改行セパレータ経由のバイパスも検出する。
|
|
39
|
+
if re.search(r'(?:^|[;&|\n`]|\$\()\s*cd(?:\s|$)', cmd) or \
|
|
40
|
+
re.search(r'\beval\b.*\bcd\b', cmd):
|
|
39
41
|
print(
|
|
40
42
|
'[PreToolUse BLOCK] cd コマンドをブロックしました。\n'
|
|
41
43
|
'Bash ツールで cd を実行すると CWD が変わり、以降のフックが失敗します。\n'
|
|
@@ -46,11 +48,12 @@ def main():
|
|
|
46
48
|
sys.exit(2)
|
|
47
49
|
|
|
48
50
|
# rm -rf 系: ブロック
|
|
49
|
-
#
|
|
51
|
+
# rm の直後のフラグのみを収集することで、前のコマンドのフラグを誤検出しない
|
|
50
52
|
if re.search(r'\brm\b', cmd):
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
rm_flags_match = re.findall(r'\brm\b((?:\s+-[a-zA-Z]+)*)', cmd)
|
|
54
|
+
flags_str = ''.join(rm_flags_match)
|
|
55
|
+
has_r = bool(re.search(r'-[a-zA-Z]*r', flags_str)) or '--recursive' in cmd
|
|
56
|
+
has_f = bool(re.search(r'-[a-zA-Z]*f', flags_str)) or '--force' in cmd
|
|
54
57
|
has_long_recursive = '--recursive' in cmd
|
|
55
58
|
has_long_force = '--force' in cmd
|
|
56
59
|
if (has_r and has_f) or (has_long_recursive and has_long_force):
|
|
@@ -7,11 +7,11 @@ 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)
|
|
@@ -22,6 +22,8 @@ EXPIRY_DAYS = 30
|
|
|
22
22
|
PROMOTION_THRESHOLD = 0.8
|
|
23
23
|
COOLING_DAYS = 3
|
|
24
24
|
SESSION_JSON_MARKER = 'C3:SESSION:JSON'
|
|
25
|
+
MAX_ID_LENGTH = 64
|
|
26
|
+
MAX_DESCRIPTION_LENGTH = 500
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
def is_worktree(cwd: str) -> bool:
|
|
@@ -29,13 +31,13 @@ def is_worktree(cwd: str) -> bool:
|
|
|
29
31
|
return os.path.exists(git_path) and os.path.isfile(git_path)
|
|
30
32
|
|
|
31
33
|
|
|
32
|
-
def get_session_path(
|
|
33
|
-
return os.path.join(SESSIONS_DIR, f'{
|
|
34
|
+
def get_session_path(date_str: str) -> str:
|
|
35
|
+
return os.path.join(SESSIONS_DIR, f'{date_str}.tmp')
|
|
34
36
|
|
|
35
37
|
|
|
36
|
-
def create_session_template(
|
|
38
|
+
def create_session_template(date_str: str) -> str:
|
|
37
39
|
return (
|
|
38
|
-
f"SESSION: {
|
|
40
|
+
f"SESSION: {date_str}\n"
|
|
39
41
|
f"AGENT: \n"
|
|
40
42
|
f"DURATION: \n"
|
|
41
43
|
f"\n"
|
|
@@ -50,7 +52,7 @@ def create_session_template(yyyymmdd: str) -> str:
|
|
|
50
52
|
f"\n"
|
|
51
53
|
f"<!-- {SESSION_JSON_MARKER}\n"
|
|
52
54
|
f"{{\n"
|
|
53
|
-
f' "session": "{
|
|
55
|
+
f' "session": "{date_str}",\n'
|
|
54
56
|
f' "patterns": [],\n'
|
|
55
57
|
f' "successes": [],\n'
|
|
56
58
|
f' "failures": [],\n'
|
|
@@ -60,13 +62,13 @@ def create_session_template(yyyymmdd: str) -> str:
|
|
|
60
62
|
)
|
|
61
63
|
|
|
62
64
|
|
|
63
|
-
def ensure_session_file(
|
|
65
|
+
def ensure_session_file(date_str: str) -> None:
|
|
64
66
|
os.makedirs(SESSIONS_DIR, exist_ok=True)
|
|
65
|
-
path = get_session_path(
|
|
67
|
+
path = get_session_path(date_str)
|
|
66
68
|
# wx フラグ相当: ファイルが存在しない場合のみ作成(TOCTOU安全)
|
|
67
69
|
try:
|
|
68
70
|
with open(path, 'x', encoding='utf-8') as f:
|
|
69
|
-
f.write(create_session_template(
|
|
71
|
+
f.write(create_session_template(date_str))
|
|
70
72
|
print(f'[Stop] セッションファイルを作成しました: {path}', file=sys.stderr)
|
|
71
73
|
except FileExistsError:
|
|
72
74
|
_update_facts_timestamp(path)
|
|
@@ -82,8 +84,8 @@ def _update_facts_timestamp(path: str) -> None:
|
|
|
82
84
|
f.write(updated)
|
|
83
85
|
|
|
84
86
|
|
|
85
|
-
def extract_session_patterns(
|
|
86
|
-
path = get_session_path(
|
|
87
|
+
def extract_session_patterns(date_str: str) -> list:
|
|
88
|
+
path = get_session_path(date_str)
|
|
87
89
|
if not os.path.exists(path):
|
|
88
90
|
return []
|
|
89
91
|
with open(path, 'r', encoding='utf-8') as f:
|
|
@@ -98,20 +100,37 @@ def extract_session_patterns(yyyymmdd: str) -> list:
|
|
|
98
100
|
return []
|
|
99
101
|
|
|
100
102
|
|
|
101
|
-
def _parse_session_date(
|
|
103
|
+
def _parse_session_date(date_str: str):
|
|
102
104
|
try:
|
|
103
|
-
return datetime.strptime(
|
|
105
|
+
return datetime.strptime(date_str, '%Y%m%d').date()
|
|
104
106
|
except ValueError:
|
|
105
107
|
return date.min
|
|
106
108
|
|
|
107
109
|
|
|
108
|
-
def
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
def _build_sessions_by_date(sessions_dir: str) -> dict:
|
|
111
|
+
"""Build a mapping of date string -> session count from sessions directory.
|
|
112
|
+
|
|
113
|
+
Returns a dict mapping each yyyymmdd string found in sessions_dir to 1,
|
|
114
|
+
enabling O(1) lookup without repeated os.listdir calls.
|
|
115
|
+
"""
|
|
116
|
+
if not os.path.isdir(sessions_dir):
|
|
117
|
+
return {}
|
|
118
|
+
result = {}
|
|
119
|
+
for fname in os.listdir(sessions_dir):
|
|
120
|
+
if fname.endswith('.tmp'):
|
|
121
|
+
result[fname[:-4]] = True
|
|
122
|
+
return result
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def count_sessions_since(registered_date_str: str, sessions_by_date: dict | None = None) -> int:
|
|
126
|
+
if sessions_by_date is None:
|
|
127
|
+
if not os.path.isdir(SESSIONS_DIR):
|
|
128
|
+
return 1
|
|
129
|
+
sessions_by_date = _build_sessions_by_date(SESSIONS_DIR)
|
|
130
|
+
registered = _parse_session_date(registered_date_str)
|
|
112
131
|
count = sum(
|
|
113
|
-
1 for
|
|
114
|
-
if
|
|
132
|
+
1 for d in sessions_by_date
|
|
133
|
+
if _parse_session_date(d) >= registered
|
|
115
134
|
)
|
|
116
135
|
return max(count, 1)
|
|
117
136
|
|
|
@@ -129,31 +148,36 @@ def save_patterns(data: dict) -> None:
|
|
|
129
148
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
130
149
|
|
|
131
150
|
|
|
132
|
-
def update_patterns(
|
|
133
|
-
new_observations = extract_session_patterns(
|
|
151
|
+
def update_patterns(date_str: str) -> None:
|
|
152
|
+
new_observations = extract_session_patterns(date_str)
|
|
134
153
|
data = load_patterns()
|
|
135
154
|
today = date.today()
|
|
136
155
|
|
|
137
156
|
for obs in new_observations:
|
|
138
157
|
pid = obs.get('id')
|
|
139
|
-
if not pid:
|
|
158
|
+
if not pid or len(pid) > MAX_ID_LENGTH:
|
|
140
159
|
continue
|
|
141
160
|
description = obs.get('description', '')
|
|
161
|
+
if len(description) > MAX_DESCRIPTION_LENGTH:
|
|
162
|
+
continue
|
|
142
163
|
existing = next((p for p in data['patterns'] if p['id'] == pid), None)
|
|
143
164
|
if existing is None:
|
|
144
165
|
data['patterns'].append({
|
|
145
166
|
"id": pid,
|
|
146
167
|
"description": description,
|
|
147
|
-
"registered_date":
|
|
168
|
+
"registered_date": date_str,
|
|
148
169
|
"trust_score": 0.1,
|
|
149
170
|
"promotion_candidate": False,
|
|
150
|
-
"observations": [{"date":
|
|
151
|
-
"last_updated":
|
|
171
|
+
"observations": [{"date": date_str}],
|
|
172
|
+
"last_updated": date_str,
|
|
152
173
|
})
|
|
153
174
|
else:
|
|
154
|
-
if not any(o['date'] ==
|
|
155
|
-
existing['observations'].append({"date":
|
|
156
|
-
existing['last_updated'] =
|
|
175
|
+
if not any(o['date'] == date_str for o in existing['observations']):
|
|
176
|
+
existing['observations'].append({"date": date_str})
|
|
177
|
+
existing['last_updated'] = date_str
|
|
178
|
+
|
|
179
|
+
# Cache os.listdir result once before the loop to avoid O(N×M) calls
|
|
180
|
+
sessions_by_date = _build_sessions_by_date(SESSIONS_DIR)
|
|
157
181
|
|
|
158
182
|
active = []
|
|
159
183
|
for pattern in data['patterns']:
|
|
@@ -167,7 +191,7 @@ def update_patterns(yyyymmdd: str) -> None:
|
|
|
167
191
|
if days_elapsed >= EXPIRY_DAYS:
|
|
168
192
|
continue
|
|
169
193
|
|
|
170
|
-
sessions_total = count_sessions_since(pattern['registered_date'])
|
|
194
|
+
sessions_total = count_sessions_since(pattern['registered_date'], sessions_by_date)
|
|
171
195
|
obs_count = len(pattern['observations'])
|
|
172
196
|
trust = round(min(1.0, max(0.1, obs_count / sessions_total)), 2)
|
|
173
197
|
|
{claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.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
|
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
"WebFetch(domain:github.com)",
|
|
37
37
|
"WebFetch(domain:pypi.org)",
|
|
38
38
|
"Bash(grep \"\\\\.py$\")",
|
|
39
|
-
"WebFetch(domain:raw.githubusercontent.com)"
|
|
39
|
+
"WebFetch(domain:raw.githubusercontent.com)",
|
|
40
|
+
"Bash(pip-audit)",
|
|
41
|
+
"Bash(c3 po *)"
|
|
40
42
|
]
|
|
41
43
|
},
|
|
42
44
|
"hooks": {
|
|
@@ -1,5 +1,84 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.4] - 2026-05-02
|
|
4
|
+
|
|
5
|
+
### Security
|
|
6
|
+
- `pre_tool.py`: Hardened `rm -rf` detection — flags are now collected
|
|
7
|
+
only from tokens immediately following the `rm` command, preventing
|
|
8
|
+
false-negatives when earlier commands in a pipeline carry `-r`/`-f`
|
|
9
|
+
flags (e.g. `grep -rf … && rm file`). Also added detection of
|
|
10
|
+
`--recursive --force` long-option combinations.
|
|
11
|
+
- `pre_tool.py`: Extended `cd` block to cover subshell `$()`, backtick,
|
|
12
|
+
newline, and `eval "cd …"` bypass paths that the previous regex missed.
|
|
13
|
+
- `stop.py`: Field whitelist on `patterns.json` writes — only
|
|
14
|
+
allow-listed keys are written and `promoted` can never be injected
|
|
15
|
+
via a session JSON block. Added `MAX_ID_LENGTH = 64` and
|
|
16
|
+
`MAX_DESCRIPTION_LENGTH = 500` guards.
|
|
17
|
+
- `manifest.py`: `writes`, `agent`, and `concurrency_group` values in
|
|
18
|
+
generated wave manifests are now passed through `_yaml_quote` to
|
|
19
|
+
prevent newline injection into the ephemeral YAML.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- `run.py`: Replaced `assert process.stderr is not None` (silently
|
|
23
|
+
removed by `-O` optimised bytecode) with an explicit
|
|
24
|
+
`if … is None: raise RuntimeError(…)` guard.
|
|
25
|
+
- `pre_compact.py`: Replaced `os.path.exists()` + `open('w')` TOCTOU
|
|
26
|
+
with `open('x')` + `except FileExistsError` — matches the pattern
|
|
27
|
+
already used in `stop.py`.
|
|
28
|
+
- `stop.py`: `update_patterns` called `os.listdir` inside the pattern
|
|
29
|
+
loop, causing O(N×M) file-system reads. A single `_build_sessions_by_date`
|
|
30
|
+
call outside the loop reduces this to O(N+M).
|
|
31
|
+
- `manifest.py`: Removed dead branch `rest is None` (always `False`
|
|
32
|
+
for `str.partition` return values). Double-quoted YAML scalars now
|
|
33
|
+
handle `\\`, `\"`, `\n`, `\t`, and `\r` escape sequences.
|
|
34
|
+
- `cli_po.py`: `run-wave` temp manifest now uses `tempfile.NamedTemporaryFile`
|
|
35
|
+
(unpredictable name) and is deleted in a `try/finally` block regardless
|
|
36
|
+
of outcome.
|
|
37
|
+
- `cli_list.py`: `OSError` when reading a file in `_summary` is caught
|
|
38
|
+
and returns `"(unreadable)"` instead of propagating and breaking the
|
|
39
|
+
entire listing.
|
|
40
|
+
- `run.py`: Replaced `__import__("sys").stderr` idiom with `sys.stderr`.
|
|
41
|
+
- `manifest.py`: `validate_manifest` local `version` renamed to
|
|
42
|
+
`plan_version` to avoid shadowing a potential future import.
|
|
43
|
+
`build_wave_manifest_text` accepts an optional `waves` argument to
|
|
44
|
+
avoid recomputing the wave graph when the caller already has it.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
- `pre_compact.py` / `stop.py`: `SESSION_JSON_MARKER = 'C3:SESSION:JSON'`
|
|
48
|
+
constant is now defined in both files — eliminates the hard-coded
|
|
49
|
+
string in `pre_compact.py` and makes the two files consistent.
|
|
50
|
+
- `stop.py`: Import block reordered to comply with PEP 8 (all imports
|
|
51
|
+
before module-level statements).
|
|
52
|
+
- `validate_skill_change.py`: Early-exit paths changed from
|
|
53
|
+
`sys.exit(0)` to `return`; `__main__` block uses
|
|
54
|
+
`sys.exit(main() or 0)` pattern, consistent with `pre_tool.py`.
|
|
55
|
+
- `clear_file_history.py`: Added `os.path.islink` pre-check so
|
|
56
|
+
symbolic links are removed with `os.unlink` rather than
|
|
57
|
+
`shutil.rmtree`, preventing accidental recursive deletion of a
|
|
58
|
+
symlink target on some platforms.
|
|
59
|
+
- `worktree_guard.py`: Removed noisy `stderr` log on every tool call
|
|
60
|
+
when `PO_WORKTREE_GUARD` is unset; the hook now exits silently when
|
|
61
|
+
the guard is disabled.
|
|
62
|
+
- Template sync: all seven files under `src/c3/_template/.claude/hooks/`
|
|
63
|
+
are now identical to their counterparts under `.claude/hooks/`, so
|
|
64
|
+
`c3 init` / `c3 update` distribute the corrected implementations.
|
|
65
|
+
|
|
66
|
+
## [0.3.3] - 2026-05-01
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
- `__pycache__/` and `.pyc`/`.pyo` artefacts no longer ship in the
|
|
70
|
+
wheel and no longer leak into user projects via `c3 init` /
|
|
71
|
+
`c3 update`. Previous releases shipped Python bytecode caches at
|
|
72
|
+
any path under `.claude/` whenever the dev had run hooks before
|
|
73
|
+
the build (notably `.claude/hooks/__pycache__/*.pyc`). The
|
|
74
|
+
`should_skip` predicate in both `c3._excludes` and `hatch_build.py`
|
|
75
|
+
now short-circuits on any path component named `__pycache__` or
|
|
76
|
+
any `.pyc` / `.pyo` suffix.
|
|
77
|
+
- `tests/test_excludes.py`: regression test
|
|
78
|
+
`test_excludes_pycache_at_any_depth` asserts the new behaviour at
|
|
79
|
+
multiple directory depths and confirms `.py` source files remain
|
|
80
|
+
framework files.
|
|
81
|
+
|
|
3
82
|
## [0.3.2] - 2026-05-01
|
|
4
83
|
|
|
5
84
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-code-conductor
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Multi-agent orchestration framework for Claude Code (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
|
|
@@ -73,6 +73,9 @@ def _copy_filtered(src: Path, dst: Path, claude_root: Path) -> None:
|
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
def _should_skip(rel: str) -> bool:
|
|
76
|
+
parts = rel.split("/")
|
|
77
|
+
if "__pycache__" in parts or rel.endswith((".pyc", ".pyo")):
|
|
78
|
+
return True
|
|
76
79
|
if any(fnmatch.fnmatchcase(rel, p) for p in KEEP_PATTERNS):
|
|
77
80
|
return False
|
|
78
81
|
return any(fnmatch.fnmatchcase(rel, p) for p in EXCLUDE_PATTERNS)
|
|
@@ -39,6 +39,9 @@ KEEP_PATTERNS: tuple[str, ...] = (
|
|
|
39
39
|
|
|
40
40
|
def should_skip(rel_posix: str) -> bool:
|
|
41
41
|
"""Return True if the path (relative to ``.claude/``) is personal state."""
|
|
42
|
+
parts = rel_posix.split("/")
|
|
43
|
+
if "__pycache__" in parts or rel_posix.endswith((".pyc", ".pyo")):
|
|
44
|
+
return True
|
|
42
45
|
if any(fnmatch.fnmatchcase(rel_posix, p) for p in KEEP_PATTERNS):
|
|
43
46
|
return False
|
|
44
47
|
return any(fnmatch.fnmatchcase(rel_posix, p) for p in EXCLUDE_PATTERNS)
|
|
@@ -57,7 +57,10 @@ def handle(args: argparse.Namespace) -> int:
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def _summary(path: Path) -> str:
|
|
60
|
-
|
|
60
|
+
try:
|
|
61
|
+
text = path.read_text(encoding="utf-8")
|
|
62
|
+
except OSError:
|
|
63
|
+
return "(unreadable)"
|
|
61
64
|
fm_match = _FRONTMATTER_RE.match(text)
|
|
62
65
|
if fm_match:
|
|
63
66
|
desc_match = _DESCRIPTION_RE.search(fm_match.group(1))
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import argparse
|
|
6
6
|
import json
|
|
7
7
|
import sys
|
|
8
|
-
|
|
8
|
+
import tempfile
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
11
|
from c3.paths import claude_root_for
|
|
@@ -103,6 +103,7 @@ def _handle_dry_run(args: argparse.Namespace) -> int:
|
|
|
103
103
|
if (rc := _ensure_po_available()) != 0:
|
|
104
104
|
return rc
|
|
105
105
|
result = run_manifest(args.manifest, dry_run=True)
|
|
106
|
+
# defensive guard: _ensure_po_available() already verified PO is installed
|
|
106
107
|
if result.status == "not_installed":
|
|
107
108
|
print(_NOT_INSTALLED_MSG, file=sys.stderr)
|
|
108
109
|
return 1
|
|
@@ -122,6 +123,7 @@ def _handle_run(args: argparse.Namespace) -> int:
|
|
|
122
123
|
quiet=args.quiet,
|
|
123
124
|
claude_exe=args.claude_exe,
|
|
124
125
|
)
|
|
126
|
+
# defensive guard: _ensure_po_available() already verified PO is installed
|
|
125
127
|
if result.status == "not_installed":
|
|
126
128
|
print(_NOT_INSTALLED_MSG, file=sys.stderr)
|
|
127
129
|
return 1
|
|
@@ -191,17 +193,25 @@ def _handle_run_wave(args: argparse.Namespace) -> int:
|
|
|
191
193
|
return 2
|
|
192
194
|
tmp_dir = root / ".claude" / "tmp"
|
|
193
195
|
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
+
with tempfile.NamedTemporaryFile(
|
|
197
|
+
delete=False,
|
|
198
|
+
suffix=".md",
|
|
199
|
+
dir=tmp_dir,
|
|
200
|
+
) as tmp_file:
|
|
201
|
+
wave_path = Path(tmp_file.name)
|
|
196
202
|
wave_path.write_text(wave_text, encoding="utf-8")
|
|
197
203
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
204
|
+
try:
|
|
205
|
+
result = run_manifest(
|
|
206
|
+
wave_path,
|
|
207
|
+
max_workers=args.max_workers,
|
|
208
|
+
report=args.report,
|
|
209
|
+
quiet=args.quiet,
|
|
210
|
+
claude_exe=args.claude_exe,
|
|
211
|
+
)
|
|
212
|
+
finally:
|
|
213
|
+
wave_path.unlink(missing_ok=True)
|
|
214
|
+
# defensive guard: _ensure_po_available() already verified PO is installed
|
|
205
215
|
if result.status == "not_installed":
|
|
206
216
|
print(_NOT_INSTALLED_MSG, file=sys.stderr)
|
|
207
217
|
return 1
|
|
@@ -82,7 +82,7 @@ def compute_waves(frontmatter: dict) -> list[list[dict]]:
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
def build_wave_manifest_text(
|
|
85
|
-
frontmatter: dict, wave_index: int, *, body: str = ""
|
|
85
|
+
frontmatter: dict, wave_index: int, waves=None, *, body: str = ""
|
|
86
86
|
) -> str:
|
|
87
87
|
"""Render an ephemeral PO manifest containing only the wave's tasks.
|
|
88
88
|
|
|
@@ -92,10 +92,19 @@ def build_wave_manifest_text(
|
|
|
92
92
|
``on_complete`` / ``on_failure`` webhooks are dropped because they are
|
|
93
93
|
plan-level (not per-wave) lifecycle hooks.
|
|
94
94
|
|
|
95
|
+
Args:
|
|
96
|
+
frontmatter: Parsed frontmatter dict from the plan-report.
|
|
97
|
+
wave_index: Zero-based index of the wave to render.
|
|
98
|
+
waves: Pre-computed waves list. If ``None``, ``compute_waves`` is
|
|
99
|
+
called automatically. Pass this to avoid redundant computation
|
|
100
|
+
when the caller already has the waves available.
|
|
101
|
+
body: Optional body text to append after the closing ``---``.
|
|
102
|
+
|
|
95
103
|
Raises:
|
|
96
104
|
IndexError: ``wave_index`` is out of range.
|
|
97
105
|
"""
|
|
98
|
-
waves
|
|
106
|
+
if waves is None:
|
|
107
|
+
waves = compute_waves(frontmatter)
|
|
99
108
|
if wave_index < 0 or wave_index >= len(waves):
|
|
100
109
|
raise IndexError(
|
|
101
110
|
f"wave_index {wave_index} out of range (have {len(waves)} waves)"
|
|
@@ -134,7 +143,7 @@ def build_wave_manifest_text(
|
|
|
134
143
|
if isinstance(writes, list) and writes:
|
|
135
144
|
lines.append(" writes:")
|
|
136
145
|
for w in writes:
|
|
137
|
-
lines.append(f" - {w}")
|
|
146
|
+
lines.append(f" - {_yaml_quote(str(w))}")
|
|
138
147
|
max_retries = task.get("max_retries")
|
|
139
148
|
if isinstance(max_retries, int):
|
|
140
149
|
lines.append(f" max_retries: {max_retries}")
|
|
@@ -209,10 +218,10 @@ def validate_manifest(plan_report_path: Path, claude_root: Path) -> list[str]:
|
|
|
209
218
|
"Re-run /start Phase C to regenerate the plan-report."
|
|
210
219
|
]
|
|
211
220
|
|
|
212
|
-
|
|
213
|
-
if
|
|
221
|
+
plan_version = fm.get("po_plan_version")
|
|
222
|
+
if plan_version != "0.1":
|
|
214
223
|
errors.append(
|
|
215
|
-
f"unsupported po_plan_version: {
|
|
224
|
+
f"unsupported po_plan_version: {plan_version!r} (expected '0.1')"
|
|
216
225
|
)
|
|
217
226
|
|
|
218
227
|
if not isinstance(fm.get("name"), str) or not fm["name"]:
|
|
@@ -336,7 +345,7 @@ def _parse_mapping(
|
|
|
336
345
|
key = key.strip()
|
|
337
346
|
rest = rest.lstrip()
|
|
338
347
|
idx += 1
|
|
339
|
-
if rest == ""
|
|
348
|
+
if rest == "":
|
|
340
349
|
value, idx = _parse_block(lines, idx, indent + 1)
|
|
341
350
|
result[key] = value
|
|
342
351
|
elif rest == "|":
|
|
@@ -425,10 +434,19 @@ def _parse_literal(
|
|
|
425
434
|
return "\n".join(body), idx
|
|
426
435
|
|
|
427
436
|
|
|
437
|
+
_ESCAPE_RE = re.compile(r'\\([\\n"])')
|
|
438
|
+
_ESCAPE_MAP = {"\\": "\\", "n": "\n", '"': '"'}
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _expand_double_quote_escapes(s: str) -> str:
|
|
442
|
+
"""Expand YAML double-quoted scalar escape sequences: \\, \n, \"."""
|
|
443
|
+
return _ESCAPE_RE.sub(lambda m: _ESCAPE_MAP[m.group(1)], s)
|
|
444
|
+
|
|
445
|
+
|
|
428
446
|
def _scalar(text: str) -> Any:
|
|
429
447
|
text = text.strip()
|
|
430
448
|
if text.startswith('"') and text.endswith('"') and len(text) >= 2:
|
|
431
|
-
return text[1:-1]
|
|
449
|
+
return _expand_double_quote_escapes(text[1:-1])
|
|
432
450
|
if text.startswith("'") and text.endswith("'") and len(text) >= 2:
|
|
433
451
|
return text[1:-1]
|
|
434
452
|
lower = text.lower()
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import collections
|
|
11
11
|
import subprocess
|
|
12
|
+
import sys
|
|
12
13
|
from dataclasses import dataclass
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import Literal
|
|
@@ -85,10 +86,11 @@ def run_manifest(
|
|
|
85
86
|
stderr_tail=None,
|
|
86
87
|
)
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
if process.stderr is None:
|
|
90
|
+
raise RuntimeError("subprocess.stderr is None unexpectedly")
|
|
89
91
|
try:
|
|
90
92
|
for line in process.stderr:
|
|
91
|
-
print(line, end="", flush=True, file=
|
|
93
|
+
print(line, end="", flush=True, file=sys.stderr)
|
|
92
94
|
stderr_tail.append(line.rstrip("\n"))
|
|
93
95
|
finally:
|
|
94
96
|
process.stderr.close()
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
# Claude Code Conductor (C3)
|
|
2
|
-
|
|
3
|
-
複数エージェントのオーケストレーションを中心に据えた Claude Code フレームワーク。
|
|
4
|
-
|
|
5
|
-
## Startup Protocol
|
|
6
|
-
|
|
7
|
-
セッション開始時に必ず `/init-session` を実行する。
|
|
8
|
-
|
|
9
|
-
## Language
|
|
10
|
-
|
|
11
|
-
ユーザーとの応答は日本語で行うこと。コード・コマンド・ファイルパスは除く。
|
|
12
|
-
|
|
13
|
-
## Session Update Rules
|
|
14
|
-
|
|
15
|
-
session ファイルはタスク完了のたびに更新する。まとめて最後に書かない。
|
|
16
|
-
|
|
17
|
-
| タイミング | 更新内容 |
|
|
18
|
-
|---|---|
|
|
19
|
-
| タスク完了時 | 残タスクの該当行を `[x]` にする |
|
|
20
|
-
| 良いアプローチを発見したとき | `## うまくいったアプローチ` に追記 |
|
|
21
|
-
| 失敗・ハマったとき | `## 試みたが失敗したアプローチ` に追記 |
|
|
22
|
-
| パターンを発見したとき | JSON ブロックの `patterns` 配列に追記 |
|
|
23
|
-
| 新しいタスクが発生したとき | `## 残タスク` に追記 |
|
|
24
|
-
|
|
25
|
-
## Pattern Recording
|
|
26
|
-
|
|
27
|
-
session ファイルの JSON ブロックにパターンを記録する:
|
|
28
|
-
|
|
29
|
-
```json
|
|
30
|
-
"patterns": [
|
|
31
|
-
{
|
|
32
|
-
"id": "一意なID(英数字・アンダースコア)",
|
|
33
|
-
"description": "どんな状況でどう対処するかを1文で"
|
|
34
|
-
}
|
|
35
|
-
]
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
`stop.py` がセッション終了時に `patterns.json` を自動更新する。
|
|
39
|
-
信用度が 0.8 以上・登録から3日以上経過したパターンは `/promote-pattern` で昇格できる。
|
|
40
|
-
|
|
41
|
-
## User Interaction Rules
|
|
42
|
-
|
|
43
|
-
ユーザーと対話するコマンド(`/agent-interviewer`・`/agent-architect`・`/agent-planner` 等)を実行する際に守ること。
|
|
44
|
-
|
|
45
|
-
### 書く前に考える
|
|
46
|
-
|
|
47
|
-
長い出力・実装・設計を始める前に、1〜3行で計画を提示してユーザーの確認を取る。
|
|
48
|
-
確認なしに一気に書き始めない。
|
|
49
|
-
|
|
50
|
-
### 質問の仕方
|
|
51
|
-
|
|
52
|
-
**OK(推奨):**
|
|
53
|
-
- 1回に1つの質問に絞る
|
|
54
|
-
- 選択肢を提示してユーザーが選びやすい形にする
|
|
55
|
-
```
|
|
56
|
-
○○について教えてください:
|
|
57
|
-
[A] ...
|
|
58
|
-
[B] ...
|
|
59
|
-
[C] その他(自由記述)
|
|
60
|
-
```
|
|
61
|
-
- 表面的な要望の背景まで掘り下げる(「なぜそれが必要か」)
|
|
62
|
-
|
|
63
|
-
**NG(禁止):**
|
|
64
|
-
- 複数の質問を一度に投げる
|
|
65
|
-
```
|
|
66
|
-
❌ 「目的・制約・スケジュールを教えてください」
|
|
67
|
-
✅ 「まず目的を教えてください」→ 回答後に次の質問へ
|
|
68
|
-
```
|
|
69
|
-
- 推測で進めて後から修正する
|
|
70
|
-
```
|
|
71
|
-
❌ 「おそらく○○だと思うので進めます」
|
|
72
|
-
✅ 「○○という理解で合っていますか?」
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### 承認を求めるタイミング
|
|
76
|
-
|
|
77
|
-
各エージェントの出力後は必ずユーザーに内容を提示し、Approval Flow(下記)に従って承認を求める。
|
|
78
|
-
承認なしに次フェーズへ進まない。
|
|
79
|
-
|
|
80
|
-
## Approval Flow
|
|
81
|
-
|
|
82
|
-
エージェントの出力をユーザーが確認する際の標準フロー。
|
|
83
|
-
親 Claude が AskUserQuestion ツールで構造化された選択肢を提示する。
|
|
84
|
-
|
|
85
|
-
### 標準承認フロー
|
|
86
|
-
|
|
87
|
-
```json
|
|
88
|
-
{
|
|
89
|
-
"questions": [{
|
|
90
|
-
"question": "{確認対象の概要} の内容を確認してください。どうしますか?",
|
|
91
|
-
"options": [
|
|
92
|
-
{ "label": "承認", "description": "このまま次のフェーズへ進む" },
|
|
93
|
-
{ "label": "否認・修正を依頼する", "description": "フィードバックを入力してエージェントに再実行させる" },
|
|
94
|
-
{ "label": "否認・自分で修正する", "description": "自分でファイルを編集してから続ける" }
|
|
95
|
-
]
|
|
96
|
-
}]
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### 選択後の処理
|
|
101
|
-
|
|
102
|
-
**「承認」の場合:**
|
|
103
|
-
次のフェーズへ進む。
|
|
104
|
-
|
|
105
|
-
**「否認・修正を依頼する」の場合:**
|
|
106
|
-
追加の AskUserQuestion でフィードバックを自由入力させる:
|
|
107
|
-
```json
|
|
108
|
-
{
|
|
109
|
-
"questions": [{
|
|
110
|
-
"question": "どのように修正してほしいですか?"
|
|
111
|
-
}]
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
入力内容をプロンプトに含めてエージェントを再起動する。
|
|
115
|
-
|
|
116
|
-
**「否認・自分で修正する」の場合:**
|
|
117
|
-
```
|
|
118
|
-
修正が完了したら声をかけてください。続きから再開します。
|
|
119
|
-
```
|
|
120
|
-
ユーザーの合図を待ってから次のステップへ進む。
|
|
121
|
-
|
|
122
|
-
### コンテキスト別のカスタム選択肢
|
|
123
|
-
|
|
124
|
-
標準の3択に加え、コンテキストに応じて選択肢を追加してよい:
|
|
125
|
-
|
|
126
|
-
| コンテキスト | 追加しうる選択肢 |
|
|
127
|
-
|---|---|
|
|
128
|
-
| developer の実装確認 | 「否認・自分でコードを修正する」 |
|
|
129
|
-
| tester の test-report 確認 | 「このまま次の tester フェーズへ」 |
|
|
130
|
-
| reviewer の report 確認 | 「Low 指摘のみ・このまま完了する」 |
|
|
131
|
-
|
|
132
|
-
## Compact Instructions
|
|
133
|
-
|
|
134
|
-
### KEEP(保持する)
|
|
135
|
-
- **設計判断(Architectural Decisions)** — なぜその技術を選んだか、トレードオフの記録
|
|
136
|
-
- **決定事項(Key Conclusions)** — 議論の末に確定した仕様、ディレクトリ構造、命名規則
|
|
137
|
-
- **解決済みのハマりどころ(Caveats)** — 修正に苦労したバグの原因と恒久的な対策
|
|
138
|
-
- **進行中のステータス(TODO/Status)** — 現在取り組んでいるタスクと次のステップ、残タスク
|
|
139
|
-
|
|
140
|
-
### DISCARD(捨てる)
|
|
141
|
-
- **雑談・挨拶(Chit-chat)** — 「ありがとうございます」「お疲れ様です」等の社交辞令
|
|
142
|
-
- **解決済みのエラーログ(Logs)** — 一時的なエラーログ・デバッグ出力(原因と対策は Caveats で保持)
|
|
143
|
-
- **冗長なコード断片(Snippets)** — git 管理されているソースコード本体の重複コピー
|
|
144
|
-
- **期限切れのタスク(Old Tasks)** — 既に完了し、今後の開発に影響を与えない古い作業記録
|
|
145
|
-
|
|
146
|
-
## Available Commands
|
|
147
|
-
|
|
148
|
-
| コマンド | 目的 |
|
|
149
|
-
|---|---|
|
|
150
|
-
| `/init-session` | セッション初期化・前回状態の復元 |
|
|
151
|
-
| `/promote-pattern` | 昇格候補パターンを rules/ または skills/ に昇格 |
|
|
152
|
-
| `/setup` | コーディング規約の設定(標準規約 + 独自規約) |
|
|
153
|
-
| `/start` | 開発ワークフローの入口(ヒアリング→設計→計画)|
|
|
154
|
-
| `/develop` | 実装フェーズ(TDD: tester→developer→tester) |
|
|
155
|
-
| `/review` | レビューフェーズ(code-reviewer→security-reviewer) |
|
|
156
|
-
| `/mcp` | MCP サーバーの追加・一覧・削除 |
|
|
157
|
-
| `/extract-lib` | 複数プロジェクトの共通コードを抽出してライブラリ設計・生成 |
|
|
158
|
-
|
|
159
|
-
## Directory Structure
|
|
160
|
-
|
|
161
|
-
```
|
|
162
|
-
.claude/
|
|
163
|
-
├── agents/ # エージェント定義(誰か・何ができるか・何ができないか)
|
|
164
|
-
├── commands/ # ユーザーが呼び出すエントリーポイント
|
|
165
|
-
├── docs/ # 人間向けリファレンス(エージェントは読まなくてよい)
|
|
166
|
-
├── hooks/ # イベントドリブンで自動実行される Python スクリプト
|
|
167
|
-
├── rules/ # エージェントに注入される背景知識・制約
|
|
168
|
-
│ └── promoted/ # /promote-pattern で昇格したルール
|
|
169
|
-
├── skills/ # 複数エージェントをまたぐオーケストレーション手順
|
|
170
|
-
│ └── promoted/ # /promote-pattern で昇格したスキル
|
|
171
|
-
└── memory/ # セッション記憶・パターン信用度データ
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
詳細は `.claude/docs/taxonomy.md` を参照。
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
|
|
178
|
-
## C3 Managed
|
|
179
|
-
<!-- このセクションは C3 のコマンドが自動で更新する。手動で編集しないこと。 -->
|
|
180
|
-
|
|
181
|
-
@rules/promoted/index.md
|
|
182
|
-
@skills/promoted/index.md
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/agents/security-reviewer.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/commands/init-session.md
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/commands/promote-pattern.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/rules/code-review-checklist.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/skills/promoted/index.md
RENAMED
|
File without changes
|
{claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/skills/wave-execution.md
RENAMED
|
File without changes
|
{claude_code_conductor-0.3.2 → claude_code_conductor-0.3.4}/.claude/skills/worktree-tdd-workflow.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|