@cldmv/gh-broker 0.0.0-stage → 0.7.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 (107) hide show
  1. package/.configs/fix-headers.json +3 -0
  2. package/.configs/vitest.config.mjs +44 -0
  3. package/.github/dependabot.yml +111 -0
  4. package/.github/workflows/branch-retention.yml +44 -0
  5. package/.github/workflows/bundle-size.yml +52 -0
  6. package/.github/workflows/ci.yml +389 -0
  7. package/.github/workflows/cla.yml +66 -0
  8. package/.github/workflows/codeql.yml +87 -0
  9. package/.github/workflows/dependabot-auto-merge.yml +62 -0
  10. package/.github/workflows/dependabot-recreate.yml +67 -0
  11. package/.github/workflows/dependency-review.yml +46 -0
  12. package/.github/workflows/feature-pr.yml +75 -0
  13. package/.github/workflows/hotfix-redirector.yml +67 -0
  14. package/.github/workflows/hotfixes-release.yml +70 -0
  15. package/.github/workflows/labeler.yml +48 -0
  16. package/.github/workflows/master-commit-audit.yml +56 -0
  17. package/.github/workflows/member-auto-merge.yml +75 -0
  18. package/.github/workflows/next-release.yml +76 -0
  19. package/.github/workflows/next-reset.yml +50 -0
  20. package/.github/workflows/pr-notify.yml +44 -0
  21. package/.github/workflows/pr-title-normalizer.yml +52 -0
  22. package/.github/workflows/provenance.yml +51 -0
  23. package/.github/workflows/publish.yml +146 -0
  24. package/.github/workflows/release-merge.yml +104 -0
  25. package/.github/workflows/release-notify.yml +41 -0
  26. package/.github/workflows/scorecard.yml +64 -0
  27. package/.github/workflows/stale.yml +50 -0
  28. package/.github/workflows/tag-health.yml +70 -0
  29. package/.github/workflows/update-major-version-tags.yml +94 -0
  30. package/.github/workflows/v4-bootstrap.yml +115 -0
  31. package/.github/workflows/welcome.yml +42 -0
  32. package/LICENSE +202 -0
  33. package/README.md +185 -2
  34. package/config.example.json +281 -0
  35. package/deploy/README.md +92 -0
  36. package/deploy/gh-broker.service +57 -0
  37. package/deploy/install.mjs +344 -0
  38. package/deploy/install.sh +29 -0
  39. package/deploy/lib/config-merge.mjs +56 -0
  40. package/deploy/lib/prompt.mjs +98 -0
  41. package/deploy/lib/secret-store.mjs +114 -0
  42. package/deploy/lib/smoke.mjs +137 -0
  43. package/deploy/lib/system.mjs +222 -0
  44. package/dist/index.mjs +22970 -0
  45. package/dist/index.mjs.map +7 -0
  46. package/docs/TOOLS.md +347 -0
  47. package/docs/agent-user-model.md +89 -0
  48. package/docs/changelog/v0/v0.2.0.md +26 -0
  49. package/docs/changelog/v0/v0.2.1.md +23 -0
  50. package/docs/changelog/v0/v0.2.2.md +23 -0
  51. package/docs/changelog/v0/v0.2.3.md +33 -0
  52. package/docs/changelog/v0/v0.2.4.md +25 -0
  53. package/docs/changelog/v0/v0.2.5.md +29 -0
  54. package/docs/changelog/v0/v0.3.0.md +84 -0
  55. package/docs/changelog/v0/v0.3.1.md +30 -0
  56. package/docs/changelog/v0/v0.3.2.md +24 -0
  57. package/docs/changelog/v0/v0.3.3.md +28 -0
  58. package/docs/changelog/v0/v0.4.0.md +58 -0
  59. package/docs/changelog/v0/v0.4.1.md +33 -0
  60. package/docs/changelog/v0/v0.4.2.md +45 -0
  61. package/docs/changelog/v0/v0.5.0.md +39 -0
  62. package/docs/changelog/v0/v0.5.1.md +28 -0
  63. package/docs/changelog/v0/v0.6.0.md +52 -0
  64. package/docs/changelog/v0/v0.6.1.md +48 -0
  65. package/docs/changelog/v0/v0.7.0.md +71 -0
  66. package/docs/deploy-architecture.md +82 -0
  67. package/package.json +71 -5
  68. package/src/appauth.ts +56 -0
  69. package/src/audit.ts +34 -0
  70. package/src/catalog.ts +165 -0
  71. package/src/config.ts +130 -0
  72. package/src/index.ts +107 -0
  73. package/src/paths.ts +60 -0
  74. package/src/router.ts +258 -0
  75. package/src/secrets.ts +137 -0
  76. package/src/server.ts +91 -0
  77. package/src/types.ts +104 -0
  78. package/src/upstream.ts +85 -0
  79. package/src/verbs/issue_labels.ts +329 -0
  80. package/src/verbs/link_issue.ts +84 -0
  81. package/src/verbs/release-analysis.ts +210 -0
  82. package/src/verbs/repo-tier.ts +90 -0
  83. package/src/verbs/report_ci_status.ts +358 -0
  84. package/src/verbs/report_release_prs.ts +238 -0
  85. package/src/verbs/report_security_alerts.ts +442 -0
  86. package/src/verbs/report_unlabeled.ts +194 -0
  87. package/src/verbs/rest-client.ts +181 -0
  88. package/src/verbs/review_thread.ts +138 -0
  89. package/tests/appauth.test.vitest.mts +76 -0
  90. package/tests/audit.test.vitest.mts +48 -0
  91. package/tests/catalog.test.vitest.mts +180 -0
  92. package/tests/config-merge.test.vitest.mts +58 -0
  93. package/tests/config.test.vitest.mts +314 -0
  94. package/tests/issue-labels.test.vitest.mts +347 -0
  95. package/tests/paths.test.vitest.mts +52 -0
  96. package/tests/release-prs.test.vitest.mts +237 -0
  97. package/tests/rest-client.test.vitest.mts +115 -0
  98. package/tests/router.test.vitest.mts +399 -0
  99. package/tests/run-vitest.mjs +51 -0
  100. package/tests/secret-store.test.vitest.mts +106 -0
  101. package/tests/secrets.test.vitest.mts +155 -0
  102. package/tests/security-alerts.test.vitest.mts +328 -0
  103. package/tests/server.test.vitest.mts +20 -0
  104. package/tests/upstream.test.vitest.mts +91 -0
  105. package/tests/verb-helpers.test.vitest.mts +331 -0
  106. package/tests/verbs.test.vitest.mts +624 -0
  107. package/tsconfig.json +21 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "@cldmv/configs/fix-headers.json"
