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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// SPDX-License-Identifier: Apache-2.0
//! `heddle discuss` — durable repository collaboration.
use clap::{ArgGroup, Args, Subcommand};
#[derive(Clone, Debug, Subcommand)]
pub enum DiscussCommands {
/// Open a discussion anchored to a symbol.
Open(DiscussOpenArgs),
/// Append a durable turn to a discussion.
Append(DiscussAppendArgs),
/// Resolve a discussion.
Resolve(DiscussResolveArgs),
/// Reopen a resolved discussion.
Reopen(DiscussReopenArgs),
/// List repository discussions.
List(DiscussListArgs),
/// Show one discussion and its causal heads.
Show(DiscussShowArgs),
/// Replay hosted discussion events after the local watermark, then go live.
Wait(DiscussWaitArgs),
}
#[derive(Clone, Debug, Args)]
#[command(
group(
ArgGroup::new("positional_open")
.args(["file", "symbol", "body"])
.multiple(true)
.conflicts_with("named_open")
),
group(
ArgGroup::new("named_open")
.args(["file_flag", "symbol_flag", "body_flag"])
.multiple(true)
)
)]
pub struct DiscussOpenArgs {
/// Path of the file containing the symbol.
#[arg(
value_name = "FILE",
required_unless_present_all = ["file_flag", "symbol_flag", "body_flag"]
)]
pub file: Option<String>,
/// Symbol name (for example `Repository::open`).
#[arg(
value_name = "SYMBOL",
required_unless_present_all = ["file_flag", "symbol_flag", "body_flag"]
)]
pub symbol: Option<String>,
/// First turn of the discussion.
#[arg(
value_name = "BODY",
required_unless_present_all = ["file_flag", "symbol_flag", "body_flag"]
)]
pub body: Option<String>,
/// Path of the file containing the symbol (named alternative to `<FILE>`).
#[arg(
long = "file",
value_name = "FILE",
required_unless_present_all = ["file", "symbol", "body"]
)]
pub file_flag: Option<String>,
/// Symbol name (named alternative to `<SYMBOL>`).
#[arg(
long = "symbol",
value_name = "SYMBOL",
required_unless_present_all = ["file", "symbol", "body"]
)]
pub symbol_flag: Option<String>,
/// First turn (named alternative to `<BODY>`).
#[arg(
long = "body",
value_name = "BODY",
required_unless_present_all = ["file", "symbol", "body"]
)]
pub body_flag: Option<String>,
/// Human-readable summary. Defaults to the first line of the first turn.
#[arg(long)]
pub title: Option<String>,
/// State the symbol anchor was observed against. Defaults to HEAD.
#[arg(long)]
pub state: Option<String>,
/// Visibility: `public` | `internal` | `team:NAME` | `restricted:LABEL` | `private:LABEL`.
#[arg(long)]
pub visibility: Option<String>,
/// Attach the discussion to a thread ref while keeping its symbol anchor.
#[arg(long, value_name = "REF")]
pub thread: Option<String>,
}
#[derive(Clone, Debug, Args)]
pub struct DiscussAppendArgs {
pub discussion_id: String,
pub body: String,
}
#[derive(Clone, Debug, Args)]
#[command(group(
ArgGroup::new("resolution")
.required(true)
.args(["mode", "into_annotation"])
))]
pub struct DiscussResolveArgs {
pub discussion_id: String,
/// Resolution kind: `by-edit` or `dismiss`.
#[arg(long, value_enum)]
pub mode: Option<ResolveModeArg>,
/// Resolve by creating a context annotation from this discussion.
#[arg(long, requires = "body")]
pub into_annotation: bool,
/// For `by-edit`: state containing the edit (defaults to HEAD).
#[arg(long, requires = "mode")]
pub state: Option<String>,
/// For `dismiss`: non-empty reason.
#[arg(long, requires = "mode")]
pub reason: Option<String>,
/// For `--into-annotation`: annotation content.
#[arg(long, requires = "into_annotation")]
pub body: Option<String>,
/// For `--into-annotation`: constraint, invariant, or rationale (defaults to rationale).
#[arg(
long,
value_parser = ["constraint", "invariant", "rationale"],
requires = "into_annotation"
)]
pub kind: Option<String>,
/// For `--into-annotation`: annotation tag (can be repeated).
#[arg(long, requires = "into_annotation")]
pub tag: Vec<String>,
}
#[derive(Clone, Debug, clap::ValueEnum)]
pub enum ResolveModeArg {
ByEdit,
Dismiss,
}
#[derive(Clone, Debug, Args)]
pub struct DiscussReopenArgs {
pub discussion_id: String,
/// Why the prior resolution no longer applies.
#[arg(long)]
pub reason: String,
}
#[derive(Clone, Debug, Args)]
pub struct DiscussListArgs {
/// Filter by the state named in the discussion anchor.
#[arg(long)]
pub state: Option<String>,
/// Filter by anchored file path.
#[arg(long)]
pub file: Option<String>,
/// Filter by anchored symbol. Requires `--file`.
#[arg(long)]
pub symbol: Option<String>,
/// Status filter: `open`, `resolved`, `conflicted`, or `all`.
#[arg(long, default_value = "open")]
pub status: String,
}
#[derive(Clone, Debug, Args)]
pub struct DiscussShowArgs {
pub discussion_id: String,
}
#[derive(Clone, Debug, Args)]
pub struct DiscussWaitArgs {
/// Resume after this hosted event id. Defaults to the persisted watermark.
#[arg(long)]
pub after: Option<i64>,
/// Hosted remote that owns the event cursor. Defaults to the repository default.
#[arg(long)]
pub remote: Option<String>,
/// Restrict the subscription to this thread name.
#[arg(long)]
pub thread: Option<String>,
/// Internal helper for tests: stop after this many events (including ignored ones).
#[arg(long, hide = true)]
pub max_events: Option<usize>,
}