Skip to content

Cloudflare migration - #55

Merged
AndreiDrang merged 130 commits into
mainfrom
cloudflare-migration
Aug 23, 2026
Merged

AndreiDrang merged 130 commits into
mainfrom
cloudflare-migration

Conversation

@AndreiDrang

@AndreiDrang AndreiDrang commented Aug 17, 2026 •

Copy link
Copy Markdown
Owner

Cloudflare migration

Migrates zai-code-bot from a GitHub Actions–hosted bot to a Cloudflare Workers architecture. The new implementation uses a two-worker hybrid topology with durable D1/Queue/R2/KV storage so webhook deliveries ack fast while LLM-heavy commands and PR analysis run to completion asynchronously — verified live on this PR (see /zai help, /zai review, /zai describe runs in the conversation). The legacy Action bot and its workflows are removed.

✨ Features & Enhancements

  • Two-worker hybrid topology (poc/workers/):
    • zai-main-worker — public webhook ingress: HMAC-SHA256 signature verification (Web Crypto, no nodejs_compat), command parsing, collaborator authorization, and routing; light commands (help) run inline.
    • zai-heavy-worker — binding-only worker (no public ingress) reached via a service binding; internal-token gate, then async dispatch under ctx.waitUntil.
  • Durable async job pipeline:
    • D1 is the single source of truth — storage adapters for deliveries, lease-based job claims, artifacts, config, and keys (migrations 0001–0003).
    • Queue producer in the main worker and consumer in the heavy worker with a 3-attempt retry budget (terminal failures ack without a DLQ) and a transactional outbox; R2 stores result artifacts with 30-day retention, KV backs config caching.
    • 5-minute cron on the main worker self-heals: recover expired leases, replay the outbox, and sweep expired storage.
  • Automated PR preview comments: metadata-only brief on opened/synchronize/title-edited (and closed-state tracking), with exactly one live bot-owned comment updated in place, plus an idempotent "PR closed by @x" lifecycle comment.
  • LLM-backed commands in the heavy worker: /zai review (full-context review), /zai describe, and PR-summary/PR-context jobs, powered by a Z.ai client, an agentic runner with a bounded tool loop, a PR context reader with head-SHA freshness checks, and dedicated prompt modules.
  • Unified invocation: /zai is the single command prefix (@zai-bot mention and /zai-bot forms retired), with author credit in the help footer and a standardized BOT_FOOTER applied to every bot comment.

