@ebalzan/ci 0.0.0-stage → 3.0.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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +302 -2
  3. package/bin/_json.mjs +95 -0
  4. package/bin/_lib.sh +153 -0
  5. package/bin/cli.mjs +105 -0
  6. package/bin/gh-bootstrap.sh +622 -0
  7. package/bin/gh-reset.sh +148 -0
  8. package/bin/onboard.sh +340 -0
  9. package/bin/sync-managed.sh +81 -0
  10. package/config/actions.env +33 -0
  11. package/config/repo-settings.json +10 -0
  12. package/config/repos.env +25 -0
  13. package/config/rulesets/protect-dev.json +10 -0
  14. package/config/rulesets/protect-main.json +10 -0
  15. package/config/rulesets/require-pull-request.json +33 -0
  16. package/config/secrets.env.example +31 -0
  17. package/docs/ARCHITECTURE.md +477 -0
  18. package/docs/AUTO_WORK_ITEMS_WORKFLOW.md +305 -0
  19. package/docs/CI_FLOW.md +205 -0
  20. package/docs/CLI.md +229 -0
  21. package/docs/COMMIT_GUIDE.md +133 -0
  22. package/docs/CONFIGURATION.md +177 -0
  23. package/docs/GITHUB_ACTIONS_SETUP.md +315 -0
  24. package/docs/MIGRATION.md +175 -0
  25. package/docs/ONBOARDING.md +166 -0
  26. package/docs/REPO_RULES.md +116 -0
  27. package/managed/scripts/release/validate-conventional-subject.sh +32 -0
  28. package/package.json +66 -4
  29. package/renovate/tooling.json +45 -0
  30. package/templates/.github/actions.env +17 -0
  31. package/templates/.github/workflows/auto-pr.yml +33 -0
  32. package/templates/.github/workflows/pr-checks.yml +48 -0
  33. package/templates/.github/workflows/promote-dev-to-main.yml +26 -0
  34. package/templates/.github/workflows/release.yml +36 -0
  35. package/templates/.husky/commit-msg +18 -0
  36. package/templates/eslint.config.mjs +6 -0
  37. package/templates/renovate.json +4 -0
