create-egregore 0.19.32 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/assets/egregore-launcher.js +118 -13
  2. package/bin/cli.js +48 -3
  3. package/lib/connect-runtime.js +4 -2
  4. package/lib/local.js +15 -1
  5. package/lib/prime-runtime.js +208 -0
  6. package/lib/setup.js +45 -3
  7. package/package.json +5 -4
  8. package/runtime/codex/.codex/skills/ingest-notion/SKILL.md +29 -0
  9. package/runtime/codex/.codex/skills/notion-connect/SKILL.md +29 -0
  10. package/runtime/codex/.codex/skills/teams-connect/SKILL.md +29 -0
  11. package/runtime/codex/.codex/spec-manifest.json +3 -3
  12. package/runtime/codex/AGENTS.md +11 -4
  13. package/runtime/codex/bin/activity-data.sh +4 -8
  14. package/runtime/codex/bin/attendant.sh +13 -0
  15. package/runtime/codex/bin/autosave.sh +27 -1
  16. package/runtime/codex/bin/codex-render-spec.mjs +6 -0
  17. package/runtime/codex/bin/codex-session-start.sh +8 -1
  18. package/runtime/codex/bin/connect-refresh.sh +33 -0
  19. package/runtime/codex/bin/graph-batch.sh +25 -6
  20. package/runtime/codex/bin/graph-op.sh +5 -4
  21. package/runtime/codex/bin/graph.sh +31 -5
  22. package/runtime/codex/bin/ingest.py +81 -5
  23. package/runtime/codex/bin/lib/context.sh +26 -1
  24. package/runtime/codex/bin/lib/greeting.sh +4 -1
  25. package/runtime/codex/bin/lib/worktree-links.sh +25 -0
  26. package/runtime/codex/manifest.json +8 -1
  27. package/runtime/pi/.codex/skills/ingest-notion/SKILL.md +29 -0
  28. package/runtime/pi/.codex/skills/notion-connect/SKILL.md +29 -0
  29. package/runtime/pi/.codex/skills/teams-connect/SKILL.md +29 -0
  30. package/runtime/pi/.codex/spec-manifest.json +3 -3
  31. package/runtime/pi/.pi/APPEND_SYSTEM.md +3 -2
  32. package/runtime/pi/.pi/extensions/egregore.ts +17 -6
  33. package/runtime/pi/.pi/spec-manifest.json +2 -2
  34. package/runtime/pi/AGENTS.md +11 -4
  35. package/runtime/pi/bin/activity-data.sh +4 -8
  36. package/runtime/pi/bin/attendant.sh +13 -0
  37. package/runtime/pi/bin/autosave.sh +27 -1
  38. package/runtime/pi/bin/codex-render-spec.mjs +6 -0
  39. package/runtime/pi/bin/codex-session-start.sh +8 -1
  40. package/runtime/pi/bin/connect-refresh.sh +33 -0
  41. package/runtime/pi/bin/graph-batch.sh +25 -6
  42. package/runtime/pi/bin/graph-op.sh +5 -4
  43. package/runtime/pi/bin/graph.sh +31 -5
  44. package/runtime/pi/bin/ingest.py +81 -5
  45. package/runtime/pi/bin/lib/context.sh +26 -1
  46. package/runtime/pi/bin/lib/greeting.sh +4 -1
  47. package/runtime/pi/bin/lib/worktree-links.sh +25 -0
  48. package/runtime/pi/bin/pi-branch-policy.mjs +138 -1
  49. package/runtime/pi/bin/pi-product-workflows.mjs +35 -7
  50. package/runtime/pi/manifest.json +8 -1
  51. package/runtime/prime/.claude/context/notification-consent.md +66 -0
  52. package/runtime/prime/.codex/config.toml +17 -0
  53. package/runtime/prime/.codex/hooks/branch-guard.js +461 -0
  54. package/runtime/prime/.codex/hooks/search-hint.js +67 -0
  55. package/runtime/prime/.codex/hooks.json +30 -0
  56. package/runtime/prime/.codex/skills/activity/SKILL.md +82 -0
  57. package/runtime/prime/.codex/skills/announce/SKILL.md +67 -0
  58. package/runtime/prime/.codex/skills/archive/SKILL.md +45 -0
  59. package/runtime/prime/.codex/skills/branch/SKILL.md +29 -0
  60. package/runtime/prime/.codex/skills/checkup/SKILL.md +45 -0
  61. package/runtime/prime/.codex/skills/commit/SKILL.md +29 -0
  62. package/runtime/prime/.codex/skills/contribute/SKILL.md +29 -0
  63. package/runtime/prime/.codex/skills/create-skill/SKILL.md +29 -0
  64. package/runtime/prime/.codex/skills/dark-mode/SKILL.md +29 -0
  65. package/runtime/prime/.codex/skills/dashboard/SKILL.md +59 -0
  66. package/runtime/prime/.codex/skills/deep-reflect/SKILL.md +109 -0
  67. package/runtime/prime/.codex/skills/delete-user/SKILL.md +29 -0
  68. package/runtime/prime/.codex/skills/emissary/SKILL.md +45 -0
  69. package/runtime/prime/.codex/skills/handoff/SKILL.md +166 -0
  70. package/runtime/prime/.codex/skills/harvest/QUESTION_PALETTE.md +76 -0
  71. package/runtime/prime/.codex/skills/harvest/SKILL.md +131 -0
  72. package/runtime/prime/.codex/skills/ingest/SKILL.md +29 -0
  73. package/runtime/prime/.codex/skills/ingest-notion/SKILL.md +29 -0
  74. package/runtime/prime/.codex/skills/ingest-user-interview/SKILL.md +45 -0
  75. package/runtime/prime/.codex/skills/invite/SKILL.md +44 -0
  76. package/runtime/prime/.codex/skills/issue/SKILL.md +45 -0
  77. package/runtime/prime/.codex/skills/me/SKILL.md +29 -0
  78. package/runtime/prime/.codex/skills/meeting/SKILL.md +45 -0
  79. package/runtime/prime/.codex/skills/note/SKILL.md +29 -0
  80. package/runtime/prime/.codex/skills/notion-connect/SKILL.md +29 -0
  81. package/runtime/prime/.codex/skills/onboarding/SKILL.md +45 -0
  82. package/runtime/prime/.codex/skills/pr/SKILL.md +29 -0
  83. package/runtime/prime/.codex/skills/pull/SKILL.md +29 -0
  84. package/runtime/prime/.codex/skills/push/SKILL.md +29 -0
  85. package/runtime/prime/.codex/skills/quest/SKILL.md +80 -0
  86. package/runtime/prime/.codex/skills/reflect/SKILL.md +45 -0
  87. package/runtime/prime/.codex/skills/review-pr/SKILL.md +45 -0
  88. package/runtime/prime/.codex/skills/save/SKILL.md +125 -0
  89. package/runtime/prime/.codex/skills/slack-connect/SKILL.md +29 -0
  90. package/runtime/prime/.codex/skills/summon/SKILL.md +45 -0
  91. package/runtime/prime/.codex/skills/sync-repos/SKILL.md +29 -0
  92. package/runtime/prime/.codex/skills/teams-connect/SKILL.md +29 -0
  93. package/runtime/prime/.codex/skills/telegram-connect/SKILL.md +29 -0
  94. package/runtime/prime/.codex/skills/telemetry/SKILL.md +29 -0
  95. package/runtime/prime/.codex/skills/test/SKILL.md +45 -0
  96. package/runtime/prime/.codex/skills/the-spiral/SKILL.md +84 -0
  97. package/runtime/prime/.codex/skills/tui-design/SKILL.md +45 -0
  98. package/runtime/prime/.codex/skills/tutorial/SKILL.md +45 -0
  99. package/runtime/prime/.codex/skills/update/SKILL.md +29 -0
  100. package/runtime/prime/.codex/skills/view/SKILL.md +156 -0
  101. package/runtime/prime/.codex/skills/view/agents/openai.yaml +4 -0
  102. package/runtime/prime/.codex/skills/view/assets/compose-scaffold.html +221 -0
  103. package/runtime/prime/.codex/skills/visual-explain/SKILL.md +45 -0
  104. package/runtime/prime/.codex/skills/wrap/SKILL.md +58 -0
  105. package/runtime/prime/.codex/spec-manifest.json +132 -0
  106. package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +393 -0
  107. package/runtime/prime/.prime/agent/extensions/egregore.ts +460 -0
  108. package/runtime/prime/.prime/agent/settings.json +4 -0
  109. package/runtime/prime/.prime/agent/skills/egregore/SKILL.md +34 -0
  110. package/runtime/prime/.prime/agent/skills/egregore/pyproject.toml +13 -0
  111. package/runtime/prime/.prime/agent/skills/egregore/src/egregore/__init__.py +156 -0
  112. package/runtime/prime/.prime/agent/spec-manifest.json +12 -0
  113. package/runtime/prime/AGENTS.md +489 -0
  114. package/runtime/prime/bin/activity-data.sh +238 -0
  115. package/runtime/prime/bin/agent.sh +775 -0
  116. package/runtime/prime/bin/attendant.sh +707 -0
  117. package/runtime/prime/bin/autosave.sh +112 -0
  118. package/runtime/prime/bin/capture-reconcile.sh +69 -0
  119. package/runtime/prime/bin/capture-run.sh +341 -0
  120. package/runtime/prime/bin/codex-install-skills.sh +78 -0
  121. package/runtime/prime/bin/codex-render-spec.mjs +440 -0
  122. package/runtime/prime/bin/codex-session-start.sh +265 -0
  123. package/runtime/prime/bin/codex-skill-render.mjs +347 -0
  124. package/runtime/prime/bin/codex-sync-skills.sh +232 -0
  125. package/runtime/prime/bin/connect-refresh.sh +33 -0
  126. package/runtime/prime/bin/dashboard-data.sh +178 -0
  127. package/runtime/prime/bin/graph-batch.sh +79 -0
  128. package/runtime/prime/bin/graph-op.sh +1102 -0
  129. package/runtime/prime/bin/graph-wal.sh +229 -0
  130. package/runtime/prime/bin/graph.sh +223 -0
  131. package/runtime/prime/bin/handoff-pr-backfill.sh +86 -0
  132. package/runtime/prime/bin/handoff-run.sh +671 -0
  133. package/runtime/prime/bin/handoff-save-egregore.sh +212 -0
  134. package/runtime/prime/bin/index-handoff.sh +322 -0
  135. package/runtime/prime/bin/ingest-graph.sh +80 -0
  136. package/runtime/prime/bin/ingest-surface.html +495 -0
  137. package/runtime/prime/bin/ingest.py +756 -0
  138. package/runtime/prime/bin/ingest.sh +279 -0
  139. package/runtime/prime/bin/ingest_graph.py +856 -0
  140. package/runtime/prime/bin/ingest_surface.py +544 -0
  141. package/runtime/prime/bin/lib/artifact-id.sh +46 -0
  142. package/runtime/prime/bin/lib/config.sh +116 -0
  143. package/runtime/prime/bin/lib/context.sh +621 -0
  144. package/runtime/prime/bin/lib/dashboard-artifact.sh +242 -0
  145. package/runtime/prime/bin/lib/ensure-develop.sh +183 -0
  146. package/runtime/prime/bin/lib/git-sync.sh +406 -0
  147. package/runtime/prime/bin/lib/greeting.sh +519 -0
  148. package/runtime/prime/bin/lib/hash.sh +13 -0
  149. package/runtime/prime/bin/lib/identity.sh +213 -0
  150. package/runtime/prime/bin/lib/metrics.sh +374 -0
  151. package/runtime/prime/bin/lib/noncode.sh +31 -0
  152. package/runtime/prime/bin/lib/permissions.sh +106 -0
  153. package/runtime/prime/bin/lib/time.sh +56 -0
  154. package/runtime/prime/bin/lib/worktree-links.sh +79 -0
  155. package/runtime/prime/bin/notify.sh +636 -0
  156. package/runtime/prime/bin/person.py +630 -0
  157. package/runtime/prime/bin/person.sh +448 -0
  158. package/runtime/prime/bin/pi-branch-policy.mjs +312 -0
  159. package/runtime/prime/bin/pi-product-workflows.mjs +394 -0
  160. package/runtime/prime/bin/prime-render-spec.mjs +123 -0
  161. package/runtime/prime/bin/prime-session-start.sh +24 -0
  162. package/runtime/prime/bin/publish-artifact.sh +271 -0
  163. package/runtime/prime/bin/publish-references.sh +144 -0
  164. package/runtime/prime/bin/repo-state.sh +96 -0
  165. package/runtime/prime/bin/search.sh +240 -0
  166. package/runtime/prime/bin/session-autosave.sh +209 -0
  167. package/runtime/prime/bin/session-log.sh +199 -0
  168. package/runtime/prime/bin/settings.sh +304 -0
  169. package/runtime/prime/bin/telemetry.sh +391 -0
  170. package/runtime/prime/bin/worktree-create.sh +128 -0
  171. package/runtime/prime/bin/worktree-remove.sh +50 -0
  172. package/runtime/prime/bin/worktree.sh +198 -0
  173. package/runtime/prime/manifest.json +178 -0
