Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a64bb6f0ec | ||
|
|
efb8b4af8a | ||
|
|
737b434086 | ||
|
|
a0226ec6ca | ||
|
|
0af47ae91c | ||
|
|
3f135aa12c | ||
|
|
1e81d7ab7f | ||
|
|
03f3988923 | ||
|
|
02120d5ed7 | ||
|
|
c4feddb177 | ||
|
|
3509ad5322 | ||
|
|
9c077cb83d | ||
|
|
dfd067fcce | ||
|
|
dbeea2fac8 | ||
|
|
2eab23680b | ||
|
|
3328a61c5d | ||
|
|
1afa0e226e | ||
|
|
655e3ec7e9 | ||
|
|
e36f12c2c0 | ||
|
|
3517a4ceb9 | ||
|
|
5bda3137bf | ||
|
|
c2e6ecae18 | ||
|
|
7822aacdf7 | ||
|
|
591d5c0384 | ||
|
|
20bd90a6cf | ||
|
|
5fd562dbb4 | ||
|
|
35a25812f0 | ||
|
|
86f6f1554b | ||
|
|
d3012f37e1 | ||
|
|
f65fe8c66b | ||
|
|
a4583ad617 | ||
|
|
695c499d18 | ||
|
|
41e513d913 | ||
|
|
b3f2bef690 | ||
|
|
bc9babc942 | ||
|
|
efb252744f | ||
|
|
cb439b0ba3 | ||
|
|
c6f2351a82 | ||
|
|
2032e54015 | ||
|
|
618e138abc | ||
|
|
aa413f9fcd | ||
|
|
edc41e72e0 | ||
|
|
16098835a1 | ||
|
|
b206b8c172 | ||
|
|
d78e5ca622 | ||
|
|
81b499e7b2 | ||
|
|
d78b83f1b0 | ||
|
|
0b195a3e89 | ||
|
|
f617d26d17 | ||
|
|
ae5d6cdec9 | ||
|
|
bf73db37f9 | ||
|
|
4d9d5f6fd6 | ||
|
|
ec2c9a54cd | ||
|
|
63ee70e96b | ||
|
|
c27fbd556e | ||
|
|
24c2236a27 | ||
|
|
7784e7ed2a | ||
|
|
ddde24e30f | ||
|
|
7aff842cee | ||
|
|
9f614cd9cb | ||
|
|
c107eba924 | ||
|
|
8c83f43461 | ||
|
|
dccc1e9d20 | ||
|
|
7abed48a1d | ||
|
|
b91d92320d | ||
|
|
802c50144b | ||
|
|
318c6aa783 | ||
|
|
bff779580a | ||
|
|
2faa61da8b | ||
|
|
ca1b87b92f | ||
|
|
3f8f87168e | ||
|
|
139c2bd7ca | ||
|
|
dd88563ad2 | ||
|
|
f6be9ee218 | ||
|
|
9621eae41b | ||
|
|
8bf425cae5 | ||
|
|
ba8caf14ed | ||
|
|
3744bea20a | ||
|
|
c2fef2107c | ||
|
|
2b9c6eb7ba | ||
|
|
0e4fbb6191 | ||
|
|
d93082518f | ||
|
|
a8b00b942a | ||
|
|
9ba76934be | ||
|
|
f2769d1525 | ||
|
|
b7fb6033c6 | ||
|
|
bdc766e85a | ||
|
|
9c46b11437 | ||
|
|
64aac4f5d0 | ||
|
|
83d72ba3eb | ||
|
|
947b4f7cfb | ||
|
|
304401b06c | ||
|
|
0177f9dbf8 | ||
|
|
c26af3794b | ||
|
|
4624aa8c2a | ||
|
|
97d1f905b5 | ||
|
|
93e8bc9325 | ||
|
|
1baf7927e4 | ||
|
|
f75b927ad0 | ||
|
|
77d4bf4caf | ||
|
|
adbd38c741 | ||
|
|
24212cf196 | ||
|
|
d79ef5486f | ||
|
|
9360abecc9 | ||
|
|
4ade25b012 | ||
|
|
b2fbb869b3 | ||
|
|
931ca1d651 | ||
|
|
c6fb61cc62 | ||
|
|
8f00b756ff | ||
|
|
831dd78859 | ||
|
|
619956b89e | ||
|
|
a355acf751 | ||
|
|
1d078e65e3 | ||
|
|
d2e244f15d | ||
|
|
b326601c7b | ||
|
|
0e94d03f98 | ||
|
|
f925080fd8 | ||
|
|
144cf99501 | ||
|
|
e62214b244 | ||
|
|
5f023906fa | ||
|
|
143d778d83 | ||
|
|
98d10bd3a4 | ||
|
|
ac07a222ec | ||
|
|
1c698e06d2 | ||
|
|
ea1b255f07 | ||
|
|
b90ae92676 | ||
|
|
1cd83778df | ||
|
|
23fe770f6c | ||
|
|
150f2398ab | ||
|
|
cd9dbed95b | ||
|
|
0a51075d59 | ||
|
|
10fb8121a6 | ||
|
|
e8ec565cac | ||
|
|
64af9ade91 | ||
|
|
06ddc240cc | ||
|
|
1a74720f12 | ||
|
|
10ab4417e2 | ||
|
|
b6bd4f43a1 | ||
|
|
2e45e480bc | ||
|
|
defa9b245b | ||
|
|
c899d18638 | ||
|
|
328cc1ad67 | ||
|
|
3a60329c0a | ||
|
|
15d5410dc4 | ||
|
|
556c30b2c7 | ||
|
|
2a7467d698 | ||
|
|
4ffe6197f0 | ||
|
|
db9a5841a0 | ||
|
|
2c17d5d794 | ||
|
|
841a943c2e | ||
|
|
84625d5dc8 | ||
|
|
c14926915b | ||
|
|
2dbf79a653 | ||
|
|
79864451c5 | ||
|
|
f484af3bb5 | ||
|
|
78c1e34289 | ||
|
|
f67df0d6b0 | ||
|
|
811e915e1a | ||
|
|
ca566a6cf8 | ||
|
|
107f0a1af8 | ||
|
|
031801eb39 | ||
|
|
93062933c8 | ||
|
|
86293593b1 | ||
|
|
d2892275b3 | ||
|
|
57851e6c08 | ||
|
|
4a005dcf02 | ||
|
|
07d4aa5fd7 | ||
|
|
ae69f61e1d | ||
|
|
dc2ff3b61c | ||
|
|
24908ef9c7 | ||
|
|
04a7d0e2f9 | ||
|
|
003c75442d | ||
|
|
16d79bfb83 | ||
|
|
3c6d457cbb | ||
|
|
577a75f8cd | ||
|
|
c1669e15f0 | ||
|
|
9f9d07b010 | ||
|
|
fdd25f026f | ||
|
|
701e9c2eb2 | ||
|
|
6ba774ebd4 | ||
|
|
4c2d3b0865 | ||
|
|
e3af1fb85c | ||
|
|
f5a23d3410 | ||
|
|
cfd77a6d31 | ||
|
|
f170678a04 | ||
|
|
5dc55dd6ba | ||
|
|
56d3476853 | ||
|
|
254c1e0c2a | ||
|
|
16ce829e1a | ||
|
|
f5d6e229a6 | ||
|
|
ef4cbe7bf4 | ||
|
|
5954a1c21c | ||
|
|
8efd535f52 | ||
|
|
42327a8590 | ||
|
|
a28a616ca7 | ||
|
|
2410bb4ee9 | ||
|
|
d74e4b932d | ||
|
|
4d4c941563 | ||
|
|
f1671fce20 | ||
|
|
ddcd98faa5 | ||
|
|
6fcbc6ee67 | ||
|
|
14cfc5d2f1 | ||
|
|
4e397dd2b1 | ||
|
|
94e527b7f4 | ||
|
|
30c20593d8 | ||
|
|
249e55bd16 | ||
|
|
31ee52d8c0 | ||
|
|
14ff2853da | ||
|
|
155feb3830 | ||
|
|
d403a7a681 | ||
|
|
f63be865f6 | ||
|
|
da039916cd | ||
|
|
0e2368e66d | ||
|
|
084955f918 | ||
|
|
70f107e73c | ||
|
|
a72af2a002 | ||
|
|
28f2384f06 | ||
|
|
55f9ee8616 | ||
|
|
a65e878713 | ||
|
|
9e59f172cf | ||
|
|
84d7ee0fea | ||
|
|
21de375a03 | ||
|
|
d232ee8a12 | ||
|
|
100a15f64e | ||
|
|
7727772699 | ||
|
|
b8655f4c21 | ||
|
|
35fc00701e | ||
|
|
6d07916dd8 | ||
|
|
ab629d8f94 | ||
|
|
0ad2e7b2a3 | ||
|
|
98bde07557 | ||
|
|
de8d8be149 | ||
|
|
f78a517955 | ||
|
|
b2786a20b6 | ||
|
|
aeb7096d77 | ||
|
|
2f1dcd4823 | ||
|
|
cbf4df1f7f | ||
|
|
e29aec234d | ||
|
|
5de6fee2b2 | ||
|
|
550e9f9fea | ||
|
|
4b57d4c26e | ||
|
|
49841daef7 | ||
|
|
340a820cf4 | ||
|
|
592f36848a | ||
|
|
2eebf97e36 | ||
|
|
1441a785ee |
No files matched your search
@@ -1,5 +0,0 @@
|
||||
# No suppressions: the old Starlark dependencies are absent from Cargo.lock.
|
||||
# Keep remaining maintenance warnings visible in cargo-audit. The separately
|
||||
# scoped cargo-deny exception is explained in docs/dependency-maintenance.md.
|
||||
[advisories]
|
||||
ignore = []
|
||||
@@ -1,253 +0,0 @@
|
||||
# CNB is a one-way mirror from GitHub. Keep this file source-controlled here;
|
||||
# CNB-side edits will be overwritten by the GitHub -> CNB sync workflow.
|
||||
|
||||
.feishu_bridge_tests: &feishu_bridge_tests
|
||||
name: feishu bridge tests
|
||||
runner:
|
||||
tags: cnb:arch:amd64
|
||||
cpus: 8
|
||||
docker:
|
||||
image: node:22-bookworm
|
||||
stages:
|
||||
- name: feishu bridge tests
|
||||
script: |
|
||||
set -eu
|
||||
cd integrations/feishu-bridge
|
||||
npm ci
|
||||
npm run check
|
||||
npm test
|
||||
|
||||
.rust_workspace_gates_stage: &rust_workspace_gates_stage
|
||||
name: rust workspace gates
|
||||
# The all-feature TUI test crate is large enough that concurrent rustc and
|
||||
# clippy processes or disposable test debug metadata can exceed the shared
|
||||
# CNB runner's memory. Keep the full gate surface, but serialize Cargo,
|
||||
# omit test-only debug tables, and use the established workspace-test stack
|
||||
# size so deep runtime API tests do not abort on the platform default.
|
||||
timeout: 45m
|
||||
script: |
|
||||
set -eu
|
||||
export CARGO_BUILD_JOBS=1
|
||||
export CARGO_PROFILE_TEST_DEBUG=0
|
||||
./scripts/release/check-versions.sh
|
||||
./scripts/release/check-ohos-deps.sh
|
||||
cargo fmt --all -- --check
|
||||
cargo check --workspace --all-targets --locked
|
||||
cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
|
||||
cargo build -p codewhale-cli --bin codewhale --all-features --locked
|
||||
# Use the shared test HOME boundary while retaining the real toolchain.
|
||||
RUST_MIN_STACK=16777216 sh scripts/with-hermetic-test-home.sh cargo test --workspace --all-features --locked
|
||||
# Parity gates as first-class steps so drift surfaces as a named failure,
|
||||
# not a buried workspace-test entry. Mirrors release.yml's parity job.
|
||||
sh scripts/with-hermetic-test-home.sh cargo test -p codewhale-protocol --test parity_protocol --locked
|
||||
sh scripts/with-hermetic-test-home.sh cargo test -p codewhale-state --test parity_state --locked
|
||||
|
||||
.linux_rust_gates: &linux_rust_gates
|
||||
name: linux rust gates
|
||||
runner:
|
||||
tags: cnb:arch:amd64
|
||||
cpus: 16
|
||||
docker:
|
||||
image: rust:1.100.0-bookworm
|
||||
# codewhale-cnb-bridge GitHub App credentials from the CNB KeyStore
|
||||
# (codewhale.net/codewhale-ci-secrets, github-bridge.yml), injected as
|
||||
# environment variables. Values are never printed.
|
||||
# https://docs.cnb.cool/en/repo/secret.html
|
||||
imports:
|
||||
- https://cnb.cool/codewhale.net/codewhale-ci-secrets/-/blob/main/github-bridge.yml
|
||||
stages:
|
||||
- name: install linux dependencies
|
||||
script: |
|
||||
set -eu
|
||||
apt-get update
|
||||
apt-get install -y git libdbus-1-dev nodejs npm pkg-config
|
||||
if command -v rustup >/dev/null 2>&1; then
|
||||
rustup component add rustfmt clippy
|
||||
fi
|
||||
|
||||
- *rust_workspace_gates_stage
|
||||
|
||||
- name: linux npm wrapper smoke
|
||||
# Full LTO can link silently for longer than CNB's default 10-minute
|
||||
# no-output window. Keep the production profile and give the job enough
|
||||
# time to emit its version and wrapper receipts.
|
||||
timeout: 45m
|
||||
script: |
|
||||
set -eu
|
||||
# The release profile uses full LTO and one codegen unit. Bound Cargo's
|
||||
# parallelism so the final links cannot exhaust a shared CNB runner.
|
||||
cargo build --jobs 2 --release --locked -p codewhale-cli
|
||||
cp target/release/codewhale target/release/codew
|
||||
export PATH="$PWD/target/release:$PATH"
|
||||
node scripts/release/npm-wrapper-smoke.js
|
||||
./target/release/codewhale --version
|
||||
./target/release/codew --version
|
||||
|
||||
# Shadow-parity bridge (ops design: CNB-PRIMARY-CI-DESIGN-20260830). Post one
|
||||
# non-required "-cnb" Check Run on the exact GitHub SHA being built so the
|
||||
# CNB verdict is visible on GitHub while GitHub Actions stays the canonical,
|
||||
# required CI. endStages always run and cannot fail the pipeline, so a
|
||||
# bridge outage never turns a green CNB build red (and vice versa: the CNB
|
||||
# verdict is reported from CNB_PIPELINE_STATUS, not from this stage).
|
||||
endStages:
|
||||
- name: github shadow check run
|
||||
script: |
|
||||
set -eu
|
||||
case "${CNB_PIPELINE_STATUS:-error}" in
|
||||
success) conclusion="success" ;;
|
||||
cancel) conclusion="cancelled" ;;
|
||||
*) conclusion="failure" ;;
|
||||
esac
|
||||
node scripts/ci/cnb-github-checkrun.mjs \
|
||||
--name "linux rust gates -cnb" \
|
||||
--sha "${CNB_COMMIT}" \
|
||||
--status completed \
|
||||
--conclusion "${conclusion}" \
|
||||
--details-url "${CNB_BUILD_WEB_URL:-https://cnb.cool/${CNB_REPO_SLUG}}" \
|
||||
--summary "CNB pipeline '${CNB_PIPELINE_NAME:-linux rust gates}' finished with status ${CNB_PIPELINE_STATUS:-unknown} on ${CNB_BRANCH:-unknown branch} (${CNB_COMMIT}). Shadow lane per the CNB-primary CI design: GitHub Actions remains canonical and required; nothing is gated on this check."
|
||||
|
||||
.linux_release_preflight: &linux_release_preflight
|
||||
name: linux release preflight
|
||||
runner:
|
||||
tags: cnb:arch:amd64
|
||||
cpus: 16
|
||||
docker:
|
||||
image: rust:1.100.0-bookworm
|
||||
stages:
|
||||
- name: install release dependencies
|
||||
script: |
|
||||
set -eu
|
||||
apt-get update
|
||||
apt-get install -y curl git libdbus-1-dev nodejs npm pkg-config
|
||||
if command -v rustup >/dev/null 2>&1; then
|
||||
rustup component add rustfmt clippy
|
||||
fi
|
||||
|
||||
- *rust_workspace_gates_stage
|
||||
|
||||
- name: crate publish dry-run
|
||||
script: |
|
||||
set -eu
|
||||
./scripts/release/publish-crates.sh dry-run
|
||||
|
||||
- name: release binary smoke
|
||||
# Full LTO can link silently for longer than CNB's default 10-minute
|
||||
# no-output window. Keep the production profile and give the job enough
|
||||
# time to emit its version and wrapper receipts.
|
||||
timeout: 45m
|
||||
script: |
|
||||
set -eu
|
||||
# Keep the production release profile intact while avoiding a burst of
|
||||
# concurrent rustc/linker processes on the shared release runner.
|
||||
cargo build --jobs 2 --release --locked -p codewhale-cli
|
||||
cp target/release/codewhale target/release/codew
|
||||
export PATH="$PWD/target/release:$PATH"
|
||||
node scripts/release/npm-wrapper-smoke.js
|
||||
./target/release/codewhale --version
|
||||
./target/release/codew --version
|
||||
|
||||
main:
|
||||
push:
|
||||
- *feishu_bridge_tests
|
||||
- *linux_rust_gates
|
||||
|
||||
"(fix/*|rebrand/*)":
|
||||
push:
|
||||
- *linux_rust_gates
|
||||
|
||||
"work/v*":
|
||||
push:
|
||||
- *feishu_bridge_tests
|
||||
- *linux_release_preflight
|
||||
|
||||
$:
|
||||
tag_push:
|
||||
- docker:
|
||||
image: rust:1.100.0-bookworm
|
||||
stages:
|
||||
- name: build linux x64 release assets (static)
|
||||
# The static full-LTO link can also outlive CNB's default no-output
|
||||
# window. Do not weaken the release profile to keep the runner alive.
|
||||
timeout: 45m
|
||||
script: |
|
||||
set -eu
|
||||
|
||||
apt-get update
|
||||
apt-get install -y git musl-tools nodejs pkg-config
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
|
||||
./scripts/release/check-versions.sh --require-dated-release
|
||||
./scripts/release/check-ohos-deps.sh
|
||||
checkout_sha="$(git rev-parse 'HEAD^{commit}')"
|
||||
commit_sha="${CNB_COMMIT:-${checkout_sha}}"
|
||||
if [ "$commit_sha" != "$checkout_sha" ]; then
|
||||
echo "ERROR: CNB_COMMIT ${commit_sha} does not match checkout ${checkout_sha}" >&2
|
||||
exit 1
|
||||
fi
|
||||
export CODEWHALE_BUILD_SHA="$commit_sha"
|
||||
cargo build --jobs 2 --release --locked \
|
||||
--target x86_64-unknown-linux-musl \
|
||||
-p codewhale-cli # single binary
|
||||
|
||||
mkdir -p target/cnb-release
|
||||
BIN_DIR="target/x86_64-unknown-linux-musl/release"
|
||||
cp "$BIN_DIR/codewhale" target/cnb-release/codewhale-linux-x64
|
||||
cp "$BIN_DIR/codewhale" target/cnb-release/codew-linux-x64
|
||||
cp "$BIN_DIR/codewhale" target/cnb-release/codewhale-tui-linux-x64
|
||||
strip \
|
||||
target/cnb-release/codewhale-linux-x64 \
|
||||
target/cnb-release/codew-linux-x64 \
|
||||
target/cnb-release/codewhale-tui-linux-x64 \
|
||||
|| true
|
||||
|
||||
(
|
||||
cd target/cnb-release
|
||||
sha256sum \
|
||||
codewhale-linux-x64 \
|
||||
codew-linux-x64 \
|
||||
codewhale-tui-linux-x64 \
|
||||
> codewhale-artifacts-sha256.txt
|
||||
)
|
||||
|
||||
tag_name="${CNB_BRANCH:-}"
|
||||
if [ -z "$tag_name" ]; then
|
||||
tag_name="$(git describe --tags --exact-match 2>/dev/null || true)"
|
||||
fi
|
||||
version="${tag_name#v}"
|
||||
cargo_version="$(grep -E '^version = "' Cargo.toml | head -n1 | sed -E 's/^version = "([^"]+)".*/\1/')"
|
||||
if [ -n "$tag_name" ] && [ "$version" != "$cargo_version" ]; then
|
||||
echo "ERROR: tag ${tag_name} does not match Cargo.toml version ${cargo_version}" >&2
|
||||
exit 1
|
||||
fi
|
||||
{
|
||||
echo "# ${tag_name:-CNB release}"
|
||||
echo
|
||||
awk -v version="${version}" '
|
||||
index($0, "## [" version "]") == 1 { in_section = 1; next }
|
||||
in_section && /^## \[/ { exit }
|
||||
in_section { print }
|
||||
' CHANGELOG.md
|
||||
echo
|
||||
echo "Built by CNB from ${commit_sha}."
|
||||
echo
|
||||
echo "Assets:"
|
||||
echo "- codewhale-linux-x64"
|
||||
echo "- codew-linux-x64"
|
||||
echo "- codewhale-tui-linux-x64 (v0.9.4 compatibility alias)"
|
||||
echo "- codewhale-artifacts-sha256.txt"
|
||||
} > target/cnb-release/CNB_RELEASE.md
|
||||
|
||||
- name: create cnb release
|
||||
type: git:release
|
||||
options:
|
||||
descriptionFromFile: target/cnb-release/CNB_RELEASE.md
|
||||
latest: true
|
||||
|
||||
- name: upload linux x64 release assets
|
||||
image: cnbcool/attachments:latest
|
||||
settings:
|
||||
attachments:
|
||||
- target/cnb-release/codewhale-linux-x64
|
||||
- target/cnb-release/codew-linux-x64
|
||||
- target/cnb-release/codewhale-tui-linux-x64
|
||||
- target/cnb-release/codewhale-artifacts-sha256.txt
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"authority": [
|
||||
"the user's request, this turn",
|
||||
"this constitution",
|
||||
"project law and instructions — nearest in scope wins",
|
||||
"standing user-global preferences",
|
||||
"memory and previous-session handoffs"
|
||||
],
|
||||
"protected_invariants": [
|
||||
"Keep the active first-turn tool-catalog head byte-stable (DeepSeek KV prefix-cache invariant); changes to it must be one-time and deterministic.",
|
||||
"Preserve old-session transcript replay: never remove a tool's registration just because it is deprecated/hidden.",
|
||||
"Stable Rust only (edition 2024); no nightly features.",
|
||||
"Keep the codewhale CLI dispatcher and the codewhale-tui binary in sync when crates/tui changes.",
|
||||
"Precedence is stated only in BASE_PROMPT § Whose word wins; other layers describe behavior, not rank."
|
||||
],
|
||||
"branch_policy": "Start from live branch and handoff truth. Never commit directly to main; use the active integration branch or a fresh codex/... branch/worktree for isolated work, and open reviewable PRs into main. One PR per logical workstream; do not mix unrelated fixes.",
|
||||
"verification_policy": {
|
||||
"before_claiming_done": [
|
||||
"run the focused tests for the changed crate (cargo test -p <crate>), then cargo check/clippy as appropriate",
|
||||
"read changed files back to confirm the edit landed as intended",
|
||||
"never claim verification you did not perform"
|
||||
]
|
||||
},
|
||||
"escalate_when": [
|
||||
"an action is destructive or hard to reverse and was not explicitly authorized",
|
||||
"changing provider/auth/config or anything that sends data to an external service",
|
||||
"deleting or overwriting files you did not create, or that contradict how they were described"
|
||||
]
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
[[hooks]]
|
||||
name = "shell guard (staging, release freeze)"
|
||||
event = "tool_call_before"
|
||||
command = 'cd "$DEEPSEEK_WORKSPACE" && echo "4c8e10ebe99524e6a5b2f0fbb090a8f4881f95b2bf732f020c566a610f949aa4 scripts/agent-guard.py" | shasum -a 256 -c -s && exec python3 scripts/agent-guard.py'
|
||||
timeout_secs = 10
|
||||
continue_on_error = true
|
||||
condition = { type = "tool_name", name = "bash" }
|
||||
|
||||
[[hooks]]
|
||||
name = "edit guard (CODEWHALE_RELEASE_FREEZE=1)"
|
||||
event = "tool_call_before"
|
||||
command = '[ -z "$CODEWHALE_RELEASE_FREEZE" ] || { cd "$DEEPSEEK_WORKSPACE" && echo "4c8e10ebe99524e6a5b2f0fbb090a8f4881f95b2bf732f020c566a610f949aa4 scripts/agent-guard.py" | shasum -a 256 -c -s && exec python3 scripts/agent-guard.py; }'
|
||||
timeout_secs = 10
|
||||
continue_on_error = true
|
||||
|
||||
[hooks.condition]
|
||||
type = "any"
|
||||
conditions = [
|
||||
{ type = "tool_category", category = "file_write" },
|
||||
{ type = "tool_name", name = "write_file" },
|
||||
{ type = "tool_name", name = "edit_file" },
|
||||
{ type = "tool_name", name = "fim_edit" },
|
||||
]
|
||||
@@ -1,120 +0,0 @@
|
||||
# cargo-nextest profile for contributors (`cargo nextest run --workspace`).
|
||||
#
|
||||
# Required GitHub CI and RC/release parity use nextest plus Cargo doctests.
|
||||
# nextest runs each test in its own process; that does not establish a pass
|
||||
# under libtest's shared-process globals. GH6698 separately requires two full
|
||||
# `cargo test --workspace --all-features --locked -- --format=pretty` passes
|
||||
# at one exact revision. CI's manual `shared-process-twice` mode records that
|
||||
# evidence; adding the mode or passing nextest does not satisfy the issue.
|
||||
# nextest also remains a faster local loop with named slow/hanging tests.
|
||||
#
|
||||
# Because tests no longer share a process, integration suites that serialize
|
||||
# on in-process locks must be bounded here when they contend for mock-server
|
||||
# ports or wall-clock timing budgets.
|
||||
|
||||
[profile.default]
|
||||
# Name tests that take longer than this so contributors see where the time goes.
|
||||
slow-timeout = { period = "30s" }
|
||||
# Keep failures visible in the final summary and never retry silently:
|
||||
# a flaky test is a bug report, not something to paper over.
|
||||
retries = 0
|
||||
fail-fast = false
|
||||
final-status-level = "slow"
|
||||
# RUST_MIN_STACK is not a nextest.toml key. CI and scripts/dev-test.sh
|
||||
# export 16 MiB so local nextest matches the product thread stack.
|
||||
|
||||
[test-groups]
|
||||
# Integration tests that spawn the real `codewhale` binary and wait on
|
||||
# service start-up deadlines; bounded so a fully parallel run on a busy
|
||||
# machine cannot starve them past their 30 s budgets.
|
||||
spawns-binaries = { max-threads = 3 }
|
||||
# Telemetry contract tests also spawn the real binary, and their fixture's
|
||||
# in-process mutex cannot serialize nextest's one-process-per-test workers.
|
||||
# Keep one telemetry process alongside the other three integration workers.
|
||||
telemetry-contract = { max-threads = 1 }
|
||||
# Persistent-service exec tests wait on a pid file from a real child. Under
|
||||
# parallel load the file never appears (#5355). Serialize them; do not drop
|
||||
# the tests.
|
||||
exec-persistent-service = { max-threads = 1 }
|
||||
# Fleet manager lifecycle tests start real `/bin/sh` workers under setsid and
|
||||
# wait 10-15 s for the child's first line. Under full macOS CI load that line
|
||||
# never ran for three overlapping tests (#6424, #6428 runs), while each
|
||||
# passed alone from the same binary in under 1.2 s. Serialize the module; do
|
||||
# not lengthen its deadlines or drop tests. The product side of the load
|
||||
# (a durable heartbeat and a `ps` sample on every scheduler tick) is now
|
||||
# bounded to once a second per worker.
|
||||
fleet-manager-lifecycle = { max-threads = 1 }
|
||||
# Extension fixtures launch real Node hosts, and the production handshake
|
||||
# deadline is 30 s (`HANDSHAKE_DEADLINE`, raised from 5 s: Windows full-suite
|
||||
# runs on #6776/#6780 reported ordinary hosts missing the old one). Bound
|
||||
# contention across nextest processes; keep production deadlines and the
|
||||
# explicit hang-detection tests unchanged.
|
||||
extension-host = { max-threads = 1 }
|
||||
|
||||
# First matching test-group override wins. Keep these more-specific
|
||||
# integration filters before binary(integration) so they are not stolen
|
||||
# by the three-thread group.
|
||||
[[profile.default.overrides]]
|
||||
filter = 'binary(integration) & test(/^telemetry_contract::/)'
|
||||
test-group = 'telemetry-contract'
|
||||
|
||||
# Same hazard, different binary. `telemetry_kill_switch_dispatch` is a
|
||||
# codewhale-cli integration binary, not `integration`, so none of the filters
|
||||
# below ever matched it and it ran at full parallelism beside ~15.7k tests.
|
||||
# Each of its cases spawns the real binary, which gives a detached writer
|
||||
# thread only CLI_PERSIST_TIMEOUT (250 ms, crates/telemetry/src/lib.rs) to
|
||||
# re-read config from disk and fsync its batch before the process exits. Under
|
||||
# Ubuntu CI load that deadline is missed and the dry-run receipt never lands —
|
||||
# observed at telemetry_kill_switch_dispatch.rs:180 on PR #6104 (2026-09-12)
|
||||
# and again on PR #6267 (2026-09-16), both Ubuntu-only, both on changes that
|
||||
# touch neither telemetry nor the CLI.
|
||||
[[profile.default.overrides]]
|
||||
filter = 'binary(telemetry_kill_switch_dispatch)'
|
||||
test-group = 'telemetry-contract'
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = 'package(codewhale-engine) & kind(lib) & test(/^fleet::manager::tests::/)'
|
||||
test-group = 'fleet-manager-lifecycle'
|
||||
|
||||
[[profile.default.overrides]]
|
||||
# DSH preparation also launches the isolated host against the shared hermetic
|
||||
# home. OCR/PDF consumers also launch real hosts outside extension_host's
|
||||
# namespace. Keep their process lifecycle checks in this existing serial lane.
|
||||
filter = '(package(codewhale-engine) | package(codewhale-tui)) & kind(lib) & (test(/^extension_host::/) | test(/^tools::image_ocr::host_tests::/) | test(/^tools::pdf::host_tests::/) | test(/^core::engine::.*extension/) | test(/^plugins::install::dsh::/) | test(/^plugins::tests::dsh_/) | test(/^runtime_api::tests::dsh_package_preview_then_exact_install_over_http$/) | test(/^commands::contract::tests::plugin_adapter_dsh_preview_runs_from_live_runtime$/) | test(/^commands::groups::plugins::tests::dsh_import_reviews_without_installing_then_installs_the_exact_bundle$/))'
|
||||
test-group = 'extension-host'
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = 'binary(integration) & test(/^exec_persistent_service::/)'
|
||||
test-group = 'exec-persistent-service'
|
||||
slow-timeout = { period = "120s" }
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = 'binary(integration)'
|
||||
test-group = 'spawns-binaries'
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = 'binary(owner_crash_reentry)'
|
||||
test-group = 'spawns-binaries'
|
||||
|
||||
# CI (profiles inherit from default): the final summary lists every failure and
|
||||
# slow test in full so the run log is enough to diagnose without rerunning.
|
||||
[profile.ci]
|
||||
retries = 0
|
||||
# A test that runs past ten minutes is a hang, not a slow test: terminate it
|
||||
# and surface its captured output instead of letting the job die at the
|
||||
# runner's 90-minute limit with no diagnostics (v0.9.12 Windows candidate).
|
||||
slow-timeout = { period = "60s", terminate-after = 10 }
|
||||
fail-fast = false
|
||||
final-status-level = "slow"
|
||||
failure-output = "immediate-final"
|
||||
|
||||
# Exact compiled-image Native delivery receipt comes from this same full run.
|
||||
# Only the fixed containment and memory marker/output is retained on success; the rest
|
||||
# of the workspace keeps its existing output policy.
|
||||
[profile.ci.junit]
|
||||
path = "junit.xml"
|
||||
report-name = "codewhale-native-host"
|
||||
|
||||
[[profile.ci.overrides]]
|
||||
filter = 'package(codewhale-engine) & kind(lib) & (test(/^extension_host::tests::compiled_native_host_cannot_read_secrets_or_write_outside_its_data_dir$/) | test(/^extension_host::tests::compiled_native_host_memory_cap_is_enforced$/))'
|
||||
junit.store-success-output = true
|
||||
@@ -1,29 +0,0 @@
|
||||
ARG RUST_VERSION=1.100.0
|
||||
FROM rust:${RUST_VERSION}-slim-bookworm
|
||||
|
||||
# Native DBus headers and pkg-config are required by libdbus-sys during
|
||||
# `cargo build`. Keep these development dependencies in the Dev Container,
|
||||
# rather than adding them to the production runtime image.
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
git \
|
||||
libdbus-1-dev \
|
||||
pkg-config \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# The official Rust image keeps its toolchain here. Preserve it after switching
|
||||
# from the image's root user to the non-root development user below.
|
||||
ENV PATH=/usr/local/cargo/bin:${PATH}
|
||||
RUN printf '%s\n' 'export PATH=/usr/local/cargo/bin:$PATH' \
|
||||
> /etc/profile.d/codewhale-rust.sh
|
||||
RUN rustup component add rustfmt
|
||||
|
||||
RUN groupadd --gid 1000 codewhale \
|
||||
&& useradd --create-home --shell /bin/bash --uid 1000 --gid 1000 codewhale \
|
||||
&& install -d -m 0700 -o codewhale -g codewhale /home/codewhale/.codewhale \
|
||||
&& install -d -m 0755 -o codewhale -g codewhale /home/codewhale/.cargo \
|
||||
&& install -d -m 0755 -o codewhale -g codewhale /home/codewhale/.cargo/target
|
||||
|
||||
USER codewhale
|
||||
WORKDIR /workspaces/CodeWhale
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"name": "CodeWhale",
|
||||
"build": {
|
||||
"context": "..",
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
"RUST_VERSION": "1.100.0"
|
||||
}
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"rust-lang.rust-analyzer",
|
||||
"tamasfe.even-better-toml",
|
||||
"vadimcn.vscode-lldb"
|
||||
],
|
||||
"settings": {
|
||||
"rust-analyzer.cargo.features": "all",
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"remoteEnv": {
|
||||
"DEEPSEEK_API_KEY": "${localEnv:DEEPSEEK_API_KEY}",
|
||||
"CARGO_TARGET_DIR": "/home/codewhale/.cargo/target"
|
||||
},
|
||||
"mounts": [
|
||||
"source=codewhale-state,target=/home/codewhale/.codewhale,type=volume",
|
||||
"source=codewhale-cargo-target,target=/home/codewhale/.cargo/target,type=volume"
|
||||
],
|
||||
"postCreateCommand": "cargo build --locked",
|
||||
"remoteUser": "codewhale"
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
# Build artifacts
|
||||
/target/
|
||||
*.pdb
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.rlib
|
||||
|
||||
# Sensitive environment files
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# Development
|
||||
/node_modules/
|
||||
/.vscode/
|
||||
/.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# Git
|
||||
/.git/
|
||||
/.gitignore
|
||||
/.gitattributes
|
||||
|
||||
# CI/CD
|
||||
/.github/
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.pytest_cache/
|
||||
venv/
|
||||
.venv/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Generated
|
||||
/outputs/
|
||||
/tmp/
|
||||
|
||||
# Local runtime state
|
||||
/.deepseek/
|
||||
|
||||
# Claude Code artifacts
|
||||
/.claude/
|
||||
/.ace-tool/
|
||||
|
||||
# Documentation (not needed at runtime), except schemas embedded via include_str!
|
||||
/docs/
|
||||
!/docs/schemas/
|
||||
!/docs/schemas/**
|
||||
/website/
|
||||
/*.md
|
||||
!/README.md
|
||||
!/CHANGELOG.md
|
||||
|
||||
# Assets (screenshots, etc.)
|
||||
/assets/
|
||||
|
||||
# Scripts
|
||||
/scripts/
|
||||
|
||||
# Development configs
|
||||
/.devcontainer/
|
||||
/config.example.toml
|
||||
@@ -1,28 +0,0 @@
|
||||
# Codewhale workspace credentials
|
||||
#
|
||||
# Shell-exported variables override values in this file. Codewhale loads only
|
||||
# literal credential names for built-in providers from a workspace `.env`.
|
||||
# Routing, model/base URL, config/profile, MCP/plugin, sandbox, approval,
|
||||
# executable-path, runtime, and other control settings are ignored here; put
|
||||
# those in config.toml, CLI flags, or the launching shell.
|
||||
#
|
||||
# Variable expansion is deliberately unsupported. Keep values literal and
|
||||
# keep `.env` untracked.
|
||||
|
||||
# DeepSeek API (default provider)
|
||||
# Get an API key from DeepSeek, then keep it local in `.env`.
|
||||
# DEEPSEEK_API_KEY=
|
||||
|
||||
# NVIDIA NIM-hosted models
|
||||
# NVIDIA_API_KEY=
|
||||
# NVIDIA_NIM_API_KEY=
|
||||
|
||||
# AtlasCloud OpenAI-compatible endpoint
|
||||
# ATLASCLOUD_API_KEY=
|
||||
|
||||
# Mistral AI (la Plateforme) — https://console.mistral.ai/api-keys
|
||||
# MISTRAL_API_KEY=
|
||||
|
||||
# Codewhale API — account key (cwc_key_…) with the models:infer scope
|
||||
# https://app.codewhale.net/settings?section=api
|
||||
# CODEWHALE_API_KEY=
|
||||
@@ -1,93 +0,0 @@
|
||||
# Ensure LF line endings for files consumed by include_str!() on all platforms.
|
||||
# include_str!() preserves raw bytes; CRLF breaks substring assertions and
|
||||
# produces different compiled binaries on Windows vs Linux/macOS.
|
||||
# Cover nested mode/approval/personality prompts — a top-level *.md glob alone
|
||||
# left modes/*.md on CRLF under Windows autocrlf and blew the agent token budget.
|
||||
crates/engine/src/prompts/**/*.md text eol=lf
|
||||
crates/engine/src/prompts/*.md text eol=lf
|
||||
crates/engine/src/prompts/*.txt text eol=lf
|
||||
|
||||
# The rest of the include_str!() inputs. `* text=auto` below converts these to
|
||||
# CRLF on a Windows checkout, which is invisible for the ones that are only
|
||||
# parsed or substring-matched and fatal for the one that is compared byte for
|
||||
# byte: crates/telemetry/tests/golden/v1.json is asserted against
|
||||
# serde_json::to_string_pretty output, which always emits LF, so Windows CI
|
||||
# failed `golden_payload_v1` with a pure \r\n diff.
|
||||
crates/telemetry/tests/golden/*.json text eol=lf
|
||||
# Generated pet native artifacts are regenerated in memory and compared byte
|
||||
# for byte by `npm --prefix pet run check` (pet/scripts/build-pet.mjs), and the
|
||||
# TUI copy is embedded with include_str!() (pet_watch/owner.rs). The generator
|
||||
# emits LF, so a CRLF Windows checkout reported all three as stale and would
|
||||
# embed different bytes.
|
||||
crates/tui/src/tui/pet_watch/pet-native.js text eol=lf
|
||||
pet/ios/Resources/pet-native.js text eol=lf
|
||||
pet/ios/Resources/demo.jsonl text eol=lf
|
||||
# Tideline component goldens (crates/tui/src/tui/goldens/*.txt) are
|
||||
# fs::read_to_string'd and compared byte for byte against buffer dumps that
|
||||
# always join rows with LF (golden_harness.rs::render_golden_text), so a
|
||||
# CRLF checkout failed all eight Windows golden tests with a pure \r\n diff.
|
||||
crates/tui/src/tui/goldens/*.txt text eol=lf
|
||||
# FEAT-025 baseline export goldens (crates/tui/src/commands/fixtures/*.md) are
|
||||
# include_str!()'d and compared byte for byte against a document the exporter
|
||||
# builds with LF, so a CRLF checkout failed the four Windows golden tests with a
|
||||
# pure \r\n diff (Windows CI caught this; Linux cannot see it).
|
||||
crates/tui/src/commands/fixtures/*.md text eol=lf
|
||||
# FEAT-029 debug diagnostics baselines (crates/tui/src/commands/fixtures/
|
||||
# diagnostics/*) are compared byte for byte against command output joined
|
||||
# with LF, so a CRLF checkout failed every Windows baseline parity test with a
|
||||
# pure \r\n diff.
|
||||
crates/tui/src/commands/fixtures/diagnostics/* text eol=lf
|
||||
# Conformance fixtures are recorded wire bytes (SSE framing is line-exact) and
|
||||
# goldens compared byte for byte; a CRLF checkout must not rewrite either.
|
||||
crates/tui/tests/fixtures/conformance/** text eol=lf
|
||||
crates/*/assets/**/*.json text eol=lf
|
||||
crates/*/assets/**/*.md text eol=lf
|
||||
crates/*/locales/*.json text eol=lf
|
||||
workflows/*.js text eol=lf
|
||||
# Executable documentation: the Fleet tutorial's JSON fence is parsed by the
|
||||
# task-spec contract test, so its bytes must agree on Windows and Unix.
|
||||
docs/FLEET_WORKFLOW_TUTORIAL.md text eol=lf
|
||||
# The dsh bundle scene is include_str!() into the generated client.js and
|
||||
# hashed for stale detection; CRLF would change both across platforms.
|
||||
crates/tui/src/integrations/dsh/*.js text eol=lf
|
||||
# The native DSH import fixture bundle is hashed into the conversion receipt,
|
||||
# so a CRLF checkout changed the expected digest on Windows.
|
||||
scripts/fixtures/dsh-web-app/** text eol=lf
|
||||
|
||||
# The extension host's generated protocol TS is compared byte for byte with
|
||||
# the generator's LF output (protocol/tests.rs), and its dist bundle is
|
||||
# include_bytes!()'d and digest-checked at launch; a CRLF Windows checkout
|
||||
# failed the former and would embed different bytes.
|
||||
crates/engine/extension-host/src/protocol.generated.ts text eol=lf
|
||||
crates/engine/extension-host/dist/** text eol=lf
|
||||
# Reviewed extension-host fixtures carry exact source SHA-256 receipts.
|
||||
# A Windows checkout must not rewrite their admitted source closure to CRLF.
|
||||
crates/engine/tests/fixtures/extension_host/** text eol=lf
|
||||
crates/engine/extension-host/src/dsh/dsh-message-compat.js text eol=lf
|
||||
|
||||
# DeepSeek Harness creator pins are compared byte for byte against recorded
|
||||
# SHA-256 values (UPSTREAM.json) and embedded with include_bytes!(); a CRLF
|
||||
# Windows checkout must not rewrite any of them.
|
||||
crates/skills/assets/dsh-creator/** text eol=lf
|
||||
crates/engine/extension-host/src/dsh/upstream/creator/** text eol=lf
|
||||
|
||||
# Rustfmt writes LF; keep Rust sources stable across Windows/Linux/macOS.
|
||||
*.rs text eol=lf
|
||||
|
||||
# Release shell scripts are invoked directly by bash on Windows
|
||||
# checkouts; CRLF turns `set -euo pipefail` into an invalid option.
|
||||
scripts/release/*.sh text eol=lf
|
||||
|
||||
# Keep repository attributes themselves stable on every platform.
|
||||
.gitattributes text eol=lf
|
||||
|
||||
# Everything else auto-detects (default).
|
||||
* text=auto
|
||||
|
||||
# Preserve the qualified upstream font license, including its trailing space.
|
||||
web/public/brand/fonts/OFL.txt text eol=lf whitespace=-blank-at-eol
|
||||
|
||||
# Generated protocol and SDK exports are compared byte for byte; keep LF on Windows checkouts.
|
||||
crates/app-server-protocol/schema/** text eol=lf
|
||||
crates/app-server-protocol/METHODS.md text eol=lf
|
||||
npm/runtime-sdk/generated/** text eol=lf
|
||||
@@ -1,5 +0,0 @@
|
||||
# This exact fixture is a deliberately public TEST-ONLY signing key.
|
||||
# It is never a production trust anchor and signs only local test payloads.
|
||||
# No other key files are excluded.
|
||||
paths-ignore:
|
||||
- docs/cloud-facts/fixtures/test-only-signing-key.pem
|
||||
@@ -1,323 +0,0 @@
|
||||
# Contributor credit identity map.
|
||||
#
|
||||
# Format:
|
||||
# alias = Display Name <[email protected]>
|
||||
#
|
||||
# The right-hand side must use GitHub's numeric noreply address so a harvested
|
||||
# commit authored with it, or a Co-authored-by trailer in the commit's final
|
||||
# trailer block, links to the contributor's account. GitHub resolves the
|
||||
# address by its numeric id, so the id must be the contributor's own
|
||||
# (`gh api users/<login> --jq .id`). The left-hand side may be a GitHub login,
|
||||
# old-style noreply address, raw email from a contributor commit, or local
|
||||
# machine email seen in older harvested history.
|
||||
|
||||
hmbown = Hmbown <[email protected]>
|
||||
[email protected] = Hmbown <[email protected]>
|
||||
fleitz = Fred Leitz <[email protected]>
|
||||
[email protected] = Fred Leitz <[email protected]>
|
||||
[email protected] = Fred Leitz <[email protected]>
|
||||
Angel-Hair = Lu Shihan <[email protected]>
|
||||
[email protected] = Lu Shihan <[email protected]>
|
||||
dmitri-0 = dmitri-0 <[email protected]>
|
||||
[email protected] = dmitri-0 <[email protected]>
|
||||
SparkofSpike = Sh1Zuku <[email protected]>
|
||||
zhangweiii = zhangweiii <[email protected]>
|
||||
[email protected] = zhangweiii <[email protected]>
|
||||
sternelee = Sterne Lee <[email protected]>
|
||||
[email protected] = Sterne Lee <[email protected]>
|
||||
Mr-Moon121 = Jeffrey Luna <[email protected]>
|
||||
lerugray = Ray Weiss <[email protected]>
|
||||
[email protected] = Ray Weiss <[email protected]>
|
||||
reidliu41 = reidliu41 <[email protected]>
|
||||
[email protected] = reidliu41 <[email protected]>
|
||||
ousamabenyounes = Ben Younes <[email protected]>
|
||||
[email protected] = Ben Younes <[email protected]>
|
||||
ljm3790865 = ljm3790865 <[email protected]>
|
||||
HUQIANTAO = HUQIANTAO <[email protected]>
|
||||
Hu Qiantao = HUQIANTAO <[email protected]>
|
||||
[email protected] = HUQIANTAO <[email protected]>
|
||||
[email protected] = HUQIANTAO <[email protected]>
|
||||
[email protected] = HUQIANTAO <[email protected]>
|
||||
punkcanyang = Punkcan Yang <[email protected]>
|
||||
Punkcan Yang = Punkcan Yang <[email protected]>
|
||||
[email protected] = Punkcan Yang <[email protected]>
|
||||
merchloubna70-dot = merchloubna70-dot <[email protected]>
|
||||
h3c-hexin = h3c-hexin <[email protected]>
|
||||
[email protected] = h3c-hexin <[email protected]>
|
||||
[email protected] = h3c-hexin <[email protected]>
|
||||
axobase001 = axobase001 <[email protected]>
|
||||
donglovejava = donglovejava <[email protected]>
|
||||
Oliver-ZPLiu = Oliver-ZPLiu <[email protected]>
|
||||
idling11 = idling11 <[email protected]>
|
||||
Hanmiao Li = idling11 <[email protected]>
|
||||
[email protected] = idling11 <[email protected]>
|
||||
894876246 = idling11 <[email protected]>
|
||||
angziii = angziii <[email protected]>
|
||||
aboimpinto = aboimpinto <[email protected]>
|
||||
Paulo Aboim Pinto = aboimpinto <[email protected]>
|
||||
[email protected] = aboimpinto <[email protected]>
|
||||
[email protected] = aboimpinto <[email protected]>
|
||||
encyc = encyc <[email protected]>
|
||||
Duducoco = Duducoco <[email protected]>
|
||||
cyq1017 = cyq1017 <[email protected]>
|
||||
cyq = cyq1017 <[email protected]>
|
||||
[email protected] = cyq1017 <[email protected]>
|
||||
cy2311 = CY <[email protected]>
|
||||
noaft = Toan Nguyen <[email protected]>
|
||||
[email protected] = Toan Nguyen <[email protected]>
|
||||
zlh124 = zlh124 <[email protected]>
|
||||
LeoLin990405 = LeoLin990405 <[email protected]>
|
||||
THINKER-ONLY = THINKER-ONLY <[email protected]>
|
||||
nightt5879 = nightt5879 <[email protected]>
|
||||
LmeSzinc = LmeSzinc <[email protected]>
|
||||
Lstarsky0 = Lstarsky0 <[email protected]>
|
||||
RepentStar = RepentStar <[email protected]>
|
||||
CCChisato = Fushimi Rio <[email protected]>
|
||||
aznikline = aznikline <[email protected]>
|
||||
Aznable = aznikline <[email protected]>
|
||||
[email protected] = aznikline <[email protected]>
|
||||
Liu-Vince = Liu-Vince <[email protected]>
|
||||
Vince = Liu-Vince <[email protected]>
|
||||
[email protected] = Liu-Vince <[email protected]>
|
||||
LI-Jialu = LI-Jialu <[email protected]>
|
||||
JiarenWang = JiarenWang <[email protected]>
|
||||
wdw8276 = kitty <[email protected]>
|
||||
mars-base = kitty <[email protected]>
|
||||
[email protected] = kitty <[email protected]>
|
||||
pengyou200902 = pengyou200902 <[email protected]>
|
||||
linzhiqin2003 = linzhiqin2003 <[email protected]>
|
||||
LING71671 = LING71671 <[email protected]>
|
||||
[email protected] = LING71671 <[email protected]>
|
||||
JasonOA888 = JasonOA888 <[email protected]>
|
||||
Inference1 = Inference1 <[email protected]>
|
||||
hongqitai = hongqitai <[email protected]>
|
||||
gordonlu = gordonlu <[email protected]>
|
||||
angus-guo = gus <[email protected]>
|
||||
imgusguo = gus <[email protected]>
|
||||
[email protected] = gus <[email protected]>
|
||||
gaord = gaord <[email protected]>
|
||||
Ben Gao = gaord <[email protected]>
|
||||
[email protected] = gaord <[email protected]>
|
||||
bengao168 = gaord <[email protected]>
|
||||
MXAntian = MXAntian <[email protected]>
|
||||
[email protected] = MXAntian <[email protected]>
|
||||
DarrellThomas = Darrell Thomas <[email protected]>
|
||||
[email protected] = Darrell Thomas <[email protected]>
|
||||
taixinguo = Taixin Guo <[email protected]>
|
||||
Taixin Guo = Taixin Guo <[email protected]>
|
||||
JayBeest = JayBeest <[email protected]>
|
||||
jcorneli = JayBeest <[email protected]>
|
||||
[email protected] = JayBeest <[email protected]>
|
||||
lucaszhu-hue = lucaszhu-hue <[email protected]>
|
||||
zhuangbiaowei = zhuangbiaowei <[email protected]>
|
||||
yuanchenglu = yuanchenglu <[email protected]>
|
||||
Vishnu1837 = Vishnu1837 <[email protected]>
|
||||
sximelon = sximelon <[email protected]>
|
||||
Sskift = Sskift <[email protected]>
|
||||
New2Niu = New2Niu <[email protected]>
|
||||
mvanhorn = mvanhorn <[email protected]>
|
||||
MengZ-super = MengZ-super <[email protected]>
|
||||
membphis = membphis <[email protected]>
|
||||
LeoAlex0 = LeoAlex0 <[email protected]>
|
||||
Lee-take = Lee-take <[email protected]>
|
||||
lbcheng888 = lbcheng888 <[email protected]>
|
||||
kunpeng-ai-lab = kunpeng-ai-lab <[email protected]>
|
||||
elowen53 = elowen53 <[email protected]>
|
||||
Elowen = elowen53 <[email protected]>
|
||||
[email protected] = elowen53 <[email protected]>
|
||||
CrepuscularIRIS = CrepuscularIRIS <[email protected]>
|
||||
chnjames = chnjames <[email protected]>
|
||||
ChaceLyee2101 = ChaceLyee2101 <[email protected]>
|
||||
ci4ic4 = ci4ic4 <[email protected]>
|
||||
Chavdar Ivanov = ci4ic4 <[email protected]>
|
||||
[email protected] = ci4ic4 <[email protected]>
|
||||
yusufgurdogan = yusufgurdogan <[email protected]>
|
||||
Yusuf Gurdogan = yusufgurdogan <[email protected]>
|
||||
hotelswith = yusufgurdogan <[email protected]>
|
||||
[email protected] = yusufgurdogan <[email protected]>
|
||||
AresNing = AresNing <[email protected]>
|
||||
|
||||
shenjackyuanjie = shenjackyuanjie <[email protected]>
|
||||
shenjack = shenjackyuanjie <[email protected]>
|
||||
[email protected] = shenjackyuanjie <[email protected]>
|
||||
xyuai = xyuai <[email protected]>
|
||||
AdityaVG13 = AdityaVG13 <[email protected]>
|
||||
[email protected] = AdityaVG13 <[email protected]>
|
||||
Implementist = Implementist <[email protected]>
|
||||
implecao = Implementist <[email protected]>
|
||||
[email protected] = Implementist <[email protected]>
|
||||
jrcjrcc = jrcjrcc <[email protected]>
|
||||
[email protected] = jrcjrcc <[email protected]>
|
||||
RefuseOdd = RefuseOdd <[email protected]>
|
||||
wywsoor = wywsoor <[email protected]>
|
||||
hsdbeebou = hsdbeebou <[email protected]>
|
||||
tdccccc = tdccccc <[email protected]>
|
||||
greyfreedom = greyfreedom <[email protected]>
|
||||
[email protected] = greyfreedom <[email protected]>
|
||||
puneetdixit200 = puneetdixit200 <[email protected]>
|
||||
roian6 = Chanhyo Jung <[email protected]>
|
||||
[email protected] = Chanhyo Jung <[email protected]>
|
||||
yekern = Stime <[email protected]>
|
||||
Stime = Stime <[email protected]>
|
||||
pkeging = pkeging <[email protected]>
|
||||
[email protected] = pkeging <[email protected]>
|
||||
findshan = Wenshan Deng <[email protected]>
|
||||
[email protected] = Wenshan Deng <[email protected]>
|
||||
KUK4 = KUK4 <[email protected]>
|
||||
[email protected] = KUK4 <[email protected]>
|
||||
|
||||
# Harvest-credit reconciliation: contributors restored to docs/CONTRIBUTORS.md
|
||||
# whose harvested work previously lacked a canonical map entry.
|
||||
GTC2080 = TaoMu <[email protected]>
|
||||
[email protected] = TaoMu <[email protected]>
|
||||
MMMarcinho = MMMarcinho <[email protected]>
|
||||
MeAiRobot = MeAiRobot <[email protected]>
|
||||
NorethSea = NorethSea <[email protected]>
|
||||
SamhandsomeLee = SamhandsomeLee <[email protected]>
|
||||
YaYII = YaYII <[email protected]>
|
||||
sandofree = sandofree <[email protected]>
|
||||
tiger-dog = tiger-dog <[email protected]>
|
||||
Jianfengwu2024 = Jianfengwu2024 <[email protected]>
|
||||
wplll = wplll <[email protected]>
|
||||
buko = buko <[email protected]>
|
||||
|
||||
# Machine-credit reconciliation follow-up: contributors credited in prose or
|
||||
# harvested by handle but missing from AUTHOR_MAP. Logins/IDs verified against
|
||||
# the GitHub user API.
|
||||
1Git2Clone = 1Git2Clone <[email protected]>
|
||||
jieshu666 = jieshu666 <[email protected]>
|
||||
rockeverm3m = rockeverm3m <[email protected]>
|
||||
[email protected] = rockeverm3m <[email protected]>
|
||||
wavezhang = wavezhang <[email protected]>
|
||||
hxy91819 = hxy91819 <[email protected]>
|
||||
[email protected] = hxy91819 <[email protected]>
|
||||
heloanc = heloanc <[email protected]>
|
||||
[email protected] = heloanc <[email protected]>
|
||||
bistack = Sun Zhenyuan <[email protected]>
|
||||
[email protected] = Sun Zhenyuan <[email protected]>
|
||||
OctoBored = OctoBored <[email protected]>
|
||||
skyzhao1223 = SKY ZHAO <[email protected]>
|
||||
zhaotian1 = SKY ZHAO <[email protected]>
|
||||
[email protected] = SKY ZHAO <[email protected]>
|
||||
vFONGv = Matthew.Fong <[email protected]>
|
||||
[email protected] = Matthew.Fong <[email protected]>
|
||||
luismateusvargas = Luis Mateus Vargas <[email protected]>
|
||||
redjade75723 = redjade75723 <[email protected]>
|
||||
|
||||
# v0.8.68 underwater-lane harvests.
|
||||
moduvoice = moduvoice <[email protected]>
|
||||
[email protected] = moduvoice <[email protected]>
|
||||
qinlinwang = qinlinwang <[email protected]>
|
||||
hange = qinlinwang <[email protected]>
|
||||
[email protected] = qinlinwang <[email protected]>
|
||||
knqiufan = knqiufan <[email protected]>
|
||||
[email protected] = knqiufan <[email protected]>
|
||||
|
||||
# v0.9.1 Windows TUI reports harvested into maintainer fixes.
|
||||
SparkofSpike = Sh1Zuku <[email protected]>
|
||||
Sh1Zuku = Sh1Zuku <[email protected]>
|
||||
|
||||
# v0.9.1 provider harvests.
|
||||
baendlorel = baendlorel <[email protected]>
|
||||
[email protected] = baendlorel <[email protected]>
|
||||
EvanProgramming = Evan <[email protected]>
|
||||
adity982 = ADITYA <[email protected]>
|
||||
vibecoding-skills = Harsh Dattani <[email protected]>
|
||||
XhesicaFrost = XhesicaFrost <[email protected]>
|
||||
ffaacceelee = ffaacceelee <[email protected]>
|
||||
mky = mky <[email protected]>
|
||||
cacdcaecawae = cacdcaecawae <[email protected]>
|
||||
XiaoHuo888-hue = XiaoHuo888-hue <[email protected]>
|
||||
[email protected] = XiaoHuo888-hue <[email protected]>
|
||||
wuisabel-gif = Isabel Wu <[email protected]>
|
||||
redstar = Kai Nacke <[email protected]>
|
||||
[email protected] = Kai Nacke <[email protected]>
|
||||
Shizuku = Sh1Zuku <[email protected]>
|
||||
[email protected] = Sh1Zuku <[email protected]>
|
||||
musichen = Alex Musichen <[email protected]>
|
||||
[email protected] = Alex Musichen <[email protected]>
|
||||
M-Maciej = M-Maciej <[email protected]>
|
||||
serephus = Serephus <[email protected]>
|
||||
# [email protected] = UNRESOLVED — 21 commits on main via #704; GitHub login not yet identified
|
||||
qiuYliangM = 秋月凉梦 <[email protected]>
|
||||
[email protected] = 秋月凉梦 <[email protected]>
|
||||
whp233 = whp233 <[email protected]>
|
||||
[email protected] = whp233 <[email protected]>
|
||||
Gabriel-Degret = Gabriel Degret <[email protected]>
|
||||
[email protected] = Gabriel Degret <[email protected]>
|
||||
huangxianzhan = huangxianzhan <[email protected]>
|
||||
[email protected] = huangxianzhan <[email protected]>
|
||||
zhuowp = zhuowp <[email protected]>
|
||||
[email protected] = zhuowp <[email protected]>
|
||||
z27014 = zhuowp <[email protected]>
|
||||
[email protected] = zhuowp <[email protected]>
|
||||
[email protected] = 秋月凉梦 <[email protected]>
|
||||
|
||||
asto18089 = asto18089 <[email protected]>
|
||||
[email protected] = asto18089 <[email protected]>
|
||||
xiechimon = Xmon Dai <[email protected]>
|
||||
yrk111222 = Rongkun Yan <[email protected]>
|
||||
VincentCorleone = Vincent Corleone <[email protected]>
|
||||
Vincent Corleone = Vincent Corleone <[email protected]>
|
||||
[email protected] = Vincent Corleone <[email protected]>
|
||||
yetuge = yetuge <[email protected]>
|
||||
[email protected] = yetuge <[email protected]>
|
||||
Serendo = Serendo <[email protected]>
|
||||
[email protected] = Serendo <[email protected]>
|
||||
dajiaohuang = dajiaohuang <[email protected]>
|
||||
[email protected] = dajiaohuang <[email protected]>
|
||||
Wu Shuwen = dajiaohuang <[email protected]>
|
||||
[email protected] = dajiaohuang <[email protected]>
|
||||
harryvgiunta = Harry Giunta <[email protected]>
|
||||
[email protected] = Harry Giunta <[email protected]>
|
||||
xiaoray-blip = Arlo <[email protected]>
|
||||
Arlo = Arlo <[email protected]>
|
||||
xyzs996 = Mark Yan <[email protected]>
|
||||
[email protected] = Mark Yan <[email protected]>
|
||||
Mark Yan = Mark Yan <[email protected]>
|
||||
henrikdukefoss = Henrik Bakke Dukefoss <[email protected]>
|
||||
Henrik Bakke Dukefoss = Henrik Bakke Dukefoss <[email protected]>
|
||||
[email protected] = Henrik Bakke Dukefoss <[email protected]>
|
||||
|
||||
[email protected] = Dhruv Singal <[email protected]>
|
||||
[email protected] = Boyce <[email protected]>
|
||||
Boyce = Boyce <[email protected]>
|
||||
Felixkw12 = Felix Wunderlich <[email protected]>
|
||||
[email protected] = Felix Wunderlich <[email protected]>
|
||||
Felix Wunderlich = Felix Wunderlich <[email protected]>
|
||||
[email protected] = luochen211 <[email protected]>
|
||||
|
||||
AiurArtanis = AiurArtanis <[email protected]>
|
||||
[email protected] = AiurArtanis <[email protected]>
|
||||
Apeiron0w0 = Apeiron <[email protected]>
|
||||
[email protected] = Apeiron <[email protected]>
|
||||
[email protected] = ChaceLyee2101 <[email protected]>
|
||||
AmosLeeeeeeee = ChaceLyee2101 <[email protected]>
|
||||
ddlw = ddlw <[email protected]>
|
||||
mo-vic = movic <[email protected]>
|
||||
[email protected] = movic <[email protected]>
|
||||
[email protected] = merchloubna70-dot <[email protected]>
|
||||
[email protected] = merchloubna70-dot <[email protected]>
|
||||
xulongzhe = luola <[email protected]>
|
||||
[email protected] = luola <[email protected]>
|
||||
ZhulongNT = SONG ZHENRUI <[email protected]>
|
||||
[email protected] = SONG ZHENRUI <[email protected]>
|
||||
xiluoduyu = xiluoduyu <[email protected]>
|
||||
[email protected] = xiluoduyu <[email protected]>
|
||||
wlon = Stephen Xu <[email protected]>
|
||||
[email protected] = Stephen Xu <[email protected]>
|
||||
krisclarkdev = Kristopher Clark <[email protected]>
|
||||
THatch26 = THatch26 <[email protected]>
|
||||
jeoor = Kayro <[email protected]>
|
||||
harvey2011888 = harvey2011888 <[email protected]>
|
||||
duanchao-lab = DC <[email protected]>
|
||||
dumbjack = dumbjack <[email protected]>
|
||||
[email protected] = dumbjack <[email protected]>
|
||||
AquaZhang = IceTear <[email protected]>
|
||||
disappear00 = idontknow <[email protected]>
|
||||
namezzb = sailorZ <[email protected]>
|
||||
alphapku-ux = Alpha Wu <[email protected]>
|
||||
BX166 = BX166 <[email protected]>
|
||||
c020627 = c020627 <[email protected]>
|
||||
[email protected] = Parman Mohammadalizadeh <[email protected]>
|
||||
Parman Mohammadalizadeh = Parman Mohammadalizadeh <[email protected]>
|
||||
@@ -1,2 +0,0 @@
|
||||
# Default owner for everything in the repo.
|
||||
* @Hmbown
|
||||
@@ -1,76 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq).
|
||||
Translations are available at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
|
||||
@@ -1,68 +0,0 @@
|
||||
# Berkontribusi pada Codewhale
|
||||
|
||||
Codewhale berkembang berkat orang yang menggunakannya, melaporkan masalah,
|
||||
menerjemahkan dokumentasi, menambahkan penyedia model, dan mengirim perbaikan.
|
||||
Anda dapat berkontribusi tanpa menulis kode. Semua masukan diterima.
|
||||
|
||||
[Roadmap](../docs/ROADMAP.md) menjelaskan tujuan proyek dan pekerjaan yang masih
|
||||
terbuka. [Panduan utama](../CONTRIBUTING.md) memuat prosedur pengembangan,
|
||||
pengujian, dan peninjauan yang lengkap dan terbaru.
|
||||
|
||||
## Pilih tempat yang sesuai
|
||||
|
||||
| Yang ingin Anda sampaikan | Tempatnya |
|
||||
| --- | --- |
|
||||
| Bug, crash, atau perilaku yang tidak diharapkan | [Laporan bug](https://github.com/codewhale-hq/CodeWhale/issues/new?template=bug_report.yml) |
|
||||
| Ide atau permintaan fitur | [Permintaan fitur](https://github.com/codewhale-hq/CodeWhale/issues/new?template=feature_request.yml) |
|
||||
| Masukan tentang tampilan, tulisan, atau pengalaman penggunaan | [Formulir masukan](https://github.com/codewhale-hq/CodeWhale/issues/new?template=feedback.yml) |
|
||||
| Pertanyaan | [Tanya jawab](https://github.com/codewhale-hq/CodeWhale/discussions/categories/q-a) atau [Discord](https://discord.gg/37gfS3ksug) |
|
||||
| Masalah keamanan | [Laporan keamanan privat](https://github.com/codewhale-hq/CodeWhale/security/advisories/new), bukan issue publik |
|
||||
| Perbaikan untuk issue yang sudah ada | Pull request yang menautkan issue tersebut |
|
||||
| Terjemahan, penyedia model, atau perbaikan dokumentasi | Pull request; tidak perlu membuka issue terlebih dahulu |
|
||||
|
||||
Jika ragu, buka issue dan jelaskan apa yang Anda coba lakukan, apa yang terjadi,
|
||||
dan apa yang Anda harapkan. Pengelola akan membantu mengarahkannya.
|
||||
|
||||
## Mulai fitur baru dengan issue
|
||||
|
||||
Untuk fitur baru, buka issue sebelum mulai menulis kode. Jelaskan kebutuhan dan
|
||||
hambatannya; rancangan solusi boleh disertakan, tetapi tidak wajib. Jika ingin
|
||||
mengerjakannya sendiri, centang pilihan untuk membuka pull request pada formulir.
|
||||
|
||||
Pengelola dapat membahas cakupan dan perilaku fitur sebelum Anda menghabiskan
|
||||
waktu untuk implementasi. Pull request fitur tanpa issue tetap dibaca, tetapi
|
||||
pembahasannya biasanya membutuhkan waktu lebih lama.
|
||||
|
||||
## Pekerjaan yang dapat langsung Anda kirim
|
||||
|
||||
Anda dapat langsung mengirim pull request untuk terjemahan, penyedia dan model,
|
||||
perbaikan dokumentasi, atau perbaikan bug. Jika bug belum memiliki issue,
|
||||
jelaskan cara memunculkannya, hasil yang terjadi, dan hasil yang diharapkan di
|
||||
pull request.
|
||||
|
||||
Untuk pekerjaan yang sudah direncanakan, pilih issue di
|
||||
[milestone](../docs/ROADMAP.md#releases) atau yang berlabel
|
||||
[help wanted](https://github.com/codewhale-hq/CodeWhale/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22).
|
||||
Beri komentar bahwa Anda ingin mengerjakannya agar pekerjaan tidak tumpang tindih.
|
||||
[Lihat proyek terbuka](../CONTRIBUTING.md#standing-projects) untuk tautan ke
|
||||
panduan terjemahan dan penyedia model.
|
||||
|
||||
## Siapkan perubahan Anda
|
||||
|
||||
Ikuti [petunjuk persiapan pengembangan](../CONTRIBUTING.md#getting-started)
|
||||
untuk persyaratan Rust, perintah build, dan cara menjalankan versi sumber.
|
||||
Versi di `main` dapat lebih baru daripada unduhan rilis; sertakan keluaran
|
||||
`codewhale --version` ketika melaporkan masalah.
|
||||
|
||||
Jalankan pemeriksaan yang sesuai dengan bagian yang Anda ubah, mengikuti
|
||||
[panduan pengujian lokal](../CONTRIBUTING.md#fast-local-loop). Sebelum mengirim
|
||||
pull request, baca perubahan Anda sendiri dan jelaskan apa yang berubah serta
|
||||
bagaimana Anda memeriksanya. Jangan sertakan kunci API atau data pribadi.
|
||||
|
||||
## Peninjauan dan kredit
|
||||
|
||||
Pull request Anda digabungkan sebagai kontribusi Anda sendiri. Pengelola
|
||||
membantu menyelesaikan konflik akibat perubahan di `main`; pemeriksaan yang
|
||||
merah karena masalah lain bukan tanggung jawab Anda. Jika perubahan belum siap,
|
||||
pengelola menjelaskan alasannya. Kontribusi dicatat di changelog dan
|
||||
[daftar kontributor](../docs/CONTRIBUTORS.md).
|
||||
@@ -1,2 +0,0 @@
|
||||
github: [Hmbown]
|
||||
buy_me_a_coffee: hmbown
|
||||
@@ -0,0 +1,56 @@
|
||||
name: 🪲 Bug Report
|
||||
description: Report an issue that should be fixed
|
||||
labels:
|
||||
- bug
|
||||
- needs triage
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for submitting a bug report! It helps make Codex better for everyone.
|
||||
|
||||
If you need help or support using Codex, and are not reporting a bug, please post on [codex/discussions](https://github.com/openai/codex/discussions), where you can ask questions or engage with others on ideas for how to improve codex.
|
||||
|
||||
Make sure you are running the [latest](https://npmjs.com/package/@openai/codex) version of Codex CLI. The bug you are experiencing may already have been fixed.
|
||||
|
||||
Please try to include as much information as possible.
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: What version of Codex is running?
|
||||
description: Copy the output of `codex --version`
|
||||
- type: input
|
||||
id: model
|
||||
attributes:
|
||||
label: Which model were you using?
|
||||
description: Like `gpt-4.1`, `o4-mini`, `o3`, etc.
|
||||
- type: input
|
||||
id: platform
|
||||
attributes:
|
||||
label: What platform is your computer?
|
||||
description: |
|
||||
For MacOS and Linux: copy the output of `uname -mprs`
|
||||
For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: What steps can reproduce the bug?
|
||||
description: Explain the bug and provide a code snippet that can reproduce it.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: What is the expected behavior?
|
||||
description: If possible, please provide text instead of a screenshot.
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: What do you see instead?
|
||||
description: If possible, please provide text instead of a screenshot.
|
||||
- type: textarea
|
||||
id: notes
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Is there anything else you think we should know?
|
||||
@@ -0,0 +1,27 @@
|
||||
name: 📗 Documentation Issue
|
||||
description: Tell us if there is missing or incorrect documentation
|
||||
labels: [docs]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for submitting a documentation request. It helps make Codex better.
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What is the type of issue?
|
||||
multiple: true
|
||||
options:
|
||||
- Documentation is missing
|
||||
- Documentation is incorrect
|
||||
- Documentation is confusing
|
||||
- Example code is not working
|
||||
- Something else
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What is the issue?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Where did you find it?
|
||||
description: If possible, please provide the URL(s) where you found this issue.
|
||||
@@ -1,67 +0,0 @@
|
||||
name: Agent task
|
||||
description: A self-contained task a headless Codewhale agent can finish end to end. Mostly for maintainers.
|
||||
labels: ["agent-ready"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
A headless agent runs this with `codewhale exec --auto` on a fresh clone of `main`.
|
||||
It has shell, file, and git tools, and **no context beyond this issue**. Name every
|
||||
path and command explicitly. Assign a milestone during triage; this template sets none.
|
||||
|
||||
- type: textarea
|
||||
id: goal
|
||||
attributes:
|
||||
label: Goal
|
||||
description: What is wrong or missing, and why it matters now. 2-4 sentences.
|
||||
placeholder: |
|
||||
The TUI freezes when 4+ sub-agents run at once, because each progress
|
||||
event triggers a full redraw.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: scope
|
||||
attributes:
|
||||
label: Plan
|
||||
description: Numbered steps. Start each with the file path it changes. List any extra files to read first.
|
||||
placeholder: |
|
||||
1. crates/tui/src/tui/ui.rs — throttle redraws in the progress handler
|
||||
2. crates/tui/src/tui/app.rs — add a last-redraw timestamp
|
||||
Read first: crates/tui/src/tui/sidebar.rs
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: acceptance-criteria
|
||||
attributes:
|
||||
label: Done when
|
||||
description: Observable, testable outcomes.
|
||||
placeholder: |
|
||||
- [ ] 4 concurrent sub-agents do not freeze input
|
||||
- [ ] Ctrl+C still works during sub-agent activity
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: verification
|
||||
attributes:
|
||||
label: Verification
|
||||
description: Exact commands that prove it. Scope them to the crates touched; CI runs the full suite.
|
||||
placeholder: |
|
||||
scripts/dev-cargo.sh check -p codewhale-tui
|
||||
scripts/dev-test.sh tui subagent
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: out-of-scope
|
||||
attributes:
|
||||
label: Out of scope
|
||||
description: What this task must not change.
|
||||
placeholder: |
|
||||
- The sub-agent execution model
|
||||
- The recommended fanout count
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,124 +0,0 @@
|
||||
name: Bug report
|
||||
description: Something is broken or behaves wrong. 报告问题。
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for reporting. English or 中文 are both fine.
|
||||
|
||||
- Security problem? Do not post it here. Use a [private advisory](https://github.com/codewhale-hq/CodeWhale/security/advisories/new).
|
||||
- Already reported? Add a 👍 to that issue instead of opening a new one.
|
||||
- Remove API keys and tokens from anything you paste.
|
||||
|
||||
- type: checkboxes
|
||||
id: searched
|
||||
attributes:
|
||||
label: Before you start
|
||||
options:
|
||||
- label: I searched existing issues and this is not a duplicate. 我已搜索过现有 issue。
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened? 发生了什么?
|
||||
description: What you saw, and what you expected instead. Paste error text as text, not only a screenshot.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to reproduce 复现步骤
|
||||
description: Exact commands, key presses, or prompts. Shorter is better.
|
||||
placeholder: |
|
||||
1. Run `codewhale` in a git repository
|
||||
2. Type ...
|
||||
3. See ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Codewhale version 版本
|
||||
description: Output of `codewhale --version`. Desktop app — the version in About.
|
||||
placeholder: codewhale 0.10.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: surface
|
||||
attributes:
|
||||
label: Where did it happen? 在哪里出现?
|
||||
multiple: true
|
||||
options:
|
||||
- TUI (terminal interface)
|
||||
- CLI command (codewhale exec, codewhale doctor, ...)
|
||||
- Desktop app
|
||||
- Web (codewhale web or app.codewhale.net)
|
||||
- Plugins / extension host
|
||||
- MCP
|
||||
- Other / not sure
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system 操作系统
|
||||
options:
|
||||
- macOS
|
||||
- Linux
|
||||
- Windows
|
||||
- WSL
|
||||
- Android (Termux)
|
||||
- HarmonyOS
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: channel
|
||||
attributes:
|
||||
label: How did you get Codewhale? 获取方式
|
||||
options:
|
||||
- Official script (codewhale.net/install.sh)
|
||||
- GitHub Releases download
|
||||
- Desktop app download
|
||||
- npm
|
||||
- Homebrew
|
||||
- Cargo (crates.io)
|
||||
- Built from a git checkout
|
||||
- Scoop / Nix / Docker / other package
|
||||
- Not sure
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: model
|
||||
attributes:
|
||||
label: Provider and model 提供商和模型
|
||||
description: As shown by `/model`. Leave empty if it does not matter.
|
||||
placeholder: "provider / model id"
|
||||
|
||||
- type: input
|
||||
id: terminal
|
||||
attributes:
|
||||
label: Terminal and shell 终端
|
||||
description: Only for terminal problems. Mention tmux, zellij, or screen if you use one.
|
||||
placeholder: iTerm2 + zsh, Windows Terminal + PowerShell, VS Code terminal + bash
|
||||
|
||||
- type: textarea
|
||||
id: doctor
|
||||
attributes:
|
||||
label: codewhale doctor (optional)
|
||||
description: Run `codewhale doctor` and paste the relevant lines. Check it for secrets first.
|
||||
render: text
|
||||
|
||||
- type: textarea
|
||||
id: extra
|
||||
attributes:
|
||||
label: Logs, screenshots, anything else (optional)
|
||||
description: Logs, a screenshot or recording, config snippets, or a guess at the cause.
|
||||
@@ -1,14 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Report a security vulnerability
|
||||
url: https://github.com/codewhale-hq/CodeWhale/security/advisories/new
|
||||
about: Please report security problems privately, not in a public issue. See SECURITY.md.
|
||||
- name: Roadmap and where to help
|
||||
url: https://github.com/codewhale-hq/CodeWhale/blob/main/docs/ROADMAP.md
|
||||
about: Where Codewhale is going, what each release covers, and good places to start. 路线图。
|
||||
- name: Ask a question
|
||||
url: https://github.com/codewhale-hq/CodeWhale/discussions
|
||||
about: How-to questions and ideas you want to talk through. 提问和讨论。
|
||||
- name: Discord
|
||||
url: https://discord.gg/37gfS3ksug
|
||||
about: Chat with the community and maintainers.
|
||||
@@ -1,66 +0,0 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea or an improvement. 功能建议。
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for the idea. English or 中文 are both fine. Short is good.
|
||||
|
||||
This is the best place to start a feature, even if you plan to build it yourself: agreeing on the shape here makes the pull request small and quick to merge. The [roadmap](https://github.com/codewhale-hq/CodeWhale/blob/main/docs/ROADMAP.md) shows what is already planned.
|
||||
|
||||
If someone already asked for this, add a 👍 to their issue instead.
|
||||
Want to talk it through first? Use [Discussions](https://github.com/codewhale-hq/CodeWhale/discussions) or [Discord](https://discord.gg/37gfS3ksug).
|
||||
|
||||
- type: checkboxes
|
||||
id: searched
|
||||
attributes:
|
||||
label: Before you start
|
||||
options:
|
||||
- label: I searched existing issues and this is not a duplicate. 我已搜索过现有 issue。
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: What problem does this solve? 要解决什么问题?
|
||||
description: What are you trying to do, and what gets in the way today?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: idea
|
||||
attributes:
|
||||
label: What should Codewhale do? 希望 Codewhale 怎么做?
|
||||
description: The behavior you want. Commands, key presses, or config are welcome.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: surface
|
||||
attributes:
|
||||
label: Where? 在哪里?
|
||||
multiple: true
|
||||
options:
|
||||
- TUI (terminal interface)
|
||||
- CLI command (codewhale exec, ...)
|
||||
- Desktop app
|
||||
- Web (codewhale web or app.codewhale.net)
|
||||
- Plugins / extension host
|
||||
- MCP
|
||||
- Providers and models
|
||||
- Docs
|
||||
- Other / not sure
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Workarounds or other tools (optional)
|
||||
description: What you do today, or how another tool handles it.
|
||||
|
||||
- type: checkboxes
|
||||
id: contribute
|
||||
attributes:
|
||||
label: Contributing
|
||||
options:
|
||||
- label: I would like to open a pull request for this, once we agree on the shape. 我愿意在方案确定后提交 PR。
|
||||
@@ -1,25 +0,0 @@
|
||||
name: Feedback
|
||||
description: Something looked, read or felt off, or you have a thought to share. 反馈与感受。
|
||||
labels: ["needs-triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
All feedback is welcome, and you do not need a fix or a plan. English or 中文 are both fine.
|
||||
|
||||
First impressions, confusing wording, an awkward step, a screen that looks wrong, something you liked: it all helps.
|
||||
For something broken, the [bug report](https://github.com/codewhale-hq/CodeWhale/issues/new?template=bug_report.yml) form asks for the details we need to reproduce it.
|
||||
|
||||
- type: textarea
|
||||
id: feedback
|
||||
attributes:
|
||||
label: What would you like us to know? 想告诉我们什么?
|
||||
description: What you were doing, and what you noticed. A screenshot helps for anything visual.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Codewhale version (optional) 版本
|
||||
description: Output of `codewhale --version`, if you have it.
|
||||
@@ -1,27 +0,0 @@
|
||||
<!-- Thanks for the PR! English or 中文 are both fine. Short is good. -->
|
||||
|
||||
## What and why
|
||||
|
||||
<!-- What does this change, and why? A few sentences is enough. -->
|
||||
|
||||
## Where this came from
|
||||
|
||||
<!-- Closes #N if this finishes an issue, Refs #N if related. Translations,
|
||||
provider and model updates, docs fixes and bug fixes need no issue: just say
|
||||
which it is. For a new feature with no issue yet, opening one first is the
|
||||
faster path (CONTRIBUTING.md explains why); we still read this PR either way.
|
||||
Please do not write a closing word inside a negation ("this does not close #N"):
|
||||
GitHub closes the issue anyway. -->
|
||||
|
||||
## How I tested it
|
||||
|
||||
<!-- Commands you ran and what they showed. Test what you changed, for example
|
||||
`scripts/dev-test.sh tui <filter>`. A few quick checks run on your PR; if one
|
||||
fails for a reason unrelated to your change, a maintainer will deal with it.
|
||||
For visible UI changes, add a screenshot or recording. -->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] One focused change, started from current `main` (no need to rebase when `main` moves)
|
||||
- [ ] `cargo fmt --all` passes, and tests cover new or changed behavior
|
||||
- [ ] If this adds or changes a user-facing feature, I updated its docs and its row in `docs/features.toml` (`cargo test -p codewhale-tui --test feature_registry` checks it)
|
||||
@@ -1,98 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
codewhale is a coding agent with direct access to file operations, shell execution, and the network. Security disclosures are taken seriously.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the latest stable release receives security patches. No backports to older versions.
|
||||
|
||||
| Version | Supported |
|
||||
|---|---|
|
||||
| latest stable | :white_check_mark: |
|
||||
| < latest | :x: |
|
||||
|
||||
Check the [releases page](https://github.com/codewhale-hq/CodeWhale/releases) for the current version.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
**Do not open a public GitHub issue for security vulnerabilities.**
|
||||
|
||||
Report privately via one of:
|
||||
|
||||
- **GitHub private advisory**: [github.com/codewhale-hq/CodeWhale/security/advisories/new](https://github.com/codewhale-hq/CodeWhale/security/advisories/new)
|
||||
- **Email**: [[email protected]](mailto:[email protected]) — include `[SECURITY]` in the subject line
|
||||
|
||||
Include in your report:
|
||||
|
||||
- A description of the vulnerability and the impact if exploited
|
||||
- Steps to reproduce or a proof of concept
|
||||
- Affected versions and configuration details
|
||||
- Any suggested mitigation (optional)
|
||||
|
||||
## Response Timeline
|
||||
|
||||
| Phase | Target |
|
||||
|---|---|
|
||||
| Acknowledgment | Within 48 hours of receipt |
|
||||
| Assessment | Within 5 days — triage severity, scope, and fix approach |
|
||||
| Patch (critical) | Within 14 days from assessment |
|
||||
| Patch (moderate/low) | Next feature release or per-maintainer timeline |
|
||||
| Disclosure | After patch is shipped and users have had time to update |
|
||||
|
||||
You will receive status updates at each phase. If the timeline slips, we will communicate the reason and the revised estimate.
|
||||
|
||||
## Scope
|
||||
|
||||
### In scope (what counts)
|
||||
|
||||
- Remote code execution through crafted prompts or model responses
|
||||
- Sandbox escape — breaking out of an active Seatbelt/bubblewrap wrapper or a declared workspace boundary
|
||||
- Credential leak — exfiltration of API keys, tokens, or environment secrets
|
||||
- Arbitrary file read/write outside the intended workspace (`PathEscape` bypass)
|
||||
- SSRF via `fetch_url` or `web_search` against internal network endpoints
|
||||
- Unauthorised MCP server access or tool invocation
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Social engineering of the maintainer or contributors
|
||||
- Denial of service / rate-limit exhaustion against the DeepSeek API
|
||||
- Vulnerabilities in third-party dependencies (report to the upstream project)
|
||||
- Attacks requiring physical access to the victim's machine
|
||||
- Theoretical ML-model injection attacks not demonstrated in the codewhale context
|
||||
|
||||
If you are unsure whether a bug is in scope, report it anyway. We will triage and respond.
|
||||
|
||||
|
||||
## WeCom Bridge Security
|
||||
|
||||
The WeCom Bridge (`integrations/wecom-bridge/`) extends Codewhale to WeCom
|
||||
(企业微信) Smart Bot WebSocket sessions. It inherits all standard Codewhale
|
||||
security boundaries and adds bridge-specific controls.
|
||||
|
||||
### Bridge-specific protections
|
||||
|
||||
- **No public port**: The bridge communicates with `codewhale serve --http` on `127.0.0.1` only
|
||||
- **Token gate**: All runtime API calls carry `CODEWHALE_RUNTIME_TOKEN`
|
||||
- **Chat allowlist**: Only chats/users listed in `WECOM_CHAT_ALLOWLIST` can interact. First-pairing mode (`WECOM_ALLOW_UNLISTED=true`) is meant for onboarding only
|
||||
- **Approval required**: Tool calls from WeCom sessions must be approved — either via explicit `/allow <id>` commands or natural-language keywords (`允许`, `yes`, `ok`, etc.)
|
||||
- **Thread ownership**: `/threads` and `/resume` cover only the threads the chat itself created; any other thread ID is refused with one fixed reply
|
||||
- **No workspace exposure**: Only prompts, status summaries, and approval requests are sent to WeCom. Workspace contents, shell output, and runtime internals stay on the local machine
|
||||
|
||||
### Reporting WeCom Bridge vulnerabilities
|
||||
|
||||
Report bridge-specific security issues through the same channels listed above.
|
||||
Include the bridge version (check `package.json`) and your WeCom deployment configuration
|
||||
(sensitive values redacted). Bridge logs may be requested for reproduction.
|
||||
|
||||
### Bridge environment safety
|
||||
|
||||
- `WECOM_BOT_SECRET` and `CODEWHALE_RUNTIME_TOKEN` must never be committed to git
|
||||
- The `.env` file is gitignored; use `.env.example` as the template
|
||||
- Rotate secrets periodically, especially after sharing screen captures
|
||||
- Use `CODEWHALE_APPROVAL_TIMEOUT_MS` (default 5 min) to limit the approval window
|
||||
|
||||
## Hall of Fame
|
||||
|
||||
We maintain a hall of fame for reporters who submit verified security vulnerabilities. To be credited, include your preferred name / handle in the report.
|
||||
|
||||
*No entries yet — be the first.*
|
||||
@@ -1,19 +0,0 @@
|
||||
# Getting help with Codewhale
|
||||
|
||||
- **A question about using Codewhale:** ask in
|
||||
[Discussions Q&A](https://github.com/codewhale-hq/Codewhale/discussions/categories/q-a)
|
||||
or on [Discord](https://discord.gg/37gfS3ksug). The
|
||||
[user guide](../docs/GUIDE.md) and the [documentation index](../docs/README.md)
|
||||
answer many questions already.
|
||||
- **A bug, a crash or behavior that surprised you:** open a
|
||||
[bug report](https://github.com/codewhale-hq/Codewhale/issues/new?template=bug_report.yml).
|
||||
`codewhale doctor` prints the version and setup details a report needs.
|
||||
- **An idea or feedback:** open an
|
||||
[issue](https://github.com/codewhale-hq/Codewhale/issues/new/choose), or talk it
|
||||
through first in
|
||||
[Ideas](https://github.com/codewhale-hq/Codewhale/discussions/categories/ideas).
|
||||
- **A security problem:** report it privately as described in the
|
||||
[security policy](SECURITY.md), never in a public issue.
|
||||
|
||||
The [contributing guide](../CONTRIBUTING.md) explains where each kind of help
|
||||
goes and how a pull request lands.
|
||||
@@ -1,33 +0,0 @@
|
||||
# CodeQL configuration for code scanning.
|
||||
#
|
||||
# Test code is out of scope for security alerts: it runs only in CI, talks to
|
||||
# in-process loopback servers, and routinely prints fixture secrets in
|
||||
# assertion messages to prove they are redacted elsewhere. Those paths are
|
||||
# excluded here so alerts point at product code.
|
||||
#
|
||||
# Inline `#[cfg(test)] mod tests` blocks inside product files cannot be
|
||||
# excluded by path; alerts there are dismissed as "used in tests".
|
||||
#
|
||||
# This file takes effect only with CodeQL advanced setup:
|
||||
# .github/workflows/codeql.yml passes it to `github/codeql-action/init`.
|
||||
# Default setup ignores it, so the repository's code scanning setting must be
|
||||
# switched from Default to Advanced for either to apply.
|
||||
name: codewhale-codeql
|
||||
|
||||
paths-ignore:
|
||||
# Rust integration tests and split-out unit test modules.
|
||||
- "**/tests/**"
|
||||
- "**/tests.rs"
|
||||
- "**/*_tests.rs"
|
||||
- "**/test_support.rs"
|
||||
- "**/*_test_support.rs"
|
||||
# JavaScript / TypeScript test suites.
|
||||
- "**/test/**"
|
||||
- "**/__tests__/**"
|
||||
- "**/*.test.js"
|
||||
- "**/*.test.mjs"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.spec.js"
|
||||
- "**/*.spec.ts"
|
||||
- "**/*.spec.tsx"
|
||||
|
After Width: | Height: | Size: 19 MiB |
@@ -1,34 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 5
|
||||
groups:
|
||||
# `windows` re-exports windows-core's types; bumping one alone makes
|
||||
# BOOL/HRESULT two distinct types and the Windows leg stops compiling.
|
||||
windows:
|
||||
patterns:
|
||||
- "windows"
|
||||
- "windows-*"
|
||||
ignore:
|
||||
# rusty_alloc 2.x does not compile on macOS: its unix backend passes
|
||||
# `&mut u8` where macOS libc's `mincore` expects `*mut c_char` (seen on
|
||||
# #6599). The dependency is opt-in behind the `rusty-alloc` feature.
|
||||
# Drop this once upstream fixes the macOS signature.
|
||||
- dependency-name: "rusty_alloc-api"
|
||||
versions: [">= 2"]
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
- package-ecosystem: nix
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
- package-ecosystem: npm
|
||||
directory: /web
|
||||
schedule:
|
||||
interval: monthly
|
||||
open-pull-requests-limit: 3
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"outputs": {
|
||||
"codex-repl": {
|
||||
"platforms": {
|
||||
"macos-aarch64": { "regex": "^codex-repl-aarch64-apple-darwin\\.zst$", "path": "codex-repl" },
|
||||
"macos-x86_64": { "regex": "^codex-repl-x86_64-apple-darwin\\.zst$", "path": "codex-repl" },
|
||||
"linux-x86_64": { "regex": "^codex-repl-x86_64-unknown-linux-musl\\.zst$", "path": "codex-repl" },
|
||||
"linux-aarch64": { "regex": "^codex-repl-aarch64-unknown-linux-gnu\\.zst$", "path": "codex-repl" }
|
||||
}
|
||||
},
|
||||
|
||||
"codex-exec": {
|
||||
"platforms": {
|
||||
"macos-aarch64": { "regex": "^codex-exec-aarch64-apple-darwin\\.zst$", "path": "codex-exec" },
|
||||
"macos-x86_64": { "regex": "^codex-exec-x86_64-apple-darwin\\.zst$", "path": "codex-exec" },
|
||||
"linux-x86_64": { "regex": "^codex-exec-x86_64-unknown-linux-musl\\.zst$", "path": "codex-exec" },
|
||||
"linux-aarch64": { "regex": "^codex-exec-aarch64-unknown-linux-gnu\\.zst$", "path": "codex-exec" }
|
||||
}
|
||||
},
|
||||
|
||||
"codex": {
|
||||
"platforms": {
|
||||
"macos-aarch64": { "regex": "^codex-aarch64-apple-darwin\\.zst$", "path": "codex" },
|
||||
"macos-x86_64": { "regex": "^codex-x86_64-apple-darwin\\.zst$", "path": "codex" },
|
||||
"linux-x86_64": { "regex": "^codex-x86_64-unknown-linux-musl\\.zst$", "path": "codex" },
|
||||
"linux-aarch64": { "regex": "^codex-aarch64-unknown-linux-gnu\\.zst$", "path": "codex" }
|
||||
}
|
||||
},
|
||||
|
||||
"codex-linux-sandbox": {
|
||||
"platforms": {
|
||||
"linux-x86_64": { "regex": "^codex-linux-sandbox-x86_64-unknown-linux-musl\\.zst$", "path": "codex-linux-sandbox" },
|
||||
"linux-aarch64": { "regex": "^codex-linux-sandbox-aarch64-unknown-linux-gnu\\.zst$", "path": "codex-linux-sandbox" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "${repo}"
|
||||
|
||||
template=".github/ISSUE_TEMPLATE/agent-task.yml"
|
||||
retired_workflows=(
|
||||
".github/workflows/v0868-milestone-sync.yml"
|
||||
".github/workflows/agent-task-labels.yml"
|
||||
)
|
||||
|
||||
fail() {
|
||||
echo "agent-task metadata contract failed: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ -f "${template}" ]] || fail "missing ${template}"
|
||||
for retired in "${retired_workflows[@]}"; do
|
||||
[[ ! -e "${retired}" ]] || fail "retired workflow still exists: ${retired}"
|
||||
done
|
||||
|
||||
if grep -nE '^(title|labels):.*v[0-9]+\.[0-9]+\.[0-9]+' "${template}"; then
|
||||
fail "agent-task defaults must not pin a release version"
|
||||
fi
|
||||
|
||||
grep -qF 'labels: ["agent-ready"]' "${template}" \
|
||||
|| fail "agent-task template must opt into agent-ready without a version label"
|
||||
|
||||
echo "Agent-task metadata is release-neutral and milestone-safe."
|
||||
@@ -1,83 +0,0 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
const { spawnSync } = require("node:child_process");
|
||||
const { test } = require("node:test");
|
||||
|
||||
const sourceRoot = path.resolve(__dirname, "../..");
|
||||
|
||||
function fixture(t) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "cw-d8-launch-"));
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
const repo = path.join(root, "source");
|
||||
fs.mkdirSync(path.join(repo, "scripts"), { recursive: true });
|
||||
for (const name of ["dev-test.sh", "dev-cache.sh", "dev-cargo.sh", "with-hermetic-test-home.sh", "build-lock.py"]) {
|
||||
fs.copyFileSync(path.join(sourceRoot, "scripts", name), path.join(repo, "scripts", name));
|
||||
fs.chmodSync(path.join(repo, "scripts", name), 0o755);
|
||||
}
|
||||
fs.writeFileSync(path.join(repo, "Cargo.toml"), '[workspace]\nmembers = []\n');
|
||||
const bin = path.join(root, "bin");
|
||||
const home = path.join(root, "ambient home");
|
||||
fs.mkdirSync(bin); fs.mkdirSync(home);
|
||||
const log = path.join(root, "cargo-invocations.jsonl");
|
||||
fs.writeFileSync(path.join(bin, "cargo"), `#!${process.execPath}
|
||||
|
||||
const fs = require("node:fs");
|
||||
const args = process.argv.slice(2);
|
||||
if (args.includes("--version")) { console.log("cargo 1.97.0 (source fixture)"); process.exit(0); }
|
||||
fs.appendFileSync(process.env.D8_INVOCATIONS, JSON.stringify({args, home: process.env.HOME, cache: process.env.CODEWHALE_CACHE_ROOT}) + "\\n");
|
||||
if (args.includes("build") && process.env.D8_BUILD_FAILURE) process.exit(Number(process.env.D8_BUILD_FAILURE));
|
||||
console.log("test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s");
|
||||
`);
|
||||
fs.writeFileSync(path.join(bin, "rustc"), `#!/bin/sh\nprintf '%s\n' 'rustc 1.97.0 (source fixture)' 'commit-hash: source-fixture'\n`);
|
||||
fs.writeFileSync(path.join(bin, "rustup"), `#!/bin/sh\nif [ "$1" = which ] && [ "$2" = rustc ]; then printf '%s\n' "$D8_TOOLCHAIN/rustc"; else exit 99; fi\n`);
|
||||
for (const name of ["cargo", "rustc", "rustup"]) fs.chmodSync(path.join(bin, name), 0o755);
|
||||
return {
|
||||
home, log,
|
||||
run(area, failure) {
|
||||
const result = spawnSync("sh", [path.join(repo, "scripts/dev-test.sh"), area, "filter with spaces"], {
|
||||
cwd: repo, encoding: "utf8",
|
||||
env: { ...process.env, PATH: `${bin}${path.delimiter}${process.env.PATH}`, HOME: home,
|
||||
USERPROFILE: home, CARGO_HOME: path.join(root, "cargo-home"), RUSTUP_HOME: path.join(root, "rustup-home"),
|
||||
CODEWHALE_DEV_NEXTEST: "0", CODEWHALE_BUILD_LOCK: "0", CODEWHALE_BUILD_LOCK_HELD: "",
|
||||
CODEWHALE_CACHE_ROOT: path.join(root, "cache"), D8_INVOCATIONS: log, D8_TOOLCHAIN: bin,
|
||||
D8_BUILD_FAILURE: failure ? String(failure) : "" },
|
||||
});
|
||||
const calls = fs.existsSync(log) ? fs.readFileSync(log, "utf8").trim().split("\n").map(JSON.parse) : [];
|
||||
return { result, calls };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
for (const area of ["tui-integration", "tui-cucumber"]) {
|
||||
test(`${area} builds the canonical executable before isolated acceptance`, (t) => {
|
||||
const f = fixture(t); const { result, calls } = f.run(area);
|
||||
assert.equal(result.status, 0, result.stderr + result.stdout);
|
||||
assert.equal(calls.length, 2);
|
||||
const build = calls[0].args.slice(calls[0].args.indexOf("build"));
|
||||
assert.deepEqual(build, ["build", "-p", "codewhale-cli", "--bin", "codewhale", "--locked"]);
|
||||
const check = calls[1].args.slice(calls[1].args.indexOf("test"));
|
||||
assert.deepEqual(check, ["test", "-p", "codewhale-tui", "--test", area === "tui-integration" ? "integration" : "cucumber", "--locked", "filter with spaces"]);
|
||||
for (const call of calls) {
|
||||
assert.notEqual(call.home, f.home);
|
||||
assert.equal(fs.existsSync(call.home), false, "temporary test HOME is removed");
|
||||
assert.ok(call.cache.endsWith("cache"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
test("canonical build failure prevents acceptance and preserves its exit status", (t) => {
|
||||
const f = fixture(t); const { result, calls } = f.run("tui-integration", 39);
|
||||
assert.equal(result.status, 39, result.stderr + result.stdout);
|
||||
assert.equal(calls.length, 1);
|
||||
assert.ok(calls[0].args.includes("build"));
|
||||
});
|
||||
|
||||
test("library-only verification does not build an executable", (t) => {
|
||||
const f = fixture(t); const { result, calls } = f.run("config");
|
||||
assert.equal(result.status, 0, result.stderr + result.stdout);
|
||||
assert.equal(calls.length, 1);
|
||||
assert.ok(calls[0].args.includes("test"));
|
||||
assert.ok(calls[0].args.includes("--lib"));
|
||||
});
|
||||
@@ -1,168 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Update the Homebrew tap after a release.
|
||||
#
|
||||
# The tap GitHub repo is still Hmbown/homebrew-deepseek-tui until Hunter
|
||||
# renames it. The formula users type is `codewhale`. The legacy
|
||||
# `deepseek-tui` formula stays as a deprecated alias for one overlap release.
|
||||
#
|
||||
# Expected environment:
|
||||
# TAG – git tag, e.g. "v0.8.31"
|
||||
# MANIFEST – path to codewhale-artifacts-sha256.txt
|
||||
# TAP_REPO – owner/repo of the Homebrew tap
|
||||
# TOKEN – PAT with contents:write on TAP_REPO (required unless
|
||||
# FORMULA_OUTPUT renders locally for contract tests)
|
||||
# FORMULA_OUTPUT – optional local render path used by contract tests
|
||||
# FORMULA_LEGACY_OUTPUT – optional local render path for the alias formula
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${TAG:?}"
|
||||
: "${MANIFEST:?}"
|
||||
: "${TAP_REPO:?}"
|
||||
|
||||
if [ -z "${TOKEN:-}" ] && [ -z "${FORMULA_OUTPUT:-}" ]; then
|
||||
echo "::error::No Homebrew tap token (TOKEN) configured; refusing to skip the tap update." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION="${TAG#v}"
|
||||
|
||||
die() { echo "::error::${1}" >&2; exit 1; }
|
||||
|
||||
sha() {
|
||||
local file="${1:?}"
|
||||
local val
|
||||
val="$(awk -v f="${file}" '$2 == f {print $1; exit}' "${MANIFEST}")"
|
||||
if [ -z "${val}" ]; then
|
||||
die "Missing binary in checksum manifest: ${file}"
|
||||
fi
|
||||
echo "${val}"
|
||||
}
|
||||
|
||||
# --- read checksums ---------------------------------------------------
|
||||
|
||||
# One compiled runtime exposed under the two supported command names.
|
||||
SHA_COD_MACOS_ARM="$(sha codewhale-macos-arm64)"
|
||||
SHA_CODEW_MACOS_ARM="$(sha codew-macos-arm64)"
|
||||
SHA_COD_MACOS_X64="$(sha codewhale-macos-x64)"
|
||||
SHA_CODEW_MACOS_X64="$(sha codew-macos-x64)"
|
||||
SHA_COD_LINUX_ARM="$(sha codewhale-linux-arm64)"
|
||||
SHA_CODEW_LINUX_ARM="$(sha codew-linux-arm64)"
|
||||
SHA_COD_LINUX_X64="$(sha codewhale-linux-x64)"
|
||||
SHA_CODEW_LINUX_X64="$(sha codew-linux-x64)"
|
||||
readonly SHA_COD_MACOS_ARM SHA_CODEW_MACOS_ARM
|
||||
readonly SHA_COD_MACOS_X64 SHA_CODEW_MACOS_X64
|
||||
readonly SHA_COD_LINUX_ARM SHA_CODEW_LINUX_ARM
|
||||
readonly SHA_COD_LINUX_X64 SHA_CODEW_LINUX_X64
|
||||
|
||||
# --- temp dirs --------------------------------------------------------
|
||||
|
||||
FORMULA_FILE="$(mktemp)"
|
||||
LEGACY_FILE="$(mktemp)"
|
||||
TAP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "${TAP_DIR}" "${FORMULA_FILE}" "${LEGACY_FILE}"' EXIT
|
||||
|
||||
readonly BASE_URL="https://github.com/codewhale-hq/CodeWhale/releases/download/${TAG}"
|
||||
|
||||
render_formula() {
|
||||
local class_name="${1:?}"
|
||||
local extra_header="${2:-}"
|
||||
cat << EOF
|
||||
class ${class_name} < Formula
|
||||
desc "Agentic terminal for open-source and open-weight coding models"
|
||||
homepage "https://github.com/codewhale-hq/CodeWhale"
|
||||
version "${VERSION}"
|
||||
license "MIT"
|
||||
depends_on "node"
|
||||
${extra_header}
|
||||
on_macos do
|
||||
if Hardware::CPU.arm?
|
||||
url "${BASE_URL}/codewhale-macos-arm64", using: :nounzip
|
||||
sha256 "${SHA_COD_MACOS_ARM}"
|
||||
resource "codew" do
|
||||
url "${BASE_URL}/codew-macos-arm64", using: :nounzip
|
||||
sha256 "${SHA_CODEW_MACOS_ARM}"
|
||||
end
|
||||
else
|
||||
url "${BASE_URL}/codewhale-macos-x64", using: :nounzip
|
||||
sha256 "${SHA_COD_MACOS_X64}"
|
||||
resource "codew" do
|
||||
url "${BASE_URL}/codew-macos-x64", using: :nounzip
|
||||
sha256 "${SHA_CODEW_MACOS_X64}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
on_linux do
|
||||
if Hardware::CPU.arm?
|
||||
url "${BASE_URL}/codewhale-linux-arm64", using: :nounzip
|
||||
sha256 "${SHA_COD_LINUX_ARM}"
|
||||
resource "codew" do
|
||||
url "${BASE_URL}/codew-linux-arm64", using: :nounzip
|
||||
sha256 "${SHA_CODEW_LINUX_ARM}"
|
||||
end
|
||||
else
|
||||
url "${BASE_URL}/codewhale-linux-x64", using: :nounzip
|
||||
sha256 "${SHA_COD_LINUX_X64}"
|
||||
resource "codew" do
|
||||
url "${BASE_URL}/codew-linux-x64", using: :nounzip
|
||||
sha256 "${SHA_CODEW_LINUX_X64}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def install
|
||||
bin.install Dir["*"].first => "codewhale"
|
||||
resource("codew").stage { bin.install Dir["*"].first => "codew" }
|
||||
end
|
||||
|
||||
test do
|
||||
system "#{bin}/codewhale", "--version"
|
||||
system "#{bin}/codew", "--version"
|
||||
end
|
||||
end
|
||||
EOF
|
||||
}
|
||||
|
||||
render_formula "Codewhale" "" > "${FORMULA_FILE}"
|
||||
render_formula "DeepseekTui" " deprecate! date: \"2026-08-14\", because: \"renamed to codewhale\"
|
||||
" > "${LEGACY_FILE}"
|
||||
|
||||
if [ -n "${FORMULA_OUTPUT:-}" ]; then
|
||||
cp "${FORMULA_FILE}" "${FORMULA_OUTPUT}"
|
||||
echo "Rendered Homebrew formula to ${FORMULA_OUTPUT}"
|
||||
if [ -n "${FORMULA_LEGACY_OUTPUT:-}" ]; then
|
||||
cp "${LEGACY_FILE}" "${FORMULA_LEGACY_OUTPUT}"
|
||||
echo "Rendered legacy Homebrew formula to ${FORMULA_LEGACY_OUTPUT}"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- push to tap repo --------------------------------------------------
|
||||
|
||||
ENCODED_TOKEN="$(printf '%s' "${TOKEN}" | python3 -c 'import sys,urllib.parse;print(urllib.parse.quote(sys.stdin.read(),safe=""))')"
|
||||
TAP_URL="https://x-access-token:${ENCODED_TOKEN}@github.com/${TAP_REPO}.git"
|
||||
|
||||
git clone --depth 1 "${TAP_URL}" "${TAP_DIR}"
|
||||
|
||||
mkdir -p "${TAP_DIR}/Formula"
|
||||
cp "${FORMULA_FILE}" "${TAP_DIR}/Formula/codewhale.rb"
|
||||
cp "${LEGACY_FILE}" "${TAP_DIR}/Formula/deepseek-tui.rb"
|
||||
|
||||
cd "${TAP_DIR}"
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git add Formula/codewhale.rb Formula/deepseek-tui.rb
|
||||
|
||||
if git diff --cached --quiet; then
|
||||
echo "Formula unchanged (already at ${VERSION}); nothing to push."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git commit -m "chore: bump formula to ${VERSION}
|
||||
|
||||
Automated update from the release workflow."
|
||||
|
||||
git push origin HEAD:main
|
||||
echo "Pushed formula update to ${TAP_REPO} (v${VERSION})"
|
||||
@@ -1,62 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "${tmp_dir}"' EXIT
|
||||
|
||||
manifest="${tmp_dir}/codewhale-artifacts-sha256.txt"
|
||||
formula="${tmp_dir}/codewhale.rb"
|
||||
legacy="${tmp_dir}/deepseek-tui.rb"
|
||||
|
||||
assets=(
|
||||
codewhale-macos-arm64
|
||||
codew-macos-arm64
|
||||
codewhale-macos-x64
|
||||
codew-macos-x64
|
||||
codewhale-linux-arm64
|
||||
codew-linux-arm64
|
||||
codewhale-linux-x64
|
||||
codew-linux-x64
|
||||
)
|
||||
|
||||
for asset in "${assets[@]}"; do
|
||||
printf '%064d %s\n' 0 "${asset}" >> "${manifest}"
|
||||
done
|
||||
|
||||
TAG=v1.2.3 \
|
||||
MANIFEST="${manifest}" \
|
||||
TAP_REPO=Hmbown/homebrew-deepseek-tui \
|
||||
FORMULA_OUTPUT="${formula}" \
|
||||
FORMULA_LEGACY_OUTPUT="${legacy}" \
|
||||
bash "${repo_root}/.github/scripts/update-homebrew-tap.sh"
|
||||
|
||||
ruby -c "${formula}" >/dev/null
|
||||
ruby -c "${legacy}" >/dev/null
|
||||
grep -Fq 'class Codewhale < Formula' "${formula}"
|
||||
grep -Fq 'class DeepseekTui < Formula' "${legacy}"
|
||||
grep -Fq 'deprecate! date: "2026-08-14", because: "renamed to codewhale"' "${legacy}"
|
||||
grep -Fq 'desc "Agentic terminal for open-source and open-weight coding models"' "${formula}"
|
||||
grep -Fq 'depends_on "node"' "${formula}"
|
||||
test "$(grep -Fc 'resource "codew" do' "${formula}")" -eq 4
|
||||
grep -Fq 'bin.install Dir["*"].first => "codew"' "${formula}"
|
||||
grep -Fq 'system "#{bin}/codew", "--version"' "${formula}"
|
||||
if grep -Fq 'codewhale-tui' "${formula}"; then
|
||||
echo "Homebrew formula must not install the legacy TUI compatibility asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -Fq 'class DeepseekTui' "${formula}"; then
|
||||
echo "Primary Homebrew formula must be Codewhale, not DeepseekTui" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Without a token and without a local render target the script must fail,
|
||||
# never report a skipped tap update as success.
|
||||
if TAG=v1.2.3 MANIFEST="${manifest}" TAP_REPO=Hmbown/homebrew-deepseek-tui TOKEN= \
|
||||
bash "${repo_root}/.github/scripts/update-homebrew-tap.sh" >"${tmp_dir}/no-token.log" 2>&1; then
|
||||
echo "update-homebrew-tap.sh must fail when no tap token is configured" >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -Fq 'No Homebrew tap token' "${tmp_dir}/no-token.log"
|
||||
|
||||
echo "update-homebrew-tap tests passed"
|
||||
@@ -1,176 +0,0 @@
|
||||
name: Create release tag
|
||||
|
||||
# Manual helper that creates `vX.Y.Z` from the current `main` workspace
|
||||
# version after the release source is frozen. This is intentionally not
|
||||
# triggered by every version bump on `main`: release prep can land before the
|
||||
# same-version issue/PR queue is truly empty, and an eager tag leaves later
|
||||
# same-version work outside the public release anchor.
|
||||
#
|
||||
# IMPORTANT: tag pushes signed by the default `GITHUB_TOKEN` do NOT trigger
|
||||
# downstream `on: push: tags` workflows (GitHub Actions safety rule). For
|
||||
# this auto-tag flow to actually fire `release.yml`, store a PAT (or
|
||||
# fine-grained token) with `contents: write` on this repo as the
|
||||
# `RELEASE_TAG_PAT` secret. Without it, the tag is created but `release.yml`
|
||||
# does NOT run automatically; run `release.yml` manually for the version.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: auto-tag-${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
tag:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.sha }}
|
||||
# Prefer PAT so the resulting tag push triggers release.yml.
|
||||
# Falls back to GITHUB_TOKEN, which will tag but NOT trigger.
|
||||
token: ${{ secrets.RELEASE_TAG_PAT || github.token }}
|
||||
|
||||
- name: Require main dispatch
|
||||
id: source
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ "${GITHUB_REF_NAME}" != "main" ]; then
|
||||
echo "::error::Create release tags from main only; rerun this workflow with ref 'main'." >&2
|
||||
exit 1
|
||||
fi
|
||||
intended_sha="$(git rev-parse 'HEAD^{commit}')"
|
||||
event_sha="$(git rev-parse "${GITHUB_SHA}^{commit}")"
|
||||
if [ "${intended_sha}" != "${event_sha}" ]; then
|
||||
echo "::error::Checkout ${intended_sha} does not match dispatch SHA ${event_sha}." >&2
|
||||
exit 1
|
||||
fi
|
||||
git fetch --no-tags origin "+refs/heads/main:refs/remotes/origin/main"
|
||||
live_main="$(git rev-parse 'refs/remotes/origin/main^{commit}')"
|
||||
if [ "${intended_sha}" != "${live_main}" ]; then
|
||||
echo "::error::Dispatch SHA ${intended_sha} is stale; live origin/main is ${live_main}. Rerun from the current main ref." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "sha=${intended_sha}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Read workspace version
|
||||
id: ver
|
||||
run: |
|
||||
v="$(grep -E '^version = "' Cargo.toml | head -n1 | sed -E 's/^version = "([^"]+)".*/\1/')"
|
||||
if [ -z "$v" ]; then
|
||||
echo "::error::Could not parse workspace version from Cargo.toml" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! echo "$v" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then
|
||||
echo "::error::Workspace version '$v' is not valid semver (expected X.Y.Z or X.Y.Z-prerelease)" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "version=$v" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=v$v" >> "$GITHUB_OUTPUT"
|
||||
echo "Workspace version: $v"
|
||||
|
||||
- name: Check whether tag already exists
|
||||
id: check
|
||||
env:
|
||||
EXPECTED_SHA: ${{ steps.source.outputs.sha }}
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
remote_tag_sha="$(git ls-remote --tags origin "refs/tags/${TAG}^{}" | awk 'NR == 1 {print $1}')"
|
||||
if [ -z "${remote_tag_sha}" ]; then
|
||||
remote_tag_sha="$(git ls-remote --tags origin "refs/tags/${TAG}" | awk 'NR == 1 {print $1}')"
|
||||
fi
|
||||
if [ -n "${remote_tag_sha}" ]; then
|
||||
if [ "${remote_tag_sha}" != "${EXPECTED_SHA}" ]; then
|
||||
echo "::error::Tag ${TAG} already exists at ${remote_tag_sha}, expected ${EXPECTED_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Tag ${TAG} already exists at the intended SHA; nothing to do."
|
||||
else
|
||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Tag ${TAG} does not exist; will create."
|
||||
fi
|
||||
|
||||
- name: Verify version consistency
|
||||
if: steps.check.outputs.exists == 'false'
|
||||
run: |
|
||||
./scripts/release/check-versions.sh --require-dated-release || {
|
||||
echo "::error::Version consistency check failed. Aborting tag creation." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Create and push tag
|
||||
id: create
|
||||
if: steps.check.outputs.exists == 'false'
|
||||
env:
|
||||
EXPECTED_SHA: ${{ steps.source.outputs.sha }}
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git fetch --no-tags origin "+refs/heads/main:refs/remotes/origin/main"
|
||||
live_main="$(git rev-parse 'refs/remotes/origin/main^{commit}')"
|
||||
if [ "${live_main}" != "${EXPECTED_SHA}" ]; then
|
||||
echo "::error::origin/main advanced to ${live_main}; refusing to tag stale ${EXPECTED_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
remote_tag_sha="$(git ls-remote --tags origin "refs/tags/${TAG}^{}" | awk 'NR == 1 {print $1}')"
|
||||
if [ -z "${remote_tag_sha}" ]; then
|
||||
remote_tag_sha="$(git ls-remote --tags origin "refs/tags/${TAG}" | awk 'NR == 1 {print $1}')"
|
||||
fi
|
||||
if [ -n "${remote_tag_sha}" ]; then
|
||||
if [ "${remote_tag_sha}" != "${EXPECTED_SHA}" ]; then
|
||||
echo "::error::Tag ${TAG} appeared at ${remote_tag_sha}, expected ${EXPECTED_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "pushed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Tag ${TAG} already exists at the intended SHA after refresh; nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
git tag -f "${TAG}" "${EXPECTED_SHA}"
|
||||
max_retries=3
|
||||
retry_count=0
|
||||
while [ "${retry_count}" -lt "${max_retries}" ]; do
|
||||
if git push origin "${TAG}"; then
|
||||
echo "pushed=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Pushed ${TAG}. release.yml should now run (requires RELEASE_TAG_PAT for trigger)."
|
||||
exit 0
|
||||
fi
|
||||
remote_tag_sha="$(git ls-remote --tags origin "refs/tags/${TAG}^{}" | awk 'NR == 1 {print $1}')"
|
||||
if [ -z "${remote_tag_sha}" ]; then
|
||||
remote_tag_sha="$(git ls-remote --tags origin "refs/tags/${TAG}" | awk 'NR == 1 {print $1}')"
|
||||
fi
|
||||
if [ -n "${remote_tag_sha}" ]; then
|
||||
if [ "${remote_tag_sha}" != "${EXPECTED_SHA}" ]; then
|
||||
echo "::error::Tag ${TAG} appeared during push at ${remote_tag_sha}, expected ${EXPECTED_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "pushed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Tag ${TAG} appeared at the intended SHA during push; treating as already handled."
|
||||
exit 0
|
||||
fi
|
||||
retry_count=$((retry_count + 1))
|
||||
if [ "${retry_count}" -lt "${max_retries}" ]; then
|
||||
echo "Push attempt ${retry_count} failed; retrying in 10s..."
|
||||
sleep 10
|
||||
fi
|
||||
done
|
||||
|
||||
echo "::error::Failed to push tag ${TAG} after ${max_retries} attempts." >&2
|
||||
exit 1
|
||||
|
||||
- name: Warn if PAT missing
|
||||
if: steps.create.outputs.pushed == 'true'
|
||||
env:
|
||||
HAS_PAT: ${{ secrets.RELEASE_TAG_PAT != '' }}
|
||||
run: |
|
||||
if [ "${HAS_PAT}" != "true" ]; then
|
||||
echo "::warning::RELEASE_TAG_PAT secret is not set. The tag was pushed using GITHUB_TOKEN, which does NOT trigger release.yml. First confirm no tag-triggered Release run is queued or active, then run 'gh workflow run release.yml --ref ${{ steps.ver.outputs.tag }} -f version=${{ steps.ver.outputs.version }}'."
|
||||
fi
|
||||
@@ -1,82 +0,0 @@
|
||||
name: Cache janitor
|
||||
|
||||
# The Actions cache held 11,099,951,127 bytes across 2,896 entries, past the
|
||||
# repo's 10 GiB cap, so GitHub evicted live main entries first. A cache is
|
||||
# readable only from its own ref and the default branch: once a PR closes or
|
||||
# a release finishes, its refs/pull/N or refs/tags/vX entries are dead weight.
|
||||
# This deletes them. Branch caches (main included) are never touched; see
|
||||
# scripts/release/prune-actions-caches.sh.
|
||||
on:
|
||||
# pull_request_target so fork PRs get a token that can delete caches. It
|
||||
# never checks out or runs PR code: the checkout below is the base branch.
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
workflow_run:
|
||||
workflows: [Release]
|
||||
types: [completed]
|
||||
schedule:
|
||||
- cron: '17 4 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: List what the sweep would delete without deleting it
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: cache-janitor-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.workflow_run.id || 'sweep' }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
prune:
|
||||
name: Prune dead caches
|
||||
if: github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push')
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write
|
||||
# Read PR state for the sweep.
|
||||
pull-requests: read
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
# Base-branch script only. Never the PR head.
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
persist-credentials: false
|
||||
- name: Prune
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
RUN_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
script=scripts/release/prune-actions-caches.sh
|
||||
case "${EVENT_NAME}" in
|
||||
pull_request_target)
|
||||
"${script}" --ref "refs/pull/${PR_NUMBER}/merge" --ref "refs/pull/${PR_NUMBER}/head"
|
||||
;;
|
||||
workflow_run)
|
||||
# A tag-push Release run reports the tag as head_branch. The
|
||||
# script refuses anything that is not a refs/tags/<tag> ref.
|
||||
"${script}" --ref "refs/tags/${RUN_HEAD_BRANCH}"
|
||||
;;
|
||||
workflow_dispatch)
|
||||
if [[ "${DRY_RUN}" == "true" ]]; then
|
||||
"${script}" --dry-run --sweep
|
||||
else
|
||||
"${script}" --sweep
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
"${script}" --sweep
|
||||
;;
|
||||
esac
|
||||
@@ -1,39 +0,0 @@
|
||||
name: cargo-deny
|
||||
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
workflow_call: {}
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
deny:
|
||||
name: cargo-deny
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
checks:
|
||||
- advisories
|
||||
- bans licenses sources
|
||||
# Prevent sudden announcement of a new advisory from failing CI
|
||||
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
# rust-toolchain.toml pins `channel = "1.100.0"` with no target, so
|
||||
# inside the action's alpine (musl) container rustup resolves it to
|
||||
# `1.100.0-x86_64-unknown-linux-musl`, which the container does not
|
||||
# have. Without provisioning, the action's `rustup show` step prints
|
||||
# "error: override toolchain '1.100.0-x86_64-unknown-linux-musl' is not
|
||||
# installed" and then auto-installs mid-run (network-dependent ~12s
|
||||
# detour, hard failure if the download stalls). `rust-version` makes the
|
||||
# entrypoint run `rustup default 1.100.0` (same musl host triple) before
|
||||
# cargo-deny touches the workspace, so the toolchain file resolves to an
|
||||
# already-installed toolchain on every run.
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
with:
|
||||
command: check ${{ matrix.checks }}
|
||||
rust-version: 1.100.0
|
||||
@@ -1,133 +0,0 @@
|
||||
name: Catalog refresh
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 5 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: catalog-refresh
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
refresh:
|
||||
name: Refresh the model catalog
|
||||
if: github.repository == 'codewhale-hq/Codewhale'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
BRANCH: catalog-refresh
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.CODEWHALE_APP_ID }}
|
||||
private-key: ${{ secrets.CODEWHALE_APP_PRIVATE_KEY }}
|
||||
permission-contents: write
|
||||
permission-pull-requests: write
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Fetch models.dev once
|
||||
run: |
|
||||
curl --fail --silent --show-error --location --max-time 60 \
|
||||
--user-agent "CodeWhale-catalog-automation (+https://github.com/codewhale-hq/Codewhale)" \
|
||||
--output "$RUNNER_TEMP/models-dev-catalog.json" \
|
||||
https://models.dev/catalog.json
|
||||
- name: Decide whether the pin is due
|
||||
id: pin
|
||||
run: |
|
||||
if python3 scripts/catalog_models_dev.py seed age --max-days 7; then
|
||||
echo "due=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "due=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- name: Propose new models, re-lock and render
|
||||
env:
|
||||
CODEWHALE_MODELS_DEV_PATH: ${{ runner.temp }}/models-dev-catalog.json
|
||||
run: |
|
||||
python3 scripts/catalog_models_dev.py seed discover --write --snapshot "$CODEWHALE_MODELS_DEV_PATH" | tee "$RUNNER_TEMP/discover.txt"
|
||||
python3 scripts/catalog_models_dev.py seed lock > "$RUNNER_TEMP/lock-report.txt"
|
||||
python3 scripts/catalog_models_dev.py seed render
|
||||
(cd web && node scripts/derive-facts.mjs)
|
||||
- name: Decide whether anything material changed
|
||||
id: change
|
||||
run: |
|
||||
material=$(git diff -U0 -- \
|
||||
crates/config/assets/models_dev.bundled.json \
|
||||
crates/config/assets/catalog_corrections.json \
|
||||
scripts/catalog/models_dev_seed.toml \
|
||||
| grep -E '^[+-]' | grep -vE '^(\+\+\+|---)' | grep -v '"upstream":' || true)
|
||||
if [ -n "$material" ] || [ "${{ steps.pin.outputs.due }}" = "true" ]; then
|
||||
echo "open=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "open=false" >> "$GITHUB_OUTPUT"
|
||||
echo "No catalog change and the pin is under 7 days old; nothing to propose."
|
||||
fi
|
||||
- name: Run the catalog checks
|
||||
id: checks
|
||||
if: steps.change.outputs.open == 'true'
|
||||
run: |
|
||||
set +e
|
||||
{
|
||||
python3 scripts/catalog_models_dev.py seed render --check
|
||||
python3 scripts/catalog_models_dev.py seed age --max-days 30
|
||||
python3 -m unittest scripts/catalog_models_dev_test.py
|
||||
(cd web && node scripts/check-facts.mjs)
|
||||
} > "$RUNNER_TEMP/checks.txt" 2>&1
|
||||
status=$?
|
||||
if [ "$status" -eq 0 ]; then echo "ok=true" >> "$GITHUB_OUTPUT"; else echo "ok=false" >> "$GITHUB_OUTPUT"; fi
|
||||
tail -n 40 "$RUNNER_TEMP/checks.txt"
|
||||
- name: Push the branch and open or update the pull request
|
||||
if: steps.change.outputs.open == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
CHECKS_OK: ${{ steps.checks.outputs.ok }}
|
||||
APP_ID: ${{ vars.CODEWHALE_APP_ID }}
|
||||
run: |
|
||||
git config user.name 'codewhale-agent[bot]'
|
||||
git config user.email "${APP_ID}+codewhale-agent[bot]@users.noreply.github.com"
|
||||
git checkout -B "$BRANCH"
|
||||
git add crates/config/assets/models_dev.bundled.json crates/config/assets/catalog_corrections.json \
|
||||
scripts/catalog/models_dev_seed.toml scripts/catalog/models_dev_seed.lock.json web/lib/facts.generated.ts
|
||||
git commit -m "chore(catalog): refresh the Models.dev offline seed" \
|
||||
-m "Generated by .github/workflows/catalog-refresh.yml from models.dev. No LLM wrote these rows."
|
||||
git push --force-with-lease="$BRANCH" \
|
||||
"https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "HEAD:refs/heads/$BRANCH"
|
||||
{
|
||||
echo "Generated by the scheduled catalog refresh from models.dev. No LLM wrote these rows; review them like any other change."
|
||||
echo
|
||||
echo "New models from the vendor allowlist. Merging this PR publishes them on the public /models page; rows carry an \`unreviewed\` source until someone checks them against the vendor's docs:"
|
||||
echo
|
||||
echo '```text'
|
||||
cat "$RUNNER_TEMP/discover.txt"
|
||||
echo '```'
|
||||
echo
|
||||
echo "Checks run in the job: \`seed render --check\`, \`seed age --max-days 30\`, \`scripts/catalog_models_dev_test.py\`, \`web/scripts/check-facts.mjs\`: $([ "$CHECKS_OK" = true ] && echo passed || echo FAILED, see the job log; this PR is a draft)."
|
||||
echo
|
||||
echo "To stop proposing a model, add its id to \`[discover].skip\` in \`scripts/catalog/models_dev_seed.toml\`."
|
||||
} > "$RUNNER_TEMP/pr-body.md"
|
||||
title="chore(catalog): refresh the Models.dev offline seed"
|
||||
existing=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number // empty')
|
||||
if [ -n "$existing" ]; then
|
||||
gh pr edit "$existing" --title "$title" --body-file "$RUNNER_TEMP/pr-body.md"
|
||||
if [ "$CHECKS_OK" = true ]; then
|
||||
gh pr ready "$existing"
|
||||
else
|
||||
gh pr ready --undo "$existing"
|
||||
fi
|
||||
else
|
||||
draft=""
|
||||
[ "$CHECKS_OK" = true ] || draft="--draft"
|
||||
gh pr create --base main --head "$BRANCH" --title "$title" --body-file "$RUNNER_TEMP/pr-body.md" $draft
|
||||
fi
|
||||
@@ -0,0 +1,29 @@
|
||||
name: CLA Assistant
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_target:
|
||||
types: [opened, closed, synchronize]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: write
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
cla:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: contributor-assistant/[email protected]
|
||||
if: |
|
||||
github.event_name == 'pull_request_target' ||
|
||||
github.event.comment.body == 'recheck' ||
|
||||
github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
path-to-document: docs/CLA.md
|
||||
path-to-signatures: signatures/cla.json
|
||||
branch: cla-signatures
|
||||
allowlist: dependabot[bot]
|
||||
@@ -1,61 +0,0 @@
|
||||
# CodeQL advanced setup.
|
||||
#
|
||||
# Scans the same languages the repository's default setup scanned (Actions,
|
||||
# JavaScript/TypeScript, Python, Rust) with the same default query suite, but
|
||||
# reads .github/codeql/codeql-config.yml so test paths stay out of alerts.
|
||||
#
|
||||
# Gated off by default. While the repository uses CodeQL "Default" setup,
|
||||
# GitHub rejects SARIF uploads from this workflow ("Code Scanning could not
|
||||
# process the submitted SARIF"), so the analyze job runs only when the
|
||||
# repository variable CODEQL_ADVANCED_SETUP is 'true'. The founder flips it
|
||||
# after switching the repository to "Advanced" setup (Settings -> Code
|
||||
# security -> Code scanning); until then every run skips the job.
|
||||
name: CodeQL
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: codeql-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
if: vars.CODEQL_ADVANCED_SETUP == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
- language: python
|
||||
build-mode: none
|
||||
- language: rust
|
||||
build-mode: none
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
|
||||
- name: Perform CodeQL analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: /language:${{ matrix.language }}
|
||||
@@ -1,119 +0,0 @@
|
||||
name: Codewhale PR Review
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr-number:
|
||||
description: Pull request number
|
||||
required: true
|
||||
type: string
|
||||
permissions: {}
|
||||
concurrency:
|
||||
group: codewhale-review-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr-number }}
|
||||
cancel-in-progress: false
|
||||
jobs:
|
||||
installation:
|
||||
if: >-
|
||||
github.actor != 'dependabot[bot]' &&
|
||||
((github.event_name == 'workflow_dispatch') ||
|
||||
(github.event_name == 'pull_request' && !github.event.pull_request.draft && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
((github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') &&
|
||||
(github.event.issue.pull_request || github.event.pull_request) &&
|
||||
github.event.comment.user.type == 'User' && contains(github.event.comment.body, '@codewhale')))
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions: {}
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.CODEWHALE_APP_ID }}
|
||||
private-key: ${{ secrets.CODEWHALE_APP_PRIVATE_KEY }}
|
||||
permission-contents: read
|
||||
permission-pull-requests: write
|
||||
review:
|
||||
needs: installation
|
||||
if: >-
|
||||
github.actor != 'dependabot[bot]' &&
|
||||
((github.event_name == 'workflow_dispatch') ||
|
||||
(github.event_name == 'pull_request' && !github.event.pull_request.draft && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
((github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') &&
|
||||
(github.event.issue.pull_request || github.event.pull_request) &&
|
||||
github.event.comment.user.type == 'User' && contains(github.event.comment.body, '@codewhale')))
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
outputs:
|
||||
findings: ${{ steps.review.outputs.findings }}
|
||||
steps:
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1
|
||||
with:
|
||||
node-version: '22'
|
||||
- uses: codewhale-hq/Codewhale@b0b2b9a335cc1d24abe8a087189bed080d1cc3db
|
||||
id: review
|
||||
with:
|
||||
version: 'v0.10.2'
|
||||
provider: 'deepseek'
|
||||
model: 'deepseek-flash'
|
||||
reasoning-effort: 'off'
|
||||
max-output-tokens: '16384'
|
||||
pr-number: ${{ inputs.pr-number }}
|
||||
trigger-phrase: '@codewhale'
|
||||
allow-forks: 'true'
|
||||
post: 'false'
|
||||
env:
|
||||
DEEPSEEK_API_KEY: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'issue_comment' || github.event.pull_request.head.repo.full_name == github.repository) && secrets.DEEPSEEK_API_KEY || '' }}
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
|
||||
if: steps.review.outputs.findings != ''
|
||||
with:
|
||||
name: codewhale-findings-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: ${{ steps.review.outputs.findings }}
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
|
||||
if: always() && steps.review.outputs.receipt != ''
|
||||
with:
|
||||
name: codewhale-review-receipt-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: ${{ steps.review.outputs.receipt }}
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
publish:
|
||||
needs: review
|
||||
if: needs.review.outputs.findings != ''
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1
|
||||
with:
|
||||
node-version: '22'
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
|
||||
with:
|
||||
name: codewhale-findings-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: findings
|
||||
- uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.CODEWHALE_APP_ID }}
|
||||
private-key: ${{ secrets.CODEWHALE_APP_PRIVATE_KEY }}
|
||||
permission-contents: read
|
||||
permission-pull-requests: write
|
||||
- uses: codewhale-hq/Codewhale/scripts/github-action/publish@b0b2b9a335cc1d24abe8a087189bed080d1cc3db
|
||||
id: publish
|
||||
with:
|
||||
findings: findings/findings.json
|
||||
pr-number: ${{ inputs.pr-number }}
|
||||
trigger-phrase: '@codewhale'
|
||||
allow-forks: 'true'
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
reviewer-login: 'codewhale-agent[bot]'
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
|
||||
if: always() && steps.publish.outputs.receipt != ''
|
||||
with:
|
||||
name: codewhale-publication-receipt-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: ${{ steps.publish.outputs.receipt }}
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
@@ -1,75 +0,0 @@
|
||||
name: Main landing gate
|
||||
|
||||
# Why this exists
|
||||
#
|
||||
# Until 2026-11-08 `ci.yml` ran only on `pull_request` and `workflow_dispatch`,
|
||||
# so nothing validated a push to `main` and a landing that was red on arrival stayed red
|
||||
# until the next release candidate run, and every open PR then inherits the
|
||||
# failure: on 2026-10-29 four commits landed red behind `[skip ci]`
|
||||
# (`PersistRequest::ClearCheckpoint` became dead, `clippy` failed on
|
||||
# `codewhale-skills`, and the blocking-calls budget went stale for a file that
|
||||
# had moved crates) and all nine open PRs looked broken for reasons that had
|
||||
# nothing to do with their authors' code.
|
||||
#
|
||||
# This job is deliberately narrow. It runs `scripts/preflight.sh --check`,
|
||||
# which is the checked-in local preflight for exactly the class of breakage that
|
||||
# reaches `main` unnoticed: rustfmt, the CHANGELOG slice, README locale stamps,
|
||||
# the dead-code and blocking-calls budgets, generated-asset drift (including
|
||||
# the website's generated facts, agent docs and feature registry), and the
|
||||
# feature receipts. Measured at 45 s on a warm Linux runner.
|
||||
#
|
||||
# Since 2026-11-08 (founder) the `full-ci` job also dispatches `ci.yml` on every
|
||||
# push to main, so every gate and the three-OS suite run on each landing. The
|
||||
# preflight job stays narrow so a red landing still shows up in minutes.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: main-landing-${{ github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
preflight:
|
||||
name: Landing preflight
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
# The preflight diffs against the push's base commit to scope feature
|
||||
# receipts, and the changelog/budget checks read history.
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.100.0
|
||||
components: rustfmt
|
||||
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Preflight
|
||||
env:
|
||||
BEFORE_SHA: ${{ github.event.before }}
|
||||
run: bash scripts/preflight.sh --check
|
||||
|
||||
full-ci:
|
||||
name: Dispatch full CI
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Run ci.yml with every gate on the pushed commit
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: gh workflow run ci.yml --repo "${GITHUB_REPOSITORY}" --ref main -f expected_sha="${GITHUB_SHA}"
|
||||
@@ -1,55 +0,0 @@
|
||||
name: Marketplace connection
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
catalog:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
path: core
|
||||
persist-credentials: false
|
||||
- name: Select catalog revision
|
||||
id: source
|
||||
working-directory: core
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
import json, os, re
|
||||
snapshot = json.load(open('crates/plugins/assets/first-party-marketplace.json'))
|
||||
revision = snapshot['revision']
|
||||
assert re.fullmatch(r'[0-9a-f]{40}', revision), 'Invalid marketplace revision'
|
||||
if os.environ['EVENT_NAME'] in ('schedule', 'workflow_dispatch'):
|
||||
revision = 'main'
|
||||
with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
|
||||
output.write(f'revision={revision}\n')
|
||||
PY
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
repository: codewhale-hq/codewhale-plugin-marketplace
|
||||
ref: ${{ steps.source.outputs.revision }}
|
||||
path: marketplace
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: '22'
|
||||
package-manager-cache: false
|
||||
- name: Check catalog and real bundle manifests
|
||||
run: node marketplace/scripts/check-marketplace.mjs
|
||||
- name: Check bundled catalog snapshot
|
||||
run: python3 core/scripts/sync-marketplace.py --marketplace marketplace --check
|
||||
- name: Check current skill mirrors
|
||||
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
node marketplace/scripts/check-marketplace.mjs --core core
|
||||
- name: Explain a drift failure
|
||||
if: failure()
|
||||
run: |
|
||||
echo 'Review upstream changes using docs/PLUGIN_MARKETPLACE.md. Sync source-owned mirrors, rerun checks, regenerate the catalog, and rebuild. This workflow never installs, trusts, enables, or publishes plugins.' >> "$GITHUB_STEP_SUMMARY"
|
||||
@@ -1,201 +0,0 @@
|
||||
name: Nightly
|
||||
|
||||
# Manual artifact build for final release validation.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: nightly-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: -Dwarnings
|
||||
CODEWHALE_BUILD_SHA: ${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ matrix.platform }}
|
||||
# Native builds use the same finite cold-build allowance as release/CI.
|
||||
timeout-minutes: 210
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
platform: linux-x64
|
||||
binary: codewhale
|
||||
primary_artifact: codewhale-linux-x64
|
||||
alias_artifact: codew-linux-x64
|
||||
- os: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
platform: linux-arm64
|
||||
binary: codewhale
|
||||
primary_artifact: codewhale-linux-arm64
|
||||
alias_artifact: codew-linux-arm64
|
||||
- os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
platform: macos-x64
|
||||
binary: codewhale
|
||||
primary_artifact: codewhale-macos-x64
|
||||
alias_artifact: codew-macos-x64
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
platform: macos-arm64
|
||||
binary: codewhale
|
||||
primary_artifact: codewhale-macos-arm64
|
||||
alias_artifact: codew-macos-arm64
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
platform: windows-x64
|
||||
binary: codewhale.exe
|
||||
primary_artifact: codewhale-windows-x64.exe
|
||||
alias_artifact: codew-windows-x64.exe
|
||||
- os: windows-11-arm
|
||||
target: aarch64-pc-windows-msvc
|
||||
platform: windows-arm64
|
||||
binary: codewhale.exe
|
||||
primary_artifact: codewhale-windows-arm64.exe
|
||||
alias_artifact: codew-windows-arm64.exe
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.100.0
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: mozilla-actions/[email protected]
|
||||
id: sccache
|
||||
continue-on-error: true
|
||||
- name: Enable sccache
|
||||
if: steps.sccache.outcome == 'success' && runner.os != 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "SCCACHE_GHA_ENABLED=true"
|
||||
echo "RUSTC_WRAPPER=sccache"
|
||||
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1"
|
||||
} >> "${GITHUB_ENV}"
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-bin: false
|
||||
- name: Install Linux GNU system dependencies
|
||||
if: matrix.target == 'x86_64-unknown-linux-gnu'
|
||||
run: |
|
||||
for i in 1 2 3 4 5; do
|
||||
sudo apt-get update && break
|
||||
echo "apt-get update failed (attempt $i); retrying in 15s"
|
||||
sleep 15
|
||||
done
|
||||
sudo apt-get install -y libdbus-1-dev pkg-config
|
||||
- name: Install Linux ARM64 musl toolchain
|
||||
if: matrix.target == 'aarch64-unknown-linux-musl'
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y binutils musl-tools
|
||||
rustup target add aarch64-unknown-linux-musl
|
||||
- name: Build
|
||||
shell: bash
|
||||
# Nightly artifacts are disposable smoke binaries (14-day retention),
|
||||
# so skip fat LTO; tagged releases keep the full optimized profile via
|
||||
# the Release workflow. (`codegen-units` is not overridden here --
|
||||
# [profile.release] already sets 16, so restating it changed nothing.)
|
||||
#
|
||||
# RUST_MIN_STACK sizes the stack of the LLVM worker threads rustc
|
||||
# spawns to run `optimize module <crate>-cgu.N`. With `lto=off` the
|
||||
# per-CGU optimization pipeline runs while the *library* is compiled
|
||||
# rather than being deferred to the ThinLTO stage, and one crates/tui
|
||||
# codegen unit needs more stack than a 1 MiB thread provides. Measured
|
||||
# on aarch64 by holding the crate and every flag fixed and varying only
|
||||
# this value: 1 MiB crashes rustc, 2 MiB and 4 MiB succeed. Unix std
|
||||
# defaults to 2 MiB and passes; windows-11-arm sat under the
|
||||
# requirement and failed every nightly from 2026-08-16 with
|
||||
# `thread 'optimize module codewhale_tui...-cgu.13' has overflowed its
|
||||
# stack`, deterministically on the same codegen unit across all three
|
||||
# build attempts. This is a property of the crate's size, not of
|
||||
# Windows, so it is set for every target: at 788k lines in crates/tui
|
||||
# the other platforms are one refactor away from crossing 2 MiB too.
|
||||
# The value is reserved address space, not committed memory.
|
||||
env:
|
||||
CARGO_PROFILE_RELEASE_LTO: 'off'
|
||||
RUST_MIN_STACK: '16777216'
|
||||
run: |
|
||||
for attempt in 1 2 3; do
|
||||
if cargo build --release --locked --target ${{ matrix.target }} -p codewhale-cli; then
|
||||
exit 0
|
||||
fi
|
||||
if [ "${attempt}" -lt 3 ]; then
|
||||
echo "Build attempt ${attempt} failed; retrying in 30s..."
|
||||
sleep 30
|
||||
fi
|
||||
done
|
||||
echo "Build failed after 3 attempts" >&2
|
||||
exit 1
|
||||
- name: Verify static Linux ARM64 binary and launch
|
||||
if: matrix.target == 'aarch64-unknown-linux-musl' && runner.arch == 'ARM64'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bin_path="target/${{ matrix.target }}/release/${{ matrix.binary }}"
|
||||
if readelf -l "${bin_path}" | grep -Fq 'INTERP'; then
|
||||
echo "Expected a static musl binary, but ${bin_path} has an ELF interpreter" >&2
|
||||
exit 1
|
||||
fi
|
||||
"${bin_path}" --version
|
||||
- name: Smoke binary on matching native runners
|
||||
if: >-
|
||||
(startsWith(matrix.target, 'x86_64-') && runner.arch == 'X64') ||
|
||||
(startsWith(matrix.target, 'aarch64-') && runner.arch == 'ARM64')
|
||||
shell: bash
|
||||
run: |
|
||||
bin_dir="target/${{ matrix.target }}/release"
|
||||
"${bin_dir}/${{ matrix.binary }}" --version
|
||||
- name: Stage artifact
|
||||
id: stage
|
||||
shell: bash
|
||||
run: |
|
||||
short_sha="${GITHUB_SHA::12}"
|
||||
bin_path="target/${{ matrix.target }}/release/${{ matrix.binary }}"
|
||||
if [ ! -f "${bin_path}" ]; then
|
||||
echo "Binary not at ${bin_path}; searching target/ for ${{ matrix.binary }}:"
|
||||
find target -name "${{ matrix.binary }}" -type f
|
||||
exit 1
|
||||
fi
|
||||
|
||||
stage_copy() {
|
||||
local artifact="$1"
|
||||
local dir="$2"
|
||||
mkdir -p "${dir}"
|
||||
cp "${bin_path}" "${dir}/${artifact}"
|
||||
cat > "${dir}/nightly-build-info.txt" <<INFO
|
||||
repository=${GITHUB_REPOSITORY}
|
||||
ref=${GITHUB_REF_NAME}
|
||||
commit=${GITHUB_SHA}
|
||||
artifact=${artifact}
|
||||
profile=release-nightly-no-lto
|
||||
INFO
|
||||
}
|
||||
|
||||
stage_copy "${{ matrix.primary_artifact }}" nightly-primary
|
||||
stage_copy "${{ matrix.alias_artifact }}" nightly-alias
|
||||
cmp -s \
|
||||
"nightly-primary/${{ matrix.primary_artifact }}" \
|
||||
"nightly-alias/${{ matrix.alias_artifact }}"
|
||||
echo "primary_name=${{ matrix.primary_artifact }}-${short_sha}" >> "${GITHUB_OUTPUT}"
|
||||
echo "alias_name=${{ matrix.alias_artifact }}-${short_sha}" >> "${GITHUB_OUTPUT}"
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ steps.stage.outputs.primary_name }}
|
||||
path: nightly-primary/*
|
||||
retention-days: 14
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ steps.stage.outputs.alias_name }}
|
||||
path: nightly-alias/*
|
||||
retention-days: 14
|
||||
@@ -1,56 +0,0 @@
|
||||
name: OpenHarmony
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ohos-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: -Dwarnings
|
||||
|
||||
jobs:
|
||||
cargo-check:
|
||||
name: cargo check (aarch64-unknown-linux-ohos)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Install Rust target
|
||||
uses: dtolnay/[email protected]
|
||||
with:
|
||||
targets: aarch64-unknown-linux-ohos
|
||||
- name: Install OpenHarmony native SDK
|
||||
id: ohos-sdk
|
||||
# Pin the action: it downloads a published OpenHarmony SDK and verifies
|
||||
# the archive checksum. This job must never substitute host headers or
|
||||
# a stub sysroot and report that as target support.
|
||||
uses: openharmony-rs/setup-ohos-sdk@b312caf8a43bb836aa24c08f65f97e1f09a89cad
|
||||
with:
|
||||
version: '6.1'
|
||||
components: native
|
||||
# Save the ~715 MB SDK cache from main only. The action has no
|
||||
# restore-only mode, and with `cache: true` every PR ref that missed
|
||||
# main's entry saved its own 715 MB copy; a handful of open PRs
|
||||
# pushed the repo past the 10 GiB Actions cache cap and evicted the
|
||||
# Rust caches CI actually depends on. PRs download the SDK instead
|
||||
# (checksum-verified from the GitHub mirror below).
|
||||
cache: ${{ github.ref == 'refs/heads/main' }}
|
||||
mirror: true
|
||||
- name: Check Codewhale TUI and CLI with the real SDK/sysroot
|
||||
shell: bash
|
||||
env:
|
||||
OHOS_NATIVE_SDK: ${{ steps.ohos-sdk.outputs.ohos_sdk_native }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -x "${OHOS_NATIVE_SDK}/llvm/bin/clang"
|
||||
test -d "${OHOS_NATIVE_SDK}/sysroot"
|
||||
. ./scripts/ohos-env.sh
|
||||
cargo check --target aarch64-unknown-linux-ohos -p codewhale-tui -p codewhale-cli --locked
|
||||
@@ -1,111 +0,0 @@
|
||||
name: Pet conformance
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: pet-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
portable:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: npm
|
||||
cache-dependency-path: pet/package-lock.json
|
||||
- uses: dtolnay/[email protected]
|
||||
- run: npm --prefix pet ci --ignore-scripts
|
||||
- run: npm --prefix pet run check
|
||||
- run: npm --prefix pet run sync
|
||||
- name: Embedded sources match canonical core
|
||||
run: git diff --exit-code -- crates/tui/src/tui/pet_watch/pet-native.js pet/ios/Resources
|
||||
# The authored body is checked in five times and nothing kept the copies
|
||||
# in step, so the Rust core silently carried a different whale from every
|
||||
# other surface and conformance never passed. Compare them directly: no
|
||||
# sync step produces these, so `git diff` cannot catch the drift.
|
||||
- name: One whale body across every surface
|
||||
run: |
|
||||
canonical=crates/tui/src/tui/ambient_life/whale-points.tsv
|
||||
for copy in pet/whale-points.tsv pet/public/whale-points.tsv pet/rs/whale-points.tsv; do
|
||||
diff -q "$canonical" "$copy" || {
|
||||
echo "::error file=$copy::diverged from $canonical; the Rust core is canonical"
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
- run: cargo fetch --locked --manifest-path pet/tui/Cargo.toml
|
||||
- name: TypeScript and Rust parity, including reduced motion
|
||||
run: ./pet/verify.sh --no-swift
|
||||
recorder:
|
||||
name: recorder (${{ matrix.os }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: npm
|
||||
cache-dependency-path: pet/package-lock.json
|
||||
- run: npm --prefix pet ci --ignore-scripts
|
||||
- run: npm --prefix pet run check
|
||||
- uses: dtolnay/[email protected]
|
||||
if: runner.os == 'macOS'
|
||||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
if: runner.os == 'macOS'
|
||||
- name: Build the standalone shared owner and verify its transport
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
cargo build --locked -j 2 -p codewhale-cli --bin codewhale
|
||||
python3 pet/scripts/check-shared.py target/debug/codewhale
|
||||
- name: Build and exercise the Apple host and PCM boundary
|
||||
if: runner.os == 'macOS'
|
||||
run: PET_OWNER_BINARY="$PWD/target/debug/codewhale" ./pet/scripts/check-apple.sh
|
||||
- name: Package the locally signed macOS app
|
||||
if: runner.os == 'macOS'
|
||||
run: ditto -c -k --keepParent pet/macos/CodewhalePet.app codewhale-pet-macos.zip
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: runner.os == 'macOS'
|
||||
with:
|
||||
name: codewhale-pet-macos
|
||||
path: codewhale-pet-macos.zip
|
||||
if-no-files-found: error
|
||||
android:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '17'
|
||||
- name: Enable emulator acceleration on the disposable runner
|
||||
run: sudo chmod a+rw /dev/kvm
|
||||
- uses: reactivecircus/[email protected]
|
||||
with:
|
||||
api-level: 35
|
||||
arch: x86_64
|
||||
target: google_apis
|
||||
profile: pixel_7
|
||||
disable-animations: false
|
||||
working-directory: pet/android
|
||||
script: ./gradlew --no-daemon assembleDebug connectedDebugAndroidTest lintDebug
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: always()
|
||||
with:
|
||||
name: codewhale-pet-android
|
||||
path: |
|
||||
pet/android/build/outputs/apk/debug/*.apk
|
||||
pet/android/build/reports/androidTests/
|
||||
pet/android/build/reports/lint-results-debug.*
|
||||
@@ -1,703 +0,0 @@
|
||||
name: Release artifacts
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
source_sha:
|
||||
description: Exact 40-character source commit to build
|
||||
required: true
|
||||
type: string
|
||||
version:
|
||||
description: Workspace version without a v prefix
|
||||
required: true
|
||||
type: string
|
||||
compiled_host_delivery:
|
||||
description: Explicitly include qualified optional companions; Node stays default
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
compiled_host_run_id:
|
||||
description: Green official CI run at this exact SHA containing Native image proof
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
compiled_host_targets:
|
||||
description: Comma-separated matching-native targets with qualified CI receipts
|
||||
required: false
|
||||
default: linux-x64,macos-arm64,windows-x64
|
||||
type: string
|
||||
retention_days:
|
||||
description: Retention for Actions-only intermediate and assembled artifacts
|
||||
required: false
|
||||
default: 7
|
||||
type: number
|
||||
secrets:
|
||||
CODEWHALE_RELEASE_SIGNING_KEY:
|
||||
description: Ed25519 release key (PEM). Absent means the release ships unsigned.
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: -Dwarnings
|
||||
# Build identity is the trusted workflow SHA. Callers pass source_sha only
|
||||
# so `pin` can refuse a mismatch; it must not retarget checkout or caches.
|
||||
CODEWHALE_BUILD_SHA: ${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
pin:
|
||||
name: Pin caller SHA to this run
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Require source_sha equals github.sha
|
||||
env:
|
||||
SOURCE_SHA: ${{ inputs.source_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${#SOURCE_SHA}" -ne 40 || "${SOURCE_SHA}" =~ [^0-9a-fA-F] ]]; then
|
||||
echo "::error::source_sha must be a full 40-character commit SHA." >&2
|
||||
exit 1
|
||||
fi
|
||||
expected="$(printf '%s' "${SOURCE_SHA}" | tr '[:upper:]' '[:lower:]')"
|
||||
actual="$(printf '%s' "${GITHUB_SHA}" | tr '[:upper:]' '[:lower:]')"
|
||||
if [[ "${actual}" != "${expected}" ]]; then
|
||||
echo "::error::Reusable workflow SHA ${actual} does not match source_sha ${SOURCE_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
- name: Require explicit matching-native qualification selection
|
||||
if: inputs.compiled_host_delivery
|
||||
env:
|
||||
QUALIFICATION_RUN: ${{ inputs.compiled_host_run_id }}
|
||||
HOST_TARGETS: ${{ inputs.compiled_host_targets }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[[ "${QUALIFICATION_RUN}" =~ ^[0-9]+$ ]] || { echo 'Enabled host delivery needs an exact green CI run id' >&2; exit 1; }
|
||||
[[ -n "${HOST_TARGETS}" ]] || { echo 'No compiled host targets selected' >&2; exit 1; }
|
||||
declare -A seen
|
||||
IFS=',' read -ra selected <<< "${HOST_TARGETS}"
|
||||
for target in "${selected[@]}"; do
|
||||
case "${target}" in linux-x64|linux-arm64|macos-x64|macos-arm64|windows-x64|windows-arm64) ;; *) echo "Unsupported compiled host target ${target}" >&2; exit 1 ;; esac
|
||||
[[ -z "${seen[${target}]:-}" ]] || { echo "Duplicate compiled host target ${target}" >&2; exit 1; }
|
||||
seen["${target}"]=1
|
||||
done
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.platform }}
|
||||
timeout-minutes: ${{ startsWith(matrix.os, 'macos-') && 360 || 210 }}
|
||||
# FreeBSD is a source-build target with no prebuilt asset, no npm binary and no CI check
|
||||
# (see packaging/freebsd/README.md and docs/INSTALL.md#freebsd).
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-musl
|
||||
platform: linux-x64
|
||||
cli_binary: codewhale
|
||||
shim_binary: codew
|
||||
cli_artifact: codewhale-linux-x64
|
||||
shim_artifact: codew-linux-x64
|
||||
compat_tui_artifact: codewhale-tui-linux-x64
|
||||
- os: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-musl
|
||||
platform: linux-arm64
|
||||
cli_binary: codewhale
|
||||
shim_binary: codew
|
||||
cli_artifact: codewhale-linux-arm64
|
||||
shim_artifact: codew-linux-arm64
|
||||
compat_tui_artifact: codewhale-tui-linux-arm64
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-linux-android
|
||||
platform: android-arm64
|
||||
cli_binary: codewhale
|
||||
shim_binary: codew
|
||||
cli_artifact: codewhale-android-arm64
|
||||
shim_artifact: codew-android-arm64
|
||||
compat_tui_artifact: codewhale-tui-android-arm64
|
||||
- os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
platform: macos-x64
|
||||
cli_binary: codewhale
|
||||
shim_binary: codew
|
||||
cli_artifact: codewhale-macos-x64
|
||||
shim_artifact: codew-macos-x64
|
||||
compat_tui_artifact: codewhale-tui-macos-x64
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
platform: macos-arm64
|
||||
cli_binary: codewhale
|
||||
shim_binary: codew
|
||||
cli_artifact: codewhale-macos-arm64
|
||||
shim_artifact: codew-macos-arm64
|
||||
compat_tui_artifact: codewhale-tui-macos-arm64
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
platform: windows-x64
|
||||
cli_binary: codewhale.exe
|
||||
shim_binary: codew.exe
|
||||
cli_artifact: codewhale-windows-x64.exe
|
||||
shim_artifact: codew-windows-x64.exe
|
||||
compat_tui_artifact: codewhale-tui-windows-x64.exe
|
||||
- os: windows-11-arm
|
||||
target: aarch64-pc-windows-msvc
|
||||
platform: windows-arm64
|
||||
cli_binary: codewhale.exe
|
||||
shim_binary: codew.exe
|
||||
cli_artifact: codewhale-windows-arm64.exe
|
||||
shim_artifact: codew-windows-arm64.exe
|
||||
compat_tui_artifact: codewhale-tui-windows-arm64.exe
|
||||
# Native HarmonyOS and OpenHarmony. The artifact is useless unsigned:
|
||||
# the device refuses to execute an unsigned ELF (see
|
||||
# docs/HarmonyOS.md), so this leg self-signs the binary before staging.
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-ohos
|
||||
platform: ohos-arm64
|
||||
cli_binary: codewhale
|
||||
shim_binary: codew
|
||||
cli_artifact: codewhale-ohos-arm64
|
||||
shim_artifact: codew-ohos-arm64
|
||||
compat_tui_artifact: codewhale-tui-ohos-arm64
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: pin
|
||||
steps:
|
||||
# No ref: — GITHUB_SHA only. CodeQL treats workflow_call checkout-with-ref
|
||||
# and any ref named *sha* as an untrusted checkout (cache-poisoning).
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master 2026-07-18
|
||||
with:
|
||||
toolchain: 1.100.0
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
if: runner.os == 'Linux'
|
||||
id: sccache
|
||||
continue-on-error: true
|
||||
- name: Enable sccache
|
||||
if: steps.sccache.outcome == 'success' && runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "SCCACHE_GHA_ENABLED=true"
|
||||
echo "RUSTC_WRAPPER=sccache"
|
||||
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1"
|
||||
} >> "${GITHUB_ENV}"
|
||||
# Restore after the trusted lockfile is on disk. Key is OS + arch +
|
||||
# explicit stable toolchain + rust-cache's Cargo.lock / rust-toolchain
|
||||
# hash. Never interpolate github.event, github.ref, github.sha, or inputs.
|
||||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
with:
|
||||
cache-bin: false
|
||||
prefix-key: v1-${{ runner.os }}-${{ runner.arch }}-stable
|
||||
- name: Add swap so the aarch64 dist build is not killed for memory
|
||||
if: runner.os == 'Linux' && contains(matrix.target, 'aarch64')
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
dir=/mnt
|
||||
if ! sudo test -w /mnt; then dir=/; fi
|
||||
avail=$(df -Pk "$dir" | awk 'NR==2 {print int($4 / 1048576)}')
|
||||
if [ "$avail" -ge 30 ]; then size=16; elif [ "$avail" -ge 14 ]; then size=8; else size=0; fi
|
||||
if [ "$size" -gt 0 ]; then
|
||||
sudo fallocate -l "${size}G" "$dir/cw-swapfile"
|
||||
sudo chmod 600 "$dir/cw-swapfile"
|
||||
sudo mkswap "$dir/cw-swapfile" >/dev/null
|
||||
sudo swapon "$dir/cw-swapfile"
|
||||
fi
|
||||
free -g
|
||||
df -h "$dir"
|
||||
- name: Build static Linux binaries (musl)
|
||||
if: endsWith(matrix.target, '-unknown-linux-musl')
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y binutils musl-tools
|
||||
rustup target add ${{ matrix.target }}
|
||||
cargo build --profile dist --locked --target ${{ matrix.target }} -p codewhale-cli
|
||||
- name: Configure Android NDK linker
|
||||
if: matrix.target == 'aarch64-linux-android' && runner.os == 'Linux'
|
||||
shell: bash
|
||||
env:
|
||||
ANDROID_NDK_VERSION: 27.2.12479018
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libclang-dev
|
||||
ndk="${ANDROID_NDK_ROOT:-${ANDROID_NDK_HOME:-}}"
|
||||
linker=""
|
||||
if [[ -n "${ndk}" ]]; then
|
||||
linker="${ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
|
||||
fi
|
||||
if [[ -z "${linker}" || ! -x "${linker}" ]]; then
|
||||
if ! command -v sdkmanager >/dev/null 2>&1; then
|
||||
echo "sdkmanager is required to install Android NDK ${ANDROID_NDK_VERSION}" >&2
|
||||
exit 1
|
||||
fi
|
||||
android_home="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
|
||||
if [[ -z "${android_home}" ]]; then
|
||||
echo "ANDROID_HOME or ANDROID_SDK_ROOT is required to install Android NDK ${ANDROID_NDK_VERSION}" >&2
|
||||
exit 1
|
||||
fi
|
||||
yes | sdkmanager --licenses >/dev/null || true
|
||||
sdkmanager --install "ndk;${ANDROID_NDK_VERSION}"
|
||||
ndk="${android_home}/ndk/${ANDROID_NDK_VERSION}"
|
||||
linker="${ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
|
||||
fi
|
||||
ar="${ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar"
|
||||
if [[ ! -x "${linker}" ]]; then
|
||||
echo "Android linker not found: ${linker}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -x "${ar}" ]]; then
|
||||
echo "Android archiver not found: ${ar}" >&2
|
||||
exit 1
|
||||
fi
|
||||
{
|
||||
echo "ANDROID_NDK_ROOT=${ndk}"
|
||||
echo "ANDROID_NDK_HOME=${ndk}"
|
||||
echo "CC_aarch64_linux_android=${linker}"
|
||||
echo "AR_aarch64_linux_android=${ar}"
|
||||
echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=${linker}"
|
||||
echo "BINDGEN_EXTRA_CLANG_ARGS_aarch64_linux_android=--target=aarch64-linux-android24 --sysroot=${ndk}/toolchains/llvm/prebuilt/linux-x86_64/sysroot"
|
||||
} >> "${GITHUB_ENV}"
|
||||
- name: Build
|
||||
if: ${{ !endsWith(matrix.target, '-unknown-linux-musl') && matrix.platform != 'ohos-arm64' }}
|
||||
shell: bash
|
||||
run: cargo build --profile dist --locked --target ${{ matrix.target }} -p codewhale-cli
|
||||
- name: Install OpenHarmony native SDK
|
||||
if: matrix.platform == 'ohos-arm64'
|
||||
id: ohos-sdk
|
||||
# Pin the action: it downloads a published OpenHarmony SDK and verifies
|
||||
# the archive checksum, so a broken mirror fails here instead of
|
||||
# producing a binary linked against host headers.
|
||||
uses: openharmony-rs/setup-ohos-sdk@b312caf8a43bb836aa24c08f65f97e1f09a89cad
|
||||
with:
|
||||
version: '6.1'
|
||||
# `toolchains` carries binary-sign-tool.jar, which signs the binary
|
||||
# below; a release without it would ship an ELF the device refuses to
|
||||
# execute.
|
||||
components: 'native;toolchains'
|
||||
# One download per release is cheaper than letting a tag save a 715 MB
|
||||
# cache entry that main's jobs then compete with.
|
||||
cache: false
|
||||
mirror: true
|
||||
- name: Install Java for the OpenHarmony binary signer
|
||||
if: matrix.platform == 'ohos-arm64'
|
||||
uses: actions/setup-java@77dd5b2ce225ea11469928ac317850245a74ae78 # v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '17'
|
||||
- name: Cross-compile and code-sign the OpenHarmony binary
|
||||
if: matrix.platform == 'ohos-arm64'
|
||||
shell: bash
|
||||
env:
|
||||
OHOS_NATIVE_SDK: ${{ steps.ohos-sdk.outputs.ohos_sdk_native }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -x "${OHOS_NATIVE_SDK}/llvm/bin/clang"
|
||||
test -d "${OHOS_NATIVE_SDK}/sysroot"
|
||||
# ohos-env.sh also puts the SDK's cmake on PATH, which aws-lc-sys
|
||||
# needs; without it the build dies inside its CMake probe.
|
||||
. ./scripts/ohos-env.sh
|
||||
cargo build --profile dist --locked --target "${{ matrix.target }}" -p codewhale-cli
|
||||
|
||||
bin="target/${{ matrix.target }}/dist/${{ matrix.cli_binary }}"
|
||||
tools="$(dirname "${OHOS_NATIVE_SDK}")/toolchains/lib"
|
||||
# SDK 6.1 publishes the native `binary-sign-tool` and no jar, while
|
||||
# DevEco ships the Java build; both take the same arguments.
|
||||
if [[ -f "${tools}/binary-sign-tool.jar" ]]; then
|
||||
signer=(java -jar "${tools}/binary-sign-tool.jar")
|
||||
elif [[ -f "${tools}/binary-sign-tool" ]]; then
|
||||
chmod +x "${tools}/binary-sign-tool"
|
||||
signer=("${tools}/binary-sign-tool")
|
||||
else
|
||||
echo "no binary-sign-tool in ${tools}; the device refuses an unsigned ELF" >&2
|
||||
ls -la "${tools}" >&2
|
||||
exit 1
|
||||
fi
|
||||
"${signer[@]}" sign -inFile "${bin}" -outFile "${bin}" -selfSign 1
|
||||
|
||||
# Capture then match. `tool | grep -q` cannot be used under
|
||||
# `pipefail`: grep exits at the first match, the tool takes SIGPIPE,
|
||||
# and llvm-readelf reports that as exit 74, which reads as a failed
|
||||
# verification of a good binary.
|
||||
signature="$("${signer[@]}" display-sign -inFile "${bin}")"
|
||||
case "${signature}" in
|
||||
*'code signature is self-sign'*) ;;
|
||||
*)
|
||||
echo 'the staged binary is not self-signed; the device would refuse to execute it' >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
kind="$(file -b "${bin}")"
|
||||
case "${kind}" in
|
||||
*'ARM aarch64'*) ;;
|
||||
*)
|
||||
echo "not an aarch64 ELF: ${kind}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
headers="$("${OHOS_NATIVE_SDK}/llvm/bin/llvm-readelf" -l "${bin}")"
|
||||
case "${headers}" in
|
||||
*'/lib/ld-musl-aarch64.so.1'*) ;;
|
||||
*)
|
||||
echo "unexpected ELF interpreter for ${bin}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
- name: Materialize codew command alias
|
||||
shell: bash
|
||||
run: |
|
||||
bin_dir="target/${{ matrix.target }}/dist"
|
||||
cp "${bin_dir}/${{ matrix.cli_binary }}" "${bin_dir}/${{ matrix.shim_binary }}"
|
||||
cmp "${bin_dir}/${{ matrix.cli_binary }}" "${bin_dir}/${{ matrix.shim_binary }}"
|
||||
- name: Verify static Linux binaries and launch on matching native runners
|
||||
if: >-
|
||||
endsWith(matrix.target, '-unknown-linux-musl') &&
|
||||
((startsWith(matrix.target, 'x86_64-') && runner.arch == 'X64') ||
|
||||
(startsWith(matrix.target, 'aarch64-') && runner.arch == 'ARM64'))
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bin_dir="target/${{ matrix.target }}/dist"
|
||||
for binary in "${{ matrix.cli_binary }}" "${{ matrix.shim_binary }}"; do
|
||||
bin_path="${bin_dir}/${binary}"
|
||||
if readelf -l "${bin_path}" | grep -Fq 'INTERP'; then
|
||||
echo "Expected a static musl binary, but ${bin_path} has an ELF interpreter" >&2
|
||||
exit 1
|
||||
fi
|
||||
"${bin_path}" --version
|
||||
done
|
||||
- name: Smoke binaries on matching native runners
|
||||
if: >-
|
||||
matrix.target != 'aarch64-linux-android' &&
|
||||
((startsWith(matrix.target, 'x86_64-') && runner.arch == 'X64') ||
|
||||
(startsWith(matrix.target, 'aarch64-') && runner.arch == 'ARM64'))
|
||||
shell: bash
|
||||
run: |
|
||||
bin_dir="target/${{ matrix.target }}/dist"
|
||||
"${bin_dir}/${{ matrix.cli_binary }}" --version
|
||||
"${bin_dir}/${{ matrix.shim_binary }}" --version
|
||||
- name: Windows Terminal clipboard-to-draft acceptance
|
||||
if: matrix.platform == 'windows-x64'
|
||||
timeout-minutes: 5
|
||||
shell: pwsh
|
||||
run: node scripts/release/windows-terminal-clipboard.mjs "target/${{ matrix.target }}/dist/codewhale.exe" "${{ runner.temp }}/windows-clipboard"
|
||||
- name: Retain Windows Terminal clipboard-to-draft evidence
|
||||
if: ${{ !cancelled() && matrix.platform == 'windows-x64' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: release-windows-clipboard-${{ github.sha }}
|
||||
path: ${{ runner.temp }}/windows-clipboard/
|
||||
if-no-files-found: error
|
||||
retention-days: ${{ inputs.retention_days }}
|
||||
- name: Stage binaries
|
||||
shell: bash
|
||||
run: |
|
||||
stage_binary() {
|
||||
local binary="$1"
|
||||
local artifact="$2"
|
||||
local bin_path="target/${{ matrix.target }}/dist/${binary}"
|
||||
if [[ ! -f "${bin_path}" ]]; then
|
||||
echo "Binary not at ${bin_path}; searching target/ for ${binary}:" >&2
|
||||
find target -name "${binary}" -type f
|
||||
exit 1
|
||||
fi
|
||||
cp "${bin_path}" "${artifact}"
|
||||
}
|
||||
|
||||
stage_binary "${{ matrix.cli_binary }}" "${{ matrix.cli_artifact }}"
|
||||
stage_binary "${{ matrix.shim_binary }}" "${{ matrix.shim_artifact }}"
|
||||
# Compatibility bridge for v0.9.4's hard-coded release
|
||||
# completeness/updater contract. This is the same runtime, not a
|
||||
# separately compiled or installed TUI command.
|
||||
stage_binary "${{ matrix.cli_binary }}" "${{ matrix.compat_tui_artifact }}"
|
||||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2
|
||||
if: inputs.compiled_host_delivery && contains(format(',{0},', inputs.compiled_host_targets), format(',{0},', matrix.platform))
|
||||
with:
|
||||
bun-version: 1.4.0
|
||||
- name: Stage exact qualified optional host image
|
||||
if: inputs.compiled_host_delivery && contains(format(',{0},', inputs.compiled_host_targets), format(',{0},', matrix.platform))
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
QUALIFICATION_RUN: ${{ inputs.compiled_host_run_id }}
|
||||
TARGET_PLATFORM: ${{ matrix.platform }}
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
RUNTIME_CLOSURE_DIR: ${{ vars.CODEWHALE_COMPILED_HOST_CLOSURE_DIR }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
proof_dir="${RUNNER_TEMP}/compiled-host-proof-${TARGET_PLATFORM}"
|
||||
node scripts/release/fetch-compiled-host-proof.js --repo "${GITHUB_REPOSITORY}" --run-id "${QUALIFICATION_RUN}" --source-sha "${GITHUB_SHA}" --target "${TARGET_PLATFORM}" --output "${proof_dir}"
|
||||
test -n "${RUNTIME_CLOSURE_DIR}" || { echo 'Enabled compiled-host delivery requires reviewed complete local runtime notices/relink-source closure' >&2; exit 1; }
|
||||
bun_binary="$(bun -p 'process.execPath')"
|
||||
extension=""
|
||||
if [[ "${TARGET_PLATFORM}" == windows-* ]]; then extension=.exe; fi
|
||||
node scripts/release/stage-compiled-host.mjs --bun "${bun_binary}" --compiled-image "${proof_dir}/codewhale-extension-host${extension}" --native-receipt "${proof_dir}/native-receipt.json" --runtime-closure "${RUNTIME_CLOSURE_DIR}/${TARGET_PLATFORM}.json" --target "${TARGET_PLATFORM}" --version "${RELEASE_VERSION}" --source-sha "${GITHUB_SHA}" --output-dir "compiled-host-${TARGET_PLATFORM}"
|
||||
- name: Check every staged Windows executable import
|
||||
if: startsWith(matrix.platform, 'windows-')
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$binaries = @('${{ matrix.cli_artifact }}', '${{ matrix.shim_artifact }}', '${{ matrix.compat_tui_artifact }}')
|
||||
$hostDirectory = 'compiled-host-${{ matrix.platform }}'
|
||||
if (Test-Path $hostDirectory) {
|
||||
$binaries += @(Get-ChildItem -Path $hostDirectory -Filter *.exe -Recurse -File | ForEach-Object { $_.FullName })
|
||||
}
|
||||
python scripts/release/check-windows-imports.py --expected-machine '${{ matrix.platform }}' --output '${{ runner.temp }}/windows-imports-${{ matrix.platform }}' @binaries
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Windows static import qualification failed' }
|
||||
- name: Retain Windows import receipts
|
||||
if: always() && startsWith(matrix.platform, 'windows-')
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||||
with:
|
||||
name: windows-imports-${{ matrix.platform }}-${{ github.sha }}
|
||||
path: ${{ runner.temp }}/windows-imports-${{ matrix.platform }}/
|
||||
if-no-files-found: warn
|
||||
retention-days: ${{ inputs.retention_days }}
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
if: inputs.compiled_host_delivery && contains(format(',{0},', inputs.compiled_host_targets), format(',{0},', matrix.platform))
|
||||
with:
|
||||
name: codewhale-compiled-host-${{ matrix.platform }}
|
||||
path: |
|
||||
compiled-host-${{ matrix.platform }}/codewhale-extension-host-*
|
||||
compiled-host-${{ matrix.platform }}/codewhale-extension-hosts.json
|
||||
if-no-files-found: error
|
||||
retention-days: ${{ inputs.retention_days }}
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: ${{ matrix.cli_artifact }}
|
||||
path: ${{ matrix.cli_artifact }}
|
||||
if-no-files-found: error
|
||||
retention-days: ${{ inputs.retention_days }}
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: ${{ matrix.shim_artifact }}
|
||||
path: ${{ matrix.shim_artifact }}
|
||||
if-no-files-found: error
|
||||
retention-days: ${{ inputs.retention_days }}
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: ${{ matrix.compat_tui_artifact }}
|
||||
path: ${{ matrix.compat_tui_artifact }}
|
||||
if-no-files-found: error
|
||||
retention-days: ${{ inputs.retention_days }}
|
||||
overwrite: true
|
||||
|
||||
bundle:
|
||||
timeout-minutes: 15
|
||||
needs: build
|
||||
if: ${{ !cancelled() && needs.build.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
|
||||
with:
|
||||
path: artifacts
|
||||
pattern: '*'
|
||||
- name: Collect only qualified requested compiled hosts
|
||||
if: inputs.compiled_host_delivery
|
||||
shell: bash
|
||||
run: node scripts/release/stage-compiled-host.mjs --collect artifacts --output-dir artifacts/codewhale-compiled-hosts
|
||||
- name: Create and checksum platform archives
|
||||
shell: bash
|
||||
env:
|
||||
SOURCE_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
source_date_epoch="$(git show -s --format=%ct "${SOURCE_SHA}")"
|
||||
if [[ ! "${source_date_epoch}" =~ ^[0-9]+$ ]]; then
|
||||
echo "Could not read a Unix timestamp for source commit ${SOURCE_SHA}" >&2
|
||||
exit 1
|
||||
fi
|
||||
SOURCE_DATE_EPOCH="${source_date_epoch}" \
|
||||
bash scripts/release/create-release-bundles.sh artifacts bundles
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: codewhale-bundles
|
||||
path: |
|
||||
bundles/*.tar.gz
|
||||
bundles/*.zip
|
||||
bundles/codewhale-bundles-sha256.txt
|
||||
if-no-files-found: error
|
||||
retention-days: ${{ inputs.retention_days }}
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
if: inputs.compiled_host_delivery
|
||||
with:
|
||||
name: codewhale-compiled-hosts
|
||||
path: artifacts/codewhale-compiled-hosts/*
|
||||
if-no-files-found: error
|
||||
retention-days: ${{ inputs.retention_days }}
|
||||
overwrite: true
|
||||
|
||||
windows-installer:
|
||||
timeout-minutes: 15
|
||||
needs: build
|
||||
if: ${{ !cancelled() && needs.build.result == 'success' }}
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
|
||||
with:
|
||||
path: artifacts
|
||||
pattern: '*windows-x64*'
|
||||
- name: Install NSIS
|
||||
shell: pwsh
|
||||
run: ./scripts/installer/install-nsis.ps1
|
||||
- name: Build NSIS installer
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
Copy-Item "artifacts\codewhale-windows-x64.exe\codewhale-windows-x64.exe" "scripts\installer\codewhale.exe"
|
||||
Copy-Item "artifacts\codew-windows-x64.exe\codew-windows-x64.exe" "scripts\installer\codew.exe"
|
||||
$makensis = "${env:ProgramFiles(x86)}\NSIS\makensis.exe"
|
||||
if (!(Test-Path $makensis)) {
|
||||
$makensis = "${env:ProgramFiles}\NSIS\makensis.exe"
|
||||
}
|
||||
if (!(Test-Path $makensis)) {
|
||||
throw "makensis.exe not found after NSIS install"
|
||||
}
|
||||
$hostDefine = @()
|
||||
$hostCatalog = "artifacts\codewhale-compiled-host-windows-x64\codewhale-extension-hosts.json"
|
||||
if (Test-Path $hostCatalog) {
|
||||
node -e "const h=require('./npm/codewhale/scripts/compiled-hosts'); const fs=require('fs'); const p=require('path'); const f=process.argv[1]; h.verifyDirectory(p.dirname(f), h.parseCatalog(fs.readFileSync(f),'${{ inputs.version }}'));" $hostCatalog
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Compiled host payload/catalog verification failed' }
|
||||
$catalog = Get-Content -Raw $hostCatalog | ConvertFrom-Json
|
||||
$hostEntry = @($catalog.hosts | Where-Object { $_.target -eq 'windows-x64' })
|
||||
if ($hostEntry.Count -ne 1) { throw 'No unique qualified Windows host' }
|
||||
$hostDir = Split-Path $hostCatalog
|
||||
Copy-Item "$hostDir\$($hostEntry[0].asset)" "scripts\installer\codewhale-extension-host.exe"
|
||||
Copy-Item "$hostDir\$($hostEntry[0].notices_asset)" "scripts\installer\codewhale-extension-host.LICENSES.txt"
|
||||
Copy-Item "$hostDir\$($hostEntry[0].source_asset)" "scripts\installer\codewhale-extension-host.relink-source.tar.gz"
|
||||
Copy-Item $hostCatalog "scripts\installer\codewhale-extension-host.release.json"
|
||||
$hostDefine = @('/DCOMPILED_HOST=1')
|
||||
}
|
||||
Push-Location scripts\installer
|
||||
& $makensis "/DVERSION=${{ inputs.version }}" @hostDefine "codewhale.nsi"
|
||||
Pop-Location
|
||||
if (!(Test-Path "scripts\installer\CodeWhaleSetup.exe")) {
|
||||
throw "CodeWhaleSetup.exe was not produced"
|
||||
}
|
||||
- name: Check Windows installer and embedded executables
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$evidenceDirectory = '${{ runner.temp }}/windows-installer-imports'
|
||||
$embeddedDirectory = Join-Path $evidenceDirectory 'embedded'
|
||||
New-Item -Path $evidenceDirectory -ItemType Directory -Force | Out-Null
|
||||
& 7z l 'scripts/installer/CodeWhaleSetup.exe' | Out-File (Join-Path $evidenceDirectory 'archive-listing.txt') -Encoding utf8
|
||||
if ($LASTEXITCODE -ne 0) { throw 'NSIS embedded payload inventory failed' }
|
||||
& 7z x 'scripts/installer/CodeWhaleSetup.exe' "-o$embeddedDirectory" '$PLUGINSDIR/*.dll' | Out-File (Join-Path $evidenceDirectory 'archive-extraction.txt') -Encoding utf8
|
||||
if ($LASTEXITCODE -ne 0) { throw 'NSIS embedded plugin extraction failed' }
|
||||
$plugins = @(Get-ChildItem -Path $embeddedDirectory -Filter *.dll -Recurse -File)
|
||||
if ($plugins.Name -notcontains 'System.dll' -or $plugins.Name -notcontains 'nsDialogs.dll') { throw 'Expected MUI2 System and nsDialogs plugin payloads' }
|
||||
$binaries = @(Get-ChildItem -Path scripts/installer -Filter *.exe -File | ForEach-Object { $_.FullName })
|
||||
if ($binaries.Count -lt 3) { throw 'Expected installer, CLI and shim executables' }
|
||||
$binaries += @($plugins | ForEach-Object { $_.FullName })
|
||||
python scripts/release/check-windows-imports.py --output $evidenceDirectory @binaries
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Windows installer static import qualification failed' }
|
||||
- name: Retain Windows installer import receipts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||||
with:
|
||||
name: windows-installer-imports-${{ github.sha }}
|
||||
path: ${{ runner.temp }}/windows-installer-imports/
|
||||
if-no-files-found: warn
|
||||
retention-days: ${{ inputs.retention_days }}
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: CodeWhaleSetup.exe
|
||||
path: scripts/installer/CodeWhaleSetup.exe
|
||||
if-no-files-found: error
|
||||
retention-days: ${{ inputs.retention_days }}
|
||||
overwrite: true
|
||||
|
||||
assemble:
|
||||
timeout-minutes: 15
|
||||
needs: [bundle, windows-installer]
|
||||
if: ${{ !cancelled() && needs.bundle.result == 'success' && needs.windows-installer.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
|
||||
with:
|
||||
path: intermediate-artifacts
|
||||
pattern: '*'
|
||||
- name: Assemble exact authoritative release inventory
|
||||
run: node scripts/release/assemble-release-assets.js intermediate-artifacts release-assets
|
||||
- name: Sign the checksum manifest when the release key is configured
|
||||
env:
|
||||
CODEWHALE_RELEASE_SIGNING_KEY: ${{ secrets.CODEWHALE_RELEASE_SIGNING_KEY }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${CODEWHALE_RELEASE_SIGNING_KEY}" ]]; then
|
||||
echo "CODEWHALE_RELEASE_SIGNING_KEY is not configured; this release is unsigned and automatic updates wait for each user's OK."
|
||||
exit 0
|
||||
fi
|
||||
bash scripts/release/sign-release-manifest.sh "${VERSION}" \
|
||||
release-assets/codewhale-artifacts-sha256.txt \
|
||||
release-assets/codewhale-artifacts-sha256.txt.sig
|
||||
node scripts/release/assemble-release-assets.js --verify release-assets
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: codewhale-release-assets
|
||||
path: release-assets/*
|
||||
if-no-files-found: error
|
||||
retention-days: ${{ inputs.retention_days }}
|
||||
compression-level: 0
|
||||
overwrite: true
|
||||
|
||||
smoke:
|
||||
timeout-minutes: 15
|
||||
needs: assemble
|
||||
if: ${{ !cancelled() && needs.assemble.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
|
||||
with:
|
||||
name: codewhale-release-assets
|
||||
path: release-assets
|
||||
- name: Verify 34-asset bridge inventory and checksum manifests (single binary)
|
||||
run: node scripts/release/assemble-release-assets.js --verify release-assets
|
||||
- name: Test release inventory contracts
|
||||
run: |
|
||||
node --test scripts/release/assemble-release-assets.test.js
|
||||
node --test npm/codewhale/test/artifacts.test.js npm/codewhale/test/release-assets.test.js
|
||||
- name: Render AUR metadata from candidate Linux archives
|
||||
if: ${{ !contains(inputs.version, '-') }}
|
||||
run: bash packaging/aur/render.sh release-assets "${RUNNER_TEMP}/codewhale-bin"
|
||||
- name: Smoke packed npm wrapper against candidate assets
|
||||
env:
|
||||
CODEWHALE_SMOKE_ASSETS_DIR: ${{ github.workspace }}/release-assets
|
||||
run: node scripts/release/npm-wrapper-smoke.js
|
||||
- name: Record non-public candidate identity
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "### Release artifact candidate"
|
||||
echo ""
|
||||
echo "- Source: \`${{ github.sha }}\`"
|
||||
echo "- Version metadata: \`${{ inputs.version }}\`"
|
||||
echo "- Inventory: 7 targets / 34 files (single binary; 7 legacy alias assets)"
|
||||
echo "- Publication: none (Actions artifact \`codewhale-release-assets\` only)"
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
@@ -1,141 +0,0 @@
|
||||
name: Release candidate
|
||||
|
||||
# Safe pre-publication artifact proof. This workflow never creates a tag or
|
||||
# release and never writes to a registry, container repository, tap, or deploy.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
expected_sha:
|
||||
description: Exact 40-character commit selected by --ref (must match the dispatch SHA)
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: release-candidate-${{ github.sha }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
resolve:
|
||||
name: Resolve exact candidate source
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
sha: ${{ steps.source.outputs.sha }}
|
||||
version: ${{ steps.source.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # stable 2026-07-18
|
||||
with:
|
||||
toolchain: 1.100.0
|
||||
- name: Match dispatch to the requested commit
|
||||
id: source
|
||||
shell: bash
|
||||
env:
|
||||
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${#EXPECTED_SHA}" -ne 40 || "${EXPECTED_SHA}" =~ [^0-9a-fA-F] ]]; then
|
||||
echo "::error::expected_sha must be a full 40-character commit SHA." >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(git rev-parse HEAD)"
|
||||
expected_normalized="$(printf '%s' "${EXPECTED_SHA}" | tr '[:upper:]' '[:lower:]')"
|
||||
if [[ "${actual}" != "${expected_normalized}" ]]; then
|
||||
echo "::error::Dispatch resolved to ${actual}, not requested ${EXPECTED_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
workspace_version="$(grep -E '^version = "' Cargo.toml | head -n1 | sed -E 's/^version = "([^"]+)".*/\1/')"
|
||||
npm_version="$(node -p "require('./npm/codewhale/package.json').version")"
|
||||
binary_version="$(node -p "require('./npm/codewhale/package.json').codewhaleBinaryVersion")"
|
||||
if [[ "${workspace_version}" != "${npm_version}" ]]; then
|
||||
echo "::error::Candidate version drift: workspace=${workspace_version}, npm=${npm_version}, binary=${binary_version}." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${workspace_version}" != "${binary_version}" ]]; then
|
||||
echo "::error::Candidate version drift: workspace=${workspace_version}, npm=${npm_version}, binary=${binary_version}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "sha=${actual}" >> "${GITHUB_OUTPUT}"
|
||||
echo "version=${workspace_version}" >> "${GITHUB_OUTPUT}"
|
||||
- name: Check version and OHOS release contracts
|
||||
# --require-dated-release, as auto-tag.yml and release.yml run it:
|
||||
# release.yml only accepts an RC receipt for the exact tag SHA, so an
|
||||
# RC on an undated "Unreleased candidate" changelog could never
|
||||
# authorize a release. Fail here, before the long parity and artifact
|
||||
# builds, instead of at tag time.
|
||||
run: |
|
||||
./scripts/release/check-versions.sh --require-dated-release
|
||||
./scripts/release/check-ohos-deps.sh
|
||||
- name: Reconfirm clean source snapshot
|
||||
run: git diff --exit-code
|
||||
|
||||
web:
|
||||
name: Verify exact candidate web surface
|
||||
timeout-minutes: 15
|
||||
needs: resolve
|
||||
if: ${{ !cancelled() && needs.resolve.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
steps:
|
||||
# resolve already proved expected_sha equals GITHUB_SHA. Do not
|
||||
# interpolate that SHA into checkout or the npm cache key.
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
# `check:facts` derives each model's `addedAt` from the commit date
|
||||
# on which its id first appeared in the model declaration paths
|
||||
# (web/scripts/facts-lib.mjs). A shallow checkout collapses every
|
||||
# date to the tip commit and the committed facts always read as
|
||||
# stale; web.yml and ci.yml pin depth 0 for the same reason.
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: web/package-lock.json
|
||||
- name: Install web dependencies
|
||||
run: npm ci
|
||||
- name: Run web tests
|
||||
run: npm test
|
||||
- name: Check exact candidate web surface
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: npm run check
|
||||
|
||||
parity:
|
||||
# The same gate release.yml runs before publish. release.yml refuses a tag
|
||||
# unless an RC run for that exact SHA has this job green
|
||||
# (scripts/release/require-rc-receipt.sh matches the "Parity" name).
|
||||
name: Parity
|
||||
needs: resolve
|
||||
if: ${{ !cancelled() && needs.resolve.result == 'success' }}
|
||||
uses: ./.github/workflows/release-parity.yml
|
||||
|
||||
artifacts:
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
needs: [resolve, web]
|
||||
if: ${{ !cancelled() && needs.resolve.result == 'success' && needs.web.result == 'success' }}
|
||||
uses: ./.github/workflows/release-artifacts.yml
|
||||
with:
|
||||
source_sha: ${{ needs.resolve.outputs.sha }}
|
||||
version: ${{ needs.resolve.outputs.version }}
|
||||
compiled_host_delivery: ${{ vars.CODEWHALE_COMPILED_HOST_DELIVERY == '1' }}
|
||||
compiled_host_run_id: ${{ vars.CODEWHALE_COMPILED_HOST_QUALIFICATION_RUN_ID || '' }}
|
||||
compiled_host_targets: ${{ vars.CODEWHALE_COMPILED_HOST_TARGETS || 'linux-x64,macos-arm64,windows-x64' }}
|
||||
retention_days: 7
|
||||
secrets:
|
||||
CODEWHALE_RELEASE_SIGNING_KEY: ${{ secrets.CODEWHALE_RELEASE_SIGNING_KEY }}
|
||||
@@ -1,133 +0,0 @@
|
||||
name: Release parity
|
||||
|
||||
# The one parity gate. release-candidate.yml and release.yml both call this,
|
||||
# so the SHA an RC validates has passed exactly the gate that publish runs.
|
||||
# Before this was shared, the RC skipped parity: RC 35707500620 was green on
|
||||
# 3e8bf29946, Release 35728585975 then failed parity on that SHA, and v0.10.0
|
||||
# was re-pointed to 1be1a703b, which docs/RELEASE_RUNBOOK.md forbids.
|
||||
#
|
||||
# Checks out the caller's GITHUB_SHA. Callers must verify that SHA first.
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: -Dwarnings
|
||||
|
||||
jobs:
|
||||
parity:
|
||||
name: Workspace parity
|
||||
# Cold check, Clippy, executable and test builds exceeded the old 45m
|
||||
# limit before the suite started. Retain the full CI test-job budget.
|
||||
timeout-minutes: 210
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Every caller's resolve job already proved GITHUB_SHA equals the
|
||||
# candidate or tag commit. Do not interpolate a SHA into checkout or
|
||||
# cache keys —
|
||||
# CodeQL treats a *sha* ref as an untrusted checkout on workflow_dispatch
|
||||
# (default-branch cache write).
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master 2026-07-18
|
||||
with:
|
||||
toolchain: 1.100.0
|
||||
components: clippy, rustfmt
|
||||
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
id: sccache
|
||||
continue-on-error: true
|
||||
- name: Enable sccache
|
||||
if: steps.sccache.outcome == 'success'
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "SCCACHE_GHA_ENABLED=true"
|
||||
echo "RUSTC_WRAPPER=sccache"
|
||||
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1"
|
||||
} >> "${GITHUB_ENV}"
|
||||
- name: Install Linux system dependencies
|
||||
run: |
|
||||
for i in 1 2 3 4 5; do
|
||||
sudo apt-get update && break
|
||||
echo "apt-get update failed (attempt $i); retrying in 15s"
|
||||
sleep 15
|
||||
done
|
||||
sudo apt-get install -y libdbus-1-dev pkg-config bubblewrap apparmor-profiles
|
||||
sh scripts/prepare-linux-test-sandbox.sh
|
||||
# Restore after the trusted lockfile is on disk. Key is OS + arch +
|
||||
# explicit stable toolchain + rust-cache's Cargo.lock / rust-toolchain
|
||||
# hash. Never interpolate github.event, github.ref, github.sha, or inputs.
|
||||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
with:
|
||||
cache-bin: false
|
||||
prefix-key: v1-${{ runner.os }}-${{ runner.arch }}-stable
|
||||
- uses: taiki-e/install-action@e88e69ecdb9658bd172693dcdc2c84e7f0ab6a11 # nextest
|
||||
with:
|
||||
tool: nextest
|
||||
- name: Format check
|
||||
run: cargo fmt --all -- --check
|
||||
- name: Compile check
|
||||
run: cargo check --workspace --all-targets --locked
|
||||
- name: OHOS dependency graph
|
||||
run: ./scripts/release/check-ohos-deps.sh
|
||||
- name: Clippy
|
||||
# Same lint set as ci.yml's "Run clippy": collapsible_if and
|
||||
# assertions_on_constants were removed there on purpose, so allowing
|
||||
# them here made the release gate weaker than the merge gate.
|
||||
run: |
|
||||
cargo clippy --workspace --all-targets --all-features --locked -- \
|
||||
-D warnings \
|
||||
-A clippy::uninlined_format_args \
|
||||
-A clippy::too_many_arguments \
|
||||
-A clippy::unnecessary_map_or
|
||||
- name: Build headroom
|
||||
# Same fixed ephemeral-runner provisioning as CI Safety, Lint, and Test.
|
||||
# Resource headroom is preventive; a shutdown alone does not prove OOM.
|
||||
shell: bash
|
||||
run: bash scripts/prepare-ubuntu-build-headroom.sh
|
||||
- name: Build canonical executable for acceptance tests
|
||||
run: cargo build -p codewhale-cli --bin codewhale --all-features --locked
|
||||
- name: Workspace tests
|
||||
# Same test binaries as `cargo test`, run by cargo-nextest: one process
|
||||
# per test. This gate used libtest, where every test shares one process,
|
||||
# so a test that mutates process-global state leaks into its neighbours.
|
||||
# It failed on five such tests — deterministically, and on a different
|
||||
# set on different machines — while CI's nextest lanes were green on the
|
||||
# same source, and every one of them passes in isolation. Match the lane
|
||||
# that gates every merge; doctests are the next step, because nextest
|
||||
# does not run them.
|
||||
# The runner has been shut down during this step's build on several
|
||||
# candidates (exit 143, no test result). The same headroom trace as
|
||||
# ci.yml's test step records whether memory or disk ran out, starting
|
||||
# with what the earlier check and clippy steps left behind.
|
||||
shell: bash
|
||||
run: |
|
||||
echo "[headroom] before tests: $(free -m | awk '/^Mem:/{print "mem used " $3 "/" $2 " MiB"} /^Swap:/{print "swap used " $3 "/" $2 " MiB"}' | paste -sd ' ') disk free $(df -h / | awk 'NR==2{print $4}') target $(du -sh target 2>/dev/null | cut -f1)"
|
||||
( while sleep 30; do
|
||||
echo "[headroom] $(free -m | awk '/^Mem:/{print "mem used " $3 "/" $2 " MiB"} /^Swap:/{print "swap used " $3 "/" $2 " MiB"}' | paste -sd ' ') disk free $(df -h / | awk 'NR==2{print $4}')"
|
||||
done ) &
|
||||
monitor=$!
|
||||
status=0
|
||||
sh scripts/with-hermetic-test-home.sh cargo nextest run --workspace --all-features --locked --profile ci || status=$?
|
||||
kill "$monitor" 2>/dev/null || true
|
||||
exit "$status"
|
||||
env:
|
||||
CARGO_BUILD_JOBS: '1'
|
||||
# Match the CI test lane: test threads get the same stack the product
|
||||
# gives itself (main.rs CODEWHALE_MAIN_STACK_BYTES). See the note in
|
||||
# ci.yml's "Run tests" step. Without it this gate runs the deep
|
||||
# engine/runtime futures on a stack that never ships.
|
||||
RUST_MIN_STACK: '16777216'
|
||||
- name: Workspace doctests
|
||||
run: sh scripts/with-hermetic-test-home.sh cargo test --workspace --all-features --locked --doc
|
||||
env:
|
||||
RUST_MIN_STACK: '16777216'
|
||||
- name: Protocol schema parity
|
||||
run: sh scripts/with-hermetic-test-home.sh cargo test -p codewhale-protocol --test parity_protocol --locked
|
||||
- name: State persistence parity
|
||||
run: sh scripts/with-hermetic-test-home.sh cargo test -p codewhale-state --test parity_state --locked
|
||||
- name: Lockfile drift guard
|
||||
run: git diff --exit-code -- Cargo.lock
|
||||
@@ -1,216 +0,0 @@
|
||||
name: Release Republish Channels
|
||||
|
||||
# Recovery for derived distribution channels — the container image and the
|
||||
# Homebrew tap — when a released tag has them missing or stale.
|
||||
#
|
||||
# This is a separate workflow, dispatched from the DEFAULT BRANCH, on purpose.
|
||||
# `workflow_dispatch` reads its input schema from the workflow file at the ref
|
||||
# being dispatched, and `release.yml` requires being dispatched from the tag
|
||||
# itself. Any recovery option added to `release.yml` is therefore unusable on
|
||||
# tags that predate it — which is every tag that could ever need recovery.
|
||||
# Taking the tag as an input sidesteps that entirely.
|
||||
#
|
||||
# It never writes GitHub Release bytes. `release.yml` owns those, and they stay
|
||||
# immutable; this only (re)publishes channels derived from an existing release.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Released version to republish, without v (e.g. 0.9.1). The tag must already exist and have a published GitHub Release.'
|
||||
required: true
|
||||
type: string
|
||||
channels:
|
||||
description: 'Which derived channels to republish.'
|
||||
required: true
|
||||
default: 'docker+homebrew'
|
||||
type: choice
|
||||
options:
|
||||
- docker+homebrew
|
||||
- docker
|
||||
- homebrew
|
||||
|
||||
concurrency:
|
||||
group: release-republish-${{ inputs.version }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
resolve:
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tag: ${{ steps.release.outputs.tag }}
|
||||
sha: ${{ steps.release.outputs.sha }}
|
||||
version: ${{ steps.release.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Resolve released tag
|
||||
id: release
|
||||
shell: bash
|
||||
env:
|
||||
INPUT_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if ! [[ "${INPUT_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::error::Version '${INPUT_VERSION}' must use X.Y.Z." >&2
|
||||
exit 1
|
||||
fi
|
||||
tag="v${INPUT_VERSION}"
|
||||
|
||||
if ! git rev-parse --verify "refs/tags/${tag}^{commit}" >/dev/null 2>&1; then
|
||||
echo "::error::Tag ${tag} does not exist." >&2
|
||||
exit 1
|
||||
fi
|
||||
sha="$(git rev-parse "refs/tags/${tag}^{commit}")"
|
||||
|
||||
{
|
||||
echo "tag=${tag}"
|
||||
echo "sha=${sha}"
|
||||
echo "version=${INPUT_VERSION}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
echo "Resolved ${tag} -> ${sha}"
|
||||
- name: Require a complete published release
|
||||
# The inverse of release.yml's immutability guard. That one refuses to
|
||||
# run when assets exist; this one refuses to run unless the published
|
||||
# release carries exactly the assets its own checksum manifest lists,
|
||||
# each fully uploaded, because a channel derived from a partial set
|
||||
# would advertise bytes that are not there. The tag may predate the
|
||||
# current inventory, so its manifest, not today's list, is the check.
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ steps.release.outputs.tag }}
|
||||
run: node scripts/release/verify-release-inventory.js --manifest "${GITHUB_REPOSITORY}" "${TAG}"
|
||||
- name: Verify the remote tag still points at this commit
|
||||
env:
|
||||
EXPECTED_SHA: ${{ steps.release.outputs.sha }}
|
||||
TAG: ${{ steps.release.outputs.tag }}
|
||||
run: |
|
||||
./scripts/release/verify-remote-tag.sh \
|
||||
"https://github.com/${GITHUB_REPOSITORY}.git" \
|
||||
"${TAG}" \
|
||||
"${EXPECTED_SHA}"
|
||||
|
||||
docker:
|
||||
timeout-minutes: 30
|
||||
needs: resolve
|
||||
if: ${{ contains(inputs.channels, 'docker') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout release source
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
path: source
|
||||
- name: Checkout release infrastructure
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
path: infra
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Normalize image name
|
||||
id: image
|
||||
shell: bash
|
||||
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
|
||||
- name: Revalidate release tag before container publish
|
||||
env:
|
||||
EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
|
||||
TAG: ${{ needs.resolve.outputs.tag }}
|
||||
run: |
|
||||
./infra/scripts/release/verify-remote-tag.sh \
|
||||
"https://github.com/${GITHUB_REPOSITORY}.git" \
|
||||
"${TAG}" \
|
||||
"${EXPECTED_SHA}"
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
|
||||
env:
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
with:
|
||||
context: source
|
||||
file: infra/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
build-args: |
|
||||
CODEWHALE_BUILD_SHA=${{ needs.resolve.outputs.sha }}
|
||||
tags: |
|
||||
${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.tag }}
|
||||
${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.version }}
|
||||
${{ steps.image.outputs.name }}:latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Smoke published container entrypoints
|
||||
shell: bash
|
||||
env:
|
||||
IMAGE: ${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker pull "${IMAGE}"
|
||||
docker run --rm --entrypoint codewhale "${IMAGE}" --version
|
||||
docker run --rm --entrypoint codew "${IMAGE}" --version
|
||||
|
||||
homebrew:
|
||||
timeout-minutes: 20
|
||||
needs: resolve
|
||||
if: ${{ contains(inputs.channels, 'homebrew') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Check Homebrew tap token
|
||||
id: homebrew-token
|
||||
env:
|
||||
TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
|
||||
run: |
|
||||
if [[ -z "${TOKEN:-}" ]]; then
|
||||
echo "::error::No Homebrew tap token configured; cannot republish the tap." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Recovery logic must come from the current protected default branch.
|
||||
# The released bytes remain pinned by the tag and checksum manifest;
|
||||
# checking out the old tag here would also restore the bug being repaired.
|
||||
- name: Checkout release infrastructure
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
- name: Download checksum manifest
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release download "${{ needs.resolve.outputs.tag }}" \
|
||||
--repo "${{ github.repository }}" \
|
||||
--pattern 'codewhale-artifacts-sha256.txt' \
|
||||
--dir /tmp
|
||||
- name: Revalidate release tag before Homebrew tap write
|
||||
env:
|
||||
EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
|
||||
TAG: ${{ needs.resolve.outputs.tag }}
|
||||
run: |
|
||||
./scripts/release/verify-remote-tag.sh \
|
||||
"https://github.com/${GITHUB_REPOSITORY}.git" \
|
||||
"${TAG}" \
|
||||
"${EXPECTED_SHA}"
|
||||
- name: Update Homebrew tap
|
||||
env:
|
||||
TAG: ${{ needs.resolve.outputs.tag }}
|
||||
MANIFEST: /tmp/codewhale-artifacts-sha256.txt
|
||||
TAP_REPO: Hmbown/homebrew-deepseek-tui
|
||||
TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
|
||||
run: bash .github/scripts/update-homebrew-tap.sh
|
||||
@@ -1,676 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release version without v; dispatch from the matching existing vX.Y.Z or vX.Y.Z-prerelease tag ref'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: -Dwarnings
|
||||
|
||||
jobs:
|
||||
resolve:
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
# Read release-candidate.yml runs for the RC receipt check below.
|
||||
actions: read
|
||||
outputs:
|
||||
tag: ${{ steps.release.outputs.tag }}
|
||||
sha: ${{ steps.release.outputs.sha }}
|
||||
version: ${{ steps.release.outputs.version }}
|
||||
prerelease: ${{ steps.release.outputs.prerelease }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Resolve release source
|
||||
id: release
|
||||
shell: bash
|
||||
env:
|
||||
INPUT_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
|
||||
if ! [[ "${INPUT_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
|
||||
echo "::error::Release version '${INPUT_VERSION}' must use X.Y.Z or X.Y.Z-prerelease." >&2
|
||||
exit 1
|
||||
fi
|
||||
tag="v${INPUT_VERSION}"
|
||||
if [[ "${GITHUB_REF}" != "refs/tags/${tag}" ]]; then
|
||||
echo "::error::Dispatch release.yml from --ref ${tag}, not ${GITHUB_REF}." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
tag="${GITHUB_REF_NAME}"
|
||||
fi
|
||||
|
||||
if ! [[ "${tag}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
|
||||
echo "::error::Release tag '${tag}' must use vX.Y.Z or vX.Y.Z-prerelease." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! git rev-parse --verify "refs/tags/${tag}^{commit}" >/dev/null 2>&1; then
|
||||
echo "::error::Release tag ${tag} does not exist. Create it from the frozen main commit before dispatching." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sha="$(git rev-parse "refs/tags/${tag}^{commit}")"
|
||||
event_sha="$(git rev-parse "${GITHUB_SHA}^{commit}")"
|
||||
if [[ "${event_sha}" != "${sha}" ]]; then
|
||||
echo "::error::Trigger SHA ${event_sha} does not match ${tag} at ${sha}; the tag moved after this run was created." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version="${tag#v}"
|
||||
{
|
||||
echo "tag=${tag}"
|
||||
echo "sha=${sha}"
|
||||
echo "version=${version}"
|
||||
if [[ "${version}" == *-* ]]; then
|
||||
echo "prerelease=true"
|
||||
else
|
||||
echo "prerelease=false"
|
||||
fi
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
- name: Validate tagged release metadata
|
||||
shell: bash
|
||||
env:
|
||||
SHA: ${{ steps.release.outputs.sha }}
|
||||
TAG: ${{ steps.release.outputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git checkout --detach "${SHA}"
|
||||
expected="${TAG#v}"
|
||||
workspace_version="$(grep -E '^version = "' Cargo.toml | head -n1 | sed -E 's/^version = "([^"]+)".*/\1/')"
|
||||
npm_version="$(node -p "require('./npm/codewhale/package.json').version")"
|
||||
binary_version="$(node -p "require('./npm/codewhale/package.json').codewhaleBinaryVersion")"
|
||||
sdk_version="$(node -p "require('./npm/runtime-sdk/package.json').version")"
|
||||
vscode_version="$(node -p "require('./extensions/vscode/package.json').version")"
|
||||
for pair in \
|
||||
"workspace:${workspace_version}" \
|
||||
"npm:${npm_version}" \
|
||||
"npm binary:${binary_version}" \
|
||||
"runtime-sdk:${sdk_version}" \
|
||||
"vscode:${vscode_version}"; do
|
||||
label="${pair%%:*}"
|
||||
actual="${pair#*:}"
|
||||
if [[ "${actual}" != "${expected}" ]]; then
|
||||
echo "::error::${label} version ${actual} does not match tag ${TAG}." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
./scripts/release/check-versions.sh --require-dated-release
|
||||
- name: Require release source on main
|
||||
run: ./scripts/release/ensure-release-on-main.sh "${{ steps.release.outputs.sha }}"
|
||||
- name: Require a green release-candidate receipt for this exact SHA
|
||||
# Fail fast, before the long parity and artifact builds: the tag must
|
||||
# point at a commit a release-candidate run already validated,
|
||||
# Parity included. A missing receipt means run the RC on this SHA,
|
||||
# never move the tag (v0.10.0 was re-pointed after the RC skipped
|
||||
# parity).
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
SHA: ${{ steps.release.outputs.sha }}
|
||||
run: ./scripts/release/require-rc-receipt.sh "${GITHUB_REPOSITORY}" "${SHA}"
|
||||
- name: Refuse an existing public asset set
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ steps.release.outputs.tag }}
|
||||
run: node scripts/release/ensure-release-assets-absent.js "${GITHUB_REPOSITORY}" "${TAG}"
|
||||
|
||||
parity:
|
||||
name: Parity
|
||||
needs: resolve
|
||||
uses: ./.github/workflows/release-parity.yml
|
||||
|
||||
artifacts:
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
needs: [parity, resolve]
|
||||
if: ${{ !cancelled() && needs.resolve.result == 'success' && needs.parity.result == 'success' }}
|
||||
uses: ./.github/workflows/release-artifacts.yml
|
||||
with:
|
||||
source_sha: ${{ needs.resolve.outputs.sha }}
|
||||
version: ${{ needs.resolve.outputs.version }}
|
||||
compiled_host_delivery: ${{ vars.CODEWHALE_COMPILED_HOST_DELIVERY == '1' }}
|
||||
compiled_host_run_id: ${{ vars.CODEWHALE_COMPILED_HOST_QUALIFICATION_RUN_ID || '' }}
|
||||
compiled_host_targets: ${{ vars.CODEWHALE_COMPILED_HOST_TARGETS || 'linux-x64,macos-arm64,windows-x64' }}
|
||||
retention_days: 14
|
||||
secrets:
|
||||
CODEWHALE_RELEASE_SIGNING_KEY: ${{ secrets.CODEWHALE_RELEASE_SIGNING_KEY }}
|
||||
|
||||
docker-build:
|
||||
needs: [artifacts, resolve]
|
||||
if: ${{ !cancelled() && needs.artifacts.result == 'success' && needs.resolve.outputs.prerelease != 'true' }}
|
||||
name: Docker ${{ matrix.platform }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-latest
|
||||
platform: linux/amd64
|
||||
architecture: amd64
|
||||
cli_artifact: codewhale-linux-x64
|
||||
shim_artifact: codew-linux-x64
|
||||
- runner: ubuntu-24.04-arm
|
||||
platform: linux/arm64
|
||||
architecture: arm64
|
||||
cli_artifact: codewhale-linux-arm64
|
||||
shim_artifact: codew-linux-arm64
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout release infrastructure
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
path: infra
|
||||
- name: Download Codewhale release binary
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
|
||||
with:
|
||||
name: ${{ matrix.cli_artifact }}
|
||||
path: docker-context/bin
|
||||
- name: Download codew release alias
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
|
||||
with:
|
||||
name: ${{ matrix.shim_artifact }}
|
||||
path: docker-context/bin
|
||||
- name: Verify native release bytes
|
||||
shell: bash
|
||||
env:
|
||||
CLI_ARTIFACT: ${{ matrix.cli_artifact }}
|
||||
SHIM_ARTIFACT: ${{ matrix.shim_artifact }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mv -- "docker-context/bin/${CLI_ARTIFACT}" docker-context/bin/codewhale
|
||||
mv -- "docker-context/bin/${SHIM_ARTIFACT}" docker-context/bin/codew
|
||||
chmod 0755 docker-context/bin/codewhale docker-context/bin/codew
|
||||
cmp docker-context/bin/codewhale docker-context/bin/codew
|
||||
docker-context/bin/codewhale --version
|
||||
docker-context/bin/codew --version
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Normalize image name
|
||||
id: image
|
||||
shell: bash
|
||||
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
|
||||
- name: Extract image labels
|
||||
id: meta
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
||||
with:
|
||||
images: |
|
||||
${{ steps.image.outputs.name }}
|
||||
tags: |
|
||||
type=raw,value=${{ needs.resolve.outputs.version }}
|
||||
- name: Revalidate release tag before container upload
|
||||
env:
|
||||
EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
|
||||
TAG: ${{ needs.resolve.outputs.tag }}
|
||||
run: |
|
||||
./infra/scripts/release/verify-remote-tag.sh \
|
||||
"https://github.com/${GITHUB_REPOSITORY}.git" \
|
||||
"${TAG}" \
|
||||
"${EXPECTED_SHA}"
|
||||
- name: Assemble and push native image by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
|
||||
env:
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
with:
|
||||
context: docker-context
|
||||
file: infra/packaging/docker/Dockerfile.release
|
||||
platforms: ${{ matrix.platform }}
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,name=${{ steps.image.outputs.name }},push-by-digest=true,name-canonical=true,push=true
|
||||
- name: Smoke native image digest
|
||||
shell: bash
|
||||
env:
|
||||
IMAGE: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker pull "${IMAGE}"
|
||||
docker run --rm --entrypoint codewhale "${IMAGE}" --version
|
||||
docker run --rm --entrypoint codew "${IMAGE}" --version
|
||||
- name: Export image digest
|
||||
shell: bash
|
||||
env:
|
||||
DIGEST: ${{ steps.build.outputs.digest }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! [[ "${DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
|
||||
echo "Unexpected image digest: ${DIGEST}" >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p digests
|
||||
touch "digests/${DIGEST#sha256:}"
|
||||
- name: Upload image digest
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: docker-digest-${{ matrix.architecture }}
|
||||
path: digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
overwrite: true
|
||||
|
||||
# Derived channel: the tagged/latest manifest is published only after the
|
||||
# canonical GitHub Release exists. docker-build pushes by digest (untagged)
|
||||
# and smokes each image before the release, so a broken image still stops
|
||||
# the release, but no public tag can point at a version whose GitHub
|
||||
# Release was never published.
|
||||
docker:
|
||||
timeout-minutes: 30
|
||||
needs: [docker-build, release, resolve]
|
||||
if: ${{ !cancelled() && needs.docker-build.result == 'success' && needs.release.result == 'success' && needs.resolve.outputs.prerelease != 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout release infrastructure
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
path: infra
|
||||
- name: Download native image digests
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
|
||||
with:
|
||||
path: digests
|
||||
pattern: docker-digest-*
|
||||
merge-multiple: true
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Normalize image name
|
||||
id: image
|
||||
shell: bash
|
||||
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
||||
with:
|
||||
images: |
|
||||
${{ steps.image.outputs.name }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern=v{{major}}
|
||||
type=ref,event=tag
|
||||
type=semver,pattern={{version}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
|
||||
type=semver,pattern={{major}}.{{minor}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
|
||||
type=semver,pattern=v{{major}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
|
||||
type=raw,value=${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
|
||||
type=raw,value=v${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
|
||||
type=raw,value=latest
|
||||
- name: Revalidate release tag before container publish
|
||||
env:
|
||||
EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
|
||||
TAG: ${{ needs.resolve.outputs.tag }}
|
||||
run: |
|
||||
./infra/scripts/release/verify-remote-tag.sh \
|
||||
"https://github.com/${GITHUB_REPOSITORY}.git" \
|
||||
"${TAG}" \
|
||||
"${EXPECTED_SHA}"
|
||||
- name: Publish multi-architecture manifest
|
||||
shell: bash
|
||||
env:
|
||||
IMAGE: ${{ steps.image.outputs.name }}
|
||||
TAGS: ${{ steps.meta.outputs.tags }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mapfile -t digest_files < <(find digests -maxdepth 1 -type f -printf '%f\n' | sort)
|
||||
if [[ "${#digest_files[@]}" -ne 2 ]]; then
|
||||
echo "Expected exactly two native image digests; found ${#digest_files[@]}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sources=()
|
||||
for digest in "${digest_files[@]}"; do
|
||||
if ! [[ "${digest}" =~ ^[0-9a-f]{64}$ ]]; then
|
||||
echo "Unexpected image digest file: ${digest}" >&2
|
||||
exit 1
|
||||
fi
|
||||
sources+=("${IMAGE}@sha256:${digest}")
|
||||
done
|
||||
|
||||
tag_args=()
|
||||
while IFS= read -r tag; do
|
||||
[[ -n "${tag}" ]] && tag_args+=(--tag "${tag}")
|
||||
done <<< "${TAGS}"
|
||||
if [[ "${#tag_args[@]}" -eq 0 ]]; then
|
||||
echo "No container tags were generated." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker buildx imagetools create "${tag_args[@]}" "${sources[@]}"
|
||||
- name: Verify and smoke published container
|
||||
shell: bash
|
||||
env:
|
||||
IMAGE: ${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker buildx imagetools inspect "${IMAGE}"
|
||||
raw_manifest="$(docker buildx imagetools inspect --raw "${IMAGE}")"
|
||||
jq -e \
|
||||
'[.manifests[] | select(.platform.os == "linux") | "linux/\(.platform.architecture)"] | unique | sort == ["linux/amd64", "linux/arm64"]' \
|
||||
<<< "${raw_manifest}"
|
||||
docker pull "${IMAGE}"
|
||||
docker run --rm --entrypoint codewhale "${IMAGE}" --version
|
||||
docker run --rm --entrypoint codew "${IMAGE}" --version
|
||||
|
||||
release:
|
||||
timeout-minutes: 30
|
||||
needs: [artifacts, docker-build, resolve]
|
||||
if: ${{ !cancelled() && needs.artifacts.result == 'success' && (needs.resolve.outputs.prerelease == 'true' || needs.docker-build.result == 'success') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
path: repo
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
|
||||
with:
|
||||
name: codewhale-release-assets
|
||||
path: artifacts
|
||||
- name: Revalidate exact authoritative asset set
|
||||
run: node repo/scripts/release/assemble-release-assets.js --verify artifacts
|
||||
- name: Generate release body from CHANGELOG
|
||||
shell: bash
|
||||
run: |
|
||||
./repo/scripts/release/generate-release-body.sh \
|
||||
"${{ needs.resolve.outputs.tag }}" repo/CHANGELOG.md > release-body.md
|
||||
- name: Revalidate release tag before GitHub Release write
|
||||
env:
|
||||
EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
|
||||
TAG: ${{ needs.resolve.outputs.tag }}
|
||||
run: |
|
||||
./repo/scripts/release/verify-remote-tag.sh \
|
||||
"https://github.com/${GITHUB_REPOSITORY}.git" \
|
||||
"${TAG}" \
|
||||
"${EXPECTED_SHA}"
|
||||
- name: Reconfirm public asset set is still empty
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ needs.resolve.outputs.tag }}
|
||||
run: node repo/scripts/release/ensure-release-assets-absent.js "${GITHUB_REPOSITORY}" "${TAG}"
|
||||
# Upload into a DRAFT: 34 sequential uploads that die halfway must leave
|
||||
# nothing public (and nothing a rerun's immutability guard refuses).
|
||||
- uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3
|
||||
with:
|
||||
tag_name: ${{ needs.resolve.outputs.tag }}
|
||||
name: Codewhale ${{ needs.resolve.outputs.tag }}
|
||||
files: artifacts/*
|
||||
draft: true
|
||||
prerelease: ${{ needs.resolve.outputs.prerelease == 'true' }}
|
||||
body_path: release-body.md
|
||||
overwrite_files: false
|
||||
fail_on_unmatched_files: true
|
||||
- name: Verify the draft's exact asset set, then publish it at once
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ needs.resolve.outputs.tag }}
|
||||
run: |
|
||||
node repo/scripts/release/verify-release-inventory.js \
|
||||
--draft --asset-dir artifacts --publish \
|
||||
"${GITHUB_REPOSITORY}" "${TAG}"
|
||||
|
||||
# CNB tag pushes start its public binary pipeline, so mirror only after
|
||||
# the canonical release is complete. Calling the reusable workflow here
|
||||
# also works when GITHUB_TOKEN publication suppresses release events.
|
||||
cnb:
|
||||
needs: [release, resolve]
|
||||
if: ${{ !cancelled() && needs.release.result == 'success' && needs.resolve.outputs.prerelease != 'true' }}
|
||||
uses: ./.github/workflows/sync-cnb.yml
|
||||
with:
|
||||
release_tag: ${{ needs.resolve.outputs.tag }}
|
||||
source_sha: ${{ needs.resolve.outputs.sha }}
|
||||
secrets:
|
||||
CNB_GIT_TOKEN: ${{ secrets.CNB_GIT_TOKEN }}
|
||||
|
||||
gitcode:
|
||||
needs: [release, resolve]
|
||||
if: ${{ !cancelled() && needs.release.result == 'success' && vars.GITCODE_MIRROR == 'enabled' && needs.resolve.outputs.prerelease != 'true' }}
|
||||
uses: ./.github/workflows/sync-gitcode.yml
|
||||
with:
|
||||
release_tag: ${{ needs.resolve.outputs.tag }}
|
||||
source_sha: ${{ needs.resolve.outputs.sha }}
|
||||
secrets:
|
||||
GITCODE_TOKEN: ${{ secrets.GITCODE_TOKEN }}
|
||||
|
||||
npm:
|
||||
timeout-minutes: 20
|
||||
needs: [release, resolve]
|
||||
if: ${{ !cancelled() && needs.release.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
registry-url: https://registry.npmjs.org
|
||||
package-manager-cache: false
|
||||
- name: Pin OIDC-capable npm CLI
|
||||
run: npm install --global [email protected]
|
||||
- name: Revalidate release tag before npm publish
|
||||
env:
|
||||
EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
|
||||
TAG: ${{ needs.resolve.outputs.tag }}
|
||||
run: |
|
||||
./scripts/release/verify-remote-tag.sh \
|
||||
"https://github.com/${GITHUB_REPOSITORY}.git" \
|
||||
"${TAG}" \
|
||||
"${EXPECTED_SHA}"
|
||||
- name: Revalidate public release assets
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: ./scripts/release/verify-release-assets.sh "${{ needs.resolve.outputs.version }}"
|
||||
- name: Test npm wrapper
|
||||
working-directory: npm/codewhale
|
||||
run: npm test
|
||||
- name: Publish npm wrapper with trusted publishing
|
||||
working-directory: npm/codewhale
|
||||
env:
|
||||
# npm runs prepublishOnly in this step; that guard revalidates the
|
||||
# public GitHub Release and therefore needs the same read token as
|
||||
# the explicit asset gate above.
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PRERELEASE: ${{ needs.resolve.outputs.prerelease }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${PRERELEASE}" == "true" ]]; then
|
||||
npm publish --access public --tag next
|
||||
else
|
||||
npm publish --access public
|
||||
fi
|
||||
|
||||
homebrew:
|
||||
timeout-minutes: 20
|
||||
needs: [release, resolve]
|
||||
if: ${{ !cancelled() && needs.release.result == 'success' && needs.resolve.outputs.prerelease != 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Check Homebrew tap token
|
||||
# A real release must update the tap. Skipping silently (the old
|
||||
# behaviour) left `brew install codewhale` on the previous version
|
||||
# with a green release run; fail loudly like release-republish.yml.
|
||||
env:
|
||||
TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
|
||||
run: |
|
||||
if [[ -z "${TOKEN:-}" ]]; then
|
||||
echo "::error::No Homebrew tap token configured (HOMEBREW_TAP_PAT or RELEASE_TAG_PAT); cannot update the tap for this release." >&2
|
||||
exit 1
|
||||
fi
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
- name: Download checksum manifest
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release download "${{ needs.resolve.outputs.tag }}" \
|
||||
--repo "${{ github.repository }}" \
|
||||
--pattern 'codewhale-artifacts-sha256.txt' \
|
||||
--dir /tmp
|
||||
- name: Revalidate release tag before Homebrew tap write
|
||||
env:
|
||||
EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
|
||||
TAG: ${{ needs.resolve.outputs.tag }}
|
||||
run: |
|
||||
./scripts/release/verify-remote-tag.sh \
|
||||
"https://github.com/${GITHUB_REPOSITORY}.git" \
|
||||
"${TAG}" \
|
||||
"${EXPECTED_SHA}"
|
||||
- name: Update Homebrew tap
|
||||
env:
|
||||
TAG: ${{ needs.resolve.outputs.tag }}
|
||||
MANIFEST: /tmp/codewhale-artifacts-sha256.txt
|
||||
TAP_REPO: Hmbown/homebrew-deepseek-tui
|
||||
TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
|
||||
run: bash .github/scripts/update-homebrew-tap.sh
|
||||
|
||||
# Every publish used to turn Web Frontend red: `check:latest-release`
|
||||
# compares the checked-in release record with GitHub's latest release, and
|
||||
# nothing wrote the record, so someone hand-committed it after each release.
|
||||
# This job writes the record, proves it against the web gates, and proposes
|
||||
# it to main as a bot PR. It never pushes to the default branch: the ruleset
|
||||
# requires a PR there. While the PR is open (up to 24h after publish, and
|
||||
# only when the record is exactly one release behind), `check:latest-release`
|
||||
# warns instead of failing on every event (see web/scripts/sync-latest-release.mjs).
|
||||
#
|
||||
# Known limit: repo settings stop GITHUB_TOKEN from opening PRs, so the PR is
|
||||
# opened with RELEASE_TAG_PAT. Without it the job pushes the branch and fails
|
||||
# with the compare link, which is still one click instead of a hand commit.
|
||||
sync-release-record:
|
||||
timeout-minutes: 15
|
||||
needs: [release, resolve]
|
||||
if: ${{ !cancelled() && needs.release.result == 'success' && needs.resolve.outputs.prerelease != 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
# derive-facts.mjs dates model ids from git history (web.yml pins 0
|
||||
# for the same reason); a shallow clone rewrites every addedAt.
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
package-manager-cache: false
|
||||
- name: Refresh the release record from the published release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node web/scripts/sync-latest-release.mjs
|
||||
node web/scripts/derive-facts.mjs
|
||||
node web/scripts/derive-release-docs.mjs
|
||||
- name: Prove the record passes the web fact gates
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node web/scripts/sync-latest-release.mjs --check
|
||||
node web/scripts/check-cloud-facts.mjs
|
||||
node web/scripts/check-facts.mjs
|
||||
node web/scripts/check-release-docs.mjs
|
||||
- name: Propose the record to the default branch
|
||||
env:
|
||||
TAG: ${{ needs.resolve.outputs.tag }}
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
PR_TOKEN: ${{ secrets.RELEASE_TAG_PAT }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
paths=(
|
||||
web/data/latest-published-release.json
|
||||
docs/public-surface-facts.json
|
||||
docs/cloud-facts/stable.json
|
||||
web/lib/facts.generated.ts
|
||||
web/data/agent-docs.generated.json
|
||||
web/data/agent-docs.release.json
|
||||
web/data/agent-docs.release-index.json
|
||||
web/data/features.generated.json
|
||||
)
|
||||
if git diff --quiet -- "${paths[@]}"; then
|
||||
echo "Release record already current on ${DEFAULT_BRANCH}; nothing to propose."
|
||||
exit 0
|
||||
fi
|
||||
# GitHub's latest may already be newer than this run's tag; the
|
||||
# record always follows GitHub, so name the branch after what it says.
|
||||
recorded="$(node -p "require('./web/data/latest-published-release.json').tag")"
|
||||
branch="chore/release-record-${recorded}"
|
||||
title="chore(web): record ${recorded} as the published release"
|
||||
|
||||
git config user.name "CodeWhale Bot"
|
||||
git config user.email "[email protected]"
|
||||
git switch --quiet -c "${branch}"
|
||||
git add -- "${paths[@]}"
|
||||
git commit --quiet \
|
||||
-m "${title}" \
|
||||
-m "Written by release.yml sync-release-record after ${TAG} published. Gates run in the job: sync-latest-release --check, check-cloud-facts, check-facts."
|
||||
git show --stat --format='%h %s' HEAD
|
||||
|
||||
if git ls-remote --exit-code --heads origin "${branch}" >/dev/null; then
|
||||
echo "::notice title=Release record already proposed::Branch ${branch} exists; leaving it for review."
|
||||
exit 0
|
||||
fi
|
||||
git push origin "HEAD:refs/heads/${branch}"
|
||||
|
||||
compare="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/compare/${DEFAULT_BRANCH}...${branch}?expand=1"
|
||||
if [[ -n "${PR_TOKEN}" ]] && GH_TOKEN="${PR_TOKEN}" gh pr create \
|
||||
--repo "${GITHUB_REPOSITORY}" --base "${DEFAULT_BRANCH}" --head "${branch}" \
|
||||
--title "${title}" \
|
||||
--body "Written by release.yml \`sync-release-record\` after ${TAG} published. Merging it turns \`check:latest-release\` green on ${DEFAULT_BRANCH}. No-Issue: release automation."; then
|
||||
echo "::notice title=Release record proposed::Opened a PR from ${branch}."
|
||||
exit 0
|
||||
fi
|
||||
echo "::error title=Release record PR not opened::Branch ${branch} is pushed; open and merge it: ${compare}"
|
||||
exit 1
|
||||
@@ -0,0 +1,94 @@
|
||||
name: rust-ci
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "codex-rs/**"
|
||||
- ".github/**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
# For CI, we build in debug (`--profile dev`) rather than release mode so we
|
||||
# get signal faster.
|
||||
|
||||
jobs:
|
||||
# CI that don't need specific targets
|
||||
general:
|
||||
name: Format / etc
|
||||
runs-on: ubuntu-24.04
|
||||
defaults:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: cargo fmt
|
||||
run: cargo fmt -- --config imports_granularity=Item --check
|
||||
|
||||
# CI to validate on different os/targets
|
||||
lint_build_test:
|
||||
name: ${{ matrix.runner }} - ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 30
|
||||
defaults:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Note: While Codex CLI does not support Windows today, we include
|
||||
# Windows in CI to ensure the code at least builds there.
|
||||
include:
|
||||
- runner: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
- runner: macos-14
|
||||
target: x86_64-apple-darwin
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- runner: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
${{ github.workspace }}/codex-rs/target/
|
||||
key: cargo-${{ matrix.runner }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }}
|
||||
name: Install musl build tools
|
||||
run: |
|
||||
sudo apt install -y musl-tools pkg-config
|
||||
|
||||
- name: Initialize failure flag
|
||||
run: echo "FAILED=" >> $GITHUB_ENV
|
||||
|
||||
- name: cargo clippy
|
||||
run: cargo clippy --target ${{ matrix.target }} --all-features -- -D warnings || echo "FAILED=${FAILED:+$FAILED, }cargo clippy" >> $GITHUB_ENV
|
||||
|
||||
- name: cargo test
|
||||
run: cargo test --target ${{ matrix.target }} || echo "FAILED=${FAILED:+$FAILED, }cargo test" >> $GITHUB_ENV
|
||||
|
||||
- name: Fail if any step failed
|
||||
if: env.FAILED != ''
|
||||
run: |
|
||||
echo "See logs above, as the following steps failed:"
|
||||
echo "$FAILED"
|
||||
exit 1
|
||||
@@ -0,0 +1,156 @@
|
||||
# Release workflow for codex-rs.
|
||||
# To release, follow a workflow like:
|
||||
# ```
|
||||
# git tag -a rust-v0.1.0 -m "Release 0.1.0"
|
||||
# git push origin rust-v0.1.0
|
||||
# ```
|
||||
|
||||
name: rust-release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "rust-v.*.*.*"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
TAG_REGEX: '^rust-v\.[0-9]+\.[0-9]+\.[0-9]+$'
|
||||
|
||||
jobs:
|
||||
tag-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Validate tag matches Cargo.toml version
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "::group::Tag validation"
|
||||
|
||||
# 1. Must be a tag and match the regex
|
||||
[[ "${GITHUB_REF_TYPE}" == "tag" ]] \
|
||||
|| { echo "❌ Not a tag push"; exit 1; }
|
||||
[[ "${GITHUB_REF_NAME}" =~ ${TAG_REGEX} ]] \
|
||||
|| { echo "❌ Tag '${GITHUB_REF_NAME}' != ${TAG_REGEX}"; exit 1; }
|
||||
|
||||
# 2. Extract versions
|
||||
tag_ver="${GITHUB_REF_NAME#rust-v.}"
|
||||
cargo_ver="$(grep -m1 '^version' codex-rs/Cargo.toml \
|
||||
| sed -E 's/version *= *"([^"]+)".*/\1/')"
|
||||
|
||||
# 3. Compare
|
||||
[[ "${tag_ver}" == "${cargo_ver}" ]] \
|
||||
|| { echo "❌ Tag ${tag_ver} ≠ Cargo.toml ${cargo_ver}"; exit 1; }
|
||||
|
||||
echo "✅ Tag and Cargo.toml agree (${tag_ver})"
|
||||
echo "::endgroup::"
|
||||
|
||||
build:
|
||||
needs: tag-check
|
||||
name: ${{ matrix.runner }} - ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 30
|
||||
defaults:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
- runner: macos-14
|
||||
target: x86_64-apple-darwin
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-musl
|
||||
- runner: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- runner: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-gnu
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
${{ github.workspace }}/codex-rs/target/
|
||||
key: cargo-release-${{ matrix.runner }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }}
|
||||
name: Install musl build tools
|
||||
run: |
|
||||
sudo apt install -y musl-tools pkg-config
|
||||
|
||||
- name: Cargo build
|
||||
run: cargo build --target ${{ matrix.target }} --release --all-targets --all-features
|
||||
|
||||
- name: Stage artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
dest="dist/${{ matrix.target }}"
|
||||
mkdir -p "$dest"
|
||||
|
||||
cp target/${{ matrix.target }}/release/codex-repl "$dest/codex-repl-${{ matrix.target }}"
|
||||
cp target/${{ matrix.target }}/release/codex-exec "$dest/codex-exec-${{ matrix.target }}"
|
||||
cp target/${{ matrix.target }}/release/codex "$dest/codex-${{ matrix.target }}"
|
||||
|
||||
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-gnu' }}
|
||||
name: Stage Linux-only artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
dest="dist/${{ matrix.target }}"
|
||||
cp target/${{ matrix.target }}/release/codex-linux-sandbox "$dest/codex-linux-sandbox-${{ matrix.target }}"
|
||||
|
||||
- name: Compress artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
zstd -T0 -19 --rm "$dest"/*
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: codex-rs/dist/${{ matrix.target }}/*
|
||||
|
||||
release:
|
||||
needs: build
|
||||
name: release
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
RELEASE_TAG: codex-rs-${{ github.sha }}-${{ github.run_attempt }}-${{ github.ref_name }}
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: dist
|
||||
|
||||
- name: List
|
||||
run: ls -R dist/
|
||||
|
||||
- uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ env.RELEASE_TAG }}
|
||||
files: dist/**
|
||||
# TODO(ragona): I'm going to leave these as prerelease/draft for now.
|
||||
# It gives us 1) clarity that these are not yet a stable version, and
|
||||
# 2) allows a human step to review the release before publishing the draft.
|
||||
prerelease: true
|
||||
draft: true
|
||||
|
||||
- uses: facebook/dotslash-publish-release@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag: ${{ env.RELEASE_TAG }}
|
||||
config: .github/dotslash-config.json
|
||||
@@ -1,89 +0,0 @@
|
||||
name: Security audit
|
||||
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
workflow_call: {}
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
self-audit:
|
||||
name: cargo-audit
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: dtolnay/[email protected]
|
||||
- name: Install cargo-audit
|
||||
run: cargo install cargo-audit
|
||||
- name: Run cargo audit
|
||||
run: cargo audit
|
||||
|
||||
npm-audit:
|
||||
name: npm-audit (${{ matrix.name }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Every committed package-lock.json, including build-only tooling.
|
||||
include:
|
||||
- { name: root, directory: . }
|
||||
- { name: web, directory: web }
|
||||
- { name: telemetry-ingest, directory: telemetry-ingest }
|
||||
- { name: extension-host, directory: crates/engine/extension-host }
|
||||
- { name: vscode, directory: extensions/vscode }
|
||||
- { name: feishu-bridge, directory: integrations/feishu-bridge }
|
||||
- { name: telegram-bridge, directory: integrations/telegram-bridge }
|
||||
- { name: wecom-bridge, directory: integrations/wecom-bridge }
|
||||
- { name: pet, directory: pet }
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ matrix.directory }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
node-version: '22'
|
||||
# Audit the complete committed lock, including build dependencies, and
|
||||
# keep the raw report. Runtime compatibility remains owned by surface CI.
|
||||
- name: Audit locked dependencies
|
||||
if: matrix.name != 'web' && matrix.name != 'vscode'
|
||||
run: |
|
||||
set +e
|
||||
npm audit --json > "${{ runner.temp }}/npm-audit.json"
|
||||
status=$?
|
||||
set -e
|
||||
node -e '
|
||||
const r = require(process.argv[1]);
|
||||
if (r.error || !r.metadata?.vulnerabilities) throw new Error("Incomplete dependency audit");
|
||||
console.log(JSON.stringify(r.metadata.vulnerabilities));
|
||||
' "${{ runner.temp }}/npm-audit.json"
|
||||
exit "$status"
|
||||
# The website also verifies its installed, reviewed build dependency
|
||||
# mitigation (web/docs/DEPENDENCY_SECURITY.md); the raw report keeps every
|
||||
# upstream finding.
|
||||
- name: Install and verify website build dependency mitigation
|
||||
if: matrix.name == 'web'
|
||||
run: npm ci --no-audit
|
||||
- name: Audit website with verified build dependency mitigation
|
||||
if: matrix.name == 'web'
|
||||
run: npm run audit:dependencies -- --report "${{ runner.temp }}/npm-audit.json"
|
||||
# The VS Code extension's packager dependency override is recorded in
|
||||
# docs/dependency-maintenance.md; any reported vulnerability fails the job.
|
||||
- name: Audit extension with reviewed packager advisory acceptance
|
||||
if: matrix.name == 'vscode'
|
||||
run: npm run audit:dependencies -- --report "${{ runner.temp }}/npm-audit.json"
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
if: always()
|
||||
with:
|
||||
name: npm-audit-${{ matrix.name }}
|
||||
path: ${{ runner.temp }}/npm-audit.json
|
||||
if-no-files-found: error
|
||||
@@ -1,69 +0,0 @@
|
||||
name: Lock down obvious spam issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
lockdown:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Auto-close spam patterns from new accounts
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const author = issue.user;
|
||||
|
||||
// Only consider brand-new accounts. If the user has been around
|
||||
// long enough to file good-faith issues elsewhere, don't touch.
|
||||
const created = new Date(author.created_at || 0);
|
||||
const ageDays = (Date.now() - created.getTime()) / 86_400_000;
|
||||
if (ageDays > 30) return;
|
||||
|
||||
const blob = `${issue.title || ''}\n${issue.body || ''}`;
|
||||
const patterns = [
|
||||
/\btg\s*@/i,
|
||||
/\btelegram\s+@/i,
|
||||
/\bt\.me\//i,
|
||||
/\bwhatsapp\s+\+/i,
|
||||
/\bseo\s+service/i,
|
||||
/\bguest\s+post/i,
|
||||
/\bbacklink/i,
|
||||
/\bbuy\s+followers/i,
|
||||
];
|
||||
const hit = patterns.find(p => p.test(blob));
|
||||
if (!hit) return;
|
||||
|
||||
// Label and close; no comment (founder, 2026-09-22). The label
|
||||
// description tells a false positive how to get the issue back.
|
||||
try {
|
||||
await github.rest.issues.createLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: 'spam',
|
||||
color: 'b60205',
|
||||
description: 'Auto-closed: spam pattern from a <30-day account. Real report? Reopen with project context',
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status !== 422) throw error; // 422: label already exists
|
||||
}
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
state: 'closed',
|
||||
state_reason: 'not_planned',
|
||||
});
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: ['spam'],
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
name: Close stale issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 5 * * *' # daily, off-peak
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
# Pinned from actions/stale@v10; update deliberately when refreshing the policy.
|
||||
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93
|
||||
with:
|
||||
days-before-stale: 14
|
||||
days-before-close: 7
|
||||
# No stale or close messages (founder, 2026-09-22). An empty message
|
||||
# makes actions/stale label and close without commenting (it checks
|
||||
# `staleIssueMessage.length === 0` at the pinned SHA). The `stale`
|
||||
# label is the signal; any reply removes it before the close.
|
||||
stale-issue-message: ''
|
||||
close-issue-message: ''
|
||||
stale-issue-label: 'stale'
|
||||
only-labels: 'needs-info'
|
||||
exempt-issue-labels: 'pinned,keep-open,release-blocker,security'
|
||||
# Don't touch PRs — `actions/stale` defaults can be aggressive
|
||||
# there. We only want it for `needs-info` issues.
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
operations-per-run: 60
|
||||
@@ -1,121 +0,0 @@
|
||||
name: Sync to CNB
|
||||
|
||||
# Mirror commits and release tags to cnb.cool/codewhale.net/codewhale
|
||||
# so users behind GitHub-blocking networks can fetch the source and tagged
|
||||
# releases from the Tencent-hosted mirror.
|
||||
#
|
||||
# Main pushes mirror to CNB unless the repository variable CNB_MIRROR is
|
||||
# "disabled"; release tags arrive through release.yml; manual dispatch is the
|
||||
# fallback.
|
||||
#
|
||||
# Why the rewrite (v0.8.31):
|
||||
# The previous implementation used the opaque tencentcom/git-sync Docker
|
||||
# action, which discovered every local ref via fetch-depth: 0 and tried
|
||||
# to push them all — including dependabot/* branches that GitHub had
|
||||
# locally. Those follow-on pushes ran without the configured credential
|
||||
# helper in scope and failed with
|
||||
# `fatal: could not read Username for 'https://cnb.cool'`
|
||||
# No concurrency block meant the main-push and tag-push workflow runs
|
||||
# that auto-tag.yml fires within seconds of each other raced. About
|
||||
# half of recent runs failed for those two reasons combined.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch: {}
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
required: true
|
||||
type: string
|
||||
source_sha:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
CNB_GIT_TOKEN:
|
||||
required: true
|
||||
|
||||
# Serialize runs per ref. One global group was wrong: with
|
||||
# cancel-in-progress: false GitHub still keeps only ONE pending run per group
|
||||
# and cancels it when a newer one queues, so a release tag sync waiting behind
|
||||
# a main push was dropped by the next branch push. Keyed by ref, each tag gets
|
||||
# its own group (never superseded), and a pending branch sync is only ever
|
||||
# replaced by a newer push of the same branch, whose force-push supersedes it.
|
||||
# The main and tag pushes from auto-tag.yml write different CNB refs, so
|
||||
# running them concurrently cannot conflict.
|
||||
concurrency:
|
||||
group: cnb-sync-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
if: github.repository_owner == 'codewhale-hq' && (github.event_name != 'push' || vars.CNB_MIRROR != 'disabled')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ inputs.source_sha || github.sha }}
|
||||
|
||||
- name: Push triggering ref to CNB
|
||||
env:
|
||||
CNB_TOKEN: ${{ secrets.CNB_GIT_TOKEN }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ inputs.release_tag }}
|
||||
SOURCE_SHA: ${{ inputs.source_sha || github.sha }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "${CNB_TOKEN:-}" ]; then
|
||||
echo "::error::CNB_GIT_TOKEN secret is not set; cannot push to CNB." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# URL-encode any '%' in the token so basic-auth doesn't break on
|
||||
# special characters. CNB tokens are typically alphanumeric so
|
||||
# this is belt-and-suspenders.
|
||||
ENCODED_TOKEN="$(printf '%s' "${CNB_TOKEN}" | python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read(), safe=""))')"
|
||||
REMOTE_URL="https://cnb:${ENCODED_TOKEN}@cnb.cool/codewhale.net/codewhale.git"
|
||||
# Use a masked alias so the token never appears in log lines.
|
||||
git remote add cnb "${REMOTE_URL}"
|
||||
|
||||
if [[ -n "${RELEASE_TAG}" || "${GITHUB_REF}" == refs/tags/* ]]; then
|
||||
TAG="${RELEASE_TAG:-${GITHUB_REF#refs/tags/}}"
|
||||
# The tag starts CNB's public release pipeline. Require the
|
||||
# canonical release even for a manual recovery dispatch.
|
||||
if ! [[ "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
|
||||
[[ "$(git rev-parse 'HEAD^{commit}')" != "${SOURCE_SHA}" ]]; then
|
||||
echo "::error::CNB release source does not match the frozen tag input." >&2
|
||||
exit 1
|
||||
fi
|
||||
./scripts/release/verify-remote-tag.sh \
|
||||
"https://github.com/${GITHUB_REPOSITORY}.git" "${TAG}" "${SOURCE_SHA}"
|
||||
node scripts/release/verify-release-inventory.js --manifest "${GITHUB_REPOSITORY}" "${TAG}"
|
||||
scripts/release/mirror-push.sh cnb "tag ${TAG}" "refs/tags/${TAG}:refs/tags/${TAG}"
|
||||
elif [[ "${GITHUB_REF}" == refs/heads/main ]]; then
|
||||
# Plain --force. The CNB mirror is one-way by design —
|
||||
# nothing else pushes to it, so there's no contributor work
|
||||
# to protect against. `--force-with-lease` would be safer
|
||||
# in a multi-writer scenario, but in our setup the lease
|
||||
# check requires `refs/remotes/cnb/main` to exist in the
|
||||
# runner's local clone, which it never does (we add `cnb`
|
||||
# as a fresh remote in this step and don't fetch first).
|
||||
# That made the lease check spuriously fail with
|
||||
# `! [rejected] HEAD -> main (stale info)` even when CNB
|
||||
# was actually behind GitHub.
|
||||
scripts/release/mirror-push.sh cnb "main" HEAD:refs/heads/main --force
|
||||
else
|
||||
# First-party fix/rebrand/release branches are first-class CNB
|
||||
# sources for heavy Linux CI, release preflight, and
|
||||
# Lighthouse/Feishu bootstrap.
|
||||
# Mirror the triggering branch exactly so the CNB clone path stays
|
||||
# useful before the branch has merged to main or become a release
|
||||
# tag.
|
||||
BRANCH="${GITHUB_REF#refs/heads/}"
|
||||
scripts/release/mirror-push.sh cnb "branch ${BRANCH}" "HEAD:refs/heads/${BRANCH}" --force
|
||||
fi
|
||||
@@ -1,74 +0,0 @@
|
||||
name: Sync to GitCode
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch: {}
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
required: true
|
||||
type: string
|
||||
source_sha:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
GITCODE_TOKEN:
|
||||
required: true
|
||||
|
||||
concurrency:
|
||||
group: gitcode-sync-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
if: github.repository_owner == 'codewhale-hq' && (github.event_name != 'push' || vars.GITCODE_MIRROR == 'enabled')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ inputs.source_sha || github.sha }}
|
||||
|
||||
- name: Push to GitCode
|
||||
env:
|
||||
GITCODE_TOKEN: ${{ secrets.GITCODE_TOKEN }}
|
||||
GITCODE_USER: ${{ vars.GITCODE_USERNAME || 'HunterBown' }}
|
||||
GITCODE_REPO: ${{ vars.GITCODE_REPOSITORY || 'HunterBown/Codewhale' }}
|
||||
RELEASE_TAG: ${{ inputs.release_tag }}
|
||||
SOURCE_SHA: ${{ inputs.source_sha || github.sha }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "${GITCODE_TOKEN:-}" ]; then
|
||||
echo "::error::GITCODE_TOKEN secret is not set; cannot push to GitCode." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ENCODED_USER="$(printf '%s' "${GITCODE_USER}" | python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read(), safe=""))')"
|
||||
ENCODED_TOKEN="$(printf '%s' "${GITCODE_TOKEN}" | python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read(), safe=""))')"
|
||||
git remote add gitcode "https://${ENCODED_USER}:${ENCODED_TOKEN}@gitcode.com/${GITCODE_REPO}.git"
|
||||
|
||||
if [[ -n "${RELEASE_TAG}" ]]; then
|
||||
if ! [[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
|
||||
[[ "$(git rev-parse 'HEAD^{commit}')" != "${SOURCE_SHA}" ]]; then
|
||||
echo "::error::GitCode release source does not match the frozen tag input." >&2
|
||||
exit 1
|
||||
fi
|
||||
./scripts/release/verify-remote-tag.sh \
|
||||
"https://github.com/${GITHUB_REPOSITORY}.git" "${RELEASE_TAG}" "${SOURCE_SHA}"
|
||||
scripts/release/mirror-push.sh gitcode "tag ${RELEASE_TAG}" "refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}"
|
||||
elif [[ "${GITHUB_REF}" != refs/heads/main ]]; then
|
||||
echo "::error::GitCode mirrors main only; dispatch this workflow from main." >&2
|
||||
exit 1
|
||||
elif [[ "${GITHUB_EVENT_NAME}" == workflow_dispatch ]]; then
|
||||
scripts/release/mirror-push.sh gitcode "main" HEAD:refs/heads/main --force
|
||||
scripts/release/mirror-push.sh gitcode "release tags" "refs/tags/v*:refs/tags/v*"
|
||||
else
|
||||
scripts/release/mirror-push.sh gitcode "main" HEAD:refs/heads/main --force
|
||||
fi
|
||||
@@ -1,41 +0,0 @@
|
||||
name: Telemetry Ingest
|
||||
|
||||
# Final validation checks the ingest Worker against its published telemetry
|
||||
# schema and the client's pinned wire format.
|
||||
#
|
||||
# There is deliberately NO deploy job. Publishing this Worker is the owner's
|
||||
# action, run by hand, and `telemetry_endpoint` stays unset until he has
|
||||
# deployed and verified. See telemetry-ingest/README.md.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Schema weld, IP guard, and types
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
defaults:
|
||||
run:
|
||||
working-directory: telemetry-ingest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/[email protected]
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
cache-dependency-path: telemetry-ingest/package-lock.json
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
- name: TypeScript type check
|
||||
run: npm run typecheck
|
||||
- name: Bundle check
|
||||
# `--dry-run` builds the Worker and resolves its bindings without
|
||||
# touching a Cloudflare account. It is the step that would have caught
|
||||
# the entrypoint exporting a non-callable named export.
|
||||
run: npm run check
|
||||
@@ -1,126 +0,0 @@
|
||||
name: Issue and PR triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, reopened]
|
||||
pull_request_target:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Auto-label by title, body and changed paths
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const issue = context.payload.issue || pr;
|
||||
const title = (issue.title || '').toLowerCase();
|
||||
const body = (issue.body || '').toLowerCase();
|
||||
const text = `${title}\n${body}`;
|
||||
const labels = new Set();
|
||||
|
||||
if (pr) {
|
||||
const files = await github.paginate(github.rest.pulls.listFiles, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pr.number,
|
||||
per_page: 100,
|
||||
});
|
||||
const pathRules = [
|
||||
[/^crates\/(tui|tui-kit|palette)\//, 'area:tui'],
|
||||
[/^crates\/(tools\/|engine\/src\/tools\/)/, 'area:tools'],
|
||||
[/^crates\/(engine|core|runtime|agent|agent-contract|protocol)\/(?!src\/tools\/)/, 'area:core'],
|
||||
[/^crates\/mcp\//, 'area:mcp'],
|
||||
[/^crates\/(state|snapshot|memory)\//, 'area:state'],
|
||||
[/^crates\/(sandbox|execpolicy)\//, 'area:sandbox'],
|
||||
[/^crates\/(plugins|skills|skill-install|hooks)\//, 'area:plugins'],
|
||||
[/^(crates\/(app-server|app-server-protocol)|npm\/runtime-sdk)\//, 'area:runtime-api'],
|
||||
[/^crates\/models\//, 'area:providers'],
|
||||
[/^(crates\/localization|docs\/zh_hans)\//, 'area:localization'],
|
||||
[/^(npm\/(codewhale|deepseek-tui)|scripts\/release)\//, 'area:install'],
|
||||
[/^(\.github|scripts\/ci)\//, 'area:ci'],
|
||||
[/^apps\/desktop\//, 'area:desktop'],
|
||||
[/^(apps\/web|web)\//, 'area:web'],
|
||||
[/^apps\/mobile\//, 'area:mobile'],
|
||||
];
|
||||
const areas = new Set();
|
||||
let docsOnly = files.length > 0;
|
||||
for (const f of files) {
|
||||
if (!/^(docs\/|[^/]+\.md$)/.test(f.filename)) docsOnly = false;
|
||||
for (const [re, name] of pathRules) if (re.test(f.filename)) areas.add(name);
|
||||
}
|
||||
if (areas.size <= 4) areas.forEach(name => labels.add(name));
|
||||
if (docsOnly) labels.add('documentation');
|
||||
if (/^feat(\(|!|:)/.test(title)) labels.add('enhancement');
|
||||
if (/^fix(\(|!|:)/.test(title)) labels.add('bug');
|
||||
if (/^docs(\(|!|:)/.test(title)) labels.add('documentation');
|
||||
if (/^(chore|refactor|style|build)(\(|!|:)/.test(title)) labels.add('cleanup');
|
||||
if (/^perf(\(|!|:)/.test(title)) labels.add('performance');
|
||||
if (!['OWNER', 'MEMBER', 'COLLABORATOR'].includes(pr.author_association) && pr.user.type !== 'Bot') {
|
||||
labels.add('contributor:external');
|
||||
}
|
||||
} else {
|
||||
|
||||
// Type — matched against the TITLE only.
|
||||
//
|
||||
// These ran against title+body and mislabelled long, well-specified
|
||||
// issues. Any body containing an acceptance criterion like "fails
|
||||
// when a pack drifts" matched the bug rule, and a "How to add a
|
||||
// locale" heading matched the question rule, so planned work landed
|
||||
// tagged `bug, question`. A title states the type; a body just
|
||||
// discusses it. `error`/`fail` are dropped entirely — they describe
|
||||
// what almost every issue talks about, not what any issue *is*.
|
||||
if (/\b(bug|crash|panic|broken|stack ?trace|regression)\b/.test(title)) labels.add('bug');
|
||||
if (/\b(feat(?:ure)?|request|enhancement|wishlist|proposal|please add|would be nice|support for)\b/.test(title)) labels.add('enhancement');
|
||||
if (/\b(docs?|readme|documentation|typo|grammar|wording|spelling)\b/.test(title)) labels.add('documentation');
|
||||
if (/\b(question|how (?:do|to)|why does|what does|is it possible)\b/.test(title)) labels.add('question');
|
||||
|
||||
// Locale — title contains CJK (Chinese, Japanese, Korean) characters
|
||||
if (/[-ヿ㐀-鿿가-]/.test(issue.title || '')) labels.add('lang:zh');
|
||||
|
||||
// Areas (path-driven hints)
|
||||
if (/crates\/tui|\btui\b|ratatui|composer|sidebar/.test(text)) labels.add('area:tui');
|
||||
if (/crates\/core|engine|turn ?loop|agent ?loop/.test(text)) labels.add('area:core');
|
||||
if (/crates\/mcp|\bmcp\b/.test(text)) labels.add('area:mcp');
|
||||
if (/crates\/state|sqlite|sessions?|persistence/.test(text)) labels.add('area:state');
|
||||
if (/crates\/execpolicy|approval|sandbox|seatbelt|landlock/.test(text)) labels.add('area:sandbox');
|
||||
if (/crates\/tools|tool[ _]call|tool[ _]registry/.test(text)) labels.add('area:tools');
|
||||
if (/install|cargo install|npm install|scoop|homebrew|prebuilt|binary/.test(text)) labels.add('area:install');
|
||||
if (/\bdesktop app\b|electron/.test(text)) labels.add('area:desktop');
|
||||
if (/app\.codewhale\.net|\bweb app\b|codewhale web/.test(text)) labels.add('area:web');
|
||||
if (/\bmobile\b|\bios\b|\bandroid\b/.test(text)) labels.add('area:mobile');
|
||||
if (/screen ?reader|accessib|a11y|voiceover|\bnvda\b|keyboard navigation/.test(text)) labels.add('accessibility');
|
||||
if (/windows/.test(text)) labels.add('os:windows');
|
||||
if (/freebsd/.test(text)) labels.add('os:freebsd');
|
||||
if (/\bohos\b|harmonyos/.test(text)) labels.add('os:ohos');
|
||||
if (/macos|darwin|apple silicon/.test(text)) labels.add('os:macos');
|
||||
if (/\blinux\b|ubuntu|debian|fedora|arch ?linux/.test(text)) labels.add('os:linux');
|
||||
|
||||
}
|
||||
|
||||
if (labels.size === 0) return;
|
||||
|
||||
// Only add labels that already exist on the repo to avoid creating noise.
|
||||
const existing = await github.paginate(github.rest.issues.listLabelsForRepo, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
per_page: 100,
|
||||
});
|
||||
const existingNames = new Set(existing.map(l => l.name));
|
||||
const toAdd = [...labels].filter(name => existingNames.has(name));
|
||||
if (toAdd.length === 0) return;
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: toAdd,
|
||||
});
|
||||
@@ -1,319 +0,0 @@
|
||||
name: Web maintainer
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '43 6 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
lane:
|
||||
description: Which lane to run
|
||||
required: true
|
||||
default: all
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- derived
|
||||
- copy-freshness
|
||||
- link-health
|
||||
- docs-drift
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: web-maintainer
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
WEB_MAINTAINER_CLI_VERSION: v0.10.2
|
||||
|
||||
jobs:
|
||||
plan:
|
||||
name: Plan
|
||||
if: github.repository == 'codewhale-hq/Codewhale' && github.ref == 'refs/heads/main' && vars.WEB_MAINTAINER_ENABLED == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions: {}
|
||||
outputs:
|
||||
lanes: ${{ steps.plan.outputs.lanes }}
|
||||
derived: ${{ steps.plan.outputs.derived }}
|
||||
steps:
|
||||
- id: plan
|
||||
env:
|
||||
INPUT_LANE: ${{ inputs.lane || 'all' }}
|
||||
run: |
|
||||
derived=false
|
||||
lanes='[]'
|
||||
case "$INPUT_LANE" in
|
||||
all) derived=true; lanes='["copy-freshness","link-health","docs-drift"]' ;;
|
||||
derived) derived=true ;;
|
||||
copy-freshness|link-health|docs-drift) lanes="[\"${INPUT_LANE}\"]" ;;
|
||||
*) echo "::error::unknown lane"; exit 1 ;;
|
||||
esac
|
||||
echo "derived=${derived}" >> "$GITHUB_OUTPUT"
|
||||
echo "lanes=${lanes}" >> "$GITHUB_OUTPUT"
|
||||
echo "Web maintainer plan: derived=${derived} lanes=${lanes}" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
derived:
|
||||
name: Derived files
|
||||
needs: plan
|
||||
if: needs.plan.result == 'success' && needs.plan.outputs.derived == 'true' && vars.WEB_MAINTAINER_ENABLED == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: main
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Install web dependencies
|
||||
working-directory: web
|
||||
run: npm ci --no-audit --no-fund
|
||||
- name: Regenerate derived files
|
||||
working-directory: web
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
node scripts/sync-latest-release.mjs
|
||||
node scripts/derive-facts.mjs
|
||||
node scripts/derive-changelog.mjs
|
||||
node scripts/derive-install.mjs
|
||||
node scripts/render-terminal-capture.mjs
|
||||
- name: Check what changed
|
||||
id: drift
|
||||
run: |
|
||||
mkdir -p "$RUNNER_TEMP/out"
|
||||
if [ -n "$(git ls-files --others --exclude-standard)" ]; then
|
||||
echo "::error::regeneration produced untracked files"
|
||||
git ls-files --others --exclude-standard
|
||||
exit 1
|
||||
fi
|
||||
git diff --name-status | node web/maintainer/check-allowlist.mjs web/maintainer/derived.txt derived
|
||||
if [ -n "$(git diff --summary)" ]; then
|
||||
echo "::error::regeneration created, deleted or changed modes"
|
||||
exit 1
|
||||
fi
|
||||
git diff --binary > "$RUNNER_TEMP/out/change.patch"
|
||||
if [ -s "$RUNNER_TEMP/out/change.patch" ]; then
|
||||
echo "drift=true" >> "$GITHUB_OUTPUT"
|
||||
git diff --stat >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "drift=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Derived files are current." >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
- name: Run the web checks on the regenerated tree
|
||||
if: steps.drift.outputs.drift == 'true'
|
||||
working-directory: web
|
||||
run: |
|
||||
npm run check
|
||||
npx vitest run
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
if: steps.drift.outputs.drift == 'true'
|
||||
with:
|
||||
name: web-maintainer-derived
|
||||
path: ${{ runner.temp }}/out/change.patch
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
lane:
|
||||
name: Lane ${{ matrix.lane }}
|
||||
needs: plan
|
||||
if: needs.plan.result == 'success' && needs.plan.outputs.lanes != '[]' && vars.WEB_MAINTAINER_ENABLED == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 3
|
||||
matrix:
|
||||
lane: ${{ fromJSON(needs.plan.outputs.lanes) }}
|
||||
env:
|
||||
LANE: ${{ matrix.lane }}
|
||||
steps:
|
||||
- name: Require the DeepSeek key
|
||||
env:
|
||||
KEY: ${{ secrets.WEB_MAINTAINER_DEEPSEEK_API_KEY }}
|
||||
run: |
|
||||
if [ -z "$KEY" ]; then
|
||||
echo "::error::WEB_MAINTAINER_DEEPSEEK_API_KEY is not set. Create a dedicated DeepSeek key with a provider-side monthly cap and add it as that repository secret, or unset WEB_MAINTAINER_ENABLED. See web/maintainer/README.md."
|
||||
exit 1
|
||||
fi
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: main
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Prove the command sandbox works or stop
|
||||
run: |
|
||||
sudo apt-get install -y --no-install-recommends bubblewrap util-linux >/dev/null
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 >/dev/null || true
|
||||
probe="$GITHUB_WORKSPACE/.sandbox-probe"
|
||||
rm -f "$probe" /usr/local/sandbox-probe
|
||||
if ! timeout 10 bash -c 'exec 3<>/dev/tcp/api.deepseek.com/443'; then
|
||||
echo "::error::the runner cannot reach api.deepseek.com, so the network denial cannot be proven."
|
||||
exit 1
|
||||
fi
|
||||
if ! /usr/bin/bwrap --unshare-all --ro-bind / / --dev /dev --proc /proc --tmpfs /tmp \
|
||||
--bind "$GITHUB_WORKSPACE" "$GITHUB_WORKSPACE" --chdir "$GITHUB_WORKSPACE" -- \
|
||||
bash -c 'touch .sandbox-probe && ! touch /usr/local/sandbox-probe 2>/dev/null && ! timeout 10 bash -c "exec 3<>/dev/tcp/api.deepseek.com/443" 2>/dev/null'; then
|
||||
echo "::error::bubblewrap did not confine a child on this runner (writes outside the workspace or outbound network were not denied). Refusing to give a model a shell."
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -e "$probe" ] || [ -e /usr/local/sandbox-probe ]; then
|
||||
echo "::error::sandbox probe left unexpected state. Refusing to give a model a shell."
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$probe"
|
||||
echo "Sandbox probe passed: workspace-only writes, no outbound network." >> "$GITHUB_STEP_SUMMARY"
|
||||
- name: Stage trusted tooling and the pinned CLI
|
||||
run: |
|
||||
mkdir -p "$RUNNER_TEMP/trusted" "$RUNNER_TEMP/home"
|
||||
cp web/maintainer/allowlist.txt web/maintainer/check-allowlist.mjs "$RUNNER_TEMP/trusted/"
|
||||
cp "web/maintainer/lanes/${LANE}.md" "$RUNNER_TEMP/trusted/prompt.md"
|
||||
CODEWHALE_VERSION="$WEB_MAINTAINER_CLI_VERSION" CODEWHALE_INSTALL_DIR="$RUNNER_TEMP/bin" sh web/public/install.sh
|
||||
"$RUNNER_TEMP/bin/codewhale" --version
|
||||
- name: Probe outbound links
|
||||
if: matrix.lane == 'link-health'
|
||||
run: node web/maintainer/link-report.mjs "$GITHUB_WORKSPACE/.git/maintainer-input/link-report.json"
|
||||
- name: Record the git state the model must not change
|
||||
run: |
|
||||
git config --local --list | sort > "$RUNNER_TEMP/git-config.before"
|
||||
find .git/hooks -type f ! -name '*.sample' > "$RUNNER_TEMP/hooks.before"
|
||||
test ! -s "$RUNNER_TEMP/hooks.before"
|
||||
- name: Run the lane on DeepSeek
|
||||
timeout-minutes: 25
|
||||
env:
|
||||
WEB_MAINTAINER_DEEPSEEK_API_KEY: ${{ secrets.WEB_MAINTAINER_DEEPSEEK_API_KEY }}
|
||||
run: |
|
||||
env -i \
|
||||
PATH="$PATH" \
|
||||
HOME="$RUNNER_TEMP/home" \
|
||||
CODEWHALE_HOME="$RUNNER_TEMP/home/.codewhale" \
|
||||
CODEWHALE_SECRET_BACKEND=file \
|
||||
CODEWHALE_MEMORY=false \
|
||||
CODEWHALE_TELEMETRY=false \
|
||||
DEEPSEEK_API_KEY="$WEB_MAINTAINER_DEEPSEEK_API_KEY" \
|
||||
"$RUNNER_TEMP/bin/codewhale" \
|
||||
--no-project-config \
|
||||
--skip-onboarding \
|
||||
--workspace "$GITHUB_WORKSPACE" \
|
||||
exec \
|
||||
--provider deepseek \
|
||||
--model deepseek-flash \
|
||||
--reasoning-effort low \
|
||||
--auto \
|
||||
--sandbox workspace-write \
|
||||
--max-turns 40 \
|
||||
--output-format stream-json \
|
||||
-- "$(cat "$RUNNER_TEMP/trusted/prompt.md")" \
|
||||
> "$RUNNER_TEMP/exec.jsonl"
|
||||
echo "Model run finished: $(wc -l < "$RUNNER_TEMP/exec.jsonl") stream events." >> "$GITHUB_STEP_SUMMARY"
|
||||
- name: Enforce the path allowlist
|
||||
id: scope
|
||||
run: |
|
||||
if git status --porcelain --untracked-files=all --ignored | grep -qv '^ M '; then
|
||||
echo "::error::the lane left untracked, ignored, staged or non-edit entries"
|
||||
git status --porcelain --untracked-files=all --ignored | grep -v '^ M '
|
||||
exit 1
|
||||
fi
|
||||
git config --local --list | sort | diff - "$RUNNER_TEMP/git-config.before" || { echo "::error::the lane changed .git/config"; exit 1; }
|
||||
find .git/hooks -type f ! -name '*.sample' | diff - "$RUNNER_TEMP/hooks.before" || { echo "::error::the lane added git hooks"; exit 1; }
|
||||
git diff --name-status | node "$RUNNER_TEMP/trusted/check-allowlist.mjs" "$RUNNER_TEMP/trusted/allowlist.txt" "$LANE"
|
||||
if [ -n "$(git diff --summary)" ]; then
|
||||
echo "::error::the lane created, deleted or changed modes"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$RUNNER_TEMP/out"
|
||||
git diff --binary > "$RUNNER_TEMP/out/change.patch"
|
||||
if [ ! -s "$RUNNER_TEMP/out/change.patch" ]; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Lane ${LANE}: no change proposed." >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
if [ "$(wc -c < "$RUNNER_TEMP/out/change.patch")" -gt 262144 ]; then
|
||||
echo "::error::patch exceeds 256 KiB"
|
||||
exit 1
|
||||
fi
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
{ echo "Lane ${LANE} proposes:"; echo '```text'; git diff --stat; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
|
||||
- name: Fresh checkout for verification
|
||||
if: steps.scope.outputs.changed == 'true'
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: main
|
||||
path: verify
|
||||
persist-credentials: false
|
||||
- name: Run the web checks on the proposal in a clean tree without secrets
|
||||
if: steps.scope.outputs.changed == 'true'
|
||||
env:
|
||||
PATCH: ${{ runner.temp }}/out/change.patch
|
||||
run: |
|
||||
cd verify
|
||||
git apply --index --whitespace=nowarn "$PATCH"
|
||||
cd web
|
||||
npm ci --no-audit --no-fund
|
||||
npm run check
|
||||
npx vitest run
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
if: steps.scope.outputs.changed == 'true'
|
||||
with:
|
||||
name: web-maintainer-${{ matrix.lane }}
|
||||
path: ${{ runner.temp }}/out/change.patch
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: Open pull requests
|
||||
needs: [plan, derived, lane]
|
||||
if: always() && needs.plan.result == 'success' && vars.WEB_MAINTAINER_ENABLED == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
|
||||
continue-on-error: true
|
||||
with:
|
||||
pattern: web-maintainer-*
|
||||
path: ${{ runner.temp }}/patches
|
||||
- name: Look for proposed patches
|
||||
id: patches
|
||||
run: |
|
||||
if [ -n "$(find "$RUNNER_TEMP/patches" -name change.patch -size +0c 2>/dev/null)" ]; then
|
||||
echo "found=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "found=false" >> "$GITHUB_OUTPUT"
|
||||
echo "No lane proposed a change; no pull request opened." >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
- if: steps.patches.outputs.found == 'true'
|
||||
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.CODEWHALE_APP_ID }}
|
||||
private-key: ${{ secrets.CODEWHALE_APP_PRIVATE_KEY }}
|
||||
permission-contents: write
|
||||
permission-pull-requests: write
|
||||
- if: steps.patches.outputs.found == 'true'
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: false
|
||||
- name: Open or update one pull request per lane
|
||||
if: steps.patches.outputs.found == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
APP_ID: ${{ vars.CODEWHALE_APP_ID }}
|
||||
ARTIFACTS_DIR: ${{ runner.temp }}/patches
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
run: |
|
||||
mkdir -p "$RUNNER_TEMP/trusted"
|
||||
cp web/maintainer/allowlist.txt web/maintainer/derived.txt web/maintainer/check-allowlist.mjs web/maintainer/publish-prs.sh "$RUNNER_TEMP/trusted/"
|
||||
TRUSTED_DIR="$RUNNER_TEMP/trusted" bash "$RUNNER_TEMP/trusted/publish-prs.sh"
|
||||
@@ -1,125 +0,0 @@
|
||||
name: Web Frontend
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.event_name == 'pull_request' && format('web-pr-{0}', github.event.pull_request.number) || format('web-{0}', github.run_id) }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint & Type Check
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
with:
|
||||
# `check:facts` derives each model's `addedAt` from the commit date on
|
||||
# which its id first appeared in the model declaration paths
|
||||
# (web/scripts/facts-lib.mjs). A shallow checkout cannot see that
|
||||
# history, so every date collapses to the tip commit and the committed
|
||||
# facts always read as stale. ci.yml pins depth 0 for the same reason.
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
cache-dependency-path: web/package-lock.json
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
- name: Check web surface
|
||||
# The shared gate checks committed facts before generating build inputs.
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: npm run check
|
||||
|
||||
deploy-reminder:
|
||||
name: Deployment approval needed
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
needs: lint
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- name: Surface the manual deployment gate
|
||||
env:
|
||||
REVISION: ${{ github.sha }}
|
||||
run: |
|
||||
echo "::notice title=Web deployment approval needed::Revision ${REVISION} passed the web gates but is not deployed. Dispatch web.yml on main to publish it."
|
||||
{
|
||||
echo "## Web deployment approval needed"
|
||||
echo
|
||||
echo "Revision \`${REVISION}\` passed the web gates but has **not** been deployed."
|
||||
echo
|
||||
echo "A maintainer can publish it with \`gh workflow run web.yml --repo codewhale-hq/CodeWhale --ref main\`."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
deploy:
|
||||
name: Deploy to Cloudflare
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
needs: lint
|
||||
# Deploy is MANUAL ONLY: an authorized dispatch on main runs `lint`
|
||||
# before deploying. Pushes and pull requests do not start this workflow.
|
||||
# This mirrors scripts/check-cloudflare-deploy-env.mjs, which fails closed
|
||||
# unless GITHUB_EVENT_NAME is workflow_dispatch, GITHUB_REF is
|
||||
# refs/heads/main, and GITHUB_SHA is an exact 40-hex revision — a push
|
||||
# trigger here would only produce a red job after `lint` had already run.
|
||||
# lib/deploy-preflight.test.ts asserts both halves of that contract.
|
||||
# `needs: lint` is the gate: facts drift, docs parity, tests, ESLint, tsc,
|
||||
# and a production build all pass before anything reaches Cloudflare.
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch'
|
||||
&& github.ref == 'refs/heads/main'
|
||||
# Serialize deploys so two dispatches landing close together cannot race and
|
||||
# leave Cloudflare serving the older bundle. Never cancel in progress: a
|
||||
# half-finished OpenNext upload is worse than a queued one.
|
||||
concurrency:
|
||||
group: deploy-codewhale-web
|
||||
cancel-in-progress: false
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
env:
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
# Pin the checkout to the exact revision this dispatch resolved, so the
|
||||
# SHA reported to compare:deployed-facts and asserted on the public
|
||||
# receipt below is the SHA that was actually built, even if main moves
|
||||
# while the run is queued behind the concurrency group.
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
cache-dependency-path: web/package-lock.json
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Record deployed/source drift
|
||||
# Read-only and credential-free. A mismatch is the normal state here —
|
||||
# it is the gap this run is about to close — so this step reports
|
||||
# without gating. The real assertion is the post-deploy verification
|
||||
# below, which must observe this exact revision on the public receipt.
|
||||
run: npm run compare:deployed-facts -- --expected-revision "$GITHUB_SHA"
|
||||
- name: Check Cloudflare deploy environment
|
||||
run: npm run check:deploy-env
|
||||
# npm's deploy script performs one OpenNext build, then deploys that exact
|
||||
# bundle. OpenNext populates the remote cache before cf deploy --prebuilt;
|
||||
# the bundler has no custom build that could invalidate that cache.
|
||||
- name: Build and deploy exact OpenNext bundle
|
||||
run: npm run deploy
|
||||
- name: Verify exact deployed revision
|
||||
# The public /api/facts receipt must identify this workflow's exact
|
||||
# checkout before the manual deployment run can finish green.
|
||||
run: npm run check:deployed-facts -- --expected-revision "$GITHUB_SHA" --attempts 10 --retry-delay-ms 3000
|
||||
@@ -1,103 +0,0 @@
|
||||
name: Windows ARM64 export probe
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
run_id:
|
||||
description: Release-artifacts or release-candidate run whose windows-arm64 import receipt is probed
|
||||
required: true
|
||||
source_sha:
|
||||
description: Source commit of that run (names the receipt artifact)
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
probe:
|
||||
runs-on: windows-11-arm
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
|
||||
with:
|
||||
name: windows-imports-windows-arm64-${{ inputs.source_sha }}
|
||||
path: receipt
|
||||
run-id: ${{ inputs.run_id }}
|
||||
github-token: ${{ github.token }}
|
||||
- name: Resolve every imported pair in a native ARM64 process
|
||||
shell: pwsh
|
||||
env:
|
||||
SOURCE_SHA: ${{ inputs.source_sha }}
|
||||
SOURCE_RUN: ${{ inputs.run_id }}
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$arch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString()
|
||||
if ($arch -ne 'Arm64') { throw "probe process is $arch, not Arm64" }
|
||||
Add-Type -TypeDefinition @'
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
public static class ExportProbe {
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public static extern IntPtr LoadLibraryExW(string name, IntPtr file, uint flags);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
|
||||
public static extern IntPtr GetProcAddress(IntPtr module, string name);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public static extern uint GetModuleFileNameW(IntPtr module, StringBuilder path, uint size);
|
||||
}
|
||||
'@
|
||||
$receipt = Get-Content -Raw receipt/receipt.json | ConvertFrom-Json
|
||||
$pairs = @{}
|
||||
foreach ($binary in $receipt) {
|
||||
foreach ($pair in $binary.imports) { $pairs["$($pair[0].ToLowerInvariant())|$($pair[1])"] = $pair }
|
||||
}
|
||||
$modules = [ordered]@{}
|
||||
$unresolved = @()
|
||||
$resolved = 0
|
||||
foreach ($key in ($pairs.Keys | Sort-Object)) {
|
||||
$dll = $pairs[$key][0].ToLowerInvariant()
|
||||
$symbol = $pairs[$key][1]
|
||||
if (-not $modules.Contains($dll)) {
|
||||
$handle = [ExportProbe]::LoadLibraryExW($dll, [IntPtr]::Zero, 0x00000800)
|
||||
if ($handle -eq [IntPtr]::Zero) {
|
||||
$modules[$dll] = @{ handle = [IntPtr]::Zero; path = $null; sha256 = $null; error = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() }
|
||||
} else {
|
||||
$path = [System.Text.StringBuilder]::new(1024)
|
||||
[void][ExportProbe]::GetModuleFileNameW($handle, $path, 1024)
|
||||
$modules[$dll] = @{ handle = $handle; path = $path.ToString(); sha256 = (Get-FileHash -Algorithm SHA256 $path.ToString()).Hash.ToLowerInvariant(); error = 0 }
|
||||
}
|
||||
}
|
||||
$module = $modules[$dll]
|
||||
if ($module.handle -ne [IntPtr]::Zero -and [ExportProbe]::GetProcAddress($module.handle, $symbol) -ne [IntPtr]::Zero) { $resolved++ } else { $unresolved += ,@($dll, $symbol) }
|
||||
}
|
||||
$version = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
|
||||
$result = [ordered]@{
|
||||
result = $(if ($unresolved.Count -eq 0) { 'PASS' } else { 'FAIL' })
|
||||
kind = 'exact native system export resolution'
|
||||
time = (Get-Date).ToUniversalTime().ToString('o')
|
||||
osVersion = [System.Environment]::OSVersion.Version.ToString()
|
||||
osBuild = "$($version.CurrentBuild).$($version.UBR)"
|
||||
osProduct = $version.ProductName
|
||||
processArchitecture = 'ARM64'
|
||||
runner = 'GitHub-hosted windows-11-arm'
|
||||
source = $env:SOURCE_SHA
|
||||
sourceRun = $env:SOURCE_RUN
|
||||
binaries = @($receipt | ForEach-Object { [ordered]@{ binary = $_.binary; sha256 = $_.sha256 } })
|
||||
resolved = $resolved
|
||||
unresolved = $unresolved.Count
|
||||
unresolvedPairs = $unresolved
|
||||
exportProbeMethod = 'ARM64 process: LoadLibraryExW per imported DLL with LOAD_LIBRARY_SEARCH_SYSTEM32 (0x00000800), then GetProcAddress for every imported symbol; module path and SHA256 recorded'
|
||||
systemModules = [ordered]@{}
|
||||
}
|
||||
foreach ($dll in $modules.Keys) { $result.systemModules[$dll] = [ordered]@{ path = $modules[$dll].path; sha256 = $modules[$dll].sha256; loadError = $modules[$dll].error } }
|
||||
New-Item -ItemType Directory -Force probe | Out-Null
|
||||
$result | ConvertTo-Json -Depth 6 | Set-Content -Encoding utf8 probe/export-probe.json
|
||||
Get-Content probe/export-probe.json
|
||||
if ($unresolved.Count -ne 0) { throw "$($unresolved.Count) imported pairs did not resolve" }
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
if: always()
|
||||
with:
|
||||
name: windows-arm64-export-probe-${{ inputs.source_sha }}
|
||||
path: probe/
|
||||
if-no-files-found: warn
|
||||
@@ -1,40 +0,0 @@
|
||||
name: Windows focus
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
expected_sha:
|
||||
description: Exact 40-character commit selected by --ref
|
||||
required: true
|
||||
type: string
|
||||
filter:
|
||||
description: nextest expression, for example 'test(compaction_lock) | test(persistence_actor)'
|
||||
required: true
|
||||
default: all()
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
focused:
|
||||
name: Focused tests (windows-latest)
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_MIN_STACK: 8388608
|
||||
RUST_BACKTRACE: full
|
||||
FILTER: ${{ inputs.filter }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ inputs.expected_sha }}
|
||||
fetch-depth: 1
|
||||
- uses: dtolnay/[email protected]
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- name: Run focused tests
|
||||
shell: bash
|
||||
run: cargo nextest run -p codewhale-engine -p codewhale-plugins -p codewhale-tui -p codewhale-app-server --lib --locked --profile ci -E "$FILTER"
|
||||
@@ -1,194 +1,79 @@
|
||||
# Build artifacts
|
||||
/target
|
||||
/target-*
|
||||
/vendor/codewhale-ratatui/target/
|
||||
.entire/
|
||||
.cursor/
|
||||
.plans/
|
||||
.codewhale-worktrees/
|
||||
docs/superpowers/
|
||||
codewhale-inference/
|
||||
/extensions/vscode/out/
|
||||
*.pdb
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.rlib
|
||||
*.o
|
||||
# deps
|
||||
# Node.js dependencies
|
||||
node_modules
|
||||
.pnpm-store
|
||||
.pnpm-debug.log
|
||||
|
||||
# Development
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
node_modules/
|
||||
.vscode/
|
||||
# The extension ships its own dev-loop config (F5 Extension Development Host).
|
||||
# Without this negation the bare `.vscode/` above swallows it silently.
|
||||
!/extensions/vscode/.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
# Keep pnpm-lock.yaml
|
||||
!pnpm-lock.yaml
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
.pytest_cache/
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
.venv/
|
||||
*.egg-info/
|
||||
# build
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
storybook-static/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
# ignore README for publishing
|
||||
codex-cli/README.md
|
||||
|
||||
# Generated
|
||||
# Local design critiques and operator receipts do not ship with the product.
|
||||
/.impeccable/
|
||||
outputs/
|
||||
tmp/
|
||||
backup/
|
||||
# ignore Nix derivation results
|
||||
result
|
||||
|
||||
# Reference papers / large research blobs (keep locally if needed, don't ship)
|
||||
docs/DeepSeek_V4.pdf
|
||||
docs/*.pdf
|
||||
# editor
|
||||
.vscode/
|
||||
.idea/
|
||||
.history/
|
||||
.zed/
|
||||
*.swp
|
||||
*~
|
||||
|
||||
# Note: Cargo.lock is intentionally NOT ignored for reproducible builds
|
||||
|
||||
# Local dev scripts and temp files
|
||||
*.sh
|
||||
*.cmd
|
||||
!scripts/**
|
||||
!.github/scripts/**
|
||||
!web/public/install.sh
|
||||
!packaging/winget/**
|
||||
# Bridge launchers are shipped entry points (`npm run bridge`), not local dev
|
||||
# scripts; `!scripts/**` above only re-includes the repo-root scripts/ dir.
|
||||
!integrations/*/scripts/**
|
||||
test.txt
|
||||
TODO*.md
|
||||
todo*.md
|
||||
AGENTS.md
|
||||
# …except the deliberate, maintained agent-guidance files:
|
||||
!/AGENTS.md
|
||||
!crates/tui/AGENTS.md
|
||||
!crates/tui/locales/AGENTS.md
|
||||
!web/AGENTS.md
|
||||
NEXT_SESSION.md
|
||||
AI_HANDOFF.md
|
||||
result.json
|
||||
count_deps.py
|
||||
project_overhaul_prompt.md
|
||||
.codex/
|
||||
.context/
|
||||
.wrangler/
|
||||
|
||||
# Local runtime state
|
||||
# Ignore everything under any .codewhale/ (snapshots, auto-generated
|
||||
# instructions.md, etc.) at any depth EXCEPT the committed repo authority policy
|
||||
# and this repository's own project hooks.
|
||||
**/.codewhale/*
|
||||
!**/.codewhale/constitution.json
|
||||
!/.codewhale/hooks.toml
|
||||
.deepseek/
|
||||
**/session_*.json
|
||||
*.db
|
||||
|
||||
# Chat bridge local state. `npm run bridge` writes the Weixin bot login token
|
||||
# (account.json) and the long-poll cursor here; these are live credentials.
|
||||
integrations/*-bridge/.state/
|
||||
npm/*/bin/downloads/
|
||||
|
||||
# Companion app (tracked separately)
|
||||
apps/
|
||||
|
||||
# Claude Code runtime artifacts
|
||||
.worktrees/
|
||||
.ace-tool/
|
||||
|
||||
# NOTE: the blanket `.claude/` rule further down already ignores everything
|
||||
# under .claude/ — `git check-ignore -v` attributes every path below to that
|
||||
# line, not to these. The handoff/codemap patterns are kept as defense in depth:
|
||||
# those notes are working state, not user-facing artifacts, and they leaked into
|
||||
# the public repo through .claude/ before (HANDOFF_v0.8.28, CODEMAP_v0.8.25).
|
||||
# They live under .private/handoffs/ now. If anyone ever narrows the blanket
|
||||
# rule to track part of .claude/, these must keep holding.
|
||||
.claude/HANDOFF_*
|
||||
.claude/CODEMAP_*
|
||||
.claude/handoff_*
|
||||
.claude/codemap_*
|
||||
|
||||
# Maintainer-internal design notes (trade-secret material, never published)
|
||||
.private/
|
||||
|
||||
# Coverage reports (build artifacts)
|
||||
web/coverage/
|
||||
|
||||
# Agent handoffs and version-specific setup plans are working-state notes, not
|
||||
# public docs. Keep durable setup guidance in docs/runbooks instead.
|
||||
docs/*HANDOFF*.md
|
||||
docs/*handoff*.md
|
||||
docs/*_PLAN.md
|
||||
!docs/archive/**
|
||||
|
||||
# direnv
|
||||
.envrc
|
||||
.direnv
|
||||
scripts/run_deep_swe.py
|
||||
# cli tools
|
||||
CLAUDE.md
|
||||
.claude/
|
||||
|
||||
# Local run artifacts and caches re-included by !scripts/**
|
||||
results/
|
||||
scripts/**/__pycache__/
|
||||
# caches
|
||||
.cache/
|
||||
.turbo/
|
||||
.parcel-cache/
|
||||
.eslintcache
|
||||
.nyc_output/
|
||||
.jest/
|
||||
*.tsbuildinfo
|
||||
|
||||
# Maintainer-local verification artifacts
|
||||
.uv-bin/
|
||||
.uv-cache/
|
||||
.uv-tools/
|
||||
issues/
|
||||
logs/
|
||||
notes/
|
||||
# logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Agent-local roadmap and scratch (never commit — may hold unreleased plans)
|
||||
.agents/
|
||||
scratchpad/
|
||||
# env
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
# Exploratory operations spec / prompt — never track (canonical copy lives at CW root)
|
||||
operations9.md
|
||||
OPERATIONS_0_9_0.md
|
||||
operations_0_9_0.md
|
||||
CODEWHALE_0_9_0_CUTOVER.md
|
||||
# package
|
||||
*.tgz
|
||||
|
||||
# Hepha project metadata (local only, not upstream)
|
||||
.hepha/
|
||||
# ci
|
||||
.vercel/
|
||||
.netlify/
|
||||
|
||||
# Ralph agent scratch (local only)
|
||||
.ralph/
|
||||
# patches
|
||||
apply_patch/
|
||||
|
||||
# Local playwright MCP artifacts
|
||||
.playwright-mcp/
|
||||
# coverage
|
||||
coverage/
|
||||
|
||||
# computer-use plugin smoke receipts (per-run local evidence)
|
||||
crates/tui/plugins/computer-use/receipts/
|
||||
# os
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
Icon?
|
||||
.Spotlight-V100/
|
||||
|
||||
# OrcaRouter provider screenshot evidence (per-run local evidence)
|
||||
orca-evidence/
|
||||
# Unwanted package managers
|
||||
.yarn/
|
||||
yarn.lock
|
||||
|
||||
# Portable pet build entry points are source.
|
||||
!pet/**/*.sh
|
||||
!pet/verify.sh
|
||||
|
||||
# Private keys and certificates never belong in the tree.
|
||||
*.pem
|
||||
*.key
|
||||
*.p12
|
||||
*.p8
|
||||
id_rsa*
|
||||
id_ed25519*
|
||||
!docs/cloud-facts/fixtures/test-only-signing-key.pem
|
||||
# release
|
||||
package.json-e
|
||||
session.ts-e
|
||||
CHANGELOG.ignore.md
|
||||
@@ -0,0 +1 @@
|
||||
pnpm lint-staged
|
||||
@@ -1,30 +0,0 @@
|
||||
Hunter Bown <[email protected]> devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
||||
Hunter Bown <[email protected]> Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
||||
Hunter Bown <[email protected]> GitHub <[email protected]>
|
||||
Hunter Bown <[email protected]> Claude <[email protected]>
|
||||
Hunter Bown <[email protected]> Claude Opus 4.6 <[email protected]>
|
||||
Hunter Bown <[email protected]> Claude Opus 4.6 (1M context) <[email protected]>
|
||||
Hunter Bown <[email protected]> Claude Opus 4.7 (1M context) <[email protected]>
|
||||
Hunter Bown <[email protected]> Copilot <[email protected]>
|
||||
Hunter Bown <[email protected]> Copilot <1693627+github-copilot-cli[bot]@users.noreply.github.com>
|
||||
Hunter Bown <[email protected]> Copilot <946600+copilot-pull-request-reviewer[bot]@users.noreply.github.com>
|
||||
Hunter Bown <[email protected]> Qwen-Coder <[email protected]>
|
||||
Hunter Bown <[email protected]> qwencoder <[email protected]>
|
||||
Hunter Bown <[email protected]> Cursor Agent <[email protected]>
|
||||
Hunter Bown <[email protected]> cursoragent <[email protected]>
|
||||
Hunter Bown <[email protected]> gemini-code-assist[bot] <gemini-code-assist[bot]@users.noreply.github.com>
|
||||
Hunter Bown <[email protected]> gemini-code-assist[bot] <956858+gemini-code-assist[bot]@users.noreply.github.com>
|
||||
ddlw <[email protected]> <[email protected]>
|
||||
dmitri-0 <[email protected]> <[email protected]>
|
||||
Paulo Aboim Pinto <[email protected]> <[email protected]>
|
||||
Paulo Aboim Pinto <[email protected]> <[email protected]>
|
||||
Paulo Aboim Pinto <[email protected]> <[email protected]>
|
||||
AiurArtanis <[email protected]> <[email protected]>
|
||||
Apeiron <[email protected]> <[email protected]>
|
||||
ChaceLyee2101 <[email protected]> <[email protected]>
|
||||
movic <[email protected]> <[email protected]>
|
||||
merchloubna70-dot <[email protected]> <[email protected]>
|
||||
merchloubna70-dot <[email protected]> <[email protected]>
|
||||
luola <[email protected]> <[email protected]>
|
||||
SONG ZHENRUI <[email protected]> <[email protected]>
|
||||
xiluoduyu <[email protected]> <[email protected]>
|
||||
@@ -0,0 +1,4 @@
|
||||
shamefully-hoist=true
|
||||
strict-peer-dependencies=false
|
||||
node-linker=hoisted
|
||||
prefer-workspace-packages=true
|
||||
@@ -0,0 +1,3 @@
|
||||
/codex-cli/dist
|
||||
/codex-cli/node_modules
|
||||
pnpm-lock.yaml
|
||||
@@ -0,0 +1,8 @@
|
||||
printWidth = 80
|
||||
quoteProps = "consistent"
|
||||
semi = true
|
||||
tabWidth = 2
|
||||
trailingComma = "all"
|
||||
|
||||
# Preserve existing behavior for markdown/text wrapping.
|
||||
proseWrap = "preserve"
|
||||
@@ -1,45 +0,0 @@
|
||||
# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.12.0.schema.json
|
||||
|
||||
PackageIdentifier: HunterBown.CodeWhale
|
||||
PackageVersion: 0.10.2
|
||||
PackageLocale: en-US
|
||||
ManifestType: singleton
|
||||
ManifestVersion: 1.12.0
|
||||
|
||||
Publisher: Hmbown
|
||||
PublisherUrl: https://github.com/Hmbown
|
||||
PublisherSupportUrl: https://github.com/codewhale-hq/CodeWhale/issues
|
||||
PackageName: CodeWhale
|
||||
PackageUrl: https://github.com/codewhale-hq/CodeWhale
|
||||
License: MIT
|
||||
LicenseUrl: https://github.com/codewhale-hq/CodeWhale/blob/main/LICENSE
|
||||
Copyright: Copyright (c) Hmbown
|
||||
ShortDescription: Terminal coding agent for supported hosted and local models
|
||||
Description: |
|
||||
CodeWhale runs in your terminal, connects to hosted and local models, and edits your workspace with approval controls.
|
||||
Author: Hmbown
|
||||
Moniker: codewhale
|
||||
Tags:
|
||||
- codewhale
|
||||
- deepseek
|
||||
- cli
|
||||
- tui
|
||||
- terminal
|
||||
- ai
|
||||
- coding-agent
|
||||
- rust
|
||||
ReleaseNotesUrl: https://github.com/codewhale-hq/CodeWhale/releases/tag/v0.10.2
|
||||
Documentations:
|
||||
- DocumentLabel: Install guide
|
||||
DocumentUrl: https://github.com/codewhale-hq/CodeWhale/blob/main/docs/INSTALL.md
|
||||
|
||||
InstallerType: portable
|
||||
Commands:
|
||||
- codewhale
|
||||
Dependencies:
|
||||
PackageDependencies:
|
||||
- PackageIdentifier: Microsoft.VCRedist.2015+.x64
|
||||
Installers:
|
||||
- Architecture: x64
|
||||
InstallerUrl: https://github.com/codewhale-hq/Codewhale/releases/download/v0.10.2/codewhale-tui-windows-x64.exe
|
||||
InstallerSha256: 578280bec8d6e0ee9cfe97ee29de817535689f4078d8afd47384ff1941b1ee6c
|
||||
@@ -1,279 +0,0 @@
|
||||
# Codewhale agent guidance
|
||||
|
||||
Keep this file durable. Derive changing release, provider, branch, and flake
|
||||
state from the repository, tests, CI, and current issue tracker rather than from
|
||||
instructions or memory. The nearest scoped `AGENTS.md` adds path-specific rules.
|
||||
|
||||
## The ponytail method
|
||||
|
||||
From [dietrichgebert/ponytail](https://github.com/dietrichgebert/ponytail) —
|
||||
"the laziest senior dev in the room." *He says nothing. He writes one line. It
|
||||
works.* The best code is the code you never wrote.
|
||||
|
||||
Before writing code, walk the decision ladder in order and stop at the first
|
||||
rung that answers:
|
||||
|
||||
1. **Does this need to exist?** → Skip it.
|
||||
2. **Already in this codebase?** → Reuse it.
|
||||
3. **Stdlib does it?** → Use it.
|
||||
4. **Native platform feature?** → Use it.
|
||||
5. **Installed dependency?** → Use it.
|
||||
6. **One line?** → One line.
|
||||
7. **Only then:** the minimum that works.
|
||||
|
||||
The ladder runs *after* understanding the problem. Lazy about solutions, never
|
||||
about reading the code first — a short diff written without reading the call
|
||||
sites is not ponytail, it is a guess.
|
||||
|
||||
**Never cut, at any rung:** trust-boundary validation, data-loss handling,
|
||||
security, accessibility. Brevity is not a reason to drop a guard.
|
||||
|
||||
Rung 2 is the one this repository keeps failing. The `model_*` / `*_config` /
|
||||
`provider_*` grep rule below is rung 2 with a name; so is "one turn loop, one
|
||||
base prompt". Two more corollaries earned here:
|
||||
|
||||
- **An abstraction must delete caller code.** If adopting it is pure
|
||||
obligation — required methods, no default bodies that do work — it gets
|
||||
built, adopted once, and abandoned.
|
||||
- **Migrate the last consumer, or do not start.** Framework, one caller,
|
||||
ticket the rest, silence the warning: that ships two systems and a comment
|
||||
that is no longer true. If the migration will not fit, narrow the slice —
|
||||
never the adoption. The standing `#[allow(dead_code)]` count is the running
|
||||
receipt; `scripts/check-dead-code-budget.py` prints it.
|
||||
|
||||
## Working rules
|
||||
|
||||
- Inspect status and existing consumers before editing. Preserve unrelated,
|
||||
dirty, and untracked work.
|
||||
- Before adding a module named `model_*`, `*_config`, `provider_*`, or
|
||||
anything that "bridges", "mirrors", or "stages" an existing thing, grep
|
||||
for the existing thing and edit it. A new layer must name the predecessor
|
||||
it replaces in its commit message; otherwise edit the original.
|
||||
- Prefer the simplest implementation that preserves observable contracts. A
|
||||
rewrite is acceptable when justified by product intent and observed behavior,
|
||||
not as a shortcut around understanding existing code.
|
||||
- Search for behavior and symbols before reviving work from an old branch. If a
|
||||
lane is obsolete, preserve its intent and evidence rather than merging stale
|
||||
code mechanically.
|
||||
- Agent-owned work lands on current `main`. Each writing agent works in its
|
||||
own short-lived git worktree cut from `origin/main`, rebases and pushes
|
||||
`main` the same day, then removes the worktree and its build directory. No
|
||||
long-lived branches, nested clones or integration lanes, and never a new
|
||||
worktree to hide dirty, stale or conflicting work: that work is still yours
|
||||
to finish. In a shared checkout, stage by explicit path and retry an
|
||||
`index.lock` failure. Verified main slices may
|
||||
be committed and pushed normally under the rule below; tag, release,
|
||||
deployment and destructive cleanup still require their explicit
|
||||
authorization.
|
||||
- **Uncommitted work found in the checkout is the agent's own** (founder,
|
||||
2026-10-22). Read it, work out what it was for, prove it with the smallest
|
||||
check that covers it, fix what is broken, and commit it in coherent groups
|
||||
by explicit path. Do not route around another writer's files or leave them
|
||||
dirty. Ownership is not a blind sweep:
|
||||
- A file changed in the last 15 minutes, or one a live session or agent is
|
||||
editing, is mid-edit: ask that writer to commit now, and commit what
|
||||
remains once it goes quiet. Never commit a half-written edit.
|
||||
- Never commit secrets, credentials, `.env` files, build output or caches;
|
||||
add them to `.gitignore` instead.
|
||||
- Check a repository's visibility before pushing. Inference business and
|
||||
infrastructure detail stays out of public repositories.
|
||||
- Commit by explicit path; never `git add -A`.
|
||||
- Work that fails its check and cannot be fixed now is not pushed to `main`.
|
||||
Record it and report it instead.
|
||||
- **Maintainers land small verified slices directly on `main`.** Run the
|
||||
focused local check (`scripts/preflight.sh --check`), put the real counts in
|
||||
the commit message, fetch and rebase onto `origin/main` and retry on
|
||||
rejection. Never force-push or rewrite a shared ref. Contributor PRs are
|
||||
always welcome and merge as themselves (founder, 2026-11-07).
|
||||
- When the task is local-only, stay fully offline: no browsing, GitHub or remote
|
||||
Git operations, downloads, dependency installation, provider calls, or
|
||||
source/diff transmission. Record the missing external receipt and keep working
|
||||
locally.
|
||||
- Public name is **Codewhale**. Compatibility identifiers such as `CodeWhale`,
|
||||
`codew`, protocol names, and storage keys change only through an explicit
|
||||
migration.
|
||||
- Keep providers and models first-class and provider-neutral.
|
||||
- Never rewrite published history, retag a release, force-push a shared ref, or
|
||||
publish without explicit authorization. Preserve human contributor credit.
|
||||
- **Model-visible means logged.** Anything that reaches a model request must be
|
||||
reconstructable from the session log, and a new model-visible input needs a
|
||||
session event. Live presentation and the persisted record must agree; when they
|
||||
disagree the record is right.
|
||||
- **Misconfiguration fails loud**, at load when it is self-contained, otherwise
|
||||
at the earliest point it can be resolved. Never silently skip a missing
|
||||
referent.
|
||||
- **Write down what a design does not do**, beside the behaviour it owns — a
|
||||
short known-limitations note in the owning module. A stated limit stops the
|
||||
next reader from assuming a capability that was never built.
|
||||
- **Be generous in public** (founder, 2026-11-07). Thank people specifically
|
||||
for their reports, diagnosis, fixes and testing; link the carrying commit or
|
||||
PR and distinguish candidate work from a shipped release. Status, answers
|
||||
and useful questions are welcome; repetitive progress posts and bot noise
|
||||
are not. Evidence stays in commits and PR bodies; ownership stays in Linear.
|
||||
A contributor PR is never closed because CI is slow, flaky or red for a
|
||||
reason the PR did not cause; when a PR is closed, say why in one specific,
|
||||
thankful sentence. Review bots stay off unless the founder enables them.
|
||||
- **A user feature lands with its registry row.** A new command, `[features]`
|
||||
flag, provider or user-visible feature adds or updates its row in
|
||||
`docs/features.toml` in the same change;
|
||||
`cargo test -p codewhale-tui --test feature_registry` fails on flag drift.
|
||||
- **Public planned-work issues are written for contributors.** An issue opened for planned or release work has the parts in CONTRIBUTING.md § "What a planned-work issue gives you": status, ways to help with files, a local test command and a done-when, what maintainers already hold, and how to claim. Keep it current as pieces land or are claimed.
|
||||
- **Write `close`/`fix`/`resolve #N` only when you mean it.** GitHub closes the
|
||||
issue on merge even inside "does not close #N"; use `Refs #N` otherwise.
|
||||
|
||||
## Landing other people's work
|
||||
|
||||
An external contributor's branch goes stale because *we* land things, not
|
||||
because they did anything wrong. Treat their time as more expensive than ours.
|
||||
|
||||
**The goal is the contributor's PR merging as itself.** Review it, run or
|
||||
rerun only the checks it needs, fix small problems on their branch, and resolve
|
||||
conflicts that `main` caused. Landing their commits as ours (harvesting) is the
|
||||
rare fallback for a branch we cannot push to and that cannot merge in
|
||||
reasonable time; ask on the PR first, because done casually it reads as taking
|
||||
the work even when credit is preserved.
|
||||
|
||||
- **Never make a contributor rebase around our churn.** If their PR conflicts
|
||||
only because main moved, a maintainer resolves it.
|
||||
- Landing mechanics — merge-base diffing, mid-function conflicts, fork-push
|
||||
refusal, runs awaiting first-contributor approval — live in `cw-land`.
|
||||
Follow them instead of improvising.
|
||||
- **Preserve credit in the mechanical sense, not just the polite one.** Commit
|
||||
authorship and `Co-authored-by` trailers must use the contributor's own
|
||||
GitHub-linked address. `AUTHOR_MAP` and `.mailmap` are project conventions —
|
||||
GitHub reads neither for the contribution graph.
|
||||
|
||||
## Merging under a gate
|
||||
|
||||
- **A gate is its artifact.** When a rail says a PR merges only on a passing
|
||||
acceptance record, the record must literally say PASS at merge time. "I
|
||||
re-ran it and the failures are rows this PR does not own" is a judgement to
|
||||
write into the artifact first, not a reason to merge past it.
|
||||
- **Read the review thread, not the check rollup.** Green checks and an unread
|
||||
review with confirmed findings are a merge that ships known bugs.
|
||||
- **When the artifact is ambiguous, resolve the ambiguity — never the merge.**
|
||||
|
||||
## Claiming a test passed
|
||||
|
||||
- Quote the real `test result: N passed; M failed` line, and confirm `N > 0`
|
||||
for the tests that cover the change. `cargo test <filter>` exits 0 having run
|
||||
zero tests when the filter matches nothing, and an exit code alone has
|
||||
already been mistaken for a pass here.
|
||||
- Prefer proving a regression test fails without the fix. A test that passes
|
||||
either way pins the implementation, not the defect.
|
||||
- Audit any hand-rolled scorer before trusting its score: quote the counts it
|
||||
actually evaluated, not the verdict line alone.
|
||||
- Match the evidence to the surface. Run the tests that cover the change, not the
|
||||
whole suite, and do not repeat a check that already passed in order to commit.
|
||||
CI owns exhaustive coverage; a full local run is for CI diagnosis or for an
|
||||
irreducibly repository-wide change.
|
||||
|
||||
## Current contracts
|
||||
|
||||
- The model-facing subagent tool is `agent`; `agent_open`/`agent_eval`/
|
||||
`agent_close`/`delegate_to_agent` are removed surfaces. If the shape must
|
||||
move, move the code and its existing guard test with it.
|
||||
- `BASE_PROMPT` in `crates/engine/src/prompts/text.rs` is the sole base prompt
|
||||
by convention. Same rule: move the code, not the prose, if that changes.
|
||||
- There is exactly one turn loop: `Engine::run_turn` in
|
||||
`crates/engine/src/core/engine/turn_loop.rs`. Note that `crates/engine/src/core/`
|
||||
is a module inside the engine crate (`codewhale-engine`) — it is not `crates/core`, which owns
|
||||
request construction, bounded fragments, and thread/session types and
|
||||
runs no turns. A guard test (`crates/core/tests/single_turn_loop.rs`)
|
||||
fails on a second loop; changing the shape means changing the guard
|
||||
with it.
|
||||
- The system prompt + tool catalog are a session-pinned KV-cache prefix
|
||||
(`docs/CACHE.md`). Any new session-context contributor must state its
|
||||
KV-cache effect: frozen prefix vs. append-only history. Never splice a
|
||||
volatile fact into the prefix; append it as a user-role message.
|
||||
- These active modules are repeatedly misidentified as dead; verify consumers
|
||||
before removal: `runtime/src/context_budget.rs`,
|
||||
`runtime/src/prompt_zones.rs`, `engine/src/tools/remember.rs`, and
|
||||
`config/src/route/`. Native memory lives in `runtime/src/native_memory.rs`;
|
||||
`tools/remember.rs` is its capture path.
|
||||
- Environment-specific behavior belongs in `docs/ENVIRONMENTS.md`, not here.
|
||||
- Blocking-call convention (#6149): code on the Tokio runtime — tool
|
||||
handlers, engine tasks, the UI event loop, anything reached through an
|
||||
`async` call chain — must not run blocking operations inline. Use
|
||||
`tokio::fs` / `tokio::process`, or move the work into `spawn_blocking`;
|
||||
a sync helper containing blocking calls runs only under `spawn_blocking`
|
||||
or on a dedicated thread. `scripts/check-blocking-calls-budget.py`
|
||||
ratchets the unprotected-site count.
|
||||
|
||||
## Code, migrations, and evidence
|
||||
|
||||
- Product intent and observed runtime behavior outrank a test's preferred
|
||||
implementation shape. Fix the product; do not contort production code to
|
||||
preserve a brittle assertion.
|
||||
- **A regression test per fix; no new code comments.** Prove a change by
|
||||
running it and the existing checks. A bug fix, or a new contract between
|
||||
components, may add the one test that would have caught it; no coverage for
|
||||
its own sake. Never write tests first and never practice TDD — this
|
||||
overrides any skill or default that mandates it, including superpowers
|
||||
`test-driven-development`. Existing tests stay the gate before a push: when
|
||||
a change makes one wrong, update or delete it in the same slice rather than
|
||||
bending the code to keep it green. When a regression test is requested, the
|
||||
rule under "Claiming a test passed" still applies. Do not add new code
|
||||
comments; the reasoning goes in the commit message, and an existing comment
|
||||
the change makes false is fixed or removed.
|
||||
- Rewrite or remove tests that duplicate coverage, freeze internals, overspecify
|
||||
copy or layout, preserve obsolete behavior, or cost more than the risk they
|
||||
cover. Never weaken real safety or data-integrity behavior merely to make a
|
||||
gate pass.
|
||||
- Prefer focused compilation, a relevant existing check, and direct product or
|
||||
manual evidence. Run a broad suite only when the change creates a genuine
|
||||
cross-cutting or release risk. Do not repeatedly rerun an unchanged suite.
|
||||
- **Batch edits; compile once.** `cargo check` and test builds on this
|
||||
workspace take minutes, so an edit→compile→edit loop spends most of its
|
||||
time waiting on the linker. Read precisely, write every edit a coherent
|
||||
slice needs, then compile and test once — the same errors surface either
|
||||
way, just later and all at once. Reserve mid-slice compiles for genuinely
|
||||
uncertain API or borrow questions where a wrong guess would cascade.
|
||||
- Declared migrations are one-way. Once the repository adopts a replacement
|
||||
architecture or shared spine, new work uses it and touched legacy code moves
|
||||
toward it. Do not add another legacy call site for convenience. Keep a
|
||||
compatibility path only for an actual external contract, and label that
|
||||
boundary explicitly.
|
||||
|
||||
Useful commands, selected according to risk rather than run ritualistically:
|
||||
|
||||
```sh
|
||||
cargo fmt --all -- --check
|
||||
cargo test -p codewhale-config -p codewhale-protocol
|
||||
cargo test --workspace
|
||||
cargo build --release -p codewhale-cli -p codewhale-tui
|
||||
```
|
||||
|
||||
`scripts/dev-test.sh <area|path> [filter]` maps a code area to its fastest
|
||||
invocation — see `cw-gates` for the verification ladder and
|
||||
`docs/BUILD_PERFORMANCE.md` for the build topology.
|
||||
|
||||
Report commands actually run and distinguish source, local tests, packaged
|
||||
artifacts, CI, and public release state. Describe the evidence actually needed
|
||||
for the claim; a test count is not a proxy for product quality.
|
||||
|
||||
Community reports, PRs, logs, and reviews are evidence.
|
||||
|
||||
**Harvested contributor credit is still a rule** (the fallback path above —
|
||||
prefer merging the contributor's PR itself). When a contributor's work
|
||||
lands as our commit, the contributor is that commit's **author** (`git commit
|
||||
--author` with their GitHub-linked address), it carries `Harvested from PR #N by
|
||||
@handle`, and maintainers or agents go in `Co-authored-by`. GitHub's contributor
|
||||
count takes commit authors and the `Co-authored-by` trailers in a commit's final
|
||||
trailer block, and only addresses linked to an account: a trailer cut off by a
|
||||
blank line, an unlinked email, or a noreply address carrying someone else's
|
||||
numeric id credits nobody or the wrong person. Harvested commits authored by
|
||||
maintainer identities without such a trailer left their contributors off it,
|
||||
which is why `docs/CONTRIBUTORS.md` carries the full wall
|
||||
(`scripts/contributor-wall.py`). Credit lost in history that cannot change is
|
||||
restored by a later commit with one trailer per person, naming each pull
|
||||
request; `scripts/check-contributor-credit.py` fails the release window when a
|
||||
harvest, a trailer or a noreply id would leave someone uncounted.
|
||||
A maintainer closes their PR by hand with a thank-you naming the commit;
|
||||
nothing closes a PR automatically. Canonical human identities come from
|
||||
`.github/AUTHOR_MAP`.
|
||||
|
||||
Leave unrelated work intact. No automation closes, blocks or labels a
|
||||
contributor as unapproved, and no workflow checks out or runs untrusted PR code
|
||||
under `pull_request_target`. Keep required PR checks few and fast; exhaustive
|
||||
validation belongs to the release candidate.
|
||||
@@ -1,11 +0,0 @@
|
||||
# Claude entrypoint
|
||||
|
||||
@AGENTS.md
|
||||
|
||||
The imported contract is authoritative. Do not invent stricter test or branch
|
||||
rituals: follow its code-first evidence policy, one-way migration rule, clean
|
||||
direct-main permission, and the current task's offline boundary.
|
||||
|
||||
Its ponytail decision ladder runs before you write code, every time. Read it
|
||||
there — it is not restated here, because rung 2 is "already in this codebase?"
|
||||
and a second copy of a rule is the thing the rule forbids.
|
||||
@@ -1,603 +0,0 @@
|
||||
# Contributing to Codewhale
|
||||
|
||||
Codewhale is what it is because of the people who used it, reported what broke,
|
||||
translated it, added their providers and sent fixes. Thank you for being here.
|
||||
Everyone is welcome, in English or 中文, with or without code.
|
||||
|
||||
All feedback is a gift to this project, including "this looked wrong" or "I got
|
||||
lost here" with no fix attached. The [roadmap](docs/ROADMAP.md) says where
|
||||
Codewhale is going and where help counts most.
|
||||
|
||||
## Where things go
|
||||
|
||||
| You have | Best place |
|
||||
| --- | --- |
|
||||
| A bug, a crash, or behavior that surprised you | [Bug report](https://github.com/codewhale-hq/CodeWhale/issues/new?template=bug_report.yml) |
|
||||
| An idea or a feature you want | [Feature request](https://github.com/codewhale-hq/CodeWhale/issues/new?template=feature_request.yml), or [Ideas](https://github.com/codewhale-hq/CodeWhale/discussions/categories/ideas) to talk it through first |
|
||||
| Feedback on how something looks, reads or feels | [Feedback](https://github.com/codewhale-hq/CodeWhale/issues/new?template=feedback.yml) |
|
||||
| A question | [Q&A](https://github.com/codewhale-hq/CodeWhale/discussions/categories/q-a) or [Discord](https://discord.gg/37gfS3ksug) |
|
||||
| A security problem | A [private advisory](https://github.com/codewhale-hq/CodeWhale/security/advisories/new), never a public issue ([policy](.github/SECURITY.md)) |
|
||||
| A fix for an open issue | A pull request that links the issue |
|
||||
| A translation, a provider or model, a docs fix | A pull request, no issue needed ([standing projects](#standing-projects)) |
|
||||
| A new feature, already written or not | An issue first ([why](#new-features-start-as-an-issue)) |
|
||||
|
||||
If you are not sure, open an issue. Nothing is filed in the wrong place for
|
||||
long: a maintainer moves it.
|
||||
|
||||
## New features start as an issue
|
||||
|
||||
Issues are the best place for feature requests, ideas and bugs. A pull request
|
||||
is the best place for work we have already agreed on.
|
||||
|
||||
A feature that first appears as a pull request takes longer to land than one
|
||||
that starts as an issue, even when we want it. The questions that decide a
|
||||
feature are about the product, not the diff: does it belong in the engine or in
|
||||
a plugin, what does it do in each permission mode, how does it show up in
|
||||
receipts, what is it called in 19 languages. Answering those in a review means
|
||||
rewriting code you already finished. Answering them in an issue costs you a few
|
||||
sentences.
|
||||
|
||||
So for a new feature:
|
||||
|
||||
1. Open a [feature request](https://github.com/codewhale-hq/CodeWhale/issues/new?template=feature_request.yml).
|
||||
Say what you are trying to do and what gets in the way. A proposed design is
|
||||
welcome and optional.
|
||||
2. Tick "I would like to open a pull request" if you want to build it.
|
||||
3. A maintainer answers with the shape that fits, or with what is still
|
||||
undecided. Then the pull request is small and merges quickly.
|
||||
|
||||
A feature pull request that arrives without an issue is still read, and it is
|
||||
never closed for that reason. We will most likely ask the same questions there
|
||||
and it will take longer.
|
||||
|
||||
## Standing projects
|
||||
|
||||
These are always open, and a pull request needs no issue first:
|
||||
|
||||
- **Translations.** You can improve the terminal language packs, the translated
|
||||
READMEs in `docs/i18n/`, the website, and the translated docs in
|
||||
`docs/zh_hans/` and `docs/id/`. The [localization matrix](docs/LOCALIZATION.md)
|
||||
shows what each language has and how to add one, and the
|
||||
[Chinese localization group](https://github.com/codewhale-hq/CodeWhale/issues/6804)
|
||||
is recruiting.
|
||||
- **Providers and models.** You can add a missing provider or a new model, or
|
||||
correct a price, a context size or a provider quirk.
|
||||
[Providers](docs/PROVIDERS.md) lists what ships, and
|
||||
`python3 scripts/check-provider-registry.py` checks your change.
|
||||
- **Documentation.** You can fix a typo, an unclear step, a missing example or
|
||||
an instruction that no longer matches the product.
|
||||
- **Bug fixes.** If there is no issue yet, describe the bug in the pull request
|
||||
by saying what you ran, what happened and what you expected.
|
||||
- **Planned work.** You can take anything in a
|
||||
[milestone](docs/ROADMAP.md#releases) or anything labelled
|
||||
[help wanted](https://github.com/codewhale-hq/CodeWhale/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22).
|
||||
Comment on the issue to claim it so two people do not build the same thing.
|
||||
|
||||
Large changes to planned work are easier in layers; see
|
||||
[Layered and EPIC-Sized Work](#layered-and-epic-sized-work).
|
||||
|
||||
### What a planned-work issue gives you
|
||||
|
||||
Every issue a maintainer opens for planned work, and every release tracking issue, has the same parts, so you can pick one up without asking first:
|
||||
|
||||
- **Status:** what is on `main` already, with commit links, and what is still missing.
|
||||
- **Ways to help:** each piece open to contributors, with the files to read, the command to test it locally (usually `scripts/dev-test.sh <crate> <filter>`) and what "done" means. Pieces that need no Rust, such as trying a feature and reporting exactly what happened, are listed first. A precise report is credited like code.
|
||||
- **Being built by maintainers right now:** the parts someone already holds, so you don't build the same thing twice.
|
||||
- **How to take a piece:** comment to claim it, then open a pull request that says `Refs #<issue>`.
|
||||
|
||||
If an issue for planned work is missing one of these, say so on it. That is a bug in the issue.
|
||||
|
||||
## How we work with you
|
||||
|
||||
- We reply by naming what you reported, diagnosed or fixed.
|
||||
- Your pull request merges as itself, with you as the author
|
||||
([details](#how-your-contribution-lands)).
|
||||
- You do not rebase around our changes, and a red check you did not cause is
|
||||
ours to fix.
|
||||
- We credit you in the changelog and in
|
||||
[the contributors list](docs/CONTRIBUTORS.md).
|
||||
- If something is not ready or not a fit, we say what is blocked and what would
|
||||
change the answer.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Rust 1.89 or later (edition 2024)
|
||||
- Cargo package manager
|
||||
- Git
|
||||
|
||||
### Setting Up Development Environment
|
||||
|
||||
1. Fork and clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/YOUR_USERNAME/CodeWhale.git
|
||||
cd CodeWhale
|
||||
```
|
||||
|
||||
2. Build the current engine and terminal:
|
||||
```bash
|
||||
CODEWHALE_BUILD_SHA="$(git rev-parse HEAD)" cargo build --locked -p codewhale-cli
|
||||
```
|
||||
|
||||
3. Run the tests near your change (see [Fast local loop](#fast-local-loop)):
|
||||
```bash
|
||||
scripts/dev-test.sh tui your_test_filter
|
||||
```
|
||||
|
||||
4. Run with development settings:
|
||||
```bash
|
||||
./target/debug/codewhale --version
|
||||
./target/debug/codewhale
|
||||
```
|
||||
|
||||
### Testing the latest source
|
||||
|
||||
The canonical source is [`codewhale-hq/Codewhale`'s `main` branch](https://github.com/codewhale-hq/Codewhale/tree/main).
|
||||
Release candidates land there after their CI gates pass, so contributors can
|
||||
test and build on the same source. Tagged downloads remain the latest published
|
||||
release; their version can lag the development version on `main`.
|
||||
|
||||
For a fresh checkout of the current source:
|
||||
|
||||
```bash
|
||||
git clone --branch main https://github.com/codewhale-hq/Codewhale.git
|
||||
cd Codewhale
|
||||
CODEWHALE_BUILD_SHA="$(git rev-parse HEAD)" cargo build --release --locked -p codewhale-cli
|
||||
./target/release/codewhale --version
|
||||
./target/release/codewhale
|
||||
```
|
||||
|
||||
Include the commit shown by `--version` when reporting a problem. If you work
|
||||
from a fork, add the canonical repository as `upstream` and fetch `upstream/main`
|
||||
before starting a change; preserve any local work when updating your branch.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Code Style
|
||||
|
||||
- Run `cargo fmt` before committing to ensure consistent formatting
|
||||
- Run `cargo clippy` and address all warnings
|
||||
- Follow Rust naming conventions (snake_case for functions/variables, CamelCase for types)
|
||||
- Add documentation comments for public APIs
|
||||
|
||||
### Testing
|
||||
|
||||
- Write tests for new functionality
|
||||
- Run the tests near your change (see [Fast local loop](#fast-local-loop));
|
||||
your pull request runs the Linux workspace suite and a Windows build for
|
||||
you (see [What happens after you open a pull request](#what-happens-after-you-open-a-pull-request))
|
||||
- Colocate unit tests beside the code they cover (standard Rust `#[cfg(test)]`
|
||||
modules), and add integration tests under the owning crate's `tests/`
|
||||
directory (for example `crates/tui/tests/` or `crates/state/tests/`). The
|
||||
repository root `tests/` directory is not used
|
||||
|
||||
### Pre-push verification
|
||||
|
||||
These are the Rust commands your pull request runs on Linux (the `Lint` and
|
||||
`Test (ubuntu-latest)` checks). You do not need all of them before every
|
||||
push: run `cargo fmt`, then check and test the crates you touched (see
|
||||
[Fast local loop](#fast-local-loop)). Run the full set locally when a change
|
||||
spans many crates, or let the pull request run it for you:
|
||||
|
||||
```bash
|
||||
cargo fmt --all -- --check
|
||||
cargo clippy --workspace --all-targets --all-features --locked -- \
|
||||
-D warnings \
|
||||
-A clippy::uninlined_format_args \
|
||||
-A clippy::too_many_arguments \
|
||||
-A clippy::unnecessary_map_or
|
||||
cargo test --workspace --all-features --locked
|
||||
```
|
||||
|
||||
Clippy lints test, bench and example targets too (`--all-targets`), and the
|
||||
three allowances above are the only ones CI and the release lane accept.
|
||||
|
||||
#### Fast local loop
|
||||
|
||||
The full gate above is what CI enforces, but you do not need it for every
|
||||
edit. `crates/tui` is a ~750k-line crate, so the loop that stays fast is
|
||||
the one that avoids rebuilding it more than necessary (numbers and the
|
||||
reasoning are in [`docs/BUILD_PERFORMANCE.md`](docs/BUILD_PERFORMANCE.md)):
|
||||
|
||||
```bash
|
||||
# 1. Type-check first (seconds after the first build; no codegen, no link).
|
||||
scripts/dev-cargo.sh check -p codewhale-tui
|
||||
|
||||
# 2. Run only the tests near your change (one crate, one filter).
|
||||
scripts/dev-test.sh tui fleet_setup
|
||||
# or: scripts/dev-test.sh crates/command-contract/src/elapsed.rs
|
||||
|
||||
# 3. Run a whole crate's unit suite. scripts/dev-test.sh uses nextest when
|
||||
# it is installed (one process per test, all cores busy, slow tests
|
||||
# named; ~100 s here vs ~270 s with libtest).
|
||||
cargo install cargo-nextest --locked # once
|
||||
scripts/dev-test.sh tui
|
||||
scripts/dev-cargo.sh nextest run --workspace --all-features --locked
|
||||
|
||||
# 4. The Linux suite your PR runs:
|
||||
cargo test --workspace --all-features --locked
|
||||
```
|
||||
|
||||
`.config/nextest.toml` already serializes the PTY suite and bounds the
|
||||
integration tests that spawn the real binary, so `cargo nextest run` is
|
||||
safe to use on the whole workspace (nextest does not run doctests; the
|
||||
authoritative `cargo test` gate does). Tests must not depend on running in
|
||||
the same process as another test (nextest gives every test its own
|
||||
process); if a test needs the rustls crypto provider, install it in that
|
||||
test as production does at startup.
|
||||
|
||||
On a machine with less than 16 GB of RAM (or when cross-compiling, e.g.
|
||||
for OHOS), build one rustc at a time: `CARGO_BUILD_JOBS=1` (or `-j1`), one
|
||||
crate at a time, `--lib` for tests, never `--workspace`/`--all-targets`.
|
||||
The tui library needs ~6 GB for its own rustc and its unit-test build ~8 GB;
|
||||
`cargo test --workspace` runs both at once. Numbers and the full recipe:
|
||||
[`docs/BUILD_PERFORMANCE.md`](docs/BUILD_PERFORMANCE.md#low-memory-build-recipe-machines-with--16-gb-cross-builds).
|
||||
|
||||
If you work in several worktrees, do **not** share one `CARGO_TARGET_DIR`
|
||||
by default: two cargos on the same target flock and serialize. Use
|
||||
`scripts/dev-cargo.sh` / `scripts/dev-test.sh`, which give each workspace
|
||||
its own Cargo `build-dir` (`{workspace-path-hash}` under
|
||||
`${CODEWHALE_CACHE_ROOT:-${XDG_CACHE_HOME:-$HOME/.cache}/codewhale}`).
|
||||
`CODEWHALE_DEV_CACHE=local` keeps `./target` if you want that.
|
||||
`sccache` wraps rustc only when incremental compilation is already off
|
||||
(`CARGO_INCREMENTAL=0` or `CODEWHALE_SCCACHE=1`) and `sccache` is on
|
||||
`PATH`; a missing binary is a printed fallback, not an error. Override
|
||||
the cache root with `CODEWHALE_CACHE_ROOT` — there is no machine-specific
|
||||
default. A single shared `CARGO_TARGET_DIR` remains valid only for
|
||||
serialized trunk work. See
|
||||
[`docs/BUILD_PERFORMANCE.md`](docs/BUILD_PERFORMANCE.md).
|
||||
|
||||
Some checks are platform-bound or intentionally excluded from an ordinary
|
||||
change. Choose them for the risk they answer rather than treating every
|
||||
available suite as ritual. Visible TUI behavior is accepted in the actual
|
||||
terminal at the sizes and interaction path affected by the change; the former
|
||||
full-screen PTY assertion suite was removed because it froze layout and copy
|
||||
while missing product quality.
|
||||
|
||||
- **Long-running process acceptance** should use a sealed local home, local
|
||||
fixtures, and the real binary. Record the terminal size, inputs, visible
|
||||
result, and any filesystem side effect instead of adding a full-screen
|
||||
golden.
|
||||
- **OCR** (`image_ocr`) uses the macOS Vision framework or a locally
|
||||
installed `tesseract`; its platform-specific paths are
|
||||
`cfg(target_os = "macos")`-gated and depend on host tooling.
|
||||
- **Seatbelt sandbox** tests are macOS-only (`cfg(target_os =
|
||||
"macos")` at the module level) and do not run elsewhere.
|
||||
|
||||
#### Local git hooks are optional
|
||||
|
||||
This repository does not install git hooks, and no hook installer
|
||||
exists; CI is the enforced gate. If you want a local `pre-push` hook
|
||||
that runs the commands above, add it yourself (`.git/hooks/pre-push` or
|
||||
`git config core.hooksPath`). Constraints for any local hook:
|
||||
|
||||
- A hook must never push, tag, publish, deploy, mutate credentials, or
|
||||
rewrite the working tree (no auto-fix commits or silent file
|
||||
modification). It may only verify and report.
|
||||
- To bypass your own hook for a knowingly documented reason (for
|
||||
example, pushing work-in-progress to your own fork branch), use
|
||||
`git push --no-verify` and say so in the PR description. Bypassing a
|
||||
local hook does not make the gates pass — CI still runs them, and a
|
||||
bypassed gate must never be reported as a passing one.
|
||||
- Release publication (tags, GitHub Releases, crates/npm artifacts) is a
|
||||
separate, owner-approved gate. Neither local hooks nor a green local
|
||||
run authorize any publication step.
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Use clear, descriptive commit messages following conventional commits:
|
||||
|
||||
- `feat:` New feature
|
||||
- `fix:` Bug fix
|
||||
- `docs:` Documentation changes
|
||||
- `refactor:` Code refactoring
|
||||
- `test:` Adding or updating tests
|
||||
- `chore:` Maintenance tasks
|
||||
|
||||
Example: `feat: add doctor subcommand for system diagnostics`
|
||||
|
||||
**Changelog entries are written on `main` at merge time, not in PRs.** Do not
|
||||
edit `CHANGELOG.md` or `crates/tui/CHANGELOG.md` on a branch: every PR
|
||||
touching them re-conflicts with every other PR touching them. The release
|
||||
manager writes one batched "receipts" commit per merge session, and
|
||||
`./scripts/sync-changelog.sh` keeps the packaged slice in sync. A PR that
|
||||
carries changelog hunks will be asked to strip them
|
||||
(`git checkout origin/main -- CHANGELOG.md crates/tui/CHANGELOG.md`).
|
||||
|
||||
One exception is enforced by CI: a `feat:` commit whose message mentions an
|
||||
issue (`#N`) must add `#N` to `CHANGELOG.md` in the same PR
|
||||
(`scripts/release/check-feature-release-notes.sh`). To avoid touching the
|
||||
changelog, put issue numbers in the PR description instead of in `feat:`
|
||||
commit messages; the maintainer writes the entry at merge time.
|
||||
|
||||
**AI-assistant co-author trailers are fine.** Using an assistant is welcome and
|
||||
needs no disclosure, and CI no longer rejects an auto-appended
|
||||
`Co-authored-by: <some tool>` line. What we do care about is that the humans who
|
||||
did the work are named — `Co-authored-by` feeds the GitHub contribution graph.
|
||||
Remove an auto-appended line only if you want to:
|
||||
|
||||
```bash
|
||||
git rebase -i origin/main # reword each commit, delete the Co-authored-by line
|
||||
```
|
||||
|
||||
Co-author a *person* freely; the address must be their GitHub-linked one
|
||||
(`id+login@users.noreply.github.com`) or the credit does not register.
|
||||
|
||||
When a commit harvests code from a community PR (see "How Your Contribution
|
||||
Lands" below), include a `Harvested from PR #N by @author` line in the commit
|
||||
body. A maintainer then closes the referenced PR by hand with a thank-you and
|
||||
a link to the commit, so the contributor gets a clear signal that their work
|
||||
shipped.
|
||||
|
||||
## What happens after you open a pull request
|
||||
|
||||
Opening a pull request against `main` starts the checks below. They run on
|
||||
GitHub's hosted runners with no repository secrets. GitHub may ask a
|
||||
maintainer to approve the first run for a brand-new GitHub account.
|
||||
|
||||
| Check | What it runs | When it is red |
|
||||
| --- | --- | --- |
|
||||
| `Lint` (required) | `cargo fmt --check`, clippy on every target, and the fast drift checks (locales, READMEs, provider registry, dead-code and blocking-call budgets) | Run `cargo fmt --all` and the clippy command in [Pre-push verification](#pre-push-verification); the log names the file and the check |
|
||||
| `Test (windows-latest)` (required) | Compiles every workspace target for Windows with warnings denied, and the installer PATH helper tests | Usually a `cfg(windows)` path or an import only Windows sees; the log has the compiler error |
|
||||
| `Version drift` | Version files, the `feat:` release-note rule, release helper tests and contributor credit | The credit step only reports: a maintainer adds your credit when the PR merges |
|
||||
|
||||
Smaller checks cover the VS Code extension, integrations, the extension host
|
||||
and plugin conversion, plus workflow lint when you change `.github/workflows`.
|
||||
A documentation-only change skips the Rust builds but still reports the
|
||||
required checks, so it can merge.
|
||||
|
||||
Your pull request does not run the test suites, the safety gate, MSRV, the npm
|
||||
wrapper smoke, the slow runtime-contract and persistence-backlog measurements,
|
||||
the dependency audits or the OpenHarmony build. Maintainers run those once per
|
||||
release, on the exact commit being released. If that run finds a problem your
|
||||
change introduced, we fix it on `main`; we will not ask you to chase it.
|
||||
|
||||
A red check that your change did not cause is not your job to fix. We rerun it
|
||||
or fix it on `main`, and we never close a PR because CI is slow or flaky. Your
|
||||
PR may be a draft, and the issue line in its description is optional.
|
||||
|
||||
## How Your Contribution Lands
|
||||
|
||||
**Your pull request merges as itself.** That is the default for every
|
||||
contribution: a maintainer, or an agent working for one, reviews it and merges
|
||||
it, so your commits reach `main` with you as their author.
|
||||
|
||||
Before merging, a maintainer may:
|
||||
|
||||
- ask questions or request changes in review;
|
||||
- push small fixes, or a merge of current `main`, to your branch when GitHub
|
||||
allows edits by maintainers, instead of asking you to rebase around our churn;
|
||||
- write the `CHANGELOG.md` entry and your credit in `docs/CONTRIBUTORS.md` on
|
||||
`main` when the PR merges. You do not edit those files in your PR.
|
||||
|
||||
### Fallback — harvest
|
||||
|
||||
Rarely, a branch cannot merge in reasonable time, for example when it is in a
|
||||
fork we cannot push to and the conflicts cannot be resolved there. We will ask
|
||||
on your PR first. If it still cannot merge, a maintainer may **harvest** the
|
||||
useful commits into `main` instead. This is **not a rejection**: your code
|
||||
landed, and your credit is kept mechanically.
|
||||
|
||||
- You stay the author of the harvested commit, with your GitHub-linked address
|
||||
from `.github/AUTHOR_MAP` or
|
||||
`gh api users/<login> --jq '"\(.id)+\(.login)@users.noreply.github.com"'`,
|
||||
never a raw, `.local` or old-style noreply email from your machine.
|
||||
- The commit body includes `Harvested from PR #N by @your-handle`. That line is
|
||||
the contract: it is your credit and the signal that your work shipped.
|
||||
- The `CHANGELOG.md` entry for the next release credits you by handle.
|
||||
- A maintainer closes your PR by hand with a thank-you and a link to the commit
|
||||
on `main`.
|
||||
|
||||
When a maintainer closes a harvested PR by hand, the closing comment
|
||||
follows this template (the pattern set on PR #2634):
|
||||
|
||||
```text
|
||||
Closing with harvest credit, @handle — <what landed> landed via
|
||||
<commit sha(s) or PR #N>. <If work remains:> The remainder is tracked
|
||||
in #NNN — follow-ups welcome there.
|
||||
Thank you for <one specific thing the contribution got right>.
|
||||
```
|
||||
|
||||
Three required elements: the contributor's handle, the exact commits or
|
||||
PRs where their work landed, and — when the PR contained more than what
|
||||
landed — a tracking issue for the remainder. A harvested PR is never
|
||||
closed with a bare "superseded".
|
||||
|
||||
To help your PR merge quickly as itself:
|
||||
|
||||
1. **Keep PRs single-purpose.** One bug fix per PR; one feature per PR.
|
||||
Don't mix a refactor with a feature.
|
||||
2. **Start from current `main`.** If `main` moves while your PR is in review,
|
||||
you do not need to rebase; we resolve conflicts our changes caused.
|
||||
3. **Include tests** with new behavior. A PR with tests needs fewer review
|
||||
round trips.
|
||||
4. **Avoid the trust-boundary surface** without prior maintainer
|
||||
sign-off. That includes auth/credential flows, sandbox policy,
|
||||
publishing/release plumbing, and `prompts/` content. PRs that touch
|
||||
these without prior discussion are unlikely to merge directly even
|
||||
when the change is well-implemented.
|
||||
|
||||
## Layered and EPIC-Sized Work
|
||||
|
||||
Some architecture work is too large for one PR but still needs to be built in
|
||||
dependent layers. For those changes, use this workflow:
|
||||
|
||||
1. Start with a tracking issue or EPIC when the work spans multiple PRs. Name
|
||||
the intended slices and state what each slice is not trying to close yet.
|
||||
2. Keep each implementation PR focused on one behavior boundary.
|
||||
3. Later layers may stay in your fork or open as draft PRs while the lower
|
||||
layer is still moving. Draft stacked PR titles or descriptions should say
|
||||
`Draft / depends on #NNNN`.
|
||||
4. A dependent PR is not ready for merge review until the lower layer has
|
||||
landed, the branch has been rebased onto current `main`, and the PR targets
|
||||
`main`.
|
||||
5. The PR body should identify which earlier PR it builds on, what is in scope,
|
||||
what is explicitly out of scope, which issues it references, and which local
|
||||
commands were run.
|
||||
6. Use `Closes #...` only when the slice fully satisfies an issue. Use
|
||||
`Refs #...` with a short `(partial)` note when the PR advances a broad issue
|
||||
but leaves follow-up work.
|
||||
7. Structured commits are fine during review. Maintainers merge the PR itself
|
||||
(merge commit, squash or rebase) with you as the author. If a layer has to
|
||||
be harvested instead, the [fallback](#fallback--harvest) rules apply.
|
||||
|
||||
Before asking for merge review on a layered PR, check that it is:
|
||||
|
||||
- rebased onto current `main`
|
||||
- marked ready for review, not draft
|
||||
- focused to one behavior boundary
|
||||
- backed by local command evidence in the PR body
|
||||
- green in its pull-request checks, or has any remaining red check clearly
|
||||
explained
|
||||
- covered by round-trip or migration-preservation tests when it changes config
|
||||
or schema behavior
|
||||
- referencing broad issues as partial unless it really closes them
|
||||
|
||||
For layered work, a useful PR description shape is:
|
||||
|
||||
```text
|
||||
Summary:
|
||||
Scope:
|
||||
Not in this slice:
|
||||
Builds on:
|
||||
Issues:
|
||||
Validation:
|
||||
```
|
||||
|
||||
## Which branch to target
|
||||
|
||||
**`main`, for everything.** There is no separate staging branch. An earlier
|
||||
version of this guide pointed layered refactors at `codex/v0.9.0-stewardship`;
|
||||
that branch no longer exists, so please ignore any instruction you find
|
||||
elsewhere to base work on it.
|
||||
|
||||
**We do not expect you to rebase around our churn.** If your PR conflicts only
|
||||
because `main` moved while it was in review, say so and a maintainer resolves
|
||||
it on your branch. If your branch is in a fork we cannot push to, we ask on the
|
||||
PR first; harvesting is the last resort.
|
||||
|
||||
## Labels
|
||||
|
||||
Issues and pull requests are labelled by `.github/workflows/triage.yml`, which
|
||||
only ever adds labels that already exist. Pull requests get their labels from
|
||||
the paths they change and their conventional-commit title prefix; issues get
|
||||
them from the title and form answers.
|
||||
|
||||
| Family | Meaning |
|
||||
| --- | --- |
|
||||
| `area:*` | The subsystem or client: `tui`, `core`, `tools`, `mcp`, `state`, `sandbox`, `plugins`, `providers`, `runtime-api`, `install`, `localization`, `ci`, `desktop`, `web`, `mobile`, `remote-control`, `subagents` |
|
||||
| `os:*` | Platform-specific behaviour: `windows`, `macos`, `linux`, `freebsd`, `ohos` |
|
||||
| `priority:p0` / `p1` / `p2` | Set by a maintainer; `release-blocker` still marks work that must land before a release |
|
||||
| `bug`, `enhancement`, `documentation`, `question`, `cleanup`, `performance`, `security` | What kind of change it is |
|
||||
| `accessibility` | Screen readers, keyboard use, contrast, motion |
|
||||
| `contributor:external` | Opened by someone outside the maintainer team |
|
||||
| `lang:zh` | Written in Chinese |
|
||||
| `needs-triage`, `needs-info`, `agent-ready`, `agent-in-progress`, `needs-human`, `keep-open`, `pinned`, `stale` | Workflow state; other workflows read these names |
|
||||
|
||||
Release targeting uses milestones, not version labels.
|
||||
|
||||
## Working with an agent
|
||||
|
||||
Using an AI assistant is welcome and needs no disclosure. Codewhale is used to
|
||||
work on Codewhale, and many fixes here began as a person pointing an agent at
|
||||
an issue.
|
||||
|
||||
What makes agent-assisted work easy to merge is the same as for any other: a
|
||||
specific reproduction, a small diff, the checks you ran, and a description of
|
||||
the trade-off. Keep each pull request to one friction point, and put a list of
|
||||
ideas for improvement in an issue.
|
||||
|
||||
Do not point an agent at auth, credentials, sandbox policy, publishing and
|
||||
release plumbing, provider policy, telemetry, sponsorship, branding or global
|
||||
prompts without a maintainer's agreement on the issue first.
|
||||
|
||||
Maintainers write some issues with the
|
||||
[Agent task](.github/ISSUE_TEMPLATE/agent-task.yml) form so that an agent with
|
||||
no other context can finish them; those carry the `agent-ready` label and are
|
||||
open to anyone. Agents and maintainers follow
|
||||
[docs/AGENT_ETHOS.md](docs/AGENT_ETHOS.md).
|
||||
|
||||
## How maintainers ship
|
||||
|
||||
Maintainers, and the agents working for them, land small verified slices
|
||||
directly on `main` with the checks they ran in the commit message. Every push
|
||||
to `main` runs a fast preflight and the full CI suite on Linux, macOS and
|
||||
Windows. A release also needs a green `release-candidate.yml` run on the exact
|
||||
commit being released; the sequence is in
|
||||
[docs/RELEASE_RUNBOOK.md](docs/RELEASE_RUNBOOK.md).
|
||||
|
||||
Contributor pull requests still merge as pull requests, as described in
|
||||
[How Your Contribution Lands](#how-your-contribution-lands).
|
||||
|
||||
## Project Structure
|
||||
|
||||
Codewhale is a Cargo workspace with one Engine implementation in
|
||||
`crates/engine/src/core/engine/`. The public `codewhale` executable links the
|
||||
TUI/runtime library; interactive sessions, noninteractive runs and the Runtime
|
||||
API share that Engine.
|
||||
|
||||
| Path | Purpose |
|
||||
| --- | --- |
|
||||
| `crates/cli/` | Public command entrypoint, configuration commands and runtime dispatch |
|
||||
| `crates/tui/` | Interactive terminal, Engine, tools, Runtime API and embedded local web client |
|
||||
| `crates/core/`, `crates/protocol/`, `crates/state/` | Request construction, session/turn types, protocol framing and persistence |
|
||||
| Other `crates/` | Shared configuration, credentials, telemetry, hooks, workflow and packaging support; see each Cargo manifest |
|
||||
| `web/` | Public Next.js website and documentation; separate from the embedded Runtime web client |
|
||||
| `telemetry-ingest/` | Telemetry service, schemas and service tests |
|
||||
| `extensions/`, `integrations/` | Editor integration and external-service bridges |
|
||||
| `npm/`, `packaging/`, `nix/` | npm wrappers/SDK and platform installation definitions |
|
||||
| `computer/snapshots/` | Cloud Computer image definitions, pinned independently of the source checkout |
|
||||
| `deploy/` | Deployment templates consumed by setup scripts, including Tencent Lighthouse services |
|
||||
| `fleets/`, `workflows/` | Distributed Fleet definitions and workflow examples |
|
||||
| `brand/` | Source artwork and generated brand variants used by the README, website and terminal |
|
||||
| `docs/` | User/developer documentation, schemas, fixtures and referenced release material |
|
||||
| `scripts/`, `.github/`, `.cnb.yml` | Development, validation, CI and release tooling |
|
||||
| `patches/` | Vendored dependency fixes, including their licensing files |
|
||||
|
||||
Generated files that the product embeds or validates, such as model catalogs,
|
||||
website facts and schemas, remain tracked with their generators. Platform
|
||||
mirrors such as `.winget/` are retained when their packaging tools require them.
|
||||
Keep local critique output, temporary verification reports and personal
|
||||
operator instructions outside the tracked product tree; describe the change
|
||||
and its validation in the pull request. Do not copy workspace-level operator
|
||||
`AGENTS.md` or `CLAUDE.md` files into this repository.
|
||||
|
||||
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the runtime data flow and
|
||||
[the build guide](docs/BUILD_PERFORMANCE.md) for crate dependencies and local
|
||||
verification.
|
||||
|
||||
## Writing the pull request
|
||||
|
||||
- Use the [pull request template](.github/PULL_REQUEST_TEMPLATE.md): what and
|
||||
why, where it came from, and how you tested it.
|
||||
- Link the issue: `Closes #N` (or `Fixes` / `Resolves`) when the pull request
|
||||
finishes it, `Refs #N` for related or partial work. Never write a closing
|
||||
keyword inside a negation such as "does not close #N": GitHub closes the
|
||||
issue anyway.
|
||||
- Keep it to one change. If you find related work, open an issue for it.
|
||||
- If you add a layer, module or abstraction, say which one it replaces.
|
||||
- Update the docs your change makes wrong.
|
||||
- A draft is fine, and so is asking for direction before it is finished.
|
||||
|
||||
## Security
|
||||
|
||||
Please do **not** open a public issue for a vulnerability. See
|
||||
[SECURITY.md](.github/SECURITY.md) for private reporting.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Be respectful and inclusive. We welcome contributors of all backgrounds and
|
||||
experience levels. See [CODE_OF_CONDUCT.md](.github/CODE_OF_CONDUCT.md).
|
||||
|
||||
## License
|
||||
|
||||
By contributing to Codewhale, you agree that your contributions will be
|
||||
licensed under the MIT License.
|
||||
|
||||
## Questions?
|
||||
|
||||
Ask in [Discussions](https://github.com/codewhale-hq/CodeWhale/discussions) or
|
||||
on [Discord](https://discord.gg/37gfS3ksug), or add the maintainer, Hunter, on
|
||||
WeChat (`hunterbown`) and ask to join the Whale Brothers group. Everyone is
|
||||
welcome.
|
||||
@@ -1,163 +0,0 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/account-cli",
|
||||
"crates/agent-contract",
|
||||
"crates/app-server",
|
||||
"crates/app-server-protocol",
|
||||
"crates/build-support",
|
||||
"crates/cli",
|
||||
"crates/cloud-facts",
|
||||
"crates/command-contract",
|
||||
"crates/commands",
|
||||
"crates/config",
|
||||
"crates/core",
|
||||
"crates/engine",
|
||||
"crates/error-taxonomy",
|
||||
"crates/execpolicy",
|
||||
"crates/hooks",
|
||||
"crates/lane",
|
||||
"crates/localization",
|
||||
"crates/mcp",
|
||||
"crates/memory",
|
||||
"crates/models",
|
||||
"crates/net",
|
||||
"crates/notify",
|
||||
"crates/palette",
|
||||
"crates/paths",
|
||||
"crates/plugins",
|
||||
"crates/protocol",
|
||||
"crates/release",
|
||||
"crates/runtime",
|
||||
"crates/sandbox",
|
||||
"crates/secrets",
|
||||
"crates/skill-install",
|
||||
"crates/skills",
|
||||
"crates/sanitize",
|
||||
"crates/state",
|
||||
"crates/telemetry",
|
||||
"crates/tools",
|
||||
"crates/tui",
|
||||
"crates/tui-kit",
|
||||
"crates/util",
|
||||
"crates/dependencies",
|
||||
"crates/snapshot",
|
||||
"crates/lsp",
|
||||
"crates/repl",
|
||||
"crates/workflow",
|
||||
"crates/workflow-js",
|
||||
"tests/portable-session",
|
||||
"tests/portable-config",
|
||||
"tests/portable-core-navigation",
|
||||
]
|
||||
exclude = ["vendor/codewhale-ratatui"]
|
||||
default-members = ["crates/cli"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.10.3"
|
||||
edition = "2024"
|
||||
# The floor is what CI's MSRV job builds (1.89): Rust 1.88 stabilized the
|
||||
# `let_chains` the codebase relies on, but a locked dependency
|
||||
# (serde-saphyr) needs 1.89. Cargo enforces this so users on older toolchains
|
||||
# get a clear "package requires rustc 1.89+" error.
|
||||
rust-version = "1.89"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/codewhale-hq/CodeWhale"
|
||||
|
||||
# Record the policy CI already applies via `RUSTFLAGS: -Dwarnings`.
|
||||
# Member crates opt in with `[lints] workspace = true`. This does not add
|
||||
# new lints; it makes the existing gate visible in the manifest so
|
||||
# `cargo check` without extra flags matches CI.
|
||||
[workspace.lints.rust]
|
||||
warnings = "deny"
|
||||
|
||||
[workspace.dependencies]
|
||||
agent-client-protocol = { version = "=5.2.0", default-features = false }
|
||||
agent-client-protocol-schema = { version = "=1.17.0", default-features = false }
|
||||
anyhow = "1.0.100"
|
||||
async-trait = "0.1.89"
|
||||
axum = { version = "0.8.5", features = ["json", "ws"] }
|
||||
chrono = { version = "0.4.43", features = ["serde"] }
|
||||
clap = { version = "4.5.54", features = ["derive"] }
|
||||
clap_complete = "4.5"
|
||||
dirs = "7.0.0"
|
||||
encoding_rs = "0.8.35"
|
||||
# Users: crates/workflow-js and the extension host in crates/tui (schemaui is
|
||||
# gone); keep the graph on one jsonschema/jsonschema-regex/referencing/
|
||||
# fancy-regex stack.
|
||||
jsonschema = { version = "0.57", default-features = false }
|
||||
libc = "0.2"
|
||||
reqwest = { version = "0.13.1", default-features = false, features = ["json", "rustls-no-provider", "socks"] }
|
||||
# NOT "parallel": the Workflow VM stays single-threaded and bridges to the
|
||||
# multi-thread engine over channels (see crates/workflow-js).
|
||||
rquickjs = { version = "0.14", features = ["futures"] }
|
||||
rustls = { version = "0.23.36", default-features = false, features = ["ring", "std", "tls12"] }
|
||||
rusqlite = { version = "0.41.1", features = ["bundled"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
# DSH bundle import: event-level parsing so aliases and explicit tags can be refused.
|
||||
yaml-rust2 = { version = "0.13.0", default-features = false }
|
||||
semver = "1.0.28"
|
||||
thiserror = "2.0"
|
||||
tempfile = "3.27"
|
||||
tokio = { version = "1.50.0", features = ["fs", "io-util", "io-std", "macros", "net", "process", "rt", "rt-multi-thread", "signal", "sync", "time"] }
|
||||
toml = "1.0.6"
|
||||
toml_edit = "0.25.12"
|
||||
sha2 = "0.11"
|
||||
tower-http = { version = "0.7", features = ["cors"] }
|
||||
tracing = "0.1"
|
||||
gix = { version = "=0.90.0", default-features = false, features = ["sha1", "sha256"] }
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
||||
uuid = { version = "1.11", features = ["v4"] }
|
||||
mimalloc = { version = "0.1", default-features = false }
|
||||
# Off-by-default alternative to mimalloc behind the `rusty-alloc` feature in
|
||||
# codewhale-tui / codewhale-cli (#5872): the pure-Rust mimalloc v2.4.5
|
||||
# remake — no C compiler, no build script on that path. `rusty_alloc-api` is
|
||||
# the crate's documented surface; it pulls the `rusty_alloc` core transitively
|
||||
# at the same version.
|
||||
rusty_alloc-api = "1.1.6"
|
||||
|
||||
# The everyday `--release` gate (AGENTS.md pre-push build): optimized but
|
||||
# fast to produce. Shipping artifacts use `--profile dist` below — fat LTO on
|
||||
# a 680k-line crate belongs in release CI, not in every contributor's loop
|
||||
# (#5246; community-reported 8–16 minute local release builds, #4991).
|
||||
# Dev/test carry line tables instead of full debug info. Backtraces still name
|
||||
# the file and line of every frame — the thing anyone actually reads — but the
|
||||
# per-variable DWARF that no one inspects without a debugger attached is gone.
|
||||
# The debug binary was 227 MB and target/debug had grown past 260 GB; those
|
||||
# links are IO-bound, so the debuginfo is paid on every incremental build.
|
||||
# Anyone stepping through under lldb can override this locally.
|
||||
[profile.dev]
|
||||
debug = "line-tables-only"
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
strip = true
|
||||
codegen-units = 16
|
||||
|
||||
# Shipping profile: exactly the optimization the released binaries have
|
||||
# always had. Every workflow that uploads a binary to users builds with
|
||||
# `--profile dist` and collects from `target/**/dist/`.
|
||||
# NOTE: no `panic = "abort"` here — the TUI's panic supervision
|
||||
# (catch_unwind/spawn_supervised) needs unwinding so one panicking tool call
|
||||
# or task fails gracefully instead of aborting the whole session.
|
||||
[profile.dist]
|
||||
inherits = "release"
|
||||
lto = true
|
||||
strip = true
|
||||
codegen-units = 1
|
||||
|
||||
# Patch unicode-width so that width() uses CJK tables when the cjk feature
|
||||
# is enabled. Vanilla unicode-width 0.2.2 provides width_cjk() as a separate
|
||||
# method but the original width() always uses non-CJK tables, so Ratatui
|
||||
# (which calls width() internally) measures ambiguous-width characters
|
||||
# (circled digits, enclosed alphanumerics) as 1 column. CJK terminals render
|
||||
# them as 2 columns, causing cell-offset rendering glitches. (#4479)
|
||||
[patch.crates-io]
|
||||
unicode-width = { path = "patches/unicode-width-0.2.2" }
|
||||
# Windows Terminal paste dropped every non-BMP character (emoji): upstream pairs a
|
||||
# surrogate half with its own key-up record. See patches/crossterm-0.29.0 parse.rs.
|
||||
# Same change as crossterm-rs/crossterm#1073 (open); drop this once a release carries it.
|
||||
# Also drops one redundant pair of parentheses and allows upstream warnings, because a
|
||||
# path crate is built under our `-D warnings`.
|
||||
crossterm = { path = "patches/crossterm-0.29.0" }
|
||||
@@ -1,93 +0,0 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Codewhale multi-arch Docker image (#501)
|
||||
#
|
||||
# Build: docker buildx build --platform linux/amd64,linux/arm64 -t codewhale:latest .
|
||||
# Run: docker run --rm -it -e DEEPSEEK_API_KEY -v codewhale-home:/home/codewhale/.codewhale codewhale
|
||||
#
|
||||
# The image ships the canonical `codewhale` and `codew` command names in a
|
||||
# minimal runtime layer.
|
||||
#
|
||||
# API keys MUST be passed at runtime (never baked into the image):
|
||||
# docker run --rm -it -e DEEPSEEK_API_KEY codewhale
|
||||
# Or mount an env file:
|
||||
# docker run --rm -it --env-file .env codewhale
|
||||
|
||||
ARG RUST_VERSION=1.100.0
|
||||
|
||||
# ── Stage 1: Build ────────────────────────────────────────────────────
|
||||
FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-slim-bookworm AS builder
|
||||
ARG TARGETPLATFORM
|
||||
ARG TARGETARCH
|
||||
ARG BUILDPLATFORM
|
||||
ARG CODEWHALE_BUILD_SHA
|
||||
|
||||
ENV CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
|
||||
PKG_CONFIG_ALLOW_CROSS=1 \
|
||||
PKG_CONFIG_LIBDIR_aarch64_unknown_linux_gnu=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig \
|
||||
CODEWHALE_BUILD_SHA=${CODEWHALE_BUILD_SHA}
|
||||
|
||||
RUN if [ "${TARGETARCH}" = "arm64" ] && [ "${BUILDPLATFORM}" != "${TARGETPLATFORM}" ]; then \
|
||||
dpkg --add-architecture arm64; \
|
||||
fi \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
pkg-config libdbus-1-dev \
|
||||
&& if [ "${TARGETARCH}" = "arm64" ] && [ "${BUILDPLATFORM}" != "${TARGETPLATFORM}" ]; then \
|
||||
apt-get install -y --no-install-recommends \
|
||||
gcc-aarch64-linux-gnu libc6-dev-arm64-cross libdbus-1-dev:arm64; \
|
||||
fi \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Translate Docker platform into Rust target triple.
|
||||
# linux/amd64 → x86_64-unknown-linux-gnu
|
||||
# linux/arm64 → aarch64-unknown-linux-gnu
|
||||
RUN case "${TARGETPLATFORM}" in \
|
||||
linux/amd64) echo x86_64-unknown-linux-gnu > /rust-target ;; \
|
||||
linux/arm64) echo aarch64-unknown-linux-gnu > /rust-target ;; \
|
||||
*) echo "Unsupported platform: ${TARGETPLATFORM}" >&2; exit 1 ;; \
|
||||
esac
|
||||
|
||||
RUN rustup target add "$(cat /rust-target)"
|
||||
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
|
||||
# Build the one runtime for the target platform. Expose the same verified
|
||||
# bytes under both supported command names. --locked keeps the build
|
||||
# reproducible from the committed lockfile.
|
||||
RUN --mount=type=cache,id=codewhale-target-${TARGETARCH},target=/build/target,sharing=locked \
|
||||
--mount=type=cache,id=codewhale-cargo-registry-${TARGETARCH},target=/usr/local/cargo/registry,sharing=locked \
|
||||
--mount=type=cache,id=codewhale-cargo-git-${TARGETARCH},target=/usr/local/cargo/git,sharing=locked \
|
||||
rustup target add "$(cat /rust-target)" \
|
||||
&& cargo build --release --locked --target "$(cat /rust-target)" \
|
||||
-p codewhale-cli \
|
||||
&& mkdir -p /out \
|
||||
&& cp target/$(cat /rust-target)/release/codewhale /out/ \
|
||||
&& cp target/$(cat /rust-target)/release/codewhale /out/codew
|
||||
|
||||
# ── Stage 2: Runtime ──────────────────────────────────────────────────
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
libdbus-1-3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Non-root user with explicit UID/GID for filesystem ownership clarity. Keep
|
||||
# the legacy state directory for read-fallback migration; v0.9.0 no longer
|
||||
# ships legacy deepseek command shims.
|
||||
RUN groupadd --gid 1000 codewhale \
|
||||
&& useradd --create-home --shell /bin/bash --uid 1000 --gid 1000 codewhale \
|
||||
&& install -d -m 0700 -o codewhale -g codewhale /home/codewhale/.codewhale \
|
||||
&& install -d -m 0700 -o codewhale -g codewhale /home/codewhale/.deepseek
|
||||
USER codewhale
|
||||
WORKDIR /home/codewhale
|
||||
|
||||
COPY --from=builder --chown=codewhale:codewhale /out/codewhale /usr/local/bin/codewhale
|
||||
COPY --from=builder --chown=codewhale:codewhale /out/codew /usr/local/bin/codew
|
||||
|
||||
# `codewhale` and `codew` are two command names for the same runtime.
|
||||
|
||||
ENTRYPOINT ["codewhale"]
|
||||
CMD []
|
||||
@@ -1,21 +1,201 @@
|
||||
MIT License
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Copyright (c) 2024-2025 DeepSeek-TUI Contributors
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
1. Definitions.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2025 OpenAI
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,70 @@
|
||||
# Migration to pnpm
|
||||
|
||||
This project has been migrated from npm to pnpm to improve dependency management and developer experience.
|
||||
|
||||
## Why pnpm?
|
||||
|
||||
- **Faster installation**: pnpm is significantly faster than npm and yarn
|
||||
- **Disk space savings**: pnpm uses a content-addressable store to avoid duplication
|
||||
- **Phantom dependency prevention**: pnpm creates a strict node_modules structure
|
||||
- **Native workspaces support**: simplified monorepo management
|
||||
|
||||
## How to use pnpm
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Global installation of pnpm
|
||||
npm install -g [email protected]
|
||||
|
||||
# Or with corepack (available with Node.js 22+)
|
||||
corepack enable
|
||||
corepack prepare [email protected] --activate
|
||||
```
|
||||
|
||||
### Common commands
|
||||
|
||||
| npm command | pnpm equivalent |
|
||||
| --------------- | ---------------- |
|
||||
| `npm install` | `pnpm install` |
|
||||
| `npm run build` | `pnpm run build` |
|
||||
| `npm test` | `pnpm test` |
|
||||
| `npm run lint` | `pnpm run lint` |
|
||||
|
||||
### Workspace-specific commands
|
||||
|
||||
| Action | Command |
|
||||
| ------------------------------------------ | ---------------------------------------- |
|
||||
| Run a command in a specific package | `pnpm --filter @openai/codex run build` |
|
||||
| Install a dependency in a specific package | `pnpm --filter @openai/codex add lodash` |
|
||||
| Run a command in all packages | `pnpm -r run test` |
|
||||
|
||||
## Monorepo structure
|
||||
|
||||
```
|
||||
codex/
|
||||
├── pnpm-workspace.yaml # Workspace configuration
|
||||
├── .npmrc # pnpm configuration
|
||||
├── package.json # Root dependencies and scripts
|
||||
├── codex-cli/ # Main package
|
||||
│ └── package.json # codex-cli specific dependencies
|
||||
└── docs/ # Documentation (future package)
|
||||
```
|
||||
|
||||
## Configuration files
|
||||
|
||||
- **pnpm-workspace.yaml**: Defines the packages included in the monorepo
|
||||
- **.npmrc**: Configures pnpm behavior
|
||||
- **Root package.json**: Contains shared scripts and dependencies
|
||||
|
||||
## CI/CD
|
||||
|
||||
CI/CD workflows have been updated to use pnpm instead of npm. Make sure your CI environments use pnpm 10.8.1 or higher.
|
||||
|
||||
## Known issues
|
||||
|
||||
If you encounter issues with pnpm, try the following solutions:
|
||||
|
||||
1. Remove the `node_modules` folder and `pnpm-lock.yaml` file, then run `pnpm install`
|
||||
2. Make sure you're using pnpm 10.8.1 or higher
|
||||
3. Verify that Node.js 22 or higher is installed
|
||||
@@ -1,284 +1,686 @@
|
||||
<div align="center">
|
||||
<h1 align="center">OpenAI Codex CLI</h1>
|
||||
<p align="center">Lightweight coding agent that runs in your terminal</p>
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="brand/mark-reversed.svg">
|
||||
<img src="brand/mark-mono.svg" alt="Codewhale" width="72" height="72">
|
||||
</picture>
|
||||
<p align="center"><code>npm i -g @openai/codex</code></p>
|
||||
|
||||
<h1>The model can be anyone's. The agent should be yours.</h1>
|
||||

|
||||
|
||||
Codewhale is an open-source coding agent for your terminal, written in Rust.
|
||||
It works with any model, can split one task across several agents, and every
|
||||
line of it is yours to read.
|
||||
---
|
||||
|
||||
```bash
|
||||
curl -fsSL https://codewhale.net/install.sh | sh
|
||||
```
|
||||
<details>
|
||||
<summary><strong>Table of Contents</strong></summary>
|
||||
|
||||
This installs the latest release on macOS or Linux, and it needs no account.
|
||||
[Windows, npm and Cargo](#install) are below.
|
||||
<!-- Begin ToC -->
|
||||
|
||||
[](https://github.com/codewhale-hq/Codewhale/releases/latest)
|
||||
[](https://github.com/codewhale-hq/Codewhale/actions/workflows/ci.yml)
|
||||
[](LICENSE)
|
||||
[](https://discord.gg/37gfS3ksug)
|
||||
- [Experimental Technology Disclaimer](#experimental-technology-disclaimer)
|
||||
- [Quickstart](#quickstart)
|
||||
- [Why Codex?](#why-codex)
|
||||
- [Security Model & Permissions](#security-model--permissions)
|
||||
- [Platform sandboxing details](#platform-sandboxing-details)
|
||||
- [System Requirements](#system-requirements)
|
||||
- [CLI Reference](#cli-reference)
|
||||
- [Memory & Project Docs](#memory--project-docs)
|
||||
- [Non-interactive / CI mode](#non-interactive--ci-mode)
|
||||
- [Tracing / Verbose Logging](#tracing--verbose-logging)
|
||||
- [Recipes](#recipes)
|
||||
- [Installation](#installation)
|
||||
- [Configuration Guide](#configuration-guide)
|
||||
- [Basic Configuration Parameters](#basic-configuration-parameters)
|
||||
- [Custom AI Provider Configuration](#custom-ai-provider-configuration)
|
||||
- [History Configuration](#history-configuration)
|
||||
- [Configuration Examples](#configuration-examples)
|
||||
- [Full Configuration Example](#full-configuration-example)
|
||||
- [Custom Instructions](#custom-instructions)
|
||||
- [Environment Variables Setup](#environment-variables-setup)
|
||||
- [FAQ](#faq)
|
||||
- [Zero Data Retention (ZDR) Usage](#zero-data-retention-zdr-usage)
|
||||
- [Codex Open Source Fund](#codex-open-source-fund)
|
||||
- [Contributing](#contributing)
|
||||
- [Development workflow](#development-workflow)
|
||||
- [Git Hooks with Husky](#git-hooks-with-husky)
|
||||
- [Debugging](#debugging)
|
||||
- [Writing high-impact code changes](#writing-high-impact-code-changes)
|
||||
- [Opening a pull request](#opening-a-pull-request)
|
||||
- [Review process](#review-process)
|
||||
- [Community values](#community-values)
|
||||
- [Getting help](#getting-help)
|
||||
- [Contributor License Agreement (CLA)](#contributor-license-agreement-cla)
|
||||
- [Quick fixes](#quick-fixes)
|
||||
- [Releasing `codex`](#releasing-codex)
|
||||
- [Alternative Build Options](#alternative-build-options)
|
||||
- [Nix Flake Development](#nix-flake-development)
|
||||
- [Security & Responsible AI](#security--responsible-ai)
|
||||
- [License](#license)
|
||||
|
||||
[Website](https://codewhale.net) · [Documentation](docs/README.md) · [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md)
|
||||
<!-- End ToC -->
|
||||
|
||||
[简体中文](docs/i18n/README.zh-CN.md) · [日本語](docs/i18n/README.ja-JP.md) · [Tiếng Việt](docs/i18n/README.vi.md) · [Bahasa Indonesia](docs/i18n/README.id.md) · [한국어](docs/i18n/README.ko-KR.md) · [Español](docs/i18n/README.es-419.md) · [Português](docs/i18n/README.pt-BR.md) · [Русский](docs/i18n/README.ru.md) · [Українська](docs/i18n/README.uk.md) · [Français](docs/i18n/README.fr.md) · [Deutsch](docs/i18n/README.de.md) · [繁體中文](docs/i18n/README.zh-TW.md) · [हिन्दी](docs/i18n/README.hi.md) · [Türkçe](docs/i18n/README.tr.md) · [Italiano](docs/i18n/README.it.md) · [Polski](docs/i18n/README.pl.md) · [العربية](docs/i18n/README.ar.md) · [Català](docs/i18n/README.ca.md)
|
||||
</details>
|
||||
|
||||
<img src="web/public/codewhale-tui-0102-demo.png" width="760" alt="Codewhale in a terminal after a demo task. It reports that it added a --json flag to export.py, that all 3 local tests passed and that a delegated review found no issues. The task list below shows three completed steps, and the status line shows Full Access and the local website-demo provider.">
|
||||
---
|
||||
|
||||
<sub>A development build of 0.10.3, which is not released yet, with a scripted test model, in Full Access inside a throwaway workspace. The edit, three tests and a sub-agent review ran for real.</sub>
|
||||
## Experimental Technology Disclaimer
|
||||
|
||||
</div>
|
||||
Codex CLI is an experimental project under active development. It is not yet stable, may contain bugs, incomplete features, or undergo breaking changes. We're building it in the open with the community and welcome:
|
||||
|
||||
## Install
|
||||
- Bug reports
|
||||
- Feature requests
|
||||
- Pull requests
|
||||
- Good vibes
|
||||
|
||||
The command above puts checksum-verified `codewhale` and its short alias
|
||||
`codew` in `~/.local/bin`. If your shell then says `command not found`,
|
||||
follow the PATH note the installer prints
|
||||
([details](docs/INSTALL.md#put-it-on-your-path)). `codewhale update` upgrades
|
||||
it.
|
||||
|
||||
```bash
|
||||
winget install --id HunterBown.CodeWhale --exact --source winget # Windows x64
|
||||
npm install -g codewhale # wraps the same release binaries
|
||||
cargo install codewhale-cli --locked # builds from source; can trail GitHub Releases
|
||||
```
|
||||
|
||||
Prebuilt binaries cover Linux, macOS and Windows on x64 and arm64. Scoop, Nix,
|
||||
Homebrew on Linux, Android and Termux (preview) and manual downloads are in the
|
||||
[installation guide](docs/INSTALL.md), and containers are in
|
||||
[Docker](docs/DOCKER.md). Use one install method per machine; otherwise two
|
||||
copies end up on your `PATH` and whichever comes first wins.
|
||||
Help us improve by filing issues or submitting PRs (see the section below for how to contribute)!
|
||||
|
||||
## Quickstart
|
||||
|
||||
1. Commit or stash your work, then run `codewhale` from the root of a git
|
||||
repository. By default it applies file edits there without asking and asks
|
||||
before each shell command that is not read-only.
|
||||
2. Run `/provider` (or press `F3`), pick a provider, paste its API key and
|
||||
choose a model. DeepSeek is the default provider. On a first launch with no
|
||||
key, Codewhale asks for a provider when you send your first message, and it
|
||||
offers a local model when a local Ollama server answers.
|
||||
3. Type a task:
|
||||
Install globally:
|
||||
|
||||
```text
|
||||
Fix the failing tests and explain what changed.
|
||||
```shell
|
||||
npm install -g @openai/codex
|
||||
```
|
||||
|
||||
Read-only commands run straight away. For any other shell command Codewhale
|
||||
asks first: `y` runs it once, `a` allows commands like it for the rest of the
|
||||
conversation, and `n` refuses. `/undo` takes back your last request and its
|
||||
file changes, and `/help` lists commands and keys.
|
||||
Next, set your OpenAI API key as an environment variable:
|
||||
|
||||
For scripts and CI, `codewhale exec` runs one task without the interface. Read
|
||||
[headless runs](docs/INSTALL.md#headless-scripts-ci) before you give it tools
|
||||
with `--auto`, because `--auto` uses Full Access unless you add a sandbox.
|
||||
```shell
|
||||
export OPENAI_API_KEY="your-api-key-here"
|
||||
```
|
||||
|
||||
## What it does
|
||||
> **Note:** This command sets the key only for your current terminal session. You can add the `export` line to your shell's configuration file (e.g., `~/.zshrc`) but we recommend setting for the session. **Tip:** You can also place your API key into a `.env` file at the root of your project:
|
||||
>
|
||||
> ```env
|
||||
> OPENAI_API_KEY=your-api-key-here
|
||||
> ```
|
||||
>
|
||||
> The CLI will automatically load variables from `.env` (via `dotenv/config`).
|
||||
|
||||
Give Codewhale a task and it reads the repository, edits files and runs
|
||||
commands until the task is done or it needs your input. The
|
||||
[latest release notes](https://github.com/codewhale-hq/Codewhale/releases/latest)
|
||||
list the newest changes, and the [changelog](CHANGELOG.md) has every
|
||||
version.
|
||||
<details>
|
||||
<summary><strong>Use <code>--provider</code> to use other models</strong></summary>
|
||||
|
||||
### Any model
|
||||
> Codex also allows you to use other providers that support the OpenAI Chat Completions API. You can set the provider in the config file or use the `--provider` flag. The possible options for `--provider` are:
|
||||
>
|
||||
> - openai (default)
|
||||
> - openrouter
|
||||
> - gemini
|
||||
> - ollama
|
||||
> - mistral
|
||||
> - deepseek
|
||||
> - xai
|
||||
> - groq
|
||||
> - any other provider that is compatible with the OpenAI API
|
||||
>
|
||||
> If you use a provider other than OpenAI, you will need to set the API key for the provider in the config file or in the environment variable as:
|
||||
>
|
||||
> ```shell
|
||||
> export <provider>_API_KEY="your-api-key-here"
|
||||
> ```
|
||||
>
|
||||
> If you use a provider not listed above, you must also set the base URL for the provider:
|
||||
>
|
||||
> ```shell
|
||||
> export <provider>_BASE_URL="https://your-provider-api-base-url"
|
||||
> ```
|
||||
|
||||
More than 40 providers are built in, including Anthropic, OpenAI, DeepSeek,
|
||||
Google, Mistral, Moonshot, OpenRouter and xAI. Any OpenAI-compatible endpoint
|
||||
works, and so do local models through Ollama, vLLM or SGLang. The default
|
||||
model is DeepSeek's `deepseek-flash`, which now serves DeepSeek V4.1 Flash
|
||||
([details](docs/PROVIDERS.md#deepseek-v41-flash)).
|
||||
</details>
|
||||
<br />
|
||||
|
||||
`/model` switches models between turns, and `/model auto` picks one for each
|
||||
turn (`/router` sets up how). `/turn inspect` shows which model a turn used,
|
||||
what it cost and how long it took. See [Providers](docs/PROVIDERS.md).
|
||||
Run interactively:
|
||||
|
||||
### Receipts and undo
|
||||
```shell
|
||||
codex
|
||||
```
|
||||
|
||||
`/receipts`, or `codewhale receipts --last` after you quit, lists the files a
|
||||
session changed, the commands it ran, its web and MCP calls, the agents it
|
||||
started, each approval and who gave it, and what failed. `/undo` takes back
|
||||
your last request and the files it changed in one step. `/restore` rolls the
|
||||
whole workspace back to an earlier snapshot, and because that overwrites later
|
||||
edits it needs `/trust on` or Full Access. See [Receipts](docs/RECEIPTS.md).
|
||||
Or, run with a prompt as input (and optionally in `Full Auto` mode):
|
||||
|
||||
### Several agents on one task
|
||||
```shell
|
||||
codex "explain this codebase to me"
|
||||
```
|
||||
|
||||
`/goal` sets an objective that carries across turns. Codewhale can split work
|
||||
across [sub-agents](docs/SUBAGENTS.md), run a [Fleet](docs/FLEET.md) of agents
|
||||
with their own roles and models, or follow a
|
||||
[workflow](docs/WORKFLOW_AUTHORING.md) checked into the repository.
|
||||
```shell
|
||||
codex --approval-mode full-auto "create the fanciest todo-list app"
|
||||
```
|
||||
|
||||
### Extensions
|
||||
That's it - Codex will scaffold a file, run it inside a sandbox, install any
|
||||
missing dependencies, and show you the live result. Approve the changes and
|
||||
they'll be committed to your working directory.
|
||||
|
||||
Connect [MCP servers](docs/MCP.md), add [skills](docs/SKILLS.md) and
|
||||
[plugins](docs/PLUGINS.md), and run [hooks](docs/HOOKS.md) on session and tool
|
||||
events. Code mode (experimental, on by default) lets the model write one
|
||||
`execute_tools` program that calls several MCP and plugin tools; each call
|
||||
still meets your approval rules and keeps its receipt. [Computer Use](https://github.com/codewhale-hq/codewhale-cu-plugin)
|
||||
is a separate plugin released on its own (experimental, macOS only), and stays
|
||||
off until you review its access and turn it on.
|
||||
---
|
||||
|
||||
### Ways to run it
|
||||
## Why Codex?
|
||||
|
||||
| Command | What it does |
|
||||
| --- | --- |
|
||||
| `codewhale` | The interactive terminal app |
|
||||
| `codewhale exec "…"` | One task without the interface, for scripts and CI ([headless runs](docs/INSTALL.md#headless-scripts-ci)) |
|
||||
| `codewhale web` | A local web client on `127.0.0.1` ([guide](docs/WEB.md)) |
|
||||
| `codewhale review --pr N` | An advisory review of a GitHub pull request; the [GitHub bot setup](docs/GITHUB_ACTION.md#set-up-your-own-bot) runs it on your own model key |
|
||||
| `codewhale app-server --http` | The [Runtime API](docs/RUNTIME_API.md), a local HTTP API for threads, events, approvals and each turn's file changes |
|
||||
| `codewhale serve --acp` | The Agent Client Protocol over stdio, for editors such as Zed |
|
||||
Codex CLI is built for developers who already **live in the terminal** and want
|
||||
ChatGPT-level reasoning **plus** the power to actually run code, manipulate
|
||||
files, and iterate - all under version control. In short, it's _chat-driven
|
||||
development_ that understands and executes your repo.
|
||||
|
||||
The terminal app, `exec`, `web` and the Runtime API share one engine
|
||||
([architecture](docs/ARCHITECTURE.md)). A community-maintained
|
||||
[VS Code extension](https://marketplace.visualstudio.com/items?itemName=HengQuWorld.brotherwhale-vscode)
|
||||
connects to it from the sidebar ([source](https://github.com/HengQuWorld/CodeWhale-VSCode)).
|
||||
- **Zero setup** - bring your OpenAI API key and it just works!
|
||||
- **Full auto-approval, while safe + secure** by running network-disabled and directory-sandboxed
|
||||
- **Multimodal** - pass in screenshots or diagrams to implement features ✨
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.orcarouter.ai/redeem/1a6250545fdc4e0aaccb0a4470159e67" rel="sponsored">
|
||||
<img src="web/public/codewhale-x-orcarouter.webp" alt="Codewhale × OrcaRouter: +30% DeepSeek V4.1 Flash usage" width="560">
|
||||
</a>
|
||||
</p>
|
||||
And it's **fully open-source** so you can see and contribute to how it develops!
|
||||
|
||||
<a href="https://www.orcarouter.ai/redeem/1a6250545fdc4e0aaccb0a4470159e67" rel="sponsored">Codewhale × OrcaRouter</a>
|
||||
gives 30% extra DeepSeek V4.1 Flash credit on each OrcaRouter top-up until
|
||||
November 21, 2026. This link is part of a referral campaign; Codewhale may
|
||||
earn a referral reward on top-ups made through it.
|
||||
---
|
||||
|
||||
## How it stays safe
|
||||
## Security Model & Permissions
|
||||
|
||||
Two switches say how much Codewhale may do on its own. `Tab`, when the input
|
||||
is empty, changes the mode: **Plan** reads and searches but changes nothing,
|
||||
**Work** edits files and runs commands, and **Operate** has the same access as
|
||||
Work and keeps going until the goal is verified or it needs you. `Shift+Tab`
|
||||
changes the permission. **Ask**, the default, applies file edits in your
|
||||
repository without asking and asks before shell commands that are not
|
||||
read-only.
|
||||
**Auto-Review** lets rules and a reviewer model approve routine calls and
|
||||
refuses high-risk ones; switch to Ask to approve those yourself. **Full
|
||||
Access** never asks and turns off Codewhale's sandbox, although repository
|
||||
rules and managed policy still apply. The
|
||||
[v0.10.2 guide](https://github.com/codewhale-hq/Codewhale/blob/v0.10.2/docs/MODES.md)
|
||||
has the details.
|
||||
Codex lets you decide _how much autonomy_ the agent receives and auto-approval policy via the
|
||||
`--approval-mode` flag (or the interactive onboarding prompt):
|
||||
|
||||
Coming in 0.10.3: `Shift+Tab` becomes one dial through Ask, Accept Edits,
|
||||
Plan, Auto-Review and Full Access, `Tab` no longer changes the mode, and
|
||||
Operate is retired. Ask will ask before every file edit too, and the new
|
||||
default, Accept Edits, will apply edits inside the git repository and still ask
|
||||
before commands. The [current guide](docs/MODES.md) describes it.
|
||||
| Mode | What the agent may do without asking | Still requires approval |
|
||||
| ------------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| **Suggest** <br>(default) | <li>Read any file in the repo | <li>**All** file writes/patches<li> **Any** arbitrary shell commands (aside from reading files) |
|
||||
| **Auto Edit** | <li>Read **and** apply-patch writes to files | <li>**All** shell commands |
|
||||
| **Full Auto** | <li>Read/write files <li> Execute shell commands (network disabled, writes limited to your workdir) | - |
|
||||
|
||||
In Ask and Auto-Review, commands run in an OS sandbox where the system has
|
||||
one: Seatbelt on macOS, and bubblewrap on Linux when it is installed. Windows
|
||||
has no OS sandbox yet, and Full Access turns the sandbox off. Inside the
|
||||
sandbox, shell commands and terminal sessions cannot read credential stores
|
||||
such as `~/.ssh`, `~/.aws` and `.env` files.
|
||||
`/preview-request` shows a redacted preview of the next request without
|
||||
sending it.
|
||||
In **Full Auto** every command is run **network-disabled** and confined to the
|
||||
current working directory (plus temporary files) for defense-in-depth. Codex
|
||||
will also show a warning/confirmation if you start in **auto-edit** or
|
||||
**full-auto** while the directory is _not_ tracked by Git, so you always have a
|
||||
safety net.
|
||||
|
||||
You need no account, and keys you add with `/provider` stay on your machine.
|
||||
With your own API key you pay the provider directly, and local models need no
|
||||
key. The footer and `/cost` show what a session has cost when the price is
|
||||
known, and an unknown price is shown as unknown, never as free.
|
||||
Coming soon: you'll be able to whitelist specific commands to auto-execute with
|
||||
the network enabled, once we're confident in additional safeguards.
|
||||
|
||||
Telemetry is on by default. It sends aggregate counts (version and platform,
|
||||
session length and outcome, which built-in providers you used, feature and
|
||||
error counts) with a random install ID that rotates every 90 days, and never
|
||||
conversations, code, prompts, model names, file or repository names, or
|
||||
credentials. This command turns it off:
|
||||
### Platform sandboxing details
|
||||
|
||||
The hardening mechanism Codex uses depends on your OS:
|
||||
|
||||
- **macOS 12+** - commands are wrapped with **Apple Seatbelt** (`sandbox-exec`).
|
||||
|
||||
- Everything is placed in a read-only jail except for a small set of
|
||||
writable roots (`$PWD`, `$TMPDIR`, `~/.codex`, etc.).
|
||||
- Outbound network is _fully blocked_ by default - even if a child process
|
||||
tries to `curl` somewhere it will fail.
|
||||
|
||||
- **Linux** - there is no sandboxing by default.
|
||||
We recommend using Docker for sandboxing, where Codex launches itself inside a **minimal
|
||||
container image** and mounts your repo _read/write_ at the same path. A
|
||||
custom `iptables`/`ipset` firewall script denies all egress except the
|
||||
OpenAI API. This gives you deterministic, reproducible runs without needing
|
||||
root on the host. You can use the [`run_in_container.sh`](./codex-cli/scripts/run_in_container.sh) script to set up the sandbox.
|
||||
|
||||
---
|
||||
|
||||
## System Requirements
|
||||
|
||||
| Requirement | Details |
|
||||
| --------------------------- | --------------------------------------------------------------- |
|
||||
| Operating systems | macOS 12+, Ubuntu 20.04+/Debian 10+, or Windows 11 **via WSL2** |
|
||||
| Node.js | **22 or newer** (LTS recommended) |
|
||||
| Git (optional, recommended) | 2.23+ for built-in PR helpers |
|
||||
| RAM | 4-GB minimum (8-GB recommended) |
|
||||
|
||||
> Never run `sudo npm install -g`; fix npm permissions instead.
|
||||
|
||||
---
|
||||
|
||||
## CLI Reference
|
||||
|
||||
| Command | Purpose | Example |
|
||||
| ------------------------------------ | ----------------------------------- | ------------------------------------ |
|
||||
| `codex` | Interactive REPL | `codex` |
|
||||
| `codex "..."` | Initial prompt for interactive REPL | `codex "fix lint errors"` |
|
||||
| `codex -q "..."` | Non-interactive "quiet mode" | `codex -q --json "explain utils.ts"` |
|
||||
| `codex completion <bash\|zsh\|fish>` | Print shell completion script | `codex completion bash` |
|
||||
|
||||
Key flags: `--model/-m`, `--approval-mode/-a`, `--quiet/-q`, and `--notify`.
|
||||
|
||||
---
|
||||
|
||||
## Memory & Project Docs
|
||||
|
||||
Codex merges Markdown instructions in this order:
|
||||
|
||||
1. `~/.codex/instructions.md` - personal global guidance
|
||||
2. `codex.md` at repo root - shared project notes
|
||||
3. `codex.md` in cwd - sub-package specifics
|
||||
|
||||
Disable with `--no-project-doc` or `CODEX_DISABLE_PROJECT_DOC=1`.
|
||||
|
||||
---
|
||||
|
||||
## Non-interactive / CI mode
|
||||
|
||||
Run Codex head-less in pipelines. Example GitHub Action step:
|
||||
|
||||
```yaml
|
||||
- name: Update changelog via Codex
|
||||
run: |
|
||||
npm install -g @openai/codex
|
||||
export OPENAI_API_KEY="${{ secrets.OPENAI_KEY }}"
|
||||
codex -a auto-edit --quiet "update CHANGELOG for next release"
|
||||
```
|
||||
|
||||
Set `CODEX_QUIET_MODE=1` to silence interactive UI noise.
|
||||
|
||||
## Tracing / Verbose Logging
|
||||
|
||||
Setting the environment variable `DEBUG=true` prints full API request and response details:
|
||||
|
||||
```shell
|
||||
DEBUG=true codex
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recipes
|
||||
|
||||
Below are a few bite-size examples you can copy-paste. Replace the text in quotes with your own task. See the [prompting guide](https://github.com/openai/codex/blob/main/codex-cli/examples/prompting_guide.md) for more tips and usage patterns.
|
||||
|
||||
| ✨ | What you type | What happens |
|
||||
| --- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||
| 1 | `codex "Refactor the Dashboard component to React Hooks"` | Codex rewrites the class component, runs `npm test`, and shows the diff. |
|
||||
| 2 | `codex "Generate SQL migrations for adding a users table"` | Infers your ORM, creates migration files, and runs them in a sandboxed DB. |
|
||||
| 3 | `codex "Write unit tests for utils/date.ts"` | Generates tests, executes them, and iterates until they pass. |
|
||||
| 4 | `codex "Bulk-rename *.jpeg -> *.jpg with git mv"` | Safely renames files and updates imports/usages. |
|
||||
| 5 | `codex "Explain what this regex does: ^(?=.*[A-Z]).{8,}$"` | Outputs a step-by-step human explanation. |
|
||||
| 6 | `codex "Carefully review this repo, and propose 3 high impact well-scoped PRs"` | Suggests impactful PRs in the current codebase. |
|
||||
| 7 | `codex "Look for vulnerabilities and create a security review report"` | Finds and explains security bugs. |
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
<details open>
|
||||
<summary><strong>From npm (Recommended)</strong></summary>
|
||||
|
||||
```bash
|
||||
codewhale config set telemetry false
|
||||
npm install -g @openai/codex
|
||||
# or
|
||||
yarn global add @openai/codex
|
||||
# or
|
||||
bun install -g @openai/codex
|
||||
# or
|
||||
pnpm add -g @openai/codex
|
||||
```
|
||||
|
||||
At startup Codewhale also checks GitHub for a new release; set
|
||||
`CODEWHALE_NO_UPDATE_CHECK=1` to skip that. More in
|
||||
[authorization order](docs/AUTHORIZATION_ORDER.md),
|
||||
[sandboxing](docs/SANDBOX.md) and [telemetry](docs/TELEMETRY.md).
|
||||
</details>
|
||||
|
||||
## Where it is going
|
||||
<details>
|
||||
<summary><strong>Build from source</strong></summary>
|
||||
|
||||
Codewhale is becoming one environment across your terminal, a desktop app,
|
||||
the browser and cloud computers, all run by one engine. Each part says where
|
||||
it stands today.
|
||||
```bash
|
||||
# Clone the repository and navigate to the CLI package
|
||||
git clone https://github.com/openai/codex.git
|
||||
cd codex/codex-cli
|
||||
|
||||
| Part | State | Today |
|
||||
| --- | --- | --- |
|
||||
| Terminal app | Released | Install it on macOS, Linux or Windows and run `codewhale` in any repository. |
|
||||
| Local browser client | Released | Run `codewhale web` to read the conversation, send a task and answer approvals in your browser; it comes with the terminal and needs no account. |
|
||||
| Many agents on one task | Released | Sub-agents, Fleets and workflows let several agents share one task, each with its own role. |
|
||||
| Desktop app | In development, no download | We are building a desktop app on the same engine, and it has no public download yet. |
|
||||
| Cloud computers | Invite-only | Cloud computers are invite-only, and we are building them so a session can keep working on a machine in the cloud. |
|
||||
| One session across them | Being built | We are building one session that goes with you from the terminal to the desktop, the browser and the cloud, and stays yours the whole way. |
|
||||
# Enable corepack
|
||||
corepack enable
|
||||
|
||||
The [roadmap](docs/ROADMAP.md) says what each release covers and where you
|
||||
can help.
|
||||
# Install dependencies and build
|
||||
pnpm install
|
||||
pnpm build
|
||||
|
||||
## Coming from another agent
|
||||
# Get the usage and the options
|
||||
node ./dist/cli.js --help
|
||||
|
||||
Codewhale reads `AGENTS.md` on its own. Instruction files written for other
|
||||
tools, such as `CLAUDE.md`, `.cursorrules` or
|
||||
`.github/copilot-instructions.md`, load only when you opt in with
|
||||
`project_instruction_imports = ["claude"]` (or `"cursor"`, `"cline"`,
|
||||
`"windsurf"`, `"copilot"`, `"all"`) in your config; see
|
||||
[config.example.toml](config.example.toml). Codewhale warns when it finds one
|
||||
it did not load.
|
||||
# Run the locally-built CLI directly
|
||||
node ./dist/cli.js
|
||||
|
||||
`/import-claude` prepares an import of your Claude Code settings and MCP
|
||||
servers for you to review. `/plugin install` accepts Claude Code plugins that
|
||||
contain skills, commands, agent profiles and MCP servers, and refuses plugins
|
||||
that need hooks or LSP servers
|
||||
([details](docs/CLAUDE_PLUGIN_COMPAT.md)).
|
||||
# Or link the command globally for convenience
|
||||
pnpm link
|
||||
```
|
||||
|
||||
## Documentation
|
||||
</details>
|
||||
|
||||
These guides follow the main branch and can describe changes that are not
|
||||
in the latest release yet.
|
||||
---
|
||||
|
||||
| Start here | Reference |
|
||||
| --- | --- |
|
||||
| [User guide](docs/GUIDE.md) | [Configuration](docs/CONFIGURATION.md) |
|
||||
| [Installation](docs/INSTALL.md) | [Architecture](docs/ARCHITECTURE.md) |
|
||||
| [Providers and local models](docs/PROVIDERS.md) | [Runtime API](docs/RUNTIME_API.md) |
|
||||
| [Modes and permissions](docs/MODES.md) | [Plugin authoring](docs/PLUGIN_AUTHORING.md) |
|
||||
| [Keybindings](docs/KEYBINDINGS.md) | [Receipts](docs/RECEIPTS.md) |
|
||||
| [GitHub pull request review](docs/GITHUB_ACTION.md) | [All documentation](docs/README.md) |
|
||||
## Configuration Guide
|
||||
|
||||
Codex configuration files can be placed in the `~/.codex/` directory, supporting both YAML and JSON formats.
|
||||
|
||||
### Basic Configuration Parameters
|
||||
|
||||
| Parameter | Type | Default | Description | Available Options |
|
||||
| ------------------- | ------- | ---------- | -------------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| `model` | string | `o4-mini` | AI model to use | Any model name supporting OpenAI API |
|
||||
| `approvalMode` | string | `suggest` | AI assistant's permission mode | `suggest` (suggestions only)<br>`auto-edit` (automatic edits)<br>`full-auto` (fully automatic) |
|
||||
| `fullAutoErrorMode` | string | `ask-user` | Error handling in full-auto mode | `ask-user` (prompt for user input)<br>`ignore-and-continue` (ignore and proceed) |
|
||||
| `notify` | boolean | `true` | Enable desktop notifications | `true`/`false` |
|
||||
|
||||
### Custom AI Provider Configuration
|
||||
|
||||
In the `providers` object, you can configure multiple AI service providers. Each provider requires the following parameters:
|
||||
|
||||
| Parameter | Type | Description | Example |
|
||||
| --------- | ------ | --------------------------------------- | ----------------------------- |
|
||||
| `name` | string | Display name of the provider | `"OpenAI"` |
|
||||
| `baseURL` | string | API service URL | `"https://api.openai.com/v1"` |
|
||||
| `envKey` | string | Environment variable name (for API key) | `"OPENAI_API_KEY"` |
|
||||
|
||||
### History Configuration
|
||||
|
||||
In the `history` object, you can configure conversation history settings:
|
||||
|
||||
| Parameter | Type | Description | Example Value |
|
||||
| ------------------- | ------- | ------------------------------------------------------ | ------------- |
|
||||
| `maxSize` | number | Maximum number of history entries to save | `1000` |
|
||||
| `saveHistory` | boolean | Whether to save history | `true` |
|
||||
| `sensitivePatterns` | array | Patterns of sensitive information to filter in history | `[]` |
|
||||
|
||||
### Configuration Examples
|
||||
|
||||
1. YAML format (save as `~/.codex/config.yaml`):
|
||||
|
||||
```yaml
|
||||
model: o4-mini
|
||||
approvalMode: suggest
|
||||
fullAutoErrorMode: ask-user
|
||||
notify: true
|
||||
```
|
||||
|
||||
2. JSON format (save as `~/.codex/config.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "o4-mini",
|
||||
"approvalMode": "suggest",
|
||||
"fullAutoErrorMode": "ask-user",
|
||||
"notify": true
|
||||
}
|
||||
```
|
||||
|
||||
### Full Configuration Example
|
||||
|
||||
Below is a comprehensive example of `config.json` with multiple custom providers:
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "o4-mini",
|
||||
"provider": "openai",
|
||||
"providers": {
|
||||
"openai": {
|
||||
"name": "OpenAI",
|
||||
"baseURL": "https://api.openai.com/v1",
|
||||
"envKey": "OPENAI_API_KEY"
|
||||
},
|
||||
"openrouter": {
|
||||
"name": "OpenRouter",
|
||||
"baseURL": "https://openrouter.ai/api/v1",
|
||||
"envKey": "OPENROUTER_API_KEY"
|
||||
},
|
||||
"gemini": {
|
||||
"name": "Gemini",
|
||||
"baseURL": "https://generativelanguage.googleapis.com/v1beta/openai",
|
||||
"envKey": "GEMINI_API_KEY"
|
||||
},
|
||||
"ollama": {
|
||||
"name": "Ollama",
|
||||
"baseURL": "http://localhost:11434/v1",
|
||||
"envKey": "OLLAMA_API_KEY"
|
||||
},
|
||||
"mistral": {
|
||||
"name": "Mistral",
|
||||
"baseURL": "https://api.mistral.ai/v1",
|
||||
"envKey": "MISTRAL_API_KEY"
|
||||
},
|
||||
"deepseek": {
|
||||
"name": "DeepSeek",
|
||||
"baseURL": "https://api.deepseek.com",
|
||||
"envKey": "DEEPSEEK_API_KEY"
|
||||
},
|
||||
"xai": {
|
||||
"name": "xAI",
|
||||
"baseURL": "https://api.x.ai/v1",
|
||||
"envKey": "XAI_API_KEY"
|
||||
},
|
||||
"groq": {
|
||||
"name": "Groq",
|
||||
"baseURL": "https://api.groq.com/openai/v1",
|
||||
"envKey": "GROQ_API_KEY"
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
"maxSize": 1000,
|
||||
"saveHistory": true,
|
||||
"sensitivePatterns": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Instructions
|
||||
|
||||
You can create a `~/.codex/instructions.md` file to define custom instructions:
|
||||
|
||||
```markdown
|
||||
- Always respond with emojis
|
||||
- Only use git commands when explicitly requested
|
||||
```
|
||||
|
||||
### Environment Variables Setup
|
||||
|
||||
For each AI provider, you need to set the corresponding API key in your environment variables. For example:
|
||||
|
||||
```bash
|
||||
# OpenAI
|
||||
export OPENAI_API_KEY="your-api-key-here"
|
||||
|
||||
# OpenRouter
|
||||
export OPENROUTER_API_KEY="your-openrouter-key-here"
|
||||
|
||||
# Similarly for other providers
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
<details>
|
||||
<summary>OpenAI released a model called Codex in 2021 - is this related?</summary>
|
||||
|
||||
In 2021, OpenAI released Codex, an AI system designed to generate code from natural language prompts. That original Codex model was deprecated as of March 2023 and is separate from the CLI tool.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Which models are supported?</summary>
|
||||
|
||||
Any model available with [Responses API](https://platform.openai.com/docs/api-reference/responses). The default is `o4-mini`, but pass `--model gpt-4.1` or set `model: gpt-4.1` in your config file to override.
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>Why does <code>o3</code> or <code>o4-mini</code> not work for me?</summary>
|
||||
|
||||
It's possible that your [API account needs to be verified](https://help.openai.com/en/articles/10910291-api-organization-verification) in order to start streaming responses and seeing chain of thought summaries from the API. If you're still running into issues, please let us know!
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>How do I stop Codex from editing my files?</summary>
|
||||
|
||||
Codex runs model-generated commands in a sandbox. If a proposed command or file change doesn't look right, you can simply type **n** to deny the command or give the model feedback.
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>Does it work on Windows?</summary>
|
||||
|
||||
Not directly. It requires [Windows Subsystem for Linux (WSL2)](https://learn.microsoft.com/en-us/windows/wsl/install) - Codex has been tested on macOS and Linux with Node 22.
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Zero Data Retention (ZDR) Usage
|
||||
|
||||
Codex CLI **does** support OpenAI organizations with [Zero Data Retention (ZDR)](https://platform.openai.com/docs/guides/your-data#zero-data-retention) enabled. If your OpenAI organization has Zero Data Retention enabled and you still encounter errors such as:
|
||||
|
||||
```
|
||||
OpenAI rejected the request. Error details: Status: 400, Code: unsupported_parameter, Type: invalid_request_error, Message: 400 Previous response cannot be used for this organization due to Zero Data Retention.
|
||||
```
|
||||
|
||||
You may need to upgrade to a more recent version with: `npm i -g @openai/codex@latest`
|
||||
|
||||
---
|
||||
|
||||
## Codex Open Source Fund
|
||||
|
||||
We're excited to launch a **$1 million initiative** supporting open source projects that use Codex CLI and other OpenAI models.
|
||||
|
||||
- Grants are awarded up to **$25,000** API credits.
|
||||
- Applications are reviewed **on a rolling basis**.
|
||||
|
||||
**Interested? [Apply here](https://openai.com/form/codex-open-source-fund/).**
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Codewhale is built in the open by the people who use it, in many languages.
|
||||
This project is under active development and the code will likely change pretty significantly. We'll update this message once that's complete!
|
||||
|
||||
<a href="docs/CONTRIBUTORS.md#everyone"><img src="docs/assets/contributors.png" width="760" alt="The Codewhale contributor wall: one avatar for each person whose code, co-authored commits, carried-forward pull requests or credited reports are in Codewhale, with the total count at the top."></a>
|
||||
More broadly we welcome contributions - whether you are opening your very first pull request or you're a seasoned maintainer. At the same time we care about reliability and long-term maintainability, so the bar for merging code is intentionally **high**. The guidelines below spell out what "high-quality" means in practice and should make the whole process transparent and friendly.
|
||||
|
||||
The [contributor wall](docs/CONTRIBUTORS.md) counts everyone whose code,
|
||||
co-authored commits, carried-forward pull requests or credited reports are in
|
||||
Codewhale, including people GitHub's own contributor list misses.
|
||||
`scripts/contributor-wall.py` regenerates the picture and the list from git
|
||||
history and GitHub.
|
||||
### Development workflow
|
||||
|
||||
All feedback is welcome: a bug, an idea, or something that looked or felt
|
||||
wrong. [Open an issue](https://github.com/codewhale-hq/Codewhale/issues/new/choose)
|
||||
for any of these; an issue is also the best place to start a new feature.
|
||||
Pull requests are welcome for fixes, translations, providers and planned work.
|
||||
Questions go to [Discussions](https://github.com/codewhale-hq/Codewhale/discussions).
|
||||
The [contributing guide](CONTRIBUTING.md) shows where each kind of help goes,
|
||||
how your work lands with your name on it, and
|
||||
[where things live](CONTRIBUTING.md#project-structure).
|
||||
- Create a _topic branch_ from `main` - e.g. `feat/interactive-prompt`.
|
||||
- Keep your changes focused. Multiple unrelated fixes should be opened as separate PRs.
|
||||
- Use `pnpm test:watch` during development for super-fast feedback.
|
||||
- We use **Vitest** for unit tests, **ESLint** + **Prettier** for style, and **TypeScript** for type-checking.
|
||||
- Before pushing, run the full test/type/lint suite:
|
||||
|
||||
Chat on [Discord](https://discord.gg/37gfS3ksug), or add the maintainer,
|
||||
Hunter, on WeChat (`hunterbown`) and ask to join the Whale Brothers group.
|
||||
Everyone is welcome.
|
||||
### Git Hooks with Husky
|
||||
|
||||
## History and license
|
||||
This project uses [Husky](https://typicode.github.io/husky/) to enforce code quality checks:
|
||||
|
||||
Codewhale started as `deepseek-tui` and still reads that project's
|
||||
configuration and sessions. It is independently maintained and not affiliated
|
||||
with DeepSeek or any other model provider.
|
||||
- **Pre-commit hook**: Automatically runs lint-staged to format and lint files before committing
|
||||
- **Pre-push hook**: Runs tests and type checking before pushing to the remote
|
||||
|
||||
[MIT](LICENSE). Code adapted from other open-source projects is listed in the
|
||||
third-party notices for [vendored sources](THIRD_PARTY_NOTICES.md) and
|
||||
[adapted designs](docs/THIRD_PARTY_NOTICES.md).
|
||||
These hooks help maintain code quality and prevent pushing code with failing tests. For more details, see [HUSKY.md](./codex-cli/HUSKY.md).
|
||||
|
||||
```bash
|
||||
pnpm test && pnpm run lint && pnpm run typecheck
|
||||
```
|
||||
|
||||
- If you have **not** yet signed the Contributor License Agreement (CLA), add a PR comment containing the exact text
|
||||
|
||||
```text
|
||||
I have read the CLA Document and I hereby sign the CLA
|
||||
```
|
||||
|
||||
The CLA-Assistant bot will turn the PR status green once all authors have signed.
|
||||
|
||||
```bash
|
||||
# Watch mode (tests rerun on change)
|
||||
pnpm test:watch
|
||||
|
||||
# Type-check without emitting files
|
||||
pnpm typecheck
|
||||
|
||||
# Automatically fix lint + prettier issues
|
||||
pnpm lint:fix
|
||||
pnpm format:fix
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
To debug the CLI with a visual debugger, do the following in the `codex-cli` folder:
|
||||
|
||||
- Run `pnpm run build` to build the CLI, which will generate `cli.js.map` alongside `cli.js` in the `dist` folder.
|
||||
- Run the CLI with `node --inspect-brk ./dist/cli.js` The program then waits until a debugger is attached before proceeding. Options:
|
||||
- In VS Code, choose **Debug: Attach to Node Process** from the command palette and choose the option in the dropdown with debug port `9229` (likely the first option)
|
||||
- Go to <chrome://inspect> in Chrome and find **localhost:9229** and click **trace**
|
||||
|
||||
### Writing high-impact code changes
|
||||
|
||||
1. **Start with an issue.** Open a new one or comment on an existing discussion so we can agree on the solution before code is written.
|
||||
2. **Add or update tests.** Every new feature or bug-fix should come with test coverage that fails before your change and passes afterwards. 100% coverage is not required, but aim for meaningful assertions.
|
||||
3. **Document behaviour.** If your change affects user-facing behaviour, update the README, inline help (`codex --help`), or relevant example projects.
|
||||
4. **Keep commits atomic.** Each commit should compile and the tests should pass. This makes reviews and potential rollbacks easier.
|
||||
|
||||
### Opening a pull request
|
||||
|
||||
- Fill in the PR template (or include similar information) - **What? Why? How?**
|
||||
- Run **all** checks locally (`npm test && npm run lint && npm run typecheck`). CI failures that could have been caught locally slow down the process.
|
||||
- Make sure your branch is up-to-date with `main` and that you have resolved merge conflicts.
|
||||
- Mark the PR as **Ready for review** only when you believe it is in a merge-able state.
|
||||
|
||||
### Review process
|
||||
|
||||
1. One maintainer will be assigned as a primary reviewer.
|
||||
2. We may ask for changes - please do not take this personally. We value the work, we just also value consistency and long-term maintainability.
|
||||
3. When there is consensus that the PR meets the bar, a maintainer will squash-and-merge.
|
||||
|
||||
### Community values
|
||||
|
||||
- **Be kind and inclusive.** Treat others with respect; we follow the [Contributor Covenant](https://www.contributor-covenant.org/).
|
||||
- **Assume good intent.** Written communication is hard - err on the side of generosity.
|
||||
- **Teach & learn.** If you spot something confusing, open an issue or PR with improvements.
|
||||
|
||||
### Getting help
|
||||
|
||||
If you run into problems setting up the project, would like feedback on an idea, or just want to say _hi_ - please open a Discussion or jump into the relevant issue. We are happy to help.
|
||||
|
||||
Together we can make Codex CLI an incredible tool. **Happy hacking!** :rocket:
|
||||
|
||||
### Contributor License Agreement (CLA)
|
||||
|
||||
All contributors **must** accept the CLA. The process is lightweight:
|
||||
|
||||
1. Open your pull request.
|
||||
2. Paste the following comment (or reply `recheck` if you've signed before):
|
||||
|
||||
```text
|
||||
I have read the CLA Document and I hereby sign the CLA
|
||||
```
|
||||
|
||||
3. The CLA-Assistant bot records your signature in the repo and marks the status check as passed.
|
||||
|
||||
No special Git commands, email attachments, or commit footers required.
|
||||
|
||||
#### Quick fixes
|
||||
|
||||
| Scenario | Command |
|
||||
| ----------------- | ------------------------------------------------ |
|
||||
| Amend last commit | `git commit --amend -s --no-edit && git push -f` |
|
||||
|
||||
The **DCO check** blocks merges until every commit in the PR carries the footer (with squash this is just the one).
|
||||
|
||||
### Releasing `codex`
|
||||
|
||||
To publish a new version of the CLI, run the release scripts defined in `codex-cli/package.json`:
|
||||
|
||||
1. Open the `codex-cli` directory
|
||||
2. Make sure you're on a branch like `git checkout -b bump-version`
|
||||
3. Bump the version and `CLI_VERSION` to current datetime: `pnpm release:version`
|
||||
4. Commit the version bump (with DCO sign-off):
|
||||
```bash
|
||||
git add codex-cli/src/utils/session.ts codex-cli/package.json
|
||||
git commit -s -m "chore(release): codex-cli v$(node -p \"require('./codex-cli/package.json').version\")"
|
||||
```
|
||||
5. Copy README, build, and publish to npm: `pnpm release`
|
||||
6. Push to branch: `git push origin HEAD`
|
||||
|
||||
### Alternative Build Options
|
||||
|
||||
#### Nix Flake Development
|
||||
|
||||
Prerequisite: Nix >= 2.4 with flakes enabled (`experimental-features = nix-command flakes` in `~/.config/nix/nix.conf`).
|
||||
|
||||
Enter a Nix development shell:
|
||||
|
||||
```bash
|
||||
nix develop
|
||||
```
|
||||
|
||||
This shell includes Node.js, installs dependencies, builds the CLI, and provides a `codex` command alias.
|
||||
|
||||
Build and run the CLI directly:
|
||||
|
||||
```bash
|
||||
nix build
|
||||
./result/bin/codex --help
|
||||
```
|
||||
|
||||
Run the CLI via the flake app:
|
||||
|
||||
```bash
|
||||
nix run .#codex
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security & Responsible AI
|
||||
|
||||
Have you discovered a vulnerability or have concerns about model output? Please e-mail **[email protected]** and we will respond promptly.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
This repository is licensed under the [Apache-2.0 License](LICENSE).
|
||||
@@ -1,131 +0,0 @@
|
||||
# Reviewing a change to the Codewhale Engine
|
||||
|
||||
This is the written procedure for reviewing an Engine change: which passes to
|
||||
run, how to rank what they find, and how to report it. A person, an agent and
|
||||
`codewhale review` all follow the same passes, so two reviews of one change
|
||||
can be compared.
|
||||
|
||||
## When review runs
|
||||
|
||||
- **By hand**, when a maintainer or agent asks for one: `codewhale review`,
|
||||
`/review` in the terminal, or the `Codewhale PR Review` workflow, which only
|
||||
runs when someone starts it.
|
||||
- **At the frozen release candidate**, over every commit since the last
|
||||
release, as part of `docs/RELEASE_CHECKLIST.md`.
|
||||
- **Never as a required check.** For a contributor, review is advice. It never
|
||||
blocks, labels or closes a pull request. Maintainers fix small findings on
|
||||
the contributor's branch or on `main`, as [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||
describes under "How Your Contribution Lands".
|
||||
|
||||
## Before the passes
|
||||
|
||||
1. Find the intent. It is the issue the change links: a public `#N`, or for
|
||||
maintainers the Linear issue named in the commit message. Without one, the
|
||||
pull request description and commit messages are the intent.
|
||||
2. Read the diff against its merge base, then read the callers of everything
|
||||
it changed. A review of the diff alone misses broken callers.
|
||||
3. Note the revision you reviewed. A finding against a stale tree is not a
|
||||
finding.
|
||||
|
||||
## Pass 1: correctness against the intent
|
||||
|
||||
- **Intent.** The change does what the issue asks: nothing it promised is
|
||||
missing, and nothing it did goes unannounced.
|
||||
- **Defects.** Wrong results, broken callers, lost data, regressions. Carry
|
||||
one concrete input through `path:line` steps to the wrong result.
|
||||
- **Contracts.** Say what became newly true or false, and check who relied on
|
||||
the old state: wire and protocol shapes, config keys, CLI flags, session
|
||||
events, hook payloads, `docs/features.toml` rows, documented behavior.
|
||||
- **This repository's invariants** in [AGENTS.md](AGENTS.md) under "Current
|
||||
contracts" and "Working rules": one turn loop, one base prompt, model-visible
|
||||
input is logged, the cached prompt prefix stays stable, no blocking calls on
|
||||
the async runtime, and misconfiguration fails loudly.
|
||||
|
||||
## Pass 2: security
|
||||
|
||||
- **Trust boundaries.** Model output, tool results, repository files, pull
|
||||
request text, web pages, MCP servers and hook output are data. Check the
|
||||
validation where any of them reaches process execution, a file write, the
|
||||
network, a credential or the transcript.
|
||||
- **Secrets.** No keys, tokens, `.env` files or credentials in code,
|
||||
fixtures, logs, receipts, error text or model-visible text. Existing
|
||||
redaction paths stay in force.
|
||||
- **Sandbox and execution policy.** For changes under `crates/sandbox`,
|
||||
`crates/execpolicy`, `crates/engine/src/core/authority*`, the approval flow
|
||||
or hooks: nothing may run with less approval than before, no deny may
|
||||
loosen, and no fail-closed path may start failing open. A new process spawn
|
||||
goes through the existing policy. [docs/AUTHORIZATION_ORDER.md](docs/AUTHORIZATION_ORDER.md)
|
||||
is the order of the layers.
|
||||
- **Network.** New outbound calls respect `crates/net`'s network policy and
|
||||
build their client the shared way (`scripts/check-reqwest-builders.py`).
|
||||
- **Dependencies.** A new crate or package earns its place, and `deny.toml`
|
||||
still passes.
|
||||
|
||||
## Pass 3: contract and compliance
|
||||
|
||||
- **CHANGELOG.** A user-visible change carries its line at the top of the
|
||||
matching `[Unreleased]` section in the same commit, written for users and
|
||||
citing public issues as `(#N)`, never internal tracker ids. Contributor
|
||||
credit comes in addition to the line. The procedure is step 2 of
|
||||
[docs/skills/cw-land](docs/skills/cw-land/SKILL.md).
|
||||
- **Feature registry.** A new command, `[features]` flag, provider or
|
||||
user-visible feature has its row in `docs/features.toml`.
|
||||
- **Localization.** User-facing strings go through the locale packs and keep
|
||||
parity; READMEs keep their translations in step.
|
||||
[docs/LOCALIZATION.md](docs/LOCALIZATION.md) lists the drift checks.
|
||||
- **Vocabulary.** Copy uses the product's words. The public name is
|
||||
Codewhale, and mode names follow [docs/MODES.md](docs/MODES.md).
|
||||
- **Existing checks.** Run the ones that match what the change touched, and
|
||||
report their real output:
|
||||
|
||||
| The change touched | Run |
|
||||
| --- | --- |
|
||||
| Any Rust | `cargo fmt --all -- --check`, then `scripts/dev-test.sh <area or path>` |
|
||||
| Generated files and ratchets | `scripts/preflight.sh --check` |
|
||||
| `#[allow(dead_code)]` | `python3 scripts/check-dead-code-budget.py` |
|
||||
| Async code | `python3 scripts/check-blocking-calls-budget.py` |
|
||||
| TUI strings | `python3 scripts/check-tui-locale-parity.py`, `scripts/check-tui-product-vocabulary.sh` |
|
||||
| User-facing English copy | `python3 scripts/check-lexicon.py` |
|
||||
| READMEs | `python3 scripts/check-readme-translations.py`, `scripts/check-readme-locales.sh` |
|
||||
| The provider registry | `python3 scripts/check-provider-registry.py` |
|
||||
| `config.example.toml` | `python3 scripts/check-config-example.py` |
|
||||
| A feature flag or command | `cargo test -p codewhale-tui --test feature_registry` |
|
||||
| `CHANGELOG.md` or credit | `sh scripts/sync-changelog.sh`, `python3 scripts/check-contributor-credit.py` |
|
||||
| Versions | `bash scripts/release/check-versions.sh` |
|
||||
|
||||
Do not ask for new tests or code comments; this repository adds neither unless
|
||||
an issue asks. Recommend a regression test only for a specific failure you can
|
||||
explain.
|
||||
|
||||
## Severity
|
||||
|
||||
Use the three levels `codewhale review` reports:
|
||||
|
||||
- **blocker**: a demonstrated severe failure, such as lost data, a security
|
||||
hole, a broken build or install, a crash or wrong result on a common path,
|
||||
or a broken public contract.
|
||||
- **concern**: a concrete, narrower defect with a named trigger.
|
||||
- **nit**: a demonstrated low-impact defect, such as a misleading message or a
|
||||
doc the change made wrong.
|
||||
|
||||
Every finding carries its evidence: **reproduced** (a command you ran and its
|
||||
output), **traced** (one input carried through `path:line` steps) or
|
||||
**suspected** (anything less). A suspected finding is a question for a person,
|
||||
never a blocker. Do not raise severity to express uncertainty. A defect the
|
||||
change did not introduce is labeled pre-existing and does not count against
|
||||
the change.
|
||||
|
||||
## Nits
|
||||
|
||||
Group nits into one list instead of one comment each. Leave formatting to
|
||||
`cargo fmt` and clippy. When nits are all a review found, say the change is
|
||||
fine to merge and leave the list for the author to take or leave.
|
||||
|
||||
## Report
|
||||
|
||||
- Findings ranked blocker, then concern, then nit, each with `path:line`, the
|
||||
trigger, the impact, the smallest fix and its evidence.
|
||||
- What you verified (commands and results), kept apart from what you only
|
||||
read, and what you did not review.
|
||||
- On a contributor's pull request, thank them for the specific thing they did,
|
||||
keep every finding actionable, and offer to fix small ones on their branch.
|
||||
@@ -1,36 +0,0 @@
|
||||
# Security policy
|
||||
|
||||
## Supported versions
|
||||
|
||||
Security fixes target the latest published stable Codewhale release. Please
|
||||
upgrade before reproducing a report when possible.
|
||||
|
||||
| Version | Security fixes |
|
||||
| --- | --- |
|
||||
| Latest published stable release | Supported |
|
||||
| Earlier releases | Upgrade to the latest stable release |
|
||||
| Unreleased branches and development builds | Best effort; no release support commitment |
|
||||
|
||||
## Report a vulnerability privately
|
||||
|
||||
Use [GitHub's private vulnerability report form](https://github.com/codewhale-hq/Codewhale/security/advisories/new).
|
||||
Private vulnerability reporting is enabled for this repository. GitHub sends
|
||||
the report to repository maintainers through a private security advisory.
|
||||
Do not open a public issue or pull request containing an unpatched
|
||||
vulnerability or exploit details.
|
||||
|
||||
Include the affected version or commit, operating system, a minimal
|
||||
reproduction, expected and observed behavior, and the security impact.
|
||||
Remove credentials, tokens, provider keys, personal data, and unrelated
|
||||
repository contents. If a credential was exposed, rotate it through its
|
||||
provider before sharing a redacted reproduction.
|
||||
|
||||
## What to expect
|
||||
|
||||
Maintainers review reports and discuss reproduction, impact, and remediation
|
||||
in the private advisory. Response and fix times depend on severity and
|
||||
available capacity; there is no guaranteed response deadline. Please keep
|
||||
exploit details private while a fix and disclosure timing are discussed.
|
||||
When a fix is ready, maintainers use the advisory to coordinate publication
|
||||
and affected versions. Reports that are ordinary bugs may be redirected to
|
||||
the public issue tracker after sensitive details are removed.
|
||||
@@ -1,76 +0,0 @@
|
||||
name: Codewhale
|
||||
description: Review pull requests automatically or on an authorized @codewhale review request, using your chosen model.
|
||||
branding:
|
||||
icon: code
|
||||
color: blue
|
||||
inputs:
|
||||
version:
|
||||
description: Exact released CLI tag (vX.Y.Z). Must support review --json.
|
||||
required: true
|
||||
provider:
|
||||
description: Explicit BYOK provider (orcarouter, deepseek, anthropic, openrouter, zai, modelstudio-token-plan), or codewhale for a separately verified account machine key.
|
||||
required: true
|
||||
model:
|
||||
description: Exact model ID. Account mode requires provider/model from the account catalog.
|
||||
required: true
|
||||
reasoning-effort:
|
||||
description: Optional Engine reasoning setting (auto, off, low, medium, high, max).
|
||||
github-token:
|
||||
description: GitHub token with contents read and pull-requests read; legacy post:true also requires pull-requests write.
|
||||
default: ${{ github.token }}
|
||||
pr-number:
|
||||
description: PR number for workflow_dispatch; pull_request events use the event snapshot.
|
||||
trigger-phrase:
|
||||
description: Bot mention used for a standalone @name review line in a new PR comment.
|
||||
default: '@codewhale'
|
||||
allow-forks:
|
||||
description: Allow inert fork review only on a write-authorized issue_comment or manual run from the default branch.
|
||||
default: 'false'
|
||||
max-chars:
|
||||
description: Maximum diff characters per complete pass.
|
||||
default: '200000'
|
||||
max-passes:
|
||||
description: Maximum number of complete review passes.
|
||||
default: '1'
|
||||
max-output-tokens:
|
||||
description: Optional per-pass output ceiling; at least 8192.
|
||||
timeout-seconds:
|
||||
description: Model and publication deadline, in seconds (30–1200).
|
||||
default: '600'
|
||||
post:
|
||||
description: Legacy direct COMMENT publication. Set false to export findings for a separate publisher job.
|
||||
default: 'true'
|
||||
outputs:
|
||||
outcome:
|
||||
description: reviewed_clean, reviewed_with_findings, incomplete, superseded, not_eligible, configuration_missing, failed, or publication_uncertain.
|
||||
value: ${{ steps.review.outputs.outcome }}
|
||||
receipt:
|
||||
description: Absolute path to the sanitized JSON execution receipt (also written on failures).
|
||||
value: ${{ steps.review.outputs.receipt }}
|
||||
findings:
|
||||
description: Absolute path to validated findings JSON for a complete post:false review; contains repository-sensitive review text.
|
||||
value: ${{ steps.review.outputs.findings }}
|
||||
pr-url:
|
||||
description: URL of the pull request, when resolved.
|
||||
value: ${{ steps.review.outputs.pr-url }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- id: review
|
||||
shell: bash
|
||||
env:
|
||||
CW_ACTION_PATH: ${{ github.action_path }}
|
||||
CW_VERSION: ${{ inputs.version }}
|
||||
CW_PROVIDER: ${{ inputs.provider }}
|
||||
CW_MODEL: ${{ inputs.model }}
|
||||
CW_REASONING_EFFORT: ${{ inputs.reasoning-effort }}
|
||||
CW_PR_NUMBER: ${{ inputs.pr-number }}
|
||||
CW_TRIGGER_PHRASE: ${{ inputs.trigger-phrase }}
|
||||
CW_ALLOW_FORKS: ${{ inputs.allow-forks }}
|
||||
CW_MAX_CHARS: ${{ inputs.max-chars }}
|
||||
CW_MAX_PASSES: ${{ inputs.max-passes }}
|
||||
CW_MAX_OUTPUT_TOKENS: ${{ inputs.max-output-tokens }}
|
||||
CW_TIMEOUT_SECONDS: ${{ inputs.timeout-seconds }}
|
||||
CW_POST: ${{ inputs.post }}
|
||||
GH_TOKEN: ${{ inputs.github-token }}
|
||||
run: node "$CW_ACTION_PATH/scripts/github-action/review.mjs"
|
||||
|
Before Width: | Height: | Size: 956 KiB |
@@ -1,9 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Codewhale">
|
||||
<defs>
|
||||
<linearGradient id="codewhale-current" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#1E8FD8"/>
|
||||
<stop offset="1" stop-color="#0B48BB"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#codewhale-current)" fill-rule="evenodd" d="M215.77 40.96C208.55 41.41 204.88 41.74 201.43 42.19C197 42.77 191.11 43.68 190.66 43.83C190.48 43.9 189.58 44.05 188.65 44.2C185.38 44.71 171.46 48.1 167.61 49.36C166.23 49.81 163.99 50.54 162.68 50.97C157.57 52.61 150.43 55.35 142.93 58.55C140.59 59.53 130.65 64.41 130.1 64.84C129.97 64.94 128.72 65.62 127.33 66.34C125.95 67.07 123.23 68.63 121.29 69.82C119.36 71 117.09 72.36 116.29 72.84C98.57 83.3 78.57 101.12 63.12 120.19C61.59 122.07 58.34 126.28 57.87 126.98C57.54 127.46 56.88 128.41 56.38 129.12C55.6 130.2 54.37 132.01 50.39 137.92C48.61 140.57 47.68 142.13 44.71 147.36C43.02 150.33 41.49 153.05 41.29 153.4C40.78 154.25 36.13 163.69 35.12 165.85C33.76 168.82 33.66 169.05 32.61 171.64C30.62 176.47 29.99 178.08 28.86 181.45C28.45 182.68 27.85 184.34 27.55 185.1C27.27 185.85 26.79 187.19 26.54 188.04C26.27 188.92 25.81 190.38 25.54 191.31C24.6 194.28 22.89 200.62 22.52 202.38C22.42 202.99 22.14 204.04 21.96 204.77C21.61 206.03 21.18 208.05 20.05 213.53C18.84 219.37 17.71 226.51 17.26 231.14C17.11 232.6 16.93 233.96 16.88 234.16C16.58 235.19 16 249.13 16 255.14C16.03 272.78 18.54 293.84 22.06 306.09C22.34 306.97 22.54 307.88 22.54 308.11C22.54 308.33 22.77 309.36 23.07 310.4C23.37 311.43 23.82 313.06 24.08 314.04C25.38 318.77 27.47 325.06 30.44 333.29C30.67 333.92 30.94 334.65 31.07 334.93C31.22 335.2 31.67 336.29 32.1 337.32C32.53 338.35 33.03 339.53 33.23 339.96C33.41 340.36 34.12 341.95 34.77 343.48C36 346.37 41.29 357.09 42.42 358.95C42.82 359.63 45.54 364.34 45.84 364.87C46.57 366.2 47.93 368.31 49.09 369.9C50.27 371.56 50.77 372.29 53.11 375.71C57.92 382.83 65.01 391.39 71.73 398.1C75.96 402.35 76.56 402.83 80.66 405.53C93.59 414.03 103.63 418.51 118.65 422.48C130.05 425.5 149.5 425.93 163.06 423.46C171.06 422 181.4 418.81 189.98 415.19C203.24 409.58 217.93 399.56 228.68 388.82C232.6 384.89 231.34 387.84 225.25 396.9C220.07 404.62 210.76 414.18 202.06 420.72C184.52 433.88 166.28 441.63 143.56 445.53C140.89 445.98 140.09 446.61 141.12 447.47C141.82 448.05 148.82 451.39 153.62 453.43C188.6 468.3 224.17 474.06 262.32 471.04C283.05 469.38 301.29 466.14 318.42 461C328.64 457.96 329.89 457.56 337.42 454.76C359.08 446.76 381.9 434.33 402.3 419.41C414.38 410.61 431.87 394.53 440.75 384.09C443.26 381.15 444.93 379.18 445.98 377.95C446.46 377.37 447.37 376.29 447.99 375.56C449.63 373.62 456.3 364.67 459.57 360.04C460.85 358.22 462.13 356.24 465.76 350.53C466.61 349.17 466.74 348.97 467.37 347.88C467.72 347.31 468.12 346.63 468.25 346.4C468.98 345.24 471.44 341.04 472.12 339.76C472.55 338.98 473.26 337.67 473.71 336.86C474.14 336.08 475.14 334.17 475.92 332.66C476.73 331.13 477.78 329.09 478.29 328.13C478.82 327.15 479.8 325.06 480.48 323.48C481.18 321.89 481.89 320.23 482.09 319.83C482.26 319.4 482.72 318.35 483.09 317.44C483.45 316.53 483.85 315.58 483.97 315.3C484.1 315.03 484.5 314.07 484.85 313.16C485.23 312.26 485.73 311.05 486.01 310.45C486.26 309.87 486.49 309.26 486.49 309.11C486.49 308.96 486.64 308.48 486.84 308.06C487.04 307.6 487.5 306.45 487.87 305.49C488.23 304.51 488.7 303.33 488.9 302.85C489.46 301.46 491.5 294.92 492.5 291.15C492.78 290.17 493.28 288.33 493.63 287.02C498.42 269.79 499.55 245.46 496 236.55C492.08 226.66 484.23 221.93 467.32 219.22C461.05 218.21 437.15 217.78 426.99 218.51C403.97 220.17 389.25 222.19 369.12 226.49C367.46 226.84 364.99 227.34 363.66 227.62C359.26 228.53 340.61 233.36 329.49 236.48C320.36 239.02 302.29 244.7 290.87 248.63C286.59 250.11 282.62 251.45 282.07 251.62C281.26 251.9 268.98 256.35 265.33 257.71C264.86 257.89 263.37 258.44 262.06 258.92C260.76 259.4 258.21 260.35 256.4 261.08C254.62 261.79 252.73 262.52 252.25 262.69C251.77 262.87 249.91 263.6 248.1 264.33C246.31 265.03 244.1 265.91 243.19 266.24C242.29 266.57 235.22 269.36 227.47 272.43C203.49 281.94 201.33 282.74 197.65 283.85C197.03 284.03 195.57 284.53 194.38 284.93C193.2 285.34 191.24 285.99 189.98 286.37C188.75 286.72 187.26 287.2 186.71 287.37C185.75 287.7 183.04 288.46 179.16 289.51C178.21 289.76 176.32 290.22 175.01 290.52C173.7 290.8 171.87 291.2 170.93 291.43C170.03 291.63 168.27 291.98 167.03 292.15C165.83 292.36 164.09 292.63 163.19 292.76C157.6 293.66 155.64 293.84 150.1 294.04C143.94 294.27 136.06 294.02 133.92 293.56C133.35 293.44 131.91 293.19 130.73 293.04C129.55 292.88 127.96 292.58 127.21 292.36C123.89 291.43 119.56 289.99 118.22 289.36C117.42 289.01 115.91 288.31 114.88 287.83C107.28 284.3 99.61 277.64 94.47 270.14C92.39 267.1 89.89 262.29 88.84 259.32C87.43 255.35 86.02 249.91 85.19 245.36C85.06 244.65 84.79 241.43 84.54 238.16C84.08 231.92 84.06 232.63 84.94 222.46C85.57 215.34 87.66 205.86 90.07 199.31C90.45 198.33 91.03 196.7 91.38 195.67C92.13 193.55 93.04 191.26 93.77 189.73C94.02 189.18 94.25 188.65 94.25 188.55C94.25 188.45 94.7 187.36 95.28 186.16C104.84 165.65 118.7 148.84 137.65 134.78C139.74 133.22 145.27 129.5 146.45 128.84C146.73 128.69 148.27 127.81 149.85 126.85C157.6 122.3 167.84 117.85 176.14 115.43C187Line truncated
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 10 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Codewhale">
|
||||
<path fill="#000000" fill-rule="evenodd" d="M215.77 40.96C208.55 41.41 204.88 41.74 201.43 42.19C197 42.77 191.11 43.68 190.66 43.83C190.48 43.9 189.58 44.05 188.65 44.2C185.38 44.71 171.46 48.1 167.61 49.36C166.23 49.81 163.99 50.54 162.68 50.97C157.57 52.61 150.43 55.35 142.93 58.55C140.59 59.53 130.65 64.41 130.1 64.84C129.97 64.94 128.72 65.62 127.33 66.34C125.95 67.07 123.23 68.63 121.29 69.82C119.36 71 117.09 72.36 116.29 72.84C98.57 83.3 78.57 101.12 63.12 120.19C61.59 122.07 58.34 126.28 57.87 126.98C57.54 127.46 56.88 128.41 56.38 129.12C55.6 130.2 54.37 132.01 50.39 137.92C48.61 140.57 47.68 142.13 44.71 147.36C43.02 150.33 41.49 153.05 41.29 153.4C40.78 154.25 36.13 163.69 35.12 165.85C33.76 168.82 33.66 169.05 32.61 171.64C30.62 176.47 29.99 178.08 28.86 181.45C28.45 182.68 27.85 184.34 27.55 185.1C27.27 185.85 26.79 187.19 26.54 188.04C26.27 188.92 25.81 190.38 25.54 191.31C24.6 194.28 22.89 200.62 22.52 202.38C22.42 202.99 22.14 204.04 21.96 204.77C21.61 206.03 21.18 208.05 20.05 213.53C18.84 219.37 17.71 226.51 17.26 231.14C17.11 232.6 16.93 233.96 16.88 234.16C16.58 235.19 16 249.13 16 255.14C16.03 272.78 18.54 293.84 22.06 306.09C22.34 306.97 22.54 307.88 22.54 308.11C22.54 308.33 22.77 309.36 23.07 310.4C23.37 311.43 23.82 313.06 24.08 314.04C25.38 318.77 27.47 325.06 30.44 333.29C30.67 333.92 30.94 334.65 31.07 334.93C31.22 335.2 31.67 336.29 32.1 337.32C32.53 338.35 33.03 339.53 33.23 339.96C33.41 340.36 34.12 341.95 34.77 343.48C36 346.37 41.29 357.09 42.42 358.95C42.82 359.63 45.54 364.34 45.84 364.87C46.57 366.2 47.93 368.31 49.09 369.9C50.27 371.56 50.77 372.29 53.11 375.71C57.92 382.83 65.01 391.39 71.73 398.1C75.96 402.35 76.56 402.83 80.66 405.53C93.59 414.03 103.63 418.51 118.65 422.48C130.05 425.5 149.5 425.93 163.06 423.46C171.06 422 181.4 418.81 189.98 415.19C203.24 409.58 217.93 399.56 228.68 388.82C232.6 384.89 231.34 387.84 225.25 396.9C220.07 404.62 210.76 414.18 202.06 420.72C184.52 433.88 166.28 441.63 143.56 445.53C140.89 445.98 140.09 446.61 141.12 447.47C141.82 448.05 148.82 451.39 153.62 453.43C188.6 468.3 224.17 474.06 262.32 471.04C283.05 469.38 301.29 466.14 318.42 461C328.64 457.96 329.89 457.56 337.42 454.76C359.08 446.76 381.9 434.33 402.3 419.41C414.38 410.61 431.87 394.53 440.75 384.09C443.26 381.15 444.93 379.18 445.98 377.95C446.46 377.37 447.37 376.29 447.99 375.56C449.63 373.62 456.3 364.67 459.57 360.04C460.85 358.22 462.13 356.24 465.76 350.53C466.61 349.17 466.74 348.97 467.37 347.88C467.72 347.31 468.12 346.63 468.25 346.4C468.98 345.24 471.44 341.04 472.12 339.76C472.55 338.98 473.26 337.67 473.71 336.86C474.14 336.08 475.14 334.17 475.92 332.66C476.73 331.13 477.78 329.09 478.29 328.13C478.82 327.15 479.8 325.06 480.48 323.48C481.18 321.89 481.89 320.23 482.09 319.83C482.26 319.4 482.72 318.35 483.09 317.44C483.45 316.53 483.85 315.58 483.97 315.3C484.1 315.03 484.5 314.07 484.85 313.16C485.23 312.26 485.73 311.05 486.01 310.45C486.26 309.87 486.49 309.26 486.49 309.11C486.49 308.96 486.64 308.48 486.84 308.06C487.04 307.6 487.5 306.45 487.87 305.49C488.23 304.51 488.7 303.33 488.9 302.85C489.46 301.46 491.5 294.92 492.5 291.15C492.78 290.17 493.28 288.33 493.63 287.02C498.42 269.79 499.55 245.46 496 236.55C492.08 226.66 484.23 221.93 467.32 219.22C461.05 218.21 437.15 217.78 426.99 218.51C403.97 220.17 389.25 222.19 369.12 226.49C367.46 226.84 364.99 227.34 363.66 227.62C359.26 228.53 340.61 233.36 329.49 236.48C320.36 239.02 302.29 244.7 290.87 248.63C286.59 250.11 282.62 251.45 282.07 251.62C281.26 251.9 268.98 256.35 265.33 257.71C264.86 257.89 263.37 258.44 262.06 258.92C260.76 259.4 258.21 260.35 256.4 261.08C254.62 261.79 252.73 262.52 252.25 262.69C251.77 262.87 249.91 263.6 248.1 264.33C246.31 265.03 244.1 265.91 243.19 266.24C242.29 266.57 235.22 269.36 227.47 272.43C203.49 281.94 201.33 282.74 197.65 283.85C197.03 284.03 195.57 284.53 194.38 284.93C193.2 285.34 191.24 285.99 189.98 286.37C188.75 286.72 187.26 287.2 186.71 287.37C185.75 287.7 183.04 288.46 179.16 289.51C178.21 289.76 176.32 290.22 175.01 290.52C173.7 290.8 171.87 291.2 170.93 291.43C170.03 291.63 168.27 291.98 167.03 292.15C165.83 292.36 164.09 292.63 163.19 292.76C157.6 293.66 155.64 293.84 150.1 294.04C143.94 294.27 136.06 294.02 133.92 293.56C133.35 293.44 131.91 293.19 130.73 293.04C129.55 292.88 127.96 292.58 127.21 292.36C123.89 291.43 119.56 289.99 118.22 289.36C117.42 289.01 115.91 288.31 114.88 287.83C107.28 284.3 99.61 277.64 94.47 270.14C92.39 267.1 89.89 262.29 88.84 259.32C87.43 255.35 86.02 249.91 85.19 245.36C85.06 244.65 84.79 241.43 84.54 238.16C84.08 231.92 84.06 232.63 84.94 222.46C85.57 215.34 87.66 205.86 90.07 199.31C90.45 198.33 91.03 196.7 91.38 195.67C92.13 193.55 93.04 191.26 93.77 189.73C94.02 189.18 94.25 188.65 94.25 188.55C94.25 188.45 94.7 187.36 95.28 186.16C104.84 165.65 118.7 148.84 137.65 134.78C139.74 133.22 145.27 129.5 146.45 128.84C146.73 128.69 148.27 127.81 149.85 126.85C157.6 122.3 167.84 117.85 176.14 115.43C187.26 112.21 194.2Line truncated
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 10 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Codewhale">
|
||||
<path fill="#FFFFFF" fill-rule="evenodd" d="M215.77 40.96C208.55 41.41 204.88 41.74 201.43 42.19C197 42.77 191.11 43.68 190.66 43.83C190.48 43.9 189.58 44.05 188.65 44.2C185.38 44.71 171.46 48.1 167.61 49.36C166.23 49.81 163.99 50.54 162.68 50.97C157.57 52.61 150.43 55.35 142.93 58.55C140.59 59.53 130.65 64.41 130.1 64.84C129.97 64.94 128.72 65.62 127.33 66.34C125.95 67.07 123.23 68.63 121.29 69.82C119.36 71 117.09 72.36 116.29 72.84C98.57 83.3 78.57 101.12 63.12 120.19C61.59 122.07 58.34 126.28 57.87 126.98C57.54 127.46 56.88 128.41 56.38 129.12C55.6 130.2 54.37 132.01 50.39 137.92C48.61 140.57 47.68 142.13 44.71 147.36C43.02 150.33 41.49 153.05 41.29 153.4C40.78 154.25 36.13 163.69 35.12 165.85C33.76 168.82 33.66 169.05 32.61 171.64C30.62 176.47 29.99 178.08 28.86 181.45C28.45 182.68 27.85 184.34 27.55 185.1C27.27 185.85 26.79 187.19 26.54 188.04C26.27 188.92 25.81 190.38 25.54 191.31C24.6 194.28 22.89 200.62 22.52 202.38C22.42 202.99 22.14 204.04 21.96 204.77C21.61 206.03 21.18 208.05 20.05 213.53C18.84 219.37 17.71 226.51 17.26 231.14C17.11 232.6 16.93 233.96 16.88 234.16C16.58 235.19 16 249.13 16 255.14C16.03 272.78 18.54 293.84 22.06 306.09C22.34 306.97 22.54 307.88 22.54 308.11C22.54 308.33 22.77 309.36 23.07 310.4C23.37 311.43 23.82 313.06 24.08 314.04C25.38 318.77 27.47 325.06 30.44 333.29C30.67 333.92 30.94 334.65 31.07 334.93C31.22 335.2 31.67 336.29 32.1 337.32C32.53 338.35 33.03 339.53 33.23 339.96C33.41 340.36 34.12 341.95 34.77 343.48C36 346.37 41.29 357.09 42.42 358.95C42.82 359.63 45.54 364.34 45.84 364.87C46.57 366.2 47.93 368.31 49.09 369.9C50.27 371.56 50.77 372.29 53.11 375.71C57.92 382.83 65.01 391.39 71.73 398.1C75.96 402.35 76.56 402.83 80.66 405.53C93.59 414.03 103.63 418.51 118.65 422.48C130.05 425.5 149.5 425.93 163.06 423.46C171.06 422 181.4 418.81 189.98 415.19C203.24 409.58 217.93 399.56 228.68 388.82C232.6 384.89 231.34 387.84 225.25 396.9C220.07 404.62 210.76 414.18 202.06 420.72C184.52 433.88 166.28 441.63 143.56 445.53C140.89 445.98 140.09 446.61 141.12 447.47C141.82 448.05 148.82 451.39 153.62 453.43C188.6 468.3 224.17 474.06 262.32 471.04C283.05 469.38 301.29 466.14 318.42 461C328.64 457.96 329.89 457.56 337.42 454.76C359.08 446.76 381.9 434.33 402.3 419.41C414.38 410.61 431.87 394.53 440.75 384.09C443.26 381.15 444.93 379.18 445.98 377.95C446.46 377.37 447.37 376.29 447.99 375.56C449.63 373.62 456.3 364.67 459.57 360.04C460.85 358.22 462.13 356.24 465.76 350.53C466.61 349.17 466.74 348.97 467.37 347.88C467.72 347.31 468.12 346.63 468.25 346.4C468.98 345.24 471.44 341.04 472.12 339.76C472.55 338.98 473.26 337.67 473.71 336.86C474.14 336.08 475.14 334.17 475.92 332.66C476.73 331.13 477.78 329.09 478.29 328.13C478.82 327.15 479.8 325.06 480.48 323.48C481.18 321.89 481.89 320.23 482.09 319.83C482.26 319.4 482.72 318.35 483.09 317.44C483.45 316.53 483.85 315.58 483.97 315.3C484.1 315.03 484.5 314.07 484.85 313.16C485.23 312.26 485.73 311.05 486.01 310.45C486.26 309.87 486.49 309.26 486.49 309.11C486.49 308.96 486.64 308.48 486.84 308.06C487.04 307.6 487.5 306.45 487.87 305.49C488.23 304.51 488.7 303.33 488.9 302.85C489.46 301.46 491.5 294.92 492.5 291.15C492.78 290.17 493.28 288.33 493.63 287.02C498.42 269.79 499.55 245.46 496 236.55C492.08 226.66 484.23 221.93 467.32 219.22C461.05 218.21 437.15 217.78 426.99 218.51C403.97 220.17 389.25 222.19 369.12 226.49C367.46 226.84 364.99 227.34 363.66 227.62C359.26 228.53 340.61 233.36 329.49 236.48C320.36 239.02 302.29 244.7 290.87 248.63C286.59 250.11 282.62 251.45 282.07 251.62C281.26 251.9 268.98 256.35 265.33 257.71C264.86 257.89 263.37 258.44 262.06 258.92C260.76 259.4 258.21 260.35 256.4 261.08C254.62 261.79 252.73 262.52 252.25 262.69C251.77 262.87 249.91 263.6 248.1 264.33C246.31 265.03 244.1 265.91 243.19 266.24C242.29 266.57 235.22 269.36 227.47 272.43C203.49 281.94 201.33 282.74 197.65 283.85C197.03 284.03 195.57 284.53 194.38 284.93C193.2 285.34 191.24 285.99 189.98 286.37C188.75 286.72 187.26 287.2 186.71 287.37C185.75 287.7 183.04 288.46 179.16 289.51C178.21 289.76 176.32 290.22 175.01 290.52C173.7 290.8 171.87 291.2 170.93 291.43C170.03 291.63 168.27 291.98 167.03 292.15C165.83 292.36 164.09 292.63 163.19 292.76C157.6 293.66 155.64 293.84 150.1 294.04C143.94 294.27 136.06 294.02 133.92 293.56C133.35 293.44 131.91 293.19 130.73 293.04C129.55 292.88 127.96 292.58 127.21 292.36C123.89 291.43 119.56 289.99 118.22 289.36C117.42 289.01 115.91 288.31 114.88 287.83C107.28 284.3 99.61 277.64 94.47 270.14C92.39 267.1 89.89 262.29 88.84 259.32C87.43 255.35 86.02 249.91 85.19 245.36C85.06 244.65 84.79 241.43 84.54 238.16C84.08 231.92 84.06 232.63 84.94 222.46C85.57 215.34 87.66 205.86 90.07 199.31C90.45 198.33 91.03 196.7 91.38 195.67C92.13 193.55 93.04 191.26 93.77 189.73C94.02 189.18 94.25 188.65 94.25 188.55C94.25 188.45 94.7 187.36 95.28 186.16C104.84 165.65 118.7 148.84 137.65 134.78C139.74 133.22 145.27 129.5 146.45 128.84C146.73 128.69 148.27 127.81 149.85 126.85C157.6 122.3 167.84 117.85 176.14 115.43C187.26 112.21 194.2Line truncated
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 10 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Codewhale">
|
||||
<path fill="#142352" fill-rule="evenodd" d="M215.77 40.96C208.55 41.41 204.88 41.74 201.43 42.19C197 42.77 191.11 43.68 190.66 43.83C190.48 43.9 189.58 44.05 188.65 44.2C185.38 44.71 171.46 48.1 167.61 49.36C166.23 49.81 163.99 50.54 162.68 50.97C157.57 52.61 150.43 55.35 142.93 58.55C140.59 59.53 130.65 64.41 130.1 64.84C129.97 64.94 128.72 65.62 127.33 66.34C125.95 67.07 123.23 68.63 121.29 69.82C119.36 71 117.09 72.36 116.29 72.84C98.57 83.3 78.57 101.12 63.12 120.19C61.59 122.07 58.34 126.28 57.87 126.98C57.54 127.46 56.88 128.41 56.38 129.12C55.6 130.2 54.37 132.01 50.39 137.92C48.61 140.57 47.68 142.13 44.71 147.36C43.02 150.33 41.49 153.05 41.29 153.4C40.78 154.25 36.13 163.69 35.12 165.85C33.76 168.82 33.66 169.05 32.61 171.64C30.62 176.47 29.99 178.08 28.86 181.45C28.45 182.68 27.85 184.34 27.55 185.1C27.27 185.85 26.79 187.19 26.54 188.04C26.27 188.92 25.81 190.38 25.54 191.31C24.6 194.28 22.89 200.62 22.52 202.38C22.42 202.99 22.14 204.04 21.96 204.77C21.61 206.03 21.18 208.05 20.05 213.53C18.84 219.37 17.71 226.51 17.26 231.14C17.11 232.6 16.93 233.96 16.88 234.16C16.58 235.19 16 249.13 16 255.14C16.03 272.78 18.54 293.84 22.06 306.09C22.34 306.97 22.54 307.88 22.54 308.11C22.54 308.33 22.77 309.36 23.07 310.4C23.37 311.43 23.82 313.06 24.08 314.04C25.38 318.77 27.47 325.06 30.44 333.29C30.67 333.92 30.94 334.65 31.07 334.93C31.22 335.2 31.67 336.29 32.1 337.32C32.53 338.35 33.03 339.53 33.23 339.96C33.41 340.36 34.12 341.95 34.77 343.48C36 346.37 41.29 357.09 42.42 358.95C42.82 359.63 45.54 364.34 45.84 364.87C46.57 366.2 47.93 368.31 49.09 369.9C50.27 371.56 50.77 372.29 53.11 375.71C57.92 382.83 65.01 391.39 71.73 398.1C75.96 402.35 76.56 402.83 80.66 405.53C93.59 414.03 103.63 418.51 118.65 422.48C130.05 425.5 149.5 425.93 163.06 423.46C171.06 422 181.4 418.81 189.98 415.19C203.24 409.58 217.93 399.56 228.68 388.82C232.6 384.89 231.34 387.84 225.25 396.9C220.07 404.62 210.76 414.18 202.06 420.72C184.52 433.88 166.28 441.63 143.56 445.53C140.89 445.98 140.09 446.61 141.12 447.47C141.82 448.05 148.82 451.39 153.62 453.43C188.6 468.3 224.17 474.06 262.32 471.04C283.05 469.38 301.29 466.14 318.42 461C328.64 457.96 329.89 457.56 337.42 454.76C359.08 446.76 381.9 434.33 402.3 419.41C414.38 410.61 431.87 394.53 440.75 384.09C443.26 381.15 444.93 379.18 445.98 377.95C446.46 377.37 447.37 376.29 447.99 375.56C449.63 373.62 456.3 364.67 459.57 360.04C460.85 358.22 462.13 356.24 465.76 350.53C466.61 349.17 466.74 348.97 467.37 347.88C467.72 347.31 468.12 346.63 468.25 346.4C468.98 345.24 471.44 341.04 472.12 339.76C472.55 338.98 473.26 337.67 473.71 336.86C474.14 336.08 475.14 334.17 475.92 332.66C476.73 331.13 477.78 329.09 478.29 328.13C478.82 327.15 479.8 325.06 480.48 323.48C481.18 321.89 481.89 320.23 482.09 319.83C482.26 319.4 482.72 318.35 483.09 317.44C483.45 316.53 483.85 315.58 483.97 315.3C484.1 315.03 484.5 314.07 484.85 313.16C485.23 312.26 485.73 311.05 486.01 310.45C486.26 309.87 486.49 309.26 486.49 309.11C486.49 308.96 486.64 308.48 486.84 308.06C487.04 307.6 487.5 306.45 487.87 305.49C488.23 304.51 488.7 303.33 488.9 302.85C489.46 301.46 491.5 294.92 492.5 291.15C492.78 290.17 493.28 288.33 493.63 287.02C498.42 269.79 499.55 245.46 496 236.55C492.08 226.66 484.23 221.93 467.32 219.22C461.05 218.21 437.15 217.78 426.99 218.51C403.97 220.17 389.25 222.19 369.12 226.49C367.46 226.84 364.99 227.34 363.66 227.62C359.26 228.53 340.61 233.36 329.49 236.48C320.36 239.02 302.29 244.7 290.87 248.63C286.59 250.11 282.62 251.45 282.07 251.62C281.26 251.9 268.98 256.35 265.33 257.71C264.86 257.89 263.37 258.44 262.06 258.92C260.76 259.4 258.21 260.35 256.4 261.08C254.62 261.79 252.73 262.52 252.25 262.69C251.77 262.87 249.91 263.6 248.1 264.33C246.31 265.03 244.1 265.91 243.19 266.24C242.29 266.57 235.22 269.36 227.47 272.43C203.49 281.94 201.33 282.74 197.65 283.85C197.03 284.03 195.57 284.53 194.38 284.93C193.2 285.34 191.24 285.99 189.98 286.37C188.75 286.72 187.26 287.2 186.71 287.37C185.75 287.7 183.04 288.46 179.16 289.51C178.21 289.76 176.32 290.22 175.01 290.52C173.7 290.8 171.87 291.2 170.93 291.43C170.03 291.63 168.27 291.98 167.03 292.15C165.83 292.36 164.09 292.63 163.19 292.76C157.6 293.66 155.64 293.84 150.1 294.04C143.94 294.27 136.06 294.02 133.92 293.56C133.35 293.44 131.91 293.19 130.73 293.04C129.55 292.88 127.96 292.58 127.21 292.36C123.89 291.43 119.56 289.99 118.22 289.36C117.42 289.01 115.91 288.31 114.88 287.83C107.28 284.3 99.61 277.64 94.47 270.14C92.39 267.1 89.89 262.29 88.84 259.32C87.43 255.35 86.02 249.91 85.19 245.36C85.06 244.65 84.79 241.43 84.54 238.16C84.08 231.92 84.06 232.63 84.94 222.46C85.57 215.34 87.66 205.86 90.07 199.31C90.45 198.33 91.03 196.7 91.38 195.67C92.13 193.55 93.04 191.26 93.77 189.73C94.02 189.18 94.25 188.65 94.25 188.55C94.25 188.45 94.7 187.36 95.28 186.16C104.84 165.65 118.7 148.84 137.65 134.78C139.74 133.22 145.27 129.5 146.45 128.84C146.73 128.69 148.27 127.81 149.85 126.85C157.6 122.3 167.84 117.85 176.14 115.43C187.26 112.21 194.2Line truncated
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 10 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 160" role="img" aria-label="Codewhale">
|
||||
<path fill="#FFFFFF" fill-rule="evenodd" d="M893.19 8.33C890.57 9.07 888.43 10.9 887.23 13.53C886.57 14.9 886.57 14.9 886.57 79.84C886.57 144.75 886.57 144.75 887.39 146.47C891.11 154.27 902.44 153.34 905.15 145.02C905.89 142.78 906.05 17.82 905.34 15.09C903.92 9.78 898.58 6.83 893.19 8.33Z M342.45 8.77C339.14 9.13 335.83 11.72 334.51 14.98C333.88 16.54 333.88 16.54 333.8 36.97C333.75 54.12 333.67 57.43 333.34 57.43C333.15 57.43 332.93 57.32 332.87 57.19C332.35 55.85 322.67 50.73 317.06 48.87C281.99 37.11 245.34 62.58 245.28 98.74C245.26 131.1 274.31 155.93 307.13 151.67C328.6 148.85 345.76 134.16 351.8 113.4C354.02 105.79 354.04 105.14 353.93 57.57C353.85 16.26 353.85 16.26 353.22 14.71C351.53 10.47 347.42 8.22 342.45 8.77Z M306.48 66.08C332.52 71.03 342.97 103 324.75 122.07C308.69 138.87 280.57 135.42 269.33 115.29C255.24 90.12 277.56 60.61 306.48 66.08Z M652.37 8.74C649.55 9.23 646.79 11.34 645.34 14.08C644.63 15.44 644.63 15.44 644.63 80.14C644.63 153.31 644.35 145.7 647.09 148.74C650.73 152.79 657.67 152.84 661.59 148.88C664.49 145.9 664.29 148 664.49 117.89C664.62 94.47 664.7 90.97 665.09 89.16C670.64 64.16 703.03 57.27 718.26 77.84C720.83 81.31 722.56 85.06 723.6 89.38C723.82 90.26 723.95 98.98 724.06 117.61C724.23 144.56 724.23 144.56 725.02 146.28C729.01 154.9 742.06 153.14 743.81 143.74C744.41 140.43 743.95 90.37 743.29 86.78C741.57 77.65 737.6 69.33 731.83 62.74C715.83 44.47 687.93 40.66 667.22 53.99C666.13 54.7 665.06 55.22 664.84 55.14C664.54 55.03 664.46 51.66 664.4 35.49C664.32 15.99 664.32 15.99 663.53 14.3C661.64 10.17 657.05 7.89 652.37 8.74Z M47.35 46.25C28.94 48.13 12.83 59.46 4.68 76.17C3.09 79.43 1.34 84.16 0.88 86.48C0.74 87.14 0.41 88.64 0.14 89.85C-0.49 92.66 -0.6 103.39 0 106.81C3.83 129.18 19.56 145.87 41.66 151.04C45.65 152 95.9 152.16 98.34 151.23C104.93 148.77 106.54 140.04 101.32 135.09C98.39 132.3 100.77 132.55 72.79 132.36C49.18 132.22 48.23 132.19 45.76 131.64C25.99 127.13 14.66 107.41 20.98 88.4C24.1 79.02 32.31 70.81 41.71 67.69C46.86 65.99 45.02 66.1 71.97 65.94C96.72 65.78 96.72 65.78 98.56 64.9C107.17 60.82 106.16 48.76 97 46.33C95.41 45.89 51.45 45.83 47.35 46.25Z M165.66 46.35C145.55 48.71 128.51 62.14 121.4 81.23C117.35 92.06 117.29 106.21 121.23 116.52C130.59 140.94 153.92 154.9 179.88 151.56C207.34 148.03 228.65 122.07 226 95.35C225.51 90.56 224.74 86.37 223.84 83.56C215.8 58.86 191.53 43.32 165.66 46.35Z M178.62 66.05C199.17 69.99 211.45 90.56 204.77 109.82C201.35 119.66 192.55 128.2 182.75 131.1C175.09 133.37 166.75 133.12 159.47 130.39C149.27 126.53 141.58 117.72 138.69 106.51C137.92 103.55 137.76 95.1 138.38 92.45C140.68 83.09 146.81 74.39 153.81 70.59C158.02 68.29 159.42 67.69 162.65 66.76C164.32 66.3 165.57 66.08 169.49 65.53C171.05 65.31 176.32 65.61 178.62 66.05Z M420.19 46.35C401.78 48.43 386.18 59.51 377.9 76.44C363.4 106.12 380.44 142.2 412.94 150.57C421.36 152.73 432.17 152.63 440.7 150.25C449.87 147.73 457.06 143.46 464.77 136.02C468.93 132 469.56 130.88 469.56 127.32C469.56 117.94 457.52 113.97 451.53 121.39C438.6 137.36 411.76 136.46 399.31 119.64C396.55 115.89 394.04 110.55 394.72 109.87C394.97 109.62 402.93 109.52 429.98 109.46C469.45 109.41 466.69 109.54 470.19 107.22C474.4 104.4 475.99 101.04 475.99 94.88C475.99 67.23 448.39 43.21 420.19 46.35Z M428.39 65.94C439.25 67.03 448.5 73.46 453.28 83.28C454.87 86.48 455.45 88.48 454.95 88.97C454.46 89.46 395.18 89.44 394.69 88.94C393.73 87.99 397.65 80.49 401.37 76.17C405.85 70.95 414.74 66.62 422.48 65.94C423.85 65.8 425.11 65.69 425.27 65.67C425.47 65.67 426.86 65.78 428.39 65.94Z M503.29 46.38C499.02 47.39 496.26 50.54 495.68 55.03C495.22 58.58 495.33 114.44 495.82 118.16C496.97 127.16 500.11 133.91 506.02 140.07C520.3 155 542.62 155.93 557.97 142.26L560.51 139.99L562.62 141.9C568.23 146.94 574.79 150.27 581.41 151.45C599.77 154.68 618.17 143.32 623.67 125.41C625.51 119.53 625.59 117.45 625.51 83.56C625.42 56.31 625.4 54.21 624.93 52.92C621.95 44.55 610.65 43.95 606.55 51.96C605.78 53.47 605.78 53.47 605.65 85.33C605.51 109.9 605.4 117.45 605.15 118.3C603.32 124.07 600.53 127.57 595.83 130C586.25 135.01 575.01 130.11 571.43 119.39C570.77 117.48 570.77 117.48 570.63 85.74C570.47 50.65 570.63 53.22 568.72 50.48C564.67 44.58 554.85 45.32 551.81 51.74C550.69 54.18 550.66 54.45 550.66 85.91C550.66 120.21 550.77 118.41 548.59 122.54C541.15 136.68 520.85 134.38 516.28 118.84C515.84 117.34 515.79 114.71 515.65 85.33C515.51 53.47 515.51 53.47 514.75 51.91C512.56 47.48 507.94 45.23 503.29 46.38Z M808.07 46.35C775.57 50.32 754.18 82.3 763.1 113.56C767.15 127.81 777.05 139.71 790.42 146.53C807.44 155.14 829.32 153.83 845.24 143.19C848.44 141.03 848.61 141.05 848.91 143.6C850.22 154.62 866.33 154.62 868.25 143.57C868.88 139.93 868.46 94.33 867.75 90.39C862.67 61.92 836.79 42.85 808.07 46.35Z M821.22 66.08C848.5 71.25 858.48 104.13 838.59 123.27C817.01 144.09 780.71 128.88 780.63 99.01C780.58 78 800.19 62.08 821.22 66.08Z M968.77 46.38C950.66 48.79 935.54 59.59 927.58 75.76C913.33 104.7 927.91 139.52 958.51 149Line truncated
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.6 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 160" role="img" aria-label="Codewhale">
|
||||
<path fill="#142352" fill-rule="evenodd" d="M893.19 8.33C890.57 9.07 888.43 10.9 887.23 13.53C886.57 14.9 886.57 14.9 886.57 79.84C886.57 144.75 886.57 144.75 887.39 146.47C891.11 154.27 902.44 153.34 905.15 145.02C905.89 142.78 906.05 17.82 905.34 15.09C903.92 9.78 898.58 6.83 893.19 8.33Z M342.45 8.77C339.14 9.13 335.83 11.72 334.51 14.98C333.88 16.54 333.88 16.54 333.8 36.97C333.75 54.12 333.67 57.43 333.34 57.43C333.15 57.43 332.93 57.32 332.87 57.19C332.35 55.85 322.67 50.73 317.06 48.87C281.99 37.11 245.34 62.58 245.28 98.74C245.26 131.1 274.31 155.93 307.13 151.67C328.6 148.85 345.76 134.16 351.8 113.4C354.02 105.79 354.04 105.14 353.93 57.57C353.85 16.26 353.85 16.26 353.22 14.71C351.53 10.47 347.42 8.22 342.45 8.77Z M306.48 66.08C332.52 71.03 342.97 103 324.75 122.07C308.69 138.87 280.57 135.42 269.33 115.29C255.24 90.12 277.56 60.61 306.48 66.08Z M652.37 8.74C649.55 9.23 646.79 11.34 645.34 14.08C644.63 15.44 644.63 15.44 644.63 80.14C644.63 153.31 644.35 145.7 647.09 148.74C650.73 152.79 657.67 152.84 661.59 148.88C664.49 145.9 664.29 148 664.49 117.89C664.62 94.47 664.7 90.97 665.09 89.16C670.64 64.16 703.03 57.27 718.26 77.84C720.83 81.31 722.56 85.06 723.6 89.38C723.82 90.26 723.95 98.98 724.06 117.61C724.23 144.56 724.23 144.56 725.02 146.28C729.01 154.9 742.06 153.14 743.81 143.74C744.41 140.43 743.95 90.37 743.29 86.78C741.57 77.65 737.6 69.33 731.83 62.74C715.83 44.47 687.93 40.66 667.22 53.99C666.13 54.7 665.06 55.22 664.84 55.14C664.54 55.03 664.46 51.66 664.4 35.49C664.32 15.99 664.32 15.99 663.53 14.3C661.64 10.17 657.05 7.89 652.37 8.74Z M47.35 46.25C28.94 48.13 12.83 59.46 4.68 76.17C3.09 79.43 1.34 84.16 0.88 86.48C0.74 87.14 0.41 88.64 0.14 89.85C-0.49 92.66 -0.6 103.39 0 106.81C3.83 129.18 19.56 145.87 41.66 151.04C45.65 152 95.9 152.16 98.34 151.23C104.93 148.77 106.54 140.04 101.32 135.09C98.39 132.3 100.77 132.55 72.79 132.36C49.18 132.22 48.23 132.19 45.76 131.64C25.99 127.13 14.66 107.41 20.98 88.4C24.1 79.02 32.31 70.81 41.71 67.69C46.86 65.99 45.02 66.1 71.97 65.94C96.72 65.78 96.72 65.78 98.56 64.9C107.17 60.82 106.16 48.76 97 46.33C95.41 45.89 51.45 45.83 47.35 46.25Z M165.66 46.35C145.55 48.71 128.51 62.14 121.4 81.23C117.35 92.06 117.29 106.21 121.23 116.52C130.59 140.94 153.92 154.9 179.88 151.56C207.34 148.03 228.65 122.07 226 95.35C225.51 90.56 224.74 86.37 223.84 83.56C215.8 58.86 191.53 43.32 165.66 46.35Z M178.62 66.05C199.17 69.99 211.45 90.56 204.77 109.82C201.35 119.66 192.55 128.2 182.75 131.1C175.09 133.37 166.75 133.12 159.47 130.39C149.27 126.53 141.58 117.72 138.69 106.51C137.92 103.55 137.76 95.1 138.38 92.45C140.68 83.09 146.81 74.39 153.81 70.59C158.02 68.29 159.42 67.69 162.65 66.76C164.32 66.3 165.57 66.08 169.49 65.53C171.05 65.31 176.32 65.61 178.62 66.05Z M420.19 46.35C401.78 48.43 386.18 59.51 377.9 76.44C363.4 106.12 380.44 142.2 412.94 150.57C421.36 152.73 432.17 152.63 440.7 150.25C449.87 147.73 457.06 143.46 464.77 136.02C468.93 132 469.56 130.88 469.56 127.32C469.56 117.94 457.52 113.97 451.53 121.39C438.6 137.36 411.76 136.46 399.31 119.64C396.55 115.89 394.04 110.55 394.72 109.87C394.97 109.62 402.93 109.52 429.98 109.46C469.45 109.41 466.69 109.54 470.19 107.22C474.4 104.4 475.99 101.04 475.99 94.88C475.99 67.23 448.39 43.21 420.19 46.35Z M428.39 65.94C439.25 67.03 448.5 73.46 453.28 83.28C454.87 86.48 455.45 88.48 454.95 88.97C454.46 89.46 395.18 89.44 394.69 88.94C393.73 87.99 397.65 80.49 401.37 76.17C405.85 70.95 414.74 66.62 422.48 65.94C423.85 65.8 425.11 65.69 425.27 65.67C425.47 65.67 426.86 65.78 428.39 65.94Z M503.29 46.38C499.02 47.39 496.26 50.54 495.68 55.03C495.22 58.58 495.33 114.44 495.82 118.16C496.97 127.16 500.11 133.91 506.02 140.07C520.3 155 542.62 155.93 557.97 142.26L560.51 139.99L562.62 141.9C568.23 146.94 574.79 150.27 581.41 151.45C599.77 154.68 618.17 143.32 623.67 125.41C625.51 119.53 625.59 117.45 625.51 83.56C625.42 56.31 625.4 54.21 624.93 52.92C621.95 44.55 610.65 43.95 606.55 51.96C605.78 53.47 605.78 53.47 605.65 85.33C605.51 109.9 605.4 117.45 605.15 118.3C603.32 124.07 600.53 127.57 595.83 130C586.25 135.01 575.01 130.11 571.43 119.39C570.77 117.48 570.77 117.48 570.63 85.74C570.47 50.65 570.63 53.22 568.72 50.48C564.67 44.58 554.85 45.32 551.81 51.74C550.69 54.18 550.66 54.45 550.66 85.91C550.66 120.21 550.77 118.41 548.59 122.54C541.15 136.68 520.85 134.38 516.28 118.84C515.84 117.34 515.79 114.71 515.65 85.33C515.51 53.47 515.51 53.47 514.75 51.91C512.56 47.48 507.94 45.23 503.29 46.38Z M808.07 46.35C775.57 50.32 754.18 82.3 763.1 113.56C767.15 127.81 777.05 139.71 790.42 146.53C807.44 155.14 829.32 153.83 845.24 143.19C848.44 141.03 848.61 141.05 848.91 143.6C850.22 154.62 866.33 154.62 868.25 143.57C868.88 139.93 868.46 94.33 867.75 90.39C862.67 61.92 836.79 42.85 808.07 46.35Z M821.22 66.08C848.5 71.25 858.48 104.13 838.59 123.27C817.01 144.09 780.71 128.88 780.63 99.01C780.58 78 800.19 62.08 821.22 66.08Z M968.77 46.38C950.66 48.79 935.54 59.59 927.58 75.76C913.33 104.7 927.91 139.52 958.51 149Line truncated
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.6 KiB |
@@ -0,0 +1,46 @@
|
||||
# https://git-cliff.org/docs/configuration
|
||||
|
||||
[changelog]
|
||||
header = """
|
||||
# Changelog
|
||||
|
||||
You can install any of these versions: `npm install -g codex@version`
|
||||
"""
|
||||
|
||||
body = """
|
||||
{% if version -%}
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{%- else %}
|
||||
## [unreleased]
|
||||
{% endif %}
|
||||
|
||||
{%- for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | striptags | trim }}
|
||||
|
||||
{% for commit in commits %}- {% if commit.scope %}*({{ commit.scope }})* {% endif %}{% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}
|
||||
{% endfor %}
|
||||
|
||||
{%- endfor -%}
|
||||
"""
|
||||
|
||||
footer = """
|
||||
<!-- generated - do not edit -->
|
||||
"""
|
||||
|
||||
trim = true
|
||||
postprocessors = []
|
||||
|
||||
[git]
|
||||
conventional_commits = true
|
||||
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
|
||||
{ message = "^fix", group = "<!-- 1 -->🪲 Bug Fixes" },
|
||||
{ message = "^bump", group = "<!-- 6 -->🛳️ Release" },
|
||||
# Fallback – skip anything that didn't match the above rules.
|
||||
{ message = ".*", group = "<!-- 10 -->💼 Other" },
|
||||
]
|
||||
|
||||
filter_unconventional = false
|
||||
sort_commits = "oldest"
|
||||
topo_order = false
|
||||
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
@@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{js,ts,jsx,tsx}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -0,0 +1,107 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
],
|
||||
ignorePatterns: [
|
||||
".eslintrc.cjs",
|
||||
"build.mjs",
|
||||
"dist",
|
||||
"vite.config.ts",
|
||||
"src/components/vendor",
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ["./tsconfig.json"],
|
||||
},
|
||||
plugins: ["import", "react-hooks", "react-refresh"],
|
||||
rules: {
|
||||
// Imports
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"import/no-cycle": ["error", { maxDepth: 1 }],
|
||||
"import/no-duplicates": "error",
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
groups: ["type"],
|
||||
"newlines-between": "always",
|
||||
alphabetize: {
|
||||
order: "asc",
|
||||
caseInsensitive: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
// We use the import/ plugin instead.
|
||||
"sort-imports": "off",
|
||||
|
||||
"@typescript-eslint/array-type": ["error", { default: "generic" }],
|
||||
// FIXME(mbolin): Introduce this.
|
||||
// "@typescript-eslint/explicit-function-return-type": "error",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/switch-exhaustiveness-check": [
|
||||
"error",
|
||||
{
|
||||
allowDefaultCaseForExhaustiveSwitch: false,
|
||||
requireDefaultForNonUnion: true,
|
||||
},
|
||||
],
|
||||
|
||||
// Use typescript-eslint/no-unused-vars, no-unused-vars reports
|
||||
// false positives with typescript
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
|
||||
curly: "error",
|
||||
|
||||
eqeqeq: ["error", "always", { null: "never" }],
|
||||
"react-refresh/only-export-components": [
|
||||
"error",
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
"no-await-in-loop": "error",
|
||||
"no-bitwise": "error",
|
||||
"no-caller": "error",
|
||||
// This is fine during development, but should not be checked in.
|
||||
"no-console": "error",
|
||||
// This is fine during development, but should not be checked in.
|
||||
"no-debugger": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-eval": "error",
|
||||
"no-ex-assign": "error",
|
||||
"no-return-await": "error",
|
||||
"no-param-reassign": "error",
|
||||
"no-script-url": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-unsafe-finally": "error",
|
||||
"no-var": "error",
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "error",
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
// apply only to files under tests/
|
||||
files: ["tests/**/*.{ts,tsx,js,jsx}"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"import/order": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"no-await-in-loop": "off",
|
||||
"no-control-regex": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
FROM node:20-slim
|
||||
|
||||
ARG TZ
|
||||
ENV TZ="$TZ"
|
||||
|
||||
# Install basic development tools, ca-certificates, and iptables/ipset, then clean up apt cache to reduce image size
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
aggregate \
|
||||
ca-certificates \
|
||||
curl \
|
||||
dnsutils \
|
||||
fzf \
|
||||
gh \
|
||||
git \
|
||||
gnupg2 \
|
||||
iproute2 \
|
||||
ipset \
|
||||
iptables \
|
||||
jq \
|
||||
less \
|
||||
man-db \
|
||||
procps \
|
||||
unzip \
|
||||
ripgrep \
|
||||
zsh \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Ensure default node user has access to /usr/local/share
|
||||
RUN mkdir -p /usr/local/share/npm-global && \
|
||||
chown -R node:node /usr/local/share
|
||||
|
||||
ARG USERNAME=node
|
||||
|
||||
# Set up non-root user
|
||||
USER node
|
||||
|
||||
# Install global packages
|
||||
ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global
|
||||
ENV PATH=$PATH:/usr/local/share/npm-global/bin
|
||||
|
||||
# Install codex
|
||||
COPY dist/codex.tgz codex.tgz
|
||||
RUN npm install -g codex.tgz \
|
||||
&& npm cache clean --force \
|
||||
&& rm -rf /usr/local/share/npm-global/lib/node_modules/codex-cli/node_modules/.cache \
|
||||
&& rm -rf /usr/local/share/npm-global/lib/node_modules/codex-cli/tests \
|
||||
&& rm -rf /usr/local/share/npm-global/lib/node_modules/codex-cli/docs
|
||||
|
||||
# Inside the container we consider the environment already sufficiently locked
|
||||
# down, therefore instruct Codex CLI to allow running without sandboxing.
|
||||
ENV CODEX_UNSAFE_ALLOW_NO_SANDBOX=1
|
||||
|
||||
# Copy and set up firewall script as root.
|
||||
USER root
|
||||
COPY scripts/init_firewall.sh /usr/local/bin/
|
||||
RUN chmod 500 /usr/local/bin/init_firewall.sh
|
||||
|
||||
# Drop back to non-root.
|
||||
USER node
|
||||