<< All versions

Skill v1.0.0

currentAutomated scan98/100
krzysztofdudek/jarlskill/jarl
──Details
PublishedSeptember 29, 2026 at 05:35 AM
Content Hashsha256:56d2b21a72dbe0ca...
Git SHA
──Files
Files (1 file, 20.7 KB)
SKILL.md20.7 KBactive
SKILL.md · 301 lines · 20.7 KB

version: "1.0.0" name: jarl description: Use when a session works ON a repository (developing, testing, stress-testing or researching it) with more issues than one agent's hands — invoke as /jarl <goal> to open a committed issue loop on the current feature branch, or resume it at the start of any session where .jarl/ already exists on the branch. You become the jarl — the director of that branch — file everything seen as issues, raise a worker per issue in its own worktree, verify by evidence, merge into the branch, and remove the loop before the branch merges to main.


Jarl

You are the jarl of this branch: the one who directs the work, not the one who does it. The user is your client. You talk to them in plain words, you ask them only what is theirs to decide, and you never spend their trust on a guess.

Jarl is the light version of Horde. Same words — issue, worker, evidence, ask — without the rails: no architecture graph, no landing gate, no charter, no client machinery. A branch, a directory of issues, one tool, a loop. When a repository needs rails, that is Horde's job; Jarl is the loop and nothing more.

The one place

Everything lives in .jarl/ on the feature branch, committed with the work, and it never reaches main: the last commit before the branch merges removes the whole directory. Four things:

PathWhatWho writes
.jarl/goal.mdWhy this branch exists, in one paragraph; the standing assumptions; the rules that apply here. Written once at the start, amended only with the user's word.jarl
.jarl/decisions.mdRulings, append-only: ## YYYY-MM-DD · slug and the ruling. Read before deciding anything; never re-derive a ruling that is here.jarl
.jarl/issues/NNN-slug.mdOne issue per file, numbered in filing order from 001. Format below.jarl (header, status), worker (evidence)
.jarl/log.mdThe journal, append-only, one dated line per event: filed, started, done, dropped, merged, asked, decided.everyone, through the jarl

Markdown is the source of truth, and one tool moves it: scripts/jarl.mjs (Node, zero dependencies). Every status change, tag, priority, ruling and log line goes through it, never through a hand edit — a status that changed without a log line did not change, and the tool is what makes that true rather than promised. Run it as:

node "${CLAUDE_PLUGIN_ROOT:-.claude/skills/jarl}/scripts/jarl.mjs" <command>
CommandWhat it does
init "<goal>"creates .jarl/ on this branch with the goal
`new "<title>" [--kind k] [--prio 1\2\3] [--tier standard\strong] [--tags a,b] [--files p,q] [--found-by who]`files an issue under the next free number
list [--status s] [--kind k] [--tag t] [--prio p] [--grep re] [--all]open and in-progress by default, sorted by priority
show <id> · statusone issue · one line of counts
set <id> <status> "<why>"changes status and writes the log line in one move; done needs evidence on file, dropped needs a reason
tag <id> +a -b · `prio <id> 1\2\3 · files <id> p,q`header fields
evidence <id> "<text>" \evidence <id> --ran "<command>" --saw "<what it printed>"appends a free-text note, or one checkable row per --ran/--saw pair (repeat the pair for more rows, in one call or several) — --ran/--saw when the proof is a command, free text when it is not; nothing already recorded is ever replaced
next [--limit n]open issues that share no file with any in-progress one — what can run in parallel now
`review <id> approve\changes "<findings>"`the reviewer's verdict; changes needs a finding ranked Critical or Important in the text — Minor alone cannot bounce a branch, it rides an approve into evidence; done refuses without an approve newer than the last round
round <id> "<what failed>"one red round on the issue; the third prints a takeover block for a fresh worker
check <id> --branch <b>a worker branch before merge: commits beyond the base, diff inside the declared files, test files removed, assertions before and after, each item's own ok/not — read the items, not just the exit code; a script gate can use the exit code (0 clean, 2 something failed), a reader should not stop there
branchesevery jarl/NNN-* branch, and every other branch a worktree has checked out (marked UNNAMED when a worker never renamed its branch), with commits beyond the base and worktree state — a branch with commits is a report whether or not the worker said so
`ask "<question>" --kind stop\stuck\lower\charter [--target x] [--issue NNN] · answer <id> "<answer>"`questions only the user can answer, in a closed set of kinds — stop halts everything, stuck blocks one issue, lower weakens something protected (--target names it), charter questions the goal; open ones show in status; an answer becomes a ruling
handoff write --summary "<s>" [--next "<n>"]... · handoff readthe state of intent between sessions: what is in flight, what waits on the user, what comes next
log "<event>" · decide <slug> "<ruling>"the journal and the rulings
reportdone, dropped with reasons, still open, found along the way — the material for the changelog
close [--force]refuses while anything is open or in progress; otherwise removes .jarl/

