Skip to content

Commit 53fd512

Browse files
authored
Merge branch 'main' into chore/dco-enforcement
2 parents 46cd0f1 + fb6f9d4 commit 53fd512

36 files changed

Lines changed: 1776 additions & 172 deletions

.github/workflows/auto-close.yml

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ on:
2424
options:
2525
- 'false'
2626
- 'true'
27+
grace_period_minutes:
28+
description: 'Grace period in minutes before a potential-duplicate issue is labelled duplicate and closed (overrides config). Leave empty to use the configured default.'
29+
required: false
30+
default: ''
31+
type: string
2732

2833
permissions:
2934
contents: read
@@ -47,15 +52,26 @@ jobs:
4752

4853
- name: Run auto-close
4954
env:
50-
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
55+
GITHUB_TOKEN: ${{ github.token }}
56+
GRACE_PERIOD_INPUT: ${{ inputs.grace_period_minutes }}
5157
run: |
5258
DRY_RUN_FLAG=""
5359
if [ "${{ inputs.dry_run }}" = "true" ]; then
5460
DRY_RUN_FLAG="--dry-run"
5561
fi
5662
63+
GRACE_ARGS=()
64+
if [[ -n "$GRACE_PERIOD_INPUT" ]]; then
65+
if [[ "$GRACE_PERIOD_INPUT" =~ ^[0-9]+$ ]]; then
66+
GRACE_ARGS=("--grace-period-minutes" "$GRACE_PERIOD_INPUT")
67+
else
68+
echo "::warning::grace_period_minutes must be a positive integer; ignoring value '$GRACE_PERIOD_INPUT'"
69+
fi
70+
fi
71+
5772
./simili-cli auto-close \
5873
--repo "${{ github.repository }}" \
5974
--config .github/simili.yaml \
6075
--verbose \
61-
$DRY_RUN_FLAG
76+
$DRY_RUN_FLAG \
77+
"${GRACE_ARGS[@]}"

