App-karr

 view release on metacpan or  search on metacpan

lib/App/karr/Cmd/SetRefs.pm  view on Meta::CPAN

option dir => (
  is        => 'ro',
  format    => 's',
  doc       => 'Path used as the starting point for Git repository discovery',
  predicate => 1,
);


sub execute {
  my ($self, $args_ref, $chain_ref) = @_;
  my ($ref_input, @content_parts) = $self->positional_args($args_ref);
  die "Usage: karr set-refs REF CONTENT...\n" unless defined $ref_input;

  my $repo_dir = '.';
  if ($self->has_dir) {
    $repo_dir = $self->dir;
  }
  elsif ($chain_ref && @$chain_ref) {
    my $root = $chain_ref->[0];
    if ($root && $root->can('has_dir') && $root->has_dir) {
      $repo_dir = $root->dir;
    }
  }

  my $git = App::karr::Git->new(dir => $repo_dir);
  die "Not a git repository.\n" unless $git->is_repo;

  # for_write: a helper ref may be read from every namespace it may not be
  # written to. refs/karr-foundation/chain/ and .../log/ are karr-foundation's
  # own structured state, and this command writes last-writer-wins with its
  # arguments joined by a space, which is not how a chain step is updated.
  my $ref = $git->validate_helper_ref( $ref_input, for_write => 1 );

  # Arguments join with a space -- one line, which is what a hint or a status
  # word is. A document is not that shape, and handing one over the obvious way
  # (a heredoc, an unquoted paste) used to collapse every newline into a space
  # and store the result without a word (#195). Reading stdin when there is
  # nothing to join is the addition that costs no existing caller anything:
  # that argv shape was a usage error before, so no invocation that worked
  # changes meaning -- whereas joining with newlines instead would silently
  # rewrite the payload of every multi-word call, including the one this
  # command's own SYNOPSIS, the README and the packaged skill all teach.
  my $content = @content_parts
    ? join( ' ', @content_parts )
    : $self->_payload_from_stdin;

  $git->write_ref($ref, $content) or die "Failed to write $ref\n";
  $git->push_ref($ref) or die "Failed to push $ref\n";

  print STDERR "Stored $ref\n";
}

# The payload as it arrives on stdin, as characters.
#
# STDIN is the one input edge App::karr::Encoding leaves without a PerlIO layer
# (App::karr::Cmd::Restore reads it the same way), so the decode is explicit and
# happens exactly once.
sub _payload_from_stdin {
  my ($self) = @_;

  # A terminal has nothing queued and would just sit there with no prompt, so
  # the shape that was a usage error before stays one instead of becoming a
  # hang.
  die "Usage: karr set-refs REF CONTENT...\n" if -t STDIN;

  binmode STDIN, ':raw';
  my $content = do { local $/; <STDIN> };

  # An empty stdin is not an empty payload: `karr set-refs REF < /dev/null`, or
  # a generator upstream that produced nothing, is a mistake, and storing '' for
  # it would report success. The deliberate way to store an empty payload is
  # still `karr set-refs REF ""`. A runtime failure (exit 1) rather than a usage
  # error, on the same reading as App::karr::Cmd::Restore's empty stdin: the
  # invocation was right, what arrived on the pipe was not.
  die "No payload on stdin. Pass CONTENT as arguments or pipe a payload in.\n"
    unless defined $content && length $content;

  return from_octets($content);
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

App::karr::Cmd::SetRefs - Store helper payloads in a Git ref

=head1 VERSION

version 0.600

=head1 SYNOPSIS

    karr set-refs superpowers/spec/1234.md draft ready
    karr set-refs refs/superpowers/spec/1234.md "full payload"
    karr set-refs superpowers/spec/1234.md < design.md
    karr set-refs superpowers/spec/1234.md "payload" --dir /path/to/repo

=head1 DESCRIPTION

Writes a helper payload into a free-form Git ref outside the protected board
namespace. This is intended for adjunct workflow data such as AI planning
artifacts or coordination hints that should sync through Git without becoming a
task card.

The payload is every argument after C<REF>, joined with a single space. That is
a one-line shape on purpose, and it is the whole payload: a multi-line document
handed over as several arguments would come back as one long line. So a
document is piped instead -- with no C<CONTENT> argument at all the payload is
read from standard input verbatim, newlines and all, and
C<< karr set-refs REF E<lt> file >> round-trips through
C<< karr get-refs REF E<gt> file >>. Stdin is only read when there is nothing
to join, so an argument form that worked before is untouched, and a bare
C<karr set-refs REF> at a terminal is still the usage error it always was
rather than a command that sits there waiting.



( run in 1.712 second using v1.01-cache-2.11-cpan-aadc1410aed )