feat: auto-implement design changes via post-commit hook

- Add scripts/design-diff-analyzer.js to detect design changes
  • Analyzes ui-design.pen diff (colors, typography, spacing, layout)
  • Generates implementation tasks for Claude Code
  • Distinguishes content-only changes (no implementation)
- Add .git/hooks/post-commit to spawn Claude Code automatically
  • Triggers when ui-design.pen is committed
  • Spawns isolated sub-agent with auto-notify on completion
  • 10min timeout, auto-cleanup after done
- Update install-hooks.sh to install post-commit hook
- Document full workflow in .github/HOOKS.md

Workflow: commit design → analyzer runs → Claude Code spawns → implements → notifies Brian → Brian notifies Luca
This commit is contained in:
lucaronin
2026-02-17 13:27:09 +01:00
parent 81d00395db
commit cfa4754f29
4 changed files with 423 additions and 2 deletions

153
.github/HOOKS.md vendored
View File

@@ -4,6 +4,17 @@
Il repository ha un pre-commit hook che verifica la qualità del codice prima di ogni commit.
## Post-Commit Hook: Auto-Implement Design Changes
Quando committi modifiche a `ui-design.pen`, il post-commit hook:
1. Analizza automaticamente le modifiche (colori, typography, spacing, layout)
2. Spawna Claude Code in background per implementare le modifiche
3. Ti notifica quando l'implementazione è completa
---
## Pre-Commit Hook Details
### Cosa Fa
1. **Analizza file staged** — controlla solo TypeScript/Rust modificati
@@ -102,3 +113,145 @@ Possibili miglioramenti:
- [ ] Fail automatico se code health < soglia
- [ ] Cache dei risultati per evitare re-analisi
- [ ] Hook pre-push più pesante per analisi completa
---
## Post-Commit Hook: Auto-Implement Design Changes
### Cosa Fa
Quando committi modifiche a `ui-design.pen`, il hook:
1. **Analizza il diff** — usa `scripts/design-diff-analyzer.js`
2. **Identifica modifiche significative:**
- 🎨 Colori (fill, backgroundColor)
- 📝 Typography (fontSize, fontFamily)
- 📏 Spacing (padding, margin, gap)
- 🔲 Layout (nuovi componenti, riorganizzazioni)
3. **Spawna Claude Code** — in background via `openclaw sessions spawn`
4. **Auto-notifica** — quando l'implementazione è completa
### Cosa Implementa
| Tipo Modifica | Azione |
|--------------|--------|
| Colori | Aggiorna `src/theme.json` o CSS variables |
| Typography | Aggiorna `src/theme.json` typography |
| Spacing | Aggiorna `src/theme.json` spacing |
| Layout | Modifica/crea componenti React |
| Testi mockup | Nessuna azione (solo design) |
### Esempio Output
```
🎨 Design file changed - analyzing...
📋 Implementation tasks:
1. [HIGH] Update color palette
- fill: $--muted-foreground → #666666
- backgroundColor: #FFFFFF → #F5F5F5
Update src/theme.json or CSS variables to match the design.
2. [MEDIUM] Update typography
- fontSize: 14px → 16px
Update src/theme.json typography settings.
🚀 Spawning Claude Code to implement changes...
✅ Claude Code spawned - you'll be notified when implementation is complete
```
### Workflow Completo
```
You Post-Commit Hook Claude Code Brian (AI)
│ │ │ │
├─ Modify ui-design.pen │ │ │
├─ git add ui-design.pen │ │ │
├─ git commit │ │ │
│ │ │ │
│ ├─ Analyze diff │ │
│ ├─ Generate tasks │ │
│ ├─ Spawn Claude Code ────────> │
│ │ │ │
│ │ ├─ Implement changes │
│ │ ├─ Test visually │
│ │ ├─ Run tests │
│ │ ├─ Commit │
│ │ ├─ openclaw system event ────>
│ │ │ │
│ │ │ ├─ Notify Telegram
│ <─────────────────────────────────────────────────────────────────────────────┘
│ "✅ Design changes implemented and tested"
```
### Design Diff Analyzer
Lo script `scripts/design-diff-analyzer.js` rileva:
- **Color changes** — `"fill": "#OLD" → "#NEW"`
- **Font changes** — `"fontSize": 14 → 16`
- **Spacing** — `"padding": 8 → 12`
- **Content** — testi mockup (no implementation)
Uso:
```bash
# Analizza ultimo commit
node scripts/design-diff-analyzer.js
# Output JSON per automation
node scripts/design-diff-analyzer.js --json
```
### Disabilitare Temporaneamente
Se vuoi committare il design senza auto-implementazione:
```bash
# Disabilita hook
mv .git/hooks/post-commit .git/hooks/post-commit.disabled
# Commit
git commit -m "design: update mockup"
# Riabilita hook
mv .git/hooks/post-commit.disabled .git/hooks/post-commit
```
### Monitorare Claude Code
Mentre Claude Code lavora in background:
```bash
# Lista sub-agent attivi
openclaw sessions list --kinds isolated
# Vedi log di un sub-agent
openclaw sessions history --session-key <key>
# Ferma sub-agent (se necessario)
openclaw subagents kill --target design-auto-implement
```
### Troubleshooting
**Hook non parte:**
- Verifica che `ui-design.pen` sia effettivamente cambiato: `git diff HEAD~1 ui-design.pen`
- Verifica che lo script analyzer esista: `ls -la scripts/design-diff-analyzer.js`
**Nessuna notifica:**
- Claude Code potrebbe essere ancora in esecuzione — controlla `openclaw sessions list`
- Verifica che il prompt includa `openclaw system event` al termine
**Modifiche non implementate:**
- Controlla i log di Claude Code: `openclaw sessions history --session-key <key>`
- Il sub-agent viene auto-eliminato dopo completion (cleanup=delete)
### Limitazioni
- **Modifiche complesse** — layout completamente nuovi potrebbero richiedere intervento manuale
- **Timeout** — 10 minuti max (configurabile in hook)
- **Solo modifiche recenti** — analizza solo HEAD vs HEAD~1

