continuum-code 0.2.0__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.
- continuum/__init__.py +8 -0
- continuum/change_summary.py +416 -0
- continuum/cli.py +414 -0
- continuum/contracts.py +183 -0
- continuum/engine.py +214 -0
- continuum/mcp_server.py +127 -0
- continuum/packs/data-dbt-airflow/README.md +42 -0
- continuum/packs/data-dbt-airflow/continuum.yaml +42 -0
- continuum/packs/node-backend/continuum.yaml +29 -0
- continuum/packs/python-fastapi/continuum.yaml +29 -0
- continuum/packs/typescript-monorepo/continuum.yaml +29 -0
- continuum/recommend.py +63 -0
- continuum/scan.py +129 -0
- continuum_code-0.2.0.dist-info/METADATA +209 -0
- continuum_code-0.2.0.dist-info/RECORD +18 -0
- continuum_code-0.2.0.dist-info/WHEEL +5 -0
- continuum_code-0.2.0.dist-info/entry_points.txt +2 -0
- continuum_code-0.2.0.dist-info/top_level.txt +1 -0
continuum/scan.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Repo scan: detect stack (dbt, airflow, node, python) and optional CODEOWNERS; produce draft continuum.yaml."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def scan_repo(repo_root: str | Path) -> dict:
|
|
9
|
+
"""Detect repo signals. Returns a dict with has_dbt, has_airflow, has_node, has_python, codeowners_path."""
|
|
10
|
+
repo_root = Path(repo_root)
|
|
11
|
+
has_dbt = (repo_root / "dbt_project.yml").exists() or (repo_root / "models").is_dir()
|
|
12
|
+
has_airflow = (repo_root / "dags").is_dir() or (repo_root / "airflow.cfg").exists()
|
|
13
|
+
has_node = (repo_root / "package.json").exists()
|
|
14
|
+
has_python = (repo_root / "pyproject.toml").exists() or (repo_root / "requirements.txt").exists()
|
|
15
|
+
codeowners = repo_root / "CODEOWNERS"
|
|
16
|
+
if not codeowners.exists():
|
|
17
|
+
codeowners = repo_root / ".github" / "CODEOWNERS"
|
|
18
|
+
codeowners_path = str(codeowners) if codeowners.exists() else None
|
|
19
|
+
return {
|
|
20
|
+
"has_dbt": has_dbt,
|
|
21
|
+
"has_airflow": has_airflow,
|
|
22
|
+
"has_node": has_node,
|
|
23
|
+
"has_python": has_python,
|
|
24
|
+
"codeowners_path": codeowners_path,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def draft_yaml(signals: dict, answers: dict | None = None) -> str:
|
|
29
|
+
"""Produce draft continuum.yaml from scan signals and optional wizard answers."""
|
|
30
|
+
answers = answers or {}
|
|
31
|
+
lines = ["version: 0.1", "scopes:"]
|
|
32
|
+
# Base repo scope
|
|
33
|
+
lines.append(" - id: repo")
|
|
34
|
+
lines.append(' match: ["**/*"]')
|
|
35
|
+
lines.append(" contracts:")
|
|
36
|
+
lines.append(" - type: define")
|
|
37
|
+
lines.append(' key: prod_ready')
|
|
38
|
+
lines.append(' value: "Add rules below; then run continuum check."')
|
|
39
|
+
# Wizard: banned deps
|
|
40
|
+
banned_deps = answers.get("banned_deps") or []
|
|
41
|
+
for dep in banned_deps:
|
|
42
|
+
dep = dep.strip()
|
|
43
|
+
if not dep:
|
|
44
|
+
continue
|
|
45
|
+
safe_id = dep.replace(".", "_").replace("-", "_")[:30]
|
|
46
|
+
lines.append(" - type: ban")
|
|
47
|
+
lines.append(f" id: ban_{safe_id}")
|
|
48
|
+
lines.append(" match:")
|
|
49
|
+
lines.append(f' deps: ["{dep}"]')
|
|
50
|
+
lines.append(f' message: "Dependency {dep} is not allowed."')
|
|
51
|
+
# Wizard: ask_first paths
|
|
52
|
+
ask_paths = answers.get("ask_first_paths") or []
|
|
53
|
+
if ask_paths:
|
|
54
|
+
paths_str = ", ".join(f'"{p.strip()}"' for p in ask_paths if p.strip())
|
|
55
|
+
if paths_str:
|
|
56
|
+
lines.append(" - type: ask_first")
|
|
57
|
+
lines.append(" id: confirm_sensitive")
|
|
58
|
+
lines.append(" match:")
|
|
59
|
+
lines.append(f" paths: [{paths_str}]")
|
|
60
|
+
lines.append(' prompt: "Touching sensitive paths. Confirm intent."')
|
|
61
|
+
# Wizard: banned commands
|
|
62
|
+
banned_cmds = answers.get("banned_commands") or []
|
|
63
|
+
if banned_cmds:
|
|
64
|
+
cmds_str = ", ".join(f'"{c.strip()}"' for c in banned_cmds if c.strip())
|
|
65
|
+
if cmds_str:
|
|
66
|
+
lines.append(" - type: ban")
|
|
67
|
+
lines.append(" id: ban_commands")
|
|
68
|
+
lines.append(" match:")
|
|
69
|
+
lines.append(f" commands: [{cmds_str}]")
|
|
70
|
+
lines.append(' message: "These commands are not allowed in code."')
|
|
71
|
+
# dbt
|
|
72
|
+
if signals.get("has_dbt"):
|
|
73
|
+
lines.append(" - id: dbt")
|
|
74
|
+
lines.append(' match: ["dbt_project.yml", "models/**", "macros/**", "snapshots/**"]')
|
|
75
|
+
lines.append(" precedence: 10")
|
|
76
|
+
lines.append(" contracts:")
|
|
77
|
+
lines.append(" - type: ask_first")
|
|
78
|
+
lines.append(" id: confirm_marts")
|
|
79
|
+
lines.append(' match:')
|
|
80
|
+
lines.append(' paths: ["models/marts/**"]')
|
|
81
|
+
lines.append(' prompt: "Touching dbt marts. Confirm: (A) additive (B) breaking (C) refactor-only"')
|
|
82
|
+
lines.append(" - type: require")
|
|
83
|
+
lines.append(" id: require_tests_marts")
|
|
84
|
+
lines.append(' match:')
|
|
85
|
+
lines.append(' paths: ["models/marts/**"]')
|
|
86
|
+
lines.append(" require:")
|
|
87
|
+
lines.append(" - kind: tests")
|
|
88
|
+
lines.append(' hint: "Add or update dbt tests for changed marts."')
|
|
89
|
+
# airflow
|
|
90
|
+
if signals.get("has_airflow"):
|
|
91
|
+
lines.append(" - id: airflow")
|
|
92
|
+
lines.append(' match: ["dags/**"]')
|
|
93
|
+
lines.append(" precedence: 10")
|
|
94
|
+
lines.append(" contracts:")
|
|
95
|
+
lines.append(" - type: ask_first")
|
|
96
|
+
lines.append(" id: confirm_dag_changes")
|
|
97
|
+
lines.append(' match:')
|
|
98
|
+
lines.append(' paths: ["dags/**"]')
|
|
99
|
+
lines.append(' prompt: "Touching DAGs. Confirm you intend to change orchestration."')
|
|
100
|
+
lines.append(" - type: ban")
|
|
101
|
+
lines.append(" id: ban_risky_commands")
|
|
102
|
+
lines.append(' match:')
|
|
103
|
+
lines.append(' commands: ["*full-refresh*", "*backfill*", "*clear*"]')
|
|
104
|
+
lines.append(' message: "Risky commands (full-refresh, backfill, clear) must not be embedded in DAGs."')
|
|
105
|
+
# backend (python or node)
|
|
106
|
+
if signals.get("has_python") or signals.get("has_node"):
|
|
107
|
+
paths = ["backend/**", "src/**", "app/**"]
|
|
108
|
+
if signals.get("has_python"):
|
|
109
|
+
paths.append("**/*.py")
|
|
110
|
+
if signals.get("has_node"):
|
|
111
|
+
paths.append("lib/**")
|
|
112
|
+
match_str = ", ".join(f'"{p}"' for p in paths[:4])
|
|
113
|
+
lines.append(" - id: backend")
|
|
114
|
+
lines.append(f" match: [{match_str}]")
|
|
115
|
+
lines.append(" precedence: 10")
|
|
116
|
+
lines.append(" contracts:")
|
|
117
|
+
lines.append(" - type: require")
|
|
118
|
+
lines.append(" id: require_tests")
|
|
119
|
+
lines.append(" match:")
|
|
120
|
+
lines.append(f" paths: [{match_str}]")
|
|
121
|
+
lines.append(" require:")
|
|
122
|
+
lines.append(" - kind: tests")
|
|
123
|
+
lines.append(' hint: "Add or adjust tests for changed modules."')
|
|
124
|
+
severity = answers.get("require_severity") or "block"
|
|
125
|
+
if severity == "warn":
|
|
126
|
+
lines.append(" severity: warn")
|
|
127
|
+
if signals.get("codeowners_path"):
|
|
128
|
+
lines.append(" # CODEOWNERS found at " + signals["codeowners_path"] + " – consider ask_first for those paths")
|
|
129
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: continuum-code
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Rule engine so coding agents obey repo rules (block/warn/ask) with clear explanations.
|
|
5
|
+
Author: Continuum
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pyyaml>=6.0
|
|
10
|
+
Requires-Dist: pydantic>=2.0
|
|
11
|
+
Requires-Dist: click>=8.0
|
|
12
|
+
Requires-Dist: mcp>=1.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
15
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
16
|
+
|
|
17
|
+
# Continuum
|
|
18
|
+
|
|
19
|
+
Rule engine so coding agents **obey repo rules** across Cursor, Claude, and ChatGPT: they get **blocked**, **warned**, or **asked** before breaking rules, with clear explanations of which rule fired and how to fix.
|
|
20
|
+
|
|
21
|
+
## v0.1 promise
|
|
22
|
+
|
|
23
|
+
- Add `continuum.yaml` to your repo.
|
|
24
|
+
- Run `continuum check` locally and in CI.
|
|
25
|
+
- Cursor (MCP) agents are gated: **blocked** / **warned** / **clarification_required** before breaking rules.
|
|
26
|
+
- The system **explains** which rule fired and how to fix it.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install -e . # from repo
|
|
32
|
+
# or when published:
|
|
33
|
+
pip install continuum-code
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Requires Python 3.10+.
|
|
37
|
+
|
|
38
|
+
### Development on Windows (WSL2)
|
|
39
|
+
|
|
40
|
+
Keep the repo on the WSL filesystem (e.g. `~/repos/continuum`) for best compatibility.
|
|
41
|
+
|
|
42
|
+
**One-time (PowerShell as Admin):**
|
|
43
|
+
|
|
44
|
+
```powershell
|
|
45
|
+
wsl --install -d Ubuntu-24.04
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
After WSL install, open Ubuntu and run:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Base (keep repo under WSL, e.g. ~/repos/continuum)
|
|
52
|
+
sudo apt update && sudo apt install -y build-essential git curl
|
|
53
|
+
|
|
54
|
+
# Python via uv (fast, clean venvs)
|
|
55
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
56
|
+
source ~/.local/bin/env # or restart shell
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Per clone (inside WSL, in repo root):**
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cd ~/repos/continuum # or your path
|
|
63
|
+
uv venv
|
|
64
|
+
source .venv/bin/activate
|
|
65
|
+
uv pip install -e ".[dev]"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Run tests:**
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pytest
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Run CLI:**
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
continuum check
|
|
78
|
+
continuum init --pack python-fastapi
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For a broader Windows + WSL2 dev setup (Terminal, Docker, Antigravity safety), see your preferred guide.
|
|
82
|
+
|
|
83
|
+
## Quick start
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Create config (optional: use a pack)
|
|
87
|
+
continuum init
|
|
88
|
+
continuum init --pack python-fastapi # or node-backend, typescript-monorepo
|
|
89
|
+
|
|
90
|
+
# Check current changes (git diff)
|
|
91
|
+
continuum check
|
|
92
|
+
continuum check --staged # only staged
|
|
93
|
+
continuum check --base origin/main # diff against branch
|
|
94
|
+
|
|
95
|
+
# Explain why a rule fired
|
|
96
|
+
continuum explain
|
|
97
|
+
continuum explain ban_lodash
|
|
98
|
+
|
|
99
|
+
# List active contracts
|
|
100
|
+
continuum inspect
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Config: `continuum.yaml`
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
version: 0.1
|
|
107
|
+
scopes:
|
|
108
|
+
- id: repo
|
|
109
|
+
match: ["**/*"]
|
|
110
|
+
contracts:
|
|
111
|
+
- type: ban
|
|
112
|
+
id: ban_lodash
|
|
113
|
+
match:
|
|
114
|
+
deps: ["lodash"]
|
|
115
|
+
message: "Use native JS or approved utils."
|
|
116
|
+
- type: ask_first
|
|
117
|
+
id: confirm_migrations
|
|
118
|
+
match:
|
|
119
|
+
paths: ["**/migrations/**"]
|
|
120
|
+
prompt: "Touching migrations. Confirm: (A) add-only (B) destructive (C) refactor"
|
|
121
|
+
- id: backend
|
|
122
|
+
match: ["backend/**"]
|
|
123
|
+
precedence: 10
|
|
124
|
+
contracts:
|
|
125
|
+
- type: require
|
|
126
|
+
id: require_tests
|
|
127
|
+
match:
|
|
128
|
+
paths: ["backend/**"]
|
|
129
|
+
require:
|
|
130
|
+
- kind: tests
|
|
131
|
+
hint: "Add or adjust unit tests for changed modules."
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Contract types: **ban** (deps/paths/commands), **require** (tests/logging/ADR), **ask_first** (confirmation gate), **define** (metadata).
|
|
135
|
+
|
|
136
|
+
## Escape hatch
|
|
137
|
+
|
|
138
|
+
To skip checks (e.g. emergency hotfix), set `CONTINUUM_SKIP=1`; `continuum check` will exit 0 without running contracts. Prefer adjusting rules (e.g. `severity: warn`) in `continuum.yaml` when possible. See [docs/adoption.md](docs/adoption.md).
|
|
139
|
+
|
|
140
|
+
## CI (GitHub Action)
|
|
141
|
+
|
|
142
|
+
```yaml
|
|
143
|
+
- uses: actions/checkout@v4
|
|
144
|
+
with:
|
|
145
|
+
fetch-depth: 0
|
|
146
|
+
- uses: ./actions/continuum-check
|
|
147
|
+
with:
|
|
148
|
+
base_ref: ${{ github.event.pull_request.base.sha }}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Or in another repo: `uses: your-org/continuum/actions/continuum-check@v0.1` (and install `continuum` via pip in the action).
|
|
152
|
+
|
|
153
|
+
## Cursor / MCP
|
|
154
|
+
|
|
155
|
+
Run the MCP server so the Cursor agent can call `continuum_check` before applying changes:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
continuum mcp --transport stdio
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Add to Cursor MCP config:
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"mcpServers": {
|
|
166
|
+
"continuum": {
|
|
167
|
+
"command": "continuum",
|
|
168
|
+
"args": ["mcp", "--transport", "stdio"]
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
See [docs/cursor-mcp.md](docs/cursor-mcp.md) and [docs/demo.md](docs/demo.md) for setup and the “Refactor auth middleware” demo.
|
|
175
|
+
|
|
176
|
+
**Golden demo:** Run the 3 scenarios in [demo/README.md](demo/README.md) (dbt marts require, airflow ask-first, banned command) in under 10 minutes.
|
|
177
|
+
|
|
178
|
+
## Packs
|
|
179
|
+
|
|
180
|
+
Starter configs:
|
|
181
|
+
|
|
182
|
+
- **node-backend** – Node/JS backend (ban lodash, require tests, ask on migrations).
|
|
183
|
+
- **python-fastapi** – FastAPI app (ban requests in favor of httpx, require tests, ask on migrations).
|
|
184
|
+
- **typescript-monorepo** – TS monorepo (ban lodash, require tests in packages).
|
|
185
|
+
- **data-dbt-airflow** – dbt + Airflow repos (ask_first on marts/DAGs, require tests on marts, ban risky commands).
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
continuum init --pack python-fastapi
|
|
189
|
+
continuum init --pack data-dbt-airflow # dbt + Airflow
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 5-minute adoption (dbt + Airflow)
|
|
193
|
+
|
|
194
|
+
1. `pip install continuum-code` (or `pip install -e .` from repo).
|
|
195
|
+
2. `continuum init --pack data-dbt-airflow` → writes `continuum.yaml`.
|
|
196
|
+
3. `continuum validate` → confirm the file is valid.
|
|
197
|
+
4. `continuum check` (or `continuum check --base origin/main` for PRs).
|
|
198
|
+
5. Add the GitHub Action for CI; optionally run `continuum mcp --transport stdio` for Cursor.
|
|
199
|
+
|
|
200
|
+
## Next steps (after v0.1)
|
|
201
|
+
|
|
202
|
+
- Richer dependency detection (poetry, pnpm, pip-tools).
|
|
203
|
+
- Pattern bans (regex on diffs).
|
|
204
|
+
- Stricter “require” checks (e.g. tests touched when src touched).
|
|
205
|
+
- Decision diffs / supersession (v0.2).
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
continuum/__init__.py,sha256=4DD2V2nT10peslESeuFpV1ViFbciCM_1FXkZbxSs1S8,210
|
|
2
|
+
continuum/change_summary.py,sha256=pBYwyStK8KvO5OPK8VVORQ9pqQ7agEmQBmc0-WBDbcY,15609
|
|
3
|
+
continuum/cli.py,sha256=MYh811DPqVgUpFnU6uTkl3IIrIIqpas8MrKraoqokl4,16264
|
|
4
|
+
continuum/contracts.py,sha256=liDj_UcbsZ-TQruLQLvDebuxqph35csvMh_NhQpnv8k,5712
|
|
5
|
+
continuum/engine.py,sha256=55E2i8fpQD2w2Z_812Li1GFmC5jdS8fwy-g3w_RU3C8,9088
|
|
6
|
+
continuum/mcp_server.py,sha256=pH3xSHYyLMgj3e4aI2A84yR-pH0_HSJ6oJHxma5SQ5Q,4932
|
|
7
|
+
continuum/recommend.py,sha256=YZeWPTmHOZFqGQ8vdtaKQJ-elxEP0NDfbNM5vu1xPLg,2416
|
|
8
|
+
continuum/scan.py,sha256=4hSUG0v-vVuONrId_nYjYR_aV9eZEGWHRX-KOMeLFJA,6133
|
|
9
|
+
continuum/packs/data-dbt-airflow/README.md,sha256=hNAQdHoMU-8oIxLEoTdTuk0VeJ-IRilLI5g7_hYu_jU,1805
|
|
10
|
+
continuum/packs/data-dbt-airflow/continuum.yaml,sha256=DfQnCGzcmuTw_ZUrhVzbmfZMi3GtdLYlJJzZSCaf5OA,1385
|
|
11
|
+
continuum/packs/node-backend/continuum.yaml,sha256=MM_lR4C6hpDhhK37EyZT9l5GwW1nsjDpykZrV1QWnp0,856
|
|
12
|
+
continuum/packs/python-fastapi/continuum.yaml,sha256=WHESGlkx7L_0VgZYiZ8L-yCYISWCAUPuaoJUfAxzfpo,905
|
|
13
|
+
continuum/packs/typescript-monorepo/continuum.yaml,sha256=MVFXp1-Pw03FwEUAfvtlcXZuTS63x-3Uw0KgJ8Ahlvk,841
|
|
14
|
+
continuum_code-0.2.0.dist-info/METADATA,sha256=aq8YMf58-ecgQmnuXTYd-lnmu6srRO3hRdIEYiILTEA,5654
|
|
15
|
+
continuum_code-0.2.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
16
|
+
continuum_code-0.2.0.dist-info/entry_points.txt,sha256=V8HMNBQVMiPLhcrAkCLpJYterSgesg0FjMkWa7s9YGA,49
|
|
17
|
+
continuum_code-0.2.0.dist-info/top_level.txt,sha256=TXST1D1spateEmtvhXZfWhbvAkUySKPQ6pmiLzqNqRg,10
|
|
18
|
+
continuum_code-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
continuum
|