standup-mr 0.6.1 → 0.7.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/CHANGELOG.md +15 -0
- package/README.md +26 -26
- package/dist/{chunk-NJO6LFYN.js → chunk-J5LAGA3U.js} +143 -46
- package/dist/cli.js +19 -5
- package/dist/index.d.ts +60 -60
- package/dist/index.js +1 -1
- package/dist/mcp/server.js +194 -70
- package/llms-install.md +12 -12
- package/mcp/README.md +27 -27
- package/package.json +15 -3
package/dist/mcp/server.js
CHANGED
|
@@ -24,21 +24,15 @@ function findPackageRoot(startDir) {
|
|
|
24
24
|
}
|
|
25
25
|
function packageVersion(moduleUrl) {
|
|
26
26
|
const root = findPackageRoot(dirname(fileURLToPath(moduleUrl)));
|
|
27
|
-
const manifest = JSON.parse(
|
|
27
|
+
const manifest = JSON.parse(
|
|
28
|
+
readFileSync(join(root, "package.json"), "utf8")
|
|
29
|
+
);
|
|
28
30
|
if (!manifest.version) {
|
|
29
31
|
throw new Error(`package.json at ${root} has no version`);
|
|
30
32
|
}
|
|
31
33
|
return manifest.version;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
// src/notify/notify.constants.ts
|
|
35
|
-
var PAYLOAD_FIELD = {
|
|
36
|
-
slack: "text",
|
|
37
|
-
discord: "content",
|
|
38
|
-
"google-chat": "text"
|
|
39
|
-
};
|
|
40
|
-
var WEBHOOK_KINDS = Object.keys(PAYLOAD_FIELD);
|
|
41
|
-
|
|
42
36
|
// src/render/chat.ts
|
|
43
37
|
var MARKDOWN_NATIVE = ["discord"];
|
|
44
38
|
var FENCE = /^```/;
|
|
@@ -59,6 +53,14 @@ function toChatText(markdown, kind) {
|
|
|
59
53
|
}).join("\n");
|
|
60
54
|
}
|
|
61
55
|
|
|
56
|
+
// src/notify/notify.constants.ts
|
|
57
|
+
var PAYLOAD_FIELD = {
|
|
58
|
+
"slack": "text",
|
|
59
|
+
"discord": "content",
|
|
60
|
+
"google-chat": "text"
|
|
61
|
+
};
|
|
62
|
+
var WEBHOOK_KINDS = Object.keys(PAYLOAD_FIELD);
|
|
63
|
+
|
|
62
64
|
// src/notify/notify.ts
|
|
63
65
|
async function postWebhook(url, text2, kind = "slack", fetchImpl = fetch) {
|
|
64
66
|
const field = PAYLOAD_FIELD[kind];
|
|
@@ -73,7 +75,9 @@ async function postWebhook(url, text2, kind = "slack", fetchImpl = fetch) {
|
|
|
73
75
|
body: JSON.stringify({ [field]: toChatText(text2, kind) })
|
|
74
76
|
});
|
|
75
77
|
if (!response.ok) {
|
|
76
|
-
throw new Error(
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Webhook rejected the message: HTTP ${response.status}.`
|
|
80
|
+
);
|
|
77
81
|
}
|
|
78
82
|
}
|
|
79
83
|
var WEBHOOK_HOSTS = {
|
|
@@ -168,7 +172,9 @@ function cliStatus(cli) {
|
|
|
168
172
|
}
|
|
169
173
|
}
|
|
170
174
|
function parseLoggedInHosts(statusOutput) {
|
|
171
|
-
const hosts = [...statusOutput.matchAll(/Logged in to (\S+)/g)].map(
|
|
175
|
+
const hosts = [...statusOutput.matchAll(/Logged in to (\S+)/g)].map(
|
|
176
|
+
(match) => match[1]
|
|
177
|
+
);
|
|
172
178
|
return [...new Set(hosts)].sort();
|
|
173
179
|
}
|
|
174
180
|
function glabHosts() {
|
|
@@ -184,13 +190,26 @@ function ghToken(host) {
|
|
|
184
190
|
return cliCapture("gh", ["auth", "token", "--hostname", host]);
|
|
185
191
|
}
|
|
186
192
|
|
|
187
|
-
// src/buckets/buckets.constants.ts
|
|
188
|
-
var STALE_DAYS = 14;
|
|
189
|
-
|
|
190
193
|
// src/dates/dates.constants.ts
|
|
191
194
|
var DAYS = {
|
|
192
|
-
en: [
|
|
193
|
-
|
|
195
|
+
en: [
|
|
196
|
+
"Sunday",
|
|
197
|
+
"Monday",
|
|
198
|
+
"Tuesday",
|
|
199
|
+
"Wednesday",
|
|
200
|
+
"Thursday",
|
|
201
|
+
"Friday",
|
|
202
|
+
"Saturday"
|
|
203
|
+
],
|
|
204
|
+
tr: [
|
|
205
|
+
"Pazar",
|
|
206
|
+
"Pazartesi",
|
|
207
|
+
"Sal\u0131",
|
|
208
|
+
"\xC7ar\u015Famba",
|
|
209
|
+
"Per\u015Fembe",
|
|
210
|
+
"Cuma",
|
|
211
|
+
"Cumartesi"
|
|
212
|
+
]
|
|
194
213
|
};
|
|
195
214
|
var MONTHS = {
|
|
196
215
|
en: [
|
|
@@ -261,10 +280,14 @@ function previousActiveDays(eventDates, today) {
|
|
|
261
280
|
}));
|
|
262
281
|
}
|
|
263
282
|
|
|
283
|
+
// src/buckets/buckets.constants.ts
|
|
284
|
+
var STALE_DAYS = 14;
|
|
285
|
+
|
|
264
286
|
// src/buckets/buckets.ts
|
|
265
287
|
function classify(mr, today, staleDays = STALE_DAYS) {
|
|
266
288
|
if (mr.draft) return "draft";
|
|
267
|
-
if (mr.pipeline === "failed" || mr.pipeline === "canceled" || mr.unresolved > 0)
|
|
289
|
+
if (mr.pipeline === "failed" || mr.pipeline === "canceled" || mr.unresolved > 0)
|
|
290
|
+
return "blocked";
|
|
268
291
|
const age = Math.round(
|
|
269
292
|
(Date.parse(`${isoDay(today)}T00:00:00Z`) - Date.parse(`${mr.updated}T00:00:00Z`)) / MS_PER_DAY
|
|
270
293
|
);
|
|
@@ -350,17 +373,25 @@ function retryAfter(response) {
|
|
|
350
373
|
const value = response.headers.get("retry-after");
|
|
351
374
|
if (!value) return "";
|
|
352
375
|
const seconds = Number(value);
|
|
353
|
-
if (!Number.isFinite(seconds) || seconds <= 0)
|
|
376
|
+
if (!Number.isFinite(seconds) || seconds <= 0)
|
|
377
|
+
return `; retry after ${value}`;
|
|
354
378
|
return `; retry after ${seconds}s`;
|
|
355
379
|
}
|
|
356
380
|
function assertUsable(response, host) {
|
|
357
381
|
if (response.ok || response.status === 404) return;
|
|
358
382
|
if (response.status === 403 || response.status === 429) {
|
|
359
383
|
if (remaining(response) === "0") {
|
|
360
|
-
throw new ApiError(
|
|
384
|
+
throw new ApiError(
|
|
385
|
+
`${host} rate limit reached${resetAt(response)}.`,
|
|
386
|
+
response.status
|
|
387
|
+
);
|
|
361
388
|
}
|
|
362
389
|
const retry = retryAfter(response);
|
|
363
|
-
if (retry)
|
|
390
|
+
if (retry)
|
|
391
|
+
throw new ApiError(
|
|
392
|
+
`${host} rate limit reached${retry}.`,
|
|
393
|
+
response.status
|
|
394
|
+
);
|
|
364
395
|
}
|
|
365
396
|
if (response.status === 401) {
|
|
366
397
|
throw new ApiError(
|
|
@@ -520,8 +551,8 @@ var GitHubProvider = class {
|
|
|
520
551
|
}
|
|
521
552
|
headers() {
|
|
522
553
|
return {
|
|
523
|
-
authorization: `Bearer ${this.token}`,
|
|
524
|
-
accept: "application/vnd.github+json",
|
|
554
|
+
"authorization": `Bearer ${this.token}`,
|
|
555
|
+
"accept": "application/vnd.github+json",
|
|
525
556
|
"x-github-api-version": API_VERSION
|
|
526
557
|
};
|
|
527
558
|
}
|
|
@@ -573,11 +604,14 @@ var GitHubProvider = class {
|
|
|
573
604
|
return rows;
|
|
574
605
|
}
|
|
575
606
|
async getLogText(path) {
|
|
576
|
-
const first = await this.send(buildUrl(this.api, path), {
|
|
607
|
+
const first = await this.send(buildUrl(this.api, path), {
|
|
608
|
+
redirect: "manual"
|
|
609
|
+
});
|
|
577
610
|
if (first.status === 404) return "";
|
|
578
611
|
const location = first.headers.get("location");
|
|
579
612
|
if (!location) {
|
|
580
|
-
if (first.status === 0 || first.status >= 300 && first.status < 400)
|
|
613
|
+
if (first.status === 0 || first.status >= 300 && first.status < 400)
|
|
614
|
+
return "";
|
|
581
615
|
assertUsable(first, this.host);
|
|
582
616
|
return first.ok ? await first.text() : "";
|
|
583
617
|
}
|
|
@@ -620,7 +654,9 @@ var GitHubProvider = class {
|
|
|
620
654
|
async shapePr(item) {
|
|
621
655
|
const project = repoFromUrl(item.repository_url);
|
|
622
656
|
const iid = item.number;
|
|
623
|
-
const pull = await this.getJson(
|
|
657
|
+
const pull = await this.getJson(
|
|
658
|
+
`repos/${project}/pulls/${iid}`
|
|
659
|
+
);
|
|
624
660
|
if (!pull) return null;
|
|
625
661
|
const sha = pull.head?.sha ?? "";
|
|
626
662
|
const [checks, reviews] = await Promise.all([
|
|
@@ -628,9 +664,15 @@ var GitHubProvider = class {
|
|
|
628
664
|
`repos/${project}/commits/${sha}/check-runs`,
|
|
629
665
|
{ per_page: PAGE_SIZE }
|
|
630
666
|
) : Promise.resolve(null),
|
|
631
|
-
this.getPaged(
|
|
667
|
+
this.getPaged(
|
|
668
|
+
`repos/${project}/pulls/${iid}/reviews`,
|
|
669
|
+
{},
|
|
670
|
+
2
|
|
671
|
+
)
|
|
632
672
|
]);
|
|
633
|
-
const { pipeline, pipelineId } = normalizeChecks(
|
|
673
|
+
const { pipeline, pipelineId } = normalizeChecks(
|
|
674
|
+
checks?.check_runs ?? []
|
|
675
|
+
);
|
|
634
676
|
return {
|
|
635
677
|
provider: "github",
|
|
636
678
|
project,
|
|
@@ -669,7 +711,9 @@ var GitHubProvider = class {
|
|
|
669
711
|
`is:pr is:open review-requested:${identity.username}`,
|
|
670
712
|
SEARCH_CAP
|
|
671
713
|
);
|
|
672
|
-
const cutoff = isoDay(
|
|
714
|
+
const cutoff = isoDay(
|
|
715
|
+
new Date(today.getTime() - FRESH_REVIEW_DAYS * MS_PER_DAY)
|
|
716
|
+
);
|
|
673
717
|
const rows = await Promise.all(
|
|
674
718
|
items.map(async (item) => {
|
|
675
719
|
const project = repoFromUrl(item.repository_url);
|
|
@@ -735,7 +779,10 @@ var GitHubProvider = class {
|
|
|
735
779
|
(run) => run.conclusion !== null && FAILED_CONCLUSIONS.has(run.conclusion)
|
|
736
780
|
);
|
|
737
781
|
if (!failed) return null;
|
|
738
|
-
const summary = [
|
|
782
|
+
const summary = [
|
|
783
|
+
failed.output?.summary ?? "",
|
|
784
|
+
failed.output?.text ?? ""
|
|
785
|
+
].filter(Boolean).join("\n");
|
|
739
786
|
const errors = extractErrors(summary);
|
|
740
787
|
if (errors.length === 0) return null;
|
|
741
788
|
return {
|
|
@@ -848,7 +895,9 @@ var GitLabProvider = class {
|
|
|
848
895
|
const raw = await this.getPaged("events", {
|
|
849
896
|
after: isoDay(since)
|
|
850
897
|
});
|
|
851
|
-
const ids = [
|
|
898
|
+
const ids = [
|
|
899
|
+
...new Set(raw.map((e) => e.project_id).filter(Boolean))
|
|
900
|
+
];
|
|
852
901
|
const paths = new Map(
|
|
853
902
|
await Promise.all(
|
|
854
903
|
ids.map(async (id) => [id, await this.projectPath(id)])
|
|
@@ -859,7 +908,7 @@ var GitLabProvider = class {
|
|
|
859
908
|
return {
|
|
860
909
|
at: String(event.created_at).slice(0, 16),
|
|
861
910
|
action: event.action_name,
|
|
862
|
-
project: paths.get(event.project_id) ?? "",
|
|
911
|
+
project: paths.get(event.project_id ?? -1) ?? "",
|
|
863
912
|
targetType: event.target_type ?? "",
|
|
864
913
|
title: event.target_title ?? "",
|
|
865
914
|
branch: push.ref ?? "",
|
|
@@ -871,7 +920,9 @@ var GitLabProvider = class {
|
|
|
871
920
|
async countUnresolved(projectId, iid) {
|
|
872
921
|
const discussions = await this.getJson(
|
|
873
922
|
`projects/${projectId}/merge_requests/${iid}/discussions`,
|
|
874
|
-
{
|
|
923
|
+
{
|
|
924
|
+
per_page: PAGE_SIZE2
|
|
925
|
+
}
|
|
875
926
|
);
|
|
876
927
|
if (!discussions) return 0;
|
|
877
928
|
return discussions.filter((discussion) => {
|
|
@@ -924,7 +975,9 @@ var GitLabProvider = class {
|
|
|
924
975
|
async approvedByMe(projectId, iid, uid) {
|
|
925
976
|
const approvals = await this.getJson(`projects/${projectId}/merge_requests/${iid}/approvals`);
|
|
926
977
|
if (!approvals) return false;
|
|
927
|
-
return (approvals.approved_by ?? []).some(
|
|
978
|
+
return (approvals.approved_by ?? []).some(
|
|
979
|
+
(entry2) => entry2.user?.id === uid
|
|
980
|
+
);
|
|
928
981
|
}
|
|
929
982
|
async getReviews(identity, today) {
|
|
930
983
|
const raw = await this.getPaged("merge_requests", {
|
|
@@ -932,7 +985,9 @@ var GitLabProvider = class {
|
|
|
932
985
|
state: "opened",
|
|
933
986
|
reviewer_id: identity.id
|
|
934
987
|
});
|
|
935
|
-
const cutoff = isoDay(
|
|
988
|
+
const cutoff = isoDay(
|
|
989
|
+
new Date(today.getTime() - FRESH_REVIEW_DAYS2 * MS_PER_DAY)
|
|
990
|
+
);
|
|
936
991
|
const rows = await Promise.all(
|
|
937
992
|
raw.map(async (mr) => ({
|
|
938
993
|
provider: "gitlab",
|
|
@@ -944,7 +999,11 @@ var GitLabProvider = class {
|
|
|
944
999
|
draft: mr.draft,
|
|
945
1000
|
url: mr.web_url,
|
|
946
1001
|
fresh: String(mr.updated_at).slice(0, 10) >= cutoff,
|
|
947
|
-
approvedByMe: await this.approvedByMe(
|
|
1002
|
+
approvedByMe: await this.approvedByMe(
|
|
1003
|
+
mr.project_id,
|
|
1004
|
+
mr.iid,
|
|
1005
|
+
identity.id
|
|
1006
|
+
)
|
|
948
1007
|
}))
|
|
949
1008
|
);
|
|
950
1009
|
return rows.sort((a, b) => b.updated.localeCompare(a.updated));
|
|
@@ -956,7 +1015,9 @@ var GitLabProvider = class {
|
|
|
956
1015
|
) ?? [];
|
|
957
1016
|
const job = jobs.find((j) => j.status === "failed");
|
|
958
1017
|
if (!job) return null;
|
|
959
|
-
const trace = await this.getText(
|
|
1018
|
+
const trace = await this.getText(
|
|
1019
|
+
`projects/${mr.projectId}/jobs/${job.id}/trace`
|
|
1020
|
+
);
|
|
960
1021
|
return {
|
|
961
1022
|
provider: "gitlab",
|
|
962
1023
|
project: mr.project,
|
|
@@ -989,7 +1050,12 @@ function hostFrom(cliHost, envHost, probe, fallback, labels) {
|
|
|
989
1050
|
if (cliHost) return cliHost;
|
|
990
1051
|
if (envHost) return envHost;
|
|
991
1052
|
const found = probe();
|
|
992
|
-
return resolveHost(
|
|
1053
|
+
return resolveHost(
|
|
1054
|
+
void 0,
|
|
1055
|
+
void 0,
|
|
1056
|
+
found.length > 0 ? found : fallback,
|
|
1057
|
+
labels
|
|
1058
|
+
);
|
|
993
1059
|
}
|
|
994
1060
|
function chooseKind(options = {}) {
|
|
995
1061
|
const env = options.env ?? process.env;
|
|
@@ -1042,7 +1108,13 @@ function connect(options = {}) {
|
|
|
1042
1108
|
const env = options.env ?? process.env;
|
|
1043
1109
|
const probe = options.probe ?? DEFAULT_PROBE;
|
|
1044
1110
|
if (chooseKind(options) === "github") {
|
|
1045
|
-
const host2 = hostFrom(
|
|
1111
|
+
const host2 = hostFrom(
|
|
1112
|
+
options.host,
|
|
1113
|
+
env.GITHUB_HOST,
|
|
1114
|
+
probe.github,
|
|
1115
|
+
[DOT_COM],
|
|
1116
|
+
GITHUB_LABELS
|
|
1117
|
+
);
|
|
1046
1118
|
const token2 = resolveToken(
|
|
1047
1119
|
host2,
|
|
1048
1120
|
options.token,
|
|
@@ -1052,7 +1124,13 @@ function connect(options = {}) {
|
|
|
1052
1124
|
);
|
|
1053
1125
|
return new GitHubProvider(host2, token2);
|
|
1054
1126
|
}
|
|
1055
|
-
const host = hostFrom(
|
|
1127
|
+
const host = hostFrom(
|
|
1128
|
+
options.host,
|
|
1129
|
+
env.GITLAB_HOST,
|
|
1130
|
+
probe.gitlab,
|
|
1131
|
+
[],
|
|
1132
|
+
GITLAB_LABELS
|
|
1133
|
+
);
|
|
1056
1134
|
const token = resolveToken(
|
|
1057
1135
|
host,
|
|
1058
1136
|
options.token,
|
|
@@ -1063,21 +1141,6 @@ function connect(options = {}) {
|
|
|
1063
1141
|
return new GitLabProvider(host, token);
|
|
1064
1142
|
}
|
|
1065
1143
|
|
|
1066
|
-
// src/skill/skill.ts
|
|
1067
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
1068
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
1069
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1070
|
-
function readStandupSkillBody() {
|
|
1071
|
-
const moduleDir = dirname2(fileURLToPath2(import.meta.url));
|
|
1072
|
-
const skillPath = join2(findPackageRoot(moduleDir), "skills", "standup", "SKILL.md");
|
|
1073
|
-
if (!existsSync2(skillPath)) {
|
|
1074
|
-
throw new Error(`Cannot find the standup skill file at ${skillPath}`);
|
|
1075
|
-
}
|
|
1076
|
-
const content = readFileSync2(skillPath, "utf8");
|
|
1077
|
-
const withoutFrontmatter = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "");
|
|
1078
|
-
return withoutFrontmatter.replace(/^\n+/, "");
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
1144
|
// src/report/report.constants.ts
|
|
1082
1145
|
var LOOKBACK_DAYS = 21;
|
|
1083
1146
|
|
|
@@ -1115,9 +1178,36 @@ async function buildReport(provider, today, lang = "en", lookbackDays = LOOKBACK
|
|
|
1115
1178
|
};
|
|
1116
1179
|
}
|
|
1117
1180
|
|
|
1181
|
+
// src/skill/skill.ts
|
|
1182
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
1183
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
1184
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1185
|
+
function readStandupSkillBody() {
|
|
1186
|
+
const moduleDir = dirname2(fileURLToPath2(import.meta.url));
|
|
1187
|
+
const skillPath = join2(
|
|
1188
|
+
findPackageRoot(moduleDir),
|
|
1189
|
+
"skills",
|
|
1190
|
+
"standup",
|
|
1191
|
+
"SKILL.md"
|
|
1192
|
+
);
|
|
1193
|
+
if (!existsSync2(skillPath)) {
|
|
1194
|
+
throw new Error(`Cannot find the standup skill file at ${skillPath}`);
|
|
1195
|
+
}
|
|
1196
|
+
const content = readFileSync2(skillPath, "utf8");
|
|
1197
|
+
const withoutFrontmatter = content.replace(
|
|
1198
|
+
/^---\r?\n[\s\S]*?\r?\n---\r?\n/,
|
|
1199
|
+
""
|
|
1200
|
+
);
|
|
1201
|
+
return withoutFrontmatter.replace(/^\n+/, "");
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1118
1204
|
// mcp/server.ts
|
|
1119
1205
|
async function collect(options = {}) {
|
|
1120
|
-
const provider = options.providerImpl ?? connect({
|
|
1206
|
+
const provider = options.providerImpl ?? connect({
|
|
1207
|
+
provider: options.provider,
|
|
1208
|
+
host: options.host,
|
|
1209
|
+
token: options.token
|
|
1210
|
+
});
|
|
1121
1211
|
return buildReport(provider, /* @__PURE__ */ new Date(), options.lang ?? "en");
|
|
1122
1212
|
}
|
|
1123
1213
|
var STANDUP_TOOL_DESCRIPTION = 'Collect merge-request-based standup data from GitLab or GitHub. Returns one JSON object: `today`, `previousDays[]` with the events of each, `todayEvents[]`, `myMrs[]` bucketed ready / blocked / draft / stale, `reviews[]` waiting on you, and `blockers[]` carrying the error lines read out of each failed pipeline job log.\n\nCall it once at the start of a working day, to write a standup note. It is a snapshot, not a search API: it cannot fetch one named merge request, reach further back than the previous working day, or filter by project.\n\nRead-only, and credentials never come from an argument \u2014 they come from the environment or a logged-in gh / glab session. A rejected token, a refused resource or a rate limit fails the call with the host\'s own message, after two retries on transient server errors. A blocker whose diagnosis could not be fetched is still returned, with `job: "unknown"`, so a red pipeline is never silently dropped.';
|
|
@@ -1138,7 +1228,9 @@ async function runStandupTool(args, collector = collect) {
|
|
|
1138
1228
|
host: args.host,
|
|
1139
1229
|
lang: args.lang
|
|
1140
1230
|
});
|
|
1141
|
-
return {
|
|
1231
|
+
return {
|
|
1232
|
+
content: [{ type: "text", text: JSON.stringify(report) }]
|
|
1233
|
+
};
|
|
1142
1234
|
}
|
|
1143
1235
|
var WEBHOOK_URL_ENV = "STANDUP_WEBHOOK_URL";
|
|
1144
1236
|
var POST_TOOL_DESCRIPTION = `Posts a finished standup note to a chat webhook. This one has a side effect: it sends a message other people will see, so only call it on a note the user has agreed to send.
|
|
@@ -1185,27 +1277,56 @@ async function runPostTool(args, deps = {}) {
|
|
|
1185
1277
|
async function runInstructionsTool() {
|
|
1186
1278
|
return text(readStandupSkillBody());
|
|
1187
1279
|
}
|
|
1188
|
-
|
|
1189
|
-
const server = new McpServer({
|
|
1190
|
-
|
|
1280
|
+
function createServer() {
|
|
1281
|
+
const server = new McpServer({
|
|
1282
|
+
name: "standup-mr",
|
|
1283
|
+
version: packageVersion(import.meta.url)
|
|
1284
|
+
});
|
|
1285
|
+
server.registerTool(
|
|
1191
1286
|
"get_standup_data",
|
|
1192
|
-
|
|
1193
|
-
|
|
1287
|
+
{
|
|
1288
|
+
description: STANDUP_TOOL_DESCRIPTION,
|
|
1289
|
+
inputSchema: STANDUP_TOOL_SCHEMA,
|
|
1290
|
+
annotations: {
|
|
1291
|
+
readOnlyHint: true,
|
|
1292
|
+
destructiveHint: false,
|
|
1293
|
+
idempotentHint: true,
|
|
1294
|
+
openWorldHint: true
|
|
1295
|
+
}
|
|
1296
|
+
},
|
|
1194
1297
|
async (args) => await runStandupTool(args)
|
|
1195
1298
|
);
|
|
1196
|
-
server.
|
|
1299
|
+
server.registerTool(
|
|
1197
1300
|
"post_standup_note",
|
|
1198
|
-
|
|
1199
|
-
|
|
1301
|
+
{
|
|
1302
|
+
description: POST_TOOL_DESCRIPTION,
|
|
1303
|
+
inputSchema: POST_TOOL_SCHEMA,
|
|
1304
|
+
annotations: {
|
|
1305
|
+
readOnlyHint: false,
|
|
1306
|
+
destructiveHint: false,
|
|
1307
|
+
idempotentHint: false,
|
|
1308
|
+
openWorldHint: true
|
|
1309
|
+
}
|
|
1310
|
+
},
|
|
1200
1311
|
async (args) => await runPostTool(args)
|
|
1201
1312
|
);
|
|
1202
|
-
server.
|
|
1313
|
+
server.registerTool(
|
|
1203
1314
|
"get_note_instructions",
|
|
1204
|
-
|
|
1205
|
-
|
|
1315
|
+
{
|
|
1316
|
+
description: INSTRUCTIONS_TOOL_DESCRIPTION,
|
|
1317
|
+
annotations: {
|
|
1318
|
+
readOnlyHint: true,
|
|
1319
|
+
destructiveHint: false,
|
|
1320
|
+
idempotentHint: true,
|
|
1321
|
+
openWorldHint: false
|
|
1322
|
+
}
|
|
1323
|
+
},
|
|
1206
1324
|
async () => await runInstructionsTool()
|
|
1207
1325
|
);
|
|
1208
|
-
|
|
1326
|
+
return server;
|
|
1327
|
+
}
|
|
1328
|
+
async function main() {
|
|
1329
|
+
await createServer().connect(new StdioServerTransport());
|
|
1209
1330
|
}
|
|
1210
1331
|
var entry = process.argv[1];
|
|
1211
1332
|
var entryUrl;
|
|
@@ -1216,8 +1337,10 @@ try {
|
|
|
1216
1337
|
}
|
|
1217
1338
|
if (entryUrl && import.meta.url === entryUrl) {
|
|
1218
1339
|
main().catch((error) => {
|
|
1219
|
-
process.stderr.write(
|
|
1220
|
-
|
|
1340
|
+
process.stderr.write(
|
|
1341
|
+
`${error instanceof Error ? error.message : String(error)}
|
|
1342
|
+
`
|
|
1343
|
+
);
|
|
1221
1344
|
process.exitCode = 1;
|
|
1222
1345
|
});
|
|
1223
1346
|
}
|
|
@@ -1229,6 +1352,7 @@ export {
|
|
|
1229
1352
|
STANDUP_TOOL_SCHEMA,
|
|
1230
1353
|
WEBHOOK_URL_ENV,
|
|
1231
1354
|
collect,
|
|
1355
|
+
createServer,
|
|
1232
1356
|
main,
|
|
1233
1357
|
runInstructionsTool,
|
|
1234
1358
|
runPostTool,
|
package/llms-install.md
CHANGED
|
@@ -18,13 +18,13 @@ Add this to the MCP settings file (for Cline,
|
|
|
18
18
|
|
|
19
19
|
```json
|
|
20
20
|
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"standup": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["-y", "standup-mr", "mcp"],
|
|
25
|
+
"env": {}
|
|
26
|
+
}
|
|
26
27
|
}
|
|
27
|
-
}
|
|
28
28
|
}
|
|
29
29
|
```
|
|
30
30
|
|
|
@@ -35,12 +35,12 @@ That is the whole install when the user already has `gh` or `glab` logged in
|
|
|
35
35
|
|
|
36
36
|
Add only the pair the user actually needs.
|
|
37
37
|
|
|
38
|
-
| Variable
|
|
39
|
-
|
|
40
|
-
| `GITHUB_TOKEN`
|
|
41
|
-
| `GITHUB_HOST`
|
|
42
|
-
| `GITLAB_TOKEN`
|
|
43
|
-
| `GITLAB_HOST`
|
|
38
|
+
| Variable | When |
|
|
39
|
+
| ------------------ | -------------------------------------------------------------------------- |
|
|
40
|
+
| `GITHUB_TOKEN` | GitHub, no `gh` session |
|
|
41
|
+
| `GITHUB_HOST` | GitHub Enterprise only; defaults to `github.com` |
|
|
42
|
+
| `GITLAB_TOKEN` | GitLab, no `glab` session |
|
|
43
|
+
| `GITLAB_HOST` | **Required for GitLab** — there is no default host |
|
|
44
44
|
| `STANDUP_PROVIDER` | `github` or `gitlab`, when both are configured and the choice is ambiguous |
|
|
45
45
|
|
|
46
46
|
Ask the user for a token; do not invent one, and do not put a token anywhere
|
package/mcp/README.md
CHANGED
|
@@ -9,16 +9,16 @@ GitLab:
|
|
|
9
9
|
|
|
10
10
|
```json
|
|
11
11
|
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
"mcpServers": {
|
|
13
|
+
"standup": {
|
|
14
|
+
"command": "npx",
|
|
15
|
+
"args": ["-y", "standup-mr", "mcp"],
|
|
16
|
+
"env": {
|
|
17
|
+
"GITLAB_HOST": "gitlab.example.com",
|
|
18
|
+
"GITLAB_TOKEN": "glpat-..."
|
|
19
|
+
}
|
|
20
|
+
}
|
|
20
21
|
}
|
|
21
|
-
}
|
|
22
22
|
}
|
|
23
23
|
```
|
|
24
24
|
|
|
@@ -26,16 +26,16 @@ GitHub:
|
|
|
26
26
|
|
|
27
27
|
```json
|
|
28
28
|
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"standup": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["-y", "standup-mr", "mcp"],
|
|
33
|
+
"env": {
|
|
34
|
+
"GITHUB_HOST": "github.com",
|
|
35
|
+
"GITHUB_TOKEN": "ghp_..."
|
|
36
|
+
}
|
|
37
|
+
}
|
|
37
38
|
}
|
|
38
|
-
}
|
|
39
39
|
}
|
|
40
40
|
```
|
|
41
41
|
|
|
@@ -46,16 +46,16 @@ If you're working from a local clone instead of the published package, point
|
|
|
46
46
|
|
|
47
47
|
```json
|
|
48
48
|
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
"mcpServers": {
|
|
50
|
+
"standup": {
|
|
51
|
+
"command": "node",
|
|
52
|
+
"args": ["/absolute/path/to/standup-mr/dist/mcp/server.js"],
|
|
53
|
+
"env": {
|
|
54
|
+
"GITHUB_HOST": "github.com",
|
|
55
|
+
"GITHUB_TOKEN": "ghp_..."
|
|
56
|
+
}
|
|
57
|
+
}
|
|
57
58
|
}
|
|
58
|
-
}
|
|
59
59
|
}
|
|
60
60
|
```
|
|
61
61
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "standup-mr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"mcpName": "io.github.Jubstaaa/standup-mr",
|
|
5
5
|
"description": "Standup notes from merge request state, not commit logs.",
|
|
6
6
|
"keywords": [
|
|
@@ -55,19 +55,31 @@
|
|
|
55
55
|
],
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "tsup",
|
|
58
|
+
"lint": "eslint .",
|
|
59
|
+
"format": "prettier . --write",
|
|
60
|
+
"format:check": "prettier . --check",
|
|
58
61
|
"test": "bun test",
|
|
59
62
|
"test:tz": "TZ=Europe/Istanbul bun test ./src/dates/dates.tz.istanbul.cases.ts && TZ=America/Los_Angeles bun test ./src/dates/dates.tz.los-angeles.cases.ts",
|
|
60
63
|
"typecheck": "tsc --noEmit",
|
|
61
64
|
"prepublishOnly": "tsup"
|
|
62
65
|
},
|
|
63
66
|
"dependencies": {
|
|
64
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
67
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
65
68
|
"zod": "^4.4.3"
|
|
66
69
|
},
|
|
67
70
|
"devDependencies": {
|
|
71
|
+
"@eslint/js": "^9.39.1",
|
|
68
72
|
"@types/bun": "^1.0.0",
|
|
69
73
|
"@types/node": "^22.0.0",
|
|
74
|
+
"eslint": "^9.39.1",
|
|
75
|
+
"eslint-config-prettier": "^10.1.8",
|
|
76
|
+
"eslint-plugin-import": "^2.32.0",
|
|
77
|
+
"eslint-plugin-perfectionist": "^5.5.0",
|
|
78
|
+
"eslint-plugin-prettier": "^5.5.6",
|
|
79
|
+
"globals": "^17.12.0",
|
|
80
|
+
"prettier": "^3.9.6",
|
|
70
81
|
"tsup": "^8.0.0",
|
|
71
|
-
"typescript": "^5.5.0"
|
|
82
|
+
"typescript": "^5.5.0",
|
|
83
|
+
"typescript-eslint": "^8.44.0"
|
|
72
84
|
}
|
|
73
85
|
}
|