Skip to content

feat(ci): add YAML and GitHub Actions lint analysis for PRs#148

Open
bedatty wants to merge 47 commits intodevelopfrom
feat/pr-analysis-lint
Open

feat(ci): add YAML and GitHub Actions lint analysis for PRs#148
bedatty wants to merge 47 commits intodevelopfrom
feat/pr-analysis-lint

Conversation

@bedatty
Copy link
Contributor

@bedatty bedatty commented Mar 17, 2026

Lerian

GitHub Actions Shared Workflows


Description

Add a self-pr-analysis workflow that runs yamllint and actionlint on every pull request targeting develop or main. This ensures YAML syntax correctness and GitHub Actions workflow validity before merge.

  • yamllint — validates all .yml files against .yamllint.yml config (relaxed truthy for on:, 200-char line limit, flexible indentation)
  • actionlint — validates GitHub Actions workflow syntax (expressions, inputs, secrets, runner labels, etc.)

Also adds:

  • .yamllint.yml config at repo root
  • linting group in Dependabot for ibiqlik/action-yamllint and raven-actions/actionlint
  • lint label in .github/labels.yml

Type of Change

  • ci: Changes to self-CI (workflows under .github/workflows/ that run on this repo)

Breaking Changes

None.

Testing

  • YAML syntax validated locally
  • Triggered a real workflow run on a caller repository using @develop or the beta tag
  • Verified all existing inputs still work with default values
  • Confirmed no secrets or tokens are printed in logs
  • Checked that unrelated workflows are not affected

Caller repo / workflow run: N/A — self-CI workflow, will validate on this PR's own checks.

Related Issues

N/A

Summary by CodeRabbit

  • Chores

    • Added PR validation workflow with changed-file detection and conditional lint jobs.
    • Introduced a Dependabot group for lint/code-quality updates and adjusted update patterns.
    • Added YAML linter and markdown-link-check configurations, a self-hosted runner label, and a new "lint" label.
  • New Features

    • Added reusable lint actions for YAML, Actions syntax, markdown links, pinned-actions, spelling, shell checks, README presence, and composite-schema validation.
    • Added a changed-files detector and a PR lint reporter that posts consolidated lint summaries to PRs.
  • Documentation

    • Added README/docs and usage examples for each lint action.

Add self-pr-analysis workflow that runs yamllint and actionlint
on pull requests to validate YAML syntax and GitHub Actions
workflow correctness.
@bedatty bedatty requested a review from a team as a code owner March 17, 2026 17:25
@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

Walkthrough

Adds Dependabot lint group and a new lint label; introduces YAML/actionlint/markdown-link-check configs; many new composite lint actions and their READMEs; a changed-files detector; a PR lint reporter; and a self PR‑validation workflow wiring these components.

Changes

