@golden-frijoles/kit 0.7.0 → 0.9.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.
- package/dist/jev-eval.fixtures.json +7871 -0
- package/dist/jev-eval.mjs +369 -0
- package/dist/lib/config-registry.mjs +11 -1
- package/dist/lib/config.mjs +1 -1
- package/dist/lib/jev.mjs +10 -5
- package/dist/lib/notification-text.mjs +41 -0
- package/dist/lib/slack-text.mjs +21 -0
- package/dist/notify-setup.mjs +317 -0
- package/dist/routine-bootstrap.mjs +194 -0
- package/dist/routines/README.md +473 -0
- package/dist/routines/ops-nightly.prompt.md +125 -0
- package/dist/routines/pmo-report.prompt.md +52 -0
- package/dist/routines/pr-review.prompt.md +98 -0
- package/dist/routines/prod-smoke.prompt.md +140 -0
- package/dist/routines/roadmap-hygiene.prompt.md +97 -0
- package/dist/routines/smoke-triage.prompt.md +196 -0
- package/dist/routines/weekly-recap.prompt.md +95 -0
- package/dist/slack-notify.mjs +154 -0
- package/package.json +1 -1
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
# Claude Routines — stand-up runbook
|
|
2
|
+
|
|
3
|
+
The approved **Claude Code Routines** (research preview) for this project, as committed,
|
|
4
|
+
reviewable prompt artifacts plus the steps to stand them up. Routines are saved cloud Claude Code
|
|
5
|
+
configurations (prompt + repos + triggers) that run **autonomously on Anthropic-managed infra, as
|
|
6
|
+
the product owner** — so they keep running with the laptop closed. Created/managed in the product owner's account at
|
|
7
|
+
`claude.ai/code/routines` (or `/schedule` in the CLI).
|
|
8
|
+
|
|
9
|
+
> **TEMPLATE FILL-IN — read this first.** These seven prompts were proven in the project this template
|
|
10
|
+
> was extracted from, and ported by dobby-foundation's plugin-audit-and-extraction epic (Sprint 2) with
|
|
11
|
+
> their doctrine intact. The dated incidents below are that project's history, kept because they are the
|
|
12
|
+
> evidence for each rule. To adopt them:
|
|
13
|
+
> 1. Replace every `<your-org>/<root-repo>`, `<app-repo>`, `<api-repo>`, `<appDir>`, `<PROD_DOMAIN>` /
|
|
14
|
+
> `<PROD_URL>` and `TEMPLATE FILL-IN` in these files (`grep -rn "TEMPLATE FILL-IN\|<[a-z-]*-repo>" .`).
|
|
15
|
+
> The reporting routines read their repos from `reporting.config.json`, not from this file.
|
|
16
|
+
> 2. Stand each routine up from its prompt, and record its trigger id where this runbook shows
|
|
17
|
+
> `<trigger-id>` — rule 3 below is why that matters.
|
|
18
|
+
> 3. To drop a routine you won't run, delete its prompt AND its name from `routines.test.mjs`'s expected
|
|
19
|
+
> list. That is a visible, reviewed diff on purpose; the one-merge-authority rule holds regardless.
|
|
20
|
+
|
|
21
|
+
This repo commits the *prompts + this runbook*. **The account stand-up itself is operational, owed to
|
|
22
|
+
the product owner** — installing the GitHub App, creating the routines from these prompts, and setting B's
|
|
23
|
+
secrets/allow-list. Nothing here provisions infra or changes any account.
|
|
24
|
+
|
|
25
|
+
## Stand one up
|
|
26
|
+
|
|
27
|
+
Fill this project's values in `golden-frijoles.config.json` under `routines`, then render a reviewed prompt:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
node scripts/routine-bootstrap.mjs <name>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Paste the output into `/schedule` in Claude Code (or `claude.ai/code/routines`). Scheduled routines have a
|
|
34
|
+
**minimum interval of one hour** and each account has a **daily run cap**, so consolidate related work before
|
|
35
|
+
adding another schedule. A routine that posts to Telegram needs a **custom network environment**: the default
|
|
36
|
+
Trusted network allowlist does not include `api.telegram.org`.
|
|
37
|
+
|
|
38
|
+
## The three rules that hold for all of them
|
|
39
|
+
|
|
40
|
+
1. **Advisory only — never a required check.** Every routine's output is a PR comment or a `claude/`
|
|
41
|
+
PR for a human to review. None gates a merge, deploy, or money path. A plain PR **comment carries no
|
|
42
|
+
commit-status**, so it *structurally cannot* be added as a required check in branch protection —
|
|
43
|
+
keep it that way (Routine A must stay comment-only; never wire any status reporting). The
|
|
44
|
+
deterministic layers (CI, `browser-smoke.yml`, `notion-sync.yml`, the deploy
|
|
45
|
+
notifiers) remain the sole sources of truth.
|
|
46
|
+
|
|
47
|
+
> ⚠️ **ONE EXCEPTION, added 2026-08-17 by the product owner: Routine B may merge its own PR** — and only
|
|
48
|
+
> when `scripts/smoke-triage-scope.mjs` returns ALLOW, meaning the diff is test scaffolding only
|
|
49
|
+
> and weakens nothing. See B's own section. Rule 1 is otherwise unchanged, and the "never a
|
|
50
|
+
> required check" half is unchanged for *every* routine including B: the browser smoke remains the
|
|
51
|
+
> sole detector and nothing a routine emits gates anything.
|
|
52
|
+
|
|
53
|
+
2. **Leave push at the `claude/` default.** A routine may only push `claude/`-prefixed branches unless
|
|
54
|
+
unrestricted push is explicitly enabled — don't enable it. A only comments (no push); B/C open
|
|
55
|
+
`claude/` PRs. (Merging a `claude/` PR needs no wider push scope — B stays inside this default.)
|
|
56
|
+
|
|
57
|
+
3. **🚨 A prompt file in this directory is NOT what the cloud stores. Keep the two reconciled, or you changed
|
|
58
|
+
nothing.** The cloud routine stores its own copy of the prompt in the product owner's account; editing the
|
|
59
|
+
committed file here does not touch it. Both known routines had silently diverged:
|
|
60
|
+
|
|
61
|
+
| Routine | Drift found | State |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `smoke-triage` (`<trigger-id>`) | Stored prompt was a short ad-hoc text that had **never** matched `smoke-triage.prompt.md` — no diagnosis rubric, no advisory banner, no failure ping, and branch `claude/smoke-fix-<date>` instead of `claude/smoke-triage-<date>` | re-synced 2026-08-17 |
|
|
64
|
+
| `ops-nightly` (`<trigger-id>`) | Stored prompt still called `skills/<name>/SKILL.md` paths that no longer exist (skills moved to the `golden-frijoles` plugin) and still described step 4 as a simple skill call, not the 3-phase CPO prose write with its mechanical guard | re-synced 2026-08-17 |
|
|
65
|
+
|
|
66
|
+
The ops-nightly divergence was caught by the routine itself — it followed the repo file instead of
|
|
67
|
+
its stored prompt and reported the mismatch. That worked because the run had the repo in front of
|
|
68
|
+
it. **Do not rely on that.** A drifted prompt is invisible from the codebase, which is the same
|
|
69
|
+
failure shape as a fixture pinned to an Actions secret.
|
|
70
|
+
|
|
71
|
+
### The fix: the stored prompt is a BOOTSTRAP, not a copy
|
|
72
|
+
|
|
73
|
+
Copying 9KB of prose into the cloud on every edit is a sync ritual, and a sync ritual is a thing
|
|
74
|
+
people forget — which is how both routines drifted in the first place. So as of 2026-08-17 the
|
|
75
|
+
stored prompt for `smoke-triage` and `ops-nightly` no longer *contains* the instructions. It is a
|
|
76
|
+
short bootstrap that says: **read `scripts/routines/<name>.prompt.md` from the repo and follow
|
|
77
|
+
everything after the first `---`.** The repo file is the only copy, so it cannot disagree with
|
|
78
|
+
itself.
|
|
79
|
+
|
|
80
|
+
Three things make that safe, and none of them is optional:
|
|
81
|
+
|
|
82
|
+
- **A bootstrap that cannot read its file must STOP**, not improvise from memory. Both say so
|
|
83
|
+
explicitly, and both fall back to a Telegram `FAILED` line rather than a guessed run. An
|
|
84
|
+
improvised triage that merges something is far worse than a night with no triage.
|
|
85
|
+
- **The few rules that must survive a bad checkout are restated in the bootstrap itself** — never
|
|
86
|
+
weaken a spec, never merge app code, never `vercel-prune --apply`, write the standup prose in
|
|
87
|
+
session. Belt and braces on the ones where being wrong is expensive.
|
|
88
|
+
- **`smoke-triage` now lists the root repo as a second source**, because its prompt and its merge
|
|
89
|
+
gate (`scripts/smoke-triage-scope.mjs`) both live here while its work happens in the frontend
|
|
90
|
+
repo.
|
|
91
|
+
|
|
92
|
+
**What still needs doing by hand:** creating a routine, changing its schedule/env/sources, or
|
|
93
|
+
editing a bootstrap. Use the `RemoteTrigger` tool — `action: "list"` shows every trigger with its
|
|
94
|
+
stored prompt, `"get"` shows one, `"update"` writes. There is no CLI, so there is no `--check`
|
|
95
|
+
script to add; a script that could not actually read the triggers would be one that exits green
|
|
96
|
+
having checked nothing.
|
|
97
|
+
|
|
98
|
+
**Not yet converted:** `roadmap-hygiene` and `prod-smoke` still carry full stored prompts.
|
|
99
|
+
`roadmap-hygiene`'s has the same drift (it predates the committed `roadmap-hygiene.prompt.md`).
|
|
100
|
+
Convert them the same way when either is next touched.
|
|
101
|
+
|
|
102
|
+
> ⚠️ **Schedule drift, recorded not fixed:** this file says smoke-triage runs "~10:00 UTC"; the
|
|
103
|
+
> live trigger is `0 11 * * *`. 11:00 is the correct one and the doc is wrong — scheduled
|
|
104
|
+
> `browser-smoke.yml` runs have been observed *starting* as late as 10:14, so a 10:00 triage would
|
|
105
|
+
> regularly find the detector still in progress and correctly no-op. Left at 11:00 deliberately;
|
|
106
|
+
> ops-nightly at 11:55 is the constraint on moving it later.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Routine A — Review on every PR *(stand up first)*
|
|
111
|
+
**Prompt:** [`pr-review.prompt.md`](pr-review.prompt.md) · **Repos:** `<your-org>/<app-repo>`
|
|
112
|
+
+ `<api-repo>` (add root `<root-repo>` only if you want Roadmap-doc PRs
|
|
113
|
+
reviewed too).
|
|
114
|
+
|
|
115
|
+
1. **Install the Claude GitHub App** on `<your-org>/<app-repo>` and `<api-repo>`.
|
|
116
|
+
2. **Create the routine** from `pr-review.prompt.md`.
|
|
117
|
+
3. **Trigger:** GitHub → `pull_request`, action **`opened`**, filter **Is draft = `false`**.
|
|
118
|
+
⚠️ A GitHub trigger takes **one specific action OR all-actions-in-category — you cannot combine
|
|
119
|
+
`opened` + `ready_for_review`** ([docs](https://code.claude.com/docs/en/routines)). Pick **`opened`**:
|
|
120
|
+
it matches directly-opened non-draft PRs (incl. Dependabot), which is how PRs land here. (`opened`
|
|
121
|
+
does **not** fire on a draft→ready flip — `ready_for_review` does; pick that instead only if you
|
|
122
|
+
habitually open drafts first, or pick **all pull_request actions** + the draft filter for full
|
|
123
|
+
coverage, at the cost of also firing on label/sync/close.)
|
|
124
|
+
4. **Env/connectors:** GitHub App only. No Notion. Network = GitHub. Push left at the `claude/`
|
|
125
|
+
default (the routine only comments).
|
|
126
|
+
5. **Output:** one advisory review comment per PR (mirrors the `cross-review.prompt.md` rubric — five
|
|
127
|
+
AGENTS rules, single pass). **Comment-only; never a status check.**
|
|
128
|
+
|
|
129
|
+
Revives the descoped `cross-agent-review-always` goal: a cloud session runs *as the product owner*, so it
|
|
130
|
+
sidesteps the CI foreign-CLI auth blocker that forced that epic local-only. (This is the
|
|
131
|
+
**Claude-family** reviewer, not codex/agy — a different second-opinion family, but auto-on-every-PR is
|
|
132
|
+
restored.)
|
|
133
|
+
|
|
134
|
+
## Routine C — Weekly roadmap/Notion hygiene *(stand up second)*
|
|
135
|
+
**Prompt:** [`roadmap-hygiene.prompt.md`](roadmap-hygiene.prompt.md) · **Repo:** root
|
|
136
|
+
`<root-repo>`.
|
|
137
|
+
|
|
138
|
+
1. **Install the Claude GitHub App** on `<root-repo>` (root repo).
|
|
139
|
+
2. **Create the routine** from `roadmap-hygiene.prompt.md`.
|
|
140
|
+
3. **Trigger:** Schedule, **weekly — Mon 14:00 UTC** (after the 08:00 nightly `notion-sync.yml`).
|
|
141
|
+
4. **Env/connectors:** **No Notion connector** — no `.mcp.json`, no `NOTION_TOKEN`. The routine grooms
|
|
142
|
+
the `00-ideas` funnel, flags status-drift, runs `node scripts/build-order.mjs`, invokes the
|
|
143
|
+
`doc-hygiene` skill (`node scripts/doc-hygiene.mjs` — always-read-set size + LEARNINGS/poster
|
|
144
|
+
dedupe-staleness candidates), and opens a `claude/` **docs PR** with the regenerated
|
|
145
|
+
`BUILD-ORDER.md` + any new `DOC-HYGIENE-REPORT-*.md` + a drift report. Network = GitHub.
|
|
146
|
+
5. **Propagation:** after the product owner merges the docs PR, the existing `notion-sync.yml` propagates
|
|
147
|
+
docs→Notion as usual. **Augments, does not replace** that workflow.
|
|
148
|
+
|
|
149
|
+
## Routine B — Nightly smoke triage *(now-unblocked fast-follow)*
|
|
150
|
+
**Prompt:** [`smoke-triage.prompt.md`](smoke-triage.prompt.md) · **Repo:**
|
|
151
|
+
`<your-org>/<app-repo>` (frontend). **Gate:** `devops-reliability-cleanup` Story 1 (the
|
|
152
|
+
smoke fix) — done, so B is unblocked.
|
|
153
|
+
|
|
154
|
+
1. **Install the Claude GitHub App** on `<app-repo>` (already done for A).
|
|
155
|
+
2. **Create the routine** from `smoke-triage.prompt.md`.
|
|
156
|
+
3. **Trigger:** Schedule, **nightly ~10:00 UTC** (after the `0 9 * * *` `browser-smoke.yml`).
|
|
157
|
+
4. **Env:** the `TEST_*` secrets (the project's per-role test identities and fixture ids — TEMPLATE FILL-IN) so authed smokes light up; **Allowed-domains:**
|
|
158
|
+
`<PROD_DOMAIN>` + the auth provider's domains + the backend's URL.
|
|
159
|
+
5. **Output:** on a **green** smoke, nothing. On a **red** smoke, a fix on `claude/smoke-triage-<date>`
|
|
160
|
+
that either **merges and is verified on `main` after deploy**, or stays a **draft** PR plus a
|
|
161
|
+
Telegram ping. **Augments, never replaces** the deterministic smoke, which stays the sole detector.
|
|
162
|
+
|
|
163
|
+
### B's teeth — the 2026-08-17 change
|
|
164
|
+
|
|
165
|
+
For its first two months B stopped at a draft PR. Nobody merged them, so the nightly stayed red for
|
|
166
|
+
three nights (08-15/16/17) and the 08-16 and 08-17 drafts **independently re-diagnosed the same
|
|
167
|
+
defect from scratch**. A fixer that cannot land its fix re-does its work every night. The product owner's call:
|
|
168
|
+
give it merge authority, bounded.
|
|
169
|
+
|
|
170
|
+
Two independent gates decide, and **both** live in
|
|
171
|
+
[`scripts/smoke-triage-scope.mjs`](../smoke-triage-scope.mjs) (root repo, `node:test`-covered) rather
|
|
172
|
+
than in the prompt — because a merge-to-production predicate must not be a model reading its own diff
|
|
173
|
+
at 10:00 UTC with nobody watching:
|
|
174
|
+
|
|
175
|
+
- **Surface** — every changed path is test scaffolding (`e2e/`, `playwright.config.ts`,
|
|
176
|
+
`browser-smoke.yml`, `scripts/browser-smoke-*`). An allow-list, not a deny-list. **Any** change to
|
|
177
|
+
`app/`, `lib/`, `components/`, database migrations — anything reaching production — stays a draft.
|
|
178
|
+
- **No weakening** — the diff must not make the smoke pass by testing less: no added
|
|
179
|
+
`test.skip`/`fixme`/`only`/serial mode, no deleted spec file, no removed assertion. "Never weaken
|
|
180
|
+
the test" has been in B's prompt since it was written, and a prompt rule is a request; deleting a
|
|
181
|
+
red spec is the cheapest way for any agent to turn a nightly green and it looks like a fix in every
|
|
182
|
+
summary it would write about itself. `test.slow()` is deliberately **allowed** — raising a timeout
|
|
183
|
+
budget is a legitimate shipped fix (#349), and a guard that rejects correct output is worse than one
|
|
184
|
+
that misses a rare fault.
|
|
185
|
+
|
|
186
|
+
Exit codes are three-valued: `0` allow, `1` block, `2` **undecidable** (part of the diff could not be
|
|
187
|
+
read — treated as block, and the ping must say the word). A blocked diff is never discarded or
|
|
188
|
+
trimmed to fit; it becomes exactly the draft PR B used to produce, which is a safe degradation to
|
|
189
|
+
yesterday's behaviour.
|
|
190
|
+
|
|
191
|
+
After merging, B **re-runs the smoke on `main` once the deploy lands** — a branch run tests
|
|
192
|
+
production's old code, and three green branch runs once missed a live PWA-nav regression. If `main`
|
|
193
|
+
goes red, B opens a **draft revert PR** and pings with `REVERT`; it never merges the revert itself and
|
|
194
|
+
never attempts a second fix in the same run.
|
|
195
|
+
|
|
196
|
+
**Still true:** B never merges application code, never marks ready outside a `claude/` branch, never
|
|
197
|
+
becomes a required check, and never weakens a spec.
|
|
198
|
+
|
|
199
|
+
## Routine ops-nightly — Daily standup + the nightly fixers *(fourth routine, stand up any time after B)*
|
|
200
|
+
**Prompt:** [`ops-nightly.prompt.md`](ops-nightly.prompt.md) · **Repo:** root `<root-repo>`
|
|
201
|
+
(reads/writes `gh` across every repo in `reporting.config.json`).
|
|
202
|
+
|
|
203
|
+
**Sprint 1** shipped step 4 alone (the standup). **Sprint 2**
|
|
204
|
+
(`ops-routines-reporting` (the origin project's `ops-routines-reporting` epic) S2) added
|
|
205
|
+
steps 1–3 — the routine now runs, in order: `build-order-sync` (regen + docs PR on drift),
|
|
206
|
+
`vercel-prune` (dry-run report only — **never** `--apply`), `babysit-pr` (once per open PR across all 3
|
|
207
|
+
repos — silent on a clean PR), then `standup-post` (reports what happened). Still **one** scheduled
|
|
208
|
+
routine (cap-safe) — see the budget table below.
|
|
209
|
+
|
|
210
|
+
1. **Install the Claude GitHub App** on `<root-repo>` (root repo) if not already done for
|
|
211
|
+
Routine C.
|
|
212
|
+
2. **Create the routine** from `ops-nightly.prompt.md`.
|
|
213
|
+
3. **Trigger:** Schedule, **nightly ~10:30 UTC** — after both the frontend's `browser-smoke.yml`
|
|
214
|
+
(`0 9 * * *`) and Routine B (smoke-triage, ~10:00 UTC) have had a chance to complete, so the standup's
|
|
215
|
+
smoke signal reflects the night's actual result.
|
|
216
|
+
4. **Env:**
|
|
217
|
+
- **`TELEGRAM_BOT_TOKEN`** in the routine's environment — this routine's Telegram use is
|
|
218
|
+
**LOAD-BEARING** (step 4's actual output), not the optional failure-ping the other three routines
|
|
219
|
+
use.
|
|
220
|
+
- **`TELEGRAM_CHAT_ID`** in the routine's environment — **this is the one that actually works for an
|
|
221
|
+
unattended routine run.** The committed `reporting.config.json` carries repos and signals; a chat
|
|
222
|
+
id there works too, but a PUBLIC repo keeps it in the gitignored `reporting.config.local.json`,
|
|
223
|
+
which never exists in a routine's fresh checkout — so `standup.mjs` uses this env var, and it's the same one the optional failure-ping already
|
|
224
|
+
needed, so one setting covers both.
|
|
225
|
+
- **Network access → Custom**, with **`api.telegram.org`** allow-listed (same requirement as the
|
|
226
|
+
other three routines' optional ping — here it's required for step 4 to do anything at all).
|
|
227
|
+
- **⚠️ The `gh` CLI is NOT pre-installed in a routine's cloud sandbox — every step here shells out to
|
|
228
|
+
it, so this is the #1 thing that blocks the whole routine if skipped.** (Confirmed live, 2026-07-02:
|
|
229
|
+
the first two `ops-nightly` runs both failed at step 2 with "no `gh` CLI binary at all" — only the
|
|
230
|
+
built-in GitHub tools were present, and those are read-oriented and scoped to whichever repo the
|
|
231
|
+
routine cloned, not the multi-repo `gh pr list`/`gh run rerun`/`gh pr comment` calls these scripts make.)
|
|
232
|
+
Fix, in the routine's **environment** settings (Edit routine → environment icon → settings gear):
|
|
233
|
+
1. **Setup script** — add:
|
|
234
|
+
```bash
|
|
235
|
+
apt update || true
|
|
236
|
+
apt install -y gh
|
|
237
|
+
```
|
|
238
|
+
**Not** the naive `apt update && apt install -y gh`, which failed live (exit 100, 2026-07-02):
|
|
239
|
+
the base image ships two THIRD-PARTY PPAs pre-configured (`deadsnakes`, `ondrej/php` — unrelated
|
|
240
|
+
to `gh`), and Launchpad returned `403 Forbidden` on both (a shared-sandbox-IP reputation issue,
|
|
241
|
+
not a network-access misconfiguration — the request reached Launchpad's server and got a real
|
|
242
|
+
403, not a proxy-block). `apt update` treats ANY repo failure as fatal even though the Ubuntu
|
|
243
|
+
archives `gh` actually needs (`noble-updates/universe`, where `gh` ships on 24.04) fetched fine.
|
|
244
|
+
`|| true` makes the update non-fatal so the install step still runs. This runs once and is
|
|
245
|
+
cached (~7-day expiry); it does not re-run every session.
|
|
246
|
+
2. **`GH_TOKEN`** env var — a GitHub Personal Access Token with access to every configured repo
|
|
247
|
+
(`<root-repo>`, `<app-repo>`, `<api-repo>`). `gh` reads
|
|
248
|
+
`GH_TOKEN` automatically — no `gh auth login` step needed. A **fine-grained PAT** scoped to just
|
|
249
|
+
every configured repo with **Contents: Read & Write**, **Pull requests: Read & Write**, **Actions: Read &
|
|
250
|
+
Write** (Metadata: Read is auto-included) is the least-privilege choice; a classic PAT with the
|
|
251
|
+
`repo` scope also works if simpler. Mint one at
|
|
252
|
+
[github.com/settings/tokens](https://github.com/settings/tokens) (or
|
|
253
|
+
[github.com/settings/personal-access-tokens/new](https://github.com/settings/personal-access-tokens/new)
|
|
254
|
+
for fine-grained).
|
|
255
|
+
3. `github.com`/`api.github.com` are already in the **Trusted** network-access default — no domain
|
|
256
|
+
change needed there, only the two steps above.
|
|
257
|
+
- **`gh` write scope sufficient for `run rerun`, `pr comment`, and `pr create` on a `claude/`-branch.**
|
|
258
|
+
Steps 1 and 3 write (build-order-sync opens a docs PR; babysit-pr re-runs failed workflow runs and
|
|
259
|
+
posts a PR comment) — both stay inside the routine's **default** `claude/`-prefix push scope (step
|
|
260
|
+
1's branch is `claude/build-order-sync-<date>`; step 3 never pushes a branch at all, only comments
|
|
261
|
+
+ re-runs).
|
|
262
|
+
- **No "Allow unrestricted branch pushes" permission is needed for ANY step, including the standup's
|
|
263
|
+
own log persistence.** `scripts/standup.mjs` (step 4) used to commit `scripts/standups.log` straight
|
|
264
|
+
to `main`, which needed that broader permission — live 2026-07-02/03, its Save button failed
|
|
265
|
+
("Failed to save changes") in the claude.ai Routines UI. Fixed by moving the log onto a dedicated
|
|
266
|
+
`claude/standup-log` branch instead (`scripts/lib/log-branch.mjs`, git plumbing only — hash-object/
|
|
267
|
+
mktree/commit-tree/push, no checkout), which is already inside the routine's **default** push scope.
|
|
268
|
+
Nothing to configure here anymore; if that Permissions-tab toggle still won't save for you, it no
|
|
269
|
+
longer matters for this routine.
|
|
270
|
+
- **`VERCEL_API_TOKEN`** so step 2's richer per-branch report actually resolves. Not load-bearing —
|
|
271
|
+
`standup.mjs`'s own simpler stale-preview count (step 4) already degrades gracefully to
|
|
272
|
+
"unavailable" without it (confirmed live in S1), same fallback the standup has always had; this is
|
|
273
|
+
purely so step 2's own dry-run report has real numbers to show, rather than an auth error.
|
|
274
|
+
5. **Output:** one Telegram message per night (the standup) — either the delta lines or a one-line
|
|
275
|
+
"quiet night, no change" post, now with a `Deck standup:` story-deck link appended for mobile
|
|
276
|
+
reading/forwarding — **plus, only when there's something to act on:** a `claude/` docs PR from step 1
|
|
277
|
+
(board was stale) and/or an advisory comment on a PR from step 3 (it had a conflict or a retryable
|
|
278
|
+
failing check). **Never** an `--apply` run, **never** a merge, **never** a required check.
|
|
279
|
+
- ⚠️ **First-live-action gate (owed to the product owner, per the epic's risk-tier rule):** before this routine
|
|
280
|
+
runs unattended on schedule, confirm the first live `vercel-prune --apply` (run only by explicit
|
|
281
|
+
ask, never by this routine) and the first live `babysit-pr` action (a real retry/comment on a real
|
|
282
|
+
PR) each look correct — see
|
|
283
|
+
`sprint-2.md` (the origin project's `ops-routines-reporting` epic)'s walkthrough.
|
|
284
|
+
|
|
285
|
+
## Routine weekly-recap — Weekly executive recap *(fifth routine, the longer-horizon complement to ops-nightly)*
|
|
286
|
+
**Prompt:** [`weekly-recap.prompt.md`](weekly-recap.prompt.md) · **Repo:** root `<root-repo>`
|
|
287
|
+
(reads `gh` + `git log` across every repo in `reporting.config.json`).
|
|
288
|
+
|
|
289
|
+
Shipped by `ops-routines-reporting` (the origin project's `ops-routines-reporting` epic)
|
|
290
|
+
S3 — a dedicated **weekly** routine (mirroring Routine C's precedent of a standalone weekly schedule,
|
|
291
|
+
rather than a day-of-week-gated step folded into the nightly `ops-nightly` routine). One step: the
|
|
292
|
+
`weekly-recap` skill (`scripts/weekly-recap.mjs`) gathers the week's merged PRs (every configured repo),
|
|
293
|
+
shipped/closed epics (README frontmatter `status:` flips), a merges-to-main deploy count per app repo,
|
|
294
|
+
and a short retro digest per shipped epic — then posts one Telegram message.
|
|
295
|
+
|
|
296
|
+
1. **Install the Claude GitHub App** on `<root-repo>` (root repo) if not already done for
|
|
297
|
+
Routine C / `ops-nightly`.
|
|
298
|
+
2. **Create the routine** from `weekly-recap.prompt.md`.
|
|
299
|
+
3. **Trigger:** Schedule, **weekly — Mon 15:30 UTC** (after Routine C's Mon 14:00 roadmap-hygiene and
|
|
300
|
+
comfortably after the prior night's `ops-nightly` run, so the recap can reflect that week's tail end).
|
|
301
|
+
4. **Env:**
|
|
302
|
+
- **The `gh` CLI setup script + `GH_TOKEN`** — same requirement as `ops-nightly`'s (see its own env
|
|
303
|
+
section above for the exact setup script and PAT scopes). `weekly-recap.mjs` shells out to
|
|
304
|
+
`gh pr list` across every configured repo, so this is load-bearing here too. If `ops-nightly` and
|
|
305
|
+
`weekly-recap` share the same cloud environment (the usual setup), provisioning it once for
|
|
306
|
+
`ops-nightly` covers this routine too — nothing to redo.
|
|
307
|
+
- **`TELEGRAM_BOT_TOKEN`** in the routine's environment — this routine's Telegram use is
|
|
308
|
+
**LOAD-BEARING** (its one step's actual output), same as `ops-nightly`'s.
|
|
309
|
+
- **`TELEGRAM_CHAT_ID`** in the routine's environment — **this is the one that actually works for an
|
|
310
|
+
unattended routine run** (same reasoning as `ops-nightly`'s: a gitignored local chat id never reaches a
|
|
311
|
+
routine's fresh checkout — `weekly-recap.mjs` uses this env var, the same one the optional failure-ping
|
|
312
|
+
already needed).
|
|
313
|
+
- **Network access → Custom**, with **`api.telegram.org`** allow-listed (same requirement as the
|
|
314
|
+
other routines).
|
|
315
|
+
- **No "Allow unrestricted branch pushes" permission needed** — same as `ops-nightly`'s. The window
|
|
316
|
+
log lives on a dedicated `claude/weekly-recap-log` branch (`scripts/lib/log-branch.mjs`), already
|
|
317
|
+
inside the routine's default `claude/`-prefix push scope, so there's nothing to configure here.
|
|
318
|
+
5. **Output:** one Telegram message per week — merged PRs, deploys (merge counts), shipped/closed epics,
|
|
319
|
+
and a short retro digest per shipped epic, or a one-line "quiet week" post when there's nothing to
|
|
320
|
+
report. **Never** a PR, **never** a code change, **never** a required check.
|
|
321
|
+
|
|
322
|
+
## Routine pmo-report — Weekly PMO operational report *(sixth routine, stakeholder report delivery)*
|
|
323
|
+
**Prompt:** [`pmo-report.prompt.md`](pmo-report.prompt.md) · **Repo:** root `<root-repo>`
|
|
324
|
+
(reads `gh` + `git log` across every configured repo; posts to Telegram; writes `claude/pmo-reports-log`).
|
|
325
|
+
|
|
326
|
+
Shipped by `pmo-operational-reports` (the origin project's `pmo-operational-reports` epic)
|
|
327
|
+
S3. One step: the `pmo-report` skill (`scripts/pmo-report.mjs --weekly`) gathers scrum/DORA/doc-ops
|
|
328
|
+
metrics, renders a story-deck URL, posts headline numbers plus the deck link to Telegram, and
|
|
329
|
+
then advances the PMO window log.
|
|
330
|
+
|
|
331
|
+
1. **Install the Claude GitHub App** on `<root-repo>` if not already done for Routine C /
|
|
332
|
+
`ops-nightly`.
|
|
333
|
+
2. **Create the routine** from `pmo-report.prompt.md`.
|
|
334
|
+
3. **Trigger:** Schedule, **weekly — Mon 16:30 UTC** (after roadmap-hygiene and weekly-recap, so the PMO
|
|
335
|
+
deck reflects the current Roadmap/doc state).
|
|
336
|
+
4. **Env:**
|
|
337
|
+
- **The `gh` CLI setup script + `GH_TOKEN`** — same requirement as `ops-nightly`'s. `pmo-report.mjs`
|
|
338
|
+
reads merged/open PRs across every configured repo through the REST rail.
|
|
339
|
+
- **`TELEGRAM_BOT_TOKEN`** in the routine's environment — load-bearing; the Telegram post is the
|
|
340
|
+
routine's actual output.
|
|
341
|
+
- **`TELEGRAM_CHAT_ID`** in the routine's environment — the unattended path. A local run can put the
|
|
342
|
+
chat id in the gitignored `reporting.config.local.json`; it never reaches a routine session.
|
|
343
|
+
- **Network access -> Custom**, with **`api.telegram.org`** allow-listed.
|
|
344
|
+
- **No unrestricted branch push needed** — the PMO window log lives on `claude/pmo-reports-log`
|
|
345
|
+
through `scripts/lib/log-branch.mjs`.
|
|
346
|
+
5. **Output:** one Telegram message per week with headline PMO numbers plus a story-deck link.
|
|
347
|
+
**Never** a PR, **never** a merge, **never** a required check.
|
|
348
|
+
|
|
349
|
+
## Routine prod-smoke — Daily production watchdog *(seventh routine — a REWRITE of the oldest one)*
|
|
350
|
+
**Prompt:** [`prod-smoke.prompt.md`](prod-smoke.prompt.md) · **Repo:** root `<root-repo>`
|
|
351
|
+
(runs `scripts/prod-smoke.mjs` against live prod; posts to Telegram; may open a `claude/` draft PR).
|
|
352
|
+
|
|
353
|
+
**This one replaces an existing routine rather than adding a new load.** The original *"prod
|
|
354
|
+
smoke (daily)"* (trigger id `<trigger-id>`) predates all six routines above and was
|
|
355
|
+
never committed — its six curl checks lived only in the cloud prompt, with no git source. So no epic
|
|
356
|
+
could update them, and two failures followed on 2026-08-05:
|
|
357
|
+
|
|
358
|
+
- **`market-architecture-foundation`** (shipped 07-31) moved `/l` behind a one-hop 308 to `/mx/l`.
|
|
359
|
+
The watchdog went red against a route that had been correct for five days. The epic updated
|
|
360
|
+
`lib/markets.ts` in both repos and the e2e specs — and had no file here to edit.
|
|
361
|
+
- The same cutover turned `/` from the marketplace into the market **selector**. That check kept
|
|
362
|
+
returning 200 and stayed green **while testing a different page**; `/mx` lost coverage entirely
|
|
363
|
+
with no signal at all. A green check that changed meaning is worse than a red one.
|
|
364
|
+
|
|
365
|
+
The checks now live in the project's `scripts/prod-smoke.checks.mjs` (the shared engine is
|
|
366
|
+
[`scripts/prod-smoke.mjs`](../prod-smoke.mjs), with `node:test` coverage), so a
|
|
367
|
+
route change is a reviewable diff and the epic that moves a route updates the smoke in the same PR.
|
|
368
|
+
Because a 200 proves only that *something* answered, every check guarding a rendered page also
|
|
369
|
+
asserts a structural body marker — status alone could not tell the selector and the marketplace apart.
|
|
370
|
+
|
|
371
|
+
> ### ✅ STOOD UP 2026-08-06 — `<trigger-id>`, daily `17 13 * * *` UTC
|
|
372
|
+
>
|
|
373
|
+
> Created via the remote-trigger API (`RemoteTrigger` / `POST /v1/code/triggers`), **not** the web UI,
|
|
374
|
+
> and confirmed with a live manual run against production. Steps 1–3 below are kept as the record of
|
|
375
|
+
> how it was stood up; they are done.
|
|
376
|
+
>
|
|
377
|
+
> **🚨 The ORIGINAL watchdog was NOT replaced, because it could not be found.** It does not appear in
|
|
378
|
+
> `GET /v1/code/triggers` for this account (which lists only `ops-nightly`, `smoke-triage` and
|
|
379
|
+
> `roadmap-hygiene`), and no workflow in any of the project's repos performs these checks. The trigger id
|
|
380
|
+
> recorded in team memory, `<trigger-id>`, returns **404**. So it is running from
|
|
381
|
+
> somewhere this session cannot see or reach.
|
|
382
|
+
>
|
|
383
|
+
> **Until someone finds and disables it, a red night pages TWICE** — once from the old prompt
|
|
384
|
+
> asserting `/l` → 200 (a false alarm; that route legitimately 308s), once from this one. Disabling
|
|
385
|
+
> the old routine is owed, and it is the only remaining step.
|
|
386
|
+
|
|
387
|
+
1. **Install the Claude GitHub App** on `<root-repo>` if not already done for Routine C /
|
|
388
|
+
`ops-nightly` / `pmo-report`.
|
|
389
|
+
2. **Create the routine** from `prod-smoke.prompt.md`. (Originally written as "replace the old
|
|
390
|
+
routine's prompt, keep its trigger" — that turned out to be impossible; see the box above.)
|
|
391
|
+
3. **Trigger:** Schedule, **daily 13:17 UTC** (~07:17 Mexico City) — a first-thing-in-the-morning
|
|
392
|
+
check. It has no dependency on CI or the other routines; it probes live production directly, so
|
|
393
|
+
its hour is not coupled to anything.
|
|
394
|
+
4. **Env:**
|
|
395
|
+
- **Network access → Custom**, with **`<PROD_DOMAIN>`** allow-listed (the checks) and
|
|
396
|
+
**`api.telegram.org`** (the alert). Without the first, every check reports **unavailable** —
|
|
397
|
+
which the script reports as `UNAVAILABLE`, never as a production outage.
|
|
398
|
+
- **`TELEGRAM_BOT_TOKEN`** + **`TELEGRAM_CHAT_ID`** — load-bearing on a red run; silent on green.
|
|
399
|
+
- Push left at the `claude/` default (it only ever opens a draft PR against the smoke itself).
|
|
400
|
+
5. **Output:** **nothing on green.** On red, one Telegram alert naming each failing check with
|
|
401
|
+
observed-vs-expected, plus — only when the change is *provably* deliberate — a `claude/` **draft**
|
|
402
|
+
PR re-pointing the check. **Never** a merge, **never** a required check, and **never** a check
|
|
403
|
+
weakened to turn a red run green.
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Daily-cap budget (Pro)
|
|
408
|
+
|
|
409
|
+
The **daily routine-run cap (Pro = 5/day) bites the SCHEDULED runs** — GitHub-event and API triggers
|
|
410
|
+
have their **own separate per-routine/per-account hourly caps**, not the scheduled daily cap, and
|
|
411
|
+
**one-off `Run now` runs don't count** at all ([docs](https://code.claude.com/docs/en/routines) ·
|
|
412
|
+
[blog](https://claude.com/blog/introducing-routines-in-claude-code)).
|
|
413
|
+
|
|
414
|
+
| Routine | Trigger | Counts against the 5/day scheduled cap? |
|
|
415
|
+
|---|---|---|
|
|
416
|
+
| A — review-on-PR | GitHub `pull_request.opened` | **No** — GitHub-event, separate hourly caps |
|
|
417
|
+
| C — roadmap hygiene | Schedule, weekly | Yes, but ~0.14/day |
|
|
418
|
+
| B — smoke triage | Schedule, nightly | Yes, 1/day |
|
|
419
|
+
| ops-nightly — standup + nightly fixers | Schedule, nightly | Yes, 1/day (still one routine, now 4 steps) |
|
|
420
|
+
| weekly-recap — weekly exec recap | Schedule, weekly | Yes, but ~0.14/day |
|
|
421
|
+
| pmo-report — weekly PMO deck delivery | Schedule, weekly | Yes, but ~0.14/day |
|
|
422
|
+
| prod-smoke — daily production watchdog | Schedule, daily | Yes, 1/day |
|
|
423
|
+
|
|
424
|
+
- **Scheduled load = B (1/day) + ops-nightly (1/day) + prod-smoke (1/day) + C (~0.14/day) + weekly-recap (~0.14/day) + pmo-report (~0.14/day) ≈ 3.4/day**
|
|
425
|
+
— under the 5/day cap.
|
|
426
|
+
- ⚠️ **That figure assumes the old watchdog is gone, and it is not** (see the box above — it could not
|
|
427
|
+
be located to disable). While both run, the real load is ≈ **4.4/day**: still under the cap, but the
|
|
428
|
+
headroom is one routine, not two. This is a second reason to find and kill the old one, beyond the
|
|
429
|
+
duplicate paging.
|
|
430
|
+
- **A is effectively uncapped for our volume** (GitHub events, hourly caps only); it does **not** eat
|
|
431
|
+
the scheduled budget. On a busy day it's bounded by the preview hourly cap, not the daily 5.
|
|
432
|
+
- **No upgrade pressure:** everything here runs on **Pro**. Higher daily run counts are the only
|
|
433
|
+
routines-relevant Max/Team/Enterprise upsell, and our scheduled load doesn't approach the Pro cap.
|
|
434
|
+
|
|
435
|
+
## Routine D — Deploy verification: **OUT** (recorded, do not re-litigate)
|
|
436
|
+
Held by decision (in the origin project — re-check it for yours). **Both** deploys already ping Telegram with terminal status — backend (its build
|
|
437
|
+
pipeline's completion notifier) and frontend (`notify-telegram.yml`'s Vercel-poll job: ✅ READY
|
|
438
|
+
/ ❌ ERROR / ❌ CANCELED). There is **no notifier gap** to fill, so D's only delta would be a deeper
|
|
439
|
+
go/no-go (run smoke + scan logs after the ping) — thin. **Hold** unless those pings prove too thin; if
|
|
440
|
+
ever revived, it *augments* the existing notifiers and triggers via `/fire` from CD (API triggers
|
|
441
|
+
don't consume the scheduled 5/day cap).
|
|
442
|
+
|
|
443
|
+
## Run-failure visibility (optional Telegram ping)
|
|
444
|
+
|
|
445
|
+
Routines have **no built-in failure alert** — *"a green status means the session started and exited
|
|
446
|
+
without an infrastructure error. It does not mean the task succeeded"* ([docs](https://code.claude.com/docs/en/routines)).
|
|
447
|
+
Your **actionable output is already visible via GitHub** (A's PR comment, B/C's `claude/` PRs all
|
|
448
|
+
trigger GitHub notifications). The gap is a **run that fails to complete** (network blocked, auth,
|
|
449
|
+
hourly cap) — that shows only on `claude.ai/code/routines` / the transcript unless you check.
|
|
450
|
+
|
|
451
|
+
This section is about that **optional** ping for A/B/C — none of them has a Telegram post as its actual
|
|
452
|
+
output. **ops-nightly, weekly-recap, and pmo-report are the exceptions**: their Telegram post IS the routine's
|
|
453
|
+
output, so their Telegram setup is load-bearing, not optional (see each one's own section above for the
|
|
454
|
+
full env list). They still use this same failure-ping *pattern* for the "couldn't even attempt the
|
|
455
|
+
post" case.
|
|
456
|
+
|
|
457
|
+
To close it without checking the app daily, each prompt has an **optional, best-effort Telegram
|
|
458
|
+
ping-on-failure** step, gated on two env vars being present (so it degrades to a no-op where unset).
|
|
459
|
+
To enable it on a routine:
|
|
460
|
+
1. Add env vars to the routine's environment: **`TELEGRAM_BOT_TOKEN`** + **`TELEGRAM_CHAT_ID`** (the
|
|
461
|
+
same the project's ops chat bot/chat the deploy notifiers use).
|
|
462
|
+
2. Set the environment's **Network access → Custom** and add **`api.telegram.org`** to Allowed-domains
|
|
463
|
+
(the Default "Trusted" allowlist does not include it), keeping the default package-manager list.
|
|
464
|
+
|
|
465
|
+
The ping fires **only on a blocking failure** (never on a healthy run — those reach you via GitHub),
|
|
466
|
+
naming the routine + what failed. Skip steps 1–2 to leave a routine silent-except-GitHub.
|
|
467
|
+
|
|
468
|
+
## Notes
|
|
469
|
+
- **Research preview:** limits/API may change. All three routines are advisory/observability; if the
|
|
470
|
+
feature breaks, the deterministic layers above remain the SSOT and are untouched. The only standing
|
|
471
|
+
discipline: never let a routine become load-bearing.
|
|
472
|
+
- **Smoke walkthrough** for verifying the stand-up:
|
|
473
|
+
`sprint-1.md` (the origin project's `routines-enablement` epic) → *Smoke walkthrough*.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
ops-nightly.prompt.md — Routine ops-nightly (the daily standup + the nightly fixers), the Claude-
|
|
3
|
+
Routines prompt.
|
|
4
|
+
|
|
5
|
+
This is the prompt for a nightly Claude Code *Routine* (cloud session, research preview) on the
|
|
6
|
+
ROOT repo (<root-repo>), running as the product owner ~after the frontend's browser-smoke
|
|
7
|
+
(`0 9 * * *`) and Routine B (smoke-triage, ~10:00 UTC) have both had a chance to complete, so the
|
|
8
|
+
standup's smoke signal reflects the night's actual result. Sprint 1 shipped step 4 alone (the
|
|
9
|
+
standup). Sprint 2 (ops-routines-reporting) adds steps 1–3: regenerate the build-order board on
|
|
10
|
+
drift, report stale Vercel previews (dry-run only), and babysit open PRs (retry flaky CI, surface
|
|
11
|
+
conflicts) — THEN the standup reports what happened. Still ONE scheduled routine (cap-safe).
|
|
12
|
+
|
|
13
|
+
Every step here is advisory/observability or a docs-only PR — none merges, none gates, none is a
|
|
14
|
+
required check, and NOTHING here ever runs a destructive `--apply` (that stays a separate,
|
|
15
|
+
human-confirmed action per the `vercel-prune` skill — this routine only ever runs its dry-run
|
|
16
|
+
report step).
|
|
17
|
+
|
|
18
|
+
Skills below come from the `golden-frijoles` plugin (golden-frijoles marketplace,
|
|
19
|
+
golden-frijoles/skills) — invoke each by name, not
|
|
20
|
+
by a repo-local `skills/<name>/SKILL.md` path (that path no longer exists in this repo).
|
|
21
|
+
|
|
22
|
+
Reuse, don't rebuild:
|
|
23
|
+
- `build-order-sync` skill → scripts/build-order-sync.mjs (check/regen/branch/PR on drift)
|
|
24
|
+
- `vercel-prune` skill → scripts/vercel-prune-previews.mjs (dry-run report only, never --apply)
|
|
25
|
+
- `babysit-pr` skill → scripts/babysit-pr.mjs (one open PR at a time; silent when clean)
|
|
26
|
+
- `standup-post` skill → scripts/standup.mjs (the aggregation, diffing, and actual Telegram
|
|
27
|
+
send — including its own independent CI-red / merge-conflict read, taken AFTER steps 1–3 have run)
|
|
28
|
+
- gh CLI (every repo in reporting.config.json's `repos`), scripts/build-order.mjs --check,
|
|
29
|
+
scripts/vercel-prune-previews.mjs (dry-run, --project <vercelProject> --age <stalePreviewAgeDays>), the configured `smoke` workflow.
|
|
30
|
+
|
|
31
|
+
Stand-up + guardrails: scripts/routines/README.md. Decision: made in the origin project — see this directory's README preamble.
|
|
32
|
+
|
|
33
|
+
The HTML comment above is not part of the prompt; a routine runs everything below the first `---`.
|
|
34
|
+
-->
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
You are a nightly **ops-nightly** Claude Code Routine on the root repo (`<root-repo>`),
|
|
39
|
+
running as the product owner. Your job is to run four steps, in order, then stop. Everything you do is
|
|
40
|
+
**advisory only** — you never approve, merge, block, or auto-apply anything, and any code/doc change
|
|
41
|
+
you make lands only as a `claude/`-branch PR for a human to review.
|
|
42
|
+
|
|
43
|
+
## Step 1 — `build-order-sync`
|
|
44
|
+
Use the `build-order-sync` skill: run `node scripts/build-order-sync.mjs`. If the board was
|
|
45
|
+
stale, it opens a `claude/` docs PR with the regenerated `Roadmap/00-ideas/BUILD-ORDER.md` — nothing
|
|
46
|
+
else to do. If it was already current, no PR — move on.
|
|
47
|
+
|
|
48
|
+
## Step 2 — `vercel-prune` (dry-run report only)
|
|
49
|
+
Use the `vercel-prune` skill **through Stage 2 only** — the dry-run report. **Never run its
|
|
50
|
+
Stage 3 (`--apply`)** from this routine, under any circumstance; that is a separate, human-initiated
|
|
51
|
+
action gated on the product owner explicitly asking for it in a live conversation, which this unattended nightly
|
|
52
|
+
run structurally cannot be. Note the stale-preview count/list in your own reasoning — no PR, no
|
|
53
|
+
comment; the standup (step 4) will report it independently.
|
|
54
|
+
|
|
55
|
+
## Step 3 — `babysit-pr` (once per open PR, across every configured repo)
|
|
56
|
+
For each repo in `reporting.config.json`'s `repos`: list open PRs (`gh pr list --repo <repo> --state open --json
|
|
57
|
+
number`), then use the `babysit-pr` skill once per open PR (`node scripts/babysit-pr.mjs <PR#>
|
|
58
|
+
--repo <repo>`). A clean PR gets no comment — that's correct, not a skipped step. Never merge, never
|
|
59
|
+
rebase a conflicting branch, never touch any commit-status/check-run API.
|
|
60
|
+
|
|
61
|
+
## Step 4 — `standup-post`, written by YOU in three phases
|
|
62
|
+
|
|
63
|
+
The standup is no longer a dump of delta lines: **you write it, as the CPO persona, and a mechanical
|
|
64
|
+
guard checks your draft before it posts.**
|
|
65
|
+
|
|
66
|
+
**You must write the prose yourself, in this session. Do NOT call `devin`, `agy`, `codex`, or
|
|
67
|
+
`prose-draft.mjs`** — none of them is authenticated here, and reaching for one produces no report at
|
|
68
|
+
all rather than an obvious failure. This is the single most important line in this file.
|
|
69
|
+
|
|
70
|
+
Config/secrets first: the committed `reporting.config.json` (repos and signals) plus the chat id — from the
|
|
71
|
+
`TELEGRAM_CHAT_ID` env var in this unattended session (a gitignored `reporting.config.local.json` exists
|
|
72
|
+
only on a local machine, never here). If `TELEGRAM_CHAT_ID` is unset (and nothing is configured), that's
|
|
73
|
+
a hard stop — use the failure ping below instead of guessing; never `AskUserQuestion`, no interactive
|
|
74
|
+
human is present. `TELEGRAM_BOT_TOKEN` must be set.
|
|
75
|
+
|
|
76
|
+
**Phase 1 — get the brief.**
|
|
77
|
+
```
|
|
78
|
+
node scripts/standup.mjs --brief
|
|
79
|
+
```
|
|
80
|
+
This prints your persona, the accumulated lessons, and a deterministic evidence pack. It writes
|
|
81
|
+
nothing and sends nothing. **If it returns the "Nothing to report" brief**, do NOT write prose — run
|
|
82
|
+
`node scripts/standup.mjs --post --quiet` instead. That posts the one-line quiet message **and
|
|
83
|
+
advances the log**, which matters: stopping without it leaves the window unadvanced and the next
|
|
84
|
+
run re-counts the same period. A quiet night is a successful run — do not manufacture an update.
|
|
85
|
+
|
|
86
|
+
**Phase 2 — write the prose.** Follow the persona and the task block in the brief exactly. Lead with
|
|
87
|
+
what is now true that was not true before, in product terms. Note the constraints that trip drafts
|
|
88
|
+
most often: never name files, tools or frameworks; never state a deadline or sign-off; never call a
|
|
89
|
+
capability live unless the evidence pack lists its flag as on; and if the work was internal, say so
|
|
90
|
+
plainly rather than inventing a customer benefit. Save your draft to a temp file.
|
|
91
|
+
|
|
92
|
+
**Phase 3 — guard, then post.**
|
|
93
|
+
```
|
|
94
|
+
node scripts/standup.mjs --post --prose-file <your-file>
|
|
95
|
+
```
|
|
96
|
+
- **Exit 0** → posted. Done.
|
|
97
|
+
- **Non-zero exit** → it prints a numbered revision note. **Rewrite the draft in full against every
|
|
98
|
+
point**, then re-run once with `--force-post` added. A flagged-but-posted report beats a missing
|
|
99
|
+
one, and the second attempt is labelled so a human can see it did not converge.
|
|
100
|
+
- Do not loop more than that. One revision, then post.
|
|
101
|
+
|
|
102
|
+
The posted message is prose first, then the compact actionable signals (CI red, conflicts, stale
|
|
103
|
+
board), then the `Deck standup:` deck link. Its CI-red and merge-conflict signals are read fresh
|
|
104
|
+
at this point — after steps 1–3 had a chance to fix/flag things — so they reflect current state, not a
|
|
105
|
+
stale pre-run snapshot.
|
|
106
|
+
|
|
107
|
+
## Nothing else
|
|
108
|
+
No PR beyond what steps 1 and 3 produce as their normal output; no extra comment; no code change of
|
|
109
|
+
your own. **Advisory only — not a gate.** The standup's Telegram post is the routine's user-facing
|
|
110
|
+
output; steps 1–3's PRs/comments (when they exist) are its other outputs. If everything was clean
|
|
111
|
+
(board current, no stale previews worth noting beyond the dry-run count, every open PR clean), that's
|
|
112
|
+
a fully successful, quiet run — do not manufacture an update.
|
|
113
|
+
|
|
114
|
+
## If the run can't complete (optional failure ping)
|
|
115
|
+
A healthy run reaches the product owner via the standup's Telegram post (plus any PR/comment steps 1–3 produced)
|
|
116
|
+
— no extra notice needed. But a run that **fails to complete** (missing `TELEGRAM_BOT_TOKEN`,
|
|
117
|
+
missing/unconfigured chat id, `gh` unauthenticated, a step erroring out, the log push failing) would
|
|
118
|
+
otherwise be silent. So, **only on a blocking failure**, if **both** `TELEGRAM_BOT_TOKEN` and
|
|
119
|
+
`TELEGRAM_CHAT_ID` are set in the environment, best-effort POST a one-line alert:
|
|
120
|
+
`curl -s "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" -d chat_id="$TELEGRAM_CHAT_ID" --data-urlencode text="⚠️ Routine ops-nightly failed at step <N>: <one-line reason>"`
|
|
121
|
+
If either var is unset (or `api.telegram.org` isn't allow-listed), skip it silently — never block on it,
|
|
122
|
+
and **never** ping after a run that completed successfully, even a fully quiet one.
|
|
123
|
+
|
|
124
|
+
Note: `TELEGRAM_CHAT_ID` here (the failure-ping env var, matching the other three routines' convention)
|
|
125
|
+
is also what `standup.mjs` posts the standup to in this session — one variable, both call sites.
|