Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .cursor/cli.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"permissions": {
"allow": [],
"deny": ["Shell(git push)", "Shell(gh pr create)", "Write(**)"]
}
}
1 change: 1 addition & 0 deletions .cursor/commands/refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refactor following our minimalist code philosophy then organize and catalog like a librarian and obsess over code consistency. Let's take this code from an A+ to an A++ 🚀
1 change: 1 addition & 0 deletions .cursor/commands/review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Analyze and meticulously catalog all the changes in this branch, including all the changes that haven't been committed yet. Report back on where the changes fall short of our development, architecture and testing rules.
14 changes: 14 additions & 0 deletions .cursor/rules/00-main.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ alwaysApply: true

We're developing Deployer PHP, a Composer package and CLI tool that simplifies provisioning and setting up servers across multiple Cloud providers, enabling you to deploy your PHP projects effortlessly.

**🚨 All our rules are MANDATORY and IMPORTANT!**

**🔎 Before ANY Task:**

- Check `composer.json` and `package.json` for installed packages
Expand All @@ -18,6 +20,18 @@ We're developing Deployer PHP, a Composer package and CLI tool that simplifies p
- Always ask yourself if less code can achieve the same result
- Refactor relentlessly for code clarity and necessity

**🤓 Organize & Catalog Like A Librarian:**

Organize and catalog code like a librarian. Group related functions into comment-separated sections for visual clarity and prefer alphabetical ordering when it doesn’t fight logical grouping. Code should be functional and visually appealing; there is beauty in order.

**🤖 Obsessive Code Consistency:**

Be rigorous to the point of perfectionism in maintaining implementation consistency and logical coherence across similar areas of the codebase. Additionally, make it a habit to regularly review other code in and around the files you are working on to identify reusable patterns.

The goal is for all the code in this repository to appear as if it were written by a single individual, adhering to a uniform style, a consistent set of standards, and aesthetic principles in software development.

This can include everything from naming files, classes, variables, or array keys to the precedence and type of parameters passed to a function, to how logic flows and how the code is organized or commented.

Comment on lines +27 to +34

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

🧩 Analysis chain

Rules conflict: testing stance here vs. tests rules file.

This doc says “Don’t worry about tests,” while .cursor/rules/02-tests.mdc mandates Pest and 60%+ coverage. Please reconcile and make a single source of truth.

Would you like me to propose a unified “When to write tests” section that aligns both files?


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo "Listing .cursor directory (if present)"
ls -la .cursor || true
echo
echo "Listing .cursor/rules directory (if present)"
ls -la .cursor/rules || true
echo

echo "Show .cursor/rules/00-main.mdc (up to 300 lines):"
if [ -f .cursor/rules/00-main.mdc ]; then
  nl -ba -w3 -s': ' .cursor/rules/00-main.mdc | sed -n '1,300p'
else
  echo "File .cursor/rules/00-main.mdc not found"
fi
echo

echo "Show .cursor/rules/02-tests.mdc (up to 300 lines):"
if [ -f .cursor/rules/02-tests.mdc ]; then
  nl -ba -w3 -s': ' .cursor/rules/02-tests.mdc | sed -n '1,300p'
else
  echo "File .cursor/rules/02-tests.mdc not found"
fi
echo

echo "Search repo for mentions of Pest, coverage, and 'don't worry about tests' phrases:"
rg -n --hidden --no-ignore-vcs -S "Pest" || true
rg -n --hidden --no-ignore-vcs -S "coverage" || true
rg -n --hidden --no-ignore-vcs -S "don't worry about tests" || true
rg -n --hidden --no-ignore-vcs -S "don’t worry" || true
rg -n --hidden --no-ignore-vcs -S "When to write tests" || true

Length of output: 782


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo
echo "Files in .cursor/rules:"
ls -la .cursor/rules || true
echo

echo "----- .cursor/rules/00-main.mdc (numbered) -----"
if [ -f .cursor/rules/00-main.mdc ]; then
  awk '{printf "%4d: %s\n", NR, $0}' .cursor/rules/00-main.mdc | sed -n '1,300p'
