aias 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +1 -0
  3. data/.github/workflows/deploy-github-pages.yml +52 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +140 -0
  6. data/COMMITS.md +196 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +249 -0
  9. data/Rakefile +27 -0
  10. data/aia_schedule_idea.md +256 -0
  11. data/docs/assets/images/logo.jpg +0 -0
  12. data/docs/cli/add.md +101 -0
  13. data/docs/cli/check.md +70 -0
  14. data/docs/cli/clear.md +45 -0
  15. data/docs/cli/dry-run.md +57 -0
  16. data/docs/cli/index.md +51 -0
  17. data/docs/cli/install.md +198 -0
  18. data/docs/cli/last.md +49 -0
  19. data/docs/cli/list.md +40 -0
  20. data/docs/cli/next.md +49 -0
  21. data/docs/cli/remove.md +87 -0
  22. data/docs/cli/show.md +54 -0
  23. data/docs/cli/uninstall.md +29 -0
  24. data/docs/cli/update.md +75 -0
  25. data/docs/getting-started/installation.md +69 -0
  26. data/docs/getting-started/quick-start.md +105 -0
  27. data/docs/guides/configuration-layering.md +168 -0
  28. data/docs/guides/cron-environment.md +112 -0
  29. data/docs/guides/scheduling-prompts.md +319 -0
  30. data/docs/guides/understanding-cron.md +134 -0
  31. data/docs/guides/validation.md +114 -0
  32. data/docs/index.md +100 -0
  33. data/docs/reference/api.md +409 -0
  34. data/docs/reference/architecture.md +122 -0
  35. data/docs/reference/environment.md +67 -0
  36. data/docs/reference/logging.md +73 -0
  37. data/example_prompts/code_health_check.md +51 -0
  38. data/example_prompts/daily_digest.md +19 -0
  39. data/example_prompts/morning_standup.md +19 -0
  40. data/example_prompts/reports/monthly_review.md +44 -0
  41. data/example_prompts/reports/weekly_summary.md +22 -0
  42. data/example_prompts/you_are_good.md +22 -0
  43. data/exe/aias +5 -0
  44. data/lib/aias/block_parser.rb +42 -0
  45. data/lib/aias/cli/add.rb +30 -0
  46. data/lib/aias/cli/check.rb +50 -0
  47. data/lib/aias/cli/clear.rb +11 -0
  48. data/lib/aias/cli/dry_run.rb +24 -0
  49. data/lib/aias/cli/install.rb +57 -0
  50. data/lib/aias/cli/last.rb +26 -0
  51. data/lib/aias/cli/list.rb +20 -0
  52. data/lib/aias/cli/next.rb +28 -0
  53. data/lib/aias/cli/remove.rb +14 -0
  54. data/lib/aias/cli/show.rb +18 -0
  55. data/lib/aias/cli/uninstall.rb +15 -0
  56. data/lib/aias/cli/update.rb +30 -0
  57. data/lib/aias/cli/version.rb +10 -0
  58. data/lib/aias/cli.rb +91 -0
  59. data/lib/aias/cron_describer.rb +142 -0
  60. data/lib/aias/crontab_manager.rb +140 -0
  61. data/lib/aias/env_file.rb +75 -0
  62. data/lib/aias/job_builder.rb +56 -0
  63. data/lib/aias/paths.rb +14 -0
  64. data/lib/aias/prompt_scanner.rb +111 -0
  65. data/lib/aias/schedule_config.rb +31 -0
  66. data/lib/aias/validator.rb +101 -0
  67. data/lib/aias/version.rb +5 -0
  68. data/lib/aias.rb +17 -0
  69. data/mkdocs.yml +137 -0
  70. data/sig/aias.rbs +4 -0
  71. metadata +191 -0
