code-foundry 1.9.5 → 1.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/CONTRIBUTING.md +27 -33
- package/.github/SECURITY.md +1 -2
- package/.github/actions/setup/action.yml +2 -1
- package/.github/code-foundry.yml +1 -3
- package/.github/workflows/ci.yml +8 -8
- package/.github/workflows/cloudflare-deploy.yml +33 -0
- package/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/draft-control_self-ci.yml +44 -0
- package/.github/workflows/opencode-security_self-ci.yml +6 -14
- package/.github/workflows/release.yml +8 -4
- package/.github/workflows/release_self-ci.yml +0 -1
- package/.github/workflows/security.yml +12 -9
- package/.github/workflows/test.yml +10 -10
- package/.github/workflows/validation.yml +7 -6
- package/.github/workflows/validation_self-ci.yml +4 -9
- package/AGENTS.md +3 -3
- package/CHANGELOG.md +15 -0
- package/README.md +6 -4
- package/docs/CONFIGURATION.md +13 -11
- package/docs/RELEASES.md +2 -2
- package/docs/WORKFLOWS.md +23 -21
- package/package.json +1 -1
- package/src/commands/doctor.mjs +29 -8
- package/src/commands/sync.mjs +76 -27
- package/src/lib/github-doctor.mjs +50 -10
- package/src/lib/validation-policy.mjs +7 -9
|
@@ -4,11 +4,7 @@ on:
|
|
|
4
4
|
pull_request:
|
|
5
5
|
branches: [main, staging]
|
|
6
6
|
types:
|
|
7
|
-
- opened
|
|
8
|
-
- synchronize
|
|
9
|
-
- reopened
|
|
10
7
|
- ready_for_review
|
|
11
|
-
- converted_to_draft
|
|
12
8
|
|
|
13
9
|
# Default every caller job to no repository permissions. Individual jobs grant
|
|
14
10
|
# only the scopes their own validation path consumes.
|
|
@@ -22,10 +18,9 @@ concurrency:
|
|
|
22
18
|
jobs:
|
|
23
19
|
mode:
|
|
24
20
|
name: Mode
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
if: vars.CI_BILLING_PAUSED != 'true' && github.event.pull_request.draft == false
|
|
21
|
+
# The workflow is emitted only for ready_for_review, so draft pull requests
|
|
22
|
+
# register no skipped validation checks and allocate no validation runner.
|
|
23
|
+
if: vars.CI_BILLING_PAUSED != 'true'
|
|
29
24
|
runs-on: ubuntu-slim
|
|
30
25
|
timeout-minutes: 10
|
|
31
26
|
permissions:
|
|
@@ -64,7 +59,7 @@ jobs:
|
|
|
64
59
|
validation:
|
|
65
60
|
name: Validation
|
|
66
61
|
needs: mode
|
|
67
|
-
if: vars.CI_BILLING_PAUSED != 'true'
|
|
62
|
+
if: vars.CI_BILLING_PAUSED != 'true'
|
|
68
63
|
# Reusable workflows can only maintain or reduce the caller job's scopes.
|
|
69
64
|
# The audit tier needs security-events: write for CodeQL uploads.
|
|
70
65
|
permissions:
|
package/AGENTS.md
CHANGED
|
@@ -71,11 +71,11 @@ Ask for clarification when a missing decision would materially change the implem
|
|
|
71
71
|
8. Run the broadest applicable validation available.
|
|
72
72
|
9. Report what changed, exact checks and results, skipped checks with reasons, risks, and remaining work.
|
|
73
73
|
|
|
74
|
-
For normal feature work, branch from `
|
|
74
|
+
For normal feature work, branch from `main` and target pull requests at `main`. Treat `main` as the protected release branch. Follow `.github/CONTRIBUTING.md` for the complete internal and external contribution flow.
|
|
75
75
|
|
|
76
76
|
## Git workflow and merging
|
|
77
77
|
|
|
78
|
-
This repository uses the `
|
|
78
|
+
This repository uses the `direct` workflow: topic branches **squash** directly into `main`, and the Release Please version PR **squashes** into `main` (`release_merge_strategy: squash`). Feature and release PRs land on `main` with squash merges. No integration branch exists; all pull requests target `main`.
|
|
79
79
|
|
|
80
80
|
Merge only with the repository's canonical method. Never merge with `--admin`, never default or auto-select a merge method, and never use a method the branch ruleset does not allow. When in doubt, prefer the merge button's configured method and verify the ruleset after merging. Check `.github/CONTRIBUTING.md` for the complete flow and merge table.
|
|
81
81
|
|
|
@@ -149,7 +149,7 @@ If a check cannot run, state the exact reason. A skipped check is not a passing
|
|
|
149
149
|
## GitHub workflows and configuration
|
|
150
150
|
|
|
151
151
|
- Keep workflows concise, independently runnable, and safe to re-run.
|
|
152
|
-
- Use `push` for `main
|
|
152
|
+
- Use `push` for `main` and `pull_request` for `main` unless a workflow has a documented event-specific reason.
|
|
153
153
|
- Give workflows clear names and jobs concise names; avoid repeating the workflow name in the job name.
|
|
154
154
|
- Use per-workflow concurrency groups that cancel superseded runs while allowing independent workflows to run in parallel.
|
|
155
155
|
- Keep setup language-aware and cache dependency downloads by lockfile; do not cache secrets, `node_modules`, virtual environments, or broad build output without a measured reason.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.9.7](https://github.com/0xPlayerOne/code-foundry/compare/v1.9.6...v1.9.7) (2026-09-08)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **ci:** run full validation on release PRs ([#517](https://github.com/0xPlayerOne/code-foundry/issues/517)) ([01a3555](https://github.com/0xPlayerOne/code-foundry/commit/01a355505d6372dc26e2d917e0e6d566ab25546c))
|
|
9
|
+
* **sync:** remove direct topology staging residue ([#515](https://github.com/0xPlayerOne/code-foundry/issues/515)) ([2765e6a](https://github.com/0xPlayerOne/code-foundry/commit/2765e6a6f66137d37f3d65f8eb8311ba28eeafd7))
|
|
10
|
+
|
|
11
|
+
## [1.9.6](https://github.com/0xPlayerOne/code-foundry/compare/v1.9.5...v1.9.6) (2026-09-08)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* **ci:** finalize direct fleet policy ([#513](https://github.com/0xPlayerOne/code-foundry/issues/513)) ([98c59a7](https://github.com/0xPlayerOne/code-foundry/commit/98c59a713f31d0bc5e3b9893434d89dba3257550))
|
|
17
|
+
|
|
3
18
|
## [1.9.5](https://github.com/0xPlayerOne/code-foundry/compare/v1.9.4...v1.9.5) (2026-09-08)
|
|
4
19
|
|
|
5
20
|
|
package/README.md
CHANGED
|
@@ -75,9 +75,10 @@ The standard workflow triggers are:
|
|
|
75
75
|
- Draft PR automation for supported feature/fix branches.
|
|
76
76
|
|
|
77
77
|
Automated feature/fix and staging-promotion pull requests open as drafts.
|
|
78
|
-
Pull-request validation
|
|
79
|
-
commits
|
|
80
|
-
|
|
78
|
+
Pull-request validation runs on the ready-for-review transition. After later
|
|
79
|
+
commits, convert the PR to draft and mark it ready again so checks attach to
|
|
80
|
+
the current head. Converting a PR to draft runs only the lightweight
|
|
81
|
+
cancellation control. Scheduled and manually dispatched audits are unaffected.
|
|
81
82
|
|
|
82
83
|
Jobs are language-aware and skip irrelevant setup inside the applicable
|
|
83
84
|
aggregate checks. TypeScript uses Oxlint, Oxfmt, and Bun's native
|
|
@@ -102,7 +103,8 @@ checks, runners, coverage, caching, and custom workflow extensions.
|
|
|
102
103
|
|
|
103
104
|
The contribution policy defaults to the `direct` workflow: feature PRs squash
|
|
104
105
|
into `main`, and Release Please version PRs use the configured
|
|
105
|
-
`release_merge_strategy` (
|
|
106
|
+
`release_merge_strategy` (squash for direct repositories; rebase for the
|
|
107
|
+
optional staging-release topology).
|
|
106
108
|
Repositories with a preview/staging environment opt into
|
|
107
109
|
`git_workflow: staging-release`, where feature PRs squash into `staging`, the
|
|
108
110
|
promotion PR rebases into `main` (`merge_strategy: rebase`), and Release Please
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -35,7 +35,7 @@ repository manifests and source
|
|
|
35
35
|
| `languages` | detected list | TypeScript, Rust, Python, Solidity |
|
|
36
36
|
| `package_manager` | `bun`, `pnpm`, `yarn`, `npm`, `none` | JavaScript setup |
|
|
37
37
|
| `toolchain` | `auto`, `native`, `mise` | Environment setup policy; defaults to `auto` |
|
|
38
|
-
| `staging_validation_mode` | `fast`, `audit` |
|
|
38
|
+
| `staging_validation_mode` | `fast`, `audit` | Staging-release-only validation tier; omitted from direct repositories |
|
|
39
39
|
| `performance` | `auto`, `true`, `false` | Run a deterministic performance task when a supported entrypoint exists; defaults to `auto` |
|
|
40
40
|
| `performance_command` | JSON argv array or array of argv arrays | One or more ordered commands for non-package harnesses; package scripts take precedence |
|
|
41
41
|
| `performance_profile` | empty, `node-package` | Optional shared package import, memory, archive, and dependency budget harness |
|
|
@@ -53,8 +53,8 @@ repository manifests and source
|
|
|
53
53
|
| `npm_publish` | `true` or `false` | Opt into npm publication |
|
|
54
54
|
| `license` | `gpl-3.0-or-later`, `agpl-3.0-or-later`, `apache-2.0`, `mit`, `preserve`, `none` | License policy; new repositories default to GPLv3 |
|
|
55
55
|
| `git_workflow` | `direct` (default), `staging-release` | Branch/release model; `direct` opens feature branches into `main`, `staging-release` promotes `staging` into `main` |
|
|
56
|
-
| `merge_strategy` | `
|
|
57
|
-
| `release_merge_strategy` | `
|
|
56
|
+
| `merge_strategy` | `squash` (direct), `rebase` (staging-release) | Feature/promotion merge policy; direct repositories require squash |
|
|
57
|
+
| `release_merge_strategy` | `squash` (direct), `rebase` (staging-release) | Merge method for Release Please version PRs into `main`; release automation fails closed on anything else |
|
|
58
58
|
| `runner` fields | GitHub runner names | Per-workflow runner policy, including `performance_runner` |
|
|
59
59
|
|
|
60
60
|
Supported features are `ci`, `codeql`, `security`, `test`, `draft-pr`,
|
|
@@ -113,14 +113,13 @@ checks should remain separate from this deterministic validation task.
|
|
|
113
113
|
|
|
114
114
|
## OpenCode Security opt-in and opt-out
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
repository without a configuration change or a new secret:
|
|
116
|
+
The generated OpenCode caller ships in every repository. The
|
|
117
|
+
`OPENCODE_SECURITY` repository or organization variable is its only enablement
|
|
118
|
+
control, so a scan can be toggled without a code change:
|
|
120
119
|
|
|
121
120
|
- `OPENCODE_SECURITY: true` opts the repository in.
|
|
122
121
|
- `OPENCODE_SECURITY: false` opts the repository out.
|
|
123
|
-
- unset
|
|
122
|
+
- unset is disabled.
|
|
124
123
|
|
|
125
124
|
The scan only runs when it is enabled and the `OPENCODE_API_KEY` secret is
|
|
126
125
|
present.
|
|
@@ -131,8 +130,8 @@ present.
|
|
|
131
130
|
|
|
132
131
|
- `direct` (default): feature branches open pull requests directly into
|
|
133
132
|
`main`. Validation and security scans run on every PR. No `staging` branch
|
|
134
|
-
exists, no promotion caller is generated, and `merge_strategy`
|
|
135
|
-
|
|
133
|
+
exists, no promotion caller is generated, and `merge_strategy` must be
|
|
134
|
+
`squash`. Release Please version PRs squash into `main`
|
|
136
135
|
(`release_merge_strategy: squash`). Dependabot updates target `main`. This is
|
|
137
136
|
the right choice when a repository has no preview or staging environment.
|
|
138
137
|
- `staging-release` (opt-in): feature branches squash into `staging`, a
|
|
@@ -181,4 +180,7 @@ Vercel's integration) by adding a small caller for the runtime's reusable
|
|
|
181
180
|
for pull-request previews and `wrangler deploy` for production, records a
|
|
182
181
|
GitHub deployment plus status with the workers.dev URL, and respects
|
|
183
182
|
`CI_BILLING_PAUSED`. It requires the `CLOUDFLARE_API_TOKEN` and
|
|
184
|
-
`CLOUDFLARE_ACCOUNT_ID` secrets in the consumer repository.
|
|
183
|
+
`CLOUDFLARE_ACCOUNT_ID` secrets in the consumer repository. Bun consumers may
|
|
184
|
+
also pass `build-script`, `install-working-directory`, and `bun-version`; the
|
|
185
|
+
runtime installs the frozen lockfile and builds the Worker before invoking
|
|
186
|
+
Wrangler.
|
package/docs/RELEASES.md
CHANGED
|
@@ -27,7 +27,7 @@ rebase`), and Release Please version PRs merge with **rebase**
|
|
|
27
27
|
branches squash straight into `main` and Release Please version PRs squash into
|
|
28
28
|
`main` (`release_merge_strategy: squash`). `merge_strategy` is not enforced. Release automation never defaults
|
|
29
29
|
to a merge method and never merges with `--admin`: `staging-release` accepts
|
|
30
|
-
only rebase for release PRs, while `direct`
|
|
30
|
+
only rebase for release PRs, while `direct` requires squash.
|
|
31
31
|
|
|
32
32
|
The release workflow opens or updates a versioned PR after changes reach
|
|
33
33
|
`main`. Merging that PR updates the changelog, creates the Git tag and GitHub
|
|
@@ -54,7 +54,7 @@ selected, `merge_strategy` applies to promotion PRs (`staging` into `main`)
|
|
|
54
54
|
and `release_merge_strategy` to Release Please version PRs; feature PRs into
|
|
55
55
|
`staging` use squash merges. `code-foundry doctor`, `code-foundry sync`, and
|
|
56
56
|
the release workflow reject any non-`rebase` `merge_strategy` or release
|
|
57
|
-
strategy in `staging-release`; `direct`
|
|
57
|
+
strategy in `staging-release`; `direct` requires `squash` for
|
|
58
58
|
release PRs and never falls back to `merge`. Both keep `main` fully linear,
|
|
59
59
|
which is what makes the post-release reconciliation possible. The final
|
|
60
60
|
branch trees are inspected before mutation; validated main-only changes are
|
package/docs/WORKFLOWS.md
CHANGED
|
@@ -12,10 +12,12 @@ pull_request:
|
|
|
12
12
|
# direct topology: branches: [main]
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
The generated caller
|
|
16
|
-
pull
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
The generated validation caller listens only for `ready_for_review`, so draft
|
|
16
|
+
pull requests register no validation checks and allocate no validation runner.
|
|
17
|
+
After any later update, convert the pull request to draft and mark it ready
|
|
18
|
+
again so required checks attach to the current head. A separate lightweight
|
|
19
|
+
draft-control caller listens for `converted_to_draft` and cancels queued or
|
|
20
|
+
running pull-request workflows without creating skipped validation jobs.
|
|
19
21
|
|
|
20
22
|
The separate `validation-audit.yml` caller is pinned to the configured released
|
|
21
23
|
runtime and handles scheduled and manual audits:
|
|
@@ -39,7 +41,7 @@ opens those PRs as drafts. Promotion automation listens to `staging` pushes
|
|
|
39
41
|
(staging-release only) and also always opens its PR as a draft. Release
|
|
40
42
|
automation listens to `main` pushes.
|
|
41
43
|
Custom deployment, indexing, search, Slither, or other workflows are
|
|
42
|
-
repository-owned extensions and should
|
|
44
|
+
repository-owned extensions and should use the same ready-transition policy.
|
|
43
45
|
|
|
44
46
|
## Billing pause
|
|
45
47
|
|
|
@@ -83,9 +85,9 @@ throughout the bounded release flow.
|
|
|
83
85
|
Custom workflows are repository-owned and are not rewritten by sync. Add
|
|
84
86
|
`if: vars.CI_BILLING_PAUSED != 'true'` to each custom root job that should
|
|
85
87
|
honor the shared billing pause. The optional OpenCode Security scan honors a
|
|
86
|
-
second toggle: the `OPENCODE_SECURITY` repository variable
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
second toggle: the `OPENCODE_SECURITY` repository or organization variable
|
|
89
|
+
(`true`/`false`) is its only enablement control, so individual repositories can
|
|
90
|
+
opt in or out without a code change.
|
|
89
91
|
|
|
90
92
|
## Standard workflow responsibilities
|
|
91
93
|
|
|
@@ -114,20 +116,20 @@ succeeds.
|
|
|
114
116
|
|
|
115
117
|
The merge audit pins one merge method per transition. `code-foundry doctor`
|
|
116
118
|
and `code-foundry sync` validate release strategy against the repository
|
|
117
|
-
topology.
|
|
118
|
-
|
|
119
|
-
strategy and never falls back to `merge`.
|
|
119
|
+
topology. Direct feature and release PRs require `squash`; staging-release
|
|
120
|
+
promotion and release PRs require `rebase`. The release workflow fails closed
|
|
121
|
+
on any other strategy and never falls back to `merge`.
|
|
120
122
|
|
|
121
|
-
| Transition | Merge method
|
|
122
|
-
| ---------------------------------------------------------- |
|
|
123
|
-
| Feature/fix PR into `main` (direct topology) | Squash
|
|
124
|
-
| Feature/fix PR into `staging` (staging-release topology) | Squash
|
|
125
|
-
| `staging` → `main` promotion PR (staging-release topology) | Rebase (`merge_strategy: rebase`)
|
|
126
|
-
| Release Please version PR into `main` |
|
|
123
|
+
| Transition | Merge method | Enforcement |
|
|
124
|
+
| ---------------------------------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
125
|
+
| Feature/fix PR into `main` (direct topology) | Squash | Contribution policy; see `CONTRIBUTING.md` |
|
|
126
|
+
| Feature/fix PR into `staging` (staging-release topology) | Squash | Contribution policy; see `CONTRIBUTING.md` |
|
|
127
|
+
| `staging` → `main` promotion PR (staging-release topology) | Rebase (`merge_strategy: rebase`) | Code Foundry creates a one-commit head with `main` as its parent and the exact validated `staging` tree; `merge_strategy` must be `rebase`, and merge commits are rejected |
|
|
128
|
+
| Release Please version PR into `main` | Squash (direct) or rebase (staging-release) | Release automation fails closed on unsupported topology/strategy; never defaults to `merge`, never uses `--admin` |
|
|
127
129
|
|
|
128
130
|
The promotion rows above apply only to `staging-release`; `direct`
|
|
129
|
-
repositories never generate a promotion caller and `merge_strategy
|
|
130
|
-
|
|
131
|
+
repositories never generate a promotion caller and require `merge_strategy:
|
|
132
|
+
squash` for feature pull requests.
|
|
131
133
|
|
|
132
134
|
Release auto-merge waits for required checks and then polls `mergeStateStatus`
|
|
133
135
|
until it is `CLEAN`, or `UNSTABLE` with `mergeable` `MERGEABLE`, before
|
|
@@ -139,7 +141,7 @@ merge policy-blocked. The mergeability poll is bounded and fails closed on
|
|
|
139
141
|
conflicts or timeout; releases without an automation token remain manual.
|
|
140
142
|
|
|
141
143
|
Keeping `main` linear — rebase promotions and release PRs in the
|
|
142
|
-
`staging-release` topology, or a single-commit
|
|
144
|
+
`staging-release` topology, or a single-commit squash release PR in the
|
|
143
145
|
`direct` topology — is what lets the post-release reconciliation fast-forward
|
|
144
146
|
or replay `staging` safely. `direct` repositories have no reconciliation step:
|
|
145
147
|
releases merge straight into `main` with the configured
|
|
@@ -147,7 +149,7 @@ releases merge straight into `main` with the configured
|
|
|
147
149
|
|
|
148
150
|
Protect `main` with the aggregate `Validation / Gate`. Require squash for
|
|
149
151
|
feature/fix pull requests and permit the configured Release Please method:
|
|
150
|
-
rebase in `staging-release`, or
|
|
152
|
+
rebase in `staging-release`, or squash in `direct`. In the
|
|
151
153
|
`staging-release` topology, protect `staging` the same way with a single GitHub
|
|
152
154
|
Actions integration path. That path uses the
|
|
153
155
|
GitHub Actions integration token by default, and optionally an SSH deploy key
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-foundry",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.7",
|
|
4
4
|
"description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
|
|
5
5
|
"homepage": "https://github.com/0xPlayerOne/code-foundry#readme",
|
|
6
6
|
"bugs": {
|
package/src/commands/doctor.mjs
CHANGED
|
@@ -116,30 +116,45 @@ export function doctor(root, options = {}) {
|
|
|
116
116
|
error(`unsupported git_workflow: ${workflow}; use direct or staging-release`)
|
|
117
117
|
}
|
|
118
118
|
const mergeStrategy = config.merge_strategy ?? 'rebase'
|
|
119
|
+
if (workflow === 'direct' && mergeStrategy !== 'squash') {
|
|
120
|
+
error(
|
|
121
|
+
`merge_strategy must be "squash" for direct feature pull requests; got "${mergeStrategy}".`
|
|
122
|
+
)
|
|
123
|
+
}
|
|
119
124
|
if (workflow === 'staging-release' && mergeStrategy !== 'rebase') {
|
|
120
125
|
error(
|
|
121
126
|
`merge_strategy must be "rebase" for the staging-release promotion topology; got "${mergeStrategy}".`
|
|
122
127
|
)
|
|
123
128
|
}
|
|
124
129
|
const releaseMergeStrategy = config.release_merge_strategy ?? ''
|
|
125
|
-
const allowedReleaseStrategies =
|
|
126
|
-
workflow === 'staging-release' ? ['rebase'] : ['rebase', 'squash']
|
|
130
|
+
const allowedReleaseStrategies = workflow === 'staging-release' ? ['rebase'] : ['squash']
|
|
127
131
|
if (
|
|
128
132
|
includesValue(features, 'release') &&
|
|
129
133
|
!allowedReleaseStrategies.includes(releaseMergeStrategy)
|
|
130
134
|
) {
|
|
131
135
|
error(
|
|
132
|
-
`release_merge_strategy must be "
|
|
136
|
+
`release_merge_strategy must be "${workflow === 'staging-release' ? 'rebase' : 'squash'}" for automated release merges; got "${releaseMergeStrategy || '(unset; release automation never defaults to merge)'}".`
|
|
133
137
|
)
|
|
134
138
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
if ('opencode_security' in config)
|
|
140
|
+
error('opencode_security is obsolete; use the OPENCODE_SECURITY repository variable.')
|
|
141
|
+
if (workflow === 'direct' && 'staging_validation_mode' in config)
|
|
142
|
+
error(
|
|
143
|
+
'staging_validation_mode is not valid in a direct-topology repository; run sync to prune it.'
|
|
144
|
+
)
|
|
140
145
|
const validationEnabled =
|
|
141
146
|
includesValue(features, 'validation') ||
|
|
142
147
|
['ci', 'test', 'security', 'codeql'].some((legacy) => includesValue(features, legacy))
|
|
148
|
+
for (const name of ['validation', 'draft-control', 'draft-pr', 'release-pr', 'release']) {
|
|
149
|
+
if (name === 'release-pr' && workflow !== 'staging-release') continue
|
|
150
|
+
const enabled = name === 'draft-control' ? validationEnabled : includesValue(features, name)
|
|
151
|
+
const candidates = [
|
|
152
|
+
join(target, `.github/workflows/${name}.yml`),
|
|
153
|
+
join(target, `.github/workflows/${name}_self-ci.yml`),
|
|
154
|
+
]
|
|
155
|
+
if (enabled && !candidates.some((file) => existsSync(file)))
|
|
156
|
+
error(`missing enabled workflow: ${name}.yml`)
|
|
157
|
+
}
|
|
143
158
|
const validationCaller = ['validation.yml', 'validation_self-ci.yml']
|
|
144
159
|
.map((file) => join(target, `.github/workflows/${file}`))
|
|
145
160
|
.find((file) => existsSync(file) && /pull_request:/.test(readFileSync(file, 'utf8')))
|
|
@@ -152,6 +167,12 @@ export function doctor(root, options = {}) {
|
|
|
152
167
|
'validation caller is missing the Validation job; the Validation / Gate aggregate check cannot form.'
|
|
153
168
|
)
|
|
154
169
|
}
|
|
170
|
+
if (!/types:\n\s+- ready_for_review/.test(caller)) {
|
|
171
|
+
error('validation caller must wait for the ready_for_review transition.')
|
|
172
|
+
}
|
|
173
|
+
if (/\s+- (?:opened|synchronize|reopened|converted_to_draft)\s*$/.test(caller)) {
|
|
174
|
+
error('validation caller registers draft-time checks; run code-foundry sync.')
|
|
175
|
+
}
|
|
155
176
|
if (
|
|
156
177
|
!/uses:\s+(?:\.\/\.github\/workflows\/validation(?:-no-codeql)?\.yml|\S+\/\.github\/workflows\/validation(?:-no-codeql)?\.yml@)/.test(
|
|
157
178
|
caller
|
package/src/commands/sync.mjs
CHANGED
|
@@ -43,6 +43,7 @@ const standardFiles = [
|
|
|
43
43
|
'.github/ISSUE_TEMPLATE/feature_request.yml',
|
|
44
44
|
'.github/workflows/validation.yml',
|
|
45
45
|
'.github/workflows/validation-audit.yml',
|
|
46
|
+
'.github/workflows/draft-control.yml',
|
|
46
47
|
'.github/workflows/draft-pr.yml',
|
|
47
48
|
'.github/workflows/release-pr.yml',
|
|
48
49
|
'.github/workflows/release.yml',
|
|
@@ -117,6 +118,15 @@ export function syncRepository(options) {
|
|
|
117
118
|
throw new Error('Missing .github/code-foundry.yml; run init first.')
|
|
118
119
|
const defaults = createDefaultConfig(target, source, existingConfig.git_workflow)
|
|
119
120
|
let config = { ...defaults, ...existingConfig }
|
|
121
|
+
const workflow = gitWorkflow(config.git_workflow)
|
|
122
|
+
if (!['direct', 'staging-release'].includes(workflow)) {
|
|
123
|
+
throw new Error(`Unsupported git_workflow: ${workflow}; use direct or staging-release.`)
|
|
124
|
+
}
|
|
125
|
+
const obsoleteConfigKeys = [
|
|
126
|
+
'opencode_security',
|
|
127
|
+
...(workflow === 'direct' ? ['staging_validation_mode'] : []),
|
|
128
|
+
]
|
|
129
|
+
for (const key of obsoleteConfigKeys) delete config[key]
|
|
120
130
|
// Resolve and validate the license policy before any sync writes occur
|
|
121
131
|
// (including config/default additions) so an unsupported policy fails
|
|
122
132
|
// fast without leaving partially generated files behind.
|
|
@@ -133,10 +143,11 @@ export function syncRepository(options) {
|
|
|
133
143
|
writeOrReport(configPath, renderConfig(config), dryRun)
|
|
134
144
|
} else {
|
|
135
145
|
const missing = Object.keys(defaults).filter((key) => !(key in existingConfig))
|
|
136
|
-
|
|
137
|
-
|
|
146
|
+
const original = readFileSync(configPath, 'utf8')
|
|
147
|
+
const normalized = removeConfigKeys(original, obsoleteConfigKeys).trimEnd()
|
|
148
|
+
if (missing.length || normalized !== original.trimEnd()) {
|
|
138
149
|
const additions = missing.map((key) => renderConfigLine(key, defaults[key])).join('\n')
|
|
139
|
-
writeOrReport(configPath, `${
|
|
150
|
+
writeOrReport(configPath, `${normalized}${additions ? `\n${additions}` : ''}\n`, dryRun)
|
|
140
151
|
}
|
|
141
152
|
}
|
|
142
153
|
|
|
@@ -155,17 +166,18 @@ export function syncRepository(options) {
|
|
|
155
166
|
if (!['auto', 'native', 'mise'].includes(toolchain)) {
|
|
156
167
|
throw new Error(`Unsupported toolchain: ${toolchain}; use auto, native, or mise.`)
|
|
157
168
|
}
|
|
158
|
-
const workflow = gitWorkflow(config.git_workflow)
|
|
159
|
-
if (!['direct', 'staging-release'].includes(workflow)) {
|
|
160
|
-
throw new Error(`Unsupported git_workflow: ${workflow}; use direct or staging-release.`)
|
|
161
|
-
}
|
|
162
169
|
const stagingValidationMode = configured(config.staging_validation_mode, 'fast')
|
|
163
|
-
if (!['fast', 'audit'].includes(stagingValidationMode)) {
|
|
170
|
+
if (workflow === 'staging-release' && !['fast', 'audit'].includes(stagingValidationMode)) {
|
|
164
171
|
throw new Error(
|
|
165
172
|
`Unsupported staging_validation_mode: ${stagingValidationMode}; use fast or audit.`
|
|
166
173
|
)
|
|
167
174
|
}
|
|
168
175
|
const mergeStrategy = configured(config.merge_strategy, 'rebase')
|
|
176
|
+
if (workflow === 'direct' && mergeStrategy !== 'squash') {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Unsupported merge_strategy: ${mergeStrategy}; the direct topology requires squash for feature pull requests.`
|
|
179
|
+
)
|
|
180
|
+
}
|
|
169
181
|
if (workflow === 'staging-release' && mergeStrategy !== 'rebase') {
|
|
170
182
|
throw new Error(
|
|
171
183
|
`Unsupported merge_strategy: ${mergeStrategy}; the staging-release topology requires rebase for staging to main promotions.`
|
|
@@ -177,11 +189,10 @@ export function syncRepository(options) {
|
|
|
177
189
|
// release commits; the direct topology has no reconciliation step, so it
|
|
178
190
|
// may also squash Release Please version PRs (a single-commit release PR
|
|
179
191
|
// squashes to the identical tree, and release-please recommends squash).
|
|
180
|
-
const allowedReleaseStrategies =
|
|
181
|
-
workflow === 'staging-release' ? ['rebase'] : ['rebase', 'squash']
|
|
192
|
+
const allowedReleaseStrategies = workflow === 'staging-release' ? ['rebase'] : ['squash']
|
|
182
193
|
if (!allowedReleaseStrategies.includes(releaseMergeStrategy)) {
|
|
183
194
|
throw new Error(
|
|
184
|
-
`Unsupported release_merge_strategy: ${releaseMergeStrategy || '(unset)'}; release automation requires
|
|
195
|
+
`Unsupported release_merge_strategy: ${releaseMergeStrategy || '(unset)'}; release automation requires ${workflow === 'staging-release' ? 'rebase' : 'squash'} for Release Please version pull requests and never defaults to merge.`
|
|
185
196
|
)
|
|
186
197
|
}
|
|
187
198
|
}
|
|
@@ -459,6 +470,7 @@ function shouldInclude(file, languages, features, config) {
|
|
|
459
470
|
// the configuration or the variable enables it and the API key exists.
|
|
460
471
|
if (file === '.github/workflows/opencode-security.yml') return true
|
|
461
472
|
const workflow = file.match(/^\.github\/workflows\/([^/]+)\.yml$/)?.[1]
|
|
473
|
+
if (workflow === 'draft-control') return true
|
|
462
474
|
// The staging promotion caller only exists in the staging-release topology;
|
|
463
475
|
// direct repositories open feature branches into main and need no promotion.
|
|
464
476
|
if (workflow === 'release-pr' && !isStagingRelease(config.git_workflow)) return false
|
|
@@ -539,6 +551,21 @@ function renderWorkflow(content, config, repository, ref, rustCodeql) {
|
|
|
539
551
|
// target main, so their callers trigger on main alone.
|
|
540
552
|
if (!isStagingRelease(config.git_workflow)) {
|
|
541
553
|
rendered = rendered.replace(/^(\s+branches:)\s*\[main,\s*staging\]\s*$/gm, `$1 [main]`)
|
|
554
|
+
// Direct repositories have no staging branch or release reconciliation,
|
|
555
|
+
// so their generated release caller must not expose the legacy deploy-key
|
|
556
|
+
// secret. Keep it in the staging-release template for repositories that
|
|
557
|
+
// still explicitly select that topology.
|
|
558
|
+
if (workflow === 'release') {
|
|
559
|
+
rendered = rendered.replace(
|
|
560
|
+
/^\s+STAGING_DEPLOY_KEY:\s+\$\{\{\s*secrets\.STAGING_DEPLOY_KEY\s*\}\}\s*\n/m,
|
|
561
|
+
''
|
|
562
|
+
)
|
|
563
|
+
}
|
|
564
|
+
} else if (workflow === 'release' && !rendered.includes('STAGING_DEPLOY_KEY')) {
|
|
565
|
+
rendered = rendered.replace(
|
|
566
|
+
/^(\s+)CODE_FOUNDRY_TOKEN:\s+\$\{\{\s*secrets\.CODE_FOUNDRY_TOKEN\s*\}\}\s*$/m,
|
|
567
|
+
'$&\n$1STAGING_DEPLOY_KEY: ${{ secrets.STAGING_DEPLOY_KEY }}'
|
|
568
|
+
)
|
|
542
569
|
}
|
|
543
570
|
if (workflow === 'draft-pr') {
|
|
544
571
|
// The draft PR caller states the PR base explicitly so the shared
|
|
@@ -645,21 +672,26 @@ function stripDependabotEcosystem(content, ecosystem) {
|
|
|
645
672
|
* @returns {string}
|
|
646
673
|
*/
|
|
647
674
|
export function renderContributionDocs(content, file, config) {
|
|
648
|
-
if (isStagingRelease(config.git_workflow)) {
|
|
649
|
-
if (configured(config.staging_validation_mode, 'fast') !== 'audit') return content
|
|
650
|
-
return content.replace(
|
|
651
|
-
'Fast validation: CI plus unit tests, ending in `Validation / Gate`',
|
|
652
|
-
'Audit validation: CI, full tests, Security, and CodeQL, ending in `Validation / Gate`'
|
|
653
|
-
)
|
|
654
|
-
}
|
|
655
675
|
const replacements = DIRECT_DOC_REPLACEMENTS[file]
|
|
656
676
|
if (!replacements) return content
|
|
657
677
|
let rendered = content
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
678
|
+
const stagingRelease = isStagingRelease(config.git_workflow)
|
|
679
|
+
for (const [staging, direct] of replacements) {
|
|
680
|
+
const from = stagingRelease ? direct : staging
|
|
681
|
+
const to = stagingRelease ? staging : direct
|
|
682
|
+
if (rendered.includes(from)) {
|
|
683
|
+
rendered = rendered.replace(from, to)
|
|
684
|
+
} else if (!rendered.includes(to)) {
|
|
685
|
+
throw new Error(
|
|
686
|
+
`Missing ${stagingRelease ? 'staging-release' : 'direct'} workflow template marker in ${file}: ${JSON.stringify(from)}`
|
|
687
|
+
)
|
|
661
688
|
}
|
|
662
|
-
|
|
689
|
+
}
|
|
690
|
+
if (stagingRelease && configured(config.staging_validation_mode, 'fast') === 'audit') {
|
|
691
|
+
rendered = rendered.replace(
|
|
692
|
+
'Fast validation: CI plus unit tests, ending in `Validation / Gate`',
|
|
693
|
+
'Audit validation: CI, full tests, Security, and CodeQL, ending in `Validation / Gate`'
|
|
694
|
+
)
|
|
663
695
|
}
|
|
664
696
|
return rendered
|
|
665
697
|
}
|
|
@@ -695,7 +727,7 @@ const DIRECT_DOC_REPLACEMENTS = {
|
|
|
695
727
|
],
|
|
696
728
|
[
|
|
697
729
|
'The Git workflow is `staging-release`: topic branches **squash** into `staging`, a promotion PR **rebases** validated changes into `main` (`merge_strategy: rebase`), and the Release Please version PR **rebases** into `main` (`release_merge_strategy: rebase`). Release automation never defaults to a merge method and never merges with `--admin`; `code-foundry doctor` and `code-foundry sync` fail closed on any other merge strategy. Re-align `staging` with `main` after a release when needed.',
|
|
698
|
-
'The Git workflow is `direct`: topic branches **squash** directly into `main`, and the Release Please version PR **squashes** into `main` (`release_merge_strategy: squash`). Release automation never defaults to a merge method and never merges with `--admin`; `code-foundry doctor` and `code-foundry sync` fail closed on any other release merge strategy.
|
|
730
|
+
'The Git workflow is `direct`: topic branches **squash** directly into `main`, and the Release Please version PR **squashes** into `main` (`release_merge_strategy: squash`). Release automation never defaults to a merge method and never merges with `--admin`; `code-foundry doctor` and `code-foundry sync` fail closed on any other release merge strategy. This repository has one protected integration and release branch: `main`.',
|
|
699
731
|
],
|
|
700
732
|
[
|
|
701
733
|
'git switch staging\ngit pull --ff-only origin staging',
|
|
@@ -717,11 +749,15 @@ const DIRECT_DOC_REPLACEMENTS = {
|
|
|
717
749
|
],
|
|
718
750
|
[
|
|
719
751
|
'| Event | Expected automation |\n| -------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| Draft pull request targeting `staging` | No runner-heavy validation; run local checks before requesting review |\n| Ready pull request targeting `staging` | Fast validation: CI plus unit tests, ending in `Validation / Gate` |\n| Draft ordinary pull request targeting `main` | No runner-heavy validation; run local checks before requesting review |\n| Ready ordinary pull request targeting `main` | Audit validation: CI, full tests, Security, and CodeQL, ending in `Validation / Gate` |\n| Exact Release Please pull request targeting `main` | Release-policy validation only, ending in `Validation / Gate` |\n| Scheduled or manual validation | Full audit tier |\n| Push to a working branch | Draft PR workflow |\n| Push to `staging` | Promotion PR workflow; canonical validation waits for the PR event |\n| Push to `main` | Release workflow; canonical validation already ran on the merged PR |\n',
|
|
720
|
-
'| Event
|
|
752
|
+
'| Event | Expected automation |\n| -------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| Draft pull request targeting `main` | No runner-heavy validation; run local checks before requesting review |\n| Ready pull request targeting `main` | Audit validation: CI, full tests, Security, and CodeQL, ending in `Validation / Gate` |\n| Exact Release Please pull request targeting `main` | Release-policy validation only, ending in `Validation / Gate` |\n| Scheduled or manual validation | Full audit tier |\n| Push to a working branch | Draft PR workflow |\n| Push to `main` | Release workflow; canonical validation already ran on the merged PR |\n',
|
|
721
753
|
],
|
|
722
754
|
[
|
|
723
755
|
'| Change | Target | Merge method | Merge gate |\n| ---------------------------- | --------- | ----------------------------------------------- | --------------------------------------------------------- |\n| Working branch | `staging` | Squash | All applicable required checks pass |\n| `staging` → `main` promotion | `main` | Rebase (`merge_strategy`) | Current staging checks, release review, and rollout notes |\n| Release Please version PR | `main` | Rebase (`release_merge_strategy`, fails closed) | Validation gate and release policy pass |\n',
|
|
724
|
-
'| Change
|
|
756
|
+
'| Change | Target | Merge method | Merge gate |\n| ------------------------- | ------ | --------------------------------- | --------------------------------------- |\n| Working branch | `main` | Squash | All applicable required checks pass |\n| Release Please version PR | `main` | Squash (`release_merge_strategy`) | Validation gate and release policy pass |\n',
|
|
757
|
+
],
|
|
758
|
+
[
|
|
759
|
+
'Draft pull requests do not start runner-heavy validation. Marking a pull request ready for review starts the applicable validation tier; converting it back to draft cancels in-flight validation, and no replacement starts until it is ready again.',
|
|
760
|
+
'Draft pull requests do not start validation. Marking a pull request ready for review starts the applicable validation tier. Convert it back to draft after an update, then mark it ready again after every update so the required checks attach to the current head. Converting it to draft runs only the lightweight cancellation control.',
|
|
725
761
|
],
|
|
726
762
|
['1. Create a focused branch from `staging`.', '1. Create a focused branch from `main`.'],
|
|
727
763
|
],
|
|
@@ -1149,7 +1185,7 @@ function createDefaultConfig(root, source, configuredWorkflow) {
|
|
|
1149
1185
|
runtime_ref: `v${readPackageVersion(source)}`,
|
|
1150
1186
|
...runners,
|
|
1151
1187
|
toolchain: 'auto',
|
|
1152
|
-
staging_validation_mode: 'fast',
|
|
1188
|
+
...(stagingRelease ? { staging_validation_mode: 'fast' } : {}),
|
|
1153
1189
|
performance: 'auto',
|
|
1154
1190
|
performance_command: '',
|
|
1155
1191
|
performance_profile: '',
|
|
@@ -1164,7 +1200,6 @@ function createDefaultConfig(root, source, configuredWorkflow) {
|
|
|
1164
1200
|
post_release: 'false',
|
|
1165
1201
|
post_release_workflow: '',
|
|
1166
1202
|
post_release_mode: 'auto',
|
|
1167
|
-
opencode_security: 'false',
|
|
1168
1203
|
opencode_security_model: '',
|
|
1169
1204
|
sync_mode: 'overlay',
|
|
1170
1205
|
custom_workflows: 'preserve',
|
|
@@ -1181,6 +1216,20 @@ function renderConfig(config) {
|
|
|
1181
1216
|
.map(([key, value]) => renderConfigLine(key, value))
|
|
1182
1217
|
.join('\n')}\n`
|
|
1183
1218
|
}
|
|
1219
|
+
|
|
1220
|
+
/** @param {string} content @param {string[]} keys */
|
|
1221
|
+
function removeConfigKeys(content, keys) {
|
|
1222
|
+
if (!keys.length) return content
|
|
1223
|
+
const rejected = new Set(keys)
|
|
1224
|
+
return content
|
|
1225
|
+
.split(/\r?\n/)
|
|
1226
|
+
.filter((line) => {
|
|
1227
|
+
const key = line.match(/^([A-Za-z0-9_-]+):/)?.[1]
|
|
1228
|
+
return !key || !rejected.has(key)
|
|
1229
|
+
})
|
|
1230
|
+
.join('\n')
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1184
1233
|
/** @param {string} key @param {string} value */
|
|
1185
1234
|
function renderConfigLine(key, value) {
|
|
1186
1235
|
if (value === '') return `${key}:`
|