NAME

App::GHGen::Analyzer - Analyze GitHub Actions workflows

SYNOPSIS

use App::GHGen::Analyzer qw(analyze_workflow);

my @issues = analyze_workflow($workflow_hashref, 'ci.yml');

FUNCTIONS

find_workflows()

Find all workflow files in .github/workflows directory. Returns a sorted list of Path::Tiny objects.

Purpose

Discover every GitHub Actions workflow file under .github/workflows in the current working directory.

Arguments

None.

Returns

A sorted list of Path::Tiny objects, one per .yml or .yaml file found. Returns an empty list when the directory does not exist or is empty.

Side Effects

None. Performs read-only filesystem access.

Usage Example

use App::GHGen::Analyzer qw(find_workflows);
chdir '/path/to/repo';
my @wfs = find_workflows();
say $_->basename for @wfs;

API SPECIFICATION

Input

# No parameters.

Output

{ type => 'array', element => { isa => 'Path::Tiny' } }

FORMAL SPECIFICATION

FindWorkflows : → seq Path

dir ≔ .github/workflows
FindWorkflows ≡
    ¬dir.exists ∨ ¬dir.is_dir  →  ⟨⟩
    | otherwise  →  sort(lex) { f ∈ dir.children ∣ f.name =~ /\.ya?ml$/i }

analyze_workflow($workflow, $filename)

Analyze a workflow hash for issues. Returns array of issue hashes.

Each issue has: type, severity, message, and an optional fix.

Purpose

Inspect a parsed GitHub Actions workflow and return a list of detected issues covering performance, security, cost, and maintenance concerns.

Arguments

$workflow (HashRef, required)

A hash reference representing the parsed workflow (e.g. from YAML::XS::LoadFile). Must contain at least a jobs key whose value is a hash of job definitions.

$filename (Str, required)

The filename of the workflow, used only as context in issue messages.

Returns

A list (not an array reference) of issue hash references. Each issue has:

{
    type     => Str,  # 'performance' | 'security' | 'cost' | 'maintenance'
    severity => Str,  # 'high' | 'medium' | 'low'
    message  => Str,
    fix      => Str,  # optional: suggested YAML snippet
}

Returns an empty list when no issues are detected.

Side Effects

None. Pure function; does not modify its arguments.

Usage Example

use App::GHGen::Analyzer qw(analyze_workflow);
use YAML::XS qw(LoadFile);

my $wf     = LoadFile('.github/workflows/ci.yml');
my @issues = analyze_workflow($wf, 'ci.yml');
for my $issue (@issues) {
    say "$issue->{severity}: $issue->{message}";
}

API SPECIFICATION

Input

{
    workflow => { type => 'hashref', required => 1 },
    filename => { type => 'scalar',  required => 1 },
}

Output

{
    type    => 'array',
    element => {
        type => 'hashref',
        keys => {
            type     => { type => 'scalar' },
            severity => { type => 'scalar' },
            message  => { type => 'scalar' },
            fix      => { type => 'scalar', optional => 1 },
        },
    },
}

FORMAL SPECIFICATION

Issue ≔ { type: IssueType, severity: Severity, message: ℤ*, fix?: ℤ* }
IssueType  ≔ performance | security | cost | maintenance
Severity   ≔ high | medium | low

analyze_workflow : Workflow × ℤ* → seq Issue

∀ w: Workflow, f: ℤ*:
  ¬has_caching(w)           ⇒ performance∈medium ∈ result(w,f)
  find_unpinned_actions(w)≠∅ ⇒ security∈high     ∈ result(w,f)
  find_outdated_actions(w)≠∅ ⇒ maintenance∈medium ∈ result(w,f)
  has_broad_triggers(w)     ⇒ cost∈medium        ∈ result(w,f)
  ¬w.concurrency            ⇒ cost∈low           ∈ result(w,f)
  has_outdated_runners(w)   ⇒ maintenance∈low    ∈ result(w,f)
  ∃ j∈w.jobs: ¬j.timeout-minutes ⇒ performance∈low ∈ result(w,f)

get_cache_suggestion($workflow)

Generate a caching suggestion based on detected project type.

Purpose

Inspect a workflow for known package-manager commands and return a ready-to-paste actions/cache YAML snippet that matches the detected ecosystem.

Arguments

$workflow (HashRef, required)

A parsed workflow hash. The function inspects each job's steps[].run commands to detect npm, pip, cargo, or bundle.

Returns

A non-empty string. When the ecosystem is recognised the string is an actions/cache YAML step snippet; otherwise a generic guidance message.

Side Effects

None. Pure function.

Usage Example

my $suggestion = get_cache_suggestion($workflow);
say $suggestion;

API SPECIFICATION

Input

{ workflow => { type => 'hashref', required => 1 } }

Output

{ type => 'scalar' }

FORMAL SPECIFICATION

Ecosystem ≔ npm | pip | cargo | bundler | unknown

get_cache_suggestion : Workflow → ℤ*

ecosystem(w) = npm     ⇒ result contains ~/.npm cache path
ecosystem(w) = pip     ⇒ result contains ~/.cache/pip path
ecosystem(w) = cargo   ⇒ result contains ~/.cargo path
ecosystem(w) = bundler ⇒ result contains vendor/bundle path
ecosystem(w) = unknown ⇒ result is a generic guidance message

AUTHOR

Nigel Horne <njh@nigelhorne.com>

https://github.com/nigelhorne

LICENCE

Copyright 2025-2026 Nigel Horne.

Usage is subject to license terms.

1 POD Error

The following errors were encountered while parsing the POD:

Around line 73:

Non-ASCII character seen before =encoding in '→'. Assuming UTF-8