@@ -0,0 +1,409 @@
1
+ # API Reference
2
+
3
+ All public classes live under the `Aias::` namespace and are autoloaded by Zeitwerk when `require "aias"` is called.
4
+
5
+ ---
6
+
7
+ ## `Aias::CLI`
8
+
9
+ **Inherits:** `Thor`
10
+
11
+ The command-line interface. All eight commands are public instance methods. `CLI.start(ARGV)` is the gem entry point called by the `aias` binary.
12
+
13
+ ### Class Methods
14
+
15
+ #### `.exit_on_failure?`
16
+
17
+ Returns `true`. Thor uses this to call `exit(1)` when a command raises an unhandled error.
18
+
19
+ ### Instance Methods
20
+
21
+ #### `#update`
22
+
23
+ Scan, validate, and install all scheduled prompts.
24
+
25
+ ```ruby
26
+ cli = Aias::CLI.new
27
+ cli.update
28
+ ```
29
+
30
+ #### `#add(path)`
31
+
32
+ Add or replace a single prompt's cron job. `path` is an absolute or relative path to the prompt file.
33
+
34
+ ```ruby
35
+ cli.add("/Users/you/.prompts/standup.md")
36
+ cli.add("./example_prompts/morning_standup.md")
37
+ ```
38
+
39
+ Raises `SystemExit(1)` when the file is missing, outside the prompts directory, has no `schedule:` key, or fails validation.
40
+
41
+ See [`aias add`](../cli/add.md) for full behaviour and upsert semantics.
42
+
43
+ #### `#check`
44
+
45
+ Print a diff between scheduled prompts and installed jobs.
46
+
47
+ ```ruby
48
+ cli.check
49
+ ```
50
+
51
+ #### `#list`
52
+
53
+ Print a table of all installed jobs.
54
+
55
+ ```ruby
56
+ cli.list
57
+ ```
58
+
59
+ #### `#dry_run`
60
+
61
+ Print the cron output that `update` would write, without touching the crontab.
62
+
63
+ ```ruby
64
+ cli.dry_run
65
+ ```
66
+
67
+ #### `#show(prompt_id)`
68
+
69
+ Print details for a single installed job. Exits 1 if not found.
70
+
71
+ ```ruby
72
+ cli.show("daily_digest")
73
+ cli.show("reports/weekly")
74
+ ```
75
+
76
+ #### `#upcoming(n = "5")`
77
+
78
+ Print the next scheduled run time for installed jobs, computed via `fugit`. Aliased as `aias next` on the command line.
79
+
80
+ ```ruby
81
+ cli.upcoming
82
+ cli.upcoming("10")
83
+ ```
84
+
85
+ #### `#last_run(n = "5")`
86
+
87
+ Print the last-run time for installed jobs, derived from the log file modification timestamp. Aliased as `aias last` on the command line.
88
+
89
+ ```ruby
90
+ cli.last_run
91
+ cli.last_run("10")
92
+ ```
93
+
94
+ #### `#clear`
95
+
96
+ Remove all aias-managed crontab entries.
97
+
98
+ ```ruby
99
+ cli.clear
100
+ ```
101
+
102
+ ### Dependency Injection
103
+
104
+ Collaborators are initialized lazily via private accessors. Set instance variables before invoking a command to inject alternatives:
105
+
106
+ ```ruby
107
+ cli = Aias::CLI.new
108
+ cli.instance_variable_set(:@validator, Aias::Validator.new(binary_to_check: "ruby"))
109
+ cli.instance_variable_set(:@scanner, Aias::PromptScanner.new(prompts_dir: "/tmp/test"))
110
+ cli.update
111
+ ```
112
+
113
+ ---
114
+
115
+ ## `Aias::PromptScanner`
116
+
117
+ Discovers AIA prompt files that declare a `schedule:` key and parses their frontmatter.
118
+
119
+ ### `::Result`
120
+
121
+ ```ruby
122
+ Aias::PromptScanner::Result = Data.define(:prompt_id, :schedule, :metadata, :file_path)
123
+ ```
124
+
125
+ A frozen, immutable value object. Fields:
126
+
127
+ | Field | Type | Description |
128
+ |---|---|---|
129
+ | `prompt_id` | `String` | Subpath relative to prompts dir, no `.md` extension |
130
+ | `schedule` | `String` | Raw value of the `schedule:` frontmatter key |
131
+ | `metadata` | `PM::Metadata` | Full parsed frontmatter object |
132
+ | `file_path` | `String` | Absolute path to the prompt file |
133
+
134
+ ### `.new`
135
+
136
+ ```ruby
137
+ scanner = Aias::PromptScanner.new
138
+ scanner = Aias::PromptScanner.new(prompts_dir: "/path/to/prompts")
139
+ ```
140
+
141
+ | Parameter | Default | Description |
142
+ |---|---|---|
143
+ | `prompts_dir:` | `ENV["AIA_PROMPTS_DIR"]` | Directory to scan |
144
+
145
+ ### `#scan`
146
+
147
+ ```ruby
148
+ results = scanner.scan # => Array<Aias::PromptScanner::Result>
149
+ ```
150
+
151
+ Returns an array of `Result` objects for every prompt that has a non-empty `schedule:` key.
152
+
153
+ **Raises** `Aias::Error` when:
154
+
155
+ - `prompts_dir` is nil or empty (AIA_PROMPTS_DIR not set)
156
+ - The directory does not exist
157
+ - The directory is not readable
158
+
159
+ Prompts with unparseable frontmatter are warned to stderr and skipped (no exception).
160
+
161
+ ### `#scan_one`
162
+
163
+ ```ruby
164
+ result = scanner.scan_one(path) # => Aias::PromptScanner::Result
165
+ ```
166
+
167
+ Parses a single prompt file by path (absolute or relative). Returns a `Result` on success.
168
+
169
+ **Raises** `Aias::Error` when:
170
+
171
+ | Condition | Message |
172
+ |---|---|
173
+ | File does not exist | `"Prompt file not found: <path>"` |
174
+ | File is not readable | `"Prompt file not readable: <path>"` |
175
+ | File is outside `prompts_dir` | `"'<path>' is not inside the prompts directory '<dir>'"` |
176
+ | No `schedule:` in frontmatter | `"'<prompt_id>' has no schedule: in its frontmatter"` |
177
+ | Frontmatter is unparseable | `"Failed to parse '<prompt_id>': <original error>"` |
178
+ | `prompts_dir` missing or unreadable | Same as `#scan` |
179
+
180
+ Unlike `#scan`, `scan_one` never silently skips — every error condition is a raised exception.
181
+
182
+ ---
183
+
184
+ ## `Aias::Validator`
185
+
186
+ Validates a `PromptScanner::Result` against three rules: schedule syntax, parameter completeness, and `aia` binary presence.
187
+
188
+ ### `::ValidationResult`
189
+
190
+ ```ruby
191
+ Aias::Validator::ValidationResult = Data.define(:valid?, :errors)
192
+ ```
193
+
194
+ A frozen, immutable value object. Fields:
195
+
196
+ | Field | Type | Description |
197
+ |---|---|---|
198
+ | `valid?` | `Boolean` | `true` when all checks pass |
199
+ | `errors` | `Array<String>` | Human-readable error messages (empty when valid) |
200
+
201
+ ### `.new`
202
+
203
+ ```ruby
204
+ validator = Aias::Validator.new
205
+ validator = Aias::Validator.new(
206
+ shell: "/bin/bash",
207
+ binary_to_check: "aia",
208
+ fallback_dirs: Aias::Validator::BINARY_FALLBACK_DIRS
209
+ )
210
+ ```
211
+
212
+ | Parameter | Default | Description |
213
+ |---|---|---|
214
+ | `shell:` | `ENV["SHELL"]` or `"/bin/bash"` | Login shell used for binary check |
215
+ | `binary_to_check:` | `"aia"` | Binary name to locate |
216
+ | `fallback_dirs:` | `BINARY_FALLBACK_DIRS` | Directories scanned when the login-shell check fails |
217
+
218
+ `binary_to_check:` and `fallback_dirs:` are primarily useful in tests — pass `"ruby"` for a check that always succeeds, or an empty/nonexistent directory list for one that always fails.
219
+
220
+ ### `::BINARY_FALLBACK_DIRS`
221
+
222
+ ```ruby
223
+ Aias::Validator::BINARY_FALLBACK_DIRS
224
+ # => [
225
+ # "~/.rbenv/shims",
226
+ # "~/.rbenv/bin",
227
+ # "~/.rvm/bin",
228
+ # "~/.asdf/shims",
229
+ # "/usr/local/bin",
230
+ # "/usr/bin",
231
+ # "/opt/homebrew/bin"
232
+ # ]
233
+ ```
234
+
235
+ The default fallback directory list. Covers rbenv, rvm, asdf, Homebrew (Intel + Apple Silicon), and the standard system paths.
236
+
237
+ ### `#validate`
238
+
239
+ ```ruby
240
+ vr = validator.validate(scanner_result) # => Aias::Validator::ValidationResult
241
+ vr.valid? # => true or false
242
+ vr.errors # => ["Schedule 'x': ...", "Parameter 'y' has no default ..."]
243
+ ```
244
+
245
+ The binary check result is memoised per `Validator` instance and only runs once.
246
+
247
+ ### `#binary_in_fallback_location?`
248
+
249
+ ```ruby
250
+ validator.binary_in_fallback_location? # => true or false
251
+ ```
252
+
253
+ Returns `true` when an executable file named `binary_to_check` exists in any of the `fallback_dirs`. Called automatically by `#validate`; exposed publicly for diagnostics.
254
+
255
+ ---
256
+
257
+ ## `Aias::JobBuilder`
258
+
259
+ Converts a `PromptScanner::Result` into a raw cron line string. Pure function — no I/O.
260
+
261
+ ### `.new`
262
+
263
+ ```ruby
264
+ builder = Aias::JobBuilder.new
265
+ builder = Aias::JobBuilder.new(shell: "/bin/zsh")
266
+ builder = Aias::JobBuilder.new(shell: "/bin/zsh", prompts_dir: "/path/to/prompts")
267
+ ```
268
+
269
+ | Parameter | Default | Description |
270
+ |---|---|---|
271
+ | `shell:` | `ENV["SHELL"]` or `"/bin/bash"` | Login shell binary used to wrap the `aia` invocation |
272
+ | `prompts_dir:` | `nil` | When set, appends `--prompts-dir DIR` to every generated `aia` command |
273
+
274
+ ### `#build`
275
+
276
+ ```ruby
277
+ cron_line = builder.build(scanner_result) # => String
278
+ ```
279
+
280
+ Returns a fully-formed cron line string suitable for passing to `CrontabManager#install` or `#add_job`. Uses `fugit` to resolve the `schedule:` value to a canonical 5-field cron expression.
281
+
282
+ **Without `prompts_dir`:**
283
+
284
+ ```
285
+ 0 8 * * * /bin/zsh -l -c 'aia daily_digest >> /Users/you/.aia/schedule/logs/daily_digest.log 2>&1'
286
+ ```
287
+
288
+ **With `prompts_dir: "/data/prompts"`:**
289
+
290
+ ```
291
+ 0 8 * * * /bin/zsh -l -c 'aia --prompts-dir /data/prompts daily_digest >> /Users/you/.aia/schedule/logs/daily_digest.log 2>&1'
292
+ ```
293
+
294
+ ### `#log_path_for`
295
+
296
+ ```ruby
297
+ builder.log_path_for("daily_digest") # => "/Users/you/.aia/schedule/logs/daily_digest.log"
298
+ builder.log_path_for("reports/weekly") # => "/Users/you/.aia/schedule/logs/reports/weekly.log"
299
+ ```
300
+
301
+ Returns the absolute log path for a given prompt ID.
302
+
303
+ ---
304
+
305
+ ## `Aias::CrontabManager`
306
+
307
+ Manages the aias-owned block in the user's crontab by directly invoking the system `crontab(1)` command via `Open3`.
308
+
309
+ ### Constants
310
+
311
+ | Constant | Value | Description |
312
+ |---|---|---|
313
+ | `IDENTIFIER` | `"aias"` | Whenever identifier that marks the managed block |
314
+ | `LOG_BASE` | `~/.aia/schedule/logs` | Default base directory for log files |
315
+ | `ENTRY_RE` | Regexp | Parses installed cron lines to extract `prompt_id`, `cron_expr`, `log_path` |
316
+
317
+ ### `.new`
318
+
319
+ ```ruby
320
+ manager = Aias::CrontabManager.new
321
+ manager = Aias::CrontabManager.new(crontab_command: "crontab", log_base: "/custom/log/base")
322
+ ```
323
+
324
+ | Parameter | Default | Description |
325
+ |---|---|---|
326
+ | `crontab_command:` | `"crontab"` | Path to the `crontab(1)` binary |
327
+ | `log_base:` | `LOG_BASE` | Base directory for log files |
328
+
329
+ The `crontab_command:` parameter makes the manager testable with a fake crontab script backed by a tmpfile, with no system crontab involvement.
330
+
331
+ ### `#install`
332
+
333
+ ```ruby
334
+ manager.install(cron_lines)
335
+ ```
336
+
337
+ Accepts a single cron line string or an array of cron line strings. Creates `@log_base` if absent, then replaces the entire `aias`-managed crontab block by piping the new content to `crontab -` via `Open3`.
338
+
339
+ **Raises** `Aias::Error` if the `crontab` command exits non-zero.
340
+
341
+ ### `#add_job`
342
+
343
+ ```ruby
344
+ manager.add_job(cron_line, prompt_id)
345
+ ```
346
+
347
+ Upserts a single cron entry into the aias-managed block. Any existing entry whose `prompt_id` matches is removed first; the new `cron_line` is appended. All other managed entries are left unchanged. Non-aias crontab entries are never touched.
348
+
349
+ `cron_line` is a fully-formed cron line as produced by `JobBuilder#build`:
350
+
351
+ ```
352
+ 0 9 * * 1-5 /bin/zsh -l -c 'aia standup >> ~/.aia/schedule/logs/standup.log 2>&1'
353
+ ```
354
+
355
+ Creates `@log_base` if absent. **Raises** `Aias::Error` if the crontab write fails.
356
+
357
+ ### `#clear`
358
+
359
+ ```ruby
360
+ manager.clear
361
+ ```
362
+
363
+ Removes the `# BEGIN aias` … `# END aias` block from the crontab and rewrites it via `crontab -`. Non-aias entries are untouched.
364
+
365
+ ### `#dry_run`
366
+
367
+ ```ruby
368
+ output = manager.dry_run(cron_lines) # => String
369
+ ```
370
+
371
+ Returns the cron lines that would be installed joined by newlines, without making any system calls or writing to the crontab.
372
+
373
+ ### `#installed_jobs`
374
+
375
+ ```ruby
376
+ jobs = manager.installed_jobs
377
+ # => [
378
+ # { prompt_id: "daily_digest", cron_expr: "0 8 * * *", log_path: "/..." },
379
+ # { prompt_id: "reports/weekly", cron_expr: "0 9 * * 1", log_path: "/..." }
380
+ # ]
381
+ ```
382
+
383
+ Reads the current crontab and parses the `aias`-managed block. Returns an empty array when no block is installed.
384
+
385
+ ### `#current_block`
386
+
387
+ ```ruby
388
+ block = manager.current_block # => String
389
+ ```
390
+
391
+ Returns the raw text between the `# BEGIN aias` and `# END aias` marker lines (markers excluded). Returns `""` when no block exists.
392
+
393
+ ### `#ensure_log_directories`
394
+
395
+ ```ruby
396
+ manager.ensure_log_directories(["daily_digest", "reports/weekly"])
397
+ ```
398
+
399
+ Creates the log subdirectory structure under `@log_base` for each prompt ID. Called by `CLI#update` before installing.
400
+
401
+ ---
402
+
403
+ ## `Aias::Error`
404
+
405
+ ```ruby
406
+ class Aias::Error < StandardError; end
407
+ ```
408
+
409
+ Raised for unrecoverable errors: missing prompts directory, crontab write failure. Caught by `CLI` which prints the message and exits 1.
@@ -0,0 +1,122 @@
1
+ # Architecture
2
+
3
+ ## Overview
4
+
5
+ `aias` is a thin orchestration layer over three well-established tools: `grep` (for discovery), `PM::Metadata` from the `prompt_manager` gem (for frontmatter parsing), and `fugit` (for cron expression parsing and natural-language schedule resolution). Its five classes have narrow, well-defined responsibilities.
6
+
7
+ ```
8
+ ┌─────────────────────────────────────────────────────────────────┐
9
+ │ aias gem │
10
+ │ │
11
+ │ Aias::CLI │
12
+ │ ├── Aias::PromptScanner grep -rl + PM::Metadata │
13
+ │ ├── Aias::Validator schedule syntax + params + binary │
14
+ │ ├── Aias::JobBuilder prompt ID + schedule → cron line │
15
+ │ └── Aias::CrontabManager crontab(1) read/write │
16
+ └─────────────────────────────────────────────────────────────────┘
17
+ ```
18
+
19
+ ## Data Flow
20
+
21
+ ```
22
+ $AIA_PROMPTS_DIR
23
+
24
+
25
+ PromptScanner#scan
26
+ │ returns Array<PromptScanner::Result>
27
+ │ Result = Data.define(:prompt_id, :schedule, :metadata, :file_path)
28
+
29
+ Validator#validate(result)
30
+ │ returns ValidationResult = Data.define(:valid?, :errors)
31
+
32
+ [partition into valid / invalid]
33
+
34
+ ├── invalid → warn to stderr, skip
35
+
36
+ └── valid ──► JobBuilder#build(result)
37
+ │ returns String (raw cron line)
38
+
39
+ CrontabManager#install(cron_lines)
40
+ │ writes via crontab(1) command
41
+
42
+ crontab (OS cron daemon manages execution)
43
+ ```
44
+
45
+ ## Class Responsibilities
46
+
47
+ ### `Aias::CLI`
48
+
49
+ The entry point. A `Thor` subclass that exposes nine commands. CLI delegates all domain logic to its four collaborators; it only formats output and handles errors.
50
+
51
+ Collaborators are created lazily and accessible via private accessors (`scanner`, `validator`, `builder`, `manager`). Tests inject replacements by setting instance variables before calling a command.
52
+
53
+ ### `Aias::PromptScanner`
54
+
55
+ Discovers scheduled prompts in two steps:
56
+
57
+ 1. **grep phase**: runs `grep -rl "schedule:" $AIA_PROMPTS_DIR` via `Open3.capture3` (no shell injection)
58
+ 2. **parse phase**: for each candidate file, calls `PM.parse(absolute_path)` and reads `metadata.schedule`
59
+
60
+ Returns an array of frozen `Result` value objects. Raises `Aias::Error` if the prompts directory is missing or unreadable.
61
+
62
+ ### `Aias::Validator`
63
+
64
+ Validates a single `PromptScanner::Result` against three rules (see [Validation](../guides/validation.md)). Returns a frozen `ValidationResult` value object.
65
+
66
+ The `aia` binary check is memoised — it runs at most once per `Validator` instance.
67
+
68
+ The `binary_to_check:` constructor parameter makes the binary check testable without stubs.
69
+
70
+ ### `Aias::JobBuilder`
71
+
72
+ Pure function: converts a `PromptScanner::Result` into a raw cron line string. No I/O, no side effects.
73
+
74
+ Uses `fugit` to resolve the `schedule:` value to a canonical 5-field cron expression, then assembles:
75
+
76
+ ```
77
+ <cron_expr> <shell> -l -c 'aia [--prompts-dir DIR] <prompt_id> >> <log> 2>&1'
78
+ ```
79
+
80
+ Uses `ENV["SHELL"]` (defaulting to `/bin/bash`) as the login shell. Log paths follow the pattern `~/.aia/schedule/logs/<prompt_id>.log`.
81
+
82
+ ### `Aias::CrontabManager`
83
+
84
+ Manages the aias-owned block in the user's crontab by directly invoking the system `crontab(1)` command via `Open3`. Three categories of operations:
85
+
86
+ | Category | Methods | How |
87
+ |---|---|---|
88
+ | Write | `install(cron_lines)`, `add_job(cron_line, prompt_id)`, `clear` | `Open3.popen2(crontab_command, "-")` — pipes new content to stdin |
89
+ | Read | `installed_jobs`, `current_block` | `Open3.capture3(crontab_command, "-l")` |
90
+ | Preview | `dry_run(cron_lines)` | `Array(cron_lines).join("\n")` — no system calls |
91
+
92
+ The managed block is delimited by `# BEGIN aias` and `# END aias` marker comments. All other crontab entries are preserved verbatim.
93
+
94
+ The `crontab_command:` and `log_base:` constructor parameters make the manager fully testable with a fake crontab script backed by a tmpfile — no system crontab is ever touched during tests.
95
+
96
+ ## Key Value Objects
97
+
98
+ Both value objects are defined with `Data.define` (Ruby 3.2+) and are automatically frozen and immutable.
99
+
100
+ ```ruby
101
+ # Returned by PromptScanner#scan
102
+ Aias::PromptScanner::Result = Data.define(:prompt_id, :schedule, :metadata, :file_path)
103
+
104
+ # Returned by Validator#validate
105
+ Aias::Validator::ValidationResult = Data.define(:valid?, :errors)
106
+ ```
107
+
108
+ ## Autoloading
109
+
110
+ Zeitwerk handles autoloading. The `cli` filename is mapped to `Aias::CLI` (not `Aias::Cli`) via a custom inflection:
111
+
112
+ ```ruby
113
+ loader = Zeitwerk::Loader.for_gem
114
+ loader.inflector.inflect("cli" => "CLI")
115
+ loader.setup
116
+ ```
117
+
118
+ ## Error Handling
119
+
120
+ A single `Aias::Error < StandardError` is raised for unrecoverable conditions (missing prompts dir, crontab write failure). The CLI rescues it, prints the message, and exits 1.
121
+
122
+ Recoverable per-prompt issues (invalid schedule, missing parameter defaults) are collected as error strings in `ValidationResult#errors` and never raised as exceptions.
@@ -0,0 +1,67 @@
1
+ # Environment Variables
2
+
3
+ ## Prompts Directory Resolution
4
+
5
+ The prompts directory is resolved in this order:
6
+
7
+ | Priority | Source |
8
+ |---|---|
9
+ | 1 (highest) | `--prompts-dir PATH` CLI option |
10
+ | 2 | `AIA_PROMPTS__DIR` environment variable (AIA >= 0.8.0) |
11
+ | 3 | `AIA_PROMPTS_DIR` environment variable (AIA < 0.8.0) |
12
+ | — | Error if none of the above is set |
13
+
14
+ ---
15
+
16
+ ## `AIA_PROMPTS__DIR`
17
+
18
+ **Preferred.** Set by AIA >= 0.8.0. Takes precedence over `AIA_PROMPTS_DIR`.
19
+
20
+ ```bash
21
+ export AIA_PROMPTS__DIR="$HOME/.prompts"
22
+ ```
23
+
24
+ ## `AIA_PROMPTS_DIR`
25
+
26
+ **Legacy (AIA < 0.8.0).** The absolute path to the directory containing your AIA prompt files. Used as a fallback when `AIA_PROMPTS__DIR` is not set.
27
+
28
+ ```bash
29
+ export AIA_PROMPTS_DIR="$HOME/.prompts"
30
+ ```
31
+
32
+ Set this in your login profile (`.zprofile`, `.bash_profile`) so it is available in cron jobs.
33
+
34
+ ## `SHELL`
35
+
36
+ **Optional.** The shell binary used to wrap each cron entry with a login invocation.
37
+
38
+ `aias` reads `ENV["SHELL"]` when building job entries. If not set, falls back to `/bin/bash`.
39
+
40
+ ```bash
41
+ echo $SHELL
42
+ # /bin/zsh
43
+ ```
44
+
45
+ This produces cron entries of the form:
46
+
47
+ ```
48
+ 0 8 * * * /bin/zsh -l -c 'aia daily_digest >> ... 2>&1'
49
+ ```
50
+
51
+ To override the shell for a specific `update` run:
52
+
53
+ ```bash
54
+ SHELL=/bin/bash aias update
55
+ ```
56
+
57
+ ## Variables Needed by AIA at Runtime
58
+
59
+ These are not used by `aias` directly, but must be available in your login profile for cron jobs to succeed.
60
+
61
+ | Variable | Description |
62
+ |---|---|
63
+ | `ANTHROPIC_API_KEY` | Anthropic API key (when using Claude) |
64
+ | `OPENAI_API_KEY` | OpenAI API key (when using GPT models) |
65
+ | `AIA_PROMPTS_DIR` | Same as above — must be in login profile |
66
+
67
+ Set these in your login profile, not only in your interactive shell's rc file (`.bashrc`, `.zshrc`). See [Cron Environment](../guides/cron-environment.md) for details.
@@ -0,0 +1,73 @@
1
+ # Logging
2
+
3
+ ## Log File Location
4
+
5
+ Each scheduled prompt writes to its own log file under `~/.aia/schedule/logs/`:
6
+
7
+ ```
8
+ ~/.aia/schedule/logs/
9
+ daily_digest.log
10
+ reports/
11
+ weekly.log
12
+ monthly_summary.log
13
+ ```
14
+
15
+ The subdirectory structure mirrors the prompt's subpath relative to `$AIA_PROMPTS_DIR`.
16
+
17
+ | Prompt ID | Log File |
18
+ |---|---|
19
+ | `daily_digest` | `~/.aia/schedule/logs/daily_digest.log` |
20
+ | `reports/weekly` | `~/.aia/schedule/logs/reports/weekly.log` |
21
+ | `a/b/deep` | `~/.aia/schedule/logs/a/b/deep.log` |
22
+
23
+ ## Log Behaviour
24
+
25
+ | Behaviour | Details |
26
+ |---|---|
27
+ | **Always append** | `>>` is used — log entries accumulate across runs |
28
+ | **Combined stdout + stderr** | `2>&1` — all output goes to the same file |
29
+ | **Created by cron** | The log file is created on the first run; `aias` only creates the parent directory |
30
+
31
+ ## Log Directory Creation
32
+
33
+ `aias update` calls `ensure_log_directories` before installing, which creates the necessary subdirectory structure under `~/.aia/schedule/logs/`. The directories are created even if no jobs have run yet.
34
+
35
+ ## Viewing Logs
36
+
37
+ ```bash
38
+ # Tail the log for a specific prompt
39
+ tail -f ~/.aia/schedule/logs/daily_digest.log
40
+
41
+ # Check the last run time
42
+ ls -la ~/.aia/schedule/logs/daily_digest.log
43
+
44
+ # View recent entries
45
+ tail -n 50 ~/.aia/schedule/logs/reports/weekly.log
46
+ ```
47
+
48
+ ## Using aias next
49
+
50
+ The `aias next` command shows the log file's modification timestamp as a proxy for the last run time:
51
+
52
+ ```bash
53
+ aias next
54
+ ```
55
+
56
+ ```
57
+ daily_digest
58
+ schedule : 0 8 * * *
59
+ last run : 2025-03-20 08:00:01 +0000
60
+ log : /Users/you/.aia/schedule/logs/daily_digest.log
61
+ ```
62
+
63
+ "never run" is shown when the log file does not exist.
64
+
65
+ ## Customising the Log Base
66
+
67
+ The log base directory defaults to `~/.aia/schedule/logs`. It can be overridden when constructing `CrontabManager` directly:
68
+
69
+ ```ruby
70
+ manager = Aias::CrontabManager.new(log_base: "/custom/log/dir")
71
+ ```
72
+
73
+ This is intended for testing and programmatic use. The CLI always uses the default.