Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
182
dss-claude-plugin/hooks/scripts/git-backup.js
Executable file
182
dss-claude-plugin/hooks/scripts/git-backup.js
Executable file
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* DSS Git Backup Hook
|
||||
* Automatically commits changes when Claude Code session ends.
|
||||
* Written from scratch for DSS.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
// Configuration
|
||||
const DEFAULT_CONFIG = {
|
||||
git_backup: {
|
||||
enabled: true,
|
||||
require_git_repo: true,
|
||||
commit_only_if_changes: true,
|
||||
include_timestamp: true,
|
||||
commit_prefix: 'auto-backup',
|
||||
show_logs: false
|
||||
}
|
||||
};
|
||||
|
||||
// Prevent duplicate execution (Claude Code bug workaround)
|
||||
const STATE_DIR = path.join(__dirname, '..', '.state');
|
||||
const LOCK_FILE = path.join(STATE_DIR, '.git-backup.lock');
|
||||
const LOCK_TIMEOUT_MS = 3000;
|
||||
|
||||
function loadConfig() {
|
||||
const configPath = path.join(process.env.HOME || '', '.dss', 'hooks-config.json');
|
||||
try {
|
||||
if (fs.existsSync(configPath)) {
|
||||
const userConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
return { ...DEFAULT_CONFIG, ...userConfig };
|
||||
}
|
||||
} catch (e) {
|
||||
// Use defaults
|
||||
}
|
||||
return DEFAULT_CONFIG;
|
||||
}
|
||||
|
||||
function checkLock() {
|
||||
try {
|
||||
if (!fs.existsSync(STATE_DIR)) {
|
||||
fs.mkdirSync(STATE_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
if (fs.existsSync(LOCK_FILE)) {
|
||||
const lastRun = parseInt(fs.readFileSync(LOCK_FILE, 'utf8'));
|
||||
if (!isNaN(lastRun) && (Date.now() - lastRun < LOCK_TIMEOUT_MS)) {
|
||||
return false; // Already ran recently
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(LOCK_FILE, Date.now().toString(), 'utf8');
|
||||
return true;
|
||||
} catch (e) {
|
||||
return true; // Proceed on error
|
||||
}
|
||||
}
|
||||
|
||||
function isGitRepo() {
|
||||
try {
|
||||
execSync('git rev-parse --is-inside-work-tree', { stdio: 'pipe' });
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function hasChanges() {
|
||||
try {
|
||||
const status = execSync('git status --porcelain', { encoding: 'utf8' });
|
||||
return status.trim().length > 0;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getChangeSummary() {
|
||||
try {
|
||||
const status = execSync('git status --short', { encoding: 'utf8' });
|
||||
const lines = status.trim().split('\n').filter(Boolean);
|
||||
|
||||
let added = 0, modified = 0, deleted = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
const status = line.trim().charAt(0);
|
||||
if (status === 'A' || status === '?') added++;
|
||||
else if (status === 'M') modified++;
|
||||
else if (status === 'D') deleted++;
|
||||
}
|
||||
|
||||
return { added, modified, deleted, total: lines.length };
|
||||
} catch (e) {
|
||||
return { added: 0, modified: 0, deleted: 0, total: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
function createBackup(config) {
|
||||
const backupConfig = config.git_backup || {};
|
||||
|
||||
try {
|
||||
// Stage all changes
|
||||
execSync('git add -A', { stdio: 'pipe' });
|
||||
|
||||
// Build commit message
|
||||
const parts = [backupConfig.commit_prefix || 'auto-backup'];
|
||||
|
||||
if (backupConfig.include_timestamp) {
|
||||
const timestamp = new Date().toISOString().replace('T', ' ').replace(/\..+/, '');
|
||||
parts.push(timestamp);
|
||||
}
|
||||
|
||||
const summary = getChangeSummary();
|
||||
const summaryText = `(${summary.total} files: +${summary.added} ~${summary.modified} -${summary.deleted})`;
|
||||
|
||||
const commitMessage = `${parts.join(': ')} ${summaryText}\n\nGenerated by DSS Git Backup Hook`;
|
||||
|
||||
// Create commit
|
||||
execSync(`git commit -m "${commitMessage}"`, { stdio: 'pipe' });
|
||||
|
||||
// Get commit hash
|
||||
const commitHash = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim();
|
||||
|
||||
return { success: true, hash: commitHash, files: summary.total };
|
||||
} catch (e) {
|
||||
return { success: false, error: e.message };
|
||||
}
|
||||
}
|
||||
|
||||
function log(config, message) {
|
||||
if (config.git_backup?.show_logs) {
|
||||
console.log(JSON.stringify({
|
||||
systemMessage: message,
|
||||
continue: true
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
// Prevent duplicate execution
|
||||
if (!checkLock()) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Prevent hook recursion
|
||||
if (process.env.STOP_HOOK_ACTIVE === 'true') {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
if (!config.git_backup?.enabled) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Check for git repo
|
||||
if (config.git_backup.require_git_repo && !isGitRepo()) {
|
||||
log(config, 'DSS Git Backup: Not a git repository, skipping');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Check for changes
|
||||
if (config.git_backup.commit_only_if_changes && !hasChanges()) {
|
||||
log(config, 'DSS Git Backup: No changes to commit');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Create backup
|
||||
const result = createBackup(config);
|
||||
|
||||
if (result.success) {
|
||||
log(config, `DSS Git Backup: Committed ${result.files} files (${result.hash})`);
|
||||
} else {
|
||||
log(config, `DSS Git Backup: Failed - ${result.error}`);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user