feat(#800): add file-issue CLI command with dedup guard - #822
feat(#800): add file-issue CLI command with dedup guard#822fullsend-ai-coder[bot] wants to merge 2 commits into
Conversation
Add a `fullsend file-issue` CLI command that creates GitHub issues with a built-in duplicate detection guard. Before filing, the command queries the GitHub Search API for issues recently created by the same author in the target repo. If an existing issue has a similar title (measured by Jaccard word-overlap coefficient), the command skips creation and returns the existing issue URL instead. This prevents the duplicate filing observed when multiple concurrent retro agent runs identify the same improvement and file nearly identical issues within a short time window. Changes: - Add SearchIssues to forge.Client interface with IssueSearchOptions - Implement SearchIssues for GitHub (Search API), GitLab (stub), and FakeClient - Add fullsend file-issue CLI command with --creator, --dedup-window, --dry-run flags and JSON output - Add title similarity via Jaccard coefficient with stop-word filtering - Add comprehensive tests for dedup logic, similarity, and forge method Note: pre-commit could not run in sandbox (network restricted). Closes #800
|
🤖 Finished Review · ✅ Success · Started 3:13 AM UTC · Completed 3:32 AM UTC |
ReviewFindingsMedium
Low
Next steps:
Previous runReviewFindingsHigh
Medium
Low
Labels: PR adds a new CLI command (file-issue) implementing a feature from issue #800 Next steps:
|
|
/fs-fix |
|
🤖 Finished Fix · ✅ Success · Started 6:48 AM UTC · Completed 6:58 AM UTC |
- Add CreateIssueComment call when duplicate is detected, fulfilling the issue #800 requirement to note additional evidence on existing issues - Switch ui.Printer to os.Stderr so JSON output on stdout is not corrupted by interleaved progress lines - Replace gh.New(token) with newGitHubLiveClient(token, "") for GitHub Enterprise Server compatibility - Make TitlesSimilar unexported (titlesSimilar) since it is only used within the cli package - Add file-issue to CLI docs (README.md and cli-internals.md) - Add test for comment-failure-is-non-fatal behavior Addresses review feedback on #822
🔧 Fix agent — iteration 1 (human-triggered)Addressed 6 of 17 review findings with code and documentation fixes. The high-severity missing-requirement (no comment on duplicate) is resolved by adding a CreateIssueComment call. The stdout-corruption bug is fixed by routing printer output to stderr. The pattern-violation is fixed by using newGitHubLiveClient. Two stale-doc findings are resolved by adding file-issue to CLI docs. The naming-convention finding is resolved by making TitlesSimilar unexported. Disagreed with 11 findings (fail-open is intentional, scope-creep is a design decision, and the remaining are low-severity items suitable for follow-up). Fixed (6):
Disagreed (11):
Tests: passedNext steps:
|
|
🤖 Finished Review · ✅ Success · Started 7:00 AM UTC · Completed 7:19 AM UTC |
| }) | ||
| if err != nil { | ||
| // Non-fatal: if the search fails (e.g., rate limit, unsupported | ||
| // forge), fall through to creation rather than blocking filing. |
There was a problem hiding this comment.
[medium] logic-error
--dry-run does not prevent side effects during duplicate detection. When a duplicate is found, the code posts a comment on the existing issue via CreateIssueComment before returning — even when --dry-run is set. The dryRun guard only gates issue creation (Phase 2), not the comment posting in Phase 1.
Suggested fix: Wrap the CreateIssueComment call in an if !dryRun { ... } guard.
| Repo string // repository name | ||
| Creator string // filter by author login (empty = any) | ||
| Since time.Time // only issues created on or after this time | ||
| State string // "open", "closed", or "" for any state |
There was a problem hiding this comment.
[medium] interface-contract-consistency
IssueSearchOptions.State field comment says open/closed/empty-for-any-state, but the GitHub implementation defaults empty State to open, and the FakeClient ignores State entirely — three different behaviors for the same contract.
Suggested fix: Update the State field comment to match the GitHub implementation: empty defaults to open.
| @@ -0,0 +1,342 @@ | |||
| package cli | |||
There was a problem hiding this comment.
[low] missing-test
No test for the interaction between --dry-run and duplicate detection. TestFileIssueWithDedup_DryRun uses an empty FakeClient with no open issues, so it never exercises the code path where a duplicate is found during a dry run.
| } | ||
|
|
||
| // defaultDedupWindow is the lookback duration for dedup searches. | ||
| const defaultDedupWindow = 30 * time.Minute |
There was a problem hiding this comment.
[low] validation-gap
Issue #800 suggests a 10-15 minute lookback window, but defaultDedupWindow is 30 minutes. The wider window is configurable via --dedup-window, but a code comment explaining the rationale for 30 minutes would help.
| Labels []string | ||
| } | ||
|
|
||
| // IssueSearchOptions configures a search for issues. Used by |
There was a problem hiding this comment.
[low] options-struct-precedent
IssueSearchOptions is the first options struct in the forge.Client interface; all other methods use positional parameters. The struct approach is justified by the number of fields.
|
This pull request has been automatically marked as stale because it has not had any activity in the last month. It will be closed in 2 weeks if no further activity occurs. Remove the |
Summary
Adds a
fullsend file-issueCLI command that wraps issue creation with a built-in dedup guard, preventing concurrent retro agent runs from filing duplicate issues. When 23 workspace-update PRs were closed simultaneously on 2026-07-24, 5 retro agents independently filed nearly identical issues within a 2-minute window. This command solves that race by searching for recent issues with similar titles before creating.Changes
SearchIssuesto theforge.Clientinterface withIssueSearchOptions(owner, repo, creator, since, state)SearchIssuesfor GitHub (via the Search API/search/issues), GitLab (stub returningErrNotSupported), andFakeClientfullsend file-issueCLI command with--creator,--dedup-window,--dry-runflags{created, url, number, duplicate_of}Testing
normalizeWordstests (stop word removal, punctuation, number preservation)SearchIssuestests: result parsing, PR exclusion, default statego vetpassesgo build ./...passesmake lint— pre-commit could not run in sandbox (network restricted); the post-script runs it authoritativelyChecklist
!for breaking changes)Closes #800
Post-script verification
agent/800-retro-dedup-guard)ba77dbdbbd17e9bb18c47bf6efa023c98b697158..HEAD)