@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
package/docs/CLI.md ADDED
@@ -0,0 +1,229 @@
1
+ # CLI reference
2
+
3
+ ```text
4
+ npx @ebalzan/ci <command> [options]
5
+ ```
6
+
7
+ Install it as a devDependency (`npm install --save-dev @ebalzan/ci`) and the
8
+ binary is `ebalzan-ci` in npm scripts and husky hooks. Without installing,
9
+ prefix with `npx @ebalzan/ci`. From a git checkout of this repo, `node
10
+ bin/cli.mjs <command>` and the scripts in `bin/` work the same way.
11
+
12
+ The commands that change GitHub (`onboard`, `bootstrap`, `reset`) are bash
13
+ scripts and need [`gh`](https://cli.github.com) logged in as an **admin** of
14
+ the target repo. Each one prints a plan and asks before changing anything, and
15
+ each has `--dry-run`.
16
+
17
+ | Command | Summary |
18
+ | ------------------------------- | --------------------------------------------------------- |
19
+ | [`onboard`](#onboard) | Bring a repo under the shared rules in one step |
20
+ | [`bootstrap`](#bootstrap) | Sync variables, secrets, settings and rulesets onto repos |
21
+ | [`reset`](#reset) | Delete Actions variables/secrets on a repo |
22
+ | [`commit-msg`](#commit-msg) | Validate a commit message (husky hook) |
23
+ | [`path`](#path) | Print the package directory |
24
+ | [`sync-managed`](#sync-managed) | Deprecated: refresh local copies of managed files |
25
+ | `version`, `help` | Print the version / the command list |
26
+
27
+ ---
28
+
29
+ ## onboard
30
+
31
+ Adds everything a repo needs, on a branch, and opens it for review.
32
+
33
+ ```bash
34
+ ebalzan-ci onboard [-C DIR] [options]
35
+ ```
36
+
37
+ | Option | Default | Effect |
38
+ | ----------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
39
+ | `-C, --repo-dir DIR` | `.` | Checkout of the repo to onboard |
40
+ | `-R, --repo OWNER/REPO` | from the git remote | Its slug |
41
+ | `--runs-on LABEL` | `ubuntu-latest` | Runner label written into every caller (e.g. `self-hosted`) |
42
+ | `--ref REF` | `v<major of the package>` | Ref the callers pin. Keep the major tag (`v2`); an exact version only freezes the workflow files ([why](ARCHITECTURE.md#9-versions-what-a-pin-freezes)) |
43
+ | `--branch NAME` | `ci/onboard-ebalzan-ci` | Branch the files are committed on |
44
+ | `--force` | off | Replace files that already exist (default: keep them) |
45
+ | `--no-npm` | off | Do not install packages, even with a `package.json` (files and `package.json` fields are still written) |
46
+ | `--no-config` | off | Do not wire `@ebalzan/config` (Prettier, ESLint) |
47
+ | `--no-push` | off | Stop after the local commit: GitHub is not touched at all |
48
+ | `-n, --dry-run` | off | Print the plan; change nothing |
49
+ | `-y, --yes` | off | No confirmation prompts (also for the bootstrap it runs) |
50
+
51
+ What it does, in order:
52
+
53
+ 1. Checks the tree is clean and the branch does not exist yet.
54
+ 2. Branches from `origin/dev`, or from the default branch when `dev` does not
55
+ exist yet.
56
+ 3. Writes the [templates](../templates/) with `__CI_REPO__`, `__CI_REF__` and
57
+ `__RUNS_ON__` filled in. Existing files are kept unless `--force`. The husky
58
+ hook and `eslint.config.mjs` are only written for a repo with `package.json`
59
+ (and `eslint.config.mjs` only when it has no ESLint config of its own);
60
+ `renovate.json` is skipped when the repo has its own Renovate config.
61
+ 4. Appends the `.github/secrets*.env` ignore lines to `.gitignore`, unless those
62
+ files are already ignored.
63
+ 5. Node repos, code style (unless `--no-config`): sets `package.json`
64
+ `"prettier": "@ebalzan/config/prettier"` when the repo has no Prettier config
65
+ of its own. A `tsconfig.json` that does not extend
66
+ `@ebalzan/config/tsconfig/*` is reported, never edited.
67
+ 6. Node repos, installs (unless `--no-npm`): `@ebalzan/ci@^<major>` and husky (if
68
+ missing), `scripts.prepare = "husky"` when there is no prepare script, and
69
+ `@ebalzan/config` with its peers `eslint` and `prettier` (if missing), at
70
+ exact versions. A failure here only warns.
71
+ 7. Commits as `ci: onboard ebalzan/ci shared workflows and rules`.
72
+ 8. Unless `--no-push`, after one confirmation: creates `dev` on GitHub from the
73
+ default branch if missing, adds the `REPOS_TOPIC` topic, runs
74
+ [`bootstrap`](#bootstrap), pushes the branch. The `auto-pr.yml` just added
75
+ runs on that push and opens the issue and the PR into `dev`.
76
+
77
+ Walkthrough: [ONBOARDING.md](ONBOARDING.md).
78
+
79
+ ---
80
+
81
+ ## bootstrap
82
+
83
+ Applies the merged shared + repo config to GitHub. Idempotent: run it as often
84
+ as you like.
85
+
86
+ ```bash
87
+ ebalzan-ci bootstrap # the repo in the current directory
88
+ ebalzan-ci bootstrap -C ../my-repo # a local checkout
89
+ ebalzan-ci bootstrap -R owner/my-repo # no checkout: remote mode
90
+ ebalzan-ci bootstrap --all --dry-run # every managed repo, plan only
91
+ ```
92
+
93
+ | Option | Effect |
94
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
95
+ | `-C, --repo-dir DIR` | Checkout whose `.github/` holds the repo layer (default: `.`) |
96
+ | `-R, --repo OWNER/REPO` | Target repo. Alone: remote mode, unless the current directory is a checkout of it. With `-C`: push DIR's config to that slug |
97
+ | `-a, --all` | Every repo `config/repos.env` selects (topic + list), each from its local checkout when there is one |
98
+ | `--skip LIST` | Comma-separated sections to leave alone: `vars`, `secrets`, `settings`, `rulesets` |
99
+ | `--prune` | Also delete what no file defines: variables, secrets, and rulesets named `ci: …` (asks first) |
100
+ | `-n, --dry-run` | Print the merged plan and, when gh is logged in, the settings diff and ruleset actions; change nothing |
101
+ | `-y, --yes` | No confirmation prompts |
102
+
103
+ Sections, in the order they are applied:
104
+
105
+ | Section | From | To GitHub |
106
+ | ---------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
107
+ | `vars` | the repo's `.github/actions.env` only (shared ones are read at run time) | repository variables |
108
+ | `secrets` | shared `secrets.env` + the repo's `secrets.env`, and `secrets.<env>.env` (from the repo secrets dir, below) | repository secrets; environment `<env>` (created) secrets |
109
+ | `settings` | `config/repo-settings.json` deep-merged with `.github/repo-settings.json` | `PATCH /repos/{repo}` (only changed keys are listed) |
110
+ | `rulesets` | each `config/rulesets/X.json` deep-merged with `.github/rulesets/X.json` | created, or updated by `name` |
111
+
112
+ **Where the shared secrets come from:** `$CI_SECRETS_DIR` if set; otherwise
113
+ `config/` when running from a git checkout of this repo; otherwise
114
+ `~/.config/ebalzan-ci/` (or `$XDG_CONFIG_HOME/ebalzan-ci/`). A missing file is
115
+ simply no shared secrets.
116
+
117
+ **Where a repo's own secrets come from:** `repos/<owner>/<repo>/` inside the
118
+ shared secrets dir when that folder exists (matched case-insensitively; the
119
+ checkout's `secrets*.env` are then ignored, with a warning); otherwise the
120
+ checkout's gitignored `.github/`. Keeping them in the shared secrets dir is
121
+ recommended: one folder for every repo, usable without checkouts
122
+ ([ARCHITECTURE §7](ARCHITECTURE.md#7-where-you-run-commands)).
123
+
124
+ **Remote mode** (`-R` without a checkout) reads the repo's committed
125
+ `.github/actions.env`, `bootstrap.conf`, `repo-settings.json` and `rulesets/`
126
+ from its default branch, and its secrets from `repos/<owner>/<repo>/`. Without
127
+ that folder its secrets are unknown: only shared secrets are synced, and
128
+ secrets are never pruned.
129
+
130
+ **Guards:** empty and placeholder secret values (`xxxx`, `sk-ant-xxx`,
131
+ `github_pat_xxx`) are refused before anything is sent; a `_FILE` key sets the
132
+ secret from a file's contents; `LOCAL_ONLY_KEYS` in `.github/bootstrap.conf`
133
+ are never pushed. Details: [GITHUB_ACTIONS_SETUP.md §4](GITHUB_ACTIONS_SETUP.md#4-file-mechanics).
134
+
135
+ **Rulesets on a private repo** need GitHub Pro/Team. On the free plan GitHub
136
+ answers "Upgrade to GitHub Pro or make this repository public", and the step
137
+ warns and moves on.
138
+
139
+ ---
140
+
141
+ ## reset
142
+
143
+ Deletes Actions variables (default) and/or secrets on one repo, after showing
144
+ them and asking you to type the repo name. Prefer `bootstrap --prune` to remove
145
+ only what no file defines any more.
146
+
147
+ ```bash
148
+ ebalzan-ci reset --dry-run # what would go
149
+ ebalzan-ci reset -R owner/repo --include-secrets
150
+ ebalzan-ci reset --match '^AUTO_'
151
+ ebalzan-ci reset --env production --secrets-only --dry-run
152
+ ```
153
+
154
+ | Option | Effect |
155
+ | ----------------------- | --------------------------------------------------- |
156
+ | `-C, --repo-dir DIR` | Checkout whose remote picks the repo (default: `.`) |
157
+ | `-R, --repo OWNER/REPO` | Target repo |
158
+ | `-e, --env NAME` | Act on environment `NAME` instead of the repository |
159
+ | `--include-secrets` | Also delete secrets |
160
+ | `--secrets-only` | Delete secrets only |
161
+ | `-m, --match REGEX` | Only names matching this extended regex |
162
+ | `-n, --dry-run` | List; delete nothing |
163
+ | `-y, --yes` | Skip the typed confirmation (dangerous) |
164
+
165
+ It never touches settings or rulesets.
166
+
167
+ ---
168
+
169
+ ## commit-msg
170
+
171
+ ```bash
172
+ ebalzan-ci commit-msg <message-file | subject>
173
+ ```
174
+
175
+ Exits 0 when the first non-comment line is a Conventional Commit subject with
176
+ one of the [allowed types](COMMIT_GUIDE.md#allowed-types), 1 (with an example)
177
+ otherwise. Subjects git writes itself (`Merge …`, `Revert "…"`, `fixup! …`,
178
+ `squash! …`, `amend! …`) pass. The rule is
179
+ `managed/scripts/release/validate-conventional-subject.sh` — the same file the
180
+ PR-title check runs in CI.
181
+
182
+ The hook `onboard` writes (`.husky/commit-msg`):
183
+
184
+ ```sh
185
+ if [ -x node_modules/.bin/ebalzan-ci ]; then
186
+ exec node_modules/.bin/ebalzan-ci commit-msg "$1"
187
+ fi
188
+ # …falls back to a checkout of ebalzan/ci at ${CI_DIR:-../ci}, else warns and
189
+ # lets the commit through (CI still checks the PR title).
190
+ ```
191
+
192
+ ---
193
+
194
+ ## path
195
+
196
+ Prints the installed package directory, for scripts that want the shared shell
197
+ helpers or config:
198
+
199
+ ```bash
200
+ source "$(npx --no-install ebalzan-ci path)/bin/_lib.sh" # info/ok/warn/die, env_get, detect_repo, …
201
+ ```
202
+
203
+ ---
204
+
205
+ ## sync-managed
206
+
207
+ **Deprecated.** Before 2.0, repos could keep a verbatim copy of the validator
208
+ at `scripts/release/validate-conventional-subject.sh`; the PR-title check fails
209
+ when such a copy drifts. This refreshes those copies (`--install` creates them,
210
+ `--check` only reports). Prefer deleting the copy and using the hook above.
211
+
212
+ ---
213
+
214
+ ## Environment variables
215
+
216
+ | Variable | Used by | Meaning |
217
+ | --------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
218
+ | `CI_SECRETS_DIR` | `bootstrap` | Directory with the shared `secrets.env` / `secrets.<env>.env`, and `repos/<owner>/<repo>/` for each repo's own |
219
+ | `CI_REPOS_ROOT` | `bootstrap --all`, `sync-managed --all` | Where local checkouts live: `<root>/<repo name>` (default: next to this package's checkout) |
220
+ | `CI_DIR` | the husky hook | A checkout of this repo, used when the package is not installed (default `../ci`) |
221
+ | `XDG_CONFIG_HOME` | `bootstrap` | Base of the default shared secrets dir |
222
+ | `NO_COLOR` / non-TTY output | all | Plain output without colours |
223
+
224
+ The pre-2.0 name `TRACEBIM_REPOS_ROOT` is still honoured.
225
+
226
+ ## Exit codes
227
+
228
+ `0` success (including a completed dry run) · `1` a check failed, invalid
229
+ input, or the user aborted · `127` bash/sh not found.
@@ -0,0 +1,133 @@
1
+ # Commit Guide
2
+
3
+ ## Purpose
4
+
5
+ This guide defines the commit conventions every repo using ebalzan/ci follows and how
6
+ commits fit into the branch workflow. The full pipeline, stage by stage, is in
7
+ [CI_FLOW.md](CI_FLOW.md).
8
+
9
+ ## Workflow Context
10
+
11
+ - The model is work branch → `dev` → `main`. Both `dev` and `main` are long-lived.
12
+ - Work happens on short-lived branches (`feat/*`, `fix/*`, `docs/*`, …) cut from `dev`.
13
+ - Work PRs target `dev` and are squash-merged.
14
+ - `main` only receives the automated `dev` → `main` promotion PR.
15
+ - Every push to `main` creates a release (and the repo ships it), then
16
+ force-points `dev` at the release commit.
17
+
18
+ ## Commit Message Format
19
+
20
+ Use Conventional Commits:
21
+
22
+ ```text
23
+ <type>(<scope>): <description>
24
+ ```
25
+
26
+ Examples:
27
+
28
+ ```bash
29
+ feat(auth): add oauth login
30
+ fix(uploads): reject invalid status changes
31
+ docs(readme): update setup section
32
+ ci(actions): add pr checks workflow
33
+ ```
34
+
35
+ The rule is `managed/scripts/release/validate-conventional-subject.sh` in
36
+ ebalzan/ci. Each Node repo's husky `commit-msg` hook runs it through the npm
37
+ package (`ebalzan-ci commit-msg`), and the shared PR-title check runs it on
38
+ every PR in CI — so repos without the hook are checked too, at PR time.
39
+
40
+ ## Allowed Types
41
+
42
+ - `feat`: new feature
43
+ - `fix`: bug fix
44
+ - `docs`: documentation changes
45
+ - `style`: formatting/style-only changes
46
+ - `refact`: code restructuring without behavior change (there is no `refactor`)
47
+ - `perf`: performance improvement
48
+ - `test`: tests added/updated
49
+ - `build`: build/tooling changes
50
+ - `ci`: CI/CD changes
51
+ - `chore`: maintenance tasks
52
+ - `revert`: revert previous commit
53
+
54
+ Branch prefixes are the same types (`refact/…`); there is no `feature/` or
55
+ `hotfix/`.
56
+
57
+ ## Practical Rules
58
+
59
+ 1. Keep `type` and `scope` lowercase.
60
+ 2. Use imperative description (`add`, `fix`, `update`).
61
+ 3. Do not end description with a period.
62
+ 4. Prefer specific scope names: the module or area touched (`auth`,
63
+ `uploads`, `api`, `db`, `actions`, `deps`).
64
+ 5. Keep the header at 72 characters or fewer.
65
+
66
+ ## Typical Developer Flow
67
+
68
+ ```bash
69
+ # 1) Branch from dev
70
+ git switch dev
71
+ git pull origin dev
72
+ git switch -c feat/your-change
73
+
74
+ # 2) Commit with conventional format
75
+ git add .
76
+ git commit -m "feat(search): add result filters"
77
+
78
+ # 3) Push: the work-items workflow opens a PR into dev with a tracking issue
79
+ git push -u origin feat/your-change
80
+ ```
81
+
82
+ ## Release Notes and Versioning
83
+
84
+ Release notes and semantic versioning are automatic.
85
+
86
+ - A push to `main` runs the repo's `release.yml`, which calls this repo's
87
+ reusable `release.yml`.
88
+ - The bump is computed by scanning every Conventional Commit since the previous
89
+ tag. Branch names don't affect it, and if no commit is conventional the release
90
+ fails.
91
+ - With a `package.json`, it updates it (and the lockfile) to the release
92
+ version and commits `chore(release): <version>`. The workflow skips commits
93
+ with that prefix, so the release commit doesn't trigger another release.
94
+ Without one, the release is the tag alone.
95
+ - It creates tag `vX.Y.Z` and a GitHub Release; the repo then ships that tag.
96
+ - It force-points `dev` at the release commit. If a later `git pull` on `dev`
97
+ conflicts, run `git reset --hard origin/dev` and branch from there.
98
+ - `PR Checks` validates PR titles so squash merges always produce a conventional commit.
99
+
100
+ Never bump the version, create a tag, or write a `chore(release):` commit by hand.
101
+
102
+ Version impact by commit convention:
103
+
104
+ - `feat` -> `minor`
105
+ - `fix`, `perf`, `docs`, `style`, `refact`, `test`, `build`, `ci`, `chore`, `revert` -> `patch`
106
+ - `BREAKING CHANGE` (or `!`) -> `major`
107
+
108
+ ## PR Titles and Squash Merges
109
+
110
+ GitHub squash merge uses the PR title as the commit title.
111
+
112
+ - keep PR titles in Conventional Commit format
113
+ - make the PR title match the release impact implied by the branch type (`feat` -> `minor`; every other supported type -> `patch`)
114
+ - if the change is breaking, mark the PR title with `!` to force a `major` release, for example `feat(api)!: remove legacy endpoint`
115
+ - the `dev` → `main` promotion PR is titled automatically from the promoted commits; it only needs to stay a valid Conventional Commit
116
+
117
+ ## Troubleshooting
118
+
119
+ ### Invalid commit message
120
+
121
+ Use the required format:
122
+
123
+ ```text
124
+ type(scope): description
125
+ ```
126
+
127
+ ### Committed to wrong branch
128
+
129
+ Prefer safe history-preserving fixes:
130
+
131
+ - `git cherry-pick` to move commit
132
+ - `git revert` to undo shared commits
133
+ - Avoid `git reset --hard` on shared branches
@@ -0,0 +1,177 @@
1
+ # Configuration reference
2
+
3
+ Every knob, its default, and who reads it. For _where_ a value should live
4
+ (variable or secret, repository or environment) read
5
+ [GITHUB_ACTIONS_SETUP.md](GITHUB_ACTIONS_SETUP.md); for settings and rulesets,
6
+ [REPO_RULES.md](REPO_RULES.md).
7
+
8
+ - [The layers](#the-layers)
9
+ - [Actions variables](#actions-variables)
10
+ - [Secrets](#secrets)
11
+ - [Workflow inputs](#workflow-inputs)
12
+ - [Config files](#config-files)
13
+
14
+ ---
15
+
16
+ ## The layers
17
+
18
+ ```text
19
+ SHARED (lower precedence) REPO (wins)
20
+ Actions variables config/actions.env + .github/actions.env
21
+ repository secrets <shared secrets dir>/secrets.env + <repo secrets dir>/secrets.env
22
+ environment secrets <shared secrets dir>/secrets.<env>.env + <repo secrets dir>/secrets.<env>.env
23
+ repository settings config/repo-settings.json + .github/repo-settings.json
24
+ branch rulesets config/rulesets/<file>.json + .github/rulesets/<file>.json
25
+ ```
26
+
27
+ - **Variables** — the shared file is part of this package and is read **at run
28
+ time** by the actions, from the version the caller pins (`@v2`). Only the
29
+ repo's own keys are pushed to GitHub, by `ebalzan-ci bootstrap`.
30
+ - **Secrets** — merged at sync time by `bootstrap`. The shared secrets dir is
31
+ `$CI_SECRETS_DIR`, else `config/` in a git checkout of this repo, else
32
+ `~/.config/ebalzan-ci/`. A repo's secrets dir is `repos/<owner>/<repo>/`
33
+ inside it when that folder exists (recommended), else the checkout's
34
+ `.github/`.
35
+ - **Settings and rulesets** — JSON, deep-merged by `bootstrap`: objects merge
36
+ key by key, arrays are replaced whole. Top-level keys starting with `$` are
37
+ comments.
38
+
39
+ A repo **inherits** a shared key by not mentioning it, **overrides** it by
40
+ setting the same key, and **adds** any key of its own. An empty repo variable
41
+ counts as unset, so it cannot blank out a shared value — override it with a
42
+ real one.
43
+
44
+ ---
45
+
46
+ ## Actions variables
47
+
48
+ All optional. Set them in a repo's `.github/actions.env` (then `ebalzan-ci
49
+ bootstrap`), or for every repo in `config/actions.env` (then a release of this
50
+ repo). The workflows pass the caller's variables whole
51
+ (`REPO_VARS_JSON: ${{ toJSON(vars) }}`), so a new key needs no workflow edit;
52
+ only `AUTO_*` keys are read.
53
+
54
+ ### Project board
55
+
56
+ | Variable | Default | Read by | Meaning |
57
+ | ------------------------------ | ---------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
58
+ | `AUTO_PROJECT_NUMBER` | unset → no board | work items, promote | Projects (v2) board number (the `N` in `/users/me/projects/N`). One board per repo is typical. |
59
+ | `AUTO_PROJECT_OWNER` | the repo owner | work items, promote | Account or organization owning the board. |
60
+ | `AUTO_STRICT_METADATA` | `true` | work items | With a board configured: fail the run when the board, a field or an option is missing. `false` only warns. No effect without a board. |
61
+ | `AUTO_START_DATE_OFFSET_DAYS` | `0` | work items | `Start date` = today + N days. |
62
+ | `AUTO_TARGET_DATE_OFFSET_DAYS` | `7` | work items | `Target date` = today + N days. |
63
+
64
+ The board needs the fields `Start date`, `Target date`, `Priority`
65
+ (single-select with options `P0`…`P4`), `Estimate` and `Size` (looked up by
66
+ name, case-insensitive). See [AUTO_WORK_ITEMS_WORKFLOW.md](AUTO_WORK_ITEMS_WORKFLOW.md#project-v2-behavior).
67
+
68
+ ### Priority, size, estimate
69
+
70
+ | Variable | Default (shared file) | Meaning |
71
+ | ------------------------ | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
72
+ | `AUTO_PRIORITY_MAP_JSON` | unset → per type: `fix perf revert` P1, `feat refact` P2, `docs test build ci` P3, `style chore` P4 | `{"<type>": "P<n>"}` override per branch type. |
73
+ | `AUTO_DEFAULT_PRIORITY` | unset | Priority for types the map does not list (before the per-type default). |
74
+ | `AUTO_SIZE_MAP_JSON` | `feat refact perf` M, the rest S | `{"<type>": "<size option>"}`. |
75
+ | `AUTO_DEFAULT_SIZE` | `M` | Size for types the map does not list. |
76
+ | `AUTO_ESTIMATE_MAP_JSON` | `feat` 3, `fix refact perf` 2, the rest 1 | `{"<type>": "<number>"}`. |
77
+ | `AUTO_DEFAULT_ESTIMATE` | `1` | Estimate for types the map does not list. |
78
+
79
+ ### Labels and milestone
80
+
81
+ | Variable | Default | Meaning |
82
+ | ----------------------- | ------- | ------------------------------------------------------------------------------------------------------ |
83
+ | `AUTO_ISSUE_LABELS` | unset | Extra labels for every tracking issue (comma-separated). Type labels are reserved and filtered out. |
84
+ | `AUTO_PR_LABELS` | unset | Extra labels for every work PR. |
85
+ | `AUTO_MILESTONE_TITLE` | unset | Milestone (exact title, case-insensitive) put on work issues/PRs and the promotion PR. |
86
+ | `AUTO_MILESTONE_NUMBER` | unset | Milestone by number; takes precedence over the title for work items (the promotion PR uses the title). |
87
+
88
+ ### Descriptions
89
+
90
+ | Variable | Default | Meaning |
91
+ | ---------------------- | ------------------- | ---------------------------------------------------------------------------------- |
92
+ | `AUTO_DESCRIBE_MODEL` | `claude-sonnet-5-5` | Claude model that writes issue/PR descriptions from the branch's commits and diff. |
93
+ | `AUTO_DESCRIBE_EFFORT` | `low` | `low` · `medium` · `high` · `xhigh` · `max`. Ignored for Haiku models. |
94
+
95
+ Needs the `ANTHROPIC_API_KEY` secret; without it the body is a fixed template.
96
+
97
+ ### This repo only
98
+
99
+ | Variable | Default | Meaning |
100
+ | ------------- | ------- | -------------------------------------------------------------------------------------------------------------------- |
101
+ | `NPM_PUBLISH` | `false` | `true` makes `self-release.yml` publish every release to npm ([CONTRIBUTING](../CONTRIBUTING.md#publishing-to-npm)). |
102
+
103
+ ---
104
+
105
+ ## Secrets
106
+
107
+ | Secret | Needed by | Required? | What |
108
+ | ------------------- | ------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
109
+ | `GH_PROJECT_TOKEN` | work items, promote | recommended | **Classic** PAT, scopes `repo` + `project`. Lets the bots write to Projects (v2) boards and makes the PRs they open trigger PR Checks. Falls back to `GITHUB_TOKEN` (no board writes; bot PRs do not start checks until you push to them). |
110
+ | `ANTHROPIC_API_KEY` | work items | optional | Writes the issue/PR descriptions. <https://console.anthropic.com/settings/keys> |
111
+
112
+ `GITHUB_TOKEN` is provided by Actions; never set it. Secrets are passed by name
113
+ in the caller (`secrets: inherit` is only guaranteed within one organization).
114
+
115
+ Why a classic PAT: a fine-grained token only has a Projects permission under
116
+ _organization_ permissions, so it cannot reach a board owned by a personal
117
+ account. The cost is that `repo` cannot be narrowed to selected repos — keep
118
+ the expiry short. Details: [GITHUB_ACTIONS_SETUP.md](GITHUB_ACTIONS_SETUP.md#fill-in-the-secrets).
119
+
120
+ ---
121
+
122
+ ## Workflow inputs
123
+
124
+ Set in the caller's `with:`. Everything else is a variable, so callers stay
125
+ four lines long.
126
+
127
+ | Workflow | Input | Default | Meaning |
128
+ | --------------------- | ------------- | --------------- | -------------------------------------------------------------------------------------- |
129
+ | all four | `runs-on` | `ubuntu-latest` | Runner label (e.g. `self-hosted`). |
130
+ | `auto-work-items.yml` | `base-branch` | `dev` | Branch the work PRs target. |
131
+ | `release.yml` | `sync-dev` | `true` | Point `dev` at the release commit afterwards. Keep it on unless the repo has no `dev`. |
132
+
133
+ `release.yml` outputs `tag` (`vX.Y.Z`, empty when nothing was released) and
134
+ `sha` (the release commit).
135
+
136
+ ---
137
+
138
+ ## Config files
139
+
140
+ ### In each repo (`.github/`)
141
+
142
+ | File | Committed? | Contents |
143
+ | ---------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
144
+ | `actions.env` | yes | The repo's own variables (dotenv). |
145
+ | `secrets.env` | **no** (gitignored) | The repo's own repository secrets, when they are not in `repos/<owner>/<repo>/` of the shared secrets dir. `secrets.env.example` beside it is committed. |
146
+ | `secrets.<env>.env` | **no** | Secrets for GitHub environment `<env>`, created by bootstrap. Same fallback rule. |
147
+ | `bootstrap.conf` | yes | `LOCAL_ONLY_KEYS=A B C` — secret keys only local tools read; never pushed. |
148
+ | `repo-settings.json` | yes | Overrides of `config/repo-settings.json`. |
149
+ | `rulesets/<file>.json` | yes | Overrides of the shared ruleset with the same file name, or extra rulesets. |
150
+
151
+ ### In this repo (`config/`)
152
+
153
+ | File | Published to npm? | Contents |
154
+ | ------------------------------ | -------------------------------------- | ------------------------------------------------------------------------------------------- |
155
+ | `actions.env` | yes | Shared variables (above). |
156
+ | `repo-settings.json` | yes | Shared repository settings. |
157
+ | `rulesets/*.json` | yes | Shared branch rulesets. |
158
+ | `repos.env` | yes | `REPOS_TOPIC`, `REPOS_OWNER`, `REPOS` — what `--all` selects; `CI_REPO` — this repo's slug. |
159
+ | `secrets.env.example` | yes | Template for the shared secrets. |
160
+ | `secrets.env`, `secrets.*.env` | **never** (gitignored, not in `files`) | Shared secrets, when you run the tools from a checkout. |
161
+
162
+ `repos.env` keys:
163
+
164
+ | Key | Default | Meaning |
165
+ | ------------- | ------------ | --------------------------------------------------------------------------------------- |
166
+ | `REPOS_TOPIC` | `ebalzan-ci` | `--all` includes every repo of `REPOS_OWNER` with this topic. Empty disables discovery. |
167
+ | `REPOS_OWNER` | the gh user | Account searched for the topic. |
168
+ | `REPOS` | empty | Extra `OWNER/REPO` slugs (space-separated), e.g. repos of an organization. |
169
+ | `CI_REPO` | `ebalzan/ci` | The slug the callers `uses:`. Change it only in a fork. |
170
+
171
+ ### dotenv format
172
+
173
+ `KEY=VALUE` per line; `#` comments and blank lines are ignored; one pair of
174
+ surrounding quotes is stripped. No interpolation, no `export`. A secret whose
175
+ key ends in `_FILE` is set from that file's contents under the stripped name
176
+ (for multi-line values such as SSH keys); a relative path is relative to the
177
+ env file.