From a3956455364550fb6544341b0f1e84270e5e62ad Mon Sep 17 00:00:00 2001 From: aeei <18022843+aeei@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:00:33 +0900 Subject: [PATCH] docs(skill): add upstream fork sync workflow --- .pi/skills/sync-upstream-fork/SKILL.md | 190 ++++++++++++++++++ .../references/ownership-template.md | 16 ++ .../references/release-gates.md | 35 ++++ .../scripts/analyze-fork.sh | 53 +++++ 4 files changed, 294 insertions(+) create mode 100644 .pi/skills/sync-upstream-fork/SKILL.md create mode 100644 .pi/skills/sync-upstream-fork/references/ownership-template.md create mode 100644 .pi/skills/sync-upstream-fork/references/release-gates.md create mode 100755 .pi/skills/sync-upstream-fork/scripts/analyze-fork.sh diff --git a/.pi/skills/sync-upstream-fork/SKILL.md b/.pi/skills/sync-upstream-fork/SKILL.md new file mode 100644 index 000000000..79bd8b259 --- /dev/null +++ b/.pi/skills/sync-upstream-fork/SKILL.md @@ -0,0 +1,190 @@ +--- +name: sync-upstream-fork +description: Synchronize a maintained GitHub fork with an upstream branch or release tag while preserving downstream customizations, package identities, release boundaries, and vendored consumer artifacts. Use for "upstream sync", "fork 최신화", "fork rebase/merge", integrating an upstream release, canonicalizing fork branches, or planning recurring upstream maintenance. +version: 1 +created: 2026-08-05 +updated: 2026-08-05 +--- + +# Sync Upstream Fork + +## Goal + +Integrate reviewed upstream history without rewriting shared downstream history or silently dropping fork-owned behavior. + +## This repository + +- Downstream: `aeei/docusaurus-theme` +- Upstream: `PaloAltoNetworks/docusaurus-openapi-docs` +- Canonical branch: `main` +- Baseline SSOT: `UPSTREAM.md` +- Upstream-owned: OpenAPI plugin, schema renderer, demo, and their package versions +- AEEI-owned: `packages/docusaurus-theme/`, `examples/docs-starter/`, Base Nova audits/visual tests, legal notices, and `@aeei/docusaurus-theme` release +- Shared reconciliation: root manifests, `yarn.lock`, workflows, and top-level docs +- Consumer artifact: Deck repository-owned `docs/manual/vendor/docusaurus-theme.tgz` + +Re-query the upstream latest GitHub release and default-branch head on every run. Never assume the baseline recorded in `UPSTREAM.md` is still current. + +## Non-negotiable rules + +- Read repository instructions and `UPSTREAM.md` before mutation. +- Use an isolated clean worktree. Never stash, reset, clean, force-push, or move unrelated user changes. +- Never rebase a shared canonical branch onto upstream. Merge an exact upstream tag/commit through a sync PR. +- Do not call an unsigned tag signed. Record its actual object type and verification result. +- Never resolve lockfiles with a blind `ours` or `theirs`. Regenerate them from the combined manifests. +- Preserve package identity and downstream release boundaries. Never reuse a package version for different archive bytes. +- Keep upstream sync, downstream feature work, package publication, and consumer artifact updates in separate reviewable stages. + +## Workflow + +### 1. Establish repository truth + +1. Find repo root, current branch, dirty state, remotes, and GitHub fork metadata. +2. Resolve the canonical downstream branch from repository policy—not from stale GitHub defaults. +3. Identify the upstream repository, branch, exact release tag/commit, and current integrated baseline. +4. If duplicate downstream branches have unrelated histories: + - compare semantic contents and commit ancestry; + - archive stale heads with annotated tags; + - switch default/protection/workflow targets to the canonical branch; + - delete stale branches only after explicit authorization and recovery-tag verification. + +Recommended evidence: + +```bash +git status --short +git remote -v +gh api repos/OWNER/REPO --jq '{fork,parent:.parent.full_name,default_branch}' +git rev-list --left-right --count DOWNSTREAM...UPSTREAM +git merge-base DOWNSTREAM UPSTREAM +``` + +### 2. Analyze divergence before choosing a strategy + +Run [`scripts/analyze-fork.sh`](scripts/analyze-fork.sh) with downstream and upstream refs. + +Review: + +- ahead/behind counts; +- common-base identity; +- changed-file overlap; +- `git merge-tree` predicted conflicts; +- functional/security upstream commits versus dependency-only commits; +- branch, workflow, version, and package-name drift. + +Decision: + +- **Tag merge**: default for maintained forks with shared history. +- **Selective cherry-pick**: only when fork policy intentionally tracks a documented subset. +- **Rebase**: only for an unpublished private patch queue and explicit user approval. + +### 3. Record ownership + +Create or verify a path ownership map. Use [`references/ownership-template.md`](references/ownership-template.md). + +Typical classes: + +- upstream-owned source; +- downstream-owned source; +- shared reconciliation surfaces; +- generated files and lockfiles; +- release metadata and consumer artifacts. + +Do not resolve a conflict until its owner and preserved behavior are known. + +### 4. Merge on an integration branch + +```bash +git switch -c sync/upstream-vX.Y.Z origin/CANONICAL +git merge --no-ff upstream-tag-or-commit +``` + +For each conflict: + +1. inspect base/ours/theirs; +2. apply the ownership policy; +3. regenerate lock/generated output from combined source inputs; +4. add a regression contract when the conflict exposes an undocumented invariant. + +Preserve the upstream merge commit. If GitHub disallows merge commits, change repository merge policy before merging the sync PR; do not flatten upstream history accidentally. + +### 5. Enforce artifact immutability + +Before and after sync: + +1. build the downstream package from a clean dependency state; +2. create the real package archive, not only `--dry-run` output; +3. compare archive file lists, semantic content, and cryptographic hashes; +4. if bytes change, bump the downstream package version—even when only toolchain formatting changed; +5. rebuild and record the final hash; +6. update every repository-owned consumer archive, integrity, lockfile, and contract in a separate consumer PR. + +Never publish or vendor two different archives under one version. + +### 6. Validate both inheritance directions + +Run all relevant checks: + +- frozen dependency install; +- upstream unit/integration regression tests; +- downstream contracts and independent review; +- all package builds; +- starter/example production build; +- browser/accessibility/visual matrix; +- package contents, notices, provenance, and hash; +- LSP/type diagnostics; +- workflow lint and `git diff --check`. + +Use [`references/release-gates.md`](references/release-gates.md) for release and consumer gates. + +### 7. Deliver in ordered PRs + +1. Canonical branch/settings PR and repository-setting changes. +2. Upstream sync PR with merge history preserved. +3. Fork-specific release workflow/policy PR. +4. Consumer vendored-artifact PR when archive bytes changed. +5. Documentation/skill update after the process is proven. + +PR body must state: + +- upstream tag and commit; +- previous baseline; +- ahead/behind and predicted conflicts; +- conflict resolutions; +- downstream package version/hash decision; +- validation evidence; +- residual external configuration. + +### 8. Release safely + +- Upstream release automation must not publish fork-owned or upstream packages accidentally. +- Prefer manual environment-gated npm trusted publishing for a fork-owned package. +- Build/package in an unprivileged job; hand off one verified tarball to the privileged publish job. +- Pin Node/npm and action SHAs. +- Verify tar manifest name/version, artifact SHA-512, registry `dist.integrity`, and exact source SHA. +- Reserve or verify the immutable source tag before npm publish. Support a documented exact-SHA retry/recovery path. +- Protect release tags from update, deletion, and non-fast-forward mutation. +- Treat npm/GitHub environment bindings as external release gates; never claim publish-ready until verified. + +## Stop conditions + +Stop and ask the user when: + +- canonical branch or upstream source is ambiguous; +- upstream tag provenance is unexpected; +- conflict ownership is unclear; +- sync changes a public API or package identity without a migration decision; +- archive bytes change but no version bump/consumer update is approved; +- required credentials, trusted-publisher binding, environment approval, or tag policy is missing; +- any required validation fails. + +## Final report + +Report: + +- canonical/downstream/upstream refs and commits; +- merge strategy and conflict list; +- preserved downstream surfaces; +- package version and archive hash before/after; +- PRs/settings changed; +- validation results; +- remaining external or operational risks. diff --git a/.pi/skills/sync-upstream-fork/references/ownership-template.md b/.pi/skills/sync-upstream-fork/references/ownership-template.md new file mode 100644 index 000000000..147fe1457 --- /dev/null +++ b/.pi/skills/sync-upstream-fork/references/ownership-template.md @@ -0,0 +1,16 @@ +# Fork ownership template + +Fill this before resolving upstream conflicts. + +| Surface | Owner | Resolution rule | Required validation | +| ---------------------------- | ------------------- | --------------------------------------------------------------------------- | ------------------------------------ | +| Original upstream packages | Upstream | Take reviewed upstream tag unless downstream contract intentionally differs | Upstream tests/build | +| Fork-owned packages/features | Downstream | Preserve downstream behavior and package identity | Downstream contracts/browser/package | +| Root manifests | Shared | Reconcile all workspaces and engines explicitly | Frozen install/full build | +| Lockfiles | Generated | Regenerate from combined manifests; never blind ours/theirs | Frozen reinstall/no diff | +| Workflows | Shared | Preserve fork repo guards and adopt compatible upstream security fixes | actionlint/policy tests | +| Notices/provenance | Shared | Distinguish runtime version from copied-source snapshot | Legal/package contracts | +| Release versions/tags | Downstream | Never reuse version for changed bytes | Pack hash/registry/tag checks | +| Vendored consumer archive | Downstream consumer | Update exact archive, hash, integrity, and every consumer lockfile together | Clean consumer installs/builds | + +Record intentional exceptions in the repository's `UPSTREAM.md` or equivalent SSOT. diff --git a/.pi/skills/sync-upstream-fork/references/release-gates.md b/.pi/skills/sync-upstream-fork/references/release-gates.md new file mode 100644 index 000000000..9e82791dd --- /dev/null +++ b/.pi/skills/sync-upstream-fork/references/release-gates.md @@ -0,0 +1,35 @@ +# Release and consumer gates + +## Package gate + +- [ ] Exact package name and version verified inside the tar manifest. +- [ ] Real tarball generated from clean build output. +- [ ] SHA-256/SHA-512 recorded. +- [ ] Previous archive compared semantically and byte-for-byte. +- [ ] Version bumped when bytes changed. +- [ ] Licenses, notices, copied-source provenance, and package file list verified. +- [ ] No unrelated workspace package can be published by the workflow. + +## Trusted publishing gate + +- [ ] Workflow is manual-only and canonical-main-only. +- [ ] Exact workflow filename/repository/environment bound in npm trusted publisher settings. +- [ ] Build job has read-only contents and no persisted Git credentials or OIDC permission. +- [ ] Privileged job has no checkout/build/untrusted code execution. +- [ ] Protected environment reviewer and branch policy verified through GitHub API/UI. +- [ ] Exact version and operation confirmation required. +- [ ] Source SHA is a canonical-main ancestor and matches checked-out HEAD. +- [ ] Source tag is reserved/verified before npm publish. +- [ ] Tag update/deletion/non-fast-forward protections active. +- [ ] Registry `dist.integrity` equals the uploaded tarball. +- [ ] Exact-SHA retry/recovery path documented and tested. + +## Consumer gate + +- [ ] Repository-owned archive replaced with the reviewed package tarball. +- [ ] Consumer contract expected hash/version updated. +- [ ] Every consumer lockfile records identical local path, version, and integrity. +- [ ] Frozen installs pass with the CI package-manager major/version. +- [ ] Typechecks, production builds, runtime packaging, and browser audit pass. +- [ ] Behavior delta/spec updated when externally observable behavior changes. +- [ ] Consumer PR merged only after upstream package PR and validation are complete. diff --git a/.pi/skills/sync-upstream-fork/scripts/analyze-fork.sh b/.pi/skills/sync-upstream-fork/scripts/analyze-fork.sh new file mode 100755 index 000000000..06fa07ded --- /dev/null +++ b/.pi/skills/sync-upstream-fork/scripts/analyze-fork.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + echo "usage: $0 " >&2 + exit 64 +} + +[[ $# -eq 2 ]] || usage + +downstream=$1 +upstream=$2 + +git rev-parse --is-inside-work-tree >/dev/null +git rev-parse --verify "${downstream}^{commit}" >/dev/null +git rev-parse --verify "${upstream}^{commit}" >/dev/null + +base=$(git merge-base "$downstream" "$upstream") +read -r ahead behind < <(git rev-list --left-right --count "$downstream...$upstream") + +tmp_dir=$(mktemp -d) +trap 'rm -rf "$tmp_dir"' EXIT + +git diff --name-only "$base..$downstream" | LC_ALL=C sort >"$tmp_dir/downstream-files" +git diff --name-only "$base..$upstream" | LC_ALL=C sort >"$tmp_dir/upstream-files" +comm -12 "$tmp_dir/downstream-files" "$tmp_dir/upstream-files" >"$tmp_dir/overlap-files" + +printf 'downstream: %s (%s)\n' "$downstream" "$(git rev-parse "${downstream}^{commit}")" +printf 'upstream: %s (%s)\n' "$upstream" "$(git rev-parse "${upstream}^{commit}")" +printf 'merge-base: %s\n' "$base" +printf 'ahead: %s\n' "$ahead" +printf 'behind: %s\n' "$behind" +printf 'downstream changed files: %s\n' "$(wc -l <"$tmp_dir/downstream-files" | tr -d ' ')" +printf 'upstream changed files: %s\n' "$(wc -l <"$tmp_dir/upstream-files" | tr -d ' ')" +printf 'overlapping files: %s\n' "$(wc -l <"$tmp_dir/overlap-files" | tr -d ' ')" + +if [[ -s "$tmp_dir/overlap-files" ]]; then + echo + echo 'overlap:' + cat "$tmp_dir/overlap-files" +fi + +echo +echo 'merge-tree preview:' +set +e +git merge-tree --write-tree "$downstream" "$upstream" +merge_tree_status=$? +set -e +printf 'merge-tree exit: %s\n' "$merge_tree_status" + +# A conflict preview is evidence, not a script failure. Missing refs and other +# preflight errors already fail above. +exit 0