frostwatch 0.1.0__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.
Files changed (87) hide show
  1. frostwatch-0.1.0/.github/CODEOWNERS +11 -0
  2. frostwatch-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +84 -0
  3. frostwatch-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +55 -0
  4. frostwatch-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +41 -0
  5. frostwatch-0.1.0/.github/dependabot.yml +71 -0
  6. frostwatch-0.1.0/.github/labeler.yml +36 -0
  7. frostwatch-0.1.0/.github/workflows/ci.yml +121 -0
  8. frostwatch-0.1.0/.github/workflows/pr-checks.yml +160 -0
  9. frostwatch-0.1.0/.github/workflows/release.yml +136 -0
  10. frostwatch-0.1.0/.github/workflows/security.yml +135 -0
  11. frostwatch-0.1.0/.gitignore +22 -0
  12. frostwatch-0.1.0/CHANGELOG.md +36 -0
  13. frostwatch-0.1.0/CODE_OF_CONDUCT.md +41 -0
  14. frostwatch-0.1.0/CONTRIBUTING.md +125 -0
  15. frostwatch-0.1.0/Dockerfile +48 -0
  16. frostwatch-0.1.0/LICENSE +21 -0
  17. frostwatch-0.1.0/PKG-INFO +36 -0
  18. frostwatch-0.1.0/README.md +147 -0
  19. frostwatch-0.1.0/ROADMAP.md +109 -0
  20. frostwatch-0.1.0/SECURITY.md +49 -0
  21. frostwatch-0.1.0/docker-compose.yml +24 -0
  22. frostwatch-0.1.0/frontend/index.html +13 -0
  23. frostwatch-0.1.0/frontend/package-lock.json +3229 -0
  24. frostwatch-0.1.0/frontend/package.json +31 -0
  25. frostwatch-0.1.0/frontend/postcss.config.js +6 -0
  26. frostwatch-0.1.0/frontend/src/App.tsx +24 -0
  27. frostwatch-0.1.0/frontend/src/api/client.ts +202 -0
  28. frostwatch-0.1.0/frontend/src/components/AnomalyBadge.tsx +48 -0
  29. frostwatch-0.1.0/frontend/src/components/CostChart.tsx +151 -0
  30. frostwatch-0.1.0/frontend/src/components/Layout.tsx +153 -0
  31. frostwatch-0.1.0/frontend/src/components/MetricCard.tsx +80 -0
  32. frostwatch-0.1.0/frontend/src/components/QueryTable.tsx +185 -0
  33. frostwatch-0.1.0/frontend/src/index.css +48 -0
  34. frostwatch-0.1.0/frontend/src/main.tsx +25 -0
  35. frostwatch-0.1.0/frontend/src/pages/Anomalies.tsx +212 -0
  36. frostwatch-0.1.0/frontend/src/pages/Dashboard.tsx +334 -0
  37. frostwatch-0.1.0/frontend/src/pages/Queries.tsx +231 -0
  38. frostwatch-0.1.0/frontend/src/pages/Reports.tsx +232 -0
  39. frostwatch-0.1.0/frontend/src/pages/Settings.tsx +617 -0
  40. frostwatch-0.1.0/frontend/src/pages/Warehouses.tsx +189 -0
  41. frostwatch-0.1.0/frontend/tailwind.config.js +30 -0
  42. frostwatch-0.1.0/frontend/tsconfig.json +21 -0
  43. frostwatch-0.1.0/frontend/tsconfig.node.json +10 -0
  44. frostwatch-0.1.0/frontend/vite.config.ts +26 -0
  45. frostwatch-0.1.0/frostwatch/__init__.py +1 -0
  46. frostwatch-0.1.0/frostwatch/alerts/__init__.py +0 -0
  47. frostwatch-0.1.0/frostwatch/alerts/email.py +69 -0
  48. frostwatch-0.1.0/frostwatch/alerts/slack.py +49 -0
  49. frostwatch-0.1.0/frostwatch/analysis/__init__.py +0 -0
  50. frostwatch-0.1.0/frostwatch/analysis/anomaly.py +120 -0
  51. frostwatch-0.1.0/frostwatch/analysis/cost.py +125 -0
  52. frostwatch-0.1.0/frostwatch/analysis/recommendations.py +102 -0
  53. frostwatch-0.1.0/frostwatch/api/__init__.py +0 -0
  54. frostwatch-0.1.0/frostwatch/api/app.py +111 -0
  55. frostwatch-0.1.0/frostwatch/api/limiter.py +6 -0
  56. frostwatch-0.1.0/frostwatch/api/models.py +135 -0
  57. frostwatch-0.1.0/frostwatch/api/routes/__init__.py +0 -0
  58. frostwatch-0.1.0/frostwatch/api/routes/anomalies.py +43 -0
  59. frostwatch-0.1.0/frostwatch/api/routes/dashboard.py +125 -0
  60. frostwatch-0.1.0/frostwatch/api/routes/queries.py +51 -0
  61. frostwatch-0.1.0/frostwatch/api/routes/reports.py +132 -0
  62. frostwatch-0.1.0/frostwatch/api/routes/scheduler_routes.py +44 -0
  63. frostwatch-0.1.0/frostwatch/api/routes/settings.py +125 -0
  64. frostwatch-0.1.0/frostwatch/api/routes/sync.py +231 -0
  65. frostwatch-0.1.0/frostwatch/api/routes/warehouses.py +107 -0
  66. frostwatch-0.1.0/frostwatch/cli.py +171 -0
  67. frostwatch-0.1.0/frostwatch/core/__init__.py +0 -0
  68. frostwatch-0.1.0/frostwatch/core/config.py +102 -0
  69. frostwatch-0.1.0/frostwatch/core/db.py +118 -0
  70. frostwatch-0.1.0/frostwatch/core/scheduler.py +135 -0
  71. frostwatch-0.1.0/frostwatch/llm/__init__.py +0 -0
  72. frostwatch-0.1.0/frostwatch/llm/anthropic_provider.py +33 -0
  73. frostwatch-0.1.0/frostwatch/llm/base.py +14 -0
  74. frostwatch-0.1.0/frostwatch/llm/factory.py +36 -0
  75. frostwatch-0.1.0/frostwatch/llm/gemini_provider.py +53 -0
  76. frostwatch-0.1.0/frostwatch/llm/ollama_provider.py +38 -0
  77. frostwatch-0.1.0/frostwatch/llm/openai_provider.py +35 -0
  78. frostwatch-0.1.0/frostwatch/snowflake/__init__.py +0 -0
  79. frostwatch-0.1.0/frostwatch/snowflake/client.py +60 -0
  80. frostwatch-0.1.0/frostwatch/snowflake/queries.py +62 -0
  81. frostwatch-0.1.0/frostwatch.yaml.example +42 -0
  82. frostwatch-0.1.0/pyproject.toml +74 -0
  83. frostwatch-0.1.0/tests/__init__.py +0 -0
  84. frostwatch-0.1.0/tests/unit/__init__.py +0 -0
  85. frostwatch-0.1.0/tests/unit/test_analysis_anomaly.py +48 -0
  86. frostwatch-0.1.0/tests/unit/test_analysis_cost.py +51 -0
  87. frostwatch-0.1.0/tests/unit/test_llm_factory.py +46 -0
