Skip to content

Solving christmas lights kata with copilot#1

Open
micaelarp wants to merge 21 commits intomainfrom
solving-christmas-lights-kata-with-copilot
Open

Solving christmas lights kata with copilot#1
micaelarp wants to merge 21 commits intomainfrom
solving-christmas-lights-kata-with-copilot

Conversation

@micaelarp
Copy link
Copy Markdown
Owner

@micaelarp micaelarp commented Dec 4, 2025

Pull Request Description: Christmas Lights Kata Refactor & SOLID/AGENTS.md Compliance

Overview

This PR refactors the entire Christmas Lights Kata codebase to fully comply with SOLID principles and the standards/practices defined in AGENTS.md. The changes improve code quality, extensibility, maintainability, and testability, making the kata an ideal base for refactoring exercises, SOLID workshops, and clean design practices.

Main Changes

  • SRP (Single Responsibility Principle):
    • Each class has a clear, single responsibility (Command, LightGrid, ChristmasLights, InstructionParser, LightPosition).
  • Encapsulation & DDD:
    • Removed direct access to internal attributes (like _grid).
    • All public interfaces use value objects (LightPosition).
  • Validation & Robustness:
    • All public methods validate their inputs and raise clear errors.
    • The parser and commands handle errors and edge cases.
  • Expressiveness & Naming:
    • Clear, domain-aligned names and docstrings.
    • Removed redundant comments, keeping only essential documentation.
  • Extensibility & Simplicity:
    • The code is easy to extend (e.g., for new commands or rules).
    • No duplication or unnecessary logic.
  • Tests:
    • Coverage for edge cases, errors, and normal cases.
    • Tests are organized by class/responsibility and have docstrings.
    • Tests only use the public interface, never internal details.
  • Integration & Robustness:
    • The controller handles errors and edge cases, and integration tests validate the full flow.
  • Comment Cleanup:
    • Removed redundant comments and docstrings throughout the code, keeping only essential and clear documentation.

Impact and Motivation

These guidelines are designed not only to ensure technical excellence, but also to foster a culture of learning, collaboration, and continuous improvement. By applying AGENTS.md, the team has already achieved significant improvements in code quality and maintainability (e.g., the Christmas Lights Kata refactor). This foundation will help onboard new contributors, streamline code reviews, and support future growth and adaptation as the project evolves.

Justification

  • Quality & Maintainability: The code is easier to understand, maintain, and extend.
  • Ready for SOLID Exercises: The design allows participants to identify and refactor SOLID violations, or practice incremental improvements.
  • Robustness: Error handling and validation are consistent and clear.
  • Coverage: Tests ensure correct and robust behavior for edge cases and errors.
  • Best Practices Example: The repository can be used as a reference for clean design and TDD/XP.

Conclusion

This PR transforms the kata into an example of clean, robust, and extensible code, aligned with SOLID and AGENTS.md, ready to be used in workshops, refactoring exercises, and as a base for new katas or features.

…nd pattern for actions, remove conditionals (SOLID baby step 2)
…rsing logic from ChristmasLights, reinforce SRP (SOLID baby step 3)
… public interface, remove direct grid access (SOLID baby step 4)
…tests by class, enforce SRP in test suite (SOLID)
…rings, type hints, and improve structure in all classes (AGENTS.md standards, TIDY FIRST)
…Position dataclass everywhere, update all tests and methods (AGENTS.md, DDD, TIDY FIRST)
…Action enum for actions, update parser, commands, and tests (AGENTS.md, DDD)
…error tests for instruction parser (AGENTS.md, TDD)
…arser validation and error messages, robust edge case handling (AGENTS.md)
…oordinate validation, improve docstring and clarity in parser (AGENTS.md)
…ction logic, improve comments and docstring, simplify parser (AGENTS.md)
… tests to use LightPosition in is_lit (interface alignment, AGENTS.md)
…rings, edge case tests, and comments to command tests (AGENTS.md)
…ocstrings and comments in LightGrid and ChristmasLights (AGENTS.md)
… raises error for invalid instructions, test robust subprocess error handling (AGENTS.md)
…sts for edge cases and robust controller flow (AGENTS.md)
….is_lit delegates to ChristmasLights.is_lit, improved encapsulation (AGENTS.md)
…tion is now immutable, improved docstring and __str__ (AGENTS.md)
…ocstrings, comments, and extensibility in instruction parser (AGENTS.md)
…dant comments and keep only essential docstrings (clean code)
Copy link
Copy Markdown

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

This PR refactors the Christmas Lights Kata solution using modern software engineering practices, applying SOLID principles, design patterns (Command, Value Object, Factory), and Domain-Driven Design. The refactoring separates concerns by introducing proper abstractions while maintaining comprehensive test coverage.

