feat: add PR walkthrough generation workflow (#2933)

This commit is contained in:
Carlos Florêncio
2026-08-22 21:46:57 +01:00
committed by GitHub
parent 906be89036
commit eb0df0a492
31 changed files with 4852 additions and 34 deletions
@@ -0,0 +1,65 @@
# ADR-2026-08-22-agent-owned-pr-walkthrough-rendering: Keep PR walkthrough rendering agent owned and provider neutral
**Status:** accepted
**Date:** 2026-08-22
**Area:** workflow, infra, security
## Context
PR walkthrough generation starts with OpenCode, but the selected agent runner
may change later. The agent must be able to correct invalid walkthrough data
after seeing renderer errors, while the secret-bearing `pull_request_target`
workflow must not grant a model broad write access or permission to execute
pull-request-owned code.
## Decision
The provider-neutral `.agents/skills/pr-walkthrough/SKILL.md` owns the complete
generation procedure. The selected agent creates the walkthrough data, invokes
the trusted renderer, inspects any validation error, and revises the data until
both the JSON and HTML outputs exist.
Each managed agent runner supplies the narrowest adapters needed to perform
that procedure. The OpenCode runner exposes base-commit-controlled
`render_pr_walkthrough` and `read_pr_file` custom tools. The renderer tool
accepts walkthrough JSON, binds authoritative pull-request identity from the
GitHub event, removes model-owned link overrides, and runs only the trusted
renderer against fixed output paths. The file tool reads only bounded regular
UTF-8 blobs from the immutable pull request head Git object. It never checks
the untrusted head out in the secret-bearing worktree. The runner does not
grant generic edit, shell, or external-directory access. A future Codex or
other runner must preserve these boundaries, follow the same skill, and
produce the same artifacts.
The generated artifact is one HTML file, not a fully offline bundle. Its fixed
shell loads Tailwind, Mermaid, Marked, DOMPurify, Shiki, and fonts from
exact-version or commit-pinned HTTPS URLs. Those providers are an explicit
runtime trust dependency. Pull request data cannot choose or override these
asset URLs.
Provider installation, credentials, and invocation remain workflow adapter
concerns. They must not be embedded in the walkthrough skill or renderer
contract.
## Consequences
- The agent can repair malformed data during its own turn instead of returning
invalid JSON for a later workflow step to reject.
- Replacing OpenCode changes the runner adapter and workflow wiring, not the
skill, JSON schema, renderer, object key, or hosting contract.
- The OpenCode adapter adds a small trusted custom tool, but avoids broad model
access to shell execution or repository writes.
- Reviewers need network access to the trusted external asset hosts when they
open a walkthrough; R2 stores no separate runtime asset bundle.
- Workflow verification checks the agent-produced files rather than rebuilding
HTML from the agent's final response.
## Alternatives Considered
- **Render after the agent exits:** simpler workflow plumbing, but the agent
cannot see renderer failures and repair its data.
- **Allow generic edit and shell tools:** lets the agent run the renderer
directly, but also permits source mutation and execution of untrusted PR
content in a secret-bearing workflow.
- **Make the skill OpenCode-specific:** avoids an adapter abstraction, but
couples the durable walkthrough instructions to the first selected provider.
@@ -0,0 +1,56 @@
# ADR-2026-08-22-pr-walkthrough-description-link: Own a top-level PR walkthrough callout
**Status:** accepted
**Date:** 2026-08-22
**Area:** workflow, infra, security, GitHub
## Context
The hosted walkthrough must be easy to discover from the pull request after
publication and after merge. Pull request bodies have no universal heading
structure, and contributors or other automations may edit them throughout the
pull request lifecycle. Updating an arbitrary section would either make the
link hard to find or risk replacing content the walkthrough workflow does not
own.
## Decision
After R2 publication and public validation succeed, a separate workflow job
places a marker-delimited walkthrough callout at the beginning of the pull
request body. The callout uses a GitHub alert and a prominent link to the
stable SHA-keyed walkthrough URL. A label rerun may replace the bytes at that
URL for the same head SHA.
The updater owns only the content between its start and end markers. A rerun
replaces that block in place, so the current generated walkthrough is linked
without duplicating content. If no owned block exists, the updater prepends
one and preserves the existing body below it. Malformed, unbalanced, or
duplicate marker blocks fail closed instead of rewriting the description.
The GitHub write runs only after the publish job succeeds. It receives the
minimum `pull-requests: write` permission, checks out only the immutable base
commit for its trusted helper, validates the event PR number and head SHA
against the URL, and receives no model or R2 credentials.
## Consequences
- The walkthrough is the first content reviewers see, independent of the
contributor's chosen headings.
- Label-triggered regeneration updates one stable callout rather than adding
comments or duplicate body sections.
- Contributor-authored and third-party body content remains outside the
walkthrough workflow's ownership boundary.
- A successful upload can still be left without a body link if the GitHub
update fails; the workflow reports that failure and the hosted object remains
available from the publication summary.
## Alternatives Considered
- **Insert under an existing heading:** preserves the opening paragraph, but
depends on a heading that many pull requests do not have and is less visible.
- **Append at the bottom:** avoids changing the opening, but existing generated
sections and preview blocks can make the walkthrough difficult to find.
- **Post a pull request comment:** leaves the description untouched, but the
durable entry point is easily buried by review activity.
- **Rewrite the full body from a template:** provides total layout control, but
would overwrite content owned by contributors and other automations.
@@ -0,0 +1,65 @@
# ADR-2026-08-22-pr-walkthrough-r2-hosting: Host PR walkthrough HTML in Cloudflare R2
**Status:** accepted
**Date:** 2026-08-22
**Area:** infra, workflow, security
## Context
The PR walkthrough workflow generates a single deployable HTML file that should
remain available after a pull request merges. The file is generated once when
the PR is opened, with an explicit label-triggered rerun, so the hosting layer
needs durable object storage rather than a short-lived CI artifact. The initial
retention target is several months, with future support for generating a new
object on every push if the model cost is acceptable.
## Decision
Publish walkthrough HTML files to a dedicated Cloudflare R2 bucket named
`kandev-pr-walkthroughs`, exposed through the custom domain
`walkthrough.kandev.ai`. Store objects under
`pr/<pull-request-number>/<head-sha>.html` and publish only the HTML, not the
JSON generation artifact.
The GitHub Actions publication job uses a bucket-scoped R2 S3-compatible
Object Read & Write credential. The generation job receives no R2 credential;
it hands its trusted-renderer output to the publication job through a GitHub
Actions artifact. The publication job validates both the stored object and
its public HTTPS URL, then writes the URL to the workflow summary without
receiving GitHub write permission. A separate post-publication job may consume
the validated URL under the ownership rules in the PR description-link ADR.
The bucket uses a lifecycle rule for the `pr/` prefix. The initial rule
expires objects 180 days after upload so a walkthrough normally survives the
period before its PR merges. Exact retention measured from merge time is
deferred to a later merge-promotion design.
## Consequences
- Walkthrough URLs survive PR merge and GitHub artifact retention.
- R2 lifecycle cleanup is automatic and applies to all current and future
SHA-keyed walkthrough objects.
- Label reruns can replace the object bytes for the same PR head without
changing its stable, commit-derived URL. The URL identifies the snapshot
scope, but it is not a content-addressed immutable object.
- Per-push generation can be enabled later by adding `synchronize`; each head
SHA will receive its own object and lifecycle cleanup will remove old ones.
- The public bucket must contain only sanitized walkthrough HTML and must be
served from a dedicated subdomain rather than the main app origin. A page may
load externally hosted scripts, styles, and fonts through exact-version URLs
controlled by the trusted shell. These third-party hosts are an explicit
runtime trust dependency. R2 still stores one HTML object per walkthrough
and no separately deployed asset bundle.
- GitHub Actions needs two R2 secrets in addition to non-secret bucket,
endpoint, and base-URL variables.
## Alternatives Considered
- **Cloudflare Pages:** It can serve the HTML and is already used elsewhere in
the repository, but its deployment history requires explicit cleanup rather
than a simple per-object lifecycle rule.
- **Sprites preview environment:** It can serve static files, but it couples
retention and access to the preview environment instead of providing a
dedicated artifact store.
- **GitHub Actions artifacts:** They are useful as the generation-to-upload
handoff, but they are not the durable public URL and retention mechanism.
+3
View File
@@ -191,3 +191,6 @@ Read individual ADRs for full context. Create new ones via `/record decision` or
| 2026-08-20-acp-client-non-underscore-extension-methods | [Route Non-Underscore Inbound Client Methods to the Extension Handler](2026-08-20-acp-client-non-underscore-extension-methods.md) | accepted | backend, protocol | 2026-08-20 |
| 2026-08-22-persistent-fork-approval-labels | [Persist Fork Approval Labels Across Pushes](2026-08-22-persistent-fork-approval-labels.md) | accepted | infra, workflow, security | 2026-08-22 |
| 2026-08-22-plugin-version-retention | [Keep Exactly One Superseded Plugin Version](2026-08-22-plugin-version-retention.md) | accepted | backend | 2026-08-22 |
| 2026-08-22-pr-walkthrough-r2-hosting | [Host PR walkthrough HTML in Cloudflare R2](2026-08-22-pr-walkthrough-r2-hosting.md) | accepted | infra, workflow, security | 2026-08-22 |
| 2026-08-22-agent-owned-pr-walkthrough-rendering | [Keep PR walkthrough rendering agent owned and provider neutral](2026-08-22-agent-owned-pr-walkthrough-rendering.md) | accepted | workflow, infra, security | 2026-08-22 |
| 2026-08-22-pr-walkthrough-description-link | [Own a top-level PR walkthrough callout](2026-08-22-pr-walkthrough-description-link.md) | accepted | workflow, infra, security, GitHub | 2026-08-22 |
+261
View File
@@ -0,0 +1,261 @@
---
spec: docs/specs/pr-walkthrough/spec.md
created: 2026-08-22
status: implementing
---
# Implementation Plan: Pull Request Walkthrough Generation
## Overview
Adapt the upstream `pr-walkthrough` skill into Kandev's `.agents/skills`
layout and retain its standard-library renderer and focused tests. Keep the
skill provider-neutral and make the selected agent own the complete
data-render-repair loop. A dedicated OpenCode walkthrough workflow supplies a
narrow trusted rendering tool, verifies the agent-built outputs, and hands
them to a separate R2 publication job. The publication job uploads only the
HTML to the preconfigured `kandev-pr-walkthroughs` bucket. A final trusted job
places the validated custom-domain URL in a marker-owned callout at the top of
the pull request description.
The workflow must not execute PR-owned code or use PR-owned renderer assets.
The agent reads the PR as untrusted source material and has no generic edit,
shell, network, subagent, commit, push, or GitHub-write capability. Its managed
rendering adapter may write only the fixed walkthrough outputs and run only the
trusted renderer.
---
## Repository harness
### `pr-walkthrough` skill and renderer
Add the provider-neutral skill under
`.agents/skills/pr-walkthrough/`. Adapt the upstream skill's default/base
branch instructions to Kandev and require the selected agent to own the full
data-render-repair loop. Keep the renderer and its fixed HTML shell in
`references/`, with the example data and standard-library unit tests. The
renderer remains independent of Kandev application code.
### Trusted rendering adapter
Add a small standard-library helper that accepts walkthrough JSON from a
managed agent tool, binds trusted pull-request identity, removes model-owned
URL overrides, writes the fixed JSON path, and invokes the trusted renderer.
The OpenCode-specific tool is a thin adapter around this helper. Other agent
runners follow the same skill and output contract through their native tools
or an equivalent narrow adapter.
---
## CI workflow
### Dedicated OpenCode generation job
Add `.github/workflows/pr-walkthrough.yml` as a dedicated workflow using the
`pull_request_target` event family. Gate it with the independent
`PR_WALKTHROUGH_ENABLED` variable, not `OPENCODE_REVIEW_ENABLED`, and keep it
decoupled from the code-review App token. The job runs for `opened`,
`reopened`, and `ready_for_review`, plus a label-triggered rerun only when the
label is `generate-pr-walkthrough`. The workflow does not subscribe to
`synchronize` in this increment.
Use the shared base-controlled setup action for OpenCode. Select
`opencode-go/muse-spark-1.2-contributor#high`. The pinned OpenCode 1.17.7 model
catalog declares reasoning support and built-in `high` and `xhigh` variants for
this model, so no custom provider override is needed.
The job will:
1. Check out `github.event.pull_request.head.sha` with full history and no
persisted credentials.
2. Compute the base-to-head context from the event SHAs.
3. Materialize the skill, renderer, shell, and managed rendering adapter from
the trusted base SHA, never from the PR head.
4. Run OpenCode with an inline agent definition that permits reads and the
narrow `render_pr_walkthrough` tool only.
5. Require OpenCode to iterate through renderer errors until it produces
`docs/pr-walkthrough/pr-<number>.json` and `.html` itself.
6. Require non-empty JSON and HTML files and preserve OpenCode logs and the
generated files as an uploaded CI artifact, including on failure.
The job keeps R2 credentials out of generation. The artifact is the only
handoff to the publication job. Add `docs/pr-walkthrough/` to `.gitignore`.
### Cloudflare R2 publication job
Add a dependent publication job that runs only after successful generation.
The job must not check out or execute PR-owned files. It downloads the HTML
artifact, uploads only the HTML through the R2 S3-compatible API, and validates
the public object.
The publication contract is:
- Bucket variable: `CLOUDFLARE_R2_BUCKET`, expected value
`kandev-pr-walkthroughs`.
- Endpoint variable: `CLOUDFLARE_R2_ENDPOINT`, normally
`https://<account-id>.r2.cloudflarestorage.com` for the default bucket
jurisdiction.
- Base URL variable: `CLOUDFLARE_R2_BASE_URL`, expected value
`https://walkthrough.kandev.ai`.
- Access-key secret: `CLOUDFLARE_R2_ACCESS_KEY_ID`.
- Secret-key secret: `CLOUDFLARE_R2_SECRET_ACCESS_KEY`.
- Existing account variable: `CLOUDFLARE_ACCOUNT_ID` remains available for
endpoint construction and is not a secret.
- Object key: `pr/<pull-request-number>/<head-sha>.html`.
- Object metadata: `Content-Type: text/html; charset=utf-8` and a short cache
lifetime that does not outlive lifecycle deletion or same-head reruns.
Use an S3-compatible upload command such as `aws s3 cp` with the endpoint,
`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_DEFAULT_REGION=auto`
environment variables. Do not pass the bucket-scoped R2 Object Read & Write
credentials to `cloudflare/wrangler-action`; those credentials are for the R2
S3-compatible API. The existing Pages `CLOUDFLARE_API_TOKEN` remains separate.
After upload, run an object metadata check and a bounded-retry public HTTPS GET
against the reported URL. Export the validated URL for the dependent link job
and write it to the workflow job summary. Do not upload the JSON artifact to
the public bucket. The publication job itself has no GitHub write permission.
### Pull request description link job
Add a final job after successful publication. Give only this job
`pull-requests: write`; do not give it model or R2 credentials. Check out the
exact base SHA without persisted credentials and run a trusted
standard-library helper that validates the GitHub PR number/head SHA and exact
hosted URL. The helper allows the live PR head to advance while publication
finishes because the SHA-keyed walkthrough remains the intended initial
snapshot.
The helper prepends a GitHub alert between `kandev-pr-walkthrough-start` and
`kandev-pr-walkthrough-end` markers. On a rerun it replaces only that leading
block. It fails on unbalanced, duplicate, or non-leading markers instead of
rewriting content it does not own.
The Cloudflare setup is an operator prerequisite rather than repository
automation: the bucket is `kandev-pr-walkthroughs`, the custom domain is
`walkthrough.kandev.ai`, and the bucket has an Object Lifecycle rule for the
`pr/` prefix. The initial plan assumes a 180-day expiration from upload so
walkthroughs normally survive the time before merge. Exact post-merge
retention would require a later merge-promotion job.
### Workflow contract coverage
Add `.github/scripts/pr-walkthrough-workflow-contract_test.py` and wire it into
`.github/workflows/lint-action-pinning.yml`. The contract test will assert the
event gates, exact base checkout, immutable head-object fetch, constrained
PR-head file reads, narrow OpenCode permissions, trusted metadata binding,
generation output paths,
artifact handoff, R2 publication command, required secret/variable names,
public URL validation, isolated PR-body write permissions, trusted helper use,
and the independently gated dedicated workflow boundary.
---
## Tests
- **What:** Renderer accepts valid walkthrough data, escapes source, converts
patches, validates edges and risk data, fills the HTML shell, and rejects
malformed fields.
**File:** `.agents/skills/pr-walkthrough/references/test_build.py`
**How:** Run the upstream-adapted Python standard-library unit suite.
- **What:** The managed renderer binds trusted PR metadata, removes model URL
overrides, produces both fixed outputs, and leaves no partial files after a
renderer failure.
**File:** `scripts/pr-walkthrough-render.test.py`
**How:** Run the Python standard-library unit suite.
- **What:** The workflow keeps PR content read-only and uses trusted base
assets while producing ignored JSON/HTML artifacts and publishing only the
rendered HTML to R2.
**File:** `.github/scripts/pr-walkthrough-workflow-contract_test.py`
**How:** Run the static workflow contract test.
- **What:** The publication job uses the intended bucket, endpoint, object key,
content type, lifecycle-compatible cache policy, and public URL check without
exposing R2 credentials to generation.
**File:** `.github/scripts/pr-walkthrough-workflow-contract_test.py`
**How:** Run the static workflow contract test and a configured CI dry run.
- **What:** The PR body helper prepends one prominent callout, updates it
idempotently, validates event identity, preserves contributor content, and
rejects malformed ownership markers.
**File:** `scripts/pr-walkthrough-pr-body.test.py`
**How:** Run the Python standard-library unit suite.
- **What:** New skill and reference files satisfy Kandev harness limits.
**File:** `.agents/skills/pr-walkthrough/SKILL.md` and `references/*`
**How:** Run the harness linter on the new skill directory.
- **What:** All workflow action refs remain immutable and the workflow edit has
no whitespace errors.
**File:** `.github/workflows/pr-walkthrough.yml` and
`.github/workflows/opencode-code-review.yml`
**How:** Run the action-pinning tests/linter and `git diff --check`.
---
## E2E Tests
There is no Kandev UI flow in this increment. The generated HTML is validated
structurally by the renderer and workflow checks, and the publication job
performs a live public GET against the R2 custom domain. Full browser
interaction testing remains out of scope.
---
## Verification Results
Completed 2026-08-22:
- `python3 scripts/pr-walkthrough-render.test.py`: 2 tests passed.
- `cd .agents/skills/pr-walkthrough/references && python3 -m unittest test_build`: 56 tests passed.
- `python3 .github/scripts/pr-walkthrough-workflow-contract_test.py`: 18 tests passed.
- `python3 scripts/pr-walkthrough-pr-body.test.py`: 8 tests passed.
- `python3 scripts/lint-harness-files.test.py`: 19 tests passed.
- `python3 .github/scripts/lint-harness-files.py .agents/skills/pr-walkthrough`: passed.
- `python3 .github/scripts/lint-action-pinning_test.py`: 9 tests passed.
- `python3 .github/scripts/lint-action-pinning.py`: 19 workflows passed.
- `actionlint` passed for the dedicated walkthrough, OpenCode review, and
action-pinning workflows.
- Pinned OpenCode 1.17.7 resolved the default-deny walkthrough agent with only
read, glob, grep, and the trusted renderer enabled. Its refreshed model
catalog reports Muse Spark 1.2 Contributor reasoning support and native
`high` and `xhigh` variants.
- The OpenCode custom tool bundled successfully with Bun after marking the
runtime-provided `@opencode-ai/plugin` import external.
- `git diff --check`: passed.
- The example JSON rendered a 1,161-line HTML file with no unreplaced tokens.
- `zizmor` was run through `mise x zizmor@1.25.2`; the repository-wide audit
remains non-zero because of existing findings in unrelated workflows, and
both targeted base-controlled workflows retain the expected
`pull_request_target` dangerous-trigger finding.
- Chromium is not installed, so the optional headless DOM check was not run.
- Operator provisioning is complete. A live R2 upload and public GET remain
pending the first eligible PR workflow run.
---
## Implementation Waves And Parallel Candidates
Wave 1:
- [x] [task-01-walkthrough-skill-renderer](task-01-walkthrough-skill-renderer.md)
- [ ] [task-02-agent-rendering-contract](task-02-agent-rendering-contract.md)
Wave 2:
- [x] [task-03-workflow-artifact-generation](task-03-workflow-artifact-generation.md)
Wave 3:
- [x] [task-04-r2-html-publication](task-04-r2-html-publication.md)
Wave 4:
- [x] [task-05-dedicated-workflow-pr-link](task-05-dedicated-workflow-pr-link.md)
Execute sequentially in the primary conversation unless the user explicitly
authorizes parallel implementation.
## Open Questions
- Whether to enable `synchronize` generation later remains a product/cost
decision. The SHA-keyed R2 object contract already supports it.
- Whether retention must be exactly 90 days after merge remains deferred. The
initial lifecycle is 180 days after upload.
@@ -0,0 +1,56 @@
---
id: "01-walkthrough-skill-renderer"
title: "Walkthrough skill renderer"
status: done
wave: 1
depends_on: []
plan: "plan.md"
spec: "../../specs/pr-walkthrough/spec.md"
---
# Task 01: Walkthrough skill renderer
Adapt the final upstream `pr-walkthrough` skill into Kandev's `.agents/skills`
layout. Keep the JSON-driven renderer, fixed HTML shell, example data, and
standard-library tests. Add the CI output-mode instructions without changing
the skill's boundary from explanation to code review.
- **Acceptance:** The skill documents both normal generation and the exact CI
JSON output contract, with Kandev-compatible paths and no hosting steps.
- **Acceptance:** The renderer produces a non-empty HTML file from the example
JSON and rejects malformed required fields, invalid edges, invalid risk data,
and unreplaced template tokens.
- **Acceptance:** Code and agent-controlled Markdown are escaped or sanitized
by the generated page before insertion into HTML.
- **Verification:**
```text
cd .agents/skills/pr-walkthrough/references && python3 -m unittest test_build
python3 .github/scripts/lint-harness-files.py .agents/skills/pr-walkthrough
git diff --check
```
- **Files likely touched:**
`.agents/skills/pr-walkthrough/SKILL.md`,
`.agents/skills/pr-walkthrough/references/build.py`,
`.agents/skills/pr-walkthrough/references/example.json`,
`.agents/skills/pr-walkthrough/references/shell.html`,
`.agents/skills/pr-walkthrough/references/test_build.py`.
- **Dependencies:** None.
- **Parallelism:** sequential, because the workflow consumes this skill.
- **Inputs:** The spec's generation contract, permissions, failure modes, and
scenarios; the final upstream PR walkthrough skill and renderer.
- **Output contract:** Report changed files, unit/lint results, generated sample
artifact path, and any renderer or dependency risks. Mark the task done after
implementation and verification.
## Results
Implemented the adapted upstream skill, fixed renderer shell, example JSON,
and standard-library tests under `.agents/skills/pr-walkthrough/`. Added the
exact CI marker-block output mode, kept hosting outside the skill, and aligned
the shell with the landing site's Figtree, Geist Mono, indigo, dark-surface,
compact-radius, and mobile-navigation language.
Verification: 56 renderer tests passed; the example rendered a non-empty
1,161-line HTML file; harness lint passed for the new skill directory.
@@ -0,0 +1,46 @@
---
id: "02-agent-rendering-contract"
title: "Provider-neutral agent rendering contract"
status: in_progress
wave: 1
depends_on: ["01-walkthrough-skill-renderer"]
plan: "plan.md"
spec: "../../specs/pr-walkthrough/spec.md"
---
# Task 02: Provider-neutral agent rendering contract
Make the walkthrough agent responsible for the complete JSON, rendering, and
repair loop without coupling the skill to OpenCode. Supply a narrow managed
adapter when a CI runner cannot safely receive generic write or shell access.
- **Acceptance:** The provider-neutral skill requires the selected agent to
render its walkthrough, inspect renderer failures, correct the data, and
finish only after both JSON and HTML outputs exist.
- **Acceptance:** The managed rendering helper binds PR number, title, URL,
repository, base branch, and head branch from trusted host metadata. It
removes model-supplied PR/file URL overrides before invoking the trusted
renderer.
- **Acceptance:** The OpenCode adapter accepts only complete walkthrough JSON,
invokes only the fixed trusted helper, and returns renderer errors so the
agent can retry. OpenCode retains denied generic edit and Bash permissions.
- **Verification:**
```text
python3 scripts/pr-walkthrough-render.test.py
bun build .github/scripts/opencode-pr-walkthrough-tool.ts --external @opencode-ai/plugin --target=bun --outfile /tmp/kandev-opencode-pr-walkthrough-tool.js
```
- **Files likely touched:** `.agents/skills/pr-walkthrough/SKILL.md`,
`scripts/pr-walkthrough-render`, `scripts/pr-walkthrough-render.test.py`,
`.github/scripts/opencode-pr-walkthrough-tool.ts`.
- **Dependencies:** Task 01.
- **Parallelism:** sequential because the workflow task consumes this adapter.
## Results
Added the trusted metadata-binding renderer helper and the OpenCode custom tool
adapter. The helper writes only the fixed PR JSON/HTML paths, strips model URL
overrides, and leaves no published outputs after renderer validation fails.
The provider-neutral skill wording remains pending because `.agents/**` is
read-only in the implementing session.
@@ -0,0 +1,86 @@
---
id: "03-workflow-artifact-generation"
title: "Workflow artifact generation"
status: done
wave: 2
depends_on: ["01-walkthrough-skill-renderer", "02-agent-rendering-contract"]
plan: "plan.md"
spec: "../../specs/pr-walkthrough/spec.md"
---
# Task 03: Workflow artifact generation
Add a dedicated PR walkthrough workflow with a generation job. The job checks
out the immutable base commit, fetches the PR head only as a Git object, uses
base-commit copies of the skill and helpers, gives OpenCode narrow trusted
rendering and PR-file tools, and uploads the agent-generated files and logs for
the separate R2 publication job.
- **Acceptance:** A non-draft authorized pull request event invokes OpenCode,
creates distinct `docs/pr-walkthrough/pr-<number>.json` and `.html` files,
and uploads them as an artifact. The generation job runs on `opened`,
`reopened`, `ready_for_review`, and the `generate-pr-walkthrough` label, but
not on `synchronize`.
- **Acceptance:** The job cannot execute PR-owned scripts through OpenCode or
modify source. OpenCode may invoke only the base-controlled rendering
adapter, which writes the fixed walkthrough outputs. The job does not request
`id-token`, hosting credentials, R2 credentials, or GitHub write permissions.
- **Acceptance:** A contract test covers the trigger, immutable base checkout,
head-object fetch, constrained PR-file reads, trusted base assets, read-only
agent permissions, output paths, artifact upload, and generation-only
boundary. The artifact handoff is explicit for the dependent R2 publication
job.
- **Acceptance:** The workflow uses `PR_WALKTHROUGH_ENABLED`, the
`opencode-go/muse-spark-1.2-contributor#high` model reference and the model's
native high-reasoning variant. The normal review workflow remains
independently gated by `OPENCODE_REVIEW_ENABLED`.
- **Verification:**
```text
python3 .github/scripts/pr-walkthrough-workflow-contract_test.py
python3 .github/scripts/lint-action-pinning_test.py
python3 .github/scripts/lint-action-pinning.py
zizmor .github/workflows/pr-walkthrough.yml
git diff --check
```
- **Files likely touched:** `.github/workflows/pr-walkthrough.yml`,
`.github/workflows/lint-action-pinning.yml`,
`.github/scripts/pr-walkthrough-workflow-contract_test.py`, `.gitignore`.
- **Dependencies:** Tasks 01 and 02.
- **Parallelism:** sequential, because it edits the shared OpenCode workflow
and consumes both earlier artifacts.
- **Inputs:** The spec's permissions, failure modes, persistence guarantees,
and scenarios; tasks 01 and 02; current OpenCode workflow gates and action
pinning conventions.
- **Output contract:** Report changed files, contract/security check results,
artifact paths from any local dry run, and any unavailable external CI
dependency. Mark the task done after implementation and verification. This
task does not publish to R2; task 04 owns that boundary.
## Results
Added the isolated same-repository OpenCode generation job to the dedicated
`pull_request_target` walkthrough workflow. It materializes the skill,
renderer, shell,
and narrow rendering adapter from the base SHA. OpenCode builds and repairs the
JSON/HTML through that adapter while generic edit and Bash remain denied. The
job preserves logs and outputs as a uniquely keyed artifact, including on
failure.
The three OpenCode jobs now share a base-SHA-materialized setup action with one
version and checksum. The walkthrough generation worktree stays on the trusted
base commit. A bounded helper reads regular UTF-8 files from the immutable PR
head Git object without exposing PR-controlled filesystem entries. Walkthrough
artifacts use a visible staging directory and fail when empty. Workflow-level
per-PR concurrency serializes generation, publication, and linking, and the
walkthrough label no longer runs the normal code-review job.
The walkthrough has its own enable variable and uses the Muse Spark contributor
model with its native high-reasoning variant. The pinned OpenCode 1.17.7 CLI
catalog reports reasoning support plus built-in `high` and `xhigh` variants,
and the CLI resolved the default-deny agent permissions.
Verification: workflow contract tests passed; action pinning tests and linter
passed; `actionlint` passed; `git diff --check` passed. The targeted `zizmor`
audit still reports the workflow's existing `pull_request_target` warning.
@@ -0,0 +1,81 @@
---
id: "04-r2-html-publication"
title: "Publish walkthrough HTML to Cloudflare R2"
status: done
wave: 3
depends_on: ["03-workflow-artifact-generation"]
plan: "plan.md"
spec: "../../specs/pr-walkthrough/spec.md"
---
# Task 04: Publish walkthrough HTML to Cloudflare R2
Add the dependent publication job that downloads the successful generation
artifact and uploads only the rendered HTML to the already-configured
Cloudflare R2 bucket. Keep the R2 credentials isolated to this job and use the
custom domain as the externally visible URL.
- **Acceptance:** The publication job runs only after successful walkthrough
generation and only for an authorized, non-draft same-repository pull
request. A label-triggered rerun publishes the current head without enabling
`synchronize` generation.
- **Acceptance:** The job downloads the generation artifact but does not check
out or execute PR-owned files and does not expose R2 credentials to the
generation job.
- **Acceptance:** The job uploads only the HTML file through the R2
S3-compatible API using a bucket-scoped Object Read & Write token. It does
not use the existing Pages `CLOUDFLARE_API_TOKEN`.
- **Acceptance:** The object key is
`pr/<pull-request-number>/<head-sha>.html`, with HTML content type and a
short cache policy suitable for label-triggered reruns.
- **Acceptance:** The job verifies the object with an R2 metadata request and
verifies the public URL
`https://walkthrough.kandev.ai/pr/<pull-request-number>/<head-sha>.html`
with an HTTPS GET before reporting publication success.
- **Acceptance:** The job writes the public URL to the workflow job summary
and exports it to the dependent trusted link job, but receives no GitHub
write permission and does not publish the JSON artifact.
- **Acceptance:** The workflow contract test covers the dependency, event
gate, artifact download, S3 endpoint, bucket/key construction, secret and
variable names, content metadata, public URL check, and the absence of
generation-job credentials.
- **Verification:**
```text
python3 .github/scripts/pr-walkthrough-workflow-contract_test.py
python3 .github/scripts/lint-action-pinning_test.py
python3 .github/scripts/lint-action-pinning.py
zizmor .github/workflows/pr-walkthrough.yml
git diff --check
```
- **Files likely touched:** `.github/workflows/pr-walkthrough.yml`,
`.github/scripts/pr-walkthrough-workflow-contract_test.py`, and possibly
`.github/workflows/lint-action-pinning.yml` if the contract test wiring is
added in this task.
- **Dependencies:** Task 03 and the operator-provisioned R2 bucket,
`walkthrough.kandev.ai` custom domain, `pr/` lifecycle rule, GitHub Actions
secrets, and GitHub Actions variables.
- **Parallelism:** sequential, because this job consumes task 03's artifact and
edits the shared OpenCode workflow.
- **Inputs:** The spec's R2 publication contract and retention semantics; the
existing Cloudflare Pages workflow's pinned-action conventions; the
bucket-scoped R2 S3 credentials configured in GitHub.
- **Output contract:** Report changed workflow and contract-test files,
security/static-check results, the generated object URL from any configured
CI dry run, and any unavailable external Cloudflare dependency. Keep the
task pending until implementation begins.
## Results
Added the dependent artifact-only publication job. It uploads only the HTML
file to the configured R2 S3 endpoint using the bucket-scoped credentials,
checks object metadata, performs a bounded-retry public HTTPS GET and byte
comparison, and writes and exports the custom URL without receiving GitHub
write permission.
Verification: workflow contract tests passed; the publication job has no
checkout step, and R2 secrets are scoped only to the upload step. Operator
provisioning is complete; a live upload remains pending the first eligible PR
workflow run.
@@ -0,0 +1,54 @@
---
id: "05-dedicated-workflow-pr-link"
title: "Separate walkthrough workflow and link it from the PR body"
status: done
wave: 4
depends_on: ["04-r2-html-publication"]
plan: "plan.md"
spec: "../../specs/pr-walkthrough/spec.md"
---
# Task 05: Separate walkthrough workflow and link it from the PR body
Move walkthrough generation and publication out of the OpenCode review
workflow. Give walkthroughs their own enable variable and model configuration,
then add a post-publication job that makes the hosted page prominent without
taking ownership of contributor-authored PR content.
- **Acceptance:** `.github/workflows/pr-walkthrough.yml` contains generation,
R2 publication, and PR linking. `.github/workflows/opencode-code-review.yml`
contains no walkthrough jobs.
- **Acceptance:** `PR_WALKTHROUGH_ENABLED` controls the walkthrough workflow
independently of `OPENCODE_REVIEW_ENABLED`.
- **Acceptance:** The walkthrough runner selects
`opencode-go/muse-spark-1.2-contributor#high`, which selects the model's
native high-reasoning variant in pinned OpenCode 1.17.7.
- **Acceptance:** Only the final job receives `pull-requests: write`. It runs
after public R2 validation, uses the trusted base-commit helper, and receives
no OpenCode or R2 credential.
- **Acceptance:** The helper places one marker-owned GitHub alert at the top of
the PR description, replaces it idempotently on rerun, preserves all other
content, and fails closed on invalid markers or mismatched PR identity.
- **Verification:**
```text
python3 .github/scripts/pr-walkthrough-workflow-contract_test.py
python3 scripts/pr-walkthrough-pr-body.test.py
actionlint .github/workflows/pr-walkthrough.yml
zizmor .github/workflows/pr-walkthrough.yml
git diff --check
```
- **Files likely touched:** `.github/workflows/pr-walkthrough.yml`,
`.github/workflows/opencode-code-review.yml`,
`.github/scripts/pr-walkthrough-workflow-contract_test.py`,
`scripts/pr-walkthrough-pr-body`, and
`scripts/pr-walkthrough-pr-body.test.py`.
## Results
Created the dedicated workflow and independent gate. Selected Muse Spark's
native high-reasoning variant and validated it in the pinned 1.17.7 model
catalog. Added the marker-owned PR-body helper and a
minimum-permission link job after R2 public validation. Contract and helper
tests pass.
+1
View File
@@ -321,6 +321,7 @@ System pages (Radarr/Sonarr-style) for status, disk usage, database maintenance,
| [executor-profile-env-precedence](executor-profile-env-precedence/spec.md) | building |
| [automations-yaml-export](automations-yaml-export/spec.md) | building |
| [task-launch-failure-recovery](task-launch-failure-recovery/spec.md) | draft |
| [pr-walkthrough](pr-walkthrough/spec.md) | building |
---
+186
View File
@@ -0,0 +1,186 @@
---
status: building
created: 2026-08-22
owner: kandev
---
# Pull Request Walkthrough Generation
## Why
Reviewers can inspect a diff in GitHub, but a large pull request still takes
time to understand. Kandev should generate a visual explanation that gives a
reviewer the change context, architecture, important code paths, risk, and
review focus before they read the full diff.
This increment generates the walkthrough HTML in CI and publishes the HTML to
the dedicated Cloudflare R2 walkthrough bucket. The hosted file remains
available after the pull request merges and expires through the bucket
lifecycle policy.
**Decisions:**
[ADR-2026-08-22-pr-walkthrough-r2-hosting](../../decisions/2026-08-22-pr-walkthrough-r2-hosting.md),
[ADR-2026-08-22-agent-owned-pr-walkthrough-rendering](../../decisions/2026-08-22-agent-owned-pr-walkthrough-rendering.md),
[ADR-2026-08-22-pr-walkthrough-description-link](../../decisions/2026-08-22-pr-walkthrough-description-link.md)
## What
- A project skill named `pr-walkthrough` is available to compatible agents.
- The skill produces one JSON data file and one HTML file for a pull request:
`docs/pr-walkthrough/pr-<number>.json` and
`docs/pr-walkthrough/pr-<number>.html`.
- The JSON describes the pull request, why it exists, an optional architecture
diagram, key code changes, data or storage, risk, trade-offs, and review
focus.
- The renderer builds the HTML from the JSON and fixed renderer assets. It
escapes code and prose, validates required fields, validates node edges, and
rejects unreplaced template tokens.
- The generated page contains a vertical reviewer story, a dark and light
theme, architecture and data diagrams when supplied, highlighted code, diff
tinting, GitHub file links, an interactive code canvas, and a linear fallback
list.
- The configured workflow agent generates and renders the walkthrough for a
non-draft same-repository pull request when it is opened, reopened, or marked
ready for review. OpenCode is the initial runner, but the skill and artifact
contract do not depend on it. A maintainer can explicitly retrigger
generation by adding the `generate-pr-walkthrough` label.
- Walkthrough automation lives in `.github/workflows/pr-walkthrough.yml` and
is enabled independently with the `PR_WALKTHROUGH_ENABLED` repository
variable. It does not share the `OPENCODE_REVIEW_ENABLED` code-review gate.
- The initial runner uses
`opencode-go/muse-spark-1.2-contributor#high`. OpenCode 1.17.7 reports native
reasoning support for the model and resolves `#high` to its built-in high
reasoning variant.
- The workflow preserves the generated JSON and HTML as CI artifacts and
uploads only the HTML to the `kandev-pr-walkthroughs` R2 bucket.
- Each published object uses the key
`pr/<pull-request-number>/<head-sha>.html` and is served at
`https://walkthrough.kandev.ai/pr/<pull-request-number>/<head-sha>.html`.
- The initial workflow does not regenerate on `synchronize`. Future
per-push generation can add that event without changing the object contract.
- After public validation succeeds, a separate minimum-permission job prepends
a prominent marker-owned walkthrough callout to the pull request
description. A rerun replaces only that callout and preserves the rest of
the description.
## Generation contract
The walkthrough agent compares the exact pull request head SHA with the exact
base SHA. It creates the JSON, invokes the trusted renderer, and corrects its
data until both outputs pass renderer validation. The generated `pr` object
includes the pull request number, title, URL, repository slug, base branch,
head branch, and diff statistics when they are available. The managed runner
binds identity and links to trusted event metadata before rendering.
Each code change includes a real repository-relative file path, a concise
explanation, and at least one real code or rendered-Markdown block. Code
excerpts come from the pull request head or its diff. The agent does not invent
source code or present a review verdict.
The walkthrough is an explanation, not a code review. It does not approve,
request changes, post findings, or claim that the pull request is safe to
merge.
## Permissions
- The workflow may read pull request metadata and repository contents.
- The selected agent reads the trusted base checkout and attached diff. A
narrow base-controlled tool may read bounded regular UTF-8 files directly
from the immutable pull request head Git object. The agent may not run
arbitrary shell commands, read outside the trusted worktree, modify source files,
invoke subagents, fetch external URLs, commit, push, or publish GitHub
changes. A managed runner may expose a narrow trusted-renderer tool that
writes only the fixed walkthrough JSON and HTML paths.
- The workflow checks out only the trusted base commit in the secret-bearing
worktree. It fetches the event head SHA as a Git object without checking it
out, and uses base-commit copies of the walkthrough skill, renderer, managed
rendering adapter, and constrained file reader. Pull request changes cannot
replace those instructions or executable generation components.
- The agent invokes the fixed renderer before it finishes. The workflow only
verifies and packages the ignored walkthrough output directory.
- The R2 publishing job receives only the bucket-scoped S3-compatible R2
credentials required to upload the rendered HTML. The generation job does
not receive R2 credentials.
- The PR-link job receives `pull-requests: write`, but no model or R2
credential. It checks out the immutable base commit and uses a trusted helper
that validates the PR number, event head SHA, and exact walkthrough URL
before constructing the GitHub API update.
- The public bucket contains only generated walkthrough HTML. It does not
publish the JSON source artifact.
## Failure modes
- If the selected agent command fails, exits non-zero, or does not produce both
required walkthrough files, generation fails and the workflow records the
diagnostic output in its CI artifacts.
- If the JSON is missing required fields, contains invalid edges or risk data,
includes a reserved renderer token, or otherwise violates the renderer
contract, the renderer fails and no HTML is treated as generated.
- If the renderer cannot read its fixed shell or cannot write the output file,
the workflow fails rather than exposing a partial page.
- If optional browser validation is unavailable on the runner, the workflow
still validates the generated file structurally and reports live browser
rendering as unverified. HTML generation itself remains a required check.
- If the R2 upload, object metadata validation, or bounded-retry public URL
check fails, the workflow fails and does not report the walkthrough as
published.
- If the PR body contains malformed, duplicate, or non-leading walkthrough
markers, the link job fails closed and does not rewrite contributor content.
## Persistence guarantees
Generated JSON and HTML are ignored working-tree artifacts. They do not merge
into `main` or become Kandev application state. The HTML is also published to
R2 independently of GitHub artifact retention, so it remains available after
the pull request merges. The initial lifecycle deletes objects under `pr/`
after 180 days from upload; this is intentionally measured from generation,
not from merge time.
## Scenarios
- **GIVEN** a non-draft same-repository pull request is opened, reopened, or
marked ready for review, **WHEN** the walkthrough job runs, **THEN** it
creates non-empty JSON and HTML artifacts and publishes the HTML under the
current head SHA in R2.
- **GIVEN** a maintainer adds the `generate-pr-walkthrough` label, **WHEN** the
label-triggered walkthrough job runs, **THEN** it regenerates the current PR
head and updates the corresponding R2 object and job-summary URL.
- **GIVEN** a pull request receives a new head commit, **WHEN** the
walkthrough workflow runs for the synchronize event, **THEN** no
walkthrough generation occurs in the initial increment.
- **GIVEN** two pull requests use different numbers, **WHEN** both jobs run,
**THEN** each output filename and R2 object key is distinct and neither run
overwrites the other's result.
- **GIVEN** two walkthrough triggers target the same pull request, **WHEN** the
newer workflow starts, **THEN** per-PR workflow concurrency cancels the older
pipeline so publication and PR linking cannot race across runs.
- **GIVEN** the agent submits malformed JSON or a review verdict instead of the
walkthrough contract, **WHEN** the managed renderer rejects it, **THEN** the
agent must correct the data; the job fails without publishing if no valid
output is produced.
- **GIVEN** a generated code excerpt contains HTML characters or a Mermaid
label contains unsafe markup, **WHEN** the page is rendered, **THEN** the
source is escaped or sanitized and no executable PR-controlled markup is
inserted by the renderer.
- **GIVEN** a draft pull request or an unauthorized fork event, **WHEN** the
workflow is triggered, **THEN** no walkthrough agent job runs.
- **GIVEN** a successfully generated HTML file, **WHEN** the publishing job
uploads it, **THEN** a public GET to the reported URL returns the same HTML
with an HTML content type.
- **GIVEN** a validated public walkthrough URL, **WHEN** the link job runs,
**THEN** the first block in the PR description is a prominent walkthrough
callout and all existing contributor content remains below it.
- **GIVEN** the walkthrough label regenerates the same or a newer PR head,
**WHEN** the link job runs again, **THEN** it replaces the owned callout
without duplicating markers or changing the remaining description.
## Out of scope
- Publishing walkthrough screenshots or using the screenshot media branch.
- Generating a new walkthrough on every push. The initial `synchronize` path
remains disabled to control model usage.
- Retaining a walkthrough for an exact period measured from merge time. The
initial lifecycle is measured from upload time.
- Adding a Kandev UI page for externally generated PR walkthroughs.
- Replacing Kandev's existing in-app `changes-walkthrough` behavior.
- Adding a merge-approval or automated code-review verdict to the page.