Files
rhema-claw/integrations/rhema-forge-integration/src/lib.rs
T
vhaudiquet fc0cbbcb6b refactor(forge): classify account notifications beside the vocabulary
The triage mapped the forges' notification reasons onto the claw's
verdicts, but it co-evolves with the vocabulary, not with the claw:
a reason that enters it (Gitea's collapsed shape) needs a verdict in
the same change, and its tests pin the mapping against the
vocabulary's own types. It now lives in the integration crate as the
default classification; a consumer may reclassify on top. The claw
imports it like the rest of the forge seam.
2026-10-20 05:49:14 +02:00

76 lines
3.1 KiB
Rust

//! `rhema-forge-integration` — the forge integration: one normalized
//! event vocabulary and one client contract for GitHub, Gitea, and
//! GitLab, and the ingest path that turns forge activity into RSD
//! dispatch.
//!
//! An integration, not an agent: no driver, no model, no loop lives
//! here. A consumer composes the crate with its own agent tier — the
//! [`crate::Watcher`] polls a [`crate::Forge`] for activity and feeds
//! the events to a [`rhema_rsd::Dispatcher`](rhema_rsd) (the forge's
//! stable dedup id becomes the event id, and the resource key is spec
//! §8's `host:owner/repo#number` shape, so re-polling unchanged history
//! dedups away and a changed row re-dispatches), and a dispatched
//! thread is answered through the same [`crate::Forge`] capabilities,
//! with [`crate::decode`] recovering the event behind an inbox item.
//! Polling cadence, handler policy, and the loop itself are the
//! consumer's — the claw daemon today, any other agent consumer of the
//! framework tomorrow; the dispatch guarantees are the dispatcher's.
//!
//! ```
//! use rhema_forge_integration::{Forge, GitHubForge, RepoRef};
//!
//! # async fn demo() -> Result<(), rhema_forge_integration::ForgeError> {
//! let forge = GitHubForge::new(std::env::var("GITHUB_TOKEN").unwrap());
//! // First poll: everything. Later polls: pass `page.cursor` back in.
//! let page = forge.poll(&RepoRef::new("acme", "widgets"), None).await?;
//! for event in &page.events {
//! println!("{:?} on {}#{}", event.kind, event.repo.path(), event.number);
//! }
//! # Ok(())
//! # }
//! ```
//!
//! Layout:
//! - [`event`] — the normalized vocabulary: `ForgeEvent`, `EventKind`,
//! `RepoRef`, `Snapshot`, `Timestamp`
//! - [`error`] — the crate's single forge error type
//! - [`forge`] — the `Forge` client contract and `ForgePage`
//! - [`github`] — the GitHub adapter (api.github.com)
//! - [`gitea`] — the Gitea adapter (self-hosted, `/api/v1`)
//! - [`gitlab`] — the GitLab adapter (gitlab.com or self-hosted, `/api/v4`)
//! - [`watcher`] — the dispatcher seam: polls a forge into an RSD
//! `Dispatcher` (keys, dedup, cursors) and decodes delivered inbox
//! items back into forge events
//! - [`triage`] — what an account notification earns: the default
//! classification of the notification vocabulary into agent
//! dispatches, surfaces, and drops
#![forbid(unsafe_code)]
#![deny(missing_docs)]
pub mod error;
pub mod event;
pub mod forge;
pub mod gitea;
pub mod github;
pub mod gitlab;
pub mod triage;
pub mod watcher;
// Re-exported so implementors of the async seam can apply #[async_trait]
// without adding the crate themselves.
pub use async_trait::async_trait;
pub use error::ForgeError;
pub use event::{
EventKind, ForgeEvent, Notification, NotificationPage, NotificationReason, RepoRef, Snapshot,
ThreadState, Timestamp,
};
pub use forge::{Forge, ForgePage};
pub use gitea::GiteaForge;
pub use github::GitHubForge;
pub use gitlab::GitLabForge;
pub use triage::{Role, Triage};
pub use watcher::{
WatchError, Watcher, decode, forge_key_fn, parse_key, register_forge, resource_key,
};