Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -241,20 +241,27 @@ jobs:
fetch-depth: 0

- name: Validate commit messages
env:
BASE_REF: ${{ github.base_ref }}
run: |
set -euo pipefail

# Only inspects commits introduced by this PR (base branch..HEAD),
# and delegates to the same validator the commit-msg hook runs so
# CI and the hook can't drift apart on scope charset or exemptions.
failed=0
while IFS= read -r msg; do
first_line="$(echo "${msg}" | head -n1)"
if ! echo "${first_line}" | grep -qP '^(\p{So}|\p{Emoji_Presentation})+\s+\w+(\(\w[\w-]*\))?!?:\s.+$'; then
echo "::warning::Non-conventional commit: ${first_line}"
tmp="$(mktemp)"
trap 'rm -f "${tmp}"' EXIT
while IFS= read -r subject; do
printf '%s\n' "${subject}" > "${tmp}"
if ! bash scripts/validate-commit-msg.sh "${tmp}" >/dev/null 2>&1; then
echo "::warning::Non-conventional commit: ${subject}"
failed=1
fi
done < <(git log --format=%s origin/main..HEAD)
done < <(git log --format=%s "origin/${BASE_REF}..HEAD")
Comment on lines +255 to +261

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

: "${BASE_REF:?Set BASE_REF to the pull request base branch}"
base_ref="origin/${BASE_REF}"

git rev-parse --verify "${base_ref}^{commit}" >/dev/null
git log --format=%s "${base_ref}..HEAD" >/dev/null

Repository: CodesWhat/portwing

Length of output: 225


🏁 Script executed:

#!/usr/bin/env bash
set -u

printf '%s\n' '--- workflow context ---'
sed -n '220,275p' .github/workflows/ci.yml
printf '%s\n' '--- BASE_REF and git setup references ---'
rg -n -C 3 'BASE_REF|origin/|fetch-depth|git fetch|set -e|shell:' .github/workflows/ci.yml

printf '%s\n' '--- isolated process-substitution status probe ---'
probe_dir="$(mktemp -d)"
trap 'rm -rf "$probe_dir"' EXIT
git -C "$probe_dir" init -q
git -C "$probe_dir" config user.email test@example.invalid
git -C "$probe_dir" config user.name test
printf 'x\n' > "$probe_dir/file"
git -C "$probe_dir" add file
git -C "$probe_dir" commit -qm initial

cat > "$probe_dir/probe.sh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
failed=0
count=0
while IFS= read -r subject; do
  count=$((count + 1))
done < <(git log --format=%s "origin/does-not-exist..HEAD")
printf 'loop_count=%s failed=%s status=success\n' "$count" "$failed"
EOF
chmod +x "$probe_dir/probe.sh"
(
  cd "$probe_dir"
  if ./probe.sh; then
    printf 'probe_exit=0\n'
  else
    printf 'probe_exit=%s\n' "$?"
  fi
)

Repository: CodesWhat/portwing

Length of output: 3474


🏁 Script executed:

#!/usr/bin/env bash
set -u

printf '%s\n' '--- process substitution with a delayed failing producer ---'
test_dir="$(mktemp -d)"
trap 'rm -rf "$test_dir"' EXIT
git -C "$test_dir" init -q
git -C "$test_dir" config user.email test@example.invalid
git -C "$test_dir" config user.name test
printf 'x\n' > "$test_dir/file"
git -C "$test_dir" add file
git -C "$test_dir" commit -qm initial

cat > "$test_dir/behavior.sh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
count=0
while IFS= read -r subject; do
  count=$((count + 1))
done < <(sleep 0.2; git log --format=%s "origin/does-not-exist..HEAD")
printf 'reached_after_loop count=%s\n' "$count"
EOF
chmod +x "$test_dir/behavior.sh"
(
  cd "$test_dir"
  if ./behavior.sh; then
    printf 'delayed_probe_exit=0\n'
  else
    printf 'delayed_probe_exit=%s\n' "$?"
  fi
)

printf '%s\n' '--- process substitution with producer failure suppressed ---'
(
  cd "$test_dir"
  if bash -c '
    set -euo pipefail
    count=0
    while IFS= read -r subject; do count=$((count + 1)); done < <(
      git log --format=%s "origin/does-not-exist..HEAD" || true
    )
    printf "reached_after_loop count=%s\n" "$count"
  '; then
    printf 'suppressed_probe_exit=0\n'
  else
    printf 'suppressed_probe_exit=%s\n' "$?"
  fi
)

printf '%s\n' '--- fail-closed ref check ---'
(
  cd "$test_dir"
  if bash -c '
    set -euo pipefail
    base_ref=origin/does-not-exist
    git rev-parse --verify "${base_ref}^{commit}" >/dev/null
    git log --format=%s "${base_ref}..HEAD" > subjects.tmp
  '; then
    printf 'fail_closed_probe_exit=0\n'
  else
    printf 'fail_closed_probe_exit=%s\n' "$?"
  fi
)

