Claude-Agent-Code-Refactor

 view release on metacpan or  search on metacpan

examples/refactor_until_clean.pl  view on Meta::CPAN


use Claude::Agent::Code::Refactor qw(refactor_until_clean);
use Claude::Agent::Code::Refactor::Options;
use IO::Async::Loop;

# Create event loop
my $loop = IO::Async::Loop->new;

# Configure refactor options
my $options = Claude::Agent::Code::Refactor::Options->new(
    max_iterations         => 5,              # Max review-fix cycles
    min_severity           => 'medium',       # Only fix medium+ issues
    categories             => ['bugs', 'security'],  # Focus on these
    permission_mode        => 'acceptEdits',  # Auto-accept file edits
    perlcritic             => 1,              # Include perlcritic analysis
    perlcritic_severity    => 4,              # Perlcritic severity level
    filter_false_positives => 1,              # Filter out false positives
);

# Run the refactor loop on lib/
my $result = refactor_until_clean(

lib/Claude/Agent/Code/Refactor.pm  view on Meta::CPAN


    use Claude::Agent::Code::Refactor qw(refactor refactor_until_clean);
    use IO::Async::Loop;

    my $loop = IO::Async::Loop->new;

    # Automatic review -> fix -> re-review loop
    my $result = refactor_until_clean(
        paths   => ['lib/'],
        options => Claude::Agent::Code::Refactor::Options->new(
            max_iterations  => 5,
            min_severity    => 'medium',
            categories      => ['bugs', 'security'],
            permission_mode => 'acceptEdits',
        ),
        loop    => $loop,
    )->get;

    if ($result->is_clean) {
        print "All issues resolved!\n";
    } else {
        print "Remaining issues: ", $result->final_issues, "\n";
    }

=head1 DESCRIPTION

Claude::Agent::Code::Refactor provides automated code refactoring using the
Claude Agent SDK. It integrates with Claude::Agent::Code::Review to create
a review-fix-re-review loop that automatically fixes issues until the code
is clean (or max iterations reached).

=head1 EXPORTED FUNCTIONS

=head2 refactor

    my $future = refactor(
        target  => $target,      # file, dir, or 'staged'
        options => $options,     # Claude::Agent::Code::Refactor::Options
        loop    => $loop,        # IO::Async::Loop
    );

lib/Claude/Agent/Code/Refactor.pm  view on Meta::CPAN


=head2 refactor_until_clean

    my $future = refactor_until_clean(
        paths   => \@paths,      # files and/or directories
        options => $options,
        loop    => $loop,
    );
    my $result = $future->get;

Main refactoring loop: review -> fix -> re-review until clean or max iterations.

=cut

async sub refactor_until_clean {
    my (%args) = @_;

    my $paths   = $args{paths} // die "refactor_until_clean() requires 'paths' argument";
    my $options = $args{options} // Claude::Agent::Code::Refactor::Options->new();
    my $loop    = $args{loop} // IO::Async::Loop->new;

    my $start_time = time();

    my $result = Claude::Agent::Code::Refactor::Result->new();

    my $review_options = $options->to_review_options;

    for my $iteration (1 .. $options->max_iterations) {
        # Step 1: Review the code
        my $report = await review_files(
            paths   => $paths,
            options => $review_options,
            loop    => $loop,
        );

        if (!defined $report) {
            $result->{error} = 'Review failed to return a report';
            last;

lib/Claude/Agent/Code/Refactor.pm  view on Meta::CPAN

    my $iter = query(
        prompt  => $prompt,
        options => $claude_options,
        loop    => $loop,
    );

    # Track what gets fixed
    my %files_modified;
    my $edit_count = 0;  # Track actual Edit operations attempted

    my $max_iterations = 1000;
    my $iteration_count = 0;
    my $start_time = time();
    my $timeout_seconds = 300;  # 5 minute timeout

    my $timeout_result;
    eval {
        while (my $msg = await $iter->next_async) {
            # Check timeout at start of loop iteration
            if ((time() - $start_time) > $timeout_seconds) {
                $iter->cancel if $iter->can('cancel');
                $timeout_result = { issues_fixed => $edit_count, files_modified => [keys %files_modified], error => 'Fix operation timed out after 5 minutes' };
                last;
            }
            $iteration_count++;
            last if $iteration_count >= $max_iterations;

            if ($msg->isa('Claude::Agent::Message::Assistant')) {
                for my $block (@{$msg->content_blocks}) {
                    if ($block->isa('Claude::Agent::Content::ToolUse')) {
                        if ($block->name eq 'Edit') {
                            $edit_count++;  # Count each Edit operation
                            my $input = $block->input;
                            if ($input && ref($input) eq 'HASH') {
                                my $file = $input->{file_path};
                                $files_modified{$file} = 1 if defined $file && length $file;

lib/Claude/Agent/Code/Refactor/Options.pm  view on Meta::CPAN

use warnings;

use Types::Common -types;

# Valid values for validation
my @VALID_SEVERITIES = qw(critical high medium low info);
my @VALID_CATEGORIES = qw(bugs security style performance maintainability);

use Marlin
    # Loop control
    'max_iterations'     => sub { 5 },
    'max_turns_per_fix'  => sub { 20 },
    'stop_on_critical'   => sub { 1 },

    # Issue filtering (passed to Code::Review)
    'min_severity'       => sub { 'low' },
    'categories'         => sub { ['bugs', 'security', 'style', 'performance', 'maintainability'] },

    # Fix behavior
    'fix_one_at_a_time'  => sub { 0 },
    'dry_run'            => sub { 0 },

lib/Claude/Agent/Code/Refactor/Options.pm  view on Meta::CPAN

            die "Categories cannot be empty";
        }
        for my $cat (@{$self->categories}) {
            unless (grep { $_ eq $cat } @VALID_CATEGORIES) {
                my $safe_cat = $cat =~ s/[^a-zA-Z0-9_-]//gr;
            die "Invalid category '$safe_cat'. Must be one of: " . join(', ', @VALID_CATEGORIES);
            }
        }
    }

    # Validate max_iterations
    if (defined $self->max_iterations) {
        if ($self->max_iterations < 1) {
            die "max_iterations must be >= 1";
        }
        if ($self->max_iterations > 100) {
            die "max_iterations must be <= 100 to prevent resource exhaustion";
        }
    }

    # Validate max_turns_per_fix
    if (defined $self->max_turns_per_fix) {
        if ($self->max_turns_per_fix < 1) {
            die "max_turns_per_fix must be >= 1";
        }
        if ($self->max_turns_per_fix > 100) {
            die "max_turns_per_fix must be <= 100 to prevent resource exhaustion";

lib/Claude/Agent/Code/Refactor/Options.pm  view on Meta::CPAN


=head1 NAME

Claude::Agent::Code::Refactor::Options - Configuration options for code refactoring

=head1 SYNOPSIS

    use Claude::Agent::Code::Refactor::Options;

    my $options = Claude::Agent::Code::Refactor::Options->new(
        max_iterations  => 5,
        min_severity    => 'medium',
        categories      => ['bugs', 'security'],
        permission_mode => 'acceptEdits',
    );

=head1 DESCRIPTION

Configuration object for Claude::Agent::Code::Refactor.

=head2 ATTRIBUTES

=over 4

=item * max_iterations - Maximum review-fix-review cycles (default: 5)

=item * max_turns_per_fix - Maximum Claude turns per fix attempt (default: 20)

=item * stop_on_critical - Halt if critical issue can't be fixed (default: 1)

=item * min_severity - Minimum severity to fix (default: 'low')

=item * categories - ArrayRef of categories to fix (default: all)

=item * fix_one_at_a_time - Fix issues one at a time vs all at once (default: 0)

lib/Claude/Agent/Code/Refactor/Result.pm  view on Meta::CPAN

package Claude::Agent::Code::Refactor::Result;

use 5.020;
use strict;
use warnings;

use Types::Common -types;

use Marlin
    'success'         => sub { 0 },
    'iterations'      => sub { 0 },
    'initial_issues'  => sub { 0 },
    'final_issues'    => sub { 0 },
    'fixes_applied'   => sub { 0 },
    'files_modified'  => sub { [] },
    'history'         => sub { [] },
    'final_report'    => sub { undef },
    'duration_ms'     => sub { 0 },
    'error'           => sub { undef };

=head1 NAME

Claude::Agent::Code::Refactor::Result - Result of a refactoring operation

=head1 SYNOPSIS

    my $result = Claude::Agent::Code::Refactor::Result->new(
        success         => 1,
        iterations      => 3,
        initial_issues  => 15,
        final_issues    => 0,
        fixes_applied   => 15,
        files_modified  => ['lib/Foo.pm', 'lib/Bar.pm'],
        history         => [...],
        duration_ms     => 45000,
    );

    if ($result->is_clean) {
        print "All issues resolved!\n";

lib/Claude/Agent/Code/Refactor/Result.pm  view on Meta::CPAN


Represents the result of a refactoring operation, including success status,
iteration history, and statistics.

=head2 ATTRIBUTES

=over 4

=item * success - True if all issues were resolved

=item * iterations - Number of review-fix cycles completed

=item * initial_issues - Number of issues found in first review

=item * final_issues - Number of issues remaining after refactoring

=item * fixes_applied - Total number of fixes applied

=item * files_modified - ArrayRef of files that were modified

=item * history - ArrayRef of per-iteration details

lib/Claude/Agent/Code/Refactor/Result.pm  view on Meta::CPAN


    push @{$self->history}, {
        iteration      => $iteration,
        issues_found   => $args{issues_found} // 0,
        issues_fixed   => $args{issues_fixed} // 0,
        files_modified => $args{files_modified} // [],
    };

    # Update totals
    $self->{fixes_applied} += $args{issues_fixed} // 0;
    $self->{iterations} = $iteration;

    # Track unique files modified
    my %seen = map { $_ => 1 } @{$self->files_modified};
    for my $file (@{$args{files_modified} // []}) {
        push @{$self->files_modified}, $file unless $seen{$file}++;
    }

    return;
}

lib/Claude/Agent/Code/Refactor/Result.pm  view on Meta::CPAN

        push @lines, "Error: " . $self->error;
    }
    elsif ($self->is_clean) {
        push @lines, "Status: SUCCESS - All issues resolved";
    }
    else {
        push @lines, "Status: INCOMPLETE - Some issues remain";
    }

    push @lines, "";
    push @lines, "Iterations: " . $self->iterations;
    push @lines, "Initial issues: " . $self->initial_issues;
    push @lines, "Final issues: " . $self->final_issues;
    push @lines, "Fixes applied: " . $self->fixes_applied;
    push @lines, "Fix rate: " . $self->fix_rate . "%";
    push @lines, "Duration: " . sprintf("%.1f", $self->duration_ms / 1000) . "s";
    push @lines, "";

    if (@{$self->files_modified}) {
        push @lines, "Files modified:";
        for my $file (@{$self->files_modified}) {

lib/Claude/Agent/Code/Refactor/Result.pm  view on Meta::CPAN


Returns a hashref representation of the result.

=cut

sub to_hash {
    my ($self) = @_;

    return {
        success        => $self->success ? 1 : 0,
        iterations     => $self->iterations,
        initial_issues => $self->initial_issues,
        final_issues   => $self->final_issues,
        fixes_applied  => $self->fixes_applied,
        fix_rate       => $self->fix_rate,
        files_modified => $self->files_modified,
        history        => $self->history,
        duration_ms    => $self->duration_ms,
        ($self->has_error ? (error => $self->error) : ()),
    };
}

t/01-basic.t  view on Meta::CPAN

use_ok('Claude::Agent::Code::Refactor::Options');
use_ok('Claude::Agent::Code::Refactor::Result');

# Test exports
can_ok('Claude::Agent::Code::Refactor', qw(refactor refactor_issues refactor_until_clean));

# Test Options
subtest 'Options defaults' => sub {
    my $opts = Claude::Agent::Code::Refactor::Options->new();

    is($opts->max_iterations, 5, 'default max_iterations');
    is($opts->max_turns_per_fix, 20, 'default max_turns_per_fix');
    is($opts->stop_on_critical, 1, 'default stop_on_critical');
    is($opts->min_severity, 'low', 'default min_severity');
    is_deeply($opts->categories,
        ['bugs', 'security', 'style', 'performance', 'maintainability'],
        'default categories');
    is($opts->fix_one_at_a_time, 0, 'default fix_one_at_a_time');
    is($opts->dry_run, 0, 'default dry_run');
    is($opts->create_backup, 0, 'default create_backup');
    is($opts->perlcritic, 0, 'default perlcritic');
    is($opts->perlcritic_severity, 4, 'default perlcritic_severity');
    is($opts->filter_false_positives, 1, 'default filter_false_positives');
    is($opts->permission_mode, 'acceptEdits', 'default permission_mode');
};

subtest 'Options custom values' => sub {
    my $opts = Claude::Agent::Code::Refactor::Options->new(
        max_iterations     => 10,
        max_turns_per_fix  => 30,
        min_severity       => 'medium',
        categories         => ['bugs', 'security'],
        dry_run            => 1,
        perlcritic         => 1,
        perlcritic_severity => 2,
        model              => 'claude-sonnet-4-5',
    );

    is($opts->max_iterations, 10, 'custom max_iterations');
    is($opts->max_turns_per_fix, 30, 'custom max_turns_per_fix');
    is($opts->min_severity, 'medium', 'custom min_severity');
    is_deeply($opts->categories, ['bugs', 'security'], 'custom categories');
    is($opts->dry_run, 1, 'custom dry_run');
    is($opts->perlcritic, 1, 'custom perlcritic');
    is($opts->perlcritic_severity, 2, 'custom perlcritic_severity');
    is($opts->model, 'claude-sonnet-4-5', 'custom model');
};

subtest 'Options validation' => sub {

t/01-basic.t  view on Meta::CPAN


    throws_ok {
        Claude::Agent::Code::Refactor::Options->new(categories => ['invalid']);
    } qr/Invalid category/, 'rejects invalid category';

    throws_ok {
        Claude::Agent::Code::Refactor::Options->new(categories => []);
    } qr/cannot be empty/, 'rejects empty categories';

    throws_ok {
        Claude::Agent::Code::Refactor::Options->new(max_iterations => 0);
    } qr/must be >= 1/, 'rejects zero max_iterations';

    throws_ok {
        Claude::Agent::Code::Refactor::Options->new(max_turns_per_fix => 0);
    } qr/must be >= 1/, 'rejects zero max_turns_per_fix';

    throws_ok {
        Claude::Agent::Code::Refactor::Options->new(perlcritic_severity => 6);
    } qr/Invalid perlcritic_severity/, 'rejects invalid perlcritic_severity';
};

# Test Result
subtest 'Result defaults' => sub {
    my $result = Claude::Agent::Code::Refactor::Result->new();

    is($result->success, 0, 'default success');
    is($result->iterations, 0, 'default iterations');
    is($result->initial_issues, 0, 'default initial_issues');
    is($result->final_issues, 0, 'default final_issues');
    is($result->fixes_applied, 0, 'default fixes_applied');
    is_deeply($result->files_modified, [], 'default files_modified');
    is_deeply($result->history, [], 'default history');
    is($result->duration_ms, 0, 'default duration_ms');
    ok(!$result->has_error, 'no error by default');
};

subtest 'Result is_clean' => sub {

t/01-basic.t  view on Meta::CPAN


subtest 'Result add_iteration' => sub {
    my $result = Claude::Agent::Code::Refactor::Result->new();

    $result->add_iteration(
        issues_found   => 10,
        issues_fixed   => 5,
        files_modified => ['lib/Foo.pm'],
    );

    is($result->iterations, 1, 'iteration count');
    is($result->fixes_applied, 5, 'fixes_applied updated');
    is_deeply($result->files_modified, ['lib/Foo.pm'], 'files_modified');
    is(scalar @{$result->history}, 1, 'history has one entry');
    is($result->history->[0]{iteration}, 1, 'history iteration number');
    is($result->history->[0]{issues_found}, 10, 'history issues_found');
    is($result->history->[0]{issues_fixed}, 5, 'history issues_fixed');

    $result->add_iteration(
        issues_found   => 5,
        issues_fixed   => 3,
        files_modified => ['lib/Foo.pm', 'lib/Bar.pm'],
    );

    is($result->iterations, 2, 'iteration count after second');
    is($result->fixes_applied, 8, 'fixes_applied cumulative');
    is(scalar @{$result->files_modified}, 2, 'files_modified deduplicated');
    is(scalar @{$result->history}, 2, 'history has two entries');
};

subtest 'Result has_error' => sub {
    my $no_error = Claude::Agent::Code::Refactor::Result->new();
    ok(!$no_error->has_error, 'no error');

    my $with_error = Claude::Agent::Code::Refactor::Result->new(
        error => 'Something went wrong',
    );
    ok($with_error->has_error, 'has error');
};

subtest 'Result as_text' => sub {
    my $result = Claude::Agent::Code::Refactor::Result->new(
        success         => 1,
        iterations      => 2,
        initial_issues  => 10,
        final_issues    => 0,
        fixes_applied   => 10,
        files_modified  => ['lib/Foo.pm'],
        duration_ms     => 5000,
    );

    my $text = $result->as_text;
    like($text, qr/REFACTOR RESULT/, 'has header');
    like($text, qr/SUCCESS/, 'shows success');
    like($text, qr/Iterations: 2/, 'shows iterations');
    like($text, qr/Initial issues: 10/, 'shows initial issues');
    like($text, qr/Fix rate: 100%/, 'shows fix rate');
    like($text, qr/lib\/Foo\.pm/, 'shows modified files');
};

subtest 'Result to_hash' => sub {
    my $result = Claude::Agent::Code::Refactor::Result->new(
        success         => 1,
        iterations      => 3,
        initial_issues  => 15,
        final_issues    => 0,
        fixes_applied   => 15,
    );

    my $hash = $result->to_hash;
    is($hash->{success}, 1, 'hash success');
    is($hash->{iterations}, 3, 'hash iterations');
    is($hash->{initial_issues}, 15, 'hash initial_issues');
    is($hash->{final_issues}, 0, 'hash final_issues');
    is($hash->{fix_rate}, 100, 'hash fix_rate');
    ok(!exists $hash->{error}, 'hash omits error when not set');
};

done_testing();

t/03-options.t  view on Meta::CPAN

use strict;
use warnings;
use Test::More;
use Test::Exception;

use_ok('Claude::Agent::Code::Refactor::Options');

subtest 'default values' => sub {
    my $opts = Claude::Agent::Code::Refactor::Options->new();

    is($opts->max_iterations, 5, 'default max_iterations is 5');
    is($opts->max_turns_per_fix, 20, 'default max_turns_per_fix is 20');
    is($opts->min_severity, 'low', 'default min_severity is low');
    is($opts->permission_mode, 'acceptEdits', 'default permission_mode is acceptEdits');
    is($opts->dry_run, 0, 'default dry_run is 0');
    is($opts->perlcritic, 0, 'default perlcritic is 0');
    is($opts->perlcritic_severity, 4, 'default perlcritic_severity is 4');
    is($opts->filter_false_positives, 1, 'default filter_false_positives is 1');
    is($opts->model, undef, 'default model is undef');
    is_deeply(
        $opts->categories,
        ['bugs', 'security', 'style', 'performance', 'maintainability'],
        'default categories is all categories'
    );
};

subtest 'custom values' => sub {
    my $opts = Claude::Agent::Code::Refactor::Options->new(
        max_iterations         => 10,
        max_turns_per_fix      => 30,
        min_severity           => 'high',
        permission_mode        => 'bypassPermissions',
        dry_run                => 1,
        perlcritic             => 1,
        perlcritic_severity    => 2,
        filter_false_positives => 0,
        model                  => 'claude-sonnet-4',
        categories             => ['bugs', 'security'],
    );

    is($opts->max_iterations, 10, 'custom max_iterations');
    is($opts->max_turns_per_fix, 30, 'custom max_turns_per_fix');
    is($opts->min_severity, 'high', 'custom min_severity');
    is($opts->permission_mode, 'bypassPermissions', 'custom permission_mode');
    is($opts->dry_run, 1, 'custom dry_run');
    is($opts->perlcritic, 1, 'custom perlcritic');
    is($opts->perlcritic_severity, 2, 'custom perlcritic_severity');
    is($opts->filter_false_positives, 0, 'custom filter_false_positives');
    is($opts->model, 'claude-sonnet-4', 'custom model');
    is_deeply($opts->categories, ['bugs', 'security'], 'custom categories');
};

t/03-options.t  view on Meta::CPAN


    throws_ok {
        Claude::Agent::Code::Refactor::Options->new(categories => []);
    } qr/cannot be empty/, 'rejects empty categories';

    throws_ok {
        Claude::Agent::Code::Refactor::Options->new(categories => 'bugs');
    } qr/must be an array/, 'rejects non-array categories';
};

subtest 'max_iterations validation' => sub {
    lives_ok {
        Claude::Agent::Code::Refactor::Options->new(max_iterations => 1);
    } 'accepts max_iterations = 1';

    lives_ok {
        Claude::Agent::Code::Refactor::Options->new(max_iterations => 100);
    } 'accepts max_iterations = 100';

    throws_ok {
        Claude::Agent::Code::Refactor::Options->new(max_iterations => 0);
    } qr/must be >= 1/, 'rejects max_iterations < 1';

    throws_ok {
        Claude::Agent::Code::Refactor::Options->new(max_iterations => 101);
    } qr/must be <= 100/, 'rejects max_iterations > 100';
};

subtest 'max_turns_per_fix validation' => sub {
    lives_ok {
        Claude::Agent::Code::Refactor::Options->new(max_turns_per_fix => 1);
    } 'accepts max_turns_per_fix = 1';

    lives_ok {
        Claude::Agent::Code::Refactor::Options->new(max_turns_per_fix => 100);
    } 'accepts max_turns_per_fix = 100';

t/04-result.t  view on Meta::CPAN

use strict;
use warnings;
use Test::More;

use_ok('Claude::Agent::Code::Refactor::Result');

subtest 'default values' => sub {
    my $result = Claude::Agent::Code::Refactor::Result->new();

    is($result->success, 0, 'default success is 0');
    is($result->iterations, 0, 'default iterations is 0');
    is($result->initial_issues, 0, 'default initial_issues is 0');
    is($result->final_issues, 0, 'default final_issues is 0');
    is($result->fixes_applied, 0, 'default fixes_applied is 0');
    is_deeply($result->files_modified, [], 'default files_modified is empty');
    is_deeply($result->history, [], 'default history is empty');
    is($result->duration_ms, 0, 'default duration_ms is 0');
    is($result->error, undef, 'default error is undef');
};

subtest 'is_clean' => sub {

t/04-result.t  view on Meta::CPAN


subtest 'add_iteration' => sub {
    my $result = Claude::Agent::Code::Refactor::Result->new();

    $result->add_iteration(
        issues_found   => 10,
        issues_fixed   => 5,
        files_modified => ['lib/Foo.pm', 'lib/Bar.pm'],
    );

    is($result->iterations, 1, 'iterations incremented');
    is($result->fixes_applied, 5, 'fixes_applied updated');
    is_deeply($result->files_modified, ['lib/Foo.pm', 'lib/Bar.pm'], 'files tracked');
    is(scalar(@{$result->history}), 1, 'history has one entry');
    is($result->history->[0]{iteration}, 1, 'iteration number recorded');
    is($result->history->[0]{issues_found}, 10, 'issues_found recorded');
    is($result->history->[0]{issues_fixed}, 5, 'issues_fixed recorded');

    $result->add_iteration(
        issues_found   => 5,
        issues_fixed   => 3,
        files_modified => ['lib/Bar.pm', 'lib/Baz.pm'],
    );

    is($result->iterations, 2, 'iterations incremented again');
    is($result->fixes_applied, 8, 'fixes_applied accumulated');
    is_deeply(
        [sort @{$result->files_modified}],
        ['lib/Bar.pm', 'lib/Baz.pm', 'lib/Foo.pm'],
        'files deduplicated'
    );
    is(scalar(@{$result->history}), 2, 'history has two entries');
};

subtest 'as_text' => sub {

t/04-result.t  view on Meta::CPAN

    );
    $result->add_iteration(
        issues_found   => 10,
        issues_fixed   => 8,
        files_modified => ['lib/Foo.pm'],
    );

    my $hash = $result->to_hash;

    is($hash->{success}, 1, 'success in hash');
    is($hash->{iterations}, 1, 'iterations in hash');
    is($hash->{initial_issues}, 10, 'initial_issues in hash');
    is($hash->{final_issues}, 2, 'final_issues in hash');
    is($hash->{fixes_applied}, 8, 'fixes_applied in hash');
    is($hash->{fix_rate}, 80, 'fix_rate in hash');
    is_deeply($hash->{files_modified}, ['lib/Foo.pm'], 'files_modified in hash');
    is($hash->{duration_ms}, 3000, 'duration_ms in hash');
    ok(!exists $hash->{error}, 'no error key when no error');
};

subtest 'to_hash with error' => sub {

t/05-edge-cases.t  view on Meta::CPAN

    } qr/Unknown target type/, 'rejects non-existent path';
};

subtest 'Result iteration with missing values' => sub {
    my $result = Claude::Agent::Code::Refactor::Result->new();

    lives_ok {
        $result->add_iteration();
    } 'add_iteration works with no arguments';

    is($result->iterations, 1, 'iteration counted');
    is($result->fixes_applied, 0, 'fixes_applied defaults to 0');
    is_deeply($result->files_modified, [], 'files_modified defaults to empty');
    is($result->history->[0]{issues_found}, 0, 'issues_found defaults to 0');
    is($result->history->[0]{issues_fixed}, 0, 'issues_fixed defaults to 0');
};

subtest 'Result multiple iterations tracking' => sub {
    my $result = Claude::Agent::Code::Refactor::Result->new();

    for my $i (1..5) {
        $result->add_iteration(
            issues_found   => 10 - $i,
            issues_fixed   => 2,
            files_modified => ["file$i.pm"],
        );
    }

    is($result->iterations, 5, '5 iterations recorded');
    is($result->fixes_applied, 10, 'total fixes accumulated');
    is(scalar(@{$result->files_modified}), 5, 'all files tracked');
    is(scalar(@{$result->history}), 5, 'history has 5 entries');

    for my $i (0..4) {
        is($result->history->[$i]{iteration}, $i + 1, "iteration " . ($i+1) . " numbered correctly");
    }
};

subtest 'Options all severities' => sub {



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