Skip to content

feat(journal): status/logs/fetch CLIs + in-process ticker (Phase 2 slice 2/3)#23

Merged
cleak merged 1 commit into
masterfrom
claude/journal-publisher-slice-2
Apr 28, 2026
Merged

feat(journal): status/logs/fetch CLIs + in-process ticker (Phase 2 slice 2/3)#23
cleak merged 1 commit into
masterfrom
claude/journal-publisher-slice-2

Conversation

@cleak
Copy link
Copy Markdown
Owner

@cleak cleak commented Apr 28, 2026

Summary

Slice 2 of 3 for Phase 2 (the publisher subsystem). Builds out the operational surface around the slice 1 publisher pipeline:

  • 3 new CLIs: tempyr journal status / logs / fetch
  • In-process tokio ticker that auto-flushes from a long-running tempyr --mcp process, with one final flush on shutdown so finalized sessions don't strand on disk until the next agent invocation
  • Multi-machine sync: journal fetch mirrors +refs/tempyr/journals/* from a remote, so agent A's pushed journals appear in agent B's local repo

Built on slice 1 (#22). Slice 3 will add init wizard, public-repo detection, auto-fetch refspec config, pack-refs cadence, and the [journal] config TOML section.

What's in here

New CLIs (in tempyr-cli)

Command Purpose
tempyr journal status [--json] Open/ready counts, last push, last error, totals, publisher running flag (via lockfile probe + diagnostic PID readback).
tempyr journal logs [--lines N] [--json] Tails publisher.log. Pretty mode: <ts> LEVEL event {fields}. JSON mode: pass-through JSONL.
tempyr journal fetch [--remote NAME] git fetch <remote> +refs/tempyr/journals/*:refs/tempyr/journals/*

In-process tokio ticker (tempyr-mcp/src/journal_ticker.rs)

Spawned after the project anchor settles. Lifecycle:

  • Every interval (default 60s, override via TEMPYR_JOURNAL_TICK_SECS), call publish_ready_sessions via spawn_blocking (publisher shells out to git, blocking).
  • On ShutdownCoordinator's cancellation token: break loop, run one final flush, return.
  • Skip silently if project root isn't a git repo.
  • Per-tick failures absorbed: publisher records them in state.json + publisher.log, .ready markers stay for retry, ticker continues.

New journal-crate helpers

  • PublisherLock::is_held(common_dir) -> bool — non-acquiring probe.
  • PublisherLock::stamped_pid(common_dir) -> Option<u32> — best-effort PID readback (returns None on Windows when locked, since OS blocks reads from other handles; PID is diagnostic only).

Decisions

Decision
Tick default 60s (conservative — agent sessions are bursty, no need to be aggressive)
Tick override TEMPYR_JOURNAL_TICK_SECS env var; ≤0 falls back to default to avoid busy-spin. Promotes to [journal] tick_secs in slice 3.
Skip in non-git dirs Yes (silent no-op) — journals are git-only by design
Final flush on shutdown Yes — main reason the ticker exists; otherwise --final sessions strand until next launch
Status lock probe try_acquire + immediate drop. Stamped PID readback is best-effort.

Out of scope (slice 3)

  • tempyr init wizard with public-repo detection (gh CLI + GitHub API fallback)
  • Auto-fetch refspec config on init
  • pack-refs cadence after N pushes
  • [journal] config TOML section (consolidates remote, tick_secs, push_timeout_secs)
  • MCP annotations across existing tools

Test plan

  • cargo test --workspace — 508 pass (was 499 on slice 1; +9 new)
  • cargo clippy --workspace --all-targets — clean
  • cargo fmt --check — clean
  • End-to-end: journal log × 2 → journal log --finaljournal status (shows 1 ready) → journal flushjournal status (shows 0 ready, last push set, totals 1/1/0)
  • journal logs empty / populated, pretty + JSON output
  • journal fetch multi-repo simulation: repo1 publishes to bare remote, repo2 fetches, ref appears
  • Ticker periodic-tick test: seed .ready after spawn, periodic tick publishes within 10s
  • Ticker shutdown-flush test: long interval (1h) so periodic doesn't fire, cancel token, verify ref appears on bare remote

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added tempyr journal status command to monitor publisher state and session metrics
    • Added tempyr journal logs command with optional line filtering for publisher logs
    • Added tempyr journal fetch command to sync journal references from remote
    • Enabled automatic background publishing of ready journal sessions in the MCP server

…e 2 slice 2/3)

Slice 2 builds out the operational surface around the slice 1 publisher:

### New CLIs (in tempyr-cli)

- **`tempyr journal status [--json]`** — open/ready session counts, last
  push timestamp, last error, totals, and whether a publisher is currently
  running (lockfile probe + diagnostic PID readback). Pretty + JSON modes.
- **`tempyr journal logs [--lines N] [--json]`** — tails
  `<journals>/publisher.log`. Pretty mode formats `<ts> LEVEL event {fields}`,
  JSON mode passes through the underlying JSONL.
- **`tempyr journal fetch [--remote NAME]`** — `git fetch <remote>
  +refs/tempyr/journals/*:refs/tempyr/journals/*` for multi-machine sync.
  Without it, agent A's pushed journals don't appear in agent B's local repo.

### In-process tokio ticker (in tempyr-mcp)

`tempyr-mcp/src/journal_ticker.rs` — a small tokio task spawned after the
project anchor settles. Wakes on `tokio::time::sleep(interval)` and calls
`publish_ready_sessions` via `spawn_blocking` (the publisher shells out to
git, which would otherwise stall the runtime). On the
`ShutdownCoordinator`'s cancellation token it breaks the loop and runs
**one final flush** so finalized sessions don't strand on disk until
the next agent invocation.

Skips silently if the project root isn't a git repo. Per-tick failures
are absorbed (recorded in `state.json` + `publisher.log` by the
publisher itself; `.ready` markers stay put for retry; ticker continues).

Interval: default 60s, override via `TEMPYR_JOURNAL_TICK_SECS`
(non-positive values fall back to default to avoid busy-spin). Slice 3
will move this into `[journal]` config.

### New journal-crate helpers

- `PublisherLock::is_held(common_dir) -> bool` — non-acquiring probe.
- `PublisherLock::stamped_pid(common_dir) -> Option<u32>` — best-effort
  PID readback from the lockfile content (returns None on Windows when
  the file is exclusively locked, since the OS blocks reads from other
  handles; that's fine — PID is diagnostic only).

### Tests

9 new tests; 508 workspace-wide pass (+9 from 499):

- `lockfile` (4 new): is_held true while held, false when unheld,
  stamped_pid round-trip, stamped_pid None on missing file.
- `journal_ticker` (5): env-var override applies, invalid/0 falls back to
  default, spawn in non-git dir → NotAGitRepo, periodic tick publishes a
  seeded session against a bare remote, shutdown runs final flush against
  a pending session.

Tests that mutate `TEMPYR_JOURNAL_TICK_SECS` are serialized via a
process-wide `Mutex`. The two tokio tests use `spawn_with_interval`
directly (an internal API also exposed for testability) so they don't
have to round-trip through the env var.

### Smoke tested end-to-end

- `journal status` — empty dir, with-ready, post-flush variants
- `journal logs` — empty + populated cases (pretty + JSON)
- `journal fetch` — bare-remote multi-repo simulation: repo1 publishes,
  repo2 fetches, ref appears

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 53bfed0c-e813-45c2-8119-4f6c0ade13c9

📥 Commits

Reviewing files that changed from the base of the PR and between fa13804 and 7f9a511.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • crates/tempyr-cli/src/commands/journal_cmd.rs
  • crates/tempyr-cli/src/main.rs
  • crates/tempyr-journal/src/lockfile.rs
  • crates/tempyr-mcp/Cargo.toml
  • crates/tempyr-mcp/src/journal_ticker.rs
  • crates/tempyr-mcp/src/lib.rs

📝 Walkthrough

Walkthrough

This PR extends the tempyr journal subsystem with operational visibility and background publication capabilities: adding CLI subcommands (status, logs, fetch) for querying publisher state, lock status, and syncing journal refs; new lockfile diagnostics (is_held, stamped_pid); and an in-process journal_ticker module that automatically publishes ready sessions at configurable intervals during MCP startup.

Changes

Cohort / File(s) Summary
CLI Journal Subcommands
crates/tempyr-cli/src/commands/journal_cmd.rs, crates/tempyr-cli/src/main.rs
Added three new CLI subcommands: status (load persisted publisher state, count open/ready sessions, check lock status), logs (read and tail publisher.log as JSONL or formatted text), and fetch (run git fetch for journal refs with forced refspec). Each backed by argument structs and handler functions with JSON/text output modes.
Publisher Lock Diagnostics
crates/tempyr-journal/src/lockfile.rs
Added two public diagnostic methods to PublisherLock: is_held() (non-blocking probe returning true if lock is contended) and stamped_pid() (parses PID from lockfile). Includes test coverage for both contention and absence scenarios.
Background Journal Ticker
crates/tempyr-mcp/src/journal_ticker.rs, crates/tempyr-mcp/src/lib.rs, crates/tempyr-mcp/Cargo.toml
New journal_ticker module spawns a background task that automatically publishes ready journal sessions at configurable cadence. MCP startup conditionally enables the ticker after project root resolution. Returns SpawnOutcome enum reporting running status, git-repo skip, or resolution errors. Includes unit and integration tests covering interval behavior, git detection, cancellation with final flush, and periodic publishing.

Sequence Diagram

sequenceDiagram
    participant MCP as MCP Server<br/>(Startup)
    participant Discover as Project Discovery
    participant Ticker as Journal Ticker
    participant Background as Background Loop
    participant Publisher as Publisher<br/>(Blocking Thread)
    participant Git as Git Repo

    MCP->>Discover: find_project_root()
    Discover-->>MCP: project_root
    
    MCP->>Ticker: spawn(project_root,<br/>cancellation_token)
    Ticker->>Ticker: resolve git paths
    
    alt Git repo found
        Ticker-->>MCP: SpawnOutcome::Running
        Ticker->>Background: spawn background task
        
        loop Periodic (configurable interval)
            Background->>Publisher: publish_ready_sessions()
            Publisher->>Git: git fetch journal refs
            Git-->>Publisher: fetch result
            Publisher-->>Background: completion
        end
        
        MCP->>Background: cancellation_token triggered
        Background->>Publisher: final flush
        Publisher-->>Background: complete
        Background-->>MCP: task done
    else Not a git repo
        Ticker-->>MCP: SpawnOutcome::NotAGitRepo
    else Resolution error
        Ticker-->>MCP: SpawnOutcome::Unavailable(reason)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 bounces with glee
A ticker ticks, the journals sing,
With status, logs—what joy we bring!
Lock whiskers twitch, PIDs we see,
Publishing ready, wild and free! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: adding three new journal CLI subcommands (status/logs/fetch) and an in-process ticker, which are the primary focus of this PR slice 2/3.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

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