1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#[allow(clippy::wildcard_imports)]
use super::*;
/// The `tix project` subcommands (mission `first-class-projects-01M3BYBW`, WP05).
///
/// A **required** subcommand, like `tix milestone` and `tix cycle`: `tix project`
/// alone is a usage error, not a listing, because guessing which of ten verbs the
/// user meant is worse than asking.
///
/// Selection is by the stable id (the registry key); a name is accepted as a
/// convenience and resolves to the same project, since `tix.toml` guarantees the
/// names are unique.
#[derive(Subcommand)]
pub enum ProjectCommand {
/// Create a project and allocate a stable id
///
/// The name is a display label, not the identity: it can be changed later with
/// `tix project edit --name`, and every issue association survives because the
/// issues reference the id.
Create {
/// Display name (unique within the registry, case-insensitively)
#[arg(value_name = "NAME")]
name: String,
/// Long-form context
#[arg(long)]
description: Option<String>,
/// Free-form project status (not a board column)
#[arg(long)]
status: Option<String>,
/// Lead identity string
#[arg(long)]
lead: Option<String>,
/// Start date (`YYYY-MM-DD`)
#[arg(long = "start-date")]
start_date: Option<String>,
/// Due date (`YYYY-MM-DD`); must be `>= start-date`
#[arg(long = "due-date")]
due_date: Option<String>,
},
/// Edit a project's fields; a rename changes the label only
///
/// Nothing is re-keyed: the id stays, so every association and every derived
/// figure is untouched. An optional text flag with a blank value clears it
/// (`--lead ""`), the same convention `tix edit` uses.
Edit {
/// Project id (a name is accepted as a convenience)
#[arg(value_name = "ID")]
reference: String,
/// New display name
#[arg(long)]
name: Option<String>,
/// New long-form context (`--description ""` clears)
#[arg(long)]
description: Option<String>,
/// New free-form status (`--status ""` clears)
#[arg(long)]
status: Option<String>,
/// New lead identity string (`--lead ""` clears)
#[arg(long)]
lead: Option<String>,
/// New start date (`YYYY-MM-DD`; `--start-date ""` clears)
#[arg(long = "start-date")]
start_date: Option<String>,
/// New due date (`YYYY-MM-DD`; `--due-date ""` clears)
#[arg(long = "due-date")]
due_date: Option<String>,
},
/// List projects in declaration order, with ids and member counts
List,
/// Show one project's record and its members
Show {
/// Project id (a name is accepted as a convenience)
#[arg(value_name = "ID")]
reference: String,
},
/// Delete a project's registry entry (its issues are never deleted)
///
/// The issues that referenced the project keep their references; the report
/// names them so the dangling references can be repaired deliberately.
Delete {
/// Project id (a name is accepted as a convenience)
#[arg(value_name = "ID")]
reference: String,
/// Confirm without prompting
#[arg(long)]
yes: bool,
},
/// Associate one or more issues with a project
///
/// Each selector is processed independently: a rejection on one is reported
/// and does not abort the rest.
Add {
/// Project id (a name is accepted as a convenience)
#[arg(value_name = "ID")]
reference: String,
/// Issue numbers, filename fragments or unique title fragments
#[arg(required = true, value_name = "ISSUE")]
issues: Vec<String>,
},
/// Dissociate one or more issues from a project
///
/// Drops only the association; no issue is deleted, and each selector is
/// processed independently.
///
/// A reference that does not name a live project is used **verbatim**, because
/// removing a retained stale id is the repair path a project deletion leaves
/// open (C-005). A typo that matches nothing is therefore an inert no-op that
/// reports `0 changed`, not a refusal — which is the one place this differs
/// from `tix edit <issue> --remove-project <id>`, where an unknown reference the
/// issue does not carry is refused as `unknown_project`.
Remove {
/// Project id (a name is accepted as a convenience)
#[arg(value_name = "ID")]
reference: String,
/// Issue numbers, filename fragments or unique title fragments
#[arg(required = true, value_name = "ISSUE")]
issues: Vec<String>,
},
/// Bulk-associate one or more issues with a project
///
/// Project association only — general bulk editing is a different feature.
/// Every selector is processed independently and every outcome is reported,
/// so one unknown selector does not discard the associations that succeeded.
///
/// An unknown **project** reference refuses the whole command before anything
/// is written (all entries then fail with `unknown_project`); use
/// `tix project remove` to drop a retained stale id, whose reference is matched
/// verbatim and reported as `0 changed` rather than refused.
BulkAdd {
/// Project id (a name is accepted as a convenience)
#[arg(value_name = "ID")]
reference: String,
/// Issue numbers, filename fragments or unique title fragments
#[arg(required = true, value_name = "ISSUE")]
issues: Vec<String>,
},
}