diff --git a/plugins/flow/bin/lib/router.js b/plugins/flow/bin/lib/router.js index ee385ba..cae970a 100644 --- a/plugins/flow/bin/lib/router.js +++ b/plugins/flow/bin/lib/router.js @@ -338,16 +338,27 @@ function passCommand(root) { // prNumber(root, branch) -> "41" | null. gh is optional: no gh, no network, // or any failure all mean "no PR", which routes to 11 (open one) not 12. +// Stealth pushes HEAD:, often from a fork, so `pr view ` +// misses; fall back to a head-name lookup under the branch's upstream name. function prNumber(root, branch) { if (!branch || branch === 'HEAD') return null; - const r = spawnSync('gh', ['pr', 'view', branch, '--json', 'number', '-q', '.number'], { - cwd: root, - encoding: 'utf8', - timeout: 15000, - }); - if (r.status !== 0) return null; - const n = (r.stdout || '').trim(); - return /^\d+$/.test(n) ? n : null; + const ghNumber = (args) => { + const r = spawnSync('gh', args, { cwd: root, encoding: 'utf8', timeout: 15000 }); + if (r.status !== 0) return null; + const n = (r.stdout || '').trim(); + return /^\d+$/.test(n) ? n : null; + }; + const direct = ghNumber(['pr', 'view', branch, '--json', 'number', '-q', '.number']); + if (direct) return direct; + const merge = git(root, ['config', `branch.${branch}.merge`]).out; + const name = merge.replace(/^refs\/heads\//, ''); + if (!name || name === merge || name === branch) return null; + const remote = git(root, ['config', `branch.${branch}.remote`]).out; + const url = remote ? git(root, ['config', `remote.${remote}.url`]).out : ''; + const m = url.match(/^(?:[\w.-]+@[^:/]+:|[a-z+]+:\/\/(?:[^@/]+@)?[^/]+\/)([A-Za-z0-9-]+)\/[^/]+?(?:\.git)?\/?$/i); + if (!m) return null; + return ghNumber(['pr', 'list', '--head', name, '--state', 'all', '--json', 'number,headRepositoryOwner', + '-q', `[.[] | select(.headRepositoryOwner.login == "${m[1]}")][0].number`]); } // ───────────────────────────────────────────────────────────────────────────── diff --git a/plugins/flow/scripts/tests/test_next.sh b/plugins/flow/scripts/tests/test_next.sh index 1603faa..59d1aa5 100644 --- a/plugins/flow/scripts/tests/test_next.sh +++ b/plugins/flow/scripts/tests/test_next.sh @@ -584,6 +584,40 @@ Verified: 2026-09-08 by user" \ rm -rf "$home" "$proj" } +t_next_row12_pr_found_by_upstream_name() { + # stealth pushes HEAD:, so `gh pr view ` finds + # nothing; the PR is only findable by the upstream branch name + local home proj base sha + home=$(tmp_dir) + proj=$(tmp_repo) + base=$(git -C "$proj" rev-parse --short HEAD) + nx_feature "$proj" 001-x + sha=$(nx_commit "$proj" a.py) + nx_tasks "$proj/.specs/001-x/TASKS.md" "$base" "Approved: 2026-09-08 by user +Verified: 2026-09-08 by user" \ + "- [x] T001 a — files: a.py — verify: \`true\` — done: $sha" \ + '' '## Gates' "- [x] G001 clean — verify: \`true\` — done: $sha" + printf 'gates green\n' >"$proj/.specs/001-x/PASS-$sha.md" + git -C "$proj" checkout -qb flow/x + printf '#!/bin/sh\ncase "$*" in\n"pr list --head feat/x "*someone*) echo 77 ;;\n*) exit 1 ;;\nesac\n' >"$home/gh" + chmod +x "$home/gh" + run_cmd bash -c 'cd "$1" || exit 1; export HOME="$2"; export PATH="$2:$PATH"; shift 2; exec "$@"' \ + _ "$proj" "$home" node "$NX_CLI_PATH" next --json + assert_contains "$OUT" '"state": "shippable"' "no upstream configured: the old no-PR answer holds" + git -C "$proj" config branch.flow/x.remote fork + git -C "$proj" config branch.flow/x.merge refs/heads/feat/x + git -C "$proj" config remote.fork.url /somewhere/local/path + run_cmd bash -c 'cd "$1" || exit 1; export HOME="$2"; export PATH="$2:$PATH"; shift 2; exec "$@"' \ + _ "$proj" "$home" node "$NX_CLI_PATH" next --json + assert_contains "$OUT" '"state": "shippable"' "a remote url with no parseable owner is not guessed at" + git -C "$proj" config remote.fork.url git@github.com:someone/repo.git + run_cmd bash -c 'cd "$1" || exit 1; export HOME="$2"; export PATH="$2:$PATH"; shift 2; exec "$@"' \ + _ "$proj" "$home" node "$NX_CLI_PATH" next --json + assert_contains "$OUT" '"state": "shipped"' "a PR under the upstream branch name is found" + assert_contains "$OUT" 'PR #77' "and its number is reported" + rm -rf "$home" "$proj" +} + t_next_row13_idle() { local home proj home=$(tmp_dir)