@gh-symphony/cli 0.0.22 → 0.1.3

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.
Files changed (36) hide show
  1. package/README.md +72 -77
  2. package/dist/{chunk-HMLBBZNY.js → chunk-2YF7PQUC.js} +16 -71
  3. package/dist/{chunk-IWFX2FMA.js → chunk-6I753NYO.js} +4 -1
  4. package/dist/{chunk-2TSM3INR.js → chunk-HQ7A3C7K.js} +575 -12
  5. package/dist/{chunk-36KYEDEO.js → chunk-MVRF7BES.js} +1 -10
  6. package/dist/{workflow-L3KT6HB7.js → chunk-NESHTYXQ.js} +27 -19
  7. package/dist/{chunk-2UW7NQLX.js → chunk-PEZUBHWJ.js} +1 -1
  8. package/dist/chunk-PG332ZS4.js +238 -0
  9. package/dist/{chunk-EEQQWTXS.js → chunk-WCOIVNHH.js} +213 -82
  10. package/dist/{chunk-QIRE2VXS.js → chunk-WOVNN5NW.js} +16 -17
  11. package/dist/{chunk-C67H3OUL.js → chunk-Z3NZOPLZ.js} +0 -81
  12. package/dist/{config-cmd-Z3A7V6NC.js → config-cmd-2ADPUYWA.js} +1 -1
  13. package/dist/{doctor-EJUMPBMW.js → doctor-2AXHIEAP.js} +464 -40
  14. package/dist/index.js +340 -294
  15. package/dist/{chunk-PUDXVBSN.js → repo-SUXYT4OK.js} +6272 -2996
  16. package/dist/{setup-TZJSM3QV.js → setup-UBHOMXUG.js} +57 -92
  17. package/dist/{upgrade-O33S2SJK.js → upgrade-355SQJ5P.js} +2 -2
  18. package/dist/{version-CW54Q7BK.js → version-4ILSDZQH.js} +1 -1
  19. package/dist/worker-entry.js +10 -5
  20. package/dist/workflow-S6YSZPQT.js +22 -0
  21. package/package.json +4 -4
  22. package/dist/chunk-DDL4BWSL.js +0 -146
  23. package/dist/chunk-DFLXHNYQ.js +0 -482
  24. package/dist/chunk-E7HYEEZD.js +0 -1318
  25. package/dist/chunk-GDE6FYN4.js +0 -26
  26. package/dist/chunk-GSX2FV3M.js +0 -103
  27. package/dist/chunk-ZHOKYUO3.js +0 -1047
  28. package/dist/init-54HMKNYI.js +0 -38
  29. package/dist/logs-GTZ4U5JE.js +0 -188
  30. package/dist/project-RMYMZSFV.js +0 -25
  31. package/dist/recover-LTLKMTRX.js +0 -133
  32. package/dist/repo-WI7GF6XQ.js +0 -749
  33. package/dist/run-IHN3ZL35.js +0 -122
  34. package/dist/start-RTAHQMR2.js +0 -19
  35. package/dist/status-F4D52OVK.js +0 -12
  36. package/dist/stop-MDKMJPVR.js +0 -10
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DEFAULT_WORKFLOW_LIFECYCLE
4
- } from "./chunk-EEQQWTXS.js";
4
+ } from "./chunk-WCOIVNHH.js";
5
+ import {
6
+ loadGlobalConfig,
7
+ loadProjectConfig
8
+ } from "./chunk-WOVNN5NW.js";
5
9
 
6
10
  // ../tracker-github/src/adapter.ts
7
11
  import { createHash } from "crypto";
@@ -23,11 +27,22 @@ var GitHubTrackerQueryError = class extends GitHubTrackerError {
23
27
  };
24
28
  var cachedGitHubGraphQLRateLimits = /* @__PURE__ */ new Map();
