@mgsoftwarebv/mcp-server-bridge 3.5.21 → 3.5.23
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/dist/index.js +1019 -308
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -105607,8 +105607,86 @@ var Server = class extends Protocol {
|
|
|
105607
105607
|
};
|
|
105608
105608
|
|
|
105609
105609
|
// src/telemetry.ts
|
|
105610
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
105611
|
+
function isUuid(value) {
|
|
105612
|
+
return UUID_RE.test(value);
|
|
105613
|
+
}
|
|
105610
105614
|
var MCP_TELEMETRY_CATEGORY = "mcp_tool_call";
|
|
105611
105615
|
var MCP_TELEMETRY_SOURCE = "mcp";
|
|
105616
|
+
function assignTicketRef(refs, value) {
|
|
105617
|
+
const trimmed = value.trim();
|
|
105618
|
+
if (!trimmed) return;
|
|
105619
|
+
if (isUuid(trimmed)) {
|
|
105620
|
+
refs.ticketId = trimmed;
|
|
105621
|
+
return;
|
|
105622
|
+
}
|
|
105623
|
+
refs.ticketNumber = trimmed;
|
|
105624
|
+
}
|
|
105625
|
+
function collectTicketRefsFromEntity(entity) {
|
|
105626
|
+
const refs = [];
|
|
105627
|
+
if (entity.ticketId) refs.push(entity.ticketId);
|
|
105628
|
+
if (entity.ticketNumber) refs.push(entity.ticketNumber);
|
|
105629
|
+
return refs;
|
|
105630
|
+
}
|
|
105631
|
+
async function resolveTicketRefMap(params) {
|
|
105632
|
+
const uuidRefs = /* @__PURE__ */ new Set();
|
|
105633
|
+
const numberRefs = /* @__PURE__ */ new Set();
|
|
105634
|
+
const allRefs = /* @__PURE__ */ new Set();
|
|
105635
|
+
for (const ref of params.refs) {
|
|
105636
|
+
const trimmed = ref.trim();
|
|
105637
|
+
if (!trimmed) continue;
|
|
105638
|
+
allRefs.add(trimmed);
|
|
105639
|
+
if (isUuid(trimmed)) uuidRefs.add(trimmed);
|
|
105640
|
+
else numberRefs.add(trimmed);
|
|
105641
|
+
}
|
|
105642
|
+
if (allRefs.size === 0) {
|
|
105643
|
+
return { byRef: /* @__PURE__ */ new Map(), unresolved: [] };
|
|
105644
|
+
}
|
|
105645
|
+
const lookup = [];
|
|
105646
|
+
if (uuidRefs.size > 0) {
|
|
105647
|
+
lookup.push(inArray(schema_exports.tickets.id, [...uuidRefs]));
|
|
105648
|
+
}
|
|
105649
|
+
for (const num of numberRefs) {
|
|
105650
|
+
lookup.push(sql`lower(${schema_exports.tickets.ticketNumber}) = lower(${num})`);
|
|
105651
|
+
}
|
|
105652
|
+
const ticketRows = await db.select({
|
|
105653
|
+
id: schema_exports.tickets.id,
|
|
105654
|
+
ticketNumber: schema_exports.tickets.ticketNumber,
|
|
105655
|
+
title: schema_exports.tickets.title,
|
|
105656
|
+
projectId: schema_exports.tickets.projectId
|
|
105657
|
+
}).from(schema_exports.tickets).where(
|
|
105658
|
+
and(
|
|
105659
|
+
inArray(schema_exports.tickets.teamId, params.teamIds),
|
|
105660
|
+
eq(schema_exports.tickets.isDeleted, false),
|
|
105661
|
+
or(...lookup)
|
|
105662
|
+
)
|
|
105663
|
+
);
|
|
105664
|
+
const byRef = /* @__PURE__ */ new Map();
|
|
105665
|
+
for (const t9 of ticketRows) {
|
|
105666
|
+
const info = {
|
|
105667
|
+
id: t9.id,
|
|
105668
|
+
ticketNumber: t9.ticketNumber,
|
|
105669
|
+
title: t9.title,
|
|
105670
|
+
projectId: t9.projectId
|
|
105671
|
+
};
|
|
105672
|
+
byRef.set(t9.id, info);
|
|
105673
|
+
if (t9.ticketNumber) {
|
|
105674
|
+
byRef.set(t9.ticketNumber, info);
|
|
105675
|
+
byRef.set(t9.ticketNumber.toLowerCase(), info);
|
|
105676
|
+
}
|
|
105677
|
+
}
|
|
105678
|
+
const unresolved = [...allRefs].filter(
|
|
105679
|
+
(ref) => !byRef.has(ref) && !byRef.has(ref.toLowerCase())
|
|
105680
|
+
);
|
|
105681
|
+
return { byRef, unresolved };
|
|
105682
|
+
}
|
|
105683
|
+
function lookupResolvedTicket(byRef, entity) {
|
|
105684
|
+
for (const ref of collectTicketRefsFromEntity(entity)) {
|
|
105685
|
+
const hit = byRef.get(ref) ?? byRef.get(ref.toLowerCase());
|
|
105686
|
+
if (hit) return hit;
|
|
105687
|
+
}
|
|
105688
|
+
return void 0;
|
|
105689
|
+
}
|
|
105612
105690
|
var ENTITY_ID_FIELDS = [
|
|
105613
105691
|
"ticketId",
|
|
105614
105692
|
"projectId",
|
|
@@ -105647,15 +105725,20 @@ function extractSafeEntityRefs(toolName, args2) {
|
|
|
105647
105725
|
if (!args2 || typeof args2 !== "object") return refs;
|
|
105648
105726
|
for (const field of ENTITY_ID_FIELDS) {
|
|
105649
105727
|
const value = args2[field];
|
|
105650
|
-
if (typeof value
|
|
105651
|
-
|
|
105728
|
+
if (typeof value !== "string" || !value.trim()) continue;
|
|
105729
|
+
const trimmed = value.trim();
|
|
105730
|
+
if (field === "ticketId") {
|
|
105731
|
+
assignTicketRef(refs, trimmed);
|
|
105732
|
+
continue;
|
|
105652
105733
|
}
|
|
105734
|
+
refs[field] = trimmed;
|
|
105653
105735
|
}
|
|
105654
105736
|
const rawId = typeof args2.id === "string" ? args2.id.trim() : "";
|
|
105655
105737
|
if (rawId) {
|
|
105656
105738
|
const n3 = toolName.toLowerCase();
|
|
105657
|
-
if (!refs.ticketId && n3.includes("ticket"))
|
|
105658
|
-
|
|
105739
|
+
if (!refs.ticketId && !refs.ticketNumber && n3.includes("ticket")) {
|
|
105740
|
+
assignTicketRef(refs, rawId);
|
|
105741
|
+
} else if (!refs.projectId && n3.includes("project")) refs.projectId = rawId;
|
|
105659
105742
|
else if (!refs.customerId && n3.includes("customer")) refs.customerId = rawId;
|
|
105660
105743
|
else if (!refs.invoiceId && n3.includes("invoice")) refs.invoiceId = rawId;
|
|
105661
105744
|
else if (!refs.documentId && n3.includes("document")) refs.documentId = rawId;
|
|
@@ -105744,6 +105827,7 @@ async function queryMcpActivityRows(params) {
|
|
|
105744
105827
|
durationMs: row.durationMs ?? null,
|
|
105745
105828
|
entity: {
|
|
105746
105829
|
ticketId: readString(meta5, "ticketId"),
|
|
105830
|
+
ticketNumber: readString(meta5, "ticketNumber"),
|
|
105747
105831
|
projectId: readString(meta5, "projectId"),
|
|
105748
105832
|
customerId: readString(meta5, "customerId"),
|
|
105749
105833
|
invoiceId: readString(meta5, "invoiceId"),
|
|
@@ -106059,6 +106143,20 @@ var TOOLS = [
|
|
|
106059
106143
|
items: { type: "string" },
|
|
106060
106144
|
description: "Filter by tag IDs"
|
|
106061
106145
|
},
|
|
106146
|
+
projectTag: {
|
|
106147
|
+
type: "string",
|
|
106148
|
+
description: "Filter by project label name \u2014 ticket's project must carry this label"
|
|
106149
|
+
},
|
|
106150
|
+
projectTags: {
|
|
106151
|
+
type: "array",
|
|
106152
|
+
items: { type: "string" },
|
|
106153
|
+
description: "Filter by project label names (OR)"
|
|
106154
|
+
},
|
|
106155
|
+
projectTagIds: {
|
|
106156
|
+
type: "array",
|
|
106157
|
+
items: { type: "string" },
|
|
106158
|
+
description: "Filter by project label tag IDs (OR)"
|
|
106159
|
+
},
|
|
106062
106160
|
pageSize: { type: "number", default: 20, maximum: 100 }
|
|
106063
106161
|
},
|
|
106064
106162
|
required: []
|
|
@@ -106072,6 +106170,20 @@ var TOOLS = [
|
|
|
106072
106170
|
properties: {
|
|
106073
106171
|
teamId: teamIdProp,
|
|
106074
106172
|
projectId: { type: "string", description: "Filter to one project UUID." },
|
|
106173
|
+
projectTag: {
|
|
106174
|
+
type: "string",
|
|
106175
|
+
description: "Filter by project label name on the ticket's project"
|
|
106176
|
+
},
|
|
106177
|
+
projectTags: {
|
|
106178
|
+
type: "array",
|
|
106179
|
+
items: { type: "string" },
|
|
106180
|
+
description: "Filter by project label names (OR)"
|
|
106181
|
+
},
|
|
106182
|
+
projectTagIds: {
|
|
106183
|
+
type: "array",
|
|
106184
|
+
items: { type: "string" },
|
|
106185
|
+
description: "Filter by project label tag IDs (OR)"
|
|
106186
|
+
},
|
|
106075
106187
|
priority: {
|
|
106076
106188
|
type: "string",
|
|
106077
106189
|
enum: ["low", "medium", "high", "critical"]
|
|
@@ -106110,6 +106222,9 @@ var TOOLS = [
|
|
|
106110
106222
|
description: "When assigneeId='all', group the items per assignee."
|
|
106111
106223
|
},
|
|
106112
106224
|
projectId: { type: "string" },
|
|
106225
|
+
projectTag: { type: "string" },
|
|
106226
|
+
projectTags: { type: "array", items: { type: "string" } },
|
|
106227
|
+
projectTagIds: { type: "array", items: { type: "string" } },
|
|
106113
106228
|
priority: {
|
|
106114
106229
|
type: "string",
|
|
106115
106230
|
enum: ["low", "medium", "high", "critical"]
|
|
@@ -106148,7 +106263,10 @@ var TOOLS = [
|
|
|
106148
106263
|
type: "boolean",
|
|
106149
106264
|
default: true,
|
|
106150
106265
|
description: "Include the unassigned bucket in the overview."
|
|
106151
|
-
}
|
|
106266
|
+
},
|
|
106267
|
+
projectTag: { type: "string" },
|
|
106268
|
+
projectTags: { type: "array", items: { type: "string" } },
|
|
106269
|
+
projectTagIds: { type: "array", items: { type: "string" } }
|
|
106152
106270
|
},
|
|
106153
106271
|
required: []
|
|
106154
106272
|
}
|
|
@@ -106783,13 +106901,27 @@ var TOOLS = [
|
|
|
106783
106901
|
},
|
|
106784
106902
|
{
|
|
106785
106903
|
name: "get-projects",
|
|
106786
|
-
description: "Get projects with optional filtering. Each project includes its ID
|
|
106904
|
+
description: "Get projects with optional filtering. Each project includes its ID, project labels (tags on the project itself \u2014 not ticket tags), and when archived, its archive timestamp/reason. Archived projects are hidden by default; pass status 'archived' or 'all' to include them.",
|
|
106787
106905
|
inputSchema: {
|
|
106788
106906
|
type: "object",
|
|
106789
106907
|
properties: {
|
|
106790
106908
|
teamId: teamIdProp,
|
|
106791
106909
|
customerId: { type: "string", description: "Filter by customer ID" },
|
|
106792
106910
|
q: { type: "string", description: "Search query for project name" },
|
|
106911
|
+
projectTag: {
|
|
106912
|
+
type: "string",
|
|
106913
|
+
description: "Filter by a single project label name (case-insensitive)"
|
|
106914
|
+
},
|
|
106915
|
+
projectTags: {
|
|
106916
|
+
type: "array",
|
|
106917
|
+
items: { type: "string" },
|
|
106918
|
+
description: "Filter by project label names (OR)"
|
|
106919
|
+
},
|
|
106920
|
+
projectTagIds: {
|
|
106921
|
+
type: "array",
|
|
106922
|
+
items: { type: "string" },
|
|
106923
|
+
description: "Filter by project label tag IDs (OR)"
|
|
106924
|
+
},
|
|
106793
106925
|
status: {
|
|
106794
106926
|
type: "string",
|
|
106795
106927
|
enum: ["active", "archived", "all"],
|
|
@@ -106803,7 +106935,7 @@ var TOOLS = [
|
|
|
106803
106935
|
},
|
|
106804
106936
|
{
|
|
106805
106937
|
name: "create-project",
|
|
106806
|
-
description: "Create a new project",
|
|
106938
|
+
description: "Create a new project. Optionally assign project labels (general team tags for grouping/filtering \u2014 distinct from ticket tags).",
|
|
106807
106939
|
inputSchema: {
|
|
106808
106940
|
type: "object",
|
|
106809
106941
|
properties: {
|
|
@@ -106811,6 +106943,16 @@ var TOOLS = [
|
|
|
106811
106943
|
name: { type: "string", description: "Project name" },
|
|
106812
106944
|
description: { type: "string" },
|
|
106813
106945
|
customerId: { type: "string" },
|
|
106946
|
+
projectTags: {
|
|
106947
|
+
type: "array",
|
|
106948
|
+
items: { type: "string" },
|
|
106949
|
+
description: "Project label names to assign (created if missing)"
|
|
106950
|
+
},
|
|
106951
|
+
projectTagIds: {
|
|
106952
|
+
type: "array",
|
|
106953
|
+
items: { type: "string" },
|
|
106954
|
+
description: "Project label tag IDs to assign"
|
|
106955
|
+
},
|
|
106814
106956
|
status: {
|
|
106815
106957
|
type: "string",
|
|
106816
106958
|
enum: ["active", "on_hold", "completed", "cancelled"],
|
|
@@ -106822,7 +106964,7 @@ var TOOLS = [
|
|
|
106822
106964
|
},
|
|
106823
106965
|
{
|
|
106824
106966
|
name: "update-project",
|
|
106825
|
-
description: "Update an existing project's fields (name, description, customer, rate, currency, billable, estimate, internal). Only provided fields change. Renaming a project renumbers its tickets. To retire a mistakenly-created project use archive-project (reversible) or delete-project (empty projects only). Find the project id via get-projects.",
|
|
106967
|
+
description: "Update an existing project's fields (name, description, customer, rate, currency, billable, estimate, internal, project labels). Only provided fields change. When projectTags/projectTagIds are provided, labels are replaced entirely. Renaming a project renumbers its tickets. To retire a mistakenly-created project use archive-project (reversible) or delete-project (empty projects only). Find the project id via get-projects.",
|
|
106826
106968
|
inputSchema: {
|
|
106827
106969
|
type: "object",
|
|
106828
106970
|
properties: {
|
|
@@ -106844,11 +106986,83 @@ var TOOLS = [
|
|
|
106844
106986
|
internal: {
|
|
106845
106987
|
type: "boolean",
|
|
106846
106988
|
description: "Whether this is an internal (no-customer) project"
|
|
106989
|
+
},
|
|
106990
|
+
projectTags: {
|
|
106991
|
+
type: "array",
|
|
106992
|
+
items: { type: "string" },
|
|
106993
|
+
description: "Replace project labels with these names (creates missing)"
|
|
106994
|
+
},
|
|
106995
|
+
projectTagIds: {
|
|
106996
|
+
type: "array",
|
|
106997
|
+
items: { type: "string" },
|
|
106998
|
+
description: "Replace project labels with these tag IDs"
|
|
106847
106999
|
}
|
|
106848
107000
|
},
|
|
106849
107001
|
required: ["id"]
|
|
106850
107002
|
}
|
|
106851
107003
|
},
|
|
107004
|
+
{
|
|
107005
|
+
name: "get-project-tags",
|
|
107006
|
+
description: "List project labels assigned to a project (metadata for grouping/filtering \u2014 not ticket tags).",
|
|
107007
|
+
inputSchema: {
|
|
107008
|
+
type: "object",
|
|
107009
|
+
properties: {
|
|
107010
|
+
teamId: teamIdProp,
|
|
107011
|
+
projectId: { type: "string", description: "Project ID" }
|
|
107012
|
+
},
|
|
107013
|
+
required: ["projectId"]
|
|
107014
|
+
}
|
|
107015
|
+
},
|
|
107016
|
+
{
|
|
107017
|
+
name: "set-project-tags",
|
|
107018
|
+
description: "Replace all project labels on a project. Pass projectTags (names) and/or projectTagIds. Missing names are created as general team tags.",
|
|
107019
|
+
inputSchema: {
|
|
107020
|
+
type: "object",
|
|
107021
|
+
properties: {
|
|
107022
|
+
teamId: teamIdProp,
|
|
107023
|
+
projectId: { type: "string", description: "Project ID" },
|
|
107024
|
+
projectTags: {
|
|
107025
|
+
type: "array",
|
|
107026
|
+
items: { type: "string" },
|
|
107027
|
+
description: "Project label names"
|
|
107028
|
+
},
|
|
107029
|
+
projectTagIds: {
|
|
107030
|
+
type: "array",
|
|
107031
|
+
items: { type: "string" },
|
|
107032
|
+
description: "Project label tag IDs"
|
|
107033
|
+
}
|
|
107034
|
+
},
|
|
107035
|
+
required: ["projectId"]
|
|
107036
|
+
}
|
|
107037
|
+
},
|
|
107038
|
+
{
|
|
107039
|
+
name: "add-project-tag",
|
|
107040
|
+
description: "Add one project label to a project (merge, does not remove existing labels).",
|
|
107041
|
+
inputSchema: {
|
|
107042
|
+
type: "object",
|
|
107043
|
+
properties: {
|
|
107044
|
+
teamId: teamIdProp,
|
|
107045
|
+
projectId: { type: "string", description: "Project ID" },
|
|
107046
|
+
projectTag: { type: "string", description: "Project label name" },
|
|
107047
|
+
projectTagId: { type: "string", description: "Project label tag ID" }
|
|
107048
|
+
},
|
|
107049
|
+
required: ["projectId"]
|
|
107050
|
+
}
|
|
107051
|
+
},
|
|
107052
|
+
{
|
|
107053
|
+
name: "remove-project-tag",
|
|
107054
|
+
description: "Remove one project label from a project.",
|
|
107055
|
+
inputSchema: {
|
|
107056
|
+
type: "object",
|
|
107057
|
+
properties: {
|
|
107058
|
+
teamId: teamIdProp,
|
|
107059
|
+
projectId: { type: "string", description: "Project ID" },
|
|
107060
|
+
projectTag: { type: "string", description: "Project label name" },
|
|
107061
|
+
projectTagId: { type: "string", description: "Project label tag ID" }
|
|
107062
|
+
},
|
|
107063
|
+
required: ["projectId"]
|
|
107064
|
+
}
|
|
107065
|
+
},
|
|
106852
107066
|
{
|
|
106853
107067
|
name: "archive-project",
|
|
106854
107068
|
description: "Safely archive (soft-retire) a project \u2014 the recommended way to clean up a mistakenly-created project. Reversible and non-destructive: it keeps all tickets, hours, trips, documents and other data, and only hides the project from get-projects by default. Use this instead of delete-project whenever a project has any history. Find the project id via get-projects. Note: the archive flag is stored in projects.settings.archivedAt; the dashboard UI does not yet read it, so the project still appears there.",
|
|
@@ -108559,7 +108773,7 @@ async function resolveTeamScope(requestedTeamId) {
|
|
|
108559
108773
|
|
|
108560
108774
|
// src/tools/ticket-access.ts
|
|
108561
108775
|
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
108562
|
-
function
|
|
108776
|
+
function isUuid2(value) {
|
|
108563
108777
|
return UUID_REGEX.test(value);
|
|
108564
108778
|
}
|
|
108565
108779
|
function notFoundResponse(identifier) {
|
|
@@ -108592,7 +108806,7 @@ var ticketLookupFields = {
|
|
|
108592
108806
|
title: schema_exports.tickets.title
|
|
108593
108807
|
};
|
|
108594
108808
|
async function resolveTicketIdentifier(requestedTeamId, identifier) {
|
|
108595
|
-
if (
|
|
108809
|
+
if (isUuid2(identifier)) {
|
|
108596
108810
|
return { ok: true, id: identifier };
|
|
108597
108811
|
}
|
|
108598
108812
|
const scope = await resolveTeamScope(requestedTeamId);
|
|
@@ -121962,7 +122176,7 @@ var INVOICE_STATUSES = [
|
|
|
121962
122176
|
"scheduled",
|
|
121963
122177
|
"refunded"
|
|
121964
122178
|
];
|
|
121965
|
-
var
|
|
122179
|
+
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
121966
122180
|
function textResponse4(text3) {
|
|
121967
122181
|
return { content: [{ type: "text", text: text3 }] };
|
|
121968
122182
|
}
|
|
@@ -122023,7 +122237,7 @@ function parseStoredLineItems(value) {
|
|
|
122023
122237
|
}
|
|
122024
122238
|
async function loadInvoiceByIdentifier(identifier, teamIds) {
|
|
122025
122239
|
if (teamIds.length === 0) return null;
|
|
122026
|
-
const byId =
|
|
122240
|
+
const byId = UUID_RE2.test(identifier);
|
|
122027
122241
|
const filters = [inArray(schema_exports.invoices.teamId, teamIds)];
|
|
122028
122242
|
filters.push(
|
|
122029
122243
|
byId ? eq(schema_exports.invoices.id, identifier) : eq(schema_exports.invoices.invoiceNumber, identifier)
|
|
@@ -122802,88 +123016,493 @@ ${JSON.stringify(payload, null, 2)}
|
|
|
122802
123016
|
);
|
|
122803
123017
|
}
|
|
122804
123018
|
|
|
122805
|
-
// src/tools/
|
|
122806
|
-
|
|
122807
|
-
|
|
122808
|
-
"archived",
|
|
122809
|
-
"all"
|
|
122810
|
-
];
|
|
122811
|
-
var DEPENDENCY_LABELS2 = {
|
|
122812
|
-
tickets: "ticket(s)",
|
|
122813
|
-
timesheetEvents: "agenda/time entr(ies)",
|
|
122814
|
-
timesheetTemplates: "timesheet template(s)",
|
|
122815
|
-
trips: "trip(s)",
|
|
122816
|
-
tripTemplates: "trip template(s)"
|
|
122817
|
-
};
|
|
122818
|
-
function getProjectArchiveState(settings) {
|
|
122819
|
-
const obj = settings && typeof settings === "object" && !Array.isArray(settings) ? settings : {};
|
|
122820
|
-
const archivedAt = typeof obj.archivedAt === "string" && obj.archivedAt.trim().length > 0 ? obj.archivedAt : null;
|
|
122821
|
-
const archiveReason = typeof obj.archiveReason === "string" && obj.archiveReason.trim().length > 0 ? obj.archiveReason : null;
|
|
122822
|
-
return { archived: archivedAt !== null, archivedAt, archiveReason };
|
|
122823
|
-
}
|
|
122824
|
-
function withArchiveSettings(settings, archivedAt, reason) {
|
|
122825
|
-
const base = settings && typeof settings === "object" && !Array.isArray(settings) ? { ...settings } : {};
|
|
122826
|
-
base.archivedAt = archivedAt;
|
|
122827
|
-
if (reason && reason.trim().length > 0) {
|
|
122828
|
-
base.archiveReason = reason.trim();
|
|
122829
|
-
}
|
|
122830
|
-
return base;
|
|
122831
|
-
}
|
|
122832
|
-
function totalProjectDependencies(counts) {
|
|
122833
|
-
return counts.tickets + counts.timesheetEvents + counts.timesheetTemplates + counts.trips + counts.tripTemplates;
|
|
123019
|
+
// src/tools/ticket-tags.ts
|
|
123020
|
+
function normalizeTagName(name21) {
|
|
123021
|
+
return name21.toLowerCase().trim();
|
|
122834
123022
|
}
|
|
122835
|
-
function
|
|
122836
|
-
|
|
123023
|
+
function formatTagList(tags2) {
|
|
123024
|
+
if (tags2.length === 0) return "";
|
|
123025
|
+
return tags2.map((t9) => t9.name).join(", ");
|
|
122837
123026
|
}
|
|
122838
|
-
function
|
|
122839
|
-
const
|
|
122840
|
-
|
|
123027
|
+
async function getTagsForTickets(ticketIds) {
|
|
123028
|
+
const result = /* @__PURE__ */ new Map();
|
|
123029
|
+
if (ticketIds.length === 0) return result;
|
|
123030
|
+
const rows = await db.select({
|
|
123031
|
+
ticketId: schema_exports.ticketTags.ticketId,
|
|
123032
|
+
id: schema_exports.tags.id,
|
|
123033
|
+
name: schema_exports.tags.name
|
|
123034
|
+
}).from(schema_exports.ticketTags).innerJoin(schema_exports.tags, eq(schema_exports.tags.id, schema_exports.ticketTags.tagId)).where(inArray(schema_exports.ticketTags.ticketId, ticketIds)).orderBy(schema_exports.tags.name);
|
|
123035
|
+
for (const row of rows) {
|
|
123036
|
+
const existing = result.get(row.ticketId) ?? [];
|
|
123037
|
+
existing.push({ id: row.id, name: row.name });
|
|
123038
|
+
result.set(row.ticketId, existing);
|
|
123039
|
+
}
|
|
123040
|
+
return result;
|
|
122841
123041
|
}
|
|
122842
|
-
|
|
122843
|
-
|
|
122844
|
-
|
|
122845
|
-
|
|
122846
|
-
|
|
122847
|
-
|
|
122848
|
-
if (
|
|
122849
|
-
|
|
122850
|
-
|
|
122851
|
-
|
|
122852
|
-
|
|
122853
|
-
|
|
122854
|
-
}
|
|
122855
|
-
]
|
|
122856
|
-
};
|
|
123042
|
+
async function resolveTagFilterIds(teamId, input) {
|
|
123043
|
+
const ids = new Set(input.tagIds ?? []);
|
|
123044
|
+
const names = [
|
|
123045
|
+
...input.tag ? [input.tag] : [],
|
|
123046
|
+
...input.tags ?? []
|
|
123047
|
+
].map(normalizeTagName).filter(Boolean);
|
|
123048
|
+
if (names.length > 0) {
|
|
123049
|
+
const nameConditions = names.map(
|
|
123050
|
+
(name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
|
|
123051
|
+
);
|
|
123052
|
+
const rows = await db.select({ id: schema_exports.tags.id }).from(schema_exports.tags).where(and(eq(schema_exports.tags.teamId, teamId), or(...nameConditions)));
|
|
123053
|
+
for (const row of rows) ids.add(row.id);
|
|
122857
123054
|
}
|
|
122858
|
-
|
|
122859
|
-
|
|
122860
|
-
|
|
122861
|
-
|
|
122862
|
-
|
|
122863
|
-
|
|
122864
|
-
|
|
122865
|
-
|
|
122866
|
-
|
|
122867
|
-
|
|
122868
|
-
|
|
122869
|
-
|
|
123055
|
+
return [...ids];
|
|
123056
|
+
}
|
|
123057
|
+
async function resolveTags(teamId, input) {
|
|
123058
|
+
const tags2 = [];
|
|
123059
|
+
const errors = [];
|
|
123060
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
123061
|
+
if (input.tagIds?.length) {
|
|
123062
|
+
const rows = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
|
|
123063
|
+
and(
|
|
123064
|
+
eq(schema_exports.tags.teamId, teamId),
|
|
123065
|
+
inArray(schema_exports.tags.id, input.tagIds)
|
|
123066
|
+
)
|
|
123067
|
+
);
|
|
123068
|
+
for (const row of rows) {
|
|
123069
|
+
if (seenIds.has(row.id)) continue;
|
|
123070
|
+
seenIds.add(row.id);
|
|
123071
|
+
tags2.push(row);
|
|
123072
|
+
}
|
|
123073
|
+
for (const id of input.tagIds) {
|
|
123074
|
+
if (!seenIds.has(id)) errors.push(`Unknown tag ID: ${id}`);
|
|
123075
|
+
}
|
|
122870
123076
|
}
|
|
122871
|
-
const
|
|
122872
|
-
if (
|
|
122873
|
-
|
|
122874
|
-
|
|
122875
|
-
|
|
122876
|
-
|
|
122877
|
-
|
|
123077
|
+
const rawNames = input.tagNames ?? [];
|
|
123078
|
+
if (rawNames.length === 0) return { tags: tags2, errors };
|
|
123079
|
+
const normalizedNames = [
|
|
123080
|
+
...new Set(rawNames.map(normalizeTagName).filter(Boolean))
|
|
123081
|
+
];
|
|
123082
|
+
if (normalizedNames.length === 0) return { tags: tags2, errors };
|
|
123083
|
+
const nameConditions = normalizedNames.map(
|
|
123084
|
+
(name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
|
|
123085
|
+
);
|
|
123086
|
+
const existing = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(and(eq(schema_exports.tags.teamId, teamId), or(...nameConditions)));
|
|
123087
|
+
const existingByNorm = /* @__PURE__ */ new Map();
|
|
123088
|
+
for (const tag2 of existing) {
|
|
123089
|
+
existingByNorm.set(normalizeTagName(tag2.name), tag2);
|
|
122878
123090
|
}
|
|
122879
|
-
const
|
|
122880
|
-
|
|
123091
|
+
for (const rawName of rawNames) {
|
|
123092
|
+
const norm = normalizeTagName(rawName);
|
|
123093
|
+
if (!norm) continue;
|
|
123094
|
+
const found = existingByNorm.get(norm);
|
|
123095
|
+
if (found) {
|
|
123096
|
+
if (!seenIds.has(found.id)) {
|
|
123097
|
+
seenIds.add(found.id);
|
|
123098
|
+
tags2.push(found);
|
|
123099
|
+
}
|
|
123100
|
+
continue;
|
|
123101
|
+
}
|
|
123102
|
+
if (!input.createMissing) {
|
|
123103
|
+
errors.push(`Tag not found: ${rawName}`);
|
|
123104
|
+
continue;
|
|
123105
|
+
}
|
|
123106
|
+
try {
|
|
123107
|
+
const [created] = await db.insert(schema_exports.tags).values({
|
|
123108
|
+
teamId,
|
|
123109
|
+
name: norm,
|
|
123110
|
+
projectId: input.projectId ?? null
|
|
123111
|
+
}).returning({ id: schema_exports.tags.id, name: schema_exports.tags.name });
|
|
123112
|
+
if (created) {
|
|
123113
|
+
seenIds.add(created.id);
|
|
123114
|
+
tags2.push(created);
|
|
123115
|
+
existingByNorm.set(norm, created);
|
|
123116
|
+
}
|
|
123117
|
+
} catch {
|
|
123118
|
+
const [retry2] = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
|
|
123119
|
+
and(
|
|
123120
|
+
eq(schema_exports.tags.teamId, teamId),
|
|
123121
|
+
sql`lower(${schema_exports.tags.name}) = ${norm}`,
|
|
123122
|
+
input.projectId ? or(
|
|
123123
|
+
eq(schema_exports.tags.projectId, input.projectId),
|
|
123124
|
+
isNull(schema_exports.tags.projectId)
|
|
123125
|
+
) : isNull(schema_exports.tags.projectId)
|
|
123126
|
+
)
|
|
123127
|
+
).limit(1);
|
|
123128
|
+
if (retry2 && !seenIds.has(retry2.id)) {
|
|
123129
|
+
seenIds.add(retry2.id);
|
|
123130
|
+
tags2.push(retry2);
|
|
123131
|
+
existingByNorm.set(norm, retry2);
|
|
123132
|
+
} else {
|
|
123133
|
+
errors.push(`Failed to create tag: ${rawName}`);
|
|
123134
|
+
}
|
|
123135
|
+
}
|
|
123136
|
+
}
|
|
123137
|
+
return { tags: tags2, errors };
|
|
123138
|
+
}
|
|
123139
|
+
async function syncTicketTags(ticketId, teamId, tagIds, mode = "replace") {
|
|
123140
|
+
const current = await db.select({ tagId: schema_exports.ticketTags.tagId }).from(schema_exports.ticketTags).where(eq(schema_exports.ticketTags.ticketId, ticketId));
|
|
123141
|
+
const currentIds = new Set(current.map((row) => row.tagId));
|
|
123142
|
+
let targetIds;
|
|
123143
|
+
if (mode === "remove") {
|
|
123144
|
+
targetIds = new Set(
|
|
123145
|
+
[...currentIds].filter((id) => !tagIds.includes(id))
|
|
123146
|
+
);
|
|
123147
|
+
} else if (mode === "merge") {
|
|
123148
|
+
targetIds = /* @__PURE__ */ new Set([...currentIds, ...tagIds]);
|
|
123149
|
+
} else {
|
|
123150
|
+
targetIds = new Set(tagIds);
|
|
123151
|
+
}
|
|
123152
|
+
const toInsert = [...targetIds].filter((id) => !currentIds.has(id));
|
|
123153
|
+
const toDelete = [...currentIds].filter((id) => !targetIds.has(id));
|
|
123154
|
+
if (toDelete.length > 0) {
|
|
123155
|
+
await db.delete(schema_exports.ticketTags).where(
|
|
123156
|
+
and(
|
|
123157
|
+
eq(schema_exports.ticketTags.ticketId, ticketId),
|
|
123158
|
+
inArray(schema_exports.ticketTags.tagId, toDelete)
|
|
123159
|
+
)
|
|
123160
|
+
);
|
|
123161
|
+
}
|
|
123162
|
+
if (toInsert.length > 0) {
|
|
123163
|
+
await db.insert(schema_exports.ticketTags).values(
|
|
123164
|
+
toInsert.map((tagId) => ({
|
|
123165
|
+
ticketId,
|
|
123166
|
+
tagId,
|
|
123167
|
+
teamId
|
|
123168
|
+
}))
|
|
123169
|
+
);
|
|
123170
|
+
}
|
|
123171
|
+
const tagIdsToDescribe = [.../* @__PURE__ */ new Set([...toInsert, ...toDelete])];
|
|
123172
|
+
const tagNamesById = /* @__PURE__ */ new Map();
|
|
123173
|
+
if (tagIdsToDescribe.length > 0) {
|
|
123174
|
+
const rows = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(inArray(schema_exports.tags.id, tagIdsToDescribe));
|
|
123175
|
+
for (const row of rows) tagNamesById.set(row.id, row.name);
|
|
123176
|
+
}
|
|
123177
|
+
return {
|
|
123178
|
+
added: toInsert.map((id) => tagNamesById.get(id) ?? id),
|
|
123179
|
+
removed: toDelete.map((id) => tagNamesById.get(id) ?? id)
|
|
123180
|
+
};
|
|
123181
|
+
}
|
|
123182
|
+
|
|
123183
|
+
// src/tools/project-tags.ts
|
|
123184
|
+
async function getTagsForProjects(projectIds) {
|
|
123185
|
+
const result = /* @__PURE__ */ new Map();
|
|
123186
|
+
if (projectIds.length === 0) return result;
|
|
123187
|
+
const rows = await db.select({
|
|
123188
|
+
projectId: schema_exports.projectTags.projectId,
|
|
123189
|
+
id: schema_exports.tags.id,
|
|
123190
|
+
name: schema_exports.tags.name
|
|
123191
|
+
}).from(schema_exports.projectTags).innerJoin(schema_exports.tags, eq(schema_exports.tags.id, schema_exports.projectTags.tagId)).where(inArray(schema_exports.projectTags.projectId, projectIds)).orderBy(schema_exports.tags.name);
|
|
123192
|
+
for (const row of rows) {
|
|
123193
|
+
const existing = result.get(row.projectId) ?? [];
|
|
123194
|
+
existing.push({ id: row.id, name: row.name });
|
|
123195
|
+
result.set(row.projectId, existing);
|
|
123196
|
+
}
|
|
123197
|
+
return result;
|
|
123198
|
+
}
|
|
123199
|
+
async function resolveProjectTagFilterIds(teamId, input) {
|
|
123200
|
+
const ids = new Set(input.projectTagIds ?? []);
|
|
123201
|
+
const names = [
|
|
123202
|
+
...input.projectTag ? [input.projectTag] : [],
|
|
123203
|
+
...input.projectTags ?? []
|
|
123204
|
+
].map(normalizeTagName).filter(Boolean);
|
|
123205
|
+
if (names.length > 0) {
|
|
123206
|
+
const nameConditions = names.map(
|
|
123207
|
+
(name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
|
|
123208
|
+
);
|
|
123209
|
+
const rows = await db.select({ id: schema_exports.tags.id }).from(schema_exports.tags).where(
|
|
123210
|
+
and(
|
|
123211
|
+
eq(schema_exports.tags.teamId, teamId),
|
|
123212
|
+
isNull(schema_exports.tags.projectId),
|
|
123213
|
+
or(...nameConditions)
|
|
123214
|
+
)
|
|
123215
|
+
);
|
|
123216
|
+
for (const row of rows) ids.add(row.id);
|
|
123217
|
+
}
|
|
123218
|
+
if (input.projectTagIds?.length) {
|
|
123219
|
+
const rows = await db.select({ id: schema_exports.tags.id }).from(schema_exports.tags).where(
|
|
123220
|
+
and(
|
|
123221
|
+
eq(schema_exports.tags.teamId, teamId),
|
|
123222
|
+
isNull(schema_exports.tags.projectId),
|
|
123223
|
+
inArray(schema_exports.tags.id, input.projectTagIds)
|
|
123224
|
+
)
|
|
123225
|
+
);
|
|
123226
|
+
for (const row of rows) ids.add(row.id);
|
|
123227
|
+
}
|
|
123228
|
+
return [...ids];
|
|
123229
|
+
}
|
|
123230
|
+
async function resolveProjectTags(teamId, input) {
|
|
123231
|
+
const tags2 = [];
|
|
123232
|
+
const errors = [];
|
|
123233
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
123234
|
+
if (input.tagIds?.length) {
|
|
123235
|
+
const rows = await db.select({
|
|
123236
|
+
id: schema_exports.tags.id,
|
|
123237
|
+
name: schema_exports.tags.name,
|
|
123238
|
+
projectId: schema_exports.tags.projectId
|
|
123239
|
+
}).from(schema_exports.tags).where(
|
|
123240
|
+
and(
|
|
123241
|
+
eq(schema_exports.tags.teamId, teamId),
|
|
123242
|
+
inArray(schema_exports.tags.id, input.tagIds)
|
|
123243
|
+
)
|
|
123244
|
+
);
|
|
123245
|
+
for (const row of rows) {
|
|
123246
|
+
if (row.projectId) {
|
|
123247
|
+
errors.push(
|
|
123248
|
+
`Tag "${row.name}" is project-scoped (ticket tag) and cannot be used as a project label`
|
|
123249
|
+
);
|
|
123250
|
+
continue;
|
|
123251
|
+
}
|
|
123252
|
+
if (seenIds.has(row.id)) continue;
|
|
123253
|
+
seenIds.add(row.id);
|
|
123254
|
+
tags2.push({ id: row.id, name: row.name });
|
|
123255
|
+
}
|
|
123256
|
+
for (const id of input.tagIds) {
|
|
123257
|
+
if (!seenIds.has(id) && !errors.some((e6) => e6.includes(id))) {
|
|
123258
|
+
errors.push(`Unknown tag ID: ${id}`);
|
|
123259
|
+
}
|
|
123260
|
+
}
|
|
123261
|
+
}
|
|
123262
|
+
const rawNames = input.tagNames ?? [];
|
|
123263
|
+
if (rawNames.length === 0) return { tags: tags2, errors };
|
|
123264
|
+
const normalizedNames = [
|
|
123265
|
+
...new Set(rawNames.map(normalizeTagName).filter(Boolean))
|
|
123266
|
+
];
|
|
123267
|
+
if (normalizedNames.length === 0) return { tags: tags2, errors };
|
|
123268
|
+
const nameConditions = normalizedNames.map(
|
|
123269
|
+
(name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
|
|
123270
|
+
);
|
|
123271
|
+
const existing = await db.select({
|
|
123272
|
+
id: schema_exports.tags.id,
|
|
123273
|
+
name: schema_exports.tags.name,
|
|
123274
|
+
projectId: schema_exports.tags.projectId
|
|
123275
|
+
}).from(schema_exports.tags).where(and(eq(schema_exports.tags.teamId, teamId), or(...nameConditions)));
|
|
123276
|
+
const existingByNorm = /* @__PURE__ */ new Map();
|
|
123277
|
+
for (const tag2 of existing) {
|
|
123278
|
+
if (tag2.projectId) {
|
|
123279
|
+
existingByNorm.set(normalizeTagName(tag2.name), {
|
|
123280
|
+
id: tag2.id,
|
|
123281
|
+
name: tag2.name,
|
|
123282
|
+
_scoped: true
|
|
123283
|
+
});
|
|
123284
|
+
} else {
|
|
123285
|
+
existingByNorm.set(normalizeTagName(tag2.name), {
|
|
123286
|
+
id: tag2.id,
|
|
123287
|
+
name: tag2.name
|
|
123288
|
+
});
|
|
123289
|
+
}
|
|
123290
|
+
}
|
|
123291
|
+
for (const rawName of rawNames) {
|
|
123292
|
+
const norm = normalizeTagName(rawName);
|
|
123293
|
+
if (!norm) continue;
|
|
123294
|
+
const found = existingByNorm.get(norm);
|
|
123295
|
+
if (found) {
|
|
123296
|
+
if (found._scoped) {
|
|
123297
|
+
errors.push(
|
|
123298
|
+
`Tag "${rawName}" is project-scoped (ticket tag) and cannot be used as a project label`
|
|
123299
|
+
);
|
|
123300
|
+
continue;
|
|
123301
|
+
}
|
|
123302
|
+
if (!seenIds.has(found.id)) {
|
|
123303
|
+
seenIds.add(found.id);
|
|
123304
|
+
tags2.push({ id: found.id, name: found.name });
|
|
123305
|
+
}
|
|
123306
|
+
continue;
|
|
123307
|
+
}
|
|
123308
|
+
if (!input.createMissing) {
|
|
123309
|
+
errors.push(`Project label not found: ${rawName}`);
|
|
123310
|
+
continue;
|
|
123311
|
+
}
|
|
123312
|
+
try {
|
|
123313
|
+
const [created] = await db.insert(schema_exports.tags).values({
|
|
123314
|
+
teamId,
|
|
123315
|
+
name: norm,
|
|
123316
|
+
projectId: null
|
|
123317
|
+
}).returning({ id: schema_exports.tags.id, name: schema_exports.tags.name });
|
|
123318
|
+
if (created) {
|
|
123319
|
+
seenIds.add(created.id);
|
|
123320
|
+
tags2.push(created);
|
|
123321
|
+
existingByNorm.set(norm, created);
|
|
123322
|
+
}
|
|
123323
|
+
} catch {
|
|
123324
|
+
const [retry2] = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
|
|
123325
|
+
and(
|
|
123326
|
+
eq(schema_exports.tags.teamId, teamId),
|
|
123327
|
+
sql`lower(${schema_exports.tags.name}) = ${norm}`,
|
|
123328
|
+
isNull(schema_exports.tags.projectId)
|
|
123329
|
+
)
|
|
123330
|
+
).limit(1);
|
|
123331
|
+
if (retry2 && !seenIds.has(retry2.id)) {
|
|
123332
|
+
seenIds.add(retry2.id);
|
|
123333
|
+
tags2.push(retry2);
|
|
123334
|
+
existingByNorm.set(norm, retry2);
|
|
123335
|
+
} else {
|
|
123336
|
+
errors.push(`Failed to create project label: ${rawName}`);
|
|
123337
|
+
}
|
|
123338
|
+
}
|
|
123339
|
+
}
|
|
123340
|
+
return { tags: tags2, errors };
|
|
123341
|
+
}
|
|
123342
|
+
async function syncProjectTags(projectId, teamId, tagIds, mode = "replace") {
|
|
123343
|
+
const current = await db.select({ tagId: schema_exports.projectTags.tagId }).from(schema_exports.projectTags).where(eq(schema_exports.projectTags.projectId, projectId));
|
|
123344
|
+
const currentIds = new Set(current.map((row) => row.tagId));
|
|
123345
|
+
let targetIds;
|
|
123346
|
+
if (mode === "remove") {
|
|
123347
|
+
targetIds = new Set(
|
|
123348
|
+
[...currentIds].filter((id) => !tagIds.includes(id))
|
|
123349
|
+
);
|
|
123350
|
+
} else if (mode === "merge") {
|
|
123351
|
+
targetIds = /* @__PURE__ */ new Set([...currentIds, ...tagIds]);
|
|
123352
|
+
} else {
|
|
123353
|
+
targetIds = new Set(tagIds);
|
|
123354
|
+
}
|
|
123355
|
+
const toInsert = [...targetIds].filter((id) => !currentIds.has(id));
|
|
123356
|
+
const toDelete = [...currentIds].filter((id) => !targetIds.has(id));
|
|
123357
|
+
if (toDelete.length > 0) {
|
|
123358
|
+
await db.delete(schema_exports.projectTags).where(
|
|
123359
|
+
and(
|
|
123360
|
+
eq(schema_exports.projectTags.projectId, projectId),
|
|
123361
|
+
inArray(schema_exports.projectTags.tagId, toDelete)
|
|
123362
|
+
)
|
|
123363
|
+
);
|
|
123364
|
+
}
|
|
123365
|
+
if (toInsert.length > 0) {
|
|
123366
|
+
await db.insert(schema_exports.projectTags).values(
|
|
123367
|
+
toInsert.map((tagId) => ({
|
|
123368
|
+
projectId,
|
|
123369
|
+
tagId,
|
|
123370
|
+
teamId
|
|
123371
|
+
}))
|
|
123372
|
+
);
|
|
123373
|
+
}
|
|
123374
|
+
const tagIdsToDescribe = [.../* @__PURE__ */ new Set([...toInsert, ...toDelete])];
|
|
123375
|
+
const tagNamesById = /* @__PURE__ */ new Map();
|
|
123376
|
+
if (tagIdsToDescribe.length > 0) {
|
|
123377
|
+
const rows = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(inArray(schema_exports.tags.id, tagIdsToDescribe));
|
|
123378
|
+
for (const row of rows) tagNamesById.set(row.id, row.name);
|
|
123379
|
+
}
|
|
123380
|
+
return {
|
|
123381
|
+
added: toInsert.map((id) => tagNamesById.get(id) ?? id),
|
|
123382
|
+
removed: toDelete.map((id) => tagNamesById.get(id) ?? id)
|
|
123383
|
+
};
|
|
123384
|
+
}
|
|
123385
|
+
function projectTagFilterSql(tagIds, ticketsAlias = schema_exports.tickets) {
|
|
123386
|
+
return sql`EXISTS (
|
|
123387
|
+
SELECT 1 FROM ${schema_exports.projectTags} pt
|
|
123388
|
+
WHERE pt.project_id = ${ticketsAlias.projectId}
|
|
123389
|
+
AND pt.tag_id IN (${sql.join(
|
|
123390
|
+
tagIds.map((id) => sql`${id}`),
|
|
123391
|
+
sql`, `
|
|
123392
|
+
)})
|
|
123393
|
+
)`;
|
|
123394
|
+
}
|
|
123395
|
+
|
|
123396
|
+
// src/tools/project-cleanup-util.ts
|
|
123397
|
+
var PROJECT_STATUS_FILTERS = [
|
|
123398
|
+
"active",
|
|
123399
|
+
"archived",
|
|
123400
|
+
"all"
|
|
123401
|
+
];
|
|
123402
|
+
var DEPENDENCY_LABELS2 = {
|
|
123403
|
+
tickets: "ticket(s)",
|
|
123404
|
+
timesheetEvents: "agenda/time entr(ies)",
|
|
123405
|
+
timesheetTemplates: "timesheet template(s)",
|
|
123406
|
+
trips: "trip(s)",
|
|
123407
|
+
tripTemplates: "trip template(s)"
|
|
123408
|
+
};
|
|
123409
|
+
function getProjectArchiveState(settings) {
|
|
123410
|
+
const obj = settings && typeof settings === "object" && !Array.isArray(settings) ? settings : {};
|
|
123411
|
+
const archivedAt = typeof obj.archivedAt === "string" && obj.archivedAt.trim().length > 0 ? obj.archivedAt : null;
|
|
123412
|
+
const archiveReason = typeof obj.archiveReason === "string" && obj.archiveReason.trim().length > 0 ? obj.archiveReason : null;
|
|
123413
|
+
return { archived: archivedAt !== null, archivedAt, archiveReason };
|
|
123414
|
+
}
|
|
123415
|
+
function withArchiveSettings(settings, archivedAt, reason) {
|
|
123416
|
+
const base = settings && typeof settings === "object" && !Array.isArray(settings) ? { ...settings } : {};
|
|
123417
|
+
base.archivedAt = archivedAt;
|
|
123418
|
+
if (reason && reason.trim().length > 0) {
|
|
123419
|
+
base.archiveReason = reason.trim();
|
|
123420
|
+
}
|
|
123421
|
+
return base;
|
|
123422
|
+
}
|
|
123423
|
+
function totalProjectDependencies(counts) {
|
|
123424
|
+
return counts.tickets + counts.timesheetEvents + counts.timesheetTemplates + counts.trips + counts.tripTemplates;
|
|
123425
|
+
}
|
|
123426
|
+
function isProjectEmpty(counts) {
|
|
123427
|
+
return totalProjectDependencies(counts) === 0;
|
|
123428
|
+
}
|
|
123429
|
+
function formatProjectDependencies(counts) {
|
|
123430
|
+
const parts = Object.keys(DEPENDENCY_LABELS2).filter((key) => counts[key] > 0).map((key) => `${counts[key]} ${DEPENDENCY_LABELS2[key]}`);
|
|
123431
|
+
return parts.length > 0 ? parts.join(", ") : "no dependencies";
|
|
123432
|
+
}
|
|
123433
|
+
|
|
123434
|
+
// src/tools/projects.ts
|
|
123435
|
+
async function handleGetProjects(input) {
|
|
123436
|
+
const ctx = getAuthContext();
|
|
123437
|
+
const { customerId, q: q3, pageSize = 20, projectTag, projectTags: projectTags2, projectTagIds } = input;
|
|
123438
|
+
const status = input.status ?? "active";
|
|
123439
|
+
if (!PROJECT_STATUS_FILTERS.includes(status)) {
|
|
123440
|
+
return {
|
|
123441
|
+
content: [
|
|
123442
|
+
{
|
|
123443
|
+
type: "text",
|
|
123444
|
+
text: `Error: invalid status "${status}". Allowed: ${PROJECT_STATUS_FILTERS.join(", ")}.`
|
|
123445
|
+
}
|
|
123446
|
+
]
|
|
123447
|
+
};
|
|
123448
|
+
}
|
|
123449
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
123450
|
+
if (!resolved.ok) return resolved.response;
|
|
123451
|
+
const projectIds = await getAccessibleProjectIds(ctx.userId, resolved.teamId);
|
|
123452
|
+
if (projectIds.length === 0) {
|
|
123453
|
+
return {
|
|
123454
|
+
content: [
|
|
123455
|
+
{
|
|
123456
|
+
type: "text",
|
|
123457
|
+
text: "No projects found or no access to any projects."
|
|
123458
|
+
}
|
|
123459
|
+
]
|
|
123460
|
+
};
|
|
123461
|
+
}
|
|
123462
|
+
const filters = [inArray(schema_exports.projects.id, projectIds)];
|
|
123463
|
+
if (customerId) filters.push(eq(schema_exports.projects.customerId, customerId));
|
|
123464
|
+
if (q3) filters.push(ilike(schema_exports.projects.name, `%${q3}%`));
|
|
123465
|
+
if (status === "active") {
|
|
123466
|
+
filters.push(sql`${schema_exports.projects.settings} ->> 'archivedAt' IS NULL`);
|
|
123467
|
+
} else if (status === "archived") {
|
|
123468
|
+
filters.push(sql`${schema_exports.projects.settings} ->> 'archivedAt' IS NOT NULL`);
|
|
123469
|
+
}
|
|
123470
|
+
const labelFilterIds = await resolveProjectTagFilterIds(resolved.teamId, {
|
|
123471
|
+
projectTag,
|
|
123472
|
+
projectTags: projectTags2,
|
|
123473
|
+
projectTagIds
|
|
123474
|
+
});
|
|
123475
|
+
if (projectTag || projectTags2?.length || projectTagIds?.length) {
|
|
123476
|
+
if (labelFilterIds.length === 0) {
|
|
123477
|
+
return {
|
|
123478
|
+
content: [
|
|
123479
|
+
{
|
|
123480
|
+
type: "text",
|
|
123481
|
+
text: "No projects found (no matching project labels for the given filter)."
|
|
123482
|
+
}
|
|
123483
|
+
]
|
|
123484
|
+
};
|
|
123485
|
+
}
|
|
123486
|
+
filters.push(
|
|
123487
|
+
sql`EXISTS (
|
|
123488
|
+
SELECT 1 FROM ${schema_exports.projectTags}
|
|
123489
|
+
WHERE ${schema_exports.projectTags.projectId} = ${schema_exports.projects.id}
|
|
123490
|
+
AND ${schema_exports.projectTags.tagId} IN (${sql.join(
|
|
123491
|
+
labelFilterIds.map((id) => sql`${id}`),
|
|
123492
|
+
sql`, `
|
|
123493
|
+
)})
|
|
123494
|
+
)`
|
|
123495
|
+
);
|
|
123496
|
+
}
|
|
123497
|
+
const rows = await db.select({
|
|
123498
|
+
id: schema_exports.projects.id,
|
|
122881
123499
|
name: schema_exports.projects.name,
|
|
122882
123500
|
description: schema_exports.projects.description,
|
|
122883
123501
|
customerId: schema_exports.projects.customerId,
|
|
122884
123502
|
createdAt: schema_exports.projects.createdAt,
|
|
122885
123503
|
settings: schema_exports.projects.settings
|
|
122886
123504
|
}).from(schema_exports.projects).where(and(...filters)).orderBy(asc(schema_exports.projects.name)).limit(Math.min(pageSize, 100));
|
|
123505
|
+
const tagsByProject = await getTagsForProjects(rows.map((p3) => p3.id));
|
|
122887
123506
|
return {
|
|
122888
123507
|
content: [
|
|
122889
123508
|
{
|
|
@@ -122892,9 +123511,12 @@ async function handleGetProjects(input) {
|
|
|
122892
123511
|
|
|
122893
123512
|
${rows.map((p3) => {
|
|
122894
123513
|
const archive = getProjectArchiveState(p3.settings);
|
|
123514
|
+
const labels = tagsByProject.get(p3.id) ?? [];
|
|
123515
|
+
const labelLine = labels.length > 0 ? `Labels: ${formatTagList(labels)} (ids: ${labels.map((t9) => t9.id).join(", ")})
|
|
123516
|
+
` : "";
|
|
122895
123517
|
return `**${p3.name}** (ID: ${p3.id})${archive.archived ? " \u2014 ARCHIVED" : ""}
|
|
122896
123518
|
${p3.description ? `Description: ${p3.description}
|
|
122897
|
-
` : ""}Created: ${new Date(p3.createdAt).toLocaleDateString()}
|
|
123519
|
+
` : ""}` + labelLine + `Created: ${new Date(p3.createdAt).toLocaleDateString()}
|
|
122898
123520
|
${archive.archived ? `Archived: ${archive.archivedAt}${archive.archiveReason ? ` (${archive.archiveReason})` : ""}
|
|
122899
123521
|
` : ""}`;
|
|
122900
123522
|
}).join("\n") || "No projects found."}`
|
|
@@ -122903,15 +123525,41 @@ ${archive.archived ? `Archived: ${archive.archivedAt}${archive.archiveReason ? `
|
|
|
122903
123525
|
};
|
|
122904
123526
|
}
|
|
122905
123527
|
async function handleCreateProject(input) {
|
|
122906
|
-
const { name: name21, description, customerId } = input;
|
|
123528
|
+
const { name: name21, description, customerId, projectTags: projectTags2, projectTagIds } = input;
|
|
122907
123529
|
const resolved = await resolveTeamId(input.teamId);
|
|
122908
123530
|
if (!resolved.ok) return resolved.response;
|
|
122909
|
-
await db.insert(schema_exports.projects).values({
|
|
123531
|
+
const [created] = await db.insert(schema_exports.projects).values({
|
|
122910
123532
|
teamId: resolved.teamId,
|
|
122911
123533
|
name: name21,
|
|
122912
123534
|
description: description ?? null,
|
|
122913
123535
|
customerId: customerId ?? null
|
|
122914
|
-
});
|
|
123536
|
+
}).returning({ id: schema_exports.projects.id });
|
|
123537
|
+
if (!created) {
|
|
123538
|
+
return textResponse7("Failed to create project.");
|
|
123539
|
+
}
|
|
123540
|
+
let labelLine = "";
|
|
123541
|
+
if (projectTags2?.length || projectTagIds?.length) {
|
|
123542
|
+
const { tags: tags2, errors } = await resolveProjectTags(resolved.teamId, {
|
|
123543
|
+
tagNames: projectTags2,
|
|
123544
|
+
tagIds: projectTagIds,
|
|
123545
|
+
createMissing: true
|
|
123546
|
+
});
|
|
123547
|
+
if (errors.length > 0) {
|
|
123548
|
+
return textResponse7(
|
|
123549
|
+
`Project created (ID: ${created.id}) but label errors:
|
|
123550
|
+
${errors.join("\n")}`
|
|
123551
|
+
);
|
|
123552
|
+
}
|
|
123553
|
+
if (tags2.length > 0) {
|
|
123554
|
+
await syncProjectTags(
|
|
123555
|
+
created.id,
|
|
123556
|
+
resolved.teamId,
|
|
123557
|
+
tags2.map((t9) => t9.id)
|
|
123558
|
+
);
|
|
123559
|
+
labelLine = `
|
|
123560
|
+
Labels: ${formatTagList(tags2)}`;
|
|
123561
|
+
}
|
|
123562
|
+
}
|
|
122915
123563
|
return {
|
|
122916
123564
|
content: [
|
|
122917
123565
|
{
|
|
@@ -122919,8 +123567,9 @@ async function handleCreateProject(input) {
|
|
|
122919
123567
|
text: `\u2705 **Project Created Successfully!**
|
|
122920
123568
|
|
|
122921
123569
|
Name: ${name21}
|
|
123570
|
+
ID: ${created.id}
|
|
122922
123571
|
${description ? `Description: ${description}
|
|
122923
|
-
` : ""}`
|
|
123572
|
+
` : ""}` + labelLine
|
|
122924
123573
|
}
|
|
122925
123574
|
]
|
|
122926
123575
|
};
|
|
@@ -123192,6 +123841,27 @@ async function handleUpdateProject(input) {
|
|
|
123192
123841
|
)
|
|
123193
123842
|
);
|
|
123194
123843
|
}
|
|
123844
|
+
let labelSyncNote = "";
|
|
123845
|
+
if (input.projectTags !== void 0 || input.projectTagIds !== void 0) {
|
|
123846
|
+
const { tags: tags2, errors } = await resolveProjectTags(owningTeamId, {
|
|
123847
|
+
tagNames: input.projectTags,
|
|
123848
|
+
tagIds: input.projectTagIds,
|
|
123849
|
+
createMissing: true
|
|
123850
|
+
});
|
|
123851
|
+
if (errors.length > 0) {
|
|
123852
|
+
return textResponse7(`Project label errors:
|
|
123853
|
+
${errors.join("\n")}`);
|
|
123854
|
+
}
|
|
123855
|
+
const syncResult = await syncProjectTags(
|
|
123856
|
+
id,
|
|
123857
|
+
owningTeamId,
|
|
123858
|
+
tags2.map((t9) => t9.id)
|
|
123859
|
+
);
|
|
123860
|
+
if (syncResult.added.length || syncResult.removed.length) {
|
|
123861
|
+
labelSyncNote = `
|
|
123862
|
+
Labels updated: +${syncResult.added.join(", ") || "none"} / -${syncResult.removed.join(", ") || "none"}`;
|
|
123863
|
+
}
|
|
123864
|
+
}
|
|
123195
123865
|
const [updated] = await db.select({
|
|
123196
123866
|
id: schema_exports.projects.id,
|
|
123197
123867
|
name: schema_exports.projects.name,
|
|
@@ -123217,6 +123887,13 @@ async function handleUpdateProject(input) {
|
|
|
123217
123887
|
);
|
|
123218
123888
|
}
|
|
123219
123889
|
lines.push(`Internal: ${updated.internal ? "yes" : "no"}`);
|
|
123890
|
+
if (labelSyncNote) lines.push(labelSyncNote.trim());
|
|
123891
|
+
const projectLabels = (await getTagsForProjects([id])).get(id) ?? [];
|
|
123892
|
+
if (projectLabels.length > 0) {
|
|
123893
|
+
lines.push(
|
|
123894
|
+
`Labels: ${formatTagList(projectLabels)} (ids: ${projectLabels.map((t9) => t9.id).join(", ")})`
|
|
123895
|
+
);
|
|
123896
|
+
}
|
|
123220
123897
|
if (willRename) {
|
|
123221
123898
|
lines.push("", "Note: tickets for this project were renumbered.");
|
|
123222
123899
|
}
|
|
@@ -123479,25 +124156,155 @@ async function handleDeleteProject(input) {
|
|
|
123479
124156
|
|
|
123480
124157
|
Dependencies: ${summary}.
|
|
123481
124158
|
|
|
123482
|
-
A hard delete would orphan these records, so it is not allowed. Use archive-project instead to safely retire this project (reversible, keeps all data).`
|
|
123483
|
-
);
|
|
123484
|
-
}
|
|
123485
|
-
if (confirmEmptyOnly !== true) {
|
|
123486
|
-
return textResponse7(
|
|
123487
|
-
`Project "${project.name}" (${project.id}) has no dependencies and can be safely deleted. This is a permanent hard delete. Re-run delete-project with confirmEmptyOnly: true to proceed (or use archive-project to keep the record).`
|
|
123488
|
-
);
|
|
124159
|
+
A hard delete would orphan these records, so it is not allowed. Use archive-project instead to safely retire this project (reversible, keeps all data).`
|
|
124160
|
+
);
|
|
124161
|
+
}
|
|
124162
|
+
if (confirmEmptyOnly !== true) {
|
|
124163
|
+
return textResponse7(
|
|
124164
|
+
`Project "${project.name}" (${project.id}) has no dependencies and can be safely deleted. This is a permanent hard delete. Re-run delete-project with confirmEmptyOnly: true to proceed (or use archive-project to keep the record).`
|
|
124165
|
+
);
|
|
124166
|
+
}
|
|
124167
|
+
await db.delete(schema_exports.projects).where(eq(schema_exports.projects.id, project.id));
|
|
124168
|
+
return textResponse7(
|
|
124169
|
+
`\u2705 **Project deleted**
|
|
124170
|
+
|
|
124171
|
+
Project: ${project.name}
|
|
124172
|
+
ID: ${project.id}
|
|
124173
|
+
Action: hard delete (empty project)
|
|
124174
|
+
Status: deleted
|
|
124175
|
+
Timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
124176
|
+
|
|
124177
|
+
The project had no tickets, hours, trips, or templates. Any project-scoped config (member access, tags, slack/github links, team rates) was removed with it.`
|
|
124178
|
+
);
|
|
124179
|
+
}
|
|
124180
|
+
async function resolveProjectForTags(projectId, teamId) {
|
|
124181
|
+
const project = await loadProjectInTeam(projectId, teamId);
|
|
124182
|
+
if (!project) {
|
|
124183
|
+
return {
|
|
124184
|
+
ok: false,
|
|
124185
|
+
response: textResponse7(
|
|
124186
|
+
`Project ${projectId} not found, or it is not owned by this team.`
|
|
124187
|
+
)
|
|
124188
|
+
};
|
|
124189
|
+
}
|
|
124190
|
+
return { ok: true, project };
|
|
124191
|
+
}
|
|
124192
|
+
async function handleGetProjectTags(input) {
|
|
124193
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
124194
|
+
if (!resolved.ok) return resolved.response;
|
|
124195
|
+
const projectResult = await resolveProjectForTags(
|
|
124196
|
+
input.projectId,
|
|
124197
|
+
resolved.teamId
|
|
124198
|
+
);
|
|
124199
|
+
if (!projectResult.ok) return projectResult.response;
|
|
124200
|
+
const labels = (await getTagsForProjects([input.projectId])).get(input.projectId) ?? [];
|
|
124201
|
+
if (labels.length === 0) {
|
|
124202
|
+
return textResponse7(
|
|
124203
|
+
`Project "${projectResult.project.name}" (${input.projectId}) has no project labels.`
|
|
124204
|
+
);
|
|
124205
|
+
}
|
|
124206
|
+
return textResponse7(
|
|
124207
|
+
`Project labels for **${projectResult.project.name}** (${input.projectId}):
|
|
124208
|
+
|
|
124209
|
+
` + labels.map((t9) => `- ${t9.name} (id: ${t9.id})`).join("\n")
|
|
124210
|
+
);
|
|
124211
|
+
}
|
|
124212
|
+
async function handleSetProjectTags(input) {
|
|
124213
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
124214
|
+
if (!resolved.ok) return resolved.response;
|
|
124215
|
+
const projectResult = await resolveProjectForTags(
|
|
124216
|
+
input.projectId,
|
|
124217
|
+
resolved.teamId
|
|
124218
|
+
);
|
|
124219
|
+
if (!projectResult.ok) return projectResult.response;
|
|
124220
|
+
const owningTeamId = projectResult.project.teamId;
|
|
124221
|
+
const { tags: tags2, errors } = await resolveProjectTags(owningTeamId, {
|
|
124222
|
+
tagNames: input.projectTags,
|
|
124223
|
+
tagIds: input.projectTagIds,
|
|
124224
|
+
createMissing: true
|
|
124225
|
+
});
|
|
124226
|
+
if (errors.length > 0) {
|
|
124227
|
+
return textResponse7(`Project label errors:
|
|
124228
|
+
${errors.join("\n")}`);
|
|
124229
|
+
}
|
|
124230
|
+
const syncResult = await syncProjectTags(
|
|
124231
|
+
input.projectId,
|
|
124232
|
+
owningTeamId,
|
|
124233
|
+
tags2.map((t9) => t9.id)
|
|
124234
|
+
);
|
|
124235
|
+
return textResponse7(
|
|
124236
|
+
`\u2705 **Project labels set** for "${projectResult.project.name}" (${input.projectId})
|
|
124237
|
+
|
|
124238
|
+
Labels: ${formatTagList(tags2) || "(none)"}
|
|
124239
|
+
Added: ${syncResult.added.join(", ") || "none"}
|
|
124240
|
+
Removed: ${syncResult.removed.join(", ") || "none"}`
|
|
124241
|
+
);
|
|
124242
|
+
}
|
|
124243
|
+
async function handleAddProjectTag(input) {
|
|
124244
|
+
if (!input.projectTag && !input.projectTagId) {
|
|
124245
|
+
return textResponse7("Provide projectTag (name) or projectTagId.");
|
|
123489
124246
|
}
|
|
123490
|
-
await
|
|
124247
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
124248
|
+
if (!resolved.ok) return resolved.response;
|
|
124249
|
+
const projectResult = await resolveProjectForTags(
|
|
124250
|
+
input.projectId,
|
|
124251
|
+
resolved.teamId
|
|
124252
|
+
);
|
|
124253
|
+
if (!projectResult.ok) return projectResult.response;
|
|
124254
|
+
const owningTeamId = projectResult.project.teamId;
|
|
124255
|
+
const { tags: tags2, errors } = await resolveProjectTags(owningTeamId, {
|
|
124256
|
+
tagNames: input.projectTag ? [input.projectTag] : void 0,
|
|
124257
|
+
tagIds: input.projectTagId ? [input.projectTagId] : void 0,
|
|
124258
|
+
createMissing: true
|
|
124259
|
+
});
|
|
124260
|
+
if (errors.length > 0) {
|
|
124261
|
+
return textResponse7(`Project label errors:
|
|
124262
|
+
${errors.join("\n")}`);
|
|
124263
|
+
}
|
|
124264
|
+
if (tags2.length === 0) {
|
|
124265
|
+
return textResponse7("No matching project label to add.");
|
|
124266
|
+
}
|
|
124267
|
+
const syncResult = await syncProjectTags(
|
|
124268
|
+
input.projectId,
|
|
124269
|
+
owningTeamId,
|
|
124270
|
+
tags2.map((t9) => t9.id),
|
|
124271
|
+
"merge"
|
|
124272
|
+
);
|
|
123491
124273
|
return textResponse7(
|
|
123492
|
-
`\u2705 **Project
|
|
124274
|
+
`\u2705 **Project label added** to "${projectResult.project.name}"
|
|
123493
124275
|
|
|
123494
|
-
|
|
123495
|
-
|
|
123496
|
-
|
|
123497
|
-
|
|
123498
|
-
|
|
124276
|
+
Added: ${syncResult.added.join(", ") || tags2.map((t9) => t9.name).join(", ")}`
|
|
124277
|
+
);
|
|
124278
|
+
}
|
|
124279
|
+
async function handleRemoveProjectTag(input) {
|
|
124280
|
+
if (!input.projectTag && !input.projectTagId) {
|
|
124281
|
+
return textResponse7("Provide projectTag (name) or projectTagId.");
|
|
124282
|
+
}
|
|
124283
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
124284
|
+
if (!resolved.ok) return resolved.response;
|
|
124285
|
+
const projectResult = await resolveProjectForTags(
|
|
124286
|
+
input.projectId,
|
|
124287
|
+
resolved.teamId
|
|
124288
|
+
);
|
|
124289
|
+
if (!projectResult.ok) return projectResult.response;
|
|
124290
|
+
const owningTeamId = projectResult.project.teamId;
|
|
124291
|
+
const filterIds = await resolveProjectTagFilterIds(owningTeamId, {
|
|
124292
|
+
projectTag: input.projectTag,
|
|
124293
|
+
projectTagIds: input.projectTagId ? [input.projectTagId] : void 0
|
|
124294
|
+
});
|
|
124295
|
+
if (filterIds.length === 0) {
|
|
124296
|
+
return textResponse7("No matching project label found to remove.");
|
|
124297
|
+
}
|
|
124298
|
+
const syncResult = await syncProjectTags(
|
|
124299
|
+
input.projectId,
|
|
124300
|
+
owningTeamId,
|
|
124301
|
+
filterIds,
|
|
124302
|
+
"remove"
|
|
124303
|
+
);
|
|
124304
|
+
return textResponse7(
|
|
124305
|
+
`\u2705 **Project label removed** from "${projectResult.project.name}"
|
|
123499
124306
|
|
|
123500
|
-
|
|
124307
|
+
Removed: ${syncResult.removed.join(", ") || "none"}`
|
|
123501
124308
|
);
|
|
123502
124309
|
}
|
|
123503
124310
|
|
|
@@ -125904,170 +126711,6 @@ ${rendered}`
|
|
|
125904
126711
|
};
|
|
125905
126712
|
}
|
|
125906
126713
|
|
|
125907
|
-
// src/tools/ticket-tags.ts
|
|
125908
|
-
function normalizeTagName(name21) {
|
|
125909
|
-
return name21.toLowerCase().trim();
|
|
125910
|
-
}
|
|
125911
|
-
function formatTagList(tags2) {
|
|
125912
|
-
if (tags2.length === 0) return "";
|
|
125913
|
-
return tags2.map((t9) => t9.name).join(", ");
|
|
125914
|
-
}
|
|
125915
|
-
async function getTagsForTickets(ticketIds) {
|
|
125916
|
-
const result = /* @__PURE__ */ new Map();
|
|
125917
|
-
if (ticketIds.length === 0) return result;
|
|
125918
|
-
const rows = await db.select({
|
|
125919
|
-
ticketId: schema_exports.ticketTags.ticketId,
|
|
125920
|
-
id: schema_exports.tags.id,
|
|
125921
|
-
name: schema_exports.tags.name
|
|
125922
|
-
}).from(schema_exports.ticketTags).innerJoin(schema_exports.tags, eq(schema_exports.tags.id, schema_exports.ticketTags.tagId)).where(inArray(schema_exports.ticketTags.ticketId, ticketIds)).orderBy(schema_exports.tags.name);
|
|
125923
|
-
for (const row of rows) {
|
|
125924
|
-
const existing = result.get(row.ticketId) ?? [];
|
|
125925
|
-
existing.push({ id: row.id, name: row.name });
|
|
125926
|
-
result.set(row.ticketId, existing);
|
|
125927
|
-
}
|
|
125928
|
-
return result;
|
|
125929
|
-
}
|
|
125930
|
-
async function resolveTagFilterIds(teamId, input) {
|
|
125931
|
-
const ids = new Set(input.tagIds ?? []);
|
|
125932
|
-
const names = [
|
|
125933
|
-
...input.tag ? [input.tag] : [],
|
|
125934
|
-
...input.tags ?? []
|
|
125935
|
-
].map(normalizeTagName).filter(Boolean);
|
|
125936
|
-
if (names.length > 0) {
|
|
125937
|
-
const nameConditions = names.map(
|
|
125938
|
-
(name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
|
|
125939
|
-
);
|
|
125940
|
-
const rows = await db.select({ id: schema_exports.tags.id }).from(schema_exports.tags).where(and(eq(schema_exports.tags.teamId, teamId), or(...nameConditions)));
|
|
125941
|
-
for (const row of rows) ids.add(row.id);
|
|
125942
|
-
}
|
|
125943
|
-
return [...ids];
|
|
125944
|
-
}
|
|
125945
|
-
async function resolveTags(teamId, input) {
|
|
125946
|
-
const tags2 = [];
|
|
125947
|
-
const errors = [];
|
|
125948
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
125949
|
-
if (input.tagIds?.length) {
|
|
125950
|
-
const rows = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
|
|
125951
|
-
and(
|
|
125952
|
-
eq(schema_exports.tags.teamId, teamId),
|
|
125953
|
-
inArray(schema_exports.tags.id, input.tagIds)
|
|
125954
|
-
)
|
|
125955
|
-
);
|
|
125956
|
-
for (const row of rows) {
|
|
125957
|
-
if (seenIds.has(row.id)) continue;
|
|
125958
|
-
seenIds.add(row.id);
|
|
125959
|
-
tags2.push(row);
|
|
125960
|
-
}
|
|
125961
|
-
for (const id of input.tagIds) {
|
|
125962
|
-
if (!seenIds.has(id)) errors.push(`Unknown tag ID: ${id}`);
|
|
125963
|
-
}
|
|
125964
|
-
}
|
|
125965
|
-
const rawNames = input.tagNames ?? [];
|
|
125966
|
-
if (rawNames.length === 0) return { tags: tags2, errors };
|
|
125967
|
-
const normalizedNames = [
|
|
125968
|
-
...new Set(rawNames.map(normalizeTagName).filter(Boolean))
|
|
125969
|
-
];
|
|
125970
|
-
if (normalizedNames.length === 0) return { tags: tags2, errors };
|
|
125971
|
-
const nameConditions = normalizedNames.map(
|
|
125972
|
-
(name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
|
|
125973
|
-
);
|
|
125974
|
-
const existing = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(and(eq(schema_exports.tags.teamId, teamId), or(...nameConditions)));
|
|
125975
|
-
const existingByNorm = /* @__PURE__ */ new Map();
|
|
125976
|
-
for (const tag2 of existing) {
|
|
125977
|
-
existingByNorm.set(normalizeTagName(tag2.name), tag2);
|
|
125978
|
-
}
|
|
125979
|
-
for (const rawName of rawNames) {
|
|
125980
|
-
const norm = normalizeTagName(rawName);
|
|
125981
|
-
if (!norm) continue;
|
|
125982
|
-
const found = existingByNorm.get(norm);
|
|
125983
|
-
if (found) {
|
|
125984
|
-
if (!seenIds.has(found.id)) {
|
|
125985
|
-
seenIds.add(found.id);
|
|
125986
|
-
tags2.push(found);
|
|
125987
|
-
}
|
|
125988
|
-
continue;
|
|
125989
|
-
}
|
|
125990
|
-
if (!input.createMissing) {
|
|
125991
|
-
errors.push(`Tag not found: ${rawName}`);
|
|
125992
|
-
continue;
|
|
125993
|
-
}
|
|
125994
|
-
try {
|
|
125995
|
-
const [created] = await db.insert(schema_exports.tags).values({
|
|
125996
|
-
teamId,
|
|
125997
|
-
name: norm,
|
|
125998
|
-
projectId: input.projectId ?? null
|
|
125999
|
-
}).returning({ id: schema_exports.tags.id, name: schema_exports.tags.name });
|
|
126000
|
-
if (created) {
|
|
126001
|
-
seenIds.add(created.id);
|
|
126002
|
-
tags2.push(created);
|
|
126003
|
-
existingByNorm.set(norm, created);
|
|
126004
|
-
}
|
|
126005
|
-
} catch {
|
|
126006
|
-
const [retry2] = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
|
|
126007
|
-
and(
|
|
126008
|
-
eq(schema_exports.tags.teamId, teamId),
|
|
126009
|
-
sql`lower(${schema_exports.tags.name}) = ${norm}`,
|
|
126010
|
-
input.projectId ? or(
|
|
126011
|
-
eq(schema_exports.tags.projectId, input.projectId),
|
|
126012
|
-
isNull(schema_exports.tags.projectId)
|
|
126013
|
-
) : isNull(schema_exports.tags.projectId)
|
|
126014
|
-
)
|
|
126015
|
-
).limit(1);
|
|
126016
|
-
if (retry2 && !seenIds.has(retry2.id)) {
|
|
126017
|
-
seenIds.add(retry2.id);
|
|
126018
|
-
tags2.push(retry2);
|
|
126019
|
-
existingByNorm.set(norm, retry2);
|
|
126020
|
-
} else {
|
|
126021
|
-
errors.push(`Failed to create tag: ${rawName}`);
|
|
126022
|
-
}
|
|
126023
|
-
}
|
|
126024
|
-
}
|
|
126025
|
-
return { tags: tags2, errors };
|
|
126026
|
-
}
|
|
126027
|
-
async function syncTicketTags(ticketId, teamId, tagIds, mode = "replace") {
|
|
126028
|
-
const current = await db.select({ tagId: schema_exports.ticketTags.tagId }).from(schema_exports.ticketTags).where(eq(schema_exports.ticketTags.ticketId, ticketId));
|
|
126029
|
-
const currentIds = new Set(current.map((row) => row.tagId));
|
|
126030
|
-
let targetIds;
|
|
126031
|
-
if (mode === "remove") {
|
|
126032
|
-
targetIds = new Set(
|
|
126033
|
-
[...currentIds].filter((id) => !tagIds.includes(id))
|
|
126034
|
-
);
|
|
126035
|
-
} else if (mode === "merge") {
|
|
126036
|
-
targetIds = /* @__PURE__ */ new Set([...currentIds, ...tagIds]);
|
|
126037
|
-
} else {
|
|
126038
|
-
targetIds = new Set(tagIds);
|
|
126039
|
-
}
|
|
126040
|
-
const toInsert = [...targetIds].filter((id) => !currentIds.has(id));
|
|
126041
|
-
const toDelete = [...currentIds].filter((id) => !targetIds.has(id));
|
|
126042
|
-
if (toDelete.length > 0) {
|
|
126043
|
-
await db.delete(schema_exports.ticketTags).where(
|
|
126044
|
-
and(
|
|
126045
|
-
eq(schema_exports.ticketTags.ticketId, ticketId),
|
|
126046
|
-
inArray(schema_exports.ticketTags.tagId, toDelete)
|
|
126047
|
-
)
|
|
126048
|
-
);
|
|
126049
|
-
}
|
|
126050
|
-
if (toInsert.length > 0) {
|
|
126051
|
-
await db.insert(schema_exports.ticketTags).values(
|
|
126052
|
-
toInsert.map((tagId) => ({
|
|
126053
|
-
ticketId,
|
|
126054
|
-
tagId,
|
|
126055
|
-
teamId
|
|
126056
|
-
}))
|
|
126057
|
-
);
|
|
126058
|
-
}
|
|
126059
|
-
const tagIdsToDescribe = [.../* @__PURE__ */ new Set([...toInsert, ...toDelete])];
|
|
126060
|
-
const tagNamesById = /* @__PURE__ */ new Map();
|
|
126061
|
-
if (tagIdsToDescribe.length > 0) {
|
|
126062
|
-
const rows = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(inArray(schema_exports.tags.id, tagIdsToDescribe));
|
|
126063
|
-
for (const row of rows) tagNamesById.set(row.id, row.name);
|
|
126064
|
-
}
|
|
126065
|
-
return {
|
|
126066
|
-
added: toInsert.map((id) => tagNamesById.get(id) ?? id),
|
|
126067
|
-
removed: toDelete.map((id) => tagNamesById.get(id) ?? id)
|
|
126068
|
-
};
|
|
126069
|
-
}
|
|
126070
|
-
|
|
126071
126714
|
// src/tools/tags.ts
|
|
126072
126715
|
async function handleGetTags(input) {
|
|
126073
126716
|
const resolved = await resolveTeamId(input.teamId);
|
|
@@ -127392,6 +128035,9 @@ async function handleGetTickets(input) {
|
|
|
127392
128035
|
tag: tag2,
|
|
127393
128036
|
tags: tags2,
|
|
127394
128037
|
tagIds,
|
|
128038
|
+
projectTag,
|
|
128039
|
+
projectTags: projectTags2,
|
|
128040
|
+
projectTagIds,
|
|
127395
128041
|
pageSize = 20
|
|
127396
128042
|
} = input;
|
|
127397
128043
|
const resolved = await resolveTeamId(input.teamId);
|
|
@@ -127451,6 +128097,24 @@ async function handleGetTickets(input) {
|
|
|
127451
128097
|
)`
|
|
127452
128098
|
);
|
|
127453
128099
|
}
|
|
128100
|
+
const filterProjectLabelIds = await resolveProjectTagFilterIds(teamId, {
|
|
128101
|
+
projectTag,
|
|
128102
|
+
projectTags: projectTags2,
|
|
128103
|
+
projectTagIds
|
|
128104
|
+
});
|
|
128105
|
+
if (projectTag || projectTags2?.length || projectTagIds?.length) {
|
|
128106
|
+
if (filterProjectLabelIds.length === 0) {
|
|
128107
|
+
return {
|
|
128108
|
+
content: [
|
|
128109
|
+
{
|
|
128110
|
+
type: "text",
|
|
128111
|
+
text: "No tickets found (no matching project labels for the given filter)."
|
|
128112
|
+
}
|
|
128113
|
+
]
|
|
128114
|
+
};
|
|
128115
|
+
}
|
|
128116
|
+
filters.push(projectTagFilterSql(filterProjectLabelIds));
|
|
128117
|
+
}
|
|
127454
128118
|
const rows = await db.select({
|
|
127455
128119
|
id: schema_exports.tickets.id,
|
|
127456
128120
|
ticketNumber: schema_exports.tickets.ticketNumber,
|
|
@@ -127863,7 +128527,7 @@ function workStatesToStatuses(workStates) {
|
|
|
127863
128527
|
}
|
|
127864
128528
|
return [...set3];
|
|
127865
128529
|
}
|
|
127866
|
-
async function loadWorkQueue(scope, assignee, filters) {
|
|
128530
|
+
async function loadWorkQueue(scope, assignee, filters, teamId) {
|
|
127867
128531
|
const conditions = [
|
|
127868
128532
|
buildTicketAccessPredicate(
|
|
127869
128533
|
scope.teamIds,
|
|
@@ -127894,6 +128558,17 @@ async function loadWorkQueue(scope, assignee, filters) {
|
|
|
127894
128558
|
if (filters.projectId) {
|
|
127895
128559
|
conditions.push(eq(t8.projectId, filters.projectId));
|
|
127896
128560
|
}
|
|
128561
|
+
const labelFilterIds = await resolveProjectTagFilterIds(teamId, {
|
|
128562
|
+
projectTag: filters.projectTag,
|
|
128563
|
+
projectTags: filters.projectTags,
|
|
128564
|
+
projectTagIds: filters.projectTagIds
|
|
128565
|
+
});
|
|
128566
|
+
if (filters.projectTag || filters.projectTags?.length || filters.projectTagIds?.length) {
|
|
128567
|
+
if (labelFilterIds.length === 0) {
|
|
128568
|
+
return [];
|
|
128569
|
+
}
|
|
128570
|
+
conditions.push(projectTagFilterSql(labelFilterIds, t8));
|
|
128571
|
+
}
|
|
127897
128572
|
const rows = await db.select({
|
|
127898
128573
|
id: t8.id,
|
|
127899
128574
|
ticketNumber: t8.ticketNumber,
|
|
@@ -127946,10 +128621,12 @@ async function handleGetMyWorkQueue(input) {
|
|
|
127946
128621
|
const ctx = getAuthContext();
|
|
127947
128622
|
const scope = await resolveTeamScope(input.teamId);
|
|
127948
128623
|
if (!scope.ok) return scope.response;
|
|
128624
|
+
const teamId = input.teamId ?? ctx.teamId;
|
|
127949
128625
|
const rows = await loadWorkQueue(
|
|
127950
128626
|
scope,
|
|
127951
128627
|
{ mode: "one", userId: ctx.userId },
|
|
127952
|
-
input
|
|
128628
|
+
input,
|
|
128629
|
+
teamId
|
|
127953
128630
|
);
|
|
127954
128631
|
const items = rows.map(serializeItem);
|
|
127955
128632
|
return jsonResponse3({
|
|
@@ -127972,10 +128649,12 @@ async function handleGetAssigneeWorkQueue(input) {
|
|
|
127972
128649
|
const rawAssignee = input.assigneeId?.trim();
|
|
127973
128650
|
const isAll = rawAssignee === "all";
|
|
127974
128651
|
const targetUserId = !rawAssignee || rawAssignee === "me" ? ctx.userId : rawAssignee;
|
|
128652
|
+
const teamId = input.teamId ?? ctx.teamId;
|
|
127975
128653
|
const rows = await loadWorkQueue(
|
|
127976
128654
|
scope,
|
|
127977
128655
|
isAll ? { mode: "all" } : { mode: "one", userId: targetUserId },
|
|
127978
|
-
input
|
|
128656
|
+
input,
|
|
128657
|
+
teamId
|
|
127979
128658
|
);
|
|
127980
128659
|
const items = rows.map(serializeItem);
|
|
127981
128660
|
if (isAll && input.groupByAssignee) {
|
|
@@ -128011,11 +128690,13 @@ async function handleGetAssigneeWorkQueue(input) {
|
|
|
128011
128690
|
});
|
|
128012
128691
|
}
|
|
128013
128692
|
async function handleGetTeamWorkloadOverview(input) {
|
|
128693
|
+
const ctx = getAuthContext();
|
|
128014
128694
|
const scope = await resolveTeamScope(input.teamId);
|
|
128015
128695
|
if (!scope.ok) return scope.response;
|
|
128016
128696
|
const staleDays = input.staleDays ?? 7;
|
|
128017
128697
|
const upcomingDays = input.upcomingDays ?? 7;
|
|
128018
128698
|
const includeUnassigned = input.includeUnassigned ?? true;
|
|
128699
|
+
const teamId = input.teamId ?? ctx.teamId;
|
|
128019
128700
|
const conditions = [
|
|
128020
128701
|
buildTicketAccessPredicate(
|
|
128021
128702
|
scope.teamIds,
|
|
@@ -128028,6 +128709,21 @@ async function handleGetTeamWorkloadOverview(input) {
|
|
|
128028
128709
|
if (!includeUnassigned) {
|
|
128029
128710
|
conditions.push(sql`${t8.assigneeId} IS NOT NULL`);
|
|
128030
128711
|
}
|
|
128712
|
+
const labelFilterIds = await resolveProjectTagFilterIds(teamId, {
|
|
128713
|
+
projectTag: input.projectTag,
|
|
128714
|
+
projectTags: input.projectTags,
|
|
128715
|
+
projectTagIds: input.projectTagIds
|
|
128716
|
+
});
|
|
128717
|
+
if (input.projectTag || input.projectTags?.length || input.projectTagIds?.length) {
|
|
128718
|
+
if (labelFilterIds.length === 0) {
|
|
128719
|
+
return jsonResponse3({
|
|
128720
|
+
teamScope: scope.teamIds,
|
|
128721
|
+
config: { staleDays, upcomingDays, includeUnassigned },
|
|
128722
|
+
assignees: []
|
|
128723
|
+
});
|
|
128724
|
+
}
|
|
128725
|
+
conditions.push(projectTagFilterSql(labelFilterIds, t8));
|
|
128726
|
+
}
|
|
128031
128727
|
const rows = await db.select({
|
|
128032
128728
|
assigneeId: t8.assigneeId,
|
|
128033
128729
|
assigneeName: schema_exports.users.fullName,
|
|
@@ -128969,11 +129665,13 @@ async function handleGetRefrontMcpActivity(input) {
|
|
|
128969
129665
|
);
|
|
128970
129666
|
}
|
|
128971
129667
|
const userIds = /* @__PURE__ */ new Set();
|
|
128972
|
-
const
|
|
129668
|
+
const ticketRefs = /* @__PURE__ */ new Set();
|
|
128973
129669
|
const projectIds = /* @__PURE__ */ new Set();
|
|
128974
129670
|
for (const r6 of rows) {
|
|
128975
129671
|
if (r6.userId) userIds.add(r6.userId);
|
|
128976
|
-
|
|
129672
|
+
for (const ref of collectTicketRefsFromEntity(r6.entity)) {
|
|
129673
|
+
ticketRefs.add(ref);
|
|
129674
|
+
}
|
|
128977
129675
|
if (r6.entity.projectId) projectIds.add(r6.entity.projectId);
|
|
128978
129676
|
}
|
|
128979
129677
|
const usersById = /* @__PURE__ */ new Map();
|
|
@@ -128981,34 +129679,27 @@ async function handleGetRefrontMcpActivity(input) {
|
|
|
128981
129679
|
const userRows = await db.select({ id: schema_exports.users.id, name: schema_exports.users.fullName }).from(schema_exports.users).where(inArray(schema_exports.users.id, [...userIds]));
|
|
128982
129680
|
for (const u2 of userRows) usersById.set(u2.id, u2.name);
|
|
128983
129681
|
}
|
|
128984
|
-
const
|
|
128985
|
-
if (
|
|
128986
|
-
const
|
|
128987
|
-
|
|
128988
|
-
|
|
128989
|
-
|
|
128990
|
-
projectId: schema_exports.tickets.projectId
|
|
128991
|
-
}).from(schema_exports.tickets).where(
|
|
128992
|
-
and(
|
|
128993
|
-
inArray(schema_exports.tickets.id, [...ticketIds]),
|
|
128994
|
-
inArray(schema_exports.tickets.teamId, scope.teamIds)
|
|
128995
|
-
)
|
|
129682
|
+
const { byRef: ticketsByRef, unresolved: unresolvedTicketRefs } = await resolveTicketRefMap({ refs: ticketRefs, teamIds: scope.teamIds });
|
|
129683
|
+
if (unresolvedTicketRefs.length > 0) {
|
|
129684
|
+
const preview = unresolvedTicketRefs.slice(0, 5).join(", ");
|
|
129685
|
+
const suffix = unresolvedTicketRefs.length > 5 ? ` (+${unresolvedTicketRefs.length - 5} more)` : "";
|
|
129686
|
+
limitations.push(
|
|
129687
|
+
`${unresolvedTicketRefs.length} ticket ref(s) could not be resolved: ${preview}${suffix}.`
|
|
128996
129688
|
);
|
|
128997
|
-
|
|
128998
|
-
|
|
128999
|
-
|
|
129000
|
-
title: t9.title,
|
|
129001
|
-
projectId: t9.projectId
|
|
129002
|
-
});
|
|
129003
|
-
if (t9.projectId) projectIds.add(t9.projectId);
|
|
129004
|
-
}
|
|
129689
|
+
}
|
|
129690
|
+
for (const info of ticketsByRef.values()) {
|
|
129691
|
+
if (info.projectId) projectIds.add(info.projectId);
|
|
129005
129692
|
}
|
|
129006
129693
|
const projectsById = /* @__PURE__ */ new Map();
|
|
129007
129694
|
if (projectIds.size > 0) {
|
|
129008
129695
|
const projectRows = await db.select({ id: schema_exports.projects.id, name: schema_exports.projects.name }).from(schema_exports.projects).where(inArray(schema_exports.projects.id, [...projectIds]));
|
|
129009
129696
|
for (const p3 of projectRows) projectsById.set(p3.id, p3.name);
|
|
129010
129697
|
}
|
|
129011
|
-
const projectOf = (r6) =>
|
|
129698
|
+
const projectOf = (r6) => {
|
|
129699
|
+
const ticket = lookupResolvedTicket(ticketsByRef, r6.entity);
|
|
129700
|
+
return r6.entity.projectId ?? ticket?.projectId ?? null;
|
|
129701
|
+
};
|
|
129702
|
+
const canonicalTicketId = (r6) => lookupResolvedTicket(ticketsByRef, r6.entity)?.id ?? r6.entity.ticketId ?? r6.entity.ticketNumber ?? null;
|
|
129012
129703
|
const byAction = {};
|
|
129013
129704
|
const byToolMap = /* @__PURE__ */ new Map();
|
|
129014
129705
|
const byTicketMap = /* @__PURE__ */ new Map();
|
|
@@ -129036,11 +129727,9 @@ async function handleGetRefrontMcpActivity(input) {
|
|
|
129036
129727
|
tool2.durationSamples += 1;
|
|
129037
129728
|
}
|
|
129038
129729
|
byToolMap.set(r6.toolName, tool2);
|
|
129039
|
-
if (r6
|
|
129040
|
-
|
|
129041
|
-
|
|
129042
|
-
(byTicketMap.get(r6.entity.ticketId) ?? 0) + 1
|
|
129043
|
-
);
|
|
129730
|
+
if (canonicalTicketId(r6)) {
|
|
129731
|
+
const key = canonicalTicketId(r6);
|
|
129732
|
+
byTicketMap.set(key, (byTicketMap.get(key) ?? 0) + 1);
|
|
129044
129733
|
}
|
|
129045
129734
|
const projectId = projectOf(r6);
|
|
129046
129735
|
if (projectId) {
|
|
@@ -129053,12 +129742,15 @@ async function handleGetRefrontMcpActivity(input) {
|
|
|
129053
129742
|
failures: v2.failures,
|
|
129054
129743
|
avgDurationMs: v2.durationSamples > 0 ? Math.round(v2.durationMsTotal / v2.durationSamples) : null
|
|
129055
129744
|
})).sort((a6, b7) => b7.count - a6.count).slice(0, TOP_ENTITIES);
|
|
129056
|
-
const byTicket = [...byTicketMap.entries()].map(([ticketId, count2]) =>
|
|
129057
|
-
ticketId
|
|
129058
|
-
|
|
129059
|
-
|
|
129060
|
-
|
|
129061
|
-
|
|
129745
|
+
const byTicket = [...byTicketMap.entries()].map(([ticketId, count2]) => {
|
|
129746
|
+
const ticket = ticketsByRef.get(ticketId) ?? ticketsByRef.get(ticketId.toLowerCase());
|
|
129747
|
+
return {
|
|
129748
|
+
ticketId: ticket?.id ?? ticketId,
|
|
129749
|
+
ticketNumber: ticket?.ticketNumber ?? null,
|
|
129750
|
+
ticketTitle: ticket?.title ?? null,
|
|
129751
|
+
count: count2
|
|
129752
|
+
};
|
|
129753
|
+
}).sort((a6, b7) => b7.count - a6.count).slice(0, TOP_ENTITIES);
|
|
129062
129754
|
const byProject = [...byProjectMap.entries()].map(([projectId, count2]) => ({
|
|
129063
129755
|
projectId,
|
|
129064
129756
|
projectName: projectsById.get(projectId) ?? null,
|
|
@@ -129070,6 +129762,9 @@ async function handleGetRefrontMcpActivity(input) {
|
|
|
129070
129762
|
);
|
|
129071
129763
|
const timeline = rows.slice(0, pageSize).map((r6) => {
|
|
129072
129764
|
const projectId = projectOf(r6);
|
|
129765
|
+
const ticket = lookupResolvedTicket(ticketsByRef, r6.entity);
|
|
129766
|
+
const ticketId = ticket?.id ?? r6.entity.ticketId ?? null;
|
|
129767
|
+
const ticketNumber = ticket?.ticketNumber ?? r6.entity.ticketNumber ?? null;
|
|
129073
129768
|
return {
|
|
129074
129769
|
timestamp: formatIsoWithOffset(new Date(r6.tsUtc), timezone),
|
|
129075
129770
|
timestampUtc: r6.tsUtc,
|
|
@@ -129078,8 +129773,8 @@ async function handleGetRefrontMcpActivity(input) {
|
|
|
129078
129773
|
success: r6.success,
|
|
129079
129774
|
durationMs: r6.durationMs,
|
|
129080
129775
|
actor: { id: r6.userId, name: r6.userId ? usersById.get(r6.userId) ?? null : null },
|
|
129081
|
-
ticketId
|
|
129082
|
-
ticketNumber
|
|
129776
|
+
ticketId,
|
|
129777
|
+
ticketNumber,
|
|
129083
129778
|
project: projectId ? { id: projectId, name: projectsById.get(projectId) ?? null } : null,
|
|
129084
129779
|
entity: r6.entity,
|
|
129085
129780
|
...r6.error ? { error: r6.error } : {}
|
|
@@ -129409,7 +130104,7 @@ var DEFAULT_PAGE_SIZE4 = 200;
|
|
|
129409
130104
|
var MAX_PAGE_SIZE4 = 500;
|
|
129410
130105
|
var MAX_TIMELINE_SOURCE = 2e3;
|
|
129411
130106
|
var COMMENT_PREVIEW_LENGTH2 = 140;
|
|
129412
|
-
var
|
|
130107
|
+
var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
129413
130108
|
var STATUS_TRANSITION_TYPES = /* @__PURE__ */ new Set(["status_changed", "status_change"]);
|
|
129414
130109
|
var DISCLAIMER3 = "Cycle-time and interaction signals are supporting context, not an exact measure of worked time. Time-in-status includes nights/weekends and idle waiting; interpret with ticket complexity, blockers, reviews and dependencies.";
|
|
129415
130110
|
function textResponse17(text3) {
|
|
@@ -129449,9 +130144,9 @@ async function handleGetTicketInteractionTimeline(input) {
|
|
|
129449
130144
|
if (scope.teamIds.length === 0) {
|
|
129450
130145
|
return textResponse17("No accessible teams found.");
|
|
129451
130146
|
}
|
|
129452
|
-
const
|
|
130147
|
+
const isUuid3 = UUID_RE3.test(rawTicket);
|
|
129453
130148
|
const idBranches = [eq(schema_exports.tickets.ticketNumber, rawTicket)];
|
|
129454
|
-
if (
|
|
130149
|
+
if (isUuid3) idBranches.push(eq(schema_exports.tickets.id, rawTicket));
|
|
129455
130150
|
const [ticket] = await db.select({
|
|
129456
130151
|
id: schema_exports.tickets.id,
|
|
129457
130152
|
teamId: schema_exports.tickets.teamId,
|
|
@@ -130009,6 +130704,22 @@ function createMcpServer() {
|
|
|
130009
130704
|
return await handleCreateProject(asToolArgs(toolArgs));
|
|
130010
130705
|
case "update-project":
|
|
130011
130706
|
return await handleUpdateProject(asToolArgs(toolArgs));
|
|
130707
|
+
case "get-project-tags":
|
|
130708
|
+
return await handleGetProjectTags(
|
|
130709
|
+
asToolArgs(toolArgs)
|
|
130710
|
+
);
|
|
130711
|
+
case "set-project-tags":
|
|
130712
|
+
return await handleSetProjectTags(
|
|
130713
|
+
asToolArgs(toolArgs)
|
|
130714
|
+
);
|
|
130715
|
+
case "add-project-tag":
|
|
130716
|
+
return await handleAddProjectTag(
|
|
130717
|
+
asToolArgs(toolArgs)
|
|
130718
|
+
);
|
|
130719
|
+
case "remove-project-tag":
|
|
130720
|
+
return await handleRemoveProjectTag(
|
|
130721
|
+
asToolArgs(toolArgs)
|
|
130722
|
+
);
|
|
130012
130723
|
case "archive-project":
|
|
130013
130724
|
return await handleArchiveProject(
|
|
130014
130725
|
asToolArgs(toolArgs)
|