Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
272 commits
Select commit Hold shift + click to select a range
633ba17
fix pdf name
rejojer Apr 9, 2025
3c7f35b
Update README.md
zmtomorrow Apr 10, 2025
b45f54c
Update README.md
zmtomorrow Apr 10, 2025
266ee54
Update README.md
zmtomorrow Apr 10, 2025
6c2bb5a
Update README.md
zmtomorrow Apr 10, 2025
68c2306
Update README.md
rejojer Apr 11, 2025
455bc4a
fix physical index
zmtomorrow Apr 18, 2025
5a0b38a
Update README.md
zmtomorrow Apr 19, 2025
e653fa4
add async. various fixes.
rejojer Apr 19, 2025
6c0f324
fix index range
rejojer Apr 19, 2025
446a4bf
Merge pull request #10 from rejojer/working
zmtomorrow Apr 20, 2025
b1bcb68
Update README.md
rejojer Apr 22, 2025
22e21f8
fix arg parsing
rejojer Apr 23, 2025
dda2ba8
Update README.md
rejojer Apr 23, 2025
5ffeb48
Update README.md
rejojer Apr 23, 2025
8d371e6
Update CHANGELOG.md
rejojer Apr 23, 2025
42108ae
Update README.md
zmtomorrow Apr 30, 2025
18fa2f4
Update README.md
zmtomorrow Apr 30, 2025
4cb2252
Update README.md
zmtomorrow Apr 30, 2025
4571f83
Update README.md
rejojer Apr 30, 2025
167a62d
Update README.md
zmtomorrow May 1, 2025
935a410
Update README.md
rejojer May 3, 2025
a2af4c9
Update README.md
rejojer May 3, 2025
b832bd4
Update README.md
rejojer May 3, 2025
a10ef58
Update README.md
rejojer May 3, 2025
0e0820d
Update README.md
rejojer May 3, 2025
59f4da2
Update README.md
rejojer May 4, 2025
f84fdd2
Update README.md
rejojer May 4, 2025
6fb26f5
Update README.md
rejojer May 4, 2025
cd29c81
Update README.md
rejojer May 4, 2025
5147bc3
Update README.md
rejojer May 4, 2025
05d9806
Update README.md
rejojer May 5, 2025
370a03d
Update README.md
rejojer May 5, 2025
c24fb3c
update readme
rejojer May 6, 2025
8d888c5
Update README.md
rejojer May 6, 2025
d1cfe21
Update README.md
zmtomorrow May 7, 2025
98f3589
Update README.md
zmtomorrow May 20, 2025
5d6de96
Update README.md
rejojer May 20, 2025
646b5be
Update README.md
rejojer May 20, 2025
6fa19e3
fix: handle TOC items exceeding document length
clarenceluo78 May 30, 2025
490256f
Merge branch 'VectifyAI:main' into main
clarenceluo78 May 30, 2025
1973906
fix option for adding node text
rejojer May 30, 2025
f5a8447
Merge pull request #21 from clarenceluo78/main
rejojer Jun 1, 2025
aa81e96
Update README.md
zmtomorrow Jun 10, 2025
fa8994e
Update README.md
zmtomorrow Jun 17, 2025
5ab302e
Update README.md
rejojer Jun 24, 2025
50e2354
Update README.md
zmtomorrow Jun 24, 2025
c6764b0
Update README.md
zmtomorrow Jun 24, 2025
58a631d
consolidate async calls
rejojer Jun 25, 2025
bc80661
use async with for client calls
rejojer Jun 25, 2025
93ddfb2
Update README.md
zmtomorrow Jun 30, 2025
b61da48
Update README.md
zmtomorrow Jul 10, 2025
ff2db7d
Update README.md
zmtomorrow Jul 10, 2025
62ffc42
Update README.md
zmtomorrow Aug 6, 2025
c579094
Update README.md
zmtomorrow Aug 7, 2025
96de2b1
Update README.md
rejojer Aug 8, 2025
4fb2531
Update README.md
rejojer Aug 8, 2025
a344853
Update README.md
rejojer Aug 8, 2025
d2f9c33
Update README.md
rejojer Aug 8, 2025
b2fb0e1
Update README.md
rejojer Aug 19, 2025
ae52f74
Update README.md
rejojer Aug 19, 2025
20bd166
Update README.md
rejojer Aug 19, 2025
7d746bc
Update README.md
rejojer Aug 19, 2025
ef8e0bc
Update README.md
rejojer Aug 19, 2025
3261136
Update README.md
rejojer Aug 20, 2025
13f9554
Update README.md
rejojer Aug 20, 2025
550bcc9
Update README.md
rejojer Aug 20, 2025
3e03cf6
Update README.md
rejojer Aug 20, 2025
b5727be
Update README.md
rejojer Aug 20, 2025
bd40e59
Update README.md
zmtomorrow Aug 20, 2025
e2b21e8
Update README.md
zmtomorrow Aug 20, 2025
a665956
add cookbook
rejojer Aug 20, 2025
52fcf93
fix notebook
rejojer Aug 21, 2025
f530993
fix notebook image
rejojer Aug 21, 2025
e3d9bde
fix notebook
rejojer Aug 21, 2025
a2e5e4d
fix notebook
rejojer Aug 21, 2025
31d249d
update print
rejojer Aug 21, 2025
ee1e5ca
fix notebook print
rejojer Aug 21, 2025
a553303
fix notebook format
rejojer Aug 21, 2025
544174c
fix notebook format
rejojer Aug 21, 2025
e1c02a1
add workflow figure
rejojer Aug 21, 2025
59a1d08
fix notebook
rejojer Aug 21, 2025
15a75e3
fix title
zmtomorrow Aug 21, 2025
9e558da
fix notebook style
rejojer Aug 21, 2025
3fb431a
fix notebook
rejojer Aug 22, 2025
da8664e
fix notebook
rejojer Aug 22, 2025
7dedb27
fix image
zmtomorrow Aug 22, 2025
f31e08b
fix image
zmtomorrow Aug 22, 2025
0caed44
fix image
zmtomorrow Aug 22, 2025
271d076
fix image
zmtomorrow Aug 22, 2025
29fdc50
fix notebook
rejojer Aug 22, 2025
5f7eccd
fix notebook
rejojer Aug 22, 2025
0501dd4
fix notebook
rejojer Aug 22, 2025
96c84dc
Update README.md
rejojer Aug 24, 2025
2086e09
update README
rejojer Aug 25, 2025
98104df
Update README.md
rejojer Aug 25, 2025
552a3fc
fix notebook
rejojer Aug 25, 2025
3e361be
Update README.md
zmtomorrow Aug 25, 2025
1e8ba61
Update README.md
rejojer Aug 25, 2025
fe271be
add markdown_to_tree
zmtomorrow Aug 25, 2025
f302e83
add summary
rejojer Aug 26, 2025
e5e4c74
fix structure
rejojer Aug 26, 2025
1718ccd
fix format
rejojer Aug 26, 2025
10e858a
fix node summary
rejojer Aug 26, 2025
66217d4
fix tree cleaning
rejojer Aug 26, 2025
8f6ab86
fix output
rejojer Aug 26, 2025
81c9067
add markdown_to_tree
zmtomorrow Aug 26, 2025
12c9afc
add markdown_to_tree
zmtomorrow Aug 26, 2025
d218f67
add markdown_to_tree
zmtomorrow Aug 26, 2025
30f5628
fix requirements.txt
rejojer Aug 26, 2025
43d4b3f
fix utility functions
rejojer Aug 26, 2025
71fb3c8
fix notebook
rejojer Aug 26, 2025
3540ae0
add markdown runner
rejojer Aug 26, 2025
cb37e04
Merge pull request #32 from VectifyAI/feat/markdown-tree
zmtomorrow Aug 27, 2025
f127b75
filter code
zmtomorrow Aug 27, 2025
f901742
Merge branch 'feat/markdown-tree' of github.com:VectifyAI/PageIndex i…
zmtomorrow Aug 27, 2025
73ed597
filter code
zmtomorrow Aug 27, 2025
dbf7dae
fix default params
rejojer Aug 27, 2025
be031ac
Merge pull request #33 from VectifyAI/feat/markdown-tree
zmtomorrow Aug 27, 2025
cc12055
fix params
rejojer Aug 28, 2025
c58e201
fix model
rejojer Aug 28, 2025
b634131
fix params
rejojer Aug 28, 2025
f4ac2cd
fix doc locations
rejojer Aug 28, 2025
1ab1689
add tutorials
rejojer Aug 29, 2025
2f556f2
add tutorials
rejojer Aug 29, 2025
f8377c1
add tutorials
rejojer Aug 29, 2025
67f41bf
fix tutorials
rejojer Aug 29, 2025
bbd4b48
fix
rejojer Aug 29, 2025
6357c73
fix
rejojer Aug 29, 2025
609fc7a
fix
rejojer Aug 29, 2025
bcdbf50
fix
rejojer Aug 29, 2025
6761acf
fix
rejojer Aug 29, 2025
75b1378
fix notebook
rejojer Aug 29, 2025
a6176bd
fix notebook
rejojer Aug 29, 2025
853a387
fix tutorials
rejojer Aug 29, 2025
7ecf0ab
fix notebook
rejojer Aug 29, 2025
190eb20
fix notebook
rejojer Aug 30, 2025
57d22c2
Update README.md
rejojer Aug 30, 2025
242da16
fix notebook
rejojer Aug 31, 2025
ba3c9b9
fix notebook
rejojer Aug 31, 2025
90e9946
fix notebook
rejojer Aug 31, 2025
7f19e73
firx print_toc
zmtomorrow Aug 31, 2025
f833065
firx print_toc
zmtomorrow Aug 31, 2025
9a296db
by default add node summary
rejojer Sep 1, 2025
36ae687
Add markdown support
zmtomorrow Sep 2, 2025
b4e5668
add output path print
rejojer Sep 2, 2025
1df4fc7
Update README.md
rejojer Sep 3, 2025
9d7c59e
Update README.md
rejojer Sep 3, 2025
dcc529b
fix notebook
rejojer Sep 3, 2025
6206350
Update README.md
rejojer Sep 3, 2025
23fc884
Update README.md
rejojer Sep 3, 2025
5e5241d
Update README.md
rejojer Sep 3, 2025
f54780c
Update README.md
rejojer Sep 3, 2025
33228a7
Update README.md
rejojer Sep 3, 2025
4b35a73
Update README.md
zmtomorrow Sep 17, 2025
fe42b9e
Revise README for PageIndex branding and features
zmtomorrow Sep 17, 2025
4d5babb
Update README.md
rejojer Sep 19, 2025
1942bbf
Update README.md
rejojer Oct 14, 2025
5ddbda5
Update README.md
rejojer Oct 14, 2025
978085a
Add vision-based RAG notebook
rejojer Oct 27, 2025
2db6cbf
fix notebook
rejojer Oct 28, 2025
e875af7
fix notebook
rejojer Oct 31, 2025
18ff6de
fix
rejojer Oct 31, 2025
f48602e
Update README.md
rejojer Nov 1, 2025
6776f26
Update README.md
rejojer Nov 1, 2025
1326699
Update README.md
rejojer Nov 1, 2025
c39041e
Update README.md
rejojer Nov 2, 2025
4458331
Update README.md
rejojer Nov 3, 2025
fe39df8
Update README.md
rejojer Nov 4, 2025
1186859
Update README.md
rejojer Nov 4, 2025
f6280cc
Update README.md
rejojer Nov 4, 2025
2c22bd9
Update README.md
rejojer Nov 4, 2025
fe2745c
Update README.md
rejojer Nov 4, 2025
4530625
Update README.md
rejojer Nov 4, 2025
b78d49d
Update README.md
rejojer Nov 4, 2025
a7ea720
Update README.md
rejojer Nov 4, 2025
c4f9f17
Update README.md
rejojer Nov 4, 2025
cd24d0d
Update README.md
rejojer Nov 4, 2025
bf3db3c
Update README.md
rejojer Nov 4, 2025
17a2c54
Update README.md
rejojer Nov 5, 2025
d104726
Update README.md
rejojer Nov 5, 2025
e679225
Update README.md
rejojer Nov 5, 2025
cd4c2b9
Update README.md
rejojer Nov 5, 2025
1b0097f
Update README.md
rejojer Nov 5, 2025
760fc91
Update README.md
rejojer Nov 6, 2025
19fbee8
Update README
rejojer Nov 7, 2025
eefe223
Update README.md
rejojer Nov 9, 2025
aff4d52
Update README.md
rejojer Nov 11, 2025
715bde9
Update README.md
rejojer Nov 12, 2025
dbe090a
Update README.md
rejojer Nov 12, 2025
3576d15
add notebook
rejojer Nov 17, 2025
af3bb93
fix notebook
rejojer Nov 17, 2025
748f934
Update recent releases in README.md
zmtomorrow Nov 19, 2025
dc54098
Revise README content and links
zmtomorrow Nov 19, 2025
1df60a1
Revise cloud service links and clean up README
zmtomorrow Nov 19, 2025
bc81bbb
Add cookbook file
zmtomorrow Nov 20, 2025
80bf586
Update README.md
rejojer Nov 20, 2025
e0e7c48
Update README.md
rejojer Nov 20, 2025
d0324b8
Update README.md
rejojer Dec 5, 2025
faf30fa
Update README.md
rejojer Dec 18, 2025
ca24336
Update README.md
rejojer Dec 18, 2025
efa8be4
Update README.md
rejojer Dec 19, 2025
8f444ce
Update README.md
rejojer Dec 19, 2025
cc2374f
Update README.md
rejojer Dec 19, 2025
d92ebd9
Update README.md
rejojer Dec 20, 2025
c00d9cf
Update README.md
rejojer Dec 22, 2025
9c2ae4f
Ignore notebooks for language stats
rejojer Jan 8, 2026
38010e0
fix: make ChatGPT_API_with_finish_reason return consistent tuple
luojiyin1987 Jan 19, 2026
53714c6
fix: prevent infinite loop in extract_toc_content
luojiyin1987 Jan 19, 2026
4f4e2e9
update link
rejojer Jan 24, 2026
a578970
Update README.md
rejojer Jan 25, 2026
ce55964
Update README.md
rejojer Jan 25, 2026
6877879
Update README.md
zmtomorrow Feb 10, 2026
31edd86
Merge pull request #118 from mooncos/patch-1
mooncos Feb 27, 2026
c9d56ef
fix: rename tob_extractor_prompt typo to toc_extractor_prompt (#109)
matiasinsaurralde Feb 27, 2026
373da2e
Initial plan
Copilot Mar 2, 2026
cb67aba
Add GitHub Actions workflows for issue deduplication and auto-close
Copilot Mar 2, 2026
8a84358
Refactor issue dedup system to use claude-code-action with /dedupe co…
BukeLy Mar 2, 2026
3c76052
Simplify scripts: unify bot detection, remove redundant API calls and…
BukeLy Mar 2, 2026
1343edc
Fix issues from Copilot review: 403 retry, comments pagination, backf…
BukeLy Mar 2, 2026
0bfc5f2
Fix backfill pagination: use raw count instead of filtered count
BukeLy Mar 2, 2026
658bd9d
Merge pull request #128 from VectifyAI/copilot/add-github-actions-setup
BukeLy Mar 2, 2026
03d22db
Fix backfill: replace gh issue list with gh api for pagination
BukeLy Mar 2, 2026
1644b81
Merge pull request #132 from VectifyAI/fix/backfill-dedupe-pagination
BukeLy Mar 2, 2026
c551fac
Allow github-actions bot to trigger claude-code-action
BukeLy Mar 2, 2026
7786a05
Merge pull request #133 from VectifyAI/fix/allow-bot-trigger
BukeLy Mar 2, 2026
700bc05
Allow all users to trigger issue dedup via claude-code-action
BukeLy Mar 4, 2026
a4f19e0
Merge pull request #142 from VectifyAI/fix/allow-all-users-dedupe
BukeLy Mar 4, 2026
212da0a
Merge pull request #63 from luojiyin1987/fix/api-error-return
BukeLy Mar 16, 2026
7633853
Merge pull request #65 from luojiyin1987/fix/extract-toc-infinite-loop
BukeLy Mar 16, 2026
dc6c9e4
Fix list_index variable shadowing in fix_incorrect_toc
BukeLy Mar 16, 2026
7770ad9
Merge pull request #167 from VectifyAI/fix/list-index-shadowing
BukeLy Mar 16, 2026
10758c2
Integrate LiteLLM for multi-provider LLM support (#168)
KylinMountain Mar 20, 2026
39a8898
Add PageIndexClient with agent-based retrieval via OpenAI Agents SDK …
KylinMountain Mar 26, 2026
ddd171f
Update demo example paper and polish README
rejojer Mar 26, 2026
eea8e89
Add agentic vectorless RAG example to README highlights
rejojer Mar 26, 2026
b10fb24
Update README
rejojer Mar 26, 2026
58e1c0a
Simplify root directory
rejojer Mar 26, 2026
f2599b6
Update README
rejojer Mar 26, 2026
0e2ad53
Merge pull request #184 from VectifyAI/cleanup/simplify-root-directory
rejojer Mar 27, 2026
43fafe3
Restructure examples directory and improve document storage (#189)
rejojer Mar 27, 2026
2accef7
Rename demo script and update README wording
rejojer Mar 27, 2026
d6a24ff
Simplify agentic vectorless RAG demo (#191)
rejojer Mar 28, 2026
9d3f97d
Disable agent tracing and auto-add litellm/ prefix for retrieve_model
rejojer Mar 28, 2026
266d7ea
Polish demo docstring and migrate to pathlib
rejojer Mar 28, 2026
b95ea6f
Merge pull request #197 from VectifyAI/polish/demo-docstring-and-pathlib
rejojer Mar 28, 2026
27ae416
Polish agent system prompt wording
rejojer Mar 28, 2026
6c0e086
Update developer links
rejojer Mar 29, 2026
2f245ba
Update README
rejojer Mar 29, 2026
25861f4
Fix compatibility for PDFs with embedded outlines
shaoqing404 Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .claude/commands/dedupe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
allowed-tools:
- Bash(gh:*)
- Bash(./.github/scripts/comment-on-duplicates.sh:*)
---

You are a GitHub issue deduplication assistant. Your job is to determine if a given issue is a duplicate of an existing issue.

## Input

The issue to check: $ARGUMENTS

## Steps

### 1. Pre-checks

First, check if the issue should be skipped:

```
gh issue view <number> --json state,labels,title,body,comments
```

Skip if:
- The issue is already closed
- The issue already has a `duplicate` label
- The issue already has a dedupe comment (check comments for "possible duplicate")

### 2. Understand the issue

Read the issue carefully and generate a concise summary of the core problem or feature request. Extract 3-5 key technical terms or concepts.

### 3. Search for duplicates

Launch 5 parallel searches using different keyword strategies to maximize coverage:

1. **Exact terms**: Use the most specific technical terms from the issue title
2. **Synonyms**: Use alternative phrasings for the core problem
3. **Error messages**: If the issue contains error messages, search for those
4. **Component names**: Search by the specific component/module mentioned
5. **Broad category**: Search by the general category of the issue

For each search, use:
```
gh search issues "<keywords> state:open" --repo $REPOSITORY --limit 20
```

### 4. Analyze candidates

For each unique candidate issue found:
- Compare the core problem being described
- Look past superficial wording differences
- Consider whether they describe the same root cause
- Only flag as duplicate if you are at least 85% confident

### 5. Filter false positives

Remove candidates that:
- Are only superficially similar (same area but different problems)
- Are related but describe distinct issues
- Are too old or already resolved differently

### 6. Report results

If you found duplicates (max 3), call:
```
./.github/scripts/comment-on-duplicates.sh --base-issue <number> --potential-duplicates <dup1> <dup2> ...
```

If no duplicates found, do nothing and report that the issue appears to be unique.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.ipynb linguist-vendored
258 changes: 258 additions & 0 deletions .github/scripts/autoclose-labeled-issues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
/**
* scripts/autoclose-labeled-issues.js
*
* Auto-closes issues that have a bot "possible duplicate" comment older than
* 3 days, unless:
* - A human has commented after the bot's duplicate comment
* - The author reacted with thumbs-down on the duplicate comment
*
* Required environment variables:
* GITHUB_TOKEN - GitHub Actions token
* REPO_OWNER - Repository owner
* REPO_NAME - Repository name
*
* Optional:
* DRY_RUN - If "true", report but do not close (default: false)
*/

'use strict';

const https = require('https');

const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
const REPO_OWNER = process.env.REPO_OWNER;
const REPO_NAME = process.env.REPO_NAME;
const DRY_RUN = process.env.DRY_RUN === 'true';

const THREE_DAYS_MS = 3 * 24 * 60 * 60 * 1000;

function githubRequest(method, path, body = null, retried = false) {
return new Promise((resolve, reject) => {
const payload = body ? JSON.stringify(body) : null;
const options = {
hostname: 'api.github.com',
path,
method,
headers: {
'Authorization': `Bearer ${GITHUB_TOKEN}`,
'Accept': 'application/vnd.github+json',
'User-Agent': 'PageIndex-Autoclose/1.0',
'X-GitHub-Api-Version': '2022-11-28',
...(payload ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) } : {}),
},
};

const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => (data += chunk));
res.on('end', async () => {
// 429: 始终重试(rate limit)
if (res.statusCode === 429 && !retried) {
const retryAfter = parseInt(res.headers['retry-after'] || '60', 10);
console.log(` Rate limited on ${method} ${path}, retrying after ${retryAfter}s...`);
await sleep(retryAfter * 1000);
try { resolve(await githubRequest(method, path, body, true)); }
catch (err) { reject(err); }
return;
}
// 403: 只在 rate limit 相关时重试
if (res.statusCode === 403 && !retried) {
const rateLimitRemaining = res.headers['x-ratelimit-remaining'];
const hasRetryAfter = res.headers['retry-after'];
if (rateLimitRemaining === '0' || hasRetryAfter) {
const retryAfter = parseInt(hasRetryAfter || '60', 10);
console.log(` Rate limited (403) on ${method} ${path}, retrying after ${retryAfter}s...`);
await sleep(retryAfter * 1000);
try { resolve(await githubRequest(method, path, body, true)); }
catch (err) { reject(err); }
return;
}
}
if (res.statusCode >= 400) {
reject(new Error(`GitHub API ${method} ${path} -> ${res.statusCode}: ${data}`));
return;
}
try { resolve(data ? JSON.parse(data) : {}); }
catch { resolve({}); }
});
});
req.on('error', reject);
if (payload) req.write(payload);
req.end();
});
}

