@mmerterden/multi-agent-pipeline 20.35.0 → 20.37.0
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/CHANGELOG.md +26 -0
- package/README.md +6 -5
- package/README.tr.md +6 -5
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/facts.json +4 -4
- package/install/catalog-history.json +1 -1
- package/manifest.json +210 -136
- package/package.json +5 -1
- package/pipeline/commands/multi-agent/SKILL.md +33 -154
- package/pipeline/commands/multi-agent/analysis/SKILL.md +14 -68
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +5 -8
- package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +38 -109
- package/pipeline/commands/multi-agent/channels/SKILL.md +38 -167
- package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +37 -144
- package/pipeline/commands/multi-agent/design-check/SKILL.md +27 -131
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +22 -48
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +37 -180
- package/pipeline/commands/multi-agent/jira/SKILL.md +16 -46
- package/pipeline/commands/multi-agent/office-pair/SKILL.md +66 -0
- package/pipeline/commands/multi-agent/office-status/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/refactor/SKILL.md +23 -184
- package/pipeline/commands/multi-agent/resume/SKILL.md +30 -136
- package/pipeline/commands/multi-agent/review/SKILL.md +24 -199
- package/pipeline/commands/multi-agent/setup/SKILL.md +25 -181
- package/pipeline/commands/multi-agent/stack/SKILL.md +9 -53
- package/pipeline/commands/multi-agent/status/SKILL.md +13 -2
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +27 -226
- package/pipeline/commands/multi-agent/sync/SKILL.md +38 -167
- package/pipeline/commands/multi-agent/test-gen/SKILL.md +2 -4
- package/pipeline/commands/multi-agent/update/SKILL.md +18 -60
- package/pipeline/contract/CHANGELOG.md +9 -0
- package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
- package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
- package/pipeline/contract/fixtures/error-unsigned.json +1 -1
- package/pipeline/contract/fixtures/runs-awaiting-question.json +1 -1
- package/pipeline/contract/fixtures/runs-empty.json +1 -1
- package/pipeline/contract/fixtures/runs-failed.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
- package/pipeline/contract/fixtures/runs-running.json +1 -1
- package/pipeline/contract/manifest.json +5 -5
- package/pipeline/contract/types/index.d.ts +3 -2
- package/pipeline/lib/qr-code.mjs +26 -0
- package/pipeline/multi-agent-refs/autopilot-on/details.md +148 -0
- package/pipeline/multi-agent-refs/channels/generator.md +14 -0
- package/pipeline/multi-agent-refs/channels/preview.md +13 -0
- package/pipeline/multi-agent-refs/channels/reference.md +46 -0
- package/pipeline/multi-agent-refs/complaint-analysis/phases.md +159 -0
- package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
- package/pipeline/multi-agent-refs/design-check/export.md +1 -0
- package/pipeline/multi-agent-refs/design-check/init-inventory.md +1 -1
- package/pipeline/multi-agent-refs/design-check/scope-and-ledger.md +64 -0
- package/pipeline/multi-agent-refs/features/analysis-command-notes.md +53 -0
- package/pipeline/multi-agent-refs/garbage-collect/phases.md +60 -0
- package/pipeline/multi-agent-refs/help/en.md +1 -0
- package/pipeline/multi-agent-refs/help/tr.md +1 -0
- package/pipeline/multi-agent-refs/ios-coding-standard/plan.md +71 -0
- package/pipeline/multi-agent-refs/ios-coding-standard/resolve-and-scan.md +135 -0
- package/pipeline/multi-agent-refs/jira/picker-detail.md +71 -0
- package/pipeline/multi-agent-refs/orchestrator/copilot-pipeline.md +88 -0
- package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +96 -1
- package/pipeline/multi-agent-refs/refactor/sweep.md +190 -0
- package/pipeline/multi-agent-refs/resume/details.md +115 -2
- package/pipeline/multi-agent-refs/review/inputs.md +33 -1
- package/pipeline/multi-agent-refs/review/module-guides.md +9 -1
- package/pipeline/multi-agent-refs/review/panel.md +75 -0
- package/pipeline/multi-agent-refs/review/valven.md +5 -1
- package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +4 -0
- package/pipeline/multi-agent-refs/setup/token-save-flow.md +13 -0
- package/pipeline/multi-agent-refs/stack/background.md +72 -0
- package/pipeline/multi-agent-refs/store-ready/gates.md +139 -0
- package/pipeline/multi-agent-refs/store-ready/report.md +40 -2
- package/pipeline/multi-agent-refs/sync/office.md +10 -0
- package/pipeline/multi-agent-refs/sync/overview.md +61 -0
- package/pipeline/multi-agent-refs/sync/repo.md +91 -0
- package/pipeline/multi-agent-refs/update/details.md +83 -0
- package/pipeline/schemas/runs-index.schema.json +2 -1
- package/pipeline/scripts/contract-server.mjs +1 -1
- package/pipeline/scripts/office-pair.mjs +40 -9
- package/pipeline/scripts/runs-index.mjs +34 -7
- package/pipeline/scripts/status-overview.mjs +401 -0
- package/pipeline/skills/.skill-manifest.json +69 -65
- package/pipeline/skills/.skills-index.json +15 -4
- package/pipeline/skills/shared/README.md +4 -3
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +8 -71
- package/pipeline/skills/shared/core/apple-archive-compliance/references/examples.md +67 -0
- package/pipeline/skills/shared/core/apple-archive-compliance/references/version-history.md +8 -0
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +11 -73
- package/pipeline/skills/shared/core/google-play-compliance/references/examples.md +69 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/notes.md +7 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/version-history.md +5 -0
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +39 -262
- package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +35 -73
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +37 -337
- package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +27 -122
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +8 -340
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +37 -180
- package/pipeline/skills/shared/core/multi-agent-office-pair/SKILL.md +19 -0
- package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +23 -184
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +14 -82
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +25 -277
- package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +13 -2
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +75 -219
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +11 -162
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-code.md +257 -0
- package/pipeline/skills/shared/external/android-architecture/SKILL.md +7 -58
- package/pipeline/skills/shared/external/android-architecture/references/code-examples.md +68 -0
- package/pipeline/skills/shared/external/app-intents/SKILL.md +3 -45
- package/pipeline/skills/shared/external/app-intents/references/code-examples.md +54 -0
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +5 -81
- package/pipeline/skills/shared/external/app-store-optimization/references/engagement-and-localization.md +96 -0
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +6 -112
- package/pipeline/skills/shared/external/app-store-review/references/monetization-and-submission.md +138 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +10 -117
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models-code.md +186 -0
- package/pipeline/skills/shared/external/authentication/SKILL.md +3 -50
- package/pipeline/skills/shared/external/authentication/references/code-examples.md +59 -0
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +3 -84
- package/pipeline/skills/shared/external/callkit-voip/references/code-examples.md +93 -0
- package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +7 -263
- package/pipeline/skills/shared/external/ci-cd-pipelines/references/pipeline-examples.md +281 -0
- package/pipeline/skills/shared/external/clean-code/SKILL.md +8 -41
- package/pipeline/skills/shared/external/clean-code/references/code-examples.md +56 -0
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +8 -146
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-code.md +214 -0
- package/pipeline/skills/shared/external/compose-components/SKILL.md +6 -80
- package/pipeline/skills/shared/external/compose-components/references/code-examples.md +89 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +5 -64
- package/pipeline/skills/shared/external/compose-navigation/references/code-examples.md +70 -0
- package/pipeline/skills/shared/external/core-data/SKILL.md +9 -157
- package/pipeline/skills/shared/external/core-data/references/core-data-code.md +221 -0
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +3 -89
- package/pipeline/skills/shared/external/core-nfc/references/code-examples.md +98 -0
- package/pipeline/skills/shared/external/coreml/SKILL.md +17 -133
- package/pipeline/skills/shared/external/coreml/references/coreml-code.md +208 -0
- package/pipeline/skills/shared/external/css-modern/SKILL.md +8 -131
- package/pipeline/skills/shared/external/css-modern/references/code-examples.md +142 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +6 -103
- package/pipeline/skills/shared/external/database-patterns/references/orm-examples.md +112 -0
- package/pipeline/skills/shared/external/html-semantic/SKILL.md +7 -48
- package/pipeline/skills/shared/external/html-semantic/references/code-examples.md +54 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +6 -19
- package/pipeline/skills/shared/external/humanizer/references/voice.md +24 -0
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +13 -170
- package/pipeline/skills/shared/external/ios-accessibility/references/assistive-tech-review.md +224 -0
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +2 -24
- package/pipeline/skills/shared/external/ios-localization/references/code-examples.md +30 -0
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +3 -58
- package/pipeline/skills/shared/external/ios-networking/references/code-examples.md +67 -0
- package/pipeline/skills/shared/external/ios-security/SKILL.md +4 -64
- package/pipeline/skills/shared/external/ios-security/references/code-examples.md +74 -0
- package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -90
- package/pipeline/skills/shared/external/live-activities/references/code-examples.md +108 -0
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -23
- package/pipeline/skills/shared/external/localization-reuse-map/references/project-settings.md +22 -0
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +5 -95
- package/pipeline/skills/shared/external/mapkit-location/references/code-examples.md +117 -0
- package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +7 -106
- package/pipeline/skills/shared/external/nextjs-app-router/references/app-router-examples.md +124 -0
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -31
- package/pipeline/skills/shared/external/permissionkit/references/code-examples.md +37 -0
- package/pipeline/skills/shared/external/play-store-review/SKILL.md +6 -74
- package/pipeline/skills/shared/external/play-store-review/references/signing-and-policies.md +83 -0
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -125
- package/pipeline/skills/shared/external/push-notifications/references/notification-code.md +176 -0
- package/pipeline/skills/shared/external/python-patterns/SKILL.md +7 -158
- package/pipeline/skills/shared/external/python-patterns/references/python-examples.md +176 -0
- package/pipeline/skills/shared/external/rest-api-design/SKILL.md +7 -157
- package/pipeline/skills/shared/external/rest-api-design/references/api-examples.md +175 -0
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +11 -230
- package/pipeline/skills/shared/external/swift-architecture/references/pattern-examples.md +295 -0
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +17 -149
- package/pipeline/skills/shared/external/swift-codable/references/codable-recipes.md +207 -0
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +12 -99
- package/pipeline/skills/shared/external/swift-concurrency/references/code-examples.md +100 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/swift-6-2-additions.md +18 -0
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +9 -78
- package/pipeline/skills/shared/external/swift-formatstyle/references/formatstyle-examples.md +113 -0
- package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -56
- package/pipeline/skills/shared/external/swift-language/references/code-examples.md +65 -0
- package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +1 -0
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +6 -122
- package/pipeline/skills/shared/external/swiftdata/references/code-examples.md +140 -0
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +12 -139
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/interop-code.md +198 -0
- package/pipeline/skills/shared/external/tailwind-css/SKILL.md +9 -81
- package/pipeline/skills/shared/external/tailwind-css/references/code-examples.md +91 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +7 -207
- package/pipeline/skills/shared/external/testing-backend/references/testing-examples.md +225 -0
- package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +8 -89
- package/pipeline/skills/shared/external/typescript-patterns/references/code-examples.md +100 -0
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -20
- package/pipeline/skills/shared/external/vision-framework/references/code-examples.md +26 -0
- package/pipeline/skills/shared/external/vue-composition/SKILL.md +7 -169
- package/pipeline/skills/shared/external/vue-composition/references/vue-examples.md +187 -0
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +7 -174
- package/pipeline/skills/shared/external/web-accessibility/references/a11y-examples.md +192 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +8 -66
- package/pipeline/skills/shared/external/web-performance/references/code-examples.md +77 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +11 -186
- package/pipeline/skills/shared/external/web-testing/references/testing-examples.md +213 -0
- package/pipeline/skills/skills-index.md +2 -1
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/format-and-output.md +0 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/publish-and-snapshot.md +0 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/sources-and-recipes.md +0 -0
|
@@ -19,275 +19,15 @@ for all pipeline definitions.
|
|
|
19
19
|
|
|
20
20
|
### Full CI Pipeline
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
# .github/workflows/ci.yml
|
|
24
|
-
name: CI
|
|
25
|
-
|
|
26
|
-
on:
|
|
27
|
-
push:
|
|
28
|
-
branches: [main]
|
|
29
|
-
pull_request:
|
|
30
|
-
branches: [main]
|
|
31
|
-
|
|
32
|
-
concurrency:
|
|
33
|
-
group: ${{ github.workflow }}-${{ github.ref }}
|
|
34
|
-
cancel-in-progress: true
|
|
35
|
-
|
|
36
|
-
env:
|
|
37
|
-
NODE_VERSION: '22'
|
|
38
|
-
REGISTRY: ghcr.io
|
|
39
|
-
IMAGE_NAME: ${{ github.repository }}
|
|
40
|
-
|
|
41
|
-
jobs:
|
|
42
|
-
lint:
|
|
43
|
-
runs-on: ubuntu-latest
|
|
44
|
-
steps:
|
|
45
|
-
- uses: actions/checkout@v4
|
|
46
|
-
|
|
47
|
-
- uses: actions/setup-node@v4
|
|
48
|
-
with:
|
|
49
|
-
node-version: ${{ env.NODE_VERSION }}
|
|
50
|
-
cache: 'npm'
|
|
51
|
-
|
|
52
|
-
- run: npm ci
|
|
53
|
-
|
|
54
|
-
- name: Lint
|
|
55
|
-
run: npm run lint
|
|
56
|
-
|
|
57
|
-
- name: Type check
|
|
58
|
-
run: npm run type-check
|
|
59
|
-
|
|
60
|
-
test:
|
|
61
|
-
runs-on: ubuntu-latest
|
|
62
|
-
needs: lint
|
|
63
|
-
|
|
64
|
-
services:
|
|
65
|
-
postgres:
|
|
66
|
-
image: postgres:16
|
|
67
|
-
env:
|
|
68
|
-
POSTGRES_USER: test
|
|
69
|
-
POSTGRES_PASSWORD: test
|
|
70
|
-
POSTGRES_DB: test_db
|
|
71
|
-
ports:
|
|
72
|
-
- 5432:5432
|
|
73
|
-
options: >-
|
|
74
|
-
--health-cmd pg_isready
|
|
75
|
-
--health-interval 10s
|
|
76
|
-
--health-timeout 5s
|
|
77
|
-
--health-retries 5
|
|
78
|
-
|
|
79
|
-
redis:
|
|
80
|
-
image: redis:7
|
|
81
|
-
ports:
|
|
82
|
-
- 6379:6379
|
|
83
|
-
|
|
84
|
-
steps:
|
|
85
|
-
- uses: actions/checkout@v4
|
|
86
|
-
|
|
87
|
-
- uses: actions/setup-node@v4
|
|
88
|
-
with:
|
|
89
|
-
node-version: ${{ env.NODE_VERSION }}
|
|
90
|
-
cache: 'npm'
|
|
91
|
-
|
|
92
|
-
- run: npm ci
|
|
93
|
-
|
|
94
|
-
- name: Run migrations
|
|
95
|
-
run: npx prisma migrate deploy
|
|
96
|
-
env:
|
|
97
|
-
DATABASE_URL: postgresql://test:test@localhost:5432/test_db
|
|
98
|
-
|
|
99
|
-
- name: Run tests
|
|
100
|
-
run: npm test -- --coverage
|
|
101
|
-
env:
|
|
102
|
-
DATABASE_URL: postgresql://test:test@localhost:5432/test_db
|
|
103
|
-
REDIS_URL: redis://localhost:6379
|
|
104
|
-
|
|
105
|
-
- name: Upload coverage
|
|
106
|
-
if: github.event_name == 'pull_request'
|
|
107
|
-
uses: codecov/codecov-action@v4
|
|
108
|
-
with:
|
|
109
|
-
token: ${{ secrets.CODECOV_TOKEN }}
|
|
110
|
-
|
|
111
|
-
e2e:
|
|
112
|
-
runs-on: ubuntu-latest
|
|
113
|
-
needs: test
|
|
114
|
-
steps:
|
|
115
|
-
- uses: actions/checkout@v4
|
|
116
|
-
|
|
117
|
-
- uses: actions/setup-node@v4
|
|
118
|
-
with:
|
|
119
|
-
node-version: ${{ env.NODE_VERSION }}
|
|
120
|
-
cache: 'npm'
|
|
121
|
-
|
|
122
|
-
- run: npm ci
|
|
123
|
-
|
|
124
|
-
- name: Install Playwright browsers
|
|
125
|
-
run: npx playwright install --with-deps chromium
|
|
126
|
-
|
|
127
|
-
- name: Run E2E tests
|
|
128
|
-
run: npx playwright test
|
|
129
|
-
env:
|
|
130
|
-
BASE_URL: http://localhost:3000
|
|
131
|
-
|
|
132
|
-
- name: Upload test results
|
|
133
|
-
if: failure()
|
|
134
|
-
uses: actions/upload-artifact@v4
|
|
135
|
-
with:
|
|
136
|
-
name: playwright-report
|
|
137
|
-
path: playwright-report/
|
|
138
|
-
|
|
139
|
-
build:
|
|
140
|
-
runs-on: ubuntu-latest
|
|
141
|
-
needs: [test, e2e]
|
|
142
|
-
if: github.ref == 'refs/heads/main'
|
|
143
|
-
permissions:
|
|
144
|
-
contents: read
|
|
145
|
-
packages: write
|
|
146
|
-
|
|
147
|
-
steps:
|
|
148
|
-
- uses: actions/checkout@v4
|
|
149
|
-
|
|
150
|
-
- name: Log in to Container Registry
|
|
151
|
-
uses: docker/login-action@v3
|
|
152
|
-
with:
|
|
153
|
-
registry: ${{ env.REGISTRY }}
|
|
154
|
-
username: ${{ github.actor }}
|
|
155
|
-
password: ${{ secrets.GITHUB_TOKEN }}
|
|
156
|
-
|
|
157
|
-
- name: Extract metadata
|
|
158
|
-
id: meta
|
|
159
|
-
uses: docker/metadata-action@v5
|
|
160
|
-
with:
|
|
161
|
-
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
162
|
-
tags: |
|
|
163
|
-
type=sha
|
|
164
|
-
type=ref,event=branch
|
|
165
|
-
type=semver,pattern={{version}}
|
|
166
|
-
|
|
167
|
-
- name: Build and push
|
|
168
|
-
uses: docker/build-push-action@v5
|
|
169
|
-
with:
|
|
170
|
-
context: .
|
|
171
|
-
push: true
|
|
172
|
-
tags: ${{ steps.meta.outputs.tags }}
|
|
173
|
-
labels: ${{ steps.meta.outputs.labels }}
|
|
174
|
-
cache-from: type=gha
|
|
175
|
-
cache-to: type=gha,mode=max
|
|
176
|
-
```
|
|
22
|
+
Code for this section: [CI/CD pipeline examples](references/pipeline-examples.md).
|
|
177
23
|
|
|
178
24
|
### Deploy Workflow
|
|
179
25
|
|
|
180
|
-
|
|
181
|
-
# .github/workflows/deploy.yml
|
|
182
|
-
name: Deploy
|
|
183
|
-
|
|
184
|
-
on:
|
|
185
|
-
workflow_run:
|
|
186
|
-
workflows: [CI]
|
|
187
|
-
types: [completed]
|
|
188
|
-
branches: [main]
|
|
189
|
-
|
|
190
|
-
jobs:
|
|
191
|
-
deploy-staging:
|
|
192
|
-
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
|
193
|
-
runs-on: ubuntu-latest
|
|
194
|
-
environment: staging
|
|
195
|
-
|
|
196
|
-
steps:
|
|
197
|
-
- uses: actions/checkout@v4
|
|
198
|
-
|
|
199
|
-
- name: Deploy to staging
|
|
200
|
-
run: |
|
|
201
|
-
echo "Deploying to staging..."
|
|
202
|
-
# Use your deployment tool
|
|
203
|
-
env:
|
|
204
|
-
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
|
|
205
|
-
DEPLOY_URL: ${{ vars.STAGING_URL }}
|
|
206
|
-
|
|
207
|
-
- name: Run smoke tests
|
|
208
|
-
run: |
|
|
209
|
-
curl --fail --retry 5 --retry-delay 10 "${{ vars.STAGING_URL }}/health"
|
|
210
|
-
|
|
211
|
-
deploy-production:
|
|
212
|
-
needs: deploy-staging
|
|
213
|
-
runs-on: ubuntu-latest
|
|
214
|
-
environment: production
|
|
215
|
-
|
|
216
|
-
steps:
|
|
217
|
-
- uses: actions/checkout@v4
|
|
218
|
-
|
|
219
|
-
- name: Deploy to production
|
|
220
|
-
run: |
|
|
221
|
-
echo "Deploying to production..."
|
|
222
|
-
env:
|
|
223
|
-
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
|
|
224
|
-
DEPLOY_URL: ${{ vars.PRODUCTION_URL }}
|
|
225
|
-
|
|
226
|
-
- name: Verify deployment
|
|
227
|
-
run: |
|
|
228
|
-
curl --fail --retry 5 --retry-delay 10 "${{ vars.PRODUCTION_URL }}/health"
|
|
229
|
-
```
|
|
26
|
+
Code for this section: [CI/CD pipeline examples](references/pipeline-examples.md).
|
|
230
27
|
|
|
231
28
|
## Docker Multi-Stage Builds
|
|
232
29
|
|
|
233
|
-
|
|
234
|
-
# Dockerfile
|
|
235
|
-
# Stage 1: Dependencies
|
|
236
|
-
FROM node:22-alpine AS deps
|
|
237
|
-
WORKDIR /app
|
|
238
|
-
COPY package.json package-lock.json ./
|
|
239
|
-
RUN npm ci --production=false
|
|
240
|
-
|
|
241
|
-
# Stage 2: Build
|
|
242
|
-
FROM node:22-alpine AS builder
|
|
243
|
-
WORKDIR /app
|
|
244
|
-
COPY --from=deps /app/node_modules ./node_modules
|
|
245
|
-
COPY . .
|
|
246
|
-
RUN npm run build
|
|
247
|
-
RUN npm prune --production
|
|
248
|
-
|
|
249
|
-
# Stage 3: Production
|
|
250
|
-
FROM node:22-alpine AS runner
|
|
251
|
-
WORKDIR /app
|
|
252
|
-
|
|
253
|
-
# Security: non-root user
|
|
254
|
-
RUN addgroup --system app && adduser --system --ingroup app app
|
|
255
|
-
|
|
256
|
-
# Copy only what's needed
|
|
257
|
-
COPY --from=builder --chown=app:app /app/dist ./dist
|
|
258
|
-
COPY --from=builder --chown=app:app /app/node_modules ./node_modules
|
|
259
|
-
COPY --from=builder --chown=app:app /app/package.json ./
|
|
260
|
-
|
|
261
|
-
USER app
|
|
262
|
-
EXPOSE 3000
|
|
263
|
-
|
|
264
|
-
ENV NODE_ENV=production
|
|
265
|
-
ENV PORT=3000
|
|
266
|
-
|
|
267
|
-
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
|
268
|
-
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
|
|
269
|
-
|
|
270
|
-
CMD ["node", "dist/server.js"]
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
```dockerfile
|
|
274
|
-
# Python multi-stage
|
|
275
|
-
FROM python:3.12-slim AS builder
|
|
276
|
-
WORKDIR /app
|
|
277
|
-
RUN pip install --no-cache-dir poetry
|
|
278
|
-
COPY pyproject.toml poetry.lock ./
|
|
279
|
-
RUN poetry export -f requirements.txt -o requirements.txt --without-hashes
|
|
280
|
-
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
|
|
281
|
-
|
|
282
|
-
FROM python:3.12-slim AS runner
|
|
283
|
-
WORKDIR /app
|
|
284
|
-
RUN useradd --system app
|
|
285
|
-
COPY --from=builder /install /usr/local
|
|
286
|
-
COPY . .
|
|
287
|
-
USER app
|
|
288
|
-
EXPOSE 8000
|
|
289
|
-
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
290
|
-
```
|
|
30
|
+
Code for this section: [CI/CD pipeline examples](references/pipeline-examples.md).
|
|
291
31
|
|
|
292
32
|
## Environment Management
|
|
293
33
|
|
|
@@ -463,3 +203,7 @@ async function handleRequest(req: Request) {
|
|
|
463
203
|
| Secret not available | Wrong scope or missing secret | Check repository vs environment secrets; verify name |
|
|
464
204
|
| Deploy succeeds but app crashes | Missing env vars in production | Validate env at startup, check deployment config |
|
|
465
205
|
| Rollback needed | Bad deploy passed health checks | Improve health checks, add smoke tests, use canary |
|
|
206
|
+
|
|
207
|
+
## References
|
|
208
|
+
|
|
209
|
+
- [CI/CD pipeline examples](references/pipeline-examples.md): open when writing a full CI workflow, a deploy workflow or a multi-stage Docker build.
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# CI/CD pipeline examples
|
|
2
|
+
|
|
3
|
+
<!-- toc -->
|
|
4
|
+
- [Full CI Pipeline](#full-ci-pipeline)
|
|
5
|
+
- [Deploy Workflow](#deploy-workflow)
|
|
6
|
+
- [Docker Multi-Stage Builds](#docker-multi-stage-builds)
|
|
7
|
+
<!-- /toc -->
|
|
8
|
+
|
|
9
|
+
Complete GitHub Actions CI and deploy workflows and a multi-stage Dockerfile. SKILL.md keeps the principles, environment and secret rules, and deployment strategies.
|
|
10
|
+
|
|
11
|
+
## Full CI Pipeline
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
# .github/workflows/ci.yml
|
|
15
|
+
name: CI
|
|
16
|
+
|
|
17
|
+
on:
|
|
18
|
+
push:
|
|
19
|
+
branches: [main]
|
|
20
|
+
pull_request:
|
|
21
|
+
branches: [main]
|
|
22
|
+
|
|
23
|
+
concurrency:
|
|
24
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
25
|
+
cancel-in-progress: true
|
|
26
|
+
|
|
27
|
+
env:
|
|
28
|
+
NODE_VERSION: '22'
|
|
29
|
+
REGISTRY: ghcr.io
|
|
30
|
+
IMAGE_NAME: ${{ github.repository }}
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
lint:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
|
|
38
|
+
- uses: actions/setup-node@v4
|
|
39
|
+
with:
|
|
40
|
+
node-version: ${{ env.NODE_VERSION }}
|
|
41
|
+
cache: 'npm'
|
|
42
|
+
|
|
43
|
+
- run: npm ci
|
|
44
|
+
|
|
45
|
+
- name: Lint
|
|
46
|
+
run: npm run lint
|
|
47
|
+
|
|
48
|
+
- name: Type check
|
|
49
|
+
run: npm run type-check
|
|
50
|
+
|
|
51
|
+
test:
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
needs: lint
|
|
54
|
+
|
|
55
|
+
services:
|
|
56
|
+
postgres:
|
|
57
|
+
image: postgres:16
|
|
58
|
+
env:
|
|
59
|
+
POSTGRES_USER: test
|
|
60
|
+
POSTGRES_PASSWORD: test
|
|
61
|
+
POSTGRES_DB: test_db
|
|
62
|
+
ports:
|
|
63
|
+
- 5432:5432
|
|
64
|
+
options: >-
|
|
65
|
+
--health-cmd pg_isready
|
|
66
|
+
--health-interval 10s
|
|
67
|
+
--health-timeout 5s
|
|
68
|
+
--health-retries 5
|
|
69
|
+
|
|
70
|
+
redis:
|
|
71
|
+
image: redis:7
|
|
72
|
+
ports:
|
|
73
|
+
- 6379:6379
|
|
74
|
+
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/checkout@v4
|
|
77
|
+
|
|
78
|
+
- uses: actions/setup-node@v4
|
|
79
|
+
with:
|
|
80
|
+
node-version: ${{ env.NODE_VERSION }}
|
|
81
|
+
cache: 'npm'
|
|
82
|
+
|
|
83
|
+
- run: npm ci
|
|
84
|
+
|
|
85
|
+
- name: Run migrations
|
|
86
|
+
run: npx prisma migrate deploy
|
|
87
|
+
env:
|
|
88
|
+
DATABASE_URL: postgresql://test:test@localhost:5432/test_db
|
|
89
|
+
|
|
90
|
+
- name: Run tests
|
|
91
|
+
run: npm test -- --coverage
|
|
92
|
+
env:
|
|
93
|
+
DATABASE_URL: postgresql://test:test@localhost:5432/test_db
|
|
94
|
+
REDIS_URL: redis://localhost:6379
|
|
95
|
+
|
|
96
|
+
- name: Upload coverage
|
|
97
|
+
if: github.event_name == 'pull_request'
|
|
98
|
+
uses: codecov/codecov-action@v4
|
|
99
|
+
with:
|
|
100
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
101
|
+
|
|
102
|
+
e2e:
|
|
103
|
+
runs-on: ubuntu-latest
|
|
104
|
+
needs: test
|
|
105
|
+
steps:
|
|
106
|
+
- uses: actions/checkout@v4
|
|
107
|
+
|
|
108
|
+
- uses: actions/setup-node@v4
|
|
109
|
+
with:
|
|
110
|
+
node-version: ${{ env.NODE_VERSION }}
|
|
111
|
+
cache: 'npm'
|
|
112
|
+
|
|
113
|
+
- run: npm ci
|
|
114
|
+
|
|
115
|
+
- name: Install Playwright browsers
|
|
116
|
+
run: npx playwright install --with-deps chromium
|
|
117
|
+
|
|
118
|
+
- name: Run E2E tests
|
|
119
|
+
run: npx playwright test
|
|
120
|
+
env:
|
|
121
|
+
BASE_URL: http://localhost:3000
|
|
122
|
+
|
|
123
|
+
- name: Upload test results
|
|
124
|
+
if: failure()
|
|
125
|
+
uses: actions/upload-artifact@v4
|
|
126
|
+
with:
|
|
127
|
+
name: playwright-report
|
|
128
|
+
path: playwright-report/
|
|
129
|
+
|
|
130
|
+
build:
|
|
131
|
+
runs-on: ubuntu-latest
|
|
132
|
+
needs: [test, e2e]
|
|
133
|
+
if: github.ref == 'refs/heads/main'
|
|
134
|
+
permissions:
|
|
135
|
+
contents: read
|
|
136
|
+
packages: write
|
|
137
|
+
|
|
138
|
+
steps:
|
|
139
|
+
- uses: actions/checkout@v4
|
|
140
|
+
|
|
141
|
+
- name: Log in to Container Registry
|
|
142
|
+
uses: docker/login-action@v3
|
|
143
|
+
with:
|
|
144
|
+
registry: ${{ env.REGISTRY }}
|
|
145
|
+
username: ${{ github.actor }}
|
|
146
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
147
|
+
|
|
148
|
+
- name: Extract metadata
|
|
149
|
+
id: meta
|
|
150
|
+
uses: docker/metadata-action@v5
|
|
151
|
+
with:
|
|
152
|
+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
153
|
+
tags: |
|
|
154
|
+
type=sha
|
|
155
|
+
type=ref,event=branch
|
|
156
|
+
type=semver,pattern={{version}}
|
|
157
|
+
|
|
158
|
+
- name: Build and push
|
|
159
|
+
uses: docker/build-push-action@v5
|
|
160
|
+
with:
|
|
161
|
+
context: .
|
|
162
|
+
push: true
|
|
163
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
164
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
165
|
+
cache-from: type=gha
|
|
166
|
+
cache-to: type=gha,mode=max
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Deploy Workflow
|
|
170
|
+
|
|
171
|
+
```yaml
|
|
172
|
+
# .github/workflows/deploy.yml
|
|
173
|
+
name: Deploy
|
|
174
|
+
|
|
175
|
+
on:
|
|
176
|
+
workflow_run:
|
|
177
|
+
workflows: [CI]
|
|
178
|
+
types: [completed]
|
|
179
|
+
branches: [main]
|
|
180
|
+
|
|
181
|
+
jobs:
|
|
182
|
+
deploy-staging:
|
|
183
|
+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
|
184
|
+
runs-on: ubuntu-latest
|
|
185
|
+
environment: staging
|
|
186
|
+
|
|
187
|
+
steps:
|
|
188
|
+
- uses: actions/checkout@v4
|
|
189
|
+
|
|
190
|
+
- name: Deploy to staging
|
|
191
|
+
run: |
|
|
192
|
+
echo "Deploying to staging..."
|
|
193
|
+
# Use your deployment tool
|
|
194
|
+
env:
|
|
195
|
+
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
|
|
196
|
+
DEPLOY_URL: ${{ vars.STAGING_URL }}
|
|
197
|
+
|
|
198
|
+
- name: Run smoke tests
|
|
199
|
+
run: |
|
|
200
|
+
curl --fail --retry 5 --retry-delay 10 "${{ vars.STAGING_URL }}/health"
|
|
201
|
+
|
|
202
|
+
deploy-production:
|
|
203
|
+
needs: deploy-staging
|
|
204
|
+
runs-on: ubuntu-latest
|
|
205
|
+
environment: production
|
|
206
|
+
|
|
207
|
+
steps:
|
|
208
|
+
- uses: actions/checkout@v4
|
|
209
|
+
|
|
210
|
+
- name: Deploy to production
|
|
211
|
+
run: |
|
|
212
|
+
echo "Deploying to production..."
|
|
213
|
+
env:
|
|
214
|
+
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
|
|
215
|
+
DEPLOY_URL: ${{ vars.PRODUCTION_URL }}
|
|
216
|
+
|
|
217
|
+
- name: Verify deployment
|
|
218
|
+
run: |
|
|
219
|
+
curl --fail --retry 5 --retry-delay 10 "${{ vars.PRODUCTION_URL }}/health"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Docker Multi-Stage Builds
|
|
223
|
+
|
|
224
|
+
```dockerfile
|
|
225
|
+
# Dockerfile
|
|
226
|
+
# Stage 1: Dependencies
|
|
227
|
+
FROM node:22-alpine AS deps
|
|
228
|
+
WORKDIR /app
|
|
229
|
+
COPY package.json package-lock.json ./
|
|
230
|
+
RUN npm ci --production=false
|
|
231
|
+
|
|
232
|
+
# Stage 2: Build
|
|
233
|
+
FROM node:22-alpine AS builder
|
|
234
|
+
WORKDIR /app
|
|
235
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
236
|
+
COPY . .
|
|
237
|
+
RUN npm run build
|
|
238
|
+
RUN npm prune --production
|
|
239
|
+
|
|
240
|
+
# Stage 3: Production
|
|
241
|
+
FROM node:22-alpine AS runner
|
|
242
|
+
WORKDIR /app
|
|
243
|
+
|
|
244
|
+
# Security: non-root user
|
|
245
|
+
RUN addgroup --system app && adduser --system --ingroup app app
|
|
246
|
+
|
|
247
|
+
# Copy only what's needed
|
|
248
|
+
COPY --from=builder --chown=app:app /app/dist ./dist
|
|
249
|
+
COPY --from=builder --chown=app:app /app/node_modules ./node_modules
|
|
250
|
+
COPY --from=builder --chown=app:app /app/package.json ./
|
|
251
|
+
|
|
252
|
+
USER app
|
|
253
|
+
EXPOSE 3000
|
|
254
|
+
|
|
255
|
+
ENV NODE_ENV=production
|
|
256
|
+
ENV PORT=3000
|
|
257
|
+
|
|
258
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
|
259
|
+
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
|
|
260
|
+
|
|
261
|
+
CMD ["node", "dist/server.js"]
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
```dockerfile
|
|
265
|
+
# Python multi-stage
|
|
266
|
+
FROM python:3.12-slim AS builder
|
|
267
|
+
WORKDIR /app
|
|
268
|
+
RUN pip install --no-cache-dir poetry
|
|
269
|
+
COPY pyproject.toml poetry.lock ./
|
|
270
|
+
RUN poetry export -f requirements.txt -o requirements.txt --without-hashes
|
|
271
|
+
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
|
|
272
|
+
|
|
273
|
+
FROM python:3.12-slim AS runner
|
|
274
|
+
WORKDIR /app
|
|
275
|
+
RUN useradd --system app
|
|
276
|
+
COPY --from=builder /install /usr/local
|
|
277
|
+
COPY . .
|
|
278
|
+
USER app
|
|
279
|
+
EXPOSE 8000
|
|
280
|
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
281
|
+
```
|
|
@@ -46,15 +46,7 @@ a tidy body behind a misleading name, because callers only ever see the name.
|
|
|
46
46
|
- Length follows scope: `i` is fine in a three-line loop; a module-level value
|
|
47
47
|
needs a full phrase.
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
# Before
|
|
51
|
-
def calc(d, f):
|
|
52
|
-
return d * (1 - f)
|
|
53
|
-
|
|
54
|
-
# After
|
|
55
|
-
def discounted_price(list_price: Decimal, discount_rate: Decimal) -> Decimal:
|
|
56
|
-
return list_price * (1 - discount_rate)
|
|
57
|
-
```
|
|
49
|
+
Code: [1. Names](references/code-examples.md#1-names).
|
|
58
50
|
|
|
59
51
|
## 2. Units of work
|
|
60
52
|
|
|
@@ -66,14 +58,7 @@ def discounted_price(list_price: Decimal, discount_rate: Decimal) -> Decimal:
|
|
|
66
58
|
usually means a missing type or two functions sharing a name.
|
|
67
59
|
- A body longer than a screen is a prompt to look for the seams, not a rule.
|
|
68
60
|
|
|
69
|
-
|
|
70
|
-
// Before: the flag hides two behaviours
|
|
71
|
-
func export(_ report: Report, asPDF: Bool) -> Data
|
|
72
|
-
|
|
73
|
-
// After: two names, no flag
|
|
74
|
-
func exportPDF(_ report: Report) -> Data
|
|
75
|
-
func exportCSV(_ report: Report) -> Data
|
|
76
|
-
```
|
|
61
|
+
Code: [2. Units of work](references/code-examples.md#2-units-of-work).
|
|
77
62
|
|
|
78
63
|
## 3. Control flow
|
|
79
64
|
|
|
@@ -85,23 +70,7 @@ func exportCSV(_ report: Report) -> Data
|
|
|
85
70
|
better than the raw expression inside an `if`.
|
|
86
71
|
- Nesting past two levels almost always hides a function waiting to be extracted.
|
|
87
72
|
|
|
88
|
-
|
|
89
|
-
// Before
|
|
90
|
-
function shippingCost(order: Order): number {
|
|
91
|
-
if (order.items.length > 0) {
|
|
92
|
-
if (!order.isDigital) {
|
|
93
|
-
return order.weightKg * RATE_PER_KG;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return 0;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// After
|
|
100
|
-
function shippingCost(order: Order): number {
|
|
101
|
-
if (order.items.length === 0 || order.isDigital) return 0;
|
|
102
|
-
return order.weightKg * RATE_PER_KG;
|
|
103
|
-
}
|
|
104
|
-
```
|
|
73
|
+
Code: [3. Control flow](references/code-examples.md#3-control-flow).
|
|
105
74
|
|
|
106
75
|
## 4. State and side effects
|
|
107
76
|
|
|
@@ -124,13 +93,7 @@ function shippingCost(order: Order): number {
|
|
|
124
93
|
- Check the boundaries every time: empty input, one element, the maximum, and
|
|
125
94
|
values that are present but invalid.
|
|
126
95
|
|
|
127
|
-
|
|
128
|
-
// Before: the caller cannot tell "not found" from "zero"
|
|
129
|
-
fun itemCount(orderId: String): Int = repository.find(orderId)?.items ?: 0
|
|
130
|
-
|
|
131
|
-
// After
|
|
132
|
-
fun itemCount(orderId: String): Int? = repository.find(orderId)?.items
|
|
133
|
-
```
|
|
96
|
+
Code: [5. Errors and edge values](references/code-examples.md#5-errors-and-edge-values).
|
|
134
97
|
|
|
135
98
|
## 6. Comments
|
|
136
99
|
|
|
@@ -194,3 +157,7 @@ Leave formatting to the formatter and say nothing about it.
|
|
|
194
157
|
- Speed and memory: profile first, then use the stack's performance skill.
|
|
195
158
|
- Module boundaries and system design: use `architecture`.
|
|
196
159
|
- Language style rules a linter enforces: follow the linter.
|
|
160
|
+
|
|
161
|
+
## References
|
|
162
|
+
|
|
163
|
+
- [Code examples](references/code-examples.md): open for the full code behind 1. Names, 2. Units of work, 3. Control flow, 5. Errors and edge values.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Clean Code Code Examples
|
|
2
|
+
|
|
3
|
+
Full code samples for the SKILL.md sections that link here. The rules and the decision guidance stay in SKILL.md; each heading below matches the SKILL.md section the code belongs to.
|
|
4
|
+
|
|
5
|
+
## 1. Names
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
# Before
|
|
9
|
+
def calc(d, f):
|
|
10
|
+
return d * (1 - f)
|
|
11
|
+
|
|
12
|
+
# After
|
|
13
|
+
def discounted_price(list_price: Decimal, discount_rate: Decimal) -> Decimal:
|
|
14
|
+
return list_price * (1 - discount_rate)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 2. Units of work
|
|
18
|
+
|
|
19
|
+
```swift
|
|
20
|
+
// Before: the flag hides two behaviours
|
|
21
|
+
func export(_ report: Report, asPDF: Bool) -> Data
|
|
22
|
+
|
|
23
|
+
// After: two names, no flag
|
|
24
|
+
func exportPDF(_ report: Report) -> Data
|
|
25
|
+
func exportCSV(_ report: Report) -> Data
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 3. Control flow
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// Before
|
|
32
|
+
function shippingCost(order: Order): number {
|
|
33
|
+
if (order.items.length > 0) {
|
|
34
|
+
if (!order.isDigital) {
|
|
35
|
+
return order.weightKg * RATE_PER_KG;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// After
|
|
42
|
+
function shippingCost(order: Order): number {
|
|
43
|
+
if (order.items.length === 0 || order.isDigital) return 0;
|
|
44
|
+
return order.weightKg * RATE_PER_KG;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 5. Errors and edge values
|
|
49
|
+
|
|
50
|
+
```kotlin
|
|
51
|
+
// Before: the caller cannot tell "not found" from "zero"
|
|
52
|
+
fun itemCount(orderId: String): Int = repository.find(orderId)?.items ?: 0
|
|
53
|
+
|
|
54
|
+
// After
|
|
55
|
+
fun itemCount(orderId: String): Int? = repository.find(orderId)?.items
|
|
56
|
+
```
|