else
  echo "missing: .cursor/rules/00-main.mdc"
fi
echo

echo "----- Lines 27-34 of .cursor/rules/00-main.mdc -----"
if [ -f .cursor/rules/00-main.mdc ]; then
  awk 'NR>=27 && NR<=34 {printf "%4d: %s\n", NR, $0}' .cursor/rules/00-main.mdc || true
fi
echo

echo "----- .cursor/rules/02-tests.mdc (numbered) -----"
if [ -f .cursor/rules/02-tests.mdc ]; then
  awk '{printf "%4d: %s\n", NR, $0}' .cursor/rules/02-tests.mdc | sed -n '1,400p'
else
  echo "missing: .cursor/rules/02-tests.mdc"
fi
echo

echo "----- Search for key phrases in both files -----"
grep -nhiE "Pest|coverage|don't worry about tests|don't worry|When to write tests|Don’t worry|write tests" .cursor/rules/*.mdc || true

echo "done"

Length of output: 7024


Conflict: testing guidance is inconsistent — reconcile test policy

00-main.mdc says tests are optional while 02-tests.mdc mandates Pest + 60%+ coverage. Consolidate to a single source of truth.

  • .cursor/rules/00-main.mdc — line 35: "✔️ Don't worry about tests: Write or run tests ONLY if specifically instructed"
  • .cursor/rules/02-tests.mdc — line 9: "Framework: Pest exclusively with it() syntax, 60%+ coverage"

Fix: remove or clarify the contradictory line in 00-main.mdc or update it to reference 02-tests.mdc (or move all testing policy into a single file). I can draft a unified "When to write tests" section that aligns both files.

🤖 Prompt for AI Agents
.cursor/rules/00-main.mdc lines 27-34: the file contains a line that contradicts
the test policy in .cursor/rules/02-tests.mdc (saying tests are optional vs.
Pest + 60% coverage required); remove or replace the contradictory sentence so
there is a single source of truth — either delete the "Don't worry about tests"
line or change it to a short pointer that delegates to
.cursor/rules/02-tests.mdc (e.g., "See 02-tests.mdc for testing policy"), and
run a quick scan of both files to ensure no other test-policy discrepancies
remain.

**✔️ Don't worry about tests:** Write or run tests ONLY if specifically instructed

**🧠 AI Agent Protocol:** ULTRATHINK → STEP BY STEP → ACT
83 changes: 61 additions & 22 deletions .cursor/rules/01-architecture.mdc
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
---
globs: app/**/*.php,templates/**/*.yaml
globs: app/**/*.php,tests/**/*.php
alwaysApply: false
---

### Architecture (MANDATORY)
## Architecture Rules

- **PHP:** PSR-12, strict types, PHP 8.x features (unions, match, attributes, readonly);
- **Imports:** Always add `use` statements instead of fully qualified class names;
- **Type Safety:** All methods must have explicit return types with proper generics (e.g., `Collection<int, User>`).
- **PHP:** PSR-12, strict types, PHP 8.x features (unions, match, attributes, readonly)
- **Imports:** Always add `use` statements instead of fully qualified class names
- **Type Safety:** All methods must have explicit return types with proper generics (e.g., `Collection<int, User>`)

**🚨 Architecture rules are IMMUTABLE - fix violating code, not the architecture rules**

### Symfony Patterns

- **Dependency Injection:** instead of manually resolving and instantiating classes
- **Symfony Classes:** instead of native PHP functions for easier mocking during testing (eg. `Filesystem::`, `Process::`, etc.)

Comment on lines +14 to +18

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

🧩 Analysis chain

Symfony dependencies: verify availability or scope the rule.

Mandating Symfony wrappers over natives requires symfony/console, symfony/filesystem, symfony/process in composer.json. Either verify they exist or soften the rule to “prefer when available.”


🏁 Script executed:

#!/bin/bash
# Verify required packages exist
jq -r '.require, .["require-dev"] | keys[]' composer.json 2>/dev/null | rg -n '^(symfony/(console|filesystem|process))$' -n -C0 || true

Length of output: 38


Require symfony/process or relax the "Symfony Patterns" rule

