skilld 0.10.0 → 0.10.2

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.
@@ -116,7 +116,7 @@ function hasCodeBlock$1(text) {
116
116
  function isNonTechnical(issue) {
117
117
  const body = (issue.body || "").trim();
118
118
  if (body.length < 200 && !hasCodeBlock$1(body) && issue.reactions > 50) return true;
119
- if (/\b(?:roadmap|tracking|love|thank|awesome|great work)\b/i.test(issue.title) && !hasCodeBlock$1(body)) return true;
119
+ if (/\b(?:love|thank|awesome|great work)\b/i.test(issue.title) && !hasCodeBlock$1(body)) return true;
120
120
  return false;
121
121
  }
122
122
  function freshnessScore(reactions, createdAt) {
@@ -197,18 +197,26 @@ function fetchIssuesByState(owner, repo, state, count, releasedAt, fromDate) {
197
197
  "api",
198
198
  `search/issues?q=${`repo:${owner}/${repo}+is:issue+is:${state}${datePart}`}&sort=reactions&order=desc&per_page=${fetchCount}`,
199
199
  "-q",
200
- ".items[] | {number, title, state, labels: [.labels[]?.name], body, createdAt: .created_at, url: .html_url, reactions: .reactions[\"+1\"], comments: .comments, user: .user.login, userType: .user.type}"
200
+ ".items[] | {number, title, state, labels: [.labels[]?.name], body, createdAt: .created_at, url: .html_url, reactions: .reactions[\"+1\"], comments: .comments, user: .user.login, userType: .user.type, authorAssociation: .author_association}"
201
201
  ], {
202
202
  encoding: "utf-8",
203
203
  maxBuffer: 10 * 1024 * 1024
204
204
  });
205
205
  if (!result) return [];
206
- return result.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line)).filter((issue) => !BOT_USERS.has(issue.user) && issue.userType !== "Bot").filter((issue) => !isNoiseIssue(issue)).filter((issue) => !isNonTechnical(issue)).map(({ user: _, userType: __, ...issue }) => ({
207
- ...issue,
208
- type: classifyIssue(issue.labels),
209
- topComments: [],
210
- score: freshnessScore(issue.reactions, issue.createdAt)
211
- })).sort((a, b) => b.score - a.score).slice(0, count);
206
+ return result.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line)).filter((issue) => !BOT_USERS.has(issue.user) && issue.userType !== "Bot").filter((issue) => !isNoiseIssue(issue)).filter((issue) => !isNonTechnical(issue)).map(({ user: _, userType: __, authorAssociation, ...issue }) => {
207
+ const isMaintainer = [
208
+ "OWNER",
209
+ "MEMBER",
210
+ "COLLABORATOR"
211
+ ].includes(authorAssociation);
212
+ const isRoadmap = /\broadmap\b/i.test(issue.title) || issue.labels.some((l) => /roadmap/i.test(l));
213
+ return {
214
+ ...issue,
215
+ type: classifyIssue(issue.labels),
216
+ topComments: [],
217
+ score: freshnessScore(issue.reactions, issue.createdAt) * (isMaintainer && isRoadmap ? 5 : 1)
218
+ };
219
+ }).sort((a, b) => b.score - a.score).slice(0, count);
212
220
  }
213
221
  function oneYearAgo() {
214
222
  const d = /* @__PURE__ */ new Date();
@@ -480,22 +488,23 @@ function compareSemver(a, b) {
480
488
  }
481
489
  function fetchReleasesViaGh(owner, repo) {
482
490
  try {
483
- const { stdout: json } = spawnSync("gh", [
491
+ const { stdout: ndjson } = spawnSync("gh", [
484
492
  "api",
485
- `repos/${owner}/${repo}/releases?per_page=100`,
493
+ `repos/${owner}/${repo}/releases`,
494
+ "--paginate",
486
495
  "--jq",
487
- "[.[] | {id: .id, tag: .tag_name, name: .name, prerelease: .prerelease, createdAt: .created_at, publishedAt: .published_at, markdown: .body}]"
496
+ ".[] | {id: .id, tag: .tag_name, name: .name, prerelease: .prerelease, createdAt: .created_at, publishedAt: .published_at, markdown: .body}"
488
497
  ], {
489
498
  encoding: "utf-8",
490
- timeout: 15e3,
499
+ timeout: 3e4,
491
500
  stdio: [
492
501
  "ignore",
493
502
  "pipe",
494
503
  "ignore"
495
504
  ]
496
505
  });
497
- if (!json) return [];
498
- return JSON.parse(json);
506
+ if (!ndjson) return [];
507
+ return ndjson.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
499
508
  } catch {
500
509
  return [];
501
510
  }
@@ -597,11 +606,6 @@ function isStubRelease(release) {
597
606
  const body = (release.markdown || "").trim();
598
607
  return body.length < 500 && /changelog\.md/i.test(body);
599
608
  }
600
- function isChangelogRedirectPattern(releases) {
601
- const sample = releases.slice(0, 3);
602
- if (sample.length === 0) return false;
603
- return sample.every(isStubRelease);
604
- }
605
609
  async function fetchChangelog(owner, repo, ref, packageName) {
606
610
  const paths = [];
607
611
  if (packageName) {
@@ -620,30 +624,23 @@ async function fetchChangelog(owner, repo, ref, packageName) {
620
624
  }
621
625
  return null;
622
626
  }
623
- async function fetchReleaseNotes(owner, repo, installedVersion, gitRef, packageName, fromDate) {
627
+ async function fetchReleaseNotes(owner, repo, installedVersion, gitRef, packageName, fromDate, changelogRef) {
624
628
  const selected = selectReleases(await fetchAllReleases(owner, repo), packageName, installedVersion, fromDate);
625
629
  if (selected.length > 0) {
626
- if (isChangelogRedirectPattern(selected)) {
627
- const changelog = await fetchChangelog(owner, repo, gitRef || selected[0].tag, packageName);
628
- if (changelog) return [{
629
- path: "releases/CHANGELOG.md",
630
- content: changelog
631
- }];
632
- }
633
630
  const docs = selected.filter((r) => !isStubRelease(r)).map((r) => {
634
631
  return {
635
632
  path: `releases/${r.tag.includes("@") || r.tag.startsWith("v") ? r.tag : `v${r.tag}`}.md`,
636
633
  content: formatRelease(r, packageName)
637
634
  };
638
635
  });
639
- const changelog = await fetchChangelog(owner, repo, gitRef || selected[0].tag, packageName);
636
+ const changelog = await fetchChangelog(owner, repo, changelogRef || gitRef || selected[0].tag, packageName);
640
637
  if (changelog && changelog.length < 5e5) docs.push({
641
638
  path: "releases/CHANGELOG.md",
642
639
  content: changelog
643
640
  });
644
641
  return docs;
645
642
  }
646
- const changelog = await fetchChangelog(owner, repo, gitRef || "main", packageName);
643
+ const changelog = await fetchChangelog(owner, repo, changelogRef || gitRef || "main", packageName);
647
644
  if (!changelog) return [];
648
645
  return [{
649
646
  path: "releases/CHANGELOG.md",
@@ -758,9 +755,29 @@ function truncateBody(body, limit) {
758
755
  if (lastParagraph > lastSafeEnd * .6) return `${slice.slice(0, lastParagraph)}\n\n...`;
759
756
  return `${slice}...`;
760
757
  }
758
+ const TITLE_NOISE_RE = /looking .*(developer|engineer|freelanc)|hiring|job post|guide me to (?:complete|finish|build)|help me (?:complete|finish|build)|seeking .* tutorial|recommend.* course/i;
759
+ const MIN_DISCUSSION_SCORE = 3;
761
760
  function scoreComment(c) {
762
761
  return (c.isMaintainer ? 3 : 1) * (hasCodeBlock(c.body) ? 2 : 1) * (1 + c.reactions);
763
762
  }
763
+ function scoreDiscussion(d) {
764
+ if (TITLE_NOISE_RE.test(d.title)) return -1;
765
+ let score = 0;
766
+ if (d.isMaintainer) score += 3;
767
+ if (hasCodeBlock([
768
+ d.body,
769
+ d.answer || "",
770
+ ...d.topComments.map((c) => c.body)
771
+ ].join("\n"))) score += 3;
772
+ score += Math.min(d.upvoteCount, 5);
773
+ if (d.answer) {
774
+ score += 2;
775
+ if (d.answer.length > 100) score += 1;
776
+ }
777
+ if (d.topComments.some((c) => c.isMaintainer)) score += 2;
778
+ if (d.topComments.some((c) => c.reactions > 0)) score += 1;
779
+ return score;
780
+ }
764
781
  async function fetchGitHubDiscussions(owner, repo, limit = 20, releasedAt, fromDate) {
765
782
  if (!isGhAvailable()) return [];
766
783
  if (!fromDate && releasedAt) {
@@ -773,7 +790,7 @@ async function fetchGitHubDiscussions(owner, repo, limit = 20, releasedAt, fromD
773
790
  "api",
774
791
  "graphql",
775
792
  "-f",
776
- `query=${`query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { discussions(first: ${Math.min(limit * 3, 80)}, orderBy: {field: CREATED_AT, direction: DESC}) { nodes { number title body category { name } createdAt url upvoteCount comments(first: 10) { totalCount nodes { body author { login } authorAssociation reactions { totalCount } } } answer { body author { login } authorAssociation } author { login } } } } }`}`,
793
+ `query=${`query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { discussions(first: ${Math.min(limit * 3, 80)}, orderBy: {field: CREATED_AT, direction: DESC}) { nodes { number title body category { name } createdAt url upvoteCount comments(first: 10) { totalCount nodes { body author { login } authorAssociation reactions { totalCount } } } answer { body author { login } authorAssociation } author { login } authorAssociation } } } }`}`,
777
794
  "-f",
778
795
  `owner=${owner}`,
779
796
  "-f",
@@ -822,15 +839,23 @@ async function fetchGitHubDiscussions(owner, repo, limit = 20, releasedAt, fromD
822
839
  url: d.url,
823
840
  upvoteCount: d.upvoteCount || 0,
824
841
  comments: d.comments?.totalCount || 0,
842
+ isMaintainer: [
843
+ "OWNER",
844
+ "MEMBER",
845
+ "COLLABORATOR"
846
+ ].includes(d.authorAssociation),
825
847
  answer,
826
848
  topComments: comments
827
849
  };
828
- }).sort((a, b) => {
829
- const aHigh = HIGH_VALUE_CATEGORIES.has(a.category.toLowerCase()) ? 1 : 0;
830
- const bHigh = HIGH_VALUE_CATEGORIES.has(b.category.toLowerCase()) ? 1 : 0;
850
+ }).map((d) => ({
851
+ d,
852
+ score: scoreDiscussion(d)
853
+ })).filter(({ score }) => score >= MIN_DISCUSSION_SCORE).sort((a, b) => {
854
+ const aHigh = HIGH_VALUE_CATEGORIES.has(a.d.category.toLowerCase()) ? 1 : 0;
855
+ const bHigh = HIGH_VALUE_CATEGORIES.has(b.d.category.toLowerCase()) ? 1 : 0;
831
856
  if (aHigh !== bHigh) return bHigh - aHigh;
832
- return b.upvoteCount + b.comments - (a.upvoteCount + a.comments);
833
- }).slice(0, limit);
857
+ return b.score - a.score;
858
+ }).slice(0, limit).map(({ d }) => d);
834
859
  } catch {
835
860
  return [];
836
861
  }