Skip to content

Releases: marcus/tasks

v1.19.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 04:21

Features

  • Find the tasks that carry a link. (#37) GET /api/v1/tasks?link=<url> keeps the tasks whose links — formal, title, or body — include that URL, and composes with scope (archived and all included) and every other filter, so "does this Slack thread already have a task?" is one request instead of paging every task. The CLI twin is tasks links --url <url> (with --all to reach done and archived tasks). Both match through one rule, links.MatchKey: scheme and host case, a leading www., a default port, the fragment, a trailing slash, utm_*/fbclid/gclid, and Slack's thread_ts/cid on a Slack permalink are ignored, percent-escapes are decoded, and the remaining query is compared order-insensitively; a query that addresses a resource (?p=/notes/review.md) still distinguishes links. /meta advertises capabilities.link_filter.
  • Links into local documents can be classified as doc. (#38) The new doc_link_patterns config (whitespace-separated whole-URL patterns, * wildcard — for example doc_link_patterns = http://127.0.0.1:8080/open?p=*) classifies matching links as the doc system ahead of any host row, so tasks links --system doc and the API's links[].system tell "where this task came from" apart from context links without client heuristics. /meta and tasks config publish the patterns. The classifier also names file: URLs and relative .md/.txt paths doc, but stored and extracted links remain http/https only, so today a document link reaches a task through a pattern-matched web URL.

Behavior changes

  • GET /api/v1/tasks/{id} finds an archived task. (#36) Without source, the read now falls back to the archive when no live task has the id, returning the resource with source: "archive", so a client resolving a cited id no longer pages the archived scope. A client that read a 404 there as "this task left the live file" should check source instead. The live task still wins when both share an id, and an explicit source=live or source=archive stays exact. Writes keep refusing archived tasks. /meta advertises capabilities.archive_fallback.

v1.18.0

Choose a tag to compare

@github-actions github-actions released this 30 Sep 21:03

Features

  • The Task resource carries its lifecycle dates, quadrant, and agent readiness. (#34) GET /api/v1/tasks/{id} and every task row now include created (the day from the Captured [YYYY-MM-DD]. note a capture writes — the store keeps no separate creation stamp, so a body edit that removes the note reports null), updated (the UTC instant of the record's stored last-write stamp, null when it has none), archived_on (the day an archived task's subtree was swept, inherited from the stamped subtree root), quadrant (Q1–Q4 from the same classifier and configured urgent_days as tasks quadrants, null for proposed, closed, and archived tasks), and agent_ready (the exact rule scope=agent_ready and tasks list --agent-ready apply, now one shared function). archived: true is unchanged. A client can sort by recently touched and render a detail pane without re-deriving any of these rules.
  • Activate a task over HTTP. (#28) POST /api/v1/tasks/{id}/activate is tasks activate for API clients: no body, mandatory If-Match, the task back with its new ETag, one undo step. It runs the same application operation as the CLI (which now goes through it too), so it keeps a past available-from date, clears a future one, and on a lead or recurring task releases just this occurrence — none of which PATCH {deferred: false, scheduled: null} could express. A web client's defer control now has all three of the TUI's choices. /meta advertises capabilities.activate.
  • Tag and note deltas on PATCH. (#29) add_tags, remove_tags, add_contexts, remove_contexts, and append_body send the store's own deltas — the ones tasks tag and tasks note use — instead of making a client read, edit, and resend the whole list or body. They are refused (422) alongside the fields they edit. A PATCH made only of deltas may send If-Match: *, so a note appended from a web form lands beside an agent's concurrent priority edit instead of failing 412; a real revision keeps its whole-task meaning, and * on anything else — a PATCH that replaces a field, or any other task write (activate, delete, the delegation actions) — is 428. A blank or whitespace-only append_body is refused, as tasks note refuses it. Appending is now a store operation (body_append) composed under the lock, which tasks note also uses. A PATCH that only nulls dates now runs the store's single date-clear operation, the one tasks undate uses. Each delta PATCH's undo step is labelled like the CLI verb it duplicates (note: …, tags: …, remove deadline: …), in GET /history as in tasks history. /meta advertises capabilities.patch_deltas.
  • Writes say what else they changed. (#33) Task write responses carry an optional meta.effects: rolled: {from, to} when completing a recurring task rolled it forward instead of closing it, and touched_ids when the write changed tasks besides the one it returns — a completion that cascaded DONE over open descendants, or a move that carried descendants with it. Both come from the store's own report of the transaction, not from comparing reads. DELETE ?cascade=true now answers 200 with {deleted: [...]} — every removed task, root first, as it stood before the delete, the shape tasks delete --json reports; a plain delete still answers 204.
  • The API routes the manager endpoints. (#25) A web client can now do what the TUI's u, ctrl-r and x do without shelling out. GET /api/v1/history peeks the next undo and redo labels with the store_revision they apply to, and POST /api/v1/history/undo and /redo require that revision back, refusing with 409 conflict (details.reason: stale_store_revision, with the current revision) when any surface wrote in between, so a stale browser tab can never undo someone else's edit; an exhausted journal and an out-of-band edit answer empty and journal_conflict. GET /api/v1/archive-preview lists the roots, descendants and every id a sweep would move, each blocked root with its open children, and a fingerprint; POST /api/v1/archive-sweeps moves them only while that fingerprint still holds, and every refusal carries the CLI's own reason word. The CLI gains the same two steps: tasks history [--json], undo/redo --store-revision REV, tasks archive --dry-run and archive --fingerprint FP, with documents byte-identical to the HTTP data. The CLI and OpenAPI vocabularies are reconciled on the CLI's names ({roots, records, moved_ids}, a blocked row list), undo --json adds store_revision, and a journal conflict now carries reason: journal_conflict. /meta advertises undo, redo and archive_sweep as true.
  • Change detection over HTTP is cheap, and can be pushed. (#26) GET /api/v1/meta answers If-None-Match with 304 and no body, deciding from a digest of the two files without parsing, validating or snapshotting the store, and is served Cache-Control: no-cache so a browser fetch() revalidates on its own. Its ETag is opaque and covers the document as well as the store — the revision joined to a digest of the process's configuration, build and capabilities — so restarting the API under a different zone, date order, urgent_days or link config never answers a stale 304; meta.store_revision stays the plain revision. GET /api/v1/events is live: a Server-Sent Events stream of store.changed frames carrying the new store_revision (also the event id, so a reconnecting EventSource is not told what it already knows), with a first frame on connect, a keep-alive comment when idle, at most eight open streams (503, reason: stream_limit, Retry-After beyond that), and a clean end on client disconnect or server shutdown. capabilities.events is true.
  • The TUI's tabs are readable over HTTP. (#23) GET /api/v1/views/{agenda,next,quadrants,inbox} is now routed. Each returns the full Task resource for every row (revision included) in the view's canonical order, plus a defined groups layout that references those rows by id: the agenda's overdue / today / tomorrow / later day buckets in the server's zone, one next group per @context (a two-context task in both) with (no context), all four quadrants Q1..Q4 with their headings, and the intake tab's two blocks — Approvals in triage order, then the accepted Inbox — each grouped by project in the Projects view's order with unfiled last. include_unavailable=true mirrors the TUI's Z toggle. The selection and grouping rules moved out of the TUI into internal/taskquery, so the tabs and the route are computed by the same functions and a web client no longer ports them. /meta advertises capabilities.views, an unknown view name is a 404 that lists the routed ones, and an unrouted method or path now says "No endpoint matches that method and path." instead of "No task with that id."
  • An outline read, and section actions on every section. (#24) GET /api/v1/views/outline returns the Outline tab as one flat DFS list of nodes — sections and tasks interleaved in file order, each with its shown parent and depth — so a client can rebuild the tree exactly, including where a project's tasks sit relative to its sub-sections. Section nodes carry the section's role, note, lifecycle and task tallies (shown, open, closed, and the · N closed count the toggle is hiding); task nodes carry full Task resources; include_closed=true mirrors the TUI's C toggle, and proposals stay out as they do in the tab. GET /api/v1/sections now carries kind, body, state and closed too. The project action routes — rename, complete, drop, reopen, archive — accept any section the Outline shows, not only what /projects lists, so a nested sub-section or an area with no open work is no longer a 404 over HTTP while the TUI can act on it; the Inbox and the Projects heading refuse rename, close and archive with a 409 instead, and reopen always works. That refusal lives in the application layer, so the TUI's outline rows now say why instead of renaming or closing either one, and a section action refused because the store is locked or invalid says so rather than reporting the project gone. /meta advertises capabilities.outline.
  • Friendly dates parse over HTTP without a write. (#27) GET /api/v1/dates/parse?input=fri%204pm reads date text with the same parser the TUI's date fields use — friendly date, optional wall time, optional trailing zone word (Europe/Berlin, UTC in any case, floating) and fold=later — and answers {"input","date","time":{"local","timezone","fold"}|null,"human"}, where time is exactly the object a PATCH/POST takes as deadline_time/scheduled_time. Text that is not a date is a 200 with {"input","error"}, like /recurrence/explain. /recurrence/explain gains anchor=YYYY-MM-DD to project from a task's own stamp instead of today, and GET /api/v1/lead/explain?input=3%20weeks&anchor=2026-11-01 previews a lead span (canonical, human, and the opens date) before any write. /meta publishes date_order. The CLI twins are tasks due --explain "<text>" (also schedule --explain) and tasks lead --explain "<span>" [--anchor <date>], whose --json is the HTTP data member. The zone/fold word grammar moved out of the TUI into temporal.ParseText, which the CLI's dated verbs now use as well, so tasks due <ref> "fri 4pm Europe/Berlin" works and a zone or fold named both as a flag and as a word is refused. /meta advertises capabilities.date_parse and capabilities.lead_explain. The TUI's date fields now honor the configured date_order — they read 10/2 as October 2 regardless of it before, and mistook a numeric date like 7/15 for a zone name.
  • Link shorthands and title-URL lifting work on every surface. (#30) Expanding a configured link.<name> shorthand (jira:OPS-1234) and lifting a trailing URL out of a capture title into a formal link used to be `tasks c...
Read more

v1.17.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 21:34

Features

  • A project has an open/closed lifecycle of its own. (#22) Closing a project used to close its tasks and leave the section itself looking open forever. tasks project complete <ref> now stamps the section DONE with today's date in the same write that cascades DONE over its open descendants, and two new verbs finish the cycle: project drop (alias project cancel) does the same with CANCELLED, and project reopen clears state and closed date from the section while leaving its tasks untouched. The lifecycle is open, then closed, then archived, each step explicit: a closed project stays in the live file until project archive sweeps it. tasks projects excludes closed projects by default, --closed selects only them, --all shows both, and a closed project is never reported as stuck. --json carries state and closed when present, and a project ref resolves against closed projects too. Over the API, GET /api/v1/projects takes ?closed=exclude|include|only, and POST /api/v1/projects/{id}/drop and /reopen join /complete. In the TUI, the Outline and the Projects tab hide closed projects transparently, so depth is unchanged and an open descendant hoists through a closed parent, and the existing session-only C toggle reveals them in place with the same muted styling closed tasks use. The detail rail now names the task's project in every view.

v1.16.0

Choose a tag to compare

@github-actions github-actions released this 28 Aug 02:04

Features

  • Delegation stamps read in your timezone. (#17) Every human-facing print of delegation.at — tasks show's (since …), the TUI detail pane, and the claim-conflict message — now renders in the configured timezone and time_format (thu 08-27 5:46p, 17:46 under 24-hour) instead of the raw UTC stamp. The year appears only when it is not the reader's own, so an old handoff can't masquerade as yesterday's. Storage, --json, and the API keep the exact UTC instant, and a stamp the validator would reject prints as stored rather than being laundered into a healthy-looking local time. The 12/24 rule now has a single implementation (temporal.ClockLabel) shared by tasks list.
  • The Outline hides finished work behind C. (#18) DONE and CANCELLED rows no longer bury the tree: the Outline shows open work by default, and C (palette: toggle closed view) reveals the closed rows in place with their existing styling. Hidden rows are transparent — an open child hoists through a hidden closed parent — and every trace stays honest: section badges read 4 · 1 closed, a search or store whose matches are all closed says N closed hidden · C shows instead of rendering blank, and malformed states stay visible rather than being miscounted as closed. Reordering and indenting now anchor on the nearest visible sibling, so a keypress can never rewrite the file without moving anything on screen. PROPOSED stays excluded, Z composes independently, and the toggle is session-only.
  • Projects can show what already shipped. (#19) The same C toggle on the Projects tab includes DONE and CANCELLED children under their project. Toggled on, the stored tree is shown as-is — a closed parent appears with its open child nested beneath it instead of being pruned and hoisted — and a project whose only live children are closed becomes a real foldable heading rather than a line on the dormant tail. A finished project drops the ⚠ stuck warning, badges count what the current filter shows with a muted · N closed leftover, and unfiled work and swept archive.jsonl history stay out. Default off: the open-only view, hoisting and all, is unchanged.

v1.15.0

Choose a tag to compare

@github-actions github-actions released this 26 Aug 18:36

Features

  • N marks the selected task NEXT. (#16) One keypress in the list or detail view now does what previously took the edit form or tasks state <ref> NEXT: the selected open task becomes a GTD next action and appears on the Next tab, in a single undo step. The key sits in the ? help and the : palette, refuses politely on proposed and done tasks, and is a silent no-op when the task is already NEXT. Lowercase n (modal cancel) is untouched, and pressing N inside any modal, filter, or form field types or cancels as before — it can never mutate state from an input context.
  • An empty Next tab explains itself. (#15) When nothing is marked NEXT, the tab no longer renders a blank pane that looks broken next to a full Agenda. It now says there are no explicit next actions, how many dated items are waiting on Agenda (the count comes from the same query the Agenda tab paints, so the two can never disagree), and how to mark one — N, or tasks state <ref> NEXT. The CLI matches: tasks next with no next actions prints a one-line message instead of silence, mentioning Agenda only when dated work actually exists there. tasks next --json still prints [], unchanged.
  • The Inbox tab groups intake by project. (#13) Approvals and accepted Inbox rows are now bucketed under project headings inside their sections, so a triage pass stays inside one theme instead of hopping between projects in file order. Group order follows the Projects tab's sequence, unfiled one-offs collect in a single trailing group, empty groups vanish, and headings are chrome — unselectable, invisible to the a/r approve-reject walk, and counted by task (the section badge's own rule) so folding a subtree never shrinks a heading. Within a group, approvals keep their priority-then-due triage order. CLI tasks inbox stays flat.
  • tasks move files proposals. (#14) A PROPOSED task can now be moved into a project — positional section, --under, --before, or --top — instead of the old reject-and-re-propose dance that minted a new id and polluted rejection history. Filing is a location change, not a decision: the task stays PROPOSED, and destination refs may name a proposal only when the moving subtree is itself proposed, so accepted work still can't nest under an undecided proposal. This matches what PATCH /api/v1/tasks/{id} always allowed; the API is unchanged.

v1.14.0

Choose a tag to compare

@github-actions github-actions released this 25 Aug 03:37

Features

  • The TUI's modals adopt Sidecar's design language. The delegate modal is the first surface rewritten on the new chrome primitives: boxed form fields instead of bare lines, buttons as real interactive surfaces, a fixed footer that stays put as content scrolls, and full scrollbars whose thumb shows where the window sits in a long note. The chrome is built from named theme slots (internal/tui/term/theme), so an embedding host — Sidecar today, anything else later — projects its own palette into them through the existing pkg/tui.ThemeOptions seam rather than having colors baked into the renderer.
  • Scrollbars are draggable. Grab the thumb and it tracks the pointer, preserving the offset you grabbed it at; click anywhere on the track to jump there. The math is state-free and ported from Sidecar's implementation, so the two behave the same way under the same gesture.

Bug Fixes

  • Clicking just left of a button no longer invokes it. Button hit spans were computed one cell left of where the buttons were painted, so the blank column beside a button was live — on the delegate modal that meant Release and Undelegate could fire from a click that visually missed them. At narrow widths the clamp was applied to already-shifted coordinates, so a button truncated off the screen entirely stayed clickable, which is precisely the case the clamp exists to prevent.
  • The ctrl-s hint names what ctrl-s does. The chip read "newline in note" while ctrl-s submits and Return is what inserts a newline, so following the on-screen hint to break a line delegated the task instead.
  • An upward scrollbar jump lands where you clicked. ScrollToRow always parked the caret on the window's bottom edge, but the renderer derives the offset from whichever edge the caret crossed, so jumping upward settled height-1 rows low and stayed there.

v1.13.0

Choose a tag to compare

@github-actions github-actions released this 22 Aug 23:24

Dependencies

  • Tasks now builds with Go 1.27. encoding/json/v2 is the default JSON
    implementation in this release; Tasks' stored task data, config, and API
    payloads round-trip unchanged, and GOEXPERIMENT=nojsonv2 remains as an
    escape hatch. The testdata/external-tui-consumer module moves with it, so
    the embedded-host contract keeps compiling against the same directive as the
    library it consumes.
  • This release exists so the td / Tasks / Sidecar trio share one toolchain:
    Sidecar resolves its CI Go version from its own go.mod, so it can only pin
    a Tasks that declares a directive its toolchain already satisfies.

v1.12.0

Choose a tag to compare

@github-actions github-actions released this 20 Aug 14:11
  • Embedded hosts can compose Tasks' list and detail regions into their own
    focus rings.
    The public pkg/tui API now exposes stable spatial-focus IDs,
    the exact rectangles from the layout Tasks renders and hit-tests, the current
    region, direct focus control, and TabOwnsFocus. The README documents the
    Sidecar capability check and the boundary between spatial focus and Tasks'
    existing interaction contexts.
  • Inputs and overlays keep ownership of Tab, including field modals, while
    passive list/detail and response views can yield it to an outer host. Compact
    layouts expose only positive rendered focus stops; hidden, unknown, or
    input-covered targets refuse direct focus without changing selection. Keys
    and clicks follow the same spatial focus, and project-detail panels retain
    list command routing so task-detail actions cannot run against a project row.

v1.11.0

Choose a tag to compare

@github-actions github-actions released this 19 Aug 04:56
  • D is a three-part modal instead of a one-line prompt. Delegating now
    asks the three questions a delegation actually has — who (a person or the
    agent pool), which mode, and the note the receiver works from — on a modal
    that is complete by keyboard and by mouse. Re-delegating prefills all three
    from the current marker, and one submit writes them in a single undo step.
    Release and undelegate are explicit affordances, and both confirm before they
    act, which retires the old flat word grammar: implement,
    release and the clear words used to share one text field, so the shortest
    inputs performed the widest and most destructive actions and a three-letter
    prefix minimum was the only thing standing between a keystroke and a revoked
    claim. Typing a verb into a text box is no longer how work is taken away.

  • The three-part delegation reaches the CLI and HTTP. tasks delegate <ref> <mode> --note "…" writes who, in what mode, and the briefing the receiver
    reads in ONE store write and therefore one undo step — no surface composes a
    delegation out of a delegate followed by a note write. --note-file <path>
    and --note-file - (stdin) exist because a long briefing written by an agent
    should not have to fight shell quoting. A mode is now accepted alongside
    --to <email>, so a person can be asked for a refine just as an agent can.
    Omitting the note keeps any existing briefing — restating the mode must not
    silently erase instructions — and --note off/none/"" clears it, the
    same two words a work reference clears with and both already reserved mode
    names, so a clear instruction can never be read as a mode. tasks delegate <ref> --note "…" with no mode and no --to rewrites the briefing in place,
    for an owner correcting instructions without re-sending a delegation.

  • Delegation reads show all three parts. show prints the mode and the
    briefing in full, list --delegated and list --agent-ready print the mode
    in the headline with a one-line preview of the note beneath it, and --json
    carries both as first-class members of the delegation object everywhere —
    including list --agent-ready --json, which is what an agent heartbeat
    reads.

  • The HTTP delegation writes work. POST /api/v1/tasks/{id}/{delegate, undelegate,claim,release}, PUT …/work_ref, and the new PUT …/delegation_note answered 501 because the mandatory If-Match could not
    be honoured — the store did not compare a revision inside its delegation
    transaction, and dropping a precondition a client set is worse than refusing.
    It does now, under the same lock the write runs in, so the routes perform the
    operation with their preconditions intact: 200 with the whole canonical
    task and a fresh ETag, 412 on a stale revision, and 409 on a lost claim
    race carrying holder and at as their own fields rather than as prose an
    agent would have to re-parse.

  • Answer a proposal for work that is already done in one keystroke. On a
    PROPOSED row c now approves the proposal AND completes the accepted task in
    a single write and a single undo step, so undo restores PROPOSED exactly;
    rejecting was previously the only one-key way to clear such a row, and it
    recorded a decline — the wrong history, and the wrong signal to whoever
    proposed the task. The store still never completes a PROPOSED task: the
    approval lands first inside the same transaction. tasks approve <ref> --done and POST /api/v1/tasks/{id}/approve?complete=true are the CLI and
    HTTP spellings of the same shared command, both --json/ETag-honest. The
    proposal row's key hints now describe what the keys actually do.

  • Refuse a recurring proposal in check. Recurrence is a schedule for accepted
    work — completing a recurring task rolls it forward instead of finishing it —
    and no write path could ever produce the shape, but a hand-edited, repaired,
    or foreign-device file could, and it passed validation. Now recur on a
    PROPOSED task is a check error, and approve+complete refuses it rather
    than reporting a DONE that did not happen.

  • A delegation becomes three orthogonal parts — who (a person or the agent
    pool), mode (refine/research/implement), and a new optional
    note, free text briefing the receiver on how to work on the task and
    where the work should land, up to 2000 characters with paragraphs allowed.
    Store and schema support only in this entry; the CLI, HTTP, and TUI
    spellings follow in the surface work.
    The note is stored last in the marker,
    so a store written by an earlier release loads, checks clean, and re-emits
    byte for byte. A mode is now valid on a human delegation as well: "Pat,
    this is a refine" is a thing the data model can say. The note travels with
    the delegation it briefs — kept across a mode change or a new assignee of the
    same kind, dropped when a person replaces the pool or the reverse — carries
    its own transition stamp on an unclaimed delegation so the most recent
    briefing wins a multi-device merge (a note on a live claim leaves the claim's
    own stamp alone, so briefing a working agent survives a sync), and merges
    atomically with the rest of the marker, where undelegate still always wins.

  • The mode vocabulary is carried as a value on the store and checker options
    rather than checked against a literal, so configuring the set later is one
    field, not a hunt through the code.

  • The delegation mode vocabulary is yours to choose. delegation_modes = triage, ship in ~/.config/tasks/config (or TASKS_DELEGATION_MODES)
    replaces refine, research, implement everywhere at once: what tasks delegate <ref> <mode> accepts, what a refusal quotes back, the delegate
    usage line, tasks help, the TUI ? overlay and delegate modal, the check view,
    and the API's vocabulary document. A mode is a bare lowercase word and
    carries no label — the label is the word. With no config key, behaviour is
    exactly what it was.

  • A delegation_modes list this binary cannot honour — a word of the wrong
    shape, a duplicate, a reserved word, an empty list — is ignored whole, with
    a warning on stderr naming the problem and the set actually in use. Keeping the readable
    half would run you against a vocabulary you never wrote. Nothing about a bad
    list makes the store unreadable or unwritable.

  • A record whose mode the active vocabulary does not list still loads, shows,
    and checks: check reports it as a WARNING, never an error. Which words are
    listed is a setting, and settings change — editing delegation_modes, or
    syncing a file from a machine configured differently, can no longer invalidate
    a file your tasks live in. A stored mode of the wrong shape is still an
    error, and writing an unlisted mode is still refused.

  • tasks config and tasks config --json report the resolved
    delegation_modes and where they came from, alongside every other setting.

  • off, none, clear, and release are reserved and cannot be configured as
    modes: release is a delegation verb and the others clear a work reference or
    a note, so a mode spelled like one of them would make an instruction and a
    mode indistinguishable wherever both are written in one place. The collision
    is reported when the config is read, not at the moment the verb is needed.

  • Hosts embedding the TUI through pkg/tui get ExportCommandsWith(modes) to
    describe delegation with their own store's vocabulary; plain ExportCommands
    now renders the built-in set rather than an unfilled template.

v1.10.0

Choose a tag to compare

@github-actions github-actions released this 18 Aug 19:07
  • Accept the relative dates people actually type. Anywhere a date is taken —
    capture, propose, due, schedule, defer, the TUI edit form, and the
    API — two weeks, in two weeks, and two weeks from now all work, in
    digits or English number words, over seconds, minutes, hours, days, weeks,
    months, or years. Calendar units also take compact forms: 2d, 2w, 2m
    (months), 2y. Second-, minute-, and hour-relative phrases produce a timed
    value from the current instant; since stored times have minute precision, a
    result carrying seconds rounds up so it never lands earlier than the
    boundary that was asked for. Such a phrase already names an exact side of a
    DST overlap, so pairing it with --fold is refused rather than silently
    resolved. next week/next month/next year keep working as the one-unit
    spellings.
  • Make releasing one command: BUMP=major|minor|patch make release derives the
    version from the latest tag, stamps ## [Unreleased] with the version and
    date, commits release: prepare vX.Y.Z, pushes main, and publishes — the
    version is stated once. RELEASE_VERSION=vX.Y.Z make release and a hand-
    stamped heading still work; make release-dry-run prints the plan, verifies
    the Homebrew publication prerequisites, and stops before any mutation. Every
    existing fail-closed publication check is unchanged.