Skip to content

Commit 5aa3749

Browse files
chore: sync docs/release-docs-staging with develop
2 parents fe147be + 990423b commit 5aa3749

4 files changed

Lines changed: 205 additions & 42 deletions

File tree

.github/workflows/cloud-docs-automation.yml

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,11 @@ jobs:
9292
import { readFileSync, appendFileSync } from "fs"
9393
const a = JSON.parse(readFileSync("/tmp/analysis.json", "utf8"))
9494
const prefix = "IMPORTANT: Only make file changes using the Write and Edit tools. Never use git commands to stage, commit, or push files.\n\nIMPORTANT: To load the writing-docs skill, use the Skill tool directly with skill='writing-docs'. Do NOT spawn a sub-agent or use the Task tool to load it.\n\n"
95+
const suffix = "\n\n---\n\nIMPORTANT — final step: after applying all documentation changes, write these two files using the Write tool:\n\n1. /tmp/pr-title.txt — a single-line pull request title. It MUST start with \"docs: \", followed by a concise summary of what was changed, and MUST end with \" [automated]\". Keep it under 100 characters. Example: \"docs: document new environment variables page [automated]\"\n2. /tmp/pr-summary.md — a short summary (2-4 sentences, or a few bullet points) of what was updated. Plain markdown, no top-level heading.\n\nIf you made no documentation changes, do not create either file.\n"
9596
const delimiter = "CLAUDE_PROMPT_EOF"
9697
appendFileSync(
9798
process.env.GITHUB_OUTPUT,
98-
`CLAUDE_PROMPT<<${delimiter}\n${prefix}${a.claudePrompt}\n${delimiter}\n`
99+
`CLAUDE_PROMPT<<${delimiter}\n${prefix}${a.claudePrompt}${suffix}\n${delimiter}\n`
99100
)
100101
EOF
101102
@@ -134,21 +135,35 @@ jobs:
134135
echo "No documentation changes found."
135136
fi
136137
137-
- name: Build PR body
138+
- name: Build PR metadata
138139
id: pr-body
139140
if: steps.check.outputs.needs_docs == 'true' && steps.changes.outputs.has_changes == 'true'
140141
run: |
141142
node --input-type=module << 'EOF'
142-
import { readFileSync, appendFileSync } from "fs"
143+
import { existsSync, readFileSync, appendFileSync } from "fs"
143144
144145
const a = JSON.parse(readFileSync("/tmp/analysis.json", "utf8"))
145146
const flagged = Array.isArray(a.featureFlaggedFeatures) ? a.featureFlaggedFeatures : []
146147
148+
// Title generated by Claude, with a fallback if it is missing or malformed
149+
let title = existsSync("/tmp/pr-title.txt")
150+
? readFileSync("/tmp/pr-title.txt", "utf8").split("\n")[0].trim()
151+
: ""
152+
if (!title.startsWith("docs: ") || !title.endsWith("[automated]")) {
153+
title = "docs: update cloud documentation [automated]"
154+
}
155+
156+
const summary = existsSync("/tmp/pr-summary.md")
157+
? readFileSync("/tmp/pr-summary.md", "utf8").trim()
158+
: ""
159+
147160
const lines = [
148161
"## Automated Cloud Documentation Updates",
149162
"",
150163
"This PR contains automated documentation changes triggered by Cloud production deployments.",
151164
"",
165+
summary || "_No summary was generated._",
166+
"",
152167
"> Review carefully before merging. Claude may have missed context or made incorrect assumptions.",
153168
]
154169
@@ -170,6 +185,7 @@ jobs:
170185
171186
const body = lines.join("\n")
172187
const delimiter = "PR_BODY_EOF"
188+
appendFileSync(process.env.GITHUB_OUTPUT, `TITLE=${title}\n`)
173189
appendFileSync(process.env.GITHUB_OUTPUT, `BODY<<${delimiter}\n${body}\n${delimiter}\n`)
174190
EOF
175191
@@ -182,8 +198,8 @@ jobs:
182198
base: develop
183199
branch: docs/cloud-docs
184200
branch-suffix: timestamp
185-
title: "chore(docs): cloud doc changes (automated)"
186-
labels: "type: chore"
201+
title: ${{ steps.pr-body.outputs.TITLE }}
202+
labels: "type: docs"
187203
add-paths: |
188204
www/apps/cloud/app
189205
www/apps/cloud/generated

.github/workflows/docs-automation.yml