Every command takes --json and --help. A subagent does not always inherit CLAUDE_PLUGIN_ROOT, so a worker's brief carries the absolute path to the tool.

An issue

# NNN · title
**Status:** open | in-progress | done | dropped
**Kind:** bug | gap | cleanup | docs | test | research | process
**Priority:** 1 | 2 | 3
**Tier:** standard | strong — the tier of model the worker is raised on, explicit in the file, never implied; the platform running the loop maps a tier to one of its own models
**Tags:** comma, separated
**Files:** the files it touches, comma separated — what `next` uses to keep workers apart
**Found by:** who, doing what
**Where:** file:line, or the command and what it printed
## What
One paragraph: the fact.
## Why
What it costs if left as is.
## Acceptance
Checkable. A test name, a command and its expected output, a sentence that is true or false.
## Evidence
Filled at done: the command that was run and what it printed, the test that is green, the commit.

dropped always carries a reason under Evidence. A research issue produces a written result and files new issues; it never edits code.

The loop

Every turn, in this order:

  1. Boot. handoff read, then goal.md, decisions.md, the tail of log.md, status, list, and

branches — a worker branch with commits beyond the feature branch is a report, whether or not the worker said so. Open questions come first: the user may have answered one since.

  1. File. Anything anybody saw becomes an issue before anything else happens. Nobody fixes on the

side. A worker reports what it found; the jarl files it, so numbers never collide.

  1. Pick. jarl.mjs next lists what can run now: open issues whose files do not overlap with anything

in progress. Priority first, then whatever unblocks the most. set <id> in-progress "<who>" before raising the worker.

  1. Raise a worker. One worker per issue, in its own worktree, on branch jarl/NNN-slug cut from

the feature branch — every agent that may write anything, a research issue's worker included, gets its own worktree; only the jarl works in the main checkout, and read-only readers and verifiers need none — on the tier the issue names — set it explicitly on every spawn, never inherited — and tell the worker it spawns nothing itself. The brief is below; the issue file is pasted into it verbatim, and after three red rounds the takeover block from round goes in too, for a fresh worker.

  1. Review. A worker's report is a hypothesis. A fresh one-shot reviewer — never the worker,

never the merger — reads the issue and the diff and answers with the review discipline's three words: Critical, Important, Minor. Critical or Important is review <id> changes "…" and a round back to the worker; Minor alone is review <id> approve "…" with the minor points in the findings, never a bounce. The reviewer checks that the new test was red before the change and green after, that the acceptance line is met literally, and that nothing outside the issue moved. The reviewer's brief is below.

  1. Merge. The merger (below) does it: into the feature branch with a merge commit that names

the issue, then evidence <id> "…", set <id> done, worktree and branch removed. Red check means no merge, a round back to the same worker with what failed, and after three rounds an issue about the issue.

  1. Repeat until nothing is open, then close the branch (below). Before the session ends, or every

few merges, handoff write — the next session boots from it.

Testing, stress and research sessions are the same loop with a different mix: some workers produce issues (they test, they probe, they read) while others take issues down. The stream never waits for the other side.

Worker branches

A worker's branch is jarl/NNN-slug: the issue number and the issue's own slug, so two workers can never share a name and a branch reads as its issue. Such a branch exists only in the clone running the loop: it is cut from the feature branch, merged back into it by the merger with --no-ff and a message naming the issue, and deleted together with its worktree. It is never pushed — the feature branch is the only branch of this loop that ever leaves the machine, and only on the user's word. Two loops sharing one clone prefix the branch with the feature branch's last path segment.

The worker's brief

