Skip to main content

coding_tools/cli/
ct_okf.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Jonathan Shook
3
4//! The `ct-okf` command grammar (see [`crate::cli`]); the `ct-okf` bin is a
5//! thin parse-and-dispatch wrapper over this `Cli`.
6
7use std::path::PathBuf;
8
9use clap::Parser;
10
11use crate::explain::Format;
12use crate::pulse::HeartbeatOpts;
13
14#[derive(Parser, Debug)]
15#[command(
16    name = "ct-okf",
17    version,
18    about = "Author and query Open Knowledge Format (OKF) bundles of Markdown concepts.",
19    long_about = "ct-okf works with OKF v0.1 bundles — directory trees of Markdown concepts whose \
20                  YAML frontmatter carries a required `type` plus optional metadata (also reachable \
21                  as `ct okf`). Read-only verbs: --validate (a conformance verdict), --list (query \
22                  concepts by --type/--tag), --show (one concept's metadata), --links (cross-link \
23                  report / broken-link verdict). Authoring verbs (these write): --new, --init, \
24                  --index, --log, --set. See `ct-okf --explain` for agent-oriented documentation."
25)]
26pub struct Cli {
27    /// Bundle root (or single concept root) to operate on.
28    #[arg(long, default_value = ".")]
29    pub base: PathBuf,
30
31    /// Limit selection to files whose name matches; '|'-separated alternatives, each substring->glob->regex promoted and anchored.
32    #[arg(long)]
33    pub name: Option<String>,
34
35    /// Include dot-entries (names starting with '.'); default skips them.
36    #[arg(long)]
37    pub hidden: bool,
38
39    /// Follow symlinks while traversing.
40    #[arg(long)]
41    pub follow: bool,
42
43    /// Walk gitignored / .ignore files too (the .git directory is always skipped); by default the walk skips what git would.
44    #[arg(long)]
45    pub no_ignore: bool,
46
47    // ----- verbs (choose exactly one) -----
48    /// Check the bundle for OKF conformance and report a verdict (the default verb when no other is given).
49    #[arg(long)]
50    pub validate: bool,
51
52    /// List the bundle's concepts with their metadata; filter with --type / --tag.
53    #[arg(long)]
54    pub list: bool,
55
56    /// Show one concept's frontmatter (give the concept path).
57    #[arg(long, value_name = "PATH")]
58    pub show: Option<PathBuf>,
59
60    /// Report the bundle's cross-links; with --strict, fail on a broken bundle-relative link.
61    #[arg(long)]
62    pub links: bool,
63
64    /// Scaffold a new concept at PATH (requires --type); refuses to overwrite.
65    #[arg(long, value_name = "PATH")]
66    pub new: Option<PathBuf>,
67
68    /// Scaffold a bundle root index.md (declaring okf_version) if absent.
69    #[arg(long)]
70    pub init: bool,
71
72    /// (Re)generate index.md for --base from the concepts' frontmatter.
73    #[arg(long)]
74    pub index: bool,
75
76    /// Prepend a dated entry to the bundle's log.md (use --log-kind to label it).
77    #[arg(long, value_name = "MESSAGE")]
78    pub log: Option<String>,
79
80    /// Set or update a frontmatter field on the --file concept: FIELD=VALUE.
81    #[arg(long, value_name = "FIELD=VALUE")]
82    pub set: Option<String>,
83
84    /// Run a .ctb script of new/set/log/index/init items atomically: simulate the whole batch, write only if every op succeeds.
85    #[arg(long, value_name = "PATH")]
86    pub script: Option<PathBuf>,
87
88    /// With --script: simulate and print the plan, but write nothing.
89    #[arg(long)]
90    pub dry_run: bool,
91
92    /// With --script: the directive prefix for script lines (default "#%").
93    #[arg(long, value_name = "STR")]
94    pub fence: Option<String>,
95
96    // ----- authoring / filtering parameters -----
97    /// The concept `type` for --new; also filters --list to this type.
98    #[arg(long = "type", value_name = "TYPE")]
99    pub type_: Option<String>,
100
101    /// With --new: the concept title.
102    #[arg(long)]
103    pub title: Option<String>,
104
105    /// With --new: the concept description (one sentence).
106    #[arg(long)]
107    pub description: Option<String>,
108
109    /// With --new: tags (comma-separated); also filters --list to concepts carrying all given tags.
110    #[arg(long, value_delimiter = ',')]
111    pub tag: Vec<String>,
112
113    /// With --log: the entry label (e.g. Update, Creation). Default: Update.
114    #[arg(long, value_name = "LABEL")]
115    pub log_kind: Option<String>,
116
117    /// With --set: the concept file to edit.
118    #[arg(long, value_name = "PATH")]
119    pub file: Option<PathBuf>,
120
121    /// With --validate / --links: also treat broken bundle-relative links as failures.
122    #[arg(long)]
123    pub strict: bool,
124
125    // ----- framed verdict (for --validate / --links) -----
126    /// Question this check answers, framing it as a test; printed as a "== ... ==" banner unless --quiet.
127    #[arg(long)]
128    pub question: Option<String>,
129
130    /// Verdict expectation over the violation count: any|none|N|=N|+N|-N. Default: none (every concept conforms / no broken links).
131    #[arg(long)]
132    pub expect: Option<String>,
133
134    /// Template written to stdout after a check. Tokens: {RESULT} {QUESTION} {COUNT} {TOTAL} {BASE} {MATCHES} ({COUNT} is the violation count).
135    #[arg(long, alias = "emit-stdout")]
136    pub emit: Option<String>,
137
138    /// Template written to stderr after a check (same tokens as --emit).
139    #[arg(long)]
140    pub emit_stderr: Option<String>,
141
142    /// Suppress informational output; report via exit status (and --emit, which still fires).
143    #[arg(long)]
144    pub quiet: bool,
145
146    /// Emit a structured JSON result instead of text.
147    #[arg(long)]
148    pub json: bool,
149
150    /// Like `--json`, but pretty-printed (indented).
151    #[arg(long)]
152    pub json_pretty: bool,
153
154    /// Abort with exit 2 if the run exceeds SECS seconds (fractional allowed).
155    #[arg(long, value_name = "SECS")]
156    pub timeout: Option<f64>,
157
158    #[command(flatten)]
159    pub heartbeat: HeartbeatOpts,
160
161    /// Print agent usage docs (md or json) and exit.
162    #[arg(long, value_enum, num_args = 0..=1, default_missing_value = "md")]
163    pub explain: Option<Format>,
164}