diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..5c8a4a8d --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,18 @@ +# CODEOWNERS — auto-assign reviewers for PRs +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +# Default: repo owner reviews everything +* @NeuZhou + +# Evolution engine — core, needs careful review +stratevo/evolution/ @NeuZhou +stratevo/fitness_plugins/ @NeuZhou + +# Factors — community contributions welcome +stratevo/factor_registry/builtin/ @NeuZhou + +# CI & Infrastructure +.github/ @NeuZhou + +# Trading — sensitive, needs extra scrutiny +stratevo/trading/ @NeuZhou diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..184ba95f --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,39 @@ +name: Stale + +on: + schedule: + - cron: '30 1 * * *' # daily at 01:30 UTC + workflow_dispatch: + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 30 + days-before-close: 14 + stale-issue-label: stale + stale-pr-label: stale + stale-issue-message: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed in 14 days if no further activity occurs. + If this is still relevant, please leave a comment or remove the stale label. + stale-pr-message: > + This pull request has been automatically marked as stale because it has not had + recent activity. It will be closed in 14 days if no further activity occurs. + If you're still working on this, please leave a comment or push a new commit. + close-issue-message: > + This issue was closed because it has been stale for 14 days with no activity. + Feel free to reopen if this is still relevant. + close-pr-message: > + This PR was closed because it has been stale for 14 days with no activity. + Feel free to reopen if you'd like to continue working on it. + exempt-issue-labels: 'pinned,priority,in-progress' + exempt-pr-labels: 'pinned,priority,in-progress' + exempt-all-milestones: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f006973..e035676c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,8 +12,8 @@ Thanks for your interest in contributing! StratEvo is an AI-native quantitative ```bash # 1. Clone the repo -git clone https://github.com/NeuZhou/finclaw.git -cd finclaw +git clone https://github.com/NeuZhou/stratevo.git +cd stratevo # 2. Install in development mode (with pre-commit hooks) make dev @@ -41,8 +41,8 @@ make check | Area | Description | |------|-------------| -| 🐛 Bug reports | Found something broken? [Open an issue](https://github.com/NeuZhou/finclaw/issues/new?template=bug_report.yml) | -| ✨ Features | Have an idea? [Request a feature](https://github.com/NeuZhou/finclaw/issues/new?template=feature_request.yml) | +| 🐛 Bug reports | Found something broken? [Open an issue](https://github.com/NeuZhou/stratevo/issues/new?template=bug_report.yml) | +| ✨ Features | Have an idea? [Request a feature](https://github.com/NeuZhou/stratevo/issues/new?template=feature_request.yml) | | 📈 Factors | Add a new alpha factor to the evolution engine | | 🏋️ Fitness Functions | Implement a new fitness function for strategy evaluation | | 📡 Data Sources | Connect a new data provider | @@ -164,6 +164,8 @@ All of the following must pass before a PR can be merged: - `ruff check .` — linting - No regressions in existing tests +> **Note for first-time contributors:** GitHub requires a maintainer to approve CI runs on your first PR from a fork. This is a security measure. Don't worry — we'll approve it quickly! + ## Issue Labels | Label | Description | diff --git a/SECURITY.md b/SECURITY.md index 88f18440..215d26dd 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,7 +4,8 @@ | Version | Supported | | ------- | ------------------ | -| 5.x.x | ✅ | +| 6.x.x | ✅ | +| 5.x.x | ✅ (security only) | | < 5.0 | ❌ | ## Reporting a Vulnerability diff --git a/stratevo/cli/commands/scaffold.py b/stratevo/cli/commands/scaffold.py index e9614b3c..673c1ef1 100644 --- a/stratevo/cli/commands/scaffold.py +++ b/stratevo/cli/commands/scaffold.py @@ -188,6 +188,19 @@ def test_supported_symbols(self): # ─── scaffold commands ────────────────────────────────────────────── +def _find_repo_root() -> Optional[str]: + """Walk up from CWD to find the repo root (contains stratevo/ package).""" + cur = os.path.abspath(".") + for _ in range(10): + if os.path.isdir(os.path.join(cur, "stratevo", "factor_registry")): + return cur + parent = os.path.dirname(cur) + if parent == cur: + break + cur = parent + return None + + def scaffold_factor( name: str, category: str = "custom", @@ -195,9 +208,24 @@ def scaffold_factor( ) -> tuple[str, str]: """Generate a factor file and its companion test. Returns tuple of paths.""" class_name = _to_class_name(name) - base = output_dir or "." - src_path = os.path.join(base, f"{name}_factor.py") - test_path = os.path.join(base, f"test_{name}_factor.py") + + # Auto-detect correct placement when output_dir is not given + if output_dir is None: + repo_root = _find_repo_root() + if repo_root is not None: + src_dir = os.path.join( + repo_root, "stratevo", "factor_registry", "builtin", + ) + test_dir = os.path.join(repo_root, "tests") + else: + src_dir = "." + test_dir = "." + else: + src_dir = output_dir + test_dir = output_dir + + src_path = os.path.join(src_dir, f"{name}_factor.py") + test_path = os.path.join(test_dir, f"test_{name}_factor.py") _write_file(src_path, _factor_template(name, category, class_name)) _write_file(test_path, _factor_test_template(name, category, class_name)) @@ -251,8 +279,7 @@ def cmd_scaffold(args: object) -> None: print(" Next steps:") print(f" 1. Open {src} and implement compute()") print(f" 2. Run tests: python -m pytest {test} -v") - print(" 3. Move into stratevo/factor_registry/builtin// when ready") - print(" 4. Submit a PR!") + print(" 3. Submit a PR!") elif scaffold_type == "fitness": name = args.name # type: ignore[attr-defined]