mcp-researchpowerpack-http 3.10.0
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 +124 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +227 -0
- package/dist/index.js.map +7 -0
- package/dist/mcp-use.json +7 -0
- package/dist/src/clients/github.d.ts +83 -0
- package/dist/src/clients/github.d.ts.map +1 -0
- package/dist/src/clients/github.js +370 -0
- package/dist/src/clients/github.js.map +7 -0
- package/dist/src/clients/reddit.d.ts +60 -0
- package/dist/src/clients/reddit.d.ts.map +1 -0
- package/dist/src/clients/reddit.js +287 -0
- package/dist/src/clients/reddit.js.map +7 -0
- package/dist/src/clients/research.d.ts +67 -0
- package/dist/src/clients/research.d.ts.map +1 -0
- package/dist/src/clients/research.js +282 -0
- package/dist/src/clients/research.js.map +7 -0
- package/dist/src/clients/scraper.d.ts +72 -0
- package/dist/src/clients/scraper.d.ts.map +1 -0
- package/dist/src/clients/scraper.js +327 -0
- package/dist/src/clients/scraper.js.map +7 -0
- package/dist/src/clients/search.d.ts +57 -0
- package/dist/src/clients/search.d.ts.map +1 -0
- package/dist/src/clients/search.js +218 -0
- package/dist/src/clients/search.js.map +7 -0
- package/dist/src/config/index.d.ts +93 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +218 -0
- package/dist/src/config/index.js.map +7 -0
- package/dist/src/schemas/deep-research.d.ts +40 -0
- package/dist/src/schemas/deep-research.d.ts.map +1 -0
- package/dist/src/schemas/deep-research.js +216 -0
- package/dist/src/schemas/deep-research.js.map +7 -0
- package/dist/src/schemas/github-score.d.ts +50 -0
- package/dist/src/schemas/github-score.d.ts.map +1 -0
- package/dist/src/schemas/github-score.js +58 -0
- package/dist/src/schemas/github-score.js.map +7 -0
- package/dist/src/schemas/scrape-links.d.ts +23 -0
- package/dist/src/schemas/scrape-links.d.ts.map +1 -0
- package/dist/src/schemas/scrape-links.js +32 -0
- package/dist/src/schemas/scrape-links.js.map +7 -0
- package/dist/src/schemas/web-search.d.ts +18 -0
- package/dist/src/schemas/web-search.d.ts.map +1 -0
- package/dist/src/schemas/web-search.js +28 -0
- package/dist/src/schemas/web-search.js.map +7 -0
- package/dist/src/scoring/github-quality.d.ts +142 -0
- package/dist/src/scoring/github-quality.d.ts.map +1 -0
- package/dist/src/scoring/github-quality.js +202 -0
- package/dist/src/scoring/github-quality.js.map +7 -0
- package/dist/src/services/file-attachment.d.ts +30 -0
- package/dist/src/services/file-attachment.d.ts.map +1 -0
- package/dist/src/services/file-attachment.js +205 -0
- package/dist/src/services/file-attachment.js.map +7 -0
- package/dist/src/services/llm-processor.d.ts +29 -0
- package/dist/src/services/llm-processor.d.ts.map +1 -0
- package/dist/src/services/llm-processor.js +206 -0
- package/dist/src/services/llm-processor.js.map +7 -0
- package/dist/src/services/markdown-cleaner.d.ts +8 -0
- package/dist/src/services/markdown-cleaner.d.ts.map +1 -0
- package/dist/src/services/markdown-cleaner.js +63 -0
- package/dist/src/services/markdown-cleaner.js.map +7 -0
- package/dist/src/tools/github-score.d.ts +12 -0
- package/dist/src/tools/github-score.d.ts.map +1 -0
- package/dist/src/tools/github-score.js +306 -0
- package/dist/src/tools/github-score.js.map +7 -0
- package/dist/src/tools/mcp-helpers.d.ts +27 -0
- package/dist/src/tools/mcp-helpers.d.ts.map +1 -0
- package/dist/src/tools/mcp-helpers.js +47 -0
- package/dist/src/tools/mcp-helpers.js.map +7 -0
- package/dist/src/tools/reddit.d.ts +54 -0
- package/dist/src/tools/reddit.d.ts.map +1 -0
- package/dist/src/tools/reddit.js +498 -0
- package/dist/src/tools/reddit.js.map +7 -0
- package/dist/src/tools/registry.d.ts +3 -0
- package/dist/src/tools/registry.d.ts.map +1 -0
- package/dist/src/tools/registry.js +17 -0
- package/dist/src/tools/registry.js.map +7 -0
- package/dist/src/tools/research.d.ts +14 -0
- package/dist/src/tools/research.d.ts.map +1 -0
- package/dist/src/tools/research.js +250 -0
- package/dist/src/tools/research.js.map +7 -0
- package/dist/src/tools/scrape.d.ts +14 -0
- package/dist/src/tools/scrape.d.ts.map +1 -0
- package/dist/src/tools/scrape.js +290 -0
- package/dist/src/tools/scrape.js.map +7 -0
- package/dist/src/tools/search.d.ts +10 -0
- package/dist/src/tools/search.d.ts.map +1 -0
- package/dist/src/tools/search.js +197 -0
- package/dist/src/tools/search.js.map +7 -0
- package/dist/src/tools/utils.d.ts +105 -0
- package/dist/src/tools/utils.d.ts.map +1 -0
- package/dist/src/tools/utils.js +96 -0
- package/dist/src/tools/utils.js.map +7 -0
- package/dist/src/utils/concurrency.d.ts +28 -0
- package/dist/src/utils/concurrency.d.ts.map +1 -0
- package/dist/src/utils/concurrency.js +62 -0
- package/dist/src/utils/concurrency.js.map +7 -0
- package/dist/src/utils/errors.d.ts +95 -0
- package/dist/src/utils/errors.d.ts.map +1 -0
- package/dist/src/utils/errors.js +289 -0
- package/dist/src/utils/errors.js.map +7 -0
- package/dist/src/utils/logger.d.ts +33 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +41 -0
- package/dist/src/utils/logger.js.map +7 -0
- package/dist/src/utils/markdown-formatter.d.ts +5 -0
- package/dist/src/utils/markdown-formatter.d.ts.map +1 -0
- package/dist/src/utils/markdown-formatter.js +15 -0
- package/dist/src/utils/markdown-formatter.js.map +7 -0
- package/dist/src/utils/response.d.ts +83 -0
- package/dist/src/utils/response.d.ts.map +1 -0
- package/dist/src/utils/response.js +109 -0
- package/dist/src/utils/response.js.map +7 -0
- package/dist/src/utils/retry.d.ts +43 -0
- package/dist/src/utils/retry.d.ts.map +1 -0
- package/dist/src/utils/retry.js +37 -0
- package/dist/src/utils/retry.js.map +7 -0
- package/dist/src/utils/url-aggregator.d.ts +92 -0
- package/dist/src/utils/url-aggregator.d.ts.map +1 -0
- package/dist/src/utils/url-aggregator.js +357 -0
- package/dist/src/utils/url-aggregator.js.map +7 -0
- package/dist/src/version.d.ts +28 -0
- package/dist/src/version.d.ts.map +1 -0
- package/dist/src/version.js +32 -0
- package/dist/src/version.js.map +7 -0
- package/package.json +73 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Repository Quality Scoring Engine
|
|
3
|
+
* Pure functions — no I/O, no side effects. Takes structured data, returns scores.
|
|
4
|
+
* Implements the "Gives a Damn" composite score algorithm.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Shannon entropy of weekly commit distribution, normalized by max possible entropy.
|
|
8
|
+
* Captures consistency of effort over time — the single hardest signal to fake.
|
|
9
|
+
*
|
|
10
|
+
* - Steady weekly commits → ~0.98
|
|
11
|
+
* - Sporadic bursts → ~0.3-0.5
|
|
12
|
+
* - Single code dump → ~0.0
|
|
13
|
+
*/
|
|
14
|
+
export declare function commitCadenceEntropy(weeklyCommits: readonly number[]): number;
|
|
15
|
+
/**
|
|
16
|
+
* Compare second-half velocity to first-half velocity.
|
|
17
|
+
* >1 = accelerating (good), ~1 = steady, <0.5 = decelerating, 0 = dead.
|
|
18
|
+
* Returns normalized 0-1 score where 1 = steady or accelerating.
|
|
19
|
+
*/
|
|
20
|
+
export declare function velocityDecay(weeklyCommits: readonly number[]): number;
|
|
21
|
+
/**
|
|
22
|
+
* How many contributors cover 80% of all commits?
|
|
23
|
+
* Higher bus factor = more resilient project.
|
|
24
|
+
*/
|
|
25
|
+
export declare function busFactor(contributorCommits: readonly number[]): number;
|
|
26
|
+
/**
|
|
27
|
+
* Gini coefficient of contributor commit distribution.
|
|
28
|
+
* 0 = perfect equality, 1 = total inequality.
|
|
29
|
+
* We return 1-gini so higher = more equal = better.
|
|
30
|
+
*/
|
|
31
|
+
export declare function contributionDiversity(contributorCommits: readonly number[]): number;
|
|
32
|
+
/**
|
|
33
|
+
* Ratio of community commits to total commits.
|
|
34
|
+
* High = community-driven, low = solo project.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ownerCommunityRatio(ownerWeeklyCommits: readonly number[], allWeeklyCommits: readonly number[]): number;
|
|
37
|
+
/**
|
|
38
|
+
* Sum of binary engineering practice flags, normalized to 0-1.
|
|
39
|
+
*/
|
|
40
|
+
export interface DisciplineFlags {
|
|
41
|
+
readonly hasLicense: boolean;
|
|
42
|
+
readonly hasContributing: boolean;
|
|
43
|
+
readonly hasIssueTemplate: boolean;
|
|
44
|
+
readonly hasPrTemplate: boolean;
|
|
45
|
+
readonly hasCodeOfConduct: boolean;
|
|
46
|
+
readonly hasCI: boolean;
|
|
47
|
+
readonly hasReleases: boolean;
|
|
48
|
+
readonly hasTopics: boolean;
|
|
49
|
+
readonly hasDescription: boolean;
|
|
50
|
+
readonly hasHomepage: boolean;
|
|
51
|
+
}
|
|
52
|
+
export declare function engineeringDiscipline(flags: DisciplineFlags): number;
|
|
53
|
+
/**
|
|
54
|
+
* Measures how iteratively the code was built.
|
|
55
|
+
* More commits per KB of code = more iterative development.
|
|
56
|
+
* AI dumps have high size with very few commits.
|
|
57
|
+
*/
|
|
58
|
+
export declare function codeIterationDensity(sizeKb: number, totalCommits: number): number;
|
|
59
|
+
/**
|
|
60
|
+
* Fork-to-star ratio — forks indicate actual usage.
|
|
61
|
+
*/
|
|
62
|
+
export declare function forkStarRatio(forks: number, stars: number): number;
|
|
63
|
+
/**
|
|
64
|
+
* Watcher-to-star ratio — watchers opt into notifications, much higher commitment.
|
|
65
|
+
*/
|
|
66
|
+
export declare function watcherStarRatio(watchers: number, stars: number): number;
|
|
67
|
+
/**
|
|
68
|
+
* Ratio of closed issues to total issues.
|
|
69
|
+
*/
|
|
70
|
+
export declare function issueCloseRatio(closedIssues: number, totalIssues: number): number;
|
|
71
|
+
/**
|
|
72
|
+
* How recently the owner committed, based on the 52-week owner array.
|
|
73
|
+
* Returns 1 if owner committed this week, decays toward 0.
|
|
74
|
+
*/
|
|
75
|
+
export declare function maintainerActivity(ownerWeeklyCommits: readonly number[]): number;
|
|
76
|
+
/**
|
|
77
|
+
* Stars per day, log-normalized. Separates steady organic growth from HN spikes.
|
|
78
|
+
*/
|
|
79
|
+
export declare function ageAdjustedStarRate(stars: number, createdAt: Date): number;
|
|
80
|
+
export interface AllMetrics {
|
|
81
|
+
readonly cadenceEntropy: number;
|
|
82
|
+
readonly velocityDecay: number;
|
|
83
|
+
readonly busFactor: number;
|
|
84
|
+
readonly contributionDiversity: number;
|
|
85
|
+
readonly ownerCommunityRatio: number;
|
|
86
|
+
readonly engineeringDiscipline: number;
|
|
87
|
+
readonly codeIterationDensity: number;
|
|
88
|
+
readonly forkStarRatio: number;
|
|
89
|
+
readonly watcherStarRatio: number;
|
|
90
|
+
readonly issueCloseRatio: number;
|
|
91
|
+
readonly maintainerActivity: number;
|
|
92
|
+
readonly ageAdjustedStarRate: number;
|
|
93
|
+
}
|
|
94
|
+
export interface SubScores {
|
|
95
|
+
readonly maintenance: number;
|
|
96
|
+
readonly community: number;
|
|
97
|
+
readonly discipline: number;
|
|
98
|
+
readonly substance: number;
|
|
99
|
+
}
|
|
100
|
+
export interface CompositeResult {
|
|
101
|
+
readonly score: number;
|
|
102
|
+
readonly subScores: SubScores;
|
|
103
|
+
readonly flags: string[];
|
|
104
|
+
}
|
|
105
|
+
interface PenaltyContext {
|
|
106
|
+
readonly archived: boolean;
|
|
107
|
+
readonly hasLicense: boolean;
|
|
108
|
+
readonly totalCommits: number;
|
|
109
|
+
readonly sizeKb: number;
|
|
110
|
+
}
|
|
111
|
+
export declare function generateFlags(metrics: AllMetrics, penaltyCtx: PenaltyContext, contributorCount: number): string[];
|
|
112
|
+
/**
|
|
113
|
+
* Compute the composite "Gives a Damn" score from all metrics.
|
|
114
|
+
*
|
|
115
|
+
* Weights:
|
|
116
|
+
* Maintenance (35%): entropy 40%, velocity 35%, maintainer activity 25%
|
|
117
|
+
* Community (20%): owner ratio 30%, bus factor 35%, fork ratio 20%, watcher ratio 15%
|
|
118
|
+
* Discipline (25%): engineering index 50%, issue close 30%, has releases 20%
|
|
119
|
+
* Substance (15%): iteration density 50%, age-adjusted stars 50%
|
|
120
|
+
* + Anti-pattern penalties
|
|
121
|
+
*/
|
|
122
|
+
export declare function computeCompositeScore(metrics: AllMetrics, penaltyCtx: PenaltyContext, contributorCount: number): CompositeResult;
|
|
123
|
+
export interface RawRepoData {
|
|
124
|
+
readonly stars: number;
|
|
125
|
+
readonly forks: number;
|
|
126
|
+
readonly watchers: number;
|
|
127
|
+
readonly sizeKb: number;
|
|
128
|
+
readonly createdAt: Date;
|
|
129
|
+
readonly archived: boolean;
|
|
130
|
+
readonly hasLicense: boolean;
|
|
131
|
+
readonly closedIssues: number;
|
|
132
|
+
readonly totalIssues: number;
|
|
133
|
+
readonly totalCommits: number;
|
|
134
|
+
readonly contributorCommits: readonly number[];
|
|
135
|
+
readonly allWeeklyCommits: readonly number[];
|
|
136
|
+
readonly ownerWeeklyCommits: readonly number[];
|
|
137
|
+
readonly disciplineFlags: DisciplineFlags;
|
|
138
|
+
}
|
|
139
|
+
export declare function computeAllMetrics(data: RawRepoData): AllMetrics;
|
|
140
|
+
export declare function scoreRepo(data: RawRepoData): CompositeResult;
|
|
141
|
+
export {};
|
|
142
|
+
//# sourceMappingURL=github-quality.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-quality.d.ts","sourceRoot":"","sources":["../../../src/scoring/github-quality.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
function logNorm(value, threshold) {
|
|
2
|
+
if (value <= 0 || threshold <= 0) return 0;
|
|
3
|
+
return Math.min(1, Math.log(1 + value) / Math.log(1 + threshold));
|
|
4
|
+
}
|
|
5
|
+
function clamp(value, min, max) {
|
|
6
|
+
return Math.max(min, Math.min(max, value));
|
|
7
|
+
}
|
|
8
|
+
function commitCadenceEntropy(weeklyCommits) {
|
|
9
|
+
const total = weeklyCommits.reduce((sum, w) => sum + w, 0);
|
|
10
|
+
if (total === 0) return 0;
|
|
11
|
+
const numWeeks = weeklyCommits.length;
|
|
12
|
+
if (numWeeks <= 1) return 0;
|
|
13
|
+
let entropy = 0;
|
|
14
|
+
for (const w of weeklyCommits) {
|
|
15
|
+
if (w > 0) {
|
|
16
|
+
const p = w / total;
|
|
17
|
+
entropy -= p * Math.log2(p);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const maxEntropy = Math.log2(numWeeks);
|
|
21
|
+
return maxEntropy > 0 ? entropy / maxEntropy : 0;
|
|
22
|
+
}
|
|
23
|
+
function velocityDecay(weeklyCommits) {
|
|
24
|
+
const half = Math.floor(weeklyCommits.length / 2);
|
|
25
|
+
const firstHalf = weeklyCommits.slice(0, half).reduce((s, w) => s + w, 0);
|
|
26
|
+
const secondHalf = weeklyCommits.slice(half).reduce((s, w) => s + w, 0);
|
|
27
|
+
if (firstHalf === 0 && secondHalf === 0) return 0;
|
|
28
|
+
if (firstHalf === 0) return 1;
|
|
29
|
+
const ratio = secondHalf / firstHalf;
|
|
30
|
+
return clamp(ratio, 0, 2) / 2;
|
|
31
|
+
}
|
|
32
|
+
function busFactor(contributorCommits) {
|
|
33
|
+
if (contributorCommits.length === 0) return 0;
|
|
34
|
+
const total = contributorCommits.reduce((s, c) => s + c, 0);
|
|
35
|
+
if (total === 0) return 0;
|
|
36
|
+
const sorted = [...contributorCommits].sort((a, b) => b - a);
|
|
37
|
+
let cumulative = 0;
|
|
38
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
39
|
+
cumulative += sorted[i];
|
|
40
|
+
if (cumulative >= 0.8 * total) {
|
|
41
|
+
return logNorm(i + 1, 10);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return logNorm(sorted.length, 10);
|
|
45
|
+
}
|
|
46
|
+
function contributionDiversity(contributorCommits) {
|
|
47
|
+
const n = contributorCommits.length;
|
|
48
|
+
if (n <= 1) return 0;
|
|
49
|
+
const sorted = [...contributorCommits].sort((a, b) => a - b);
|
|
50
|
+
const total = sorted.reduce((s, v) => s + v, 0);
|
|
51
|
+
if (total === 0) return 0;
|
|
52
|
+
let sum = 0;
|
|
53
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
54
|
+
sum += (2 * i - n + 1) * sorted[i];
|
|
55
|
+
}
|
|
56
|
+
const gini = sum / (n * total);
|
|
57
|
+
return clamp(1 - gini, 0, 1);
|
|
58
|
+
}
|
|
59
|
+
function ownerCommunityRatio(ownerWeeklyCommits, allWeeklyCommits) {
|
|
60
|
+
const ownerTotal = ownerWeeklyCommits.reduce((s, w) => s + w, 0);
|
|
61
|
+
const allTotal = allWeeklyCommits.reduce((s, w) => s + w, 0);
|
|
62
|
+
if (allTotal === 0) return 0;
|
|
63
|
+
return clamp((allTotal - ownerTotal) / allTotal, 0, 1);
|
|
64
|
+
}
|
|
65
|
+
function engineeringDiscipline(flags) {
|
|
66
|
+
const booleans = [
|
|
67
|
+
flags.hasLicense,
|
|
68
|
+
flags.hasContributing,
|
|
69
|
+
flags.hasIssueTemplate,
|
|
70
|
+
flags.hasPrTemplate,
|
|
71
|
+
flags.hasCodeOfConduct,
|
|
72
|
+
flags.hasCI,
|
|
73
|
+
flags.hasReleases,
|
|
74
|
+
flags.hasTopics,
|
|
75
|
+
flags.hasDescription,
|
|
76
|
+
flags.hasHomepage
|
|
77
|
+
];
|
|
78
|
+
const count = booleans.filter(Boolean).length;
|
|
79
|
+
return count / booleans.length;
|
|
80
|
+
}
|
|
81
|
+
function codeIterationDensity(sizeKb, totalCommits) {
|
|
82
|
+
if (sizeKb <= 0 || totalCommits <= 0) return 0;
|
|
83
|
+
const density = totalCommits / sizeKb;
|
|
84
|
+
return logNorm(density, 1);
|
|
85
|
+
}
|
|
86
|
+
function forkStarRatio(forks, stars) {
|
|
87
|
+
if (stars <= 0) return 0;
|
|
88
|
+
return logNorm(forks / stars, 0.5);
|
|
89
|
+
}
|
|
90
|
+
function watcherStarRatio(watchers, stars) {
|
|
91
|
+
if (stars <= 0) return 0;
|
|
92
|
+
return logNorm(watchers / stars, 0.1);
|
|
93
|
+
}
|
|
94
|
+
function issueCloseRatio(closedIssues, totalIssues) {
|
|
95
|
+
if (totalIssues <= 0) return 0.5;
|
|
96
|
+
return clamp(closedIssues / totalIssues, 0, 1);
|
|
97
|
+
}
|
|
98
|
+
function maintainerActivity(ownerWeeklyCommits) {
|
|
99
|
+
let weeksSinceActive = ownerWeeklyCommits.length;
|
|
100
|
+
for (let i = ownerWeeklyCommits.length - 1; i >= 0; i--) {
|
|
101
|
+
if ((ownerWeeklyCommits[i] ?? 0) > 0) {
|
|
102
|
+
weeksSinceActive = ownerWeeklyCommits.length - 1 - i;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return 1 - logNorm(weeksSinceActive, 26);
|
|
107
|
+
}
|
|
108
|
+
function ageAdjustedStarRate(stars, createdAt) {
|
|
109
|
+
const ageDays = Math.max(1, (Date.now() - createdAt.getTime()) / (1e3 * 60 * 60 * 24));
|
|
110
|
+
const starsPerDay = stars / ageDays;
|
|
111
|
+
return logNorm(starsPerDay, 5);
|
|
112
|
+
}
|
|
113
|
+
function computePenalty(ctx, velocityDecayScore) {
|
|
114
|
+
let penalty = 1;
|
|
115
|
+
if (ctx.archived) penalty *= 0.1;
|
|
116
|
+
if (!ctx.hasLicense) penalty *= 0.8;
|
|
117
|
+
if (ctx.totalCommits < 5 && ctx.sizeKb > 100) penalty *= 0.5;
|
|
118
|
+
if (velocityDecayScore < 0.05) penalty *= 0.7;
|
|
119
|
+
return penalty;
|
|
120
|
+
}
|
|
121
|
+
function generateFlags(metrics, penaltyCtx, contributorCount) {
|
|
122
|
+
const flags = [];
|
|
123
|
+
if (penaltyCtx.archived) flags.push("archived");
|
|
124
|
+
if (!penaltyCtx.hasLicense) flags.push("no-license");
|
|
125
|
+
if (contributorCount <= 1) flags.push("single-maintainer");
|
|
126
|
+
if (metrics.maintainerActivity < 0.3) flags.push("stale-6mo");
|
|
127
|
+
if (metrics.busFactor > 0.5) flags.push("high-bus-factor");
|
|
128
|
+
if (metrics.ownerCommunityRatio > 0.5) flags.push("active-community");
|
|
129
|
+
if (penaltyCtx.totalCommits < 5 && penaltyCtx.sizeKb > 100) flags.push("ai-dump-signal");
|
|
130
|
+
if (metrics.cadenceEntropy > 0.7) flags.push("consistent-commits");
|
|
131
|
+
if (metrics.velocityDecay > 0.6) flags.push("growing");
|
|
132
|
+
if (metrics.engineeringDiscipline > 0.6) flags.push("well-organized");
|
|
133
|
+
return flags;
|
|
134
|
+
}
|
|
135
|
+
function computeCompositeScore(metrics, penaltyCtx, contributorCount) {
|
|
136
|
+
const maintenance = metrics.cadenceEntropy * 0.4 + metrics.velocityDecay * 0.35 + metrics.maintainerActivity * 0.25;
|
|
137
|
+
const community = metrics.ownerCommunityRatio * 0.3 + metrics.busFactor * 0.35 + metrics.forkStarRatio * 0.2 + metrics.watcherStarRatio * 0.15;
|
|
138
|
+
const discipline = metrics.engineeringDiscipline * 0.5 + metrics.issueCloseRatio * 0.3 + (metrics.engineeringDiscipline > 0 ? 0.2 : 0);
|
|
139
|
+
const substance = metrics.codeIterationDensity * 0.5 + metrics.ageAdjustedStarRate * 0.5;
|
|
140
|
+
const raw = maintenance * 0.35 + community * 0.2 + discipline * 0.25 + substance * 0.15;
|
|
141
|
+
const penalty = computePenalty(penaltyCtx, metrics.velocityDecay);
|
|
142
|
+
const score = Math.round(clamp(raw * penalty * 100, 0, 100));
|
|
143
|
+
const flags = generateFlags(metrics, penaltyCtx, contributorCount);
|
|
144
|
+
return {
|
|
145
|
+
score,
|
|
146
|
+
subScores: {
|
|
147
|
+
maintenance: Math.round(maintenance * 100) / 100,
|
|
148
|
+
community: Math.round(community * 100) / 100,
|
|
149
|
+
discipline: Math.round(discipline * 100) / 100,
|
|
150
|
+
substance: Math.round(substance * 100) / 100
|
|
151
|
+
},
|
|
152
|
+
flags
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function computeAllMetrics(data) {
|
|
156
|
+
return {
|
|
157
|
+
cadenceEntropy: commitCadenceEntropy(data.allWeeklyCommits),
|
|
158
|
+
velocityDecay: velocityDecay(data.allWeeklyCommits),
|
|
159
|
+
busFactor: busFactor(data.contributorCommits),
|
|
160
|
+
contributionDiversity: contributionDiversity(data.contributorCommits),
|
|
161
|
+
ownerCommunityRatio: ownerCommunityRatio(data.ownerWeeklyCommits, data.allWeeklyCommits),
|
|
162
|
+
engineeringDiscipline: engineeringDiscipline(data.disciplineFlags),
|
|
163
|
+
codeIterationDensity: codeIterationDensity(data.sizeKb, data.totalCommits),
|
|
164
|
+
forkStarRatio: forkStarRatio(data.forks, data.stars),
|
|
165
|
+
watcherStarRatio: watcherStarRatio(data.watchers, data.stars),
|
|
166
|
+
issueCloseRatio: issueCloseRatio(data.closedIssues, data.totalIssues),
|
|
167
|
+
maintainerActivity: maintainerActivity(data.ownerWeeklyCommits),
|
|
168
|
+
ageAdjustedStarRate: ageAdjustedStarRate(data.stars, data.createdAt)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function scoreRepo(data) {
|
|
172
|
+
const metrics = computeAllMetrics(data);
|
|
173
|
+
return computeCompositeScore(
|
|
174
|
+
metrics,
|
|
175
|
+
{
|
|
176
|
+
archived: data.archived,
|
|
177
|
+
hasLicense: data.hasLicense,
|
|
178
|
+
totalCommits: data.totalCommits,
|
|
179
|
+
sizeKb: data.sizeKb
|
|
180
|
+
},
|
|
181
|
+
data.contributorCommits.length
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
export {
|
|
185
|
+
ageAdjustedStarRate,
|
|
186
|
+
busFactor,
|
|
187
|
+
codeIterationDensity,
|
|
188
|
+
commitCadenceEntropy,
|
|
189
|
+
computeAllMetrics,
|
|
190
|
+
computeCompositeScore,
|
|
191
|
+
contributionDiversity,
|
|
192
|
+
engineeringDiscipline,
|
|
193
|
+
forkStarRatio,
|
|
194
|
+
generateFlags,
|
|
195
|
+
issueCloseRatio,
|
|
196
|
+
maintainerActivity,
|
|
197
|
+
ownerCommunityRatio,
|
|
198
|
+
scoreRepo,
|
|
199
|
+
velocityDecay,
|
|
200
|
+
watcherStarRatio
|
|
201
|
+
};
|
|
202
|
+
//# sourceMappingURL=github-quality.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/scoring/github-quality.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * GitHub Repository Quality Scoring Engine\n * Pure functions \u2014 no I/O, no side effects. Takes structured data, returns scores.\n * Implements the \"Gives a Damn\" composite score algorithm.\n */\n\n// ============================================================================\n// Normalization Helpers\n// ============================================================================\n\n/**\n * Log-normalize a value against a threshold to 0-1 range.\n * Prevents any single metric from dominating via logarithmic compression.\n * Borrowed from OpenSSF Criticality Score (Rob Pike's formula).\n */\nfunction logNorm(value: number, threshold: number): number {\n if (value <= 0 || threshold <= 0) return 0;\n return Math.min(1, Math.log(1 + value) / Math.log(1 + threshold));\n}\n\n/** Clamp a value between min and max */\nfunction clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n}\n\n// ============================================================================\n// Individual Metric Calculators (each returns 0\u20131)\n// ============================================================================\n\n/**\n * Shannon entropy of weekly commit distribution, normalized by max possible entropy.\n * Captures consistency of effort over time \u2014 the single hardest signal to fake.\n *\n * - Steady weekly commits \u2192 ~0.98\n * - Sporadic bursts \u2192 ~0.3-0.5\n * - Single code dump \u2192 ~0.0\n */\nexport function commitCadenceEntropy(weeklyCommits: readonly number[]): number {\n const total = weeklyCommits.reduce((sum, w) => sum + w, 0);\n if (total === 0) return 0;\n\n const numWeeks = weeklyCommits.length;\n if (numWeeks <= 1) return 0;\n\n let entropy = 0;\n for (const w of weeklyCommits) {\n if (w > 0) {\n const p = w / total;\n entropy -= p * Math.log2(p);\n }\n }\n\n const maxEntropy = Math.log2(numWeeks);\n return maxEntropy > 0 ? entropy / maxEntropy : 0;\n}\n\n/**\n * Compare second-half velocity to first-half velocity.\n * >1 = accelerating (good), ~1 = steady, <0.5 = decelerating, 0 = dead.\n * Returns normalized 0-1 score where 1 = steady or accelerating.\n */\nexport function velocityDecay(weeklyCommits: readonly number[]): number {\n const half = Math.floor(weeklyCommits.length / 2);\n const firstHalf = weeklyCommits.slice(0, half).reduce((s, w) => s + w, 0);\n const secondHalf = weeklyCommits.slice(half).reduce((s, w) => s + w, 0);\n\n if (firstHalf === 0 && secondHalf === 0) return 0;\n if (firstHalf === 0) return 1; // all activity in second half = new project\n\n const ratio = secondHalf / firstHalf;\n // Normalize: ratio of 1+ maps to 1.0, ratio of 0 maps to 0\n return clamp(ratio, 0, 2) / 2;\n}\n\n/**\n * How many contributors cover 80% of all commits?\n * Higher bus factor = more resilient project.\n */\nexport function busFactor(contributorCommits: readonly number[]): number {\n if (contributorCommits.length === 0) return 0;\n\n const total = contributorCommits.reduce((s, c) => s + c, 0);\n if (total === 0) return 0;\n\n const sorted = [...contributorCommits].sort((a, b) => b - a);\n let cumulative = 0;\n for (let i = 0; i < sorted.length; i++) {\n cumulative += sorted[i]!;\n if (cumulative >= 0.8 * total) {\n // i+1 people cover 80% of commits\n return logNorm(i + 1, 10);\n }\n }\n return logNorm(sorted.length, 10);\n}\n\n/**\n * Gini coefficient of contributor commit distribution.\n * 0 = perfect equality, 1 = total inequality.\n * We return 1-gini so higher = more equal = better.\n */\nexport function contributionDiversity(contributorCommits: readonly number[]): number {\n const n = contributorCommits.length;\n if (n <= 1) return 0;\n\n const sorted = [...contributorCommits].sort((a, b) => a - b);\n const total = sorted.reduce((s, v) => s + v, 0);\n if (total === 0) return 0;\n\n let sum = 0;\n for (let i = 0; i < sorted.length; i++) {\n sum += (2 * i - n + 1) * sorted[i]!;\n }\n const gini = sum / (n * total);\n return clamp(1 - gini, 0, 1);\n}\n\n/**\n * Ratio of community commits to total commits.\n * High = community-driven, low = solo project.\n */\nexport function ownerCommunityRatio(\n ownerWeeklyCommits: readonly number[],\n allWeeklyCommits: readonly number[],\n): number {\n const ownerTotal = ownerWeeklyCommits.reduce((s, w) => s + w, 0);\n const allTotal = allWeeklyCommits.reduce((s, w) => s + w, 0);\n if (allTotal === 0) return 0;\n return clamp((allTotal - ownerTotal) / allTotal, 0, 1);\n}\n\n/**\n * Sum of binary engineering practice flags, normalized to 0-1.\n */\nexport interface DisciplineFlags {\n readonly hasLicense: boolean;\n readonly hasContributing: boolean;\n readonly hasIssueTemplate: boolean;\n readonly hasPrTemplate: boolean;\n readonly hasCodeOfConduct: boolean;\n readonly hasCI: boolean;\n readonly hasReleases: boolean;\n readonly hasTopics: boolean;\n readonly hasDescription: boolean;\n readonly hasHomepage: boolean;\n}\n\nexport function engineeringDiscipline(flags: DisciplineFlags): number {\n const booleans = [\n flags.hasLicense,\n flags.hasContributing,\n flags.hasIssueTemplate,\n flags.hasPrTemplate,\n flags.hasCodeOfConduct,\n flags.hasCI,\n flags.hasReleases,\n flags.hasTopics,\n flags.hasDescription,\n flags.hasHomepage,\n ];\n const count = booleans.filter(Boolean).length;\n return count / booleans.length;\n}\n\n/**\n * Measures how iteratively the code was built.\n * More commits per KB of code = more iterative development.\n * AI dumps have high size with very few commits.\n */\nexport function codeIterationDensity(sizeKb: number, totalCommits: number): number {\n if (sizeKb <= 0 || totalCommits <= 0) return 0;\n // commits per KB \u2014 higher = more iterative\n const density = totalCommits / sizeKb;\n return logNorm(density, 1); // threshold: 1 commit/KB is very iterative\n}\n\n/**\n * Fork-to-star ratio \u2014 forks indicate actual usage.\n */\nexport function forkStarRatio(forks: number, stars: number): number {\n if (stars <= 0) return 0;\n return logNorm(forks / stars, 0.5);\n}\n\n/**\n * Watcher-to-star ratio \u2014 watchers opt into notifications, much higher commitment.\n */\nexport function watcherStarRatio(watchers: number, stars: number): number {\n if (stars <= 0) return 0;\n return logNorm(watchers / stars, 0.1);\n}\n\n/**\n * Ratio of closed issues to total issues.\n */\nexport function issueCloseRatio(closedIssues: number, totalIssues: number): number {\n if (totalIssues <= 0) return 0.5; // no issues = neutral, not penalized\n return clamp(closedIssues / totalIssues, 0, 1);\n}\n\n/**\n * How recently the owner committed, based on the 52-week owner array.\n * Returns 1 if owner committed this week, decays toward 0.\n */\nexport function maintainerActivity(ownerWeeklyCommits: readonly number[]): number {\n // Find most recent week with owner commits (array index 0 = oldest, last = most recent)\n let weeksSinceActive = ownerWeeklyCommits.length; // default: no activity found\n for (let i = ownerWeeklyCommits.length - 1; i >= 0; i--) {\n if ((ownerWeeklyCommits[i] ?? 0) > 0) {\n weeksSinceActive = ownerWeeklyCommits.length - 1 - i;\n break;\n }\n }\n // 0 weeks since active \u2192 1.0, 26+ weeks \u2192 ~0\n return 1 - logNorm(weeksSinceActive, 26);\n}\n\n/**\n * Stars per day, log-normalized. Separates steady organic growth from HN spikes.\n */\nexport function ageAdjustedStarRate(stars: number, createdAt: Date): number {\n const ageDays = Math.max(1, (Date.now() - createdAt.getTime()) / (1000 * 60 * 60 * 24));\n const starsPerDay = stars / ageDays;\n return logNorm(starsPerDay, 5);\n}\n\n// ============================================================================\n// All Metrics Bundle\n// ============================================================================\n\nexport interface AllMetrics {\n readonly cadenceEntropy: number;\n readonly velocityDecay: number;\n readonly busFactor: number;\n readonly contributionDiversity: number;\n readonly ownerCommunityRatio: number;\n readonly engineeringDiscipline: number;\n readonly codeIterationDensity: number;\n readonly forkStarRatio: number;\n readonly watcherStarRatio: number;\n readonly issueCloseRatio: number;\n readonly maintainerActivity: number;\n readonly ageAdjustedStarRate: number;\n}\n\nexport interface SubScores {\n readonly maintenance: number;\n readonly community: number;\n readonly discipline: number;\n readonly substance: number;\n}\n\nexport interface CompositeResult {\n readonly score: number; // 0-100\n readonly subScores: SubScores;\n readonly flags: string[];\n}\n\n// ============================================================================\n// Composite Score Calculator\n// ============================================================================\n\ninterface PenaltyContext {\n readonly archived: boolean;\n readonly hasLicense: boolean;\n readonly totalCommits: number;\n readonly sizeKb: number;\n}\n\nfunction computePenalty(ctx: PenaltyContext, velocityDecayScore: number): number {\n let penalty = 1;\n if (ctx.archived) penalty *= 0.1;\n if (!ctx.hasLicense) penalty *= 0.8;\n // AI dump signal: <5 commits with >100KB\n if (ctx.totalCommits < 5 && ctx.sizeKb > 100) penalty *= 0.5;\n // Dead project: velocity near zero\n if (velocityDecayScore < 0.05) penalty *= 0.7;\n return penalty;\n}\n\nexport function generateFlags(\n metrics: AllMetrics,\n penaltyCtx: PenaltyContext,\n contributorCount: number,\n): string[] {\n const flags: string[] = [];\n\n if (penaltyCtx.archived) flags.push('archived');\n if (!penaltyCtx.hasLicense) flags.push('no-license');\n if (contributorCount <= 1) flags.push('single-maintainer');\n if (metrics.maintainerActivity < 0.3) flags.push('stale-6mo');\n if (metrics.busFactor > 0.5) flags.push('high-bus-factor');\n if (metrics.ownerCommunityRatio > 0.5) flags.push('active-community');\n if (penaltyCtx.totalCommits < 5 && penaltyCtx.sizeKb > 100) flags.push('ai-dump-signal');\n if (metrics.cadenceEntropy > 0.7) flags.push('consistent-commits');\n if (metrics.velocityDecay > 0.6) flags.push('growing');\n if (metrics.engineeringDiscipline > 0.6) flags.push('well-organized');\n\n return flags;\n}\n\n/**\n * Compute the composite \"Gives a Damn\" score from all metrics.\n *\n * Weights:\n * Maintenance (35%): entropy 40%, velocity 35%, maintainer activity 25%\n * Community (20%): owner ratio 30%, bus factor 35%, fork ratio 20%, watcher ratio 15%\n * Discipline (25%): engineering index 50%, issue close 30%, has releases 20%\n * Substance (15%): iteration density 50%, age-adjusted stars 50%\n * + Anti-pattern penalties\n */\nexport function computeCompositeScore(\n metrics: AllMetrics,\n penaltyCtx: PenaltyContext,\n contributorCount: number,\n): CompositeResult {\n // Sub-scores (each 0-1)\n const maintenance =\n metrics.cadenceEntropy * 0.40 +\n metrics.velocityDecay * 0.35 +\n metrics.maintainerActivity * 0.25;\n\n const community =\n metrics.ownerCommunityRatio * 0.30 +\n metrics.busFactor * 0.35 +\n metrics.forkStarRatio * 0.20 +\n metrics.watcherStarRatio * 0.15;\n\n const discipline =\n metrics.engineeringDiscipline * 0.50 +\n metrics.issueCloseRatio * 0.30 +\n (metrics.engineeringDiscipline > 0 ? 0.20 : 0); // has releases component baked into discipline\n\n const substance =\n metrics.codeIterationDensity * 0.50 +\n metrics.ageAdjustedStarRate * 0.50;\n\n // Weighted combination\n const raw =\n maintenance * 0.35 +\n community * 0.20 +\n discipline * 0.25 +\n substance * 0.15;\n\n // Apply penalties\n const penalty = computePenalty(penaltyCtx, metrics.velocityDecay);\n const score = Math.round(clamp(raw * penalty * 100, 0, 100));\n\n const flags = generateFlags(metrics, penaltyCtx, contributorCount);\n\n return {\n score,\n subScores: {\n maintenance: Math.round(maintenance * 100) / 100,\n community: Math.round(community * 100) / 100,\n discipline: Math.round(discipline * 100) / 100,\n substance: Math.round(substance * 100) / 100,\n },\n flags,\n };\n}\n\n// ============================================================================\n// Convenience: Compute All Metrics from Raw Data\n// ============================================================================\n\nexport interface RawRepoData {\n readonly stars: number;\n readonly forks: number;\n readonly watchers: number;\n readonly sizeKb: number;\n readonly createdAt: Date;\n readonly archived: boolean;\n readonly hasLicense: boolean;\n readonly closedIssues: number;\n readonly totalIssues: number;\n readonly totalCommits: number;\n readonly contributorCommits: readonly number[];\n readonly allWeeklyCommits: readonly number[];\n readonly ownerWeeklyCommits: readonly number[];\n readonly disciplineFlags: DisciplineFlags;\n}\n\nexport function computeAllMetrics(data: RawRepoData): AllMetrics {\n return {\n cadenceEntropy: commitCadenceEntropy(data.allWeeklyCommits),\n velocityDecay: velocityDecay(data.allWeeklyCommits),\n busFactor: busFactor(data.contributorCommits),\n contributionDiversity: contributionDiversity(data.contributorCommits),\n ownerCommunityRatio: ownerCommunityRatio(data.ownerWeeklyCommits, data.allWeeklyCommits),\n engineeringDiscipline: engineeringDiscipline(data.disciplineFlags),\n codeIterationDensity: codeIterationDensity(data.sizeKb, data.totalCommits),\n forkStarRatio: forkStarRatio(data.forks, data.stars),\n watcherStarRatio: watcherStarRatio(data.watchers, data.stars),\n issueCloseRatio: issueCloseRatio(data.closedIssues, data.totalIssues),\n maintainerActivity: maintainerActivity(data.ownerWeeklyCommits),\n ageAdjustedStarRate: ageAdjustedStarRate(data.stars, data.createdAt),\n };\n}\n\nexport function scoreRepo(data: RawRepoData): CompositeResult {\n const metrics = computeAllMetrics(data);\n return computeCompositeScore(\n metrics,\n {\n archived: data.archived,\n hasLicense: data.hasLicense,\n totalCommits: data.totalCommits,\n sizeKb: data.sizeKb,\n },\n data.contributorCommits.length,\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File attachment service for reading and formatting file contents
|
|
3
|
+
*/
|
|
4
|
+
interface FileAttachment {
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly start_line?: number | undefined;
|
|
7
|
+
readonly end_line?: number | undefined;
|
|
8
|
+
readonly description?: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
export declare class FileAttachmentService {
|
|
11
|
+
/**
|
|
12
|
+
* Format multiple file attachments into a markdown section
|
|
13
|
+
*/
|
|
14
|
+
formatAttachments(attachments: FileAttachment[]): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Format a single file attachment
|
|
17
|
+
*/
|
|
18
|
+
private formatSingleFile;
|
|
19
|
+
/**
|
|
20
|
+
* Format code block with line numbers
|
|
21
|
+
*/
|
|
22
|
+
private formatCodeBlock;
|
|
23
|
+
/**
|
|
24
|
+
* Validate line range and return corrected values
|
|
25
|
+
*/
|
|
26
|
+
private validateLineRange;
|
|
27
|
+
private detectLanguage;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=file-attachment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-attachment.d.ts","sourceRoot":"","sources":["../../../src/services/file-attachment.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,UAAU,cAAc;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AASD,qBAAa,qBAAqB;IAChC;;OAEG;IACG,iBAAiB,CAAC,WAAW,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBvE;;OAEG;YACW,gBAAgB;IAsE9B;;OAEG;IACH,OAAO,CAAC,eAAe;IAYvB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoEzB,OAAO,CAAC,cAAc;CAcvB"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import { extname } from "node:path";
|
|
3
|
+
import { pMap } from "../utils/concurrency.js";
|
|
4
|
+
const FILE_READ_CONCURRENCY = 5;
|
|
5
|
+
class FileAttachmentService {
|
|
6
|
+
/**
|
|
7
|
+
* Format multiple file attachments into a markdown section
|
|
8
|
+
*/
|
|
9
|
+
async formatAttachments(attachments) {
|
|
10
|
+
if (!attachments || attachments.length === 0) {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
const results = await pMap(attachments, (attachment) => this.formatSingleFile(attachment), FILE_READ_CONCURRENCY);
|
|
14
|
+
const parts = ["\n\n---\n\n# \u{1F4CE} ATTACHED FILES\n\n"];
|
|
15
|
+
parts.push(`*${results.length} file${results.length > 1 ? "s" : ""} attached for context*
|
|
16
|
+
|
|
17
|
+
`);
|
|
18
|
+
for (const result of results) {
|
|
19
|
+
parts.push(result.content);
|
|
20
|
+
parts.push("\n\n");
|
|
21
|
+
}
|
|
22
|
+
return parts.join("");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Format a single file attachment
|
|
26
|
+
*/
|
|
27
|
+
async formatSingleFile(attachment) {
|
|
28
|
+
const { path, start_line, end_line, description } = attachment;
|
|
29
|
+
try {
|
|
30
|
+
await access(path);
|
|
31
|
+
} catch {
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
path,
|
|
35
|
+
content: `## \u274C ${path}
|
|
36
|
+
|
|
37
|
+
**FILE NOT FOUND**
|
|
38
|
+
${description ? `
|
|
39
|
+
*Description:* ${description}
|
|
40
|
+
` : ""}`,
|
|
41
|
+
error: "File not found"
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const content = await readFile(path, "utf-8");
|
|
46
|
+
const lines = content.split("\n");
|
|
47
|
+
const language = this.detectLanguage(path);
|
|
48
|
+
const validatedRange = this.validateLineRange(start_line, end_line, lines.length);
|
|
49
|
+
if (!validatedRange.valid) {
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
path,
|
|
53
|
+
content: `## \u26A0\uFE0F ${path}
|
|
54
|
+
|
|
55
|
+
**INVALID LINE RANGE**: ${validatedRange.error}
|
|
56
|
+
${description ? `
|
|
57
|
+
*Description:* ${description}
|
|
58
|
+
` : ""}`,
|
|
59
|
+
error: validatedRange.error
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const startIdx = validatedRange.start - 1;
|
|
63
|
+
const endIdx = validatedRange.end - 1;
|
|
64
|
+
const selectedLines = lines.slice(startIdx, endIdx + 1);
|
|
65
|
+
let formatted = `## \u{1F4C4} ${path}
|
|
66
|
+
|
|
67
|
+
`;
|
|
68
|
+
const isPartial = start_line !== void 0 || end_line !== void 0;
|
|
69
|
+
formatted += `**Language:** ${language} | `;
|
|
70
|
+
formatted += `**Lines:** ${isPartial ? `${validatedRange.start}-${validatedRange.end}` : lines.length} | `;
|
|
71
|
+
formatted += `**Size:** ${(content.length / 1024).toFixed(2)} KB
|
|
72
|
+
`;
|
|
73
|
+
if (description) {
|
|
74
|
+
formatted += `
|
|
75
|
+
*${description}*
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
formatted += "\n";
|
|
79
|
+
formatted += this.formatCodeBlock(selectedLines, language, startIdx);
|
|
80
|
+
return {
|
|
81
|
+
success: true,
|
|
82
|
+
path,
|
|
83
|
+
content: formatted
|
|
84
|
+
};
|
|
85
|
+
} catch {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
path,
|
|
89
|
+
content: `## \u274C ${path}
|
|
90
|
+
|
|
91
|
+
**ERROR READING FILE**: Unable to read the specified file. Please verify the file path exists and is accessible.
|
|
92
|
+
${description ? `
|
|
93
|
+
*Description:* ${description}
|
|
94
|
+
` : ""}`,
|
|
95
|
+
error: "Unable to read the specified file"
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Format code block with line numbers
|
|
101
|
+
*/
|
|
102
|
+
formatCodeBlock(lines, language, startIdx) {
|
|
103
|
+
const parts = [`\`\`\`${language.toLowerCase()}
|
|
104
|
+
`];
|
|
105
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
106
|
+
const lineNumber = startIdx + idx + 1;
|
|
107
|
+
parts.push(`${lineNumber.toString().padStart(4, " ")}: ${lines[idx]}
|
|
108
|
+
`);
|
|
109
|
+
}
|
|
110
|
+
parts.push("```");
|
|
111
|
+
return parts.join("");
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Validate line range and return corrected values
|
|
115
|
+
*/
|
|
116
|
+
validateLineRange(start_line, end_line, totalLines) {
|
|
117
|
+
if (start_line === void 0 && end_line === void 0) {
|
|
118
|
+
return { valid: true, start: 1, end: totalLines };
|
|
119
|
+
}
|
|
120
|
+
if (start_line !== void 0 && end_line === void 0) {
|
|
121
|
+
if (start_line < 1 || start_line > totalLines) {
|
|
122
|
+
return {
|
|
123
|
+
valid: false,
|
|
124
|
+
start: 1,
|
|
125
|
+
end: totalLines,
|
|
126
|
+
error: `start_line ${start_line} out of range (1-${totalLines})`
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return { valid: true, start: start_line, end: totalLines };
|
|
130
|
+
}
|
|
131
|
+
if (start_line === void 0 && end_line !== void 0) {
|
|
132
|
+
if (end_line < 1 || end_line > totalLines) {
|
|
133
|
+
return {
|
|
134
|
+
valid: false,
|
|
135
|
+
start: 1,
|
|
136
|
+
end: totalLines,
|
|
137
|
+
error: `end_line ${end_line} out of range (1-${totalLines})`
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return { valid: true, start: 1, end: end_line };
|
|
141
|
+
}
|
|
142
|
+
if (start_line !== void 0 && end_line !== void 0) {
|
|
143
|
+
if (start_line < 1 || start_line > totalLines) {
|
|
144
|
+
return {
|
|
145
|
+
valid: false,
|
|
146
|
+
start: 1,
|
|
147
|
+
end: totalLines,
|
|
148
|
+
error: `start_line ${start_line} out of range (1-${totalLines})`
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (end_line < 1 || end_line > totalLines) {
|
|
152
|
+
return {
|
|
153
|
+
valid: false,
|
|
154
|
+
start: 1,
|
|
155
|
+
end: totalLines,
|
|
156
|
+
error: `end_line ${end_line} out of range (1-${totalLines})`
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (start_line > end_line) {
|
|
160
|
+
return {
|
|
161
|
+
valid: false,
|
|
162
|
+
start: 1,
|
|
163
|
+
end: totalLines,
|
|
164
|
+
error: `start_line ${start_line} cannot be greater than end_line ${end_line}`
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return { valid: true, start: start_line, end: end_line };
|
|
168
|
+
}
|
|
169
|
+
return { valid: true, start: 1, end: totalLines };
|
|
170
|
+
}
|
|
171
|
+
detectLanguage(filePath) {
|
|
172
|
+
const ext = extname(filePath).toLowerCase();
|
|
173
|
+
const map = {
|
|
174
|
+
".js": "javascript",
|
|
175
|
+
".jsx": "javascript",
|
|
176
|
+
".mjs": "javascript",
|
|
177
|
+
".ts": "typescript",
|
|
178
|
+
".tsx": "typescript",
|
|
179
|
+
".py": "python",
|
|
180
|
+
".go": "go",
|
|
181
|
+
".rs": "rust",
|
|
182
|
+
".rb": "ruby",
|
|
183
|
+
".java": "java",
|
|
184
|
+
".c": "c",
|
|
185
|
+
".cpp": "cpp",
|
|
186
|
+
".h": "c",
|
|
187
|
+
".json": "json",
|
|
188
|
+
".yaml": "yaml",
|
|
189
|
+
".yml": "yaml",
|
|
190
|
+
".toml": "toml",
|
|
191
|
+
".md": "markdown",
|
|
192
|
+
".html": "html",
|
|
193
|
+
".css": "css",
|
|
194
|
+
".sql": "sql",
|
|
195
|
+
".sh": "bash",
|
|
196
|
+
".xml": "xml"
|
|
197
|
+
};
|
|
198
|
+
if (filePath.endsWith("Dockerfile")) return "dockerfile";
|
|
199
|
+
return map[ext] || "text";
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
export {
|
|
203
|
+
FileAttachmentService
|
|
204
|
+
};
|
|
205
|
+
//# sourceMappingURL=file-attachment.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/services/file-attachment.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * File attachment service for reading and formatting file contents\n */\n\nimport { access, readFile } from 'node:fs/promises';\nimport { extname } from 'node:path';\nimport { pMap } from '../utils/concurrency.js';\n\n\n/** Default concurrency for parallel file reads */\nconst FILE_READ_CONCURRENCY = 5 as const;\n\ninterface FileAttachment {\n readonly path: string;\n readonly start_line?: number | undefined;\n readonly end_line?: number | undefined;\n readonly description?: string | undefined;\n}\n\ninterface FormattedFileResult {\n readonly success: boolean;\n readonly path: string;\n readonly content: string;\n readonly error?: string | undefined;\n}\n\nexport class FileAttachmentService {\n /**\n * Format multiple file attachments into a markdown section\n */\n async formatAttachments(attachments: FileAttachment[]): Promise<string> {\n if (!attachments || attachments.length === 0) {\n return '';\n }\n\n const results = await pMap(attachments, (attachment) => this.formatSingleFile(attachment), FILE_READ_CONCURRENCY);\n\n // Build the attachments section\n const parts: string[] = ['\\n\\n---\\n\\n# \uD83D\uDCCE ATTACHED FILES\\n\\n'];\n parts.push(`*${results.length} file${results.length > 1 ? 's' : ''} attached for context*\\n\\n`);\n for (const result of results) {\n parts.push(result.content);\n parts.push('\\n\\n');\n }\n return parts.join('');\n }\n\n /**\n * Format a single file attachment\n */\n private async formatSingleFile(attachment: FileAttachment): Promise<FormattedFileResult> {\n const { path, start_line, end_line, description } = attachment;\n\n // Check if file exists\n try {\n await access(path);\n } catch {\n return {\n success: false,\n path,\n content: `## \u274C ${path}\\n\\n**FILE NOT FOUND**\\n${description ? `\\n*Description:* ${description}\\n` : ''}`,\n error: 'File not found',\n };\n }\n\n try {\n // Read file content\n const content = await readFile(path, 'utf-8');\n const lines = content.split('\\n');\n const language = this.detectLanguage(path);\n\n // Validate line ranges\n const validatedRange = this.validateLineRange(start_line, end_line, lines.length);\n if (!validatedRange.valid) {\n return {\n success: false,\n path,\n content: `## \u26A0\uFE0F ${path}\\n\\n**INVALID LINE RANGE**: ${validatedRange.error}\\n${description ? `\\n*Description:* ${description}\\n` : ''}`,\n error: validatedRange.error,\n };\n }\n\n // Extract relevant lines\n const startIdx = validatedRange.start - 1;\n const endIdx = validatedRange.end - 1;\n const selectedLines = lines.slice(startIdx, endIdx + 1);\n\n // Build formatted output\n let formatted = `## \uD83D\uDCC4 ${path}\\n\\n`;\n\n // Add metadata\n const isPartial = start_line !== undefined || end_line !== undefined;\n formatted += `**Language:** ${language} | `;\n formatted += `**Lines:** ${isPartial ? `${validatedRange.start}-${validatedRange.end}` : lines.length} | `;\n formatted += `**Size:** ${(content.length / 1024).toFixed(2)} KB\\n`;\n\n if (description) {\n formatted += `\\n*${description}*\\n`;\n }\n\n formatted += '\\n';\n\n // Add file content with line numbers\n formatted += this.formatCodeBlock(selectedLines, language, startIdx);\n\n return {\n success: true,\n path,\n content: formatted,\n };\n } catch {\n return {\n success: false,\n path,\n content: `## \u274C ${path}\\n\\n**ERROR READING FILE**: Unable to read the specified file. Please verify the file path exists and is accessible.\\n${description ? `\\n*Description:* ${description}\\n` : ''}`,\n error: 'Unable to read the specified file',\n };\n }\n }\n\n /**\n * Format code block with line numbers\n */\n private formatCodeBlock(lines: string[], language: string, startIdx: number): string {\n const parts: string[] = [`\\`\\`\\`${language.toLowerCase()}\\n`];\n\n for (let idx = 0; idx < lines.length; idx++) {\n const lineNumber = startIdx + idx + 1;\n parts.push(`${lineNumber.toString().padStart(4, ' ')}: ${lines[idx]}\\n`);\n }\n\n parts.push('```');\n return parts.join('');\n }\n\n /**\n * Validate line range and return corrected values\n */\n private validateLineRange(\n start_line: number | undefined,\n end_line: number | undefined,\n totalLines: number\n ): { valid: boolean; start: number; end: number; error?: string } {\n // No range specified - return full file\n if (start_line === undefined && end_line === undefined) {\n return { valid: true, start: 1, end: totalLines };\n }\n\n // Only start_line specified\n if (start_line !== undefined && end_line === undefined) {\n if (start_line < 1 || start_line > totalLines) {\n return {\n valid: false,\n start: 1,\n end: totalLines,\n error: `start_line ${start_line} out of range (1-${totalLines})`,\n };\n }\n return { valid: true, start: start_line, end: totalLines };\n }\n\n // Only end_line specified\n if (start_line === undefined && end_line !== undefined) {\n if (end_line < 1 || end_line > totalLines) {\n return {\n valid: false,\n start: 1,\n end: totalLines,\n error: `end_line ${end_line} out of range (1-${totalLines})`,\n };\n }\n return { valid: true, start: 1, end: end_line };\n }\n\n // Both specified\n if (start_line !== undefined && end_line !== undefined) {\n if (start_line < 1 || start_line > totalLines) {\n return {\n valid: false,\n start: 1,\n end: totalLines,\n error: `start_line ${start_line} out of range (1-${totalLines})`,\n };\n }\n if (end_line < 1 || end_line > totalLines) {\n return {\n valid: false,\n start: 1,\n end: totalLines,\n error: `end_line ${end_line} out of range (1-${totalLines})`,\n };\n }\n if (start_line > end_line) {\n return {\n valid: false,\n start: 1,\n end: totalLines,\n error: `start_line ${start_line} cannot be greater than end_line ${end_line}`,\n };\n }\n return { valid: true, start: start_line, end: end_line };\n }\n\n return { valid: true, start: 1, end: totalLines };\n }\n\n private detectLanguage(filePath: string): string {\n const ext = extname(filePath).toLowerCase();\n const map: Record<string, string> = {\n '.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript',\n '.ts': 'typescript', '.tsx': 'typescript',\n '.py': 'python', '.go': 'go', '.rs': 'rust', '.rb': 'ruby',\n '.java': 'java', '.c': 'c', '.cpp': 'cpp', '.h': 'c',\n '.json': 'json', '.yaml': 'yaml', '.yml': 'yaml', '.toml': 'toml',\n '.md': 'markdown', '.html': 'html', '.css': 'css', '.sql': 'sql',\n '.sh': 'bash', '.xml': 'xml',\n };\n if (filePath.endsWith('Dockerfile')) return 'dockerfile';\n return map[ext] || 'text';\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|