Files
kandev/docs/plans/pr-walkthrough/plan.md
T
2026-08-22 21:46:57 +01:00

262 lines
12 KiB
Markdown

---
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.