hub-launch 1.0.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 (331) hide show
  1. package/CHANGELOG.md +144 -0
  2. package/README.md +195 -0
  3. package/bin/hublaunch +20 -0
  4. package/dist/commands/checkout.d.ts +7 -0
  5. package/dist/commands/checkout.d.ts.map +1 -0
  6. package/dist/commands/checkout.js +164 -0
  7. package/dist/commands/checkout.js.map +1 -0
  8. package/dist/commands/clean.d.ts +7 -0
  9. package/dist/commands/clean.d.ts.map +1 -0
  10. package/dist/commands/clean.js +206 -0
  11. package/dist/commands/clean.js.map +1 -0
  12. package/dist/commands/close.d.ts +7 -0
  13. package/dist/commands/close.d.ts.map +1 -0
  14. package/dist/commands/close.js +109 -0
  15. package/dist/commands/close.js.map +1 -0
  16. package/dist/commands/create.d.ts +7 -0
  17. package/dist/commands/create.d.ts.map +1 -0
  18. package/dist/commands/create.js +367 -0
  19. package/dist/commands/create.js.map +1 -0
  20. package/dist/commands/diff.d.ts +7 -0
  21. package/dist/commands/diff.d.ts.map +1 -0
  22. package/dist/commands/diff.js +98 -0
  23. package/dist/commands/diff.js.map +1 -0
  24. package/dist/commands/execute.d.ts +7 -0
  25. package/dist/commands/execute.d.ts.map +1 -0
  26. package/dist/commands/execute.js +376 -0
  27. package/dist/commands/execute.js.map +1 -0
  28. package/dist/commands/init.d.ts +8 -0
  29. package/dist/commands/init.d.ts.map +1 -0
  30. package/dist/commands/init.js +1243 -0
  31. package/dist/commands/init.js.map +1 -0
  32. package/dist/commands/interactive.d.ts +12 -0
  33. package/dist/commands/interactive.d.ts.map +1 -0
  34. package/dist/commands/interactive.js +1071 -0
  35. package/dist/commands/interactive.js.map +1 -0
  36. package/dist/commands/launch.d.ts +16 -0
  37. package/dist/commands/launch.d.ts.map +1 -0
  38. package/dist/commands/launch.js +684 -0
  39. package/dist/commands/launch.js.map +1 -0
  40. package/dist/commands/login.d.ts +40 -0
  41. package/dist/commands/login.d.ts.map +1 -0
  42. package/dist/commands/login.js +469 -0
  43. package/dist/commands/login.js.map +1 -0
  44. package/dist/commands/logs.d.ts +22 -0
  45. package/dist/commands/logs.d.ts.map +1 -0
  46. package/dist/commands/logs.js +105 -0
  47. package/dist/commands/logs.js.map +1 -0
  48. package/dist/commands/merge.d.ts +7 -0
  49. package/dist/commands/merge.d.ts.map +1 -0
  50. package/dist/commands/merge.js +240 -0
  51. package/dist/commands/merge.js.map +1 -0
  52. package/dist/commands/preview.d.ts +7 -0
  53. package/dist/commands/preview.d.ts.map +1 -0
  54. package/dist/commands/preview.js +160 -0
  55. package/dist/commands/preview.js.map +1 -0
  56. package/dist/commands/project.d.ts +18 -0
  57. package/dist/commands/project.d.ts.map +1 -0
  58. package/dist/commands/project.js +352 -0
  59. package/dist/commands/project.js.map +1 -0
  60. package/dist/commands/respond.d.ts +7 -0
  61. package/dist/commands/respond.d.ts.map +1 -0
  62. package/dist/commands/respond.js +112 -0
  63. package/dist/commands/respond.js.map +1 -0
  64. package/dist/commands/setPriority.d.ts +7 -0
  65. package/dist/commands/setPriority.d.ts.map +1 -0
  66. package/dist/commands/setPriority.js +112 -0
  67. package/dist/commands/setPriority.js.map +1 -0
  68. package/dist/commands/track.d.ts +7 -0
  69. package/dist/commands/track.d.ts.map +1 -0
  70. package/dist/commands/track.js +140 -0
  71. package/dist/commands/track.js.map +1 -0
  72. package/dist/commands/upload.d.ts +21 -0
  73. package/dist/commands/upload.d.ts.map +1 -0
  74. package/dist/commands/upload.js +145 -0
  75. package/dist/commands/upload.js.map +1 -0
  76. package/dist/commands/view.d.ts +7 -0
  77. package/dist/commands/view.d.ts.map +1 -0
  78. package/dist/commands/view.js +168 -0
  79. package/dist/commands/view.js.map +1 -0
  80. package/dist/commands/watch.d.ts +7 -0
  81. package/dist/commands/watch.d.ts.map +1 -0
  82. package/dist/commands/watch.js +438 -0
  83. package/dist/commands/watch.js.map +1 -0
  84. package/dist/commands/worktree.d.ts +7 -0
  85. package/dist/commands/worktree.d.ts.map +1 -0
  86. package/dist/commands/worktree.js +122 -0
  87. package/dist/commands/worktree.js.map +1 -0
  88. package/dist/config/command-names.d.ts +16 -0
  89. package/dist/config/command-names.d.ts.map +1 -0
  90. package/dist/config/command-names.js +17 -0
  91. package/dist/config/command-names.js.map +1 -0
  92. package/dist/config/constants.d.ts +12 -0
  93. package/dist/config/constants.d.ts.map +1 -0
  94. package/dist/config/constants.js +12 -0
  95. package/dist/config/constants.js.map +1 -0
  96. package/dist/config/index.d.ts +47 -0
  97. package/dist/config/index.d.ts.map +1 -0
  98. package/dist/config/index.js +175 -0
  99. package/dist/config/index.js.map +1 -0
  100. package/dist/index.d.ts +3 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +148 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/services/api/HulaApiClient.d.ts +170 -0
  105. package/dist/services/api/HulaApiClient.d.ts.map +1 -0
  106. package/dist/services/api/HulaApiClient.js +233 -0
  107. package/dist/services/api/HulaApiClient.js.map +1 -0
  108. package/dist/services/diff/DiffService.d.ts +44 -0
  109. package/dist/services/diff/DiffService.d.ts.map +1 -0
  110. package/dist/services/diff/DiffService.js +126 -0
  111. package/dist/services/diff/DiffService.js.map +1 -0
  112. package/dist/services/editor/EditorService.d.ts +53 -0
  113. package/dist/services/editor/EditorService.d.ts.map +1 -0
  114. package/dist/services/editor/EditorService.js +161 -0
  115. package/dist/services/editor/EditorService.js.map +1 -0
  116. package/dist/services/git/GitService.d.ts +124 -0
  117. package/dist/services/git/GitService.d.ts.map +1 -0
  118. package/dist/services/git/GitService.js +423 -0
  119. package/dist/services/git/GitService.js.map +1 -0
  120. package/dist/services/git/WorktreeService.d.ts +144 -0
  121. package/dist/services/git/WorktreeService.d.ts.map +1 -0
  122. package/dist/services/git/WorktreeService.js +505 -0
  123. package/dist/services/git/WorktreeService.js.map +1 -0
  124. package/dist/services/github/ActionsService.d.ts +66 -0
  125. package/dist/services/github/ActionsService.d.ts.map +1 -0
  126. package/dist/services/github/ActionsService.js +114 -0
  127. package/dist/services/github/ActionsService.js.map +1 -0
  128. package/dist/services/github/AssetUploadService.d.ts +103 -0
  129. package/dist/services/github/AssetUploadService.d.ts.map +1 -0
  130. package/dist/services/github/AssetUploadService.js +299 -0
  131. package/dist/services/github/AssetUploadService.js.map +1 -0
  132. package/dist/services/github/BatchCopilotDetectionService.d.ts +58 -0
  133. package/dist/services/github/BatchCopilotDetectionService.d.ts.map +1 -0
  134. package/dist/services/github/BatchCopilotDetectionService.js +321 -0
  135. package/dist/services/github/BatchCopilotDetectionService.js.map +1 -0
  136. package/dist/services/github/CopilotDetectionService.d.ts +40 -0
  137. package/dist/services/github/CopilotDetectionService.d.ts.map +1 -0
  138. package/dist/services/github/CopilotDetectionService.js +280 -0
  139. package/dist/services/github/CopilotDetectionService.js.map +1 -0
  140. package/dist/services/github/CopilotService.d.ts +27 -0
  141. package/dist/services/github/CopilotService.d.ts.map +1 -0
  142. package/dist/services/github/CopilotService.js +249 -0
  143. package/dist/services/github/CopilotService.js.map +1 -0
  144. package/dist/services/github/DeploymentDetectionService.d.ts +38 -0
  145. package/dist/services/github/DeploymentDetectionService.d.ts.map +1 -0
  146. package/dist/services/github/DeploymentDetectionService.js +104 -0
  147. package/dist/services/github/DeploymentDetectionService.js.map +1 -0
  148. package/dist/services/github/GithubAppService.d.ts +40 -0
  149. package/dist/services/github/GithubAppService.d.ts.map +1 -0
  150. package/dist/services/github/GithubAppService.js +187 -0
  151. package/dist/services/github/GithubAppService.js.map +1 -0
  152. package/dist/services/github/IssueService.d.ts +65 -0
  153. package/dist/services/github/IssueService.d.ts.map +1 -0
  154. package/dist/services/github/IssueService.js +231 -0
  155. package/dist/services/github/IssueService.js.map +1 -0
  156. package/dist/services/github/LabelService.d.ts +36 -0
  157. package/dist/services/github/LabelService.d.ts.map +1 -0
  158. package/dist/services/github/LabelService.js +122 -0
  159. package/dist/services/github/LabelService.js.map +1 -0
  160. package/dist/services/github/PRService.d.ts +77 -0
  161. package/dist/services/github/PRService.d.ts.map +1 -0
  162. package/dist/services/github/PRService.js +287 -0
  163. package/dist/services/github/PRService.js.map +1 -0
  164. package/dist/services/github/ProjectService.d.ts +144 -0
  165. package/dist/services/github/ProjectService.d.ts.map +1 -0
  166. package/dist/services/github/ProjectService.js +804 -0
  167. package/dist/services/github/ProjectService.js.map +1 -0
  168. package/dist/services/hooks/HookExecutor.d.ts +66 -0
  169. package/dist/services/hooks/HookExecutor.d.ts.map +1 -0
  170. package/dist/services/hooks/HookExecutor.js +227 -0
  171. package/dist/services/hooks/HookExecutor.js.map +1 -0
  172. package/dist/services/hooks/index.d.ts +2 -0
  173. package/dist/services/hooks/index.d.ts.map +1 -0
  174. package/dist/services/hooks/index.js +2 -0
  175. package/dist/services/hooks/index.js.map +1 -0
  176. package/dist/services/logs/LogFormatterService.d.ts +17 -0
  177. package/dist/services/logs/LogFormatterService.d.ts.map +1 -0
  178. package/dist/services/logs/LogFormatterService.js +35 -0
  179. package/dist/services/logs/LogFormatterService.js.map +1 -0
  180. package/dist/services/logs/TempFileService.d.ts +27 -0
  181. package/dist/services/logs/TempFileService.d.ts.map +1 -0
  182. package/dist/services/logs/TempFileService.js +90 -0
  183. package/dist/services/logs/TempFileService.js.map +1 -0
  184. package/dist/services/logs/index.d.ts +3 -0
  185. package/dist/services/logs/index.d.ts.map +1 -0
  186. package/dist/services/logs/index.js +4 -0
  187. package/dist/services/logs/index.js.map +1 -0
  188. package/dist/services/plan/PlanService.d.ts +70 -0
  189. package/dist/services/plan/PlanService.d.ts.map +1 -0
  190. package/dist/services/plan/PlanService.js +211 -0
  191. package/dist/services/plan/PlanService.js.map +1 -0
  192. package/dist/services/polling/GitHubDirectPollingAdapter.d.ts +16 -0
  193. package/dist/services/polling/GitHubDirectPollingAdapter.d.ts.map +1 -0
  194. package/dist/services/polling/GitHubDirectPollingAdapter.js +87 -0
  195. package/dist/services/polling/GitHubDirectPollingAdapter.js.map +1 -0
  196. package/dist/services/polling/HulaServerPollingAdapter.d.ts +23 -0
  197. package/dist/services/polling/HulaServerPollingAdapter.d.ts.map +1 -0
  198. package/dist/services/polling/HulaServerPollingAdapter.js +78 -0
  199. package/dist/services/polling/HulaServerPollingAdapter.js.map +1 -0
  200. package/dist/services/polling/IStatusPollingAdapter.d.ts +29 -0
  201. package/dist/services/polling/IStatusPollingAdapter.d.ts.map +1 -0
  202. package/dist/services/polling/IStatusPollingAdapter.js +2 -0
  203. package/dist/services/polling/IStatusPollingAdapter.js.map +1 -0
  204. package/dist/services/polling/StatusPollingAdapterFactory.d.ts +14 -0
  205. package/dist/services/polling/StatusPollingAdapterFactory.d.ts.map +1 -0
  206. package/dist/services/polling/StatusPollingAdapterFactory.js +41 -0
  207. package/dist/services/polling/StatusPollingAdapterFactory.js.map +1 -0
  208. package/dist/services/polling/index.d.ts +5 -0
  209. package/dist/services/polling/index.d.ts.map +1 -0
  210. package/dist/services/polling/index.js +5 -0
  211. package/dist/services/polling/index.js.map +1 -0
  212. package/dist/services/preview/PreviewService.d.ts +86 -0
  213. package/dist/services/preview/PreviewService.d.ts.map +1 -0
  214. package/dist/services/preview/PreviewService.js +439 -0
  215. package/dist/services/preview/PreviewService.js.map +1 -0
  216. package/dist/services/tracking/ProjectTrackingAdapter.d.ts +41 -0
  217. package/dist/services/tracking/ProjectTrackingAdapter.d.ts.map +1 -0
  218. package/dist/services/tracking/ProjectTrackingAdapter.js +230 -0
  219. package/dist/services/tracking/ProjectTrackingAdapter.js.map +1 -0
  220. package/dist/services/tracking/RemoteTrackingAdapter.d.ts +62 -0
  221. package/dist/services/tracking/RemoteTrackingAdapter.d.ts.map +1 -0
  222. package/dist/services/tracking/RemoteTrackingAdapter.js +242 -0
  223. package/dist/services/tracking/RemoteTrackingAdapter.js.map +1 -0
  224. package/dist/services/tracking/TrackingAdapterFactory.d.ts +25 -0
  225. package/dist/services/tracking/TrackingAdapterFactory.d.ts.map +1 -0
  226. package/dist/services/tracking/TrackingAdapterFactory.js +55 -0
  227. package/dist/services/tracking/TrackingAdapterFactory.js.map +1 -0
  228. package/dist/services/tracking/TrackingService.d.ts +104 -0
  229. package/dist/services/tracking/TrackingService.d.ts.map +1 -0
  230. package/dist/services/tracking/TrackingService.js +256 -0
  231. package/dist/services/tracking/TrackingService.js.map +1 -0
  232. package/dist/services/tracking/UntrackedIssuesService.d.ts +20 -0
  233. package/dist/services/tracking/UntrackedIssuesService.d.ts.map +1 -0
  234. package/dist/services/tracking/UntrackedIssuesService.js +148 -0
  235. package/dist/services/tracking/UntrackedIssuesService.js.map +1 -0
  236. package/dist/services/tracking/local-file-migration.d.ts +16 -0
  237. package/dist/services/tracking/local-file-migration.d.ts.map +1 -0
  238. package/dist/services/tracking/local-file-migration.js +86 -0
  239. package/dist/services/tracking/local-file-migration.js.map +1 -0
  240. package/dist/templates/planning-instructions.md +752 -0
  241. package/dist/templates/proceed-instructions.md +453 -0
  242. package/dist/templates/scripts/hula-fix-commit.sh +110 -0
  243. package/dist/templates/scripts/hula-fix-setup.sh +145 -0
  244. package/dist/templates/scripts/hula-launch-run.sh +89 -0
  245. package/dist/templates/scripts/hula-merge-local.sh +138 -0
  246. package/dist/templates/scripts/hula-merge-remote.sh +74 -0
  247. package/dist/templates/scripts/hula-read-config.sh +38 -0
  248. package/dist/templates/scripts/hula-verify-gather.sh +135 -0
  249. package/dist/templates/scripts/hula-verify-post.sh +84 -0
  250. package/dist/templates/skills/hula-confirm/SKILL.md +361 -0
  251. package/dist/templates/skills/hula-create/SKILL.md +211 -0
  252. package/dist/templates/skills/hula-fix/SKILL.md +224 -0
  253. package/dist/templates/skills/hula-launch/SKILL.md +159 -0
  254. package/dist/templates/skills/hula-log/SKILL.md +105 -0
  255. package/dist/templates/skills/hula-merge/SKILL.md +387 -0
  256. package/dist/templates/skills/hula-plan/SKILL.md +148 -0
  257. package/dist/templates/skills/hula-upload/SKILL.md +115 -0
  258. package/dist/templates/skills/hula-verify/SKILL.md +754 -0
  259. package/dist/types/common.schema.d.ts +46 -0
  260. package/dist/types/common.schema.d.ts.map +1 -0
  261. package/dist/types/common.schema.js +60 -0
  262. package/dist/types/common.schema.js.map +1 -0
  263. package/dist/types/config.schema.d.ts +443 -0
  264. package/dist/types/config.schema.d.ts.map +1 -0
  265. package/dist/types/config.schema.js +126 -0
  266. package/dist/types/config.schema.js.map +1 -0
  267. package/dist/types/index.d.ts +6 -0
  268. package/dist/types/index.d.ts.map +1 -0
  269. package/dist/types/index.js +7 -0
  270. package/dist/types/index.js.map +1 -0
  271. package/dist/types/issue.schema.d.ts +136 -0
  272. package/dist/types/issue.schema.d.ts.map +1 -0
  273. package/dist/types/issue.schema.js +49 -0
  274. package/dist/types/issue.schema.js.map +1 -0
  275. package/dist/types/pr.schema.d.ts +118 -0
  276. package/dist/types/pr.schema.d.ts.map +1 -0
  277. package/dist/types/pr.schema.js +40 -0
  278. package/dist/types/pr.schema.js.map +1 -0
  279. package/dist/types/tracking.schema.d.ts +340 -0
  280. package/dist/types/tracking.schema.d.ts.map +1 -0
  281. package/dist/types/tracking.schema.js +56 -0
  282. package/dist/types/tracking.schema.js.map +1 -0
  283. package/dist/utils/browser-launcher.d.ts +60 -0
  284. package/dist/utils/browser-launcher.d.ts.map +1 -0
  285. package/dist/utils/browser-launcher.js +263 -0
  286. package/dist/utils/browser-launcher.js.map +1 -0
  287. package/dist/utils/cache.d.ts +44 -0
  288. package/dist/utils/cache.d.ts.map +1 -0
  289. package/dist/utils/cache.js +94 -0
  290. package/dist/utils/cache.js.map +1 -0
  291. package/dist/utils/config-parser.d.ts +16 -0
  292. package/dist/utils/config-parser.d.ts.map +1 -0
  293. package/dist/utils/config-parser.js +210 -0
  294. package/dist/utils/config-parser.js.map +1 -0
  295. package/dist/utils/editor.d.ts +21 -0
  296. package/dist/utils/editor.d.ts.map +1 -0
  297. package/dist/utils/editor.js +213 -0
  298. package/dist/utils/editor.js.map +1 -0
  299. package/dist/utils/git-utils.d.ts +5 -0
  300. package/dist/utils/git-utils.d.ts.map +1 -0
  301. package/dist/utils/git-utils.js +18 -0
  302. package/dist/utils/git-utils.js.map +1 -0
  303. package/dist/utils/github-cli.d.ts +62 -0
  304. package/dist/utils/github-cli.d.ts.map +1 -0
  305. package/dist/utils/github-cli.js +492 -0
  306. package/dist/utils/github-cli.js.map +1 -0
  307. package/dist/utils/logger.d.ts +45 -0
  308. package/dist/utils/logger.d.ts.map +1 -0
  309. package/dist/utils/logger.js +118 -0
  310. package/dist/utils/logger.js.map +1 -0
  311. package/dist/utils/open-url.d.ts +19 -0
  312. package/dist/utils/open-url.d.ts.map +1 -0
  313. package/dist/utils/open-url.js +37 -0
  314. package/dist/utils/open-url.js.map +1 -0
  315. package/dist/utils/project-resolver.d.ts +13 -0
  316. package/dist/utils/project-resolver.d.ts.map +1 -0
  317. package/dist/utils/project-resolver.js +35 -0
  318. package/dist/utils/project-resolver.js.map +1 -0
  319. package/dist/utils/prompts.d.ts +80 -0
  320. package/dist/utils/prompts.d.ts.map +1 -0
  321. package/dist/utils/prompts.js +326 -0
  322. package/dist/utils/prompts.js.map +1 -0
  323. package/dist/utils/shell.d.ts +54 -0
  324. package/dist/utils/shell.d.ts.map +1 -0
  325. package/dist/utils/shell.js +152 -0
  326. package/dist/utils/shell.js.map +1 -0
  327. package/dist/utils/validators.d.ts +32 -0
  328. package/dist/utils/validators.d.ts.map +1 -0
  329. package/dist/utils/validators.js +64 -0
  330. package/dist/utils/validators.js.map +1 -0
  331. package/package.json +73 -0
