refactor: rename gitea-issues to git-issues and symlink projects to monorepo

- Rename directory and scripts env GIT_ISSUES_DIR; secrets path .secrets/git-issues
- Rename agent git-issues-process; update docs GIT_ISSUES_SCRIPTS_AGENTS.md
- Symlink projects/enso, smart_ide, builazoo to ../../projects/<id> when used inside smart_ide
- Update project conf paths for git-issues tokens and imap bridge
This commit is contained in:
Nicolas Cantu 2026-04-03 19:05:28 +02:00
parent 285e72039e
commit e86a9fbb6e
74 changed files with 441 additions and 749 deletions

View File

@ -1,6 +1,6 @@
--- ---
name: agent-loop name: agent-loop
description: Orchestre la boucle de récupération des mails et le traitement par gitea-issues-process. Paramètre /agent-loop = nombre de boucles (1 min chacune), pas des secondes. Exécutions délimitées uniquement (N cycles) ; ne jamais lancer de processus en arrière-plan (nohup / &). description: Orchestre la boucle de récupération des mails et le traitement par git-issues-process. Paramètre /agent-loop = nombre de boucles (1 min chacune), pas des secondes. Exécutions délimitées uniquement (N cycles) ; ne jamais lancer de processus en arrière-plan (nohup / &).
model: inherit model: inherit
is_background: false is_background: false
--- ---
@ -35,19 +35,19 @@ En tant qu'agent, avant de solliciter l'ia, regarde ce que tu peux scripter (imp
**Horodatage** : au début et à la fin d'exécution, afficher date/heure, projet (id), branche, répertoire de travail du dépôt concerné. **Horodatage** : au début et à la fin d'exécution, afficher date/heure, projet (id), branche, répertoire de travail du dépôt concerné.
Tu es l'agent qui **orchestre** la surveillance des mails et leur traitement. Tu ne traites pas les mails toimême : le traitement (réponse, issues, marquage lu) est fait par l'**agent gitea-issues-process**. Tu lances les scripts et/ou les sous-agents selon la demande. Tu es l'agent qui **orchestre** la surveillance des mails et leur traitement. Tu ne traites pas les mails toimême : le traitement (réponse, issues, marquage lu) est fait par l'**agent git-issues-process**. Tu lances les scripts et/ou les sous-agents selon la demande.
**Récupération et filtrage** : la **récupération** des mails et le **filtrage** (to, from, `tickets.authorized_emails`, date) sont assurés par le **script** `tickets-fetch-inbox.sh` (qui appelle le Python associé). L'agent ne fait que **lancer** ce script ; il ne récupère ni ne filtre luimême. Les adresses « to » des mails reçus déterminent le projet (routage par le script) ; les réponses sont envoyées à l'**expéditeur** (« from »). Aucune adresse n'est fixée en dur. **Récupération et filtrage** : la **récupération** des mails et le **filtrage** (to, from, `tickets.authorized_emails`, date) sont assurés par le **script** `tickets-fetch-inbox.sh` (qui appelle le Python associé). L'agent ne fait que **lancer** ce script ; il ne récupère ni ne filtre luimême. Les adresses « to » des mails reçus déterminent le projet (routage par le script) ; les réponses sont envoyées à l'**expéditeur** (« from »). Aucune adresse n'est fixée en dur.
**Paramètre de la commande /agent-loop** : lorsqu'on invoque l'agent avec un argument (ex. `/agent-loop 600`), cet argument est le **nombre de boucles** (chaque boucle = 1 minute). Ce n'est **pas** un intervalle en secondes. Ex. `/agent-loop 600` = exécuter **600 cycles** (section 2 : récupération + traitement + attente 1 min, répété 600 fois). Interpréter tout paramètre numérique comme ce nombre de boucles et lancer autant de cycles (section 2) ou passer N à `agent-loop-chat-iterations.sh [N]` (section 3). **Paramètre de la commande /agent-loop** : lorsqu'on invoque l'agent avec un argument (ex. `/agent-loop 600`), cet argument est le **nombre de boucles** (chaque boucle = 1 minute). Ce n'est **pas** un intervalle en secondes. Ex. `/agent-loop 600` = exécuter **600 cycles** (section 2 : récupération + traitement + attente 1 min, répété 600 fois). Interpréter tout paramètre numérique comme ce nombre de boucles et lancer autant de cycles (section 2) ou passer N à `agent-loop-chat-iterations.sh [N]` (section 3).
**Références obligatoires** : lire `projects/ia_dev/docs/GITEA_ISSUES_SCRIPTS_AGENTS.md` (contexte d'exécution). Usage standalone : tous les scripts sont invoqués depuis la **racine de ia_dev** : `./gitea-issues/<script>.sh`. **Références obligatoires** : lire `projects/ia_dev/docs/GIT_ISSUES_SCRIPTS_AGENTS.md` (contexte d'exécution). Usage standalone : tous les scripts sont invoqués depuis la **racine de ia_dev** : `./git-issues/<script>.sh`.
**Script agent-loop.sh** (lancement manuel uniquement, pas depuis l'agent) : premier argument optionnel = **intervalle en secondes** entre deux tours (ex. `./gitea-issues/agent-loop.sh 60` = 60 s). À ne pas confondre avec le paramètre de la commande /agent-loop (nombre de boucles). La boucle utilise le **spooler** (critère from/to dans conf.json), pas le statut IMAP « non lu ». Seuls les mails **à partir du 10 mars 2026** (ou `MAIL_SINCE_DATE` en env) sont récupérés. **Fichier témoin** `projects/<id>/logs/gitea-issues/agent-loop.status` : fichier d**état** (pas un log) mis à jour à chaque itération ; chemin sous `projects/<id>/logs/` pour que chaque projet ait son propre état de boucle ; actif si mtime < 2×intervalle. Fichier pending : `projects/<id>/logs/gitea-issues/agent-loop.pending` (chemins des .pending du spooler à traiter). Variables (optionnel `.secrets/gitea-issues/agent-loop.env`) : `AGENT_LOOP_INTERVAL_SEC` (défaut 60), `AGENT_LOOP_RUN_AGENT` (0|1), `AGENT_LOOP_MODEL` (défaut sonnet-4.6), `AGENT_LOOP_STATUS_FILE`, `AGENT_LOOP_PENDING_FILE`. Hook « remonter mails » : `.smartIde/hooks/remonter-mails.sh` lit `projects/<id>/data/issues/*.pending` ou `agent-loop.pending`. **Script agent-loop.sh** (lancement manuel uniquement, pas depuis l'agent) : premier argument optionnel = **intervalle en secondes** entre deux tours (ex. `./git-issues/agent-loop.sh 60` = 60 s). À ne pas confondre avec le paramètre de la commande /agent-loop (nombre de boucles). La boucle utilise le **spooler** (critère from/to dans conf.json), pas le statut IMAP « non lu ». Seuls les mails **à partir du 10 mars 2026** (ou `MAIL_SINCE_DATE` en env) sont récupérés. **Fichier témoin** `projects/<id>/logs/git-issues/agent-loop.status` : fichier d**état** (pas un log) mis à jour à chaque itération ; chemin sous `projects/<id>/logs/` pour que chaque projet ait son propre état de boucle ; actif si mtime < 2×intervalle. Fichier pending : `projects/<id>/logs/git-issues/agent-loop.pending` (chemins des .pending du spooler à traiter). Variables (optionnel `.secrets/git-issues/agent-loop.env`) : `AGENT_LOOP_INTERVAL_SEC` (défaut 60), `AGENT_LOOP_RUN_AGENT` (0|1), `AGENT_LOOP_MODEL` (défaut sonnet-4.6), `AGENT_LOOP_STATUS_FILE`, `AGENT_LOOP_PENDING_FILE`. Hook « remonter mails » : `.smartIde/hooks/remonter-mails.sh` lit `projects/<id>/data/issues/*.pending` ou `agent-loop.pending`.
**Fichiers de contrôle (section 2 — boucle x cycles)** : **Fichiers de contrôle (section 2 — boucle x cycles)** :
- **Lock (une seule instance)** : `projects/<id>/logs/gitea-issues/agent-loop.lock`. Utiliser les scripts : **agent-loop-lock-acquire.sh** (vérifie mtime < 24 h et crée le lock ; exit 1 si déjà actif), **agent-loop-lock-release.sh** (supprime lock et fichier stop). - **Lock (une seule instance)** : `projects/<id>/logs/git-issues/agent-loop.lock`. Utiliser les scripts : **agent-loop-lock-acquire.sh** (vérifie mtime < 24 h et crée le lock ; exit 1 si déjà actif), **agent-loop-lock-release.sh** (supprime lock et fichier stop).
- **Arrêt à la demande** : `projects/<id>/logs/gitea-issues/agent-loop.stop`. Au début de chaque cycle, exécuter **agent-loop-stop-requested.sh** (exit 0 si le fichier existe) ; si oui, lancer **agent-loop-lock-release.sh** et sortir. Pour demander l'arrêt : `Depuis la racine de ia_dev (MAIL_TO ou AI_AGENT_TOKEN défini) : ./gitea-issues/agent-loop-stop.sh` ou `touch projects/<id>/logs/gitea-issues/agent-loop.stop`. - **Arrêt à la demande** : `projects/<id>/logs/git-issues/agent-loop.stop`. Au début de chaque cycle, exécuter **agent-loop-stop-requested.sh** (exit 0 si le fichier existe) ; si oui, lancer **agent-loop-lock-release.sh** et sortir. Pour demander l'arrêt : `Depuis la racine de ia_dev (MAIL_TO ou AI_AGENT_TOKEN défini) : ./git-issues/agent-loop-stop.sh` ou `touch projects/<id>/logs/git-issues/agent-loop.stop`.
--- ---
@ -61,26 +61,26 @@ Si l'utilisateur demande explicitement « lancer les 2 boucles en arrière-plan
## 2. Lancer x fois : récupération 1 fois puis traitement 1 fois (x cycles) ## 2. Lancer x fois : récupération 1 fois puis traitement 1 fois (x cycles)
Si l'utilisateur demande de **lancer x fois** les deux sous-agents (une récupération puis un traitement, répété x fois), ou invoque **/agent-loop N** (N = nombre de boucles de 1 min) : **x = N**. Chaque cycle = 1 récupération + 1 traitement (agent gitea-issues-process) + 1 min d'attente. **Pour que les N cycles s'exécutent réellement** (sans limite de session) : l'agent lance **une seule commande** `./gitea-issues/agent-loop-n-cycles.sh [N]` avec un timeout ≥ N minutes ; le script fait N × (récupération + agent via Cursor Agent CLI + 1 min). Voir section 3 (prérequis CLI et AGENT_LOOP_RUN_AGENT=1). Si l'utilisateur demande de **lancer x fois** les deux sous-agents (une récupération puis un traitement, répété x fois), ou invoque **/agent-loop N** (N = nombre de boucles de 1 min) : **x = N**. Chaque cycle = 1 récupération + 1 traitement (agent git-issues-process) + 1 min d'attente. **Pour que les N cycles s'exécutent réellement** (sans limite de session) : l'agent lance **une seule commande** `./git-issues/agent-loop-n-cycles.sh [N]` avec un timeout ≥ N minutes ; le script fait N × (récupération + agent via Cursor Agent CLI + 1 min). Voir section 3 (prérequis CLI et AGENT_LOOP_RUN_AGENT=1).
**Avant de commencer les x cycles** : **Avant de commencer les x cycles** :
- Exécuter depuis la racine de ia_dev : `./gitea-issues/agent-loop-lock-acquire.sh`. - Exécuter depuis la racine de ia_dev : `./git-issues/agent-loop-lock-acquire.sh`.
- Si le script **sort avec un code non nul** : **ne pas lancer** les cycles ; indiquer à l'utilisateur qu'une instance est déjà en cours (lock actif, mtime < 24 h). - Si le script **sort avec un code non nul** : **ne pas lancer** les cycles ; indiquer à l'utilisateur qu'une instance est déjà en cours (lock actif, mtime < 24 h).
- Si le script sort 0 : le lock est acquis ; poursuivre. - Si le script sort 0 : le lock est acquis ; poursuivre.
- **À la fin** (normale ou après arrêt) : exécuter `Depuis la racine de ia_dev : ./gitea-issues/agent-loop-lock-release.sh` (supprime le lock et le fichier stop s'il existe). - **À la fin** (normale ou après arrêt) : exécuter `Depuis la racine de ia_dev : ./git-issues/agent-loop-lock-release.sh` (supprime le lock et le fichier stop s'il existe).
Pour chaque cycle `i` de 1 à x : Pour chaque cycle `i` de 1 à x :
**Au début du cycle** (avant l'étape 1) : exécuter `Depuis la racine de ia_dev : ./gitea-issues/agent-loop-stop-requested.sh`. Si le script **sort 0** (fichier stop présent) : exécuter `agent-loop-lock-release.sh`, puis **sortir** en indiquant que la boucle a été arrêtée à la demande. **Au début du cycle** (avant l'étape 1) : exécuter `Depuis la racine de ia_dev : ./git-issues/agent-loop-stop-requested.sh`. Si le script **sort 0** (fichier stop présent) : exécuter `agent-loop-lock-release.sh`, puis **sortir** en indiquant que la boucle a été arrêtée à la demande.
1. **Récupération une fois** : exécuter depuis la racine de ia_dev : 1. **Récupération une fois** : exécuter depuis la racine de ia_dev :
```bash ```bash
Depuis la racine de ia_dev : ./gitea-issues/agent-loop-retrieval-once.sh Depuis la racine de ia_dev : ./git-issues/agent-loop-retrieval-once.sh
``` ```
Ce script exécute `tickets-fetch-inbox.sh` puis `list-pending-spooler.sh` et écrit les chemins des .pending dans `projects/<id>/logs/gitea-issues/agent-loop.pending` (et met à jour le fichier témoin). Pas d'arrière-plan : attendre la fin du script. Ce script exécute `tickets-fetch-inbox.sh` puis `list-pending-spooler.sh` et écrit les chemins des .pending dans `projects/<id>/logs/git-issues/agent-loop.pending` (et met à jour le fichier témoin). Pas d'arrière-plan : attendre la fin du script.
2. **Traitement une fois** : lancer **intégralement** l'agent **gitea-issues-process** sur les mails reçus (contenu de `projects/<id>/logs/gitea-issues/agent-loop.pending`). Utiliser le sous-agent Cursor (mcp_task ou équivalent) avec le type `gitea-issues-process` et un prompt du type : 2. **Traitement une fois** : lancer **intégralement** l'agent **git-issues-process** sur les mails reçus (contenu de `projects/<id>/logs/git-issues/agent-loop.pending`). Utiliser le sous-agent Cursor (mcp_task ou équivalent) avec le type `git-issues-process` et un prompt du type :
« Traite les mails du spooler listés dans `projects/<id>/logs/gitea-issues/agent-loop.pending` (chemins des .pending) ou en exécutant `list-pending-spooler.sh`. Pour chaque fichier .pending : lire le JSON (from, to, subject, body, message_id, base). Envoyer la réponse à l'**expéditeur** (champ « from » du JSON), pas à une adresse fixe ; le « to » du mail reçu a déjà déterminé le projet. Rédiger une réponse pertinente (uniquement ton texte ; pas de citation — mail-send-reply.sh refuse si le body contient From:, Message-ID, wrote:, etc.). Appeler mail-send-reply.sh --to <from> puis write-response-spooler.sh. Ne pas appeler mail-mark-read.sh (spooler). » « Traite les mails du spooler listés dans `projects/<id>/logs/git-issues/agent-loop.pending` (chemins des .pending) ou en exécutant `list-pending-spooler.sh`. Pour chaque fichier .pending : lire le JSON (from, to, subject, body, message_id, base). Envoyer la réponse à l'**expéditeur** (champ « from » du JSON), pas à une adresse fixe ; le « to » du mail reçu a déjà déterminé le projet. Rédiger une réponse pertinente (uniquement ton texte ; pas de citation — mail-send-reply.sh refuse si le body contient From:, Message-ID, wrote:, etc.). Appeler mail-send-reply.sh --to <from> puis write-response-spooler.sh. Ne pas appeler mail-mark-read.sh (spooler). »
3. **Attente 1 minute entre cycles** : après chaque cycle (après létape 2), si ce nest pas le dernier cycle (`i` < x), attendre **1 minute** (60 secondes) avant de commencer le cycle suivant exécuter `sleep 60` ou faire attendre lorchestration 60 s. Pas dattente après le dernier cycle. 3. **Attente 1 minute entre cycles** : après chaque cycle (après létape 2), si ce nest pas le dernier cycle (`i` < x), attendre **1 minute** (60 secondes) avant de commencer le cycle suivant exécuter `sleep 60` ou faire attendre lorchestration 60 s. Pas dattente après le dernier cycle.
@ -90,10 +90,10 @@ Répéter les étapes 1, 2 et 3 pour les x cycles demandés. Chaque cycle traite
## 3. Autres demandes ## 3. Autres demandes
- **Boucle limitée depuis le chat (N itérations, attente 1 min entre chaque)** : pour **/agent-loop N** (ex. 600), l'agent exécute **une seule commande** depuis la racine de ia_dev : `./gitea-issues/agent-loop-n-cycles.sh [N]` (défaut N=600), avec un **timeout** d'au moins N minutes (ex. 39600000 ms pour N=600 ≈ 11 h). Le script effectue **exactement N cycles** (vérification arrêt → récupération → invocation de l'agent **gitea-issues-process** via **Cursor Agent CLI** si `AGENT_LOOP_RUN_AGENT=1` → attente 1 min) puis lock-release. Aucun contournement : le traitement à chaque cycle est le même agent gitea-issues-process, invoqué par le script via la CLI. **Prérequis** : Cursor Agent CLI dans le PATH et `AGENT_LOOP_RUN_AGENT=1` dans `.secrets/gitea-issues/agent-loop.env` pour que les mails soient traités à chaque cycle. Ne **pas** utiliser `agent-loop-chat-iterations.sh` pour /agent-loop N : ce script fait uniquement mail-list-unread (legacy) et **ne traite pas le spooler**. Pour une demande explicite « N itérations legacy (non lu uniquement) » sans réponse mail : `./gitea-issues/agent-loop-chat-iterations.sh [N]`. Par défaut N=3 ; `--repeat` pour relancer après N itérations. - **Boucle limitée depuis le chat (N itérations, attente 1 min entre chaque)** : pour **/agent-loop N** (ex. 600), l'agent exécute **une seule commande** depuis la racine de ia_dev : `./git-issues/agent-loop-n-cycles.sh [N]` (défaut N=600), avec un **timeout** d'au moins N minutes (ex. 39600000 ms pour N=600 ≈ 11 h). Le script effectue **exactement N cycles** (vérification arrêt → récupération → invocation de l'agent **git-issues-process** via **Cursor Agent CLI** si `AGENT_LOOP_RUN_AGENT=1` → attente 1 min) puis lock-release. Aucun contournement : le traitement à chaque cycle est le même agent git-issues-process, invoqué par le script via la CLI. **Prérequis** : Cursor Agent CLI dans le PATH et `AGENT_LOOP_RUN_AGENT=1` dans `.secrets/git-issues/agent-loop.env` pour que les mails soient traités à chaque cycle. Ne **pas** utiliser `agent-loop-chat-iterations.sh` pour /agent-loop N : ce script fait uniquement mail-list-unread (legacy) et **ne traite pas le spooler**. Pour une demande explicite « N itérations legacy (non lu uniquement) » sans réponse mail : `./git-issues/agent-loop-chat-iterations.sh [N]`. Par défaut N=3 ; `--repeat` pour relancer après N itérations.
- **Arrêter la boucle en cours (section 2)** : exécuter `Depuis la racine de ia_dev (MAIL_TO ou AI_AGENT_TOKEN défini) : ./gitea-issues/agent-loop-stop.sh`. Cela crée le fichier `agent-loop.stop` ; l'instance en cours le détecte au début du cycle suivant et s'arrête proprement. - **Arrêter la boucle en cours (section 2)** : exécuter `Depuis la racine de ia_dev (MAIL_TO ou AI_AGENT_TOKEN défini) : ./git-issues/agent-loop-stop.sh`. Cela crée le fichier `agent-loop.stop` ; l'instance en cours le détecte au début du cycle suivant et s'arrête proprement.
- **Consulter les mails en attente** : lire le fichier `projects/<id>/logs/gitea-issues/agent-loop.pending` ou inviter l'utilisateur à lancer l'agent gitea-issues-process avec ce fichier en entrée. - **Consulter les mails en attente** : lire le fichier `projects/<id>/logs/git-issues/agent-loop.pending` ou inviter l'utilisateur à lancer l'agent git-issues-process avec ce fichier en entrée.
- **Vérifier si la boucle est active** : le fichier témoin est `ia_dev/projects/<id>/logs/gitea-issues/agent-loop.status` ; s'il a été modifié depuis moins de 2 × intervalle (ex. 120 s si intervalle 60 s), la boucle est considérée active. - **Vérifier si la boucle est active** : le fichier témoin est `ia_dev/projects/<id>/logs/git-issues/agent-loop.status` ; s'il a été modifié depuis moins de 2 × intervalle (ex. 120 s si intervalle 60 s), la boucle est considérée active.
--- ---
@ -102,7 +102,7 @@ Répéter les étapes 1, 2 et 3 pour les x cycles demandés. Chaque cycle traite
- **Pas de processus en arrière-plan** : ne jamais lancer `agent-loop.sh` ni `agent-loop-treatment.sh` avec `nohup` ou `&` ; les boucles sont gérées par l'agent via des exécutions délimitées (agent-loop-chat-iterations.sh N, ou cycles section 2). - **Pas de processus en arrière-plan** : ne jamais lancer `agent-loop.sh` ni `agent-loop-treatment.sh` avec `nohup` ou `&` ; les boucles sont gérées par l'agent via des exécutions délimitées (agent-loop-chat-iterations.sh N, ou cycles section 2).
- Ne pas déclencher la CI, ne pas écrire en base, ne pas masquer les sorties des scripts. - Ne pas déclencher la CI, ne pas écrire en base, ne pas masquer les sorties des scripts.
- Répertoire d'exécution des scripts : toujours **racine de ia_dev** (standalone). - Répertoire d'exécution des scripts : toujours **racine de ia_dev** (standalone).
- Le traitement des mails (réponse réelle, workflow fil, marquage lu) est **uniquement** assuré par l'agent gitea-issues-process ; ne pas court-circuiter son workflow. - Le traitement des mails (réponse réelle, workflow fil, marquage lu) est **uniquement** assuré par l'agent git-issues-process ; ne pas court-circuiter son workflow.
## Clôture complète obligatoire (tous les cas, sans exception) ## Clôture complète obligatoire (tous les cas, sans exception)

View File

@ -97,7 +97,7 @@ Tu appliques les règles ci-dessous **lorsqu'il y a du code à produire** (évol
20. Lancer obligatoirement un lint 20. Lancer obligatoirement un lint
Utiliser l'agent `.smartIde/agents/fix-lint.md` (commande /fix-lint) Utiliser l'agent `.smartIde/agents/fix-lint.md` (commande /fix-lint)
21. **Documentation** : Compléter le wiki avec l'objectif, les impacts, les modifications, les modalités de déploiement et d'analyse. **`docs/` est hors versionnement** : maintenir les fichiers dans `docs/` localement (ne pas les supprimer), puis exécuter `./gitea-issues/wiki-migrate-docs.sh` pour pousser vers le wiki ; ou éditer la page wiki directement. Ne pas committer `docs/`. **Avant d'exécuter wiki-migrate-docs.sh :** lire le script, présenter un résumé de ce qu'il fait, puis l'exécuter. 21. **Documentation** : Compléter le wiki avec l'objectif, les impacts, les modifications, les modalités de déploiement et d'analyse. **`docs/` est hors versionnement** : maintenir les fichiers dans `docs/` localement (ne pas les supprimer), puis exécuter `./git-issues/wiki-migrate-docs.sh` pour pousser vers le wiki ; ou éditer la page wiki directement. Ne pas committer `docs/`. **Avant d'exécuter wiki-migrate-docs.sh :** lire le script, présenter un résumé de ce qu'il fait, puis l'exécuter.
22. **Commit** : Préparer le commit avec le format de `.smartIde/agents/push-by-script.md` (lignes 15-32) : 22. **Commit** : Préparer le commit avec le format de `.smartIde/agents/push-by-script.md` (lignes 15-32) :

View File

@ -39,7 +39,7 @@ Ce document **centralise toutes les informations** sur la documentation du proje
**Avant d'exécuter un script du projet :** **Avant d'exécuter un script du projet :**
1. Lire le fichier du script avec l'outil de lecture (ex. `gitea-issues/wiki-migrate-docs.sh` lorsqu'il est invoqué). 1. Lire le fichier du script avec l'outil de lecture (ex. `git-issues/wiki-migrate-docs.sh` lorsqu'il est invoqué).
2. Présenter à l'utilisateur un résumé de ce que le script va faire : étapes principales, options utilisées, effets attendus. 2. Présenter à l'utilisateur un résumé de ce que le script va faire : étapes principales, options utilisées, effets attendus.
3. Lancer le script uniquement après cette présentation. 3. Lancer le script uniquement après cette présentation.
@ -55,21 +55,21 @@ Ce document **centralise toutes les informations** sur la documentation du proje
* **Objectif des travaux :** Se concentrer sur la réalisation de la liste des tâches décrite dans `docs/todoFix/` et la documentation (wiki). * **Objectif des travaux :** Se concentrer sur la réalisation de la liste des tâches décrite dans `docs/todoFix/` et la documentation (wiki).
* **Structure de la documentation :** * **Structure de la documentation :**
* La documentation générale et pérenne se trouve dans le **wiki** du projet (URL dans `projects/<id>/conf.json``git.wiki_url`). Page d'accueil du wiki : **Home**. * La documentation générale et pérenne se trouve dans le **wiki** du projet (URL dans `projects/<id>/conf.json``git.wiki_url`). Page d'accueil du wiki : **Home**.
* Pour mettre à jour le wiki : modifier le fichier correspondant dans `docs/` puis exécuter depuis la racine projet (chemin absolu) : `./gitea-issues/wiki-migrate-docs.sh` ; ou éditer la page directement sur le wiki. Correspondance fichier → page : voir `projects/ia_dev/docs/GITEA_ISSUES_SCRIPTS_AGENTS.md` (section Migration docs/ → wiki). * Pour mettre à jour le wiki : modifier le fichier correspondant dans `docs/` puis exécuter depuis la racine projet (chemin absolu) : `./git-issues/wiki-migrate-docs.sh` ; ou éditer la page directement sur le wiki. Correspondance fichier → page : voir `projects/ia_dev/docs/GIT_ISSUES_SCRIPTS_AGENTS.md` (section Migration docs/ → wiki).
* **`docs/` est hors versionnement** : maintenir `docs/` localement (ne pas le supprimer), pousser vers le wiki avec `wiki-migrate-docs.sh` après édition ; ne jamais committer `docs/`. * **`docs/` est hors versionnement** : maintenir `docs/` localement (ne pas le supprimer), pousser vers le wiki avec `wiki-migrate-docs.sh` après édition ; ne jamais committer `docs/`.
* Les features et corrections sont documentées dans le wiki (pages Operations, Frontend, Code-Standards, etc.) ; les tâches en cours dans `docs/todoFix/`. * Les features et corrections sont documentées dans le wiki (pages Operations, Frontend, Code-Standards, etc.) ; les tâches en cours dans `docs/todoFix/`.
* Les user stories se trouvent dans `docs/user_stories/` (43 user stories documentées). * Les user stories se trouvent dans `docs/user_stories/` (43 user stories documentées).
* **User Stories :** Consulter `docs/user_stories/INDEX.md` pour la liste complète et les dépendances. Chaque user story documente un parcours utilisateur avec actions précises, vérifications backend, valeurs de test. Utiliser comme référence pour l'autonomie du développement. * **User Stories :** Consulter `docs/user_stories/INDEX.md` pour la liste complète et les dépendances. Chaque user story documente un parcours utilisateur avec actions précises, vérifications backend, valeurs de test. Utiliser comme référence pour l'autonomie du développement.
* **Qualité et sécurité :** Consulter les pages wiki correspondantes (ex. Code-Standards) ou `docs/` si présents. * **Qualité et sécurité :** Consulter les pages wiki correspondantes (ex. Code-Standards) ou `docs/` si présents.
* **Utilisation de la documentation existante :** Ne pas ajouter de nouveaux documents sans raison ; enrichir et mettre à jour le wiki (ou docs/ puis wiki-migrate-docs.sh). * **Utilisation de la documentation existante :** Ne pas ajouter de nouveaux documents sans raison ; enrichir et mettre à jour le wiki (ou docs/ puis wiki-migrate-docs.sh).
* **Mise à jour continue :** Mettre à jour la documentation (wiki via docs/ et `./gitea-issues/wiki-migrate-docs.sh`, `docs/todoFix/`, `docs/user_stories/` et commentaires dans le code) après les modifications ou pour clarifier. * **Mise à jour continue :** Mettre à jour la documentation (wiki via docs/ et `./git-issues/wiki-migrate-docs.sh`, `docs/todoFix/`, `docs/user_stories/` et commentaires dans le code) après les modifications ou pour clarifier.
* **Changelog :** Le fichier `CHANGELOG.md` de cette version en cours intègre toutes les modifications majeures. Ce contenu est repris dans la splash notice de l'application front. Les mises à jour mineures sont ajoutées au `CHANGELOG.md` sans enlever d'élément existant. * **Changelog :** Le fichier `CHANGELOG.md` de cette version en cours intègre toutes les modifications majeures. Ce contenu est repris dans la splash notice de l'application front. Les mises à jour mineures sont ajoutées au `CHANGELOG.md` sans enlever d'élément existant.
## docs/features extract ## docs/features extract
Dans l'ordre et pour tous les documents de docs/features : Dans l'ordre et pour tous les documents de docs/features :
1) Extraire toutes les données pertinentes des documents de docs/features et les intégrer dans les pages wiki existantes (mettre à jour les fichiers correspondants dans docs/ puis exécuter `./gitea-issues/wiki-migrate-docs.sh`). 1) Extraire toutes les données pertinentes des documents de docs/features et les intégrer dans les pages wiki existantes (mettre à jour les fichiers correspondants dans docs/ puis exécuter `./git-issues/wiki-migrate-docs.sh`).
2) Supprimer tous les fichiers dans docs/features 2) Supprimer tous les fichiers dans docs/features
@ -77,7 +77,7 @@ Dans l'ordre et pour tous les documents de docs/features :
Dans l'ordre et pour tous les documents de docs/fixKnowledge : Dans l'ordre et pour tous les documents de docs/fixKnowledge :
1) Extraire toutes les données pertinentes des documents de docs/fixKnowledge et les intégrer dans les pages wiki existantes (mettre à jour docs/ puis `./gitea-issues/wiki-migrate-docs.sh`). 1) Extraire toutes les données pertinentes des documents de docs/fixKnowledge et les intégrer dans les pages wiki existantes (mettre à jour docs/ puis `./git-issues/wiki-migrate-docs.sh`).
2) Supprimer tous les fichiers dans docs/fixKnowledge 2) Supprimer tous les fichiers dans docs/fixKnowledge
@ -88,7 +88,7 @@ Dans l'ordre et pour tous les documents de docs et les pages wiki :
Documents / pages à ne pas supprimer lors des étapes suivantes (équivalents wiki des anciens fichiers docs/) : Documents / pages à ne pas supprimer lors des étapes suivantes (équivalents wiki des anciens fichiers docs/) :
* Page wiki Home (page d'accueil du wiki) * Page wiki Home (page d'accueil du wiki)
* Pages wiki : Api, Architecture, Code-Standards, Deployment, Operations, Readme, Scripts, etc. (voir projects/ia_dev/docs/GITEA_ISSUES_SCRIPTS_AGENTS.md pour la correspondance complète). * Pages wiki : Api, Architecture, Code-Standards, Deployment, Operations, Readme, Scripts, etc. (voir projects/ia_dev/docs/GIT_ISSUES_SCRIPTS_AGENTS.md pour la correspondance complète).
* docs/sources/* * docs/sources/*
* docs/fixKnowledge/* * docs/fixKnowledge/*
* docs/features/* * docs/features/*

View File

@ -1,6 +1,6 @@
--- ---
name: evol name: evol
description: En charge des évolutions. Implémente les évolutions, documente les spécifications dans le wiki (docs/ puis ./gitea-issues/wiki-migrate-docs.sh), prépare le commit puis lance push-by-script. Réponse structurée selon cloture-evolution.mdc. description: En charge des évolutions. Implémente les évolutions, documente les spécifications dans le wiki (docs/ puis ./git-issues/wiki-migrate-docs.sh), prépare le commit puis lance push-by-script. Réponse structurée selon cloture-evolution.mdc.
model: inherit model: inherit
is_background: false is_background: false
--- ---
@ -40,7 +40,7 @@ Tu es l'agent evol, en charge des **évolutions** (nouvelles fonctionnalités, a
## Principes ## Principes
- Implémenter l'évolution demandée en respectant l'architecture et les conventions du projet. - Implémenter l'évolution demandée en respectant l'architecture et les conventions du projet.
- Documenter les spécifications dans le wiki (mettre à jour le fichier correspondant dans docs/ puis exécuter `./gitea-issues/wiki-migrate-docs.sh`, ou éditer la page wiki concernée). **`docs/` est hors versionnement** : maintenir `docs/` localement, ne pas le supprimer, ne pas committer `docs/` ; toujours pousser vers le wiki après édition. **Avant d'exécuter wiki-migrate-docs.sh :** lire le script, présenter un résumé de ce qu'il fait, puis l'exécuter. - Documenter les spécifications dans le wiki (mettre à jour le fichier correspondant dans docs/ puis exécuter `./git-issues/wiki-migrate-docs.sh`, ou éditer la page wiki concernée). **`docs/` est hors versionnement** : maintenir `docs/` localement, ne pas le supprimer, ne pas committer `docs/` ; toujours pousser vers le wiki après édition. **Avant d'exécuter wiki-migrate-docs.sh :** lire le script, présenter un résumé de ce qu'il fait, puis l'exécuter.
## Workflow ## Workflow

View File

@ -58,7 +58,7 @@ Tu es l'agent fix-search, en charge des **investigations** en vue d'identifier l
## Livrables ## Livrables
- Synthèse d'investigation (symptôme, causes, root cause, preuves). - Synthèse d'investigation (symptôme, causes, root cause, preuves).
- Si des documents d'investigation ou de retour d'expérience doivent être créés ou complétés, les rédiger dans le wiki (page Operations ou autre) ou dans `docs/` puis exécuter `./gitea-issues/wiki-migrate-docs.sh`. **`docs/` est hors versionnement** : maintenir `docs/` localement, ne pas le supprimer, ne pas le committer ; toujours pousser vers le wiki après édition. **Avant d'exécuter wiki-migrate-docs.sh :** lire le script, présenter un résumé, puis l'exécuter. **Lecture/écriture limitée à la doc** (pas de modification du code ni des configs). - Si des documents d'investigation ou de retour d'expérience doivent être créés ou complétés, les rédiger dans le wiki (page Operations ou autre) ou dans `docs/` puis exécuter `./git-issues/wiki-migrate-docs.sh`. **`docs/` est hors versionnement** : maintenir `docs/` localement, ne pas le supprimer, ne pas le committer ; toujours pousser vers le wiki après édition. **Avant d'exécuter wiki-migrate-docs.sh :** lire le script, présenter un résumé, puis l'exécuter. **Lecture/écriture limitée à la doc** (pas de modification du code ni des configs).
- Préparer le commit avec le format défini dans `.smartIde/agents/push-by-script.md` (lignes 15-32) : - Préparer le commit avec le format défini dans `.smartIde/agents/push-by-script.md` (lignes 15-32) :
- Etat initial - Etat initial
- Motivation du changement - Motivation du changement

View File

@ -1,6 +1,6 @@
--- ---
name: fix name: fix
description: En charge des correctifs. Applique les corrections en priorisant la root cause, lance fix-search, vérifie récurrence et solutions globales. Documente dans le wiki (docs/ puis ./gitea-issues/wiki-migrate-docs.sh) et prépare le commit puis push-by-script. description: En charge des correctifs. Applique les corrections en priorisant la root cause, lance fix-search, vérifie récurrence et solutions globales. Documente dans le wiki (docs/ puis ./git-issues/wiki-migrate-docs.sh) et prépare le commit puis push-by-script.
model: inherit model: inherit
is_background: false is_background: false
--- ---
@ -53,7 +53,7 @@ Tu es l'agent fix, en charge des **correctifs** à partir d'un problème remont
- Ne jamais contourner, supprimer le contexte du problème, créer de régression fonctionnelle, mettre de résultat en dur ni écraser les cas ; gérer tous les cas explicitement. - Ne jamais contourner, supprimer le contexte du problème, créer de régression fonctionnelle, mettre de résultat en dur ni écraser les cas ; gérer tous les cas explicitement.
- Étendre la correction aux endroits similaires identifiés. - Étendre la correction aux endroits similaires identifiés.
- Proposer, si pertinent, des évolutions plus globales (architecture, mutualisation, centralisation). - Proposer, si pertinent, des évolutions plus globales (architecture, mutualisation, centralisation).
- **Documentation** : `docs/` est hors versionnement ; maintenir `docs/` localement, pousser vers le wiki avec `./gitea-issues/wiki-migrate-docs.sh`, ne pas committer `docs/`. - **Documentation** : `docs/` est hors versionnement ; maintenir `docs/` localement, pousser vers le wiki avec `./git-issues/wiki-migrate-docs.sh`, ne pas committer `docs/`.
- **En cas de code à produire**, appliquer intégralement les règles de `.smartIde/agents/code.md` (agent commande /code). - **En cas de code à produire**, appliquer intégralement les règles de `.smartIde/agents/code.md` (agent commande /code).
## Clôture complète obligatoire (tous les cas, sans exception) ## Clôture complète obligatoire (tous les cas, sans exception)

View File

@ -1,6 +1,6 @@
--- ---
name: gitea-issues-process name: git-issues-process
description: Traite les tickets Gitea (issues) en s'appuyant au maximum sur les scripts gitea-issues/. Liste les issues, crée une branche par issue, récupère le contenu via script, lance /fix ou /evol puis /push-by-script et optionnellement commente l'issue. Push direct uniquement ; ne jamais créer de pull request. description: Traite les tickets Gitea (issues) en s'appuyant au maximum sur les scripts git-issues/. Liste les issues, crée une branche par issue, récupère le contenu via script, lance /fix ou /evol puis /push-by-script et optionnellement commente l'issue. Push direct uniquement ; ne jamais créer de pull request.
model: inherit model: inherit
is_background: false is_background: false
--- ---
@ -18,7 +18,7 @@ En tant qu'agent, avant de solliciter l'ia, regarde ce que tu peux scripter (imp
- **Lint (obligatoire avant clôture)** : Sur le dépôt applicatif du projet (`repository_root` et `build_dirs` dans `projects/<id>/conf.json`), exécuter `npm run lint` (ou équivalent) pour **chaque** `build_dir` de la conf — **tout** le périmètre à chaque fois, pas seulement le sous-projet modifié dans la session (ex. tâche front : lancer aussi le lint sur les autres `build_dirs`). Compter **erreurs + warnings**. Si **N ≥ 1** : appliquer des corrections dans **ce** run jusqu'à traiter **au moins min(5, N)** diagnostics (donc **au moins 5** lorsque N ≥ 5 ; si N < 5, tout corriger jusqu'à 0). **Interdit** de s'exonérer par un lint déjà passé dans `pousse`/build **sans** changements ESLint dans le workspace, ou en reportant sur un **`/fix-lint` ultérieur** : les corrections (min. 5 quand N 5) font partie **du même run** que la clôture. Clôture : commandes, périmètres, **décompte avant/après**. Voir `.smartIde/rules/cloture-lint.mdc`, dont la section **Diagnostics préexistants / hors périmètre de la session** (correction obligatoire pour tout diagnostic du périmètre, y compris hors fichiers modifiés dans ce run ; **interdit** en clôture : « warning existant », « hors scope session », « préexistait »). - **Lint (obligatoire avant clôture)** : Sur le dépôt applicatif du projet (`repository_root` et `build_dirs` dans `projects/<id>/conf.json`), exécuter `npm run lint` (ou équivalent) pour **chaque** `build_dir` de la conf — **tout** le périmètre à chaque fois, pas seulement le sous-projet modifié dans la session (ex. tâche front : lancer aussi le lint sur les autres `build_dirs`). Compter **erreurs + warnings**. Si **N ≥ 1** : appliquer des corrections dans **ce** run jusqu'à traiter **au moins min(5, N)** diagnostics (donc **au moins 5** lorsque N ≥ 5 ; si N < 5, tout corriger jusqu'à 0). **Interdit** de s'exonérer par un lint déjà passé dans `pousse`/build **sans** changements ESLint dans le workspace, ou en reportant sur un **`/fix-lint` ultérieur** : les corrections (min. 5 quand N 5) font partie **du même run** que la clôture. Clôture : commandes, périmètres, **décompte avant/après**. Voir `.smartIde/rules/cloture-lint.mdc`, dont la section **Diagnostics préexistants / hors périmètre de la session** (correction obligatoire pour tout diagnostic du périmètre, y compris hors fichiers modifiés dans ce run ; **interdit** en clôture : « warning existant », « hors scope session », « préexistait »).
# Agent gitea-issues-process # Agent git-issues-process
## Règle d'exécution intégrale (obligatoire, non négociable) ## Règle d'exécution intégrale (obligatoire, non négociable)
@ -29,50 +29,50 @@ En tant qu'agent, avant de solliciter l'ia, regarde ce que tu peux scripter (imp
--- ---
**Documentation** : La doc des projets gérés est dans **`projects/<id>/docs`** ; la doc ia_dev (scripts gitea-issues, spooler) est dans **`projects/ia_dev/docs`**. **Documentation** : La doc des projets gérés est dans **`projects/<id>/docs`** ; la doc ia_dev (scripts git-issues, spooler) est dans **`projects/ia_dev/docs`**.
**À lire en début d'exécution** (documentation fournie à l'agent) : **À lire en début d'exécution** (documentation fournie à l'agent) :
- projects/ia_dev/docs/GITEA_ISSUES_SCRIPTS_AGENTS.md (contexte d'exécution, scripts) - projects/ia_dev/docs/GIT_ISSUES_SCRIPTS_AGENTS.md (contexte d'exécution, scripts)
- .smartIde/agents/agent-loop.md (fichier témoin, variables, boucles) - .smartIde/agents/agent-loop.md (fichier témoin, variables, boucles)
- projects/ia_dev/docs/TICKETS_SPOOL_FORMAT.md (format JSON du spooler projects/<id>/data/issues/, schémas incoming/response, pièces jointes) - projects/ia_dev/docs/TICKETS_SPOOL_FORMAT.md (format JSON du spooler projects/<id>/data/issues/, schémas incoming/response, pièces jointes)
**Contexte projet :** La configuration et la documentation du projet sont dans `projects/<id>/`. L'identifiant `<id>` est résolu **uniquement** par **MAIL_TO** (adresse « to » des mails) ou **AI_AGENT_TOKEN** (token des requêtes) ; pas de fallback. Voir `docs/repo/ia-dev-project-conf-schema.md`. Rappeler en début d'exécution : projet = id résolu par MAIL_TO ou AI_AGENT_TOKEN, config = `ia_dev/projects/<id>/`. Ne pas hardcoder de chemin projet. **Contexte projet :** La configuration et la documentation du projet sont dans `projects/<id>/`. L'identifiant `<id>` est résolu **uniquement** par **MAIL_TO** (adresse « to » des mails) ou **AI_AGENT_TOKEN** (token des requêtes) ; pas de fallback. Voir `docs/repo/ia-dev-project-conf-schema.md`. Rappeler en début d'exécution : projet = id résolu par MAIL_TO ou AI_AGENT_TOKEN, config = `ia_dev/projects/<id>/`. Ne pas hardcoder de chemin projet.
Tu es l'agent qui traite les **tickets (issues) Gitea** du dépôt du projet courant. Le dépôt et l'URL Gitea (ticketing, wiki) sont définis dans `projects/<id>/conf.json` (clé `tickets.ticketing_url`, `git.wiki_url`). Toute la logique d'appel API et Git doit passer par les **scripts** du dossier `gitea-issues/` ; l'agent ne fait pas d'appels curl ou git directs pour ces opérations. Tu es l'agent qui traite les **tickets (issues) Gitea** du dépôt du projet courant. Le dépôt et l'URL Gitea (ticketing, wiki) sont définis dans `projects/<id>/conf.json` (clé `tickets.ticketing_url`, `git.wiki_url`). Toute la logique d'appel API et Git doit passer par les **scripts** du dossier `git-issues/` ; l'agent ne fait pas d'appels curl ou git directs pour ces opérations.
**Horodatage et contexte** : au début et à la fin d'exécution, afficher date/heure, **projet** (id), **branche** et **répertoire de travail** du dépôt concerné. **Horodatage et contexte** : au début et à la fin d'exécution, afficher date/heure, **projet** (id), **branche** et **répertoire de travail** du dépôt concerné.
**Avant d'exécuter un script du projet :** **Avant d'exécuter un script du projet :**
1. Lire le fichier du script avec l'outil de lecture (chaque script `gitea-issues/*.sh` avant de l'appeler). 1. Lire le fichier du script avec l'outil de lecture (chaque script `git-issues/*.sh` avant de l'appeler).
2. Présenter à l'utilisateur un résumé de ce que le script va faire : étapes principales, options/arguments, effets attendus. 2. Présenter à l'utilisateur un résumé de ce que le script va faire : étapes principales, options/arguments, effets attendus.
3. Lancer le script uniquement après cette présentation. 3. Lancer le script uniquement après cette présentation.
## Prérequis ## Prérequis
- `GITEA_TOKEN` défini ou fichier `.secrets/gitea-issues/token` présent. - `GITEA_TOKEN` défini ou fichier `.secrets/git-issues/token` présent.
- Exécution (standalone) depuis la **racine de ia_dev** : `./gitea-issues/*.sh`. Le projet est résolu par MAIL_TO ou AI_AGENT_TOKEN (voir `docs/repo/ia-dev-project-conf-schema.md`). - Exécution (standalone) depuis la **racine de ia_dev** : `./git-issues/*.sh`. Le projet est résolu par MAIL_TO ou AI_AGENT_TOKEN (voir `docs/repo/ia-dev-project-conf-schema.md`).
- Dépendances : `jq`, `curl` (les scripts les utilisent). - Dépendances : `jq`, `curl` (les scripts les utilisent).
**Contexte** : `gitea-issues/` est dans ia_dev ; l'id projet est résolu par MAIL_TO ou AI_AGENT_TOKEN ; la config est dans `ia_dev/projects/<id>/`. `.secrets` est sous ia_dev (`./.secrets`). Voir `projects/ia_dev/docs/GITEA_ISSUES_SCRIPTS_AGENTS.md` (Contexte d'exécution). **Contexte** : `git-issues/` est dans ia_dev ; l'id projet est résolu par MAIL_TO ou AI_AGENT_TOKEN ; la config est dans `ia_dev/projects/<id>/`. `.secrets` est sous ia_dev (`./.secrets`). Voir `projects/ia_dev/docs/GIT_ISSUES_SCRIPTS_AGENTS.md` (Contexte d'exécution).
## Workflow (script au maximum) ## Workflow (script au maximum)
1. **Lister les issues** 1. **Lister les issues**
Exécuter depuis la racine de ia_dev : `./gitea-issues/list-open-issues.sh --lines`. Si l'utilisateur a fourni un numéro d'issue précis, traiter uniquement cette issue. Exécuter depuis la racine de ia_dev : `./git-issues/list-open-issues.sh --lines`. Si l'utilisateur a fourni un numéro d'issue précis, traiter uniquement cette issue.
2. **Pour chaque issue à traiter** (ou la seule ciblée) : 2. **Pour chaque issue à traiter** (ou la seule ciblée) :
- **Créer la branche** : `Depuis la racine de ia_dev (MAIL_TO ou AI_AGENT_TOKEN défini) : ./gitea-issues/create-branch-for-issue.sh <issue_number> [base]` (base par défaut : `test`). Ne pas inventer de commande git ; utiliser uniquement ce script. - **Créer la branche** : `Depuis la racine de ia_dev (MAIL_TO ou AI_AGENT_TOKEN défini) : ./git-issues/create-branch-for-issue.sh <issue_number> [base]` (base par défaut : `test`). Ne pas inventer de commande git ; utiliser uniquement ce script.
- **Récupérer le contenu du ticket** : `Depuis la racine de ia_dev (MAIL_TO ou AI_AGENT_TOKEN défini) : ./gitea-issues/print-issue-prompt.sh <issue_number>` et utiliser la sortie comme **consigne** pour l'étape suivante. - **Récupérer le contenu du ticket** : `Depuis la racine de ia_dev (MAIL_TO ou AI_AGENT_TOKEN défini) : ./git-issues/print-issue-prompt.sh <issue_number>` et utiliser la sortie comme **consigne** pour l'étape suivante.
- **Choisir fix ou evol** : selon les labels ou le titre/corps de l'issue (bug, correctif → /fix ; évolution, feature → /evol). En cas de doute, privilégier /evol. - **Choisir fix ou evol** : selon les labels ou le titre/corps de l'issue (bug, correctif → /fix ; évolution, feature → /evol). En cas de doute, privilégier /evol.
- **Traiter le ticket** : lancer et exécuter **intégralement** l'agent **/fix** ou **/evol** en lui fournissant comme demande le contenu issu de `print-issue-prompt.sh` (titre + corps de l'issue). - **Traiter le ticket** : lancer et exécuter **intégralement** l'agent **/fix** ou **/evol** en lui fournissant comme demande le contenu issu de `print-issue-prompt.sh` (titre + corps de l'issue).
- **Pousser** : après succès de fix/evol, lancer et exécuter **intégralement** l'agent **/push-by-script** (message de commit conforme au projet). Push direct sur la branche ; ne jamais créer de pull request. - **Pousser** : après succès de fix/evol, lancer et exécuter **intégralement** l'agent **/push-by-script** (message de commit conforme au projet). Push direct sur la branche ; ne jamais créer de pull request.
- **Commenter l'issue (optionnel)** : exécuter `Depuis la racine de ia_dev (MAIL_TO ou AI_AGENT_TOKEN défini) : ./gitea-issues/comment-issue.sh <issue_number> "Traitement effectué dans la branche issue/<num>. Commit poussé."` (ou message adapté). - **Commenter l'issue (optionnel)** : exécuter `Depuis la racine de ia_dev (MAIL_TO ou AI_AGENT_TOKEN défini) : ./git-issues/comment-issue.sh <issue_number> "Traitement effectué dans la branche issue/<num>. Commit poussé."` (ou message adapté).
3. **Boucle** : répéter l'étape 2 pour chaque issue de la liste (ou une seule si numéro fourni). Ne pas traiter en parallèle : une issue après l'autre. 3. **Boucle** : répéter l'étape 2 pour chaque issue de la liste (ou une seule si numéro fourni). Ne pas traiter en parallèle : une issue après l'autre.
## Workflow mails (interaction agent ↔ scripts) ## Workflow mails (interaction agent ↔ scripts)
L'agent **ne fait pas** d'appels IMAP/SMTP ni de curl Gitea directs : il **invoque uniquement** les scripts `gitea-issues/*.sh` depuis la racine de ia_dev. Les scripts lisent la config (`.secrets` à la racine de ia_dev) et font les appels réels. L'agent **ne fait pas** d'appels IMAP/SMTP ni de curl Gitea directs : il **invoque uniquement** les scripts `git-issues/*.sh` depuis la racine de ia_dev. Les scripts lisent la config (`.secrets` à la racine de ia_dev) et font les appels réels.
**Ordre pour traiter les mails en attente** : deux sources possibles. **Aucun enregistrement ne doit être supprimé.** **Ordre pour traiter les mails en attente** : deux sources possibles. **Aucun enregistrement ne doit être supprimé.**
@ -80,22 +80,22 @@ L'agent **ne fait pas** d'appels IMAP/SMTP ni de curl Gitea directs : il **invoq
**A. Spooler data/issues (prioritaire)** — Un seul fichier par message (`<base>.pending`). Le statut est dans le JSON (`status`: `pending` | `responded`). À traiter = fichiers dont `status` est `pending`. **A. Spooler data/issues (prioritaire)** — Un seul fichier par message (`<base>.pending`). Le statut est dans le JSON (`status`: `pending` | `responded`). À traiter = fichiers dont `status` est `pending`.
- **Lister les mails à traiter** : exécuter `./gitea-issues/list-pending-spooler.sh`. Sortie = chemins des fichiers `projects/<id>/data/issues/<date>.<id>.<from>.pending` pour lesquels `status == "pending"`. Ne traiter **que** ces fichiers. - **Lister les mails à traiter** : exécuter `./git-issues/list-pending-spooler.sh`. Sortie = chemins des fichiers `projects/<id>/data/issues/<date>.<id>.<from>.pending` pour lesquels `status == "pending"`. Ne traiter **que** ces fichiers.
- **Pour chaque fichier listé** : lire le JSON (from, to, subject, body, message_id, uid, id, etc.). Le **base** est le nom du fichier sans `.pending` (ex. `2026-03-14T094530.a1b2c3d4.user_example.com`). Répondre uniquement si pertinent (demande d'info, évolution, etc.). Rédiger une **réponse pertinente** (composée par toi, uniquement ton texte ; pas de citation du mail reçu). Appeler `mail-send-reply.sh --to <from> --subject "Re: ..." --body "<ta réponse>" --in-reply-to "<message_id du JSON>"`. **Ne pas appeler** `mail-mark-read.sh` (inutile avec le spooler). Après envoi réussi : appeler `./gitea-issues/write-response-spooler.sh --base <base> --to <from> --subject "Re: ..." --body "<ta réponse>" --in-reply-to "<message_id>"`. Le script met à jour le **même** fichier (ajout de `response`, `status` = `responded`). Optionnel : `mail-thread-log.sh append-sent` pour tracer. - **Pour chaque fichier listé** : lire le JSON (from, to, subject, body, message_id, uid, id, etc.). Le **base** est le nom du fichier sans `.pending` (ex. `2026-03-14T094530.a1b2c3d4.user_example.com`). Répondre uniquement si pertinent (demande d'info, évolution, etc.). Rédiger une **réponse pertinente** (composée par toi, uniquement ton texte ; pas de citation du mail reçu). Appeler `mail-send-reply.sh --to <from> --subject "Re: ..." --body "<ta réponse>" --in-reply-to "<message_id du JSON>"`. **Ne pas appeler** `mail-mark-read.sh` (inutile avec le spooler). Après envoi réussi : appeler `./git-issues/write-response-spooler.sh --base <base> --to <from> --subject "Re: ..." --body "<ta réponse>" --in-reply-to "<message_id>"`. Le script met à jour le **même** fichier (ajout de `response`, `status` = `responded`). Optionnel : `mail-thread-log.sh append-sent` pour tracer.
**B. Legacy agent-loop.pending** — Mails « non lus » listés par `mail-list-unread.sh` (également limités à partir du 10 mars 2026 / `MAIL_SINCE_DATE`). **B. Legacy agent-loop.pending** — Mails « non lus » listés par `mail-list-unread.sh` (également limités à partir du 10 mars 2026 / `MAIL_SINCE_DATE`).
- **Lister** : exécuter `./gitea-issues/mail-list-unread.sh`. Pour chaque UID listé : `mail-get-thread.sh <uid>`, `mail-thread-log.sh init --uid <uid>`, rédiger réponse, `mail-send-reply.sh`, **puis** `mail-mark-read.sh <uid>` uniquement après succès de l'envoi, puis `mail-thread-log.sh append-sent`. - **Lister** : exécuter `./git-issues/mail-list-unread.sh`. Pour chaque UID listé : `mail-get-thread.sh <uid>`, `mail-thread-log.sh init --uid <uid>`, rédiger réponse, `mail-send-reply.sh`, **puis** `mail-mark-read.sh <uid>` uniquement après succès de l'envoi, puis `mail-thread-log.sh append-sent`.
**Réponses mail (obligatoire)** : le `--body` est **uniquement** le texte que tu rédiges (réponse pertinente, adaptée au contenu du mail). Le script nenvoie que ce corps plus la signature ; aucun autre contenu nest ajouté. Ne jamais recopier le mail reçu, le sujet, un bloc type client mail ou une citation dans le body. **Réponses mail (obligatoire)** : le `--body` est **uniquement** le texte que tu rédiges (réponse pertinente, adaptée au contenu du mail). Le script nenvoie que ce corps plus la signature ; aucun autre contenu nest ajouté. Ne jamais recopier le mail reçu, le sujet, un bloc type client mail ou une citation dans le body.
**Récupération mails (spooler data/issues)** : exécuter `./gitea-issues/tickets-fetch-inbox.sh` depuis la racine de ia_dev. C'est le **script** (et le Python qu'il appelle) qui **récupère** les mails et les **filtre** (to, from, `tickets.authorized_emails`, date) ; l'agent se contente de lancer le script. Le script écrit les mails retenus en `projects/<id>/data/issues/*.pending` (JSON). Mails à partir du 10 mars 2026 (ou `MAIL_SINCE_DATE`). Pas de marquage lu/non lu. **Boucle legacy (non lu)** : si l'utilisateur demande « Lance la boucle récupération emails… N itérations », exécuter `./gitea-issues/agent-loop-chat-iterations.sh [N] [--repeat]`. Mails en attente : **projects/<id>/data/issues/*.pending** (prioritaire) ou `projects/<id>/logs/gitea-issues/agent-loop.pending` ; les traiter selon le workflow ci-dessus. **Récupération mails (spooler data/issues)** : exécuter `./git-issues/tickets-fetch-inbox.sh` depuis la racine de ia_dev. C'est le **script** (et le Python qu'il appelle) qui **récupère** les mails et les **filtre** (to, from, `tickets.authorized_emails`, date) ; l'agent se contente de lancer le script. Le script écrit les mails retenus en `projects/<id>/data/issues/*.pending` (JSON). Mails à partir du 10 mars 2026 (ou `MAIL_SINCE_DATE`). Pas de marquage lu/non lu. **Boucle legacy (non lu)** : si l'utilisateur demande « Lance la boucle récupération emails… N itérations », exécuter `./git-issues/agent-loop-chat-iterations.sh [N] [--repeat]`. Mails en attente : **projects/<id>/data/issues/*.pending** (prioritaire) ou `projects/<id>/logs/git-issues/agent-loop.pending` ; les traiter selon le workflow ci-dessus.
**Pièces jointes (spooler data/issues)** : chaque fichier `projects/<id>/data/issues/*.pending` est un JSON pouvant contenir un tableau `attachments` (champs `filename`, `path`, `content_type`, `size`). Les fichiers sont stockés sous `projects/<id>/data/issues/<base>.d/` (`<base>` = `<date>.<id>.<from>`) ; `path` est relatif à `data/issues/`. Pour utiliser une pièce jointe, lire le fichier à `ia_dev/projects/<id>/data/issues/<path>`. Les utiliser pour traiter le ticket (analyse, création dissue avec référence au fichier, etc.) sans les supprimer. **Pièces jointes (spooler data/issues)** : chaque fichier `projects/<id>/data/issues/*.pending` est un JSON pouvant contenir un tableau `attachments` (champs `filename`, `path`, `content_type`, `size`). Les fichiers sont stockés sous `projects/<id>/data/issues/<base>.d/` (`<base>` = `<date>.<id>.<from>`) ; `path` est relatif à `data/issues/`. Pour utiliser une pièce jointe, lire le fichier à `ia_dev/projects/<id>/data/issues/<path>`. Les utiliser pour traiter le ticket (analyse, création dissue avec référence au fichier, etc.) sans les supprimer.
## Contraintes ## Contraintes
- Ne pas appeler l'API Gitea ni exécuter des commandes git pour les issues en dehors des scripts `gitea-issues/*.sh`. - Ne pas appeler l'API Gitea ni exécuter des commandes git pour les issues en dehors des scripts `git-issues/*.sh`.
- En cas d'échec d'un script (code de sortie non nul), rapporter l'erreur et s'arrêter pour cette issue sans masquer la sortie. - En cas d'échec d'un script (code de sortie non nul), rapporter l'erreur et s'arrêter pour cette issue sans masquer la sortie.
- Les agents /fix et /evol appliquent la clôture complète (cloture-evolution.mdc) ; ne pas court-circuiter leur workflow. - Les agents /fix et /evol appliquent la clôture complète (cloture-evolution.mdc) ; ne pas court-circuiter leur workflow.

View File

@ -12,7 +12,7 @@ model: inherit
**Contexte projet :** Les agents sont définis et lancés depuis ia_dev (centralisé) mais sont **dédiés aux projets configurés** (lecoffreio, enso, algo, etc.), pas à ia_dev. La configuration et la documentation de chaque projet sont dans `projects/<id>/`. L'identifiant `<id>` est résolu par MAIL_TO ou AI_AGENT_TOKEN. Rappeler le projet et la branche au début de chaque agent. **Contexte projet :** Les agents sont définis et lancés depuis ia_dev (centralisé) mais sont **dédiés aux projets configurés** (lecoffreio, enso, algo, etc.), pas à ia_dev. La configuration et la documentation de chaque projet sont dans `projects/<id>/`. L'identifiant `<id>` est résolu par MAIL_TO ou AI_AGENT_TOKEN. Rappeler le projet et la branche au début de chaque agent.
**Répertoire d'exécution des scripts (standalone) :** Racine de ia_dev. Tous les scripts `deploy/` et `gitea-issues/` doivent être invoqués depuis la racine de ia_dev, ex. `./deploy/pousse.sh`, `./gitea-issues/wiki-migrate-docs.sh`. Les chemins absolus dans `conf.json` pointent vers les dépôts des projets. **Répertoire d'exécution des scripts (standalone) :** Racine de ia_dev. Tous les scripts `deploy/` et `git-issues/` doivent être invoqués depuis la racine de ia_dev, ex. `./deploy/pousse.sh`, `./git-issues/wiki-migrate-docs.sh`. Les chemins absolus dans `conf.json` pointent vers les dépôts des projets.
**Référence unique** : le détail de l'horodatage et des étapes 1 à 17 est défini uniquement ici. Les agents appliquent ce fichier intégralement et ne recopient pas les blocs détaillés. **Référence unique** : le détail de l'horodatage et des étapes 1 à 17 est défini uniquement ici. Les agents appliquent ce fichier intégralement et ne recopient pas les blocs détaillés.

View File

@ -8,7 +8,7 @@ model: inherit
**Contexte projet :** Les agents sont **définis et lancés depuis ia_dev** (code et définitions centralisés dans ce dépôt) mais sont **dédiés aux projets configurés** (lecoffreio, enso, algo, etc.) : ils opèrent sur ces projets, pas sur ia_dev. La configuration et la documentation de chaque projet sont dans `projects/<id>/`. L'identifiant `<id>` est résolu par MAIL_TO ou AI_AGENT_TOKEN. Rappeler le projet et la branche en début et en fin d'exécution de chaque agent. **Contexte projet :** Les agents sont **définis et lancés depuis ia_dev** (code et définitions centralisés dans ce dépôt) mais sont **dédiés aux projets configurés** (lecoffreio, enso, algo, etc.) : ils opèrent sur ces projets, pas sur ia_dev. La configuration et la documentation de chaque projet sont dans `projects/<id>/`. L'identifiant `<id>` est résolu par MAIL_TO ou AI_AGENT_TOKEN. Rappeler le projet et la branche en début et en fin d'exécution de chaque agent.
**Répertoire d'exécution des scripts (standalone) :** Les scripts `deploy/` et `gitea-issues/` s'exécutent depuis la **racine de ia_dev**. Ils déploient ou traitent les **projets configurés** (chemins absolus dans `projects/<id>/conf.json`), pas ia_dev. Invoquer depuis la racine de ia_dev, ex. : `./deploy/pousse.sh`, `./gitea-issues/wiki-migrate-docs.sh`. **Répertoire d'exécution des scripts (standalone) :** Les scripts `deploy/` et `git-issues/` s'exécutent depuis la **racine de ia_dev**. Ils déploient ou traitent les **projets configurés** (chemins absolus dans `projects/<id>/conf.json`), pas ia_dev. Invoquer depuis la racine de ia_dev, ex. : `./deploy/pousse.sh`, `./git-issues/wiki-migrate-docs.sh`.
## Communication et langues ## Communication et langues

View File

@ -4,10 +4,10 @@
# Runs in foreground (no background); chat can run it for a few iterations to avoid timeout. # Runs in foreground (no background); chat can run it for a few iterations to avoid timeout.
# #
# Usage: # Usage:
# ./gitea-issues/agent-loop-chat-iterations.sh [N] [--repeat] # ./git-issues/agent-loop-chat-iterations.sh [N] [--repeat]
# N = number of iterations (default 3). Each iteration: mail-list-unread.sh then sleep 60. # N = number of iterations (default 3). Each iteration: mail-list-unread.sh then sleep 60.
# --repeat = after N iterations, relaunch (infinite loop of N-by-N runs). # --repeat = after N iterations, relaunch (infinite loop of N-by-N runs).
# Output and mail list (expéditeur, sujet) are appended to projects/<id>/logs/gitea-issues/agent-loop-chat-iterations.log. # Output and mail list (expéditeur, sujet) are appended to projects/<id>/logs/git-issues/agent-loop-chat-iterations.log.
# #
set -euo pipefail set -euo pipefail
if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
@ -18,14 +18,14 @@ if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
fi fi
[ -n "${HOME:-}" ] && [ -d "$HOME/.local/bin" ] && export PATH="$HOME/.local/bin:$PATH" [ -n "${HOME:-}" ] && [ -d "$HOME/.local/bin" ] && export PATH="$HOME/.local/bin:$PATH"
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
ROOT="$(cd "${GITEA_ISSUES_DIR}/../.." && pwd)" ROOT="$(cd "${GIT_ISSUES_DIR}/../.." && pwd)"
export GITEA_ISSUES_DIR export GIT_ISSUES_DIR
export REPO_ROOT="${ROOT}" export REPO_ROOT="${ROOT}"
cd "$ROOT" cd "$ROOT"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" 2>/dev/null || true source "${GIT_ISSUES_DIR}/lib.sh" 2>/dev/null || true
LOGS_GITEA="${PROJECT_LOGS_DIR:-$ROOT/logs}/gitea-issues" LOGS_GIT_ISSUES="${PROJECT_LOGS_DIR:-$ROOT/logs}/git-issues"
REPEAT="" REPEAT=""
if [ "${1:-}" = "--repeat" ]; then if [ "${1:-}" = "--repeat" ]; then
@ -39,7 +39,7 @@ if ! [[ "$N" =~ ^[0-9]+$ ]] || [ "$N" -lt 1 ]; then
exit 1 exit 1
fi fi
LOG_DIR="${LOGS_GITEA}" LOG_DIR="${LOGS_GIT_ISSUES}"
LOG_FILE="${LOG_DIR}/agent-loop-chat-iterations.log" LOG_FILE="${LOG_DIR}/agent-loop-chat-iterations.log"
mkdir -p "$LOG_DIR" mkdir -p "$LOG_DIR"
# Ensure absolute path so logs are always in the same place # Ensure absolute path so logs are always in the same place
@ -53,7 +53,7 @@ log_and_echo() {
log_and_echo "[agent-loop-chat] $(date -Iseconds) — log file: $LOG_FILE" log_and_echo "[agent-loop-chat] $(date -Iseconds) — log file: $LOG_FILE"
# Test send at launch: one test email to nicolas.cantu@pm.me # Test send at launch: one test email to nicolas.cantu@pm.me
log_and_echo "[agent-loop-chat] $(date -Iseconds) — test d'envoi vers nicolas.cantu@pm.me" log_and_echo "[agent-loop-chat] $(date -Iseconds) — test d'envoi vers nicolas.cantu@pm.me"
"${GITEA_ISSUES_DIR}/mail-send-reply.sh" --to "nicolas.cantu@pm.me" --subject "Test envoi - agent-loop-chat $(date +%Y-%m-%dT%H:%M:%S)" --body "Mail de test envoyé au lancement de agent-loop-chat-iterations.sh." 2>&1 | tee -a "$LOG_FILE" "${GIT_ISSUES_DIR}/mail-send-reply.sh" --to "nicolas.cantu@pm.me" --subject "Test envoi - agent-loop-chat $(date +%Y-%m-%dT%H:%M:%S)" --body "Mail de test envoyé au lancement de agent-loop-chat-iterations.sh." 2>&1 | tee -a "$LOG_FILE"
if [ "${PIPESTATUS[0]:-0}" -eq 0 ]; then if [ "${PIPESTATUS[0]:-0}" -eq 0 ]; then
log_and_echo "[agent-loop-chat] $(date -Iseconds) — test d'envoi OK" log_and_echo "[agent-loop-chat] $(date -Iseconds) — test d'envoi OK"
else else
@ -64,7 +64,7 @@ fi
run_iterations() { run_iterations() {
for i in $(seq 1 "$N"); do for i in $(seq 1 "$N"); do
log_and_echo "[agent-loop-chat] $(date -Iseconds) — iteration $i/$N" log_and_echo "[agent-loop-chat] $(date -Iseconds) — iteration $i/$N"
"${GITEA_ISSUES_DIR}/mail-list-unread.sh" 2>&1 | tee -a "$LOG_FILE" || true "${GIT_ISSUES_DIR}/mail-list-unread.sh" 2>&1 | tee -a "$LOG_FILE" || true
if [ "$i" -lt "$N" ]; then if [ "$i" -lt "$N" ]; then
log_and_echo "[agent-loop-chat] $(date -Iseconds) — attente 60 s avant prochaine itération" log_and_echo "[agent-loop-chat] $(date -Iseconds) — attente 60 s avant prochaine itération"
sleep 60 sleep 60

View File

@ -3,7 +3,7 @@
# Run from repo root. Used by agent-loop agent before starting x cycles (section 2). # Run from repo root. Used by agent-loop agent before starting x cycles (section 2).
# #
# Usage: # Usage:
# Depuis la racine de ia_dev : ./gitea-issues/agent-loop-lock-acquire.sh # Depuis la racine de ia_dev : ./git-issues/agent-loop-lock-acquire.sh
# #
set -euo pipefail set -euo pipefail
if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
@ -13,16 +13,16 @@ if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
set -u set -u
fi fi
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
ROOT="$(cd "${GITEA_ISSUES_DIR}/../.." && pwd)" ROOT="$(cd "${GIT_ISSUES_DIR}/../.." && pwd)"
export GITEA_ISSUES_DIR export GIT_ISSUES_DIR
export REPO_ROOT="${ROOT}" export REPO_ROOT="${ROOT}"
cd "$ROOT" cd "$ROOT"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" 2>/dev/null || true source "${GIT_ISSUES_DIR}/lib.sh" 2>/dev/null || true
LOGS_GITEA="${PROJECT_LOGS_DIR:-$ROOT/logs}/gitea-issues" LOGS_GIT_ISSUES="${PROJECT_LOGS_DIR:-$ROOT/logs}/git-issues"
LOCK_FILE="${AGENT_LOOP_LOCK_FILE:-$LOGS_GITEA/agent-loop.lock}" LOCK_FILE="${AGENT_LOOP_LOCK_FILE:-$LOGS_GIT_ISSUES/agent-loop.lock}"
STALE_SEC=$((24 * 3600)) # 24 hours STALE_SEC=$((24 * 3600)) # 24 hours
mkdir -p "$(dirname "$LOCK_FILE")" mkdir -p "$(dirname "$LOCK_FILE")"

View File

@ -3,7 +3,7 @@
# Run from repo root. # Run from repo root.
# #
# Usage: # Usage:
# Depuis la racine de ia_dev : ./gitea-issues/agent-loop-lock-release.sh # Depuis la racine de ia_dev : ./git-issues/agent-loop-lock-release.sh
# #
set -euo pipefail set -euo pipefail
if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
@ -13,17 +13,17 @@ if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
set -u set -u
fi fi
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
ROOT="$(cd "${GITEA_ISSUES_DIR}/../.." && pwd)" ROOT="$(cd "${GIT_ISSUES_DIR}/../.." && pwd)"
export GITEA_ISSUES_DIR export GIT_ISSUES_DIR
export REPO_ROOT="${ROOT}" export REPO_ROOT="${ROOT}"
cd "$ROOT" cd "$ROOT"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" 2>/dev/null || true source "${GIT_ISSUES_DIR}/lib.sh" 2>/dev/null || true
LOGS_GITEA="${PROJECT_LOGS_DIR:-$ROOT/logs}/gitea-issues" LOGS_GIT_ISSUES="${PROJECT_LOGS_DIR:-$ROOT/logs}/git-issues"
LOCK_FILE="${AGENT_LOOP_LOCK_FILE:-$LOGS_GITEA/agent-loop.lock}" LOCK_FILE="${AGENT_LOOP_LOCK_FILE:-$LOGS_GIT_ISSUES/agent-loop.lock}"
STOP_FILE="${AGENT_LOOP_STOP_FILE:-$LOGS_GITEA/agent-loop.stop}" STOP_FILE="${AGENT_LOOP_STOP_FILE:-$LOGS_GIT_ISSUES/agent-loop.stop}"
if [ -f "$LOCK_FILE" ]; then if [ -f "$LOCK_FILE" ]; then
rm -f "$LOCK_FILE" rm -f "$LOCK_FILE"

View File

@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Run N cycles of (retrieval + treatment via Cursor Agent CLI + sleep 60s) in foreground. # Run N cycles of (retrieval + treatment via Cursor Agent CLI + sleep 60s) in foreground.
# Uses same lock as section 2. When AGENT_LOOP_RUN_AGENT=1 and "agent" in PATH, runs # Uses same lock as section 2. When AGENT_LOOP_RUN_AGENT=1 and "agent" in PATH, runs
# gitea-issues-process workflow via CLI each cycle (same as agent-loop.sh). No contournement: # git-issues-process workflow via CLI each cycle (same as agent-loop.sh). No contournement:
# /agent-loop 600 = agent runs this script with N=600 and timeout ~11h; script does 600 full cycles. # /agent-loop 600 = agent runs this script with N=600 and timeout ~11h; script does 600 full cycles.
# #
# Usage: from ia_dev root: ./gitea-issues/agent-loop-n-cycles.sh [N] # Usage: from ia_dev root: ./git-issues/agent-loop-n-cycles.sh [N]
# N = number of cycles (default 600). Each cycle = retrieval + agent CLI (if configured) + 60s sleep. # N = number of cycles (default 600). Each cycle = retrieval + agent CLI (if configured) + 60s sleep.
# #
set -euo pipefail set -euo pipefail
@ -16,16 +16,16 @@ if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
fi fi
[ -n "${HOME:-}" ] && [ -d "$HOME/.local/bin" ] && export PATH="$HOME/.local/bin:$PATH" [ -n "${HOME:-}" ] && [ -d "$HOME/.local/bin" ] && export PATH="$HOME/.local/bin:$PATH"
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
ROOT="$(cd "${GITEA_ISSUES_DIR}/../.." && pwd)" ROOT="$(cd "${GIT_ISSUES_DIR}/../.." && pwd)"
IA_DEV_ROOT="${GITEA_ISSUES_DIR}/.." IA_DEV_ROOT="${GIT_ISSUES_DIR}/.."
export GITEA_ISSUES_DIR export GIT_ISSUES_DIR
export REPO_ROOT="${ROOT}" export REPO_ROOT="${ROOT}"
cd "$ROOT" cd "$ROOT"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" 2>/dev/null || true source "${GIT_ISSUES_DIR}/lib.sh" 2>/dev/null || true
AGENT_LOOP_ENV="${IA_DEV_ROOT}/.secrets/gitea-issues/agent-loop.env" AGENT_LOOP_ENV="${IA_DEV_ROOT}/.secrets/git-issues/agent-loop.env"
if [ -r "$AGENT_LOOP_ENV" ]; then if [ -r "$AGENT_LOOP_ENV" ]; then
set +u set +u
# shellcheck source=/dev/null # shellcheck source=/dev/null
@ -39,19 +39,19 @@ if ! [[ "$N" =~ ^[0-9]+$ ]] || [ "$N" -lt 1 ]; then
exit 1 exit 1
fi fi
"${GITEA_ISSUES_DIR}/agent-loop-lock-acquire.sh" || exit 1 "${GIT_ISSUES_DIR}/agent-loop-lock-acquire.sh" || exit 1
AGENT_PROMPT="Exécute le workflow mails du spooler (agent gitea-issues-process). 1) Lister les mails à traiter avec ./gitea-issues/list-pending-spooler.sh (ou utiliser les chemins dans agent-loop.pending). 2) Pour chaque fichier .pending : lire le JSON (from, to, subject, body, message_id, base). Répondre à l'expéditeur (--to <from> du JSON), pas à une adresse fixe ; le « to » du mail reçu a déjà déterminé le projet. Rédiger une réponse pertinente (uniquement ton texte, pas de citation ; mail-send-reply.sh refuse si le body contient From:, Message-ID, wrote:, etc.). Envoyer avec ./gitea-issues/mail-send-reply.sh --to <from> --subject \"Re: ...\" --body \"<ta_réponse>\" --in-reply-to \"<message_id>\". Après envoi réussi : ./gitea-issues/write-response-spooler.sh --base <base> --to <from> --subject \"Re: ...\" --body \"<ta_réponse>\" --in-reply-to \"<message_id>\". Ne pas appeler mail-mark-read.sh (spooler)." AGENT_PROMPT="Exécute le workflow mails du spooler (agent git-issues-process). 1) Lister les mails à traiter avec ./git-issues/list-pending-spooler.sh (ou utiliser les chemins dans agent-loop.pending). 2) Pour chaque fichier .pending : lire le JSON (from, to, subject, body, message_id, base). Répondre à l'expéditeur (--to <from> du JSON), pas à une adresse fixe ; le « to » du mail reçu a déjà déterminé le projet. Rédiger une réponse pertinente (uniquement ton texte, pas de citation ; mail-send-reply.sh refuse si le body contient From:, Message-ID, wrote:, etc.). Envoyer avec ./git-issues/mail-send-reply.sh --to <from> --subject \"Re: ...\" --body \"<ta_réponse>\" --in-reply-to \"<message_id>\". Après envoi réussi : ./git-issues/write-response-spooler.sh --base <base> --to <from> --subject \"Re: ...\" --body \"<ta_réponse>\" --in-reply-to \"<message_id>\". Ne pas appeler mail-mark-read.sh (spooler)."
i=1 i=1
while [ "$i" -le "$N" ]; do while [ "$i" -le "$N" ]; do
if "${GITEA_ISSUES_DIR}/agent-loop-stop-requested.sh" 2>/dev/null; then if "${GIT_ISSUES_DIR}/agent-loop-stop-requested.sh" 2>/dev/null; then
echo "[agent-loop-n-cycles] $(date -Iseconds) — Arrêt demandé (cycle $i/$N). Libération du lock." echo "[agent-loop-n-cycles] $(date -Iseconds) — Arrêt demandé (cycle $i/$N). Libération du lock."
"${GITEA_ISSUES_DIR}/agent-loop-lock-release.sh" "${GIT_ISSUES_DIR}/agent-loop-lock-release.sh"
exit 0 exit 0
fi fi
echo "[agent-loop-n-cycles] $(date -Iseconds) — Cycle $i/$N" echo "[agent-loop-n-cycles] $(date -Iseconds) — Cycle $i/$N"
"${GITEA_ISSUES_DIR}/agent-loop-retrieval-once.sh" || true "${GIT_ISSUES_DIR}/agent-loop-retrieval-once.sh" || true
if [ "${AGENT_LOOP_RUN_AGENT:-0}" = "1" ] && command -v agent >/dev/null 2>&1; then if [ "${AGENT_LOOP_RUN_AGENT:-0}" = "1" ] && command -v agent >/dev/null 2>&1; then
AGENT_MODEL="${AGENT_LOOP_MODEL:-sonnet-4.6}" AGENT_MODEL="${AGENT_LOOP_MODEL:-sonnet-4.6}"
@ -68,5 +68,5 @@ while [ "$i" -le "$N" ]; do
i=$((i + 1)) i=$((i + 1))
done done
"${GITEA_ISSUES_DIR}/agent-loop-lock-release.sh" "${GIT_ISSUES_DIR}/agent-loop-lock-release.sh"
echo "[agent-loop-n-cycles] $(date -Iseconds)$N cycles terminés." echo "[agent-loop-n-cycles] $(date -Iseconds)$N cycles terminés."

View File

@ -3,7 +3,7 @@
# Run from repo root. Used by agent-loop agent for "x times" cycles. Criterion: from/to in conf.json, not IMAP unread. # Run from repo root. Used by agent-loop agent for "x times" cycles. Criterion: from/to in conf.json, not IMAP unread.
# #
# Usage: # Usage:
# Depuis la racine de ia_dev : ./gitea-issues/agent-loop-retrieval-once.sh # Depuis la racine de ia_dev : ./git-issues/agent-loop-retrieval-once.sh
# #
set -euo pipefail set -euo pipefail
if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
@ -13,27 +13,27 @@ if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
set -u set -u
fi fi
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
ROOT="$(cd "${GITEA_ISSUES_DIR}/../.." && pwd)" ROOT="$(cd "${GIT_ISSUES_DIR}/../.." && pwd)"
export GITEA_ISSUES_DIR export GIT_ISSUES_DIR
export REPO_ROOT="${ROOT}" export REPO_ROOT="${ROOT}"
cd "$ROOT" cd "$ROOT"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" 2>/dev/null || true source "${GIT_ISSUES_DIR}/lib.sh" 2>/dev/null || true
# When no PROJECT_ID (no MAIL_TO/AI_AGENT_TOKEN), use generic ROOT/logs for status/pending file # When no PROJECT_ID (no MAIL_TO/AI_AGENT_TOKEN), use generic ROOT/logs for status/pending file
LOGS_GITEA="${PROJECT_LOGS_DIR:-$ROOT/logs}/gitea-issues" LOGS_GIT_ISSUES="${PROJECT_LOGS_DIR:-$ROOT/logs}/git-issues"
STATUS_FILE="${AGENT_LOOP_STATUS_FILE:-$LOGS_GITEA/agent-loop.status}" STATUS_FILE="${AGENT_LOOP_STATUS_FILE:-$LOGS_GIT_ISSUES/agent-loop.status}"
PENDING_FILE="${AGENT_LOOP_PENDING_FILE:-$LOGS_GITEA/agent-loop.pending}" PENDING_FILE="${AGENT_LOOP_PENDING_FILE:-$LOGS_GIT_ISSUES/agent-loop.pending}"
mkdir -p "$(dirname "$STATUS_FILE")" mkdir -p "$(dirname "$STATUS_FILE")"
write_status() { write_status() {
printf "%s\n%s\n%s\n" "$(date -Iseconds)" "$1" "${2:-}" > "$STATUS_FILE" printf "%s\n%s\n%s\n" "$(date -Iseconds)" "$1" "${2:-}" > "$STATUS_FILE"
} }
"${GITEA_ISSUES_DIR}/tickets-fetch-inbox.sh" 2>&1 || true "${GIT_ISSUES_DIR}/tickets-fetch-inbox.sh" 2>&1 || true
pending_out="" pending_out=""
pending_out=$("${GITEA_ISSUES_DIR}/list-pending-spooler.sh" 2>&1) || true pending_out=$("${GIT_ISSUES_DIR}/list-pending-spooler.sh" 2>&1) || true
if [ -n "$pending_out" ] && echo "$pending_out" | grep -q "\.pending"; then if [ -n "$pending_out" ] && echo "$pending_out" | grep -q "\.pending"; then
write_status "mails_pending" "One-shot: mails en attente dans le spooler (from/to)." write_status "mails_pending" "One-shot: mails en attente dans le spooler (from/to)."
printf "%s\n%s\n%s\n%s\n" "$(date -Iseconds)" "mails_pending" "---" "$pending_out" > "$PENDING_FILE" printf "%s\n%s\n%s\n%s\n" "$(date -Iseconds)" "mails_pending" "---" "$pending_out" > "$PENDING_FILE"

View File

@ -3,7 +3,7 @@
# Run from repo root. # Run from repo root.
# #
# Usage: # Usage:
# Depuis la racine de ia_dev : ./gitea-issues/agent-loop-stop-requested.sh # Depuis la racine de ia_dev : ./git-issues/agent-loop-stop-requested.sh
# #
set -euo pipefail set -euo pipefail
if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
@ -13,15 +13,15 @@ if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
set -u set -u
fi fi
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
ROOT="$(cd "${GITEA_ISSUES_DIR}/../.." && pwd)" ROOT="$(cd "${GIT_ISSUES_DIR}/../.." && pwd)"
export GITEA_ISSUES_DIR export GIT_ISSUES_DIR
export REPO_ROOT="${ROOT}" export REPO_ROOT="${ROOT}"
cd "$ROOT" cd "$ROOT"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" 2>/dev/null || true source "${GIT_ISSUES_DIR}/lib.sh" 2>/dev/null || true
LOGS_GITEA="${PROJECT_LOGS_DIR:-$ROOT/logs}/gitea-issues" LOGS_GIT_ISSUES="${PROJECT_LOGS_DIR:-$ROOT/logs}/git-issues"
STOP_FILE="${AGENT_LOOP_STOP_FILE:-$LOGS_GITEA/agent-loop.stop}" STOP_FILE="${AGENT_LOOP_STOP_FILE:-$LOGS_GIT_ISSUES/agent-loop.stop}"
[ -f "$STOP_FILE" ] [ -f "$STOP_FILE" ]
# exit 0 if file exists, 1 otherwise # exit 0 if file exists, 1 otherwise

View File

@ -1,9 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Create agent-loop.stop so the running agent-loop (section 2) stops at the next cycle start. # Create agent-loop.stop so the running agent-loop (section 2) stops at the next cycle start.
# Run from repo root. Same paths as agent-loop-retrieval-once.sh (projects/<id>/logs/gitea-issues). # Run from repo root. Same paths as agent-loop-retrieval-once.sh (projects/<id>/logs/git-issues).
# #
# Usage: # Usage:
# Depuis la racine de ia_dev : ./gitea-issues/agent-loop-stop.sh # Depuis la racine de ia_dev : ./git-issues/agent-loop-stop.sh
# #
set -euo pipefail set -euo pipefail
if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
@ -13,16 +13,16 @@ if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
set -u set -u
fi fi
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
ROOT="$(cd "${GITEA_ISSUES_DIR}/../.." && pwd)" ROOT="$(cd "${GIT_ISSUES_DIR}/../.." && pwd)"
export GITEA_ISSUES_DIR export GIT_ISSUES_DIR
export REPO_ROOT="${ROOT}" export REPO_ROOT="${ROOT}"
cd "$ROOT" cd "$ROOT"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" 2>/dev/null || true source "${GIT_ISSUES_DIR}/lib.sh" 2>/dev/null || true
LOGS_GITEA="${PROJECT_LOGS_DIR:-$ROOT/logs}/gitea-issues" LOGS_GIT_ISSUES="${PROJECT_LOGS_DIR:-$ROOT/logs}/git-issues"
STOP_FILE="${AGENT_LOOP_STOP_FILE:-$LOGS_GITEA/agent-loop.stop}" STOP_FILE="${AGENT_LOOP_STOP_FILE:-$LOGS_GIT_ISSUES/agent-loop.stop}"
mkdir -p "$(dirname "$STOP_FILE")" mkdir -p "$(dirname "$STOP_FILE")"
touch "$STOP_FILE" touch "$STOP_FILE"
echo "[agent-loop-stop] $(date -Iseconds)$STOP_FILE créé. La boucle en cours s'arrêtera au début du prochain cycle." echo "[agent-loop-stop] $(date -Iseconds)$STOP_FILE créé. La boucle en cours s'arrêtera au début du prochain cycle."

View File

@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Treatment loop: periodically check agent-loop.pending and run Cursor Agent CLI (git-issues-process workflow) when non-empty.
# Run from repo root. No timeout; runs forever. Do NOT start this script from the agent-loop agent (use bounded runs only).
#
# Usage (manual only; agent must not launch with nohup/&):
# Depuis la racine de ia_dev : ./git-issues/agent-loop-treatment.sh
#
set -euo pipefail
if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
set +u
# shellcheck source=/dev/null
source "$HOME/.bashrc" 2>/dev/null || true
set -u
fi
[ -n "${HOME:-}" ] && [ -d "$HOME/.local/bin" ] && export PATH="$HOME/.local/bin:$PATH"
GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || ROOT="$(cd "${GIT_ISSUES_DIR}/../.." && pwd)"
export GIT_ISSUES_DIR
export REPO_ROOT="${ROOT}"
cd "$ROOT"
# shellcheck source=lib.sh
source "${GIT_ISSUES_DIR}/lib.sh" 2>/dev/null || true
LOGS_GIT_ISSUES="${PROJECT_LOGS_DIR:-$ROOT/logs}/git-issues"
PENDING_FILE="${AGENT_LOOP_PENDING_FILE:-$LOGS_GIT_ISSUES/agent-loop.pending}"
LOG_DIR="$(dirname "$PENDING_FILE")"
mkdir -p "$LOG_DIR"
AGENT_LOOP_ENV="${GIT_ISSUES_DIR}/../.secrets/git-issues/agent-loop.env"
if [ -r "$AGENT_LOOP_ENV" ]; then
set +u
# shellcheck source=/dev/null
source "$AGENT_LOOP_ENV"
set -u
fi
INTERVAL="${AGENT_LOOP_TREATMENT_INTERVAL_SEC:-60}"
AGENT_MODEL="${AGENT_LOOP_MODEL:-sonnet-4.6}"
PROMPT="Exécute le workflow mails du spooler (agent git-issues-process). Les chemins des fichiers .pending sont dans projects/<id>/logs/git-issues/agent-loop.pending (ou exécuter ./git-issues/list-pending-spooler.sh). Pour chaque fichier .pending : lire le JSON (from, to, subject, body, message_id, base). Répondre à l'expéditeur (--to <from> du JSON), pas à une adresse fixe ; le « to » du mail reçu a déjà déterminé le projet. Rédiger une réponse pertinente (uniquement ton texte ; pas de citation — mail-send-reply.sh refuse si le body contient From:, Message-ID, wrote:, etc.). Envoyer avec ./git-issues/mail-send-reply.sh --to <from> --subject \"Re: ...\" --body \"<ta_réponse>\" --in-reply-to \"<message_id>\". Après envoi réussi : ./git-issues/write-response-spooler.sh --base <base> --to <from> --subject \"Re: ...\" --body \"<ta_réponse>\" --in-reply-to \"<message_id>\". Ne pas appeler mail-mark-read.sh (spooler)."
while true; do
if [ -s "$PENDING_FILE" ] && command -v agent >/dev/null 2>&1; then
echo "[agent-loop-treatment] $(date -Iseconds) — Pending non vide, lancement de l'agent Cursor."
if agent -p "$PROMPT" -f --model "$AGENT_MODEL" 2>&1; then
echo "[agent-loop-treatment] $(date -Iseconds) — Agent terminé."
else
echo "[agent-loop-treatment] $(date -Iseconds) — Agent terminé avec erreur."
fi
fi
sleep "$INTERVAL"
done

View File

@ -0,0 +1,19 @@
# Agent-loop parameters (Cursor Agent CLI, model, interval).
# Copy to .secrets/git-issues/agent-loop.env and set as needed.
# Do not commit .secrets/git-issues/agent-loop.env (directory is gitignored).
#
# Run Cursor Agent when unread mails are detected (0 or 1)
# AGENT_LOOP_RUN_AGENT=1
#
# Model used by the CLI (default: sonnet-4.6 to avoid Opus usage limits)
# List: agent models
# AGENT_LOOP_MODEL=sonnet-4.6
#
# Polling interval in seconds (default: 60)
# AGENT_LOOP_INTERVAL_SEC=60
#
# Optional: custom paths for status, pending, lock and stop files
# AGENT_LOOP_STATUS_FILE=ia_dev/projects/<id>/logs/git-issues/agent-loop.status
# AGENT_LOOP_PENDING_FILE=ia_dev/projects/<id>/logs/git-issues/agent-loop.pending
# AGENT_LOOP_LOCK_FILE=ia_dev/projects/<id>/logs/git-issues/agent-loop.lock
# AGENT_LOOP_STOP_FILE=ia_dev/projects/<id>/logs/git-issues/agent-loop.stop

View File

@ -3,12 +3,12 @@
# Run from repo root. Runs forever. Do NOT start from the agent-loop agent (use bounded runs only, e.g. agent-loop-chat-iterations.sh N). # Run from repo root. Runs forever. Do NOT start from the agent-loop agent (use bounded runs only, e.g. agent-loop-chat-iterations.sh N).
# #
# Usage (manual only; agent must not launch with nohup/&): # Usage (manual only; agent must not launch with nohup/&):
# ./gitea-issues/agent-loop.sh [interval_seconds] # ./git-issues/agent-loop.sh [interval_seconds]
# AGENT_LOOP_INTERVAL_SEC=120 ./gitea-issues/agent-loop.sh # AGENT_LOOP_INTERVAL_SEC=120 ./git-issues/agent-loop.sh
# #
# Witness file: projects/<id>/logs/gitea-issues/agent-loop.status (or AGENT_LOOP_STATUS_FILE) # Witness file: projects/<id>/logs/git-issues/agent-loop.status (or AGENT_LOOP_STATUS_FILE)
# State file (not a log): updated every iteration. If mtime is older than 2*interval, loop is considered stopped. # State file (not a log): updated every iteration. If mtime is older than 2*interval, loop is considered stopped.
# Pending file: projects/<id>/logs/gitea-issues/agent-loop.pending (or AGENT_LOOP_PENDING_FILE) # Pending file: projects/<id>/logs/git-issues/agent-loop.pending (or AGENT_LOOP_PENDING_FILE)
# Written when there are .pending mails in the spooler (no matching .response); contains paths to treat. # Written when there are .pending mails in the spooler (no matching .response); contains paths to treat.
# #
# Optional: set AGENT_LOOP_RUN_AGENT=1 to run the Cursor Agent CLI when mails are detected. # Optional: set AGENT_LOOP_RUN_AGENT=1 to run the Cursor Agent CLI when mails are detected.
@ -26,18 +26,18 @@ if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
fi fi
[ -n "${HOME:-}" ] && [ -d "$HOME/.local/bin" ] && export PATH="$HOME/.local/bin:$PATH" [ -n "${HOME:-}" ] && [ -d "$HOME/.local/bin" ] && export PATH="$HOME/.local/bin:$PATH"
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
ROOT="$(cd "${GITEA_ISSUES_DIR}/../.." && pwd)" ROOT="$(cd "${GIT_ISSUES_DIR}/../.." && pwd)"
export GITEA_ISSUES_DIR export GIT_ISSUES_DIR
export REPO_ROOT="${ROOT}" export REPO_ROOT="${ROOT}"
cd "$ROOT" cd "$ROOT"
# Per-project logs under projects/<id>/logs (lib.sh sets PROJECT_LOGS_DIR) # Per-project logs under projects/<id>/logs (lib.sh sets PROJECT_LOGS_DIR)
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" 2>/dev/null || true source "${GIT_ISSUES_DIR}/lib.sh" 2>/dev/null || true
LOGS_GITEA="${PROJECT_LOGS_DIR:-$ROOT/logs}/gitea-issues" LOGS_GIT_ISSUES="${PROJECT_LOGS_DIR:-$ROOT/logs}/git-issues"
# Load agent-loop parameters from .secrets (optional; .secrets under ia_dev) # Load agent-loop parameters from .secrets (optional; .secrets under ia_dev)
AGENT_LOOP_ENV="${GITEA_ISSUES_DIR}/../.secrets/gitea-issues/agent-loop.env" AGENT_LOOP_ENV="${GIT_ISSUES_DIR}/../.secrets/git-issues/agent-loop.env"
if [ -r "$AGENT_LOOP_ENV" ]; then if [ -r "$AGENT_LOOP_ENV" ]; then
set +u set +u
# shellcheck source=/dev/null # shellcheck source=/dev/null
@ -46,9 +46,9 @@ if [ -r "$AGENT_LOOP_ENV" ]; then
fi fi
INTERVAL="${1:-${AGENT_LOOP_INTERVAL_SEC:-60}}" INTERVAL="${1:-${AGENT_LOOP_INTERVAL_SEC:-60}}"
# STATUS_FILE: state/witness file (not a log) — indicates if the loop is active; under projects/<id>/logs/gitea-issues/ for per-project state. # STATUS_FILE: state/witness file (not a log) — indicates if the loop is active; under projects/<id>/logs/git-issues/ for per-project state.
STATUS_FILE="${AGENT_LOOP_STATUS_FILE:-$LOGS_GITEA/agent-loop.status}" STATUS_FILE="${AGENT_LOOP_STATUS_FILE:-$LOGS_GIT_ISSUES/agent-loop.status}"
PENDING_FILE="${AGENT_LOOP_PENDING_FILE:-$LOGS_GITEA/agent-loop.pending}" PENDING_FILE="${AGENT_LOOP_PENDING_FILE:-$LOGS_GIT_ISSUES/agent-loop.pending}"
mkdir -p "$(dirname "$STATUS_FILE")" mkdir -p "$(dirname "$STATUS_FILE")"
write_status() { write_status() {
@ -60,20 +60,20 @@ write_status() {
while true; do while true; do
write_status "running" "interval=${INTERVAL}s" write_status "running" "interval=${INTERVAL}s"
# Spooler flow: fetch by from/to (conf.json), then list .pending without .response. No IMAP unread criterion. # Spooler flow: fetch by from/to (conf.json), then list .pending without .response. No IMAP unread criterion.
"${GITEA_ISSUES_DIR}/tickets-fetch-inbox.sh" 2>&1 || true "${GIT_ISSUES_DIR}/tickets-fetch-inbox.sh" 2>&1 || true
pending_out="" pending_out=""
pending_out=$("${GITEA_ISSUES_DIR}/list-pending-spooler.sh" 2>&1) || true pending_out=$("${GIT_ISSUES_DIR}/list-pending-spooler.sh" 2>&1) || true
if [ -n "$pending_out" ] && echo "$pending_out" | grep -q "\.pending"; then if [ -n "$pending_out" ] && echo "$pending_out" | grep -q "\.pending"; then
write_status "mails_pending" "Mails en attente dans le spooler (from/to). Lancer l'agent gitea-issues-process dans Cursor." write_status "mails_pending" "Mails en attente dans le spooler (from/to). Lancer l'agent git-issues-process dans Cursor."
printf "%s\n%s\n%s\n%s\n" "$(date -Iseconds)" "mails_pending" "---" "$pending_out" > "$PENDING_FILE" printf "%s\n%s\n%s\n%s\n" "$(date -Iseconds)" "mails_pending" "---" "$pending_out" > "$PENDING_FILE"
n=$(echo "$pending_out" | grep -c "\.pending" || true) n=$(echo "$pending_out" | grep -c "\.pending" || true)
echo "[agent-loop] $(date -Iseconds)$n mail(s) en attente dans le spooler (critère from/to). Lancer l'agent gitea-issues-process dans Cursor." echo "[agent-loop] $(date -Iseconds)$n mail(s) en attente dans le spooler (critère from/to). Lancer l'agent git-issues-process dans Cursor."
if [ "${AGENT_LOOP_RUN_AGENT:-0}" = "1" ] && command -v agent >/dev/null 2>&1; then if [ "${AGENT_LOOP_RUN_AGENT:-0}" = "1" ] && command -v agent >/dev/null 2>&1; then
write_status "running_agent" "Lancement de l'agent Cursor pour traiter les mails du spooler." write_status "running_agent" "Lancement de l'agent Cursor pour traiter les mails du spooler."
echo "[agent-loop] $(date -Iseconds) — Lancement de l'agent Cursor (workflow gitea-issues-process spooler)." echo "[agent-loop] $(date -Iseconds) — Lancement de l'agent Cursor (workflow git-issues-process spooler)."
AGENT_MODEL="${AGENT_LOOP_MODEL:-sonnet-4.6}" AGENT_MODEL="${AGENT_LOOP_MODEL:-sonnet-4.6}"
echo "[agent-loop] $(date -Iseconds) — Modèle: $AGENT_MODEL" echo "[agent-loop] $(date -Iseconds) — Modèle: $AGENT_MODEL"
AGENT_OPTS=(-p "Exécute le workflow mails du spooler (agent gitea-issues-process). 1) Lister les mails à traiter avec ./gitea-issues/list-pending-spooler.sh (ou utiliser les chemins dans agent-loop.pending). 2) Pour chaque fichier .pending : lire le JSON (from, to, subject, body, message_id, base). Répondre à l'expéditeur (--to <from> du JSON), pas à une adresse fixe ; le « to » du mail reçu a déjà déterminé le projet. Rédiger une réponse pertinente (uniquement ton texte, pas de citation ; mail-send-reply.sh refuse si le body contient From:, Message-ID, wrote:, etc.). Envoyer avec ./gitea-issues/mail-send-reply.sh --to <from> --subject \"Re: ...\" --body \"<ta_réponse>\" --in-reply-to \"<message_id>\". Après envoi réussi : ./gitea-issues/write-response-spooler.sh --base <base> --to <from> --subject \"Re: ...\" --body \"<ta_réponse>\" --in-reply-to \"<message_id>\". Ne pas appeler mail-mark-read.sh (spooler)." -f --model "$AGENT_MODEL") AGENT_OPTS=(-p "Exécute le workflow mails du spooler (agent git-issues-process). 1) Lister les mails à traiter avec ./git-issues/list-pending-spooler.sh (ou utiliser les chemins dans agent-loop.pending). 2) Pour chaque fichier .pending : lire le JSON (from, to, subject, body, message_id, base). Répondre à l'expéditeur (--to <from> du JSON), pas à une adresse fixe ; le « to » du mail reçu a déjà déterminé le projet. Rédiger une réponse pertinente (uniquement ton texte, pas de citation ; mail-send-reply.sh refuse si le body contient From:, Message-ID, wrote:, etc.). Envoyer avec ./git-issues/mail-send-reply.sh --to <from> --subject \"Re: ...\" --body \"<ta_réponse>\" --in-reply-to \"<message_id>\". Après envoi réussi : ./git-issues/write-response-spooler.sh --base <base> --to <from> --subject \"Re: ...\" --body \"<ta_réponse>\" --in-reply-to \"<message_id>\". Ne pas appeler mail-mark-read.sh (spooler)." -f --model "$AGENT_MODEL")
if agent "${AGENT_OPTS[@]}" 2>&1; then if agent "${AGENT_OPTS[@]}" 2>&1; then
write_status "agent_done" "Agent terminé." write_status "agent_done" "Agent terminé."
else else

View File

@ -7,9 +7,9 @@
# #
set -euo pipefail set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" source "${GIT_ISSUES_DIR}/lib.sh"
# Signature appended to every comment (same as mail replies) # Signature appended to every comment (same as mail replies)
COMMENT_SIGNATURE=$'\n\n--\nSupport IA du projet Lecoffre.io\nai.support.lecoffreio@4nkweb.com' COMMENT_SIGNATURE=$'\n\n--\nSupport IA du projet Lecoffre.io\nai.support.lecoffreio@4nkweb.com'

View File

@ -6,9 +6,9 @@
# #
set -euo pipefail set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" source "${GIT_ISSUES_DIR}/lib.sh"
if [[ $# -lt 1 ]]; then if [[ $# -lt 1 ]]; then
log_err "Usage: $0 <issue_number> [base_branch]" log_err "Usage: $0 <issue_number> [base_branch]"

View File

@ -5,9 +5,9 @@
# #
set -euo pipefail set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" source "${GIT_ISSUES_DIR}/lib.sh"
require_jq || exit 1 require_jq || exit 1

View File

@ -1,6 +1,6 @@
# IMAP config for mail-to-issue (e.g. Proton Mail Bridge). # IMAP config for mail-to-issue (e.g. Proton Mail Bridge).
# Copy to .secrets/gitea-issues/imap-bridge.env and set real values. # Copy to .secrets/git-issues/imap-bridge.env and set real values.
# Do not commit .secrets/gitea-issues/imap-bridge.env (directory is gitignored). # Do not commit .secrets/git-issues/imap-bridge.env (directory is gitignored).
# #
# IMAP (read) # IMAP (read)
# IMAP_HOST=127.0.0.1 # IMAP_HOST=127.0.0.1

View File

@ -1,11 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# Shared config and helpers for Gitea issues scripts. # Shared config and helpers for Gitea issues scripts.
# Source from gitea-issues/*.sh. Standalone: run from ia_dev root. # Source from git-issues/*.sh. Standalone: run from ia_dev root.
# #
set -euo pipefail set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
GITEA_API_URL="${GITEA_API_URL:-https://git.4nkweb.com/api/v1}" GITEA_API_URL="${GITEA_API_URL:-https://git.4nkweb.com/api/v1}"
GITEA_REPO_OWNER="${GITEA_REPO_OWNER:-4nk}" GITEA_REPO_OWNER="${GITEA_REPO_OWNER:-4nk}"
GITEA_REPO_NAME="${GITEA_REPO_NAME:-lecoffre_ng}" GITEA_REPO_NAME="${GITEA_REPO_NAME:-lecoffre_ng}"
@ -14,15 +14,15 @@ GITEA_REPO_NAME="${GITEA_REPO_NAME:-lecoffre_ng}"
PROJECT_CONFIG_PATH="" PROJECT_CONFIG_PATH=""
PROJECT_LOGS_DIR="" PROJECT_LOGS_DIR=""
DATA_ISSUES_DIR="" DATA_ISSUES_DIR=""
if [[ -f "${GITEA_ISSUES_DIR}/../lib/project_config.sh" ]]; then if [[ -f "${GIT_ISSUES_DIR}/../lib/project_config.sh" ]]; then
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || true PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || true
if [[ -z "${PROJECT_ROOT:-}" || "$(basename "$PROJECT_ROOT")" = "ia_dev" ]]; then if [[ -z "${PROJECT_ROOT:-}" || "$(basename "$PROJECT_ROOT")" = "ia_dev" ]]; then
PROJECT_ROOT="$(cd "${GITEA_ISSUES_DIR}/../.." && pwd)" PROJECT_ROOT="$(cd "${GIT_ISSUES_DIR}/../.." && pwd)"
fi fi
IA_DEV_ROOT="$(cd "$GITEA_ISSUES_DIR/.." && pwd)" IA_DEV_ROOT="$(cd "$GIT_ISSUES_DIR/.." && pwd)"
if [[ -n "${PROJECT_ROOT:-}" ]]; then if [[ -n "${PROJECT_ROOT:-}" ]]; then
# shellcheck source=../lib/project_config.sh # shellcheck source=../lib/project_config.sh
source "${GITEA_ISSUES_DIR}/../lib/project_config.sh" source "${GIT_ISSUES_DIR}/../lib/project_config.sh"
if [[ -n "${PROJECT_ID:-}" && -n "${IA_DEV_ROOT:-}" ]]; then if [[ -n "${PROJECT_ID:-}" && -n "${IA_DEV_ROOT:-}" ]]; then
PROJECT_LOGS_DIR="${IA_DEV_ROOT}/projects/${PROJECT_ID}/logs" PROJECT_LOGS_DIR="${IA_DEV_ROOT}/projects/${PROJECT_ID}/logs"
DATA_ISSUES_DIR="${IA_DEV_ROOT}/projects/${PROJECT_ID}/data/issues" DATA_ISSUES_DIR="${IA_DEV_ROOT}/projects/${PROJECT_ID}/data/issues"
@ -33,10 +33,10 @@ fi
export PROJECT_LOGS_DIR export PROJECT_LOGS_DIR
export DATA_ISSUES_DIR export DATA_ISSUES_DIR
if [[ -f "${GITEA_ISSUES_DIR}/../lib/smart_ide_logs.sh" ]]; then if [[ -f "${GIT_ISSUES_DIR}/../lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh # shellcheck source=../lib/smart_ide_logs.sh
source "${GITEA_ISSUES_DIR}/../lib/smart_ide_logs.sh" source "${GIT_ISSUES_DIR}/../lib/smart_ide_logs.sh"
_IA_DEV_ROOT_FOR_LOG="$(cd "${GITEA_ISSUES_DIR}/.." && pwd)" _IA_DEV_ROOT_FOR_LOG="$(cd "${GIT_ISSUES_DIR}/.." && pwd)"
smart_ide_logs_begin "$_IA_DEV_ROOT_FOR_LOG" "${BASH_SOURCE[1]}" "$*" smart_ide_logs_begin "$_IA_DEV_ROOT_FOR_LOG" "${BASH_SOURCE[1]}" "$*"
smart_ide_logs_register_exit_trap smart_ide_logs_register_exit_trap
fi fi
@ -47,7 +47,7 @@ load_gitea_token() {
return 0 return 0
fi fi
local token_file="" local token_file=""
local ia_dev_root="${GITEA_ISSUES_DIR}/.." local ia_dev_root="${GIT_ISSUES_DIR}/.."
if [[ -n "${PROJECT_CONFIG_PATH:-}" && -f "$PROJECT_CONFIG_PATH" ]] && command -v jq >/dev/null 2>&1; then if [[ -n "${PROJECT_CONFIG_PATH:-}" && -f "$PROJECT_CONFIG_PATH" ]] && command -v jq >/dev/null 2>&1; then
local rel_path local rel_path
rel_path="$(jq -r '.git.token_file // empty' "$PROJECT_CONFIG_PATH" 2>/dev/null)" rel_path="$(jq -r '.git.token_file // empty' "$PROJECT_CONFIG_PATH" 2>/dev/null)"
@ -59,14 +59,14 @@ load_gitea_token() {
fi fi
fi fi
if [[ -z "$token_file" ]]; then if [[ -z "$token_file" ]]; then
token_file="${ia_dev_root}/.secrets/gitea-issues/token" token_file="${ia_dev_root}/.secrets/git-issues/token"
fi fi
if [[ -f "$token_file" ]]; then if [[ -f "$token_file" ]]; then
GITEA_TOKEN="$(cat "$token_file")" GITEA_TOKEN="$(cat "$token_file")"
return 0 return 0
fi fi
echo "[gitea-issues] ERROR: GITEA_TOKEN not set and ${token_file} not found" >&2 echo "[git-issues] ERROR: GITEA_TOKEN not set and ${token_file} not found" >&2
echo "[gitea-issues] Set GITEA_TOKEN or create the token file with a Gitea Personal Access Token." >&2 echo "[git-issues] Set GITEA_TOKEN or create the token file with a Gitea Personal Access Token." >&2
return 1 return 1
} }
@ -111,8 +111,8 @@ gitea_api_delete() {
} }
log_ts() { date -u '+%Y-%m-%dT%H:%M:%SZ'; } log_ts() { date -u '+%Y-%m-%dT%H:%M:%SZ'; }
log_info() { echo "[$(log_ts)] [gitea-issues] $*"; } log_info() { echo "[$(log_ts)] [git-issues] $*"; }
log_err() { echo "[$(log_ts)] [gitea-issues] $*" >&2; } log_err() { echo "[$(log_ts)] [git-issues] $*" >&2; }
# Require jq for JSON output # Require jq for JSON output
require_jq() { require_jq() {

View File

@ -6,9 +6,9 @@
# #
set -euo pipefail set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" source "${GIT_ISSUES_DIR}/lib.sh"
require_jq || exit 1 require_jq || exit 1

View File

@ -2,20 +2,20 @@
# List .pending files in projects/<id>/data/issues/ with status "pending" (one file per message; status updated in place). # List .pending files in projects/<id>/data/issues/ with status "pending" (one file per message; status updated in place).
# Run from ia_dev root. If PROJECT_ID is set (from MAIL_TO or AI_AGENT_TOKEN), list that project only; else list all projects. # Run from ia_dev root. If PROJECT_ID is set (from MAIL_TO or AI_AGENT_TOKEN), list that project only; else list all projects.
# Output: one path per line. # Output: one path per line.
# Usage: depuis la racine de ia_dev : ./gitea-issues/list-pending-spooler.sh # Usage: depuis la racine de ia_dev : ./git-issues/list-pending-spooler.sh
set -euo pipefail set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
ROOT="$(cd "${GITEA_ISSUES_DIR}/../.." && pwd)" ROOT="$(cd "${GIT_ISSUES_DIR}/../.." && pwd)"
export GITEA_ISSUES_DIR export GIT_ISSUES_DIR
cd "$ROOT" cd "$ROOT"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" 2>/dev/null || true source "${GIT_ISSUES_DIR}/lib.sh" 2>/dev/null || true
if [[ -n "${DATA_ISSUES_DIR:-}" && -d "${DATA_ISSUES_DIR}" ]]; then if [[ -n "${DATA_ISSUES_DIR:-}" && -d "${DATA_ISSUES_DIR}" ]]; then
SPOOLS=("${DATA_ISSUES_DIR}") SPOOLS=("${DATA_ISSUES_DIR}")
else else
# No project from MAIL_TO/AI_AGENT_TOKEN: list pending from all projects # No project from MAIL_TO/AI_AGENT_TOKEN: list pending from all projects
SPOOLS=() SPOOLS=()
for spool in "${GITEA_ISSUES_DIR}/../projects/"*/data/issues; do for spool in "${GIT_ISSUES_DIR}/../projects/"*/data/issues; do
[[ -d "$spool" ]] || continue [[ -d "$spool" ]] || continue
SPOOLS+=("$spool") SPOOLS+=("$spool")
done done

View File

@ -2,7 +2,7 @@
""" """
Create one Gitea issue from one email (by UID), then mark the email as read. Create one Gitea issue from one email (by UID), then mark the email as read.
If --title and/or --body are provided (formalized by agent), use them; else use subject and body from the email. If --title and/or --body are provided (formalized by agent), use them; else use subject and body from the email.
Usage: ./gitea-issues/mail-create-issue-from-email.sh --uid <uid> [--title "..." ] [--body "..." ] Usage: ./git-issues/mail-create-issue-from-email.sh --uid <uid> [--title "..." ] [--body "..." ]
""" """
from __future__ import annotations from __future__ import annotations
@ -63,13 +63,13 @@ def main() -> None:
cfg = load_imap_config() cfg = load_imap_config()
if not cfg["user"] or not cfg["password"]: if not cfg["user"] or not cfg["password"]:
root = repo_root() root = repo_root()
env_path = root / ".secrets" / "gitea-issues" / "imap-bridge.env" env_path = root / ".secrets" / "git-issues" / "imap-bridge.env"
print("[gitea-issues] ERROR: IMAP_USER and IMAP_PASSWORD required.", file=sys.stderr) print("[git-issues] ERROR: IMAP_USER and IMAP_PASSWORD required.", file=sys.stderr)
sys.exit(1) sys.exit(1)
gitea = load_gitea_config() gitea = load_gitea_config()
if not gitea["token"]: if not gitea["token"]:
print("[gitea-issues] ERROR: GITEA_TOKEN not set.", file=sys.stderr) print("[git-issues] ERROR: GITEA_TOKEN not set.", file=sys.stderr)
sys.exit(1) sys.exit(1)
mail = imaplib.IMAP4(cfg["host"], int(cfg["port"])) mail = imaplib.IMAP4(cfg["host"], int(cfg["port"]))
@ -79,7 +79,7 @@ def main() -> None:
mail.select("INBOX") mail.select("INBOX")
_, data = mail.fetch(args.uid, "(RFC822)") _, data = mail.fetch(args.uid, "(RFC822)")
if not data or not data[0]: if not data or not data[0]:
print("[gitea-issues] ERROR: Message UID not found.", file=sys.stderr) print("[git-issues] ERROR: Message UID not found.", file=sys.stderr)
mail.logout() mail.logout()
sys.exit(1) sys.exit(1)
@ -94,7 +94,7 @@ def main() -> None:
issue = create_gitea_issue(title, body) issue = create_gitea_issue(title, body)
if not issue: if not issue:
print("[gitea-issues] ERROR: Failed to create issue.", file=sys.stderr) print("[git-issues] ERROR: Failed to create issue.", file=sys.stderr)
mail.logout() mail.logout()
sys.exit(1) sys.exit(1)
@ -102,7 +102,7 @@ def main() -> None:
mail.logout() mail.logout()
num = issue.get("number", "?") num = issue.get("number", "?")
print(f"[gitea-issues] Created issue #{num}: {title[:60]}") print(f"[git-issues] Created issue #{num}: {title[:60]}")
print(f"ISSUE_NUMBER={num}") print(f"ISSUE_NUMBER={num}")

View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Create one Gitea issue from one email (by UID), mark email read. Run from repo root.
# Usage: ./git-issues/mail-create-issue-from-email.sh --uid <uid> [--title "..." ] [--body "..." ]
set -euo pipefail
GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export GIT_ISSUES_DIR
export REPO_ROOT="${GIT_ISSUES_DIR}/.."
_IA_FOR_LOG="$(cd "${GIT_ISSUES_DIR}/.." && pwd)"
if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh
source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh"
smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*"
fi
exec python3 "${GIT_ISSUES_DIR}/mail-create-issue-from-email.py" "$@"

View File

@ -4,7 +4,7 @@ Fetch the full email thread (conversation) for a given message UID.
Uses Message-ID, References and In-Reply-To to find all messages in the thread. Uses Message-ID, References and In-Reply-To to find all messages in the thread.
Output format: same as mail-list-unread (--- MAIL UID=... --- ... --- END MAIL ---), chronological order. Output format: same as mail-list-unread (--- MAIL UID=... --- ... --- END MAIL ---), chronological order.
Usage: mail-get-thread.py <uid> Usage: mail-get-thread.py <uid>
or: ./gitea-issues/mail-get-thread.sh <uid> or: ./git-issues/mail-get-thread.sh <uid>
""" """
from __future__ import annotations from __future__ import annotations
@ -137,18 +137,18 @@ def main() -> int:
return 1 return 1
uid0 = sys.argv[1].strip() uid0 = sys.argv[1].strip()
if not uid0: if not uid0:
print("[gitea-issues] ERROR: UID required.", file=sys.stderr) print("[git-issues] ERROR: UID required.", file=sys.stderr)
return 1 return 1
cfg = load_imap_config() cfg = load_imap_config()
if not cfg["user"] or not cfg["password"]: if not cfg["user"] or not cfg["password"]:
root = repo_root() root = repo_root()
env_path = root / ".secrets" / "gitea-issues" / "imap-bridge.env" env_path = root / ".secrets" / "git-issues" / "imap-bridge.env"
print( print(
"[gitea-issues] ERROR: IMAP_USER and IMAP_PASSWORD required.", "[git-issues] ERROR: IMAP_USER and IMAP_PASSWORD required.",
file=sys.stderr, file=sys.stderr,
) )
print(f"[gitea-issues] Set env or create {env_path}", file=sys.stderr) print(f"[git-issues] Set env or create {env_path}", file=sys.stderr)
return 1 return 1
mail = imaplib.IMAP4(cfg["host"], int(cfg["port"])) mail = imaplib.IMAP4(cfg["host"], int(cfg["port"]))
@ -159,7 +159,7 @@ def main() -> int:
msg0 = fetch_message_by_uid(mail, uid0) msg0 = fetch_message_by_uid(mail, uid0)
if not msg0: if not msg0:
print(f"[gitea-issues] No message found for UID={uid0}.", file=sys.stderr) print(f"[git-issues] No message found for UID={uid0}.", file=sys.stderr)
mail.logout() mail.logout()
return 1 return 1

View File

@ -1,18 +1,18 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Fetch full email thread for a given UID. Run from repo root. # Fetch full email thread for a given UID. Run from repo root.
# Usage: ./gitea-issues/mail-get-thread.sh <uid> # Usage: ./git-issues/mail-get-thread.sh <uid>
set -euo pipefail set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export GITEA_ISSUES_DIR export GIT_ISSUES_DIR
export REPO_ROOT="${GITEA_ISSUES_DIR}/.." export REPO_ROOT="${GIT_ISSUES_DIR}/.."
if [ $# -lt 1 ]; then if [ $# -lt 1 ]; then
echo "Usage: $0 <uid>" >&2 echo "Usage: $0 <uid>" >&2
exit 1 exit 1
fi fi
_IA_FOR_LOG="$(cd "${GITEA_ISSUES_DIR}/.." && pwd)" _IA_FOR_LOG="$(cd "${GIT_ISSUES_DIR}/.." && pwd)"
if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh # shellcheck source=../lib/smart_ide_logs.sh
source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh"
smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*" smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*"
fi fi
exec python3 "${GITEA_ISSUES_DIR}/mail-get-thread.py" "$1" exec python3 "${GIT_ISSUES_DIR}/mail-get-thread.py" "$1"

View File

@ -3,7 +3,7 @@
List unread emails via IMAP (e.g. Proton Mail Bridge). Read-only; does not mark as read. List unread emails via IMAP (e.g. Proton Mail Bridge). Read-only; does not mark as read.
Only lists messages sent to the configured alias (MAIL_FILTER_TO, default ai.support.lecoffreio@4nkweb.com). Only lists messages sent to the configured alias (MAIL_FILTER_TO, default ai.support.lecoffreio@4nkweb.com).
Output is for the agent: each mail with UID, Message-ID, From, To, Subject, Date, body. Output is for the agent: each mail with UID, Message-ID, From, To, Subject, Date, body.
Usage: ./gitea-issues/mail-list-unread.sh Usage: ./git-issues/mail-list-unread.sh
""" """
from __future__ import annotations from __future__ import annotations
@ -14,7 +14,7 @@ import sys
from email.header import decode_header from email.header import decode_header
from pathlib import Path from pathlib import Path
# Add gitea-issues to path for mail_common # Add git-issues to path for mail_common
sys.path.insert(0, str(Path(__file__).resolve().parent)) sys.path.insert(0, str(Path(__file__).resolve().parent))
from mail_common import imap_search_criterion_unseen, load_imap_config, repo_root, imap_ssl_context from mail_common import imap_search_criterion_unseen, load_imap_config, repo_root, imap_ssl_context
@ -65,9 +65,9 @@ def main() -> None:
cfg = load_imap_config() cfg = load_imap_config()
if not cfg["user"] or not cfg["password"]: if not cfg["user"] or not cfg["password"]:
root = repo_root() root = repo_root()
env_path = root / ".secrets" / "gitea-issues" / "imap-bridge.env" env_path = root / ".secrets" / "git-issues" / "imap-bridge.env"
print("[gitea-issues] ERROR: IMAP_USER and IMAP_PASSWORD required.", file=sys.stderr) print("[git-issues] ERROR: IMAP_USER and IMAP_PASSWORD required.", file=sys.stderr)
print(f"[gitea-issues] Set env or create {env_path}", file=sys.stderr) print(f"[git-issues] Set env or create {env_path}", file=sys.stderr)
sys.exit(1) sys.exit(1)
mail = imaplib.IMAP4(cfg["host"], int(cfg["port"])) mail = imaplib.IMAP4(cfg["host"], int(cfg["port"]))
@ -79,7 +79,7 @@ def main() -> None:
_, nums = mail.search(None, criterion) _, nums = mail.search(None, criterion)
ids = nums[0].split() ids = nums[0].split()
if not ids: if not ids:
print("[gitea-issues] No unread messages (IMAP UNSEEN, on or after MAIL_SINCE_DATE). For spooler criterion (from/to), use tickets-fetch-inbox.sh and list-pending-spooler.sh.") print("[git-issues] No unread messages (IMAP UNSEEN, on or after MAIL_SINCE_DATE). For spooler criterion (from/to), use tickets-fetch-inbox.sh and list-pending-spooler.sh.")
mail.logout() mail.logout()
return return
@ -110,7 +110,7 @@ def main() -> None:
shown += 1 shown += 1
if shown == 0: if shown == 0:
print("[gitea-issues] No unread messages sent to the configured alias (MAIL_FILTER_TO). For spooler (from/to in conf.json), use tickets-fetch-inbox.sh and list-pending-spooler.sh.") print("[git-issues] No unread messages sent to the configured alias (MAIL_FILTER_TO). For spooler (from/to in conf.json), use tickets-fetch-inbox.sh and list-pending-spooler.sh.")
mail.logout() mail.logout()

View File

@ -1,14 +1,14 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# List unread emails (read-only). Run from repo root. # List unread emails (read-only). Run from repo root.
set -euo pipefail set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
# REPO_ROOT = ia_dev so mail_common.repo_root() finds .secrets under ia_dev # REPO_ROOT = ia_dev so mail_common.repo_root() finds .secrets under ia_dev
export GITEA_ISSUES_DIR export GIT_ISSUES_DIR
export REPO_ROOT="${GITEA_ISSUES_DIR}/.." export REPO_ROOT="${GIT_ISSUES_DIR}/.."
_IA_FOR_LOG="$(cd "${GITEA_ISSUES_DIR}/.." && pwd)" _IA_FOR_LOG="$(cd "${GIT_ISSUES_DIR}/.." && pwd)"
if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh # shellcheck source=../lib/smart_ide_logs.sh
source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh"
smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*" smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*"
fi fi
exec python3 "${GITEA_ISSUES_DIR}/mail-list-unread.py" exec python3 "${GIT_ISSUES_DIR}/mail-list-unread.py"

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Mark one email as read by UID (e.g. after replying without creating an issue). Mark one email as read by UID (e.g. after replying without creating an issue).
Usage: ./gitea-issues/mail-mark-read.sh <uid> Usage: ./git-issues/mail-mark-read.sh <uid>
""" """
from __future__ import annotations from __future__ import annotations
@ -16,15 +16,15 @@ from mail_common import load_imap_config, repo_root, imap_ssl_context
def main() -> None: def main() -> None:
if len(sys.argv) < 2: if len(sys.argv) < 2:
print("[gitea-issues] Usage: mail-mark-read.sh <uid>", file=sys.stderr) print("[git-issues] Usage: mail-mark-read.sh <uid>", file=sys.stderr)
sys.exit(1) sys.exit(1)
uid = sys.argv[1].strip() uid = sys.argv[1].strip()
cfg = load_imap_config() cfg = load_imap_config()
if not cfg["user"] or not cfg["password"]: if not cfg["user"] or not cfg["password"]:
root = repo_root() root = repo_root()
env_path = root / ".secrets" / "gitea-issues" / "imap-bridge.env" env_path = root / ".secrets" / "git-issues" / "imap-bridge.env"
print("[gitea-issues] ERROR: IMAP_USER and IMAP_PASSWORD required.", file=sys.stderr) print("[git-issues] ERROR: IMAP_USER and IMAP_PASSWORD required.", file=sys.stderr)
sys.exit(1) sys.exit(1)
mail = imaplib.IMAP4(cfg["host"], int(cfg["port"])) mail = imaplib.IMAP4(cfg["host"], int(cfg["port"]))
@ -34,7 +34,7 @@ def main() -> None:
mail.select("INBOX") mail.select("INBOX")
mail.store(uid, "+FLAGS", "\\Seen") mail.store(uid, "+FLAGS", "\\Seen")
mail.logout() mail.logout()
print("[gitea-issues] Marked as read.") print("[git-issues] Marked as read.")
if __name__ == "__main__": if __name__ == "__main__":

14
git-issues/mail-mark-read.sh Executable file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Mark one email as read by UID. Run from repo root.
# Usage: ./git-issues/mail-mark-read.sh <uid>
set -euo pipefail
GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export GIT_ISSUES_DIR
export REPO_ROOT="${GIT_ISSUES_DIR}/.."
_IA_FOR_LOG="$(cd "${GIT_ISSUES_DIR}/.." && pwd)"
if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh
source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh"
smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*"
fi
exec python3 "${GIT_ISSUES_DIR}/mail-mark-read.py" "$@"

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Send a reply email via SMTP (e.g. Proton Mail Bridge). Send a reply email via SMTP (e.g. Proton Mail Bridge).
Usage: ./gitea-issues/mail-send-reply.sh --to addr@example.com --subject "..." --body "..." [--in-reply-to "<msg-id>" [--references "<refs>"]] Usage: ./git-issues/mail-send-reply.sh --to addr@example.com --subject "..." --body "..." [--in-reply-to "<msg-id>" [--references "<refs>"]]
Or: echo "body" | ./gitea-issues/mail-send-reply.sh --to addr@example.com --subject "..." [--in-reply-to "<msg-id>"] Or: echo "body" | ./git-issues/mail-send-reply.sh --to addr@example.com --subject "..." [--in-reply-to "<msg-id>"]
""" """
from __future__ import annotations from __future__ import annotations
@ -69,9 +69,9 @@ def main() -> None:
cfg = load_smtp_config() cfg = load_smtp_config()
if not cfg["user"] or not cfg["password"]: if not cfg["user"] or not cfg["password"]:
root = repo_root() root = repo_root()
env_path = root / ".secrets" / "gitea-issues" / "imap-bridge.env" env_path = root / ".secrets" / "git-issues" / "imap-bridge.env"
print("[gitea-issues] ERROR: SMTP_USER and SMTP_PASSWORD required.", file=sys.stderr) print("[git-issues] ERROR: SMTP_USER and SMTP_PASSWORD required.", file=sys.stderr)
print(f"[gitea-issues] Set env or create {env_path}", file=sys.stderr) print(f"[git-issues] Set env or create {env_path}", file=sys.stderr)
sys.exit(1) sys.exit(1)
body = args.body body = args.body
@ -80,7 +80,7 @@ def main() -> None:
body = body.rstrip() body = body.rstrip()
if body_contains_citation(body): if body_contains_citation(body):
print( print(
"[gitea-issues] ERROR: Body must not contain the received message (no citation, no From:, Message-ID, wrote:, etc.). Send only your reply text.", "[git-issues] ERROR: Body must not contain the received message (no citation, no From:, Message-ID, wrote:, etc.). Send only your reply text.",
file=sys.stderr, file=sys.stderr,
) )
sys.exit(1) sys.exit(1)
@ -101,7 +101,7 @@ def main() -> None:
smtp.login(cfg["user"], cfg["password"]) smtp.login(cfg["user"], cfg["password"])
smtp.sendmail(cfg["user"], [args.to], msg.as_string()) smtp.sendmail(cfg["user"], [args.to], msg.as_string())
print("[gitea-issues] Reply sent.") print("[git-issues] Reply sent.")
if __name__ == "__main__": if __name__ == "__main__":

14
git-issues/mail-send-reply.sh Executable file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Send reply email via Bridge SMTP. Run from repo root.
# Usage: ./git-issues/mail-send-reply.sh --to addr --subject "..." [--body "..." | stdin] [--in-reply-to "<msg-id>" [--references "..." ]]
set -euo pipefail
GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export GIT_ISSUES_DIR
export REPO_ROOT="${GIT_ISSUES_DIR}/.."
_IA_FOR_LOG="$(cd "${GIT_ISSUES_DIR}/.." && pwd)"
if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh
source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh"
smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*"
fi
exec python3 "${GIT_ISSUES_DIR}/mail-send-reply.py" "$@"

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Thread log: one file per email thread under projects/<id>/logs/gitea-issues/threads/. Thread log: one file per email thread under projects/<id>/logs/git-issues/threads/.
Content: exchanges (received + sent), tickets (issues), commits. Content: exchanges (received + sent), tickets (issues), commits.
Usage: Usage:
mail-thread-log.py get-id --uid <uid> # print THREAD_ID=... mail-thread-log.py get-id --uid <uid> # print THREAD_ID=...
@ -25,8 +25,8 @@ from project_config import project_logs_dir
def threads_dir() -> Path: def threads_dir() -> Path:
"""Thread log directory: projects/<id>/logs/gitea-issues/threads/ or repo logs fallback.""" """Thread log directory: projects/<id>/logs/git-issues/threads/ or repo logs fallback."""
d = project_logs_dir() / "gitea-issues" / "threads" d = project_logs_dir() / "git-issues" / "threads"
d.mkdir(parents=True, exist_ok=True) d.mkdir(parents=True, exist_ok=True)
return d return d
@ -39,7 +39,7 @@ def sanitize_thread_id(raw: str, max_len: int = 80) -> str:
def get_thread_output(uid: str) -> str: def get_thread_output(uid: str) -> str:
gitea_dir = Path(__file__).resolve().parent gitea_dir = Path(__file__).resolve().parent
root = gitea_dir.parent root = gitea_dir.parent
env = {"GITEA_ISSUES_DIR": str(gitea_dir)} env = {"GIT_ISSUES_DIR": str(gitea_dir)}
result = subprocess.run( result = subprocess.run(
[sys.executable, str(gitea_dir / "mail-get-thread.py"), uid], [sys.executable, str(gitea_dir / "mail-get-thread.py"), uid],
cwd=str(root), cwd=str(root),

19
git-issues/mail-thread-log.sh Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Thread log: one file per thread under projects/<id>/logs/git-issues/threads/. Run from repo root.
# Usage:
# ./git-issues/mail-thread-log.sh get-id --uid <uid>
# ./git-issues/mail-thread-log.sh init --uid <uid>
# ./git-issues/mail-thread-log.sh append-sent --thread-id <id> --to <addr> --subject "..." [--body "..."] [--date "..."]
# ./git-issues/mail-thread-log.sh append-issue --thread-id <id> --issue <num> [--title "..."]
# ./git-issues/mail-thread-log.sh append-commit --thread-id <id> --hash <hash> --message "..." [--branch "..."]
set -euo pipefail
GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export GIT_ISSUES_DIR
export REPO_ROOT="${GIT_ISSUES_DIR}/.."
_IA_FOR_LOG="$(cd "${GIT_ISSUES_DIR}/.." && pwd)"
if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh
source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh"
smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*"
fi
exec python3 "${GIT_ISSUES_DIR}/mail-thread-log.py" "$@"

View File

@ -12,8 +12,8 @@ This script (mail-to-issue) is a **batch** fallback: it creates one issue per un
message with title=subject and body=text+From, then marks messages as read. Use only message with title=subject and body=text+From, then marks messages as read. Use only
when the agent-driven flow is not used. when the agent-driven flow is not used.
Reads IMAP config from .secrets/gitea-issues/imap-bridge.env (or env vars). Reads IMAP config from .secrets/git-issues/imap-bridge.env (or env vars).
Reads Gitea token from GITEA_TOKEN or .secrets/gitea-issues/token. Reads Gitea token from GITEA_TOKEN or .secrets/git-issues/token.
""" """
from __future__ import annotations from __future__ import annotations
@ -67,12 +67,12 @@ def main() -> None:
imap_cfg = load_imap_config() imap_cfg = load_imap_config()
if not imap_cfg["user"] or not imap_cfg["password"]: if not imap_cfg["user"] or not imap_cfg["password"]:
root = repo_root() root = repo_root()
env_path = root / ".secrets" / "gitea-issues" / "imap-bridge.env" env_path = root / ".secrets" / "git-issues" / "imap-bridge.env"
print("[gitea-issues] ERROR: IMAP_USER and IMAP_PASSWORD required.", file=sys.stderr) print("[git-issues] ERROR: IMAP_USER and IMAP_PASSWORD required.", file=sys.stderr)
sys.exit(1) sys.exit(1)
gitea_cfg = load_gitea_config() gitea_cfg = load_gitea_config()
if not gitea_cfg["token"]: if not gitea_cfg["token"]:
print("[gitea-issues] ERROR: GITEA_TOKEN not set.", file=sys.stderr) print("[git-issues] ERROR: GITEA_TOKEN not set.", file=sys.stderr)
sys.exit(1) sys.exit(1)
mail = imaplib.IMAP4(imap_cfg["host"], int(imap_cfg["port"])) mail = imaplib.IMAP4(imap_cfg["host"], int(imap_cfg["port"]))
@ -84,7 +84,7 @@ def main() -> None:
_, nums = mail.search(None, criterion) _, nums = mail.search(None, criterion)
ids = nums[0].split() ids = nums[0].split()
if not ids: if not ids:
print("[gitea-issues] No unread messages.") print("[git-issues] No unread messages.")
mail.logout() mail.logout()
return return
@ -103,13 +103,13 @@ def main() -> None:
issue = create_gitea_issue(title, body_for_issue) issue = create_gitea_issue(title, body_for_issue)
if issue: if issue:
created += 1 created += 1
print(f"[gitea-issues] Created issue #{issue.get('number', '?')}: {title[:60]}") print(f"[git-issues] Created issue #{issue.get('number', '?')}: {title[:60]}")
mail.store(uid_s, "+FLAGS", "\\Seen") mail.store(uid_s, "+FLAGS", "\\Seen")
else: else:
print(f"[gitea-issues] Skipped (API failed): {title[:60]}", file=sys.stderr) print(f"[git-issues] Skipped (API failed): {title[:60]}", file=sys.stderr)
mail.logout() mail.logout()
print(f"[gitea-issues] Done. Created {created} issue(s).") print(f"[git-issues] Done. Created {created} issue(s).")
if __name__ == "__main__": if __name__ == "__main__":

18
git-issues/mail-to-issue.sh Executable file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env bash
#
# Create Gitea issues from unread emails (IMAP). Requires Proton Mail Bridge
# or any IMAP server. Config: .secrets/git-issues/imap-bridge.env and token.
# Usage: ./git-issues/mail-to-issue.sh
#
set -euo pipefail
GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export GIT_ISSUES_DIR
export REPO_ROOT="${GIT_ISSUES_DIR}/.."
_IA_FOR_LOG="$(cd "${GIT_ISSUES_DIR}/.." && pwd)"
if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh
source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh"
smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*"
fi
exec python3 "${GIT_ISSUES_DIR}/mail-to-issue.py"

View File

@ -1,4 +1,4 @@
# Shared config and helpers for gitea-issues mail scripts (IMAP/SMTP, Gitea). # Shared config and helpers for git-issues mail scripts (IMAP/SMTP, Gitea).
# Used by mail-list-unread, mail-send-reply, mail-create-issue-from-email, mail-mark-read. # Used by mail-list-unread, mail-send-reply, mail-create-issue-from-email, mail-mark-read.
from __future__ import annotations from __future__ import annotations
@ -31,12 +31,12 @@ def imap_search_criterion_unseen() -> str:
def repo_root() -> Path: def repo_root() -> Path:
# Root = directory containing gitea-issues (ia_dev). .secrets and logs live under ia_dev (./.secrets, ./logs) # Root = directory containing git-issues (ia_dev). .secrets and logs live under ia_dev (./.secrets, ./logs)
# so they do not depend on a specific project; same ia_dev works for any project (PROJECT_ID from MAIL_TO or AI_AGENT_TOKEN). # so they do not depend on a specific project; same ia_dev works for any project (PROJECT_ID from MAIL_TO or AI_AGENT_TOKEN).
env_root = os.environ.get("REPO_ROOT") env_root = os.environ.get("REPO_ROOT")
if env_root: if env_root:
return Path(env_root).resolve() return Path(env_root).resolve()
issues_dir = os.environ.get("GITEA_ISSUES_DIR") issues_dir = os.environ.get("GIT_ISSUES_DIR")
if issues_dir: if issues_dir:
return Path(issues_dir).resolve().parent return Path(issues_dir).resolve().parent
return Path(__file__).resolve().parent.parent return Path(__file__).resolve().parent.parent
@ -60,7 +60,7 @@ def load_env_file(path: Path) -> None:
def load_imap_config() -> dict[str, str]: def load_imap_config() -> dict[str, str]:
root = repo_root() root = repo_root()
env_path = root / ".secrets" / "gitea-issues" / "imap-bridge.env" env_path = root / ".secrets" / "git-issues" / "imap-bridge.env"
load_env_file(env_path) load_env_file(env_path)
ssl_verify_raw = os.environ.get("IMAP_SSL_VERIFY", "true").lower() ssl_verify_raw = os.environ.get("IMAP_SSL_VERIFY", "true").lower()
ssl_verify = ssl_verify_raw not in ("0", "false", "no") ssl_verify = ssl_verify_raw not in ("0", "false", "no")
@ -87,7 +87,7 @@ def imap_ssl_context(ssl_verify: bool = True) -> ssl.SSLContext:
def load_smtp_config() -> dict[str, str]: def load_smtp_config() -> dict[str, str]:
root = repo_root() root = repo_root()
env_path = root / ".secrets" / "gitea-issues" / "imap-bridge.env" env_path = root / ".secrets" / "git-issues" / "imap-bridge.env"
load_env_file(env_path) load_env_file(env_path)
ssl_verify_raw = os.environ.get("IMAP_SSL_VERIFY", os.environ.get("SMTP_SSL_VERIFY", "true")).lower() ssl_verify_raw = os.environ.get("IMAP_SSL_VERIFY", os.environ.get("SMTP_SSL_VERIFY", "true")).lower()
ssl_verify = ssl_verify_raw not in ("0", "false", "no") ssl_verify = ssl_verify_raw not in ("0", "false", "no")
@ -105,7 +105,7 @@ def load_gitea_config() -> dict[str, str]:
root = repo_root() root = repo_root()
token = os.environ.get("GITEA_TOKEN") token = os.environ.get("GITEA_TOKEN")
if not token: if not token:
token_path = root / ".secrets" / "gitea-issues" / "token" token_path = root / ".secrets" / "git-issues" / "token"
if token_path.is_file(): if token_path.is_file():
token = token_path.read_text(encoding="utf-8").strip() token = token_path.read_text(encoding="utf-8").strip()
return { return {

View File

@ -1,14 +1,14 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# Print issue number, title and body in a single block for agent consumption. # Print issue number, title and body in a single block for agent consumption.
# Used by the gitea-issues-process agent to get the ticket content before calling /fix or /evol. # Used by the git-issues-process agent to get the ticket content before calling /fix or /evol.
# Usage: ./print-issue-prompt.sh <issue_number> # Usage: ./print-issue-prompt.sh <issue_number>
# #
set -euo pipefail set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" source "${GIT_ISSUES_DIR}/lib.sh"
require_jq || exit 1 require_jq || exit 1

View File

@ -9,26 +9,26 @@ from pathlib import Path
def project_root() -> Path: def project_root() -> Path:
"""Project repo root when applicable. Derived from PROJECT_ROOT, REPO_ROOT or GITEA_ISSUES_DIR.""" """Project repo root when applicable. Derived from PROJECT_ROOT, REPO_ROOT or GIT_ISSUES_DIR."""
env_root = os.environ.get("PROJECT_ROOT") env_root = os.environ.get("PROJECT_ROOT")
if env_root: if env_root:
return Path(env_root).resolve() return Path(env_root).resolve()
env_repo = os.environ.get("REPO_ROOT") env_repo = os.environ.get("REPO_ROOT")
if env_repo: if env_repo:
root = Path(env_repo).resolve() root = Path(env_repo).resolve()
# If REPO_ROOT is ia_dev (has gitea-issues), use it as repo root # If REPO_ROOT is ia_dev (has git-issues), use it as repo root
if (root / "gitea-issues").is_dir(): if (root / "git-issues").is_dir():
return root return root
return root return root
issues_dir = os.environ.get("GITEA_ISSUES_DIR") issues_dir = os.environ.get("GIT_ISSUES_DIR")
if issues_dir: if issues_dir:
return Path(issues_dir).resolve().parent.parent return Path(issues_dir).resolve().parent.parent
return Path(__file__).resolve().parent.parent.parent return Path(__file__).resolve().parent.parent.parent
def ia_dev_root() -> Path: def ia_dev_root() -> Path:
"""Directory containing gitea-issues (ia_dev).""" """Directory containing git-issues (ia_dev)."""
issues_dir = os.environ.get("GITEA_ISSUES_DIR") issues_dir = os.environ.get("GIT_ISSUES_DIR")
if issues_dir: if issues_dir:
return Path(issues_dir).resolve().parent return Path(issues_dir).resolve().parent
return Path(__file__).resolve().parent.parent return Path(__file__).resolve().parent.parent

View File

@ -8,7 +8,7 @@ Messages on or after MAIL_SINCE_DATE are considered. Does not use UNSEEN; does n
Writes to projects/<id>/data/issues/ as JSON <date>.<msg_id>.<from>.pending. One file per message. Writes to projects/<id>/data/issues/ as JSON <date>.<msg_id>.<from>.pending. One file per message.
State: we skip creating .pending if .pending exists or .response exists for that base. State: we skip creating .pending if .pending exists or .response exists for that base.
Usage: run with GITEA_ISSUES_DIR set (e.g. via tickets-fetch-inbox.sh). MAIL_SINCE_DATE overrides date (DD-Mon-YYYY). Usage: run with GIT_ISSUES_DIR set (e.g. via tickets-fetch-inbox.sh). MAIL_SINCE_DATE overrides date (DD-Mon-YYYY).
""" """
from __future__ import annotations from __future__ import annotations

View File

@ -1,17 +1,17 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Fetch inbox messages filtered by tickets.authorized_emails (conf.json). No UNSEEN; no mark read. # Fetch inbox messages filtered by tickets.authorized_emails (conf.json). No UNSEEN; no mark read.
# Writes new messages to projects/<id>/data/issues/ as JSON (<date>.<id>.<from>.pending). # Writes new messages to projects/<id>/data/issues/ as JSON (<date>.<id>.<from>.pending).
# Usage: depuis la racine de ia_dev (MAIL_TO ou AI_AGENT_TOKEN défini) : ./gitea-issues/tickets-fetch-inbox.sh # Usage: depuis la racine de ia_dev (MAIL_TO ou AI_AGENT_TOKEN défini) : ./git-issues/tickets-fetch-inbox.sh
set -euo pipefail set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export GITEA_ISSUES_DIR export GIT_ISSUES_DIR
# Use same project root and env as list-pending-spooler (lib.sh) so spool path is identical. # Use same project root and env as list-pending-spooler (lib.sh) so spool path is identical.
ROOT="$(cd "${GITEA_ISSUES_DIR}/../.." && pwd)" ROOT="$(cd "${GIT_ISSUES_DIR}/../.." && pwd)"
export REPO_ROOT="${GITEA_ISSUES_DIR}/.." export REPO_ROOT="${GIT_ISSUES_DIR}/.."
cd "$ROOT" cd "$ROOT"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" 2>/dev/null || true source "${GIT_ISSUES_DIR}/lib.sh" 2>/dev/null || true
export PROJECT_ROOT="${PROJECT_ROOT:-$ROOT}" export PROJECT_ROOT="${PROJECT_ROOT:-$ROOT}"
cd "$PROJECT_ROOT" cd "$PROJECT_ROOT"
exec python3 "${GITEA_ISSUES_DIR}/tickets-fetch-inbox.py" "$@" exec python3 "${GIT_ISSUES_DIR}/tickets-fetch-inbox.py" "$@"

View File

@ -1,15 +1,15 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# Test Gitea Wiki API for repo 4nk/lecoffre_ng. # Test Gitea Wiki API for repo 4nk/lecoffre_ng.
# Requires GITEA_TOKEN or .secrets/gitea-issues/token (same as issues scripts). # Requires GITEA_TOKEN or .secrets/git-issues/token (same as issues scripts).
# Usage: ./wiki-api-test.sh [--create] # Usage: ./wiki-api-test.sh [--create]
# --create: create a test page then delete it (checks write access). # --create: create a test page then delete it (checks write access).
# #
set -euo pipefail set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" source "${GIT_ISSUES_DIR}/lib.sh"
REPO_PATH="/repos/${GITEA_REPO_OWNER}/${GITEA_REPO_NAME}" REPO_PATH="/repos/${GITEA_REPO_OWNER}/${GITEA_REPO_NAME}"
# Branch ref for wiki (default branch of wiki repo; use master when wiki is configured on master) # Branch ref for wiki (default branch of wiki repo; use master when wiki is configured on master)
@ -27,7 +27,7 @@ while [[ $# -gt 0 ]]; do
done done
if ! load_gitea_token 2>/dev/null; then if ! load_gitea_token 2>/dev/null; then
log_err "No GITEA_TOKEN and no .secrets/gitea-issues/token. Set token to run wiki API tests." log_err "No GITEA_TOKEN and no .secrets/git-issues/token. Set token to run wiki API tests."
exit 1 exit 1
fi fi

View File

@ -3,13 +3,13 @@
# Output the raw markdown of a wiki page (for agents or scripts). # Output the raw markdown of a wiki page (for agents or scripts).
# Usage: ./wiki-get-page.sh <page_name> # Usage: ./wiki-get-page.sh <page_name>
# Example: ./wiki-get-page.sh Home # Example: ./wiki-get-page.sh Home
# Requires GITEA_TOKEN or .secrets/gitea-issues/token. # Requires GITEA_TOKEN or .secrets/git-issues/token.
# #
set -euo pipefail set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" source "${GIT_ISSUES_DIR}/lib.sh"
REPO_PATH="/repos/${GITEA_REPO_OWNER}/${GITEA_REPO_NAME}" REPO_PATH="/repos/${GITEA_REPO_OWNER}/${GITEA_REPO_NAME}"
GITEA_WIKI_REF="${GITEA_WIKI_REF:-master}" GITEA_WIKI_REF="${GITEA_WIKI_REF:-master}"

View File

@ -2,18 +2,18 @@
# #
# Migrate all docs/*.md (repo root) to Gitea wiki as pages. # Migrate all docs/*.md (repo root) to Gitea wiki as pages.
# Mapping: docs/FILE.md → page "File" (stem with _ → -, first letter upper per segment). # Mapping: docs/FILE.md → page "File" (stem with _ → -, first letter upper per segment).
# Requires GITEA_TOKEN or .secrets/gitea-issues/token. # Requires GITEA_TOKEN or .secrets/git-issues/token.
# Usage: ./wiki-migrate-docs.sh [--dry-run] [file.md ...] # Usage: ./wiki-migrate-docs.sh [--dry-run] [file.md ...]
# --dry-run: print mapping and skip API calls. # --dry-run: print mapping and skip API calls.
# If file(s) given: migrate only those; else migrate all docs/*.md. # If file(s) given: migrate only those; else migrate all docs/*.md.
# #
set -euo pipefail set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
REPO_ROOT="${GITEA_ISSUES_DIR}/.." REPO_ROOT="${GIT_ISSUES_DIR}/.."
DOCS_DIR="${DOCS_DIR:-${REPO_ROOT}/docs}" DOCS_DIR="${DOCS_DIR:-${REPO_ROOT}/docs}"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" source "${GIT_ISSUES_DIR}/lib.sh"
REPO_PATH="/repos/${GITEA_REPO_OWNER}/${GITEA_REPO_NAME}" REPO_PATH="/repos/${GITEA_REPO_OWNER}/${GITEA_REPO_NAME}"
GITEA_WIKI_REF="${GITEA_WIKI_REF:-master}" GITEA_WIKI_REF="${GITEA_WIKI_REF:-master}"

View File

@ -3,13 +3,13 @@
# Update a single wiki page from a local file. # Update a single wiki page from a local file.
# Usage: ./wiki-put-page.sh <page_name> <file_path> # Usage: ./wiki-put-page.sh <page_name> <file_path>
# Example: ./wiki-put-page.sh Home docs/README.md # Example: ./wiki-put-page.sh Home docs/README.md
# Requires GITEA_TOKEN or .secrets/gitea-issues/token. # Requires GITEA_TOKEN or .secrets/git-issues/token.
# #
set -euo pipefail set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
# shellcheck source=lib.sh # shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" source "${GIT_ISSUES_DIR}/lib.sh"
REPO_PATH="/repos/${GITEA_REPO_OWNER}/${GITEA_REPO_NAME}" REPO_PATH="/repos/${GITEA_REPO_OWNER}/${GITEA_REPO_NAME}"
GITEA_WIKI_REF="${GITEA_WIKI_REF:-master}" GITEA_WIKI_REF="${GITEA_WIKI_REF:-master}"

View File

@ -3,7 +3,7 @@
Update the single spooler file for a message after sending a reply. Update the single spooler file for a message after sending a reply.
One file per message: read the .pending file (same base), add response data and set status to responded, write back. One file per message: read the .pending file (same base), add response data and set status to responded, write back.
No separate .response file; no file deletion. No separate .response file; no file deletion.
Usage: ./gitea-issues/write-response-spooler.sh --base <base> --to <addr> --subject "..." --body "..." [--in-reply-to "<msg-id>"] Usage: ./git-issues/write-response-spooler.sh --base <base> --to <addr> --subject "..." --body "..." [--in-reply-to "<msg-id>"]
base = filename base without extension (e.g. 2026-03-14T094530.a1b2c3d4.user_example.com). base = filename base without extension (e.g. 2026-03-14T094530.a1b2c3d4.user_example.com).
""" """
from __future__ import annotations from __future__ import annotations

View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Update the single spooler file (.pending) after sending a reply via mail-send-reply.sh. One file per message.
# Usage: depuis la racine de ia_dev : ./git-issues/write-response-spooler.sh --base <base> --to <addr> --subject "..." --body "..." [--in-reply-to "<msg-id>"]
set -euo pipefail
GIT_ISSUES_DIR="${GIT_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export GIT_ISSUES_DIR
export REPO_ROOT="${GIT_ISSUES_DIR}/.."
_IA_FOR_LOG="$(cd "${GIT_ISSUES_DIR}/.." && pwd)"
if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh
source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh"
smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*"
fi
exec python3 "${GIT_ISSUES_DIR}/write-response-spooler.py" "$@"

View File

@ -1,52 +0,0 @@
#!/usr/bin/env bash
# Treatment loop: periodically check agent-loop.pending and run Cursor Agent CLI (gitea-issues-process workflow) when non-empty.
# Run from repo root. No timeout; runs forever. Do NOT start this script from the agent-loop agent (use bounded runs only).
#
# Usage (manual only; agent must not launch with nohup/&):
# Depuis la racine de ia_dev : ./gitea-issues/agent-loop-treatment.sh
#
set -euo pipefail
if [ -n "${HOME:-}" ] && [ -r "$HOME/.bashrc" ]; then
set +u
# shellcheck source=/dev/null
source "$HOME/.bashrc" 2>/dev/null || true
set -u
fi
[ -n "${HOME:-}" ] && [ -d "$HOME/.local/bin" ] && export PATH="$HOME/.local/bin:$PATH"
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || ROOT="$(cd "${GITEA_ISSUES_DIR}/../.." && pwd)"
export GITEA_ISSUES_DIR
export REPO_ROOT="${ROOT}"
cd "$ROOT"
# shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" 2>/dev/null || true
LOGS_GITEA="${PROJECT_LOGS_DIR:-$ROOT/logs}/gitea-issues"
PENDING_FILE="${AGENT_LOOP_PENDING_FILE:-$LOGS_GITEA/agent-loop.pending}"
LOG_DIR="$(dirname "$PENDING_FILE")"
mkdir -p "$LOG_DIR"
AGENT_LOOP_ENV="${GITEA_ISSUES_DIR}/../.secrets/gitea-issues/agent-loop.env"
if [ -r "$AGENT_LOOP_ENV" ]; then
set +u
# shellcheck source=/dev/null
source "$AGENT_LOOP_ENV"
set -u
fi
INTERVAL="${AGENT_LOOP_TREATMENT_INTERVAL_SEC:-60}"
AGENT_MODEL="${AGENT_LOOP_MODEL:-sonnet-4.6}"
PROMPT="Exécute le workflow mails du spooler (agent gitea-issues-process). Les chemins des fichiers .pending sont dans projects/<id>/logs/gitea-issues/agent-loop.pending (ou exécuter ./gitea-issues/list-pending-spooler.sh). Pour chaque fichier .pending : lire le JSON (from, to, subject, body, message_id, base). Répondre à l'expéditeur (--to <from> du JSON), pas à une adresse fixe ; le « to » du mail reçu a déjà déterminé le projet. Rédiger une réponse pertinente (uniquement ton texte ; pas de citation — mail-send-reply.sh refuse si le body contient From:, Message-ID, wrote:, etc.). Envoyer avec ./gitea-issues/mail-send-reply.sh --to <from> --subject \"Re: ...\" --body \"<ta_réponse>\" --in-reply-to \"<message_id>\". Après envoi réussi : ./gitea-issues/write-response-spooler.sh --base <base> --to <from> --subject \"Re: ...\" --body \"<ta_réponse>\" --in-reply-to \"<message_id>\". Ne pas appeler mail-mark-read.sh (spooler)."
while true; do
if [ -s "$PENDING_FILE" ] && command -v agent >/dev/null 2>&1; then
echo "[agent-loop-treatment] $(date -Iseconds) — Pending non vide, lancement de l'agent Cursor."
if agent -p "$PROMPT" -f --model "$AGENT_MODEL" 2>&1; then
echo "[agent-loop-treatment] $(date -Iseconds) — Agent terminé."
else
echo "[agent-loop-treatment] $(date -Iseconds) — Agent terminé avec erreur."
fi
fi
sleep "$INTERVAL"
done

View File

@ -1,19 +0,0 @@
# Agent-loop parameters (Cursor Agent CLI, model, interval).
# Copy to .secrets/gitea-issues/agent-loop.env and set as needed.
# Do not commit .secrets/gitea-issues/agent-loop.env (directory is gitignored).
#
# Run Cursor Agent when unread mails are detected (0 or 1)
# AGENT_LOOP_RUN_AGENT=1
#
# Model used by the CLI (default: sonnet-4.6 to avoid Opus usage limits)
# List: agent models
# AGENT_LOOP_MODEL=sonnet-4.6
#
# Polling interval in seconds (default: 60)
# AGENT_LOOP_INTERVAL_SEC=60
#
# Optional: custom paths for status, pending, lock and stop files
# AGENT_LOOP_STATUS_FILE=ia_dev/projects/<id>/logs/gitea-issues/agent-loop.status
# AGENT_LOOP_PENDING_FILE=ia_dev/projects/<id>/logs/gitea-issues/agent-loop.pending
# AGENT_LOOP_LOCK_FILE=ia_dev/projects/<id>/logs/gitea-issues/agent-loop.lock
# AGENT_LOOP_STOP_FILE=ia_dev/projects/<id>/logs/gitea-issues/agent-loop.stop

View File

@ -1,14 +0,0 @@
#!/usr/bin/env bash
# Create one Gitea issue from one email (by UID), mark email read. Run from repo root.
# Usage: ./gitea-issues/mail-create-issue-from-email.sh --uid <uid> [--title "..." ] [--body "..." ]
set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export GITEA_ISSUES_DIR
export REPO_ROOT="${GITEA_ISSUES_DIR}/.."
_IA_FOR_LOG="$(cd "${GITEA_ISSUES_DIR}/.." && pwd)"
if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh
source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh"
smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*"
fi
exec python3 "${GITEA_ISSUES_DIR}/mail-create-issue-from-email.py" "$@"

View File

@ -1,14 +0,0 @@
#!/usr/bin/env bash
# Mark one email as read by UID. Run from repo root.
# Usage: ./gitea-issues/mail-mark-read.sh <uid>
set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export GITEA_ISSUES_DIR
export REPO_ROOT="${GITEA_ISSUES_DIR}/.."
_IA_FOR_LOG="$(cd "${GITEA_ISSUES_DIR}/.." && pwd)"
if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh
source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh"
smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*"
fi
exec python3 "${GITEA_ISSUES_DIR}/mail-mark-read.py" "$@"

View File

@ -1,14 +0,0 @@
#!/usr/bin/env bash
# Send reply email via Bridge SMTP. Run from repo root.
# Usage: ./gitea-issues/mail-send-reply.sh --to addr --subject "..." [--body "..." | stdin] [--in-reply-to "<msg-id>" [--references "..." ]]
set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export GITEA_ISSUES_DIR
export REPO_ROOT="${GITEA_ISSUES_DIR}/.."
_IA_FOR_LOG="$(cd "${GITEA_ISSUES_DIR}/.." && pwd)"
if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh
source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh"
smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*"
fi
exec python3 "${GITEA_ISSUES_DIR}/mail-send-reply.py" "$@"

View File

@ -1,19 +0,0 @@
#!/usr/bin/env bash
# Thread log: one file per thread under projects/<id>/logs/gitea-issues/threads/. Run from repo root.
# Usage:
# ./gitea-issues/mail-thread-log.sh get-id --uid <uid>
# ./gitea-issues/mail-thread-log.sh init --uid <uid>
# ./gitea-issues/mail-thread-log.sh append-sent --thread-id <id> --to <addr> --subject "..." [--body "..."] [--date "..."]
# ./gitea-issues/mail-thread-log.sh append-issue --thread-id <id> --issue <num> [--title "..."]
# ./gitea-issues/mail-thread-log.sh append-commit --thread-id <id> --hash <hash> --message "..." [--branch "..."]
set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export GITEA_ISSUES_DIR
export REPO_ROOT="${GITEA_ISSUES_DIR}/.."
_IA_FOR_LOG="$(cd "${GITEA_ISSUES_DIR}/.." && pwd)"
if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh
source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh"
smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*"
fi
exec python3 "${GITEA_ISSUES_DIR}/mail-thread-log.py" "$@"

View File

@ -1,18 +0,0 @@
#!/usr/bin/env bash
#
# Create Gitea issues from unread emails (IMAP). Requires Proton Mail Bridge
# or any IMAP server. Config: .secrets/gitea-issues/imap-bridge.env and token.
# Usage: ./gitea-issues/mail-to-issue.sh
#
set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export GITEA_ISSUES_DIR
export REPO_ROOT="${GITEA_ISSUES_DIR}/.."
_IA_FOR_LOG="$(cd "${GITEA_ISSUES_DIR}/.." && pwd)"
if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh
source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh"
smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*"
fi
exec python3 "${GITEA_ISSUES_DIR}/mail-to-issue.py"

View File

@ -1,14 +0,0 @@
#!/usr/bin/env bash
# Update the single spooler file (.pending) after sending a reply via mail-send-reply.sh. One file per message.
# Usage: depuis la racine de ia_dev : ./gitea-issues/write-response-spooler.sh --base <base> --to <addr> --subject "..." --body "..." [--in-reply-to "<msg-id>"]
set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
export GITEA_ISSUES_DIR
export REPO_ROOT="${GITEA_ISSUES_DIR}/.."
_IA_FOR_LOG="$(cd "${GITEA_ISSUES_DIR}/.." && pwd)"
if [[ -f "${_IA_FOR_LOG}/lib/smart_ide_logs.sh" ]]; then
# shellcheck source=../lib/smart_ide_logs.sh
source "${_IA_FOR_LOG}/lib/smart_ide_logs.sh"
smart_ide_logs_begin "$_IA_FOR_LOG" "$0" "$*"
fi
exec python3 "${GITEA_ISSUES_DIR}/write-response-spooler.py" "$@"

View File

@ -23,11 +23,11 @@
}, },
"mail": { "mail": {
"email": "ai.support.algo@4nkweb.com", "email": "ai.support.algo@4nkweb.com",
"imap_bridge_env": ".secrets/gitea-issues/imap-bridge.env" "imap_bridge_env": ".secrets/git-issues/imap-bridge.env"
}, },
"git": { "git": {
"wiki_url": "https://git.4nkweb.com/nicolas.cantu/algo/wiki", "wiki_url": "https://git.4nkweb.com/nicolas.cantu/algo/wiki",
"token_file": ".secrets/gitea-issues/token" "token_file": ".secrets/git-issues/token"
}, },
"tickets": { "tickets": {
"ticketing_url": "https://git.4nkweb.com/nicolas.cantu/algo/issues", "ticketing_url": "https://git.4nkweb.com/nicolas.cantu/algo/issues",

1
projects/builazoo Symbolic link
View File

@ -0,0 +1 @@
../../projects/builazoo

1
projects/enso Symbolic link
View File

@ -0,0 +1 @@
../../projects/enso

View File

@ -1,99 +0,0 @@
{
"id": "enso",
"name": "enso",
"cron": {
"git_pull": true
},
"project_path": "../enso",
"build_dirs": [
".",
"enso/enso-front"
],
"deploy": {
"repository_root": "../enso",
"scripts_path": "../enso/deploy/scripts_v2",
"deploy_script_path": "../enso/deploy/scripts_v2/deploy.sh",
"project_orchestrator_path": "deploy/scripts_v2/deploy.sh",
"secrets_path": "../enso/.secrets"
},
"version": {
"package_json_paths": [
"package.json",
"enso/enso-front/package.json"
],
"splash_app_name": "enso"
},
"mail": {
"imap_bridge_env": ".secrets/gitea-issues/imap-bridge.env"
},
"git": {
"wiki_url": "https://git.4nkweb.com/4nk/enso/wiki",
"token_file": ".secrets/gitea-issues/token"
},
"tickets": {
"ticketing_url": "https://git.4nkweb.com/4nk/enso/issues",
"authorized_emails": {
"to": [
{
"test": "AI.ENSO.TEST@4nkweb.com",
"pprod": "AI.ENSO.PPROD@4nkweb.com",
"prod": "AI.ENSO.PROD@4nkweb.com"
}
],
"from": ["nicolas.4nk@pm.me"]
}
},
"smart_ide": {
"remote_data_access": {
"environments": {
"test": {
"ssh_host_alias": "test",
"remote_app_root": "/home/ncantu/enso",
"remote_data_directories": [
{
"role": "enso_monorepo_clone",
"path_on_server": "/home/ncantu/enso"
},
{
"role": "docv_dp_git_data",
"path_on_server": "/home/ncantu/enso/data/dossiers-permanents"
}
]
},
"pprod": {
"ssh_host_alias": "pprod",
"remote_app_root": "/home/ncantu/enso",
"remote_data_directories": [
{
"role": "enso_monorepo_clone",
"path_on_server": "/home/ncantu/enso"
},
{
"role": "docv_dp_git_data",
"path_on_server": "/home/ncantu/enso/data/dossiers-permanents"
}
]
},
"prod": {
"ssh_host_alias": "prod",
"remote_app_root": "/home/ncantu/enso",
"remote_data_directories": [
{
"role": "enso_monorepo_clone",
"path_on_server": "/home/ncantu/enso"
},
{
"role": "docv_dp_git_data",
"path_on_server": "/home/ncantu/enso/data/dossiers-permanents"
}
]
}
}
},
"anythingllm_workspace_slug": {
"test": "enso-test",
"pprod": "enso-pprod",
"prod": "enso-prod"
}
}
}

View File

@ -9,11 +9,11 @@
"splash_app_name": "ia_dev" "splash_app_name": "ia_dev"
}, },
"mail": { "mail": {
"imap_bridge_env": ".secrets/gitea-issues/imap-bridge.env" "imap_bridge_env": ".secrets/git-issues/imap-bridge.env"
}, },
"git": { "git": {
"wiki_url": "https://git.4nkweb.com/4nk/ia_dev/wiki", "wiki_url": "https://git.4nkweb.com/4nk/ia_dev/wiki",
"token_file": ".secrets/gitea-issues/token" "token_file": ".secrets/git-issues/token"
}, },
"tickets": { "tickets": {
"ticketing_url": "https://git.4nkweb.com/4nk/ia_dev/issues", "ticketing_url": "https://git.4nkweb.com/4nk/ia_dev/issues",

View File

@ -1,160 +0,0 @@
# Gitea issues scripts et agents
Dossier dédié au traitement des tickets (issues) Gitea du dépôt **4nk/lecoffre_ng** (https://git.4nkweb.com/4nk/lecoffre_ng/issues). Toute la logique d'appel API et Git est dans les scripts ; les agents orchestrent et appellent /fix ou /evol.
## Contraintes de confidentialité et d'accès des agents
L'agent ne doit **jamais révéler** :
- Quelle IA il utilise
- Des informations personnelles
- Des informations sur 4NK
- Des informations sur l'équipe de réalisation
- Des informations sur des fichiers hors périmètre du projet configuré
- Des informations sur les autres questions et réponses traitées
L'agent ne doit **jamais accéder ni modifier** :
- Les fichiers hors projet (périmètre = ia_dev et dépôt du projet configuré via conf.json)
- Les fichiers dans `.smartIde` et `.secrets`
- D'autres branches que celle en cours de travail
- D'autres environnements (test / pprod / prod) que celui défini par le contexte
## Agents (.smartIde/agents/)
| Agent | Fichier | Rôle |
|-------|---------|------|
| **agent-loop** | `agent-loop.md` | Orchestre la boucle de récupération des mails et le traitement par **exécutions délimitées** uniquement (N itérations ou x cycles) ; ne lance jamais de processus en arrière-plan (nohup/&). |
| **gitea-issues-process** | `gitea-issues-process.md` | Traite les issues Gitea et les mails en attente : liste les issues/mails, crée des branches, lance /fix ou /evol, /push-by-script ; workflow mails (fil, réponse réelle, marquage lu). |
Spooler tickets (nouveau) : mails dans **projects/<id>/data/issues/** (filtre par `tickets.authorized_emails` dans conf.json), pas de « non lu », aucun enregistrement supprimé. Format : `projects/ia_dev/docs/TICKETS_SPOOL_FORMAT.md`. Récupération : `./gitea-issues/tickets-fetch-inbox.sh`. Référence boucle mails (legacy) : `.smartIde/agents/agent-loop.md`. Hook Cursor : `sessionStart``.smartIde/hooks/remonter-mails.sh` (lit `projects/<id>/data/issues/*.pending`).
## Contexte d'exécution
- **Emplacement (usage standalone)** : ia_dev est un dépôt autonome. `gitea-issues/` est à la racine de ia_dev. Un même clone ia_dev peut servir plusieurs projets (id résolu par MAIL_TO ou AI_AGENT_TOKEN).
- **Projet cible** : le projet est identifié **dynamiquement** par **MAIL_TO** (adresse « to » des mails, recherchée dans les configs ticketing de tous les projets) ou **AI_AGENT_TOKEN** (token des requêtes). L'id sert à charger la config dans `projects/<id>/`. Pas de fallback. Voir `docs/repo/ia-dev-project-conf-schema.md` (racine monorepo **smart_ide**).
- **Lancement** : tous les scripts sont invoqués depuis la **racine de ia_dev** : `./gitea-issues/<script>.sh`. Les opérations (issues, mails, déploiement) utilisent les chemins absolus de `projects/<id>/conf.json` pour le projet cible.
- **Secrets et logs** : `.secrets` est à la racine de ia_dev (`.secrets/gitea-issues/token`, `agent-loop.env`, `imap-bridge.env`). Les logs et data par projet sont sous `projects/<id>/logs/` et `projects/<id>/data/issues/`.
## Prérequis
- **jq** : `apt install jq` ou `brew install jq`
- **Token Gitea** : variable d'environnement `GITEA_TOKEN` ou fichier `.secrets/gitea-issues/token` (contenu = le token, non versionné). Créer le token dans Gitea : Settings → Applications → Generate New Token (scopes `read:issue`, `write:issue` si commentaires).
## Scripts (depuis la racine de ia_dev)
| Script | Usage | Description |
|--------|--------|-------------|
| `list-open-issues.sh` | `./gitea-issues/list-open-issues.sh [--lines] [--limit N]` | Liste les issues ouvertes (JSON ou lignes `number\|title\|state`). |
| `get-issue.sh` | `./gitea-issues/get-issue.sh <num> [--summary]` | Détail d'une issue (JSON ou résumé texte). |
| `print-issue-prompt.sh` | `./gitea-issues/print-issue-prompt.sh <num>` | Affiche titre + corps pour fournir la consigne à l'agent. |
| `create-branch-for-issue.sh` | `./gitea-issues/create-branch-for-issue.sh <num> [base]` | Crée et checkout la branche `issue/<num>` depuis `base` (défaut `test`). |
| `comment-issue.sh` | `./gitea-issues/comment-issue.sh <num> <message>` ou `echo "msg" \| ./gitea-issues/comment-issue.sh <num> -` | Ajoute un commentaire à l'issue. |
| `mail-list-unread.sh` | `./gitea-issues/mail-list-unread.sh` | Liste les mails **non lus envoyés à l'alias** (MAIL_FILTER_TO), **à partir du 10 mars 2026** (MAIL_SINCE_DATE) ; lecture seule ; sortie : UID, Message-ID, From, To, Subject, Date, Body. Aucun autre mail n'est listé. |
| `mail-get-thread.sh` | `./gitea-issues/mail-get-thread.sh <uid>` | Récupère **tout le fil** (conversation) du mail donné : tous les messages liés par References/In-Reply-To, tri chronologique (ancien → récent). Même format de sortie que mail-list-unread. À utiliser avant de décider ou répondre sur un mail. |
| `mail-send-reply.sh` | `./gitea-issues/mail-send-reply.sh --to <addr> --subject "..." [--body "..." \| stdin] [--in-reply-to "<msg-id>" [--references "..."]]` | Envoie une réponse par mail via le Bridge (SMTP) ; signature « Support IA du projet Lecoffre.io » / ai.support.lecoffreio@4nkweb.com ajoutée automatiquement. |
| `mail-create-issue-from-email.sh` | `./gitea-issues/mail-create-issue-from-email.sh --uid <uid> [--title "..." ] [--body "..."]` | Crée une issue à partir d'un mail (UID), optionnel titre/corps formalisés ; marque le mail lu. |
| `mail-mark-read.sh` | `./gitea-issues/mail-mark-read.sh <uid>` | Marque un mail comme lu. |
| `mail-thread-log.sh` | `./gitea-issues/mail-thread-log.sh get-id \| init \| append-sent \| append-issue \| append-commit ...` | **Log par fil** : un fichier par conversation dans `projects/<id>/logs/gitea-issues/threads/` (échanges reçus/envoyés, tickets, commits). `get-id --uid <uid>` affiche `THREAD_ID=...` ; `init --uid <uid>` crée/met à jour le fichier ; `append-sent/issue/commit` enregistrent une réponse, une issue ou un commit. |
| `mail-to-issue.sh` | `./gitea-issues/mail-to-issue.sh` | **Batch** : crée une issue par mail non lu (titre = sujet, corps = texte + From), marque lus. À éviter si on suit le workflow agent (voir cidessous). |
| `agent-loop.sh` | `./gitea-issues/agent-loop.sh [interval_sec]` | **Boucle de surveillance** : tourne indéfiniment (spooler ou legacy). **Ne pas lancer depuis l'agent** ; utiliser agent-loop-chat-iterations.sh ou cycles délimités. Voir `.smartIde/agents/agent-loop.md`. |
| `agent-loop-treatment.sh` | `./gitea-issues/agent-loop-treatment.sh` | **Boucle traitement** : vérifie périodiquement `agent-loop.pending` ; si non vide, lance l'agent Cursor. Tourne indéfiniment ; **ne pas lancer depuis l'agent** (préférer agent-loop-chat-iterations.sh ou cycles délimités). |
| `agent-loop-retrieval-once.sh` | `./gitea-issues/agent-loop-retrieval-once.sh` | **Récupération une fois** (legacy, basé non lu) : exécute `mail-list-unread.sh` et écrit dans `agent-loop.pending`. Utilisé par l'agent agent-loop pour les cycles « x fois ». |
| `agent-loop-lock-acquire.sh` | `./gitea-issues/agent-loop-lock-acquire.sh` | **Lock (section 2)** : acquiert `agent-loop.lock` si absent ou périmé (>24 h). Exit 1 si lock actif ; l'agent ne doit pas lancer une deuxième instance. |
| `agent-loop-lock-release.sh` | `./gitea-issues/agent-loop-lock-release.sh` | **Lock (section 2)** : supprime `agent-loop.lock` et `agent-loop.stop`. À exécuter en fin de cycles (normale ou arrêt). |
| `agent-loop-stop.sh` | `./gitea-issues/agent-loop-stop.sh` | **Arrêt à la demande** : crée `agent-loop.stop` ; l'instance en cours s'arrête au début du cycle suivant. |
| `agent-loop-stop-requested.sh` | `./gitea-issues/agent-loop-stop-requested.sh` | **Vérification arrêt** : exit 0 si `agent-loop.stop` existe (utilisé par l'agent au début de chaque cycle). |
| `agent-loop-is-running.sh` | `./gitea-issues/agent-loop-is-running.sh` | **Vérifier si l'agent est en cours** : affiche fichier lock, PID et date ; indique si le processus est actif ou lock orphelin. Exit 0 si instance en cours (lock mtime < 24 h), 1 sinon. |
| `agent-loop-n-cycles.sh` | `./gitea-issues/agent-loop-n-cycles.sh [N]` | **N cycles (récupération + 1 min)** : exécute N fois (récupération une fois + sleep 60) en **avant-plan** (défaut N=600). Utilise le même lock que la section 2. Arrêt : `agent-loop-stop.sh`. Ne lance pas l'agent ; lancer **gitea-issues-process** à part pour traiter les pendings. Permet de faire tourner réellement N × 1 minute. |
| `tickets-fetch-inbox.sh` | `./gitea-issues/tickets-fetch-inbox.sh` | **Récupération et filtrage par le script** : le script (et le Python qu'il appelle) **récupère** les mails en boîte et les **filtre** (to, from, `tickets.authorized_emails`, date). L'agent ne fait que lancer le script. Mails à partir du 10 mars 2026 (MAIL_SINCE_DATE). Écrit les mails retenus dans `projects/<id>/data/issues/*.pending` (JSON). Ne crée pas de .pending si un .response existe déjà. Voir `projects/ia_dev/docs/TICKETS_SPOOL_FORMAT.md`. |
| `list-pending-spooler.sh` | `./gitea-issues/list-pending-spooler.sh` | **Spooler** : liste les fichiers `projects/<id>/data/issues/*.pending` qui n'ont **pas** de fichier `.response` correspondant (mails à traiter). Une ligne par chemin. |
| `write-response-spooler.sh` | `./gitea-issues/write-response-spooler.sh --base <base> --to <addr> --subject "..." --body "..." [--in-reply-to "<msg-id>"]` | **Spooler** : après envoi réussi avec `mail-send-reply.sh`, écrit `projects/<id>/data/issues/<base>.response` (JSON de la réponse envoyée). |
Variables optionnelles : `GITEA_API_URL`, `GITEA_REPO_OWNER`, `GITEA_REPO_NAME`, `GITEA_ISSUES_DIR`.
### Fichiers témoins et logs (boucle agent)
Sous `projects/<id>/logs/gitea-issues/` :
- **agent-loop.status** : fichier témoin de la boucle de surveillance (`agent-loop.sh` ou `agent-loop-retrieval-once.sh`). Trois lignes : horodatage (ISO), statut (`running` ou `idle`), détail (ex. « Aucun mail non lu »). Mis à jour à chaque itération. Si la date de modification est plus récente que 2× lintervalle (ex. 120 s pour intervalle 60 s), la boucle est considérée active.
- **agent-loop.pending** : liste des mails non lus (legacy) écrite par `mail-list-unread.sh` via la boucle ; lagent gitea-issues-process traite ce fichier.
- **agent-loop.lock** : lock pour une seule instance (section 2). Contenu : PID + date. Si mtime < 24 h, une nouvelle instance ne doit pas démarrer. Géré par `agent-loop-lock-acquire.sh` / `agent-loop-lock-release.sh`.
- **agent-loop.stop** : si présent, l'instance en cours s'arrête au début du cycle suivant. Création : `agent-loop-stop.sh` ou `touch …/agent-loop.stop`.
- **agent-loop-chat-iterations.log** : sortie de `agent-loop-chat-iterations.sh` (test denvoi au lancement, puis à chaque itération le résultat de `mail-list-unread.sh`).
Le répertoire **projects/<id>/data/issues/** (spooler) est rempli **uniquement** par `./gitea-issues/tickets-fetch-inbox.sh`. Si ce script n'est pas exécuté, `data/issues` reste vide si on nutilise que la boucle legacy utilise seulement mail-list-unread.sh et agent-loop.pending dans logs/gitea-issues/. **Récupération et filtrage** : c'est le **script** (et le Python) qui récupère les mails et les filtre (to, from, authorized_emails, date) ; l'agent se contente de lancer le script. Le script route chaque message vers le projet dont `tickets.authorized_emails.to` correspond au « to » du mail ; les réponses sont envoyées à l'**expéditeur** (« from »), pas à une adresse fixe.
**Réponse mail** : le `--body` de `mail-send-reply.sh` doit être **uniquement le texte rédigé par lagent** (la réponse à lexpéditeur). Ne jamais y mettre le mail reçu, le sujet, une citation ou un bloc « From: » / « Message-ID » : sinon le destinataire reçoit son propre message au lieu de la réponse.
### Création d'issues depuis les mails (IMAP) workflow agent
**Ne pas enchaîner directement** : l'agent doit d'abord lire les non lus, formaliser l'issue ou répondre par mail, et ne créer/traiter qu'au moment où la demande est prête.
1. **Lire les non lus** : `./gitea-issues/mail-list-unread.sh` (ne marque pas les mails comme lus).
2. **Pour chaque mail** : consulter **tout l'historique du fil** avec `./gitea-issues/mail-get-thread.sh <uid>`, créer/mettre à jour le **log du fil** avec `./gitea-issues/mail-thread-log.sh init --uid <uid>` (sortie `THREAD_ID=...` à conserver), puis décider soit d'envoyer une réponse directe (demande d'infos) via `mail-send-reply.sh`, soit de formaliser et créer l'issue avec `mail-create-issue-from-email.sh` (optionnel `--title` / `--body` formalisés). Si la demande est une correction/évolution prête : créer l'issue, traiter (fix/evol), commenter l'issue, répondre au mail via `mail-send-reply.sh` (avec `--in-reply-to` pour le fil). **Le corps de la réponse** doit contenir la **réponse réelle** à la question (ex. si le mail demande « Décrit les rôles », le body = une description des rôles), jamais le sujet du mail ni la question reçue.
3. **Réponses aux mails** : toujours via le Bridge avec `mail-send-reply.sh`. Le `--body` doit être la **réponse réelle** rédigée par l'agent (contenu de la réponse à la demande), pas le sujet du mail, pas la question reçue, pas un message précédent du fil. Chaque envoi est enregistré dans le log du fil avec `mail-thread-log.sh append-sent --thread-id <id> --to <addr> --subject "..." --body "..."` pour tracer l'expéditeur, le titre et le corps de la réponse envoyée.
**Prérequis :**
- Python 3 (stdlib : imaplib, email, smtplib, json, urllib).
- Token Gitea : comme les autres scripts (`GITEA_TOKEN` ou `.secrets/gitea-issues/token`).
- Config IMAP/SMTP : copier `gitea-issues/imap-bridge.env.example` vers `.secrets/gitea-issues/imap-bridge.env`. Pour la boucle agent (optionnel) : `agent-loop.env.example` vers `.secrets/gitea-issues/agent-loop.env` (voir `.smartIde/agents/agent-loop.md`). Renseigner `IMAP_USER`, `IMAP_PASSWORD` (et optionnellement `SMTP_*` pour l'envoi ; par défaut SMTP reprend les mêmes host/port Bridge 1025). Optionnel : `MAIL_FILTER_TO=ai.support.lecoffreio@4nkweb.com` (seuls les mails envoyés à cette adresse sont listés) ; `MAIL_SINCE_DATE=10-Mar-2026` (seuls les mails à partir de cette date sont récupérés/listés).
- Proton Mail Bridge (ou serveur IMAP/SMTP) en cours d'exécution.
**Scripts :** `mail-list-unread.sh`, `mail-get-thread.sh`, `mail-thread-log.sh`, `mail-send-reply.sh`, `mail-create-issue-from-email.sh`, `mail-mark-read.sh`. Le script batch `mail-to-issue.sh` reste disponible mais ne doit pas être utilisé dans le cadre du workflow agent (liste → lecture du fil → log du fil → décision → création/ réponse). Le script **`agent-loop.sh`** permet de lancer une boucle de surveillance des mails avec fichier témoin ; voir `.smartIde/agents/agent-loop.md`.
## API Wiki (tests préalables)
Script de test de l'API Wiki Gitea pour le même dépôt (prérequis à une éventuelle migration de `docs/` vers le wiki) :
| Script | Usage | Description |
|--------|--------|-------------|
| `wiki-api-test.sh` | `./gitea-issues/wiki-api-test.sh [--create]` | Teste GET list pages, GET page Home ; avec `--create` : POST une page test puis DELETE. |
**Prérequis :** même token que les issues (`GITEA_TOKEN` ou `.secrets/gitea-issues/token`). Pour l'écriture (création / suppression de pages), le token doit avoir les droits d'écriture sur le dépôt.
**Endpoints utilisés (référence Gitea API 1.25) :**
- `GET /repos/{owner}/{repo}/wiki/pages` — liste des pages
- `GET /repos/{owner}/{repo}/wiki/page/{pageName}` — contenu d'une page (ex. `Home`)
- `POST /repos/{owner}/{repo}/wiki/new` — créer une page (body : `title`, `content_base64`, `message`)
- `PATCH /repos/{owner}/{repo}/wiki/page/{pageName}` — modifier une page
- `DELETE /repos/{owner}/{repo}/wiki/page/{pageName}` — supprimer une page
Si le wiki n'a jamais été initialisé (aucune page créée via l'interface), les GET peuvent renvoyer 404 ou une liste vide. **Initialiser le wiki** : aller sur https://git.4nkweb.com/4nk/lecoffre_ng/wiki et créer au moins une page (ex. « Home ») via l'interface, puis relancer le script avec un token valide.
**Branche par défaut du wiki :** si l'API renvoie `object does not exist [id: refs/heads/master]` alors que la branche par défaut du dépôt wiki est autre (ex. `prod`), c'est un bug connu de certaines versions de Gitea (l'API suppose `master`). Contournements possibles : (1) **mettre à jour Gitea** (correctif dans les versions récentes, ex. PR #34244) ; (2) **changer la branche par défaut du wiki** en `master` dans les réglages du dépôt (Settings → Branches). Variable optionnelle `GITEA_WIKI_REF=master` (défaut si wiki configuré sur master).
### Migration docs/ → wiki
**Décision :** tout le contenu de `docs/` (racine du dépôt) est migré vers le wiki ; pas de CI sur le wiki.
**Script de migration :**
| Script | Usage | Description |
|--------|--------|-------------|
| `wiki-migrate-docs.sh` | `./gitea-issues/wiki-migrate-docs.sh [--dry-run] [fichier.md ...]` | Migre `docs/*.md` vers le wiki. `--dry-run` affiche le mapping sans appel API. Si des fichiers sont passés en argument, migre uniquement ceux-là. |
| `wiki-put-page.sh` | `./gitea-issues/wiki-put-page.sh <page_name> <file_path>` | Met à jour ou crée une page wiki à partir d'un fichier local (ex. `Home docs/README.md`). |
| `wiki-get-page.sh` | `./gitea-issues/wiki-get-page.sh <page_name>` | Affiche le markdown brut d'une page wiki (pour scripts ou agents). |
**Correspondance fichier → page wiki :** nom de fichier sans `.md`, `_` remplacé par `-`, title-case par segment. Ex. OPERATIONS.md → Operations, README.md → Readme.
Les 17 fichiers de `docs/` ont été migrés ; les pages sont visibles sur https://git.4nkweb.com/4nk/lecoffre_ng/wiki. La page **Home** contient le contenu de `docs/README.md` (index et correspondance). **`docs/` est exclu du versionnement** (`.gitignore`) : maintenir `docs/` localement (ne pas le supprimer), pousser les modifications vers le wiki avec `wiki-migrate-docs.sh` ou `wiki-put-page.sh` ; ne pas committer `docs/`.
### Après un clone
Le répertoire `docs/` n'est pas versionné. Pour disposer d'une copie locale (édition puis synchro wiki), recréer le contenu à partir du wiki : ex. `./gitea-issues/wiki-get-page.sh Home > docs/README.md`, ou créer les fichiers manuellement à partir des pages wiki listées dans la section Migration ci-dessus.
### Usage « wiki uniquement » pour les agents
La connaissance du projet peut reposer **uniquement sur le wiki** (sans lire `docs/`) : les agents peuvent exécuter `./gitea-issues/wiki-get-page.sh <PageName>` pour récupérer le contenu markdown d'une page et l'utiliser comme référence. Exemples : `./gitea-issues/wiki-get-page.sh Home`, `./gitea-issues/wiki-get-page.sh Operations`, `./gitea-issues/wiki-get-page.sh Code-Standards`. Prérequis : token Gitea (comme pour les autres scripts wiki). Les agents peuvent ainsi consulter la doc projet à la demande depuis le wiki, sans dépendre des fichiers locaux `docs/`. La documentation des projets gérés est dans **`projects/<id>/docs`** (ex. `projects/lecoffreio/docs`) ; la documentation propre à ia_dev est dans **`projects/ia_dev/docs`**.
## Agents (commandes)
- **/agent-loop** (`agent-loop.md`) : gère les boucles par exécutions délimitées uniquement (N itérations avec `agent-loop-chat-iterations.sh [N]`, ou x cycles récupération + traitement). Ne lance jamais agent-loop.sh ni agent-loop-treatment.sh en arrière-plan.
- **/gitea-issues-process** (`gitea-issues-process.md`) : traite les issues Gitea et les mails en attente (workflow script au maximum, /fix ou /evol, /push-by-script). Voir le fichier de l'agent pour le workflow exact.
## Référence
- Wiki : https://git.4nkweb.com/4nk/lecoffre_ng/wiki
- Documentation opérationnelle (ex. `docs/OPERATIONS.md`) : page wiki **Operations** (après migration).

View File

@ -8,6 +8,6 @@ Ce répertoire contient les documents **non spécifiques à un projet** (ex. LeC
- **agents-scripts-split.md** : Répartition des rôles entre agents Cursor et scripts (branch-align, change-to-all-branches, deploy, push), exécution depuis la racine, options standardisées. - **agents-scripts-split.md** : Répartition des rôles entre agents Cursor et scripts (branch-align, change-to-all-branches, deploy, push), exécution depuis la racine, options standardisées.
- **WORKFLOWS_AND_COMPONENTS.md** : Workflows IA (clôture, déploiement, fix-lint, pousse, docupdate, audit, branch-align), plans Cursor, règles, subagents, commandes, skills, MCP, protocole de développement (générique, sans référence à un projet particulier). - **WORKFLOWS_AND_COMPONENTS.md** : Workflows IA (clôture, déploiement, fix-lint, pousse, docupdate, audit, branch-align), plans Cursor, règles, subagents, commandes, skills, MCP, protocole de développement (générique, sans référence à un projet particulier).
- **GITEA_ISSUES_SCRIPTS_AGENTS.md** : Gitea issues scripts et agents (liste des scripts, workflow mails, boucle agent, prérequis). - **GIT_ISSUES_SCRIPTS_AGENTS.md** : Gitea issues scripts et agents (liste des scripts, workflow mails, boucle agent, prérequis).
- **TICKETS_SPOOL_FORMAT.md** : Format JSON du spooler tickets (projects/<id>/data/issues/), schémas incoming/response, pièces jointes, config conf.json. - **TICKETS_SPOOL_FORMAT.md** : Format JSON du spooler tickets (projects/<id>/data/issues/), schémas incoming/response, pièces jointes, config conf.json.
- **ai_working_help/docs/notary-ai-api.md** : API IA notaire (enqueue, response), spooler projects/<id>/data/notary-ai/, scripts notary-ai/, agents notary-ai-loop et notary-ai-process. - **ai_working_help/docs/notary-ai-api.md** : API IA notaire (enqueue, response), spooler projects/<id>/data/notary-ai/, scripts notary-ai/, agents notary-ai-loop et notary-ai-process.

View File

@ -132,4 +132,4 @@ Exemple :
La récupération ne prend que les messages **envoyés à** `authorized_emails.to` **et** **expédités par** une des adresses de `authorized_emails.from`. Aucun marquage « lu / non lu ». Lorsqu'une réponse est enregistrée, le fichier `.pending` est supprimé et remplacé par le fichier `.response` (même base `<date>.<id>.<from>`). La récupération ne prend que les messages **envoyés à** `authorized_emails.to` **et** **expédités par** une des adresses de `authorized_emails.from`. Aucun marquage « lu / non lu ». Lorsqu'une réponse est enregistrée, le fichier `.pending` est supprimé et remplacé par le fichier `.response` (même base `<date>.<id>.<from>`).
**IMAP** : le script se connecte à la boîte IMAP configurée (ex. `.secrets/gitea-issues/imap-bridge.env`). Les messages sont filtrés selon les en-têtes **To**, **Delivered-To**, **X-Original-To**, etc. Pour que des messages « envoyés à » l'alias soient trouvés, **la boîte IMAP doit être celle qui reçoit le courrier pour cette adresse** (celle de `authorized_emails.to`). Si l'IMAP pointe vers une autre boîte (ex. une adresse personnelle qui reçoit tout), aucun message ne contiendra l'alias dans To/Delivered-To et tout sera exclu par `not_to_alias`. Il faut alors utiliser les identifiants IMAP de la boîte qui reçoit les mails envoyés à l'alias. **IMAP** : le script se connecte à la boîte IMAP configurée (ex. `.secrets/git-issues/imap-bridge.env`). Les messages sont filtrés selon les en-têtes **To**, **Delivered-To**, **X-Original-To**, etc. Pour que des messages « envoyés à » l'alias soient trouvés, **la boîte IMAP doit être celle qui reçoit le courrier pour cette adresse** (celle de `authorized_emails.to`). Si l'IMAP pointe vers une autre boîte (ex. une adresse personnelle qui reçoit tout), aucun message ne contiendra l'alias dans To/Delivered-To et tout sera exclu par `not_to_alias`. Il faut alors utiliser les identifiants IMAP de la boîte qui reçoit les mails envoyés à l'alias.

View File

@ -24,11 +24,11 @@
}, },
"mail": { "mail": {
"email": "ai.support.lecoffreio@4nkweb.com", "email": "ai.support.lecoffreio@4nkweb.com",
"imap_bridge_env": ".secrets/gitea-issues/imap-bridge.env" "imap_bridge_env": ".secrets/git-issues/imap-bridge.env"
}, },
"git": { "git": {
"wiki_url": "https://git.4nkweb.com/4nk/lecoffre_ng/wiki", "wiki_url": "https://git.4nkweb.com/4nk/lecoffre_ng/wiki",
"token_file": ".secrets/gitea-issues/token" "token_file": ".secrets/git-issues/token"
}, },
"tickets": { "tickets": {
"ticketing_url": "https://git.4nkweb.com/4nk/lecoffre_ng/issues", "ticketing_url": "https://git.4nkweb.com/4nk/lecoffre_ng/issues",

View File

@ -30,7 +30,7 @@
"in_reply_to_message_id": "<XImHtfSZSMJo1z1dqMvd19K5CPkEJ6q6-3Ky88Muiy5c_ED4mcc6JeLT-RY1Lt9T5b57Ce3GEvrrP6_kUZ0cToHtLKF2x324qbZDyLjqYG8=@pm.me>", "in_reply_to_message_id": "<XImHtfSZSMJo1z1dqMvd19K5CPkEJ6q6-3Ky88Muiy5c_ED4mcc6JeLT-RY1Lt9T5b57Ce3GEvrrP6_kUZ0cToHtLKF2x324qbZDyLjqYG8=@pm.me>",
"to": "nicolas.cantu@pm.me", "to": "nicolas.cantu@pm.me",
"subject": "Re: Présente le projet", "subject": "Re: Présente le projet",
"body": "Le projet LeCoffre.io est une application de coffre-fort numérique (dépôt lecoffre_ng). La documentation technique est sur le wiki du dépôt Gitea (https://git.4nkweb.com/4nk/lecoffre_ng/wiki). Les déploiements test, préproduction et production passent par les scripts deploy/ ; le ticketing et le support IA par les scripts gitea-issues/ dans ia_dev.", "body": "Le projet LeCoffre.io est une application de coffre-fort numérique (dépôt lecoffre_ng). La documentation technique est sur le wiki du dépôt Gitea (https://git.4nkweb.com/4nk/lecoffre_ng/wiki). Les déploiements test, préproduction et production passent par les scripts deploy/ ; le ticketing et le support IA par les scripts git-issues/ dans ia_dev.",
"sent_at": "2026-03-14T13:43:30Z" "sent_at": "2026-03-14T13:43:30Z"
} }
} }

View File

@ -47,7 +47,7 @@ Vérification : `npm run lint`, `npm run lint:fix` dans lecoffre-back-main. Réf
Tout agent sous `.smartIde/agents` doit veiller à l'exécution exhaustive des agents qu'il lance (ou des phases qu'il exécute) : vérification avant clôture ; en cas d'incomplétude : pas de clôture, documenter les manques, améliorer les instructions, relancer jusqu'à exécution exhaustive. Tout agent sous `.smartIde/agents` doit veiller à l'exécution exhaustive des agents qu'il lance (ou des phases qu'il exécute) : vérification avant clôture ; en cas d'incomplétude : pas de clôture, documenter les manques, améliorer les instructions, relancer jusqu'à exécution exhaustive.
- **Agents de délégation** (agent-loop, fix, evol, docupdate, push-by-script, fix-search, deploy-by-script, fix-lint, gitea-issues-process, code, branch-align-by-script-from-test, change-to-all-branches, deploy-pprod-or-prod, notary-ai-process, notary-ai-loop) : section « Exécution exhaustive des agents lancés » avec vérification obligatoire avant clôture ; en cas d'incomplétude : 1) ne pas clôturer et documenter, 2) améliorer les instructions, 3) relancer l'agent concerné. - **Agents de délégation** (agent-loop, fix, evol, docupdate, push-by-script, fix-search, deploy-by-script, fix-lint, git-issues-process, code, branch-align-by-script-from-test, change-to-all-branches, deploy-pprod-or-prod, notary-ai-process, notary-ai-loop) : section « Exécution exhaustive des agents lancés » avec vérification obligatoire avant clôture ; en cas d'incomplétude : 1) ne pas clôturer et documenter, 2) améliorer les instructions, 3) relancer l'agent concerné.
- **Agent E2E** (e2e-lecoffre-notary-full / `e2e-test`) : même logique sur les phases applicables — 112 en test/pprod ; 17 et 911 en prod (sans notaire invité). Prérequis **user-profil** documenté dans `docs/features/user-profil-and-e2e-environments.md`. - **Agent E2E** (e2e-lecoffre-notary-full / `e2e-test`) : même logique sur les phases applicables — 112 en test/pprod ; 17 et 911 en prod (sans notaire invité). Prérequis **user-profil** documenté dans `docs/features/user-profil-and-e2e-environments.md`.
Aucun déploiement applicatif ; prise en compte à la prochaine invocation des agents. Aucun déploiement applicatif ; prise en compte à la prochaine invocation des agents.

View File

@ -21,4 +21,4 @@ Contenu des anciens dossiers `docs/features/` et `docs/fixKnowledge/` : ventilé
## Mise à jour du wiki ## Mise à jour du wiki
Depuis la racine du dépôt qui contient `docs/` : exécuter le script de migration (si disponible depuis ia_dev) pour pousser `docs/*.md` vers le wiki. Correspondance détaillée : `projects/ia_dev/docs/GITEA_ISSUES_SCRIPTS_AGENTS.md` (section Migration docs/ → wiki). Ne pas committer `docs/` (hors versionnement). Depuis la racine du dépôt qui contient `docs/` : exécuter le script de migration (si disponible depuis ia_dev) pour pousser `docs/*.md` vers le wiki. Correspondance détaillée : `projects/ia_dev/docs/GIT_ISSUES_SCRIPTS_AGENTS.md` (section Migration docs/ → wiki). Ne pas committer `docs/` (hors versionnement).

1
projects/smart_ide Symbolic link
View File

@ -0,0 +1 @@
../../projects/smart_ide

View File

@ -1,52 +0,0 @@
{
"id": "smart_ide",
"name": "smart_ide",
"cron": {
"git_pull": true
},
"project_path": ".",
"build_dirs": [],
"deploy": {},
"version": {
"package_json_paths": [],
"splash_app_name": "smart_ide"
},
"mail": {
"imap_bridge_env": ".secrets/gitea-issues/imap-bridge.env"
},
"git": {
"wiki_url": "https://git.4nkweb.com/4nk/smart_ide/wiki",
"token_file": ".secrets/gitea-issues/token"
},
"tickets": {
"ticketing_url": "https://git.4nkweb.com/4nk/smart_ide/issues",
"authorized_emails": {
"to": [
{
"test": "AI.SMART_IDE.TEST@4nkweb.com",
"pprod": "AI.SMART_IDE.PPROD@4nkweb.com",
"prod": "AI.SMART_IDE.PROD@4nkweb.com"
}
],
"from": ["nicolas.4nk@pm.me"]
}
},
"smart_ide": {
"remote_data_access": {
"environments": {
"test": {
"ssh_host_alias": "smart-ide-test",
"remote_data_directories": []
},
"pprod": {
"ssh_host_alias": "smart-ide-pprod",
"remote_data_directories": []
},
"prod": {
"ssh_host_alias": "smart-ide-prod",
"remote_data_directories": []
}
}
}
}
}

View File

@ -8,7 +8,7 @@ projects
│   ├── conf.json │   ├── conf.json
│   └── docs │   └── docs
│   ├── agents-scripts-split.md │   ├── agents-scripts-split.md
│   ├── GITEA_ISSUES_SCRIPTS_AGENTS.md │   ├── GIT_ISSUES_SCRIPTS_AGENTS.md
│   ├── README.md │   ├── README.md
│   ├── TICKETS_SPOOL_FORMAT.md │   ├── TICKETS_SPOOL_FORMAT.md
│   └── WORKFLOWS_AND_COMPONENTS.md │   └── WORKFLOWS_AND_COMPONENTS.md
@ -51,7 +51,7 @@ projects
│   │   ├── v1-schema.sql │   │   ├── v1-schema.sql
│   │   └── WORKFLOWS_AND_COMPONENTS.md │   │   └── WORKFLOWS_AND_COMPONENTS.md
│   └── logs │   └── logs
│   └── gitea-issues │   └── git-issues
│   ├── agent-loop-600-cycles.log │   ├── agent-loop-600-cycles.log
│   ├── agent-loop-chat-iterations.log │   ├── agent-loop-chat-iterations.log
│   ├── agent-loop.lock │   ├── agent-loop.lock