| name | dayflow-sync |
| description | 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. |
| allowed-tools | Bash,Read,Edit,Write |
Dayflow Sync
Weekly upstream sync routine for the caezium/Dayflow fork. Tracks
JerryZLiu/Dayflow.
Tiered conflict policy
This skill runs mostly autonomously but stops at clearly-defined
checkpoints:
- Tier A — known-droppable commits. Conflicts on commits flagged
on_conflict: auto_skip_if in state/commits.yaml are auto-skipped
after their condition is verified. The dropped diff is captured
in the pre-install summary so the user can spot regressions.
- Tier B — hardening + features. Any conflict on a commit flagged
on_conflict: stop halts the run. These patches must be re-ported
by hand; never --skip them.
- Pre-install checkpoint. After rebase + audit + Debug build pass,
the skill always stops with a glanceable summary and waits for
the user to reply "install" (or abort). It never auto-touches
/Applications/Dayflow.app.
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. main mirrors upstream/main exactly.
- 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 processed
last-synced-date.txt
last-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:
origin points to https://github.com/caezium/Dayflow
upstream points to https://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
ls -1dt "$HOME/Library/Application Support"/Dayflow.backup-* 2>/dev/null | tail -n +5 | xargs -I{} rm -rf "{}"
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.yaml and find the
first entry whose subject_match is 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
notes field
Ask the user how to resolve each conflict. After they choose,
git add + git rebase --continue.
-
If matched entry has on_conflict: auto_skip_if → Tier A:
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.md if
it exists).
- Entitlements / Info.plist: anything beyond version bumps — flag
it. Especially watch for changes to
SUEnableInstallerLauncherService,
SUEnableAutomaticChecks, sandbox keys, or com.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.yaml is 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" | tail
If 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 || true
-
Archive 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.app
The Caezium Dayflow Local identity 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 codesign fails with no 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-release
-
Sanity-check the installed bundle:
defaults read /Applications/Dayflow.app/Contents/Info CFBundleIdentifier
defaults read /Applications/Dayflow.app/Contents/Info SUEnableAutomaticChecks
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.