standup-mr 0.6.0 → 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 CHANGED
@@ -4,6 +4,33 @@ 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
+
22
+ ## [0.6.1] - 2026-09-02
23
+
24
+ ### Fixed
25
+
26
+ - A note posted to Google Chat or Slack arrived with its formatting showing:
27
+ `**bold**` as literal asterisks, `## Dün` as a literal heading marker.
28
+ Neither renders standard Markdown — both use a single asterisk for bold and
29
+ have no headings — while the note is Markdown throughout. Bold is now
30
+ converted and headings become bold lines for those two. Discord speaks
31
+ Markdown natively and is sent untouched, and fenced blocks pass through
32
+ verbatim so a code sample keeps its asterisks.
33
+
7
34
  ## [0.6.0] - 2026-09-02
8
35
 
9
36
  ### Added
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
- | | 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 |
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
- | | GitHub | GitLab |
40
- |---|---|---|
41
- | Flags | `--host` / `--token` | `--host` / `--token` |
42
- | Env | `GITHUB_HOST` / `GITHUB_TOKEN` | `GITLAB_HOST` / `GITLAB_TOKEN` |
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
- "mcpServers": {
97
- "standup": {
98
- "command": "npx",
99
- "args": ["-y", "standup-mr", "mcp"],
100
- "env": {
101
- "GITHUB_TOKEN": "ghp_..."
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 | What it does |
127
- |---|---|
128
- | `get_standup_data` | Reads the provider and returns the report as JSON. Optional `provider`, `host`, `lang`. |
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` | Posts a finished note to a Slack, Discord or Google Chat webhook. |
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
@@ -135,7 +135,12 @@ it belongs with the tokens, not in a transcript. The payload shape is inferred
135
135
  from the URL host; `kind` is only needed when a proxy hides it.
136
136
 
137
137
  Three shapes are implemented: `slack` and `google-chat` both post `{"text"}`,
138
- `discord` posts `{"content"}`. Anything else that accepts a Slack-shaped body —
138
+ `discord` posts `{"content"}`.
139
+
140
+ Slack and Google Chat do not render standard Markdown, so the note is rewritten
141
+ on the way out: `**bold**` becomes `*bold*` and a `##` heading becomes a bold
142
+ line. Inline code, fenced blocks and their contents are left alone. Discord
143
+ speaks Markdown natively and is sent untouched. Anything else that accepts a Slack-shaped body —
139
144
  Mattermost, Rocket.Chat, an n8n or Zapier endpoint — works today by passing
140
145
  `kind: "slack"`, or `--slack URL` on the CLI.
141
146
 
@@ -191,8 +196,8 @@ _Structured digest — not a written note._
191
196
  ## Blockers
192
197
 
193
198
  - `acme/mobile` !6 — job `quality`
194
- - `npm ERR! code E404`
195
- - `npm ERR! 404 Not Found - GET https://registry.example.com/@acme%2fui`
199
+ - `npm ERR! code E404`
200
+ - `npm ERR! 404 Not Found - GET https://registry.example.com/@acme%2fui`
196
201
  ```
197
202
 
198
203
  The last section is the point of the tool. Every other standup tool can tell you
@@ -227,7 +232,7 @@ pipe `standup fetch` into anything, or import the package, read
227
232
  returns `null` with no error.
228
233
  - `MergeRequest`, `Review` and `Blocker` carry a required `provider` field, and
229
234
  `Provider.getReviews` takes an `Identity` rather than a numeric id.
230
- - The MCP `CollectOptions.provider` is now a provider *name*; inject a
235
+ - The MCP `CollectOptions.provider` is now a provider _name_; inject a
231
236
  `Provider` instance through `providerImpl`.
232
237
 
233
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: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
9
- tr: ["Pazar", "Pazartesi", "Sal\u0131", "\xC7ar\u015Famba", "Per\u015Fembe", "Cuma", "Cumartesi"]
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) return "blocked";
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((match) => match[1]);
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,10 +205,30 @@ function ghToken(host) {
186
205
  return cliCapture("gh", ["auth", "token", "--hostname", host]);
187
206
  }
188
207
 
208
+ // src/render/chat.ts
209
+ var MARKDOWN_NATIVE = ["discord"];
210
+ var FENCE = /^```/;
211
+ function convertLine(line) {
212
+ const heading = line.match(/^#{1,6}\s+(.*)$/);
213
+ if (heading) return `*${heading[1]}*`;
214
+ return line.replace(/\*\*(?=\S)([\s\S]*?\S)\*\*/g, "*$1*");
215
+ }
216
+ function toChatText(markdown, kind) {
217
+ if (MARKDOWN_NATIVE.includes(kind)) return markdown;
218
+ let fenced = false;
219
+ return markdown.split("\n").map((line) => {
220
+ if (FENCE.test(line)) {
221
+ fenced = !fenced;
222
+ return line;
223
+ }
224
+ return fenced ? line : convertLine(line);
225
+ }).join("\n");
226
+ }
227
+
189
228
  // src/notify/notify.constants.ts
190
229
  var PAYLOAD_FIELD = {
191
- slack: "text",
192
- discord: "content",
230
+ "slack": "text",
231
+ "discord": "content",
193
232
  "google-chat": "text"
194
233
  };
195
234
  var WEBHOOK_KINDS = Object.keys(PAYLOAD_FIELD);
@@ -205,10 +244,12 @@ async function postWebhook(url, text, kind = "slack", fetchImpl = fetch) {
205
244
  const response = await fetchImpl(url, {
206
245
  method: "POST",
207
246
  headers: { "Content-Type": "application/json" },
208
- body: JSON.stringify({ [field]: text })
247
+ body: JSON.stringify({ [field]: toChatText(text, kind) })
209
248
  });
210
249
  if (!response.ok) {
211
- throw new Error(`Webhook rejected the message: HTTP ${response.status}.`);
250
+ throw new Error(
251
+ `Webhook rejected the message: HTTP ${response.status}.`
252
+ );
212
253
  }
213
254
  }
214
255
  var WEBHOOK_HOSTS = {
@@ -259,17 +300,25 @@ function retryAfter(response) {
259
300
  const value = response.headers.get("retry-after");
260
301
  if (!value) return "";
261
302
  const seconds = Number(value);
262
- if (!Number.isFinite(seconds) || seconds <= 0) return `; retry after ${value}`;
303
+ if (!Number.isFinite(seconds) || seconds <= 0)
304
+ return `; retry after ${value}`;
263
305
  return `; retry after ${seconds}s`;
264
306
  }
265
307
  function assertUsable(response, host) {
266
308
  if (response.ok || response.status === 404) return;
267
309
  if (response.status === 403 || response.status === 429) {
268
310
  if (remaining(response) === "0") {
269
- throw new ApiError(`${host} rate limit reached${resetAt(response)}.`, response.status);
311
+ throw new ApiError(
312
+ `${host} rate limit reached${resetAt(response)}.`,
313
+ response.status
314
+ );
270
315
  }
271
316
  const retry = retryAfter(response);
272
- if (retry) throw new ApiError(`${host} rate limit reached${retry}.`, response.status);
317
+ if (retry)
318
+ throw new ApiError(
319
+ `${host} rate limit reached${retry}.`,
320
+ response.status
321
+ );
273
322
  }
274
323
  if (response.status === 401) {
275
324
  throw new ApiError(
@@ -470,8 +519,8 @@ var GitHubProvider = class {
470
519
  }
471
520
  headers() {
472
521
  return {
473
- authorization: `Bearer ${this.token}`,
474
- accept: "application/vnd.github+json",
522
+ "authorization": `Bearer ${this.token}`,
523
+ "accept": "application/vnd.github+json",
475
524
  "x-github-api-version": API_VERSION
476
525
  };
477
526
  }
@@ -523,11 +572,14 @@ var GitHubProvider = class {
523
572
  return rows;
524
573
  }
525
574
  async getLogText(path) {
526
- const first = await this.send(buildUrl(this.api, path), { redirect: "manual" });
575
+ const first = await this.send(buildUrl(this.api, path), {
576
+ redirect: "manual"
577
+ });
527
578
  if (first.status === 404) return "";
528
579
  const location = first.headers.get("location");
529
580
  if (!location) {
530
- if (first.status === 0 || first.status >= 300 && first.status < 400) return "";
581
+ if (first.status === 0 || first.status >= 300 && first.status < 400)
582
+ return "";
531
583
  assertUsable(first, this.host);
532
584
  return first.ok ? await first.text() : "";
533
585
  }
@@ -570,7 +622,9 @@ var GitHubProvider = class {
570
622
  async shapePr(item) {
571
623
  const project = repoFromUrl(item.repository_url);
572
624
  const iid = item.number;
573
- const pull = await this.getJson(`repos/${project}/pulls/${iid}`);
625
+ const pull = await this.getJson(
626
+ `repos/${project}/pulls/${iid}`
627
+ );
574
628
  if (!pull) return null;
575
629
  const sha = pull.head?.sha ?? "";
576
630
  const [checks, reviews] = await Promise.all([
@@ -578,9 +632,15 @@ var GitHubProvider = class {
578
632
  `repos/${project}/commits/${sha}/check-runs`,
579
633
  { per_page: PAGE_SIZE }
580
634
  ) : Promise.resolve(null),
581
- this.getPaged(`repos/${project}/pulls/${iid}/reviews`, {}, 2)
635
+ this.getPaged(
636
+ `repos/${project}/pulls/${iid}/reviews`,
637
+ {},
638
+ 2
639
+ )
582
640
  ]);
583
- const { pipeline, pipelineId } = normalizeChecks(checks?.check_runs ?? []);
641
+ const { pipeline, pipelineId } = normalizeChecks(
642
+ checks?.check_runs ?? []
643
+ );
584
644
  return {
585
645
  provider: "github",
586
646
  project,
@@ -619,7 +679,9 @@ var GitHubProvider = class {
619
679
  `is:pr is:open review-requested:${identity.username}`,
620
680
  SEARCH_CAP
621
681
  );
622
- const cutoff = isoDay(new Date(today.getTime() - FRESH_REVIEW_DAYS * MS_PER_DAY));
682
+ const cutoff = isoDay(
683
+ new Date(today.getTime() - FRESH_REVIEW_DAYS * MS_PER_DAY)
684
+ );
623
685
  const rows = await Promise.all(
624
686
  items.map(async (item) => {
625
687
  const project = repoFromUrl(item.repository_url);
@@ -685,7 +747,10 @@ var GitHubProvider = class {
685
747
  (run) => run.conclusion !== null && FAILED_CONCLUSIONS.has(run.conclusion)
686
748
  );
687
749
  if (!failed) return null;
688
- const summary = [failed.output?.summary ?? "", failed.output?.text ?? ""].filter(Boolean).join("\n");
750
+ const summary = [
751
+ failed.output?.summary ?? "",
752
+ failed.output?.text ?? ""
753
+ ].filter(Boolean).join("\n");
689
754
  const errors = extractErrors(summary);
690
755
  if (errors.length === 0) return null;
691
756
  return {
@@ -798,7 +863,9 @@ var GitLabProvider = class {
798
863
  const raw = await this.getPaged("events", {
799
864
  after: isoDay(since)
800
865
  });
801
- const ids = [...new Set(raw.map((e) => e.project_id).filter(Boolean))];
866
+ const ids = [
867
+ ...new Set(raw.map((e) => e.project_id).filter(Boolean))
868
+ ];
802
869
  const paths = new Map(
803
870
  await Promise.all(
804
871
  ids.map(async (id) => [id, await this.projectPath(id)])
@@ -809,7 +876,7 @@ var GitLabProvider = class {
809
876
  return {
810
877
  at: String(event.created_at).slice(0, 16),
811
878
  action: event.action_name,
812
- project: paths.get(event.project_id) ?? "",
879
+ project: paths.get(event.project_id ?? -1) ?? "",
813
880
  targetType: event.target_type ?? "",
814
881
  title: event.target_title ?? "",
815
882
  branch: push.ref ?? "",
@@ -821,7 +888,9 @@ var GitLabProvider = class {
821
888
  async countUnresolved(projectId, iid) {
822
889
  const discussions = await this.getJson(
823
890
  `projects/${projectId}/merge_requests/${iid}/discussions`,
824
- { per_page: PAGE_SIZE2 }
891
+ {
892
+ per_page: PAGE_SIZE2
893
+ }
825
894
  );
826
895
  if (!discussions) return 0;
827
896
  return discussions.filter((discussion) => {
@@ -874,7 +943,9 @@ var GitLabProvider = class {
874
943
  async approvedByMe(projectId, iid, uid) {
875
944
  const approvals = await this.getJson(`projects/${projectId}/merge_requests/${iid}/approvals`);
876
945
  if (!approvals) return false;
877
- return (approvals.approved_by ?? []).some((entry) => entry.user?.id === uid);
946
+ return (approvals.approved_by ?? []).some(
947
+ (entry) => entry.user?.id === uid
948
+ );
878
949
  }
879
950
  async getReviews(identity, today) {
880
951
  const raw = await this.getPaged("merge_requests", {
@@ -882,7 +953,9 @@ var GitLabProvider = class {
882
953
  state: "opened",
883
954
  reviewer_id: identity.id
884
955
  });
885
- const cutoff = isoDay(new Date(today.getTime() - FRESH_REVIEW_DAYS2 * MS_PER_DAY));
956
+ const cutoff = isoDay(
957
+ new Date(today.getTime() - FRESH_REVIEW_DAYS2 * MS_PER_DAY)
958
+ );
886
959
  const rows = await Promise.all(
887
960
  raw.map(async (mr) => ({
888
961
  provider: "gitlab",
@@ -894,7 +967,11 @@ var GitLabProvider = class {
894
967
  draft: mr.draft,
895
968
  url: mr.web_url,
896
969
  fresh: String(mr.updated_at).slice(0, 10) >= cutoff,
897
- approvedByMe: await this.approvedByMe(mr.project_id, mr.iid, identity.id)
970
+ approvedByMe: await this.approvedByMe(
971
+ mr.project_id,
972
+ mr.iid,
973
+ identity.id
974
+ )
898
975
  }))
899
976
  );
900
977
  return rows.sort((a, b) => b.updated.localeCompare(a.updated));
@@ -906,7 +983,9 @@ var GitLabProvider = class {
906
983
  ) ?? [];
907
984
  const job = jobs.find((j) => j.status === "failed");
908
985
  if (!job) return null;
909
- const trace = await this.getText(`projects/${mr.projectId}/jobs/${job.id}/trace`);
986
+ const trace = await this.getText(
987
+ `projects/${mr.projectId}/jobs/${job.id}/trace`
988
+ );
910
989
  return {
911
990
  provider: "gitlab",
912
991
  project: mr.project,
@@ -939,7 +1018,12 @@ function hostFrom(cliHost, envHost, probe, fallback, labels) {
939
1018
  if (cliHost) return cliHost;
940
1019
  if (envHost) return envHost;
941
1020
  const found = probe();
942
- return resolveHost(void 0, void 0, found.length > 0 ? found : fallback, labels);
1021
+ return resolveHost(
1022
+ void 0,
1023
+ void 0,
1024
+ found.length > 0 ? found : fallback,
1025
+ labels
1026
+ );
943
1027
  }
944
1028
  function chooseKind(options = {}) {
945
1029
  const env = options.env ?? process.env;
@@ -992,7 +1076,13 @@ function connect(options = {}) {
992
1076
  const env = options.env ?? process.env;
993
1077
  const probe = options.probe ?? DEFAULT_PROBE;
994
1078
  if (chooseKind(options) === "github") {
995
- const host2 = hostFrom(options.host, env.GITHUB_HOST, probe.github, [DOT_COM], GITHUB_LABELS);
1079
+ const host2 = hostFrom(
1080
+ options.host,
1081
+ env.GITHUB_HOST,
1082
+ probe.github,
1083
+ [DOT_COM],
1084
+ GITHUB_LABELS
1085
+ );
996
1086
  const token2 = resolveToken(
997
1087
  host2,
998
1088
  options.token,
@@ -1002,7 +1092,13 @@ function connect(options = {}) {
1002
1092
  );
1003
1093
  return new GitHubProvider(host2, token2);
1004
1094
  }
1005
- const host = hostFrom(options.host, env.GITLAB_HOST, probe.gitlab, [], GITLAB_LABELS);
1095
+ const host = hostFrom(
1096
+ options.host,
1097
+ env.GITLAB_HOST,
1098
+ probe.gitlab,
1099
+ [],
1100
+ GITLAB_LABELS
1101
+ );
1006
1102
  const token = resolveToken(
1007
1103
  host,
1008
1104
  options.token,
@@ -1066,10 +1162,18 @@ function eventLine(event) {
1066
1162
  }
1067
1163
  function toMarkdown(report, lang = "en") {
1068
1164
  const base = STRINGS[lang] ?? STRINGS.en;
1069
- const t = { ...base, ...PROVIDER_STRINGS[report.provider][lang] ?? {} };
1165
+ const t = {
1166
+ ...base,
1167
+ ...PROVIDER_STRINGS[report.provider][lang] ?? {}
1168
+ };
1070
1169
  const ref = REF_PREFIX[report.provider];
1071
1170
  const out = [];
1072
- out.push(`# ${report.today.label} \u2014 ${report.user}`, "", `_${t.digest}_`, "");
1171
+ out.push(
1172
+ `# ${report.today.label} \u2014 ${report.user}`,
1173
+ "",
1174
+ `_${t.digest}_`,
1175
+ ""
1176
+ );
1073
1177
  if (report.previousDays.length === 0) {
1074
1178
  out.push(`## ${t.previous}`, "", `_${t.nothing}_`, "");
1075
1179
  }
@@ -1096,7 +1200,10 @@ function toMarkdown(report, lang = "en") {
1096
1200
  }
1097
1201
  const pending = report.reviews.filter((r) => !r.approvedByMe);
1098
1202
  if (pending.length > 0) {
1099
- out.push(`## ${t.reviews} (${report.reviewPendingCount} ${t.pending})`, "");
1203
+ out.push(
1204
+ `## ${t.reviews} (${report.reviewPendingCount} ${t.pending})`,
1205
+ ""
1206
+ );
1100
1207
  for (const review of pending) {
1101
1208
  out.push(
1102
1209
  `- \`${review.project}\` ${ref}${review.iid} ${review.title} \u2014 ${review.author}`
@@ -1107,7 +1214,9 @@ function toMarkdown(report, lang = "en") {
1107
1214
  if (report.blockers.length > 0) {
1108
1215
  out.push(`## ${t.blockers}`, "");
1109
1216
  for (const blocker of report.blockers) {
1110
- out.push(`- \`${blocker.project}\` ${ref}${blocker.mr} \u2014 job \`${blocker.job}\``);
1217
+ out.push(
1218
+ `- \`${blocker.project}\` ${ref}${blocker.mr} \u2014 job \`${blocker.job}\``
1219
+ );
1111
1220
  out.push(...blocker.errors.map((line) => ` - \`${line}\``));
1112
1221
  }
1113
1222
  out.push("");
@@ -1175,21 +1284,29 @@ import { dirname as dirname2, join as join2 } from "path";
1175
1284
  import { fileURLToPath as fileURLToPath2 } from "url";
1176
1285
  function readStandupSkillBody() {
1177
1286
  const moduleDir = dirname2(fileURLToPath2(import.meta.url));
1178
- const skillPath = join2(findPackageRoot(moduleDir), "skills", "standup", "SKILL.md");
1287
+ const skillPath = join2(
1288
+ findPackageRoot(moduleDir),
1289
+ "skills",
1290
+ "standup",
1291
+ "SKILL.md"
1292
+ );
1179
1293
  if (!existsSync2(skillPath)) {
1180
1294
  throw new Error(`Cannot find the standup skill file at ${skillPath}`);
1181
1295
  }
1182
1296
  const content = readFileSync2(skillPath, "utf8");
1183
- const withoutFrontmatter = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "");
1297
+ const withoutFrontmatter = content.replace(
1298
+ /^---\r?\n[\s\S]*?\r?\n---\r?\n/,
1299
+ ""
1300
+ );
1184
1301
  return withoutFrontmatter.replace(/^\n+/, "");
1185
1302
  }
1186
1303
 
1187
1304
  export {
1188
- STALE_DAYS,
1189
1305
  isoDay,
1190
1306
  localAt,
1191
1307
  label,
1192
1308
  previousActiveDays,
1309
+ STALE_DAYS,
1193
1310
  classify,
1194
1311
  markMissingPipelines,
1195
1312
  ConfigError,
@@ -1203,6 +1320,7 @@ export {
1203
1320
  glabToken,
1204
1321
  ghHosts,
1205
1322
  ghToken,
1323
+ toChatText,
1206
1324
  WEBHOOK_KINDS,
1207
1325
  postWebhook,
1208
1326
  inferWebhookKind,
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  postWebhook,
9
9
  readStandupSkillBody,
10
10
  toMarkdown
11
- } from "./chunk-MB7KWLUR.js";
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(connect(values), /* @__PURE__ */ new Date(), values.lang);
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) => [kind2, { type: "string" }])
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((candidate) => `--${candidate} URL`).join(" | ");
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(findPackageRoot(moduleDir), "dist", "mcp", "server.js");
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();