#[allow(clippy::wildcard_imports)]
use super::*;
#[allow(clippy::too_many_lines)]
pub(super) fn descriptors() -> Vec<ToolDescriptor> {
vec![
ToolDescriptor {
name: "time_show",
description: "The issue's current time entries and its total (`tix time show <ISSUE>`). The issue is resolved by the crate's own selector grammar, so an archived or otherwise off-view issue is still readable, and every entry is listed — malformed ones included, each explained by a finding in `diagnostics`, because a malformed entry is reported rather than hidden and blocks no read (C-005). Each entry carries the shared DTO's own fields: its stable `id`, `duration_minutes`, `date`, `author`, `note` and its preserved `extensions` as a **nested object** (never flattened beside the typed fields, which is how a preserved key would come to shadow one), and the total carries its explicit `state` — `recorded`, `nothing` (no entry) or `unknown` (entries, none usable). An absent value is `null` in the structure and `-` in the text, never `0`, so a malformed entry does not read as a zero-minute one (C-002, C-006). Read-only: no write, no History entry.",
input_schema: json!({
"type": "object",
"additionalProperties": false,
"properties": {
"issue": { "type": ["string", "integer"], "description": "Issue selector — a number, a filename fragment or a unique title fragment, the same selector `tix view` and `tix time show` take." }
},
"required": ["issue"]
}),
},
ToolDescriptor {
name: "time_history",
description: "What History retains for one issue (`tix time history <ISSUE>`): the current-entry count beside every prior value a deletion left behind — the entry it named, when the deletion was recorded and who recorded it — plus how many corrections the issue's History keeps (FR-005, C-004). Corrections are counted rather than re-rendered: the prior values live in the `## History` line the mutation wrote, and re-parsing that grammar here would make this a second owner of a grammar the shared time service already owns. An event whose `entry_id` is `null` is a hand-edited History line the grammar cannot read; it is still counted and still listed, so the audit never silently shrinks. Read-only: no write, no History entry.",
input_schema: json!({
"type": "object",
"additionalProperties": false,
"properties": {
"issue": { "type": ["string", "integer"], "description": "Issue selector — a number, a filename fragment or a unique title fragment." }
},
"required": ["issue"]
}),
},
ToolDescriptor {
name: "time_total",
description: "Total recorded effort for one scope (`tix time total`), and optionally its breakdown. The scope is the shared `TimeFilter`: `issue` (the same selector `time_show` takes), `assignee` — the identity that **did the work**, an entry's author, never the issue's own `assignee` (ownership) — an inclusive calendar period (`from`/`to`, the caller's dates: this surface resolves no `today`), `audit` (report what deletions left in History; never added to the total) and `by` (`issue` | `assignee` | `date`, with `all` the scope's one ungrouped total). Every number is a field of the shared `TimeTotal` DTO, and `malformed_count`/`undated_count` say whether the sum is the whole story (`is_partial`). An absent value is `null` in the structure and `-` in the text, never `0`: a scope that selected no entry is `state: \"nothing\"`, a scope whose entries carry no usable duration is `state: \"unknown\"`, and only a genuinely recorded zero is `0`. A refused filter, or an issue the snapshot does not hold, is refused in the CLI's own words and writes nothing (C-005, C-006, FR-003, NFR-003). Read-only.",
input_schema: json!({
"type": "object",
"additionalProperties": false,
"properties": {
"issue": { "type": ["string", "integer"], "description": "Scope the total to one issue, by the same selector `time_show` takes (`tix time total <ISSUE>`). Omit for every active issue." },
"assignee": { "type": "string", "description": "The identity that did the work — an entry's `author`. `ada`, `@ada` and `Ada Lovelace <ada@example.com>` are one identity. Never the issue's own `assignee`." },
"from": { "type": "string", "description": "Inclusive period start, `YYYY-MM-DD`." },
"to": { "type": "string", "description": "Inclusive period end, `YYYY-MM-DD`." },
"audit": { "type": "boolean", "description": "Also report how many prior values deletions left in History. Never added to the total." },
"by": { "type": "string", "enum": ["all", "issue", "assignee", "date"], "description": "Break the total down. `all` (the default) is the scope's one ungrouped total; an entry that cannot be attributed or placed is shown as `(unattributed)`/`(undated)`, never dropped." }
}
}),
},
ToolDescriptor {
name: "time_add",
description: "Record effort against an issue (`tix time add <ISSUE> --minutes <N> --author <IDENTITY>`), through the shared time service — there is no second writer. `minutes` is whole minutes at or above zero (a negative value is refused by the shared entry validator with its field name), `date` is an optional `YYYY-MM-DD` **calendar date** (never an instant; this surface resolves no `today`), `author` is required because an entry without attribution is not auditable, and `note` is optional free text. The stable entry id is minted by the service and returned. `actor`/`timestamp`/`reason` are the service's own attribution seams — the CLI takes the crate's git identity and clock, and has no `--reason` on `add`, so a reason is optional here too. Appends exactly one attributable History event and touches no other issue field: never ownership, status, estimate or another entry (C-003, C-008, NFR-002).",
input_schema: json!({
"type": "object",
"additionalProperties": false,
"properties": {
"issue": { "type": ["string", "integer"], "description": "Issue selector — a number, a filename fragment or a unique title fragment." },
"minutes": { "type": "integer", "description": "The effort, in whole minutes at or above zero. Deliberately not pre-validated here: the shared entry validator refuses a negative value with its field name and its remedy." },
"date": { "type": "string", "description": "The calendar date the effort belongs to, `YYYY-MM-DD`. Optional, and an entry without one is real — a period-bounded read reports it as undated rather than as zero." },
"author": { "type": "string", "description": "Who did the work: a handle (`ada`, `@ada`) or a `Name <email>` identity." },
"note": { "type": "string", "description": "Free-text context, preserved verbatim on one line." },
"actor": { "type": "string", "description": "Who is recording the entry, for the History event. Default: the boundary's actor (the git identity)." },
"timestamp": { "type": "string", "description": "The instant to record, `YYYY-MM-DDTHH:MM:SSZ`. Default: now. An off-shape value is refused as `invalid_timestamp`." },
"reason": { "type": "string", "description": "Why, recorded verbatim in the History event. Optional for a create, as the CLI's `tix time add` has no `--reason`." }
},
"required": ["issue", "minutes", "author"]
}),
},
ToolDescriptor {
name: "time_edit",
description: "Correct one time entry (`tix time edit <ISSUE> --entry <ID>`), through the shared service's patch: a field the call does not name is left exactly as it was, including the entry's date, note and any metadata another build attached. `date`/`note` replace the value and `clear_date`/`clear_note` remove it — naming a replacement beside its clear is refused, exactly as `clap` refuses `--date` beside `--clear-date`, so no call is accepted that the CLI would reject. `reason` is required by the shared service, which refuses a blank one with `missing_reason`: the prior value stays in History and the reason is what makes the change attributable (C-003, C-004, FR-005). An edit that names no field is refused with `empty_edit`, and a request that asks for exactly what is already there writes nothing and appends no event.",
input_schema: json!({
"type": "object",
"additionalProperties": false,
"properties": {
"issue": { "type": ["string", "integer"], "description": "Issue selector — a number, a filename fragment or a unique title fragment." },
"entry": { "type": "string", "description": "The stable id of the entry to correct; `time_show` lists them. Ids are never reused." },
"minutes": { "type": "integer", "description": "Replacement duration, in whole minutes at or above zero." },
"date": { "type": "string", "description": "Replacement calendar date, `YYYY-MM-DD`." },
"clear_date": { "type": "boolean", "description": "Remove the entry's date. Refused beside `date`, as the CLI refuses the flag pair." },
"note": { "type": "string", "description": "Replacement note." },
"clear_note": { "type": "boolean", "description": "Remove the entry's note. Refused beside `note`." },
"author": { "type": "string", "description": "Replacement author identity. There is no way to clear it: an entry without attribution is not auditable." },
"reason": { "type": "string", "description": "Why the entry is being corrected; recorded in History. Required by the shared service, which refuses a missing or blank one with `missing_reason`." },
"actor": { "type": "string", "description": "Who is correcting it, for the History event. Default: the git identity." },
"timestamp": { "type": "string", "description": "The instant to record, `YYYY-MM-DDTHH:MM:SSZ`. Default: now." }
},
"required": ["issue", "entry"]
}),
},
ToolDescriptor {
name: "time_delete",
description: "Remove one entry from the issue's **current** entries (`tix time delete <ISSUE> --entry <ID> --reason <TEXT> --confirm`). The entry leaves the frontmatter; its value and its event stay in History, where `time_history` reads them back, so a deletion is auditable rather than a rewrite (C-003, C-004, FR-005). Requires `confirm: true` — an unconfirmed deletion is refused before the store is even read, in the CLI's own words, and writes nothing. `reason` is required by the shared service (`missing_reason`). The stable id is never reused, in the current entries or in History.",
input_schema: json!({
"type": "object",
"additionalProperties": false,
"properties": {
"issue": { "type": ["string", "integer"], "description": "Issue selector — a number, a filename fragment or a unique title fragment." },
"entry": { "type": "string", "description": "The stable id of the entry to remove." },
"reason": { "type": "string", "description": "Why the entry is being removed; recorded in History. Required by the shared service." },
"confirm": { "type": "boolean", "description": "Must be true. Without it nothing is removed and the refusal names the missing confirmation." },
"actor": { "type": "string", "description": "Who is removing it, for the History event. Default: the git identity." },
"timestamp": { "type": "string", "description": "The instant to record, `YYYY-MM-DDTHH:MM:SSZ`. Default: now." }
},
"required": ["issue", "entry", "confirm"]
}),
},
]
}