From 5e8e2a3b81139b067e0788ae8b6027c0f2290311 Mon Sep 17 00:00:00 2001 From: roottool Date: Mon, 2 Mar 2026 02:38:15 +0900 Subject: [PATCH 1/7] ci(prepare-release-pr): update CHANGELOG header and include in release commit On release branch creation, rewrite the [Unreleased] section header to the versioned tag (e.g. [v1.2.3]) via sed, and stage CHANGELOG.md alongside package.json so the header change ships in the release PR. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/prepare-release-pr.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/prepare-release-pr.yml b/.github/workflows/prepare-release-pr.yml index 658656a..98c7ffc 100644 --- a/.github/workflows/prepare-release-pr.yml +++ b/.github/workflows/prepare-release-pr.yml @@ -57,6 +57,15 @@ jobs: run: | echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT + - name: Update CHANGELOG + run: | + VERSION="v${{ steps.version.outputs.version }}" + if grep -q "^## \[Unreleased\]" CHANGELOG.md; then + sed -i "s/^## \[Unreleased\]/## [$VERSION]/" CHANGELOG.md + else + echo "::notice::No [Unreleased] section found in CHANGELOG.md, skipping" + fi + - name: Create release branch and commit id: create_release_branch run: | @@ -64,7 +73,7 @@ jobs: git config user.name "safe-formdata[bot]" git config user.email "252148499+safe-formdata[bot]@users.noreply.github.com" git checkout -b "$BRANCH_NAME" - git add package.json + git add package.json CHANGELOG.md git commit -m "chore(release): v${{ steps.version.outputs.version }}" git push --set-upstream origin "$BRANCH_NAME" echo "branch=$BRANCH_NAME" >> $GITHUB_OUTPUT From faf26a8b5644cfe67513a49e4612ce4b54d58323 Mon Sep 17 00:00:00 2001 From: roottool Date: Mon, 2 Mar 2026 02:38:27 +0900 Subject: [PATCH 2/7] ci(publish): extract CHANGELOG section and set as GitHub Release body Add an "Extract changelog section" step that uses awk to pull the versioned section from CHANGELOG.md and trim surrounding blank lines. Pass the result to "Create GitHub Release" via an env var so the migration guide appears in the Release body alongside the auto-generated notes. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/publish.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f416770..153c8c8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -55,6 +55,21 @@ jobs: run: | echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT + - name: Extract changelog section + id: changelog + run: | + VERSION="v${{ steps.version.outputs.version }}" + BODY=$(awk "/^## \[$VERSION\]/{found=1; next} found && /^## /{exit} found{print}" CHANGELOG.md) + BODY=$(echo "$BODY" | awk 'NF{found=1} found' | awk '{a[NR]=$0} NF{last=NR} END{for(i=1;i<=last;i++) print a[i]}') + if [ -z "$BODY" ]; then + echo "::warning::No changelog section found for $VERSION in CHANGELOG.md" + fi + { + echo "body<> "$GITHUB_OUTPUT" + - name: Ensure version not published run: | VERSION="v${{ steps.version.outputs.version }}" @@ -73,6 +88,9 @@ jobs: git push origin "v${{ steps.version.outputs.version }}" - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CHANGELOG_BODY: ${{ steps.changelog.outputs.body }} run: | gh api \ --method POST \ @@ -80,7 +98,6 @@ jobs: -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/${{ github.repository }}/releases" \ -f tag_name="v${{ steps.version.outputs.version }}" \ + -f body="$CHANGELOG_BODY" \ -F draft=true \ -F generate_release_notes=true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 2ef64e2fef7bf7182b992ce80dc85768dc332867 Mon Sep 17 00:00:00 2001 From: roottool Date: Mon, 2 Mar 2026 02:40:58 +0900 Subject: [PATCH 3/7] docs(changelog): correct ParseIssue.key description for v0.2.0 key existed in v0.1.x as `key?: unknown`. The actual change was narrowing it to required `string`, not adding it from scratch. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6fa474..391bee7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,20 +33,25 @@ interface ParseIssue { **Migration**: Remove all references to `issue.path`. Because `path` was always an empty array, any reference to it was effectively a no-op and can be deleted outright. -### `ParseIssue.key` added (required, `string`) +### `ParseIssue.key` narrowed to required `string` -A required `key: string` field has been added to identify which FormData key caused the issue. +`key` existed in v0.1.x as `key?: unknown`. It is now required and typed as `string`. ```ts -// v0.1.x — no key field -issue.code; // "forbidden_key" +// v0.1.x +interface ParseIssue { + code: IssueCode; + key?: unknown; // optional, untyped +} -// v0.2.0 — key identifies the offending field -issue.code; // "forbidden_key" -issue.key; // "__proto__" +// v0.2.0 +interface ParseIssue { + code: IssueCode; + key: string; // required, typed +} ``` -**Migration**: Use `issue.key` wherever you need to identify the offending field. This is an additive change with no runtime impact, but type definitions that reference `ParseIssue` must be updated. +**Migration**: Remove any `undefined` guards on `issue.key` and update type annotations that reference `ParseIssue.key` as `unknown`. ### `issues` on failure narrowed to a non-empty tuple From 91419e03180c982e6da54de19cc743998f940c1c Mon Sep 17 00:00:00 2001 From: roottool Date: Mon, 2 Mar 2026 02:42:25 +0900 Subject: [PATCH 4/7] docs(changelog): fix ParseIssue.path code example for v0.1.x Add the missing key?: unknown field to the v0.1.x interface snapshot, and correct the comment from "added" to "narrowed" to match reality. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 391bee7..d95192a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,12 +22,13 @@ The `path` field has been removed from `ParseIssue`. interface ParseIssue { code: IssueCode; path: readonly []; // removed + key?: unknown; } // v0.2.0 interface ParseIssue { code: IssueCode; - key: string; // added (see below) + key: string; // narrowed (see below) } ``` From 21c48adcff4e6f94e8ceab1951faee623b9b20ba Mon Sep 17 00:00:00 2001 From: roottool Date: Mon, 2 Mar 2026 02:44:55 +0900 Subject: [PATCH 5/7] docs(readme): correct ParseResult type definition for v0.2.0 Replace the outdated interface with the actual discriminated union type. Update the accompanying bullet point to reflect that issues is [] on success and non-empty on failure. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5220e95..9c62a67 100644 --- a/README.md +++ b/README.md @@ -224,15 +224,14 @@ const { data, issues } = parse(formData); ### Result ```ts -export interface ParseResult { - data: Record | null; - issues: ParseIssue[]; -} +export type ParseResult = + | { data: Record; issues: [] } + | { data: null; issues: [ParseIssue, ...ParseIssue[]] }; ``` - `data` is non-null only when no boundary violations are detected - `data` is always a flat object; no structural inference is performed -- `issues` must always be checked by the caller +- Use `data !== null` to narrow the type; `issues` is `[]` on success and non-empty on failure ### Issues From 55d08d3d8deebc48a299e69ef1277c5fb5375d9e Mon Sep 17 00:00:00 2001 From: roottool Date: Mon, 2 Mar 2026 02:47:11 +0900 Subject: [PATCH 6/7] docs(skills): correct ParseResult failure issues type to non-empty tuple Replace `issues: ParseIssue[]` with `issues: [ParseIssue, ...ParseIssue[]]` in api-contract.md (type narrowing comment) and good-code.md (type definition and narrowing comment), matching the actual v0.2.0 type. Co-Authored-By: Claude Sonnet 4.6 --- skills/boundary-validator/examples/good-code.md | 4 ++-- skills/boundary-validator/references/api-contract.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/skills/boundary-validator/examples/good-code.md b/skills/boundary-validator/examples/good-code.md index 1d52088..b0be59c 100644 --- a/skills/boundary-validator/examples/good-code.md +++ b/skills/boundary-validator/examples/good-code.md @@ -232,7 +232,7 @@ export type IssueCode = "invalid_key" | "forbidden_key" | "duplicate_key"; // ✅ ParseResult discriminated union export type ParseResult = | { data: Record; issues: [] } - | { data: null; issues: ParseIssue[] }; + | { data: null; issues: [ParseIssue, ...ParseIssue[]] }; // ✅ Type narrowing with data !== null if (result.data !== null) { @@ -315,7 +315,7 @@ function handleResult(result: ParseResult) { console.log(result.issues.length); // 0 } else { // TypeScript knows: result.data is null - // TypeScript knows: result.issues is ParseIssue[] + // TypeScript knows: result.issues is [ParseIssue, ...ParseIssue[]] for (const issue of result.issues) { console.error(issue.code); } diff --git a/skills/boundary-validator/references/api-contract.md b/skills/boundary-validator/references/api-contract.md index d6c3422..2f76ca7 100644 --- a/skills/boundary-validator/references/api-contract.md +++ b/skills/boundary-validator/references/api-contract.md @@ -88,7 +88,7 @@ if (result.data !== null) { console.log(result.data.username); } else { // TypeScript knows: data is null - // TypeScript knows: issues is ParseIssue[] + // TypeScript knows: issues is [ParseIssue, ...ParseIssue[]] console.error(result.issues); } ``` From 7f1e2e84f1e038ae085a2ecf70d5b77538bd2c94 Mon Sep 17 00:00:00 2001 From: roottool Date: Mon, 2 Mar 2026 02:49:24 +0900 Subject: [PATCH 7/7] docs: fix ParseResult type and update publishing workflow description AGENTS.md: correct ParseResult failure issues type from ParseIssue[] to [ParseIssue, ...ParseIssue[]], matching the actual v0.2.0 type. docs/PUBLISHING.md: add missing "Update CHANGELOG" step to the Prepare Release PR workflow overview. Co-Authored-By: Claude Sonnet 4.6 --- AGENTS.md | 2 +- docs/PUBLISHING.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6f37971..ba224a8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -150,7 +150,7 @@ export type ParseResult = } | { data: null; - issues: ParseIssue[]; + issues: [ParseIssue, ...ParseIssue[]]; }; ``` diff --git a/docs/PUBLISHING.md b/docs/PUBLISHING.md index cc20421..7c3c72e 100644 --- a/docs/PUBLISHING.md +++ b/docs/PUBLISHING.md @@ -31,7 +31,7 @@ This runs the following steps. ## Workflow Overview -1. **Prepare Release PR**: Bump version → Create release branch → Open PR for review +1. **Prepare Release PR**: Bump version → Update CHANGELOG → Create release branch → Open PR for review 2. **Review & Merge**: Maintainer reviews and merges release PR to main 3. **Publish**: Manual trigger of publish workflow → All checks run → npm publish