Skip to content

feat: inventory crud - #16

Merged
loadinglucian merged 8 commits into
mainfrom
feat/inventory-crud
Sep 27, 2025
Merged

feat: inventory crud#16
loadinglucian merged 8 commits into
mainfrom
feat/inventory-crud

Conversation

@loadinglucian

@loadinglucian loadinglucian commented Sep 27, 2025

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added inventory management using a local YAML file with dot-notation create/read/update/delete and graceful handling of missing files.
  • Tests

    • Introduced comprehensive unit tests for inventory management and a filesystem mocking helper; minor test cleanup elsewhere.
  • Documentation

    • Updated internal guidance and review instructions; added supporting command notes for analysis, branch comparison, diff, and reporting.
  • Chores

    • Minor formatting cleanup in tests.

- Remove monolithic analyze.md command
- Add _analyze.md for general analysis functionality
- Add _in-branch.md for branch-specific analysis
- Add _in-diff.md for working tree analysis
- Add report.md for reporting without changes

This modular approach allows for more targeted analysis commands
and better separation of concerns in cursor automation.
- Add 'meticulously catalog and analyze' prefix to create-branch.md
- Add 'meticulously catalog and analyze' prefix to create-commits.md
- Clarify that commands should analyze both staged and unstaged changes

This provides clearer instructions for more thorough analysis
when creating branches and commits.
Update testing exception rule to allow direct instantiation in tests
rather than only Container instantiation. This provides clearer
guidance that tests can use direct instantiation for better mocking
and isolation while production code must use App::build().
@coderabbitai

coderabbitai Bot commented Sep 27, 2025

Copy link
Copy Markdown
Contributor

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

📥 Commits

Reviewing files that changed from the base of the PR and between cac417f and 53af1a9.

📒 Files selected for processing (7)
  • .cursor/commands/_analyze.md (1 hunks)
  • .cursor/commands/_report.md (1 hunks)
  • .cursor/commands/_review.md (1 hunks)
  • .cursor/commands/review.md (0 hunks)
  • tests/TestHelpers.php (2 hunks)
  • tests/Unit/EnvServiceTest.php (0 hunks)
  • tests/Unit/InventoryServiceTest.php (1 hunks)
 _________________________________
< WMD: Weapons of Mass Debugging. >
 ---------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).

Tip

CodeRabbit can use your project's PHP CodeSniffer (phpcs) configuration to improve the quality of PHP code reviews.

Add a phpcs.xml or phpcs.xml.dist file to your project to customize how CodeRabbit runs phpcs. See PHP CodeSniffer documentation for more details.

Walkthrough

Adds a new YAML-backed InventoryService with dot-path CRUD, accompanying comprehensive unit tests. Updates Cursor command docs and architecture rule wording. Minor whitespace tweak in VersionService test. Introduces a new report command doc and small instruction refinements in several Cursor command files.

Changes

Cohort / File(s) Summary of Changes
Cursor command docs adjustments
.cursor/commands/_analyze.md, .cursor/commands/_in-branch.md, .cursor/commands/_in-diff.md, .cursor/commands/analyze.md, .cursor/commands/create-branch.md, .cursor/commands/create-commits.md, .cursor/commands/report.md
Added three new single-line directive files; removed one line from analyze.md; refined wording in create-branch.md and create-commits.md to emphasize working tree (staged/unstaged); added report.md with a single instruction.
Architecture rule doc tweak
.cursor/rules/01-architecture.mdc
Revised testing exception rationale; expanded example to show direct instantiation vs. production App::build(...).
Inventory service
app/Services/InventoryService.php
New InventoryService with YAML-backed store at ~/.deployer/inventory.yml; provides set, get, getAll, has, delete; handles dot-path nested access; ensures directory creation; uses Symfony Filesystem; includes read/write helpers and error handling.
Unit tests: Inventory
tests/Unit/InventoryServiceTest.php
New comprehensive tests using a mock Filesystem; covers CRUD operations, nested paths, missing files, YAML errors, workflow scenarios, and write/dir-creation failure handling.
Unit tests: Version
tests/Unit/VersionServiceTest.php
Removed a blank line; no behavioral changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant InventoryService
  participant Filesystem
  participant YAML as Yaml Parser

  rect rgb(240,248,255)
  note over Client,InventoryService: Get value by dot-path
  Client->>InventoryService: get(path)
  InventoryService->>Filesystem: read ~/.deployer/inventory.yml
  alt file exists
    Filesystem-->>InventoryService: contents
    InventoryService->>YAML: parse(contents)
    YAML-->>InventoryService: inventory array
    InventoryService-->>Client: value or null
  else missing file
    InventoryService-->>Client: null or []
  end
  end

  rect rgb(245,255,240)
  note over Client,InventoryService: Set value by dot-path
  Client->>InventoryService: set(path, value)
  InventoryService->>Filesystem: read ~/.deployer/inventory.yml (optional)
  InventoryService->>YAML: parse(existing or empty)
  YAML-->>InventoryService: inventory array
  InventoryService->>InventoryService: upsert nested value
  InventoryService->>Filesystem: ensure ~/.deployer exists
  InventoryService->>YAML: dump(inventory)
  YAML-->>InventoryService: yaml string
  InventoryService->>Filesystem: write inventory.yml
  InventoryService-->>Client: void
  end

  rect rgb(255,245,245)
  note over InventoryService,Filesystem: Error handling
  Filesystem--x InventoryService: mkdir/write failure
  InventoryService-->>Client: throw RuntimeException
  YAML--x InventoryService: parse failure
  InventoryService-->>Client: throw ParseException
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • bigpixelrocket/deployer-php#12 — Also updates Cursor configuration and rules, including overlapping files under .cursor/commands/* and .cursor/rules/01-architecture.mdc.