Cohort / File(s) Summary
Repo config & linters
\.yamllint\.yml, .github/markdown-link-check-config.json, .github/actionlint.yaml
Add YAML lint configuration, markdown-link-check settings (ignore patterns, headers, timeout/retries, aliveStatusCodes), and actionlint runner label.
Dependabot & labels
.github/dependabot.yml, .github/labels.yml
Add a Dependabot "lint/code-quality" group with patterns for action linters and typos; remove markdown-link-check from utilities group; add lint label.
Top-level workflow
.github/workflows/self-pr-validation.yml
New workflow to detect changed files, conditionally run multiple lint jobs, and aggregate results into the PR lint reporter.
Changed-files detector
src/config/changed-workflows/action.yml, src/config/changed-workflows/README.md
Composite action that enumerates PR or repo files and emits categorized outputs (yaml-files, workflow-files, action-files, composite-files, markdown-files, all-files).
Aggregate reporter
src/notify/pr-lint-reporter/action.yml, src/notify/pr-lint-reporter/README.md
Composite action that builds a Markdown lint summary, fetches failure annotations from failed jobs, and upserts a PR comment.
YAML & actionlint linters
src/lint/yamllint/action.yml, src/lint/yamllint/README.md, src/lint/actionlint/action.yml, src/lint/actionlint/README.md
Composite actions/docs to run yamllint and actionlint with configurable inputs and file logging.
Markdown link check
src/lint/markdown-link-check/action.yml, src/lint/markdown-link-check/README.md
Composite action delegating to markdown-link-check with default config .github/markdown-link-check-config.json.
Pinned-actions check
src/lint/pinned-actions/action.yml, src/lint/pinned-actions/README.md
Scan uses:...@ refs, classify internal warn-patterns, warn for internal pre-releases, and fail on external non-final refs.
Spelling (typos) check
src/lint/typos/action.yml, src/lint/typos/README.md
Composite action running crate-ci/typos with configurable config/files and summary reporting.
Shellcheck runner
src/lint/shellcheck/action.yml, src/lint/shellcheck/README.md
Extract run blocks from workflows/composites, normalize expressions, run shellcheck, emit annotations and fail on violations.
README presence check
src/lint/readme-check/action.yml, src/lint/readme-check/README.md
Verify each src/*/action.yml has a sibling README.md; fail when missing.
Composite schema linter
src/lint/composite-schema/action.yml, src/lint/composite-schema/README.md
Validate composite action manifests under src/ (path conventions, name/description, step limit, inputs schema, reserved prefixes), emit annotations and fail on violations.
Actions metadata & docs
src/lint/*/action.yml, src/lint/*/README.md (new files under src/lint/*)
Add composite action manifests and READMEs for the linting components used by the workflow (action metadata, inputs, usage examples).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding YAML and GitHub Actions lint analysis for PRs, which matches the primary objective of introducing yamllint and actionlint validation.
Description check ✅ Passed The description follows the template structure, includes all required sections (Type of Change, Breaking Changes, Testing, Related Issues), clearly explains what was added and why, and marks the applicable change type.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/pr-analysis-lint
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@bedatty bedatty self-assigned this Mar 17, 2026
coderabbitai[bot]
coderabbitai bot previously requested changes Mar 17, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.yamllint.yml:
- Around line 4-5: The yamllint configuration uses a glob "yaml-files: -
\"*.yml\"" that only matches root-level YAML files; change the pattern to a
recursive glob by replacing the value "*.yml" with "**/*.yml" so
README/workflows and composite actions (e.g., files under .github/workflows/ and
src/) are included in linting; update the "yaml-files" entry in .yamllint.yml
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 64c7aa2f-68e7-4587-b8fc-09ebd2080306

📥 Commits

Reviewing files that changed from the base of the PR and between 3ac4d79 and 274b868.

📒 Files selected for processing (4)
  • .github/dependabot.yml
  • .github/labels.yml
  • .github/workflows/self-pr-analysis.yml
  • .yamllint.yml

