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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
//! Subcommand implementations for the `doiget` CLI.
//!
//! Each module corresponds to a single `clap` subcommand declared in
//! `main.rs`. The dispatch table in `main.rs` calls `run(...)` on the
//! matching module. Subcommands return `anyhow::Result<()>`; any error
//! surfaces via the CLI's top-level error reporter (stderr).
//!
//! ## Phase 1 surface (so far)
//!
//! - [`audit_log`] — `doiget audit-log --verify` recomputes the SHA-256 hash
//! chain on the provenance log and reports any mismatches.
//! - [`batch`] — `doiget batch <path>` multi-ref orchestrator (rate-bounded).
//! - [`bib`] — `doiget bib <ref>` BibTeX exporter (Phase 2 starter).
//! - [`cite`] — `doiget cite <ref>` live-resolve BibTeX (doi2bib-style).
//! - [`config`] — `doiget config show/path/doctor`.
//! - [`csl`] — `doiget csl <ref>` exports a stored entry as CSL JSON 1.0.
//! - [`fetch`] — `doiget fetch <ref>` orchestrator (arXiv E2E + DOI metadata-only).
//! - [`info`] — prints a stored entry's `Metadata` as TOML on stdout.
//! - [`list_recent`] — prints up to N most-recently-fetched entries.
//! - [`search`] — case-insensitive substring search over stored metadata.
//!
//! `serve` has no module here: it delegates straight to `doiget-mcp`. It
//! is wired in `main.rs` and is what every `docs/INTEGRATION/` guide tells
//! users to run.
/// Parse a ref, or render the failure in the `docs/ERRORS.md` §3
/// "Researcher (CLI human)" form and return the CLI exit error.
///
/// #477. `error[CODE]: message` held on `fetch` alone -- #119 did it there
/// and nowhere else -- while `info`, `link`, `cite`, `text`, `tag`, `bib`,
/// `csl` and `source` each wrote their own `with_context("invalid ref: …")`
/// and let `anyhow` print a bare `Error:` plus a `Caused by:` chain. The
/// closed error-code set is a load-bearing promise of this project: a
/// caller is told it can key off `error[CODE]:`, and on eight of nine
/// commands there was no code to key off, while the `Caused by:` chain
/// leaked internal error types that are in no contract.
///
/// Same shape as `render_fetch_error` and for the same reason -- one
/// renderer, so a future change to the contract cannot reach some call
/// sites and miss others.
///
/// # Errors
///
/// Always, when parsing fails: an [`anyhow::Error`] wrapping
/// [`CliExit`](fetch::CliExit) with the `INVALID_REF` exit code. The
/// message has already been written to stderr.
/// The renderer on its own, for call sites holding a
/// [`doiget_core::RefParseError`] that did not come from
/// [`parse_ref_or_exit`].
///
/// Both now take the exit code from `fetch::cli_exit_code(InvalidRef)`,
/// which is **2** since #492 / ADR-0049. Before that `fetch` fell to the
/// generic `_ => 1` arm while `graph` hard-coded the 2 that
/// `docs/ERRORS.md` §4 prescribes, so one binary gave two answers for the
/// same input and each site's comment claimed agreement with the other.
///
/// One renderer and one exit code. `every_ref_taking_command_exits_2_for_
/// an_invalid_ref` is what keeps it that way.
// Phase 4 / Slice 16. Compile-gated by the `citation` Cargo feature
// (which itself enables `doiget-core/citation`).
use ;
use Utf8PathBuf;
/// Resolve the path of the user `config.toml`, for messages that need to
/// name the file the user must edit.
///
/// Same resolution as [`config::ResolvedConfig::from_env`]
/// (`<dirs::config_dir()>/doiget/config.toml`), kept here so the fetch-side
/// denial help (issue #405) and `config doctor` cannot drift. Returns
/// `None` only when the platform has no config dir at all, in which case
/// callers should fall back to naming the file generically — a missing
/// config dir must never turn an advisory line into a hard error.
pub
/// Where a resolved store root came from (#441).
///
/// Reported by `doiget config doctor` so that a setting which did nothing
/// can no longer look like a setting that worked. That was the sharpest
/// part of #441: `config init` recommended `[store] root`, `doctor`
/// confirmed the recommendation, and the value was never read.
pub
/// Resolve the on-disk store root.
///
/// Resolution order (ADR-0036, `docs/CONFIG.md` §4):
///
/// 1. `DOIGET_STORE_ROOT`, if set and non-empty. `--store-root` is applied
/// by writing this variable, so the flag rides the same rung.
/// 2. `[store] root` in the user's `config.toml`, expanded via
/// [`doiget_core::user_extension::expand_store_root`].
/// 3. `./papers` — `papers/` directly under the current working directory
/// (#344 / ADR-0036), so fetched artifacts are visible where the user
/// (or an LLM agent) is working rather than hidden in a far-off home
/// directory.
///
/// Rung 2 is new in #441. It was documented from the start — ADR-0036
/// states the order, `docs/CONFIG.md` §3 lists the key, `config init`
/// writes it into the template it generates and `config doctor` recommends
/// it — and read by nothing, so the store silently kept following the cwd.
/// The old doc comment on this function said the config rung "lands with
/// the `config` subcommand"; that subcommand shipped in 0.8.8 without it.
pub
/// [`resolve_store_root`] plus which rung answered.
pub
/// `[store] root` from the user's `config.toml`, if any.
///
/// Does not fail the command on a malformed file — the store root is
/// resolved by every subcommand, and one bad line should not be a total
/// outage — but it does NOT stay quiet about it.
///
/// The previous comment here justified silence by claiming "a parse error
/// surfaces with a proper diagnostic on the network path that owns this
/// file". The #468 review checked that claim and it is false for most
/// callers: `list-recent`, `search`, `info`, `tag`, `bib` and `csl` never
/// build an HTTP client, so that diagnostic never runs for them. TOML fails
/// the whole document, so a typo anywhere — even under `[network]` — makes
/// `[store] root` unreadable, and the command silently used `./papers`
/// under the cwd as though nothing had been configured.
///
/// `search` then reports zero results, indistinguishable from an empty
/// library. Worse, `tag` **writes** metadata into the wrong store and
/// reports success. A warning is the minimum; the resolved source is also
/// reported by `config doctor`.