Skip to content

Commit 573c9b2

Browse files
Pigbibicodex
andauthored
feat: add QSL version control CLI (#159)
* feat: add QSL version control CLI Co-Authored-By: Codex <noreply@openai.com> * feat: add QSL workspace report plan Co-Authored-By: Codex <noreply@openai.com> --------- Co-authored-by: Codex <noreply@openai.com>
1 parent 21add24 commit 573c9b2

7 files changed

Lines changed: 766 additions & 12 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ This repository is the central manifest source for QSL compatibility checks.
3333

3434
- `qsl.toml`: current repo QSL metadata (`tier`, `upgrade_ring`, `bundle`).
3535
- `scripts/check_qsl_compat.py`: validate a repo's QSL compliance in `pyproject.toml`/`uv.lock` against central bundle.
36+
- `python/scripts/qslctl.py`: unified QSL version-control CLI for repo checks, workspace checks, ring reports/plans, and matrix generation.
3637
- `scripts/render_qsl_dependency_graph.py`: render dependency graph for review.
3738
- `compat/bundles/2026.07.0.toml`: first compatibility bundle (CalVer).
3839
- `compat/repo-tiers.toml`: repo tier and upgrade ring policy notes.

docs/qsl_version_control.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# QSL version control plane
2+
3+
QuantRuntimeSettings is the control plane for QuantStrategyLab internal version
4+
management.
5+
6+
## Source of truth
7+
8+
- `compat/bundles/*.toml` is the source of truth for internal repository commit pins.
9+
- Each consumer repository declares its bundle in `qsl.toml`.
10+
- Consumer files (`pyproject.toml`, `uv.lock`, `requirements.txt`, `constraints.txt`) must match the declared bundle.
11+
- `internal_dependency_matrix.json` is generated from local consumer dependency files; do not hand-edit it except for emergency repair.
12+
13+
## CLI
14+
15+
Use `qslctl` for repository and workspace checks:
16+
17+
```bash
18+
python3 python/scripts/qslctl.py check --repo-root ../UsEquityStrategies
19+
python3 python/scripts/qslctl.py check-all --projects-root /Users/lisiyi/Projects
20+
python3 python/scripts/qslctl.py report --projects-root /Users/lisiyi/Projects
21+
python3 python/scripts/qslctl.py plan --projects-root /Users/lisiyi/Projects
22+
python3 python/scripts/qslctl.py generate-matrix --projects-root /Users/lisiyi/Projects --check
23+
python3 python/scripts/qslctl.py generate-matrix --projects-root /Users/lisiyi/Projects --sync
24+
```
25+
26+
## Rollout policy
27+
28+
1. Create or update a bundle in `compat/bundles/`.
29+
2. Apply that bundle by ring: core → strategy libraries → pipelines/research → runtime platforms → ops/tooling.
30+
3. Regenerate `internal_dependency_matrix.json` after consumer pins are updated.
31+
4. Keep strict checks enabled only after a ring is converged.
32+
33+
## Workspace report / plan
34+
35+
- `qslctl report` is read-only. It groups the current workspace by ring, status, and bundle hotspot.
36+
- `qslctl plan` is read-only. It renders the ring-by-ring convergence order and highlights which repos should be fixed before the next ring starts.
37+
- Use `report` to answer “what is broken right now?” and `plan` to answer “what should we fix first?”

internal_dependency_matrix.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
"path": "requirements.txt",
9191
"package": "us-equity-strategies",
9292
"source_repo": "UsEquityStrategies",
93-
"ref": "cd959e30e91e57697ad878d98799c1afb43bf5fb"
93+
"ref": "1643f37d723c4d0fdc475ce1030b3d911c07aacb"
9494
},
9595
{
9696
"consumer_repo": "InteractiveBrokersPlatform",

python/scripts/check_internal_dependency_matrix.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,18 @@
1414
ROOT = Path(__file__).resolve().parents[2]
1515
DEFAULT_MATRIX_PATH = ROOT / "internal_dependency_matrix.json"
1616
DEFAULT_PROJECTS_ROOT = ROOT.parent
17-
DEPENDENCY_PATTERN = re.compile(
17+
PYPROJECT_DEPENDENCY_PATTERN = re.compile(
1818
r"(?P<package>[A-Za-z0-9_.-]+)\s*@\s*"
1919
r"git\+https://github\.com/QuantStrategyLab/"
2020
r"(?P<source_repo>[A-Za-z0-9_.-]+)\.git@(?P<ref>[A-Za-z0-9_.-]+)"
2121
)
22-
TRACKED_DEPENDENCY_PATHS = ("requirements.txt", "requirements-lock.txt", "pyproject.toml")
22+
UV_DEPENDENCY_PATTERN = re.compile(
23+
r"name\s*=\s*\"(?P<package>[A-Za-z0-9_.-]+)\"[^\n]*"
24+
r"git\s*=\s*\"https://github\.com/QuantStrategyLab/"
25+
r"(?P<source_repo>[A-Za-z0-9_.-]+)\.git\?rev=(?P<ref>[A-Za-z0-9_.-]+)"
26+
)
27+
DEPENDENCY_PATTERNS = (PYPROJECT_DEPENDENCY_PATTERN, UV_DEPENDENCY_PATTERN)
28+
TRACKED_DEPENDENCY_PATHS = ("requirements.txt", "requirements-lock.txt", "pyproject.toml", "uv.lock")
2329
LEGACY_DEPENDENCY_PATHS = ("requirements.txt", "requirements-lock.txt")
2430
PYPROJECT_FALLBACK_PATH = "pyproject.toml"
2531

@@ -116,16 +122,19 @@ def _required_string(item: dict[str, Any], key: str, index: int) -> str:
116122

117123

118124
def parse_dependency_pins(consumer_repo: str, path: str, text: str) -> list[DependencyPin]:
119-
return [
120-
DependencyPin(
121-
consumer_repo=consumer_repo,
122-
path=path,
123-
package=match.group("package"),
124-
source_repo=match.group("source_repo"),
125-
ref=match.group("ref"),
125+
pins: list[DependencyPin] = []
126+
for pattern in DEPENDENCY_PATTERNS:
127+
pins.extend(
128+
DependencyPin(
129+
consumer_repo=consumer_repo,
130+
path=path,
131+
package=match.group("package"),
132+
source_repo=match.group("source_repo"),
133+
ref=match.group("ref"),
134+
)
135+
for match in pattern.finditer(text)
126136
)
127-
for match in DEPENDENCY_PATTERN.finditer(text)
128-
]
137+
return _sort_dependency_pins(pins)
129138

130139

131140
def _parse_repo_pins(projects_root: Path, consumer_repo: str, relative_path: str) -> list[DependencyPin]:

0 commit comments

Comments
 (0)