Docs

api-hooks

Source: crates/lvcs-api/src/hooks.rs.

/// `track_read` (decision 117), the PostToolUse `Read` hook's op: the
/// tool read `path` (the absolute path it used, in one of this daemon's
/// mirrors), whose bytes had the sha-256 `hash` (lowercase hex). The
/// mirror records the heads those bytes were rendered at, and the file's
/// next save is based on them when the document holds them and they are
/// at or after the author's latest change there. Refused `REJECTED` when
/// `path` is in no mirror of this daemon, or the mirror's author isn't the
/// caller.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct TrackReadParams {
    pub path: String,
    pub hash: String,
}

/// `track_read` result: an empty object.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TrackReadResult {}

/// `track_edit` (decisions 117 and 134), the PreToolUse hook's op: the
/// tool is about to replace `old` (`None` for a whole-file write) in
/// `path` (as for `track_read`). The daemon locates `old` at its first
/// occurrence in the text at the heads the tool read (the whole document
/// when it can't), then notifies that range with `op` and `what` and
/// waits, answering [`TrackEditResult`] when the wait ends, as `announce`
/// does. Refused `REJECTED` when `path` is in no mirror of this daemon,
/// or the mirror's author isn't the caller.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct TrackEditParams {
    pub path: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub old: Option<String>,
    pub op: AnnounceOpParam,
    pub what: String,
}

/// `track_edit` result (decision 134): as [`AnnounceResult`], but for holds.
///
/// Each hold is the [`Notification`] the caller's `open` list holds for it
/// (`kind` `hold`, its sender's tier, harness, and model from the caps
/// render, the notified range, its thread), metadata only; its words are
/// read with `notifications.read` (decision 173).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TrackEditResult {
    pub notification: String,
    pub outcome: AnnounceOutcome,
    /// The holds received during the wait, as the caller's `open` list
    /// holds them.
    #[serde(default)]
    pub holds: Vec<Notification>,
    #[serde(default)]
    pub notes: Vec<ResponseItem>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub yielded_to: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub ask: Option<AskResult>,
    /// How to go on, as for `announce`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub recover: Option<String>,
    /// The action that goes on, as for `announce`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub next: Option<Next>,
    /// How to name each participant the result names (decision 243), by
    /// id.
    #[serde(default)]
    pub names: BTreeMap<String, Named>,
}

/// `track_write` (decision 117), the `PostToolUse` `Write` and `Edit` hook's op.
///
/// The tool wrote `path` (as for `track_read`); lists the caller's unread
/// notifications as `notifications` does, up to `max_bytes` of their JSON
/// with its `open` notifications counted first (no bound when omitted).
/// Refused `REJECTED` when `path` is in no mirror of this daemon.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct TrackWriteParams {
    /// The absolute path the tool wrote.
    pub path: String,
    /// The most bytes of notification JSON listed.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_bytes: Option<u32>,
}

/// `track_write` result. Listing marks nothing read; it is a hinting
/// listing (decision 242), so it moves the caller's hint watermark.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TrackWriteResult {
    /// Whether the mirror's author isn't the caller (§5's warning: the
    /// write is attributed to the author).
    #[serde(default)]
    pub author_mismatch: bool,
    /// The caller's unread notifications that fit, not open.
    #[serde(default)]
    pub notifications: Vec<Notification>,
    /// The caller's open asks and holds, as `notifications` lists them.
    #[serde(default)]
    pub open: Vec<Notification>,
    /// How many didn't fit.
    #[serde(default)]
    pub omitted: u32,
    /// The ids listed, open ones included, that arrived after the caller's
    /// last hinting listing (decision 242).
    #[serde(default)]
    pub fresh: Vec<String>,
    /// How to name each participant the result names (decision 243), by
    /// id.
    #[serde(default)]
    pub names: BTreeMap<String, Named>,
}

/// `stop_gate` (decisions 214 and 217), the `Stop` hook's op: whether the
/// caller may stop now. Read-only, and never retried.
///
/// The daemon keeps the caller's newest change to each file it changed
/// through this daemon, and to each workspace document it changed there,
/// since the daemon started. The answer is `failed` while the caller has
/// an open `action_failed` streak (decision 193) and changed nothing since
/// the notice that opened it, once per set of open streaks: a later
/// `stop_gate` with the same set and nothing changed answers `pending`,
/// naming no file and those streaks, until a streak closes or opens, the
/// caller changes something, or `STOP_WAIT_MAX_MS` passed since the
/// `failed`, which then answers `failed` again (decision 230); else `merge` while one of those changes is in
/// a forked workspace no merge took since (decision 217); else `pending`
/// while a streak of its is open, or, for one of those files, no frontier
/// of the change set's value holds its newest change, unless the project
/// configures no check; and `passed` otherwise. A restart forgets the
/// changes, as it forgets `status.checks`.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct StopGateParams {}

/// What the `Stop` gate decided (decisions 214 and 217).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StopGate {
    /// The caller may stop: the change set holds its newest changes, and
    /// no check of its fails.
    Passed,
    /// The runners haven't passed its newest changes yet, its fix of a
    /// failed check included: ask again after `retry_after_ms`.
    Pending,
    /// Its newest change is in a workspace it hasn't merged: merge it with
    /// `ws.merge`.
    Merge,
    /// A check failed on its changes: read the `action_failed` with
    /// `notifications.read` and fix the change.
    Failed,
}

/// `stop_gate` result.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StopGateResult {
    /// The decision.
    pub gate: StopGate,
    /// `failed`: the caller's open failing streaks, as `status.checks`
    /// lists them (metadata only); `pending` with no file: those streaks,
    /// already shown at a `Stop` (decision 230); empty otherwise.
    #[serde(default)]
    pub failed: Vec<Notification>,
    /// `merge`: the workspaces holding its unmerged changes, each by its
    /// name when this daemon runs it, else its id in `ID_ENCODING`; empty
    /// otherwise.
    #[serde(default)]
    pub workspaces: Vec<String>,
    /// `pending`: the files whose newest change of the caller's no frontier
    /// of the change set holds yet, sorted; empty otherwise.
    #[serde(default)]
    pub pending: Vec<String>,
    /// `pending`: how long to wait before asking again (`STOP_RETRY_MS`).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub retry_after_ms: Option<u64>,
}