274 lines
12 KiB
Markdown
274 lines
12 KiB
Markdown
---
|
|
name: TZGyn-skill
|
|
description: Use proactively for ANY development task in TZGyn's working directories — starting a new project in any language (Go, Rust, TypeScript, not just Swift), cloning/studying/porting a third-party repo (reference material goes in .reference/, never the working root), working in an existing TZGyn repo, creating Gitea repos, publishing to the TZGyn Homebrew tap (casks for GUI apps, formulae for CLIs), signing and notarizing macOS builds, cutting releases, or extending this skill. Load it at the start of any dev session on this machine even when the task doesn't mention Gitea, Homebrew, or macOS.
|
|
---
|
|
|
|
# TZGyn dev skill
|
|
|
|
Personal conventions for TZGyn projects. Source of truth is the Git repo
|
|
`https://gitea.tzgyn.dev/TZGyn/TZGyn-skill`; it is installed as a global Crush
|
|
skill at `~/.agents/skills/TZGyn-skill` (a symlink to the checkout, so editing
|
|
the repo updates the live skill).
|
|
|
|
The reference implementations to copy from are `~/Git/kapture` and
|
|
`~/Git/konference` (macOS menu-bar apps).
|
|
|
|
## Ground rules
|
|
|
|
- **Applies from the first command of any session**, in any TZGyn working
|
|
directory — before any clone, scaffold, or build. Third-party repos cloned
|
|
to study, port, or compare against go straight into `.reference/` (section
|
|
2); waiting until the project is a git repo is too late.
|
|
- **Host**: Gitea at `https://gitea.tzgyn.dev`, owner `TZGyn`, `tea` login name
|
|
`gitea.tzgyn.dev`. SSH: `git@gitea.tzgyn.dev:TZGyn/<repo>.git`.
|
|
- **Always print the upgrade script** (the `brew` commands below) in the final
|
|
answer after any release or version bump. The user wants it every time.
|
|
- **Every GUI release must be notarized and stapled.** An unnotarized build is
|
|
not a release: never publish one, and never paper over Gatekeeper with
|
|
`xattr -dr com.apple.quarantine` or `brew install --cask --no-quarantine`.
|
|
If notarization fails, fix the cause (see
|
|
[Troubleshooting](#notarization-troubleshooting)) before publishing.
|
|
- Never commit build artifacts. `.gitignore` covers `.build/`, `*.app/`,
|
|
`dist/`, `.swiftpm/`, `xcuserdata/`, `DerivedData/`, `.DS_Store`.
|
|
(`go build ./...` drops a binary next to the source — ignore it explicitly.)
|
|
- Never commit secrets (`.p8`, API keys) or push to a remote unless asked.
|
|
- **Reference material goes in `.reference/`, never in the repo.** Every project
|
|
that is modelled on another app or repo gets a `.reference/` folder with a
|
|
`.gitignore` containing `*` — see [Reference material](#2-reference-material-reference)
|
|
for the exact shape and what to put in it.
|
|
- Commit messages: concise, why-focused, first line < 72 chars; add a body only
|
|
when it helps a future reader.
|
|
- Prefer SwiftPM (Swift/SwiftUI) for macOS apps; no external dependencies.
|
|
|
|
## 1. New project on Gitea
|
|
|
|
```bash
|
|
# 1. Create the repo (as the authenticated user).
|
|
tea repos create --name <repo> --desc "<one-line description>" --login gitea.tzgyn.dev
|
|
# Do NOT pass --owner TZGyn: that hits /orgs/TZGyn/repos and 404s for a user.
|
|
|
|
# 2. Initialise and push.
|
|
cd ~/Git/<repo>
|
|
git init -b main
|
|
git add -A && git commit -m "<why>"
|
|
git remote add origin https://gitea.tzgyn.dev/TZGyn/<repo>.git
|
|
git push -u origin main
|
|
```
|
|
|
|
Scaffold every repo with `README.md`, `AGENTS.md` (architecture + commands +
|
|
gotchas), `.gitignore`, and `.reference/` (see below). See
|
|
`~/Git/simpletopbar` for the minimal shape: `Package.swift`,
|
|
`Sources/<Name>/*.swift`, `build-app.sh`, `release.sh`, `scripts/`.
|
|
|
|
## 2. Reference material (`.reference/`)
|
|
|
|
Apps and repos that a project is modelled on are research input, not source
|
|
code. Keep them in an untracked `.reference/` folder at the repo root so they
|
|
are available to every session without ever entering the repository:
|
|
|
|
```bash
|
|
mkdir -p ~/Git/<repo>/.reference
|
|
printf '*\n' > ~/Git/<repo>/.reference/.gitignore
|
|
```
|
|
|
|
- The `.gitignore` **inside** `.reference/` contains a single `*`. Ignore the
|
|
whole folder there, not in the repo's root `.gitignore`: the folder is then
|
|
self-contained and stays untracked even if the root ignore file is rewritten.
|
|
- **Any repo cloned to study, port, or compare against is reference material.**
|
|
When asked to "check out this repo" (e.g. to reimplement it in another
|
|
language), create `.reference/` and its `.gitignore` as the very first step
|
|
and clone straight into it — never into the working root, even when the
|
|
project is not a git repo yet.
|
|
- Clone reference repos inside it:
|
|
`git clone --depth 1 <url> .reference/<name>`. Do not vendor them into
|
|
`Sources/` or `vendor/`.
|
|
- Write one short note per reference (`tangerine.md`, `filee.md`) covering: what
|
|
the app does, its interaction model, the formats/features worth matching, and
|
|
what we deliberately skip. Notes are untracked too — that is fine; they are
|
|
for the next session, not for the public repo.
|
|
- Check licences before reusing code. MIT/Apache-2.0 code can be adapted with
|
|
attribution; never copy assets, icons, or code from a closed app.
|
|
- `.reference/` is never committed, never released, and never required to build.
|
|
|
|
## 3. Homebrew tap
|
|
|
|
- The tap clone lives at **`~/.cache/homebrew-tap`**
|
|
(origin `https://gitea.tzgyn.dev/TZGyn/homebrew-tap.git`).
|
|
- Layout: **GUI apps → `Casks/<name>.rb`**; **CLI tools → `Formula/<name>.rb`**.
|
|
- After pushing, sync Homebrew's own clone:
|
|
|
|
```bash
|
|
git -C ~/.cache/homebrew-tap add -A
|
|
git -C ~/.cache/homebrew-tap commit -m "Add <name> cask"
|
|
git -C ~/.cache/homebrew-tap push
|
|
git -C /opt/homebrew/Library/Taps/tzgyn/homebrew-tap pull --ff-only
|
|
brew audit --cask tzgyn/tap/<name> # or: brew audit --formula tzgyn/tap/<name>
|
|
```
|
|
|
|
- Users tap once (non-GitHub taps need the full URL):
|
|
|
|
```bash
|
|
brew tap TZGyn/tap https://gitea.tzgyn.dev/TZGyn/homebrew-tap.git
|
|
brew trust TZGyn/tap
|
|
```
|
|
|
|
### Cask (GUI app)
|
|
|
|
```ruby
|
|
cask "<name>" do
|
|
version "X.Y.Z"
|
|
sha256 "<sha256 of the release zip>"
|
|
|
|
url "https://gitea.tzgyn.dev/TZGyn/<repo>/releases/download/v#{version}/<Asset>.zip"
|
|
name "<Display Name>"
|
|
desc "<one line>"
|
|
homepage "https://gitea.tzgyn.dev/TZGyn/<repo>"
|
|
|
|
app "<Name>.app"
|
|
end
|
|
```
|
|
|
|
### Formula (CLI)
|
|
|
|
```ruby
|
|
class <Name> < Formula
|
|
desc "<one line>"
|
|
homepage "https://gitea.tzgyn.dev/TZGyn/<repo>"
|
|
url "https://gitea.tzgyn.dev/TZGyn/<repo>/releases/download/v0.1.0/<asset>-0.1.0-darwin-arm64.tar.gz"
|
|
sha256 "<sha256 of the tarball>"
|
|
version "0.1.0"
|
|
|
|
def install
|
|
bin.install "<binary>"
|
|
end
|
|
end
|
|
```
|
|
|
|
## 4. Releasing a macOS GUI app
|
|
|
|
Two scripts per repo: `build-app.sh <version>` and `release.sh <version>`.
|
|
|
|
1. **Build + sign** (`build-app.sh`): `swift build -c release` → handwritten
|
|
`Info.plist` (NOT generated from `Package.swift`) → `make-icon.sh` → sign
|
|
with the Developer ID identity resolved from the login keychain
|
|
(`security find-identity -v -p codesigning | awk '/Developer ID Application:/{print $2;exit}'`),
|
|
falling back to ad-hoc (`-`). GUI apps set `LSUIElement = true` and
|
|
`CFBundleIdentifier = dev.tzgyn.<name>`.
|
|
2. **Notarize + staple + zip** (`release.sh`): build → `ditto -c -k --keepParent
|
|
<App>.app dist/<App>.zip` → submit to Apple → staple → re-zip → print sha256.
|
|
See [Notarization](#5-notarization) for the credentials.
|
|
3. **Publish the release** and attach the zip:
|
|
|
|
```bash
|
|
tea releases create --tag v<version> --title "<App> <version>" \
|
|
--note "<what changed>" --asset dist/<App>.zip \
|
|
--repo TZGyn/<repo> --login gitea.tzgyn.dev
|
|
```
|
|
|
|
Push `main` **before** this: `tea` tags the remote default branch, so an
|
|
unpushed commit means the tag points at source that is not what shipped
|
|
(the binary would contain changes absent from the tag). If it already
|
|
happened, re-point the tag: `git push origin :refs/tags/v<version>` then
|
|
`git tag -f v<version> <commit> && git push origin v<version>`.
|
|
|
|
4. **Bump the cask** (`version` + `sha256`), push the tap, pull Homebrew's
|
|
clone, `brew audit`. Verify by downloading the asset back and comparing the
|
|
sha256, and `spctl -a -vv <App>.app` → `accepted` /
|
|
`source=Notarized Developer ID`. If `spctl` does not say
|
|
`source=Notarized Developer ID`, stop and fix notarization first — do not
|
|
publish the release or the cask.
|
|
|
|
5. **Print the upgrade script**:
|
|
|
|
```bash
|
|
brew update
|
|
brew upgrade --cask <name>
|
|
```
|
|
|
|
## 5. Notarization
|
|
|
|
Signing identity: `Developer ID Application: Teh Zi Gyn (RX6767S6BW)`.
|
|
App Store Connect API key (shared with Kapture):
|
|
|
|
| Value | Where |
|
|
| --------- | ------------------------------------------------------------ |
|
|
| Key file | `~/.config/kapture/AuthKey_K5KXMMSB28.p8` |
|
|
| Key ID | `K5KXMMSB28` |
|
|
| Issuer ID | `3e25e5dc-8b75-453f-a71c-c7f3feaac628` (not secret) |
|
|
| Team ID | `RX6767S6BW` |
|
|
|
|
Signing must use a hardened runtime and a secure timestamp:
|
|
|
|
```bash
|
|
codesign --force --options runtime --timestamp --sign "$IDENT" "<App>.app"
|
|
```
|
|
|
|
Submit and verify:
|
|
|
|
```bash
|
|
xcrun notarytool submit dist/<App>.zip \
|
|
--key "$HOME/.config/kapture/AuthKey_K5KXMMSB28.p8" \
|
|
--key-id K5KXMMSB28 --issuer 3e25e5dc-8b75-453f-a71c-c7f3feaac628 \
|
|
--wait --output-format json
|
|
xcrun stapler staple <App>.app && xcrun stapler validate <App>.app
|
|
spctl -a -vv <App>.app # want: accepted, source=Notarized Developer ID
|
|
```
|
|
|
|
`release.sh` **must** notarize. Treat any skip or failure as a failed release:
|
|
do not publish the tag, the release, or the cask, and do not suggest
|
|
quarantine workarounds (`xattr -dr com.apple.quarantine`,
|
|
`brew install --cask --no-quarantine`) — those hand the user a build macOS
|
|
rejects. Only publish once `spctl -a -vv <App>.app` reports `accepted` with
|
|
`source=Notarized Developer ID`.
|
|
|
|
### Notarization troubleshooting
|
|
|
|
| Symptom | Cause / fix |
|
|
| --- | --- |
|
|
| `HTTP status code: 403. A required agreement is missing or has expired.` | The **Apple Developer Program License Agreement** for the team is unsigned or expired. The Account Holder must accept it in App Store Connect → **Business → Agreements** *and* at `developer.apple.com/account` → **Agreements**. Account-wide: every call fails, including read-only `notarytool history`, until it is accepted; allow a few minutes to propagate. |
|
|
| The same 403 *after* accepting | The wrong agreement was accepted (App Store Connect "Paid Apps" instead of the Program License Agreement) or it was accepted under a different team's account. |
|
|
| `401 Authentication credentials are incorrect` | Wrong key id / issuer, or the key belongs to another team. |
|
|
|
|
Confirm the team before blaming the credentials — do not "fix" this by
|
|
switching teams or re-signing:
|
|
|
|
```bash
|
|
security find-identity -v -p codesigning # the Developer ID team must match the key's team
|
|
spctl -a -vv /Applications/<App>.app # an already-notarized app proves the team/key pair works
|
|
xcrun notarytool history --key ... --key-id ... --issuer ... # 403 here = account agreement, not the app
|
|
```
|
|
|
|
`notarytool history` failing the same way as the submit is the tell: the
|
|
problem is the account, not the bundle, so there is nothing to change in the
|
|
app or the scripts.
|
|
|
|
## 6. Mobile
|
|
|
|
For mobile (iOS/Android), notarization does not apply; the equivalent is store
|
|
signing (App Store Connect / Play Console) or EAS. Ask for the specific stack
|
|
before assuming; this skill currently only codifies the macOS path.
|
|
|
|
## 7. Extending this skill (self-improvement)
|
|
|
|
This skill is meant to grow. When the user asks for a new behaviour, preference,
|
|
or convention:
|
|
|
|
1. Edit the relevant section of `SKILL.md` (add a new section if it is a new
|
|
topic). Keep it concrete: exact commands, paths, and templates.
|
|
2. Keep it truthful — only document what has actually worked; delete steps that
|
|
became stale (e.g. a changed path or flag).
|
|
3. Commit and push:
|
|
|
|
```bash
|
|
cd ~/Git/TZGyn-skill
|
|
git add -A && git commit -m "<what behaviour was added>"
|
|
git push
|
|
```
|
|
|
|
4. The installed copy is a symlink, so it updates immediately; a new Crush
|
|
session picks it up. Confirm with `crush_info` (the skill should appear under
|
|
`[skills]`).
|
|
|
|
Prefer updating this skill over repeating ad-hoc instructions in a project: if a
|
|
convention applies to more than one repo, it belongs here.
|