View File

@@ -9,11 +9,17 @@ echo "Installing git hooks..."
# Copy pre-commit hook
cp "$SCRIPT_DIR/pre-commit" "$HOOKS_DIR/pre-commit"
chmod +x "$HOOKS_DIR/pre-commit"
echo "✅ Installed pre-commit hook"
# Copy post-commit hook
cp "$SCRIPT_DIR/post-commit" "$HOOKS_DIR/post-commit"
chmod +x "$HOOKS_DIR/post-commit"
echo "✅ Installed post-commit hook"
echo ""
echo "Hooks installed:"
echo " - pre-commit: CodeScene code health check"
echo " - post-commit: Auto-implement design changes via Claude Code"
echo ""
echo "To bypass a hook, use: git commit --no-verify"
echo "To bypass pre-commit, use: git commit --no-verify"
echo "Or include [skip codescene] in your commit message"

72
.github/hooks/post-commit vendored Normal file
View File

@@ -0,0 +1,72 @@
#!/bin/bash
# Post-commit hook: Auto-implement design changes via Claude Code
# Copy to .git/hooks/post-commit and make executable
set -e
# Check if ui-design.pen was modified in this commit
if ! git diff --name-only HEAD~1 HEAD | grep -q "ui-design.pen"; then
exit 0
fi
echo ""
echo "🎨 Design file changed - analyzing..."
# Run analyzer
ANALYZER_OUTPUT=$(node scripts/design-diff-analyzer.js --json 2>&1)
if [ $? -eq 0 ]; then
echo "✅ No implementation tasks needed (content-only changes)"
exit 0
fi
# Extract tasks from JSON
TASKS_JSON=$(echo "$ANALYZER_OUTPUT" | sed -n '/---JSON---/,$ p' | tail -n +2)
if [ -z "$TASKS_JSON" ]; then
echo "✅ No significant design changes"
exit 0
fi
echo "$ANALYZER_OUTPUT" | sed '/---JSON---/,$ d'
# Generate Claude Code prompt
PROMPT=$(cat <<EOF
The design file ui-design.pen was just updated. Analyze the changes and implement them.
Design changes detected:
$ANALYZER_OUTPUT
Tasks:
1. Review the git diff of ui-design.pen (HEAD vs HEAD~1)
2. Identify what changed (colors, typography, spacing, layout)
3. Update the corresponding code:
- Colors/Typography/Spacing → update src/theme.json
- Layout changes → update React components
- New elements → create new components
4. Test visually in Chrome (pnpm dev, open localhost:5173)
5. Run tests: pnpm test
6. Commit changes with descriptive message referencing the design update
When completely finished, run:
openclaw system event --text "✅ Design changes implemented and tested" --mode now
EOF
)
echo ""
echo "🚀 Spawning Claude Code to implement changes..."
echo ""
# Spawn Claude Code via OpenClaw
openclaw sessions spawn \
--task "$PROMPT" \
--label "design-auto-implement" \
--cleanup delete \
--timeout-seconds 600 \
--agent-id main
echo ""
echo "✅ Claude Code spawned - you'll be notified when implementation is complete"
echo ""
exit 0

190
scripts/design-diff-analyzer.js Executable file
View File

