In Part 1 (Design), I wrote about folder structure and tag design. This time it’s the analysis installment.
To be honest, you can live without everything I’m about to show. I just enjoy visualizing my own activity, and before I knew it my Makefile had sprouted all sorts of things.
Why analyze?
Not because I need to. Because I like it.
- I want to see when I’m most focused
- I want to know which topics my notes lean toward
- Graphs make me happy
If I had to give a practical reason, knowing “which topics have piled up” makes it easier to spot notes that could turn into blog posts. But really, building the analysis pipeline is just fun in itself.
The overall Makefile
I have a Makefile for managing the knowledge base. make help lists the available commands.
VAULT_PATH := vault
.PHONY: help status tag-graph day-graph time-graph heatmap
help: ## Show this help message @grep -E '^[a-zA-Z_-]+:.*?## .*$' $(MAKEFILE_LIST) | sort | \ awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $1, $2}'Below, I’ll walk through a few of the commands.
Note count statistics
make status shows statistics for the whole vault.
status: ## Show vault status @echo "Vault Statistics:" @echo "==================" @echo "Total markdown files: $(find $(VAULT_PATH) -name "*.md" | wc -l)" @echo "" @echo "Directory Breakdown:" @for dir in $(find $(VAULT_PATH) -maxdepth 1 -type d ! -path $(VAULT_PATH) | sort); do \ basename=$(basename "$dir"); \ count=$(find "$dir" -name "*.md" 2>/dev/null | wc -l); \ if [ "$count" -gt 0 ]; then \ printf "%-25s: %d\n" "$basename" "$count"; \ fi; \ doneThis gives you the note count per folder. If Reference Notes are piling up, it might be time to create a MOC.

Tag skew analysis
make tag-graph visualizes the most-used tags.
tag-graph: ## Meaningful tag usage with ASCII bar chart @echo "Tag Usage Frequency:" @echo "====================" @find $(VAULT_PATH) -name "*.md" -type f -exec grep -A 20 "^tags:" {} \; 2>/dev/null | \ grep "^ - " | sed 's/^ - //' | \ grep -v -E '^(reference_note|permanent_note|fleeting_note)$' | \ sort | uniq -c | sort -nr | head -15 | \ awk '{count=$1; tag=$2; bar=""; for(i=0;i<int(count*40/50);i++) bar=bar"█"; printf "%-25s %3d %s\n", tag, count, bar}'You can see which topics your notes cluster around. Tags with long bars tend to make good blog post material.

Note count growth graph
I also like make growth-graph. It shows how the note count in the vault has grown over time.
growth-graph: ## Show markdown file count growth over time (ASCII graph) @git log --pretty=format:"%H %ad" --date=short --reverse | \ xargs -P 8 -I {} sh -c 'echo $(echo "{}" | cut -d" " -f2-),$(git ls-tree -r $(echo "{}" | cut -d" " -f1) --name-only | grep -c "\.md$")' | \ gnuplot -e "set terminal dumb 80 24; set datafile separator ','; set xdata time; set timefmt '%Y-%m-%d'; set format x '%m/%d'; set xlabel 'Date'; set ylabel 'Files'; set title 'Vault Growth Over Time'; plot '/dev/stdin' using 1:2 with lines notitle"It tallies the Markdown file count at each commit and draws a growth curve. Seeing the notes climb steadily is motivating enough to keep going.

Activity by day of week
make day-graph shows the commit count per day of the week. The ASCII chart is generated by gnuplot.
day-graph: ## Gnuplot bar chart of commits by day of week @git log --pretty=format:"%ad" --date=format:"%w" | sort | uniq -c | \ awk '{days[$2]=$1} END {for(i=0;i<=6;i++) printf "%d %s %d\n", i, \ (i==0?"Sun":i==1?"Mon":i==2?"Tue":i==3?"Wed":i==4?"Thu":i==5?"Fri":"Sat"), \ (days[i]?days[i]:0)}' | \ gnuplot -e "set terminal dumb 80 20; set style data histogram; \ set title 'Commits by Day of Week'; plot '/dev/stdin' using 3:xtic(2) notitle"In my case, weekends turned out to have far more commits than weekdays. Apparently I’m too tired after work to write notes.

