tix-tracker 0.12.0

A terminal issue tracker: markdown + YAML frontmatter issues with a kanban TUI and CLI
Documentation
#[allow(clippy::wildcard_imports)]
use super::*;

#[allow(clippy::too_many_lines)]
pub(super) fn descriptors() -> Vec<ToolDescriptor> {
    vec![
        ToolDescriptor {
            name: "list_projects",
            description: "List the projects in the registry, in declaration order, each with its **id**, its display name and its derived member count. The id is always present because selecting by id is the contract and a rename must never look like a new project (mirrors `tix project list`). Never fails: an empty registry is an empty list.",
            input_schema: json!({
                "type": "object",
                "additionalProperties": false,
                "properties": {}
            }),
        },
        ToolDescriptor {
            name: "get_project",
            description: "Show one project's record and its derived, ordered members (mirrors `tix project show`). Resolved by its **stable id**; a name resolves to the same project. Every member carries its issue number and the shared `#<number> <title>` display label, computed on read so it is never stale. Membership is derived from the issues' association lists, never stored.",
            input_schema: json!({
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "project": { "type": "string", "description": "Stable project id, or a project name that resolves to the same project." }
                },
                "required": ["project"]
            }),
        },
        ToolDescriptor {
            name: "create_project",
            description: "Create a project (mirrors `tix project create`). The registry allocates a **stable generated id**, which is returned; the name is a mutable display label and not the identity. Validates the name (non-blank, unpadded, unique case-folded) and the dates (`YYYY-MM-DD`, `due_date >= start_date`) through the service, so a refusal writes nothing.",
            input_schema: json!({
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "name": { "type": "string", "description": "Display label. Required and unique in the registry (case-folded, trimmed); never the identity." },
                    "description": { "type": "string", "description": "Optional long-form context." },
                    "status": { "type": "string", "description": "Optional project status. Free-form project metadata — **not** a board column and not a workflow category." },
                    "lead": { "type": "string", "description": "Optional lead identity string (a bare handle or `Name <email>`); not a user account." },
                    "start_date": { "type": "string", "description": "`YYYY-MM-DD`." },
                    "due_date": { "type": "string", "description": "`YYYY-MM-DD`; must be `>= start_date`." }
                },
                "required": ["name"]
            }),
        },
        ToolDescriptor {
            name: "update_project",
            description: "Edit a project's fields (mirrors `tix project edit`). Resolved by its **stable id** (or a name). A `name` change is a **label change only**: the id is untouched, so no association and no derived figure is re-keyed (C-002). A blank optional field (`\"\"`) clears it. At least one field must be provided; a name that duplicates another project is refused, and an inverted date pair is refused, either way with nothing written.",
            input_schema: json!({
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "project": { "type": "string", "description": "Stable project id, or a project name that resolves to the same project." },
                    "name": { "type": "string", "description": "New display label. The id does not change." },
                    "description": { "type": "string", "description": "New description; `\"\"` clears it." },
                    "status": { "type": "string", "description": "New project status; `\"\"` clears it." },
                    "lead": { "type": "string", "description": "New lead identity string; `\"\"` clears it." },
                    "start_date": { "type": "string", "description": "New `YYYY-MM-DD` start date; `\"\"` clears it." },
                    "due_date": { "type": "string", "description": "New `YYYY-MM-DD` due date; `\"\"` clears it." }
                },
                "required": ["project"]
            }),
        },
        ToolDescriptor {
            name: "delete_project",
            description: "Delete a project's **registry entry only** (mirrors `tix project delete --yes`). No issue is deleted or modified: the issues that referenced it keep their references, which then read as diagnosable `missing_project_reference` findings until repaired (C-004, C-005). Returns the affected issues (the CLI's impact report) so an agent can warn the user instead of orphaning work. Requires `confirm: true`.",
            input_schema: json!({
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "project": { "type": "string", "description": "Stable project id, or a project name that resolves to the same project." },
                    "confirm": { "type": "boolean", "description": "Must be true. Acknowledges that only the registry entry is removed and no issue is deleted." }
                },
                "required": ["project", "confirm"]
            }),
        },
        ToolDescriptor {
            name: "project_membership",
            description: "The derived, ordered membership of one project (the `membership` operation of the project service). Resolved by its **stable id** (or a name). Returns the raw `member_count` and the ordered member list — each member's issue number and its `#<number> <title>` display label — in the service's order (by issue number), never a formatted summary, so the value can be compared with the CLI's and the TUI's (NFR-003). Read-only: no write, no History entry.",
            input_schema: json!({
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "project": { "type": "string", "description": "Stable project id, or a project name that resolves to the same project." }
                },
                "required": ["project"]
            }),
        },
        ToolDescriptor {
            name: "project_health",
            description: "One project's typed progress and health (the `health` operation of the project service; the same value `tix show` and `tix list --show progress` render). Resolved by its **stable id** (or a name). Returns the **raw** figures — `done`, `total`, `overdue`, `blocked`, `unassigned`, and `missing_references` — not a formatted summary, so parity is assertable by value (NFR-003). Every figure is computed once, in the service: `overdue` and `blocked` are consumed from their owning derived reads, never recomputed (C-006). Read-only: no write, no History entry.",
            input_schema: json!({
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "project": { "type": "string", "description": "Stable project id, or a project name that resolves to the same project." }
                },
                "required": ["project"]
            }),
        },
        ToolDescriptor {
            name: "project_associate_issues",
            description: "Associate one or more issues with a project, each independently (mirrors `tix project add` / `bulk-add`; the project-side form of the bulk associate operation). The project is resolved by its **stable id** once, up front, so an unknown project refuses before any write. Each selector is processed independently through the store's service-backed association write: one rejection is reported beside the rest rather than aborting them, and each accepted change appends exactly one History entry. An association already present is a no-op that appends nothing.",
            input_schema: json!({
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "project": { "type": "string", "description": "Stable project id, or a project name that resolves to the same project." },
                    "issues": { "type": "string", "description": "Comma-separated issue selectors (a number, a filename, or a title fragment — as everywhere a selector is taken). One outcome is reported per selector." }
                },
                "required": ["project", "issues"]
            }),
        },
        ToolDescriptor {
            name: "project_dissociate_issues",
            description: "Dissociate one or more issues from a project, each independently (mirrors `tix project remove`). Drops only the association — never an issue (C-004). The project reference resolves to its **stable id** when it names a live project and is otherwise used **verbatim**, which is the repair path for a stale stored id (C-005). Each selector is independent: a rejection is reported beside the rest rather than aborting them, and an absent association is an idempotent no-op that appends nothing.",
            input_schema: json!({
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "project": { "type": "string", "description": "Stable project id, or a name. A reference that resolves to no live project is used verbatim, so a stale stored id can still be removed." },
                    "issues": { "type": "string", "description": "Comma-separated issue selectors (a number, a filename, or a title fragment). One outcome is reported per selector." }
                },
                "required": ["project", "issues"]
            }),
        },
        ToolDescriptor {
            name: "analytics_projection",
            description: "The read-only end-date forecast over one cycle or milestone at `as_of` (`tix analytics projection`). The `content` text is that command's stdout byte for byte, and `structuredContent` is the shared chart document carrying the projection: its status (`projected`/`unknown`/`insufficient_data`), the planning `end_date` it was drawn against, the `remaining` work and `observed_rate`, and the `projected_completion` and `meets_end_date` — each `null` when the record cannot derive it, never a fabricated date or a `0` (C-004). An unanswerable forecast is an **answer**, not a refusal: its own `status` says so. The request-level refusals (a malformed `as_of`, a scope with no planning end date, estimate mode with no unit) happen before the fold and read exactly as the CLI prints them. Read-only.",
            input_schema: json!({
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "scope": { "type": "string", "enum": ["cycle", "milestone"], "description": "The scope kind the `id` names." },
                    "id": { "type": "string", "description": "The scope's planning identity: a cycle name or its stable id, or a milestone name." },
                    "as_of": { "type": "string", "description": "The forecast origin, a `YYYY-MM-DD` calendar date — the caller's day, never a clock. Required: a forecast without an origin has nothing to be drawn from." },
                    "from": { "type": "string", "description": "The series' inclusive start, `YYYY-MM-DD`." },
                    "to": { "type": "string", "description": "The planning end date the forecast is drawn against, `YYYY-MM-DD`. Replaces the scope's own; a scope that declares none requires it." },
                    "mode": { "type": "string", "enum": ["count", "estimate"], "description": "How the series the forecast is drawn from counts. `count` (the default) or `estimate`." },
                    "unit": { "type": "string", "description": "The unit estimate mode counts in; required in estimate mode, ignored in count mode." }
                },
                "required": ["scope", "id", "as_of"]
            }),
        },
    ]
}