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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
//! Path builders for the moadim jobs and handlers directory layout.
use OsString;
use PathBuf;
/// Environment variable that, when set, overrides the home directory all moadim paths resolve
/// under. Used by tests to redirect config/routines/jobs/agents/workbenches into a tempdir so they
/// never read or write the user's real `~/.config/moadim`.
const HOME_OVERRIDE_ENV: &str = "MOADIM_HOME_OVERRIDE";
/// Environment variable from the XDG Base Directory spec that relocates the user's config root.
const XDG_CONFIG_HOME_ENV: &str = "XDG_CONFIG_HOME";
/// Resolve the base home directory, honoring the [`HOME_OVERRIDE_ENV`] test seam when set.
///
/// Exposed to the crate so platform service installers resolve their home-relative paths (e.g. the
/// macOS `LaunchAgents` plist) through the same override seam, keeping tests off the real home.
pub
/// Resolve the config root the moadim config tree nests under, honoring the XDG Base Directory
/// spec.
///
/// When `$XDG_CONFIG_HOME` is set to an **absolute** path it is used verbatim; an unset, empty, or
/// relative value falls back to `$HOME/.config`. This mirrors the `dirs` crate that the Linux
/// systemd installer ([`crate::service`]) already uses for the unit path, so a user who relocates
/// their config root via `$XDG_CONFIG_HOME` gets a single coherent config tree instead of a
/// surprise second one under `~/.config`.
/// Resolve the config root from an explicit `$XDG_CONFIG_HOME` value and home directory.
///
/// Split out from [`config_root`] so the resolution rules are unit-testable without mutating
/// process-global environment variables. A relative `$XDG_CONFIG_HOME` is ignored, per the spec
/// ("All paths set in these environment variables must be absolute"). Falls back to `.` when the
/// home directory is undeterminable.
/// Returns the moadim config directory: `$XDG_CONFIG_HOME/moadim`, defaulting to `~/.config/moadim`.
// ─── Routines ────────────────────────────────────────────────────────────────
/// Returns the path to `{config_dir}/routines/` (default `~/.config/moadim/routines/`).
/// Returns the path to `{routines_dir}/{id}/`.
/// Returns the path to `{routines_dir}/README.md`, a daemon-generated orientation doc explaining
/// the per-routine directory layout.
/// Returns the path to `{routines_dir}/{id}/routine.toml`, the tracked routine metadata.
/// Returns the path to `{routines_dir}/{id}/schedule.cron`, the routine's tracked cron entry.
/// Returns the path to `{routines_dir}/{id}/prompts/`.
/// Returns the path to `{routines_dir}/{id}/prompts/prompt.pure.md`, the raw user-authored prompt.
/// Returns the path to `{routines_dir}/{id}/prompts/prompt.compiled.local.md`, the composed prompt
/// (repositories preamble + pure prompt) that the launch command copies into the workbench.
///
/// `.local.` keeps it matching the config `.gitignore`'s `*.local.*` pattern: it is fully derived
/// from `prompt.pure.md` + `routine.toml` and rewritten on every [`crate::routine_storage::write_routine`]
/// call, so (unlike `prompt.pure.md`) it should never be tracked (issue #1046).
/// Returns the path to `{routines_dir}/{id}/.gitignore`, the legacy per-routine gitignore an
/// older daemon generated. No longer written (the config dir's root `.gitignore` covers every
/// routine directory recursively); an existing file is left untouched, since it may carry
/// user-added patterns. Test-only: production code no longer touches this path.
/// Returns the path to `{routines_dir}/{id}/state.local.toml`, the gitignored sidecar holding
/// daemon-written runtime state (`snoozed_until`, `skip_runs`) kept out of the tracked `routine.toml`.
///
/// The `.local.` infix matches the `*.local.*` pattern seeded into the config `.gitignore`, so
/// snooze churn never produces version-control diffs.
/// Returns the path to `{routines_dir}/{id}/routine.local.toml`, the gitignored sidecar a human
/// (not the daemon) edits directly to layer secret or machine-local environment variable
/// overrides on top of `routine.toml`'s tracked `[env]` table — see
/// [`crate::routines::build_routine_command`] and issue #408.
///
/// The `.local.` infix matches the `*.local.*` pattern seeded into the config `.gitignore`, so it
/// is never accidentally committed.
/// Returns the path to `{routines_dir}/{id}/scheduled.log`, the gitignored append-only log that
/// records every scheduled (cron) firing as one Unix-timestamp line.
///
/// The cron shell command appends a line (`printf '%s\n' "$TS" >> scheduled.log`) at each firing;
/// the daemon reads only the last line to derive `last_scheduled_trigger_at`. The `.log` suffix
/// matches the `*.log` pattern seeded into the config `.gitignore`.
/// Returns the path to `{routines_dir}/{id}/manual.log`, the gitignored append-only log that
/// records every manual trigger as one Unix-timestamp line.
///
/// The daemon appends a line at each manual trigger; reading the last line gives
/// `last_manual_trigger_at`. The `.log` suffix matches the `*.log` pattern in the config
/// `.gitignore`.
/// Returns the path to `{routines_dir}/{id}/skip.log`, the gitignored append-only log recording
/// why a trigger did not spawn a workbench (agent load failure, an oversized inline prompt, the
/// per-routine overlap guard, or the global concurrency cap — see
/// `crate::routines::service_trigger::spawn_routine_command`).
///
/// Without this, a skipped trigger left no trace anywhere a caller could read back: `routine_logs`
/// looks up the newest *workbench's* `agent.log`, and a skipped trigger never creates a workbench
/// (#1145). The `.log` suffix matches the `*.log` pattern in the config `.gitignore`.
/// Returns the path to `{routines_dir}/{id}/runs.log`, the gitignored append-only NDJSON log of
/// every finished run's outcome, keyed by the routine's stable UUID (unlike its workbenches, which
/// are keyed by slug and reaped after their TTL).
///
/// One compact JSON object is appended per run, right before its workbench is reaped (see
/// `routines::cleanup::reap_dir`), so run history survives past workbench retention instead of
/// disappearing the moment its workbench directory is removed. The `.log` suffix matches the
/// `*.log` pattern seeded into the config `.gitignore`.
/// Returns the path to `{config_dir}/removed_defaults.local.toml`, the gitignored file recording
/// which built-in default routines the user has explicitly deleted, so
/// [`crate::routines::ensure_default_routines`] does not resurrect them on the next startup. The
/// `.local.` infix matches the `*.local.*` pattern seeded into the config `.gitignore`.
/// Returns the path to `{routines_dir}/{id}/run.sh`, a legacy per-routine launch script.
///
/// No longer generated — the crontab line now invokes `moadim schedule trigger <id>` directly. The
/// path is retained so [`crate::routine_storage::write_routine`] can delete any stale script left by
/// an older daemon.
/// Returns the path to `{routines_dir}/{id}/flags/`, holding one file per open flag an agent (or a
/// human, via MCP/HTTP) has raised against the routine — a gap, bug, edge case, or question it
/// couldn't resolve mid-run. See [`crate::routines::flags`].
// ─── Agent registry ──────────────────────────────────────────────────────────
/// Returns the path to `{config_dir}/agents/` (default `~/.config/moadim/agents/`).
/// Returns the path to `{agents_dir}/{name}.toml`.
/// Returns the path to `{agents_dir}/README.md`, a daemon-generated orientation doc explaining the
/// agent registry's file format.
// ─── Daemon runtime files ────────────────────────────────────────────────────
/// Returns the path to `{config_dir}/moadim.pid`, where the running server records its PID.
/// Returns the path to `{config_dir}/daemon.log`, where a backgrounded server writes its output.
/// Returns the path to `{config_dir}/.gitignore`, used to keep generated runtime
/// files (`*.pid`, `*.log`) out of version control when the config dir is tracked.
/// Returns the path to `{config_dir}/README.md`, a daemon-generated orientation doc explaining the
/// config tree's layout for anyone who opens or git-tracks it directly.
/// Returns the path to `~/.config/moadim/.lock`, a committed global lock that halts all routine
/// scheduling and manual triggers when present. Checked into version control so the lock can be
/// shared across machines via a git push/pull.
/// Returns the path to `~/.config/moadim/.local.lock`, a machine-local global lock that halts all
/// routine scheduling and manual triggers when present. The `.local.` infix matches the `*.local.*`
/// pattern seeded into the config `.gitignore`, so this sentinel never leaks into version control.
/// Returns the path to `~/.config/moadim/install_prompt.local.marker`, a machine-local sentinel
/// recording that the post-start "install as a system service?" prompt (see
/// [`crate::cli::run_background`]) has already been shown, so it fires at most once regardless of
/// the answer given. The `.local.` infix matches the `*.local.*` pattern seeded into the config
/// `.gitignore`, so this sentinel never leaks into a shared config repo.
/// Returns the path to `~/.config/moadim/machine.local.toml`, the gitignored, per-machine file
/// that records this install's machine identity (the `name` used to match a routine/job's
/// `machines` targeting list). The `.local.` infix matches the `*.local.*` pattern seeded into the
/// config `.gitignore`, so a machine name set on one host never leaks into the shared config repo.
/// Returns the machine-config path under `home`, or `.` if `home` is `None`.
pub
// ─── System prompts ──────────────────────────────────────────────────────────
/// Returns the path to `{config_dir}/user_prompt.md`, where the user writes a persistent
/// system prompt injected into every agent workbench `CLAUDE.md` alongside the moadim prompt.
// ─── Workbenches ─────────────────────────────────────────────────────────────
/// Returns the path to `~/.moadim/`.
/// Returns the moadim home directory under `home`, or `.` if `home` is `None`.
pub
/// Returns the path to `~/.moadim/workbenches/`.
// ─── Claude Code shared config ───────────────────────────────────────────────
/// Returns the path to `~/.claude.json`, the Claude Code config file shared with the live `claude`
/// process. The built-in `claude` agent's `setup` step seeds a per-workbench `projects` entry here
/// on every run (see `crate::routines::agents`); `crate::utils::claude_json` prunes that entry
/// once the cleanup sweep (`crate::routines::cleanup`) reaps the workbench, so the file does not
/// grow unbounded.
///
/// `None` when the home directory cannot be resolved.