trusty-mpm 1.8.2

trusty-mpm: unified multi-agent orchestration platform (core, daemon, CLI, TUI, Telegram)
//! The Disk dashboard's MCP tool descriptor (#6927, DOC-73 §16.6 item 2).
//!
//! Why: DOC-73 §16.4 states trusty-console reaches trusty-mpm through its MCP
//! tools and never through the daemon's HTTP port, and no existing tool exposes
//! the merged-PR worktree survey — `session_prune` is orphan-only, and the
//! `merged_prs` argument lives on the HTTP route alone. This is that tool.
//! What: [`disk_tools`] returns the single `disk_survey` descriptor. Keeping it
//! in its own file follows the per-group split every other tool family uses and
//! keeps `tools/mod.rs` a thin facade.
//! Test: `super::tests::disk_tools_present`,
//! `super::tests::catalog_names_match_constant`,
//! `crate::mcp::tests::dispatch_disk_survey_tool`.

use serde_json::{Value, json};

use super::tool;

/// Build the one Disk-dashboard tool descriptor.
///
/// Why: the console Disk view (DOC-73 §16.3) needs projects, their worktrees,
/// each worktree's bytes, and each worktree's staleness tier from ONE call —
/// the sunburst renders all four at once.
/// What: `disk_survey`, with three optional arguments and a closed schema.
/// `project` scopes the survey to one managed project; `budget_seconds` bounds
/// the whole pass — git and `gh` subprocesses per worktree, and the byte walks,
/// which are the expensive half (#6929) — capped at
/// [`MAX_BUDGET_SECONDS`](crate::daemon::mcp_disk::MAX_BUDGET_SECONDS) because a
/// larger figure outlives the stdio bridge that carries the answer (#7313);
/// `group_by` adds the per-session roll-up (#7313).
/// Test: `super::tests::disk_tools_present`,
/// `super::tests::disk_survey_schema_round_trips`.
pub(super) fn disk_tools() -> Vec<Value> {
    vec![tool(
        "disk_survey",
        "Survey every managed project and worktree for the console Disk view: \
         per-worktree bytes (from the cached size index, with `from_cache` / \
         `truncated` / `measured_at` passed through), a staleness tier \
         (`stale` / `review` / `keep` / `missing`) and the REASONS behind it \
         (`dirty`, `unpushed`, `live-session`, `keep-list`, \
         `unknown-branch-state`, …), the reclaim gate that refused each \
         worktree, and the owner keep-list that was applied. READ-ONLY: it \
         classifies and reports, and removes nothing — clearing a worktree is a \
         separate, explicitly confirmed action. A worktree holding uncommitted \
         or unpushed work, claimed by a live session, owned by a dispatched \
         agent, or named by the operator's `disk.keep_list` config is never \
         reported `stale`. EXPENSIVE: classification runs git and `gh` \
         subprocesses per worktree AND a byte walk per project and per \
         workspace root, so bound it with `budget_seconds` when polling — the \
         budget stops both halves, worktrees past it are still listed as \
         `review`, and a project or root past it reports `bytes: null`. \
         Worktrees are measured before projects and the root, so a \
         budget-limited pass spends what it has on the rows a view colours. \
         A pass that runs out of budget starts one unbudgeted background pass, \
         and later budgeted calls are answered from the last complete pass at \
         once (#8985): `freshness` (`live`, `cached`, `partial`), `age_seconds` and \
         `background_pass` (`running`, `idle`) say which answer this is. \
         Every row also carries `owning_session` — the live session claiming \
         it, else the session its ownership sentinel names, so an ENDED \
         session's leftovers are attributed rather than orphaned — and \
         `build_dir_bytes`, the `target*` share of its total. Pass \
         `group_by: \"session\"` for the `by_session` roll-up over those.",
        json!({
            "type": "object",
            "properties": {
                "project": {
                    "type": "string",
                    "description": "Scope to one managed project: its `<owner>/<repo>` label, its bare repository directory name, or an absolute path prefix. Omit to survey every project."
                },
                "budget_seconds": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 55,
                    "description": "Stop after this many seconds — classification AND byte measurement both (#6929). Remaining worktrees are listed as `review` with an `unknown-branch-state` reason, never as `stale`; a project or root the budget was reached before reports `bytes: null`. CLAMPED to 55 (#7313): the `tm serve --stdio` bridge every caller reaches this tool through gives up at 60 s, so a larger budget returns a transport error rather than a survey. A clamped request answers with `budget_clamped: true`. A budgeted call for a survey key known to exceed the budget is answered from the cache at once: the last complete pass, labelled `freshness: cached` with its `age_seconds` (#8985). Omit for a live, unbounded survey that never reads the cache, which on a large fleet outruns any caller with a request timeout."
                },
                "group_by": {
                    "type": "string",
                    "enum": ["session"],
                    "description": "Add a per-session roll-up beside the project tree (#7313), as `by_session`: one entry per owning session — total bytes, `target*` build-directory bytes, worktree count, the tier split, and the worktree paths — sorted by bytes descending, with a `session_id: null` bucket for worktrees nothing attributed. A worktree is charged to the live session claiming it, else to the session its `.trusty-mpm-worktree` sentinel names, so an ENDED session's leftovers are still attributed. Omit to leave `by_session` out of the response entirely."
                }
            },
            "additionalProperties": false
        }),
    )]
}