diff --git a/.github/ISSUE_TEMPLATE/issue_template.md b/.github/ISSUE_TEMPLATE/issue_template.md new file mode 100644 index 0000000..d3e9a66 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue_template.md @@ -0,0 +1,32 @@ +--- +name: Issue Report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug or feature request** +A clear and concise description of what the bug is or what the feature request is about. + +**To Reproduce** +Steps to reproduce the behavior (if applicable): +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..411e9fd --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,22 @@ +## Description +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. + +Fixes # (issue) + +## Type of change +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +## How Has This Been Tested? +Please describe the tests that you ran to verify your changes. + +## Checklist: +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml new file mode 100644 index 0000000..413391c --- /dev/null +++ b/.github/workflows/ai-review.yml @@ -0,0 +1,18 @@ +name: AI Code Review + +on: + pull_request: + types: [opened, synchronize] + +jobs: + review: + runs-on: ubuntu-latest + steps: + - uses: coderabbitai/openai-pr-reviewer@v1.17.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + with: + debug: false + review_simple_changes: false + review_comment_lgtm: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9fd5981 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + branches: [ "main", "master" ] + pull_request: + branches: [ "main", "master" ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + lfs: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + + - name: Install uv + run: curl -LsSf https://astral.sh/uv/install.sh | sh + + - name: Install dependencies + run: | + $HOME/.local/bin/uv venv + $HOME/.local/bin/uv pip install -e ".[dev]" + + - name: Run Python tests + run: $HOME/.local/bin/uv run pytest tests/ + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install Frontend dependencies + run: cd web && npm install + + - name: Run Frontend tests + run: cd web && npm run build diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml new file mode 100644 index 0000000..3de6e67 --- /dev/null +++ b/.github/workflows/greetings.yml @@ -0,0 +1,31 @@ +name: Greetings + +on: + issues: + types: [opened] + pull_request: + types: [opened] + +jobs: + welcome: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v6 + with: + script: | + const isPR = !!context.payload.pull_request; + const user = context.payload.sender.login; + const authorAssociation = context.payload.issue?.author_association || context.payload.pull_request?.author_association; + + if (authorAssociation === 'FIRST_TIMER' || authorAssociation === 'FIRST_TIME_CONTRIBUTOR') { + const body = isPR + ? `Welcome @${user}! Thank you for your first pull request. A maintainer will review it shortly. Please ensure all tests pass and your code follows our contribution guidelines.` + : `Welcome @${user}! Thank you for opening your first issue. We will look into it as soon as possible.`; + + github.rest.issues.createComment({ + issue_number: context.issue.number || context.payload.pull_request.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: body + }); + } diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..ef860cb --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,35 @@ +name: Deploy documentation to GitHub Pages + +on: + push: + branches: ["main"] + + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: './docs' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/repo-maintenance.yml b/.github/workflows/repo-maintenance.yml new file mode 100644 index 0000000..16ee9b5 --- /dev/null +++ b/.github/workflows/repo-maintenance.yml @@ -0,0 +1,56 @@ +name: Repository Maintenance + +on: + push: + branches: [ "main", "master" ] + pull_request: + branches: [ "main", "master" ] + +permissions: + contents: write + +jobs: + maintenance: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref || github.ref }} + lfs: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + $HOME/.local/bin/uv venv + $HOME/.local/bin/uv pip install --system pydeps xdg-utils cyclonedx-bom ruff + + - name: Run Ruff autofix + run: $HOME/.local/bin/uv run ruff check --fix . || true + + - name: Generate Knowledge Graph + run: python tools/generate_knowledge_graph.py + + - name: Sync Docs + run: python tools/docs_sync.py + + - name: Generate Architecture Diagram + run: pydeps . -o docs/arch.svg || true + + - name: Generate SBOM + run: | + $HOME/.local/bin/uv venv + $HOME/.local/bin/uv pip install -e ".[dev]" + $HOME/.local/bin/uv run cyclonedx-py environment -o bom.json || true + + - name: Commit changes + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add docs/ bom.json || true + git commit -m "Auto-generated repository artifacts" || true + git push || true diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..db3b015 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,20 @@ +name: Manage Stale Issues + +on: + schedule: + - cron: "0 0 * * *" + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v8 + with: + stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.' + stale-pr-message: 'This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.' + days-before-stale: 60 + days-before-close: 7 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..6b83cee --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,27 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e462d0b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# Contributing + +First off, thank you for considering contributing to this project. It's people like you that make open source such a great community. + +## Where do I go from here? + +If you've noticed a bug or have a feature request, make sure to check our Issues to see if someone else in the community has already created a ticket. If not, go ahead and make one! + +## Fork & create a branch + +If this is something you think you can fix, then fork the repository and create a branch with a descriptive name. + +## Implement your fix or feature + +At this point, you're ready to make your changes! Feel free to ask for help; everyone is a beginner at first. + +## Make a Pull Request + +At this point, you should switch back to your master branch and make sure it's up to date with the main project's master branch. +Then run `uv run pytest tests/` and `cd web && npm test` to ensure tests are passing. +Once tests pass, open a Pull Request. + +## Keeping your Pull Request updated + +If a maintainer asks you to rebase your PR, they're saying that a lot of code has changed, and that you need to update your branch so it's easier to merge. diff --git a/tools/docs_sync.py b/tools/docs_sync.py new file mode 100644 index 0000000..d8e873e --- /dev/null +++ b/tools/docs_sync.py @@ -0,0 +1,26 @@ +import os + + +def sync_docs(docs_dir="docs"): + index_path = os.path.join(docs_dir, "index.md") + + markdown_files = [] + for root, _, files in os.walk(docs_dir): + for file in files: + if file.endswith(".md") and file != "index.md": + rel_path = os.path.relpath(os.path.join(root, file), docs_dir) + markdown_files.append(rel_path) + + markdown_files.sort() + + os.makedirs(docs_dir, exist_ok=True) + with open(index_path, "w", encoding="utf-8") as f: + f.write("# Documentation Index\n\n") + f.write("This index is automatically generated.\n\n") + for file in markdown_files: + title = file.replace(".md", "").replace("_", " ").title() + f.write(f"* [{title}]({file})\n") + + +if __name__ == "__main__": + sync_docs() diff --git a/tools/generate_knowledge_graph.py b/tools/generate_knowledge_graph.py new file mode 100644 index 0000000..9b0ede5 --- /dev/null +++ b/tools/generate_knowledge_graph.py @@ -0,0 +1,76 @@ +import os +import ast +import json + + +def generate_knowledge_graph(root_dir="."): + graph = {"nodes": [], "edges": []} + + file_id_counter = 1 + file_ids = {} + + for dirpath, _, filenames in os.walk(root_dir): + if any( + exclude in dirpath + for exclude in [".git", "__pycache__", "node_modules", ".venv", "venv"] + ): + continue + + for filename in filenames: + if not filename.endswith(".py"): + continue + + filepath = os.path.join(dirpath, filename) + + try: + with open(filepath, "r", encoding="utf-8") as f: + content = f.read() + + tree = ast.parse(content) + + file_id = f"file_{file_id_counter}" + file_ids[filepath] = file_id + file_id_counter += 1 + + graph["nodes"].append( + {"id": file_id, "type": "file", "path": filepath, "label": filename} + ) + + for node in ast.walk(tree): + if isinstance(node, ast.ClassDef): + class_id = f"class_{file_id}_{node.name}" + graph["nodes"].append( + { + "id": class_id, + "type": "class", + "label": node.name, + "file": filepath, + } + ) + graph["edges"].append( + {"source": file_id, "target": class_id, "type": "contains"} + ) + elif isinstance(node, ast.FunctionDef): + func_id = f"func_{file_id}_{node.name}" + graph["nodes"].append( + { + "id": func_id, + "type": "function", + "label": node.name, + "file": filepath, + } + ) + graph["edges"].append( + {"source": file_id, "target": func_id, "type": "contains"} + ) + + except Exception as e: + print(f"Error parsing {filepath}: {e}") + + os.makedirs("docs", exist_ok=True) + with open("docs/knowledge_graph.json", "w") as f: + json.dump(graph, f, indent=2) + + +if __name__ == "__main__": + generate_knowledge_graph()