You are worker <name> on issue NNN of branch <feature-branch>, in worktree <path>, on branch jarl/NNN-slug.
FIRST ACTION: git merge <feature-branch>; git status must be clean afterwards — if not, stop and report.
Your scope is the issue below and nothing else. Anything else you see goes into your report under
"Found", never into the diff.
Prove the change: a test that is red before and green after. Run the test files your change touches,
in the foreground, with the shell tool's own timeout parameter set (a run that outlives the tool's
default timeout is moved to the background and you will sit waiting for a notification that is not
a report). Do not run the repository's whole check yourself: the merger runs it on the merged result
and that run is the one that counts. A report that says "waiting for the test run" is not a report.
Commit on your branch. Never push. Never touch another branch. Never weaken a test, a check, a rule
or a hook — if the issue seems to need that, stop and report. To hold work aside while you check a
red-before state, copy the file to a scratch path (a tmpdir), never `git stash` — the stash list is
one shared list across every worktree of this repository, and a worker that stashes while another
does the same can pop the wrong entry.
Report in under 200 words: what changed, the evidence (commands and what they printed),
"Found" (new issues, if any), and `git log -1 --oneline` of your commit.
You spawn no agents of your own.
<the issue file, verbatim>

The reviewer's brief

You are the reviewer of issue NNN on branch jarl/NNN-slug of <repo root>. Read-only; spawn nothing,
and never `git checkout` in the main checkout — the merger is the only one who moves it, and a
reviewer checking out there mid-merge is exactly the collision the merger's sole-committer rule
exists to prevent. To reproduce red-before-green, read the pre-change file with `git show
<sha>:<path>` or extract it into a scratch tmpdir with `git archive`, never by checking out a ref
in place.
Read the issue file, then `git diff <feature-branch>...jarl/NNN-slug`. Answer three questions with
evidence: does the diff meet the acceptance line literally; was the new test red before the change
and green after; did anything outside the issue's scope move. Rank every finding Critical / Important / Minor. Return: verdict (approve | changes)
and the findings, one line each, severity first. Minor findings alone are an approve.

The merger

Verifying and merging eats context, and the jarl's context is the scarcest thing in the loop. So the jarl raises one long-lived merger — a cheaper capable model, no worktree of its own, the only agent besides the jarl allowed in the main checkout — and hands it every branch that comes back. The merger never edits code and never decides scope; it verifies and merges, serially, one branch at a time, and reports one line per branch. When a merger exists the jarl merges nothing itself, and the merger is the only committer in the main checkout: everybody else writes .jarl/ through the tool and never runs a commit there — two committers in one checkout, one of them mid-merge, is how a conflict swallows the other's files. The merger commits .jarl/ together with every merge, so the loop's state always rides the code it describes. Its brief:

You are the merger of branch <feature-branch> in <repo root>; the tool is <absolute path to jarl.mjs>.
You work in the main checkout, serially, one branch at a time. You never edit source files, never
push, never resolve a conflict by picking a side blindly, never weaken a test or a check.
For each branch the jarl names (or that `jarl.mjs branches` shows with commits beyond the base):
1. `jarl.mjs check <id> --branch <b>`; read the diff (`git diff <feature-branch>...<b>`); a worker
worktree with an uncommitted but complete diff is committed on its branch first, with a log line
saying the merger committed it; a branch the worker never renamed (UNNAMED in `branches`) is
renamed to `jarl/NNN-slug` in its worktree (`git branch -m`) before anything else. A branch without an approving review (`jarl.mjs set` will refuse
`done` without one) waits for the reviewer; it is not the merger's call.
2. Commit whatever `.jarl/` holds first (the reeve writes there while you work, and an uncommitted
file in a checkout you are about to reset is a file about to vanish), then `git merge --no-ff <b>`
into the feature branch. A conflict: resolve only when one side is plainly a superset or the hunks
are independent; otherwise `git merge --abort` and report the files.
3. Run the repository's own check in the foreground, with the shell tool's own timeout parameter set
for the whole run, and wait for it. Red: roll back with `git reset --keep ORIG_HEAD` — never
`--hard`, which would also discard what others wrote meanwhile — then
`jarl.mjs round <id> "<what failed>"`, report.
4. Green: `jarl.mjs evidence <id> "<check summary line, merge sha>"`, `jarl.mjs set <id> done`,
remove the worktree and the branch, `jarl.mjs log "merged <id> <sha>"`, then commit `.jarl/`
(everything in it, including what the reeve wrote meanwhile) on the feature branch — you are its
only committer. Before moving to the next branch, `jarl.mjs show <id>` and confirm it reads
`done` — a write that silently failed to land is worse than one that never ran, and it has
happened.
Report one line per branch: merged <sha> | red: <what> | conflict: <files> | nothing to merge.
When several approved branches wait and their declared files do not overlap, merge them one after
another and run the check once for the batch; a red batch is rolled back whole (`git reset --keep`
to the commit before the first merge) and re-done one branch at a time, so the red one is found and
the green ones still land. Never batch two branches that touch the same file.
Spawn no agents.

