issuewatcher-mcp 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # IssueWatcher for your AI assistant
2
2
 
3
- **Ask your AI assistant "What should I work on today?" and get back the GitHub issues that are actually worth your time, with the reason for each.**
3
+ **Ask your AI assistant "What GitHub issues should I work on today?" and get back the GitHub issues that are actually worth your time, with the reason for each.**
4
4
 
5
5
  IssueWatcher is an [MCP](https://modelcontextprotocol.io) server. It watches the repositories you starred and the open-source libraries your own projects depend on, ranks their newest issues, and explains every score. It works inside Claude, Cursor, VS Code and other apps that support MCP.
6
6
 
@@ -133,20 +133,22 @@ You should see:
133
133
  [ok] API budget: 4994 of 5000 requests left (resets ...).
134
134
  [ok] Watching 51 repos: 16 starred, 35 from libraries your projects use (7.2s).
135
135
 
136
- All good. Add this server to your AI app, then ask: "What should I work on today?"
136
+ All good. Add this server to your AI app, then ask: "What GitHub issues should I work on today?"
137
137
  ```
138
138
 
139
139
  ### 4. Ask
140
140
 
141
141
  Restart your AI app, then try:
142
142
 
143
- - *"What should I work on today?"*
143
+ - *"What GitHub issues should I work on today?"*
144
144
  - *"Show me good first issues from the last 24 hours."*
145
145
  - *"What broke in my dependencies this week?"*
146
146
  - *"Why did you rank the first one so high?"*
147
147
  - *"Only show issues in nodejs/node and pydantic/pydantic."*
148
148
  - *"What repos are you watching for me?"*
149
149
 
150
+ **If your AI answers with general advice instead of using IssueWatcher**, nudge it: add "use IssueWatcher" to the question (*"What GitHub issues should I work on today? Use IssueWatcher."*), or pick **IssueWatcher** from the tools or prompts menu of the chat box. Some apps decide on their own whether a question needs a connected tool, and a vague one like "what should I work on today?" can sound like a request for life advice. Mentioning GitHub or issues usually removes the doubt.
151
+
150
152
  IssueWatcher starts loading your repositories as soon as your AI app starts it, so by the time you type your first question the answer is usually instant. If you ask within the first few seconds it may take 10 to 20 seconds (longer on a slow connection); follow-up questions are instant for the next thirty minutes. It looks at the 20 newest issues per repository in the time window.
151
153
 
152
154
  ## What the assistant can do (the tools)
@@ -164,7 +166,7 @@ There is also a prompt, `issue_radar`, that some apps show as a shortcut.
164
166
  Every issue gets a score from 0 to 100 from transparent rules, each with a reason you can read:
165
167
 
166
168
  - **Why it is on your radar:** a repo you starred, or a library one of your projects depends on (runtime counts more than dev tools).
167
- - **Could you pick it up?** `good first issue` or `help wanted` labels, unassigned, opened recently, few comments, reproduction steps. Issues already assigned, claimed by a team, or where the reporter says they are opening a PR are scored down.
169
+ - **Could you pick it up?** `good first issue` or `help wanted` labels, unassigned, opened recently, few comments, reproduction steps. Issues already assigned, claimed by a team, or where the reporter says they are opening a PR are scored down. Newer issues rank higher, and an issue with no reply and no taker after two weeks, or a feature request nobody has answered, is scored down.
168
170
  - **Could it break you?** the issue mentions a version in your range, or words like crash, regression, CVE.
169
171
  - **Noise is scored down:** bots, questions, duplicates, vague or throwaway titles.
170
172
 
@@ -75,16 +75,16 @@ export async function discoverDependencies(source, registry, opts, now = Date.no
75
75
  .slice(0, opts.maxDependencies);
76
76
  let lookupFailures = 0;
77
77
  const lookupsEnd = Date.now() + (opts.registryBudgetMs ?? 8_000);
78
- const resolved = await inPool(ranked, 12, async (dep) => {
78
+ const lookup = async (dep) => {
79
79
  let timer;
80
80
  try {
81
81
  const remaining = Math.max(0, lookupsEnd - Date.now());
82
82
  const budget = new Promise((_, reject) => {
83
83
  timer = setTimeout(() => reject(new Error("registry lookups ran out of time")), remaining);
84
84
  });
85
- const lookup = registry.resolve(dep.ecosystem, dep.pkg);
86
- lookup.catch(() => undefined); // the race may already be lost; a late failure is not news
87
- return await Promise.race([lookup, budget]);
85
+ const pending = registry.resolve(dep.ecosystem, dep.pkg);
86
+ pending.catch(() => undefined); // the race may already be lost; a late failure is not news
87
+ return await Promise.race([pending, budget]);
88
88
  }
89
89
  catch (err) {
90
90
  lookupFailures++;
@@ -94,7 +94,18 @@ export async function discoverDependencies(source, registry, opts, now = Date.no
94
94
  finally {
95
95
  clearTimeout(timer);
96
96
  }
97
+ };
98
+ // One pool per registry: a slow PyPI must not hold the slots that npm lookups need (D-053).
99
+ const resolved = new Array(ranked.length).fill(null);
100
+ const byEcosystem = new Map();
101
+ ranked.forEach((dep, index) => {
102
+ const group = byEcosystem.get(dep.ecosystem) ?? [];
103
+ group.push({ dep, index });
104
+ byEcosystem.set(dep.ecosystem, group);
97
105
  });
106
+ await Promise.all([...byEcosystem.entries()].map(([ecosystem, group]) => inPool(group, ecosystem === "npm" ? 10 : 4, async ({ dep, index }) => {
107
+ resolved[index] = await lookup(dep);
108
+ })));
98
109
  const map = {};
99
110
  ranked.forEach((dep, i) => {
100
111
  const repo = resolved[i];
@@ -65,6 +65,6 @@ export async function runCheck(settings, problems, version, write = (l) => conso
65
65
  return 1;
66
66
  }
67
67
  write(`\nMode: ${settings.mode}. Cache folder: ${settings.cacheDir}`);
68
- write(failed ? "\nSetup needs attention (see above)." : "\nAll good. Add this server to your AI app, then ask: \"What should I work on today?\"");
68
+ write(failed ? "\nSetup needs attention (see above)." : "\nAll good. Add this server to your AI app, then ask: \"What GitHub issues should I work on today?\"");
69
69
  return failed ? 1 : 0;
70
70
  }
@@ -20,6 +20,13 @@ const LABEL_NOISE = /\b(question|support|docs?|documentation|stale|discussion)\b
20
20
  const LABEL_NOISE_CONTRIBUTOR = /\b(question|support|stale|discussion)\b/i;
21
21
  const LABEL_EASY = /(good[ -]first[ -]issue|beginner|starter|easy|first[ -]timers?)/i;
22
22
  const LABEL_HELP = /(help[ -]wanted|hacktoberfest|contributions?[ -]welcome)/i;
23
+ /** A request for new behaviour ("[Feature]: ...", "Feature Request: ..."), as opposed to a report of something broken. */
24
+ const PROPOSAL_TITLE = /^\s*\[?\s*(?:feature|proposal|rfc|enhancement)\b[^\]:]*\]?\s*[:-]|\bfeature request\b/i;
25
+ const DAY_H = 24;
26
+ /** Work for the maintainers (CI, releases, bots), not a problem for users and not a task for an outside contributor. */
27
+ const MAINTENANCE_TITLE = /^\s*(?:chore|ci|build)(?:\([^)]*\))?\s*:|\b(?:trusted publishing|release workflow|github actions?|dependabot|renovate)\b/i;
28
+ /** "docs: ..." and "docs(scope): ..." conventional-commit style titles. */
29
+ const DOCS_TITLE = /^\s*docs?(?:\([^)]*\))?\s*:/i;
23
30
  const REPRO = /(steps to reproduce|expected (?:behaviou?r|result)|actual (?:behaviou?r|result)|traceback|stack ?trace|minimal reproduc|reproduction)/i;
24
31
  const HOUR_MS = 3_600_000;
25
32
  const TEST_WORK = /\b(tests?|testing|suite)\b/i;
@@ -196,6 +203,12 @@ export function scoreIssueDetailed(issue, target, opts = {}) {
196
203
  if (severity > severityCap) {
197
204
  add(severityCap - severity, "severity-cap", undefined, `severity capped at ${severityCap}${devOnly ? " (dev dependency)" : ""}`);
198
205
  }
206
+ if (MAINTENANCE_TITLE.test(issue.title)) {
207
+ add(-15, "maintenance", "maintenance task for the maintainers");
208
+ }
209
+ else if (consumer && !contributor && DOCS_TITLE.test(issue.title)) {
210
+ add(-10, "docs-title", "documentation issue");
211
+ }
199
212
  // Dismissals and noise.
200
213
  if (LABEL_DISMISSED.test(labels)) {
201
214
  add(-25, "dismissed-label", "marked duplicate/invalid");
@@ -241,9 +254,22 @@ export function scoreIssueDetailed(issue, target, opts = {}) {
241
254
  else {
242
255
  add(10, "unassigned", "unassigned");
243
256
  }
257
+ // Newer is better to pick up; an issue nobody has answered or taken for two weeks is probably not wanted (D-051).
244
258
  const ageH = (now - Date.parse(issue.createdAt)) / HOUR_MS;
245
- if (Number.isFinite(ageH) && ageH >= 0 && ageH <= 48) {
246
- add(10, "fresh", ageH < 1 ? "opened just now" : `opened ${Math.floor(ageH)}h ago`);
259
+ if (Number.isFinite(ageH) && ageH >= 0) {
260
+ if (ageH <= 48) {
261
+ add(10, "fresh", ageH < 1 ? "opened just now" : `opened ${Math.floor(ageH)}h ago`);
262
+ }
263
+ else if (ageH <= 7 * DAY_H) {
264
+ add(5, "recent", "opened this week");
265
+ }
266
+ else if (ageH > 14 * DAY_H && issue.comments === 0 && issue.assignees === 0) {
267
+ add(-10, "stale", `open ${Math.floor(ageH / DAY_H)} days with no replies`);
268
+ }
269
+ }
270
+ // A proposal nobody has answered yet needs a maintainer's yes before it is worth starting.
271
+ if (PROPOSAL_TITLE.test(issue.title) && issue.comments === 0 && !LABEL_EASY.test(labels) && !LABEL_HELP.test(labels)) {
272
+ add(-10, "unanswered-proposal", "feature request nobody has answered yet");
247
273
  }
248
274
  // Work that is already taken is not something to pick up.
249
275
  if (LABEL_CLAIMED.test(labels)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "issuewatcher-mcp",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "MCP server that ranks new GitHub issues for your AI assistant: what to pick up, and what may break your projects.",
5
5
  "mcpName": "io.github.mac146/issuewatcher",
6
6
  "license": "MIT",