Skip to content

Conversation

@jordanpartridge
Copy link
Contributor

@jordanpartridge jordanpartridge commented Dec 14, 2025

Summary

  • Adds synapse-sentinel/gate GitHub Action for universal code quality checks
  • Runs tests, coverage (50% threshold), security audit, and Pest syntax validation

Expected Behavior

The gate will likely fail initially because:

  1. Tests use test() function instead of describe()/it() blocks
  2. This is intentional - testing that the action works correctly

Once validated, we can either:

  • Convert tests to describe()/it() syntax
  • Make syntax check optional in the gate action

Summary by CodeRabbit

  • Chores

    • Replaced multiple CI workflows with a consolidated "Gate" workflow enforcing checks and a 50% coverage threshold; removed separate style, static analysis, and matrix test workflows.
  • New Features

    • Added full support for issue comments, issue events/timeline, and milestones (list, get, create, update, delete, close/reopen).
    • Introduced immutable data objects for Comment, IssueEvent, TimelineEvent, and Milestone.
  • Documentation

    • Added comprehensive usage docs for Issue Events and Timeline API.
  • Tests

    • Added extensive unit tests for new data models, request classes, traits, and service behaviours.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 14, 2025

Warning

Rate limit exceeded

@jordanpartridge has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 7 minutes and 21 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between bdd3c58 and e38f599.

📒 Files selected for processing (16)
  • .claude/reviews/.env.example (1 hunks)
  • .claude/reviews/2025-12-14_00-19-46/INDEX.md (1 hunks)
  • .claude/reviews/2025-12-14_00-19-46/REVIEW_SUMMARY.txt (1 hunks)
  • .claude/reviews/2025-12-14_00-19-46/detailed-findings.md (1 hunks)
  • .claude/reviews/2025-12-14_00-19-46/metadata.json (1 hunks)
  • .claude/reviews/2025-12-14_00-19-46/pr-context.md (1 hunks)
  • .claude/reviews/2025-12-14_00-19-46/questions-and-next-steps.md (1 hunks)
  • .claude/reviews/2025-12-14_00-19-46/synthesis-report.md (1 hunks)
  • .github/workflows/gate.yml (1 hunks)
  • tests/Integration/ConfigurationTest.php (1 hunks)
  • tests/Unit/Data/IssueEventTest.php (1 hunks)
  • tests/Unit/Data/IssueTest.php (1 hunks)
  • tests/Unit/Data/LabelTest.php (1 hunks)
  • tests/Unit/Data/MilestoneTest.php (1 hunks)
  • tests/Unit/Data/TimelineEventTest.php (1 hunks)
  • tests/Unit/Data/UserTest.php (1 hunks)

Walkthrough

Adds issue comments, issue events/timeline, and milestone features (interfaces, traits, request classes, data value objects, and service wiring) with corresponding unit tests and validation updates; replaces existing CI workflows by removing three workflows and adding a single sentinel gate workflow; adds documentation EVENTS_USAGE.md.

Changes

