datasecops-cli 0.4.9__tar.gz → 0.5.1__tar.gz

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 (56) hide show
  1. datasecops_cli-0.5.1/.github/workflows/test.yml +28 -0
  2. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/CHANGELOG.md +28 -0
  3. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/PKG-INFO +3 -3
  4. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/README.md +2 -2
  5. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/docs/getting-started.md +3 -3
  6. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/docs/git-operations.md +7 -4
  7. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/docs/mcp-server.md +48 -18
  8. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/mcp-servers.json +1 -1
  9. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/pyproject.toml +1 -1
  10. datasecops_cli-0.5.1/src/datasecops_cli/__init__.py +1 -0
  11. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/config.py +7 -1
  12. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/main.py +21 -5
  13. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/menus/configuration.py +15 -4
  14. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/menus/downloads.py +19 -6
  15. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/menus/git_operations.py +14 -7
  16. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/models/project_config.py +15 -2
  17. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_mcp/connection.py +11 -0
  18. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_mcp/server.py +38 -5
  19. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/tests/test_models.py +1 -2
  20. datasecops_cli-0.4.9/src/datasecops_cli/__init__.py +0 -1
  21. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/.github/workflows/auto-tag.yml +0 -0
  22. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/.github/workflows/publish-cli.yml +0 -0
  23. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/.gitignore +0 -0
  24. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/DEVELOPMENT.md +0 -0
  25. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/LICENSE +0 -0
  26. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/docs/legacy.md +0 -0
  27. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/docs/legacy_plan_of_action.md +0 -0
  28. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/setup.ps1 +0 -0
  29. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/setup.sh +0 -0
  30. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/menus/__init__.py +0 -0
  31. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/menus/development.py +0 -0
  32. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/models/__init__.py +0 -0
  33. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/models/git_helpers.py +0 -0
  34. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/services/__init__.py +0 -0
  35. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/services/bootstrap_service.py +0 -0
  36. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/services/dbt_project_generator.py +0 -0
  37. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/services/dbt_runner.py +0 -0
  38. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/services/directory_scaffolder.py +0 -0
  39. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/services/download_service.py +0 -0
  40. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/services/git_service.py +0 -0
  41. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/services/linting_service.py +0 -0
  42. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/services/skill_service.py +0 -0
  43. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/services/snowflake_service.py +0 -0
  44. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/services/upstream_service.py +0 -0
  45. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/utilities/__init__.py +0 -0
  46. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/utilities/display.py +0 -0
  47. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/utilities/file_utils.py +0 -0
  48. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
  49. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_mcp/__init__.py +0 -0
  50. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/src/datasecops_mcp/__main__.py +0 -0
  51. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/tests/__init__.py +0 -0
  52. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/tests/test_config.py +0 -0
  53. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/tests/test_file_utils.py +0 -0
  54. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/tests/test_main.py +0 -0
  55. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/tests/test_version.py +0 -0
  56. {datasecops_cli-0.4.9 → datasecops_cli-0.5.1}/tests/test_yaml_utils.py +0 -0
@@ -0,0 +1,28 @@
1
+ name: Test
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ test:
9
+ name: Test (Python ${{ matrix.python-version }})
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ['3.11', '3.12', '3.13']
14
+
15
+ steps:
16
+ - name: Checkout repository
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install package with test dependencies
25
+ run: pip install -e ".[test]"
26
+
27
+ - name: Run tests
28
+ run: pytest --tb=short
@@ -2,6 +2,34 @@
2
2
 
3
3
  All notable changes to the DataSecOps CLI are documented in this file.
4
4
 
