Skip to content

Commit aaa6430

Browse files
committed
create-issue fix
1 parent 5ffd467 commit aaa6430

21 files changed

Lines changed: 338 additions & 192 deletions

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.6.3] - 2026-03-27
9+
10+
### Fixed
11+
12+
- **Countermeasure issue tracker command** - Fixed a bug where `countermeasure create-issue` would fail to create issues in the selected issue tracker.
13+
814
## [0.6.2] - 2026-02-18
915

1016
### Fixed
@@ -403,6 +409,7 @@ Multiple repositories can now contribute to a single IriusRisk project, enabling
403409
- Configuration best practices
404410
- AI workflow examples
405411

412+
[0.6.3]: https://github.com/iriusrisk/iriusrisk_cli/releases/tag/v0.6.3
406413
[0.6.2]: https://github.com/iriusrisk/iriusrisk_cli/releases/tag/v0.6.2
407414
[0.6.1]: https://github.com/iriusrisk/iriusrisk_cli/releases/tag/v0.6.1
408415
[0.6.0]: https://github.com/iriusrisk/iriusrisk_cli/releases/tag/v0.6.0

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
setup(
1010
name="iriusrisk-cli",
11-
version="0.6.2",
11+
version="0.6.3",
1212
author="IriusRisk",
1313
author_email="support@iriusrisk.com",
1414
description="AI-powered threat modeling integration for IriusRisk. Command line interface and MCP server for security analysis.",

src/iriusrisk_cli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""IriusRisk CLI - A command line interface for IriusRisk API v2."""
22

3-
__version__ = "0.6.2"
3+
__version__ = "0.6.3"
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""Issue tracker API client for IriusRisk API."""
2+
3+
from typing import Dict, Any, Optional
4+
5+
from .base_client import BaseApiClient
6+
from ..config import Config
7+
8+
9+
class IssueTrackerApiClient(BaseApiClient):
10+
"""API client for issue tracker profile operations."""
11+
12+
def __init__(self, config: Optional[Config] = None):
13+
"""Initialize the issue tracker API client.
14+
15+
Args:
16+
config: Configuration instance (creates new one if not provided)
17+
"""
18+
super().__init__(config)
19+
20+
def get_issue_tracker_profiles(self, page: int = 0, size: int = 9999) -> Dict[str, Any]:
21+
"""Get all available issue tracker profiles.
22+
23+
Args:
24+
page: Page number (0-based)
25+
size: Number of profiles per page
26+
27+
Returns:
28+
Issue tracker profiles response with _embedded.items containing the profiles
29+
"""
30+
params = {
31+
'page': page,
32+
'size': size
33+
}
34+
return self._make_request('GET', '/issue-tracker-profiles/summary', params=params)
35+
36+
def get_project_issue_trackers(self, project_id: str, page: int = 0, size: int = 9999) -> Dict[str, Any]:
37+
"""Get issue trackers configured for a specific project.
38+
39+
Args:
40+
project_id: Project UUID
41+
page: Page number (0-based)
42+
size: Number of issue trackers per page
43+
44+
Returns:
45+
Project issue trackers response with _embedded.items containing the trackers
46+
"""
47+
params = {
48+
'page': page,
49+
'size': size
50+
}
51+
return self._make_request('GET', f'/projects/{project_id}/issue-trackers/summary', params=params)

src/iriusrisk_cli/api/report_client.py

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -185,35 +185,3 @@ def download_report_content_from_url(self, download_url: str) -> bytes:
185185
else:
186186
raise Exception(f"Failed to download report: {str(e)}")
187187

188-
def get_issue_tracker_profiles(self, page: int = 0, size: int = 9999) -> Dict[str, Any]:
189-
"""Get all available issue tracker profiles.
190-
191-
Args:
192-
page: Page number (0-based)
193-
size: Number of profiles per page
194-
195-
Returns:
196-
Issue tracker profiles response with _embedded.items containing the profiles
197-
"""
198-
params = {
199-
'page': page,
200-
'size': size
201-
}
202-
return self._make_request('GET', '/issue-tracker-profiles/summary', params=params)
203-
204-
def get_project_issue_trackers(self, project_id: str, page: int = 0, size: int = 9999) -> Dict[str, Any]:
205-
"""Get issue trackers configured for a specific project.
206-
207-
Args:
208-
project_id: Project UUID
209-
page: Page number (0-based)
210-
size: Number of issue trackers per page
211-
212-
Returns:
213-
Project issue trackers response with _embedded.items containing the trackers
214-
"""
215-
params = {
216-
'page': page,
217-
'size': size
218-
}
219-
return self._make_request('GET', f'/projects/{project_id}/issue-trackers/summary', params=params)

src/iriusrisk_cli/api_client.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .api.threat_client import ThreatApiClient
77
from .api.countermeasure_client import CountermeasureApiClient
88
from .api.report_client import ReportApiClient
9+
from .api.issue_tracker_client import IssueTrackerApiClient
910
from .api.health_client import HealthApiClient
1011
from .api.version_client import VersionApiClient
1112
from .api.questionnaire_client import QuestionnaireApiClient
@@ -29,6 +30,7 @@ def __init__(self, config: Optional[Config] = None):
2930
self.threat_client = ThreatApiClient(config=config)
3031
self.countermeasure_client = CountermeasureApiClient(config=config)
3132
self.report_client = ReportApiClient(config=config)
33+
self.issue_tracker_client = IssueTrackerApiClient(config=config)
3234
self.health_client = HealthApiClient(config=config)
3335
self.version_client = VersionApiClient(config=config)
3436
self.questionnaire_client = QuestionnaireApiClient(config=config)
@@ -168,14 +170,14 @@ def download_report_content_from_url(self, download_url: str) -> bytes:
168170
"""Download the content of a generated report from a full URL."""
169171
return self.report_client.download_report_content_from_url(download_url)
170172

171-
# Issue Tracker API methods - delegate to report_client
173+
# Issue Tracker API methods - delegate to issue_tracker_client
172174
def get_issue_tracker_profiles(self, page: int = 0, size: int = 9999) -> Dict[str, Any]:
173175
"""Get all available issue tracker profiles."""
174-
return self.report_client.get_issue_tracker_profiles(page, size)
175-
176+
return self.issue_tracker_client.get_issue_tracker_profiles(page, size)
177+
176178
def get_project_issue_trackers(self, project_id: str, page: int = 0, size: int = 9999) -> Dict[str, Any]:
177179
"""Get issue trackers configured for a specific project."""
178-
return self.report_client.get_project_issue_trackers(project_id, page, size)
180+
return self.issue_tracker_client.get_project_issue_trackers(project_id, page, size)
179181

180182

181183
# Global API client instance - removed to prevent eager config loading

src/iriusrisk_cli/commands/mcp.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,9 @@ def _apply_prompt_customizations(tool_name: str, base_prompt: str) -> str:
173173
logger.info(f"No customizations found for {tool_name}")
174174
return base_prompt
175175

176-
# Get the .iriusrisk directory for resolving relative file paths
177-
if project_root:
178-
# Convert to Path if it's a string (handles both real usage and mocked tests)
179-
if isinstance(project_root, str):
180-
project_root = Path(project_root)
181-
iriusrisk_dir = project_root / '.iriusrisk'
182-
else:
183-
iriusrisk_dir = Path.cwd() / '.iriusrisk'
176+
# Get the .iriusrisk directory for resolving relative file paths.
177+
# Config was read from Path.cwd()/.iriusrisk/project.json, so this is always the correct dir.
178+
iriusrisk_dir = Path.cwd() / '.iriusrisk'
184179

185180
# Handle replace first (it overrides everything)
186181
if 'replace' in customizations:

src/iriusrisk_cli/container.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .repositories.project_repository import ProjectRepository
88
from .repositories.threat_repository import ThreatRepository
99
from .repositories.countermeasure_repository import CountermeasureRepository
10+
from .repositories.issue_tracker_repository import IssueTrackerRepository
1011
from .repositories.report_repository import ReportRepository
1112
from .repositories.version_repository import VersionRepository
1213
from .repositories.questionnaire_repository import QuestionnaireRepository
@@ -57,6 +58,9 @@ def _register_factories(self):
5758
self._factories[CountermeasureRepository] = lambda: CountermeasureRepository(
5859
api_client=self.get(IriusRiskApiClient).countermeasure_client
5960
)
61+
self._factories[IssueTrackerRepository] = lambda: IssueTrackerRepository(
62+
api_client=self.get(IriusRiskApiClient).issue_tracker_client
63+
)
6064
self._factories[ReportRepository] = lambda: ReportRepository(
6165
api_client=self.get(IriusRiskApiClient).report_client
6266
)
@@ -77,7 +81,8 @@ def _register_factories(self):
7781
threat_repository=self.get(ThreatRepository)
7882
)
7983
self._factories[CountermeasureService] = lambda: CountermeasureService(
80-
countermeasure_repository=self.get(CountermeasureRepository)
84+
countermeasure_repository=self.get(CountermeasureRepository),
85+
issue_tracker_repository=self.get(IssueTrackerRepository)
8186
)
8287
self._factories[ReportService] = lambda: ReportService(
8388
report_repository=self.get(ReportRepository)

src/iriusrisk_cli/repositories/countermeasure_repository.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -274,33 +274,6 @@ def create_issue(self, project_id: str, countermeasure_id: str,
274274
# to avoid duplicate error messages
275275
self._handle_error(e, f"creating issue for countermeasure '{countermeasure_id}'")
276276

277-
def get_issue_tracker_profiles(self) -> Dict[str, Any]:
278-
"""Get available issue tracker profiles.
279-
280-
Returns:
281-
Dictionary containing issue tracker profiles
282-
283-
Raises:
284-
IriusRiskError: If API request fails
285-
"""
286-
logger.debug("Retrieving issue tracker profiles")
287-
288-
try:
289-
response = self.api_client.get_issue_tracker_profiles()
290-
profiles_data = self._extract_items_from_response(response)
291-
292-
logger.debug(f"Retrieved {len(profiles_data)} issue tracker profiles")
293-
294-
return {
295-
'profiles': profiles_data,
296-
'full_response': response
297-
}
298-
299-
except Exception as e:
300-
# Don't log here - let the error handling layer handle it
301-
# to avoid duplicate error messages
302-
self._handle_error(e, "retrieving issue tracker profiles")
303-
304277
def find_countermeasure_by_reference_or_uuid(self, project_id: str,
305278
countermeasure_id: str) -> Optional[Dict[str, Any]]:
306279
"""Find a countermeasure by reference ID or UUID.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""Issue tracker repository for issue tracker profile data access."""
2+
3+
import logging
4+
from typing import Dict, Any
5+
6+
from .base_repository import BaseRepository
7+
from ..utils.error_handling import IriusRiskError
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
class IssueTrackerRepository(BaseRepository):
13+
"""Repository for issue tracker profile data access operations."""
14+
15+
def get_by_id(self, profile_id: str, **kwargs) -> Dict[str, Any]:
16+
"""Not supported — issue tracker profiles are accessed via get_issue_tracker_profiles."""
17+
raise IriusRiskError(
18+
"Issue tracker profiles should be accessed through get_issue_tracker_profiles()"
19+
)
20+
21+
def list_all(self, **kwargs) -> Dict[str, Any]:
22+
"""List all issue tracker profiles."""
23+
return self.get_issue_tracker_profiles()
24+
25+
def get_issue_tracker_profiles(self) -> Dict[str, Any]:
26+
"""Get all available issue tracker profiles.
27+
28+
Returns:
29+
Dictionary with 'profiles' list and 'full_response'
30+
31+
Raises:
32+
IriusRiskError: If API request fails
33+
"""
34+
logger.debug("Retrieving issue tracker profiles")
35+
36+
try:
37+
response = self.api_client.get_issue_tracker_profiles()
38+
profiles_data = self._extract_items_from_response(response)
39+
40+
logger.debug(f"Retrieved {len(profiles_data)} issue tracker profiles")
41+
42+
return {
43+
'profiles': profiles_data,
44+
'full_response': response
45+
}
46+
47+
except Exception as e:
48+
self._handle_error(e, "retrieving issue tracker profiles")
49+
50+
def get_project_issue_trackers(self, project_id: str) -> Dict[str, Any]:
51+
"""Get issue trackers configured for a specific project.
52+
53+
Args:
54+
project_id: Project UUID
55+
56+
Returns:
57+
Dictionary with 'trackers' list and 'full_response'
58+
59+
Raises:
60+
IriusRiskError: If API request fails
61+
"""
62+
logger.debug(f"Retrieving issue trackers for project '{project_id}'")
63+
64+
try:
65+
response = self.api_client.get_project_issue_trackers(project_id)
66+
trackers_data = self._extract_items_from_response(response)
67+
68+
logger.debug(f"Retrieved {len(trackers_data)} issue trackers for project '{project_id}'")
69+
70+
return {
71+
'trackers': trackers_data,
72+
'full_response': response
73+
}
74+
75+
except Exception as e:
76+
self._handle_error(e, f"retrieving issue trackers for project '{project_id}'")

0 commit comments

Comments
 (0)