Poem

In burrows deep I stash my YAML stash,
Dot-paths hop along—set, get, dash!
A twig of tests to weave the proof,
Filesystem snug beneath the roof.
I twitch my nose—reports in paw—
New service burrows in without a flaw. 🐇📦

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.42% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title “feat: inventory crud” succinctly and accurately conveys the primary change, which is the introduction of CRUD functionality for inventory management via the new InventoryService. It clearly highlights the main feature added without extraneous detail. The conventional commit format further clarifies the nature of the change.

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

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 4

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4bd78a and cac417f.

📒 Files selected for processing (11)
  • .cursor/commands/_analyze.md (1 hunks)
  • .cursor/commands/_in-branch.md (1 hunks)
  • .cursor/commands/_in-diff.md (1 hunks)
  • .cursor/commands/analyze.md (0 hunks)
  • .cursor/commands/create-branch.md (1 hunks)
  • .cursor/commands/create-commits.md (1 hunks)
  • .cursor/commands/report.md (1 hunks)
  • .cursor/rules/01-architecture.mdc (1 hunks)
  • app/Services/InventoryService.php (1 hunks)
  • tests/Unit/InventoryServiceTest.php (1 hunks)
  • tests/Unit/VersionServiceTest.php (0 hunks)
💤 Files with no reviewable changes (2)
  • tests/Unit/VersionServiceTest.php
  • .cursor/commands/analyze.md
🧰 Additional context used
📓 Path-based instructions (5)
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

Organize code by grouping related functions into comment-separated sections; prefer alphabetical ordering when it doesn’t conflict with logical grouping

Files:

  • tests/Unit/InventoryServiceTest.php
  • app/Services/InventoryService.php
tests/**

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

Do not write or run tests unless specifically instructed

Files:

  • tests/Unit/InventoryServiceTest.php
tests/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/02-tests.mdc)

tests/**/*.php: Use Pest exclusively for PHP tests with it() syntax.
Keep each test file under 1.8x the size of the source code it tests.
Test core business logic only; do not test the framework.
Use dataset-driven testing (->with([...])) for multiple scenarios.
Consolidate related assertions using expect(...)->and(...).
Mock only external dependencies; avoid mocking internal logic.
Do not write performance tests unless performance is the primary concern.
Do not consolidate tests when covering different public methods.
Do not consolidate exception-flow tests with normal-flow tests.
Do not consolidate when setup requirements differ.
Do not consolidate when testing distinct business logic.
Follow the AAA pattern (Arrange, Act, Assert) in tests.
For exception tests, use a combined // ACT & ASSERT section when the act triggers the assertion.
Organize tests using describe() blocks, beforeEach() setup, and extracted helpers/traits for DRY.
Avoid type-only and generic assertions (e.g., toBeInstanceOf, toBeArray, not->toBeNull, expect(true)).
Avoid sleep(...); use time mocking instead.
Write meaningful assertions tied to behavior and outcomes (e.g., checking config values, validator results, and mock expectations).
Unit tests must mock all external dependencies, test single units in isolation, and complete in milliseconds.
Integration tests should use real file operations and external processes; cover CLI commands and full workflows.
Ignore PHPStan issues in tests; prioritize test functionality over static analysis compliance.
Avoid excessive PHPDoc in tests added solely to appease types.

