build_project_ids ($self) {
    my $want = $self->project;
    return [
        map      { $_->id }
            grep { $want? ( $_->name =~ /\Q$want/i ) : 1 }
            $self->_pt_api->projects->@*
    ];
}

sub _find_project ($self) {
    my $want     = $self->project;
    my @projects = grep { $want? ( $_->name =~ /\Q$want/i ) : 1 }
        $self->_pt_api->projects->@*;

    return $projects[0] if @projects == 1;

    my %project_by_name = map { $_->name => $_ } @projects;
    my $name            = $self->_choose( [ sort keys %project_by_name ] );
    return $project_by_name{$name};
}

sub _find_requester ( $self, $project ) {
    my $want        = $self->requester;
    my @memberships = grep { $want? ( $_->person->name =~ /\Q$want/i ) : 1 }
        $project->memberships->@*;

    return $memberships[0]->person if @memberships == 1;

    my %membership_by_person_name
        = map { $_->person->name => $_ } @memberships;
    my $name = $self->_choose( [ sort keys %membership_by_person_name ] );
    return $membership_by_person_name{$name}->person;
}

before print_usage_text => sub {
    say <<'EOF';
Please see POD in App::GHPT for installation and troubleshooting directions.
EOF
};

sub run ($self) {
    unless ( -s dir( File::HomeDir->my_home )->file( '.config', 'hub' ) ) {
        die
            "hub does not appear to be set up. Please run 'hub browse' to set it up.\n";
    }

    my ( $requester, $chosen_story ) = $self->_choose_pt_story;
    unless ($requester) {
        die "No requester found!\n";
    }
    unless ($chosen_story) {
        die "No started stories found!\n";
    }

    my $pull_request_url = $self->_create_pull_request(
        $self->_append_question_answers(
            $self->_confirm_story(
                $self->_text_for_story( $chosen_story, $requester ),
            ),
        ),
    );
    $self->_update_pt_story( $chosen_story, $pull_request_url );
    say $chosen_story->url;
    say $pull_request_url;

    return 0;
}

sub _append_question_answers ( $self, $text ) {
    my $qa_markdown = App::GHPT::WorkSubmitter::AskPullRequestQuestions->new(
        merge_to_branch_name => 'origin/'. $self->base,
        question_namespaces  => $self->_question_namespaces,
    )->ask_questions;
    return $text unless defined $qa_markdown and length $qa_markdown;
    return join "\n\n",
        $text,
        '----',
        $qa_markdown,
        ;
}

## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _pt_token ($self) {
    my $env_key = 'PIVOTALTRACKER_TOKEN';
    my $key     ='submit-work.pivotaltracker.token';
    return $ENV{$env_key} // $self->_config_val($key)
        // $self->_require_git_config($key);
}
## use critic

sub _choose {
    my $self = shift;
    return choose(@_)
        || exit 1;    # user hit q or ctrl-d to quit
}

sub _choose_pt_story ($self) {
    if ( $self->create_story ) {
        my $project   = $self->_find_project;
        my $requester = $self->_find_requester($project);
        my $name      = $self->_get_story_name;

        if ( $self->dry_run ) {
            say "Would create story $name in "
               . $project->name
               .'with requester '
               . $requester->name
               .'but this is a dry-run.';
            exit;
        }

        return (
            $requester->name,
            $self->_pt_api->create_story(
                current_state =>'started',

                # This is primarily intended for small changes/stories, so 0 points.
                estimate        => 0,
                name            => $name,
                owner_ids       => [ $self->_pt_api->me->id ],
                project_id      => $project->id,
                requested_by_id => $requester->id,
            )
        );
    }

    my $stories = [
        map {
            $self->_pt_api->project_stories_where(
                project_id => $_,
                filter     => sprintf(
                    '(owner:%s AND (state:started OR state:finished))',
                    $self->_username
                ),
                )->@*
        } $self->_project_ids->@*
    ];

    $stories = $self->_filter_chores_and_maybe_warn_user($stories);

    return undef unless $stories->@*;

    my %stories_lookup = map { $_->name => $_ } $stories->@*;
    my $chosen_story   = $self->_choose( [ sort keys %stories_lookup ] );

    return (
        $stories_lookup{$chosen_story}->requested_by->name,
        $stories_lookup{$chosen_story}
    );
}

sub _get_story_name ($self) {
    my $story_name = $self->story_name;
    if (!$story_name ) {
        say q{Please enter the new story's name:};
        $story_name = $self->_read_line;
    }
    return $story_name;
}

sub _read_line ($) {
    while (1) {
        my $l = readline( \*STDIN );
        $l =~ s/^\s+|\s+$//g;
        return $l if $l;
    }
}

sub _filter_chores_and_maybe_warn_user ( $self, $stories ) {
    my ( $chore_stories, $non_chore_stories )
        = part { $_->story_type eq 'chore'? 0 : 1 } $stories->@*;

    say 'Note: '
       . ( scalar $chore_stories->@* )
       . PL('chore', scalar $chore_stories->@* )
       . PL_V('is', scalar $chore_stories->@* )
       .'not shown here (chores by definition do not require review).'
        if $chore_stories;

    return $non_chore_stories // [];
}

sub _confirm_story ( $self, $text ) {
    my $result = $self->_choose(
        [ 'Accept', 'Edit' ],
        { prompt => $text, clear_screen => $ENV{'SUBMIT_WORK_CLEAR'} // 0 }
    );
    return $text if $result eq 'Accept';
    my $fh = solicit($text);
    return do { local $/ = undef; <$fh> };
}

sub _text_for_story ( $self, $story, $reviewer ) {
    join "\n\n",
        $story->name,
        $story->url,
        ( $story->description? $story->description : () ),
        (
        $self->_include_requester_name_in_pr
       ? 'Reviewer: '. $reviewer
        : ()
        ),
        ;
}

sub _create_pull_request ( $self, $text ) {
    if ( $self->dry_run ) {
        print $text;
        exit;
    }

    run3(
        [ qw(hub pull-request -F - -b), $self->base ],
        \$text,
        \my $hub_output,
        \my $err,
        {
            binmode_stdin