Activity by time of day
make time-graph is my favorite. It shows commits broken down by hour.
time-graph: ## Gnuplot line chart of commits by hour @git log --pretty=format:"%ad" --date=format:"%H" | sort | uniq -c | \ awk '{hours[$2]=$1} END {for(i=0;i<=23;i++) printf "%d %d\n", i, \ (hours[sprintf("%02d",i)]?hours[sprintf("%02d",i)]:0)}' | \ gnuplot -e "set terminal dumb 80 20; set xlabel 'Hour (0-23)'; \ set title 'Commit Activity by Hour'; plot '/dev/stdin' using 1:2 with linespoints notitle"You can see whether you’re a morning person or a night owl, and when you actually focus.

Day-of-week × hour heatmap
make heatmap visualizes commit frequency along two axes: day of week and hour.
heatmap: ## Activity heatmap showing day of week vs hour @git log --pretty=format:"%ad" --date=format:"%w %H" | \ awk '{day_hour[$1 " " $2]++; if(day_hour[$1 " " $2] > max) max = day_hour[$1 " " $2]} END { \ days[0]="Sun"; days[1]="Mon"; days[2]="Tue"; days[3]="Wed"; \ days[4]="Thu"; days[5]="Fri"; days[6]="Sat"; \ print "Activity Heatmap: Commits by Day and Hour"; \ print ""; \ printf " 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23\n"; \ for(d=0;d<=6;d++) { \ printf "%s: ", days[d]; \ for(h=0;h<24;h++) { \ hour = sprintf("%02d", h); \ count = (day_hour[d " " hour]?day_hour[d " " hour]:0); \ if(count == 0) printf " . "; \ else if(count <= max/4) printf " ░ "; \ else if(count <= max/2) printf " ▒ "; \ else if(count <= max*3/4) printf " ▓ "; \ else printf " █ "; \ } \ printf "\n"; \ } \ print ""; \ print "Legend: . = none, ░ = low, ▒ = medium, ▓ = high, █ = very high"; \ }'It’s clear that I focus most from Sunday afternoon into the evening. It feels a lot like GitHub’s contribution graph, but since this one is dedicated to the knowledge base, my learning pattern shows up more accurately.

