@@ -234,6 +234,216 @@ describe('core flow smoke tests (bash scripts)', () => {
234234 ) . toBe ( true ) ;
235235 } ) ;
236236
237+ it ( 'executor should bypass blocking pre-push hooks when NW_GIT_PUSH_NO_VERIFY=1' , ( ) => {
238+ const projectDir = mkTempDir ( 'nw-smoke-executor-no-verify-' ) ;
239+ initGitRepo ( projectDir ) ;
240+ createPrd ( projectDir , '01-smoke-no-verify' ) ;
241+ commitAll ( projectDir , 'add PRD' ) ;
242+
243+ const remoteDir = mkTempDir ( 'nw-smoke-executor-no-verify-origin-' ) ;
244+ execSync ( 'git init --bare' , { cwd : remoteDir , stdio : 'ignore' } ) ;
245+ execSync ( `git remote add origin "${ remoteDir } "` , { cwd : projectDir , stdio : 'ignore' } ) ;
246+
247+ const hookLog = path . join ( projectDir , '.smoke-pre-push-hook.log' ) ;
248+ const createdFlag = path . join ( projectDir , '.smoke-pr-created' ) ;
249+ const branchName = 'night-watch/01-smoke-no-verify' ;
250+ fs . writeFileSync (
251+ path . join ( projectDir , '.git' , 'hooks' , 'pre-push' ) ,
252+ '#!/usr/bin/env bash\n' +
253+ 'printf \'hook-ran\\n\' >> "$NW_SMOKE_PRE_PUSH_HOOK_LOG"\n' +
254+ 'exit 1\n' ,
255+ { encoding : 'utf-8' , mode : 0o755 } ,
256+ ) ;
257+
258+ const fakeBin = mkTempDir ( 'nw-smoke-bin-no-verify-' ) ;
259+ fs . writeFileSync (
260+ path . join ( fakeBin , 'claude' ) ,
261+ '#!/usr/bin/env bash\n' +
262+ "printf 'provider change\\n' > smoke-provider-change.txt\n" +
263+ 'git add smoke-provider-change.txt\n' +
264+ 'git commit -m "feat: smoke provider progress" >/dev/null 2>&1\n' +
265+ 'exit 0\n' ,
266+ {
267+ encoding : 'utf-8' ,
268+ mode : 0o755 ,
269+ } ,
270+ ) ;
271+ fs . writeFileSync (
272+ path . join ( fakeBin , 'gh' ) ,
273+ '#!/usr/bin/env bash\n' +
274+ 'if [[ "$1" == "pr" && "$2" == "list" ]]; then\n' +
275+ ' if [[ -f "$NW_SMOKE_CREATED_FLAG" ]]; then\n' +
276+ ' printf \'[{"number":123,"headRefName":"%s","url":"https://example.test/pull/123","title":"Smoke","isDraft":true,"labels":[],"createdAt":"2026-01-01T00:00:00Z"}]\\n\' "$NW_SMOKE_BRANCH"\n' +
277+ ' else\n' +
278+ " echo '[]'\n" +
279+ ' fi\n' +
280+ ' exit 0\n' +
281+ 'fi\n' +
282+ 'if [[ "$1" == "pr" && "$2" == "create" ]]; then\n' +
283+ ' touch "$NW_SMOKE_CREATED_FLAG"\n' +
284+ " echo 'https://example.test/pull/123'\n" +
285+ ' exit 0\n' +
286+ 'fi\n' +
287+ 'exit 0\n' ,
288+ { encoding : 'utf-8' , mode : 0o755 } ,
289+ ) ;
290+
291+ const result = runScript ( executorScript , projectDir , {
292+ PATH : `${ fakeBin } :${ process . env . PATH } ` ,
293+ NW_PROVIDER_CMD : 'claude' ,
294+ NW_PRD_DIR : 'docs/PRDs/night-watch' ,
295+ NW_DEFAULT_BRANCH : 'main' ,
296+ NW_GIT_PUSH_NO_VERIFY : '1' ,
297+ NW_SMOKE_PRE_PUSH_HOOK_LOG : hookLog ,
298+ NW_SMOKE_CREATED_FLAG : createdFlag ,
299+ NW_SMOKE_BRANCH : branchName ,
300+ } ) ;
301+
302+ expect ( result . status ) . toBe ( 1 ) ;
303+ expect ( result . stdout ) . toContain ( 'NIGHT_WATCH_RESULT:' ) ;
304+ expect ( fs . existsSync ( hookLog ) ) . toBe ( false ) ;
305+
306+ const remoteBranches = execSync ( 'git for-each-ref --format="%(refname:short)" refs/heads' , {
307+ cwd : remoteDir ,
308+ encoding : 'utf-8' ,
309+ } ) ;
310+ expect ( remoteBranches ) . toContain ( 'night-watch/01-smoke-no-verify' ) ;
311+ } ) ;
312+
313+ it ( 'executor should not push or create a PR before the branch is ahead of its resolved base' , ( ) => {
314+ const projectDir = mkTempDir ( 'nw-smoke-executor-pr-before-commit-' ) ;
315+ initGitRepo ( projectDir ) ;
316+ createPrd ( projectDir , '01-smoke-pr-before-commit' ) ;
317+ commitAll ( projectDir , 'add PRD' ) ;
318+
319+ const remoteDir = mkTempDir ( 'nw-smoke-executor-pr-before-commit-origin-' ) ;
320+ execSync ( 'git init --bare' , { cwd : remoteDir , stdio : 'ignore' } ) ;
321+ execSync ( `git remote add origin "${ remoteDir } "` , { cwd : projectDir , stdio : 'ignore' } ) ;
322+
323+ const hookLog = path . join ( projectDir , '.smoke-pre-push-hook.log' ) ;
324+ const prCreateLog = path . join ( projectDir , '.smoke-pr-create.log' ) ;
325+ fs . writeFileSync (
326+ path . join ( projectDir , '.git' , 'hooks' , 'pre-push' ) ,
327+ '#!/usr/bin/env bash\n' +
328+ 'printf \'hook-ran\\n\' >> "$NW_SMOKE_PRE_PUSH_HOOK_LOG"\n' +
329+ 'exit 1\n' ,
330+ { encoding : 'utf-8' , mode : 0o755 } ,
331+ ) ;
332+
333+ const fakeBin = mkTempDir ( 'nw-smoke-bin-pr-before-commit-' ) ;
334+ fs . writeFileSync ( path . join ( fakeBin , 'claude' ) , '#!/usr/bin/env bash\nexit 1\n' , {
335+ encoding : 'utf-8' ,
336+ mode : 0o755 ,
337+ } ) ;
338+ fs . writeFileSync (
339+ path . join ( fakeBin , 'gh' ) ,
340+ '#!/usr/bin/env bash\n' +
341+ 'if [[ "$1" == "pr" && "$2" == "list" ]]; then\n' +
342+ " echo '[]'\n" +
343+ ' exit 0\n' +
344+ 'fi\n' +
345+ 'if [[ "$1" == "pr" && "$2" == "create" ]]; then\n' +
346+ ' printf \'pr-create\\n\' >> "$NW_SMOKE_PR_CREATE_LOG"\n' +
347+ ' exit 1\n' +
348+ 'fi\n' +
349+ 'exit 0\n' ,
350+ { encoding : 'utf-8' , mode : 0o755 } ,
351+ ) ;
352+
353+ const result = runScript ( executorScript , projectDir , {
354+ PATH : `${ fakeBin } :${ process . env . PATH } ` ,
355+ NW_PROVIDER_CMD : 'claude' ,
356+ NW_PRD_DIR : 'docs/PRDs/night-watch' ,
357+ NW_DEFAULT_BRANCH : 'trunk' ,
358+ NW_SMOKE_PRE_PUSH_HOOK_LOG : hookLog ,
359+ NW_SMOKE_PR_CREATE_LOG : prCreateLog ,
360+ } ) ;
361+
362+ expect ( result . status ) . toBe ( 1 ) ;
363+ expect ( result . stdout ) . toContain ( 'reason=provider_exit' ) ;
364+ expect ( result . stdout ) . not . toContain ( 'reason=pr_setup_failed' ) ;
365+ expect ( fs . existsSync ( hookLog ) ) . toBe ( false ) ;
366+ expect ( fs . existsSync ( prCreateLog ) ) . toBe ( false ) ;
367+ } ) ;
368+
369+ it ( 'executor should defer draft PR creation until the branch has a real commit' , ( ) => {
370+ const projectDir = mkTempDir ( 'nw-smoke-executor-pr-deferred-' ) ;
371+ initGitRepo ( projectDir ) ;
372+ createPrd ( projectDir , '01-smoke-pr-deferred' ) ;
373+ commitAll ( projectDir , 'add PRD' ) ;
374+
375+ const fakeBin = mkTempDir ( 'nw-smoke-bin-pr-deferred-' ) ;
376+ const providerDoneFlag = path . join ( projectDir , '.smoke-provider-done' ) ;
377+ const createdFlag = path . join ( projectDir , '.smoke-pr-created' ) ;
378+ const branchName = 'night-watch/01-smoke-pr-deferred' ;
379+
380+ fs . writeFileSync (
381+ path . join ( fakeBin , 'claude' ) ,
382+ '#!/usr/bin/env bash\n' +
383+ "printf 'provider change\\n' > smoke-provider-change.txt\n" +
384+ 'git add smoke-provider-change.txt\n' +
385+ 'git commit -m "feat: smoke provider progress" >/dev/null 2>&1\n' +
386+ 'touch "$NW_SMOKE_PROVIDER_DONE_FLAG"\n' +
387+ 'exit 0\n' ,
388+ { encoding : 'utf-8' , mode : 0o755 } ,
389+ ) ;
390+
391+ fs . writeFileSync (
392+ path . join ( fakeBin , 'gh' ) ,
393+ '#!/usr/bin/env bash\n' +
394+ 'if [[ "$1" == "pr" && "$2" == "list" ]]; then\n' +
395+ ' jq_query=""\n' +
396+ ' for ((i=1; i<=$#; i++)); do\n' +
397+ ' if [[ "${!i}" == "--jq" ]]; then\n' +
398+ ' j=$((i+1))\n' +
399+ ' jq_query="${!j}"\n' +
400+ ' fi\n' +
401+ ' done\n' +
402+ ' if [[ -f "$NW_SMOKE_CREATED_FLAG" ]]; then\n' +
403+ ' if [[ "$jq_query" == *".url"* ]]; then\n' +
404+ " echo 'https://example.test/pull/123'\n" +
405+ ' elif [[ -n "$jq_query" ]]; then\n' +
406+ ' echo "$NW_SMOKE_BRANCH"\n' +
407+ ' else\n' +
408+ ' printf \'[{"number":123,"headRefName":"%s","url":"https://example.test/pull/123","title":"Smoke","isDraft":true,"labels":[],"createdAt":"2026-01-01T00:00:00Z"}]\\n\' "$NW_SMOKE_BRANCH"\n' +
409+ ' fi\n' +
410+ ' else\n' +
411+ ' if [[ -n "$jq_query" ]]; then\n' +
412+ ' exit 0\n' +
413+ ' fi\n' +
414+ " echo '[]'\n" +
415+ ' fi\n' +
416+ ' exit 0\n' +
417+ 'fi\n' +
418+ 'if [[ "$1" == "pr" && "$2" == "create" ]]; then\n' +
419+ ' if [[ ! -f "$NW_SMOKE_PROVIDER_DONE_FLAG" ]]; then\n' +
420+ " echo 'create called before provider completed' >&2\n" +
421+ ' exit 1\n' +
422+ ' fi\n' +
423+ ' touch "$NW_SMOKE_CREATED_FLAG"\n' +
424+ " echo 'https://example.test/pull/123'\n" +
425+ ' exit 0\n' +
426+ 'fi\n' +
427+ 'exit 0\n' ,
428+ { encoding : 'utf-8' , mode : 0o755 } ,
429+ ) ;
430+
431+ const result = runScript ( executorScript , projectDir , {
432+ PATH : `${ fakeBin } :${ process . env . PATH } ` ,
433+ NW_PROVIDER_CMD : 'claude' ,
434+ NW_PRD_DIR : 'docs/PRDs/night-watch' ,
435+ NW_DEFAULT_BRANCH : 'main' ,
436+ NW_SMOKE_PROVIDER_DONE_FLAG : providerDoneFlag ,
437+ NW_SMOKE_CREATED_FLAG : createdFlag ,
438+ NW_SMOKE_BRANCH : branchName ,
439+ } ) ;
440+
441+ expect ( result . status ) . toBe ( 0 ) ;
442+ expect ( result . stdout ) . toContain ( 'NIGHT_WATCH_RESULT:success_open_pr' ) ;
443+ expect ( result . stdout ) . toContain ( 'pr_url=https://example.test/pull/123' ) ;
444+ expect ( fs . existsSync ( createdFlag ) ) . toBe ( true ) ;
445+ } ) ;
446+
237447 it ( 'executor filesystem mode should preserve queue cleanup after claiming a PRD' , ( ) => {
238448 const projectDir = mkTempDir ( 'nw-smoke-executor-queue-filesystem-' ) ;
239449 initGitRepo ( projectDir ) ;
0 commit comments