A task manager for humans and agents where the files are the truth, git is the sync and the history, one binary is the tool, and static HTML is the view.
No server, no daemon, no vendor, and no dependency: go.mod has no require
block, the standard library does all of it.
A jalon is the stake a surveyor plants in the ground to mark out a line. The id carried in a commit message is exactly that: a marker planted in the commit graph, which is why the link between a task and its code outlives any tool.
go install github.com/AltSoyuz/jalon@latestOr take a single static binary from the
releases: linux, macOS, windows
and freebsd, amd64 and arm64 (plus arm and 386 on linux), each with its sha256.
Uninstalling is rm.
tar -xzf jalon-linux-amd64-v0.1.0.tar.gz && ./jalon versionHomebrew has no tap yet, and that is deliberate.
Tracking a task in a forge costs an agent several network round trips and a few
thousand tokens before it is oriented. A task here is one markdown file, read
sequentially, most useful part first. jalon digest composes that file with the
files it points at, the commits that carry its id, and the open pull requests,
as a single block on stdout.
The links between a task and the code are commits, so they survive any change of platform. They are not metadata in someone else's database.
.tasks/260806-migration-auth.md
---
status: todo
created: 2026-08-06
links: [internal/auth/session.go, docs/adr-3.md]
---
# Migration auth
## Context
Bounded, rewritten at compaction, always current.
## Decisions
- 2026-08-06 gt: cookie sessions over JWT, revocation is required.
## Log
- 2026-08-06 gt: started, blocked on the refresh path.- The id is
YYMMDD-slug. No counter, no coordination, and it stays unique across years so thatgit log --grep '[260806]'keeps meaning one thing. - The front matter is a small
key: valueblock. It is valid YAML, so a forge renders it as a table. Keysjalondoes not know are preserved as written. - The sections are in the order an agent reads them:
Contextis bounded and rewritten,DecisionsandLogare append only, one line per entry. statusis conventionallytodo,doingordone. Any other value is accepted and gets its own group in the view.
The full history is in git, for free. That is what makes truncation safe.
jalon new "migration auth" # creates the file, prints its path
jalon new -issue 42 # or seeds it from a GitHub issue
jalon list -status doing # the cheap half of orientation
jalon append 260806-migration "blocked on the refresh path"
jalon append -decision 260806-migration "cookie sessions over JWT"
jalon digest 260806-migration # the whole context, one block
jalon compact 260806-migration # truncate the Log, report the budget
jalon render # regenerate .tasks/site/
jalon close 260806-migrationEvery command takes an id or its unique prefix, and refuses to guess when a prefix matches several tasks. Flags come before the arguments; a flag placed after them is refused rather than silently read as text.
Orientation is two steps, and the sizes are why. list costs about fifteen
tokens per task and says what exists; its stdout is one line per task and
nothing else, so a harness hook is jalon list -status doing with no glue
around it. digest costs a couple of thousand tokens and says everything about
one task.
digest is the verb for the agent. It writes, in this order: the front matter, Context,
Decisions, the last N Log entries, the content of each linked file, the
commits carrying the id, the open pull requests, and the issue thread when
issue: is set. Every cap it applies is stated in the output, never silent.
-offline skips every gh call.
It reports its own size on stderr:
# digest 260806-migration-auth: 4312 bytes, ~1078 tokens (bytes/4), 2 linked files, 6 commits, 11ms
That line is the point: comparing this against a forge costs nothing, it falls
out of normal usage. ~tokens is bytes/4, an estimate, not a tokenizer.
Set JALON_METRICS=~/.jalon-metrics.jsonl and every invocation appends one JSON
line there: verb, task, bytes, tokens, duration, degraded state, error. Unset,
nothing is written. See docs/measuring.md.
Truncates the Log to its last entries and replaces the older ones with one
line pointing at git log. It does not rewrite Context: this tool holds no
model and will never call one. It reports the token budget and tells you when
Context is what needs your attention.
jalon compact -check <id> changes nothing and exits 1 over budget, which is
what you want in a pre-commit hook.
Regenerates everything, every time: an index grouped by status and one page per
task, with the commits that carry each id. Measured on an Apple M3: 500
tasks of about 5 KB render in about 120 ms (make bench). An incremental
build would be a cache to invalidate for no gain anyone can measure.
Hand written CSS, no JavaScript. Read it over file://, serve it with
python3 -m http.server, or rsync it anywhere. The exit cost is one rsync.
Carry the id in every commit message that touches the task:
git commit -m "[260806] fix the refresh token path"This is what makes the task to code link atomic, inside the commit graph, and
portable. A tag matches when it is a prefix of the id, so [260806],
[260806-migration] and the full id all reach the same task: use the shortest
unambiguous form and the subject stays inside git's fifty characters.
Commit task updates together with the code they describe. No long lived branch
on .tasks/.
hooks/post-merge is a sample hook that reads closes 260806-migration in a merge
message and flips the status. Install it by hand or do not; nothing depends
on it.
There is none, on purpose. Two writable stores would need a token, a mapping
table, a conflict rule and something scheduled to run, which would break the
three properties this tool exists for. The bridge is one way, on demand and
stateless: jalon new -issue 42 seeds a task from an issue in one gh call,
and digest shows the thread afterwards.
Closing needs no code at all. A pull request body holding both lines:
closes #42
closes 260806-migration
closes the issue through GitHub's own mechanism and the task through the post-merge hook. Two systems, two mechanisms, no glue. The reasoning and the condition to revisit it are in docs/workflow.md.
render implements a subset on purpose, rather than depending on a parser:
ATX headings 1 to 3, fenced code blocks, single level bullet lists, paragraphs,
inline code, and links.
Anything outside the subset is emitted as escaped literal text, never
guessed. Tables and nested lists show up as raw markdown in the local view.
The failure mode is "it looks like the source file", never broken HTML and
never an injection. testdata/subset.md is the contract, and its golden output
is checked in CI.
The forge renders full markdown anyway. The view is a comfort, never a dependency.
What you pay: about 1250 lines of Go to own (CSS and HTML templates included),
gh as an optional extra, and conventions a team has to keep.
What you give up: notifications, mentions, contributions from people without repository access, the mobile comfort of a forge, and review threads, which stay on the forge if you keep one.
What you get: no data outside the repository, no network on the hot path, a format any human can read and any agent can digest in one pass, and task to code links stronger than a forge's, because they are commits.
Diagnosing this system is cat and git log. If those two stop being enough,
it has grown too big and the fix is to shrink it.
Degraded modes, all of them explicit on stderr and none of them fatal: outside
a git repository there are no commits and no pull requests; without gh there
are no pull requests; without the binary the files are still readable and
editable by hand; without the view the forge still renders the markdown.
Signature on entries, resolved in this order: -sig, then $JALON_SIG, then the
first word of git config user.name lowercased, then $USER, then unknown.
Tokens and tool calls until an agent is oriented, jalon digest against
gh issue view against free exploration, on real tasks, over two weeks. If
digest does not clearly win, the product reduces to its conventions, which
cost nothing and stay.
None of these is written before its ceiling is hit and measured: browser
writing for non developers (a git based CMS over the same files), a search
index when grep and links stop covering it, a coordination layer if
conflicts on the same files become frequent and counted.
- docs/format.md is the file format, treated as a compatibility surface: what jalon writes, what it preserves, what it will never do to your files.
- docs/workflow.md is how a person, a team and an agent use it, hooks included, and where GitHub fits.
- docs/measuring.md is how to settle the bet this tool is built on, including what it can never measure about itself.
- docs/release.md is the release procedure, the artifacts and the Homebrew question.
- CONTRIBUTING.md and AGENTS.md are the rules for humans and for coding agents.
Its own tasks live in .tasks/, and CI runs the freshly built binary
against them on every pull request: a format change that breaks real files fails
there before it reaches anyone. It is the dogfooding and the regression corpus
at once.
MIT, see LICENSE.