huly-mcp-selfhost 2.1.0 → 3.2.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/.env.example +39 -2
- package/README.md +462 -16
- package/dist/connection.d.ts +8 -0
- package/dist/connection.js +13 -1
- package/dist/connection.js.map +1 -1
- package/dist/env.d.ts +1 -0
- package/dist/env.js +17 -0
- package/dist/env.js.map +1 -0
- package/dist/guard.d.ts +28 -0
- package/dist/guard.js +152 -0
- package/dist/guard.js.map +1 -0
- package/dist/guides/costs.md +48 -0
- package/dist/guides/features.md +49 -0
- package/dist/guides/onboarding.md +56 -0
- package/dist/guides/workflow.md +57 -0
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/dist/junit-cli.d.ts +8 -0
- package/dist/junit-cli.js +106 -0
- package/dist/junit-cli.js.map +1 -0
- package/dist/registry.d.ts +18 -0
- package/dist/registry.js +318 -0
- package/dist/registry.js.map +1 -0
- package/dist/resources.d.ts +12 -0
- package/dist/resources.js +212 -0
- package/dist/resources.js.map +1 -0
- package/dist/schemas.d.ts +1269 -17
- package/dist/schemas.js +890 -97
- package/dist/schemas.js.map +1 -1
- package/dist/server.d.ts +27 -1
- package/dist/server.js +197 -128
- package/dist/server.js.map +1 -1
- package/dist/tools/activity.d.ts +10 -0
- package/dist/tools/activity.js +62 -0
- package/dist/tools/activity.js.map +1 -0
- package/dist/tools/agent.d.ts +156 -0
- package/dist/tools/agent.js +523 -0
- package/dist/tools/agent.js.map +1 -0
- package/dist/tools/attachments.d.ts +4 -4
- package/dist/tools/calendar.d.ts +49 -0
- package/dist/tools/calendar.js +116 -0
- package/dist/tools/calendar.js.map +1 -0
- package/dist/tools/cards.d.ts +87 -0
- package/dist/tools/cards.js +246 -0
- package/dist/tools/cards.js.map +1 -0
- package/dist/tools/channels.d.ts +76 -7
- package/dist/tools/channels.js +86 -5
- package/dist/tools/channels.js.map +1 -1
- package/dist/tools/comments.d.ts +16 -4
- package/dist/tools/comments.js +26 -9
- package/dist/tools/comments.js.map +1 -1
- package/dist/tools/components.d.ts +5 -5
- package/dist/tools/components.js +4 -22
- package/dist/tools/components.js.map +1 -1
- package/dist/tools/contacts.d.ts +69 -0
- package/dist/tools/contacts.js +130 -0
- package/dist/tools/contacts.js.map +1 -0
- package/dist/tools/crm.d.ts +61 -0
- package/dist/tools/crm.js +117 -0
- package/dist/tools/crm.js.map +1 -0
- package/dist/tools/documents.d.ts +35 -12
- package/dist/tools/documents.js +107 -22
- package/dist/tools/documents.js.map +1 -1
- package/dist/tools/drive.d.ts +107 -0
- package/dist/tools/drive.js +277 -0
- package/dist/tools/drive.js.map +1 -0
- package/dist/tools/fields.d.ts +33 -0
- package/dist/tools/fields.js +124 -0
- package/dist/tools/fields.js.map +1 -0
- package/dist/tools/generic.d.ts +72 -0
- package/dist/tools/generic.js +242 -0
- package/dist/tools/generic.js.map +1 -0
- package/dist/tools/github.d.ts +21 -0
- package/dist/tools/github.js +80 -0
- package/dist/tools/github.js.map +1 -0
- package/dist/tools/hr.d.ts +97 -0
- package/dist/tools/hr.js +150 -0
- package/dist/tools/hr.js.map +1 -0
- package/dist/tools/inbox.d.ts +23 -0
- package/dist/tools/inbox.js +76 -0
- package/dist/tools/inbox.js.map +1 -0
- package/dist/tools/inventory.d.ts +61 -0
- package/dist/tools/inventory.js +103 -0
- package/dist/tools/inventory.js.map +1 -0
- package/dist/tools/issues.d.ts +63 -12
- package/dist/tools/issues.js +259 -48
- package/dist/tools/issues.js.map +1 -1
- package/dist/tools/labels.d.ts +52 -4
- package/dist/tools/labels.js +86 -4
- package/dist/tools/labels.js.map +1 -1
- package/dist/tools/log-time.d.ts +15 -1
- package/dist/tools/log-time.js +70 -6
- package/dist/tools/log-time.js.map +1 -1
- package/dist/tools/meetings.d.ts +30 -0
- package/dist/tools/meetings.js +78 -0
- package/dist/tools/meetings.js.map +1 -0
- package/dist/tools/members.d.ts +1 -1
- package/dist/tools/members.js +17 -1
- package/dist/tools/members.js.map +1 -1
- package/dist/tools/message-templates.d.ts +54 -0
- package/dist/tools/message-templates.js +88 -0
- package/dist/tools/message-templates.js.map +1 -0
- package/dist/tools/milestones.d.ts +9 -9
- package/dist/tools/organizations.d.ts +5 -5
- package/dist/tools/organizations.js +2 -1
- package/dist/tools/organizations.js.map +1 -1
- package/dist/tools/overview.d.ts +55 -0
- package/dist/tools/overview.js +395 -0
- package/dist/tools/overview.js.map +1 -0
- package/dist/tools/planner.d.ts +60 -0
- package/dist/tools/planner.js +145 -0
- package/dist/tools/planner.js.map +1 -0
- package/dist/tools/process.d.ts +50 -0
- package/dist/tools/process.js +137 -0
- package/dist/tools/process.js.map +1 -0
- package/dist/tools/products.d.ts +17 -0
- package/dist/tools/products.js +45 -0
- package/dist/tools/products.js.map +1 -0
- package/dist/tools/projects.d.ts +17 -4
- package/dist/tools/projects.js +31 -2
- package/dist/tools/projects.js.map +1 -1
- package/dist/tools/qms.d.ts +57 -0
- package/dist/tools/qms.js +171 -0
- package/dist/tools/qms.js.map +1 -0
- package/dist/tools/recruiting.d.ts +112 -0
- package/dist/tools/recruiting.js +203 -0
- package/dist/tools/recruiting.js.map +1 -0
- package/dist/tools/relations.d.ts +45 -3
- package/dist/tools/relations.js +128 -39
- package/dist/tools/relations.js.map +1 -1
- package/dist/tools/search.d.ts +14 -1
- package/dist/tools/search.js +29 -1
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/sharing.d.ts +62 -0
- package/dist/tools/sharing.js +149 -0
- package/dist/tools/sharing.js.map +1 -0
- package/dist/tools/statuses.d.ts +3 -3
- package/dist/tools/surveys.d.ts +22 -0
- package/dist/tools/surveys.js +53 -0
- package/dist/tools/surveys.js.map +1 -0
- package/dist/tools/templates.d.ts +95 -0
- package/dist/tools/templates.js +260 -0
- package/dist/tools/templates.js.map +1 -0
- package/dist/tools/test-automation.d.ts +81 -0
- package/dist/tools/test-automation.js +225 -0
- package/dist/tools/test-automation.js.map +1 -0
- package/dist/tools/testing.d.ts +234 -0
- package/dist/tools/testing.js +401 -0
- package/dist/tools/testing.js.map +1 -0
- package/dist/tools/workspace-admin.d.ts +55 -0
- package/dist/tools/workspace-admin.js +117 -0
- package/dist/tools/workspace-admin.js.map +1 -0
- package/dist/tools/workspaces.d.ts +1 -1
- package/dist/utils/attributes.d.ts +37 -0
- package/dist/utils/attributes.js +188 -0
- package/dist/utils/attributes.js.map +1 -0
- package/dist/utils/classes.d.ts +51 -0
- package/dist/utils/classes.js +73 -0
- package/dist/utils/classes.js.map +1 -0
- package/dist/utils/content.d.ts +11 -0
- package/dist/utils/content.js +86 -0
- package/dist/utils/content.js.map +1 -0
- package/dist/utils/errors.js +1 -1
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/files.d.ts +5 -0
- package/dist/utils/files.js +50 -0
- package/dist/utils/files.js.map +1 -0
- package/dist/utils/format.d.ts +2 -0
- package/dist/utils/format.js +7 -13
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/junit.d.ts +13 -0
- package/dist/utils/junit.js +81 -0
- package/dist/utils/junit.js.map +1 -0
- package/dist/utils/markdown.js +9 -4
- package/dist/utils/markdown.js.map +1 -1
- package/dist/utils/meeting.d.ts +13 -0
- package/dist/utils/meeting.js +118 -0
- package/dist/utils/meeting.js.map +1 -0
- package/dist/utils/people.d.ts +25 -0
- package/dist/utils/people.js +80 -0
- package/dist/utils/people.js.map +1 -0
- package/dist/utils/redact.d.ts +3 -0
- package/dist/utils/redact.js +26 -0
- package/dist/utils/redact.js.map +1 -0
- package/dist/utils/resolve.d.ts +17 -0
- package/dist/utils/resolve.js +54 -0
- package/dist/utils/resolve.js.map +1 -0
- package/dist/utils/spec.d.ts +10 -0
- package/dist/utils/spec.js +90 -0
- package/dist/utils/spec.js.map +1 -0
- package/dist/utils/storage.js +7 -3
- package/dist/utils/storage.js.map +1 -1
- package/dist/utils/tasks.d.ts +22 -0
- package/dist/utils/tasks.js +81 -0
- package/dist/utils/tasks.js.map +1 -0
- package/dist/watch.d.ts +62 -0
- package/dist/watch.js +405 -0
- package/dist/watch.js.map +1 -0
- package/package.json +4 -3
- package/scripts/setup.js +3 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export declare const listMilestones: (args: {
|
|
2
|
-
projectIdentifier: string;
|
|
3
2
|
workspace?: string | undefined;
|
|
3
|
+
projectIdentifier: string;
|
|
4
4
|
}) => Promise<{
|
|
5
5
|
content: Array<{
|
|
6
|
-
type:
|
|
6
|
+
type: 'text';
|
|
7
7
|
text: string;
|
|
8
8
|
}>;
|
|
9
9
|
}>;
|
|
@@ -12,20 +12,20 @@ export declare const createMilestone: (args: {
|
|
|
12
12
|
projectIdentifier: string;
|
|
13
13
|
label: string;
|
|
14
14
|
targetDate: string;
|
|
15
|
-
status: "
|
|
15
|
+
status: "Canceled" | "Completed" | "InProgress" | "Planned";
|
|
16
16
|
}) => Promise<{
|
|
17
17
|
content: Array<{
|
|
18
|
-
type:
|
|
18
|
+
type: 'text';
|
|
19
19
|
text: string;
|
|
20
20
|
}>;
|
|
21
21
|
}>;
|
|
22
22
|
export declare const getMilestone: (args: {
|
|
23
|
+
workspace?: string | undefined;
|
|
23
24
|
projectIdentifier: string;
|
|
24
25
|
label: string;
|
|
25
|
-
workspace?: string | undefined;
|
|
26
26
|
}) => Promise<{
|
|
27
27
|
content: Array<{
|
|
28
|
-
type:
|
|
28
|
+
type: 'text';
|
|
29
29
|
text: string;
|
|
30
30
|
}>;
|
|
31
31
|
}>;
|
|
@@ -35,11 +35,11 @@ export declare const updateMilestone: (args: {
|
|
|
35
35
|
label: string;
|
|
36
36
|
newLabel?: string | undefined;
|
|
37
37
|
targetDate?: string | undefined;
|
|
38
|
-
status?: "
|
|
38
|
+
status?: "Canceled" | "Completed" | "InProgress" | "Planned" | undefined;
|
|
39
39
|
description?: string | null | undefined;
|
|
40
40
|
}) => Promise<{
|
|
41
41
|
content: Array<{
|
|
42
|
-
type:
|
|
42
|
+
type: 'text';
|
|
43
43
|
text: string;
|
|
44
44
|
}>;
|
|
45
45
|
}>;
|
|
@@ -50,7 +50,7 @@ export declare const deleteMilestone: (args: {
|
|
|
50
50
|
moveIssuesTo?: string | undefined;
|
|
51
51
|
}) => Promise<{
|
|
52
52
|
content: Array<{
|
|
53
|
-
type:
|
|
53
|
+
type: 'text';
|
|
54
54
|
text: string;
|
|
55
55
|
}>;
|
|
56
56
|
}>;
|
|
@@ -2,16 +2,16 @@ export declare const listOrganizations: (args: {
|
|
|
2
2
|
workspace?: string | undefined;
|
|
3
3
|
}) => Promise<{
|
|
4
4
|
content: Array<{
|
|
5
|
-
type:
|
|
5
|
+
type: 'text';
|
|
6
6
|
text: string;
|
|
7
7
|
}>;
|
|
8
8
|
}>;
|
|
9
9
|
export declare const getOrganization: (args: {
|
|
10
|
-
organizationId: string;
|
|
11
10
|
workspace?: string | undefined;
|
|
11
|
+
organizationId: string;
|
|
12
12
|
}) => Promise<{
|
|
13
13
|
content: Array<{
|
|
14
|
-
type:
|
|
14
|
+
type: 'text';
|
|
15
15
|
text: string;
|
|
16
16
|
}>;
|
|
17
17
|
}>;
|
|
@@ -21,7 +21,7 @@ export declare const createOrganization: (args: {
|
|
|
21
21
|
city?: string | undefined;
|
|
22
22
|
}) => Promise<{
|
|
23
23
|
content: Array<{
|
|
24
|
-
type:
|
|
24
|
+
type: 'text';
|
|
25
25
|
text: string;
|
|
26
26
|
}>;
|
|
27
27
|
}>;
|
|
@@ -31,7 +31,7 @@ export declare const updateOrganization: (args: {
|
|
|
31
31
|
description: string;
|
|
32
32
|
}) => Promise<{
|
|
33
33
|
content: Array<{
|
|
34
|
-
type:
|
|
34
|
+
type: 'text';
|
|
35
35
|
text: string;
|
|
36
36
|
}>;
|
|
37
37
|
}>;
|
|
@@ -10,6 +10,7 @@ const connection_1 = require("../connection");
|
|
|
10
10
|
const errors_1 = require("../utils/errors");
|
|
11
11
|
const markdown_1 = require("../utils/markdown");
|
|
12
12
|
const storage_1 = require("../utils/storage");
|
|
13
|
+
const content_1 = require("../utils/content");
|
|
13
14
|
exports.listOrganizations = (0, errors_1.wrapToolHandler)(async (args) => {
|
|
14
15
|
const client = await (0, connection_1.getConnection)(args.workspace);
|
|
15
16
|
const orgs = await client.findAll(contact_1.default.class.Organization, {});
|
|
@@ -34,7 +35,7 @@ exports.getOrganization = (0, errors_1.wrapToolHandler)(async (args) => {
|
|
|
34
35
|
// Token goes in the Authorization header only — never in the URL (see get_document).
|
|
35
36
|
const blobUrl = `${frontUrl}/files?file=${encodeURIComponent(org.description)}&workspace=${workspaceUuid}`;
|
|
36
37
|
try {
|
|
37
|
-
const res = await
|
|
38
|
+
const res = await (0, content_1.fetchBlob)(blobUrl, wsToken);
|
|
38
39
|
if (res.ok) {
|
|
39
40
|
const text = await res.text();
|
|
40
41
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organizations.js","sourceRoot":"","sources":["../../src/tools/organizations.ts"],"names":[],"mappings":";;;;;;AAAA,qEAAmE;AACnE,8CAA0D;AAC1D,8CAA+D;AAC/D,4CAAiD;AACjD,gDAAsE;AACtE,8CAAmD;
|
|
1
|
+
{"version":3,"file":"organizations.js","sourceRoot":"","sources":["../../src/tools/organizations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Start of the given day (UTC, like the calendar tools) or of today. */
|
|
2
|
+
export declare function dayStart(date?: string): number;
|
|
3
|
+
export declare const myDay: (args: {
|
|
4
|
+
workspace?: string | undefined;
|
|
5
|
+
date?: string | undefined;
|
|
6
|
+
limit: number;
|
|
7
|
+
}) => Promise<{
|
|
8
|
+
content: Array<{
|
|
9
|
+
type: 'text';
|
|
10
|
+
text: string;
|
|
11
|
+
}>;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function myDayText(client: any, date: string | undefined, limit: number): Promise<string>;
|
|
14
|
+
export declare const projectReport: (args: {
|
|
15
|
+
workspace?: string | undefined;
|
|
16
|
+
projectIdentifier: string;
|
|
17
|
+
since?: string | undefined;
|
|
18
|
+
}) => Promise<{
|
|
19
|
+
content: Array<{
|
|
20
|
+
type: 'text';
|
|
21
|
+
text: string;
|
|
22
|
+
}>;
|
|
23
|
+
}>;
|
|
24
|
+
/** Start of the previous working day (Monday → Friday), UTC. */
|
|
25
|
+
export declare function lastWorkingDay(now?: number): number;
|
|
26
|
+
export declare const standup: (args: {
|
|
27
|
+
workspace?: string | undefined;
|
|
28
|
+
since?: string | undefined;
|
|
29
|
+
}) => Promise<{
|
|
30
|
+
content: Array<{
|
|
31
|
+
type: 'text';
|
|
32
|
+
text: string;
|
|
33
|
+
}>;
|
|
34
|
+
}>;
|
|
35
|
+
/** One-line plain text of a comment: no HTML (bot comments), no entities, collapsed whitespace. */
|
|
36
|
+
export declare const plain: (markup: string) => string;
|
|
37
|
+
export declare const triageInbox: (args: {
|
|
38
|
+
workspace?: string | undefined;
|
|
39
|
+
limit: number;
|
|
40
|
+
}) => Promise<{
|
|
41
|
+
content: Array<{
|
|
42
|
+
type: 'text';
|
|
43
|
+
text: string;
|
|
44
|
+
}>;
|
|
45
|
+
}>;
|
|
46
|
+
export declare const releaseNotes: (args: {
|
|
47
|
+
workspace?: string | undefined;
|
|
48
|
+
projectIdentifier: string;
|
|
49
|
+
milestoneLabel: string;
|
|
50
|
+
}) => Promise<{
|
|
51
|
+
content: Array<{
|
|
52
|
+
type: 'text';
|
|
53
|
+
text: string;
|
|
54
|
+
}>;
|
|
55
|
+
}>;
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.releaseNotes = exports.triageInbox = exports.plain = exports.standup = exports.projectReport = exports.myDay = void 0;
|
|
7
|
+
exports.dayStart = dayStart;
|
|
8
|
+
exports.myDayText = myDayText;
|
|
9
|
+
exports.lastWorkingDay = lastWorkingDay;
|
|
10
|
+
// Overviews that combine several modules in one call: "my day" (calendar, to-dos, my issues,
|
|
11
|
+
// reviews, inbox) so Claude can answer "what's on today?" without five round trips, and a
|
|
12
|
+
// project report for a period (weekly report).
|
|
13
|
+
const tracker_1 = __importDefault(require("@hcengineering/tracker"));
|
|
14
|
+
const task_1 = __importDefault(require("@hcengineering/task"));
|
|
15
|
+
const core_1 = require("@hcengineering/core");
|
|
16
|
+
const connection_1 = require("../connection");
|
|
17
|
+
const errors_1 = require("../utils/errors");
|
|
18
|
+
const classes_1 = require("../utils/classes");
|
|
19
|
+
const format_1 = require("../utils/format");
|
|
20
|
+
const people_1 = require("../utils/people");
|
|
21
|
+
const agent_1 = require("./agent");
|
|
22
|
+
const resolve_1 = require("../utils/resolve");
|
|
23
|
+
const DAY = 86_400_000;
|
|
24
|
+
const day = (ts) => new Date(ts).toISOString().slice(0, 10);
|
|
25
|
+
const hm = (ts) => new Date(ts).toISOString().slice(11, 16);
|
|
26
|
+
/** Start of the given day (UTC, like the calendar tools) or of today. */
|
|
27
|
+
function dayStart(date) {
|
|
28
|
+
if (date == null)
|
|
29
|
+
return Date.parse(new Date().toISOString().slice(0, 10));
|
|
30
|
+
const t = Date.parse(date.slice(0, 10));
|
|
31
|
+
if (Number.isNaN(t))
|
|
32
|
+
throw new Error(`date must be YYYY-MM-DD, got '${date}'.`);
|
|
33
|
+
return t;
|
|
34
|
+
}
|
|
35
|
+
const due = (ts, from) => {
|
|
36
|
+
if (ts == null)
|
|
37
|
+
return '';
|
|
38
|
+
if (ts < from)
|
|
39
|
+
return ` · ⚠️ overdue (${day(ts)})`;
|
|
40
|
+
if (ts < from + DAY)
|
|
41
|
+
return ' · due today';
|
|
42
|
+
return ` · due ${day(ts)}`;
|
|
43
|
+
};
|
|
44
|
+
exports.myDay = (0, errors_1.wrapToolHandler)(async (args) => await myDayText(await (0, connection_1.getConnection)(args.workspace), args.date, args.limit ?? 10));
|
|
45
|
+
async function myDayText(client, date, limit) {
|
|
46
|
+
const from = dayStart(date);
|
|
47
|
+
const to = from + DAY - 1;
|
|
48
|
+
const [me, uuid] = await Promise.all([(0, people_1.currentPerson)(client), (0, people_1.currentAccountUuid)(client)]);
|
|
49
|
+
const [events, todos, issues, prs, unread] = await Promise.all([
|
|
50
|
+
client.findAll(classes_1.C.Event, { date: { $lte: to }, dueDate: { $gte: from } }, { sort: { date: core_1.SortingOrder.Ascending } }),
|
|
51
|
+
client.findAll(classes_1.C.ToDo, { user: me._id, doneOn: null }, { sort: { rank: core_1.SortingOrder.Ascending } }),
|
|
52
|
+
client.findAll(tracker_1.default.class.Issue, { assignee: me._id }),
|
|
53
|
+
client.findAll(classes_1.C.PullRequest, { state: 0 }),
|
|
54
|
+
client.findAll(classes_1.C.InboxNotification, { user: uuid, isViewed: false, archived: false }, { sort: { createdOn: core_1.SortingOrder.Descending }, limit: 500 })
|
|
55
|
+
]);
|
|
56
|
+
const out = [`# My day — ${day(from)} (times UTC)`];
|
|
57
|
+
// Calendar: own events and work slots (the query also returns other people's public events).
|
|
58
|
+
const mine = events.filter((e) => (e.participants ?? []).includes(me._id) || String(e.calendar ?? '').startsWith(uuid) || e.user === me._id);
|
|
59
|
+
out.push('', `## Calendar (${mine.length})`);
|
|
60
|
+
if (mine.length === 0)
|
|
61
|
+
out.push('Nothing scheduled.');
|
|
62
|
+
for (const e of mine.slice(0, limit)) {
|
|
63
|
+
const when = e.allDay === true ? 'all day' : `${hm(Math.max(e.date, from))}–${hm(Math.min(e.dueDate, to))}`;
|
|
64
|
+
const slot = e._class === classes_1.C.WorkSlot ? ` · work slot for ${await (0, resolve_1.loadLabel)(client, e.attachedToClass, e.attachedTo)}` : '';
|
|
65
|
+
out.push(`- ${when} **${String(e.title)}**${e.location != null && e.location !== '' ? ` · 📍 ${String(e.location)}` : ''}${slot}`);
|
|
66
|
+
}
|
|
67
|
+
// To-dos: overdue and due today first, then the top of the planner.
|
|
68
|
+
const urgency = (t) => t.dueDate == null ? 2 : t.dueDate <= to ? 0 : 1;
|
|
69
|
+
const sortedTodos = [...todos].sort((a, b) => urgency(a) - urgency(b));
|
|
70
|
+
const dueNow = todos.filter((t) => t.dueDate != null && t.dueDate <= to).length;
|
|
71
|
+
out.push('', `## To-dos (${todos.length} open${dueNow > 0 ? `, ${dueNow} due by today` : ''})`);
|
|
72
|
+
if (todos.length === 0)
|
|
73
|
+
out.push('No open to-dos.');
|
|
74
|
+
for (const t of sortedTodos.slice(0, limit)) {
|
|
75
|
+
const prio = t.priority != null && t.priority !== classes_1.TODO_PRIORITY.NoPriority ? ` · ${String(classes_1.TODO_PRIORITY_LABEL[t.priority] ?? t.priority)}` : '';
|
|
76
|
+
out.push(`- [ ] **${String(t.title)}**${prio}${due(t.dueDate, from)} — \`${String(t._id)}\``);
|
|
77
|
+
}
|
|
78
|
+
// My issues: open ones, in progress first, then by due date and priority.
|
|
79
|
+
const statuses = issues.length > 0 ? await client.findAll('core:class:Status', { _id: { $in: [...new Set(issues.map((i) => i.status))] } }) : [];
|
|
80
|
+
const st = new Map(statuses.map((s) => [s._id, s]));
|
|
81
|
+
const closed = new Set([task_1.default.statusCategory.Won, task_1.default.statusCategory.Lost]);
|
|
82
|
+
// PRs are issues too (github:class:GithubPullRequest); they belong to the reviews section.
|
|
83
|
+
const open = issues.filter((i) => i._class !== classes_1.C.PullRequest && !closed.has(st.get(i.status)?.category));
|
|
84
|
+
const active = (i) => st.get(i.status)?.category === task_1.default.statusCategory.Active ? 0 : 1;
|
|
85
|
+
open.sort((a, b) => active(a) - active(b) || (a.dueDate ?? Infinity) - (b.dueDate ?? Infinity) || (a.priority || 9) - (b.priority || 9));
|
|
86
|
+
out.push('', `## My issues (${open.length} open, ${open.filter((i) => active(i) === 0).length} in progress)`);
|
|
87
|
+
if (open.length === 0)
|
|
88
|
+
out.push('No open issues assigned to you.');
|
|
89
|
+
for (const i of open.slice(0, limit)) {
|
|
90
|
+
const prio = i.priority != null && i.priority !== 0 ? ` · ${(0, format_1.priorityLabel)(i.priority)}` : '';
|
|
91
|
+
out.push(`- **${String(i.identifier)}** ${String(i.title)} — ${String(st.get(i.status)?.name ?? '?')}${prio}${due(i.dueDate, from)}`);
|
|
92
|
+
}
|
|
93
|
+
// Reviews: open PRs that ask me to review, and my own open PRs with their review state.
|
|
94
|
+
const DECISION = ['changes requested', 'approved', 'review required'];
|
|
95
|
+
const toReview = prs.filter((p) => (p.reviewers ?? []).includes(me._id));
|
|
96
|
+
const own = prs.filter((p) => p.assignee === me._id && !toReview.includes(p));
|
|
97
|
+
out.push('', `## Reviews (${toReview.length} for you, ${own.length} of yours open)`);
|
|
98
|
+
if (toReview.length + own.length === 0)
|
|
99
|
+
out.push('No open pull requests for you.');
|
|
100
|
+
for (const p of toReview.slice(0, limit))
|
|
101
|
+
out.push(`- 👀 **${String(p.identifier)}** ${String(p.title)}${p.draft === true ? ' · draft' : ''}`);
|
|
102
|
+
for (const p of own.slice(0, limit))
|
|
103
|
+
out.push(`- **${String(p.identifier)}** ${String(p.title)} · ${p.reviewDecision != null ? DECISION[p.reviewDecision] ?? p.reviewDecision : 'no review yet'}${p.draft === true ? ' · draft' : ''}`);
|
|
104
|
+
// Inbox: unread notifications grouped by object, newest first.
|
|
105
|
+
const groups = new Map();
|
|
106
|
+
for (const n of unread)
|
|
107
|
+
groups.set(n.docNotifyContext, [...(groups.get(n.docNotifyContext) ?? []), n]);
|
|
108
|
+
out.push('', `## Inbox (${unread.length}${unread.length === 500 ? '+' : ''} unread on ${groups.size} item(s))`);
|
|
109
|
+
if (unread.length === 0)
|
|
110
|
+
out.push('Inbox zero.');
|
|
111
|
+
const ctxs = groups.size > 0 ? await client.findAll(classes_1.C.DocNotifyContext, { _id: { $in: [...groups.keys()].slice(0, limit) } }) : [];
|
|
112
|
+
for (const [ctxId, notes] of [...groups.entries()].slice(0, limit)) {
|
|
113
|
+
const ctx = ctxs.find((c) => c._id === ctxId);
|
|
114
|
+
const cls = ctx?.objectClass ?? notes[0].objectClass;
|
|
115
|
+
const label = cls != null ? await (0, resolve_1.loadLabel)(client, cls, ctx?.objectId ?? notes[0].objectId) : '(unknown)';
|
|
116
|
+
out.push(`- **${label}** — ${notes.length} unread`);
|
|
117
|
+
}
|
|
118
|
+
if (unread.length > 0)
|
|
119
|
+
out.push(`_${groups.size > limit ? `${groups.size - limit} more. ` : ''}Work through them: triage_inbox._`);
|
|
120
|
+
return out.join('\n');
|
|
121
|
+
}
|
|
122
|
+
// ── Project report: what happened in a project over a period (default: last 7 days) ──
|
|
123
|
+
const pct = (part, total) => total === 0 ? '—' : `${Math.round(100 * part / total)}%`;
|
|
124
|
+
exports.projectReport = (0, errors_1.wrapToolHandler)(async (args) => {
|
|
125
|
+
const client = await (0, connection_1.getConnection)(args.workspace);
|
|
126
|
+
const project = await client.findOne(tracker_1.default.class.Project, { identifier: args.projectIdentifier });
|
|
127
|
+
if (project == null)
|
|
128
|
+
throw new Error(`Project '${args.projectIdentifier}' not found.`);
|
|
129
|
+
const since = (0, agent_1.parseSince)(args.since ?? '7d');
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
const all = await client.findAll(tracker_1.default.class.Issue, { space: project._id });
|
|
132
|
+
const issues = all.filter((i) => i._class !== classes_1.C.PullRequest);
|
|
133
|
+
const prs = all.filter((i) => i._class === classes_1.C.PullRequest);
|
|
134
|
+
const ids = issues.map((i) => i._id);
|
|
135
|
+
const [statuses, milestones, reports, statusMsgs] = await Promise.all([
|
|
136
|
+
client.findAll('core:class:Status', { _id: { $in: [...new Set(all.map((i) => i.status))] } }),
|
|
137
|
+
client.findAll(tracker_1.default.class.Milestone, { space: project._id }),
|
|
138
|
+
ids.length > 0 ? client.findAll(tracker_1.default.class.TimeSpendReport, { attachedTo: { $in: ids } }) : [],
|
|
139
|
+
ids.length > 0 ? client.findAll(classes_1.C.DocUpdateMessage, { attachedTo: { $in: ids }, createdOn: { $gte: since } }) : []
|
|
140
|
+
]);
|
|
141
|
+
const names = await (0, people_1.peopleNames)(client);
|
|
142
|
+
const st = new Map(statuses.map((s) => [s._id, s]));
|
|
143
|
+
const cat = (i) => st.get(i.status)?.category;
|
|
144
|
+
const won = (i) => cat(i) === task_1.default.statusCategory.Won;
|
|
145
|
+
const isOpen = (i) => !won(i) && cat(i) !== task_1.default.statusCategory.Lost;
|
|
146
|
+
const who = (i) => i.assignee != null ? ` · ${names.get(i.assignee) ?? '?'}` : '';
|
|
147
|
+
const line = (i, extra = '') => `- **${String(i.identifier)}** ${String(i.title)}${extra}${who(i)}`;
|
|
148
|
+
// Done in the period: done now and its status changed in the period. modifiedOn is no use here —
|
|
149
|
+
// GitHub sync touches closed issues long after they were finished.
|
|
150
|
+
const statusChanged = new Set(statusMsgs.filter((m) => m.attributeUpdates?.attrKey === 'status').map((m) => m.attachedTo));
|
|
151
|
+
const done = issues.filter((i) => won(i) && statusChanged.has(i._id));
|
|
152
|
+
const created = issues.filter((i) => (i.createdOn ?? 0) >= since);
|
|
153
|
+
const open = issues.filter(isOpen);
|
|
154
|
+
const active = open.filter((i) => cat(i) === task_1.default.statusCategory.Active);
|
|
155
|
+
const openIds = new Set(open.map((i) => i._id));
|
|
156
|
+
const blocked = open.filter((i) => (i.blockedBy ?? []).some((b) => openIds.has(b._id ?? b)));
|
|
157
|
+
const overdue = open.filter((i) => i.dueDate != null && i.dueDate < now);
|
|
158
|
+
const stale = active.filter((i) => (i.modifiedOn ?? 0) < since);
|
|
159
|
+
const merged = prs.filter((p) => p.state === 2 && (p.mergedAt ?? p.modifiedOn ?? 0) >= since);
|
|
160
|
+
const openPrs = prs.filter((p) => p.state === 0);
|
|
161
|
+
const time = reports.filter((r) => (r.date ?? 0) >= since);
|
|
162
|
+
const hours = time.reduce((s, r) => s + (r.value ?? 0), 0);
|
|
163
|
+
const L = 15;
|
|
164
|
+
const section = (title, list, render = (i) => line(i)) => {
|
|
165
|
+
if (list.length === 0)
|
|
166
|
+
return;
|
|
167
|
+
out.push('', `## ${title} (${list.length})`, ...list.slice(0, L).map(render));
|
|
168
|
+
if (list.length > L)
|
|
169
|
+
out.push(`_…${list.length - L} more_`);
|
|
170
|
+
};
|
|
171
|
+
const out = [
|
|
172
|
+
`# ${String(project.name)} (${String(project.identifier)}) — ${day(since)} to ${day(now)}`,
|
|
173
|
+
'',
|
|
174
|
+
`**${done.length} done · ${created.length} new · ${open.length} open (${active.length} in progress) · ${blocked.length} blocked · ${overdue.length} overdue · ${merged.length} PRs merged · ${Math.round(hours * 10) / 10}h logged**`
|
|
175
|
+
];
|
|
176
|
+
section('Done', done);
|
|
177
|
+
section('New', created, (i) => line(i, ` — ${String(st.get(i.status)?.name ?? '?')}`));
|
|
178
|
+
section('In progress', active, (i) => line(i, stale.includes(i) ? ' · ⚠️ no change in this period' : ''));
|
|
179
|
+
section('Blocked', blocked, (i) => line(i, ` — by ${i.blockedBy.map((b) => String(all.find((x) => x._id === (b._id ?? b))?.identifier ?? '?')).join(', ')}`));
|
|
180
|
+
section('Overdue', overdue, (i) => line(i, ` — due ${day(i.dueDate)}`));
|
|
181
|
+
section('Pull requests merged', merged);
|
|
182
|
+
section('Pull requests open', openPrs, (p) => line(p, p.reviewDecision != null ? ` · ${['changes requested', 'approved', 'review required'][p.reviewDecision] ?? ''}` : ''));
|
|
183
|
+
const ms = milestones.filter((m) => m.status !== 2 && m.status !== 3).sort((a, b) => (a.targetDate ?? Infinity) - (b.targetDate ?? Infinity));
|
|
184
|
+
section('Milestones', ms, (m) => {
|
|
185
|
+
const of = issues.filter((i) => i.milestone === m._id);
|
|
186
|
+
const late = m.targetDate != null && m.targetDate < now ? ' ⚠️ past target' : '';
|
|
187
|
+
return `- **${String(m.label)}** — ${of.filter(won).length}/${of.length} done (${pct(of.filter(won).length, of.length)})${m.targetDate != null ? `, target ${day(m.targetDate)}${late}` : ''}`;
|
|
188
|
+
});
|
|
189
|
+
if (time.length > 0) {
|
|
190
|
+
const per = new Map();
|
|
191
|
+
for (const r of time) {
|
|
192
|
+
const n = names.get(r.employee) ?? '?';
|
|
193
|
+
per.set(n, (per.get(n) ?? 0) + (r.value ?? 0));
|
|
194
|
+
}
|
|
195
|
+
out.push('', '## Time logged', ...[...per].sort((a, b) => b[1] - a[1]).map(([n, h]) => `- ${n}: ${Math.round(h * 10) / 10}h`));
|
|
196
|
+
}
|
|
197
|
+
return out.join('\n');
|
|
198
|
+
});
|
|
199
|
+
// ── Standup: what I did since the last working day, what's on today, what blocks me ──
|
|
200
|
+
/** Start of the previous working day (Monday → Friday), UTC. */
|
|
201
|
+
function lastWorkingDay(now = Date.now()) {
|
|
202
|
+
const today = dayStart(new Date(now).toISOString());
|
|
203
|
+
const dow = new Date(today).getUTCDay(); // 0 Sun … 6 Sat
|
|
204
|
+
const back = dow === 1 ? 3 : dow === 0 ? 2 : 1;
|
|
205
|
+
return today - back * DAY;
|
|
206
|
+
}
|
|
207
|
+
/** Sections of a markdown text by "## " heading. */
|
|
208
|
+
const sections = (md) => new Map(md.split(/\n(?=## )/).filter((s) => s.startsWith('## ')).map((s) => [s.split('\n')[0].replace(/^## | \(.*$/g, ''), s.trim()]));
|
|
209
|
+
exports.standup = (0, errors_1.wrapToolHandler)(async (args) => {
|
|
210
|
+
const client = await (0, connection_1.getConnection)(args.workspace);
|
|
211
|
+
const since = args.since != null ? (0, agent_1.parseSince)(args.since) : lastWorkingDay();
|
|
212
|
+
const me = await (0, people_1.currentPerson)(client);
|
|
213
|
+
const idents = await client.findAll('contact:class:SocialIdentity', { attachedTo: me._id });
|
|
214
|
+
const mine = [...new Set([client.user, ...idents.map((i) => i._id)])];
|
|
215
|
+
const [updates, comments, reports, doneTodos] = await Promise.all([
|
|
216
|
+
client.findAll(classes_1.C.DocUpdateMessage, { createdBy: { $in: mine }, createdOn: { $gte: since } }, { limit: 1000, sort: { createdOn: core_1.SortingOrder.Descending } }),
|
|
217
|
+
client.findAll('chunter:class:ChatMessage', { createdBy: { $in: mine }, createdOn: { $gte: since }, attachedToClass: { $in: [tracker_1.default.class.Issue, classes_1.C.PullRequest] } }, { limit: 200 }),
|
|
218
|
+
client.findAll(tracker_1.default.class.TimeSpendReport, { employee: me._id, date: { $gte: since } }),
|
|
219
|
+
client.findAll(classes_1.C.ToDo, { user: me._id, doneOn: { $gte: since } })
|
|
220
|
+
]);
|
|
221
|
+
// What I did, per issue: created, status changes, other fields, comments, time.
|
|
222
|
+
const per = new Map();
|
|
223
|
+
const entry = (id) => per.get(id) ?? per.set(id, { created: false, status: [], fields: new Set(), comments: 0, hours: 0 }).get(id);
|
|
224
|
+
const statusIds = new Set();
|
|
225
|
+
updates.sort((a, b) => (a.createdOn ?? 0) - (b.createdOn ?? 0));
|
|
226
|
+
for (const u of updates) {
|
|
227
|
+
if (u.objectClass != null && u.objectClass !== tracker_1.default.class.Issue && u.objectClass !== classes_1.C.PullRequest && u.attachedToClass !== tracker_1.default.class.Issue)
|
|
228
|
+
continue;
|
|
229
|
+
const e = entry(u.attachedTo);
|
|
230
|
+
if (u.action === 'create')
|
|
231
|
+
e.created = true;
|
|
232
|
+
else if (u.attributeUpdates?.attrKey === 'status') {
|
|
233
|
+
for (const s of u.attributeUpdates.set ?? []) {
|
|
234
|
+
e.status.push(s);
|
|
235
|
+
statusIds.add(s);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else if (u.attributeUpdates?.attrKey != null && !['rank', 'subIssues', 'comments', 'reports', 'reportedTime', 'remainingTime', 'modifiedOn'].includes(u.attributeUpdates.attrKey))
|
|
239
|
+
e.fields.add(u.attributeUpdates.attrKey === 'attachedTo' ? 'parent' : u.attributeUpdates.attrKey);
|
|
240
|
+
}
|
|
241
|
+
for (const c of comments)
|
|
242
|
+
entry(c.attachedTo).comments++;
|
|
243
|
+
for (const r of reports)
|
|
244
|
+
entry(r.attachedTo).hours += r.value ?? 0;
|
|
245
|
+
const issues = per.size > 0 ? await client.findAll(tracker_1.default.class.Issue, { _id: { $in: [...per.keys()] } }) : [];
|
|
246
|
+
const stNames = new Map((statusIds.size > 0 ? await client.findAll('core:class:Status', { _id: { $in: [...statusIds] } }) : []).map((s) => [s._id, s.name]));
|
|
247
|
+
const out = [`# Standup — since ${new Date(since).toISOString().slice(0, 16).replace('T', ' ')} UTC`, '', '## Done / worked on'];
|
|
248
|
+
if (updates.length === 1000)
|
|
249
|
+
out.push('_Very busy period: only the latest 1000 changes are counted._');
|
|
250
|
+
// Most telling first: status changes and new issues, then comments/time, then field edits.
|
|
251
|
+
const weight = (i) => { const e = per.get(i._id); return e.status.length > 0 || e.created ? 0 : e.comments > 0 || e.hours > 0 ? 1 : 2; };
|
|
252
|
+
issues.sort((a, b) => weight(a) - weight(b));
|
|
253
|
+
const did = issues.slice(0, 15).map((i) => {
|
|
254
|
+
const e = per.get(i._id);
|
|
255
|
+
const parts = [
|
|
256
|
+
e.created ? 'created' : undefined,
|
|
257
|
+
e.status.length > 0 ? `→ ${e.status.map((s) => stNames.get(s) ?? '?').filter((n, k, a) => k === 0 || n !== a[k - 1]).join(' → ')}` : undefined,
|
|
258
|
+
e.fields.size > 0 ? `changed ${[...e.fields].join(', ')}` : undefined,
|
|
259
|
+
e.comments > 0 ? `${e.comments} comment(s)` : undefined,
|
|
260
|
+
e.hours > 0 ? `${Math.round(e.hours * 10) / 10}h` : undefined
|
|
261
|
+
].filter((p) => p != null);
|
|
262
|
+
return `- **${String(i.identifier)}** ${String(i.title)} — ${parts.join(' · ')}`;
|
|
263
|
+
});
|
|
264
|
+
if (issues.length > 15)
|
|
265
|
+
did.push(`_…${issues.length - 15} more issues touched — changes_since for all._`);
|
|
266
|
+
for (const t of doneTodos)
|
|
267
|
+
did.push(`- [x] ${String(t.title)}`);
|
|
268
|
+
out.push(...(did.length > 0 ? did : ['Nothing recorded in Huly.']));
|
|
269
|
+
// Today and blockers: reuse my_day (calendar, to-dos, my issues).
|
|
270
|
+
const day = sections(await myDayText(client, undefined, 5));
|
|
271
|
+
out.push('', '## Today');
|
|
272
|
+
for (const key of ['Calendar', 'To-dos', 'My issues']) {
|
|
273
|
+
const s = day.get(key);
|
|
274
|
+
if (s != null)
|
|
275
|
+
out.push(s.replace(/^## /, '### '));
|
|
276
|
+
}
|
|
277
|
+
const blocked = await myBlockedIssues(client, me._id);
|
|
278
|
+
out.push('', '## Blockers', ...(blocked.length > 0 ? blocked : ['None in Huly.']));
|
|
279
|
+
return out.join('\n');
|
|
280
|
+
});
|
|
281
|
+
/** My open issues that wait for another open issue. */
|
|
282
|
+
async function myBlockedIssues(client, me) {
|
|
283
|
+
const mineOpen = (await client.findAll(tracker_1.default.class.Issue, { assignee: me })).filter((i) => (i.blockedBy ?? []).length > 0);
|
|
284
|
+
if (mineOpen.length === 0)
|
|
285
|
+
return [];
|
|
286
|
+
const blockerIds = [...new Set(mineOpen.flatMap((i) => i.blockedBy.map((b) => b._id ?? b)))];
|
|
287
|
+
const [blockers, statuses] = await Promise.all([
|
|
288
|
+
client.findAll(tracker_1.default.class.Issue, { _id: { $in: blockerIds } }),
|
|
289
|
+
client.findAll('core:class:Status', {})
|
|
290
|
+
]);
|
|
291
|
+
const closed = new Set(statuses.filter((s) => s.category === task_1.default.statusCategory.Won || s.category === task_1.default.statusCategory.Lost).map((s) => s._id));
|
|
292
|
+
const out = [];
|
|
293
|
+
for (const i of mineOpen) {
|
|
294
|
+
if (closed.has(i.status))
|
|
295
|
+
continue;
|
|
296
|
+
const open = i.blockedBy.map((b) => blockers.find((x) => x._id === (b._id ?? b))).filter((b) => b != null && !closed.has(b.status));
|
|
297
|
+
if (open.length > 0)
|
|
298
|
+
out.push(`- **${String(i.identifier)}** ${String(i.title)} — waits for ${open.map((b) => `${String(b.identifier)}`).join(', ')}`);
|
|
299
|
+
}
|
|
300
|
+
return out;
|
|
301
|
+
}
|
|
302
|
+
// ── Inbox triage: per unread object, what happened since I last looked ──
|
|
303
|
+
/** One-line plain text of a comment: no HTML (bot comments), no entities, collapsed whitespace. */
|
|
304
|
+
const plain = (markup) => (0, format_1.markupToText)(markup).replace(/<[^>]+>/g, ' ').replace(/ | /g, ' ').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&').replace(/\s+/g, ' ').trim();
|
|
305
|
+
exports.plain = plain;
|
|
306
|
+
exports.triageInbox = (0, errors_1.wrapToolHandler)(async (args) => {
|
|
307
|
+
const client = await (0, connection_1.getConnection)(args.workspace);
|
|
308
|
+
const uuid = await (0, people_1.currentAccountUuid)(client);
|
|
309
|
+
const limit = args.limit ?? 10;
|
|
310
|
+
const unread = await client.findAll(classes_1.C.InboxNotification, { user: uuid, isViewed: false, archived: false }, { sort: { createdOn: core_1.SortingOrder.Descending }, limit: 500 });
|
|
311
|
+
if (unread.length === 0)
|
|
312
|
+
return 'Inbox zero — nothing unread.';
|
|
313
|
+
const groups = new Map();
|
|
314
|
+
for (const n of unread)
|
|
315
|
+
groups.set(n.docNotifyContext, [...(groups.get(n.docNotifyContext) ?? []), n]);
|
|
316
|
+
const ctxIds = [...groups.keys()].slice(0, limit);
|
|
317
|
+
const ctxs = await client.findAll(classes_1.C.DocNotifyContext, { _id: { $in: ctxIds } });
|
|
318
|
+
const statuses = new Map((await client.findAll('core:class:Status', {})).map((s) => [s._id, s.name]));
|
|
319
|
+
const names = await (0, people_1.peopleNames)(client);
|
|
320
|
+
const out = [`# Inbox triage — ${unread.length}${unread.length === 500 ? '+' : ''} unread on ${groups.size} item(s)${groups.size > limit ? `, newest ${limit} shown` : ''}`];
|
|
321
|
+
for (const ctxId of ctxIds) {
|
|
322
|
+
const notes = groups.get(ctxId);
|
|
323
|
+
const ctx = ctxs.find((c) => c._id === ctxId);
|
|
324
|
+
const objectId = ctx?.objectId ?? notes[0].objectId;
|
|
325
|
+
const objectClass = ctx?.objectClass ?? notes[0].objectClass;
|
|
326
|
+
// Since I last looked: the context's lastViewedTimestamp, else just before the oldest unread.
|
|
327
|
+
const since = ctx?.lastViewedTimestamp ?? Math.min(...notes.map((n) => n.createdOn ?? Date.now())) - 1;
|
|
328
|
+
const [doc, found] = await Promise.all([
|
|
329
|
+
objectClass != null ? client.findOne(objectClass, { _id: objectId }) : undefined,
|
|
330
|
+
client.findAll(classes_1.C.ActivityMessage, { attachedTo: objectId, createdOn: { $gt: since } }, { sort: { createdOn: core_1.SortingOrder.Descending }, limit: 50 })
|
|
331
|
+
]);
|
|
332
|
+
const msgs = found.filter((m) => (m.createdOn ?? 0) > since).sort((a, b) => (b.createdOn ?? 0) - (a.createdOn ?? 0));
|
|
333
|
+
const label = objectClass != null ? await (0, resolve_1.loadLabel)(client, objectClass, objectId) : '(unknown)';
|
|
334
|
+
const state = doc?.status != null ? ` — ${String(statuses.get(doc.status) ?? '?')}${doc.assignee != null ? ` · ${names.get(doc.assignee) ?? '?'}` : ''}` : '';
|
|
335
|
+
out.push('', `## ${label}${state} (${notes.length} unread)`);
|
|
336
|
+
const who = await (0, people_1.socialIdNames)(client, [...new Set(msgs.map((m) => m.createdBy).filter((x) => x != null))]);
|
|
337
|
+
// Only real comments (ChatMessage and derived); activity-info messages carry a label id as "message".
|
|
338
|
+
const h = client.getHierarchy();
|
|
339
|
+
const comments = msgs.filter((m) => typeof m.message === 'string' && (m._class === 'chunter:class:ChatMessage' || h.isDerived(m._class, 'chunter:class:ChatMessage')));
|
|
340
|
+
const fields = [...new Set(msgs.filter((m) => m._class === classes_1.C.DocUpdateMessage).map((m) => m.action === 'create' ? 'created' : m.attributeUpdates?.attrKey === 'attachedTo' ? 'parent' : m.attributeUpdates?.attrKey).filter((k) => k != null && !['rank', 'subIssues', 'comments', 'modifiedOn'].includes(k)))];
|
|
341
|
+
if (fields.length > 0)
|
|
342
|
+
out.push(`- changed: ${fields.join(', ')}`);
|
|
343
|
+
for (const m of comments.slice(0, 3).reverse())
|
|
344
|
+
out.push(`- 💬 **${who.get(m.createdBy) ?? '?'}**: ${(0, format_1.clip)((0, exports.plain)(m.message), 200, 'list_comments')}`);
|
|
345
|
+
if (comments.length > 3)
|
|
346
|
+
out.push(`- _${comments.length - 3} older new comment(s)_`);
|
|
347
|
+
if (fields.length === 0 && comments.length === 0)
|
|
348
|
+
out.push('- (no details recorded)');
|
|
349
|
+
}
|
|
350
|
+
out.push('', '_Done with an item? mark_notifications_read with its id (e.g. PROJ-12)._');
|
|
351
|
+
return out.join('\n');
|
|
352
|
+
});
|
|
353
|
+
// ── Release notes from a milestone ──
|
|
354
|
+
const FIX = /\b(bug|fix|fixes|fixed|hotfix|regression|crash|security|fehler|bugfix|absturz|stürzt|behoben|reparier\w*|sicherheit)\b/i;
|
|
355
|
+
exports.releaseNotes = (0, errors_1.wrapToolHandler)(async (args) => {
|
|
356
|
+
const client = await (0, connection_1.getConnection)(args.workspace);
|
|
357
|
+
const project = await client.findOne(tracker_1.default.class.Project, { identifier: args.projectIdentifier });
|
|
358
|
+
if (project == null)
|
|
359
|
+
throw new Error(`Project '${args.projectIdentifier}' not found.`);
|
|
360
|
+
const milestones = await client.findAll(tracker_1.default.class.Milestone, { space: project._id });
|
|
361
|
+
const ms = milestones.find((m) => String(m.label).toLowerCase() === args.milestoneLabel.trim().toLowerCase());
|
|
362
|
+
if (ms == null)
|
|
363
|
+
throw new Error(`Milestone '${args.milestoneLabel}' not found. Milestones: ${milestones.map((m) => m.label).join(', ') || 'none'}.`);
|
|
364
|
+
const all = await client.findAll(tracker_1.default.class.Issue, { space: project._id, milestone: ms._id });
|
|
365
|
+
const issues = all.filter((i) => i._class !== classes_1.C.PullRequest);
|
|
366
|
+
const ids = issues.map((i) => i._id);
|
|
367
|
+
const [statuses, labels, prs] = await Promise.all([
|
|
368
|
+
client.findAll('core:class:Status', {}),
|
|
369
|
+
ids.length > 0 ? client.findAll('tags:class:TagReference', { attachedTo: { $in: ids } }) : [],
|
|
370
|
+
ids.length > 0 ? client.findAll(classes_1.C.PullRequest, { attachedTo: { $in: ids } }) : []
|
|
371
|
+
]);
|
|
372
|
+
const cat = new Map(statuses.map((s) => [s._id, s.category]));
|
|
373
|
+
// Epics (issues with sub-issues) are containers; their children are listed instead.
|
|
374
|
+
const done = issues.filter((i) => cat.get(i.status) === task_1.default.statusCategory.Won && (i.subIssues ?? 0) === 0);
|
|
375
|
+
const open = issues.filter((i) => cat.get(i.status) !== task_1.default.statusCategory.Won && cat.get(i.status) !== task_1.default.statusCategory.Lost);
|
|
376
|
+
const isFix = (i) => FIX.test(String(i.title)) || labels.some((l) => l.attachedTo === i._id && FIX.test(String(l.title)));
|
|
377
|
+
const pr = (i) => {
|
|
378
|
+
const nums = prs.filter((p) => p.attachedTo === i._id && p.state === 2).map((p) => `#${String(p['github:mixin:GithubIssue']?.githubNumber ?? p.identifier)}`);
|
|
379
|
+
return nums.length > 0 ? ` (${nums.join(', ')})` : '';
|
|
380
|
+
};
|
|
381
|
+
const line = (i) => `- ${String(i.title)} — ${String(i.identifier)}${pr(i)}`;
|
|
382
|
+
const out = [`# ${String(ms.label)}${ms.targetDate != null ? ` — ${day(ms.targetDate)}` : ''}`];
|
|
383
|
+
const features = done.filter((i) => !isFix(i));
|
|
384
|
+
const fixes = done.filter(isFix);
|
|
385
|
+
if (features.length > 0)
|
|
386
|
+
out.push('', '## New and changed', ...features.map(line));
|
|
387
|
+
if (fixes.length > 0)
|
|
388
|
+
out.push('', '## Fixes', ...fixes.map(line));
|
|
389
|
+
if (done.length === 0)
|
|
390
|
+
out.push('', 'Nothing done in this milestone yet.');
|
|
391
|
+
if (open.length > 0)
|
|
392
|
+
out.push('', `## Still open (${open.length}) — not part of the notes`, ...open.slice(0, 15).map((i) => `- ${String(i.identifier)} ${String(i.title)}`));
|
|
393
|
+
return out.join('\n');
|
|
394
|
+
});
|
|
395
|
+
//# sourceMappingURL=overview.js.map
|