Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
e87ced3
feat(routines): add six new cloud routines and trim Custodian
JacobPEvans-personal May 22, 2026
cf07903
chore(routines): back-commit trigger_ids for six new cloud routines
JacobPEvans-personal May 22, 2026
694c26e
feat(attribution): unify PR/issue provenance across all routines
JacobPEvans-personal May 24, 2026
2de04ab
Merge remote-tracking branch 'origin/main' into feat/six-new-routines
JacobPEvans-personal May 25, 2026
03ef0f8
chore(security): add CODEOWNERS for routines + deploy skill + CLAUDE.md
JacobPEvans-personal May 25, 2026
696134b
feat(routines): add cross-cutting controls to CLAUDE.md
JacobPEvans-personal May 25, 2026
abeb704
refactor(distributor): thin callers, 4 inline tiers, SHA-pinned refs,…
JacobPEvans-personal May 25, 2026
77e2220
refactor(inspector): 3 rules with proper scoping, redaction filter, c…
JacobPEvans-personal May 25, 2026
da42dd0
refactor(quartermaster): collapse to single pre-commit dimension with…
JacobPEvans-personal May 25, 2026
2c5a614
refactor(archivist): pivot to README quality + Mintlify coverage
JacobPEvans-personal May 25, 2026
1e6d7f9
refactor(apothecary): CodeQL-primary triage, severity-level gating, d…
JacobPEvans-personal May 25, 2026
5d75981
refactor(conductor): allowlist + batching, release-PR file gate, sign…
JacobPEvans-personal May 25, 2026
1f59d71
refactor(routines): simplify per /simplify review (api batching + ter…
JacobPEvans-personal May 25, 2026
26f45c4
Merge remote-tracking branch 'origin/main' into feat/six-new-routines
JacobPEvans-personal May 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# CODEOWNERS — review-required paths
#
# Per §S4 of the routines rewrite plan: prompts are load-bearing
# code that the cloud sandbox executes on cron. A malicious or
# accidental edit to any of these files can mass-mutate the
# JacobPEvans estate via the `JacobPEvans-claude` GitHub App
# (contents:write across the owner).
#
# Pair with branch protection requiring CODEOWNERS review.

/routines/ @JacobPEvans
/.claude/skills/deploy-routine-changes/ @JacobPEvans
/CLAUDE.md @JacobPEvans
/.github/CODEOWNERS @JacobPEvans
2 changes: 1 addition & 1 deletion .github/workflows/issue-solver.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ concurrency:

jobs:
solve:
name: Pick one open issue, draft a PR
name: Pick one open issue, open a review-ready PR
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
timeout-minutes: 30
Expand Down
207 changes: 205 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Claude Code Routines — Operator Guide

This repo is the source of truth for six cloud routines hosted on
This repo is the source of truth for twelve cloud routines hosted on
Anthropic's Claude Code platform. Files in `routines/*.prompt.md` are the
versioned prompts; the cloud manages execution.

Expand All @@ -11,11 +11,17 @@ them. A new value means a new cloud routine, not an update.

| Routine | File basename | Cron (UTC) |
| --- | --- | --- |
| Issue Solver | `issue-solver` | `0 0,12 * * *` |
| Daily Polish | `daily-polish` | `0 4 * * *` |
| The Sentinel | `sentinel` | `33 5 * * *` |
| The Inspector | `inspector` | `0 6 * * *` |
| The Custodian | `custodian` | `0 7 * * *` |
| Issue Solver | `issue-solver` | `0 0,12 * * *` |
| The Quartermaster | `quartermaster` | `0 8 * * *` |
| The Archivist | `archivist` | `0 9 * * *` |
| Morning Briefing | `morning-briefing` | `0 10 * * *` |
| The Conductor | `conductor` | `15 11,17 * * *` |
| The Apothecary | `apothecary` | `0 13 * * *` |
| The Distributor | `distributor` | `0 14 * * *` |
| Weekly Scorecard | `weekly-scorecard` | `0 10 * * 1` |

Files live under `routines/<basename>.prompt.md`.
Expand Down Expand Up @@ -71,6 +77,23 @@ last-resort path is the `/schedule update` CLI flow:
Do **not** paste into the web UI — the whole point of versioning
these files is keeping cloud and repo in lockstep.

### Staggered deploy after multi-routine merges

When a single PR rewrites multiple routine prompts (e.g. PR #20),
do NOT deploy all updates in one `RemoteTrigger update` burst.
Stage by blast radius and watch each stage for 48 hours:

1. **Stage 1 (Day 0)** — read-mostly routines (Inspector, Sentinel,
Morning Briefing). Watch 48h.
2. **Stage 2 (Day 2)** — label-only / config-only mutations
(Apothecary, Quartermaster, Daily Polish). Watch 48h.
3. **Stage 3 (Day 4)** — high-mutation routines (Distributor,
Archivist, Conductor, Custodian, Issue Solver). Watch 48h.

If a stage produces unexpected PRs/issues/merges, halt subsequent
stages, set `ROUTINE_PAUSED=true` on the misbehaving routine via
the claude.ai web UI, and fix forward.

## Hard rules for routine prompts

These rules apply to every routine that mutates GitHub state. Bake them
Expand Down Expand Up @@ -99,6 +122,186 @@ identity/auth/signing model in one place).
session-ID variable. References like
`${CLAUDE_CODE_REMOTE_SESSION_ID}` render literally. If you need a
session link, there isn't one.
5. **Paused flag.** Every routine checks `${ROUTINE_PAUSED}` at the
top of its main task. If set (any non-empty value), emit a
single Slack message `🛑 <Routine> paused via env` and exit.
This is the kill switch for a misbehaving routine — setting the
env var on the claude.ai web UI takes effect on the next cron
tick without a redeploy.
6. **Body redaction before any commit/issue/PR composition.** Every
string fetched from outside the routine (file bodies, PR titles,
issue bodies, alert names, commit messages) and destined for
GitHub or Slack MUST pass through the redaction regex set
before being written. Canonical regex set:

```text
s|/Users/[^/]+/|/Users/<redacted>/|g
s|\$\{GIT_HOME[A-Z_]*\}|<path>|g
s|GH_PAT_[A-Z]+|<secret>|g
s|sk-ant-[A-Za-z0-9_-]+|<key>|g
s|gh[ps]_[A-Za-z0-9]+|<key>|g
s|\b\d{12}\b|<aws-account>|g
```

Skip-list when scanning source files: `*.local.md`, `.envrc`,
`.envrc.local`, `CLAUDE.local.md`. A redacted match in a
Provenance "Why" line MUST describe the rule that fired, not
quote the offending string.
7. **Slack output sanitization.** Slack's `<!channel>`, `<!here>`,
`<@USERID>`, `<#CHANNEL>`, `<URL|text>` tokens can be smuggled
through PR titles, issue bodies, alert names. Every Slack-emit
path MUST escape `<` → `‹` and `>` → `›` in any field derived
from repo content:

```bash
safe() { jq -Rr 'gsub("<"; "‹") | gsub(">"; "›")'; }
echo "${untrusted_title}" | safe
```

8. **State gist convention.** Each routine that holds cross-run
memory uses one private GitHub Gist named `<routine>-state`
(e.g. `distributor-state`). Schema:

```json
{
"schema_version": 2,
"prompt_sha256": "abc123…",
"run_log": [
{"ts":"2026-05-25T14:00:00Z","repo":"JacobPEvans/nix-darwin",
"action":"pr_opened","resource_id":"https://github.com/...","reason":""}
],
"closed_pairs": {"JacobPEvans/foo": ["bar.yml"]},
"cooldowns": {"JacobPEvans/foo": "2026-06-01T00:00:00Z"}
}
```

Retention is per-field, not blanket: `run_log` trimmed to 90
days (archive overflow to sibling gist `<routine>-state-archive`),
`closed_pairs` and `apothecary-codeql-ignore` retained
**indefinitely** (rejection memory must outlive trim windows),
cooldowns trim once expired. Hard cap 1 MB per gist. Never
write secrets, raw alert payloads, full PR diffs, or repo file
contents to a state gist — `run_log.reason` is bounded to 200
chars after redaction (rule 6).

9. **Per-repo PR budget.** PR-emitting routines (Distributor,
Inspector, Quartermaster, Archivist Task 1) consult a shared
gist `routine-pr-budget` before opening a PR. Schema:

```json
{
"2026-05-25": {"JacobPEvans/nix-darwin": 1,
"JacobPEvans/ai-workflows": 2}
}
```

Soft cap: **2 PRs per repo per UTC day across all routines**
(Conductor merges don't count). Read the day's counter, skip
the repo if at cap, otherwise increment and proceed.
Concurrency posture is best-effort, not exactly-once — cron
stagger keeps near-misses rare. If the gist is missing,
corrupted, or returns non-JSON: fail open (proceed with the
routine's own per-run cap) AND emit a Slack warning.

10. **Prompt fingerprint logging.** Each run appends one
`prompt_sha256` entry to the state gist (overwrites the
previous entry — only the most-recent fingerprint is needed).
Sentinel cross-checks this against `sha256` of the prompt file
at HEAD of `main` in `JacobPEvans/claude-code-routines`; a
mismatch indicates the cloud deployment is stale or has been
mutated out-of-band.

## Attribution conventions

Every PR or issue created by a cloud routine MUST be self-identifying.
Three layers: title suffix → label → body Provenance block. The user
can't tell which routine made a PR if any of these are missing.

These rules apply to all PR-creating routines (Daily Polish, Sentinel,
Inspector, Quartermaster, Archivist, Apothecary, Distributor,
Issue Solver) and all issue-creating routines (Custodian's repo-audit,
Archivist's private-docs issue, Sentinel's secret alerts if filed).

### Title

```text
<conventional-prefix>(<scope>): <description> [routine:<basename>]
```

`<basename>` matches the routine file basename (`daily-polish`,
`distributor`, `issue-solver`, etc.). Title must NOT contain emoji
(soul rule: no emoji in commit messages, PR titles, PR descriptions,
or release notes). Conventional-commit prefix is preserved so
release-please continues to parse it.

For issues (no conventional prefix needed):

```text
[routine:<basename>] <description>
```

### Body — Provenance block at the bottom

Every PR body and every issue body ends with this block:

```markdown
---

## Provenance

- **Generated by:** [<Routine Name>](<prompt file URL>) -
cloud routine, <cron description>
- **Triggered:** <what fired this run (cron + task selection if any)>
- **Why this PR/issue:** <one-line rationale tying the selection
algorithm to this specific output>
- **State:** [<gist name>](<gist URL>)
- **Label:** `cloud-routine`
```

The block is appended; the rest of the body remains whatever the
routine already writes. No emoji in the body either.

### Label

Apply the `cloud-routine` label after creating the PR or issue:

```bash
gh pr edit "$PR_NUMBER" --repo "$OWNER/$REPO" --add-label cloud-routine
gh issue edit "$ISSUE_NUMBER" --repo "$OWNER/$REPO" --add-label cloud-routine
```

The label is defined in `JacobPEvans/.github/.github/labels.yml` and
propagated to every public repo by the `label-sync.yml` workflow —
routines do NOT need to `gh label create` per repo. If a label-add
call fails because the target repo is private and outside the sync
list, log a warning in Slack but proceed.

### Branch naming

Per-run, dated, namespaced:

```text
<type>/<routine-basename>/<slug>-<YYYY-MM-DD>
```

Examples: `chore/distributor/add-gh-aw-pin-refresh-2026-05-23`,
`docs/daily-polish/int_homelab-2026-05-23`. Avoid collisions across
runs by always including the date in the branch name.

### Review-ready, not draft (with one exception)

`gh pr create` calls do NOT pass `--draft`. PRs open review-ready so
the `ai-workflows` review workflows (`claude-review`,
`final-pr-review`, `ai-merge-gate`) pick them up immediately.
Routines never auto-merge; merges go through the normal review flow
or `The Conductor`'s strict bot-author allowlist (which routine bots
are NOT a member of).

**Exception** — PRs that modify `.github/workflows/*.yml` MUST pass
`--draft`. Two routines do this: Inspector's `no-scripts` rule
(extracts inline workflow logic into `scripts/`) and Distributor's
caller-pin migrations. Draft forces explicit human ready-flip
before any auto-review fires; broken YAML never lands.

## Out of scope for this repo

Expand Down
Loading