panopticon-cli 0.5.0 → 0.5.1

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 (55) hide show
  1. package/dist/{agents-E43Y3HNU.js → agents-5OPQKM5K.js} +4 -3
  2. package/dist/{chunk-OMNXYPXC.js → chunk-2V4NF7J2.js} +14 -1
  3. package/dist/chunk-2V4NF7J2.js.map +1 -0
  4. package/dist/{chunk-WQG2TYCB.js → chunk-4YSYJ4HM.js} +2 -2
  5. package/dist/{chunk-NTO3EDB3.js → chunk-76F6DSVS.js} +47 -8
  6. package/dist/chunk-76F6DSVS.js.map +1 -0
  7. package/dist/{chunk-GR6ZZMCX.js → chunk-F5555J3A.js} +18 -1
  8. package/dist/chunk-F5555J3A.js.map +1 -0
  9. package/dist/{chunk-PPRFKTVC.js → chunk-NLQRED36.js} +2 -2
  10. package/dist/{chunk-AAFQANKW.js → chunk-OWHXCGVO.js} +29 -29
  11. package/dist/chunk-OWHXCGVO.js.map +1 -0
  12. package/dist/{chunk-HZT2AOPN.js → chunk-VHKSS7QX.js} +28 -5
  13. package/dist/chunk-VHKSS7QX.js.map +1 -0
  14. package/dist/{chunk-GFP3PIPB.js → chunk-YGJ54GW2.js} +1 -1
  15. package/dist/chunk-YGJ54GW2.js.map +1 -0
  16. package/dist/cli/index.js +401 -318
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/dashboard/public/assets/{index-DQHkwvvJ.js → index-Ce6q21Fm.js} +135 -135
  19. package/dist/dashboard/public/assets/{index-BxpjweAL.css → index-NzpI0ItZ.css} +1 -1
  20. package/dist/dashboard/public/index.html +2 -2
  21. package/dist/dashboard/server.js +1420 -1007
  22. package/dist/{feedback-writer-LVZ5TFYZ.js → feedback-writer-VRMMWWTW.js} +2 -2
  23. package/dist/git-utils-I2UDKNZH.js +131 -0
  24. package/dist/git-utils-I2UDKNZH.js.map +1 -0
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.js +2 -2
  27. package/dist/{projects-JEIVIYC6.js → projects-CFX3RTDL.js} +4 -2
  28. package/dist/{remote-workspace-AHVHQEES.js → remote-workspace-7FPGF2RM.js} +2 -2
  29. package/dist/{review-status-EPFG4XM7.js → review-status-TDPSOU5J.js} +2 -2
  30. package/dist/{specialist-context-ZC6A4M3I.js → specialist-context-WGUUYDWY.js} +3 -3
  31. package/dist/{specialist-logs-KLGJCEUL.js → specialist-logs-XJB5TCKJ.js} +3 -3
  32. package/dist/{specialists-O4HWDJL5.js → specialists-5LBRHYFA.js} +3 -3
  33. package/dist/{traefik-QN7R5I6V.js → traefik-WFMQX2LY.js} +3 -3
  34. package/dist/{workspace-manager-IE4JL2JP.js → workspace-manager-E434Z45T.js} +2 -2
  35. package/package.json +1 -1
  36. package/scripts/record-cost-event.js +5 -5
  37. package/skills/pan-new-project/SKILL.md +304 -0
  38. package/dist/chunk-AAFQANKW.js.map +0 -1
  39. package/dist/chunk-GFP3PIPB.js.map +0 -1
  40. package/dist/chunk-GR6ZZMCX.js.map +0 -1
  41. package/dist/chunk-HZT2AOPN.js.map +0 -1
  42. package/dist/chunk-NTO3EDB3.js.map +0 -1
  43. package/dist/chunk-OMNXYPXC.js.map +0 -1
  44. /package/dist/{agents-E43Y3HNU.js.map → agents-5OPQKM5K.js.map} +0 -0
  45. /package/dist/{chunk-WQG2TYCB.js.map → chunk-4YSYJ4HM.js.map} +0 -0
  46. /package/dist/{chunk-PPRFKTVC.js.map → chunk-NLQRED36.js.map} +0 -0
  47. /package/dist/{feedback-writer-LVZ5TFYZ.js.map → feedback-writer-VRMMWWTW.js.map} +0 -0
  48. /package/dist/{projects-JEIVIYC6.js.map → projects-CFX3RTDL.js.map} +0 -0
  49. /package/dist/{remote-workspace-AHVHQEES.js.map → remote-workspace-7FPGF2RM.js.map} +0 -0
  50. /package/dist/{review-status-EPFG4XM7.js.map → review-status-TDPSOU5J.js.map} +0 -0
  51. /package/dist/{specialist-context-ZC6A4M3I.js.map → specialist-context-WGUUYDWY.js.map} +0 -0
  52. /package/dist/{specialist-logs-KLGJCEUL.js.map → specialist-logs-XJB5TCKJ.js.map} +0 -0
  53. /package/dist/{specialists-O4HWDJL5.js.map → specialists-5LBRHYFA.js.map} +0 -0
  54. /package/dist/{traefik-QN7R5I6V.js.map → traefik-WFMQX2LY.js.map} +0 -0
  55. /package/dist/{workspace-manager-IE4JL2JP.js.map → workspace-manager-E434Z45T.js.map} +0 -0
@@ -18,11 +18,12 @@ import {
18
18
  saveSessionId,
19
19
  spawnAgent,
20
20
  stopAgent
21
- } from "./chunk-HZT2AOPN.js";
21
+ } from "./chunk-VHKSS7QX.js";
22
22
  import "./chunk-FTCPTHIJ.js";
23
- import "./chunk-NTO3EDB3.js";
23
+ import "./chunk-76F6DSVS.js";
24
24
  import "./chunk-HJSM6E6U.js";
25
25
  import "./chunk-FQ66DECN.js";
26
+ import "./chunk-2V4NF7J2.js";
26
27
  import "./chunk-CFCUOV3Q.js";
27
28
  import "./chunk-ZTFNYOC7.js";
28
29
  import "./chunk-ZHC57RCV.js";
@@ -47,4 +48,4 @@ export {
47
48
  spawnAgent,
48
49
  stopAgent
49
50
  };
50
- //# sourceMappingURL=agents-E43Y3HNU.js.map
51
+ //# sourceMappingURL=agents-5OPQKM5K.js.map
@@ -14,6 +14,7 @@ __export(projects_exports, {
14
14
  PROJECTS_CONFIG_FILE: () => PROJECTS_CONFIG_FILE,
15
15
  createDefaultProjectsConfig: () => createDefaultProjectsConfig,
16
16
  extractTeamPrefix: () => extractTeamPrefix,
17
+ findProjectByPath: () => findProjectByPath,
17
18
  findProjectByTeam: () => findProjectByTeam,
18
19
  findProjectsByRallyProject: () => findProjectsByRallyProject,
19
20
  getProject: () => getProject,
@@ -88,6 +89,17 @@ function findProjectByTeam(teamPrefix) {
88
89
  }
89
90
  return null;
90
91
  }
92
+ function findProjectByPath(workspacePath) {
93
+ const config = loadProjectsConfig();
94
+ const normalizedTarget = resolve(workspacePath);
95
+ for (const [, projectConfig] of Object.entries(config.projects)) {
96
+ const normalizedProject = resolve(projectConfig.path);
97
+ if (normalizedTarget === normalizedProject || normalizedTarget.startsWith(normalizedProject + "/")) {
98
+ return projectConfig;
99
+ }
100
+ }
101
+ return null;
102
+ }
91
103
  function resolveProjectPath(project, labels = []) {
92
104
  if (!project.issue_routing || project.issue_routing.length === 0) {
93
105
  return project.path;
@@ -244,6 +256,7 @@ export {
244
256
  unregisterProject,
245
257
  extractTeamPrefix,
246
258
  findProjectByTeam,
259
+ findProjectByPath,
247
260
  resolveProjectPath,
248
261
  resolveProjectFromIssue,
249
262
  getProject,
@@ -257,4 +270,4 @@ export {
257
270
  projects_exports,
258
271
  init_projects
259
272
  };
260
- //# sourceMappingURL=chunk-OMNXYPXC.js.map
273
+ //# sourceMappingURL=chunk-2V4NF7J2.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 }>; copy_dirs?: 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 github_repo?: string; // e.g. \"owner/repo\"\n gitlab_repo?: string; // e.g. \"group/repo\"\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 * Find project by workspace path.\n * Matches any project whose root path is an ancestor of the given path.\n * Used to resolve the tracker (GitHub/GitLab) from a workspace directory.\n */\nexport function findProjectByPath(workspacePath: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n const normalizedTarget = resolve(workspacePath);\n\n for (const [, projectConfig] of Object.entries(config.projects)) {\n const normalizedProject = resolve(projectConfig.path);\n if (normalizedTarget === normalizedProject || normalizedTarget.startsWith(normalizedProject + '/')) {\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":[]}
@@ -13,7 +13,7 @@ import {
13
13
  init_github,
14
14
  init_gitlab,
15
15
  init_linear
16
- } from "./chunk-NTO3EDB3.js";
16
+ } from "./chunk-76F6DSVS.js";
17
17
  import {
18
18
  getDevrootPath,
19
19
  init_config
@@ -674,4 +674,4 @@ export {
674
674
  LinkManager,
675
675
  getLinkManager
676
676
  };
677
- //# sourceMappingURL=chunk-WQG2TYCB.js.map
677
+ //# sourceMappingURL=chunk-4YSYJ4HM.js.map
@@ -370,7 +370,43 @@ var init_github = __esm({
370
370
  };
371
371
  }
372
372
  async transitionIssue(id, state) {
373
- await this.updateIssue(id, { state });
373
+ const issueNumber = parseInt(id.replace(/^#/, ""), 10);
374
+ if (state === "in_progress") {
375
+ await this.ensureLabelExists("in-progress", "In progress", "0075ca");
376
+ await this.octokit.issues.addLabels({
377
+ owner: this.owner,
378
+ repo: this.repo,
379
+ issue_number: issueNumber,
380
+ labels: ["in-progress"]
381
+ });
382
+ } else {
383
+ const issue = await this.getIssue(id);
384
+ if (issue.labels?.includes("in-progress")) {
385
+ await this.octokit.issues.removeLabel({
386
+ owner: this.owner,
387
+ repo: this.repo,
388
+ issue_number: issueNumber,
389
+ name: "in-progress"
390
+ }).catch(() => {
391
+ });
392
+ }
393
+ await this.updateIssue(id, { state });
394
+ }
395
+ }
396
+ /** Ensure a label exists in the repo, creating it if needed. */
397
+ async ensureLabelExists(name, description, color) {
398
+ try {
399
+ await this.octokit.issues.getLabel({ owner: this.owner, repo: this.repo, name });
400
+ } catch {
401
+ await this.octokit.issues.createLabel({
402
+ owner: this.owner,
403
+ repo: this.repo,
404
+ name,
405
+ description,
406
+ color
407
+ }).catch(() => {
408
+ });
409
+ }
374
410
  }
375
411
  async linkPR(issueId, prUrl) {
376
412
  await this.addComment(
@@ -379,15 +415,16 @@ var init_github = __esm({
379
415
  );
380
416
  }
381
417
  normalizeIssue(ghIssue) {
418
+ const labels = ghIssue.labels.map(
419
+ (l) => typeof l === "string" ? l : l.name
420
+ );
382
421
  return {
383
422
  id: String(ghIssue.id),
384
423
  ref: `#${ghIssue.number}`,
385
424
  title: ghIssue.title,
386
425
  description: ghIssue.body ?? "",
387
- state: this.mapStateFromGitHub(ghIssue.state),
388
- labels: ghIssue.labels.map(
389
- (l) => typeof l === "string" ? l : l.name
390
- ),
426
+ state: this.mapStateFromGitHub(ghIssue.state, labels),
427
+ labels,
391
428
  assignee: ghIssue.assignee?.login,
392
429
  url: ghIssue.html_url,
393
430
  tracker: "github",
@@ -399,8 +436,10 @@ var init_github = __esm({
399
436
  updatedAt: ghIssue.updated_at
400
437
  };
401
438
  }
402
- mapStateFromGitHub(ghState) {
403
- return ghState === "closed" ? "closed" : "open";
439
+ mapStateFromGitHub(ghState, labels = []) {
440
+ if (ghState === "closed") return "closed";
441
+ if (labels.includes("in-progress")) return "in_progress";
442
+ return "open";
404
443
  }
405
444
  mapStateToGitHub(state) {
406
445
  if (!state) return "open";
@@ -597,4 +636,4 @@ export {
597
636
  getAllTrackers,
598
637
  init_factory
599
638
  };
600
- //# sourceMappingURL=chunk-NTO3EDB3.js.map
639
+ //# sourceMappingURL=chunk-76F6DSVS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/tracker/linear.ts","../src/lib/tracker/github.ts","../src/lib/tracker/gitlab.ts","../src/lib/tracker/factory.ts"],"sourcesContent":["/**\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 // Resolve the Linear issue directly (avoid normalizeIssue which may fail on SDK edge cases)\n let linearIssue: any;\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 if (isUuid) {\n linearIssue = await this.client.issue(id);\n } else {\n const results = await this.client.searchIssues(id, { first: 1 });\n if (results.nodes.length > 0) {\n linearIssue = results.nodes[0];\n } else {\n throw new IssueNotFoundError(id, 'linear');\n }\n }\n\n // Get workflow states for the issue's team\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 // Multiple states can share the same type (e.g., \"In Planning\", \"In Progress\", \"In Review\"\n // are all type \"started\"). Prefer the one with the lowest position (most basic/default state\n // for that type), which matches Linear's convention.\n const matchingStates = states.nodes\n .filter((s: any) => s.type === targetStateType)\n .sort((a: any, b: any) => (a.position ?? 0) - (b.position ?? 0));\n const targetState = matchingStates[0];\n if (!targetState) {\n throw new Error(`No state found matching type: ${targetStateType}`);\n }\n\n await this.client.updateIssue(linearIssue.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 const issueNumber = parseInt(id.replace(/^#/, ''), 10);\n\n if (state === 'in_progress') {\n // GitHub has no native \"in progress\" state — use a label instead.\n await this.ensureLabelExists('in-progress', 'In progress', '0075ca');\n await this.octokit.issues.addLabels({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n labels: ['in-progress'],\n });\n } else {\n // Remove in-progress label when moving to open or closed\n const issue = await this.getIssue(id);\n if (issue.labels?.includes('in-progress')) {\n await this.octokit.issues.removeLabel({\n owner: this.owner,\n repo: this.repo,\n issue_number: issueNumber,\n name: 'in-progress',\n }).catch(() => {/* label may not exist, ignore */});\n }\n await this.updateIssue(id, { state });\n }\n }\n\n /** Ensure a label exists in the repo, creating it if needed. */\n private async ensureLabelExists(name: string, description: string, color: string): Promise<void> {\n try {\n await this.octokit.issues.getLabel({ owner: this.owner, repo: this.repo, name });\n } catch {\n await this.octokit.issues.createLabel({\n owner: this.owner,\n repo: this.repo,\n name,\n description,\n color,\n }).catch(() => {/* race condition: another process created it first */});\n }\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 const labels: string[] = ghIssue.labels.map((l: any) =>\n typeof l === 'string' ? l : l.name\n );\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, labels),\n labels,\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, labels: string[] = []): IssueState {\n if (ghState === 'closed') return 'closed';\n if (labels.includes('in-progress')) return 'in_progress';\n return '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 { config: 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"],"mappings":";;;;;;;;;;;;;;;;;;AAMA,SAAS,oBAAoB;AAN7B,IAoBM,WAQO;AA5Bb;AAAA;AAAA;AAAA;AAiBA;AAGA,IAAM,YAAwC;AAAA,MAC5C,SAAS;AAAA,MACT,WAAW;AAAA,MACX,SAAS;AAAA,MACT,WAAW;AAAA,MACX,UAAU;AAAA,IACZ;AAEO,IAAM,gBAAN,MAA4C;AAAA,MACxC,OAAoB;AAAA,MACrB;AAAA,MACA;AAAA,MAER,YAAY,QAAgB,SAA6B;AACvD,YAAI,CAAC,QAAQ;AACX,gBAAM,IAAI,iBAAiB,UAAU,qBAAqB;AAAA,QAC5D;AACA,aAAK,SAAS,IAAI,aAAa,EAAE,OAAO,CAAC;AACzC,aAAK,cAAc,SAAS;AAAA,MAC9B;AAAA,MAEA,MAAM,WAAW,SAA0C;AACzD,cAAM,OAAO,SAAS,QAAQ,KAAK;AAEnC,cAAM,SAAS,MAAM,KAAK,OAAO,OAAO;AAAA,UACtC,OAAO,SAAS,SAAS;AAAA,UACzB,QAAQ;AAAA,YACN,MAAM,OAAO,EAAE,KAAK,EAAE,IAAI,KAAK,EAAE,IAAI;AAAA,YACrC,OAAO,SAAS,QACZ,EAAE,MAAM,EAAE,IAAI,KAAK,gBAAgB,QAAQ,KAAK,EAAE,EAAE,IACpD,SAAS,gBACP,SACA,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE;AAAA,YACnC,QAAQ,SAAS,QAAQ,SACrB,EAAE,MAAM,EAAE,IAAI,QAAQ,OAAO,EAAE,IAC/B;AAAA,YACJ,UAAU,SAAS,WACf,EAAE,MAAM,EAAE,oBAAoB,QAAQ,SAAS,EAAE,IACjD;AAAA,UACN;AAAA,QACF,CAAC;AAED,cAAM,SAAkB,CAAC;AACzB,mBAAW,QAAQ,OAAO,OAAO;AAC/B,iBAAO,KAAK,MAAM,KAAK,eAAe,IAAI,CAAC;AAAA,QAC7C;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,SAAS,IAA4B;AACzC,YAAI;AAEF,gBAAM,SAAS,kEAAkE,KAAK,EAAE;AAExF,cAAI,QAAQ;AAEV,kBAAM,QAAQ,MAAM,KAAK,OAAO,MAAM,EAAE;AACxC,gBAAI,OAAO;AACT,qBAAO,KAAK,eAAe,KAAK;AAAA,YAClC;AAAA,UACF,OAAO;AAEL,kBAAM,QAAQ,GAAG,MAAM,mBAAmB;AAC1C,gBAAI,OAAO;AACT,oBAAM,CAAC,EAAE,SAAS,MAAM,IAAI;AAE5B,oBAAM,UAAU,MAAM,KAAK,OAAO,aAAa,IAAI,EAAE,OAAO,EAAE,CAAC;AAC/D,kBAAI,QAAQ,MAAM,SAAS,GAAG;AAC5B,uBAAO,KAAK,eAAe,QAAQ,MAAM,CAAC,CAAC;AAAA,cAC7C;AAAA,YACF;AAAA,UACF;AAEA,gBAAM,IAAI,mBAAmB,IAAI,QAAQ;AAAA,QAC3C,SAAS,OAAO;AACd,cAAI,iBAAiB,mBAAoB,OAAM;AAC/C,gBAAM,IAAI,mBAAmB,IAAI,QAAQ;AAAA,QAC3C;AAAA,MACF;AAAA,MAEA,MAAM,YAAY,IAAY,QAAqC;AACjE,cAAM,QAAQ,MAAM,KAAK,SAAS,EAAE;AAEpC,cAAM,gBAAyC,CAAC;AAEhD,YAAI,OAAO,UAAU,QAAW;AAC9B,wBAAc,QAAQ,OAAO;AAAA,QAC/B;AACA,YAAI,OAAO,gBAAgB,QAAW;AACpC,wBAAc,cAAc,OAAO;AAAA,QACrC;AACA,YAAI,OAAO,aAAa,QAAW;AACjC,wBAAc,WAAW,OAAO;AAAA,QAClC;AACA,YAAI,OAAO,YAAY,QAAW;AAChC,wBAAc,UAAU,OAAO;AAAA,QACjC;AACA,YAAI,OAAO,UAAU,QAAW;AAG9B,gBAAM,KAAK,gBAAgB,IAAI,OAAO,KAAK;AAAA,QAC7C;AACA,YAAI,OAAO,WAAW,QAAW;AAAA,QAGjC;AAEA,YAAI,OAAO,KAAK,aAAa,EAAE,SAAS,GAAG;AACzC,gBAAM,KAAK,OAAO,YAAY,MAAM,IAAI,aAAa;AAAA,QACvD;AAEA,eAAO,KAAK,SAAS,EAAE;AAAA,MACzB;AAAA,MAEA,MAAM,YAAY,UAAoC;AACpD,cAAM,OAAO,SAAS,QAAQ,KAAK;AAEnC,YAAI,CAAC,MAAM;AACT,gBAAM,IAAI,MAAM,qCAAqC;AAAA,QACvD;AAGA,cAAM,QAAQ,MAAM,KAAK,OAAO,MAAM;AAAA,UACpC,QAAQ,EAAE,KAAK,EAAE,IAAI,KAAK,EAAE;AAAA,QAC9B,CAAC;AAED,YAAI,MAAM,MAAM,WAAW,GAAG;AAC5B,gBAAM,IAAI,MAAM,mBAAmB,IAAI,EAAE;AAAA,QAC3C;AAEA,cAAM,SAAS,MAAM,MAAM,CAAC,EAAE;AAE9B,cAAM,SAAS,MAAM,KAAK,OAAO,YAAY;AAAA,UAC3C;AAAA,UACA,OAAO,SAAS;AAAA,UAChB,aAAa,SAAS;AAAA,UACtB,UAAU,SAAS;AAAA,UACnB,SAAS,SAAS;AAAA,QACpB,CAAC;AAED,cAAM,UAAU,MAAM,OAAO;AAC7B,YAAI,CAAC,SAAS;AACZ,gBAAM,IAAI,MAAM,wBAAwB;AAAA,QAC1C;AAEA,eAAO,KAAK,eAAe,OAAO;AAAA,MACpC;AAAA,MAEA,MAAM,YAAY,SAAqC;AACrD,cAAM,QAAQ,MAAM,KAAK,OAAO,MAAM,OAAO;AAC7C,cAAM,WAAW,MAAM,MAAM,SAAS;AAEtC,eAAO,SAAS,MAAM,IAAI,CAAC,OAAO;AAAA,UAChC,IAAI,EAAE;AAAA,UACN;AAAA,UACA,MAAM,EAAE;AAAA,UACR,QAAQ,EAAE,MAAM,KAAK,CAAC,MAAM,GAAG,QAAQ,SAAS;AAAA;AAAA,UAChD,WAAW,EAAE,UAAU,YAAY;AAAA,UACnC,WAAW,EAAE,UAAU,YAAY;AAAA,QACrC,EAAE;AAAA,MACJ;AAAA,MAEA,MAAM,WAAW,SAAiB,MAAgC;AAChE,cAAM,SAAS,MAAM,KAAK,OAAO,cAAc;AAAA,UAC7C;AAAA,UACA;AAAA,QACF,CAAC;AAED,cAAM,UAAU,MAAM,OAAO;AAC7B,YAAI,CAAC,SAAS;AACZ,gBAAM,IAAI,MAAM,0BAA0B;AAAA,QAC5C;AAEA,eAAO;AAAA,UACL,IAAI,QAAQ;AAAA,UACZ;AAAA,UACA,MAAM,QAAQ;AAAA,UACd,QAAQ;AAAA;AAAA,UACR,WAAW,QAAQ,UAAU,YAAY;AAAA,UACzC,WAAW,QAAQ,UAAU,YAAY;AAAA,QAC3C;AAAA,MACF;AAAA,MAEA,MAAM,gBAAgB,IAAY,OAAkC;AAElE,YAAI;AACJ,cAAM,SAAS,kEAAkE,KAAK,EAAE;AACxF,YAAI,QAAQ;AACV,wBAAc,MAAM,KAAK,OAAO,MAAM,EAAE;AAAA,QAC1C,OAAO;AACL,gBAAM,UAAU,MAAM,KAAK,OAAO,aAAa,IAAI,EAAE,OAAO,EAAE,CAAC;AAC/D,cAAI,QAAQ,MAAM,SAAS,GAAG;AAC5B,0BAAc,QAAQ,MAAM,CAAC;AAAA,UAC/B,OAAO;AACL,kBAAM,IAAI,mBAAmB,IAAI,QAAQ;AAAA,UAC3C;AAAA,QACF;AAGA,cAAM,OAAO,MAAM,YAAY;AAC/B,YAAI,CAAC,MAAM;AACT,gBAAM,IAAI,MAAM,gCAAgC;AAAA,QAClD;AAEA,cAAM,SAAS,MAAM,KAAK,OAAO;AACjC,cAAM,kBAAkB,KAAK,gBAAgB,KAAK;AAMlD,cAAM,iBAAiB,OAAO,MAC3B,OAAO,CAAC,MAAW,EAAE,SAAS,eAAe,EAC7C,KAAK,CAAC,GAAQ,OAAY,EAAE,YAAY,MAAM,EAAE,YAAY,EAAE;AACjE,cAAM,cAAc,eAAe,CAAC;AACpC,YAAI,CAAC,aAAa;AAChB,gBAAM,IAAI,MAAM,iCAAiC,eAAe,EAAE;AAAA,QACpE;AAEA,cAAM,KAAK,OAAO,YAAY,YAAY,IAAI;AAAA,UAC5C,SAAS,YAAY;AAAA,QACvB,CAAC;AAAA,MACH;AAAA,MAEA,MAAM,OAAO,SAAiB,OAA8B;AAC1D,cAAM,QAAQ,MAAM,KAAK,SAAS,OAAO;AAEzC,cAAM,KAAK,OAAO,iBAAiB;AAAA,UACjC,SAAS,MAAM;AAAA,UACf,OAAO;AAAA,UACP,KAAK;AAAA,QACP,CAAC;AAAA,MACH;AAAA,MAEA,MAAc,eAAe,aAAkC;AAC7D,cAAM,QAAQ,MAAM,YAAY;AAChC,cAAM,WAAW,MAAM,YAAY;AACnC,cAAM,SAAS,MAAM,YAAY,OAAO;AAGxC,YAAI;AACJ,YAAI,YAAY,SAAS;AACvB,oBAAU,YAAY,mBAAmB,OACrC,YAAY,QAAQ,YAAY,IAChC,OAAO,YAAY,OAAO;AAAA,QAChC;AAEA,eAAO;AAAA,UACL,IAAI,YAAY;AAAA,UAChB,KAAK,YAAY;AAAA,UACjB,OAAO,YAAY;AAAA,UACnB,aAAa,YAAY,eAAe;AAAA,UACxC,OAAO,KAAK,SAAS,OAAO,QAAQ,SAAS;AAAA,UAC7C,QAAQ,QAAQ,OAAO,IAAI,CAAC,MAAW,EAAE,IAAI,KAAK,CAAC;AAAA,UACnD,UAAU,UAAU;AAAA,UACpB,KAAK,YAAY;AAAA,UACjB,SAAS;AAAA,UACT,UAAU,YAAY;AAAA,UACtB;AAAA,UACA,WAAW,YAAY,qBAAqB,OACxC,YAAY,UAAU,YAAY,IAClC,OAAO,YAAY,SAAS;AAAA,UAChC,WAAW,YAAY,qBAAqB,OACxC,YAAY,UAAU,YAAY,IAClC,OAAO,YAAY,SAAS;AAAA,QAClC;AAAA,MACF;AAAA,MAEQ,SAAS,aAAiC;AAChD,eAAO,UAAU,WAAW,KAAK;AAAA,MACnC;AAAA,MAEQ,gBAAgB,OAA2B;AACjD,gBAAQ,OAAO;AAAA,UACb,KAAK;AACH,mBAAO;AAAA,UACT,KAAK;AACH,mBAAO;AAAA,UACT,KAAK;AACH,mBAAO;AAAA,UACT;AACE,mBAAO;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA;AAAA;;;AC1SA,SAAS,eAAe;AANxB,IAmBa;AAnBb;AAAA;AAAA;AAAA;AAiBA;AAEO,IAAM,gBAAN,MAA4C;AAAA,MACxC,OAAoB;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MAER,YAAY,OAAe,OAAe,MAAc;AACtD,YAAI,CAAC,OAAO;AACV,gBAAM,IAAI,iBAAiB,UAAU,mBAAmB;AAAA,QAC1D;AACA,YAAI,CAAC,SAAS,CAAC,MAAM;AACnB,gBAAM,IAAI,MAAM,oCAAoC;AAAA,QACtD;AAEA,aAAK,UAAU,IAAI,QAAQ,EAAE,MAAM,MAAM,CAAC;AAC1C,aAAK,QAAQ;AACb,aAAK,OAAO;AAAA,MACd;AAAA,MAEA,MAAM,WAAW,SAA0C;AACzD,cAAM,QAAQ,KAAK,iBAAiB,SAAS,KAAK;AAElD,cAAM,WAAW,MAAM,KAAK,QAAQ,OAAO,YAAY;AAAA,UACrD,OAAO,KAAK;AAAA,UACZ,MAAM,KAAK;AAAA,UACX,OAAO,SAAS,gBAAgB,QAAQ;AAAA,UACxC,QAAQ,SAAS,QAAQ,KAAK,GAAG,KAAK;AAAA,UACtC,UAAU,SAAS,YAAY;AAAA,UAC/B,UAAU,SAAS,SAAS;AAAA,QAC9B,CAAC;AAGD,cAAM,SAAS,SAAS,KAAK,OAAO,CAAC,SAAS,CAAC,KAAK,YAAY;AAEhE,eAAO,OAAO,IAAI,CAAC,UAAU,KAAK,eAAe,KAAK,CAAC;AAAA,MACzD;AAAA,MAEA,MAAM,SAAS,IAA4B;AACzC,YAAI;AAEF,gBAAM,cAAc,SAAS,GAAG,QAAQ,MAAM,EAAE,GAAG,EAAE;AAErD,cAAI,MAAM,WAAW,GAAG;AACtB,kBAAM,IAAI,mBAAmB,IAAI,QAAQ;AAAA,UAC3C;AAEA,gBAAM,EAAE,MAAM,MAAM,IAAI,MAAM,KAAK,QAAQ,OAAO,IAAI;AAAA,YACpD,OAAO,KAAK;AAAA,YACZ,MAAM,KAAK;AAAA,YACX,cAAc;AAAA,UAChB,CAAC;AAED,iBAAO,KAAK,eAAe,KAAK;AAAA,QAClC,SAAS,OAAY;AACnB,cAAI,OAAO,WAAW,KAAK;AACzB,kBAAM,IAAI,mBAAmB,IAAI,QAAQ;AAAA,UAC3C;AACA,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,MAEA,MAAM,YAAY,IAAY,QAAqC;AACjE,cAAM,cAAc,SAAS,GAAG,QAAQ,MAAM,EAAE,GAAG,EAAE;AAErD,cAAM,gBAAyC,CAAC;AAEhD,YAAI,OAAO,UAAU,QAAW;AAC9B,wBAAc,QAAQ,OAAO;AAAA,QAC/B;AACA,YAAI,OAAO,gBAAgB,QAAW;AACpC,wBAAc,OAAO,OAAO;AAAA,QAC9B;AACA,YAAI,OAAO,UAAU,QAAW;AAC9B,wBAAc,QAAQ,OAAO,UAAU,WAAW,WAAW;AAAA,QAC/D;AACA,YAAI,OAAO,WAAW,QAAW;AAC/B,wBAAc,SAAS,OAAO;AAAA,QAChC;AACA,YAAI,OAAO,aAAa,QAAW;AACjC,wBAAc,YAAY,OAAO,WAAW,CAAC,OAAO,QAAQ,IAAI,CAAC;AAAA,QACnE;AAEA,cAAM,KAAK,QAAQ,OAAO,OAAO;AAAA,UAC/B,OAAO,KAAK;AAAA,UACZ,MAAM,KAAK;AAAA,UACX,cAAc;AAAA,UACd,GAAG;AAAA,QACL,CAAC;AAED,eAAO,KAAK,SAAS,EAAE;AAAA,MACzB;AAAA,MAEA,MAAM,YAAY,UAAoC;AACpD,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,KAAK,QAAQ,OAAO,OAAO;AAAA,UACvD,OAAO,KAAK;AAAA,UACZ,MAAM,KAAK;AAAA,UACX,OAAO,SAAS;AAAA,UAChB,MAAM,SAAS;AAAA,UACf,QAAQ,SAAS;AAAA,UACjB,WAAW,SAAS,WAAW,CAAC,SAAS,QAAQ,IAAI;AAAA,QACvD,CAAC;AAED,eAAO,KAAK,eAAe,KAAK;AAAA,MAClC;AAAA,MAEA,MAAM,YAAY,SAAqC;AACrD,cAAM,cAAc,SAAS,QAAQ,QAAQ,MAAM,EAAE,GAAG,EAAE;AAE1D,cAAM,EAAE,MAAM,SAAS,IAAI,MAAM,KAAK,QAAQ,OAAO,aAAa;AAAA,UAChE,OAAO,KAAK;AAAA,UACZ,MAAM,KAAK;AAAA,UACX,cAAc;AAAA,QAChB,CAAC;AAED,eAAO,SAAS,IAAI,CAAC,OAAO;AAAA,UAC1B,IAAI,OAAO,EAAE,EAAE;AAAA,UACf;AAAA,UACA,MAAM,EAAE,QAAQ;AAAA,UAChB,QAAQ,EAAE,MAAM,SAAS;AAAA,UACzB,WAAW,EAAE;AAAA,UACb,WAAW,EAAE;AAAA,QACf,EAAE;AAAA,MACJ;AAAA,MAEA,MAAM,WAAW,SAAiB,MAAgC;AAChE,cAAM,cAAc,SAAS,QAAQ,QAAQ,MAAM,EAAE,GAAG,EAAE;AAE1D,cAAM,EAAE,MAAM,QAAQ,IAAI,MAAM,KAAK,QAAQ,OAAO,cAAc;AAAA,UAChE,OAAO,KAAK;AAAA,UACZ,MAAM,KAAK;AAAA,UACX,cAAc;AAAA,UACd;AAAA,QACF,CAAC;AAED,eAAO;AAAA,UACL,IAAI,OAAO,QAAQ,EAAE;AAAA,UACrB;AAAA,UACA,MAAM,QAAQ,QAAQ;AAAA,UACtB,QAAQ,QAAQ,MAAM,SAAS;AAAA,UAC/B,WAAW,QAAQ;AAAA,UACnB,WAAW,QAAQ;AAAA,QACrB;AAAA,MACF;AAAA,MAEA,MAAM,gBAAgB,IAAY,OAAkC;AAClE,cAAM,cAAc,SAAS,GAAG,QAAQ,MAAM,EAAE,GAAG,EAAE;AAErD,YAAI,UAAU,eAAe;AAE3B,gBAAM,KAAK,kBAAkB,eAAe,eAAe,QAAQ;AACnE,gBAAM,KAAK,QAAQ,OAAO,UAAU;AAAA,YAClC,OAAO,KAAK;AAAA,YACZ,MAAM,KAAK;AAAA,YACX,cAAc;AAAA,YACd,QAAQ,CAAC,aAAa;AAAA,UACxB,CAAC;AAAA,QACH,OAAO;AAEL,gBAAM,QAAQ,MAAM,KAAK,SAAS,EAAE;AACpC,cAAI,MAAM,QAAQ,SAAS,aAAa,GAAG;AACzC,kBAAM,KAAK,QAAQ,OAAO,YAAY;AAAA,cACpC,OAAO,KAAK;AAAA,cACZ,MAAM,KAAK;AAAA,cACX,cAAc;AAAA,cACd,MAAM;AAAA,YACR,CAAC,EAAE,MAAM,MAAM;AAAA,YAAkC,CAAC;AAAA,UACpD;AACA,gBAAM,KAAK,YAAY,IAAI,EAAE,MAAM,CAAC;AAAA,QACtC;AAAA,MACF;AAAA;AAAA,MAGA,MAAc,kBAAkB,MAAc,aAAqB,OAA8B;AAC/F,YAAI;AACF,gBAAM,KAAK,QAAQ,OAAO,SAAS,EAAE,OAAO,KAAK,OAAO,MAAM,KAAK,MAAM,KAAK,CAAC;AAAA,QACjF,QAAQ;AACN,gBAAM,KAAK,QAAQ,OAAO,YAAY;AAAA,YACpC,OAAO,KAAK;AAAA,YACZ,MAAM,KAAK;AAAA,YACX;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC,EAAE,MAAM,MAAM;AAAA,UAAuD,CAAC;AAAA,QACzE;AAAA,MACF;AAAA,MAEA,MAAM,OAAO,SAAiB,OAA8B;AAG1D,cAAM,KAAK;AAAA,UACT;AAAA,UACA,wBAAwB,KAAK;AAAA,QAC/B;AAAA,MACF;AAAA,MAEQ,eAAe,SAAqB;AAC1C,cAAM,SAAmB,QAAQ,OAAO;AAAA,UAAI,CAAC,MAC3C,OAAO,MAAM,WAAW,IAAI,EAAE;AAAA,QAChC;AACA,eAAO;AAAA,UACL,IAAI,OAAO,QAAQ,EAAE;AAAA,UACrB,KAAK,IAAI,QAAQ,MAAM;AAAA,UACvB,OAAO,QAAQ;AAAA,UACf,aAAa,QAAQ,QAAQ;AAAA,UAC7B,OAAO,KAAK,mBAAmB,QAAQ,OAAO,MAAM;AAAA,UACpD;AAAA,UACA,UAAU,QAAQ,UAAU;AAAA,UAC5B,KAAK,QAAQ;AAAA,UACb,SAAS;AAAA,UACT,UAAU;AAAA;AAAA,UACV,SAAS;AAAA;AAAA,UACT,WAAW,QAAQ;AAAA,UACnB,WAAW,QAAQ;AAAA,QACrB;AAAA,MACF;AAAA,MAEQ,mBAAmB,SAAiB,SAAmB,CAAC,GAAe;AAC7E,YAAI,YAAY,SAAU,QAAO;AACjC,YAAI,OAAO,SAAS,aAAa,EAAG,QAAO;AAC3C,eAAO;AAAA,MACT;AAAA,MAEQ,iBACN,OAC2B;AAC3B,YAAI,CAAC,MAAO,QAAO;AACnB,YAAI,UAAU,SAAU,QAAO;AAC/B,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA;;;ACxPA,IAmBa;AAnBb;AAAA;AAAA;AAAA;AAiBA;AAEO,IAAM,gBAAN,MAA4C;AAAA,MAGjD,YACU,OACA,WACR;AAFQ;AACA;AAAA,MAGV;AAAA,MAPS,OAAoB;AAAA,MAS7B,MAAM,WAAW,UAA2C;AAC1D,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAM,SAAS,KAA6B;AAC1C,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAM,YAAY,KAAa,SAAsC;AACnE,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAM,YAAY,QAAkC;AAClD,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAM,YAAY,UAAsC;AACtD,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAM,WAAW,UAAkB,OAAiC;AAClE,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAM,gBAAgB,KAAa,QAAmC;AACpE,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAM,OAAO,UAAkB,QAA+B;AAC5D,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA;AAAA;;;AChCA,SAAS,wBAAwB,aAA8C;AAC7E,MAAI;AACF,UAAM,EAAE,QAAQ,WAAW,IAAI,WAAe;AAC9C,WAAO,WAAW,YAAY,WAAW;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,cAAc,QAAqC;AACjE,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK,UAAU;AACb,YAAM,YAAY,wBAAwB,QAAQ;AAClD,YAAM,SAAS,OAAO,YAClB,QAAQ,IAAI,OAAO,SAAS,IAC5B,QAAQ,IAAI;AAChB,YAAM,SAAS,aAAa;AAE5B,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI;AAAA,UACR;AAAA,UACA,mDAAmD,OAAO,aAAa,gBAAgB;AAAA,QACzF;AAAA,MACF;AAEA,aAAO,IAAI,cAAc,QAAQ,EAAE,MAAM,OAAO,KAAK,CAAC;AAAA,IACxD;AAAA,IAEA,KAAK,UAAU;AACb,YAAM,YAAY,wBAAwB,QAAQ;AAClD,YAAM,WAAW,OAAO,WACpB,QAAQ,IAAI,OAAO,QAAQ,IAC3B,QAAQ,IAAI;AAChB,YAAM,QAAQ,aAAa;AAE3B,UAAI,CAAC,OAAO;AACV,cAAM,IAAI;AAAA,UACR;AAAA,UACA,iDAAiD,OAAO,YAAY,cAAc;AAAA,QACpF;AAAA,MACF;AAEA,UAAI,CAAC,OAAO,SAAS,CAAC,OAAO,MAAM;AACjC,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,aAAO,IAAI,cAAc,OAAO,OAAO,OAAO,OAAO,IAAI;AAAA,IAC3D;AAAA,IAEA,KAAK,UAAU;AACb,YAAM,YAAY,wBAAwB,QAAQ;AAClD,YAAM,WAAW,OAAO,WACpB,QAAQ,IAAI,OAAO,QAAQ,IAC3B,QAAQ,IAAI;AAChB,YAAM,QAAQ,aAAa;AAE3B,UAAI,CAAC,OAAO;AACV,cAAM,IAAI;AAAA,UACR;AAAA,UACA,iDAAiD,OAAO,YAAY,cAAc;AAAA,QACpF;AAAA,MACF;AAEA,UAAI,CAAC,OAAO,WAAW;AACrB,cAAM,IAAI,MAAM,iDAAiD;AAAA,MACnE;AAEA,aAAO,IAAI,cAAc,OAAO,OAAO,SAAS;AAAA,IAClD;AAAA,IAEA,KAAK,SAAS;AACZ,YAAM,YAAY,wBAAwB,OAAO;AACjD,YAAM,SAAS,OAAO,YAClB,QAAQ,IAAI,OAAO,SAAS,IAC5B,QAAQ,IAAI;AAChB,YAAM,SAAS,aAAa;AAE5B,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI;AAAA,UACR;AAAA,UACA,mDAAmD,OAAO,aAAa,eAAe;AAAA,QACxF;AAAA,MACF;AAEA,aAAO,IAAI,aAAa;AAAA,QACtB;AAAA,QACA,QAAQ,OAAO;AAAA,QACf,WAAW,OAAO;AAAA,QAClB,SAAS,OAAO;AAAA,MAClB,CAAC;AAAA,IACH;AAAA,IAEA;AACE,YAAM,IAAI,MAAM,yBAAyB,OAAO,IAAI,EAAE;AAAA,EAC1D;AACF;AAKO,SAAS,wBACd,gBACA,aACc;AACd,QAAM,SAAS,eAAe,WAAW;AAEzC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR,uCAAuC,WAAW,mBAAmB,WAAW;AAAA,IAClF;AAAA,EACF;AAEA,SAAO,cAAc,EAAE,GAAG,QAAQ,MAAM,YAAY,CAAC;AACvD;AAKO,SAAS,kBAAkB,gBAA8C;AAC9E,SAAO,wBAAwB,gBAAgB,eAAe,OAAO;AACvE;AAKO,SAAS,oBACd,gBACqB;AACrB,MAAI,CAAC,eAAe,WAAW;AAC7B,WAAO;AAAA,EACT;AACA,SAAO,wBAAwB,gBAAgB,eAAe,SAAS;AACzE;AAKO,SAAS,eAAe,gBAAgD;AAC7E,QAAM,WAA2B,CAAC,kBAAkB,cAAc,CAAC;AAEnE,QAAM,YAAY,oBAAoB,cAAc;AACpD,MAAI,WAAW;AACb,aAAS,KAAK,SAAS;AAAA,EACzB;AAEA,SAAO;AACT;AApMA;AAAA;AAAA;AAAA;AAOA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;","names":[]}
@@ -220,6 +220,8 @@ async function createWorktree(repoPath, targetPath, branchName, defaultBranch =
220
220
  } else {
221
221
  await execAsync(`git worktree add "${targetPath}" -b "${branchName}" "${defaultBranch}"`, { cwd: repoPath });
222
222
  }
223
+ await execAsync("git config beads.role agent", { cwd: targetPath }).catch(() => {
224
+ });
223
225
  return { success: true, message: `Created worktree at ${targetPath}` };
224
226
  } catch (error) {
225
227
  return { success: false, message: `Failed to create worktree: ${error}` };
@@ -675,6 +677,21 @@ async function stopWorkspaceDocker(workspacePath, projectName, featureName) {
675
677
  result.steps.push(`Docker cleanup attempted (${error.message?.split("\n")[0] || "containers may not be running"})`);
676
678
  }
677
679
  }
680
+ const networkPattern = `${projectName}-feature-${featureName}_devnet`;
681
+ try {
682
+ const { stdout } = await execAsync(
683
+ `docker network ls --format '{{.Name}}' --filter name=${networkPattern} 2>/dev/null`,
684
+ { encoding: "utf-8", timeout: 5e3 }
685
+ );
686
+ for (const net of stdout.trim().split("\n").filter(Boolean)) {
687
+ try {
688
+ await execAsync(`docker network rm "${net}" 2>/dev/null`, { timeout: 5e3 });
689
+ result.steps.push(`Removed network: ${net}`);
690
+ } catch {
691
+ }
692
+ }
693
+ } catch {
694
+ }
678
695
  try {
679
696
  await execAsync(
680
697
  `docker run --rm -v "${workspacePath}:/workspace" alpine sh -c "find /workspace -user root -delete 2>&1 | tail -100 || true"`,
@@ -813,4 +830,4 @@ export {
813
830
  removeWorkspace,
814
831
  init_workspace_manager
815
832
  };
816
- //# sourceMappingURL=chunk-GR6ZZMCX.js.map
833
+ //# sourceMappingURL=chunk-F5555J3A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/skills-merge.ts","../src/lib/workspace-manager.ts"],"sourcesContent":["import {\n existsSync,\n readdirSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n copyFileSync,\n statSync,\n} from 'fs';\nimport { join, relative, dirname } from 'path';\nimport { SKILLS_DIR, CACHE_AGENTS_DIR, CACHE_RULES_DIR } from './paths.js';\nimport {\n readManifest,\n writeManifest,\n collectSourceFiles,\n hashFile,\n setManifestEntry,\n compareFileToManifest,\n type Manifest,\n} from './manifest.js';\n\nexport interface MergeResult {\n added: string[];\n updated: string[];\n skipped: string[];\n overlayed: string[];\n}\n\n/**\n * Copy all files from a source directory into a target directory,\n * preserving subdirectory structure. Returns the list of relative paths copied.\n */\nfunction copyTree(sourceDir: string, targetDir: string): string[] {\n const copied: string[] = [];\n if (!existsSync(sourceDir)) return copied;\n\n function walk(dir: string): void {\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(fullPath);\n } else if (entry.isFile()) {\n const rel = relative(sourceDir, fullPath);\n const targetPath = join(targetDir, rel);\n mkdirSync(dirname(targetPath), { recursive: true });\n copyFileSync(fullPath, targetPath);\n copied.push(rel);\n }\n }\n }\n\n walk(sourceDir);\n return copied;\n}\n\n/**\n * Merge Panopticon skills, agents, and rules into a workspace using file copies.\n *\n * Flow:\n * 1. Copy from cache (skills, agent-definitions, rules) → workspace/.claude/\n * 2. Write manifest tracking what was placed\n *\n * Project template overlay is handled separately by workspace-manager.ts\n * (processTemplates + createSymlinks → now also copy-based).\n */\nexport function mergeSkillsIntoWorkspace(workspacePath: string): MergeResult {\n const claudeDir = join(workspacePath, '.claude');\n const manifestPath = join(claudeDir, '.panopticon-manifest.json');\n const manifest = readManifest(manifestPath);\n\n const result: MergeResult = {\n added: [],\n updated: [],\n skipped: [],\n overlayed: [],\n };\n\n // Ensure base directories exist\n mkdirSync(join(claudeDir, 'skills'), { recursive: true });\n mkdirSync(join(claudeDir, 'agents'), { recursive: true });\n\n // Sources to copy: category → source cache directory\n const sources: Array<{ category: string; sourceDir: string; targetSubdir: string }> = [\n { category: 'skills', sourceDir: SKILLS_DIR, targetSubdir: 'skills' },\n { category: 'agents', sourceDir: CACHE_AGENTS_DIR, targetSubdir: 'agents' },\n { category: 'rules', sourceDir: CACHE_RULES_DIR, targetSubdir: 'rules' },\n ];\n\n for (const { category, sourceDir, targetSubdir } of sources) {\n if (!existsSync(sourceDir)) continue;\n\n const prefix = targetSubdir ? `${targetSubdir}/` : '';\n const files = collectSourceFiles(sourceDir, '');\n\n for (const file of files) {\n const relativePath = `${prefix}${file.relativePath}`;\n const targetPath = join(claudeDir, relativePath);\n const sourceHash = hashFile(file.absolutePath);\n\n // Check status against manifest\n const status = compareFileToManifest(targetPath, relativePath, manifest);\n\n switch (status.action) {\n case 'new':\n // File doesn't exist at target — copy it\n mkdirSync(dirname(targetPath), { recursive: true });\n copyFileSync(file.absolutePath, targetPath);\n setManifestEntry(manifest, relativePath, sourceHash, 'panopticon');\n result.added.push(relativePath);\n break;\n\n case 'update':\n // File exists and matches manifest — safe to overwrite with latest\n copyFileSync(file.absolutePath, targetPath);\n setManifestEntry(manifest, relativePath, sourceHash, 'panopticon');\n result.updated.push(relativePath);\n break;\n\n case 'modified':\n // User modified the file — skip to preserve their changes\n result.skipped.push(`${relativePath} (modified by user)`);\n break;\n\n case 'user-owned':\n // File exists but wasn't placed by us — never touch\n result.skipped.push(`${relativePath} (user-owned)`);\n break;\n }\n }\n }\n\n // Write updated manifest\n writeManifest(manifestPath, manifest);\n\n return result;\n}\n\n/**\n * Apply project template overlay on top of Panopticon base files in a workspace.\n *\n * This copies files from the project's agent template directory into\n * workspace/.claude/, overwriting Panopticon files where the project\n * provides its own version. Updates the manifest with source=\"project-template\".\n *\n * @param workspacePath - Path to the workspace\n * @param templateDir - Absolute path to the project's agent template directory\n * @param templates - Optional list of specific template files to process (source → target mappings)\n */\nexport function applyProjectTemplateOverlay(\n workspacePath: string,\n templateDir: string,\n templates?: Array<{ source: string; target: string }>,\n): string[] {\n const claudeDir = join(workspacePath, '.claude');\n const manifestPath = join(claudeDir, '.panopticon-manifest.json');\n const manifest = readManifest(manifestPath);\n const overlayed: string[] = [];\n\n if (!existsSync(templateDir)) return overlayed;\n\n if (templates && templates.length > 0) {\n // Process specific template mappings\n for (const { source, target } of templates) {\n const sourcePath = join(templateDir, source);\n if (!existsSync(sourcePath)) continue;\n\n const targetPath = join(workspacePath, target);\n mkdirSync(dirname(targetPath), { recursive: true });\n\n // Read template content and check if it's a template file\n if (source.endsWith('.template')) {\n // Template files are handled by workspace-manager's processTemplates\n // We just track them in the manifest after they're processed\n continue;\n }\n\n copyFileSync(sourcePath, targetPath);\n\n // Track in manifest if it's under .claude/\n if (target.startsWith('.claude/')) {\n const relativePath = target.slice('.claude/'.length);\n const hash = hashFile(targetPath);\n setManifestEntry(manifest, relativePath, hash, 'project-template');\n overlayed.push(relativePath);\n }\n }\n } else {\n // Copy all .claude/ subdirectories from template dir\n const claudeInTemplate = join(templateDir, '.claude');\n if (existsSync(claudeInTemplate)) {\n const copied = copyTree(claudeInTemplate, claudeDir);\n for (const rel of copied) {\n const targetPath = join(claudeDir, rel);\n const hash = hashFile(targetPath);\n setManifestEntry(manifest, rel, hash, 'project-template');\n overlayed.push(rel);\n }\n }\n }\n\n // Write updated manifest\n writeManifest(manifestPath, manifest);\n\n return overlayed;\n}\n\n// ─── Legacy exports (kept for migration, to be removed in future) ───\n\n/**\n * @deprecated No longer needed — skills are copies, not symlinks. Kept for migration.\n */\nexport function cleanupGitignore(gitignorePath: string): {\n cleaned: boolean;\n duplicatesRemoved: number;\n entriesAfter: number;\n} {\n if (!existsSync(gitignorePath)) {\n return { cleaned: false, duplicatesRemoved: 0, entriesAfter: 0 };\n }\n\n const PANOPTICON_HEADER = '# Panopticon-managed symlinks (not committed)';\n let content: string;\n try {\n content = readFileSync(gitignorePath, 'utf-8');\n } catch {\n return { cleaned: false, duplicatesRemoved: 0, entriesAfter: 0 };\n }\n\n // If no Panopticon section, nothing to clean\n if (!content.includes(PANOPTICON_HEADER)) {\n return { cleaned: false, duplicatesRemoved: 0, entriesAfter: 0 };\n }\n\n // Remove the entire Panopticon section (skills are copies now, not symlinks)\n const lines = content.split('\\n');\n const newLines: string[] = [];\n let inPanopticonSection = false;\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed === PANOPTICON_HEADER) {\n inPanopticonSection = true;\n continue;\n }\n if (inPanopticonSection) {\n if (trimmed.startsWith('#') && trimmed !== '') {\n inPanopticonSection = false;\n newLines.push(line);\n } else if (trimmed === '') {\n // Skip blank lines in Panopticon section\n continue;\n }\n // Skip entries in Panopticon section\n continue;\n }\n newLines.push(line);\n }\n\n // Write cleaned file\n try {\n writeFileSync(gitignorePath, newLines.join('\\n'), 'utf-8');\n return { cleaned: true, duplicatesRemoved: 0, entriesAfter: 0 };\n } catch {\n return { cleaned: false, duplicatesRemoved: 0, entriesAfter: 0 };\n }\n}\n\n/**\n * @deprecated No longer needed — skills are copies, not symlinks. Kept for migration.\n */\nexport function cleanupWorkspaceGitignore(workspacePath: string): {\n cleaned: boolean;\n duplicatesRemoved: number;\n entriesAfter: number;\n} {\n const gitignorePath = join(workspacePath, '.claude', 'skills', '.gitignore');\n return cleanupGitignore(gitignorePath);\n}\n","/**\n * Workspace Manager\n *\n * Handles workspace creation and removal for both monorepo and polyrepo projects.\n */\n\nimport { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, copyFileSync, symlinkSync, chmodSync, realpathSync } from 'fs';\nimport { join, dirname, basename, extname } from 'path';\nimport { homedir } from 'os';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport {\n ProjectConfig,\n WorkspaceConfig,\n TemplatePlaceholders,\n replacePlaceholders,\n getDefaultWorkspaceConfig,\n} from './workspace-config.js';\nimport { addDnsEntry, removeDnsEntry, syncDnsToWindows } from './dns.js';\nimport { addTunnelIngress, removeTunnelIngress } from './tunnel.js';\nimport { createHumeConfig, deleteHumeConfig } from './hume.js';\nimport { mergeSkillsIntoWorkspace } from './skills-merge.js';\n\nconst execAsync = promisify(exec);\n\nexport interface WorkspaceCreateOptions {\n projectConfig: ProjectConfig;\n featureName: string;\n startDocker?: boolean;\n dryRun?: boolean;\n}\n\nexport interface WorkspaceCreateResult {\n success: boolean;\n workspacePath: string;\n errors: string[];\n steps: string[];\n}\n\n/**\n * Create placeholders for template substitution\n */\nfunction createPlaceholders(\n projectConfig: ProjectConfig,\n featureName: string,\n workspacePath: string\n): TemplatePlaceholders {\n const featureFolder = `feature-${featureName}`;\n const domain = projectConfig.workspace?.dns?.domain || 'localhost';\n\n return {\n FEATURE_NAME: featureName,\n FEATURE_FOLDER: featureFolder,\n BRANCH_NAME: `feature/${featureName}`,\n COMPOSE_PROJECT: `${basename(projectConfig.path)}-${featureFolder}`,\n DOMAIN: domain,\n PROJECT_NAME: basename(projectConfig.path),\n PROJECT_PATH: projectConfig.path,\n WORKSPACE_PATH: workspacePath,\n HOME: homedir(),\n };\n}\n\n/**\n * Sanitize docker-compose files to use platform-agnostic paths\n * Replaces hardcoded /home/username paths with ${HOME}\n */\nfunction sanitizeComposeFile(filePath: string): void {\n if (!existsSync(filePath)) return;\n\n let content = readFileSync(filePath, 'utf-8');\n const originalContent = content;\n\n // Pattern to match hardcoded home paths like /home/username or /Users/username\n // Replace with ${HOME} which docker-compose expands\n const homePatterns = [\n /\\/home\\/[a-zA-Z0-9_-]+\\//g, // Linux: /home/username/\n /\\/Users\\/[a-zA-Z0-9_-]+\\//g, // macOS: /Users/username/\n ];\n\n for (const pattern of homePatterns) {\n content = content.replace(pattern, '${HOME}/');\n }\n\n if (content !== originalContent) {\n writeFileSync(filePath, content, 'utf-8');\n }\n}\n\n/**\n * Validate feature name (alphanumeric and hyphens only)\n */\nfunction validateFeatureName(name: string): boolean {\n return /^[a-zA-Z0-9-]+$/.test(name);\n}\n\n/**\n * Create a git worktree\n * @param repoPath Path to the source git repository\n * @param targetPath Where to create the worktree\n * @param branchName Name of the feature branch to create/checkout\n * @param defaultBranch Base branch to create new branches from (default: 'main')\n */\nasync function createWorktree(\n repoPath: string,\n targetPath: string,\n branchName: string,\n defaultBranch: string = 'main'\n): Promise<{ success: boolean; message: string }> {\n try {\n // Fetch latest from origin\n await execAsync('git fetch origin', { cwd: repoPath });\n\n // Prune stale worktree entries (e.g., from deleted workspaces)\n await execAsync('git worktree prune', { cwd: repoPath });\n\n // Check if branch exists locally or remotely\n const { stdout: localBranches } = await execAsync('git branch --list', { cwd: repoPath });\n const { stdout: remoteBranches } = await execAsync('git branch -r --list', { cwd: repoPath });\n\n const branchExists =\n localBranches.includes(branchName) ||\n remoteBranches.includes(`origin/${branchName}`);\n\n if (branchExists) {\n await execAsync(`git worktree add \"${targetPath}\" \"${branchName}\"`, { cwd: repoPath });\n } else {\n // Create new branch from the configured default branch\n await execAsync(`git worktree add \"${targetPath}\" -b \"${branchName}\" \"${defaultBranch}\"`, { cwd: repoPath });\n }\n\n // Configure beads role so agents don't get \"beads.role not configured\" warnings\n await execAsync('git config beads.role agent', { cwd: targetPath }).catch(() => {});\n\n return { success: true, message: `Created worktree at ${targetPath}` };\n } catch (error) {\n return { success: false, message: `Failed to create worktree: ${error}` };\n }\n}\n\n/**\n * Remove a git worktree\n */\nasync function removeWorktree(\n repoPath: string,\n targetPath: string,\n branchName: string\n): Promise<{ success: boolean; message: string }> {\n try {\n // Remove worktree\n await execAsync(`git worktree remove \"${targetPath}\" --force`, { cwd: repoPath }).catch(() => {});\n\n // Optionally delete the branch\n await execAsync(`git branch -D \"${branchName}\"`, { cwd: repoPath }).catch(() => {});\n\n return { success: true, message: `Removed worktree at ${targetPath}` };\n } catch (error) {\n return { success: false, message: `Failed to remove worktree: ${error}` };\n }\n}\n\n// DNS functions (addWsl2HostEntry, removeWsl2HostEntry, syncDnsToWindows)\n// are now in src/lib/dns.ts and imported above\n\n/**\n * Assign a port from a range\n */\nfunction assignPort(\n portFile: string,\n featureFolder: string,\n range: [number, number]\n): number {\n // Ensure port file exists\n if (!existsSync(portFile)) {\n mkdirSync(dirname(portFile), { recursive: true });\n writeFileSync(portFile, '');\n }\n\n const content = readFileSync(portFile, 'utf-8');\n const lines = content.split('\\n').filter(Boolean);\n\n // Check if already assigned\n for (const line of lines) {\n const [folder, port] = line.split(':');\n if (folder === featureFolder) {\n return parseInt(port, 10);\n }\n }\n\n // Find next available port\n const usedPorts = new Set(lines.map(l => parseInt(l.split(':')[1], 10)));\n for (let port = range[0]; port <= range[1]; port++) {\n if (!usedPorts.has(port)) {\n writeFileSync(portFile, content + (content.endsWith('\\n') ? '' : '\\n') + `${featureFolder}:${port}\\n`);\n return port;\n }\n }\n\n throw new Error(`No available ports in range ${range[0]}-${range[1]}`);\n}\n\n/**\n * Release a port assignment\n */\nfunction releasePort(portFile: string, featureFolder: string): boolean {\n try {\n if (!existsSync(portFile)) return true;\n\n let content = readFileSync(portFile, 'utf-8');\n const lines = content.split('\\n').filter(line => !line.startsWith(`${featureFolder}:`));\n writeFileSync(portFile, lines.join('\\n'));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Process template files with placeholder replacement\n */\nfunction processTemplates(\n templateDir: string,\n targetDir: string,\n placeholders: TemplatePlaceholders,\n templates?: Array<{ source: string; target: string }>\n): string[] {\n const steps: string[] = [];\n\n if (!existsSync(templateDir)) {\n return steps;\n }\n\n // If specific templates are defined, process those\n if (templates && templates.length > 0) {\n for (const { source, target } of templates) {\n const sourcePath = join(templateDir, source);\n const targetPath = join(targetDir, target);\n\n if (existsSync(sourcePath)) {\n const content = readFileSync(sourcePath, 'utf-8');\n const processed = replacePlaceholders(content, placeholders);\n mkdirSync(dirname(targetPath), { recursive: true });\n writeFileSync(targetPath, processed);\n steps.push(`Processed template: ${source} -> ${target}`);\n }\n }\n } else {\n // Process all .template files\n const files = readdirSync(templateDir);\n for (const file of files) {\n if (file.endsWith('.template')) {\n const sourcePath = join(templateDir, file);\n const targetPath = join(targetDir, file.replace('.template', ''));\n\n const content = readFileSync(sourcePath, 'utf-8');\n const processed = replacePlaceholders(content, placeholders);\n writeFileSync(targetPath, processed);\n steps.push(`Processed template: ${file}`);\n }\n }\n }\n\n return steps;\n}\n\n/**\n * @deprecated Use copyProjectTemplateDirs instead. Kept for non-.claude paths.\n */\nfunction createSymlinks(\n sourceDir: string,\n targetDir: string,\n symlinks: string[]\n): string[] {\n const steps: string[] = [];\n\n for (const symlink of symlinks) {\n const sourcePath = join(sourceDir, symlink);\n const targetPath = join(targetDir, symlink);\n\n if (existsSync(sourcePath)) {\n mkdirSync(dirname(targetPath), { recursive: true });\n try {\n symlinkSync(sourcePath, targetPath);\n steps.push(`Created symlink: ${symlink}`);\n } catch {\n // Symlink might already exist\n }\n }\n }\n\n return steps;\n}\n\n/**\n * Copy project template directories into workspace (replaces symlinks).\n * Recursively copies all files from each source directory.\n */\nconst TEXT_EXTENSIONS = new Set([\n '.md', '.sh', '.yml', '.yaml', '.json', '.ts', '.js', '.env', '.txt', '.toml', '.template',\n]);\n\nfunction copyProjectTemplateDirs(\n sourceDir: string,\n targetDir: string,\n dirs: string[],\n placeholders?: TemplatePlaceholders\n): string[] {\n const steps: string[] = [];\n\n for (const dir of dirs) {\n const sourcePath = join(sourceDir, dir);\n const targetPath = join(targetDir, dir);\n\n if (!existsSync(sourcePath)) continue;\n\n // Recursively copy all files, applying placeholder substitution to text files\n function copyDir(src: string, dest: string): number {\n let count = 0;\n mkdirSync(dest, { recursive: true });\n const entries = readdirSync(src, { withFileTypes: true });\n for (const entry of entries) {\n const srcEntry = join(src, entry.name);\n const destEntry = join(dest, entry.name);\n if (entry.isDirectory()) {\n count += copyDir(srcEntry, destEntry);\n } else if (entry.isFile()) {\n const ext = extname(entry.name).toLowerCase();\n if (placeholders && TEXT_EXTENSIONS.has(ext)) {\n const content = readFileSync(srcEntry, 'utf-8');\n writeFileSync(destEntry, replacePlaceholders(content, placeholders));\n } else {\n copyFileSync(srcEntry, destEntry);\n }\n count++;\n }\n }\n return count;\n }\n\n const count = copyDir(sourcePath, targetPath);\n steps.push(`Copied ${count} files from project template: ${dir}`);\n }\n\n return steps;\n}\n\n/**\n * Create a workspace\n */\nexport async function createWorkspace(options: WorkspaceCreateOptions): Promise<WorkspaceCreateResult> {\n const { projectConfig, featureName, startDocker, dryRun } = options;\n const result: WorkspaceCreateResult = {\n success: true,\n workspacePath: '',\n errors: [],\n steps: [],\n };\n\n // Validate feature name\n if (!validateFeatureName(featureName)) {\n result.success = false;\n result.errors.push('Invalid feature name. Use alphanumeric and hyphens only.');\n return result;\n }\n\n // Reject 'main' as feature name\n if (featureName === 'main') {\n result.success = false;\n result.errors.push('Cannot create workspace for \"main\". Use base repos directly.');\n return result;\n }\n\n const workspaceConfig = projectConfig.workspace || getDefaultWorkspaceConfig();\n const workspacesDir = join(projectConfig.path, workspaceConfig.workspaces_dir || 'workspaces');\n const featureFolder = `feature-${featureName}`;\n const workspacePath = join(workspacesDir, featureFolder);\n result.workspacePath = workspacePath;\n\n // Check if workspace already exists\n if (existsSync(workspacePath)) {\n result.success = false;\n result.errors.push(`Workspace already exists at ${workspacePath}`);\n return result;\n }\n\n if (dryRun) {\n result.steps.push('[DRY RUN] Would create workspace at: ' + workspacePath);\n return result;\n }\n\n // Create placeholders\n const placeholders = createPlaceholders(projectConfig, featureName, workspacePath);\n\n // Create workspace directory\n mkdirSync(workspacePath, { recursive: true });\n result.steps.push('Created workspace directory');\n\n // Handle polyrepo vs monorepo\n if (workspaceConfig.type === 'polyrepo' && workspaceConfig.repos) {\n // Create worktrees for each repo\n for (const repo of workspaceConfig.repos) {\n // Resolve symlinks to get the actual git repository path\n // (e.g., myn/frontend -> ../frontend needs to resolve to actual path)\n const rawRepoPath = join(projectConfig.path, repo.path);\n const repoPath = existsSync(rawRepoPath) ? realpathSync(rawRepoPath) : rawRepoPath;\n const targetPath = join(workspacePath, repo.name);\n const branchPrefix = repo.branch_prefix || 'feature/';\n const branchName = `${branchPrefix}${featureName}`;\n // Per-repo default_branch overrides workspace-level, falls back to 'main'\n const defaultBranch = repo.default_branch || workspaceConfig.default_branch || 'main';\n\n const worktreeResult = await createWorktree(repoPath, targetPath, branchName, defaultBranch);\n if (worktreeResult.success) {\n result.steps.push(`Created worktree for ${repo.name}: ${branchName} (from ${defaultBranch})`);\n } else {\n result.errors.push(`${repo.name}: ${worktreeResult.message}`);\n result.success = false; // Fail the entire workspace creation if any worktree fails\n }\n }\n } else {\n // Monorepo: create single worktree\n const branchName = `feature/${featureName}`;\n const defaultBranch = workspaceConfig.default_branch || 'main';\n const worktreeResult = await createWorktree(projectConfig.path, workspacePath, branchName, defaultBranch);\n if (worktreeResult.success) {\n result.steps.push(`Created worktree: ${branchName} (from ${defaultBranch})`);\n } else {\n result.errors.push(worktreeResult.message);\n result.success = false; // Fail the entire workspace creation if worktree fails\n }\n }\n\n // Sanitize any docker-compose files in the workspace to use platform-agnostic paths\n // This handles files inherited from worktrees that may have hardcoded home paths\n const devcontainerDir = join(workspacePath, '.devcontainer');\n if (existsSync(devcontainerDir)) {\n const composeFiles = readdirSync(devcontainerDir)\n .filter(f => f.includes('compose') && (f.endsWith('.yml') || f.endsWith('.yaml')));\n for (const composeFile of composeFiles) {\n sanitizeComposeFile(join(devcontainerDir, composeFile));\n }\n if (composeFiles.length > 0) {\n result.steps.push(`Sanitized ${composeFiles.length} compose file(s) for platform compatibility`);\n }\n }\n\n // Setup TLDR code analysis for workspace (after worktree creation to ensure directory is ready)\n try {\n // Check if python3 is available\n await execAsync('python3 --version');\n const venvPath = join(workspacePath, '.venv');\n const tldrBin = join(venvPath, 'bin', 'tldr');\n\n // Check if main branch already has a working venv with llm-tldr\n const mainVenvTldr = join(projectConfig.path, '.venv', 'bin', 'tldr');\n const mainVenvExists = existsSync(mainVenvTldr);\n\n if (mainVenvExists) {\n // Copy the entire venv from main — faster than pip install (seconds vs 30s+)\n await execAsync(`cp -a \"${join(projectConfig.path, '.venv')}\" \"${venvPath}\"`);\n result.steps.push('Copied Python venv from main branch');\n } else {\n // Create fresh venv and install llm-tldr\n await execAsync(`python3 -m venv \"${venvPath}\"`, { cwd: workspacePath });\n const pipPath = join(venvPath, 'bin', 'pip');\n await execAsync(`\"${pipPath}\" install llm-tldr`, { cwd: workspacePath, timeout: 120000 });\n result.steps.push('Created Python venv and installed llm-tldr');\n\n // Apply .tsx/.jsx support patch (upstream llm-tldr only checks .ts)\n const patchScript = join(projectConfig.path, 'scripts', 'patches', 'llm-tldr-tsx-support.py');\n if (existsSync(patchScript)) {\n await execAsync(`python3 \"${patchScript}\" \"${venvPath}\"`);\n result.steps.push('Applied llm-tldr .tsx/.jsx patch');\n }\n }\n\n // Verify tldr binary exists after setup\n if (!existsSync(tldrBin)) {\n result.steps.push('TLDR setup incomplete: tldr binary not found after venv creation');\n } else {\n // Copy .tldr index from main branch if it exists\n const mainTldrDir = join(projectConfig.path, '.tldr');\n const workspaceTldrDir = join(workspacePath, '.tldr');\n\n if (existsSync(mainTldrDir)) {\n await execAsync(`cp -r \"${mainTldrDir}\" \"${workspaceTldrDir}\"`);\n result.steps.push('Copied TLDR index from main branch');\n }\n\n // Start TLDR daemon for this workspace\n const { getTldrDaemonService } = await import('./tldr-daemon.js');\n const tldrService = getTldrDaemonService(workspacePath, venvPath);\n await tldrService.start(true);\n result.steps.push('Started TLDR daemon');\n\n // Warm the index in the background — ensures workspaces always have a working index\n // even when the main branch cache was empty (nothing to copy)\n try {\n await tldrService.warm(true); // background=true: non-blocking\n result.steps.push('TLDR index warm initiated (background)');\n } catch {\n // Non-fatal — daemon may not support warm yet\n }\n }\n } catch (error: any) {\n // TLDR setup is optional — don't fail workspace creation, but log clearly\n if (error.message?.includes('python3')) {\n result.steps.push('Skipped TLDR setup (python3 not available)');\n } else {\n console.warn(`⚠ TLDR setup failed: ${error.message}`);\n result.steps.push(`TLDR setup failed: ${error.message}`);\n }\n }\n\n // Configure DNS\n if (workspaceConfig.dns) {\n const dnsMethod = workspaceConfig.dns.sync_method || 'wsl2hosts';\n for (const entryPattern of workspaceConfig.dns.entries) {\n const hostname = replacePlaceholders(entryPattern, placeholders);\n\n if (addDnsEntry(dnsMethod, hostname)) {\n result.steps.push(`Added DNS entry: ${hostname} (${dnsMethod})`);\n }\n }\n\n // Sync to Windows if using wsl2hosts method\n if (dnsMethod === 'wsl2hosts') {\n const synced = await syncDnsToWindows();\n if (synced) {\n result.steps.push('Synced DNS to Windows hosts file');\n }\n }\n }\n\n // Assign ports\n if (workspaceConfig.ports) {\n for (const [portName, portConfig] of Object.entries(workspaceConfig.ports)) {\n const portFile = join(projectConfig.path, `.${portName}-ports`);\n try {\n const port = assignPort(portFile, featureFolder, portConfig.range);\n result.steps.push(`Assigned ${portName} port: ${port}`);\n // Add to placeholders for use in templates\n (placeholders as any)[`${portName.toUpperCase()}_PORT`] = String(port);\n } catch (error) {\n result.errors.push(`Failed to assign ${portName} port: ${error}`);\n }\n }\n }\n\n // Install base Panopticon skills/agents/rules from cache\n const mergeResult = mergeSkillsIntoWorkspace(workspacePath);\n const mergeTotal = mergeResult.added.length + mergeResult.updated.length;\n if (mergeTotal > 0) {\n result.steps.push(`Installed ${mergeTotal} Panopticon files (${mergeResult.added.length} new, ${mergeResult.updated.length} updated)`);\n }\n\n // Process agent templates (project template overlay — wins over Panopticon base)\n if (workspaceConfig.agent?.template_dir) {\n const templateDir = join(projectConfig.path, workspaceConfig.agent.template_dir);\n\n // Process template files\n const templateSteps = processTemplates(\n templateDir,\n workspacePath,\n placeholders,\n workspaceConfig.agent.templates\n );\n result.steps.push(...templateSteps);\n\n // Copy .claude/ directories from project template (copy_dirs replaces legacy symlinks)\n const dirsToSync = workspaceConfig.agent.copy_dirs || workspaceConfig.agent.symlinks;\n if (dirsToSync) {\n const copySteps = copyProjectTemplateDirs(templateDir, workspacePath, dirsToSync, placeholders);\n result.steps.push(...copySteps);\n }\n }\n\n // Generate .env file\n if (workspaceConfig.env?.template) {\n const envContent = replacePlaceholders(workspaceConfig.env.template, placeholders);\n writeFileSync(join(workspacePath, '.env'), envContent);\n result.steps.push('Created .env file');\n }\n\n // Process Docker compose templates\n if (workspaceConfig.docker?.compose_template) {\n const templateDir = join(projectConfig.path, workspaceConfig.docker.compose_template);\n const devcontainerDir = join(workspacePath, '.devcontainer');\n mkdirSync(devcontainerDir, { recursive: true });\n\n const templateSteps = processTemplates(templateDir, devcontainerDir, placeholders);\n result.steps.push(...templateSteps);\n\n // Copy non-template files (like Dockerfile)\n if (existsSync(templateDir)) {\n const files = readdirSync(templateDir);\n for (const file of files) {\n if (!file.endsWith('.template')) {\n const sourcePath = join(templateDir, file);\n const targetPath = join(devcontainerDir, file);\n copyFileSync(sourcePath, targetPath);\n }\n }\n }\n\n // Sanitize docker-compose files to use platform-agnostic paths\n // This fixes hardcoded /home/username or /Users/username paths\n const composeFiles = readdirSync(devcontainerDir)\n .filter(f => f.includes('compose') && (f.endsWith('.yml') || f.endsWith('.yaml')));\n for (const composeFile of composeFiles) {\n sanitizeComposeFile(join(devcontainerDir, composeFile));\n }\n if (composeFiles.length > 0) {\n result.steps.push(`Sanitized ${composeFiles.length} compose file(s) for platform compatibility`);\n }\n\n // Create ./dev symlink at workspace root pointing to .devcontainer/dev\n // Symlink keeps changes in sync - editing ./dev updates .devcontainer/dev\n const devScriptInContainer = join(devcontainerDir, 'dev');\n const devScriptAtRoot = join(workspacePath, 'dev');\n if (existsSync(devScriptInContainer) && !existsSync(devScriptAtRoot)) {\n try {\n symlinkSync('.devcontainer/dev', devScriptAtRoot);\n chmodSync(devScriptInContainer, 0o755); // Make executable\n result.steps.push('Created ./dev symlink');\n } catch (error) {\n result.errors.push(`Failed to create ./dev symlink: ${error}`);\n }\n }\n }\n\n // Note: Beads initialization is handled by the calling command (workspace.ts)\n // With beads v0.47.1+, worktrees use shared database with labels for isolation\n // The workspace.ts command creates a bead with workspace:issue-id label\n\n // Set up Cloudflare tunnel for external access (before Docker so containers can use tunnel URLs)\n if (workspaceConfig.tunnel) {\n const tunnelResult = await addTunnelIngress(workspaceConfig.tunnel, placeholders);\n result.steps.push(...tunnelResult.steps);\n if (!tunnelResult.success) {\n result.errors.push('Tunnel setup had failures (see steps for details)');\n }\n }\n\n // Create Hume EVI config and write env file for Docker (before Docker so containers pick up the config ID)\n if (workspaceConfig.hume) {\n const humeResult = await createHumeConfig(workspaceConfig.hume, placeholders);\n result.steps.push(...humeResult.steps);\n if (humeResult.configId) {\n writeFileSync(\n join(workspacePath, '.hume-config'),\n `HUME_CONFIG_ID=${humeResult.configId}\\nVITE_HUME_CONFIG_ID=${humeResult.configId}\\n`,\n );\n result.steps.push('Wrote .hume-config with Hume EVI config ID');\n }\n if (!humeResult.success) {\n result.errors.push('Hume EVI config setup had failures (see steps for details)');\n }\n }\n\n // Start Docker containers if requested\n if (startDocker) {\n // Check for Traefik\n if (workspaceConfig.docker?.traefik) {\n const traefikPath = join(projectConfig.path, workspaceConfig.docker.traefik);\n if (existsSync(traefikPath)) {\n try {\n await execAsync(`docker compose -f \"${traefikPath}\" up -d`, { cwd: projectConfig.path });\n result.steps.push('Started Traefik');\n } catch (error: any) {\n const msg = error?.message || String(error);\n if (msg.includes('port is already allocated') || msg.includes('address already in use')) {\n // Traefik (or another reverse proxy) is already running — not an error\n result.steps.push('Traefik already running (port in use)');\n } else {\n result.errors.push(`Failed to start Traefik: ${error}`);\n }\n }\n }\n }\n\n // Start workspace containers\n const composeLocations = [\n join(workspacePath, 'docker-compose.yml'),\n join(workspacePath, 'docker-compose.yaml'),\n join(workspacePath, '.devcontainer', 'docker-compose.yml'),\n join(workspacePath, '.devcontainer', 'docker-compose.devcontainer.yml'),\n ];\n\n for (const composePath of composeLocations) {\n if (existsSync(composePath)) {\n try {\n // Don't pass -p: the compose file's `name:` field is the authority.\n // Passing -p with a different value creates a second Docker project\n // on container restart, splitting services onto separate networks.\n await execAsync(`docker compose -f \"${composePath}\" up -d --build`, { cwd: dirname(composePath), timeout: 300000 });\n result.steps.push(`Started containers from ${basename(composePath)}`);\n } catch (error) {\n result.errors.push(`Failed to start containers: ${error}`);\n }\n break;\n }\n }\n }\n\n // Pre-trust workspace directory in Claude Code so agents don't get the trust prompt\n try {\n preTrustDirectory(workspacePath);\n result.steps.push('Pre-trusted workspace in Claude Code');\n } catch {\n // Non-fatal — agent can still work, user will just see trust prompt\n }\n\n result.success = result.errors.length === 0;\n return result;\n}\n\n/**\n * Pre-register a directory as trusted in Claude Code's ~/.claude.json.\n * This prevents the \"Quick safety check: Is this a project you created or one you trust?\" prompt\n * when agents are spawned in dynamically-created workspace directories.\n */\nexport function preTrustDirectory(dirPath: string): void {\n const claudeJsonPath = join(homedir(), '.claude.json');\n if (!existsSync(claudeJsonPath)) return;\n\n const data = JSON.parse(readFileSync(claudeJsonPath, 'utf8'));\n if (!data.projects) data.projects = {};\n\n // Only add if not already present\n if (data.projects[dirPath]) {\n if (!data.projects[dirPath].hasTrustDialogAccepted) {\n data.projects[dirPath].hasTrustDialogAccepted = true;\n writeFileSync(claudeJsonPath, JSON.stringify(data, null, 2), 'utf8');\n }\n return;\n }\n\n data.projects[dirPath] = {\n allowedTools: [],\n mcpContextUris: [],\n mcpServers: {},\n enabledMcpjsonServers: [],\n disabledMcpjsonServers: [],\n hasTrustDialogAccepted: true,\n projectOnboardingSeenCount: 0,\n hasClaudeMdExternalIncludesApproved: false,\n hasClaudeMdExternalIncludesWarningShown: false,\n };\n\n writeFileSync(claudeJsonPath, JSON.stringify(data, null, 2), 'utf8');\n}\n\nexport interface WorkspaceRemoveOptions {\n projectConfig: ProjectConfig;\n featureName: string;\n dryRun?: boolean;\n}\n\nexport interface WorkspaceRemoveResult {\n success: boolean;\n errors: string[];\n steps: string[];\n}\n\n/**\n * Result of Docker container cleanup for a workspace.\n */\nexport interface DockerCleanupResult {\n /** Whether compose files were found (containers may or may not have been running) */\n containersFound: boolean;\n /** Human-readable log of cleanup steps taken */\n steps: string[];\n}\n\n/**\n * Stop Docker containers and clean up Docker-created files for a workspace.\n *\n * Extracted as a standalone function so it can be used by:\n * - removeWorkspace() during normal workspace removal\n * - deep-wipe endpoint for complete issue cleanup\n * - workspace-migrate for pre-migration cleanup\n *\n * Failures are logged but never thrown — callers should not fail if Docker is unavailable.\n */\nexport async function stopWorkspaceDocker(\n workspacePath: string,\n projectName: string,\n featureName: string,\n): Promise<DockerCleanupResult> {\n const result: DockerCleanupResult = {\n containersFound: false,\n steps: [],\n };\n\n // Find all compose files in devcontainer directory (some projects use multiple)\n const devcontainerDir = join(workspacePath, '.devcontainer');\n const composeFiles: string[] = [];\n\n if (existsSync(devcontainerDir)) {\n const possibleFiles = [\n 'docker-compose.devcontainer.yml',\n 'docker-compose.yml',\n 'compose.yml',\n 'compose.infra.yml',\n 'compose.override.yml',\n ];\n for (const file of possibleFiles) {\n const fullPath = join(devcontainerDir, file);\n if (existsSync(fullPath)) {\n composeFiles.push(fullPath);\n }\n }\n }\n\n // Fallback: check for compose file in workspace root\n if (composeFiles.length === 0) {\n const rootCompose = join(workspacePath, 'docker-compose.yml');\n if (existsSync(rootCompose)) {\n composeFiles.push(rootCompose);\n }\n }\n\n if (composeFiles.length > 0) {\n result.containersFound = true;\n try {\n const fileFlags = composeFiles.map(f => `-f \"${f}\"`).join(' ');\n const cwd = existsSync(devcontainerDir) ? devcontainerDir : workspacePath;\n\n // Don't pass -p: let the compose file's `name:` field determine the project.\n // This must match what was used at startup to target the correct containers.\n await execAsync(`docker compose ${fileFlags} down -v --remove-orphans`, {\n cwd,\n timeout: 60000,\n });\n result.steps.push(`Stopped Docker containers (${composeFiles.length} compose files)`);\n } catch (error: any) {\n // Log but don't fail — containers might not be running\n result.steps.push(`Docker cleanup attempted (${error.message?.split('\\n')[0] || 'containers may not be running'})`);\n }\n }\n\n // Clean up the compose project network (docker compose down often leaves it behind\n // if Traefik or other containers were attached when it ran)\n const networkPattern = `${projectName}-feature-${featureName}_devnet`;\n try {\n const { stdout } = await execAsync(\n `docker network ls --format '{{.Name}}' --filter name=${networkPattern} 2>/dev/null`,\n { encoding: 'utf-8', timeout: 5000 }\n );\n for (const net of stdout.trim().split('\\n').filter(Boolean)) {\n try {\n await execAsync(`docker network rm \"${net}\" 2>/dev/null`, { timeout: 5000 });\n result.steps.push(`Removed network: ${net}`);\n } catch {\n // Network may still have containers — non-fatal\n }\n }\n } catch {\n // docker not available or network doesn't exist\n }\n\n // Clean up Docker-created files (root-owned in containers)\n try {\n await execAsync(\n `docker run --rm -v \"${workspacePath}:/workspace\" alpine sh -c \"find /workspace -user root -delete 2>&1 | tail -100 || true\"`,\n { timeout: 30000, maxBuffer: 10 * 1024 * 1024 }\n );\n result.steps.push('Cleaned up Docker-created files');\n } catch {\n // Alpine container might not be available\n }\n\n return result;\n}\n\n/**\n * Remove a workspace\n */\nexport async function removeWorkspace(options: WorkspaceRemoveOptions): Promise<WorkspaceRemoveResult> {\n const { projectConfig, featureName, dryRun } = options;\n const result: WorkspaceRemoveResult = {\n success: true,\n errors: [],\n steps: [],\n };\n\n const workspaceConfig = projectConfig.workspace || getDefaultWorkspaceConfig();\n const workspacesDir = join(projectConfig.path, workspaceConfig.workspaces_dir || 'workspaces');\n const featureFolder = `feature-${featureName}`;\n const workspacePath = join(workspacesDir, featureFolder);\n\n if (!existsSync(workspacePath)) {\n result.success = false;\n result.errors.push(`Workspace not found at ${workspacePath}`);\n return result;\n }\n\n if (dryRun) {\n result.steps.push('[DRY RUN] Would remove workspace at: ' + workspacePath);\n return result;\n }\n\n // Stop TLDR daemon for workspace (if it exists)\n const venvPath = join(workspacePath, '.venv');\n if (existsSync(venvPath)) {\n try {\n const { getTldrDaemonService } = await import('./tldr-daemon.js');\n const tldrService = getTldrDaemonService(workspacePath, venvPath);\n await tldrService.stop();\n result.steps.push('Stopped TLDR daemon');\n } catch (error: any) {\n // Non-fatal - daemon may not be running\n console.warn(`⚠ Failed to stop TLDR daemon: ${error?.message}`);\n }\n }\n\n // Stop Docker containers and clean up Docker-created files\n const dockerResult = await stopWorkspaceDocker(workspacePath, projectConfig.name || 'workspace', featureName);\n result.steps.push(...dockerResult.steps);\n\n // Remove worktrees\n if (workspaceConfig.type === 'polyrepo' && workspaceConfig.repos) {\n for (const repo of workspaceConfig.repos) {\n const repoPath = join(projectConfig.path, repo.path);\n const targetPath = join(workspacePath, repo.name);\n const branchPrefix = repo.branch_prefix || 'feature/';\n const branchName = `${branchPrefix}${featureName}`;\n\n const worktreeResult = await removeWorktree(repoPath, targetPath, branchName);\n if (worktreeResult.success) {\n result.steps.push(`Removed worktree for ${repo.name}`);\n } else {\n result.errors.push(worktreeResult.message);\n }\n }\n } else {\n // Monorepo: remove single worktree\n const branchName = `feature/${featureName}`;\n const worktreeResult = await removeWorktree(projectConfig.path, workspacePath, branchName);\n if (worktreeResult.success) {\n result.steps.push('Removed worktree');\n } else {\n result.errors.push(worktreeResult.message);\n }\n }\n\n // Remove DNS entries\n if (workspaceConfig.dns) {\n const placeholders = createPlaceholders(projectConfig, featureName, workspacePath);\n\n const dnsMethod = workspaceConfig.dns.sync_method || 'wsl2hosts';\n for (const entryPattern of workspaceConfig.dns.entries) {\n const hostname = replacePlaceholders(entryPattern, placeholders);\n if (removeDnsEntry(dnsMethod, hostname)) {\n result.steps.push(`Removed DNS entry: ${hostname}`);\n }\n }\n }\n\n // Remove Cloudflare tunnel entries\n if (workspaceConfig.tunnel) {\n const placeholders = createPlaceholders(projectConfig, featureName, workspacePath);\n const tunnelResult = await removeTunnelIngress(workspaceConfig.tunnel, placeholders);\n result.steps.push(...tunnelResult.steps);\n }\n\n // Remove Hume EVI config\n if (workspaceConfig.hume) {\n const placeholders = createPlaceholders(projectConfig, featureName, workspacePath);\n const humeResult = await deleteHumeConfig(workspaceConfig.hume, placeholders);\n result.steps.push(...humeResult.steps);\n }\n\n // Release ports\n if (workspaceConfig.ports) {\n for (const [portName] of Object.entries(workspaceConfig.ports)) {\n const portFile = join(projectConfig.path, `.${portName}-ports`);\n if (releasePort(portFile, featureFolder)) {\n result.steps.push(`Released ${portName} port`);\n }\n }\n }\n\n // Remove workspace directory\n try {\n await execAsync(`rm -rf \"${workspacePath}\"`, { maxBuffer: 10 * 1024 * 1024 });\n result.steps.push('Removed workspace directory');\n } catch (error) {\n result.errors.push(`Failed to remove workspace directory: ${error}`);\n }\n\n result.success = result.errors.length === 0;\n return result;\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","writeFileSync","readFileSync","readdirSync","copyFileSync","join","dirname","copyDir","count","devcontainerDir"]}
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  init_projects,
7
7
  loadProjectsConfig
8
- } from "./chunk-OMNXYPXC.js";
8
+ } from "./chunk-2V4NF7J2.js";
9
9
  import {
10
10
  SOURCE_TRAEFIK_TEMPLATES,
11
11
  TRAEFIK_CERTS_DIR,
@@ -151,4 +151,4 @@ export {
151
151
  ensureProjectCerts,
152
152
  cleanupStaleTlsSections
153
153
  };
154
- //# sourceMappingURL=chunk-PPRFKTVC.js.map
154
+ //# sourceMappingURL=chunk-NLQRED36.js.map