Skip to content

feat: write-through file cache for external readers - #18

Open
skomae wants to merge 1 commit into
masterfrom
worktree-cache4wut
Open

feat: write-through file cache for external readers#18
skomae wants to merge 1 commit into
masterfrom
worktree-cache4wut

Conversation

@skomae

@skomae skomae commented Jul 31, 2026

Copy link
Copy Markdown
Owner

What

Adds an opt-in, plain-YAML file cache that fjira writes as a side effect of normal use, so external tools can read issue state without hitting Jira. Covers the first three writer work items of the cache contract: cache-dir resolution, config keys, the cache store package, and API write-through.

How

  • internal/cache (new): envelope types (v/kind/workspace/fetchedAt), issue/search/children entry schemas, atomic temp-file+rename writes, replace-in-place (one file per entity, named <id>.<local-date>.yaml, older dates unlinked), JQL-hash filenames (first 16 hex of SHA-256 over the exact query bytes, pinned by test vector), TTL parser (3w/10d/36h), and an mtime-based reaper.
  • Write-through decorator: cache.WrapApi wraps jira.Api at the single fetch choke point — GetIssueDetailedissue/, SearchJqlsearch/, and the epic-children query (parent = "KEY" ORDER BY key ASC) → children/<KEY>. SearchJqlPageable caches page 0 only so later pages can't clobber the snapshot with a mid-list slice. Per-keystroke fuzzy Search() is deliberately not cached. Cache writes are best-effort: they never fail a fetch, and fjira never reads the cache.
  • Config: manage.cache.files (default false) / manage.cache.expires (default 3w) in fjira.yaml, with FJIRA_CACHE_FILES / FJIRA_CACHE_EXPIRES env overrides. The manage: block lives on the Settings struct so whole-file rewrites (e.g. issue-filter persistence) preserve a hand-added block — tested.
  • internal/os: MustGetFjiraCacheDir()$XDG_CACHE_HOME/fjira, falling back to ~/.cache/fjira; deterministic from env alone (no existence probing) so external readers resolve the identical path.
  • internal/jira: capture Jira Cloud's accountType on User, mapped to authorType: human|bot on cached comments (empty when not derivable, e.g. Jira Server).

Testing

  • Unit tests for hash pinning, atomic write + replace-in-place (incl. shared-prefix keys like TEST-1 vs TEST-12), reaper, entry mapping, decorator routing, and the disabled-by-default no-op path.
  • Wiring test drives wrapApiWithCacheWrite against a mock HTTP Jira and asserts the file lands under <cache>/fjira/<workspace>/issue/.
  • go test ./internal/... green; go vet + gofmt clean. (cmd/fjira-cli/commands tests fail without a tty — pre-existing on master, unrelated.)

Notes

  • With caching off (the default), the cache directory is never created and the API is returned untouched.
  • No tokens or credentials are ever written to the cache.
  • Later work items (headless --json subcommands, lookup/ id table) are deliberately out of scope for this PR.

🤖 Generated with Claude Code

Mirror every fetch into a plain-YAML cache under $XDG_CACHE_HOME/fjira/
(~/.cache/fjira/) so external tools can read issue state without hitting
Jira. Off by default; enabled via manage.cache.files in fjira.yaml or
FJIRA_CACHE_FILES=1, with a writer-side TTL (manage.cache.expires /
FJIRA_CACHE_EXPIRES, default 3w) reaped on startup.

- internal/cache: envelope + issue/search/children entry types, atomic
  temp+rename writes, replace-in-place (one file per entity, dated by
  local fetch day), JQL-hash filenames (first 16 hex of SHA-256 over the
  exact query bytes), TTL reaper, and a jira.Api decorator that caches
  GetIssueDetailed, SearchJql, and page 0 of SearchJqlPageable; the
  epic-children query lands under children/<KEY>
- internal/os: MustGetFjiraCacheDir (XDG_CACHE_HOME, ~/.cache fallback)
- internal/workspaces: manage.cache settings block (survives fjira.yaml
  rewrites) + env overrides
- internal/jira: capture cloud accountType on User, mapped to the cache
  comments' authorType (human/bot)

Cache writes are best-effort and never fail or serve a fetch; fjira
stays live-only. No credentials are ever written.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant