mirror of
https://github.com/anthropics/claude-code.git
synced 2026-02-19 04:27:33 -08:00
Compare commits
91 Commits
boris/nvko
...
dalton/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a2442227a | ||
|
|
f5dd15ac7e | ||
|
|
c0a28eede9 | ||
|
|
81f65bea8a | ||
|
|
dc8f77c7ef | ||
|
|
786259a00c | ||
|
|
8e679e75f7 | ||
|
|
87560460bc | ||
|
|
07e13937b2 | ||
|
|
55988caadf | ||
|
|
8beb9b0c76 | ||
|
|
4607d83fa8 | ||
|
|
70d5361861 | ||
|
|
c792b7d4c7 | ||
|
|
5f52517c0b | ||
|
|
cc09d58e8e | ||
|
|
d820a4dbd7 | ||
|
|
eb0e43457b | ||
|
|
b417bfc532 | ||
|
|
2b46e47360 | ||
|
|
c58a7da257 | ||
|
|
239aeb55ee | ||
|
|
f4e707fdcc | ||
|
|
6d79459b16 | ||
|
|
d2f88820c9 | ||
|
|
a3620cdd0b | ||
|
|
da6d2f715e | ||
|
|
f200ab3c5c | ||
|
|
fa29b8f9c0 | ||
|
|
2558619a83 | ||
|
|
80ceacaa78 | ||
|
|
20ba9a34a5 | ||
|
|
4e63568abd | ||
|
|
5d0b81ae41 | ||
|
|
b1751f2e86 | ||
|
|
eb48d5e4a8 | ||
|
|
fc8c10995f | ||
|
|
01fb7af5b3 | ||
|
|
10a1f7dab9 | ||
|
|
afb0fc9156 | ||
|
|
370a97d939 | ||
|
|
f54569efd2 | ||
|
|
d8cf5a874c | ||
|
|
e499db6e9e | ||
|
|
5300e12135 | ||
|
|
4a04589002 | ||
|
|
04cace9ec0 | ||
|
|
2dbf1e97a0 | ||
|
|
0662600e93 | ||
|
|
27d2c6fdcf | ||
|
|
dd53f86325 | ||
|
|
c40c658e1f | ||
|
|
e05411140d | ||
|
|
ce5b9164fa | ||
|
|
5af0b38a92 | ||
|
|
22946869b2 | ||
|
|
1579216fc7 | ||
|
|
f0042afb3b | ||
|
|
6059607354 | ||
|
|
478f63be73 | ||
|
|
a7edbdc9e7 | ||
|
|
399a7dcf2f | ||
|
|
9ced2a3470 | ||
|
|
d7aa61e6f1 | ||
|
|
072856dd5b | ||
|
|
1cc90e9b78 | ||
|
|
483e0e892f | ||
|
|
5248fa06bc | ||
|
|
eb48a37a48 | ||
|
|
fdb7efe6f1 | ||
|
|
7060992aa5 | ||
|
|
b7116c78d8 | ||
|
|
3fd750e3cb | ||
|
|
3262b673c3 | ||
|
|
369bd9b6d7 | ||
|
|
611956def4 | ||
|
|
a9f1fefbe9 | ||
|
|
3d5ef4e8c0 | ||
|
|
d967bbbe30 | ||
|
|
5faa082d6e | ||
|
|
60124df21f | ||
|
|
1aaffa4e6d | ||
|
|
f5b24d5480 | ||
|
|
07e6bec5ff | ||
|
|
cf8c6fdf2d | ||
|
|
d720bf7aba | ||
|
|
dde41f6225 | ||
|
|
78e0785950 | ||
|
|
b3658880e5 | ||
|
|
9be8e07e92 | ||
|
|
8b2cbe3f86 |
19
.claude/commands/commit-push-pr.md
Normal file
19
.claude/commands/commit-push-pr.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
allowed-tools: Bash(git checkout --branch:*), Bash(git add:*), Bash(git status:*), Bash(git push:*), Bash(git commit:*), Bash(gh pr create:*)
|
||||
description: Commit, push, and open a PR
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
- Current git status: !`git status`
|
||||
- Current git diff (staged and unstaged changes): !`git diff HEAD`
|
||||
- Current branch: !`git branch --show-current`
|
||||
|
||||
## Your task
|
||||
|
||||
Based on the above changes:
|
||||
1. Create a new branch if on main
|
||||
2. Create a single commit with an appropriate message
|
||||
3. Push the branch to origin
|
||||
4. Create a pull request using `gh pr create`
|
||||
5. You have the capability to call multiple tools in a single response. You MUST do all of the above in a single message. Do not use any other tools or do anything else. Do not send any other text or messages besides these tool calls.
|
||||
@@ -28,6 +28,11 @@ Found 3 possible duplicate issues:
|
||||
2. <link to issue>
|
||||
3. <link to issue>
|
||||
|
||||
If your issue is a duplicate, please close it and 👍 the existing issue instead.
|
||||
This issue will be automatically closed as a duplicate in 3 days.
|
||||
|
||||
## 🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
- If your issue is a duplicate, please close it and 👍 the existing issue instead
|
||||
- To prevent auto-closure, add a comment or 👎 this comment
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
---
|
||||
|
||||
@@ -65,8 +65,8 @@ ENV PATH=$PATH:/usr/local/share/npm-global/bin
|
||||
ENV SHELL=/bin/zsh
|
||||
|
||||
# Set the default editor and visual
|
||||
ENV EDITOR nano
|
||||
ENV VISUAL nano
|
||||
ENV EDITOR=nano
|
||||
ENV VISUAL=nano
|
||||
|
||||
# Default powerline10k theme
|
||||
ARG ZSH_IN_DOCKER_VERSION=1.2.0
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"anthropic.claude-code",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"eamodio.gitlens"
|
||||
@@ -51,5 +52,6 @@
|
||||
},
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated",
|
||||
"workspaceFolder": "/workspace",
|
||||
"postCreateCommand": "sudo /usr/local/bin/init-firewall.sh"
|
||||
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh",
|
||||
"waitFor": "postStartCommand"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
set -euo pipefail # Exit on error, undefined vars, and pipeline failures
|
||||
IFS=$'\n\t' # Stricter word splitting
|
||||
|
||||
# 1. Extract Docker DNS info BEFORE any flushing
|
||||
DOCKER_DNS_RULES=$(iptables-save -t nat | grep "127\.0\.0\.11" || true)
|
||||
|
||||
# Flush existing rules and delete existing ipsets
|
||||
iptables -F
|
||||
iptables -X
|
||||
@@ -11,6 +14,16 @@ iptables -t mangle -F
|
||||
iptables -t mangle -X
|
||||
ipset destroy allowed-domains 2>/dev/null || true
|
||||
|
||||
# 2. Selectively restore ONLY internal Docker DNS resolution
|
||||
if [ -n "$DOCKER_DNS_RULES" ]; then
|
||||
echo "Restoring Docker DNS rules..."
|
||||
iptables -t nat -N DOCKER_OUTPUT 2>/dev/null || true
|
||||
iptables -t nat -N DOCKER_POSTROUTING 2>/dev/null || true
|
||||
echo "$DOCKER_DNS_RULES" | xargs -L 1 iptables -t nat
|
||||
else
|
||||
echo "No Docker DNS rules to restore"
|
||||
fi
|
||||
|
||||
# First allow DNS and localhost before any restrictions
|
||||
# Allow outbound DNS
|
||||
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
||||
@@ -56,9 +69,12 @@ for domain in \
|
||||
"api.anthropic.com" \
|
||||
"sentry.io" \
|
||||
"statsig.anthropic.com" \
|
||||
"statsig.com"; do
|
||||
"statsig.com" \
|
||||
"marketplace.visualstudio.com" \
|
||||
"vscode.blob.core.windows.net" \
|
||||
"update.code.visualstudio.com"; do
|
||||
echo "Resolving $domain..."
|
||||
ips=$(dig +short A "$domain")
|
||||
ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}')
|
||||
if [ -z "$ips" ]; then
|
||||
echo "ERROR: Failed to resolve $domain"
|
||||
exit 1
|
||||
@@ -100,6 +116,9 @@ iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
# Then allow only specific outbound traffic to allowed domains
|
||||
iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT
|
||||
|
||||
# Explicitly REJECT all other outbound traffic for immediate feedback
|
||||
iptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited
|
||||
|
||||
echo "Firewall configuration complete"
|
||||
echo "Verifying firewall rules..."
|
||||
if curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then
|
||||
|
||||
31
.github/workflows/auto-close-duplicates.yml
vendored
Normal file
31
.github/workflows/auto-close-duplicates.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Auto-close duplicate issues
|
||||
description: Auto-closes issues that are duplicates of existing issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 9 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
auto-close-duplicates:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Auto-close duplicate issues
|
||||
run: bun run scripts/auto-close-duplicates.ts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
|
||||
GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }}
|
||||
STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}
|
||||
44
.github/workflows/backfill-duplicate-comments.yml
vendored
Normal file
44
.github/workflows/backfill-duplicate-comments.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Backfill Duplicate Comments
|
||||
description: Triggers duplicate detection for old issues that don't have duplicate comments
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
days_back:
|
||||
description: 'How many days back to look for old issues'
|
||||
required: false
|
||||
default: '90'
|
||||
type: string
|
||||
dry_run:
|
||||
description: 'Dry run mode (true to only log what would be done)'
|
||||
required: false
|
||||
default: 'true'
|
||||
type: choice
|
||||
options:
|
||||
- 'true'
|
||||
- 'false'
|
||||
|
||||
jobs:
|
||||
backfill-duplicate-comments:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
actions: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Backfill duplicate comments
|
||||
run: bun run scripts/backfill-duplicate-comments.ts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DAYS_BACK: ${{ inputs.days_back }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
59
.github/workflows/claude-dedupe-issues.yml
vendored
59
.github/workflows/claude-dedupe-issues.yml
vendored
@@ -3,9 +3,15 @@ description: Automatically dedupe GitHub issues using Claude Code
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_number:
|
||||
description: 'Issue number to process for duplicate detection'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
triage-issue:
|
||||
claude-dedupe-issues:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
@@ -19,7 +25,56 @@ jobs:
|
||||
- name: Run Claude Code slash command
|
||||
uses: anthropics/claude-code-base-action@beta
|
||||
with:
|
||||
prompt: "/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number }}"
|
||||
prompt: "/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}"
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
claude_env: |
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Log duplicate comment event to Statsig
|
||||
if: always()
|
||||
env:
|
||||
STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}
|
||||
run: |
|
||||
ISSUE_NUMBER=${{ github.event.issue.number || inputs.issue_number }}
|
||||
REPO=${{ github.repository }}
|
||||
|
||||
if [ -z "$STATSIG_API_KEY" ]; then
|
||||
echo "STATSIG_API_KEY not found, skipping Statsig logging"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Prepare the event payload
|
||||
EVENT_PAYLOAD=$(jq -n \
|
||||
--arg issue_number "$ISSUE_NUMBER" \
|
||||
--arg repo "$REPO" \
|
||||
--arg triggered_by "${{ github.event_name }}" \
|
||||
'{
|
||||
events: [{
|
||||
eventName: "github_duplicate_comment_added",
|
||||
value: 1,
|
||||
metadata: {
|
||||
repository: $repo,
|
||||
issue_number: ($issue_number | tonumber),
|
||||
triggered_by: $triggered_by,
|
||||
workflow_run_id: "${{ github.run_id }}"
|
||||
},
|
||||
time: (now | floor | tostring)
|
||||
}]
|
||||
}')
|
||||
|
||||
# Send to Statsig API
|
||||
echo "Logging duplicate comment event to Statsig for issue #${ISSUE_NUMBER}"
|
||||
|
||||
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST https://events.statsigapi.net/v1/log_event \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "STATSIG-API-KEY: ${STATSIG_API_KEY}" \
|
||||
-d "$EVENT_PAYLOAD")
|
||||
|
||||
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
||||
BODY=$(echo "$RESPONSE" | head -n-1)
|
||||
|
||||
if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 202 ]; then
|
||||
echo "Successfully logged duplicate comment event for issue #${ISSUE_NUMBER}"
|
||||
else
|
||||
echo "Failed to log duplicate comment event for issue #${ISSUE_NUMBER}. HTTP ${HTTP_CODE}: ${BODY}"
|
||||
fi
|
||||
|
||||
59
.github/workflows/issue-notify.yml
vendored
Normal file
59
.github/workflows/issue-notify.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: Issue Notification
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: read
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Send notification
|
||||
env:
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
ISSUE_TITLE: ${{ github.event.issue.title }}
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
ISSUE_AUTHOR: ${{ github.event.issue.user.login }}
|
||||
ISSUE_LABELS: ${{ toJSON(github.event.issue.labels) }}
|
||||
ISSUE_URL: ${{ github.event.issue.html_url }}
|
||||
ISSUE_CREATED_AT: ${{ github.event.issue.created_at }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
DISPATCH_TOKEN: ${{ secrets.CROSS_REPO_TOKEN_CLAUDE_CODE }}
|
||||
DISPATCH_ENDPOINT: ${{ secrets.DISPATCH_ENDPOINT }}
|
||||
run: |
|
||||
if [ -z "$DISPATCH_TOKEN" ] || [ -z "$DISPATCH_ENDPOINT" ]; then
|
||||
echo "Dispatch configuration not complete, skipping notification"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Prepare payload with issue metadata
|
||||
PAYLOAD=$(cat <<EOF
|
||||
{
|
||||
"event_type": "issue_event",
|
||||
"client_payload": {
|
||||
"source_repo": "$REPOSITORY",
|
||||
"issue_number": "$ISSUE_NUMBER",
|
||||
"issue_title": $(echo "$ISSUE_TITLE" | jq -Rs .),
|
||||
"issue_body": $(echo "$ISSUE_BODY" | jq -Rs .),
|
||||
"issue_author": "$ISSUE_AUTHOR",
|
||||
"issue_labels": $ISSUE_LABELS,
|
||||
"issue_url": "$ISSUE_URL",
|
||||
"issue_created_at": "$ISSUE_CREATED_AT",
|
||||
"timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# Send notification to configured endpoint
|
||||
curl -X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: token $DISPATCH_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$DISPATCH_ENDPOINT" \
|
||||
-d "$PAYLOAD" \
|
||||
--fail-with-body || echo "Notification sent"
|
||||
84
.github/workflows/lock-closed-issues.yml
vendored
84
.github/workflows/lock-closed-issues.yml
vendored
@@ -13,16 +13,80 @@ concurrency:
|
||||
group: lock-threads
|
||||
|
||||
jobs:
|
||||
lock-threads:
|
||||
lock-closed-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
|
||||
- name: Lock closed issues after 7 days of inactivity
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
issue-inactive-days: "7"
|
||||
process-only: "issues"
|
||||
log-output: true
|
||||
issue-comment: >
|
||||
This issue has been automatically locked since it was
|
||||
closed and has not had any activity for 7 days.
|
||||
If you're experiencing a similar issue, please file a new issue
|
||||
and reference this one if it's relevant.
|
||||
script: |
|
||||
const sevenDaysAgo = new Date();
|
||||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
||||
|
||||
const lockComment = `This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.`;
|
||||
|
||||
let page = 1;
|
||||
let hasMore = true;
|
||||
let totalLocked = 0;
|
||||
|
||||
while (hasMore) {
|
||||
// Get closed issues (pagination)
|
||||
const { data: issues } = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'closed',
|
||||
sort: 'updated',
|
||||
direction: 'asc',
|
||||
per_page: 100,
|
||||
page: page
|
||||
});
|
||||
|
||||
if (issues.length === 0) {
|
||||
hasMore = false;
|
||||
break;
|
||||
}
|
||||
|
||||
for (const issue of issues) {
|
||||
// Skip if already locked
|
||||
if (issue.locked) continue;
|
||||
|
||||
// Skip pull requests
|
||||
if (issue.pull_request) continue;
|
||||
|
||||
// Check if updated more than 7 days ago
|
||||
const updatedAt = new Date(issue.updated_at);
|
||||
if (updatedAt > sevenDaysAgo) {
|
||||
// Since issues are sorted by updated_at ascending,
|
||||
// once we hit a recent issue, all remaining will be recent too
|
||||
hasMore = false;
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
// Add comment before locking
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: lockComment
|
||||
});
|
||||
|
||||
// Lock the issue
|
||||
await github.rest.issues.lock({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
lock_reason: 'resolved'
|
||||
});
|
||||
|
||||
totalLocked++;
|
||||
console.log(`Locked issue #${issue.number}: ${issue.title}`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to lock issue #${issue.number}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
page++;
|
||||
}
|
||||
|
||||
console.log(`Total issues locked: ${totalLocked}`);
|
||||
|
||||
40
.github/workflows/log-issue-events.yml
vendored
Normal file
40
.github/workflows/log-issue-events.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Log Issue Events to Statsig
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, closed]
|
||||
|
||||
jobs:
|
||||
log-to-statsig:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: read
|
||||
steps:
|
||||
- name: Log issue creation to Statsig
|
||||
env:
|
||||
STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
REPO: ${{ github.repository }}
|
||||
ISSUE_TITLE: ${{ github.event.issue.title }}
|
||||
AUTHOR: ${{ github.event.issue.user.login }}
|
||||
CREATED_AT: ${{ github.event.issue.created_at }}
|
||||
run: |
|
||||
# All values are now safely passed via environment variables
|
||||
# No direct templating in the shell script to prevent injection attacks
|
||||
|
||||
curl -X POST "https://events.statsigapi.net/v1/log_event" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "statsig-api-key: $STATSIG_API_KEY" \
|
||||
-d '{
|
||||
"events": [{
|
||||
"eventName": "github_issue_created",
|
||||
"metadata": {
|
||||
"issue_number": "'"$ISSUE_NUMBER"'",
|
||||
"repository": "'"$REPO"'",
|
||||
"title": "'"$(echo "$ISSUE_TITLE" | sed "s/\"/\\\\\"/g")"'",
|
||||
"author": "'"$AUTHOR"'",
|
||||
"created_at": "'"$CREATED_AT"'"
|
||||
},
|
||||
"time": '"$(date +%s)000"'
|
||||
}]
|
||||
}'
|
||||
129
CHANGELOG.md
129
CHANGELOG.md
@@ -1,5 +1,134 @@
|
||||
# Changelog
|
||||
|
||||
## 1.0.97
|
||||
|
||||
- Settings: /doctor now validates permission rule syntax and suggests corrections
|
||||
|
||||
## 1.0.94
|
||||
|
||||
- Vertex: add support for global endpoints for supported models
|
||||
- /memory command now allows direct editing of all imported memory files
|
||||
- SDK: Add custom tools as callbacks
|
||||
- Added /todos command to list current todo items
|
||||
|
||||
## 1.0.93
|
||||
|
||||
- Windows: Add alt + v shortcut for pasting images from clipboard
|
||||
- Support NO_PROXY environment variable to bypass proxy for specified hostnames and IPs
|
||||
|
||||
## 1.0.90
|
||||
|
||||
- Settings file changes take effect immediately - no restart required
|
||||
|
||||
## 1.0.88
|
||||
|
||||
- Fixed issue causing "OAuth authentication is currently not supported"
|
||||
- Status line input now includes `exceeds_200k_tokens`
|
||||
- Fixed incorrect usage tracking in /cost.
|
||||
- Introduced `ANTHROPIC_DEFAULT_SONNET_MODEL` and `ANTHROPIC_DEFAULT_OPUS_MODEL` for controlling model aliases opusplan, opus, and sonnet.
|
||||
- Bedrock: Updated default Sonnet model to Sonnet 4
|
||||
|
||||
## 1.0.86
|
||||
|
||||
- Added /context to help users self-serve debug context issues
|
||||
- SDK: Added UUID support for all SDK messages
|
||||
- SDK: Added `--replay-user-messages` to replay user messages back to stdout
|
||||
|
||||
## 1.0.85
|
||||
|
||||
- Status line input now includes session cost info
|
||||
- Hooks: Introduced SessionEnd hook
|
||||
|
||||
## 1.0.84
|
||||
|
||||
- Fix tool_use/tool_result id mismatch error when network is unstable
|
||||
- Fix Claude sometimes ignoring real-time steering when wrapping up a task
|
||||
- @-mention: Add ~/.claude/\* files to suggestions for easier agent, output style, and slash command editing
|
||||
- Use built-in ripgrep by default; to opt out of this behavior, set USE_BUILTIN_RIPGREP=0
|
||||
|
||||
## 1.0.83
|
||||
|
||||
- @-mention: Support files with spaces in path
|
||||
- New shimmering spinner
|
||||
|
||||
## 1.0.82
|
||||
|
||||
- SDK: Add request cancellation support
|
||||
- SDK: New additionalDirectories option to search custom paths, improved slash command processing
|
||||
- Settings: Validation prevents invalid fields in .claude/settings.json files
|
||||
- MCP: Improve tool name consistency
|
||||
- Bash: Fix crash when Claude tries to automatically read large files
|
||||
|
||||
## 1.0.81
|
||||
|
||||
- Released output styles, including new built-in educational output styles "Explanatory" and "Learning". Docs: https://docs.anthropic.com/en/docs/claude-code/output-styles
|
||||
- Agents: Fix custom agent loading when agent files are unparsable
|
||||
|
||||
## 1.0.80
|
||||
|
||||
- UI improvements: Fix text contrast for custom subagent colors and spinner rendering issues
|
||||
|
||||
## 1.0.77
|
||||
|
||||
- Bash tool: Fix heredoc and multiline string escaping, improve stderr redirection handling
|
||||
- SDK: Add session support and permission denial tracking
|
||||
- Fix token limit errors in conversation summarization
|
||||
- Opus Plan Mode: New setting in `/model` to run Opus only in plan mode, Sonnet otherwise
|
||||
|
||||
## 1.0.73
|
||||
|
||||
- MCP: Support multiple config files with `--mcp-config file1.json file2.json`
|
||||
- MCP: Press Esc to cancel OAuth authentication flows
|
||||
- Bash: Improved command validation and reduced false security warnings
|
||||
- UI: Enhanced spinner animations and status line visual hierarchy
|
||||
- Linux: Added support for Alpine and musl-based distributions (requires separate ripgrep installation)
|
||||
|
||||
## 1.0.72
|
||||
|
||||
- Ask permissions: have Claude Code always ask for confirmation to use specific tools with /permissions
|
||||
|
||||
## 1.0.71
|
||||
|
||||
- Background commands: (Ctrl-b) to run any Bash command in the background so Claude can keep working (great for dev servers, tailing logs, etc.)
|
||||
- Customizable status line: add your terminal prompt to Claude Code with /statusline
|
||||
|
||||
## 1.0.70
|
||||
|
||||
- Performance: Optimized message rendering for better performance with large contexts
|
||||
- Windows: Fixed native file search, ripgrep, and subagent functionality
|
||||
- Added support for @-mentions in slash command arguments
|
||||
|
||||
## 1.0.69
|
||||
|
||||
- Upgraded Opus to version 4.1
|
||||
|
||||
## 1.0.68
|
||||
|
||||
- Fix incorrect model names being used for certain commands like `/pr-comments`
|
||||
- Windows: improve permissions checks for allow / deny tools and project trust. This may create a new project entry in `.claude.json` - manually merge the history field if desired.
|
||||
- Windows: improve sub-process spawning to eliminate "No such file or directory" when running commands like pnpm
|
||||
- Enhanced /doctor command with CLAUDE.md and MCP tool context for self-serve debugging
|
||||
- SDK: Added canUseTool callback support for tool confirmation
|
||||
- Added `disableAllHooks` setting
|
||||
- Improved file suggestions performance in large repos
|
||||
|
||||
## 1.0.65
|
||||
|
||||
- IDE: Fixed connection stability issues and error handling for diagnostics
|
||||
- Windows: Fixed shell environment setup for users without .bashrc files
|
||||
|
||||
## 1.0.64
|
||||
|
||||
- Agents: Added model customization support - you can now specify which model an agent should use
|
||||
- Agents: Fixed unintended access to the recursive agent tool
|
||||
- Hooks: Added systemMessage field to hook JSON output for displaying warnings and context
|
||||
- SDK: Fixed user input tracking across multi-turn conversations
|
||||
- Added hidden files to file search and @-mention suggestions
|
||||
|
||||
## 1.0.63
|
||||
|
||||
- Windows: Fixed file search, @agent mentions, and custom slash commands functionality
|
||||
|
||||
## 1.0.62
|
||||
|
||||
- Added @-mention support with typeahead for custom agents. @<your-custom-agent> to invoke it
|
||||
|
||||
@@ -24,15 +24,17 @@ npm install -g @anthropic-ai/claude-code
|
||||
|
||||
We welcome your feedback. Use the `/bug` command to report issues directly within Claude Code, or file a [GitHub issue](https://github.com/anthropics/claude-code/issues).
|
||||
|
||||
## Connect on Discord
|
||||
|
||||
Join the [Claude Developers Discord](https://anthropic.com/discord) to connect with other developers using Claude Code. Get help, share feedback, and discuss your projects with the community.
|
||||
|
||||
## Data collection, usage, and retention
|
||||
|
||||
When you use Claude Code, we collect feedback, which includes usage data (such as code acceptance or rejections), associated conversation data, and user feedback submitted via the `/bug` command.
|
||||
|
||||
### How we use your data
|
||||
|
||||
We may use feedback to improve our products and services, but we will not train generative models using your feedback from Claude Code. Given their potentially sensitive nature, we store user feedback transcripts for only 30 days.
|
||||
|
||||
If you choose to send us feedback about Claude Code, such as transcripts of your usage, Anthropic may use that feedback to debug related issues and improve Claude Code's functionality (e.g., to reduce the risk of similar bugs occurring in the future).
|
||||
See our [data usage policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
|
||||
|
||||
### Privacy safeguards
|
||||
|
||||
|
||||
@@ -40,14 +40,18 @@ Write-Host "Using backend: $($Backend)"
|
||||
# --- Prerequisite Check ---
|
||||
Write-Host "Checking for required commands..."
|
||||
try {
|
||||
Get-Command $Backend -ErrorAction Stop | Out-Null
|
||||
if (-not (Get-Command $Backend -ErrorAction SilentlyContinue)) {
|
||||
throw "Required command '$($Backend)' not found."
|
||||
}
|
||||
Write-Host "- $($Backend) command found."
|
||||
Get-Command devcontainer -ErrorAction Stop | Out-Null
|
||||
if (-not (Get-Command devcontainer -ErrorAction SilentlyContinue)) {
|
||||
throw "Required command 'devcontainer' not found."
|
||||
}
|
||||
Write-Host "- devcontainer command found."
|
||||
}
|
||||
catch {
|
||||
Write-Error "A required command is not installed or not in your PATH."
|
||||
Write-Error "Please ensure '$($_.Exception.Message.Split(':')[0])' and 'devcontainer' are installed and accessible."
|
||||
Write-Error "A required command is not installed or not in your PATH. $($_.Exception.Message)"
|
||||
Write-Error "Please ensure both '$Backend' and 'devcontainer' are installed and accessible in your system's PATH."
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
277
scripts/auto-close-duplicates.ts
Normal file
277
scripts/auto-close-duplicates.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
declare global {
|
||||
var process: {
|
||||
env: Record<string, string | undefined>;
|
||||
};
|
||||
}
|
||||
|
||||
interface GitHubIssue {
|
||||
number: number;
|
||||
title: string;
|
||||
user: { id: number };
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface GitHubComment {
|
||||
id: number;
|
||||
body: string;
|
||||
created_at: string;
|
||||
user: { type: string; id: number };
|
||||
}
|
||||
|
||||
interface GitHubReaction {
|
||||
user: { id: number };
|
||||
content: string;
|
||||
}
|
||||
|
||||
async function githubRequest<T>(endpoint: string, token: string, method: string = 'GET', body?: any): Promise<T> {
|
||||
const response = await fetch(`https://api.github.com${endpoint}`, {
|
||||
method,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
"User-Agent": "auto-close-duplicates-script",
|
||||
...(body && { "Content-Type": "application/json" }),
|
||||
},
|
||||
...(body && { body: JSON.stringify(body) }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`GitHub API request failed: ${response.status} ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
function extractDuplicateIssueNumber(commentBody: string): number | null {
|
||||
// Try to match #123 format first
|
||||
let match = commentBody.match(/#(\d+)/);
|
||||
if (match) {
|
||||
return parseInt(match[1], 10);
|
||||
}
|
||||
|
||||
// Try to match GitHub issue URL format: https://github.com/owner/repo/issues/123
|
||||
match = commentBody.match(/github\.com\/[^\/]+\/[^\/]+\/issues\/(\d+)/);
|
||||
if (match) {
|
||||
return parseInt(match[1], 10);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
async function closeIssueAsDuplicate(
|
||||
owner: string,
|
||||
repo: string,
|
||||
issueNumber: number,
|
||||
duplicateOfNumber: number,
|
||||
token: string
|
||||
): Promise<void> {
|
||||
await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues/${issueNumber}`,
|
||||
token,
|
||||
'PATCH',
|
||||
{
|
||||
state: 'closed',
|
||||
state_reason: 'duplicate',
|
||||
labels: ['duplicate']
|
||||
}
|
||||
);
|
||||
|
||||
await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
|
||||
token,
|
||||
'POST',
|
||||
{
|
||||
body: `This issue has been automatically closed as a duplicate of #${duplicateOfNumber}.
|
||||
|
||||
If this is incorrect, please re-open this issue or create a new one.
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)`
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
async function autoCloseDuplicates(): Promise<void> {
|
||||
console.log("[DEBUG] Starting auto-close duplicates script");
|
||||
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
if (!token) {
|
||||
throw new Error("GITHUB_TOKEN environment variable is required");
|
||||
}
|
||||
console.log("[DEBUG] GitHub token found");
|
||||
|
||||
const owner = process.env.GITHUB_REPOSITORY_OWNER || "anthropics";
|
||||
const repo = process.env.GITHUB_REPOSITORY_NAME || "claude-code";
|
||||
console.log(`[DEBUG] Repository: ${owner}/${repo}`);
|
||||
|
||||
const threeDaysAgo = new Date();
|
||||
threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
|
||||
console.log(
|
||||
`[DEBUG] Checking for duplicate comments older than: ${threeDaysAgo.toISOString()}`
|
||||
);
|
||||
|
||||
console.log("[DEBUG] Fetching open issues created more than 3 days ago...");
|
||||
const allIssues: GitHubIssue[] = [];
|
||||
let page = 1;
|
||||
const perPage = 100;
|
||||
|
||||
while (true) {
|
||||
const pageIssues: GitHubIssue[] = await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues?state=open&per_page=${perPage}&page=${page}`,
|
||||
token
|
||||
);
|
||||
|
||||
if (pageIssues.length === 0) break;
|
||||
|
||||
// Filter for issues created more than 3 days ago
|
||||
const oldEnoughIssues = pageIssues.filter(issue =>
|
||||
new Date(issue.created_at) <= threeDaysAgo
|
||||
);
|
||||
|
||||
allIssues.push(...oldEnoughIssues);
|
||||
page++;
|
||||
|
||||
// Safety limit to avoid infinite loops
|
||||
if (page > 20) break;
|
||||
}
|
||||
|
||||
const issues = allIssues;
|
||||
console.log(`[DEBUG] Found ${issues.length} open issues`);
|
||||
|
||||
let processedCount = 0;
|
||||
let candidateCount = 0;
|
||||
|
||||
for (const issue of issues) {
|
||||
processedCount++;
|
||||
console.log(
|
||||
`[DEBUG] Processing issue #${issue.number} (${processedCount}/${issues.length}): ${issue.title}`
|
||||
);
|
||||
|
||||
console.log(`[DEBUG] Fetching comments for issue #${issue.number}...`);
|
||||
const comments: GitHubComment[] = await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues/${issue.number}/comments`,
|
||||
token
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} has ${comments.length} comments`
|
||||
);
|
||||
|
||||
const dupeComments = comments.filter(
|
||||
(comment) =>
|
||||
comment.body.includes("Found") &&
|
||||
comment.body.includes("possible duplicate") &&
|
||||
comment.user.type === "Bot"
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} has ${dupeComments.length} duplicate detection comments`
|
||||
);
|
||||
|
||||
if (dupeComments.length === 0) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - no duplicate comments found, skipping`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const lastDupeComment = dupeComments[dupeComments.length - 1];
|
||||
const dupeCommentDate = new Date(lastDupeComment.created_at);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${
|
||||
issue.number
|
||||
} - most recent duplicate comment from: ${dupeCommentDate.toISOString()}`
|
||||
);
|
||||
|
||||
if (dupeCommentDate > threeDaysAgo) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - duplicate comment is too recent, skipping`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
console.log(
|
||||
`[DEBUG] Issue #${
|
||||
issue.number
|
||||
} - duplicate comment is old enough (${Math.floor(
|
||||
(Date.now() - dupeCommentDate.getTime()) / (1000 * 60 * 60 * 24)
|
||||
)} days)`
|
||||
);
|
||||
|
||||
const commentsAfterDupe = comments.filter(
|
||||
(comment) => new Date(comment.created_at) > dupeCommentDate
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - ${commentsAfterDupe.length} comments after duplicate detection`
|
||||
);
|
||||
|
||||
if (commentsAfterDupe.length > 0) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - has activity after duplicate comment, skipping`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - checking reactions on duplicate comment...`
|
||||
);
|
||||
const reactions: GitHubReaction[] = await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues/comments/${lastDupeComment.id}/reactions`,
|
||||
token
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - duplicate comment has ${reactions.length} reactions`
|
||||
);
|
||||
|
||||
const authorThumbsDown = reactions.some(
|
||||
(reaction) =>
|
||||
reaction.user.id === issue.user.id && reaction.content === "-1"
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - author thumbs down reaction: ${authorThumbsDown}`
|
||||
);
|
||||
|
||||
if (authorThumbsDown) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - author disagreed with duplicate detection, skipping`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const duplicateIssueNumber = extractDuplicateIssueNumber(lastDupeComment.body);
|
||||
if (!duplicateIssueNumber) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - could not extract duplicate issue number from comment, skipping`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
candidateCount++;
|
||||
const issueUrl = `https://github.com/${owner}/${repo}/issues/${issue.number}`;
|
||||
|
||||
try {
|
||||
console.log(
|
||||
`[INFO] Auto-closing issue #${issue.number} as duplicate of #${duplicateIssueNumber}: ${issueUrl}`
|
||||
);
|
||||
await closeIssueAsDuplicate(owner, repo, issue.number, duplicateIssueNumber, token);
|
||||
console.log(
|
||||
`[SUCCESS] Successfully closed issue #${issue.number} as duplicate of #${duplicateIssueNumber}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[ERROR] Failed to close issue #${issue.number} as duplicate: ${error}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[DEBUG] Script completed. Processed ${processedCount} issues, found ${candidateCount} candidates for auto-close`
|
||||
);
|
||||
}
|
||||
|
||||
autoCloseDuplicates().catch(console.error);
|
||||
|
||||
// Make it a module
|
||||
export {};
|
||||
213
scripts/backfill-duplicate-comments.ts
Normal file
213
scripts/backfill-duplicate-comments.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
declare global {
|
||||
var process: {
|
||||
env: Record<string, string | undefined>;
|
||||
};
|
||||
}
|
||||
|
||||
interface GitHubIssue {
|
||||
number: number;
|
||||
title: string;
|
||||
state: string;
|
||||
state_reason?: string;
|
||||
user: { id: number };
|
||||
created_at: string;
|
||||
closed_at?: string;
|
||||
}
|
||||
|
||||
interface GitHubComment {
|
||||
id: number;
|
||||
body: string;
|
||||
created_at: string;
|
||||
user: { type: string; id: number };
|
||||
}
|
||||
|
||||
async function githubRequest<T>(endpoint: string, token: string, method: string = 'GET', body?: any): Promise<T> {
|
||||
const response = await fetch(`https://api.github.com${endpoint}`, {
|
||||
method,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
"User-Agent": "backfill-duplicate-comments-script",
|
||||
...(body && { "Content-Type": "application/json" }),
|
||||
},
|
||||
...(body && { body: JSON.stringify(body) }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`GitHub API request failed: ${response.status} ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function triggerDedupeWorkflow(
|
||||
owner: string,
|
||||
repo: string,
|
||||
issueNumber: number,
|
||||
token: string,
|
||||
dryRun: boolean = true
|
||||
): Promise<void> {
|
||||
if (dryRun) {
|
||||
console.log(`[DRY RUN] Would trigger dedupe workflow for issue #${issueNumber}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await githubRequest(
|
||||
`/repos/${owner}/${repo}/actions/workflows/claude-dedupe-issues.yml/dispatches`,
|
||||
token,
|
||||
'POST',
|
||||
{
|
||||
ref: 'main',
|
||||
inputs: {
|
||||
issue_number: issueNumber.toString()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function backfillDuplicateComments(): Promise<void> {
|
||||
console.log("[DEBUG] Starting backfill duplicate comments script");
|
||||
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
if (!token) {
|
||||
throw new Error(`GITHUB_TOKEN environment variable is required
|
||||
|
||||
Usage:
|
||||
GITHUB_TOKEN=your_token bun run scripts/backfill-duplicate-comments.ts
|
||||
|
||||
Environment Variables:
|
||||
GITHUB_TOKEN - GitHub personal access token with repo and actions permissions (required)
|
||||
DRY_RUN - Set to "false" to actually trigger workflows (default: true for safety)
|
||||
MAX_ISSUE_NUMBER - Only process issues with numbers less than this value (default: 4050)`);
|
||||
}
|
||||
console.log("[DEBUG] GitHub token found");
|
||||
|
||||
const owner = "anthropics";
|
||||
const repo = "claude-code";
|
||||
const dryRun = process.env.DRY_RUN !== "false";
|
||||
const maxIssueNumber = parseInt(process.env.MAX_ISSUE_NUMBER || "4050", 10);
|
||||
const minIssueNumber = parseInt(process.env.MIN_ISSUE_NUMBER || "1", 10);
|
||||
|
||||
console.log(`[DEBUG] Repository: ${owner}/${repo}`);
|
||||
console.log(`[DEBUG] Dry run mode: ${dryRun}`);
|
||||
console.log(`[DEBUG] Looking at issues between #${minIssueNumber} and #${maxIssueNumber}`);
|
||||
|
||||
console.log(`[DEBUG] Fetching issues between #${minIssueNumber} and #${maxIssueNumber}...`);
|
||||
const allIssues: GitHubIssue[] = [];
|
||||
let page = 1;
|
||||
const perPage = 100;
|
||||
|
||||
while (true) {
|
||||
const pageIssues: GitHubIssue[] = await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues?state=all&per_page=${perPage}&page=${page}&sort=created&direction=desc`,
|
||||
token
|
||||
);
|
||||
|
||||
if (pageIssues.length === 0) break;
|
||||
|
||||
// Filter to only include issues within the specified range
|
||||
const filteredIssues = pageIssues.filter(issue =>
|
||||
issue.number >= minIssueNumber && issue.number < maxIssueNumber
|
||||
);
|
||||
allIssues.push(...filteredIssues);
|
||||
|
||||
// If the oldest issue in this page is still above our minimum, we need to continue
|
||||
// but if the oldest issue is below our minimum, we can stop
|
||||
const oldestIssueInPage = pageIssues[pageIssues.length - 1];
|
||||
if (oldestIssueInPage && oldestIssueInPage.number >= maxIssueNumber) {
|
||||
console.log(`[DEBUG] Oldest issue in page #${page} is #${oldestIssueInPage.number}, continuing...`);
|
||||
} else if (oldestIssueInPage && oldestIssueInPage.number < minIssueNumber) {
|
||||
console.log(`[DEBUG] Oldest issue in page #${page} is #${oldestIssueInPage.number}, below minimum, stopping`);
|
||||
break;
|
||||
} else if (filteredIssues.length === 0 && pageIssues.length > 0) {
|
||||
console.log(`[DEBUG] No issues in page #${page} are in range #${minIssueNumber}-#${maxIssueNumber}, continuing...`);
|
||||
}
|
||||
|
||||
page++;
|
||||
|
||||
// Safety limit to avoid infinite loops
|
||||
if (page > 200) {
|
||||
console.log("[DEBUG] Reached page limit, stopping pagination");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[DEBUG] Found ${allIssues.length} issues between #${minIssueNumber} and #${maxIssueNumber}`);
|
||||
|
||||
let processedCount = 0;
|
||||
let candidateCount = 0;
|
||||
let triggeredCount = 0;
|
||||
|
||||
for (const issue of allIssues) {
|
||||
processedCount++;
|
||||
console.log(
|
||||
`[DEBUG] Processing issue #${issue.number} (${processedCount}/${allIssues.length}): ${issue.title}`
|
||||
);
|
||||
|
||||
console.log(`[DEBUG] Fetching comments for issue #${issue.number}...`);
|
||||
const comments: GitHubComment[] = await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues/${issue.number}/comments`,
|
||||
token
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} has ${comments.length} comments`
|
||||
);
|
||||
|
||||
// Look for existing duplicate detection comments (from the dedupe bot)
|
||||
const dupeDetectionComments = comments.filter(
|
||||
(comment) =>
|
||||
comment.body.includes("Found") &&
|
||||
comment.body.includes("possible duplicate") &&
|
||||
comment.user.type === "Bot"
|
||||
);
|
||||
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} has ${dupeDetectionComments.length} duplicate detection comments`
|
||||
);
|
||||
|
||||
// Skip if there's already a duplicate detection comment
|
||||
if (dupeDetectionComments.length > 0) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} already has duplicate detection comment, skipping`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
candidateCount++;
|
||||
const issueUrl = `https://github.com/${owner}/${repo}/issues/${issue.number}`;
|
||||
|
||||
try {
|
||||
console.log(
|
||||
`[INFO] ${dryRun ? '[DRY RUN] ' : ''}Triggering dedupe workflow for issue #${issue.number}: ${issueUrl}`
|
||||
);
|
||||
await triggerDedupeWorkflow(owner, repo, issue.number, token, dryRun);
|
||||
|
||||
if (!dryRun) {
|
||||
console.log(
|
||||
`[SUCCESS] Successfully triggered dedupe workflow for issue #${issue.number}`
|
||||
);
|
||||
}
|
||||
triggeredCount++;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[ERROR] Failed to trigger workflow for issue #${issue.number}: ${error}`
|
||||
);
|
||||
}
|
||||
|
||||
// Add a delay between workflow triggers to avoid overwhelming the system
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[DEBUG] Script completed. Processed ${processedCount} issues, found ${candidateCount} candidates without duplicate comments, ${dryRun ? 'would trigger' : 'triggered'} ${triggeredCount} workflows`
|
||||
);
|
||||
}
|
||||
|
||||
backfillDuplicateComments().catch(console.error);
|
||||
|
||||
// Make it a module
|
||||
export {};
|
||||
Reference in New Issue
Block a user