In tests, direct Container instantiation is allowed to isolate state (new Container())

Files:

  • tests/Unit/InventoryServiceTest.php
{app,tests}/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

{app,tests}/**/*.php: Follow PSR-12, declare strict_types, and target PHP 8.x features (unions, match, attributes, readonly) in all PHP files
Use import statements (use Foo\Bar;) instead of fully qualified class names in code
All methods must declare explicit return types, using proper generics in docblocks (e.g., Collection<int, User>)
Add concise DocBlock comments for classes and functions including descriptions, parameters, and return types
Use comments as visual separators for sections/subsections/paragraphs with the prescribed formatting and spacing
Always use the full section header comment format with the correct dash line length (do not use simplified form)

Files:

  • tests/Unit/InventoryServiceTest.php
  • app/Services/InventoryService.php
app/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

app/**/*.php: Use App::build(ClassName::class) for object creation instead of new, except for value objects/DTOs/pure data structures
Prefer Symfony helper classes (e.g., Filesystem, Process) over native PHP functions for easier mocking
Commands handle user interaction and orchestration; they must not contain business logic
Commands must not duplicate orchestration logic; extract shared orchestration into Services
Commands should not invoke other commands (no proxy commands)
Use SymfonyStyle consistently for all user-facing console output
Services provide atomic, reusable functionality and must be stateless and dependency-injected
Services perform business logic, external API calls, and file operations with no console I/O
Services accept and return plain PHP data types
Only Commands perform console input/output operations; Services must not perform I/O
Declare all dependencies in constructor signatures (constructor injection)
No circular dependencies between services/commands

Files:

  • app/Services/InventoryService.php
🧠 Learnings (2)
📚 Learning: 2025-09-24T19:26:21.229Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-09-24T19:26:21.229Z
Learning: Applies to tests/**/*.php : In tests, direct Container instantiation is allowed to isolate state (new Container())

Applied to files:

  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-09-24T19:26:21.229Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-09-24T19:26:21.229Z
Learning: Applies to app/**/*.php : Use App::build(ClassName::class) for object creation instead of new, except for value objects/DTOs/pure data structures

Applied to files:

  • .cursor/rules/01-architecture.mdc
🧬 Code graph analysis (2)
tests/Unit/InventoryServiceTest.php (1)
app/Services/InventoryService.php (5)
  • InventoryService (37-270)
  • set (56-63)
  • has (89-95)
  • delete (100-107)
  • getAll (81-84)
app/Services/InventoryService.php (1)
tests/Unit/InventoryServiceTest.php (2)
  • mkdir (37-42)
  • dumpFile (44-49)
🪛 PHPMD (2.15.0)
tests/Unit/InventoryServiceTest.php

27-27: Avoid unused parameters such as '$files'. (undefined)

(UnusedFormalParameter)


37-37: Avoid unused parameters such as '$dirs'. (undefined)

(UnusedFormalParameter)


37-37: Avoid unused parameters such as '$mode'. (undefined)

(UnusedFormalParameter)


44-44: Avoid unused parameters such as '$filename'. (undefined)

(UnusedFormalParameter)


44-44: Avoid unused parameters such as '$content'. (undefined)

(UnusedFormalParameter)

🪛 GitHub Actions: Pest
tests/Unit/InventoryServiceTest.php

[error] 16-16: PHP Fatal error: Cannot redeclare function mockFilesystem() (previously declared in EnvServiceTest.php:15) during 'vendor/bin/pest --parallel --coverage'.

🪛 GitHub Actions: Rector
tests/Unit/InventoryServiceTest.php

[error] 17-17: vendor/bin/rector --dry-run: ReadOnlyPropertyRector would modify property declarations to 'readonly' in tests/Unit/InventoryServiceTest.php (line 17) — private to private readonly. Exit code 2.

Comment on lines +53 to +107
/**
* Set a value using dot notation path.
*/
public function set(string $path, mixed $value): void
{
$inventory = $this->readInventory();
$segments = $this->parsePath($path);

$this->setByPath($inventory, $segments, $value);
$this->writeInventory($inventory);
}

