Introduce core configuration, GitHub client, and bot steps for issue …#86
Conversation
📝 WalkthroughWalkthroughAdds an automated duplicate-issue closure feature: new AutoCloseConfig, GitHub client methods to remove labels and close issues, an AutoCloser service that processes issues labeled "potential-duplicate" with grace-period and human-activity checks, duplicate detection now suggests the label, and a scheduled GitHub Actions workflow runs the CLI auto-close command. Changes
Sequence DiagramsequenceDiagram
participant Workflow as "GitHub Actions\nWorkflow"
participant CLI as "Simili CLI"
participant AutoCloser as "AutoCloser\nService"
participant GH as "GitHub API"
Workflow->>CLI: Run auto-close (daily or manual)
CLI->>AutoCloser: Init (config, dry-run)
AutoCloser->>GH: List open issues with "potential-duplicate"
GH-->>AutoCloser: Return issues
loop For each issue
AutoCloser->>GH: Get issue events (find label time)
GH-->>AutoCloser: Return timeline
alt Grace period elapsed
AutoCloser->>GH: List comments since labeled time
GH-->>AutoCloser: Return comments
alt No human activity detected
alt Dry-run
AutoCloser->>AutoCloser: Log closure intent
else Production
AutoCloser->>GH: Post closing comment
AutoCloser->>GH: Remove "potential-duplicate" label
AutoCloser->>GH: Add "duplicate" label
AutoCloser->>GH: Close issue
end
else Human activity detected
AutoCloser->>AutoCloser: Skip issue
end
else Grace period pending
AutoCloser->>AutoCloser: Skip issue
end
end
AutoCloser-->>CLI: Return results (closed, skipped, errors)
CLI-->>Workflow: Exit
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 4❌ Failed checks (2 warnings, 2 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
…auto-closing and duplicate detection. Signed-off-by: Sachindu-Nethmin <sachindunethminweerasinghe@gmail.com>
80ce85b to
8d064c4
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
internal/steps/auto_closer_test.go (2)
81-93: This test validates its own constants, not production code.
TestAutoCloseResultCountsconstructs anAutoCloseResultwith hardcoded values and checks that they sum correctly. It doesn't exerciseAutoCloser.Run()or any other production logic — it only asserts that2 + 2 + 1 == 5.Consider replacing this with an integration-style test that uses a mock GitHub client, runs
AutoCloser.Run(), and verifies the resulting counts reflect actual processing decisions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/steps/auto_closer_test.go` around lines 81 - 93, TestAutoCloseResultCounts only validates its own hardcoded AutoCloseResult constants rather than exercising production logic; replace this self-check with a test that runs the real flow. Update the test to instantiate an AutoCloser, inject a mock GitHub client that simulates issues/prs and responses, call AutoCloser.Run(), and then assert the returned AutoCloseResult fields (Processed, Closed, SkippedGrace, SkippedHuman) reflect the decisions made by the AutoCloser logic rather than summing hardcoded values; keep the test named TestAutoCloseResultCounts and reuse AutoCloseResult for assertions.
39-79: Grace period test re-implements the logic rather than testing the production code.This test independently computes
time.Since(labeledAt) >= gracePeriodinstead of calling any function fromauto_closer.go. It validates the concept but won't catch regressions if the production grace-period check changes (e.g., to use<=or a different clock source).Consider extracting the grace-period check in
auto_closer.gointo a testable helper (e.g.,isGracePeriodExpired(labeledAt time.Time, gracePeriod time.Duration) bool) and testing that directly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/steps/auto_closer_test.go` around lines 39 - 79, Extract the grace-period check from auto_closer.go into a small, exported or package-private helper function (for example isGracePeriodExpired(labeledAt time.Time, gracePeriod time.Duration) bool) and have the test in auto_closer_test.go call that helper instead of recomputing time.Since(...) directly; update the production code to call the new helper so behavior stays identical, and if the production code uses an injectable clock, mirror that in the helper signature (or keep it using time.Since for now) so tests exercise the exact same logic as production.internal/steps/auto_closer.go (1)
230-259:hasHumanActivitydoes not paginate — may miss comments beyond the first 100.Only the first page (100 comments) is fetched. If there are more than 100 comments after the label was applied, human activity could be missed (though this is unlikely within a 72-hour window). Consider adding pagination for completeness, consistent with
fetchPotentialDuplicatesandListIssueEventswhich both paginate.Pagination sketch
func (ac *AutoCloser) hasHumanActivity(ctx context.Context, org, repo string, number int, since time.Time) (bool, error) { opts := &githubapi.IssueListCommentsOptions{ Since: &since, ListOptions: githubapi.ListOptions{ PerPage: 100, }, } - comments, _, err := ac.github.ListComments(ctx, org, repo, number, opts) - if err != nil { - return false, err - } - - for _, comment := range comments { - if comment.User == nil { - continue - } - author := comment.User.GetLogin() - if !isBotUser(author, ac.cfg.BotUsers) { - if ac.verbose { - log.Printf("[auto-closer] #%d: human comment by %q at %v", - number, author, comment.CreatedAt.Time) + for { + comments, resp, err := ac.github.ListComments(ctx, org, repo, number, opts) + if err != nil { + return false, err + } + for _, comment := range comments { + if comment.User == nil { + continue + } + author := comment.User.GetLogin() + if !isBotUser(author, ac.cfg.BotUsers) { + if ac.verbose { + log.Printf("[auto-closer] #%d: human comment by %q at %v", + number, author, comment.CreatedAt.Time) + } + return true, nil } - return true, nil } + if resp == nil || resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage } return false, nil }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/steps/auto_closer.go` around lines 230 - 259, hasHumanActivity currently only fetches the first page of comments via ac.github.ListComments with githubapi.IssueListCommentsOptions, so add pagination: turn the single call into a loop that calls ac.github.ListComments repeatedly, passing opts.ListOptions.Page and appending each returned comments slice, using the returned resp (response) to break when resp.NextPage == 0; preserve the existing early-return behavior (return true when a non-bot user is found) and keep the verbose log path, and ensure opts.ListOptions.Page starts at 1 and increments from resp.NextPage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/auto-close.yml:
- Line 1: The file .github/workflows/auto-close.yml has CRLF line endings;
convert it to Unix LF endings and commit the change so YAML lint passes (e.g.,
use a line-ending conversion tool or editor to replace CRLF with LF), and
optionally add a .gitattributes entry or adjust Git core.autocrlf to prevent
reintroducing CRLF in future.
- Around line 48-61: The workflow invokes a non-existent CLI command
"./simili-cli auto-close" (the auto-close command and
cmd/simili/commands/auto_close.go were removed), so either remove or replace
that step in .github/workflows/auto-close.yml: update the "Run auto-close" job
to call an existing command (e.g., a current simili-cli subcommand) or delete
the job, or alternatively restore and re-register the auto-close command
implementation (recreate cmd/simili/commands/auto_close.go and ensure it is
added to the root command registration) so "./simili-cli auto-close" succeeds;
pick one approach and make the corresponding change consistently in the workflow
and command registration.
In `@internal/core/config/config.go`:
- Around line 393-399: The AutoClose.DryRun boolean is "sticky" because
child.AutoClose.DryRun is a plain bool and the merge uses if
child.AutoClose.DryRun so a child cannot unset a true parent; change
AutoClose.DryRun to *bool in the AutoCloseConfig type, update mergeConfigs (the
block that inspects child.AutoClose.DryRun/result.AutoClose.DryRun) to test for
nil and assign the pointer value when non-nil, and update any consumers to
handle dereferencing (or use helper accessors) so callers read the actual
boolean value safely; this mirrors the pattern used for Transfer.Enabled and
ensures children can explicitly set false.
In `@internal/steps/auto_closer.go`:
- Around line 276-316: closeIssue currently removes the "potential-duplicate"
label before attempting to close the issue, which can leave the issue
unrecoverable if CloseIssue fails; change the operation order in closeIssue so
you Post the closing comment, then call ac.github.CloseIssue(ctx, org, repo,
number) first, and only if closing succeeds perform label updates (call
ac.github.RemoveLabel and ac.github.AddLabels afterwards), retaining the
existing warning logs for label failures; keep CreateComment before CloseIssue
as-is and return the CloseIssue error unchanged so failures prevent label
mutation.
---
Nitpick comments:
In `@internal/steps/auto_closer_test.go`:
- Around line 81-93: TestAutoCloseResultCounts only validates its own hardcoded
AutoCloseResult constants rather than exercising production logic; replace this
self-check with a test that runs the real flow. Update the test to instantiate
an AutoCloser, inject a mock GitHub client that simulates issues/prs and
responses, call AutoCloser.Run(), and then assert the returned AutoCloseResult
fields (Processed, Closed, SkippedGrace, SkippedHuman) reflect the decisions
made by the AutoCloser logic rather than summing hardcoded values; keep the test
named TestAutoCloseResultCounts and reuse AutoCloseResult for assertions.
- Around line 39-79: Extract the grace-period check from auto_closer.go into a
small, exported or package-private helper function (for example
isGracePeriodExpired(labeledAt time.Time, gracePeriod time.Duration) bool) and
have the test in auto_closer_test.go call that helper instead of recomputing
time.Since(...) directly; update the production code to call the new helper so
behavior stays identical, and if the production code uses an injectable clock,
mirror that in the helper signature (or keep it using time.Since for now) so
tests exercise the exact same logic as production.
In `@internal/steps/auto_closer.go`:
- Around line 230-259: hasHumanActivity currently only fetches the first page of
comments via ac.github.ListComments with githubapi.IssueListCommentsOptions, so
add pagination: turn the single call into a loop that calls
ac.github.ListComments repeatedly, passing opts.ListOptions.Page and appending
each returned comments slice, using the returned resp (response) to break when
resp.NextPage == 0; preserve the existing early-return behavior (return true
when a non-bot user is found) and keep the verbose log path, and ensure
opts.ListOptions.Page starts at 1 and increments from resp.NextPage.
| @@ -0,0 +1,61 @@ | |||
| # ============================================================================= | |||
There was a problem hiding this comment.
Fix line endings: CRLF detected, expected LF.
YAMLlint reports wrong newline characters. Ensure this file uses Unix-style line endings (\n), not Windows-style (\r\n).
# Fix locally:
sed -i 's/\r$//' .github/workflows/auto-close.yml🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 1-1: wrong new line character: expected \n
(new-lines)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/auto-close.yml at line 1, The file
.github/workflows/auto-close.yml has CRLF line endings; convert it to Unix LF
endings and commit the change so YAML lint passes (e.g., use a line-ending
conversion tool or editor to replace CRLF with LF), and optionally add a
.gitattributes entry or adjust Git core.autocrlf to prevent reintroducing CRLF
in future.
| - name: Run auto-close | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GH_PAT }} | ||
| run: | | ||
| DRY_RUN_FLAG="" | ||
| if [ "${{ inputs.dry_run }}" = "true" ]; then | ||
| DRY_RUN_FLAG="--dry-run" | ||
| fi | ||
|
|
||
| ./simili-cli auto-close \ | ||
| --repo "${{ github.repository }}" \ | ||
| --config .github/simili.yaml \ | ||
| --verbose \ | ||
| $DRY_RUN_FLAG |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
echo "=== Check for simili.yaml file ==="
if [ -f .github/simili.yaml ]; then
echo "File exists. Content:"
cat .github/simili.yaml
else
echo "File not found at .github/simili.yaml"
find . -name "simili.yaml" -type f 2>/dev/null
fiRepository: similigh/simili-bot
Length of output: 493
🏁 Script executed:
#!/bin/bash
echo "=== Find and examine config.go file ==="
fd "config.go" -type f | head -5Repository: similigh/simili-bot
Length of output: 290
🏁 Script executed:
#!/bin/bash
echo "=== Find auto-close command implementation ==="
fd "main.go" -path "*/cmd/*" -type fRepository: similigh/simili-bot
Length of output: 295
🏁 Script executed:
#!/bin/bash
echo "=== Search for Validate function in config context ==="
rg -n "func.*Validate" --type=go -B 2 -A 10Repository: similigh/simili-bot
Length of output: 3645
🏁 Script executed:
#!/bin/bash
echo "=== Search for Load function in config.go ==="
rg -n "^func.*Load" internal/core/config/config.go -A 15Repository: similigh/simili-bot
Length of output: 957
🏁 Script executed:
#!/bin/bash
echo "=== Find auto-close command entry point ==="
find . -path "*/cmd/*" -name "*.go" | grep -i "main\|auto"Repository: similigh/simili-bot
Length of output: 152
🏁 Script executed:
#!/bin/bash
echo "=== Search for auto-close command directory ==="
find . -type d -name "*auto*" -o -type d -name "*cmd*" | head -10Repository: similigh/simili-bot
Length of output: 116
🏁 Script executed:
#!/bin/bash
echo "=== Check entire internal/core/config/config.go to understand Load and Validate flow ==="
wc -l internal/core/config/config.goRepository: similigh/simili-bot
Length of output: 186
🏁 Script executed:
#!/bin/bash
echo "=== Check cmd/simili/main.go for auto-close command ==="
cat -n cmd/simili/main.go | head -150Repository: similigh/simili-bot
Length of output: 521
🏁 Script executed:
#!/bin/bash
echo "=== Search for auto-close command handler ==="
rg -n "auto-close|AutoClose" cmd/simili/main.go -B 3 -A 20Repository: similigh/simili-bot
Length of output: 108
🏁 Script executed:
#!/bin/bash
echo "=== Check if there are subcommands or separate files ==="
find cmd/simili -type f -name "*.go"Repository: similigh/simili-bot
Length of output: 393
🏁 Script executed:
#!/bin/bash
echo "=== Search for auto-close in commands directory ==="
rg -n "auto.close" cmd/simili/commands/ -iRepository: similigh/simili-bot
Length of output: 114
🏁 Script executed:
#!/bin/bash
echo "=== Check root.go for command definitions ==="
cat -n cmd/simili/commands/root.goRepository: similigh/simili-bot
Length of output: 1871
🏁 Script executed:
#!/bin/bash
echo "=== List all Go files in cmd/simili/commands ==="
ls -la cmd/simili/commands/Repository: similigh/simili-bot
Length of output: 720
🏁 Script executed:
#!/bin/bash
echo "=== Search entire repo for auto-close command ==="
rg -n "auto" cmd/ -i --type=goRepository: similigh/simili-bot
Length of output: 290
🏁 Script executed:
#!/bin/bash
echo "=== Search entire repo for 'auto-close' ==="
rg -n "auto-close" . -iRepository: similigh/simili-bot
Length of output: 2096
🏁 Script executed:
#!/bin/bash
echo "=== Check what simili-cli is and where it comes from ==="
rg -n "simili-cli" . -iRepository: similigh/simili-bot
Length of output: 119
🏁 Script executed:
#!/bin/bash
echo "=== Check .github/workflows/auto-close.yml to understand the full context ==="
cat -n .github/workflows/auto-close.ymlRepository: similigh/simili-bot
Length of output: 2235
🏁 Script executed:
#!/bin/bash
echo "=== Check if auto-close is in any of the command files ==="
rg -l "auto.close\|AutoClose" cmd/simili/commands/ -iRepository: similigh/simili-bot
Length of output: 121
🏁 Script executed:
#!/bin/bash
echo "=== Check batch.go since it might contain the auto-close command ==="
grep -n "auto\|close\|Close" cmd/simili/commands/batch.go | head -20Repository: similigh/simili-bot
Length of output: 348
🏁 Script executed:
#!/bin/bash
echo "=== Check for 'auto-close' command registration ==="
rg -n "auto" cmd/simili/commands/ -i -B 2 -A 5Repository: similigh/simili-bot
Length of output: 1587
🏁 Script executed:
#!/bin/bash
echo "=== Check process.go for auto-close ==="
head -100 cmd/simili/commands/process.goRepository: similigh/simili-bot
Length of output: 3198
🏁 Script executed:
#!/bin/bash
echo "=== Search all Go files for 'auto-close' command definition ==="
rg -n "Use.*auto" cmd/ --type=goRepository: similigh/simili-bot
Length of output: 126
🏁 Script executed:
#!/bin/bash
echo "=== Check if auto-close is in process.go or any other file ==="
rg -n "auto.close" cmd/simili/commands/ --type=go -iRepository: similigh/simili-bot
Length of output: 125
🏁 Script executed:
#!/bin/bash
echo "=== Search for AddCommand or command registration ==="
rg -n "AddCommand\|registerCmd\|rootCmd.AddCommand" cmd/simili/commands/ --type=goRepository: similigh/simili-bot
Length of output: 116
🏁 Script executed:
#!/bin/bash
echo "=== Check full batch.go file for command definition ==="
head -60 cmd/simili/commands/batch.goRepository: similigh/simili-bot
Length of output: 1529
🏁 Script executed:
#!/bin/bash
echo "=== List all init() functions in commands to see registered commands ==="
rg -n "func init()" cmd/simili/commands/ -A 10Repository: similigh/simili-bot
Length of output: 5799
🏁 Script executed:
#!/bin/bash
echo "=== Check all .go files in cmd/simili for all cobra command definitions ==="
rg -n "var.*Cmd.*=.*&cobra.Command" cmd/simili/commands/Repository: similigh/simili-bot
Length of output: 455
🏁 Script executed:
#!/bin/bash
echo "=== Verify if auto-close command exists anywhere in the repo ==="
rg -n "\"auto-close\"|'auto-close'" . --type=goRepository: similigh/simili-bot
Length of output: 127
🏁 Script executed:
#!/bin/bash
echo "=== Final check: list all available commands in simili-cli ==="
rg -n "rootCmd.AddCommand" cmd/simili/commands/ --type=goRepository: similigh/simili-bot
Length of output: 377
🏁 Script executed:
#!/bin/bash
echo "=== Verify auto_closer.go exists and what it contains ==="
head -50 internal/steps/auto_closer.goRepository: similigh/simili-bot
Length of output: 1707
🏁 Script executed:
#!/bin/bash
echo "=== Search for any references to auto_closer in commands ==="
rg -n "auto_closer\|AutoClose" cmd/simili/Repository: similigh/simili-bot
Length of output: 123
🏁 Script executed:
#!/bin/bash
echo "=== Check if there's an auto-close command file we missed ==="
find cmd/simili/commands -name "*auto*" -o -name "*close*"Repository: similigh/simili-bot
Length of output: 124
🏁 Script executed:
#!/bin/bash
echo "=== Verify the workflow actually tries to call 'auto-close' ==="
grep -n "auto-close" .github/workflows/auto-close.ymlRepository: similigh/simili-bot
Length of output: 216
🏁 Script executed:
#!/bin/bash
echo "=== Check if auto-close was recently added or in progress ==="
git log --oneline --all --grep="auto-close" 2>/dev/null | head -10Repository: similigh/simili-bot
Length of output: 295
🏁 Script executed:
#!/bin/bash
echo "=== Search for any TODO or FIXME about auto-close command ==="
rg -n "TODO|FIXME" cmd/simili/commands/ -i | grep -i "auto\|close" -iRepository: similigh/simili-bot
Length of output: 124
🏁 Script executed:
#!/bin/bash
echo "=== Check git status and current HEAD to understand state ==="
git log --oneline -5
echo ""
echo "=== Check if auto-close command file exists in current HEAD ==="
git show HEAD:cmd/simili/commands/ 2>/dev/null | grep autoRepository: similigh/simili-bot
Length of output: 304
🏁 Script executed:
#!/bin/bash
echo "=== Look at the recent revert commit ==="
git show --stat 47bbe1c 2>/dev/null | head -30Repository: similigh/simili-bot
Length of output: 984
🏁 Script executed:
#!/bin/bash
echo "=== Verify what files exist now in cmd/simili/commands ==="
ls -la cmd/simili/commands/
echo ""
echo "=== Check if any file might contain auto-close ==="
find . -name "*.go" -exec grep -l "auto-close" {} \; 2>/dev/nullRepository: similigh/simili-bot
Length of output: 886
The auto-close command does not exist in the codebase.
The workflow calls ./simili-cli auto-close at line 57, but the auto-close command was reverted in commit 47bbe1c (Feb 22). The command file cmd/simili/commands/auto_close.go was removed, and the command is not registered. The workflow will fail immediately with an "unknown command" error when executed.
Either restore the auto-close command implementation or remove this workflow file entirely.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/auto-close.yml around lines 48 - 61, The workflow invokes
a non-existent CLI command "./simili-cli auto-close" (the auto-close command and
cmd/simili/commands/auto_close.go were removed), so either remove or replace
that step in .github/workflows/auto-close.yml: update the "Run auto-close" job
to call an existing command (e.g., a current simili-cli subcommand) or delete
the job, or alternatively restore and re-register the auto-close command
implementation (recreate cmd/simili/commands/auto_close.go and ensure it is
added to the root command registration) so "./simili-cli auto-close" succeeds;
pick one approach and make the corresponding change consistently in the workflow
and command registration.
| // AutoClose: override if fields are set | ||
| if child.AutoClose.GracePeriodHours != 0 { | ||
| result.AutoClose.GracePeriodHours = child.AutoClose.GracePeriodHours | ||
| } | ||
| if child.AutoClose.DryRun { | ||
| result.AutoClose.DryRun = child.AutoClose.DryRun | ||
| } |
There was a problem hiding this comment.
Child config cannot disable DryRun when parent sets it to true.
Because DryRun is a plain bool, the zero value (false) is indistinguishable from "not set." If a parent config sets dry_run: true, a child config setting dry_run: false will not override it — the if child.AutoClose.DryRun guard only fires for true.
Other boolean fields in the config (e.g., Transfer.Enabled) use *bool to handle this. Consider using *bool for consistency, or document that dry_run is "sticky" once enabled.
Suggested fix using *bool
In AutoCloseConfig:
type AutoCloseConfig struct {
GracePeriodHours int `yaml:"grace_period_hours"`
GracePeriodMinutesOverride int `yaml:"-"`
- DryRun bool `yaml:"dry_run,omitempty"`
+ DryRun *bool `yaml:"dry_run,omitempty"`
}In mergeConfigs:
- if child.AutoClose.DryRun {
+ if child.AutoClose.DryRun != nil {
result.AutoClose.DryRun = child.AutoClose.DryRun
}Then update consumers to dereference accordingly.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/core/config/config.go` around lines 393 - 399, The AutoClose.DryRun
boolean is "sticky" because child.AutoClose.DryRun is a plain bool and the merge
uses if child.AutoClose.DryRun so a child cannot unset a true parent; change
AutoClose.DryRun to *bool in the AutoCloseConfig type, update mergeConfigs (the
block that inspects child.AutoClose.DryRun/result.AutoClose.DryRun) to test for
nil and assign the pointer value when non-nil, and update any consumers to
handle dereferencing (or use helper accessors) so callers read the actual
boolean value safely; this mirrors the pattern used for Transfer.Enabled and
ensures children can explicitly set false.
| // closeIssue performs the close: swap labels, post comment, close issue. | ||
| func (ac *AutoCloser) closeIssue(ctx context.Context, org, repo string, number int) error { | ||
| // Determine effective grace period for display | ||
| gracePeriodDisplay := ac.cfg.AutoClose.GracePeriodHours | ||
| if gracePeriodDisplay <= 0 { | ||
| gracePeriodDisplay = 72 | ||
| } | ||
|
|
||
| // Post closing comment | ||
| comment := fmt.Sprintf( | ||
| "<!-- simili-bot-auto-close -->\n"+ | ||
| "### Auto-Closed as Duplicate\n\n"+ | ||
| "This issue was automatically closed because it was marked as a **potential duplicate** "+ | ||
| "and no objections were raised during the %d-hour grace period.\n\n"+ | ||
| "If this was closed in error, please reopen this issue and leave a comment explaining "+ | ||
| "why it is not a duplicate.\n\n"+ | ||
| "---\n"+ | ||
| "<sub>Generated by [Simili Bot](https://github.com/similigh/simili-bot)</sub>", | ||
| gracePeriodDisplay, | ||
| ) | ||
|
|
||
| if err := ac.github.CreateComment(ctx, org, repo, number, comment); err != nil { | ||
| return fmt.Errorf("failed to post closing comment: %w", err) | ||
| } | ||
|
|
||
| // Swap labels: remove "potential-duplicate", add "duplicate" | ||
| if err := ac.github.RemoveLabel(ctx, org, repo, number, "potential-duplicate"); err != nil { | ||
| log.Printf("[auto-closer] Warning: failed to remove 'potential-duplicate' label from #%d: %v", number, err) | ||
| } | ||
|
|
||
| if err := ac.github.AddLabels(ctx, org, repo, number, []string{"duplicate"}); err != nil { | ||
| log.Printf("[auto-closer] Warning: failed to add 'duplicate' label to #%d: %v", number, err) | ||
| } | ||
|
|
||
| // Close the issue | ||
| if err := ac.github.CloseIssue(ctx, org, repo, number); err != nil { | ||
| return fmt.Errorf("failed to close issue: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| } |
There was a problem hiding this comment.
Operation ordering in closeIssue can leave the issue in an unrecoverable state.
If RemoveLabel("potential-duplicate") succeeds (line 302) but CloseIssue fails (line 311), the issue loses its potential-duplicate label. On the next scheduled run, fetchPotentialDuplicates won't find it — the issue is left open with a duplicate label and a closing comment, but never actually closed, and will never be retried.
Reorder so the label is only removed after the issue is successfully closed:
Proposed fix: close first, then swap labels
func (ac *AutoCloser) closeIssue(ctx context.Context, org, repo string, number int) error {
// ...comment setup omitted...
if err := ac.github.CreateComment(ctx, org, repo, number, comment); err != nil {
return fmt.Errorf("failed to post closing comment: %w", err)
}
- // Swap labels: remove "potential-duplicate", add "duplicate"
- if err := ac.github.RemoveLabel(ctx, org, repo, number, "potential-duplicate"); err != nil {
- log.Printf("[auto-closer] Warning: failed to remove 'potential-duplicate' label from #%d: %v", number, err)
- }
-
- if err := ac.github.AddLabels(ctx, org, repo, number, []string{"duplicate"}); err != nil {
- log.Printf("[auto-closer] Warning: failed to add 'duplicate' label to #%d: %v", number, err)
- }
-
// Close the issue
if err := ac.github.CloseIssue(ctx, org, repo, number); err != nil {
return fmt.Errorf("failed to close issue: %w", err)
}
+ // Swap labels only after successful close
+ if err := ac.github.RemoveLabel(ctx, org, repo, number, "potential-duplicate"); err != nil {
+ log.Printf("[auto-closer] Warning: failed to remove 'potential-duplicate' label from #%d: %v", number, err)
+ }
+
+ if err := ac.github.AddLabels(ctx, org, repo, number, []string{"duplicate"}); err != nil {
+ log.Printf("[auto-closer] Warning: failed to add 'duplicate' label to #%d: %v", number, err)
+ }
+
return nil
}📝 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.
| // closeIssue performs the close: swap labels, post comment, close issue. | |
| func (ac *AutoCloser) closeIssue(ctx context.Context, org, repo string, number int) error { | |
| // Determine effective grace period for display | |
| gracePeriodDisplay := ac.cfg.AutoClose.GracePeriodHours | |
| if gracePeriodDisplay <= 0 { | |
| gracePeriodDisplay = 72 | |
| } | |
| // Post closing comment | |
| comment := fmt.Sprintf( | |
| "<!-- simili-bot-auto-close -->\n"+ | |
| "### Auto-Closed as Duplicate\n\n"+ | |
| "This issue was automatically closed because it was marked as a **potential duplicate** "+ | |
| "and no objections were raised during the %d-hour grace period.\n\n"+ | |
| "If this was closed in error, please reopen this issue and leave a comment explaining "+ | |
| "why it is not a duplicate.\n\n"+ | |
| "---\n"+ | |
| "<sub>Generated by [Simili Bot](https://github.com/similigh/simili-bot)</sub>", | |
| gracePeriodDisplay, | |
| ) | |
| if err := ac.github.CreateComment(ctx, org, repo, number, comment); err != nil { | |
| return fmt.Errorf("failed to post closing comment: %w", err) | |
| } | |
| // Swap labels: remove "potential-duplicate", add "duplicate" | |
| if err := ac.github.RemoveLabel(ctx, org, repo, number, "potential-duplicate"); err != nil { | |
| log.Printf("[auto-closer] Warning: failed to remove 'potential-duplicate' label from #%d: %v", number, err) | |
| } | |
| if err := ac.github.AddLabels(ctx, org, repo, number, []string{"duplicate"}); err != nil { | |
| log.Printf("[auto-closer] Warning: failed to add 'duplicate' label to #%d: %v", number, err) | |
| } | |
| // Close the issue | |
| if err := ac.github.CloseIssue(ctx, org, repo, number); err != nil { | |
| return fmt.Errorf("failed to close issue: %w", err) | |
| } | |
| return nil | |
| } | |
| // closeIssue performs the close: swap labels, post comment, close issue. | |
| func (ac *AutoCloser) closeIssue(ctx context.Context, org, repo string, number int) error { | |
| // Determine effective grace period for display | |
| gracePeriodDisplay := ac.cfg.AutoClose.GracePeriodHours | |
| if gracePeriodDisplay <= 0 { | |
| gracePeriodDisplay = 72 | |
| } | |
| // Post closing comment | |
| comment := fmt.Sprintf( | |
| "<!-- simili-bot-auto-close -->\n"+ | |
| "### Auto-Closed as Duplicate\n\n"+ | |
| "This issue was automatically closed because it was marked as a **potential duplicate** "+ | |
| "and no objections were raised during the %d-hour grace period.\n\n"+ | |
| "If this was closed in error, please reopen this issue and leave a comment explaining "+ | |
| "why it is not a duplicate.\n\n"+ | |
| "---\n"+ | |
| "<sub>Generated by [Simili Bot](https://github.com/similigh/simili-bot)</sub>", | |
| gracePeriodDisplay, | |
| ) | |
| if err := ac.github.CreateComment(ctx, org, repo, number, comment); err != nil { | |
| return fmt.Errorf("failed to post closing comment: %w", err) | |
| } | |
| // Close the issue | |
| if err := ac.github.CloseIssue(ctx, org, repo, number); err != nil { | |
| return fmt.Errorf("failed to close issue: %w", err) | |
| } | |
| // Swap labels only after successful close | |
| if err := ac.github.RemoveLabel(ctx, org, repo, number, "potential-duplicate"); err != nil { | |
| log.Printf("[auto-closer] Warning: failed to remove 'potential-duplicate' label from #%d: %v", number, err) | |
| } | |
| if err := ac.github.AddLabels(ctx, org, repo, number, []string{"duplicate"}); err != nil { | |
| log.Printf("[auto-closer] Warning: failed to add 'duplicate' label to #%d: %v", number, err) | |
| } | |
| return nil | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/steps/auto_closer.go` around lines 276 - 316, closeIssue currently
removes the "potential-duplicate" label before attempting to close the issue,
which can leave the issue unrecoverable if CloseIssue fails; change the
operation order in closeIssue so you Post the closing comment, then call
ac.github.CloseIssue(ctx, org, repo, number) first, and only if closing succeeds
perform label updates (call ac.github.RemoveLabel and ac.github.AddLabels
afterwards), retaining the existing warning logs for label failures; keep
CreateComment before CloseIssue as-is and return the CloseIssue error unchanged
so failures prevent label mutation.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
internal/steps/auto_closer.go (1)
261-274:isBotUserhas inconsistent case-sensitivity for built-in patterns.
HasSuffix(author, "[bot]")andHasPrefix(author, "gh-simili")are case-sensitive, whileEqualFold(author, "simili-bot")is case-insensitive. GitHub usernames are case-insensitive, so"GH-SIMILI-BOT"would not be recognized as a bot by the prefix check. Normalizing to lowercase before all checks is more robust:♻️ Proposed refactor
func isBotUser(author string, configBotUsers []string) bool { - if strings.HasSuffix(author, "[bot]") || - strings.HasPrefix(author, "gh-simili") || - strings.EqualFold(author, "simili-bot") { + lower := strings.ToLower(author) + if strings.HasSuffix(lower, "[bot]") || + strings.HasPrefix(lower, "gh-simili") || + lower == "simili-bot" { return true }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/steps/auto_closer.go` around lines 261 - 274, The isBotUser function uses mixed case-sensitivity (strings.HasSuffix/HasPrefix are case-sensitive while strings.EqualFold is not); normalize the author string with strings.ToLower at the start of isBotUser (e.g., authorLower := strings.ToLower(author)) and then perform suffix/prefix/equality checks against lowercased literal patterns ("[bot]", "gh-simili", "simili-bot"); for configBotUsers either compare with strings.EqualFold or pre-normalize entries (strings.ToLower) and compare against authorLower to ensure all checks are case-insensitive.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@internal/integrations/github/client.go`:
- Around line 80-85: The nil dereference happens when asserting err as
*github.ErrorResponse and then reading ghErr.Response.StatusCode in the
RemoveLabelForIssue error handling; guard against a nil Response by checking
ghErr.Response != nil (or use errors.As to assert the type first) before
accessing StatusCode and only treat 404 as non-error when ghErr.Response is
non-nil and StatusCode == 404; update the error branch in the code around
c.client.Issues.RemoveLabelForIssue and the github.ErrorResponse check to return
nil only after confirming ghErr.Response is not nil and StatusCode == 404.
In `@internal/steps/auto_closer_test.go`:
- Around line 81-93: The test TestAutoCloseResultCounts assumes
Closed+SkippedGrace+SkippedHuman == Processed, but Run() actually increments
Processed for errors too; update the test (or add a complementary test) to
assert Closed + SkippedGrace + SkippedHuman + len(Errors) == Processed for
AutoCloseResult, and/or create a second case that populates Errors to validate
the error path; refer to AutoCloseResult fields (Processed, Closed,
SkippedGrace, SkippedHuman, Errors) and the Run() behavior when implementing the
fix.
In `@internal/steps/auto_closer.go`:
- Around line 250-253: The verbose logging line can panic because
comment.CreatedAt is a *github.Timestamp and may be nil; update the ac.verbose
logging branch (the log.Printf call that references comment.CreatedAt.Time with
variables number and author) to guard against nil by using
comment.GetCreatedAt() or by checking comment.CreatedAt != nil and using its
Time when present (falling back to a zero/placeholder time or skipping the
timestamp) before calling log.Printf so the code never dereferences a nil
CreatedAt.
- Around line 278-295: The closing comment currently uses gracePeriodDisplay
derived only from cfg.AutoClose.GracePeriodHours, ignoring the CLI override
(GracePeriodMinutesOverride) so the message can be wrong; update the code to use
the actually computed grace period (the variable used to decide closure, e.g.
gracePeriod or the value computed in Run) for the display text instead of
GracePeriodHours, and modify closeIssue (and its call site in Run) to accept
that computed grace period so the comment printed by closeIssue reflects the
real minutes/hours used for the decision; touch the symbols gracePeriodDisplay,
GracePeriodHours, GracePeriodMinutesOverride, closeIssue and Run to pass and
render the correct value.
---
Duplicate comments:
In @.github/workflows/auto-close.yml:
- Line 1: The workflow file contains CRLF line endings (look for the header line
"#
=============================================================================");
convert this file to LF endings, commit the change, and ensure future files are
normalized by adding or updating .gitattributes (e.g., set YAML files to eol=lf
or use "* text=auto eol=lf") or by running a line-ending normalization tool
(dos2unix/git config core.autocrlf) before committing so the
.github/workflows/*.yml files use LF consistently.
- Around line 48-61: The workflow step "Run auto-close" is invoking a
non-existent binary './simili-cli auto-close'; update the run command to a valid
invocation (for example use npx to run the installed package: replace
'./simili-cli auto-close' with 'npx simili-cli auto-close') or point to the
correct executable path in the repo, keeping the same flags (--repo, --config,
--verbose, $DRY_RUN_FLAG) and preserving the DRY_RUN_FLAG logic that reads the
inputs.dry_run value.
In `@internal/core/config/config.go`:
- Around line 393-399: mergeConfigs currently treats AutoClose.DryRun as a
sticky boolean by only copying it when true, which prevents explicitly setting
DryRun=false in child configs; change the AutoClose.DryRun field to a tri-state
(preferably *bool) or add an explicit DryRunSet flag in the AutoClose struct,
update parsing/unmarshal code so the presence of the field is preserved, and
modify mergeConfigs (the block handling result.AutoClose and child.AutoClose) to
check the new indicator (nil vs non-nil or DryRunSet) and assign
result.AutoClose.DryRun from child.AutoClose when the child explicitly sets the
value; adjust any consumers of AutoClose.DryRun accordingly (e.g., places
reading AutoClose.DryRun) to handle the pointer/flag semantics.
In `@internal/steps/auto_closer.go`:
- Around line 276-315: The current closeIssue flow removes the
"potential-duplicate" label before closing the issue, which can leave issues in
an unrecoverable state if the close fails; change the operation order in
closeIssue so you post the comment (CreateComment), then call CloseIssue first,
and only after a successful close attempt perform the label swaps (RemoveLabel
and AddLabels) while still logging warnings on failure — keep the existing error
returns for CreateComment and CloseIssue and retain the warning-style logging
for RemoveLabel/AddLabels so label errors do not block the close.
---
Nitpick comments:
In `@internal/steps/auto_closer.go`:
- Around line 261-274: The isBotUser function uses mixed case-sensitivity
(strings.HasSuffix/HasPrefix are case-sensitive while strings.EqualFold is not);
normalize the author string with strings.ToLower at the start of isBotUser
(e.g., authorLower := strings.ToLower(author)) and then perform
suffix/prefix/equality checks against lowercased literal patterns ("[bot]",
"gh-simili", "simili-bot"); for configBotUsers either compare with
strings.EqualFold or pre-normalize entries (strings.ToLower) and compare against
authorLower to ensure all checks are case-insensitive.
| _, err := c.client.Issues.RemoveLabelForIssue(ctx, org, repo, number, label) | ||
| if err != nil { | ||
| // If the label wasn't on the issue, GitHub returns 404 — not an error for us. | ||
| if ghErr, ok := err.(*github.ErrorResponse); ok && ghErr.Response.StatusCode == 404 { | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Guard against a nil Response before accessing StatusCode.
If ErrorResponse.Response is nil (e.g., when the error is constructed outside the normal HTTP flow), the dereference at line 83 panics.
🛡️ Proposed fix
- if ghErr, ok := err.(*github.ErrorResponse); ok && ghErr.Response.StatusCode == 404 {
+ if ghErr, ok := err.(*github.ErrorResponse); ok && ghErr.Response != nil && ghErr.Response.StatusCode == 404 {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/integrations/github/client.go` around lines 80 - 85, The nil
dereference happens when asserting err as *github.ErrorResponse and then reading
ghErr.Response.StatusCode in the RemoveLabelForIssue error handling; guard
against a nil Response by checking ghErr.Response != nil (or use errors.As to
assert the type first) before accessing StatusCode and only treat 404 as
non-error when ghErr.Response is non-nil and StatusCode == 404; update the error
branch in the code around c.client.Issues.RemoveLabelForIssue and the
github.ErrorResponse check to return nil only after confirming ghErr.Response is
not nil and StatusCode == 404.
| func TestAutoCloseResultCounts(t *testing.T) { | ||
| result := &AutoCloseResult{ | ||
| Processed: 5, | ||
| Closed: 2, | ||
| SkippedGrace: 2, | ||
| SkippedHuman: 1, | ||
| } | ||
|
|
||
| total := result.Closed + result.SkippedGrace + result.SkippedHuman | ||
| if total != result.Processed { | ||
| t.Errorf("counts don't add up: closed(%d) + grace(%d) + human(%d) = %d, want %d", | ||
| result.Closed, result.SkippedGrace, result.SkippedHuman, total, result.Processed) | ||
| } |
There was a problem hiding this comment.
TestAutoCloseResultCounts tests an invariant that doesn't hold in the presence of errors.
The Run() method increments Processed for every issue — including error cases — but errors only append to result.Errors without incrementing any counter. The true invariant is:
Closed + SkippedGrace + SkippedHuman + len(Errors) == Processed
The test as written asserts 2+2+1 == 5 against a hand-constructed struct with no errors, which is trivially true and doesn't exercise real Run() output. Consider updating the assertion or adding a complementary test case with errors:
total := result.Closed + result.SkippedGrace + result.SkippedHuman
+totalWithErrors := total + len(result.Errors)
-if total != result.Processed {
- t.Errorf(...)
+if totalWithErrors != result.Processed {
+ t.Errorf("counts don't add up: closed(%d) + grace(%d) + human(%d) + errors(%d) = %d, want %d",
+ result.Closed, result.SkippedGrace, result.SkippedHuman, len(result.Errors), totalWithErrors, result.Processed)
}📝 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.
| func TestAutoCloseResultCounts(t *testing.T) { | |
| result := &AutoCloseResult{ | |
| Processed: 5, | |
| Closed: 2, | |
| SkippedGrace: 2, | |
| SkippedHuman: 1, | |
| } | |
| total := result.Closed + result.SkippedGrace + result.SkippedHuman | |
| if total != result.Processed { | |
| t.Errorf("counts don't add up: closed(%d) + grace(%d) + human(%d) = %d, want %d", | |
| result.Closed, result.SkippedGrace, result.SkippedHuman, total, result.Processed) | |
| } | |
| func TestAutoCloseResultCounts(t *testing.T) { | |
| result := &AutoCloseResult{ | |
| Processed: 5, | |
| Closed: 2, | |
| SkippedGrace: 2, | |
| SkippedHuman: 1, | |
| } | |
| total := result.Closed + result.SkippedGrace + result.SkippedHuman | |
| totalWithErrors := total + len(result.Errors) | |
| if totalWithErrors != result.Processed { | |
| t.Errorf("counts don't add up: closed(%d) + grace(%d) + human(%d) + errors(%d) = %d, want %d", | |
| result.Closed, result.SkippedGrace, result.SkippedHuman, len(result.Errors), totalWithErrors, result.Processed) | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/steps/auto_closer_test.go` around lines 81 - 93, The test
TestAutoCloseResultCounts assumes Closed+SkippedGrace+SkippedHuman == Processed,
but Run() actually increments Processed for errors too; update the test (or add
a complementary test) to assert Closed + SkippedGrace + SkippedHuman +
len(Errors) == Processed for AutoCloseResult, and/or create a second case that
populates Errors to validate the error path; refer to AutoCloseResult fields
(Processed, Closed, SkippedGrace, SkippedHuman, Errors) and the Run() behavior
when implementing the fix.
| if ac.verbose { | ||
| log.Printf("[auto-closer] #%d: human comment by %q at %v", | ||
| number, author, comment.CreatedAt.Time) | ||
| } |
There was a problem hiding this comment.
Nil dereference on comment.CreatedAt in verbose path.
comment.CreatedAt is *github.Timestamp. If it is nil, accessing .Time at line 252 panics. While GitHub always returns created_at for comments in normal operation, this path is unguarded.
🛡️ Proposed fix
- if ac.verbose {
- log.Printf("[auto-closer] #%d: human comment by %q at %v",
- number, author, comment.CreatedAt.Time)
- }
+ if ac.verbose {
+ var createdAt interface{} = "<unknown>"
+ if comment.CreatedAt != nil {
+ createdAt = comment.CreatedAt.Time
+ }
+ log.Printf("[auto-closer] #%d: human comment by %q at %v",
+ number, author, createdAt)
+ }📝 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.
| if ac.verbose { | |
| log.Printf("[auto-closer] #%d: human comment by %q at %v", | |
| number, author, comment.CreatedAt.Time) | |
| } | |
| if ac.verbose { | |
| var createdAt interface{} = "<unknown>" | |
| if comment.CreatedAt != nil { | |
| createdAt = comment.CreatedAt.Time | |
| } | |
| log.Printf("[auto-closer] #%d: human comment by %q at %v", | |
| number, author, createdAt) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/steps/auto_closer.go` around lines 250 - 253, The verbose logging
line can panic because comment.CreatedAt is a *github.Timestamp and may be nil;
update the ac.verbose logging branch (the log.Printf call that references
comment.CreatedAt.Time with variables number and author) to guard against nil by
using comment.GetCreatedAt() or by checking comment.CreatedAt != nil and using
its Time when present (falling back to a zero/placeholder time or skipping the
timestamp) before calling log.Printf so the code never dereferences a nil
CreatedAt.
| // Determine effective grace period for display | ||
| gracePeriodDisplay := ac.cfg.AutoClose.GracePeriodHours | ||
| if gracePeriodDisplay <= 0 { | ||
| gracePeriodDisplay = 72 | ||
| } | ||
|
|
||
| // Post closing comment | ||
| comment := fmt.Sprintf( | ||
| "<!-- simili-bot-auto-close -->\n"+ | ||
| "### Auto-Closed as Duplicate\n\n"+ | ||
| "This issue was automatically closed because it was marked as a **potential duplicate** "+ | ||
| "and no objections were raised during the %d-hour grace period.\n\n"+ | ||
| "If this was closed in error, please reopen this issue and leave a comment explaining "+ | ||
| "why it is not a duplicate.\n\n"+ | ||
| "---\n"+ | ||
| "<sub>Generated by [Simili Bot](https://github.com/similigh/simili-bot)</sub>", | ||
| gracePeriodDisplay, | ||
| ) |
There was a problem hiding this comment.
Closing comment misreports grace period when GracePeriodMinutesOverride is active.
gracePeriodDisplay reads only cfg.AutoClose.GracePeriodHours, ignoring the CLI GracePeriodMinutesOverride. With --grace-minutes 5 the issue gets a comment saying "72-hour grace period" when only 5 minutes were required — misleading in test runs and audits.
🐛 Proposed fix: pass the computed grace period into `closeIssue`
Change the signature to accept the actual grace period:
-func (ac *AutoCloser) closeIssue(ctx context.Context, org, repo string, number int) error {
- gracePeriodDisplay := ac.cfg.AutoClose.GracePeriodHours
- if gracePeriodDisplay <= 0 {
- gracePeriodDisplay = 72
- }
+func (ac *AutoCloser) closeIssue(ctx context.Context, org, repo string, number int, gracePeriod time.Duration) error {
+ gracePeriodDisplay := int(gracePeriod.Hours())
+ if gracePeriodDisplay <= 0 {
+ gracePeriodDisplay = 1
+ }And pass gracePeriod at the call site in Run:
- if err := ac.closeIssue(ctx, org, repo, number); err != nil {
+ if err := ac.closeIssue(ctx, org, repo, number, gracePeriod); err != nil {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/steps/auto_closer.go` around lines 278 - 295, The closing comment
currently uses gracePeriodDisplay derived only from
cfg.AutoClose.GracePeriodHours, ignoring the CLI override
(GracePeriodMinutesOverride) so the message can be wrong; update the code to use
the actually computed grace period (the variable used to decide closure, e.g.
gracePeriod or the value computed in Run) for the display text instead of
GracePeriodHours, and modify closeIssue (and its call site in Run) to accept
that computed grace period so the comment printed by closeIssue reflects the
real minutes/hours used for the decision; touch the symbols gracePeriodDisplay,
GracePeriodHours, GracePeriodMinutesOverride, closeIssue and Run to pass and
render the correct value.
Description
Implements automated closure of duplicate issues after a configurable grace period. When the bot detects a high-confidence duplicate, it now labels the issue as
potential-duplicateand warns the author that the issue will be auto-closed in 72 hours unless they object. A newsimili auto-closeCLI command scans for these labeled issues and closes those whose grace period has expired with no human activity.Type of Change
Related Issues
Fixes #57
Relates to #54
Relates to #55
Changes Made
grace_period_hours(default: 72h) to config (internal/core/config/config.go)simili auto-closeCLI command with--dry-run,--verbose,--grace-minutesflags (cmd/simili/commands/auto_close.go)potential-duplicatelabel toSuggestedLabelswhen duplicate is detected (internal/steps/duplicate_detector.go)Testing
go build ./...successfullygo test ./...successfullygo vet ./...successfullyManual Testing on
Sachindu-Nethmin/simili-botpotential-duplicatelabel, run auto-close with--grace-minutes 1duplicate, comment postedpotential-duplicatelabel + human comment, run auto-closeskipped_human_activity)skipped_grace_period)--dry-runChecklist
Additional Notes
--grace-minutesflag is for testing only (not serialized to YAML config). In production, useauto_close.grace_period_hoursinsimili.yaml.secrets.GH_PAT— ensure this secret hasissues:writepermission..gitignorepatternsimiliblocks new files undercmd/simili/;auto_close.gowas force-added withgit add -f.Summary by CodeRabbit
New Features
Tests