sq is a lightweight task-list CLI with structured sources.
It manages tasks in a JSONL file. You can use it directly from the shell or instruct agents to manage them for you.
If you're coming from Beads, see sq vs. Beads for a comparison of the two tools and the trade-offs sq makes in favour of simplicity.
brew install derekstride/tap/sift-queuecargo install sift-queueUsing a package manager above is recommended so you can easily get updates, but if that is for some reason problematic you can also manually install as follows:
- Download the latest version for your architecture.
- Copy the single
sqfile into your preferred directory (e.g.~/.bin) - Make sure that directory is in your
$PATH
You can install this repo as a plugin source to get the sq skills.
pi install https://github.com/DerekStride/sqclaude plugin marketplace add https://github.com/DerekStride/sq
claude plugin install sqNote
There's no queue! See the FAQ section to see the origin of the name.
By default, sq discovers the nearest existing .sift/issues.jsonl within the current git worktree and otherwise falls back to <cwd>/.sift/issues.jsonl. Outside git repositories, it also falls back to .sift/issues.jsonl in the current directory. You can override it with:
-q, --queue <PATH>SQ_QUEUE_PATH=<PATH>
sq add— create a single tasksq collect— create many tasks from piped stdinsq list— list taskssq show <id>— show task detailssq edit <id>— edit task fields/sourcessq close <id>— mark task as closedsq rm <id>— remove tasksq prime— outputsqworkflow context for AI agents
Use sq --help for a full list of options.
Dependencies are modeled with blocked_by. An item is considered ready when it is pending and none of its blocker IDs refer to another non-closed item.
Use these views intentionally:
sq list --ready— actionable work only (pendingand unblocked)sq list— default view; shows all non-closed items so blocked dependencies andin_progresswork remain visiblesq list --all— include closed items for history/auditing
When choosing the next task to start, prefer sq list --ready.
# Add task with title, description, priority, and pasted source text
sq add --title "Investigate checkout exception" \
--description "Review the pasted error report and identify the failing code path" \
--priority 1 \
--text "Sentry alert: NoMethodError in Checkout::ApplyDiscount at app/services/checkout/apply_discount.rb:42"
# Add source-less task
sq add --title "Refactor parser" --description "Split command logic"
# Add task with metadata
sq add --title "Triage follow-up" --description "Review support escalation" \
--metadata '{"pi_tasks":{"escalation":"support"}}'
# Collect one task per file from ripgrep JSON
rg --json -n -C2 'OldApi.call' | sq collect --by-file \
--title-template "migrate: {{filepath}}" \
--description "Migrate OldApi.call to NewApi.call"
# Machine-readable output
sq add --title "Summarize support escalation" \
--description "Emit the created item as JSON for downstream tooling" \
--text "Customer reports checkout fails when applying a discount code on mobile Safari" --json
sq edit abc --set-status closed --json
rg --json PATTERN | sq collect --by-file --title-template "review: {{filepath}}" \
--description "Review ripgrep matches" --json
# Merge metadata patch
sq edit abc --merge-metadata '{"pi_tasks":{"type":"bug"},"owner":"derek"}'
# Mark task as closed
sq close abcsq collect --by-file is the bulk-ingestion command for turning search results into tasks. It reads rg --json output from stdin, groups results by file, and creates one task per file.
rg --json -n -C2 'OldApi.call' | sq collect --by-file \
--title-template "migrate: {{filepath}}" \
--description "Migrate OldApi.call to NewApi.call"Plain-text rg output is not supported. Pass ripgrep context flags like -n, -C2, -A2, -B2 to include line numbers and surrounding context in each created text source.
For each file group, sq collect --by-file creates:
- a
filesource for the filepath - a
textsource containing the grouped ripgrep match/context lines
The default title template is {{match_count}}:{{filepath}}. Available variables in --title-template:
{{filepath}}— full grouped file path{{filename}}— basename of{{filepath}}{{match_count}}— number of ripgrepmatchevents collected for that file
# Build and run
cargo run -- --help
# Run all tests (unit + integration)
cargo test
# Run only integration tests
cargo test --test cli_integration
cargo test --test queue_parityThe initial design was meant to manage a queue for sift a Human-in-the-loop review tool I was building. That model stopped making sense pretty quickly as the tool evolved. The current tool is better understood as a lightweight task list with structured sources, filtering, and dependency state.
The name stuck because it was short, memorable, and already embedded in the CLI (sq, -q, --queue). Keeping the name does not mean sq is trying to be a literal FIFO queue.