feat(journal): status/logs/fetch CLIs + in-process ticker (Phase 2 slice 2/3)#23
Conversation
…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>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThis 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
Summary
Slice 2 of 3 for Phase 2 (the publisher subsystem). Builds out the operational surface around the slice 1 publisher pipeline:
tempyr journal status/logs/fetchtempyr --mcpprocess, with one final flush on shutdown so finalized sessions don't strand on disk until the next agent invocationjournal fetchmirrors+refs/tempyr/journals/*from a remote, so agent A's pushed journals appear in agent B's local repoBuilt 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)tempyr journal status [--json]tempyr journal logs [--lines N] [--json]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:
interval(default 60s, override viaTEMPYR_JOURNAL_TICK_SECS), callpublish_ready_sessionsviaspawn_blocking(publisher shells out to git, blocking).ShutdownCoordinator's cancellation token: break loop, run one final flush, return.state.json+publisher.log,.readymarkers 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
TEMPYR_JOURNAL_TICK_SECSenv var; ≤0 falls back to default to avoid busy-spin. Promotes to[journal] tick_secsin slice 3.--finalsessions strand until next launchtry_acquire+ immediate drop. Stamped PID readback is best-effort.Out of scope (slice 3)
tempyr initwizard with public-repo detection (gh CLI + GitHub API fallback)pack-refscadence after N pushes[journal]config TOML section (consolidatesremote,tick_secs,push_timeout_secs)Test plan
cargo test --workspace— 508 pass (was 499 on slice 1; +9 new)cargo clippy --workspace --all-targets— cleancargo fmt --check— cleanjournal log× 2 →journal log --final→journal status(shows 1 ready) →journal flush→journal status(shows 0 ready, last push set, totals 1/1/0)journal logsempty / populated, pretty + JSON outputjournal fetchmulti-repo simulation: repo1 publishes to bare remote, repo2 fetches, ref appears.readyafter spawn, periodic tick publishes within 10s🤖 Generated with Claude Code
Summary by CodeRabbit
tempyr journal statuscommand to monitor publisher state and session metricstempyr journal logscommand with optional line filtering for publisher logstempyr journal fetchcommand to sync journal references from remote