panopticon-cli 0.4.28 → 0.4.31

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 (51) hide show
  1. package/dist/{agents-ND4NKCK2.js → agents-GQDAKTEQ.js} +5 -4
  2. package/dist/{chunk-SIAUVHVO.js → chunk-3XAB4IXF.js} +4 -2
  3. package/dist/{chunk-SIAUVHVO.js.map → chunk-3XAB4IXF.js.map} +1 -1
  4. package/dist/chunk-ELK6Q7QI.js +545 -0
  5. package/dist/chunk-ELK6Q7QI.js.map +1 -0
  6. package/dist/{chunk-ZLB6G4NW.js → chunk-HNEWTIR3.js} +41 -9
  7. package/dist/chunk-HNEWTIR3.js.map +1 -0
  8. package/dist/chunk-LYSBSZYV.js +1523 -0
  9. package/dist/chunk-LYSBSZYV.js.map +1 -0
  10. package/dist/{chunk-4KNEZGKZ.js → chunk-TMXN7THF.js} +45 -24
  11. package/dist/chunk-TMXN7THF.js.map +1 -0
  12. package/dist/{chunk-ON5NIBGW.js → chunk-VIWUCJ4V.js} +37 -8
  13. package/dist/chunk-VIWUCJ4V.js.map +1 -0
  14. package/dist/{chunk-VTMXR7JF.js → chunk-VU4FLXV5.js} +47 -40
  15. package/dist/{chunk-VTMXR7JF.js.map → chunk-VU4FLXV5.js.map} +1 -1
  16. package/dist/cli/index.js +153 -86
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/{config-QWTS63TU.js → config-BOAMSKTF.js} +4 -2
  19. package/dist/dashboard/public/assets/{index--VPaQ2VU.css → index-C7X6LP5Z.css} +1 -1
  20. package/dist/dashboard/public/assets/{index-GYQaqwVS.js → index-izWbAt7V.js} +152 -152
  21. package/dist/dashboard/public/index.html +2 -2
  22. package/dist/dashboard/server.js +94706 -24017
  23. package/dist/feedback-writer-AAKF5BTK.js +111 -0
  24. package/dist/feedback-writer-AAKF5BTK.js.map +1 -0
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +16 -14
  27. package/dist/index.js.map +1 -1
  28. package/dist/{remote-workspace-FNXLMNBG.js → remote-workspace-2G6V2KNP.js} +7 -5
  29. package/dist/{remote-workspace-FNXLMNBG.js.map → remote-workspace-2G6V2KNP.js.map} +1 -1
  30. package/dist/{specialist-context-WXO3FKIB.js → specialist-context-6SE5VRRC.js} +3 -3
  31. package/dist/{specialist-logs-SJWLETJT.js → specialist-logs-EXLOQHQ2.js} +3 -3
  32. package/dist/{specialists-5YJIDRW6.js → specialists-BRUHPAXE.js} +3 -3
  33. package/dist/{traefik-7OLLXUD7.js → traefik-CUJM6K5Z.js} +3 -3
  34. package/package.json +3 -2
  35. package/scripts/record-cost-event.js +243 -79
  36. package/scripts/record-cost-event.ts +128 -68
  37. package/templates/traefik/docker-compose.yml +7 -4
  38. package/templates/traefik/dynamic/panopticon.yml.template +3 -1
  39. package/dist/chunk-46DPNFMW.js +0 -278
  40. package/dist/chunk-46DPNFMW.js.map +0 -1
  41. package/dist/chunk-4KNEZGKZ.js.map +0 -1
  42. package/dist/chunk-ON5NIBGW.js.map +0 -1
  43. package/dist/chunk-SUMIHS2B.js +0 -1714
  44. package/dist/chunk-SUMIHS2B.js.map +0 -1
  45. package/dist/chunk-ZLB6G4NW.js.map +0 -1
  46. /package/dist/{agents-ND4NKCK2.js.map → agents-GQDAKTEQ.js.map} +0 -0
  47. /package/dist/{config-QWTS63TU.js.map → config-BOAMSKTF.js.map} +0 -0
  48. /package/dist/{specialist-context-WXO3FKIB.js.map → specialist-context-6SE5VRRC.js.map} +0 -0
  49. /package/dist/{specialist-logs-SJWLETJT.js.map → specialist-logs-EXLOQHQ2.js.map} +0 -0
  50. /package/dist/{specialists-5YJIDRW6.js.map → specialists-BRUHPAXE.js.map} +0 -0
  51. /package/dist/{traefik-7OLLXUD7.js.map → traefik-CUJM6K5Z.js.map} +0 -0
