HomeDocumentation

grít — Full Usage Reference

This document is the complete reference for every command, flag, configuration option, and behavior in grít.


Table of Contents

  1. Installation
  2. Initialization
  3. Commit interview
  4. Pausing and disabling
  5. Real-time file watcher
  6. Viewing your log
  7. Recording decisions
  8. Revert post-mortems
  9. End-of-day reflection
  10. Statistics and analytics
  11. Exporting data
  12. Removing grít
  13. Configuration reference
  14. Database reference
  15. Git hooks reference
  16. Answer tagging
  17. How complexity is scored
  18. Windows notes

Installation

Prerequisites

RequirementNotes
Go 1.21+go version to check
GCCRequired for CGO / SQLite. See below.

GCC installation:

  • Windows — install via MSYS2:
    pacman -S mingw-w64-ucrt-x86_64-gcc
    
  • macOSxcode-select --install
  • Linuxsudo apt install build-essential (or distro equivalent)

Build from source

git clone https://github.com/alchemistreturns/grit
cd grit

# Windows — gcc must be on PATH
PATH="/c/msys64/ucrt64/bin:$PATH" go build -o grit.exe .

# macOS / Linux
go build -o grit .

Add the resulting binary to your PATH.

go install

# Windows
PATH="/c/msys64/ucrt64/bin:$PATH" go install github.com/alchemistreturns/grit@latest

# macOS / Linux
go install github.com/alchemistreturns/grit@latest

The first build takes 30–60 seconds. The SQLite C amalgamation is compiled once and then cached — subsequent builds are fast.


Initialization

cd your-project
grit init

grit init must be run inside a git repository. It creates:

PathDescription
.grit.yamlProject-level config (question pool, thresholds, watch settings)
.grit/store.dbPer-repository SQLite database
.git/hooks/pre-commitRuns grit commit before every commit
.git/hooks/post-rewriteDetects revert commits and triggers post-mortems
.git/hooks/post-commitRecords the commit SHA against the interview event

Idempotency: safe to run multiple times. If .grit.yaml already exists, it is left untouched. If a pre-commit hook already exists, the grít invocation is appended rather than replacing the file.


Commit interview

The commit interview runs automatically every time you git commit. You never need to invoke it manually.

How it works

  1. grít reads the staged diff to understand the nature of the change.
  2. It selects 2 questions from the configured pool, avoiding questions asked in the last N commits (the "window").
  3. Each question is displayed as an interactive prompt with a 30-second timeout.
  4. Answers are stored in .grit/store.db and optionally tagged.

Context-aware questions

grít analyzes the staged diff and injects a context-specific question when it detects:

ConditionInjected question
Diff > 200 lines"Should this commit be split into smaller pieces?"
New files added"What's the contract / responsibility of the new file(s)?"
Only test files changed"What edge cases are you not testing yet?"
Config files changed"What value would break this if set incorrectly?"

Commit types that are silently skipped

grít reads the commit message and skips the interview for:

  • Merge commits (Merge branch …, Merge pull request …)
  • Fixup / squash commits (fixup!, squash!)
  • WIP commits (message starts with WIP or wip:)
  • Amend commits (--amend reuses the message, grít detects this)

Revert commits

When grít detects a commit message starting with Revert "…", it skips the normal interview and instead runs the post-mortem interview (3 questions). See Revert post-mortems.

TTY / CI behavior

If there is no TTY available (CI, piped commands, SSH without terminal allocation), grít detects this, stores a skipped event, and exits 0. Your pipeline never breaks.

Exit code guarantee

grit commit always exits 0. A commit is never blocked by grít, even if grít itself panics.


Pausing and disabling

Sometimes you need to push rapidly without interruption. grít provides three commands to pause the friction interview without uninstalling hooks.

grit snooze [duration]

Pauses interviews for a fixed duration. Duration uses Go's duration syntax (h, m, s). Defaults to 1 hour when no argument is given.

