ccsetup 1.1.1 → 1.2.1

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 (86) hide show
  1. package/README.md +144 -342
  2. package/bin/create-project.js +1246 -90
  3. package/bin/lib/claudeInterface.js +209 -0
  4. package/lib/aiAgentSelector.js +155 -0
  5. package/lib/templates/README.md +176 -0
  6. package/lib/templates/catalog.js +230 -0
  7. package/lib/templates/filter.js +257 -0
  8. package/lib/templates/index.js +45 -0
  9. package/lib/templates/metadata/agents.json +413 -0
  10. package/lib/templates/metadata-extractor.js +329 -0
  11. package/lib/templates/search.js +356 -0
  12. package/package.json +13 -5
  13. package/template/{agents → .claude/agents}/checker.md +29 -0
  14. package/template/.claude/settings.json +32 -0
  15. package/template/.claude/skills/codex-review/SKILL.md +139 -0
  16. package/template/.claude/skills/prd/SKILL.md +343 -0
  17. package/template/.claude/skills/ralph/SKILL.md +339 -0
  18. package/template/.claude/skills/secops/SKILL.md +259 -0
  19. package/template/.codex/skills/codex-review/SKILL.md +139 -0
  20. package/template/.codex/skills/prd/SKILL.md +343 -0
  21. package/template/.codex/skills/ralph/SKILL.md +339 -0
  22. package/template/AGENTS.md +43 -0
  23. package/template/CLAUDE.md +141 -21
  24. package/template/CONTRIBUTING.md +37 -0
  25. package/template/agents/README.md +15 -171
  26. package/template/docs/ROADMAP.md +0 -36
  27. package/template/docs/agent-orchestration.md +24 -141
  28. package/template/docs/codex-setup.md +32 -0
  29. package/template/hooks/codex-review/index.js +105 -0
  30. package/template/hooks/workflow-selector/index.js +398 -0
  31. package/template/scripts/codex-review/codex-review.sh +266 -0
  32. package/template/scripts/ralph/CLAUDE.md +174 -0
  33. package/template/scripts/ralph/CODEX.md +76 -0
  34. package/template/scripts/ralph/ralph.sh +150 -0
  35. package/template/tickets/ticket-list.md +17 -68
  36. package/template/agents/ai-engineer.md +0 -31
  37. package/template/agents/api-documenter.md +0 -31
  38. package/template/agents/architect-review.md +0 -42
  39. package/template/agents/backend-architect.md +0 -29
  40. package/template/agents/business-analyst.md +0 -34
  41. package/template/agents/c-pro.md +0 -34
  42. package/template/agents/cloud-architect.md +0 -31
  43. package/template/agents/code-reviewer.md +0 -28
  44. package/template/agents/content-marketer.md +0 -34
  45. package/template/agents/context-manager.md +0 -63
  46. package/template/agents/cpp-pro.md +0 -37
  47. package/template/agents/customer-support.md +0 -34
  48. package/template/agents/data-engineer.md +0 -31
  49. package/template/agents/data-scientist.md +0 -28
  50. package/template/agents/database-admin.md +0 -31
  51. package/template/agents/database-optimizer.md +0 -31
  52. package/template/agents/debugger.md +0 -29
  53. package/template/agents/deployment-engineer.md +0 -31
  54. package/template/agents/devops-troubleshooter.md +0 -31
  55. package/template/agents/dx-optimizer.md +0 -62
  56. package/template/agents/error-detective.md +0 -31
  57. package/template/agents/frontend-developer.md +0 -30
  58. package/template/agents/golang-pro.md +0 -31
  59. package/template/agents/graphql-architect.md +0 -31
  60. package/template/agents/incident-responder.md +0 -73
  61. package/template/agents/javascript-pro.md +0 -34
  62. package/template/agents/legacy-modernizer.md +0 -31
  63. package/template/agents/ml-engineer.md +0 -31
  64. package/template/agents/mlops-engineer.md +0 -56
  65. package/template/agents/mobile-developer.md +0 -31
  66. package/template/agents/network-engineer.md +0 -31
  67. package/template/agents/payment-integration.md +0 -31
  68. package/template/agents/performance-engineer.md +0 -31
  69. package/template/agents/prompt-engineer.md +0 -58
  70. package/template/agents/python-pro.md +0 -31
  71. package/template/agents/quant-analyst.md +0 -31
  72. package/template/agents/risk-manager.md +0 -40
  73. package/template/agents/rust-pro.md +0 -34
  74. package/template/agents/sales-automator.md +0 -34
  75. package/template/agents/search-specialist.md +0 -58
  76. package/template/agents/security-auditor.md +0 -31
  77. package/template/agents/sql-pro.md +0 -34
  78. package/template/agents/terraform-specialist.md +0 -34
  79. package/template/agents/test-automator.md +0 -31
  80. /package/template/{agents → .claude/agents}/backend.md +0 -0
  81. /package/template/{agents → .claude/agents}/blockchain.md +0 -0
  82. /package/template/{agents → .claude/agents}/coder.md +0 -0
  83. /package/template/{agents → .claude/agents}/frontend.md +0 -0
  84. /package/template/{agents → .claude/agents}/planner.md +0 -0
  85. /package/template/{agents → .claude/agents}/researcher.md +0 -0
  86. /package/template/{agents → .claude/agents}/shadcn.md +0 -0