/**
* Get a value using dot notation path.
*/
public function get(string $path): mixed
{
$inventory = $this->readInventory();
$segments = $this->parsePath($path);

return $this->getByPath($inventory, $segments);
}

/**
* Get the entire inventory structure.
*
* @return array<string, mixed>
*/
public function getAll(): array
{
return $this->readInventory();
}

/**
* Check if a path exists using dot notation.
*/
public function has(string $path): bool
{
$inventory = $this->readInventory();
$segments = $this->parsePath($path);

return $this->hasByPath($inventory, $segments);
}

/**
* Delete a value using dot notation path.
*/
public function delete(string $path): void
{
$inventory = $this->readInventory();
$segments = $this->parsePath($path);

$this->unsetByPath($inventory, $segments);
$this->writeInventory($inventory);
}

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 | 🟠 Major

Docblocks need parameter/return tags per repo rules.

Per the repository’s PHP guidelines, every method docblock must include the parameter and return annotations. All of the public methods here are missing those tags. Please add the required annotations across the class.

As per coding guidelines

Here’s an example of the expected format:

     /**
      * Set a value using dot notation path.
+     *
+     * @param string $path
+     * @param mixed  $value
+     *
+     * @return void
      */
📝 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
/**
* Set a value using dot notation path.
*/
public function set(string $path, mixed $value): void
{
$inventory = $this->readInventory();
$segments = $this->parsePath($path);
$this->setByPath($inventory, $segments, $value);
$this->writeInventory($inventory);
}
/**
* Get a value using dot notation path.
*/
public function get(string $path): mixed
{
$inventory = $this->readInventory();
$segments = $this->parsePath($path);
return $this->getByPath($inventory, $segments);
}
/**
* Get the entire inventory structure.
*
* @return array<string, mixed>
*/
public function getAll(): array
{
return $this->readInventory();
}
/**
* Check if a path exists using dot notation.
*/
public function has(string $path): bool
{
$inventory = $this->readInventory();
$segments = $this->parsePath($path);
return $this->hasByPath($inventory, $segments);
}
/**
* Delete a value using dot notation path.
*/
public function delete(string $path): void
{
$inventory = $this->readInventory();
$segments = $this->parsePath($path);
$this->unsetByPath($inventory, $segments);
$this->writeInventory($inventory);
}
/**
* Set a value using dot notation path.
*
* @param string $path
* @param mixed $value
*
* @return void
*/
public function set(string $path, mixed $value): void
{
$inventory = $this->readInventory();
$segments = $this->parsePath($path);
$this->setByPath($inventory, $segments, $value);
$this->writeInventory($inventory);
}
/**
* Get a value using dot notation path.
*/
public function get(string $path): mixed
{
$inventory = $this->readInventory();
$segments = $this->parsePath($path);
return $this->getByPath($inventory, $segments);
}
/**
* Get the entire inventory structure.
*
* @return array<string, mixed>
*/
public function getAll(): array
{
return $this->readInventory();
}
/**
* Check if a path exists using dot notation.
*/
public function has(string $path): bool
{
$inventory = $this->readInventory();
$segments = $this->parsePath($path);
return $this->hasByPath($inventory, $segments);
}
/**
* Delete a value using dot notation path.
*/
public function delete(string $path): void
{
$inventory = $this->readInventory();
$segments = $this->parsePath($path);
$this->unsetByPath($inventory, $segments);
$this->writeInventory($inventory);
}
🤖 Prompt for AI Agents
In app/Services/InventoryService.php around lines 53 to 107, the public method
docblocks for set, get, getAll, has, and delete are missing @param/@return
annotations; update each docblock to include the parameter and return tags per
repo rules (set: @param string $path, @param mixed $value, @return void; get:
@param string $path, @return mixed; getAll: @return array<string,mixed>; has:
@param string $path, @return bool; delete: @param string $path, @return void),
keeping descriptions concise and matching existing docblock style.

Comment on lines +238 to +244
$raw = $this->filesystem->readFile($path);
$parsed = Yaml::parse($raw);

/** @var array<string, mixed> $result */
$result = is_array($parsed) ? $parsed : [];
return $result;
}

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 | 🔴 Critical

Undefined Filesystem::readFile call will fatal at runtime.

