slicer
Roadmap and slice management for the review → slice → implement → done loop.
slicer is AI-friendly: a coding agent can review a project, turn accepted findings into an importable roadmap, and work through bounded slices using commands with JSON output. For a new project, start with goals and acceptance criteria instead of review findings. The AI supplies the review and planning; slicer stores, validates, prioritizes, and renders the work. It runs locally without an AI service or API key.
Run slicer ai instructions for a concise agent quick start, or add --json for
machine-readable output. It works before initialization and reads no project state.
Start with the worked workflows, use the agent prompts, or follow the contributor workflow to work on slicer itself. See the changelog for notable changes by release.
State is JSON. The markdown under .slicer/render/ is generated output — readable,
committed, and never parsed back. Edit through the commands or the TUI, not by hand.
Stdlib Python 3.11+, no dependencies.
Install
Python 3.11+, no dependencies. Install it to use slicer; clone it to change slicer.
Install it (no clone)
Into its own virtual environment from PyPI. The distribution is squalor-slicer
because the slicer name on PyPI is taken; the command it installs is still slicer.
python3 -m venv ~/.slicer-venv
~/.slicer-venv/bin/pip install squalor-slicer
ln -s ~/.slicer-venv/bin/slicer ~/.local/bin/slicer # or anywhere on your PATH
slicer --help confirms it. Update with
~/.slicer-venv/bin/pip install -U squalor-slicer; uninstall by
deleting the venv and the symlink.
If you prefer a single command, pip install --user squalor-slicer also works, with two
caveats: on macOS Homebrew and recent Debian/Ubuntu/Fedora the system Python is
"externally managed" (PEP 668) and rejects it — use the venv above instead — and the user
scripts directory must be on your PATH (~/.local/bin on Linux,
~/Library/Python/3.11/bin on macOS). Uninstall with pip uninstall squalor-slicer. If
you already use pipx or uv,
pipx install squalor-slicer or uv tool install squalor-slicer install it isolated and
on your PATH in one step.
To try unreleased main instead of the latest release, replace the package name with
git+https://github.com/squalor-xyz/slicer.
Develop on it (clone + editable)
To hack on slicer itself, use an editable install so the command tracks your checkout:
git clone git@github.com:squalor-xyz/slicer.git
cd slicer
python3 -m venv .venv
.venv/bin/pip install -e .
ln -s "$PWD/.venv/bin/slicer" ~/.local/bin/slicer # or anywhere on your PATH
The install is editable, so slicer follows the checkout. slicer --help confirms it.
Use it on a project
cd any-repo
slicer init # creates .slicer/
slicer add "Parse the config file" --size M --tree core
slicer promote S01 # give it a slice file from the template
slicer edit S01 --section Why --text "Load settings before starting the app"
slicer render # regenerate .slicer/render/
slicer check # the gate: exit 1 if anything drifted
add appends a roadmap row; promote gives it a slice file.
Got a whole roadmap to load? Write it as a markdown outline and import it in one go:
slicer import --skeleton > roadmap.md # a template, built from your config
slicer import roadmap.md --dry-run # validate; writes nothing
slicer import roadmap.md # apply
Each ## heading is an item, optional key: value lines carry its size, tree and
dependencies, and ### sections become the slice itself — so one file can produce a
fully written roadmap. It is a one-way ramp: the file is yours to delete afterwards.
Already running this workflow by hand in markdown? slicer migrate --from docs/slices
converts an existing tree that is already in slicer's legacy format. It refuses to
write anything unless every file round-trips byte for byte, so a document slicer cannot
reproduce is never half-migrated.
→ docs/getting-started.md walks through all of this with real output. docs/import.md is the outline format; docs/agents.md is how to drive slicer from an AI agent; docs/migrate-format.md is the legacy grammar; and docs/configuration.md is every config key.
Commands
ai instructions |
agent quick start, available without a project; supports --json |
ai skill |
the same loop and exit rules as a SKILL.md for Claude Code, Codex, and Grok |
init [--force] |
create .slicer/ with config and templates; --force rewrites an existing config and templates only |
setup-git |
print the two git config lines that enable the slicer-generated render merge driver in this clone (slicer setup-git | sh applies them); needs no project |
import FILE [--dry-run] [--force] |
bulk-load a roadmap from a markdown outline |
import --skeleton |
print an outline template built from your config |
migrate --from DIR [--dry-run] [--force] |
convert an existing legacy markdown tree; --force replaces an existing roadmap |
add TITLE [--id/--size/--tree/--findings/--status/--pass/--importance/--urgency/--effort/--depends-on/--short-title] |
append a roadmap item (no slice file yet); repeat --depends-on ID for multiple dependencies. --effort is 1–3 and optional; an open item left at importance 2, urgency 2 and no effort gets a stderr hint |
promote ID [--file/--stdin] [--boundary TEXT] [--force] |
give an item a slice file; a one-item outline fills its sections in one call. --force overwrites an existing slice |
move ID --before/--after/--to |
reorder the queue; position is the manual priority, and breaks score ties |
sort [--by score|effort] [--render] |
reorder the whole queue in one step. score (default) persists list --sort score. effort persists list --sort effort: lightest estimate first, unset last |
next [-n N] [--start] [--show|--ready [--section NAME ...]] |
one eligible item at offset N (default 0), with its effective score and status; --start marks it started. --show adds the full item and its slice. --ready returns item identity, the slice, and blocked ids. Repeat --section with --ready to return the scope boundary and those sections only. An item started or claimed in a sibling Git worktree is skipped and reported (in_work_elsewhere), unless this checkout has it too |
next-id |
the id the next add or import would take, without allocating it |
list [--all] [--status/--tree/--pass/--flag] [--sort score|effort] |
the queue in next's order: unblocked started, then unblocked open, then the other visible rows, each by effective score. The text table has a CLAIM column: the local owner, * for locally in-progress with no claim, wt:NAME for work in a sibling worktree, or -. wt:NAME+N means N more worktrees. --json includes claim ({"owner", "at"} or null) and in_work_elsewhere (an array of {worktree, owner}). Done and retired items are omitted unless --all is set or --status names them. Repeat --flag to keep an item that has any of those flags. Flags are free-form labels set with set --flag. --sort score is a flat score sort. --sort effort orders estimates 1–3 and puts unset items last, without writing state |
find PATTERN [--in FIELDS] |
search items by text (id, title, findings and slice bodies by default); shows the matched field and a snippet |
deps [ID] [--format mermaid] |
dependencies: unblocked open items, or one item's waits-on/blocked-by/dependents; --format mermaid renders the graph |
show ID [--section NAME ...] [--context] |
print one slice or selected sections; --context adds title, dependencies, and scope boundary |
set ID [ID ...] --title/--short-title/--size/--tree/--findings/--status/--pass/--depends-on/--flag/--no-flags/--group/--importance/--urgency/--effort/--no-effort |
change fields. --no-effort clears an estimate. add and set refuse an unknown, self, retired, or cycle-closing --depends-on and write nothing |
edit ID (--section NAME / --boundary) [--text/--file/--stdin] |
edit a section or scope boundary; sections also accept --append |
note ID [--text/--file/--stdin] [--render] |
append a dated note to any item — no slice needed (shows in show/render, unlike done --note) |
prose list / show REF / edit REF |
read and edit the roadmap's own prose |
prose add-pass KEY / drop-pass KEY |
open or close a pass group |
goals |
print the project's goals and non-goals together; supports --json |
start ID [ID ...] [--note TEXT] |
mark an item in progress and claim it (owner and time). The owner is claim_owner in config, otherwise the git user name, otherwise the worktree name. A second start does not refresh the claim |
release ID [ID ...] |
clear a claim without changing status. An item that was in progress stays in progress and lists as * |
handoff ID [ID ...] [--note TEXT] |
hand a started slice to review: status becomes review_status and the claim is cleared. next skips review items and dependents stay blocked until done; a reviewer finds them with list --status review and claims one with start |
done ID [ID ...] / park ID [ID ...] / unpark ID [ID ...] [--note TEXT] |
change status; --note records a one-line history entry (for a durable note on the item, use slicer note); done moves the file with git mv and clears a claim |
remove ID --reason "…" |
retire an obsolete item; the id stays claimed |
remove ID --purge |
delete outright, for something that never should have existed |
remove ID --purge/--reason --dry-run |
preview the removal and its fallout (dependents, id fate); write nothing |
remove ID ... --force |
retire or purge despite dependents, or a done item |
render |
regenerate .slicer/render/ (ROADMAP.md, a browser-viewable ROADMAP.html, and one file per slice) |
sync [--check] |
rewrite derived lines in other documents |
verify |
check the index for consistency, and against git log (unless git_check is off) |
check [--diff] |
the CI gate: render staleness, sync drift, integrity |
stats / log [--limit N] [--item ID] [--action A] |
counts + completion % and per-tree progress; history, newest first (--limit defaults to 20; --item/--action scope it; set records old→new values) |
status |
the front door: next item, progress census, and blockers in one view (--json) |
tui / ui |
browse, read, reorder and edit interactively (two names for the same command) |
The sibling worktree signal comes from local git worktree list and each checkout's
.slicer/index.json. It sees worktrees on this machine only, not work on another machine.
slicer next -n 1 returns the item after the current next item. Offsets are
nonnegative integers: -n 0 is the same as next. Eligible started items come
before eligible open items; each group uses descending effective priority with
roadmap order breaking ties. Skipping an item does not complete it or unblock its
dependents. The command returns one item, including rows without slice files;
an exhausted offset exits 2 (JSON returns item: null and blocked details).
The text form of slicer list labels its columns: number, id, status, size,
effort, score, quadrant, and title. An unset effort appears as -. When the
default status filter hides done or retired items, a final line counts the
matching hidden rows and points to --all. --all and --status suppress that
notice; --json remains an array of the visible item records.
slicer next --ready is a bounded pickup of that same item: id, title,
status, depends_on, effective_score, path, the slice when the item has
one, and every blocked id. The agent loop uses
slicer next --ready --section "Implement" --section "Check" --json --lean.
The headings are examples; pass the section names the project configures.
Repeat --section NAME with --ready to keep the
scope boundary and those section bodies; omit it and the slice stays complete.
--section without --ready is a usage error. A row with no slice still says
to run promote. An empty queue uses the same exit 2 result as next.
Pass either --ready or --show. The text form of --ready stays the
identity, the boundary, and the section headings.
In the TUI, the queue opens in the same ranked order as slicer list: eligible
started items, then eligible open items, then the other visible rows, each by
effective score, with parked items last. Done and retired stay hidden until
you clear the filter or ask for them. o sorts that view by ranked order, ID, title, status, size,
importance, urgency, effective score, or effort, ascending or descending. The
choice lasts for the session and does not rewrite the stored queue. J, K,
T, and M still move items in stored order, and only when no filter or
search is active. tab moves between the queue and the detail pane, e opens $EDITOR on
whatever is selected there — an item field (size, trees, findings, depends, importance,
urgency), a slice section, its scope boundary, a note (edit it, or empty to remove; the
+ add a note line adds one), or a prose block — s starts the selected item and a adds a
new one. Press w for the guided roadmap wizard; an empty roadmap offers it once
when the TUI starts.
The wizard collects an optional roadmap heading and each item's title, size, trees,
findings, importance, urgency, group, and dependencies (comma-separated exact titles,
including later draft items). Enter advances and Shift-Tab goes back. Each configured
section offers e to open $EDITOR, or Enter to leave its body as it is. Every item
gets a slice, even when its section bodies are empty.
After adding items, review the answers with Up/Down and Enter to revisit a field or
section, add another item, or select Save roadmap. Esc asks before discarding draft
answers. Nothing is saved until the final save; validation errors keep the draft for
correction. A supplied heading is prepended to existing roadmap preamble prose; a blank
heading leaves it unchanged. Saving generates the normal roadmap output without a
separate outline file. The project must already be initialized with slicer init.
The main TUI screen keeps common shortcuts visible below the status and feedback
lines: pane switching, editing, adding, starting/completing items, search, filters,
show all, jump, movement, help, and quit. Hints use one row when they fit or two at
80 columns, and stay visible after actions. These are fixed defaults; ? opens
the complete shortcut list. Prompts and overlays show their own instructions.
The TUI initially hides the project's configured done status. View controls:
| Key | Action |
|---|---|
/ |
Search IDs, full titles, and short titles as you type; Enter accepts, Esc cancels |
f |
Filter by status, tree, pass, importance, and urgency |
c |
Clear search and all filters, including the default hide-done filter |
g |
Jump to an ID; hidden targets are revealed by clearing search and filters |
J / K |
Reorder the selected item down / up |
T |
Move the selected item to the top |
M |
Move the selected item to a numbered position |
? |
Open help; arrows or j/k scroll, ? or Esc closes |
In the filter panel, arrows or j/k navigate, Space toggles choices, Enter applies,
and Esc cancels. Each group offers Any; tree and pass also offer (none). Multiple
choices within a group match any selected value; different groups must all match.
Importance and urgency use the item's assigned values (1–3), not inherited priority.
Search is case-insensitive literal text and combines with the filters.
The status line shows matching/total item counts and active restrictions. Roadmap
prose stays accessible below the items and is excluded from those counts. Filters
last only for this session. J/K reorder down/up, T moves to the top and M moves to
a numbered position; reordering requires clearing all restrictions with c. Filtering
itself preserves queue order.
Queue and Details headings mark the focused pane with >. Focused selections use
reverse/bold; inactive selections retain a marker and bold text. Queue rows show
P:22-style base priority scores (importance × 10 + urgency); an axis of 3 emphasizes
the score without reordering items. The detail pane retains the score and quadrant.
When supported, cyan marks headings/started work, green marks done/success, yellow
marks parked/blocked work and high priority, and red marks errors. Labels and !
blocked markers remain visible without color. Feedback uses OK:, Error:, and
Info: prefixes; unchanged actions and cancellations are informational. Set
NO_COLOR=1 for monochrome; unsupported terminals also fall back automatically.
Below 80 columns or 10 rows, the TUI shows a resize prompt and preserves the session.
Every command except the interactive tui/ui takes --json, including the failures — an agent calls slicer next --json rather than parsing markdown, and reads {"error": {"code": ...}} rather than
prose. Exit codes: 0 fine, 1 drift or a failed check, 2 usage, validation, or nothing to do, 3 internal or state (corrupt, locked, io, config, schema_too_new).
See docs/agents.md.
Every command that changes state — add, set, start, done, move, sort, promote,
edit, note, remove, park, unpark, import, migrate, and the prose edits — takes --render
to regenerate .slicer/render/ in the same step, so a mutation and its render are one
command. By default the change is saved first and rendered after; add --strict to require
the render to succeed first, so a change that cannot be rendered is rolled back rather than
landed (this is how done --render already behaves). The agent loop passes
--render --strict on start and on slice edits. done stays --render.
Items carry an Eisenhower-style priority: an --importance and an --urgency (each 1–3),
combined into a score (importance leads). A blocker of a critical item inherits its
priority, so slicer next and slicer list --sort score surface the blockers of
important work first, while the stored queue order stays whatever move set.
slicer never commits, pushes or tags. git access is allowlisted to
rev-parse, status, log, mv, ls-files, and the read-only queries
worktree list --porcelain, branch --all, and config --get user.name. start uses
the branch and worktree queries to warn when another checkout already refers to the slice;
the exit code does not change, and
next stays silent. Writing subcommands cannot be reached from the code at all.
Batch changes
done, start, release, park, unpark, and set accept multiple IDs.
For example, slicer set S01 S02 --urgency 3 --render applies the same fields to
both items. Use slicer done - to read whitespace-separated IDs from stdin.
See batch changes for validation and output rules.
What lives in .slicer/
config.json paths, statuses, fields, templates, sync targets
index.json the ordered queue plus roadmap prose (preamble, goals, non-goals, epilogue)
slices/<ID>.json one open slice: title, lead, sections (ordered list)
slices/done/<ID>.json finished slices
slices/retired/<ID>.json obsolete slices, with the reason on the item
templates/*.md render templates, yours to edit
render/ GENERATED - ROADMAP.md, ROADMAP.html (browser-viewable), and one file per slice
log.jsonl append-only history of status changes
.gitattributes union-merges log.jsonl, and keeps generated render/ on merge (see below)
Sections are an ordered list, not a map: real slices carry headings no schema names, sometimes more than once, and their order is part of the document.
Landing work on parallel branches touches these files: log.jsonl is append-only and
union-merges automatically (via the generated .gitattributes), so both sides' entries
survive without a conflict. index.json is the source of truth — a genuine overlap there is
yours to resolve. Anything under render/ is a projection of index.json, so after resolving
a merge just re-run slicer render (and slicer check will flag it if you forget) rather than
merging the generated markdown by hand.
The .gitattributes also points render/ at a slicer-generated merge driver that keeps the
current branch's copy instead of writing conflict markers — but a driver name only resolves
once the clone defines it. Run slicer setup-git to print the two lines (or slicer setup-git | sh to apply them), once per clone — slicer's git allowlist cannot run git config for you:
git config merge.slicer-generated.name "keep the current branch's generated files"
git config merge.slicer-generated.driver true
Either way, re-run slicer render after resolving index.json so the kept files match it.
The scope boundary is a separate field on each slice. It renders after metadata and
before sections, so section edits cannot remove it. Use slicer edit ID --boundary
with --text, --file, --stdin, or the editor to change the full paragraph. Empty
text clears it. promote --boundary TEXT overrides the source/default boundary.
Removing an item
Removal is two different acts, so remove has two modes.
remove ID --reason "superseded by S30" retires it: the status becomes retired, the
slice moves to .slicer/slices/retired/, and the row keeps rendering with the reason
beside it. The id stays claimed. This is for something that existed and was cited — a
commit or a review that names it must still resolve to something that explains itself.
remove ID --purge deletes it: the item and its slice file go. This is for a mistyped
add. The id comes back only when it was the most recently allocated and no commit
subject mentions it — that is undoing an allocation, not reusing an identifier. Any
earlier id stays burned, and the output says which happened and why.
Both refuse when another item depends on it, or when it is done; --force overrides and
names the rule it overrode. After a forced purge, slicer check reports the dangling
dependency it left behind. Add --dry-run to either mode to preview the outcome first — the
dependents that would dangle and whether a purge would free or burn the id — without writing;
it turns that surprise into a decision.
Retiring needs a status to move into. A tracking directory created before remove existed
gains a retired status automatically on load — only that one key, so a project that
dropped some other status does not get it back.
Roadmap prose
A roadmap carries text that belongs to no slice: an opening note, a heading and prose
around each pass group, and a closing section. slicer prose addresses those blocks:
preamble the opening note
goals project goals (see below)
non_goals project non-goals (see below)
pass.<key>.heading the group's markdown heading
pass.<key>.intro prose above the group's table
pass.<key>.outro prose below it
epilogue the closing section
slicer prose list names every block in the order it renders. Both edit and prose edit
take exactly one of --text, --file, or --stdin, or open $EDITOR when none is
supplied. In replacement mode, --text preserves the argument exactly, including
newlines, and --text "" clears the body. Section editing also accepts --append with an
explicit source: it joins old and new text with one blank line, removing boundary
newline characters. Empty appended content leaves the body unchanged.
For example, slicer prose edit preamble --text "Current priorities" replaces the preamble.
A pass group is opened with prose add-pass 6 --heading "# ..." and closed with drop-pass, which refuses while any item is still filed under
it. Items are filed with slicer add --pass 6 or moved with slicer set <id> --pass 6.
A declared pass renders even with no items yet, so a group can be opened before its first slice exists.
Goals and non-goals
The backlog says what is queued; goals and non-goals say what the project is for, so
humans and AI agents can judge what belongs on the backlog at all. They are two roadmap
prose blocks (goals, non_goals), edited like any other prose and rendered near the top
of ROADMAP.md:
slicer goals print both, or slicer goals --json for agents
slicer prose edit goals record or revise them (--text/--file/--stdin/$EDITOR)
slicer prose edit non_goals
slicer check keeps the rendered copy current. Set direction with the owner — do not
infer it from the backlog.
slicer's own goals and non-goals: run slicer goals, or read them at the top of
the roadmap. In short, slicer is a small, dependency-light,
file-based store for a project's roadmap, goals, and issues — canonical JSON that humans
and AI agents plan from, projected deterministically to markdown — and is not a
real-time, multi-user collaboration tool (coordination happens through git).
Configuration
Everything project-specific is in .slicer/config.json — status vocabulary and how each
one renders, the section list promote seeds, the scope-boundary marker, which flags
exclude an item from derived pointers, and the sync targets. Nothing is compiled into
the tool, so slicer works on a repo with no review protocol at all.
Every key, its default, and which ones are unsafe to change once items exist:
docs/configuration.md. Two to know up front — id.prefix and
id.width can change before the first item exists. After allocation the index owns the
scheme; changing the config does not renumber items, and a mismatch fails check.
Tests
python3 -m unittest discover -s tests -t tests
No install step and no dependencies — the suite puts src/ on sys.path itself.
tests/fixtures/legacy/ is a synthetic 14-slice tree for a project that does not
exist. It is shaped to exercise the awkward parts of the legacy format — a middot
inside a findings value, singular and plural tree keys, a collective trees cell,
headings no schema names, prose between the tables — and the suite proves every file
round-trips through the parser byte for byte.
Set SLICER_LEGACY_TREE=/path/to/docs/slices to additionally prove the migrator
against a live markdown tree of your own. That test is skipped when the variable is
unset, and it is the only way to exercise the migrator against real, messily
hand-written markdown — worth running before changing legacy.py or migrator.py.
Status
slicer manages its own roadmap: .slicer/ in this repository is a worked
example you can read, and .slicer/render/ROADMAP.md is what it renders to. Run
slicer --version for the installed version, and see the changelog.
docs/getting-started.md is the walkthrough, and docs/agents.md covers driving slicer from an agent. ARCHITECTURE.md explains the layering and the invariants. AGENTS.md has the commands and the house style.
Apache-2.0. Stdlib Python, no dependencies, and none planned.
Metadata
Release files for squalor-slicer 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| squalor_slicer-1.0.0.tar.gz | 215.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| squalor_slicer-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 339.8 kB
Release files / squalor_slicer-1.0.0.tar.gz
| Download URL | squalor_slicer-1.0.0.tar.gz |
|---|---|
| Size | 215.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f7db2461d0f981e067668bed2964eae0c5ee61f713168aa7fd99c30b2e0da1b7
|
|
BLAKE2b-256 checksum How to use checksums |
1f6ab70c621b0e5050e4beea15aa34500add073813089fabc107cbb1708cf5b4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logRelease files / squalor_slicer-1.0.0-py3-none-any.whl
| Download URL | squalor_slicer-1.0.0-py3-none-any.whl |
|---|---|
| Size | 124.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
109bd2f82b27d0673821702310623c816fded7cc67b2ec176946a25283abd0a8
|
|
BLAKE2b-256 checksum How to use checksums |
610ed6239b1da48763c341eba7cf77105f5f63f1c4c4d4dfc67dcf4a23daadc0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency log