Skip to content

Releases: ericmann/journal

v3.0.3

Choose a tag to compare

@github-actions github-actions released this 02 Jul 19:53

Added

  • Detached journal log failures now leave a readable trail. The
    recording daemon and capture pipeline run with stdout/stderr on
    /dev/null, so a transcription, land, or recording failure was previously
    undiagnosable after the fact beyond a generic notification. Failures now
    append a timestamped stage\terror line to
    <log.audio.tmp_dir>/pipeline.log (trivially truncated past 1MB), and the
    ✕ journal log failed notification includes the underlying error text
    (truncated for readability) plus the log path. See
    DATA-FLOWS.md and USAGE.md.

v3.0.2

Choose a tag to compare

@github-actions github-actions released this 02 Jul 19:03

Added

  • GUI launchers can now find the journal without shell environment. Two
    changes close the class of silent failures where a Hammerspoon hotkey or
    Claude Desktop MCP config ran journal in a launchd environment (no
    ~/.zshrc exports, minimal PATH) and died invisibly:
    • Global default_repojournal config set-default-repo <path>
      records the repo in ~/.config/journal/config.yaml (XDG-aware); repo
      resolution falls back to it when no --journal-dir flag, $JOURNAL_DIR,
      or enclosing .journal resolves. Explicit flag/env still wins and still
      fails loudly; the "not inside a journal repo" error now names every
      resolution method. journal config shows the current setting. Applies
      to journal mcp too — Claude Desktop configs no longer need env blocks
      for the single-repo case.
    • Homebrew fallback for external tools (internal/toolpath) — ffmpeg,
      whisper-cli/whisper-cpp/whisper, and terminal-notifier now resolve via
      PATH or well-known install locations (/opt/homebrew/bin,
      /usr/local/bin, Linuxbrew), so GUI-launched recordings work without
      PATH gymnastics. PATH hits always win.

v3.0.1

Choose a tag to compare

@github-actions github-actions released this 02 Jul 17:51

Fixed

  • journal models pull now actually provisions the model journal log
    needs.
    The pull command's transcriber slot downloads a faster-whisper
    (CTranslate2) model for the WhisperX meeting pipeline — an artifact the
    whisper.cpp backend behind journal log <audio.wav> cannot read — yet every
    missing-model error and doctor hint said "run journal models pull", which
    succeeded while fixing nothing. A new log_transcriber pull slot (config
    log.transcriber.{model_id,revision,checksum}, default
    ggerganov/whisper.cpp) fetches the ggml artifact (ggml-<model>.bin), and
    model resolution — shared by journal log, its record-start preflight, and
    journal doctor — now checks the manual-install path
    (<model_dir>/<model>.bin, which always wins) and the pulled layout.
    models list/verify/MODELS.md cover the log model too. Set
    log.transcriber.model_id: "" to keep manual installs authoritative.
  • journal doctor no longer reports failed optional checks as [ok ].
    v3.0.0's audio-toolchain checks (and the older reranker/Quill informational
    checks) printed failure text — "whisper.cpp binary not found", "model missing
    — run journal models pull" — under an [ok ] mark and still concluded
    "all checks passed". Optional-but-broken checks now render as [warn], the
    summary reads "all required checks passed — N warning(s) above", and the
    --json report gains a per-check warn field (additive; ok semantics and
    the exit code are unchanged — warnings still never fail doctor).

v3.0.0

Choose a tag to compare

@github-actions github-actions released this 01 Jul 22:53

