From 17cd600b419f509d83afc7ce8aeed5900327006a Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Thu, 21 May 2026 15:40:41 -0400 Subject: [PATCH 1/2] ci(lychee-cron): prompt @docs-agent in thread, drop artifact upload Adds a triage prompt addressed to the @docs-agent Slack bot at the bottom of the thread message, with the broken-link list above it so the agent has full context (URL + status code + lychee reason) when deciding whether to fix the link, replace it, or add a bot-block exception to lychee.toml. Also removes the now-redundant artifact upload step: the thread message holds all the info anyone needs to triage, and 30k chars fits ~200+ failures comfortably. Truncation message updated to point at re-running the workflow instead of the (now absent) artifact. Co-Authored-By: Claude --- .github/workflows/link-check-scheduled.yml | 7 ------ scripts/link-check-slack.ts | 29 +++++++++++++++++----- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/.github/workflows/link-check-scheduled.yml b/.github/workflows/link-check-scheduled.yml index f83e41190..300c27ef1 100644 --- a/.github/workflows/link-check-scheduled.yml +++ b/.github/workflows/link-check-scheduled.yml @@ -43,13 +43,6 @@ jobs: LYCHEE_STATUS: ${{ steps.lychee.outcome }} run: pnpm exec tsx scripts/link-check-slack.ts ./broken-links.json - - name: Upload report - if: always() - uses: actions/upload-artifact@v4 - with: - name: broken-links - path: ./broken-links.json - - name: Fail if broken links found run: | ERRORS=$(jq -r '.errors // 0' broken-links.json) diff --git a/scripts/link-check-slack.ts b/scripts/link-check-slack.ts index f3adf826f..6e2332f05 100644 --- a/scripts/link-check-slack.ts +++ b/scripts/link-check-slack.ts @@ -56,6 +56,19 @@ const SUMMARY_ROWS: ReadonlyArray<[string, keyof LycheeReport]> = [ const SLACK_CHANNEL = "#dx-developer-relations"; const SLACK_API = "https://slack.com/api/chat.postMessage"; +// Slack user ID for the docs-agent bot. Uses the literal `<@U...>` form so +// the mention triggers a real notification (plain `@docs-agent` text does +// not). +const DOCS_AGENT_MENTION = "<@U0AHN61NBGA>"; + +const DOCS_AGENT_PROMPT = [ + `${DOCS_AGENT_MENTION} please fix the broken links above. For each one:`, + "• If the URL points to the wrong page on a domain we control, update it to the correct path.", + "• If the page is gone, replace the link with the best current equivalent (prefer official docs).", + "• If the failure is a 403/429/SSL/bot-block from a third-party domain, add the URL pattern to the `exclude` list in `lychee.toml` with a one-line comment explaining why.", + "Open a single PR with the fixes and link this thread in the description.", +].join("\n"); + // Slack hard-caps chat.postMessage text at 40k chars. Stay well under so the // payload fits with the surrounding JSON envelope. const MAX_THREAD_CHARS = 30_000; @@ -83,16 +96,18 @@ const formatSummary = (stats: LycheeReport): string => ).join("\n"); /** - * Nested-bullet error list. Top-level bullet is the source file; indented - * sub-bullets are the broken links inside it, with the bracketed status code - * folded into the Slack link label so each line stays compact. + * Builds the thread body: nested-bullet error list followed by a prompt to + * @docs-agent. Each broken link sub-bullet includes the status code, the + * shortened URL, and the lychee status text — the agent uses the reason + * (Network error, Rejected status code, etc.) to decide between updating the + * URL, finding a replacement, or adding a bot-block exception in lychee.toml. */ const formatErrorLines = ( errorMap: Record, ): string => { const lines: string[] = []; let truncated = false; - let charBudget = MAX_THREAD_CHARS; + let charBudget = MAX_THREAD_CHARS - DOCS_AGENT_PROMPT.length; const push = (line: string): boolean => { if (charBudget - line.length < 0) { @@ -109,8 +124,9 @@ const formatErrorLines = ( if (!push(`• \`${file}\``)) break; for (const error of errors) { const code = error.status?.code ?? "ERR"; + const reason = error.status?.text ?? "Unknown error"; const label = `[${code}] ${shortenUrl(error.url)}`; - if (!push(` ◦ <${error.url}|${label}>`)) break; + if (!push(` ◦ <${error.url}|${label}> — ${reason}`)) break; } if (truncated) break; } @@ -118,9 +134,10 @@ const formatErrorLines = ( if (truncated) { lines.push(""); lines.push( - "_…output truncated. See the workflow artifact for the full report._", + "_…output truncated. Re-run the workflow to regenerate the full report._", ); } + lines.push("", DOCS_AGENT_PROMPT); return lines.join("\n"); }; From e2e4da860e0077aa906597d4a6c1683a216b3373 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Thu, 21 May 2026 15:47:02 -0400 Subject: [PATCH 2/2] style(lychee-cron): move lychee reason to its own sub-bullet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three-level nesting (file → URL → reason) reads cleaner than the inline em-dash variant when the reason text is long. Co-Authored-By: Claude --- scripts/link-check-slack.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/link-check-slack.ts b/scripts/link-check-slack.ts index 6e2332f05..4326a9af5 100644 --- a/scripts/link-check-slack.ts +++ b/scripts/link-check-slack.ts @@ -126,7 +126,8 @@ const formatErrorLines = ( const code = error.status?.code ?? "ERR"; const reason = error.status?.text ?? "Unknown error"; const label = `[${code}] ${shortenUrl(error.url)}`; - if (!push(` ◦ <${error.url}|${label}> — ${reason}`)) break; + if (!push(` ◦ <${error.url}|${label}>`)) break; + if (!push(` ▪︎ ${reason}`)) break; } if (truncated) break; }