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/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ All notable changes to standup-mr are recorded here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
|
|
5
5
|
[semantic versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.7.0] - 2026-10-10
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Tool annotations.** Every MCP tool now declares `readOnlyHint`,
|
|
12
|
+
`destructiveHint`, `idempotentHint` and `openWorldHint`, so a host can tell
|
|
13
|
+
the read-only `get_standup_data` and `get_note_instructions` apart from
|
|
14
|
+
`post_standup_note`, which sends a message other people will see, and warn
|
|
15
|
+
before invoking it.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- The MCP server registers its tools with `registerTool`, which needs
|
|
20
|
+
`@modelcontextprotocol/sdk` 1.30.0 or later.
|
|
21
|
+
|
|
7
22
|
## [0.6.1] - 2026-09-02
|
|
8
23
|
|
|
9
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -12,13 +12,13 @@ pipeline is red, it opens the job log and tells you **why**.
|
|
|
12
12
|
|
|
13
13
|
## What makes it different
|
|
14
14
|
|
|
15
|
-
|
|
|
16
|
-
|
|
17
|
-
| Source
|
|
18
|
-
| Merge request state
|
|
19
|
-
| Review queue
|
|
20
|
-
| Failed pipeline
|
|
21
|
-
| Self-hosted (GitLab CE/EE, GitHub Enterprise) | varies
|
|
15
|
+
| | commit-log tools | standup-mr |
|
|
16
|
+
| --------------------------------------------- | ---------------- | ----------------------------------------------------- |
|
|
17
|
+
| Source | local `git log` | GitLab or GitHub API |
|
|
18
|
+
| Merge request state | ✗ | ready / blocked / draft / stale |
|
|
19
|
+
| Review queue | ✗ | pending only, approvals filtered out |
|
|
20
|
+
| Failed pipeline | ✗ | error lines from the job trace or the Actions job log |
|
|
21
|
+
| Self-hosted (GitLab CE/EE, GitHub Enterprise) | varies | first class |
|
|
22
22
|
|
|
23
23
|
## Use
|
|
24
24
|
|
|
@@ -36,10 +36,10 @@ npx standup-mr fetch --markdown | npx standup-mr post --google-chat "$URL"
|
|
|
36
36
|
|
|
37
37
|
### Identity
|
|
38
38
|
|
|
39
|
-
|
|
|
40
|
-
|
|
41
|
-
| Flags
|
|
42
|
-
| Env
|
|
39
|
+
| | GitHub | GitLab |
|
|
40
|
+
| ----------- | ------------------------------------------- | ------------------------------------------------------- |
|
|
41
|
+
| Flags | `--host` / `--token` | `--host` / `--token` |
|
|
42
|
+
| Env | `GITHUB_HOST` / `GITHUB_TOKEN` | `GITLAB_HOST` / `GITLAB_TOKEN` |
|
|
43
43
|
| CLI session | [`gh`](https://cli.github.com/) auth config | [`glab`](https://gitlab.com/gitlab-org/cli) auth config |
|
|
44
44
|
|
|
45
45
|
GitHub defaults to `github.com` when no host is given. GitLab has no default —
|
|
@@ -93,15 +93,15 @@ paste in the note-writing rules separately.
|
|
|
93
93
|
|
|
94
94
|
```json
|
|
95
95
|
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
"mcpServers": {
|
|
97
|
+
"standup": {
|
|
98
|
+
"command": "npx",
|
|
99
|
+
"args": ["-y", "standup-mr", "mcp"],
|
|
100
|
+
"env": {
|
|
101
|
+
"GITHUB_TOKEN": "ghp_..."
|
|
102
|
+
}
|
|
103
|
+
}
|
|
103
104
|
}
|
|
104
|
-
}
|
|
105
105
|
}
|
|
106
106
|
```
|
|
107
107
|
|
|
@@ -123,11 +123,11 @@ documentation for the current format rather than trusting this file blindly.
|
|
|
123
123
|
|
|
124
124
|
The server exposes three tools:
|
|
125
125
|
|
|
126
|
-
| Tool
|
|
127
|
-
|
|
128
|
-
| `get_standup_data`
|
|
126
|
+
| Tool | What it does |
|
|
127
|
+
| ----------------------- | -------------------------------------------------------------------------------------------- |
|
|
128
|
+
| `get_standup_data` | Reads the provider and returns the report as JSON. Optional `provider`, `host`, `lang`. |
|
|
129
129
|
| `get_note_instructions` | Returns the note-writing rules, so the assistant can write the note the way the skill would. |
|
|
130
|
-
| `post_standup_note`
|
|
130
|
+
| `post_standup_note` | Posts a finished note to a Slack, Discord or Google Chat webhook. |
|
|
131
131
|
|
|
132
132
|
`post_standup_note` reads the webhook URL from `STANDUP_WEBHOOK_URL` and never
|
|
133
133
|
takes it as an argument — anyone holding that URL can post to the channel, so
|
|
@@ -196,8 +196,8 @@ _Structured digest — not a written note._
|
|
|
196
196
|
## Blockers
|
|
197
197
|
|
|
198
198
|
- `acme/mobile` !6 — job `quality`
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
- `npm ERR! code E404`
|
|
200
|
+
- `npm ERR! 404 Not Found - GET https://registry.example.com/@acme%2fui`
|
|
201
201
|
```
|
|
202
202
|
|
|
203
203
|
The last section is the point of the tool. Every other standup tool can tell you
|
|
@@ -232,7 +232,7 @@ pipe `standup fetch` into anything, or import the package, read
|
|
|
232
232
|
returns `null` with no error.
|
|
233
233
|
- `MergeRequest`, `Review` and `Blocker` carry a required `provider` field, and
|
|
234
234
|
`Provider.getReviews` takes an `Identity` rather than a numeric id.
|
|
235
|
-
- The MCP `CollectOptions.provider` is now a provider
|
|
235
|
+
- The MCP `CollectOptions.provider` is now a provider _name_; inject a
|
|
236
236
|
`Provider` instance through `providerImpl`.
|
|
237
237
|
|
|
238
238
|
Claude Code plugin users should run `/plugin marketplace update standup-mr` —
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/buckets/buckets.constants.ts
|
|
4
|
-
var STALE_DAYS = 14;
|
|
5
|
-
|
|
6
3
|
// src/dates/dates.constants.ts
|
|
7
4
|
var DAYS = {
|
|
8
|
-
en: [
|
|
9
|
-
|
|
5
|
+
en: [
|
|
6
|
+
"Sunday",
|
|
7
|
+
"Monday",
|
|
8
|
+
"Tuesday",
|
|
9
|
+
"Wednesday",
|
|
10
|
+
"Thursday",
|
|
11
|
+
"Friday",
|
|
12
|
+
"Saturday"
|
|
13
|
+
],
|
|
14
|
+
tr: [
|
|
15
|
+
"Pazar",
|
|
16
|
+
"Pazartesi",
|
|
17
|
+
"Sal\u0131",
|
|
18
|
+
"\xC7ar\u015Famba",
|
|
19
|
+
"Per\u015Fembe",
|
|
20
|
+
"Cuma",
|
|
21
|
+
"Cumartesi"
|
|
22
|
+
]
|
|
10
23
|
};
|
|
11
24
|
var MONTHS = {
|
|
12
25
|
en: [
|
|
@@ -77,10 +90,14 @@ function previousActiveDays(eventDates, today) {
|
|
|
77
90
|
}));
|
|
78
91
|
}
|
|
79
92
|
|
|
93
|
+
// src/buckets/buckets.constants.ts
|
|
94
|
+
var STALE_DAYS = 14;
|
|
95
|
+
|
|
80
96
|
// src/buckets/buckets.ts
|
|
81
97
|
function classify(mr, today, staleDays = STALE_DAYS) {
|
|
82
98
|
if (mr.draft) return "draft";
|
|
83
|
-
if (mr.pipeline === "failed" || mr.pipeline === "canceled" || mr.unresolved > 0)
|
|
99
|
+
if (mr.pipeline === "failed" || mr.pipeline === "canceled" || mr.unresolved > 0)
|
|
100
|
+
return "blocked";
|
|
84
101
|
const age = Math.round(
|
|
85
102
|
(Date.parse(`${isoDay(today)}T00:00:00Z`) - Date.parse(`${mr.updated}T00:00:00Z`)) / MS_PER_DAY
|
|
86
103
|
);
|
|
@@ -169,7 +186,9 @@ function cliStatus(cli) {
|
|
|
169
186
|
}
|
|
170
187
|
}
|
|
171
188
|
function parseLoggedInHosts(statusOutput) {
|
|
172
|
-
const hosts = [...statusOutput.matchAll(/Logged in to (\S+)/g)].map(
|
|
189
|
+
const hosts = [...statusOutput.matchAll(/Logged in to (\S+)/g)].map(
|
|
190
|
+
(match) => match[1]
|
|
191
|
+
);
|
|
173
192
|
return [...new Set(hosts)].sort();
|
|
174
193
|
}
|
|
175
194
|
var parseGlabHosts = parseLoggedInHosts;
|
|
@@ -186,14 +205,6 @@ function ghToken(host) {
|
|
|
186
205
|
return cliCapture("gh", ["auth", "token", "--hostname", host]);
|
|
187
206
|
}
|
|
188
207
|
|
|
189
|
-
// src/notify/notify.constants.ts
|
|
190
|
-
var PAYLOAD_FIELD = {
|
|
191
|
-
slack: "text",
|
|
192
|
-
discord: "content",
|
|
193
|
-
"google-chat": "text"
|
|
194
|
-
};
|
|
195
|
-
var WEBHOOK_KINDS = Object.keys(PAYLOAD_FIELD);
|
|
196
|
-
|
|
197
208
|
// src/render/chat.ts
|
|
198
209
|
var MARKDOWN_NATIVE = ["discord"];
|
|
199
210
|
var FENCE = /^```/;
|
|
@@ -214,6 +225,14 @@ function toChatText(markdown, kind) {
|
|
|
214
225
|
}).join("\n");
|
|
215
226
|
}
|
|
216
227
|
|
|
228
|
+
// src/notify/notify.constants.ts
|
|
229
|
+
var PAYLOAD_FIELD = {
|
|
230
|
+
"slack": "text",
|
|
231
|
+
"discord": "content",
|
|
232
|
+
"google-chat": "text"
|
|
233
|
+
};
|
|
234
|
+
var WEBHOOK_KINDS = Object.keys(PAYLOAD_FIELD);
|
|
235
|
+
|
|
217
236
|
// src/notify/notify.ts
|
|
218
237
|
async function postWebhook(url, text, kind = "slack", fetchImpl = fetch) {
|
|
219
238
|
const field = PAYLOAD_FIELD[kind];
|
|
@@ -228,7 +247,9 @@ async function postWebhook(url, text, kind = "slack", fetchImpl = fetch) {
|
|
|
228
247
|
body: JSON.stringify({ [field]: toChatText(text, kind) })
|
|
229
248
|
});
|
|
230
249
|
if (!response.ok) {
|
|
231
|
-
throw new Error(
|
|
250
|
+
throw new Error(
|
|
251
|
+
`Webhook rejected the message: HTTP ${response.status}.`
|
|
252
|
+
);
|
|
232
253
|
}
|
|
233
254
|
}
|
|
234
255
|
var WEBHOOK_HOSTS = {
|
|
@@ -279,17 +300,25 @@ function retryAfter(response) {
|
|
|
279
300
|
const value = response.headers.get("retry-after");
|
|
280
301
|
if (!value) return "";
|
|
281
302
|
const seconds = Number(value);
|
|
282
|
-
if (!Number.isFinite(seconds) || seconds <= 0)
|
|
303
|
+
if (!Number.isFinite(seconds) || seconds <= 0)
|
|
304
|
+
return `; retry after ${value}`;
|
|
283
305
|
return `; retry after ${seconds}s`;
|
|
284
306
|
}
|
|
285
307
|
function assertUsable(response, host) {
|
|
286
308
|
if (response.ok || response.status === 404) return;
|
|
287
309
|
if (response.status === 403 || response.status === 429) {
|
|
288
310
|
if (remaining(response) === "0") {
|
|
289
|
-
throw new ApiError(
|
|
311
|
+
throw new ApiError(
|
|
312
|
+
`${host} rate limit reached${resetAt(response)}.`,
|
|
313
|
+
response.status
|
|
314
|
+
);
|
|
290
315
|
}
|
|
291
316
|
const retry = retryAfter(response);
|
|
292
|
-
if (retry)
|
|
317
|
+
if (retry)
|
|
318
|
+
throw new ApiError(
|
|
319
|
+
`${host} rate limit reached${retry}.`,
|
|
320
|
+
response.status
|
|
321
|
+
);
|
|
293
322
|
}
|
|
294
323
|
if (response.status === 401) {
|
|
295
324
|
throw new ApiError(
|
|
@@ -490,8 +519,8 @@ var GitHubProvider = class {
|
|
|
490
519
|
}
|
|
491
520
|
headers() {
|
|
492
521
|
return {
|
|
493
|
-
authorization: `Bearer ${this.token}`,
|
|
494
|
-
accept: "application/vnd.github+json",
|
|
522
|
+
"authorization": `Bearer ${this.token}`,
|
|
523
|
+
"accept": "application/vnd.github+json",
|
|
495
524
|
"x-github-api-version": API_VERSION
|
|
496
525
|
};
|
|
497
526
|
}
|
|
@@ -543,11 +572,14 @@ var GitHubProvider = class {
|
|
|
543
572
|
return rows;
|
|
544
573
|
}
|
|
545
574
|
async getLogText(path) {
|
|
546
|
-
const first = await this.send(buildUrl(this.api, path), {
|
|
575
|
+
const first = await this.send(buildUrl(this.api, path), {
|
|
576
|
+
redirect: "manual"
|
|
577
|
+
});
|
|
547
578
|
if (first.status === 404) return "";
|
|
548
579
|
const location = first.headers.get("location");
|
|
549
580
|
if (!location) {
|
|
550
|
-
if (first.status === 0 || first.status >= 300 && first.status < 400)
|
|
581
|
+
if (first.status === 0 || first.status >= 300 && first.status < 400)
|
|
582
|
+
return "";
|
|
551
583
|
assertUsable(first, this.host);
|
|
552
584
|
return first.ok ? await first.text() : "";
|
|
553
585
|
}
|
|
@@ -590,7 +622,9 @@ var GitHubProvider = class {
|
|
|
590
622
|
async shapePr(item) {
|
|
591
623
|
const project = repoFromUrl(item.repository_url);
|
|
592
624
|
const iid = item.number;
|
|
593
|
-
const pull = await this.getJson(
|
|
625
|
+
const pull = await this.getJson(
|
|
626
|
+
`repos/${project}/pulls/${iid}`
|
|
627
|
+
);
|
|
594
628
|
if (!pull) return null;
|
|
595
629
|
const sha = pull.head?.sha ?? "";
|
|
596
630
|
const [checks, reviews] = await Promise.all([
|
|
@@ -598,9 +632,15 @@ var GitHubProvider = class {
|
|
|
598
632
|
`repos/${project}/commits/${sha}/check-runs`,
|
|
599
633
|
{ per_page: PAGE_SIZE }
|
|
600
634
|
) : Promise.resolve(null),
|
|
601
|
-
this.getPaged(
|
|
635
|
+
this.getPaged(
|
|
636
|
+
`repos/${project}/pulls/${iid}/reviews`,
|
|
637
|
+
{},
|
|
638
|
+
2
|
|
639
|
+
)
|
|
602
640
|
]);
|
|
603
|
-
const { pipeline, pipelineId } = normalizeChecks(
|
|
641
|
+
const { pipeline, pipelineId } = normalizeChecks(
|
|
642
|
+
checks?.check_runs ?? []
|
|
643
|
+
);
|
|
604
644
|
return {
|
|
605
645
|
provider: "github",
|
|
606
646
|
project,
|
|
@@ -639,7 +679,9 @@ var GitHubProvider = class {
|
|
|
639
679
|
`is:pr is:open review-requested:${identity.username}`,
|
|
640
680
|
SEARCH_CAP
|
|
641
681
|
);
|
|
642
|
-
const cutoff = isoDay(
|
|
682
|
+
const cutoff = isoDay(
|
|
683
|
+
new Date(today.getTime() - FRESH_REVIEW_DAYS * MS_PER_DAY)
|
|
684
|
+
);
|
|
643
685
|
const rows = await Promise.all(
|
|
644
686
|
items.map(async (item) => {
|
|
645
687
|
const project = repoFromUrl(item.repository_url);
|
|
@@ -705,7 +747,10 @@ var GitHubProvider = class {
|
|
|
705
747
|
(run) => run.conclusion !== null && FAILED_CONCLUSIONS.has(run.conclusion)
|
|
706
748
|
);
|
|
707
749
|
if (!failed) return null;
|
|
708
|
-
const summary = [
|
|
750
|
+
const summary = [
|
|
751
|
+
failed.output?.summary ?? "",
|
|
752
|
+
failed.output?.text ?? ""
|
|
753
|
+
].filter(Boolean).join("\n");
|
|
709
754
|
const errors = extractErrors(summary);
|
|
710
755
|
if (errors.length === 0) return null;
|
|
711
756
|
return {
|
|
@@ -818,7 +863,9 @@ var GitLabProvider = class {
|
|
|
818
863
|
const raw = await this.getPaged("events", {
|
|
819
864
|
after: isoDay(since)
|
|
820
865
|
});
|
|
821
|
-
const ids = [
|
|
866
|
+
const ids = [
|
|
867
|
+
...new Set(raw.map((e) => e.project_id).filter(Boolean))
|
|
868
|
+
];
|
|
822
869
|
const paths = new Map(
|
|
823
870
|
await Promise.all(
|
|
824
871
|
ids.map(async (id) => [id, await this.projectPath(id)])
|
|
@@ -829,7 +876,7 @@ var GitLabProvider = class {
|
|
|
829
876
|
return {
|
|
830
877
|
at: String(event.created_at).slice(0, 16),
|
|
831
878
|
action: event.action_name,
|
|
832
|
-
project: paths.get(event.project_id) ?? "",
|
|
879
|
+
project: paths.get(event.project_id ?? -1) ?? "",
|
|
833
880
|
targetType: event.target_type ?? "",
|
|
834
881
|
title: event.target_title ?? "",
|
|
835
882
|
branch: push.ref ?? "",
|
|
@@ -841,7 +888,9 @@ var GitLabProvider = class {
|
|
|
841
888
|
async countUnresolved(projectId, iid) {
|
|
842
889
|
const discussions = await this.getJson(
|
|
843
890
|
`projects/${projectId}/merge_requests/${iid}/discussions`,
|
|
844
|
-
{
|
|
891
|
+
{
|
|
892
|
+
per_page: PAGE_SIZE2
|
|
893
|
+
}
|
|
845
894
|
);
|
|
846
895
|
if (!discussions) return 0;
|
|
847
896
|
return discussions.filter((discussion) => {
|
|
@@ -894,7 +943,9 @@ var GitLabProvider = class {
|
|
|
894
943
|
async approvedByMe(projectId, iid, uid) {
|
|
895
944
|
const approvals = await this.getJson(`projects/${projectId}/merge_requests/${iid}/approvals`);
|
|
896
945
|
if (!approvals) return false;
|
|
897
|
-
return (approvals.approved_by ?? []).some(
|
|
946
|
+
return (approvals.approved_by ?? []).some(
|
|
947
|
+
(entry) => entry.user?.id === uid
|
|
948
|
+
);
|
|
898
949
|
}
|
|
899
950
|
async getReviews(identity, today) {
|
|
900
951
|
const raw = await this.getPaged("merge_requests", {
|
|
@@ -902,7 +953,9 @@ var GitLabProvider = class {
|
|
|
902
953
|
state: "opened",
|
|
903
954
|
reviewer_id: identity.id
|
|
904
955
|
});
|
|
905
|
-
const cutoff = isoDay(
|
|
956
|
+
const cutoff = isoDay(
|
|
957
|
+
new Date(today.getTime() - FRESH_REVIEW_DAYS2 * MS_PER_DAY)
|
|
958
|
+
);
|
|
906
959
|
const rows = await Promise.all(
|
|
907
960
|
raw.map(async (mr) => ({
|
|
908
961
|
provider: "gitlab",
|
|
@@ -914,7 +967,11 @@ var GitLabProvider = class {
|
|
|
914
967
|
draft: mr.draft,
|
|
915
968
|
url: mr.web_url,
|
|
916
969
|
fresh: String(mr.updated_at).slice(0, 10) >= cutoff,
|
|
917
|
-
approvedByMe: await this.approvedByMe(
|
|
970
|
+
approvedByMe: await this.approvedByMe(
|
|
971
|
+
mr.project_id,
|
|
972
|
+
mr.iid,
|
|
973
|
+
identity.id
|
|
974
|
+
)
|
|
918
975
|
}))
|
|
919
976
|
);
|
|
920
977
|
return rows.sort((a, b) => b.updated.localeCompare(a.updated));
|
|
@@ -926,7 +983,9 @@ var GitLabProvider = class {
|
|
|
926
983
|
) ?? [];
|
|
927
984
|
const job = jobs.find((j) => j.status === "failed");
|
|
928
985
|
if (!job) return null;
|
|
929
|
-
const trace = await this.getText(
|
|
986
|
+
const trace = await this.getText(
|
|
987
|
+
`projects/${mr.projectId}/jobs/${job.id}/trace`
|
|
988
|
+
);
|
|
930
989
|
return {
|
|
931
990
|
provider: "gitlab",
|
|
932
991
|
project: mr.project,
|
|
@@ -959,7 +1018,12 @@ function hostFrom(cliHost, envHost, probe, fallback, labels) {
|
|
|
959
1018
|
if (cliHost) return cliHost;
|
|
960
1019
|
if (envHost) return envHost;
|
|
961
1020
|
const found = probe();
|
|
962
|
-
return resolveHost(
|
|
1021
|
+
return resolveHost(
|
|
1022
|
+
void 0,
|
|
1023
|
+
void 0,
|
|
1024
|
+
found.length > 0 ? found : fallback,
|
|
1025
|
+
labels
|
|
1026
|
+
);
|
|
963
1027
|
}
|
|
964
1028
|
function chooseKind(options = {}) {
|
|
965
1029
|
const env = options.env ?? process.env;
|
|
@@ -1012,7 +1076,13 @@ function connect(options = {}) {
|
|
|
1012
1076
|
const env = options.env ?? process.env;
|
|
1013
1077
|
const probe = options.probe ?? DEFAULT_PROBE;
|
|
1014
1078
|
if (chooseKind(options) === "github") {
|
|
1015
|
-
const host2 = hostFrom(
|
|
1079
|
+
const host2 = hostFrom(
|
|
1080
|
+
options.host,
|
|
1081
|
+
env.GITHUB_HOST,
|
|
1082
|
+
probe.github,
|
|
1083
|
+
[DOT_COM],
|
|
1084
|
+
GITHUB_LABELS
|
|
1085
|
+
);
|
|
1016
1086
|
const token2 = resolveToken(
|
|
1017
1087
|
host2,
|
|
1018
1088
|
options.token,
|
|
@@ -1022,7 +1092,13 @@ function connect(options = {}) {
|
|
|
1022
1092
|
);
|
|
1023
1093
|
return new GitHubProvider(host2, token2);
|
|
1024
1094
|
}
|
|
1025
|
-
const host = hostFrom(
|
|
1095
|
+
const host = hostFrom(
|
|
1096
|
+
options.host,
|
|
1097
|
+
env.GITLAB_HOST,
|
|
1098
|
+
probe.gitlab,
|
|
1099
|
+
[],
|
|
1100
|
+
GITLAB_LABELS
|
|
1101
|
+
);
|
|
1026
1102
|
const token = resolveToken(
|
|
1027
1103
|
host,
|
|
1028
1104
|
options.token,
|
|
@@ -1086,10 +1162,18 @@ function eventLine(event) {
|
|
|
1086
1162
|
}
|
|
1087
1163
|
function toMarkdown(report, lang = "en") {
|
|
1088
1164
|
const base = STRINGS[lang] ?? STRINGS.en;
|
|
1089
|
-
const t = {
|
|
1165
|
+
const t = {
|
|
1166
|
+
...base,
|
|
1167
|
+
...PROVIDER_STRINGS[report.provider][lang] ?? {}
|
|
1168
|
+
};
|
|
1090
1169
|
const ref = REF_PREFIX[report.provider];
|
|
1091
1170
|
const out = [];
|
|
1092
|
-
out.push(
|
|
1171
|
+
out.push(
|
|
1172
|
+
`# ${report.today.label} \u2014 ${report.user}`,
|
|
1173
|
+
"",
|
|
1174
|
+
`_${t.digest}_`,
|
|
1175
|
+
""
|
|
1176
|
+
);
|
|
1093
1177
|
if (report.previousDays.length === 0) {
|
|
1094
1178
|
out.push(`## ${t.previous}`, "", `_${t.nothing}_`, "");
|
|
1095
1179
|
}
|
|
@@ -1116,7 +1200,10 @@ function toMarkdown(report, lang = "en") {
|
|
|
1116
1200
|
}
|
|
1117
1201
|
const pending = report.reviews.filter((r) => !r.approvedByMe);
|
|
1118
1202
|
if (pending.length > 0) {
|
|
1119
|
-
out.push(
|
|
1203
|
+
out.push(
|
|
1204
|
+
`## ${t.reviews} (${report.reviewPendingCount} ${t.pending})`,
|
|
1205
|
+
""
|
|
1206
|
+
);
|
|
1120
1207
|
for (const review of pending) {
|
|
1121
1208
|
out.push(
|
|
1122
1209
|
`- \`${review.project}\` ${ref}${review.iid} ${review.title} \u2014 ${review.author}`
|
|
@@ -1127,7 +1214,9 @@ function toMarkdown(report, lang = "en") {
|
|
|
1127
1214
|
if (report.blockers.length > 0) {
|
|
1128
1215
|
out.push(`## ${t.blockers}`, "");
|
|
1129
1216
|
for (const blocker of report.blockers) {
|
|
1130
|
-
out.push(
|
|
1217
|
+
out.push(
|
|
1218
|
+
`- \`${blocker.project}\` ${ref}${blocker.mr} \u2014 job \`${blocker.job}\``
|
|
1219
|
+
);
|
|
1131
1220
|
out.push(...blocker.errors.map((line) => ` - \`${line}\``));
|
|
1132
1221
|
}
|
|
1133
1222
|
out.push("");
|
|
@@ -1195,21 +1284,29 @@ import { dirname as dirname2, join as join2 } from "path";
|
|
|
1195
1284
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1196
1285
|
function readStandupSkillBody() {
|
|
1197
1286
|
const moduleDir = dirname2(fileURLToPath2(import.meta.url));
|
|
1198
|
-
const skillPath = join2(
|
|
1287
|
+
const skillPath = join2(
|
|
1288
|
+
findPackageRoot(moduleDir),
|
|
1289
|
+
"skills",
|
|
1290
|
+
"standup",
|
|
1291
|
+
"SKILL.md"
|
|
1292
|
+
);
|
|
1199
1293
|
if (!existsSync2(skillPath)) {
|
|
1200
1294
|
throw new Error(`Cannot find the standup skill file at ${skillPath}`);
|
|
1201
1295
|
}
|
|
1202
1296
|
const content = readFileSync2(skillPath, "utf8");
|
|
1203
|
-
const withoutFrontmatter = content.replace(
|
|
1297
|
+
const withoutFrontmatter = content.replace(
|
|
1298
|
+
/^---\r?\n[\s\S]*?\r?\n---\r?\n/,
|
|
1299
|
+
""
|
|
1300
|
+
);
|
|
1204
1301
|
return withoutFrontmatter.replace(/^\n+/, "");
|
|
1205
1302
|
}
|
|
1206
1303
|
|
|
1207
1304
|
export {
|
|
1208
|
-
STALE_DAYS,
|
|
1209
1305
|
isoDay,
|
|
1210
1306
|
localAt,
|
|
1211
1307
|
label,
|
|
1212
1308
|
previousActiveDays,
|
|
1309
|
+
STALE_DAYS,
|
|
1213
1310
|
classify,
|
|
1214
1311
|
markMissingPipelines,
|
|
1215
1312
|
ConfigError,
|
|
@@ -1223,8 +1320,8 @@ export {
|
|
|
1223
1320
|
glabToken,
|
|
1224
1321
|
ghHosts,
|
|
1225
1322
|
ghToken,
|
|
1226
|
-
WEBHOOK_KINDS,
|
|
1227
1323
|
toChatText,
|
|
1324
|
+
WEBHOOK_KINDS,
|
|
1228
1325
|
postWebhook,
|
|
1229
1326
|
inferWebhookKind,
|
|
1230
1327
|
buildUrl,
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
postWebhook,
|
|
9
9
|
readStandupSkillBody,
|
|
10
10
|
toMarkdown
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-J5LAGA3U.js";
|
|
12
12
|
|
|
13
13
|
// src/cli/cli.ts
|
|
14
14
|
import { realpathSync } from "fs";
|
|
@@ -64,7 +64,11 @@ async function main(argv) {
|
|
|
64
64
|
markdown: { type: "boolean", default: false }
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
|
-
const report = await buildReport(
|
|
67
|
+
const report = await buildReport(
|
|
68
|
+
connect(values),
|
|
69
|
+
/* @__PURE__ */ new Date(),
|
|
70
|
+
values.lang
|
|
71
|
+
);
|
|
68
72
|
const output = values.markdown ? toMarkdown(report, values.lang) : JSON.stringify(report, null, 1);
|
|
69
73
|
process.stdout.write(`${output}
|
|
70
74
|
`);
|
|
@@ -75,7 +79,10 @@ async function main(argv) {
|
|
|
75
79
|
args: rest,
|
|
76
80
|
options: {
|
|
77
81
|
...Object.fromEntries(
|
|
78
|
-
WEBHOOK_KINDS.map((kind2) => [
|
|
82
|
+
WEBHOOK_KINDS.map((kind2) => [
|
|
83
|
+
kind2,
|
|
84
|
+
{ type: "string" }
|
|
85
|
+
])
|
|
79
86
|
),
|
|
80
87
|
text: { type: "string", default: "-" }
|
|
81
88
|
}
|
|
@@ -83,7 +90,9 @@ async function main(argv) {
|
|
|
83
90
|
const urls = values;
|
|
84
91
|
const kind = WEBHOOK_KINDS.find((candidate) => urls[candidate]);
|
|
85
92
|
if (!kind) {
|
|
86
|
-
const flags = WEBHOOK_KINDS.map(
|
|
93
|
+
const flags = WEBHOOK_KINDS.map(
|
|
94
|
+
(candidate) => `--${candidate} URL`
|
|
95
|
+
).join(" | ");
|
|
87
96
|
process.stderr.write(`Pass one of: ${flags}.
|
|
88
97
|
`);
|
|
89
98
|
return 1;
|
|
@@ -99,7 +108,12 @@ async function main(argv) {
|
|
|
99
108
|
}
|
|
100
109
|
if (command === "mcp") {
|
|
101
110
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
102
|
-
const serverPath = join(
|
|
111
|
+
const serverPath = join(
|
|
112
|
+
findPackageRoot(moduleDir),
|
|
113
|
+
"dist",
|
|
114
|
+
"mcp",
|
|
115
|
+
"server.js"
|
|
116
|
+
);
|
|
103
117
|
const serverUrl = pathToFileURL(serverPath).href;
|
|
104
118
|
const { main: startMcpServer } = await import(serverUrl);
|
|
105
119
|
await startMcpServer();
|