25
29
  function normalizeProjectItem(projectId, item, lifecycle = DEFAULT_WORKFLOW_LIFECYCLE, priority = {}, rateLimits = null) {
26
- if (item.content?.__typename !== "Issue") {
30
+ if (item.content?.__typename !== "Issue" && item.content?.__typename !== "PullRequest") {
27
31
  return null;
28
32
  }
29
33
  const fieldValues = extractFieldValues(item.fieldValues?.nodes ?? []);
30
34
  const state = fieldValues[lifecycle.stateFieldName] ?? "Unknown";
35
+ if (item.content.__typename === "PullRequest") {
36
+ return normalizePullRequestProjectItem(
37
+ projectId,
38
+ item,
39
+ item.content,
40
+ fieldValues,
41
+ state,
42
+ priority,
43
+ rateLimits
44
+ );
45
+ }
31
46
  const repository = item.content.repository;
32
47
  const blockedBy = (item.content.blockedBy?.nodes ?? []).flatMap(
33
48
  (node) => node ? [
@@ -41,6 +56,10 @@ function normalizeProjectItem(projectId, item, lifecycle = DEFAULT_WORKFLOW_LIFE
41
56
  const issueUpdatedAtMs = parseTimestampMs(item.content.updatedAt);
42
57
  const itemUpdatedAtMs = parseTimestampMs(item.updatedAt);
43
58
  const trackedUpdatedAt = itemUpdatedAtMs !== null && (issueUpdatedAtMs === null || itemUpdatedAtMs > issueUpdatedAtMs) ? item.updatedAt : item.content.updatedAt ?? item.updatedAt;
59
+ const linkedPullRequests = normalizePullRequestNodes(
60
+ item.content.closedByPullRequestsReferences?.nodes ?? []
61
+ );
62
+ const linkedPullRequestsTruncated = item.content.closedByPullRequestsReferences?.pageInfo?.hasNextPage ?? false;
44
63
  return {
45
64
  id: item.content.id,
46
65
  identifier: `${repository.owner.login}/${repository.name}#${item.content.number}`,
@@ -51,7 +70,7 @@ function normalizeProjectItem(projectId, item, lifecycle = DEFAULT_WORKFLOW_LIFE
51
70
  state,
52
71
  branchName: null,
53
72
  url: item.content.url,
54
- labels: (item.content.labels?.nodes ?? []).flatMap((label) => label?.name ? [label.name.toLowerCase()] : []).sort(),
73
+ labels: normalizeLabelNames(item.content.labels?.nodes ?? []),
55
74
  blockedBy,
56
75
  createdAt: item.content.createdAt,
57
76
  updatedAt: trackedUpdatedAt,
@@ -66,7 +85,44 @@ function normalizeProjectItem(projectId, item, lifecycle = DEFAULT_WORKFLOW_LIFE
66
85
  bindingId: projectId,
67
86
  itemId: item.id
68
87
  },
69
- metadata: fieldValues,
88
+ metadata: withIssueMetadata(
89
+ fieldValues,
90
+ linkedPullRequests,
91
+ linkedPullRequestsTruncated
92
+ ),
93
+ rateLimits
94
+ };
95
+ }
96
+ function normalizePullRequestProjectItem(projectId, item, content, fieldValues, state, priority, rateLimits) {
97
+ const pullRequest = normalizePullRequestNode(content);
98
+ const itemUpdatedAtMs = parseTimestampMs(item.updatedAt);
99
+ const pullRequestUpdatedAtMs = parseTimestampMs(content.updatedAt);
100
+ const trackedUpdatedAt = itemUpdatedAtMs !== null && (pullRequestUpdatedAtMs === null || itemUpdatedAtMs > pullRequestUpdatedAtMs) ? item.updatedAt : content.updatedAt ?? item.updatedAt;
101
+ return {
102
+ id: content.id,
103
+ identifier: pullRequest.identifier,
104
+ number: content.number,
105
+ title: content.title,
106
+ description: content.body,
107
+ priority: resolvePriority(item, priority),
108
+ state,
109
+ branchName: content.headRefName,
110
+ url: content.url,
111
+ labels: pullRequest.labels,
112
+ blockedBy: [],
113
+ createdAt: content.createdAt,
114
+ updatedAt: trackedUpdatedAt,
115
+ repository: pullRequest.repository,
116
+ tracker: {
117
+ adapter: "github-project",
118
+ bindingId: projectId,
119
+ itemId: item.id
120
+ },
121
+ metadata: withGitHubMetadata(fieldValues, {
122
+ contentType: "PullRequest",
123
+ pullRequest,
124
+ linkedPullRequests: []
125
+ }),
70
126
  rateLimits
71
127
  };
72
128
  }
@@ -228,6 +284,70 @@ async function fetchProjectItemsPage(config, cursor, fetchImpl) {
228
284
  var fetchGithubProjectIssues = fetchProjectIssues;
229
285
  var fetchGithubIssueStatesByIds = fetchIssueStatesByIds;
230
286
  var fetchGithubProjectIssueByRepositoryAndNumber = fetchProjectIssueByRepositoryAndNumber;
287
+ var upsertGithubIssueComment = upsertIssueComment;
288
+ async function upsertIssueComment(config, issueId, input, fetchImpl = fetch) {
289
+ const existingComment = await findIssueCommentByMarker(
290
+ config,
291
+ issueId,
292
+ input.marker,
293
+ fetchImpl
294
+ );
295
+ if (!existingComment) {
296
+ await executeGraphQLQuery(
297
+ config,
298
+ ADD_ISSUE_COMMENT_MUTATION,
299
+ {
300
+ subjectId: issueId,
301
+ body: input.body
302
+ },
303
+ fetchImpl
304
+ );
305
+ return "created";
306
+ }
307
+ if (existingComment.body === input.body) {
308
+ return "unchanged";
309
+ }
310
+ await executeGraphQLQuery(
311
+ config,
312
+ UPDATE_ISSUE_COMMENT_MUTATION,
313
+ {
314
+ commentId: existingComment.id,
315
+ body: input.body
316
+ },
317
+ fetchImpl
318
+ );
319
+ return "updated";
320
+ }
321
+ async function findIssueCommentByMarker(config, issueId, marker, fetchImpl) {
322
+ let cursor = null;
323
+ while (true) {
324
+ const data = await executeGraphQLQuery(
325
+ config,
326
+ ISSUE_COMMENTS_BY_ID_QUERY,
327
+ {
328
+ issueId,
329
+ cursor
330
+ },
331
+ fetchImpl
332
+ );
333
+ if (data.node?.__typename !== "Issue") {
334
+ throw new GitHubTrackerQueryError(
335
+ "GitHub GraphQL response did not include issue comments."
336
+ );
337
+ }
338
+ const issueNode = data.node;
339
+ const match = issueNode.comments.nodes?.find(
340
+ (comment) => comment?.body.includes(marker)
341
+ ) ?? null;
342
+ if (match) {
343
+ return match;
344
+ }
345
+ if (!issueNode.comments.pageInfo.hasNextPage) {
346
+ return null;
347
+ }
348
+ cursor = issueNode.comments.pageInfo.endCursor;
349
+ }
350
+ }
231
351
  async function fetchCurrentUserLogin(config, fetchImpl) {
232
352
  const response = await fetchImpl(resolveRestUserApiUrl(config.apiUrl), {
233
353
  method: "GET",
@@ -255,7 +375,7 @@ async function fetchCurrentUserLogin(config, fetchImpl) {
255
375
  return payload.login;
256
376
  }
257
377
  function isIssueAssignedToLogin(item, login) {
258
- if (item.content?.__typename !== "Issue") {
378
+ if (item.content?.__typename !== "Issue" && item.content?.__typename !== "PullRequest") {
259
379
  return false;
260
380
  }
261
381
  return (item.content.assignees?.nodes ?? []).some(
@@ -289,7 +409,7 @@ function extractFieldValues(nodes) {
289
409
  }, {});
290
410
  }
291
411
  function normalizeIssueStateLookupNode(projectId, issue, projectItem, lifecycle = DEFAULT_WORKFLOW_LIFECYCLE, rateLimits = null) {
292
- if (issue?.__typename !== "Issue") {
412
+ if (issue?.__typename !== "Issue" && issue?.__typename !== "PullRequest") {
293
413
  return null;
294
414
  }
295
415
  if (!projectItem) {
@@ -299,6 +419,7 @@ function normalizeIssueStateLookupNode(projectId, issue, projectItem, lifecycle
299
419
  const state = fieldValues[lifecycle.stateFieldName] ?? "Unknown";
300
420
  const repository = issue.repository;
301
421
  const identifier = `${repository.owner.login}/${repository.name}#${issue.number}`;
422
+ const url = issue.url ?? `${repository.url}/${issue.__typename === "PullRequest" ? "pull" : "issues"}/${issue.number}`;
302
423
  return {
303
424
  id: issue.id,
304
425
  identifier,
@@ -307,8 +428,8 @@ function normalizeIssueStateLookupNode(projectId, issue, projectItem, lifecycle
307
428
  description: null,
308
429
  priority: null,
309
430
  state,
310
- branchName: null,
311
- url: `${repository.url}/issues/${issue.number}`,
431
+ branchName: issue.__typename === "PullRequest" ? issue.headRefName ?? null : null,
432
+ url,
312
433
  labels: [],
313
434
  blockedBy: [],
314
435
  createdAt: null,
@@ -324,7 +445,7 @@ function normalizeIssueStateLookupNode(projectId, issue, projectItem, lifecycle
324
445
  bindingId: projectId,
325
446
  itemId: projectItem.id
326
447
  },
327
- metadata: fieldValues,
448
+ metadata: issue.__typename === "PullRequest" ? withGitHubMetadata(fieldValues, { contentType: "PullRequest" }) : fieldValues,
328
449
  rateLimits
329
450
  };
330
451
  }
@@ -345,8 +466,64 @@ function normalizeRepositoryIssueLookup(projectId, issue, projectItem, lifecycle
345
466
  rateLimits
346
467
  );
347
468
  }
469
+ function normalizePullRequestNodes(nodes) {
470
+ return nodes.flatMap(
471
+ (node) => node ? [normalizePullRequestNode(node)] : []
472
+ );
473
+ }
474
+ function normalizePullRequestNode(node) {
475
+ const repository = normalizeRepositoryRef(node.repository);
476
+ return {
477
+ id: node.id,
478
+ number: node.number,
479
+ identifier: `${repository.owner}/${repository.name}#${node.number}`,
480
+ title: node.title,
481
+ body: node.body,
482
+ url: node.url,
483
+ state: node.state,
484
+ isDraft: node.isDraft,
485
+ merged: node.merged,
486
+ headRefName: node.headRefName,
487
+ baseRefName: node.baseRefName,
488
+ headRepository: node.headRepository ? normalizeRepositoryRef(node.headRepository) : null,
489
+ repository,
490
+ labels: normalizeLabelNames(node.labels?.nodes ?? []),
491
+ assignees: normalizeAssigneeLogins(node.assignees?.nodes ?? []),
492
+ createdAt: node.createdAt,
493
+ updatedAt: node.updatedAt
494
+ };
495
+ }
496
+ function normalizeRepositoryRef(repository) {
497
+ return {
498
+ owner: repository.owner.login,
499
+ name: repository.name,
500
+ url: repository.url,
501
+ cloneUrl: deriveCloneUrl(repository.url)
502
+ };
503
+ }
504
+ function normalizeLabelNames(nodes) {
505
+ return nodes.flatMap((label) => label?.name ? [label.name.toLowerCase()] : []).sort();
506
+ }
507
+ function normalizeAssigneeLogins(nodes) {
508
+ return nodes.flatMap((assignee) => assignee?.login ? [assignee.login] : []);
509
+ }
510
+ function withGitHubMetadata(fieldValues, metadata) {
511
+ return {
512
+ ...fieldValues,
513
+ ...metadata
514
+ };
515
+ }
516
+ function withIssueMetadata(fieldValues, linkedPullRequests, linkedPullRequestsTruncated = false) {
517
+ if (linkedPullRequests.length === 0 && !linkedPullRequestsTruncated) {
518
+ return fieldValues;
519
+ }
520
+ return withGitHubMetadata(fieldValues, {
521
+ linkedPullRequests,
522
+ linkedPullRequestsTruncated
523
+ });
524
+ }
348
525
  async function resolveIssueProjectItemForStateLookup(config, issue, fetchImpl) {
349
- if (issue?.__typename !== "Issue") {
526
+ if (issue?.__typename !== "Issue" && issue?.__typename !== "PullRequest") {
350
527
  return null;
351
528
  }
352
529
  let connection = issue.projectItems;
@@ -383,7 +560,7 @@ async function fetchIssueProjectItemsPage(config, issueId, cursor, fetchImpl) {
383
560
  );
384
561
  const data = result.data;
385
562
  const issue = data.node;
386
- if (issue?.__typename !== "Issue" || !issue.projectItems) {
563
+ if (issue?.__typename !== "Issue" && issue?.__typename !== "PullRequest" || !issue.projectItems) {
387
564
  throw new GitHubTrackerQueryError(
388
565
  "GitHub GraphQL response did not include issue project items."
389
566
  );
@@ -670,6 +847,17 @@ var PROJECT_ITEMS_QUERY = `
670
847
  }
671
848
  }
672
849
  }
850
+ closedByPullRequestsReferences(first: 20) {
851
+ nodes {
852
+ ...PullRequestMetadata
853
+ }
854
+ pageInfo {
855
+ hasNextPage
856
+ }
857
+ }
858
+ }
859
+ ... on PullRequest {
860
+ ...PullRequestMetadata
673
861
  }
674
862
  }
675
863
  }
@@ -681,6 +869,45 @@ var PROJECT_ITEMS_QUERY = `
681
869
  }
682
870
  }
683
871
  }
872
+
873
+ fragment PullRequestMetadata on PullRequest {
874
+ id
875
+ number
876
+ title
877
+ body
878
+ url
879
+ state
880
+ isDraft
881
+ merged
882
+ headRefName
883
+ baseRefName
884
+ headRepository {
885
+ name
886
+ url
887
+ owner {
888
+ login
889
+ }
890
+ }
891
+ repository {
892
+ name
893
+ url
894
+ owner {
895
+ login
896
+ }
897
+ }
898
+ labels(first: 20) {
899
+ nodes {
900
+ name
901
+ }
902
+ }
903
+ assignees(first: 20) {
904
+ nodes {
905
+ login
906
+ }
907
+ }
908
+ createdAt
909
+ updatedAt
910
+ }
684
911
  `;
685
912
  var PROJECT_FIELDS_QUERY = `
686
913
  query ProjectFields($projectId: ID!) {
@@ -710,7 +937,57 @@ var ISSUE_STATES_BY_IDS_QUERY = `
710
937
  ... on Issue {
711
938
  id
712
939
  number
940
+ url
941
+ updatedAt
942
+ repository {
943
+ name
944
+ url
945
+ owner {
946
+ login
947
+ }
948
+ }
949
+ projectItems(first: 100, includeArchived: false) {
950
+ nodes {
951
+ id
952
+ updatedAt
953
+ project {
954
+ id
955
+ }
956
+ fieldValues(first: 20) {
957
+ nodes {
958
+ __typename
959
+ ... on ProjectV2ItemFieldSingleSelectValue {
960
+ name
961
+ optionId
962
+ field {
963
+ ... on ProjectV2SingleSelectField {
964
+ name
965
+ }
966
+ }
967
+ }
968
+ ... on ProjectV2ItemFieldTextValue {
969
+ text
970
+ field {
971
+ ... on ProjectV2FieldCommon {
972
+ name
973
+ }
974
+ }
975
+ }
976
+ }
977
+ }
978
+ }
979
+ pageInfo {
980
+ endCursor
981
+ hasNextPage
982
+ }
983
+ }
984
+ }
985
+ ... on PullRequest {
986
+ id
987
+ number
988
+ url
713
989
  updatedAt
990
+ headRefName
714
991
  repository {
715
992
  name
716
993
  url
@@ -764,6 +1041,7 @@ var ISSUE_PROJECT_ITEMS_PAGE_QUERY = `
764
1041
  ... on Issue {
765
1042
  id
766
1043
  number
1044
+ url
767
1045
  updatedAt
768
1046
  repository {
769
1047
  name
@@ -808,6 +1086,96 @@ var ISSUE_PROJECT_ITEMS_PAGE_QUERY = `
808
1086
  }
809
1087
  }
810
1088
  }
1089
+ ... on PullRequest {
1090
+ id
1091
+ number
1092
+ url
1093
+ updatedAt
1094
+ headRefName
1095
+ repository {
1096
+ name
1097
+ url
1098
+ owner {
1099
+ login
1100
+ }
1101
+ }
1102
+ projectItems(first: 100, after: $cursor, includeArchived: false) {
1103
+ nodes {
1104
+ id
1105
+ updatedAt
1106
+ project {
1107
+ id
1108
+ }
1109
+ fieldValues(first: 20) {
1110
+ nodes {
1111
+ __typename
1112
+ ... on ProjectV2ItemFieldSingleSelectValue {
1113
+ name
1114
+ optionId
1115
+ field {
1116
+ ... on ProjectV2SingleSelectField {
1117
+ name
1118
+ }
1119
+ }
1120
+ }
1121
+ ... on ProjectV2ItemFieldTextValue {
1122
+ text
1123
+ field {
1124
+ ... on ProjectV2FieldCommon {
1125
+ name
1126
+ }
1127
+ }
1128
+ }
1129
+ }
1130
+ }
1131
+ }
1132
+ pageInfo {
1133
+ endCursor
1134
+ hasNextPage
1135
+ }
1136
+ }
1137
+ }
1138
+ }
1139
+ }
1140
+ `;
1141
+ var ISSUE_COMMENTS_BY_ID_QUERY = `
1142
+ query IssueCommentsById($issueId: ID!, $cursor: String) {
1143
+ node(id: $issueId) {
1144
+ __typename
1145
+ ... on Issue {
1146
+ comments(first: 100, after: $cursor) {
1147
+ nodes {
1148
+ id
1149
+ body
1150
+ }
1151
+ pageInfo {
1152
+ endCursor
1153
+ hasNextPage
1154
+ }
1155
+ }
1156
+ }
1157
+ }
1158
+ }
1159
+ `;
1160
+ var ADD_ISSUE_COMMENT_MUTATION = `
1161
+ mutation AddIssueComment($subjectId: ID!, $body: String!) {
1162
+ addComment(input: { subjectId: $subjectId, body: $body }) {
1163
+ commentEdge {
1164
+ node {
1165
+ id
1166
+ body
1167
+ }
1168
+ }
1169
+ }
1170
+ }
1171
+ `;
1172
+ var UPDATE_ISSUE_COMMENT_MUTATION = `
1173
+ mutation UpdateIssueComment($commentId: ID!, $body: String!) {
1174
+ updateIssueComment(input: { id: $commentId, body: $body }) {
1175
+ issueComment {
1176
+ id
1177
+ body
1178
+ }
811
1179
  }
812
1180
  }
813
1181
  `;
@@ -857,6 +1225,14 @@ var REPOSITORY_ISSUE_QUERY = `
857
1225
  }
858
1226
  }
859
1227
  }
1228
+ closedByPullRequestsReferences(first: 20) {
1229
+ nodes {
1230
+ ...RepositoryIssuePullRequestMetadata
1231
+ }
1232
+ pageInfo {
1233
+ hasNextPage
1234
+ }
1235
+ }
860
1236
  projectItems(first: 20, includeArchived: false) {
861
1237
  nodes {
862
1238
  id
@@ -895,6 +1271,45 @@ var REPOSITORY_ISSUE_QUERY = `
895
1271
  }
896
1272
  }
897
1273
  }
1274
+
1275
+ fragment RepositoryIssuePullRequestMetadata on PullRequest {
1276
+ id
1277
+ number
1278
+ title
1279
+ body
1280
+ url
1281
+ state
1282
+ isDraft
1283
+ merged
1284
+ headRefName
1285
+ baseRefName
1286
+ headRepository {
1287
+ name
1288
+ url
1289
+ owner {
1290
+ login
1291
+ }
1292
+ }
1293
+ repository {
1294
+ name
1295
+ url
1296
+ owner {
1297
+ login
1298
+ }
1299
+ }
1300
+ labels(first: 20) {
1301
+ nodes {
1302
+ name
1303
+ }
1304
+ }
1305
+ assignees(first: 20) {
1306
+ nodes {
1307
+ login
1308
+ }
1309
+ }
1310
+ createdAt
1311
+ updatedAt
1312
+ }
898
1313
  `;
899
1314
 
900
1315
  // ../tracker-github/src/orchestrator-adapter.ts
@@ -949,6 +1364,15 @@ var githubProjectTrackerAdapter = {
949
1364
  },
950
1365
  metadata: {}
951
1366
  };
1367
+ },
1368
+ async upsertIssueComment(project, issue, input, dependencies = {}) {
1369
+ const trackerConfig = resolveGitHubTrackerConfig(project, dependencies);
1370
+ return upsertGithubIssueComment(
1371
+ trackerConfig,
1372
+ issue.id,
1373
+ input,
1374
+ dependencies.fetchImpl
1375
+ );
952
1376
  }
953
1377
  };
954
1378
  async function findGithubProjectIssue(project, identifier, dependencies = {}) {
@@ -1078,8 +1502,147 @@ function parseIssueIdentifier(identifier) {
1078
1502
  };
1079
1503
  }
1080
1504
 
1505
+ // src/project-selection.ts
1506
+ import * as p from "@clack/prompts";
1507
+ function isInteractiveTerminal() {
1508
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
1509
+ }
1510
+ function explicitProjectRequiredMessage() {
1511
+ return "Multiple repository runtime configs are present. Run 'gh-symphony repo init' from the target repository to refresh the cwd runtime.\n";
1512
+ }
1513
+ async function inspectManagedProjectSelection(input) {
1514
+ if (input.requestedProjectId) {
1515
+ const projectConfig = await loadProjectConfig(
1516
+ input.configDir,
1517
+ input.requestedProjectId
1518
+ );
1519
+ if (!projectConfig) {
1520
+ return {
1521
+ kind: "requested_project_missing",
1522
+ projectId: input.requestedProjectId,
1523
+ message: `Project "${input.requestedProjectId}" is not configured. Run 'gh-symphony repo init' from the target repository.`
1524
+ };
1525
+ }
1526
+ return {
1527
+ kind: "resolved",
1528
+ projectId: input.requestedProjectId,
1529
+ projectConfig
1530
+ };
1531
+ }
1532
+ const global = await loadGlobalConfig(input.configDir);
1533
+ if (!global) {
1534
+ return {
1535
+ kind: "missing_global_config",
1536
+ message: "No repository runtime config found. Run 'gh-symphony repo init' first."
1537
+ };
1538
+ }
1539
+ const projectIds = global.projects ?? [];
1540
+ if (projectIds.length === 0) {
1541
+ return {
1542
+ kind: "no_projects",
1543
+ message: "No repository runtime config is configured. Run 'gh-symphony repo init' first."
1544
+ };
1545
+ }
1546
+ if (projectIds.length > 1 && !isInteractiveTerminal()) {
1547
+ return {
1548
+ kind: "multiple_projects_require_selection",
1549
+ message: explicitProjectRequiredMessage().trimEnd()
1550
+ };
1551
+ }
1552
+ if (global.activeProject) {
1553
+ const projectConfig = await loadProjectConfig(
1554
+ input.configDir,
1555
+ global.activeProject
1556
+ );
1557
+ if (!projectConfig) {
1558
+ return {
1559
+ kind: "active_project_missing",
1560
+ projectId: global.activeProject,
1561
+ message: `Active project "${global.activeProject}" is configured in config.json but its project config is missing. Re-run 'gh-symphony repo init'.`
1562
+ };
1563
+ }
1564
+ return {
1565
+ kind: "resolved",
1566
+ projectId: global.activeProject,
1567
+ projectConfig
1568
+ };
1569
+ }
1570
+ if (projectIds.length === 1) {
1571
+ const projectId = projectIds[0];
1572
+ const projectConfig = await loadProjectConfig(input.configDir, projectId);
1573
+ if (!projectConfig) {
1574
+ return {
1575
+ kind: "configured_project_missing",
1576
+ projectId,
1577
+ message: `Configured project "${projectId}" is missing its project config file. Re-run 'gh-symphony repo init'.`
1578
+ };
1579
+ }
1580
+ return {
1581
+ kind: "resolved",
1582
+ projectId,
1583
+ projectConfig
1584
+ };
1585
+ }
1586
+ return {
1587
+ kind: "multiple_projects_require_selection",
1588
+ message: "Multiple repository runtime configs are present and no active project is set. Re-run 'gh-symphony repo init' from the target repository."
1589
+ };
1590
+ }
1591
+ async function resolveManagedProjectConfig(input) {
1592
+ if (input.requestedProjectId) {
1593
+ return loadProjectConfig(input.configDir, input.requestedProjectId);
1594
+ }
1595
+ const global = await loadGlobalConfig(input.configDir);
1596
+ const projectIds = global?.projects ?? [];
1597
+ if (projectIds.length === 0) {
1598
+ return null;
1599
+ }
1600
+ if (projectIds.length === 1) {
1601
+ return loadProjectConfig(input.configDir, projectIds[0]);
1602
+ }
1603
+ if (!isInteractiveTerminal()) {
1604
+ process.stderr.write(explicitProjectRequiredMessage());
1605
+ process.exitCode = 1;
1606
+ return null;
1607
+ }
1608
+ const projects = await Promise.all(
1609
+ projectIds.map(async (projectId) => ({
1610
+ projectId,
1611
+ config: await loadProjectConfig(input.configDir, projectId)
1612
+ }))
1613
+ );
1614
+ const selected = await p.select({
1615
+ message: "Select a project:",
1616
+ options: projects.map(({ projectId, config }) => ({
1617
+ value: projectId,
1618
+ label: config?.displayName ?? config?.slug ?? projectId,
1619
+ hint: projectId === global?.activeProject ? "current" : config && config.displayName && config.displayName !== projectId ? projectId : void 0
1620
+ })),
1621
+ maxItems: 10
1622
+ });
1623
+ if (p.isCancel(selected)) {
1624
+ p.cancel("Cancelled.");
1625
+ process.exitCode = 130;
1626
+ return null;
1627
+ }
1628
+ return loadProjectConfig(input.configDir, selected);
1629
+ }
1630
+ function handleMissingManagedProjectConfig() {
1631
+ if (process.exitCode) {
1632
+ return;
1633
+ }
1634
+ process.stderr.write(
1635
+ "No repository runtime config found. Run 'gh-symphony repo init' first.\n"
1636
+ );
1637
+ process.exitCode = 1;
1638
+ }
1639
+
1081
1640
  export {
1641
+ fetchGithubProjectIssues,
1082
1642
  fetchGithubProjectIssueByRepositoryAndNumber,
1083
1643
  findGithubProjectIssue,
1084
- resolveTrackerAdapter
1644
+ resolveTrackerAdapter,
1645
+ inspectManagedProjectSelection,
1646
+ resolveManagedProjectConfig,
1647
+ handleMissingManagedProjectConfig
1085
1648
  };