Repository: CodesWhat/portwing

Length of output: 734


Fail closed when origin/${BASE_REF} cannot be resolved.

set -euo pipefail does not propagate the git log status from process substitution. A failed git log can leave the loop with no subjects and failed=0. Validate the ref and write the log to a temporary file before the loop.

Proposed fix
+          subjects_tmp="$(mktemp)"
+          trap 'rm -f "${tmp}" "${subjects_tmp}"' EXIT
+          base_ref="origin/${BASE_REF}"
+          git rev-parse --verify "${base_ref}^{commit}" >/dev/null
+          git log --format=%s "${base_ref}..HEAD" > "${subjects_tmp}"
           while IFS= read -r subject; do
...
-          done < <(git log --format=%s "origin/${BASE_REF}..HEAD")
+          done < "${subjects_tmp}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
while IFS= read -r subject; do
printf '%s\n' "${subject}" > "${tmp}"
if ! bash scripts/validate-commit-msg.sh "${tmp}" >/dev/null 2>&1; then
echo "::warning::Non-conventional commit: ${subject}"
failed=1
fi
done < <(git log --format=%s origin/main..HEAD)
done < <(git log --format=%s "origin/${BASE_REF}..HEAD")
subjects_tmp="$(mktemp)"
trap 'rm -f "${tmp}" "${subjects_tmp}"' EXIT
base_ref="origin/${BASE_REF}"
git rev-parse --verify "${base_ref}^{commit}" >/dev/null
git log --format=%s "${base_ref}..HEAD" > "${subjects_tmp}"
while IFS= read -r subject; do
printf '%s\n' "${subject}" > "${tmp}"
if ! bash scripts/validate-commit-msg.sh "${tmp}" >/dev/null 2>&1; then
echo "::warning::Non-conventional commit: ${subject}"
failed=1
fi
done < "${subjects_tmp}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 255 - 261, Update the
commit-validation workflow around the git log process substitution to resolve
and validate origin/${BASE_REF} before iterating. Write the output of git log
--format=%s to a temporary file, fail the step if that command cannot resolve
the ref, then feed the file into the existing validation loop while preserving
its warning and failed-flag behavior.


if [ "${failed}" -eq 1 ]; then
echo "::warning::Some commits don't follow emoji conventional format. Expected: <emoji> <type>(scope): description"
echo "::warning::Some commits don't follow Conventional Commits format. Expected: <type>(scope): description — no emoji. See scripts/validate-commit-msg.sh for allowed types."
fi