Added

  • Linux recording support for journal log. The mic-recording toggle
    (bare journal log) now works on Linux, not just macOS: it captures via
    ffmpeg -f pulse by default, or -f alsa on ALSA-only boxes (set
    log.audio.backend: alsa), selected automatically by OS
    (log.audio.backend: "", the default) with an explicit override available.
    Desktop notifications on Linux go through notify-send, degrading silently
    (same as macOS's osascript/terminal-notifier) when it's absent. An
    unsupported platform (anything other than macOS/Linux) fails fast on the
    starting press, before the recording daemon spawns. macOS behavior
    (avfoundation, osascript/terminal-notifier) is unchanged.
  • log.audio.{silence_duration,silence_noise_db} config keys. The
    silence_autostop watchdog's silence-interval length (default 30 seconds)
    and silencedetect noise floor (default -35 dB) are now configurable
    instead of hardcoded, so a noisy room or a different mic can tune when the
    safety-net stop fires. Existing behavior is unchanged unless overridden.
  • Meeting pipeline migrated onto the shared land/index core (Phase 5c).
    journal quill-sync and journal transcribe now write and index through the
    same internal/log.Land/IndexTranscript primitives the voice-note pipeline
    (journal log) uses, replacing bespoke os.WriteFile glue in each command.
    Frontmatter (source: quill/source: whisperx), filenames, and chunking
    behavior are byte-identical to before — this is an internal refactor, not a
    behavior change. journal models pull gains a second, independently-optional
    gated model slot (diarization.*, same shape as transcriber.*) so the
    meeting pipeline's pyannote diarization model can be provisioned as a fast
    credential preflight — see docs/TRANSCRIBE.md and
    docs/CONFIGURATION.md. diarization.model_id is
    empty (disabled) by default; journal models pull skips it entirely until
    configured, and attempts every configured model even if one fails, exiting
    non-zero only if any of them did. internal/models.Pull gained a
    PullFile variant supporting a configurable remote/local filename (needed
    since pyannote's repo has no model.bin) — Pull is now a thin wrapper
    around it with no behavior change for existing whisper-model call sites.
  • journal models pull gated/HuggingFace-token path (Phase 5b). Extends
    Phase 2a's model provisioning with support for gated HuggingFace repos (the
    first being pyannote's speaker-diarization models, arriving with the meeting
    pipeline migration). Set transcriber.gated: true and transcriber.accept_url
    in config; pull sends HF_TOKEN (read from the environment only, never
    config) as a Bearer token. A gated model with no/invalid token fails with an
    explicit "accept terms at <url>, set HF_TOKEN" message instead of a raw
    401. MODELS.md now records each installed model's gated status and
    acceptance-page link. Ungated pulls are unaffected — no token, no behavior
    change.
  • MCP tools journal_log_text / journal_log_audio (Phase 5a). The MCP server
    (journal mcp) now exposes the journal log --text and journal log <audio.wav>
    pipelines as tools: journal_log_text(text) runs shape→assemble→land→index,
    journal_log_audio(audio_path) runs transcribe→shape→assemble→land→index for a
    server-local audio file. Both return {path, title, landed} and honor
    local_only/local_only_mcp exactly like the rest of the MCP surface. The
    mic-recording stage stays CLI-only and is never exposed over MCP — an MCP server
    must not seize the user's microphone.
  • Start/finish desktop notifications for journal log (Phase 4, macOS only).
    The recording toggle now pairs the existing terminal output with a real desktop
    notification: starting a recording pops "● recording", and the async pipeline
    landing the note pops "✓ logged: <title>" with the note's relative path. Sent
    via osascript (display notification), falling back to terminal-notifier if
    osascript is unavailable; a missing/failing notifier degrades silently (logged,
    not surfaced) and never blocks or fails the recording or pipeline.
  • internal/audio.Notifier: an injectable desktop-notification boundary
    (DefaultNotifier for production, FakeNotifier for tests) mirroring the
    existing Recorder pattern, so tests never pop a real OS notification.
  • Hammerspoon hotkey binding documented (docs/USAGE.md) — an init.lua
    snippet binding a single hotkey to bare journal log, plus an optional
    menubar-dot add-on, so the recording toggle is fully usable from one key press.
  • journal log recording toggle — mic capture (Phase 3, macOS only). The bare
    journal log command now toggles mic recording: the first press starts a detached
    background recorder (ffmpeg -f avfoundation, 16 kHz/mono/16-bit PCM) and prints
    "● recording"; the second press stops it and hands the finalized WAV off to the
    existing transcribe→shape→assemble→land→index pipeline asynchronously, so both
    presses return immediately. --start/--stop/--cancel/--status give explicit
    control (--cancel discards the recording — no note is produced). The toggle state
    lives in a lockfile ($XDG_RUNTIME_DIR/journal-log.lock or
    <tmp>/journal-log/journal-log.lock) holding {pid, wav_path, started_at}; a dead
    PID is detected and cleaned up automatically on the next press. log.audio.max_duration
    (default 900s) self-finalizes a long recording; log.audio.silence_autostop (default
    off) is an optional safety-net stop after sustained silence. The recorded WAV is
    deleted after a successful run unless log.audio.keep_wav: true, in which case its
    path is recorded in the landed note's audio: frontmatter field. A WAV passed
    directly (journal log <file>.wav) is never auto-deleted, regardless of this
    setting — only recorder-produced scratch files are.
  • internal/audio package: lockfile primitives (ReadLock/WriteLock/RemoveLock,
    injectable PIDAlive) and an injectable Recorder interface (FfmpegRecorder for
    production, FakeRecorder for tests) so the mic/ffmpeg dependency never runs in tests.
  • log.audio.{tmp_dir,max_duration,silence_autostop,keep_wav} config keys and the
    LogAudioTmpDirAbs() config accessor, additive to the existing log.audio.{device, sample_rate,channels} keys.
  • journal log <audio.wav> — transcribe stage (Phase 2b). Pass a WAV file as
    a positional argument to transcribe it locally via whisper.cpp and run the full
    transcribe→shape→assemble→land→index pipeline. No network access: a missing model
    fails fast with "run journal models pull". Silent recordings skip the pipeline;
    transcription errors are retryable (WAV is kept). The transcriber frontmatter
    records the backend/model used; duration_sec is derived from the WAV header.
  • log.transcriber.{backend,model,model_dir} config keys replace the previous
    stub log.transcriber.{engine,model} keys. backend selects the engine
    (whisper.cpp, the only built-in); model_dir defaults to the same path as
    transcriber.model_dir so a single journal models pull serves both paths.
  • LogTranscriberModelDirAbs() helper on *config.Config mirrors
    TranscriberModelDirAbs() for the log transcriber path.
  • internal/log.Transcriber interface with a FakeTranscriber for tests and a
    WhisperCPP default implementation. The boundary is injectable so commands never
    exec a real binary in tests.
  • journal log --text "..." — voice note capture (Phase 1). The full
    shape→assemble→land→index pipeline is now available for typed text. The LLM
    shaping step (configurable via log.shaping.enabled) cleans disfluencies,
    generates a title and summary, extracts @todo/@decision/@question markers,
    and tags the note. Notes always land to logs/YYYY-MM-DD-HHMM-<slug>.md even
    when shaping is unavailable (raw fallback). Index failure is non-fatal.
  • SourceVoice = "voice" source constant in internal/store; voice chunks
    are indexed separately from notes and transcripts. No schema migration required —
    the source TEXT column already accepts any value.
  • journal search --source voice (aliases log/logs) scopes search results
    to voice-note chunks.
  • log: config namespace with shaping, landing, audio, and transcriber
    keys; LogAbsPath()/LogRelPath() helpers mirror TranscriptsAbsPath().

Fixed

  • journal log hotkey/mic-toggle no longer fails silently. When the
    background transcribe→land pipeline can't run (most commonly whisper.cpp or the
    model isn't installed), the detached pipeline process has no visible stdout, so
    the only feedback the user gets is a desktop notification — and previously only
    the success notification (✓ logged) ever fired. journal log now pops a
    ✕ journal log failed notification naming the retained WAV to retry with, and a
    low-key "empty recording — nothing to log" notification when a recording
    transcribes to silence, so a hotkey press always resolves to a visible outcome.

v2.7.1

Choose a tag to compare

@github-actions github-actions released this 25 Jun 20:11

Added

  • journal dismiss — bulk-dismiss open todos in one commit. A new dismiss
    subcommand selects all open @todos matching a --project filter, an
    --before/--older-than age window, or both, and rewrites each to @done YYYY-MM-DD (same rewrite as journal done). Requires explicit confirmation
    (--yes or an interactive y prompt). All file edits and re-indexing land in a
    single auto-commit whose message records the filter used (e.g. dismissed 5 todo(s) (project=acme, before=4w)). The optional --resolution flag appends a
    Resolution: line to each dismissed block. Single journal done behavior is
    unchanged.

Fixed

  • journal today now aggregates all of a day's notes, not just the daily file.
    The Notes section previously read only daily/YYYY/MM/YYYY-MM-DD.md, so a day
    with notes captured via --project (or any project note) was reported empty.
    It now queries the index for all note chunks with source=note and
    created_at >= midnight, grouping project note sections with a source label.
    Meeting transcripts are excluded from Notes and continue to appear under
    Today's meetings. The MCP today tool inherits the fix (it routes through
    gatherToday). The journal://today MCP resource is unchanged — it remains
    the literal daily file; the aggregated view is the today tool.

  • Embed retry now rides through transient Ollama runner restarts. The retry
    window for transient embed-runner crashes (400 with EOF / do embedding request body — a known llama.cpp/Metal SIGTRAP flake on Apple Silicon) has
    been extended from ~1.4 s to a 45 s wall-clock budget, sized to outlast a
    model reload. Backoff is exponential (n²×100 ms), capped per-sleep at 5 s,
    and jittered ±25%. Transport-level dial failures (ErrUnreachable) still fail
    fast via maxRetries; non-retryable 4xx (model not found, bad dimensions)
    still fail immediately.

