datasecops-cli 0.4.8__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.
- datasecops_cli-0.5.1/.github/workflows/test.yml +28 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/CHANGELOG.md +46 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/PKG-INFO +3 -3
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/README.md +2 -2
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/docs/getting-started.md +3 -3
- datasecops_cli-0.5.1/docs/git-operations.md +208 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/docs/mcp-server.md +48 -18
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/mcp-servers.json +1 -1
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/pyproject.toml +1 -1
- datasecops_cli-0.5.1/src/datasecops_cli/__init__.py +1 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/config.py +7 -1
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/main.py +21 -5
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/menus/configuration.py +15 -4
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/menus/downloads.py +19 -6
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/menus/git_operations.py +121 -43
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/models/project_config.py +15 -2
- datasecops_cli-0.5.1/src/datasecops_cli/services/git_service.py +399 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_mcp/connection.py +11 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_mcp/server.py +38 -5
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/tests/test_models.py +1 -2
- datasecops_cli-0.4.8/src/datasecops_cli/__init__.py +0 -1
- datasecops_cli-0.4.8/src/datasecops_cli/services/git_service.py +0 -183
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/.github/workflows/auto-tag.yml +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/.github/workflows/publish-cli.yml +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/.gitignore +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/DEVELOPMENT.md +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/LICENSE +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/docs/legacy.md +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/docs/legacy_plan_of_action.md +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/setup.ps1 +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/setup.sh +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/menus/__init__.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/menus/development.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/models/__init__.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/models/git_helpers.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/services/__init__.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/services/bootstrap_service.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/services/dbt_project_generator.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/services/dbt_runner.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/services/directory_scaffolder.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/services/download_service.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/services/linting_service.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/services/skill_service.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/services/snowflake_service.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/services/upstream_service.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/utilities/__init__.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/utilities/display.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/utilities/file_utils.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_mcp/__init__.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/src/datasecops_mcp/__main__.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/tests/__init__.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/tests/test_config.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/tests/test_file_utils.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/tests/test_main.py +0 -0
- {datasecops_cli-0.4.8 → datasecops_cli-0.5.1}/tests/test_version.py +0 -0
- {datasecops_cli-0.4.8 → 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,52 @@
|
|
|
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
|
+
|
|
33
|
+
## [0.4.9] - 2026-05-21
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- **Remove from test** — new git menu option to remove a branch from the test integration branch. Displays squash-merged branches by name and drops the selected commit from test's history via rebase, then force-pushes. The test branch is never deleted.
|
|
38
|
+
- **Branch cleanup** — new option in branch management to delete all local branches (except main, test, and current) and prune stale remote tracking references in one step.
|
|
39
|
+
- **Git operations documentation** — `docs/git-operations.md` documenting the full branch strategy, menu structure, and behaviour of each operation.
|
|
40
|
+
- **Ticket integration plan** — `plans/ticket-integration-plan.md` specifying the design for Azure DevOps, Jira, and GitHub Projects ticket fetching during branch creation.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- **Squash merge into test rebases onto main first** — prevents drift by rebasing the test branch onto `origin/main` before squash-merging, ensuring test always has main as its base with squash commits layered on top.
|
|
45
|
+
- **Branch creation uses configured `branch_format`** — branch names are now built using the `branch_format` field from the framework's SOURCE_CONTROL config instead of a hardcoded pattern.
|
|
46
|
+
- **Deploy to prod enforced from main only** — production deployments are hardcoded to `force=False` and can only originate from the main branch.
|
|
47
|
+
- **Protected branches** — `main` and `test` cannot be deleted via delete, cleanup, or reset operations.
|
|
48
|
+
- **Stash/restore on branch-switching operations** — squash merge and remove from test now stash uncommitted changes before switching branches and restore them afterward, preventing dirty-tree errors.
|
|
49
|
+
- **Test branch auto-creation** — if neither local nor remote `test` branch exists, it is created from `origin/main` automatically during squash merge or remove operations.
|
|
50
|
+
|
|
5
51
|
## [0.4.8] - 2026-05-20
|
|
6
52
|
|
|
7
53
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datasecops-cli
|
|
3
|
-
Version: 0.
|
|
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
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Git Operations
|
|
2
|
+
|
|
3
|
+
The CLI provides a structured git workflow designed around a branching strategy where feature branches are squash-merged into a `test` branch for integration testing, and only `main` is deployed to production.
|
|
4
|
+
|
|
5
|
+
## Branch Strategy
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
main ─────────────────────────────────────── prod
|
|
9
|
+
│
|
|
10
|
+
├── feature/ticket_name ──┐
|
|
11
|
+
├── bugfix/ticket_name ──┤── squash merge ──► test
|
|
12
|
+
└── hotfix/ticket_name ──┘
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- `main` is the source of truth and the only branch that deploys to production
|
|
16
|
+
- `test` is an integration branch built on top of main (rebased to prevent drift)
|
|
17
|
+
- Feature/bugfix/hotfix branches are created from main and squash-merged into test
|
|
18
|
+
- Branches can be removed from test without affecting main or other branches on test
|
|
19
|
+
|
|
20
|
+
## Main Menu
|
|
21
|
+
|
|
22
|
+
| Option | Name | Description |
|
|
23
|
+
|--------|------|-------------|
|
|
24
|
+
| 1 | Branching | Manage branches (create, checkout, switch, delete, prune, cleanup, reset) |
|
|
25
|
+
| 2 | Commit | Stage all changes and commit with a message, then push |
|
|
26
|
+
| 3 | Push | Push current branch to remote |
|
|
27
|
+
| 4 | Pull | Pull latest changes from remote for current branch |
|
|
28
|
+
| 5 | Rebase | Rebase current branch with main |
|
|
29
|
+
| 6 | Deploy | Deploy to an environment branch (e.g. test, prod) |
|
|
30
|
+
| 7 | Squash to test | Squash merge current branch into test |
|
|
31
|
+
| 8 | Remove from test | Remove a branch's changes from test |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Operations Detail
|
|
36
|
+
|
|
37
|
+
### 1. Branching
|
|
38
|
+
|
|
39
|
+
Submenu for branch management operations.
|
|
40
|
+
|
|
41
|
+
| Option | Name | Description |
|
|
42
|
+
|--------|------|-------------|
|
|
43
|
+
| 1 | New | Create a new branch from main |
|
|
44
|
+
| 2 | Checkout | Checkout a remote branch locally |
|
|
45
|
+
| 3 | Switch | Switch to an existing local branch |
|
|
46
|
+
| 4 | Delete | Delete a local branch (and its remote) |
|
|
47
|
+
| 5 | Prune | Remove stale remote tracking references |
|
|
48
|
+
| 6 | Cleanup | Delete all local branches (except main, test, current) and prune remote |
|
|
49
|
+
| 7 | Reset | Hard reset to main and delete all local branches |
|
|
50
|
+
|
|
51
|
+
#### New Branch
|
|
52
|
+
|
|
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)
|
|
58
|
+
- Creates from `origin/main`, checks out, and pushes with upstream tracking
|
|
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
|
+
|
|
62
|
+
#### Delete Branch
|
|
63
|
+
|
|
64
|
+
- Cannot delete the current branch
|
|
65
|
+
- Cannot delete protected branches (`main`, `test`)
|
|
66
|
+
- Deletes both the local branch and the remote branch
|
|
67
|
+
|
|
68
|
+
#### Cleanup
|
|
69
|
+
|
|
70
|
+
- Deletes all local branches except `main`, `test`, and the current branch
|
|
71
|
+
- Prunes stale remote tracking references
|
|
72
|
+
|
|
73
|
+
#### Reset to Main
|
|
74
|
+
|
|
75
|
+
- Checks out main and hard resets to `origin/main`
|
|
76
|
+
- Deletes all local branches except `main` and `test`
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### 2. Commit
|
|
81
|
+
|
|
82
|
+
- Detects uncommitted changes (modified and untracked files)
|
|
83
|
+
- Displays a summary of changed files
|
|
84
|
+
- Prompts for a commit message
|
|
85
|
+
- Stages all changes, commits, and pushes to remote
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### 3. Push
|
|
90
|
+
|
|
91
|
+
Pushes the current branch to its remote counterpart.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### 4. Pull
|
|
96
|
+
|
|
97
|
+
Pulls the latest changes from remote for the current branch.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### 5. Rebase
|
|
102
|
+
|
|
103
|
+
Submenu for rebase operations.
|
|
104
|
+
|
|
105
|
+
| Option | Name | Description |
|
|
106
|
+
|--------|------|-------------|
|
|
107
|
+
| 1 | Rebase | Standard rebase of current branch onto `origin/main` |
|
|
108
|
+
| 2 | Squash & rebase | Squash all commits into one, then rebase onto main |
|
|
109
|
+
| 3 | Continue | Continue rebase after resolving conflicts |
|
|
110
|
+
| 4 | Abort | Abort an in-progress rebase |
|
|
111
|
+
|
|
112
|
+
#### Standard Rebase
|
|
113
|
+
|
|
114
|
+
Fetches from origin and rebases the current branch onto `origin/main`. If conflicts occur, you can resolve them and use "continue" or "abort".
|
|
115
|
+
|
|
116
|
+
#### Squash & Rebase
|
|
117
|
+
|
|
118
|
+
Soft-resets to the main branch point, creates a single squash commit, then rebases onto `origin/main`. This gives you a clean single commit on top of main.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
### 6. Deploy
|
|
123
|
+
|
|
124
|
+
Pushes the current branch to a configured deployment branch.
|
|
125
|
+
|
|
126
|
+
**Production rules:**
|
|
127
|
+
- Production deployments can only come from the `main` branch
|
|
128
|
+
- If not on main, the CLI will offer to switch to main first
|
|
129
|
+
- Production pushes are never force-pushed
|
|
130
|
+
|
|
131
|
+
**Non-production environments:**
|
|
132
|
+
- Any branch can be pushed to non-production environments
|
|
133
|
+
- Force-push is used for non-production deployments
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
### 7. Squash to Test
|
|
138
|
+
|
|
139
|
+
Squash merges the current feature branch into the `test` integration branch.
|
|
140
|
+
|
|
141
|
+
**Pre-flight checks (before merge):**
|
|
142
|
+
|
|
143
|
+
1. **Branch must be up to date with main** — if your branch is behind `origin/main`, the merge is rejected. Rebase with main first.
|
|
144
|
+
2. **File conflict detection** — checks if any files modified on your branch are also modified by another branch's squash-merge commit currently on test:
|
|
145
|
+
- If the conflicting branch **still exists on remote** → merge is **rejected**. You must resolve with the branch owner or remove that branch from test first.
|
|
146
|
+
- If the conflicting branch **no longer exists** (stale) → the stale commit is **automatically removed** from test before proceeding.
|
|
147
|
+
|
|
148
|
+
**What it does (after pre-flight passes):**
|
|
149
|
+
1. Stashes any uncommitted changes
|
|
150
|
+
2. Fetches latest from origin
|
|
151
|
+
3. Checks out the `test` branch (creates it from `origin/main` if it doesn't exist)
|
|
152
|
+
4. Pulls latest test
|
|
153
|
+
5. Rebases test onto `origin/main` to prevent drift from main
|
|
154
|
+
6. Squash merges the feature branch into test
|
|
155
|
+
7. Commits with message: `squash merge: {branch_name} into test`
|
|
156
|
+
8. Force-pushes test (required due to the rebase)
|
|
157
|
+
9. Checks out the original branch and restores stashed changes
|
|
158
|
+
|
|
159
|
+
**Key behaviours:**
|
|
160
|
+
- The test branch always has `main` as its base — squash commits are layered on top
|
|
161
|
+
- Force-push is used because the rebase rewrites test's history to stay aligned with main
|
|
162
|
+
- The commit message naming convention enables the "remove from test" feature
|
|
163
|
+
- File overlap detection prevents two active branches from silently overwriting each other's changes on test
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### 8. Remove from Test
|
|
168
|
+
|
|
169
|
+
Removes a previously squash-merged branch from the test branch by dropping its commit from history.
|
|
170
|
+
|
|
171
|
+
**What it does:**
|
|
172
|
+
1. Fetches latest and lists all squash-merge commits on test (identified by commit message prefix `squash merge:`)
|
|
173
|
+
2. Displays the branches currently on test by name
|
|
174
|
+
3. User selects which branch to remove
|
|
175
|
+
4. Stashes any uncommitted changes
|
|
176
|
+
5. Checks out test
|
|
177
|
+
6. Rebases test, dropping the selected commit: `git rebase --onto <commit>^ <commit> test`
|
|
178
|
+
7. Force-pushes test
|
|
179
|
+
8. Checks out the original branch and restores stashed changes
|
|
180
|
+
|
|
181
|
+
**Key behaviours:**
|
|
182
|
+
- The commit is completely removed from test's history (not reverted)
|
|
183
|
+
- All other branches on test remain intact
|
|
184
|
+
- The test branch itself is never deleted
|
|
185
|
+
- If the rebase fails (e.g. conflicts), it aborts and returns to the original branch
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Protected Branches
|
|
190
|
+
|
|
191
|
+
The following branches are protected and cannot be deleted:
|
|
192
|
+
- `main` — source of truth, deploys to production
|
|
193
|
+
- `test` — integration branch for testing
|
|
194
|
+
|
|
195
|
+
These are protected across all operations: delete, cleanup, and reset.
|
|
196
|
+
|
|
197
|
+
## Error Handling
|
|
198
|
+
|
|
199
|
+
All operations that switch branches follow a safe pattern:
|
|
200
|
+
1. Stash uncommitted changes before switching
|
|
201
|
+
2. Perform the operation
|
|
202
|
+
3. Switch back to the original branch
|
|
203
|
+
4. Restore stashed changes
|
|
204
|
+
|
|
205
|
+
If any step fails, the error handler will:
|
|
206
|
+
- Abort any in-progress rebase/revert
|
|
207
|
+
- Switch back to the original branch
|
|
208
|
+
- Restore stashed changes
|
|
@@ -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
|
-
|
|
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
|
|
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,
|
|
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
|
},
|
|
@@ -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", "--",
|
|
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(
|
|
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": {
|
|
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
|
-
"
|
|
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(
|
|
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
|
-
|
|
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",
|