Repository navigation
v0.2.2
** Companion compatibility
Harrow 0.1.0 can read format-3 projects and the existing saved views. Its
closed_at filtering is not yet compatible: use Cairn for completion-date
queries until the companion contract work in v0.3 lands. A later edit changes
updated, not closed_at; do not substitute one date for the other.
** Bare initialization suggests a command that works
cairn init --bare no longer suggests creating work under v0.1, which that
mode deliberately does not create. The next-command hint names a milestone
only when initialization actually wrote the example milestones.
** cairn set no longer reports failure for a change it made
cairn set 1 title=Taken exited 1 with cannot rename to ...: file already exists -- and the title had been changed and written. The rename failed, not
the set. Running it again failed again, while the title had been right the whole
time, and a script keying off the exit status treated a completed change as not
having happened.
The filename is cosmetic in cairn: the identifier lives in the frontmatter, and
cairn check reports drift from the title as a warning. So a name already taken
is now reported rather than failed -- after the line saying what happened, the
way close reports unticked criteria -- and the file in the way is still never
touched.
Every command that keeps a filename in step with a title had the same defect,
because all of them save the item first: set, propose, edit, import and
the MCP update_item, which says it in the reply since no agent reads standard
error. renumber still fails, and deliberately: it exists to make filenames and
identifiers agree, so a name it cannot take means the repair did not happen.
** cairn log --range: what a branch did to the backlog
cairn log answered what happened to one item. Nothing answered what a branch
did, which is the question a reviewer has -- so a pull request touching a dozen
items was read as a dozen YAML diffs, and the summary reconstructed by hand or not
at all.
cairn log --range main..HEAD
cairn log --range v0.2.1..
cairn log --range main..HEAD --json
Grouped by what happened rather than by file:
new 0128 A finished project looks like it contradicts itself
closed 0044 The rewind works
changed 0090 Timeline scrubbing priority p2 -> p0
removed 0077 Filed twice by mistake
renumbered 2 item(s)
Items are matched by the id in their frontmatter and never by filename, because
the filename follows the title and is cosmetic. A renumber is counted rather than
listed: it moves identifiers and the filenames follow, so reported as they arrive a
repair of fifty identifiers would read as fifty items gone and fifty arrived, and
bury whatever else the branch did.
--json carries the same, for a script or a comment on a pull request.
** A Windows checkout is no longer told its roadmap is stale for ever
ROADMAP.md is generated with LF and carries no -text attribute, so a checkout
with core.autocrlf set -- the default on Windows -- converts it to CRLF on the
way out of git. The staleness check compared bytes, so it could never match: a
Windows user was told the roadmap was out of date on every run, and cairn render
rewrote it as LF, which git then reported as every line changed. There was no
state in which the project was quiet.
The comparison now ignores line endings, and rendering writes back whichever
ending the file already had -- the same pair item files have always had. A
genuine change is still a genuine change.
** A clone is told the merge driver did not come with it
A merge driver is per-clone configuration: git deliberately refuses to take an
executable name from a file in the repository. .gitattributes is tracked and
the driver is not, so cairn init --git committed a file asking for a driver that
nobody who cloned the project had.
The failure was silent and one-sided. It worked for whoever ran init --git,
which means the one person who could fix it was the one person who could not see
it; everybody else got .gitattributes asking for a driver git had never heard
of, git merged the item files itself, and two branches that had both allocated an
identifier kept it. What the newcomer learned about cairn was that a shared
backlog produces merge conflicts.
cairn check now says so -- as a note about the working copy rather than a
finding about the project, which is the right distinction and not a nicety: every
continuous-integration run is a fresh checkout with no driver and no need of one,
so a warning would have made --strict fail everywhere. cairn config no longer
calls such a project integrated, because the driver is part of the integration and
was not being checked. cairn init --git was already safe to run twice.
** When something was finished, recorded
updated records the last edit, which is exactly why it could not answer what
did we finish this week: fixing a typo in a closed item moved it, and the week's
work changed retroactively.
cairn close now stamps closed_at, a date, at the transition.
cairn list -A --filter 'closed_at>=2026-09-01'
Stamped at the transition and only there. Closing something already closed
leaves the original date alone, reopening clears it, and every ordinary edit
afterwards leaves it where it is. It can also be set by hand, for an import or a
project adopting cairn mid-life.
closed is unchanged: it remains the derived boolean asking whether an item is
finished at all, which the filter grammar promises keeps working, so the stored
date needed a name of its own.
The key is optional and additive, so no format number and no migration -- the
specification already allows an item to gain an optional key, and requires readers
to preserve keys they do not recognise. §4 documents it, spec/reader.py
implements it, and a corpus case carries it with updated deliberately different,
so the two readers are held to agreeing about the distinction.
** A finished project no longer looks like it contradicts itself
Reported from a project with 70 items done and 7 milestones still open.
cairn list --status backlog printed no items match while seven items had
exactly that status. They matched and were then hidden for being containers, so
the message described the filter rather than what happened to the result -- and
the obvious response was to go and rewrite a filter that was correct. An empty
listing now says how many items were hidden, why, and how to see them, for
containers and for closed items alike. Naming a status still does not unhide
containers, and naming a type still does: that symmetry is deliberate.
Finishing a milestone's last item never said so. The milestone stayed in
backlog for ever, and backlog is usually the initial status, which the
roadmap does not print -- so the only visible fact was 100%. cairn close now
says it once, at the moment it becomes true, and cairn roadmap marks a
milestone whose work is all done while it is still open.
Nothing is closed for you. A project's later or Someday milestone can have
every item under it finished and be meant to stay open, so whether done work
means a shipped milestone is a judgement that stays with the person. A milestone
with nothing filed under it is not reported either: nothing filed is vacuously
complete.
** What migrating changes, and how to undo it
A project behind the current format says so on every command, and the note was
too short to answer the question it raises. The recurring line stays short, but
the write refusal -- reached once, by somebody who now needs to know -- says that
migrating rewrites cairn.toml, may create item files, changes nothing already
in the item directory, and is undone by restoring that one file. cairn migrate --dry-run says the same, counted exactly, and only where it is true: a migration
that rewrote existing items would not be undone as cheaply.
What's Changed
- Work is filed under the grouping type's own name by @oddurs in #82
- Bump the actions group across 1 directory with 10 updates by @dependabot[bot] in #38
- Cleanup and quality run by @oddurs in #83
- Clippy pedantic on, exceptions argued by @oddurs in #84
- A lock that recorded nothing can still be broken by @oddurs in #85
- A write cannot leave the project in a state check rejects by @oddurs in #86
- Close 0124 by @oddurs in #87
- Read the backlog once per bulk change, not twice per item by @oddurs in #88
- A completed change stops reporting itself as a failure by @oddurs in #89
- A finished project no longer looks like it contradicts itself by @oddurs in #90
- File what makes cairn hard to collaborate with by @oddurs in #92
- Record when something was finished by @oddurs in #93
- Tell a clone that the merge driver did not come with it by @oddurs in #94
- A Windows checkout is no longer told its roadmap is stale for ever by @oddurs in #95
- cairn log --range: what a branch did to the backlog by @oddurs in #96
New Contributors
- @dependabot[bot] made their first contribution in #38
Full Changelog: v0.2.1...v0.2.2