From 50f55f662bde7cff47fdf7922f3021c365632947 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sun, 11 Oct 2026 21:25:44 -0700 Subject: [PATCH] refactor(scripts): dedupe percentile and release helpers --- scripts/approval-forensics.ts | 12 +----- scripts/intervention-forensics.ts | 12 +----- scripts/percentile.ts | 12 ++++++ scripts/release.sh | 54 +++++++++++++++------------ scripts/tool-fingerprint-forensics.ts | 15 ++------ scripts/verify-corbits-only-scope.sh | 5 --- 6 files changed, 48 insertions(+), 62 deletions(-) create mode 100644 scripts/percentile.ts diff --git a/scripts/approval-forensics.ts b/scripts/approval-forensics.ts index d8bc776c4..7c54163d7 100644 --- a/scripts/approval-forensics.ts +++ b/scripts/approval-forensics.ts @@ -16,22 +16,12 @@ import { join } from "node:path"; import { homedir } from "node:os"; import { findAll } from "./find-all.js"; +import { percentile } from "./percentile.js"; import { APPROVAL_LOG_FILE, type ApprovalRecord, } from "../src/permission/approval-log.js"; -function percentile(sorted: readonly number[], p: number): number { - if (sorted.length === 0) return 0; - const index = Math.min( - sorted.length - 1, - Math.floor((p / 100) * sorted.length), - ); - const value = sorted[index]; - if (value === undefined) return 0; - return value; -} - interface Bucket { count: number; byOutcome: Map; diff --git a/scripts/intervention-forensics.ts b/scripts/intervention-forensics.ts index fe9024872..72ca21683 100644 --- a/scripts/intervention-forensics.ts +++ b/scripts/intervention-forensics.ts @@ -25,22 +25,12 @@ import { join } from "node:path"; import { homedir } from "node:os"; import { findAll } from "./find-all.js"; +import { percentile } from "./percentile.js"; import { INTERVENTION_FILE, type InterventionRecord, } from "../src/subagent/intervention-log.js"; -function percentile(sorted: readonly number[], p: number): number { - if (sorted.length === 0) return 0; - const index = Math.min( - sorted.length - 1, - Math.floor((p / 100) * sorted.length), - ); - const value = sorted[index]; - if (value === undefined) return 0; - return value; -} - interface Bucket { count: number; byFamily: Map; diff --git a/scripts/percentile.ts b/scripts/percentile.ts new file mode 100644 index 000000000..a576f251e --- /dev/null +++ b/scripts/percentile.ts @@ -0,0 +1,12 @@ +// Shared percentile for the forensics scripts. `sorted` must be sorted in +// ascending order; an empty array yields 0. +export function percentile(sorted: readonly number[], p: number): number { + if (sorted.length === 0) return 0; + const index = Math.min( + sorted.length - 1, + Math.floor((p / 100) * sorted.length), + ); + const value = sorted[index]; + if (value === undefined) return 0; + return value; +} diff --git a/scripts/release.sh b/scripts/release.sh index 30fd2de34..8498d187e 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -47,7 +47,6 @@ TAP_REPO="corbitsdev/homebrew-tap" # tap repo (formula) TAP_SLUG="corbitsdev/tap" # `brew tap' name of TAP_REPO BINARY="corbits" # CLI binary + tarball/deb stem BREW_FORMULA="corbits-code" # `brew install` name (file Formula/$BREW_FORMULA.rb) -FORMULA="$BINARY" # legacy alias used in package paths DESC="Single-process coding agent CLI built on the Interchange runtime" DOC_FILES=(LICENSE.md README.md CHANGELOG.md GPLv2-AI-Exception.md GPL-2.0.txt) # shipped with the binary @@ -94,6 +93,9 @@ step() { printf '\n\033[1;34m==>\033[0m \033[1m%s\033[0m\n' "$*"; } info() { printf ' %s\n' "$*"; } skip() { printf ' \033[2m(skip) %s\033[0m\n' "$*"; } die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } +is_blank() { # is_blank STRING — true when STRING is empty or only whitespace + [ -z "$(printf '%s' "$1" | sed '/^[[:space:]]*$/d')" ] +} # Push a repo's HEAD (and optionally a ref) to origin. HTTPS remotes authenticate # via `gh auth git-credential` (token never lands in argv/URLs/process lists). @@ -196,21 +198,27 @@ tar_root() { # tar_root OUTPUT.tgz DIR PATH... fi } +# First-party plugins ship next to the binary (discoverRepoPlugins execPath); +# copy them into a package payload dir or a tarball staging dir. +copy_plugins() { # copy_plugins DEST_DIR + if [ -d "$ROOT/plugins" ]; then + cp -R "$ROOT/plugins" "$1/plugins" + fi +} + # Build a Debian package by hand: a .deb is an `ar` archive of debian-binary, # control.tar.gz, and data.tar.gz (in that order). `ar rcS` keeps that order # and skips the symbol table macOS `ar` would otherwise prepend. build_deb() { # build_deb BINARY DEB-ARCH OUTPUT.deb local bin=$1 arch=$2 out=$3 local wd; wd=$(mktemp -d) - mkdir -p "$wd/data/usr/bin" "$wd/data/usr/share/doc/$FORMULA" "$wd/ctrl" - install -m 0755 "$bin" "$wd/data/usr/bin/$FORMULA" - for f in "${DOC_FILES[@]}"; do cp "$ROOT/$f" "$wd/data/usr/share/doc/$FORMULA/"; done - if [ -d "$ROOT/plugins" ]; then - cp -R "$ROOT/plugins" "$wd/data/usr/bin/plugins" - fi + mkdir -p "$wd/data/usr/bin" "$wd/data/usr/share/doc/$BINARY" "$wd/ctrl" + install -m 0755 "$bin" "$wd/data/usr/bin/$BINARY" + for f in "${DOC_FILES[@]}"; do cp "$ROOT/$f" "$wd/data/usr/share/doc/$BINARY/"; done + copy_plugins "$wd/data/usr/bin" local kb; kb=$(( ( $(wc -c < "$bin") + 1023 ) / 1024 )) cat > "$wd/ctrl/control" < "$pkg.tar.gz.sha256" ) rm -rf "$STAGE/$pkg" @@ -466,11 +472,11 @@ for entry in "${TARGETS[@]}"; do # Debian package for the Linux targets. if [ "$kind" = "linux" ] && [ "$SKIP_DEB" != 1 ]; then - deb="$STAGE/${FORMULA}_${VERSION}_${debarch}.deb" + deb="$STAGE/${BINARY}_${VERSION}_${debarch}.deb" if [ -f "$deb" ] && [ -f "$deb.sha256" ]; then skip "$(basename "$deb") already built" else - bin="$STAGE/$FORMULA-$label.bin" + bin="$STAGE/$BINARY-$label.bin" [ -s "$bin" ] || { # tarball was cached: recompile the raw binary for the deb info "recompiling $label for .deb" compile_bin "$target" "$bin" @@ -482,7 +488,7 @@ for entry in "${TARGETS[@]}"; do info "packaged $(basename "$deb") ($(du -h "$deb" | cut -f1))" fi fi - rm -f "$STAGE/$FORMULA-$label.bin" + rm -f "$STAGE/$BINARY-$label.bin" done # ---- 4. land the release commit on main via PR, then tag ------------------ @@ -505,7 +511,7 @@ else --json number --jq '.[0].number // empty') if [ -z "$PR_NUM" ]; then gh pr create --repo "$MAIN_REPO" --head "$RELEASE_BRANCH" --base main \ - --title "chore(release): $FORMULA $VERSION" \ + --title "chore(release): $BINARY $VERSION" \ --body "Version bump to $VERSION. Release notes are generated from the pull requests merged since the previous release." >/dev/null PR_NUM=$(gh pr list --repo "$MAIN_REPO" --head "$RELEASE_BRANCH" --state open \ --json number --jq '.[0].number // empty') @@ -541,10 +547,10 @@ if git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; the else if ! git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then if [ "$DO_PUSH" = 1 ]; then - git tag -a "$TAG" -m "$FORMULA $VERSION" "origin/main" + git tag -a "$TAG" -m "$BINARY $VERSION" "origin/main" info "tagged $TAG at origin/main" else - git tag -a "$TAG" -m "$FORMULA $VERSION" + git tag -a "$TAG" -m "$BINARY $VERSION" info "tagged $TAG at HEAD (--no-push)" fi fi diff --git a/scripts/tool-fingerprint-forensics.ts b/scripts/tool-fingerprint-forensics.ts index 1fc26a91e..1dbce8fad 100644 --- a/scripts/tool-fingerprint-forensics.ts +++ b/scripts/tool-fingerprint-forensics.ts @@ -19,6 +19,7 @@ import { join } from "node:path"; import { homedir } from "node:os"; import { findAll } from "./find-all.js"; +import { percentile } from "./percentile.js"; function stableJson(value: unknown): string { if (value === null || typeof value !== "object") return JSON.stringify(value); @@ -132,14 +133,6 @@ for (const file of files) { } runLengths.sort((a, b) => a - b); -function percentile(p: number): number { - if (runLengths.length === 0) return 0; - const idx = Math.min( - runLengths.length - 1, - Math.floor((p / 100) * runLengths.length), - ); - return runLengths[idx] as number; -} console.log( JSON.stringify( @@ -147,9 +140,9 @@ console.log( sessionFilesScanned: files.length, sessionsWithToolOnlyRun, totalToolOnlyRuns: runLengths.length, - runLengthP50: percentile(50), - runLengthP90: percentile(90), - runLengthP99: percentile(99), + runLengthP50: percentile(runLengths, 50), + runLengthP90: percentile(runLengths, 90), + runLengthP99: percentile(runLengths, 99), runLengthMax: runLengths[runLengths.length - 1] ?? 0, // Largest exact-repeat count observed per period; 1 means no repeat // beyond the base occurrence. diff --git a/scripts/verify-corbits-only-scope.sh b/scripts/verify-corbits-only-scope.sh index 54607a4fe..cca1b30bf 100644 --- a/scripts/verify-corbits-only-scope.sh +++ b/scripts/verify-corbits-only-scope.sh @@ -13,11 +13,6 @@ changed="$( } | sed '/^$/d' | LC_ALL=C sort -u )" -if [ -z "$changed" ]; then - echo "OK: no forbidden path changes detected." - exit 0 -fi - violations="$(printf '%s\n' "$changed" | grep -E "$forbidden" || true)" if [ -n "$violations" ]; then echo "SCOPE VIOLATION: changes under vendor/ are not allowed for this wave."