feat(pijul-qol:move-to-zero): move to laravel zero, why not?

Chasesomero
Mar 6, 2026, 3:26 PM
F435FWSFVVX77ADD2R27X3TTECEYWAVV7X6MDLMMTGBJZBCRMMFQC

Dependencies

  • [2] WHWKBNW2
  • [3] 32ICZXL4 init laravel zero for fun
  • [4] ZHWYHIOH feat(pijul-qol): have copilot make script to show change messages beside changes, in pijul credit output
  • [5] XUNF6O2C feat(pijul-qol): a record script that surfaces past tags to frontload organization a little
  • [6] PHV2QDRE replace readme with my own notes

Change contents

  • file addition: PijulRecordWithIntentTagsCommandTest.php (----------)
    [3.1888]
    <?php
    use Illuminate\Process\PendingProcess;
    use Illuminate\Support\Facades\Process;
    it('lists known intent tags', function () {
    Process::fake(function (PendingProcess $process) {
    return match ($process->command) {
    ['pijul', 'log', '--limit', '500', '--output-format', 'json'] => Process::result(json_encode([
    ['message' => 'feat(pijul-qol): first'],
    ['message' => 'feat(init): second'],
    ['message' => 'plain message'],
    ['message' => 'feat(pijul-qol): duplicate'],
    ], JSON_THROW_ON_ERROR)),
    default => Process::result('', 'Unexpected command.', 1),
    };
    });
    $this->artisan('pijul:record-with-intent-tags', ['--list-tags' => true])
    ->expectsOutput('feat(pijul-qol)')
    ->expectsOutput('feat(init)')
    ->assertExitCode(0);
    Process::assertDidntRun(fn (PendingProcess $process) => is_array($process->command)
    && ($process->command[1] ?? null) === 'record');
    });
    it('records a change with a reused intent tag', function () {
    Process::fake(function (PendingProcess $process) {
    return match ($process->command) {
    ['pijul', 'log', '--limit', '500', '--output-format', 'json'] => Process::result(json_encode([
    ['message' => 'feat(pijul-qol): first'],
    ['message' => 'feat(init): second'],
    ], JSON_THROW_ON_ERROR)),
    ['pijul', 'record', '-m', 'feat(pijul-qol): add laravel command', 'app'] => Process::result(''),
    default => Process::result('', 'Unexpected command.', 1),
    };
    });
    $this->artisan('pijul:record-with-intent-tags', ['prefixes' => ['app']])
    ->expectsOutputToContain('Known tags: feat(pijul-qol), feat(init)')
    ->expectsQuestion('Intent tag (optional)', 'feat(pijul-qol)')
    ->expectsQuestion('message', 'add laravel command')
    ->assertExitCode(0);
    });
    it('forwards an explicit message without prompting', function () {
    Process::fake(function (PendingProcess $process) {
    return match ($process->command) {
    ['pijul', 'log', '--limit', '500', '--output-format', 'json'] => Process::result(json_encode([
    ['message' => 'feat(pijul-qol): first'],
    ], JSON_THROW_ON_ERROR)),
    ['pijul', 'record', '-m', 'ship it', '--all', '--description', 'desc', 'app'] => Process::result(''),
    default => Process::result('', 'Unexpected command.', 1),
    };
    });
    $this->artisan('pijul:record-with-intent-tags', [
    '--message' => 'ship it',
    '--all' => true,
    '--description' => 'desc',
    'prefixes' => ['app'],
    ])
    ->expectsOutputToContain('Known tags: feat(pijul-qol)')
    ->assertExitCode(0);
    });
  • file addition: PijulCreditWithMessagesCommandTest.php (----------)
    [3.1888]
    <?php
    use Illuminate\Process\PendingProcess;
    use Illuminate\Support\Facades\Process;
    it('shows change messages inline in pijul credit output', function () {
    Process::fake(function (PendingProcess $process) {
    return match ($process->command) {
    ['pijul', 'credit', 'README.md'] => Process::result(<<<'OUTPUT'
    ABC123
    A regular line
    OUTPUT),
    ['pijul', 'change', 'ABC123'] => Process::result("message = \"feat(pijul-qol): inline context\"\n"),
    default => Process::result('', 'Unexpected command.', 1),
    };
    });
    $this->artisan('pijul:credit-with-messages', ['file' => 'README.md'])
    ->expectsOutputToContain('ABC123 (feat(pijul-qol): inline context)')
    ->expectsOutputToContain('A regular line')
    ->assertExitCode(0);
    });
  • file addition: scripts (d--r------)
    [2.1]
  • file addition: pijul-record-with-intent-tags (---r------)
    [0.3807]
    #!/usr/bin/env bash
    set -euo pipefail
    TAG_LIMIT="${PIJUL_RECORD_TAG_LIMIT:-500}"
    usage() {
    cat <<'EOF'
    Use previously seen intent tags while creating a new Pijul change.
    Usage:
    scripts/pijul-record-with-intent-tags [pijul record options and prefixes...]
    scripts/pijul-record-with-intent-tags --list-tags
    Behavior:
    - Extracts recent intent tags from `pijul log`.
    - Uses `fzf` for interactive selection when available.
    - Otherwise prints known tags as a reminder and asks for the message body.
    - If `-m/--message` is already provided, it prints known tags and forwards
    directly to `pijul record`.
    Environment:
    PIJUL_RECORD_TAG_LIMIT Number of recent log entries to scan (default: 500)
    EOF
    }
    has_message_arg() {
    local arg
    for arg in "$@"; do
    case "$arg" in
    --)
    return 1
    ;;
    -m|--message|--message=*|-m?*)
    return 0
    ;;
    esac
    done
    return 1
    }
    collect_intent_tags() {
    pijul log --limit "$TAG_LIMIT" 2>/dev/null \
    | sed -nE 's/^ ([[:alnum:]_-]+\([^)]*\)).*/\1/p' \
    | awk 'NF && !seen[$0]++'
    }
    join_lines() {
    awk '
    NR == 1 { printf "%s", $0; next }
    { printf ", %s", $0 }
    END { if (NR > 0) printf "\n" }
    '
    }
    print_known_tags() {
    local tags="$1"
    if [[ -z "$tags" ]]; then
    return
    fi
    printf 'Known tags: %s\n' "$(printf '%s\n' "$tags" | join_lines)" >&2
    }
    select_tag() {
    local tags="$1"
    if [[ -z "$tags" || ! -t 0 ]]; then
    return
    fi
    if command -v fzf >/dev/null 2>&1; then
    printf '%s\n' "$tags" \
    | fzf --prompt='intent tag (Esc=skip)> ' --height=40% --reverse \
    || true
    return
    fi
    print_known_tags "$tags"
    }
    main() {
    local tags=""
    local tag=""
    local body=""
    local message=""
    case "${1-}" in
    --help|-h)
    usage
    exit 0
    ;;
    --list-tags)
    collect_intent_tags || true
    exit 0
    ;;
    esac
    tags="$(collect_intent_tags || true)"
    if has_message_arg "$@"; then
    if [[ -t 1 ]]; then
    print_known_tags "$tags"
    fi
    exec pijul record "$@"
    fi
    if [[ ! -t 0 ]]; then
    printf 'This helper needs a terminal unless you pass -m/--message.\n' >&2
    exit 1
    fi
    tag="$(select_tag "$tags")"
    tag="${tag%%$'\n'*}"
    tag="${tag%$'\r'}"
    if [[ -n "$tag" ]]; then
    printf 'Using tag: %s\n' "$tag" >&2
    fi
    read -erp "message: " body < /dev/tty
    if [[ -n "$tag" && -n "$body" ]]; then
    message="$tag: $body"
    elif [[ -n "$tag" ]]; then
    message="$tag"
    else
    message="$body"
    fi
    if [[ -z "$message" ]]; then
    printf 'Change message cannot be empty.\n' >&2
    exit 1
    fi
    exec pijul record -m "$message" "$@"
    }
    main "$@"
  • file addition: pijul-credit-with-messages (---r------)
    [0.3807]
    #!/usr/bin/env bash
    set -euo pipefail
    declare -A CHANGE_MESSAGES
    resolve_message() {
    local hash="$1"
    if [[ -n "${CHANGE_MESSAGES[$hash]+x}" ]]; then
    printf '%s' "${CHANGE_MESSAGES[$hash]}"
    return
    fi
    local raw_message
    raw_message="$(pijul change "$hash" 2>/dev/null | sed -nE 's/^message = "(.*)"$/\1/p' | head -n 1 || true)"
    if [[ -z "$raw_message" ]]; then
    CHANGE_MESSAGES["$hash"]="<message unavailable>"
    else
    CHANGE_MESSAGES["$hash"]="${raw_message//\\\"/\"}"
    fi
    printf '%s' "${CHANGE_MESSAGES[$hash]}"
    }
    is_hash_line() {
    local line="$1"
    [[ "$line" =~ ^[A-Z0-9]+([[:space:]]*,[[:space:]]*[A-Z0-9]+)*$ ]]
    }
    format_hash_line() {
    local line="$1"
    local formatted=""
    local index=0
    local hash
    local message
    IFS=',' read -ra hashes <<< "$line"
    for hash in "${hashes[@]}"; do
    hash="${hash#"${hash%%[![:space:]]*}"}"
    hash="${hash%"${hash##*[![:space:]]}"}"
    message="$(resolve_message "$hash")"
    if (( index > 0 )); then
    formatted+=", "
    fi
    formatted+="${hash} (${message})"
    ((index += 1))
    done
    printf '%s\n' "$formatted"
    }
    while IFS= read -r line || [[ -n "$line" ]]; do
    if is_hash_line "$line"; then
    format_hash_line "$line"
    else
    printf '%s\n' "$line"
    fi
    done < <(pijul credit "$@")
  • file addition: PijulRecordWithIntentTagsCommand.php (----------)
    [3.310659]
    <?php
    namespace App\Commands;
    use App\Commands\Concerns\InteractsWithPijul;
    use JsonException;
    use LaravelZero\Framework\Commands\Command;
    class PijulRecordWithIntentTagsCommand extends Command
    {
    use InteractsWithPijul;
    /**
    * @var string
    */
    protected $signature = 'pijul:record-with-intent-tags
    {prefixes?* : Paths in which to record the changes}
    {--a|all : Record all paths that have changed}
    {--m|message= : Set the change message}
    {--description= : Set the description field}
    {--author= : Set the author field}
    {--timestamp= : Set the timestamp field}
    {--ignore-missing : Ignore missing (deleted) files}
    {--working-copy= : Override the working copy path}
    {--amend= : Amend this change instead of creating a new change}
    {--identity= : Identity to sign changes with}
    {--patience : Use Patience diff}
    {--histogram : Use Histogram diff}
    {--repository= : Work with the repository at PATH}
    {--channel= : Work with CHANNEL instead of the current channel}
    {--no-prompt : Abort rather than prompt for input}
    {--list-tags : Print known intent tags and exit}
    {--tag-limit=500 : Number of recent log entries to scan}';
    /**
    * @var string
    */
    protected $description = 'Record a Pijul change with reusable intent tags';
    /**
    * @var array<int, string>
    */
    protected $aliases = ['pijul-record-with-intent-tags'];
    /**
    * @throws JsonException
    */
    public function handle(): int
    {
    $tags = $this->collectIntentTags();
    if ($this->option('list-tags')) {
    foreach ($tags as $tag) {
    $this->line($tag);
    }
    return self::SUCCESS;
    }
    $message = $this->option('message');
    if ($message !== null) {
    $this->printKnownTags($tags);
    $this->runPijul($this->recordArguments((string) $message), interactive: $this->input->isInteractive());
    return self::SUCCESS;
    }
    if (! $this->input->isInteractive()) {
    $this->error('This command needs an interactive terminal unless you pass --message.');
    return self::FAILURE;
    }
    $this->printKnownTags($tags);
    $tag = $tags === []
    ? ''
    : (string) $this->anticipate('Intent tag (optional)', $tags, '');
    $body = (string) $this->ask('message');
    $fullMessage = $this->buildMessage($tag, $body);
    if ($fullMessage === '') {
    $this->error('Change message cannot be empty.');
    return self::FAILURE;
    }
    $this->runPijul($this->recordArguments($fullMessage), interactive: true);
    return self::SUCCESS;
    }
    /**
    * @return list<string>
    *
    * @throws JsonException
    */
    private function collectIntentTags(): array
    {
    $arguments = [
    'log',
    '--limit',
    (string) $this->option('tag-limit'),
    '--output-format',
    'json',
    ];
    foreach (['repository', 'channel'] as $option) {
    $value = $this->option($option);
    if ($value !== null) {
    $arguments[] = sprintf('--%s', $option);
    $arguments[] = (string) $value;
    }
    }
    if ($this->option('no-prompt')) {
    $arguments[] = '--no-prompt';
    }
    $result = $this->runPijul($arguments);
    $changes = json_decode($result->output(), true, 512, JSON_THROW_ON_ERROR);
    $tags = [];
    foreach ($changes as $change) {
    $message = $change['message'] ?? null;
    if (! is_string($message) || preg_match('/^([A-Za-z0-9_-]+\([^)]*\))/', $message, $matches) !== 1) {
    continue;
    }
    $tags[$matches[1]] = true;
    }
    return array_keys($tags);
    }
    private function printKnownTags(array $tags): void
    {
    if ($tags === []) {
    return;
    }
    $this->line(sprintf('Known tags: %s', implode(', ', $tags)));
    }
    private function buildMessage(string $tag, string $body): string
    {
    $tag = trim($tag);
    $body = trim($body);
    if ($tag !== '' && $body !== '') {
    return sprintf('%s: %s', $tag, $body);
    }
    return $tag !== '' ? $tag : $body;
    }
    /**
    * @return array<int, string>
    */
    private function recordArguments(string $message): array
    {
    $arguments = ['record', '-m', $message];
    if ($this->option('all')) {
    $arguments[] = '--all';
    }
    foreach ([
    'description',
    'author',
    'timestamp',
    'working-copy',
    'identity',
    'repository',
    'channel',
    ] as $option) {
    $value = $this->option($option);
    if ($value !== null) {
    $arguments[] = sprintf('--%s', $option);
    $arguments[] = (string) $value;
    }
    }
    if ($this->input->hasParameterOption('--amend')) {
    $arguments[] = '--amend';
    if (($amend = $this->option('amend')) !== null) {
    $arguments[] = (string) $amend;
    }
    }
    foreach (['ignore-missing', 'patience', 'histogram', 'no-prompt'] as $option) {
    if ($this->option($option)) {
    $arguments[] = sprintf('--%s', $option);
    }
    }
    foreach ($this->argument('prefixes') as $prefix) {
    $arguments[] = (string) $prefix;
    }
    return $arguments;
    }
    }
  • file addition: PijulCreditWithMessagesCommand.php (----------)
    [3.310659]
    <?php
    namespace App\Commands;
    use App\Commands\Concerns\InteractsWithPijul;
    use LaravelZero\Framework\Commands\Command;
    class PijulCreditWithMessagesCommand extends Command
    {
    use InteractsWithPijul;
    /**
    * @var string
    */
    protected $signature = 'pijul:credit-with-messages
    {file : The file to annotate}
    {--repository= : Work with the repository at PATH}
    {--channel= : Work with CHANNEL instead of the current channel}
    {--no-prompt : Abort rather than prompt for input}';
    /**
    * @var string
    */
    protected $description = 'Show pijul credit output with change messages inline';
    /**
    * @var array<int, string>
    */
    protected $aliases = ['pijul-credit-with-messages'];
    /**
    * @var array<string, string>
    */
    private array $changeMessages = [];
    public function handle(): int
    {
    $output = $this->runPijul($this->creditArguments())->output();
    foreach (preg_split("/\r\n|\n|\r/", rtrim($output, "\r\n")) ?: [] as $line) {
    $this->line($this->isHashLine($line) ? $this->formatHashLine($line) : $line);
    }
    if (str_ends_with($output, "\n\n")) {
    $this->newLine();
    }
    return self::SUCCESS;
    }
    /**
    * @return array<int, string>
    */
    private function creditArguments(): array
    {
    $arguments = ['credit'];
    if (($repository = $this->option('repository')) !== null) {
    $arguments[] = '--repository';
    $arguments[] = $repository;
    }
    if (($channel = $this->option('channel')) !== null) {
    $arguments[] = '--channel';
    $arguments[] = $channel;
    }
    if ($this->option('no-prompt')) {
    $arguments[] = '--no-prompt';
    }
    $arguments[] = (string) $this->argument('file');
    return $arguments;
    }
    private function isHashLine(string $line): bool
    {
    return preg_match('/^[A-Z0-9]+(?:\s*,\s*[A-Z0-9]+)*$/', $line) === 1;
    }
    private function formatHashLine(string $line): string
    {
    $hashes = preg_split('/\s*,\s*/', trim($line)) ?: [];
    $formatted = [];
    foreach ($hashes as $hash) {
    $formatted[] = sprintf('%s (%s)', $hash, $this->resolveMessage($hash));
    }
    return implode(', ', $formatted);
    }
    private function resolveMessage(string $hash): string
    {
    if (array_key_exists($hash, $this->changeMessages)) {
    return $this->changeMessages[$hash];
    }
    $changeArguments = ['change'];
    if (($repository = $this->option('repository')) !== null) {
    $changeArguments[] = '--repository';
    $changeArguments[] = $repository;
    }
    if ($this->option('no-prompt')) {
    $changeArguments[] = '--no-prompt';
    }
    $changeArguments[] = $hash;
    $rawOutput = $this->runPijul($changeArguments)->output();
    if (preg_match('/^message = "(.*)"$/m', $rawOutput, $matches) !== 1) {
    return $this->changeMessages[$hash] = '<message unavailable>';
    }
    return $this->changeMessages[$hash] = str_replace('\"', '"', $matches[1]);
    }
    }
  • file addition: Concerns (d--r------)
    [3.310659]
  • file addition: InteractsWithPijul.php (----------)
    [0.17797]
    <?php
    namespace App\Commands\Concerns;
    use Illuminate\Contracts\Process\ProcessResult;
    use Illuminate\Process\PendingProcess;
    use Illuminate\Support\Facades\Process;
    use RuntimeException;
    trait InteractsWithPijul
    {
    protected function runPijul(array $arguments, bool $interactive = false): ProcessResult
    {
    $pendingProcess = Process::path(base_path());
    if ($interactive) {
    $pendingProcess = $this->configureInteractiveProcess($pendingProcess);
    }
    $result = $pendingProcess->run(array_merge(['pijul'], $arguments));
    if ($result->failed()) {
    $errorOutput = trim($result->errorOutput());
    throw new RuntimeException($errorOutput !== ''
    ? $errorOutput
    : sprintf('The command "%s" failed.', $result->command()));
    }
    return $result;
    }
    private function configureInteractiveProcess(PendingProcess $pendingProcess): PendingProcess
    {
    $pendingProcess = $pendingProcess->forever();
    if (Process::supportsTty()) {
    $pendingProcess = $pendingProcess->tty();
    }
    return $pendingProcess;
    }
    }
  • replacement in README.md at line 9
    [4.32][4.32:159]()
    Use `scripts/pijul-credit-with-messages` to keep `pijul credit` output intact while showing each hash's change message inline.
    [4.32]
    [4.159]
    Use `./pijultester pijul:credit-with-messages` to keep `pijul credit` output intact while showing each hash's change message inline.
  • replacement in README.md at line 14
    [4.178][4.178:223]()
    scripts/pijul-credit-with-messages README.md
    [4.178]
    [5.0]
    ./pijultester pijul:credit-with-messages README.md
  • replacement in README.md at line 19
    [5.39][5.39:163]()
    Use `scripts/pijul-record-with-intent-tags` to reuse intent tags from recent `pijul log` messages while recording a change.
    [5.39]
    [5.163]
    Use `./pijultester pijul:record-with-intent-tags` to reuse intent tags from recent `pijul log` messages while recording a change.
  • replacement in README.md at line 21
    [5.164][5.164:466]()
    - If `fzf` is installed, you can fuzzy-search existing tags and press Enter to reuse one.
    - If `fzf` is not installed, the script prints known tags as a reminder before asking for the rest of your message.
    - Press Esc in `fzf`, or just ignore the reminder, if you want to type a fresh message instead.
    [5.164]
    [5.466]
    - The command lists known tags as a reminder before prompting.
    - The intent-tag prompt supports terminal autocompletion from recent `pijul log` messages.
    - Skip the tag prompt or pass `--message` if you want to type a fresh message directly.
  • replacement in README.md at line 28
    [5.486][5.486:618]()
    scripts/pijul-record-with-intent-tags
    scripts/pijul-record-with-intent-tags --all
    scripts/pijul-record-with-intent-tags --list-tags
    [5.486]
    [4.223]
    ./pijultester pijul:record-with-intent-tags
    ./pijultester pijul:record-with-intent-tags --all
    ./pijultester pijul:record-with-intent-tags --list-tags
  • replacement in .ignore at line 3
    [3.314777][3.314777:314785]()
    vendor/*
    [3.314777]
    vendor/*
    vendor