Cohort / File(s) Summary
Workflows
\.github/workflows/gate.yml, \.github/workflows/code-style.yml, \.github/workflows/static-analysis.yml, \.github/workflows/tests.yml
Added gate.yml (sentinel gate job invoking synapse-sentinel/gate@main, triggers on push/pull_request to master, permissions: contents read, checks write, inputs: check=certify, coverage-threshold=50). Removed three prior CI workflows (code-style, static-analysis, tests).
Public contracts / service wiring
src/Contracts/IssuesServiceInterface.php, src/Contracts/ManagesIssueCommentsInterface.php, src/Contracts/ManagesIssueEventsInterface.php, src/Contracts/ManagesMilestonesInterface.php, src/Services/IssuesService.php
Extended IssuesServiceInterface to include ManagesIssueEventsInterface and ManagesMilestonesInterface; added new interfaces for managing comments, events, and milestones; composed IssuesService with new traits (ManagesIssueComments, ManagesIssueEvents, ManagesMilestones).
Traits (behaviour)
src/Traits/ManagesIssueComments.php, src/Traits/ManagesIssueEvents.php, src/Traits/ManagesMilestones.php, src/Traits/ValidatesInput.php
Added traits implementing comment, event/timeline, and milestone operations (list/get/create/update/delete/close/reopen as applicable). Expanded ValidatesInput with comment/milestone-related validation and sanitization helpers (e.g., validateMilestoneData, validateDueDate, validateCommentId).
Data value objects
src/Data/Comment.php, src/Data/IssueEvent.php, src/Data/TimelineEvent.php, src/Data/Milestone.php
Added readonly value objects for Comment, IssueEvent, TimelineEvent, and Milestone with fromArray/toArray, date parsing/formatting, and helper predicates (e.g., isLabelEvent, isComment, isOverdue, completionPercentage).
Requests (Saloon)
src/Requests/Comments/*, src/Requests/Events/*, src/Requests/Milestones/* (many files; see paths)
Added typed Saloon request classes for comments (List/Get/Create/Update/Delete), events (list issue events, timeline, repo events), and milestones (List/Get/Create/Update/Delete). Methods implement resolveEndpoint, defaultBody/defaultQuery, appropriate HTTP methods, and preview header for timeline.
Tests
tests/Unit/Data/*, tests/Unit/Requests/*, tests/Unit/Traits/* (many files; see paths)
Added comprehensive unit tests covering new data models (Comment, IssueEvent, TimelineEvent, Milestone), request classes (comments/events/milestones), and trait behaviours (ManagesIssueComments, ManagesIssueEvents, ManagesMilestones).
Docs
EVENTS_USAGE.md
New documentation describing Issue Events and Timeline API usage, DTOs, examples, and testing notes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review attention areas:
    • Validation logic in src/Traits/ValidatesInput.php (edge cases, date parsing, sanitization).
    • Trait methods mapping API responses to DTOs (fromArray/downstream assumptions) in ManagesIssueEvents/ManagesMilestones/ManagesIssueComments.
    • Endpoint correctness and headers for timeline request (preview Accept header).
    • Tests matching request/response shapes and use of mock client expectations.

Possibly related issues

Possibly related PRs

Poem

🐇 I nibbled at code and stitched new threads,
Comment crumbs and timelines in my sheds.
Milestones hop in, events trace the trail,
A carrot of tests ensures none fail.
I twitch, I code — a tiny rabbit's tale.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.43% 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 PR title 'chore: add synapse-sentinel/gate workflow' accurately and concisely describes the main change: adding a new GitHub Actions workflow file for the synapse-sentinel/gate action.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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

🧹 Nitpick comments (2)
.github/workflows/gate.yml (2)

9-16: Add explicit permissions: for least-privilege GITHUB_TOKEN usage.
Start with contents: read and add only what synapse-sentinel/gate actually needs (often checks: write / pull-requests: write for annotations).

 name: Gate

+permissions:
+  contents: read
+
 on:
   push:
     branches: [master]
   pull_request:
     branches: [master]

3-16: Rollout: decide whether you really want push-gating while it’s expected to fail.
Given the PR note that the syntax validation will likely fail initially, consider temporarily running only on pull_request (or making the syntax check optional) to avoid blocking direct pushes to master unintentionally.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b038292 and 9e8a315.

📒 Files selected for processing (1)
  • .github/workflows/gate.yml (1 hunks)
🔇 Additional comments (1)
.github/workflows/gate.yml (1)

3-8: Confirm branch targeting matches the repo’s default/protection rules (master vs main).
If the default branch is main (or you want this on release branches too), this workflow won’t run where you expect.

Comment on lines 14 to 16
- uses: synapse-sentinel/gate@main
with:
coverage-threshold: 50
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

🧩 Analysis chain

🏁 Script executed:

# First, let's check the current state of the workflow file
cat -n .github/workflows/gate.yml | head -20

Repository: conduit-ui/issue

Length of output: 432


🌐 Web query:

synapse-sentinel/gate GitHub action

💡 Result:

I couldn't find a public GitHub repo or Action named synapse-sentinel/gate. Did you mean a different name or a private repo? I can (a) search GitHub more widely for that exact repo, (b) look up related Actions (e.g., Synapse workspace deployment), or (c) help you create a GitHub Action named synapse-sentinel/gate — which would you prefer?

References I checked: GitHub Marketplace Synapse workspace deployment [1], Microsoft docs on Synapse CI/CD with GitHub Actions [2].


🌐 Web query:

GitHub Actions best practices pin version tag commit SHA

💡 Result:

Short summary of best practices for pinning Actions (tags vs SHAs):

  • Prefer full-length commit SHA for third-party actions — SHA is immutable and safest against tampered tags/branches. Example: uses: actions/checkout@f43a0e5 # v4.1.7. [1][2]

  • If you must use tags, pin to a specific release tag (vX.Y.Z), not floating tags (vX or main); add a comment with the tag version next to the SHA for clarity. Floating tags can be moved/changed. [2][6]

  • Avoid short (8-char) SHAs; use the full commit SHA. [3]

  • Automate updates: use Dependabot / Renovate / tools like pinact to safely update pinned SHAs/tags and surface PRs for review. Review changes before merging. [1][8]

  • Enforce and audit: enable organization/repository allowed-actions policies to require SHA pinning or block untrusted actions; use verified creators as a trust signal and audit action code where feasible. [1][2]

  • Tradeoffs: SHAs are most secure but require manual/automated updates (less convenient). Tags are convenient but mutable. Choose SHA for high-risk workflows (those that handle secrets or produce releases). [2][6]

Key references:

  • GitHub Docs — Security hardening for GitHub Actions (pin to commit SHA; policies). [1]
  • GitHub Well‑Architected — Pin versions of Actions (recommend full commit SHA, include version comment). [2]
  • Examples/tools & community guidance (pinact, Ensure SHA Pinned Actions, blog posts). [4][8]

If you want, I can: (a) show exact workflow snippets for SHA pinning; (b) add Dependabot/Renovate config to update SHAs; or (c) produce a script to extract commit SHAs for a list of actions. Which would you like?


Use a pinned version for synapse-sentinel/gate instead of @main.
Using @main allows silent behavior changes and increases supply-chain risk. Pin to a specific commit SHA (preferred for third-party actions) or a release tag.

-      - uses: synapse-sentinel/gate@main
+      - uses: synapse-sentinel/gate@<commit-sha-or-tag>
         with:
           coverage-threshold: 50

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
.github/workflows/gate.yml around lines 14 to 16: the workflow currently uses
synapse-sentinel/gate@main which risks silent upstream changes; update the
action reference to a pinned version by replacing @main with a specific release
tag or a commit SHA (preferred) for the synapse-sentinel/gate action (e.g.,
@vX.Y.Z or @<full-commit-sha>), commit the updated YAML, and verify the workflow
run succeeds.

- Remove code-style.yml, static-analysis.yml, tests.yml
- Add comprehensive gate.yml with separate jobs:
  - Tests & Coverage
  - Security Audit
  - Pest Syntax
  - Sentinel Certification (final verdict)
- Each check shows as separate entry in PR checks
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 (1)
.github/workflows/gate.yml (1)

18-18: Replace @main with a pinned version for all synapse-sentinel/gate action references.

Using @main allows silent upstream behavior changes and increases supply-chain risk. Pin each action reference to a specific commit SHA (preferred for third-party actions) or a release tag (e.g., @vX.Y.Z).

Apply this diff to pin the action versions:

-      - uses: synapse-sentinel/gate@main
+      - uses: synapse-sentinel/gate@<commit-sha-or-tag>

Repeat this change for all four occurrences (lines 18, 32, 45, 59). Once you identify the correct version, update all references consistently.

Also applies to: 32-32, 45-45, 59-59

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c270f8a and b75f39d.

📒 Files selected for processing (4)
  • .github/workflows/code-style.yml (0 hunks)
  • .github/workflows/gate.yml (1 hunks)
  • .github/workflows/static-analysis.yml (0 hunks)
  • .github/workflows/tests.yml (0 hunks)
💤 Files with no reviewable changes (3)
  • .github/workflows/static-analysis.yml
  • .github/workflows/code-style.yml
  • .github/workflows/tests.yml
🔇 Additional comments (1)
.github/workflows/gate.yml (1)

10-22: Job structure, permissions, and orchestration look sound.

The workflow correctly defines four jobs with appropriate dependencies (certify waits for tests, security, syntax), proper permissions (contents: read, checks: write), and consistent token handling. Coverage threshold is appropriately scoped to tests and certify jobs only.

Once the action version pinning is resolved, verify that the workflow executes successfully, particularly the syntax check against test() function syntax (as noted in PR objectives).

Also applies to: 24-35, 37-48, 50-62

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: 7

♻️ Duplicate comments (1)
.github/workflows/gate.yml (1)

17-22: Pin the synapse-sentinel/gate action to a specific version.

Using @main allows silent behavior changes and increases supply-chain risk. Pin to a specific commit SHA or release tag for stability.

       - uses: actions/checkout@v4
-      - uses: synapse-sentinel/gate@main
+      - uses: synapse-sentinel/gate@v1.0.0  # or commit SHA
         with:
           check: certify
           coverage-threshold: 50
🧹 Nitpick comments (7)
src/Traits/ValidatesInput.php (2)

269-277: strtotime() is too permissive for ISO 8601 validation.

strtotime() accepts many non-ISO 8601 formats (e.g., "tomorrow", "next week", "+1 day"). For strict ISO 8601 validation, use DateTime::createFromFormat() or a regex pattern.

-        if (! strtotime($trimmed)) {
-            throw new InvalidArgumentException('Due date must be a valid ISO 8601 date string');
-        }
+        $date = DateTime::createFromFormat(DateTime::ATOM, $trimmed);
+        if ($date === false) {
+            // Also try the basic ISO 8601 format GitHub commonly uses
+            $date = DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $trimmed);
+        }
+        if ($date === false) {
+            throw new InvalidArgumentException('Due date must be a valid ISO 8601 date string');
+        }

246-257: Consider consolidating duplicate state validation logic.

validateMilestoneState (lines 246-257) duplicates the logic of validateState (lines 116-127). Both validate against the same ['open', 'closed'] set. Consider extracting a shared helper or reusing validateState.

     private function validateMilestoneState(mixed $state): void
     {
-        if (! is_string($state)) {
-            throw new InvalidArgumentException('State must be a string');
-        }
-
-        $validStates = ['open', 'closed'];
-
-        if (! in_array($state, $validStates, true)) {
-            throw new InvalidArgumentException('State must be one of: open, closed');
-        }
+        $this->validateState($state);
     }
tests/Unit/Requests/Comments/DeleteCommentRequestTest.php (1)

9-14: Consider extracting reflection-based method testing to a shared helper.

The reflection logic for testing protected properties appears across multiple request test files (DeleteCommentRequestTest, GetCommentRequestTest, UpdateCommentRequestTest). A shared test helper could reduce duplication:

function assertRequestMethod(Request $request, Method $expectedMethod): void
{
    $reflection = new ReflectionClass($request);
    $property = $reflection->getProperty('method');
    expect($property->getValue($request))->toBe($expectedMethod);
}
src/Data/Comment.php (1)

9-61: Prefer DateTimeImmutable (or freeze the DateTime instances) to keep the VO truly immutable.
Even in a readonly class, DateTime can be mutated after construction (e.g., via methods called on the property).

-use DateTime;
+use DateTimeImmutable;

 readonly class Comment
 {
@@
-        public DateTime $createdAt,
-        public DateTime $updatedAt,
+        public DateTimeImmutable $createdAt,
+        public DateTimeImmutable $updatedAt,
@@
-            createdAt: new DateTime($data['created_at']),
-            updatedAt: new DateTime($data['updated_at']),
+            createdAt: new DateTimeImmutable($data['created_at']),
+            updatedAt: new DateTimeImmutable($data['updated_at']),
tests/Unit/Traits/ManagesMilestonesTest.php (1)

11-289: Consider asserting request shape in a few tests to catch regressions in request wiring.

Saloon's MockClient supports assertSent() with a closure to inspect the sent request (endpoint, method, query, body). For example, one or two tests could verify that listMilestones() builds the correct endpoint and query parameters. This is optional but useful for catching unintended changes to request construction.

src/Data/IssueEvent.php (1)

34-34: Inconsistent null handling for milestone field.

Line 34 uses $data['milestone']['title'] ?? null without checking if $data['milestone'] is an array first, while other nullable nested objects (lines 28, 32, 33) use isset($data['field']) && $data['field'] !== null before accessing nested properties. For consistency and safety, consider applying the same pattern.

Apply this diff for consistent null handling:

-            milestone: $data['milestone']['title'] ?? null,
+            milestone: isset($data['milestone']['title']) ? $data['milestone']['title'] : null,
src/Data/Milestone.php (1)

75-82: Consider more explicit DateTime instantiation.

Line 81 uses new DateTime; (without parentheses) to represent "now". While valid PHP, using new DateTime() or explicitly new DateTime('now') would be more conventional and clearer.

-        return $this->dueOn < new DateTime;
+        return $this->dueOn < new DateTime();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a19329 and bdd3c58.

📒 Files selected for processing (42)
  • .github/workflows/gate.yml (1 hunks)
  • EVENTS_USAGE.md (1 hunks)
  • src/Contracts/IssuesServiceInterface.php (1 hunks)
  • src/Contracts/ManagesIssueCommentsInterface.php (1 hunks)
  • src/Contracts/ManagesIssueEventsInterface.php (1 hunks)
  • src/Contracts/ManagesMilestonesInterface.php (1 hunks)
  • src/Data/Comment.php (1 hunks)
  • src/Data/IssueEvent.php (1 hunks)
  • src/Data/Milestone.php (1 hunks)
  • src/Data/TimelineEvent.php (1 hunks)
  • src/Requests/Comments/CreateCommentRequest.php (1 hunks)
  • src/Requests/Comments/DeleteCommentRequest.php (1 hunks)
  • src/Requests/Comments/GetCommentRequest.php (1 hunks)
  • src/Requests/Comments/ListCommentsRequest.php (1 hunks)
  • src/Requests/Comments/UpdateCommentRequest.php (1 hunks)
  • src/Requests/Events/ListIssueEventsRequest.php (1 hunks)
  • src/Requests/Events/ListIssueTimelineRequest.php (1 hunks)
  • src/Requests/Events/ListRepositoryIssueEventsRequest.php (1 hunks)
  • src/Requests/Milestones/CreateMilestoneRequest.php (1 hunks)
  • src/Requests/Milestones/DeleteMilestoneRequest.php (1 hunks)
  • src/Requests/Milestones/GetMilestoneRequest.php (1 hunks)
  • src/Requests/Milestones/ListMilestonesRequest.php (1 hunks)
  • src/Requests/Milestones/UpdateMilestoneRequest.php (1 hunks)
  • src/Services/IssuesService.php (1 hunks)
  • src/Traits/ManagesIssueComments.php (1 hunks)
  • src/Traits/ManagesIssueEvents.php (1 hunks)
  • src/Traits/ManagesMilestones.php (1 hunks)
  • src/Traits/ValidatesInput.php (2 hunks)
  • tests/Unit/Data/CommentTest.php (1 hunks)
  • tests/Unit/Data/IssueEventTest.php (1 hunks)
  • tests/Unit/Data/MilestoneTest.php (1 hunks)
  • tests/Unit/Data/TimelineEventTest.php (1 hunks)
  • tests/Unit/Requests/Comments/CreateCommentRequestTest.php (1 hunks)
  • tests/Unit/Requests/Comments/DeleteCommentRequestTest.php (1 hunks)
  • tests/Unit/Requests/Comments/GetCommentRequestTest.php (1 hunks)
  • tests/Unit/Requests/Comments/ListCommentsRequestTest.php (1 hunks)
  • tests/Unit/Requests/Comments/UpdateCommentRequestTest.php (1 hunks)
  • tests/Unit/Requests/EventRequestsTest.php (1 hunks)
  • tests/Unit/Requests/MilestoneRequestsTest.php (1 hunks)
  • tests/Unit/Traits/ManagesIssueCommentsTest.php (1 hunks)
  • tests/Unit/Traits/ManagesIssueEventsTest.php (1 hunks)
  • tests/Unit/Traits/ManagesMilestonesTest.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (27)
src/Requests/Comments/DeleteCommentRequest.php (4)
src/Requests/Comments/CreateCommentRequest.php (2)
  • __construct (18-23)
  • resolveEndpoint (25-28)
src/Requests/Comments/GetCommentRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Comments/ListCommentsRequest.php (2)
  • __construct (14-19)
  • resolveEndpoint (21-24)
src/Requests/Comments/UpdateCommentRequest.php (2)
  • __construct (18-23)
  • resolveEndpoint (25-28)
src/Requests/Milestones/GetMilestoneRequest.php (4)
src/Requests/Milestones/CreateMilestoneRequest.php (2)
  • __construct (18-22)
  • resolveEndpoint (24-27)
src/Requests/Milestones/DeleteMilestoneRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Milestones/ListMilestonesRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Milestones/UpdateMilestoneRequest.php (2)
  • __construct (18-23)
  • resolveEndpoint (25-28)
tests/Unit/Requests/EventRequestsTest.php (3)
src/Requests/Events/ListIssueEventsRequest.php (2)
  • ListIssueEventsRequest (10-30)
  • resolveEndpoint (21-24)
src/Requests/Events/ListIssueTimelineRequest.php (2)
  • ListIssueTimelineRequest (10-37)
  • resolveEndpoint (21-24)
src/Requests/Events/ListRepositoryIssueEventsRequest.php (2)
  • ListRepositoryIssueEventsRequest (10-29)
  • resolveEndpoint (20-23)
src/Requests/Comments/GetCommentRequest.php (4)
src/Requests/Comments/CreateCommentRequest.php (2)
  • __construct (18-23)
  • resolveEndpoint (25-28)
src/Requests/Comments/DeleteCommentRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Comments/ListCommentsRequest.php (2)
  • __construct (14-19)
  • resolveEndpoint (21-24)
src/Requests/Comments/UpdateCommentRequest.php (2)
  • __construct (18-23)
  • resolveEndpoint (25-28)
src/Contracts/ManagesMilestonesInterface.php (2)
src/Data/Milestone.php (1)
  • Milestone (9-94)
src/Traits/ManagesMilestones.php (7)
  • listMilestones (23-38)
  • getMilestone (40-52)
  • createMilestone (54-66)
  • updateMilestone (68-81)
  • deleteMilestone (83-95)
  • closeMilestone (97-100)
  • reopenMilestone (102-105)
tests/Unit/Requests/Comments/ListCommentsRequestTest.php (2)
src/Requests/Comments/ListCommentsRequest.php (2)
  • ListCommentsRequest (10-30)
  • resolveEndpoint (21-24)
src/Requests/Comments/CreateCommentRequest.php (1)
  • resolveEndpoint (25-28)
src/Requests/Milestones/ListMilestonesRequest.php (5)
src/Data/Milestone.php (1)
  • __construct (11-25)
src/Requests/Milestones/CreateMilestoneRequest.php (2)
  • __construct (18-22)
  • resolveEndpoint (24-27)
src/Requests/Milestones/DeleteMilestoneRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Milestones/GetMilestoneRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Milestones/UpdateMilestoneRequest.php (2)
  • __construct (18-23)
  • resolveEndpoint (25-28)
tests/Unit/Traits/ManagesIssueCommentsTest.php (3)
src/Data/Comment.php (4)
  • Comment (9-61)
  • isOwner (47-50)
  • isMember (52-55)
  • isContributor (57-60)
src/Contracts/ManagesIssueCommentsInterface.php (5)
  • listComments (15-15)
  • getComment (17-17)
  • createComment (19-19)
  • updateComment (21-21)
  • deleteComment (23-23)
src/Traits/ManagesIssueComments.php (5)
  • listComments (23-39)
  • getComment (41-53)
  • createComment (55-68)
  • updateComment (70-83)
  • deleteComment (85-97)
src/Contracts/ManagesIssueCommentsInterface.php (2)
src/Data/Comment.php (1)
  • Comment (9-61)
src/Traits/ManagesIssueComments.php (5)
  • listComments (23-39)
  • getComment (41-53)
  • createComment (55-68)
  • updateComment (70-83)
  • deleteComment (85-97)
tests/Unit/Requests/Comments/CreateCommentRequestTest.php (1)
src/Requests/Comments/CreateCommentRequest.php (2)
  • CreateCommentRequest (12-36)
  • resolveEndpoint (25-28)
src/Requests/Comments/ListCommentsRequest.php (6)
src/Requests/Comments/CreateCommentRequest.php (2)
  • __construct (18-23)
  • resolveEndpoint (25-28)
src/Requests/Comments/DeleteCommentRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Comments/GetCommentRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Comments/UpdateCommentRequest.php (2)
  • __construct (18-23)
  • resolveEndpoint (25-28)
src/Requests/Events/ListIssueEventsRequest.php (3)
  • __construct (14-19)
  • resolveEndpoint (21-24)
  • defaultQuery (26-29)
src/Requests/Events/ListIssueTimelineRequest.php (3)
  • __construct (14-19)
  • resolveEndpoint (21-24)
  • defaultQuery (26-29)
src/Contracts/ManagesIssueEventsInterface.php (1)
src/Traits/ManagesIssueEvents.php (3)
  • listIssueEvents (22-38)
  • listIssueTimeline (43-59)
  • listRepositoryEvents (64-79)
tests/Unit/Data/IssueEventTest.php (3)
src/Data/IssueEvent.php (7)
  • IssueEvent (9-72)
  • fromArray (23-36)
  • toArray (38-51)
  • isLabelEvent (53-56)
  • isAssigneeEvent (58-61)
  • isStateEvent (63-66)
  • isMilestoneEvent (68-71)
src/Data/Label.php (1)
  • Label (7-35)
src/Data/User.php (1)
  • User (7-38)
src/Requests/Milestones/CreateMilestoneRequest.php (4)
src/Requests/Milestones/DeleteMilestoneRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Milestones/GetMilestoneRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Milestones/ListMilestonesRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Milestones/UpdateMilestoneRequest.php (3)
  • __construct (18-23)
  • resolveEndpoint (25-28)
  • defaultBody (30-33)
src/Data/Milestone.php (1)
src/Data/User.php (1)
  • User (7-38)
src/Requests/Comments/CreateCommentRequest.php (4)
src/Requests/Comments/DeleteCommentRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Comments/GetCommentRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Comments/ListCommentsRequest.php (2)
  • __construct (14-19)
  • resolveEndpoint (21-24)
src/Requests/Comments/UpdateCommentRequest.php (3)
  • __construct (18-23)
  • resolveEndpoint (25-28)
  • defaultBody (30-35)
tests/Unit/Data/CommentTest.php (2)
src/Data/Comment.php (6)
  • Comment (9-61)
  • fromArray (21-32)
  • toArray (34-45)
  • isOwner (47-50)
  • isMember (52-55)
  • isContributor (57-60)
src/Data/User.php (1)
  • User (7-38)
src/Requests/Events/ListRepositoryIssueEventsRequest.php (5)
src/Data/IssueEvent.php (1)
  • __construct (11-21)
src/Requests/Comments/ListCommentsRequest.php (3)
  • __construct (14-19)
  • resolveEndpoint (21-24)
  • defaultQuery (26-29)
src/Requests/Events/ListIssueEventsRequest.php (3)
  • __construct (14-19)
  • resolveEndpoint (21-24)
  • defaultQuery (26-29)
src/Requests/Events/ListIssueTimelineRequest.php (3)
  • __construct (14-19)
  • resolveEndpoint (21-24)
  • defaultQuery (26-29)
src/Requests/Milestones/ListMilestonesRequest.php (3)
  • __construct (14-18)
  • resolveEndpoint (20-23)
  • defaultQuery (25-28)
tests/Unit/Requests/Comments/UpdateCommentRequestTest.php (1)
src/Requests/Comments/UpdateCommentRequest.php (2)
  • UpdateCommentRequest (12-36)
  • resolveEndpoint (25-28)
tests/Unit/Requests/Comments/GetCommentRequestTest.php (1)
src/Requests/Comments/GetCommentRequest.php (2)
  • GetCommentRequest (10-24)
  • resolveEndpoint (20-23)
src/Data/IssueEvent.php (2)
src/Data/User.php (1)
  • User (7-38)
src/Data/Label.php (1)
  • Label (7-35)
tests/Unit/Requests/Comments/DeleteCommentRequestTest.php (1)
src/Requests/Comments/DeleteCommentRequest.php (2)
  • DeleteCommentRequest (10-24)
  • resolveEndpoint (20-23)
src/Data/TimelineEvent.php (2)
src/Data/User.php (1)
  • User (7-38)
src/Data/Label.php (1)
  • Label (7-35)
tests/Unit/Requests/MilestoneRequestsTest.php (5)
src/Requests/Milestones/CreateMilestoneRequest.php (2)
  • CreateMilestoneRequest (12-33)
  • resolveEndpoint (24-27)
src/Requests/Milestones/DeleteMilestoneRequest.php (2)
  • DeleteMilestoneRequest (10-24)
  • resolveEndpoint (20-23)
src/Requests/Milestones/GetMilestoneRequest.php (2)
  • GetMilestoneRequest (10-24)
  • resolveEndpoint (20-23)
src/Requests/Milestones/ListMilestonesRequest.php (2)
  • ListMilestonesRequest (10-29)
  • resolveEndpoint (20-23)
src/Requests/Milestones/UpdateMilestoneRequest.php (2)
  • UpdateMilestoneRequest (12-34)
  • resolveEndpoint (25-28)
tests/Unit/Data/MilestoneTest.php (2)
src/Data/Milestone.php (7)
  • Milestone (9-94)
  • fromArray (27-44)
  • toArray (46-63)
  • isOpen (65-68)
  • isClosed (70-73)
  • isOverdue (75-82)
  • completionPercentage (84-93)
src/Data/User.php (1)
  • User (7-38)
src/Requests/Milestones/DeleteMilestoneRequest.php (5)
src/Data/Milestone.php (1)
  • __construct (11-25)
src/Requests/Milestones/CreateMilestoneRequest.php (2)
  • __construct (18-22)
  • resolveEndpoint (24-27)
src/Requests/Milestones/GetMilestoneRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Milestones/ListMilestonesRequest.php (2)
  • __construct (14-18)
  • resolveEndpoint (20-23)
src/Requests/Milestones/UpdateMilestoneRequest.php (2)
  • __construct (18-23)
  • resolveEndpoint (25-28)
src/Data/Comment.php (1)
src/Data/User.php (1)
  • User (7-38)
🪛 GitHub Actions: 🛡️ Sentinel Gate
tests/Unit/Data/IssueEventTest.php

[error] 1-1: Pest Syntax: Uses test() instead of describe/it blocks.

tests/Unit/Data/MilestoneTest.php

[error] 1-1: Pest Syntax: Uses test() instead of describe/it blocks.

tests/Unit/Data/TimelineEventTest.php

[error] 1-1: Pest Syntax: Uses test() instead of describe/it blocks.

🪛 LanguageTool
EVENTS_USAGE.md

[grammar] ~85-~85: Use a hyphen to join words.
Context: ...cts ### IssueEvent Represents a single issue event: ```php class IssueEvent { ...

(QB_NEW_EN_HYPHEN)

🔇 Additional comments (39)
src/Requests/Events/ListIssueTimelineRequest.php (1)

1-29: Clean implementation following established patterns.

The request class correctly follows the Saloon conventions and mirrors the structure of other request classes in this codebase (e.g., ListIssueEventsRequest, ListCommentsRequest).

src/Traits/ValidatesInput.php (1)

197-216: Validation helpers are well-structured.

The new validateMilestoneNumber, validateCommentId, and validateNotEmpty methods follow the established patterns and provide clear, consistent validation with meaningful error messages.

src/Requests/Events/ListRepositoryIssueEventsRequest.php (1)

1-29: LGTM!

The implementation is clean and consistent with the established patterns for request classes in this codebase. The endpoint correctly targets the repository-level issues events API.

src/Data/TimelineEvent.php (1)

9-26: Clean implementation of the value object pattern.

The readonly class with fromArray/toArray methods and predicate helpers follows the established pattern from User and Label. The use of array_filter to omit null values in toArray is a good approach for API payloads.

Also applies to: 48-66, 68-86

tests/Unit/Requests/Comments/DeleteCommentRequestTest.php (1)

8-31: Test coverage looks good.

The tests appropriately validate HTTP method, endpoint resolution, and dynamic comment ID handling. The reflection-based approach is consistent with the codebase's testing patterns.

tests/Unit/Requests/Comments/UpdateCommentRequestTest.php (1)

8-46: Comprehensive test coverage.

The tests validate all key aspects of the UpdateCommentRequest including HTTP method, endpoint resolution, request body structure, and edge cases like multiline comments. The reflection-based testing of protected methods is appropriate.

tests/Unit/Requests/Comments/GetCommentRequestTest.php (1)

8-31: Test coverage is solid.

The tests appropriately validate the GetCommentRequest behavior including HTTP method, endpoint format, and dynamic ID handling.

src/Services/IssuesService.php (1)

10-11: Clean trait composition.

The addition of ManagesIssueComments, ManagesIssueEvents, and ManagesMilestones traits follows the existing service composition pattern and integrates well with the codebase architecture.

Also applies to: 14-14, 19-20, 23-23

tests/Unit/Requests/Comments/ListCommentsRequestTest.php (1)

8-46: Thorough test coverage.

The tests validate all aspects of ListCommentsRequest including method, endpoint, query parameter handling, and the edge case of empty filters. The reflection-based approach for testing protected methods is appropriate.

src/Contracts/IssuesServiceInterface.php (1)

7-7: Interface composition aligns with service implementation.

The addition of ManagesIssueEventsInterface and ManagesMilestonesInterface to the extends clause correctly reflects the trait composition in IssuesService, maintaining consistency between the interface contract and its implementation.

tests/Unit/Data/CommentTest.php (1)

8-157: Excellent test coverage for Comment data class.

The test suite comprehensively validates the Comment data object including:

  • Factory method (fromArray) with nested User object handling
  • Serialization (toArray) with proper date formatting
  • All author association helper methods (isOwner, isMember, isContributor)
  • Edge cases (NONE association)

The test structure and assertions are well-designed.

EVENTS_USAGE.md (1)

1-273: Well-structured and comprehensive documentation.

The documentation provides excellent coverage of the Issue Events and Timeline API including:

  • Clear installation and setup instructions
  • Complete API method documentation with practical examples
  • Detailed DTO reference with helper methods
  • Comprehensive event type catalog
  • Error handling patterns
  • Real-world usage examples
  • Testing guidance and architecture overview

Note: The static analysis grammar hint about "single issue event" on line 85 is a false positive - no hyphenation is needed.

tests/Unit/Requests/Comments/CreateCommentRequestTest.php (1)

1-46: LGTM! Comprehensive test coverage with proper Pest syntax.

The test suite thoroughly validates the CreateCommentRequest class with appropriate use of describe()/it() blocks, which aligns with the sentinel gate workflow requirements. The reflection-based approach for testing protected members is valid, and the coverage includes both standard and edge cases (multiline bodies).

tests/Unit/Data/TimelineEventTest.php (1)

1-243: Intentional pipeline failure confirmed; test quality is excellent.

As noted in the PR objectives, the pipeline failure for Pest syntax (test() vs. describe()/it()) is intentional to verify the sentinel gate action is working correctly. The test coverage itself is comprehensive and well-structured, covering:

  • Multiple event types (commented, labeled, closed, committed, cross-referenced)
  • Array serialization/deserialization with proper null filtering
  • Predicate helper methods
  • Edge cases (null actor, nested structures)

Once the gate validation is complete, you'll need to either convert these tests to describe()/it() syntax or configure the gate to make syntax checks optional.

src/Requests/Comments/ListCommentsRequest.php (1)

1-30: LGTM! Clean and consistent implementation.

The request class follows the established pattern used throughout the codebase, with proper use of readonly properties, correct endpoint resolution, and appropriate query parameter handling. The implementation aligns with GitHub's API structure.

src/Requests/Milestones/GetMilestoneRequest.php (1)

1-24: LGTM! Straightforward and correct implementation.

The request class properly implements milestone retrieval with correct HTTP method, endpoint construction, and readonly properties. The implementation is consistent with related milestone request classes.

src/Requests/Milestones/DeleteMilestoneRequest.php (1)

1-24: LGTM! Proper DELETE implementation.

The request class correctly implements milestone deletion with the appropriate HTTP method and endpoint structure. The implementation follows the same clean pattern as the other milestone request classes.

src/Requests/Comments/DeleteCommentRequest.php (1)

1-24: LGTM! Clean deletion request implementation.

The request class properly implements comment deletion with correct HTTP method, endpoint construction, and readonly properties. The implementation is consistent with the other comment request classes in the PR.

src/Requests/Milestones/UpdateMilestoneRequest.php (1)

1-34: LGTM! Proper PATCH implementation with body handling.

The request class correctly implements milestone updates using the PATCH method with appropriate body handling via the HasJsonBody trait. The implementation follows the same pattern as other update operations in the codebase.

src/Requests/Milestones/ListMilestonesRequest.php (1)

1-29: LGTM! Consistent list request implementation.

The request class properly implements milestone listing with correct HTTP method, endpoint construction, and query parameter handling. The implementation follows the same clean pattern used for other list-type requests throughout the codebase.

tests/Unit/Requests/MilestoneRequestsTest.php (1)

12-137: LGTM! Tests are well-structured and comprehensive.

The test suite thoroughly validates endpoint resolution, HTTP methods, and data handling for all milestone request classes using the describe/it syntax that passes the Pest syntax check.

src/Requests/Comments/GetCommentRequest.php (1)

10-24: LGTM! Clean implementation following Saloon patterns.

The request class correctly implements the GET endpoint for fetching individual comments and is consistent with other comment-related request classes in the codebase.

tests/Unit/Data/MilestoneTest.php (1)

8-299: Test logic is solid; syntax issue is intentional.

The tests comprehensively validate the Milestone data object including fromArray/toArray conversions, state predicates (isOpen, isClosed, isOverdue), and completionPercentage calculations. The use of test() syntax instead of describe/it blocks is causing the pipeline failure, which per the PR objectives is intentional to verify the gate action.

src/Requests/Milestones/CreateMilestoneRequest.php (1)

12-33: LGTM! Correct implementation for milestone creation.

The request class properly implements the POST endpoint with JSON body handling using Saloon's HasJsonBody trait, consistent with other create/update request classes in the codebase.

tests/Unit/Traits/ManagesIssueCommentsTest.php (1)

30-215: LGTM! Comprehensive test coverage for comment management.

The test suite thoroughly validates the ManagesIssueComments trait, including CRUD operations, validation checks, and author association predicates. The use of describe/it syntax is consistent with the project's testing standards.

src/Requests/Comments/CreateCommentRequest.php (1)

12-36: LGTM! Clean implementation for comment creation.

The request class correctly implements the POST endpoint with proper JSON body structure for creating issue comments, consistent with UpdateCommentRequest and following Saloon best practices.

tests/Unit/Requests/EventRequestsTest.php (1)

10-140: LGTM! Comprehensive tests for event request classes.

The test suite thoroughly validates HTTP methods, endpoint resolution, query parameters, and the special timeline preview header for ListIssueTimelineRequest. The describe/it syntax is consistent with project standards.

tests/Unit/Data/IssueEventTest.php (1)

9-258: Test logic is comprehensive; syntax issue is intentional.

The tests thoroughly validate the IssueEvent data object including fromArray conversions for various event types (labeled, assigned, milestoned, referenced, closed), toArray serialization, and event type predicates. The use of test() syntax instead of describe/it blocks is causing the pipeline failure, which per the PR objectives is intentional to verify the gate action.

src/Traits/ManagesIssueComments.php (2)

41-97: CRUD methods are cohesive and consistent.
Validation + request objects + Comment::fromArray is clean; delete returning status() === 204 is correct for GitHub-style deletes.


20-39: No changes needed—the mapping is safe as-is.

In Saloon v3.x, Response::json() always returns an array of decoded JSON. The handleApiResponse() call before json() acts as a guard: if the response is unsuccessful (non-2xx status), it throws an exception; if successful, it returns early. Therefore, when json() is reached, the response is guaranteed to be successful and will always return an array, making the collect()->map() pattern safe.

tests/Unit/Traits/ManagesIssueEventsTest.php (1)

14-372: Good coverage of event/timeline mapping variants (null label/body, source/state fields, etc.).

src/Contracts/ManagesIssueEventsInterface.php (1)

9-25: Interface matches the trait surface and documents DTO collection item types well.

src/Contracts/ManagesMilestonesInterface.php (1)

10-28: Clean, complete milestone management contract.

src/Requests/Comments/UpdateCommentRequest.php (1)

12-35: Pattern is correct and follows Saloon v3.10 documented behavior. The implementation correctly implements HasBody, uses HasJsonBody, and overrides protected defaultBody(): array to provide the JSON payload—this is the standard approach in Saloon v3.10 for PATCH requests. Saloon automatically sets Content-Type: application/json and allows runtime body modifications via the body() helper if needed.

src/Requests/Events/ListIssueEventsRequest.php (1)

10-29: LGTM. The defaultQuery() method correctly implements Saloon v3.10's query parameter hook with the proper signature and usage.

src/Contracts/ManagesIssueCommentsInterface.php (1)

10-24: LGTM!

The interface design is clean and follows standard CRUD patterns. Method signatures are well-typed and the DocBlock for listComments clearly documents the collection type.

src/Traits/ManagesIssueEvents.php (1)

14-80: LGTM!

The trait implementation follows consistent patterns across all three methods with proper validation, error handling, and data mapping. The use of composed traits (HandlesApiErrors, ValidatesInput) promotes good separation of concerns.

src/Traits/ManagesMilestones.php (1)

15-106: LGTM!

The trait provides comprehensive milestone management with proper validation and error handling. The closeMilestone and reopenMilestone convenience methods (lines 97-105) are particularly nice—they provide a cleaner API for common state transitions.

src/Data/Milestone.php (1)

84-93: LGTM!

The completionPercentage method correctly handles the edge case of zero total issues and provides a useful metric with appropriate precision.

Comment on lines +38 to +51
public function toArray(): array
{
return [
'id' => $this->id,
'event' => $this->event,
'actor' => $this->actor?->toArray(),
'created_at' => $this->createdAt->format('c'),
'commit_id' => $this->commitId,
'commit_url' => $this->commitUrl,
'label' => $this->label?->toArray(),
'assignee' => $this->assignee?->toArray(),
'milestone' => $this->milestone,
];
}
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

Serialization asymmetry for milestone field.

The fromArray method expects $data['milestone'] to be an array with a 'title' key (line 34), but toArray outputs milestone as a plain string (line 49). This creates an asymmetry where calling toArray() then fromArray() on the result will fail.

If milestone should remain a string property (just the title), update fromArray to handle both array and string inputs:

-            milestone: $data['milestone']['title'] ?? null,
+            milestone: is_array($data['milestone']) ? ($data['milestone']['title'] ?? null) : ($data['milestone'] ?? null),

Alternatively, if the full milestone object structure should be preserved, change the constructor to accept a Milestone object instead of a string, and update both methods accordingly.

Committable suggestion skipped: line range outside the PR's diff.

creator: User::fromArray($data['creator']),
openIssues: $data['open_issues'],
closedIssues: $data['closed_issues'],
dueOn: $data['due_on'] ? new DateTime($data['due_on']) : null,
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

Unsafe array key access for optional DateTime fields.

Lines 38 and 41 access $data['due_on'] and $data['closed_at'] without checking if the keys exist. If these keys are missing from the input array, PHP will generate undefined array key warnings.

Apply this diff to safely handle missing keys:

-            dueOn: $data['due_on'] ? new DateTime($data['due_on']) : null,
+            dueOn: !empty($data['due_on']) ? new DateTime($data['due_on']) : null,
-            closedAt: $data['closed_at'] ? new DateTime($data['closed_at']) : null,
+            closedAt: !empty($data['closed_at']) ? new DateTime($data['closed_at']) : null,

Also applies to: 41-41

🤖 Prompt for AI Agents
In src/Data/Milestone.php around lines 38 and 41, the code directly indexes
$data['due_on'] and $data['closed_at'] which can trigger undefined array key
warnings if those keys are absent; update both lines to first check for the
presence and non-null value (e.g., using isset() or the null coalescing
operator) before constructing a DateTime and otherwise assign null, so DateTime
is only created when the key exists and contains a non-null/non-empty value.

id: $data['id'],
event: $data['event'],
actor: isset($data['actor']) && $data['actor'] !== null ? User::fromArray($data['actor']) : null,
createdAt: new DateTime($data['created_at']),
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

Uncaught exception risk from invalid created_at values.

new DateTime($data['created_at']) will throw an Exception if the date string is malformed or missing. Consider wrapping in a try-catch or validating the input first.

-            createdAt: new DateTime($data['created_at']),
+            createdAt: self::parseDateTime($data['created_at']),

Add a helper method:

private static function parseDateTime(string $value): DateTime
{
    try {
        return new DateTime($value);
    } catch (\Exception $e) {
        throw new \InvalidArgumentException("Invalid created_at date: {$value}", 0, $e);
    }
}
🤖 Prompt for AI Agents
In src/Data/TimelineEvent.php around line 34, constructing DateTime directly
from $data['created_at'] can throw on missing or malformed values; replace the
direct constructor with a safe parser: add a private static parseDateTime(string
$value): DateTime helper that catches DateTime exceptions and rethrows a clear
InvalidArgumentException, then call self::parseDateTime($data['created_at']) (or
validate presence before calling) so malformed or absent dates are handled with
a controlled exception.

label: isset($data['label']) && $data['label'] !== null ? Label::fromArray($data['label']) : null,
assignee: isset($data['assignee']) && $data['assignee'] !== null ? User::fromArray($data['assignee']) : null,
milestone: $data['milestone']['title'] ?? null,
rename: $data['rename']['from'] ?? $data['rename']['to'] ?? null,
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

Data loss: rename field loses structure.

GitHub's rename event provides both from and to fields, but this mapping only captures one value. Consider preserving the full structure or both values.

-            rename: $data['rename']['from'] ?? $data['rename']['to'] ?? null,
+            rename: isset($data['rename']) ? json_encode($data['rename']) : null,

Alternatively, add separate renameFrom and renameTo properties:

public ?string $renameFrom = null,
public ?string $renameTo = null,

And map:

renameFrom: $data['rename']['from'] ?? null,
renameTo: $data['rename']['to'] ?? null,
🤖 Prompt for AI Agents
In src/Data/TimelineEvent.php around line 41 the current mapping flattens the
GitHub rename object into a single scalar (choosing from or to) which loses the
original structure; change the constructor/property model to preserve both
values by adding two nullable properties (renameFrom and renameTo) and map them
from $data['rename']['from'] and $data['rename']['to'] respectively, or
alternatively keep a single rename array/object property and assign the full
$data['rename'] to it so both fields are retained.

Comment on lines +31 to +36
protected function defaultHeaders(): array
{
return [
'Accept' => 'application/vnd.github.mockingbird-preview+json',
];
}
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

🧩 Analysis chain

🌐 Web query:

GitHub API timeline mockingbird-preview header 2025 required

💡 Result:

Short answer: No — as of current GitHub REST docs the timeline endpoint no longer requires the mockingbird preview header. Use the normal REST Accept (e.g. application/vnd.github+json) per the REST docs; the old preview (application/vnd.github.mockingbird-preview+json) was required only during the original preview period. [1][2][3]

Sources

  • REST API docs — timeline endpoints (shows recommended accept header/application/vnd.github+json). [1]
  • GitHub changelog: many REST previews graduated; only a couple previews still required as of 2021 (mockingbird not listed among remaining ones). [2]
  • Historical developer blog announcing the Timeline API preview (mockingbird) during its preview period. [3]

🏁 Script executed:

# Search for Accept header usage in other request classes
rg "Accept.*vnd\.github" --type php -B 2 -A 2

Repository: conduit-ui/issue

Length of output: 794


🏁 Script executed:

# Check the file structure and context around the ListIssueTimelineRequest
cd src/Requests/Events && ls -la

Repository: conduit-ui/issue

Length of output: 406


Update the Accept header to the standard GitHub REST API format.

The mockingbird-preview media type is no longer required; GitHub graduated this preview feature years ago. Replace 'Accept' => 'application/vnd.github.mockingbird-preview+json' with 'Accept' => 'application/vnd.github+json' per current GitHub REST API documentation. Also update the corresponding test assertion in tests/Unit/Requests/EventRequestsTest.php.

🤖 Prompt for AI Agents
In src/Requests/Events/ListIssueTimelineRequest.php around lines 31 to 36,
replace the outdated Accept header
'application/vnd.github.mockingbird-preview+json' with the current standard
'application/vnd.github+json' to match the graduated API; then update the
corresponding assertion in tests/Unit/Requests/EventRequestsTest.php to expect
'application/vnd.github+json' instead of the mockingbird preview media type.

Comment on lines +105 to +107
if ($trimmed === '') {
throw new InvalidArgumentException("{$field} cannot be empty");
}
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

Breaking change: body field can no longer be empty.

Adding the empty-string check to sanitizeString is a breaking change. The validateIssueData method at line 69 passes body through this function, which will now reject empty body strings. If empty bodies were previously valid (GitHub allows issues with empty bodies), this could break existing consumers.

Consider whether this validation should be conditional or if a separate sanitizer without the empty check is needed for nullable/optional fields.

🤖 Prompt for AI Agents
In src/Traits/ValidatesInput.php around lines 105 to 107, the added empty-string
check in sanitizeString throws on trimmed === '' which makes the change breaking
for optional/nullable fields like the issue "body" (validated at line 69);
update the code so sanitizeString does not unconditionally reject empty strings
— either add an optional parameter (e.g., $allowEmpty = false) to control the
empty check, or add a separate sanitizeOptionalString that skips the exception —
then call the appropriate variant from validateIssueData for the body field (or
adjust validateIssueData to only enforce non-empty for required fields).

Comment on lines +106 to +114
it('handles repository not found', function () {
$this->mockClient->addResponse(MockResponse::make(
['message' => 'Not Found'],
404
));

expect(fn () => $this->service->listIssueEvents('owner', 'repo', 123))
->toThrow(IssueNotFoundException::class);
});
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

Test name/expectation mismatch on 404 (“repository not found” vs IssueNotFoundException).
Either rename the test to “handles issue not found” or update the expected exception to match the intended 404 mapping.

🤖 Prompt for AI Agents
In tests/Unit/Traits/ManagesIssueEventsTest.php around lines 106–114 the test
title says "handles repository not found" but the expectation asserts
IssueNotFoundException on a 404; either rename the test to "handles issue not
found" to match the asserted IssueNotFoundException, or change the expected
exception to the repository-not-found exception that the service maps 404 to
(e.g., RepositoryNotFoundException) so the test name and expected exception
align; update only the test name or the ->toThrow(...) to make them consistent
and run the test suite.

…fication

Convert 6 data test files from test() to describe/it blocks
to meet Pest syntax requirements for Sentinel Gate certification.

Files updated:
- IssueTest.php
- LabelTest.php
- UserTest.php
- MilestoneTest.php
- TimelineEventTest.php
- IssueEventTest.php
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.

2 participants