panopticon-cli 0.4.15 → 0.4.16

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.
@@ -85167,6 +85167,21 @@ var IssueDataService = class {
85167
85167
  }
85168
85168
  }
85169
85169
  }
85170
+ /**
85171
+ * Look up which tracker an issue belongs to by its identifier.
85172
+ * Returns 'github' | 'linear' | 'rally' | null.
85173
+ */
85174
+ getIssueSource(identifier) {
85175
+ const id = identifier.toLowerCase();
85176
+ for (const [trackerName, state] of Object.entries(this.trackers)) {
85177
+ for (const issue of state.lastFetchedIssues) {
85178
+ if ((issue.identifier || "").toLowerCase() === id) {
85179
+ return trackerName;
85180
+ }
85181
+ }
85182
+ }
85183
+ return null;
85184
+ }
85170
85185
  /**
85171
85186
  * Get all issues from cache. Applies shadow state and filtering.
85172
85187
  * This is the hot path — must be fast.
@@ -90635,6 +90650,23 @@ function getLinearApiKey2() {
90635
90650
  }
90636
90651
  return process.env.LINEAR_API_KEY || null;
90637
90652
  }
90653
+ function getRallyConfig2() {
90654
+ const envFile = join43(homedir20(), ".panopticon.env");
90655
+ if (!existsSync44(envFile))
90656
+ return null;
90657
+ const content = readFileSync37(envFile, "utf-8");
90658
+ const apiKeyMatch = content.match(/RALLY_API_KEY=(.+)/);
90659
+ if (!apiKeyMatch)
90660
+ return null;
90661
+ const apiKey = apiKeyMatch[1].trim();
90662
+ const serverMatch = content.match(/RALLY_SERVER=(.+)/);
90663
+ const server2 = serverMatch?.[1].trim();
90664
+ const workspaceMatch = content.match(/RALLY_WORKSPACE=(.+)/);
90665
+ const workspace = workspaceMatch?.[1].trim();
90666
+ const projectMatch = content.match(/RALLY_PROJECT=(.+)/);
90667
+ const project = projectMatch?.[1].trim();
90668
+ return { apiKey, server: server2, workspace, project };
90669
+ }
90638
90670
  function getGitHubConfig2() {
90639
90671
  const envFile = join43(homedir20(), ".panopticon.env");
90640
90672
  if (!existsSync44(envFile))
@@ -95346,9 +95378,10 @@ app.post("/api/issues/:issueId/close", async (req, res) => {
95346
95378
  const workspacePath = join43(projectPath, "workspaces", `feature-${issueLower}`);
95347
95379
  const branchName = `feature/${issueLower}`;
95348
95380
  try {
95349
- const isGitHubIssue2 = issueId.toUpperCase().startsWith("PAN-");
95381
+ const githubCheck = isGitHubIssue(issueId);
95382
+ const issueSource = issueDataService.getIssueSource(issueId);
95350
95383
  const apiKey = getLinearApiKey2();
95351
- if (isGitHubIssue2) {
95384
+ if (githubCheck.isGitHub) {
95352
95385
  const ghConfig = getGitHubConfig2();
95353
95386
  const number = parseInt(issueId.split("-")[1], 10);
95354
95387
  const repoConfig = ghConfig?.repos.find((r) => r.prefix === "PAN") || ghConfig?.repos[0];
@@ -95369,6 +95402,23 @@ app.post("/api/issues/:issueId/close", async (req, res) => {
95369
95402
  });
95370
95403
  }
95371
95404
  }
95405
+ } else if (issueSource === "rally") {
95406
+ const rallyConfig = getRallyConfig2();
95407
+ if (rallyConfig) {
95408
+ try {
95409
+ const { RallyTracker: RallyTracker2 } = await Promise.resolve().then(() => (init_rally(), rally_exports));
95410
+ const tracker = new RallyTracker2({
95411
+ apiKey: rallyConfig.apiKey,
95412
+ server: rallyConfig.server,
95413
+ workspace: rallyConfig.workspace,
95414
+ project: rallyConfig.project
95415
+ });
95416
+ await tracker.transitionIssue(issueId, "closed");
95417
+ console.log(`Closed Rally issue ${issueId}`);
95418
+ } catch (rallyErr) {
95419
+ console.error(`Rally close failed for ${issueId}:`, rallyErr.message);
95420
+ }
95421
+ }
95372
95422
  } else if (apiKey) {
95373
95423
  const getIssueQuery = `
95374
95424
  query GetIssue($id: String!) {
@@ -95421,7 +95471,7 @@ app.post("/api/issues/:issueId/close", async (req, res) => {
95421
95471
  console.error("Error removing worktree:", wtError.message);
95422
95472
  }
95423
95473
  }
95424
- if (isGitHubIssue2) {
95474
+ if (githubCheck.isGitHub) {
95425
95475
  try {
95426
95476
  await execAsync15("pan sync", { encoding: "utf-8", timeout: 3e4 });
95427
95477
  console.log("pan sync completed");
@@ -95432,9 +95482,12 @@ app.post("/api/issues/:issueId/close", async (req, res) => {
95432
95482
  success: true,
95433
95483
  message: `Closed ${issueId}${reason ? ": " + reason : ""}`
95434
95484
  });
95435
- if (isGitHubIssue2) {
95485
+ if (githubCheck.isGitHub) {
95436
95486
  issueDataService.invalidateTracker("github").catch(() => {
95437
95487
  });
95488
+ } else if (issueSource === "rally") {
95489
+ issueDataService.invalidateTracker("rally").catch(() => {
95490
+ });
95438
95491
  } else {
95439
95492
  issueDataService.invalidateTracker("linear").catch(() => {
95440
95493
  });
@@ -97355,6 +97408,8 @@ app.post("/api/issues/:id/reset", async (req, res) => {
97355
97408
  });
97356
97409
  issueDataService.invalidateTracker("linear").catch(() => {
97357
97410
  });
97411
+ issueDataService.invalidateTracker("rally").catch(() => {
97412
+ });
97358
97413
  } catch (error) {
97359
97414
  console.error("Reset failed:", error);
97360
97415
  res.status(500).json({
@@ -97368,37 +97423,66 @@ app.post("/api/issues/:id/reopen", async (req, res) => {
97368
97423
  const { id } = req.params;
97369
97424
  const { skipPlan = false } = req.body || {};
97370
97425
  try {
97371
- const linearKey = process.env.LINEAR_API_KEY || "";
97372
- if (!linearKey) {
97373
- return res.status(400).json({ error: "LINEAR_API_KEY not configured" });
97374
- }
97375
- const { LinearClient: LinearClient2 } = await Promise.resolve().then(() => __toESM(require_index_cjs_min(), 1));
97376
- const client = new LinearClient2({ apiKey: linearKey });
97377
- const issue = await client.issue(id);
97378
- if (!issue) {
97379
- return res.status(404).json({ error: `Issue ${id} not found` });
97380
- }
97381
- const team = await issue.team;
97382
- if (!team) {
97383
- return res.status(400).json({ error: "Could not determine team for issue" });
97384
- }
97385
- const states = await team.states();
97386
- const backlogState = states.nodes.find((s) => s.type === "backlog");
97387
- if (!backlogState) {
97388
- return res.status(400).json({ error: "Could not find Backlog state for team" });
97389
- }
97390
- await issue.update({ stateId: backlogState.id });
97391
- console.log(`Reopened issue ${id} - moved to Backlog`);
97392
- if (!skipPlan) {
97426
+ const githubCheck = isGitHubIssue(id);
97427
+ const issueSource = issueDataService.getIssueSource(id);
97428
+ if (issueSource === "rally") {
97429
+ const rallyConfig = getRallyConfig2();
97430
+ if (!rallyConfig) {
97431
+ return res.status(400).json({ error: "Rally not configured" });
97432
+ }
97433
+ const { RallyTracker: RallyTracker2 } = await Promise.resolve().then(() => (init_rally(), rally_exports));
97434
+ const tracker = new RallyTracker2({
97435
+ apiKey: rallyConfig.apiKey,
97436
+ server: rallyConfig.server,
97437
+ workspace: rallyConfig.workspace,
97438
+ project: rallyConfig.project
97439
+ });
97440
+ await tracker.transitionIssue(id, "open");
97441
+ console.log(`Reopened Rally issue ${id}`);
97442
+ res.json({
97443
+ success: true,
97444
+ message: `Issue ${id} reopened`,
97445
+ issueId: id,
97446
+ newState: "Backlog"
97447
+ });
97448
+ issueDataService.invalidateTracker("rally").catch(() => {
97449
+ });
97450
+ } else {
97451
+ const linearKey = process.env.LINEAR_API_KEY || "";
97452
+ if (!linearKey) {
97453
+ return res.status(400).json({ error: "LINEAR_API_KEY not configured" });
97454
+ }
97455
+ const { LinearClient: LinearClient2 } = await Promise.resolve().then(() => __toESM(require_index_cjs_min(), 1));
97456
+ const client = new LinearClient2({ apiKey: linearKey });
97457
+ const issue = await client.issue(id);
97458
+ if (!issue) {
97459
+ return res.status(404).json({ error: `Issue ${id} not found` });
97460
+ }
97461
+ const team = await issue.team;
97462
+ if (!team) {
97463
+ return res.status(400).json({ error: "Could not determine team for issue" });
97464
+ }
97465
+ const states = await team.states();
97466
+ const backlogState = states.nodes.find((s) => s.type === "backlog");
97467
+ if (!backlogState) {
97468
+ return res.status(400).json({ error: "Could not find Backlog state for team" });
97469
+ }
97470
+ await issue.update({ stateId: backlogState.id });
97471
+ console.log(`Reopened issue ${id} - moved to Backlog`);
97472
+ res.json({
97473
+ success: true,
97474
+ message: `Issue ${id} reopened and moved to Backlog`,
97475
+ issueId: issue.identifier,
97476
+ newState: "Backlog"
97477
+ });
97478
+ if (githubCheck.isGitHub) {
97479
+ issueDataService.invalidateTracker("github").catch(() => {
97480
+ });
97481
+ } else {
97482
+ issueDataService.invalidateTracker("linear").catch(() => {
97483
+ });
97484
+ }
97393
97485
  }
97394
- res.json({
97395
- success: true,
97396
- message: `Issue ${id} reopened and moved to Backlog`,
97397
- issueId: issue.identifier,
97398
- newState: "Backlog"
97399
- });
97400
- issueDataService.invalidateTracker("linear").catch(() => {
97401
- });
97402
97486
  } catch (error) {
97403
97487
  console.error("Error reopening issue:", error);
97404
97488
  res.status(500).json({ error: "Failed to reopen issue: " + error.message });
@@ -97423,15 +97507,34 @@ app.post("/api/issues/:id/move-status", async (req, res) => {
97423
97507
  };
97424
97508
  const issueState = canonicalToIssueState[targetStatus];
97425
97509
  const shadowResult = await updateShadowState2(id, issueState, "dashboard-drag-drop", targetStatus);
97510
+ const issueSource = issueDataService.getIssueSource(id);
97511
+ const githubCheck = isGitHubIssue(id);
97426
97512
  if (syncToTracker) {
97427
- const linearKey = process.env.LINEAR_API_KEY || "";
97428
- if (!linearKey) {
97429
- return res.status(400).json({ error: "LINEAR_API_KEY not configured for sync" });
97430
- }
97431
- const githubCheck = isGitHubIssue(id);
97432
97513
  if (githubCheck.isGitHub) {
97433
97514
  console.log(`GitHub issue ${id} - skipping tracker sync`);
97515
+ } else if (issueSource === "rally") {
97516
+ const rallyConfig = getRallyConfig2();
97517
+ if (!rallyConfig) {
97518
+ return res.status(400).json({ error: "Rally not configured for sync" });
97519
+ }
97520
+ try {
97521
+ const { RallyTracker: RallyTracker2 } = await Promise.resolve().then(() => (init_rally(), rally_exports));
97522
+ const tracker = new RallyTracker2({
97523
+ apiKey: rallyConfig.apiKey,
97524
+ server: rallyConfig.server,
97525
+ workspace: rallyConfig.workspace,
97526
+ project: rallyConfig.project
97527
+ });
97528
+ await tracker.transitionIssue(id, issueState);
97529
+ console.log(`Synced issue ${id} to Rally state: ${issueState}`);
97530
+ } catch (rallyErr) {
97531
+ console.error(`Rally sync failed for ${id}:`, rallyErr.message);
97532
+ }
97434
97533
  } else {
97534
+ const linearKey = process.env.LINEAR_API_KEY || "";
97535
+ if (!linearKey) {
97536
+ return res.status(400).json({ error: "LINEAR_API_KEY not configured for sync" });
97537
+ }
97435
97538
  const { LinearClient: LinearClient2 } = await Promise.resolve().then(() => __toESM(require_index_cjs_min(), 1));
97436
97539
  const client = new LinearClient2({ apiKey: linearKey });
97437
97540
  const issue = await client.issue(id);
@@ -97468,10 +97571,12 @@ app.post("/api/issues/:id/move-status", async (req, res) => {
97468
97571
  syncToTracker,
97469
97572
  shadowState: shadowResult
97470
97573
  });
97471
- const githubMoveCheck = isGitHubIssue(id);
97472
- if (githubMoveCheck.isGitHub) {
97574
+ if (githubCheck.isGitHub) {
97473
97575
  issueDataService.invalidateTracker("github").catch(() => {
97474
97576
  });
97577
+ } else if (issueSource === "rally") {
97578
+ issueDataService.invalidateTracker("rally").catch(() => {
97579
+ });
97475
97580
  } else {
97476
97581
  issueDataService.invalidateTracker("linear").catch(() => {
97477
97582
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panopticon-cli",
3
- "version": "0.4.15",
3
+ "version": "0.4.16",
4
4
  "description": "Multi-agent orchestration for AI coding assistants (Claude Code, Codex, Cursor, Gemini CLI)",
5
5
  "keywords": [
6
6
  "ai-agents",