panopticon-cli 0.4.32 → 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.
Files changed (142) hide show
  1. package/README.md +96 -210
  2. package/dist/{agents-BDFHF4T3.js → agents-E43Y3HNU.js} +10 -7
  3. package/dist/chunk-7SN4L4PH.js +150 -0
  4. package/dist/chunk-7SN4L4PH.js.map +1 -0
  5. package/dist/{chunk-2NIAOCIC.js → chunk-AAFQANKW.js} +358 -97
  6. package/dist/chunk-AAFQANKW.js.map +1 -0
  7. package/dist/chunk-AQXETQHW.js +113 -0
  8. package/dist/chunk-AQXETQHW.js.map +1 -0
  9. package/dist/chunk-B3PF6JPQ.js +212 -0
  10. package/dist/chunk-B3PF6JPQ.js.map +1 -0
  11. package/dist/chunk-CFCUOV3Q.js +669 -0
  12. package/dist/chunk-CFCUOV3Q.js.map +1 -0
  13. package/dist/chunk-CWELWPWQ.js +32 -0
  14. package/dist/chunk-CWELWPWQ.js.map +1 -0
  15. package/dist/chunk-DI7ABPNQ.js +352 -0
  16. package/dist/chunk-DI7ABPNQ.js.map +1 -0
  17. package/dist/{chunk-VU4FLXV5.js → chunk-FQ66DECN.js} +31 -4
  18. package/dist/chunk-FQ66DECN.js.map +1 -0
  19. package/dist/{chunk-VIWUCJ4V.js → chunk-FTCPTHIJ.js} +57 -432
  20. package/dist/chunk-FTCPTHIJ.js.map +1 -0
  21. package/dist/{review-status-GWQYY77L.js → chunk-GFP3PIPB.js} +14 -7
  22. package/dist/chunk-GFP3PIPB.js.map +1 -0
  23. package/dist/chunk-GR6ZZMCX.js +816 -0
  24. package/dist/chunk-GR6ZZMCX.js.map +1 -0
  25. package/dist/chunk-HJSM6E6U.js +1038 -0
  26. package/dist/chunk-HJSM6E6U.js.map +1 -0
  27. package/dist/{chunk-XP2DXWYP.js → chunk-HZT2AOPN.js} +164 -39
  28. package/dist/chunk-HZT2AOPN.js.map +1 -0
  29. package/dist/chunk-JQBV3Q2W.js +29 -0
  30. package/dist/chunk-JQBV3Q2W.js.map +1 -0
  31. package/dist/{chunk-BWGFN44T.js → chunk-JT4O4YVM.js} +28 -16
  32. package/dist/chunk-JT4O4YVM.js.map +1 -0
  33. package/dist/chunk-NTO3EDB3.js +600 -0
  34. package/dist/chunk-NTO3EDB3.js.map +1 -0
  35. package/dist/{chunk-JY7R7V4G.js → chunk-OMNXYPXC.js} +2 -2
  36. package/dist/chunk-OMNXYPXC.js.map +1 -0
  37. package/dist/chunk-PELXV435.js +215 -0
  38. package/dist/chunk-PELXV435.js.map +1 -0
  39. package/dist/chunk-PPRFKTVC.js +154 -0
  40. package/dist/chunk-PPRFKTVC.js.map +1 -0
  41. package/dist/chunk-WQG2TYCB.js +677 -0
  42. package/dist/chunk-WQG2TYCB.js.map +1 -0
  43. package/dist/{chunk-HCTJFIJJ.js → chunk-YLPSQAM2.js} +2 -2
  44. package/dist/{chunk-HCTJFIJJ.js.map → chunk-YLPSQAM2.js.map} +1 -1
  45. package/dist/{chunk-6HXKTOD7.js → chunk-ZTFNYOC7.js} +53 -38
  46. package/dist/chunk-ZTFNYOC7.js.map +1 -0
  47. package/dist/cli/index.js +5103 -3165
  48. package/dist/cli/index.js.map +1 -1
  49. package/dist/{config-BOAMSKTF.js → config-4CJNUE3O.js} +7 -3
  50. package/dist/dashboard/prompts/merge-agent.md +217 -0
  51. package/dist/dashboard/prompts/review-agent.md +409 -0
  52. package/dist/dashboard/prompts/sync-main.md +84 -0
  53. package/dist/dashboard/prompts/test-agent.md +283 -0
  54. package/dist/dashboard/prompts/work-agent.md +249 -0
  55. package/dist/dashboard/public/assets/index-BxpjweAL.css +32 -0
  56. package/dist/dashboard/public/assets/index-DQHkwvvJ.js +743 -0
  57. package/dist/dashboard/public/index.html +2 -2
  58. package/dist/dashboard/server.js +17619 -4044
  59. package/dist/{dns-L3L2BB27.js → dns-7BDJSD3E.js} +4 -2
  60. package/dist/{feedback-writer-AAKF5BTK.js → feedback-writer-LVZ5TFYZ.js} +8 -4
  61. package/dist/feedback-writer-LVZ5TFYZ.js.map +1 -0
  62. package/dist/hume-WMAUBBV2.js +13 -0
  63. package/dist/index.d.ts +162 -40
  64. package/dist/index.js +67 -23
  65. package/dist/index.js.map +1 -1
  66. package/dist/{projects-VXRUCMLM.js → projects-JEIVIYC6.js} +3 -3
  67. package/dist/rally-RKFSWC7E.js +10 -0
  68. package/dist/{remote-agents-Z3R2A5BN.js → remote-agents-TFSMW7GN.js} +2 -2
  69. package/dist/{remote-workspace-2G6V2KNP.js → remote-workspace-AHVHQEES.js} +8 -8
  70. package/dist/review-status-EPFG4XM7.js +19 -0
  71. package/dist/shadow-state-5MDP6YXH.js +30 -0
  72. package/dist/shadow-state-5MDP6YXH.js.map +1 -0
  73. package/dist/{specialist-context-N32QBNNQ.js → specialist-context-ZC6A4M3I.js} +8 -7
  74. package/dist/{specialist-context-N32QBNNQ.js.map → specialist-context-ZC6A4M3I.js.map} +1 -1
  75. package/dist/{specialist-logs-GF3YV4KL.js → specialist-logs-KLGJCEUL.js} +7 -6
  76. package/dist/specialist-logs-KLGJCEUL.js.map +1 -0
  77. package/dist/{specialists-JBIW6MP4.js → specialists-O4HWDJL5.js} +7 -6
  78. package/dist/specialists-O4HWDJL5.js.map +1 -0
  79. package/dist/tldr-daemon-T3THOUGT.js +21 -0
  80. package/dist/tldr-daemon-T3THOUGT.js.map +1 -0
  81. package/dist/traefik-QN7R5I6V.js +19 -0
  82. package/dist/traefik-QN7R5I6V.js.map +1 -0
  83. package/dist/tunnel-W2GZBLEV.js +13 -0
  84. package/dist/tunnel-W2GZBLEV.js.map +1 -0
  85. package/dist/workspace-manager-IE4JL2JP.js +22 -0
  86. package/dist/workspace-manager-IE4JL2JP.js.map +1 -0
  87. package/package.json +2 -2
  88. package/scripts/heartbeat-hook +37 -10
  89. package/scripts/patches/llm-tldr-tsx-support.py +109 -0
  90. package/scripts/pre-tool-hook +26 -15
  91. package/scripts/record-cost-event.js +177 -43
  92. package/scripts/record-cost-event.ts +87 -3
  93. package/scripts/statusline.sh +169 -0
  94. package/scripts/stop-hook +21 -11
  95. package/scripts/tldr-post-edit +72 -0
  96. package/scripts/tldr-read-enforcer +275 -0
  97. package/scripts/work-agent-stop-hook +137 -0
  98. package/skills/check-merged/SKILL.md +143 -0
  99. package/skills/crash-investigation/SKILL.md +301 -0
  100. package/skills/github-cli/SKILL.md +185 -0
  101. package/skills/myn-standards/SKILL.md +351 -0
  102. package/skills/pan-reopen/SKILL.md +65 -0
  103. package/skills/pan-sync-main/SKILL.md +87 -0
  104. package/skills/pan-tldr/SKILL.md +149 -0
  105. package/skills/react-best-practices/SKILL.md +125 -0
  106. package/skills/spec-readiness/REPORT-TEMPLATE.md +158 -0
  107. package/skills/spec-readiness/SCORING-REFERENCE.md +369 -0
  108. package/skills/spec-readiness/SKILL.md +400 -0
  109. package/skills/spec-readiness-setup/SKILL.md +361 -0
  110. package/skills/workspace-status/SKILL.md +56 -0
  111. package/skills/write-spec/SKILL.md +138 -0
  112. package/templates/traefik/dynamic/panopticon.yml.template +0 -5
  113. package/templates/traefik/traefik.yml +0 -8
  114. package/dist/chunk-2NIAOCIC.js.map +0 -1
  115. package/dist/chunk-3XAB4IXF.js +0 -51
  116. package/dist/chunk-3XAB4IXF.js.map +0 -1
  117. package/dist/chunk-6HXKTOD7.js.map +0 -1
  118. package/dist/chunk-BBCUK6N2.js +0 -241
  119. package/dist/chunk-BBCUK6N2.js.map +0 -1
  120. package/dist/chunk-BWGFN44T.js.map +0 -1
  121. package/dist/chunk-ELK6Q7QI.js +0 -545
  122. package/dist/chunk-ELK6Q7QI.js.map +0 -1
  123. package/dist/chunk-JY7R7V4G.js.map +0 -1
  124. package/dist/chunk-LYSBSZYV.js +0 -1523
  125. package/dist/chunk-LYSBSZYV.js.map +0 -1
  126. package/dist/chunk-VIWUCJ4V.js.map +0 -1
  127. package/dist/chunk-VU4FLXV5.js.map +0 -1
  128. package/dist/chunk-XP2DXWYP.js.map +0 -1
  129. package/dist/dashboard/public/assets/index-C7X6LP5Z.css +0 -32
  130. package/dist/dashboard/public/assets/index-ClYqpcAJ.js +0 -645
  131. package/dist/feedback-writer-AAKF5BTK.js.map +0 -1
  132. package/dist/review-status-GWQYY77L.js.map +0 -1
  133. package/dist/traefik-CUJM6K5Z.js +0 -12
  134. /package/dist/{agents-BDFHF4T3.js.map → agents-E43Y3HNU.js.map} +0 -0
  135. /package/dist/{config-BOAMSKTF.js.map → config-4CJNUE3O.js.map} +0 -0
  136. /package/dist/{dns-L3L2BB27.js.map → dns-7BDJSD3E.js.map} +0 -0
  137. /package/dist/{projects-VXRUCMLM.js.map → hume-WMAUBBV2.js.map} +0 -0
  138. /package/dist/{remote-agents-Z3R2A5BN.js.map → projects-JEIVIYC6.js.map} +0 -0
  139. /package/dist/{specialist-logs-GF3YV4KL.js.map → rally-RKFSWC7E.js.map} +0 -0
  140. /package/dist/{specialists-JBIW6MP4.js.map → remote-agents-TFSMW7GN.js.map} +0 -0
  141. /package/dist/{remote-workspace-2G6V2KNP.js.map → remote-workspace-AHVHQEES.js.map} +0 -0
  142. /package/dist/{traefik-CUJM6K5Z.js.map → review-status-EPFG4XM7.js.map} +0 -0
