Database
kasl uses SQLite as its local database for storing work sessions, tasks, and configuration data.
Overview
Section titled “Overview”The database provides:
- Local Storage: All data stored locally for privacy
- Migration System: Schema changes are applied automatically, in order, on every startup
- Cross-Platform: Works on all supported platforms
Database Location
Section titled “Database Location”Database files are stored in platform-specific locations:
- Windows:
%LOCALAPPDATA%\lacodda\kasl\kasl.db - macOS:
~/Library/Application Support/lacodda/kasl/kasl.db - Linux:
~/.local/share/lacodda/kasl/kasl.db
On every open, kasl runs PRAGMA foreign_keys = ON and applies any pending migrations.
Schema Overview
Section titled “Schema Overview”Tables
Section titled “Tables”workdays
Section titled “workdays”Stores daily work session information:
CREATE TABLE workdays ( id INTEGER PRIMARY KEY, date DATE NOT NULL UNIQUE, start TIMESTAMP NOT NULL, end TIMESTAMP, notes TEXT);pauses
Section titled “pauses”Stores break periods during work sessions:
CREATE TABLE pauses ( id INTEGER NOT NULL PRIMARY KEY, start TIMESTAMP NOT NULL, end TIMESTAMP, duration INTEGER, protected INTEGER NOT NULL DEFAULT 0, reason TEXT);protected marks a pause entered by hand with kasl pauses add --keep. Protected pauses are exempt from both cleanup filters applied to detected pauses: the minimum-duration threshold and merging with an adjacent pause. reason is the optional note passed via --reason. See pauses for the filtering and productivity rules.
Stores task information and metadata:
CREATE TABLE tasks ( id INTEGER NOT NULL PRIMARY KEY, task_id INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, name TEXT NOT NULL, comment TEXT, completeness INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 100, excluded_from_search BOOLEAN NOT NULL ON CONFLICT REPLACE DEFAULT FALSE, deleted_at TIMESTAMP, jira_key TEXT);task_idgroups a task with its own history across days - it points at the id of the first task in the chain, sotask findcan offer yesterday’s unfinished work and see today’s progress as the same item. It is not an external reference.jira_keyis the Jira issue the task was taken from, set bykasl inbox take. TheUPDATEbehindkasl task editdoes not list this column, so renaming a task cannot detach it from its issue.deleted_atwas added for soft delete, but nothing in the current codebase sets or reads it -kasl task removedeletes rows outright. Treat the column as reserved.
Stores task categorization tags:
CREATE TABLE tags ( id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, color TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);task_tags
Section titled “task_tags”Links tasks to tags (many-to-many relationship):
CREATE TABLE task_tags ( task_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, PRIMARY KEY (task_id, tag_id), FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE, FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE);task_templates
Section titled “task_templates”Stores reusable task templates:
CREATE TABLE task_templates ( id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, task_name TEXT NOT NULL, comment TEXT, completeness INTEGER DEFAULT 100, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);jira_inbox
Section titled “jira_inbox”Stores Jira issues assigned to you, synced by the background watcher. See inbox for the command that reads and manages this table:
CREATE TABLE jira_inbox ( issue_key TEXT PRIMARY KEY NOT NULL, issue_id TEXT NOT NULL, summary TEXT NOT NULL, priority TEXT, priority_rank INTEGER NOT NULL DEFAULT 999, url TEXT NOT NULL, first_seen TIMESTAMP NOT NULL, last_seen TIMESTAMP NOT NULL, notified INTEGER NOT NULL DEFAULT 0, pinned INTEGER NOT NULL DEFAULT 0, dismissed INTEGER NOT NULL DEFAULT 0, raw_updated TEXT, status_id TEXT, sort_value REAL, gone_at TIMESTAMP, last_change TEXT, changed_at TIMESTAMP, taken_at TIMESTAMP);status_idreferencesjira_statuses.idand resolves to a display name via join.sort_valueis the numeric value of a configured Jira custom field (e.g. Scoring), used to rank issues.gone_atis stamped when an issue stops appearing in the Jira poll (closed or reassigned); it clears if the issue reappears. Rows withgone_atset are hidden from the default list and only shown withkasl inbox --all.last_change/changed_atrecord the most recent visible change (status, priority, or score) so the list can badge it.taken_atis stamped bykasl inbox take. Unlikedismissed, it keeps the row in the list: a taken issue is still part of the picture, it is just already in hand.pinnedanddismissedare set bykasl inbox pin/kasl inbox dismiss.
jira_statuses
Section titled “jira_statuses”Local catalog of Jira workflow statuses, populated from issue sync so jira_inbox.status_id can resolve to a name:
CREATE TABLE jira_statuses ( id TEXT PRIMARY KEY NOT NULL, name TEXT NOT NULL);migrations
Section titled “migrations”Tracks database schema version:
CREATE TABLE migrations ( id INTEGER PRIMARY KEY, version INTEGER NOT NULL UNIQUE, name TEXT NOT NULL, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);Data Types
Section titled “Data Types”Timestamps
Section titled “Timestamps”- Format: ISO 8601 (
YYYY-MM-DD HH:MM:SS) - Timezone: Local system time
- Storage: SQLite
TIMESTAMPaffinity (stored as text)
- Format: ISO 8601 (
YYYY-MM-DD)
Durations
Section titled “Durations”- Unit: Seconds
- Storage: INTEGER for efficient calculations
Booleans
Section titled “Booleans”- Storage: INTEGER (0 = false, 1 = true)
- SQLite standard: No native boolean type
Migration System
Section titled “Migration System”Migrations run automatically whenever kasl opens the database - there is no separate command to trigger them:
kasl watch # opening the database applies any pending migrationsSchema history, in order:
create_tables_and_indices- basetasks,pauses,workdaystables and their indicesadd_task_templates-task_templatesadd_tags_system-tags,task_tagsadd_soft_delete-deleted_atcolumn and index ontasksadd_workday_notes-notescolumn onworkdaysadd_breaks_table- manual breaks table (later folded away, see migration 11)add_jira_inbox_table-jira_inboxjira_inbox_status_id_and_sort_value-jira_statuses,status_id/sort_valueonjira_inboxclear_jira_inbox_legacy_status_text- clears the legacystatustext columndrop_jira_inbox_legacy_status_column- drops itfold_breaks_into_protected_pauses- addsprotected/reasontopauses, migrates rows out ofbreaksas protected pauses, dropsbreaksjira_inbox_gone_and_change_tracking- addsgone_at,last_change,changed_attojira_inboxlink_taken_issues_to_their_tasks- addsjira_keyand its index totasks, andtaken_attojira_inbox
Each migration runs inside a transaction; a failure rolls back that migration.
Inspecting migrations
Section titled “Inspecting migrations”Debug builds only expose a migrations subcommand for inspection:
kasl migrations status # current version, pending or up to datekasl migrations history # applied migrations with timestampsThis command does not exist in release builds - it is compiled out (#[cfg(debug_assertions)]). Do not point end users at it; on a release install, use direct SQL against the migrations table instead if you need to check the version:
sqlite3 kasl.db "SELECT * FROM migrations ORDER BY version;"Data Management
Section titled “Data Management”Backup
Section titled “Backup”Create database backups:
# Copy database filecp ~/.local/share/lacodda/kasl/kasl.db kasl_backup.db
# Export datakasl export all --format json --output backup.jsonRestore
Section titled “Restore”Restore from backup:
# Replace database filecp kasl_backup.db ~/.local/share/lacodda/kasl/kasl.dbThere is no kasl import command. A JSON export from kasl export all is for reading or archiving outside kasl, not for reloading back into the database - restoring means replacing the .db file itself.
Cleanup
Section titled “Cleanup”Remove old data:
# Remove specific taskskasl task remove 1 2 3
# Remove all today's taskskasl task remove --today
# Delete old pauses (manual SQL)sqlite3 kasl.db "DELETE FROM pauses WHERE start < date('now', '-30 days');"Indexes
Section titled “Indexes”-- Workdays tableCREATE INDEX idx_workdays_date ON workdays(date);
-- Tasks tableCREATE INDEX idx_tasks_timestamp ON tasks(timestamp);CREATE INDEX idx_tasks_task_id ON tasks(task_id);CREATE INDEX idx_tasks_deleted_at ON tasks(deleted_at);CREATE INDEX idx_tasks_jira_key ON tasks(jira_key);
-- Pauses tableCREATE INDEX idx_pauses_start ON pauses(start);
-- Jira inbox tableCREATE INDEX idx_jira_inbox_active ON jira_inbox(dismissed, pinned DESC, priority_rank ASC, last_seen DESC);CREATE INDEX idx_jira_inbox_sort ON jira_inbox(dismissed, pinned DESC, sort_value DESC, priority_rank ASC);Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”Problem: Database locked
# Check for running processesps aux | grep kasl
# Stop all kasl processeskasl watch --stop
# Check file permissionsls -la ~/.local/share/lacodda/kasl/kasl.dbProblem: Corrupted database
# Check database integritysqlite3 kasl.db "PRAGMA integrity_check;"
# Recover if possiblesqlite3 kasl.db ".recover" | sqlite3 kasl_recovered.db
# Restore from backupcp kasl_backup.db kasl.dbDebug Database
Section titled “Debug Database”# Show SQL queriesRUST_LOG=kasl=debug kasl report
# Direct database accesssqlite3 ~/.local/share/lacodda/kasl/kasl.db
# Common queriesSELECT * FROM workdays ORDER BY date DESC LIMIT 5;SELECT * FROM tasks WHERE date(timestamp) = date('now');SELECT COUNT(*) FROM pauses WHERE date(start) = date('now');Advanced Usage
Section titled “Advanced Usage”Direct SQL Access
Section titled “Direct SQL Access”Access database directly:
sqlite3 ~/.local/share/lacodda/kasl/kasl.dbCommon queries:
-- Today's work sessionSELECT * FROM workdays WHERE date = date('now');
-- Today's tasksSELECT * FROM tasks WHERE date(timestamp) = date('now');
-- Today's pausesSELECT * FROM pauses WHERE date(start) = date('now');
-- Task completion statisticsSELECT COUNT(*) as total_tasks, SUM(CASE WHEN completeness = 100 THEN 1 ELSE 0 END) as completed, AVG(completeness) as avg_completionFROM tasksWHERE date(timestamp) = date('now');Data Export
Section titled “Data Export”Export specific data:
# Export workdayssqlite3 kasl.db "SELECT * FROM workdays;" > workdays.csv
# Export tasks with tagssqlite3 kasl.db "SELECT t.name, t.completeness, GROUP_CONCAT(tag.name) as tagsFROM tasks tLEFT JOIN task_tags tt ON t.id = tt.task_idLEFT JOIN tags tag ON tt.tag_id = tag.idGROUP BY t.idORDER BY t.timestamp DESC;" > tasks_with_tags.csvCustom Queries
Section titled “Custom Queries”Create custom reports:
-- Weekly summarySELECT date, COUNT(*) as tasks, AVG(completeness) as avg_completionFROM tasksWHERE date(timestamp) >= date('now', '-7 days')GROUP BY dateORDER BY date;
-- Tag usage statisticsSELECT tag.name, COUNT(*) as usage_countFROM tags tagJOIN task_tags tt ON tag.id = tt.tag_idGROUP BY tag.idORDER BY usage_count DESC;Related pages
Section titled “Related pages”pauses- protected pauses and the--keep/--reasonflags behind thepausescolumnstask- task commands, includingremoveinbox- the Jira inbox commands backed byjira_inboxandjira_statusesexport- export formats and data types, includingexport all- Configuration - config keys referenced by pauses, reports, and Jira sync