@@ -0,0 +1,804 @@
1
+ import { ghGraphQL, getRepoInfo, runGh } from '../../utils/github-cli.js';
2
+ import { logger } from '../../utils/logger.js';
3
+ import { STATUS } from '../../types/common.schema.js';
4
+ import { DeploymentDetectionService } from './DeploymentDetectionService.js';
5
+ /**
6
+ * Service for GitHub Projects V2 operations
7
+ * Single Responsibility: GitHub Projects integration
8
+ */
9
+ export class ProjectService {
10
+ fieldsCache = new Map();
11
+ /**
12
+ * Ensure GitHub CLI has project scope
13
+ */
14
+ async ensureProjectScope() {
15
+ logger.info('Checking GitHub CLI project scope...');
16
+ try {
17
+ // Check current scopes
18
+ const status = await runGh(['auth', 'status'], { silent: true });
19
+ if (status.includes('project')) {
20
+ logger.success('Project scope already configured');
21
+ return true;
22
+ }
23
+ logger.warning('Project scope not found. Adding...');
24
+ // Add project scope with hostname to avoid interactive prompt
25
+ await runGh([
26
+ 'auth',
27
+ 'refresh',
28
+ '-s',
29
+ 'project',
30
+ '--hostname',
31
+ 'github.com',
32
+ ]);
33
+ logger.success('Project scope added successfully');
34
+ return true;
35
+ }
36
+ catch (error) {
37
+ logger.error('Failed to add project scope');
38
+ logger.error(error instanceof Error ? error.message : String(error));
39
+ return false;
40
+ }
41
+ }
42
+ /**
43
+ * Find existing HubLaunch project
44
+ */
45
+ async findExistingProject(owner) {
46
+ logger.info("Searching for existing 'HubLaunch Issue Tracking' project...");
47
+ try {
48
+ const result = await runGh([
49
+ 'project',
50
+ 'list',
51
+ '--owner',
52
+ owner,
53
+ '--format',
54
+ 'json',
55
+ ]);
56
+ const projects = JSON.parse(result);
57
+ const project = projects.projects.find((p) => p.title === 'HubLaunch Issue Tracking');
58
+ if (project) {
59
+ logger.success(`Found existing project: #${project.number}`);
60
+ return project.number;
61
+ }
62
+ return null;
63
+ }
64
+ catch (error) {
65
+ logger.error('Could not list projects');
66
+ logger.debug(error instanceof Error ? error.message : String(error));
67
+ return null;
68
+ }
69
+ }
70
+ /**
71
+ * Create new HubLaunch project
72
+ */
73
+ async createProject(owner) {
74
+ logger.info("Creating new 'HubLaunch Issue Tracking' project...");
75
+ try {
76
+ const result = await runGh([
77
+ 'project',
78
+ 'create',
79
+ '--owner',
80
+ owner,
81
+ '--title',
82
+ 'HubLaunch Issue Tracking',
83
+ '--format',
84
+ 'json',
85
+ ]);
86
+ const data = JSON.parse(result);
87
+ if (!data.number) {
88
+ throw new Error('Could not extract project number from response');
89
+ }
90
+ logger.success(`Created project: #${data.number}`);
91
+ return data.number;
92
+ }
93
+ catch (error) {
94
+ logger.error('Failed to create project');
95
+ throw error;
96
+ }
97
+ }
98
+ /**
99
+ * Get or create project
100
+ */
101
+ async getOrCreateProject(owner) {
102
+ // First check if project exists
103
+ const existingProject = await this.findExistingProject(owner);
104
+ if (existingProject !== null) {
105
+ return existingProject;
106
+ }
107
+ // Create new project
108
+ return this.createProject(owner);
109
+ }
110
+ /**
111
+ * Get project global ID
112
+ */
113
+ async getProjectId(owner, projectNumber) {
114
+ const query = `
115
+ query($owner: String!, $number: Int!) {
116
+ organization(login: $owner) {
117
+ projectV2(number: $number) {
118
+ id
119
+ }
120
+ }
121
+ }
122
+ `;
123
+ try {
124
+ const result = await ghGraphQL(query, { owner, number: projectNumber }, { suppressErrors: true });
125
+ // Check if organization query returned data
126
+ if (result.data.organization?.projectV2?.id) {
127
+ return result.data.organization.projectV2.id;
128
+ }
129
+ // Organization query returned null, try user query
130
+ throw new Error('Organization not found or project not accessible');
131
+ }
132
+ catch (error) {
133
+ // Try user query if organization fails
134
+ logger.debug(`Organization query failed, trying user query for ${owner}`);
135
+ const userQuery = `
136
+ query($owner: String!, $number: Int!) {
137
+ user(login: $owner) {
138
+ projectV2(number: $number) {
139
+ id
140
+ }
141
+ }
142
+ }
143
+ `;
144
+ try {
145
+ const userResult = await ghGraphQL(userQuery, { owner, number: projectNumber }, { suppressErrors: true });
146
+ if (userResult.data.user?.projectV2?.id) {
147
+ return userResult.data.user.projectV2.id;
148
+ }
149
+ throw new Error('User project not found or not accessible');
150
+ }
151
+ catch (userError) {
152
+ logger.debug(`Both organization and user queries failed for ${owner}/#${projectNumber}`);
153
+ throw new Error(`Could not find project #${projectNumber} for owner "${owner}". Ensure the owner is correct and you have access to the project.`);
154
+ }
155
+ }
156
+ }
157
+ /**
158
+ * List project fields (with caching to avoid rate limits)
159
+ */
160
+ async listFields(owner, projectNumber) {
161
+ const cacheKey = `${owner}/${projectNumber}`;
162
+ // Check cache first
163
+ if (this.fieldsCache.has(cacheKey)) {
164
+ logger.debug(`Using cached fields for project ${cacheKey}`);
165
+ return this.fieldsCache.get(cacheKey);
166
+ }
167
+ try {
168
+ logger.debug(`Fetching fields for project ${cacheKey}`);
169
+ const result = await runGh([
170
+ 'project',
171
+ 'field-list',
172
+ projectNumber.toString(),
173
+ '--owner',
174
+ owner,
175
+ '--format',
176
+ 'json',
177
+ ]);
178
+ const data = JSON.parse(result);
179
+ // Cache the result
180
+ this.fieldsCache.set(cacheKey, data.fields);
181
+ return data.fields;
182
+ }
183
+ catch (error) {
184
+ logger.error('Could not list project fields');
185
+ throw error;
186
+ }
187
+ }
188
+ /**
189
+ * Create a single-select field with options using GraphQL
190
+ */
191
+ async createSingleSelectField(projectId, fieldName, options) {
192
+ logger.info(`Creating '${fieldName}' field...`);
193
+ const mutation = `
194
+ mutation($projectId: ID!, $name: String!, $dataType: ProjectV2CustomFieldType!, $options: [ProjectV2SingleSelectFieldOptionInput!]!) {
195
+ createProjectV2Field(
196
+ input: {
197
+ projectId: $projectId
198
+ dataType: $dataType
199
+ name: $name
200
+ singleSelectOptions: $options
201
+ }
202
+ ) {
203
+ projectV2Field {
204
+ ... on ProjectV2SingleSelectField {
205
+ id
206
+ name
207
+ options {
208
+ id
209
+ name
210
+ }
211
+ }
212
+ }
213
+ }
214
+ }
215
+ `;
216
+ try {
217
+ // Use runCommandWithStdin for complex GraphQL mutations with arrays
218
+ const variables = {
219
+ projectId,
220
+ name: fieldName,
221
+ dataType: 'SINGLE_SELECT',
222
+ options: options.map((opt) => ({
223
+ name: opt.name,
224
+ color: opt.color || 'GRAY',
225
+ description: opt.description || '',
226
+ })),
227
+ };
228
+ const payload = JSON.stringify({
229
+ query: mutation,
230
+ variables,
231
+ });
232
+ // Use runCommandWithStdin to send complex JSON payload
233
+ const { runCommandWithStdin } = await import('../../utils/shell.js');
234
+ const result = await runCommandWithStdin('gh api graphql --input -', payload);
235
+ const parsed = JSON.parse(result);
236
+ const fieldId = parsed.data.createProjectV2Field.projectV2Field.id;
237
+ logger.success(`Created '${fieldName}' field with ${options.length} options`);
238
+ // Clear cache to force refresh
239
+ this.fieldsCache.clear();
240
+ return fieldId;
241
+ }
242
+ catch (error) {
243
+ logger.error(`Failed to create '${fieldName}' field`);
244
+ throw error;
245
+ }
246
+ }
247
+ /**
248
+ * Ensure single-select field exists with given options
249
+ */
250
+ async ensureSingleSelectField(owner, projectNumber, projectId, fieldName, options) {
251
+ logger.info(`Checking for '${fieldName}' field...`);
252
+ const fields = await this.listFields(owner, projectNumber);
253
+ const field = fields.find((f) => f.name === fieldName);
254
+ if (field) {
255
+ logger.success(`Found '${fieldName}' field`);
256
+ return field.id;
257
+ }
258
+ // Create the field with options
259
+ const fieldOptions = options.map((name) => ({ name }));
260
+ return this.createSingleSelectField(projectId, fieldName, fieldOptions);
261
+ }
262
+ /**
263
+ * Ensure tracking name field exists
264
+ */
265
+ async ensureTrackingNameField(owner, projectNumber) {
266
+ logger.info("Checking for 'Tracking Name' field...");
267
+ const fields = await this.listFields(owner, projectNumber);
268
+ const trackingField = fields.find((f) => f.name === 'Tracking Name');
269
+ if (trackingField) {
270
+ logger.success("Found 'Tracking Name' field");
271
+ return trackingField.id;
272
+ }
273
+ // Create the field
274
+ logger.info("Creating 'Tracking Name' field...");
275
+ try {
276
+ const result = await runGh([
277
+ 'project',
278
+ 'field-create',
279
+ projectNumber.toString(),
280
+ '--owner',
281
+ owner,
282
+ '--name',
283
+ 'Tracking Name',
284
+ '--data-type',
285
+ 'TEXT',
286
+ '--format',
287
+ 'json',
288
+ ]);
289
+ const data = JSON.parse(result);
290
+ if (!data.id) {
291
+ throw new Error('Could not extract field ID from response');
292
+ }
293
+ logger.success("Created 'Tracking Name' field");
294
+ return data.id;
295
+ }
296
+ catch (error) {
297
+ logger.error('Failed to create field');
298
+ throw error;
299
+ }
300
+ }
301
+ /**
302
+ * Add issue to project
303
+ */
304
+ async addIssueToProject(issueNumber, projectNumber, owner) {
305
+ const repo = await getRepoInfo();
306
+ const repoOwner = owner ?? repo.owner;
307
+ const issueUrl = `https://github.com/${repo.owner}/${repo.name}/issues/${issueNumber}`;
308
+ logger.info(`Adding issue #${issueNumber} to project...`);
309
+ try {
310
+ const result = await runGh([
311
+ 'project',
312
+ 'item-add',
313
+ projectNumber.toString(),
314
+ '--owner',
315
+ repoOwner,
316
+ '--url',
317
+ issueUrl,
318
+ '--format',
319
+ 'json',
320
+ ]);
321
+ const data = JSON.parse(result);
322
+ if (!data.id) {
323
+ throw new Error('Could not extract item ID from response');
324
+ }
325
+ logger.success('Added issue to project');
326
+ return data.id;
327
+ }
328
+ catch (error) {
329
+ logger.warning('Could not add issue to project');
330
+ throw error;
331
+ }
332
+ }
333
+ /**
334
+ * Update text field in project item
335
+ */
336
+ async updateTextField(projectId, itemId, fieldId, value) {
337
+ const mutation = `
338
+ mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: String!) {
339
+ updateProjectV2ItemFieldValue(
340
+ input: {
341
+ projectId: $projectId
342
+ itemId: $itemId
343
+ fieldId: $fieldId
344
+ value: {
345
+ text: $value
346
+ }
347
+ }
348
+ ) {
349
+ projectV2Item {
350
+ id
351
+ }
352
+ }
353
+ }
354
+ `;
355
+ await ghGraphQL(mutation, {
356
+ projectId,
357
+ itemId,
358
+ fieldId,
359
+ value,
360
+ });
361
+ }
362
+ /**
363
+ * Update single-select field in project item
364
+ */
365
+ async updateSingleSelectField(projectId, itemId, fieldId, optionId) {
366
+ const mutation = `
367
+ mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
368
+ updateProjectV2ItemFieldValue(
369
+ input: {
370
+ projectId: $projectId
371
+ itemId: $itemId
372
+ fieldId: $fieldId
373
+ value: {
374
+ singleSelectOptionId: $optionId
375
+ }
376
+ }
377
+ ) {
378
+ projectV2Item {
379
+ id
380
+ }
381
+ }
382
+ }
383
+ `;
384
+ await ghGraphQL(mutation, {
385
+ projectId,
386
+ itemId,
387
+ fieldId,
388
+ optionId,
389
+ });
390
+ }
391
+ /**
392
+ * Set tracking name for project item
393
+ */
394
+ async setTrackingName(issueNumber, trackingName, projectNumber, projectId, owner) {
395
+ const repo = await getRepoInfo();
396
+ const repoOwner = owner ?? repo.owner;
397
+ // Get or create tracking name field
398
+ const fieldId = await this.ensureTrackingNameField(repoOwner, projectNumber);
399
+ // Add issue to project (or get existing item)
400
+ let itemId;
401
+ try {
402
+ itemId = await this.addIssueToProject(issueNumber, projectNumber, repoOwner);
403
+ }
404
+ catch (error) {
405
+ logger.warning('Issue may already be in project, continuing...');
406
+ // If item already exists, we need to find it
407
+ // For now, rethrow the error
408
+ throw error;
409
+ }
410
+ // Update the tracking name field
411
+ logger.info('Setting tracking name...');
412
+ await this.updateTextField(projectId, itemId, fieldId, trackingName);
413
+ logger.success('Tracking name set successfully');
414
+ }
415
+ /**
416
+ * Remove issue from project
417
+ */
418
+ async removeIssueFromProject(itemId, projectNumber, owner) {
419
+ const repo = await getRepoInfo();
420
+ const repoOwner = owner ?? repo.owner;
421
+ logger.info('Removing issue from project...');
422
+ try {
423
+ await runGh([
424
+ 'project',
425
+ 'item-delete',
426
+ projectNumber.toString(),
427
+ '--owner',
428
+ repoOwner,
429
+ '--id',
430
+ itemId,
431
+ ]);
432
+ logger.success('Removed issue from project');
433
+ }
434
+ catch (error) {
435
+ logger.warning('Could not remove issue from project');
436
+ throw error;
437
+ }
438
+ }
439
+ /**
440
+ * Get project info
441
+ */
442
+ async getProjectInfo(owner, projectNumber) {
443
+ const query = `
444
+ query($owner: String!, $number: Int!) {
445
+ organization(login: $owner) {
446
+ projectV2(number: $number) {
447
+ id
448
+ title
449
+ url
450
+ }
451
+ }
452
+ }
453
+ `;
454
+ try {
455
+ const result = await ghGraphQL(query, { owner, number: projectNumber }, { suppressErrors: true });
456
+ return {
457
+ number: projectNumber,
458
+ id: result.data.organization.projectV2.id,
459
+ title: result.data.organization.projectV2.title,
460
+ url: result.data.organization.projectV2.url,
461
+ };
462
+ }
463
+ catch (error) {
464
+ // Try user query if organization fails
465
+ const userQuery = `
466
+ query($owner: String!, $number: Int!) {
467
+ user(login: $owner) {
468
+ projectV2(number: $number) {
469
+ id
470
+ title
471
+ url
472
+ }
473
+ }
474
+ }
475
+ `;
476
+ try {
477
+ const userResult = await ghGraphQL(userQuery, { owner, number: projectNumber }, { suppressErrors: true });
478
+ return {
479
+ number: projectNumber,
480
+ id: userResult.data.user.projectV2.id,
481
+ title: userResult.data.user.projectV2.title,
482
+ url: userResult.data.user.projectV2.url,
483
+ };
484
+ }
485
+ catch (userError) {
486
+ throw new Error(`Could not find project #${projectNumber} for owner "${owner}"`);
487
+ }
488
+ }
489
+ }
490
+ /**
491
+ * Find field by name
492
+ */
493
+ async findFieldByName(owner, projectNumber, fieldName) {
494
+ const fields = await this.listFields(owner, projectNumber);
495
+ return fields.find((f) => f.name === fieldName) ?? null;
496
+ }
497
+ /**
498
+ * Get option ID for single-select field
499
+ */
500
+ async getFieldOptionId(owner, projectNumber, fieldName, optionName) {
501
+ const field = await this.findFieldByName(owner, projectNumber, fieldName);
502
+ if (!field?.options) {
503
+ logger.debug(`[getFieldOptionId] Field ${fieldName} has no options`);
504
+ return null;
505
+ }
506
+ logger.debug(`[getFieldOptionId] Looking for option "${optionName}" in field "${fieldName}"`);
507
+ logger.debug(`[getFieldOptionId] Available options: ${field.options
508
+ .map((o) => `"${o.name}"`)
509
+ .join(', ')}`);
510
+ const option = field.options.find((o) => o.name === optionName);
511
+ if (option) {
512
+ logger.debug(`[getFieldOptionId] Found option "${optionName}" with id: ${option.id}`);
513
+ }
514
+ else {
515
+ logger.debug(`[getFieldOptionId] Option "${optionName}" not found in field "${fieldName}"`);
516
+ }
517
+ return option?.id ?? null;
518
+ }
519
+ /**
520
+ * Update status field for an issue in project
521
+ */
522
+ async updateIssueStatus(itemId, status, projectId, owner, projectNumber) {
523
+ const field = await this.findFieldByName(owner, projectNumber, 'trackingStatus');
524
+ if (!field) {
525
+ throw new Error('trackingStatus field not found in project');
526
+ }
527
+ const optionId = await this.getFieldOptionId(owner, projectNumber, 'trackingStatus', status);
528
+ if (!optionId) {
529
+ throw new Error(`Status option "${status}" not found in trackingStatus field`);
530
+ }
531
+ await this.updateSingleSelectField(projectId, itemId, field.id, optionId);
532
+ }
533
+ /**
534
+ * Update priority field for an issue in project
535
+ */
536
+ async updateIssuePriority(itemId, priority, projectId, owner, projectNumber) {
537
+ const field = await this.findFieldByName(owner, projectNumber, 'priority');
538
+ if (!field) {
539
+ throw new Error('priority field not found in project');
540
+ }
541
+ const optionId = await this.getFieldOptionId(owner, projectNumber, 'priority', priority);
542
+ if (!optionId) {
543
+ throw new Error(`Priority option "${priority}" not found in priority field`);
544
+ }
545
+ await this.updateSingleSelectField(projectId, itemId, field.id, optionId);
546
+ }
547
+ /**
548
+ * Set issue tracking status (OwnerIsTracking or AssigneeIsFollowing)
549
+ * Determines which field to update based on whether current user is the issue creator
550
+ */
551
+ async setIssueTracking(itemId, isTracking, projectId, owner, projectNumber) {
552
+ logger.debug(`[setIssueTracking] Starting for itemId: ${itemId}, isTracking: ${isTracking}`);
553
+ // Get the issue to check creator
554
+ const { ghGraphQL: gql } = await import('../../utils/github-cli.js');
555
+ // Get current user
556
+ const userQuery = `
557
+ query {
558
+ viewer {
559
+ login
560
+ }
561
+ }
562
+ `;
563
+ const userResult = await gql(userQuery, {});
564
+ const currentUser = userResult.data.viewer.login;
565
+ logger.debug(`[setIssueTracking] Current user: ${currentUser}`);
566
+ // Get issue creator from the item
567
+ const itemQuery = `
568
+ query($itemId: ID!) {
569
+ node(id: $itemId) {
570
+ ... on ProjectV2Item {
571
+ content {
572
+ ... on Issue {
573
+ number
574
+ author {
575
+ login
576
+ }
577
+ }
578
+ }
579
+ }
580
+ }
581
+ }
582
+ `;
583
+ const itemResult = await gql(itemQuery, { itemId });
584
+ const issueCreator = itemResult.data.node.content.author.login;
585
+ const isOwner = issueCreator === currentUser;
586
+ logger.debug(`[setIssueTracking] Issue creator: ${issueCreator}, isOwner: ${isOwner}`);
587
+ // Determine which field to update
588
+ const fieldName = isOwner ? 'OwnerIsTracking' : 'AssigneeIsFollowing';
589
+ logger.debug(`[setIssueTracking] Will update field: ${fieldName}`);
590
+ const field = await this.findFieldByName(owner, projectNumber, fieldName);
591
+ if (!field) {
592
+ logger.error(`${fieldName} field not found in project`);
593
+ return;
594
+ }
595
+ logger.debug(`[setIssueTracking] Found field: ${fieldName}, id: ${field.id}`);
596
+ // Get option ID for "true" or "false"
597
+ const optionValue = isTracking ? 'true' : 'false';
598
+ const optionId = await this.getFieldOptionId(owner, projectNumber, fieldName, optionValue);
599
+ if (!optionId) {
600
+ logger.error(`Option "${optionValue}" not found in ${fieldName} field`);
601
+ return;
602
+ }
603
+ logger.debug(`[setIssueTracking] Found option: ${optionValue}, id: ${optionId}`);
604
+ await this.updateSingleSelectField(projectId, itemId, field.id, optionId);
605
+ logger.info(`✓ Set ${fieldName} = ${optionValue} for item ${itemId}`);
606
+ }
607
+ /**
608
+ * Get all issues from project with their fields
609
+ */
610
+ async getProjectIssues(owner, projectNumber) {
611
+ const query = `
612
+ query($owner: String!, $projectNumber: Int!) {
613
+ organization(login: $owner) {
614
+ projectV2(number: $projectNumber) {
615
+ items(first: 100) {
616
+ nodes {
617
+ id
618
+ content {
619
+ ... on Issue {
620
+ number
621
+ title
622
+ state
623
+ timelineItems(first: 20, itemTypes: [CROSS_REFERENCED_EVENT]) {
624
+ nodes {
625
+ ... on CrossReferencedEvent {
626
+ source {
627
+ ... on PullRequest {
628
+ number
629
+ url
630
+ state
631
+ comments(last: 10) {
632
+ nodes {
633
+ author {
634
+ login
635
+ }
636
+ body
637
+ createdAt
638
+ }
639
+ }
640
+ }
641
+ }
642
+ }
643
+ }
644
+ }
645
+ }
646
+ }
647
+ fieldValues(first: 20) {
648
+ nodes {
649
+ ... on ProjectV2ItemFieldTextValue {
650
+ text
651
+ field {
652
+ ... on ProjectV2FieldCommon {
653
+ name
654
+ }
655
+ }
656
+ }
657
+ ... on ProjectV2ItemFieldSingleSelectValue {
658
+ name
659
+ field {
660
+ ... on ProjectV2FieldCommon {
661
+ name
662
+ }
663
+ }
664
+ }
665
+ }
666
+ }
667
+ }
668
+ }
669
+ }
670
+ }
671
+ }
672
+ `;
673
+ try {
674
+ logger.debug(`Fetching project issues for ${owner}/${projectNumber}`);
675
+ logger.debug(`Trying organization query first...`);
676
+ const result = await ghGraphQL(query, { owner, projectNumber }, { suppressErrors: true });
677
+ logger.debug(`Successfully fetched ${result.data.organization.projectV2.items.nodes.length} items from project`);
678
+ const issues = result.data.organization.projectV2.items.nodes
679
+ .filter((item) => item.content?.number)
680
+ .map((item) => {
681
+ const fields = item.fieldValues.nodes;
682
+ const trackingName = fields.find((f) => f.field?.name === 'Tracking Name')?.text ?? '';
683
+ const status = fields.find((f) => f.field?.name === 'trackingStatus')?.name ??
684
+ STATUS.UNASSIGNED;
685
+ const priority = fields.find((f) => f.field?.name === 'priority')?.name ?? 'Medium';
686
+ // Check for tracking fields - only 'true' means tracked, undefined = false
687
+ const ownerField = fields.find((f) => f.field?.name === 'OwnerIsTracking');
688
+ const assigneeField = fields.find((f) => f.field?.name === 'AssigneeIsFollowing');
689
+ // Only consider tracked if explicitly set to "true" (case-insensitive)
690
+ // undefined or any other value = false
691
+ const ownerIsTracking = ownerField?.name?.toLowerCase() === 'true';
692
+ const assigneeIsFollowing = assigneeField?.name?.toLowerCase() === 'true';
693
+ const githubState = item.content.state === 'OPEN' ? 'open' : 'closed';
694
+ // Find linked PR from timeline events and detect deployment status
695
+ let prNumber;
696
+ let prUrl;
697
+ let deploymentStatus;
698
+ let deploymentTimestamp;
699
+ if (item.content.timelineItems?.nodes) {
700
+ // Find first PR that references this issue
701
+ const prEvent = item.content.timelineItems.nodes.find((node) => node.source?.number && node.source?.url);
702
+ if (prEvent?.source) {
703
+ prNumber = prEvent.source.number;
704
+ prUrl = prEvent.source.url;
705
+ // Detect deployment status from PR comments
706
+ if (prEvent.source.comments?.nodes) {
707
+ const deploymentService = new DeploymentDetectionService();
708
+ const comments = prEvent.source.comments.nodes.map((c) => ({
709
+ author: c.author.login,
710
+ body: c.body,
711
+ createdAt: c.createdAt,
712
+ }));
713
+ const deploymentResult = deploymentService.detectDeploymentStatus(comments);
714
+ deploymentStatus = deploymentResult.status;
715
+ deploymentTimestamp = deploymentResult.timestamp;
716
+ }
717
+ }
718
+ }
719
+ return {
720
+ number: item.content.number,
721
+ title: item.content.title,
722
+ trackingName,
723
+ status,
724
+ priority,
725
+ itemId: item.id,
726
+ githubState,
727
+ ownerIsTracking,
728
+ assigneeIsFollowing,
729
+ prNumber,
730
+ prUrl,
731
+ deploymentStatus,
732
+ deploymentTimestamp,
733
+ };
734
+ });
735
+ return issues;
736
+ }
737
+ catch (error) {
738
+ // Try user query if organization fails
739
+ logger.debug(`Organization query failed, trying user query...`);
740
+ logger.debug(`Error: ${error instanceof Error ? error.message : String(error)}`);
741
+ const userQuery = query.replace('organization', 'user');
742
+ try {
743
+ const userResult = await ghGraphQL(userQuery, { owner, projectNumber }, { suppressErrors: true });
744
+ const issues = userResult.data.user.projectV2.items.nodes
745
+ .filter((item) => item.content?.number)
746
+ .map((item) => {
747
+ const fields = item.fieldValues.nodes;
748
+ const trackingName = fields.find((f) => f.field?.name === 'Tracking Name')?.text ?? '';
749
+ const status = fields.find((f) => f.field?.name === 'trackingStatus')?.name ??
750
+ STATUS.UNASSIGNED;
751
+ const priority = fields.find((f) => f.field?.name === 'priority')?.name ??
752
+ 'Medium';
753
+ // Check for tracking fields - only 'true' means tracked, undefined = false
754
+ const ownerField = fields.find((f) => f.field?.name === 'OwnerIsTracking');
755
+ const assigneeField = fields.find((f) => f.field?.name === 'AssigneeIsFollowing');
756
+ // Only consider tracked if explicitly set to "true" (case-insensitive)
757
+ const ownerIsTracking = ownerField?.name?.toLowerCase() === 'true';
758
+ const assigneeIsFollowing = assigneeField?.name?.toLowerCase() === 'true';
759
+ const githubState = item.content.state === 'OPEN' ? 'open' : 'closed';
760
+ // Find linked PR from timeline events
761
+ let prNumber;
762
+ let prUrl;
763
+ if (item.content.timelineItems?.nodes) {
764
+ // Find first PR that references this issue
765
+ const prEvent = item.content.timelineItems.nodes.find((node) => node.source?.number && node.source?.url);
766
+ if (prEvent?.source) {
767
+ prNumber = prEvent.source.number;
768
+ prUrl = prEvent.source.url;
769
+ }
770
+ }
771
+ return {
772
+ number: item.content.number,
773
+ title: item.content.title,
774
+ trackingName,
775
+ status,
776
+ priority,
777
+ itemId: item.id,
778
+ githubState,
779
+ ownerIsTracking,
780
+ assigneeIsFollowing,
781
+ prNumber,
782
+ prUrl,
783
+ };
784
+ });
785
+ return issues;
786
+ }
787
+ catch (userError) {
788
+ logger.debug(`Both organization and user queries failed for project #${projectNumber}`);
789
+ logger.debug(`Original error: ${error instanceof Error ? error.message : String(error)}`);
790
+ logger.debug(`User error: ${userError instanceof Error ? userError.message : String(userError)}`);
791
+ throw new Error(`Could not access project #${projectNumber} for owner "${owner}". Ensure the owner name is correct and you have access.`);
792
+ }
793
+ }
794
+ }
795
+ /**
796
+ * Find project item by issue number
797
+ */
798
+ async findProjectItem(owner, projectNumber, issueNumber) {
799
+ const issues = await this.getProjectIssues(owner, projectNumber);
800
+ const issue = issues.find((i) => i.number === issueNumber);
801
+ return issue?.itemId ?? null;
802
+ }
803
+ }
804
+ //# sourceMappingURL=ProjectService.js.map