Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions .claude/hooks/continuous_save.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def on_user_message(content: str, metadata: Dict = None):
"""Handler para mensagem do usuário (UserPromptSubmit)."""
entry = {
'type': 'user_message',
'content': content[:2000], # Limitar tamanho
'content': content[:200], # Limitar tamanho (security: avoid logging sensitive data)
'metadata': metadata or {}
}

Expand All @@ -304,11 +304,20 @@ def on_user_message(content: str, metadata: Dict = None):

def on_tool_use(tool_name: str, tool_input: Dict = None, result_preview: str = None):
"""Handler para uso de ferramenta (PostToolUse)."""
# Security: log only metadata, not full content (may contain secrets/tokens)
safe_input = None
if tool_input:
# Only log file paths and tool name, not content
safe_input = {}
for key in ['file_path', 'filepath', 'path', 'command', 'pattern']:
if key in tool_input:
safe_input[key] = str(tool_input[key])[:200]

entry = {
'type': 'tool_use',
'tool': tool_name,
'input_preview': str(tool_input)[:500] if tool_input else None,
'result_preview': result_preview[:500] if result_preview else None
'input_preview': str(safe_input)[:200] if safe_input else None,
'result_preview': result_preview[:200] if result_preview else None
}

# Extrair arquivo se for Edit/Write/Read
Expand All @@ -325,7 +334,7 @@ def on_response_complete(preview: str = None):
"""Handler para resposta completa (Stop)."""
entry = {
'type': 'response',
'preview': preview[:500] if preview else '[response complete]'
'preview': preview[:200] if preview else '[response complete]'
}

append_to_jsonl(entry)
Expand Down
20 changes: 16 additions & 4 deletions .claude/hooks/gsd-check-update.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node
// Check for GSD updates in background, write result to cache
// Called by SessionStart hook - runs once per session
// NOTE: This is the ONLY hook with network access (npm registry query)

const fs = require('fs');
const path = require('path');
Expand All @@ -22,9 +23,10 @@ if (!fs.existsSync(cacheDir)) {
}

// Run check in background (spawn background process, windowsHide prevents console flash)
// Security: uses execFileSync (no shell) with explicit timeout for network call
const child = spawn(process.execPath, ['-e', `
const fs = require('fs');
const { execSync } = require('child_process');
const { execFileSync } = require('child_process');

const cacheFile = ${JSON.stringify(cacheFile)};
const projectVersionFile = ${JSON.stringify(projectVersionFile)};
Expand All @@ -42,8 +44,14 @@ const child = spawn(process.execPath, ['-e', `

let latest = null;
try {
latest = execSync('npm view get-shit-done-cc version', { encoding: 'utf8', timeout: 10000, windowsHide: true }).trim();
} catch (e) {}
latest = execFileSync('npm', ['view', 'get-shit-done-cc', 'version'], {
encoding: 'utf8',
timeout: 10000,
windowsHide: true
}).trim();
} catch (e) {
// Network or npm failure - non-critical, write cache with error state
}

const result = {
update_available: latest && installed !== latest,
Expand All @@ -52,7 +60,11 @@ const child = spawn(process.execPath, ['-e', `
checked: Math.floor(Date.now() / 1000)
};

fs.writeFileSync(cacheFile, JSON.stringify(result));
try {
fs.writeFileSync(cacheFile, JSON.stringify(result));
} catch (e) {
// Cache write failure - non-critical
}
`], {
stdio: 'ignore',
windowsHide: true,
Expand Down
30 changes: 30 additions & 0 deletions .claude/hooks/memory_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ def update_memory_file(section, content):
with open(memory_path, 'r', encoding='utf-8') as f:
memory = f.read()

original_memory = memory # snapshot for audit diff

# Encontrar secao de decisoes e adicionar
if section == 'decisions':
# Procurar por "### Decisoes Importantes"
Expand Down Expand Up @@ -128,6 +130,15 @@ def update_memory_file(section, content):
with open(memory_path, 'w', encoding='utf-8') as f:
f.write(memory)

# Audit: log what changed
if memory != original_memory:
diff_lines = len(memory.splitlines()) - len(original_memory.splitlines())
audit_file_modification(
str(memory_path),
f'update_section:{section}',
f'+{diff_lines} lines, content: {content[:100]}'
)

return True

def log_update(update_type, content):
Expand All @@ -145,6 +156,25 @@ def log_update(update_type, content):
with open(log_path, 'a', encoding='utf-8') as f:
f.write(json.dumps(log_entry, ensure_ascii=False) + '\n')

def audit_file_modification(file_path, modification_type, diff_summary):
"""Audit log for ALL file modifications made by this hook."""
project_dir = get_project_dir()
audit_path = Path(project_dir) / 'logs' / 'memory-audit.jsonl'
audit_path.parent.mkdir(parents=True, exist_ok=True)

audit_entry = {
'timestamp': datetime.now().isoformat(),
'file_modified': str(file_path),
'modification_type': modification_type,
'diff_summary': diff_summary[:500]
}

try:
with open(audit_path, 'a', encoding='utf-8') as f:
f.write(json.dumps(audit_entry, ensure_ascii=False) + '\n')
except Exception:
pass # Audit logging should never block the hook

def main():
"""Funcao principal do hook."""
try:
Expand Down
9 changes: 8 additions & 1 deletion .claude/hooks/notification_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,17 @@
LOGS_PATH = PROJECT_ROOT / 'logs' / 'notifications'
LOGS_PATH.mkdir(parents=True, exist_ok=True)

def _escape_applescript(s: str) -> str:
"""Escape string for safe use inside AppleScript double-quoted strings."""
return s.replace('\\', '\\\\').replace('"', '\\"')

def send_macos_notification(title: str, message: str, sound: str = "default"):
"""Send native macOS notification"""
safe_message = _escape_applescript(message)
safe_title = _escape_applescript(title)
safe_sound = _escape_applescript(sound)
script = f'''
display notification "{message}" with title "{title}" sound name "{sound}"
display notification "{safe_message}" with title "{safe_title}" sound name "{safe_sound}"
'''
try:
subprocess.run(['osascript', '-e', script], capture_output=True, timeout=5)
Expand Down