Shelf-life check for Fleeting Notes
make check-expired detects Fleeting Notes that have been sitting untouched for more than 14 days.
check-expired: ## Check for expired fleeting notes (older than 2 weeks) @echo "Expired Fleeting Notes (>14 days old):" @for file in $(VAULT_PATH)/01_FleetingNotes/*.md; do \ if [ -f "$file" ]; then \ note_date=$(grep "^date:" "$file" | sed 's/date: *//; s/"//g' | head -1); \ if [ -n "$note_date" ]; then \ note_timestamp=$(date -d "$note_date" +%s 2>/dev/null); \ current_timestamp=$(date +%s); \ days_old=$(( ($current_timestamp - $note_timestamp) / 86400 )); \ if [ "$days_old" -gt 14 ]; then \ echo " $days_old days old: $(basename "$file")"; \ fi; \ fi; \ fi; \ doneFleeting Notes are “look into this later” memos. If two weeks pass and you still haven’t looked into them, you’ve probably lost interest. This gives you a prompt to either delete them or promote them to a Reference Note.
Why everything stays in the terminal
All of the analysis lives inside the terminal. I use gnuplot’s dumb terminal to produce ASCII graphs.
Why not a GUI tool? Simple: I spend most of my time in the terminal.
I can see my activity pattern with make heatmap without opening Obsidian. It works over SSH. It composes into scripts. That convenience matters.
Also, ASCII graphs just plain make me happy.
Complete Makefile
Here’s the full version, including the commands above plus link checking, weekly reports, and more.
Show the full Makefile
VAULT_PATH := vault
.PHONY: help check-links status day-graph time-graph heatmap tag-graph format
help: ## Show this help message @echo "Obsidian Knowledge Base Management" @echo "==================================" @echo "" @grep -E '^[a-zA-Z_-]+:.*?## .*$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $1, $2}'
# Link Managementcheck-links: ## Check for broken links using lychee lychee --offline $(VAULT_PATH)/
format: ## Format markdown files using pre-commit @echo -e "\033[1;34mFormatting markdown files with pre-commit...\033[0m" @pre-commit run markdownlint-cli2 --all-files || true @echo -e "\033[1;32mFormatting complete!\033[0m"
status: ## Show vault status @echo -e "\033[1;34mVault Statistics:\033[0m" @echo -e "\033[1;34m==================\033[0m" @echo -e "\033[1;33mTotal markdown files:\033[0m \033[1;36m$(find $(VAULT_PATH) -name "*.md" | wc -l)\033[0m" @echo "" @echo -e "\033[1;34mDirectory Breakdown:\033[0m" @for dir in $(find $(VAULT_PATH) -maxdepth 1 -type d ! -path $(VAULT_PATH) ! -name ".obsidian" | sort); do \ basename=$(basename "$dir"); \ count=$(find "$dir" -name "*.md" 2>/dev/null | wc -l); \ if [ "$count" -gt 0 ]; then \ printf "\033[32m%-25s:\033[0m %d\n" "$basename" "$count"; \ fi; \ done @echo "" @echo -e "\033[1;34mAssets & Other Files:\033[0m" @for dir in $(find $(VAULT_PATH) -maxdepth 1 -type d ! -path $(VAULT_PATH) ! -name ".obsidian" | sort); do \ basename=$(basename "$dir"); \ count=$(find "$dir" -type f ! -name "*.md" 2>/dev/null | wc -l); \ if [ "$count" -gt 0 ]; then \ printf "\033[32m%-25s:\033[0m %d non-md files\n" "$basename" "$count"; \ fi; \ done @echo "" @echo -e "\033[1;35mRecent files (newest by YAML date):\033[0m" @find $(VAULT_PATH) -name "*.md" -type f ! -path "$(VAULT_PATH)/99_Templates/*" | \ xargs -P 8 -I {} sh -c 'date=$(grep "^date:" "{}" 2>/dev/null | head -1 | sed "s/date: *//; s/\"//g"); [ -n "$date" ] && echo "$date|{}"' | \ sort -t'|' -k1,1r | head -5 | while IFS='|' read date file; do \ echo " $(basename "$file") ($date)"; \ done @echo "" @echo -e "\033[1;35mOldest files (by YAML date):\033[0m" @find $(VAULT_PATH) -name "*.md" -type f ! -path "$(VAULT_PATH)/99_Templates/*" | \ xargs -P 8 -I {} sh -c 'date=$(grep "^date:" "{}" 2>/dev/null | head -1 | sed "s/date: *//; s/\"//g"); [ -n "$date" ] && echo "$date|{}"' | \ sort -t'|' -k1,1 | head -5 | while IFS='|' read date file; do \ echo " $(basename "$file") ($date)"; \ done
growth-graph: ## Show markdown file count growth over time (ASCII graph) @echo "Generating vault growth chart..." >&2 @git log --pretty=format:"%H %ad" --date=short --reverse | \ xargs -P 8 -I {} sh -c 'echo $(echo "{}" | cut -d" " -f2-),$(git ls-tree -r $(echo "{}" | cut -d" " -f1) --name-only | grep -c "\.md$")' | \ gnuplot -e "set terminal dumb 80 24; set datafile separator ','; set xdata time; set timefmt '%Y-%m-%d'; set format x '%m/%d'; set xlabel 'Date'; set ylabel 'Files'; set title 'Vault Growth Over Time'; plot '/dev/stdin' using 1:2 with lines notitle"
tag-graph: ## Meaningful tag usage with ASCII bar chart @echo "Meaningful Tag Usage Frequency:" @echo "===============================" @find $(VAULT_PATH) -name "*.md" -type f -exec grep -A 20 "^tags:" {} \; 2>/dev/null | grep "^ - " | sed 's/^ - //' | \ grep -v -E '^(reference_note|permanent_note|fleeting_note|cheat_sheet|moc|idea)$' | \ sort | uniq -c | sort -nr | head -15 | \ awk '{count=$1; tag=$2; bar=""; for(i=0;i<int(count*40/50);i++) bar=bar"█"; printf "%-25s %3d %s\n", tag, count, bar}'
day-graph: ## Gnuplot bar chart of commits by day of week @echo "Generating day-of-week activity chart..." >&2 @git log --pretty=format:"%ad" --date=format:"%w" | sort | uniq -c | \ awk '{days[$2]=$1} END {for(i=0;i<=6;i++) printf "%d %s %d\n", i, (i==0?"Sun":i==1?"Mon":i==2?"Tue":i==3?"Wed":i==4?"Thu":i==5?"Fri":"Sat"), (days[i]?days[i]:0)}' | \ gnuplot -e "set terminal dumb 80 20; set style data histogram; set style histogram cluster gap 1; set style fill solid border -1; set boxwidth 0.9; set xtics rotate by -45; set ylabel 'Commits'; set title 'Commits by Day of Week'; plot '/dev/stdin' using 3:xtic(2) notitle"
time-graph: ## Gnuplot line chart of commits by hour @echo "Generating hourly activity chart..." >&2 @git log --pretty=format:"%ad" --date=format:"%H" | sort | uniq -c | \ awk '{hours[$2]=$1} END {for(i=0;i<=23;i++) printf "%d %d\n", i, (hours[sprintf("%02d",i)]?hours[sprintf("%02d",i)]:0)}' | \ gnuplot -e "set terminal dumb 80 20; set xlabel 'Hour of Day (0-23)'; set ylabel 'Commits'; set title 'Commit Activity Throughout the Day'; set xrange [0:23]; set grid; plot '/dev/stdin' using 1:2 with linespoints notitle"
heatmap: ## Activity heatmap showing day of week vs hour @echo "Generating activity heatmap..." >&2 @git log --pretty=format:"%ad" --date=format:"%w %H" | \ awk '{day_hour[$1 " " $2]++; day_count[$1]++; if(day_hour[$1 " " $2] > max) max = day_hour[$1 " " $2]} END { \ days[0]="Sun"; days[1]="Mon"; days[2]="Tue"; days[3]="Wed"; days[4]="Thu"; days[5]="Fri"; days[6]="Sat"; \ print "Activity Heatmap: Commits by Day and Hour"; \ print ""; \ printf " 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23\n"; \ printf " ────────────────────────────────────────────────────────────────────────\n"; \ for(d=0;d<=6;d++) { \ printf "%s: ", days[d]; \ for(h=0;h<24;h++) { \ hour = sprintf("%02d", h); \ count = (day_hour[d " " hour]?day_hour[d " " hour]:0); \ if(count == 0) printf " . "; \ else if(count <= max/4) printf " ░ "; \ else if(count <= max/2) printf " ▒ "; \ else if(count <= max*3/4) printf " ▓ "; \ else printf " █ "; \ } \ total = (day_count[d]?day_count[d]:0); \ printf " (%3d)\n", total; \ } \ print " ────────────────────────────────────────────────────────────────────────"; \ print ""; \ print "Legend: . = no activity, ░ = low, ▒ = medium, ▓ = high, █ = very high"; \ }'
week-graph: ## Weekly activity graph (last 7 days) @echo "Weekly Activity (Last 7 Days)" @echo "=============================" @for i in 6 5 4 3 2 1 0; do \ date=$(date -d "$i days ago" "+%Y-%m-%d"); \ day=$(date -d "$i days ago" "+%a"); \ count=$(git log --since="$date 00:00:00" --until="$date 23:59:59" --pretty=format:"%H" 2>/dev/null | wc -l); \ bar=""; \ for j in $(seq 1 $((count*2))); do bar="$bar█"; done; \ printf "%s %-10s %3d %s\n" "$day" "$date" "$count" "$bar"; \ done @echo "" @echo "Total commits this week: $(git log --since="7 days ago" --pretty=format:"%H" | wc -l)"
recent-notes: ## Recently modified notes (last 7 days) @echo "Recently Modified Notes (Last 7 Days)" @echo "=====================================" @echo "" @echo -e "\033[1;33mNew Notes:\033[0m" @git log --diff-filter=A --name-only --since="7 days ago" -- "$(VAULT_PATH)/*.md" | grep "\.md$" | xargs -I{} basename {} 2>/dev/null | head -10 | sed 's/^/ • /' @echo "" @echo -e "\033[1;34mUpdated Notes:\033[0m" @git log --since="7 days ago" --name-only --pretty=format: -- "$(VAULT_PATH)/*.md" | grep "\.md$" | sort -u | while read file; do \ if [ -f "$file" ] && [ "$(git log --diff-filter=A --since="7 days ago" -- "$file" | wc -l)" -eq 0 ]; then \ echo " • $(basename "$file") - $(git log -1 --pretty=format:"%ar" -- "$file")"; \ fi; \ done | head -15 @echo "" @echo -e "\033[1;32mSummary:\033[0m" @echo " Total files modified: $(find $(VAULT_PATH) -name "*.md" -type f -mtime -7 | wc -l)" @echo " Most active folder: $(find $(VAULT_PATH) -name "*.md" -type f -mtime -7 -exec dirname {} \; | sort | uniq -c | sort -rn | head -1 | awk '{print $2}')"
check-expired: ## Check for expired fleeting notes (older than 2 weeks) @echo -e "\033[1;31mExpired Fleeting Notes (>14 days old):\033[0m" @echo "======================================" @count=0; \ for file in $(VAULT_PATH)/01_FleetingNotes/*.md; do \ if [ -f "$file" ]; then \ note_date=$(grep "^date:" "$file" | sed 's/date: *//; s/"//g' | head -1); \ if [ -n "$note_date" ]; then \ note_timestamp=$(date -d "$note_date" +%s 2>/dev/null); \ if [ -n "$note_timestamp" ]; then \ current_timestamp=$(date +%s); \ days_old=$(( ($current_timestamp - $note_timestamp) / 86400 )); \ if [ "$days_old" -gt 14 ]; then \ echo -e " \033[33m$days_old days old\033[0m: $(basename "$file") (created: $note_date)"; \ count=$((count + 1)); \ fi; \ fi; \ fi; \ fi; \ done; \ if [ "$count" -eq 0 ]; then \ echo " No expired fleeting notes found."; \ else \ echo ""; \ echo -e "\033[1;33mTotal expired notes: $count\033[0m"; \ echo "Run 'make clean-expired' to archive these notes"; \ fi
weekly-tags: ## Tag usage trends for the week @echo "Weekly Tag Usage Trends" @echo "=======================" @echo "" @echo "Tags used in notes modified this week:" @find $(VAULT_PATH) -name "*.md" -type f -mtime -7 -exec grep -A 20 "^tags:" {} \; 2>/dev/null | grep "^ - " | sed 's/^ - //' | \ grep -v -E '^(reference_note|permanent_note|fleeting_note|cheat_sheet|moc|idea)$' | \ sort | uniq -c | sort -nr | head -10 | \ awk '{count=$1; tag=$2; bar=""; for(i=0;i<int(count);i++) bar=bar"▓"; printf "%-25s %2d %s\n", tag, count, bar}' @echo "" @echo "New tags this week (not in older notes):" @comm -13 <(find $(VAULT_PATH) -name "*.md" -type f ! -mtime -7 -exec grep -A 20 "^tags:" {} \; 2>/dev/null | grep "^ - " | sed 's/^ - //' | sort -u) \ <(find $(VAULT_PATH) -name "*.md" -type f -mtime -7 -exec grep -A 20 "^tags:" {} \; 2>/dev/null | grep "^ - " | sed 's/^ - //' | sort -u) | \ grep -v -E '^(reference_note|permanent_note|fleeting_note|cheat_sheet|moc|idea)$' | head -5 | sed 's/^/ • /'Wrap-up
make statusfor note count statisticsmake tag-graphto visualize tag skewmake heatmapfor the day-of-week × hour activity patternmake check-expiredto surface neglected Fleeting Notes
To repeat myself: none of this is strictly necessary. I just enjoy visualizing my own activity, so I built it.
If this is your kind of hobby too, feel free to use it as a reference.