Filter both linters to only check files modified in the PR instead
of scanning the entire repository. Falls back to full scan on
workflow_dispatch.
coderabbitai[bot]
coderabbitai bot previously requested changes Mar 17, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/self-pr-analysis.yml:
- Around line 30-36: The PR diff may include deleted/renamed paths, so filter
the list from gh pr diff --name-only to only those that exist in the checked-out
workspace before assigning to the files output; update both places where you
build the files variable (the branches that set files=$(gh pr diff --name-only
...) and the later block around lines referencing the same pattern) to pipe the
name list through a check for file existence (e.g., test -e or a small loop) and
only include existing filenames, then write that filtered list to GITHUB_OUTPUT
as the files variable.
- Around line 18-72: The self-entrypoint currently contains the linting business
logic inside jobs yamllint and actionlint (including the "Get changed YAML
files" and "Get changed workflow files" steps); extract that logic into a
reusable workflow file (e.g., create or update
.github/workflows/self-pr-analysis-reusable.yml) that defines the yamllint and
actionlint jobs and their steps, and then simplify this self-pr-analysis.yml to
a thin wrapper that uses workflow_call to call the local reusable workflow
(./.github/workflows/<reusable-name>.yml), keeping only the minimal
inputs/outputs and the uses: './.github/workflows/<reusable-name>.yml'
invocation; ensure job names (yamllint, actionlint) and step IDs (changed)
remain consistent so callers continue to work.
- Around line 30-32: The grep in the changed-file collectors (the command
assigning files=$(gh pr diff ... | grep -E '\.yml$' | tr '\n' ' ' | sed 's/
$//') and the similar collector later) can return non-zero when there are no
matches and cause the step to fail; change the pipeline to tolerate no matches
by making grep non-fatal (e.g., use grep -E '\.yml$' || true or grep -E '\.yml$'
|| : directly in the pipeline) so the subsequent tr/sed still run and yield an
empty string, ensuring the job cleanly skips when there are no matching changed
files.
- Line 1: The self-pr-analysis.yml contains business logic (yamllint/actionlint)
and must be replaced by a call to a reusable workflow: extract the existing jobs
into a new reusable workflow file named pr-analysis.yml that defines
workflow_call inputs and contains the yamllint and actionlint jobs (preserving
job names and any required secrets/permissions), then change
self-pr-analysis.yml to be a thin wrapper that invokes the reusable workflow
using uses: ./path/to/pr-analysis.yml (passing through any needed
inputs/secrets) and remove all direct job definitions from self-pr-analysis.yml
so it only delegates to pr-analysis.yml.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2cdcd2a5-4629-461e-b5ca-7b186ff7844e

📥 Commits

Reviewing files that changed from the base of the PR and between 274b868 and 29ec34c.

📒 Files selected for processing (1)
  • .github/workflows/self-pr-analysis.yml

bedatty added 2 commits March 17, 2026 14:36
Add actionlint config with blacksmith-4vcpu-ubuntu-2404 as a known
self-hosted runner label to avoid false-positive runner-label errors.
…elf-pr-validation

- Pinned Actions Check: fails on uses: @main/@master (skips LerianStudio)
- Markdown Link Check: validates links in changed .md files
- Spelling Check: typos-cli across the codebase
- self-pr-validation: thin entrypoint calling pr-validation.yml
  with source branch enforcement for PRs to main
- Add .github/markdown-link-check-config.json
- Update dependabot groups for new actions
Consolidate all PR checks into a single self-pr-validation workflow:
PR validation, YAML lint, actionlint, pinned actions, markdown link
check, and spelling. Remove self-pr-analysis.yml.
coderabbitai[bot]
coderabbitai bot previously requested changes Mar 17, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/self-pr-validation.yml:
- Around line 42-49: The file discovery only matches ".yml" because the grep
pattern used when building the files variable (the command with grep -E
'\.yml$') omits ".yaml" files; update that grep invocation to match both
extensions (e.g., use a regex like '\.(yml|yaml)$' or '\.ya?ml$') so the files
variable includes both .yml and .yaml files when constructing the list for the
pull_request branch.
- Around line 71-78: The workflow only matches .yml files; update the file
selection so it includes both .yml and .yaml and follows the pinned-actions
pattern: when constructing the PR diff list change the grep pattern used to
build files to match both extensions (use the same '\.ya?ml$' style regex
referenced in the diff) and change the default branch case to include both
extensions (e.g., set files to match both .yml and .yaml instead of only
".github/workflows/*.yml"); keep the rest of the logic that writes to the
GITHUB_OUTPUT variable and the use of
github.event_name/github.event.pull_request.number unchanged.
- Around line 113-133: The run step uses bash-specific process substitution (<
<(grep -n ...)) inside the block that defines the loop (while IFS= read -r line;
do ... done < <(grep -n ...)), so make the step explicitly use bash by adding
shell: bash to that run step in the workflow; this ensures the
process-substitution and the loop/IFS/read logic work reliably across runners
and avoids sh/bash default drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9a4804b6-6de9-4910-9981-2cf56ae4238e

📥 Commits

Reviewing files that changed from the base of the PR and between 4e446f5 and 49cd047.

📒 Files selected for processing (1)
  • .github/workflows/self-pr-validation.yml

Create 5 composite actions under src/lint/:
- yamllint: YAML syntax validation
- actionlint: GitHub Actions workflow validation
- pinned-actions: ensures uses: refs are pinned
- markdown-link-check: validates links in .md files
- typos: spelling check via typos-cli

Refactor self-pr-validation to use composites directly with a
shared changed-files detection job for all lint checks.
coderabbitai[bot]
coderabbitai bot previously requested changes Mar 17, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/self-pr-validation.yml:
- Around line 61-64: Combine the four echo lines that append to the same output
file into a single grouped redirection to reduce repeated opens; update the
block that currently echoes "yaml_files=${yaml_files}",
"workflow_files=${workflow_files}", "action_files=${action_files}", and
"markdown_files=${markdown_files}" so they are written in one grouped redirect
(using { ... } >> "$GITHUB_OUTPUT") instead of four separate echo >>
"$GITHUB_OUTPUT" calls.

In `@src/lint/pinned-actions/action.yml`:
- Line 41: The current grep check (done < <(grep -n 'uses:.*@\(main\|master\)'
"$file" 2>/dev/null || true)) intentionally only flags `@main/`@master; either
tighten the check or document the allowed exceptions: to enforce SHA-only
pinning, expand the grep to also match semantic version tags (e.g. change the
pattern to include v<semver> like
'uses:.*@\(main\|master\|v[0-9]\+\(\.[0-9]\+\)*\)' or a more robust semver
regex), or update the action description to explicitly state that semantic
version tags (e.g. `@v3`, `@v3.1.1`) are acceptable exceptions while disallowing
branch pins.

In `@src/lint/typos/action.yml`:
- Line 14: The workflow step currently pins the GitHub Action using a mutable
tag ("uses: crate-ci/typos@v1.44.0"); replace that tag with the immutable commit
SHA for the corresponding release (i.e., update the "uses:
crate-ci/typos@v1.44.0" entry to "uses: crate-ci/typos@<COMMIT_SHA>") so the
action is pinned to a specific commit and the PR validation "Unpinned action
found" error is resolved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 71047d0a-3685-4b55-aad2-09f653333905

📥 Commits

Reviewing files that changed from the base of the PR and between 49cd047 and b7f000d.

📒 Files selected for processing (11)
  • .github/workflows/self-pr-validation.yml
  • src/lint/actionlint/README.md
  • src/lint/actionlint/action.yml
  • src/lint/markdown-link-check/README.md
  • src/lint/markdown-link-check/action.yml
  • src/lint/pinned-actions/README.md
  • src/lint/pinned-actions/action.yml
  • src/lint/typos/README.md
  • src/lint/typos/action.yml
  • src/lint/yamllint/README.md
  • src/lint/yamllint/action.yml

…tion

Create src/config/changed-workflows composite that categorizes
changed files by type (YAML, workflows, actions, markdown) for
downstream lint jobs. Refactor self-pr-validation to use it.
@lerian-studio
Copy link

This PR is very large (23 files, 1353 lines changed). Consider breaking it into smaller PRs for easier review.

@lerian-studio
Copy link

This PR is very large (24 files, 1412 lines changed). Consider breaking it into smaller PRs for easier review.

@lerian-studio
Copy link

This PR is very large (24 files, 1412 lines changed). Consider breaking it into smaller PRs for easier review.

@lerian-studio
Copy link

This PR is very large (24 files, 1412 lines changed). Consider breaking it into smaller PRs for easier review.

coderabbitai[bot]
coderabbitai bot previously requested changes Mar 18, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lint/shellcheck/action.yml`:
- Around line 9-12: The action currently only increments violations when level
== 'error' and ignores non-JSON failures; update the result-handling logic so
that (1) you map severity strings to a priority order (e.g., {style:0, info:1,
warning:2, error:3}), read the configured input severity, and when iterating
parsed shellcheck findings increment the violations counter for any finding
whose level priority is >= configured minimum (use the existing level variable
from parsed items); and (2) handle non-JSON runs by detecting an empty
result.stdout and treating that as a failure: increment violations, include
result.stderr (or stdout) text in the annotations/logs, and fail the action
accordingly. Ensure you update references to the existing violations variable
and use result.stdout/result.stderr parsing locations so behavior is consistent
for both JSON and non-JSON outputs.
- Around line 89-105: The code always forces bash by prefixing scripts with
'#!/usr/bin/env bash' and passing '--shell=bash' to shellcheck; change both to
use the actual shell variable (shell) from the step so steps with 'shell: sh'
are handled correctly: set the shebang using the shell value (e.g.,
'#!/usr/bin/env ' + shell) when building script and replace the hardcoded
'--shell=bash' in the subprocess.run argument with f'--shell={shell}'
(references: the variables shell, step, script and the subprocess.run call).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1839affe-91ad-4887-a748-c18a314202ff

📥 Commits

Reviewing files that changed from the base of the PR and between c489cdb and ed313b6.

📒 Files selected for processing (6)
  • .github/workflows/self-pr-validation.yml
  • src/lint/readme-check/README.md
  • src/lint/readme-check/action.yml
  • src/lint/shellcheck/README.md
  • src/lint/shellcheck/action.yml
  • src/notify/pr-lint-reporter/action.yml

@lerian-studio
Copy link

This PR is very large (24 files, 1412 lines changed). Consider breaking it into smaller PRs for easier review.

@lerian-studio
Copy link

This PR is very large (24 files, 1416 lines changed). Consider breaking it into smaller PRs for easier review.

coderabbitai[bot]
coderabbitai bot previously requested changes Mar 18, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lint/shellcheck/action.yml`:
- Around line 57-59: The regex in replace_gha_exprs fails for GHA expressions
with nested braces because r'\$\{\{[^}]*\}\}' stops at the first '}', so update
the pattern used in the re.sub inside replace_gha_exprs to use a non-greedy
match (e.g., change the inner token from [^}]* to a non-greedy .*? ) so
expressions like ${{ format('{0}', x) }} are fully matched and replaced; keep
the replacement string '${GHA_PLACEHOLDER}' and ensure the re.sub call uses the
new pattern.
- Around line 121-123: ShellCheck line numbers are offset by one because the
temporary file includes a prepended shebang; adjust the reported line before
printing annotations by decrementing the retrieved line number by 1 (but clamp
to a minimum of 1) so the annotation reflects the original run: block; apply
this change where line is computed (the variable line used later in the print
call) so the printed annotation (using filepath, step_name, code, msg, ann)
reports the corrected line.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3540787f-2419-409a-844a-667b902cb4ce

📥 Commits

Reviewing files that changed from the base of the PR and between 7906317 and 24a5ddf.

📒 Files selected for processing (1)
  • src/lint/shellcheck/action.yml

@lerian-studio
Copy link

This PR is very large (24 files, 1416 lines changed). Consider breaking it into smaller PRs for easier review.

@lerian-studio
Copy link

This PR is very large (26 files, 1592 lines changed). Consider breaking it into smaller PRs for easier review.

coderabbitai[bot]
coderabbitai bot previously requested changes Mar 19, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lint/composite-schema/action.yml`:
- Around line 73-91: The inputs loop is silently skipping malformed scalar
entries and will crash on a null description due to .strip(); update the logic
around inputs = data.get('inputs') and the for name, spec in inputs.items():
block so that non-dict specs are treated as violations (print an error and
increment violations rather than continue), guard description access by using
(spec.get('description') or '').strip() when computing has_desc, and ensure the
final branch that prints the required=false/no-default error also increments
violations; refer to the variables/locations: inputs, spec, has_desc, required,
has_default, violations and the for-name loop to make these changes.

In `@src/lint/composite-schema/README.md`:
- Around line 22-43: Add a "Usage as reusable workflow" section to
src/lint/composite-schema/README.md alongside the existing "Usage as composite
step" example: include a complete GitHub Actions workflow YAML that demonstrates
calling the composite as a reusable workflow (uses:
LerianStudio/github-actions-shared-workflows/src/lint/composite-schema@develop),
include the required permissions block, and explicitly show "secrets: inherit"
in the caller workflow so the README meets the composite-action template
guidelines.

In `@src/notify/pr-lint-reporter/action.yml`:
- Around line 81-129: The JS literals in the checks array (entries like jobName
'YAML Lint', 'Action Lint', 'Pinned Actions', 'Markdown Link Check', 'Spelling
Check', 'Shell Check', 'README Check', 'Composite Schema') directly interpolate
inputs.* into single-quoted strings which can break on apostrophes/backslashes;
update each files: and entireRepo: interpolation to use a safely escaped JSON
form (e.g., toJSON(inputs['yamllint-files']) or env-mapped values) so strings
are properly escaped before .trim().split(...).filter(Boolean) is called and
ensure the same pattern is applied for all '*-files' inputs referenced in the
checks array.

In `@src/notify/pr-lint-reporter/README.md`:
- Around line 32-62: The README's "Usage as composite step" example only
supplies the result inputs (yamllint-result, actionlint-result,
shellcheck-result, readme-result, composite-schema-result, github-token) which
yields empty file lists and misleading "Files Scanned" output; update that
section to a full composite-step YAML that also passes the corresponding file
list inputs (e.g., yamllint-files, actionlint-files, shellcheck-files,
readme-files, composite-schema-files or whatever file-list input names the
action defines) and any other required inputs, and add the missing "Usage as
reusable workflow" section with a complete workflow YAML that calls
LerianStudio/github-actions-shared-workflows/src/notify/pr-lint-reporter@develop
and includes secrets: inherit and proper permissions (pull-requests: write,
issues: write, checks: read) so the reusable example is complete and shows real
file lists.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dc49c857-c79a-4b4b-814e-e8e0bea5a0e9

📥 Commits

Reviewing files that changed from the base of the PR and between 6ade279 and 81f4a9d.

📒 Files selected for processing (5)
  • .github/workflows/self-pr-validation.yml
  • src/lint/composite-schema/README.md
  • src/lint/composite-schema/action.yml
  • src/notify/pr-lint-reporter/README.md
  • src/notify/pr-lint-reporter/action.yml

@lerian-studio
Copy link

This PR is very large (26 files, 1640 lines changed). Consider breaking it into smaller PRs for easier review.

coderabbitai[bot]
coderabbitai bot previously requested changes Mar 19, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/lint/composite-schema/README.md (1)

36-57: ⚠️ Potential issue | 🟡 Minor

Missing "Usage as reusable workflow" section.

The README documents composite step usage but omits the reusable workflow form required by repo conventions.

As per coding guidelines, README.md in composite actions must include... usage as reusable workflow (full YAML with secrets: inherit).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lint/composite-schema/README.md` around lines 36 - 57, Add a "Usage as
reusable workflow" section to src/lint/composite-schema/README.md showing the
reusable-workflow YAML pattern required by repo conventions: include the
workflow invocation with uses: LerianStudio/.../lint/composite-schema@develop
(or the correct ref), the jobs calling the reusable workflow, and include the
required permissions and secrets: inherit entry; ensure the section mirrors the
composite step example but in reusable-workflow form and explicitly shows
"secrets: inherit".
src/lint/composite-schema/action.yml (1)

95-98: ⚠️ Potential issue | 🟠 Major

Malformed input specs are silently skipped instead of flagged as violations.

If inputs in a composite action is a list (not a mapping), line 96 will raise AttributeError on .items(). Non-dict spec entries are also silently ignored rather than reported.

Proposed fix
             # ── Input conventions ──
             inputs = data.get('inputs') or {}
+            if not isinstance(inputs, dict):
+                err(filepath, '"inputs" must be a mapping, not a scalar or list.')
+                continue
+
             for name, spec in inputs.items():
                 if not isinstance(spec, dict):
-                    continue
+                    err(filepath, f'Input "{name}" definition must be a mapping.')
+                    continue
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lint/composite-schema/action.yml` around lines 95 - 98, The loop over
inputs blindly calls data.get('inputs') and then .items(), which will raise
AttributeError if inputs is a list and currently silently skips non-dict specs;
update the logic to first validate that inputs is a mapping (e.g.,
isinstance(inputs, dict)) and emit a violation if it is not, and when iterating
over inputs ensure each spec is validated (emit a violation for any non-dict
spec entry instead of continue) so malformed input specs are reported rather
than ignored; refer to the inputs variable and the for name, spec in
inputs.items() loop when making these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/self-pr-validation.yml:
- Around line 12-32: The validation job is being invoked for manual
workflow_dispatch runs which lack pull_request context; modify the job named
"validation" to guard it so it only runs for pull_request events (i.e., add an
if that checks github.event_name == 'pull_request') before it calls the reusable
workflow .github/workflows/pr-validation.yml so the PR-specific context fields
are defined.

---

Duplicate comments:
In `@src/lint/composite-schema/action.yml`:
- Around line 95-98: The loop over inputs blindly calls data.get('inputs') and
then .items(), which will raise AttributeError if inputs is a list and currently
silently skips non-dict specs; update the logic to first validate that inputs is
a mapping (e.g., isinstance(inputs, dict)) and emit a violation if it is not,
and when iterating over inputs ensure each spec is validated (emit a violation
for any non-dict spec entry instead of continue) so malformed input specs are
reported rather than ignored; refer to the inputs variable and the for name,
spec in inputs.items() loop when making these changes.

In `@src/lint/composite-schema/README.md`:
- Around line 36-57: Add a "Usage as reusable workflow" section to
src/lint/composite-schema/README.md showing the reusable-workflow YAML pattern
required by repo conventions: include the workflow invocation with uses:
LerianStudio/.../lint/composite-schema@develop (or the correct ref), the jobs
calling the reusable workflow, and include the required permissions and secrets:
inherit entry; ensure the section mirrors the composite step example but in
reusable-workflow form and explicitly shows "secrets: inherit".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: aa270591-302c-4958-99f8-59446190b34e

📥 Commits

Reviewing files that changed from the base of the PR and between 81f4a9d and cba4fd8.

📒 Files selected for processing (4)
  • .github/workflows/self-pr-validation.yml
  • src/config/changed-workflows/action.yml
  • src/lint/composite-schema/README.md
  • src/lint/composite-schema/action.yml

@lerian-studio
Copy link

This PR is very large (26 files, 1668 lines changed). Consider breaking it into smaller PRs for easier review.

coderabbitai[bot]
coderabbitai bot previously requested changes Mar 19, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/self-pr-validation.yml:
- Around line 183-197: The workflow permissions block for the reporter job is
missing the actions scope so the reporter step (named "Post Lint Report" which
invokes src/notify/pr-lint-reporter and calls actions.listJobsForWorkflowRun())
gets a token that causes 403s; add actions: read to the permissions list
alongside contents: read, pull-requests: write, issues: write, checks: read so
the reporter can call actions.listJobsForWorkflowRun successfully.

In `@src/lint/composite-schema/action.yml`:
- Around line 69-74: The validator currently silently skips or will later crash
on malformed action.yml entries; instead, when the top-level `data` is not a
dict or when `runs` is missing or `runs.get('using') != 'composite'`, treat this
as a validation failure (emit an annotation/error) rather than `continue`;
similarly validate that `runs` is a mapping and that `runs.get('steps')` is a
list of mappings before accessing `len(steps)` or iterating — update the checks
surrounding the `data`, `runs`, and `steps` variables (and the analogous block
at the later occurrence around lines 88-92) to raise/record a clear validation
error when types/values are wrong so malformed manifests fail with a helpful
message.
- Around line 81-85: The current checks use str(data.get('name')).strip() and
str(data.get('description')).strip(), which treat null/None as the string
"None"; update the validations to require a non-empty string: ensure
data.get('name') is an instance of str and that data.get('name').strip() is not
empty (call err(filepath, 'Missing top-level "name" field.') otherwise), and do
the same for data.get('description') (calling err(filepath, 'Missing top-level
"description" field.') when missing). Use the existing err(...) function and the
data.get(...) calls to locate where to change the checks.

In `@src/notify/pr-lint-reporter/action.yml`:
- Around line 5-7: Update the github-token input description to include the
missing actions:read permission: the github-token input (named "github-token")
currently documents pull-requests:write, issues:write and checks:read but must
also mention actions:read because the code calls
github.rest.actions.listJobsForWorkflowRun() when reporting failure details;
change the description text for github-token to list actions:read alongside the
other scopes so callers know to provide that permission.

In `@src/notify/pr-lint-reporter/README.md`:
- Around line 38-63: The example job's needs array is missing the dependent job
"changed-files" while the step POST Lint Report references
needs.changed-files.outputs.*, so update the job's needs declaration (the needs
array used in the YAML snippet) to include "changed-files" alongside yamllint,
actionlint, pinned-actions, markdown-link-check, typos, shellcheck,
readme-check, and composite-schema so that the outputs referenced
(needs.changed-files.outputs.yaml_files, workflow_files, action_files,
markdown_files, all_files, composite_files) are available at runtime.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 37850669-c962-4e8c-a564-a9bce863df30

📥 Commits

Reviewing files that changed from the base of the PR and between cba4fd8 and 152be7c.

📒 Files selected for processing (4)
  • .github/workflows/self-pr-validation.yml
  • src/lint/composite-schema/action.yml
  • src/notify/pr-lint-reporter/README.md
  • src/notify/pr-lint-reporter/action.yml

@lerian-studio
Copy link

This PR is very large (26 files, 1678 lines changed). Consider breaking it into smaller PRs for easier review.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/lint/composite-schema/action.yml (1)

73-78: ⚠️ Potential issue | 🟠 Major

Fail non-composite manifests instead of skipping them.

runs.using != 'composite' currently falls through continue, so a wrong action type under src/**/action.yml passes this check entirely.

Suggested fix
             runs = data.get('runs')
             if not isinstance(runs, dict):
                 err(filepath, '"runs" must be a mapping.')
                 continue
             if runs.get('using') != 'composite':
-                continue
+                err(filepath, 'Composite actions must declare "runs.using: composite".')
+                continue
As per coding guidelines, `src/**/action.yml`: `This is a composite action in a shared CI/CD library.`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lint/composite-schema/action.yml` around lines 73 - 78, The code
currently skips manifests whose runs.using is not "composite"; change this to
report an error instead: in the block that reads runs = data.get('runs') and
checks isinstance(runs, dict), replace the continue when runs.get('using') !=
'composite' with a call to err(...) (include filepath and a clear message like
'"runs.using" must be "composite"') and then continue; update the check around
runs.get('using') in the same scope (refer to the runs variable and the err
function) so non-composite manifests fail the validation rather than being
silently ignored.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lint/composite-schema/action.yml`:
- Around line 103-112: The code currently normalizes malformed "inputs" via
inputs = data.get('inputs') or {}, which lets non-mapping values slip through
and causes double-counting because err(filepath, ...) already increments
violations; change the logic in the validation block that sets and checks inputs
so you (1) retrieve inputs with data.get('inputs') without falling back to {}
and (2) if inputs is None skip the loop, but if inputs exists and is not a dict
call err(filepath, '"inputs" must be a mapping.') and continue without
iterating; also remove the extra violations += 1 where err(...) already
increments violations (or ensure only err increments) so the scalar-spec branch
for for name, spec in inputs.items() does not double-count.

In `@src/notify/pr-lint-reporter/README.md`:
- Around line 38-39: The example workflow currently uses "if: always() &&
github.event_name == 'pull_request'" which lets the job run even when the
required changed-files job failed; update the job's if condition to also require
the changed-files job succeeded (e.g., include needs.changed-files.result ==
'success') so that this job is gated by the outcome of the changed-files
check—modify the if expression where the "if: always() && github.event_name ==
'pull_request'" line appears (the same location that mirrors the gating used in
.github/workflows/self-pr-validation.yml for the lint-report job).

---

Duplicate comments:
In `@src/lint/composite-schema/action.yml`:
- Around line 73-78: The code currently skips manifests whose runs.using is not
"composite"; change this to report an error instead: in the block that reads
runs = data.get('runs') and checks isinstance(runs, dict), replace the continue
when runs.get('using') != 'composite' with a call to err(...) (include filepath
and a clear message like '"runs.using" must be "composite"') and then continue;
update the check around runs.get('using') in the same scope (refer to the runs
variable and the err function) so non-composite manifests fail the validation
rather than being silently ignored.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3d0bb763-9155-4213-8815-fb5e2243324b

📥 Commits

Reviewing files that changed from the base of the PR and between 152be7c and 6a50cce.

📒 Files selected for processing (4)
  • .github/workflows/self-pr-validation.yml
  • src/lint/composite-schema/action.yml
  • src/notify/pr-lint-reporter/README.md
  • src/notify/pr-lint-reporter/action.yml

Comment on lines +103 to +112
inputs = data.get('inputs') or {}
if not isinstance(inputs, dict):
err(filepath, '"inputs" must be a mapping.')
continue

for name, spec in inputs.items():
if not isinstance(spec, dict):
err(filepath, f'Input "{name}" definition must be a mapping.')
violations += 1
continue
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't normalize malformed inputs to {} or double-count them.

data.get('inputs') or {} lets inputs: [] / '' pass as a valid empty mapping, and the scalar-spec branch inflates the final total because err() already increments violations.

Suggested fix
-            inputs = data.get('inputs') or {}
+            if 'inputs' not in data:
+                inputs = {}
+            else:
+                inputs = data.get('inputs')
             if not isinstance(inputs, dict):
                 err(filepath, '"inputs" must be a mapping.')
                 continue

             for name, spec in inputs.items():
                 if not isinstance(spec, dict):
                     err(filepath, f'Input "{name}" definition must be a mapping.')
-                    violations += 1
                     continue
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lint/composite-schema/action.yml` around lines 103 - 112, The code
currently normalizes malformed "inputs" via inputs = data.get('inputs') or {},
which lets non-mapping values slip through and causes double-counting because
err(filepath, ...) already increments violations; change the logic in the
validation block that sets and checks inputs so you (1) retrieve inputs with
data.get('inputs') without falling back to {} and (2) if inputs is None skip the
loop, but if inputs exists and is not a dict call err(filepath, '"inputs" must
be a mapping.') and continue without iterating; also remove the extra violations
+= 1 where err(...) already increments violations (or ensure only err
increments) so the scalar-spec branch for for name, spec in inputs.items() does
not double-count.

Comment on lines +38 to +39
needs: [changed-files, yamllint, actionlint, pinned-actions, markdown-link-check, typos, shellcheck, readme-check, composite-schema]
if: always() && github.event_name == 'pull_request'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Gate the example on successful file detection.

This snippet still runs on always() alone. If changed-files fails, copied workflows will recreate the misleading “skipped / no changes” report that the real workflow already fixed.

Suggested fix
-    if: always() && github.event_name == 'pull_request'
+    if: always() && github.event_name == 'pull_request' && needs.changed-files.result == 'success'
As per coding guidelines, `.github/workflows/self-pr-validation.yml` gates `lint-report` with `needs.changed-files.result == 'success'`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
needs: [changed-files, yamllint, actionlint, pinned-actions, markdown-link-check, typos, shellcheck, readme-check, composite-schema]
if: always() && github.event_name == 'pull_request'
needs: [changed-files, yamllint, actionlint, pinned-actions, markdown-link-check, typos, shellcheck, readme-check, composite-schema]
if: always() && github.event_name == 'pull_request' && needs.changed-files.result == 'success'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notify/pr-lint-reporter/README.md` around lines 38 - 39, The example
workflow currently uses "if: always() && github.event_name == 'pull_request'"
which lets the job run even when the required changed-files job failed; update
the job's if condition to also require the changed-files job succeeded (e.g.,
include needs.changed-files.result == 'success') so that this job is gated by
the outcome of the changed-files check—modify the if expression where the "if:
always() && github.event_name == 'pull_request'" line appears (the same location
that mirrors the gating used in .github/workflows/self-pr-validation.yml for the
lint-report job).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants