From 7e7552ce42d8b65d10678d8782642e3efb1bb8cb Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Fri, 11 Sep 2026 05:13:33 -0600 Subject: [PATCH 1/9] ci: fail PRs that break redirects, with suggested fixes dev/check-redirects.mjs checks every entry in src/data/redirects.ts: source shadows a page, source has a #fragment, duplicate source, /docs prefix, chained redirect, missing destination page or heading. The workflow compares against the merge base, so only redirects a PR breaks are reported, grouped by problem with the fix explained under each heading, and posts one suggested change per fixable entry the PR added (deleted again once the finding is gone). Not part of `npm run check`: main has hundreds of pre-existing findings. Squash of the check-redirects branch rebased onto main; the check-links commits it carried are already on main. Amp-Thread-ID: https://ampcode.com/threads/T-01a08fee-74b4-76dc-aaf9-d1245d68fdc9 Co-authored-by: Amp --- .github/workflows/check-redirects.yml | 133 +++++++ AGENTS.md | 1 + dev/check-redirects.mjs | 492 ++++++++++++++++++++++++++ 3 files changed, 626 insertions(+) create mode 100644 .github/workflows/check-redirects.yml create mode 100644 dev/check-redirects.mjs diff --git a/.github/workflows/check-redirects.yml b/.github/workflows/check-redirects.yml new file mode 100644 index 000000000..634678aa0 --- /dev/null +++ b/.github/workflows/check-redirects.yml @@ -0,0 +1,133 @@ +name: Check redirects + +# Reports redirects in src/data/redirects.ts that this PR breaks, compared with +# the merge base: destinations that no longer exist, #fragments whose heading +# was renamed, and new redirects that shadow an existing page. Pre-existing +# broken redirects on the base branch are ignored. + +on: + pull_request: + +# A new push supersedes the run for the previous one +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + +jobs: + check-redirects: + name: Broken redirects introduced by this PR + runs-on: ubuntu-latest + steps: + - name: Check out pull request head + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + + - name: Install github-slugger, the only dependency of dev/check-redirects.mjs + # Into a scratch prefix, not the repo: `npm install ` next to + # package.json would install every dependency of the site + run: | + npm install --prefix "$RUNNER_TEMP/deps" --no-package-lock --no-audit --no-fund \ + "github-slugger@$(node -p 'require("./package.json").dependencies["github-slugger"]')" + ln -s "$RUNNER_TEMP/deps/node_modules" node_modules + + - name: Check out merge base + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + merge_base=$(git merge-base "$BASE_SHA" HEAD) + git worktree add "$RUNNER_TEMP/base" "$merge_base" + git diff -U0 "$merge_base" HEAD -- src/data/redirects.ts > "$RUNNER_TEMP/changes.diff" + + - name: Record broken redirects already present on the base branch + # Exit 1 means findings, which is expected here + run: | + node dev/check-redirects.mjs --format json \ + --root "$RUNNER_TEMP/base" > "$RUNNER_TEMP/base-redirects.json" \ + || [ $? -eq 1 ] + + - name: Find redirects broken by this PR + id: check + env: + # Line links in the report open redirects.ts on the PR branch + LINK_BASE: ${{ github.event.pull_request.head.repo.html_url }}/blob/${{ github.event.pull_request.head.ref }} + run: | + if node dev/check-redirects.mjs --format markdown \ + --baseline "$RUNNER_TEMP/base-redirects.json" \ + --diff "$RUNNER_TEMP/changes.diff" \ + --review "$RUNNER_TEMP/review.json" \ + --link-base "$LINK_BASE" > "$RUNNER_TEMP/report.md"; then + echo "broken=false" >> "$GITHUB_OUTPUT" + else + echo "broken=true" >> "$GITHUB_OUTPUT" + fi + cat "$RUNNER_TEMP/report.md" + + - name: Comment on the pull request + # Fork PRs get a read-only token; the report is still in the job log + if: github.event.pull_request.head.repo.full_name == github.repository + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + BROKEN: ${{ steps.check.outputs.broken }} + run: | + marker='' + existing_comment=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \ + --paginate --jq ".[] | select(.body | startswith(\"$marker\")) | .id" | head -n 1) + + # Comment only when there is something to report, or an earlier report to resolve + if [ "$BROKEN" = true ]; then + { echo "$marker"; cat "$RUNNER_TEMP/report.md"; } > "$RUNNER_TEMP/comment.md" + elif [ -n "$existing_comment" ]; then + printf '%s\n### ✅ The redirects an earlier revision of this PR broke are fixed\n' \ + "$marker" > "$RUNNER_TEMP/comment.md" + else + exit 0 + fi + + if [ -n "$existing_comment" ]; then + gh api --method PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$existing_comment" \ + --field body=@"$RUNNER_TEMP/comment.md" + else + gh pr comment "$PR_NUMBER" --body-file "$RUNNER_TEMP/comment.md" + fi + + - name: Suggest fixes as review comments + # One suggested change per fixable entry this PR added. Suggestions + # already on the PR (same lines and entry) are not posted again; + # suggestions for findings that are gone are deleted. GitHub sets line + # to null on comments it could not carry to the new revision, so those + # are deleted too. + if: github.event.pull_request.head.repo.full_name == github.repository + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + key='(.path + ":" + (.line | tostring) + ":" + (.body | split("\n")[0]))' + gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/comments" --paginate \ + --jq ".[] | select(.body | startswith(\"`, + `Problem: ${problem}`, + `Fix: ${describeFix(finding)}`, + '````suggestion', + ...entry, + '````' + ]; + return { + path: REDIRECTS_PATH, + ...(startLine !== endLine && {start_line: startLine, start_side: 'RIGHT'}), + line: endLine, + side: 'RIGHT', + body: body.join('\n') + }; + }); + return {event: 'COMMENT', body: '', comments}; +} + +const FORMATTERS = { + text: formatText, + json: findings => JSON.stringify(findings, null, '\t') + '\n', + markdown: formatMarkdown +}; + +function main() { + const format = FORMATTERS[FORMAT]; + if (!format) { + throw new Error( + `Unknown --format "${FORMAT}"; use text, json, or markdown` + ); + } + + let findings = findBrokenRedirects(loadRedirects(), buildHeadingsByRoute()); + if (BASELINE_FILE) { + findings = withoutBaseline(findings, BASELINE_FILE); + } + + if (REVIEW_FILE) { + const added = DIFF_FILE ? addedLines(DIFF_FILE) : new Set(); + fs.writeFileSync( + REVIEW_FILE, + JSON.stringify(reviewRequest(findings, added), null, '\t') + '\n' + ); + } + + // Not process.exit(): that can truncate stdout when it is a pipe + process.stdout.write(format(findings)); + process.exitCode = findings.length === 0 ? 0 : 1; +} + +main(); From befb8a8d70a4426113e1d3de138b1b1595e51af4 Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Fri, 11 Sep 2026 05:26:15 -0600 Subject: [PATCH 2/9] check-redirects: don't repeat the final destination the Fix line already names Amp-Thread-ID: https://ampcode.com/threads/T-01a08fee-74b4-76dc-aaf9-d1245d68fdc9 Co-authored-by: Amp --- dev/check-redirects.mjs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/dev/check-redirects.mjs b/dev/check-redirects.mjs index 6317eacc8..f13831db8 100644 --- a/dev/check-redirects.mjs +++ b/dev/check-redirects.mjs @@ -232,11 +232,15 @@ function findBrokenRedirects(redirects, headingsByRoute) { const destination = splitUrl(redirect.destination); if (isRedirect(destination.pathname)) { + // The fix names the final destination; only a loop needs `detail` const final = finalDestination(destination.pathname); - report(redirect, PROBLEM.chained, { - detail: final ?? 'none, redirect loop', - fix: final && {source: redirect.source, destination: final} - }); + report( + redirect, + PROBLEM.chained, + final + ? {fix: {source: redirect.source, destination: final}} + : {detail: 'none, redirect loop'} + ); continue; } const headings = headingsByRoute.get(destination.pathname); From 5227ad4615935d5fb456d1cbcdd98b736bd720da Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Fri, 11 Sep 2026 04:54:05 -0600 Subject: [PATCH 3/9] check-links: one suggestion per fix, synced with findings; report one fact per line - Review comments: one suggested change per finding with a fix, no review body. Each starts with a marker so the workflow can delete suggestions for findings that are fixed and skip ones already posted. - Summary comment and review comments list line, link, problem, and fix on their own lines. - Absolute links to this site get their own section instead of Outbound. - Case-mismatch findings now carry a fix. - Wording: 'links on this site', 'these other pages', drop docs.sourcegraph.com; reproduce command matches package.json. Amp-Thread-ID: https://ampcode.com/threads/T-01a08fee-74b4-76dc-aaf9-d1245d68fdc9 Co-authored-by: Amp --- .github/workflows/check-links.yml | 20 +++-- dev/check-links.mjs | 137 ++++++++++++++---------------- 2 files changed, 81 insertions(+), 76 deletions(-) diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index e0dab4ba3..98063e9d6 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -97,19 +97,29 @@ jobs: fi - name: Suggest fixes as review comments - # One suggested change per added line with a fix. Suggestions already on - # the PR (same file, line, and text) are not posted again. - if: steps.check.outputs.broken == 'true' && github.event.pull_request.head.repo.full_name == github.repository + # One suggested change per finding with a fix. Suggestions already on the + # PR (same file, line, and link) are not posted again; suggestions for + # findings that are gone are deleted. GitHub sets line to null on comments + # it could not carry to the new revision, so those are deleted too. + if: github.event.pull_request.head.repo.full_name == github.repository env: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} run: | + key='(.path + ":" + (.line | tostring) + ":" + (.body | split("\n")[0]))' gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/comments" --paginate \ - --jq '.[] | {path, line, body}' | jq -s . > "$RUNNER_TEMP/posted.json" + --jq ".[] | select(.body | startswith(\"`, + `Link: \`${url}\``, + `Problem: ${error}`, + `Fix: \`${fix}\``, + '````suggestion', + source.split(url).join(fix), + '````' + ]; + return { path: file, line, side: 'RIGHT', body: body.join('\n') }; }); - return { - event: 'COMMENT', - body: 'Suggested fixes for the links this PR adds; details in the check-links comment.', - comments - }; + return { event: 'COMMENT', body: '', comments }; } const FORMATTERS = { From 64a5f130d050ac0545cb8233c14821fbce82b2b6 Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Fri, 11 Sep 2026 05:07:03 -0600 Subject: [PATCH 4/9] cspell: allow tostring, a jq builtin used in the workflows Amp-Thread-ID: https://ampcode.com/threads/T-01a08fee-74b4-76dc-aaf9-d1245d68fdc9 Co-authored-by: Amp --- cspell-allow-list.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/cspell-allow-list.txt b/cspell-allow-list.txt index 9b26c3c7c..3411345cc 100644 --- a/cspell-allow-list.txt +++ b/cspell-allow-list.txt @@ -545,6 +545,7 @@ toolcall topk topsecretorg topsecretproject +tostring # jq builtin transactionally transformchanges transformchangesgroup From 0708983fb6079216e708eaad466230b2bffe6418 Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Fri, 11 Sep 2026 05:27:35 -0600 Subject: [PATCH 5/9] check-links: don't repeat the link and fix inside the case-mismatch problem Amp-Thread-ID: https://ampcode.com/threads/T-01a08fee-74b4-76dc-aaf9-d1245d68fdc9 Co-authored-by: Amp --- dev/check-links.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/check-links.mjs b/dev/check-links.mjs index 5eab267b9..8ad57213f 100644 --- a/dev/check-links.mjs +++ b/dev/check-links.mjs @@ -414,7 +414,7 @@ function validateLink(link, currentFile, maps) { resolvedPath.replace(/\/$/, '').toLowerCase() ); if (realPath) { - return { error: `Case mismatch: "${resolvedPath}" should be "${realPath}"`, fix: anchor ? `${realPath}#${anchor}` : realPath }; + return { error: 'Path case mismatch: works on macOS, 404s on the Linux build', fix: anchor ? `${realPath}#${anchor}` : realPath }; } // Check if it's a file with extension (like .png, .pdf) From bbbff3906d92d6e3bfb2a19f63693a698a74925b Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Fri, 11 Sep 2026 05:32:30 -0600 Subject: [PATCH 6/9] check-links: inbound section wording Amp-Thread-ID: https://ampcode.com/threads/T-01a08fee-74b4-76dc-aaf9-d1245d68fdc9 Co-authored-by: Amp --- dev/check-links.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/check-links.mjs b/dev/check-links.mjs index 8ad57213f..3bb48936e 100644 --- a/dev/check-links.mjs +++ b/dev/check-links.mjs @@ -606,7 +606,7 @@ function formatMarkdown(findings) { section('Absolute links', ABSOLUTE_LINKS_ADVICE, absolute); section( 'Inbound', - 'A change your PR made broke inbound links from elsewhere. Please fix the inbound links on these other pages.', + 'A change your PR made broke inbound links from these other files. Please fix the inbound links in these other files.', inbound ); } else { From 056ae2645562927a17b354ec1ded750b1abf8959 Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Fri, 11 Sep 2026 04:58:11 -0600 Subject: [PATCH 7/9] spell check: link summary items to the source, shorten them, flag unsorted dictionary entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Summary comment: line and column link to the file in source view (?plain=1) with the word highlighted; each item is `word` → `first suggestion` instead of the whole line. - check-spelling.mjs reports entries added to cspell-allow-list.txt or cspell-block-list.txt out of alphabetical order (case- and accent-insensitive, like CSpell matches; comments and blank lines start a new run). Trailing '# comments' after a word are ignored, as CSpell does. - Drop the unused context field from the JSON findings. Amp-Thread-ID: https://ampcode.com/threads/T-01a08fee-74b4-76dc-aaf9-d1245d68fdc9 Co-authored-by: Amp --- dev/check-spelling.mjs | 57 +++++++++++++++++++++++++++++------- dev/post-spelling-review.mjs | 41 +++++++++++++++++++------- 2 files changed, 77 insertions(+), 21 deletions(-) diff --git a/dev/check-spelling.mjs b/dev/check-spelling.mjs index 733949636..43a2fddb7 100644 --- a/dev/check-spelling.mjs +++ b/dev/check-spelling.mjs @@ -1,7 +1,8 @@ #!/usr/bin/env node /** - * Reports CSpell findings on lines added by a Git diff. + * Reports CSpell findings on lines added by a Git diff, and dictionary entries + * added out of alphabetical order. * * Usage: node dev/check-spelling.mjs --base [--format text|json] * @@ -10,6 +11,7 @@ */ import {execFileSync, spawnSync} from 'child_process'; +import {readFileSync} from 'fs'; import path from 'path'; import {fileURLToPath} from 'url'; @@ -87,11 +89,45 @@ function runCSpell(files) { column: issue.col, word: issue.text, suggestions: issue.suggestions?.slice(0, 3) ?? [], - text: issue.line.text.replace(/\r?\n$/, ''), - context: issue.context?.text.trim() ?? issue.line.text.trim() + text: issue.line.text.replace(/\r?\n$/, '') })); } +// Entries in the dictionary files must be sorted, so duplicates stand out and +// merges are clean. Sorted the way CSpell matches: case- and accent-insensitive. +// Blank lines and comments start a new sorted run, so the lists can be sectioned. +const DICTIONARY_FILES = ['cspell-allow-list.txt', 'cspell-block-list.txt']; +const collator = new Intl.Collator('en', {sensitivity: 'base'}); + +function unsortedDictionaryEntries(files) { + const findings = []; + for (const file of files.filter(file => DICTIONARY_FILES.includes(file))) { + let previous; + readFileSync(file, 'utf8') + .split('\n') + .forEach((text, index) => { + if (/^\s*(#|$)/.test(text)) { + previous = undefined; + return; + } + const word = text.replace(/\s*#.*/, '').trim(); + if (previous && collator.compare(word, previous) < 0) { + findings.push({ + file, + line: index + 1, + column: 1, + word, + suggestions: [], + text, + message: `\`${word}\` is out of alphabetical order: it belongs before \`${previous}\`, the entry above it.` + }); + } + previous = word; + }); + } + return findings; +} + function findingsOnAddedLines(ranges, issues) { return issues.filter(issue => (ranges.get(issue.file) ?? []).some( @@ -102,15 +138,15 @@ function findingsOnAddedLines(ranges, issues) { function formatText(findings) { if (findings.length === 0) { - return 'No spelling errors found in added lines.\n'; + return 'No issues found in added lines.\n'; } const lines = [ - `Found ${findings.length} spelling error(s) in added lines:` + `Found ${findings.length} issue(s) in added lines:` ]; for (const finding of findings) { lines.push( - `${finding.file}:${finding.line}:${finding.column} - Unknown word (${finding.word})` + `${finding.file}:${finding.line}:${finding.column} - ${finding.message ?? `Unknown word (${finding.word})`}` ); } return lines.join('\n') + '\n'; @@ -125,10 +161,11 @@ async function main() { } const ranges = addedLineRanges(BASE); - const findings = findingsOnAddedLines( - ranges, - runCSpell([...ranges.keys()]) - ); + const files = [...ranges.keys()]; + const findings = findingsOnAddedLines(ranges, [ + ...runCSpell(files), + ...unsortedDictionaryEntries(files) + ]); process.stdout.write( FORMAT === 'json' ? JSON.stringify(findings, null, '\t') + '\n' diff --git a/dev/post-spelling-review.mjs b/dev/post-spelling-review.mjs index 38d18a88e..8501f367f 100644 --- a/dev/post-spelling-review.mjs +++ b/dev/post-spelling-review.mjs @@ -100,10 +100,27 @@ function groupByFile(findings) { return grouped; } +// Source view (?plain=1, so Markdown is not rendered) with the word highlighted +function sourceLink(finding) { + const {file, line, column, word} = finding; + const end = column + word.length; + return `https://github.com/${REPOSITORY}/blob/${HEAD_REF}/${file}?plain=1#L${line}C${column}-L${line}C${end}`; +} + +// `word` → `suggestion`, or the finding's own message for non-spelling +// findings such as an unsorted dictionary entry +function summaryItem(finding) { + if (finding.message) { + return finding.message; + } + const suggestion = bestSuggestion(finding); + return `\`${finding.word}\`${suggestion ? ` → \`${suggestion}\`` : ''}`; +} + function summaryBody(findings) { const lines = [ SUMMARY_MARKER, - `### ⚠️ CSpell found ${findings.length} spelling error(s) in this PR`, + `### ⚠️ Spell check found ${findings.length} issue(s) in this PR`, '', 'Only findings on lines added by this PR are shown.', ...(findings.length > MAX_INLINE_COMMENTS @@ -115,10 +132,10 @@ function summaryBody(findings) { ]; for (const [file, fileFindings] of groupByFile(findings)) { lines.push(`**\`${file}\`**`); - for (const {line, column, word, context} of fileFindings) { - const excerpt = context.replaceAll('`', "'").slice(0, 160); + for (const finding of fileFindings) { + const {line, column} = finding; lines.push( - `- line ${line}, column ${column}: \`${word}\` — \`${excerpt}\`` + `- [line ${line}, column ${column}](${sourceLink(finding)}): ${summaryItem(finding)}` ); } lines.push(''); @@ -207,13 +224,15 @@ function suggestionBlock(finding) { } function inlineBody(finding) { - return [ - `${INLINE_MARKER} ${finding.word} -->`, - `\`${finding.word}\` is not in the dictionary.`, - '', - ...suggestionBlock(finding), - `Please correct the spelling, or add the word to ${ALLOW_LIST_LINK} if it is correct.` - ].join('\n'); + const explanation = finding.message + ? [finding.message] + : [ + `\`${finding.word}\` is not in the dictionary.`, + '', + ...suggestionBlock(finding), + `Please correct the spelling, or add the word to ${ALLOW_LIST_LINK} if it is correct.` + ]; + return [`${INLINE_MARKER} ${finding.word} -->`, ...explanation].join('\n'); } async function syncInlineComments(findings) { From 830f625f51ea6468a7d4c6409e23ea08defb8380 Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Fri, 11 Sep 2026 05:32:09 -0600 Subject: [PATCH 8/9] spell check: put the fix under the line link; say which line an unsorted entry belongs on Amp-Thread-ID: https://ampcode.com/threads/T-01a08fee-74b4-76dc-aaf9-d1245d68fdc9 Co-authored-by: Amp --- dev/check-spelling.mjs | 27 +++++++++++++++++---------- dev/post-spelling-review.mjs | 19 ++++++++++++++----- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/dev/check-spelling.mjs b/dev/check-spelling.mjs index 43a2fddb7..cf07ab1c9 100644 --- a/dev/check-spelling.mjs +++ b/dev/check-spelling.mjs @@ -102,27 +102,33 @@ const collator = new Intl.Collator('en', {sensitivity: 'base'}); function unsortedDictionaryEntries(files) { const findings = []; for (const file of files.filter(file => DICTIONARY_FILES.includes(file))) { - let previous; + let run = []; // [{word, line}] of the current sorted run readFileSync(file, 'utf8') .split('\n') .forEach((text, index) => { if (/^\s*(#|$)/.test(text)) { - previous = undefined; + run = []; return; } const word = text.replace(/\s*#.*/, '').trim(); - if (previous && collator.compare(word, previous) < 0) { + const line = index + 1; + const belongsBefore = run.find( + entry => collator.compare(word, entry.word) < 0 + ); + if (belongsBefore) { findings.push({ file, - line: index + 1, + line, column: 1, word, suggestions: [], text, - message: `\`${word}\` is out of alphabetical order: it belongs before \`${previous}\`, the entry above it.` + message: `\`${word}\` is out of alphabetical order: move it above \`${belongsBefore.word}\``, + relatedLine: belongsBefore.line // rendered as a link after the message }); + } else { + run.push({word, line}); } - previous = word; }); } return findings; @@ -144,10 +150,11 @@ function formatText(findings) { const lines = [ `Found ${findings.length} issue(s) in added lines:` ]; - for (const finding of findings) { - lines.push( - `${finding.file}:${finding.line}:${finding.column} - ${finding.message ?? `Unknown word (${finding.word})`}` - ); + for (const {file, line, column, word, message, relatedLine} of findings) { + const detail = message + ? `${message}${relatedLine ? ` on line ${relatedLine}` : ''}` + : `Unknown word (${word})`; + lines.push(`${file}:${line}:${column} - ${detail}`); } return lines.join('\n') + '\n'; } diff --git a/dev/post-spelling-review.mjs b/dev/post-spelling-review.mjs index 8501f367f..94eacb399 100644 --- a/dev/post-spelling-review.mjs +++ b/dev/post-spelling-review.mjs @@ -107,11 +107,19 @@ function sourceLink(finding) { return `https://github.com/${REPOSITORY}/blob/${HEAD_REF}/${file}?plain=1#L${line}C${column}-L${line}C${end}`; } -// `word` → `suggestion`, or the finding's own message for non-spelling -// findings such as an unsorted dictionary entry +// A non-spelling finding's own message, e.g. an unsorted dictionary entry, +// pointing at its `relatedLine` when it has one +function messageText(finding) { + const {file, message, relatedLine} = finding; + if (!relatedLine) return `${message}.`; + const url = `https://github.com/${REPOSITORY}/blob/${HEAD_REF}/${file}?plain=1#L${relatedLine}`; + return `${message} on [line ${relatedLine}](${url}).`; +} + +// `word` → `suggestion`, or the finding's own message function summaryItem(finding) { if (finding.message) { - return finding.message; + return messageText(finding); } const suggestion = bestSuggestion(finding); return `\`${finding.word}\`${suggestion ? ` → \`${suggestion}\`` : ''}`; @@ -135,7 +143,8 @@ function summaryBody(findings) { for (const finding of fileFindings) { const {line, column} = finding; lines.push( - `- [line ${line}, column ${column}](${sourceLink(finding)}): ${summaryItem(finding)}` + `- [line ${line}, column ${column}](${sourceLink(finding)})`, + ` - ${summaryItem(finding)}` ); } lines.push(''); @@ -225,7 +234,7 @@ function suggestionBlock(finding) { function inlineBody(finding) { const explanation = finding.message - ? [finding.message] + ? [messageText(finding)] : [ `\`${finding.word}\` is not in the dictionary.`, '', From 7b023603c9b93f7ff55587de7c5afdd6b4389e20 Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Fri, 11 Sep 2026 04:11:15 -0600 Subject: [PATCH 9/9] test: break links, spelling, and redirects to exercise every PR check (do not merge) Check links: three absolute self-links (one to a moved page) and a dead external link on one line; a missing page, missing heading, and wrong-case path on the next; and the "Symbol search" heading renamed to break the inbound anchor link from search-based-code-navigation.mdx. Spell check: nine misspellings on one line, five of them block-list words. Check redirects: one broken entry per category (shadowed page, #fragment source, /docs prefix, duplicate source, chain, missing page, missing heading). Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-01a08fee-74b4-76dc-aaf9-d1245d68fdc9 --- cspell-allow-list.txt | 1 + docs/admin/markdown.mdx | 3 --- docs/admin/tls_ssl.mdx | 5 +++++ docs/batch-changes/server-side.mdx | 2 +- docs/code-search/features.mdx | 8 +++++++- src/data/redirects.ts | 20 ++++++++++++++++++++ 6 files changed, 34 insertions(+), 5 deletions(-) delete mode 100644 docs/admin/markdown.mdx create mode 100644 docs/admin/tls_ssl.mdx diff --git a/cspell-allow-list.txt b/cspell-allow-list.txt index 3411345cc..39cf96ecc 100644 --- a/cspell-allow-list.txt +++ b/cspell-allow-list.txt @@ -598,3 +598,4 @@ YOURUSERNAME Zaporizhzhia Zoekt zoomable +aardvark # test entry for the sorted-list check (will be reverted) diff --git a/docs/admin/markdown.mdx b/docs/admin/markdown.mdx deleted file mode 100644 index 3308e167f..000000000 --- a/docs/admin/markdown.mdx +++ /dev/null @@ -1,3 +0,0 @@ -# Sourcegraph-flavored Markdown - -Sourcegraph uses [GitHub Flavored Markdown (GFM)](https://github.github.com/gfm/) anywhere Markdown is rendered. We like this [Markdown cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) for reference. diff --git a/docs/admin/tls_ssl.mdx b/docs/admin/tls_ssl.mdx new file mode 100644 index 000000000..11c9a2c37 --- /dev/null +++ b/docs/admin/tls_ssl.mdx @@ -0,0 +1,5 @@ +--- +title: TLS test page +--- + +# TLS test page diff --git a/docs/batch-changes/server-side.mdx b/docs/batch-changes/server-side.mdx index c301fa1d8..537b21f59 100644 --- a/docs/batch-changes/server-side.mdx +++ b/docs/batch-changes/server-side.mdx @@ -37,7 +37,7 @@ Running batch changes server-side has been tested to run a simple **45K changese By default, docker on mac will build docker images for `linux/arm64`, which will result in errors when running server-side because executors provide `linux/amd64` hosts. If you're creating your own images to run in batch changes, this can be a problem. Use the `--platform linux/amd64` flag with `docker build` to build images compatible with the server-side host. -## Using file mounts with server-side execution +## Using file mounts with SSBC Running a batch spec server-side with file mounts is currently only diff --git a/docs/code-search/features.mdx b/docs/code-search/features.mdx index b86a4d8f3..7a1b47dd5 100644 --- a/docs/code-search/features.mdx +++ b/docs/code-search/features.mdx @@ -45,7 +45,7 @@ Searching over commit messages is supported in Sourcegraph by adding `type:commi See our [query syntax](/code-search/queries#diff-and-commit-searches-only) documentation for a comprehensive list of supported parameters. -## Symbol search +## Symbol lookup Searching for symbols makes it easier to find specific functions, variables, and more. Use the `type:symbol` filter to search for symbol results. Symbol results also appear in typeahead suggestions, so you can jump directly to symbols by name. When on an [indexed](/admin/search#indexed-search) commit, it uses Zoekt. Otherwise it uses the [symbols service](/code-search/types/symbol) @@ -148,3 +148,9 @@ This file picker is useful when comparing branches with thousands of changed fil - When viewing a file or directory, press the `y` key to expand the URL to its canonical form (with the full 40-character Git commit SHA). - To share a link to multi-line range in a file, click on the starting line number and shift-click on the ending line number (in the left-hand gutter). + +Test links for the check-links workflow (will be reverted): [site config](https://sourcegraph.com/docs/admin/config/site-config), [search](//www.sourcegraph.com/docs/code-search/), [moved page](http://docs.sourcegraph.com/admin/http_https_configuration#sourcegraph-via-docker-compose-caddy-2), [dead external](https://github.com/sourcegraph/docs/blob/main/this-file-does-not-exist.md), [live external](https://github.com/sourcegraph/docs). + +More test links, none of which the check can fix for you: [missing page](/code-search/no-such-page), [missing heading](/code-search/features#no-such-heading), [wrong case](/Code-Search/queries). + +Test paragraph for the spell check workflow (will be reverted): Sourcegraph indexs every repositry acros your organization, so seach results are alwasy fresh. It keeps compatability with the databse layer, and the exector runs each batch spec in its own contiainer. diff --git a/src/data/redirects.ts b/src/data/redirects.ts index c1a548241..2652334e0 100644 --- a/src/data/redirects.ts +++ b/src/data/redirects.ts @@ -1,6 +1,22 @@ import {TECHNICAL_CHANGELOG_RSS_URL} from './constants'; const redirectsData = [ + { + source: "/docs/old-prefixed", + destination: "/code-ownership" + }, + { + source: "/old-chain", + destination: "/admin/http_https_configuration" + }, + { + source: "/old-ownership", + destination: "/code-ownershp" + }, + { + source: "/old-ownership#anchor", + destination: "/code-ownership" + }, { source: '/integration/img/disable_extension.png', destination: '/integration/img/disable-extension.png' @@ -5896,6 +5912,10 @@ const redirectsData = [ source: '/code-search/how-to/create-search-context-graphql', destination: '/api' }, + { + source: "/admin/http_https_configuration", + destination: "/self-hosted/http-https-configuration" + } ]; const updatedRedirectsData = redirectsData.map(redirect => {