v2.7.0

Choose a tag to compare

@github-actions github-actions released this 19 Jun 20:55
d46696a

This release rounds out the MCP surface (the agent-facing side of journal),
makes decisions & todos first-class, and adds project tooling — a docs site,
a curated release pipeline, and the agentic-workflow layer.

Added

  • MCP synth tool. The MCP server now exposes synthesis as a synth tool so
    MCP clients (e.g. an agent drafting a weekly Slack summary) can run
    weekly|daily|meetings|decisions|stale synthesis jobs without shelling out to
    the CLI. By default the tool calls the synthesis provider and returns the
    generated text without writing a file (persist: false); set persist: true to
    also write the draft note to disk, mirroring journal synth --write. Honors
    synth_provider, local_only, and voice_profile exactly as the CLI does;
    returns a clean {"error":"…"} when synthesis is unavailable.
    Optional scoping params: kind (default weekly), days, project, date.
  • MCP ask tool — runs retrieve→synthesize and returns
    {answer, citations} (grounded text + path:line references) so clients get a
    direct "what did I decide about X" answer instead of raw chunks. When no chunks
    match, returns "No relevant notes found." rather than calling the model
    ungrounded. Honors local_only / provider availability with a clear error
    (#32).
  • MCP stats and today tools — the same stable JSON as
    journal stats --json / journal today --json, for "how's my note volume" and
    "what does my day look like" (#31).
  • MCP resourcesjournal://today, journal://recent, and
    journal://projects/{slug}/index expose raw Markdown as addressable context via
    resources/list / resources/read, so clients can pull journal context without
    orchestrating tool calls. URIs are stable across server runs
    (#37).
  • MCP promptsweekly reflection, decisions review, and project status
    are exposed as first-class, one-click prompts with journal context pre-assembled
    (reusing synth.AssembleWeekly / AssembleDecisions)
    (#44).
  • journal tags — list distinct #tags with usage counts, and
    journal tags rename <old> <new> rewrites a tag across all notes, re-indexes the
    affected files, and auto-commits (--dry-run previews). Boundary-aware so #foo
    isn't matched inside #foobar (#33).
  • First-class decisions & todos. Dedicated capture commands, crisp
    date/statement/citation rendering, proactive surfacing in journal today, and
    resolution notes on journal done. Fully backward compatible — existing
    @decision/@todo/@done blocks parse and surface unchanged
    (#47).
  • Documentation site at https://journal.eamann.com — an mdBook site
    (Catppuccin Latte) with warm, second-person prose across 14 chapters (install,
    capture, search, synthesis, meetings, configuration, integrations). Additive: the
    docs/ tree is untouched (#38).
  • One-click releases. A prepare-release workflow (Actions → Prepare Release →
    version) finalizes the ## [Unreleased] CHANGELOG section to the version, tags,
    and triggers the Release workflow; and the GitHub Release notes now come from the
    curated CHANGELOG section (scripts/changelog-section.sh + GoReleaser
    --release-notes) instead of an auto-generated commit list. See
    docs/RELEASING.md. Deterministic, project-specific tooling.
  • Agentic-workflow layer + CLAUDE.md. GitHub Actions for agent-ready issues
    (auto-label → trigger → PR), a planning-approval gate for high-complexity work,
    @claude PR feedback (gated to write-access collaborators), and a retro flow;
    plus issue/PR templates, a label set, and a contributor guide grounded in the
    real layout. Contributor/CI tooling — no change to the shipped binary
    (#25).

Changed

  • Better LLM-as-reranker quality. The rerank prompt is now a structured
    relevance rubric (0/5/10 anchors) instead of "reply with only the number", and
    score parsing is a robust multi-strategy parser (N/10 fraction → labelled
    Score: N → last in-range number, skipping negatives). More reliable precision
    on the optional reranking path (#36).

Fixed

  • journal index --watch survives transient embed failures. A per-file embed
    error during a watch pass is now logged and skipped (continue) instead of
    aborting the whole batch; only context cancellation still stops the run.
    Debounce coalescing of rapid edits is now covered by tests
    (#35).
  • Indexing retries transient Ollama embed-server failures (sporadic 400 /
    EOF) instead of failing the run, hardening large journal index passes
    (#7).

v2.6.1

Choose a tag to compare

@github-actions github-actions released this 18 Jun 18:13
b7643dc

Changelog

  • 520da68: feat: section-aware transcript chunking (summary is its own chunk) (@ericmann)

v2.6.0

Choose a tag to compare

@github-actions github-actions released this 18 Jun 17:52
3528c75

Changelog

  • 60a38bc: feat: journal transcribe — ingest non-Quill recordings (WhisperX → summarized, indexed transcript) (@ericmann)

v2.5.0

Choose a tag to compare

@github-actions github-actions released this 17 Jun 16:34
7cfe03e

Changelog

  • 20aa6fd: feat: pluggable OpenAI-compatible providers for synth and embeddings (@ericmann)

v2.4.5

Choose a tag to compare

@github-actions github-actions released this 17 Jun 15:46
bdf054a

Changelog

  • 78405a9: fix: bump wazero v1.8.2 -> v1.12.0 for Go 1.26 compatibility (@ericmann)