code-foundry 0.38.1 → 0.39.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.
@@ -15,7 +15,7 @@ runtime_ref: v0.34.4
15
15
  release_type: node
16
16
  npm_publish: true
17
17
  license: agpl-3.0-or-later
18
- git_workflow: staging-release
18
+ git_workflow: direct
19
19
  merge_strategy: rebase
20
20
  release_merge_strategy: rebase
21
21
  runner: ubuntu-latest
@@ -22,7 +22,7 @@ jobs:
22
22
  uses: ./.github/workflows/draft-pr.yml
23
23
  with:
24
24
  runner: ubuntu-slim
25
- base: staging
25
+ base: main
26
26
  secrets:
27
27
  CODE_FOUNDRY_TOKEN: ${{ secrets.CODE_FOUNDRY_TOKEN }}
28
28
  RELEASE_PLEASE_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}
@@ -46,6 +46,7 @@ jobs:
46
46
  # reusable workflow already checks out the consumer repository.
47
47
  source_ref: b3dce823322672b285fbe99b870ea984c01826cb
48
48
  runner: ubuntu-latest
49
+ model: opencode-go/muse-spark-1.2-contributor
49
50
  mode: standard
50
51
  fail_on_severity: high
51
52
  max_cost_usd: 1
@@ -15,11 +15,12 @@ on:
15
15
  required: false
16
16
 
17
17
  permissions:
18
- contents: read
18
+ contents: write
19
19
  pull-requests: write
20
20
 
21
21
  env:
22
22
  HAS_AUTOMATION_TOKEN: ${{ secrets.CODE_FOUNDRY_TOKEN != '' || secrets.RELEASE_PLEASE_TOKEN != '' }}
23
+ PROMOTION_BRANCH: code-foundry/promote/staging-to-main
23
24
 
24
25
  jobs:
25
26
  create-release-pr:
@@ -44,8 +45,11 @@ jobs:
44
45
  # persist-credentials disabled, git has no auth for that fetch; configure
45
46
  # the credential helper with the short-lived workflow token. NiftyLeague
46
47
  # rejects long-lived fine-grained automation tokens with HTTP 403 even
47
- # on REST, so all read-only operations use github.token; it is scoped
48
- # to contents: read, so this never enables write credentials.
48
+ # on REST, so all read-only operations use github.token. It is used
49
+ # only for GitHub operations owned by this workflow. The job's
50
+ # contents: write permission is required later for the isolated,
51
+ # deterministic promotion ref; protected main and staging are never
52
+ # written by this workflow.
49
53
  env:
50
54
  GH_TOKEN: ${{ github.token }}
51
55
  run: gh auth setup-git
@@ -65,7 +69,7 @@ jobs:
65
69
  # REST query keeps the exact base/head/state filter and the per_page=1
66
70
  # cap (existing is 0 or 1) and fails closed on any API error.
67
71
  EXISTING=$(gh api \
68
- "repos/${GITHUB_REPOSITORY}/pulls?base=main&head=staging&state=open&per_page=1" \
72
+ "repos/${GITHUB_REPOSITORY}/pulls?base=main&head=${PROMOTION_BRANCH}&state=open&per_page=1" \
69
73
  --jq 'length')
70
74
  MAIN_TREE=$(gh api "repos/${GITHUB_REPOSITORY}/commits/main" --jq '.commit.tree.sha')
71
75
  STAGING_TREE=$(gh api "repos/${GITHUB_REPOSITORY}/commits/staging" --jq '.commit.tree.sha')
@@ -144,6 +148,51 @@ jobs:
144
148
  echo 'staging is already aligned with main; no promotion PR is needed.'
145
149
  fi
146
150
 