Key changes:

  • Introduced Command pattern for light operations (TurnOn, TurnOff, Toggle)
  • Created value objects (LightPosition) and enums (LightAction) for type safety
  • Extracted instruction parsing logic into dedicated InstructionParser class
  • Refactored LightGrid to use composition over inheritance (facade pattern)
  • Enhanced test coverage with new test suites for parser, commands, and controller edge cases

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
python/christmas_lights_kata/src/position.py Adds immutable LightPosition value object to replace tuple-based coordinates
python/christmas_lights_kata/src/action.py Introduces LightAction enum to replace string-based action identifiers
python/christmas_lights_kata/src/instruction_parser.py Extracts command parsing logic into dedicated parser class with validation
python/christmas_lights_kata/src/commands.py Implements Command pattern with TurnOnCommand, TurnOffCommand, ToggleCommand classes
python/christmas_lights_kata/src/controller.py Refactors controller to use new parser and command abstractions with error handling
python/christmas_lights_kata/src/lights.py Refactors grid to use LightPosition, changes LightGrid from inheritance to composition
python/christmas_lights_kata/tests/test_lights.py Updates tests to use new LightPosition API and removes legacy tests
python/christmas_lights_kata/tests/test_instruction_parser.py Adds comprehensive test suite for instruction parser validation
python/christmas_lights_kata/tests/test_controller.py Enhances controller tests with subprocess-based integration tests for error cases
python/christmas_lights_kata/tests/test_commands.py Adds dedicated test suite for command pattern implementations
python/christmas_lights_kata/tests/test_christmas_lights.py Adds integration tests for ChristmasLights class
python/christmas_lights_kata/tests/instr.md Test fixture file with invalid command for testing error handling
python/christmas_lights_kata/src/__init__.py Package marker file
python/christmas_lights_kata/__init__.py Package marker file
python/__init__.py Package marker file
AGENTS.md Adds comprehensive coding guidelines and best practices documentation
Add:, Fix:, Refactor: Archive files with unconventional names containing AGENTS.md content
Comments suppressed due to low confidence (4)

python/christmas_lights_kata/src/action.py:1

  • Import of 'auto' is not used.
from enum import Enum, auto

python/christmas_lights_kata/src/commands.py:1

  • Import of 'Tuple' is not used.
from typing import Tuple

python/christmas_lights_kata/src/controller.py:9

  • Import of 'LightPosition' is not used.
from .position import LightPosition

python/christmas_lights_kata/src/controller.py:10

  • Import of 'LightAction' is not used.
from .action import LightAction

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +25 to +26
import subprocess
import os
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

According to the AGENTS.md guidelines (line 84), inline imports should be avoided. The subprocess and os imports should be moved to the top of the file.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

instr = "turn on -1,0 through 0,0"
instr_path = tmp_path / "instr.md"
instr_path.write_text(instr)
import subprocess, os
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

According to the AGENTS.md guidelines (line 84), inline imports should be avoided. The subprocess and os imports should be moved to the top of the file. Additionally, multiple imports on the same line (comma-separated) should be on separate lines per PEP 8.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,7 @@
from enum import Enum, auto
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The auto import from enum is unused. The enum values are explicitly assigned as strings rather than using auto(). Either remove the import or consider using auto() if the string values aren't required externally.

Suggested change
from enum import Enum, auto
from enum import Enum

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +28
if not isinstance(start, LightPosition) or not isinstance(end, LightPosition):
raise TypeError("start and end must be LightPosition instances")
grid.turn_on_region(start, end)

class TurnOffCommand(Command):
"""Turn off lights in a region."""
def apply(self, grid: object, start: LightPosition, end: LightPosition) -> None:
if not isinstance(start, LightPosition) or not isinstance(end, LightPosition):
raise TypeError("start and end must be LightPosition instances")
grid.turn_off_region(start, end)

class ToggleCommand(Command):
"""Toggle lights in a region."""
def apply(self, grid: object, start: LightPosition, end: LightPosition) -> None:
if not isinstance(start, LightPosition) or not isinstance(end, LightPosition):
raise TypeError("start and end must be LightPosition instances")
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The runtime type checks using isinstance are unnecessary since the InstructionParser already returns LightPosition instances and Python's type system with type hints provides compile-time checking. These checks add unnecessary runtime overhead and duplicate validation. Consider removing them and relying on the type system, or if validation is truly needed, add a comment explaining why runtime checks are required.

