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
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ jobs:
type-firewall:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Use Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
Expand Down Expand Up @@ -49,20 +49,20 @@ jobs:
matrix:
node: [22]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Run unit + integration tests
run: docker compose -f docker-compose.test.yml run --rm test-node${{ matrix.node }}

test-bun:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Run Bun integration tests
run: docker compose -f docker-compose.test.yml run --rm test-bun

test-deno:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Run Deno integration tests
run: docker compose -f docker-compose.test.yml run --rm test-deno
2 changes: 1 addition & 1 deletion .github/workflows/links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
name: Check broken links
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Lychee link check
uses: lycheeverse/lychee-action@v2
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ jobs:
preflight:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Setup Node 22
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm
Expand Down Expand Up @@ -59,7 +59,7 @@ jobs:
echo "pre=$PRE" >> "$GITHUB_OUTPUT"

- name: PR comment (release preview)
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
const body = [
Expand Down
92 changes: 79 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ jobs:
is_prerelease: ${{ steps.meta.outputs.is_prerelease }}
npm_dist_tag: ${{ steps.meta.outputs.npm_dist_tag }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
ref: ${{ github.ref_name }}

- name: Setup Node 22
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm
Expand Down Expand Up @@ -135,12 +135,12 @@ jobs:
package_name: ${{ steps.pkg.outputs.name }}
version: ${{ steps.pkg.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
ref: ${{ needs.verify.outputs.tag }}

- name: Setup Node 22
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm
Expand All @@ -158,7 +158,23 @@ jobs:
echo "name=$(node -p "require('./package.json').name")" >> "$GITHUB_OUTPUT"
echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"

- name: Skip if npm version already exists
id: npm_exists
shell: bash
run: |
set -euo pipefail
PACKAGE="${{ steps.pkg.outputs.name }}"
VERSION="${{ steps.pkg.outputs.version }}"

if npm view "${PACKAGE}@${VERSION}" version >/dev/null 2>&1; then
echo "::warning::npm version ${PACKAGE}@${VERSION} already exists; skipping immutable publish."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

- name: Publish npm (retry x3)
if: ${{ steps.npm_exists.outputs.skip != 'true' }}
uses: ./.github/actions/retry
with:
attempts: "3"
Expand All @@ -175,20 +191,59 @@ jobs:
environment: jsr
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
ref: ${{ needs.verify.outputs.tag }}

- name: Setup Node 22
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm

- name: Install
run: npm ci

- name: Read JSR metadata
id: jsr_pkg
shell: bash
run: |
set -euo pipefail
NAME=$(node -p "require('./jsr.json').name")
VERSION=$(node -p "require('./jsr.json').version")
NPM_NAME=$(node - <<'NODE'
const name = require('./jsr.json').name;
if (!name.startsWith('@')) {
throw new Error(`Unsupported unscoped JSR package name: ${name}`);
}
const [scope, pkg] = name.slice(1).split('/');
if (!scope || !pkg) {
throw new Error(`Unsupported JSR package name: ${name}`);
}
process.stdout.write(`@jsr/${scope}__${pkg}`);
NODE
)
echo "name=$NAME" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "npm_name=$NPM_NAME" >> "$GITHUB_OUTPUT"

Comment on lines +207 to +229
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle jsr.jsonc in the publish job too.

The verify job accepts jsr.json or jsr.jsonc, but this step hardcodes require('./jsr.json'). If the repo keeps JSR metadata in jsr.jsonc, verify passes and publish_jsr fails here before the immutable-skip check runs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 207 - 229, The step currently
hardcodes require('./jsr.json') for NAME, VERSION and the NPM_NAME node heredoc;
change it to prefer jsr.jsonc when present (fall back to jsr.json) and parse
JSONC before JSON.parse so both .json and .jsonc work. Update the NAME/VERSION
assignments and the NPM_NAME node block to first check
fs.existsSync('./jsr.jsonc') and if present readFileSync('./jsr.jsonc'), strip
comments (e.g. remove // and /* */ blocks) then JSON.parse that content;
otherwise require('./jsr.json') as before, ensuring the same package name logic
that writes to NPM_NAME remains unchanged.

- name: Skip if JSR version already exists
id: jsr_exists
shell: bash
run: |
set -euo pipefail
PACKAGE="${{ steps.jsr_pkg.outputs.npm_name }}"
VERSION="${{ steps.jsr_pkg.outputs.version }}"

if npm view "${PACKAGE}@${VERSION}" version --registry=https://npm.jsr.io >/dev/null 2>&1; then
echo "::warning::JSR version ${{ steps.jsr_pkg.outputs.name }}@${VERSION} already exists; skipping immutable publish."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

- name: Publish JSR (retry x3)
if: ${{ steps.jsr_exists.outputs.skip != 'true' }}
uses: ./.github/actions/retry
with:
attempts: "3"
Expand Down Expand Up @@ -220,13 +275,24 @@ jobs:
EOF

- name: Create / update release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.verify.outputs.tag }}
prerelease: ${{ needs.verify.outputs.is_prerelease == 'true' }}
generate_release_notes: true
append_body: true
body_path: RELEASE_SUMMARY.md
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.verify.outputs.tag }}
IS_PRERELEASE: ${{ needs.verify.outputs.is_prerelease }}
shell: bash
run: |
set -euo pipefail

if gh release view "$TAG" >/dev/null 2>&1; then
echo "::warning::GitHub Release $TAG already exists; skipping immutable release update."
else
SUMMARY=$(cat RELEASE_SUMMARY.md)
args=(release create "$TAG" --generate-notes --notes "$SUMMARY")
if [[ "$IS_PRERELEASE" == "true" ]]; then
args+=(--prerelease)
fi
gh "${args[@]}"
fi
Comment on lines +278 to +295
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the full github_release job and its context
cat -n .github/workflows/release.yml | head -350 | tail -100

Repository: git-stunts/git-warp

Length of output: 4204


🏁 Script executed:

# Search for the github_release job definition to see the full context
grep -n "github_release:" .github/workflows/release.yml -A 30

Repository: git-stunts/git-warp

Length of output: 1146


🏁 Script executed:

# Check if there are other jobs in this workflow that reference repository checkout
grep -n "uses: actions/checkout" .github/workflows/release.yml

Repository: git-stunts/git-warp

Length of output: 175


🌐 Web query:

GitHub CLI gh release view GH_REPO environment variable required no checkout

💡 Result:

gh release view only needs a repository context if you’re not running inside a checked-out git repo. You can provide that context either via --repo/-R or the GH_REPO environment variable (so GH_REPO is not required if you use --repo). [1], [2]

Examples (no checkout needed):

# Option A: pass repo explicitly
gh release view v1.2.3 --repo OWNER/REPO
gh release view --repo OWNER/REPO          # shows latest release if no tag is given

[1]

# Option B: set repo via environment variable
export GH_REPO=OWNER/REPO
gh release view v1.2.3
gh release view                             # latest release

[2]

If you’re in CI / a clean environment, you may also need to provide auth via GH_TOKEN or GITHUB_TOKEN (in that precedence order). [2]


🌐 Web query:

gh CLI repository context requirements gh release create without checkout

💡 Result:

gh release create does not require a local checkout as long as gh can determine which repository to target.

How gh finds the repository context

  • In a checked-out git repo: gh infers the repo from your local git remotes (typical default behavior).
  • Without a checkout: you must specify the repo explicitly via either:
    • -R/--repo [HOST/]OWNER/REPO (per-command), or
    • GH_REPO=[HOST/]OWNER/REPO (environment variable). [2]

Creating a release without checking out the repo

gh release create v1.2.3 -R OWNER/REPO --generate-notes

or

export GH_REPO=OWNER/REPO
gh release create v1.2.3 --generate-notes

-R/--repo is supported by gh release create. [1]

Tags / targets when you don’t have local git data

  • If the tag doesn’t exist, gh will create it from the latest state of the default branch; use --target to point at a specific branch or full commit SHA. [1]
  • If you need an annotated tag, you must create it locally with git, push it, then run gh release create. [1]
  • If you want to fail instead of auto-creating a tag, use --verify-tag. [1]

Pass an explicit repository to the gh release commands.

This job lacks a repository checkout, so gh cannot infer the repository context. Both gh release view and gh release create require explicit repository specification when running outside a git repository. Without it, the commands will fail with unclear errors. Add GH_REPO: ${{ github.repository }} to the environment and pass --repo "$GH_REPO" to both release commands.

🛠️ Suggested change
         env:
           GH_TOKEN: ${{ github.token }}
+          GH_REPO: ${{ github.repository }}
           TAG: ${{ needs.verify.outputs.tag }}
           IS_PRERELEASE: ${{ needs.verify.outputs.is_prerelease }}
         shell: bash
         run: |
           set -euo pipefail

-          if gh release view "$TAG" >/dev/null 2>&1; then
+          if gh release view "$TAG" --repo "$GH_REPO" >/dev/null 2>&1; then
             echo "::warning::GitHub Release $TAG already exists; skipping immutable release update."
           else
             SUMMARY=$(cat RELEASE_SUMMARY.md)
-            args=(release create "$TAG" --generate-notes --notes "$SUMMARY")
+            args=(release create "$TAG" --repo "$GH_REPO" --generate-notes --notes "$SUMMARY")
             if [[ "$IS_PRERELEASE" == "true" ]]; then
               args+=(--prerelease)
             fi
             gh "${args[@]}"
           fi
📝 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
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.verify.outputs.tag }}
IS_PRERELEASE: ${{ needs.verify.outputs.is_prerelease }}
shell: bash
run: |
set -euo pipefail
if gh release view "$TAG" >/dev/null 2>&1; then
echo "::warning::GitHub Release $TAG already exists; skipping immutable release update."
else
SUMMARY=$(cat RELEASE_SUMMARY.md)
args=(release create "$TAG" --generate-notes --notes "$SUMMARY")
if [[ "$IS_PRERELEASE" == "true" ]]; then
args+=(--prerelease)
fi
gh "${args[@]}"
fi
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
TAG: ${{ needs.verify.outputs.tag }}
IS_PRERELEASE: ${{ needs.verify.outputs.is_prerelease }}
shell: bash
run: |
set -euo pipefail
if gh release view "$TAG" --repo "$GH_REPO" >/dev/null 2>&1; then
echo "::warning::GitHub Release $TAG already exists; skipping immutable release update."
else
SUMMARY=$(cat RELEASE_SUMMARY.md)
args=(release create "$TAG" --repo "$GH_REPO" --generate-notes --notes "$SUMMARY")
if [[ "$IS_PRERELEASE" == "true" ]]; then
args+=(--prerelease)
fi
gh "${args[@]}"
fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 278 - 295, Add an explicit
repository variable and pass it to the gh commands: add GH_REPO: ${{
github.repository }} to the env block (alongside GH_TOKEN, TAG, IS_PRERELEASE),
and update both gh invocations — the gh release view "$TAG" check and the gh
release create invocation built in the args array (which reads
RELEASE_SUMMARY.md into SUMMARY and appends --prerelease when IS_PRERELEASE is
true) — to include --repo "$GH_REPO" so the commands run outside a checked-out
repo.


