From 1675dcf6d75779a3ce98e241f40b65ae6c0b7bc5 Mon Sep 17 00:00:00 2001 From: joehart2001 Date: Sun, 3 May 2026 19:00:54 +0100 Subject: [PATCH 1/6] add ai for science index --- topics/ai-for-science/index.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 topics/ai-for-science/index.md diff --git a/topics/ai-for-science/index.md b/topics/ai-for-science/index.md new file mode 100644 index 000000000000..c44c56077cb9 --- /dev/null +++ b/topics/ai-for-science/index.md @@ -0,0 +1,10 @@ +--- +aliases: ai4science, ml-for-science, artificial-intelligence-for-science +display_name: AI for science +related: machine-learning, deep-learning, simulation, bioinformatics, chemistry, physics, data-science +short_description: AI for science applies machine learning and artificial intelligence to accelerate scientific discovery across disciplines. +topic: ai-for-science +--- +AI for science is the application of machine learning and artificial intelligence methods to accelerate research and discovery across scientific domains. It encompasses work in protein structure prediction, climate modeling, drug discovery, materials design, and particle physics, among others. + +Rather than replacing traditional scientific methods, AI for science augments them by learning patterns from experimental and simulation data to generate hypotheses, design experiments, and build fast surrogate models. Landmark examples include AlphaFold for protein structure prediction, GraphCast for weather forecasting, and FermiNet for quantum chemistry. From 0741065e935091173fca340cb5ee36759d65c268 Mon Sep 17 00:00:00 2001 From: joehart2001 Date: Sun, 3 May 2026 19:02:34 +0100 Subject: [PATCH 2/6] add ai as related --- topics/ai-for-science/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topics/ai-for-science/index.md b/topics/ai-for-science/index.md index c44c56077cb9..84bccaa59778 100644 --- a/topics/ai-for-science/index.md +++ b/topics/ai-for-science/index.md @@ -1,7 +1,7 @@ --- aliases: ai4science, ml-for-science, artificial-intelligence-for-science display_name: AI for science -related: machine-learning, deep-learning, simulation, bioinformatics, chemistry, physics, data-science +related: machine-learning, deep-learning, simulation, bioinformatics, chemistry, physics, data-science, ai short_description: AI for science applies machine learning and artificial intelligence to accelerate scientific discovery across disciplines. topic: ai-for-science --- From eb82a3002bdc1be63e5a29f6ad84718df7448e80 Mon Sep 17 00:00:00 2001 From: Justin Kenyon Date: Mon, 4 May 2026 09:53:12 -0400 Subject: [PATCH 3/6] ci: add Explore PR Triage Commenter workflow Posts a sticky maintainer-facing comment on PRs that touch topic or collection pages. For topic PRs: repo count for the topic. For collection PRs: per-item stars, last push, owner type, and a self-submission flag. Edit-in-place via marker comment, so synchronize/reopen events update the same comment instead of stacking. Reads PR head content via repos.getContent at pr.head.sha rather than checking out the fork (avoids the issues addressed in #5094 by sidestepping checkout entirely). --- .../workflows/explore-triage-commenter.yml | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 .github/workflows/explore-triage-commenter.yml diff --git a/.github/workflows/explore-triage-commenter.yml b/.github/workflows/explore-triage-commenter.yml new file mode 100644 index 000000000000..1013e4ab0a50 --- /dev/null +++ b/.github/workflows/explore-triage-commenter.yml @@ -0,0 +1,207 @@ +name: Explore PR Triage Commenter + +# Posts a sticky comment on PRs that touch topic or collection pages, +# surfacing the facts maintainers normally look up by hand: +# - topics: repo count for the topic +# - collections: per-item stars, last push, owner type, plus a flag if +# the PR author looks like one of the item owners (self-submission) +# +# Edit-in-place: subsequent runs (synchronize, reopen) update the same +# comment instead of posting a new one. Marker: + +on: + pull_request_target: + types: [opened, synchronize, reopened] + paths: + - 'topics/**' + - 'collections/**' + +permissions: + contents: read + pull-requests: write + +jobs: + triage: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v9 + env: + MARKER: '' + with: + script: | + const marker = process.env.MARKER; + const pr = context.payload.pull_request; + const prNumber = pr.number; + const prAuthor = pr.user.login.toLowerCase(); + const headSha = pr.head.sha; + const baseOwner = context.repo.owner; + const baseRepo = context.repo.repo; + + // List files in the PR (paginated). + const files = await github.paginate(github.rest.pulls.listFiles, { + owner: baseOwner, + repo: baseRepo, + pull_number: prNumber, + per_page: 100, + }); + + // Detect topic and collection slugs touched. + // Skip removed files; only validate slug shape we'd ever expect on disk. + const SLUG = /^[a-z0-9](?:[a-z0-9-]{0,80}[a-z0-9])?$/i; + const topics = new Set(); + const collections = new Set(); + for (const f of files) { + if (f.status === 'removed') continue; + const m = f.filename.match(/^(topics|collections)\/([^\/]+)\//); + if (!m) continue; + const slug = m[2]; + if (!SLUG.test(slug)) continue; + if (m[1] === 'topics') topics.add(slug); + else collections.add(slug); + } + + if (topics.size === 0 && collections.size === 0) { + core.info('No topic or collection changes detected; nothing to do.'); + return; + } + + const sections = []; + + // ---- Topic section ---- + if (topics.size > 0) { + const lines = ['### Topics', '']; + for (const slug of topics) { + let count = null; + try { + const res = await github.rest.search.repos({ + q: `topic:${slug}`, + per_page: 1, + }); + count = res.data.total_count; + } catch (err) { + core.warning(`Search failed for topic '${slug}': ${err.message}`); + } + const url = `https://github.com/topics/${encodeURIComponent(slug)}`; + if (count == null) { + lines.push(`- **${slug}** — [topic page](${url}) _(repo count lookup failed)_`); + } else { + lines.push(`- **${slug}** — ${count.toLocaleString()} repositories — [topic page](${url})`); + } + } + sections.push(lines.join('\n')); + } + + // ---- Collection section ---- + if (collections.size > 0) { + for (const slug of collections) { + const lines = [`### Collection \`${slug}\``, '']; + + // Read collection's index.md at the PR head SHA. + // PR commits from forks are mirrored into the base repo's network, + // so we can fetch from the base repo with the head SHA — simpler + // and avoids any cross-repo token concerns. + let content; + try { + const res = await github.rest.repos.getContent({ + owner: baseOwner, + repo: baseRepo, + path: `collections/${slug}/index.md`, + ref: headSha, + }); + content = Buffer.from(res.data.content, 'base64').toString('utf8'); + } catch (err) { + lines.push(`_Could not read \`collections/${slug}/index.md\` at PR head (\`${err.status || 'error'}\`)._`); + sections.push(lines.join('\n')); + continue; + } + + const items = parseCollectionItems(content); + if (items.length === 0) { + lines.push('_No `items:` list found in frontmatter._'); + sections.push(lines.join('\n')); + continue; + } + + lines.push('| Item | Stars | Last push | Owner type | Notes |'); + lines.push('| --- | ---: | --- | --- | --- |'); + + for (const item of items) { + if (!/^[\w.-]+\/[\w.-]+$/.test(item)) { + lines.push(`| \`${item}\` | – | – | – | invalid format |`); + continue; + } + const [owner, repo] = item.split('/'); + try { + const r = await github.rest.repos.get({ owner, repo }); + const stars = r.data.stargazers_count.toLocaleString(); + const pushed = r.data.pushed_at ? r.data.pushed_at.slice(0, 10) : '–'; + const ownerType = r.data.owner.type; + const notes = []; + if (owner.toLowerCase() === prAuthor) notes.push('⚠️ possible self-submission'); + if (r.data.archived) notes.push('archived'); + if (r.data.disabled) notes.push('disabled'); + lines.push(`| [\`${item}\`](https://github.com/${item}) | ${stars} | ${pushed} | ${ownerType} | ${notes.join(', ') || '–'} |`); + } catch (err) { + const note = err.status === 404 ? 'not found' : `error (${err.status || '?'})`; + lines.push(`| \`${item}\` | – | – | – | ${note} |`); + } + } + lines.push(''); + sections.push(lines.join('\n')); + } + } + + const body = [ + marker, + '', + '', + '## Maintainer triage', + '', + ...sections, + ].join('\n'); + + // Edit-in-place via marker. + const comments = await github.paginate(github.rest.issues.listComments, { + owner: baseOwner, + repo: baseRepo, + issue_number: prNumber, + per_page: 100, + }); + const existing = comments.find(c => c.body && c.body.startsWith(marker)); + + if (existing) { + await github.rest.issues.updateComment({ + owner: baseOwner, + repo: baseRepo, + comment_id: existing.id, + body, + }); + core.info(`Updated comment ${existing.id}`); + } else { + await github.rest.issues.createComment({ + owner: baseOwner, + repo: baseRepo, + issue_number: prNumber, + body, + }); + core.info('Created new comment'); + } + + function parseCollectionItems(text) { + // Frontmatter between leading --- lines. + const fmMatch = text.match(/^---\n([\s\S]*?)\n---/); + if (!fmMatch) return []; + const lines = fmMatch[1].split('\n'); + const items = []; + let inItems = false; + for (const line of lines) { + if (/^items:\s*$/.test(line)) { inItems = true; continue; } + // Next top-level key ends the items block. + if (inItems && /^[a-zA-Z_]\w*\s*:/.test(line)) break; + if (inItems) { + const m = line.match(/^\s*-\s*([^\s#]+)/); + if (m) items.push(m[1]); + } + } + return items; + } From dd6bb154ed8741612c2a365db7a6ce37e8d7661a Mon Sep 17 00:00:00 2001 From: Justin Kenyon Date: Mon, 4 May 2026 10:08:39 -0400 Subject: [PATCH 4/6] ci: remove disabled topic-commenter.yml Replaced by .github/workflows/explore-triage-commenter.yml in the preceding commit. The original commenter has been disabled (paths gated to ENOSUCHPATH) since it failed on fork PRs due to permissions on the pull_request trigger; the replacement uses pull_request_target and reads PR head content via the API instead of checking out, which sidesteps the issue entirely. --- .github/workflows/topic-commenter.yml | 78 --------------------------- 1 file changed, 78 deletions(-) delete mode 100644 .github/workflows/topic-commenter.yml diff --git a/.github/workflows/topic-commenter.yml b/.github/workflows/topic-commenter.yml deleted file mode 100644 index f9e30dca9ab6..000000000000 --- a/.github/workflows/topic-commenter.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Topic PR Commenter - -# this workflow is failing due to permissions problems -# until we can fix it with a better bot, i'll preserve -# the code but make it so it never matches a real path -on: - pull_request: - paths: - - 'ENOSUCHPATH' - -permissions: - contents: read - pull-requests: write - -jobs: - comment: - runs-on: ubuntu-latest - - steps: - - name: Comment on PR with topic info - uses: actions/github-script@v9 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - script: | - // Get the PR number from the event payload - const prNumber = context.payload.pull_request.number; - - // List the files changed in the PR - const { data: files } = await github.rest.pulls.listFiles({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber, - }); - - // Extract topics from any file changed in the "topics/" folder. - // Assumes the file name (e.g. "python.md") indicates the topic "python" - const topics = []; - for (const file of files) { - if (file.filename.startsWith('topics/')) { - const parts = file.filename.split('/'); - const topicName = parts[parts.length - 2]; - topics.push(topicName); - } - } - - if (topics.length === 0) { - console.log('No topics found in changed files.'); - return; - } - - // Remove duplicate topic names (in case multiple files reference the same topic) - const uniqueTopics = [...new Set(topics)]; - - // Prepare the body of the comment - let commentBody = '## Topic Information\n\n'; - - for (const topic of uniqueTopics) { - // Query the GitHub Search API for repositories with the topic. - // Note: The Search API endpoint returns a JSON with a total_count field. - const searchResponse = await github.request('GET /search/repositories', { - q: `topic:${topic}` - }); - const repoCount = searchResponse.data.total_count; - - // Append topic details to the comment body - commentBody += `### ${topic}\n`; - commentBody += `- [Topic Page](https://github.com/topics/${topic})\n`; - commentBody += `- Repositories: ${repoCount}\n\n`; - } - - // Post the comment on the PR - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body: commentBody - }); From eb1b6c757bd00f8a8c682fcb1bae4ced28fa5ecf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 14:16:48 +0000 Subject: [PATCH 5/6] Add concurrency group to prevent duplicate sticky comments Agent-Logs-Url: https://github.com/github/explore/sessions/1439af4e-c097-4fc9-8a1f-22c96f6de461 Co-authored-by: kenyonj <4008677+kenyonj@users.noreply.github.com> --- .github/workflows/explore-triage-commenter.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/explore-triage-commenter.yml b/.github/workflows/explore-triage-commenter.yml index 1013e4ab0a50..8b62ba26c226 100644 --- a/.github/workflows/explore-triage-commenter.yml +++ b/.github/workflows/explore-triage-commenter.yml @@ -16,6 +16,10 @@ on: - 'topics/**' - 'collections/**' +concurrency: + group: explore-triage-commenter-${{ github.event.pull_request.number }} + cancel-in-progress: true + permissions: contents: read pull-requests: write From c6f2ac51096f5e9959e81163885fa87c4482c612 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 14:19:27 +0000 Subject: [PATCH 6/6] Sanitize item in invalid format branch to prevent Markdown injection Agent-Logs-Url: https://github.com/github/explore/sessions/f0f46334-8894-40b6-bcc2-87dba102c83d Co-authored-by: kenyonj <4008677+kenyonj@users.noreply.github.com> --- .github/workflows/explore-triage-commenter.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/explore-triage-commenter.yml b/.github/workflows/explore-triage-commenter.yml index 8b62ba26c226..d1215fdc26c6 100644 --- a/.github/workflows/explore-triage-commenter.yml +++ b/.github/workflows/explore-triage-commenter.yml @@ -131,7 +131,8 @@ jobs: for (const item of items) { if (!/^[\w.-]+\/[\w.-]+$/.test(item)) { - lines.push(`| \`${item}\` | – | – | – | invalid format |`); + const safeItem = item.replace(/`/g, "'").replace(/\\/g, '\\\\').replace(/\|/g, '\\|'); + lines.push(`| \`${safeItem}\` | – | – | – | invalid format |`); continue; } const [owner, repo] = item.split('/');