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:
153
.github/HOOKS.md
vendored
153
.github/HOOKS.md
vendored
@@ -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
|
||||
|
||||
10
.github/hooks/install-hooks.sh
vendored
10
.github/hooks/install-hooks.sh
vendored
@@ -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
72
.github/hooks/post-commit
vendored
Normal 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
190
scripts/design-diff-analyzer.js
Executable 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();
|
||||
Reference in New Issue
Block a user