From b7a8627bcfa64d2611e08f4d7963f35956e07400 Mon Sep 17 00:00:00 2001
From: Phan Huy Tran
Date: Wed, 28 May 2025 12:50:54 +0200
Subject: [PATCH 01/11] test: dice service
---
.../casino/blackjack/BlackJackGameEntity.java | 2 -
.../java/de/szut/casino/dice/DiceDto.java | 2 +
.../java/de/szut/casino/dice/DiceService.java | 5 +-
.../de/szut/casino/dice/DiceServiceTest.java | 216 ++++++++++++++++++
4 files changed, 221 insertions(+), 4 deletions(-)
create mode 100644 backend/src/test/java/de/szut/casino/dice/DiceServiceTest.java
diff --git a/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameEntity.java b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameEntity.java
index c9f57d7..4f22c9d 100644
--- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameEntity.java
+++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameEntity.java
@@ -4,8 +4,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import de.szut.casino.user.UserEntity;
import jakarta.persistence.*;
-import jakarta.validation.constraints.NotNull;
-import jakarta.validation.constraints.Positive;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
diff --git a/backend/src/main/java/de/szut/casino/dice/DiceDto.java b/backend/src/main/java/de/szut/casino/dice/DiceDto.java
index ecbf3d7..f0caf48 100644
--- a/backend/src/main/java/de/szut/casino/dice/DiceDto.java
+++ b/backend/src/main/java/de/szut/casino/dice/DiceDto.java
@@ -5,12 +5,14 @@ import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
+import lombok.NoArgsConstructor;
import lombok.Setter;
import java.math.BigDecimal;
@Getter
@Setter
+@NoArgsConstructor
public class DiceDto extends BetDto {
private boolean rollOver;
diff --git a/backend/src/main/java/de/szut/casino/dice/DiceService.java b/backend/src/main/java/de/szut/casino/dice/DiceService.java
index 71e4584..836620b 100644
--- a/backend/src/main/java/de/szut/casino/dice/DiceService.java
+++ b/backend/src/main/java/de/szut/casino/dice/DiceService.java
@@ -11,10 +11,11 @@ import java.util.Random;
@Service
public class DiceService {
private static final int MAX_DICE_VALUE = 100;
- private final Random random = new Random();
+ private final Random random;
private final BalanceService balanceService;
- public DiceService(BalanceService balanceService) {
+ public DiceService(Random random, BalanceService balanceService) {
+ this.random = random;
this.balanceService = balanceService;
}
diff --git a/backend/src/test/java/de/szut/casino/dice/DiceServiceTest.java b/backend/src/test/java/de/szut/casino/dice/DiceServiceTest.java
new file mode 100644
index 0000000..21ca6cf
--- /dev/null
+++ b/backend/src/test/java/de/szut/casino/dice/DiceServiceTest.java
@@ -0,0 +1,216 @@
+package de.szut.casino.dice;
+
+import de.szut.casino.shared.service.BalanceService;
+import de.szut.casino.user.UserEntity;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.math.BigDecimal;
+import java.util.Random;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class DiceServiceTest {
+
+ @Mock
+ private BalanceService balanceService;
+
+ @Mock
+ private Random random;
+
+ @InjectMocks
+ private DiceService diceService;
+
+ private UserEntity user;
+ private DiceDto diceDto;
+
+ @BeforeEach
+ void setUp() {
+ user = new UserEntity();
+ user.setId(1L);
+ user.setBalance(BigDecimal.valueOf(1000));
+
+ diceDto = new DiceDto();
+ diceDto.setBetAmount(BigDecimal.valueOf(10));
+ diceDto.setTargetValue(BigDecimal.valueOf(50));
+ diceDto.setRollOver(true);
+ }
+
+ @Test
+ void play_rollOver_win() {
+ diceDto.setRollOver(true);
+ diceDto.setTargetValue(BigDecimal.valueOf(50));
+ when(random.nextInt(anyInt())).thenReturn(55);
+
+ DiceResult result = diceService.play(user, diceDto);
+
+ assertTrue(result.isWin());
+ assertEquals(BigDecimal.valueOf(56), result.getRolledValue());
+ verify(balanceService, times(1)).subtractFunds(user, diceDto.getBetAmount());
+ verify(balanceService, times(1)).addFunds(eq(user), any(BigDecimal.class));
+ }
+
+ @Test
+ void play_rollOver_lose() {
+ diceDto.setRollOver(true);
+ diceDto.setTargetValue(BigDecimal.valueOf(50));
+ when(random.nextInt(anyInt())).thenReturn(49);
+
+ DiceResult result = diceService.play(user, diceDto);
+
+ assertFalse(result.isWin());
+ assertEquals(BigDecimal.valueOf(50), result.getRolledValue());
+ assertEquals(BigDecimal.ZERO, result.getPayout());
+ verify(balanceService, times(1)).subtractFunds(user, diceDto.getBetAmount());
+ verify(balanceService, never()).addFunds(eq(user), any(BigDecimal.class));
+ }
+
+ @Test
+ void play_rollUnder_win() {
+ diceDto.setRollOver(false);
+ diceDto.setTargetValue(BigDecimal.valueOf(50));
+ when(random.nextInt(anyInt())).thenReturn(48);
+
+ DiceResult result = diceService.play(user, diceDto);
+
+ assertTrue(result.isWin());
+ assertEquals(BigDecimal.valueOf(49), result.getRolledValue());
+ verify(balanceService, times(1)).subtractFunds(user, diceDto.getBetAmount());
+ verify(balanceService, times(1)).addFunds(eq(user), any(BigDecimal.class));
+ }
+
+ @Test
+ void play_rollUnder_lose() {
+ diceDto.setRollOver(false);
+ diceDto.setTargetValue(BigDecimal.valueOf(50));
+ when(random.nextInt(anyInt())).thenReturn(50);
+
+ DiceResult result = diceService.play(user, diceDto);
+
+ assertFalse(result.isWin());
+ assertEquals(BigDecimal.valueOf(51), result.getRolledValue());
+ assertEquals(BigDecimal.ZERO, result.getPayout());
+ verify(balanceService, times(1)).subtractFunds(user, diceDto.getBetAmount());
+ verify(balanceService, never()).addFunds(eq(user), any(BigDecimal.class));
+ }
+
+ @Test
+ void play_rollOver_targetValueOne_lose() {
+ diceDto.setRollOver(true);
+ diceDto.setTargetValue(BigDecimal.valueOf(1));
+ when(random.nextInt(anyInt())).thenReturn(0);
+
+ DiceResult result = diceService.play(user, diceDto);
+
+ assertFalse(result.isWin());
+ assertEquals(BigDecimal.valueOf(1), result.getRolledValue());
+ assertEquals(BigDecimal.ZERO, result.getPayout());
+ }
+
+ @Test
+ void play_rollUnder_targetValueOne_win() {
+ diceDto.setRollOver(false);
+ diceDto.setTargetValue(BigDecimal.valueOf(1));
+ when(random.nextInt(anyInt())).thenReturn(0);
+
+ DiceResult result = diceService.play(user, diceDto);
+
+ assertFalse(result.isWin());
+ assertEquals(BigDecimal.valueOf(1), result.getRolledValue());
+ assertEquals(BigDecimal.ZERO, result.getPayout());
+ }
+
+ @Test
+ void play_rollOver_targetValueNinetyNine_win() {
+ diceDto.setRollOver(true);
+ diceDto.setTargetValue(BigDecimal.valueOf(99));
+ when(random.nextInt(anyInt())).thenReturn(99);
+
+ DiceResult result = diceService.play(user, diceDto);
+
+ assertTrue(result.isWin());
+ assertEquals(BigDecimal.valueOf(100), result.getRolledValue());
+ }
+
+ @Test
+ void play_rollUnder_targetValueNinetyNine_win() {
+ diceDto.setRollOver(false);
+ diceDto.setTargetValue(BigDecimal.valueOf(99));
+ when(random.nextInt(anyInt())).thenReturn(97);
+
+ DiceResult result = diceService.play(user, diceDto);
+
+ assertTrue(result.isWin());
+ assertEquals(BigDecimal.valueOf(98), result.getRolledValue());
+ }
+
+ @Test
+ void play_rollOver_targetValueOneHundred_lose() {
+ diceDto.setRollOver(true);
+ diceDto.setTargetValue(BigDecimal.valueOf(100));
+ when(random.nextInt(anyInt())).thenReturn(99);
+
+ DiceResult result = diceService.play(user, diceDto);
+
+ assertFalse(result.isWin());
+ assertEquals(BigDecimal.valueOf(100), result.getRolledValue());
+ assertEquals(BigDecimal.ZERO, result.getPayout());
+ }
+
+ @Test
+ void play_rollUnder_targetValueOneHundred_win() {
+ diceDto.setRollOver(false);
+ diceDto.setTargetValue(BigDecimal.valueOf(100));
+ when(random.nextInt(anyInt())).thenReturn(98);
+
+ DiceResult result = diceService.play(user, diceDto);
+
+ assertTrue(result.isWin());
+ assertEquals(BigDecimal.valueOf(99), result.getRolledValue());
+ }
+
+ @Test
+ void play_payoutCalculationCorrect() {
+ diceDto.setRollOver(true);
+ diceDto.setTargetValue(BigDecimal.valueOf(75));
+ when(random.nextInt(anyInt())).thenReturn(75);
+
+ // Multiplier for win chance 25: (100-1)/25 = 99/25 = 3.96
+ // Payout: 10 * 3.96 = 39.6
+
+ DiceResult result = diceService.play(user, diceDto);
+
+ assertTrue(result.isWin());
+ assertEquals(BigDecimal.valueOf(39.6).stripTrailingZeros(), result.getPayout().stripTrailingZeros());
+ }
+
+ @Test
+ void play_payoutCalculationCorrect_rollUnder() {
+ diceDto.setRollOver(false);
+ diceDto.setTargetValue(BigDecimal.valueOf(25));
+ when(random.nextInt(anyInt())).thenReturn(0);
+
+ // Multiplier for win chance 24: (100-1)/24 = 99/24 = 4.125
+ // Payout: 10 * 4.125 = 41.25
+
+ DiceResult result = diceService.play(user, diceDto);
+
+ assertTrue(result.isWin());
+ assertEquals(BigDecimal.valueOf(41.25).stripTrailingZeros(), result.getPayout().stripTrailingZeros());
+ }
+
+ @Test
+ void play_betAmountSubtracted() {
+ when(random.nextInt(anyInt())).thenReturn(50);
+
+ diceService.play(user, diceDto);
+
+ verify(balanceService, times(1)).subtractFunds(user, diceDto.getBetAmount());
+ }
+}
From 78b8f4696c658d15c4b388577dce59fc5a85d7be Mon Sep 17 00:00:00 2001
From: Phan Huy Tran
Date: Wed, 28 May 2025 12:58:40 +0200
Subject: [PATCH 02/11] test: refactor
---
.../de/szut/casino/dice/DiceServiceTest.java | 61 +++++++++++++++----
1 file changed, 48 insertions(+), 13 deletions(-)
diff --git a/backend/src/test/java/de/szut/casino/dice/DiceServiceTest.java b/backend/src/test/java/de/szut/casino/dice/DiceServiceTest.java
index 21ca6cf..ec5f979 100644
--- a/backend/src/test/java/de/szut/casino/dice/DiceServiceTest.java
+++ b/backend/src/test/java/de/szut/casino/dice/DiceServiceTest.java
@@ -10,6 +10,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.*;
@@ -101,78 +102,112 @@ class DiceServiceTest {
}
@Test
- void play_rollOver_targetValueOne_lose() {
+ void play_rollOver_targetValueOne_rolledOne_lose() {
diceDto.setRollOver(true);
diceDto.setTargetValue(BigDecimal.valueOf(1));
- when(random.nextInt(anyInt())).thenReturn(0);
+ when(random.nextInt(anyInt())).thenReturn(0); // rolledValue = 1
DiceResult result = diceService.play(user, diceDto);
assertFalse(result.isWin());
assertEquals(BigDecimal.valueOf(1), result.getRolledValue());
assertEquals(BigDecimal.ZERO, result.getPayout());
+ verify(balanceService, times(1)).subtractFunds(user, diceDto.getBetAmount());
+ verify(balanceService, never()).addFunds(eq(user), any(BigDecimal.class));
}
@Test
- void play_rollUnder_targetValueOne_win() {
+ void play_rollOver_targetValueOne_rolledTwo_win() {
+ diceDto.setRollOver(true);
+ diceDto.setTargetValue(BigDecimal.valueOf(1));
+ when(random.nextInt(anyInt())).thenReturn(1); // rolledValue = 2
+
+ DiceResult result = diceService.play(user, diceDto);
+
+ assertTrue(result.isWin());
+ assertEquals(BigDecimal.valueOf(2), result.getRolledValue());
+ // Win chance for target 1 (roll over) is 99. Multiplier = (100-1)/99 = 1
+ assertEquals(diceDto.getBetAmount().stripTrailingZeros(), result.getPayout().stripTrailingZeros());
+ verify(balanceService, times(1)).subtractFunds(user, diceDto.getBetAmount());
+ verify(balanceService, times(1)).addFunds(eq(user), any(BigDecimal.class));
+ }
+
+ @Test
+ void play_rollUnder_targetValueOne_alwaysLose_winChanceZero() {
diceDto.setRollOver(false);
diceDto.setTargetValue(BigDecimal.valueOf(1));
- when(random.nextInt(anyInt())).thenReturn(0);
+ when(random.nextInt(anyInt())).thenReturn(0); // rolledValue = 1
DiceResult result = diceService.play(user, diceDto);
assertFalse(result.isWin());
assertEquals(BigDecimal.valueOf(1), result.getRolledValue());
assertEquals(BigDecimal.ZERO, result.getPayout());
+ verify(balanceService, times(1)).subtractFunds(user, diceDto.getBetAmount());
+ verify(balanceService, never()).addFunds(eq(user), any(BigDecimal.class));
}
@Test
- void play_rollOver_targetValueNinetyNine_win() {
+ void play_rollOver_targetValueNinetyNine_rolledHundred_win() {
diceDto.setRollOver(true);
diceDto.setTargetValue(BigDecimal.valueOf(99));
- when(random.nextInt(anyInt())).thenReturn(99);
+ when(random.nextInt(anyInt())).thenReturn(99); // rolledValue = 100
DiceResult result = diceService.play(user, diceDto);
assertTrue(result.isWin());
assertEquals(BigDecimal.valueOf(100), result.getRolledValue());
+ // Win chance for target 99 (roll over) is 1. Multiplier = (100-1)/1 = 99
+ assertEquals(diceDto.getBetAmount().multiply(BigDecimal.valueOf(99)).stripTrailingZeros(), result.getPayout().stripTrailingZeros());
+ verify(balanceService, times(1)).subtractFunds(user, diceDto.getBetAmount());
+ verify(balanceService, times(1)).addFunds(eq(user), any(BigDecimal.class));
}
@Test
- void play_rollUnder_targetValueNinetyNine_win() {
+ void play_rollUnder_targetValueNinetyNine_rolledNinetyEight_win() {
diceDto.setRollOver(false);
diceDto.setTargetValue(BigDecimal.valueOf(99));
- when(random.nextInt(anyInt())).thenReturn(97);
+ when(random.nextInt(anyInt())).thenReturn(97); // rolledValue = 98
DiceResult result = diceService.play(user, diceDto);
assertTrue(result.isWin());
assertEquals(BigDecimal.valueOf(98), result.getRolledValue());
+ // Win chance for target 99 (roll under) is 98. Multiplier = (100-1)/98 = 99/98
+ assertEquals(diceDto.getBetAmount().multiply(BigDecimal.valueOf(99).divide(BigDecimal.valueOf(98), 4, RoundingMode.HALF_UP)), result.getPayout());
+ verify(balanceService, times(1)).subtractFunds(user, diceDto.getBetAmount());
+ verify(balanceService, times(1)).addFunds(eq(user), any(BigDecimal.class));
}
@Test
- void play_rollOver_targetValueOneHundred_lose() {
+ void play_rollOver_targetValueOneHundred_alwaysLose_winChanceZero() {
diceDto.setRollOver(true);
diceDto.setTargetValue(BigDecimal.valueOf(100));
- when(random.nextInt(anyInt())).thenReturn(99);
+ when(random.nextInt(anyInt())).thenReturn(99); // rolledValue = 100
DiceResult result = diceService.play(user, diceDto);
assertFalse(result.isWin());
assertEquals(BigDecimal.valueOf(100), result.getRolledValue());
assertEquals(BigDecimal.ZERO, result.getPayout());
+ verify(balanceService, times(1)).subtractFunds(user, diceDto.getBetAmount());
+ verify(balanceService, never()).addFunds(eq(user), any(BigDecimal.class));
}
@Test
- void play_rollUnder_targetValueOneHundred_win() {
- diceDto.setRollOver(false);
+ void play_rollUnder_targetValueOneHundred_rolledNinetyNine_win() {
+ diceDto.setRollOver(false); // Assuming a typo in original, should be setRollOver(false)
diceDto.setTargetValue(BigDecimal.valueOf(100));
- when(random.nextInt(anyInt())).thenReturn(98);
+ when(random.nextInt(anyInt())).thenReturn(98); // rolledValue = 99
DiceResult result = diceService.play(user, diceDto);
assertTrue(result.isWin());
assertEquals(BigDecimal.valueOf(99), result.getRolledValue());
+ // Win chance for target 100 (roll under) is 99. Multiplier = (100-1)/99 = 1
+ assertEquals(diceDto.getBetAmount().stripTrailingZeros(), result.getPayout().stripTrailingZeros());
+ verify(balanceService, times(1)).subtractFunds(user, diceDto.getBetAmount());
+ verify(balanceService, times(1)).addFunds(eq(user), any(BigDecimal.class));
}
@Test
From 3e1c15e023aac8d8c97176bb5cb6618e5f3e4233 Mon Sep 17 00:00:00 2001
From: Phan Huy Tran
Date: Wed, 28 May 2025 13:01:18 +0200
Subject: [PATCH 03/11] test: adjust commments
---
.../de/szut/casino/dice/DiceServiceTest.java | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/backend/src/test/java/de/szut/casino/dice/DiceServiceTest.java b/backend/src/test/java/de/szut/casino/dice/DiceServiceTest.java
index ec5f979..6b2e230 100644
--- a/backend/src/test/java/de/szut/casino/dice/DiceServiceTest.java
+++ b/backend/src/test/java/de/szut/casino/dice/DiceServiceTest.java
@@ -105,7 +105,7 @@ class DiceServiceTest {
void play_rollOver_targetValueOne_rolledOne_lose() {
diceDto.setRollOver(true);
diceDto.setTargetValue(BigDecimal.valueOf(1));
- when(random.nextInt(anyInt())).thenReturn(0); // rolledValue = 1
+ when(random.nextInt(anyInt())).thenReturn(0);
DiceResult result = diceService.play(user, diceDto);
@@ -120,7 +120,7 @@ class DiceServiceTest {
void play_rollOver_targetValueOne_rolledTwo_win() {
diceDto.setRollOver(true);
diceDto.setTargetValue(BigDecimal.valueOf(1));
- when(random.nextInt(anyInt())).thenReturn(1); // rolledValue = 2
+ when(random.nextInt(anyInt())).thenReturn(1);
DiceResult result = diceService.play(user, diceDto);
@@ -136,7 +136,7 @@ class DiceServiceTest {
void play_rollUnder_targetValueOne_alwaysLose_winChanceZero() {
diceDto.setRollOver(false);
diceDto.setTargetValue(BigDecimal.valueOf(1));
- when(random.nextInt(anyInt())).thenReturn(0); // rolledValue = 1
+ when(random.nextInt(anyInt())).thenReturn(0);
DiceResult result = diceService.play(user, diceDto);
@@ -151,7 +151,7 @@ class DiceServiceTest {
void play_rollOver_targetValueNinetyNine_rolledHundred_win() {
diceDto.setRollOver(true);
diceDto.setTargetValue(BigDecimal.valueOf(99));
- when(random.nextInt(anyInt())).thenReturn(99); // rolledValue = 100
+ when(random.nextInt(anyInt())).thenReturn(99);
DiceResult result = diceService.play(user, diceDto);
@@ -167,7 +167,7 @@ class DiceServiceTest {
void play_rollUnder_targetValueNinetyNine_rolledNinetyEight_win() {
diceDto.setRollOver(false);
diceDto.setTargetValue(BigDecimal.valueOf(99));
- when(random.nextInt(anyInt())).thenReturn(97); // rolledValue = 98
+ when(random.nextInt(anyInt())).thenReturn(97);
DiceResult result = diceService.play(user, diceDto);
@@ -183,7 +183,7 @@ class DiceServiceTest {
void play_rollOver_targetValueOneHundred_alwaysLose_winChanceZero() {
diceDto.setRollOver(true);
diceDto.setTargetValue(BigDecimal.valueOf(100));
- when(random.nextInt(anyInt())).thenReturn(99); // rolledValue = 100
+ when(random.nextInt(anyInt())).thenReturn(99);
DiceResult result = diceService.play(user, diceDto);
@@ -196,9 +196,9 @@ class DiceServiceTest {
@Test
void play_rollUnder_targetValueOneHundred_rolledNinetyNine_win() {
- diceDto.setRollOver(false); // Assuming a typo in original, should be setRollOver(false)
+ diceDto.setRollOver(false);
diceDto.setTargetValue(BigDecimal.valueOf(100));
- when(random.nextInt(anyInt())).thenReturn(98); // rolledValue = 99
+ when(random.nextInt(anyInt())).thenReturn(98);
DiceResult result = diceService.play(user, diceDto);
From acd098225ca3203127a242a7d52c8eeebd56ee58 Mon Sep 17 00:00:00 2001
From: jank
Date: Sun, 1 Jun 2025 10:35:14 +0200
Subject: [PATCH 04/11] fix: Update claude to not decline pr's
---
.gitea/workflows/claude.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.gitea/workflows/claude.yml b/.gitea/workflows/claude.yml
index 72efd20..598b3b2 100644
--- a/.gitea/workflows/claude.yml
+++ b/.gitea/workflows/claude.yml
@@ -69,7 +69,7 @@ jobs:
Once you are done with your review, post your feedback as a reject or review on the pull request using the following exact format:
- tea \"\" ${PR_NUMBER} \"\"
+ tea \"\" ${PR_NUMBER} \"\"
Make sure the comment is clear, professional, and helpful. Only run the tea comment command once you're finished reviewing all changes. AND MOST IMPORTANDLY ONLY REVIEW THE DIFF FROM THE CURRENT STATE TO THE MAIN BRANCH TO GET THAT USE GIT DIFF
You may also use the tea cli to find out various things about the pull request
From 189fb859183df49d11ab0bce473cd19e67349368 Mon Sep 17 00:00:00 2001
From: jank
Date: Sun, 1 Jun 2025 10:35:14 +0200
Subject: [PATCH 05/11] fix: Update pipeline name
---
.gitea/workflows/claude.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.gitea/workflows/claude.yml b/.gitea/workflows/claude.yml
index 598b3b2..eacd10c 100644
--- a/.gitea/workflows/claude.yml
+++ b/.gitea/workflows/claude.yml
@@ -1,11 +1,11 @@
-name: Setup Gitea Tea CLI
+name: Claude PR Review
on:
pull_request:
types: [opened, synchronize] # Runs on new PRs and updates
jobs:
- setup-and-login:
+ claude-code:
runs-on: ubuntu-latest
steps:
- name: Checkout code
From c719e43ba00640982efc31d5f9fc24aa507b4713 Mon Sep 17 00:00:00 2001
From: jank
Date: Sun, 1 Jun 2025 11:22:10 +0200
Subject: [PATCH 06/11] chore: Claude can respond in pr's
---
.gitea/workflows/claude-comment.yml | 112 ++++++++++++++++++++++++++++
1 file changed, 112 insertions(+)
create mode 100644 .gitea/workflows/claude-comment.yml
diff --git a/.gitea/workflows/claude-comment.yml b/.gitea/workflows/claude-comment.yml
new file mode 100644
index 0000000..9f3ca9b
--- /dev/null
+++ b/.gitea/workflows/claude-comment.yml
@@ -0,0 +1,112 @@
+name: Claude Gitea PR Interaction via Comment
+
+on:
+ issue_comment:
+ types: [created]
+
+jobs:
+ claude-interact-on-comment:
+ runs-on: ubuntu-latest
+ if: |
+ github.event.issue.pull_request &&
+ contains(github.event.comment.body, '@Claude') &&
+ github.event.sender.type != 'Bot' # Prevents bot loops
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Required for git diff against main/master
+
+ - name: Set Tea Version
+ id: tea_version
+ run: echo "version=0.9.2" >> $GITHUB_OUTPUT # Check for the latest stable version
+
+ - name: Download Tea CLI
+ run: |
+ TEA_VERSION=$(echo "${{ steps.tea_version.outputs.version }}")
+ wget "https://gitea.com/gitea/tea/releases/download/v${TEA_VERSION}/tea-${TEA_VERSION}-linux-amd64" -O tea
+ chmod +x tea
+ sudo mv tea /usr/local/bin/tea
+
+ - name: Verify Tea Installation
+ run: tea --version
+
+ - name: Add Gitea Login
+ env:
+ GITEA_URL: ${{ secrets._GITEA_URL }}
+ GITEA_TOKEN: ${{ secrets._GITEA_TOKEN }}
+ run: |
+ if [ -z "$GITEA_URL" ]; then
+ echo "Error: GITEA_URL secret is not set."
+ exit 1
+ fi
+ if [ -z "$GITEA_TOKEN" ]; then
+ echo "Error: GITEA_TOKEN secret is not set."
+ exit 1
+ fi
+ INSECURE_FLAG=""
+ if [[ "${GITEA_URL}" == http://* ]]; then
+ INSECURE_FLAG="--insecure"
+ fi
+ tea login add --name mygitea --url "$GITEA_URL" --token "$GITEA_TOKEN" $INSECURE_FLAG
+
+ - name: Install bun
+ uses: oven-sh/setup-bun@v2
+
+ - name: Install claude-code
+ run: bun i -g @anthropic-ai/claude-code
+
+ - name: Claude Process PR Comment
+ env:
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ PR_NUMBER: ${{ github.event.issue.number }}
+ COMMENT_BODY: ${{ github.event.comment.body }}
+ COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
+ GITEA_URL: ${{ secrets._GITEA_URL }}
+ run: |
+ claude --allowedTools "Bash(tea:*)" --allowedTools "Bash(git:*)" --allowedTools "Read" --allowedTools "Grep" --allowedTools "WebFetch" --allowedTools "Glob" --allowedTools "LS" -p "You are an AI assistant integrated with Gitea (at ${GITEA_URL}) via the 'tea' CLI.
+ You have been invoked because user '${COMMENT_AUTHOR}' left the following comment on Pull Request #${PR_NUMBER}:
+ ---
+ ${COMMENT_BODY}
+ ---
+
+ Your primary task is to:
+ 1. Carefully understand the user's request within their comment.
+ 2. Use the 'tea' CLI to perform the requested action(s) on Pull Request #${PR_NUMBER}.
+ 3. If the request is to review the PR, fetch the diff against the PR's base branch (e.g., 'git fetch origin main && git diff origin/main...HEAD' or similar; adapt branch name if necessary, or use 'tea pr diff ${PR_NUMBER}') and provide constructive feedback.
+ 4. For other actions, translate the user's intent into the appropriate 'tea' command.
+
+ **How to Post Reviews and Other Feedback:**
+ - When you provide a review, post it as a comment using:
+ \`tea pr comment ${PR_NUMBER} -m \"Claude's Review:\n[Your detailed review, mentioning files and line numbers.]\"\`
+ - For other informational responses or clarifications, also use \`tea pr comment ...\`.
+
+ **Critical: Handling Approval, Rejection, or Merge Requests:**
+ Pull Request approval, rejection, and merging are critical actions and should not be used to 'cheat' the review process. You cannot verify Gitea user permissions.
+ - If a user comments asking you to directly approve (e.g., '@claude approve this'), merge, or reject a PR:
+ 1. **Do NOT blindly execute these commands.**
+ 2. **Approval/Merge:**
+ - State in a comment (using \`tea pr comment ...\`) that direct approval/merge requests via you are typically for convenience after a proper human review process has been implicitly completed or if the requester is a designated maintainer explicitly overriding.
+ - If the PR has not been reviewed by you yet, and the comment implies a review is also needed, perform the review FIRST and post it.
+ - You should only consider proceeding with a \`tea pr approve ${PR_NUMBER}\` or \`tea pr merge ${PR_NUMBER}\` command if:
+ a. The comment explicitly states that all necessary human reviews are complete and this is just a formal step by a trusted user.
+ b. OR, your own comprehensive review found no critical issues and the request seems appropriate in context.
+ - If in doubt, default to posting your review (if applicable) and stating that a maintainer should perform the final approval/merge. Your goal is to assist, not to bypass established review procedures.
+ 3. **Rejection/Requesting Changes:**
+ - If asked to reject or request changes, you should typically base this on your own review of the PR's changes.
+ - First, perform a review if you haven't already.
+ - Then, you can use \`tea pr reject ${PR_NUMBER} -m \"Claude's Review Summary: [summary of reasons for rejection/changes based on your review]\"\`. Ensure your detailed review is also available as a comment.
+
+ Examples of interpreting comments and acting (keeping the above critical guidelines in mind):
+ - User: '@claude LGTM, approve this' -> You: First, consider if a review is implied or done. If so, and you agree, you might approve. If not, you might say, 'I can approve this if the standard review process is complete. Have maintainers reviewed this?' or perform your own review.
+ - User: '@claude please review this PR' -> You: Get diffs, review, then \`tea pr comment ${PR_NUMBER} -m \"Claude's Review: ...\"\`.
+ - User: '@claude close this PR' -> You: \`tea pr close ${PR_NUMBER}\` (Closing is generally less sensitive than approving/merging).
+ - User: '@claude add label enhancement' -> You: \`tea pr label ${PR_NUMBER} --add enhancement\`
+
+ IMPORTANT GUIDELINES:
+ - You MUST use the 'tea' CLI for all Gitea interactions.
+ - For PR reviews, ALWAYS analyze the diff. Make sure to mention specific files and line numbers.
+ - Be precise with 'tea' commands. If ambiguous, ask for clarification using \`tea pr comment ...\`.
+ - Execute only necessary 'tea' command(s).
+ - Ensure reviews are professional, constructive, and helpful.
+ "
From 84a4ede0265b0c38953fae7886fe592de361fa10 Mon Sep 17 00:00:00 2001
From: jank
Date: Sun, 1 Jun 2025 12:16:44 +0200
Subject: [PATCH 07/11] fix: Fix claude pipeline
---
.gitea/workflows/claude-comment.yml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.gitea/workflows/claude-comment.yml b/.gitea/workflows/claude-comment.yml
index 9f3ca9b..e650ae4 100644
--- a/.gitea/workflows/claude-comment.yml
+++ b/.gitea/workflows/claude-comment.yml
@@ -9,8 +9,7 @@ jobs:
runs-on: ubuntu-latest
if: |
github.event.issue.pull_request &&
- contains(github.event.comment.body, '@Claude') &&
- github.event.sender.type != 'Bot' # Prevents bot loops
+ contains(github.event.comment.body, '@Claude')
steps:
- name: Check out code
uses: actions/checkout@v4
From 5d5c27827b480f7a7ecc1a34c8dbfb56b4195eef Mon Sep 17 00:00:00 2001
From: jank
Date: Sun, 1 Jun 2025 12:24:42 +0200
Subject: [PATCH 08/11] fix: Fix claude to always use tea command to review
---
.gitea/workflows/claude-comment.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.gitea/workflows/claude-comment.yml b/.gitea/workflows/claude-comment.yml
index e650ae4..a3ca00d 100644
--- a/.gitea/workflows/claude-comment.yml
+++ b/.gitea/workflows/claude-comment.yml
@@ -104,8 +104,8 @@ jobs:
IMPORTANT GUIDELINES:
- You MUST use the 'tea' CLI for all Gitea interactions.
+ - You MUST use the tea cli to comment on the PR so the user gets a response.
- For PR reviews, ALWAYS analyze the diff. Make sure to mention specific files and line numbers.
- Be precise with 'tea' commands. If ambiguous, ask for clarification using \`tea pr comment ...\`.
- - Execute only necessary 'tea' command(s).
- Ensure reviews are professional, constructive, and helpful.
"
From 262c814df0ff26c6ed515c99f1b7a517d090c91b Mon Sep 17 00:00:00 2001
From: jank
Date: Sun, 1 Jun 2025 13:37:49 +0200
Subject: [PATCH 09/11] fix: Force claude to use tea cli
---
.gitea/workflows/claude-comment.yml | 35 ++++++++++++++++++++---------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/.gitea/workflows/claude-comment.yml b/.gitea/workflows/claude-comment.yml
index a3ca00d..01312d2 100644
--- a/.gitea/workflows/claude-comment.yml
+++ b/.gitea/workflows/claude-comment.yml
@@ -96,16 +96,29 @@ jobs:
- First, perform a review if you haven't already.
- Then, you can use \`tea pr reject ${PR_NUMBER} -m \"Claude's Review Summary: [summary of reasons for rejection/changes based on your review]\"\`. Ensure your detailed review is also available as a comment.
- Examples of interpreting comments and acting (keeping the above critical guidelines in mind):
- - User: '@claude LGTM, approve this' -> You: First, consider if a review is implied or done. If so, and you agree, you might approve. If not, you might say, 'I can approve this if the standard review process is complete. Have maintainers reviewed this?' or perform your own review.
- - User: '@claude please review this PR' -> You: Get diffs, review, then \`tea pr comment ${PR_NUMBER} -m \"Claude's Review: ...\"\`.
- - User: '@claude close this PR' -> You: \`tea pr close ${PR_NUMBER}\` (Closing is generally less sensitive than approving/merging).
- - User: '@claude add label enhancement' -> You: \`tea pr label ${PR_NUMBER} --add enhancement\`
+ Examples of interpreting comments and generating appropriate \`tea\` commands (keeping the above critical guidelines in mind):
+ - User: '@claude LGTM, approve this' -> You: First, consider if a review is implied or done. If so, and you agree, you might generate \`tea pr approve ${PR_NUMBER}\`. If not, you might generate \`tea pr comment ${PR_NUMBER} -m \"Claude: I can approve this if the standard review process is complete. Have maintainers reviewed this?\"\` or perform your own review and then comment.
+ - User: '@claude please review this PR' -> You: Get diffs, review, then generate \`tea pr comment ${PR_NUMBER} -m \"Claude's Review: ...\"\`.
+ - User: '@claude close this PR' -> You: Generate \`tea pr close ${PR_NUMBER}\` and optionally \`tea pr comment ${PR_NUMBER} -m \"Claude: PR #${PR_NUMBER} has been closed as requested.\"\`.
+ - User: '@claude add label enhancement' -> You: Generate \`tea pr label ${PR_NUMBER} --add enhancement\` and \`tea pr comment ${PR_NUMBER} -m \"Claude: Label 'enhancement' added to PR #${PR_NUMBER}.\"\`
+ - User: '@claude what are the labels on this PR?' -> You: Generate \`tea pr label ${PR_NUMBER} --list\` (this command outputs to stdout, which is fine for your internal use). Then, to inform the user, you generate: \`tea pr comment ${PR_NUMBER} -m \"Claude: The current labels are: [output from tea pr label --list].\"\` (You'll need to capture the output of the first command to formulate the second if the tool allows such chaining, otherwise, focus on commands that directly achieve the user's goal or report information). *Self-correction: The Bash tool can capture output. So, if you need to run a \`tea\` command to get information for yourself, do so, then use that information to formulate your \`tea pr comment ...\` to the user.*
- IMPORTANT GUIDELINES:
- - You MUST use the 'tea' CLI for all Gitea interactions.
- - You MUST use the tea cli to comment on the PR so the user gets a response.
- - For PR reviews, ALWAYS analyze the diff. Make sure to mention specific files and line numbers.
- - Be precise with 'tea' commands. If ambiguous, ask for clarification using \`tea pr comment ...\`.
- - Ensure reviews are professional, constructive, and helpful.
+ **IMPORTANT GUIDELINES FOR YOUR OPERATION AND RESPONSE GENERATION:**
+ - **Your SOLE METHOD of communicating back to the user on Gitea is by generating a \`tea pr comment ${PR_NUMBER} -m \"...\"\` command.** This is non-negotiable. Do not output plain text messages intended for the user. Your response *is* the command.
+ - **Use the 'tea' CLI for ALL Gitea interactions.** This includes fetching PR details, diffs, labels, status, and posting comments, reviews, approvals, etc.
+ - **For PR reviews, ALWAYS analyze the diff.** Use \`tea pr diff ${PR_NUMBER}\` or git commands to get the diff. Make sure to mention specific files and line numbers in your review comment.
+ - **Be precise with 'tea' commands.** If a user's request is ambiguous, DO NOT GUESS. Instead, generate a \`tea pr comment ${PR_NUMBER} -m \"Claude Asks: [Your clarifying question]\"\` command to ask for more details.
+ - **Execute only necessary 'tea' command(s).** If a user asks for a review, your primary output should be the \`tea pr comment ...\` command containing the review. If they ask to add a label, your output should be \`tea pr label ...\` and then a confirmation \`tea pr comment ...\`.
+ - **Ensure reviews are professional, constructive, and helpful.**
+ - **If you need to perform an action AND then report on it, generate both \`tea\` commands sequentially.** For example, to add a label and confirm:
+ \`tea pr label ${PR_NUMBER} --add bug\`
+ \`tea pr comment ${PR_NUMBER} -m "Claude: I've added the 'bug' label."\`
+ The GitHub Actions workflow will execute these commands one after another.
+ - **If a user's request cannot be fulfilled using the 'tea' CLI or the allowed tools, explain this limitation by generating a \`tea pr comment ...\` command.** For example: \`tea pr comment ${PR_NUMBER} -m "Claude: I cannot perform that action as it's outside my current capabilities or allowed tools."\`
+ - **Think step-by-step.** 1. Understand request. 2. Identify necessary `tea` command(s). 3. If it's a review, get the diff. 4. Formulate the `tea` command(s) as your direct output.
+
+ **Final Check before outputting:**
+ "Is my entire response that's intended for the Gitea user wrapped in a \`tea pr comment ${PR_NUMBER} -m '...' \` command (or another appropriate \`tea\` command if it's an action like \`tea pr label ...\`)? If not, I must fix it."
+
+ You are now ready to process the comment. Remember, your output will be executed in a shell. Generate only the \`tea\` command(s) needed.
"
From ffea4c0ec3e256677ead02835a5a6c9b804c1a99 Mon Sep 17 00:00:00 2001
From: jank
Date: Sun, 1 Jun 2025 13:43:36 +0200
Subject: [PATCH 10/11] fix: Remove -m
---
.gitea/workflows/claude-comment.yml | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/.gitea/workflows/claude-comment.yml b/.gitea/workflows/claude-comment.yml
index 01312d2..e13a540 100644
--- a/.gitea/workflows/claude-comment.yml
+++ b/.gitea/workflows/claude-comment.yml
@@ -77,7 +77,7 @@ jobs:
**How to Post Reviews and Other Feedback:**
- When you provide a review, post it as a comment using:
- \`tea pr comment ${PR_NUMBER} -m \"Claude's Review:\n[Your detailed review, mentioning files and line numbers.]\"\`
+ \`tea pr comment ${PR_NUMBER} \"Claude's Review:\n[Your detailed review, mentioning files and line numbers.]\"\`
- For other informational responses or clarifications, also use \`tea pr comment ...\`.
**Critical: Handling Approval, Rejection, or Merge Requests:**
@@ -94,31 +94,31 @@ jobs:
3. **Rejection/Requesting Changes:**
- If asked to reject or request changes, you should typically base this on your own review of the PR's changes.
- First, perform a review if you haven't already.
- - Then, you can use \`tea pr reject ${PR_NUMBER} -m \"Claude's Review Summary: [summary of reasons for rejection/changes based on your review]\"\`. Ensure your detailed review is also available as a comment.
+ - Then, you can use \`tea pr reject ${PR_NUMBER} \"Claude's Review Summary: [summary of reasons for rejection/changes based on your review]\"\`. Ensure your detailed review is also available as a comment.
Examples of interpreting comments and generating appropriate \`tea\` commands (keeping the above critical guidelines in mind):
- - User: '@claude LGTM, approve this' -> You: First, consider if a review is implied or done. If so, and you agree, you might generate \`tea pr approve ${PR_NUMBER}\`. If not, you might generate \`tea pr comment ${PR_NUMBER} -m \"Claude: I can approve this if the standard review process is complete. Have maintainers reviewed this?\"\` or perform your own review and then comment.
- - User: '@claude please review this PR' -> You: Get diffs, review, then generate \`tea pr comment ${PR_NUMBER} -m \"Claude's Review: ...\"\`.
- - User: '@claude close this PR' -> You: Generate \`tea pr close ${PR_NUMBER}\` and optionally \`tea pr comment ${PR_NUMBER} -m \"Claude: PR #${PR_NUMBER} has been closed as requested.\"\`.
- - User: '@claude add label enhancement' -> You: Generate \`tea pr label ${PR_NUMBER} --add enhancement\` and \`tea pr comment ${PR_NUMBER} -m \"Claude: Label 'enhancement' added to PR #${PR_NUMBER}.\"\`
- - User: '@claude what are the labels on this PR?' -> You: Generate \`tea pr label ${PR_NUMBER} --list\` (this command outputs to stdout, which is fine for your internal use). Then, to inform the user, you generate: \`tea pr comment ${PR_NUMBER} -m \"Claude: The current labels are: [output from tea pr label --list].\"\` (You'll need to capture the output of the first command to formulate the second if the tool allows such chaining, otherwise, focus on commands that directly achieve the user's goal or report information). *Self-correction: The Bash tool can capture output. So, if you need to run a \`tea\` command to get information for yourself, do so, then use that information to formulate your \`tea pr comment ...\` to the user.*
+ - User: '@claude LGTM, approve this' -> You: First, consider if a review is implied or done. If so, and you agree, you might generate \`tea pr approve ${PR_NUMBER}\`. If not, you might generate \`tea pr comment ${PR_NUMBER} \"Claude: I can approve this if the standard review process is complete. Have maintainers reviewed this?\"\` or perform your own review and then comment.
+ - User: '@claude please review this PR' -> You: Get diffs, review, then generate \`tea pr comment ${PR_NUMBER} \"Claude's Review: ...\"\`.
+ - User: '@claude close this PR' -> You: Generate \`tea pr close ${PR_NUMBER}\` and optionally \`tea pr comment ${PR_NUMBER} \"Claude: PR #${PR_NUMBER} has been closed as requested.\"\`.
+ - User: '@claude add label enhancement' -> You: Generate \`tea pr label ${PR_NUMBER} --add enhancement\` and \`tea pr comment ${PR_NUMBER} \"Claude: Label 'enhancement' added to PR #${PR_NUMBER}.\"\`
+ - User: '@claude what are the labels on this PR?' -> You: Generate \`tea pr label ${PR_NUMBER} --list\` (this command outputs to stdout, which is fine for your internal use). Then, to inform the user, you generate: \`tea pr comment ${PR_NUMBER} \"Claude: The current labels are: [output from tea pr label --list].\"\` (You'll need to capture the output of the first command to formulate the second if the tool allows such chaining, otherwise, focus on commands that directly achieve the user's goal or report information). *Self-correction: The Bash tool can capture output. So, if you need to run a \`tea\` command to get information for yourself, do so, then use that information to formulate your \`tea pr comment ...\` to the user.*
**IMPORTANT GUIDELINES FOR YOUR OPERATION AND RESPONSE GENERATION:**
- - **Your SOLE METHOD of communicating back to the user on Gitea is by generating a \`tea pr comment ${PR_NUMBER} -m \"...\"\` command.** This is non-negotiable. Do not output plain text messages intended for the user. Your response *is* the command.
+ - **Your SOLE METHOD of communicating back to the user on Gitea is by generating a \`tea pr comment ${PR_NUMBER} \"...\"\` command.** This is non-negotiable. Do not output plain text messages intended for the user. Your response *is* the command.
- **Use the 'tea' CLI for ALL Gitea interactions.** This includes fetching PR details, diffs, labels, status, and posting comments, reviews, approvals, etc.
- **For PR reviews, ALWAYS analyze the diff.** Use \`tea pr diff ${PR_NUMBER}\` or git commands to get the diff. Make sure to mention specific files and line numbers in your review comment.
- - **Be precise with 'tea' commands.** If a user's request is ambiguous, DO NOT GUESS. Instead, generate a \`tea pr comment ${PR_NUMBER} -m \"Claude Asks: [Your clarifying question]\"\` command to ask for more details.
+ - **Be precise with 'tea' commands.** If a user's request is ambiguous, DO NOT GUESS. Instead, generate a \`tea pr comment ${PR_NUMBER} \"Claude Asks: [Your clarifying question]\"\` command to ask for more details.
- **Execute only necessary 'tea' command(s).** If a user asks for a review, your primary output should be the \`tea pr comment ...\` command containing the review. If they ask to add a label, your output should be \`tea pr label ...\` and then a confirmation \`tea pr comment ...\`.
- **Ensure reviews are professional, constructive, and helpful.**
- **If you need to perform an action AND then report on it, generate both \`tea\` commands sequentially.** For example, to add a label and confirm:
\`tea pr label ${PR_NUMBER} --add bug\`
- \`tea pr comment ${PR_NUMBER} -m "Claude: I've added the 'bug' label."\`
+ \`tea pr comment ${PR_NUMBER} "Claude: I've added the 'bug' label."\`
The GitHub Actions workflow will execute these commands one after another.
- - **If a user's request cannot be fulfilled using the 'tea' CLI or the allowed tools, explain this limitation by generating a \`tea pr comment ...\` command.** For example: \`tea pr comment ${PR_NUMBER} -m "Claude: I cannot perform that action as it's outside my current capabilities or allowed tools."\`
+ - **If a user's request cannot be fulfilled using the 'tea' CLI or the allowed tools, explain this limitation by generating a \`tea pr comment ...\` command.** For example: \`tea pr comment ${PR_NUMBER} "Claude: I cannot perform that action as it's outside my current capabilities or allowed tools."\`
- **Think step-by-step.** 1. Understand request. 2. Identify necessary `tea` command(s). 3. If it's a review, get the diff. 4. Formulate the `tea` command(s) as your direct output.
**Final Check before outputting:**
- "Is my entire response that's intended for the Gitea user wrapped in a \`tea pr comment ${PR_NUMBER} -m '...' \` command (or another appropriate \`tea\` command if it's an action like \`tea pr label ...\`)? If not, I must fix it."
+ "Is my entire response that's intended for the Gitea user wrapped in a \`tea pr comment ${PR_NUMBER} '...' \` command (or another appropriate \`tea\` command if it's an action like \`tea pr label ...\`)? If not, I must fix it."
You are now ready to process the comment. Remember, your output will be executed in a shell. Generate only the \`tea\` command(s) needed.
"
From bd1d8f83393c8c0f36c068be344ca6d56fd0e07b Mon Sep 17 00:00:00 2001
From: Jan K9f
Date: Mon, 2 Jun 2025 14:04:15 +0200
Subject: [PATCH 11/11] chore: Add stale pipeline
---
.gitea/workflows/stale.yml | 14 ++++++++++++++
1 file changed, 14 insertions(+)
create mode 100644 .gitea/workflows/stale.yml
diff --git a/.gitea/workflows/stale.yml b/.gitea/workflows/stale.yml
new file mode 100644
index 0000000..773eac4
--- /dev/null
+++ b/.gitea/workflows/stale.yml
@@ -0,0 +1,14 @@
+name: "Close stale issues and PRs"
+on:
+ schedule:
+ - cron: "30 1 * * *"
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v9
+ with:
+ stale-pr-message: "Will be closed in x days bc yo mom is a bitch. im not telling you when it will be closed fuckface"
+ days-before-pr-stale: 2
+ days-before-pr-close: 3