Suggested change
if not isinstance(start, LightPosition) or not isinstance(end, LightPosition):
raise TypeError("start and end must be LightPosition instances")
grid.turn_on_region(start, end)
class TurnOffCommand(Command):
"""Turn off lights in a region."""
def apply(self, grid: object, start: LightPosition, end: LightPosition) -> None:
if not isinstance(start, LightPosition) or not isinstance(end, LightPosition):
raise TypeError("start and end must be LightPosition instances")
grid.turn_off_region(start, end)
class ToggleCommand(Command):
"""Toggle lights in a region."""
def apply(self, grid: object, start: LightPosition, end: LightPosition) -> None:
if not isinstance(start, LightPosition) or not isinstance(end, LightPosition):
raise TypeError("start and end must be LightPosition instances")
grid.turn_on_region(start, end)
class TurnOffCommand(Command):
"""Turn off lights in a region."""
def apply(self, grid: object, start: LightPosition, end: LightPosition) -> None:
grid.turn_off_region(start, end)
class ToggleCommand(Command):
"""Toggle lights in a region."""
def apply(self, grid: object, start: LightPosition, end: LightPosition) -> None:

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +28
if not isinstance(start, LightPosition) or not isinstance(end, LightPosition):
raise TypeError("start and end must be LightPosition instances")
grid.turn_on_region(start, end)

class TurnOffCommand(Command):
"""Turn off lights in a region."""
def apply(self, grid: object, start: LightPosition, end: LightPosition) -> None:
if not isinstance(start, LightPosition) or not isinstance(end, LightPosition):
raise TypeError("start and end must be LightPosition instances")
grid.turn_off_region(start, end)

class ToggleCommand(Command):
"""Toggle lights in a region."""
def apply(self, grid: object, start: LightPosition, end: LightPosition) -> None:
if not isinstance(start, LightPosition) or not isinstance(end, LightPosition):
raise TypeError("start and end must be LightPosition instances")
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The runtime type checks using isinstance are unnecessary since the InstructionParser already returns LightPosition instances and Python's type system with type hints provides compile-time checking. These checks add unnecessary runtime overhead and duplicate validation. Consider removing them and relying on the type system, or if validation is truly needed, add a comment explaining why runtime checks are required.

Suggested change
if not isinstance(start, LightPosition) or not isinstance(end, LightPosition):
raise TypeError("start and end must be LightPosition instances")
grid.turn_on_region(start, end)
class TurnOffCommand(Command):
"""Turn off lights in a region."""
def apply(self, grid: object, start: LightPosition, end: LightPosition) -> None:
if not isinstance(start, LightPosition) or not isinstance(end, LightPosition):
raise TypeError("start and end must be LightPosition instances")
grid.turn_off_region(start, end)
class ToggleCommand(Command):
"""Toggle lights in a region."""
def apply(self, grid: object, start: LightPosition, end: LightPosition) -> None:
if not isinstance(start, LightPosition) or not isinstance(end, LightPosition):
raise TypeError("start and end must be LightPosition instances")
grid.turn_on_region(start, end)
class TurnOffCommand(Command):
"""Turn off lights in a region."""
def apply(self, grid: object, start: LightPosition, end: LightPosition) -> None:
grid.turn_off_region(start, end)
class ToggleCommand(Command):
"""Toggle lights in a region."""
def apply(self, grid: object, start: LightPosition, end: LightPosition) -> None:

Copilot uses AI. Check for mistakes.

def main() -> None:
"""Main entry point for running the Christmas Lights Kata."""
import argparse
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

According to the AGENTS.md guidelines (line 84), inline imports should be avoided. The import argparse should be moved to the top of the file with the other imports.

Copilot uses AI. Check for mistakes.
"""Should report zero lights lit for file with only comments."""
instr_path = tmp_path / "instr.md"
instr_path.write_text("# comment\n# another comment")
import subprocess, os
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

According to the AGENTS.md guidelines (line 84), inline imports should be avoided. The subprocess and os imports should be moved to the top of the file. Additionally, multiple imports on the same line (comma-separated) should be on separate lines per PEP 8.

Copilot uses AI. Check for mistakes.
instr = " turn on 0,0 through 0,0 "
instr_path = tmp_path / "instr.md"
instr_path.write_text(instr)
import subprocess, os
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

According to the AGENTS.md guidelines (line 84), inline imports should be avoided. The subprocess and os imports should be moved to the top of the file. Additionally, multiple imports on the same line (comma-separated) should be on separate lines per PEP 8.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,39 @@
from typing import Tuple
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The Tuple import is unused in this file. Remove it to keep imports clean.

Suggested change
from typing import Tuple

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +10
from .position import LightPosition
from .action import LightAction
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The imports LightPosition and LightAction are unused in this file. Remove them to keep the imports clean.

Suggested change
from .position import LightPosition
from .action import LightAction

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI commented Dec 4, 2025

@micaelarp I've opened a new pull request, #2, to work on those changes. Once the pull request is ready, I'll request review from you.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants