Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .docket/ledger.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,4 @@
{"schema":2,"kind":"decision","id":"d130","text":"A correction appended with its id already set skips the write-time refusals.","state":"adopted","ts":"2026-09-23T09:15:42+00:00","author":"claude-code","session":"","branch":"feat/ledger-corrections","scope":["docket/ledger.py","docket/corrections.py","docket/rebase.py"],"rationale":"A correction valid on its own branch can be refused against the other branch's corrected state, which leaves a rebase half-applied.","supports":[["d129"]],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"A hand-written numbered correction line bypasses the refusals; validation still bounds its fields.","pinned":false,"choice":"append runs the no-op drop and the echo and question-text refusals only for a correction the CLI submits unnumbered. A pre-numbered correction, as rebase appends, passes on validation alone, the same as a pre-numbered record.","alternatives":["Run the refusals on every appended correction, including rebased ones."],"decided_by":""}
{"schema":2,"kind":"decision","id":"d131","text":"Ledger filtering uses one query language parsed in Python, and the viewer calls the CLI to apply it.","state":"adopted","ts":"2026-09-23T10:15:32+00:00","author":"claude-code","session":"","branch":"main","scope":["graph/**","docket/cli/graph.py","docket/cli/query.py","docket/where.py"],"rationale":"One parser keeps the CLI and viewer in agreement and keeps filtering in Python, where the viewer decision put it. A submitted filter costs one Python start, about 150 ms.","supports":[["d36"]],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Live filtering on field terms would need a Go parser later, and the viewer's layout change moves every key hint users know.","pinned":false,"choice":"docket/where.py parses field:value terms (kind, state, scope, is, author, branch, after, before) plus bare text. list and graph take --where. The viewer sends a submitted query to a hidden docket _filter-ids command and keeps the returned ids; bare words still filter live in Go. The viewer gains a three-line bottom pane for filter, status, and key hints, with a ? help overlay.","alternatives":["Parse the query in both Python and Go, kept in step by a shared fixture table.","Python pre-computes matchable fields and Go runs a reduced matcher.","Toggle keys or a filter form in the viewer instead of a query prompt."],"decided_by":""}
{"schema":2,"kind":"decision","id":"d132","text":"The graph viewer receives its filter callback in the DOCKET_GRAPH_FILTER_CMD environment variable.","state":"adopted","ts":"2026-09-23T14:02:55+00:00","author":"claude-code","session":"","branch":"feat/ledger-filtering","scope":["graph/main.go","docket/cli/graph.py"],"rationale":"The viewer binary ships separately from the CLI. An older binary exits 2 on an unknown flag and ignores an unknown variable, so the variable keeps docket graph working across an update skew.","supports":[["d131"]],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"The callback is invisible in the viewer's --help; anyone running the binary by hand must know the variable.","pinned":false,"choice":"docket graph sets DOCKET_GRAPH_FILTER_CMD to a JSON argv ([python, bin/docket, _filter-ids, --]) for the viewer process; the viewer appends the query as one argument.","alternatives":["Pass the argv as a --filter-cmd flag, as the spec first named it."],"decided_by":""}
{"schema":2,"kind":"decision","id":"d133","text":"A pushed v* tag publishes the release through CI; the local recipe stops at the tag push.","state":"adopted","ts":"2026-09-23T14:57:17+00:00","author":"claude-code","session":"","branch":"ci/tag-release","scope":[".github/workflows/installer.yml","justfile"],"rationale":"Two uploaders built the same assets; the CI upload failed with 'already exists' from v0.19.0 on. One uploader on a clean runner gives reproducible assets and drops the local gh requirement.","supports":[["d120"]],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"docket update offers a version only after the workflow finishes, a few minutes after the push; a failed workflow leaves a pushed tag with no release until it is rerun.","pinned":false,"choice":"just release bumps, tests, commits, tags, and pushes. .github/workflows/installer.yml runs on v* tag pushes: it checks the tag against VERSION, runs the tests, builds the installer and graph binaries, and creates the GitHub release with roll_changelog notes, or replaces the assets of an existing one on a rerun.","alternatives":["Keep building and creating the release locally with gh, and have CI rebuild and re-upload on release published."],"decided_by":""}
32 changes: 23 additions & 9 deletions .github/workflows/installer.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: Installer release binaries
name: Release

on:
release:
types: [published]
push:
tags: ["v*"]
workflow_dispatch:
inputs:
version:
Expand All @@ -24,6 +24,11 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Check the tag against VERSION
if: github.event_name == 'push'
run: |
test "v$(cat VERSION)" = "$GITHUB_REF_NAME" \
|| { echo "tag $GITHUB_REF_NAME does not match VERSION $(cat VERSION)"; exit 1; }
- name: Test native installer
working-directory: installer
run: go test ./...
Expand All @@ -38,21 +43,30 @@ jobs:
run: python3 -m unittest installer/test_bootstrap.py
- name: Build release assets
env:
RELEASE_VERSION: ${{ github.event.release.tag_name || inputs.version }}
RELEASE_VERSION: ${{ github.event_name == 'push' && github.ref_name || inputs.version }}
run: python3 installer/release.py --output-dir dist/installer --version "$RELEASE_VERSION"
- name: Build graph viewer release assets
env:
RELEASE_VERSION: ${{ github.event.release.tag_name || inputs.version }}
RELEASE_VERSION: ${{ github.event_name == 'push' && github.ref_name || inputs.version }}
run: python3 graph/release.py --output-dir dist/graph --version "$RELEASE_VERSION"
- uses: actions/upload-artifact@v4
with:
name: docket-native-binaries
path: |
dist/installer/*
dist/graph/*
- name: Upload assets to the published release
if: github.event_name == 'release'
# docket update reads releases/latest, so the release exists only once
# every asset is attached: a rerun after a partial failure replaces them.
- name: Publish the GitHub release
if: github.event_name == 'push'
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: gh release upload "$RELEASE_TAG" dist/installer/* dist/graph/* --clobber
run: |
version="${GITHUB_REF_NAME#v}"
python3 scripts/roll_changelog.py "$version" --notes > notes.md
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
gh release upload "$GITHUB_REF_NAME" dist/installer/* dist/graph/* --clobber
else
gh release create "$GITHUB_REF_NAME" --title "docket $version" --notes-file notes.md \
--latest --verify-tag dist/installer/* dist/graph/*
fi
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- A pushed `v*` tag now publishes the release. The Release workflow tests the tagged tree, builds the installer and graph viewer binaries, and creates the GitHub release with its changelog notes. `just release` stops after it pushes the tag, and no longer needs `gh`.

## [0.20.0] - 2026-09-23

### Added
Expand Down
29 changes: 11 additions & 18 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ where:
fi
./bin/docket where

# bump, roll the changelog, test, commit, tag, push and publish the GitHub release
# bump, roll the changelog, test, commit, tag, and push; CI publishes the release
[group('release')]
release new:
#!/usr/bin/env bash
Expand Down Expand Up @@ -184,30 +184,23 @@ release new:
git tag -a "v{{new}}" -m "docket {{new}}"
just publish "{{new}}"

# push a tag from `just release`, build its assets, create the GitHub release
# push a tag from `just release`; the Release workflow builds and publishes it
[group('release')]
publish new:
#!/usr/bin/env bash
set -euo pipefail
# Separate from release so a failed publish is one command to retry, and so
# a tag cut on a machine without gh can be published from another. docket
# update reads releases/latest, so a tag alone reaches nobody: v0.16.0 was
# tagged and pushed while every installed copy was still offered 0.15.0.
command -v gh >/dev/null || { echo "gh is not installed; cannot publish v{{new}}"; exit 1; }
# Separate from release so a failed push is one command to retry. The tag
# push triggers .github/workflows/installer.yml, which tests, builds every
# asset on a clean runner, and creates the GitHub release. docket update
# reads releases/latest, so installed copies see the version only once
# that workflow finishes.
git rev-parse -q --verify "refs/tags/v{{new}}" >/dev/null \
|| { echo "no tag v{{new}}; run just release {{new}} first"; exit 1; }
test "$(cat VERSION)" = "{{new}}" \
|| { echo "VERSION says $(cat VERSION), not {{new}}"; exit 1; }
git push origin HEAD --follow-tags
# Built here, not reused from a previous run: an asset must come from the
# tree the tag names, and dist/ survives a checkout.
rm -rf dist
python3 installer/release.py
python3 graph/release.py
notes="$(mktemp)"
trap 'rm -f "$notes"' EXIT
python3 scripts/roll_changelog.py "{{new}}" --notes > "$notes"
gh release create "v{{new}}" --title "docket {{new}}" --notes-file "$notes" --latest \
dist/installer/* dist/graph/*
rm -rf dist
echo "pushed v{{new}}; the Release workflow publishes it"
if command -v gh >/dev/null; then
echo "watch it with: gh run watch \$(gh run list --workflow installer.yml --limit 1 --json databaseId --jq '.[0].databaseId')"
fi
echo "published v{{new}}; docket update now offers it"
Loading