@plainconceptsplatform/workflows 0.28.12 → 0.28.14
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.
- package/loops/actions/housekeeping/action.yml +52 -0
- package/loops/docs/diagrams.md +49 -47
- package/loops/templates/ci/app-ci-dotnet-next.yml +330 -330
- package/loops/templates/ci/app-ci-node-monorepo.yml +260 -260
- package/loops/templates/opencode/opencode.ci.json +24 -2
- package/loops/workflows/agent-apply-review.md +3 -0
- package/loops/workflows/agent-implement.md +3 -0
- package/loops/workflows/agent-merge-gate.md +4 -0
- package/loops/workflows/agent-refine.md +7 -0
- package/loops/workflows/agent-triage.md +2 -0
- package/loops/workflows/agent-visual-verify.md +1 -0
- package/loops/workflows/work-router.yml +2 -0
- package/package.json +4 -4
|
@@ -1,260 +1,260 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/templates/ci/app-ci-node-monorepo.yml. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
2
|
-
name: "App: CI (Node monorepo)"
|
|
3
|
-
|
|
4
|
-
on:
|
|
5
|
-
pull_request:
|
|
6
|
-
schedule:
|
|
7
|
-
- cron: "0 6 * * 1"
|
|
8
|
-
workflow_dispatch:
|
|
9
|
-
|
|
10
|
-
env:
|
|
11
|
-
NODE_VERSION: "22"
|
|
12
|
-
PNPM_VERSION: "10"
|
|
13
|
-
WEB_PACKAGE: "@app/web"
|
|
14
|
-
DESKTOP_PACKAGE: "@app/desktop"
|
|
15
|
-
MOBILE_PACKAGE: "@app/mobile"
|
|
16
|
-
WEB_EXPORT_PATH: apps/web/out
|
|
17
|
-
PLAYWRIGHT_REPORT_PATH: apps/web/playwright-report
|
|
18
|
-
DESKTOP_MAIN_PATH: apps/desktop/out/main/index.js
|
|
19
|
-
DESKTOP_PRELOAD_PATH: apps/desktop/out/preload/index.cjs
|
|
20
|
-
DESKTOP_PRELOAD_BRIDGE_PATTERN: exposeInMainWorld
|
|
21
|
-
MOBILE_IOS_PATH: apps/mobile/ios
|
|
22
|
-
MOBILE_ANDROID_PATH: apps/mobile/android
|
|
23
|
-
WEB_ARTIFACT_NAME: web-export
|
|
24
|
-
PLAYWRIGHT_ARTIFACT_NAME: playwright-report
|
|
25
|
-
TRUFFLEHOG_IMAGE: "trufflesecurity/trufflehog:latest"
|
|
26
|
-
SEMGREP_IMAGE: "semgrep/semgrep:1.168.0"
|
|
27
|
-
SBOM_ARTIFACT_NAME: app-sbom.cyclonedx.json
|
|
28
|
-
MUTATION_THRESHOLD: "60"
|
|
29
|
-
|
|
30
|
-
permissions:
|
|
31
|
-
contents: read
|
|
32
|
-
|
|
33
|
-
concurrency:
|
|
34
|
-
group: ci-${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
|
35
|
-
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
36
|
-
|
|
37
|
-
jobs:
|
|
38
|
-
lint:
|
|
39
|
-
name: Lint (Biome)
|
|
40
|
-
if: github.event_name != 'schedule'
|
|
41
|
-
runs-on: RunnerLandingZone
|
|
42
|
-
steps:
|
|
43
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
44
|
-
- uses: pnpm/action-setup@
|
|
45
|
-
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
46
|
-
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
47
|
-
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
48
|
-
- run: pnpm install --frozen-lockfile
|
|
49
|
-
- run: pnpm exec biome check .
|
|
50
|
-
|
|
51
|
-
web:
|
|
52
|
-
name: Web (Next.js static export)
|
|
53
|
-
if: github.event_name != 'schedule'
|
|
54
|
-
runs-on: RunnerLandingZone
|
|
55
|
-
steps:
|
|
56
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
57
|
-
- uses: pnpm/action-setup@
|
|
58
|
-
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
59
|
-
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
60
|
-
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
61
|
-
- run: pnpm install --frozen-lockfile
|
|
62
|
-
- run: pnpm --filter "$WEB_PACKAGE" run typecheck
|
|
63
|
-
- run: pnpm --filter "$WEB_PACKAGE" run test
|
|
64
|
-
- run: pnpm --filter "$WEB_PACKAGE" run lint:fsd
|
|
65
|
-
- run: pnpm --filter "$WEB_PACKAGE" run build
|
|
66
|
-
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
67
|
-
with:
|
|
68
|
-
name: ${{ env.WEB_ARTIFACT_NAME }}
|
|
69
|
-
path: ${{ env.WEB_EXPORT_PATH }}
|
|
70
|
-
retention-days: 3
|
|
71
|
-
if-no-files-found: error
|
|
72
|
-
|
|
73
|
-
mutation-web:
|
|
74
|
-
name: Mutation (web, diff)
|
|
75
|
-
if: github.event_name == 'pull_request'
|
|
76
|
-
runs-on: RunnerLandingZone
|
|
77
|
-
timeout-minutes: 25
|
|
78
|
-
steps:
|
|
79
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
80
|
-
with:
|
|
81
|
-
fetch-depth: 0
|
|
82
|
-
- uses: pnpm/action-setup@
|
|
83
|
-
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
84
|
-
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
85
|
-
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
86
|
-
- name: Detect web changes
|
|
87
|
-
id: scope
|
|
88
|
-
run: |
|
|
89
|
-
git fetch origin "$GITHUB_BASE_REF" --depth=1
|
|
90
|
-
if git diff --name-only "origin/$GITHUB_BASE_REF" HEAD | grep -qE "^apps/web/"; then
|
|
91
|
-
echo "run=true" >> "$GITHUB_OUTPUT"
|
|
92
|
-
else
|
|
93
|
-
echo "run=false" >> "$GITHUB_OUTPUT"
|
|
94
|
-
echo "No apps/web changes in this PR; skipping web mutation."
|
|
95
|
-
fi
|
|
96
|
-
- run: pnpm install --frozen-lockfile
|
|
97
|
-
if: steps.scope.outputs.run == 'true'
|
|
98
|
-
- name: StrykerJS (changed files only)
|
|
99
|
-
if: steps.scope.outputs.run == 'true'
|
|
100
|
-
run: pnpm --filter "$WEB_PACKAGE" exec stryker run --since="origin/$GITHUB_BASE_REF"
|
|
101
|
-
|
|
102
|
-
e2e:
|
|
103
|
-
name: E2E (Playwright)
|
|
104
|
-
if: github.event_name != 'schedule'
|
|
105
|
-
needs: web
|
|
106
|
-
runs-on: RunnerLandingZone
|
|
107
|
-
steps:
|
|
108
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
109
|
-
- uses: pnpm/action-setup@
|
|
110
|
-
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
111
|
-
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
112
|
-
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
113
|
-
- run: pnpm install --frozen-lockfile
|
|
114
|
-
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
|
115
|
-
with:
|
|
116
|
-
name: ${{ env.WEB_ARTIFACT_NAME }}
|
|
117
|
-
path: ${{ env.WEB_EXPORT_PATH }}
|
|
118
|
-
- run: pnpm --filter "$WEB_PACKAGE" exec playwright install --with-deps chromium webkit
|
|
119
|
-
- run: pnpm --filter "$WEB_PACKAGE" run e2e
|
|
120
|
-
- if: failure()
|
|
121
|
-
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
122
|
-
with:
|
|
123
|
-
name: ${{ env.PLAYWRIGHT_ARTIFACT_NAME }}
|
|
124
|
-
path: ${{ env.PLAYWRIGHT_REPORT_PATH }}
|
|
125
|
-
retention-days: 7
|
|
126
|
-
|
|
127
|
-
desktop:
|
|
128
|
-
name: Desktop (Electron)
|
|
129
|
-
if: github.event_name != 'schedule'
|
|
130
|
-
needs: web
|
|
131
|
-
runs-on: RunnerLandingZone
|
|
132
|
-
steps:
|
|
133
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
134
|
-
- uses: pnpm/action-setup@
|
|
135
|
-
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
136
|
-
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
137
|
-
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
138
|
-
- run: pnpm install --frozen-lockfile
|
|
139
|
-
- run: pnpm --filter "$DESKTOP_PACKAGE" run typecheck
|
|
140
|
-
- run: pnpm --filter "$DESKTOP_PACKAGE" run build
|
|
141
|
-
- run: |
|
|
142
|
-
test -f "$DESKTOP_MAIN_PATH"
|
|
143
|
-
test -f "$DESKTOP_PRELOAD_PATH"
|
|
144
|
-
grep -q "$DESKTOP_PRELOAD_BRIDGE_PATTERN" "$DESKTOP_PRELOAD_PATH"
|
|
145
|
-
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
|
146
|
-
with:
|
|
147
|
-
name: ${{ env.WEB_ARTIFACT_NAME }}
|
|
148
|
-
path: ${{ env.WEB_EXPORT_PATH }}
|
|
149
|
-
- run: pnpm --filter "$DESKTOP_PACKAGE" exec playwright install-deps chromium
|
|
150
|
-
- run: xvfb-run --auto-servernum pnpm --filter "$DESKTOP_PACKAGE" run e2e
|
|
151
|
-
|
|
152
|
-
mobile:
|
|
153
|
-
name: Mobile (Capacitor)
|
|
154
|
-
if: github.event_name != 'schedule'
|
|
155
|
-
runs-on: RunnerLandingZone
|
|
156
|
-
steps:
|
|
157
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
158
|
-
- uses: pnpm/action-setup@
|
|
159
|
-
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
160
|
-
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
161
|
-
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
162
|
-
- run: pnpm install --frozen-lockfile
|
|
163
|
-
- run: pnpm --filter "$MOBILE_PACKAGE" run typecheck
|
|
164
|
-
- run: |
|
|
165
|
-
if [ -d "$MOBILE_IOS_PATH" ] || [ -d "$MOBILE_ANDROID_PATH" ]; then
|
|
166
|
-
pnpm --filter "$WEB_PACKAGE" run build
|
|
167
|
-
pnpm --filter "$MOBILE_PACKAGE" run sync
|
|
168
|
-
else
|
|
169
|
-
echo "::notice::Native projects absent; skipping Capacitor sync."
|
|
170
|
-
fi
|
|
171
|
-
|
|
172
|
-
secret-scan:
|
|
173
|
-
name: Secret scan (TruffleHog)
|
|
174
|
-
runs-on: RunnerLandingZone
|
|
175
|
-
steps:
|
|
176
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
177
|
-
- run: docker run --rm -v "${{ github.workspace }}:/repo" "$TRUFFLEHOG_IMAGE" filesystem /repo --only-verified --fail --no-update
|
|
178
|
-
|
|
179
|
-
deps-and-iac-scan:
|
|
180
|
-
name: Dependencies and IaC (Trivy)
|
|
181
|
-
runs-on: RunnerLandingZone
|
|
182
|
-
steps:
|
|
183
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
184
|
-
- uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
|
185
|
-
with:
|
|
186
|
-
scan-type: fs
|
|
187
|
-
scanners: vuln,misconfig,secret
|
|
188
|
-
severity: CRITICAL,HIGH
|
|
189
|
-
ignore-unfixed: true
|
|
190
|
-
exit-code: "1"
|
|
191
|
-
format: table
|
|
192
|
-
|
|
193
|
-
sast-scan:
|
|
194
|
-
name: SAST (Semgrep)
|
|
195
|
-
runs-on: RunnerLandingZone
|
|
196
|
-
steps:
|
|
197
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
198
|
-
- run: docker run --rm -v "${{ github.workspace }}:/src" -w /src "$SEMGREP_IMAGE" semgrep scan --error --metrics=off --oss-only --disable-version-check --config p/default --config p/security-audit --config p/secrets --config p/typescript --config p/react --exclude-rule yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
|
|
199
|
-
|
|
200
|
-
sbom:
|
|
201
|
-
name: SBOM (CycloneDX)
|
|
202
|
-
runs-on: RunnerLandingZone
|
|
203
|
-
steps:
|
|
204
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
205
|
-
- uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
|
|
206
|
-
with:
|
|
207
|
-
format: cyclonedx-json
|
|
208
|
-
artifact-name: ${{ env.SBOM_ARTIFACT_NAME }}
|
|
209
|
-
|
|
210
|
-
# A bot pull request is opened with GITHUB_TOKEN, and GitHub delivers no workflow_run event
|
|
211
|
-
# for the CI runs that follow (the run's actor is the bot). The router's CI-completion route
|
|
212
|
-
# never sees them, so this job hands the verdict over from inside CI. workflow_dispatch is
|
|
213
|
-
# one of the two events GITHUB_TOKEN may raise, so no App token is needed here, and the
|
|
214
|
-
# router treats a dispatch as trusted. Human pull requests are left to the workflow_run
|
|
215
|
-
# route, which does fire for them. The reconcile belt remains the hourly fallback.
|
|
216
|
-
#
|
|
217
|
-
# If a deploy pipeline calls this workflow with `uses:`, that caller must also grant
|
|
218
|
-
# `actions: write`. A called workflow may not request a scope its caller withheld, and the
|
|
219
|
-
# whole run fails to start with zero jobs and no annotation when it does.
|
|
220
|
-
dispatch-merge-gate:
|
|
221
|
-
name: Dispatch merge gate
|
|
222
|
-
needs: [lint, web, mutation-web, e2e, desktop, mobile]
|
|
223
|
-
if: >
|
|
224
|
-
always() &&
|
|
225
|
-
github.event_name == 'pull_request' &&
|
|
226
|
-
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
227
|
-
github.event.pull_request.draft == false &&
|
|
228
|
-
github.event.pull_request.user.type == 'Bot'
|
|
229
|
-
runs-on: RunnerLandingZone
|
|
230
|
-
timeout-minutes: 5
|
|
231
|
-
permissions:
|
|
232
|
-
contents: read
|
|
233
|
-
actions: write
|
|
234
|
-
steps:
|
|
235
|
-
- name: Dispatch the merge gate with this run's verdict
|
|
236
|
-
env:
|
|
237
|
-
GH_TOKEN: ${{ github.token }}
|
|
238
|
-
REPO: ${{ github.repository }}
|
|
239
|
-
REF: ${{ github.event.repository.default_branch }}
|
|
240
|
-
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
241
|
-
RUN_ID: ${{ github.run_id }}
|
|
242
|
-
RESULTS: ${{ join(needs.*.result, ' ') }}
|
|
243
|
-
run: |
|
|
244
|
-
set -euo pipefail
|
|
245
|
-
conclusion=success
|
|
246
|
-
for result in $RESULTS; do
|
|
247
|
-
case "$result" in
|
|
248
|
-
failure) conclusion=failure; break ;;
|
|
249
|
-
cancelled | timed_out) conclusion=cancelled ;;
|
|
250
|
-
esac
|
|
251
|
-
done
|
|
252
|
-
# A cancelled run was superseded by a newer push, whose own run dispatches.
|
|
253
|
-
if [ "$conclusion" = cancelled ]; then
|
|
254
|
-
echo "CI was cancelled; not dispatching the merge gate."
|
|
255
|
-
exit 0
|
|
256
|
-
fi
|
|
257
|
-
echo "Dispatching the merge gate for PR #$PR_NUMBER with CI $conclusion (run $RUN_ID)."
|
|
258
|
-
gh workflow run work-router.yml --repo "$REPO" --ref "$REF" \
|
|
259
|
-
-f operation=merge-gate -f pr-number="$PR_NUMBER" \
|
|
260
|
-
-f ci-conclusion="$conclusion" -f ci-run-id="$RUN_ID"
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/templates/ci/app-ci-node-monorepo.yml. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
2
|
+
name: "App: CI (Node monorepo)"
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
pull_request:
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: "0 6 * * 1"
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
env:
|
|
11
|
+
NODE_VERSION: "22"
|
|
12
|
+
PNPM_VERSION: "10"
|
|
13
|
+
WEB_PACKAGE: "@app/web"
|
|
14
|
+
DESKTOP_PACKAGE: "@app/desktop"
|
|
15
|
+
MOBILE_PACKAGE: "@app/mobile"
|
|
16
|
+
WEB_EXPORT_PATH: apps/web/out
|
|
17
|
+
PLAYWRIGHT_REPORT_PATH: apps/web/playwright-report
|
|
18
|
+
DESKTOP_MAIN_PATH: apps/desktop/out/main/index.js
|
|
19
|
+
DESKTOP_PRELOAD_PATH: apps/desktop/out/preload/index.cjs
|
|
20
|
+
DESKTOP_PRELOAD_BRIDGE_PATTERN: exposeInMainWorld
|
|
21
|
+
MOBILE_IOS_PATH: apps/mobile/ios
|
|
22
|
+
MOBILE_ANDROID_PATH: apps/mobile/android
|
|
23
|
+
WEB_ARTIFACT_NAME: web-export
|
|
24
|
+
PLAYWRIGHT_ARTIFACT_NAME: playwright-report
|
|
25
|
+
TRUFFLEHOG_IMAGE: "trufflesecurity/trufflehog:latest"
|
|
26
|
+
SEMGREP_IMAGE: "semgrep/semgrep:1.168.0"
|
|
27
|
+
SBOM_ARTIFACT_NAME: app-sbom.cyclonedx.json
|
|
28
|
+
MUTATION_THRESHOLD: "60"
|
|
29
|
+
|
|
30
|
+
permissions:
|
|
31
|
+
contents: read
|
|
32
|
+
|
|
33
|
+
concurrency:
|
|
34
|
+
group: ci-${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
|
35
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
36
|
+
|
|
37
|
+
jobs:
|
|
38
|
+
lint:
|
|
39
|
+
name: Lint (Biome)
|
|
40
|
+
if: github.event_name != 'schedule'
|
|
41
|
+
runs-on: RunnerLandingZone
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
44
|
+
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
45
|
+
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
46
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
47
|
+
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
48
|
+
- run: pnpm install --frozen-lockfile
|
|
49
|
+
- run: pnpm exec biome check .
|
|
50
|
+
|
|
51
|
+
web:
|
|
52
|
+
name: Web (Next.js static export)
|
|
53
|
+
if: github.event_name != 'schedule'
|
|
54
|
+
runs-on: RunnerLandingZone
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
57
|
+
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
58
|
+
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
59
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
60
|
+
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
61
|
+
- run: pnpm install --frozen-lockfile
|
|
62
|
+
- run: pnpm --filter "$WEB_PACKAGE" run typecheck
|
|
63
|
+
- run: pnpm --filter "$WEB_PACKAGE" run test
|
|
64
|
+
- run: pnpm --filter "$WEB_PACKAGE" run lint:fsd
|
|
65
|
+
- run: pnpm --filter "$WEB_PACKAGE" run build
|
|
66
|
+
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
67
|
+
with:
|
|
68
|
+
name: ${{ env.WEB_ARTIFACT_NAME }}
|
|
69
|
+
path: ${{ env.WEB_EXPORT_PATH }}
|
|
70
|
+
retention-days: 3
|
|
71
|
+
if-no-files-found: error
|
|
72
|
+
|
|
73
|
+
mutation-web:
|
|
74
|
+
name: Mutation (web, diff)
|
|
75
|
+
if: github.event_name == 'pull_request'
|
|
76
|
+
runs-on: RunnerLandingZone
|
|
77
|
+
timeout-minutes: 25
|
|
78
|
+
steps:
|
|
79
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
80
|
+
with:
|
|
81
|
+
fetch-depth: 0
|
|
82
|
+
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
83
|
+
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
84
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
85
|
+
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
86
|
+
- name: Detect web changes
|
|
87
|
+
id: scope
|
|
88
|
+
run: |
|
|
89
|
+
git fetch origin "$GITHUB_BASE_REF" --depth=1
|
|
90
|
+
if git diff --name-only "origin/$GITHUB_BASE_REF" HEAD | grep -qE "^apps/web/"; then
|
|
91
|
+
echo "run=true" >> "$GITHUB_OUTPUT"
|
|
92
|
+
else
|
|
93
|
+
echo "run=false" >> "$GITHUB_OUTPUT"
|
|
94
|
+
echo "No apps/web changes in this PR; skipping web mutation."
|
|
95
|
+
fi
|
|
96
|
+
- run: pnpm install --frozen-lockfile
|
|
97
|
+
if: steps.scope.outputs.run == 'true'
|
|
98
|
+
- name: StrykerJS (changed files only)
|
|
99
|
+
if: steps.scope.outputs.run == 'true'
|
|
100
|
+
run: pnpm --filter "$WEB_PACKAGE" exec stryker run --since="origin/$GITHUB_BASE_REF"
|
|
101
|
+
|
|
102
|
+
e2e:
|
|
103
|
+
name: E2E (Playwright)
|
|
104
|
+
if: github.event_name != 'schedule'
|
|
105
|
+
needs: web
|
|
106
|
+
runs-on: RunnerLandingZone
|
|
107
|
+
steps:
|
|
108
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
109
|
+
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
110
|
+
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
111
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
112
|
+
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
113
|
+
- run: pnpm install --frozen-lockfile
|
|
114
|
+
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
|
115
|
+
with:
|
|
116
|
+
name: ${{ env.WEB_ARTIFACT_NAME }}
|
|
117
|
+
path: ${{ env.WEB_EXPORT_PATH }}
|
|
118
|
+
- run: pnpm --filter "$WEB_PACKAGE" exec playwright install --with-deps chromium webkit
|
|
119
|
+
- run: pnpm --filter "$WEB_PACKAGE" run e2e
|
|
120
|
+
- if: failure()
|
|
121
|
+
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
122
|
+
with:
|
|
123
|
+
name: ${{ env.PLAYWRIGHT_ARTIFACT_NAME }}
|
|
124
|
+
path: ${{ env.PLAYWRIGHT_REPORT_PATH }}
|
|
125
|
+
retention-days: 7
|
|
126
|
+
|
|
127
|
+
desktop:
|
|
128
|
+
name: Desktop (Electron)
|
|
129
|
+
if: github.event_name != 'schedule'
|
|
130
|
+
needs: web
|
|
131
|
+
runs-on: RunnerLandingZone
|
|
132
|
+
steps:
|
|
133
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
134
|
+
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
135
|
+
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
136
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
137
|
+
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
138
|
+
- run: pnpm install --frozen-lockfile
|
|
139
|
+
- run: pnpm --filter "$DESKTOP_PACKAGE" run typecheck
|
|
140
|
+
- run: pnpm --filter "$DESKTOP_PACKAGE" run build
|
|
141
|
+
- run: |
|
|
142
|
+
test -f "$DESKTOP_MAIN_PATH"
|
|
143
|
+
test -f "$DESKTOP_PRELOAD_PATH"
|
|
144
|
+
grep -q "$DESKTOP_PRELOAD_BRIDGE_PATTERN" "$DESKTOP_PRELOAD_PATH"
|
|
145
|
+
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
|
146
|
+
with:
|
|
147
|
+
name: ${{ env.WEB_ARTIFACT_NAME }}
|
|
148
|
+
path: ${{ env.WEB_EXPORT_PATH }}
|
|
149
|
+
- run: pnpm --filter "$DESKTOP_PACKAGE" exec playwright install-deps chromium
|
|
150
|
+
- run: xvfb-run --auto-servernum pnpm --filter "$DESKTOP_PACKAGE" run e2e
|
|
151
|
+
|
|
152
|
+
mobile:
|
|
153
|
+
name: Mobile (Capacitor)
|
|
154
|
+
if: github.event_name != 'schedule'
|
|
155
|
+
runs-on: RunnerLandingZone
|
|
156
|
+
steps:
|
|
157
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
158
|
+
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
159
|
+
with: { version: "${{ env.PNPM_VERSION }}", run_install: false }
|
|
160
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
161
|
+
with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm }
|
|
162
|
+
- run: pnpm install --frozen-lockfile
|
|
163
|
+
- run: pnpm --filter "$MOBILE_PACKAGE" run typecheck
|
|
164
|
+
- run: |
|
|
165
|
+
if [ -d "$MOBILE_IOS_PATH" ] || [ -d "$MOBILE_ANDROID_PATH" ]; then
|
|
166
|
+
pnpm --filter "$WEB_PACKAGE" run build
|
|
167
|
+
pnpm --filter "$MOBILE_PACKAGE" run sync
|
|
168
|
+
else
|
|
169
|
+
echo "::notice::Native projects absent; skipping Capacitor sync."
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
secret-scan:
|
|
173
|
+
name: Secret scan (TruffleHog)
|
|
174
|
+
runs-on: RunnerLandingZone
|
|
175
|
+
steps:
|
|
176
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
177
|
+
- run: docker run --rm -v "${{ github.workspace }}:/repo" "$TRUFFLEHOG_IMAGE" filesystem /repo --only-verified --fail --no-update
|
|
178
|
+
|
|
179
|
+
deps-and-iac-scan:
|
|
180
|
+
name: Dependencies and IaC (Trivy)
|
|
181
|
+
runs-on: RunnerLandingZone
|
|
182
|
+
steps:
|
|
183
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
184
|
+
- uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
|
185
|
+
with:
|
|
186
|
+
scan-type: fs
|
|
187
|
+
scanners: vuln,misconfig,secret
|
|
188
|
+
severity: CRITICAL,HIGH
|
|
189
|
+
ignore-unfixed: true
|
|
190
|
+
exit-code: "1"
|
|
191
|
+
format: table
|
|
192
|
+
|
|
193
|
+
sast-scan:
|
|
194
|
+
name: SAST (Semgrep)
|
|
195
|
+
runs-on: RunnerLandingZone
|
|
196
|
+
steps:
|
|
197
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
198
|
+
- run: docker run --rm -v "${{ github.workspace }}:/src" -w /src "$SEMGREP_IMAGE" semgrep scan --error --metrics=off --oss-only --disable-version-check --config p/default --config p/security-audit --config p/secrets --config p/typescript --config p/react --exclude-rule yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
|
|
199
|
+
|
|
200
|
+
sbom:
|
|
201
|
+
name: SBOM (CycloneDX)
|
|
202
|
+
runs-on: RunnerLandingZone
|
|
203
|
+
steps:
|
|
204
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
205
|
+
- uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
|
|
206
|
+
with:
|
|
207
|
+
format: cyclonedx-json
|
|
208
|
+
artifact-name: ${{ env.SBOM_ARTIFACT_NAME }}
|
|
209
|
+
|
|
210
|
+
# A bot pull request is opened with GITHUB_TOKEN, and GitHub delivers no workflow_run event
|
|
211
|
+
# for the CI runs that follow (the run's actor is the bot). The router's CI-completion route
|
|
212
|
+
# never sees them, so this job hands the verdict over from inside CI. workflow_dispatch is
|
|
213
|
+
# one of the two events GITHUB_TOKEN may raise, so no App token is needed here, and the
|
|
214
|
+
# router treats a dispatch as trusted. Human pull requests are left to the workflow_run
|
|
215
|
+
# route, which does fire for them. The reconcile belt remains the hourly fallback.
|
|
216
|
+
#
|
|
217
|
+
# If a deploy pipeline calls this workflow with `uses:`, that caller must also grant
|
|
218
|
+
# `actions: write`. A called workflow may not request a scope its caller withheld, and the
|
|
219
|
+
# whole run fails to start with zero jobs and no annotation when it does.
|
|
220
|
+
dispatch-merge-gate:
|
|
221
|
+
name: Dispatch merge gate
|
|
222
|
+
needs: [lint, web, mutation-web, e2e, desktop, mobile]
|
|
223
|
+
if: >
|
|
224
|
+
always() &&
|
|
225
|
+
github.event_name == 'pull_request' &&
|
|
226
|
+
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
227
|
+
github.event.pull_request.draft == false &&
|
|
228
|
+
github.event.pull_request.user.type == 'Bot'
|
|
229
|
+
runs-on: RunnerLandingZone
|
|
230
|
+
timeout-minutes: 5
|
|
231
|
+
permissions:
|
|
232
|
+
contents: read
|
|
233
|
+
actions: write
|
|
234
|
+
steps:
|
|
235
|
+
- name: Dispatch the merge gate with this run's verdict
|
|
236
|
+
env:
|
|
237
|
+
GH_TOKEN: ${{ github.token }}
|
|
238
|
+
REPO: ${{ github.repository }}
|
|
239
|
+
REF: ${{ github.event.repository.default_branch }}
|
|
240
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
241
|
+
RUN_ID: ${{ github.run_id }}
|
|
242
|
+
RESULTS: ${{ join(needs.*.result, ' ') }}
|
|
243
|
+
run: |
|
|
244
|
+
set -euo pipefail
|
|
245
|
+
conclusion=success
|
|
246
|
+
for result in $RESULTS; do
|
|
247
|
+
case "$result" in
|
|
248
|
+
failure) conclusion=failure; break ;;
|
|
249
|
+
cancelled | timed_out) conclusion=cancelled ;;
|
|
250
|
+
esac
|
|
251
|
+
done
|
|
252
|
+
# A cancelled run was superseded by a newer push, whose own run dispatches.
|
|
253
|
+
if [ "$conclusion" = cancelled ]; then
|
|
254
|
+
echo "CI was cancelled; not dispatching the merge gate."
|
|
255
|
+
exit 0
|
|
256
|
+
fi
|
|
257
|
+
echo "Dispatching the merge gate for PR #$PR_NUMBER with CI $conclusion (run $RUN_ID)."
|
|
258
|
+
gh workflow run work-router.yml --repo "$REPO" --ref "$REF" \
|
|
259
|
+
-f operation=merge-gate -f pr-number="$PR_NUMBER" \
|
|
260
|
+
-f ci-conclusion="$conclusion" -f ci-run-id="$RUN_ID"
|
|
@@ -43,11 +43,33 @@
|
|
|
43
43
|
"models": {
|
|
44
44
|
"glm-5-2": {
|
|
45
45
|
"name": "GLM 5.3",
|
|
46
|
-
"
|
|
46
|
+
"tool_call": true,
|
|
47
|
+
"reasoning": true,
|
|
48
|
+
"attachment": true,
|
|
49
|
+
"modalities": {
|
|
50
|
+
"input": ["text", "image"],
|
|
51
|
+
"output": ["text"]
|
|
52
|
+
},
|
|
53
|
+
"limit": {
|
|
54
|
+
"context": 1048576,
|
|
55
|
+
"input": 1048576,
|
|
56
|
+
"output": 131072
|
|
57
|
+
}
|
|
47
58
|
},
|
|
48
59
|
"quasar-438b": {
|
|
49
60
|
"name": "GLM 5.2",
|
|
50
|
-
"
|
|
61
|
+
"tool_call": true,
|
|
62
|
+
"reasoning": true,
|
|
63
|
+
"attachment": true,
|
|
64
|
+
"modalities": {
|
|
65
|
+
"input": ["text", "image"],
|
|
66
|
+
"output": ["text"]
|
|
67
|
+
},
|
|
68
|
+
"limit": {
|
|
69
|
+
"context": 1048576,
|
|
70
|
+
"input": 1048576,
|
|
71
|
+
"output": 131072
|
|
72
|
+
}
|
|
51
73
|
}
|
|
52
74
|
}
|
|
53
75
|
}
|
|
@@ -81,6 +81,7 @@ jobs:
|
|
|
81
81
|
# and gate both the reservation and the agent on it.
|
|
82
82
|
still_open:
|
|
83
83
|
runs-on: agents-arc
|
|
84
|
+
timeout-minutes: 5
|
|
84
85
|
permissions:
|
|
85
86
|
contents: read
|
|
86
87
|
issues: read
|
|
@@ -99,6 +100,7 @@ jobs:
|
|
|
99
100
|
issue-number: ${{ inputs.issue-number }}
|
|
100
101
|
subject:
|
|
101
102
|
runs-on: agents-arc
|
|
103
|
+
timeout-minutes: 5
|
|
102
104
|
permissions:
|
|
103
105
|
contents: read
|
|
104
106
|
issues: read
|
|
@@ -168,6 +170,7 @@ jobs:
|
|
|
168
170
|
needs: [subject, still_open]
|
|
169
171
|
if: needs.subject.outputs.found == 'true' && needs.subject.outputs.issue != '' && needs.still_open.outputs.open == 'true'
|
|
170
172
|
runs-on: agents-arc
|
|
173
|
+
timeout-minutes: 5
|
|
171
174
|
permissions:
|
|
172
175
|
contents: read
|
|
173
176
|
issues: write
|
|
@@ -110,6 +110,7 @@ jobs:
|
|
|
110
110
|
# and gate both the reservation and the agent on it.
|
|
111
111
|
still_open:
|
|
112
112
|
runs-on: agents-arc
|
|
113
|
+
timeout-minutes: 5
|
|
113
114
|
permissions:
|
|
114
115
|
contents: read
|
|
115
116
|
issues: read
|
|
@@ -128,6 +129,7 @@ jobs:
|
|
|
128
129
|
issue-number: ${{ inputs.issue-number }}
|
|
129
130
|
eligibility:
|
|
130
131
|
runs-on: agents-arc
|
|
132
|
+
timeout-minutes: 5
|
|
131
133
|
permissions:
|
|
132
134
|
issues: read
|
|
133
135
|
outputs:
|
|
@@ -175,6 +177,7 @@ jobs:
|
|
|
175
177
|
needs: [eligibility, still_open]
|
|
176
178
|
if: needs.eligibility.outputs.eligible == 'true' && needs.still_open.outputs.open == 'true'
|
|
177
179
|
runs-on: agents-arc
|
|
180
|
+
timeout-minutes: 5
|
|
178
181
|
permissions:
|
|
179
182
|
contents: read
|
|
180
183
|
issues: write
|
|
@@ -141,6 +141,7 @@ on:
|
|
|
141
141
|
jobs:
|
|
142
142
|
subject:
|
|
143
143
|
runs-on: agents-arc
|
|
144
|
+
timeout-minutes: 5
|
|
144
145
|
permissions:
|
|
145
146
|
contents: read
|
|
146
147
|
issues: read
|
|
@@ -196,6 +197,7 @@ jobs:
|
|
|
196
197
|
needs: subject
|
|
197
198
|
if: needs.subject.outputs.found == 'true'
|
|
198
199
|
runs-on: agents-arc
|
|
200
|
+
timeout-minutes: 5
|
|
199
201
|
permissions:
|
|
200
202
|
contents: read
|
|
201
203
|
pull-requests: read
|
|
@@ -242,6 +244,7 @@ jobs:
|
|
|
242
244
|
# there are no protected files instead of skipping it and blocking remediation.
|
|
243
245
|
if: always() && needs.subject.outputs.found == 'true'
|
|
244
246
|
runs-on: agents-arc
|
|
247
|
+
timeout-minutes: 5
|
|
245
248
|
permissions:
|
|
246
249
|
contents: read
|
|
247
250
|
issues: write
|
|
@@ -301,6 +304,7 @@ jobs:
|
|
|
301
304
|
needs: subject
|
|
302
305
|
if: needs.subject.outputs.found == 'true'
|
|
303
306
|
runs-on: agents-arc
|
|
307
|
+
timeout-minutes: 5
|
|
304
308
|
permissions:
|
|
305
309
|
contents: read
|
|
306
310
|
issues: write
|