Initial: desk + ncantu placeholder + per-project cursor configs
**Motivations:** - Centraliser les fichiers Cursor (rules, skills, agents, commands, hooks) par user et par projet **Root causes:** - N/A **Correctifs:** - N/A **Evolutions:** - desk: rules, skills-cursor, agents, commands, hooks, argv/hooks/mcp.json - ncantu: README placeholder - 4NK_node, algo, builazoo, ia_local, lecoffre_ng, lecoffre_ng_pprod, lecoffre_ng_test: .cursor contents **Pages affectées:** - cursor/desk/, cursor/ncantu/, cursor/<project>/
This commit is contained in:
38
desk/hooks/README.md
Normal file
38
desk/hooks/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Cursor Hooks (niveau utilisateur)
|
||||
|
||||
Hooks pour observer et étendre le cycle de l'agent Cursor. Utiles pour la commande `/fix-lint` et le subagent fix-lint en arrière-plan.
|
||||
|
||||
## Logs
|
||||
|
||||
Tous les événements sont journalisés dans `~/.cursor/logs/hooks.log`.
|
||||
|
||||
## Hooks configurés
|
||||
|
||||
- **sessionStart** : Log démarrage session
|
||||
- **sessionEnd** : Log fin session
|
||||
- **subagentStart** : Log lancement subagent, autorise fix-lint
|
||||
- **subagentStop** : Log fin subagent, followup pour fix-lint (vérifier lint)
|
||||
- **beforeShellExecution** : Log commande shell, autorise par défaut
|
||||
- **afterShellExecution** : Log sortie commande (tronquée)
|
||||
- **beforeReadFile** : Log accès fichier
|
||||
- **afterFileEdit** : Log édition fichier
|
||||
- **stop** : Log fin agent (completed/aborted/error)
|
||||
- **postToolUseFailure** : Log échec outil (shell, etc.)
|
||||
|
||||
## Fix-lint
|
||||
|
||||
Quand le subagent fix-lint se termine avec succès, `subagentStop` ajoute un message de suivi suggérant de relancer `npm run lint` dans les trois applications.
|
||||
|
||||
## Déploiement et correction automatique
|
||||
|
||||
Quand un subagent de déploiement échoue (`status=error` et résultat contenant deploy/déploiement), `subagentStop` retourne un `followup_message` demandant à l'agent parent d'analyser l'erreur, identifier la root cause, corriger le bug et retenter. Pour bénéficier de ce flux, lancer le déploiement via le subagent `/deploy` (et non via une commande shell directe).
|
||||
|
||||
## Personnalisation
|
||||
|
||||
- **Bloquer une commande** : modifier `before-shell-execution.sh`, retourner `"permission":"deny"`.
|
||||
- **Bloquer un subagent** : modifier `subagent-start.sh`, retourner `{"decision":"deny","reason":"..."}`.
|
||||
- **Désactiver un hook** : retirer l'entrée correspondante dans `~/.cursor/hooks.json`.
|
||||
|
||||
## Redémarrage
|
||||
|
||||
Redémarrer Cursor après modification de `hooks.json` ou des scripts.
|
||||
11
desk/hooks/after-file-edit.sh
Executable file
11
desk/hooks/after-file-edit.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
# afterFileEdit: log edit. Format can be enabled by uncommenting the block below.
|
||||
HOOKS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${HOOKS_DIR}/lib.sh"
|
||||
|
||||
input=$(cat)
|
||||
path=$(echo "$input" | jq -r '.path // .file_path // .fsPath // .file // ""')
|
||||
log_event "afterFileEdit" "path=$path"
|
||||
echo '{}'
|
||||
exit 0
|
||||
20
desk/hooks/after-shell-execution.sh
Executable file
20
desk/hooks/after-shell-execution.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
# afterShellExecution: log command output (truncated), useful for lint runs
|
||||
HOOKS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${HOOKS_DIR}/lib.sh"
|
||||
|
||||
input=$(cat)
|
||||
command=$(echo "$input" | jq -r '.command // ""')
|
||||
duration=$(echo "$input" | jq -r '.duration // 0')
|
||||
output=$(echo "$input" | jq -r '.tool_output // .command_output // ""' 2>/dev/null || echo "$input" | jq -r '.command_output // ""' 2>/dev/null)
|
||||
# Some hooks use tool_output, others command_output - try both
|
||||
if [ -z "$output" ]; then
|
||||
output=$(echo "$input" | jq -r '.tool_output // ""' 2>/dev/null)
|
||||
fi
|
||||
log_event "afterShellExecution" "cmd=${command:0:80} duration=${duration}ms"
|
||||
if [ -n "$output" ] && [ ${#output} -gt 0 ]; then
|
||||
log_json_truncated "afterShellExecution_output" "$output" 400
|
||||
fi
|
||||
echo '{}'
|
||||
exit 0
|
||||
12
desk/hooks/before-read-file.sh
Executable file
12
desk/hooks/before-read-file.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# beforeReadFile: log file access, allow by default
|
||||
HOOKS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${HOOKS_DIR}/lib.sh"
|
||||
|
||||
input=$(cat)
|
||||
path=$(echo "$input" | jq -r '.path // .file_path // .fsPath // "?"')
|
||||
log_event "beforeReadFile" "path=$path"
|
||||
# Allow all reads. Return empty or allow decision.
|
||||
echo '{}'
|
||||
exit 0
|
||||
14
desk/hooks/before-shell-execution.sh
Executable file
14
desk/hooks/before-shell-execution.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# beforeShellExecution: log command, allow by default. Block dangerous patterns if needed.
|
||||
HOOKS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${HOOKS_DIR}/lib.sh"
|
||||
|
||||
input=$(cat)
|
||||
command=$(echo "$input" | jq -r '.command // ""')
|
||||
cwd=$(echo "$input" | jq -r '.cwd // "?"')
|
||||
log_event "beforeShellExecution" "cwd=$cwd cmd=${command:0:100}"
|
||||
|
||||
# Allow all commands by default. Add deny rules here if needed (e.g. rm -rf /, etc.)
|
||||
echo '{"continue":true,"permission":"allow"}'
|
||||
exit 0
|
||||
31
desk/hooks/lib.sh
Executable file
31
desk/hooks/lib.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Shared helpers for Cursor hooks. Source with: source "$(dirname "$0")/lib.sh"
|
||||
|
||||
LOG_DIR="${CURSOR_HOOKS_LOG_DIR:-$HOME/.cursor/logs}"
|
||||
LOG_FILE="${LOG_DIR}/hooks.log"
|
||||
|
||||
log_event() {
|
||||
local event="$1"
|
||||
local summary="$2"
|
||||
mkdir -p "$LOG_DIR"
|
||||
echo "[$(date -Iseconds)] [$event] $summary" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_json() {
|
||||
local event="$1"
|
||||
local json="$2"
|
||||
mkdir -p "$LOG_DIR"
|
||||
echo "[$(date -Iseconds)] [$event] $json" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_json_truncated() {
|
||||
local event="$1"
|
||||
local json="$2"
|
||||
local max="${3:-500}"
|
||||
mkdir -p "$LOG_DIR"
|
||||
if [ ${#json} -gt "$max" ]; then
|
||||
echo "[$(date -Iseconds)] [$event] ${json:0:$max}... (truncated)" >> "$LOG_FILE"
|
||||
else
|
||||
echo "[$(date -Iseconds)] [$event] $json" >> "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
18
desk/hooks/post-tool-use-failure.sh
Executable file
18
desk/hooks/post-tool-use-failure.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
# postToolUseFailure: log when a tool fails (shell, etc.)
|
||||
HOOKS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${HOOKS_DIR}/lib.sh"
|
||||
|
||||
input=$(cat)
|
||||
tool_name=$(echo "$input" | jq -r '.tool_name // "?"')
|
||||
failure_type=$(echo "$input" | jq -r '.failure_type // "?"')
|
||||
error_message=$(echo "$input" | jq -r '.error_message // ""')
|
||||
command=$(echo "$input" | jq -r '.tool_input.command // .tool_input // ""' 2>/dev/null || echo "")
|
||||
log_event "postToolUseFailure" "tool=$tool_name failure=$failure_type"
|
||||
log_json_truncated "postToolUseFailure_error" "$error_message" 500
|
||||
if [ -n "$command" ]; then
|
||||
log_event "postToolUseFailure" "command=${command:0:150}"
|
||||
fi
|
||||
echo '{}'
|
||||
exit 0
|
||||
9
desk/hooks/session-end.sh
Executable file
9
desk/hooks/session-end.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
# sessionEnd: log session end
|
||||
HOOKS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${HOOKS_DIR}/lib.sh"
|
||||
|
||||
input=$(cat)
|
||||
log_event "sessionEnd" "conversation_id=$(echo "$input" | jq -r '.conversation_id // "?"')"
|
||||
exit 0
|
||||
9
desk/hooks/session-start.sh
Executable file
9
desk/hooks/session-start.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
# sessionStart: log session start, inject context for fix-lint / commands
|
||||
HOOKS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${HOOKS_DIR}/lib.sh"
|
||||
|
||||
input=$(cat)
|
||||
log_event "sessionStart" "conversation_id=$(echo "$input" | jq -r '.conversation_id // "?"') workspace=$(echo "$input" | jq -r '.workspace_roots[0] // "?"')"
|
||||
exit 0
|
||||
13
desk/hooks/stop.sh
Executable file
13
desk/hooks/stop.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
# stop: log agent completion/abort/error
|
||||
HOOKS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${HOOKS_DIR}/lib.sh"
|
||||
|
||||
input=$(cat)
|
||||
status=$(echo "$input" | jq -r '.status // "?"')
|
||||
loop_count=$(echo "$input" | jq -r '.loop_count // 0')
|
||||
conversation_id=$(echo "$input" | jq -r '.conversation_id // "?"')
|
||||
log_event "stop" "conversation_id=$conversation_id status=$status loop_count=$loop_count"
|
||||
echo '{}'
|
||||
exit 0
|
||||
12
desk/hooks/subagent-start.sh
Executable file
12
desk/hooks/subagent-start.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# subagentStart: log subagent launch, allow fix-lint and other subagents
|
||||
HOOKS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${HOOKS_DIR}/lib.sh"
|
||||
|
||||
input=$(cat)
|
||||
subagent_type=$(echo "$input" | jq -r '.subagent_type // "?"')
|
||||
prompt_preview=$(echo "$input" | jq -r '.prompt // ""' | head -c 120)
|
||||
log_event "subagentStart" "type=$subagent_type prompt_preview=${prompt_preview}..."
|
||||
echo '{"decision":"allow"}'
|
||||
exit 0
|
||||
33
desk/hooks/subagent-stop.sh
Executable file
33
desk/hooks/subagent-stop.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
# subagentStop: log completion, add followup for fix-lint when completed
|
||||
HOOKS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# shellcheck source=lib.sh
|
||||
. "${HOOKS_DIR}/lib.sh"
|
||||
|
||||
input=$(cat)
|
||||
subagent_type=$(echo "$input" | jq -r '.subagent_type // "?"')
|
||||
status=$(echo "$input" | jq -r '.status // "?"')
|
||||
duration=$(echo "$input" | jq -r '.duration // 0')
|
||||
result_preview=$(echo "$input" | jq -r '.result // ""' | head -c 200)
|
||||
log_event "subagentStop" "type=$subagent_type status=$status duration=${duration}ms"
|
||||
log_json_truncated "subagentStop_result" "$result_preview" 300
|
||||
|
||||
result_lower=$(echo "$input" | jq -r '.result // ""' | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
# For fix-lint or lint-related subagents: suggest next step when completed
|
||||
if [ "$status" = "completed" ]; then
|
||||
if echo "$result_lower" | grep -qE "lint|fix-lint|corriger.*erreur"; then
|
||||
echo '{"followup_message":"Lint fix terminé. Vérifier : npm run lint dans lecoffre-back-main, lecoffre-front-main, lecoffre-ressources-dev."}'
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# For deploy subagent: on error, request bug correction
|
||||
if [ "$status" = "error" ]; then
|
||||
if echo "$result_lower" | grep -qE "deploy|déploiement|build-and-deploy|deploy-app"; then
|
||||
echo '{"followup_message":"Le déploiement a échoué. Analyser l'\''erreur, identifier la root cause, corriger le bug puis retenter le déploiement."}'
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
echo '{}'
|
||||
exit 0
|
||||
Reference in New Issue
Block a user