@@ -0,0 +1,305 @@
1
+ # Auto Work Items Workflow Guide
2
+
3
+ This document explains the automated branch workflow every repo using ebalzan/ci runs.
4
+
5
+ ## What It Does
6
+
7
+ When you push to any branch other than `main` or `dev`, the workflow automatically:
8
+
9
+ 1. Creates or reuses a tracking **Issue** for the branch.
10
+ 2. Creates or reuses a ready-for-review **PR** from the branch to `dev` (the `base-branch` input).
11
+ 3. Links the PR to the issue (`Closes #<issue-number>`).
12
+ 4. Applies the exact label set to both issue and PR (removes stale extras).
13
+ 5. Applies milestone (if configured).
14
+ 6. Adds both issue and PR to GitHub Project (v2) (if `AUTO_PROJECT_NUMBER` is set).
15
+ 7. Assigns issue and PR to the latest commit author (if resolvable to a GitHub user).
16
+ 8. Updates project fields: `Start date`, `Target date`, `Priority`, `Estimate`, and `Size`.
17
+
18
+ The process is idempotent and safe to run on every push.
19
+
20
+ ## Workflow Files
21
+
22
+ - Reusable workflow: `.github/workflows/auto-work-items.yml` (this repo)
23
+ - Composite action it runs: `actions/auto-work-items/action.yml`
24
+ - Caller in each repo: `.github/workflows/auto-pr.yml` — only the trigger
25
+ (`push`, `branches-ignore: [main, dev]`), the permissions, and the `uses:` line.
26
+
27
+ ## Script Architecture
28
+
29
+ The workflow is orchestrated by `actions/github-script`, but all logic is implemented in TypeScript modules.
30
+
31
+ Source of truth:
32
+
33
+ - `actions/auto-work-items/src/index.ts` (orchestrator)
34
+ - `actions/auto-work-items/src/issue.ts`
35
+ - `actions/auto-work-items/src/pr.ts`
36
+ - `actions/auto-work-items/src/project.ts`
37
+ - `actions/auto-work-items/src/config.ts`
38
+ - `actions/auto-work-items/src/labels.ts`
39
+ - `actions/auto-work-items/src/milestone.ts`
40
+ - `actions/auto-work-items/src/utils.ts`
41
+ - `actions/auto-work-items/src/types.ts`
42
+
43
+ Runtime files used by GitHub Actions:
44
+
45
+ - `actions/auto-work-items/dist/*.js`
46
+
47
+ Why both exist:
48
+
49
+ - `actions/github-script` executes JavaScript, not `.ts` directly.
50
+ - We keep typed source in `src/` and compile to `dist/` for runtime.
51
+
52
+ ## Trigger Rules
53
+
54
+ The caller (`auto-pr.yml`) runs it on:
55
+
56
+ - `push` to any branch except `main` and `dev`
57
+ - tags are ignored
58
+
59
+ ```yaml
60
+ on:
61
+ push:
62
+ branches-ignore:
63
+ - main
64
+ - dev
65
+ tags-ignore:
66
+ - '**'
67
+ ```
68
+
69
+ The reusable workflow also skips `chore/renovate-*` branches, whose PR
70
+ Renovate owns.
71
+
72
+ ## Concurrency and Idempotency
73
+
74
+ Concurrency is enabled per branch:
75
+
76
+ - group: `auto-work-items-${{ github.ref }}`
77
+ - in-progress run for the same branch is canceled
78
+
79
+ Idempotency is handled by a marker in issue/PR bodies:
80
+
81
+ - `Marker: auto-work-item:<branch-name>`
82
+
83
+ If an issue/PR already exists for that branch, it is reused and updated.
84
+
85
+ ## End-to-End Flow
86
+
87
+ 1. Parse branch name and derive `branchType` (`feat`, `fix`, `chore`, etc.; any other prefix fails the run).
88
+ 2. Build titles, body content, marker, labels, and priority decision.
89
+ 3. Ensure labels exist and sync color/description to expected values.
90
+ 4. Resolve milestone by number (preferred) or exact title.
91
+ 5. Upsert tracking issue.
92
+ 6. Upsert PR (opened ready for review, not draft) to `AUTO_BASE_BRANCH` (`dev`).
93
+ 7. Ensure PR includes `Closes #<issue-number>`.
94
+ 8. Add issue and PR to configured Project v2.
95
+ 9. Resolve project fields by name (`Start date`, `Target date`, `Priority`, `Estimate`, `Size`).
96
+ 10. Set date fields and metadata fields on both items.
97
+ 11. Emit workflow notice with final issue/PR numbers.
98
+
99
+ ## Branch Type Mapping
100
+
101
+ Current branch-type defaults:
102
+
103
+ | Branch Type | PR Prefix | Type Label | Default Priority | Label Color |
104
+ | ----------- | --------- | ---------- | ---------------- | ----------- |
105
+ | `feat` | `feat` | `feat` | `P2` | `1D76DB` |
106
+ | `fix` | `fix` | `fix` | `P1` | `D73A4A` |
107
+ | `docs` | `docs` | `docs` | `P3` | `5319E7` |
108
+ | `style` | `style` | `style` | `P4` | `C2E0C6` |
109
+ | `refact` | `refact` | `refact` | `P2` | `FBCA04` |
110
+ | `perf` | `perf` | `perf` | `P1` | `F9D0C4` |
111
+ | `test` | `test` | `test` | `P3` | `0E8A16` |
112
+ | `build` | `build` | `build` | `P3` | `0052CC` |
113
+ | `ci` | `ci` | `ci` | `P3` | `0052CC` |
114
+ | `chore` | `chore` | `chore` | `P4` | `BFD4F2` |
115
+ | `revert` | `revert` | `revert` | `P1` | `E99695` |
116
+
117
+ These are the only accepted branch types. Any other prefix (`feature/`, `hotfix/`,
118
+ `refactor/`, …) fails the run with no issue or PR created — rename the branch.
119
+
120
+ ## Labels Applied
121
+
122
+ Always applied labels:
123
+
124
+ - type label from branch type (for example `feat`, `refact`, `chore`)
125
+
126
+ Optional extra labels from variables:
127
+
128
+ - `AUTO_ISSUE_LABELS` (CSV)
129
+ - `AUTO_PR_LABELS` (CSV)
130
+
131
+ Note:
132
+
133
+ - Reserved type labels (for example `feat`, `fix`, `chore`, `ci`) are controlled by branch type and filtered out from `AUTO_ISSUE_LABELS` and `AUTO_PR_LABELS`.
134
+
135
+ Label color behavior:
136
+
137
+ - Known labels (type labels) use configured fixed colors.
138
+ - Custom/extra labels use a deterministic non-gray palette.
139
+ - `priority:P0` through `priority:P4` map to priority-specific colors.
140
+ - Existing labels are updated when current color/description drift from expected values.
141
+
142
+ ## Milestone Behavior
143
+
144
+ Milestone resolution order:
145
+
146
+ 1. `AUTO_MILESTONE_NUMBER` (numeric)
147
+ 2. `AUTO_MILESTONE_TITLE` (exact case-insensitive title match)
148
+ 3. none only when both variables are unset
149
+
150
+ When resolved, milestone is set on both issue and PR.
151
+ A milestone that cannot be found (or is closed) produces a warning, not a
152
+ failure.
153
+
154
+ Setup guidance:
155
+
156
+ - If you want stable pinning, set `AUTO_MILESTONE_NUMBER`.
157
+ - If you want name-based matching, set `AUTO_MILESTONE_TITLE` and leave `AUTO_MILESTONE_NUMBER` unset.
158
+ - Do not keep a stale `AUTO_MILESTONE_NUMBER`; it takes precedence over title.
159
+
160
+ ## Project v2 Behavior
161
+
162
+ Project selection:
163
+
164
+ - owner: `AUTO_PROJECT_OWNER` (defaults to repo owner)
165
+ - project number: `AUTO_PROJECT_NUMBER` — unset means no board: the issue and
166
+ PR are still created, and the board step is skipped
167
+
168
+ Field lookup is by name (case-insensitive):
169
+
170
+ - `Start date`
171
+ - `Target date`
172
+ - `Priority` (single-select)
173
+ - `Estimate`
174
+ - `Size`
175
+
176
+ There are no custom field-name variables in the current implementation.
177
+
178
+ Date values:
179
+
180
+ - Start = `today + AUTO_START_DATE_OFFSET_DAYS` (default `0`)
181
+ - Target = `today + AUTO_TARGET_DATE_OFFSET_DAYS` (default `7`)
182
+
183
+ Priority value:
184
+
185
+ 1. `AUTO_PRIORITY_MAP_JSON[branchType]`
186
+ 2. `AUTO_DEFAULT_PRIORITY`
187
+ 3. branch default from mapping table
188
+
189
+ If the requested priority is unavailable in the project field options, the workflow falls back to the nearest available `P<number>` option.
190
+
191
+ Size value:
192
+
193
+ 1. `AUTO_SIZE_MAP_JSON[branchType]`
194
+ 2. `AUTO_DEFAULT_SIZE`
195
+ 3. fallback: `M`
196
+
197
+ Estimate value:
198
+
199
+ 1. `AUTO_ESTIMATE_MAP_JSON[branchType]`
200
+ 2. `AUTO_DEFAULT_ESTIMATE`
201
+ 3. fallback: `1`
202
+
203
+ ## Strict vs Non-Strict Metadata Mode
204
+
205
+ `AUTO_STRICT_METADATA` controls failure behavior for project metadata sync.
206
+
207
+ - `true` (default): with a board configured, a missing project, field or
208
+ option fails the workflow.
209
+ - `false`: the same problems only emit warnings.
210
+ - Without `AUTO_PROJECT_NUMBER` neither applies: the board is skipped.
211
+
212
+ This is useful when metadata is mandatory for your process.
213
+
214
+ ## Variables and Secret Reference
215
+
216
+ `auto-work-items.yml` passes the caller repo's variables whole
217
+ (`REPO_VARS_JSON: ${{ toJSON(vars) }}`) — the repo's `.github/actions.env`,
218
+ pushed by `ebalzan-ci bootstrap` — and the action takes their `AUTO_*` keys. A
219
+ knob the repo leaves unset is filled from `config/actions.env` at the action's
220
+ ref (`src/sharedVars.ts`), else the code default applies. So a knob is set in
221
+ the repo's file (then the bootstrap) or in `config/actions.env` (then a release
222
+ that moves the major tag) — never in the workflow. Defaults and meanings:
223
+ [CONFIGURATION.md](CONFIGURATION.md#actions-variables).
224
+
225
+ - `AUTO_BASE_BRANCH` (from the `base-branch` input, default `dev`)
226
+ - `AUTO_ISSUE_LABELS`
227
+ - `AUTO_PR_LABELS`
228
+ - `AUTO_MILESTONE_NUMBER`
229
+ - `AUTO_MILESTONE_TITLE`
230
+ - `AUTO_PROJECT_OWNER`
231
+ - `AUTO_PROJECT_NUMBER` (repo variable — one board per repo)
232
+ - `AUTO_DEFAULT_PRIORITY`
233
+ - `AUTO_PRIORITY_MAP_JSON`
234
+ - `AUTO_DEFAULT_SIZE`
235
+ - `AUTO_SIZE_MAP_JSON`
236
+ - `AUTO_DEFAULT_ESTIMATE`
237
+ - `AUTO_ESTIMATE_MAP_JSON`
238
+ - `AUTO_START_DATE_OFFSET_DAYS`
239
+ - `AUTO_TARGET_DATE_OFFSET_DAYS`
240
+ - `AUTO_STRICT_METADATA` (defaults to `true` if unset)
241
+ - `AUTO_DESCRIBE_MODEL`, `AUTO_DESCRIBE_EFFORT` (describe.ts)
242
+ - secret `ANTHROPIC_API_KEY` (describe.ts; without it the body is a stub)
243
+
244
+ Notes:
245
+
246
+ - Without `AUTO_PROJECT_NUMBER` the board is skipped, whatever `AUTO_STRICT_METADATA` says.
247
+ - `GH_PROJECT_TOKEN` is recommended when `github.token` cannot write Project v2 metadata.
248
+
249
+ ## Permissions Used
250
+
251
+ Workflow permissions (the caller must grant them; a reusable workflow cannot
252
+ raise its caller's):
253
+
254
+ - `contents: read`
255
+ - `issues: write`
256
+ - `pull-requests: write`
257
+ - `repository-projects: write`
258
+
259
+ Token source used by workflow:
260
+
261
+ - `secrets.GH_PROJECT_TOKEN` if present
262
+ - otherwise falls back to `github.token`
263
+
264
+ ## Build and Maintenance
265
+
266
+ Compile TypeScript workflow scripts:
267
+
268
+ ```bash
269
+ npm run build # in a checkout of ebalzan/ci
270
+ ```
271
+
272
+ Important:
273
+
274
+ - Commit both `src/` and `dist/` changes. CI (`npm run check`) fails when
275
+ `dist/` does not match `src/`.
276
+ - Callers run `dist/` straight from the action download — nothing builds it on
277
+ the runner. A change reaches the repos only once the major tag moves
278
+ ([CONTRIBUTING → Releasing](../CONTRIBUTING.md#releasing-a-change)).
279
+
280
+ ## Troubleshooting
281
+
282
+ ### "Context access might be invalid"
283
+
284
+ This warning usually comes from static editor validation, not Actions runtime.
285
+ It is common with `vars.*` and `secrets.*` in workflow `env` blocks — which is
286
+ why the workflows pass `toJSON(vars)` instead of naming each variable.
287
+
288
+ ### Common runtime failures
289
+
290
+ - Unsupported branch prefix (`feature/`, `hotfix/`, `refactor/`, …): rename the branch
291
+ - Project not found (`AUTO_PROJECT_OWNER` / `AUTO_PROJECT_NUMBER` mismatch)
292
+ - Missing required fields in Project v2 (`Start date`, `Target date`, `Priority`, `Estimate`, `Size`)
293
+ - Priority option text does not exist in the project field
294
+ - Token missing permission for project updates
295
+ - Milestone title not found
296
+ - Stale `AUTO_MILESTONE_NUMBER` overriding `AUTO_MILESTONE_TITLE`
297
+
298
+ ### Quick verification checklist
299
+
300
+ 1. Push a test branch like `chore/workflow-test`.
301
+ 2. Confirm issue + PR are created.
302
+ 3. Confirm PR body includes `Closes #...`.
303
+ 4. Confirm issue and PR are added to the right project.
304
+ 5. Confirm issue and PR assignees are set to the latest commit author.
305
+ 6. Confirm `Start date`/`Target date`/`Priority`/`Estimate`/`Size` fields are set correctly.
@@ -0,0 +1,205 @@
1
+ # CI/CD Flow (shared stages)
2
+
3
+ The branch → `dev` → `main` → release flow every repo using ebalzan/ci runs.
4
+ This file covers the stages this repo owns; what happens **after** a release
5
+ (a container rollout, a Vercel deploy, an app store build) and each repo's own
6
+ build/test checks belong to that repo, chained on the stages below. This repo
7
+ runs the same flow on itself (`.github/workflows/self-*.yml`), where the release
8
+ also moves the major tag the other repos pin
9
+ ([CONTRIBUTING → Releasing](../CONTRIBUTING.md#releasing-a-change)).
10
+
11
+ > **Model:** feature branch → `dev` → `main`.
12
+ > `dev` is the integration branch. `main` is the release branch.
13
+ > Every push to `main` produces a versioned release, which the repo then ships.
14
+
15
+ ---
16
+
17
+ ## 1. Branching model
18
+
19
+ | Branch | Role | Receives merges from | Protected |
20
+ | ------------------------------- | ----------------------- | -------------------- | --------- |
21
+ | `main` | Releases | `dev` only | Yes ¹ |
22
+ | `dev` | Integration of features | feature branches | Yes ¹ |
23
+ | `feat/*`, `fix/*`, `chore/*`, … | Short-lived work | (you push to them) | No |
24
+
25
+ Supported work-branch prefixes (each maps to a release type for the
26
+ feature → `dev` PR check): `feat`, `fix`, `docs`, `style`, `refact`, `perf`,
27
+ `test`, `build`, `ci`, `chore`, `revert`. Any other prefix fails both the PR
28
+ check and the auto work-items run. A `refact/` branch takes a `refact:` title;
29
+ there is no `refactor` type, and no `feature/` or `hotfix/` alias.
30
+
31
+ ¹ By the rulesets `bootstrap` applies: neither can be deleted, `main` cannot be
32
+ force-pushed, and an opt-in ruleset requires PRs ([REPO_RULES.md](REPO_RULES.md)).
33
+
34
+ ---
35
+
36
+ ## 2. The flow at a glance
37
+
38
+ ```text
39
+ feature branch ── git push
40
+ │
41
+ ▼
42
+ (1) Auto Create Work Items auto-work-items.yml
43
+ │ Issue + PR (base = dev) + project card
44
+ ▼
45
+ PR: feat → dev ── (2) PR Checks: pr-title.yml + the repo's own jobs
46
+ │ ✅ squash-merge into dev
47
+ ▼
48
+ (3) Promote dev to main promote-dev-to-main.yml
49
+ │ ONE PR: dev → main (auto title + commit list)
50
+ ▼
51
+ PR: dev → main ── (2) PR Checks again (base = main)
52
+ │ ✅ squash-merge into main ← this is the release approval
53
+ ▼
54
+ (4) Release release.yml
55
+ │ bump from commits → tag vX.Y.Z → GitHub Release → dev synced
56
+ ▼
57
+ (5) Ship — the repo's own job, chained on release.yml's `tag` / `sha` outputs
58
+ ```
59
+
60
+ Each stage is a reusable workflow here, called from a thin file in the repo
61
+ that holds only the trigger, the permissions and the `uses:` line.
62
+
63
+ ---
64
+
65
+ ## 3. Stage by stage
66
+
67
+ ### Stage 1 — Auto Create Work Items
68
+
69
+ - **Reusable workflow:** `.github/workflows/auto-work-items.yml`
70
+ - **Repo caller:** `.github/workflows/auto-pr.yml`, triggered on `push` to any
71
+ branch except `main` and `dev` (never on tags).
72
+ - **What it does:** creates or updates (idempotent per branch) a GitHub
73
+ **Issue**, a **PR** into `dev`, and the **project card** with labels,
74
+ milestone, priority, size, estimate and dates. Details:
75
+ [AUTO_WORK_ITEMS_WORKFLOW.md](./AUTO_WORK_ITEMS_WORKFLOW.md).
76
+ - **Token:** `GH_PROJECT_TOKEN` if present, else `github.token`. A classic PAT
77
+ with the `project` scope is required to write to a user-owned board, and so
78
+ that the created PR triggers Stage 2.
79
+
80
+ ### Stage 2 — PR Checks
81
+
82
+ - **Repo file:** `.github/workflows/pr-checks.yml`, on `pull_request` into
83
+ `main` or `dev` (`opened`, `synchronize`, `reopened`, `ready_for_review`).
84
+ - **Shared job** (`pr-title.yml` → `actions/pr-title`):
85
+ 1. `validate-conventional-subject.sh` — the title must be a Conventional
86
+ Commits subject (e.g. `feat(search): add filters`).
87
+ 2. `verify-pr-release-type.mjs` — checks the title's release type against the
88
+ **branch**:
89
+ - **Feature branch** (`feat/...` → `dev`): the title's type must match the
90
+ branch type, unless the title marks a breaking change with `!`.
91
+ - **Promotion** (`dev` → `main`): only requires a valid conventional title;
92
+ the bump is computed later from the promoted commits.
93
+ 3. Managed-file drift: if the repo still keeps a (deprecated) local copy of a
94
+ `managed/` file, it must equal this version's ([CLI → sync-managed](CLI.md#sync-managed)).
95
+ - **Repo jobs** (validate, build, image build, delivery preview, …) run in
96
+ parallel beside it. A bad title still fails the run.
97
+ - The check is reported as **`<caller job id> / Validate PR Title`**. Name the
98
+ caller job `title` (the template does): `title / Validate PR Title` is the
99
+ context the `ci: require pull request` ruleset requires.
100
+
101
+ ### Stage 3 — Promote dev to main
102
+
103
+ - **Reusable workflow:** `.github/workflows/promote-dev-to-main.yml`
104
+ (`scripts/release/promote-dev-to-main.mjs`)
105
+ - **Repo caller:** `push` to `dev`, plus `workflow_dispatch`.
106
+ - **What it does:**
107
+ 1. Compares `main...dev`. If `dev` is not ahead, it stops.
108
+ 2. Computes the bump from the promoted commits and creates or updates a
109
+ single `dev → main` PR titled with the most significant type among them
110
+ (`feat` > `fix` > `refact` > `perf` > `revert` > `build` > `ci` > `docs` >
111
+ `test` > `style` > `chore`, `!` for breaking) plus the projected version,
112
+ e.g. `refact: promote dev to main (v0.6.3)`. The body lists every commit.
113
+ 3. Best-effort metadata (a failure warns, never fails the run): assignee = who
114
+ pushed to `dev`; labels `release` + each promoted commit type (only ever
115
+ added); the `AUTO_MILESTONE_TITLE` milestone and the project board, when
116
+ configured.
117
+ - **Token:** `GH_PROJECT_TOKEN` if present, else `GITHUB_TOKEN`.
118
+ ⚠️ A PR opened with the default `GITHUB_TOKEN` does not trigger other
119
+ workflows — without the PAT, PR Checks will not run on the promotion PR until
120
+ you push to it or reopen it.
121
+
122
+ ### Stage 4 — Release
123
+
124
+ - **Reusable workflow:** `.github/workflows/release.yml`
125
+ (`scripts/release/release-main.mjs`)
126
+ - **Repo caller:** `push` to `main`.
127
+ - **Skip guard:** skipped when the head commit starts with `chore(release):`,
128
+ so the release commit cannot release again.
129
+ - **What it does:**
130
+ 1. Reads the latest `vX.Y.Z` tag (none yet: the `package.json` version, or `0.0.0`).
131
+ 2. Computes the bump from every Conventional Commit since it (§4).
132
+ 3. With a `package.json`: bumps it (and the lockfile, if any), commits
133
+ `chore(release): X.Y.Z`, tags `vX.Y.Z`. Without one: tags the merged
134
+ commit as is (tag-only release). Pushes the branch and the tag.
135
+ 4. Publishes a GitHub Release (associated PR + included commits).
136
+ 5. `sync-dev`: force-points `dev` at the release commit (§4, "Squash
137
+ everything").
138
+ - **Outputs:** `tag` (`vX.Y.Z`, empty when nothing was released) and `sha`
139
+ (the release commit). The repo's own ship jobs chain on them.
140
+
141
+ ---
142
+
143
+ ## 4. Version bump rules (commit-scan)
144
+
145
+ The bump is derived from **the Conventional Commits landed on `main` since the
146
+ previous tag**, not from any branch name. Highest wins:
147
+
148
+ | Found in any commit since last tag | Bump |
149
+ | ---------------------------------------------------------------------------------- | ----------------- |
150
+ | `!` after type, or `BREAKING CHANGE:` footer | `major` |
151
+ | `feat:` | `minor` |
152
+ | `fix`, `perf`, `docs`, `style`, `refact`, `test`, `build`, `ci`, `chore`, `revert` | `patch` |
153
+ | no Conventional Commit found | release **fails** |
154
+
155
+ Implementation: `highestRelease()` in `scripts/release/release-utils.mjs`, used
156
+ by both `release-main.mjs` (the bump) and `promote-dev-to-main.mjs` (the title
157
+ preview).
158
+
159
+ ### Why this works for either merge method on `dev → main`
160
+
161
+ - **Squash merge:** `main` gets one commit using the PR title; because the
162
+ promotion title is generated to carry the highest bump, the scan still yields
163
+ the right version. **This is the method every repo uses** — the shared
164
+ repo settings allow nothing else.
165
+ - **Merge commit:** the feature commits flow into `main`; the scan (which skips
166
+ merge commits) sees each one.
167
+
168
+ > **Squash everything.** A squash (or rebase) rewrites the promoted commits, so
169
+ > `dev` and `main` end up with identical content under unrelated histories, and
170
+ > the _next_ promotion would re-merge everything from the last common ancestor
171
+ > and conflict against itself. `sync-dev` prevents that by force-pointing `dev`
172
+ > at the release commit after each release. Consequence: if you have local
173
+ > commits on `dev` that were not released, `git reset --hard origin/dev` after a
174
+ > release and branch off it.
175
+
176
+ ---
177
+
178
+ ## 5. Developer flow (cheat sheet)
179
+
180
+ ```bash
181
+ git checkout dev && git pull origin dev
182
+ git checkout -b feat/your-change
183
+ git commit -m "feat(search): add result filters" # husky checks it
184
+ git push -u origin feat/your-change # Stage 1 opens Issue + PR into dev
185
+ # merge into dev (squash) once PR Checks pass
186
+ # Stage 3 opens/updates dev → main; merge it (squash) to release
187
+ # Stage 4 versions, tags, releases; the repo ships the tag
188
+ ```
189
+
190
+ You never edit versions, tags or release notes by hand.
191
+
192
+ ---
193
+
194
+ ## 6. Troubleshooting
195
+
196
+ | Symptom | Likely cause / fix |
197
+ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
198
+ | Workflow fails to start: "workflow was not found" | Wrong `uses:` path or ref, or the repo's Actions policy does not allow actions from other repos (Settings → Actions → General). |
199
+ | "The nested job is requesting 'X', but is only allowed…" | The caller's `permissions:` lacks what the reusable job declares. Grant it in the caller. |
200
+ | No Issue/PR after pushing a feature branch | Branch was `dev`/`main` (ignored), unsupported prefix, or `GH_PROJECT_TOKEN` missing/insufficient. |
201
+ | `dev → main` PR exists but no checks ran | Opened with the default `GITHUB_TOKEN`. Set `GH_PROJECT_TOKEN`, or push to it. |
202
+ | PR Checks: "differs from ebalzan/ci's managed copy" | Delete the local copy and use the hook from the package ([CLI](CLI.md#commit-msg)). |
203
+ | Release failed: "Could not determine a release type" | No Conventional Commit since the last tag. |
204
+ | Behaviour did not change after editing ebalzan/ci | Callers pin `@v2`: the change ships only once it is released and `v2` moves. |
205
+ | Work items: "unsupported type" | The branch prefix is not one of the types above. Rename the branch. |