App-karr
view release on metacpan or search on metacpan
lib/App/karr/Cmd/Context.pm view on Meta::CPAN
# > 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;
my $decoded = eval { json_decode($line) };
push @entries, $git->maybe_repair_legacy($decoded) if $decoded;
}
}
@entries = sort { ($a->{ts} // '') cmp ($b->{ts} // '') } @entries;
my $limit = $self->activity_limit;
@entries = @entries[-$limit .. -1] if $limit && @entries > $limit;
# Newest first, like recently-completed -- the point of a briefing is that
# the most relevant items are the ones on top.
return map {
my $e = $_;
{
ts => $e->{ts},
agent => $e->{agent},
action => $e->{action},
task_id => $e->{task_id},
( defined $e->{detail} && length $e->{detail} ? ( detail => $e->{detail} ) : () ),
}
} reverse @entries;
}
# The in-progress section is the briefing's "what is being worked on right
# now", sorted most-urgent-first. The order comes from the board's own
# priorities list -- a hardcoded table used to give the wrong answer for any
# priority the default set did not know (ticket #149). Convention matches
# pick / kanban-md: higher index in the list = more urgent.
sub _pri_order {
my ($self, $task) = @_;
my @priorities = $self->config->priorities;
my %index;
$index{$priorities[$_]} //= $_ for 0 .. $#priorities;
my $max = $#priorities;
return $max - ( $index{ $task->priority } // -1 );
}
sub _count_overdue {
my ($self, $tasks) = @_;
my $now = gmtime->strftime('%Y-%m-%d');
return scalar grep { $self->_is_overdue($_, $now) } @$tasks;
}
# 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;
}
( run in 1.260 second using v1.01-cache-2.11-cpan-364913b4093 )