Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions plugins/flow/bin/lib/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -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:<plain-name>, often from a fork, so `pr view <local>`
// 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`]);
}

// ─────────────────────────────────────────────────────────────────────────────
Expand Down
34 changes: 34 additions & 0 deletions plugins/flow/scripts/tests/test_next.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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:<plain-name>, so `gh pr view <local-branch>` 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)
Expand Down