composer.json contains symfony/console and symfony/filesystem but not symfony/process — either add symfony/process to composer.json or change .cursor/rules/01-architecture.mdc (lines 14–18) to "prefer Symfony wrappers when available" instead of mandating them.

🤖 Prompt for AI Agents
In .cursor/rules/01-architecture.mdc around lines 14 to 18, the rule mandates
using Symfony wrappers (e.g., Process::) but the project lacks symfony/process;
either add symfony/process to composer.json (require "symfony/process":
"^version" and run composer install) so the rule can remain strict, or change
the rule text to a relaxed phrasing such as "prefer Symfony wrappers when
available (use Symfony classes like Filesystem::, Console::, Process:: where the
corresponding Symfony package is installed) " so it no longer mandates packages
not present.

### Dependency Injection

- Dependencies are automatically injected by the `app/Container` class
- ALL dependencies MUST be injected through constructors - NO manual instantiation
- **Exceptions:** Only value objects, DTOs, and pure data structures can be manually instantiated

Comment on lines +21 to +24

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Container existence and DI feasibility.

The rule depends on an app/Container class. Verify it exists and is wired; otherwise this becomes aspirational and will cause drift.


🏁 Script executed:

#!/bin/bash
# Look for a Container class in app/
fd -a 'Container.php' app/ | xargs -r -I{} rg -nP '^\s*class\s+Container\b' {}

Length of output: 0


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== Search: class Container definitions ==="
rg -nP --hidden --follow -S '\bclass\s+Container\b' || true

echo "=== Search: files named Container.php ==="
find . -type f -iname 'Container.php' -print || true

echo "=== Search: literal 'app/Container' occurrences ==="
rg -n --hidden --follow -S 'app/Container' || true

echo "=== Search: namespaced App\\Container occurrences ==="
rg -n --hidden --follow -S 'App\\\\Container' || true

echo "=== composer.json (if present) ==="
if [ -f composer.json ]; then sed -n '1,240p' composer.json; else echo "no composer.json"; fi

echo "=== composer.lock (scan for DI packages) ==="
if [ -f composer.lock ]; then rg -n --hidden --follow -S 'php-di|illuminate/container|symfony/component/dependency-injection|pimple' composer.lock || true; else echo "no composer.lock"; fi

Length of output: 2170


app/Container referenced in rule but missing — implement it or update the rule

The rule (.cursor/rules/01-architecture.mdc:21-24) claims dependencies are injected by app/Container, but the repo contains no app/Container class or Container.php; this makes the rule aspirational and will cause drift.

  • Option A: Add a concrete Container at app/Container.php (namespace Bigpixelrocket\DeployerPHP), implement/wire DI (PSR-11 or project contract) and update bootstrap to use it.
  • Option B: Remove the hard reference to app/Container and reword the rule to require a DI contract (e.g., PSR-11) or the actual DI mechanism used.

### Command Layer

- Commands handle user interaction (input/output) and orchestrate Services
Expand All @@ -26,31 +38,58 @@ globs: app/**/*.php,templates/**/*.yaml
- Services handle core business logic, external API calls, file operations
- Complex orchestration shared by multiple Commands should be extracted to dedicated Services

### Shared Utilities

- Common path resolution, configuration loading goes in dedicated utility classes
- Utility classes are static or singleton patterns for simple operations
- No business logic or state in utilities - pure functions only

### Console I/O Rules

- Only Commands perform console input/output operations
- Services return exceptions or structured data for Commands to handle
- Use SymfonyStyle consistently for all user-facing output
- Services return exceptions or structured data for Commands to handle
- Validation errors and business exceptions bubble up to Commands for display

### Dependency Injection (Mandatory)

- ALL dependencies MUST be injected through constructors - NO manual instantiation
- Commands receive Services via constructor injection
- Services receive other Services/utilities via constructor injection
- Use ServiceContainer or dependency injection container for all object creation
- NEVER use `new ClassName()` inside methods - always inject dependencies
- Exceptions: Only value objects, DTOs, and pure data structures can be manually instantiated

### Dependency Rules

