panopticon-cli 0.4.17 → 0.4.19

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 (27) hide show
  1. package/dist/{chunk-K72CCKWP.js → chunk-77Q3GY6S.js} +22 -4
  2. package/dist/chunk-77Q3GY6S.js.map +1 -0
  3. package/dist/{chunk-45PZFXJM.js → chunk-QB3W4NX6.js} +9 -9
  4. package/dist/{chunk-K45YD6A3.js → chunk-SOL4WQJD.js} +7 -1
  5. package/dist/chunk-SOL4WQJD.js.map +1 -0
  6. package/dist/cli/index.js +24 -12
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/dashboard/public/assets/{index-Bp7DyPLO.css → index-BIT09aRJ.css} +1 -1
  9. package/dist/dashboard/public/assets/{index-CE4Bu8aP.js → index-CmEW4W6o.js} +136 -136
  10. package/dist/dashboard/public/index.html +2 -2
  11. package/dist/dashboard/server.js +11082 -11019
  12. package/dist/index.d.ts +4 -0
  13. package/dist/index.js +1 -1
  14. package/dist/{projects-ESIB34QQ.js → projects-AMR3XQM4.js} +4 -2
  15. package/dist/{remote-workspace-WZVEDYQA.js → remote-workspace-V57IZJRC.js} +2 -2
  16. package/dist/{specialist-context-JO4RB7UC.js → specialist-context-2G6YOU3M.js} +3 -3
  17. package/dist/{specialist-logs-7P5EUIUT.js → specialist-logs-5OTCVAVH.js} +3 -3
  18. package/dist/{specialists-B4QAT244.js → specialists-H5QEVLNN.js} +3 -3
  19. package/package.json +1 -1
  20. package/dist/chunk-K45YD6A3.js.map +0 -1
  21. package/dist/chunk-K72CCKWP.js.map +0 -1
  22. /package/dist/{chunk-45PZFXJM.js.map → chunk-QB3W4NX6.js.map} +0 -0
  23. /package/dist/{projects-ESIB34QQ.js.map → projects-AMR3XQM4.js.map} +0 -0
  24. /package/dist/{remote-workspace-WZVEDYQA.js.map → remote-workspace-V57IZJRC.js.map} +0 -0
  25. /package/dist/{specialist-context-JO4RB7UC.js.map → specialist-context-2G6YOU3M.js.map} +0 -0
  26. /package/dist/{specialist-logs-7P5EUIUT.js.map → specialist-logs-5OTCVAVH.js.map} +0 -0
  27. /package/dist/{specialists-B4QAT244.js.map → specialists-H5QEVLNN.js.map} +0 -0
@@ -1035,8 +1035,13 @@ var RallyTracker = class {
1035
1035
  console.debug("[Rally] Query filters:", JSON.stringify(filters));
1036
1036
  }
1037
1037
  const limit = filters?.limit ?? 50;
