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 )