Shape CLI stores all data in git-friendly formats. This guide explains the storage structure, file formats, and how to work with them directly.
.shape/
├── briefs/
│ ├── b-7f2a3b1.md # Brief markdown files
│ ├── b-8c3d2e1.md
│ └── index.jsonl # Auto-generated index (git-ignored)
├── tasks.jsonl # All tasks
├── config.toml # Project configuration
├── plugins/ # Local plugins
├── sync/ # Sync state (git-ignored)
│ ├── github.jsonl
│ └── linear.jsonl
└── .cache/ # SQLite cache (git-ignored)
└── shape.db
Briefs are stored as markdown files with YAML frontmatter.
---
id: b-7f2a3b1
title: User Authentication
status: in_progress
type: shapeup
appetite: 2-weeks
created: 2025-01-16T10:30:00Z
updated: 2025-01-16T14:00:00Z
---
# User Authentication
## Problem
Users currently have no way to...
## Solution
Implement OAuth-based authentication...| Field | Required | Description |
|---|---|---|
id |
Yes | Brief identifier (e.g., b-7f2a3b1) |
title |
Yes | Brief title |
status |
Yes | proposed, betting, in_progress, shipped, archived |
type |
Yes | Brief type (e.g., minimal, shapeup) |
created |
Yes | ISO 8601 timestamp |
updated |
No | ISO 8601 timestamp |
appetite |
No | Time budget (ShapeUp: 1-week, 2-weeks, 6-weeks) |
Brief IDs are derived from a BLAKE3 hash of the title and creation timestamp:
b-{blake3(title + created)[0:7]}
This ensures:
- Deterministic IDs
- Low collision probability
- Short, readable identifiers
Tasks are stored in tasks.jsonl — one JSON object per line.
{"id":"b-7f2a3b1.1","brief_id":"b-7f2a3b1","title":"Research OAuth providers","status":"done","created":"2025-01-16T10:35:00Z","dependencies":[],"notes":[],"links":[]}
{"id":"b-7f2a3b1.2","brief_id":"b-7f2a3b1","title":"Implement OAuth flow","status":"in_progress","created":"2025-01-16T10:36:00Z","dependencies":[{"id":"b-7f2a3b1.1","type":"blocks"}],"notes":["Found edge case"],"links":[{"type":"commit","value":"abc1234"}]}| Field | Type | Description |
|---|---|---|
id |
string | Task identifier (e.g., b-7f2a3b1.1) |
brief_id |
string | Parent brief ID (null for standalone) |
title |
string | Task title |
status |
string | todo, in_progress, done |
created |
string | ISO 8601 timestamp |
updated |
string | ISO 8601 timestamp |
dependencies |
array | List of dependency objects |
claimed_by |
string | Agent name (if claimed) |
blocked_reason |
string | Explicit block reason |
notes |
array | List of note strings |
links |
array | List of link objects |
history |
array | List of history events |
{
"id": "b-7f2a3b1.1",
"type": "blocks"
}Types: blocks, from, related, duplicates
{
"type": "commit",
"value": "abc1234"
}Types: commit, pr, file, url
- Git-friendly — Line-based diffs
- Append-only friendly — New tasks add lines, don't modify existing
- Streaming — Can process without loading entire file
- Conflict-resolvable — Each line is independent
Project configuration in config.toml:
[project]
name = "my-project"
default_brief_type = "minimal"
[daemon]
enabled = true
sync_interval = 300 # seconds
[plugins.sync.github]
repo = "owner/repo"
[compact]
default_days = 7The brief index in briefs/index.jsonl is auto-generated for fast queries:
{"id":"b-7f2a3b1","title":"User Authentication","status":"in_progress","type":"shapeup","created":"2025-01-16T10:30:00Z"}
{"id":"b-8c3d2e1","title":"Search Redesign","status":"proposed","type":"minimal","created":"2025-01-16T11:00:00Z"}This file is:
- Git-ignored (regenerated from markdown files)
- Rebuilt on demand when stale
- Used for fast listing without parsing all markdown
The .cache/shape.db SQLite database provides:
- Full-text search across briefs and tasks
- Fast queries without scanning JSONL
- Temporary data (not committed to git)
shape cache build # Rebuild from source files
shape cache clear # Delete cache
shape cache analyze # Show cache statsThe sync/ directory stores ID mappings for external tools:
{"local":"b-7f2a3b1","remote":"123","plugin":"github","synced":"2025-01-16T10:30:00Z"}
{"local":"b-7f2a3b1.1","remote":"124","plugin":"github","synced":"2025-01-16T10:31:00Z"}This is git-ignored because:
- Remote IDs are environment-specific
- Different team members may have different permissions
- Sync state can be regenerated
Shape includes a custom git merge driver for tasks.jsonl conflicts.
shape merge-setupThis adds to .gitattributes:
.shape/tasks.jsonl merge=shape-tasks
And configures the merge driver in .git/config.
When conflicts occur, the merge driver:
- Parses both versions
- Identifies conflicting tasks by ID
- Uses last-write-wins based on
updatedtimestamp - Preserves all unique tasks from both branches
If the merge driver can't resolve automatically:
- The file is left with conflict markers
- Manually edit to resolve
- Run
shape cache buildto rebuild index
Shape uses file locking (via fs2) for concurrent access:
- Prevents corruption when multiple processes write
- Short-lived locks (released immediately after write)
- Graceful fallback if locking unavailable
Everything important is in git:
git add .shape/
git commit -m "Backup shape data"If files are corrupted:
# Restore from git
git checkout HEAD -- .shape/
# Rebuild cache
shape cache buildIf the brief index is corrupted:
# Delete and let Shape rebuild
rm .shape/briefs/index.jsonl
shape brief list # Triggers rebuildYou can edit files directly:
Edit markdown files in any editor:
vim .shape/briefs/b-7f2a3b1.mdThen rebuild index:
shape cache buildEditing tasks.jsonl directly is possible but not recommended. Use CLI commands instead.
If you must edit:
- Back up the file
- Edit carefully (each line must be valid JSON)
- Run
shape cache build
The .shape/ directory is fully portable:
- Copy to another machine
- Check into any git repository
- No external dependencies for data
Only .cache/ and sync/ are environment-specific and git-ignored.