Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
18 changes: 9 additions & 9 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
* @code-yeongyu
* @paulpham157

.github/workflows/* @code-yeongyu
.github/dependabot.yml @code-yeongyu
package.json @code-yeongyu
package-lock.json @code-yeongyu
LICENSE @code-yeongyu
NOTICE @code-yeongyu
README.md @code-yeongyu
CHANGELOG.md @code-yeongyu
.github/workflows/* @paulpham157
.github/dependabot.yml @paulpham157
package.json @paulpham157
package-lock.json @paulpham157
LICENSE @paulpham157
NOTICE @paulpham157
README.md @paulpham157
CHANGELOG.md @paulpham157
13 changes: 11 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ on:
pull_request:
branches: [main]
workflow_dispatch:
workflow_call:
inputs:
ref:
description: Commit to validate
required: false
type: string

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ci-${{ github.workflow }}-${{ github.event_name }}-${{ inputs.ref || github.ref }}
cancel-in-progress: true

permissions:
Expand All @@ -22,11 +28,14 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
node: ["22"]
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false

- name: Setup Node ${{ matrix.node }}
uses: actions/setup-node@v6
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: PR title

on:
pull_request:
branches: [main]
types: [opened, edited, synchronize, reopened, ready_for_review]

permissions:
contents: read

jobs:
title:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Validate Conventional Commit title
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
node <<'NODE'
const title = process.env.PR_TITLE;
if (!/^(feat|fix|perf|docs|style|refactor|test|build|ci|chore|revert)(\([^()\r\n]+\))?!?: \S[^\r\n]*$/.test(title)) {
console.error('Use a Conventional Commit PR title, e.g. fix: handle Windows paths');
process.exit(1);
}
NODE
60 changes: 36 additions & 24 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,49 +1,61 @@
name: publish

on:
release:
types: [published]
push:
branches: [main]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: npm-release
cancel-in-progress: false

permissions:
contents: read
id-token: write

jobs:
validate:
if: github.ref == 'refs/heads/main'
uses: ./.github/workflows/ci.yml
with:
ref: ${{ github.sha }}

publish:
needs: validate
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 20
permissions:
contents: write
id-token: write
steps:
- name: Checkout
- name: Checkout tested commit and release history
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
fetch-depth: 0
persist-credentials: false

- name: Setup Node 22
- name: Setup Node 24
uses: actions/setup-node@v6
with:
node-version: "22"
registry-url: https://registry.npmjs.org
node-version: "24"
cache: npm

- name: Install dependencies
run: npm ci

- name: Type check
run: npm run typecheck

- name: Lint
run: npm run lint

- name: Unit tests
run: npm test

- name: Package smoke
run: npm pack --dry-run

- name: Publish to npm
run: npm publish --access public --provenance
# The existing npm release predates semantic-release and has no Git tag.
- name: Establish existing release baseline
run: |
set -euo pipefail
baseline=74d55c8036927e5da578a86f3ca31338baa43680
git merge-base --is-ancestor "$baseline" HEAD
if git show-ref --verify --quiet refs/tags/v0.1.3; then
test "$(git rev-parse 'v0.1.3^{commit}')" = "$baseline"
else
git tag v0.1.3 "$baseline"
fi

- name: Release to npm and GitHub
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
GITHUB_TOKEN: ${{ github.token }}
run: npm run release
15 changes: 15 additions & 0 deletions .releaserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"branches": ["main"],
"tagFormat": "v${version}",
"plugins": [
["@semantic-release/commit-analyzer", { "preset": "conventionalcommits" }],
["@semantic-release/release-notes-generator", { "preset": "conventionalcommits" }],
"@semantic-release/npm",
["@semantic-release/github", {
"successComment": false,
"failComment": false,
"failTitle": false,
"releasedLabels": false
}]
]
}
14 changes: 10 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# Changelog

## [Unreleased]
## 0.1.3

### Added
- Publish the distribution as @paulpham157/apply-patch.
- Enable apply_patch for every model, replacing native edit/write tools.
- Support grammar calls where available and JSON function calls otherwise, using Pi >=0.85.1.
- Validate the entire patch before writing; confine paths to cwd and reject symlinks, existing add/move destinations, and overlapping paths.
- Include examples for creation, updates, direct moves, and deletion, with actionable syntax errors.

- Initial standalone `apply_patch` pi extension.
- Support GPT models exposed through custom `openai-responses` and `openai-codex-responses` providers.
## Earlier versions

- Initial standalone apply_patch extension with GPT-family activation.
- Support custom openai-responses and openai-codex-responses providers.
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
# pi-apply-patch
# apply-patch

One `apply_patch` tool for all model families in Pi **0.85.1 or later**. It replaces native `edit` and `write`, including after model switches, while preserving other active tools such as `bash`.
[Repository](https://github.com/paulpham157/pi-apply-patch) · [Issues](https://github.com/paulpham157/pi-apply-patch/issues)

## Usage
Universal `apply_patch` for **every model on Pi**, not just the `gpt-*` family. In the spirit of Pi's model-agnostic workflow, use the same patch tool with GPT, Claude, Gemini, DeepSeek, Kimi, GLM, Qwen, Muse, and other models available through Pi providers.

There is no model-name or provider allowlist. On Pi **0.85.1 or later**, the extension replaces native `edit` and `write` and stays active when you switch models, while preserving other active tools such as `bash`. Providers with grammar support receive grammar calls; others use the same patch engine through a standard JSON function tool.

## Installation

Requires Pi >=0.85.1 and Node >=22. Install the published package:

```bash
pi install npm:@paulpham157/apply-patch
```

To pin this release, use `pi install npm:@paulpham157/apply-patch@0.1.3`.

## Local development

```bash
npm install
Expand Down Expand Up @@ -41,6 +55,10 @@ TypeScript strict mode, Node >=22, ESM imports with `.js` suffixes, tabs, double

Design decisions are in [docs/adr](docs/adr); vocabulary is in [CONTEXT.md](CONTEXT.md).

## Releases

After CI passes on Ubuntu, macOS, and Windows, semantic-release analyzes Conventional Commits on main, selects the next version, publishes to npm, and creates a GitHub Release when needed. See [release setup and recovery](docs/releasing.md) for the required npm Trusted Publisher configuration.

## Origin and license

Forked from [code-yeongyu/pi-apply-patch](https://github.com/code-yeongyu/pi-apply-patch), originally extracted from `code-yeongyu/senpi-mono`. The patch grammar is retained; activation, transport metadata, descriptions, and file safety intentionally differ.
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@
"lineWidth": 120
},
"files": {
"includes": ["src/**/*.ts", "test/**/*.ts", "!**/node_modules/**/*", "!**/dist/**/*"]
"includes": ["src/**/*.ts", "test/**/*.ts", "test/**/*.js", "!**/node_modules/**/*", "!**/dist/**/*"]
}
}
48 changes: 48 additions & 0 deletions docs/releasing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Automatic releases

