prdigest 0.2.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 409ae44cab5ad005c5dcb413632295fdd3aae2e8d6383852b71863f8fc484d15
4
- data.tar.gz: 7ae70339af9e94123d83795362bfde4c07ee57d94bf447123fe4ce305f9117b4
3
+ metadata.gz: 85eefac373cdb54b93c372913fcb97cdcd4f1c003aa7cae487355ffd4da2e5e6
4
+ data.tar.gz: cb91e2f60e8ae566485dbdb7f56053a93b01df8fe357a9b0b6442c616c1498ec
5
5
  SHA512:
6
- metadata.gz: f5c7c0e265e13c87e2a625c3ee8fd10cde644b99297277b0dd5fd1f2f4a980ea0d7d81f67a63021b9741af8811b8903af47595c9713b507dd36fb749118e4489
7
- data.tar.gz: 4c787a605900e7f93722a72929708cf5032e35d1aff08a2134d41430d6e17b58f578ef20d965141391c87943eefbe3e99781a99cfcf72f08e0b35257c90d1d50
6
+ metadata.gz: 2c5009123d2b0df8c39612e47c89ea794a98383808cc709ddbdd1b415d32b71881733007a26f5c3dff69b13027a6c0c9e1ab59e79ee63832c363584b3ef32dc6
7
+ data.tar.gz: ab5fd25f659459f3dbd9a787747756bfd9f73c7cc7a56d237529eea18792a15515ac27c9f8a59e954db557338e967c3f639f7c77e318d697255e89be51307d18
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 - 2026-09-16
4
+
5
+ - Add bounded PR descriptions and relevant patches to facts, plus the reusable
6
+ provider-free `Prdigest::Document` generation boundary for embedders.
7
+ - Generate one concise editorial Markdown digest with dated and grouped
8
+ headings, user-facing outcomes, and clickable source PR links.
9
+ - Canonicalize renamed repository aliases before collection and deduplicate
10
+ repeated repository configuration.
11
+
12
+ ## 0.3.0 - 2026-07-27
13
+
14
+ - Remove `prdigest run`, deterministic Telegram HTML, schedule/cursor state,
15
+ catch-up processing, the `prdigest-result` envelope, and the `serve` stub.
16
+ - Keep `prdigest facts` for agents and `prdigest prose` for stdout or
17
+ checkpointed plain-text Telegram delivery.
18
+ - Make `prdigest prose --deliver` the systemd and container default.
19
+
3
20
  ## 0.2.0 - 2026-07-26
4
21
 
5
22
  - Add a deterministic `prdigest facts` JSON contract over the canonical
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <h1>PRDigest</h1>
4
4
 
5
- <p><strong>One source of truth for merged pull requests. Three ways to present it.</strong></p>
5
+ <p><strong>One merged-PR facts contract. Prose for people and JSON for agents.</strong></p>
6
6
 
7
7
  <p>
8
8
  <a href="https://github.com/ivankuznetsov/prdigest/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/ivankuznetsov/prdigest/actions/workflows/ci.yml/badge.svg"></a>
@@ -12,14 +12,14 @@
12
12
  </p>
13
13
 
14
14
  <p>
15
- Collect merged PRs once, then deliver a deterministic Telegram digest, emit<br>
16
- stable JSON for an agent, or generate optional prose through an
17
- OpenAI-compatible Chat Completions endpoint.
15
+ Give OpenClaw and other agents stable merged-PR facts, or generate concise<br>
16
+ prose through any OpenAI-compatible Chat Completions endpoint.
18
17
  </p>
19
18
 
20
19
  <p>
21
20
  <a href="#quick-start">Quick start</a> ·
22
21
  <a href="#choose-a-mode">Choose a mode</a> ·
22
+ <a href="#standalone-telegram-bot">Telegram bot</a> ·
23
23
  <a href="#configuration">Configuration</a> ·
24
24
  <a href="#deployment">Deploy</a> ·
25
25
  <a href="#openclaw">OpenClaw</a>
