App-karr

 view release on metacpan or  search on metacpan

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

    'overdue'            => 'Overdue',
    'recently-completed' => 'Recently Completed',
    'activity'           => 'Recent Activity',
  );

  for my $sec (@$sections) {
    $md .= "### " . ($section_title{$sec->{name}} // $sec->{name}) . "\n\n";
    if ($sec->{name} eq 'activity') {
      # An activity item is a log event, not a task -- it has no priority or
      # assignee to report, so it gets its own line shape instead of being
      # forced into _task_item's.
      for my $item (@{$sec->{items}}) {
        $md .= sprintf "- %s **%s** %s task#%s", $item->{ts} // '?',
          $item->{agent} // '?', $item->{action} // '?', $item->{task_id} // '?';
        $md .= " ($item->{detail})" if defined $item->{detail} && length $item->{detail};
        $md .= "\n";
      }
    } else {
      for my $item (@{$sec->{items}}) {
        $md .= sprintf "- **#%d** %s (%s", $item->{id}, $item->{title}, $item->{priority};
        $md .= ", \@$item->{assignee}" if $item->{assignee};
        $md .= ")";
        $md .= " \x{2014} $item->{note}" if $item->{note};
        $md .= "\n";
      }
    }
    $md .= "\n";
  }

  $md .= "<!-- END kanban-md context -->\n";
  return $md;
}

sub _write_to_file {
  my ($self, $md) = @_;
  my $file = Path::Tiny::path($self->write_to);

  # Decide the whole file first, then write it once. --write into a directory
  # karr may not write is the user's path, not karr's, and Path::Tiny's error
  # would otherwise report this file and line at them (#77). A merely
  # read-only target file still goes through: spew renames into place.
  my $out = $md;
  if ($file->exists) {
    my $content = eval { $file->slurp_utf8 };
    defined $content
      or user_error( "Could not read $file: ", clean_error($@) );
    if ($content =~ /<!-- BEGIN kanban-md context -->.*<!-- END kanban-md context -->/s) {
      $content =~ s/<!-- BEGIN kanban-md context -->.*<!-- END kanban-md context -->\n?/$md/s;
      $out = $content;
    } else {
      my $sep = $content =~ /\n$/ ? "\n" : "\n\n";
      $out = $content . $sep . $md;
    }
  }

  eval { $file->spew_utf8($out); 1 }
    or user_error( "Could not write $file: ", clean_error($@) );

  # stdout belongs to the payload when an output flag claims it, so the
  # confirmation goes to stderr there. Same answer #248 gave for `delete`'s
  # prompt and for the same reason: `karr context --json --write-to AGENTS.md
  # > ctx.json` has to leave behind a file that decodes whole, and a
  # key=value rendering that carries one line of prose is not key=value.
  # Without an output flag stdout is prose anyway, so the line stays put.
  if ( $self->json || $self->compact ) {
    printf STDERR "Context written to %s\n", $self->write_to;
  }
  else {
    printf "Context written to %s\n", $self->write_to;
  }
}

sub _task_item {
  my ($self, $task, $note) = @_;
  return {
    id       => $task->id,
    title    => $task->title,
    status   => $task->status,
    priority => $task->priority,
    # Empty means absent, as in pick and list (ticket #59): an `assignee: ""`
    # from kanban-md must not become an "assignee":"" key in the --json
    # payload. The Markdown renderer already tested truth rather than the
    # predicate, so only --json ever saw it.
    ( $task->has_assignee && length $task->assignee
      ? ( assignee => $task->assignee )
      : () ),
    ($note ? (note => $note) : ()),
  };
}

