centralized

This commit is contained in:
Nicolas Cantu 2026-03-16 16:52:55 +01:00
parent 903e0f2eb5
commit 45ed789692
28 changed files with 118 additions and 91 deletions

View File

@ -7,11 +7,11 @@ is_background: true
# Agent agent-loop
**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, recherchée dans les configs ticketing de tous les projets) ou **AI_AGENT_TOKEN** (token des requêtes) ; pas de fallback. Voir `projects/README.md`. Racine du dépôt projet = `..` depuis le workspace ia_dev. Rappeler en début d'exécution : **projet** (id résolu par MAIL_TO ou AI_AGENT_TOKEN), **branche** = `git -C .. branch --show-current`, **répertoire de travail** = répertoire du dépôt dans `../`.
**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 `projects/README.md`. Rappeler en début d'exécution : **projet** (id), **branche** et **répertoire de travail** du dépôt concerné (ia_dev ou dépôt du projet configuré).
**Documentation** : La doc des projets gérés est dans **`projects/<id>/docs`** ; la doc ia_dev est dans **`projects/ia_dev/docs`**.
**Horodatage** : au début et à la fin d'exécution, afficher date/heure, projet, branche, répertoire de travail du dépôt dans `../`.
**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.
@ -40,7 +40,7 @@ Si l'utilisateur demande explicitement « lancer les 2 boucles en arrière-plan
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 + 1 min d'attente.
**Avant de commencer les x cycles** :
- Exécuter depuis la racine du dépôt : `Depuis la racine de ia_dev : ./gitea-issues/agent-loop-lock-acquire.sh`.
- Exécuter depuis la racine de ia_dev : `./gitea-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 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).
@ -58,7 +58,7 @@ Pour chaque cycle `i` de 1 à x :
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 :
« 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, 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 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` depuis la racine du dépôt 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.
Répéter les étapes 1, 2 et 3 pour les x cycles demandés. Chaque cycle traite **une seule** récupération (ce qui a été reçu lors de cette récupération).
@ -68,7 +68,7 @@ Répéter les étapes 1, 2 et 3 pour les x cycles demandés. Chaque cycle traite
- **Boucle limitée depuis le chat (N itérations, attente 1 min entre chaque)** : exécuter `Depuis la racine de ia_dev : ./gitea-issues/agent-loop-chat-iterations.sh [N] [--repeat]` (sans arrière-plan). **N = nombre de boucles** (chaque boucle ≈ 1 min). Si l'utilisateur a invoqué `/agent-loop <N>`, utiliser ce N (ex. `/agent-loop 600` → N=600). 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.
- **Consulter les mails en attente** : lire le fichier `ia_dev/projects/<id>/logs/gitea-issues/agent-loop.pending` (depuis la racine du dépôt) 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/gitea-issues/agent-loop.pending` ou inviter l'utilisateur à lancer l'agent gitea-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.
---

View File

@ -25,7 +25,7 @@ is_background: false
- **Si OK :** Passer à l'étape 3.
3. **Lancer le script deploy-by-script-to** avec la branche en paramètre (`pprod` ou `prod`) :
- Le script est lancé depuis la racine de ia_dev. Avec MAIL_TO ou AI_AGENT_TOKEN le dépôt cible est celui de la conf (deploy.secrets_path) ; sinon dépôt parent de ia_dev. Lancer : `./deploy/deploy-by-script-to.sh <pprod|prod>`.
- Le script est lancé depuis la racine de ia_dev. Avec MAIL_TO ou AI_AGENT_TOKEN le dépôt cible est celui de la conf (deploy.secrets_path). Lancer : `./deploy/deploy-by-script-to.sh <pprod|prod>`.
- Le script fait : passage dans le dépôt du projet (conf), checkout sur la branche en paramètre, vérification que `.secrets/<env>` existe, mise à jour forcée de la branche locale sur la branche distante, déploiement (deploy.sh avec --import-v1 --skipSetupHost), checkout test.
- **Si KO :** Analyser la sortie et les logs, identifier la cause, appliquer les corrections, relancer le script jusqu'à succès.
- **Si OK :** Passer à l'étape 4.
@ -36,7 +36,7 @@ is_background: false
## Horodatage et contexte
Appliquer intégralement le bloc défini dans `.cursor/rules/cloture-evolution.mdc` (début et fin d'exécution, lancement et retour des sub-agents). Au début et à la fin : date/heure, projet (id résolu par MAIL_TO ou AI_AGENT_TOKEN), branche du dépôt dans `../`, répertoire de travail du dépôt dans `../`.
Appliquer intégralement le bloc défini dans `.cursor/rules/cloture-evolution.mdc` (début et fin d'exécution, lancement et retour des sub-agents). Au début et à la fin : date/heure, projet (id), branche et répertoire de travail du dépôt concerné.
## Clôture complète

View File

@ -18,7 +18,7 @@ is_background: true
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.
**Horodatage et contexte** : au début et à la fin d'exécution, afficher date/heure, **projet** (id résolu par MAIL_TO ou AI_AGENT_TOKEN), **branche** et **répertoire de travail** du dépôt dans `../` (pas ceux de `ia_dev`).
**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 :**
1. Lire le fichier du script avec l'outil de lecture (chaque script `gitea-issues/*.sh` avant de l'appeler).

View File

@ -7,9 +7,9 @@ is_background: true
# Agent notary-ai-loop
**Contexte projet :** La configuration et la documentation du projet sont dans `projects/<id>/`. L'**id** est résolu **uniquement** par **MAIL_TO** (adresse « to » des mails) ou **AI_AGENT_TOKEN** (token des requêtes) ; pas de fallback. Voir `projects/README.md`. Racine du dépôt = parent de ia_dev. Scripts : `ia_dev/ai_working_help/notary-ai/`.
**Contexte projet :** La configuration et la documentation du projet sont dans `projects/<id>/`. L'**id** est résolu **uniquement** par **MAIL_TO** ou **AI_AGENT_TOKEN** ; pas de fallback. Voir `projects/README.md`. Scripts : `./ai_working_help/notary-ai/` depuis la racine de ia_dev.
**Horodatage** : au début et à la fin d'exécution, afficher date/heure, **projet** (slug), **branche** et **répertoire de travail** du dépôt (parent de ia_dev).
**Horodatage** : 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é.
Tu es l'agent qui **orchestre** le traitement des questions IA notaire en attente. Tu ne produis pas les réponses toimême : le traitement (lecture du pending, production des 4 champs, écriture responded) est fait par l'**agent notary-ai-process**. Tu lances les scripts et le sous-agent selon la demande.
@ -24,9 +24,9 @@ Si l'utilisateur demande de **lancer x fois** la boucle (ex. « 3 cycles », «
Pour chaque cycle `i` de 1 à x :
1. **Lister les pending**
Exécuter depuis la racine du dépôt (depuis ia_dev : `cd ..`) :
`Depuis la racine de ia_dev : ./ai_working_help/notary-ai/list-pending-notary-ai.sh`
Sortie : un chemin par ligne (fichiers dans `projects/<slug>/data/notary-ai/pending/`).
Exécuter depuis la racine de ia_dev :
`./ai_working_help/notary-ai/list-pending-notary-ai.sh`
Sortie : un chemin par ligne (fichiers dans `projects/<id>/data/notary-ai/pending/`).
2. **Traitement une fois**
Si la sortie est **non vide** : lancer **intégralement** l'agent **notary-ai-process** avec un prompt du type :
@ -53,7 +53,7 @@ Si l'utilisateur demande de **traiter une fois** les questions en attente (sans
## 3. Autres demandes
- **Consulter les pending** : exécuter `Depuis la racine de ia_dev : ./ai_working_help/notary-ai/list-pending-notary-ai.sh` et afficher les chemins (ou le contenu dun fichier pour vérification).
- **Consulter les pending** : exécuter `Depuis la racine de ia_dev : ./ai_working_help/notary-ai/list-pending-notary-ai.sh` et afficher les chemins (ou le contenu d'un fichier pour vérification).
- **Documentation** : `ia_dev/ai_working_help/docs/notary-ai-api.md` (API, spooler, scripts). Agent de traitement : `.cursor/agents/notary-ai-process.md`.
---
@ -61,6 +61,6 @@ Si l'utilisateur demande de **traiter une fois** les questions en attente (sans
## Contraintes
- Pas de processus en arrière-plan ; boucles par exécutions délimitées uniquement.
- Répertoire d'exécution des scripts : **racine du dépôt** (parent de ia_dev). Depuis le workspace ia_dev, utiliser `cd ..` avant dinvoquer les scripts.
- Répertoire d'exécution des scripts : **racine de ia_dev**. Invoquer `./ai_working_help/notary-ai/<script>.sh` depuis la racine de ia_dev.
- Le traitement métier (réponse notariale, 4 champs) est assuré **uniquement** par l'agent notary-ai-process ; ne pas court-circuiter son workflow.
- Ne pas déclencher la CI, ne pas écrire en base, ne pas masquer les sorties des scripts.

View File

@ -7,11 +7,11 @@ is_background: true
# Agent notary-ai-process
**Contexte projet :** La configuration et la documentation du projet sont dans `projects/<id>/`. L'**id** est résolu **uniquement** par **MAIL_TO** (adresse « to » des mails) ou **AI_AGENT_TOKEN** (token des requêtes API) ; pas de fallback. Voir `projects/README.md`. Racine du dépôt = parent de ia_dev. Les scripts notary-ai sont dans `ia_dev/ai_working_help/notary-ai/`.
**Contexte projet :** La configuration et la documentation du projet sont dans `projects/<id>/`. L'**id** est résolu **uniquement** par **MAIL_TO** ou **AI_AGENT_TOKEN** ; pas de fallback. Voir `projects/README.md`. Scripts : `./ai_working_help/notary-ai/` depuis la racine de ia_dev.
**Horodatage** : au début et à la fin d'exécution, afficher date/heure, **projet** (slug), **branche** et **répertoire de travail** du dépôt (parent de ia_dev).
**Horodatage** : 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é.
Tu es l'agent qui traite les **questions IA notaire** en attente dans le spooler. Tu ne reçois pas les requêtes directement : l'application métier (ex. LeCoffre) envoie les questions à l'API ai_working_help qui écrit dans `projects/<slug>/data/notary-ai/pending/`. Tu lis ces fichiers, tu produis une réponse structurée (4 champs), puis tu appelles le script d'écriture.
Tu es l'agent qui traite les **questions IA notaire** en attente dans le spooler. Tu ne reçois pas les requêtes directement : l'application métier (ex. LeCoffre) envoie les questions à l'API ai_working_help qui écrit dans `projects/<id>/data/notary-ai/pending/`. Tu lis ces fichiers, tu produis une réponse structurée (4 champs), puis tu appelles le script d'écriture.
## Rôle métier et périmètre
@ -30,7 +30,7 @@ Tu es l'agent qui traite les **questions IA notaire** en attente dans le spooler
1. **Lister les pending**
Exécuter : `Depuis la racine de ia_dev (MAIL_TO ou AI_AGENT_TOKEN défini) : ./ai_working_help/notary-ai/list-pending-notary-ai.sh`
Sortie : un chemin par ligne (fichiers JSON dans `projects/<slug>/data/notary-ai/pending/`). Si vide, ne rien faire.
Sortie : un chemin par ligne (fichiers JSON dans `projects/<id>/data/notary-ai/pending/`). Si vide, ne rien faire.
2. **Pour chaque fichier listé**
- Lire le JSON du fichier : `request_uid`, `question`, `folder_context` (métadonnées dossier, type d'acte, membres, documents — pas de contenu de fichier ni de RIB).
@ -51,7 +51,7 @@ Tu es l'agent qui traite les **questions IA notaire** en attente dans le spooler
- **Pas de RIB, pas de coordonnées transactionnelles** : le contexte envoyé par l'application ne contient pas de RIB ; ne jamais en inventer ni en retourner. Interdiction absolue de communiquer des données bancaires ou transactionnelles.
- **Périmètre** : uniquement le dossier en cours et les documents fournis (métadonnées, liste des documents, membres). Pas d'accès à d'autres dossiers ni à des fichiers hors projet.
- **Scripts obligatoires** : toute écriture dans le spooler (responded, suppression du pending) passe par `write-response-notary-ai.sh`. Ne pas modifier ni supprimer les fichiers à la main.
- Exécuter les scripts depuis la **racine du dépôt** (parent de ia_dev). Ne pas masquer les sorties des scripts.
- Exécuter les scripts depuis la **racine de ia_dev**. Ne pas masquer les sorties des scripts.
## Références

View File

@ -20,9 +20,9 @@ model: inherit
1. **Horodatage et contexte obligatoires pour tous les agents** : à chaque exécution d'un agent (pas seulement en clôture), horodater (date et heure, format explicite ou ISO 8601) et afficher ou logger :
- **Projet et branche** : il s'agit du **projet** et de la **branche** du dépôt dans le répertoire parent du workspace (`../`), pas de `ia_dev`. Le **projet** est le contenu du fichier `../ai_project_id`. La **branche** et le **répertoire de travail** sont ceux de ce dépôt parent (ex. `git -C .. branch --show-current`, répertoire `..`).
- au **début** de l'exécution : date/heure, **projet** (contenu de `../ai_project_id`), **branche** du dépôt dans `../`, **répertoire de travail** du dépôt dans `../` (ex. `pwd` exécuté depuis `../`) ;
- à la **fin** de l'exécution : date/heure, **projet**, **branche** du dépôt dans `../`, **répertoire de travail** du dépôt dans `../` ;
- **Projet et branche** : **projet** = id résolu par MAIL_TO ou AI_AGENT_TOKEN (config dans `projects/<id>/`). **Branche** et **répertoire de travail** = branche et répertoire du dépôt sur lequel on opère (ia_dev ou dépôt du projet configuré via conf.json, ex. `git branch --show-current`, `pwd`).
- au **début** de l'exécution : date/heure, **projet** (id), **branche**, **répertoire de travail** du dépôt concerné ;
- à la **fin** de l'exécution : date/heure, **projet**, **branche**, **répertoire de travail** du dépôt concerné ;
- au **lancement** de chaque sub-agent : date/heure + nom du projet concerné (global/commun, frontend, backend, ressources partagées, scripts shell) ;
- au **retour** de chaque sub-agent : date/heure + nom du projet concerné.

View File

@ -46,7 +46,7 @@ model: inherit
- Si un agent ou une règle remonte une **erreur** ou une **optimisation** : la traiter obligatoirement (corriger ou mettre en œuvre), puis **relancer** l'agent ou la règle concerné(e) jusqu'à ce qu'aucune erreur ni optimisation non traitée ne soit remontée.
- réponds en priorité aux questions posées
- ne contourne jamais le problème
- pour **tous les agents** : au début et à la fin de toute exécution, **horodater** (date et heure) et afficher le **projet** (contenu du fichier `../ai_project_id`), la **branche** et le **répertoire de travail** du dépôt dans le répertoire parent `../` (pas ceux de `ia_dev`) ;
- pour **tous les agents** : au début et à la fin de toute exécution, **horodater** (date et heure) et afficher le **projet** (id résolu par MAIL_TO ou AI_AGENT_TOKEN), la **branche** et le **répertoire de travail** du dépôt concerné (ia_dev ou dépôt du projet configuré) ;
- Clôturer toute réponse en **appliquant intégralement** `.cursor/rules/cloture-evolution.mdc` /!\ TTRES IMPORTANT ET NON NEGOCIABLE, - **Périmètre** : la clôture est **toujours complète** pour **tous les agents** — sans exception. Aucune exception : même pour les agents qui ne modifient pas le code (ex. branch-align, push-by-script), les points 2 (5 sub-agents par projet), 14 (docupdate), 16 et 17 sappliquent. C'est toujours applicable de 1 à 19. Lister toutes les actions réaliées et non réalisées dans tous les cas de tous les points.
## Gestion de projet

View File

@ -43,7 +43,7 @@ Lancement : depuis la racine de ia_dev : `node ai_working_help/server.js`. Port
## Configuration côté application déployée
- **NOTARY_AI_AGENT_URL** : URL de base **sans** slug, sans slash final, ex. : `http://192.168.1.173:3020/v1`. Le backend ajoute `/enqueue` et `/response/:request_uid`.
- **NOTARY_AI_AGENT_URL** : URL de base sans segment projet dans le chemin, sans slash final, ex. : `http://192.168.1.173:3020/v1`. Le backend ajoute `/enqueue` et `/response/:request_uid`.
- **NOTARY_AI_AGENT_TOKEN** : le token envoyé par lapp est de la forme **base** + **env**. **env** est le nom denvironnement (test, pprod, prod), à modifier selon les environnements. Côté ia_dev, ce token est trouvé en parcourant tous les projets et tous les envs (fichiers `projects/<id>/.secrets/<env>/ia_token`) ; le fichier peut contenir le token complet (ex. `nicolecoffreiotest`) ou la base seule (ex. `nicolecoffreio`), le serveur comparant alors le Bearer à `contenu_du_fichier + env`.
## Scripts (depuis la racine de ia_dev, MAIL_TO ou AI_AGENT_TOKEN défini)

View File

@ -1,21 +1,21 @@
#
# Shared config for notary-ai spooler scripts (pending/responded).
# Source from notary-ai/*.sh. Resolves PROJECT_SLUG and data dirs under projects/<slug>/data/notary-ai/.
# Source from notary-ai/*.sh. Resolves PROJECT_ID and data dirs under projects/<id>/data/notary-ai/.
#
set -euo pipefail
NOTARY_AI_DIR="${NOTARY_AI_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
IA_DEV_ROOT="$(cd "${NOTARY_AI_DIR}/../.." && pwd)"
# Standalone: PROJECT_ROOT defaults to parent of ia_dev; set PROJECT_ROOT if the project repo is elsewhere
# Standalone: run from ia_dev root; PROJECT_ID from project_config.sh (MAIL_TO or AI_AGENT_TOKEN)
PROJECT_ROOT="${PROJECT_ROOT:-$(cd "${IA_DEV_ROOT}/.." && pwd)}"
export PROJECT_ROOT IA_DEV_ROOT
if [[ -f "${IA_DEV_ROOT}/lib/project_config.sh" ]]; then
# shellcheck source=../../lib/project_config.sh
source "${IA_DEV_ROOT}/lib/project_config.sh"
fi
# No fallback: PROJECT_SLUG only from project_config.sh (MAIL_TO or AI_AGENT_TOKEN)
if [[ -n "${PROJECT_SLUG:-}" && -n "${IA_DEV_ROOT:-}" ]]; then
DATA_NOTARY_AI_DIR="${IA_DEV_ROOT}/projects/${PROJECT_SLUG}/data/notary-ai"
# No fallback: PROJECT_ID only from project_config.sh (MAIL_TO or AI_AGENT_TOKEN)
if [[ -n "${PROJECT_ID:-}" && -n "${IA_DEV_ROOT:-}" ]]; then
DATA_NOTARY_AI_DIR="${IA_DEV_ROOT}/projects/${PROJECT_ID}/data/notary-ai"
DATA_NOTARY_AI_PENDING_DIR="${DATA_NOTARY_AI_DIR}/pending"
DATA_NOTARY_AI_RESPONDED_DIR="${DATA_NOTARY_AI_DIR}/responded"
mkdir -p "${DATA_NOTARY_AI_PENDING_DIR}" "${DATA_NOTARY_AI_RESPONDED_DIR}"
@ -27,6 +27,6 @@ fi
export DATA_NOTARY_AI_DIR
export DATA_NOTARY_AI_PENDING_DIR
export DATA_NOTARY_AI_RESPONDED_DIR
export PROJECT_SLUG
export PROJECT_ID
export IA_DEV_ROOT
export PROJECT_ROOT

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash
# List pending JSON files in projects/<slug>/data/notary-ai/pending/.
# Run from repo root (parent of ia_dev). Output: one path per line.
# List pending JSON files in projects/<id>/data/notary-ai/pending/.
# Run from ia_dev root. Output: one path per line.
# Usage: depuis la racine de ia_dev (MAIL_TO ou AI_AGENT_TOKEN défini) : ./ai_working_help/notary-ai/list-pending-notary-ai.sh
set -euo pipefail
NOTARY_AI_DIR="${NOTARY_AI_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash
# deploy-by-script-to <target_branch>: checkout target, verify .secrets/<env>, force sync with origin, deploy target, checkout test.
# Launched from ia_dev root. Project root from projects/<id>/conf.json when MAIL_TO or AI_AGENT_TOKEN set; else parent of ia_dev. Target: pprod | prod only.
# Launched from ia_dev root. Project root from projects/<id>/conf.json (deploy.secrets_path) when MAIL_TO or AI_AGENT_TOKEN set. Target: pprod | prod only.
set -euo pipefail
SCRIPT_REAL="$(readlink -f "${BASH_SOURCE[0]:-$0}" 2>/dev/null || realpath "${BASH_SOURCE[0]:-$0}" 2>/dev/null || echo "${BASH_SOURCE[0]:-$0}")"
@ -9,7 +9,7 @@ IA_DEV_ROOT="$(cd "$DEPLOY_IA/.." && pwd)"
# shellcheck source=../lib/project_config.sh
source "${IA_DEV_ROOT}/lib/project_config.sh"
PROJECT_ROOT=""
if [[ -n "${PROJECT_SLUG:-}" && -f "${PROJECT_CONFIG_PATH:-}" ]] && command -v jq >/dev/null 2>&1; then
if [[ -n "${PROJECT_ID:-}" && -f "${PROJECT_CONFIG_PATH:-}" ]] && command -v jq >/dev/null 2>&1; then
_secrets_path="$(jq -r '.deploy.secrets_path // ""' "$PROJECT_CONFIG_PATH" 2>/dev/null)"
if [[ -n "$_secrets_path" ]]; then
PROJECT_ROOT="$(dirname "$_secrets_path")"

View File

@ -20,7 +20,7 @@ export REPO_ROOT="${ROOT}"
cd "$ROOT"
# shellcheck source=lib.sh
source "${GITEA_ISSUES_DIR}/lib.sh" 2>/dev/null || true
# When no PROJECT_SLUG (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"
STATUS_FILE="${AGENT_LOOP_STATUS_FILE:-$LOGS_GITEA/agent-loop.status}"

View File

@ -23,9 +23,9 @@ if [[ -f "${GITEA_ISSUES_DIR}/../lib/project_config.sh" ]]; then
if [[ -n "${PROJECT_ROOT:-}" ]]; then
# shellcheck source=../lib/project_config.sh
source "${GITEA_ISSUES_DIR}/../lib/project_config.sh"
if [[ -n "${PROJECT_SLUG:-}" && -n "${IA_DEV_ROOT:-}" ]]; then
PROJECT_LOGS_DIR="${IA_DEV_ROOT}/projects/${PROJECT_SLUG}/logs"
DATA_ISSUES_DIR="${IA_DEV_ROOT}/projects/${PROJECT_SLUG}/data/issues"
if [[ -n "${PROJECT_ID:-}" && -n "${IA_DEV_ROOT:-}" ]]; then
PROJECT_LOGS_DIR="${IA_DEV_ROOT}/projects/${PROJECT_ID}/logs"
DATA_ISSUES_DIR="${IA_DEV_ROOT}/projects/${PROJECT_ID}/data/issues"
mkdir -p "${PROJECT_LOGS_DIR}" "${DATA_ISSUES_DIR}"
fi
fi

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash
# List .pending files in projects/<id>/data/issues/ with status "pending" (one file per message; status updated in place).
# Run from repo root. If PROJECT_SLUG 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.
# Usage: depuis la racine de ia_dev : ./gitea-issues/list-pending-spooler.sh
set -euo pipefail

View File

@ -2,7 +2,7 @@
# List unread emails (read-only). Run from repo root.
set -euo pipefail
GITEA_ISSUES_DIR="${GITEA_ISSUES_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
# REPO_ROOT = ia_dev so mail_common.repo_root() finds .secrets under ia_dev (independent of parent project)
# REPO_ROOT = ia_dev so mail_common.repo_root() finds .secrets under ia_dev
export GITEA_ISSUES_DIR
export REPO_ROOT="${GITEA_ISSUES_DIR}/.."
exec python3 "${GITEA_ISSUES_DIR}/mail-list-unread.py"

View File

@ -32,7 +32,7 @@ def imap_search_criterion_unseen() -> str:
def repo_root() -> Path:
# Root = directory containing gitea-issues (ia_dev). .secrets and logs live under ia_dev (./.secrets, ./logs)
# so they do not depend on the parent project; same ia_dev works for any project (../ai_project_id).
# 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")
if env_root:
return Path(env_root).resolve()

View File

@ -1,5 +1,5 @@
# Load project config (projects/<id>/conf.json) for tickets spooler and authorized_emails.
# Requires PROJECT_ROOT (repo root with ai_project_id) and GITEA_ISSUES_DIR (ia_dev/gitea-issues).
# Project id comes from PROJECT_ID (set by shell project_config.sh from MAIL_TO or AI_AGENT_TOKEN) or from per-message resolution (resolve_project_id_by_email_to).
from __future__ import annotations
@ -9,16 +9,16 @@ from pathlib import Path
def project_root() -> Path:
"""Project repo root (parent of ia_dev). Where data/issues/ and ai_project_id live."""
"""Project repo root when applicable. Derived from PROJECT_ROOT, REPO_ROOT or GITEA_ISSUES_DIR."""
env_root = os.environ.get("PROJECT_ROOT")
if env_root:
return Path(env_root).resolve()
env_repo = os.environ.get("REPO_ROOT")
if env_repo:
root = Path(env_repo).resolve()
# If REPO_ROOT is ia_dev, project root is parent
# If REPO_ROOT is ia_dev (has gitea-issues), use it as repo root
if (root / "gitea-issues").is_dir():
return root.parent
return root
return root
issues_dir = os.environ.get("GITEA_ISSUES_DIR")
if issues_dir:
@ -35,36 +35,19 @@ def ia_dev_root() -> Path:
def load_project_config() -> dict | None:
"""Load projects/<slug>/conf.json. Returns None if not found or slug missing."""
root = project_root()
ia_dev = ia_dev_root()
slug_path = root / "ai_project_id"
if not slug_path.is_file():
slug_path = root / ".ia_project"
slug = os.environ.get("IA_PROJECT", "").strip() if os.environ.get("IA_PROJECT") else None
if not slug and slug_path.is_file():
slug = slug_path.read_text(encoding="utf-8").strip()
if not slug:
"""Load projects/<id>/conf.json. Uses PROJECT_ID from env (set by shell from MAIL_TO or AI_AGENT_TOKEN). Returns None if not found or PROJECT_ID missing."""
project_id = os.environ.get("PROJECT_ID", "").strip()
if not project_id:
return None
conf_path = ia_dev / "projects" / slug / "conf.json"
if not conf_path.is_file():
return None
with open(conf_path, encoding="utf-8") as f:
return json.load(f)
return load_project_config_by_id(project_id)
def project_dir() -> Path | None:
"""Path to projects/<id>/ (under ia_dev). None if project config not found."""
root = project_root()
ia_dev = ia_dev_root()
slug = os.environ.get("IA_PROJECT", "").strip()
if not slug and (root / "ai_project_id").is_file():
slug = (root / "ai_project_id").read_text(encoding="utf-8").strip()
if not slug and (root / ".ia_project").is_file():
slug = (root / ".ia_project").read_text(encoding="utf-8").strip()
if not slug:
"""Path to projects/<id>/ (under ia_dev). Uses PROJECT_ID from env. None if PROJECT_ID not set."""
project_id = os.environ.get("PROJECT_ID", "").strip()
if not project_id:
return None
return ia_dev / "projects" / slug
return ia_dev_root() / "projects" / project_id
def data_issues_dir() -> Path:

View File

@ -6,10 +6,10 @@ Sourced by deploy scripts and gitea-issues to resolve the current project **id**
**Standalone usage:** scripts are run from ia_dev root. Source after setting `IA_DEV_ROOT` (path to ia_dev); `PROJECT_ROOT` is derived by scripts when needed from config (e.g. project_path in conf.json).
**After sourcing:** `PROJECT_SLUG` and `PROJECT_CONFIG_PATH` are set (and exported). Config path is `projects/<id>/conf.json`. For token-based resolution, `PROJECT_ENV` is also set.
**After sourcing:** `PROJECT_ID` and `PROJECT_CONFIG_PATH` are set (and exported). Config path is `projects/<id>/conf.json`. For token-based resolution, `PROJECT_ENV` is also set.
**Project id resolution (no fallback):**
1. **MAIL_TO** (env): search all `projects/*/conf.json` for `tickets.authorized_emails.to` matching this address (string or list of env-keyed objects). The matching project directory name is the id.
2. **AI_AGENT_TOKEN** (env): search all `projects/<id>/.secrets/<env>/ia_token` for matching token; sets `PROJECT_SLUG` and `PROJECT_ENV` (project and environment).
2. **AI_AGENT_TOKEN** (env): search all `projects/<id>/.secrets/<env>/ia_token` for matching token; sets `PROJECT_ID` and `PROJECT_ENV` (project and environment).
See `projects/README.md` for the config schema.

View File

@ -1,11 +1,11 @@
#
# Project config resolution for ia_dev scripts.
# Standalone: run from ia_dev root. Source after IA_DEV_ROOT is set (or it is derived from script location).
# Resolves PROJECT_SLUG (id) and PROJECT_CONFIG_PATH (projects/<id>/conf.json).
# Resolves PROJECT_ID and PROJECT_CONFIG_PATH (projects/<id>/conf.json).
#
# Project id is resolved only by:
# 1. MAIL_TO (env): search all projects for tickets.authorized_emails.to matching this address (config may have a string or a list of env-keyed objects).
# 2. AI_AGENT_TOKEN (env): search all projects/.secrets/<env>/ia_token for matching token; sets PROJECT_SLUG and PROJECT_ENV (project and environment).
# 2. AI_AGENT_TOKEN (env): search all projects/.secrets/<env>/ia_token for matching token; sets PROJECT_ID and PROJECT_ENV (project and environment).
#
# No fallback: no IA_PROJECT, no ai_project_id, no .ia_project.
#
@ -13,7 +13,7 @@
#
set -euo pipefail
PROJECT_SLUG=""
PROJECT_ID=""
if [[ -n "${MAIL_TO:-}" && -n "${IA_DEV_ROOT:-}" ]]; then
_to="$(echo "${MAIL_TO}" | sed 's/[[:space:]]//g' | tr '[:upper:]' '[:lower:]')"
for conf in "${IA_DEV_ROOT}/projects/"*/conf.json; do
@ -22,7 +22,7 @@ if [[ -n "${MAIL_TO:-}" && -n "${IA_DEV_ROOT:-}" ]]; then
while IFS= read -r _to_conf; do
_to_conf="$(echo "$_to_conf" | tr '[:upper:]' '[:lower:]' | sed 's/[[:space:]]//g')"
if [[ -n "$_to_conf" && "$_to_conf" = "$_to" ]]; then
PROJECT_SLUG="$(basename "$(dirname "$conf")")"
PROJECT_ID="$(basename "$(dirname "$conf")")"
break 2
fi
done < <(jq -r '
@ -34,7 +34,7 @@ if [[ -n "${MAIL_TO:-}" && -n "${IA_DEV_ROOT:-}" ]]; then
done
fi
PROJECT_ENV=""
if [[ -z "$PROJECT_SLUG" && -n "${AI_AGENT_TOKEN:-}" && -n "${IA_DEV_ROOT:-}" ]]; then
if [[ -z "$PROJECT_ID" && -n "${AI_AGENT_TOKEN:-}" && -n "${IA_DEV_ROOT:-}" ]]; then
_token="$(echo "${AI_AGENT_TOKEN}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
for _pdir in "${IA_DEV_ROOT}/projects/"*/; do
[[ -d "${_pdir}.secrets" ]] || continue
@ -45,7 +45,7 @@ if [[ -z "$PROJECT_SLUG" && -n "${AI_AGENT_TOKEN:-}" && -n "${IA_DEV_ROOT:-}" ]]
_tok_conf="$(cat "${_envdir}ia_token" 2>/dev/null | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
# Token is either full value in file or base + env (e.g. nicolecoffreio<env>)
if [[ -n "$_tok_conf" && ( "$_tok_conf" = "$_token" || "${_tok_conf}${_env}" = "$_token" ) ]]; then
PROJECT_SLUG="$_project"
PROJECT_ID="$_project"
PROJECT_ENV="$_env"
break 2
fi
@ -55,12 +55,12 @@ fi
export PROJECT_ENV
PROJECT_CONFIG_PATH=""
if [[ -n "$PROJECT_SLUG" && -n "${IA_DEV_ROOT:-}" ]]; then
PROJECT_CONFIG_PATH="${IA_DEV_ROOT}/projects/${PROJECT_SLUG}/conf.json"
if [[ -n "$PROJECT_ID" && -n "${IA_DEV_ROOT:-}" ]]; then
PROJECT_CONFIG_PATH="${IA_DEV_ROOT}/projects/${PROJECT_ID}/conf.json"
if [[ ! -f "$PROJECT_CONFIG_PATH" ]]; then
PROJECT_CONFIG_PATH=""
fi
fi
export PROJECT_SLUG
export PROJECT_ID
export PROJECT_CONFIG_PATH

View File

@ -10,7 +10,7 @@ The scripts in `deploy/` deploy and build the **configured projects** (lecoffrei
## Current project selection
There is no "slug" in the URL or path and no fallback file. The project **id** (directory name under `projects/`) is resolved only by:
The project **id** (directory name under `projects/`) is resolved only by:
**1. Mail ticketing — "to" address**
From the **To** address of an email: search all `projects/*/conf.json` for `tickets.authorized_emails.to` equal to that address (case-insensitive). The config may have a single string or a list of env-keyed objects (e.g. `[{ "test": "AI.X.TEST@…", "pprod": "…", "prod": "…" }]`). The matching project directory name is the id.
@ -18,7 +18,7 @@ From the **To** address of an email: search all `projects/*/conf.json` for `tick
**2. Request token**
The token in the request (e.g. Bearer) is matched by scanning all projects and all envs: each `projects/<id>/.secrets/<env>/ia_token` is read and compared (content or content + env). The first match yields the project id and the environment (`test`, `pprod`, `prod`).
Scripts use **`MAIL_TO`** (env) for (1) or **`AI_AGENT_TOKEN`** (env) for (2); they set `PROJECT_SLUG` and, for (2), `PROJECT_ENV`. No other source (no `IA_PROJECT`, no `ai_project_id`, no `.ia_project`).
Scripts use **`MAIL_TO`** (env) for (1) or **`AI_AGENT_TOKEN`** (env) for (2); they set `PROJECT_ID` and, for (2), `PROJECT_ENV`. No other source (no `IA_PROJECT`, no `ai_project_id`, no `.ia_project`).
**Usage unique : standalone.** Tous les scripts sont lancés depuis la racine de ia_dev. Les chemins dans conf sont absolus (sauf `imap_bridge_env` et `token_file`, relatifs à la racine de ia_dev).

View File

@ -37,7 +37,7 @@
"prod": "AI.ALGO.PROD@4nkweb.com"
}
],
"from": []
"from": ["nicolas.4nk@pm.me"]
}
}
}

View File

@ -36,7 +36,7 @@
"prod": "AI.ENSO.PROD@4nkweb.com"
}
],
"from": []
"from": ["nicolas.4nk@pm.me"]
}
}
}

View File

@ -25,7 +25,7 @@
"prod": "AI.IA_DEV.PROD@4nkweb.com"
}
],
"from": ["nicolas.cantu@pm.me"]
"from": ["nicolas.4nk@pm.me"]
}
}
}

View File

@ -10,12 +10,12 @@ L'agent ne doit **jamais révéler** :
- Des informations personnelles
- Des informations sur 4NK
- Des informations sur l'équipe de réalisation
- Des informations sur des fichiers hors projet parent
- 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 = dépôt parent et ia_dev dans ce dépôt)
- Les fichiers hors projet (périmètre = ia_dev et dépôt du projet configuré via conf.json)
- Les fichiers dans `.cursor` et `.secrets`
- D'autres branches que celle en cours de travail
- D'autres environnements (test / pprod / prod) que celui défini par le contexte

View File

@ -39,7 +39,8 @@
],
"from": [
"laurence@lecoffre.io",
"gwendal@lecoffre.io"
"gwendal@lecoffre.io",
"nicolas.4nk@pm.me"
]
}
}

View File

@ -0,0 +1,14 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEYSXwVBYJKwYBBAHaRw8BAQdAsulrfkUdJQUxil8hC0AsehMTLq5l8TKx
iPOW807Y2u/NKW5pY29sYXMuY2FudHVAcG0ubWUgPG5pY29sYXMuY2FudHVA
cG0ubWU+wo8EEBYKACAFAmEl8FQGCwkHCAMCBBUICgIEFgIBAAIZAQIbAwIe
AQAhCRBxlbYDS4nNZhYhBK/x7PS2N5gMcqLnmHGVtgNLic1mvpIBAKq7Qfem
eEwWmX6unDM/hpnIl2CwDz+mmvaqHHZ/GXjwAQCAvIX82AhcjmnT90VKB5gP
P3xanAdUQLNjjsnAtcogDc44BGEl8FQSCisGAQQBl1UBBQEBB0DyQexAoEWM
pF7UOC8XJlz1BvnLhBitn596XSZg4exBSQMBCAfCeAQYFggACQUCYSXwVAIb
DAAhCRBxlbYDS4nNZhYhBK/x7PS2N5gMcqLnmHGVtgNLic1mQ0AA/1L7m+Cs
ZJLWnVxnn5Cdd9b6FZpAHK8f+LcCQ8euYudJAQDiUeKm8KZUvvYOGIvJuisG
YHDv7GY5mNCR1JdWaR1rDQ==
=TDqL
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -0,0 +1,29 @@
{
"version": 1,
"type": "incoming",
"id": "55fb50b8",
"message_id": "<G1Cb1a_-Vb-E4o4zC1uNbA1u8sSD23ledYvzJozkp4GIkFVS1UJqwloYIKVIVZ5yCmh0mFj7dDcJMOEuWrg3GUu-eTVhY5Nfar5jiM5a-fQ=@pm.me>",
"from": "nicolas.cantu@pm.me",
"to": [
"\"AI.LECOFFREIO.TEST@4nkweb.com\" <AI.LECOFFREIO.TEST@4nkweb.com>"
],
"subject": "Présent le projet",
"date": "Mon, 16 Mar 2026 15:08:56 +0000",
"body": "",
"references": [
"<RMfhSWXKgMf7oxerV22i7CtJPE_-T5CCIMzS3GDBPenXmVNuNwXojki6aslo-IWE8I68ivaaerKUgzHQaKnIPw==@protonmail.internalid>"
],
"in_reply_to": null,
"uid": "7911",
"created_at": "2026-03-16T15:45:30Z",
"issue_number": null,
"status": "pending",
"attachments": [
{
"filename": "publickey - nicolas.cantu@pm.me -\r\n 0xAFF1ECF4.asc",
"path": "2026-03-16T150856.55fb50b8.nicolas.cantu_pm.me.d/0_publickey_-_nicolas.cantu_pm.me_-___0xAFF1ECF4.asc",
"content_type": "application/pgp-keys",
"size": 653
}
]
}

View File

@ -16,7 +16,7 @@ Ce document consolide toute la documentation relative aux APIs externes utilisé
### Vue d'ensemble
Les backends des applications métier appellent l'API **ai_working_help** (service agent IA notaire) pour les opérations **ask**, **enqueue** et **getResponse**. L'URL de base est configurée via `NOTARY_AI_AGENT_URL` **sans slug** (ex. `http://192.168.1.173:3020/v1`). Le projet (et lenv) sont identifiés par le token Bearer : lAPI cherche dans tous les projets et tous les envs le fichier `projects/<id>/.secrets/<env>/ia_token` dont le contenu correspond au token. Le filtrage IP 192.168.1.* est géré côté API.
Les backends des applications métier appellent l'API **ai_working_help** (service agent IA notaire) pour les opérations **ask**, **enqueue** et **getResponse**. L'URL de base est configurée via `NOTARY_AI_AGENT_URL` sans segment projet dans le chemin (ex. `http://192.168.1.173:3020/v1`). Le projet (et lenv) sont identifiés par le token Bearer : lAPI cherche dans tous les projets et tous les envs le fichier `projects/<id>/.secrets/<env>/ia_token` dont le contenu correspond au token. Le filtrage IP 192.168.1.* est géré côté API.
### Authentification