Maintainer Guide¶
Solution Design¶
This section explains the core design philosophy and key decisions behind
shai_tix.
Design Philosophy¶
shai_tix balances two competing requirements:
Human-friendly - Files and directories that humans can browse, read, and edit directly using any text editor or file manager
Machine-friendly - Fast querying and reliable ID generation for AI agents and programmatic access
The solution: Filesystem as source of truth + SQLite as index/cache.
This hybrid approach provides:
Git-friendly storage (trackable, mergeable, diffable)
Natural markdown editing for humans
Fast queries without full filesystem scans
Resilience: if SQLite corrupts, rebuild from filesystem
Key Design Decisions¶
Why not just SQLite?
Humans cannot easily browse or edit SQLite databases
Markdown files are git-friendly with clear diffs
AI agents can read and write markdown naturally
Version control integration is seamless
Why not just filesystem?
Scanning directories is slow for large projects
No efficient way to query by status or date range
ID generation would require scanning all directories
Search operations become O(n) instead of O(1)
Why global ID space?
Stories and tasks share the same monotonically increasing ID sequence:
Simpler implementation with no ID collisions
IDs are unique across all entities
Unambiguous references: “task 42” cannot be confused with “story 42”
Natural chronological ordering
Why two-level hierarchy only?
Story (Epic-level work item)
└── Task (Atomic work unit)
Story: A feature or large work item containing multiple tasks
Task: An atomic unit of work that can be completed independently
Tasks cannot be nested. If a task needs subtasks, promote it to a story.
This constraint keeps the system simple while covering most real-world workflows. Deep nesting rarely adds value and complicates navigation.
Source Code Architecture¶
This section describes the internal architecture of shai_tix, including
module relationships, storage mechanisms, and the high-level API design.
Module Overview¶
The core functionality is split between two modules:
shai_tix.dbDefines SQLAlchemy ORM models (
Story,Task) that serve dual purposes:Database persistence - Maps to SQLite tables for fast querying
Domain objects - Provides file I/O methods for metadata, description, and report files
Key classes:
Base- SQLAlchemy declarative baseStoryOrTask- Abstract base class with common fields (id,date,title,path) and file operationsStory- Story entity withtasksrelationshipTask- Task entity withstory_idforeign key
shai_tix.tixThe main API class
Tixthat orchestrates all operations. It manages:Filesystem scanning (
iter_stories,iter_tasks)SQLite index database (
rebuild_index_db,ensure_index_db)CRUD operations for stories and tasks
Search functionality
Module Dependency:
tix.py ──imports──> db.py ──imports──> utils.py
│
└──> constants.py
Dual Storage Architecture¶
shai_tix uses a dual storage approach:
- Filesystem (Source of Truth)
Human-readable directory structure
Git-friendly (trackable, mergeable, diffable)
Contains
metadata.json,description.md,report.mdfilesStories and tasks are folders with naming pattern:
{type}-{date}-{id}-{title}
- SQLite Index (Cache Layer)
Fast querying by ID, date range, or title
Rebuilt from filesystem when needed
Located at
{dir_root}/index.sqlite
The filesystem is authoritative. If the SQLite index becomes corrupted or out of sync, it can be rebuilt by scanning the filesystem:
tix.rebuild_index_db()
Session Context Manager¶
The session() context manager ensures the SQLite index
is synchronized before performing queries:
tix = Tix(dir_root=Path(".tix"))
with tix.session():
# Index is rebuilt on entry
stories = tix.query_stories()
tasks = tix.query_tasks()
What happens on entry:
Calls
rebuild_index_db()to scan filesystemDrops and recreates SQLite tables
Populates tables with current filesystem state
When to use:
Use
session()for batch read operations where fresh data is neededCRUD methods (
create_*,update_*,delete_*) manage their own database updatesThe
get_*methods will auto-rebuild if entity not found on first attempt
Directory Structure¶
A typical .tix directory structure:
.tix/
├── index.sqlite # SQLite index database
└── stories/
├── story-2025-01-15-00001-user-login/
│ ├── metadata.json # {"status": "IN_PROGRESS"}
│ ├── description.md # Story description
│ ├── report.md # Completion report (optional)
│ └── tasks/
│ ├── task-2025-01-15-00002-create-login-form/
│ │ ├── metadata.json
│ │ ├── description.md
│ │ └── report.md
│ └── task-2025-01-16-00003-add-validation/
│ ├── metadata.json
│ └── description.md
└── story-2025-01-20-00004-payment-integration/
├── metadata.json
├── description.md
└── tasks/
└── ...
Naming Convention:
Format:
{type}-{date}-{id}-{sanitized_title}Type:
storyortaskDate:
YYYY-MM-DD(creation date in UTC)ID: 5-digit zero-padded global ID (e.g.,
00001)Title: Hyphen-separated alphanumeric characters
High-Level API¶
The Tix class provides high-level CRUD and search methods
for both stories and tasks:
Story Operations:
Method |
Description |
|---|---|
|
Create new story with auto-generated ID |
|
Get story by ID (rebuilds index if not found) |
|
Update story metadata and content files |
|
Delete story and all its tasks |
|
Search stories with filters, sorted by ID descending |
Task Operations:
Method |
Description |
|---|---|
|
Create new task under a story |
|
Get task by ID (rebuilds index if not found) |
|
Update task metadata and content files |
|
Delete task from filesystem and database |
|
Search tasks with filters, sorted by ID descending |
Query Operations:
query_stories()- Get all stories from indexquery_tasks()- Get all tasks from indexquery_story(id)- Get single story by IDquery_task(id)- Get single task by IDquery_tasks_by_story(story_id)- Get tasks under a story
Usage Example:
from pathlib import Path
from shai_tix.tix import Tix
from shai_tix.constants import StatusEnum
# Initialize
tix = Tix(dir_root=Path(".tix"))
# Create a story with tasks
story = tix.create_story(
title="Implement User Authentication",
description="Add login/logout functionality"
)
task = tix.create_task(
story_id=story.id,
title="Create login form",
description="HTML form with email/password fields"
)
# Update status
tix.update_task(id=task.id, status=StatusEnum.COMPLETED)
# Search
results = tix.search_stories(title="auth")
for s in results:
print(f"[{s.id}] {s.title}")
CLI Interface (For AI Agents)¶
The shai-tix command-line interface is designed for AI agents (like Claude Code)
to interact with the task management system. It provides a simple, text-based
interface that AI can easily parse and use.
Installation:
After installing the package, the shai-tix command becomes available:
pip install shai-tix
Usage:
By default, all commands operate on the .tix directory in the current
working directory. Use --root to specify a different project root:
# Use current directory
shai-tix list_stories
# Use specific project root
shai-tix list_stories --root /path/to/project
Index Management¶
Command |
Description |
|---|---|
|
Rebuild SQLite index from filesystem |
When to use rebuild_index_db:
Before running multiple query commands in batch
After external changes to the
.tixdirectory (e.g., git pull)When query results seem stale or incorrect
Query commands (list_*, search_*, get_*) use ensure_index_db()
which only creates the index if it doesn’t exist. For fresh data after
filesystem changes, call rebuild_index_db first:
# Rebuild index once, then run multiple queries
shai-tix rebuild_index_db
shai-tix list_stories
shai-tix list_tasks
shai-tix search_stories --title "auth"
Story Commands¶
Command |
Description |
|---|---|
|
List all stories (default limit: 20) |
|
Search stories by filters |
|
Create a new story |
|
Get story details by ID |
|
Update story fields |
|
Delete a story and all its tasks |
Task Commands¶
Command |
Description |
|---|---|
|
List all tasks (default limit: 20) |
|
List tasks under a specific story |
|
Search tasks by filters |
|
Create a new task under a story |
|
Get task details by ID |
|
Update task fields |
|
Delete a task |
Status Values¶
Valid status values for --status parameter:
TODO- Task is planned but not startedIN_PROGRESS- Task is currently being worked onCOMPLETED- Task is finishedBLOCKED- Task is blocked by external dependenciesCANCELED- Task has been canceled
Example Workflow¶
# Create a story
shai-tix create_story "Implement user authentication" --description "Add login/logout"
# Create tasks under the story
shai-tix create_task 1 "Create login form" --description "HTML form with validation"
shai-tix create_task 1 "Add session management"
# List tasks for the story
shai-tix list_tasks_by_story 1
# Update task status
shai-tix update_task 2 --status IN_PROGRESS
shai-tix update_task 2 --status COMPLETED --report "Login form implemented"
# Search for tasks
shai-tix search_tasks --title "login"