claude-code-conductor 0.2.0__py3-none-any.whl

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.
Files changed (57) hide show
  1. c3/__init__.py +3 -0
  2. c3/__main__.py +4 -0
  3. c3/_template/.claude/CLAUDE.md +182 -0
  4. c3/_template/.claude/agents/architect.md +50 -0
  5. c3/_template/.claude/agents/code-reviewer.md +50 -0
  6. c3/_template/.claude/agents/developer.md +55 -0
  7. c3/_template/.claude/agents/doc-writer.md +62 -0
  8. c3/_template/.claude/agents/interviewer.md +46 -0
  9. c3/_template/.claude/agents/planner.md +59 -0
  10. c3/_template/.claude/agents/project-setup.md +106 -0
  11. c3/_template/.claude/agents/security-reviewer.md +51 -0
  12. c3/_template/.claude/agents/tdd-develop.md +117 -0
  13. c3/_template/.claude/agents/tester.md +48 -0
  14. c3/_template/.claude/commands/develop.md +10 -0
  15. c3/_template/.claude/commands/doc.md +174 -0
  16. c3/_template/.claude/commands/extract-lib.md +292 -0
  17. c3/_template/.claude/commands/init-session.md +110 -0
  18. c3/_template/.claude/commands/mcp.md +322 -0
  19. c3/_template/.claude/commands/promote-pattern.md +135 -0
  20. c3/_template/.claude/commands/review.md +9 -0
  21. c3/_template/.claude/commands/setup.md +206 -0
  22. c3/_template/.claude/commands/start.md +88 -0
  23. c3/_template/.claude/docs/parallel-orchestra-manifest.md +108 -0
  24. c3/_template/.claude/hooks/clear_file_history.py +39 -0
  25. c3/_template/.claude/hooks/enable_sandbox.py +61 -0
  26. c3/_template/.claude/hooks/pre_compact.py +82 -0
  27. c3/_template/.claude/hooks/pre_tool.py +64 -0
  28. c3/_template/.claude/hooks/statusline.py +170 -0
  29. c3/_template/.claude/hooks/stop.py +202 -0
  30. c3/_template/.claude/hooks/validate_skill_change.py +33 -0
  31. c3/_template/.claude/hooks/worktree_guard.py +53 -0
  32. c3/_template/.claude/memory/.gitkeep +0 -0
  33. c3/_template/.claude/rules/code-review-checklist.md +91 -0
  34. c3/_template/.claude/rules/promoted/index.md +5 -0
  35. c3/_template/.claude/rules/security-review-checklist.md +84 -0
  36. c3/_template/.claude/settings.json +136 -0
  37. c3/_template/.claude/settings.local.json +126 -0
  38. c3/_template/.claude/skills/dev-workflow.md +484 -0
  39. c3/_template/.claude/skills/parallel-execution.md +121 -0
  40. c3/_template/.claude/skills/promoted/index.md +5 -0
  41. c3/_template/.claude/skills/worktree-tdd-workflow.md +71 -0
  42. c3/cli.py +63 -0
  43. c3/cli_doctor.py +135 -0
  44. c3/cli_init.py +70 -0
  45. c3/cli_list.py +69 -0
  46. c3/cli_po.py +102 -0
  47. c3/cli_update.py +117 -0
  48. c3/paths.py +64 -0
  49. c3/po/__init__.py +11 -0
  50. c3/po/detect.py +44 -0
  51. c3/po/manifest.py +336 -0
  52. c3/po/run.py +105 -0
  53. claude_code_conductor-0.2.0.dist-info/METADATA +362 -0
  54. claude_code_conductor-0.2.0.dist-info/RECORD +57 -0
  55. claude_code_conductor-0.2.0.dist-info/WHEEL +4 -0
  56. claude_code_conductor-0.2.0.dist-info/entry_points.txt +2 -0
  57. claude_code_conductor-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env python3
