27 Commits
v1.0.1 ... main

Author SHA1 Message Date
2b860fbc27 Add: clean-destination input to optionally clear destination folder during IIS deployment
- Introduces a new input to allow cleanup of destination folder while retaining specific files and folders (`appsettings.json`, `web.config`, and `store`).
- Updates error handling for IIS site and app pool stop commands to continue execution on failure.
2026-06-07 15:06:05 +02:00
55b2e0f545 Fix: corretti caratteri \n letterali nei commenti di triage
- Esempio JSON nel prompt: corretto da \\n a \n (doppio escape ingannava il LLM)
- Aggiunto safety net con printf '%b' su COMMENT e MD_CONTENT dopo jq
- LINK_MD: sostituiti doppi apici con printf per interpretare correttamente i newline
2026-05-31 20:23:32 +02:00
adbbaf54a4 fix: converte \n letterali in newline reali nei commenti (#25)
## Bug
I commenti mostravano \n letterali invece di newline.

## Cause
1. LINK_MD in doppi apici bash: \n e' letterale, non newline
2. AI puo' produrre \n doppiamente escapat. nel JSON

## Fix
- LINK_MD: newline reali invece di \n letterali
- COMMENT/MD_CONTENT: sed per convertire \n in newline

Per commenti vecchi non retroattivo.

Co-authored-by: codex <codex@incloud.ovh>
Reviewed-on: #25
Co-authored-by: codex <codex@noreply.localhost>
Co-committed-by: codex <codex@noreply.localhost>
2026-05-31 11:54:03 +00:00
a991512d79 Sostituisce utente anuti con codex, aggiorna token 2026-05-31 01:08:56 +02:00
3b21441524 Riattiva anti-loop @codex 2026-05-31 00:51:22 +02:00
ec5cd5712e temp disable antiloop for final test 2026-05-31 00:48:00 +02:00
ea182577d7 Fix model name: opencode-go/deepseek-v4-flash, ripristina anti-loop 2026-05-31 00:47:49 +02:00
37d938f5f0 Temporaneamente disabilita anti-loop per test @codex 2026-05-31 00:43:53 +02:00
929cf8419e Aggiunge action codex-reply e workflow @codex 2026-05-31 00:42:55 +02:00
87ee3d4c0f Aggiunge guida setup triage per repository 2026-05-31 00:28:27 +02:00
e6c26f9759 Pulizia: ignora .ai, rimuove log di test 2026-05-31 00:17:29 +02:00
b0957a39c2 Aggiorna README con le nuove action opencode 2026-05-31 00:15:13 +02:00
863746b65d Imposta default model deepseek/deepseek-v4-flash 2026-05-31 00:14:07 +02:00
2c09b5c349 Fix: usa browser_download_url per asset upload 2026-05-30 23:57:54 +02:00
749faf7c46 Fix: quoting colon in triage-issue description 2026-05-30 23:53:52 +02:00
12df1398a2 Enhance triage: analisi codice, gap analysis, report MD come asset 2026-05-30 23:50:16 +02:00
e50b1d8f89 Fix: usa printf invece di heredoc, estrae JSON con grep -E 2026-05-30 23:32:05 +02:00
370c2967f0 Fix: installa curl se assente, sostituisce grep -P con jq per estrazione JSON 2026-05-30 23:28:08 +02:00
4ca5287d67 Fix: installa jq se assente sul runner 2026-05-30 23:24:36 +02:00
d35a5cc717 Fix: indenta contenuto heredoc in triage-issue 2026-05-30 23:22:21 +02:00
47eb32cd7a Fix: installa npm se assente, rimuove validate.yml 2026-05-30 23:20:11 +02:00
28971b4b3b Corregge YAML: quoting description con due punti
Some checks failed
Validate Actions / lint (push) Has been cancelled
2026-05-30 23:15:20 +02:00
7c6196dbb5 Aggiorna nomi secrets nel workflow triage
Some checks failed
Validate Actions / lint (push) Has been cancelled
2026-05-30 23:11:03 +02:00
3de900eeb5 Corregge runs-on: linux_amd64
Some checks failed
Validate Actions / lint (push) Has been cancelled
2026-05-30 23:05:53 +02:00
0147569aa9 Corregge runs-on: gitea-runner
Some checks failed
Validate Actions / lint (push) Has been cancelled
2026-05-30 22:47:21 +02:00
3bc16776cc Aggiunge action triage-issue e workflow di test (#2)
Co-authored-by: alberto <a.nuti@live.it>
Co-committed-by: alberto <a.nuti@live.it>
2026-05-30 20:43:23 +00:00
2603f780e6 Aggiunge azioni opencode-prompt e install-opencode (#1)
Aggiunge due nuove action composite:

### `install-opencode`
Verifica i prerequisiti (node/npm) e installa opencode-ai globalmente.
Input opzionale `version` per fissare una versione specifica.

### `opencode-prompt`
Esegue opencode con un prompt sul codice del repository.
- Input: `prompt` (req), `api-key` (req), `api-provider`, `model`, `agent`, `working-directory`
- Output: `result` con la risposta di opencode
- Salva copia in `opencode-output.txt` nel workspace

Reviewed-on: #1
Co-authored-by: alberto <a.nuti@live.it>
Co-committed-by: alberto <a.nuti@live.it>
2026-05-30 20:26:00 +00:00
11 changed files with 679 additions and 38 deletions

View File

@@ -0,0 +1,27 @@
name: Codex Reply
on:
issue_comment:
types: [created]
jobs:
codex:
runs-on: linux_amd64
if: |
contains(github.event.comment.body, '@codex') &&
github.event.comment.user.login != 'codex'
steps:
- uses: actions/checkout@v4
- uses: ./install-opencode
- uses: ./codex-reply
with:
issue-number: ${{ github.event.issue.number }}
repository: ${{ github.repository }}
comment-body: ${{ github.event.comment.body }}
comment-author: ${{ github.event.comment.user.login }}
issue-title: ${{ github.event.issue.title }}
issue-body: ${{ github.event.issue.body }}
api-token: ${{ secrets.TOKEN }}
api-key: ${{ secrets.OPENCODE_API_KEY }}

View File

@@ -0,0 +1,22 @@
name: Triage Issue
on:
issues:
types: [opened]
jobs:
triage:
runs-on: linux_amd64
steps:
- uses: actions/checkout@v4
- uses: ./install-opencode
- uses: ./triage-issue
with:
issue-title: ${{ github.event.issue.title }}
issue-body: ${{ github.event.issue.body }}
issue-number: ${{ github.event.issue.number }}
repository: ${{ github.repository }}
api-token: ${{ secrets.TOKEN }}
api-key: ${{ secrets.OPENCODE_API_KEY }}

View File

@@ -1,34 +0,0 @@
name: Validate Actions
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Valida YAML
run: |
for f in */action.yml; do
echo "Validazione: $f"
python3 -c "import yaml; yaml.safe_load(open('$f')); print('OK')"
done
- name: Verifica struttura action
run: |
required_fields=("name" "runs")
for f in */action.yml; do
for field in "${required_fields[@]}"; do
if ! grep -q "^$field:" "$f"; then
echo "ERRORE: $f manca del campo '$field'"
exit 1
fi
done
done
echo "Struttura azioni valida"

1
.gitignore vendored
View File

@@ -1 +1,2 @@
.idea/
.ai/

View File

@@ -5,6 +5,94 @@ utilizzabili da altri repository del workspace.
## Action disponibili
### `install-opencode`
Installa [OpenCode](https://opencode.ai) globalmente via npm. Verifica la presenza di Node.js e npm,
installando npm automaticamente su Alpine se mancante.
```yaml
- uses: https://<host>/<owner>/Actions/install-opencode@<ref>
# with:
# version: "1.15.0" # fissa una versione specifica
```
### `opencode-prompt`
Esegue un prompt OpenCode sul codice del repository. Richiede che `install-opencode`
sia eseguito prima, oppure che OpenCode sia già presente sul runner.
```yaml
- id: ai
uses: https://<host>/<owner>/Actions/opencode-prompt@<ref>
with:
prompt: "Analizza il codice e trova potenziali bug"
api-key: ${{ secrets.OPENCODE_API_KEY }}
# opzionali:
model: "deepseek/deepseek-v4-flash" # default
agent: "" # agente opencode
working-directory: "" # default: radice repo
```
L'output `result` contiene la risposta di OpenCode e viene salvato anche in
`$GITHUB_WORKSPACE/opencode-output.txt`.
### `triage-issue`
Analizza automaticamente le issue in apertura con OpenCode: classifica come **bug** o
**richiesta**, produce un riassunto, una gap analisi rispetto al codice sorgente, domande
aperte e un report tecnico dettagliato in formato Markdown caricato come asset.
#### Utilizzo
```yaml
- uses: https://<host>/<owner>/Actions/triage-issue@<ref>
with:
issue-title: ${{ github.event.issue.title }}
issue-body: ${{ github.event.issue.body }}
issue-number: ${{ github.event.issue.number }}
repository: ${{ github.repository }}
api-token: ${{ secrets.TOKEN }}
api-key: ${{ secrets.OPENCODE_API_KEY }}
# opzionali:
model: "deepseek/deepseek-v4-flash" # default
gitea-host: "https://git.incloud.ovh" # default
```
#### Prerequisiti nel repository target
1. **Secrets**: `TOKEN` (API Gitea), `OPENCODE_API_KEY` (API AI)
2. **Label**: `bug` (rosso `dc3545`) e `richiesta` (blu `007bff`)
3. **Workflow** (esempio `.gitea/workflows/triage.yml`):
```yaml
name: Triage Issue
on:
issues:
types: [opened]
jobs:
triage:
runs-on: linux_amd64
steps:
- uses: actions/checkout@v4
- uses: https://git.incloud.ovh/anuti/Actions/install-opencode@main
- uses: https://git.incloud.ovh/anuti/Actions/triage-issue@main
with:
issue-title: ${{ github.event.issue.title }}
issue-body: ${{ github.event.issue.body }}
issue-number: ${{ github.event.issue.number }}
repository: ${{ github.repository }}
api-token: ${{ secrets.TOKEN }}
api-key: ${{ secrets.OPENCODE_API_KEY }}
```
#### Output
- **Label** `bug` o `richiesta` applicata sull'issue
- **Commento** con riassunto, gap analisi e domande aperte
- **Asset Markdown** (`triage-issue-N.md`) con analisi tecnica completa, linkato nel commento
---
### `version-from-tag`
Estrae la versione da un tag (formato `v1.2.3.4[-suffix]`) e produce le variabili

90
SETUP_TRIAGE.md Normal file
View File

@@ -0,0 +1,90 @@
# Setup triage automatico su un repository
## Prerequisiti
- Gitea Actions abilitate sul repository
- Issues abilitate sul repository
- Runner disponibile con label `linux_amd64` (o label equivalente)
- Accesso admin al repository per creare secrets e labels
## Passaggi
### 1. Secrets
Crea i seguenti secrets nel repository (`Settings → Actions → Secrets`):
| Secret | Valore | Descrizione |
|---|---|---|
| `TOKEN` | Token API Gitea | Permette all'action di applicare label e commentare |
| `OPENCODE_API_KEY` | API key OpenCode | Autenticazione per il provider AI |
### 2. Labels
Crea le label `bug` e `richiesta` (o verifica che esistano):
```
bug #ee0701 Malfunzionamento, errore, crash o anomalia
richiesta #007bff Nuova funzionalità, miglioramento o ottimizzazione
```
Se il repo ha già label simili con nomi diversi, unificarle sulla versione minuscola per evitare ambiguità col triage.
### 3. Workflow
Crea il file `.gitea/workflows/triage-issue.yml`:
```yaml
name: Triage Issue
on:
issues:
types: [opened]
jobs:
triage:
runs-on: linux_amd64
steps:
- uses: actions/checkout@v4
- uses: https://git.incloud.ovh/anuti/Actions/install-opencode@main
- uses: https://git.incloud.ovh/anuti/Actions/triage-issue@main
with:
issue-title: ${{ github.event.issue.title }}
issue-body: ${{ github.event.issue.body }}
issue-number: ${{ github.event.issue.number }}
repository: ${{ github.repository }}
api-token: ${{ secrets.TOKEN }}
api-key: ${{ secrets.OPENCODE_API_KEY }}
```
Se il runner ha una label diversa da `linux_amd64`, modificare `runs-on` di conseguenza.
### 4. Commit e push
```bash
git add .gitea/workflows/triage-issue.yml
git commit -m "Aggiunge workflow triage automatico"
git push
```
### 5. Test
Aprire una nuova issue sul repository. Entro pochi secondi il runner dovrebbe:
1. Classificare l'issue come `bug` o `richiesta`
2. Applicare la label corrispondente
3. Pubblicare un commento con riassunto, gap analisi e domande aperte
4. Caricare un file `.md` con l'analisi tecnica completa come asset dell'issue
### Troubleshooting
**Il workflow non parte**
- Verificare che `has_actions: true` sul repository (API: `PATCH /repos/{owner}/{repo}` con body `{"has_actions":true}`)
- Verificare che il runner sia online e abbia la label corretta
**Errore "secret not found"**
- Verificare che i secrets `TOKEN` e `OPENCODE_API_KEY` siano configurati
**Label non trovata**
- Il triage cerca label con nome esattamente `bug` e `richiesta`. Crearle se mancanti.

136
codex-reply/action.yml Normal file
View File

@@ -0,0 +1,136 @@
name: Codex Reply
description: "Risponde a un commento @codex analizzando issue, cronologia e codice repository."
inputs:
issue-number:
description: Numero dell'issue.
required: true
repository:
description: Repository in formato owner/repo.
required: true
comment-body:
description: Testo del commento che contiene @codex.
required: true
comment-author:
description: Login dell'autore del commento.
required: true
issue-title:
description: Titolo dell'issue.
required: true
issue-body:
description: Corpo dell'issue.
required: true
api-token:
description: Token API Gitea.
required: true
gitea-host:
description: URL del server Gitea.
required: false
default: "https://git.incloud.ovh"
api-key:
description: API key per opencode.
required: true
model:
description: Modello AI in formato provider/model.
required: false
default: "opencode-go/deepseek-v4-flash"
runs:
using: composite
steps:
- name: Configura autenticazione opencode
shell: bash
run: |
set -euo pipefail
if ! command -v jq &> /dev/null; then
echo "jq non trovato, tentativo di installazione..."
if command -v apt-get &> /dev/null; then
apt-get update -qq && apt-get install -y -qq jq 2>&1 || true
elif command -v apk &> /dev/null; then
apk add --no-cache jq 2>&1 || true
fi
command -v jq &> /dev/null || { echo "ERRORE: impossibile installare jq."; exit 1; }
fi
mkdir -p ~/.local/share/opencode
jq -n \
--arg provider "opencode-go" \
--arg key "${{ inputs.api-key }}" \
'{($provider): {type: "api", key: $key}}' > ~/.local/share/opencode/auth.json
chmod 600 ~/.local/share/opencode/auth.json
- name: Rispondi a @codex
id: codex
shell: bash
run: |
set -euo pipefail
if ! command -v curl &> /dev/null; then
echo "curl non trovato, tentativo di installazione..."
if command -v apt-get &> /dev/null; then
apt-get update -qq && apt-get install -y -qq curl 2>&1 || true
elif command -v apk &> /dev/null; then
apk add --no-cache curl 2>&1 || true
fi
command -v curl &> /dev/null || { echo "ERRORE: impossibile installare curl."; exit 1; }
fi
HOST="${{ inputs.gitea-host }}"
TOKEN="${{ inputs.api-token }}"
REPO="${{ inputs.repository }}"
ISSUE_NUM="${{ inputs.issue-number }}"
AUTHOR="${{ inputs.comment-author }}"
MODEL_ARG=()
[ -n "${{ inputs.model }}" ] && MODEL_ARG=(--model "${{ inputs.model }}")
# Estrai la domanda dopo @codex
QUERY=$(echo "${{ inputs.comment-body }}" | sed -n 's/.*@codex[[:space:],:]*//p')
if [ -z "$QUERY" ]; then
QUERY="(nessuna domanda specifica dopo @codex)"
fi
# Recupera cronologia commenti, escludendo quelli del bot
COMMENTS_JSON=$(curl -sS -f "$HOST/api/v1/repos/$REPO/issues/$ISSUE_NUM/comments" \
-H "Authorization: token $TOKEN" \
-H "Accept: application/json" 2>/dev/null || echo "[]")
COMMENTS_TEXT=$(echo "$COMMENTS_JSON" | jq -r '
[.[] | select(.user.login != "codex")]
| sort_by(.created_at)
| .[-20:]
| .[]
| "**\(.user.login)**: \(.body)\n"
' 2>/dev/null || echo "")
# Costruisci il prompt
BODY=$(echo "${{ inputs.issue-body }}" | head -c 10000)
[ "${#BODY}" -ge 10000 ] && BODY+=$'\n... (troncato)'
printf -v PROMPT '%s\n\n%s\n%s\n\n## %s\n%s\n%s\n\n## %s\n%s\n\n## %s - @%s\n%s' \
"Sei un assistente esperto per repository software." \
"Rispondi alla domanda in modo diretto e tecnico, in italiano." \
"Analizza il codice sorgente nel repository quando e' rilevante." \
"Issue" \
"**Titolo**: ${{ inputs.issue-title }}" \
"**Corpo**: $BODY" \
"Cronologia commenti" "$COMMENTS_TEXT" \
"Domanda" "$AUTHOR" "$QUERY"
# Esegui opencode
OUTFILE="$GITHUB_WORKSPACE/opencode-codex.txt"
opencode run "$PROMPT" "${MODEL_ARG[@]}" --dangerously-skip-permissions 2>&1 | tee "$OUTFILE"
# Pulisci la risposta: togli ANSI codes, banner build, righe vuote iniziali
ESC=$(printf '\x1b')
sed "s/${ESC}\[[0-9;]*m//g" "$OUTFILE" > /tmp/codex-clean.txt
RESPONSE=$(sed '/^> build /d; /^[[:space:]]*$/d; s/\\n/\n/g' /tmp/codex-clean.txt)
if [ -z "$RESPONSE" ]; then
RESPONSE="Mi dispiace, non ho generato una risposta. Riprova."
fi
# Pubblica il commento
COMMENT_JSON=$(jq -n --arg body "$RESPONSE" '{body: $body}')
curl -sS -X POST "$HOST/api/v1/repos/$REPO/issues/$ISSUE_NUM/comments" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "$COMMENT_JSON" 2>/dev/null && \
echo "Risposta @codex pubblicata sull'issue #$ISSUE_NUM" || \
echo "WARN: Impossibile pubblicare la risposta"

View File

@@ -22,6 +22,10 @@ inputs:
description: Elenco di file da escludere dal mirroring (separati da virgola, punto e virgola o newline).
required: false
default: appsettings.json
clean-destination:
description: Se 'true', elimina tutto il contenuto della cartella di destinazione (dopo aver fermato IIS) tranne appsettings.json, web.config e la cartella store.
required: false
default: 'false'
runs:
using: composite
@@ -99,15 +103,27 @@ runs:
try {
& $appcmd stop site "/site.name:$site"
if ($LASTEXITCODE -ne 0) { throw "stop site fallito ($LASTEXITCODE)" }
$siteStopped = $true
if ($LASTEXITCODE -ne 0) {
Write-Warning "stop site fallito ($LASTEXITCODE) - si prosegue comunque"
} else {
$siteStopped = $true
}
& $appcmd stop apppool "/apppool.name:$appPool"
if ($LASTEXITCODE -ne 0) { throw "stop apppool fallito ($LASTEXITCODE)" }
$poolStopped = $true
if ($LASTEXITCODE -ne 0) {
Write-Warning "stop apppool fallito ($LASTEXITCODE) - si prosegue comunque"
} else {
$poolStopped = $true
}
New-Item -ItemType Directory -Force -Path $dst | Out-Null
if ('${{ inputs.clean-destination }}' -eq 'true') {
$keepItems = @('appsettings.json', 'web.config', 'store')
Get-ChildItem -Path $dst | Where-Object { $_.Name -notin $keepItems } | Remove-Item -Recurse -Force
Write-Host "Pulizia destinazione completata (keep: $($keepItems -join ', '))"
}
& robocopy @robocopyArgs
$robocopyExitCode = $LASTEXITCODE
if ($robocopyExitCode -ge 8) {

View File

@@ -0,0 +1,49 @@
name: Installa OpenCode
description: Installa opencode-ai globalmente tramite npm.
inputs:
version:
description: "Versione di opencode da installare (default: latest)."
required: false
default: ""
runs:
using: composite
steps:
- name: Verifica prerequisiti e installa npm se assente
shell: bash
run: |
set -euo pipefail
if ! command -v node &> /dev/null; then
echo "ERRORE: node non trovato. Installa Node.js sul runner."
exit 1
fi
if ! command -v npm &> /dev/null; then
echo "npm non trovato, tentativo di installazione..."
if command -v apt-get &> /dev/null; then
apt-get update -qq && apt-get install -y -qq npm 2>&1 || true
elif command -v apk &> /dev/null; then
apk add --no-cache npm 2>&1 || true
fi
if ! command -v npm &> /dev/null; then
echo "ERRORE: impossibile installare npm."
exit 1
fi
fi
echo "node $(node --version), npm $(npm --version)"
- name: Installa opencode
shell: bash
run: |
set -euo pipefail
if command -v opencode &> /dev/null; then
echo "opencode già installato: $(opencode --version)"
exit 0
fi
VERSION="${{ inputs.version }}"
if [ -n "$VERSION" ]; then
npm install -g "opencode-ai@${VERSION}"
else
npm install -g opencode-ai
fi
echo "opencode installato: $(opencode --version)"

View File

@@ -0,0 +1,64 @@
name: OpenCode Prompt
description: Esegue un prompt opencode sul codice del repository.
inputs:
prompt:
description: Il prompt da passare a opencode.
required: true
api-key:
description: API key per il provider opencode.
required: true
api-provider:
description: "Nome del provider (default: opencode-go)."
required: false
default: "opencode-go"
model:
description: Modello AI (formato provider/model).
required: false
default: "opencode-go/deepseek-v4-flash"
agent:
description: Agente opencode da utilizzare.
required: false
default: ""
working-directory:
description: Directory di lavoro per opencode. Se vuoto, usa la radice del repo.
required: false
default: ""
outputs:
result:
description: Output prodotto da opencode.
value: ${{ steps.esegui.outputs.result }}
runs:
using: composite
steps:
- name: Configura autenticazione opencode
shell: bash
run: |
set -euo pipefail
mkdir -p ~/.local/share/opencode
jq -n \
--arg provider "${{ inputs.api-provider }}" \
--arg key "${{ inputs.api-key }}" \
'{($provider): {type: "api", key: $key}}' > ~/.local/share/opencode/auth.json
chmod 600 ~/.local/share/opencode/auth.json
- name: Esegui opencode
id: esegui
shell: bash
run: |
set -euo pipefail
WD="${{ inputs.working-directory }}"
[ -z "$WD" ] && WD="$GITHUB_WORKSPACE"
cd "$WD"
EXTRA_ARGS=()
[ -n "${{ inputs.model }}" ] && EXTRA_ARGS+=(--model "${{ inputs.model }}")
[ -n "${{ inputs.agent }}" ] && EXTRA_ARGS+=(--agent "${{ inputs.agent }}")
opencode run "${{ inputs.prompt }}" "${EXTRA_ARGS[@]}" --dangerously-skip-permissions 2>&1 | tee "$GITHUB_WORKSPACE/opencode-output.txt"
RESULT=$(cat "$GITHUB_WORKSPACE/opencode-output.txt")
{
echo "result<<EOF"
echo "$RESULT"
echo "EOF"
} >> "$GITHUB_OUTPUT"

182
triage-issue/action.yml Normal file
View File

@@ -0,0 +1,182 @@
name: Triage Issue
description: "Analizza una issue con opencode: classifica, riassume, gap analisi e produce report MD."
inputs:
issue-title:
description: Titolo dell'issue.
required: true
issue-body:
description: Corpo dell'issue.
required: true
issue-number:
description: Numero dell'issue.
required: true
repository:
description: Repository in formato owner/repo.
required: true
api-token:
description: Token API Gitea.
required: true
gitea-host:
description: URL del server Gitea.
required: false
default: "https://git.incloud.ovh"
api-key:
description: API key per opencode.
required: true
model:
description: Modello AI in formato provider/model.
required: false
default: "opencode-go/deepseek-v4-flash"
outputs:
label:
description: Label attribuita (bug o richiesta).
value: ${{ steps.classifica.outputs.label }}
comment:
description: Commento di triage pubblicato.
value: ${{ steps.classifica.outputs.comment }}
runs:
using: composite
steps:
- name: Configura autenticazione opencode
shell: bash
run: |
set -euo pipefail
if ! command -v jq &> /dev/null; then
echo "jq non trovato, tentativo di installazione..."
if command -v apt-get &> /dev/null; then
apt-get update -qq && apt-get install -y -qq jq 2>&1 || true
elif command -v apk &> /dev/null; then
apk add --no-cache jq 2>&1 || true
fi
command -v jq &> /dev/null || { echo "ERRORE: impossibile installare jq."; exit 1; }
fi
mkdir -p ~/.local/share/opencode
jq -n \
--arg provider "opencode-go" \
--arg key "${{ inputs.api-key }}" \
'{($provider): {type: "api", key: $key}}' > ~/.local/share/opencode/auth.json
chmod 600 ~/.local/share/opencode/auth.json
- name: Classifica issue
id: classifica
shell: bash
run: |
set -euo pipefail
# Assicura curl (potrebbe mancare su Alpine)
if ! command -v curl &> /dev/null; then
echo "curl non trovato, tentativo di installazione..."
if command -v apt-get &> /dev/null; then
apt-get update -qq && apt-get install -y -qq curl 2>&1 || true
elif command -v apk &> /dev/null; then
apk add --no-cache curl 2>&1 || true
fi
command -v curl &> /dev/null || { echo "ERRORE: impossibile installare curl."; exit 1; }
fi
HOST="${{ inputs.gitea-host }}"
TOKEN="${{ inputs.api-token }}"
REPO="${{ inputs.repository }}"
ISSUE_NUM="${{ inputs.issue-number }}"
MODEL_ARG=()
[ -n "${{ inputs.model }}" ] && MODEL_ARG=(--model "${{ inputs.model }}")
BODY="$(echo "${{ inputs.issue-body }}" | head -c 10000)"
[ "${#BODY}" -ge 10000 ] && BODY+=$'\n... (troncato)'
printf -v PROMPT '%s\n\n%s\n\n%s\n\n%s\n\n%s\n\n%s\n\n%s\n\n%s\n\n%s: %s\n%s:\n%s' \
"Sei un sistema di triage automatico per repository software." \
"Analizza il codice sorgente nel repository ed esamina la seguente issue." \
"Classifica con UNA delle label:" \
'- "bug": malfunzionamento, errore, crash, anomalia, comportamento inaspettato' \
'- "richiesta": nuova funzionalità, miglioramento, refactoring, ottimizzazione, o dubbio' \
"Rispondi ESCLUSIVAMENTE con un JSON valido su una SINGOLA riga, senza nessun altro testo:" \
'{"label":"bug","comment":"**Riassunto**: ...\n\n**Gap analisi**: codice mancante...\n\n**Domande aperte**:\n1. ...","md":"# Analisi tecnica\n\n## Codice coinvolto\n..."}' \
"Il campo 'comment' deve contenere: riassunto della issue, gap analisi (cosa manca rispetto al codice), domande aperte. Usa \\n per i newline." \
"Il campo 'md' deve contenere: analisi tecnica completa in formato markdown, file/moduli coinvolti, ipotesi root cause, proposta di fix. Usa \\n per i newline." \
"Titolo" "${{ inputs.issue-title }}" \
"Corpo" "$BODY"
OUTFILE="$GITHUB_WORKSPACE/opencode-triage.txt"
opencode run "$PROMPT" "${MODEL_ARG[@]}" --dangerously-skip-permissions 2>&1 | tee "$OUTFILE"
# Estrai la riga JSON dalla risposta (salta banner ANSI)
JSON_LINE=$(grep -E '^\{"label":"(bug|richiesta)","comment":' "$OUTFILE" | head -1)
JSON=""
if [ -n "$JSON_LINE" ]; then
JSON=$(echo "$JSON_LINE" | jq -c . 2>/dev/null)
fi
if [ -z "$JSON" ] || ! echo "$JSON" | jq empty 2>/dev/null; then
echo "WARN: Impossibile estrarre JSON valido, default a richiesta"
LABEL="richiesta"
COMMENT="Classificazione automatica non riuscita. Label impostata a richiesta per default."
MD_CONTENT=""
else
LABEL=$(echo "$JSON" | jq -r '.label // "richiesta"')
COMMENT=$(echo "$JSON" | jq -r '.comment // "Classificazione automatica."')
COMMENT=$(printf '%b' "$COMMENT")
MD_CONTENT=$(echo "$JSON" | jq -r '.md // ""')
MD_CONTENT=$(printf '%b' "$MD_CONTENT")
fi
case "$LABEL" in
bug|richiesta) ;;
*) echo "WARN: Label '$LABEL' sconosciuta, default a richiesta"; LABEL="richiesta" ;;
esac
echo "label=$LABEL" >> "$GITHUB_OUTPUT"
{
echo "comment<<EOF"
echo "$COMMENT"
echo "EOF"
} >> "$GITHUB_OUTPUT"
echo "Label determinata: $LABEL"
LABEL_ID=$(curl -sS -f "$HOST/api/v1/repos/$REPO/labels" \
-H "Authorization: token $TOKEN" \
-H "Accept: application/json" \
2>/dev/null | jq -r ".[] | select(.name==\"$LABEL\") | .id" | head -1)
if [ -n "$LABEL_ID" ]; then
curl -sS -X PUT "$HOST/api/v1/repos/$REPO/issues/$ISSUE_NUM/labels" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"labels\":[$LABEL_ID]}" 2>/dev/null && \
echo "Label '$LABEL' applicata" || \
echo "WARN: Impossibile applicare la label '$LABEL'"
else
echo "WARN: Label '$LABEL' non trovata nel repo. Creala manualmente o passa un token con permessi."
fi
# Carica il file MD come asset (prima del commento per poterlo linkare)
LINK_MD=""
if [ -n "$MD_CONTENT" ] && [ "$MD_CONTENT" != "null" ]; then
MD_FILE="/tmp/triage-issue-${ISSUE_NUM}.md"
printf '%s\n' "$MD_CONTENT" > "$MD_FILE"
ASSET_RESP=$(curl -sS -X POST "$HOST/api/v1/repos/$REPO/issues/$ISSUE_NUM/assets" \
-H "Authorization: token $TOKEN" \
-H "Accept: application/json" \
-F "attachment=@${MD_FILE};filename=triage-issue-${ISSUE_NUM}.md" 2>/dev/null)
ASSET_URL=$(echo "$ASSET_RESP" | jq -r '.browser_download_url // empty' 2>/dev/null)
if [ -n "$ASSET_URL" ] && [ "$ASSET_URL" != "null" ]; then
printf -v LINK_MD '\n\n---\n📎 **Analisi tecnica completa**: [triage-issue-%s.md](%s)' "$ISSUE_NUM" "$ASSET_URL"
echo "Asset MD caricato: $ASSET_URL"
else
echo "WARN: Impossibile caricare il file MD come asset"
fi
fi
COMMENT_BODY="${COMMENT}${LINK_MD}"
COMMENT_JSON=$(jq -n --arg body "$COMMENT_BODY" '{body: $body}')
curl -sS -X POST "$HOST/api/v1/repos/$REPO/issues/$ISSUE_NUM/comments" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "$COMMENT_JSON" 2>/dev/null && \
echo "Commento pubblicato sull'issue #$ISSUE_NUM" || \
echo "WARN: Impossibile pubblicare il commento"