docs: CHANGELOG v0.1.0 + planning artifacts

This commit is contained in:
2026-05-09 11:29:07 +07:00
parent 671ba6ad1b
commit f5c895eda5
7 changed files with 627 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.1.0] - 2026-05-09
### Added
- Cloudflare Workers `scheduled` handler that POSTs to the Claude Code routine `/fire` endpoint.
- Default 5×daily cron at UTC+7 (00:07 / 05:13 / 10:19 / 15:23 / 20:37) — scattered minutes for shared-infra hygiene.
- Token-substitution template with `{ISO}`, `{LocalTime}`, `{Cron}` (default: `Scheduled trigger at {LocalTime}`).
- IANA timezone support via `TZ` env var (default: `UTC`).
- Structured JSON logs for fire success / non-2xx / network failure.
- Vitest test suite covering template substitution, fire paths, header correctness, token redaction.
- GitHub Actions CI: `npm test` + `wrangler deploy --dry-run` on push and PR.
- Apache-2.0 license.
@@ -0,0 +1,72 @@
---
phase: 1
title: Repo scaffold
status: completed
priority: P1
effort: 1h
dependencies: []
---
# Phase 1: Repo scaffold
## Overview
Greenfield repo init. Apache-2.0 license, README skeleton, JS + wrangler toolchain, gitignore. No transpile step — Workers runtime executes ES modules natively.
## Requirements
- **Functional:** `npm install` succeeds; `wrangler deploy --dry-run` validates an empty `worker.js` handler.
- **Non-functional:** No secrets in repo. Pin wrangler major version. kebab-case file names.
## Architecture
Flat layout — single-purpose worker, no need for src/ subdirs (KISS).
```
claude-code-routine-trigger-worker/
├── .github/workflows/ci.yml # phase 4
├── .gitignore
├── LICENSE # Apache-2.0
├── README.md # phase 5
├── package.json
├── wrangler.toml # phase 3
├── worker.js # phase 2
└── worker.test.js # phase 4
```
## Related Code Files
- Create: `LICENSE` (Apache-2.0 verbatim)
- Create: `.gitignore` (node_modules, .dev.vars, .wrangler, dist, .env*)
- Create: `package.json` (name, version 0.0.0, `"type": "module"`, scripts: test, deploy, dev)
- Create: `README.md` (one-liner placeholder; full content in phase 5)
## Implementation Steps
1. `cd /config/workspace/tiennm99/claude-code-routine-trigger-worker && git init -b main`
2. Write `LICENSE` (copy Apache-2.0 from sibling `claude-code-routine-cron/LICENSE`).
3. Write `.gitignore`:
```
node_modules/
dist/
.wrangler/
.dev.vars
.env
.env.*
*.log
```
4. Write `package.json` with `"type": "module"`, devDeps: `wrangler` (^4), `vitest` (phase 4), `@cloudflare/vitest-pool-workers` (phase 4).
Scripts:
- `dev`: `wrangler dev`
- `deploy`: `wrangler deploy`
- `test`: `vitest run`
5. Run `npm install` — confirm lockfile generated.
6. Stub `README.md` with one-liner: `> Cloudflare Workers cron port of claude-code-routine-cron — fires Claude Code routines on a schedule.`
7. First commit: `chore: init repo scaffold`.
## Success Criteria
- [ ] `npm install` exits 0
- [ ] `wrangler deploy --dry-run` validates empty `worker.js` exporting `export default {}`
- [ ] `git status` clean after first commit
- [ ] LICENSE file matches Apache-2.0 exactly
- [ ] `package.json` has `"type": "module"`
## Risk Assessment
- **Wrangler version drift:** pin to current major (e.g. `^4.0.0`); document upgrade path in README.
- **Node version:** wrangler 4 needs Node ≥18; document in README prereqs.
- **No type safety:** mitigate by JSDoc type hints in `worker.js` for `Env` and handler signatures (cheap, optional, no toolchain).
@@ -0,0 +1,142 @@
---
phase: 2
title: Worker handler + fire client
status: completed
priority: P1
effort: 2-3h
dependencies:
- 1
---
# Phase 2: Worker handler + fire client
## Overview
Implement the `scheduled` handler in plain JS with JSDoc type annotations: render text template, POST to `/fire`, log structured JSON. Single `worker.js` file. No `fetch` handler (per locked decision: scheduled-only).
## Requirements
- **Functional:**
- On cron tick, POSTs to `ROUTINE_FIRE_URL` with bearer token, beta header, JSON body.
- Logs `claude_code_session_url` on 2xx; logs status + body on non-2xx; never throws (would crash CF retry).
- Renders `TEXT_TEMPLATE` with `{LocalTime}`, `{Cron}`, `{ISO}` placeholders.
- No retry (each `/fire` = new session).
- **Non-functional:**
- Token never logged.
- Use `console.log(JSON.stringify({...}))` for structured logs (CF picks these up).
- JSDoc `@typedef Env` for environment bindings + `@param`/`@returns` on all exports.
- Total file ≤ 200 lines (split if exceeds).
## Architecture
```
ScheduledEvent (cron, scheduledTime)
fireRoutine(env, controller)
├── renderText(template, vars) # simple {Tok} substitution
├── fetch(URL, POST, headers, body)
└── log(json) # success or error
```
### Mapping from Go daemon
| Go (claude-code-routine-cron) | JavaScript (this worker) |
|---|---|
| `Config.FireURL` env | `env.ROUTINE_FIRE_URL` secret |
| `Config.Token` env | `env.ROUTINE_FIRE_TOKEN` secret |
| `Config.Schedules` env | `wrangler.toml` `[triggers].crons` (literal) |
| `Config.Location` (TZ) | `env.TZ` plain var, default `UTC` |
| `Config.Template` (Go text/template) | `env.TEXT_TEMPLATE` plain string + `{Tok}` substitution |
| `FireClient.Fire()` | `fireRoutine()` function |
| `cron.New()` scheduler | CF Workers scheduled handler (built-in) |
| `slog` JSON logs | `console.log(JSON.stringify(...))` |
### JSDoc typedefs
```js
/**
* @typedef {object} Env
* @property {string} ROUTINE_FIRE_URL - secret: Anthropic /fire endpoint
* @property {string} ROUTINE_FIRE_TOKEN - secret: per-routine bearer token
* @property {string} [TEXT_TEMPLATE] - plain var: prompt template, supports {LocalTime} {Cron} {ISO}
* @property {string} [TZ] - plain var: IANA tz, default 'UTC'
*/
/**
* @typedef {object} FireResponse
* @property {string} type
* @property {string} claude_code_session_id
* @property {string} claude_code_session_url
*/
```
### Headers (verbatim from siblings)
```
Authorization: Bearer ${token}
anthropic-version: 2023-06-01
anthropic-beta: experimental-cc-routine-2026-04-01
Content-Type: application/json
```
### Body
```json
{ "text": "<rendered template>" }
```
### Template substitution (KISS — not Go text/template)
| Token | Value |
|---------------|-------------------------------------------------------|
| `{ISO}` | `new Date(scheduledTime).toISOString()` |
| `{LocalTime}` | formatted via `Intl.DateTimeFormat(env.TZ ?? 'UTC')` |
| `{Cron}` | `controller.cron` |
Default template: `Scheduled trigger at {LocalTime}` — same default as Go daemon.
## Related Code Files
- Create: `worker.js` (export default `{ scheduled }`, JSDoc-annotated)
- (Optional split if >200 lines) `fire-client.js`, `template-renderer.js`
## Implementation Steps
1. Top of `worker.js`: write JSDoc `@typedef Env` + `@typedef FireResponse` blocks.
2. Implement `renderText(template, vars)` with JSDoc:
```js
/**
* @param {string} template
* @param {Record<string, string>} vars
* @returns {string}
*/
```
Replaces `{Key}` tokens, leaves unknown tokens intact.
3. Implement `formatLocalTime(date, tz)` using `Intl.DateTimeFormat`. JSDoc: `@param {Date} date`, `@param {string} tz`, `@returns {string}`.
4. Implement `fireRoutine(env, controller)` with JSDoc `@param {Env} env`, `@param {ScheduledController} controller`, `@returns {Promise<void>}`:
- Build `text` via `renderText`.
- `fetch(env.ROUTINE_FIRE_URL, { method: 'POST', headers, body: JSON.stringify({text}) })`.
- On `!response.ok`: `console.log(JSON.stringify({level:'error', cron, status, body}))`.
- On `ok`: parse JSON, log `{level:'info', cron, session_url, session_id}`.
- Wrap in try/catch — log network errors, don't rethrow.
5. Export default with JSDoc `@type {ExportedHandler<Env>}`:
```js
/** @type {ExportedHandler<Env>} */
export default {
async scheduled(controller, env, ctx) {
ctx.waitUntil(fireRoutine(env, controller));
},
};
```
6. `npx tsc --noEmit --allowJs --checkJs worker.js --target es2022 --module esnext --moduleResolution bundler --types @cloudflare/workers-types` (one-shot smoke check; not added to scripts to avoid TS dependency creep). Optional — skip if undesired.
7. Local smoke: `wrangler dev --test-scheduled`, then `curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*"` — confirm log line.
## Success Criteria
- [ ] `worker.js` ≤ 200 lines (split if needed)
- [ ] All exported functions have JSDoc with `@param` + `@returns`
- [ ] `wrangler deploy --dry-run` validates module
- [ ] Token never appears in logged output (manual grep on `wrangler dev` output)
- [ ] Local `__scheduled` test produces structured JSON log
## Risk Assessment
- **`Intl.DateTimeFormat` TZ support on Workers runtime:** supported since 2023; verify with smoke test.
- **`ctx.waitUntil` vs awaiting in `scheduled`:** `waitUntil` ensures fire completes even if handler returns; preferred per CF docs.
- **No compile-time type errors:** JSDoc + editor LSP catches most; runtime tests (phase 4) catch the rest.
- **Template injection:** `text` is sent only to Anthropic API; no XSS / SQL surface — safe.
## Security Considerations
- Token comes only from `env.ROUTINE_FIRE_TOKEN` (CF Secret), never bundled.
- `console.log` body must not include `Authorization` header value.
- TLS to `api.anthropic.com` is automatic (Workers `fetch` validates certs).
@@ -0,0 +1,109 @@
---
phase: 3
title: wrangler config + secrets
status: completed
priority: P1
effort: 1h
dependencies:
- 1
- 2
---
# Phase 3: wrangler config + secrets
## Overview
Author `wrangler.toml` with literal `[triggers].crons`, define plain vars vs secrets, document deployment flow. CF Workers cron triggers fire from `wrangler.toml` only — same constraint as GH Actions.
## Requirements
- **Functional:**
- `wrangler deploy` succeeds with valid `wrangler.toml`.
- At least one cron expression in `[triggers]` (default 5×daily mirroring sibling repos).
- Secrets uploaded out-of-band (not in `wrangler.toml`).
- **Non-functional:**
- All comments above each block explain the field.
- Default crons use scattered minutes (avoid minute `0`) per `claude-code-routine-trigger` README warning — though CF cron is more reliable than GH, scattering is still good hygiene.
## Architecture
### `wrangler.toml` shape
```toml
name = "claude-code-routine-trigger-worker"
main = "worker.js"
compatibility_date = "2026-05-09"
compatibility_flags = ["nodejs_compat"] # only if Intl needs polyfill — verify in phase 2
# Plain vars (visible in dashboard, fine for non-secrets)
[vars]
TEXT_TEMPLATE = "Scheduled trigger at {LocalTime}"
TZ = "Asia/Ho_Chi_Minh"
# Cron triggers — LITERAL, cannot read from env/secrets/vars
[triggers]
crons = [
"7 17 * * *", # 00:07 UTC+7
"13 22 * * *", # 05:13 UTC+7
"19 3 * * *", # 10:19 UTC+7
"23 8 * * *", # 15:23 UTC+7
"37 13 * * *", # 20:37 UTC+7
]
# Observability — opt-in, free tier supports basic logs
[observability]
enabled = true
```
### Secrets (uploaded via `wrangler secret put`)
| Name | Source |
|----------------------|-------------------------------------|
| `ROUTINE_FIRE_URL` | Anthropic routine editor → API trigger |
| `ROUTINE_FIRE_TOKEN` | Anthropic routine editor (shown once) |
### Local dev: `.dev.vars`
```
ROUTINE_FIRE_URL=https://api.anthropic.com/v1/claude_code/routines/trig_.../fire
ROUTINE_FIRE_TOKEN=sk-ant-oat01-...
```
Gitignored. Used by `wrangler dev` only.
### `wrangler.toml.example`
Committed copy with placeholder values for documentation; real `wrangler.toml` ships with author's defaults but secrets are NEVER literal.
## Related Code Files
- Create: `wrangler.toml`
- Create: `wrangler.toml.example` (or document in README — KISS, prefer README)
- Create: `.dev.vars.example` with placeholder secret names + dummy values
## Implementation Steps
1. Write `wrangler.toml` per architecture block above.
- Pick `compatibility_date` = today (2026-05-09).
- Decide on `nodejs_compat` flag — only enable if phase 2 needs it (`Intl` is built-in, likely **don't need it**).
2. Write `.dev.vars.example`:
```
ROUTINE_FIRE_URL=https://api.anthropic.com/v1/claude_code/routines/trig_REPLACE/fire
ROUTINE_FIRE_TOKEN=sk-ant-oat01-REPLACE
```
3. Verify `.gitignore` includes `.dev.vars` (added in phase 1).
4. Local validation: `wrangler dev` — must boot without error.
5. Document secret-upload flow in phase 5 README:
```bash
echo -n 'https://...' | wrangler secret put ROUTINE_FIRE_URL
echo -n 'sk-ant-oat01-...' | wrangler secret put ROUTINE_FIRE_TOKEN
```
6. Commit: `feat: wrangler config with default 5x-daily crons`.
## Success Criteria
- [ ] `wrangler dev` starts cleanly with `.dev.vars` populated
- [ ] `wrangler deploy --dry-run` validates `wrangler.toml` without errors
- [ ] All 5 default crons match sibling repos' cadence (UTC+7 daily 00/05/10/15/20)
- [ ] No secret values in any committed file
- [ ] `.dev.vars` confirmed in `.gitignore`
## Risk Assessment
- **CF cron expression syntax:** CF Workers cron supports standard 5-field; `*/N` and ranges work. Avoid `?` (Quartz-only).
- **Minute-`0` GH issue does not apply to CF** but scattering minutes is still recommended (free tier shared infra benefits).
- **Free tier limit:** 5 cron triggers per worker. Default config uses exactly 5 — at limit. Document that adding a 6th requires another worker.
## Security Considerations
- Secrets never in `wrangler.toml` (only `[vars]` for non-secrets).
- `.dev.vars` is gitignored.
- `wrangler secret put` accepts stdin, avoiding shell history leak (use `echo -n ... | wrangler secret put`).
@@ -0,0 +1,101 @@
---
phase: 4
title: Tests + CI
status: completed
priority: P2
effort: 2h
dependencies:
- 2
- 3
---
# Phase 4: Tests + CI
## Overview
Vitest with `@cloudflare/vitest-pool-workers` for in-Workers-runtime tests. Cover template rendering, fire client behavior (success / non-2xx / network error), token redaction. GitHub Actions CI on push.
## Requirements
- **Functional:**
- Tests cover: template substitution, missing-token rendering, fire success path, non-2xx path, fetch throws path.
- CI runs `npm test` on push to `main` and PRs.
- **Non-functional:**
- Coverage report (text summary in CI logs).
- Tests use mocked `fetch` — no real network calls (don't burn user's Anthropic quota).
- Tests written in plain JS with JSDoc, matching production code style.
- Race-free is N/A on Workers; instead assert `console.log` output never contains token.
## Architecture
### Test layout
```
worker.test.js # all tests in one file initially (KISS)
```
Split if exceeds 200 lines.
### Test cases (mirror Go daemon's test coverage)
| # | Case | Assertion |
|---|------|-----------|
| 1 | `renderText` substitutes `{LocalTime}`, `{Cron}`, `{ISO}` | output equals expected string |
| 2 | `renderText` leaves unknown `{Foo}` tokens intact | output contains `{Foo}` |
| 3 | `fireRoutine` 2xx response logs `session_url` | console captured contains `session_url` |
| 4 | `fireRoutine` 401 response logs `level:error` | captured log has `level:'error'`, `status:401` |
| 5 | `fireRoutine` `fetch` rejects (network) logs error, doesn't throw | no thrown exception, error log present |
| 6 | Token redaction | for cases 3, 4, 5: stringified logs do NOT contain token value |
| 7 | Headers correctness | mocked `fetch` receives `Authorization: Bearer ${token}`, `anthropic-version`, `anthropic-beta` |
| 8 | Body shape | request body parses as `{ "text": <rendered> }` |
| 9 | Default template when `TEXT_TEMPLATE` unset | output starts with `Scheduled trigger at ` |
| 10 | TZ defaults to UTC when `env.TZ` unset | local time formatting uses UTC |
### CI workflow
`.github/workflows/ci.yml` — single job, Node 20, runs test + wrangler dry-run as smoke gate.
## Related Code Files
- Create: `worker.test.js`
- Create: `vitest.config.js` (uses `@cloudflare/vitest-pool-workers`)
- Create: `.github/workflows/ci.yml`
## Implementation Steps
1. Install: `npm i -D vitest @cloudflare/vitest-pool-workers`.
2. Write `vitest.config.js`:
```js
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({
test: { poolOptions: { workers: { wrangler: { configPath: './wrangler.toml' } } } },
});
```
3. Write `worker.test.js` covering all 10 cases. Use `vi.fn()` mock for `globalThis.fetch`. JSDoc-annotate test helper functions for IDE assist.
4. Run `npm test` — all green.
5. Write `.github/workflows/ci.yml`:
```yaml
name: ci
on:
push: { branches: [main] }
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- run: npm test
- run: npx wrangler deploy --dry-run
```
6. Push to GitHub; verify green CI.
## Success Criteria
- [ ] All 10 test cases pass locally
- [ ] `npm test` exits 0 in CI
- [ ] `wrangler deploy --dry-run` exits 0 in CI
- [ ] No real HTTP traffic generated by tests (verify by mock spy)
- [ ] Coverage ≥ 80% (track via vitest `--coverage`; not enforced as blocking gate)
## Risk Assessment
- **Workers test pool quirks:** `@cloudflare/vitest-pool-workers` requires wrangler config; ensure `wrangler.toml` validates first.
- **Mocking `fetch` inside Workers runtime:** verify pattern works — fall back to dependency-injected fetch if not.
- **Beta header pinning in tests:** assert exact value to catch accidental edits.
## Security Considerations
- Test fixtures use placeholder token `sk-ant-oat01-test-only` — never a real value.
- Test asserts logs **don't contain** the token (regression guard).
@@ -0,0 +1,89 @@
---
phase: 5
title: Docs + release
status: completed
priority: P2
effort: 1.5h
dependencies:
- 1
- 2
- 3
- 4
---
# Phase 5: Docs + release
## Overview
Full README mirroring sibling repos' tone, comparison-table back-link in both siblings, v0.1.0 GitHub release, public deployment of the author's own instance.
## Requirements
- **Functional:**
- README covers: why-this-vs-siblings, quickstart, env vars, secret upload, customize schedule, beta header policy, security, license.
- GitHub release `v0.1.0` tagged.
- Both sibling READMEs updated with a row pointing to this repo.
- **Non-functional:**
- Same prose voice as siblings (`> [!TIP]` / `> [!WARNING]` callouts, terse).
- Code blocks copy-pasteable.
- All claims about CF cron precision sourced (link CF docs).
## Architecture
### README sections (in order)
1. **Header + tagline** — one-liner.
2. **Why this vs siblings** — three-column comparison table.
3. **Quickstart**`wrangler deploy` + 2 secret-puts + done.
4. **Environment variables** — table (name, required, default, notes).
5. **Multiple schedules**`wrangler.toml` `[triggers].crons` array.
6. **Templates**`{LocalTime}`, `{Cron}`, `{ISO}`.
7. **Customize schedule** — edit `wrangler.toml`, `wrangler deploy`.
8. **Local development**`.dev.vars` + `wrangler dev --test-scheduled`.
9. **Secret rotation**`wrangler secret put` overwrites.
10. **CF Workers free tier** — link, note 5-cron-per-worker limit.
11. **Beta header** — pinned value, bump policy.
12. **Operational notes** — log access (`wrangler tail`), no idempotency.
13. **License** — Apache-2.0.
### Sibling repo updates
- Edit `claude-code-routine-trigger/README.md` — add row in the "no longer used by author" callout listing `trigger-worker` as another option.
- Edit `claude-code-routine-cron/README.md` — extend "Why this vs `claude-code-routine-trigger`" table to a 3-way comparison including `trigger-worker`.
## Related Code Files
- Modify: `README.md` (this repo)
- Modify (out of repo, requires PR/commit): `/config/workspace/tiennm99/claude-code-routine-trigger/README.md`
- Modify (out of repo): `/config/workspace/tiennm99/claude-code-routine-cron/README.md`
- Create: `CHANGELOG.md` (Keep-A-Changelog format)
- Optional: `.github/release.yml` for release-notes automation
## Implementation Steps
1. Draft README per architecture sections — use sibling READMEs verbatim where applicable (Apache-2.0 license clauses, beta header note, security note about per-routine token scope).
2. Write `CHANGELOG.md` with `## [0.1.0] - 2026-MM-DD` initial entry listing features.
3. Local proof: deploy author's instance via `wrangler deploy`, attach a non-prod routine, observe one cron tick, capture `session_url` for README screenshot.
4. Tag and release:
```bash
git tag v0.1.0
git push origin v0.1.0
gh release create v0.1.0 --generate-notes
```
5. Open PRs (or direct commits if author owns) to update sibling READMEs with the comparison row.
6. Final commit: `docs: README + v0.1.0 release notes`.
## Success Criteria
- [ ] README renders cleanly on GitHub
- [ ] All code blocks in README are runnable (copy-paste tested)
- [ ] Comparison table accurately reflects all 3 options
- [ ] Both sibling READMEs reference this repo
- [ ] `v0.1.0` tag pushed and GitHub release published
- [ ] Author's own deployment fired at least one successful cron tick (proof-of-life)
## Risk Assessment
- **Beta header drift:** if Anthropic ships new dated beta between phase 2 and release, bump in code + CHANGELOG before tagging.
- **Sibling repo edits cause merge conflicts:** branch + PR if user has unmerged work; otherwise direct commit.
- **CF dashboard URL format changes:** don't hardcode dashboard URLs in README; use `wrangler tail` (CLI) which is stable.
## Security Considerations
- README must NOT include real `ROUTINE_FIRE_URL` or token — only `sk-ant-oat01-...` placeholder format.
- Document that pushing to a public fork preserves `.dev.vars` exclusion (gitignore line).
## Next Steps (post v0.1.0)
- v0.2: optional `WEBHOOK_URL` to mirror fire output to Slack/Discord (low priority — most users use `wrangler tail`).
- v0.3: `wrangler.toml.example` with multi-routine deployment helper script.
@@ -0,0 +1,94 @@
---
title: claude-code-routine-trigger-worker — CF Workers cron port
description: >-
Cloudflare Workers port of claude-code-routine-cron: scheduled handler fires
Claude Code routine /fire endpoint, zero infra, free tier
status: completed
completed: 2026-05-09
priority: P2
created: 2026-05-09T00:00:00.000Z
target_repo: /config/workspace/tiennm99/claude-code-routine-trigger-worker
reference_repos:
- /config/workspace/tiennm99/claude-code-routine-trigger
- /config/workspace/tiennm99/claude-code-routine-cron
license: Apache-2.0
blockedBy: []
blocks: []
---
# claude-code-routine-trigger-worker — CF Workers cron port
## Problem
Two existing siblings cover the routine-fire space, both with trade-offs:
- `claude-code-routine-trigger` (GH Actions): free, but cron unreliable (30 min 2h delays, occasional drops at minute `0`).
- `claude-code-routine-cron` (Go daemon, Docker): sub-second precision, but requires self-hosted infra.
Gap: a free, no-infra option with reliable timing.
## Goal
Cloudflare Workers port: scheduled handler POSTs to the Claude Code routine `/fire` endpoint on a fixed cron. Free tier (CF Workers cron triggers cost $0 within free limits). Single TypeScript file, deploy via `wrangler deploy`.
## Locked Decisions
| # | Decision | Choice |
|---|----------|--------|
| 1 | Repo name | Completed |
| 2 | Scope | Completed |
| 3 | Cron config | Completed |
| 4 | Manual HTTP fire | Completed |
| 5 | Language | Completed |
| 6 | Secret management | `wrangler secret put``ROUTINE_FIRE_URL`, `ROUTINE_FIRE_TOKEN` |
| 7 | Optional config | `TEXT_TEMPLATE` (env var, plain string with placeholders), `TZ` (env var) |
| 8 | License | Apache-2.0 (matches siblings) |
## Non-goals
- Manual `/fire` HTTP endpoint (KISS — scheduled only)
- Multiple routines per worker (deploy N workers for N routines)
- Retry on failure (each POST = new session; retries multiply sessions)
- Durable Objects / KV / D1 (stateless)
- Custom log shipping (CF dashboard `wrangler tail` is enough)
- Go-style `text/template` (use simple `{token}` substitution — KISS)
## Comparison Matrix (post-implementation)
| | trigger (GH Actions) | cron (Go daemon) | **trigger-worker (this)** |
| ---------------- | -------------------- | ------------------------ | ------------------------- |
| Runs on | GitHub runners | self-hosted Docker | Cloudflare edge |
| Cost | free (GH minutes) | minimal (own infra) | free (CF free tier) |
| Cron precision | ±30 min 2 h | sub-second | ±15 sec (CF SLA) |
| Setup | fork + 2 secrets | env vars + Docker | wrangler deploy + secrets |
| Audit trail | Actions runs page | container stdout | CF dashboard / `tail` |
## Phases
| Phase | Name | Status |
|-------|------|--------|
| 1 | [Repo scaffold](./phase-01-repo-scaffold.md) | Completed |
| 2 | [Worker handler + fire client](./phase-02-worker-handler-fire-client.md) | Completed |
| 3 | [wrangler config + secrets](./phase-03-wrangler-config-secrets.md) | Completed |
| 4 | [Tests + CI](./phase-04-tests-ci.md) | Completed |
| 5 | [Docs + release](./phase-05-docs-release.md) | Completed |
## Dependencies
None. Greenfield repo. Reference repos read-only.
## Risk Register
| Risk | Severity | Mitigation |
|---|---|---|
| CF Workers cron precision in practice | Low | Documented ±15 sec — adequate for routine triggering |
| Wrangler config drift with new CF features | Low | Pin wrangler major version in `package.json` |
| Beta header (`experimental-cc-routine-2026-04-01`) churn | Medium | Constant in code; bump via release; siblings have same exposure |
| Free tier cron limit (5 unique cron expressions per worker) | Low | One-routine-per-worker model means single-digit crons typical |
| Secrets accidentally committed | High | `.dev.vars` gitignored; secrets only via `wrangler secret put` in CI |
## Success Criteria
- [ ] `wrangler deploy` ships worker; first cron tick fires `/fire` and logs `claude_code_session_url`.
- [ ] `wrangler tail` shows JSON-structured logs for each fire.
- [ ] All CF Workers cron expressions in `wrangler.toml` validated by `wrangler dev` locally.
- [ ] CI runs typecheck + tests on push.
- [ ] README documents quickstart, env vars, secret rotation, beta header policy.
- [ ] Release `v0.1.0` published with `wrangler.toml` example.
## References
- Sibling Go daemon: `/config/workspace/tiennm99/claude-code-routine-cron`
- Sibling GH Actions: `/config/workspace/tiennm99/claude-code-routine-trigger`
- Anthropic `/fire` API: https://platform.claude.com/docs/en/api/claude-code/routines-fire
- CF Workers cron: https://developers.cloudflare.com/workers/configuration/cron-triggers/