- Commands depend on Services
- Services depend on other Services or utilities
- No circular dependencies allowed
- All dependencies declared in constructor signatures

**IMPORTANT:** No circular dependencies allowed.

### Comments

**DocBlock:** Add docblock comments with minimalist descriptions, parameters and return types for classes and functions;

**Comment everything:** Use comments to separate sections and explain or summarize complex logic;

```
//
// {Section Header}
// -------------------------------------------------------------------------------

//
// {Section Subheader}

// {Paragraph}
```

- Use comments as visual separators to help separate different sections
- Separate section headers, subheaders and paragraphs with a single newline
- Avoid commenting the obvious or leaving comments behind when removing code

**ALWAYS use the correct section header comment format and not the simplified one:**

```
//
// {Section Header}
// ---- ❌ Too few dashes
```

### Quality Gates

**ALWAYS run these commands against the files you have touched and fix any issues BEFORE considering a task complete:**

```bash
vendor/bin/rector $CHANGED_PHP_FILES # Code improvements (changed files only)
vendor/bin/pint $CHANGED_PHP_FILES # Fix code style (changed files only)

# Static analysis excluding tests (never do static analysis against tests)
vendor/bin/phpstan analyze $CHANGED_PHP_FILES_EXCEPT_TESTS
```
35 changes: 0 additions & 35 deletions .cursor/rules/02-code-quality.mdc

This file was deleted.

102 changes: 102 additions & 0 deletions .cursor/rules/02-tests.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
globs: app/**/*.php,tests/**/*.php
---

## Testing Rules

**Philosophy:** "A test that never fails is not a test, it's a lie."

**Framework:** Pest exclusively with `it()` syntax, 80%+ coverage

### Running Tests

- `composer pest` - run entire test suite in parallel, with coverage
- `vendor/bin/pest $TEST_FILE` - run specific test file

### Test Minimalism

**Target:** Keep test files under 1.8x the size of source code they test.

**Rules:**

- Test core business logic only, skip framework testing
- Use dataset-driven testing: `->with([])` for multiple scenarios
- Eliminate test overlap: no two tests covering same functionality
- Consolidate assertions: `expect($x)->toBe(1)->and($y)->toBe(2)`
- Mock external dependencies only
- No performance tests unless performance is the primary concern
- Don't sacrifice readability for ratio targets

**Don't consolidate when:**

- Different public methods
- Exception vs normal flow tests
- Different setup requirements
- Distinct business logic

### AAA Pattern (MANDATORY)

```php
it('does something specific', function () {
// ARRANGE
$service = new Service(mock(Dependency::class));

// ACT
$result = $service->performAction();

// ASSERT
expect($result)->toBe('expected');

// CLEANUP (when needed)
$this->resetTimeState();
unlink($tempFile);
});
```

**Exception tests:** Use `// ACT & ASSERT` when act triggers assertion.

**Organization:** Use `describe()` blocks, `beforeEach()` setup, extract helpers/traits for DRY tests.

### Testing Patterns

**❌ FORBIDDEN:**

```php
expect($x)->toBeInstanceOf(Class::class); // Type-only testing
expect($x)->toBeArray(); // Generic assertions
expect($x)->not->toBeNull(); // Meaningless
expect(true)->toBeTrue(); // Literally meaningless
sleep(...); // Use time mocking
```

**✅ REQUIRED:**

```php
expect($config->getValue('host'))->toBe('example.com');
expect($this->validator->isValid($input))->toBe($expected);
$mock->shouldReceive('method')->with('param')->andReturn('result');
```

### Test Types

**Unit Tests:**

- Mock all external dependencies (filesystem, HTTP, processes)
- Test single units in isolation
- Complete in milliseconds

**Integration Tests:**

- Real file operations and external processes
- CLI commands and full workflows

**Layer Strategy:**

- CLI Commands → Integration tests
- Business Services → Unit tests (mocked dependencies)
- Utilities/Helpers → Unit tests

### Static Analysis

- Ignore PHPStan issues in tests - focus on test functionality over compliance
- Avoid excessive phpdoc just to appease types
Loading