@@ -35,20 +35,28 @@ PRDigest keeps collection separate from presentation. Every mode starts from
35
35
  the same ordered, immutable facts, so adding an agent or prose model never
36
36
  changes what was fetched from GitHub.
37
37
 
38
+ Facts include bounded PR descriptions and relevant file patches for prose
39
+ generation. `description_truncated`, each patch's `truncated` or `omitted`,
40
+ and `patches_omitted` explicitly mark incomplete evidence; consumers must not
41
+ present it as a full diff. Embedders can call
42
+ `Prdigest::Document.generate(facts:, generator:)` with precollected facts and
43
+ their own generator. It makes no provider, GitHub, or Telegram call itself.
44
+ Prompt construction keeps every PR reference, title, and description, trimming
45
+ patches first with explicit metadata and failing if non-patch metadata cannot
46
+ fit.
47
+
38
48
  ```mermaid
39
49
  flowchart LR
40
50
  GH[GitHub repositories] --> C[Canonical collector]
41
- C --> RUN["prdigest run<br/>deterministic Telegram"]
42
51
  C --> FACTS["prdigest facts<br/>versioned JSON"]
43
52
  C --> PROSE["prdigest prose<br/>provider-written text"]
44
53
  FACTS --> OC[OpenClaw or another client]
45
54
  PROSE --> OUT[stdout or explicit Telegram delivery]
46
55
  ```
47
56
 
48
- - **Deterministic by default** — repository order, pull-request order, and JSON
49
- shape are stable.
50
- - **AI stays optional** — neither `run` nor `facts` configures or contacts a
51
- prose provider.
57
+ - **Stable facts for agents** — repository order, pull-request order, and JSON
58
+ shape are deterministic.
59
+ - **AI stays explicit** — `facts` never configures or contacts a prose provider.
52
60
  - **Safe to resume** — delivery checkpoints prevent already accepted chunks
53
61
  from being sent twice.
54
62
  - **Secrets stay out of config** — YAML names environment variables; it never
@@ -58,10 +66,9 @@ flowchart LR
58
66
 
59
67
  | What you need | Command | Result | Side effects |
60
68
  |---|---|---|---|
61
- | Reliable scheduled digest | `prdigest run` | Deterministic Telegram HTML | Reads/writes schedule and delivery state |
62
- | Facts for OpenClaw or another client | `prdigest facts` | `prdigest-facts` JSON on stdout | No schedule state, Telegram, or AI provider |
69
+ | Facts for OpenClaw or another agent | `prdigest facts` | `prdigest-facts` JSON on stdout | No Telegram, provider, or delivery state |
63
70
  | Provider-written text | `prdigest prose` | Plain text on stdout | Fresh run calls GitHub, then the provider; no Telegram or checkpoint |
64
- | Provider-written Telegram digest | `prdigest prose --deliver` | Checkpointed Telegram delivery | Fresh run calls GitHub and provider, persists, then sends; resume uses the checkpoint without GitHub/provider generation |
71
+ | Scheduled or one-off Telegram prose | `prdigest prose --deliver` | Checkpointed plain-text Telegram delivery | Fresh run calls GitHub and provider, persists, then sends; resume reuses the checkpoint |
65
72
 
66
73
  All modes accept `--date YYYY-MM-DD` and repeatable
67
74
  `--repo OWNER/NAME` overrides. Repository order is always preserved.
@@ -84,7 +91,6 @@ config to state paths writable by your user:
84
91
 
85
92
  ```yaml
86
93
  state:
87
- path: tmp/prdigest/state.json
88
94
  delivery_path: tmp/prdigest/deliveries
89
95
  ```
90
96
 
@@ -96,7 +102,7 @@ mode you plan to run:
96
102
 
97
103
  ```sh
98
104
  export GITHUB_TOKEN=github_pat_...
99
- export TELEGRAM_BOT_TOKEN=... # run or prose --deliver only
105
+ export TELEGRAM_BOT_TOKEN=... # prose --deliver only
100
106
  export OPENROUTER_API_KEY=... # prose only; use your configured env name
101
107
  ```
