code-foundry 1.9.12 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/CONTRIBUTING.md +22 -1
- package/.github/workflows/cloudflare-deploy.yml +13 -3
- package/.github/workflows/draft-enforcement_self-ci.yml +79 -0
- package/.oxfmtrc.json +1 -1
- package/AGENTS.md +24 -1
- package/CHANGELOG.md +14 -0
- package/README.md +7 -4
- package/docs/CONFIGURATION.md +3 -1
- package/docs/INITIALIZATION.md +10 -1
- package/docs/WORKFLOWS.md +19 -13
- package/package.json +1 -1
- package/src/commands/doctor.mjs +12 -2
- package/src/commands/release.mjs +1 -0
- package/src/commands/sync.mjs +75 -10
package/.github/CONTRIBUTING.md
CHANGED
|
@@ -32,6 +32,27 @@ Agents must not:
|
|
|
32
32
|
- Change branch protections, secrets, deployments, or external systems unless that action is explicitly in scope.
|
|
33
33
|
- Claim completion when tests, deployment checks, or required reviews are still pending.
|
|
34
34
|
|
|
35
|
+
<!-- code-foundry-managed: pull-request-policy -->
|
|
36
|
+
|
|
37
|
+
### Pull request readiness (mandatory)
|
|
38
|
+
|
|
39
|
+
This repository uses the `direct` workflow. Topic pull requests target `main`.
|
|
40
|
+
|
|
41
|
+
- Open every ordinary pull request as a draft. Use `gh pr create --draft` or
|
|
42
|
+
set `draft: true` in the GitHub API; never create a ready ordinary pull
|
|
43
|
+
request as a shortcut.
|
|
44
|
+
- Keep ordinary pull requests in draft while making commits. The generated
|
|
45
|
+
Draft Guard resets a ready ordinary pull request to draft on creation,
|
|
46
|
+
reopening, or a new commit, and runner-heavy validation starts only after an
|
|
47
|
+
explicit `ready_for_review` transition.
|
|
48
|
+
- Run local validation and finish review preparation before marking an ordinary
|
|
49
|
+
pull request ready. After any later commit, mark it ready again only after
|
|
50
|
+
the current head is ready for review.
|
|
51
|
+
- Release Please version pull requests are managed by the Code Foundry release
|
|
52
|
+
workflow; do not manually change their draft state unless the workflow asks.
|
|
53
|
+
|
|
54
|
+
<!-- /code-foundry-managed: pull-request-policy -->
|
|
55
|
+
|
|
35
56
|
## Branching model
|
|
36
57
|
|
|
37
58
|
```text
|
|
@@ -162,7 +183,7 @@ Keep pull requests focused and reviewable. Include screenshots or recordings for
|
|
|
162
183
|
| Push to a working branch | Draft PR workflow |
|
|
163
184
|
| Push to `main` | Release workflow; canonical validation already ran on the merged PR |
|
|
164
185
|
|
|
165
|
-
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.
|
|
186
|
+
Draft pull requests do not start validation. The lightweight Draft Guard also converts ordinary pull requests opened, reopened, or updated while ready back to draft; it never checks out pull-request code and it excludes Release Please version heads, whose release workflow owns their state. 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.
|
|
166
187
|
|
|
167
188
|
Pull-request validation keys concurrency by event and pull-request head, so a newer update cancels its superseded run. Scheduled and manual audits use a separate caller pinned to the protected default branch; this prevents caller-selected runtime code from executing with default-branch cache access. Both callers use the mode-aware orchestrator, which fans out only the required jobs and concludes with the stable aggregate gate.
|
|
168
189
|
|
|
@@ -87,15 +87,25 @@ jobs:
|
|
|
87
87
|
env:
|
|
88
88
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
89
89
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
90
|
+
BUILD_SCRIPT: ${{ inputs.build-script }}
|
|
90
91
|
WRANGLER_VERSION: ${{ inputs.wrangler-version }}
|
|
91
92
|
MODE: ${{ inputs.mode }}
|
|
92
93
|
run: |
|
|
93
94
|
set -euo pipefail
|
|
94
|
-
|
|
95
|
+
# Bun callers install workspace dependencies with Bun. Keep that
|
|
96
|
+
# package-manager context for Wrangler's OpenNext auto-delegation;
|
|
97
|
+
# Wrangler otherwise selects npm/npx and cannot resolve the local
|
|
98
|
+
# opennextjs-cloudflare binary from a Bun workspace.
|
|
99
|
+
if [ -n "$BUILD_SCRIPT" ]; then
|
|
100
|
+
wrangler=(bunx --bun "wrangler@$WRANGLER_VERSION")
|
|
101
|
+
else
|
|
102
|
+
wrangler=(npx -y "wrangler@$WRANGLER_VERSION")
|
|
103
|
+
fi
|
|
104
|
+
"${wrangler[@]}" --version
|
|
95
105
|
if [ "$MODE" = "production" ]; then
|
|
96
|
-
|
|
106
|
+
"${wrangler[@]}" deploy 2>&1 | tee deploy-output.txt
|
|
97
107
|
elif [ "$MODE" = "preview" ]; then
|
|
98
|
-
|
|
108
|
+
"${wrangler[@]}" versions upload --message "preview ${GITHUB_SHA::7}" 2>&1 | tee deploy-output.txt
|
|
99
109
|
else
|
|
100
110
|
echo "Unsupported mode: $MODE (use preview or production)" >&2
|
|
101
111
|
exit 1
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
name: Code Foundry Draft Guard
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
# This workflow runs from the trusted base branch and only reads pull-request
|
|
5
|
+
# metadata before changing draft state. It never checks out or executes PR code.
|
|
6
|
+
pull_request_target:
|
|
7
|
+
branches: [main, staging]
|
|
8
|
+
types:
|
|
9
|
+
- opened
|
|
10
|
+
- reopened
|
|
11
|
+
- synchronize
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
pull-requests: write
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: code-foundry-draft-guard-${{ github.repository }}-${{ github.event.pull_request.number }}
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
enforce:
|
|
22
|
+
name: Draft Guard / Enforce
|
|
23
|
+
if: vars.CI_BILLING_PAUSED != 'true'
|
|
24
|
+
runs-on: ubuntu-slim
|
|
25
|
+
timeout-minutes: 5
|
|
26
|
+
steps:
|
|
27
|
+
- name: Keep ordinary pull requests in draft
|
|
28
|
+
env:
|
|
29
|
+
GH_TOKEN: ${{ github.token }}
|
|
30
|
+
EVENT_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
31
|
+
EVENT_UPDATED_AT: ${{ github.event.pull_request.updated_at }}
|
|
32
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
33
|
+
run: |
|
|
34
|
+
set -euo pipefail
|
|
35
|
+
if [ -z "$EVENT_HEAD_SHA" ] || [ -z "$EVENT_UPDATED_AT" ] || [ -z "$PR_NUMBER" ]; then
|
|
36
|
+
echo '::error title=Draft guard cannot verify event freshness::The pull-request event did not include the required immutable metadata.'
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
read -r draft head_ref head_sha updated_at < <(
|
|
41
|
+
gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" \
|
|
42
|
+
--jq '[.draft, .head.ref, .head.sha, .updated_at] | @tsv'
|
|
43
|
+
)
|
|
44
|
+
if [ -z "$draft" ] || [ -z "$head_ref" ] || [ -z "$head_sha" ] || [ -z "$updated_at" ]; then
|
|
45
|
+
echo '::error title=Draft guard cannot verify current state::The GitHub API response did not include the required pull-request metadata.'
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Release Please owns the state of generated version PRs. Its release
|
|
50
|
+
# workflow deliberately resets and readies these PRs before merging.
|
|
51
|
+
case "$head_ref" in
|
|
52
|
+
release-please--branches--main|release-please--branches--main--*)
|
|
53
|
+
echo "Release Please head $head_ref is managed by the release workflow; leaving its draft state unchanged."
|
|
54
|
+
exit 0
|
|
55
|
+
;;
|
|
56
|
+
esac
|
|
57
|
+
|
|
58
|
+
# A newer state transition or commit superseded this event. Do not
|
|
59
|
+
# undo a user's later explicit readiness decision with a stale event.
|
|
60
|
+
if [ "$head_sha" != "$EVENT_HEAD_SHA" ] || [ "$updated_at" != "$EVENT_UPDATED_AT" ]; then
|
|
61
|
+
echo 'A newer pull-request state or head commit superseded this draft-guard event; leaving the current state unchanged.'
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
if [ "$draft" = true ]; then
|
|
66
|
+
echo "Pull request #$PR_NUMBER is already a draft; no validation state change is needed."
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
if [ "$draft" != false ]; then
|
|
70
|
+
echo "::error title=Draft guard cannot verify current state::Unexpected draft value: $draft"
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
gh api \
|
|
75
|
+
--method PATCH \
|
|
76
|
+
"repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" \
|
|
77
|
+
-F draft=true \
|
|
78
|
+
--silent >/dev/null
|
|
79
|
+
echo "Converted ordinary pull request #$PR_NUMBER to draft; runner-heavy validation waits for ready_for_review."
|
package/.oxfmtrc.json
CHANGED
package/AGENTS.md
CHANGED
|
@@ -94,6 +94,29 @@ them:
|
|
|
94
94
|
- Keep pull requests focused; merge with the canonical method only after
|
|
95
95
|
required checks pass.
|
|
96
96
|
|
|
97
|
+
<!-- code-foundry-managed: pull-request-policy -->
|
|
98
|
+
|
|
99
|
+
## Code Foundry workflow policy (mandatory)
|
|
100
|
+
|
|
101
|
+
This repository uses the `direct` workflow. Topic pull requests target `main`.
|
|
102
|
+
|
|
103
|
+
- Open every ordinary pull request as a draft. Use `gh pr create --draft` or
|
|
104
|
+
set `draft: true` in the GitHub API; never create a ready ordinary pull
|
|
105
|
+
request as a shortcut.
|
|
106
|
+
- Keep ordinary pull requests in draft while making commits. The generated
|
|
107
|
+
Draft Guard resets a ready ordinary pull request to draft on creation,
|
|
108
|
+
reopening, or a new commit, and runner-heavy validation starts only after an
|
|
109
|
+
explicit `ready_for_review` transition.
|
|
110
|
+
- Run local validation and finish review preparation before marking an ordinary
|
|
111
|
+
pull request ready. After any later commit, mark it ready again only after
|
|
112
|
+
the current head is ready for review.
|
|
113
|
+
- This contract is mandatory for every agent scope. Nested `AGENTS.md` files
|
|
114
|
+
may add stricter rules but must not weaken or replace it.
|
|
115
|
+
- Release Please version pull requests are managed by the Code Foundry release
|
|
116
|
+
workflow; do not manually change their draft state unless the workflow asks.
|
|
117
|
+
|
|
118
|
+
<!-- /code-foundry-managed: pull-request-policy -->
|
|
119
|
+
|
|
97
120
|
## Toolchain and dependencies
|
|
98
121
|
|
|
99
122
|
- Follow `toolchain: auto` in `.github/code-foundry.yml`; use native tools by
|
|
@@ -154,7 +177,7 @@ If a check cannot run, state the exact reason. A skipped check is not a passing
|
|
|
154
177
|
- Use per-workflow concurrency groups that cancel superseded runs while allowing independent workflows to run in parallel.
|
|
155
178
|
- 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.
|
|
156
179
|
- Use least-privilege permissions and pin action versions consistently with the template.
|
|
157
|
-
- Keep CI, Test, Security, CodeQL, Draft PR, Release PR, and Release concerns separated.
|
|
180
|
+
- Keep CI, Test, Security, CodeQL, Draft Guard, Draft PR, Release PR, and Release concerns separated.
|
|
158
181
|
- Security and CodeQL may skip when repository visibility or GitHub plan support does not permit them. Do not make an unavailable check required.
|
|
159
182
|
- Optional Turborepo Remote Caching uses `TURBO_TOKEN` and `TURBO_TEAM`; do not add Vercel deployment behavior just to enable caching.
|
|
160
183
|
- Update branch protection when adding or renaming required job checks; verify the actual GitHub status context.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.10.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.9.13...v1.10.0) (2026-09-08)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **ci:** enforce draft pull request readiness ([#530](https://github.com/0xPlayerOne/code-foundry/issues/530)) ([fe62147](https://github.com/0xPlayerOne/code-foundry/commit/fe621477a46b49dee69a4c74c482e815a3e60b8d))
|
|
9
|
+
|
|
10
|
+
## [1.9.13](https://github.com/0xPlayerOne/code-foundry/compare/v1.9.12...v1.9.13) (2026-09-08)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **cloudflare:** preserve Bun package manager for OpenNext deploys ([#529](https://github.com/0xPlayerOne/code-foundry/issues/529)) ([2f1bc00](https://github.com/0xPlayerOne/code-foundry/commit/2f1bc000ea28e207e4f12cc7abb5c330cb8448b9))
|
|
16
|
+
|
|
3
17
|
## [1.9.12](https://github.com/0xPlayerOne/code-foundry/compare/v1.9.11...v1.9.12) (2026-09-08)
|
|
4
18
|
|
|
5
19
|
|
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ contract. For normal updates, edit that file and run `npx code-foundry sync`.
|
|
|
36
36
|
## What it installs
|
|
37
37
|
|
|
38
38
|
- Short workflow callers for pull-request validation, protected scheduled/manual
|
|
39
|
-
audits, Draft PR, Release PR, and Release.
|
|
39
|
+
audits, Draft Guard, Draft PR, Release PR, and Release.
|
|
40
40
|
- A deterministic performance lane with ordered command support, stable result
|
|
41
41
|
artifacts, and an optional shared Node package budget profile.
|
|
42
42
|
- A small `.githooks/pre-commit` launcher with language-aware formatting and
|
|
@@ -75,9 +75,12 @@ 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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
The trusted Draft Guard is a fallback for manually created or reopened ready
|
|
79
|
+
PRs and for new commits to ordinary ready PRs: it converts them to drafts
|
|
80
|
+
without checking out PR code. It verifies the event head and update timestamp
|
|
81
|
+
before changing state, and excludes Release Please version PRs whose release
|
|
82
|
+
workflow owns readiness. Pull-request validation runs only on the
|
|
83
|
+
ready-for-review transition. Converting a PR to draft runs only the lightweight
|
|
81
84
|
cancellation control. Scheduled and manually dispatched audits are unaffected.
|
|
82
85
|
|
|
83
86
|
Jobs are language-aware and skip irrelevant setup inside the applicable
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -183,4 +183,6 @@ GitHub deployment plus status with the workers.dev URL, and respects
|
|
|
183
183
|
`CLOUDFLARE_ACCOUNT_ID` secrets in the consumer repository. Bun consumers may
|
|
184
184
|
also pass `build-script`, `install-working-directory`, and `bun-version`; the
|
|
185
185
|
runtime installs the frozen lockfile and builds the Worker before invoking
|
|
186
|
-
Wrangler.
|
|
186
|
+
Wrangler. Bun-backed callers invoke Wrangler through `bunx` so OpenNext's
|
|
187
|
+
production delegation resolves the workspace-local `opennextjs-cloudflare`
|
|
188
|
+
binary; callers without `build-script` retain the npm/npx path.
|
package/docs/INITIALIZATION.md
CHANGED
|
@@ -38,7 +38,16 @@ runtime, then run sync.
|
|
|
38
38
|
|
|
39
39
|
Sync updates standard Code Foundry files only. It preserves application code,
|
|
40
40
|
authored documentation, existing `.mise.toml` selections, and custom workflows
|
|
41
|
-
such as deployment, search, Slither, or monitoring workflows.
|
|
41
|
+
such as deployment, search, Slither, or monitoring workflows. A missing
|
|
42
|
+
`AGENTS.md` receives the baseline agent contract. If another initializer has
|
|
43
|
+
created an unmarked `AGENTS.md` or `.github/CONTRIBUTING.md`, sync preserves its
|
|
44
|
+
surrounding text and adds or refreshes only the marked Code Foundry policy block;
|
|
45
|
+
this lets later syncs restore mandatory pull-request rules without replacing
|
|
46
|
+
agent-specific instructions. Marked generated policy documents continue to
|
|
47
|
+
receive topology-aware baseline updates, and missing configuration keys are
|
|
48
|
+
added without changing existing values. The Oxfmt baseline also ignores
|
|
49
|
+
`plugin.json`, whose serialization is owned by Release Please; plugin manifest
|
|
50
|
+
semantics remain covered by repository tests rather than a formatter rewrite.
|
|
42
51
|
|
|
43
52
|
The environment bootstrap enables repository hooks and uses mise only when an
|
|
44
53
|
existing `.mise.toml` is present or `toolchain: mise` is selected. Otherwise it
|
package/docs/WORKFLOWS.md
CHANGED
|
@@ -14,10 +14,15 @@ pull_request:
|
|
|
14
14
|
|
|
15
15
|
The generated validation caller listens only for `ready_for_review`, so draft
|
|
16
16
|
pull requests register no validation checks and allocate no validation runner.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
draft
|
|
20
|
-
|
|
17
|
+
A separate lightweight Draft Guard runs from the trusted base branch on
|
|
18
|
+
`opened`, `reopened`, and `synchronize`; it converts ordinary ready pull
|
|
19
|
+
requests back to draft without checking out pull-request code. It rechecks the
|
|
20
|
+
current head and update timestamp before mutating state, so a stale event
|
|
21
|
+
cannot undo a later draft or ready transition. Release Please version heads are
|
|
22
|
+
excluded because the release workflow owns their state. A separate draft-control
|
|
23
|
+
caller listens for `converted_to_draft` and cancels queued or running
|
|
24
|
+
pull-request workflows without creating skipped validation jobs. Marking a pull
|
|
25
|
+
request ready again starts validation for the current head.
|
|
21
26
|
|
|
22
27
|
The separate `validation-audit.yml` caller is pinned to the configured released
|
|
23
28
|
runtime and handles scheduled and manual audits:
|
|
@@ -91,15 +96,16 @@ opt in or out without a code change.
|
|
|
91
96
|
|
|
92
97
|
## Standard workflow responsibilities
|
|
93
98
|
|
|
94
|
-
| Workflow
|
|
95
|
-
|
|
|
96
|
-
| CI
|
|
97
|
-
| Test
|
|
98
|
-
| Security
|
|
99
|
-
| CodeQL
|
|
100
|
-
| Draft PR
|
|
101
|
-
|
|
|
102
|
-
| Release
|
|
99
|
+
| Workflow | Responsibility |
|
|
100
|
+
| ----------- | ----------------------------------------------------------------- |
|
|
101
|
+
| CI | Format, lint, type-check, and build |
|
|
102
|
+
| Test | Unit, standardized performance, integration, E2E, and smoke tests |
|
|
103
|
+
| Security | Profile, audits, and public-only Dependency Review |
|
|
104
|
+
| CodeQL | GitHub-native code scanning, kept separate from CI |
|
|
105
|
+
| Draft PR | Create/update development pull requests |
|
|
106
|
+
| Draft Guard | Keep ordinary PRs draft until `ready_for_review` |
|
|
107
|
+
| Release PR | Promote `staging` into `main` (staging-release topology only) |
|
|
108
|
+
| Release | Release Please, GitHub release, and optional npm publication |
|
|
103
109
|
|
|
104
110
|
Use concise job names such as `CI / Format`, `Test / Unit`, and
|
|
105
111
|
`CodeQL / Analyze (Python)`. Per-language CodeQL analyzers (Rust shards
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-foundry",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
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
|
@@ -150,9 +150,19 @@ export function doctor(root, options = {}) {
|
|
|
150
150
|
const validationEnabled =
|
|
151
151
|
includesValue(features, 'validation') ||
|
|
152
152
|
['ci', 'test', 'security', 'codeql'].some((legacy) => includesValue(features, legacy))
|
|
153
|
-
for (const name of [
|
|
153
|
+
for (const name of [
|
|
154
|
+
'validation',
|
|
155
|
+
'draft-control',
|
|
156
|
+
'draft-enforcement',
|
|
157
|
+
'draft-pr',
|
|
158
|
+
'release-pr',
|
|
159
|
+
'release',
|
|
160
|
+
]) {
|
|
154
161
|
if (name === 'release-pr' && workflow !== 'staging-release') continue
|
|
155
|
-
const enabled =
|
|
162
|
+
const enabled =
|
|
163
|
+
name === 'draft-control' || name === 'draft-enforcement'
|
|
164
|
+
? validationEnabled
|
|
165
|
+
: includesValue(features, name)
|
|
156
166
|
const candidates = [
|
|
157
167
|
join(target, `.github/workflows/${name}.yml`),
|
|
158
168
|
join(target, `.github/workflows/${name}_self-ci.yml`),
|
package/src/commands/release.mjs
CHANGED
package/src/commands/sync.mjs
CHANGED
|
@@ -44,6 +44,7 @@ const standardFiles = [
|
|
|
44
44
|
'.github/workflows/validation.yml',
|
|
45
45
|
'.github/workflows/validation-audit.yml',
|
|
46
46
|
'.github/workflows/draft-control.yml',
|
|
47
|
+
'.github/workflows/draft-enforcement.yml',
|
|
47
48
|
'.github/workflows/draft-pr.yml',
|
|
48
49
|
'.github/workflows/release-pr.yml',
|
|
49
50
|
'.github/workflows/release.yml',
|
|
@@ -221,10 +222,6 @@ export function syncRepository(options) {
|
|
|
221
222
|
const sourceFile = sourcePath(source, file)
|
|
222
223
|
if (!existsSync(sourceFile)) throw new Error(`Template file missing: ${file}`)
|
|
223
224
|
const destination = join(target, file)
|
|
224
|
-
if (!force && protectedFiles.has(file) && existsSync(destination)) {
|
|
225
|
-
const existing = readFileSync(destination, 'utf8')
|
|
226
|
-
if (!isLegacyManagedDoc(file, existing) && !isManagedConfigPolicy(file, existing)) continue
|
|
227
|
-
}
|
|
228
225
|
if (
|
|
229
226
|
(file === 'LICENSE' || file === 'NOTICE') &&
|
|
230
227
|
license === 'preserve' &&
|
|
@@ -267,6 +264,19 @@ export function syncRepository(options) {
|
|
|
267
264
|
mergeIgnorePatternsConfig(content.toString('utf8'), readFileSync(destination, 'utf8'))
|
|
268
265
|
)
|
|
269
266
|
}
|
|
267
|
+
if (!force && protectedFiles.has(file) && existsSync(destination)) {
|
|
268
|
+
const existing = readFileSync(destination, 'utf8')
|
|
269
|
+
if (!isLegacyManagedDoc(file, existing) && !isManagedConfigPolicy(file, existing)) {
|
|
270
|
+
if (configAwarePolicyFiles.has(file)) {
|
|
271
|
+
const merged = mergeManagedPolicyBlocks(existing, content.toString('utf8'))
|
|
272
|
+
if (merged !== existing) {
|
|
273
|
+
changed.push(file)
|
|
274
|
+
writeOrReport(destination, merged, dryRun)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
continue
|
|
278
|
+
}
|
|
279
|
+
}
|
|
270
280
|
if (!existsSync(destination) || !buffersEqual(content, readFileSync(destination))) {
|
|
271
281
|
changed.push(file)
|
|
272
282
|
writeOrReport(destination, content, dryRun)
|
|
@@ -478,7 +488,12 @@ function shouldInclude(file, languages, features, config) {
|
|
|
478
488
|
// the configuration or the variable enables it and the API key exists.
|
|
479
489
|
if (file === '.github/workflows/opencode-security.yml') return true
|
|
480
490
|
const workflow = file.match(/^\.github\/workflows\/([^/]+)\.yml$/)?.[1]
|
|
481
|
-
if (workflow === 'draft-control')
|
|
491
|
+
if (workflow === 'draft-control' || workflow === 'draft-enforcement') {
|
|
492
|
+
return (
|
|
493
|
+
includesValue(features, 'validation') ||
|
|
494
|
+
LEGACY_GENERATED_CALLERS.some((legacy) => includesValue(features, legacy))
|
|
495
|
+
)
|
|
496
|
+
}
|
|
482
497
|
// The staging promotion caller only exists in the staging-release topology;
|
|
483
498
|
// direct repositories open feature branches into main and need no promotion.
|
|
484
499
|
if (workflow === 'release-pr' && !isStagingRelease(config.git_workflow)) return false
|
|
@@ -719,8 +734,16 @@ const DIRECT_DOC_REPLACEMENTS = {
|
|
|
719
734
|
'This repository uses the `staging-release` workflow: 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`). Feature PRs land on `staging` with squash merges; promotion and release PRs land on `main` with rebase merges. Re-align `staging` with `main` after a release when needed.',
|
|
720
735
|
'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`.',
|
|
721
736
|
],
|
|
737
|
+
[
|
|
738
|
+
'This repository uses the `staging-release` workflow. Topic pull requests target `staging`; promotion pull requests target `main`.',
|
|
739
|
+
'This repository uses the `direct` workflow. Topic pull requests target `main`.',
|
|
740
|
+
],
|
|
722
741
|
],
|
|
723
742
|
'.github/CONTRIBUTING.md': [
|
|
743
|
+
[
|
|
744
|
+
'This repository uses the `staging-release` workflow. Topic pull requests target `staging`; promotion pull requests target `main`.',
|
|
745
|
+
'This repository uses the `direct` workflow. Topic pull requests target `main`.',
|
|
746
|
+
],
|
|
724
747
|
[
|
|
725
748
|
'4. Branch from `staging` and target pull requests at `staging`; do not work directly on `main`.',
|
|
726
749
|
'4. Branch from `main` and target pull requests at `main`; do not push directly to `main`.',
|
|
@@ -764,8 +787,8 @@ const DIRECT_DOC_REPLACEMENTS = {
|
|
|
764
787
|
'| 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',
|
|
765
788
|
],
|
|
766
789
|
[
|
|
767
|
-
'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.',
|
|
768
|
-
'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.',
|
|
790
|
+
'Draft pull requests do not start runner-heavy validation. The lightweight Draft Guard also converts ordinary pull requests opened, reopened, or updated while ready back to draft; it never checks out pull-request code and it excludes Release Please version heads, whose release workflow owns their state. 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 back to draft cancels in-flight validation, and no replacement starts until it is ready again.',
|
|
791
|
+
'Draft pull requests do not start validation. The lightweight Draft Guard also converts ordinary pull requests opened, reopened, or updated while ready back to draft; it never checks out pull-request code and it excludes Release Please version heads, whose release workflow owns their state. 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.',
|
|
769
792
|
],
|
|
770
793
|
['1. Create a focused branch from `staging`.', '1. Create a focused branch from `main`.'],
|
|
771
794
|
],
|
|
@@ -1116,9 +1139,12 @@ function isLegacyManagedDoc(file, content) {
|
|
|
1116
1139
|
|
|
1117
1140
|
/**
|
|
1118
1141
|
* Config-aware policy documents are generated contracts: a normal sync must
|
|
1119
|
-
* refresh
|
|
1120
|
-
*
|
|
1121
|
-
*
|
|
1142
|
+
* refresh generated copies after branch-topology or validation-policy edits.
|
|
1143
|
+
* Unmarked consumer-owned documents receive only the explicitly marked policy
|
|
1144
|
+
* blocks, so another agent's initializer cannot be overwritten. The top-level
|
|
1145
|
+
* marker owns generated copies. Exact scaffold signatures migrate older
|
|
1146
|
+
* generated copies without treating arbitrary repository documentation as
|
|
1147
|
+
* managed.
|
|
1122
1148
|
* @param {string} file
|
|
1123
1149
|
* @param {string} content
|
|
1124
1150
|
*/
|
|
@@ -1157,6 +1183,45 @@ function git(target, args) {
|
|
|
1157
1183
|
spawnSync('git', args, { cwd: target, stdio: 'ignore' })
|
|
1158
1184
|
}
|
|
1159
1185
|
|
|
1186
|
+
/**
|
|
1187
|
+
* Merge the marked policy blocks from a rendered baseline into a consumer-owned
|
|
1188
|
+
* policy document. Unmarked AGENTS.md/CONTRIBUTING.md files are commonly
|
|
1189
|
+
* generated by another agent initializer, so sync must add and refresh the
|
|
1190
|
+
* Code Foundry contract without replacing the user's surrounding instructions.
|
|
1191
|
+
* @param {string} existing
|
|
1192
|
+
* @param {string} baseline
|
|
1193
|
+
* @returns {string}
|
|
1194
|
+
*/
|
|
1195
|
+
function mergeManagedPolicyBlocks(existing, baseline) {
|
|
1196
|
+
const blockPattern =
|
|
1197
|
+
/<!-- code-foundry-managed: ([A-Za-z0-9_-]+) -->[\s\S]*?<!-- \/code-foundry-managed: \1 -->/g
|
|
1198
|
+
const blocks = [...baseline.matchAll(blockPattern)]
|
|
1199
|
+
if (!blocks.length) return existing
|
|
1200
|
+
|
|
1201
|
+
let merged = existing
|
|
1202
|
+
for (const match of blocks) {
|
|
1203
|
+
const id = match[1]
|
|
1204
|
+
const block = match[0]
|
|
1205
|
+
const start = `<!-- code-foundry-managed: ${id} -->`
|
|
1206
|
+
const end = `<!-- /code-foundry-managed: ${id} -->`
|
|
1207
|
+
const pattern = new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}`, 'g')
|
|
1208
|
+
const occurrences = merged.match(pattern) ?? []
|
|
1209
|
+
if (occurrences.length > 1) {
|
|
1210
|
+
throw new Error(`Managed policy block appears more than once in consumer document: ${id}`)
|
|
1211
|
+
}
|
|
1212
|
+
if (occurrences.length === 1) {
|
|
1213
|
+
merged = merged.replace(pattern, block)
|
|
1214
|
+
continue
|
|
1215
|
+
}
|
|
1216
|
+
if (merged.includes(start) || merged.includes(end)) {
|
|
1217
|
+
throw new Error(`Managed policy block is incomplete in consumer document: ${id}`)
|
|
1218
|
+
}
|
|
1219
|
+
const separator = merged.endsWith('\n\n') ? '' : merged.endsWith('\n') ? '\n' : '\n\n'
|
|
1220
|
+
merged = `${merged}${separator}${block}\n`
|
|
1221
|
+
}
|
|
1222
|
+
return merged
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1160
1225
|
/** @param {string} file @param {Buffer|string} content @param {boolean} dryRun */
|
|
1161
1226
|
function writeOrReport(file, content, dryRun) {
|
|
1162
1227
|
if (dryRun) {
|