mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-06 10:48:12 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Instances expose ~23 experimental feature settings, all declared in one shared zod schema and toggled per instance > - Deployment tooling and hosting control planes have no machine-readable list of those feature keys for a given release — the schema is only reachable from code that imports the package > - Any external system that references feature keys therefore does so as free text, and typos drift silently > - This pull request derives a versioned `feature-catalog.json` build artifact from the schema, with a compiler-checked metadata map so the schema stays the single source of truth > - The benefit is a stable contract external tooling can validate feature-key references against, with zero runtime behavior change ## Linked Issues or Issue Description No public issue exists; `feature_request` template fields: **Problem or motivation:** External deployment tooling cannot enumerate or validate an instance's feature keys per release; free-text references fail silently when keys are renamed or removed. **Proposed solution:** A metadata map keyed by the settings schema's own keys (compiler flags drift) plus a build step emitting `feature-catalog.json` (keys, tiers, defaults, `catalogVersion`) as a release artifact. **Alternatives considered:** A hand-maintained catalog file (drifts from the schema); serving the schema from a runtime API (requires a running instance at validation time — a build artifact works offline and pins to a release). **Roadmap alignment:** Supports the in-progress "Cloud deployments" milestone in `ROADMAP.md`. ## What Changed Adds a metadata map (title, description, tier, cloud/self-hosted defaults) keyed by the keys of `instanceExperimentalSettingsSchema`, so the schema stays the single source of truth and the compiler flags any drift. A new build step (`build:feature-catalog --version <v>`) emits `feature-catalog.json` — all 23 feature keys, their tiers, and a `catalogVersion` — as a release artifact that managed-hosting control planes can validate feature-flag writes against. No runtime behavior changes. - New `packages/shared/src/feature-catalog.ts`: per-flag metadata map keyed by a type derived from the settings schema (adding/removing/renaming a flag without updating the map is a compile error), plus `featureCatalogArtifactSchema` and `buildFeatureCatalogArtifact`/`renderFeatureCatalogArtifact` for the artifact - New `scripts/generate-feature-catalog.ts` wired as `pnpm build:feature-catalog --version <v>` - `scripts/create-github-release.sh` generates the artifact and uploads it as a GitHub Release asset (with a dry-run preview line) - Tests in `packages/shared/src/feature-catalog.test.ts` ## Verification - `vitest run packages/shared/src/feature-catalog.test.ts` — 9 tests: schema-key coverage, drift detection, artifact shape - `pnpm --filter @paperclipai/shared typecheck` - Artifact generation run end-to-end: `pnpm build:feature-catalog --version 0.0.0-test` emits 23 keys with `catalogVersion` ## Risks Low risk — no runtime behavior changes; the change is metadata, a build script, and a release-artifact emission step only. ## Model Used Claude Fable 5 (`claude-fable-5`), extended thinking, agentic tool use; independently peer-reviewed by a second AI agent before push ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
125 lines
3.7 KiB
Bash
Executable File
125 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
# shellcheck source=./release-lib.sh
|
|
. "$REPO_ROOT/scripts/release-lib.sh"
|
|
|
|
dry_run=false
|
|
version=""
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
./scripts/create-github-release.sh <version> [--dry-run]
|
|
|
|
Examples:
|
|
./scripts/create-github-release.sh 2026.318.0
|
|
./scripts/create-github-release.sh 2026.318.0 --dry-run
|
|
|
|
Notes:
|
|
- Run this after pushing the stable tag.
|
|
- Resolves the git remote automatically.
|
|
- In GitHub Actions, origin is used explicitly.
|
|
- If the release already exists, this script updates its title and notes.
|
|
EOF
|
|
}
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--dry-run) dry_run=true ;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
if [ -n "$version" ]; then
|
|
echo "Error: only one version may be provided." >&2
|
|
exit 1
|
|
fi
|
|
version="$1"
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
if [ -z "$version" ]; then
|
|
usage
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "Error: version must be a stable calendar version like 2026.318.0." >&2
|
|
exit 1
|
|
fi
|
|
|
|
tag="v$version"
|
|
notes_file="$REPO_ROOT/releases/${tag}.md"
|
|
if [ "${GITHUB_ACTIONS:-}" = "true" ] && [ -z "${PUBLISH_REMOTE:-}" ] && git_remote_exists origin; then
|
|
PUBLISH_REMOTE=origin
|
|
fi
|
|
PUBLISH_REMOTE="$(resolve_release_remote)"
|
|
if ! command -v gh >/dev/null 2>&1; then
|
|
echo "Error: gh CLI is required to create GitHub releases." >&2
|
|
exit 1
|
|
fi
|
|
|
|
GITHUB_REPO="$(github_repo_from_remote "$PUBLISH_REMOTE" || true)"
|
|
if [ -z "$GITHUB_REPO" ]; then
|
|
echo "Error: could not determine GitHub repository from remote $PUBLISH_REMOTE." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "$notes_file" ]; then
|
|
echo "Error: release notes file not found at $notes_file." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! git -C "$REPO_ROOT" rev-parse "$tag" >/dev/null 2>&1; then
|
|
echo "Error: local git tag $tag does not exist." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# The catalog is derived from the checked-out sources, so it must be generated
|
|
# from the exact commit the release tag points at, with no local edits.
|
|
tag_commit="$(git -C "$REPO_ROOT" rev-parse "$tag^{commit}")"
|
|
head_commit="$(git -C "$REPO_ROOT" rev-parse HEAD)"
|
|
if [ "$head_commit" != "$tag_commit" ]; then
|
|
echo "Error: HEAD ($head_commit) does not match tag $tag ($tag_commit). Check out the release tag before generating the feature catalog." >&2
|
|
exit 1
|
|
fi
|
|
if [ -n "$(git -C "$REPO_ROOT" status --porcelain --untracked-files=no)" ]; then
|
|
echo "Error: working tree has uncommitted changes. The feature catalog must be generated from the pristine release commit." >&2
|
|
exit 1
|
|
fi
|
|
|
|
catalog_dir="$(mktemp -d)"
|
|
trap 'rm -rf "$catalog_dir"' EXIT
|
|
catalog_file="$catalog_dir/feature-catalog.json"
|
|
node "$REPO_ROOT/cli/node_modules/tsx/dist/cli.mjs" \
|
|
"$REPO_ROOT/scripts/generate-feature-catalog.ts" \
|
|
--version "$version" \
|
|
--out "$catalog_file"
|
|
|
|
if [ "$dry_run" = true ]; then
|
|
echo "[dry-run] gh release create $tag -R $GITHUB_REPO --title $tag --notes-file $notes_file"
|
|
echo "[dry-run] gh release upload $tag -R $GITHUB_REPO --clobber $catalog_file"
|
|
exit 0
|
|
fi
|
|
|
|
if ! git -C "$REPO_ROOT" ls-remote --exit-code --tags "$PUBLISH_REMOTE" "refs/tags/$tag" >/dev/null 2>&1; then
|
|
echo "Error: remote tag $tag was not found on $PUBLISH_REMOTE. Push the release commit and tag first." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if gh release view "$tag" -R "$GITHUB_REPO" >/dev/null 2>&1; then
|
|
gh release edit "$tag" -R "$GITHUB_REPO" --title "$tag" --notes-file "$notes_file"
|
|
echo "Updated GitHub Release $tag"
|
|
else
|
|
gh release create "$tag" -R "$GITHUB_REPO" --title "$tag" --notes-file "$notes_file"
|
|
echo "Created GitHub Release $tag"
|
|
fi
|
|
|
|
gh release upload "$tag" -R "$GITHUB_REPO" --clobber "$catalog_file"
|
|
echo "Uploaded feature-catalog.json to GitHub Release $tag"
|