2
+ """Context gauge statusline script.
3
+ Displays context usage + optional rate limit gauges (when plan provides rate_limits data).
4
+ """
5
+
6
+ import json
7
+ import sys
8
+ import threading
9
+ from datetime import datetime, timezone
10
+
11
+ sys.stdout.reconfigure(encoding='utf-8')
12
+ sys.stderr.reconfigure(encoding='utf-8')
13
+ sys.stdin.reconfigure(encoding='utf-8')
14
+
15
+ MAX_INPUT = 64 * 1024 # 64 KB
16
+
17
+ # ANSI color / style codes
18
+ GREEN = '\x1b[32m'
19
+ RED = '\x1b[31m'
20
+ YELLOW = '\x1b[33m'
21
+ ORANGE = '\x1b[38;5;208m'
22
+ DIM = '\x1b[2m'
23
+ RESET = '\x1b[0m'
24
+
25
+ # Gauge characters
26
+ BLOCK = '█'
27
+ BLOCK_EMPTY = '░'
28
+ TOTAL_CELLS = 10
29
+
30
+
31
+ def pct_color(pct: int) -> str:
32
+ if pct > 90:
33
+ return RED
34
+ elif pct > 75:
35
+ return ORANGE
36
+ elif pct > 60:
37
+ return YELLOW
38
+ else:
39
+ return GREEN
40
+
41
+
42
+ def build_gauge(pct: int) -> str:
43
+ filled = min(pct // 10, TOTAL_CELLS)
44
+ empty = TOTAL_CELLS - filled
45
+ color = pct_color(pct)
46
+ return (
47
+ DIM + '[' + RESET +
48
+ color + BLOCK * filled + RESET +
49
+ DIM + BLOCK_EMPTY * empty + RESET +
50
+ DIM + ']' + RESET
51
+ )
52
+
53
+
54
+ def format_reset_time(resets_at) -> str:
55
+ if not resets_at:
56
+ return ''
57
+ try:
58
+ if isinstance(resets_at, (int, float)):
59
+ ts_sec = float(resets_at)
60
+ else:
61
+ ts_sec = datetime.fromisoformat(
62
+ str(resets_at).replace('Z', '+00:00')
63
+ ).timestamp()
64
+ now_sec = datetime.now(timezone.utc).timestamp()
65
+ diff_sec = int(ts_sec - now_sec)
66
+ except Exception:
67
+ return ''
68
+
69
+ if diff_sec <= 0:
70
+ return 'reset'
71
+
72
+ days = diff_sec // 86400
73
+ hours = (diff_sec % 86400) // 3600
74
+ mins = (diff_sec % 3600) // 60
75
+
76
+ if days > 0:
77
+ return f'{days}d {hours}h'
78
+ if hours > 0:
79
+ return f'{hours}h {mins}m'
80
+ return f'{mins}m'
81
+
82
+
83
+ def render_output(raw: str) -> None:
84
+ data: dict = {}
85
+ try:
86
+ data = json.loads(raw)
87
+ except Exception:
88
+ pass
89
+
90
+ ctx_window = data.get('context_window') or {}
91
+ ctx_pct = round(ctx_window.get('used_percentage') or 0)
92
+
93
+ parts = [
94
+ DIM + 'context usage:' + RESET + ' ' +
95
+ build_gauge(ctx_pct) + ' ' +
96
+ pct_color(ctx_pct) + str(ctx_pct) + '%' + RESET
97
+ ]
98
+
99
+ rate_limits = data.get('rate_limits')
100
+ if rate_limits:
101
+ five_hour = (
102
+ rate_limits.get('five_hour') or
103
+ rate_limits.get('5h') or
104
+ rate_limits.get('fiveHour')
105
+ )
106
+ if five_hour:
107
+ pct = round(five_hour.get('used_percentage') or 0)
108
+ reset_str = format_reset_time(five_hour.get('resets_at'))
109
+ part = (
110
+ DIM + '5hour limits:' + RESET + ' ' +
111
+ build_gauge(pct) + ' ' +
112
+ pct_color(pct) + str(pct) + '%' + RESET
113
+ )
114
+ if reset_str:
115
+ part += ' ' + DIM + reset_str + RESET
116
+ parts.append(part)
117
+
118
+ seven_day = (
119
+ rate_limits.get('seven_day') or
120
+ rate_limits.get('7d') or
121
+ rate_limits.get('sevenDay')
122
+ )
123
+ if seven_day:
124
+ pct = round(seven_day.get('used_percentage') or 0)
125
+ reset_str = format_reset_time(seven_day.get('resets_at'))
126
+ part = (
127
+ DIM + '7day limits:' + RESET + ' ' +
128
+ build_gauge(pct) + ' ' +
129
+ pct_color(pct) + str(pct) + '%' + RESET
130
+ )
131
+ if reset_str:
132
+ part += ' ' + DIM + reset_str + RESET
133
+ parts.append(part)
134
+
135
+ sys.stdout.write(' '.join(parts) + '\n')
136
+ sys.stdout.flush()
137
+
138
+
139
+ def main() -> None:
140
+ chunks = []
141
+ total_size = 0
142
+ rendered = False
143
+
144
+ def do_render():
145
+ nonlocal rendered
146
+ if rendered:
147
+ return
148
+ rendered = True
149
+ render_output(''.join(chunks))
150
+
151
+ # Timeout fallback: render with whatever we have after 5 seconds
152
+ timer = threading.Timer(5.0, do_render)
153
+ timer.daemon = True
154
+ timer.start()
155
+
156
+ try:
157
+ for line in sys.stdin:
158
+ chunks.append(line)
159
+ total_size += len(line)
160
+ if total_size > MAX_INPUT:
161
+ break
162
+ except Exception:
163
+ pass
164
+ finally:
165
+ timer.cancel()
166
+ do_render()
167
+
168
+
169
+ if __name__ == '__main__':
170
+ main()
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Stop hook: session template creation and pattern trust score management.
4
+ Triggered at the end of each Claude Code session.
5
+ """
6
+
7
+ import json
8
+ import sys
9
+ import os
10
+
11
+ sys.stdout.reconfigure(encoding='utf-8')
12
+ sys.stderr.reconfigure(encoding='utf-8')
13
+ import re
14
+ from datetime import date, datetime, timezone
15
+
16
+ _HOOKS_DIR = os.path.dirname(os.path.abspath(__file__))
17
+ _CLAUDE_DIR = os.path.dirname(_HOOKS_DIR)
18
+ SESSIONS_DIR = os.path.join(_CLAUDE_DIR, 'memory', 'sessions')
19
+ PATTERNS_FILE = os.path.join(_CLAUDE_DIR, 'memory', 'patterns.json')
20
+
21
+ EXPIRY_DAYS = 30
22
+ PROMOTION_THRESHOLD = 0.8
23
+ COOLING_DAYS = 3
24
+ SESSION_JSON_MARKER = 'C3:SESSION:JSON'
25
+
26
+
27
+ def is_worktree(cwd: str) -> bool:
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
+ )
61
+
62
+
63
+ def ensure_session_file(yyyymmdd: str) -> None:
64
+ os.makedirs(SESSIONS_DIR, exist_ok=True)
65
+ path = get_session_path(yyyymmdd)
66
+ # wx フラグ相当: ファイルが存在しない場合のみ作成(TOCTOU安全)
67
+ try:
68
+ with open(path, 'x', encoding='utf-8') as f:
69
+ f.write(create_session_template(yyyymmdd))
70
+ print(f'[Stop] セッションファイルを作成しました: {path}', file=sys.stderr)
71
+ except FileExistsError:
72
+ _update_facts_timestamp(path)
73
+
74
+
75
+ def _update_facts_timestamp(path: str) -> None:
76
+ with open(path, 'r', encoding='utf-8') as f:
77
+ content = f.read()
78
+ now = datetime.now(timezone.utc).astimezone().strftime('%Y-%m-%d %H:%M:%S')
79
+ updated = re.sub(r'(- 記録時刻: ).*', rf'\g<1>{now}', content)
80
+ if updated != content:
81
+ with open(path, 'w', encoding='utf-8') as f:
82
+ f.write(updated)
83
+
84
+
85
+ def extract_session_patterns(yyyymmdd: str) -> list:
86
+ path = get_session_path(yyyymmdd)
87
+ if not os.path.exists(path):
88
+ return []
89
+ with open(path, 'r', encoding='utf-8') as f:
90
+ content = f.read()
91
+ match = re.search(rf'<!-- {SESSION_JSON_MARKER}\s*(.*?)-->', content, re.DOTALL)
92
+ if not match:
93
+ return []
94
+ try:
95
+ data = json.loads(match.group(1).strip())
96
+ return data.get('patterns', [])
97
+ except json.JSONDecodeError:
98
+ return []
99
+
100
+
101
+ def _parse_session_date(yyyymmdd: str):
102
+ try:
103
+ return datetime.strptime(yyyymmdd, '%Y%m%d').date()
104
+ except ValueError:
105
+ return date.min
106
+
107
+
108
+ def count_sessions_since(registered_yyyymmdd: str) -> int:
109
+ if not os.path.isdir(SESSIONS_DIR):
110
+ return 1
111
+ registered = _parse_session_date(registered_yyyymmdd)
112
+ count = sum(
113
+ 1 for fname in os.listdir(SESSIONS_DIR)
114
+ if fname.endswith('.tmp') and _parse_session_date(fname[:-4]) >= registered
115
+ )
116
+ return max(count, 1)
117
+
118
+
119
+ def load_patterns() -> dict:
120
+ if os.path.exists(PATTERNS_FILE):
121
+ with open(PATTERNS_FILE, 'r', encoding='utf-8') as f:
122
+ return json.load(f)
123
+ return {"patterns": []}
124
+
125
+
126
+ def save_patterns(data: dict) -> None:
127
+ os.makedirs(os.path.dirname(PATTERNS_FILE), exist_ok=True)
128
+ with open(PATTERNS_FILE, 'w', encoding='utf-8') as f:
129
+ json.dump(data, f, ensure_ascii=False, indent=2)
130
+
131
+
132
+ def update_patterns(yyyymmdd: str) -> None:
133
+ new_observations = extract_session_patterns(yyyymmdd)
134
+ data = load_patterns()
135
+ today = date.today()
136
+
137
+ for obs in new_observations:
138
+ pid = obs.get('id')
139
+ if not pid:
140
+ continue
141
+ description = obs.get('description', '')
142
+ existing = next((p for p in data['patterns'] if p['id'] == pid), None)
143
+ if existing is None:
144
+ data['patterns'].append({
145
+ "id": pid,
146
+ "description": description,
147
+ "registered_date": yyyymmdd,
148
+ "trust_score": 0.1,
149
+ "promotion_candidate": False,
150
+ "observations": [{"date": yyyymmdd}],
151
+ "last_updated": yyyymmdd,
152
+ })
153
+ else:
154
+ if not any(o['date'] == yyyymmdd for o in existing['observations']):
155
+ existing['observations'].append({"date": yyyymmdd})
156
+ existing['last_updated'] = yyyymmdd
157
+
158
+ active = []
159
+ for pattern in data['patterns']:
160
+ if pattern.get('promoted', False):
161
+ active.append(pattern)
162
+ continue
163
+
164
+ registered = _parse_session_date(pattern['registered_date'])
165
+ days_elapsed = (today - registered).days
166
+
167
+ if days_elapsed >= EXPIRY_DAYS:
168
+ continue
169
+
170
+ sessions_total = count_sessions_since(pattern['registered_date'])
171
+ obs_count = len(pattern['observations'])
172
+ trust = round(min(1.0, max(0.1, obs_count / sessions_total)), 2)
173
+
174
+ pattern['trust_score'] = trust
175
+ pattern['promotion_candidate'] = (
176
+ days_elapsed >= COOLING_DAYS and trust >= PROMOTION_THRESHOLD
177
+ )
178
+ active.append(pattern)
179
+
180
+ data['patterns'] = active
181
+ save_patterns(data)
182
+
183
+ print(f'[Stop] セッション終了処理が完了しました', file=sys.stderr)
184
+
185
+
186
+ def main():
187
+ try:
188
+ json.loads(sys.stdin.read())
189
+ except (json.JSONDecodeError, ValueError):
190
+ pass
191
+
192
+ cwd = os.getcwd()
193
+ if is_worktree(cwd):
194
+ sys.exit(0)
195
+
196
+ today_str = date.today().strftime('%Y%m%d')
197
+ ensure_session_file(today_str)
198
+ update_patterns(today_str)
199
+
200
+
201
+ if __name__ == '__main__':
202
+ main()
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env python3
2
+ """PostToolUse hook: remind to test when skills/ files are modified."""
3
+
4
+ import json
5
+ import sys
6
+ import os
7
+
8
+ sys.stdout.reconfigure(encoding='utf-8')
9
+ sys.stderr.reconfigure(encoding='utf-8')
10
+
11
+
12
+ def main():
13
+ try:
14
+ payload = json.loads(sys.stdin.read())
15
+ except (json.JSONDecodeError, ValueError):
16
+ sys.exit(0)
17
+
18
+ if payload.get('tool_name') not in ('Write', 'Edit'):
19
+ sys.exit(0)
20
+
21
+ file_path = payload.get('tool_input', {}).get('file_path', '')
22
+ normalized = file_path.replace('\\', '/')
23
+
24
+ if '/.claude/skills/' not in normalized:
25
+ sys.exit(0)
26
+
27
+ skill_name = os.path.basename(file_path)
28
+ print(f'[C3] .claude/skills/{skill_name} を変更しました。実際のエージェント動作で確認してください。')
29
+ sys.exit(0)
30
+
31
+
32
+ if __name__ == '__main__':
33
+ main()
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env python3
2
+ """PreToolUse hook: worktree boundary guardrail.
3
+
4
+ PO_WORKTREE_GUARD=1 が設定されている場合のみ動作する。
5
+ parallel-orchestra が read_only:false タスクを起動するときに自動セットする。
6
+ Write / Edit ツールの対象パスが CWD(worktree ルート)外であればブロックする。
7
+ """
8
+
9
+ import json
10
+ import os
11
+ import sys
12
+
13
+ sys.stdout.reconfigure(encoding='utf-8')
14
+ sys.stderr.reconfigure(encoding='utf-8')
15
+
16
+
17
+ def main():
18
+ if os.environ.get('PO_WORKTREE_GUARD') != '1':
19
+ sys.exit(0)
20
+
21
+ try:
22
+ payload = json.loads(sys.stdin.read())
23
+ except (json.JSONDecodeError, ValueError):
24
+ sys.exit(0)
25
+
26
+ tool_name = payload.get('tool_name', '')
27
+ if tool_name not in ('Write', 'Edit'):
28
+ sys.exit(0)
29
+
30
+ file_path = payload.get('tool_input', {}).get('file_path', '')
31
+ if not file_path:
32
+ sys.exit(0)
33
+
34
+ cwd = os.path.realpath(os.getcwd())
35
+ resolved = os.path.realpath(
36
+ file_path if os.path.isabs(file_path) else os.path.join(cwd, file_path)
37
+ )
38
+
39
+ if resolved != cwd and not resolved.startswith(cwd + os.sep):
40
+ print(
41
+ f'[WorktreeGuard BLOCK] worktree 外へのファイル操作をブロックしました。\n'
42
+ f' 対象パス: {file_path}\n'
43
+ f' 解決パス: {resolved}\n'
44
+ f' 許可範囲: {cwd}',
45
+ file=sys.stderr
46
+ )
47
+ sys.exit(2)
48
+
49
+ sys.exit(0)
50
+
51
+
52
+ if __name__ == '__main__':
53
+ main()
File without changes
@@ -0,0 +1,91 @@
1
+ # Code Review Checklist
2
+
3
+ ## 必須チェック項目(全作成物共通)
4
+
5
+ ### コード品質
6
+ - [ ] 関数の責務が単一か(単一責任原則・1関数1役割)
7
+ - [ ] 関数の長さが適切か(目安50行以内。超える場合は分割を検討)
8
+ - [ ] ネストの深さが適切か(目安3段階以内。深い場合は早期リターンや関数分割を検討)
9
+ - [ ] マジックナンバー・マジック文字列が定数化されているか
10
+ - [ ] デッドコード(到達不能コード・未使用変数・未使用関数)が残っていないか
11
+ - [ ] コメントアウトされたコードが残っていないか(削除またはチケット化すること)
12
+ - [ ] 不要なデバッグ出力(console.log / print等)が残っていないか
13
+
14
+ ### 命名規則
15
+ - [ ] 変数・関数名が意図を表しているか(何をするかが名前からわかるか)
16
+ - [ ] ブール値の変数名が is / has / can / should で始まっているか
17
+ - [ ] 略語を使わずに書かれているか(tmp → temporary、btn → button等)
18
+ - [ ] 一貫した命名規則が守られているか(camelCase / snake_case等がコードベース全体で統一されているか)
19
+
20
+ ### エラーハンドリング
21
+ - [ ] 全ての例外・エラーが適切にハンドリングされているか
22
+ - [ ] エラーを握りつぶしていないか(空のcatch / 無言のfailが存在しないか)
23
+ - [ ] エラー発生時にリソース(ファイル・DB接続・ネットワーク)が正しく解放されているか
24
+ - [ ] ユーザーへのエラーメッセージが適切か(内部情報を含まないか・わかりやすいか)
25
+ - [ ] エラーの伝播方法が適切か(再スロー・ラップ・変換が適切か)
26
+
27
+ ### ログ
28
+ - [ ] ログ出力が適切なレベル(DEBUG / INFO / WARN / ERROR)で行われているか
29
+ - [ ] 個人情報・秘密情報がログに出力されていないか
30
+ - [ ] トラブルシューティングに十分な情報がログに含まれているか
31
+ - [ ] ログが過剰でないか(ループ内での大量出力等)
32
+
33
+ ### テスト
34
+ - [ ] 新機能・修正に対してテストが追加・更新されているか
35
+ - [ ] 既存テストが全て通過するか
36
+ - [ ] テスト名から何をテストしているか明確にわかるか
37
+ - [ ] テストが実装の内部ロジックに依存しすぎていないか(振る舞いをテストしているか)
38
+
39
+ ### 型安全性
40
+ - [ ] 型アノテーション・型宣言が適切に付いているか(TypeScript / Python等)
41
+ - [ ] `any` 型や型アサションの多用がないか(あれば理由がコメントで記載されているか)
42
+ - [ ] null / undefined の扱いが安全か(null チェック・Optional チェーニング等)
43
+
44
+ ### 保守性
45
+ - [ ] 重複コードがないか(DRY原則)
46
+ - [ ] 変更に強い設計か(マジックナンバー・ハードコードされた設定がないか)
47
+ - [ ] 複雑なロジックに説明コメントが付いているか
48
+ - [ ] 循環依存が発生していないか
49
+
50
+ ### 並行処理・スレッド安全性
51
+ - [ ] 共有リソースへのアクセスが適切に排他制御されているか(ロック・ミューテックス・セマフォ等)
52
+ - [ ] 競合状態(Race Condition)が発生しないか
53
+ - [ ] デッドロックが発生しないか(ロック取得順序が一定か)
54
+ - [ ] 非同期処理のキャンセル・タイムアウトが適切に実装されているか
55
+
56
+ ## 推奨チェック項目(全作成物共通)
57
+ - [ ] 依存ライブラリの追加が妥当か(既存ライブラリで代替できないか)
58
+ - [ ] ドキュメント(README・API仕様等)が更新されているか
59
+ - [ ] コードの変更がアーキテクチャ方針と一致しているか
60
+
61
+ ---
62
+
63
+ ## 作成物別チェック項目
64
+
65
+ ### Web API / バックエンド向け
66
+ - [ ] N+1クエリが発生していないか(ループ内でのDB呼び出し等)
67
+ - [ ] 不要なデータを取得していないか(SELECT * の多用・不要なJOIN等)
68
+ - [ ] トランザクション境界が適切か(複数テーブル更新時の整合性)
69
+ - [ ] ページネーション・件数制限が実装されているか(大量データの一括取得がないか)
70
+ - [ ] レスポンスに不要なフィールドが含まれていないか
71
+ - [ ] 非同期処理が適切に実装されているか(Promise / async-await の正しい使用)
72
+ - [ ] タイムアウトが設定されているか(外部API呼び出し・DB接続等)
73
+
74
+ ### フロントエンド向け
75
+ - [ ] 不要な再レンダリングが発生していないか(メモ化・依存配列の設定等)
76
+ - [ ] メモリリークが発生していないか(イベントリスナー・タイマーの解放)
77
+ - [ ] 大量データのレンダリングに仮想スクロール等の対策があるか
78
+ - [ ] アクセシビリティ(aria属性・キーボード操作・コントラスト)が考慮されているか
79
+ - [ ] 国際化(i18n)対応が必要な場合に対応されているか
80
+
81
+ ### バッチ処理 / 非同期処理向け
82
+ - [ ] 冪等性が担保されているか(同じ処理を複数回実行しても問題ないか)
83
+ - [ ] 処理の途中失敗時にリカバリできるか(再実行・チェックポイント)
84
+ - [ ] メモリ使用量が適切か(大量データを一括ロードしていないか・ストリーム処理等)
85
+ - [ ] キューの詰まり・バックプレッシャーへの対策があるか
86
+
87
+ ### ライブラリ / SDK向け
88
+ - [ ] 公開APIのインターフェースが安定しているか(破壊的変更がないか)
89
+ - [ ] 破壊的変更がある場合にバージョンが適切に上がっているか(セマンティックバージョニング)
90
+ - [ ] 公開APIにドキュメント(型定義・JSDoc / docstring等)が付いているか
91
+ - [ ] ライブラリ利用者の環境を壊さないか(グローバル変数の汚染・副作用等)
@@ -0,0 +1,5 @@
1
+ # Promoted Rules
2
+ <!-- このファイルは /promote-pattern コマンドが自動で更新する。手動で編集しないこと。 -->
3
+
4
+ <!-- C3:PROMOTED_RULES:BEGIN -->
5
+ <!-- C3:PROMOTED_RULES:END -->
@@ -0,0 +1,84 @@
1
+ # Security Review Checklist
2
+
3
+ ## 必須チェック項目(OWASP Top 10 準拠・全作成物共通)
4
+
5
+ ### インジェクション
6
+ - [ ] SQLインジェクションの対策がされているか(プレースホルダ・パラメータバインド使用等)
7
+ - [ ] OSコマンド・インジェクションの対策がされているか(シェル呼び出し禁止・引数エスケープ)
8
+ - [ ] XSS(クロスサイトスクリプティング)の対策がされているか(出力エスケープ・CSP)
9
+ - [ ] LDAPインジェクションの対策がされているか(LDAP使用時)
10
+ - [ ] XMLインジェクション・XXEの対策がされているか(XML処理時)
11
+
12
+ ### 認証・認可
13
+ - [ ] 認証が適切に実装されているか
14
+ - [ ] 認可チェックが全エンドポイントに実装されているか
15
+ - [ ] セッションID固定化攻撃の対策がされているか(ログイン後にセッションIDを再生成しているか)
16
+ - [ ] セッション管理が適切か(タイムアウト・Secure属性・HttpOnly属性)
17
+ - [ ] セッションハイジャック対策がされているか(Secure/HttpOnly Cookie・トークン検証)
18
+ - [ ] ブルートフォース・辞書・パスワードリスト攻撃の対策がされているか(アカウントロックアウト・レートリミット・CAPTCHA)
19
+ - [ ] リプレイ攻撃の対策がされているか(ノンス・タイムスタンプ・ワンタイムトークン検証)
20
+ - [ ] パスワードが適切にハッシュ化されているか(bcrypt・Argon2等)
21
+
22
+ ### JWT・トークン管理
23
+ - [ ] JWT の署名アルゴリズムが適切か(`alg: none` を許可していないか・HS256/RS256等を明示指定しているか)
24
+ - [ ] JWT の有効期限(exp)が設定されているか
25
+ - [ ] JWT の秘密鍵・公開鍵が適切に管理されているか(ハードコードされていないか)
26
+ - [ ] リフレッシュトークンの無効化(ローテーション・ブラックリスト)が実装されているか
27
+ - [ ] OAuth2 / OIDC を使用する場合、state パラメータで CSRF 対策がされているか
28
+
29
+ ### 秘密情報
30
+ - [ ] APIキー・パスワード・トークンがハードコードされていないか
31
+ - [ ] 秘密情報が .gitignore 対象ファイルで管理されているか
32
+ - [ ] ログに秘密情報が出力されていないか
33
+
34
+ ### 入力値バリデーション
35
+ - [ ] ユーザー入力が全てバリデーションされているか
36
+ - [ ] ディレクトリトラバーサルの対策がされているか(パスのサニタイズ・正規化・絶対パス検証)
37
+ - [ ] ファイルアップロードの検証が実装されているか(拡張子・MIMEタイプ・サイズ)
38
+ - [ ] リダイレクト先URLの検証がされているか(オープンリダイレクト対策)
39
+
40
+ ### SSRF(Server-Side Request Forgery)
41
+ - [ ] サーバーから外部URLへリクエストを行う箇所で、送信先URLがユーザー入力から生成されていないか
42
+ - [ ] 内部ネットワーク・クラウドメタデータエンドポイント(169.254.169.254等)へのアクセスをブロックしているか
43
+ - [ ] URLのホスト名をホワイトリストで制限しているか(または DNS リバインディング対策をしているか)
44
+ - [ ] HTTPリダイレクトのフォローを無効化または制限しているか
45
+
46
+ ### メモリ安全性
47
+ - [ ] バッファオーバフロー攻撃の対策がされているか(境界チェック・安全なAPI使用・メモリ安全な言語の選択)
48
+
49
+ ### 通信の安全性
50
+ - [ ] HTTPS/TLSが強制されているか(スニファ・電波傍受対策)
51
+ - [ ] HSTSが設定されているか
52
+ - [ ] 証明書の検証が正しく実装されているか(自己署名証明書の無効化等)
53
+
54
+ ### 依存関係
55
+ - [ ] 依存ライブラリに既知の脆弱性がないか(npm audit / pip audit 等)
56
+ - [ ] 不要な権限・スコープを持つライブラリがないか
57
+
58
+ ## 推奨チェック項目(全作成物共通)
59
+ - [ ] エラーメッセージに内部情報(スタックトレース・DB構造等)が含まれていないか
60
+ - [ ] セキュリティヘッダーが適切に設定されているか(X-Frame-Options・X-Content-Type-Options等)
61
+ - [ ] CORS の設定が適切か(`Access-Control-Allow-Origin: *` の多用・不必要なオリジン許可がないか)
62
+ - [ ] セキュリティイベント(認証失敗・認可エラー・不正入力等)が適切にログ記録されているか(OWASP A09 対策)
63
+
64
+ ---
65
+
66
+ ## 作成物別チェック項目
67
+
68
+ ### Webアプリケーション向け
69
+ - [ ] CSRF(クロスサイトリクエストフォージェリ)の対策がされているか(CSRFトークン・SameSite Cookie)
70
+ - [ ] フォームジャッキング攻撃の対策がされているか(CSP・SRI によるスクリプト改ざん検知)
71
+ - [ ] MITB(Man-in-the-Browser)攻撃の対策がされているか(CSP・サブリソース完全性 SRI の設定)
72
+
73
+ ### AI機能を持つシステム向け
74
+ - [ ] プロンプトインジェクション攻撃の対策がされているか(入力サニタイズ・出力検証・システムプロンプトの分離)
75
+
76
+ ### メール送信機能向け
77
+ - [ ] スパムメール対策がされているか(送信レートリミット・CAPTCHA・SPF/DKIM/DMARC設定)
78
+ - [ ] メールヘッダーインジェクションの対策がされているか
79
+
80
+ ### 公開Webサービス向け
81
+ - [ ] DDoS・F5アタック(大量リクエスト)への対策がされているか(アプリケーション層のレートリミット・リクエスト数制限)
82
+
83
+ ### 外部APIやDNSに依存するシステム向け
84
+ - [ ] DNSキャッシュポイズニングの影響を軽減できているか(HTTPS証明書検証により名前解決が汚染されても通信先を検証できるか)