# Cross-agent recent activity (ticket #92). #64 put every mutating command
# through the log, but context read none of it -- the log was still summarised
# purely from task state. Read via the same merged-refs walk `karr log` does,
# but bounded, because this is a briefing meant to stay short, not the log
# viewer: the whole log is what `karr log` is for.
#
# The bound excludes the invoking identity's own entries rather than
# truncating a merged view blindly. An agent about to pick up work already
# knows what it itself just did -- `karr show --me` is the tool for that --
# so what changes its decision is what *other* identities have been doing.
# Only the current-scheme refs are excluded; entries left on a pre-#75 legacy
# ref (see App::karr::ActivityLog) are rare enough, and old enough, that
# counting them as "someone else" costs nothing in practice.
#
# "The current-scheme refs" is plural and asked of the log itself (owns_ref),
# not compared against one ref name: since #171 an identity's log rotates into
# refs/karr/log/<role>/<email>+NNNNNN segments, and an equality test would have
# started reporting this agent's own older entries as another agent's the
# moment its log outgrew one segment.
sub _recent_activity {
  my ($self) = @_;
  my $git = $self->git;
  my $log = $self->activity_log;

  my @entries;
  for my $ref ($git->list_refs('refs/karr/log/')) {
    next if $log->owns_ref($ref);
    my $content = $git->read_ref($ref);
    next unless defined $content && length $content;
    for my $line (split /\n/, $content) {
      next unless length $line;

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

# One overdue test for the count and the section, so the header can never
# disagree with the list under it.
#
# `due: ""` satisfies the predicate but is not a date, and the empty string
# sorts before every real one -- so a kanban-md card carrying it was reported
# overdue for ever, with "due " and nothing after it. Empty means absent, as it
# does in pick (ticket #59).
sub _is_overdue {
  my ($self, $task, $now) = @_;
  return 0 unless $task->has_due && length $task->due;
  return 0 unless $task->due lt $now;
  return !$self->store->is_terminal_status($task->status);
}

sub _load_tasks {
  my ($self) = @_;
  return $self->load_tasks;
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

App::karr::Cmd::Context - Generate board context summary for embedding

=head1 VERSION

version 0.600

=head1 SYNOPSIS

    karr context
    karr context --sections blocked,overdue
    karr context --write-to AGENTS.md --days 14
    karr context --activity-limit 10
    karr context --json
    karr context --compact

=head1 DESCRIPTION

Builds a concise board summary suitable for embedding into agent context files
such as F<AGENTS.md>. The command can print Markdown directly, emit structured
JSON, or update an existing file between sentinel comments.

C<--compact> prints the board's four numbers and nothing else -- one
C<key=value> per line, under the same names the C<--json> summary uses, with no
headings, no sections and no sentinels:

    board_name=karr
    total_tasks=41
    active=7
    blocked=1
    overdue=0

That is the reading of a briefing that fits in a prompt header or a status
line, and it is what C<context --compact> was silently failing to do while
C<--compact> was declared for every command in L<App::karr::Role::Output>
(#254). It shapes what is printed, not what is written: with C<--write-to> the
file still receives the Markdown block, because those sentinels are an interop
contract with kanban-md (see L</FILE UPDATE MODE>) and a compacted block would
be one neither tool could find again.

C<--json> and C<--compact> do not compete with C<--write-to> and never did
(#260). C<--write-to> is a side effect; the output flags decide what stdout
carries. All three combinations write the same Markdown block to the file and
differ only in what is printed:

    karr context --write-to AGENTS.md              Context written to AGENTS.md
    karr context --json    --write-to AGENTS.md    the JSON payload
    karr context --compact --write-to AGENTS.md    the four numbers

With an output flag the C<Context written to ...> confirmation goes to
B<stderr>, so C<< karr context --json --write-to AGENTS.md > ctx.json >>
leaves behind a file that decodes whole -- the channel rule C<delete>'s prompt
follows for the same reason (#248). Without one, stdout is prose anyway and
the line stays there.

Only C<archived> tasks are left out of the summary. Finished work still counts
towards the reported total and is still reported as blocked if it is, which is
the rule kanban-md applies to the same block.

=head1 SECTIONS

The generated context can include C<in-progress>, C<blocked>, C<overdue>,
C<recently-completed>, and C<activity>. Use C<--sections> with a
comma-separated list to limit the output to a subset.

C<activity> is the board's activity log (see L<App::karr::Cmd::Log>), filtered
to entries written by identities other than the one invoking C<context> and
bounded by C<--activity-limit> (default 5). An agent reading its own briefing
already knows what it just did -- C<karr show --me> is the tool for that --
so what belongs in a briefing is what everyone *else* has been doing.

C<recently-completed> looks back C<--days> days (default 7) from now; a task
qualifies when its C<completed> stamp falls on or after that cutoff, compared
to day granularity rather than to the second so the comparison stays correct
whether the stamp is a bare C<YYYY-MM-DD> or a full RFC3339 timestamp.

=head1 FILE UPDATE MODE

When C<--write-to> is used, the command replaces the content between
C<BEGIN kanban-md context> and C<END kanban-md context> if those sentinels are
already present; otherwise it appends the generated block to the file. A file
that does not exist yet is created carrying the block alone.

It is a file update and not a redirection: the rest of the host file is left
as it was, and a later run rewrites the same block in place rather than adding
a second one. That is why the block is always Markdown, whatever C<--json> or
C<--compact> ask for on stdout -- both tools find their block by these
sentinels, and a payload between them is one neither could update again.

=head1 SEE ALSO

L<karr>, L<App::karr>, L<App::karr::Cmd::Board>, L<App::karr::Cmd::List>,
L<App::karr::Cmd::Config>, L<App::karr::Cmd::Skill>, L<App::karr::Cmd::Log>

=head1 SUPPORT

=head2 Issues

Please report bugs and feature requests on GitHub at
L<https://github.com/Getty/karr/issues>.

=head2 IRC

Join C<#langertha> on C<irc.perl.org> or message Getty directly.

=head1 CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

=head1 AUTHOR

Torsten Raudssus <getty@cpan.org>



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