102
108
 
@@ -107,12 +113,9 @@ allowlisted destination chat.
107
113
  ### 2. Try the read-only paths
108
114
 
109
115
  ```sh
110
- # Stable JSON: no scheduler state, Telegram, or prose provider
116
+ # Stable JSON: no Telegram, delivery state, or prose provider
111
117
  bundle exec prdigest facts --config prdigest.yml
112
118
 
113
- # Deterministic Telegram preview: fetches GitHub but does not send or save state
114
- bundle exec prdigest run --config prdigest.yml --dry-run
115
-
116
119
  # Provider-written text on stdout: no Telegram or delivery checkpoint
117
120
  bundle exec prdigest prose --config prdigest.yml
118
121
  ```
@@ -120,9 +123,6 @@ bundle exec prdigest prose --config prdigest.yml
120
123
  ### 3. Deliver intentionally
121
124
 
122
125
  ```sh
123
- # Scheduled deterministic delivery
124
- bundle exec prdigest run --config prdigest.yml
125
-
126
126
  # Provider-written delivery, checkpointed before the first Telegram request
127
127
  bundle exec prdigest prose --config prdigest.yml --deliver
128
128
  ```
@@ -131,7 +131,7 @@ To build and install the current checkout as a gem without publishing it:
131
131
 
132
132
  ```sh
133
133
  gem build prdigest.gemspec
134
- gem install prdigest-0.2.0.gem
134
+ gem install prdigest-0.4.0.gem
135
135
  prdigest version
136
136
  ```
137
137
 
@@ -167,13 +167,8 @@ github:
167
167
  - owner/api
168
168
  - owner/web
169
169
 
170
- schedule:
171
- max_catchup_days: 7
172
-
173
170
  digest:
174
171
  line_stats: true
175
- send_empty: true
176
- empty_message: "Merged PR digest — {date}\nTotal: 0 PRs"
177
172
 
178
173
  telegram:
179
174
  token_env: TELEGRAM_BOT_TOKEN
@@ -181,7 +176,6 @@ telegram:
181
176
  chat_id: -1001234567890
182
177
 
183
178
  state:
184
- path: /var/lib/prdigest/state.json
185
179
  delivery_path: /var/lib/prdigest/deliveries
186
180
 
187
181
  prose:
@@ -198,12 +192,10 @@ annotated configuration.
198
192
  <summary><strong>Configuration rules</strong></summary>
199
193
 
200
194
  - Repository order controls digest order.
201
- - `max_catchup_days` must be between `1` and `30`.
202
195
  - `chat_id` must appear in the non-empty allowlist. Extra IDs are accepted for
203
196
  schema compatibility, but delivery sends only to `chat_id`.
204
197
  - Token values belong in environment variables, never YAML.
205
- - The prose block is validated only for `prdigest prose`; it does not ambiently
206
- enable provider access for deterministic commands.
198
+ - The prose block is validated only for `prdigest prose`; `facts` ignores it.
207
199
  - Remote provider URLs require HTTPS. Plaintext HTTP is accepted only for exact
208
200
  loopback hosts.
209
201
 
@@ -212,24 +204,11 @@ annotated configuration.
212
204
  ## Command reference
213
205
 
214
206
  ```text
215
- prdigest run [--config PATH] [--date YYYY-MM-DD] [--dry-run] [--json] [--repo OWNER/NAME ...]
216
207
  prdigest facts [--config PATH] [--date YYYY-MM-DD] [--repo OWNER/NAME ...]
217
208
  prdigest prose [--config PATH] [--date YYYY-MM-DD] [--repo OWNER/NAME ...] [--deliver]
218
- prdigest serve
219
209
  prdigest version
220
210
  ```
221
211
 