@@ -0,0 +1,398 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ // Environment variable toggle — exit early if not enabled
7
+ // Enable with: export CCSETUP_WORKFLOW=1
8
+ const enabled = process.env.CCSETUP_WORKFLOW;
9
+ if (!enabled || (enabled !== '1' && enabled.toLowerCase() !== 'true')) {
10
+ console.log('{}');
11
+ process.exit(0);
12
+ }
13
+
14
+ // Simple workflow selector that reads from agent-orchestration.md
15
+ class WorkflowSelector {
16
+ constructor() {
17
+ this.workflows = this.loadWorkflows();
18
+ this.availableAgents = this.getAvailableAgents();
19
+ }
20
+
21
+ loadWorkflows() {
22
+ const orchestrationPath = path.join(process.cwd(), 'docs', 'agent-orchestration.md');
23
+
24
+ if (!fs.existsSync(orchestrationPath)) {
25
+ return this.getDefaultWorkflows();
26
+ }
27
+
28
+ try {
29
+ // First try to use Claude to extract workflows intelligently
30
+ const workflows = this.loadWorkflowsWithClaude(orchestrationPath);
31
+ if (workflows && Object.keys(workflows).length > 0) {
32
+ return workflows;
33
+ }
34
+ } catch (error) {
35
+ // Claude extraction failed, continue with regex
36
+ }
37
+
38
+ // Fallback to regex-based extraction
39
+ return this.loadWorkflowsWithRegex(orchestrationPath);
40
+ }
41
+
42
+ loadWorkflowsWithClaude(orchestrationPath) {
43
+ const { execSync } = require('child_process');
44
+
45
+ try {
46
+ const content = fs.readFileSync(orchestrationPath, 'utf8');
47
+
48
+ const extractPrompt = `
49
+ Extract all workflows from this agent orchestration document.
50
+
51
+ For each workflow, identify:
52
+ 1. The workflow name
53
+ 2. The sequence of agents used
54
+ 3. The purpose/description
55
+
56
+ Return ONLY a JSON object with this structure:
57
+ {
58
+ "workflow_key": {
59
+ "name": "Workflow Name",
60
+ "agents": ["agent1", "agent2", "agent3"],
61
+ "purpose": "Brief description"
62
+ }
63
+ }
64
+
65
+ Document content:
66
+ ${content}
67
+
68
+ Extract all workflows and return valid JSON only.
69
+ `;
70
+
71
+ const claudeResult = execSync(
72
+ `claude --print "${extractPrompt.replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`,
73
+ { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'], maxBuffer: 1024 * 1024 * 10 }
74
+ );
75
+
76
+ // Parse the JSON result
77
+ const parsed = JSON.parse(claudeResult);
78
+
79
+ // Validate and normalize the workflows
80
+ const workflows = {};
81
+ for (const [key, workflow] of Object.entries(parsed)) {
82
+ if (workflow.name && Array.isArray(workflow.agents) && workflow.agents.length > 0) {
83
+ // Normalize agent names (remove "agent" suffix, lowercase)
84
+ const normalizedAgents = workflow.agents.map(agent =>
85
+ agent.toLowerCase().replace(/\s*agent\s*$/, '').trim()
86
+ );
87
+
88
+ workflows[key] = {
89
+ name: workflow.name,
90
+ agents: normalizedAgents,
91
+ purpose: workflow.purpose || ''
92
+ };
93
+ }
94
+ }
95
+
96
+ return workflows;
97
+ } catch (error) {
98
+ // Claude extraction failed
99
+ return null;
100
+ }
101
+ }
102
+
103
+ loadWorkflowsWithRegex(orchestrationPath) {
104
+ try {
105
+ const content = fs.readFileSync(orchestrationPath, 'utf8');
106
+ const workflows = {};
107
+
108
+ // Enhanced regex patterns for better extraction
109
+ const workflowSections = this.extractWorkflowSections(content);
110
+
111
+ for (const section of workflowSections) {
112
+ const workflow = this.parseWorkflowSection(section);
113
+ if (workflow) {
114
+ const key = workflow.name.toLowerCase().replace(/\s+/g, '_');
115
+ workflows[key] = workflow;
116
+ }
117
+ }
118
+
119
+ // If no workflows found, return defaults
120
+ return Object.keys(workflows).length > 0 ? workflows : this.getDefaultWorkflows();
121
+ } catch (error) {
122
+ return this.getDefaultWorkflows();
123
+ }
124
+ }
125
+
126
+ extractWorkflowSections(content) {
127
+ const sections = [];
128
+
129
+ // Split by workflow headers
130
+ const workflowRegex = /### \d+\.\s+(.+?)\s+Workflow([\s\S]*?)(?=### \d+\.|## |$)/g;
131
+ let match;
132
+
133
+ while ((match = workflowRegex.exec(content)) !== null) {
134
+ sections.push({
135
+ name: match[1].trim(),
136
+ content: match[2].trim()
137
+ });
138
+ }
139
+
140
+ return sections;
141
+ }
142
+
143
+ parseWorkflowSection(section) {
144
+ const workflow = {
145
+ name: section.name,
146
+ agents: []
147
+ };
148
+
149
+ // Look for the Flow section
150
+ const flowMatch = section.content.match(/\*\*Flow\*\*:?\s*([\s\S]*?)(?=\*\*|###|$)/);
151
+ if (!flowMatch) return null;
152
+
153
+ const flowContent = flowMatch[1];
154
+
155
+ // Extract agents from various formats
156
+ const agents = this.extractAgentsFromFlow(flowContent);
157
+
158
+ if (agents.length > 0) {
159
+ workflow.agents = agents;
160
+ return workflow;
161
+ }
162
+
163
+ return null;
164
+ }
165
+
166
+ extractAgentsFromFlow(flowContent) {
167
+ const agents = [];
168
+ const lines = flowContent.split('\n');
169
+
170
+ for (const line of lines) {
171
+ // Pattern 1: "1. **Agent Name** → Description"
172
+ let match = line.match(/^\d+\.\s*\*\*([^*→]+?)(?:\s+Agent)?\*\*/);
173
+
174
+ // Pattern 2: "- Agent Name: Description"
175
+ if (!match) {
176
+ match = line.match(/^[-•]\s*\*\*([^*:]+?)(?:\s+Agent)?\*\*/);
177
+ }
178
+
179
+ // Pattern 3: "Agent Name →"
180
+ if (!match) {
181
+ match = line.match(/^\s*([A-Za-z\s]+?)\s*(?:Agent\s*)?→/);
182
+ }
183
+
184
+ if (match) {
185
+ const agent = match[1].toLowerCase()
186
+ .replace(/\s*agent\s*$/, '')
187
+ .trim();
188
+
189
+ if (agent && !agents.includes(agent) && agent.length > 0) {
190
+ agents.push(agent);
191
+ }
192
+ }
193
+ }
194
+
195
+ return agents;
196
+ }
197
+
198
+ getDefaultWorkflows() {
199
+ return {
200
+ feature_development: {
201
+ name: 'Feature Development',
202
+ agents: ['researcher', 'planner', 'coder', 'checker']
203
+ },
204
+ bug_fix: {
205
+ name: 'Bug Fix',
206
+ agents: ['researcher', 'coder', 'checker']
207
+ },
208
+ refactoring: {
209
+ name: 'Refactoring',
210
+ agents: ['researcher', 'planner', 'coder', 'checker']
211
+ },
212
+ api_development: {
213
+ name: 'API Development',
214
+ agents: ['planner', 'backend', 'frontend', 'checker']
215
+ },
216
+ ui_component: {
217
+ name: 'UI Component',
218
+ agents: ['frontend', 'shadcn', 'checker']
219
+ },
220
+ blockchain: {
221
+ name: 'Blockchain Development',
222
+ agents: ['planner', 'blockchain', 'checker']
223
+ },
224
+ qa: {
225
+ name: 'QA',
226
+ agents: ['researcher', 'checker', 'coder', 'checker']
227
+ }
228
+ };
229
+ }
230
+
231
+ getAvailableAgents() {
232
+ const agentsDir = path.join(process.cwd(), '.claude', 'agents');
233
+
234
+ if (!fs.existsSync(agentsDir)) {
235
+ return [];
236
+ }
237
+
238
+ try {
239
+ return fs.readdirSync(agentsDir)
240
+ .filter(file => file.endsWith('.md'))
241
+ .map(file => file.replace('.md', '').toLowerCase());
242
+ } catch (error) {
243
+ return [];
244
+ }
245
+ }
246
+
247
+ async selectWorkflow(prompt) {
248
+ const { execSync } = require('child_process');
249
+
250
+ try {
251
+ // First, let's use Claude to analyze the prompt and understand the task type
252
+ const analyzePrompt = `
253
+ Analyze this user prompt and determine the most appropriate workflow type.
254
+ User prompt: "${prompt}"
255
+
256
+ Available workflows:
257
+ ${Object.entries(this.workflows).map(([key, workflow]) =>
258
+ `- ${workflow.name}: uses agents [${workflow.agents.join(', ')}]`
259
+ ).join('\n')}
260
+
261
+ Consider:
262
+ 1. What type of task is this? (feature, bug fix, refactoring, API work, UI work, testing, etc.)
263
+ 2. What agents would be most helpful?
264
+ 3. Match to the most appropriate workflow
265
+
266
+ Respond with ONLY the workflow key (e.g., 'feature_development', 'bug_fix', etc.)
267
+ `;
268
+
269
+ // Use Claude to analyze the prompt
270
+ const claudeAnalysis = execSync(
271
+ `claude --print "${analyzePrompt.replace(/"/g, '\\"')}"`,
272
+ { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
273
+ ).trim().toLowerCase().replace(/['"]/g, '');
274
+
275
+ // Validate the response is a valid workflow key
276
+ if (this.workflows[claudeAnalysis]) {
277
+ return this.workflows[claudeAnalysis];
278
+ }
279
+
280
+ // If Claude's response isn't valid, fall back to enhanced keyword matching
281
+ return this.enhancedKeywordMatching(prompt);
282
+
283
+ } catch (error) {
284
+ // If Claude command fails, fall back to enhanced keyword matching
285
+ console.error('Claude analysis failed, using keyword matching:', error.message);
286
+ return this.enhancedKeywordMatching(prompt);
287
+ }
288
+ }
289
+
290
+ enhancedKeywordMatching(prompt) {
291
+ const promptLower = prompt.toLowerCase();
292
+
293
+ // Score each workflow based on keyword matches
294
+ const scores = {};
295
+
296
+ // Define keyword weights for each workflow
297
+ const workflowKeywords = {
298
+ bug_fix: {
299
+ keywords: ['fix', 'bug', 'error', 'issue', 'broken', 'crash', 'fail', 'debug', 'problem', 'wrong'],
300
+ weight: 2
301
+ },
302
+ refactoring: {
303
+ keywords: ['refactor', 'improve', 'optimize', 'clean', 'restructure', 'reorganize', 'simplify', 'enhance'],
304
+ weight: 2
305
+ },
306
+ api_development: {
307
+ keywords: ['api', 'endpoint', 'rest', 'graphql', 'backend', 'server', 'route', 'request', 'response'],
308
+ weight: 2
309
+ },
310
+ ui_component: {
311
+ keywords: ['ui', 'component', 'frontend', 'react', 'vue', 'interface', 'button', 'form', 'page', 'view'],
312
+ weight: 2
313
+ },
314
+ qa: {
315
+ keywords: ['test', 'qa', 'quality', 'testing', 'unit', 'integration', 'e2e', 'coverage', 'assert'],
316
+ weight: 2
317
+ },
318
+ feature_development: {
319
+ keywords: ['add', 'create', 'implement', 'build', 'develop', 'feature', 'new', 'functionality'],
320
+ weight: 1
321
+ },
322
+ blockchain: {
323
+ keywords: ['blockchain', 'smart contract', 'web3', 'ethereum', 'solidity', 'defi', 'crypto', 'wallet'],
324
+ weight: 3
325
+ }
326
+ };
327
+
328
+ // Calculate scores for each workflow
329
+ Object.entries(workflowKeywords).forEach(([workflow, config]) => {
330
+ scores[workflow] = 0;
331
+ config.keywords.forEach(keyword => {
332
+ if (promptLower.includes(keyword)) {
333
+ scores[workflow] += config.weight;
334
+ }
335
+ });
336
+ });
337
+
338
+ // Find the workflow with the highest score
339
+ let bestWorkflow = 'feature_development';
340
+ let highestScore = 0;
341
+
342
+ Object.entries(scores).forEach(([workflow, score]) => {
343
+ if (score > highestScore && this.workflows[workflow]) {
344
+ highestScore = score;
345
+ bestWorkflow = workflow;
346
+ }
347
+ });
348
+
349
+ return this.workflows[bestWorkflow] || this.workflows.feature_development || Object.values(this.workflows)[0];
350
+ }
351
+
352
+ filterAgentsByAvailability(agents) {
353
+ if (this.availableAgents.length === 0) {
354
+ return agents; // Return all if we can't check
355
+ }
356
+
357
+ return agents.filter(agent => this.availableAgents.includes(agent));
358
+ }
359
+ }
360
+
361
+ // Main execution - reads from stdin as Claude Code provides
362
+ if (require.main === module) {
363
+ let inputData = '';
364
+
365
+ process.stdin.on('data', (chunk) => {
366
+ inputData += chunk;
367
+ });
368
+
369
+ process.stdin.on('end', async () => {
370
+ try {
371
+ const input = JSON.parse(inputData);
372
+ const prompt = input.prompt || '';
373
+
374
+ if (!prompt) {
375
+ console.log('{}');
376
+ return;
377
+ }
378
+
379
+ const selector = new WorkflowSelector();
380
+ const workflow = await selector.selectWorkflow(prompt);
381
+ const agents = selector.filterAgentsByAvailability(workflow.agents);
382
+
383
+ // Output suggestion — Claude should ask the user before applying
384
+ const output = {
385
+ workflow: workflow.name,
386
+ agents: agents,
387
+ message: `[Workflow Suggestion] Based on this prompt, the "${workflow.name}" workflow may be a good fit: ${agents.join(' → ')}. Ask the user if they'd like to follow this workflow or just proceed normally with Claude Code's default behavior.`
388
+ };
389
+
390
+ console.log(JSON.stringify(output));
391
+ } catch (error) {
392
+ // Silent fail - just return empty
393
+ console.log('{}');
394
+ }
395
+ });
396
+ }
397
+
398
+ module.exports = WorkflowSelector;
@@ -0,0 +1,266 @@
1
+ #!/bin/bash
2
+ # codex-review.sh — Review plans, implementations, or code changes via Codex CLI
3
+ # Usage: codex-review.sh [plan-file-or--] [--model <model>]
4
+ # No arguments: reviews git changes (code review)
5
+ # With plan file: reviews plan, or plan+implementation if git changes exist
6
+ #
7
+ # Exit codes:
8
+ # 0 = success
9
+ # 1 = codex CLI not installed / nothing to review
10
+ # 2 = auth/API error
11
+ # 3 = timeout
12
+
13
+ set -euo pipefail
14
+
15
+ PLAN_FILE=""
16
+ PLAN_CONTENT=""
17
+ DIFF_CONTENT=""
18
+ MODEL="${CODEX_REVIEW_MODEL:-}"
19
+ TIMEOUT=120
20
+ MAX_DIFF_CHARS=50000
21
+
22
+ if command -v timeout &>/dev/null; then
23
+ TIMEOUT_CMD="timeout"
24
+ elif command -v gtimeout &>/dev/null; then
25
+ TIMEOUT_CMD="gtimeout"
26
+ else
27
+ TIMEOUT_CMD=""
28
+ fi
29
+
30
+ while [[ $# -gt 0 ]]; do
31
+ case $1 in
32
+ --model)
33
+ MODEL="$2"
34
+ shift 2
35
+ ;;
36
+ --model=*)
37
+ MODEL="${1#*=}"
38
+ shift
39
+ ;;
40
+ -)
41
+ PLAN_FILE="-"
42
+ shift
43
+ ;;
44
+ *)
45
+ if [[ -z "$PLAN_FILE" ]]; then
46
+ PLAN_FILE="$1"
47
+ fi
48
+ shift
49
+ ;;
50
+ esac
51
+ done
52
+
53
+ # Check prerequisites
54
+ if ! command -v codex &>/dev/null; then
55
+ echo "Error: codex CLI is not installed. Install it with: npm install -g @openai/codex" >&2
56
+ exit 1
57
+ fi
58
+
59
+ # Read plan content (optional — only when a plan file is provided)
60
+ if [[ "$PLAN_FILE" == "-" ]]; then
61
+ PLAN_CONTENT=$(cat)
62
+ elif [[ -n "$PLAN_FILE" && -f "$PLAN_FILE" ]]; then
63
+ PLAN_CONTENT=$(cat "$PLAN_FILE")
64
+ elif [[ -n "$PLAN_FILE" ]]; then
65
+ echo "Error: Plan file not found: $PLAN_FILE" >&2
66
+ exit 1
67
+ fi
68
+
69
+ # Gather git diff (silently skips if git unavailable or not in a repo)
70
+ gather_git_diff() {
71
+ if ! command -v git &>/dev/null; then return; fi
72
+ if ! git rev-parse --is-inside-work-tree &>/dev/null 2>&1; then return; fi
73
+
74
+ DIFF_CONTENT=$(git diff HEAD 2>/dev/null || true)
75
+
76
+ if [[ -z "$DIFF_CONTENT" ]]; then
77
+ DIFF_CONTENT=$(git diff HEAD~1..HEAD 2>/dev/null || true)
78
+ fi
79
+
80
+ # Fallback for initial commit (no HEAD yet) or staged-only changes
81
+ if [[ -z "$DIFF_CONTENT" ]]; then
82
+ DIFF_CONTENT=$(git diff --cached 2>/dev/null || true)
83
+ fi
84
+
85
+ if [[ -n "$DIFF_CONTENT" && ${#DIFF_CONTENT} -gt $MAX_DIFF_CHARS ]]; then
86
+ DIFF_CONTENT="${DIFF_CONTENT:0:$MAX_DIFF_CHARS}
87
+
88
+ [... diff truncated at ${MAX_DIFF_CHARS} characters ...]"
89
+ fi
90
+ }
91
+
92
+ gather_git_diff
93
+
94
+ # Must have at least a plan or git changes to review
95
+ if [[ -z "$PLAN_CONTENT" && -z "$DIFF_CONTENT" ]]; then
96
+ echo "Error: No plan file or git changes found. Nothing to review." >&2
97
+ echo "Usage: codex-review.sh [plan-file-or--] [--model <model>]" >&2
98
+ exit 1
99
+ fi
100
+
101
+ CMD_ARGS=()
102
+ if [[ -n "$MODEL" ]]; then
103
+ CMD_ARGS+=(--model "$MODEL")
104
+ fi
105
+
106
+ # Build prompt based on available inputs
107
+ if [[ -n "$PLAN_CONTENT" && -n "$DIFF_CONTENT" ]]; then
108
+ # Implementation review: validate code changes against the plan
109
+ REVIEW_PROMPT="You are a senior architect reviewing an implementation against its plan. Validate that the code changes correctly fulfill the plan requirements.
110
+
111
+ ## Plan
112
+
113
+ $PLAN_CONTENT
114
+
115
+ ---
116
+
117
+ ## Implementation (git diff)
118
+
119
+ $DIFF_CONTENT
120
+
121
+ ---
122
+
123
+ Provide a structured review covering:
124
+
125
+ ## Plan Compliance
126
+ - Which plan requirements are correctly implemented?
127
+ - Which plan requirements are missing or incomplete?
128
+ - Any divergence from the planned approach?
129
+
130
+ ## Acceptance Criteria
131
+ - For each acceptance criterion in the plan, is it met by the implementation?
132
+ - List any unmet criteria explicitly
133
+
134
+ ## Code Quality
135
+ - Are there bugs or logic errors in the implementation?
136
+ - Security concerns in the changed code?
137
+ - Performance issues?
138
+
139
+ ## Suggestions
140
+ - Specific issues to fix before merging
141
+ - Missing tests or validation
142
+ - Improvements to better match the plan
143
+
144
+ Be direct and specific. Reference exact file paths and line ranges from the diff."
145
+
146
+ elif [[ -n "$PLAN_CONTENT" ]]; then
147
+ # Plan review: architectural review of the plan itself
148
+ REVIEW_PROMPT="You are a senior architect reviewing this plan. Provide a structured review covering:
149
+
150
+ ## Architecture Review
151
+ - Are the technical choices sound?
152
+ - Are there simpler alternatives?
153
+ - Any missing dependencies or integration concerns?
154
+
155
+ ## Risk Assessment
156
+ - What could go wrong?
157
+ - What edge cases are unhandled?
158
+ - Any security or performance concerns?
159
+
160
+ ## Suggestions
161
+ - Specific improvements with rationale
162
+ - Missing acceptance criteria
163
+ - Implementation order concerns
164
+
165
+ Be direct and specific. Reference exact sections of the plan.
166
+
167
+ ---
168
+
169
+ Plan to review:
170
+
171
+ $PLAN_CONTENT"
172
+
173
+ else
174
+ # Code review: standalone review of git changes
175
+ REVIEW_PROMPT="You are a senior engineer performing a code review. Review the following code changes for quality, correctness, and best practices.
176
+
177
+ ## Code Changes (git diff)
178
+
179
+ $DIFF_CONTENT
180
+
181
+ ---
182
+
183
+ Provide a structured review covering:
184
+
185
+ ## Bugs and Correctness
186
+ - Logic errors or incorrect behavior
187
+ - Missing null/error handling
188
+ - Off-by-one errors or boundary conditions
189
+
190
+ ## Security
191
+ - Injection vulnerabilities
192
+ - Exposed secrets or credentials
193
+ - Missing input validation
194
+
195
+ ## Performance
196
+ - Unnecessary computations or allocations
197
+ - Inefficient patterns
198
+ - Missing caching opportunities
199
+
200
+ ## Code Quality
201
+ - Naming and readability
202
+ - Adherence to existing code conventions
203
+ - Dead code or unnecessary complexity
204
+
205
+ ## Suggestions
206
+ - Specific improvements with rationale
207
+ - Missing tests
208
+ - Documentation gaps
209
+
210
+ Be direct and specific. Reference exact file paths and line ranges from the diff."
211
+ fi
212
+
213
+ # Run codex exec with timeout (if available)
214
+ # Temporarily disable exit-on-error to capture the actual exit code before
215
+ # checking it. Using "if ! OUTPUT=$(cmd)" sets $? to 0 inside the then-block
216
+ # (the negated result), making timeout detection (exit 124) impossible.
217
+ set +e
218
+ if [[ -n "$TIMEOUT_CMD" ]]; then
219
+ OUTPUT=$($TIMEOUT_CMD "${TIMEOUT}s" codex exec ${CMD_ARGS[@]+"${CMD_ARGS[@]}"} "$REVIEW_PROMPT" 2>&1)
220
+ else
221
+ OUTPUT=$(codex exec ${CMD_ARGS[@]+"${CMD_ARGS[@]}"} "$REVIEW_PROMPT" 2>&1)
222
+ fi
223
+ EXIT_CODE=$?
224
+ set -e
225
+
226
+ if [[ $EXIT_CODE -ne 0 ]]; then
227
+ if [[ $EXIT_CODE -eq 124 ]]; then
228
+ echo "Error: Codex review timed out after ${TIMEOUT}s. Try a shorter plan or increase TIMEOUT." >&2
229
+ exit 3
230
+ fi
231
+
232
+ if echo "$OUTPUT" | grep -qi "login\|log in\|sign in\|authenticate first"; then
233
+ echo "Error: Codex CLI requires login. Run 'codex login' first." >&2
234
+ exit 2
235
+ fi
236
+
237
+ if echo "$OUTPUT" | grep -qi "auth\|unauthorized\|api.key\|invalid.*key\|forbidden\|permission denied"; then
238
+ echo "Error: Codex authentication failed. Check your OpenAI API key." >&2
239
+ echo "$OUTPUT" >&2
240
+ exit 2
241
+ fi
242
+
243
+ if echo "$OUTPUT" | grep -qi "rate.limit\|too many requests\|429\|quota\|exceeded.*limit"; then
244
+ echo "Error: Rate limited by OpenAI API. Wait a moment and try again." >&2
245
+ echo "$OUTPUT" >&2
246
+ exit 2
247
+ fi
248
+
249
+ if echo "$OUTPUT" | grep -qi "network\|connect\|ECONNREFUSED\|ENOTFOUND\|DNS\|resolve\|unreachable\|timed out"; then
250
+ echo "Error: Network error. Check your internet connection." >&2
251
+ echo "$OUTPUT" >&2
252
+ exit 2
253
+ fi
254
+
255
+ if echo "$OUTPUT" | grep -qi "model.*not found\|does not exist\|invalid.*model\|unknown model"; then
256
+ echo "Error: Invalid model '${MODEL:-default}'. Check available models with 'codex --help'." >&2
257
+ echo "$OUTPUT" >&2
258
+ exit 2
259
+ fi
260
+
261
+ echo "Error: Codex review failed (exit code $EXIT_CODE)" >&2
262
+ echo "$OUTPUT" >&2
263
+ exit 2
264
+ fi
265
+
266
+ echo "$OUTPUT"