Lines changed: 123 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -101,18 +101,100 @@ jobs:
101101
echo "Created new branch docs/release-docs-staging"
102102
fi
103103
104+
- name: Fetch existing PR body
105+
id: existing-pr
106+
if: steps.check.outputs.needs_docs == 'true'
107+
env:
108+
GH_TOKEN: ${{ secrets.REFERENCE_PAT }}
109+
REPO: ${{ github.repository }}
110+
run: |
111+
# The docs/release-docs-staging branch is long-lived: its PR is reused
112+
# until the next release. Claude rewrites this body in place, so a
113+
# failed lookup fails the step rather than losing the existing entries.
114+
REPO_OWNER="${REPO%%/*}"
115+
PR_NUMBER=$(gh api "repos/$REPO/pulls?head=${REPO_OWNER}:docs/release-docs-staging&state=open" --jq '.[0].number')
116+
if [ "$PR_NUMBER" = "null" ]; then
117+
PR_NUMBER=""
118+
fi
119+
120+
if [ -n "$PR_NUMBER" ]; then
121+
gh api "repos/$REPO/pulls/$PR_NUMBER" --jq '.body // ""' > /tmp/existing-pr-body.md
122+
echo "Fetched body of PR #$PR_NUMBER"
123+
else
124+
: > /tmp/existing-pr-body.md
125+
echo "No open PR for docs/release-docs-staging yet"
126+
fi
127+
echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
128+
104129
- name: Extract Claude prompt
105130
id: extract-prompt
106131
if: steps.check.outputs.needs_docs == 'true'
132+
env:
133+
COMMIT_SHA: ${{ inputs.commit_sha || github.sha }}
134+
REPO: ${{ github.repository }}
107135
run: |
108136
node --input-type=module << 'EOF'
109137
import { readFileSync, appendFileSync } from "fs"
110138
const a = JSON.parse(readFileSync("/tmp/analysis.json", "utf8"))
139+
const { COMMIT_SHA: sha, REPO: repo } = process.env
140+
const date = new Date().toISOString().split("T")[0]
141+
const projectList = a.affectedProjects
142+
.map((p) => `- **${p.project}**: ${p.reason}`)
143+
.join("\n")
144+
111145
const prefix = "IMPORTANT: Only make file changes using the Write and Edit tools. Never use git commands to stage, commit, or push files.\n\nIMPORTANT: To load the writing-docs skill, use the Skill tool directly with skill='writing-docs'. Do NOT spawn a sub-agent or use the Task tool to load it.\n\n"
146+
147+
// Claude owns the whole PR description so its format stays consistent
148+
// across runs instead of being appended to by this workflow.
149+
const suffix = [
150+
"",
151+
"",
152+
"---",
153+
"",
154+
"IMPORTANT — final step: update the pull request description.",
155+
"",
156+
"These documentation changes land on a long-lived pull request that is reused for every commit until the next release. Read the current description from /tmp/existing-pr-body.md with the Read tool (the file is empty if the pull request does not exist yet), then write the FULL updated description to /tmp/pr-body.md with the Write tool.",
157+
"",
158+
"Rules for the updated description:",
159+
"- Keep the existing structure and formatting exactly as-is. Do not reformat, reorder, or rewrite entries that are already there.",
160+
"- Never remove, merge, or shorten existing entries. The description only grows.",
161+
"- Add exactly one new entry for this commit, after the existing entries, matching the format the existing entries already use.",
162+
"- Your new entry must summarize the documentation changes you just made in 2-4 sentences or a few bullet points.",
163+
"- Write the file even if the description ends up unchanged in structure.",
164+
"",
165+
"Facts for this commit's entry:",
166+
`- Commit: [\`${sha.slice(0, 7)}\`](https://github.com/${repo}/commit/${sha})`,
167+
`- Date: ${date}`,
168+
"- Affected projects:",
169+
projectList.replace(/^/gm, " "),
170+
"",
171+
"If /tmp/existing-pr-body.md is empty, create the description from scratch with exactly this structure:",
172+
"",
173+
"```markdown",
174+
"## Automated Documentation Updates",
175+
"",
176+
"This PR accumulates automated documentation changes for the next release.",
177+
"Each entry below corresponds to a merged commit that triggered updates.",
178+
"",
179+
"> Review carefully before merging. Claude may have missed context",
180+
"> or made incorrect assumptions.",
181+
"",
182+
"---",
183+
"",
184+
`### [\`${sha.slice(0, 7)}\`](https://github.com/${repo}/commit/${sha}) — ${date}`,
185+
"",
186+
"<your 2-4 sentence summary of what was updated>",
187+
"",
188+
"**Affected projects:**",
189+
projectList,
190+
"```",
191+
"",
192+
].join("\n")
193+
112194
const delimiter = "CLAUDE_PROMPT_EOF"
113195
appendFileSync(
114196
process.env.GITHUB_OUTPUT,
115-
`CLAUDE_PROMPT<<${delimiter}\n${prefix}${a.claudePrompt}\n${delimiter}\n`
197+
`CLAUDE_PROMPT<<${delimiter}\n${prefix}${a.claudePrompt}${suffix}\n${delimiter}\n`
116198
)
117199
EOF
118200
@@ -187,43 +269,52 @@ jobs:
187269
if: steps.check.outputs.needs_docs == 'true' && steps.changes.outputs.has_changes == 'true'
188270
run: git push origin docs/release-docs-staging
189271

190-
- name: Create or update Pull Request
272+
- name: Resolve PR body
191273
if: steps.check.outputs.needs_docs == 'true' && steps.changes.outputs.has_changes == 'true'
192274
env:
193-
GH_TOKEN: ${{ secrets.REFERENCE_PAT }}
194275
COMMIT_SHA: ${{ inputs.commit_sha || github.sha }}
195276
REPO: ${{ github.repository }}
196277
run: |
197278
node --input-type=module << 'EOF'
198-
import { readFileSync, writeFileSync } from "fs"
199-
import { execSync } from "child_process"
279+
import { existsSync, readFileSync, writeFileSync } from "fs"
200280
201281
const a = JSON.parse(readFileSync("/tmp/analysis.json", "utf8"))
202282
const { COMMIT_SHA: sha, REPO: repo } = process.env
203283
const date = new Date().toISOString().split("T")[0]
204284
285+
const existingBody = readFileSync("/tmp/existing-pr-body.md", "utf8").trim()
286+
const claudeBody = existsSync("/tmp/pr-body.md")
287+
? readFileSync("/tmp/pr-body.md", "utf8").trim()
288+
: ""
289+
290+
// Claude owns the body. Fall back to a deterministic append only if it
291+
// wrote nothing, or if it dropped content that was already there.
292+
const droppedContent =
293+
existingBody.length > 0 && claudeBody.length < existingBody.length
294+
if (claudeBody && !droppedContent) {
295+
writeFileSync("/tmp/pr-body.md", claudeBody)
296+
process.exit(0)
297+
}
298+
299+
console.log(
300+
claudeBody
301+
? "Claude's PR body is shorter than the existing one; falling back."
302+
: "Claude wrote no PR body; falling back."
303+
)
304+
205305
const projectList = a.affectedProjects
206306
.map((p) => `- **${p.project}**: ${p.reason}`)
207307
.join("\n")
208308
209-
const commitUrl = `https://github.com/${repo}/commit/${sha}`
210309
const newEntry = [
211-
`### [\`${sha.slice(0, 7)}\`](${commitUrl}) — ${date}`,
310+
`### [\`${sha.slice(0, 7)}\`](https://github.com/${repo}/commit/${sha}) — ${date}`,
311+
"",
312+
"_No summary was generated._",
212313
"",
213314
"**Affected projects:**",
214315
projectList,
215316
].join("\n")
216317
217-
let existingBody = ""
218-
try {
219-
const owner = repo.split("/")[0]
220-
const out = execSync(
221-
`gh api "repos/${repo}/pulls?head=${owner}:docs/release-docs-staging&state=open" --jq '.[0].body'`,
222-
{ encoding: "utf8" }
223-
).trim()
224-
if (out !== "null" && out !== "") existingBody = out
225-
} catch { /* PR does not exist yet */ }
226-
227318
const header = [
228319
"## Automated Documentation Updates",
229320
"",
@@ -234,29 +325,33 @@ jobs:
234325
"> or made incorrect assumptions.",
235326
].join("\n")
236327
237-
const body = existingBody
238-
? `${existingBody}\n\n---\n\n${newEntry}`
239-
: `${header}\n\n---\n\n${newEntry}`
240-
241-
writeFileSync("/tmp/pr-body.md", body)
328+
const base = existingBody || header
329+
writeFileSync("/tmp/pr-body.md", `${base}\n\n---\n\n${newEntry}`)
242330
EOF
243331
244-
REPO_OWNER="${REPO%%/*}"
245-
PR_NUMBER=$(gh api "repos/$REPO/pulls?head=${REPO_OWNER}:docs/release-docs-staging&state=open" --jq '.[0].number' 2>/dev/null || echo "")
246-
if [ -n "$PR_NUMBER" ] && [ "$PR_NUMBER" != "null" ]; then
332+
- name: Create or update Pull Request
333+
if: steps.check.outputs.needs_docs == 'true' && steps.changes.outputs.has_changes == 'true'
334+
env:
335+
GH_TOKEN: ${{ secrets.REFERENCE_PAT }}
336+
REPO: ${{ github.repository }}
337+
PR_NUMBER: ${{ steps.existing-pr.outputs.number }}
338+
# The branch is long-lived, so the title stays static
339+
PR_TITLE: "docs: doc changes for next release [automated]"
340+
run: |
341+
if [ -n "$PR_NUMBER" ]; then
247342
gh api "repos/$REPO/pulls/$PR_NUMBER" \
248343
--method PATCH \
249-
--field title="chore(docs): doc changes for next release (automated)" \
344+
--field title="$PR_TITLE" \
250345
--field body="$(cat /tmp/pr-body.md)"
251346
else
252347
PR_NUMBER=$(gh api "repos/$REPO/pulls" \
253348
--method POST \
254-
--field title="chore(docs): doc changes for next release (automated)" \
349+
--field title="$PR_TITLE" \
255350
--field body="$(cat /tmp/pr-body.md)" \
256351
--field base="develop" \
257352
--field head="docs/release-docs-staging" \
258353
--jq '.number')
259354
gh api "repos/$REPO/issues/$PR_NUMBER/labels" \
260355
--method POST \
261-
--field 'labels[]=type: chore'
356+
--field 'labels[]=type: docs'
262357
fi

.github/workflows/dx-triage-doc-fixes.yml

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ jobs:
128128
DX-2591
129129
DX-2593
130130
131+
## Step 5 — Write the PR title and summary
132+
133+
After applying all fixes, write these two files using the Write tool:
134+
135+
1. /tmp/pr-title.txt — a single-line pull request title. It MUST start with "docs: ", followed by a concise summary of what was changed, and MUST end with " [automated]". Keep it under 100 characters. Example: "docs: fix incorrect subscriber examples and payment provider setup [automated]"
136+
2. /tmp/pr-summary.md — a short summary (2-4 sentences, or a few bullet points) of what was updated. Plain markdown, no top-level heading.
137+
138+
If no issues qualified or were fixed, do not create either file.
139+
131140
- name: Stage documentation changes
132141
id: changes
133142
run: |
@@ -151,6 +160,21 @@ jobs:
151160
echo "No issues fixed."
152161
fi
153162
163+
- name: Read Claude-generated PR metadata
164+
id: pr-meta
165+
if: steps.changes.outputs.has_changes == 'true'
166+
run: |
167+
TITLE=""
168+
if [ -s /tmp/pr-title.txt ]; then
169+
TITLE=$(head -n 1 /tmp/pr-title.txt | sed 's/[[:space:]]*$//')
170+
fi
171+
# Fall back to a static title if Claude's title is missing or malformed
172+
case "$TITLE" in
173+
"docs: "*"[automated]") ;;
174+
*) TITLE="docs: fix documentation issues in triage inbox [automated]" ;;
175+
esac
176+
echo "title=$TITLE" >> "$GITHUB_OUTPUT"
177+
154178
- name: Commit documentation changes
155179
if: steps.changes.outputs.has_changes == 'true'
156180
run: |
@@ -167,6 +191,7 @@ jobs:
167191
REPO: ${{ github.repository }}
168192
BRANCH: ${{ steps.branch.outputs.name }}
169193
FIXED_IDS: ${{ steps.fixed-ids.outputs.ids }}
194+
PR_TITLE: ${{ steps.pr-meta.outputs.title }}
170195
run: |
171196
BODY_FILE=$(mktemp)
172197
@@ -175,6 +200,16 @@ jobs:
175200
176201
This PR fixes documentation issues from the triage inbox.
177202
Each issue was evaluated by Claude and deemed a self-contained doc fix.
203+
BODY_EOF
204+
205+
echo "" >> "$BODY_FILE"
206+
if [ -s /tmp/pr-summary.md ]; then
207+
cat /tmp/pr-summary.md >> "$BODY_FILE"
208+
else
209+
echo "_No summary was generated._" >> "$BODY_FILE"
210+
fi
211+
212+
cat >> "$BODY_FILE" << 'BODY_EOF'
178213
179214
> Review carefully before merging. Claude may have missed context or made
180215
> incorrect assumptions.
@@ -192,19 +227,19 @@ jobs:
192227
if [ -n "$PR_NUMBER" ] && [ "$PR_NUMBER" != "null" ]; then
193228
gh api "repos/$REPO/pulls/$PR_NUMBER" \
194229
--method PATCH \
195-
--field title="docs: fix documentation issues in triage inbox" \
230+
--field title="$PR_TITLE" \
196231
--field body="$(cat "$BODY_FILE")"
197232
echo "Updated PR #$PR_NUMBER"
198233
else
199234
PR_NUMBER=$(gh api "repos/$REPO/pulls" \
200235
--method POST \
201-
--field title="docs: fix documentation issues in triage inbox" \
236+
--field title="$PR_TITLE" \
202237
--field body="$(cat "$BODY_FILE")" \
203238
--field base="develop" \
204239
--field head="$BRANCH" \
205240
--jq '.number')
206241
gh api "repos/$REPO/issues/$PR_NUMBER/labels" \
207242
--method POST \
208-
--field 'labels[]=type: chore'
243+
--field 'labels[]=type: docs'
209244
echo "Created PR #$PR_NUMBER"
210245
fi

0 commit comments

Comments
 (0)