151
+ - name: Prepare exact-tree promotion head
152
+ if: steps.check-pr.outputs.commits != '0' && steps.check-pr.outputs.tree_equal != 'true' && steps.check-pr.outputs.release_only != 'true'
153
+ env:
154
+ GH_TOKEN: ${{ github.token }}
155
+ AUTOMATION_TOKEN: ${{ secrets.CODE_FOUNDRY_TOKEN || secrets.RELEASE_PLEASE_TOKEN }}
156
+ run: |
157
+ set -euo pipefail
158
+ MAIN_SHA=$(git rev-parse origin/main)
159
+ STAGING_SHA=$(git rev-parse origin/staging)
160
+ STAGING_TREE=$(git rev-parse 'origin/staging^{tree}')
161
+ STAGING_SUBJECT=$(git log -1 --format=%s origin/staging)
162
+ COMMIT_FILE="$RUNNER_TEMP/promotion-commit.json"
163
+ node - "$MAIN_SHA" "$STAGING_SHA" "$STAGING_TREE" "$STAGING_SUBJECT" <<'NODE' > "$COMMIT_FILE"
164
+ const [main, staging, tree, subject] = process.argv.slice(2)
165
+ process.stdout.write(JSON.stringify({
166
+ message: `${subject}\n\nPromoted from staging snapshot ${staging.slice(0, 12)}.`,
167
+ tree,
168
+ parents: [main],
169
+ }))
170
+ NODE
171
+ PROMOTION_SHA=$(gh api \
172
+ --method POST \
173
+ "repos/${GITHUB_REPOSITORY}/git/commits" \
174
+ --input "$COMMIT_FILE" \
175
+ --jq '.sha')
176
+ REF_ENDPOINT="repos/${GITHUB_REPOSITORY}/git/refs/heads/${PROMOTION_BRANCH}"
177
+ if gh api "$REF_ENDPOINT" >/dev/null 2>&1; then
178
+ UPDATED=false
179
+ if [ "$HAS_AUTOMATION_TOKEN" = true ] && GH_TOKEN="$AUTOMATION_TOKEN" gh api --method PATCH "$REF_ENDPOINT" --field sha="$PROMOTION_SHA" --field force=true >/dev/null; then
180
+ UPDATED=true
181
+ fi
182
+ if [ "$UPDATED" != true ]; then
183
+ if [ "$HAS_AUTOMATION_TOKEN" = true ]; then
184
+ echo "::warning title=Promotion token fallback::Automation token could not refresh the promotion ref; using the workflow token. GitHub may require a manual check rerun for an already-open pull request."
185
+ fi
186
+ gh api --method PATCH "$REF_ENDPOINT" --field sha="$PROMOTION_SHA" --field force=true >/dev/null
187
+ fi
188
+ else
189
+ gh api --method POST \
190
+ "repos/${GITHUB_REPOSITORY}/git/refs" \
191
+ --field ref="refs/heads/${PROMOTION_BRANCH}" \
192
+ --field sha="$PROMOTION_SHA" >/dev/null
193
+ fi
194
+ echo "Prepared ${PROMOTION_BRANCH} at ${PROMOTION_SHA} with parent ${MAIN_SHA} and the exact staging tree ${STAGING_TREE}."
195
+
147
196
  - name: Create
148
197
  if: steps.check-pr.outputs.existing == '0' && steps.check-pr.outputs.commits != '0' && steps.check-pr.outputs.tree_equal != 'true' && steps.check-pr.outputs.release_only != 'true'
149
198
  env:
@@ -203,7 +252,7 @@ jobs:
203
252
  "repos/${GITHUB_REPOSITORY}/pulls"
204
253
  --method POST
205
254
  --field base=main
206
- --field head=staging
255
+ --field head="$PROMOTION_BRANCH"
207
256
  --field title="Promote staging -> main ($DATE)"
208
257
  --field body=@"$BODY_FILE"
209
258
  )
@@ -237,7 +286,7 @@ jobs:
237
286
  # the short-lived workflow token; gh pr list issues a GraphQL query
238
287
  # that NiftyLeague's long-lived fine-grained token policy rejects.
239
288
  EXISTING_NUMBER=$(GH_TOKEN="${{ github.token }}" gh api \
240
- "repos/${GITHUB_REPOSITORY}/pulls?base=main&head=staging&state=open&per_page=1" \
289
+ "repos/${GITHUB_REPOSITORY}/pulls?base=main&head=${PROMOTION_BRANCH}&state=open&per_page=1" \
241
290
  --jq '.[0].number // empty')
242
291
  if [ -z "$EXISTING_NUMBER" ]; then
243
292
  echo 'Pull request creation failed and no existing promotion PR was found.' >&2
@@ -6,7 +6,7 @@ on:
6
6
  workflow_dispatch:
7
7
 
8
8
  permissions:
9
- contents: read
9
+ contents: write
10
10
  pull-requests: write
11
11
 