`publish.yml` runs on pushes to `main` and manual dispatches on `main`. It validates the exact commit on Ubuntu, macOS, and Windows before running semantic-release on Ubuntu with Node 24. All three CI jobs must pass. Direct pushes to main also trigger this process; use branch protection to require PRs.

## Commit and merge convention

Use squash merge with the PR title as the squash commit title. Make the `PR title / title` check and all three CI checks required in branch protection. Configure GitHub's squash commit default to **Pull request title**, and disable merge/rebase merging if every merged PR must follow this convention. The title check cannot enforce repository merge settings or prevent editing the final squash message.

| Title | Release |
| --- | --- |
| `fix: handle Windows paths` | Patch |
| `perf: reduce patch parsing overhead` | Patch |
| `feat: add a patch operation` | Minor |
| `feat!: change the input contract` | Major |
| `docs: explain installation` | None |
| `ci: add Windows tests` | None |

A `BREAKING CHANGE:` footer also triggers a major release. Breaking changes follow normal SemVer even while the package is at `0.x`: a major change can release `1.0.0`. The highest release type among commits since the last release wins. Do not manually bump the version for each PR.

## One-time npm setup

Configure a GitHub Actions Trusted Publisher in the npm settings for `@paulpham157/apply-patch`:

| Field | Value |
| --- | --- |
| Organization or user | `paulpham157` |
| Repository | `pi-apply-patch` |
| Workflow filename | `publish.yml` |
| Environment | Leave blank |
| Allowed action | Enable direct `npm publish` |

The workflow uses OIDC with `id-token: write`; no npm token or interactive OTP is needed. GitHub's built-in token creates tags and releases through `contents: write`. Tag rules must allow these releases. Issue/PR comments and labels are disabled.

## Version history and artifacts

The already published npm version `0.1.3` reports gitHead `74d55c8036927e5da578a86f3ca31338baa43680`. The workflow establishes a local `v0.1.3` baseline tag at that commit if absent and rejects a conflicting tag. This prevents semantic-release from treating the package as an unversioned first release. Its next release pushes the baseline with the release tags.

semantic-release updates package metadata in the publishing workspace, publishes npm, and creates a GitHub Release with generated notes. It does not commit version bumps or changelog updates to main. `package.json` on main remains a development baseline; npm has the released version. `CHANGELOG.md` retains historical entries; new release notes live in GitHub Releases.

Only one release workflow runs at a time. GitHub may replace a pending run with a newer one; semantic-release includes all unreleased commits, so it does not require a separate run or version for every PR. An outdated run may skip publication if main has advanced; the newer run handles those changes after its own CI passes.

## Failure recovery and verification

For failures before publication, fix the cause and dispatch `publish` on main. If npm publication succeeded but GitHub Release creation failed, inspect npm and Git tags before recovery: rerunning semantic-release does not guarantee it repairs every partially completed release. Never delete a published version's tag to force a retry.

Local checks cover extension tests and commit classification. OIDC authentication and actual publishing require the first GitHub Actions run after setup. Do not treat a local check as proof of publishing permissions.

References: [semantic-release GitHub Actions](https://semantic-release.org/recipes/ci-configurations/github-actions/), [commit conventions](https://semantic-release.org/intro/), [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers/).
Loading