- name: Fail if both registries failed
if: ${{ needs.publish_npm.result != 'success' && needs.publish_jsr.result != 'success' }}
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- **GitHub Actions runtime refresh** — Workflow actions now use Node-24-capable majors (`actions/checkout@v6`, `actions/setup-node@v6`, `actions/github-script@v8`), while the repo jobs themselves continue to run on Node 22. The release workflow now treats GitHub Releases and registry versions as immutable: if a tagged version already exists, it emits a warning and skips the repeated publish/update instead of mutating the existing release or retrying a republish.

## [14.1.0] — 2026-03-14

### Added
Expand Down
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ROADMAP — @git-stunts/git-warp

> **Current version:** v14.0.0
> **Last reconciled:** 2026-03-14 (main during v14.1.0 release prep; 26 active standalone items remain after trust/serve hardening, type-surface cleanup, large-graph traversal work, test-infra extraction, the constructor-default lint cleanup, checkpoint content-anchor batching, tree-construction determinism fuzzing, CI gate dedupe, the explicit type-only export manifest split, the merged Markdown code-sample lint gate, the pre-push gate regression harness, the missing-content blob error hardening, and the issue-45 content metadata API. The GitHub issue queue is now empty, so remaining tracked work resumes at B88, with a new benchmark slice queued for native-vs-WASM roaring evaluation.)
> **Current version:** v14.1.0
> **Last reconciled:** 2026-03-15 (main after the v14.1.0 release; 26 active standalone items remain after trust/serve hardening, type-surface cleanup, large-graph traversal work, test-infra extraction, the constructor-default lint cleanup, checkpoint content-anchor batching, tree-construction determinism fuzzing, CI gate dedupe, the explicit type-only export manifest split, the merged Markdown code-sample lint gate, the pre-push gate regression harness, the missing-content blob error hardening, and the issue-45 content metadata API. The GitHub issue queue is now empty, so remaining tracked work resumes at B88, with a new benchmark slice queued for native-vs-WASM roaring evaluation.)
> **Completed milestones:** [docs/ROADMAP/COMPLETED.md](docs/ROADMAP/COMPLETED.md)

---
Expand Down
Loading