Skip to main content

heddle_cli_args/cli/cli_args/
commands_discuss.rs

1// SPDX-License-Identifier: Apache-2.0
2//! `heddle discuss` — durable repository collaboration.
3
4use clap::{ArgGroup, Args, Subcommand};
5
6#[derive(Clone, Debug, Subcommand)]
7pub enum DiscussCommands {
8    /// Open a discussion anchored to a symbol.
9    Open(DiscussOpenArgs),
10    /// Append a durable turn to a discussion.
11    Append(DiscussAppendArgs),
12    /// Resolve a discussion.
13    Resolve(DiscussResolveArgs),
14    /// Reopen a resolved discussion.
15    Reopen(DiscussReopenArgs),
16    /// List repository discussions.
17    List(DiscussListArgs),
18    /// Show one discussion and its causal heads.
19    Show(DiscussShowArgs),
20}
21
22#[derive(Clone, Debug, Args)]
23#[command(
24    group(
25        ArgGroup::new("positional_open")
26            .args(["file", "symbol", "body"])
27            .multiple(true)
28            .conflicts_with("named_open")
29    ),
30    group(
31        ArgGroup::new("named_open")
32            .args(["file_flag", "symbol_flag", "body_flag"])
33            .multiple(true)
34    )
35)]
36pub struct DiscussOpenArgs {
37    /// Path of the file containing the symbol.
38    #[arg(
39        value_name = "FILE",
40        required_unless_present_all = ["file_flag", "symbol_flag", "body_flag"]
41    )]
42    pub file: Option<String>,
43    /// Symbol name (for example `Repository::open`).
44    #[arg(
45        value_name = "SYMBOL",
46        required_unless_present_all = ["file_flag", "symbol_flag", "body_flag"]
47    )]
48    pub symbol: Option<String>,
49    /// First turn of the discussion.
50    #[arg(
51        value_name = "BODY",
52        required_unless_present_all = ["file_flag", "symbol_flag", "body_flag"]
53    )]
54    pub body: Option<String>,
55    /// Path of the file containing the symbol (named alternative to `<FILE>`).
56    #[arg(
57        long = "file",
58        value_name = "FILE",
59        required_unless_present_all = ["file", "symbol", "body"]
60    )]
61    pub file_flag: Option<String>,
62    /// Symbol name (named alternative to `<SYMBOL>`).
63    #[arg(
64        long = "symbol",
65        value_name = "SYMBOL",
66        required_unless_present_all = ["file", "symbol", "body"]
67    )]
68    pub symbol_flag: Option<String>,
69    /// First turn (named alternative to `<BODY>`).
70    #[arg(
71        long = "body",
72        value_name = "BODY",
73        required_unless_present_all = ["file", "symbol", "body"]
74    )]
75    pub body_flag: Option<String>,
76    /// Human-readable summary. Defaults to the first line of the first turn.
77    #[arg(long)]
78    pub title: Option<String>,
79    /// State the symbol anchor was observed against. Defaults to HEAD.
80    #[arg(long)]
81    pub state: Option<String>,
82    /// Visibility: `public` | `internal` | `team:NAME` | `restricted:LABEL` | `private:LABEL`.
83    #[arg(long)]
84    pub visibility: Option<String>,
85    /// Attach the discussion to a thread ref while keeping its symbol anchor.
86    #[arg(long, value_name = "REF")]
87    pub thread: Option<String>,
88}
89
90#[derive(Clone, Debug, Args)]
91pub struct DiscussAppendArgs {
92    pub discussion_id: String,
93    pub body: String,
94}
95
96#[derive(Clone, Debug, Args)]
97#[command(group(
98    ArgGroup::new("resolution")
99        .required(true)
100        .args(["mode", "into_annotation"])
101))]
102pub struct DiscussResolveArgs {
103    pub discussion_id: String,
104    /// Resolution kind: `by-edit` or `dismiss`.
105    #[arg(long, value_enum)]
106    pub mode: Option<ResolveModeArg>,
107    /// Resolve by creating a context annotation from this discussion.
108    #[arg(long, requires = "body")]
109    pub into_annotation: bool,
110    /// For `by-edit`: state containing the edit (defaults to HEAD).
111    #[arg(long, requires = "mode")]
112    pub state: Option<String>,
113    /// For `dismiss`: non-empty reason.
114    #[arg(long, requires = "mode")]
115    pub reason: Option<String>,
116    /// For `--into-annotation`: annotation content.
117    #[arg(long, requires = "into_annotation")]
118    pub body: Option<String>,
119    /// For `--into-annotation`: constraint, invariant, or rationale (defaults to rationale).
120    #[arg(
121        long,
122        value_parser = ["constraint", "invariant", "rationale"],
123        requires = "into_annotation"
124    )]
125    pub kind: Option<String>,
126    /// For `--into-annotation`: annotation tag (can be repeated).
127    #[arg(long, requires = "into_annotation")]
128    pub tag: Vec<String>,
129}
130
131#[derive(Clone, Debug, clap::ValueEnum)]
132pub enum ResolveModeArg {
133    ByEdit,
134    Dismiss,
135}
136
137#[derive(Clone, Debug, Args)]
138pub struct DiscussReopenArgs {
139    pub discussion_id: String,
140    /// Why the prior resolution no longer applies.
141    #[arg(long)]
142    pub reason: String,
143}
144
145#[derive(Clone, Debug, Args)]
146pub struct DiscussListArgs {
147    /// Filter by the state named in the discussion anchor.
148    #[arg(long)]
149    pub state: Option<String>,
150    /// Filter by anchored file path.
151    #[arg(long)]
152    pub file: Option<String>,
153    /// Filter by anchored symbol. Requires `--file`.
154    #[arg(long)]
155    pub symbol: Option<String>,
156    /// Status filter: `open`, `resolved`, `conflicted`, or `all`.
157    #[arg(long, default_value = "open")]
158    pub status: String,
159}
160
161#[derive(Clone, Debug, Args)]
162pub struct DiscussShowArgs {
163    pub discussion_id: String,
164}