12
12
  jobs:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.39.0](https://github.com/0xPlayerOne/code-foundry/compare/v0.38.2...v0.39.0) (2026-08-29)
4
+
5
+
6
+ ### Features
7
+
8
+ * code-foundry 0.38.3 — direct workflow default, muse spark model, opencode_security_model override ([436da59](https://github.com/0xPlayerOne/code-foundry/commit/436da5947098039642cb961987e962cd88960db8))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * draft-pr base main for direct workflow (staging deleted) ([b1c063f](https://github.com/0xPlayerOne/code-foundry/commit/b1c063fa8e967800b85ffb9ae42df5d62e9a4e1e))
14
+
15
+ ## [0.38.2](https://github.com/0xPlayerOne/code-foundry/compare/v0.38.1...v0.38.2) (2026-08-29)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **release:** preserve promotion release semantics ([#418](https://github.com/0xPlayerOne/code-foundry/issues/418)) ([b1702a7](https://github.com/0xPlayerOne/code-foundry/commit/b1702a756cc6d7297f98b529b0bca178a1cdc3d1))
21
+
3
22
  ## [0.38.1](https://github.com/0xPlayerOne/code-foundry/compare/v0.38.0...v0.38.1) (2026-08-29)
4
23
 
5
24
 
package/docs/RELEASES.md CHANGED
@@ -61,6 +61,14 @@ possible. The final branch trees are inspected before mutation; validated
61
61
  main-only changes are inherited by `staging`, while unpromoted staging work is
62
62
  replayed on top of `main`.
63
63
 
64
+ Promotion never opens a pull request directly from a divergent `staging`
65
+ history. The workflow creates or refreshes a deterministic promotion branch
66
+ whose single commit has the current `main` tip as its parent and the exact
67
+ validated `staging` tree. This keeps the review diff limited to the actual
68
+ environment delta even after squash or rebase merges have changed ancestry.
69
+ The generated commit preserves the staged tip's conventional subject so
70
+ Release Please still detects the appropriate release type after promotion.
71
+
64
72
  The `staging` → `main` reconciliation exists only in the `staging-release`
65
73
  topology. Patch-equivalent divergence between `main` and `staging` is treated
66
74
  as aligned. When `staging` has pending commits that are not yet represented on
package/docs/WORKFLOWS.md CHANGED
@@ -95,7 +95,7 @@ workflow refuses to run unless its strategy is exactly `rebase`.
95
95
  | --- | --- | --- |
96
96
  | Feature/fix PR into `main` (direct topology) | Squash | Contribution policy; see `CONTRIBUTING.md` |
97
97
  | Feature/fix PR into `staging` (staging-release topology) | Squash | Contribution policy; see `CONTRIBUTING.md` |
98
- | `staging` → `main` promotion PR (staging-release topology) | Rebase (`merge_strategy: rebase`) | `merge_strategy` must be `rebase` when `git_workflow: staging-release`; merge commits are rejected |
98
+ | `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 |
99
99
  | Release Please version PR into `main` | Rebase (`release_merge_strategy: rebase`) | Release automation fails closed unless `rebase`; never defaults to `merge`, never uses `--admin` |
100
100
 
101
101
  The promotion rows above apply only to `staging-release`; `direct`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "0.38.1",
3
+ "version": "0.39.0",
4
4
  "description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-or-later",
@@ -388,6 +388,10 @@ function renderWorkflow(content, config, repository, ref, rustCodeql) {
388
388
  rendered = rendered.replace(/^(\s+rust-threads:)\s+.*$/m, `$1 '${rustCodeql.threads}'`)
389
389
  rendered = rendered.replace(/^(\s+rust-max-parallel:)\s+.*$/m, `$1 ${rustCodeql.maxParallel}`)
390
390
  }
391
+ if (workflow === 'opencode-security') {
392
+ const model = configured(config.opencode_security_model, '').trim()
393
+ if (model) rendered = rendered.replace(/^(\s+model:)\s+.*$/m, `$1 ${model}`)
394
+ }
391
395
  return rendered
392
396
  }
393
397
 
@@ -645,7 +649,7 @@ function createDefaultConfig(root, source) {
645
649
  prune_standard: 'false', cache_packages: 'auto', cache_build: 'auto', coverage_minimum: '80', turbo_remote: 'auto',
646
650
  release_type: detectPackageManager(root) === 'none' ? 'auto' : 'node', npm_publish: 'false',
647
651
  post_release: 'false', post_release_workflow: '', post_release_mode: 'auto',
648
- opencode_security: 'false',
652
+ opencode_security: 'false', opencode_security_model: '',
649
653
  sync_mode: 'overlay', custom_workflows: 'preserve',
650
654
  license: existsSync(join(root, 'LICENSE')) ? 'preserve' : 'gpl-3.0-or-later', git_workflow: 'direct', merge_strategy: 'rebase', release_merge_strategy: 'rebase',
651
655
  }