grit snooze          # pause for 1 hour
grit snooze 30m      # pause for 30 minutes
grit snooze 2h       # pause for 2 hours
grit snooze 1h30m    # pause for 1.5 hours

grit disable

Pauses interviews indefinitely, until you explicitly resume.

grit disable

grit resume

Re-enables interviews, cancelling any active snooze or disable.

grit resume

How pausing works

Pause state is stored in .grit/pause:

  • Snoozed — the file contains an RFC 3339 timestamp; the pause clears automatically when that time passes.
  • Disabled — the file contains the string disabled; the pause persists until grit resume.
  • Active — the file is absent or the snooze timestamp has elapsed.

While paused, grit commit records a skipped event and exits 0 immediately. No questions appear and no commits are delayed.


Real-time file watcher

grit watch

Run this in a second terminal while you code. grít watches your project directory recursively and prompts you when it detects friction signals.

Watched file types

Controlled by watch.extensions in .grit.yaml (default: .go, .js, .ts, .py, .rs, .java, .c, .cpp).

Excluded directories

.git, node_modules, vendor are always excluded.

Debouncing

Each file path has a 200ms debounce. Burst saves (e.g. VSCode's rename-then-create pattern) are coalesced into a single analysis event.

Friction signals

Complexity threshold

When a file's complexity score exceeds thresholds.complexity (default 10.0), grít prints:

  • The file's current score and your running average for that file
  • A per-function breakdown showing which functions contribute most

No question is asked — this is informational.

Vague naming

When new lines (since the previous save) contain identifiers matching the weak-name list, grít shows a prompt:

Weak name detected: "handleData" in auth.go
→ What would a better name be?

The built-in list includes: result, temp, tmp, data, stuff, thing, value, item, obj, res, resp, req, and functions starting with handle, process, do, get, set, manage, run.

Additional per-language weak names can be configured in .grit.yaml under watch.language_names.

AI-assist prompt (15+ new lines)

When 15 or more new lines appear since the last save, grít waits 2 seconds and then asks:

→ Was any of this AI-assisted? What did you verify?

Paste comprehension prompt (30+ new lines)

When 30 or more new lines appear, grít asks:

→ Did you paste this? Do you fully understand what it does?

Undo spike prompt (20+ lines deleted)

When 20 or more lines are removed, grít asks:

→ Wrong turn or intentional cleanup?

Dead time prompt (40+ minutes in same file)

When grít detects multiple writes to the same file over a 40+ minute window without meaningful forward progress, it asks:

→ You've been in this file a while. Is the problem still clear?

All thresholds are configurable. See Configuration reference.


Viewing your log

grit log

Displays all friction events in reverse-chronological order, grouped by day. Each event shows:

  • Time
  • Event type (hook)
  • Commit message snippet (for commit-time events)
  • All questions and answers

Filters

# filter by event type
grit log --hook interview
grit log --hook file_complexity
grit log --hook naming
grit log --hook ai_reflect
grit log --hook paste
grit log --hook undo_spike
grit log --hook dead_time
grit log --hook decision
grit log --hook revert

# show events from a date onward
grit log --since 2025-06-01

# show only events where you skipped all questions
grit log --skipped

# look up the friction interview linked to a specific commit
grit log --commit abc1234
grit log --commit abc1234def5678   # full or partial hash — prefix match

# combine filters
grit log --hook interview --since 2025-06-01

--commit flag

After every successful git commit, grít's post-commit hook runs grit post-commit, which looks up the most recent interview event and stores the actual commit SHA in the database. This means you can later trace exactly what you were thinking when you made any given commit:

git log --oneline          # find the hash of the commit you're curious about
grit log --commit e3f9a2b  # replay the interview from that commit

Prefix matching is supported — you don't need the full 40-character SHA.

Event types

Hook valueWhen it's recorded
interviewCommit-time friction interview
file_complexityComplexity threshold exceeded
namingWeak identifier detected
ai_reflect15+ lines added
paste30+ lines added
undo_spike20+ lines deleted
dead_time40+ min inactivity in file
decisiongrit decision interview
revertRevert commit post-mortem

Recording decisions

grit decision

Runs a structured 4-question interview to record an architectural decision:

  1. Context — What situation or constraint forces this decision?
  2. Alternatives — What alternatives did you evaluate?
  3. Decision — What did you decide and why?
  4. Consequences — What do you give up? What could go wrong?

Decisions are stored as regular events in the database with hook type decision.

Listing decisions

grit decision list

Prints all recorded decisions with their dates and decision title (extracted from the decision answer).

Exporting to ADR markdown

grit decision export

Exports each decision as an ADR (Architecture Decision Record) markdown file:

decisions/
├── 2025-06-01-use-sqlite-for-local-storage.md
├── 2025-06-15-cobra-for-cli-framework.md
└── ...

Each file is formatted with sections:

  • Context
  • Options Considered
  • Decision
  • Consequences

Revert post-mortems

When you git revert a commit, the post-rewrite hook automatically triggers a 3-question post-mortem:

  1. What went wrong with <original commit message>?
  2. Was this caught in review or did it reach production?
  3. What would have caught this earlier?

grít extracts the original commit hash from the revert message and stores it in events.related_commit so you can trace the revert back to its source.

Manual invocation

You rarely need this, but you can also run:

grit revert --check

This is what the post-rewrite hook calls internally.


End-of-day reflection

grit reflect

Shows your daily stats and asks 2 questions from the reflection pool:

Stats shown:

  • Total friction events logged today
  • Completed interviews vs. skipped
  • Consecutive days streak (days with at least one completed interview)

Reflection questions (2 selected from the pool, avoiding recent repeats):

  • "What problem did you think you were solving this morning versus what you actually solved?"
  • "What would you tell a teammate who picks up this code tomorrow?"
  • "What would you have done differently if you had one more hour?"
  • "What's the most surprising thing you learned today?"
  • "What decision today might you regret in 6 months?"
  • "Where did your estimate go wrong today, and why?"

Deep reflection export

If deep_reflect.enabled: true in .grit.yaml, answers are also written to a dated markdown file:

.grit/reflections/
├── 2025-06-01.md
├── 2025-06-02.md
└── ...

Statistics and analytics

Weekly summary

grit stats week

Shows the past 7 days:

  • Total commits and interview completion rate
  • Consecutive days streak
  • Top friction tags with bar chart visualization
  • Most complex files touched (peak complexity scores)

File complexity trend

grit stats file path/to/file.go

Shows:

  • Sparkline of complexity scores over time for that file
  • All friction answers that mention the file

Contribution heatmap

grit stats heatmap

12-week GitHub-style heatmap of friction density:

     Jun        Jul        Aug
Mon  ░ ░ ▒ ▓ █ ░ ░ ▒ ▒ ░ ░ ░
Wed  ░ ▒ ▒ ▓ ▒ ░ ▒ ▓ █ ▒ ░ ░
Fri  ░ ░ ░ ▒ ░ ░ ░ ▒ ▒ ░ ░ ░
CharacterEvents/day
0
1–2
3–4
5+

Tagged digest

grit stats digest

Groups all friction answers by their [tag] prefix. Untagged answers appear under [general]. Useful for identifying recurring themes.


Exporting data

# export to Markdown
grit push --md

# export to JSON
grit push --json

# export a specific date range
grit push --md --since 2025-01-01

Output is written to .grit/exports/grit-friction-{period}.{format}.

Markdown export: organizes events by day with formatted question/answer pairs.

JSON export: array of event objects with ISO 8601 timestamps and nested answers.

Default range is the last month if --since is not specified.


Removing grít

grit remove

Unregisters grít from the repository without destroying your data.

CommandWhat it removes
grit removegrít entries from all three git hooks, .grit.yaml
grit remove --allEverything above plus the entire .grit/ directory (database, exports, reflections)

Details

  • Hooks are edited surgically — if you have other tools in the same hook file, their logic is preserved
  • If a hook file contained only the grít block, the hook file is deleted entirely
  • If grít was never installed (e.g. hook file missing), the command exits cleanly without error
  • --all is irreversible: the SQLite database and all exported/reflection files are deleted
# remove hooks and config, keep the database
grit remove

# wipe everything
grit remove --all

Configuration reference

grít looks for .grit.yaml in the current working directory. All sections and fields are optional — defaults apply when absent.

# .grit.yaml — full example with all options

questions:
  # The pool of questions grít draws from at commit time.
  # You can add, remove, or rewrite any of these.
  pool:
    - "What's the hardest part of this change?"
    - "What would you do differently next time?"
    - "What assumption are you most uncertain about?"
    - "What did you learn that surprised you?"
    - "What would break first if this code were wrong?"
    - "What did you have to look up or re-learn to write this?"
    - "What's the least obvious thing about this diff?"
    - "If you came back to this in 6 months, what would confuse you?"
    - "What did you almost do instead?"
    - "What corner case are you intentionally ignoring?"
    - "What would make this change unnecessary?"
    - "What conversation should this commit have started?"
  # Number of recent questions to avoid repeating. Set to 0 to disable rotation.
  window: 5

thresholds:
  # Complexity score (keyword count) that triggers a watcher report.
  complexity: 10.0
  # Number of new lines added that triggers the AI-assist prompt.
  ai_reflect_lines: 15
  # Minutes of inactivity in a file before the dead-time prompt.
  dead_time_minutes: 40
  # Number of lines deleted that triggers the undo-spike prompt.
  undo_spike_lines: 20
  # Number of new lines that triggers the paste-comprehension prompt.
  paste_lines: 30

watch:
  # File extensions the watcher pays attention to.
  extensions:
    - .go
    - .js
    - .ts
    - .py
    - .rs
    - .java
    - .c
    - .cpp
  # Additional weak names to flag per language.
  # Keys must match the language name grít derives from the file extension.
  language_names:
    go:
      - result
      - tmp
      - data
      - obj
    python:
      - data
      - stuff
      - res
      - val

export:
  # Directory where `grit push` writes export files.
  path: ".grit/exports"

deep_reflect:
  # If true, `grit reflect` writes answers to a dated markdown file.
  enabled: false
  # Directory for dated reflection markdown files.
  output_dir: ".grit/reflections"

Database reference

The SQLite database lives at .grit/store.db inside each repository. It is opened in WAL mode with a 5-second busy timeout so grit watch and grit commit can write simultaneously without contention.

events table

CREATE TABLE events (
    id             TEXT PRIMARY KEY,   -- 16-byte random hex ID
    hook           TEXT NOT NULL,      -- event type (see below)
    occurred_at    INTEGER NOT NULL,   -- Unix timestamp
    skipped        INTEGER DEFAULT 0,  -- 1 if all questions were skipped/timed out
    commit_msg     TEXT,               -- commit message (commit-time events only)
    commit_hash    TEXT,               -- full SHA set by the post-commit hook
    related_commit TEXT               -- for revert events: hash of the reverted commit
);

Hook values: interview, file_complexity, naming, ai_reflect, paste, undo_spike, dead_time, decision, revert

answers table

CREATE TABLE answers (
    id         TEXT PRIMARY KEY,
    event_id   TEXT NOT NULL REFERENCES events(id),
    question   TEXT NOT NULL,
    answer     TEXT NOT NULL,
    tag        TEXT DEFAULT ''   -- extracted from [tag] prefix in the answer text
);

complexity_history table

CREATE TABLE complexity_history (
    id          TEXT PRIMARY KEY,
    path        TEXT NOT NULL,
    score       REAL NOT NULL,
    recorded_at INTEGER NOT NULL
);

Querying the database directly

sqlite3 .grit/store.db

# all events from the last 7 days
SELECT hook, occurred_at, commit_msg
FROM events
WHERE occurred_at > strftime('%s', 'now', '-7 days')
ORDER BY occurred_at DESC;

# find the interview for a specific commit hash
SELECT e.hook, e.commit_msg, a.question, a.answer
FROM events e
JOIN answers a ON a.event_id = e.id
WHERE e.commit_hash LIKE 'abc1234%';

# tag frequency
SELECT tag, COUNT(*) as n
FROM answers
WHERE tag != ''
GROUP BY tag
ORDER BY n DESC;

# complexity trend for a file
SELECT score, recorded_at
FROM complexity_history
WHERE path LIKE '%auth.go'
ORDER BY recorded_at;

Git hooks reference

pre-commit

#!/bin/sh
if command -v grit > /dev/null 2>&1; then
    grit commit
fi
  • Runs before every git commit
  • If grit is not on PATH (e.g. a team member who hasn't installed it), the hook silently no-ops
  • Always exits 0 — commits are never blocked

post-rewrite

#!/bin/sh
if command -v grit > /dev/null 2>&1; then
    grit revert --check "$@"
fi
  • Runs after git commit --amend and git rebase
  • grít inspects the arguments to detect revert commits specifically
  • Silently no-ops for non-revert rewrites

post-commit

#!/bin/sh
if command -v grit > /dev/null 2>&1; then
    grit post-commit
fi
  • Runs immediately after every successful commit
  • Reads the commit SHA from git rev-parse HEAD and stores it on the most recent interview event
  • This is what makes grit log --commit <hash> work

Manually installing / repairing hooks

# re-run init — hooks are appended idempotently
grit init

To remove grít from your hooks cleanly, use:

grit remove          # remove hooks and .grit.yaml, keep the database
grit remove --all    # remove everything including the .grit folder

Answer tagging

Any answer can be prefixed with a [tag] to categorize the friction:

[debug]    spent 45 minutes on a nil pointer that a type assertion would have caught
[scope]    started with auth, ended up refactoring the whole session layer
[design]   not sure if this belongs in the handler or the service
[test]     no easy way to test this without a real database

Tags are extracted and stored in answers.tag. They surface in:

  • grit stats digest — grouped by tag
  • grit stats week — top tags bar chart
  • grit push --md / --json exports

You can use any tag name. No predefined taxonomy is enforced.


How complexity is scored

grít uses a keyword-counting heuristic that works across all supported languages without per-language parsers.

Algorithm:

  1. Start with a base score of 1
  2. For each non-comment line, add 1 for each occurrence of: if, else, for, switch, case, select, &&, ||, catch, while, do
  3. Comment lines (starting with //, #, *, --) are skipped entirely

Per-function breakdown (ScoreByFunction):

grít also parses function boundaries using a multi-language detection approach:

LanguageFunction detection
Gofunc prefix
Pythondef prefix
JavaScript / TypeScriptfunction , arrow functions
Rustfn prefix
Java / C#method signatures
C / C++{ after parameter list

When the watcher detects a file above the threshold, it reports per-function scores so you can pinpoint the offending function rather than the whole file.


Windows notes

  • GCC required — install MSYS2 and run pacman -S mingw-w64-ucrt-x86_64-gcc
  • PATH for build/install/c/msys64/ucrt64/bin must be on PATH during go build and go install
  • TTY in git hooks — bubbletea prompts use CONIN$ on Windows via tea.WithInputTTY(), which works correctly when invoked from a git hook inside Windows Terminal, Git Bash, and VS Code's integrated terminal
  • Binary name — build produces grit.exe; add the directory containing it to your Windows PATH or MSYS2 PATH

Typical daily workflow

# morning: start your watcher
cd your-project
grit watch          # keep this running in a second terminal

# during the day: work normally
# grít prompts appear automatically at commits and on friction signals

# end of day
grit reflect        # review stats and answer 2 reflection questions
grit stats week     # see your weekly friction picture
grit log --since $(date +%Y-%m-%d)  # review today's events

# when you make a significant architectural choice
grit decision

# export everything for a retrospective
grit push --md --since 2025-06-01