Symfony\Component\Filesystem\Filesystem does not provide a readFile() method, so this call will immediately trigger “Call to undefined method Filesystem::readFile()” in production. Please switch to a supported way of reading the file (e.g., wrap file_get_contents with proper error handling) before parsing the YAML.

Apply this diff to fix the problem:

-        $raw = $this->filesystem->readFile($path);
+        $raw = @file_get_contents($path);
+        if ($raw === false) {
+            throw new \RuntimeException("Failed to read inventory file at {$path}");
+        }
📝 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
$raw = $this->filesystem->readFile($path);
$parsed = Yaml::parse($raw);
/** @var array<string, mixed> $result */
$result = is_array($parsed) ? $parsed : [];
return $result;
}
$raw = @file_get_contents($path);
if ($raw === false) {
throw new \RuntimeException("Failed to read inventory file at {$path}");
}
$parsed = Yaml::parse($raw);
/** @var array<string, mixed> $result */
$result = is_array($parsed) ? $parsed : [];
return $result;
}
🤖 Prompt for AI Agents
In app/Services/InventoryService.php around lines 238 to 244, the code calls
$this->filesystem->readFile($path) which does not exist on Symfony's Filesystem
and will fatal; replace that call with a safe file read: check file_exists and
is_readable($path) (or use @file_get_contents with error check), retrieve the
file contents into a variable, throw or log a descriptive error if reading
fails, only pass non-false content to Yaml::parse and guard that parse result is
an array before returning it (otherwise return an empty array); ensure any
exceptions from YAML parsing are caught or allowed to bubble with context so
runtime failures are handled cleanly.

Comment thread tests/Unit/InventoryServiceTest.php Outdated
Comment thread tests/Unit/InventoryServiceTest.php Outdated
Comment on lines +18 to +24
return new class ($exists, $yamlContent, $throwOnMkdir, $throwOnDump) extends Filesystem {
public function __construct(
private bool $exists,
private string $yamlContent,
private bool $throwOnMkdir,
private bool $throwOnDump
) {

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 | 🟠 Major

Mark mock filesystem properties as readonly

Rector fails because these constructor-promoted properties are never mutated after instantiation. Declare them readonly to satisfy the code-style rule and unblock the Rector check.

-            private bool $exists,
-            private string $yamlContent,
-            private bool $throwOnMkdir,
-            private bool $throwOnDump
+            private readonly bool $exists,
+            private readonly string $yamlContent,
+            private readonly bool $throwOnMkdir,
+            private readonly bool $throwOnDump
📝 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
return new class ($exists, $yamlContent, $throwOnMkdir, $throwOnDump) extends Filesystem {
public function __construct(
private bool $exists,
private string $yamlContent,
private bool $throwOnMkdir,
private bool $throwOnDump
) {
return new class ($exists, $yamlContent, $throwOnMkdir, $throwOnDump) extends Filesystem {
public function __construct(
private readonly bool $exists,
private readonly string $yamlContent,
private readonly bool $throwOnMkdir,
private readonly bool $throwOnDump
) {
🤖 Prompt for AI Agents
In tests/Unit/InventoryServiceTest.php around lines 18 to 24, the anonymous
class constructor promotes properties that are never mutated; update the
constructor-promoted properties to be readonly by adding the readonly modifier
to each promoted parameter (bool $exists, string $yamlContent, bool
$throwOnMkdir, bool $throwOnDump) so they become readonly properties and satisfy
the Rector code-style rule.

Eliminates fatal 'Cannot redeclare function mockFilesystem()' error by:

- Creating unified mockFilesystem() in tests/TestHelpers.php with comprehensive
  error simulation (throwOnRead, throwOnMkdir, throwOnDump parameters)
- Removing duplicate implementations from EnvServiceTest.php and InventoryServiceTest.php
- Maintaining backward compatibility for EnvServiceTest calls
- Adding throwOnRead=false parameter to InventoryServiceTest calls
- Supporting all required Filesystem methods: exists(), readFile(), mkdir(), dumpFile()

All 112 tests now pass with 92.8% coverage.
- Replace report.md/review.md with _report.md/_review.md
- Update _analyze.md capitalization for consistency
- Refine command descriptions for clearer intent
@loadinglucian
loadinglucian merged commit a443994 into main Sep 27, 2025
4 of 5 checks passed
@loadinglucian
loadinglucian deleted the feat/inventory-crud branch September 27, 2025 13:19
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.

1 participant