const sleep = (ms) => new Promise(r => setTimeout(r, ms));

/**
* Fetches open issues with the "duplicate" label, paginating as needed.
* Only returns issues created more than 3 days ago.
*/
async function fetchDuplicateIssues() {
const issues = [];
let page = 1;
while (true) {
const data = await githubRequest(
'GET',
`/repos/${REPO_OWNER}/${REPO_NAME}/issues?state=open&labels=duplicate&per_page=100&page=${page}`
);
if (!Array.isArray(data) || data.length === 0) break;
issues.push(...data.filter(i => !i.pull_request));
if (data.length < 100) break;
page++;
}

const cutoff = new Date(Date.now() - THREE_DAYS_MS);
return issues.filter(i => new Date(i.created_at) < cutoff);
}

function isBot(user) {
return user.type === 'Bot' || user.login.endsWith('[bot]') || user.login === 'github-actions';
}

/**
* Finds the bot's duplicate comment on an issue (contains "possible duplicate").
*/
function findDuplicateComment(comments) {
return comments.find(c =>
isBot(c.user) && c.body.includes('possible duplicate')
);
}

/**
* Checks if there are human comments after the duplicate comment.
*/
function hasHumanCommentAfter(comments, afterDate) {
return comments.some(c => {
if (isBot(c.user)) return false;
return new Date(c.created_at) > afterDate;
});
}

/**
* Fetches all comments for an issue, handling pagination.
* Requests per_page=100 and loops until we get fewer than 100 or an empty array.
*/
async function fetchAllComments(issueNumber) {
const allComments = [];
let page = 1;
while (true) {
const comments = await githubRequest(
'GET',
`/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issueNumber}/comments?per_page=100&page=${page}`
);
if (!Array.isArray(comments) || comments.length === 0) break;
allComments.push(...comments);
if (comments.length < 100) break;
page++;
}
return allComments;
}

/**
* Checks if the duplicate comment has a thumbs-down reaction.
*/
async function hasThumbsDownReaction(commentId) {
const reactions = await githubRequest(
'GET',
`/repos/${REPO_OWNER}/${REPO_NAME}/issues/comments/${commentId}/reactions`
);
return Array.isArray(reactions) && reactions.some(r => r.content === '-1');
}

/**
* Closes an issue as duplicate with a comment.
*/
async function closeAsDuplicate(issueNumber) {
const body =
'This issue has been automatically closed as a duplicate. ' +
'No human activity or objection was received within the 3-day grace period.\n\n' +
'If you believe this was closed in error, please reopen the issue and leave a comment.';

await githubRequest(
'POST',
`/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issueNumber}/comments`,
{ body }
);

await githubRequest(
'PATCH',
`/repos/${REPO_OWNER}/${REPO_NAME}/issues/${issueNumber}`,
{ state: 'closed', state_reason: 'completed' }
);
}

async function processIssue(issue) {
const num = issue.number;
console.log(`\nChecking issue #${num}: ${issue.title}`);

const comments = await fetchAllComments(num);

if (!Array.isArray(comments) || comments.length === 0) {
console.log(` -> Could not fetch comments, skipping.`);
return false;
}

const dupeComment = findDuplicateComment(comments);
if (!dupeComment) {
console.log(` -> No duplicate comment found, skipping.`);
return false;
}

const commentDate = new Date(dupeComment.created_at);
const ageMs = Date.now() - commentDate.getTime();

if (ageMs < THREE_DAYS_MS) {
const daysLeft = Math.ceil((THREE_DAYS_MS - ageMs) / (24 * 60 * 60 * 1000));
console.log(` -> Duplicate comment is less than 3 days old (${daysLeft}d remaining), skipping.`);
return false;
}

if (hasHumanCommentAfter(comments, commentDate)) {
console.log(` -> Human commented after duplicate comment, skipping.`);
return false;
}

if (await hasThumbsDownReaction(dupeComment.id)) {
console.log(` -> Author reacted with thumbs-down, skipping.`);
return false;
}

if (DRY_RUN) {
console.log(` [DRY RUN] Would close issue #${num}`);
return true;
}

await closeAsDuplicate(num);
console.log(` -> Closed issue #${num} as duplicate`);
return true;
}

async function main() {
const missing = ['GITHUB_TOKEN', 'REPO_OWNER', 'REPO_NAME'].filter(k => !process.env[k]);
if (missing.length) {
console.error(`Missing required environment variables: ${missing.join(', ')}`);
process.exit(1);
}

console.log('Auto-close duplicate issues');
console.log(` Repository: ${REPO_OWNER}/${REPO_NAME}`);
console.log(` Dry run: ${DRY_RUN}`);

const issues = await fetchDuplicateIssues();
console.log(`\nFound ${issues.length} duplicate-labeled issue(s) older than 3 days.`);

let closedCount = 0;
for (const issue of issues) {
const closed = await processIssue(issue);
if (closed) closedCount++;
await sleep(1000);
}

console.log(`\nSummary: ${closedCount} issue(s) closed.`);
}

main().catch(err => {
console.error('Fatal error:', err.message);
process.exit(1);
});
Loading