@@ -0,0 +1,190 @@
#!/usr/bin/env node
/**
* Design Diff Analyzer
* Analyzes changes to ui-design.pen and generates implementation tasks for Claude Code
*/
const { execSync } = require('child_process');
const fs = require('fs');
function getDiff() {
try {
return execSync('git diff HEAD~1 ui-design.pen', { encoding: 'utf8' });
} catch (e) {
return '';
}
}
function analyzeDiff(diff) {
const changes = {
colors: [],
typography: [],
spacing: [],
layout: [],
content: []
};
const lines = diff.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Color changes
if (line.includes('"fill":') || line.includes('"backgroundColor":')) {
const oldMatch = lines[i-1]?.match(/"(fill|backgroundColor)":\s*"([^"]+)"/);
const newMatch = line.match(/"(fill|backgroundColor)":\s*"([^"]+)"/);
if (oldMatch && newMatch && oldMatch[2] !== newMatch[2]) {
changes.colors.push({
type: oldMatch[1],
from: oldMatch[2],
to: newMatch[2]
});
}
}
// Font size changes
if (line.includes('"fontSize":')) {
const oldMatch = lines[i-1]?.match(/"fontSize":\s*(\d+)/);
const newMatch = line.match(/"fontSize":\s*(\d+)/);
if (oldMatch && newMatch && oldMatch[1] !== newMatch[1]) {
changes.typography.push({
type: 'fontSize',
from: parseInt(oldMatch[1]),
to: parseInt(newMatch[1])
});
}
}
// Spacing/padding changes
if (line.includes('"padding":') || line.includes('"margin":') || line.includes('"gap":')) {
const oldMatch = lines[i-1]?.match(/"(padding|margin|gap)":\s*(\d+)/);
const newMatch = line.match(/"(padding|margin|gap)":\s*(\d+)/);
if (oldMatch && newMatch && oldMatch[2] !== newMatch[2]) {
changes.spacing.push({
type: oldMatch[1],
from: parseInt(oldMatch[2]),
to: parseInt(newMatch[2])
});
}
}
// Content changes (text only - no implementation needed)
if (line.includes('"content":')) {
const oldMatch = lines[i-1]?.match(/"content":\s*"([^"]+)"/);
const newMatch = line.match(/"content":\s*"([^"]+)"/);
if (oldMatch && newMatch && oldMatch[1] !== newMatch[1]) {
changes.content.push({
from: oldMatch[1],
to: newMatch[1]
});
}
}
}
return changes;
}
function generateTasks(changes) {
const tasks = [];
// Color changes → update theme.json or CSS variables
if (changes.colors.length > 0) {
const colorList = changes.colors.map(c =>
` - ${c.type}: ${c.from}${c.to}`
).join('\n');
tasks.push({
priority: 'high',
description: 'Update color palette',
details: `Color changes detected:\n${colorList}\n\nUpdate src/theme.json or CSS variables to match the design.`
});
}
// Typography changes → update theme.json
if (changes.typography.length > 0) {
const typoList = changes.typography.map(t =>
` - ${t.type}: ${t.from}px → ${t.to}px`
).join('\n');
tasks.push({
priority: 'medium',
description: 'Update typography',
details: `Typography changes detected:\n${typoList}\n\nUpdate src/theme.json typography settings.`
});
}
// Spacing changes → update theme.json
if (changes.spacing.length > 0) {
const spacingList = changes.spacing.map(s =>
` - ${s.type}: ${s.from}px → ${s.to}px`
).join('\n');
tasks.push({
priority: 'medium',
description: 'Update spacing',
details: `Spacing changes detected:\n${spacingList}\n\nUpdate src/theme.json spacing values.`
});
}
return tasks;
}
function formatOutput(tasks, changes) {
if (tasks.length === 0 && changes.content.length === 0) {
return null;
}
let output = '🎨 Design changes detected:\n\n';
if (tasks.length > 0) {
output += '📋 Implementation tasks:\n\n';
tasks.forEach((task, i) => {
output += `${i + 1}. [${task.priority.toUpperCase()}] ${task.description}\n`;
output += `${task.details}\n\n`;
});
}
if (changes.content.length > 0) {
output += '📝 Content changes (mockup text only, no code changes needed):\n';
changes.content.forEach(c => {
output += ` - "${c.from}" → "${c.to}"\n`;
});
output += '\n';
}
return { output, tasks };
}
function main() {
const diff = getDiff();
if (!diff) {
console.log('No design changes detected');
process.exit(0);
}
const changes = analyzeDiff(diff);
const tasks = generateTasks(changes);
const result = formatOutput(tasks, changes);
if (!result) {
console.log('No significant design changes detected');
process.exit(0);
}
console.log(result.output);
// Output JSON for automation
if (process.argv.includes('--json')) {
console.log('\n---JSON---');
console.log(JSON.stringify({ tasks, changes }, null, 2));
}
process.exit(tasks.length > 0 ? 1 : 0); // Exit 1 if there are tasks to implement
}
main();