goreleaser-check:
Expand Down
22 changes: 17 additions & 5 deletions .github/workflows/release-cut.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,21 @@ jobs:
else
# Determine bump level from conventional commits since last tag.
# feat = minor bump, fix/anything else = patch bump.
# A "!" suffix on the commit subject = major bump (body/footer not read).
# A "!" before ":" in the commit subject = major bump (body/footer
# not read). Only subjects matching the full header grammar count:
# optional legacy emoji prefix (non-ASCII run + whitespace, so
# pre-migration gitmoji history still computes), then a known
# type, optional scope, optional "!", then ":". Anything else —
# "urgent!: x", "feature: x" — is ignored, not misclassified.
header_prefix='^([^ -~]+[[:space:]]+)?'
header_types='(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)'
header_scope='(\([A-Za-z0-9._/-]+\))?'
bump="patch"
while IFS= read -r subject; do
if echo "${subject}" | grep -qP '^[^:]+!:'; then
if echo "${subject}" | grep -qE "${header_prefix}${header_types}${header_scope}!:[[:space:]]"; then
bump="major"
break
elif echo "${subject}" | grep -qP '^✨\s+feat'; then
elif echo "${subject}" | grep -qE "${header_prefix}feat${header_scope}!?:[[:space:]]"; then
if [ "${bump}" != "major" ]; then
bump="minor"
fi
Expand Down Expand Up @@ -142,8 +150,12 @@ jobs:
echo "::error::CHANGELOG has no entry for ${RELEASE_TAG}. Add release notes before cutting a tag."
exit 1
fi
# Verify the entry is non-empty (has at least one non-heading line after it)
awk "/## \[${RELEASE_TAG}\]|## \[${RELEASE_TAG#v}\]/{found=1; next} found && /^## /{exit} found && /\S/{ok=1; exit} END{exit ok ? 0 : 1}" CHANGELOG.md || {
# Verify the entry is non-empty (has at least one non-heading line after it).
# Use [^[:space:]] rather than \S: mawk (the default awk on the ubuntu
# runners) has no Perl character classes and reads \S as a literal "S",
# so the check silently passed only for entries that happened to contain
# a capital S and failed for ones that did not.
awk "/## \[${RELEASE_TAG}\]|## \[${RELEASE_TAG#v}\]/{found=1; next} found && /^## /{exit} found && /[^[:space:]]/{ok=1; exit} END{exit ok ? 0 : 1}" CHANGELOG.md || {
echo "::error::CHANGELOG entry for ${RELEASE_TAG} appears empty. Add content before cutting a tag."
exit 1
}
Expand Down
7 changes: 7 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ changelog:
sort: asc
filters:
exclude:
# Plain Conventional Commits (current convention).
- "^docs"
- "^test"
- "^style"
- "^chore\\(config\\)"
# Legacy emoji-conventional history (pre-migration) — kept so old
# tags still exclude the same commit categories.
- "^📝"
- "^🧪"
- "^🔧"
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ go test -run='^$' -fuzz='^FuzzMCPHandler$' -fuzztime=5s ./internal/mcp/

## Conventions

- **Commits:** emoji conventional commits — `<emoji> <type>(scope): <description>` (see CONTRIBUTING.md). Enforced by lefthook via `scripts/validate-commit-msg.sh`.
- **Commits:** plain Conventional Commits, no emoji — `<type>(scope): <description>` (see CONTRIBUTING.md). Enforced by lefthook via `scripts/validate-commit-msg.sh`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

State that the scope is optional.

Line 57 presents <type>(scope): <description> as the required form. scripts/validate-commit-msg.sh accepts both scoped and unscoped messages, and CONTRIBUTING.md states that scope is optional. Use <type>: <description> or <type>(scope): <description>.

Proposed fix
-- **Commits:** plain Conventional Commits, no emoji — `<type>(scope): <description>` (see CONTRIBUTING.md). Enforced by lefthook via `scripts/validate-commit-msg.sh`.
+- **Commits:** plain Conventional Commits, no emoji — `<type>: <description>` or `<type>(scope): <description>` (see CONTRIBUTING.md). Enforced by lefthook via `scripts/validate-commit-msg.sh`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- **Commits:** plain Conventional Commits, no emoji — `<type>(scope): <description>` (see CONTRIBUTING.md). Enforced by lefthook via `scripts/validate-commit-msg.sh`.
- **Commits:** plain Conventional Commits, no emoji — `<type>: <description>` or `<type>(scope): <description>` (see CONTRIBUTING.md). Enforced by lefthook via `scripts/validate-commit-msg.sh`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` at line 57, Update the Commits guidance in AGENTS.md to state that
scope is optional, showing both accepted formats: <type>: <description> and
<type>(scope): <description>. Keep the existing Conventional Commits and
validation references unchanged.

- **Branches:** `main` is production; one active dev branch is the next release; feature branches merge into the dev branch promptly and are deleted after merge.
- **Tests:** table-driven with `httptest`; anything touching goroutines runs under `-race` in CI — write tests accordingly (no unsynchronized `httptest.ResponseRecorder` access from a handler goroutine; wrap with a mutex-guarded recorder).
- **Errors:** wrap with `fmt.Errorf("context: %w", err)`; structured logging via `log/slog` only.
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [v0.9.3] - 2026-08-11

### Changed

- **Commit convention migrated from gitmoji to plain Conventional Commits.**
The commit-msg validator, the git hook, and the CI commit-check workflow
all moved to the new convention. `release-cut`'s bump-math grammar was
hardened to parse the full commit header, so subjects like `urgent!:` no
longer falsely trigger a major version bump. CI's commit check now
delegates to the same shared validator script the hook uses, instead of
duplicating the logic.
- **Renovate dependency updates applied.** `biome`, `turbo`, `@types/react`,
`@types/node`, `postcss`, `typescript`, and `@types/react-dom` were bumped
to their current pinned versions, and a stale `wolfi-base` digest
reference in `Dockerfile.release` — missed when `Dockerfile` got the same
bump — was corrected.

### Fixed

- **Lockfile regenerated to restore cross-platform optional dependencies.**
The lockfile only carried resolved entries for the `darwin-arm64`
platform, so `npm ci` on Linux CI builders never installed
`lightningcss`'s native binaries, breaking website deploys. A full
regeneration restores the complete platform matrix.

## [v0.9.2] - 2026-08-04

### Changed
Expand Down
37 changes: 21 additions & 16 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,26 +71,31 @@ go test -run=^$ -fuzz=^FuzzEnvelope$ -fuzztime=5s ./internal/protocol

## Commit convention

We use **Gitmoji + Conventional Commits**:
We use **Conventional Commits** — no emoji:

```text
<emoji> <type>(<scope>): <description>
<type>(<scope>): <description>
```

| Emoji | Type | Use |
|-------|------|-----|
| ✨ | `feat` | New feature |
| 🐛 | `fix` | Bug fix |
| 📝 | `docs` | Documentation |
| 🎨 | `style` | Formatting only |
| 🔄 | `refactor` | Refactor (no feature/fix) |
| 🧪 | `test` | Tests |
| 📦 | `deps` | Dependencies |
| 🔧 | `config` | Configuration / tooling |
| 🚀 | `deploy` | Deployment / release |
| 🗑️ | `remove` | Removing code/files |

Multi-change commits: lead emoji+type on first line, bulleted sub-changes in body. Reference Linear issues in footer as `Fixes: LIN-XXX`.
Scope is optional. A `!` before the colon marks a breaking change (`feat(api)!: drop v1 tokens`). A `BREAKING CHANGE:` footer is valid Conventional Commit syntax, but release versioning currently reads only the subject-line `!` for major bumps.

| Type | Use |
|------|-----|
| `feat` | New feature |
| `fix` | Bug fix |
| `docs` | Documentation |
| `style` | Formatting only |
| `refactor` | Refactor (no feature/fix) |
| `perf` | Performance improvement |
| `test` | Tests |
| `build` | Build system / dependencies (e.g. `build(deps): bump gorilla/websocket`) |
| `ci` | CI / deployment config (e.g. `ci(deploy): add release-cut concurrency guard`) |
| `chore` | Everything else / tooling / config (e.g. `chore(config): tune lefthook timeouts`) |
| `revert` | Reverting a previous commit |

Example: `feat(auth): add Ed25519 enrollment`

Multi-change commits: lead type on first line, bulleted sub-changes in body. Reference Linear issues in footer as `Fixes: LIN-XXX`.

## Pull request guidelines

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.release
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# Wolfi rootfs — amd64 and arm64 share one recipe; buildx pulls the per-arch
# wolfi-base via --platform, so the arm64 build of this stage installs arm64
# packages even though the stage is named "-amd64".
FROM cgr.dev/chainguard/wolfi-base:latest@sha256:d2ad9a742d38e1ab550fbb20911056339632a5ca2f01777a32422a4c944fcb99 AS rootfs-amd64
FROM cgr.dev/chainguard/wolfi-base:latest@sha256:003627df3c1e1bba0c4116afcddb314aca9594ee2328c7e876a8081a6c988b2e AS rootfs-amd64
RUN apk add --no-cache --initdb --root /out \
--repository https://packages.wolfi.dev/os \
--keys-dir /etc/apk/keys \
Expand Down
2 changes: 1 addition & 1 deletion RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
Go to **Actions → 🏷️ Release: Cut** → **Run workflow** on `main`. The workflow:

- Polls until `ci.yml` has a successful run on HEAD
- Computes the next semver from emoji-conventional-commit history (`feat` = minor, anything else = patch, `!` in the commit subject = major; a `BREAKING CHANGE` footer alone does not trigger a major bump today)
- Computes the next semver from Conventional Commit history (`feat` = minor, anything else = patch, `!` in the commit subject = major; a `BREAKING CHANGE` footer alone does not trigger a major bump today). Tolerates a legacy leading emoji from pre-migration history, so old commits still compute correctly.
- Validates the CHANGELOG entry is non-empty for the computed tag
- Creates and pushes an annotated tag using the repo bot identity

Expand Down
4 changes: 2 additions & 2 deletions biome.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
"$schema": "https://biomejs.dev/schemas/2.5.7/schema.json",
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
"files": {
"includes": ["website/src/**/*.{ts,tsx,mjs}", "docs/src/**/*.{ts,tsx}"]
},
"formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2, "lineWidth": 100 },
"linter": {
"enabled": true,
"rules": { "recommended": true }
"rules": { "preset": "recommended" }
},
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"overrides": [
Expand Down
8 changes: 4 additions & 4 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
"devDependencies": {
"@tailwindcss/postcss": "4.3.3",
"@types/mdx": "2.0.14",
"@types/node": "^26.1.2",
"@types/react": "^19.2.17",
"@types/node": "26.1.2",
"@types/react": "19.2.18",
"@types/react-dom": "19.2.4",
"postcss": "^8.5.24",
"postcss": "8.5.25",
"tailwindcss": "4.3.3",
"typescript": "^7.0.2"
"typescript": "7.0.2"
},
"overrides": {
"next": {
Expand Down
2 changes: 1 addition & 1 deletion lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ commit-msg:
commands:
convention:
run: scripts/validate-commit-msg.sh {1}
fail_text: "Commit message does not follow emoji conventional commit format"
fail_text: "Commit message does not follow Conventional Commits format"

pre-commit:
piped: true
Expand Down
Loading
Loading