@@ -0,0 +1,11 @@
1
+ # Global owner — reviews required for all PRs
2
+ * @arunrajiah
3
+
4
+ # Backend
5
+ frostwatch/ @arunrajiah
6
+
7
+ # Frontend
8
+ frontend/ @arunrajiah
9
+
10
+ # CI / Release
11
+ .github/ @arunrajiah
@@ -0,0 +1,84 @@
1
+ name: Bug Report
2
+ description: Report something that is broken or behaving unexpectedly
3
+ labels: ["bug", "needs-triage"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Thanks for taking the time to report a bug. Please fill in as much detail as you can.
9
+
10
+ - type: input
11
+ id: version
12
+ attributes:
13
+ label: FrostWatch version
14
+ description: Run `frostwatch version` to get this
15
+ placeholder: "0.1.0"
16
+ validations:
17
+ required: true
18
+
19
+ - type: input
20
+ id: python
21
+ attributes:
22
+ label: Python version
23
+ placeholder: "3.11.9"
24
+ validations:
25
+ required: true
26
+
27
+ - type: input
28
+ id: os
29
+ attributes:
30
+ label: Operating system
31
+ placeholder: "macOS 14.5 / Ubuntu 22.04 / Docker"
32
+ validations:
33
+ required: true
34
+
35
+ - type: textarea
36
+ id: description
37
+ attributes:
38
+ label: Description
39
+ description: A clear and concise description of what the bug is
40
+ validations:
41
+ required: true
42
+
43
+ - type: textarea
44
+ id: reproduce
45
+ attributes:
46
+ label: Steps to reproduce
47
+ placeholder: |
48
+ 1. Configure X as ...
49
+ 2. Run `frostwatch serve`
50
+ 3. Navigate to /warehouses
51
+ 4. See error
52
+ validations:
53
+ required: true
54
+
55
+ - type: textarea
56
+ id: expected
57
+ attributes:
58
+ label: Expected behaviour
59
+ validations:
60
+ required: true
61
+
62
+ - type: textarea
63
+ id: actual
64
+ attributes:
65
+ label: Actual behaviour
66
+ validations:
67
+ required: true
68
+
69
+ - type: textarea
70
+ id: logs
71
+ attributes:
72
+ label: Relevant logs or stack traces
73
+ render: text
74
+
75
+ - type: dropdown
76
+ id: llm_provider
77
+ attributes:
78
+ label: LLM provider (if relevant)
79
+ options:
80
+ - anthropic
81
+ - openai
82
+ - gemini
83
+ - ollama
84
+ - N/A
@@ -0,0 +1,55 @@
1
+ name: Feature Request
2
+ description: Suggest a new feature or improvement
3
+ labels: ["enhancement", "needs-triage"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Check [ROADMAP.md](https://github.com/arunrajiah/frostwatch/blob/main/ROADMAP.md) first — your idea may already be planned.
9
+
10
+ - type: textarea
11
+ id: problem
12
+ attributes:
13
+ label: What problem does this solve?
14
+ description: Describe the pain point or use case. "I'm trying to do X but I can't because..."
15
+ validations:
16
+ required: true
17
+
18
+ - type: textarea
19
+ id: solution
20
+ attributes:
21
+ label: Proposed solution
22
+ description: What would you like to see? How would it work?
23
+ validations:
24
+ required: true
25
+
26
+ - type: textarea
27
+ id: alternatives
28
+ attributes:
29
+ label: Alternatives considered
30
+ description: What have you tried or considered instead?
31
+
32
+ - type: dropdown
33
+ id: area
34
+ attributes:
35
+ label: Area
36
+ options:
37
+ - Cost analysis
38
+ - Anomaly detection
39
+ - LLM / AI features
40
+ - Alerts (Slack / email)
41
+ - Web UI
42
+ - CLI
43
+ - Snowflake integration
44
+ - Deployment / Docker
45
+ - Documentation
46
+ - Other
47
+ validations:
48
+ required: true
49
+
50
+ - type: checkboxes
51
+ id: contribution
52
+ attributes:
53
+ label: Contribution
54
+ options:
55
+ - label: I am willing to submit a PR for this feature
@@ -0,0 +1,41 @@
1
+ ## Summary
2
+
3
+ <!-- One or two sentences describing what this PR does and why. -->
4
+
5
+ Fixes # <!-- issue number, if applicable -->
6
+
7
+ ## Type of change
8
+
9
+ - [ ] Bug fix (non-breaking change that fixes an issue)
10
+ - [ ] New feature (non-breaking change that adds functionality)
11
+ - [ ] Breaking change (fix or feature that would cause existing behaviour to change)
12
+ - [ ] Documentation update
13
+ - [ ] Refactor / internal improvement
14
+ - [ ] CI / tooling
15
+
16
+ ## Changes
17
+
18
+ <!-- Bullet-point list of what changed. Be specific — mention file names and function names where useful. -->
19
+
20
+ -
21
+ -
22
+
23
+ ## Testing
24
+
25
+ <!-- Describe how you tested this change. -->
26
+
27
+ - [ ] Unit tests added / updated (`pytest tests/`)
28
+ - [ ] Manually tested locally
29
+ - [ ] Frontend build passes (`cd frontend && npm run build`)
30
+ - [ ] No new lint or type errors (`ruff check frostwatch/ && mypy frostwatch/`)
31
+
32
+ ## Screenshots (if UI change)
33
+
34
+ <!-- Before / after screenshots help reviewers a lot. Delete this section if not applicable. -->
35
+
36
+ ## Checklist
37
+
38
+ - [ ] PR title follows Conventional Commits format (`feat(scope): description`)
39
+ - [ ] I have read [CONTRIBUTING.md](CONTRIBUTING.md)
40
+ - [ ] My changes do not introduce new security vulnerabilities
41
+ - [ ] I have updated documentation where relevant
@@ -0,0 +1,71 @@
1
+ version: 2
2
+
3
+ updates:
4
+ - package-ecosystem: pip
5
+ directory: "/"
6
+ schedule:
7
+ interval: weekly
8
+ day: monday
9
+ time: "06:00"
10
+ open-pull-requests-limit: 5
11
+ labels:
12
+ - dependencies
13
+ - backend
14
+ ignore:
15
+ - dependency-name: "*"
16
+ update-types: ["version-update:semver-major"]
17
+ groups:
18
+ llm-providers:
19
+ patterns:
20
+ - "anthropic"
21
+ - "openai"
22
+ - "google-genai"
23
+ fastapi-stack:
24
+ patterns:
25
+ - "fastapi"
26
+ - "uvicorn"
27
+ - "pydantic*"
28
+ - "sqlalchemy"
29
+ - "aiosqlite"
30
+
31
+ - package-ecosystem: npm
32
+ directory: "/frontend"
33
+ schedule:
34
+ interval: weekly
35
+ day: monday
36
+ time: "06:00"
37
+ open-pull-requests-limit: 5
38
+ labels:
39
+ - dependencies
40
+ - frontend
41
+ ignore:
42
+ - dependency-name: "*"
43
+ update-types: ["version-update:semver-major"]
44
+ groups:
45
+ react:
46
+ patterns:
47
+ - "react"
48
+ - "react-dom"
49
+ - "react-router-dom"
50
+ - "@types/react*"
51
+ build-tools:
52
+ patterns:
53
+ - "vite"
54
+ - "@vitejs/*"
55
+ - "typescript"
56
+ - "tailwindcss"
57
+ - "postcss"
58
+ - "autoprefixer"
59
+
60
+ - package-ecosystem: github-actions
61
+ directory: "/"
62
+ schedule:
63
+ interval: weekly
64
+ day: monday
65
+ time: "06:00"
66
+ labels:
67
+ - dependencies
68
+ - ci
69
+ ignore:
70
+ - dependency-name: "*"
71
+ update-types: ["version-update:semver-major"]
@@ -0,0 +1,36 @@
1
+ backend:
2
+ - changed-files:
3
+ - any-glob-to-any-file: "frostwatch/**/*"
4
+
5
+ frontend:
6
+ - changed-files:
7
+ - any-glob-to-any-file: "frontend/src/**/*"
8
+
9
+ ci:
10
+ - changed-files:
11
+ - any-glob-to-any-file: ".github/workflows/**/*"
12
+
13
+ docs:
14
+ - changed-files:
15
+ - any-glob-to-any-file:
16
+ - "*.md"
17
+ - "docs/**/*"
18
+
19
+ docker:
20
+ - changed-files:
21
+ - any-glob-to-any-file:
22
+ - "Dockerfile"
23
+ - "docker-compose.yml"
24
+
25
+ dependencies:
26
+ - changed-files:
27
+ - any-glob-to-any-file:
28
+ - "pyproject.toml"
29
+ - "frontend/package.json"
30
+ - "frontend/package-lock.json"
31
+
32
+ security:
33
+ - changed-files:
34
+ - any-glob-to-any-file:
35
+ - ".github/workflows/security.yml"
36
+ - "SECURITY.md"
@@ -0,0 +1,121 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ concurrency:
10
+ group: ci-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ lint:
15
+ name: Lint & format (Python)
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.11"
23
+
24
+ - name: Install ruff
25
+ run: pip install ruff==0.4.10
26
+
27
+ - name: Check formatting
28
+ run: ruff format --check frostwatch/
29
+
30
+ - name: Lint
31
+ run: ruff check frostwatch/
32
+
33
+ typecheck:
34
+ name: Type check (Python)
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+
39
+ - uses: actions/setup-python@v5
40
+ with:
41
+ python-version: "3.11"
42
+
43
+ - name: Install package + mypy
44
+ run: |
45
+ pip install -e ".[dev]"
46
+ pip install mypy types-PyYAML types-aiofiles
47
+
48
+ - name: mypy
49
+ run: mypy frostwatch/ --ignore-missing-imports --no-strict-optional
50
+
51
+ test:
52
+ name: Unit tests (Python ${{ matrix.python-version }})
53
+ runs-on: ubuntu-latest
54
+ strategy:
55
+ matrix:
56
+ python-version: ["3.11", "3.13"]
57
+ steps:
58
+ - uses: actions/checkout@v4
59
+
60
+ - uses: actions/setup-python@v5
61
+ with:
62
+ python-version: ${{ matrix.python-version }}
63
+
64
+ - name: Install package + test deps
65
+ run: pip install -e ".[dev]"
66
+
67
+ - name: Run tests
68
+ run: pytest tests/unit/ -v --cov=frostwatch --cov-report=xml --cov-report=term-missing
69
+
70
+ - name: Upload coverage
71
+ uses: codecov/codecov-action@v4
72
+ if: matrix.python-version == '3.11'
73
+ with:
74
+ files: coverage.xml
75
+ fail_ci_if_error: false
76
+
77
+ frontend:
78
+ name: Frontend build & type check
79
+ runs-on: ubuntu-latest
80
+ steps:
81
+ - uses: actions/checkout@v4
82
+
83
+ - uses: actions/setup-node@v4
84
+ with:
85
+ node-version: "20"
86
+ cache: "npm"
87
+ cache-dependency-path: frontend/package-lock.json
88
+
89
+ - name: Install dependencies
90
+ run: npm ci
91
+ working-directory: frontend
92
+
93
+ - name: Type check + build
94
+ run: npm run build
95
+ working-directory: frontend
96
+
97
+ - name: Upload dist artifact
98
+ uses: actions/upload-artifact@v4
99
+ with:
100
+ name: frontend-dist
101
+ path: frontend/dist/
102
+ retention-days: 7
103
+
104
+ docker-build:
105
+ name: Docker build (smoke test)
106
+ runs-on: ubuntu-latest
107
+ needs: [lint, typecheck, test, frontend]
108
+ steps:
109
+ - uses: actions/checkout@v4
110
+
111
+ - name: Download frontend dist
112
+ uses: actions/download-artifact@v4
113
+ with:
114
+ name: frontend-dist
115
+ path: frontend/dist/
116
+
117
+ - name: Build Docker image
118
+ run: docker build -t frostwatch:ci .
119
+
120
+ - name: Smoke test — frostwatch version
121
+ run: docker run --rm frostwatch:ci frostwatch version
@@ -0,0 +1,160 @@
1
+ name: PR Checks
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, edited, synchronize, reopened, labeled, unlabeled]
6
+ pull_request_target:
7
+ types: [opened]
8
+
9
+ permissions:
10
+ contents: read
11
+ pull-requests: write
12
+ issues: write
13
+
14
+ jobs:
15
+ pr-title:
16
+ name: Conventional commit title
17
+ runs-on: ubuntu-latest
18
+ if: github.event_name == 'pull_request'
19
+ steps:
20
+ - name: Check PR title
21
+ uses: amannn/action-semantic-pull-request@v5
22
+ env:
23
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24
+ with:
25
+ types: |
26
+ feat
27
+ fix
28
+ docs
29
+ style
30
+ refactor
31
+ test
32
+ chore
33
+ ci
34
+ perf
35
+ revert
36
+ build
37
+ requireScope: false
38
+
39
+ pr-description:
40
+ name: PR description not empty
41
+ runs-on: ubuntu-latest
42
+ if: github.event_name == 'pull_request'
43
+ steps:
44
+ - name: Validate description
45
+ uses: actions/github-script@v7
46
+ with:
47
+ script: |
48
+ const body = context.payload.pull_request.body || '';
49
+ const stripped = body
50
+ .replace(/<!--.*?-->/gs, '') // strip HTML comments (template hints)
51
+ .replace(/\[[ x]\]/g, '') // strip checkboxes
52
+ .trim();
53
+ if (stripped.length < 30) {
54
+ core.setFailed(
55
+ 'PR description is too short. Please fill in the Summary and Changes sections of the PR template.'
56
+ );
57
+ } else {
58
+ core.info('PR description looks good ✓');
59
+ }
60
+
61
+ size-label:
62
+ name: PR size label
63
+ runs-on: ubuntu-latest
64
+ if: github.event_name == 'pull_request'
65
+ steps:
66
+ - uses: codelytv/pr-size-labeler@v1
67
+ with:
68
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
69
+ xs_label: "size/XS"
70
+ xs_max_size: 9
71
+ s_label: "size/S"
72
+ s_max_size: 99
73
+ m_label: "size/M"
74
+ m_max_size: 499
75
+ l_label: "size/L"
76
+ l_max_size: 999
77
+ xl_label: "size/XL"
78
+ fail_if_xl: false
79
+ message_if_xl: |
80
+ This PR is very large. Consider splitting it into smaller PRs for easier review.
81
+
82
+ labeler:
83
+ name: Auto-label by changed paths
84
+ runs-on: ubuntu-latest
85
+ if: github.event_name == 'pull_request'
86
+ steps:
87
+ - uses: actions/labeler@v5
88
+ with:
89
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
90
+ configuration-path: .github/labeler.yml
91
+ sync-labels: true
92
+
93
+ check-changelog:
94
+ name: Remind about CHANGELOG
95
+ runs-on: ubuntu-latest
96
+ if: "github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'skip-changelog')"
97
+ steps:
98
+ - uses: actions/checkout@v4
99
+ with:
100
+ fetch-depth: 0
101
+
102
+ - name: Check if CHANGELOG.md was updated
103
+ run: |
104
+ if git diff --name-only origin/main...HEAD | grep -q "CHANGELOG.md"; then
105
+ echo "✅ CHANGELOG.md updated"
106
+ else
107
+ echo "::warning::CHANGELOG.md was not updated. Add a 'skip-changelog' label if this PR doesn't need a changelog entry."
108
+ fi
109
+
110
+ welcome-contributor:
111
+ name: Welcome first-time contributor
112
+ runs-on: ubuntu-latest
113
+ if: github.event_name == 'pull_request_target' && github.event.action == 'opened'
114
+ steps:
115
+ - name: Check contribution history
116
+ id: check
117
+ uses: actions/github-script@v7
118
+ with:
119
+ script: |
120
+ const { data: prs } = await github.rest.pulls.list({
121
+ owner: context.repo.owner,
122
+ repo: context.repo.repo,
123
+ state: 'all',
124
+ per_page: 10,
125
+ });
126
+ const author = context.payload.pull_request.user.login;
127
+ const previous = prs.filter(
128
+ pr => pr.user.login === author && pr.number !== context.payload.pull_request.number
129
+ );
130
+ return previous.length === 0;
131
+ result-encoding: string
132
+
133
+ - name: Post welcome comment
134
+ if: steps.check.outputs.result == 'true'
135
+ uses: actions/github-script@v7
136
+ with:
137
+ script: |
138
+ const author = context.payload.pull_request.user.login;
139
+ await github.rest.issues.createComment({
140
+ owner: context.repo.owner,
141
+ repo: context.repo.repo,
142
+ issue_number: context.payload.pull_request.number,
143
+ body: `👋 Welcome to FrostWatch, @${author}! Thanks for opening your first PR.
144
+
145
+ Here's what happens next:
146
+ 1. **CI runs automatically** — lint, type check, unit tests (Python 3.11 + 3.13), frontend build, and Docker smoke test must all pass.
147
+ 2. **A maintainer will review** your changes. Please make sure the PR template is filled in.
148
+ 3. **Need help?** Check [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md) or open a [Discussion](../../discussions).
149
+
150
+ Quick local checks before pushing:
151
+ \`\`\`bash
152
+ # Backend
153
+ ruff check frostwatch/ && mypy frostwatch/
154
+ pytest tests/unit/
155
+
156
+ # Frontend
157
+ cd frontend && npm run build
158
+ \`\`\`
159
+ `,
160
+ });