gitflow-analytics 3.8.1__py3-none-any.whl → 3.9.2__py3-none-any.whl

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.
@@ -1,4 +1,4 @@
1
1
  """Version information for gitflow-analytics."""
2
2
 
3
- __version__ = "3.8.1"
3
+ __version__ = "3.9.2"
4
4
  __version_info__ = tuple(int(x) for x in __version__.split("."))
@@ -33,7 +33,7 @@ class InstallWizard:
33
33
  PROFILES = {
34
34
  "1": {
35
35
  "name": "Standard",
36
- "description": "GitHub + JIRA + AI (Full featured)",
36
+ "description": "GitHub + PM Tools (JIRA/Linear/ClickUp/GitHub Issues) + AI (Full featured)",
37
37
  "github": True,
38
38
  "repositories": "manual",
39
39
  "jira": True,
@@ -176,12 +176,55 @@ class InstallWizard:
176
176
  # Custom mode - ask user
177
177
  return False
178
178
 
179
- # Step 3: JIRA Setup (conditional based on profile)
179
+ # Step 3: PM Platform Setup (conditional based on profile)
180
180
  if self.profile["jira"]:
181
- self._setup_jira()
181
+ # Profile includes PM tools - let user select which ones
182
+ selected_platforms = self._select_pm_platforms()
183
+
184
+ # Setup each selected platform
185
+ if "jira" in selected_platforms:
186
+ self._setup_jira()
187
+ if "linear" in selected_platforms:
188
+ linear_config = self._setup_linear()
189
+ if linear_config:
190
+ if "pm" not in self.config_data:
191
+ self.config_data["pm"] = {}
192
+ self.config_data["pm"]["linear"] = linear_config
193
+ if "clickup" in selected_platforms:
194
+ clickup_config = self._setup_clickup()
195
+ if clickup_config:
196
+ if "pm" not in self.config_data:
197
+ self.config_data["pm"] = {}
198
+ self.config_data["pm"]["clickup"] = clickup_config
199
+ # GitHub Issues uses github.token automatically - no separate setup needed
200
+
201
+ # Store selected platforms for analysis configuration
202
+ self._selected_pm_platforms = selected_platforms
182
203
  elif self.profile["jira"] is None:
183
204
  # Custom mode - ask user
184
- self._setup_jira()
205
+ selected_platforms = self._select_pm_platforms()
206
+
207
+ # Setup each selected platform
208
+ if "jira" in selected_platforms:
209
+ self._setup_jira()
210
+ if "linear" in selected_platforms:
211
+ linear_config = self._setup_linear()
212
+ if linear_config:
213
+ if "pm" not in self.config_data:
214
+ self.config_data["pm"] = {}
215
+ self.config_data["pm"]["linear"] = linear_config
216
+ if "clickup" in selected_platforms:
217
+ clickup_config = self._setup_clickup()
218
+ if clickup_config:
219
+ if "pm" not in self.config_data:
220
+ self.config_data["pm"] = {}
221
+ self.config_data["pm"]["clickup"] = clickup_config
222
+
223
+ # Store selected platforms for analysis configuration
224
+ self._selected_pm_platforms = selected_platforms
225
+ else:
226
+ # Profile excludes PM tools
227
+ self._selected_pm_platforms = []
185
228
 
186
229
  # Step 4: OpenRouter/ChatGPT Setup (conditional based on profile)
187
230
  if self.profile["ai"]:
@@ -282,6 +325,64 @@ class InstallWizard:
282
325
 
283
326
  return False
284
327
 
328
+ def _select_pm_platforms(self) -> list:
329
+ """Let user select which PM platforms to configure.
330
+
331
+ Returns:
332
+ List of selected platform names (e.g., ['jira', 'linear'])
333
+ """
334
+ click.echo("\n📋 Project Management Platform Selection")
335
+ click.echo("-" * 50)
336
+ click.echo("Select which PM platforms you want to configure:\n")
337
+ click.echo(" 1. JIRA (Atlassian)")
338
+ click.echo(" 2. Linear (linear.app)")
339
+ click.echo(" 3. ClickUp (clickup.com)")
340
+ click.echo(" 4. GitHub Issues (Auto-configured with your GitHub token)")
341
+ click.echo()
342
+ click.echo("Enter numbers separated by spaces or commas (e.g., '1 2 4' or '1,2,4')")
343
+ click.echo("Press Enter to skip all PM platform setup")
344
+ click.echo()
345
+
346
+ selection = click.prompt(
347
+ "Select platforms",
348
+ type=str,
349
+ default="",
350
+ show_default=False,
351
+ ).strip()
352
+
353
+ if not selection:
354
+ click.echo("⏭️ Skipping all PM platform setup")
355
+ return []
356
+
357
+ # Parse selection (handle both space and comma separated)
358
+ selection = selection.replace(",", " ")
359
+ choices = selection.split()
360
+
361
+ platforms = []
362
+ platform_map = {"1": "jira", "2": "linear", "3": "clickup", "4": "github"}
363
+
364
+ for choice in choices:
365
+ if choice in platform_map:
366
+ platforms.append(platform_map[choice])
367
+
368
+ if not platforms:
369
+ click.echo(
370
+ "⚠️ No valid platforms selected, defaulting to JIRA for backward compatibility"
371
+ )
372
+ return ["jira"]
373
+
374
+ # Display selected platforms
375
+ platform_names = {
376
+ "jira": "JIRA",
377
+ "linear": "Linear",
378
+ "clickup": "ClickUp",
379
+ "github": "GitHub Issues",
380
+ }
381
+ selected_names = [platform_names[p] for p in platforms]
382
+ click.echo(f"\n✅ Selected platforms: {', '.join(selected_names)}\n")
383
+
384
+ return platforms
385
+
285
386
  def _setup_repositories(self) -> bool:
286
387
  """Setup repository configuration.
287
388
 
@@ -558,6 +659,271 @@ class InstallWizard:
558
659
 
559
660
  click.echo("⏭️ Skipping JIRA setup")
560
661
 
662
+ def _setup_linear(self) -> Optional[dict]:
663
+ """Setup Linear integration (optional).
664
+
665
+ Returns:
666
+ Linear configuration dict if successful, None otherwise
667
+ """
668
+ click.echo("\n📋 Linear Setup")
669
+ click.echo("-" * 50)
670
+ click.echo("\nLinear Configuration:")
671
+ click.echo("You'll need:")
672
+ click.echo(" • Linear API key from: https://linear.app/settings/api")
673
+ click.echo(" • Optional: Team IDs to filter issues")
674
+ click.echo()
675
+
676
+ max_retries = 3
677
+ for attempt in range(max_retries):
678
+ if attempt > 0:
679
+ # Add exponential backoff to prevent rate limiting
680
+ delay = 2 ** (attempt - 1) # 1, 2, 4 seconds
681
+ click.echo(f"⏳ Waiting {delay} seconds before retry...")
682
+ time.sleep(delay)
683
+
684
+ api_key = self._get_password("Linear API key: ", "Linear API key").strip()
685
+
686
+ if not api_key:
687
+ click.echo("❌ API key cannot be empty")
688
+ continue
689
+
690
+ # Validate Linear credentials
691
+ if not self.skip_validation:
692
+ click.echo("🔍 Validating Linear API key...")
693
+ if self._validate_linear(api_key):
694
+ click.echo("✅ Linear API key validated!")
695
+
696
+ # Optional: Team IDs
697
+ team_ids = click.prompt(
698
+ "Team IDs (comma-separated, press Enter to skip)",
699
+ type=str,
700
+ default="",
701
+ show_default=False,
702
+ ).strip()
703
+
704
+ # Store configuration
705
+ self.env_data["LINEAR_API_KEY"] = api_key
706
+ linear_config = {
707
+ "api_key": "${LINEAR_API_KEY}",
708
+ }
709
+
710
+ if team_ids:
711
+ team_list = [tid.strip() for tid in team_ids.split(",") if tid.strip()]
712
+ if team_list:
713
+ linear_config["team_ids"] = team_list
714
+ click.echo(f"✅ Configured {len(team_list)} team ID(s)")
715
+
716
+ return linear_config
717
+ else:
718
+ if attempt < max_retries - 1:
719
+ retry = click.confirm("Linear validation failed. Try again?", default=True)
720
+ if not retry:
721
+ click.echo("⏭️ Skipping Linear setup")
722
+ return None
723
+ else:
724
+ click.echo("❌ Maximum retry attempts reached")
725
+ click.echo("⏭️ Skipping Linear setup")
726
+ return None
727
+ else:
728
+ # Skip validation mode
729
+ self.env_data["LINEAR_API_KEY"] = api_key
730
+ return {"api_key": "${LINEAR_API_KEY}"}
731
+
732
+ click.echo("⏭️ Skipping Linear setup")
733
+ return None
734
+
735
+ def _validate_linear(self, api_key: str) -> bool:
736
+ """Validate Linear API key.
737
+
738
+ Args:
739
+ api_key: Linear API key
740
+
741
+ Returns:
742
+ True if credentials are valid, False otherwise
743
+ """
744
+ # Suppress requests logging to prevent credential exposure
745
+ urllib3_logger = logging.getLogger("urllib3")
746
+ requests_logger = logging.getLogger("requests")
747
+ original_urllib3 = urllib3_logger.level
748
+ original_requests = requests_logger.level
749
+
750
+ urllib3_logger.setLevel(logging.WARNING)
751
+ requests_logger.setLevel(logging.WARNING)
752
+
753
+ try:
754
+ headers = {
755
+ "Authorization": api_key,
756
+ "Content-Type": "application/json",
757
+ }
758
+
759
+ # Simple GraphQL query to validate authentication
760
+ query = {"query": "{ viewer { name } }"}
761
+
762
+ response = requests.post(
763
+ "https://api.linear.app/graphql",
764
+ headers=headers,
765
+ json=query,
766
+ timeout=10,
767
+ verify=True,
768
+ )
769
+
770
+ if response.status_code == 200:
771
+ data = response.json()
772
+ if "data" in data and "viewer" in data["data"]:
773
+ viewer_name = data["data"]["viewer"].get("name", "Unknown")
774
+ click.echo(f" Authenticated as: {viewer_name}")
775
+ return True
776
+ else:
777
+ click.echo(" Authentication failed: Invalid response")
778
+ return False
779
+ else:
780
+ click.echo(f" Authentication failed (status {response.status_code})")
781
+ return False
782
+
783
+ except requests.exceptions.RequestException as e:
784
+ # Never expose raw exception - could contain credentials
785
+ error_type = type(e).__name__
786
+ click.echo(f" Connection error: {error_type}")
787
+ logger.debug(f"Linear connection error type: {error_type}")
788
+ return False
789
+ except Exception as e:
790
+ click.echo(" Linear validation failed")
791
+ logger.error(f"Linear validation error type: {type(e).__name__}")
792
+ return False
793
+ finally:
794
+ # Always restore logging levels
795
+ urllib3_logger.setLevel(original_urllib3)
796
+ requests_logger.setLevel(original_requests)
797
+
798
+ def _setup_clickup(self) -> Optional[dict]:
799
+ """Setup ClickUp integration (optional).
800
+
801
+ Returns:
802
+ ClickUp configuration dict if successful, None otherwise
803
+ """
804
+ click.echo("\n📋 ClickUp Setup")
805
+ click.echo("-" * 50)
806
+ click.echo("\nClickUp Configuration:")
807
+ click.echo("You'll need:")
808
+ click.echo(" • ClickUp API token from: https://app.clickup.com/settings/apps")
809
+ click.echo(" • Workspace URL (e.g., https://app.clickup.com/12345/v/)")
810
+ click.echo()
811
+
812
+ max_retries = 3
813
+ for attempt in range(max_retries):
814
+ if attempt > 0:
815
+ # Add exponential backoff to prevent rate limiting
816
+ delay = 2 ** (attempt - 1) # 1, 2, 4 seconds
817
+ click.echo(f"⏳ Waiting {delay} seconds before retry...")
818
+ time.sleep(delay)
819
+
820
+ api_token = self._get_password("ClickUp API token: ", "ClickUp API token").strip()
821
+ workspace_url = click.prompt("ClickUp workspace URL", type=str).strip()
822
+
823
+ if not all([api_token, workspace_url]):
824
+ click.echo("❌ All ClickUp fields are required")
825
+ continue
826
+
827
+ # Normalize workspace_url
828
+ workspace_url = workspace_url.rstrip("/")
829
+
830
+ # Validate ClickUp credentials
831
+ if not self.skip_validation:
832
+ click.echo("🔍 Validating ClickUp credentials...")
833
+ if self._validate_clickup(api_token):
834
+ click.echo("✅ ClickUp credentials validated!")
835
+
836
+ # Store configuration
837
+ self.env_data["CLICKUP_API_TOKEN"] = api_token
838
+ self.env_data["CLICKUP_WORKSPACE_URL"] = workspace_url
839
+
840
+ clickup_config = {
841
+ "api_token": "${CLICKUP_API_TOKEN}",
842
+ "workspace_url": "${CLICKUP_WORKSPACE_URL}",
843
+ }
844
+
845
+ return clickup_config
846
+ else:
847
+ if attempt < max_retries - 1:
848
+ retry = click.confirm("ClickUp validation failed. Try again?", default=True)
849
+ if not retry:
850
+ click.echo("⏭️ Skipping ClickUp setup")
851
+ return None
852
+ else:
853
+ click.echo("❌ Maximum retry attempts reached")
854
+ click.echo("⏭️ Skipping ClickUp setup")
855
+ return None
856
+ else:
857
+ # Skip validation mode
858
+ self.env_data["CLICKUP_API_TOKEN"] = api_token
859
+ self.env_data["CLICKUP_WORKSPACE_URL"] = workspace_url
860
+ return {
861
+ "api_token": "${CLICKUP_API_TOKEN}",
862
+ "workspace_url": "${CLICKUP_WORKSPACE_URL}",
863
+ }
864
+
865
+ click.echo("⏭️ Skipping ClickUp setup")
866
+ return None
867
+
868
+ def _validate_clickup(self, api_token: str) -> bool:
869
+ """Validate ClickUp API token.
870
+
871
+ Args:
872
+ api_token: ClickUp API token
873
+
874
+ Returns:
875
+ True if credentials are valid, False otherwise
876
+ """
877
+ # Suppress requests logging to prevent credential exposure
878
+ urllib3_logger = logging.getLogger("urllib3")
879
+ requests_logger = logging.getLogger("requests")
880
+ original_urllib3 = urllib3_logger.level
881
+ original_requests = requests_logger.level
882
+
883
+ urllib3_logger.setLevel(logging.WARNING)
884
+ requests_logger.setLevel(logging.WARNING)
885
+
886
+ try:
887
+ headers = {
888
+ "Authorization": api_token,
889
+ "Content-Type": "application/json",
890
+ }
891
+
892
+ response = requests.get(
893
+ "https://api.clickup.com/api/v2/user",
894
+ headers=headers,
895
+ timeout=10,
896
+ verify=True,
897
+ )
898
+
899
+ if response.status_code == 200:
900
+ user_info = response.json()
901
+ if "user" in user_info:
902
+ username = user_info["user"].get("username", "Unknown")
903
+ click.echo(f" Authenticated as: {username}")
904
+ return True
905
+ else:
906
+ click.echo(" Authentication failed: Invalid response")
907
+ return False
908
+ else:
909
+ click.echo(f" Authentication failed (status {response.status_code})")
910
+ return False
911
+
912
+ except requests.exceptions.RequestException as e:
913
+ # Never expose raw exception - could contain credentials
914
+ error_type = type(e).__name__
915
+ click.echo(f" Connection error: {error_type}")
916
+ logger.debug(f"ClickUp connection error type: {error_type}")
917
+ return False
918
+ except Exception as e:
919
+ click.echo(" ClickUp validation failed")
920
+ logger.error(f"ClickUp validation error type: {type(e).__name__}")
921
+ return False
922
+ finally:
923
+ # Always restore logging levels
924
+ urllib3_logger.setLevel(original_urllib3)
925
+ requests_logger.setLevel(original_requests)
926
+
561
927
  def _validate_jira(self, base_url: str, username: str, api_token: str) -> bool:
562
928
  """Validate JIRA credentials.
563
929
 
@@ -946,6 +1312,25 @@ class InstallWizard:
946
1312
  click.echo(" gitflow-analytics aliases -c config.yaml --apply")
947
1313
  click.echo(" This will analyze your repos and generate aliases automatically.\n")
948
1314
 
1315
+ # Configure ticket platforms based on selected PM tools
1316
+ if hasattr(self, "_selected_pm_platforms") and self._selected_pm_platforms:
1317
+ ticket_platforms = []
1318
+
1319
+ # Add platforms in order of setup
1320
+ if "jira" in self._selected_pm_platforms:
1321
+ ticket_platforms.append("jira")
1322
+ if "linear" in self._selected_pm_platforms:
1323
+ ticket_platforms.append("linear")
1324
+ if "clickup" in self._selected_pm_platforms:
1325
+ ticket_platforms.append("clickup")
1326
+ if "github" in self._selected_pm_platforms or "github" in self.config_data:
1327
+ # GitHub Issues auto-configured with GitHub token
1328
+ ticket_platforms.append("github")
1329
+
1330
+ if ticket_platforms:
1331
+ self.config_data["analysis"]["ticket_platforms"] = ticket_platforms
1332
+ click.echo(f"✅ Configured ticket platforms: {', '.join(ticket_platforms)}\n")
1333
+
949
1334
  def _clear_sensitive_data(self) -> None:
950
1335
  """Clear sensitive data from memory after use."""
951
1336
  sensitive_keys = ["TOKEN", "KEY", "PASSWORD", "SECRET"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitflow-analytics
3
- Version: 3.8.1
3
+ Version: 3.9.2
4
4
  Summary: Analyze Git repositories for developer productivity insights
5
5
  Author-email: Bob Matyas <bobmatnyc@gmail.com>
6
6
  License: MIT
@@ -338,13 +338,13 @@ Here's a complete example showing `.env` file and corresponding YAML configurati
338
338
  GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
339
339
  GITHUB_ORG=your-organization
340
340
 
341
- # JIRA Configuration
341
+ # PM Platform Configuration
342
342
  JIRA_ACCESS_USER=developer@company.com
343
343
  JIRA_ACCESS_TOKEN=ATATT3xxxxxxxxxxx
344
+ LINEAR_API_KEY=lin_api_xxxxxxxxxxxx
345
+ CLICKUP_API_TOKEN=pk_xxxxxxxxxxxx
344
346
 
345
- # Optional: Other integrations
346
- # CLICKUP_TOKEN=pk_xxxxxxxxxxxx
347
- # LINEAR_TOKEN=lin_api_xxxxxxxxxxxx
347
+ # Note: GitHub Issues uses GITHUB_TOKEN automatically
348
348
  ```
349
349
 
350
350
  ### `config.yaml` file
@@ -356,12 +356,22 @@ github:
356
356
  token: "${GITHUB_TOKEN}"
357
357
  organization: "${GITHUB_ORG}"
358
358
 
359
- # JIRA integration for story points
360
- jira:
361
- access_user: "${JIRA_ACCESS_USER}"
362
- access_token: "${JIRA_ACCESS_TOKEN}"
363
- base_url: "https://company.atlassian.net"
359
+ # Multi-platform PM integration
360
+ pm:
361
+ jira:
362
+ access_user: "${JIRA_ACCESS_USER}"
363
+ access_token: "${JIRA_ACCESS_TOKEN}"
364
+ base_url: "https://company.atlassian.net"
364
365
 
366
+ linear:
367
+ api_key: "${LINEAR_API_KEY}"
368
+ team_ids: ["team_123abc"] # Optional: filter by specific teams
369
+
370
+ clickup:
371
+ api_token: "${CLICKUP_API_TOKEN}"
372
+ workspace_url: "https://app.clickup.com/12345/v/"
373
+
374
+ # JIRA story point integration (optional)
365
375
  jira_integration:
366
376
  enabled: true
367
377
  fetch_story_points: true
@@ -371,9 +381,12 @@ jira_integration:
371
381
 
372
382
  # Analysis configuration
373
383
  analysis:
374
- # Only track JIRA tickets (ignore GitHub issues, etc.)
384
+ # Track tickets from all configured platforms
375
385
  ticket_platforms:
376
386
  - jira
387
+ - linear
388
+ - clickup
389
+ - github # GitHub Issues (uses GITHUB_TOKEN)
377
390
 
378
391
  # Exclude bot commits and boilerplate files
379
392
  exclude:
@@ -666,24 +679,97 @@ story_point_patterns:
666
679
 
667
680
  ## Ticket Platform Support
668
681
 
669
- Automatically detects and tracks tickets from:
682
+ Automatically detects and tracks tickets from multiple PM platforms:
670
683
  - **JIRA**: `PROJ-123`
671
- - **GitHub**: `#123`, `GH-123`
684
+ - **GitHub Issues**: `#123`, `GH-123`
672
685
  - **ClickUp**: `CU-abc123`
673
686
  - **Linear**: `ENG-123`
674
687
 
675
- ### JIRA Integration
688
+ ### Multi-Platform PM Integration
676
689
 
677
- GitFlow Analytics can fetch story points directly from JIRA tickets. Configure your JIRA instance:
690
+ GitFlow Analytics supports multiple project management platforms simultaneously. You can configure one or more platforms based on your team's workflow:
678
691
 
679
692
  ```yaml
680
- jira:
681
- access_user: "${JIRA_ACCESS_USER}"
682
- access_token: "${JIRA_ACCESS_TOKEN}"
683
- base_url: "https://your-company.atlassian.net"
693
+ # Configure which platforms to track
694
+ analysis:
695
+ ticket_platforms:
696
+ - jira
697
+ - linear
698
+ - clickup
699
+ - github # GitHub Issues
700
+
701
+ # Platform-specific configuration
702
+ pm:
703
+ jira:
704
+ access_user: "${JIRA_ACCESS_USER}"
705
+ access_token: "${JIRA_ACCESS_TOKEN}"
706
+ base_url: "https://your-company.atlassian.net"
707
+
708
+ linear:
709
+ api_key: "${LINEAR_API_KEY}"
710
+ team_ids: # Optional: filter by team
711
+ - "team_123abc"
712
+
713
+ clickup:
714
+ api_token: "${CLICKUP_API_TOKEN}"
715
+ workspace_url: "https://app.clickup.com/12345/v/"
716
+
717
+ # GitHub Issues uses existing GitHub token automatically
718
+ github:
719
+ token: "${GITHUB_TOKEN}"
720
+ ```
684
721
 
722
+ ### Platform Setup Guides
723
+
724
+ #### JIRA Setup
725
+ 1. **Get API Token**: Go to [Atlassian API Tokens](https://id.atlassian.com/manage-profile/security/api-tokens)
726
+ 2. **Required Permissions**: Read access to projects and issues
727
+ 3. **Configuration**:
728
+ ```yaml
729
+ pm:
730
+ jira:
731
+ access_user: "${JIRA_ACCESS_USER}" # Your Atlassian email
732
+ access_token: "${JIRA_ACCESS_TOKEN}"
733
+ base_url: "https://your-company.atlassian.net"
734
+ ```
735
+
736
+ #### Linear Setup
737
+ 1. **Get API Key**: Go to [Linear Settings → API](https://linear.app/settings/api)
738
+ 2. **Required Permissions**: Read access to issues
739
+ 3. **Configuration**:
740
+ ```yaml
741
+ pm:
742
+ linear:
743
+ api_key: "${LINEAR_API_KEY}"
744
+ team_ids: ["team_123abc"] # Optional: specify team IDs
745
+ ```
746
+
747
+ #### ClickUp Setup
748
+ 1. **Get API Token**: Go to [ClickUp Settings → Apps](https://app.clickup.com/settings/apps)
749
+ 2. **Get Workspace URL**: Copy from browser when viewing your workspace
750
+ 3. **Configuration**:
751
+ ```yaml
752
+ pm:
753
+ clickup:
754
+ api_token: "${CLICKUP_API_TOKEN}"
755
+ workspace_url: "https://app.clickup.com/12345/v/"
756
+ ```
757
+
758
+ #### GitHub Issues Setup
759
+ GitHub Issues is automatically enabled when GitHub integration is configured. No additional setup required:
760
+ ```yaml
761
+ github:
762
+ token: "${GITHUB_TOKEN}" # Same token for repo access and issues
763
+ ```
764
+
765
+ ### JIRA Story Point Integration
766
+
767
+ GitFlow Analytics can fetch story points directly from JIRA tickets:
768
+
769
+ ```yaml
685
770
  jira_integration:
686
771
  enabled: true
772
+ fetch_story_points: true
687
773
  story_point_fields:
688
774
  - "Story point estimate" # Your custom field name
689
775
  - "customfield_10016" # Or use field ID
@@ -694,6 +780,21 @@ To discover your JIRA story point fields:
694
780
  gitflow-analytics discover-storypoint-fields -c config.yaml
695
781
  ```
696
782
 
783
+ ### Environment Variables for Credentials
784
+
785
+ Store credentials securely in a `.env` file:
786
+
787
+ ```bash
788
+ # .env file (keep this secure and don't commit to git!)
789
+ GITHUB_TOKEN=ghp_your_token_here
790
+
791
+ # PM Platform Credentials
792
+ JIRA_ACCESS_USER=your.email@company.com
793
+ JIRA_ACCESS_TOKEN=ATATT3xxxxxxxxxxx
794
+ LINEAR_API_KEY=lin_api_xxxxxxxxxxxx
795
+ CLICKUP_API_TOKEN=pk_xxxxxxxxxxxx
796
+ ```
797
+
697
798
  ## Caching
698
799
 
699
800
  The tool uses SQLite for intelligent caching:
@@ -1,5 +1,5 @@
1
1
  gitflow_analytics/__init__.py,sha256=W3Jaey5wuT1nBPehVLTIRkVIyBa5jgYOlBKc_UFfh-4,773
2
- gitflow_analytics/_version.py,sha256=5C5GuURF4I6gU-R6wAwn2wFCU4AI8jhQuJ2WgKIngIk,137
2
+ gitflow_analytics/_version.py,sha256=1vJ2cOJm4JBq3ASHO5cRbx3_lFVH3QIh1Caj_cSjWN4,137
3
3
  gitflow_analytics/cli.py,sha256=pYW6V0b6SRa3-NyOmXGQhf5emcKHUHgOVL2PFOAS8LQ,273331
4
4
  gitflow_analytics/config.py,sha256=XRuxvzLWyn_ML7mDCcuZ9-YFNAEsnt33vIuWxQQ_jxg,1033
5
5
  gitflow_analytics/constants.py,sha256=GXEncUJS9ijOI5KWtQCTANwdqxPfXpw-4lNjhaWTKC4,2488
@@ -11,7 +11,7 @@ gitflow_analytics/classification/feature_extractor.py,sha256=W82vztPQO8-MFw9Yt17
11
11
  gitflow_analytics/classification/linguist_analyzer.py,sha256=HjLx9mM7hGXtrvMba6osovHJLAacTx9oDmN6CS5w0bE,17687
12
12
  gitflow_analytics/classification/model.py,sha256=2KbmFh9MpyvHMcNHbqwUTAAVLHHu3MiTfFIPyZSGa-8,16356
13
13
  gitflow_analytics/cli_wizards/__init__.py,sha256=D73D97cS1hZsB_fCQQaAiWtd_w2Lb8TtcGc9Pn2DIyE,343
14
- gitflow_analytics/cli_wizards/install_wizard.py,sha256=ib2H1JOaV2ts9iXN0nhHIxtITWhe22XCiT2gqpZa0HI,45045
14
+ gitflow_analytics/cli_wizards/install_wizard.py,sha256=KcmDD2RiqFrPuXdBhmXCYvftlV_V-_7p82Z6Gu8NgO0,60817
15
15
  gitflow_analytics/cli_wizards/run_launcher.py,sha256=J6G_C7IqxPg7_GhAfbV99D1dIIWwb1s_qmHC7Iv2iGI,15038
16
16
  gitflow_analytics/config/__init__.py,sha256=KziRIbBJctB5LOLcKLzELWA1rXwjS6-C2_DeM_hT9rM,1133
17
17
  gitflow_analytics/config/aliases.py,sha256=z9F0X6qbbF544Tw7sHlOoBj5mpRSddMkCpoKLzvVzDU,10960
@@ -120,9 +120,9 @@ gitflow_analytics/training/model_loader.py,sha256=xGZLSopGxDhC--2XN6ytRgi2CyjOKY
120
120
  gitflow_analytics/training/pipeline.py,sha256=PQegTk_-OsPexVyRDfiy-3Df-7pcs25C4vPASr-HT9E,19951
121
121
  gitflow_analytics/ui/__init__.py,sha256=UBhYhZMvwlSrCuGWjkIdoP2zNbiQxOHOli-I8mqIZUE,441
122
122
  gitflow_analytics/ui/progress_display.py,sha256=3xJnCOSs1DRVAfS-rTu37EsLfWDFW5-mbv-bPS9NMm4,59182
123
- gitflow_analytics-3.8.1.dist-info/licenses/LICENSE,sha256=xwvSwY1GYXpRpmbnFvvnbmMwpobnrdN9T821sGvjOY0,1066
124
- gitflow_analytics-3.8.1.dist-info/METADATA,sha256=gKBIn_JFUkkRTpCIe9UQ9OTLv3ZwGE5hBPwdTxRqH_w,34051
125
- gitflow_analytics-3.8.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
126
- gitflow_analytics-3.8.1.dist-info/entry_points.txt,sha256=ZOsX0GLsnMysp5FWPOfP_qyoS7WJ8IgcaDFDxWBYl1g,98
127
- gitflow_analytics-3.8.1.dist-info/top_level.txt,sha256=CQyxZXjKvpSB1kgqqtuE0PCRqfRsXZJL8JrYpJKtkrk,18
128
- gitflow_analytics-3.8.1.dist-info/RECORD,,
123
+ gitflow_analytics-3.9.2.dist-info/licenses/LICENSE,sha256=xwvSwY1GYXpRpmbnFvvnbmMwpobnrdN9T821sGvjOY0,1066
124
+ gitflow_analytics-3.9.2.dist-info/METADATA,sha256=zFtrIA8qcqvHtfmDMA9wqTLf2RQbhweU02RzzqqeerY,36830
125
+ gitflow_analytics-3.9.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
126
+ gitflow_analytics-3.9.2.dist-info/entry_points.txt,sha256=ZOsX0GLsnMysp5FWPOfP_qyoS7WJ8IgcaDFDxWBYl1g,98
127
+ gitflow_analytics-3.9.2.dist-info/top_level.txt,sha256=CQyxZXjKvpSB1kgqqtuE0PCRqfRsXZJL8JrYpJKtkrk,18
128
+ gitflow_analytics-3.9.2.dist-info/RECORD,,