1038
+ let projectObjectId;
1039
+ if (this.project) {
1040
+ const match = this.project.match(/\/project\/(\d+)/);
1041
+ if (match) projectObjectId = match[1];
1042
+ }
1038
1043
  const queries = QUERYABLE_TYPES.map(async (artifactType) => {
1039
- const queryString = this.buildQueryStringForType(filters, artifactType);
1044
+ const queryString = this.buildQueryStringForType(filters, artifactType, projectObjectId);
1040
1045
  if (process.env.DEBUG?.includes("rally")) {
1041
1046
  console.debug(`[Rally] ${artifactType.type} query:`, queryString);
1042
1047
  }
@@ -1267,8 +1272,11 @@ var RallyTracker = class {
1267
1272
  * Rally WSAPI v2.0 requires binary-nested AND/OR with outer parentheses.
1268
1273
  * (PAN-166, PAN-168)
1269
1274
  */
1270
- buildQueryStringForType(filters, artifactType) {
1275
+ buildQueryStringForType(filters, artifactType, projectObjectId) {
1271
1276
  const conditions = [];
1277
+ if (projectObjectId) {
1278
+ conditions.push(`(Project.ObjectID = "${projectObjectId}")`);
1279
+ }
1272
1280
  if (filters?.state && !filters.includeClosed) {
1273
1281
  const kind = artifactType.type.startsWith("portfolioitem") ? "feature" : artifactType.type === "defect" ? "defect" : "story";
1274
1282
  const rallyState = this.reverseMapState(filters.state, kind);
@@ -1317,6 +1325,14 @@ var RallyTracker = class {
1317
1325
  const artifactType = rallyArtifact._type || "artifact";
1318
1326
  const baseUrl = this.restApi.server.replace("/slm/webservice/", "");
1319
1327
  const url = `${baseUrl}/#/detail/${artifactType.toLowerCase()}/${objectId}`;
1328
+ let parentRef;
1329
+ if (rallyArtifact.Parent) {
1330
+ if (rallyArtifact.Parent.FormattedID) {
1331
+ parentRef = rallyArtifact.Parent.FormattedID;
1332
+ } else if (rallyArtifact.Parent._refObjectName) {
1333
+ parentRef = rallyArtifact.Parent._refObjectName;
1334
+ }
1335
+ }
1320
1336
  return {
1321
1337
  id: String(objectId),
1322
1338
  ref: rallyArtifact.FormattedID,
@@ -1330,7 +1346,9 @@ var RallyTracker = class {
1330
1346
  priority,
1331
1347
  dueDate: rallyArtifact.DueDate,
1332
1348
  createdAt: rallyArtifact.CreationDate,
1333
- updatedAt: rallyArtifact.LastUpdateDate
1349
+ updatedAt: rallyArtifact.LastUpdateDate,
1350
+ parentRef,
1351
+ artifactType
1334
1352
  };
1335
1353
  }
1336
1354
  mapState(rallyState) {
@@ -1684,4 +1702,4 @@ export {
1684
1702
  LinkManager,
1685
1703
  getLinkManager
1686
1704
  };
1687
- //# sourceMappingURL=chunk-K72CCKWP.js.map
1705
+ //# sourceMappingURL=chunk-77Q3GY6S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/shell.ts","../src/lib/backup.ts","../src/lib/sync.ts","../src/lib/tracker/interface.ts","../src/lib/tracker/linear.ts","../src/lib/tracker/github.ts","../src/lib/tracker/gitlab.ts","../src/lib/tracker/factory.ts","../src/lib/tracker/rally.ts","../src/lib/tracker/rally-api.ts","../src/lib/tracker/linking.ts","../src/lib/tracker/index.ts"],"sourcesContent":["import { existsSync, readFileSync, appendFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport type Shell = 'bash' | 'zsh' | 'fish' | 'unknown';\n\nexport function detectShell(): Shell {\n const shell = process.env.SHELL || '';\n\n if (shell.includes('zsh')) return 'zsh';\n if (shell.includes('bash')) return 'bash';\n if (shell.includes('fish')) return 'fish';\n\n return 'unknown';\n}\n\nexport function getShellRcFile(shell: Shell): string | null {\n const home = homedir();\n\n switch (shell) {\n case 'zsh':\n return join(home, '.zshrc');\n case 'bash':\n // Prefer .bashrc, fall back to .bash_profile\n const bashrc = join(home, '.bashrc');\n if (existsSync(bashrc)) return bashrc;\n return join(home, '.bash_profile');\n case 'fish':\n return join(home, '.config', 'fish', 'config.fish');\n default:\n return null;\n }\n}\n\nconst ALIAS_LINE = 'alias pan=\"panopticon\"';\nconst ALIAS_MARKER = '# Panopticon CLI alias';\n\nexport function hasAlias(rcFile: string): boolean {\n if (!existsSync(rcFile)) return false;\n\n const content = readFileSync(rcFile, 'utf8');\n return content.includes(ALIAS_MARKER) || content.includes(ALIAS_LINE);\n}\n\nexport function addAlias(rcFile: string): void {\n if (hasAlias(rcFile)) return;\n\n const aliasBlock = `\n${ALIAS_MARKER}\n${ALIAS_LINE}\n`;\n\n appendFileSync(rcFile, aliasBlock, 'utf8');\n}\n\nexport function getAliasInstructions(shell: Shell): string {\n const rcFile = getShellRcFile(shell);\n\n if (!rcFile) {\n return `Add this to your shell config:\\n ${ALIAS_LINE}`;\n }\n\n return `Alias added to ${rcFile}. Run:\\n source ${rcFile}`;\n}\n","import { existsSync, mkdirSync, readdirSync, cpSync, rmSync, lstatSync } from 'fs';\nimport { join, basename } from 'path';\nimport { BACKUPS_DIR } from './paths.js';\n\nexport interface BackupInfo {\n timestamp: string;\n path: string;\n targets: string[];\n}\n\nexport function createBackupTimestamp(): string {\n return new Date().toISOString().replace(/[:.]/g, '-');\n}\n\nexport function createBackup(sourceDirs: string[]): BackupInfo {\n const timestamp = createBackupTimestamp();\n const backupPath = join(BACKUPS_DIR, timestamp);\n\n mkdirSync(backupPath, { recursive: true });\n\n const targets: string[] = [];\n\n for (const sourceDir of sourceDirs) {\n if (!existsSync(sourceDir)) continue;\n\n const targetName = basename(sourceDir);\n const targetPath = join(backupPath, targetName);\n\n // Use filter to skip symlinks — sync targets (e.g. ~/.claude/skills/)\n // contain symlinks back into ~/.panopticon/skills/ which causes cpSync\n // to fail with \"cannot copy to a subdirectory of self\".\n cpSync(sourceDir, targetPath, {\n recursive: true,\n filter: (src) => !lstatSync(src).isSymbolicLink(),\n });\n targets.push(targetName);\n }\n\n return {\n timestamp,\n path: backupPath,\n targets,\n };\n}\n\nexport function listBackups(): BackupInfo[] {\n if (!existsSync(BACKUPS_DIR)) return [];\n\n const entries = readdirSync(BACKUPS_DIR, { withFileTypes: true });\n\n return entries\n .filter((e) => e.isDirectory())\n .map((e) => {\n const backupPath = join(BACKUPS_DIR, e.name);\n const contents = readdirSync(backupPath);\n\n return {\n timestamp: e.name,\n path: backupPath,\n targets: contents,\n };\n })\n .sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n}\n\nexport function restoreBackup(timestamp: string, targetDirs: Record<string, string>): void {\n const backupPath = join(BACKUPS_DIR, timestamp);\n\n if (!existsSync(backupPath)) {\n throw new Error(`Backup not found: ${timestamp}`);\n }\n\n const contents = readdirSync(backupPath, { withFileTypes: true });\n\n for (const entry of contents) {\n if (!entry.isDirectory()) continue;\n\n const sourcePath = join(backupPath, entry.name);\n const targetPath = targetDirs[entry.name];\n\n if (!targetPath) continue;\n\n // Remove existing and restore from backup\n if (existsSync(targetPath)) {\n rmSync(targetPath, { recursive: true });\n }\n\n cpSync(sourcePath, targetPath, { recursive: true });\n }\n}\n\nexport function cleanOldBackups(keepCount: number = 10): number {\n const backups = listBackups();\n\n if (backups.length <= keepCount) return 0;\n\n const toRemove = backups.slice(keepCount);\n let removed = 0;\n\n for (const backup of toRemove) {\n rmSync(backup.path, { recursive: true });\n removed++;\n }\n\n return removed;\n}\n","import { existsSync, mkdirSync, readdirSync, symlinkSync, unlinkSync, lstatSync, readlinkSync, rmSync, copyFileSync, chmodSync } from 'fs';\nimport { join, basename } from 'path';\nimport { SKILLS_DIR, COMMANDS_DIR, AGENTS_DIR, BIN_DIR, SOURCE_SCRIPTS_DIR, SOURCE_DEV_SKILLS_DIR, SYNC_TARGETS, isDevMode, type Runtime } from './paths.js';\n\nexport interface SyncItem {\n name: string;\n sourcePath: string;\n targetPath: string;\n status: 'new' | 'exists' | 'conflict' | 'symlink';\n}\n\nexport interface SyncPlan {\n runtime: Runtime;\n skills: SyncItem[];\n commands: SyncItem[];\n agents: SyncItem[];\n devSkills: SyncItem[]; // Developer-only skills (only synced in dev mode)\n}\n\n/**\n * Remove a file, symlink, or directory safely\n */\nfunction removeTarget(targetPath: string): void {\n const stats = lstatSync(targetPath);\n if (stats.isDirectory() && !stats.isSymbolicLink()) {\n // It's a real directory, remove recursively\n rmSync(targetPath, { recursive: true, force: true });\n } else {\n // It's a file or symlink\n unlinkSync(targetPath);\n }\n}\n\n/**\n * Check if a path is a Panopticon-managed symlink\n */\nexport function isPanopticonSymlink(targetPath: string): boolean {\n if (!existsSync(targetPath)) return false;\n\n try {\n const stats = lstatSync(targetPath);\n if (!stats.isSymbolicLink()) return false;\n\n const linkTarget = readlinkSync(targetPath);\n // It's ours if it points to our skills/commands dir\n return linkTarget.includes('.panopticon');\n } catch {\n return false;\n }\n}\n\n/**\n * Plan what would be synced (dry run)\n */\nexport function planSync(runtime: Runtime): SyncPlan {\n const targets = SYNC_TARGETS[runtime];\n if (!targets) {\n throw new Error(`Unknown sync target \"${runtime}\". Valid targets: ${Object.keys(SYNC_TARGETS).join(', ')}`);\n }\n const plan: SyncPlan = {\n runtime,\n skills: [],\n commands: [],\n agents: [],\n devSkills: [],\n };\n\n // Plan skills sync\n if (existsSync(SKILLS_DIR)) {\n const skills = readdirSync(SKILLS_DIR, { withFileTypes: true })\n .filter((d) => d.isDirectory());\n\n for (const skill of skills) {\n const sourcePath = join(SKILLS_DIR, skill.name);\n const targetPath = join(targets.skills, skill.name);\n\n let status: SyncItem['status'] = 'new';\n\n if (existsSync(targetPath)) {\n if (isPanopticonSymlink(targetPath)) {\n status = 'symlink'; // Already managed by us\n } else {\n status = 'conflict'; // User content exists\n }\n }\n\n plan.skills.push({ name: skill.name, sourcePath, targetPath, status });\n }\n }\n\n // Plan dev-skills sync (only in dev mode)\n if (isDevMode() && existsSync(SOURCE_DEV_SKILLS_DIR)) {\n const devSkills = readdirSync(SOURCE_DEV_SKILLS_DIR, { withFileTypes: true })\n .filter((d) => d.isDirectory());\n\n for (const skill of devSkills) {\n const sourcePath = join(SOURCE_DEV_SKILLS_DIR, skill.name);\n const targetPath = join(targets.skills, skill.name);\n\n let status: SyncItem['status'] = 'new';\n\n if (existsSync(targetPath)) {\n if (isPanopticonSymlink(targetPath)) {\n status = 'symlink'; // Already managed by us\n } else {\n status = 'conflict'; // User content exists\n }\n }\n\n plan.devSkills.push({ name: skill.name, sourcePath, targetPath, status });\n }\n }\n\n // Plan commands sync\n if (existsSync(COMMANDS_DIR)) {\n const commands = readdirSync(COMMANDS_DIR, { withFileTypes: true })\n .filter((d) => d.isDirectory());\n\n for (const cmd of commands) {\n const sourcePath = join(COMMANDS_DIR, cmd.name);\n const targetPath = join(targets.commands, cmd.name);\n\n let status: SyncItem['status'] = 'new';\n\n if (existsSync(targetPath)) {\n if (isPanopticonSymlink(targetPath)) {\n status = 'symlink';\n } else {\n status = 'conflict';\n }\n }\n\n plan.commands.push({ name: cmd.name, sourcePath, targetPath, status });\n }\n }\n\n // Plan agents sync\n if (existsSync(AGENTS_DIR)) {\n const agents = readdirSync(AGENTS_DIR, { withFileTypes: true })\n .filter((entry) => entry.isFile() && entry.name.endsWith('.md'));\n\n for (const agent of agents) {\n const sourcePath = join(AGENTS_DIR, agent.name);\n const targetPath = join(targets.agents, agent.name);\n\n let status: SyncItem['status'] = 'new';\n\n if (existsSync(targetPath)) {\n if (isPanopticonSymlink(targetPath)) {\n status = 'symlink'; // Already managed by us\n } else {\n status = 'conflict'; // User content exists\n }\n }\n\n plan.agents.push({ name: agent.name, sourcePath, targetPath, status });\n }\n }\n\n return plan;\n}\n\nexport interface SyncOptions {\n force?: boolean;\n dryRun?: boolean;\n}\n\nexport interface SyncResult {\n created: string[];\n skipped: string[];\n conflicts: string[];\n}\n\n/**\n * Execute sync for a runtime\n */\nexport function executeSync(runtime: Runtime, options: SyncOptions = {}): SyncResult {\n const targets = SYNC_TARGETS[runtime];\n if (!targets) {\n throw new Error(`Unknown sync target \"${runtime}\". Valid targets: ${Object.keys(SYNC_TARGETS).join(', ')}`);\n }\n const plan = planSync(runtime);\n const result: SyncResult = {\n created: [],\n skipped: [],\n conflicts: [],\n };\n\n // Ensure target directories exist\n mkdirSync(targets.skills, { recursive: true });\n mkdirSync(targets.commands, { recursive: true });\n mkdirSync(targets.agents, { recursive: true });\n\n // Process skills\n for (const item of plan.skills) {\n if (options.dryRun) {\n if (item.status === 'new' || item.status === 'symlink') {\n result.created.push(item.name);\n } else {\n result.conflicts.push(item.name);\n }\n continue;\n }\n\n if (item.status === 'conflict' && !options.force) {\n result.conflicts.push(item.name);\n continue;\n }\n\n // Remove existing if force or if it's our symlink\n if (existsSync(item.targetPath)) {\n removeTarget(item.targetPath);\n }\n\n // Create symlink\n symlinkSync(item.sourcePath, item.targetPath);\n result.created.push(item.name);\n }\n\n // Process commands\n for (const item of plan.commands) {\n if (options.dryRun) {\n if (item.status === 'new' || item.status === 'symlink') {\n result.created.push(item.name);\n } else {\n result.conflicts.push(item.name);\n }\n continue;\n }\n\n if (item.status === 'conflict' && !options.force) {\n result.conflicts.push(item.name);\n continue;\n }\n\n if (existsSync(item.targetPath)) {\n removeTarget(item.targetPath);\n }\n\n symlinkSync(item.sourcePath, item.targetPath);\n result.created.push(item.name);\n }\n\n // Process agents\n for (const item of plan.agents) {\n if (options.dryRun) {\n if (item.status === 'new' || item.status === 'symlink') {\n result.created.push(item.name);\n } else {\n result.conflicts.push(item.name);\n }\n continue;\n }\n\n if (item.status === 'conflict' && !options.force) {\n result.conflicts.push(item.name);\n continue;\n }\n\n if (existsSync(item.targetPath)) {\n removeTarget(item.targetPath);\n }\n\n symlinkSync(item.sourcePath, item.targetPath);\n result.created.push(item.name);\n }\n\n // Process dev-skills (only in dev mode)\n for (const item of plan.devSkills) {\n if (options.dryRun) {\n if (item.status === 'new' || item.status === 'symlink') {\n result.created.push(`${item.name} (dev)`);\n } else {\n result.conflicts.push(`${item.name} (dev)`);\n }\n continue;\n }\n\n if (item.status === 'conflict' && !options.force) {\n result.conflicts.push(`${item.name} (dev)`);\n continue;\n }\n\n if (existsSync(item.targetPath)) {\n removeTarget(item.targetPath);\n }\n\n symlinkSync(item.sourcePath, item.targetPath);\n result.created.push(`${item.name} (dev)`);\n }\n\n return result;\n}\n\n/**\n * Hook item for sync planning\n */\nexport interface HookItem {\n name: string;\n sourcePath: string;\n targetPath: string;\n status: 'new' | 'updated' | 'current';\n}\n\n/**\n * Plan hooks sync (checks what would be updated)\n */\nexport function planHooksSync(): HookItem[] {\n const hooks: HookItem[] = [];\n\n if (!existsSync(SOURCE_SCRIPTS_DIR)) {\n return hooks;\n }\n\n // Only sync hook scripts (no extension) - skip helper scripts like .mjs, .sh\n const scripts = readdirSync(SOURCE_SCRIPTS_DIR, { withFileTypes: true })\n .filter((entry) => entry.isFile() && !entry.name.startsWith('.') && !entry.name.includes('.'));\n\n for (const script of scripts) {\n const sourcePath = join(SOURCE_SCRIPTS_DIR, script.name);\n const targetPath = join(BIN_DIR, script.name);\n\n let status: HookItem['status'] = 'new';\n\n if (existsSync(targetPath)) {\n // Could compare file contents/timestamps here for 'current' vs 'updated'\n // For now, always update to ensure latest version\n status = 'updated';\n }\n\n hooks.push({ name: script.name, sourcePath, targetPath, status });\n }\n\n return hooks;\n}\n\n/**\n * Sync hooks (copy scripts to ~/.panopticon/bin/)\n */\nexport function syncHooks(): { synced: string[]; errors: string[] } {\n const result = { synced: [] as string[], errors: [] as string[] };\n\n // Ensure bin directory exists\n mkdirSync(BIN_DIR, { recursive: true });\n\n const hooks = planHooksSync();\n\n for (const hook of hooks) {\n try {\n copyFileSync(hook.sourcePath, hook.targetPath);\n chmodSync(hook.targetPath, 0o755); // Make executable\n result.synced.push(hook.name);\n } catch (error) {\n result.errors.push(`${hook.name}: ${error}`);\n }\n }\n\n return result;\n}\n","/**\n * Issue Tracker Abstraction Layer\n *\n * Provides a unified interface for different issue tracking systems\n * (Linear, GitHub Issues, GitLab Issues, etc.)\n */\n\n// Supported tracker types\nexport type TrackerType = 'linear' | 'github' | 'gitlab' | 'rally';\n\n// Normalized issue state (lowest common denominator)\nexport type IssueState = 'open' | 'in_progress' | 'closed';\n\n// Normalized issue format\nexport interface Issue {\n /** Tracker-specific unique ID */\n id: string;\n\n /** Human-readable reference (e.g., MIN-630, #42) */\n ref: string;\n\n /** Issue title */\n title: string;\n\n /** Issue description/body (markdown) */\n description: string;\n\n /** Normalized state */\n state: IssueState;\n\n /** Labels/tags */\n labels: string[];\n\n /** Assignee username/name */\n assignee?: string;\n\n /** Web URL to the issue */\n url: string;\n\n /** Which tracker this issue came from */\n tracker: TrackerType;\n\n /** Cross-tracker linked issue references */\n linkedIssues?: string[];\n\n /** Priority (1=urgent, 2=high, 3=normal, 4=low) */\n priority?: number;\n\n /** Due date (ISO string) */\n dueDate?: string;\n\n /** Creation timestamp (ISO string) */\n createdAt: string;\n\n /** Last update timestamp (ISO string) */\n updatedAt: string;\n\n /** Parent issue FormattedID (e.g., \"F1234\") for Rally hierarchy */\n parentRef?: string;\n\n /** Rally artifact type (e.g., \"HierarchicalRequirement\", \"PortfolioItem/Feature\") */\n artifactType?: string;\n}\n\n// Comment on an issue\nexport interface Comment {\n id: string;\n issueId: string;\n body: string;\n author: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// Filters for listing issues\nexport interface IssueFilters {\n /** Filter by state */\n state?: IssueState;\n\n /** Filter by labels (AND logic) */\n labels?: string[];\n\n /** Filter by assignee */\n assignee?: string;\n\n /** Filter by team/project (tracker-specific) */\n team?: string;\n\n /** Search query for title/description */\n query?: string;\n\n /** Maximum number of results */\n limit?: number;\n\n /** Include closed issues (default: false) */\n includeClosed?: boolean;\n}\n\n// Data for creating a new issue\nexport interface NewIssue {\n title: string;\n description?: string;\n labels?: string[];\n assignee?: string;\n team?: string;\n priority?: number;\n dueDate?: string;\n}\n\n// Data for updating an issue\nexport interface IssueUpdate {\n title?: string;\n description?: string;\n state?: IssueState;\n labels?: string[];\n assignee?: string;\n priority?: number;\n dueDate?: string;\n}\n\n/**\n * Abstract interface for issue trackers.\n * Implementations must handle normalization to/from tracker-specific formats.\n */\nexport interface IssueTracker {\n /** Tracker type identifier */\n readonly name: TrackerType;\n\n /**\n * List issues matching filters\n */\n listIssues(filters?: IssueFilters): Promise<Issue[]>;\n\n /**\n * Get a single issue by ID or ref\n * @param id - Issue ID or human-readable ref (e.g., \"MIN-630\", \"#42\")\n */\n getIssue(id: string): Promise<Issue>;\n\n /**\n * Update an existing issue\n */\n updateIssue(id: string, update: IssueUpdate): Promise<Issue>;\n\n /**\n * Create a new issue\n */\n createIssue(issue: NewIssue): Promise<Issue>;\n\n /**\n * Get comments on an issue\n */\n getComments(issueId: string): Promise<Comment[]>;\n\n /**\n * Add a comment to an issue\n */\n addComment(issueId: string, body: string): Promise<Comment>;\n\n /**\n * Transition issue to a new state\n */\n transitionIssue(id: string, state: IssueState): Promise<void>;\n\n /**\n * Link a PR/MR to an issue\n */\n linkPR(issueId: string, prUrl: string): Promise<void>;\n}\n\n/**\n * Error thrown when a tracker feature is not implemented\n */\nexport class NotImplementedError extends Error {\n constructor(feature: string) {\n super(`Not implemented: ${feature}`);\n this.name = 'NotImplementedError';\n }\n}\n\n/**\n * Error thrown when an issue is not found\n */\nexport class IssueNotFoundError extends Error {\n constructor(id: string, tracker: TrackerType) {\n super(`Issue not found: ${id} (tracker: ${tracker})`);\n this.name = 'IssueNotFoundError';\n }\n}\n\n/**\n * Error thrown when tracker authentication fails\n */\nexport class TrackerAuthError extends Error {\n constructor(tracker: TrackerType, message: string) {\n super(`Authentication failed for ${tracker}: ${message}`);\n this.name = 'TrackerAuthError';\n }\n}\n","/**\n * Linear Issue Tracker Adapter\n *\n * Implements IssueTracker interface for Linear.\n */\n\nimport { LinearClient } from '@linear/sdk';\nimport type {\n Issue,\n IssueFilters,\n IssueState,\n IssueTracker,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\nimport { IssueNotFoundError, TrackerAuthError } from './interface.js';\n\n// Map Linear state types to our normalized states\nconst STATE_MAP: Record<string, IssueState> = {\n backlog: 'open',\n unstarted: 'open',\n started: 'in_progress',\n completed: 'closed',\n canceled: 'closed',\n};\n\nexport class LinearTracker implements IssueTracker {\n readonly name: TrackerType = 'linear';\n private client: LinearClient;\n private defaultTeam?: string;\n\n constructor(apiKey: string, options?: { team?: string }) {\n if (!apiKey) {\n throw new TrackerAuthError('linear', 'API key is required');\n }\n this.client = new LinearClient({ apiKey });\n this.defaultTeam = options?.team;\n }\n\n async listIssues(filters?: IssueFilters): Promise<Issue[]> {\n const team = filters?.team ?? this.defaultTeam;\n\n const result = await this.client.issues({\n first: filters?.limit ?? 50,\n filter: {\n team: team ? { key: { eq: team } } : undefined,\n state: filters?.state\n ? { type: { eq: this.reverseMapState(filters.state) } }\n : filters?.includeClosed\n ? undefined\n : { type: { neq: 'completed' } },\n labels: filters?.labels?.length\n ? { name: { in: filters.labels } }\n : undefined,\n assignee: filters?.assignee\n ? { name: { containsIgnoreCase: filters.assignee } }\n : undefined,\n },\n });\n\n const issues: Issue[] = [];\n for (const node of result.nodes) {\n issues.push(await this.normalizeIssue(node));\n }\n return issues;\n }\n\n async getIssue(id: string): Promise<Issue> {\n try {\n // Check if it's a UUID (36 chars with hyphens)\n 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);\n\n if (isUuid) {\n // Fetch directly by UUID\n const issue = await this.client.issue(id);\n if (issue) {\n return this.normalizeIssue(issue);\n }\n } else {\n // Parse identifier (e.g., MIN-630) and search\n const match = id.match(/^([A-Z]+)-(\\d+)$/i);\n if (match) {\n const [, teamKey, number] = match;\n // Use searchIssues which supports identifier matching\n const results = await this.client.searchIssues(id, { first: 1 });\n if (results.nodes.length > 0) {\n return this.normalizeIssue(results.nodes[0]);\n }\n }\n }\n\n throw new IssueNotFoundError(id, 'linear');\n } catch (error) {\n if (error instanceof IssueNotFoundError) throw error;\n throw new IssueNotFoundError(id, 'linear');\n }\n }\n\n async updateIssue(id: string, update: IssueUpdate): Promise<Issue> {\n const issue = await this.getIssue(id);\n\n const updatePayload: Record<string, unknown> = {};\n\n if (update.title !== undefined) {\n updatePayload.title = update.title;\n }\n if (update.description !== undefined) {\n updatePayload.description = update.description;\n }\n if (update.priority !== undefined) {\n updatePayload.priority = update.priority;\n }\n if (update.dueDate !== undefined) {\n updatePayload.dueDate = update.dueDate;\n }\n if (update.state !== undefined) {\n // Need to find the state ID - this is complex in Linear\n // For now, we'll use the transition method\n await this.transitionIssue(id, update.state);\n }\n if (update.labels !== undefined) {\n // Need to look up label IDs - complex operation\n // TODO: Implement label updates\n }\n\n if (Object.keys(updatePayload).length > 0) {\n await this.client.updateIssue(issue.id, updatePayload);\n }\n\n return this.getIssue(id);\n }\n\n async createIssue(newIssue: NewIssue): Promise<Issue> {\n const team = newIssue.team ?? this.defaultTeam;\n\n if (!team) {\n throw new Error('Team is required to create an issue');\n }\n\n // Get team ID from key\n const teams = await this.client.teams({\n filter: { key: { eq: team } },\n });\n\n if (teams.nodes.length === 0) {\n throw new Error(`Team not found: ${team}`);\n }\n\n const teamId = teams.nodes[0].id;\n\n const result = await this.client.createIssue({\n teamId,\n title: newIssue.title,\n description: newIssue.description,\n priority: newIssue.priority,\n dueDate: newIssue.dueDate,\n });\n\n const created = await result.issue;\n if (!created) {\n throw new Error('Failed to create issue');\n }\n\n return this.normalizeIssue(created);\n }\n\n async getComments(issueId: string): Promise<Comment[]> {\n const issue = await this.client.issue(issueId);\n const comments = await issue.comments();\n\n return comments.nodes.map((c) => ({\n id: c.id,\n issueId,\n body: c.body,\n author: c.user?.then((u) => u?.name ?? 'Unknown') as unknown as string, // Simplified\n createdAt: c.createdAt.toISOString(),\n updatedAt: c.updatedAt.toISOString(),\n }));\n }\n\n async addComment(issueId: string, body: string): Promise<Comment> {\n const result = await this.client.createComment({\n issueId,\n body,\n });\n\n const comment = await result.comment;\n if (!comment) {\n throw new Error('Failed to create comment');\n }\n\n return {\n id: comment.id,\n issueId,\n body: comment.body,\n author: 'Panopticon', // Simplified\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n };\n }\n\n async transitionIssue(id: string, state: IssueState): Promise<void> {\n const issue = await this.getIssue(id);\n\n // Get workflow states for the issue's team\n const linearIssue = await this.client.issue(issue.id);\n const team = await linearIssue.team;\n if (!team) {\n throw new Error('Could not determine issue team');\n }\n\n const states = await team.states();\n const targetStateType = this.reverseMapState(state);\n\n // Find a state matching the target type\n const targetState = states.nodes.find((s) => s.type === targetStateType);\n if (!targetState) {\n throw new Error(`No state found matching type: ${targetStateType}`);\n }\n\n await this.client.updateIssue(issue.id, {\n stateId: targetState.id,\n });\n }\n\n async linkPR(issueId: string, prUrl: string): Promise<void> {\n const issue = await this.getIssue(issueId);\n\n await this.client.createAttachment({\n issueId: issue.id,\n title: 'Pull Request',\n url: prUrl,\n });\n }\n\n private async normalizeIssue(linearIssue: any): Promise<Issue> {\n const state = await linearIssue.state;\n const assignee = await linearIssue.assignee;\n const labels = await linearIssue.labels();\n\n // Handle dueDate - can be Date, string, or undefined\n let dueDate: string | undefined;\n if (linearIssue.dueDate) {\n dueDate = linearIssue.dueDate instanceof Date\n ? linearIssue.dueDate.toISOString()\n : String(linearIssue.dueDate);\n }\n\n return {\n id: linearIssue.id,\n ref: linearIssue.identifier,\n title: linearIssue.title,\n description: linearIssue.description ?? '',\n state: this.mapState(state?.type ?? 'backlog'),\n labels: labels?.nodes?.map((l: any) => l.name) ?? [],\n assignee: assignee?.name,\n url: linearIssue.url,\n tracker: 'linear',\n priority: linearIssue.priority,\n dueDate,\n createdAt: linearIssue.createdAt instanceof Date\n ? linearIssue.createdAt.toISOString()\n : String(linearIssue.createdAt),\n updatedAt: linearIssue.updatedAt instanceof Date\n ? linearIssue.updatedAt.toISOString()\n : String(linearIssue.updatedAt),\n };\n }\n\n private mapState(linearState: string): IssueState {\n return STATE_MAP[linearState] ?? 'open';\n }\n\n private reverseMapState(state: IssueState): string {\n switch (state) {\n case 'open':\n return 'unstarted';\n case 'in_progress':\n return 'started';\n case 'closed':\n return 'completed';\n default:\n return 'unstarted';\n }\n }\n}\n","/**\n * GitHub Issues Tracker Adapter\n *\n * Implements IssueTracker interface for GitHub Issues.\n */\n\nimport { Octokit } from '@octokit/rest';\nimport type {\n Issue,\n IssueFilters,\n IssueState,\n IssueTracker,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\nimport { IssueNotFoundError, TrackerAuthError } from './interface.js';\n\nexport class GitHubTracker implements IssueTracker {\n readonly name: TrackerType = 'github';\n private octokit: Octokit;\n private owner: string;\n private repo: string;\n\n constructor(token: string, owner: string, repo: string) {\n if (!token) {\n throw new TrackerAuthError('github', 'Token is required');\n }\n if (!owner || !repo) {\n throw new Error('GitHub owner and repo are required');\n }\n\n this.octokit = new Octokit({ auth: token });\n this.owner = owner;\n this.repo = repo;\n }\n\n async listIssues(filters?: IssueFilters): Promise<Issue[]> {\n const state = this.mapStateToGitHub(filters?.state);\n\n const response = await this.octokit.issues.listForRepo({\n owner: this.owner,\n repo: this.repo,\n state: filters?.includeClosed ? 'all' : state,\n labels: filters?.labels?.join(',') || undefined,\n assignee: filters?.assignee || undefined,\n per_page: filters?.limit ?? 50,\n });\n\n // Filter out pull requests (GitHub API returns both)\n const issues = response.data.filter((item) => !item.pull_request);\n\n return issues.map((issue) => this.normalizeIssue(issue));\n }\n\n async getIssue(id: string): Promise<Issue> {\n try {\n // Parse the issue number from refs like \"#42\" or just \"42\"\n const issueNumber = parseInt(id.replace(/^#/, ''), 10);\n\n if (isNaN(issueNumber)) {\n throw new IssueNotFoundError(id, 'github');\n }\n\n const { data: issue } = await this.octokit.issues.get({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n });\n\n return this.normalizeIssue(issue);\n } catch (error: any) {\n if (error?.status === 404) {\n throw new IssueNotFoundError(id, 'github');\n }\n throw error;\n }\n }\n\n async updateIssue(id: string, update: IssueUpdate): Promise<Issue> {\n const issueNumber = parseInt(id.replace(/^#/, ''), 10);\n\n const updatePayload: Record<string, unknown> = {};\n\n if (update.title !== undefined) {\n updatePayload.title = update.title;\n }\n if (update.description !== undefined) {\n updatePayload.body = update.description;\n }\n if (update.state !== undefined) {\n updatePayload.state = update.state === 'closed' ? 'closed' : 'open';\n }\n if (update.labels !== undefined) {\n updatePayload.labels = update.labels;\n }\n if (update.assignee !== undefined) {\n updatePayload.assignees = update.assignee ? [update.assignee] : [];\n }\n\n await this.octokit.issues.update({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n ...updatePayload,\n });\n\n return this.getIssue(id);\n }\n\n async createIssue(newIssue: NewIssue): Promise<Issue> {\n const { data: issue } = await this.octokit.issues.create({\n owner: this.owner,\n repo: this.repo,\n title: newIssue.title,\n body: newIssue.description,\n labels: newIssue.labels,\n assignees: newIssue.assignee ? [newIssue.assignee] : undefined,\n });\n\n return this.normalizeIssue(issue);\n }\n\n async getComments(issueId: string): Promise<Comment[]> {\n const issueNumber = parseInt(issueId.replace(/^#/, ''), 10);\n\n const { data: comments } = await this.octokit.issues.listComments({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n });\n\n return comments.map((c) => ({\n id: String(c.id),\n issueId,\n body: c.body ?? '',\n author: c.user?.login ?? 'Unknown',\n createdAt: c.created_at,\n updatedAt: c.updated_at,\n }));\n }\n\n async addComment(issueId: string, body: string): Promise<Comment> {\n const issueNumber = parseInt(issueId.replace(/^#/, ''), 10);\n\n const { data: comment } = await this.octokit.issues.createComment({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n body,\n });\n\n return {\n id: String(comment.id),\n issueId,\n body: comment.body ?? '',\n author: comment.user?.login ?? 'Unknown',\n createdAt: comment.created_at,\n updatedAt: comment.updated_at,\n };\n }\n\n async transitionIssue(id: string, state: IssueState): Promise<void> {\n await this.updateIssue(id, { state });\n }\n\n async linkPR(issueId: string, prUrl: string): Promise<void> {\n // GitHub auto-links PRs that mention issues\n // Add a comment with the PR link\n await this.addComment(\n issueId,\n `Linked Pull Request: ${prUrl}`\n );\n }\n\n private normalizeIssue(ghIssue: any): Issue {\n return {\n id: String(ghIssue.id),\n ref: `#${ghIssue.number}`,\n title: ghIssue.title,\n description: ghIssue.body ?? '',\n state: this.mapStateFromGitHub(ghIssue.state),\n labels: ghIssue.labels.map((l: any) =>\n typeof l === 'string' ? l : l.name\n ),\n assignee: ghIssue.assignee?.login,\n url: ghIssue.html_url,\n tracker: 'github',\n priority: undefined, // GitHub doesn't have priority\n dueDate: undefined, // GitHub doesn't have due dates on issues\n createdAt: ghIssue.created_at,\n updatedAt: ghIssue.updated_at,\n };\n }\n\n private mapStateFromGitHub(ghState: string): IssueState {\n // GitHub only has open and closed states\n // No way to distinguish \"in_progress\" without custom labels\n return ghState === 'closed' ? 'closed' : 'open';\n }\n\n private mapStateToGitHub(\n state?: IssueState\n ): 'open' | 'closed' | 'all' {\n if (!state) return 'open';\n if (state === 'closed') return 'closed';\n return 'open'; // Both 'open' and 'in_progress' map to 'open'\n }\n}\n","/**\n * GitLab Issues Tracker Adapter (Stub)\n *\n * Placeholder implementation for GitLab Issues support.\n * Full implementation will use @gitbeaker/rest.\n */\n\nimport type {\n Issue,\n IssueFilters,\n IssueState,\n IssueTracker,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\nimport { NotImplementedError } from './interface.js';\n\nexport class GitLabTracker implements IssueTracker {\n readonly name: TrackerType = 'gitlab';\n\n constructor(\n private token: string,\n private projectId: string\n ) {\n // Stub - will initialize @gitbeaker client when implemented\n }\n\n async listIssues(_filters?: IssueFilters): Promise<Issue[]> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async getIssue(_id: string): Promise<Issue> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async updateIssue(_id: string, _update: IssueUpdate): Promise<Issue> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async createIssue(_issue: NewIssue): Promise<Issue> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async getComments(_issueId: string): Promise<Comment[]> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async addComment(_issueId: string, _body: string): Promise<Comment> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async transitionIssue(_id: string, _state: IssueState): Promise<void> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n\n async linkPR(_issueId: string, _prUrl: string): Promise<void> {\n throw new NotImplementedError(\n 'GitLab tracker is not yet implemented. Coming soon!'\n );\n }\n}\n","/**\n * Tracker Factory\n *\n * Creates appropriate tracker instances based on configuration.\n */\n\nimport type { IssueTracker, TrackerType } from './interface.js';\nimport { TrackerAuthError } from './interface.js';\nimport { LinearTracker } from './linear.js';\nimport { GitHubTracker } from './github.js';\nimport { GitLabTracker } from './gitlab.js';\nimport { RallyTracker } from './rally.js';\nimport type { TrackersConfig } from '../config.js';\nimport { loadConfig as loadYamlConfig } from '../config-yaml.js';\n\n// Configuration for a single tracker\nexport interface TrackerConfig {\n type: TrackerType;\n\n // Linear-specific\n apiKeyEnv?: string;\n team?: string;\n\n // GitHub-specific\n tokenEnv?: string;\n owner?: string;\n repo?: string;\n\n // GitLab-specific\n projectId?: string;\n\n // Rally-specific\n server?: string;\n workspace?: string;\n project?: string;\n}\n\n// Multi-tracker configuration (re-exported from config.ts)\n// Note: Use TrackersConfig from config.ts for full type with nested configs\n\n/**\n * Get tracker API key from config.yaml (Settings page).\n * This is checked FIRST — env vars are the fallback, not the other way around.\n */\nfunction getTrackerKeyFromConfig(trackerType: TrackerType): string | undefined {\n try {\n const yamlConfig = loadYamlConfig();\n return yamlConfig.trackerKeys[trackerType];\n } catch {\n return undefined;\n }\n}\n\n/**\n * Create a tracker instance from configuration.\n * Priority: config.yaml (Settings) > environment variable > custom env var name\n */\nexport function createTracker(config: TrackerConfig): IssueTracker {\n switch (config.type) {\n case 'linear': {\n const configKey = getTrackerKeyFromConfig('linear');\n const envKey = config.apiKeyEnv\n ? process.env[config.apiKeyEnv]\n : process.env.LINEAR_API_KEY;\n const apiKey = configKey || envKey;\n\n if (!apiKey) {\n throw new TrackerAuthError(\n 'linear',\n `API key not found. Configure in Settings or set ${config.apiKeyEnv ?? 'LINEAR_API_KEY'} environment variable.`\n );\n }\n\n return new LinearTracker(apiKey, { team: config.team });\n }\n\n case 'github': {\n const configKey = getTrackerKeyFromConfig('github');\n const envToken = config.tokenEnv\n ? process.env[config.tokenEnv]\n : process.env.GITHUB_TOKEN;\n const token = configKey || envToken;\n\n if (!token) {\n throw new TrackerAuthError(\n 'github',\n `Token not found. Configure in Settings or set ${config.tokenEnv ?? 'GITHUB_TOKEN'} environment variable.`\n );\n }\n\n if (!config.owner || !config.repo) {\n throw new Error(\n 'GitHub tracker requires owner and repo configuration'\n );\n }\n\n return new GitHubTracker(token, config.owner, config.repo);\n }\n\n case 'gitlab': {\n const configKey = getTrackerKeyFromConfig('gitlab');\n const envToken = config.tokenEnv\n ? process.env[config.tokenEnv]\n : process.env.GITLAB_TOKEN;\n const token = configKey || envToken;\n\n if (!token) {\n throw new TrackerAuthError(\n 'gitlab',\n `Token not found. Configure in Settings or set ${config.tokenEnv ?? 'GITLAB_TOKEN'} environment variable.`\n );\n }\n\n if (!config.projectId) {\n throw new Error('GitLab tracker requires projectId configuration');\n }\n\n return new GitLabTracker(token, config.projectId);\n }\n\n case 'rally': {\n const configKey = getTrackerKeyFromConfig('rally');\n const envKey = config.apiKeyEnv\n ? process.env[config.apiKeyEnv]\n : process.env.RALLY_API_KEY;\n const apiKey = configKey || envKey;\n\n if (!apiKey) {\n throw new TrackerAuthError(\n 'rally',\n `API key not found. Configure in Settings or set ${config.apiKeyEnv ?? 'RALLY_API_KEY'} environment variable.`\n );\n }\n\n return new RallyTracker({\n apiKey,\n server: config.server,\n workspace: config.workspace,\n project: config.project,\n });\n }\n\n default:\n throw new Error(`Unknown tracker type: ${config.type}`);\n }\n}\n\n/**\n * Create tracker from trackers configuration section\n */\nexport function createTrackerFromConfig(\n trackersConfig: TrackersConfig,\n trackerType: TrackerType\n): IssueTracker {\n const config = trackersConfig[trackerType];\n\n if (!config) {\n throw new Error(\n `No configuration found for tracker: ${trackerType}. Add [trackers.${trackerType}] to config.`\n );\n }\n\n return createTracker({ ...config, type: trackerType });\n}\n\n/**\n * Get the primary tracker from configuration\n */\nexport function getPrimaryTracker(trackersConfig: TrackersConfig): IssueTracker {\n return createTrackerFromConfig(trackersConfig, trackersConfig.primary);\n}\n\n/**\n * Get the secondary tracker from configuration (if configured)\n */\nexport function getSecondaryTracker(\n trackersConfig: TrackersConfig\n): IssueTracker | null {\n if (!trackersConfig.secondary) {\n return null;\n }\n return createTrackerFromConfig(trackersConfig, trackersConfig.secondary);\n}\n\n/**\n * Get all configured trackers\n */\nexport function getAllTrackers(trackersConfig: TrackersConfig): IssueTracker[] {\n const trackers: IssueTracker[] = [getPrimaryTracker(trackersConfig)];\n\n const secondary = getSecondaryTracker(trackersConfig);\n if (secondary) {\n trackers.push(secondary);\n }\n\n return trackers;\n}\n","/**\n * Rally Tracker Adapter\n *\n * Implements IssueTracker interface for Broadcom Rally (formerly CA Agile Central).\n * Supports all Rally work item types: User Stories, Defects, Tasks, and Features.\n */\n\nimport { RallyRestApi } from './rally-api.js';\nimport type {\n Issue,\n IssueFilters,\n IssueState,\n IssueTracker,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\nimport { IssueNotFoundError, TrackerAuthError, NotImplementedError } from './interface.js';\n\n// Map Rally ScheduleState/State to normalized IssueState.\n// Covers all standard states for User Stories, Defects, Tasks, and Features.\nconst STATE_MAP: Record<string, IssueState> = {\n // User Stories (ScheduleState)\n 'New': 'open',\n 'Idea': 'open',\n 'Defined': 'open',\n 'In-Progress': 'in_progress',\n 'Completed': 'closed',\n 'Accepted': 'closed',\n\n // Defects (State)\n 'Submitted': 'open',\n 'Open': 'in_progress', // \"Open\" defects are actively being worked\n 'Fixed': 'closed',\n 'Closed': 'closed',\n\n // Features / PortfolioItems (State)\n 'Discovering': 'open',\n 'Developing': 'in_progress',\n 'Done': 'closed',\n};\n\n// Rally artifact types we support\ntype RallyArtifactType = 'HierarchicalRequirement' | 'Defect' | 'Task' | 'PortfolioItem/Feature';\n\n/**\n * Type-specific query configuration.\n *\n * Rally WSAPI cannot filter generic Artifact by ScheduleState because not all\n * subtypes have that field. Instead, we query each type separately with its\n * own state field and merge the results. (PAN-168)\n */\ninterface ArtifactTypeQuery {\n /** WSAPI endpoint type (lowercase) */\n type: string;\n /** The state field used for filtering on this artifact type */\n stateField: 'ScheduleState' | 'State';\n /** State values that represent \"closed\" for this type */\n closedStates: string[];\n}\n\nconst QUERYABLE_TYPES: ArtifactTypeQuery[] = [\n { type: 'hierarchicalrequirement', stateField: 'ScheduleState', closedStates: ['Completed', 'Accepted'] },\n { type: 'defect', stateField: 'State', closedStates: ['Closed'] },\n { type: 'task', stateField: 'State', closedStates: ['Completed'] },\n { type: 'portfolioitem/feature', stateField: 'State', closedStates: ['Done'] },\n];\n\nconst FETCH_FIELDS = [\n 'ObjectID',\n 'FormattedID',\n 'Name',\n 'Description',\n 'ScheduleState',\n 'State',\n 'Tags',\n 'Owner',\n 'Priority',\n 'DueDate',\n 'CreationDate',\n 'LastUpdateDate',\n 'Parent',\n '_type',\n];\n\n// Rally priority strings to numbers\nconst PRIORITY_MAP: Record<string, number> = {\n 'Resolve Immediately': 0,\n High: 1,\n Normal: 2,\n Low: 3,\n};\n\n// Reverse priority mapping\nconst REVERSE_PRIORITY_MAP: Record<number, string> = {\n 0: 'Resolve Immediately',\n 1: 'High',\n 2: 'Normal',\n 3: 'Low',\n 4: 'Low',\n};\n\nexport interface RallyConfig {\n apiKey: string;\n server?: string; // Default: rally1.rallydev.com\n workspace?: string; // Rally workspace OID (e.g., \"/workspace/12345\")\n project?: string; // Rally project OID (e.g., \"/project/67890\")\n}\n\nexport class RallyTracker implements IssueTracker {\n readonly name: TrackerType = 'rally' as TrackerType;\n private restApi: RallyRestApi;\n private workspace?: string;\n private project?: string;\n\n constructor(config: RallyConfig) {\n if (!config.apiKey) {\n throw new TrackerAuthError('rally' as TrackerType, 'API key is required');\n }\n\n this.restApi = new RallyRestApi({\n apiKey: config.apiKey,\n server: config.server || 'https://rally1.rallydev.com',\n requestOptions: {\n headers: {\n 'X-RallyIntegrationType': 'Panopticon',\n 'X-RallyIntegrationName': 'Panopticon CLI',\n 'X-RallyIntegrationVendor': 'Mind Your Now',\n 'X-RallyIntegrationVersion': '0.2.0',\n },\n },\n });\n\n this.workspace = config.workspace;\n this.project = config.project;\n }\n\n /**\n * List issues by querying each artifact type separately and merging results.\n *\n * Rally WSAPI cannot apply ScheduleState filters across the generic Artifact\n * endpoint because not all subtypes have that field. We query each type with\n * its own state field, then merge and sort. (PAN-168)\n */\n async listIssues(filters?: IssueFilters): Promise<Issue[]> {\n if (process.env.DEBUG?.includes('rally')) {\n console.debug('[Rally] Query filters:', JSON.stringify(filters));\n }\n\n const limit = filters?.limit ?? 50;\n\n // Extract ObjectID from project ref for explicit query scoping\n // e.g., \"/project/822404704163\" → \"822404704163\"\n let projectObjectId: string | undefined;\n if (this.project) {\n const match = this.project.match(/\\/project\\/(\\d+)/);\n if (match) projectObjectId = match[1];\n }\n\n const queries = QUERYABLE_TYPES.map(async (artifactType) => {\n const queryString = this.buildQueryStringForType(filters, artifactType, projectObjectId);\n\n if (process.env.DEBUG?.includes('rally')) {\n console.debug(`[Rally] ${artifactType.type} query:`, queryString);\n }\n\n const query: any = {\n type: artifactType.type,\n fetch: FETCH_FIELDS,\n limit,\n query: queryString,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n if (this.project) {\n query.project = this.project;\n query.projectScopeDown = true;\n }\n\n try {\n const result = await this.queryRally(query);\n return result.Results.map((artifact: any) => this.normalizeIssue(artifact));\n } catch (error: any) {\n if (error.message?.includes('Unauthorized') || error.message?.includes('401')) {\n throw new TrackerAuthError('rally' as TrackerType, 'Invalid API key or insufficient permissions');\n }\n // Log and skip individual type failures so other types still return\n if (process.env.DEBUG?.includes('rally')) {\n console.debug(`[Rally] Failed to query ${artifactType.type}:`, error.message);\n }\n return [];\n }\n });\n\n const results = await Promise.all(queries);\n const allIssues = results.flat();\n\n // Sort by most recently updated first, then apply overall limit\n allIssues.sort(\n (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()\n );\n\n return allIssues.slice(0, limit);\n }\n\n async getIssue(id: string): Promise<Issue> {\n try {\n // Rally FormattedIDs look like: US123, DE456, TA789, F012\n const query: any = {\n type: 'artifact',\n fetch: [\n 'FormattedID',\n 'Name',\n 'Description',\n 'ScheduleState',\n 'State',\n 'Tags',\n 'Owner',\n 'Priority',\n 'DueDate',\n 'CreationDate',\n 'LastUpdateDate',\n 'Parent',\n '_type',\n ],\n query: `(FormattedID = \"${id}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n\n if (!result.Results || result.Results.length === 0) {\n throw new IssueNotFoundError(id, 'rally' as TrackerType);\n }\n\n return this.normalizeIssue(result.Results[0]);\n } catch (error: any) {\n if (error instanceof IssueNotFoundError) throw error;\n throw new IssueNotFoundError(id, 'rally' as TrackerType);\n }\n }\n\n async updateIssue(id: string, update: IssueUpdate): Promise<Issue> {\n const issue = await this.getIssue(id);\n\n // Get the Rally object reference\n const query: any = {\n type: 'artifact',\n fetch: ['ObjectID', '_ref', '_type'],\n query: `(FormattedID = \"${id}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n if (!result.Results || result.Results.length === 0) {\n throw new IssueNotFoundError(id, 'rally' as TrackerType);\n }\n\n const artifact = result.Results[0];\n const updatePayload: Record<string, unknown> = {};\n\n if (update.title !== undefined) {\n updatePayload.Name = update.title;\n }\n if (update.description !== undefined) {\n updatePayload.Description = update.description;\n }\n if (update.state !== undefined) {\n const artifactType = (artifact._type || '').toLowerCase();\n const kind = artifactType.startsWith('portfolioitem') ? 'feature'\n : artifactType === 'defect' ? 'defect' : 'story';\n const rallyState = this.reverseMapState(update.state, kind);\n if (kind === 'story') {\n updatePayload.ScheduleState = rallyState;\n } else {\n updatePayload.State = rallyState;\n }\n }\n if (update.priority !== undefined) {\n updatePayload.Priority = REVERSE_PRIORITY_MAP[update.priority] || 'Normal';\n }\n if (update.dueDate !== undefined) {\n updatePayload.DueDate = update.dueDate;\n }\n\n if (Object.keys(updatePayload).length > 0) {\n await this.updateRally(artifact._type.toLowerCase(), artifact._ref, updatePayload);\n }\n\n return this.getIssue(id);\n }\n\n async createIssue(newIssue: NewIssue): Promise<Issue> {\n if (!this.project && !newIssue.team) {\n throw new Error('Project is required to create an issue. Set it in config or provide team field.');\n }\n\n const project = newIssue.team || this.project;\n\n // Default to HierarchicalRequirement (User Story) for new issues\n const createPayload: Record<string, unknown> = {\n Name: newIssue.title,\n Description: newIssue.description || '',\n Project: project,\n };\n\n if (newIssue.priority !== undefined) {\n createPayload.Priority = REVERSE_PRIORITY_MAP[newIssue.priority] || 'Normal';\n }\n if (newIssue.dueDate) {\n createPayload.DueDate = newIssue.dueDate;\n }\n if (this.workspace) {\n createPayload.Workspace = this.workspace;\n }\n\n const result = await this.createRally('hierarchicalrequirement', createPayload);\n\n // Fetch the created issue to return normalized format\n return this.getIssue(result.Object.FormattedID);\n }\n\n async getComments(issueId: string): Promise<Comment[]> {\n const issue = await this.getIssue(issueId);\n\n // Get the Rally object to find its Discussion\n const query: any = {\n type: 'artifact',\n fetch: ['ObjectID', '_ref', 'Discussion'],\n query: `(FormattedID = \"${issueId}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n if (!result.Results || result.Results.length === 0) {\n return [];\n }\n\n const artifact = result.Results[0];\n if (!artifact.Discussion) {\n return [];\n }\n\n // Query ConversationPosts for this Discussion\n const postsQuery: any = {\n type: 'conversationpost',\n fetch: ['ObjectID', 'Text', 'User', 'CreationDate', 'PostNumber'],\n query: `(Discussion = \"${artifact.Discussion._ref}\")`,\n order: 'PostNumber',\n };\n\n const postsResult = await this.queryRally(postsQuery);\n\n return (postsResult.Results || []).map((post: any) => ({\n id: post.ObjectID,\n issueId,\n body: post.Text || '',\n author: post.User?._refObjectName || 'Unknown',\n createdAt: post.CreationDate,\n updatedAt: post.CreationDate, // Rally doesn't track comment updates separately\n }));\n }\n\n async addComment(issueId: string, body: string): Promise<Comment> {\n // Get the Rally object to find its Discussion\n const query: any = {\n type: 'artifact',\n fetch: ['ObjectID', '_ref', 'Discussion'],\n query: `(FormattedID = \"${issueId}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n if (!result.Results || result.Results.length === 0) {\n throw new IssueNotFoundError(issueId, 'rally' as TrackerType);\n }\n\n const artifact = result.Results[0];\n\n // If no Discussion exists, create one\n let discussionRef = artifact.Discussion?._ref;\n if (!discussionRef) {\n const discussionResult = await this.createRally('conversationpost', {\n Artifact: artifact._ref,\n Text: body,\n });\n\n return {\n id: discussionResult.Object.ObjectID,\n issueId,\n body,\n author: 'Panopticon',\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n }\n\n // Add a post to existing Discussion\n const postResult = await this.createRally('conversationpost', {\n Artifact: artifact._ref,\n Text: body,\n });\n\n return {\n id: postResult.Object.ObjectID,\n issueId,\n body,\n author: 'Panopticon',\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n }\n\n async transitionIssue(id: string, state: IssueState): Promise<void> {\n await this.updateIssue(id, { state });\n }\n\n async linkPR(issueId: string, prUrl: string): Promise<void> {\n // Add a comment with the PR link\n await this.addComment(issueId, `Linked Pull Request: ${prUrl}`);\n }\n\n // Private helper methods\n\n /**\n * Build a Rally WSAPI query string for a specific artifact type.\n *\n * Each artifact type has its own state field:\n * - HierarchicalRequirement: ScheduleState (Defined, In-Progress, Completed, Accepted)\n * - Defect: State (Submitted, Open, Fixed, Closed)\n * - Task: State (Defined, In-Progress, Completed)\n *\n * Rally WSAPI v2.0 requires binary-nested AND/OR with outer parentheses.\n * (PAN-166, PAN-168)\n */\n private buildQueryStringForType(\n filters: IssueFilters | undefined,\n artifactType: ArtifactTypeQuery,\n projectObjectId?: string,\n ): string {\n const conditions: string[] = [];\n\n // Explicit project scoping — more reliable than WSAPI project param alone\n if (projectObjectId) {\n conditions.push(`(Project.ObjectID = \"${projectObjectId}\")`);\n }\n\n if (filters?.state && !filters.includeClosed) {\n const kind = artifactType.type.startsWith('portfolioitem') ? 'feature'\n : artifactType.type === 'defect' ? 'defect' : 'story';\n const rallyState = this.reverseMapState(filters.state, kind);\n conditions.push(`(${artifactType.stateField} = \"${rallyState}\")`);\n }\n\n if (!filters?.includeClosed) {\n // Exclude closed states for this specific artifact type\n const closedConditions = artifactType.closedStates.map(\n (state) => `(${artifactType.stateField} != \"${state}\")`\n );\n // Rally WSAPI only supports binary AND — nest into pairs\n const closedExpr = closedConditions.reduce(\n (acc, cond) => (acc ? `(${acc} AND ${cond})` : cond),\n '',\n );\n conditions.push(closedExpr);\n }\n\n if (filters?.assignee) {\n conditions.push(`(Owner.Name contains \"${filters.assignee}\")`);\n }\n\n if (filters?.labels && filters.labels.length > 0) {\n const labelConditions = filters.labels.map(\n (label) => `(Tags.Name contains \"${label}\")`\n );\n // Rally WSAPI only supports binary AND — nest into pairs\n const labelExpr = labelConditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, '');\n conditions.push(labelExpr);\n }\n\n if (filters?.query) {\n conditions.push(`((Name contains \"${filters.query}\") OR (Description contains \"${filters.query}\"))`);\n }\n\n // Rally WSAPI only supports binary (expr AND expr) — reduce into nested pairs\n return conditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, '');\n }\n\n private normalizeIssue(rallyArtifact: any): Issue {\n // Determine state from ScheduleState (User Stories, Tasks) or State (Defects)\n const stateValue = rallyArtifact.ScheduleState || rallyArtifact.State || 'Defined';\n const state = this.mapState(stateValue);\n\n // Extract tags — ensure all entries are strings\n const labels: string[] = [];\n if (rallyArtifact.Tags && rallyArtifact.Tags._tagsNameArray) {\n for (const tag of rallyArtifact.Tags._tagsNameArray) {\n if (typeof tag === 'string') {\n labels.push(tag);\n } else if (tag?.Name) {\n labels.push(tag.Name);\n }\n }\n }\n\n // Map priority\n const priority = rallyArtifact.Priority\n ? PRIORITY_MAP[rallyArtifact.Priority] ?? 2\n : undefined;\n\n // Use ObjectID if available, fall back to FormattedID\n const objectId = rallyArtifact.ObjectID || rallyArtifact.FormattedID;\n const artifactType = rallyArtifact._type || 'artifact';\n\n // Build URL - Rally's web UI detail path\n const baseUrl = this.restApi.server.replace('/slm/webservice/', '');\n const url = `${baseUrl}/#/detail/${artifactType.toLowerCase()}/${objectId}`;\n\n // Resolve parent reference — try FormattedID first, fall back to _refObjectName\n let parentRef: string | undefined;\n if (rallyArtifact.Parent) {\n if (rallyArtifact.Parent.FormattedID) {\n parentRef = rallyArtifact.Parent.FormattedID;\n } else if (rallyArtifact.Parent._refObjectName) {\n parentRef = rallyArtifact.Parent._refObjectName;\n }\n }\n\n return {\n id: String(objectId),\n ref: rallyArtifact.FormattedID,\n title: rallyArtifact.Name || '',\n description: rallyArtifact.Description || '',\n state,\n labels,\n assignee: rallyArtifact.Owner?._refObjectName,\n url,\n tracker: 'rally' as TrackerType,\n priority,\n dueDate: rallyArtifact.DueDate,\n createdAt: rallyArtifact.CreationDate,\n updatedAt: rallyArtifact.LastUpdateDate,\n parentRef,\n artifactType,\n };\n }\n\n private mapState(rallyState: string): IssueState {\n return STATE_MAP[rallyState] ?? 'open';\n }\n\n private reverseMapState(state: IssueState, kind: 'story' | 'defect' | 'feature' = 'story'): string {\n if (kind === 'feature') {\n // Features / PortfolioItems use State: Discovering, Developing, Done\n switch (state) {\n case 'open': return 'Discovering';\n case 'in_progress': return 'Developing';\n case 'closed': return 'Done';\n default: return 'Discovering';\n }\n }\n if (kind === 'defect') {\n // Defects use State: Submitted, Open, Fixed, Closed\n switch (state) {\n case 'open': return 'Submitted';\n case 'in_progress': return 'Open';\n case 'closed': return 'Closed';\n default: return 'Submitted';\n }\n }\n // User Stories / Tasks use ScheduleState: Defined, In-Progress, Completed\n switch (state) {\n case 'open': return 'Defined';\n case 'in_progress': return 'In-Progress';\n case 'closed': return 'Completed';\n default: return 'Defined';\n }\n }\n\n // Rally API wrapper methods\n private async queryRally(queryConfig: any): Promise<any> {\n const result = await this.restApi.query(queryConfig);\n // Extract Results from WSAPI response format\n return {\n Results: result.QueryResult.Results,\n TotalResultCount: result.QueryResult.TotalResultCount,\n };\n }\n\n private async createRally(type: string, data: any): Promise<any> {\n const result = await this.restApi.create({\n type,\n data,\n fetch: ['FormattedID', 'ObjectID', '_ref'],\n });\n // Extract Object from WSAPI response format\n return {\n Object: result.CreateResult.Object,\n };\n }\n\n private async updateRally(type: string, ref: string, data: any): Promise<any> {\n const result = await this.restApi.update({\n type,\n ref,\n data,\n fetch: ['FormattedID', 'ObjectID'],\n });\n // Extract Object from WSAPI response format\n return {\n Object: result.OperationResult.Object,\n };\n }\n}\n","/**\n * Rally WSAPI REST Client\n *\n * Thin wrapper around native fetch for Rally Web Services API v2.0.\n * Provides typed methods for query, create, and update operations.\n */\n\nexport interface RallyQueryConfig {\n type: string;\n fetch?: string[];\n query?: string;\n limit?: number;\n workspace?: string;\n project?: string;\n projectScopeDown?: boolean;\n order?: string;\n}\n\nexport interface RallyQueryResult {\n QueryResult: {\n Results: any[];\n TotalResultCount: number;\n Errors: string[];\n Warnings: string[];\n };\n}\n\nexport interface RallyCreateConfig {\n type: string;\n data: any;\n fetch?: string[];\n}\n\nexport interface RallyCreateResult {\n CreateResult: {\n Object: any;\n Errors: string[];\n Warnings: string[];\n };\n}\n\nexport interface RallyUpdateConfig {\n type: string;\n ref: string;\n data: any;\n fetch?: string[];\n}\n\nexport interface RallyUpdateResult {\n OperationResult: {\n Object: any;\n Errors: string[];\n Warnings: string[];\n };\n}\n\nexport interface RallyApiConfig {\n apiKey: string;\n server?: string;\n requestOptions?: {\n headers?: Record<string, string>;\n };\n}\n\nexport class RallyRestApi {\n private apiKey: string;\n public server: string;\n private customHeaders: Record<string, string>;\n\n constructor(config: RallyApiConfig) {\n this.apiKey = config.apiKey;\n this.server = config.server || 'https://rally1.rallydev.com';\n this.customHeaders = config.requestOptions?.headers || {};\n }\n\n /**\n * Query Rally artifacts\n */\n async query(config: RallyQueryConfig): Promise<RallyQueryResult> {\n const params = new URLSearchParams();\n\n if (config.query) {\n params.set('query', config.query);\n }\n\n if (config.fetch && config.fetch.length > 0) {\n params.set('fetch', config.fetch.join(','));\n }\n\n if (config.limit !== undefined) {\n params.set('pagesize', String(config.limit));\n }\n\n if (config.workspace) {\n params.set('workspace', config.workspace);\n }\n\n if (config.project) {\n params.set('project', config.project);\n if (config.projectScopeDown) {\n params.set('projectScopeDown', 'true');\n }\n }\n\n if (config.order) {\n params.set('order', config.order);\n }\n\n const url = `${this.server}/slm/webservice/v2.0/${config.type}?${params.toString()}`;\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'ZSESSIONID': this.apiKey,\n 'Content-Type': 'application/json',\n ...this.customHeaders,\n },\n });\n\n if (!response.ok) {\n if (response.status === 401) {\n throw new Error('Unauthorized: Invalid API key or insufficient permissions');\n }\n throw new Error(`Rally API query failed: ${response.status} ${response.statusText}`);\n }\n\n const result = await response.json() as RallyQueryResult;\n\n if (result.QueryResult.Errors && result.QueryResult.Errors.length > 0) {\n const errorDetail = result.QueryResult.Errors.join(', ');\n const queryDetail = config.query ? ` (Query: ${config.query})` : '';\n if (process.env.DEBUG?.includes('rally')) {\n console.error('[Rally WSAPI] Query failed:', { query: config.query, errors: result.QueryResult.Errors });\n }\n throw new Error(`Rally API query failed: ${errorDetail}${queryDetail}`);\n }\n\n return result;\n }\n\n /**\n * Create a Rally object\n */\n async create(config: RallyCreateConfig): Promise<RallyCreateResult> {\n const url = `${this.server}/slm/webservice/v2.0/${config.type}/create`;\n\n const body: any = {\n [config.type]: config.data,\n };\n\n const params = new URLSearchParams();\n if (config.fetch && config.fetch.length > 0) {\n params.set('fetch', config.fetch.join(','));\n }\n\n const finalUrl = params.toString() ? `${url}?${params.toString()}` : url;\n\n const response = await fetch(finalUrl, {\n method: 'POST',\n headers: {\n 'ZSESSIONID': this.apiKey,\n 'Content-Type': 'application/json',\n ...this.customHeaders,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n throw new Error(`Rally API create failed: ${response.status} ${response.statusText}`);\n }\n\n const result = await response.json() as RallyCreateResult;\n\n if (result.CreateResult.Errors && result.CreateResult.Errors.length > 0) {\n throw new Error(`Rally API create failed: ${result.CreateResult.Errors.join(', ')}`);\n }\n\n return result;\n }\n\n /**\n * Update a Rally object\n */\n async update(config: RallyUpdateConfig): Promise<RallyUpdateResult> {\n // Extract ObjectID from ref (e.g., \"/hierarchicalrequirement/12345\" -> \"12345\")\n const objectId = config.ref.split('/').pop();\n const url = `${this.server}/slm/webservice/v2.0/${config.type}/${objectId}`;\n\n const body: any = {\n [config.type]: config.data,\n };\n\n const params = new URLSearchParams();\n if (config.fetch && config.fetch.length > 0) {\n params.set('fetch', config.fetch.join(','));\n }\n\n const finalUrl = params.toString() ? `${url}?${params.toString()}` : url;\n\n const response = await fetch(finalUrl, {\n method: 'POST',\n headers: {\n 'ZSESSIONID': this.apiKey,\n 'Content-Type': 'application/json',\n ...this.customHeaders,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n throw new Error(`Rally API update failed: ${response.status} ${response.statusText}`);\n }\n\n const result = await response.json() as RallyUpdateResult;\n\n if (result.OperationResult.Errors && result.OperationResult.Errors.length > 0) {\n throw new Error(`Rally API update failed: ${result.OperationResult.Errors.join(', ')}`);\n }\n\n return result;\n }\n}\n","/**\n * Cross-Tracker Linking\n *\n * Manages links between issues in different trackers.\n * Links are stored in a local JSON file for persistence.\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { TrackerType } from './interface.js';\n\n// Link direction types\nexport type LinkDirection = 'blocks' | 'blocked_by' | 'related' | 'duplicate_of';\n\n// A single link between two issues\nexport interface TrackerLink {\n sourceIssueRef: string; // e.g., \"MIN-630\"\n sourceTracker: TrackerType;\n targetIssueRef: string; // e.g., \"#42\"\n targetTracker: TrackerType;\n direction: LinkDirection;\n createdAt: string; // ISO timestamp\n}\n\n// Storage format\ninterface LinkStore {\n version: 1;\n links: TrackerLink[];\n}\n\n/**\n * Parse an issue reference to extract tracker and ID\n * Examples:\n * \"#42\" -> { tracker: \"github\", ref: \"#42\" }\n * \"github#42\" -> { tracker: \"github\", ref: \"#42\" }\n * \"MIN-630\" -> { tracker: \"linear\", ref: \"MIN-630\" }\n * \"gitlab#15\" -> { tracker: \"gitlab\", ref: \"#15\" }\n */\nexport function parseIssueRef(ref: string): { tracker: TrackerType; ref: string } | null {\n // Explicit tracker prefix\n if (ref.startsWith('github#')) {\n return { tracker: 'github', ref: `#${ref.slice(7)}` };\n }\n if (ref.startsWith('gitlab#')) {\n return { tracker: 'gitlab', ref: `#${ref.slice(7)}` };\n }\n if (ref.startsWith('linear:')) {\n return { tracker: 'linear', ref: ref.slice(7) };\n }\n\n // GitHub-style refs (#number)\n if (/^#\\d+$/.test(ref)) {\n return { tracker: 'github', ref };\n }\n\n // Linear-style refs (XXX-123)\n if (/^[A-Z]+-\\d+$/i.test(ref)) {\n return { tracker: 'linear', ref: ref.toUpperCase() };\n }\n\n return null;\n}\n\n/**\n * Format an issue ref with tracker prefix for display\n */\nexport function formatIssueRef(ref: string, tracker: TrackerType): string {\n if (tracker === 'github') {\n return ref.startsWith('#') ? `github${ref}` : `github#${ref}`;\n }\n if (tracker === 'gitlab') {\n return ref.startsWith('#') ? `gitlab${ref}` : `gitlab#${ref}`;\n }\n return ref; // Linear refs are already unique\n}\n\n/**\n * Link Manager for cross-tracker issue linking\n */\nexport class LinkManager {\n private storePath: string;\n private store: LinkStore;\n\n constructor(storePath?: string) {\n this.storePath = storePath ?? join(homedir(), '.panopticon', 'links.json');\n this.store = this.load();\n }\n\n private load(): LinkStore {\n if (existsSync(this.storePath)) {\n try {\n const data = JSON.parse(readFileSync(this.storePath, 'utf-8'));\n if (data.version === 1) {\n return data;\n }\n } catch {\n // Fall through to default\n }\n }\n return { version: 1, links: [] };\n }\n\n private save(): void {\n const dir = join(this.storePath, '..');\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(this.storePath, JSON.stringify(this.store, null, 2));\n }\n\n /**\n * Add a link between two issues\n */\n addLink(\n source: { ref: string; tracker: TrackerType },\n target: { ref: string; tracker: TrackerType },\n direction: LinkDirection = 'related'\n ): TrackerLink {\n // Check if link already exists\n const existing = this.store.links.find(\n (l) =>\n l.sourceIssueRef === source.ref &&\n l.sourceTracker === source.tracker &&\n l.targetIssueRef === target.ref &&\n l.targetTracker === target.tracker\n );\n\n if (existing) {\n // Update direction if different\n if (existing.direction !== direction) {\n existing.direction = direction;\n this.save();\n }\n return existing;\n }\n\n const link: TrackerLink = {\n sourceIssueRef: source.ref,\n sourceTracker: source.tracker,\n targetIssueRef: target.ref,\n targetTracker: target.tracker,\n direction,\n createdAt: new Date().toISOString(),\n };\n\n this.store.links.push(link);\n this.save();\n return link;\n }\n\n /**\n * Remove a link between two issues\n */\n removeLink(\n source: { ref: string; tracker: TrackerType },\n target: { ref: string; tracker: TrackerType }\n ): boolean {\n const index = this.store.links.findIndex(\n (l) =>\n l.sourceIssueRef === source.ref &&\n l.sourceTracker === source.tracker &&\n l.targetIssueRef === target.ref &&\n l.targetTracker === target.tracker\n );\n\n if (index >= 0) {\n this.store.links.splice(index, 1);\n this.save();\n return true;\n }\n return false;\n }\n\n /**\n * Get all issues linked to a given issue\n */\n getLinkedIssues(ref: string, tracker: TrackerType): TrackerLink[] {\n return this.store.links.filter(\n (l) =>\n (l.sourceIssueRef === ref && l.sourceTracker === tracker) ||\n (l.targetIssueRef === ref && l.targetTracker === tracker)\n );\n }\n\n /**\n * Get all links (for debugging/admin)\n */\n getAllLinks(): TrackerLink[] {\n return [...this.store.links];\n }\n\n /**\n * Find linked issue in another tracker\n */\n findLinkedIssue(\n ref: string,\n sourceTracker: TrackerType,\n targetTracker: TrackerType\n ): string | null {\n // Check as source\n const asSource = this.store.links.find(\n (l) =>\n l.sourceIssueRef === ref &&\n l.sourceTracker === sourceTracker &&\n l.targetTracker === targetTracker\n );\n if (asSource) return asSource.targetIssueRef;\n\n // Check as target\n const asTarget = this.store.links.find(\n (l) =>\n l.targetIssueRef === ref &&\n l.targetTracker === sourceTracker &&\n l.sourceTracker === targetTracker\n );\n if (asTarget) return asTarget.sourceIssueRef;\n\n return null;\n }\n\n /**\n * Clear all links (for testing)\n */\n clear(): void {\n this.store.links = [];\n this.save();\n }\n}\n\n// Singleton instance\nlet _linkManager: LinkManager | null = null;\n\nexport function getLinkManager(): LinkManager {\n if (!_linkManager) {\n _linkManager = new LinkManager();\n }\n return _linkManager;\n}\n","/**\n * Issue Tracker Module\n *\n * Provides a unified interface for different issue tracking systems.\n */\n\n// Core interface and types\nexport type {\n IssueTracker,\n Issue,\n IssueFilters,\n IssueState,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\n\nexport {\n NotImplementedError,\n IssueNotFoundError,\n TrackerAuthError,\n} from './interface.js';\n\n// Tracker implementations\nexport { LinearTracker } from './linear.js';\nexport { GitHubTracker } from './github.js';\nexport { GitLabTracker } from './gitlab.js';\n\n// Factory functions\nexport type { TrackerConfig } from './factory.js';\nexport {\n createTracker,\n createTrackerFromConfig,\n getPrimaryTracker,\n getSecondaryTracker,\n getAllTrackers,\n} from './factory.js';\n\n// Cross-tracker linking\nexport type { TrackerLink, LinkDirection } from './linking.js';\nexport {\n LinkManager,\n getLinkManager,\n parseIssueRef,\n formatIssueRef,\n} from './linking.js';\n"],"mappings":"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","names":["existsSync","join","existsSync","mkdirSync","readdirSync","lstatSync","rmSync","join","STATE_MAP","readFileSync","existsSync","mkdirSync","join","homedir"]}
@@ -11,7 +11,7 @@ import {
11
11
  import {
12
12
  init_projects,
13
13
  projects_exports
14
- } from "./chunk-K45YD6A3.js";
14
+ } from "./chunk-SOL4WQJD.js";
15
15
  import {
16
16
  COSTS_DIR,
17
17
  PANOPTICON_HOME,
@@ -552,9 +552,9 @@ function recordWake(name, sessionId) {
552
552
  }
553
553
  async function spawnEphemeralSpecialist(projectKey, specialistType, task) {
554
554
  ensureProjectSpecialistDir(projectKey, specialistType);
555
- const { loadContextDigest } = await import("./specialist-context-JO4RB7UC.js");
555
+ const { loadContextDigest } = await import("./specialist-context-2G6YOU3M.js");
556
556
  const contextDigest = loadContextDigest(projectKey, specialistType);
557
- const { createRunLog: createRunLog2 } = await import("./specialist-logs-7P5EUIUT.js");
557
+ const { createRunLog: createRunLog2 } = await import("./specialist-logs-5OTCVAVH.js");
558
558
  const { runId, filePath: logFilePath } = createRunLog2(
559
559
  projectKey,
560
560
  specialistType,
@@ -619,7 +619,7 @@ echo "## Specialist completed task"
619
619
  }
620
620
  }
621
621
  async function buildTaskPrompt(projectKey, specialistType, task, contextDigest) {
622
- const { getSpecialistPromptOverride } = await import("./projects-ESIB34QQ.js");
622
+ const { getSpecialistPromptOverride } = await import("./projects-AMR3XQM4.js");
623
623
  const customPrompt = getSpecialistPromptOverride(projectKey, specialistType);
624
624
  let prompt = `# ${specialistType} Task - ${task.issueId}
625
625
 
@@ -777,7 +777,7 @@ async function terminateSpecialist(projectKey, specialistType) {
777
777
  console.error(`[specialist] Failed to kill tmux session ${tmuxSession}:`, error);
778
778
  }
779
779
  if (metadata.currentRun) {
780
- const { finalizeRunLog: finalizeRunLog2 } = await import("./specialist-logs-7P5EUIUT.js");
780
+ const { finalizeRunLog: finalizeRunLog2 } = await import("./specialist-logs-5OTCVAVH.js");
781
781
  try {
782
782
  finalizeRunLog2(projectKey, specialistType, metadata.currentRun, {
783
783
  status: metadata.lastRunStatus || "incomplete",
@@ -795,15 +795,15 @@ async function terminateSpecialist(projectKey, specialistType) {
795
795
  state: "suspended",
796
796
  lastActivity: (/* @__PURE__ */ new Date()).toISOString()
797
797
  });
798
- const { scheduleDigestGeneration } = await import("./specialist-context-JO4RB7UC.js");
798
+ const { scheduleDigestGeneration } = await import("./specialist-context-2G6YOU3M.js");
799
799
  scheduleDigestGeneration(projectKey, specialistType);
800
800
  scheduleLogCleanup(projectKey, specialistType);
801
801
  }
802
802
  function scheduleLogCleanup(projectKey, specialistType) {
803
803
  Promise.resolve().then(async () => {
804
804
  try {
805
- const { cleanupOldLogs: cleanupOldLogs2 } = await import("./specialist-logs-7P5EUIUT.js");
806
- const { getSpecialistRetention } = await import("./projects-ESIB34QQ.js");
805
+ const { cleanupOldLogs: cleanupOldLogs2 } = await import("./specialist-logs-5OTCVAVH.js");
806
+ const { getSpecialistRetention } = await import("./projects-AMR3XQM4.js");
807
807
  const retention = getSpecialistRetention(projectKey);
808
808
  const deleted = cleanupOldLogs2(projectKey, specialistType, { maxDays: retention.max_days, maxRuns: retention.max_runs });
809
809
  if (deleted > 0) {
@@ -1976,4 +1976,4 @@ export {
1976
1976
  getFeedbackStats,
1977
1977
  init_specialists
1978
1978
  };
1979
- //# sourceMappingURL=chunk-45PZFXJM.js.map
1979
+ //# sourceMappingURL=chunk-QB3W4NX6.js.map
@@ -15,6 +15,7 @@ __export(projects_exports, {
15
15
  createDefaultProjectsConfig: () => createDefaultProjectsConfig,
16
16
  extractTeamPrefix: () => extractTeamPrefix,
17
17
  findProjectByTeam: () => findProjectByTeam,
18
+ findProjectsByRallyProject: () => findProjectsByRallyProject,
18
19
  getProject: () => getProject,
19
20
  getSpecialistConfig: () => getSpecialistConfig,
20
21
  getSpecialistPromptOverride: () => getSpecialistPromptOverride,
@@ -208,6 +209,10 @@ function getSpecialistRetention(projectKey) {
208
209
  const config = getSpecialistConfig(projectKey);
209
210
  return config.retention;
210
211
  }
212
+ function findProjectsByRallyProject() {
213
+ const config = loadProjectsConfig();
214
+ return Object.entries(config.projects).filter(([, projectConfig]) => !!projectConfig.rally_project).map(([key, projectConfig]) => ({ key, config: projectConfig }));
215
+ }
211
216
  function getSpecialistPromptOverride(projectKey, specialistType) {
212
217
  const config = getSpecialistConfig(projectKey);
213
218
  return config.prompts[specialistType] || null;
@@ -247,8 +252,9 @@ export {
247
252
  initializeProjectsConfig,
248
253
  getSpecialistConfig,
249
254
  getSpecialistRetention,
255
+ findProjectsByRallyProject,
250
256
  getSpecialistPromptOverride,
251
257
  projects_exports,
252
258
  init_projects
253
259
  };
254
- //# sourceMappingURL=chunk-K45YD6A3.js.map
260
+ //# sourceMappingURL=chunk-SOL4WQJD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/projects.ts"],"sourcesContent":["/**\n * Project Registry - Multi-project support for Panopticon\n *\n * Maps Linear team prefixes and labels to project paths for workspace creation.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { PANOPTICON_HOME } from './paths.js';\n\nexport const PROJECTS_CONFIG_FILE = join(PANOPTICON_HOME, 'projects.yaml');\n\n/**\n * Issue routing rule - routes issues with certain labels to specific paths\n */\nexport interface IssueRoutingRule {\n labels?: string[];\n default?: boolean;\n path: string;\n}\n\n/**\n * Workspace configuration (imported from workspace-config.ts for full details)\n */\nexport interface WorkspaceConfig {\n type?: 'polyrepo' | 'monorepo';\n workspaces_dir?: string;\n repos?: Array<{ name: string; path: string; branch_prefix?: string }>;\n dns?: { domain: string; entries: string[]; sync_method?: 'wsl2hosts' | 'hosts_file' | 'dnsmasq' };\n ports?: Record<string, { range: [number, number] }>;\n docker?: { traefik?: string; compose_template?: string };\n database?: { seed_file?: string; container_name?: string; [key: string]: any };\n agent?: { template_dir: string; templates?: Array<{ source: string; target: string }>; symlinks?: string[] };\n env?: { template?: string; secrets_file?: string };\n services?: Array<{ name: string; path: string; start_command: string; docker_command?: string; health_url?: string; port?: number }>;\n}\n\n/**\n * Test configuration\n */\nexport interface TestConfig {\n type: string;\n path: string;\n command: string;\n container?: boolean;\n container_name?: string;\n env?: Record<string, string>;\n}\n\n/**\n * Specialist configuration for per-project specialists\n */\nexport interface SpecialistConfig {\n /** Number of recent runs to include in context digest (default: 5) */\n context_runs?: number;\n /** Model to use for generating context digests (null = same as specialist) */\n digest_model?: string | null;\n /** Log retention policy */\n retention?: {\n /** Maximum days to keep logs */\n max_days: number;\n /** Maximum number of runs to keep (whichever is more permissive) */\n max_runs: number;\n };\n /** Per-specialist prompt overrides */\n prompts?: {\n 'review-agent'?: string;\n 'test-agent'?: string;\n 'merge-agent'?: string;\n };\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n name: string;\n path: string;\n linear_team?: string;\n issue_routing?: IssueRoutingRule[];\n /** Workspace configuration */\n workspace?: WorkspaceConfig;\n /** Test configuration by name */\n tests?: Record<string, TestConfig>;\n /** Custom command to create workspaces (e.g., infra/new-feature for MYN) */\n workspace_command?: string;\n /** Custom command to remove workspaces */\n workspace_remove_command?: string;\n /** Rally project OID (e.g., \"/project/822404704163\") for per-project Rally scoping */\n rally_project?: string;\n /** Specialist agent configuration */\n specialists?: SpecialistConfig;\n}\n\n/**\n * Full projects configuration file\n */\nexport interface ProjectsConfig {\n projects: Record<string, ProjectConfig>;\n}\n\n/**\n * Resolved project info for workspace creation\n */\nexport interface ResolvedProject {\n projectKey: string;\n projectName: string;\n projectPath: string;\n linearTeam?: string;\n}\n\n/**\n * Load projects configuration from ~/.panopticon/projects.yaml\n */\nexport function loadProjectsConfig(): ProjectsConfig {\n if (!existsSync(PROJECTS_CONFIG_FILE)) {\n return { projects: {} };\n }\n\n try {\n const content = readFileSync(PROJECTS_CONFIG_FILE, 'utf-8');\n const config = parseYaml(content) as ProjectsConfig;\n return config || { projects: {} };\n } catch (error: any) {\n console.error(`Failed to parse projects.yaml: ${error.message}`);\n return { projects: {} };\n }\n}\n\n/**\n * Save projects configuration\n */\nexport function saveProjectsConfig(config: ProjectsConfig): void {\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const yaml = stringifyYaml(config, { indent: 2 });\n writeFileSync(PROJECTS_CONFIG_FILE, yaml, 'utf-8');\n}\n\n/**\n * Get a list of all registered projects\n */\nexport function listProjects(): Array<{ key: string; config: ProjectConfig }> {\n const config = loadProjectsConfig();\n return Object.entries(config.projects).map(([key, projectConfig]) => ({\n key,\n config: projectConfig,\n }));\n}\n\n/**\n * Add or update a project in the registry\n */\nexport function registerProject(key: string, projectConfig: ProjectConfig): void {\n const config = loadProjectsConfig();\n config.projects[key] = projectConfig;\n saveProjectsConfig(config);\n}\n\n/**\n * Remove a project from the registry\n */\nexport function unregisterProject(key: string): boolean {\n const config = loadProjectsConfig();\n if (config.projects[key]) {\n delete config.projects[key];\n saveProjectsConfig(config);\n return true;\n }\n return false;\n}\n\n/**\n * Extract Linear team prefix from an issue ID\n * E.g., \"MIN-123\" -> \"MIN\", \"PAN-456\" -> \"PAN\"\n */\nexport function extractTeamPrefix(issueId: string): string | null {\n const match = issueId.match(/^([A-Z]+)-\\d+$/i);\n return match ? match[1].toUpperCase() : null;\n}\n\n/**\n * Find project by Linear team prefix\n */\nexport function findProjectByTeam(teamPrefix: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n\n for (const [, projectConfig] of Object.entries(config.projects)) {\n if (projectConfig.linear_team?.toUpperCase() === teamPrefix.toUpperCase()) {\n return projectConfig;\n }\n }\n\n return null;\n}\n\n/**\n * Resolve the correct project path for an issue based on labels\n *\n * @param project - The project config\n * @param labels - Array of label names from the Linear issue\n * @returns The resolved path (may differ from project.path based on routing rules)\n */\nexport function resolveProjectPath(project: ProjectConfig, labels: string[] = []): string {\n if (!project.issue_routing || project.issue_routing.length === 0) {\n return project.path;\n }\n\n // Normalize labels to lowercase for comparison\n const normalizedLabels = labels.map(l => l.toLowerCase());\n\n // First, check label-based routing rules\n for (const rule of project.issue_routing) {\n if (rule.labels && rule.labels.length > 0) {\n const ruleLabels = rule.labels.map(l => l.toLowerCase());\n const hasMatch = ruleLabels.some(label => normalizedLabels.includes(label));\n if (hasMatch) {\n return rule.path;\n }\n }\n }\n\n // Then, find default rule\n for (const rule of project.issue_routing) {\n if (rule.default) {\n return rule.path;\n }\n }\n\n // Fall back to project path\n return project.path;\n}\n\n/**\n * Resolve project from an issue ID (and optional labels)\n *\n * @param issueId - Linear issue ID (e.g., \"MIN-123\")\n * @param labels - Optional array of label names\n * @returns Resolved project info or null if not found\n */\nexport function resolveProjectFromIssue(\n issueId: string,\n labels: string[] = []\n): ResolvedProject | null {\n const teamPrefix = extractTeamPrefix(issueId);\n if (!teamPrefix) {\n return null;\n }\n\n const config = loadProjectsConfig();\n\n // Find project by team prefix\n for (const [key, projectConfig] of Object.entries(config.projects)) {\n if (projectConfig.linear_team?.toUpperCase() === teamPrefix) {\n const resolvedPath = resolveProjectPath(projectConfig, labels);\n return {\n projectKey: key,\n projectName: projectConfig.name,\n projectPath: resolvedPath,\n linearTeam: projectConfig.linear_team,\n };\n }\n }\n\n return null;\n}\n\n/**\n * Get a project by key\n */\nexport function getProject(key: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n return config.projects[key] || null;\n}\n\n/**\n * Check if projects.yaml exists and has any projects\n */\nexport function hasProjects(): boolean {\n const config = loadProjectsConfig();\n return Object.keys(config.projects).length > 0;\n}\n\n/**\n * Create a default projects.yaml with example structure\n */\nexport function createDefaultProjectsConfig(): ProjectsConfig {\n const defaultConfig: ProjectsConfig = {\n projects: {\n // Example project - commented out in actual file\n },\n };\n\n return defaultConfig;\n}\n\n/**\n * Initialize projects.yaml with example configuration\n */\nexport function initializeProjectsConfig(): void {\n if (existsSync(PROJECTS_CONFIG_FILE)) {\n console.log(`Projects config already exists at ${PROJECTS_CONFIG_FILE}`);\n return;\n }\n\n const exampleYaml = `# Panopticon Project Registry\n# Maps Linear teams to project paths for workspace creation\n\nprojects:\n # Example: Mind Your Now project\n # myn:\n # name: \"Mind Your Now\"\n # path: /home/user/projects/myn\n # linear_team: MIN\n # issue_routing:\n # # Route docs/marketing issues to docs repo\n # - labels: [docs, marketing, seo, landing-pages]\n # path: /home/user/projects/myn/docs\n # # Default: main repo\n # - default: true\n # path: /home/user/projects/myn\n # specialists:\n # context_runs: 5\n # digest_model: null # Use same model as specialist\n # retention:\n # max_days: 30\n # max_runs: 50\n # prompts:\n # review-agent: |\n # Pay special attention to:\n # - Database migration safety\n # - API backward compatibility\n\n # Example: Panopticon itself\n # panopticon:\n # name: \"Panopticon\"\n # path: /home/user/projects/panopticon\n # linear_team: PAN\n`;\n\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(PROJECTS_CONFIG_FILE, exampleYaml, 'utf-8');\n console.log(`Created example projects config at ${PROJECTS_CONFIG_FILE}`);\n}\n\n/**\n * Default specialist configuration values\n */\nconst DEFAULT_SPECIALIST_CONFIG: Required<SpecialistConfig> = {\n context_runs: 5,\n digest_model: null,\n retention: {\n max_days: 30,\n max_runs: 50,\n },\n prompts: {},\n};\n\n/**\n * Get specialist configuration for a project with defaults\n *\n * @param projectKey - Project key\n * @returns Specialist config with defaults applied\n */\nexport function getSpecialistConfig(projectKey: string): Required<SpecialistConfig> {\n const project = getProject(projectKey);\n\n if (!project || !project.specialists) {\n return DEFAULT_SPECIALIST_CONFIG;\n }\n\n return {\n context_runs: project.specialists.context_runs ?? DEFAULT_SPECIALIST_CONFIG.context_runs,\n digest_model: project.specialists.digest_model ?? DEFAULT_SPECIALIST_CONFIG.digest_model,\n retention: {\n max_days: project.specialists.retention?.max_days ?? DEFAULT_SPECIALIST_CONFIG.retention.max_days,\n max_runs: project.specialists.retention?.max_runs ?? DEFAULT_SPECIALIST_CONFIG.retention.max_runs,\n },\n prompts: project.specialists.prompts ?? DEFAULT_SPECIALIST_CONFIG.prompts,\n };\n}\n\n/**\n * Get retention policy for a project's specialists\n *\n * @param projectKey - Project key\n * @returns Retention policy\n */\nexport function getSpecialistRetention(projectKey: string): { max_days: number; max_runs: number } {\n const config = getSpecialistConfig(projectKey);\n return config.retention;\n}\n\n/**\n * Find all projects that have a rally_project configured.\n * Returns array of { key, config } for projects with Rally project OIDs.\n */\nexport function findProjectsByRallyProject(): Array<{ key: string; config: ProjectConfig }> {\n const config = loadProjectsConfig();\n return Object.entries(config.projects)\n .filter(([, projectConfig]) => !!projectConfig.rally_project)\n .map(([key, projectConfig]) => ({ key, config: projectConfig }));\n}\n\n/**\n * Get custom prompt override for a specialist (if configured)\n *\n * @param projectKey - Project key\n * @param specialistType - Specialist type\n * @returns Custom prompt or null if not configured\n */\nexport function getSpecialistPromptOverride(\n projectKey: string,\n specialistType: 'review-agent' | 'test-agent' | 'merge-agent'\n): string | null {\n const config = getSpecialistConfig(projectKey);\n return config.prompts[specialistType] || null;\n}\n"],"mappings":"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","names":[]}
package/dist/cli/index.js CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  recordWake,
26
26
  wakeSpecialistOrQueue,
27
27
  wakeSpecialistWithTask
28
- } from "../chunk-45PZFXJM.js";
28
+ } from "../chunk-QB3W4NX6.js";
29
29
  import {
30
30
  addAlias,
31
31
  cleanOldBackups,
@@ -40,7 +40,7 @@ import {
40
40
  planSync,
41
41
  restoreBackup,
42
42
  syncHooks
43
- } from "../chunk-K72CCKWP.js";
43
+ } from "../chunk-77Q3GY6S.js";
44
44
  import {
45
45
  init_remote_agents,
46
46
  isRemoteAgentRunning,
@@ -119,7 +119,7 @@ import {
119
119
  registerProject,
120
120
  resolveProjectFromIssue,
121
121
  unregisterProject
122
- } from "../chunk-K45YD6A3.js";
122
+ } from "../chunk-SOL4WQJD.js";
123
123
  import {
124
124
  createExeProvider,
125
125
  init_exe_provider
@@ -1289,7 +1289,7 @@ async function handleRemoteWorkspace(issueId, options, spinner) {
1289
1289
  if (!remoteMetadata) {
1290
1290
  spinner.text = "Remote workspace not found, creating...";
1291
1291
  try {
1292
- const { createRemoteWorkspace: createRemoteWorkspace2 } = await import("../remote-workspace-WZVEDYQA.js");
1292
+ const { createRemoteWorkspace: createRemoteWorkspace2 } = await import("../remote-workspace-V57IZJRC.js");
1293
1293
  remoteMetadata = await createRemoteWorkspace2(issueId, { spinner });
1294
1294
  } catch (error) {
1295
1295
  spinner.fail(`Failed to create remote workspace: ${error.message}`);
@@ -11522,7 +11522,7 @@ import { promisify as promisify12 } from "util";
11522
11522
  var execAsync12 = promisify12(exec12);
11523
11523
  async function listLogsCommand(project2, type, options) {
11524
11524
  try {
11525
- const { listRunLogs } = await import("../specialist-logs-7P5EUIUT.js");
11525
+ const { listRunLogs } = await import("../specialist-logs-5OTCVAVH.js");
11526
11526
  const limit = options.limit ? parseInt(options.limit) : 10;
11527
11527
  const runs = listRunLogs(project2, type, { limit });
11528
11528
  if (options.json) {
@@ -11567,7 +11567,7 @@ View a specific run: pan specialists logs ${project2} ${type} <runId>
11567
11567
  }
11568
11568
  async function viewLogCommand(project2, type, runId, options) {
11569
11569
  try {
11570
- const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-7P5EUIUT.js");
11570
+ const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-5OTCVAVH.js");
11571
11571
  const content = getRunLog(project2, type, runId);
11572
11572
  if (!content) {
11573
11573
  console.error(`\u274C Run log not found: ${runId}`);
@@ -11591,8 +11591,8 @@ async function viewLogCommand(project2, type, runId, options) {
11591
11591
  }
11592
11592
  async function tailLogCommand(project2, type) {
11593
11593
  try {
11594
- const { getRunLogPath } = await import("../specialist-logs-7P5EUIUT.js");
11595
- const { getProjectSpecialistMetadata } = await import("../specialists-B4QAT244.js");
11594
+ const { getRunLogPath } = await import("../specialist-logs-5OTCVAVH.js");
11595
+ const { getProjectSpecialistMetadata } = await import("../specialists-H5QEVLNN.js");
11596
11596
  const metadata = getProjectSpecialistMetadata(project2, type);
11597
11597
  if (!metadata.currentRun) {
11598
11598
  console.error(`\u274C No active run for ${project2}/${type}`);
@@ -11661,7 +11661,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
11661
11661
  console.log(" Use --force to confirm.");
11662
11662
  process.exit(1);
11663
11663
  }
11664
- const { cleanupAllLogs } = await import("../specialist-logs-7P5EUIUT.js");
11664
+ const { cleanupAllLogs } = await import("../specialist-logs-5OTCVAVH.js");
11665
11665
  console.log("\u{1F9F9} Cleaning up old logs for all projects...\n");
11666
11666
  const results = cleanupAllLogs();
11667
11667
  console.log(`
@@ -11688,8 +11688,8 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
11688
11688
  console.log(" Use --force to confirm.");
11689
11689
  process.exit(1);
11690
11690
  }
11691
- const { cleanupOldLogs } = await import("../specialist-logs-7P5EUIUT.js");
11692
- const { getSpecialistRetention } = await import("../projects-ESIB34QQ.js");
11691
+ const { cleanupOldLogs } = await import("../specialist-logs-5OTCVAVH.js");
11692
+ const { getSpecialistRetention } = await import("../projects-AMR3XQM4.js");
11693
11693
  const retention = getSpecialistRetention(projectOrAll);
11694
11694
  console.log(`\u{1F9F9} Cleaning up old logs for ${projectOrAll}/${type}...`);
11695
11695
  console.log(` Retention: ${retention.max_days} days or ${retention.max_runs} runs
@@ -12426,6 +12426,9 @@ async function projectAddCommand(projectPath, options = {}) {
12426
12426
  if (linearTeam) {
12427
12427
  projectConfig.linear_team = linearTeam.toUpperCase();
12428
12428
  }
12429
+ if (options.rallyProject) {
12430
+ projectConfig.rally_project = options.rallyProject;
12431
+ }
12429
12432
  registerProject(key, projectConfig);
12430
12433
  console.log(chalk45.green(`\u2713 Added project: ${name}`));
12431
12434
  console.log(chalk45.dim(` Key: ${key}`));
@@ -12433,6 +12436,9 @@ async function projectAddCommand(projectPath, options = {}) {
12433
12436
  if (linearTeam) {
12434
12437
  console.log(chalk45.dim(` Linear team: ${linearTeam}`));
12435
12438
  }
12439
+ if (options.rallyProject) {
12440
+ console.log(chalk45.dim(` Rally project: ${options.rallyProject}`));
12441
+ }
12436
12442
  console.log("");
12437
12443
  const hasDevcontainer = existsSync42(join40(fullPath, ".devcontainer"));
12438
12444
  const hasInfra = existsSync42(join40(fullPath, "infra"));
@@ -12543,6 +12549,9 @@ async function projectListCommand(options = {}) {
12543
12549
  if (config2.linear_team) {
12544
12550
  console.log(` ${chalk45.cyan(`Linear: ${config2.linear_team}`)}`);
12545
12551
  }
12552
+ if (config2.rally_project) {
12553
+ console.log(` ${chalk45.cyan(`Rally: ${config2.rally_project}`)}`);
12554
+ }
12546
12555
  if (config2.issue_routing && config2.issue_routing.length > 0) {
12547
12556
  console.log(` ${chalk45.dim(`Routes: ${config2.issue_routing.length} rules`)}`);
12548
12557
  }
@@ -12611,6 +12620,9 @@ Project: ${foundKey}
12611
12620
  if (found.linear_team) {
12612
12621
  console.log(` Team: ${found.linear_team}`);
12613
12622
  }
12623
+ if (found.rally_project) {
12624
+ console.log(` Rally: ${found.rally_project}`);
12625
+ }
12614
12626
  if (found.issue_routing && found.issue_routing.length > 0) {
12615
12627
  console.log("\n " + chalk45.bold("Routing Rules:"));
12616
12628
  for (const rule of found.issue_routing) {
@@ -14670,7 +14682,7 @@ program.command("down").description("Stop dashboard (and Traefik if enabled)").o
14670
14682
  console.log("");
14671
14683
  });
14672
14684
  var project = program.command("project").description("Project registry for multi-project workspace support");
14673
- project.command("add <path>").description("Register a project with Panopticon").option("--name <name>", "Project name").option("--type <type>", "Project type (standalone/monorepo)", "standalone").option("--linear-team <team>", "Linear team prefix (e.g., MIN, PAN)").action(projectAddCommand);
14685
+ project.command("add <path>").description("Register a project with Panopticon").option("--name <name>", "Project name").option("--type <type>", "Project type (standalone/monorepo)", "standalone").option("--linear-team <team>", "Linear team prefix (e.g., MIN, PAN)").option("--rally-project <oid>", "Rally project OID (e.g., /project/822404704163)").action(projectAddCommand);
14674
14686
  project.command("list").description("List all registered projects").option("--json", "Output as JSON").action(projectListCommand);
14675
14687
  project.command("show <key>").description("Show details for a specific project").action(projectShowCommand);
14676
14688
  project.command("remove <nameOrPath>").description("Remove a project from the registry").action(projectRemoveCommand);