.github/workflows/e2e-test.yml

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,38 @@ jobs:
284284
echo "Waiting 15s for GitHub to register the workflow…"
285285
sleep 15
286286
287+
# ----- 7b. Create auto-closer test issue -----
288+
# Created BEFORE the trigger issue so it ages long enough to exceed the
289+
# 1-minute grace-period override we use in the auto-closer E2E step below.
290+
# We manually ensure the "potential-duplicate" label exists and apply it
291+
# immediately, independent of what the triage bot decides.
292+
- name: Setup auto-closer test issue
293+
id: ac_setup
294+
env:
295+
GH_TOKEN: ${{ secrets.BOT_PAT }}
296+
run: |
297+
REPO="${{ env.BOT_NAME }}/${{ env.TEST_REPO_NAME }}"
298+
299+
# Ensure the potential-duplicate label exists (bot creates it lazily;
300+
# this step runs before the trigger issue is triaged).
301+
gh label create "potential-duplicate" \
302+
--repo "$REPO" \
303+
--color "FBCA04" \
304+
--description "Potential duplicate — pending grace period" \
305+
2>/dev/null || true
306+
307+
# Create the AC test issue
308+
URL=$(gh issue create --repo "$REPO" \
309+
--title "Auto-closer grace period and human-activity E2E test" \
310+
--body "This issue is created by the E2E pipeline to verify the auto-closer detects expired grace periods and correctly skips issues with human activity signals.")
311+
AC_NUM=$(echo "$URL" | grep -oE '[0-9]+$')
312+
echo "ac_issue_number=$AC_NUM" >> "$GITHUB_OUTPUT"
313+
echo "AC test issue: #$AC_NUM"
314+
315+
# Apply potential-duplicate label immediately so the grace period clock starts
316+
gh issue edit "$AC_NUM" --repo "$REPO" --add-label "potential-duplicate"
317+
echo "✅ Labeled #$AC_NUM as potential-duplicate"
318+
287319
# ----- 8. Create trigger issue (should match seed #1) -----
288320
- name: Create trigger issue
289321
id: trigger
@@ -366,6 +398,67 @@ jobs:
366398
fi
367399
echo "✅ No loop – single comment"
368400
401+
# ----- 11b. Auto-closer E2E test -----
402+
# By this point the AC test issue (step 7b) and the trigger issue (step 8)
403+
# have both been labeled "potential-duplicate" for 6–8 minutes, well past
404+
# the 1-minute grace-period override used here.
405+
# In dry-run mode the closer reports what it WOULD do, so no issues are
406+
# actually closed. We verify:
407+
# • The CLI runs without error and emits valid JSON.
408+
# • At least the AC test issue is processed (processed >= 1).
409+
# • Issues past grace with no human activity are reported as "closed"
410+
# (dry-run), confirming Checks A, B, C found no human signals.
411+
- name: Run auto-closer E2E test
412+
id: ac_test
413+
if: always() && steps.ac_setup.outputs.ac_issue_number != ''
414+
env:
415+
GITHUB_TOKEN: ${{ secrets.BOT_PAT }}
416+
GH_TOKEN: ${{ secrets.BOT_PAT }}
417+
run: |
418+
REPO="${{ env.BOT_NAME }}/${{ env.TEST_REPO_NAME }}"
419+
420+
echo "Running auto-closer dry-run (grace-period-minutes=1)…"
421+
422+
# Use the config from the cloned test-repo working tree.
423+
# Qdrant/Gemini sections are ignored by the auto-closer.
424+
AC_OUTPUT=$("$RUNNER_TEMP/simili-cli" auto-close \
425+
--repo "$REPO" \
426+
--config "test-repo-wf/.github/simili.yaml" \
427+
--dry-run \
428+
--grace-period-minutes 1 \
429+
--verbose \
430+
2>/tmp/ac-stderr.txt) || AC_EXIT=$?
431+
432+
echo "--- stderr ---"
433+
cat /tmp/ac-stderr.txt || true
434+
echo "--- stdout (JSON) ---"
435+
echo "$AC_OUTPUT"
436+
437+
# Parse summary fields from JSON output
438+
PROCESSED=$(echo "$AC_OUTPUT" | jq -r '.processed // 0' 2>/dev/null || echo "0")
439+
CLOSED=$(echo "$AC_OUTPUT" | jq -r '.closed // 0' 2>/dev/null || echo "0")
440+
SKIPPED_GRACE=$(echo "$AC_OUTPUT" | jq -r '.skipped_grace_period // 0' 2>/dev/null || echo "0")
441+
SKIPPED_HUMAN=$(echo "$AC_OUTPUT" | jq -r '.skipped_human_activity // 0' 2>/dev/null || echo "0")
442+
443+
echo "ac_processed=$PROCESSED" >> "$GITHUB_OUTPUT"
444+
echo "ac_closed=$CLOSED" >> "$GITHUB_OUTPUT"
445+
echo "ac_skipped_grace=$SKIPPED_GRACE" >> "$GITHUB_OUTPUT"
446+
echo "ac_skipped_human=$SKIPPED_HUMAN" >> "$GITHUB_OUTPUT"
447+
448+
echo ""
449+
echo "Auto-closer summary: processed=$PROCESSED closed=$CLOSED skipped_grace=$SKIPPED_GRACE skipped_human=$SKIPPED_HUMAN"
450+
451+
if [ "${AC_EXIT:-0}" -ne 0 ]; then
452+
echo "❌ auto-close command failed (exit $AC_EXIT)"
453+
exit 1
454+
fi
455+
456+
if [ "$PROCESSED" -gt 0 ]; then
457+
echo "✅ Auto-closer processed $PROCESSED issue(s) — dry-run OK"
458+
else
459+
echo "⚠️ Auto-closer found no issues to process (AC test issue may not be labeled yet)"
460+
fi
461+
369462
# ----- 12. Cleanup Qdrant (always) -----
370463
- name: Cleanup Qdrant collection
371464
if: always()
@@ -422,12 +515,25 @@ jobs:
422515
REPO="${{ env.BOT_NAME }}/${{ env.TEST_REPO_NAME }}"
423516
OK="${{ steps.poll.outputs.bot_commented }}"
424517
ICON=$([ "$OK" = "true" ] && echo "✅" || echo "❌")
518+
519+
AC_PROC="${{ steps.ac_test.outputs.ac_processed }}"
520+
AC_CLOSED="${{ steps.ac_test.outputs.ac_closed }}"
521+
AC_GRACE="${{ steps.ac_test.outputs.ac_skipped_grace }}"
522+
AC_HUMAN="${{ steps.ac_test.outputs.ac_skipped_human }}"
523+
if [ -n "$AC_PROC" ]; then
524+
AC_LINE="| **Auto-closer (dry-run)** | processed: ${AC_PROC} closed: ${AC_CLOSED} grace: ${AC_GRACE} human: ${AC_HUMAN} |"
525+
else
526+
AC_LINE="| **Auto-closer** | skipped (AC setup failed) |"
527+
fi
528+
425529
gh pr comment "$PR_NUM" \
426530
--repo "${{ github.repository }}" \
427531
--body "## 🧪 E2E Test
428532
429533
${ICON} **Bot responded:** $([ "$OK" = "true" ] && echo "yes" || echo "no")
430534
535+
${AC_LINE}
536+
431537
**Test repo →** [${REPO}](https://github.com/${REPO})
432538
**Run →** [logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
433539
@@ -449,4 +555,10 @@ jobs:
449555
echo "| Bot responded | ❌ |"
450556
fi
451557
echo "| Test repo | [${{ env.BOT_NAME }}/${{ env.TEST_REPO_NAME }}](https://github.com/${{ env.BOT_NAME }}/${{ env.TEST_REPO_NAME }}) |"
558+
AC="${{ steps.ac_test.outputs.ac_processed }}"
559+
if [ -n "$AC" ]; then
560+
echo "| Auto-closer (dry-run) | processed: $AC closed: ${{ steps.ac_test.outputs.ac_closed }} grace: ${{ steps.ac_test.outputs.ac_skipped_grace }} human: ${{ steps.ac_test.outputs.ac_skipped_human }} |"
561+
else
562+
echo "| Auto-closer | ⚠️ no output |"
563+
fi
452564
} >> "$GITHUB_STEP_SUMMARY"

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,50 @@ Notes:
220220
- `llm.api_key` can be omitted if `GEMINI_API_KEY` is set.
221221
- You can override the model at runtime with `LLM_MODEL`.
222222

223+
### `simili auto-close`
224+
225+
Scan all open issues labelled `potential-duplicate` and close those whose grace period has expired with no human activity. Closed issues are relabelled from `potential-duplicate` → `duplicate`.
226+
227+
```bash
228+
simili auto-close --repo owner/repo --grace-period-minutes 60
229+
```
230+
231+
**Flags:**
232+
- `--repo` (required): Target repository (`owner/name`); falls back to `GITHUB_REPOSITORY` env var
233+
- `--grace-period-minutes`: Override the grace period in minutes for this run (see precedence below)
234+
- `--dry-run`: Print what would be closed without making any changes
235+
- `--config`: Path to `simili.yaml` (auto-discovered if omitted)
236+
237+
**Grace period precedence** (highest → lowest):
238+
239+
| Source | How to set |
240+
|--------|-----------|
241+
| `--grace-period-minutes` CLI flag | Pass at runtime — overrides everything |
242+
| `auto_close.grace_period_hours` in `simili.yaml` | Persistent per-repo config |
243+
| Built-in default | 72 hours (3 days) |
244+
245+
**`simili.yaml` configuration:**
246+
247+
```yaml
248+
auto_close:
249+
grace_period_hours: 48 # default: 72
250+
dry_run: false
251+
```
252+
253+
**Human activity signals** — any of these prevent auto-close:
254+
1. A negative reaction (👎 or 😕) on the bot's triage comment by a non-bot user.
255+
2. The issue was reopened by a human after the `potential-duplicate` label was applied.
256+
3. A non-bot comment posted after the label was applied.
257+
258+
**GitHub Actions usage** — the `auto-close.yml` workflow runs daily at 10:00 UTC and can be triggered manually via `workflow_dispatch` with an optional `grace_period_minutes` input:
259+
260+
```yaml
261+
# Trigger from GitHub UI or gh CLI:
262+
gh workflow run auto-close.yml -f grace_period_minutes=60 -f dry_run=false
263+
```
264+
265+
Leaving `grace_period_minutes` empty uses the value from `simili.yaml` (or the 72 h default).
266+
223267
## Development
224268

225269
```bash

cmd/simili-web/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717

1818
"github.com/similigh/simili-bot/internal/core/config"
1919
"github.com/similigh/simili-bot/internal/core/pipeline"
20-
"github.com/similigh/simili-bot/internal/integrations/gemini"
20+
"github.com/similigh/simili-bot/internal/integrations/ai"
2121
"github.com/similigh/simili-bot/internal/integrations/github"
2222
"github.com/similigh/simili-bot/internal/integrations/qdrant"
2323
"github.com/similigh/simili-bot/internal/steps"
@@ -115,7 +115,7 @@ func initDependencies(cfg *config.Config) (*pipeline.Dependencies, error) {
115115
}
116116

117117
// Embedder (Gemini/OpenAI auto-selected by available keys)
118-
embedder, err := gemini.NewEmbedder(cfg.Embedding.APIKey, cfg.Embedding.Model)
118+
embedder, err := ai.NewEmbedder(cfg.Embedding.APIKey, cfg.Embedding.Model)
119119
if err != nil {
120120
return nil, fmt.Errorf("failed to init embedder: %w", err)
121121
}
@@ -151,7 +151,7 @@ func initDependencies(cfg *config.Config) (*pipeline.Dependencies, error) {
151151
if envModel := os.Getenv("LLM_MODEL"); envModel != "" {
152152
llmModel = envModel
153153
}
154-
llm, err := gemini.NewLLMClient(llmKey, llmModel)
154+
llm, err := ai.NewLLMClient(llmKey, llmModel)
155155
if err != nil {
156156
return nil, fmt.Errorf("failed to init LLM: %w", err)
157157
}

0 commit comments

Comments
 (0)