dev-memory-update
Update development memory (events.jsonl) based on commit metadata and diff analysis. Automatically tracks features, fixes, refactorings, and decisions.
$ 安裝
git clone https://github.com/BerryKuipers/claude-code-toolkit /tmp/claude-code-toolkit && cp -r /tmp/claude-code-toolkit/.claude/skills/memory/dev-memory/update ~/.claude/skills/claude-code-toolkit// tip: Run this command in your terminal to install the skill
name: dev-memory-update description: Update development memory (events.jsonl) based on commit metadata and diff analysis. Automatically tracks features, fixes, refactorings, and decisions.
Dev Memory Update Skill
Automatically extract and store development events from git commits into ai_memory/events.jsonl.
Purpose
This skill analyzes commit metadata (message, diff, branch, timestamp) and creates structured event records that build an automated development timeline for the project.
When to Use
Automatic Invocation
- Post-commit hook: Runs automatically after every
git commit - Batch mode: Process multiple commits at once
Manual Invocation
- When reviewing past commits to backfill memory
- When commit hook was disabled and you want to catch up
- When you want to add events for non-commit activities (meetings, decisions)
Input Requirements
Required
{
repo: string; // Repository name (not full path)
branch: string; // Current git branch
commit_hash: string; // Git SHA (short or long form)
commit_message: string; // Full commit message
commit_timestamp: string; // ISO 8601 timestamp
}
Optional
{
diff_summary?: string; // Output of `git diff --stat`
files_changed?: number; // Count of modified files
related_issues?: string[]; // Extracted issue numbers
related_prs?: string[]; // Extracted PR numbers
author?: string; // Commit author
}
Behavior
Step 1: Initialize Memory Directory
# Ensure ai_memory/ exists
MEMORY_DIR="ai_memory"
mkdir -p "$MEMORY_DIR"
# Create .gitkeep if first time
if [ ! -f "$MEMORY_DIR/.gitkeep" ]; then
echo "# AI Development Memory" > "$MEMORY_DIR/.gitkeep"
echo "# Auto-generated by dev-memory-update skill" >> "$MEMORY_DIR/.gitkeep"
fi
Step 2: Extract Metadata
// Parse commit message for patterns
const parseCommitMessage = (message: string) => {
const lines = message.split('\n');
const subject = lines[0];
const body = lines.slice(1).join('\n').trim();
// Extract type from conventional commit format
const typeMatch = subject.match(/^(feat|fix|refactor|test|docs|chore|perf|style|build|ci)(\(.+\))?:/);
const type = typeMatch ? typeMatch[1] : null;
// Extract issue/PR numbers
const issueMatches = message.match(/#(\d+)/g) || [];
const issues = issueMatches.map(m => m);
const prMatches = message.match(/\bPR #(\d+)\b/gi) || [];
const prs = prMatches.map(m => '#' + m.match(/\d+/)[0]);
// Extract epic ID if present
const epicMatch = message.match(/epic[:-]\s*([a-z0-9-]+)/i);
const epicId = epicMatch ? epicMatch[1] : null;
return { subject, body, type, issues, prs, epicId };
};
Step 3: Infer Event Type
Map commit type to event type:
| Commit Type | Event Type | Notes |
|---|---|---|
feat | feature_implemented | New functionality |
fix | bug_fixed | Bug resolution |
refactor | refactor | Code restructuring |
test | test_added | Test coverage |
docs | docs_updated | Documentation |
perf | feature_implemented | Performance improvement treated as feature |
build, ci, chore | No event | Skip unless significant |
| No type prefix | feature_implemented | Default assumption |
Decision logic:
const inferEventType = (commitType: string | null, message: string): EventType => {
if (commitType === 'feat' || commitType === 'perf') return 'feature_implemented';
if (commitType === 'fix') return 'bug_fixed';
if (commitType === 'refactor') return 'refactor';
if (commitType === 'test') return 'test_added';
if (commitType === 'docs') return 'docs_updated';
// Check for decision keywords in message
if (/\b(decided|chose|selected|adopted)\b/i.test(message)) {
return 'decision';
}
// Check for breaking change keywords
if (/BREAKING CHANGE|breaking:/i.test(message)) {
return 'breaking_change';
}
// Default to feature
return 'feature_implemented';
};
Step 4: Generate Event ID
Performance Note: For large files, this implementation reads the entire file. Consider optimizing by reading backwards (using tac command or a reverse-reading library) to find the last event ID more efficiently:
# Alternative: Read last matching event backwards (shell example)
LAST_EVENT=$(tac ai_memory/events.jsonl 2>/dev/null | grep -m1 "\"id\":\"evt-$TODAY_DATE" | jq -r '.id')
const generateEventId = (): string => {
const date = new Date().toISOString().split('T')[0].replace(/-/g, '');
// Read existing events to find next sequence number
const eventsFile = 'ai_memory/events.jsonl';
const todayPrefix = `evt-${date}`;
let maxSeq = 0;
if (fs.existsSync(eventsFile)) {
const lines = fs.readFileSync(eventsFile, 'utf-8').split('\n').filter(l => l.trim());
for (const line of lines) {
try {
const event = JSON.parse(line);
if (event.id?.startsWith(todayPrefix)) {
const seqMatch = event.id.match(/-(\d{3})$/);
if (seqMatch) {
maxSeq = Math.max(maxSeq, parseInt(seqMatch[1], 10));
}
}
} catch (e) {
// Skip malformed lines
}
}
}
const nextSeq = (maxSeq + 1).toString().padStart(3, '0');
return `${todayPrefix}-${nextSeq}`;
};
Step 5: Extract Open Questions and Next Steps
const extractActions = (message: string) => {
const openQuestions: string[] = [];
const nextSteps: string[] = [];
const lines = message.split('\n');
for (const line of lines) {
const trimmed = line.trim();
// Look for questions
if (trimmed.endsWith('?') && trimmed.length > 10) {
openQuestions.push(trimmed);
}
// Look for TODO, FIXME, action items
if (/^(TODO|FIXME|Next|Action|Follow-up):/i.test(trimmed)) {
const action = trimmed.replace(/^[^:]+:\s*/, '');
nextSteps.push(action);
}
// Look for bullet points that look like next steps
if (/^[-*]\s+(Add|Create|Update|Fix|Test|Implement)/i.test(trimmed)) {
nextSteps.push(trimmed.replace(/^[-*]\s+/, ''));
}
}
return { openQuestions, nextSteps };
};
Step 6: Create Event Object
const createEvent = (input: CommitInput): DevEvent => {
const { subject, body, type, issues, prs, epicId } = parseCommitMessage(input.commit_message);
const eventType = inferEventType(type, input.commit_message);
const { openQuestions, nextSteps } = extractActions(input.commit_message);
const event: DevEvent = {
id: generateEventId(),
timestamp: input.commit_timestamp,
repo: input.repo,
branch: input.branch,
type: eventType,
title: subject.substring(0, 100), // Truncate if needed
summary: body.substring(0, 500) || subject,
commit_hash: input.commit_hash.substring(0, 7), // Short SHA
};
// Add optional fields only if present
if (issues.length > 0) event.related_issues = issues;
if (prs.length > 0) event.related_prs = prs;
if (epicId) event.epic_id = epicId;
if (openQuestions.length > 0) event.open_questions = openQuestions;
if (nextSteps.length > 0) event.next_steps = nextSteps;
if (input.files_changed) event.files_changed = input.files_changed;
// Set confidence based on how much we could extract
event.confidence = type && issues.length > 0 ? 'high' :
type ? 'medium' : 'low';
return event;
};
Step 7: Append to events.jsonl
# Write event as single-line JSON
echo "$EVENT_JSON" >> ai_memory/events.jsonl
IMPORTANT:
- No trailing comma
- No pretty-printing (single line)
- UTF-8 encoding
- Append mode (
>>not>)
Step 8: Update or Create Session
Performance Note: This implementation reads the entire sessions file. For better performance, consider reading backwards to find the most recent session for the current branch and day:
# Alternative: Read sessions backwards (shell example)
LAST_SESSION=$(tac ai_memory/sessions.jsonl 2>/dev/null | grep -m1 "\"branch\":\"$BRANCH\"" | jq -r 'select(.timestamp_start | startswith("'$TODAY'"))')
const updateSession = (event: DevEvent, commitInput: CommitInput) => {
const sessionFile = 'ai_memory/sessions.jsonl';
const sessionId = `sess-${event.timestamp.split('T')[0].replace(/-/g, '')}-${event.timestamp.split('T')[1].substring(0, 6).replace(/:/g, '')}`;
// Try to find existing session for today on this branch
let existingSession: DevSession | null = null;
const today = event.timestamp.split('T')[0];
if (fs.existsSync(sessionFile)) {
const lines = fs.readFileSync(sessionFile, 'utf-8').split('\n').filter(l => l.trim());
for (const line of lines) {
try {
const session = JSON.parse(line);
if (session.timestamp_start.startswith(today) && session.branch === event.branch) {
existingSession = session;
break;
}
} catch (e) {
// Skip malformed lines
}
}
}
if (existingSession) {
// Update existing session
existingSession.timestamp_end = event.timestamp;
existingSession.created_events.push(event.id);
if (event.commit_hash) {
existingSession.commits = existingSession.commits || [];
existingSession.commits.push(event.commit_hash);
}
// Re-write updated session (append-only: add new version, old one ignored when reading latest)
fs.appendFileSync(sessionFile, JSON.stringify(existingSession) + '\n', 'utf-8');
} else {
// Create new session
const newSession: DevSession = {
id: sessionId,
timestamp_start: event.timestamp,
timestamp_end: event.timestamp,
repo: event.repo,
branch: event.branch,
agent: 'Claude Code',
summary: `Working on ${event.title}`,
created_events: [event.id],
commits: event.commit_hash ? [event.commit_hash] : [],
};
fs.appendFileSync(sessionFile, JSON.stringify(newSession) + '\n', 'utf-8');
}
};
Example Usage
From Post-Commit Hook
#!/bin/bash
# .claude/hooks/post-commit-memory.sh
# Extract commit info
REPO=$(basename "$(git rev-parse --show-toplevel)")
BRANCH=$(git branch --show-current)
COMMIT_HASH=$(git rev-parse HEAD)
COMMIT_MESSAGE=$(git log -1 --pretty=%B)
COMMIT_TIMESTAMP=$(git log -1 --format=%aI)
FILES_CHANGED=$(git diff-tree --no-commit-id --name-only -r HEAD | wc -l)
# Call dev-memory-update skill (Claude invokes this)
echo "→ Updating dev memory for commit ${COMMIT_HASH:0:7}..."
# Claude would execute this skill with the extracted data
Manual Backfill
# Process last 10 commits
git log -10 --pretty=format:'%H|%aI|%s' | while IFS='|' read hash timestamp subject; do
# Extract and process each commit
echo "Processing: $subject"
done
Constraints
Max Events Per Commit
Default: 3 events maximum per commit
Rationale: Most commits should represent 1 logical change. If more than 3 events, commit is likely too large.
Override: Can be configured in .claude/config.yml:
devMemory:
maxEventsPerCommit: 3
Skipped Commits
Skip these commit types:
chore:- Unless significant (dependency upgrades)build:- Unless build system changesci:- Unless CI/CD improvements- Merge commits - Don't create events for merges
- Revert commits - Could create
bug_fixedevent with note
Error Handling
If memory update fails:
- Log warning to stderr
- Don't block the commit
- Continue gracefully
Never:
- Fail the commit because memory update failed
- Throw errors that stop the workflow
- Corrupt existing JSONL files
Output
Success
{
"status": "success",
"events_created": 1,
"event_ids": ["evt-20251210-001"],
"session_updated": true,
"session_id": "sess-20251210-210500"
}
Skipped
{
"status": "skipped",
"reason": "Commit type 'chore' not significant enough",
"commit_type": "chore"
}
Error
{
"status": "error",
"error": "Failed to parse commit message",
"graceful": true
}
Integration with Workflows
Conductor Workflow
- After Phase 4, Step 2 (commit-with-validation)
- Post-commit hook runs automatically
- Memory updated with feature/fix details
Manual Commit Workflow
- Hook runs on every commit
- No user intervention needed
- Silent unless errors
Related Skills
commit-with-validation- Creates the commit that triggers this skilldev-memory-briefing- Reads events created by this skillproject-memory- Complementary long-term memory (MCP-based)
Configuration
In .claude/config.yml:
devMemory:
enabled: true
autoUpdateOnCommit: true
maxEventsPerCommit: 3
skipCommitTypes: ['chore', 'build', 'ci']
confidenceThreshold: 'low' # Include all events, even low confidence
Best Practices
- Write descriptive commit messages - Better messages = better memory
- Use conventional commit format - Helps with type inference
- Link issues in commits - Use
Fixes #123format - Mention epic in commit - Include
epic-namein message body - Add TODO/FIXME - Extracted as next steps automatically
- One logical change per commit - Easier to categorize
Troubleshooting
Memory not updating?
- Check
.claude/config.yml- ensuredevMemory.enabled: true - Check hook is configured in
.claude/settings.json - Run
ls -la .claude/hooks/post-commit-memory.sh- ensure executable - Check
ai_memory/events.jsonlpermissions
Wrong event types?
- Use conventional commit prefixes:
feat:,fix:, etc. - Review event type inference logic above
- Manually edit
events.jsonlif needed (it's just JSON)
Events.jsonl growing too large?
- Check file size:
wc -l ai_memory/events.jsonl - If > 10,000 lines, consider archiving old events
- Compress:
gzip ai_memory/events-2024.jsonl
Duplicate events?
- Check if hook running multiple times
- Review
.claude/settings.jsonPostToolUse hooks - Remove duplicates manually (edit JSONL file)
Repository
