standup-mr 1.0.0 → 1.1.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 +45 -0
- package/README.md +33 -11
- package/dist/{chunk-S7DAKN36.js → chunk-RPKMLYHH.js} +7 -7
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +153 -1
- package/dist/mcp/server.js +48 -7
- package/mcp/README.md +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,51 @@ 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
|
+
## [1.1.0] - 2026-10-12
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **A compact chat digest for scheduled posts.** Scheduled and test posts now
|
|
12
|
+
send `toChatDigest` instead of the full Markdown report: one line per piece
|
|
13
|
+
of work from the last active day (repeated pushes collapsed, merges marked,
|
|
14
|
+
branch-deletion and merge-commit noise dropped), ready MRs grouped by
|
|
15
|
+
project, the reason each blocked MR is stuck, and a one-line count of
|
|
16
|
+
drafts, stale MRs and waiting reviews. The CLI's `--markdown` output is
|
|
17
|
+
unchanged.
|
|
18
|
+
|
|
19
|
+
- **Scheduled posting on the hosted connector.** Once a webhook is saved, the
|
|
20
|
+
connect page can post the digest automatically at a chosen time, time zone
|
|
21
|
+
and weekdays, with one section per linked account, and send a test right
|
|
22
|
+
away. A run missed by more than 30 minutes while the server was down is
|
|
23
|
+
skipped rather than posted late.
|
|
24
|
+
|
|
25
|
+
- **A `standup` prompt.** Clients that show MCP prompts offer it as a one-click
|
|
26
|
+
command — `/standup` in Claude Code — that writes the note, optionally for a
|
|
27
|
+
named account.
|
|
28
|
+
|
|
29
|
+
- **Gemini CLI extension.** `gemini extensions install
|
|
30
|
+
https://github.com/Jubstaaa/standup-mr` adds the hosted connector to Gemini
|
|
31
|
+
CLI, with a context file that tells it how to use the tools.
|
|
32
|
+
- The README shows how to add the hosted connector to Claude Code, Cursor,
|
|
33
|
+
VS Code, Gemini CLI, Codex CLI and ChatGPT, with one-click install buttons
|
|
34
|
+
for Cursor and VS Code.
|
|
35
|
+
|
|
36
|
+
## [1.0.1] - 2026-10-12
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- GitLab merge requests take their pipeline from `head_pipeline`, the one
|
|
41
|
+
GitLab shows on the MR. The `/pipelines` list is not ordered newest first,
|
|
42
|
+
so a merge request could be reported blocked by a stale red pipeline from an
|
|
43
|
+
earlier commit, and the current failure's log was never read.
|
|
44
|
+
- Error lines from GitLab's timestamped job logs no longer start with the
|
|
45
|
+
stream marker (`01O`, `00E+`) that follows each timestamp.
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- Every MCP tool declares a human-readable `title`, which clients show in
|
|
50
|
+
their tool lists and permission prompts.
|
|
51
|
+
|
|
7
52
|
## [1.0.0] - 2026-10-12
|
|
8
53
|
|
|
9
54
|
First stable release. From here on the CLI commands and flags, the JSON that
|
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://glama.ai/mcp/servers/Jubstaaa/standup-mr)
|
|
4
4
|
[](https://mcpservers.org/servers/jubstaaa/standup-mr)
|
|
5
|
+
[](https://smithery.ai/servers/ilkerbalcilartr/standup-mr)
|
|
5
6
|
|
|
6
7
|
Standup notes from **merge request state**, not commit logs.
|
|
7
8
|
|
|
@@ -10,6 +11,8 @@ which is not what anyone asks in a standup. This one reads GitLab or GitHub:
|
|
|
10
11
|
what is ready to merge, what is blocked, what is waiting on you — and when a
|
|
11
12
|
pipeline is red, it opens the job log and tells you **why**.
|
|
12
13
|
|
|
14
|
+

|
|
15
|
+
|
|
13
16
|
## What makes it different
|
|
14
17
|
|
|
15
18
|
| | commit-log tools | standup-mr |
|
|
@@ -66,17 +69,34 @@ So if you already use `gh` or `glab`, there is nothing to configure.
|
|
|
66
69
|
npx standup-mr fetch --markdown | npx standup-mr post --slack "$SLACK_WEBHOOK_URL"
|
|
67
70
|
```
|
|
68
71
|
|
|
69
|
-
##
|
|
72
|
+
## Hosted connector: any AI assistant
|
|
70
73
|
|
|
71
|
-
No token, no config file — sign in instead.
|
|
72
|
-
|
|
74
|
+
No token, no config file — sign in instead. The hosted server speaks the
|
|
75
|
+
standard MCP OAuth flow, so any assistant that supports remote MCP servers can
|
|
76
|
+
use it: add the URL, and a browser page opens where you sign in.
|
|
73
77
|
|
|
74
78
|
```
|
|
75
79
|
https://standup.ilkerbalcilar.com/mcp
|
|
76
80
|
```
|
|
77
81
|
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
[](https://cursor.com/en/install-mcp?name=standup&config=eyJ1cmwiOiJodHRwczovL3N0YW5kdXAuaWxrZXJiYWxjaWxhci5jb20vbWNwIn0%3D)
|
|
83
|
+
[](https://insiders.vscode.dev/redirect/mcp/install?name=standup&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fstandup.ilkerbalcilar.com%2Fmcp%22%7D)
|
|
84
|
+
|
|
85
|
+
| Assistant | How |
|
|
86
|
+
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
|
|
87
|
+
| **claude.ai** (web, mobile, Desktop) | Settings → Connectors → Add custom connector → paste the URL → Connect |
|
|
88
|
+
| **Claude Code** | `claude mcp add --scope user --transport http standup https://standup.ilkerbalcilar.com/mcp`, then `/mcp` to sign in |
|
|
89
|
+
| **Cursor** | The button above, or `"standup": { "url": "https://standup.ilkerbalcilar.com/mcp" }` in `~/.cursor/mcp.json` |
|
|
90
|
+
| **VS Code** (Copilot) | The button above, or `"standup": { "type": "http", "url": "https://standup.ilkerbalcilar.com/mcp" }` in `mcp.json` |
|
|
91
|
+
| **Gemini CLI** | `gemini extensions install https://github.com/Jubstaaa/standup-mr`, then `/mcp auth standup` |
|
|
92
|
+
| **Codex CLI** | `codex mcp add standup --url https://standup.ilkerbalcilar.com/mcp`, then `codex mcp login standup` |
|
|
93
|
+
| **ChatGPT** | With developer mode on: Settings → Apps → Create → paste the URL, OAuth |
|
|
94
|
+
|
|
95
|
+
claude.ai and Claude Code are tested end to end; the others use the same
|
|
96
|
+
standard flow. If one of them doesn't sign in, please
|
|
97
|
+
[open an issue](https://github.com/Jubstaaa/standup-mr/issues).
|
|
98
|
+
|
|
99
|
+
On the sign-in page you link the accounts the note should read:
|
|
80
100
|
|
|
81
101
|
- **GitHub** — sign in through the standup-mr GitHub App. It only sees the
|
|
82
102
|
repositories it is installed on, so install it on your org too; if the org
|
|
@@ -87,18 +107,19 @@ read:
|
|
|
87
107
|
Otherwise set it up once: the page shows the exact redirect URI and scopes
|
|
88
108
|
for an OAuth application on your host, you paste its ID and secret, and
|
|
89
109
|
everyone at that host can sign in from then on. A read-only token works too.
|
|
90
|
-
- **Chat webhook** _(optional)_ — Slack, Discord or Google Chat, so
|
|
91
|
-
post the finished note when you ask it to.
|
|
110
|
+
- **Chat webhook** _(optional)_ — Slack, Discord or Google Chat, so your assistant
|
|
111
|
+
can post the finished note when you ask it to.
|
|
92
112
|
|
|
93
113
|
Link as many as you like — say, GitHub for side projects and your company's
|
|
94
114
|
GitLab for work — then ask for "my standup" or "my work standup". With
|
|
95
|
-
several accounts linked,
|
|
115
|
+
several accounts linked, the assistant asks which one you mean.
|
|
96
116
|
|
|
97
|
-
|
|
117
|
+
Added in an assistant with a mobile app — Claude or ChatGPT — it works from
|
|
118
|
+
your phone too: ask for your standup on the way to the meeting.
|
|
98
119
|
|
|
99
120
|
**What is stored.** Your provider tokens and webhook URL, encrypted with
|
|
100
121
|
AES-256-GCM, and only to read your merge requests and post your note. The
|
|
101
|
-
access tokens
|
|
122
|
+
access tokens your assistant holds are stored as hashes. Disconnecting it
|
|
102
123
|
deletes everything; connections unused for 30 days are deleted. The server is
|
|
103
124
|
the code in [`remote/`](remote/).
|
|
104
125
|
|
|
@@ -114,7 +135,8 @@ below on your own machine — the token never leaves it.
|
|
|
114
135
|
or any MCP client that runs a local process. See [`mcp/README.md`](mcp/README.md).
|
|
115
136
|
|
|
116
137
|
**Remote MCP server** (`remote/`) — the same tools over HTTPS with sign-in, for
|
|
117
|
-
|
|
138
|
+
any assistant that supports remote MCP servers, including the web and mobile
|
|
139
|
+
apps. See [above](#hosted-connector-any-ai-assistant).
|
|
118
140
|
|
|
119
141
|
**Claude Code plugin** — the note-writing playbook, shipped as the `standup`
|
|
120
142
|
skill. From inside Claude Code:
|
|
@@ -383,7 +383,7 @@ var SECTION = /section_(start|end):\d+:\S*/g;
|
|
|
383
383
|
var SIGNAL = /(\berror\b|Error:|fatal:|npm ERR!|\bfailed\b)/i;
|
|
384
384
|
var NOISE = /^(Cleaning up|Job failed: exit status|ERROR: Job failed|Uploading artifacts|Job succeeded)/i;
|
|
385
385
|
var MAX_LINE = 200;
|
|
386
|
-
var TIMESTAMP = /^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s
|
|
386
|
+
var TIMESTAMP = /^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s+(?:[0-9a-f]{2}[OE]\+?\s)?/;
|
|
387
387
|
var ERROR_MARKER = /^##\[error\]\s*/;
|
|
388
388
|
var GROUP_START = /^##\[group\]/;
|
|
389
389
|
var GROUP_END = /^##\[endgroup\]/;
|
|
@@ -904,14 +904,11 @@ var GitLabProvider = class {
|
|
|
904
904
|
async shapeMr(mr) {
|
|
905
905
|
const projectId = mr.project_id;
|
|
906
906
|
const iid = mr.iid;
|
|
907
|
-
const [
|
|
908
|
-
this.getJson(
|
|
909
|
-
`projects/${projectId}/merge_requests/${iid}/pipelines`,
|
|
910
|
-
{ per_page: 1 }
|
|
911
|
-
),
|
|
907
|
+
const [detail, unresolved] = await Promise.all([
|
|
908
|
+
this.getJson(`projects/${projectId}/merge_requests/${iid}`),
|
|
912
909
|
this.countUnresolved(projectId, iid)
|
|
913
910
|
]);
|
|
914
|
-
const latest =
|
|
911
|
+
const latest = detail?.head_pipeline ?? void 0;
|
|
915
912
|
return {
|
|
916
913
|
provider: "gitlab",
|
|
917
914
|
project: String(mr.references.full).split("!")[0],
|
|
@@ -1348,6 +1345,9 @@ export {
|
|
|
1348
1345
|
GitLabProvider,
|
|
1349
1346
|
chooseKind,
|
|
1350
1347
|
connect,
|
|
1348
|
+
STRINGS,
|
|
1349
|
+
REF_PREFIX,
|
|
1350
|
+
PROVIDER_STRINGS,
|
|
1351
1351
|
toMarkdown,
|
|
1352
1352
|
buildReport,
|
|
1353
1353
|
findPackageRoot,
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -279,6 +279,8 @@ declare function connect(options?: SelectOptions): Provider;
|
|
|
279
279
|
|
|
280
280
|
declare function toChatText(markdown: string, kind: WebhookKind): string;
|
|
281
281
|
|
|
282
|
+
declare function toChatDigest(report: StandupReport): string;
|
|
283
|
+
|
|
282
284
|
declare function toMarkdown(report: StandupReport, lang?: string): string;
|
|
283
285
|
|
|
284
286
|
declare function buildReport(provider: Provider, today: Date, lang?: string, lookbackDays?: number): Promise<StandupReport>;
|
|
@@ -287,4 +289,4 @@ declare function readStandupSkillBody(): string;
|
|
|
287
289
|
|
|
288
290
|
declare function extractErrors(rawTrace: string, limit?: number): string[];
|
|
289
291
|
|
|
290
|
-
export { type ActiveDay, type ActivityEvent, ApiError, type Blocker, type Bucket, ConfigError, DIAGNOSIS_UNAVAILABLE, type FetchLike, GITHUB_LABELS, GITLAB_LABELS, GitHubProvider, GitLabProvider, type Identity, type MergeRequest, type Provider, type ProviderKind, type ProviderLabels, RETRY_ATTEMPTS, RETRY_BACKOFF_MS, type Review, STALE_DAYS, type SelectOptions, type StandupReport, approvedBy, assertUsable, buildReport, buildUrl, chooseKind, classify, connect, countChangesRequested, degradable, extractErrors, ghHosts, ghToken, glabHosts, glabToken, inferWebhookKind, isoDay, label, latestStateByReviewer, localAt, mapEvent, markMissingPipelines, normalizeChecks, parseGlabHosts, parseLoggedInHosts, postWebhook, previousActiveDays, readStandupSkillBody, repoFromUrl, resolveHost, resolveToken, sendWithRetry, toChatText, toMarkdown, undiagnosed, unreachable };
|
|
292
|
+
export { type ActiveDay, type ActivityEvent, ApiError, type Blocker, type Bucket, ConfigError, DIAGNOSIS_UNAVAILABLE, type FetchLike, GITHUB_LABELS, GITLAB_LABELS, GitHubProvider, GitLabProvider, type Identity, type MergeRequest, type Provider, type ProviderKind, type ProviderLabels, RETRY_ATTEMPTS, RETRY_BACKOFF_MS, type Review, STALE_DAYS, type SelectOptions, type StandupReport, approvedBy, assertUsable, buildReport, buildUrl, chooseKind, classify, connect, countChangesRequested, degradable, extractErrors, ghHosts, ghToken, glabHosts, glabToken, inferWebhookKind, isoDay, label, latestStateByReviewer, localAt, mapEvent, markMissingPipelines, normalizeChecks, parseGlabHosts, parseLoggedInHosts, postWebhook, previousActiveDays, readStandupSkillBody, repoFromUrl, resolveHost, resolveToken, sendWithRetry, toChatDigest, toChatText, toMarkdown, undiagnosed, unreachable };
|
package/dist/index.js
CHANGED
|
@@ -7,9 +7,12 @@ import {
|
|
|
7
7
|
GITLAB_LABELS,
|
|
8
8
|
GitHubProvider,
|
|
9
9
|
GitLabProvider,
|
|
10
|
+
PROVIDER_STRINGS,
|
|
11
|
+
REF_PREFIX,
|
|
10
12
|
RETRY_ATTEMPTS,
|
|
11
13
|
RETRY_BACKOFF_MS,
|
|
12
14
|
STALE_DAYS,
|
|
15
|
+
STRINGS,
|
|
13
16
|
approvedBy,
|
|
14
17
|
assertUsable,
|
|
15
18
|
buildReport,
|
|
@@ -45,7 +48,155 @@ import {
|
|
|
45
48
|
toMarkdown,
|
|
46
49
|
undiagnosed,
|
|
47
50
|
unreachable
|
|
48
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-RPKMLYHH.js";
|
|
52
|
+
|
|
53
|
+
// src/render/digest.constants.ts
|
|
54
|
+
var MAX_DAY_LINES = 8;
|
|
55
|
+
var TITLE_LIMIT = 80;
|
|
56
|
+
var ERROR_LIMIT = 120;
|
|
57
|
+
var MERGED_ACTIONS = ["accepted", "merged"];
|
|
58
|
+
var MERGE_COMMIT = /^Merge (branch|remote-tracking branch|pull request) /;
|
|
59
|
+
var DIGEST_STRINGS = {
|
|
60
|
+
standup: "Standup",
|
|
61
|
+
yesterday: "Yesterday",
|
|
62
|
+
daysAgo: "days ago",
|
|
63
|
+
merged: "merged",
|
|
64
|
+
more: "more",
|
|
65
|
+
ready: "Ready to merge",
|
|
66
|
+
blocked: "Blocked",
|
|
67
|
+
pipelineFailed: "pipeline failed",
|
|
68
|
+
also: "Also:",
|
|
69
|
+
drafts: "drafts",
|
|
70
|
+
stale: "stale",
|
|
71
|
+
reviews: "reviews waiting"
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/render/digest.ts
|
|
75
|
+
function shortName(project) {
|
|
76
|
+
return project.split("/").pop() ?? project;
|
|
77
|
+
}
|
|
78
|
+
function truncate(text, limit) {
|
|
79
|
+
return text.length > limit ? `${text.slice(0, limit - 1).trimEnd()}\u2026` : text;
|
|
80
|
+
}
|
|
81
|
+
function squash(text) {
|
|
82
|
+
return text.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
83
|
+
}
|
|
84
|
+
function isNoise(text, project) {
|
|
85
|
+
if (!text || MERGE_COMMIT.test(text)) return true;
|
|
86
|
+
return !text.includes(":") && squash(text).endsWith(squash(shortName(project)));
|
|
87
|
+
}
|
|
88
|
+
function eventTitle(event, mrs) {
|
|
89
|
+
if (!event.commits && !event.commitTitle) return event.title;
|
|
90
|
+
const mr = mrs.find(
|
|
91
|
+
(mr2) => mr2.project === event.project && mr2.branch === event.branch
|
|
92
|
+
);
|
|
93
|
+
return mr?.title ?? (event.commitTitle || event.branch);
|
|
94
|
+
}
|
|
95
|
+
function dayItems(day, mrs) {
|
|
96
|
+
const items = /* @__PURE__ */ new Map();
|
|
97
|
+
for (const event of day.events) {
|
|
98
|
+
const title = eventTitle(event, mrs);
|
|
99
|
+
if (isNoise(title, event.project)) continue;
|
|
100
|
+
const key = `${event.project}
|
|
101
|
+
${title}`;
|
|
102
|
+
const item = items.get(key) ?? {
|
|
103
|
+
merged: false,
|
|
104
|
+
project: event.project,
|
|
105
|
+
title
|
|
106
|
+
};
|
|
107
|
+
item.merged ||= MERGED_ACTIONS.includes(event.action);
|
|
108
|
+
items.set(key, item);
|
|
109
|
+
}
|
|
110
|
+
return [...items.values()];
|
|
111
|
+
}
|
|
112
|
+
function hasWeekdayBetween(from, to) {
|
|
113
|
+
const day = /* @__PURE__ */ new Date(`${from}T00:00:00Z`);
|
|
114
|
+
day.setUTCDate(day.getUTCDate() + 1);
|
|
115
|
+
for (; day.toISOString().slice(0, 10) < to; day.setUTCDate(day.getUTCDate() + 1)) {
|
|
116
|
+
const weekday = day.getUTCDay();
|
|
117
|
+
if (weekday >= 1 && weekday <= 5) return true;
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
function daySection(report) {
|
|
122
|
+
const day = report.previousDays.at(-1);
|
|
123
|
+
if (!day) return [];
|
|
124
|
+
const items = dayItems(day, report.myMrs);
|
|
125
|
+
if (items.length === 0) return [];
|
|
126
|
+
const heading = hasWeekdayBetween(day.date, report.today.date) ? `**${day.label} (${day.gapDays} ${DIGEST_STRINGS.daysAgo})**` : `**${DIGEST_STRINGS.yesterday} (${day.label})**`;
|
|
127
|
+
const lines = items.slice(0, MAX_DAY_LINES).map((item) => {
|
|
128
|
+
const merged = item.merged ? ` (${DIGEST_STRINGS.merged})` : "";
|
|
129
|
+
return `- ${shortName(item.project)} \u2014 ${truncate(item.title, TITLE_LIMIT)}${merged}`;
|
|
130
|
+
});
|
|
131
|
+
const hidden = items.length - MAX_DAY_LINES;
|
|
132
|
+
if (hidden > 0) lines.push(`- +${hidden} ${DIGEST_STRINGS.more}`);
|
|
133
|
+
return [heading, ...lines];
|
|
134
|
+
}
|
|
135
|
+
function readySection(report, ref) {
|
|
136
|
+
const ready = report.myMrs.filter((mr) => mr.bucket === "ready");
|
|
137
|
+
if (ready.length === 0) return [];
|
|
138
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
139
|
+
for (const mr of ready) {
|
|
140
|
+
const name = shortName(mr.project);
|
|
141
|
+
byProject.set(name, [...byProject.get(name) ?? [], `${ref}${mr.iid}`]);
|
|
142
|
+
}
|
|
143
|
+
const groups = [...byProject].map(
|
|
144
|
+
([name, refs]) => `${name} ${refs.join(", ")}`
|
|
145
|
+
);
|
|
146
|
+
return [`**${DIGEST_STRINGS.ready} (${ready.length})** ${groups.join(" \xB7 ")}`];
|
|
147
|
+
}
|
|
148
|
+
function blockedReason(report, mr, unresolved) {
|
|
149
|
+
const reasons = [];
|
|
150
|
+
const blocker = report.blockers.find(
|
|
151
|
+
(blocker2) => blocker2.project === mr.project && blocker2.mr === mr.iid
|
|
152
|
+
);
|
|
153
|
+
if (blocker) {
|
|
154
|
+
const error = blocker.errors[0];
|
|
155
|
+
reasons.push(
|
|
156
|
+
`${DIGEST_STRINGS.pipelineFailed} (${blocker.job})` + (error ? `: \`${truncate(error, ERROR_LIMIT)}\`` : "")
|
|
157
|
+
);
|
|
158
|
+
} else if (mr.pipeline === "failed") {
|
|
159
|
+
reasons.push(DIGEST_STRINGS.pipelineFailed);
|
|
160
|
+
}
|
|
161
|
+
if (mr.pipelineMissing) reasons.push(STRINGS.en.noPipeline);
|
|
162
|
+
if (mr.unresolved) reasons.push(`${mr.unresolved} ${unresolved}`);
|
|
163
|
+
return reasons.join("; ");
|
|
164
|
+
}
|
|
165
|
+
function blockedSection(report, ref) {
|
|
166
|
+
const blocked = report.myMrs.filter((mr) => mr.bucket === "blocked");
|
|
167
|
+
if (blocked.length === 0) return [];
|
|
168
|
+
const unresolved = PROVIDER_STRINGS[report.provider].en?.unresolved ?? STRINGS.en.unresolved;
|
|
169
|
+
return [
|
|
170
|
+
`**${DIGEST_STRINGS.blocked}**`,
|
|
171
|
+
...blocked.map((mr) => {
|
|
172
|
+
const reason = blockedReason(report, mr, unresolved);
|
|
173
|
+
const suffix = reason ? ` \u2014 ${reason}` : "";
|
|
174
|
+
return `- ${shortName(mr.project)} ${ref}${mr.iid}${suffix}`;
|
|
175
|
+
})
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
function alsoLine(report) {
|
|
179
|
+
const count = (bucket) => report.myMrs.filter((mr) => mr.bucket === bucket).length;
|
|
180
|
+
const parts = [
|
|
181
|
+
[count("draft"), DIGEST_STRINGS.drafts],
|
|
182
|
+
[count("stale"), DIGEST_STRINGS.stale],
|
|
183
|
+
[report.reviewPendingCount, DIGEST_STRINGS.reviews]
|
|
184
|
+
];
|
|
185
|
+
const shown = parts.filter(([n]) => n > 0).map(([n, name]) => `${n} ${name}`);
|
|
186
|
+
return shown.length > 0 ? [`**${DIGEST_STRINGS.also}** ${shown.join(" \xB7 ")}`] : [];
|
|
187
|
+
}
|
|
188
|
+
function toChatDigest(report) {
|
|
189
|
+
const ref = REF_PREFIX[report.provider];
|
|
190
|
+
const lines = [
|
|
191
|
+
`**${DIGEST_STRINGS.standup} \u2014 ${report.today.label}** \xB7 ${report.user}`,
|
|
192
|
+
...daySection(report),
|
|
193
|
+
...readySection(report, ref),
|
|
194
|
+
...blockedSection(report, ref),
|
|
195
|
+
...alsoLine(report)
|
|
196
|
+
];
|
|
197
|
+
return `${lines.join("\n")}
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
49
200
|
export {
|
|
50
201
|
ApiError,
|
|
51
202
|
ConfigError,
|
|
@@ -88,6 +239,7 @@ export {
|
|
|
88
239
|
resolveHost,
|
|
89
240
|
resolveToken,
|
|
90
241
|
sendWithRetry,
|
|
242
|
+
toChatDigest,
|
|
91
243
|
toChatText,
|
|
92
244
|
toMarkdown,
|
|
93
245
|
undiagnosed,
|
package/dist/mcp/server.js
CHANGED
|
@@ -306,7 +306,7 @@ var SECTION = /section_(start|end):\d+:\S*/g;
|
|
|
306
306
|
var SIGNAL = /(\berror\b|Error:|fatal:|npm ERR!|\bfailed\b)/i;
|
|
307
307
|
var NOISE = /^(Cleaning up|Job failed: exit status|ERROR: Job failed|Uploading artifacts|Job succeeded)/i;
|
|
308
308
|
var MAX_LINE = 200;
|
|
309
|
-
var TIMESTAMP = /^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s
|
|
309
|
+
var TIMESTAMP = /^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s+(?:[0-9a-f]{2}[OE]\+?\s)?/;
|
|
310
310
|
var ERROR_MARKER = /^##\[error\]\s*/;
|
|
311
311
|
var GROUP_START = /^##\[group\]/;
|
|
312
312
|
var GROUP_END = /^##\[endgroup\]/;
|
|
@@ -936,14 +936,11 @@ var GitLabProvider = class {
|
|
|
936
936
|
async shapeMr(mr) {
|
|
937
937
|
const projectId = mr.project_id;
|
|
938
938
|
const iid = mr.iid;
|
|
939
|
-
const [
|
|
940
|
-
this.getJson(
|
|
941
|
-
`projects/${projectId}/merge_requests/${iid}/pipelines`,
|
|
942
|
-
{ per_page: 1 }
|
|
943
|
-
),
|
|
939
|
+
const [detail, unresolved] = await Promise.all([
|
|
940
|
+
this.getJson(`projects/${projectId}/merge_requests/${iid}`),
|
|
944
941
|
this.countUnresolved(projectId, iid)
|
|
945
942
|
]);
|
|
946
|
-
const latest =
|
|
943
|
+
const latest = detail?.head_pipeline ?? void 0;
|
|
947
944
|
return {
|
|
948
945
|
provider: "gitlab",
|
|
949
946
|
project: String(mr.references.full).split("!")[0],
|
|
@@ -1280,6 +1277,31 @@ async function runPostTool(args, deps = {}) {
|
|
|
1280
1277
|
async function runInstructionsTool() {
|
|
1281
1278
|
return text(readStandupSkillBody());
|
|
1282
1279
|
}
|
|
1280
|
+
var STANDUP_TOOL_TITLE = "Get standup data";
|
|
1281
|
+
var POST_TOOL_TITLE = "Post standup note";
|
|
1282
|
+
var INSTRUCTIONS_TOOL_TITLE = "Get note instructions";
|
|
1283
|
+
var STANDUP_PROMPT_TITLE = "Write my standup";
|
|
1284
|
+
var STANDUP_PROMPT_DESCRIPTION = "Writes your standup note from merge request state: what you did, what is ready or blocked today, and why any pipeline is red.";
|
|
1285
|
+
var STANDUP_PROMPT_SCHEMA = {
|
|
1286
|
+
account: z.string().optional().describe(
|
|
1287
|
+
"Which account to read when several are linked: github, gitlab, or a host."
|
|
1288
|
+
)
|
|
1289
|
+
};
|
|
1290
|
+
function standupPrompt(args) {
|
|
1291
|
+
const account = args.account?.trim();
|
|
1292
|
+
const which = account ? ` Read the ${account} account (pass it as provider or host).` : "";
|
|
1293
|
+
return {
|
|
1294
|
+
messages: [
|
|
1295
|
+
{
|
|
1296
|
+
role: "user",
|
|
1297
|
+
content: {
|
|
1298
|
+
type: "text",
|
|
1299
|
+
text: "Write my standup note. First call get_note_instructions and follow its rules, then call get_standup_data." + which + " If several accounts are linked and I did not say which, ask me. Write the note in the language I'm using. Do not post it anywhere unless I ask."
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
]
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1283
1305
|
var STANDUP_TOOL_ANNOTATIONS = {
|
|
1284
1306
|
readOnlyHint: true,
|
|
1285
1307
|
destructiveHint: false,
|
|
@@ -1319,6 +1341,7 @@ function createServer() {
|
|
|
1319
1341
|
server.registerTool(
|
|
1320
1342
|
"get_standup_data",
|
|
1321
1343
|
{
|
|
1344
|
+
title: STANDUP_TOOL_TITLE,
|
|
1322
1345
|
description: STANDUP_TOOL_DESCRIPTION,
|
|
1323
1346
|
inputSchema: STANDUP_TOOL_SCHEMA,
|
|
1324
1347
|
annotations: STANDUP_TOOL_ANNOTATIONS
|
|
@@ -1328,6 +1351,7 @@ function createServer() {
|
|
|
1328
1351
|
server.registerTool(
|
|
1329
1352
|
"post_standup_note",
|
|
1330
1353
|
{
|
|
1354
|
+
title: POST_TOOL_TITLE,
|
|
1331
1355
|
description: POST_TOOL_DESCRIPTION,
|
|
1332
1356
|
inputSchema: POST_TOOL_SCHEMA,
|
|
1333
1357
|
annotations: POST_TOOL_ANNOTATIONS
|
|
@@ -1337,11 +1361,21 @@ function createServer() {
|
|
|
1337
1361
|
server.registerTool(
|
|
1338
1362
|
"get_note_instructions",
|
|
1339
1363
|
{
|
|
1364
|
+
title: INSTRUCTIONS_TOOL_TITLE,
|
|
1340
1365
|
description: INSTRUCTIONS_TOOL_DESCRIPTION,
|
|
1341
1366
|
annotations: INSTRUCTIONS_TOOL_ANNOTATIONS
|
|
1342
1367
|
},
|
|
1343
1368
|
async () => await runInstructionsTool()
|
|
1344
1369
|
);
|
|
1370
|
+
server.registerPrompt(
|
|
1371
|
+
"standup",
|
|
1372
|
+
{
|
|
1373
|
+
title: STANDUP_PROMPT_TITLE,
|
|
1374
|
+
description: STANDUP_PROMPT_DESCRIPTION,
|
|
1375
|
+
argsSchema: STANDUP_PROMPT_SCHEMA
|
|
1376
|
+
},
|
|
1377
|
+
(args) => standupPrompt(args)
|
|
1378
|
+
);
|
|
1345
1379
|
return server;
|
|
1346
1380
|
}
|
|
1347
1381
|
async function main() {
|
|
@@ -1366,13 +1400,19 @@ if (entryUrl && import.meta.url === entryUrl) {
|
|
|
1366
1400
|
export {
|
|
1367
1401
|
INSTRUCTIONS_TOOL_ANNOTATIONS,
|
|
1368
1402
|
INSTRUCTIONS_TOOL_DESCRIPTION,
|
|
1403
|
+
INSTRUCTIONS_TOOL_TITLE,
|
|
1369
1404
|
POST_TOOL_ANNOTATIONS,
|
|
1370
1405
|
POST_TOOL_DESCRIPTION,
|
|
1371
1406
|
POST_TOOL_SCHEMA,
|
|
1407
|
+
POST_TOOL_TITLE,
|
|
1372
1408
|
SERVER_ICONS,
|
|
1409
|
+
STANDUP_PROMPT_DESCRIPTION,
|
|
1410
|
+
STANDUP_PROMPT_SCHEMA,
|
|
1411
|
+
STANDUP_PROMPT_TITLE,
|
|
1373
1412
|
STANDUP_TOOL_ANNOTATIONS,
|
|
1374
1413
|
STANDUP_TOOL_DESCRIPTION,
|
|
1375
1414
|
STANDUP_TOOL_SCHEMA,
|
|
1415
|
+
STANDUP_TOOL_TITLE,
|
|
1376
1416
|
WEBHOOK_URL_ENV,
|
|
1377
1417
|
collect,
|
|
1378
1418
|
createServer,
|
|
@@ -1381,5 +1421,6 @@ export {
|
|
|
1381
1421
|
runInstructionsTool,
|
|
1382
1422
|
runPostTool,
|
|
1383
1423
|
runStandupTool,
|
|
1424
|
+
standupPrompt,
|
|
1384
1425
|
text
|
|
1385
1426
|
};
|
package/mcp/README.md
CHANGED
|
@@ -6,7 +6,7 @@ the same JSON as `standup fetch`.
|
|
|
6
6
|
|
|
7
7
|
This is the local, stdio server: it runs on your machine and reads tokens from
|
|
8
8
|
its environment. On claude.ai (web or mobile), use the hosted connector instead
|
|
9
|
-
— see [
|
|
9
|
+
— see the [hosted connector](../README.md#hosted-connector-any-ai-assistant).
|
|
10
10
|
|
|
11
11
|
## Configure
|
|
12
12
|
|