5
+ ## [0.5.1] - 2026-06-04
6
+
7
+ ### Added
8
+
9
+ - **MCP server CLI arguments** — `datasecops-mcp` now accepts `--connection-name` and `--app-database` arguments, eliminating the dependency on `.datasecops.yml` being in the working directory. This fixes connection failures when AI tools (Cortex Code, Cursor, etc.) launch the MCP server from a different working directory.
10
+ - **`init_snowflake_service()` function** — new public API in `datasecops_mcp.connection` to pre-initialize the Snowflake service singleton with explicit parameters.
11
+
12
+ ### Changed
13
+
14
+ - **MCP registration passes connection details** — all CLI paths that register the MCP server (setup, configuration menu, downloads menu) now pass `--connection-name` and `--app-database` to the registration command and `mcp.json` args.
15
+ - **`DownloadsMenu` accepts `datasecops_config`** — the downloads menu now receives the `DatasecopsConfig` so it can pass connection details during MCP server registration.
16
+
17
+ ## [0.5.0] - 2026-05-21
18
+
19
+ ### Added
20
+
21
+ - **Work Items MCP tool** — new `get_work_items_config` tool in the MCP server returns the configured ticket system (Azure DevOps, Jira, or GitHub Issues), whether ticket numbers are required, and system-specific connection details. Used by the `new-branch` skill.
22
+ - **WorkItems model** — new `WorkItems` model in `project_config.py` stores ticket system configuration separately from source control.
23
+
24
+ ### Changed
25
+
26
+ - **`ticket_number_required` moved from Source Control to Work Items** — the ticket requirement is now part of the WORK_ITEMS configuration (separate from SOURCE_CONTROL). The `get_branching_rules` MCP tool no longer includes this field; use `get_work_items_config` instead.
27
+ - **Branch format default updated** — default branch format changed from `{branch_type}/{branch_name}` to `{branch_type}/{ticket_name}_{name}` with explicit placeholders for ticket and description.
28
+ - **Branch creation uses `str.replace()` instead of `str.format()`** — supports the new `{ticket_name}` and `{name}` placeholders in branch format without breaking on missing keys.
29
+ - **`validate_branch_name` reads ticket requirement from WORK_ITEMS** — the MCP validation tool now queries the WORK_ITEMS config for `ticket_number_required` instead of SOURCE_CONTROL.
30
+ - **Config class loads WORK_ITEMS** — `Config.load_from_native_app()` now also fetches the WORK_ITEMS configuration.
31
+ - **Git operations menu accepts work_items** — `GitOperationsMenu` now receives `WorkItems` to determine ticket enforcement during branch creation.
32
+
5
33
  ## [0.4.9] - 2026-05-21
6
34
 
7
35
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datasecops-cli
3
- Version: 0.4.9
3
+ Version: 0.5.1
4
4
  Summary: DataSecOps Framework CLI for Snowflake Native App
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -161,7 +161,7 @@ The package includes an MCP (Model Context Protocol) server that exposes your fr
161
161
  **Cortex Code:**
162
162
 
163
163
  ```bash
164
- cortex mcp add datasecops-framework -- datasecops-mcp
164
+ cortex mcp add datasecops-framework -- datasecops-mcp --connection-name <CONNECTION> --app-database <APP_DB>
165
165
  ```
166
166
 
167
167
  **VS Code / Cursor** — add to `.vscode/mcp.json` or `.cursor/mcp.json`:
@@ -171,7 +171,7 @@ cortex mcp add datasecops-framework -- datasecops-mcp
171
171
  "mcpServers": {
172
172
  "datasecops-framework": {
173
173
  "command": "datasecops-mcp",
174
- "args": []
174
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"]
175
175
  }
176
176
  }
177
177
  }
@@ -142,7 +142,7 @@ The package includes an MCP (Model Context Protocol) server that exposes your fr
142
142
  **Cortex Code:**
143
143
 
144
144
  ```bash
145
- cortex mcp add datasecops-framework -- datasecops-mcp
145
+ cortex mcp add datasecops-framework -- datasecops-mcp --connection-name <CONNECTION> --app-database <APP_DB>
146
146
  ```
147
147
 
148
148
  **VS Code / Cursor** — add to `.vscode/mcp.json` or `.cursor/mcp.json`:
@@ -152,7 +152,7 @@ cortex mcp add datasecops-framework -- datasecops-mcp
152
152
  "mcpServers": {
153
153
  "datasecops-framework": {
154
154
  "command": "datasecops-mcp",
155
- "args": []
155
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"]
156
156
  }
157
157
  }
158
158
  }
@@ -156,7 +156,7 @@ Create `.vscode/mcp.json` in your project root:
156
156
  "mcpServers": {
157
157
  "datasecops-framework": {
158
158
  "command": "datasecops-mcp",
159
- "args": []
159
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"]
160
160
  },
161
161
  "dbt": {
162
162
  "command": "uvx",
@@ -185,7 +185,7 @@ For Azure DevOps instead of GitHub:
185
185
  "mcpServers": {
186
186
  "datasecops-framework": {
187
187
  "command": "datasecops-mcp",
188
- "args": []
188
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"]
189
189
  },
190
190
  "dbt": {
191
191
  "command": "uvx",
@@ -219,7 +219,7 @@ Add servers from the command line:
219
219
 
220
220
  ```bash
221
221
  # DataSecOps Framework
222
- cortex mcp add datasecops-framework -- datasecops-mcp
222
+ cortex mcp add datasecops-framework -- datasecops-mcp --connection-name <CONNECTION> --app-database <APP_DB>
223
223
 
224
224
  # dbt
225
225
  cortex mcp add dbt -- uvx dbt-mcp
@@ -50,12 +50,15 @@ Submenu for branch management operations.
50
50
 
51
51
  #### New Branch
52
52
 
53
- Creates a branch using the naming convention `{type}/{ticket}_{name}`:
54
- - Prompts for branch type (feature, bugfix, hotfix — or configured types)
55
- - Prompts for ticket number (required or optional based on config)
56
- - Prompts for branch name
53
+ Creates a branch using the configured `branch_format` (default: `{branch_type}/{ticket_name}_{name}`):
54
+ - Prompts for branch type (feature, bugfix, hotfix — or configured types from Source Control settings)
55
+ - Prompts for ticket number (required or optional based on Work Items config `ticket_number_required`)
56
+ - Prompts for branch name (description)
57
+ - Builds the full name using `format_map` with placeholders: `{branch_type}`, `{ticket_name}`, `{name}`, `{branch_name}` (legacy)
57
58
  - Creates from `origin/main`, checks out, and pushes with upstream tracking
58
59
 
60
+ > **Tip:** Use the `new-branch` Cortex Code skill instead of the CLI menu for an AI-assisted workflow that also fetches ticket details and creates a plan document.
61
+
59
62
  #### Delete Branch
60
63
 
61
64
  - Cannot delete the current branch
@@ -13,32 +13,25 @@ pip install datasecops-cli[mcp]
13
13
  Run the server:
14
14
 
15
15
  ```bash
16
- datasecops-mcp
16
+ datasecops-mcp --connection-name <CONNECTION> --app-database <APP_DB>
17
17
  ```
18
18
 
19
19
  The server communicates via stdio transport and is designed to be launched by an MCP client (not run interactively).
20
20
 
21
+ If `--connection-name` and `--app-database` are not provided, the server falls back to reading `.datasecops.yml` from the current working directory.
22
+
21
23
  ## Configuring Your AI Tool
22
24
 
23
25
  ### Cortex Code
24
26
 
25
- Add to your Cortex Code MCP configuration:
26
-
27
- ```json
28
- {
29
- "mcpServers": {
30
- "datasecops-framework": {
31
- "command": "datasecops-mcp",
32
- "args": []
33
- }
34
- }
35
- }
27
+ ```bash
28
+ cortex mcp add datasecops-framework -- datasecops-mcp --connection-name <CONNECTION> --app-database <APP_DB>
36
29
  ```
37
30
 
38
31
  ### Claude Code
39
32
 
40
33
  ```bash
41
- claude mcp add datasecops-framework datasecops-mcp
34
+ claude mcp add datasecops-framework datasecops-mcp -- --connection-name <CONNECTION> --app-database <APP_DB>
42
35
  ```
43
36
 
44
37
  ### Cursor / VS Code
@@ -50,7 +43,7 @@ Add to `.cursor/mcp.json` or your IDE's MCP settings:
50
43
  "mcpServers": {
51
44
  "datasecops-framework": {
52
45
  "command": "datasecops-mcp",
53
- "args": []
46
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"]
54
47
  }
55
48
  }
56
49
  }
@@ -58,7 +51,7 @@ Add to `.cursor/mcp.json` or your IDE's MCP settings:
58
51
 
59
52
  ## Prerequisites
60
53
 
61
- The MCP server requires a `.datasecops.yml` file in your project root (the same file used by the `datasecops` CLI):
54
+ The MCP server connects to Snowflake using the connection name specified via CLI arguments (or from `.datasecops.yml`):
62
55
 
63
56
  ```yaml
64
57
  connection_name: my_snowflake_connection
@@ -74,7 +67,8 @@ It uses your Snowflake connection from `~/.snowflake/connections.toml` to authen
74
67
 
75
68
  | Tool | Description |
76
69
  |------|-------------|
77
- | `get_branching_rules` | Branch types, naming conventions, ticket requirements, merge strategies |
70
+ | `get_branching_rules` | Branch types, naming conventions, environment branches, merge strategies |
71
+ | `get_work_items_config` | Work item system (Azure DevOps / Jira / GitHub Issues), ticket requirement, connection details |
78
72
  | `get_linting_rules` | SQLFluff rules (dialect, indentation, enabled rule codes) |
79
73
  | `get_dbt_packages` | Approved packages with pinned versions |
80
74
  | `get_pipeline_config` | CI/CD pipeline YAML templates (GitHub Actions or Azure DevOps) |
@@ -110,6 +104,11 @@ The MCP server works transparently in the background. When you ask your AI assis
110
104
 
111
105
  ### Example Interactions
112
106
 
107
+ **Starting work on a ticket (using the `new-branch` skill):**
108
+ > "Start work on ticket JIRA-456"
109
+
110
+ The AI calls `get_branching_rules` for branch format and types, `get_work_items_config` for the ticket system, fetches the ticket details via the platform MCP server (Azure DevOps / Atlassian / GitHub), creates a branch like `feature/JIRA-456_add-customer-dim`, and saves the ticket details to a plan document.
111
+
113
112
  **Creating a branch:**
114
113
  > "Create a new branch for ticket JIRA-456 to add a customer dimension model"
115
114
 
@@ -166,7 +165,7 @@ For full platform integration (PR creation, CI status, issue tracking), add the
166
165
  "mcpServers": {
167
166
  "datasecops-framework": {
168
167
  "command": "datasecops-mcp",
169
- "args": []
168
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"]
170
169
  },
171
170
  "github": {
172
171
  "command": "npx",
@@ -193,7 +192,7 @@ This enables:
193
192
  "mcpServers": {
194
193
  "datasecops-framework": {
195
194
  "command": "datasecops-mcp",
196
- "args": []
195
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"]
197
196
  },
198
197
  "azure-devops": {
199
198
  "command": "npx",
@@ -212,8 +211,39 @@ This enables:
212
211
  - Creating pull requests with templates
213
212
  - Checking build pipeline status
214
213
  - Linking to work items
214
+ - Managing and updating work items
215
215
  - Managing boards and sprints
216
216
 
217
+ ### Atlassian (Jira)
218
+
219
+ The Atlassian Rovo MCP Server provides access to Jira, Confluence, and Compass. Authentication is handled via OAuth 2.1 (browser flow) or API token — no local credentials needed.
220
+
221
+ ```json
222
+ {
223
+ "mcpServers": {
224
+ "datasecops-framework": {
225
+ "command": "datasecops-mcp",
226
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"]
227
+ },
228
+ "atlassian": {
229
+ "url": "https://mcp.atlassian.com/v1/mcp/authv2"
230
+ }
231
+ }
232
+ }
233
+ ```
234
+
235
+ Or via CLI:
236
+
237
+ ```bash
238
+ cortex mcp add atlassian https://mcp.atlassian.com/v1/mcp/authv2 --transport http
239
+ ```
240
+
241
+ This enables:
242
+ - Searching, creating, and updating Jira issues
243
+ - Reading Confluence pages for context
244
+ - Querying Compass components and dependencies
245
+ - Linking tickets to branches and PRs
246
+
217
247
  ## Combined Workflow Example
218
248
 
219
249
  With both the framework MCP server and GitHub MCP server configured, a developer can say:
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "datasecops-framework": {
4
4
  "command": "datasecops-mcp",
5
- "args": [],
5
+ "args": ["--connection-name", "<CONNECTION>", "--app-database", "<APP_DB>"],
6
6
  "env": {},
7
7
  "description": "DataSecOps Framework governance rules, branching conventions, linting config, and project profiles from your Snowflake Native App"
8
8
  },
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datasecops-cli"
7
- version = "0.4.9"
7
+ version = "0.5.1"
8
8
  description = "DataSecOps Framework CLI for Snowflake Native App"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1 @@
1
+ __version__ = "0.5.1"
@@ -2,7 +2,7 @@ from pathlib import Path
2
2
  from typing import Optional
3
3
 
4
4
  from datasecops_cli.models.project_config import (
5
- DatasecopsConfig, ProjectSettings, SourceControl, ProjectProfile, DbtTarget
5
+ DatasecopsConfig, ProjectSettings, SourceControl, WorkItems, ProjectProfile, DbtTarget
6
6
  )
7
7
  from datasecops_cli.utilities.yaml_utils import read_datasecops_config, read_dbt_project
8
8
  from datasecops_cli.utilities.display import error_line, info_line
@@ -15,6 +15,7 @@ class Config:
15
15
  self.datasecops: DatasecopsConfig = DatasecopsConfig()
16
16
  self.project_settings: ProjectSettings = ProjectSettings()
17
17
  self.source_control: SourceControl = SourceControl()
18
+ self.work_items: WorkItems = WorkItems()
18
19
  self.profile: Optional[ProjectProfile] = None
19
20
  self.all_profiles: list[ProjectProfile] = []
20
21
  self.project_dir: Path = Path.cwd()
@@ -77,6 +78,11 @@ class Config:
77
78
  if raw:
78
79
  self.source_control = SourceControl(**{k: v for k, v in raw.items() if k in SourceControl.model_fields})
79
80
 
81
+ # Load work items
82
+ raw = snowflake_service.get_framework_config("WORK_ITEMS")
83
+ if raw:
84
+ self.work_items = WorkItems(**{k: v for k, v in raw.items() if k in WorkItems.model_fields})
85
+
80
86
  # Load project profiles
81
87
  profiles_data = snowflake_service.get_project_profiles()
82
88
  if profiles_data:
@@ -175,11 +175,12 @@ def _run_setup(project_dir: Path):
175
175
  # --- Connect to Snowflake ---
176
176
  info_line("")
177
177
  info_line("Connecting to Snowflake...")
178
- from datasecops_cli.models.project_config import DatasecopsConfig, ProjectSettings, ProjectProfile, SourceControl
178
+ from datasecops_cli.models.project_config import DatasecopsConfig, ProjectSettings, ProjectProfile, SourceControl, WorkItems
179
179
  temp_config = DatasecopsConfig(connection_name=connection_name, app_database=app_database)
180
180
  temp_sf = SnowflakeService(temp_config)
181
181
  project_settings = ProjectSettings()
182
182
  source_control = SourceControl()
183
+ work_items = WorkItems()
183
184
  matched_profile = None
184
185
 
185
186
  try:
@@ -196,6 +197,11 @@ def _run_setup(project_dir: Path):
196
197
  if raw_sc:
197
198
  source_control = SourceControl(**{k: v for k, v in raw_sc.items() if k in SourceControl.model_fields})
198
199
 
200
+ # Load WORK_ITEMS settings (for ticket system and ticket_number_required)
201
+ raw_wi = temp_sf.get_framework_config("WORK_ITEMS")
202
+ if raw_wi:
203
+ work_items = WorkItems(**{k: v for k, v in raw_wi.items() if k in WorkItems.model_fields})
204
+
199
205
  # Get account identifier for MCP URL construction
200
206
  account_identifier = ""
201
207
  try:
@@ -311,11 +317,16 @@ def _run_setup(project_dir: Path):
311
317
  info_line("")
312
318
  info_line("Adding MCP servers to Cortex Code...")
313
319
  try:
314
- subprocess.run(["cortex", "mcp", "add", "datasecops-framework", "--", "datasecops-mcp"],
320
+ subprocess.run(["cortex", "mcp", "add", "datasecops-framework", "--",
321
+ "datasecops-mcp", "--connection-name", connection_name,
322
+ "--app-database", app_database],
315
323
  capture_output=True, text=True)
316
324
  success_line("Added datasecops-framework MCP server")
317
325
  except FileNotFoundError:
318
- warning_line("cortex not found — run manually: cortex mcp add datasecops-framework -- datasecops-mcp")
326
+ warning_line(
327
+ "cortex not found — run manually: cortex mcp add datasecops-framework "
328
+ f"-- datasecops-mcp --connection-name {connection_name} --app-database {app_database}"
329
+ )
319
330
 
320
331
  # Add AI Agent MCP server if enabled
321
332
  if ai_agent_mcp_url:
@@ -366,7 +377,10 @@ def _run_setup(project_dir: Path):
366
377
  mcp_dir = dir_map.get(tool_choice, ".vscode")
367
378
  mcp_path = project_dir / mcp_dir / "mcp.json"
368
379
 
369
- servers = {"datasecops-framework": {"command": "datasecops-mcp", "args": []}}
380
+ servers = {"datasecops-framework": {
381
+ "command": "datasecops-mcp",
382
+ "args": ["--connection-name", connection_name, "--app-database", app_database],
383
+ }}
370
384
 
371
385
  # Add AI Agent MCP server if enabled
372
386
  if ai_agent_mcp_url:
@@ -683,7 +697,8 @@ def _main_menu(config: Config, dbt_runner: DbtRunner, git_service: GitService,
683
697
  elif option == 2 and git_service:
684
698
  git_menu = GitOperationsMenu(
685
699
  git_service, config.source_control,
686
- config.get_deployment_branches(), profile_name
700
+ config.get_deployment_branches(), profile_name,
701
+ work_items=config.work_items
687
702
  )
688
703
  git_menu.show()
689
704
 
@@ -741,6 +756,7 @@ def _main_menu(config: Config, dbt_runner: DbtRunner, git_service: GitService,
741
756
  profile=config.profile,
742
757
  source_control=config.source_control,
743
758
  all_profiles=config.all_profiles,
759
+ datasecops_config=config.datasecops,
744
760
  )
745
761
  dl_menu.show()
746
762
 
@@ -167,13 +167,19 @@ class ConfigurationMenu:
167
167
  info_line("")
168
168
  for server in servers:
169
169
  if server == "datasecops-framework":
170
+ conn = self.datasecops_config.connection_name
171
+ app_db = self.datasecops_config.app_database
170
172
  try:
171
- subprocess.run(["cortex", "mcp", "add", "datasecops-framework",
172
- "--", "datasecops-mcp"],
173
+ subprocess.run(["cortex", "mcp", "add", "datasecops-framework", "--",
174
+ "datasecops-mcp", "--connection-name", conn,
175
+ "--app-database", app_db],
173
176
  capture_output=True, text=True)
174
177
  success_line("Added datasecops-framework MCP server")
175
178
  except FileNotFoundError:
176
- warning_line("cortex not found — run manually: cortex mcp add datasecops-framework -- datasecops-mcp")
179
+ warning_line(
180
+ "cortex not found — run manually: cortex mcp add datasecops-framework "
181
+ f"-- datasecops-mcp --connection-name {conn} --app-database {app_db}"
182
+ )
177
183
 
178
184
  elif server == "GitHub":
179
185
  token = get_input_string("Enter your GitHub PAT (or press Enter to skip): ", allow_empty=True)
@@ -228,7 +234,12 @@ class ConfigurationMenu:
228
234
 
229
235
  for server in servers:
230
236
  if server == "datasecops-framework":
231
- server_config["datasecops-framework"] = {"command": "datasecops-mcp", "args": []}
237
+ conn = self.datasecops_config.connection_name
238
+ app_db = self.datasecops_config.app_database
239
+ server_config["datasecops-framework"] = {
240
+ "command": "datasecops-mcp",
241
+ "args": ["--connection-name", conn, "--app-database", app_db],
242
+ }
232
243
  elif server == "GitHub":
233
244
  server_config["github"] = {
234
245
  "command": "npx",
@@ -3,7 +3,7 @@ import json
3
3
  import shutil
4
4
  import subprocess
5
5
 
6
- from datasecops_cli.models.project_config import ProjectProfile, ProjectSettings, SourceControl
6
+ from datasecops_cli.models.project_config import DatasecopsConfig, ProjectProfile, ProjectSettings, SourceControl
7
7
  from datasecops_cli.services.download_service import DownloadService
8
8
  from datasecops_cli.services.skill_service import SkillService
9
9
  from datasecops_cli.services.dbt_runner import DbtRunner
@@ -19,7 +19,8 @@ class DownloadsMenu:
19
19
  def __init__(self, download_service: DownloadService, skill_service: SkillService,
20
20
  dbt_runner: DbtRunner, profile_name: str, dbt_project_dir: Path,
21
21
  project_settings: ProjectSettings = None, profile: ProjectProfile = None,
22
- source_control: SourceControl = None, all_profiles: list[ProjectProfile] = None):
22
+ source_control: SourceControl = None, all_profiles: list[ProjectProfile] = None,
23
+ datasecops_config: DatasecopsConfig = None):
23
24
  self.downloads = download_service
24
25
  self.skills = skill_service
25
26
  self.dbt = dbt_runner
@@ -29,6 +30,7 @@ class DownloadsMenu:
29
30
  self.profile = profile
30
31
  self.source_control = source_control
31
32
  self.all_profiles = all_profiles
33
+ self.datasecops_config = datasecops_config or DatasecopsConfig()
32
34
 
33
35
  def show(self) -> None:
34
36
  self._menu()
@@ -149,13 +151,19 @@ class DownloadsMenu:
149
151
  info_line("")
150
152
  for server in servers:
151
153
  if server == "datasecops-framework":
154
+ conn = self.datasecops_config.connection_name
155
+ app_db = self.datasecops_config.app_database
152
156
  try:
153
- subprocess.run(["cortex", "mcp", "add", "datasecops-framework",
154
- "--", "datasecops-mcp"],
157
+ subprocess.run(["cortex", "mcp", "add", "datasecops-framework", "--",
158
+ "datasecops-mcp", "--connection-name", conn,
159
+ "--app-database", app_db],
155
160
  capture_output=True, text=True)
156
161
  success_line("Added datasecops-framework MCP server")
157
162
  except FileNotFoundError:
158
- warning_line("cortex not found — run manually: cortex mcp add datasecops-framework -- datasecops-mcp")
163
+ warning_line(
164
+ "cortex not found — run manually: cortex mcp add datasecops-framework "
165
+ f"-- datasecops-mcp --connection-name {conn} --app-database {app_db}"
166
+ )
159
167
 
160
168
  elif server == "GitHub":
161
169
  token = get_input_string("Enter your GitHub PAT (or press Enter to skip): ", allow_empty=True)
@@ -210,7 +218,12 @@ class DownloadsMenu:
210
218
 
211
219
  for server in servers:
212
220
  if server == "datasecops-framework":
213
- server_config["datasecops-framework"] = {"command": "datasecops-mcp", "args": []}
221
+ conn = self.datasecops_config.connection_name
222
+ app_db = self.datasecops_config.app_database
223
+ server_config["datasecops-framework"] = {
224
+ "command": "datasecops-mcp",
225
+ "args": ["--connection-name", conn, "--app-database", app_db],
226
+ }
214
227
  elif server == "GitHub":
215
228
  server_config["github"] = {
216
229
  "command": "npx",
@@ -1,5 +1,7 @@
1
+ from collections import defaultdict
2
+
1
3
  from datasecops_cli.services.git_service import GitService
2
- from datasecops_cli.models.project_config import SourceControl
4
+ from datasecops_cli.models.project_config import SourceControl, WorkItems
3
5
  from datasecops_cli.utilities.display import (
4
6
  clear, section_header, display_action_header, menu_option,
5
7
  get_input_number, get_input_string, get_input_true_false,
@@ -10,9 +12,11 @@ from datasecops_cli.utilities.display import (
10
12
 
11
13
  class GitOperationsMenu:
12
14
  def __init__(self, git_service: GitService, source_control: SourceControl,
13
- deployment_branches: dict[str, str], profile_name: str):
15
+ deployment_branches: dict[str, str], profile_name: str,
16
+ work_items: WorkItems = None):
14
17
  self.git = git_service
15
18
  self.source_control = source_control
19
+ self.work_items = work_items or WorkItems()
16
20
  self.deployment_branches = deployment_branches
17
21
  self.profile_name = profile_name
18
22
 
@@ -106,7 +110,7 @@ class GitOperationsMenu:
106
110
  return
107
111
 
108
112
  ticket = ""
109
- if self.source_control.ticket_number_required:
113
+ if self.work_items.ticket_number_required:
110
114
  ticket = get_input_string("Enter ticket number: ")
111
115
  if ticket == "0":
112
116
  return
@@ -119,10 +123,13 @@ class GitOperationsMenu:
119
123
 
120
124
  branch_name = name.replace(" ", "-").lower()
121
125
  fmt = self.source_control.branch_format
122
- if ticket:
123
- full_name = fmt.format(branch_type=selected_type, branch_name=f"{ticket}_{branch_name}")
124
- else:
125
- full_name = fmt.format(branch_type=selected_type, branch_name=branch_name)
126
+ ctx = {
127
+ "branch_type": selected_type,
128
+ "ticket_name": ticket,
129
+ "name": branch_name,
130
+ "branch_name": f"{ticket}_{branch_name}" if ticket else branch_name,
131
+ }
132
+ full_name = fmt.format_map(defaultdict(str, ctx))
126
133
 
127
134
  self.git.create_branch(full_name)
128
135
 
@@ -1,6 +1,9 @@
1
1
  from pydantic import BaseModel, Field
2
2
  from typing import Optional
3
3
 
4
+ # Centralized default branch format used across the CLI and MCP server
5
+ DEFAULT_BRANCH_FORMAT = "{branch_type}/{ticket_name}_{name}"
6
+
4
7
  class DatasecopsConfig(BaseModel):
5
8
  """Local .datasecops.yml config."""
6
9
  connection_name: str = ""
@@ -60,11 +63,21 @@ class EnvironmentBranch(BaseModel):
60
63
 
61
64
  class SourceControl(BaseModel):
62
65
  branch_types: list[BranchType] = Field(default_factory=list)
63
- ticket_number_required: bool = False
64
- branch_format: str = "{branch_type}/{branch_name}"
66
+ branch_format: str = DEFAULT_BRANCH_FORMAT
65
67
  source_control_platform: str = "GitHub"
66
68
  environments: list[EnvironmentBranch] = Field(default_factory=list)
67
69
 
70
+ class WorkItems(BaseModel):
71
+ system: str = "" # "azure_devops", "jira", "github_issues", or ""
72
+ ticket_number_required: bool = False
73
+ azure_devops_org_url: str = ""
74
+ azure_devops_project: str = ""
75
+ azure_devops_auth_method: str = "pat"
76
+ jira_project_key: str = ""
77
+ github_owner: str = ""
78
+ github_repo: str = ""
79
+ github_auth_method: str = "pat"
80
+
68
81
  class ProjectProfile(BaseModel):
69
82
  project_id: int = 0
70
83
  profile_name: str = ""
@@ -141,6 +141,17 @@ def load_datasecops_config(project_dir: Path = None) -> dict:
141
141
  _service: Optional[MCPSnowflakeService] = None
142
142
 
143
143
 
144
+ def init_snowflake_service(connection_name: str, app_database: str) -> MCPSnowflakeService:
145
+ """Pre-initialize the Snowflake service with explicit parameters.
146
+
147
+ Use this when connection_name and app_database are provided via CLI arguments,
148
+ bypassing the need for .datasecops.yml in the working directory.
149
+ """
150
+ global _service
151
+ _service = MCPSnowflakeService(connection_name, app_database)
152
+ return _service
153
+
154
+
144
155
  def get_snowflake_service(project_dir: Path = None) -> MCPSnowflakeService:
145
156
  """Get or create the Snowflake service singleton."""
146
157
  global _service
@@ -11,6 +11,7 @@ import logging
11
11
  from mcp.server.fastmcp import FastMCP
12
12
 
13
13
  from datasecops_mcp.connection import get_snowflake_service
14
+ from datasecops_cli.models.project_config import DEFAULT_BRANCH_FORMAT
14
15
 
15
16
  logger = logging.getLogger(__name__)
16
17
 
@@ -25,7 +26,7 @@ def get_branching_rules() -> str:
25
26
  """Get the source control branching rules enforced by the framework.
26
27
 
27
28
  Returns branch types (feature, hotfix, etc.), naming conventions,
28
- environment branches, merge strategies, and whether ticket numbers are required.
29
+ environment branches, and merge strategies.
29
30
  Use this to understand how branches should be created and named.
30
31
  """
31
32
  sf = get_snowflake_service()
@@ -35,14 +36,30 @@ def get_branching_rules() -> str:
35
36
 
36
37
  result = {
37
38
  "branch_types": raw.get("branch_types", []),
38
- "ticket_number_required": raw.get("ticket_number_required", False),
39
- "branch_format": raw.get("branch_format", "{branch_type}/{branch_name}"),
39
+ "branch_format": raw.get("branch_format", DEFAULT_BRANCH_FORMAT),
40
40
  "source_control_platform": raw.get("source_control_platform", "GitHub"),
41
41
  "environments": raw.get("environments", []),
42
42
  }
43
43
  return json.dumps(result, indent=2)
44
44
 
45
45
 
46
+ @mcp.tool()
47
+ def get_work_items_config() -> str:
48
+ """Get the work item tracking configuration from the framework.
49
+
50
+ Returns which ticket system is configured (Azure DevOps, Jira, or GitHub Issues),
51
+ whether ticket numbers are required in branch names, and the system-specific
52
+ connection details (org URL, project, etc.).
53
+ Auth tokens are managed per-user via the CLI — not stored here.
54
+ """
55
+ sf = get_snowflake_service()
56
+ raw = sf.get_framework_config("WORK_ITEMS")
57
+ if not raw:
58
+ return json.dumps({"system": "", "ticket_number_required": False})
59
+
60
+ return json.dumps(raw, indent=2)
61
+
62
+
46
63
  @mcp.tool()
47
64
  def get_linting_rules() -> str:
48
65
  """Get the SQLFluff linting rules configured for this project.
@@ -313,8 +330,11 @@ def validate_branch_name(branch_name: str) -> str:
313
330
  return "No source control config found - cannot validate"
314
331
 
315
332
  branch_types = [bt.get("name", "") for bt in raw.get("branch_types", [])]
316
- ticket_required = raw.get("ticket_number_required", False)
317
- branch_format = raw.get("branch_format", "{branch_type}/{branch_name}")
333
+ branch_format = raw.get("branch_format", DEFAULT_BRANCH_FORMAT)
334
+
335
+ # Check WORK_ITEMS for ticket requirement
336
+ work_items = sf.get_framework_config("WORK_ITEMS") or {}
337
+ ticket_required = work_items.get("ticket_number_required", False)
318
338
 
319
339
  parts = branch_name.split("/", 1)
320
340
  errors = []
@@ -528,6 +548,19 @@ def _find_project_dir() -> "Path":
528
548
 
529
549
  def main():
530
550
  """Run the MCP server via stdio transport."""
551
+ import argparse
552
+
553
+ parser = argparse.ArgumentParser(description="DataSecOps MCP Server")
554
+ parser.add_argument("--connection-name", help="Snowflake connection name (overrides .datasecops.yml)")
555
+ parser.add_argument("--app-database", help="Native app database name (overrides .datasecops.yml)")
556
+ args = parser.parse_args()
557
+
558
+ if args.connection_name or args.app_database:
559
+ if not args.connection_name or not args.app_database:
560
+ parser.error("--connection-name and --app-database must both be provided")
561
+ from datasecops_mcp.connection import init_snowflake_service
562
+ init_snowflake_service(args.connection_name, args.app_database)
563
+
531
564
  mcp.run(transport="stdio")
532
565
 
533
566
 
@@ -100,8 +100,7 @@ class TestSourceControl:
100
100
  def test_defaults(self):
101
101
  sc = SourceControl()
102
102
  assert sc.branch_types == []
103
- assert sc.ticket_number_required is False
104
- assert sc.branch_format == "{branch_type}/{branch_name}"
103
+ assert sc.branch_format == "{branch_type}/{ticket_name}_{name}"
105
104
  assert sc.source_control_platform == "GitHub"
106
105
  assert sc.environments == []
107
106
 
@@ -1 +0,0 @@
1
- __version__ = "0.4.8"
File without changes
File without changes
File without changes