222
- ### `run`
223
-
224
- An ordinary run processes owed dates oldest-first and advances state after each
225
- settled day. `--date` replays exactly one local date without reading or writing
226
- the schedule cursor. `--dry-run` previews an explicit date or yesterday and
227
- constructs neither state nor Telegram delivery.
228
-
229
- `--json` emits a versioned `prdigest-result` document with requested, settled,
230
- skipped, failed, and remaining dates plus delivery progress. `serve` is a
231
- compatibility stub; use the supplied systemd timer for scheduling.
232
-
233
212
  ### `facts`
234
213
 
235
214
  Fetches one explicit date or yesterday and prints exactly one JSON document. It
@@ -275,17 +254,17 @@ Sends the same facts document as untrusted data to
275
254
  facts without adding to or changing them.
276
255
 
277
256
  Without `--deliver`, prose is printed to stdout. With `--deliver`, the final
278
- escaped and chunked payload is stored under `state.delivery_path/prose` before
279
- the first Telegram request. Provider output containing terminal control
257
+ plain-text chunks are stored under `state.delivery_path/prose` before the first
258
+ Telegram request. Provider output containing terminal control
280
259
  characters is rejected before it can reach stdout, a checkpoint, or Telegram.
281
260
 
282
- There is no silent fallback to deterministic prose or another provider.
261
+ There is no silent fallback to another provider.
283
262
 
284
263
  ## Delivery guarantees
285
264
 
286
265
  PRDigest treats sending as a durable protocol, not a best-effort loop:
287
266
 
288
- 1. Fetch and render the complete day.
267
+ 1. Fetch facts and generate the complete prose digest.
289
268
  2. Persist the exact final chunk list.
290
269
  3. Mark a chunk in flight before sending.
291
270
  4. Advance only after Telegram definitely accepts it.
@@ -295,49 +274,134 @@ A definite Telegram 429/5xx response receives at most three attempts. Transport
295
274
  failures are ambiguous because Telegram may have accepted the request before
296
275
  the connection failed; PRDigest parks them instead of risking a duplicate.
297
276
 
298
- Prose delivery uses its own checkpoint namespace. Once a payload exists, retry
277
+ Once a prose payload exists, retry
299
278
  loads those exact chunks before checking GitHub or provider credentials, so a
300
279
  resume never regenerates different prose. A failure before the payload becomes
301
280
  durable can incur another provider request on retry.
302
281
 
303
282
  <details>
304
- <summary><strong>Scheduling, replay, and state details</strong></summary>
283
+ <summary><strong>Date and checkpoint details</strong></summary>
305
284
 
306
285
  Each local day is converted to independent UTC midnight boundaries, including
307
- DST gaps and repeats. A day settles only after every chunk succeeds, an enabled
308
- empty message succeeds, or an empty message is intentionally suppressed.
286
+ DST gaps and repeats. Both commands use yesterday in the configured timezone
287
+ unless `--date YYYY-MM-DD` is supplied.
309
288
 
310
- State is secret-free JSON version 1:
289
+ Checkpoint directories are mode `0700`; files and locks are mode `0600`.
290
+ A per-date lock prevents concurrent sends for the same repository scope and
291
+ chat. While a completed checkpoint exists, repeating `prose --deliver` for that
292
+ date is a no-op. Moving that checkpoint aside intentionally allows complete
293
+ regeneration and redelivery.
311
294
 
