Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
88d4b3c
fix: execute fullsend CLI and harden entrypoint output handling
waynesun09 Apr 13, 2026
da88194
fix: restore echo stub for unimplemented entrypoint command
waynesun09 Apr 13, 2026
0e1c705
feat: add sticky review comments and post-review CLI
Apr 23, 2026
1a56135
feat: extract sticky comment package and add post-comment CLI
waynesun09 Apr 27, 2026
2945ca4
test: add coverage for ListIssueComments pagination and MinimizeComme…
waynesun09 Apr 27, 2026
7e1fd45
feat: wire formal PR reviews and fix review findings
waynesun09 Apr 27, 2026
42985b9
fix: use NodeID for MinimizeComment and eliminate TOCTOU race
waynesun09 Apr 27, 2026
6a1c332
fix: harden sticky comments, FakeClient fidelity, and MinimizeComment…
waynesun09 Apr 27, 2026
4a1601e
fix: add input size limits, marker spoofing protection, and smart his…
waynesun09 Apr 27, 2026
3c96bba
fix: address review feedback — CutPrefix, auth warning, readBody tests
waynesun09 Apr 28, 2026
0fbcf30
feat: add stale-head detection, failure handling, and migrate post-re…
waynesun09 Apr 28, 2026
6fae501
fix: eliminate swallowed error, add input validation, and harden revi…
waynesun09 Apr 28, 2026
28c8e7a
fix: restore action.yml to main's version after rebase conflict
waynesun09 Apr 28, 2026
2f24b52
fix: pin review to commit SHA and validate head_sha input
waynesun09 Apr 28, 2026
19f50cd
fix: require full SHA for head_sha and sanitize failure reason
waynesun09 Apr 28, 2026
6c4a81a
fix: case-insensitive SHA comparison, empty-reason default, error mes…
waynesun09 Apr 28, 2026
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
85 changes: 85 additions & 0 deletions internal/cli/postcomment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package cli

import (
"fmt"
"os"
"strings"

"github.com/spf13/cobra"

gh "github.com/fullsend-ai/fullsend/internal/forge/github"
"github.com/fullsend-ai/fullsend/internal/sticky"
"github.com/fullsend-ai/fullsend/internal/ui"
)

func newPostCommentCmd() *cobra.Command {
var (
repo string
number int
marker string
result string
token string
dryRun bool
)

cmd := &cobra.Command{
Use: "post-comment",
Short: "Post or update a sticky comment on an issue or PR",
Long: `Posts a comment with a hidden HTML marker on an issue or pull request.

On first run, creates a new comment. On re-runs, finds the existing
comment by its marker and edits in-place, collapsing old content into
<details> blocks. This prevents comment flooding on re-runs.

The --marker flag identifies this agent's comments. Each agent should
use a unique marker (e.g. "<!-- fullsend:triage-agent -->").

The --result flag accepts a file path or "-" for stdin.`,
RunE: func(cmd *cobra.Command, args []string) error {
printer := ui.New(os.Stdout)

if token == "" {
token = os.Getenv("GITHUB_TOKEN")
}
if token == "" {
return fmt.Errorf("--token or GITHUB_TOKEN required")
}

if number <= 0 {
return fmt.Errorf("--number must be a positive integer, got %d", number)
}

parts := strings.SplitN(repo, "/", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return fmt.Errorf("--repo must be in owner/repo format, got %q", repo)
}
owner, repoName := parts[0], parts[1]

body, err := readBody(result)
if err != nil {
return fmt.Errorf("reading comment body: %w", err)
}

printer.Header("Post Comment")

client := gh.New(token)
cfg := sticky.Config{
Marker: marker,
DryRun: dryRun,
}
return sticky.Post(cmd.Context(), client, owner, repoName, number, body, cfg, printer)
},
}

cmd.Flags().StringVar(&repo, "repo", "", "repository in owner/repo format (required)")
cmd.Flags().IntVar(&number, "number", 0, "issue or pull request number (required)")
cmd.Flags().StringVar(&marker, "marker", "", "hidden HTML marker to identify this agent's comments (required)")
cmd.Flags().StringVar(&result, "result", "-", "path to comment body file, or '-' for stdin")
cmd.Flags().StringVar(&token, "token", "", "GitHub token (default: $GITHUB_TOKEN)")
cmd.Flags().BoolVar(&dryRun, "dry-run", false, "print what would be posted without making API calls")
_ = cmd.MarkFlagRequired("repo")
_ = cmd.MarkFlagRequired("number")
_ = cmd.MarkFlagRequired("marker")

return cmd
}
30 changes: 30 additions & 0 deletions internal/cli/postcomment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cli

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewPostCommentCmd_RequiredFlags(t *testing.T) {
cmd := newPostCommentCmd()

// Verify required flags are registered.
for _, name := range []string{"repo", "number", "marker"} {
f := cmd.Flags().Lookup(name)
require.NotNil(t, f, "flag %q should exist", name)
}
}

func TestNewPostCommentCmd_DefaultFlags(t *testing.T) {
cmd := newPostCommentCmd()

result := cmd.Flags().Lookup("result")
require.NotNil(t, result)
assert.Equal(t, "-", result.DefValue)

dryRun := cmd.Flags().Lookup("dry-run")
require.NotNil(t, dryRun)
assert.Equal(t, "false", dryRun.DefValue)
}
Loading
Loading