@maudecode/kanvil 0.6.1 → 0.8.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/apps/server/dist/{auth-CFjetlB4.mjs → auth-CVnX2XIg.mjs} +3828 -3463
- package/apps/server/dist/client.mjs +2 -2
- package/apps/server/dist/{config-BQw6mPzE.mjs → config-DUIdB3c2.mjs} +1 -1
- package/apps/server/dist/index.mjs +165 -43
- package/apps/server/dist/migrate.mjs +1 -1
- package/apps/server/dist/recover.mjs +3 -3
- package/apps/server/dist/{src-BaTNDJB7.mjs → src-CC6lPIU_.mjs} +190 -35
- package/apps/server/drizzle/0040_upload_urls.sql +20 -0
- package/apps/server/drizzle/0041_project_repositories.sql +2 -0
- package/apps/server/drizzle/0042_public_boards.sql +37 -0
- package/apps/server/drizzle/0043_epics.sql +18 -0
- package/apps/server/drizzle/meta/0040_snapshot.json +7170 -0
- package/apps/server/drizzle/meta/0041_snapshot.json +7183 -0
- package/apps/server/drizzle/meta/0042_snapshot.json +7315 -0
- package/apps/server/drizzle/meta/0043_snapshot.json +7466 -0
- package/apps/server/drizzle/meta/_journal.json +28 -0
- package/apps/web/dist/assets/{CompositeItem-Dotj8JZL.js → CompositeItem-GgAhkI4m.js} +1 -1
- package/apps/web/dist/assets/{Trans-CE4EAAY1.js → Trans-CT9xiBED.js} +1 -1
- package/apps/web/dist/assets/admin-settings-C7yxWm-w.js +1 -0
- package/apps/web/dist/assets/{archive-restore-BMtgjFUu.js → archive-restore-UgUTJyLs.js} +1 -1
- package/apps/web/dist/assets/az-AZ-IkRP1MKo.js +1 -0
- package/apps/web/dist/assets/{az-B-DxMc__.js → az-CZ46yOUl.js} +1 -1
- package/apps/web/dist/assets/configuration-CU0QOaRw.js +1 -0
- package/apps/web/dist/assets/{corner-left-up-O4OsTW-L.js → corner-left-up-BFY7AqdS.js} +1 -1
- package/apps/web/dist/assets/{de-Btbkq-aX.js → de-CseN-3gF.js} +1 -1
- package/apps/web/dist/assets/de-DE-DCGKtpLI.js +1 -0
- package/apps/web/dist/assets/default-CN9zcRIy.js +56 -0
- package/apps/web/dist/assets/delivery-settings-xx9Bb6Ux.js +1 -0
- package/apps/web/dist/assets/el-GR-C5w6p2-z.js +1 -0
- package/apps/web/dist/assets/{el-DBVEX16k.js → el-psH0HOR3.js} +1 -1
- package/apps/web/dist/assets/{ellipsis-CvEPDi9V.js → ellipsis-CcIJddV1.js} +1 -1
- package/apps/web/dist/assets/{es-CMJoxZBk.js → es-BVvGrD23.js} +1 -1
- package/apps/web/dist/assets/es-ES-PjJlJCgT.js +1 -0
- package/apps/web/dist/assets/{forms-DbfnnPg_.js → forms-BUNl5uNV.js} +1 -1
- package/apps/web/dist/assets/{fr-OyEmYoRn.js → fr-C43nghuI.js} +1 -1
- package/apps/web/dist/assets/fr-FR-CGWH-Juq.js +1 -0
- package/apps/web/dist/assets/{hi-CIgXSseB.js → hi-Ds3DGtT-.js} +1 -1
- package/apps/web/dist/assets/hi-IN-B3bq8hDJ.js +1 -0
- package/apps/web/dist/assets/{highlight-B4K3yEEh.js → highlight-BPZqoBaO.js} +1 -1
- package/apps/web/dist/assets/id-ID-98H240ET.js +1 -0
- package/apps/web/dist/assets/{id-WV2oSAPB.js → id-tg9awgd8.js} +1 -1
- package/apps/web/dist/assets/index-CvFGdLre.js +17 -0
- package/apps/web/dist/assets/index-DPT5LAs1.css +2 -0
- package/apps/web/dist/assets/{invitations-BTB8f2Xl.js → invitations-fH1M9hYm.js} +1 -1
- package/apps/web/dist/assets/{it-BWSrRqus.js → it-DRwaWQyE.js} +1 -1
- package/apps/web/dist/assets/it-IT-CNS9cL7I.js +1 -0
- package/apps/web/dist/assets/{item-HmFuRZet.js → item-BbaYy50i.js} +1 -1
- package/apps/web/dist/assets/ja-JP-BFTqtCdV.js +1 -0
- package/apps/web/dist/assets/{ja-BtOHWvUJ.js → ja-jj4Ubayf.js} +1 -1
- package/apps/web/dist/assets/{ko-DAryK0HN.js → ko-Dn2pRrar.js} +1 -1
- package/apps/web/dist/assets/ko-KR-C58VNccr.js +1 -0
- package/apps/web/dist/assets/{label-settings-ssfEX2uR.js → label-settings-CbBHSL0c.js} +1 -1
- package/apps/web/dist/assets/{markdown-blocks-B1kO6iku.js → markdown-blocks-B6mE_S6n.js} +1 -1
- package/apps/web/dist/assets/{member-directory-BA4ZT1qU.js → member-directory-CoGg7WkW.js} +1 -1
- package/apps/web/dist/assets/{mk-DBbiGUk5.js → mk-DQUnXaPE.js} +1 -1
- package/apps/web/dist/assets/mk-MK-Bf5RDo6j.js +1 -0
- package/apps/web/dist/assets/{nl-D4V-8WdF.js → nl-C_Ijir3O.js} +1 -1
- package/apps/web/dist/assets/nl-NL-D1WFJyOY.js +1 -0
- package/apps/web/dist/assets/{pl-C--QrxO0.js → pl-DgWXgYL-.js} +1 -1
- package/apps/web/dist/assets/pl-PL--jAcwyAK.js +1 -0
- package/apps/web/dist/assets/project-lifecycle-BHWCMq7o.js +1 -0
- package/apps/web/dist/assets/project-settings-CBG5CZX1.js +1 -0
- package/apps/web/dist/assets/pt-BR-CJQfuq9b.js +1 -0
- package/apps/web/dist/assets/{pt-BR-DvFVKpmO.js → pt-BR-N-Xvp9fC.js} +1 -1
- package/apps/web/dist/assets/public-project-DmTr9q4c.js +1 -0
- package/apps/web/dist/assets/{rotate-ccw-CB36Zu3L.js → rotate-ccw-BFYpBtuj.js} +1 -1
- package/apps/web/dist/assets/{ru-UmbPUOka.js → ru-Ctq-5FYl.js} +1 -1
- package/apps/web/dist/assets/ru-RU-BtvJ8Glt.js +1 -0
- package/apps/web/dist/assets/{search-Cvetr1OK.js → search-C2QaoLwI.js} +6 -6
- package/apps/web/dist/assets/{send-CJDct4Oj.js → send-qRPqbT2m.js} +1 -1
- package/apps/web/dist/assets/settings-BNPajW83.js +1 -0
- package/apps/web/dist/assets/{sliders-horizontal-dmqWngxy.js → sliders-horizontal-S16oyCiS.js} +1 -1
- package/apps/web/dist/assets/{switch-BtScTpP9.js → switch-C44Ouoqh.js} +1 -1
- package/apps/web/dist/assets/{table-byFokzo-.js → table-esnQpDtb.js} +1 -1
- package/apps/web/dist/assets/ticket-detail-BVyScTlg.js +2 -0
- package/apps/web/dist/assets/ticket-editor-BjuSeeiA.js +2 -0
- package/apps/web/dist/assets/ticket-list-BCvUvYju.js +6 -0
- package/apps/web/dist/assets/ticket-list-controls-BiC5CIwM.js +1 -0
- package/apps/web/dist/assets/{tr-CNpgQ4ka.js → tr-CYT8BT69.js} +1 -1
- package/apps/web/dist/assets/tr-TR-Bahy-EKO.js +1 -0
- package/apps/web/dist/assets/uk-UA-DDVgxWqX.js +1 -0
- package/apps/web/dist/assets/{uk-MQoTXdpk.js → uk-y7QuyfrN.js} +1 -1
- package/apps/web/dist/assets/{upload-D1x_ib3B.js → upload-e7CmLHhp.js} +1 -1
- package/apps/web/dist/assets/{useQueries-BnlNCoy4.js → useQueries-U3MOyy3t.js} +1 -1
- package/apps/web/dist/assets/{vi-CEw4gTWc.js → vi-Bz2g5X1k.js} +1 -1
- package/apps/web/dist/assets/vi-VN-CuHCYxNQ.js +1 -0
- package/apps/web/dist/assets/{visual-editor-CpdMRn3e.js → visual-editor-UmVaDXWu.js} +1 -1
- package/apps/web/dist/assets/workspace-settings-BiWJqW0m.js +1 -0
- package/apps/web/dist/assets/zh-CN-C94sYOI_.js +1 -0
- package/apps/web/dist/assets/{zh-CN-ChzoXkfr.js → zh-CN-y_sluDUm.js} +1 -1
- package/apps/web/dist/assets/zh-TW-BoRQfGQL.js +1 -0
- package/apps/web/dist/assets/{zh-TW-bWBKARrK.js → zh-TW-DIEfy239.js} +1 -1
- package/apps/web/dist/index.html +8 -8
- package/docs/api.md +63 -5
- package/docs/cli.md +1 -1
- package/docs/mobile.md +1 -1
- package/docs/plan.md +1 -0
- package/docs/releasing.md +1 -1
- package/package.json +1 -1
- package/apps/web/dist/assets/admin-settings-CO7nnD9T.js +0 -1
- package/apps/web/dist/assets/az-AZ-R8_HFWJr.js +0 -1
- package/apps/web/dist/assets/configuration-Bnt72HRu.js +0 -1
- package/apps/web/dist/assets/de-DE-BHgMj19Y.js +0 -1
- package/apps/web/dist/assets/default-DqsN66dq.js +0 -56
- package/apps/web/dist/assets/delivery-settings-Cji0N0m0.js +0 -1
- package/apps/web/dist/assets/el-GR-CQFGM5Sr.js +0 -1
- package/apps/web/dist/assets/es-ES-DZkcRVA4.js +0 -1
- package/apps/web/dist/assets/fr-FR-CRPAezXU.js +0 -1
- package/apps/web/dist/assets/hi-IN-2YFE4UQk.js +0 -1
- package/apps/web/dist/assets/id-ID-B-J0_QIW.js +0 -1
- package/apps/web/dist/assets/index-BAnwOrh1.css +0 -2
- package/apps/web/dist/assets/index-nltuyQtq.js +0 -17
- package/apps/web/dist/assets/it-IT-CJ0HYggO.js +0 -1
- package/apps/web/dist/assets/ja-JP-DDQS7D7b.js +0 -1
- package/apps/web/dist/assets/ko-KR-CkpoewEa.js +0 -1
- package/apps/web/dist/assets/mk-MK-C0RhS6XR.js +0 -1
- package/apps/web/dist/assets/nl-NL-BSKqlzcL.js +0 -1
- package/apps/web/dist/assets/pl-PL-CHz38SiU.js +0 -1
- package/apps/web/dist/assets/project-lifecycle-COZb9FqM.js +0 -1
- package/apps/web/dist/assets/project-settings-DwmTm9KA.js +0 -1
- package/apps/web/dist/assets/pt-BR-BotWHRiO.js +0 -1
- package/apps/web/dist/assets/public-project-B3hEkeCF.js +0 -1
- package/apps/web/dist/assets/ru-RU-DtOwNna6.js +0 -1
- package/apps/web/dist/assets/settings-3IXm9lJ-.js +0 -1
- package/apps/web/dist/assets/ticket-detail-C3YA3Hzc.js +0 -2
- package/apps/web/dist/assets/ticket-editor-CZnZQMG1.js +0 -2
- package/apps/web/dist/assets/ticket-list-BXUorfmj.js +0 -6
- package/apps/web/dist/assets/ticket-list-controls-BxGKCZMo.js +0 -1
- package/apps/web/dist/assets/tr-TR-CdG2alU2.js +0 -1
- package/apps/web/dist/assets/uk-UA-Ceu5gorw.js +0 -1
- package/apps/web/dist/assets/vi-VN-CxshgV4W.js +0 -1
- package/apps/web/dist/assets/workspace-settings-BickBtZx.js +0 -1
- package/apps/web/dist/assets/zh-CN-D7Yrjhd3.js +0 -1
- package/apps/web/dist/assets/zh-TW-DyF38kqx.js +0 -1
|
@@ -290,10 +290,15 @@ var en_US_default = {
|
|
|
290
290
|
priority: "Priority",
|
|
291
291
|
kind: "Kind",
|
|
292
292
|
difficulty: "Difficulty",
|
|
293
|
+
automation: "Agent readiness",
|
|
293
294
|
label: "Label",
|
|
294
295
|
assigneeId: "Assignee",
|
|
295
296
|
customField: "Field"
|
|
296
297
|
},
|
|
298
|
+
automationFilter: {
|
|
299
|
+
ready: "Ready for agents",
|
|
300
|
+
manual: "Needs a person"
|
|
301
|
+
},
|
|
297
302
|
sort: {
|
|
298
303
|
priority: "Priority",
|
|
299
304
|
updated: "Recently updated",
|
|
@@ -436,6 +441,10 @@ var en_US_default = {
|
|
|
436
441
|
updated: "Time entry updated",
|
|
437
442
|
deleted: "Time entry deleted"
|
|
438
443
|
},
|
|
444
|
+
epic: {
|
|
445
|
+
created: "Epic created",
|
|
446
|
+
updated: "Epic updated"
|
|
447
|
+
},
|
|
439
448
|
label: {
|
|
440
449
|
created: "Label created",
|
|
441
450
|
updated: "Label updated",
|
|
@@ -489,6 +498,7 @@ var en_US_default = {
|
|
|
489
498
|
created: "Calendar feed created",
|
|
490
499
|
revoked: "Calendar feed revoked"
|
|
491
500
|
},
|
|
501
|
+
public_board: { updated: "Public board updated" },
|
|
492
502
|
project: {
|
|
493
503
|
created: "Project created",
|
|
494
504
|
updated: "Project updated",
|
|
@@ -535,6 +545,7 @@ var en_US_default = {
|
|
|
535
545
|
time_entry_conflict: "This actor already has a running entry on the ticket.",
|
|
536
546
|
claim_conflict: "Another worker holds this ticket.",
|
|
537
547
|
idempotency_conflict: "This request was already used for another change.",
|
|
548
|
+
duplicate_repository: "Another project in this workspace links this repository.",
|
|
538
549
|
project_archived: "Restore the archived project before changing it.",
|
|
539
550
|
rate_limited: "Too many changes in a short time. Wait and retry.",
|
|
540
551
|
snapshot_required: "The live feed fell behind. Reload to continue.",
|
|
@@ -1073,6 +1084,8 @@ var en_US_default = {
|
|
|
1073
1084
|
kind: "Kind",
|
|
1074
1085
|
difficulty: "Difficulty",
|
|
1075
1086
|
assignee: "Assignee",
|
|
1087
|
+
epic: "Epic",
|
|
1088
|
+
noEpic: "No epic",
|
|
1076
1089
|
you: "{{name}} (you)",
|
|
1077
1090
|
assignTo: "Assign to…",
|
|
1078
1091
|
set: "Set {{label, lowercase}}…",
|
|
@@ -1162,6 +1175,7 @@ var en_US_default = {
|
|
|
1162
1175
|
priority: "All priorities",
|
|
1163
1176
|
kind: "All kinds",
|
|
1164
1177
|
difficulty: "All difficulties",
|
|
1178
|
+
automation: "Any readiness",
|
|
1165
1179
|
label: "All labels",
|
|
1166
1180
|
assigneeId: "Anyone",
|
|
1167
1181
|
customField: "Any field value"
|
|
@@ -1171,6 +1185,7 @@ var en_US_default = {
|
|
|
1171
1185
|
priority: "Search priorities",
|
|
1172
1186
|
kind: "Search kinds",
|
|
1173
1187
|
difficulty: "Search difficulties",
|
|
1188
|
+
automation: "Search readiness",
|
|
1174
1189
|
label: "Search labels",
|
|
1175
1190
|
assigneeId: "Search people",
|
|
1176
1191
|
customField: "Search fields"
|
|
@@ -1703,6 +1718,9 @@ var en_US_default = {
|
|
|
1703
1718
|
changePrefixDescription: "Every ticket in {{name}} takes the new prefix and keeps its number.",
|
|
1704
1719
|
changing: "Changing…",
|
|
1705
1720
|
prefixChanged: "Tickets now use {{key}}",
|
|
1721
|
+
repositories: "Repositories",
|
|
1722
|
+
repositoriesDescription: "Agents can pass one of these git remotes as the project ID.",
|
|
1723
|
+
repositoriesInvalid: "Enter up to 20 git remotes, such as git@github.com:owner/repo.git.",
|
|
1706
1724
|
publicLink: "Public link",
|
|
1707
1725
|
publicDisclosure: "Anyone with the link can read every unarchived ticket's title, description, acceptance criteria, status, priority, kind, difficulty, labels, scopes, dates, and links to other tickets in this project. People, comments, activity, attachments, evidence, and claims stay private.",
|
|
1708
1726
|
publicOn: "Public link on",
|
|
@@ -1823,6 +1841,8 @@ var en_US_default = {
|
|
|
1823
1841
|
kept: "Kept",
|
|
1824
1842
|
projects: "Projects",
|
|
1825
1843
|
members: "Members",
|
|
1844
|
+
epics: "Epics",
|
|
1845
|
+
epicTickets: "Tickets leaving an epic",
|
|
1826
1846
|
sharedLabels: "Shared labels",
|
|
1827
1847
|
signIn: "Sign-in",
|
|
1828
1848
|
sessions: "Sessions",
|
|
@@ -2937,6 +2957,7 @@ const scopes = [
|
|
|
2937
2957
|
];
|
|
2938
2958
|
const attachmentLimit = 1e7;
|
|
2939
2959
|
const stagedUploadLifetime = 864e5;
|
|
2960
|
+
const uploadUrlLifetime = 9e5;
|
|
2940
2961
|
const stateLabels = labelTable(states, (value) => t(`common.status.${value}`));
|
|
2941
2962
|
const priorityLabels = labelTable(priorities, (value) => t(`common.priority.${value}`));
|
|
2942
2963
|
ticketSymbols.status.backlog.tone, ticketSymbols.status.todo.tone, ticketSymbols.status.in_progress.tone, ticketSymbols.status.in_review.tone, ticketSymbols.status.done.tone, ticketSymbols.status.cancelled.tone;
|
|
@@ -3053,9 +3074,30 @@ const placement = {
|
|
|
3053
3074
|
after: id.optional()
|
|
3054
3075
|
};
|
|
3055
3076
|
const oneNeighbor = (input) => input.before === void 0 || input.after === void 0;
|
|
3056
|
-
const projectInput = z.object({ projectId:
|
|
3077
|
+
const projectInput = z.object({ projectId: z.string().min(1).max(300) });
|
|
3057
3078
|
const ticketInput = projectInput.extend({ ticketId: id });
|
|
3058
3079
|
const projectKey = z.string().trim().regex(/^[A-Z][A-Z0-9]{1,9}$/);
|
|
3080
|
+
const remoteHost = "(?<host>[a-z0-9][a-z0-9.-]*)";
|
|
3081
|
+
const remotePath = "(?<path>[^?#]+)";
|
|
3082
|
+
const remoteForms = [
|
|
3083
|
+
new RegExp(`^(?<scheme>https|ssh)://(?:[^/]*@)?${remoteHost}(?::(?<port>\\d+))?/${remotePath}$`),
|
|
3084
|
+
new RegExp(`^[^/:@]+@${remoteHost}:${remotePath}$`),
|
|
3085
|
+
new RegExp(`^${remoteHost}(?::(?<port>\\d+))?/${remotePath}$`)
|
|
3086
|
+
];
|
|
3087
|
+
const defaultRemotePorts = {
|
|
3088
|
+
https: "443",
|
|
3089
|
+
ssh: "22"
|
|
3090
|
+
};
|
|
3091
|
+
function normalizeRemote(value) {
|
|
3092
|
+
const remote = value.trim().toLowerCase();
|
|
3093
|
+
if (/\s/.test(remote)) return null;
|
|
3094
|
+
const { scheme, host, port, path } = remoteForms.map((form) => form.exec(remote)).find(Boolean)?.groups ?? {};
|
|
3095
|
+
const segments = path?.replace(/\/+$/, "").replace(/\.git$/, "").split("/").filter(Boolean);
|
|
3096
|
+
if (!host || !segments?.length) return null;
|
|
3097
|
+
return `${host}${port && port !== defaultRemotePorts[scheme ?? ""] ? `:${port}` : ""}/${segments.join("/")}`;
|
|
3098
|
+
}
|
|
3099
|
+
const repositoryRemote = z.string().max(300).refine((value) => normalizeRemote(value) !== null, "Enter a git remote URL").overwrite((value) => normalizeRemote(value) ?? value);
|
|
3100
|
+
const projectRepositories = z.array(repositoryRemote).max(20).overwrite((remotes) => [...new Set(remotes)]);
|
|
3059
3101
|
const reason = z.object({
|
|
3060
3102
|
code: z.string(),
|
|
3061
3103
|
message: z.string(),
|
|
@@ -3072,15 +3114,19 @@ const filterFieldNames = [
|
|
|
3072
3114
|
"priority",
|
|
3073
3115
|
"kind",
|
|
3074
3116
|
"difficulty",
|
|
3117
|
+
"automation",
|
|
3075
3118
|
"label",
|
|
3076
3119
|
"assigneeId",
|
|
3077
3120
|
"customField"
|
|
3078
3121
|
];
|
|
3122
|
+
const automationFilters = ["ready", "manual"];
|
|
3123
|
+
labelTable(automationFilters, (value) => t(`common.automationFilter.${value}`));
|
|
3079
3124
|
const filterFields = {
|
|
3080
3125
|
status: z.enum(states),
|
|
3081
3126
|
priority: z.enum(priorities),
|
|
3082
3127
|
kind: z.enum(kinds),
|
|
3083
3128
|
difficulty: z.enum(difficulties),
|
|
3129
|
+
automation: z.enum(automationFilters),
|
|
3084
3130
|
label: z.string().max(40),
|
|
3085
3131
|
assigneeId: z.union([id, z.literal("none")]),
|
|
3086
3132
|
customField: z.string().regex(/^[0-9a-f-]{36}:(?:none|true|[0-9a-f-]{36})$/)
|
|
@@ -3098,6 +3144,8 @@ const filterShape = {
|
|
|
3098
3144
|
kindNot: filterValues(filterFields.kind),
|
|
3099
3145
|
difficulty: filterValues(filterFields.difficulty),
|
|
3100
3146
|
difficultyNot: filterValues(filterFields.difficulty),
|
|
3147
|
+
automation: filterValues(filterFields.automation),
|
|
3148
|
+
automationNot: filterValues(filterFields.automation),
|
|
3101
3149
|
label: filterValues(filterFields.label),
|
|
3102
3150
|
labelNot: filterValues(filterFields.label),
|
|
3103
3151
|
assigneeId: filterValues(filterFields.assigneeId),
|
|
@@ -3167,6 +3215,7 @@ const ticketFilter = z.object({
|
|
|
3167
3215
|
]).default("priority"),
|
|
3168
3216
|
order: z.enum(["asc", "desc"]).optional()
|
|
3169
3217
|
}).strict().superRefine(rejectConflicts);
|
|
3218
|
+
const listSort = ticketFilter.shape.sort.unwrap().optional();
|
|
3170
3219
|
labelTable(ticketFilter.shape.sort.unwrap().options, (sort) => t(`common.sort.${sort}`));
|
|
3171
3220
|
const defaultOrder = {
|
|
3172
3221
|
priority: "asc",
|
|
@@ -3197,6 +3246,7 @@ const ticketFields = z.object({
|
|
|
3197
3246
|
scopes: tags,
|
|
3198
3247
|
assigneeId: z.string().nullable(),
|
|
3199
3248
|
parentId: z.uuid().nullable(),
|
|
3249
|
+
epicId: z.uuid().nullable(),
|
|
3200
3250
|
automationReady: z.boolean(),
|
|
3201
3251
|
holdReason: z.string().trim().max(2e3).nullable(),
|
|
3202
3252
|
startAt: dueDate.nullable(),
|
|
@@ -3275,6 +3325,7 @@ const projectSchema = z.object({
|
|
|
3275
3325
|
key: z.string(),
|
|
3276
3326
|
name: z.string(),
|
|
3277
3327
|
description: z.string(),
|
|
3328
|
+
repositories: z.array(z.string()).default([]),
|
|
3278
3329
|
version,
|
|
3279
3330
|
archivedAt: timestamp.nullable().default(null),
|
|
3280
3331
|
createdAt: timestamp,
|
|
@@ -3304,7 +3355,8 @@ const projectImpactSchema = z.object({
|
|
|
3304
3355
|
copiedLabels: z.array(z.string()),
|
|
3305
3356
|
mergedLabels: z.array(z.string()),
|
|
3306
3357
|
subscriptions: count,
|
|
3307
|
-
invitations: count
|
|
3358
|
+
invitations: count,
|
|
3359
|
+
epicTickets: count.default(0)
|
|
3308
3360
|
}).nullable()
|
|
3309
3361
|
});
|
|
3310
3362
|
const workspaceImpactSchema = projectImpactSchema.omit({ transfer: true }).extend({
|
|
@@ -3315,7 +3367,8 @@ const workspaceImpactSchema = projectImpactSchema.omit({ transfer: true }).exten
|
|
|
3315
3367
|
})),
|
|
3316
3368
|
members: count,
|
|
3317
3369
|
invitations: count,
|
|
3318
|
-
labels: count
|
|
3370
|
+
labels: count,
|
|
3371
|
+
epics: count.default(0)
|
|
3319
3372
|
});
|
|
3320
3373
|
const deletedAccountName = "Deleted account";
|
|
3321
3374
|
const accountImpactSchema = z.object({
|
|
@@ -3337,6 +3390,7 @@ const accountImpactSchema = z.object({
|
|
|
3337
3390
|
});
|
|
3338
3391
|
const ticketSchema = z.object({
|
|
3339
3392
|
...ticketFields.shape,
|
|
3393
|
+
epicId: z.uuid().nullable().default(null),
|
|
3340
3394
|
startAt: dueDate.nullable().default(null),
|
|
3341
3395
|
stateId: z.uuid().nullable().default(null),
|
|
3342
3396
|
customFields: z.record(z.string(), customFieldValue).default({}),
|
|
@@ -3365,11 +3419,27 @@ const ticketSummarySchema = ticketSchema.pick({
|
|
|
3365
3419
|
scopes: true,
|
|
3366
3420
|
archived: true,
|
|
3367
3421
|
automationReady: true,
|
|
3422
|
+
epicId: true,
|
|
3368
3423
|
updatedAt: true,
|
|
3369
3424
|
version: true
|
|
3370
3425
|
});
|
|
3371
3426
|
const ticketView = { view: z.enum(["summary", "full"]).default("full") };
|
|
3372
3427
|
const ticketRow = z.union([ticketSchema, ticketSummarySchema]);
|
|
3428
|
+
const epicSchema = z.object({
|
|
3429
|
+
id: z.uuid(),
|
|
3430
|
+
workspaceId: z.uuid(),
|
|
3431
|
+
title: text.max(200),
|
|
3432
|
+
description: z.string(),
|
|
3433
|
+
archived: z.boolean(),
|
|
3434
|
+
version,
|
|
3435
|
+
createdBy: z.string(),
|
|
3436
|
+
createdAt: timestamp,
|
|
3437
|
+
updatedAt: timestamp
|
|
3438
|
+
});
|
|
3439
|
+
const epicRollupSchema = epicSchema.extend({
|
|
3440
|
+
openTickets: count,
|
|
3441
|
+
closedTickets: count
|
|
3442
|
+
});
|
|
3373
3443
|
const snapshotSchema = z.object({
|
|
3374
3444
|
project: projectSchema,
|
|
3375
3445
|
tickets: z.array(ticketSchema),
|
|
@@ -3445,6 +3515,11 @@ const attachmentSchema = z.object({
|
|
|
3445
3515
|
createdAt: timestamp
|
|
3446
3516
|
});
|
|
3447
3517
|
const stagedUploadSchema = attachmentSchema.omit({ ticketId: true }).extend({ expiresAt: timestamp });
|
|
3518
|
+
const uploadUrlSchema = z.object({
|
|
3519
|
+
uploadId: z.uuid(),
|
|
3520
|
+
url: z.url(),
|
|
3521
|
+
expiresAt: timestamp
|
|
3522
|
+
});
|
|
3448
3523
|
const httpUrl = z.url().regex(/^https?:\/\//i, "Use an HTTP or HTTPS URL");
|
|
3449
3524
|
const evidenceFields = z.object({
|
|
3450
3525
|
kind: z.enum([
|
|
@@ -3539,6 +3614,25 @@ const calendarFeedSchema = z.object({
|
|
|
3539
3614
|
createdAt: timestamp,
|
|
3540
3615
|
active: z.boolean()
|
|
3541
3616
|
});
|
|
3617
|
+
const publicBoardSchema = z.object({
|
|
3618
|
+
projectId: z.uuid(),
|
|
3619
|
+
enabled: z.boolean(),
|
|
3620
|
+
title: text.max(80),
|
|
3621
|
+
labels: tags,
|
|
3622
|
+
excludedLabels: tags,
|
|
3623
|
+
version,
|
|
3624
|
+
createdAt: timestamp,
|
|
3625
|
+
updatedAt: timestamp
|
|
3626
|
+
});
|
|
3627
|
+
const publicBoardTicketSchema = z.object({
|
|
3628
|
+
projectId: z.uuid(),
|
|
3629
|
+
key: z.string(),
|
|
3630
|
+
title: z.string(),
|
|
3631
|
+
kind: z.enum(kinds),
|
|
3632
|
+
status: z.enum(states),
|
|
3633
|
+
createdAt: timestamp,
|
|
3634
|
+
updatedAt: timestamp
|
|
3635
|
+
});
|
|
3542
3636
|
const eventSchema = z.object({
|
|
3543
3637
|
id: z.string().regex(/^\d+$/),
|
|
3544
3638
|
projectId: z.uuid(),
|
|
@@ -3922,6 +4016,8 @@ const eventLabels = labelTable([
|
|
|
3922
4016
|
"label.created",
|
|
3923
4017
|
"label.updated",
|
|
3924
4018
|
"label.deleted",
|
|
4019
|
+
"epic.created",
|
|
4020
|
+
"epic.updated",
|
|
3925
4021
|
"workflow.created",
|
|
3926
4022
|
"workflow.updated",
|
|
3927
4023
|
"workflow.deleted",
|
|
@@ -3955,6 +4051,7 @@ const eventLabels = labelTable([
|
|
|
3955
4051
|
"resource.removed",
|
|
3956
4052
|
"calendar_feed.created",
|
|
3957
4053
|
"calendar_feed.revoked",
|
|
4054
|
+
"public_board.updated",
|
|
3958
4055
|
"project.created",
|
|
3959
4056
|
"project.updated",
|
|
3960
4057
|
"project.archived",
|
|
@@ -4210,21 +4307,25 @@ const page = (item) => z.object({
|
|
|
4210
4307
|
items: z.array(item),
|
|
4211
4308
|
total: z.number().int().nonnegative()
|
|
4212
4309
|
});
|
|
4213
|
-
const
|
|
4310
|
+
const fileMetadata = {
|
|
4214
4311
|
name: text.max(255).refine((value) => {
|
|
4215
4312
|
for (const character of value) if (character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127) return false;
|
|
4216
4313
|
return true;
|
|
4217
4314
|
}, "Use a filename without control characters"),
|
|
4218
|
-
contentType: z.string().max(120).regex(/^[a-z0-9][a-z0-9.+-]*\/[a-z0-9][a-z0-9.+-]*$/i).default("application/octet-stream")
|
|
4315
|
+
contentType: z.string().max(120).regex(/^[a-z0-9][a-z0-9.+-]*\/[a-z0-9][a-z0-9.+-]*$/i).default("application/octet-stream")
|
|
4316
|
+
};
|
|
4317
|
+
const fileFields = {
|
|
4318
|
+
...fileMetadata,
|
|
4219
4319
|
data: z.string().max(Math.ceil(attachmentLimit / 3) * 4)
|
|
4220
4320
|
};
|
|
4221
|
-
const op = (input, output, method, path, scope, description) => ({
|
|
4321
|
+
const op = (input, output, method, path, scope, description, options = {}) => ({
|
|
4222
4322
|
input,
|
|
4223
4323
|
output,
|
|
4224
4324
|
method,
|
|
4225
4325
|
path,
|
|
4226
4326
|
scope,
|
|
4227
|
-
description
|
|
4327
|
+
description,
|
|
4328
|
+
...options
|
|
4228
4329
|
});
|
|
4229
4330
|
const operations = {
|
|
4230
4331
|
list_workspaces: op(z.object({}), z.array(workspaceSchema), "GET", "/workspaces", "read", "List accessible workspaces and the current membership role."),
|
|
@@ -4290,6 +4391,37 @@ const operations = {
|
|
|
4290
4391
|
userId: z.string().min(1),
|
|
4291
4392
|
confirmEmail: z.string().trim().max(320)
|
|
4292
4393
|
}).strict(), okSchema, "DELETE", "/accounts/{userId}", "write", "Delete an account: remove its sign-in methods, sessions, API keys, OAuth grants, devices, notification settings, memberships, and photo, release its claims, cancel its imports, and replace its name and email with a `Deleted account` tombstone. Tickets, comments, evidence, and activity keep the account ID. Only the account itself or an instance administrator, signed in within the last 15 minutes, can delete; `confirmEmail` must repeat the account's email. Fails with `workspace_owner` while it owns a workspace and `last_administrator` for the last active instance or workspace administrator."),
|
|
4394
|
+
list_epics: op(z.object({
|
|
4395
|
+
workspaceId: z.uuid(),
|
|
4396
|
+
archived: ticketFilter.shape.archived,
|
|
4397
|
+
...pagination
|
|
4398
|
+
}), page(epicRollupSchema), "GET", "/workspaces/{workspaceId}/epics", "read", "List the workspace's Epics by title, each with open and closed ticket counts from the projects you can read, leaving out archived tickets. Archived Epics are left out unless `archived` is `include` or `only`."),
|
|
4399
|
+
get_epic: op(z.object({
|
|
4400
|
+
workspaceId: z.uuid(),
|
|
4401
|
+
epicId: z.uuid()
|
|
4402
|
+
}), epicRollupSchema, "GET", "/workspaces/{workspaceId}/epics/{epicId}", "read", "Read one Epic with the same ticket counts as list_epics."),
|
|
4403
|
+
create_epic: op(z.object({
|
|
4404
|
+
...mutation,
|
|
4405
|
+
workspaceId: z.uuid(),
|
|
4406
|
+
title: epicSchema.shape.title,
|
|
4407
|
+
description: z.string().max(1e5).default("")
|
|
4408
|
+
}).strict(), epicSchema, "POST", "/workspaces/{workspaceId}/epics", "write", "Create an Epic that groups tickets from any project in the workspace. Add a ticket by setting `epicId` through create_ticket, update_ticket, or bulk_update_tickets. Membership has no effect on readiness, eligibility, or claims. Reuse requestId when retrying this write."),
|
|
4409
|
+
update_epic: op(z.object({
|
|
4410
|
+
...mutation,
|
|
4411
|
+
workspaceId: z.uuid(),
|
|
4412
|
+
epicId: z.uuid(),
|
|
4413
|
+
expectedVersion: version,
|
|
4414
|
+
title: epicSchema.shape.title.optional(),
|
|
4415
|
+
description: z.string().max(1e5).optional(),
|
|
4416
|
+
archived: z.boolean().optional()
|
|
4417
|
+
}).strict().refine(({ title, description, archived }) => title !== void 0 || description !== void 0 || archived !== void 0, "Provide at least one change"), epicSchema, "PATCH", "/workspaces/{workspaceId}/epics/{epicId}", "write", "Change an Epic's title or description, archive it, or restore it with `archived: false`. An archived Epic keeps its members and refuses new ones. There is no deletion. Reuse requestId when retrying this write."),
|
|
4418
|
+
list_epic_tickets: op(z.object({
|
|
4419
|
+
workspaceId: z.uuid(),
|
|
4420
|
+
epicId: z.uuid(),
|
|
4421
|
+
archived: ticketFilter.shape.archived,
|
|
4422
|
+
...pagination,
|
|
4423
|
+
...ticketView
|
|
4424
|
+
}), page(ticketRow).extend({ restrictedCount: count }), "GET", "/workspaces/{workspaceId}/epics/{epicId}/tickets", "read", "List an Epic's tickets across projects, oldest first. Only tickets of projects you can read appear; `restrictedCount` counts the rest without naming them. With `view: \"summary\"`, rows omit the description and acceptance criteria."),
|
|
4293
4425
|
list_workspace_labels: op(z.object({ workspaceId: z.uuid() }), z.array(workspaceCatalogLabel), "GET", "/workspaces/{workspaceId}/labels", "read", "List the workspace's shared labels with ticket counts from the projects you can read, including archived tickets."),
|
|
4294
4426
|
create_workspace_label: op(z.object({
|
|
4295
4427
|
...mutation,
|
|
@@ -4312,7 +4444,7 @@ const operations = {
|
|
|
4312
4444
|
workspaceId: z.uuid(),
|
|
4313
4445
|
labelId: z.uuid(),
|
|
4314
4446
|
expectedVersion: version
|
|
4315
|
-
}).strict(), okSchema, "DELETE", "/workspaces/{workspaceId}/labels/{labelId}", "admin", "Delete a workspace label and remove it from tickets and saved-view filters in every project of the workspace atomically."),
|
|
4447
|
+
}).strict(), okSchema, "DELETE", "/workspaces/{workspaceId}/labels/{labelId}", "admin", "Delete a workspace label and remove it from tickets and saved-view filters in every project of the workspace atomically. Fails with `label_in_use` while an enabled public board in the workspace excludes it."),
|
|
4316
4448
|
list_invitations: op(z.object({ workspaceId: z.uuid() }), z.array(invitationSchema), "GET", "/workspaces/{workspaceId}/invitations", "admin", "List the workspace's pending and expired invitations."),
|
|
4317
4449
|
create_invitation: op(z.object({
|
|
4318
4450
|
workspaceId: z.uuid(),
|
|
@@ -4414,8 +4546,9 @@ const operations = {
|
|
|
4414
4546
|
publicReadEnabled: z.boolean().optional(),
|
|
4415
4547
|
name: text.max(120),
|
|
4416
4548
|
description: z.string().max(5e3),
|
|
4417
|
-
key: projectKey.optional()
|
|
4418
|
-
|
|
4549
|
+
key: projectKey.optional(),
|
|
4550
|
+
repositories: projectRepositories.optional()
|
|
4551
|
+
}).strict(), projectSchema, "PATCH", "/projects/{projectId}", "admin", "Update project details. A new `key` re-keys every ticket of the project; the previous prefix stays reserved for the project, and the project and every key issued under it keep resolving. `publicReadEnabled` shares or stops sharing the project's read-only public view (see the public operations). `repositories` replaces the project's linked git remotes (at most 20), and any `projectId` then accepts one of them; within a workspace only one unarchived project can hold a remote."),
|
|
4419
4552
|
move_project: op(projectInput.extend({
|
|
4420
4553
|
...mutation,
|
|
4421
4554
|
expectedVersion: version,
|
|
@@ -4426,18 +4559,18 @@ const operations = {
|
|
|
4426
4559
|
expectedVersion: version,
|
|
4427
4560
|
archived: z.boolean()
|
|
4428
4561
|
}).strict(), projectSchema, "POST", "/projects/{projectId}/archive", "admin", "Archive a project, or restore it with `archived: false`. An archived project keeps its tickets and history but leaves default project lists, search, My work, calendar feeds, public links, reminders, and agent queues, and its tickets and settings are read only. Fails with `claim_conflict` while any ticket is claimed."),
|
|
4429
|
-
get_project_impact: op(projectInput.extend({ workspaceId: z.uuid().optional() }), projectImpactSchema, "GET", "/projects/{projectId}/impact", "admin", "Preview what deleting the project removes, or with `workspaceId`, what moving it to that workspace changes: who loses or gains access, labels copied or merged, follows and invitations dropped, and blockers such as claimed tickets."),
|
|
4562
|
+
get_project_impact: op(projectInput.extend({ workspaceId: z.uuid().optional() }), projectImpactSchema, "GET", "/projects/{projectId}/impact", "admin", "Preview what deleting the project removes, or with `workspaceId`, what moving it to that workspace changes: who loses or gains access, labels copied or merged, follows and invitations dropped, tickets leaving their Epic, and blockers such as claimed tickets."),
|
|
4430
4563
|
transfer_project: op(projectInput.extend({
|
|
4431
4564
|
...mutation,
|
|
4432
4565
|
expectedVersion: version,
|
|
4433
4566
|
workspaceId: z.uuid()
|
|
4434
|
-
}).strict(), projectSchema, "POST", "/projects/{projectId}/transfer", "admin", "Move a project with its tickets, keys, and links to another workspace. Requires administration of both workspaces and applies the reconciliation `get_project_impact` previews in one transaction
|
|
4567
|
+
}).strict(), projectSchema, "POST", "/projects/{projectId}/transfer", "admin", "Move a project with its tickets, keys, and links to another workspace. Requires administration of both workspaces and applies the reconciliation `get_project_impact` previews in one transaction, including clearing each ticket's `epicId`; blockers fail with their code."),
|
|
4435
4568
|
delete_project: op(projectInput.extend({
|
|
4436
4569
|
...mutation,
|
|
4437
4570
|
expectedVersion: version,
|
|
4438
4571
|
confirmKey: z.string().max(20)
|
|
4439
|
-
}).strict(), okSchema, "DELETE", "/projects/{projectId}", "admin", "Permanently delete a project's tickets, comments, attachments, labels, feeds, and destinations. `confirmKey` must repeat the project prefix. Fails with `claim_conflict` while any ticket is claimed. The prefix stays reserved and the activity history and deletion events are kept."),
|
|
4440
|
-
export_project: op(projectInput, ticketExportSchema, "GET", "/projects/{projectId}/export", "read", "Export the project's tickets, archived ones included, as a versioned `kanvil.tickets` bundle: workflow states, custom fields, labels, relationships, comments, evidence, and links, with the people they name. Records reference each other by source ID. `completeness.omitted` counts what the bundle leaves out, such as attachments and activity history."),
|
|
4572
|
+
}).strict(), okSchema, "DELETE", "/projects/{projectId}", "admin", "Permanently delete a project's tickets, comments, attachments, labels, feeds, public board, and destinations. `confirmKey` must repeat the project prefix. Fails with `claim_conflict` while any ticket is claimed. The prefix stays reserved and the activity history and deletion events are kept."),
|
|
4573
|
+
export_project: op(projectInput, ticketExportSchema, "GET", "/projects/{projectId}/export", "read", "Export the project's tickets, archived ones included, as a versioned `kanvil.tickets` bundle: workflow states, custom fields, labels, relationships, comments, evidence, and links, with the people they name. Records reference each other by source ID. `completeness.omitted` counts what the bundle leaves out, such as attachments, Epics, and activity history."),
|
|
4441
4574
|
preview_import: op(z.object({
|
|
4442
4575
|
...mutation,
|
|
4443
4576
|
workspaceId: z.uuid(),
|
|
@@ -4479,13 +4612,15 @@ const operations = {
|
|
|
4479
4612
|
}).strict(), okSchema, "POST", "/projects/{projectId}/members", "admin", "Add or update an existing user's membership with a seeded role or a custom role of the project's workspace, or remove it with a null role. Needs `member.manage`; the role, and any role the person holds now, must grant nothing the caller lacks."),
|
|
4480
4613
|
list_tickets: op(projectInput.extend({
|
|
4481
4614
|
...ticketFilter.shape,
|
|
4615
|
+
sort: listSort,
|
|
4482
4616
|
...pagination,
|
|
4483
4617
|
relatedTo: id.optional(),
|
|
4484
4618
|
stateId: z.uuid().optional(),
|
|
4619
|
+
epicId: z.uuid().optional(),
|
|
4485
4620
|
scheduledFrom: timestamp.optional(),
|
|
4486
4621
|
scheduledTo: timestamp.optional(),
|
|
4487
4622
|
...ticketView
|
|
4488
|
-
}).superRefine(rejectConflicts), page(ticketRow), "GET", "/projects/{projectId}/tickets", "read", "Search tickets with server-side filters, stable ordering, and pagination. Each property filter takes one value or a repeated list (any of them); its `Not` twin excludes values.
|
|
4623
|
+
}).superRefine(rejectConflicts), page(ticketRow), "GET", "/projects/{projectId}/tickets", "read", "Search tickets with server-side filters, stable ordering, and pagination. Each property filter takes one value or a repeated list (any of them); its `Not` twin excludes values. `q` keeps only tickets whose key, title, description, acceptance criteria, labels, or scopes contain its text as written, or whose key or ID it names exactly; double-quoted segments are exact phrases that must each appear on word boundaries, so `\"IME\"` does not match `time` while unquoted `IME` does. With `q` and no `sort`, results order by relevance as in search_tickets; otherwise by `sort` (default `priority`). With `view: \"summary\"`, rows omit the description and acceptance criteria."),
|
|
4489
4624
|
list_ticket_labels: op(projectInput.extend({ archived: ticketFilter.shape.archived }), z.array(z.object({
|
|
4490
4625
|
name: z.string(),
|
|
4491
4626
|
count: z.number().int().nonnegative()
|
|
@@ -4507,7 +4642,7 @@ const operations = {
|
|
|
4507
4642
|
...mutation,
|
|
4508
4643
|
labelId: z.uuid(),
|
|
4509
4644
|
expectedVersion: version
|
|
4510
|
-
}).strict(), okSchema, "DELETE", "/projects/{projectId}/labels/{labelId}", "admin", "Delete a project label and remove it from tickets and saved-view filters atomically."),
|
|
4645
|
+
}).strict(), okSchema, "DELETE", "/projects/{projectId}/labels/{labelId}", "admin", "Delete a project label and remove it from tickets and saved-view filters atomically. Fails with `label_in_use` while the project's enabled public board excludes it."),
|
|
4511
4646
|
list_label_colors: op(z.object({}), z.array(z.object({
|
|
4512
4647
|
value: hexColor,
|
|
4513
4648
|
createdAt: timestamp
|
|
@@ -4579,7 +4714,7 @@ const operations = {
|
|
|
4579
4714
|
claim: claimSchema.nullable(),
|
|
4580
4715
|
subscribed: z.boolean(),
|
|
4581
4716
|
canReleaseClaim: z.boolean()
|
|
4582
|
-
}), "GET", "/projects/{projectId}/tickets/{ticketId}", "read", "Read a ticket by ID or key, with readiness and ownership."),
|
|
4717
|
+
}), "GET", "/projects/{projectId}/tickets/{ticketId}", "read", "Read a ticket by ID or key, with readiness and ownership.", { mcp: "core" }),
|
|
4583
4718
|
create_ticket: op(projectInput.extend({
|
|
4584
4719
|
...mutation,
|
|
4585
4720
|
title: ticketFields.shape.title,
|
|
@@ -4596,19 +4731,20 @@ const operations = {
|
|
|
4596
4731
|
scopes: tags.default([]),
|
|
4597
4732
|
assigneeId: z.string().nullable().default(null),
|
|
4598
4733
|
parentId: z.uuid().nullable().default(null),
|
|
4734
|
+
epicId: z.uuid().nullable().default(null),
|
|
4599
4735
|
automationReady: z.boolean().default(false),
|
|
4600
4736
|
holdReason: z.string().trim().max(2e3).nullable().default(null),
|
|
4601
4737
|
archived: z.boolean().default(false),
|
|
4602
4738
|
uploads: z.array(z.uuid()).max(50).default([]),
|
|
4603
4739
|
customFields: customFieldValues.default({})
|
|
4604
|
-
}).strict(), ticketSchema, "POST", "/projects/{projectId}/tickets", "write", "Create a ticket. `customFields` maps field IDs from list_custom_fields to values; omitted fields take their default, and a required field must end with a value. `uploads` lists your staged inline uploads in this project; they become its attachments in the same write, or nothing is created. Reuse requestId when retrying this write."),
|
|
4605
|
-
duplicate_ticket: op(ticketInput.extend(mutation).strict(), ticketSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/duplicate", "write", "Copy a ticket into a new To do ticket in the same project, linked to the original by a clones relationship. Copies requirements, properties, labels, scopes, and
|
|
4740
|
+
}).strict(), ticketSchema, "POST", "/projects/{projectId}/tickets", "write", "Create a ticket. `customFields` maps field IDs from list_custom_fields to values; omitted fields take their default, and a required field must end with a value. `uploads` lists your staged inline uploads in this project; they become its attachments in the same write, or nothing is created. Reuse requestId when retrying this write.", { mcp: "core" }),
|
|
4741
|
+
duplicate_ticket: op(ticketInput.extend(mutation).strict(), ticketSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/duplicate", "write", "Copy a ticket into a new To do ticket in the same project, linked to the original by a clones relationship. Copies requirements, properties, labels, scopes, parent, and an Epic the caller can still add to; resets agent readiness and never copies claims, comments, evidence, attachments, or children. Reuse requestId when retrying this write."),
|
|
4606
4742
|
update_ticket: op(ticketInput.extend({
|
|
4607
4743
|
...mutation,
|
|
4608
4744
|
expectedVersion: version,
|
|
4609
4745
|
changes: ticketChanges,
|
|
4610
4746
|
claimId: z.uuid().optional()
|
|
4611
|
-
}).strict(), ticketSchema, "PATCH", "/projects/{projectId}/tickets/{ticketId}", "write", "Update a ticket with optimistic concurrency. Worker completion requires its active claim. `changes.customFields` sets only the named fields; null clears one."),
|
|
4747
|
+
}).strict(), ticketSchema, "PATCH", "/projects/{projectId}/tickets/{ticketId}", "write", "Update a ticket with optimistic concurrency. Worker completion requires its active claim. `changes.customFields` sets only the named fields; null clears one.", { mcp: "core" }),
|
|
4612
4748
|
update_ticket_labels: op(ticketInput.extend({
|
|
4613
4749
|
...mutation,
|
|
4614
4750
|
expectedVersion: version,
|
|
@@ -4638,7 +4774,7 @@ const operations = {
|
|
|
4638
4774
|
resolve_ticket: op(z.object({ ticketId: id }).strict(), z.object({
|
|
4639
4775
|
ticket: ticketSchema,
|
|
4640
4776
|
project: projectRef
|
|
4641
|
-
}), "GET", "/tickets/resolve", "read", "Find a ticket by ID, current key, or any earlier key from a prefix change or a move, across every accessible project. An unknown or inaccessible ticket reads as not found."),
|
|
4777
|
+
}), "GET", "/tickets/resolve", "read", "Find a ticket by ID, current key, or any earlier key from a prefix change or a move, across every accessible project. An unknown or inaccessible ticket reads as not found.", { mcp: "core" }),
|
|
4642
4778
|
bulk_update_tickets: op(projectInput.extend({
|
|
4643
4779
|
...mutation,
|
|
4644
4780
|
tickets: claimedTicketSelection,
|
|
@@ -4661,7 +4797,7 @@ const operations = {
|
|
|
4661
4797
|
ticket: ticketRow,
|
|
4662
4798
|
project: projectRef,
|
|
4663
4799
|
snippet: z.string().optional()
|
|
4664
|
-
})) }), "GET", "/tickets/search", "read", "Search unarchived tickets by key or text across every accessible project, optionally within one workspace. Results order by exact key match, then relevance, then recency. Double-quoted segments of `q` match as exact phrases
|
|
4800
|
+
})) }), "GET", "/tickets/search", "read", "Search unarchived tickets by key or text across every accessible project, optionally within one workspace. Results order by exact key match, then relevance, then recency. Double-quoted segments of `q` match as exact phrases on word boundaries, so `\"IME\"` does not match `time`. With `view: \"summary\"`, each ticket is a compact row and the result carries a `snippet` of the matching text.", { mcp: "core" }),
|
|
4665
4801
|
search_workspace: op(z.object({
|
|
4666
4802
|
q: z.string().trim().min(1).max(200),
|
|
4667
4803
|
workspaceId: z.uuid().optional(),
|
|
@@ -4676,19 +4812,19 @@ const operations = {
|
|
|
4676
4812
|
counts: z.record(z.enum(searchResultTypes), z.number().int().nonnegative()),
|
|
4677
4813
|
engine: z.enum(["postgres", "index"]),
|
|
4678
4814
|
indexing: z.boolean()
|
|
4679
|
-
}), "GET", "/search", "read", "Search accessible tickets, projects, comments, and activity by text or ticket key, optionally within one workspace or one result type. Property filters restrict results to matching tickets. Results order by exact key match, then type, then ticket relevance, then recency. Double-quoted segments of `q` match ticket text as exact phrases.
|
|
4815
|
+
}), "GET", "/search", "read", "Search accessible tickets, projects, comments, and activity by text or ticket key, optionally within one workspace or one result type. Property filters restrict results to matching tickets. Results order by exact key match, then type, then ticket relevance, then recency. Double-quoted segments of `q` match ticket text as exact phrases on word boundaries. With `view: \"summary\"`, ticket results carry their summary row and each result a `snippet` of the matching text."),
|
|
4680
4816
|
list_my_work: op(z.object({
|
|
4681
4817
|
workspaceId: z.uuid().optional(),
|
|
4682
4818
|
q: ticketFilter.shape.q,
|
|
4683
|
-
sort:
|
|
4819
|
+
sort: listSort,
|
|
4684
4820
|
order: ticketFilter.shape.order,
|
|
4685
4821
|
activity: z.coerce.number().int().min(0).max(50).default(0),
|
|
4686
4822
|
...pagination
|
|
4687
|
-
}).strict(), page(ticketSchema).extend({ activity: z.array(eventSchema).default([]) }), "GET", "/my-work", "read", "List open, unarchived tickets assigned to the current identity across every accessible project, optionally within one workspace, with the most recent accessible activity when `activity` is set."),
|
|
4823
|
+
}).strict(), page(ticketSchema).extend({ activity: z.array(eventSchema).default([]) }), "GET", "/my-work", "read", "List open, unarchived tickets assigned to the current identity across every accessible project, optionally within one workspace, with the most recent accessible activity when `activity` is set. `q` and `sort` work as in list_tickets.", { mcp: "core" }),
|
|
4688
4824
|
list_eligible_tickets: op(projectInput.extend(pagination), page(z.object({
|
|
4689
4825
|
ticket: ticketSchema,
|
|
4690
4826
|
readiness: readinessSchema
|
|
4691
|
-
})), "GET", "/projects/{projectId}/eligibility", "read", "Inspect eligibility and explicit blocking reasons for open tickets."),
|
|
4827
|
+
})), "GET", "/projects/{projectId}/eligibility", "read", "Inspect eligibility and explicit blocking reasons for open tickets.", { mcp: "core" }),
|
|
4692
4828
|
list_relations: op(ticketInput, z.array(relationSchema), "GET", "/projects/{projectId}/tickets/{ticketId}/relations", "read", "List incoming and outgoing ticket relationships with each side's key, title, and project. A ticket in a project the caller cannot read appears by ID and key alone, with `restricted: true`."),
|
|
4693
4829
|
add_relation: op(ticketInput.extend({
|
|
4694
4830
|
...mutation,
|
|
@@ -4704,7 +4840,7 @@ const operations = {
|
|
|
4704
4840
|
add_comment: op(ticketInput.extend({
|
|
4705
4841
|
...mutation,
|
|
4706
4842
|
body: text.max(5e4)
|
|
4707
|
-
}).strict(), commentSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/comments", "write", "Add a comment with authenticated attribution. Mention a project member with `[@Name](mention:<user id>)` from list_members; members who can read the project are notified."),
|
|
4843
|
+
}).strict(), commentSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/comments", "write", "Add a comment with authenticated attribution. Mention a project member with `[@Name](mention:<user id>)` from list_members; members who can read the project are notified.", { mcp: "core" }),
|
|
4708
4844
|
update_comment: op(projectInput.extend({
|
|
4709
4845
|
...mutation,
|
|
4710
4846
|
commentId: z.uuid(),
|
|
@@ -4720,11 +4856,16 @@ const operations = {
|
|
|
4720
4856
|
upload_attachment: op(ticketInput.extend({
|
|
4721
4857
|
...mutation,
|
|
4722
4858
|
...fileFields
|
|
4723
|
-
}).strict(), attachmentSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/attachments", "write", "Upload up to 10 MB of base64-encoded bytes. The original filename is metadata, never a filesystem path. An intake credential can attach up to 10 files to a quarantined ticket it created, within 1 GB across its quarantined tickets."),
|
|
4859
|
+
}).strict(), attachmentSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/attachments", "write", "Upload up to 10 MB of base64-encoded bytes. The original filename is metadata, never a filesystem path. An intake credential can attach up to 10 files to a quarantined ticket it created, within 1 GB across its quarantined tickets. For a file on disk, use create_upload_url instead of inlining it."),
|
|
4724
4860
|
stage_upload: op(projectInput.extend({
|
|
4725
4861
|
...mutation,
|
|
4726
4862
|
...fileFields
|
|
4727
|
-
}).strict(), stagedUploadSchema, "POST", "/projects/{projectId}/uploads", "write", "Stage up to 10 MB of base64-encoded bytes for a ticket that does not exist yet. Only you can read it, at its attachment content address, until create_ticket claims it; unclaimed uploads expire after 24 hours."),
|
|
4863
|
+
}).strict(), stagedUploadSchema, "POST", "/projects/{projectId}/uploads", "write", "Stage up to 10 MB of base64-encoded bytes for a ticket that does not exist yet. Only you can read it, at its attachment content address, until create_ticket claims it; unclaimed uploads expire after 24 hours. For a file on disk, use create_upload_url instead of inlining it."),
|
|
4864
|
+
create_upload_url: op(projectInput.extend({
|
|
4865
|
+
...fileMetadata,
|
|
4866
|
+
ticketId: id.optional(),
|
|
4867
|
+
size: z.number().int().nonnegative().max(attachmentLimit)
|
|
4868
|
+
}).strict(), uploadUrlSchema, "POST", "/projects/{projectId}/upload-urls", "write", "Get a single-use URL for one file of up to 10 MB, so its bytes never pass through the tool call. PUT exactly `size` bytes to `url` within 15 minutes, for example `curl --upload-file <path> <url>`; the URL needs no other credential and reads nothing. With `ticketId` the file becomes that ticket's attachment; without it, it is a staged upload that create_ticket `uploads` claims by `uploadId`."),
|
|
4728
4869
|
discard_upload: op(projectInput.extend({
|
|
4729
4870
|
...mutation,
|
|
4730
4871
|
uploadId: z.uuid()
|
|
@@ -4739,7 +4880,7 @@ const operations = {
|
|
|
4739
4880
|
...mutation,
|
|
4740
4881
|
...evidenceFields.shape,
|
|
4741
4882
|
claimId: z.uuid().optional()
|
|
4742
|
-
}).strict(), evidenceSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/evidence", "write", "Attach evidence with its commit and provenance."),
|
|
4883
|
+
}).strict(), evidenceSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/evidence", "write", "Attach evidence with its commit and provenance.", { mcp: "core" }),
|
|
4743
4884
|
list_time_entries: op(ticketInput.extend(pagination), page(timeEntrySchema), "GET", "/projects/{projectId}/tickets/{ticketId}/time-entries", "read", "List observed time entries, newest first. Running entries have null endAt and durationMs."),
|
|
4744
4885
|
get_time_entry: op(ticketInput.extend({ timeEntryId: z.uuid() }), timeEntrySchema, "GET", "/projects/{projectId}/tickets/{ticketId}/time-entries/{timeEntryId}", "read", "Read one attributed time entry within its current ticket and project."),
|
|
4745
4886
|
create_time_entry: op(ticketInput.extend({
|
|
@@ -4785,18 +4926,18 @@ const operations = {
|
|
|
4785
4926
|
claim_ticket: op(ticketInput.extend({
|
|
4786
4927
|
...mutation,
|
|
4787
4928
|
workerId: text.max(200),
|
|
4788
|
-
expectedVersion: version,
|
|
4929
|
+
expectedVersion: version.optional(),
|
|
4789
4930
|
leaseSeconds: claimLeaseSecondsSchema.optional()
|
|
4790
|
-
}).strict(), claimSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/claim", "claim", "Atomically claim ready work. Expired claims require explicit recovery."),
|
|
4931
|
+
}).strict(), claimSchema.extend({ ticket: ticketSchema }), "POST", "/projects/{projectId}/tickets/{ticketId}/claim", "claim", "Atomically claim ready work and return the claim with the claimed ticket. expectedVersion is optional; readiness always applies. Expired claims require explicit recovery.", { mcp: "core" }),
|
|
4791
4932
|
renew_claim: op(ticketInput.extend({
|
|
4792
4933
|
...mutation,
|
|
4793
4934
|
claimId: z.uuid(),
|
|
4794
4935
|
leaseSeconds: claimLeaseSecondsSchema.optional()
|
|
4795
|
-
}).strict(), claimSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/claim/renew", "claim", "Renew the caller's unexpired claim."),
|
|
4936
|
+
}).strict(), claimSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/claim/renew", "claim", "Renew the caller's unexpired claim.", { mcp: "core" }),
|
|
4796
4937
|
release_claim: op(ticketInput.extend({
|
|
4797
4938
|
...mutation,
|
|
4798
4939
|
claimId: z.uuid()
|
|
4799
|
-
}).strict(), okSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/claim/release", "claim", "Release the caller's claim."),
|
|
4940
|
+
}).strict(), okSchema, "POST", "/projects/{projectId}/tickets/{ticketId}/claim/release", "claim", "Release the caller's claim.", { mcp: "core" }),
|
|
4800
4941
|
recover_claim: op(ticketInput.extend({
|
|
4801
4942
|
...mutation,
|
|
4802
4943
|
claimId: z.uuid().optional(),
|
|
@@ -4829,6 +4970,18 @@ const operations = {
|
|
|
4829
4970
|
...mutation,
|
|
4830
4971
|
feedId: z.uuid()
|
|
4831
4972
|
}).strict(), okSchema, "DELETE", "/projects/{projectId}/calendar-feeds/{feedId}", "admin", "Revoke a calendar feed. Its link stops working immediately."),
|
|
4973
|
+
get_project_board: op(projectInput, z.object({
|
|
4974
|
+
board: publicBoardSchema.nullable(),
|
|
4975
|
+
tickets: z.array(publicBoardTicketSchema)
|
|
4976
|
+
}), "GET", "/projects/{projectId}/public-board", "admin", "Read the project's public board, or null, and the tickets it selects exactly as it publishes them, even while it is disabled."),
|
|
4977
|
+
update_project_board: op(projectInput.extend({
|
|
4978
|
+
...mutation,
|
|
4979
|
+
expectedVersion: version.nullable(),
|
|
4980
|
+
enabled: z.boolean(),
|
|
4981
|
+
title: text.max(80),
|
|
4982
|
+
labels: tags,
|
|
4983
|
+
excludedLabels: tags
|
|
4984
|
+
}).strict(), publicBoardSchema, "PATCH", "/projects/{projectId}/public-board", "admin", "Create or replace the project's public board. It publishes the key, title, kind, status, and dates of unarchived, unheld tickets that carry any of `labels` and none of `excludedLabels`. Enabling requires at least one label."),
|
|
4832
4985
|
list_views: op(projectInput, z.array(z.object({
|
|
4833
4986
|
id: z.uuid(),
|
|
4834
4987
|
name: z.string(),
|
|
@@ -5044,6 +5197,8 @@ const permissionOverrides = {
|
|
|
5044
5197
|
list_calendar_feeds: "sharing.manage",
|
|
5045
5198
|
create_calendar_feed: "sharing.manage",
|
|
5046
5199
|
revoke_calendar_feed: "sharing.manage",
|
|
5200
|
+
get_project_board: "sharing.manage",
|
|
5201
|
+
update_project_board: "sharing.manage",
|
|
5047
5202
|
update_project: null
|
|
5048
5203
|
};
|
|
5049
5204
|
function operationPermission(name) {
|
|
@@ -5084,4 +5239,4 @@ function parseOutput(name, value) {
|
|
|
5084
5239
|
return operations[name].output.parse(value);
|
|
5085
5240
|
}
|
|
5086
5241
|
//#endregion
|
|
5087
|
-
export {
|
|
5242
|
+
export { parseCustomFieldFilter as $, filterList as A, ticketImportSchema as At, isPublicOperation as B, instanceUpdateSchema as Bt, destinationKinds as C, stagedUploadLifetime as Ct, eventLabels as D, streamErrorSchema as Dt, eventCategories as E, states as Et, importSecretFields as F, workflowOrder as Ft, notificationIdsInput as G, searchSettingsSchema as Gt, memberSchema as H, photoResultSchema as Ht, invitationStatuses as I, workspaceSchema as It, notificationReadInput as J, notificationPreferencesInput as K, isHexColor as L, adminInstanceSchema as Lt, hasAdminRole as M, ticketSchema as Mt, importLimits as N, ticketSummarySchema as Nt, eventSchema as O, ticketExportFormat as Ot, importProviderDefinitions as P, uploadUrlLifetime as Pt, operations as Q, isOperation as R, authenticationPolicySchema as Rt, destinationEventKeys as S, snapshotSchema as St, errorSchema as T, stateLabels as Tt, mentionScheme as U, s3SettingsSchema as Ut, isSeededRole as V, photoInputSchema as Vt, normalizeRemote as W, searchAuthentication as Wt, okSchema as X, notificationsSchema as Y, operationPermission as Z, defaultOrder as _, roles as _t, channelRouteAllows as a, priorities as at, deliverySecretFields as b, seededPermissions as bt, claimSchema as c, publicOperations as ct, credentialProjectsSchema as d, repositoryProviderDefinitions as dt, parseOutput as et, customFieldFilterTokens as f, repositoryProviders as ft, defaultClaimMaxLeaseSeconds as g, roleLabels as gt, customFieldValueSchema as h, roleCategory as ht, channelKinds as i, permissions as it, formatEvent as j, ticketPredicates as jt, filterKey as k, ticketFilter as kt, closedStates as l, pushDeviceInput as lt, customFieldValue as m, resourceProvider as mt, attachmentLimit as n, permissionLabels as nt, checkpointSchema as o, priorityLabels as ot, customFieldSchema as p, repositorySecretFields as pt, notificationPreferencesSchema as q, attachmentSchema as r, permissionScope as rt, claimLeaseSecondsSchema as s, projectSchema as st, ApiError as t, parsePublicOutput as tt, credentialInput as u, remoteKinds as ut, defaultWorkflow as v, scopes as vt, difficulties as w, stagedUploadSchema as wt, deliverySettingsSchema as x, signInMethodLabels as xt, deletedAccountName as y, searchResultTypes as yt, isPresetColor as z, brokerSettingsSchema as zt };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
CREATE TABLE "upload_urls" (
|
|
2
|
+
"id" uuid PRIMARY KEY NOT NULL,
|
|
3
|
+
"project_id" uuid NOT NULL,
|
|
4
|
+
"ticket_id" uuid,
|
|
5
|
+
"actor_id" text NOT NULL,
|
|
6
|
+
"credential" jsonb,
|
|
7
|
+
"name" text NOT NULL,
|
|
8
|
+
"content_type" text NOT NULL,
|
|
9
|
+
"size" integer NOT NULL,
|
|
10
|
+
"token_hash" text NOT NULL,
|
|
11
|
+
"expires_at" timestamp with time zone NOT NULL,
|
|
12
|
+
"used_at" timestamp with time zone,
|
|
13
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
14
|
+
CONSTRAINT "upload_urls_token_hash_unique" UNIQUE("token_hash")
|
|
15
|
+
);
|
|
16
|
+
--> statement-breakpoint
|
|
17
|
+
ALTER TABLE "upload_urls" ADD CONSTRAINT "upload_urls_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
18
|
+
ALTER TABLE "upload_urls" ADD CONSTRAINT "upload_urls_ticket_id_tickets_id_fk" FOREIGN KEY ("ticket_id") REFERENCES "public"."tickets"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
19
|
+
ALTER TABLE "upload_urls" ADD CONSTRAINT "upload_urls_actor_id_user_id_fk" FOREIGN KEY ("actor_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
20
|
+
CREATE INDEX "upload_urls_expires" ON "upload_urls" USING btree ("expires_at");
|