312
- ```json
313
- {"version":1,"timezone":"Europe/London","last_digested_date":"2026-07-15"}
295
+ The supplied systemd timer invokes `prose --deliver` once each day. PRDigest
296
+ does not maintain a catch-up cursor: explicitly run missed dates with `--date`.
297
+
298
+ </details>
299
+
300
+ ## Standalone Telegram bot
301
+
302
+ This path creates an outbound bot that posts one provider-written digest per
303
+ day. It does not listen for Telegram commands; scheduling is owned by systemd,
304
+ and every send uses `prdigest prose --deliver`.
305
+
306
+ ### 1. Create the bot and destination
307
+
308
+ 1. Open Telegram's official `@BotFather`, run `/newbot`, and keep the returned
309
+ token private.
310
+ 2. For a direct message, open the new bot and send `/start`. For a group, add
311
+ the bot and send a command such as `/start@your_bot_name` in that group.
312
+ 3. Read the destination chat ID from Telegram without putting the token in the
313
+ command line or browser history:
314
+
315
+ ```bash
316
+ read -rsp "Telegram bot token: " TELEGRAM_BOT_TOKEN
317
+ echo
318
+ export TELEGRAM_BOT_TOKEN
319
+ ruby -rjson -rnet/http -e '
320
+ uri = URI("https://api.telegram.org/bot#{ENV.fetch("TELEGRAM_BOT_TOKEN")}/getUpdates")
321
+ updates = JSON.parse(Net::HTTP.get(uri)).fetch("result", [])
322
+ keys = %w[message edited_message channel_post edited_channel_post my_chat_member chat_member]
323
+ chats = updates.filter_map { |update| keys.filter_map { |key| update.dig(key, "chat") }.first }
324
+ chats.uniq { |chat| chat.fetch("id") }.each do |chat|
325
+ puts [chat.fetch("id"), chat["title"] || chat["username"] || chat["first_name"]].compact.join("\t")
326
+ end
327
+ '
328
+ unset TELEGRAM_BOT_TOKEN
329
+ ```
330
+
331
+ If no ID appears, send the bot another command and repeat the lookup. Direct
332
+ chat IDs are normally positive; groups and channels normally use negative IDs.
333
+
334
+ ### 2. Install and configure PRDigest
335
+
336
+ Install the released gem:
337
+
338
+ ```sh
339
+ gem install prdigest -v 0.4.0
340
+ prdigest version
341
+ ```
342
+
343
+ Create a private `prdigest.yml` with the repositories, destination chat, and
344
+ provider you want. This example uses OpenRouter with DeepSeek V4 Flash:
345
+
346
+ ```yaml
347
+ timezone: Europe/London
348
+
349
+ github:
350
+ token_env: GITHUB_TOKEN
351
+ repos:
352
+ - owner/api
353
+ - owner/web
354
+
355
+ digest:
356
+ line_stats: true
357
+
358
+ telegram:
359
+ token_env: TELEGRAM_BOT_TOKEN
360
+ chat_id_allowlist: [-1001234567890]
361
+ chat_id: -1001234567890
362
+
363
+ prose:
364
+ provider: openai_compatible
365
+ base_url: https://openrouter.ai/api/v1
366
+ model: deepseek/deepseek-v4-flash
367
+ api_key_env: OPENROUTER_API_KEY
314
368
  ```
315
369
 
316
- Writes use atomic mode-`0600` replacement and directory fsync. Delivery
317
- directories are mode `0700`; files and locks are mode `0600`. A per-date lock
318
- prevents concurrent sends for the same checkpoint.
370
+ The provider and model above are only an example. Replace them with any
371
+ OpenAI-compatible endpoint and model you prefer.
319
372
 
320
- Missing state means first run and requests yesterday only. Malformed, future,
321
- unsupported, unreadable, or timezone-mismatched state fails closed.
373
+ Replace both chat IDs with the value from step 1. Keep the GitHub, Telegram,
374
+ and provider tokens in environment variables, never in YAML. Use a dedicated
375
+ bot token, a read-only fine-grained GitHub token, and a provider key with a
376
+ small spend limit.
322
377
 
323
- When backlog exceeds the configured cap, PRDigest durably skips the oldest
324
- prefix and processes only the newest window. For a timezone migration, stop the
325
- timer, preserve and move aside the old state, change timezone, explicitly
326
- replay any required dates, then restart the timer.
378
+ ### 3. Preview, send once, then schedule
327
379
 
328
- GitHub does not guarantee search-index freshness. Keep the host timezone aligned
329
- with the digest timezone and use `--date YYYY-MM-DD` for a delayed merge found
330
- by a later audit. Explicit replay uses the same delivery ledger: while a
331
- completed checkpoint for that date exists, delivery is a no-op and newly
332
- rendered content is ignored. Intentionally archiving the checkpoint makes the
333
- next replay regenerate and send the complete date, not only the delayed pull
334
- request.
380
+ Read the three credentials without saving them in shell history, preview the
381
+ prose without Telegram, then perform the first intentional delivery:
335
382
 
336
- </details>
383
+ ```bash
384
+ read -rsp "GitHub token: " GITHUB_TOKEN; echo
385
+ read -rsp "Telegram bot token: " TELEGRAM_BOT_TOKEN; echo
386
+ read -rsp "OpenRouter API key: " OPENROUTER_API_KEY; echo
387
+ export GITHUB_TOKEN TELEGRAM_BOT_TOKEN OPENROUTER_API_KEY
388
+
389
+ prdigest prose --config ./prdigest.yml
390
+ prdigest prose --config ./prdigest.yml --deliver
391
+ unset GITHUB_TOKEN TELEGRAM_BOT_TOKEN OPENROUTER_API_KEY
392
+ ```
393
+
394
+ Once the one-off send succeeds, use the tested [systemd deployment](#deployment)
395
+ below. Its timer runs at `09:05` in the host timezone and sends the previous
396
+ day's digest. Change `OnCalendar` before enabling the timer if you want another
397
+ time. Re-running a completed date is a no-op; use `--date YYYY-MM-DD` for an
398
+ explicit missed day.
337
399
 
338
400
  ## OpenClaw
339
401
 
340
- The repository includes a ClawHub-ready skill at
402
+ The repository includes the published ClawHub skill
403
+ [`@ivankuznetsov/prdigest`](https://clawhub.ai/ivankuznetsov/skills/prdigest)
404
+ under **Development**, with its source at
341
405
  [`openclaw/skills/prdigest/SKILL.md`](openclaw/skills/prdigest/SKILL.md). It:
342
406
 
343
407
  - invokes only `prdigest facts`;
@@ -346,31 +410,53 @@ The repository includes a ClawHub-ready skill at
346
410
  - makes no second GitHub request; and
347
411
  - never invokes delivery or configures a prose provider.
348
412
 
349
- The skill is source-ready, **not claimed as published**. After a separately
350
- authorized ClawHub release, the expected command is:
413
+ The Ruby CLI and ClawHub skill are separate installs with independent version
414
+ histories. Pin the Ruby gem release you want, but let ClawHub resolve the
415
+ current skill. To let OpenClaw install both, copy and paste this prompt into an
416
+ OpenClaw chat:
351
417
 
352
- ```sh
353
- clawhub install @ivankuznetsov/prdigest
418
+ ```text
419
+ Install PRDigest 0.4.0 in the same user/runtime context as OpenClaw with
420
+ `gem install prdigest -v 0.4.0`, then install the ClawHub skill with
421
+ `openclaw skills install @ivankuznetsov/prdigest`. This message
422
+ explicitly authorizes those two installs and only the PATH adjustment needed to
423
+ make the installed `prdigest` executable visible to the OpenClaw runtime. Do
424
+ not create PRDigest configuration files, store credentials, enable Telegram
425
+ delivery, or install a scheduler. First verify Ruby 3.2 or newer is available;
426
+ if it is not, stop and report the exact blocker instead of changing system
427
+ packages. After installation, run `prdigest version`, confirm that OpenClaw can
428
+ discover the installed PRDigest skill, and report the installed paths and
429
+ versions without exposing environment variables or tokens.
354
430
  ```
