@fink-andreas/pi-linear-tools 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +131 -3
- package/extensions/pi-linear-tools.js +227 -89
- package/index.js +1 -1019
- package/package.json +2 -2
- package/src/cli.js +700 -6
- package/src/handlers.js +466 -3
- package/src/linear.js +2792 -782
- package/src/sync-doc.js +1208 -0
package/src/handlers.js
CHANGED
|
@@ -13,18 +13,33 @@ import {
|
|
|
13
13
|
updateIssue,
|
|
14
14
|
createIssue,
|
|
15
15
|
fetchProjects,
|
|
16
|
+
fetchProjectDetails,
|
|
16
17
|
fetchTeams,
|
|
17
18
|
resolveProjectRef,
|
|
18
19
|
resolveTeamRef,
|
|
20
|
+
resolveMilestoneRef,
|
|
19
21
|
getTeamWorkflowStates,
|
|
20
22
|
fetchIssueDetails,
|
|
23
|
+
fetchIssueActivity,
|
|
21
24
|
formatIssueAsMarkdown,
|
|
25
|
+
formatIssueActivityAsMarkdown,
|
|
22
26
|
fetchIssuesByProject,
|
|
23
27
|
fetchProjectMilestones,
|
|
24
28
|
fetchMilestoneDetails,
|
|
25
29
|
createProjectMilestone,
|
|
26
30
|
updateProjectMilestone,
|
|
27
31
|
deleteProjectMilestone,
|
|
32
|
+
createProject,
|
|
33
|
+
updateProject,
|
|
34
|
+
deleteProject,
|
|
35
|
+
archiveProject,
|
|
36
|
+
unarchiveProject,
|
|
37
|
+
fetchProjectUpdates,
|
|
38
|
+
fetchProjectUpdateDetails,
|
|
39
|
+
createProjectUpdate,
|
|
40
|
+
updateProjectUpdate,
|
|
41
|
+
archiveProjectUpdate,
|
|
42
|
+
unarchiveProjectUpdate,
|
|
28
43
|
deleteIssue,
|
|
29
44
|
withHandlerErrorHandling,
|
|
30
45
|
getViewer,
|
|
@@ -44,6 +59,17 @@ function ensureNonEmpty(value, fieldName) {
|
|
|
44
59
|
return text;
|
|
45
60
|
}
|
|
46
61
|
|
|
62
|
+
function parseRefList(value) {
|
|
63
|
+
if (value === undefined || value === null) return [];
|
|
64
|
+
if (Array.isArray(value)) {
|
|
65
|
+
return value.map((item) => String(item || '').trim()).filter(Boolean);
|
|
66
|
+
}
|
|
67
|
+
return String(value)
|
|
68
|
+
.split(',')
|
|
69
|
+
.map((item) => item.trim())
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
}
|
|
72
|
+
|
|
47
73
|
// ===== GIT OPERATIONS (for issue start) =====
|
|
48
74
|
|
|
49
75
|
/**
|
|
@@ -226,6 +252,28 @@ export async function executeIssueView(client, params) {
|
|
|
226
252
|
};
|
|
227
253
|
}
|
|
228
254
|
|
|
255
|
+
export async function executeIssueActivity(client, params) {
|
|
256
|
+
const issue = ensureNonEmpty(params.issue, 'issue');
|
|
257
|
+
const activityData = await fetchIssueActivity(client, issue, {
|
|
258
|
+
limit: params.limit || 25,
|
|
259
|
+
includeArchived: params.includeArchived === true,
|
|
260
|
+
});
|
|
261
|
+
const markdown = formatIssueActivityAsMarkdown(activityData, {
|
|
262
|
+
limit: params.limit,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
content: [{ type: 'text', text: markdown }],
|
|
267
|
+
details: {
|
|
268
|
+
issueId: activityData.issue.id,
|
|
269
|
+
identifier: activityData.issue.identifier,
|
|
270
|
+
title: activityData.issue.title,
|
|
271
|
+
activityCount: activityData.activity.length,
|
|
272
|
+
url: activityData.issue.url,
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
229
277
|
/**
|
|
230
278
|
* Create a new issue
|
|
231
279
|
*/
|
|
@@ -272,6 +320,10 @@ export async function executeIssueCreate(client, params, options = {}) {
|
|
|
272
320
|
createInput.priority = params.priority;
|
|
273
321
|
}
|
|
274
322
|
|
|
323
|
+
if (params.estimate !== undefined && params.estimate !== null) {
|
|
324
|
+
createInput.estimate = params.estimate;
|
|
325
|
+
}
|
|
326
|
+
|
|
275
327
|
if (params.parentId) {
|
|
276
328
|
createInput.parentId = params.parentId;
|
|
277
329
|
}
|
|
@@ -354,6 +406,7 @@ export async function executeIssueUpdate(client, params) {
|
|
|
354
406
|
title: params.title,
|
|
355
407
|
description: params.description,
|
|
356
408
|
priority: params.priority,
|
|
409
|
+
estimate: params.estimate,
|
|
357
410
|
state: params.state,
|
|
358
411
|
milestone: params.milestone,
|
|
359
412
|
projectMilestoneId: params.projectMilestoneId,
|
|
@@ -551,6 +604,362 @@ export async function executeProjectList(client) {
|
|
|
551
604
|
});
|
|
552
605
|
}
|
|
553
606
|
|
|
607
|
+
export async function executeProjectView(client, params) {
|
|
608
|
+
return withHandlerErrorHandling(async () => {
|
|
609
|
+
const projectRef = ensureNonEmpty(params.project, 'project');
|
|
610
|
+
const project = await fetchProjectDetails(client, projectRef);
|
|
611
|
+
|
|
612
|
+
const lines = [`# Project: ${project.name}`];
|
|
613
|
+
const metaParts = [];
|
|
614
|
+
|
|
615
|
+
if (project.status?.name) metaParts.push(`**Status:** ${project.status.name}`);
|
|
616
|
+
if (project.health) metaParts.push(`**Health:** ${project.health}`);
|
|
617
|
+
if (project.progress !== undefined && project.progress !== null) metaParts.push(`**Progress:** ${project.progress}%`);
|
|
618
|
+
if (project.priority !== undefined && project.priority !== null) metaParts.push(`**Priority:** ${project.priority}`);
|
|
619
|
+
if (project.startDate) metaParts.push(`**Start:** ${project.startDate}`);
|
|
620
|
+
if (project.targetDate) metaParts.push(`**Target:** ${project.targetDate}`);
|
|
621
|
+
|
|
622
|
+
if (metaParts.length > 0) {
|
|
623
|
+
lines.push('');
|
|
624
|
+
lines.push(metaParts.join(' | '));
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const teamLabel = project.teams.length > 0
|
|
628
|
+
? project.teams.map((team) => `\`${team.key}\``).join(', ')
|
|
629
|
+
: 'None';
|
|
630
|
+
const leadLabel = project.lead?.displayName || project.lead?.name || 'Unassigned';
|
|
631
|
+
|
|
632
|
+
lines.push('');
|
|
633
|
+
lines.push(`**Teams:** ${teamLabel}`);
|
|
634
|
+
lines.push(`**Lead:** ${leadLabel}`);
|
|
635
|
+
|
|
636
|
+
if (project.url) {
|
|
637
|
+
lines.push(`**URL:** ${project.url}`);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (project.description) {
|
|
641
|
+
lines.push('');
|
|
642
|
+
lines.push(project.description);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (project.content) {
|
|
646
|
+
lines.push('');
|
|
647
|
+
if (project.description) {
|
|
648
|
+
lines.push('## Content');
|
|
649
|
+
lines.push('');
|
|
650
|
+
}
|
|
651
|
+
lines.push(project.content);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (project.projectMilestones.length > 0) {
|
|
655
|
+
lines.push('');
|
|
656
|
+
lines.push(`## Milestones (${project.projectMilestones.length})`);
|
|
657
|
+
lines.push('');
|
|
658
|
+
|
|
659
|
+
for (const milestone of project.projectMilestones) {
|
|
660
|
+
const progressLabel = milestone.progress !== undefined && milestone.progress !== null
|
|
661
|
+
? `${milestone.progress}%`
|
|
662
|
+
: 'N/A';
|
|
663
|
+
const targetLabel = milestone.targetDate ? ` → ${milestone.targetDate}` : '';
|
|
664
|
+
lines.push(`- **${milestone.name}** _[${milestone.status}]_ (${progressLabel})${targetLabel}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return toTextResult(lines.join('\n'), {
|
|
669
|
+
projectId: project.id,
|
|
670
|
+
name: project.name,
|
|
671
|
+
status: project.status,
|
|
672
|
+
teamCount: project.teams.length,
|
|
673
|
+
milestoneCount: project.projectMilestones.length,
|
|
674
|
+
url: project.url,
|
|
675
|
+
});
|
|
676
|
+
}, 'executeProjectView');
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export async function executeProjectCreate(client, params) {
|
|
680
|
+
return withHandlerErrorHandling(async () => {
|
|
681
|
+
const name = ensureNonEmpty(params.name, 'name');
|
|
682
|
+
const teamRefs = parseRefList(params.teams ?? params.team);
|
|
683
|
+
|
|
684
|
+
if (teamRefs.length === 0) {
|
|
685
|
+
throw new Error('Missing required field: teams');
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const teams = await Promise.all(teamRefs.map((teamRef) => resolveTeamRef(client, teamRef)));
|
|
689
|
+
|
|
690
|
+
let leadId;
|
|
691
|
+
if (params.lead === 'me') {
|
|
692
|
+
const viewer = await getViewer(client);
|
|
693
|
+
leadId = viewer.id;
|
|
694
|
+
} else if (params.lead) {
|
|
695
|
+
leadId = params.lead;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const project = await createProject(client, {
|
|
699
|
+
name,
|
|
700
|
+
teamIds: teams.map((team) => team.id),
|
|
701
|
+
description: params.description,
|
|
702
|
+
leadId,
|
|
703
|
+
priority: params.priority,
|
|
704
|
+
color: params.color,
|
|
705
|
+
icon: params.icon,
|
|
706
|
+
startDate: params.startDate,
|
|
707
|
+
targetDate: params.targetDate,
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
return toTextResult(
|
|
711
|
+
`Created project **${project.name}** for ${teams.map((team) => team.key).join(', ')}`,
|
|
712
|
+
{
|
|
713
|
+
projectId: project.id,
|
|
714
|
+
name: project.name,
|
|
715
|
+
teams: project.teams,
|
|
716
|
+
status: project.status,
|
|
717
|
+
url: project.url,
|
|
718
|
+
}
|
|
719
|
+
);
|
|
720
|
+
}, 'executeProjectCreate');
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
export async function executeProjectUpdate(client, params) {
|
|
724
|
+
return withHandlerErrorHandling(async () => {
|
|
725
|
+
const projectRef = ensureNonEmpty(params.project, 'project');
|
|
726
|
+
const patch = {
|
|
727
|
+
name: params.name,
|
|
728
|
+
description: params.description,
|
|
729
|
+
priority: params.priority,
|
|
730
|
+
color: params.color,
|
|
731
|
+
icon: params.icon,
|
|
732
|
+
startDate: params.startDate,
|
|
733
|
+
targetDate: params.targetDate,
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
if (params.lead === 'me') {
|
|
737
|
+
const viewer = await getViewer(client);
|
|
738
|
+
patch.leadId = viewer.id;
|
|
739
|
+
} else if (params.lead === 'none') {
|
|
740
|
+
patch.leadId = null;
|
|
741
|
+
} else if (params.lead !== undefined) {
|
|
742
|
+
patch.leadId = params.lead;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (params.teams !== undefined || params.team !== undefined) {
|
|
746
|
+
const teamRefs = parseRefList(params.teams ?? params.team);
|
|
747
|
+
if (teamRefs.length === 0) {
|
|
748
|
+
throw new Error('At least one team is required when updating teams');
|
|
749
|
+
}
|
|
750
|
+
const teams = await Promise.all(teamRefs.map((teamRef) => resolveTeamRef(client, teamRef)));
|
|
751
|
+
patch.teamIds = teams.map((team) => team.id);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const result = await updateProject(client, projectRef, patch);
|
|
755
|
+
|
|
756
|
+
return toTextResult(
|
|
757
|
+
`Updated project **${result.project.name}** (${result.changed.join(', ')})`,
|
|
758
|
+
{
|
|
759
|
+
projectId: result.project.id,
|
|
760
|
+
name: result.project.name,
|
|
761
|
+
changed: result.changed,
|
|
762
|
+
status: result.project.status,
|
|
763
|
+
}
|
|
764
|
+
);
|
|
765
|
+
}, 'executeProjectUpdate');
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
export async function executeProjectDelete(client, params) {
|
|
769
|
+
return withHandlerErrorHandling(async () => {
|
|
770
|
+
const projectRef = ensureNonEmpty(params.project, 'project');
|
|
771
|
+
const result = await deleteProject(client, projectRef);
|
|
772
|
+
|
|
773
|
+
return toTextResult(
|
|
774
|
+
`Deleted project **${result.name}** \`${result.projectId}\``,
|
|
775
|
+
{
|
|
776
|
+
projectId: result.projectId,
|
|
777
|
+
name: result.name,
|
|
778
|
+
success: result.success,
|
|
779
|
+
}
|
|
780
|
+
);
|
|
781
|
+
}, 'executeProjectDelete');
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
export async function executeProjectArchive(client, params) {
|
|
785
|
+
return withHandlerErrorHandling(async () => {
|
|
786
|
+
const projectRef = ensureNonEmpty(params.project, 'project');
|
|
787
|
+
const result = await archiveProject(client, projectRef);
|
|
788
|
+
|
|
789
|
+
return toTextResult(
|
|
790
|
+
`Archived project **${result.name || result.entity?.name || result.projectId}**`,
|
|
791
|
+
{
|
|
792
|
+
projectId: result.projectId,
|
|
793
|
+
name: result.name || result.entity?.name || null,
|
|
794
|
+
success: result.success,
|
|
795
|
+
}
|
|
796
|
+
);
|
|
797
|
+
}, 'executeProjectArchive');
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
export async function executeProjectUnarchive(client, params) {
|
|
801
|
+
return withHandlerErrorHandling(async () => {
|
|
802
|
+
const projectRef = ensureNonEmpty(params.project, 'project');
|
|
803
|
+
const result = await unarchiveProject(client, projectRef);
|
|
804
|
+
|
|
805
|
+
return toTextResult(
|
|
806
|
+
`Unarchived project **${result.project.name}**`,
|
|
807
|
+
{
|
|
808
|
+
projectId: result.project.id,
|
|
809
|
+
name: result.project.name,
|
|
810
|
+
success: result.success,
|
|
811
|
+
}
|
|
812
|
+
);
|
|
813
|
+
}, 'executeProjectUnarchive');
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// ===== PROJECT UPDATE HANDLERS =====
|
|
817
|
+
|
|
818
|
+
export async function executeProjectUpdateList(client, params) {
|
|
819
|
+
return withHandlerErrorHandling(async () => {
|
|
820
|
+
const projectRef = ensureNonEmpty(params.project, 'project');
|
|
821
|
+
const { project, updates } = await fetchProjectUpdates(client, projectRef, {
|
|
822
|
+
limit: params.limit ?? 10,
|
|
823
|
+
includeArchived: params.includeArchived === true,
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
if (updates.length === 0) {
|
|
827
|
+
return toTextResult(`No project updates found for "${project.name}"`, {
|
|
828
|
+
projectId: project.id,
|
|
829
|
+
projectName: project.name,
|
|
830
|
+
updateCount: 0,
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
const lines = [`## Project updates for "${project.name}" (${updates.length})`, ''];
|
|
835
|
+
for (const update of updates) {
|
|
836
|
+
const author = update.user?.displayName || update.user?.name || 'Unknown';
|
|
837
|
+
const createdAt = update.createdAt ? String(update.createdAt).slice(0, 10) : 'unknown date';
|
|
838
|
+
const healthLabel = update.health ? ` [${update.health}]` : '';
|
|
839
|
+
const archivedLabel = update.archivedAt ? ' [archived]' : '';
|
|
840
|
+
const preview = update.body.replace(/\s+/g, ' ').trim().slice(0, 120);
|
|
841
|
+
lines.push(`- **${update.id}**${healthLabel}${archivedLabel} by ${author} on ${createdAt}`);
|
|
842
|
+
if (preview) {
|
|
843
|
+
lines.push(` ${preview}${update.body.length > 120 ? '...' : ''}`);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
return toTextResult(lines.join('\n'), {
|
|
848
|
+
projectId: project.id,
|
|
849
|
+
projectName: project.name,
|
|
850
|
+
updateCount: updates.length,
|
|
851
|
+
});
|
|
852
|
+
}, 'executeProjectUpdateList');
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
export async function executeProjectUpdateView(client, params) {
|
|
856
|
+
return withHandlerErrorHandling(async () => {
|
|
857
|
+
const projectUpdateId = ensureNonEmpty(params.projectUpdate, 'projectUpdate');
|
|
858
|
+
const projectUpdate = await fetchProjectUpdateDetails(client, projectUpdateId);
|
|
859
|
+
|
|
860
|
+
const lines = [`# Project update ${projectUpdate.id}`];
|
|
861
|
+
const meta = [];
|
|
862
|
+
if (projectUpdate.project?.name) meta.push(`**Project:** ${projectUpdate.project.name}`);
|
|
863
|
+
if (projectUpdate.health) meta.push(`**Health:** ${projectUpdate.health}`);
|
|
864
|
+
if (projectUpdate.user?.displayName || projectUpdate.user?.name) meta.push(`**Author:** ${projectUpdate.user?.displayName || projectUpdate.user?.name}`);
|
|
865
|
+
if (projectUpdate.createdAt) meta.push(`**Created:** ${String(projectUpdate.createdAt).slice(0, 10)}`);
|
|
866
|
+
if (projectUpdate.archivedAt) meta.push(`**Archived:** ${String(projectUpdate.archivedAt).slice(0, 10)}`);
|
|
867
|
+
if (meta.length > 0) {
|
|
868
|
+
lines.push('');
|
|
869
|
+
lines.push(meta.join(' | '));
|
|
870
|
+
}
|
|
871
|
+
if (projectUpdate.url) {
|
|
872
|
+
lines.push('');
|
|
873
|
+
lines.push(`**URL:** ${projectUpdate.url}`);
|
|
874
|
+
}
|
|
875
|
+
if (projectUpdate.body) {
|
|
876
|
+
lines.push('');
|
|
877
|
+
lines.push(projectUpdate.body);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return toTextResult(lines.join('\n'), {
|
|
881
|
+
projectUpdateId: projectUpdate.id,
|
|
882
|
+
projectId: projectUpdate.project?.id || null,
|
|
883
|
+
projectName: projectUpdate.project?.name || null,
|
|
884
|
+
health: projectUpdate.health,
|
|
885
|
+
archivedAt: projectUpdate.archivedAt,
|
|
886
|
+
});
|
|
887
|
+
}, 'executeProjectUpdateView');
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
export async function executeProjectUpdateCreate(client, params) {
|
|
891
|
+
return withHandlerErrorHandling(async () => {
|
|
892
|
+
const projectRef = ensureNonEmpty(params.project, 'project');
|
|
893
|
+
const resolved = await resolveProjectRef(client, projectRef);
|
|
894
|
+
const projectUpdate = await createProjectUpdate(client, {
|
|
895
|
+
projectId: resolved.id,
|
|
896
|
+
body: params.body,
|
|
897
|
+
health: params.health,
|
|
898
|
+
isDiffHidden: params.isDiffHidden,
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
return toTextResult(
|
|
902
|
+
`Created project update **${projectUpdate.id}** for "${resolved.name}"`,
|
|
903
|
+
{
|
|
904
|
+
projectUpdateId: projectUpdate.id,
|
|
905
|
+
projectId: resolved.id,
|
|
906
|
+
projectName: resolved.name,
|
|
907
|
+
health: projectUpdate.health,
|
|
908
|
+
}
|
|
909
|
+
);
|
|
910
|
+
}, 'executeProjectUpdateCreate');
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
export async function executeProjectUpdateUpdate(client, params) {
|
|
914
|
+
return withHandlerErrorHandling(async () => {
|
|
915
|
+
const projectUpdateId = ensureNonEmpty(params.projectUpdate, 'projectUpdate');
|
|
916
|
+
const result = await updateProjectUpdate(client, projectUpdateId, {
|
|
917
|
+
body: params.body,
|
|
918
|
+
health: params.health,
|
|
919
|
+
isDiffHidden: params.isDiffHidden,
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
return toTextResult(
|
|
923
|
+
`Updated project update **${result.projectUpdate.id}** (${result.changed.join(', ')})`,
|
|
924
|
+
{
|
|
925
|
+
projectUpdateId: result.projectUpdate.id,
|
|
926
|
+
changed: result.changed,
|
|
927
|
+
health: result.projectUpdate.health,
|
|
928
|
+
}
|
|
929
|
+
);
|
|
930
|
+
}, 'executeProjectUpdateUpdate');
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
export async function executeProjectUpdateArchive(client, params) {
|
|
934
|
+
return withHandlerErrorHandling(async () => {
|
|
935
|
+
const projectUpdateId = ensureNonEmpty(params.projectUpdate, 'projectUpdate');
|
|
936
|
+
const result = await archiveProjectUpdate(client, projectUpdateId);
|
|
937
|
+
|
|
938
|
+
return toTextResult(
|
|
939
|
+
`Archived project update **${projectUpdateId}**`,
|
|
940
|
+
{
|
|
941
|
+
projectUpdateId,
|
|
942
|
+
success: result.success,
|
|
943
|
+
}
|
|
944
|
+
);
|
|
945
|
+
}, 'executeProjectUpdateArchive');
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
export async function executeProjectUpdateUnarchive(client, params) {
|
|
949
|
+
return withHandlerErrorHandling(async () => {
|
|
950
|
+
const projectUpdateId = ensureNonEmpty(params.projectUpdate, 'projectUpdate');
|
|
951
|
+
const result = await unarchiveProjectUpdate(client, projectUpdateId);
|
|
952
|
+
|
|
953
|
+
return toTextResult(
|
|
954
|
+
`Unarchived project update **${result.projectUpdate.id}**`,
|
|
955
|
+
{
|
|
956
|
+
projectUpdateId: result.projectUpdate.id,
|
|
957
|
+
success: result.success,
|
|
958
|
+
}
|
|
959
|
+
);
|
|
960
|
+
}, 'executeProjectUpdateUnarchive');
|
|
961
|
+
}
|
|
962
|
+
|
|
554
963
|
// ===== TEAM HANDLERS =====
|
|
555
964
|
|
|
556
965
|
/**
|
|
@@ -638,7 +1047,24 @@ export async function executeMilestoneList(client, params) {
|
|
|
638
1047
|
* View milestone details
|
|
639
1048
|
*/
|
|
640
1049
|
export async function executeMilestoneView(client, params) {
|
|
641
|
-
|
|
1050
|
+
let projectId = null;
|
|
1051
|
+
let milestoneId = params.milestone;
|
|
1052
|
+
|
|
1053
|
+
// If milestone is not a Linear ID, resolve it with project context
|
|
1054
|
+
const ref = String(milestoneId || '').trim();
|
|
1055
|
+
const isId = typeof ref === 'string' && /^[0-9a-fA-F-]{16,}$/.test(ref);
|
|
1056
|
+
|
|
1057
|
+
if (!isId) {
|
|
1058
|
+
// Need project context to resolve milestone name
|
|
1059
|
+
let projectRef = params.project;
|
|
1060
|
+
if (!projectRef) {
|
|
1061
|
+
projectRef = path.basename(process.cwd());
|
|
1062
|
+
}
|
|
1063
|
+
const resolvedProject = await resolveProjectRef(client, projectRef);
|
|
1064
|
+
projectId = resolvedProject.id;
|
|
1065
|
+
const resolvedMilestone = await resolveMilestoneRef(client, ref, projectId);
|
|
1066
|
+
milestoneId = resolvedMilestone.id;
|
|
1067
|
+
}
|
|
642
1068
|
|
|
643
1069
|
const milestoneData = await fetchMilestoneDetails(client, milestoneId);
|
|
644
1070
|
|
|
@@ -760,7 +1186,25 @@ export async function executeMilestoneCreate(client, params) {
|
|
|
760
1186
|
* Update a milestone
|
|
761
1187
|
*/
|
|
762
1188
|
export async function executeMilestoneUpdate(client, params) {
|
|
763
|
-
|
|
1189
|
+
let projectId = null;
|
|
1190
|
+
let milestoneId = params.milestone;
|
|
1191
|
+
|
|
1192
|
+
// If milestone is not a Linear ID, resolve it with project context
|
|
1193
|
+
const ref = String(milestoneId || '').trim();
|
|
1194
|
+
const isId = typeof ref === 'string' && /^[0-9a-fA-F-]{16,}$/.test(ref);
|
|
1195
|
+
|
|
1196
|
+
|
|
1197
|
+
if (!isId) {
|
|
1198
|
+
// Need project context to resolve milestone name
|
|
1199
|
+
let projectRef = params.project;
|
|
1200
|
+
if (!projectRef) {
|
|
1201
|
+
projectRef = path.basename(process.cwd());
|
|
1202
|
+
}
|
|
1203
|
+
const resolvedProject = await resolveProjectRef(client, projectRef);
|
|
1204
|
+
projectId = resolvedProject.id;
|
|
1205
|
+
const resolvedMilestone = await resolveMilestoneRef(client, ref, projectId);
|
|
1206
|
+
milestoneId = resolvedMilestone.id;
|
|
1207
|
+
}
|
|
764
1208
|
|
|
765
1209
|
// Note: status is not included as it's a computed/read-only field in Linear's API
|
|
766
1210
|
const result = await updateProjectMilestone(client, milestoneId, {
|
|
@@ -799,7 +1243,26 @@ export async function executeMilestoneUpdate(client, params) {
|
|
|
799
1243
|
* Delete a milestone
|
|
800
1244
|
*/
|
|
801
1245
|
export async function executeMilestoneDelete(client, params) {
|
|
802
|
-
|
|
1246
|
+
let projectId = null;
|
|
1247
|
+
let milestoneId = params.milestone;
|
|
1248
|
+
|
|
1249
|
+
// If milestone is not a Linear ID, resolve it with project context
|
|
1250
|
+
const ref = String(milestoneId || '').trim();
|
|
1251
|
+
const isId = typeof ref === 'string' && /^[0-9a-fA-F-]{16,}$/.test(ref);
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
if (!isId) {
|
|
1255
|
+
// Need project context to resolve milestone name
|
|
1256
|
+
let projectRef = params.project;
|
|
1257
|
+
if (!projectRef) {
|
|
1258
|
+
projectRef = path.basename(process.cwd());
|
|
1259
|
+
}
|
|
1260
|
+
const resolvedProject = await resolveProjectRef(client, projectRef);
|
|
1261
|
+
projectId = resolvedProject.id;
|
|
1262
|
+
const resolvedMilestone = await resolveMilestoneRef(client, ref, projectId);
|
|
1263
|
+
milestoneId = resolvedMilestone.id;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
803
1266
|
const result = await deleteProjectMilestone(client, milestoneId);
|
|
804
1267
|
|
|
805
1268
|
const label = result.name
|