🐛 Bug Fixes

  • Normalize Cloudflare Secrets Store bindings (string | {get()} | Promise) via resolveSecretValue() at every read site — raw bindings stringified to "[object Object]" and broke every webhook signature with 401.
  • Short-circuit empty 204 No Content bodies in the GitHub client (collaborator check) instead of failing JSON.parse on the success path.
  • Update the existing preview comment on synchronize instead of posting duplicates; comment lookup matches the stored github_comment_id so updates work for both GitHub App and PAT identities.
  • Reclassify ask/explain/describe as heavy commands (they make LLM calls and cannot fit GitHub's ~10s webhook window) — only help remains light.

🔨 Refactoring & Chore

  • Remove the legacy GitHub Actions bot entirely: the src/ tree (handlers for ask/explain/describe/help/impact/review/scheduled, API, context, continuity, logging) and the zai-code-bot.yml, zai-agents-init-example.yml, and zai-agents-update.yml workflows.
  • Delete obsolete migration planning docs (plans/) now that the implementation exists under poc/workers/.
  • Add an OKF knowledge bundle (okf/) documenting the architecture, workflows (webhook ingress, command routing, PR pipelines, LLM execution, cron self-healing), state machines (job lifecycle, comment publication), business rules (retry budget, R2 retention, authorization), contracts, and the D1 schema.
  • Add Cloudflare and Agents SDK skill documentation under .agents/skills/.

⚙️ Infrastructure & Tooling

  • Rework CI: tests and security audit now run in poc/ with coverage from poc/coverage/lcov.info; the legacy build and dist-drift jobs are replaced by a workers dry-run deploy of both workers.
  • Port the Vitest + v8-coverage + miniflare test stack (~30 spec files running against the Workers runtime, coverage thresholds enforced; project coverage 96.91% per Codecov).
  • Add a Makefile single entry point for install, test, format, and deploy (heavy worker deploys first so the service binding resolves), plus Prettier configuration.
  • Centralize all worker secrets in one Cloudflare Secrets Store bound to both workers via [[secrets_store_secrets]], with per-worker .dev.vars.example templates for local dev.
  • Wire Cloudflare Workers Builds Git integration for deploys from this branch.

mistral-vibe and others added 30 commits August 6, 2026 17:55
…lare Computer

- Complete architecture for POC implementation
- Step-by-step implementation plan (5 phases, 10 days)
- Code templates for all components
- Testing and performance measurement strategy
- Success criteria and next steps

Closes: Migration planning for Cloudflare Computer

Co-authored-by: AndreiDrang <AndreiDrang@users.noreply.github.com>
- Minimal setup instructions
- Step-by-step deployment guide
- Troubleshooting section
- Performance measurement tips

Related to Cloudflare Computer migration

Co-authored-by: AndreiDrang <AndreiDrang@users.noreply.github.com>
- Add poc/ directory with full Cloudflare Worker implementation
- Add src/index.js - Main Worker with webhook handling
- Add src/lib/github.js - GitHub API client
- Add src/lib/commands.js - Command parsing utilities
- Add src/lib/handlers/help.js - Help command handler
- Add src/lib/logging.js - Logging utilities
- Add src/config/constants.js - Application constants
- Add tests/test.js - Unit tests for POC
- Add wrangler.toml - Cloudflare configuration
- Add package.json - Project dependencies
- Add README.md - Complete POC documentation

POC implements only /zai help command to validate Cloudflare Computer architecture.
Ready for deployment and performance testing.

Related to Cloudflare Computer migration plan

Co-authored-by: AndreiDrang <AndreiDrang@users.noreply.github.com>
- Complete summary of POC implementation
- Performance expectations and metrics
- Success criteria checklist
- Next steps for full migration
- All documentation ready for deployment

Final commit for cloudflare-migration branch setup

Co-authored-by: AndreiDrang <AndreiDrang@users.noreply.github.com>
Why:
* split the flat single-worker poc into a hybrid topology so heavy
  commands (review, impact) run in their own worker lifetime, decoupled
  from the ~10s GitHub webhook response limit
* add a shared library layer so both workers reuse one set of
  command/auth/crypto/github/logging modules

What:
* replace poc/src (flat) with poc/workers/{shared,zai-main-worker,zai-heavy-worker}
* main worker: webhook sig check -> parse -> auth -> route; runs light
  commands (help, describe) inline and delegates heavy commands to the
  heavy worker via a service binding (env.HEAVY_WORKER) under ctx.waitUntil
* heavy worker: internal-token gate -> 202 ack -> ctx.waitUntil dispatch
* shared/crypto.js: Web Crypto HMAC-SHA256 (no nodejs_compat flag needed)
* heavy worker sets workers_dev=false (binding-only, no public ingress);
  main worker gets a public route (zai-worker.tokenbel.info)
* migrate + expand tests to workers/tests/test.js (36 assertions)

Changes:
* poc/src/**, poc/wrangler.toml, poc/tests/test.js: removed (flat layout)
* poc/workers/shared/*.js: shared lib (6 modules)
* poc/workers/zai-main-worker/**: main worker (8 files)
* poc/workers/zai-heavy-worker/**: heavy worker (6 files)
* poc/workers/tests/test.js: migrated test suite
* poc/README.md: rewritten as hybrid architecture doc
* poc/package.json + package-lock.json: hybrid worker scripts + lockfile

Stats:
* 32 files changed, +1659/-1564 lines

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* give the hybrid workers a single entry point for deploy, dry-run, test
  and formatting so contributors don't memorize per-worker wrangler flags
* enforce a consistent JS style via Prettier

What:
* Makefile: targets for dependencies, test, refactor-js (install + format),
  format-check, deploy-dry-run (heavy first, then main so the service
  binding resolves), deploy, per-worker dev/tail, and clean
* .prettierrc.json: style matching the existing worker source
* .prettierignore: exclude node_modules/.wrangler/dist
* note: the format:js scripts + prettier devDep live in poc/package.json
  (committed with the restructure); this Makefile wires them up

Changes:
* poc/Makefile: added (tokenbel-wiki conventions: /bin/sh, .PHONY, tab recipes)
* poc/.prettierrc.json: added
* poc/.prettierignore: added

Stats:
* 3 files changed, +76 lines

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* per-worker `wrangler secret put` secrets don't scale across two workers and
  can't be shared; one Cloudflare Secrets Store centralizes all four values

What:
* bind GITHUB_WEBHOOK_SECRET, GITHUB_TOKEN, ZAI_INTERNAL_TOKEN, ZAI_API_KEY in
  zai-main-worker (4) and zai-heavy-worker (3, no webhook secret) from store
  629e5dd6594845a889e6ddabb26cc009 via [[secrets_store_secrets]]
* store bindings expose plain env.* strings, so delegator/crypto/handlers keep
  reading env.GITHUB_TOKEN etc. unchanged — zero code changes
* add per-worker .dev.vars.example for `wrangler dev`; gitignore the real
  .dev.vars plus node_modules, .wrangler, dist
* document the binding table, deploy webhook URL, and roadmap in the README

Changes:
* workers/zai-main-worker/wrangler.toml: 4 secrets_store_secrets bindings
* workers/zai-heavy-worker/wrangler.toml: 3 secrets_store_secrets bindings
* workers/zai-main-worker/.dev.vars.example: local-dev dotenv template (4 keys)
* workers/zai-heavy-worker/.dev.vars.example: local-dev dotenv template (3 keys)
* .gitignore: ignore .dev.vars, node_modules/, .wrangler/, dist/
* README.md: secrets-store table, deploy webhook URL, roadmap note

Stats:
* 6 files changed
* +114/-37 lines

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* Secrets Store bindings can arrive as string | {get()} | Promise at
  runtime; passing them straight to crypto/encode stringified to
  "[object Object]" and broke every webhook signature (401).
* GitHubClient parsed 204 No Content (collaborator check) as JSON,
  throwing "Unexpected end of JSON input" (500) on the success path.

What:
* Add resolveSecretValue() to normalize any binding shape to a trimmed
  string; resolve at every read site (webhook secret, GitHub token,
  internal token).
* GitHubClient.request() short-circuits empty bodies (204) to null and
  wraps JSON.parse to preserve the error.status contract.
* Tighten the heavy-worker internal-token gate to reject missing bindings.

Changes:
* shared/secrets.js: new resolveSecretValue() helper
* shared/auth.js: resolve GITHUB_TOKEN before constructing GitHubClient
* shared/github.js: empty-body + parse-error handling in request()
* zai-main-worker/src/index.js: resolve webhook secret + GitHub token
* zai-main-worker/src/delegator.js: resolve internal token inside send()
* zai-heavy-worker/src/index.js: resolve token gate + GitHub token
* README.md: document the binding-type ambiguity and resolution pattern
* tests/test.js: resolveSecretValue + 204 empty-body regression coverage

Stats:
* 8 files changed, +215/-17

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* Retire the @zai-bot mention and /zai-bot slash forms; "/zai" is now
  the single invocation prefix.
* Credit the author in the help footer.

What:
* COMMAND_REGEX accepts only "/zai"; drop MENTION_REGEX and its parse
  branch (capture groups shifted to type=match[1], args=match[2]).
* formatHelp(): remove @zai-bot usage notes; add an AndreiDrang credit
  linking to https://github.com/AndreiDrang.

Changes:
* shared/commands.js: /zai-only regex, mention branch removed, footer
* tests/test.js: /zai-bot + @zai-bot now rejected; footer link asserted

Stats:
* 2 files changed, +13/-21

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* the bot is migrating from GitHub Actions to Cloudflare Workers (poc/workers)
* the Action-based deployment workflow (uses: ./) is now obsolete

What:
* delete the legacy zai-code-bot.yml workflow (PR / issue_comment /
  pull_request_review_comment triggers are now handled by the Cloudflare
  Worker webhook)

Changes:
* .github/workflows/zai-code-bot.yml: removed

Stats:
* 1 file changed, -33 lines

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* port the Vitest + v8-coverage stack from tbel/cf_workers
  (tb-bcse-securities-collector) for Workers-runtime-fidelity testing
* the POC previously used a hand-rolled assert runner with no coverage gating

What:
* add vitest@^2.1.8, @vitest/coverage-v8@^2.1.8, vitest-environment-miniflare@^2.14.4
* wire npm test -> vitest run --coverage and add test:watch
* add vitest.config.js (globals, miniflare env via inline environmentOptions,
  80% thresholds on workers/shared + main-worker router)
* add make test / make test-watch targets
* ignore coverage/, .env, *.log, .DS_Store

Changes:
* package.json: devDeps + test/test:watch scripts
* package-lock.json: lockfile for new deps
* vitest.config.js: new config
* Makefile: test/test-watch targets + help
* .gitignore: coverage/, .env, *.log, .DS_Store

Stats:
* 5 files changed, +3019/-7 lines

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* replace the hand-rolled assert runner with a real Vitest suite mirroring
  tb-bcse-securities-collector (one .test.js per shared module)
* document the new test stack in the POC README

What:
* add 7 Vitest spec files using describe/it/expect + vi.spyOn(globalThis,'fetch')
  against the miniflare environment: auth, commands, crypto, github, logging,
  router, secrets
* delete the old monolithic workers/tests/test.js runner
* document the Vitest + v8 coverage stack in README (layout, dev, testing)

Changes:
* workers/tests/*.test.js (7 new): 99 tests, 100% stmts/lines/funcs, 97.9% branches
* workers/tests/test.js: removed (replaced by the Vitest suite)
* README.md: document the test stack

Stats:
* 9 files changed, +633/-261 lines

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* the cloudflare migration is now implemented under poc/workers/; these
  planning artifacts (architecture sketches, quick-starts, starter code,
  reports) are obsolete and risk misleading future readers

What:
* delete the entire plans/ directory (10 files)

Stats:
* 10 files deleted
* 6280 deletions

Co-Authored-By: Claude <noreply@anthropic.com>
Why:
* GitHub webhooks time out in ~10s, but PR analysis must run longer. PR
  events are now recorded durably in D1 and processed asynchronously via a
  Queue, so the main worker acks fast and the heavy worker runs to completion

What:
* D1 storage adapters: deliveries, jobs (lease-based claims), artifacts,
  config, keys, database helpers — D1 is the single source of truth
* migrations 0001 (schema) + 0002 (job leases, publication state machine)
* queue producer in main worker (job-enqueuer) + consumer in heavy worker
  (queue.js) with a 3-attempt retry budget; terminal failures ack without a DLQ
* PR preview handler: bounded stats fetch, R2 manifest + result artifacts,
  one live bot-owned comment via D1 publication lease, head-sha freshness check
* 5-min cron on main worker: recover expired leases, replay outbox, sweep
  expired storage (30-day retention)
* shared comments/pr-preview/pr-stats helpers + github.js getPullRequest/getPrFiles
* wrangler bindings: BOT_DB, BOT_ARTIFACTS (bot-storage), BOT_CACHE (bot-cache),
  BOT_JOBS (bot-jobs), R2_RETENTION_DAYS=30

Changes:
* shared/storage/{database,keys,artifacts,config,deliveries,jobs}.js: new adapters
* shared/{comments,pr-preview,pr-stats}.js: comment publication + rendering + stats
* shared/{constants,github}.js: markers/event types + PR REST methods
* main/src/{job-enqueuer,pr-events}.js: queue publisher + recovery, event extraction
* main/src/index.js: pull_request durable path + scheduled() cron sweep
* main/wrangler.toml: BOT_DB/ARTIFACTS/CACHE/JOBS bindings + cron trigger
* main/migrations/000{1,2}_*.sql: schema + hardening
* heavy/src/queue.js: claim -> run -> ack/retry/fail state machine
* heavy/handlers/pr-preview.js: durable preview job handler
* heavy/src/index.js + handlers/index.js + wrangler.toml: queue consumer wiring
* tests/{storage,storage-state,storage-runtime,github-storage,queue}.test.js

Stats:
* 27 files changed
* +2286/-12 lines

Co-Authored-By: Claude <noreply@anthropic.com>
Why:
* the readme described the pre-storage v0.2 layout and referenced now-removed
  planning docs; it did not document the queue, cron, or durable PR-preview flow

What:
* rewrite as v0.3: durable PR-preview path as the primary flow (mermaid)
* document the job lifecycle state machine and 3-attempt retry budget
* document the 5-min cron self-healing sweep (lease/outbox/retention)
* actualize the file tree, service bindings, and handler status table
* remove all plan-file references

Stats:
* 1 file changed
* +279/-160 lines

Co-Authored-By: Claude <noreply@anthropic.com>
Why:
* ask/explain/describe make Z.ai LLM calls, so they cannot run inline in the
  main worker within GitHub's ~10s webhook window — only help is truly light
  (pure formatting, no API call). The old split would block the webhook once
  real handlers landed

What:
* constants.js: LIGHT_COMMANDS=[help], HEAVY_COMMANDS=[ask,explain,describe,
  review,impact]
* move describe out of the main (light) handler registry; only help remains
* add ask/explain/describe heavy stub handlers (payload interface) in the
  heavy worker, matching the existing review/impact stub pattern
* mark ask/explain/describe as *(heavy)* in the help text
* update README file tree, routing examples, command-path sections, status

Changes:
* shared/constants.js: split arrays — help is the only light command
* shared/commands.js: help text heavy annotations
* zai-main-worker/src/handlers/index.js: drop describe case (help only)
* zai-main-worker/src/handlers/describe.js: deleted (orphaned light handler)
* zai-heavy-worker/src/handlers/{ask,explain,describe}.js: new heavy stubs
* zai-heavy-worker/src/handlers/index.js: register the 3 new heavy handlers
* README.md: reflect the corrected classification (v0.3.1)

Stats:
* 9 files changed
* +183/-72 lines

Co-Authored-By: Claude <noreply@anthropic.com>
Why:
* The poc/ workers implement a durable, multi-resource architecture (D1, R2,
  Queue, KV, two-worker split) whose business rules and contracts were only
  implicit in source code and the README. An OKF bundle makes the domain
  knowledge explicit, navigable, and maintainable.

What:
* Create a 22-file OKF bundle at repo root (okf/): root index (okf_version 0.1),
  update log, 6 directory indexes, and 14 concept documents across architecture,
  workflows, state, rules, contracts, and datasets.
* Wire the bundle into AGENTS.md via the canonical managed OKF guidance block.

Concepts documented:
* architecture: two-worker-split, storage-authority-model
* workflows: webhook-ingress, command-routing, pr-preview-pipeline,
  cron-self-healing
* state: job-lifecycle (bounded leases), comment-publication (one-live-comment)
* rules: retry-budget (3 attempts, no DLQ), r2-retention (30-day),
  authorization (collaborator gate)
* contracts: queue-message (job-id only), transactional-outbox
* datasets: d1-storage-schema (9 tables, migrations 0001+0002)

All source_paths verified to exist on disk; all internal cross-links resolve.

Changes:
* AGENTS.md: append managed OKF guidance block
* okf/index.md, okf/log.md: bundle root and changelog
* okf/architecture/{index,two-worker-split,storage-authority-model}.md
* okf/workflows/{index,webhook-ingress,command-routing,pr-preview-pipeline,cron-self-healing}.md
* okf/state/{index,job-lifecycle,comment-publication}.md
* okf/rules/{index,retry-budget,r2-retention,authorization}.md
* okf/contracts/{index,queue-message,transactional-outbox}.md
* okf/datasets/{index,d1-storage-schema}.md

Stats:
* 23 files changed
* +additions lines

Co-authored-by: Claude <noreply@anthropic.com>
Why:
* bot comments had inconsistent or missing attribution — help carried the
  full footer, pr-preview said "Powered by Z.ai", stubs and errors had none

What:
* add a shared BOT_FOOTER constant and apply it uniformly before each
  message's hidden marker so every bot comment ends with identical
  attribution

Changes:
* shared/constants.js: add BOT_FOOTER constant
* shared/commands.js: use BOT_FOOTER in formatHelp and formatCommandNotAvailable
* heavy handlers (ask/describe/explain/impact/review): add footer to stub notices
* heavy index.js: add footer to the heavy-command failure comment
* main help.js: add footer to the /zai help error comment
* main index.js: add footer to the unauthorized-command comment

Stats:
* 10 files changed, +29/-18

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* the auto-preview brief only needs PR identity; per-file stats are the
  job of the heavy /zai review pipeline, and computing them cost up to 30
  paginated getPrFiles calls per large PR

What:
* render a metadata-only comment (repo/pr/title/author/head) and adopt the
  shared footer; stop computing, rendering, or persisting per-file data

Changes:
* shared/pr-preview.js: rewrite renderPrPreview to metadata-only, use BOT_FOOTER
* shared/pr-stats.js: deleted (fetchPrStats + MAX_PR_FILES_API_LIMIT, no consumers)
* shared/storage/keys.js: retire unused prFilesArtifactKey
* heavy handlers/pr-preview.js: drop fetchPrStats call and the per-file R2 artifact
* tests/storage.test.js: assert metadata-only rendering, drop the pagination test

Stats:
* 5 files changed, +26/-139

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* lock in the dedup contract — a PR gets exactly one preview comment, and
  new commits (synchronize) update it instead of creating duplicates

What:
* exercise handlePrPreviewJob across opened, synchronize, and stale-skip
  paths and assert update-not-create via the shared comment marker and
  the presence of the footer on every published body

Changes:
* tests/pr-preview-sync.test.js: add regression suite (4 cases)

Stats:
* 1 file changed, +165

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* the bundle still described the old per-file stats preview and had no concept
  for the shared BOT_FOOTER applied to every bot comment

What:
* add a unified bot comment footer rule concept and rework the PR-preview
  pipeline plus cross-referenced concepts to the metadata-only brief; record
  the refresh in the log

Changes:
* rules/comment-footer.md: new Business Rule for the shared BOT_FOOTER
* workflows/pr-preview-pipeline.md: metadata-only steps, drop file-manifest + pr-stats source, fix failure step range
* contracts/queue-message.md: rendered results, not manifests
* rules/r2-retention.md: preview results, not file manifests
* architecture/storage-authority-model.md: R2 role is rendered preview results
* state/comment-publication.md: add Preview body section + footer link
* rules/index.md, index.md: register the footer concept
* log.md: record footer create + metadata-only updates

Stats:
* 9 files changed, +83/-17

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* the README still described the pr-stats / file-manifest flow and the old
  12-file/130-test suite, and did not mention the unified footer

What:
* update the file tree, the preview flow diagram, and the storage/GitHub tables
  to the metadata-only brief, and bump the doc version

Changes:
* README.md: drop pr-stats.js + GET /pulls/{n}/files; single result artifact in the mermaid; metadata-only tree descs; 13 files/133 tests; version 0.3.2

Stats:
* 1 file changed, +19/-21

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* the PR preview posted a NEW comment on every synchronize instead of
  updating the previous one

What:
* findMarkerComment now accepts the exact comment we previously published
  (matched by the stored github_comment_id) regardless of author type, so the
  update path resolves for both GitHub Apps and PAT-owned bots even when
  GITHUB_BOT_LOGIN is not configured; the Bot/login filter remains for the
  marker-only fallback
* add a real upsertComment regression suite (in-memory D1 fake, comments.js
  NOT mocked) covering PAT and App identity, fresh-create, and a
  stray-comment non-adoption guard

Changes:
* shared/comments.js: accept stored comment id in findMarkerComment filter
* tests/comments-upsert.test.js: real-path update-vs-create regression tests

Stats:
* 2 files changed, +284/-0

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* editing a PR title sent action=edited, which the preview gate did not accept,
  so the title change triggered no refresh

What:
* accept the pull_request "edited" action but gate it on changes.title, since
  the preview is metadata-only (repository/PR/title/author/head) and body/base
  edits don't affect it; this avoids wasteful re-renders on unrelated edits

Changes:
* zai-main-worker/src/pr-events.js: add "edited" to SUPPORTED_PR_ACTIONS and gate it on changes.title in isSupportedPullRequestEvent(event, action, payload)
* zai-main-worker/src/index.js: pass payload to the gate at the call site
* tests/pr-events.test.js: cover edited+title vs body/base/no-change, plus extractPullRequestEvent title extraction

Stats:
* 3 files changed, +124/-3

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* closing a PR sent action=closed, which the preview gate rejected, so nothing
  happened and the PR state in D1 never advanced to closed

What:
* accept pull_request "closed", persist state=closed + closed_by (the webhook
  sender) on pull_requests, and post an idempotent "PR closed by @x" lifecycle
  comment that skips the supersede guard and leaves the preview untouched

Changes:
* pr-events.js: gate "closed"; extractPullRequestEvent adds closedBy (sender)
* storage/deliveries.js: UPSERT closed_by via COALESCE; JOB_SELECT += p.state, p.closed_by
* shared/constants.js: PR_CLOSED_MARKER
* shared/pr-preview.js: renderPrClosed
* handlers/pr-preview.js: closed branch -> publishClosedComment (no getPullRequest)
* migrations/0003_pr_closed_by.sql: ALTER TABLE pull_requests ADD closed_by
* tests: closed gate + closedBy extraction, renderPrClosed, handler closed branch

Stats:
* 9 files changed, +277/-12

Co-authored-by: pi <noreply@earendil-works.com>
Why:
* the OKF bundle trailed the code: it still listed only opened/reopened/
  synchronize/ready_for_review and had no concept of the close branch or the
  new closed_by column

What:
* add the closed lifecycle to the preview pipeline, the pr_closed comment kind,
  and migration 0003 (closed_by) to the schema concept; log the change

Changes:
* workflows/pr-preview-pipeline.md: trigger actions += edited/closed; Closed lifecycle section
* state/comment-publication.md: comment kinds table (pr_preview, pr_closed)
* datasets/d1-storage-schema.md: migrations 0001-0003, pull_requests.closed_by, 0003 section
* log.md: 2026-08-07 entry for closed lifecycle + edited action

Stats:
* 4 files changed

Co-authored-by: pi <noreply@earendil-works.com>
@github-actions

Copy link
Copy Markdown

Error: No file path provided. Usage: /zai review

Why:
* progressive retry timeouts guaranteed that a slow reasoning response would
  be aborted even sooner after its first timeout, so review runs with
  already-retrieved evidence failed instead of finishing

What:
* give gathering requests a fixed 90-second timeout with at most two total
  attempts, bounded by the deadline before the 40-second finalization reserve
* enter a single 35-second no-tools final answer when a gathering request
  times out after at least one successful tool call (new llm_timeout
  finalization reason) instead of failing the run
* split the runner clock into gathering and hard deadlines so each stage gets
  its own request timeout, attempt limit, and retry policy
* honor Retry-After on retryable errors; direct calls keep progressive
  timeouts unchanged
* propagate httpStatus, providerRequestId, requestBytes, and retryAfterMs
  through per-attempt logs and failure records
* update the agent runner OKF contract, workflow, context-tools notes, and log

Changes:
* poc/workers/shared/agent/limits.js: raise maxLlmRequestDurationMs to 90s, add maxLlmAttempts and finalLlmRequestDurationMs
* poc/workers/shared/agent/runner.js: gathering/finalization deadlines, timeout-triggered finalization, stage-aware chat options
* poc/workers/shared/zai-client.js: fixed per-attempt timeout, maxAttempts/retryTimeouts, Retry-After parsing, provider request ids, request-byte telemetry
* poc/workers/shared/llm-command-runner.js: surface providerHttpStatus and providerRequestId on failures
* poc/workers/zai-heavy-worker/src/handlers/review.js: apply the new limits to the review profile
* poc/workers/tests/agent-runner.test.js: cover timeout-to-finalization flow and the limit snapshot
* poc/workers/tests/zai-client.test.js: cover long timeouts, timeout retry gating, and Retry-After attempts
* okf/contracts/agent-runner.md, okf/contracts/agent-context-tools.md, okf/workflows/llm-command-execution.md, okf/log.md: document the stage-aware budgets and telemetry

Stats:
* 11 files changed
* +359/-64 lines

Co-Authored-By: Claude <noreply@anthropic.com>
@AndreiDrang

Copy link
Copy Markdown
Owner Author

/zai review

@github-actions

Copy link
Copy Markdown

🤖 Reviewing /zai review...

@github-actions

Copy link
Copy Markdown

Error: No file path provided. Usage: /zai review

AndreiDrang and others added 9 commits August 24, 2026 00:03
Why:
* pi-lens reformatted the files touched by the review-fix series; leaving the
  drift uncommitted would mix formatting into the next change

What:
* prettier-style reflow only (argument wrapping, member-chain breaks)
* no behavior change — affected specs re-run green before committing

Changes:
* poc/workers/shared/llm-command-runner.js: wrap publishNotice/publishResult call arguments onto one line or indented blocks
* poc/workers/tests/handlers-review-llm.test.js: wrap objectContaining assertion fields
* poc/workers/tests/job-enqueuer.test.js: break the mockRejectedValueOnce chain
* poc/workers/tests/storage-state.test.js: break vi.fn() chains in the wait-out test

Stats:
* 4 files changed
* +51/-33 lines

Co-Authored-By: Claude <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 23, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
zai-main-worker d5e6700 Aug 23 2026, 10:20 PM

AndreiDrang and others added 13 commits August 24, 2026 00:24
Why:
* the shared branch threshold (83%) has been failing on pre-existing debt
  (llm-command-runner 60%, context-service 69%, zai-client 72% branches),
  so every npm test run exited red regardless of the change under review

What:
* both coverage globs now enforce a uniform 70% floor across lines,
  branches, functions, and statements
* gate is green again at current actuals (shared ~81% branches,
  main-worker ~94%) with ratchet milestones documented in-config

Changes:
* poc/vitest.config.js: thresholds 90/90/83/90 → 70/70/70/70 per glob, comment rewritten

Stats:
* 1 file changed
* +12/-10 lines

Co-Authored-By: Claude <noreply@anthropic.com>
…ailure notices

Why:
* llm-command-runner was the worst branch-coverage file in shared/ (60.5%)
  because only the agent-mode review handler exercised it

What:
* new dedicated spec drives runLlmCommand directly with the context service
  and agent runner mocked: non-agent completion, live-diff fallback and
  no-diff edges, stored PR-summary head matching, error-category fallbacks,
  agent failure shaping, rich/zeroed review metadata, and buildFailureNotice
  tails (max_retrieved_bytes, unknown categories, attempt counts)
* ratchet shared branch floor 70 -> 85 (actual 86.07%)

Stats:
* 22 new tests, 414 total green
* llm-command-runner: 64 -> 8 uncovered branches, 0 uncovered statements
* shared rollup: 80.90% -> 86.07% branches

Co-Authored-By: Claude <noreply@anthropic.com>
Why:
* context-service was 69% branches — only the happy paths reached it through
  the review handler's snapshot reads

What:
* new spec drives the real service against a fake R2 snapshot: missing/stale
  manifests, path-prefix validation and truncation, diff result states
  (not_found, unavailable reasons, artifact_missing, truncation), getFile
  guards (invalid path/revision, source unconfigured/incomplete, not-found,
  hard and soft limits, null content), file ranges, description/commits/
  comments clamps, combined-diff omission rules, and snapshot-slice
  metadata fallbacks

Stats:
* 45 new tests, 450 total green
* context-service: 40 -> ~14 uncovered branches, 52 -> ~3 statements
* shared rollup: 86.07% -> 88.21% branches

Co-Authored-By: Claude <noreply@anthropic.com>
…arsing

Why:
* zai-client sat at 72% branches — timeout aborts, protocol failures, and
  deadline math were reachable only through integration paths

What:
* transport edges: network-failure attempt reports, unreadable error bodies,
  missing assistant messages, tool-call-only responses via call(), the
  defensive negative-maxRetries tail, global fetch/default sleeper defaults
* chat deadlines: real AbortController timeout via fake timers, maxAttempts
  clamp, past-deadline result, retry-that-cannot-fit timeout, plain-500 retry
* parsing: provider request-id header variants, garbage usage normalization,
  numeric/HTTP-date/negative/garbage retry-after values, sanitizer fallback

Stats:
* 13 new tests, 465 total green
* zai-client: 33 -> 7 uncovered branches
* shared rollup: 88.21% -> 90.48% branches
* ratchet both coverage globs to 90/90/90/90

Co-Authored-By: Claude <noreply@anthropic.com>
Why:
* agent/runner (82%) and llm-context (85%) held protocol-validation and
  renderer branches reachable only through crafted model responses

What:
* new agent-runner spec with a scripted chat mock: construction guards,
  non-array messages, non-object responses, errorless failures, onAttempt
  accounting, malformed assistant messages and tool calls, per-turn budget
  results, default tool arguments, safe error mapping, normalized and
  raw-key duplicate detection, and all finalization paths (retrieval
  budget, llm timeout, finalization-time tool calls, hard deadline)
* llm-context defensive edges: missing slices, whitespace diffs,
  layout-disabled sections, degenerate commits, anonymous/unlocated
  comments, statusless files, conversation fallback topic

Stats:
* 26 new tests, 469 total green
* agent/runner: 25 -> 16 uncovered branches; llm-context: 18 -> 5
* shared rollup: 90.48% -> 92.34% branches

Co-Authored-By: Claude <noreply@anthropic.com>
Why:
* the storage/github/misc sweep was the last concentration of uncovered
  branches after the runner, context, and client paydowns

What:
* storage: list-limit clamps and empty results, outbox publish without a
  producer binding, delivery id validation, UNIQUE-race winner rethrow and
  reconciliation, unreadable created jobs, command-job delivery ownership,
  kind-less delivery lookup, non-string PR heads, defensive changedRows/
  requireBinding/run shapes
* github: non-array pagination pages, null PR bodies, maxComments clamping,
  non-array comment slices, ref-less file content
* prompts: describe/pr-summary no-context fallback notes
* ratchet: shared floor 90 -> 93 branches / 95 others (actual 94.7% branches)

Stats:
* 26 new tests, 507 total green
* shared rollup: 92.87% -> 94.70% branches

Co-Authored-By: Claude <noreply@anthropic.com>
Why:
* the vitest text table scrolls past the fold on large suites, so a run's
  headline numbers (% Stmts / % Branch / % Funcs / % Lines) were easy to miss
  and had to be recomputed by hand for reporting

What:
* new poc/scripts/coverage-averages.js summarizes the istanbul JSON the suite
  already emits: per-column means across instrumented files, overall and per
  threshold glob (shared / main worker), with the file count behind each mean
  and n/a for metrics a file set cannot measure
* make test now pipes npm test into that summarizer, so every run ends with
  the averages; a failing suite still aborts before any summary is printed

Co-Authored-By: Claude <noreply@anthropic.com>
Pure prettier reflow of the specs added during the coverage paydown (long
expect() lines rewrapped to the 100-column limit); no behavior change. Tests
rerun green on the reformatted tree.

Co-Authored-By: Claude <noreply@anthropic.com>
- git mv poc/workers -> src (relative imports unchanged)
- package.json/vitest.config.js/Makefile/prettier/scripts -> repo root
- rewire npm scripts, vitest include/threshold globs, Makefile, and CI
  (no more working-directory: poc; codecov path now ./coverage/lcov.info)
- src/AGENTS.md + per-worker AGENTS.md path updates
- drop POC wording from code comments; fold poc/.gitignore into root
Root README absorbs the workers README: command-flow sequence diagram,
PR-context/pr_summary chain with the R2 v2/prs/ layout, bindings detail,
prompt regeneration, and the src/ tree. The src/ copy is removed.
- ARCHITECTURE.md: drop POC framing and leftover-dir note, rewrite all
  code locations, code map, evidence paths, and the docs-strategy list
  for the merged README and src/AGENTS.md
- AGENTS.md: new repository tree, important-files table, context routing,
  validation from the repo root
- CONTRIBUTING.md: root-level workflow; README+ARCHITECTURE doc rule
- rewrite source_paths and body references across all 19 concepts
  (poc/workers/... -> src/..., poc/README.md -> README.md)
- retitle bundle index away from 'Workers POC'
- append 2026-08-24 migration entry to okf/log.md; historical entries
  preserved verbatim
- validated: frontmatter, types, source-path existence, bundle-relative
  links, index/directory parity, duplicate resources — all pass
Why:
* the markdown formatter normalized tables, quotes, and comment-block
  spacing in files touched by the src/ migration

What:
* pad table columns to aligned widths in AGENTS.md and the okf
  two-worker-split concept
* add blank lines around the okf-knowledge marker block in AGENTS.md
* switch okf_version frontmatter quotes from double to single

Changes:
* AGENTS.md: aligned important-files table, spaced marker block
* okf/index.md: single-quote okf_version value
* okf/architecture/two-worker-split.md: aligned workers table

Stats:
* 3 files changed
* +18/-16 lines

Co-Authored-By: Claude <noreply@anthropic.com>
@AndreiDrang
AndreiDrang merged commit e188a0a into main Aug 23, 2026
4 of 8 checks passed
@AndreiDrang
AndreiDrang deleted the cloudflare-migration branch August 23, 2026 22:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants