-
Notifications
You must be signed in to change notification settings - Fork 10
feat: add auto-docs workflow and screenshot functionality for documen… #667
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| You are updating the Glific documentation site (glific/docs) in response to a GitHub issue. | ||
|
|
||
| Issue #${ISSUE_NUMBER}: ${ISSUE_TITLE} | ||
|
|
||
| ${ISSUE_BODY} | ||
|
|
||
| Two related product repositories have already been cloned, read-only, as subdirectories | ||
| of this working directory: | ||
| - repos/glific (backend, Elixir) | ||
| - repos/glific-frontend (frontend, React) | ||
|
|
||
| Your task: | ||
|
|
||
| 1. Investigate the issue above and, using the two repos, figure out what product | ||
| behavior, screen, or feature it refers to. Look at recent commits, relevant source | ||
| files, and README/CHANGELOG content in those repos for context. | ||
|
|
||
| 2. Decide which existing doc page(s) under docs/ need updating, or whether a new page | ||
| is needed. Follow the structure, numbering, and page conventions documented in this | ||
| repo's CLAUDE.md. | ||
|
|
||
| 3. Edit or create the minimal set of doc files needed, matching the conventions in | ||
| CLAUDE.md and the neighboring pages in the same folder. | ||
|
|
||
| 4. Wherever the doc should show a screenshot of the actual running app, insert a single | ||
| placeholder line of this exact form (a later automated step replaces it with a real | ||
| image — do not invent or guess an image path yourself): | ||
|
|
||
|  | ||
|
|
||
| - <short-slug> is a short kebab-case identifier, unique within this change (e.g. | ||
| "flow-editor-new-node"). | ||
| - <app-route-path> is the in-app route to screenshot, starting with "/" (e.g. | ||
| "/flow/configure/123"). | ||
| - Only add these where a screenshot genuinely helps the reader; no more than 3. | ||
|
|
||
| 5. If, after investigating, this issue does not actually require a documentation change, | ||
| make NO file changes and instead write exactly the word "SKIP" as the first line of | ||
| pr-body.md at the repo root, followed by a one-sentence explanation on the next line. | ||
|
|
||
| 6. Otherwise, write a concise PR description (2-4 sentences: what changed and why, | ||
| referencing the issue) to pr-body.md at the repo root. Body text only, no title. | ||
|
|
||
| Constraints: | ||
| - Only create or edit files under docs/, plus the single file pr-body.md at the repo | ||
| root. Do not touch workflow files, package.json, static/img/generated/, or anything | ||
| under repos/. | ||
| - Do not invent product behavior you can't find evidence for in the two repos; if | ||
| uncertain, note the uncertainty in the doc text rather than guessing confidently. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| // Scans the docs/ files Claude just edited for `SCREENSHOT:<slug>:<route>` placeholders, | ||
| // captures each route from the staging Glific instance, and rewrites the placeholder | ||
| // with a real image path. No AI involved here — purely mechanical, run after the | ||
| // Claude authoring step in .github/workflows/auto-docs.yml. | ||
| // | ||
| // Logs in once and reuses that single browser session for every screenshot in the run | ||
| // (Glific auth is phone number + password — see src/containers/Auth/Login/Login.tsx | ||
| // in glific-frontend: the field names are "phoneNumber" and "password", and the submit | ||
| // button is data-testid="SubmitButton"; login finishes with a hard page redirect away | ||
| // from /login rather than client-side routing). | ||
|
|
||
| import { execSync } from "node:child_process"; | ||
| import { chromium } from "playwright"; | ||
| import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; | ||
| import { dirname, relative } from "node:path"; | ||
|
|
||
| const STAGING_URL = requireEnv("GLIFIC_STAGING_URL").replace(/\/+$/, ""); | ||
| const PHONE = requireEnv("GLIFIC_STAGING_PHONE"); | ||
| const PASSWORD = requireEnv("GLIFIC_STAGING_PASSWORD"); | ||
| const ISSUE_NUMBER = requireEnv("ISSUE_NUMBER"); | ||
|
|
||
| const PLACEHOLDER_RE = /!\[\]\(SCREENSHOT:([a-z0-9-]+):([^)]+)\)/g; | ||
|
|
||
| function requireEnv(name) { | ||
| const value = process.env[name]; | ||
| if (!value) { | ||
| console.error(`Missing required env var ${name}`); | ||
| process.exit(1); | ||
| } | ||
| return value; | ||
| } | ||
|
|
||
| function changedDocFiles() { | ||
| const output = execSync("git status --porcelain -- docs", { | ||
| encoding: "utf8", | ||
| }); | ||
| return output | ||
| .split("\n") | ||
| .map((line) => line.slice(3).trim()) | ||
| .filter((path) => path.endsWith(".md") || path.endsWith(".mdx")); | ||
| } | ||
|
|
||
| function findPlaceholders(files) { | ||
| const found = []; | ||
| for (const file of files) { | ||
| const content = readFileSync(file, "utf8"); | ||
| for (const match of content.matchAll(PLACEHOLDER_RE)) { | ||
| found.push({ file, full: match[0], slug: match[1], route: match[2] }); | ||
| } | ||
| } | ||
| return found; | ||
| } | ||
|
|
||
| function relativeImagePath(docFile, imagePath) { | ||
| const rel = relative(dirname(docFile), imagePath); | ||
| return rel.startsWith(".") ? rel : `./${rel}`; | ||
|
Comment on lines
+54
to
+56
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Write the public Docusaurus image path.
Proposed fix-function relativeImagePath(docFile, imagePath) {
- const rel = relative(dirname(docFile), imagePath);
- return rel.startsWith(".") ? rel : `./${rel}`;
-}
-
...
- const markdown = `})`;
+ const markdown = ``;Also applies to: 105-105 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| async function login(page) { | ||
| await page.goto(`${STAGING_URL}/login`, { waitUntil: "networkidle" }); | ||
| await page.fill('input[name="phoneNumber"]', PHONE); | ||
| await page.fill('input[name="password"]', PASSWORD); | ||
| try { | ||
| await Promise.all([ | ||
| page.waitForURL((url) => !url.pathname.includes("/login"), { | ||
| timeout: 20_000, | ||
| }), | ||
| page.click('[data-testid="SubmitButton"]'), | ||
| ]); | ||
| } catch (err) { | ||
| throw new Error( | ||
| `Login to staging Glific instance (${STAGING_URL}) did not leave /login within 20s — check GLIFIC_STAGING_PHONE/GLIFIC_STAGING_PASSWORD. Underlying error: ${err.message}` | ||
| ); | ||
| } | ||
| await page.waitForLoadState("networkidle"); | ||
| } | ||
|
|
||
| async function main() { | ||
| const files = changedDocFiles(); | ||
| const placeholders = findPlaceholders(files); | ||
|
|
||
| if (placeholders.length === 0) { | ||
| console.log("No SCREENSHOT: placeholders found, nothing to capture."); | ||
| return; | ||
| } | ||
|
|
||
| const browser = await chromium.launch(); | ||
| const page = await browser.newPage({ | ||
| viewport: { width: 1440, height: 900 }, | ||
| }); | ||
|
|
||
| try { | ||
| await login(page); // one session, reused for every capture below | ||
|
|
||
| const replacements = new Map(); // file -> [{full, markdown}] | ||
| for (const { file, full, slug, route } of placeholders) { | ||
| const outDir = `static/img/generated/${ISSUE_NUMBER}`; | ||
| const outPath = `${outDir}/${slug}.png`; | ||
| mkdirSync(outDir, { recursive: true }); | ||
|
|
||
| console.log(`Capturing ${route} -> ${outPath}`); | ||
| await page.goto(`${STAGING_URL}${route}`, { waitUntil: "networkidle" }); | ||
| await page.screenshot({ path: outPath }); | ||
|
|
||
| const markdown = `})`; | ||
| const list = replacements.get(file) ?? []; | ||
| list.push({ full, markdown }); | ||
| replacements.set(file, list); | ||
| } | ||
|
|
||
| for (const [file, list] of replacements) { | ||
| let content = readFileSync(file, "utf8"); | ||
| for (const { full, markdown } of list) { | ||
| content = content.split(full).join(markdown); | ||
| } | ||
| writeFileSync(file, content); | ||
| } | ||
| } finally { | ||
| await browser.close(); | ||
| } | ||
| } | ||
|
|
||
| main().catch((err) => { | ||
| console.error(err); | ||
| process.exit(1); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| name: Auto Docs (label-triggered) | ||
|
|
||
| # Fires when an issue is labeled "auto-docs". Investigates the ticket against | ||
| # the glific and glific-frontend repos, updates the relevant doc page(s) with | ||
| # Claude, captures a fresh screenshot of the real app for any page that needs | ||
| # one, and opens a PR for human review. Never auto-merges. | ||
|
|
||
| on: | ||
| issues: | ||
| types: [labeled] | ||
|
|
||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| issues: write | ||
|
Comment on lines
+12
to
+15
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift Prevent Claude from reading a write-capable Git credential.
Set Also applies to: 22-37 🧰 Tools🪛 zizmor (1.29.0)[error] 13-13: overly broad permissions (excessive-permissions): contents: write is overly broad at the workflow level (excessive-permissions) [error] 14-14: overly broad permissions (excessive-permissions): pull-requests: write is overly broad at the workflow level (excessive-permissions) [error] 15-15: overly broad permissions (excessive-permissions): issues: write is overly broad at the workflow level (excessive-permissions) [warning] 13-13: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment (undocumented-permissions) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| jobs: | ||
| auto-docs: | ||
| if: github.event.label.name == 'auto-docs' && github.event.issue.pull_request == null | ||
| runs-on: ubuntu-latest | ||
|
Comment on lines
+17
to
+20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Serialize and make repeated issue runs idempotent. A second Also applies to: 135-142 🧰 Tools🪛 zizmor (1.29.0)[info] 18-18: workflow or action definition without a name (anonymous-definition): this job (anonymous-definition) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| steps: | ||
| - name: Checkout docs | ||
| uses: actions/checkout@v4 | ||
|
Comment on lines
+22
to
+23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== workflow excerpt =="
cat -n .github/workflows/auto-docs.yml | sed -n '1,130p'
echo
echo "== git diff stat/name-status (if available) =="
git diff --stat HEAD~1..HEAD 2>/dev/null || true
git diff --numstat HEAD~1..HEAD 2>/dev/null || true
echo
echo "== references to checkout/setup-node/claude-code-action in workflow =="
rg -n "actions/checkout|anthropics/claude-code-action|actions/setup-node|uses:" .github/workflows/auto-docs.ymlRepository: glific/docs Length of output: 6560 Pin GitHub Actions to commit SHAs. This workflow runs with write permissions and passes 🧰 Tools🪛 zizmor (1.29.0)[warning] 22-23: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false (artipacked) [error] 23-23: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| - name: Checkout glific (backend, read-only context) | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| repository: glific/glific | ||
| path: repos/glific | ||
| fetch-depth: 1 | ||
|
|
||
| - name: Checkout glific-frontend (read-only context) | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| repository: glific/glific-frontend | ||
| path: repos/glific-frontend | ||
| fetch-depth: 1 | ||
|
|
||
| - name: Build prompt | ||
| id: build_prompt | ||
| env: | ||
| ISSUE_TITLE: ${{ github.event.issue.title }} | ||
| ISSUE_BODY: ${{ github.event.issue.body }} | ||
| ISSUE_NUMBER: ${{ github.event.issue.number }} | ||
| run: | | ||
| envsubst < .github/scripts/docs-agent-prompt.md > /tmp/prompt.md | ||
| { | ||
| echo 'prompt<<EOF_PROMPT_9f3a1c' | ||
| cat /tmp/prompt.md | ||
| echo 'EOF_PROMPT_9f3a1c' | ||
| } >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Run Claude doc-authoring step | ||
| uses: anthropics/claude-code-action@v1 | ||
| with: | ||
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | ||
| prompt: ${{ steps.build_prompt.outputs.prompt }} | ||
| claude_args: | | ||
| --max-turns 15 | ||
| --allowedTools "Read,Glob,Grep,Edit,Write" | ||
|
|
||
| - name: Remove cloned context repos (never committed, not screenshotted) | ||
| run: rm -rf repos | ||
|
|
||
| - name: Guardrail - confirm Claude only touched docs/ and pr-body.md | ||
| id: guardrail | ||
| run: | | ||
| # cut -c4- (not awk) because doc folder names contain spaces, e.g. | ||
| # "docs/3. Product Features/..." — awk would truncate at the first space. | ||
| changed=$(git status --porcelain | cut -c4-) | ||
| bad=0 | ||
| while IFS= read -r f; do | ||
| [ -z "$f" ] && continue | ||
| case "$f" in | ||
| *' -> '*) f="${f#*-> }" ;; # renames: check the destination path | ||
| esac | ||
| case "$f" in | ||
| docs/*|pr-body.md) ;; | ||
| *) | ||
| echo "::error::Unexpected file changed outside docs/: $f" | ||
| bad=1 | ||
| ;; | ||
| esac | ||
| done <<< "$changed" | ||
|
Comment on lines
+70
to
+84
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Parse Git status paths as NUL-delimited records.
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| if [ "$bad" = "1" ]; then | ||
| echo "Claude's changes touched files outside the allowed scope. Aborting without committing." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [ -f pr-body.md ] && [ "$(head -n1 pr-body.md)" = "SKIP" ]; then | ||
| echo "skip=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "skip=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Comment when skipped | ||
| if: steps.guardrail.outputs.skip == 'true' | ||
| run: | | ||
| reason=$(tail -n +2 pr-body.md) | ||
| gh issue comment "${{ github.event.issue.number }}" --body "Auto-docs pipeline ran but made no changes: ${reason:-no docs update was needed for this ticket.}" | ||
| rm -f pr-body.md | ||
| env: | ||
| GITHUB_TOKEN: ${{ github.token }} | ||
|
|
||
| - name: Set up Node | ||
| if: steps.guardrail.outputs.skip == 'false' | ||
| uses: actions/setup-node@v3 | ||
| with: | ||
| node-version: 18 | ||
| cache: yarn | ||
|
Comment on lines
+105
to
+110
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
npx --yes actionlint .github/workflows/auto-docs.ymlRepository: glific/docs Length of output: 626 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== workflow files =="
git ls-files .github/workflows || true
echo "== target workflow relevant lines =="
if [ -f .github/workflows/auto-docs.yml ]; then
nl -ba .github/workflows/auto-docs.yml | sed -n '90,120p'
fi
echo "== all setup-node actions in workflows =="
rg -n 'uses:\s*actions/setup-node@' .github/workflows || true
echo "== available binaries/tools =="
command -v actionlint || true
command -v npx || true
npm config get registry || trueRepository: glific/docs Length of output: 412 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== target workflow lines with line numbers =="
python3 - <<'PY'
from pathlib import Path
path = Path('.github/workflows/auto-docs.yml')
for idx, line in enumerate(path.read_text().splitlines(), 1):
if 90 <= idx <= 120:
print(f"{idx:4}: {line}")
PY
echo "== all setup-node action pins =="
python3 - <<'PY'
from pathlib import Path
for path in Path('.github/workflows').glob('*'):
text = path.read_text(errors='replace')
for line in text.splitlines():
if 'uses:' in line and 'actions/setup-node@' in line:
print(f"{path}:{line.strip()}")
PY
echo "== actionlint availability =="
command -v actionlint || true
command -v npx || true
echo "== actionlint from npm if installed globally in prefix =="
for bin in /usr/local/opt/ruby/bin/actionlint \
/usr/local/bin/actionlint \
/usr/bin/actionlint \
~/.npm-g-global/bin/actionlint; do
[ -x "$bin" ] && echo "$bin exists and is executable" || true
doneRepository: glific/docs Length of output: 1889 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== actionlint workflow report =="
/usr/bin/actionlint .github/workflows/auto-docs.yml || true
echo "== actionlint version =="
/usr/bin/actionlint --version || true
echo "== actionlint setup-node failure scan across workflows =="
/usr/bin/actionlint .github/workflows || trueRepository: glific/docs Length of output: 702 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== all actionlint action runner warnings =="
python3 - <<'PY'
import subprocess, pathlib
for path in pathlib.Path('.github/workflows').glob('*'):
result = subprocess.run(['/usr/bin/actionlint', str(path)], text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
for line in (result.stdout + result.stderr).splitlines():
if 'runner' in line and 'too old' in line:
print(line)
PY
echo "== GitHub actions/setup-node v4 branch existence =="
curl -fsSLI -o /dev/null https://github.com/actions/setup-node/tree/v4 || trueRepository: glific/docs Length of output: 1841 🌐 Web query:
💡 Result: The actions/setup-node repository manages its releases using Git tags, and v4 is a specific major version series within that history [1][2]. Key details regarding v4 and the repository's release structure: 1. Versioning: The action uses Semantic Versioning (SemVer) with tags like v4.0.0, v4.0.4, and v4.4.0 [3][4][5]. Each major version (e.g., v4, v5, v6, v7) represents a distinct branch or evolution of the action, often involving updates to the underlying Node.js runtime for the action itself [3][6][7][8]. 2. Current Status: As of August 7, 2026, the current major version is v7 [6][8]. The v4 series is no longer the latest [1][6]. 3. GitHub Releases: All releases, including the v4 series, are documented on the official GitHub Releases page for the repository [1]. Each tag corresponds to a specific release state, allowing users to pin their workflows to specific versions (e.g., actions/setup-node@v4 or actions/setup-node@v4.4.0) to ensure stability [1][5][8]. For users looking to reference v4 specifically in their workflows, using the tag actions/setup-node@v4 will generally point to the latest minor/patch release within that major version [1][5]. However, it is recommended to check the releases page for the specific requirements and features associated with newer major versions like v7 [1][6][8]. Citations:
Upgrade
🧰 Tools🪛 actionlint (1.7.12)[error] 107-107: the runner of "actions/setup-node@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue (action) 🪛 zizmor (1.29.0)[error] 107-107: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| - name: Install dependencies | ||
| if: steps.guardrail.outputs.skip == 'false' | ||
| run: yarn install --frozen-lockfile | ||
|
|
||
| - name: Install Playwright browser | ||
| if: steps.guardrail.outputs.skip == 'false' | ||
| run: npx playwright install --with-deps chromium | ||
|
|
||
| - name: Take screenshots and rewrite placeholders | ||
| if: steps.guardrail.outputs.skip == 'false' | ||
| env: | ||
| GLIFIC_STAGING_URL: ${{ secrets.GLIFIC_STAGING_URL }} | ||
| GLIFIC_STAGING_PHONE: ${{ secrets.GLIFIC_STAGING_PHONE }} | ||
| GLIFIC_STAGING_PASSWORD: ${{ secrets.GLIFIC_STAGING_PASSWORD }} | ||
| ISSUE_NUMBER: ${{ github.event.issue.number }} | ||
| run: node .github/scripts/take-screenshots.mjs | ||
|
|
||
| - name: Open PR | ||
| if: steps.guardrail.outputs.skip == 'false' | ||
| env: | ||
| GITHUB_TOKEN: ${{ github.token }} | ||
| ISSUE_NUMBER: ${{ github.event.issue.number }} | ||
| run: | | ||
| branch="docs/auto-${ISSUE_NUMBER}" | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
| git checkout -b "$branch" | ||
| git add docs | ||
| [ -d static/img/generated ] && git add static/img/generated | ||
| git commit -m "docs: auto-update from issue #${ISSUE_NUMBER}" | ||
| git push -u origin "$branch" | ||
|
|
||
| { | ||
| cat pr-body.md | ||
| echo "" | ||
| echo "Closes #${ISSUE_NUMBER}" | ||
| echo "" | ||
| echo "_Opened automatically by the auto-docs pipeline. Please review before merging._" | ||
| } > /tmp/pr-body-final.md | ||
| rm -f pr-body.md | ||
|
|
||
| gh pr create \ | ||
| --title "docs: update from issue #${ISSUE_NUMBER}" \ | ||
| --body-file /tmp/pr-body-final.md \ | ||
| --label auto-docs \ | ||
| --base main \ | ||
| --head "$branch" | ||
|
|
||
| gh issue comment "$ISSUE_NUMBER" --body "Opened a docs PR for this: $(gh pr view "$branch" --json url -q .url)" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
Repository: glific/docs
Length of output: 15911
🏁 Script executed:
Repository: glific/docs
Length of output: 4244
🏁 Script executed:
Repository: glific/docs
Length of output: 12340
Restrict authenticated screenshot routes to documented safe routes.
The workflow labels issue-controlled input as the doc prompt, and the screenshot script captures every
SCREENSHOT:<slug>:<route>placeholder using staging login credentials before opening a PR. Add a documented allowlist in.github/scripts/docs-agent-prompt.md, validate each route against it beforepage.goto()in.github/scripts/take-screenshots.mjs, reject unapproved routes, and use a staging account containing only scrubbed demonstration data.🧰 Tools
🪛 markdownlint-cli2 (0.23.2)
[warning] 29-29: Images should have alternate text (alt text)
(MD045, no-alt-text)
📍 Affects 2 files
.github/scripts/docs-agent-prompt.md#L25-L35(this comment).github/scripts/take-screenshots.mjs#L96-L103🤖 Prompt for AI Agents