@@ -1,1714 +0,0 @@
1
- import {
2
- init_config_yaml,
3
- loadConfig
4
- } from "./chunk-BBCUK6N2.js";
5
- import {
6
- AGENTS_DIR,
7
- BACKUPS_DIR,
8
- BIN_DIR,
9
- COMMANDS_DIR,
10
- SKILLS_DIR,
11
- SOURCE_DEV_SKILLS_DIR,
12
- SOURCE_SCRIPTS_DIR,
13
- SYNC_TARGETS,
14
- init_paths,
15
- isDevMode
16
- } from "./chunk-6HXKTOD7.js";
17
- import {
18
- init_esm_shims
19
- } from "./chunk-ZHC57RCV.js";
20
-
21
- // src/lib/shell.ts
22
- init_esm_shims();
23
- import { existsSync, readFileSync, appendFileSync } from "fs";
24
- import { homedir } from "os";
25
- import { join } from "path";
26
- function detectShell() {
27
- const shell = process.env.SHELL || "";
28
- if (shell.includes("zsh")) return "zsh";
29
- if (shell.includes("bash")) return "bash";
30
- if (shell.includes("fish")) return "fish";
31
- return "unknown";
32
- }
33
- function getShellRcFile(shell) {
34
- const home = homedir();
35
- switch (shell) {
36
- case "zsh":
37
- return join(home, ".zshrc");
38
- case "bash":
39
- const bashrc = join(home, ".bashrc");
40
- if (existsSync(bashrc)) return bashrc;
41
- return join(home, ".bash_profile");
42
- case "fish":
43
- return join(home, ".config", "fish", "config.fish");
44
- default:
45
- return null;
46
- }
47
- }
48
- var ALIAS_LINE = 'alias pan="panopticon"';
49
- var ALIAS_MARKER = "# Panopticon CLI alias";
50
- function hasAlias(rcFile) {
51
- if (!existsSync(rcFile)) return false;
52
- const content = readFileSync(rcFile, "utf8");
53
- return content.includes(ALIAS_MARKER) || content.includes(ALIAS_LINE);
54
- }
55
- function addAlias(rcFile) {
56
- if (hasAlias(rcFile)) return;
57
- const aliasBlock = `
58
- ${ALIAS_MARKER}
59
- ${ALIAS_LINE}
60
- `;
61
- appendFileSync(rcFile, aliasBlock, "utf8");
62
- }
63
- function getAliasInstructions(shell) {
64
- const rcFile = getShellRcFile(shell);
65
- if (!rcFile) {
66
- return `Add this to your shell config:
67
- ${ALIAS_LINE}`;
68
- }
69
- return `Alias added to ${rcFile}. Run:
70
- source ${rcFile}`;
71
- }
72
-
73
- // src/lib/backup.ts
74
- init_esm_shims();
75
- init_paths();
76
- import { existsSync as existsSync2, mkdirSync, readdirSync, cpSync, rmSync, lstatSync } from "fs";
77
- import { join as join2, basename } from "path";
78
- function createBackupTimestamp() {
79
- return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
80
- }
81
- function createBackup(sourceDirs) {
82
- const timestamp = createBackupTimestamp();
83
- const backupPath = join2(BACKUPS_DIR, timestamp);
84
- mkdirSync(backupPath, { recursive: true });
85
- const targets = [];
86
- for (const sourceDir of sourceDirs) {
87
- if (!existsSync2(sourceDir)) continue;
88
- const targetName = basename(sourceDir);
89
- const targetPath = join2(backupPath, targetName);
90
- cpSync(sourceDir, targetPath, {
91
- recursive: true,
92
- filter: (src) => !lstatSync(src).isSymbolicLink()
93
- });
94
- targets.push(targetName);
95
- }
96
- return {
97
- timestamp,
98
- path: backupPath,
99
- targets
100
- };
101
- }
102
- function listBackups() {
103
- if (!existsSync2(BACKUPS_DIR)) return [];
104
- const entries = readdirSync(BACKUPS_DIR, { withFileTypes: true });
105
- return entries.filter((e) => e.isDirectory()).map((e) => {
106
- const backupPath = join2(BACKUPS_DIR, e.name);
107
- const contents = readdirSync(backupPath);
108
- return {
109
- timestamp: e.name,
110
- path: backupPath,
111
- targets: contents
112
- };
113
- }).sort((a, b) => b.timestamp.localeCompare(a.timestamp));
114
- }
115
- function restoreBackup(timestamp, targetDirs) {
116
- const backupPath = join2(BACKUPS_DIR, timestamp);
117
- if (!existsSync2(backupPath)) {
118
- throw new Error(`Backup not found: ${timestamp}`);
119
- }
120
- const contents = readdirSync(backupPath, { withFileTypes: true });
121
- for (const entry of contents) {
122
- if (!entry.isDirectory()) continue;
123
- const sourcePath = join2(backupPath, entry.name);
124
- const targetPath = targetDirs[entry.name];
125
- if (!targetPath) continue;
126
- if (existsSync2(targetPath)) {
127
- rmSync(targetPath, { recursive: true });
128
- }
129
- cpSync(sourcePath, targetPath, { recursive: true });
130
- }
131
- }
132
- function cleanOldBackups(keepCount = 10) {
133
- const backups = listBackups();
134
- if (backups.length <= keepCount) return 0;
135
- const toRemove = backups.slice(keepCount);
136
- let removed = 0;
137
- for (const backup of toRemove) {
138
- rmSync(backup.path, { recursive: true });
139
- removed++;
140
- }
141
- return removed;
142
- }
143
-
144
- // src/lib/sync.ts
145
- init_esm_shims();
146
- init_paths();
147
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, symlinkSync, unlinkSync, lstatSync as lstatSync2, readlinkSync, rmSync as rmSync2, copyFileSync, chmodSync } from "fs";
148
- import { join as join3 } from "path";
149
- function removeTarget(targetPath) {
150
- const stats = lstatSync2(targetPath);
151
- if (stats.isDirectory() && !stats.isSymbolicLink()) {
152
- rmSync2(targetPath, { recursive: true, force: true });
153
- } else {
154
- unlinkSync(targetPath);
155
- }
156
- }
157
- function isPanopticonSymlink(targetPath) {
158
- if (!existsSync3(targetPath)) return false;
159
- try {
160
- const stats = lstatSync2(targetPath);
161
- if (!stats.isSymbolicLink()) return false;
162
- const linkTarget = readlinkSync(targetPath);
163
- return linkTarget.includes(".panopticon");
164
- } catch {
165
- return false;
166
- }
167
- }
168
- function planSync(runtime) {
169
- const targets = SYNC_TARGETS[runtime];
170
- if (!targets) {
171
- throw new Error(`Unknown sync target "${runtime}". Valid targets: ${Object.keys(SYNC_TARGETS).join(", ")}`);
172
- }
173
- const plan = {
174
- runtime,
175
- skills: [],
176
- commands: [],
177
- agents: [],
178
- devSkills: []
179
- };
180
- if (existsSync3(SKILLS_DIR)) {
181
- const skills = readdirSync2(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
182
- for (const skill of skills) {
183
- const sourcePath = join3(SKILLS_DIR, skill.name);
184
- const targetPath = join3(targets.skills, skill.name);
185
- let status = "new";
186
- if (existsSync3(targetPath)) {
187
- if (isPanopticonSymlink(targetPath)) {
188
- status = "symlink";
189
- } else {
190
- status = "conflict";
191
- }
192
- }
193
- plan.skills.push({ name: skill.name, sourcePath, targetPath, status });
194
- }
195
- }
196
- if (isDevMode() && existsSync3(SOURCE_DEV_SKILLS_DIR)) {
197
- const devSkills = readdirSync2(SOURCE_DEV_SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
198
- for (const skill of devSkills) {
199
- const sourcePath = join3(SOURCE_DEV_SKILLS_DIR, skill.name);
200
- const targetPath = join3(targets.skills, skill.name);
201
- let status = "new";
202
- if (existsSync3(targetPath)) {
203
- if (isPanopticonSymlink(targetPath)) {
204
- status = "symlink";
205
- } else {
206
- status = "conflict";
207
- }
208
- }
209
- plan.devSkills.push({ name: skill.name, sourcePath, targetPath, status });
210
- }
211
- }
212
- if (existsSync3(COMMANDS_DIR)) {
213
- const commands = readdirSync2(COMMANDS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
214
- for (const cmd of commands) {
215
- const sourcePath = join3(COMMANDS_DIR, cmd.name);
216
- const targetPath = join3(targets.commands, cmd.name);
217
- let status = "new";
218
- if (existsSync3(targetPath)) {
219
- if (isPanopticonSymlink(targetPath)) {
220
- status = "symlink";
221
- } else {
222
- status = "conflict";
223
- }
224
- }
225
- plan.commands.push({ name: cmd.name, sourcePath, targetPath, status });
226
- }
227
- }
228
- if (existsSync3(AGENTS_DIR)) {
229
- const agents = readdirSync2(AGENTS_DIR, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md"));
230
- for (const agent of agents) {
231
- const sourcePath = join3(AGENTS_DIR, agent.name);
232
- const targetPath = join3(targets.agents, agent.name);
233
- let status = "new";
234
- if (existsSync3(targetPath)) {
235
- if (isPanopticonSymlink(targetPath)) {
236
- status = "symlink";
237
- } else {
238
- status = "conflict";
239
- }
240
- }
241
- plan.agents.push({ name: agent.name, sourcePath, targetPath, status });
242
- }
243
- }
244
- return plan;
245
- }
246
- function executeSync(runtime, options = {}) {
247
- const targets = SYNC_TARGETS[runtime];
248
- if (!targets) {
249
- throw new Error(`Unknown sync target "${runtime}". Valid targets: ${Object.keys(SYNC_TARGETS).join(", ")}`);
250
- }
251
- const plan = planSync(runtime);
252
- const result = {
253
- created: [],
254
- skipped: [],
255
- conflicts: []
256
- };
257
- mkdirSync2(targets.skills, { recursive: true });
258
- mkdirSync2(targets.commands, { recursive: true });
259
- mkdirSync2(targets.agents, { recursive: true });
260
- for (const item of plan.skills) {
261
- if (options.dryRun) {
262
- if (item.status === "new" || item.status === "symlink") {
263
- result.created.push(item.name);
264
- } else {
265
- result.conflicts.push(item.name);
266
- }
267
- continue;
268
- }
269
- if (item.status === "conflict" && !options.force) {
270
- result.conflicts.push(item.name);
271
- continue;
272
- }
273
- if (existsSync3(item.targetPath)) {
274
- removeTarget(item.targetPath);
275
- }
276
- symlinkSync(item.sourcePath, item.targetPath);
277
- result.created.push(item.name);
278
- }
279
- for (const item of plan.commands) {
280
- if (options.dryRun) {
281
- if (item.status === "new" || item.status === "symlink") {
282
- result.created.push(item.name);
283
- } else {
284
- result.conflicts.push(item.name);
285
- }
286
- continue;
287
- }
288
- if (item.status === "conflict" && !options.force) {
289
- result.conflicts.push(item.name);
290
- continue;
291
- }
292
- if (existsSync3(item.targetPath)) {
293
- removeTarget(item.targetPath);
294
- }
295
- symlinkSync(item.sourcePath, item.targetPath);
296
- result.created.push(item.name);
297
- }
298
- for (const item of plan.agents) {
299
- if (options.dryRun) {
300
- if (item.status === "new" || item.status === "symlink") {
301
- result.created.push(item.name);
302
- } else {
303
- result.conflicts.push(item.name);
304
- }
305
- continue;
306
- }
307
- if (item.status === "conflict" && !options.force) {
308
- result.conflicts.push(item.name);
309
- continue;
310
- }
311
- if (existsSync3(item.targetPath)) {
312
- removeTarget(item.targetPath);
313
- }
314
- symlinkSync(item.sourcePath, item.targetPath);
315
- result.created.push(item.name);
316
- }
317
- for (const item of plan.devSkills) {
318
- if (options.dryRun) {
319
- if (item.status === "new" || item.status === "symlink") {
320
- result.created.push(`${item.name} (dev)`);
321
- } else {
322
- result.conflicts.push(`${item.name} (dev)`);
323
- }
324
- continue;
325
- }
326
- if (item.status === "conflict" && !options.force) {
327
- result.conflicts.push(`${item.name} (dev)`);
328
- continue;
329
- }
330
- if (existsSync3(item.targetPath)) {
331
- removeTarget(item.targetPath);
332
- }
333
- symlinkSync(item.sourcePath, item.targetPath);
334
- result.created.push(`${item.name} (dev)`);
335
- }
336
- return result;
337
- }
338
- function planHooksSync() {
339
- const hooks = [];
340
- if (!existsSync3(SOURCE_SCRIPTS_DIR)) {
341
- return hooks;
342
- }
343
- const scripts = readdirSync2(SOURCE_SCRIPTS_DIR, { withFileTypes: true }).filter((entry) => entry.isFile() && !entry.name.startsWith(".") && !entry.name.includes("."));
344
- for (const script of scripts) {
345
- const sourcePath = join3(SOURCE_SCRIPTS_DIR, script.name);
346
- const targetPath = join3(BIN_DIR, script.name);
347
- let status = "new";
348
- if (existsSync3(targetPath)) {
349
- status = "updated";
350
- }
351
- hooks.push({ name: script.name, sourcePath, targetPath, status });
352
- }
353
- return hooks;
354
- }
355
- function syncHooks() {
356
- const result = { synced: [], errors: [] };
357
- mkdirSync2(BIN_DIR, { recursive: true });
358
- const hooks = planHooksSync();
359
- for (const hook of hooks) {
360
- try {
361
- copyFileSync(hook.sourcePath, hook.targetPath);
362
- chmodSync(hook.targetPath, 493);
363
- result.synced.push(hook.name);
364
- } catch (error) {
365
- result.errors.push(`${hook.name}: ${error}`);
366
- }
367
- }
368
- return result;
369
- }
370
-
371
- // src/lib/tracker/interface.ts
372
- init_esm_shims();
373
- var NotImplementedError = class extends Error {
374
- constructor(feature) {
375
- super(`Not implemented: ${feature}`);
376
- this.name = "NotImplementedError";
377
- }
378
- };
379
- var IssueNotFoundError = class extends Error {
380
- constructor(id, tracker) {
381
- super(`Issue not found: ${id} (tracker: ${tracker})`);
382
- this.name = "IssueNotFoundError";
383
- }
384
- };
385
- var TrackerAuthError = class extends Error {
386
- constructor(tracker, message) {
387
- super(`Authentication failed for ${tracker}: ${message}`);
388
- this.name = "TrackerAuthError";
389
- }
390
- };
391
-
392
- // src/lib/tracker/linear.ts
393
- init_esm_shims();
394
- import { LinearClient } from "@linear/sdk";
395
- var STATE_MAP = {
396
- backlog: "open",
397
- unstarted: "open",
398
- started: "in_progress",
399
- completed: "closed",
400
- canceled: "closed"
401
- };
402
- var LinearTracker = class {
403
- name = "linear";
404
- client;
405
- defaultTeam;
406
- constructor(apiKey, options) {
407
- if (!apiKey) {
408
- throw new TrackerAuthError("linear", "API key is required");
409
- }
410
- this.client = new LinearClient({ apiKey });
411
- this.defaultTeam = options?.team;
412
- }
413
- async listIssues(filters) {
414
- const team = filters?.team ?? this.defaultTeam;
415
- const result = await this.client.issues({
416
- first: filters?.limit ?? 50,
417
- filter: {
418
- team: team ? { key: { eq: team } } : void 0,
419
- state: filters?.state ? { type: { eq: this.reverseMapState(filters.state) } } : filters?.includeClosed ? void 0 : { type: { neq: "completed" } },
420
- labels: filters?.labels?.length ? { name: { in: filters.labels } } : void 0,
421
- assignee: filters?.assignee ? { name: { containsIgnoreCase: filters.assignee } } : void 0
422
- }
423
- });
424
- const issues = [];
425
- for (const node of result.nodes) {
426
- issues.push(await this.normalizeIssue(node));
427
- }
428
- return issues;
429
- }
430
- async getIssue(id) {
431
- try {
432
- 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);
433
- if (isUuid) {
434
- const issue = await this.client.issue(id);
435
- if (issue) {
436
- return this.normalizeIssue(issue);
437
- }
438
- } else {
439
- const match = id.match(/^([A-Z]+)-(\d+)$/i);
440
- if (match) {
441
- const [, teamKey, number] = match;
442
- const results = await this.client.searchIssues(id, { first: 1 });
443
- if (results.nodes.length > 0) {
444
- return this.normalizeIssue(results.nodes[0]);
445
- }
446
- }
447
- }
448
- throw new IssueNotFoundError(id, "linear");
449
- } catch (error) {
450
- if (error instanceof IssueNotFoundError) throw error;
451
- throw new IssueNotFoundError(id, "linear");
452
- }
453
- }
454
- async updateIssue(id, update) {
455
- const issue = await this.getIssue(id);
456
- const updatePayload = {};
457
- if (update.title !== void 0) {
458
- updatePayload.title = update.title;
459
- }
460
- if (update.description !== void 0) {
461
- updatePayload.description = update.description;
462
- }
463
- if (update.priority !== void 0) {
464
- updatePayload.priority = update.priority;
465
- }
466
- if (update.dueDate !== void 0) {
467
- updatePayload.dueDate = update.dueDate;
468
- }
469
- if (update.state !== void 0) {
470
- await this.transitionIssue(id, update.state);
471
- }
472
- if (update.labels !== void 0) {
473
- }
474
- if (Object.keys(updatePayload).length > 0) {
475
- await this.client.updateIssue(issue.id, updatePayload);
476
- }
477
- return this.getIssue(id);
478
- }
479
- async createIssue(newIssue) {
480
- const team = newIssue.team ?? this.defaultTeam;
481
- if (!team) {
482
- throw new Error("Team is required to create an issue");
483
- }
484
- const teams = await this.client.teams({
485
- filter: { key: { eq: team } }
486
- });
487
- if (teams.nodes.length === 0) {
488
- throw new Error(`Team not found: ${team}`);
489
- }
490
- const teamId = teams.nodes[0].id;
491
- const result = await this.client.createIssue({
492
- teamId,
493
- title: newIssue.title,
494
- description: newIssue.description,
495
- priority: newIssue.priority,
496
- dueDate: newIssue.dueDate
497
- });
498
- const created = await result.issue;
499
- if (!created) {
500
- throw new Error("Failed to create issue");
501
- }
502
- return this.normalizeIssue(created);
503
- }
504
- async getComments(issueId) {
505
- const issue = await this.client.issue(issueId);
506
- const comments = await issue.comments();
507
- return comments.nodes.map((c) => ({
508
- id: c.id,
509
- issueId,
510
- body: c.body,
511
- author: c.user?.then((u) => u?.name ?? "Unknown"),
512
- // Simplified
513
- createdAt: c.createdAt.toISOString(),
514
- updatedAt: c.updatedAt.toISOString()
515
- }));
516
- }
517
- async addComment(issueId, body) {
518
- const result = await this.client.createComment({
519
- issueId,
520
- body
521
- });
522
- const comment = await result.comment;
523
- if (!comment) {
524
- throw new Error("Failed to create comment");
525
- }
526
- return {
527
- id: comment.id,
528
- issueId,
529
- body: comment.body,
530
- author: "Panopticon",
531
- // Simplified
532
- createdAt: comment.createdAt.toISOString(),
533
- updatedAt: comment.updatedAt.toISOString()
534
- };
535
- }
536
- async transitionIssue(id, state) {
537
- const issue = await this.getIssue(id);
538
- const linearIssue = await this.client.issue(issue.id);
539
- const team = await linearIssue.team;
540
- if (!team) {
541
- throw new Error("Could not determine issue team");
542
- }
543
- const states = await team.states();
544
- const targetStateType = this.reverseMapState(state);
545
- const targetState = states.nodes.find((s) => s.type === targetStateType);
546
- if (!targetState) {
547
- throw new Error(`No state found matching type: ${targetStateType}`);
548
- }
549
- await this.client.updateIssue(issue.id, {
550
- stateId: targetState.id
551
- });
552
- }
553
- async linkPR(issueId, prUrl) {
554
- const issue = await this.getIssue(issueId);
555
- await this.client.createAttachment({
556
- issueId: issue.id,
557
- title: "Pull Request",
558
- url: prUrl
559
- });
560
- }
561
- async normalizeIssue(linearIssue) {
562
- const state = await linearIssue.state;
563
- const assignee = await linearIssue.assignee;
564
- const labels = await linearIssue.labels();
565
- let dueDate;
566
- if (linearIssue.dueDate) {
567
- dueDate = linearIssue.dueDate instanceof Date ? linearIssue.dueDate.toISOString() : String(linearIssue.dueDate);
568
- }
569
- return {
570
- id: linearIssue.id,
571
- ref: linearIssue.identifier,
572
- title: linearIssue.title,
573
- description: linearIssue.description ?? "",
574
- state: this.mapState(state?.type ?? "backlog"),
575
- labels: labels?.nodes?.map((l) => l.name) ?? [],
576
- assignee: assignee?.name,
577
- url: linearIssue.url,
578
- tracker: "linear",
579
- priority: linearIssue.priority,
580
- dueDate,
581
- createdAt: linearIssue.createdAt instanceof Date ? linearIssue.createdAt.toISOString() : String(linearIssue.createdAt),
582
- updatedAt: linearIssue.updatedAt instanceof Date ? linearIssue.updatedAt.toISOString() : String(linearIssue.updatedAt)
583
- };
584
- }
585
- mapState(linearState) {
586
- return STATE_MAP[linearState] ?? "open";
587
- }
588
- reverseMapState(state) {
589
- switch (state) {
590
- case "open":
591
- return "unstarted";
592
- case "in_progress":
593
- return "started";
594
- case "closed":
595
- return "completed";
596
- default:
597
- return "unstarted";
598
- }
599
- }
600
- };
601
-
602
- // src/lib/tracker/github.ts
603
- init_esm_shims();
604
- import { Octokit } from "@octokit/rest";
605
- var GitHubTracker = class {
606
- name = "github";
607
- octokit;
608
- owner;
609
- repo;
610
- constructor(token, owner, repo) {
611
- if (!token) {
612
- throw new TrackerAuthError("github", "Token is required");
613
- }
614
- if (!owner || !repo) {
615
- throw new Error("GitHub owner and repo are required");
616
- }
617
- this.octokit = new Octokit({ auth: token });
618
- this.owner = owner;
619
- this.repo = repo;
620
- }
621
- async listIssues(filters) {
622
- const state = this.mapStateToGitHub(filters?.state);
623
- const response = await this.octokit.issues.listForRepo({
624
- owner: this.owner,
625
- repo: this.repo,
626
- state: filters?.includeClosed ? "all" : state,
627
- labels: filters?.labels?.join(",") || void 0,
628
- assignee: filters?.assignee || void 0,
629
- per_page: filters?.limit ?? 50
630
- });
631
- const issues = response.data.filter((item) => !item.pull_request);
632
- return issues.map((issue) => this.normalizeIssue(issue));
633
- }
634
- async getIssue(id) {
635
- try {
636
- const issueNumber = parseInt(id.replace(/^#/, ""), 10);
637
- if (isNaN(issueNumber)) {
638
- throw new IssueNotFoundError(id, "github");
639
- }
640
- const { data: issue } = await this.octokit.issues.get({
641
- owner: this.owner,
642
- repo: this.repo,
643
- issue_number: issueNumber
644
- });
645
- return this.normalizeIssue(issue);
646
- } catch (error) {
647
- if (error?.status === 404) {
648
- throw new IssueNotFoundError(id, "github");
649
- }
650
- throw error;
651
- }
652
- }
653
- async updateIssue(id, update) {
654
- const issueNumber = parseInt(id.replace(/^#/, ""), 10);
655
- const updatePayload = {};
656
- if (update.title !== void 0) {
657
- updatePayload.title = update.title;
658
- }
659
- if (update.description !== void 0) {
660
- updatePayload.body = update.description;
661
- }
662
- if (update.state !== void 0) {
663
- updatePayload.state = update.state === "closed" ? "closed" : "open";
664
- }
665
- if (update.labels !== void 0) {
666
- updatePayload.labels = update.labels;
667
- }
668
- if (update.assignee !== void 0) {
669
- updatePayload.assignees = update.assignee ? [update.assignee] : [];
670
- }
671
- await this.octokit.issues.update({
672
- owner: this.owner,
673
- repo: this.repo,
674
- issue_number: issueNumber,
675
- ...updatePayload
676
- });
677
- return this.getIssue(id);
678
- }
679
- async createIssue(newIssue) {
680
- const { data: issue } = await this.octokit.issues.create({
681
- owner: this.owner,
682
- repo: this.repo,
683
- title: newIssue.title,
684
- body: newIssue.description,
685
- labels: newIssue.labels,
686
- assignees: newIssue.assignee ? [newIssue.assignee] : void 0
687
- });
688
- return this.normalizeIssue(issue);
689
- }
690
- async getComments(issueId) {
691
- const issueNumber = parseInt(issueId.replace(/^#/, ""), 10);
692
- const { data: comments } = await this.octokit.issues.listComments({
693
- owner: this.owner,
694
- repo: this.repo,
695
- issue_number: issueNumber
696
- });
697
- return comments.map((c) => ({
698
- id: String(c.id),
699
- issueId,
700
- body: c.body ?? "",
701
- author: c.user?.login ?? "Unknown",
702
- createdAt: c.created_at,
703
- updatedAt: c.updated_at
704
- }));
705
- }
706
- async addComment(issueId, body) {
707
- const issueNumber = parseInt(issueId.replace(/^#/, ""), 10);
708
- const { data: comment } = await this.octokit.issues.createComment({
709
- owner: this.owner,
710
- repo: this.repo,
711
- issue_number: issueNumber,
712
- body
713
- });
714
- return {
715
- id: String(comment.id),
716
- issueId,
717
- body: comment.body ?? "",
718
- author: comment.user?.login ?? "Unknown",
719
- createdAt: comment.created_at,
720
- updatedAt: comment.updated_at
721
- };
722
- }
723
- async transitionIssue(id, state) {
724
- await this.updateIssue(id, { state });
725
- }
726
- async linkPR(issueId, prUrl) {
727
- await this.addComment(
728
- issueId,
729
- `Linked Pull Request: ${prUrl}`
730
- );
731
- }
732
- normalizeIssue(ghIssue) {
733
- return {
734
- id: String(ghIssue.id),
735
- ref: `#${ghIssue.number}`,
736
- title: ghIssue.title,
737
- description: ghIssue.body ?? "",
738
- state: this.mapStateFromGitHub(ghIssue.state),
739
- labels: ghIssue.labels.map(
740
- (l) => typeof l === "string" ? l : l.name
741
- ),
742
- assignee: ghIssue.assignee?.login,
743
- url: ghIssue.html_url,
744
- tracker: "github",
745
- priority: void 0,
746
- // GitHub doesn't have priority
747
- dueDate: void 0,
748
- // GitHub doesn't have due dates on issues
749
- createdAt: ghIssue.created_at,
750
- updatedAt: ghIssue.updated_at
751
- };
752
- }
753
- mapStateFromGitHub(ghState) {
754
- return ghState === "closed" ? "closed" : "open";
755
- }
756
- mapStateToGitHub(state) {
757
- if (!state) return "open";
758
- if (state === "closed") return "closed";
759
- return "open";
760
- }
761
- };
762
-
763
- // src/lib/tracker/gitlab.ts
764
- init_esm_shims();
765
- var GitLabTracker = class {
766
- constructor(token, projectId) {
767
- this.token = token;
768
- this.projectId = projectId;
769
- }
770
- name = "gitlab";
771
- async listIssues(_filters) {
772
- throw new NotImplementedError(
773
- "GitLab tracker is not yet implemented. Coming soon!"
774
- );
775
- }
776
- async getIssue(_id) {
777
- throw new NotImplementedError(
778
- "GitLab tracker is not yet implemented. Coming soon!"
779
- );
780
- }
781
- async updateIssue(_id, _update) {
782
- throw new NotImplementedError(
783
- "GitLab tracker is not yet implemented. Coming soon!"
784
- );
785
- }
786
- async createIssue(_issue) {
787
- throw new NotImplementedError(
788
- "GitLab tracker is not yet implemented. Coming soon!"
789
- );
790
- }
791
- async getComments(_issueId) {
792
- throw new NotImplementedError(
793
- "GitLab tracker is not yet implemented. Coming soon!"
794
- );
795
- }
796
- async addComment(_issueId, _body) {
797
- throw new NotImplementedError(
798
- "GitLab tracker is not yet implemented. Coming soon!"
799
- );
800
- }
801
- async transitionIssue(_id, _state) {
802
- throw new NotImplementedError(
803
- "GitLab tracker is not yet implemented. Coming soon!"
804
- );
805
- }
806
- async linkPR(_issueId, _prUrl) {
807
- throw new NotImplementedError(
808
- "GitLab tracker is not yet implemented. Coming soon!"
809
- );
810
- }
811
- };
812
-
813
- // src/lib/tracker/factory.ts
814
- init_esm_shims();
815
-
816
- // src/lib/tracker/rally.ts
817
- init_esm_shims();
818
-
819
- // src/lib/tracker/rally-api.ts
820
- init_esm_shims();
821
- var RallyRestApi = class {
822
- apiKey;
823
- server;
824
- customHeaders;
825
- constructor(config) {
826
- this.apiKey = config.apiKey;
827
- this.server = config.server || "https://rally1.rallydev.com";
828
- this.customHeaders = config.requestOptions?.headers || {};
829
- }
830
- /**
831
- * Query Rally artifacts
832
- */
833
- async query(config) {
834
- const params = new URLSearchParams();
835
- if (config.query) {
836
- params.set("query", config.query);
837
- }
838
- if (config.fetch && config.fetch.length > 0) {
839
- params.set("fetch", config.fetch.join(","));
840
- }
841
- if (config.limit !== void 0) {
842
- params.set("pagesize", String(config.limit));
843
- }
844
- if (config.workspace) {
845
- params.set("workspace", config.workspace);
846
- }
847
- if (config.project) {
848
- params.set("project", config.project);
849
- if (config.projectScopeDown) {
850
- params.set("projectScopeDown", "true");
851
- }
852
- }
853
- if (config.order) {
854
- params.set("order", config.order);
855
- }
856
- const url = `${this.server}/slm/webservice/v2.0/${config.type}?${params.toString()}`;
857
- const response = await fetch(url, {
858
- method: "GET",
859
- headers: {
860
- "ZSESSIONID": this.apiKey,
861
- "Content-Type": "application/json",
862
- ...this.customHeaders
863
- }
864
- });
865
- if (!response.ok) {
866
- if (response.status === 401) {
867
- throw new Error("Unauthorized: Invalid API key or insufficient permissions");
868
- }
869
- throw new Error(`Rally API query failed: ${response.status} ${response.statusText}`);
870
- }
871
- const result = await response.json();
872
- if (result.QueryResult.Errors && result.QueryResult.Errors.length > 0) {
873
- const errorDetail = result.QueryResult.Errors.join(", ");
874
- const queryDetail = config.query ? ` (Query: ${config.query})` : "";
875
- if (process.env.DEBUG?.includes("rally")) {
876
- console.error("[Rally WSAPI] Query failed:", { query: config.query, errors: result.QueryResult.Errors });
877
- }
878
- throw new Error(`Rally API query failed: ${errorDetail}${queryDetail}`);
879
- }
880
- return result;
881
- }
882
- /**
883
- * Create a Rally object
884
- */
885
- async create(config) {
886
- const url = `${this.server}/slm/webservice/v2.0/${config.type}/create`;
887
- const body = {
888
- [config.type]: config.data
889
- };
890
- const params = new URLSearchParams();
891
- if (config.fetch && config.fetch.length > 0) {
892
- params.set("fetch", config.fetch.join(","));
893
- }
894
- const finalUrl = params.toString() ? `${url}?${params.toString()}` : url;
895
- const response = await fetch(finalUrl, {
896
- method: "POST",
897
- headers: {
898
- "ZSESSIONID": this.apiKey,
899
- "Content-Type": "application/json",
900
- ...this.customHeaders
901
- },
902
- body: JSON.stringify(body)
903
- });
904
- if (!response.ok) {
905
- throw new Error(`Rally API create failed: ${response.status} ${response.statusText}`);
906
- }
907
- const result = await response.json();
908
- if (result.CreateResult.Errors && result.CreateResult.Errors.length > 0) {
909
- throw new Error(`Rally API create failed: ${result.CreateResult.Errors.join(", ")}`);
910
- }
911
- return result;
912
- }
913
- /**
914
- * Update a Rally object
915
- */
916
- async update(config) {
917
- const objectId = config.ref.split("/").pop();
918
- const url = `${this.server}/slm/webservice/v2.0/${config.type}/${objectId}`;
919
- const body = {
920
- [config.type]: config.data
921
- };
922
- const params = new URLSearchParams();
923
- if (config.fetch && config.fetch.length > 0) {
924
- params.set("fetch", config.fetch.join(","));
925
- }
926
- const finalUrl = params.toString() ? `${url}?${params.toString()}` : url;
927
- const response = await fetch(finalUrl, {
928
- method: "POST",
929
- headers: {
930
- "ZSESSIONID": this.apiKey,
931
- "Content-Type": "application/json",
932
- ...this.customHeaders
933
- },
934
- body: JSON.stringify(body)
935
- });
936
- if (!response.ok) {
937
- throw new Error(`Rally API update failed: ${response.status} ${response.statusText}`);
938
- }
939
- const result = await response.json();
940
- if (result.OperationResult.Errors && result.OperationResult.Errors.length > 0) {
941
- throw new Error(`Rally API update failed: ${result.OperationResult.Errors.join(", ")}`);
942
- }
943
- return result;
944
- }
945
- };
946
-
947
- // src/lib/tracker/rally.ts
948
- var STATE_MAP2 = {
949
- // User Stories (ScheduleState)
950
- "New": "open",
951
- "Idea": "open",
952
- "Defined": "open",
953
- "In-Progress": "in_progress",
954
- "Completed": "closed",
955
- "Accepted": "closed",
956
- // Defects (State)
957
- "Submitted": "open",
958
- "Open": "in_progress",
959
- // "Open" defects are actively being worked
960
- "Fixed": "closed",
961
- "Closed": "closed",
962
- // Features / PortfolioItems (State)
963
- "Discovering": "open",
964
- "Developing": "in_progress",
965
- "Done": "closed"
966
- };
967
- var QUERYABLE_TYPES = [
968
- { type: "hierarchicalrequirement", stateField: "ScheduleState", closedStates: ["Completed", "Accepted"] },
969
- { type: "defect", stateField: "State", closedStates: ["Closed"] },
970
- { type: "task", stateField: "State", closedStates: ["Completed"] },
971
- { type: "portfolioitem/feature", stateField: "State", closedStates: ["Done"] }
972
- ];
973
- var FETCH_FIELDS = [
974
- "ObjectID",
975
- "FormattedID",
976
- "Name",
977
- "Description",
978
- "ScheduleState",
979
- "State",
980
- "Tags",
981
- "Owner",
982
- "Priority",
983
- "DueDate",
984
- "CreationDate",
985
- "LastUpdateDate",
986
- "Parent",
987
- "PortfolioItem",
988
- "_type"
989
- ];
990
- var PRIORITY_MAP = {
991
- "Resolve Immediately": 0,
992
- High: 1,
993
- Normal: 2,
994
- Low: 3
995
- };
996
- var REVERSE_PRIORITY_MAP = {
997
- 0: "Resolve Immediately",
998
- 1: "High",
999
- 2: "Normal",
1000
- 3: "Low",
1001
- 4: "Low"
1002
- };
1003
- var RallyTracker = class {
1004
- name = "rally";
1005
- restApi;
1006
- workspace;
1007
- project;
1008
- constructor(config) {
1009
- if (!config.apiKey) {
1010
- throw new TrackerAuthError("rally", "API key is required");
1011
- }
1012
- this.restApi = new RallyRestApi({
1013
- apiKey: config.apiKey,
1014
- server: config.server || "https://rally1.rallydev.com",
1015
- requestOptions: {
1016
- headers: {
1017
- "X-RallyIntegrationType": "Panopticon",
1018
- "X-RallyIntegrationName": "Panopticon CLI",
1019
- "X-RallyIntegrationVendor": "Mind Your Now",
1020
- "X-RallyIntegrationVersion": "0.2.0"
1021
- }
1022
- }
1023
- });
1024
- this.workspace = config.workspace;
1025
- this.project = config.project;
1026
- }
1027
- /**
1028
- * List issues by querying each artifact type separately and merging results.
1029
- *
1030
- * Rally WSAPI cannot apply ScheduleState filters across the generic Artifact
1031
- * endpoint because not all subtypes have that field. We query each type with
1032
- * its own state field, then merge and sort. (PAN-168)
1033
- */
1034
- async listIssues(filters) {
1035
- if (process.env.DEBUG?.includes("rally")) {
1036
- console.debug("[Rally] Query filters:", JSON.stringify(filters));
1037
- }
1038
- const limit = filters?.limit ?? 50;
1039
- let projectObjectId;
1040
- if (this.project) {
1041
- const match = this.project.match(/\/project\/(\d+)/);
1042
- if (match) projectObjectId = match[1];
1043
- }
1044
- const queries = QUERYABLE_TYPES.map(async (artifactType) => {
1045
- const queryString = this.buildQueryStringForType(filters, artifactType, projectObjectId);
1046
- if (process.env.DEBUG?.includes("rally")) {
1047
- console.debug(`[Rally] ${artifactType.type} query:`, queryString);
1048
- }
1049
- const query = {
1050
- type: artifactType.type,
1051
- fetch: FETCH_FIELDS,
1052
- limit,
1053
- query: queryString
1054
- };
1055
- if (this.workspace) {
1056
- query.workspace = this.workspace;
1057
- }
1058
- if (this.project) {
1059
- query.project = this.project;
1060
- query.projectScopeDown = true;
1061
- }
1062
- try {
1063
- const result = await this.queryRally(query);
1064
- return result.Results.map((artifact) => this.normalizeIssue(artifact));
1065
- } catch (error) {
1066
- if (error.message?.includes("Unauthorized") || error.message?.includes("401")) {
1067
- throw new TrackerAuthError("rally", "Invalid API key or insufficient permissions");
1068
- }
1069
- if (process.env.DEBUG?.includes("rally")) {
1070
- console.debug(`[Rally] Failed to query ${artifactType.type}:`, error.message);
1071
- }
1072
- return [];
1073
- }
1074
- });
1075
- const results = await Promise.all(queries);
1076
- const allIssues = results.flat();
1077
- allIssues.sort(
1078
- (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
1079
- );
1080
- return allIssues.slice(0, limit);
1081
- }
1082
- async getIssue(id) {
1083
- try {
1084
- const query = {
1085
- type: "artifact",
1086
- fetch: [
1087
- "FormattedID",
1088
- "Name",
1089
- "Description",
1090
- "ScheduleState",
1091
- "State",
1092
- "Tags",
1093
- "Owner",
1094
- "Priority",
1095
- "DueDate",
1096
- "CreationDate",
1097
- "LastUpdateDate",
1098
- "Parent",
1099
- "_type"
1100
- ],
1101
- query: `(FormattedID = "${id}")`
1102
- };
1103
- if (this.workspace) {
1104
- query.workspace = this.workspace;
1105
- }
1106
- const result = await this.queryRally(query);
1107
- if (!result.Results || result.Results.length === 0) {
1108
- throw new IssueNotFoundError(id, "rally");
1109
- }
1110
- return this.normalizeIssue(result.Results[0]);
1111
- } catch (error) {
1112
- if (error instanceof IssueNotFoundError) throw error;
1113
- throw new IssueNotFoundError(id, "rally");
1114
- }
1115
- }
1116
- async updateIssue(id, update) {
1117
- const issue = await this.getIssue(id);
1118
- const query = {
1119
- type: "artifact",
1120
- fetch: ["ObjectID", "_ref", "_type"],
1121
- query: `(FormattedID = "${id}")`
1122
- };
1123
- if (this.workspace) {
1124
- query.workspace = this.workspace;
1125
- }
1126
- const result = await this.queryRally(query);
1127
- if (!result.Results || result.Results.length === 0) {
1128
- throw new IssueNotFoundError(id, "rally");
1129
- }
1130
- const artifact = result.Results[0];
1131
- const updatePayload = {};
1132
- if (update.title !== void 0) {
1133
- updatePayload.Name = update.title;
1134
- }
1135
- if (update.description !== void 0) {
1136
- updatePayload.Description = update.description;
1137
- }
1138
- if (update.state !== void 0) {
1139
- const artifactType = (artifact._type || "").toLowerCase();
1140
- const kind = artifactType.startsWith("portfolioitem") ? "feature" : artifactType === "defect" ? "defect" : "story";
1141
- const rallyState = this.reverseMapState(update.state, kind);
1142
- if (kind === "story") {
1143
- updatePayload.ScheduleState = rallyState;
1144
- } else {
1145
- updatePayload.State = rallyState;
1146
- }
1147
- }
1148
- if (update.priority !== void 0) {
1149
- updatePayload.Priority = REVERSE_PRIORITY_MAP[update.priority] || "Normal";
1150
- }
1151
- if (update.dueDate !== void 0) {
1152
- updatePayload.DueDate = update.dueDate;
1153
- }
1154
- if (Object.keys(updatePayload).length > 0) {
1155
- await this.updateRally(artifact._type.toLowerCase(), artifact._ref, updatePayload);
1156
- }
1157
- return this.getIssue(id);
1158
- }
1159
- async createIssue(newIssue) {
1160
- if (!this.project && !newIssue.team) {
1161
- throw new Error("Project is required to create an issue. Set it in config or provide team field.");
1162
- }
1163
- const project = newIssue.team || this.project;
1164
- const createPayload = {
1165
- Name: newIssue.title,
1166
- Description: newIssue.description || "",
1167
- Project: project
1168
- };
1169
- if (newIssue.priority !== void 0) {
1170
- createPayload.Priority = REVERSE_PRIORITY_MAP[newIssue.priority] || "Normal";
1171
- }
1172
- if (newIssue.dueDate) {
1173
- createPayload.DueDate = newIssue.dueDate;
1174
- }
1175
- if (this.workspace) {
1176
- createPayload.Workspace = this.workspace;
1177
- }
1178
- const result = await this.createRally("hierarchicalrequirement", createPayload);
1179
- return this.getIssue(result.Object.FormattedID);
1180
- }
1181
- async getComments(issueId) {
1182
- const issue = await this.getIssue(issueId);
1183
- const query = {
1184
- type: "artifact",
1185
- fetch: ["ObjectID", "_ref", "Discussion"],
1186
- query: `(FormattedID = "${issueId}")`
1187
- };
1188
- if (this.workspace) {
1189
- query.workspace = this.workspace;
1190
- }
1191
- const result = await this.queryRally(query);
1192
- if (!result.Results || result.Results.length === 0) {
1193
- return [];
1194
- }
1195
- const artifact = result.Results[0];
1196
- if (!artifact.Discussion) {
1197
- return [];
1198
- }
1199
- const postsQuery = {
1200
- type: "conversationpost",
1201
- fetch: ["ObjectID", "Text", "User", "CreationDate", "PostNumber"],
1202
- query: `(Discussion = "${artifact.Discussion._ref}")`,
1203
- order: "PostNumber"
1204
- };
1205
- const postsResult = await this.queryRally(postsQuery);
1206
- return (postsResult.Results || []).map((post) => ({
1207
- id: post.ObjectID,
1208
- issueId,
1209
- body: post.Text || "",
1210
- author: post.User?._refObjectName || "Unknown",
1211
- createdAt: post.CreationDate,
1212
- updatedAt: post.CreationDate
1213
- // Rally doesn't track comment updates separately
1214
- }));
1215
- }
1216
- async addComment(issueId, body) {
1217
- const query = {
1218
- type: "artifact",
1219
- fetch: ["ObjectID", "_ref", "Discussion"],
1220
- query: `(FormattedID = "${issueId}")`
1221
- };
1222
- if (this.workspace) {
1223
- query.workspace = this.workspace;
1224
- }
1225
- const result = await this.queryRally(query);
1226
- if (!result.Results || result.Results.length === 0) {
1227
- throw new IssueNotFoundError(issueId, "rally");
1228
- }
1229
- const artifact = result.Results[0];
1230
- let discussionRef = artifact.Discussion?._ref;
1231
- if (!discussionRef) {
1232
- const discussionResult = await this.createRally("conversationpost", {
1233
- Artifact: artifact._ref,
1234
- Text: body
1235
- });
1236
- return {
1237
- id: discussionResult.Object.ObjectID,
1238
- issueId,
1239
- body,
1240
- author: "Panopticon",
1241
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1242
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1243
- };
1244
- }
1245
- const postResult = await this.createRally("conversationpost", {
1246
- Artifact: artifact._ref,
1247
- Text: body
1248
- });
1249
- return {
1250
- id: postResult.Object.ObjectID,
1251
- issueId,
1252
- body,
1253
- author: "Panopticon",
1254
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1255
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1256
- };
1257
- }
1258
- async transitionIssue(id, state) {
1259
- await this.updateIssue(id, { state });
1260
- }
1261
- async linkPR(issueId, prUrl) {
1262
- await this.addComment(issueId, `Linked Pull Request: ${prUrl}`);
1263
- }
1264
- // Private helper methods
1265
- /**
1266
- * Build a Rally WSAPI query string for a specific artifact type.
1267
- *
1268
- * Each artifact type has its own state field:
1269
- * - HierarchicalRequirement: ScheduleState (Defined, In-Progress, Completed, Accepted)
1270
- * - Defect: State (Submitted, Open, Fixed, Closed)
1271
- * - Task: State (Defined, In-Progress, Completed)
1272
- *
1273
- * Rally WSAPI v2.0 requires binary-nested AND/OR with outer parentheses.
1274
- * (PAN-166, PAN-168)
1275
- */
1276
- buildQueryStringForType(filters, artifactType, projectObjectId) {
1277
- const conditions = [];
1278
- if (projectObjectId) {
1279
- conditions.push(`(Project.ObjectID = "${projectObjectId}")`);
1280
- }
1281
- if (filters?.state && !filters.includeClosed) {
1282
- const kind = artifactType.type.startsWith("portfolioitem") ? "feature" : artifactType.type === "defect" ? "defect" : "story";
1283
- const rallyState = this.reverseMapState(filters.state, kind);
1284
- conditions.push(`(${artifactType.stateField} = "${rallyState}")`);
1285
- }
1286
- if (!filters?.includeClosed) {
1287
- const closedConditions = artifactType.closedStates.map(
1288
- (state) => `(${artifactType.stateField} != "${state}")`
1289
- );
1290
- const closedExpr = closedConditions.reduce(
1291
- (acc, cond) => acc ? `(${acc} AND ${cond})` : cond,
1292
- ""
1293
- );
1294
- conditions.push(closedExpr);
1295
- }
1296
- if (filters?.assignee) {
1297
- conditions.push(`(Owner.Name contains "${filters.assignee}")`);
1298
- }
1299
- if (filters?.labels && filters.labels.length > 0) {
1300
- const labelConditions = filters.labels.map(
1301
- (label) => `(Tags.Name contains "${label}")`
1302
- );
1303
- const labelExpr = labelConditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, "");
1304
- conditions.push(labelExpr);
1305
- }
1306
- if (filters?.query) {
1307
- conditions.push(`((Name contains "${filters.query}") OR (Description contains "${filters.query}"))`);
1308
- }
1309
- return conditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, "");
1310
- }
1311
- normalizeIssue(rallyArtifact) {
1312
- const rawStateValue = rallyArtifact.ScheduleState || rallyArtifact.State || "Defined";
1313
- const stateValue = typeof rawStateValue === "object" && rawStateValue !== null ? rawStateValue.Name || rawStateValue._refObjectName || "Defined" : rawStateValue;
1314
- const state = this.mapState(stateValue);
1315
- const labels = [];
1316
- if (rallyArtifact.Tags && rallyArtifact.Tags._tagsNameArray) {
1317
- for (const tag of rallyArtifact.Tags._tagsNameArray) {
1318
- if (typeof tag === "string") {
1319
- labels.push(tag);
1320
- } else if (tag?.Name) {
1321
- labels.push(tag.Name);
1322
- }
1323
- }
1324
- }
1325
- const priority = rallyArtifact.Priority ? PRIORITY_MAP[rallyArtifact.Priority] ?? 2 : void 0;
1326
- const objectId = rallyArtifact.ObjectID || rallyArtifact.FormattedID;
1327
- const artifactType = rallyArtifact._type || "artifact";
1328
- const baseUrl = this.restApi.server.replace("/slm/webservice/", "");
1329
- const url = `${baseUrl}/#/detail/${artifactType.toLowerCase()}/${objectId}`;
1330
- let parentRef;
1331
- if (rallyArtifact.PortfolioItem) {
1332
- if (rallyArtifact.PortfolioItem.FormattedID) {
1333
- parentRef = rallyArtifact.PortfolioItem.FormattedID;
1334
- } else if (rallyArtifact.PortfolioItem._refObjectName) {
1335
- parentRef = rallyArtifact.PortfolioItem._refObjectName;
1336
- }
1337
- } else if (rallyArtifact.Parent) {
1338
- if (rallyArtifact.Parent.FormattedID) {
1339
- parentRef = rallyArtifact.Parent.FormattedID;
1340
- } else if (rallyArtifact.Parent._refObjectName) {
1341
- parentRef = rallyArtifact.Parent._refObjectName;
1342
- }
1343
- }
1344
- return {
1345
- id: String(objectId),
1346
- ref: rallyArtifact.FormattedID,
1347
- title: rallyArtifact.Name || "",
1348
- description: rallyArtifact.Description || "",
1349
- state,
1350
- labels,
1351
- assignee: rallyArtifact.Owner?._refObjectName,
1352
- url,
1353
- tracker: "rally",
1354
- priority,
1355
- dueDate: rallyArtifact.DueDate,
1356
- createdAt: rallyArtifact.CreationDate,
1357
- updatedAt: rallyArtifact.LastUpdateDate,
1358
- parentRef,
1359
- artifactType,
1360
- rawState: stateValue
1361
- };
1362
- }
1363
- mapState(rallyState) {
1364
- return STATE_MAP2[rallyState] ?? "open";
1365
- }
1366
- reverseMapState(state, kind = "story") {
1367
- if (kind === "feature") {
1368
- switch (state) {
1369
- case "open":
1370
- return "Discovering";
1371
- case "in_progress":
1372
- return "Developing";
1373
- case "closed":
1374
- return "Done";
1375
- default:
1376
- return "Discovering";
1377
- }
1378
- }
1379
- if (kind === "defect") {
1380
- switch (state) {
1381
- case "open":
1382
- return "Submitted";
1383
- case "in_progress":
1384
- return "Open";
1385
- case "closed":
1386
- return "Closed";
1387
- default:
1388
- return "Submitted";
1389
- }
1390
- }
1391
- switch (state) {
1392
- case "open":
1393
- return "Defined";
1394
- case "in_progress":
1395
- return "In-Progress";
1396
- case "closed":
1397
- return "Completed";
1398
- default:
1399
- return "Defined";
1400
- }
1401
- }
1402
- // Rally API wrapper methods
1403
- async queryRally(queryConfig) {
1404
- const result = await this.restApi.query(queryConfig);
1405
- return {
1406
- Results: result.QueryResult.Results,
1407
- TotalResultCount: result.QueryResult.TotalResultCount
1408
- };
1409
- }
1410
- async createRally(type, data) {
1411
- const result = await this.restApi.create({
1412
- type,
1413
- data,
1414
- fetch: ["FormattedID", "ObjectID", "_ref"]
1415
- });
1416
- return {
1417
- Object: result.CreateResult.Object
1418
- };
1419
- }
1420
- async updateRally(type, ref, data) {
1421
- const result = await this.restApi.update({
1422
- type,
1423
- ref,
1424
- data,
1425
- fetch: ["FormattedID", "ObjectID"]
1426
- });
1427
- return {
1428
- Object: result.OperationResult.Object
1429
- };
1430
- }
1431
- };
1432
-
1433
- // src/lib/tracker/factory.ts
1434
- init_config_yaml();
1435
- function getTrackerKeyFromConfig(trackerType) {
1436
- try {
1437
- const yamlConfig = loadConfig();
1438
- return yamlConfig.trackerKeys[trackerType];
1439
- } catch {
1440
- return void 0;
1441
- }
1442
- }
1443
- function createTracker(config) {
1444
- switch (config.type) {
1445
- case "linear": {
1446
- const configKey = getTrackerKeyFromConfig("linear");
1447
- const envKey = config.apiKeyEnv ? process.env[config.apiKeyEnv] : process.env.LINEAR_API_KEY;
1448
- const apiKey = configKey || envKey;
1449
- if (!apiKey) {
1450
- throw new TrackerAuthError(
1451
- "linear",
1452
- `API key not found. Configure in Settings or set ${config.apiKeyEnv ?? "LINEAR_API_KEY"} environment variable.`
1453
- );
1454
- }
1455
- return new LinearTracker(apiKey, { team: config.team });
1456
- }
1457
- case "github": {
1458
- const configKey = getTrackerKeyFromConfig("github");
1459
- const envToken = config.tokenEnv ? process.env[config.tokenEnv] : process.env.GITHUB_TOKEN;
1460
- const token = configKey || envToken;
1461
- if (!token) {
1462
- throw new TrackerAuthError(
1463
- "github",
1464
- `Token not found. Configure in Settings or set ${config.tokenEnv ?? "GITHUB_TOKEN"} environment variable.`
1465
- );
1466
- }
1467
- if (!config.owner || !config.repo) {
1468
- throw new Error(
1469
- "GitHub tracker requires owner and repo configuration"
1470
- );
1471
- }
1472
- return new GitHubTracker(token, config.owner, config.repo);
1473
- }
1474
- case "gitlab": {
1475
- const configKey = getTrackerKeyFromConfig("gitlab");
1476
- const envToken = config.tokenEnv ? process.env[config.tokenEnv] : process.env.GITLAB_TOKEN;
1477
- const token = configKey || envToken;
1478
- if (!token) {
1479
- throw new TrackerAuthError(
1480
- "gitlab",
1481
- `Token not found. Configure in Settings or set ${config.tokenEnv ?? "GITLAB_TOKEN"} environment variable.`
1482
- );
1483
- }
1484
- if (!config.projectId) {
1485
- throw new Error("GitLab tracker requires projectId configuration");
1486
- }
1487
- return new GitLabTracker(token, config.projectId);
1488
- }
1489
- case "rally": {
1490
- const configKey = getTrackerKeyFromConfig("rally");
1491
- const envKey = config.apiKeyEnv ? process.env[config.apiKeyEnv] : process.env.RALLY_API_KEY;
1492
- const apiKey = configKey || envKey;
1493
- if (!apiKey) {
1494
- throw new TrackerAuthError(
1495
- "rally",
1496
- `API key not found. Configure in Settings or set ${config.apiKeyEnv ?? "RALLY_API_KEY"} environment variable.`
1497
- );
1498
- }
1499
- return new RallyTracker({
1500
- apiKey,
1501
- server: config.server,
1502
- workspace: config.workspace,
1503
- project: config.project
1504
- });
1505
- }
1506
- default:
1507
- throw new Error(`Unknown tracker type: ${config.type}`);
1508
- }
1509
- }
1510
- function createTrackerFromConfig(trackersConfig, trackerType) {
1511
- const config = trackersConfig[trackerType];
1512
- if (!config) {
1513
- throw new Error(
1514
- `No configuration found for tracker: ${trackerType}. Add [trackers.${trackerType}] to config.`
1515
- );
1516
- }
1517
- return createTracker({ ...config, type: trackerType });
1518
- }
1519
- function getPrimaryTracker(trackersConfig) {
1520
- return createTrackerFromConfig(trackersConfig, trackersConfig.primary);
1521
- }
1522
- function getSecondaryTracker(trackersConfig) {
1523
- if (!trackersConfig.secondary) {
1524
- return null;
1525
- }
1526
- return createTrackerFromConfig(trackersConfig, trackersConfig.secondary);
1527
- }
1528
- function getAllTrackers(trackersConfig) {
1529
- const trackers = [getPrimaryTracker(trackersConfig)];
1530
- const secondary = getSecondaryTracker(trackersConfig);
1531
- if (secondary) {
1532
- trackers.push(secondary);
1533
- }
1534
- return trackers;
1535
- }
1536
-
1537
- // src/lib/tracker/linking.ts
1538
- init_esm_shims();
1539
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
1540
- import { join as join4 } from "path";
1541
- import { homedir as homedir2 } from "os";
1542
- function parseIssueRef(ref) {
1543
- if (ref.startsWith("github#")) {
1544
- return { tracker: "github", ref: `#${ref.slice(7)}` };
1545
- }
1546
- if (ref.startsWith("gitlab#")) {
1547
- return { tracker: "gitlab", ref: `#${ref.slice(7)}` };
1548
- }
1549
- if (ref.startsWith("linear:")) {
1550
- return { tracker: "linear", ref: ref.slice(7) };
1551
- }
1552
- if (/^#\d+$/.test(ref)) {
1553
- return { tracker: "github", ref };
1554
- }
1555
- if (/^[A-Z]+-\d+$/i.test(ref)) {
1556
- return { tracker: "linear", ref: ref.toUpperCase() };
1557
- }
1558
- return null;
1559
- }
1560
- function formatIssueRef(ref, tracker) {
1561
- if (tracker === "github") {
1562
- return ref.startsWith("#") ? `github${ref}` : `github#${ref}`;
1563
- }
1564
- if (tracker === "gitlab") {
1565
- return ref.startsWith("#") ? `gitlab${ref}` : `gitlab#${ref}`;
1566
- }
1567
- return ref;
1568
- }
1569
- var LinkManager = class {
1570
- storePath;
1571
- store;
1572
- constructor(storePath) {
1573
- this.storePath = storePath ?? join4(homedir2(), ".panopticon", "links.json");
1574
- this.store = this.load();
1575
- }
1576
- load() {
1577
- if (existsSync4(this.storePath)) {
1578
- try {
1579
- const data = JSON.parse(readFileSync2(this.storePath, "utf-8"));
1580
- if (data.version === 1) {
1581
- return data;
1582
- }
1583
- } catch {
1584
- }
1585
- }
1586
- return { version: 1, links: [] };
1587
- }
1588
- save() {
1589
- const dir = join4(this.storePath, "..");
1590
- if (!existsSync4(dir)) {
1591
- mkdirSync3(dir, { recursive: true });
1592
- }
1593
- writeFileSync(this.storePath, JSON.stringify(this.store, null, 2));
1594
- }
1595
- /**
1596
- * Add a link between two issues
1597
- */
1598
- addLink(source, target, direction = "related") {
1599
- const existing = this.store.links.find(
1600
- (l) => l.sourceIssueRef === source.ref && l.sourceTracker === source.tracker && l.targetIssueRef === target.ref && l.targetTracker === target.tracker
1601
- );
1602
- if (existing) {
1603
- if (existing.direction !== direction) {
1604
- existing.direction = direction;
1605
- this.save();
1606
- }
1607
- return existing;
1608
- }
1609
- const link = {
1610
- sourceIssueRef: source.ref,
1611
- sourceTracker: source.tracker,
1612
- targetIssueRef: target.ref,
1613
- targetTracker: target.tracker,
1614
- direction,
1615
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
1616
- };
1617
- this.store.links.push(link);
1618
- this.save();
1619
- return link;
1620
- }
1621
- /**
1622
- * Remove a link between two issues
1623
- */
1624
- removeLink(source, target) {
1625
- const index = this.store.links.findIndex(
1626
- (l) => l.sourceIssueRef === source.ref && l.sourceTracker === source.tracker && l.targetIssueRef === target.ref && l.targetTracker === target.tracker
1627
- );
1628
- if (index >= 0) {
1629
- this.store.links.splice(index, 1);
1630
- this.save();
1631
- return true;
1632
- }
1633
- return false;
1634
- }
1635
- /**
1636
- * Get all issues linked to a given issue
1637
- */
1638
- getLinkedIssues(ref, tracker) {
1639
- return this.store.links.filter(
1640
- (l) => l.sourceIssueRef === ref && l.sourceTracker === tracker || l.targetIssueRef === ref && l.targetTracker === tracker
1641
- );
1642
- }
1643
- /**
1644
- * Get all links (for debugging/admin)
1645
- */
1646
- getAllLinks() {
1647
- return [...this.store.links];
1648
- }
1649
- /**
1650
- * Find linked issue in another tracker
1651
- */
1652
- findLinkedIssue(ref, sourceTracker, targetTracker) {
1653
- const asSource = this.store.links.find(
1654
- (l) => l.sourceIssueRef === ref && l.sourceTracker === sourceTracker && l.targetTracker === targetTracker
1655
- );
1656
- if (asSource) return asSource.targetIssueRef;
1657
- const asTarget = this.store.links.find(
1658
- (l) => l.targetIssueRef === ref && l.targetTracker === sourceTracker && l.sourceTracker === targetTracker
1659
- );
1660
- if (asTarget) return asTarget.sourceIssueRef;
1661
- return null;
1662
- }
1663
- /**
1664
- * Clear all links (for testing)
1665
- */
1666
- clear() {
1667
- this.store.links = [];
1668
- this.save();
1669
- }
1670
- };
1671
- var _linkManager = null;
1672
- function getLinkManager() {
1673
- if (!_linkManager) {
1674
- _linkManager = new LinkManager();
1675
- }
1676
- return _linkManager;
1677
- }
1678
-
1679
- // src/lib/tracker/index.ts
1680
- init_esm_shims();
1681
-
1682
- export {
1683
- detectShell,
1684
- getShellRcFile,
1685
- hasAlias,
1686
- addAlias,
1687
- getAliasInstructions,
1688
- createBackupTimestamp,
1689
- createBackup,
1690
- listBackups,
1691
- restoreBackup,
1692
- cleanOldBackups,
1693
- isPanopticonSymlink,
1694
- planSync,
1695
- executeSync,
1696
- planHooksSync,
1697
- syncHooks,
1698
- NotImplementedError,
1699
- IssueNotFoundError,
1700
- TrackerAuthError,
1701
- LinearTracker,
1702
- GitHubTracker,
1703
- GitLabTracker,
1704
- createTracker,
1705
- createTrackerFromConfig,
1706
- getPrimaryTracker,
1707
- getSecondaryTracker,
1708
- getAllTrackers,
1709
- parseIssueRef,
1710
- formatIssueRef,
1711
- LinkManager,
1712
- getLinkManager
1713
- };
1714
- //# sourceMappingURL=chunk-SUMIHS2B.js.map