3
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ *
3
+ * @Project: @cldmv/gh-broker
4
+ * @Filename: /.configs/vitest.config.mjs
5
+ * @Date: 2026-08-02T16:55:52-07:00 (1785714952)
6
+ * @Author: Nate Corcoran <CLDMV>
7
+ * @Email: <Shinrai@users.noreply.github.com>
8
+ * -----
9
+ * @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
10
+ * @Last modified time: 2026-10-02T11:30:57-07:00 (1790965857)
11
+ * -----
12
+ * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
13
+ *
14
+ */
15
+
16
+ import { defineConfig } from "vitest/config";
17
+ import { fileURLToPath } from "node:url";
18
+ import path from "node:path";
19
+
20
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
21
+
22
+ export default defineConfig({
23
+ root,
24
+ test: {
25
+ include: ["tests/**/*.test.vitest.mts"],
26
+ exclude: ["node_modules", "dist"],
27
+ environment: "node",
28
+ testTimeout: 30000,
29
+ reporters: ["dot"],
30
+ coverage: {
31
+ provider: "v8",
32
+ include: ["src/**/*.ts"],
33
+ // index.ts is the process entrypoint (arg parse + wire + listen);
34
+ // types.ts is type-only (elided at compile time — zero runtime code).
35
+ // Neither is unit-testable. server.ts (the stdio<->socket bridge) is
36
+ // deliberately NOT excluded — its logic is real and only partly covered,
37
+ // so it should show honestly rather than be hidden. Measured units are
38
+ // the pure logic: config, router/owner-routing, catalog, audit, secrets,
39
+ // verbs, upstream, appauth.
40
+ exclude: ["tests/**", "**/*.json", "src/index.ts", "src/types.ts"],
41
+ reporter: ["text", "html", "json-summary", "json"]
42
+ }
43
+ }
44
+ });
@@ -0,0 +1,111 @@
1
+ #
2
+ # @Project: @cldmv/gh-broker
3
+ # @Filename: /.github/dependabot.yml
4
+ # @Date: 2026-05-26T00:00:00-07:00 (1779778800)
5
+ # @Author: Nate Corcoran <CLDMV>
6
+ # @Email: <Shinrai@users.noreply.github.com>
7
+ # -----
8
+ # @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
9
+ # @Last modified time: 2026-10-02T11:30:57-07:00 (1790965857)
10
+ # -----
11
+ # @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
12
+ #
13
+
14
+ # Individual repo: .github/dependabot.yml
15
+ #
16
+ # Dependabot configuration tuned for the v4 staging-branch release flow.
17
+ #
18
+ # How it fits with v4:
19
+ # - All Dependabot PRs target `next` so they pool with every other
20
+ # contributor change and batch into the next release.
21
+ # - `dependabot-auto-merge.yml` (in this same folder) auto-merges
22
+ # patch/minor bumps into `next` after CI passes — zero-touch pooling.
23
+ # Delete that workflow if you'd rather review each bump by hand.
24
+ # - Security updates are detected by `hotfix-redirector.yml`
25
+ # (release-flow-v4/) by GHSA references in the PR body and auto-promoted
26
+ # from `next` → `hotfixes` so they ship via the hotfix lane, not the
27
+ # next-batch release. No special routing needed in this config.
28
+ #
29
+ # Customize per repo:
30
+ # - Add or remove `package-ecosystem` blocks for your stack (gomod, pip,
31
+ # bundler, gradle, maven, cargo, docker, etc.).
32
+ # - Adjust `directory` if your manifests don't live at the repo root.
33
+ # - Tighten `open-pull-requests-limit` if Dependabot's noise is too much.
34
+ # - Add `allow` / `ignore` rules for specific packages.
35
+ # - Add `groups` to bundle related bumps into a single PR.
36
+
37
+ version: 2
38
+ updates:
39
+ # GitHub Actions: keep pinned action SHAs / version tags fresh.
40
+ - package-ecosystem: "github-actions"
41
+ directory: "/"
42
+ target-branch: "next"
43
+ schedule:
44
+ interval: "weekly"
45
+ open-pull-requests-limit: 5
46
+ commit-message:
47
+ prefix: "deps"
48
+ # Grouped PRs cut noise: one PR per (security | patch | minor) bundle
49
+ # per week instead of N separate PRs. Security PRs still get retargeted
50
+ # to `hotfixes` by hotfix-redirector.yml when GHSA refs appear in the
51
+ # body — bundling N GHSA fixes into one PR is fine, the redirector
52
+ # only needs one match to retarget.
53
+ groups:
54
+ security:
55
+ applies-to: security-updates
56
+ patterns: ["*"]
57
+ patch:
58
+ applies-to: version-updates
59
+ update-types: ["patch"]
60
+ minor:
61
+ applies-to: version-updates
62
+ update-types: ["minor"]
63
+
64
+ # NPM: package.json + package-lock.json updates.
65
+ # Delete this block if your repo isn't a Node project.
66
+ - package-ecosystem: "npm"
67
+ directory: "/"
68
+ target-branch: "next"
69
+ schedule:
70
+ interval: "weekly"
71
+ open-pull-requests-limit: 5
72
+ commit-message:
73
+ prefix: "deps"
74
+ groups:
75
+ # vitest and @vitest/coverage-v8 (and other @vitest/* packages) peer
76
+ # each other EXACTLY, so a partial bump (e.g. vitest to 5.0.0 while
77
+ # @vitest/coverage-v8 stays 4.1.11) breaks `npm ci` with an ERESOLVE.
78
+ # Bump the whole family together in one PR so the exact-peer
79
+ # versions never diverge. Must come before security/patch/minor
80
+ # below — Dependabot assigns each update to the FIRST matching
81
+ # group, and this one has no applies-to restriction so it always
82
+ # wins for vitest-family packages regardless of update type.
83
+ vitest:
84
+ patterns:
85
+ - "vitest"
86
+ - "@vitest/*"
87
+ # @eslint/js peers eslint with a major-locked range ("^10.0.0"), so a
88
+ # future eslint major bump without @eslint/js moving too would hit
89
+ # the same npm ci ERESOLVE class as the vitest/coverage-v8 split.
90
+ # @cldmv/eslint-plugin-jsonv doesn't peer eslint directly but is
91
+ # bundled here to keep the lint-config family moving together.
92
+ eslint:
93
+ patterns:
94
+ - "eslint"
95
+ - "@eslint/*"
96
+ - "@cldmv/eslint-plugin-*"
97
+ # @cldmv/prettier-plugin-jsonv peers prettier with a major-locked
98
+ # range ("^3.0.0") — same reasoning as the eslint group above.
99
+ prettier:
100
+ patterns:
101
+ - "prettier"
102
+ - "@cldmv/prettier-plugin-*"
103
+ security:
104
+ applies-to: security-updates
105
+ patterns: ["*"]
106
+ patch:
107
+ applies-to: version-updates
108
+ update-types: ["patch"]
109
+ minor:
110
+ applies-to: version-updates
111
+ update-types: ["minor"]
@@ -0,0 +1,44 @@
1
+ #
2
+ # @Project: @cldmv/gh-broker
3
+ # @Filename: /.github/workflows/branch-retention.yml
4
+ # @Date: 2026-05-20T00:00:00-07:00 (1779260400)
5
+ # @Author: Nate Corcoran <CLDMV>
6
+ # @Email: <Shinrai@users.noreply.github.com>
7
+ # -----
8
+ # @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
9
+ # @Last modified time: 2026-10-02T11:30:57-07:00 (1790965857)
10
+ # -----
11
+ # @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
12
+ #
13
+
14
+ # Individual repo: .github/workflows/branch-retention.yml
15
+ #
16
+ # On PR merge: most branches deleted immediately; release/* keeps last 5,
17
+ # hotfix/* keeps last 3. master/main/badges/gh-pages never touched.
18
+ #
19
+ # v4 flow: feature PRs merge into `next` and hotfix PRs into `hotfixes`
20
+ # (not directly into master). next/hotfixes are in the branches: filter
21
+ # below so this workflow fires on those PR closures too — otherwise
22
+ # feat/* / fix/* / chore/* etc. would pile up on origin indefinitely.
23
+ # (Repos that haven't adopted v4 just won't see those branches; the
24
+ # extra entries in the filter are harmless.)
25
+ name: 🌿 Branch Retention
26
+
27
+ on:
28
+ pull_request:
29
+ types: [closed]
30
+ branches: [master, main, next, hotfixes]
31
+
32
+ permissions:
33
+ contents: read
34
+
35
+ jobs:
36
+ retain:
37
+ permissions:
38
+ contents: write
39
+ pull-requests: read
40
+ if: github.event.pull_request.merged == true
41
+ uses: CLDMV/.github/.github/workflows/reusable-branch-retention.yml@v4
42
+ secrets:
43
+ BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }}
44
+ BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }}
@@ -0,0 +1,52 @@
1
+ #
2
+ # @Project: @cldmv/gh-broker
3
+ # @Filename: /.github/workflows/bundle-size.yml
4
+ # @Date: 2026-05-20T00:00:00-07:00 (1779260400)
5
+ # @Author: Nate Corcoran <CLDMV>
6
+ # @Email: <Shinrai@users.noreply.github.com>
7
+ # -----
8
+ # @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
9
+ # @Last modified time: 2026-10-02T11:30:57-07:00 (1790965857)
10
+ # -----
11
+ # @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
12
+ #
13
+
14
+ # Individual repo: .github/workflows/bundle-size.yml
15
+ #
16
+ # Only relevant for runtime libraries where bundle size matters (e.g.
17
+ # @cldmv/slothlet). Skip this template for tool repos / meta repos.
18
+ #
19
+ # Fork-PR caveat: builds PR-supplied code, so we use `pull_request`
20
+ # (NOT pull_request_target). Fork builds run safely without secrets;
21
+ # comment posting fails for fork PRs because the token is read-only.
22
+ # Maintainer can run via workflow_dispatch after reviewing the code.
23
+ #
24
+ # Batch 5.4 from tmp/plan-future-workflows.md.
25
+ name: 📊 Bundle Size
26
+
27
+ on:
28
+ pull_request:
29
+ types: [opened, synchronize, reopened, ready_for_review]
30
+ branches: [master, main]
31
+ workflow_dispatch:
32
+
33
+ permissions:
34
+ contents: read
35
+ pull-requests: write
36
+
37
+ jobs:
38
+ diff:
39
+ uses: CLDMV/.github/.github/workflows/reusable-bundle-size.yml@v4
40
+ with:
41
+ build_command: "npm run build:ci"
42
+ dist_paths: "dist/**"
43
+ # warning_pct: 5
44
+ # warning_bytes: 500
45
+ # comment_mode: "update"
46
+ # Optional. Without these, the size-diff comment is posted by
47
+ # github-actions[bot]. With these, it's posted by your CLDMV bot App.
48
+ # Note: fork PRs can't access org secrets, so the bot attribution only
49
+ # applies to same-repo PRs; fork PRs fall back to GITHUB_TOKEN.
50
+ secrets:
51
+ BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }}
52
+ BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }}
@@ -0,0 +1,389 @@
1
+ #
2
+ # @Project: @cldmv/gh-broker
3
+ # @Filename: /.github/workflows/ci.yml
4
+ # @Date: 2026-05-20T00:00:00-07:00 (1779260400)
5
+ # @Author: Nate Corcoran <CLDMV>
6
+ # @Email: <Shinrai@users.noreply.github.com>
7
+ # -----
8
+ # @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
9
+ # @Last modified time: 2026-10-02T11:30:57-07:00 (1790965857)
10
+ # -----
11
+ # @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
12
+ #
13
+
14
+ # Individual repo: .github/workflows/ci.yml
15
+ name: 🧪 CI Tests & Build
16
+
17
+ on:
18
+ # Note: do NOT add `paths:` / `paths-ignore:` at the trigger level. Doing
19
+ # that makes GitHub skip the workflow entirely for docs-only changes, which
20
+ # means `Required PR Check` never posts and the ruleset blocks the merge.
21
+ # The reusable workflow's `paths-gate` job does the same job from inside,
22
+ # and exposes a `docs_only` output so this workflow can still green-light
23
+ # the required check for docs-only PRs (see `required-check` below). The
24
+ # ignore globs themselves are passed via the `paths_ignore:` input below
25
+ # — override there if your repo needs different rules.
26
+ #
27
+ # `push` fires for branches in this repo only (forks push to their own remote,
28
+ # not ours). Branch protection on the PR reads the status check from the
29
+ # commit SHA, so this single trigger covers both pre-PR pushes and PR head
30
+ # updates without duplicating runs.
31
+ push:
32
+ # Bot-managed branches (badges, gh-pages) carry no source to test.
33
+ branches-ignore: [badges, gh-pages]
34
+ # `pull_request` covers two cases:
35
+ # - Fork PRs (push doesn't fire upstream for fork commits).
36
+ # - Release PRs from `next` / `hotfixes` → `master`. Their head SHA is
37
+ # a bot `chore: bump version` commit that workflow-ci.yml's
38
+ # `commit-gate` job filters out on the push path, so without the
39
+ # pull_request fallback the release PR's `Required PR Check`
40
+ # status never gets posted and the ruleset blocks the merge.
41
+ # `branches:` includes the v4 integration branches so PRs targeting
42
+ # `next` / `hotfixes` get CI too — feature PRs from forks would
43
+ # otherwise get nothing. Non-fork feature PRs still skip the
44
+ # pull_request `ci` job (push covers them); see the `if:` on the job.
45
+ pull_request:
46
+ types: [opened, synchronize, reopened, ready_for_review]
47
+ branches: [master, main, next, hotfixes]
48
+ workflow_dispatch:
49
+ inputs:
50
+ debug:
51
+ description: "Enable debug logging for troubleshooting"
52
+ type: boolean
53
+ required: false
54
+ default: false
55
+ node_version:
56
+ description: "Node.js version to use (default: lts/*)"
57
+ type: string
58
+ required: false
59
+ default: "lts/*"
60
+ min_node_version:
61
+ description: "Minimum Node.js version for matrix testing (default: 22.12.0 — the floor vitest 5 actually runs on)"
62
+ type: string
63
+ required: false
64
+ default: "22.12.0"
65
+ max_node_major:
66
+ description: "Max Node.js major version for the test matrix. Leave blank (the default) to inherit the CLDMV/.github reusable workflow's default; set a value only to pin/override for a specific run."
67
+ type: string
68
+ required: false
69
+ default: ""
70
+ lts_only_matrix:
71
+ description: "Only include even-numbered (LTS) Node.js major versions in the test matrix"
72
+ type: boolean
73
+ required: false
74
+ default: true
75
+ package_manager:
76
+ description: "Package manager (npm or yarn)"
77
+ type: string
78
+ required: false
79
+ default: "npm"
80
+ test_environment:
81
+ description: "Environment for tests (affects NODE_ENV and NODE_OPTIONS --conditions flag)"
82
+ type: string
83
+ required: false
84
+ default: "development"
85
+ # ── Lint & format (autofix) — all default to `--if-present` (no-op when absent)
86
+ format_command:
87
+ description: "Formatter that writes fixes (prettier --write)"
88
+ type: string
89
+ required: false
90
+ default: "npm run format --if-present"
91
+ lint_fix_command:
92
+ description: "Linter that writes safe fixes (eslint --fix)"
93
+ type: string
94
+ required: false
95
+ default: "npm run lint:fix --if-present"
96
+ lint_command:
97
+ description: "Lint check (no writes) — the authoritative lint gate"
98
+ type: string
99
+ required: false
100
+ default: "npm run lint --if-present"
101
+ format_check_command:
102
+ description: "Format check (no writes) — used on fork / integration branches"
103
+ type: string
104
+ required: false
105
+ default: "npm run format:check --if-present"
106
+ # ── Coverage badge ───────────────────────────────────────────────
107
+ enable_coverage_badge:
108
+ description: "Run the coverage + badge-push job after CI passes"
109
+ type: boolean
110
+ required: false
111
+ default: true
112
+ coverage_command:
113
+ description: "Command to run tests and generate coverage data"
114
+ type: string
115
+ required: false
116
+ default: "npm run ci:coverage"
117
+ coverage_summary_path:
118
+ description: "Path to the coverage-summary.json produced by Jest / c8"
119
+ type: string
120
+ required: false
121
+ default: "coverage/coverage-summary.json"
122
+ badges_branch:
123
+ description: "Branch where the badge JSON is published"
124
+ type: string
125
+ required: false
126
+ default: "badges"
127
+ badge_filename:
128
+ description: "Filename for the badge JSON committed to the badges branch"
129
+ type: string
130
+ required: false
131
+ default: "coverage.json"
132
+ upload_coverage_artifact:
133
+ description: "Upload the full coverage/ directory as a workflow artifact"
134
+ type: boolean
135
+ required: false
136
+ default: true
137
+ # ── Type check ──────────────────────────────────────────────────
138
+ type_check_command:
139
+ description: "Command to run type checking"
140
+ type: string
141
+ required: false
142
+ default: "npm run test:types"
143
+ skip_type_check:
144
+ description: "Skip the type-check step in the coverage-badge job"
145
+ type: boolean
146
+ required: false
147
+ default: false
148
+ default_branch:
149
+ description: "Default branch name — badge is only pushed on pushes to this branch"
150
+ type: string
151
+ required: false
152
+ default: "master"
153
+ enable_coverage_pr_comment:
154
+ description: "Inject a coverage badge into the PR description on pull request events"
155
+ type: boolean
156
+ required: false
157
+ default: true
158
+
159
+ # Concurrency policy, by context:
160
+ # - FEATURE branches / feature PRs → cancel superseded runs (per-ref group +
161
+ # cancel-in-progress): a newer push makes the older run redundant.
162
+ # - RELEASE-relevant contexts → NEVER superseded. Pushes to the release base
163
+ # branch (derived: the CLDMV_RELEASE_BASE var → the repo's default branch),
164
+ # to next/hotfixes, and the next/hotfixes → base release PRs each get a
165
+ # UNIQUE group per run (run_id appended), so nothing cancels them. During
166
+ # the burst of pushes a release makes to next/hotfixes (the feature squash,
167
+ # the post-hotfix base→next sync merge, the bot's `chore: bump version`),
168
+ # every run completes and posts a GREEN check instead of the earlier one
169
+ # being cancelled into a red X on the release PR. A bare
170
+ # `cancel-in-progress: false` is NOT enough — GitHub still cancels the
171
+ # middle PENDING run when a newer one queues; a unique group avoids it.
172
+ # The base is NOT hardcoded to master/main — it derives the same way
173
+ # resolve-release-base does (CLDMV_RELEASE_BASE override → default_branch).
174
+ # next/hotfixes are the flow's fixed integration-branch names. github.head_ref
175
+ # is set only on pull_request (the release PR's head → next/hotfixes);
176
+ # github.ref carries the branch on push.
177
+ concurrency:
178
+ group: ci-${{ github.workflow }}-${{ github.ref }}${{ (github.ref == format('refs/heads/{0}', vars.CLDMV_RELEASE_BASE != '' && vars.CLDMV_RELEASE_BASE || github.event.repository.default_branch) || github.ref == 'refs/heads/next' || github.ref == 'refs/heads/hotfixes' || github.head_ref == 'next' || github.head_ref == 'hotfixes') && format('-{0}', github.run_id) || '' }}
179
+ cancel-in-progress: true
180
+
181
+ # Workflow-level: matches the broadest write surface the called
182
+ # `workflow-ci.yml` reaches across its branches:
183
+ # - coverage-badge: contents:write (push to `badges` branch)
184
+ # - coverage-pr-comment: pull-requests:write (edit PR description body)
185
+ # Jobs that don't need write (CI matrix, commit-gate, the mirror below)
186
+ # inherit but never exercise the surface. The mirror job overrides to
187
+ # `permissions: {}` since it's pure shell.
188
+ permissions:
189
+ contents: read
190
+
191
+ jobs:
192
+ ci:
193
+ permissions:
194
+ contents: write
195
+ pull-requests: write
196
+ name: 🏗️ Continuous Integration
197
+ # Run on pull_request when:
198
+ # - The PR is from a fork (push doesn't fire upstream for fork commits).
199
+ # - The PR is a v4 release PR — head ref is `next` or `hotfixes`
200
+ # targeting `master`/`main`. Push-event CI on the head SHA is
201
+ # unreliable for these because workflow-ci.yml's `commit-gate`
202
+ # filters out the bot's `chore: bump version` commit, so without
203
+ # this fallback the release PR's `Required PR Check` never posts.
204
+ # Other (in-repo, non-release) PRs skip — the push event on the head
205
+ # branch already ran CI and posted status to the SHA.
206
+ if: |
207
+ github.event_name != 'pull_request' ||
208
+ github.event.pull_request.head.repo.fork == true ||
209
+ github.event.pull_request.head.ref == 'next' ||
210
+ github.event.pull_request.head.ref == 'hotfixes'
211
+ uses: CLDMV/.github/.github/workflows/workflow-ci.yml@v4
212
+ with:
213
+ package_name: "@cldmv/gh-broker" # Required: replace with your NPM package name
214
+ # Globs that should NOT trigger the heavy CI matrix. When every changed
215
+ # file matches one of these, `docs_only=true` flows out of the reusable
216
+ # and `required-check` below posts a green Required PR Check without
217
+ # running CI. The default in the reusable matches these — override only
218
+ # if your repo needs different rules.
219
+ paths_ignore: |
220
+ **.md
221
+ docs/**
222
+ *.md
223
+ LICENSE
224
+ .gitignore
225
+ debug: ${{ github.event.inputs.debug == 'true' }}
226
+ node_version: ${{ github.event.inputs.node_version || 'lts/*' }}
227
+ min_node_version: ${{ github.event.inputs.min_node_version || '22.12.0' }}
228
+ max_node_major: ${{ github.event.inputs.max_node_major || '' }} # blank ⇒ inherit the CLDMV/.github reusable default
229
+ # LTS-only matrix (even majors: 20, 22, 24, …) on every event. Odd majors
230
+ # (21, 23, …) are non-LTS interim releases, and the native-binding test
231
+ # toolchain (vitest 4 / rolldown / vite 8) excludes them via `engines`
232
+ # (`^20.19.0 || >=22.12.0`), so a "full matrix" on them only re-discovers a
233
+ # known toolchain gap ("Cannot find native binding") rather than a real
234
+ # per-version regression. workflow_dispatch can still opt out (set false).
235
+ lts_only_matrix: ${{ github.event.inputs.lts_only_matrix != 'false' }}
236
+ package_manager: ${{ github.event.inputs.package_manager || 'npm' }}
237
+ test_command: "npm test" # Use defaults: NODE_ENV=development, NODE_OPTIONS=--conditions=development
238
+ # test_command: "NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override NODE_OPTIONS only
239
+ # test_command: "NODE_ENV=test npm test" # Override NODE_ENV only
240
+ # test_command: "NODE_ENV=test NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override both
241
+ test_environment: ${{ github.event.inputs.test_environment || 'development' }} # Alternative to setting in test_command
242
+ # ── Lint & format (autofix gate) ──────────────────────────────────
243
+ # The lint-format job applies safe fixes (prettier --write, eslint --fix)
244
+ # and commits them back on internal branches; on fork PRs / integration
245
+ # branches it runs the check variants and fails with a fix hint. Every
246
+ # command uses `--if-present`, so a repo with no such script is a graceful
247
+ # no-op. Add the scripts to package.json to opt in; override a command for
248
+ # a monorepo-aware invocation; or set empty ("") to disable that phase.
249
+ format_command: ${{ github.event.inputs.format_command || 'npm run format --if-present' }}
250
+ lint_fix_command: ${{ github.event.inputs.lint_fix_command || 'npm run lint:fix --if-present' }}
251
+ lint_command: ${{ github.event.inputs.lint_command || 'npm run lint --if-present' }}
252
+ format_check_command: ${{ github.event.inputs.format_check_command || 'npm run format:check --if-present' }}
253
+ build_command: "npm run build:ci"
254
+ skip_performance_tests: false
255
+ skip_matrix_tests: false
256
+
257
+ # ── Coverage badge ─────────────────────────────────────────────────────
258
+ # Runs after a successful CI build; pushes a Shields.io-compatible badge
259
+ # JSON to the `badges` branch (signed commit via bot GPG).
260
+ # Only runs on direct pushes to default_branch — PRs and feature branches
261
+ # are automatically skipped so coverage always reflects merged master code.
262
+ # Requires: the coverage_command produces coverage/coverage-summary.json
263
+ enable_coverage_badge: ${{ github.event.inputs.enable_coverage_badge != 'false' }}
264
+ default_branch: ${{ github.event.inputs.default_branch || 'master' }} # Badge only pushed when a push lands on this branch
265
+ coverage_command: ${{ github.event.inputs.coverage_command || 'npm run ci:coverage' }}
266
+ coverage_summary_path: ${{ github.event.inputs.coverage_summary_path || 'coverage/coverage-summary.json' }}
267
+ badges_branch: ${{ github.event.inputs.badges_branch || 'badges' }}
268
+ badge_filename: ${{ github.event.inputs.badge_filename || 'coverage.json' }}
269
+ upload_coverage_artifact: ${{ github.event.inputs.upload_coverage_artifact != 'false' }}
270
+
271
+ # ── Type check (runs inside the coverage-badge job) ────────────────────
272
+ # gh-broker is a TypeScript project, so the type-check runs. `test:types`
273
+ # is `tsc -p tsconfig.json --noEmit` — the same check `npm run build` runs
274
+ # before esbuild bundles.
275
+ type_check_command: ${{ github.event.inputs.type_check_command || 'npm run test:types' }}
276
+ skip_type_check: ${{ github.event.inputs.skip_type_check == 'true' }}
277
+
278
+ # ── PR coverage badge ─────────────────────────────────────────────────
279
+ # Injects a Shields.io badge + breakdown table directly into the PR body
280
+ # on every push to the PR branch. Only fires on pull_request events;
281
+ # skipped automatically on push and workflow_dispatch. No files committed.
282
+ enable_coverage_pr_comment: ${{ github.event.inputs.enable_coverage_pr_comment != 'false' }}
283
+
284
+ # Authentication & Bot Configuration
285
+ # The workflow supports automatic App token detection for enhanced permissions and proper attribution:
286
+ # - WITH App secrets: Operations attributed to CLDMV bot, enhanced permissions for workflow repositories
287
+ # - WITHOUT App secrets: Falls back to GitHub Actions bot with standard permissions
288
+ # Note: CI workflow currently only runs build/test jobs, but App secrets are included for consistency
289
+ # To set up App authentication, add these secrets to your repository settings:
290
+ secrets:
291
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
292
+ # Optional: CLDMV Bot credentials for enhanced permissions and proper attribution
293
+ # If not provided, will use default GITHUB_TOKEN with GitHub Actions bot attribution
294
+ BOT_APP_CLIENT_ID: ${{ secrets.CLDMV_BOT_APP_CLIENT_ID }}
295
+ BOT_APP_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_APP_PRIVATE_KEY }}
296
+ # Required when enable_coverage_badge: true
297
+ BOT_NAME: ${{ secrets.CLDMV_BOT_NAME }}
298
+ BOT_EMAIL: ${{ secrets.CLDMV_BOT_EMAIL }}
299
+ BOT_GPG_PRIVATE_KEY: ${{ secrets.CLDMV_BOT_GPG_PRIVATE_KEY }}
300
+ BOT_GPG_PASSPHRASE: ${{ secrets.CLDMV_BOT_GPG_PASSPHRASE }}
301
+
302
+ # ✅ Stable check that mirrors the `ci` result so branch protection has a
303
+ # single, predictable status name to require. The push event runs on the
304
+ # same SHA that becomes the PR head, so the status attaches to the PR
305
+ # automatically — no `pull_request` round-trip needed for non-fork
306
+ # non-release PRs.
307
+ required-check:
308
+ # Which name this job reports under is the whole point of it.
309
+ #
310
+ # The ruleset gates merges on `✅ Required PR Check`, so a check with that
311
+ # name must only ever exist on a head SHA after the full test matrix for
312
+ # that SHA has finished, mirroring its result. `needs: ci` guarantees the
313
+ # timing: the job is only created once every Build & Test leg and the
314
+ # coverage job are done.
315
+ #
316
+ # On an in-repo feature PR the `pull_request` run does not own the status
317
+ # (the push run on the head branch does), so it must not post
318
+ # `✅ Required PR Check` at all. Two traps rule out the obvious shapes:
319
+ # - A job SKIPPED by `if:` still posts a check run under its name, and
320
+ # GitHub treats a skipped required check as satisfied. Under the real
321
+ # name that let PRs merge mid-test (CLDMV/slothlet#553).
322
+ # - GitHub does not evaluate the `name:` of a skipped job, so a
323
+ # conditional name on a skippable job shows up as the raw expression
324
+ # text (#350).
325
+ # So the job never skips: it runs on every path, the name expression is
326
+ # always evaluated, and the in-repo PR path lands on a readable,
327
+ # non-required name and passes as a no-op. The condition below is
328
+ # repeated in the step's OWNS_STATUS and must stay identical. The paths
329
+ # that own the status:
330
+ # 1. push events (job needs CI run)
331
+ # 2. fork PRs (push doesn't cover forks)
332
+ # 3. release PRs from `next` → master/main (push covers SHA but commit-gate skips chore-bump)
333
+ # 4. release PRs from `hotfixes` → master/main (same reason)
334
+ name: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == true || github.event.pull_request.head.ref == 'next' || github.event.pull_request.head.ref == 'hotfixes') && '✅ Required PR Check' || '⏭️ Required PR Check (reported by the push run)' }}
335
+ needs: ci
336
+ # always(): run even when `ci` is skipped (the in-repo PR path) or failed
337
+ # (so the mirror can report red).
338
+ if: always()
339
+ # Match the reusable's runner routing (workflow-ci.yml): private CLDMV
340
+ # repos run on self-hosted cldmv-runners (GitHub-hosted Actions budget is
341
+ # private-metered and exhausted), public repos use free GitHub-hosted, and
342
+ # RUNS_ON_DEFAULT overrides. Hardcoding ubuntu-latest here made this
343
+ # required check fail to provision a runner on private repos once the
344
+ # reusable moved its own jobs to cldmv-runners in v4.19.1 (see #208).
345
+ runs-on: ${{ vars.RUNS_ON_DEFAULT != '' && vars.RUNS_ON_DEFAULT || ((github.repository_owner == 'CLDMV' && github.event.repository.private) && 'cldmv-runners' || 'ubuntu-latest') }}
346
+ # Pure shell mirror — no GitHub API access. Strip the workflow's
347
+ # write defaults to zero for this job.
348
+ permissions: {}
349
+ steps:
350
+ - name: Mirror reusable result
351
+ env:
352
+ OWNS_STATUS: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == true || github.event.pull_request.head.ref == 'next' || github.event.pull_request.head.ref == 'hotfixes' }}
353
+ IS_MASTER_SYNC: ${{ needs.ci.outputs.is_master_sync }}
354
+ DOCS_ONLY: ${{ needs.ci.outputs.docs_only }}
355
+ CI_RESULT: ${{ needs.ci.result }}
356
+ run: |
357
+ # In-repo feature PR: the push run on the head branch reports
358
+ # `✅ Required PR Check`. This job runs under the
359
+ # `⏭️ Required PR Check (reported by the push run)` name and
360
+ # must not gate anything.
361
+ if [ "$OWNS_STATUS" != "true" ]; then
362
+ echo "In-repo PR event — the push run reports ✅ Required PR Check for this SHA."
363
+ exit 0
364
+ fi
365
+ echo "ci.result=$CI_RESULT docs_only=$DOCS_ONLY is_master_sync=$IS_MASTER_SYNC"
366
+ # next/hotfixes was force-synced to master — head SHA matches the
367
+ # default branch, nothing new to test, green-light without running CI.
368
+ if [ "$IS_MASTER_SYNC" = "true" ]; then
369
+ echo "Branch tip matches master — Required PR Check passes without running CI."
370
+ exit 0
371
+ fi
372
+ # Docs-only PR — the reusable skipped the heavy chain and exported
373
+ # docs_only=true. Green-light Required PR Check so the ruleset
374
+ # doesn't block a docs change.
375
+ if [ "$DOCS_ONLY" = "true" ]; then
376
+ echo "Docs-only change — Required PR Check passes without running CI."
377
+ exit 0
378
+ fi
379
+ if [ "$CI_RESULT" = "success" ]; then
380
+ echo "Reusable CI passed."
381
+ exit 0
382
+ elif [ "$CI_RESULT" = "failure" ] || [ "$CI_RESULT" = "cancelled" ]; then
383
+ echo "Reusable CI did not pass."
384
+ exit 1
385
+ else
386
+ # covers 'skipped' or undefined; force red to avoid silent green
387
+ echo "Reusable CI produced no pass/fail; treating as failure."
388
+ exit 1
389
+ fi