skills / Tooling & Setup
dayflow-sync
Sync caezium/Dayflow fork with upstream JerryZLiu/Dayflow. Rebases private-hardening onto upstream/main, auto-skips known-droppable commits, runs audit + Debug build, then stops at a pre-install checkpoint and waits for the user to say "install". Use when the user says "/dayflow-sync", "sync dayflow", or asks to update the Dayflow fork.
Dayflow Sync
Weekly upstream sync routine for the caezium/Dayflow fork. Tracks
JerryZLiu/Dayflow.
How it runs
Runs mostly autonomously, halting only at three named points. Each is defined here only enough to name it; the mechanics live in one place — the step that enforces it.
- Tier A / Tier B — per-commit conflict policy from
state/commits.yaml, applied during the rebase (step 5). Tier A auto-skips known-droppable commits once their condition verifies; Tier B halts for hand re-porting. - Pre-install checkpoint — after rebase + audit + Debug build, the skill
always stops for a go-ahead before touching
/Applications/Dayflow.app(step 9).
Context
- Local clone:
~/Desktop/Dayflow - Fork remote (
origin):https://github.com/caezium/Dayflow - Upstream remote (
upstream):https://github.com/JerryZLiu/Dayflow - Integration branch:
private-hardening— contains the security/privacy patches plus reimplemented bug fixes. This is the branch the user builds from.mainmirrorsupstream/mainexactly. - Per-commit metadata:
~/.claude/skills/dayflow-sync/state/commits.yaml. Read at step 5 to decide conflict policy per commit. - State markers:
~/.claude/skills/dayflow-sync/state/last-synced-upstream.txt— last upstream HEAD SHA processedlast-synced-date.txtlast-drift-report.md— written each run; diffable across runs
Steps
1. Sanity check + disk hygiene (fail fast)
cd ~/Desktop/Dayflow
git remote -v
git status
git branch --show-current
df -h ~ | tail -1
Required state before proceeding:
originpoints tohttps://github.com/caezium/Dayflowupstreampoints tohttps://github.com/JerryZLiu/Dayflow- Working tree clean (no uncommitted changes)
- Free space on
~≥ 4 GB
If working tree is dirty, stop and ask the user.
If free space < 4 GB, clean caches first:
rm -rf /tmp/dayflow-build /tmp/dayflow-release
# Prune oldest data backups beyond the last 4
ls -1dt "$HOME/Library/Application Support"/Dayflow.backup-* 2>/dev/null | tail -n +5 | xargs -I{} rm -rf "{}"
# Prune oldest app archives beyond the last 3
ls -1dt "$HOME/Library/Caches/dayflow-app-archive"/*.app 2>/dev/null | tail -n +4 | xargs -I{} rm -rf "{}"
df -h ~ | tail -1
If still under 4 GB after cleanup, stop and report — the user needs to free space manually. Do not attempt a Release build under 4 GB free; ad-hoc-signing a half-built bundle has corrupted runs in the past.
2. Fetch upstream
cd ~/Desktop/Dayflow
git fetch upstream --prune
git fetch origin --prune
Compare git rev-parse upstream/main against
~/.claude/skills/dayflow-sync/state/last-synced-upstream.txt.
If equal, nothing changed upstream this week — tell the user in one
line and exit. No rebase, no checkpoint, no install.
3. Show what's new upstream
git log <last-synced-sha>..upstream/main --oneline --no-merges
Summarize in plain English ("Upstream added N commits — bug fixes for X, features for Y"). Don't dump the full log.
4. Update main to mirror upstream
cd ~/Desktop/Dayflow
git checkout main
git reset --hard upstream/main
git push origin main --force-with-lease
5. Rebase private-hardening with tiered conflict policy
cd ~/Desktop/Dayflow
git checkout private-hardening
git rebase main
On conflict:
Identify the conflicted commit:
CONFLICT_SUBJECT=$(git log -1 --format='%s' REBASE_HEAD)Read
~/.claude/skills/dayflow-sync/state/commits.yamland find the first entry whosesubject_matchis a substring of$CONFLICT_SUBJECT.If no match found → Tier B fallthrough: stop and report (an unknown commit means commits.yaml is out of date; ask the user to update it before proceeding).
If matched entry has
on_conflict: stop→ Tier B: stop and surface full context:- Conflicted file paths (
git status) - Conflicting hunks
- Upstream's nearby change for each file
(
git log main -p --follow -- <file> -1) - The matched entry's
notesfield Ask the user how to resolve each conflict. After they choose,git add+git rebase --continue.
- Conflicted file paths (
If matched entry has
on_conflict: auto_skip_if→ Tier A:- Capture the diff that would be re-introduced if we skip, so the
pre-install summary can show it:
mkdir -p ~/.claude/skills/dayflow-sync/state git diff REBASE_HEAD~1..REBASE_HEAD > ~/.claude/skills/dayflow-sync/state/last-skipped-commit.diff git log -1 --format='%H %s' REBASE_HEAD >> ~/.claude/skills/dayflow-sync/state/last-skipped-commit.diff - Evaluate
auto_skip_if.condition:build_main_succeeds: stash rebase state, run a quick Debug build ofmain(see step 8's command, but pointed at a fresh checkout of main in a worktree, or against current HEAD if the rebase is paused — the in-progress rebase leaves the worktree at a clean conflicted state, sogit stashfirst):
Look forgit stash push -u -m 'dayflow-sync conflict probe' || true git checkout main -- . xcodebuild build -scheme Dayflow -configuration Debug \ -destination 'platform=macOS' -derivedDataPath /tmp/dayflow-probe \ CODE_SIGNING_ALLOWED=NO 2>&1 | grep -E "error:|^\*\* BUILD" | head PROBE_RESULT=$? rm -rf /tmp/dayflow-probe git checkout REBASE_HEAD -- . git stash pop || true** BUILD SUCCEEDED **in the probe output.
- If condition met →
git rebase --skip, log the skip, continue. - If condition NOT met → stop and report. The workaround is still needed; the user must port the revert forward by hand.
- Capture the diff that would be re-introduced if we skip, so the
pre-install summary can show it:
If rebase finishes clean, push:
git push origin private-hardening --force-with-lease
6. Re-run audit checks against the new tree
| Finding | Verify by |
|---|---|
| Telemetry default off | grep -n 'return false' Dayflow/Dayflow/System/AnalyticsService.swift shows the isOptedIn getter |
| No Keychain key prefix print | ! grep -n 'apiKey.prefix(8)' Dayflow/Dayflow/Core/Security/KeychainManager.swift |
| No Host.localizedName super-prop | ! grep -n 'Host.current().localizedName' Dayflow/Dayflow/System/AnalyticsService.swift |
| Feedback share-logs default false | grep -n 'feedbackShareLogs = false' Dayflow/Dayflow/Views/UI/MainView/MainView.swift Dayflow/Dayflow/Views/UI/MainView/Actions.swift (both must match) |
| Sparkle XPC disabled | grep -A1 'SUEnableInstallerLauncherService' Dayflow/Dayflow/Info.plist shows <false/> |
| Popover fullscreen | grep -n 'fullScreenAuxiliary' Dayflow/Dayflow/System/StatusBarController.swift |
| Onboarding persist deferred | ! grep -n 'persistGeminiAPIKey(source: "onboarding_step")' Dayflow/Dayflow/Views/Onboarding/ProviderSetupState.swift |
If any check fails after rebase, stop and tell the user — a patch got lost or was reordered weirdly. Don't push, don't proceed.
7. Drift detection + persistence
Re-scan upstream changes for new privacy/security surface area, and
write the result to state/last-drift-report.md so it survives
the session and can be diffed across runs.
SINCE=$(cat ~/.claude/skills/dayflow-sync/state/last-synced-upstream.txt)
UNTIL=$(git rev-parse upstream/main)
REPORT=~/.claude/skills/dayflow-sync/state/last-drift-report.md
{
echo "# Drift report — $(date +%Y-%m-%d)"
echo
echo "Range: \`$SINCE\` .. \`$UNTIL\`"
echo
echo "## New external network endpoints"
git diff "$SINCE..$UNTIL" -- '*.swift' \
| grep -E '^\+.*https://' \
| grep -v '://github\.com\|://docs\.\|://localhost\|aistudio\.google\.com\|developers\.openai\.com\|docs\.anthropic\.com' \
|| echo "_none_"
echo
echo "## New PostHog analytics events"
git diff "$SINCE..$UNTIL" -- '*.swift' \
| grep -E '^\+.*AnalyticsService\.shared\.capture\("' \
| grep -oE '"[a-z_]+"' | sort -u \
|| echo "_none_"
echo
echo "## Entitlement / Info.plist diff"
git diff "$SINCE..$UNTIL" -- \
Dayflow/Dayflow/Dayflow.entitlements Dayflow/Dayflow/Info.plist \
|| echo "_no changes_"
} > "$REPORT"
Summarize for the pre-install checkpoint:
- Endpoints: anything non-Google/non-localhost — flag it.
- Analytics events: list them all; note any prefix you haven't
seen in past runs (compare against prior
last-drift-report.mdif it exists). - Entitlements / Info.plist: anything beyond version bumps — flag
it. Especially watch for changes to
SUEnableInstallerLauncherService,SUEnableAutomaticChecks, sandbox keys, orcom.apple.security.network.*.
8. Verify Debug build
cd ~/Desktop/Dayflow/Dayflow
xcodebuild build -scheme Dayflow -configuration Debug \
-destination 'platform=macOS' -derivedDataPath /tmp/dayflow-build \
CODE_SIGNING_ALLOWED=NO 2>&1 | grep -E "error:|^\*\* BUILD" | head
rm -rf /tmp/dayflow-build
If the build fails:
- Errors in files my patches touched → patch needs updating. Stop.
- Errors in files only upstream touched → upstream regression.
Tell the user; consider whether a new temporary-workaround entry
in
commits.yamlis warranted.
9. CHECKPOINT — pre-install summary
Always stop here. Do NOT proceed to step 10 until the user replies with "install" (or some clear go-ahead). This is the hard gate before the live binary is touched.
Present a glanceable summary, in this shape:
Dayflow sync — ready to install? (<upstream-version>, <N> upstream commits)
Rebase: clean / N conflicts auto-skipped / N conflicts manually resolved
Auto-skipped: <commit subject> — condition met (<which>). See state/last-skipped-commit.diff
Audit (7 checks): all pass
Debug build: ** BUILD SUCCEEDED **
Drift (full report at state/last-drift-report.md):
- <N> new endpoints (or "none beyond known-safe")
- <N> new analytics events: <list-or-summary> (or "none")
- Info.plist / entitlements: <summary, or "version bump only">
Reply "install" to proceed with Release build + install to /Applications.
Reply "abort" to stop without touching the installed app.
If anything looks off (new non-Google endpoint, new sandbox entitlement, audit pass count below 7), explicitly recommend "abort" or "investigate first" rather than "install."
10. (On user "install") Release build + install
Run only after the user says install.
APFS-clone the data dir as a safety net:
STAMP=$(date +%Y%m%d-%H%M%S) cp -c -R "$HOME/Library/Application Support/Dayflow" \ "$HOME/Library/Application Support/Dayflow.backup-$STAMP"Build Release:
cd ~/Desktop/Dayflow/Dayflow rm -rf /tmp/dayflow-release xcodebuild build -scheme Dayflow -configuration Release \ -destination 'platform=macOS' -derivedDataPath /tmp/dayflow-release \ CODE_SIGNING_ALLOWED=NO 2>&1 | grep -E "error:|^\*\* BUILD" | tailIf build fails: stop. Don't touch the installed app.
Quit running Dayflow:
osascript -e 'tell application "Dayflow" to quit' 2>/dev/null sleep 2 pkill -f "/Applications/Dayflow.app/Contents/MacOS/Dayflow" 2>/dev/null || trueArchive the currently-installed Dayflow.app (keep last 3):
ARCHIVE_DIR="$HOME/Library/Caches/dayflow-app-archive" mkdir -p "$ARCHIVE_DIR" if [ -d /Applications/Dayflow.app ]; then INSTALLED_VER=$(defaults read /Applications/Dayflow.app/Contents/Info CFBundleShortVersionString 2>/dev/null || echo "unknown") mv /Applications/Dayflow.app "$ARCHIVE_DIR/Dayflow-$INSTALLED_VER-$STAMP.app" fi ls -1dt "$ARCHIVE_DIR"/*.app | tail -n +4 | xargs -I{} rm -rf "{}"Install + sign with the stable local cert:
cp -R /tmp/dayflow-release/Build/Products/Release/Dayflow.app /Applications/Dayflow.app xattr -dr com.apple.quarantine /Applications/Dayflow.app 2>/dev/null || true codesign --force --deep --sign "Caezium Dayflow Local" /Applications/Dayflow.appThe
Caezium Dayflow Localidentity is a self-signed code-signing cert created once in~/Library/Keychains/login.keychain-db. Signing every build with the same identity gives a stable code anchor across rebuilds, so TCC (Screen Recording) and the Keychain ACL (Gemini key) don't get re-prompted weekly.If
codesignfails withno identity found, the cert was deleted — stop and tell the user; the rebuild flow is in a previous session.Free the build cache:
rm -rf /tmp/dayflow-releaseSanity-check the installed bundle:
defaults read /Applications/Dayflow.app/Contents/Info CFBundleIdentifier # must == teleportlabs.com.Dayflow defaults read /Applications/Dayflow.app/Contents/Info SUEnableAutomaticChecks # must == 0 codesign -dv /Applications/Dayflow.app 2>&1 | grep "Authority=Caezium Dayflow Local"If bundle id changed, stop and tell the user — the new build won't read existing data. If the Authority line is missing or shows
Signature=adhoc, the cert wasn't used — stop and report.
First-install warnings — needed exactly once, when the installed signature changes:
- Switching from official Developer-ID build → fork: re-grant Screen Recording, re-paste Gemini key. One-time.
- Switching from any prior ad-hoc-signed fork build → cert-signed fork
build (i.e., the first install after introducing
Caezium Dayflow Local): re-grant Screen Recording, re-paste Gemini key. One-time. - All subsequent installs (cert → cert, same identity): no re-grant, no re-paste. This is the steady state.
11. Bump state markers
After successful install (or after step 6 if no install ran but rebase was clean and the user explicitly chose "abort" — in the abort case do NOT bump, so next run re-syncs):
mkdir -p ~/.claude/skills/dayflow-sync/state
git -C ~/Desktop/Dayflow rev-parse upstream/main > ~/.claude/skills/dayflow-sync/state/last-synced-upstream.txt
date +%Y-%m-%d > ~/.claude/skills/dayflow-sync/state/last-synced-date.txt
State marker advances ONLY after a successful end-to-end run. Aborts, build failures, audit failures, and Tier B conflicts all leave the marker at the previous SHA so the next run retries from the same base.
12. Final report (post-install)
Tell the user, under 200 words:
- N upstream commits merged, installed version
- Conflicts: auto-skipped (with which commit + condition met) vs. manually resolved
- Audit: pass/fail per check
- Debug + Release build results
- Bundle sanity-check: bundle id, Sparkle off, ad-hoc signed
- Drift summary (one line each for endpoints / events / entitlements)
- Any first-install permissions reminders (Screen Recording, Gemini key)
Failure modes
- Working tree dirty → ask the user before anything.
- Disk < 4 GB after cleanup → stop, report, don't build.
- Tier B rebase conflict → stop with full context, never
--skip. - Tier A auto-skip condition unmet → stop, ask the user to port the workaround forward.
- Unknown commit in conflict → stop; commits.yaml needs updating.
- Audit check fails after rebase → stop, don't push, name the check.
- Debug build fails → stop, don't push, show the Swift error.
- No upstream changes → exit early, one-line report.
- Pre-install checkpoint → always stops here; not a failure, just the design.