@@ -0,0 +1,406 @@
1
+ # shellcheck shell=bash
2
+ # git-sync.sh — Git synchronization for session-start.sh
3
+ #
4
+ # Handles all git operations during session startup:
5
+ # - Pull rebase configuration
6
+ # - Parallel fetches (origin, upstream, memory, managed repos)
7
+ # - Worktree orphan cleanup
8
+ # - Git health check
9
+ # - Develop branch setup + sync
10
+ # - Memory sync
11
+ # - Managed repos sync
12
+ #
13
+ # Inputs: SCRIPT_DIR, IS_WORKTREE, MAIN_PROJECT_DIR, STATE_FILE, ENV_FILE,
14
+ # HEALTH_GIT, BASE_BRANCH (optional — resolved here if unset),
15
+ # MANAGED_REPOS (not yet set — read from config here)
16
+ # Outputs: HEALTH_GIT, DEVELOP_SYNCED, COMMITS_AHEAD, ACTION, SAVED_BRANCH,
17
+ # BRANCH, MEMORY_SYNCED, REPOS_STATUS, MANAGED_REPOS, CURRENT_BRANCH,
18
+ # FRAMEWORK_UPDATED, FRAMEWORK_UPDATED_COUNT, FRAMEWORK_UPDATED_FILES,
19
+ # BASE_BRANCH, BASE_BRANCH_READY
20
+
21
+ FRAMEWORK_UPDATED="false"
22
+ FRAMEWORK_UPDATED_COUNT=0
23
+ FRAMEWORK_UPDATED_FILES=""
24
+
25
+ # The core repo's integration branch: "develop" unless egregore.json sets
26
+ # base_branch. session-start.sh resolves this before sourcing; resolve it here
27
+ # too so the lib stands alone for other callers.
28
+ if [ -z "${BASE_BRANCH:-}" ]; then
29
+ if ! BASE_BRANCH=$(_get_base_branch); then
30
+ HEALTH_GIT="fail"
31
+ return 1 2>/dev/null || exit 1
32
+ fi
33
+ fi
34
+
35
+ # --- Ensure pull.rebase is set (prevents "divergent branches" errors) ---
36
+ git config pull.rebase true 2>/dev/null || true
37
+
38
+ # --- Fetch all remotes in parallel ---
39
+ # Set git transfer timeout so hangs don't block startup indefinitely.
40
+ export GIT_HTTP_LOW_SPEED_LIMIT=1000 # abort if <1KB/s
41
+ export GIT_HTTP_LOW_SPEED_TIME=10 # for 10 seconds
42
+
43
+ # Attendant fast path: the ambient daemon (bin/attendant.sh) fetches origin,
44
+ # memory, and managed repos every few minutes. If its warm marker is fresh,
45
+ # refs are already up to date — skip the network fetches and launch on local
46
+ # ops only. Upstream fetch (framework update check) is not covered by the
47
+ # attendant and stays unconditional below.
48
+ _REFS_FRESH="false"
49
+ _ATT_SLUG=$(jq -r '.slug // "egregore"' "$SCRIPT_DIR/egregore.json" 2>/dev/null || echo "egregore")
50
+ # Key the marker by the MAIN checkout, not this directory: the attendant keys
51
+ # its state by MAIN_DIR, so hashing a worktree path here meant worktree
52
+ # sessions never matched the warm marker and always paid the full fetch round.
53
+ # Worktrees share refs with the main checkout (common git dir), so the skip is
54
+ # equally valid there. session-start.sh provides MAIN_PROJECT_DIR; standalone
55
+ # callers fall back to SCRIPT_DIR (identical for non-worktree checkouts).
56
+ _ATT_KEY="${_ATT_SLUG}-$(echo -n "${MAIN_PROJECT_DIR:-$SCRIPT_DIR}" | cksum | cut -d' ' -f1)"
57
+ _ATT_MARKER="${ATTENDANT_HOME:-$HOME/.egregore/attendant}/${_ATT_KEY}.warm-ts"
58
+ if [ -f "$_ATT_MARKER" ]; then
59
+ # Harden the read: an empty or garbled marker (e.g. caught mid-write) must
60
+ # degrade to "not fresh", never abort startup with an arithmetic error.
61
+ _ATT_TS=$(cat "$_ATT_MARKER" 2>/dev/null || echo 0)
62
+ case "$_ATT_TS" in ''|*[!0-9]*) _ATT_TS=0 ;; esac
63
+ _ATT_AGE=$(( $(date +%s) - _ATT_TS ))
64
+ # The attendant warms every ~5min and stamps the marker AFTER its fetches
65
+ # finish, so a healthy marker's age peaks just above 300s. A 360s threshold
66
+ # made the skip a coin flip — half of boots paid the full fetch round for
67
+ # nothing. 600s (2× the warm cycle) means refs are at most ~10min stale at
68
+ # launch, and the attendant converges them minutes later anyway.
69
+ [ "$_ATT_AGE" -ge 0 ] && [ "$_ATT_AGE" -lt 600 ] && _REFS_FRESH="true"
70
+ fi
71
+
72
+ [ "$_REFS_FRESH" != "true" ] && git fetch origin --quiet 2>/dev/null &
73
+
74
+ # Fetch upstream framework (for update check — non-blocking)
75
+ # Default: official egregore-core repo. Set upstream_url in egregore.json to override,
76
+ # or set it to "none" to disable (e.g., in the dev repo where this IS the source).
77
+ _UPSTREAM_URL=$(jq -r '.upstream_url // empty' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
78
+ [ -z "$_UPSTREAM_URL" ] && _UPSTREAM_URL="https://github.com/egregore-labs/egregore.git"
79
+ if [ "$_UPSTREAM_URL" != "none" ]; then
80
+ git remote add upstream "$_UPSTREAM_URL" 2>/dev/null || true
81
+ git fetch upstream main --quiet 2>/dev/null &
82
+ fi
83
+
84
+ # Sync memory in parallel (symlink or direct directory — both valid)
85
+ MEMORY_SYNCED="false"
86
+ if [ -d "$SCRIPT_DIR/memory/.git" ] && [ "$_REFS_FRESH" != "true" ]; then
87
+ git -C "$SCRIPT_DIR/memory" fetch origin --quiet 2>/dev/null &
88
+ fi
89
+
90
+ # Fetch managed repos in parallel
91
+ MANAGED_REPOS=$(jq -r '(.repos[]? // empty) | if type == "object" then .name else . end' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
92
+ if [ "$_REFS_FRESH" != "true" ]; then
93
+ for REPO in $MANAGED_REPOS; do
94
+ if [ -d "$SCRIPT_DIR/../$REPO/.git" ]; then
95
+ git -C "$SCRIPT_DIR/../$REPO" fetch origin --quiet 2>/dev/null &
96
+ fi
97
+ done
98
+ fi
99
+
100
+ # Wait for all fetches
101
+ wait 2>/dev/null || true
102
+
103
+ # --- Auto-update framework from upstream (deferred) ---
104
+ # Moved AFTER setup_develop() so framework commits land on develop, not on
105
+ # working branches. See _apply_framework_update() below.
106
+
107
+ # _read_auto_update — resolve the auto_update flag. Correct, and FAIL CLOSED.
108
+ #
109
+ # This flag is the only thing standing between a user's checkout and a blind
110
+ # `git checkout upstream/main --` over every framework path, so it has to be
111
+ # read exactly right. It was not.
112
+ #
113
+ # The bug: `jq -r '.auto_update // true'`. jq's `//` returns its right-hand
114
+ # side when the left is null *or false* — so a config saying
115
+ # {"auto_update": false} evaluated to the string "true", and the guard
116
+ # `[ "$_AUTO_UPDATE" = "false" ]` could never fire. The documented opt-out
117
+ # has never disabled anything for anyone. Do not reintroduce `// <bool>` for
118
+ # a flag whose `false` is meaningful; read the raw value and map it.
119
+ #
120
+ # Second failure: jq prints nothing and exits non-zero on a missing or
121
+ # malformed egregore.json, and an empty string is not "false" either — so an
122
+ # unreadable config also read as consent. Absent key means "on" (the intended
123
+ # default); everything else that is not a parsed `true` means "leave the
124
+ # user's files alone".
125
+ _read_auto_update() {
126
+ local _config="$SCRIPT_DIR/egregore.json"
127
+ local _val
128
+ [ -f "$_config" ] || { echo "false"; return; }
129
+ # `| tojson`, not a bare `-r` read: under -r a *string* "true" renders as bare
130
+ # true, indistinguishable from boolean true, so {"auto_update": "true"} would
131
+ # re-authorize the overwrite — a fail-open hole in the middle of a fail-closed
132
+ # resolver. tojson keeps the quotes, so the true arm matches only an absent key
133
+ # or a real boolean.
134
+ _val=$(jq -r '.auto_update | tojson' "$_config" 2>/dev/null) || { echo "false"; return; }
135
+ case "$_val" in
136
+ null|true) echo "true" ;; # key absent, or explicitly enabled
137
+ *) echo "false" ;; # false, "false", "true", garbage, or unparseable
138
+ esac
139
+ }
140
+
141
+ _apply_framework_update() {
142
+ # Applies framework paths from upstream to the CURRENT branch, which must be
143
+ # the configured base branch.
144
+ # Disable with "auto_update": false in egregore.json.
145
+ # Dev repos (upstream_url: "none") skip this entirely.
146
+ #
147
+ # The flag is read HERE, not at source time: callers reach this function
148
+ # after setup_develop() has switched branches, and egregore.json may differ
149
+ # between the branch the session opened on and the configured base. Reading
150
+ # late means the config that is actually checked out is the config that
151
+ # decides.
152
+ local _update_branch
153
+ _update_branch=$(git branch --show-current 2>/dev/null) || return 1
154
+ if [ "$_update_branch" != "$BASE_BRANCH" ]; then
155
+ return 1
156
+ fi
157
+ _AUTO_UPDATE=$(_read_auto_update)
158
+ if [ "$_UPSTREAM_URL" = "none" ] || [ "$_AUTO_UPDATE" != "true" ]; then
159
+ return 0
160
+ fi
161
+ if ! git show-ref --verify --quiet refs/remotes/upstream/main 2>/dev/null; then
162
+ return 0
163
+ fi
164
+ # Re-sync the base branch right before committing onto it: the warm-marker
165
+ # fast path can leave origin/$BASE_BRANCH up to ~10min stale, and an update
166
+ # committed onto a stale base creates divergence that later ff-only syncs
167
+ # cannot resolve. Only this rare path (upstream set + auto_update on) pays
168
+ # the fetch. FAIL CLOSED: if the fetch or fast-forward doesn't succeed
169
+ # (offline, or the base already diverged), skip the update entirely rather
170
+ # than commit upstream files onto a stale base — the next healthy boot
171
+ # applies it.
172
+ git fetch origin "$BASE_BRANCH" --quiet 2>/dev/null || return 0
173
+ git merge --ff-only "origin/$BASE_BRANCH" --quiet 2>/dev/null || return 0
174
+ # Apply upstream changes — checkout is idempotent, skip diff check
175
+ # (git diff with variable path lists breaks in zsh — no word-splitting)
176
+ for _p in bin/ .claude/commands/ .claude/skills/ .claude/hooks/ .claude/context/ .claude/agents/ loom/ CLAUDE.md skills/; do
177
+ git checkout upstream/main -- "$_p" 2>/dev/null || true
178
+ done
179
+ # Only commit if there are actual changes. Record WHAT changed before
180
+ # committing — an overwrite of local edits is the one outcome the user has
181
+ # to be able to see afterwards, and the commit is the only other record.
182
+ local _changed
183
+ _changed=$(git status --porcelain bin/ .claude/ loom/ CLAUDE.md skills/ 2>/dev/null)
184
+ if [ -n "$_changed" ]; then
185
+ FRAMEWORK_UPDATED_COUNT=$(printf '%s\n' "$_changed" | grep -c . || echo 0)
186
+ FRAMEWORK_UPDATED_FILES=$(printf '%s\n' "$_changed" | awk '{print $NF}' | head -3 | tr '\n' ' ')
187
+ git add bin/ .claude/ loom/ CLAUDE.md skills/ 2>/dev/null
188
+ EGREGORE_FRAMEWORK_UPDATE=1 git commit -m "Auto-update Egregore framework" --quiet 2>/dev/null || true
189
+ FRAMEWORK_UPDATED="true"
190
+ fi
191
+ }
192
+
193
+ # --- Worktree prune (just clean git's internal list, don't delete anything) ---
194
+ git worktree prune 2>/dev/null || true
195
+
196
+ # --- Git health check ---
197
+ if git show-ref --verify --quiet "refs/remotes/origin/$BASE_BRANCH" 2>/dev/null; then
198
+ HEALTH_GIT="ok"
199
+ else
200
+ HEALTH_GIT="fail"
201
+ fi
202
+
203
+ # --- Guarded staging -------------------------------------------------------
204
+ # git_add_guarded() lives in the shared lib so the /handoff auto-save uses the
205
+ # exact same logic. Fallback to plain `git add -A` if the lib can't be sourced,
206
+ # so an unattended auto-save never silently no-ops.
207
+ # shellcheck source=bin/lib/git-safe.sh
208
+ . "$SCRIPT_DIR/bin/lib/git-safe.sh" 2>/dev/null || true
209
+ if ! type git_add_guarded >/dev/null 2>&1; then
210
+ git_add_guarded() { git add -A 2>/dev/null || true; }
211
+ fi
212
+
213
+ # --- Setup base branch (wrapped — failures set HEALTH_GIT but don't crash) ---
214
+ # Named setup_develop for continuity; it operates on $BASE_BRANCH, which is
215
+ # "develop" by default and whatever egregore.json's base_branch says otherwise.
216
+ setup_develop() {
217
+ # Ensure the base branch exists locally
218
+ if ! git show-ref --verify --quiet "refs/heads/$BASE_BRANCH" 2>/dev/null; then
219
+ if git show-ref --verify --quiet "refs/remotes/origin/$BASE_BRANCH" 2>/dev/null; then
220
+ git checkout -b "$BASE_BRANCH" "origin/$BASE_BRANCH" --quiet 2>/dev/null || return 1
221
+ else
222
+ # Not on the remote either — create it and publish it
223
+ git checkout -b "$BASE_BRANCH" --quiet 2>/dev/null || return 1
224
+ git push -u origin "$BASE_BRANCH" --quiet 2>/dev/null || return 1
225
+ fi
226
+ fi
227
+
228
+ # Sync the base branch (without checkout — safe for concurrent sessions)
229
+ CURRENT_BRANCH=$(git branch --show-current 2>/dev/null) || return 1
230
+
231
+ # Update the local ref from remote without switching branches, using
232
+ # force-update to handle divergence (e.g., from auto-update commits).
233
+ # origin/$BASE_BRANCH is already current: either the parallel `git fetch
234
+ # origin` above ran this boot, or the attendant fetched within the freshness
235
+ # window. A second fetch here cost ~2s on every boot and never changed refs.
236
+ if [[ "$CURRENT_BRANCH" != "$BASE_BRANCH" ]]; then
237
+ # Safe to force-update when not checked out
238
+ git branch -f "$BASE_BRANCH" "origin/$BASE_BRANCH" 2>/dev/null || true
239
+ else
240
+ # On the base branch — try ff merge, fall back to reset only if no unpushed work
241
+ git merge --ff-only "origin/$BASE_BRANCH" --quiet 2>/dev/null || {
242
+ # Only reset if there are no unpushed commits and no dirty tracked work.
243
+ # A reset here runs before autosave and must never discard local changes.
244
+ LOCAL_AHEAD=$(git rev-list "origin/$BASE_BRANCH..$BASE_BRANCH" --count 2>/dev/null || echo "0")
245
+ if [ "$LOCAL_AHEAD" = "0" ] && [ -z "$(git status --porcelain 2>/dev/null)" ]; then
246
+ git reset --hard "origin/$BASE_BRANCH" --quiet 2>/dev/null || true
247
+ fi
248
+ }
249
+ fi
250
+ # Count commits on the base branch ahead of main. Meaningless in
251
+ # single-branch mode, where the base branch IS main.
252
+ COMMITS_AHEAD=0
253
+ if [ "$BASE_BRANCH" != "main" ] && git show-ref --verify --quiet refs/remotes/origin/main 2>/dev/null; then
254
+ COMMITS_AHEAD=$(git rev-list "origin/main..$BASE_BRANCH" --count 2>/dev/null || echo "0")
255
+ fi
256
+
257
+ # Always start from the base branch
258
+ # Every session begins there. Claude creates a fresh topic branch
259
+ # (dev/{author}/{topic-slug}) when the user says what they're working on.
260
+ # If we're on a working branch with uncommitted work, save it first.
261
+ ACTION="ready"
262
+ SAVED_BRANCH=""
263
+
264
+ if [[ "$CURRENT_BRANCH" != "$BASE_BRANCH" ]]; then
265
+ # Check for uncommitted work (staged + unstaged + untracked in tracked dirs)
266
+ if [ -n "$(git status --porcelain 2>/dev/null | head -1)" ]; then
267
+ # Save uncommitted work so nothing is lost (guarded: never auto-commit
268
+ # stray submodule pointers from sibling repos in the checkout)
269
+ git_add_guarded
270
+ git commit -m "Auto-save: uncommitted work from $CURRENT_BRANCH" --quiet 2>/dev/null || true
271
+ SAVED_BRANCH="$CURRENT_BRANCH"
272
+ fi
273
+
274
+ # Push the branch if it has unpushed commits (so work is never only local)
275
+ if [[ "$CURRENT_BRANCH" == dev/* ]] || [[ "$CURRENT_BRANCH" == feature/* ]] || [[ "$CURRENT_BRANCH" == bugfix/* ]]; then
276
+ LOCAL_HEAD=$(git rev-parse HEAD 2>/dev/null || echo "")
277
+ REMOTE_HEAD=$(git rev-parse "origin/$CURRENT_BRANCH" 2>/dev/null || echo "none")
278
+ if [ "$LOCAL_HEAD" != "$REMOTE_HEAD" ]; then
279
+ git push origin "$CURRENT_BRANCH" --quiet 2>/dev/null || true
280
+ fi
281
+ fi
282
+
283
+ # Switch to the base branch (already updated by the fetch above)
284
+ if ! git checkout "$BASE_BRANCH" --quiet 2>/dev/null; then
285
+ BRANCH=$(git branch --show-current 2>/dev/null || echo "?")
286
+ return 1
287
+ fi
288
+ else
289
+ # Already on it — fetch couldn't update a checked-out branch, so pull
290
+ git merge --ff-only "origin/$BASE_BRANCH" --quiet 2>/dev/null || true
291
+ fi
292
+
293
+ # Never report the intended branch as the actual branch. A checkout can fail
294
+ # when the base is held by another worktree or the index blocks the switch.
295
+ # setup_develop must fail in that case so callers cannot apply framework files
296
+ # to the user's working branch.
297
+ BRANCH=$(git branch --show-current 2>/dev/null || echo "?")
298
+ if [ "$BRANCH" != "$BASE_BRANCH" ]; then
299
+ return 1
300
+ fi
301
+ DEVELOP_SYNCED="true"
302
+ return 0
303
+ }
304
+
305
+ # Initialize defaults used by setup_develop
306
+ CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
307
+ DEVELOP_SYNCED="false"
308
+ COMMITS_AHEAD=0
309
+ ACTION="ready"
310
+ SAVED_BRANCH=""
311
+ BRANCH="${CURRENT_BRANCH:-$BASE_BRANCH}"
312
+ BASE_BRANCH_READY="false"
313
+
314
+ if [ "$IS_WORKTREE" = "true" ]; then
315
+ # Inside a worktree — skip the base-branch checkout and framework update.
316
+ # Worktrees are isolated working copies; framework updates belong on the base.
317
+ BRANCH=$(git branch --show-current 2>/dev/null || echo "?")
318
+ DEVELOP_SYNCED="true"
319
+
320
+ # --- Worktree branch health check ---
321
+ # Detect if the branch was already merged into the base branch (e.g., PR
322
+ # merged but worktree not cleaned up). This prevents working on a stale
323
+ # branch whose commits already landed.
324
+ WORKTREE_STALE="false"
325
+ if [ "$BRANCH" != "?" ] && [ "$BRANCH" != "$BASE_BRANCH" ] && [ "$BRANCH" != "main" ]; then
326
+ # Check if remote branch still exists. When the attendant marker is fresh,
327
+ # local remote-tracking refs are authoritative (the attendant fetches with
328
+ # --prune every warm cycle) — answering locally saves a ~2s ls-remote
329
+ # round-trip on every worktree boot. Cold marker keeps the live query.
330
+ if [ "$_REFS_FRESH" = "true" ]; then
331
+ _BRANCH_ON_REMOTE() { git show-ref --verify --quiet "refs/remotes/origin/$BRANCH" 2>/dev/null; }
332
+ else
333
+ _BRANCH_ON_REMOTE() { git ls-remote --heads origin "$BRANCH" 2>/dev/null | grep -q "$BRANCH"; }
334
+ fi
335
+ if ! _BRANCH_ON_REMOTE; then
336
+ # Remote branch gone — check if it was merged into the base branch
337
+ if git branch -r --merged "origin/$BASE_BRANCH" 2>/dev/null | grep -q "origin/$BRANCH" 2>/dev/null || \
338
+ git merge-base --is-ancestor HEAD "origin/$BASE_BRANCH" 2>/dev/null; then
339
+ WORKTREE_STALE="merged"
340
+ else
341
+ WORKTREE_STALE="remote_deleted"
342
+ fi
343
+ fi
344
+ fi
345
+
346
+ if [ "$WORKTREE_STALE" = "merged" ]; then
347
+ echo ""
348
+ echo "WARNING: Branch '$BRANCH' was already merged into $BASE_BRANCH."
349
+ echo "This worktree is stale — your work here is already in $BASE_BRANCH."
350
+ echo "Start a new session in the main project to get a fresh branch."
351
+ echo ""
352
+ HEALTH_GIT="fail"
353
+ elif [ "$WORKTREE_STALE" = "remote_deleted" ]; then
354
+ echo ""
355
+ echo "WARNING: Remote branch '$BRANCH' was deleted but NOT merged into $BASE_BRANCH."
356
+ echo "This worktree may contain unique local commits. Check before discarding:"
357
+ echo " git log origin/$BASE_BRANCH..HEAD --oneline"
358
+ echo ""
359
+ fi
360
+
361
+ # Use main project's .env and state if ours are missing
362
+ if [ ! -f "$SCRIPT_DIR/.env" ] && [ -f "$MAIN_PROJECT_DIR/.env" ]; then
363
+ export ENV_FILE="$MAIN_PROJECT_DIR/.env"
364
+ fi
365
+ elif setup_develop 2>/dev/null; then
366
+ BASE_BRANCH_READY="true"
367
+ else
368
+ HEALTH_GIT="fail"
369
+ fi
370
+
371
+ # Apply a framework update only after setup verified the configured base branch.
372
+ if [ "$IS_WORKTREE" != "true" ] && [ "$BASE_BRANCH_READY" = "true" ]; then
373
+ _apply_framework_update 2>/dev/null || true
374
+ fi
375
+
376
+ # --- Sync memory (symlink or direct directory — both valid) ---
377
+ if [ -d "$SCRIPT_DIR/memory/.git" ]; then
378
+ MEM_LOCAL=$(git -C "$SCRIPT_DIR/memory" rev-parse HEAD 2>/dev/null || echo "")
379
+ MEM_REMOTE=$(git -C "$SCRIPT_DIR/memory" rev-parse origin/main 2>/dev/null || echo "")
380
+ if [ -n "$MEM_LOCAL" ] && [ -n "$MEM_REMOTE" ] && [ "$MEM_LOCAL" != "$MEM_REMOTE" ]; then
381
+ git -C "$SCRIPT_DIR/memory" pull origin main --quiet 2>/dev/null || true
382
+ fi
383
+ MEMORY_SYNCED="true"
384
+ fi
385
+
386
+ # --- Sync managed repos ---
387
+ # Managed repos sync silently — they are a CLI concern and never render in the
388
+ # greeting. REPOS_STATUS stays exported (empty) for consumers of this lib.
389
+ REPOS_STATUS=""
390
+ for REPO in $MANAGED_REPOS; do
391
+ REPO_DIR="$SCRIPT_DIR/../$REPO"
392
+ if [ -d "$REPO_DIR/.git" ]; then
393
+ # Resolve base branch for this repo (from egregore.json or auto-detect)
394
+ if ! REPO_BASE=$(_get_base_branch "$REPO"); then
395
+ HEALTH_GIT="fail"
396
+ continue
397
+ fi
398
+ # Sync base branch with remote (if not checked out)
399
+ R_BRANCH=$(git -C "$REPO_DIR" branch --show-current 2>/dev/null || echo "?")
400
+ if [ "$R_BRANCH" != "$REPO_BASE" ]; then
401
+ git -C "$REPO_DIR" branch -f "$REPO_BASE" "origin/$REPO_BASE" --quiet 2>/dev/null || true
402
+ else
403
+ git -C "$REPO_DIR" merge --ff-only "origin/$REPO_BASE" --quiet 2>/dev/null || true
404
+ fi
405
+ fi
406
+ done