3838// === Verbose Logging ===
3939$ verbose = 0 ;
4040$ running = true ;
41+ $ logFile = null ;
4142$ optind = 0 ;
42- $ args = getopt ('vhwb ' , ['verbose ' , 'help ' , 'wait-if-empty ' , 'review-bots ' ], $ optind );
43+ $ args = getopt ('vhwb ' , ['verbose ' , 'help ' , 'wait-if-empty ' , 'review-bots ' , ' log:: ' ], $ optind );
4344if (isset ($ args ['h ' ]) || isset ($ args ['help ' ])) {
44- fwrite (STDOUT , "Usage: php scripts/github-code-review.php [-v|--verbose]... [-w|--wait-if-empty] [-b|--review-bots] \n" );
45- fwrite (STDOUT , " -v, --verbose Increase verbosity (can stack: -vv, -vvv) \n" );
45+ fwrite (STDOUT , "Usage: php scripts/github-code-review.php [-v|--verbose]... [-w|--wait-if-empty] [-b|--review-bots] [--log=FILE] \n" );
46+ fwrite (STDOUT , " -v, --verbose Increase verbosity (can stack: -vv, -vvv, -vvvv) \n" );
47+ fwrite (STDOUT , " -v [INFO] Essential progress (job start/complete/errors) \n" );
48+ fwrite (STDOUT , " -vv [DEBUG] Detailed progress (checkpoints, function entry) \n" );
49+ fwrite (STDOUT , " -vvv [TRACE] Full trace (loop iterations, gh commands) \n" );
50+ fwrite (STDOUT , " -vvvv [RAW] Everything: raw gh output, API responses, full command output \n" );
4651 fwrite (STDOUT , " -w, --wait-if-empty Wait for jobs when queue is empty (default: exit) \n" );
4752 fwrite (STDOUT , " -b, --review-bots Also review PRs from bots like dependabot (default: skip) \n" );
53+ fwrite (STDOUT , " --log=FILE Append all log output to FILE (in addition to stderr) \n" );
4854 fwrite (STDOUT , " -h, --help Show this help message \n" );
4955 fwrite (STDOUT , "\n Press Ctrl-C to exit gracefully at any time. \n" );
5056 exit (0 );
5157}
5258if (isset ($ args ['v ' ])) {
53- $ verbose = min (3 , $ verbose + count ((array )$ args ['v ' ]));
59+ $ verbose = min (4 , $ verbose + count ((array )$ args ['v ' ]));
5460}
5561if (isset ($ args ['verbose ' ])) {
56- $ verbose = min (3 , $ verbose + count ((array )$ args ['verbose ' ]));
62+ $ verbose = min (4 , $ verbose + count ((array )$ args ['verbose ' ]));
5763}
5864$ waitIfEmpty = isset ($ args ['w ' ]) || isset ($ args ['wait-if-empty ' ]);
5965$ reviewBots = isset ($ args ['b ' ]) || isset ($ args ['review-bots ' ]);
66+ if (isset ($ args ['log ' ])) {
67+ $ logFile = $ args ['log ' ] !== false ? $ args ['log ' ] : 'github-code-review.log ' ;
68+ }
6069
6170/**
6271 * Emit verbose log message at the specified verbosity level.
6372 *
6473 * @param string $message Log message
65- * @param int $level Verbosity level (1=INFO, 2=DEBUG, 3=TRACE)
74+ * @param int $level Verbosity level (1=INFO, 2=DEBUG, 3=TRACE, 4=RAW )
6675 */
6776function verbose_log (string $ message , int $ level = 1 ): void
6877{
69- global $ verbose ;
78+ global $ verbose, $ logFile ;
7079 if ($ verbose >= $ level ) {
7180 $ prefix = match ($ level ) {
7281 1 => '[INFO] ' ,
7382 2 => '[DEBUG] ' ,
7483 3 => '[TRACE] ' ,
84+ 4 => '[RAW] ' ,
7585 default => '[VERBOSE] '
7686 };
77- error_log ("github-code-review: {$ prefix } {$ message }" );
87+ $ line = "github-code-review: {$ prefix } {$ message }" ;
88+ error_log ($ line );
89+ if ($ logFile !== null ) {
90+ $ timestamp = date ('Y-m-d H:i:s. ' ) . sprintf ('%06d ' , (microtime (true ) - floor (microtime (true ))) * 1000000 );
91+ file_put_contents ($ logFile , "[ {$ timestamp }] {$ line }\n" , FILE_APPEND );
92+ }
93+ }
94+ }
95+
96+ /**
97+ * Log raw command output (only at -vvvv level)
98+ */
99+ function verbose_raw (string $ label , string $ output ): void
100+ {
101+ global $ verbose , $ logFile ;
102+ if ($ verbose >= 4 ) {
103+ $ prefix = '[RAW] ' ;
104+ $ line = "github-code-review: {$ prefix } {$ label }" ;
105+ error_log ($ line );
106+ error_log ("--- BEGIN {$ label } --- " );
107+ error_log ($ output );
108+ error_log ("--- END {$ label } --- " );
109+ if ($ logFile !== null ) {
110+ $ timestamp = date ('Y-m-d H:i:s. ' ) . sprintf ('%06d ' , (microtime (true ) - floor (microtime (true ))) * 1000000 );
111+ file_put_contents ($ logFile , "[ {$ timestamp }] {$ line }\n[ {$ timestamp }] --- BEGIN {$ label } --- \n{$ output }\n[ {$ timestamp }] --- END {$ label } --- \n" , FILE_APPEND );
112+ }
78113 }
79114}
80115
@@ -103,6 +138,10 @@ function getPRRef(string $repo, int $prNumber, string $refType): ?string
103138 );
104139 exec ($ cmd , $ output , $ ret );
105140
141+ // At level 4, log the raw gh api output
142+ $ rawOutput = implode ("\n" , $ output );
143+ verbose_raw ("gh_api_ {$ refType }" , "cmd: {$ cmd }\noutput: {$ rawOutput }\nret: {$ ret }" );
144+
106145 if ($ ret === 0 && !empty ($ output )) {
107146 $ ref = trim (implode ("\n" , $ output ));
108147 if ($ ref !== '' ) {
@@ -992,6 +1031,9 @@ function checkoutBranch(string $repo, string $branch, string $checkoutPath): str
9921031
9931032 exec ($ cloneCmd , $ cloneOutput , $ cloneRet );
9941033
1034+ // At level 4, log the raw clone output
1035+ verbose_raw ("gh_clone_attempt1 " , "cmd: {$ cloneCmd }\noutput: " . implode ("\n" , $ cloneOutput ) . "\nret: {$ cloneRet }" );
1036+
9951037 // After exec returns, check if signal arrived during exec
9961038 // @phpstan-ignore-next-line
9971039 if (!$ running ) {
@@ -1023,6 +1065,9 @@ function checkoutBranch(string $repo, string $branch, string $checkoutPath): str
10231065 );
10241066 exec ($ cloneCmd , $ cloneOutput , $ cloneRet );
10251067
1068+ // At level 4, log the raw clone output
1069+ verbose_raw ("gh_clone_attempt2 " , "cmd: {$ cloneCmd }\noutput: " . implode ("\n" , $ cloneOutput ) . "\nret: {$ cloneRet }" );
1070+
10261071 // After exec returns, check if signal arrived during exec
10271072 // @phpstan-ignore-next-line
10281073 if (!$ running ) {
@@ -1085,10 +1130,16 @@ function runOpencodeAnalysis(string $dir, string $jobId, string $cmdTemplate): s
10851130 }
10861131
10871132 // Build command: opencode analyzes the working directory's changes
1133+ // Write prompt to temp file to avoid shell quoting issues with special chars
1134+ // (parentheses, quotes, $, `, etc. in the prompt break sh -c quoting)
1135+ $ promptFile = '/tmp/opencode-prompt- ' . $ jobId . '.txt ' ;
1136+ file_put_contents ($ promptFile , $ reviewPrompt );
1137+
1138+ // Use bash -c with $(cat ...) to read prompt from file - avoids all escaping issues
10881139 $ opencodeCmd = sprintf (
1089- 'sh -c " cd %s && git diff --name-only && git diff && opencode run %s --format json" 2>&1 ' ,
1140+ 'bash -c \' cd %s && git diff --name-only && git diff && opencode run "$(cat %s)" --format json \' 2>&1 ' ,
10901141 escapeshellarg ($ dir ),
1091- escapeshellarg ($ reviewPrompt )
1142+ escapeshellarg ($ promptFile )
10921143 );
10931144
10941145 verbose_log ("runOpencodeAnalysis: executing analysis in {$ dir }" , 3 );
@@ -1100,9 +1151,9 @@ function runOpencodeAnalysis(string $dir, string $jobId, string $cmdTemplate): s
11001151 $ result = implode ("\n" , $ output );
11011152 verbose_log ("runOpencodeAnalysis: completed, output length= " . strlen ($ result ), 3 );
11021153
1103- // Debug: log full raw output when parsing will likely fail
1154+ // At level 4 (RAW), log the full opencode output for debugging
11041155 if (strlen ($ result ) > 0 ) {
1105- verbose_log ( " RAW_OUTPUT_START \n" . substr ( $ result, 0 , 8000 ) . "\n RAW_OUTPUT_END " , 2 );
1156+ verbose_raw ( " opencode_output " , $ result );
11061157 }
11071158
11081159 return $ result ;
0 commit comments