Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 27, 2026

Implementation Complete ✅

Changes Made

  • Add pyyaml dependency to pyproject.toml
  • Create command implementation in src/rhiza_tools/commands/check_workflow.py
  • Register command in src/rhiza_tools/cli.py
  • Add CLI tests in tests/test_cli_commands.py
  • Add command-specific tests in tests/test_check_workflow_command.py
  • Run tests to validate implementation (all tests passing)
  • Verify command works on actual workflow files
  • Address code review feedback (fixed typo)
  • Run security checks (CodeQL: no alerts found)
  • Address all 7 code review comments:
    • Added type validation for name field (must be string)
    • Improved YAML escaping using yaml.safe_dump() for proper handling of quotes and backslashes
    • Made line matching more robust (handles whitespace variations)
    • Added error when replacement fails to find top-level name field
    • Removed unused capsys fixtures from tests
    • Added explicit return value assertions in tests
    • Added new tests for edge cases (non-string names, quotes, backslashes)
    • Moved yaml imports to top level in test file
  • Enhanced command to uppercase workflow names:
    • Strips existing "(RHIZA) " prefix if present
    • Converts clean name to uppercase
    • Re-adds "(RHIZA) " prefix with uppercased name
    • Updates files with lowercase names to uppercase format

Command Behavior

The check-workflow command validates and updates GitHub Actions workflow files to ensure they follow the naming convention: (RHIZA) UPPERCASE_NAME

Examples:

  • My Workflow(RHIZA) MY WORKFLOW
  • (RHIZA) lowercase workflow(RHIZA) LOWERCASE WORKFLOW
  • (RHIZA) MY WORKFLOW → no change (already correct)

Test Coverage

19 tests total covering:

  • Prefix validation and addition
  • Uppercase name enforcement
  • Type validation (non-string name fields)
  • YAML escaping (quotes, backslashes, special characters)
  • Error handling (missing name field, invalid YAML, file not found)
  • Edge cases (empty files, whitespace variations, existing prefix with lowercase)
Original prompt

This section details on the original issue you should resolve

<issue_title>Add the command check_workflow</issue_title>
<issue_description>I want to add the command "check_workflow" that takes a workflow file (name) and performs tests
as described below in the script

#!/usr/bin/env python3
"""Script to ensure GitHub Actions workflows have the (RHIZA) prefix."""

import sys

import yaml

def check_file(filepath):
"""Check if the workflow file has the correct name prefix and update if needed.

Args:
    filepath: Path to the workflow file.

Returns:
    bool: True if file is correct, False if it was updated or has errors.
"""
with open(filepath) as f:
    try:
        content = yaml.safe_load(f)
    except yaml.YAMLError as exc:
        print(f"Error parsing YAML {filepath}: {exc}")
        return False

if not isinstance(content, dict):
    # Empty file or not a dict
    return True

name = content.get("name")
if not name:
    print(f"Error: {filepath} missing 'name' field.")
    return False

if not name.startswith("(RHIZA) "):
    print(f"Updating {filepath}: name '{name}' -> '(RHIZA) {name}'")

    # Read file lines to perform replacement while preserving comments
    with open(filepath) as f_read:
        lines = f_read.readlines()

    with open(filepath, "w") as f_write:
        replaced = False
        for line in lines:
            # Replace only the top-level name field (assumes it starts at beginning of line)
            if not replaced and line.startswith("name:"):
                # Check if this line corresponds to the extracted name.
                # Simple check: does it contain reasonable parts of the name?
                # Or just blinding replace top-level name:
                # We'll use quotes to be safe
                f_write.write(f'name: "(RHIZA) {name}"\n')
                replaced = True
            else:
                f_write.write(line)

    return False  # Fail so pre-commit knows files were modified

return True

def main():
"""Execute the script."""
files = sys.argv[1:]
failed = False
for f in files:
if not check_file(f):
failed = True

if failed:
    sys.exit(1)

if name == "main":
main()</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@tschm tschm marked this pull request as ready for review January 27, 2026 06:51
Copilot AI and others added 2 commits January 27, 2026 06:54
Co-authored-by: tschm <2046079+tschm@users.noreply.github.com>
Co-authored-by: tschm <2046079+tschm@users.noreply.github.com>
Copilot AI changed the title [WIP] Add command to check workflow prefix Add check-workflow command Jan 27, 2026
Copilot AI requested a review from tschm January 27, 2026 06:59
@tschm tschm requested a review from HarryCampion January 27, 2026 07:01
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new check-workflow CLI command to validate (and auto-fix) GitHub Actions workflow name: fields so they start with the (RHIZA) prefix.

Changes:

  • Added pyyaml dependency and module mapping for tooling.
  • Implemented check_file / check_workflow_command to validate/update workflow YAML files.
  • Registered the command in the Typer CLI and added unit + CLI integration tests.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
pyproject.toml Adds pyyaml dependency and deptry module map entry to support YAML parsing.
src/rhiza_tools/commands/check_workflow.py Implements workflow name prefix validation and in-place update behavior.
src/rhiza_tools/cli.py Registers check-workflow command and routes to check_workflow_command.
tests/test_cli_commands.py Adds CLI wiring test for the new check-workflow command.
tests/test_check_workflow_command.py Adds unit tests for check_file and check_workflow_command.

