Xcode updates the String Catalogs after an IDE build, but xcodebuild
only emits the compiler's .stringsdata. scripts/localize sync builds
the app and runs `xcrun xcstringstool sync` over that data so the
catalogs can be kept current (and checked in CI) from the command line.
verify keeps catalogs in UI targets and rejects empty translations,
which render blank rather than falling back to English; status
summarises per-language progress.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Keep the project overview and development setup in `README.md`.
- Put system design in `ARCHITECTURE.md` and detailed contributor and
agent guidance in `AGENTS.md`; symlink `CLAUDE.md` to that source.
- Remove duplicate rules, memory files and unused Jekyll tooling.
`.swiftlint.yml` had `included: Brew`, so `swiftlint lint --strict` skipped
the app targets entirely; it now lists `Homebrew` and `HomebrewUpgradeHelper`.
`pr_build_test.yml` filtered on `Brew/**` and `Brew.entitlements`, so an
app-only change could skip Build and Test. No entitlements file exists
anywhere in the tree, so that entry is dropped rather than repointed.
BrewUILint's glob reached `Homebrew` but not `HomebrewUpgradeHelper`, leaving
2 files unlinted; CI, `scripts/pre-commit` and AGENTS.md now agree on
`find Homebrew HomebrewUpgradeHelper Sources`.
CONVENTIONS.md and two Cursor rules pointed at `Brew/Models/HomebrewPackageReference.swift`
and `Brew/PreviewSupport/AppPreviewSupport.swift`, neither of which exists under
any prefix; they now point at `Sources/BrewCore/Models/HomebrewPackageID.swift`
and `Sources/BrewRepositoryInterfaces/PreviewSupport/PreviewSupport.swift`.
Nightly cron only fires on the default branch, so the suite would have
gone unrun until after it merged, and the first real evidence it works
would have arrived on main. Moving it to pull_request means it is exercised
where it can still be fixed.
It stays a separate job rather than joining the deterministic suite's:
Brew-UI still skips these tests by name, so a live failure reads as a
live failure and never as a broken fixture-backed test. Concurrency now
keys on the pull request, since queueing every PR behind one run only made
sense while the group modelled a single shared machine.
The workflow file loses its `_nightly` name, and the docs that promised a
nightly schedule now say what actually happens.
Two gaps in a suite that retries. A failure's message says an element was
missing; it cannot say whether a dialog was covering the app, which is how
the last one actually failed. And a retry that turns red into green leaves
a run that reads as clean.
BrewUITestCase now attaches a screenshot of the whole screen to every
issue it records, so the picture lands in the result bundle CI uploads.
It goes in as PNG data rather than as the XCUIScreenshot, which cannot
cross out of the main actor into this nonisolated override. Both the
attachment and the plan keep screenshots always: the interesting one
belongs to a failed attempt of a test that ultimately passed, and
deleteOnSuccess prunes exactly that.
The plan now retries a failing test twice rather than once, and
annotate-flaky-tests reads the result bundle afterwards and names any test
that needed more than one run to pass — a ::warning:: annotation on GitHub,
plain text locally, never a failure by itself. It also tells the workflow
to keep the bundle for a green-but-retried run, so the evidence the
annotation points at still exists.
The live suite's comments restated what the code says and argued its own
design at length. What survives is the part a reader cannot recover: that
a login shell keeps exported variables, that a real inventory virtualizes
so an installed row may never render, that cleanup runs after the app is
terminated to stay clear of Homebrew's lock, and why the runner must be
signed. The reasoning behind the rest is in the commits and in
E2E/README.md, which is where it belongs.
The suite could not run at all: macOS put up "BrewUITests-Runner is
damaged and can't be opened", and xcodebuild sat there until it gave up
with "The test runner hung before establishing connection." The dialog is
the cause and the hang is only the consequence — a runner Gatekeeper
refuses to start never connects.
CODE_SIGNING_ALLOWED=NO, copied here from scripts/test-ui, does not
produce an unsigned runner. BrewUITests-Runner.app is a copy of Xcode's
XCTRunner.app template, which arrives signed by Apple; the build inserts
our .xctest into its PlugIns directory and then, with signing disallowed,
skips the CodeSign step. What launches is a bundle carrying Apple's
signature over contents that no longer match it, which codesign reports
as "code has no resources but signature indicates they must be present".
Allowing signing with identity "-" ad-hoc signs the runner as
sh.brew.BrewUITests.xctrunner with a valid seal. Ad-hoc needs no identity,
team or provisioning profile, so this is not a local-only accommodation —
it works on a bare CI runner too.
scripts/test-ui and the ui-test CI job still carry the flag and the same
latent defect; TROUBLESHOOTING.md now says so, so the next person to meet
this dialog reads it rather than re-derives it.
A canary nobody runs is a canary that reports nothing, so the suite gets a
schedule rather than only a plan: nightly on an ephemeral GitHub runner,
which ships Homebrew and has egress to formulae.brew.sh and ghcr.io, and
by hand before cutting a release — the moment you most want to know brew
has not shifted under the app. The release workflow is deliberately not
wired to it; that stays a decision someone makes.
Concurrency is keyed on the workflow rather than the ref, because two runs
would fight over one machine's brew state.
scripts/test-e2e mirrors scripts/test-ui, including its refusal to pass
-derivedDataPath, and leads with what it does to the machine it runs on:
this one installs and uninstalls a formula, and the script is the last
place that fact can be read before it happens.
scripts/test-ui mirrors what CI runs, so green locally means green on CI.
It deliberately does not override -derivedDataPath: a project-relative
DerivedData location reproducibly makes the test runner hang for around
five minutes before failing to establish a connection, where the identical
run against the default location passes in seconds.
The ui-test CI job runs alongside the unit job with no needs: dependency,
so UI coverage costs wall-clock time on a separate runner rather than
sitting on the critical path.
It replaces ui_smoke.yml, which triggered on the same pull_request event
and ran the same Brew-UI scheme, so keeping both would run every test
twice. Its inline signing block is gone with it: the default path was the
ad-hoc signing scripts/test-ui already does, and keeping the logic in the
script is what keeps a local run honest about CI.
TROUBLESHOOTING.md covers the things that make a local run fail for
reasons unrelated to the code, since the suite drives real windows and
real input.
Also ignores the artefacts a UI run leaves behind: coverage drops .profraw
wherever an instrumented binary runs, and the script writes a result bundle
at the repository root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Marketing version and build number are now derived from git rather than checked
in: derive-version maps the nearest v* tag + distance to a semver (distance 0 =>
the tag; past a tag on main => minor+1; on release/* => patch+1), and
derive-build-number is the commit count. A "Stamp app version" Xcode build phase
runs stamp-app-version on every build — local and CI — patching
CFBundleShortVersionString / CFBundleVersion into the built app's Info.plist
before signing, so a local debug build shows the real git version too.
Requires ENABLE_USER_SCRIPT_SANDBOXING = NO (the phase shells out to git).
Version.xcconfig keeps only inert placeholders to seed GENERATE_INFOPLIST_FILE
before the phase overwrites them. Validated with a real Debug build: the app
came out stamped 0.2.0 (build 706).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The postinstall script installed Homebrew by curling install.sh at
install time: it needs network access during installation, runs as the
console user via su, and fails silently on headless/MDM installs.
Instead, download the official Homebrew.pkg and ship it as a nested
sub-package in the distribution: productbuild --synthesize builds a
distribution that installs Homebrew first, then Brew.app. This drops the
--scripts postinstall from pkgbuild and deletes scripts/postinstall.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The BrewUILint step scanned 'Brew/', which no longer exists after the
source folder was renamed to 'Homebrew/', so the hook errored on every
commit. Point the whole-tree scan at 'Homebrew/'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the Xcode app target, product, and project from Brew to Homebrew:
- Brew.xcodeproj -> Homebrew.xcodeproj, and fix the file-system-
synchronized group path (Brew -> Homebrew) so the app target picks
up its sources again
- Source folder Brew/ -> Homebrew/
- Update schemes, test plans, CI workflows, README, and bootstrap script
- PRODUCT_BUNDLE_IDENTIFIER and pkgbuild identifier -> sh.brew.app
The package and repository names are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BrewUILint only scanned Brew/ (now the thin app shell), so its rules — notably
NonisolatedExtensionRule, which guards the cross-package MainActor-default
extension trap the split made possible — no longer reached Sources/. Point CI
(and the AGENTS.md parity command) at `find Brew Sources`, and add a pre-commit
step that builds + runs BrewUILint over the whole tree on every commit (it must
see all files in one pass so the rule knows every `nonisolated` type). Tests/
stay excluded. Also annotate two fixture extensions on BrewCore types
`nonisolated` per that rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run the custom linter on Brew sources in swift_quality, document local parity in AGENTS.md, align package identity cursor guidance, and fix pre-commit when no files are partially staged.
Co-authored-by: Cursor <cursoragent@cursor.com>