355
431
 
356
- ClawHub installation and Ruby CLI installation are separate trust boundaries.
357
- See [`openclaw/README.md`](openclaw/README.md) for local use and the
358
- release-gated publication checklist.
432
+ For a manual install, run `gem install prdigest -v 0.4.0` and
433
+ `openclaw skills install @ivankuznetsov/prdigest`. The skill gives an agent
434
+ facts-to-prose behavior only; use the
435
+ [standalone Telegram bot](#standalone-telegram-bot) when PRDigest itself should
436
+ generate and deliver scheduled prose.
437
+
438
+ See [`openclaw/README.md`](openclaw/README.md) for local development and
439
+ publication details.
359
440
 
360
441
  ## Deployment
361
442
 
362
443
  <details open>
363
- <summary><strong>systemd on Ubuntu</strong></summary>
444
+ <summary><strong>systemd on Linux</strong></summary>
364
445
 
365
- Install Ruby, `tzdata`, and the gem, then:
446
+ Install Ruby 3.2 or newer and `tzdata`, then install the gem system-wide. The
447
+ gem contains the example configuration and tested service units, so a source
448
+ checkout is not required:
366
449
 
367
450
  ```sh
368
- sudo useradd --system --home /nonexistent --shell /usr/sbin/nologin prdigest
451
+ sudo gem install prdigest -v 0.4.0 --no-document --bindir /usr/local/bin
452
+ gem_root=$(ruby -e 'print Gem::Specification.find_by_name("prdigest", "0.4.0").full_gem_path')
453
+
454
+ sudo useradd --system --home /nonexistent --shell "$(command -v nologin)" prdigest
369
455
  sudo install -d -o root -g prdigest -m 0750 /etc/prdigest
370
- sudo install -o root -g prdigest -m 0640 configs/config.example.yml /etc/prdigest/config.yml
371
- sudo install -o root -g root -m 0600 .env.example /etc/prdigest/.env
372
- sudo install -o root -g root -m 0644 scripts/systemd/prdigest.service /etc/systemd/system/
373
- sudo install -o root -g root -m 0644 scripts/systemd/prdigest.timer /etc/systemd/system/
456
+ sudo install -o root -g prdigest -m 0640 "$gem_root/configs/config.example.yml" /etc/prdigest/config.yml
457
+ sudo install -o root -g root -m 0600 /dev/null /etc/prdigest/.env
458
+ sudo install -o root -g root -m 0644 "$gem_root/scripts/systemd/prdigest.service" /etc/systemd/system/
459
+ sudo install -o root -g root -m 0644 "$gem_root/scripts/systemd/prdigest.timer" /etc/systemd/system/
374
460
  sudoedit /etc/prdigest/config.yml
375
461
  sudoedit /etc/prdigest/.env
376
462
  sudo systemctl daemon-reload
@@ -378,6 +464,10 @@ sudo systemctl start prdigest.service
378
464
  sudo systemctl enable --now prdigest.timer
379
465
  ```
380
466
 
467
+ Put `GITHUB_TOKEN=...`, `TELEGRAM_BOT_TOKEN=...`, and the configured provider
468
+ key such as `OPENROUTER_API_KEY=...` in `/etc/prdigest/.env`. Do not prefix
469
+ systemd environment-file entries with `export`.
470
+
381
471
  systemd creates `/var/lib/prdigest` as `prdigest:prdigest` mode `0700`.
382
472
 
383
473
  ```sh
@@ -416,25 +506,24 @@ docker run --rm --env-file /etc/prdigest/.env \
416
506
  ### Rollback
417
507
 
418
508
  Stop the timer, install the prior gem or image, restore its matching config and
419
- a known-good state backup, then restart. Replay omitted dates explicitly; never
420
- move a checkpoint forward by hand.
509
+ checkpoint backup, then restart. Never move a checkpoint forward by hand.
421
510
 
422
511
  ## Exit codes and troubleshooting
423
512
 
424
513
  | Exit | Meaning | First check |
425
514
  |---:|---|---|
426
- | `0` | Completed or dry-run | — |
515
+ | `0` | Completed | — |
427
516
  | `1` | Unexpected or render failure | Logs and input shape |
428
517
  | `2` | CLI/configuration refusal | Config path, YAML, timezone, allowlist, env |
429
518
  | `3` | GitHub failure | Token scope, repository access, rate/search limits |
430
519
  | `4` | Telegram failure | `error.kind` and delivery checkpoint |
431
- | `5` | State failure | Path, owner, mode, JSON version/date/timezone |
432
- | `6` | Failure after durable progress | Settled/skipped dates before retry |
520
+ | `5` | Checkpoint state failure | Path, owner, mode, checkpoint JSON |
433
521
  | `7` | Provider failure or ambiguous outcome | Endpoint, model, key env, retry cost |
434
522
 
435
523
  Reconcile `telegram_ambiguous`, `telegram_permanent`, and
436
- `delivery_checkpoint_permanent` before moving a checkpoint. Concurrent
437
- scheduled runs are unsupported; the systemd oneshot is the normal coordinator.
524
+ `delivery_checkpoint_permanent` before moving a checkpoint. Overlapping delivery
525
+ for the same date is refused by the checkpoint lock; the systemd oneshot is the
526
+ normal scheduler.
438
527
 
439
528
  See [`SECURITY.md`](SECURITY.md) for token scope, rotation, and private-data
440
529
  flow.
data/SECURITY.md CHANGED
@@ -35,8 +35,8 @@ and model boundary so OpenClaw can write prose. Standalone `prdigest prose`
35
35
  sends the same complete document plus the configured model name to the
36
36
  OpenAI-compatible endpoint. Private repositories therefore require an OpenClaw
37
37
  deployment or provider whose data handling, retention, and access controls are
38
- acceptable to the operator. Neither mode is ambient: `prdigest run` and
39
- `prdigest facts` never contact the standalone prose provider.
38
+ acceptable to the operator. Provider access is never ambient: `prdigest facts`
39
+ does not contact the standalone prose provider.
40
40
 
41
41
  Pull-request fields are untrusted input. The built-in provider prompt and the
42
42
  OpenClaw skill explicitly classify the facts JSON as data, never instructions,
@@ -45,9 +45,8 @@ processing private repository content. The OpenClaw skill must not make a second
45
45
  GitHub query, deliver messages, install software silently, or print credentials.
46
46
 
47
47
  Prose Telegram checkpoints contain generated text derived from repository facts.
48
- They use the same secret-free mode-`0600` files and mode-`0700` directories as
49
- deterministic delivery, under a separate `prose` namespace. Protect, retain, and
50
- delete them as private repository metadata.
48
+ They use secret-free mode-`0600` files inside mode-`0700` directories. Protect,
49
+ retain, and delete them as private repository metadata.
51
50
 
52
51
  ## Reporting
53
52
 
@@ -1,12 +1,6 @@
1
- # prdigest — multi-repo merged-PR daily digest (Hive-independent)
1
+ # prdigest — merged-PR facts for agents and provider-written prose
2
2
 
3
3
  timezone: Europe/London
4
- schedule:
5
- # Prefer systemd timer + `prdigest run` for v1.
6
- # serve mode may honor this later.
7
- cron: "5 9 * * *"
8
- # Newest retained catch-up window; valid range is 1..30.
9
- max_catchup_days: 7
10
4
 
11
5
  github:
12
6
  token_env: GITHUB_TOKEN
@@ -23,11 +17,9 @@ telegram:
23
17
 
24
18
  digest:
25
19
  line_stats: true
26
- send_empty: true
27
- empty_message: "Merged PR digest — {date}\nTotal: 0 PRs"
28
20
 
29
21
  prose:
30
- # Used only by `prdigest prose`; `run` and `facts` never contact this endpoint.
22
+ # Used only by `prdigest prose`; `facts` never contacts this endpoint.
31
23
  provider: openai_compatible
32
24
  base_url: https://openrouter.ai/api/v1
33
25
  # Replace with a model identifier accepted by the configured endpoint.
@@ -36,6 +28,5 @@ prose:
36
28
  api_key_env: OPENROUTER_API_KEY
37
29
 
38
30
  state:
39
- path: /var/lib/prdigest/state.json
40
- # Stable rendered chunks and next-unsent position, separate from the date cursor.
31
+ # Stable prose chunks and the next-unsent position.
41
32
  delivery_path: /var/lib/prdigest/deliveries