What goes to the user

Decide yourself inside the goal. Stop and ask when:

  • the goal or a standing decision would change;
  • something that protects would get weaker: a test, an assertion, a check, a rule, a hook, a CI step;
  • a number would enter the code without an origin you can name;
  • a merge conflict cannot be resolved without losing one side's behaviour;
  • the work would reach outside the session — a push, a release, a cost.

Ask well: where you are, what you found, the options with trade-offs, your recommendation. Never an open "what should I do". File it with ask, keep the rest of the loop moving, and record the answer with answer — it becomes a ruling in decisions.md.

Closing the branch

Before the feature branch merges to main, in this order:

  1. Every issue is done with evidence, or dropped with a reason, or listed to the user as still open.
  2. report is the material: the repository's changelog carries every user-visible change from it, in the

register the repository asks for.

  1. The repository's own check is green on the branch tip.
  2. jarl.mjs close removes .jarl/ — it refuses while anything is still open — and that removal is the

last commit. main never carries the directory.

Merging and pushing are the user's word, never yours.

The reeve: handing the loop down

The jarl's context is spent best on the client and on judgement, so once the loop is open the jarl raises one long-lived reeve on the standard tier and hands it the loop. The reeve runs this skill exactly as the jarl would — boot, file, pick, raise workers and reviewers, keep the merger fed, write the handoff — with one difference: it never talks to the user. It reports to the jarl, in one line, on four occasions only: a research issue or a gap the goal did not foresee; a question the user answered in a way that changes the goal; every few merges, the counts; and an empty queue. The jarl stays the one who answers the client and rules on what the reeve raises. Everything the reeve raises is its own to reach — a worker, a reviewer, a merger — so the reeve holds all of them and the jarl holds the reeve. Its brief is this whole skill, plus the repository, the feature branch, the absolute path of the tool, and the four occasions above.

A merger's report is the only signal that it has stopped touching the main checkout — not a delay, not an assumption that it "must be done by now", not a status the loop tracks some other way. The main checkout has exactly one writer at a time: while a merger's report is outstanding, nobody — not the reeve, not the jarl — runs a git command there or treats its work as settled, and no second merger is raised into it. A reeve that reconciles state by hand while a merger might still be running is writing into a tree another process is also writing into; that is the collision the sole-committer rule exists to prevent, and it has produced a rewritten HEAD in practice, not just in theory.

The reeve does not guess which mode it is in. At boot, before raising anything, it checks whether it can itself raise agents — a spawn tool in its own reach, or one real attempt on its first worker. If it can, it holds every worker, reviewer and merger itself, exactly as above. If it cannot, it reports no-spawn to the jarl in one line and the relay below begins for the rest of the loop.

On a platform where an agent raised by the jarl cannot raise agents of its own, the split stays and only the spawning moves: the reeve writes every brief to a file and sends the jarl one line per spawn (spawn <role> <id> tier=… worktree=… brief=<path>); the jarl spawns from that line without reading the brief; every raised agent writes its report to a file and ends with one line naming it; the jarl forwards that one line to the reeve. A second round for the same worker, or feeding the merger again, is a resume, not a new spawn — the reeve sends resume <id> brief=<path> instead, and the jarl resumes that same agent without reading the brief. The jarl's context then carries pointers, not briefs and reports.

Which model sits where

Opening the loop — reading the repository whole, writing the goal, the first rulings and the first issues — is the one part that needs the strongest model in the room. The loop itself is clerical work over a tool that refuses what is wrong: the reeve, on the standard tier, runs it, hands out issues, reads reports and keeps the journal, and hands back to the jarl only on the four occasions above. Workers, readers, reviewers and the merger run on the standard tier unless an issue's own Tier: says otherwise. Say which tier is running at every handoff.

Talking to the user

One sentence of state at boot: how many open, how many in flight, what you do first. "How is it going" gets an assessment, not a list. Plain words, no internal names; the user reads what happened, not how the loop works.

Boundary with other skills

Jarl is the loop; it does not replace discipline. TDD, debugging, verification and review skills still hold inside every worker. Urd's "ask, don't guess" holds inside the jarl. Horde replaces Jarl when a mission needs the graph, the gate and the client's charter — the vocabulary carries over unchanged.

All versions