Skip to content

Latest commit

 

History

History
192 lines (145 loc) · 9.66 KB

File metadata and controls

192 lines (145 loc) · 9.66 KB

Feature tracking

.docket/features.jsonl records work in progress: its name, declared paths, and actual changes. It sits beside ledger.jsonl and follows the same location rules: a project store under .docket/ when one exists, otherwise the per-repository store under ~/.claude/docket/. docket init moves both files together.

The ledger records what was settled. It does not record which piece of work is underway, what that work intended, or which commits belong to it. The feature store answers those three questions and never writes to the ledger.

Events

Like the ledger, the store is append-only, with one JSON object per line. Docket derives the current state from the whole log.

Event Required fields Effect
start slug, text, paths Creates the feature. Records the fork-point base and the current branch.
amend slug, one changed field Replaces status, paths, intends, include or exclude wholesale.
note slug, text Appends a dated line to the feature's log.
done slug Closes the feature and records the realized change set.
abandon slug, text Closes the feature with no realized change set.

start refuses a slug that already belongs to an open feature.

Status versus state

A feature has a declared status and a derived state. The status is active (the default), paused, or review. done and abandoned are not legal values for status: they arrive only through the done and abandon events, so closing a feature requires a corresponding event in its history.

State resolves in this order: a done event takes precedence over an abandon event, which takes precedence over the declared status.

Slugs are refs, IDs are permanent

A slug behaves like a branch name: unique among open features, reusable once closed. The f-prefixed ID (f1, f2, ...) permanently identifies the feature. docket feature show <slug> resolves the open feature; when only closed runs match, it exits 1 and lists their IDs.

The composite form f7@6f0898e2 appends eight characters of the feature's base SHA. It appears only when two features collide on the same bare ID, such as after a hand-resolved merge.

Commands

docket feature start <slug> --text TEXT --path GLOB [--path GLOB] [--intends TEXT]
docket feature list [--state STATE] [--json]
docket feature show <slug|id> [--json]
docket feature note <slug> TEXT
docket feature amend <slug> [--status STATUS] [--path GLOB] [--intends TEXT] [--include CSV] [--exclude CSV] [--clear FIELD]
docket feature done <slug> [--held CSV] [--failed CSV]
docket feature abandon <slug> --text REASON
docket feature brief [<slug|id>]
docket feature remap [MAPFILE]
docket feature gc [--expire DAYS]
$ docket feature start billing-storage \
    --text "Billing stores invoices in Postgres" \
    --path 'billing/**'
f1 billing-storage

$ docket feature note billing-storage "confirmed invoice storage in an integration test"

$ docket feature amend billing-storage --status paused

$ docket feature list
f1    paused     billing-storage       Billing stores invoices in Postgres

$ docket feature done billing-storage
f1 done: 2 intentional, 1 outside
  outside declared paths: docs/random-note.md

$ docket feature abandon some-other-slug --text "superseded by a different approach"

The fork-point base and the change set

start records base as the merge base with the default branch. This includes earlier branch work when you declare a feature partway through a task. On the default branch itself, base is HEAD and start warns that there is no fork point.

done walks the branch's own commits with git log --first-parent --no-merges -M base..HEAD, then classifies each changed path against the feature's declared paths, using the same matcher docket context uses to select records.

A diff between base and HEAD would also include changes merged from the default branch. Because base is an ancestor of HEAD, base...HEAD gives the same result as base..HEAD. --first-parent follows this branch's history and excludes commits brought in through a merge's second parent. --no-merges excludes merge commits, including any conflict resolution made in them.

Run done before squashing or rebasing the branch: once base is no longer an ancestor of HEAD, done refuses and names the stale SHA.

A declared path must contain a / or a glob character. The shared matcher skips a bare word, so --path Makefile or --path installer would match nothing; start and amend refuse both and tell you to write installer/**.

The brief

docket feature brief [SLUG_OR_ID] finds the ledger records that apply to a feature by matching its declared paths. Paths expand against the files Git tracks, and every ledger record whose scope covers one of those files attaches, strongest match first.

Each attached record's line names why it attached: strength is the same scope score docket context ranks records by, specificity is the length of the matching glob's literal prefix (a record scoped to billing/storage.py outranks one scoped to billing/** even at equal strength), and matches is how many of the feature's files that glob covers. Use these values to inspect the ranking. docket feature amend --exclude ID removes an incorrectly matched record; --include ID attaches one the globs miss.

Both flags replace the whole list. To undo an override rather than change it, run docket feature amend <slug> --clear include (or --clear exclude, or --clear intends). These flags explicitly clear a field; omitting the field from an amendment preserves its value.

For example, a billing feature's brief can attach a database decision scoped to billing/storage.py and an open question scoped to billing/**. Each record shows its ID, text, and ranking values so you can inspect why it was selected.

Blocked

blocked replaces the declared status on feature list and feature show when an attached decision is blocked by its prerequisites. This uses the same prerequisite rules as docket context. An open question in the feature's scope does not block it: sharing a scope does not establish that the work depends on the answer. Set the status to paused when work is waiting on a question.

Claim verification at done

done asks only about claims whose scope intersects the branch's actual changes. Claims about untouched code are outside this check. --held CSV and --failed CSV mark verdicts; anything else attached comes back as unanswered. For each --failed claim, done prints the docket claim ... --supersedes command that would record the correction and stops without writing that record. The three verdict lists are stored on the done event and read back with feature show --json.

Branch convergence

Two active features on different branches may declare overlapping paths. done prints an advisory when another open feature's declared paths overlap the actual changes. The advisory does not change the exit code.

features.jsonl merges through the same driver as the ledger (see Sharing a ledger). The other branch's new events are appended under fresh f IDs, so two branches that both started a feature merge cleanly. Two branches that both opened one slug leave a conflict, because the store cannot hold two open features with one slug.

Every event that names ledger records stores a key for each one: a hash of the record's kind, timestamp, author, session and original text. When a ledger merge renumbers a record the other branch's feature cited, the brief follows the key to the record's new ID. docket check reports each such ID, and docket feature remap writes one amend event per feature that repoints its include and exclude lists. Remapping preserves the original start and amend lines. Events recorded before keys existed have none, and their IDs are taken as written. docket feature remap MAPFILE repoints through the ID map docket rebase --emit-map PATH writes instead.

The briefing header

docket context names the active feature above the record selection: its slug, state, and declared intent, followed by its three highest-ranked attached records. The header takes a reserved share of the same character budget as the record selection, at most a quarter of the total. The remaining budget is available for records. If the repository has no feature store or no Git metadata, or the feature store cannot be read, the briefing omits the header and continues.

When no feature is open on any branch, or the repository has no feature store, and git reports a modified, staged or untracked path outside .docket/ (ignored files do not count), the briefing prints one hint line in the header's place: # feature: none open, and the working tree has uncommitted changes outside .docket/. If that work continues past this session, load the docket-feature skill and start a feature. A clean tree, or changes only under .docket/, prints nothing. Any git failure also prints nothing.

Archival

Run docket feature gc to move closed features' events into .docket/archive/features-<revision>.jsonl. Archiving runs only when invoked; the record count does not trigger it. --expire DAYS limits the selection to features closed more than that many days ago. It does not schedule an archive.

Archiving never frees the ID. A new feature's number starts above the highest ID any archive holds, so a citation to an archived feature keeps pointing at that feature.

$ docket feature gc
docket: archived 4 feature event(s) to .docket/archive/features-9f2ab1c4e8a0.jsonl

docket feature show reads the archive when a slug or ID misses in the live store, so a closed feature stays retrievable after gc moves it.