@@ -1,1523 +0,0 @@
1
- import {
2
- init_config_yaml,
3
- loadConfig
4
- } from "./chunk-BBCUK6N2.js";
5
- import {
6
- SETTINGS_FILE,
7
- init_paths
8
- } from "./chunk-6HXKTOD7.js";
9
- import {
10
- __esm,
11
- init_esm_shims
12
- } from "./chunk-ZHC57RCV.js";
13
-
14
- // src/lib/settings.ts
15
- import { readFileSync, writeFileSync, existsSync } from "fs";
16
- function deepMerge(defaults, overrides) {
17
- const result = { ...defaults };
18
- for (const key of Object.keys(overrides)) {
19
- const defaultVal = defaults[key];
20
- const overrideVal = overrides[key];
21
- if (overrideVal === void 0) continue;
22
- if (typeof defaultVal === "object" && defaultVal !== null && !Array.isArray(defaultVal) && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(overrideVal)) {
23
- result[key] = deepMerge(defaultVal, overrideVal);
24
- } else {
25
- result[key] = overrideVal;
26
- }
27
- }
28
- return result;
29
- }
30
- function loadSettings() {
31
- let settings;
32
- if (!existsSync(SETTINGS_FILE)) {
33
- settings = getDefaultSettings();
34
- } else {
35
- try {
36
- const content = readFileSync(SETTINGS_FILE, "utf8");
37
- const parsed = JSON.parse(content);
38
- settings = deepMerge(DEFAULT_SETTINGS, parsed);
39
- } catch (error) {
40
- console.error("Warning: Failed to parse settings.json, using defaults");
41
- settings = getDefaultSettings();
42
- }
43
- }
44
- const envApiKeys = {};
45
- if (process.env.OPENAI_API_KEY) envApiKeys.openai = process.env.OPENAI_API_KEY;
46
- if (process.env.GOOGLE_API_KEY) envApiKeys.google = process.env.GOOGLE_API_KEY;
47
- if (process.env.ZAI_API_KEY) envApiKeys.zai = process.env.ZAI_API_KEY;
48
- if (process.env.KIMI_API_KEY) envApiKeys.kimi = process.env.KIMI_API_KEY;
49
- settings.api_keys = {
50
- ...envApiKeys,
51
- ...settings.api_keys
52
- };
53
- return settings;
54
- }
55
- function saveSettings(settings) {
56
- const content = JSON.stringify(settings, null, 2);
57
- writeFileSync(SETTINGS_FILE, content, "utf8");
58
- }
59
- function validateSettings(settings) {
60
- if (!settings.models) {
61
- return "Missing models configuration";
62
- }
63
- if (!settings.models.specialists) {
64
- return "Missing specialists configuration";
65
- }
66
- const specialists = settings.models.specialists;
67
- if (!specialists.review_agent || !specialists.test_agent || !specialists.merge_agent) {
68
- return "Missing specialist agent model configuration";
69
- }
70
- if (!settings.models.planning_agent) {
71
- return "Missing planning_agent configuration";
72
- }
73
- if (!settings.models.complexity) {
74
- return "Missing complexity configuration";
75
- }
76
- const complexity = settings.models.complexity;
77
- const requiredLevels = ["trivial", "simple", "medium", "complex", "expert"];
78
- for (const level of requiredLevels) {
79
- if (!complexity[level]) {
80
- return `Missing complexity level: ${level}`;
81
- }
82
- }
83
- if (!settings.api_keys) {
84
- return "Missing api_keys configuration";
85
- }
86
- return null;
87
- }
88
- function getDefaultSettings() {
89
- return JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
90
- }
91
- function getAvailableModels(settings) {
92
- const anthropicModels = [
93
- "claude-opus-4-6",
94
- "claude-sonnet-4-5",
95
- "claude-haiku-4-5"
96
- ];
97
- const openaiModels = settings.api_keys.openai ? ["gpt-5.2-codex", "o3-deep-research", "gpt-4o", "gpt-4o-mini"] : [];
98
- const googleModels = settings.api_keys.google ? ["gemini-3-pro-preview", "gemini-3-flash-preview"] : [];
99
- const zaiModels = settings.api_keys.zai ? ["glm-4.7", "glm-4.7-flash"] : [];
100
- const kimiModels = settings.api_keys.kimi ? ["kimi-k2", "kimi-k2.5"] : [];
101
- return {
102
- anthropic: anthropicModels,
103
- openai: openaiModels,
104
- google: googleModels,
105
- zai: zaiModels,
106
- kimi: kimiModels
107
- };
108
- }
109
- function isAnthropicModel(modelId) {
110
- return modelId.startsWith("claude-");
111
- }
112
- function getClaudeModelFlag(modelId) {
113
- const modelMap = {
114
- "claude-opus-4-6": "opus",
115
- "claude-sonnet-4-5": "sonnet",
116
- "claude-haiku-4-5": "haiku"
117
- };
118
- return modelMap[modelId] || "sonnet";
119
- }
120
- function getAgentCommand(modelId) {
121
- if (isAnthropicModel(modelId)) {
122
- return {
123
- command: "claude",
124
- args: ["--model", getClaudeModelFlag(modelId)]
125
- };
126
- }
127
- return {
128
- command: "claude-code-router",
129
- args: []
130
- };
131
- }
132
- var DEFAULT_SETTINGS;
133
- var init_settings = __esm({
134
- "src/lib/settings.ts"() {
135
- "use strict";
136
- init_esm_shims();
137
- init_paths();
138
- DEFAULT_SETTINGS = {
139
- models: {
140
- specialists: {
141
- review_agent: "claude-opus-4-6",
142
- test_agent: "claude-sonnet-4-5",
143
- merge_agent: "claude-sonnet-4-5"
144
- },
145
- planning_agent: "claude-opus-4-6",
146
- status_review: "claude-opus-4-6",
147
- complexity: {
148
- trivial: "claude-haiku-4-5",
149
- simple: "claude-haiku-4-5",
150
- medium: "kimi-k2.5",
151
- complex: "kimi-k2.5",
152
- expert: "claude-opus-4-6"
153
- }
154
- },
155
- api_keys: {}
156
- };
157
- }
158
- });
159
-
160
- // src/lib/providers.ts
161
- function getProviderForModel(modelId) {
162
- if (["claude-opus-4-6", "claude-sonnet-4-5", "claude-haiku-4-5"].includes(modelId)) {
163
- return PROVIDERS.anthropic;
164
- }
165
- if (["gpt-5.2-codex", "o3-deep-research", "gpt-4o", "gpt-4o-mini"].includes(modelId)) {
166
- return PROVIDERS.openai;
167
- }
168
- if (["gemini-3-pro-preview", "gemini-3-flash-preview"].includes(modelId)) {
169
- return PROVIDERS.google;
170
- }
171
- if (["glm-4.7", "glm-4.7-flash"].includes(modelId)) {
172
- return PROVIDERS.zai;
173
- }
174
- if (["kimi-k2", "kimi-k2.5"].includes(modelId)) {
175
- return PROVIDERS.kimi;
176
- }
177
- return PROVIDERS.anthropic;
178
- }
179
- function requiresRouter(provider) {
180
- return PROVIDERS[provider].compatibility === "router";
181
- }
182
- function getRouterProviders() {
183
- return Object.values(PROVIDERS).filter((p) => p.compatibility === "router");
184
- }
185
- function getDirectProviders() {
186
- return Object.values(PROVIDERS).filter((p) => p.compatibility === "direct");
187
- }
188
- function needsRouter(apiKeys) {
189
- return !!(apiKeys.openai || apiKeys.google);
190
- }
191
- function getProviderEnv(provider, apiKey) {
192
- if (provider.compatibility === "direct") {
193
- const env = {};
194
- if (provider.baseUrl) {
195
- env.ANTHROPIC_BASE_URL = provider.baseUrl;
196
- }
197
- if (provider.name !== "anthropic") {
198
- env.ANTHROPIC_AUTH_TOKEN = apiKey;
199
- }
200
- if (provider.name === "zai") {
201
- env.API_TIMEOUT_MS = "300000";
202
- }
203
- return env;
204
- } else {
205
- return {
206
- ANTHROPIC_BASE_URL: "http://localhost:3456",
207
- ANTHROPIC_AUTH_TOKEN: "router-managed"
208
- };
209
- }
210
- }
211
- var PROVIDERS;
212
- var init_providers = __esm({
213
- "src/lib/providers.ts"() {
214
- "use strict";
215
- init_esm_shims();
216
- PROVIDERS = {
217
- anthropic: {
218
- name: "anthropic",
219
- displayName: "Anthropic",
220
- compatibility: "direct",
221
- models: ["claude-opus-4-6", "claude-sonnet-4-5", "claude-haiku-4-5"],
222
- tested: true,
223
- description: "Native Claude API"
224
- },
225
- kimi: {
226
- name: "kimi",
227
- displayName: "Kimi (Moonshot AI)",
228
- compatibility: "direct",
229
- baseUrl: "https://api.kimi.com/coding/",
230
- models: [],
231
- // Kimi uses same model names as Anthropic
232
- tested: true,
233
- description: "Anthropic-compatible API, tested 2026-01-28"
234
- },
235
- zai: {
236
- name: "zai",
237
- displayName: "Z.AI (GLM)",
238
- compatibility: "direct",
239
- baseUrl: "https://api.z.ai/api/anthropic",
240
- models: ["glm-4.7", "glm-4.7-flash"],
241
- tested: true,
242
- description: "Anthropic-compatible API, tested 2026-01-28"
243
- },
244
- openai: {
245
- name: "openai",
246
- displayName: "OpenAI",
247
- compatibility: "router",
248
- models: ["gpt-5.2-codex", "o3-deep-research", "gpt-4o", "gpt-4o-mini"],
249
- tested: false,
250
- description: "Requires claude-code-router for API translation"
251
- },
252
- google: {
253
- name: "google",
254
- displayName: "Google (Gemini)",
255
- compatibility: "router",
256
- models: ["gemini-3-pro-preview", "gemini-3-flash-preview"],
257
- tested: false,
258
- description: "Requires claude-code-router for API translation"
259
- }
260
- };
261
- }
262
- });
263
-
264
- // src/lib/tracker/interface.ts
265
- var NotImplementedError, IssueNotFoundError, TrackerAuthError;
266
- var init_interface = __esm({
267
- "src/lib/tracker/interface.ts"() {
268
- "use strict";
269
- init_esm_shims();
270
- NotImplementedError = class extends Error {
271
- constructor(feature) {
272
- super(`Not implemented: ${feature}`);
273
- this.name = "NotImplementedError";
274
- }
275
- };
276
- IssueNotFoundError = class extends Error {
277
- constructor(id, tracker) {
278
- super(`Issue not found: ${id} (tracker: ${tracker})`);
279
- this.name = "IssueNotFoundError";
280
- }
281
- };
282
- TrackerAuthError = class extends Error {
283
- constructor(tracker, message) {
284
- super(`Authentication failed for ${tracker}: ${message}`);
285
- this.name = "TrackerAuthError";
286
- }
287
- };
288
- }
289
- });
290
-
291
- // src/lib/tracker/linear.ts
292
- import { LinearClient } from "@linear/sdk";
293
- var STATE_MAP, LinearTracker;
294
- var init_linear = __esm({
295
- "src/lib/tracker/linear.ts"() {
296
- "use strict";
297
- init_esm_shims();
298
- init_interface();
299
- STATE_MAP = {
300
- backlog: "open",
301
- unstarted: "open",
302
- started: "in_progress",
303
- completed: "closed",
304
- canceled: "closed"
305
- };
306
- LinearTracker = class {
307
- name = "linear";
308
- client;
309
- defaultTeam;
310
- constructor(apiKey, options) {
311
- if (!apiKey) {
312
- throw new TrackerAuthError("linear", "API key is required");
313
- }
314
- this.client = new LinearClient({ apiKey });
315
- this.defaultTeam = options?.team;
316
- }
317
- async listIssues(filters) {
318
- const team = filters?.team ?? this.defaultTeam;
319
- const result = await this.client.issues({
320
- first: filters?.limit ?? 50,
321
- filter: {
322
- team: team ? { key: { eq: team } } : void 0,
323
- state: filters?.state ? { type: { eq: this.reverseMapState(filters.state) } } : filters?.includeClosed ? void 0 : { type: { neq: "completed" } },
324
- labels: filters?.labels?.length ? { name: { in: filters.labels } } : void 0,
325
- assignee: filters?.assignee ? { name: { containsIgnoreCase: filters.assignee } } : void 0
326
- }
327
- });
328
- const issues = [];
329
- for (const node of result.nodes) {
330
- issues.push(await this.normalizeIssue(node));
331
- }
332
- return issues;
333
- }
334
- async getIssue(id) {
335
- try {
336
- const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);
337
- if (isUuid) {
338
- const issue = await this.client.issue(id);
339
- if (issue) {
340
- return this.normalizeIssue(issue);
341
- }
342
- } else {
343
- const match = id.match(/^([A-Z]+)-(\d+)$/i);
344
- if (match) {
345
- const [, teamKey, number] = match;
346
- const results = await this.client.searchIssues(id, { first: 1 });
347
- if (results.nodes.length > 0) {
348
- return this.normalizeIssue(results.nodes[0]);
349
- }
350
- }
351
- }
352
- throw new IssueNotFoundError(id, "linear");
353
- } catch (error) {
354
- if (error instanceof IssueNotFoundError) throw error;
355
- throw new IssueNotFoundError(id, "linear");
356
- }
357
- }
358
- async updateIssue(id, update) {
359
- const issue = await this.getIssue(id);
360
- const updatePayload = {};
361
- if (update.title !== void 0) {
362
- updatePayload.title = update.title;
363
- }
364
- if (update.description !== void 0) {
365
- updatePayload.description = update.description;
366
- }
367
- if (update.priority !== void 0) {
368
- updatePayload.priority = update.priority;
369
- }
370
- if (update.dueDate !== void 0) {
371
- updatePayload.dueDate = update.dueDate;
372
- }
373
- if (update.state !== void 0) {
374
- await this.transitionIssue(id, update.state);
375
- }
376
- if (update.labels !== void 0) {
377
- }
378
- if (Object.keys(updatePayload).length > 0) {
379
- await this.client.updateIssue(issue.id, updatePayload);
380
- }
381
- return this.getIssue(id);
382
- }
383
- async createIssue(newIssue) {
384
- const team = newIssue.team ?? this.defaultTeam;
385
- if (!team) {
386
- throw new Error("Team is required to create an issue");
387
- }
388
- const teams = await this.client.teams({
389
- filter: { key: { eq: team } }
390
- });
391
- if (teams.nodes.length === 0) {
392
- throw new Error(`Team not found: ${team}`);
393
- }
394
- const teamId = teams.nodes[0].id;
395
- const result = await this.client.createIssue({
396
- teamId,
397
- title: newIssue.title,
398
- description: newIssue.description,
399
- priority: newIssue.priority,
400
- dueDate: newIssue.dueDate
401
- });
402
- const created = await result.issue;
403
- if (!created) {
404
- throw new Error("Failed to create issue");
405
- }
406
- return this.normalizeIssue(created);
407
- }
408
- async getComments(issueId) {
409
- const issue = await this.client.issue(issueId);
410
- const comments = await issue.comments();
411
- return comments.nodes.map((c) => ({
412
- id: c.id,
413
- issueId,
414
- body: c.body,
415
- author: c.user?.then((u) => u?.name ?? "Unknown"),
416
- // Simplified
417
- createdAt: c.createdAt.toISOString(),
418
- updatedAt: c.updatedAt.toISOString()
419
- }));
420
- }
421
- async addComment(issueId, body) {
422
- const result = await this.client.createComment({
423
- issueId,
424
- body
425
- });
426
- const comment = await result.comment;
427
- if (!comment) {
428
- throw new Error("Failed to create comment");
429
- }
430
- return {
431
- id: comment.id,
432
- issueId,
433
- body: comment.body,
434
- author: "Panopticon",
435
- // Simplified
436
- createdAt: comment.createdAt.toISOString(),
437
- updatedAt: comment.updatedAt.toISOString()
438
- };
439
- }
440
- async transitionIssue(id, state) {
441
- let linearIssue;
442
- const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);
443
- if (isUuid) {
444
- linearIssue = await this.client.issue(id);
445
- } else {
446
- const results = await this.client.searchIssues(id, { first: 1 });
447
- if (results.nodes.length > 0) {
448
- linearIssue = results.nodes[0];
449
- } else {
450
- throw new IssueNotFoundError(id, "linear");
451
- }
452
- }
453
- const team = await linearIssue.team;
454
- if (!team) {
455
- throw new Error("Could not determine issue team");
456
- }
457
- const states = await team.states();
458
- const targetStateType = this.reverseMapState(state);
459
- const matchingStates = states.nodes.filter((s) => s.type === targetStateType).sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
460
- const targetState = matchingStates[0];
461
- if (!targetState) {
462
- throw new Error(`No state found matching type: ${targetStateType}`);
463
- }
464
- await this.client.updateIssue(linearIssue.id, {
465
- stateId: targetState.id
466
- });
467
- }
468
- async linkPR(issueId, prUrl) {
469
- const issue = await this.getIssue(issueId);
470
- await this.client.createAttachment({
471
- issueId: issue.id,
472
- title: "Pull Request",
473
- url: prUrl
474
- });
475
- }
476
- async normalizeIssue(linearIssue) {
477
- const state = await linearIssue.state;
478
- const assignee = await linearIssue.assignee;
479
- const labels = await linearIssue.labels();
480
- let dueDate;
481
- if (linearIssue.dueDate) {
482
- dueDate = linearIssue.dueDate instanceof Date ? linearIssue.dueDate.toISOString() : String(linearIssue.dueDate);
483
- }
484
- return {
485
- id: linearIssue.id,
486
- ref: linearIssue.identifier,
487
- title: linearIssue.title,
488
- description: linearIssue.description ?? "",
489
- state: this.mapState(state?.type ?? "backlog"),
490
- labels: labels?.nodes?.map((l) => l.name) ?? [],
491
- assignee: assignee?.name,
492
- url: linearIssue.url,
493
- tracker: "linear",
494
- priority: linearIssue.priority,
495
- dueDate,
496
- createdAt: linearIssue.createdAt instanceof Date ? linearIssue.createdAt.toISOString() : String(linearIssue.createdAt),
497
- updatedAt: linearIssue.updatedAt instanceof Date ? linearIssue.updatedAt.toISOString() : String(linearIssue.updatedAt)
498
- };
499
- }
500
- mapState(linearState) {
501
- return STATE_MAP[linearState] ?? "open";
502
- }
503
- reverseMapState(state) {
504
- switch (state) {
505
- case "open":
506
- return "unstarted";
507
- case "in_progress":
508
- return "started";
509
- case "closed":
510
- return "completed";
511
- default:
512
- return "unstarted";
513
- }
514
- }
515
- };
516
- }
517
- });
518
-
519
- // src/lib/tracker/github.ts
520
- import { Octokit } from "@octokit/rest";
521
- var GitHubTracker;
522
- var init_github = __esm({
523
- "src/lib/tracker/github.ts"() {
524
- "use strict";
525
- init_esm_shims();
526
- init_interface();
527
- GitHubTracker = class {
528
- name = "github";
529
- octokit;
530
- owner;
531
- repo;
532
- constructor(token, owner, repo) {
533
- if (!token) {
534
- throw new TrackerAuthError("github", "Token is required");
535
- }
536
- if (!owner || !repo) {
537
- throw new Error("GitHub owner and repo are required");
538
- }
539
- this.octokit = new Octokit({ auth: token });
540
- this.owner = owner;
541
- this.repo = repo;
542
- }
543
- async listIssues(filters) {
544
- const state = this.mapStateToGitHub(filters?.state);
545
- const response = await this.octokit.issues.listForRepo({
546
- owner: this.owner,
547
- repo: this.repo,
548
- state: filters?.includeClosed ? "all" : state,
549
- labels: filters?.labels?.join(",") || void 0,
550
- assignee: filters?.assignee || void 0,
551
- per_page: filters?.limit ?? 50
552
- });
553
- const issues = response.data.filter((item) => !item.pull_request);
554
- return issues.map((issue) => this.normalizeIssue(issue));
555
- }
556
- async getIssue(id) {
557
- try {
558
- const issueNumber = parseInt(id.replace(/^#/, ""), 10);
559
- if (isNaN(issueNumber)) {
560
- throw new IssueNotFoundError(id, "github");
561
- }
562
- const { data: issue } = await this.octokit.issues.get({
563
- owner: this.owner,
564
- repo: this.repo,
565
- issue_number: issueNumber
566
- });
567
- return this.normalizeIssue(issue);
568
- } catch (error) {
569
- if (error?.status === 404) {
570
- throw new IssueNotFoundError(id, "github");
571
- }
572
- throw error;
573
- }
574
- }
575
- async updateIssue(id, update) {
576
- const issueNumber = parseInt(id.replace(/^#/, ""), 10);
577
- const updatePayload = {};
578
- if (update.title !== void 0) {
579
- updatePayload.title = update.title;
580
- }
581
- if (update.description !== void 0) {
582
- updatePayload.body = update.description;
583
- }
584
- if (update.state !== void 0) {
585
- updatePayload.state = update.state === "closed" ? "closed" : "open";
586
- }
587
- if (update.labels !== void 0) {
588
- updatePayload.labels = update.labels;
589
- }
590
- if (update.assignee !== void 0) {
591
- updatePayload.assignees = update.assignee ? [update.assignee] : [];
592
- }
593
- await this.octokit.issues.update({
594
- owner: this.owner,
595
- repo: this.repo,
596
- issue_number: issueNumber,
597
- ...updatePayload
598
- });
599
- return this.getIssue(id);
600
- }
601
- async createIssue(newIssue) {
602
- const { data: issue } = await this.octokit.issues.create({
603
- owner: this.owner,
604
- repo: this.repo,
605
- title: newIssue.title,
606
- body: newIssue.description,
607
- labels: newIssue.labels,
608
- assignees: newIssue.assignee ? [newIssue.assignee] : void 0
609
- });
610
- return this.normalizeIssue(issue);
611
- }
612
- async getComments(issueId) {
613
- const issueNumber = parseInt(issueId.replace(/^#/, ""), 10);
614
- const { data: comments } = await this.octokit.issues.listComments({
615
- owner: this.owner,
616
- repo: this.repo,
617
- issue_number: issueNumber
618
- });
619
- return comments.map((c) => ({
620
- id: String(c.id),
621
- issueId,
622
- body: c.body ?? "",
623
- author: c.user?.login ?? "Unknown",
624
- createdAt: c.created_at,
625
- updatedAt: c.updated_at
626
- }));
627
- }
628
- async addComment(issueId, body) {
629
- const issueNumber = parseInt(issueId.replace(/^#/, ""), 10);
630
- const { data: comment } = await this.octokit.issues.createComment({
631
- owner: this.owner,
632
- repo: this.repo,
633
- issue_number: issueNumber,
634
- body
635
- });
636
- return {
637
- id: String(comment.id),
638
- issueId,
639
- body: comment.body ?? "",
640
- author: comment.user?.login ?? "Unknown",
641
- createdAt: comment.created_at,
642
- updatedAt: comment.updated_at
643
- };
644
- }
645
- async transitionIssue(id, state) {
646
- await this.updateIssue(id, { state });
647
- }
648
- async linkPR(issueId, prUrl) {
649
- await this.addComment(
650
- issueId,
651
- `Linked Pull Request: ${prUrl}`
652
- );
653
- }
654
- normalizeIssue(ghIssue) {
655
- return {
656
- id: String(ghIssue.id),
657
- ref: `#${ghIssue.number}`,
658
- title: ghIssue.title,
659
- description: ghIssue.body ?? "",
660
- state: this.mapStateFromGitHub(ghIssue.state),
661
- labels: ghIssue.labels.map(
662
- (l) => typeof l === "string" ? l : l.name
663
- ),
664
- assignee: ghIssue.assignee?.login,
665
- url: ghIssue.html_url,
666
- tracker: "github",
667
- priority: void 0,
668
- // GitHub doesn't have priority
669
- dueDate: void 0,
670
- // GitHub doesn't have due dates on issues
671
- createdAt: ghIssue.created_at,
672
- updatedAt: ghIssue.updated_at
673
- };
674
- }
675
- mapStateFromGitHub(ghState) {
676
- return ghState === "closed" ? "closed" : "open";
677
- }
678
- mapStateToGitHub(state) {
679
- if (!state) return "open";
680
- if (state === "closed") return "closed";
681
- return "open";
682
- }
683
- };
684
- }
685
- });
686
-
687
- // src/lib/tracker/gitlab.ts
688
- var GitLabTracker;
689
- var init_gitlab = __esm({
690
- "src/lib/tracker/gitlab.ts"() {
691
- "use strict";
692
- init_esm_shims();
693
- init_interface();
694
- GitLabTracker = class {
695
- constructor(token, projectId) {
696
- this.token = token;
697
- this.projectId = projectId;
698
- }
699
- name = "gitlab";
700
- async listIssues(_filters) {
701
- throw new NotImplementedError(
702
- "GitLab tracker is not yet implemented. Coming soon!"
703
- );
704
- }
705
- async getIssue(_id) {
706
- throw new NotImplementedError(
707
- "GitLab tracker is not yet implemented. Coming soon!"
708
- );
709
- }
710
- async updateIssue(_id, _update) {
711
- throw new NotImplementedError(
712
- "GitLab tracker is not yet implemented. Coming soon!"
713
- );
714
- }
715
- async createIssue(_issue) {
716
- throw new NotImplementedError(
717
- "GitLab tracker is not yet implemented. Coming soon!"
718
- );
719
- }
720
- async getComments(_issueId) {
721
- throw new NotImplementedError(
722
- "GitLab tracker is not yet implemented. Coming soon!"
723
- );
724
- }
725
- async addComment(_issueId, _body) {
726
- throw new NotImplementedError(
727
- "GitLab tracker is not yet implemented. Coming soon!"
728
- );
729
- }
730
- async transitionIssue(_id, _state) {
731
- throw new NotImplementedError(
732
- "GitLab tracker is not yet implemented. Coming soon!"
733
- );
734
- }
735
- async linkPR(_issueId, _prUrl) {
736
- throw new NotImplementedError(
737
- "GitLab tracker is not yet implemented. Coming soon!"
738
- );
739
- }
740
- };
741
- }
742
- });
743
-
744
- // src/lib/tracker/rally-api.ts
745
- var RallyRestApi;
746
- var init_rally_api = __esm({
747
- "src/lib/tracker/rally-api.ts"() {
748
- "use strict";
749
- init_esm_shims();
750
- RallyRestApi = class {
751
- apiKey;
752
- server;
753
- customHeaders;
754
- constructor(config) {
755
- this.apiKey = config.apiKey;
756
- this.server = config.server || "https://rally1.rallydev.com";
757
- this.customHeaders = config.requestOptions?.headers || {};
758
- }
759
- /**
760
- * Query Rally artifacts
761
- */
762
- async query(config) {
763
- const params = new URLSearchParams();
764
- if (config.query) {
765
- params.set("query", config.query);
766
- }
767
- if (config.fetch && config.fetch.length > 0) {
768
- params.set("fetch", config.fetch.join(","));
769
- }
770
- if (config.limit !== void 0) {
771
- params.set("pagesize", String(config.limit));
772
- }
773
- if (config.workspace) {
774
- params.set("workspace", config.workspace);
775
- }
776
- if (config.project) {
777
- params.set("project", config.project);
778
- if (config.projectScopeDown) {
779
- params.set("projectScopeDown", "true");
780
- }
781
- }
782
- if (config.order) {
783
- params.set("order", config.order);
784
- }
785
- const url = `${this.server}/slm/webservice/v2.0/${config.type}?${params.toString()}`;
786
- const response = await fetch(url, {
787
- method: "GET",
788
- headers: {
789
- "ZSESSIONID": this.apiKey,
790
- "Content-Type": "application/json",
791
- ...this.customHeaders
792
- }
793
- });
794
- if (!response.ok) {
795
- if (response.status === 401) {
796
- throw new Error("Unauthorized: Invalid API key or insufficient permissions");
797
- }
798
- throw new Error(`Rally API query failed: ${response.status} ${response.statusText}`);
799
- }
800
- const result = await response.json();
801
- if (result.QueryResult.Errors && result.QueryResult.Errors.length > 0) {
802
- const errorDetail = result.QueryResult.Errors.join(", ");
803
- const queryDetail = config.query ? ` (Query: ${config.query})` : "";
804
- if (process.env.DEBUG?.includes("rally")) {
805
- console.error("[Rally WSAPI] Query failed:", { query: config.query, errors: result.QueryResult.Errors });
806
- }
807
- throw new Error(`Rally API query failed: ${errorDetail}${queryDetail}`);
808
- }
809
- return result;
810
- }
811
- /**
812
- * Create a Rally object
813
- */
814
- async create(config) {
815
- const url = `${this.server}/slm/webservice/v2.0/${config.type}/create`;
816
- const body = {
817
- [config.type]: config.data
818
- };
819
- const params = new URLSearchParams();
820
- if (config.fetch && config.fetch.length > 0) {
821
- params.set("fetch", config.fetch.join(","));
822
- }
823
- const finalUrl = params.toString() ? `${url}?${params.toString()}` : url;
824
- const response = await fetch(finalUrl, {
825
- method: "POST",
826
- headers: {
827
- "ZSESSIONID": this.apiKey,
828
- "Content-Type": "application/json",
829
- ...this.customHeaders
830
- },
831
- body: JSON.stringify(body)
832
- });
833
- if (!response.ok) {
834
- throw new Error(`Rally API create failed: ${response.status} ${response.statusText}`);
835
- }
836
- const result = await response.json();
837
- if (result.CreateResult.Errors && result.CreateResult.Errors.length > 0) {
838
- throw new Error(`Rally API create failed: ${result.CreateResult.Errors.join(", ")}`);
839
- }
840
- return result;
841
- }
842
- /**
843
- * Update a Rally object
844
- */
845
- async update(config) {
846
- const objectId = config.ref.split("/").pop();
847
- const url = `${this.server}/slm/webservice/v2.0/${config.type}/${objectId}`;
848
- const body = {
849
- [config.type]: config.data
850
- };
851
- const params = new URLSearchParams();
852
- if (config.fetch && config.fetch.length > 0) {
853
- params.set("fetch", config.fetch.join(","));
854
- }
855
- const finalUrl = params.toString() ? `${url}?${params.toString()}` : url;
856
- const response = await fetch(finalUrl, {
857
- method: "POST",
858
- headers: {
859
- "ZSESSIONID": this.apiKey,
860
- "Content-Type": "application/json",
861
- ...this.customHeaders
862
- },
863
- body: JSON.stringify(body)
864
- });
865
- if (!response.ok) {
866
- throw new Error(`Rally API update failed: ${response.status} ${response.statusText}`);
867
- }
868
- const result = await response.json();
869
- if (result.OperationResult.Errors && result.OperationResult.Errors.length > 0) {
870
- throw new Error(`Rally API update failed: ${result.OperationResult.Errors.join(", ")}`);
871
- }
872
- return result;
873
- }
874
- };
875
- }
876
- });
877
-
878
- // src/lib/tracker/rally.ts
879
- var STATE_MAP2, QUERYABLE_TYPES, FETCH_FIELDS, PRIORITY_MAP, REVERSE_PRIORITY_MAP, RallyTracker;
880
- var init_rally = __esm({
881
- "src/lib/tracker/rally.ts"() {
882
- "use strict";
883
- init_esm_shims();
884
- init_rally_api();
885
- init_interface();
886
- STATE_MAP2 = {
887
- // User Stories (ScheduleState)
888
- "New": "open",
889
- "Idea": "open",
890
- "Defined": "open",
891
- "In-Progress": "in_progress",
892
- "Completed": "closed",
893
- "Accepted": "closed",
894
- // Defects (State)
895
- "Submitted": "open",
896
- "Open": "in_progress",
897
- // "Open" defects are actively being worked
898
- "Fixed": "closed",
899
- "Closed": "closed",
900
- // Features / PortfolioItems (State)
901
- "Discovering": "open",
902
- "Developing": "in_progress",
903
- "Done": "closed"
904
- };
905
- QUERYABLE_TYPES = [
906
- { type: "hierarchicalrequirement", stateField: "ScheduleState", closedStates: ["Completed", "Accepted"] },
907
- { type: "defect", stateField: "State", closedStates: ["Closed"] },
908
- { type: "task", stateField: "State", closedStates: ["Completed"] },
909
- { type: "portfolioitem/feature", stateField: "State", closedStates: ["Done"] }
910
- ];
911
- FETCH_FIELDS = [
912
- "ObjectID",
913
- "FormattedID",
914
- "Name",
915
- "Description",
916
- "ScheduleState",
917
- "State",
918
- "Tags",
919
- "Owner",
920
- "Priority",
921
- "DueDate",
922
- "CreationDate",
923
- "LastUpdateDate",
924
- "Parent",
925
- "PortfolioItem",
926
- "_type"
927
- ];
928
- PRIORITY_MAP = {
929
- "Resolve Immediately": 0,
930
- High: 1,
931
- Normal: 2,
932
- Low: 3
933
- };
934
- REVERSE_PRIORITY_MAP = {
935
- 0: "Resolve Immediately",
936
- 1: "High",
937
- 2: "Normal",
938
- 3: "Low",
939
- 4: "Low"
940
- };
941
- RallyTracker = class {
942
- name = "rally";
943
- restApi;
944
- workspace;
945
- project;
946
- constructor(config) {
947
- if (!config.apiKey) {
948
- throw new TrackerAuthError("rally", "API key is required");
949
- }
950
- this.restApi = new RallyRestApi({
951
- apiKey: config.apiKey,
952
- server: config.server || "https://rally1.rallydev.com",
953
- requestOptions: {
954
- headers: {
955
- "X-RallyIntegrationType": "Panopticon",
956
- "X-RallyIntegrationName": "Panopticon CLI",
957
- "X-RallyIntegrationVendor": "Mind Your Now",
958
- "X-RallyIntegrationVersion": "0.2.0"
959
- }
960
- }
961
- });
962
- this.workspace = config.workspace;
963
- this.project = config.project;
964
- }
965
- /**
966
- * List issues by querying each artifact type separately and merging results.
967
- *
968
- * Rally WSAPI cannot apply ScheduleState filters across the generic Artifact
969
- * endpoint because not all subtypes have that field. We query each type with
970
- * its own state field, then merge and sort. (PAN-168)
971
- */
972
- async listIssues(filters) {
973
- if (process.env.DEBUG?.includes("rally")) {
974
- console.debug("[Rally] Query filters:", JSON.stringify(filters));
975
- }
976
- const limit = filters?.limit ?? 50;
977
- let projectObjectId;
978
- if (this.project) {
979
- const match = this.project.match(/\/project\/(\d+)/);
980
- if (match) projectObjectId = match[1];
981
- }
982
- const queries = QUERYABLE_TYPES.map(async (artifactType) => {
983
- const queryString = this.buildQueryStringForType(filters, artifactType, projectObjectId);
984
- if (process.env.DEBUG?.includes("rally")) {
985
- console.debug(`[Rally] ${artifactType.type} query:`, queryString);
986
- }
987
- const query = {
988
- type: artifactType.type,
989
- fetch: FETCH_FIELDS,
990
- limit,
991
- query: queryString
992
- };
993
- if (this.workspace) {
994
- query.workspace = this.workspace;
995
- }
996
- if (this.project) {
997
- query.project = this.project;
998
- query.projectScopeDown = true;
999
- }
1000
- try {
1001
- const result = await this.queryRally(query);
1002
- return result.Results.map((artifact) => this.normalizeIssue(artifact));
1003
- } catch (error) {
1004
- if (error.message?.includes("Unauthorized") || error.message?.includes("401")) {
1005
- throw new TrackerAuthError("rally", "Invalid API key or insufficient permissions");
1006
- }
1007
- if (process.env.DEBUG?.includes("rally")) {
1008
- console.debug(`[Rally] Failed to query ${artifactType.type}:`, error.message);
1009
- }
1010
- return [];
1011
- }
1012
- });
1013
- const results = await Promise.all(queries);
1014
- const allIssues = results.flat();
1015
- allIssues.sort(
1016
- (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
1017
- );
1018
- return allIssues.slice(0, limit);
1019
- }
1020
- async getIssue(id) {
1021
- try {
1022
- const query = {
1023
- type: "artifact",
1024
- fetch: [
1025
- "FormattedID",
1026
- "Name",
1027
- "Description",
1028
- "ScheduleState",
1029
- "State",
1030
- "Tags",
1031
- "Owner",
1032
- "Priority",
1033
- "DueDate",
1034
- "CreationDate",
1035
- "LastUpdateDate",
1036
- "Parent",
1037
- "_type"
1038
- ],
1039
- query: `(FormattedID = "${id}")`
1040
- };
1041
- if (this.workspace) {
1042
- query.workspace = this.workspace;
1043
- }
1044
- const result = await this.queryRally(query);
1045
- if (!result.Results || result.Results.length === 0) {
1046
- throw new IssueNotFoundError(id, "rally");
1047
- }
1048
- return this.normalizeIssue(result.Results[0]);
1049
- } catch (error) {
1050
- if (error instanceof IssueNotFoundError) throw error;
1051
- throw new IssueNotFoundError(id, "rally");
1052
- }
1053
- }
1054
- async updateIssue(id, update) {
1055
- const issue = await this.getIssue(id);
1056
- const query = {
1057
- type: "artifact",
1058
- fetch: ["ObjectID", "_ref", "_type"],
1059
- query: `(FormattedID = "${id}")`
1060
- };
1061
- if (this.workspace) {
1062
- query.workspace = this.workspace;
1063
- }
1064
- const result = await this.queryRally(query);
1065
- if (!result.Results || result.Results.length === 0) {
1066
- throw new IssueNotFoundError(id, "rally");
1067
- }
1068
- const artifact = result.Results[0];
1069
- const updatePayload = {};
1070
- if (update.title !== void 0) {
1071
- updatePayload.Name = update.title;
1072
- }
1073
- if (update.description !== void 0) {
1074
- updatePayload.Description = update.description;
1075
- }
1076
- if (update.state !== void 0) {
1077
- const artifactType = (artifact._type || "").toLowerCase();
1078
- const kind = artifactType.startsWith("portfolioitem") ? "feature" : artifactType === "defect" ? "defect" : "story";
1079
- const rallyState = this.reverseMapState(update.state, kind);
1080
- if (kind === "story") {
1081
- updatePayload.ScheduleState = rallyState;
1082
- } else {
1083
- updatePayload.State = rallyState;
1084
- }
1085
- }
1086
- if (update.priority !== void 0) {
1087
- updatePayload.Priority = REVERSE_PRIORITY_MAP[update.priority] || "Normal";
1088
- }
1089
- if (update.dueDate !== void 0) {
1090
- updatePayload.DueDate = update.dueDate;
1091
- }
1092
- if (Object.keys(updatePayload).length > 0) {
1093
- await this.updateRally(artifact._type.toLowerCase(), artifact._ref, updatePayload);
1094
- }
1095
- return this.getIssue(id);
1096
- }
1097
- async createIssue(newIssue) {
1098
- if (!this.project && !newIssue.team) {
1099
- throw new Error("Project is required to create an issue. Set it in config or provide team field.");
1100
- }
1101
- const project = newIssue.team || this.project;
1102
- const createPayload = {
1103
- Name: newIssue.title,
1104
- Description: newIssue.description || "",
1105
- Project: project
1106
- };
1107
- if (newIssue.priority !== void 0) {
1108
- createPayload.Priority = REVERSE_PRIORITY_MAP[newIssue.priority] || "Normal";
1109
- }
1110
- if (newIssue.dueDate) {
1111
- createPayload.DueDate = newIssue.dueDate;
1112
- }
1113
- if (this.workspace) {
1114
- createPayload.Workspace = this.workspace;
1115
- }
1116
- const result = await this.createRally("hierarchicalrequirement", createPayload);
1117
- return this.getIssue(result.Object.FormattedID);
1118
- }
1119
- async getComments(issueId) {
1120
- const issue = await this.getIssue(issueId);
1121
- const query = {
1122
- type: "artifact",
1123
- fetch: ["ObjectID", "_ref", "Discussion"],
1124
- query: `(FormattedID = "${issueId}")`
1125
- };
1126
- if (this.workspace) {
1127
- query.workspace = this.workspace;
1128
- }
1129
- const result = await this.queryRally(query);
1130
- if (!result.Results || result.Results.length === 0) {
1131
- return [];
1132
- }
1133
- const artifact = result.Results[0];
1134
- if (!artifact.Discussion) {
1135
- return [];
1136
- }
1137
- const postsQuery = {
1138
- type: "conversationpost",
1139
- fetch: ["ObjectID", "Text", "User", "CreationDate", "PostNumber"],
1140
- query: `(Discussion = "${artifact.Discussion._ref}")`,
1141
- order: "PostNumber"
1142
- };
1143
- const postsResult = await this.queryRally(postsQuery);
1144
- return (postsResult.Results || []).map((post) => ({
1145
- id: post.ObjectID,
1146
- issueId,
1147
- body: post.Text || "",
1148
- author: post.User?._refObjectName || "Unknown",
1149
- createdAt: post.CreationDate,
1150
- updatedAt: post.CreationDate
1151
- // Rally doesn't track comment updates separately
1152
- }));
1153
- }
1154
- async addComment(issueId, body) {
1155
- const query = {
1156
- type: "artifact",
1157
- fetch: ["ObjectID", "_ref", "Discussion"],
1158
- query: `(FormattedID = "${issueId}")`
1159
- };
1160
- if (this.workspace) {
1161
- query.workspace = this.workspace;
1162
- }
1163
- const result = await this.queryRally(query);
1164
- if (!result.Results || result.Results.length === 0) {
1165
- throw new IssueNotFoundError(issueId, "rally");
1166
- }
1167
- const artifact = result.Results[0];
1168
- let discussionRef = artifact.Discussion?._ref;
1169
- if (!discussionRef) {
1170
- const discussionResult = await this.createRally("conversationpost", {
1171
- Artifact: artifact._ref,
1172
- Text: body
1173
- });
1174
- return {
1175
- id: discussionResult.Object.ObjectID,
1176
- issueId,
1177
- body,
1178
- author: "Panopticon",
1179
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1180
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1181
- };
1182
- }
1183
- const postResult = await this.createRally("conversationpost", {
1184
- Artifact: artifact._ref,
1185
- Text: body
1186
- });
1187
- return {
1188
- id: postResult.Object.ObjectID,
1189
- issueId,
1190
- body,
1191
- author: "Panopticon",
1192
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1193
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1194
- };
1195
- }
1196
- async transitionIssue(id, state) {
1197
- await this.updateIssue(id, { state });
1198
- }
1199
- async linkPR(issueId, prUrl) {
1200
- await this.addComment(issueId, `Linked Pull Request: ${prUrl}`);
1201
- }
1202
- // Private helper methods
1203
- /**
1204
- * Build a Rally WSAPI query string for a specific artifact type.
1205
- *
1206
- * Each artifact type has its own state field:
1207
- * - HierarchicalRequirement: ScheduleState (Defined, In-Progress, Completed, Accepted)
1208
- * - Defect: State (Submitted, Open, Fixed, Closed)
1209
- * - Task: State (Defined, In-Progress, Completed)
1210
- *
1211
- * Rally WSAPI v2.0 requires binary-nested AND/OR with outer parentheses.
1212
- * (PAN-166, PAN-168)
1213
- */
1214
- buildQueryStringForType(filters, artifactType, projectObjectId) {
1215
- const conditions = [];
1216
- if (projectObjectId) {
1217
- conditions.push(`(Project.ObjectID = "${projectObjectId}")`);
1218
- }
1219
- if (filters?.state && !filters.includeClosed) {
1220
- const kind = artifactType.type.startsWith("portfolioitem") ? "feature" : artifactType.type === "defect" ? "defect" : "story";
1221
- const rallyState = this.reverseMapState(filters.state, kind);
1222
- conditions.push(`(${artifactType.stateField} = "${rallyState}")`);
1223
- }
1224
- if (!filters?.includeClosed) {
1225
- const closedConditions = artifactType.closedStates.map(
1226
- (state) => `(${artifactType.stateField} != "${state}")`
1227
- );
1228
- const closedExpr = closedConditions.reduce(
1229
- (acc, cond) => acc ? `(${acc} AND ${cond})` : cond,
1230
- ""
1231
- );
1232
- conditions.push(closedExpr);
1233
- }
1234
- if (filters?.assignee) {
1235
- conditions.push(`(Owner.Name contains "${filters.assignee}")`);
1236
- }
1237
- if (filters?.labels && filters.labels.length > 0) {
1238
- const labelConditions = filters.labels.map(
1239
- (label) => `(Tags.Name contains "${label}")`
1240
- );
1241
- const labelExpr = labelConditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, "");
1242
- conditions.push(labelExpr);
1243
- }
1244
- if (filters?.query) {
1245
- conditions.push(`((Name contains "${filters.query}") OR (Description contains "${filters.query}"))`);
1246
- }
1247
- return conditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, "");
1248
- }
1249
- normalizeIssue(rallyArtifact) {
1250
- const rawStateValue = rallyArtifact.ScheduleState || rallyArtifact.State || "Defined";
1251
- const stateValue = typeof rawStateValue === "object" && rawStateValue !== null ? rawStateValue.Name || rawStateValue._refObjectName || "Defined" : rawStateValue;
1252
- const state = this.mapState(stateValue);
1253
- const labels = [];
1254
- if (rallyArtifact.Tags && rallyArtifact.Tags._tagsNameArray) {
1255
- for (const tag of rallyArtifact.Tags._tagsNameArray) {
1256
- if (typeof tag === "string") {
1257
- labels.push(tag);
1258
- } else if (tag?.Name) {
1259
- labels.push(tag.Name);
1260
- }
1261
- }
1262
- }
1263
- const priority = rallyArtifact.Priority ? PRIORITY_MAP[rallyArtifact.Priority] ?? 2 : void 0;
1264
- const objectId = rallyArtifact.ObjectID || rallyArtifact.FormattedID;
1265
- const artifactType = rallyArtifact._type || "artifact";
1266
- const baseUrl = this.restApi.server.replace("/slm/webservice/", "");
1267
- const url = `${baseUrl}/#/detail/${artifactType.toLowerCase()}/${objectId}`;
1268
- let parentRef;
1269
- if (rallyArtifact.PortfolioItem) {
1270
- if (rallyArtifact.PortfolioItem.FormattedID) {
1271
- parentRef = rallyArtifact.PortfolioItem.FormattedID;
1272
- } else if (rallyArtifact.PortfolioItem._refObjectName) {
1273
- parentRef = rallyArtifact.PortfolioItem._refObjectName;
1274
- }
1275
- } else if (rallyArtifact.Parent) {
1276
- if (rallyArtifact.Parent.FormattedID) {
1277
- parentRef = rallyArtifact.Parent.FormattedID;
1278
- } else if (rallyArtifact.Parent._refObjectName) {
1279
- parentRef = rallyArtifact.Parent._refObjectName;
1280
- }
1281
- }
1282
- return {
1283
- id: String(objectId),
1284
- ref: rallyArtifact.FormattedID,
1285
- title: rallyArtifact.Name || "",
1286
- description: rallyArtifact.Description || "",
1287
- state,
1288
- labels,
1289
- assignee: rallyArtifact.Owner?._refObjectName,
1290
- url,
1291
- tracker: "rally",
1292
- priority,
1293
- dueDate: rallyArtifact.DueDate,
1294
- createdAt: rallyArtifact.CreationDate,
1295
- updatedAt: rallyArtifact.LastUpdateDate,
1296
- parentRef,
1297
- artifactType,
1298
- rawState: stateValue
1299
- };
1300
- }
1301
- mapState(rallyState) {
1302
- return STATE_MAP2[rallyState] ?? "open";
1303
- }
1304
- reverseMapState(state, kind = "story") {
1305
- if (kind === "feature") {
1306
- switch (state) {
1307
- case "open":
1308
- return "Discovering";
1309
- case "in_progress":
1310
- return "Developing";
1311
- case "closed":
1312
- return "Done";
1313
- default:
1314
- return "Discovering";
1315
- }
1316
- }
1317
- if (kind === "defect") {
1318
- switch (state) {
1319
- case "open":
1320
- return "Submitted";
1321
- case "in_progress":
1322
- return "Open";
1323
- case "closed":
1324
- return "Closed";
1325
- default:
1326
- return "Submitted";
1327
- }
1328
- }
1329
- switch (state) {
1330
- case "open":
1331
- return "Defined";
1332
- case "in_progress":
1333
- return "In-Progress";
1334
- case "closed":
1335
- return "Completed";
1336
- default:
1337
- return "Defined";
1338
- }
1339
- }
1340
- // Rally API wrapper methods
1341
- async queryRally(queryConfig) {
1342
- const result = await this.restApi.query(queryConfig);
1343
- return {
1344
- Results: result.QueryResult.Results,
1345
- TotalResultCount: result.QueryResult.TotalResultCount
1346
- };
1347
- }
1348
- async createRally(type, data) {
1349
- const result = await this.restApi.create({
1350
- type,
1351
- data,
1352
- fetch: ["FormattedID", "ObjectID", "_ref"]
1353
- });
1354
- return {
1355
- Object: result.CreateResult.Object
1356
- };
1357
- }
1358
- async updateRally(type, ref, data) {
1359
- const result = await this.restApi.update({
1360
- type,
1361
- ref,
1362
- data,
1363
- fetch: ["FormattedID", "ObjectID"]
1364
- });
1365
- return {
1366
- Object: result.OperationResult.Object
1367
- };
1368
- }
1369
- };
1370
- }
1371
- });
1372
-
1373
- // src/lib/tracker/factory.ts
1374
- function getTrackerKeyFromConfig(trackerType) {
1375
- try {
1376
- const yamlConfig = loadConfig();
1377
- return yamlConfig.trackerKeys[trackerType];
1378
- } catch {
1379
- return void 0;
1380
- }
1381
- }
1382
- function createTracker(config) {
1383
- switch (config.type) {
1384
- case "linear": {
1385
- const configKey = getTrackerKeyFromConfig("linear");
1386
- const envKey = config.apiKeyEnv ? process.env[config.apiKeyEnv] : process.env.LINEAR_API_KEY;
1387
- const apiKey = configKey || envKey;
1388
- if (!apiKey) {
1389
- throw new TrackerAuthError(
1390
- "linear",
1391
- `API key not found. Configure in Settings or set ${config.apiKeyEnv ?? "LINEAR_API_KEY"} environment variable.`
1392
- );
1393
- }
1394
- return new LinearTracker(apiKey, { team: config.team });
1395
- }
1396
- case "github": {
1397
- const configKey = getTrackerKeyFromConfig("github");
1398
- const envToken = config.tokenEnv ? process.env[config.tokenEnv] : process.env.GITHUB_TOKEN;
1399
- const token = configKey || envToken;
1400
- if (!token) {
1401
- throw new TrackerAuthError(
1402
- "github",
1403
- `Token not found. Configure in Settings or set ${config.tokenEnv ?? "GITHUB_TOKEN"} environment variable.`
1404
- );
1405
- }
1406
- if (!config.owner || !config.repo) {
1407
- throw new Error(
1408
- "GitHub tracker requires owner and repo configuration"
1409
- );
1410
- }
1411
- return new GitHubTracker(token, config.owner, config.repo);
1412
- }
1413
- case "gitlab": {
1414
- const configKey = getTrackerKeyFromConfig("gitlab");
1415
- const envToken = config.tokenEnv ? process.env[config.tokenEnv] : process.env.GITLAB_TOKEN;
1416
- const token = configKey || envToken;
1417
- if (!token) {
1418
- throw new TrackerAuthError(
1419
- "gitlab",
1420
- `Token not found. Configure in Settings or set ${config.tokenEnv ?? "GITLAB_TOKEN"} environment variable.`
1421
- );
1422
- }
1423
- if (!config.projectId) {
1424
- throw new Error("GitLab tracker requires projectId configuration");
1425
- }
1426
- return new GitLabTracker(token, config.projectId);
1427
- }
1428
- case "rally": {
1429
- const configKey = getTrackerKeyFromConfig("rally");
1430
- const envKey = config.apiKeyEnv ? process.env[config.apiKeyEnv] : process.env.RALLY_API_KEY;
1431
- const apiKey = configKey || envKey;
1432
- if (!apiKey) {
1433
- throw new TrackerAuthError(
1434
- "rally",
1435
- `API key not found. Configure in Settings or set ${config.apiKeyEnv ?? "RALLY_API_KEY"} environment variable.`
1436
- );
1437
- }
1438
- return new RallyTracker({
1439
- apiKey,
1440
- server: config.server,
1441
- workspace: config.workspace,
1442
- project: config.project
1443
- });
1444
- }
1445
- default:
1446
- throw new Error(`Unknown tracker type: ${config.type}`);
1447
- }
1448
- }
1449
- function createTrackerFromConfig(trackersConfig, trackerType) {
1450
- const config = trackersConfig[trackerType];
1451
- if (!config) {
1452
- throw new Error(
1453
- `No configuration found for tracker: ${trackerType}. Add [trackers.${trackerType}] to config.`
1454
- );
1455
- }
1456
- return createTracker({ ...config, type: trackerType });
1457
- }
1458
- function getPrimaryTracker(trackersConfig) {
1459
- return createTrackerFromConfig(trackersConfig, trackersConfig.primary);
1460
- }
1461
- function getSecondaryTracker(trackersConfig) {
1462
- if (!trackersConfig.secondary) {
1463
- return null;
1464
- }
1465
- return createTrackerFromConfig(trackersConfig, trackersConfig.secondary);
1466
- }
1467
- function getAllTrackers(trackersConfig) {
1468
- const trackers = [getPrimaryTracker(trackersConfig)];
1469
- const secondary = getSecondaryTracker(trackersConfig);
1470
- if (secondary) {
1471
- trackers.push(secondary);
1472
- }
1473
- return trackers;
1474
- }
1475
- var init_factory = __esm({
1476
- "src/lib/tracker/factory.ts"() {
1477
- "use strict";
1478
- init_esm_shims();
1479
- init_interface();
1480
- init_linear();
1481
- init_github();
1482
- init_gitlab();
1483
- init_rally();
1484
- init_config_yaml();
1485
- }
1486
- });
1487
-
1488
- export {
1489
- loadSettings,
1490
- saveSettings,
1491
- validateSettings,
1492
- getDefaultSettings,
1493
- getAvailableModels,
1494
- isAnthropicModel,
1495
- getClaudeModelFlag,
1496
- getAgentCommand,
1497
- init_settings,
1498
- PROVIDERS,
1499
- getProviderForModel,
1500
- requiresRouter,
1501
- getRouterProviders,
1502
- getDirectProviders,
1503
- needsRouter,
1504
- getProviderEnv,
1505
- init_providers,
1506
- NotImplementedError,
1507
- IssueNotFoundError,
1508
- TrackerAuthError,
1509
- init_interface,
1510
- LinearTracker,
1511
- init_linear,
1512
- GitHubTracker,
1513
- init_github,
1514
- GitLabTracker,
1515
- init_gitlab,
1516
- createTracker,
1517
- createTrackerFromConfig,
1518
- getPrimaryTracker,
1519
- getSecondaryTracker,
1520
- getAllTrackers,
1521
- init_factory
1522
- };
1523
- //# sourceMappingURL=chunk-LYSBSZYV.js.map