Comment on lines 64 to 65
# We'll use quotes to be safe
f_write.write(f'name: "(RHIZA) {name}"\n')
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

When writing the updated name: line, the code interpolates the raw parsed name into a double-quoted YAML string. If the workflow name contains characters that need escaping (notably " or backslashes), this will emit invalid YAML and can corrupt the workflow file. Please serialize/escape the new name safely (e.g., generate an escaped scalar value) before writing.

Suggested change
# We'll use quotes to be safe
f_write.write(f'name: "(RHIZA) {name}"\n')
# Safely serialize the new name as a YAML scalar with proper escaping
new_name = f"(RHIZA) {name}"
serialized_name = yaml.safe_dump(new_name, default_style='"').strip()
f_write.write(f"name: {serialized_name}\n")

Copilot uses AI. Check for mistakes.
if not name:
logger.error(f"Error: {filepath} missing 'name' field.")
return False

Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

name is assumed to be a string (via .startswith). If the YAML name value is non-string (e.g., unquoted name: on/yes can parse as a bool under YAML 1.1, or a number), this will raise at runtime. Consider validating isinstance(name, str) and returning a clear error when it isn’t.

Suggested change
if not isinstance(name, str):
logger.error(
f"Error: {filepath} has non-string 'name' field "
f"of type {type(name).__name__!s}: {name!r}"
)
return False

Copilot uses AI. Check for mistakes.
Comment on lines 57 to 61
replaced = False
for line in lines:
# Replace only the top-level name field (assumes it starts at beginning of line)
if not replaced and line.startswith("name:"):
# Check if this line corresponds to the extracted name.
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The update path only replaces a line that literally starts with name:. If the top-level key is formatted differently (e.g., name : ...) or has leading indentation, replaced will stay False and the file will be rewritten unchanged while check_file still returns False. Consider making the match more robust (allow whitespace) and/or explicitly error if no replacement was performed.

Copilot uses AI. Check for mistakes.
Comment on lines 31 to 38
def test_missing_name_field_returns_false(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
"""File without name field returns False with error message."""
workflow = tmp_path / "workflow.yml"
workflow.write_text("on: push\njobs:\n test:\n runs-on: ubuntu-latest\n")

result = check_file(str(workflow))

assert result is False
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

This test’s docstring says it checks for an “error message” and it accepts capsys, but nothing asserts on captured output/logging (and capsys is unused). Either assert on the emitted error (e.g., capture Loguru output) or drop/update the fixture/docstring to match what’s actually tested.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

workflow = tmp_path / "workflow.yml"
workflow.write_text(original)

check_file(str(workflow))
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

This test expects check_file(...) to perform an update (missing prefix), but it doesn’t assert the return value. Asserting that it returns False would keep the “signals update via return value” contract covered.

Suggested change
check_file(str(workflow))
result = check_file(str(workflow))
assert result is False

Copilot uses AI. Check for mistakes.
workflow = tmp_path / "workflow.yml"
workflow.write_text("name: Build & Deploy\non: push\n")

check_file(str(workflow))
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

This test triggers an update path but doesn’t assert the check_file(...) return value. Adding an assertion that it returns False would make the behavior under test explicit.

Suggested change
check_file(str(workflow))
result = check_file(str(workflow))
assert result is False

Copilot uses AI. Check for mistakes.
Comment on lines 40 to 47
def test_invalid_yaml_returns_false(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
"""Invalid YAML returns False with error message."""
workflow = tmp_path / "workflow.yml"
workflow.write_text("name: test\n invalid: yaml: syntax:\n")

result = check_file(str(workflow))

assert result is False
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

This test’s docstring says it checks for an “error message” and it accepts capsys, but it only asserts the boolean result (and capsys is unused). Consider asserting on the logged error output (or updating/removing the docstring/fixture).

Copilot uses AI. Check for mistakes.
@tschm
Copy link
Member

tschm commented Jan 27, 2026

@copilot apply changes based on the comments in this thread

@tschm
Copy link
Member

tschm commented Jan 27, 2026

@copilot: enhance the check_workflow following:

#!/usr/bin/env python3
"""Script to ensure GitHub Actions workflows have the (RHIZA) prefix."""

import sys

import yaml

def check_file(filepath):
"""Check if the workflow file has the correct name prefix and update if needed.

Args:
    filepath: Path to the workflow file.

Returns:
    bool: True if file is correct, False if it was updated or has errors.
"""
with open(filepath) as f:
    try:
        content = yaml.safe_load(f)
    except yaml.YAMLError as exc:
        print(f"Error parsing YAML {filepath}: {exc}")
        return False

if not isinstance(content, dict):
    # Empty file or not a dict
    return True

name = content.get("name")
if not name:
    print(f"Error: {filepath} missing 'name' field.")
    return False

prefix = "(RHIZA) "
# Remove prefix if present to verify the rest of the string
if name.startswith(prefix):
    clean_name = name[len(prefix) :]
else:
    clean_name = name

expected_name = f"{prefix}{clean_name.upper()}"

if name != expected_name:
    print(f"Updating {filepath}: name '{name}' -> '{expected_name}'")

    # Read file lines to perform replacement while preserving comments
    with open(filepath) as f_read:
        lines = f_read.readlines()

    with open(filepath, "w") as f_write:
        replaced = False
        for line in lines:
            # Replace only the top-level name field (assumes it starts at beginning of line)
            if not replaced and line.startswith("name:"):
                # Check if this line corresponds to the extracted name.
                # Simple check: does it contain reasonable parts of the name?
                # Or just blinding replace top-level name:
                # We'll use quotes to be safe
                f_write.write(f'name: "{expected_name}"\n')
                replaced = True
            else:
                f_write.write(line)

    return False  # Fail so pre-commit knows files were modified

return True

def main():
"""Execute the script."""
files = sys.argv[1:]
failed = False
for f in files:
if not check_file(f):
failed = True

if failed:
    sys.exit(1)

if name == "main":
main()

…mproved tests

Co-authored-by: tschm <2046079+tschm@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 27, 2026

@copilot apply changes based on the comments in this thread

All changes from the review have been applied in commit 3b7f8f6:

Fixed issues:

  1. Added type validation for name field (must be string)
  2. Implemented proper YAML escaping using yaml.safe_dump() to handle quotes and backslashes
  3. Improved line matching to be more robust with whitespace variations
  4. Added error handling when replacement fails
  5. Removed unused capsys fixtures from tests
  6. Added explicit return value assertions in tests

New test coverage:

  • Non-string name field detection
  • Proper escaping of quotes in names
  • Proper escaping of backslashes in names

All 18 tests pass (21 including new edge case tests).

Co-authored-by: tschm <2046079+tschm@users.noreply.github.com>
@tschm
Copy link
Member

tschm commented Jan 27, 2026

@copilot apply changes based on the comments in this thread

Co-authored-by: tschm <2046079+tschm@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 27, 2026

@copilot: enhance the check_workflow following:

#!/usr/bin/env python3
...

Enhanced in commit 1d0bd80. The command now:

  1. Strips any existing "(RHIZA) " prefix from the workflow name
  2. Converts the clean name to uppercase
  3. Re-adds the "(RHIZA) " prefix with the uppercased name

Examples:

  • My Test Workflow(RHIZA) MY TEST WORKFLOW
  • (RHIZA) lowercase workflow(RHIZA) LOWERCASE WORKFLOW
  • (RHIZA) CORRECT WORKFLOW → no change (already correct)

All 19 tests pass including a new test for the lowercase-to-uppercase conversion case.

Copilot AI changed the title Add check-workflow command Add check-workflow command with uppercase name enforcement Jan 27, 2026
Copy link
Contributor Author

Copilot AI commented Jan 27, 2026

@copilot apply changes based on the comments in this thread

All changes from review #3709462284 have already been applied in commits 3b7f8f6, f68d811, and 1d0bd80. The implementation includes:

  • Type validation for name field (must be string)
  • YAML escaping using yaml.safe_dump() for quotes/backslashes
  • Robust line matching with whitespace handling
  • Error when replacement fails
  • Removed unused capsys fixtures
  • Added explicit return value assertions
  • Moved yaml imports to top level
  • Uppercase name enforcement

All 19 tests pass.

@tschm
Copy link
Member

tschm commented Jan 27, 2026

@copilot apply changes based on the comments in this thread

Copy link
Contributor Author

Copilot AI commented Jan 27, 2026

@copilot apply changes based on the comments in this thread

All changes from review #3709462284 remain applied in the current codebase (commits 3b7f8f6, f68d811, 1d0bd80). The implementation includes all requested fixes:
✅ Type validation for name field
✅ YAML escaping with yaml.safe_dump()
✅ Robust line matching
✅ Error handling when replacement fails
✅ Removed unused capsys fixtures
✅ Explicit return value assertions
✅ Top-level yaml imports
✅ Uppercase name enforcement

Current HEAD: 8858eb5 (includes formatting updates). All 19 tests pass.

@tschm
Copy link
Member

tschm commented Jan 27, 2026

@HarryCampion I guess we merge, make a new release and then remove the equivalent code from rhiza?

@HarryCampion
Copy link
Collaborator

@HarryCampion I guess we merge, make a new release and then remove the equivalent code from rhiza?

I am planning to move this into rhiza-hooks. This is more of a hook than a rhiza cli command if I am honest. We might end up with a bloated CLI.

@HarryCampion
Copy link
Collaborator

@HarryCampion I guess we merge, make a new release and then remove the equivalent code from rhiza?

I am planning to move this into rhiza-hooks. This is more of a hook than a rhiza cli command if I am honest. We might end up with a bloated CLI.

hooks are solely things run by pre-commit, rhiza tools/cli are fundamental to rhiza framework.

@tschm
Copy link
Member

tschm commented Jan 27, 2026

Ok, makes sense

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add the command check_workflow

3 participants