Summary
Implement comprehensive comment management API for PR comments, inline code comments, and comment threads.
Acceptance Criteria
Comment Manager Interface
namespace ConduitUI\Pr\Contracts;
use ConduitUI\Pr\Data\Comment;
use Illuminate\Support\Collection;
interface CommentManagerInterface
{
public function get(): Collection;
public function create(string $body): Comment;
public function update(int $commentId, string $body): Comment;
public function delete(int $commentId): bool;
public function reply(int $commentId, string $body): Comment;
}
Comment Manager Implementation
namespace ConduitUI\Pr\Services;
use ConduitUI\Pr\Contracts\CommentManagerInterface;
use ConduitUI\Pr\Data\Comment;
use ConduitUI\Connector\GitHub;
use Illuminate\Support\Collection;
final class CommentManager implements CommentManagerInterface
{
public function __construct(
protected GitHub $github,
protected string $fullName,
protected int $prNumber,
) {}
/**
* Get all issue comments for the PR
*/
public function get(): Collection
{
$response = $this->github->get(
"/repos/{$this->fullName}/issues/{$this->prNumber}/comments"
);
return collect($response->json())
->map(fn($comment) => Comment::fromArray($comment));
}
/**
* Create a new issue comment
*/
public function create(string $body): Comment
{
$response = $this->github->post(
"/repos/{$this->fullName}/issues/{$this->prNumber}/comments",
['body' => $body]
);
return Comment::fromArray($response->json());
}
/**
* Update an existing comment
*/
public function update(int $commentId, string $body): Comment
{
$response = $this->github->patch(
"/repos/{$this->fullName}/issues/comments/{$commentId}",
['body' => $body]
);
return Comment::fromArray($response->json());
}
/**
* Delete a comment
*/
public function delete(int $commentId): bool
{
$response = $this->github->delete(
"/repos/{$this->fullName}/issues/comments/{$commentId}"
);
return $response->successful();
}
/**
* Reply to a comment (creates new comment with reference)
*/
public function reply(int $commentId, string $body): Comment
{
$replyBody = "> Replying to comment #{$commentId}\n\n{$body}";
return $this->create($replyBody);
}
}
Inline Comment Manager
namespace ConduitUI\Pr\Services;
use ConduitUI\Pr\Data\ReviewComment;
use ConduitUI\Connector\GitHub;
use Illuminate\Support\Collection;
final class InlineCommentManager
{
public function __construct(
protected GitHub $github,
protected string $fullName,
protected int $prNumber,
) {}
/**
* Get all review comments (inline comments)
*/
public function get(): Collection
{
$response = $this->github->get(
"/repos/{$this->fullName}/pulls/{$this->prNumber}/comments"
);
return collect($response->json())
->map(fn($comment) => ReviewComment::fromArray($comment));
}
/**
* Create inline comment on specific line
*/
public function create(
string $path,
int $line,
string $body,
?string $side = 'RIGHT'
): ReviewComment {
$data = [
'body' => $body,
'path' => $path,
'line' => $line,
'side' => $side,
];
$response = $this->github->post(
"/repos/{$this->fullName}/pulls/{$this->prNumber}/comments",
$data
);
return ReviewComment::fromArray($response->json());
}
/**
* Create suggestion comment
*/
public function suggest(
string $path,
int $startLine,
int $endLine,
string $suggestion
): ReviewComment {
$body = "```suggestion\n{$suggestion}\n```";
return $this->create($path, $endLine, $body);
}
/**
* Reply to inline comment
*/
public function reply(int $commentId, string $body): ReviewComment
{
$response = $this->github->post(
"/repos/{$this->fullName}/pulls/{$this->prNumber}/comments/{$commentId}/replies",
['body' => $body]
);
return ReviewComment::fromArray($response->json());
}
/**
* Update inline comment
*/
public function update(int $commentId, string $body): ReviewComment
{
$response = $this->github->patch(
"/repos/{$this->fullName}/pulls/comments/{$commentId}",
['body' => $body]
);
return ReviewComment::fromArray($response->json());
}
/**
* Delete inline comment
*/
public function delete(int $commentId): bool
{
$response = $this->github->delete(
"/repos/{$this->fullName}/pulls/comments/{$commentId}"
);
return $response->successful();
}
}
Comment DTOs
namespace ConduitUI\Pr\Data;
use Carbon\Carbon;
/**
* Issue comment (general PR comment)
*/
final readonly class Comment
{
public function __construct(
public int $id,
public User $user,
public string $body,
public string $htmlUrl,
public Carbon $createdAt,
public Carbon $updatedAt,
) {}
public static function fromArray(array $data): self;
}
/**
* Review comment (inline code comment)
*/
final readonly class ReviewComment
{
public function __construct(
public int $id,
public User $user,
public string $body,
public string $path,
public int $line,
public ?int $startLine,
public string $side, // LEFT | RIGHT
public ?int $inReplyToId,
public string $htmlUrl,
public Carbon $createdAt,
public Carbon $updatedAt,
) {}
public static function fromArray(array $data): self;
public function isReply(): bool
{
return $this->inReplyToId !== null;
}
public function isSuggestion(): bool
{
return str_contains($this->body, '```suggestion');
}
}
Integration with PullRequestInstance
// Add to PullRequestInstance
public function comments(): CommentManager
{
return new CommentManager($this->github, $this->fullName, $this->number);
}
public function inlineComments(): InlineCommentManager
{
return new InlineCommentManager($this->github, $this->fullName, $this->number);
}
public function comment(string $body): PullRequest
{
$this->comments()->create($body);
return $this->fresh();
}
Usage Examples
General PR Comments
$pr = PullRequests::find('owner/repo', 123);
// Add comment
$pr->comment('Please address the failing tests');
// Get all comments
$comments = $pr->comments()->get();
// Update comment
$pr->comments()->update(456, 'Updated comment text');
// Delete comment
$pr->comments()->delete(456);
Inline Code Comments
$pr = PullRequests::find('owner/repo', 123);
// Add inline comment
$pr->inlineComments()->create(
path: 'src/Service.php',
line: 42,
body: 'This could cause a race condition'
);
// Add suggestion
$pr->inlineComments()->suggest(
path: 'src/Controller.php',
startLine: 10,
endLine: 15,
suggestion: 'return $this->repository->findOrFail($id);'
);
// Reply to inline comment
$pr->inlineComments()->reply(789, 'Good catch! Will fix.');
// Get all inline comments
$inlineComments = $pr->inlineComments()->get();
Automated Comment Bot
PullRequests::forRepo('owner/repo')
->whereOpen()
->get()
->each(function($pr) {
$stats = $pr->files()->stats();
if ($stats->totalFiles > 20) {
$pr->comment('⚠️ Large PR detected. Consider splitting into smaller PRs.');
}
if ($pr->checksFail()) {
$failing = $pr->checks()->whereFailing();
$message = "❌ Failing checks:\n";
foreach ($failing as $check) {
$message .= "- {$check->name}\n";
}
$pr->comment($message);
}
});
Conditional Comments
$pr = PullRequests::find('owner/repo', 123);
// Only comment if no tests changed
$testsChanged = $pr->files()
->wherePath('tests/**/*.php')
->isNotEmpty();
if (!$testsChanged) {
$pr->comment('💡 Consider adding tests for these changes.');
}
Multi-line Suggestions
$pr = PullRequests::find('owner/repo', 123);
$pr->inlineComments()->suggest(
path: 'src/UserController.php',
startLine: 20,
endLine: 25,
suggestion: <<<'PHP'
return $this->validate($request, [
'email' => 'required|email|unique:users',
'password' => 'required|min:8',
]);
PHP
);
Comment Threading
$pr = PullRequests::find('owner/repo', 123);
// Get a specific comment and reply
$comment = $pr->comments()->get()->first();
$pr->comments()->reply($comment->id, 'Thanks for the feedback!');
Bulk Comment Operations
$pr = PullRequests::find('owner/repo', 123);
// Delete all bot comments
$pr->comments()
->get()
->filter(fn($c) => $c->user->login === 'github-actions[bot]')
->each(fn($c) => $pr->comments()->delete($c->id));
Testing Requirements
it('creates PR comment')
->expect(fn() =>
PullRequests::find('test/repo', 1)->comment('Test comment')
)->toBeInstanceOf(PullRequest::class);
it('gets all comments')
->expect(fn() =>
PullRequests::find('test/repo', 1)->comments()->get()
)->toBeInstanceOf(Collection::class);
it('creates inline comment')
->expect(fn() =>
PullRequests::find('test/repo', 1)
->inlineComments()
->create('src/File.php', 10, 'Comment')
)->toBeInstanceOf(ReviewComment::class);
it('creates suggestion')
->expect(fn() =>
PullRequests::find('test/repo', 1)
->inlineComments()
->suggest('src/File.php', 10, 15, 'new code')
)->toBeInstanceOf(ReviewComment::class);
it('deletes comment')
->expect(fn() =>
PullRequests::find('test/repo', 1)->comments()->delete(1)
)->toBeBool();
Dependencies
References
Labels
- enhancement
- comments
- reviews
Summary
Implement comprehensive comment management API for PR comments, inline code comments, and comment threads.
Acceptance Criteria
Comment Manager Interface
Comment Manager Implementation
Inline Comment Manager
Comment DTOs
Integration with PullRequestInstance
Usage Examples
General PR Comments
Inline Code Comments
Automated Comment Bot
Conditional Comments
Multi-line Suggestions
Comment Threading
Bulk Comment Operations
Testing Requirements
Dependencies
References
Labels