create-egregore 0.19.23 → 0.19.24

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 (119) hide show
  1. package/assets/egregore-launcher.js +50 -7
  2. package/assets/egregore-launcher.sh +10 -2
  3. package/bin/cli.js +21 -3
  4. package/lib/codex-runtime.js +25 -3
  5. package/lib/connect-runtime.js +329 -0
  6. package/lib/pi-runtime.js +23 -3
  7. package/lib/setup.js +13 -0
  8. package/package.json +3 -3
  9. package/runtime/codex/.codex/skills/handoff/SKILL.md +35 -11
  10. package/runtime/codex/.codex/skills/save/SKILL.md +39 -4
  11. package/runtime/codex/.codex/spec-manifest.json +2 -2
  12. package/runtime/codex/AGENTS.md +18 -1
  13. package/runtime/codex/bin/agent.sh +28 -14
  14. package/runtime/codex/bin/attendant.sh +169 -6
  15. package/runtime/codex/bin/codex-render-spec.mjs +18 -1
  16. package/runtime/codex/bin/codex-session-start.sh +13 -0
  17. package/runtime/codex/bin/codex-sync-skills.sh +1 -0
  18. package/runtime/codex/bin/graph.sh +21 -1
  19. package/runtime/codex/bin/handoff-run.sh +25 -5
  20. package/runtime/codex/bin/lib/context.sh +220 -172
  21. package/runtime/codex/bin/lib/dashboard-artifact.sh +113 -13
  22. package/runtime/codex/bin/lib/git-sync.sh +44 -8
  23. package/runtime/codex/bin/lib/greeting.sh +34 -11
  24. package/runtime/codex/bin/lib/identity.sh +32 -14
  25. package/runtime/codex/bin/person.py +20 -0
  26. package/runtime/codex/manifest.json +3 -72
  27. package/runtime/pi/.codex/skills/handoff/SKILL.md +35 -11
  28. package/runtime/pi/.codex/skills/save/SKILL.md +39 -4
  29. package/runtime/pi/.codex/spec-manifest.json +2 -2
  30. package/runtime/pi/.pi/APPEND_SYSTEM.md +18 -1
  31. package/runtime/pi/.pi/spec-manifest.json +2 -2
  32. package/runtime/pi/AGENTS.md +18 -1
  33. package/runtime/pi/bin/agent.sh +28 -14
  34. package/runtime/pi/bin/attendant.sh +169 -6
  35. package/runtime/pi/bin/codex-render-spec.mjs +18 -1
  36. package/runtime/pi/bin/codex-session-start.sh +13 -0
  37. package/runtime/pi/bin/codex-sync-skills.sh +1 -0
  38. package/runtime/pi/bin/graph.sh +21 -1
  39. package/runtime/pi/bin/handoff-run.sh +25 -5
  40. package/runtime/pi/bin/lib/context.sh +220 -172
  41. package/runtime/pi/bin/lib/dashboard-artifact.sh +113 -13
  42. package/runtime/pi/bin/lib/git-sync.sh +44 -8
  43. package/runtime/pi/bin/lib/greeting.sh +34 -11
  44. package/runtime/pi/bin/lib/identity.sh +32 -14
  45. package/runtime/pi/bin/person.py +20 -0
  46. package/runtime/pi/bin/pi-product-workflows.mjs +5 -1
  47. package/runtime/pi/manifest.json +3 -72
  48. package/runtime/codex/.codex/skills/add/SKILL.md +0 -29
  49. package/runtime/codex/.codex/skills/answer/SKILL.md +0 -29
  50. package/runtime/codex/.codex/skills/ask/SKILL.md +0 -67
  51. package/runtime/codex/.codex/skills/audit/SKILL.md +0 -29
  52. package/runtime/codex/.codex/skills/autosave/SKILL.md +0 -29
  53. package/runtime/codex/.codex/skills/branch/SKILL.md +0 -29
  54. package/runtime/codex/.codex/skills/commit/SKILL.md +0 -29
  55. package/runtime/codex/.codex/skills/demo-mode/SKILL.md +0 -29
  56. package/runtime/codex/.codex/skills/deploy-bridge/SKILL.md +0 -29
  57. package/runtime/codex/.codex/skills/design/SKILL.md +0 -29
  58. package/runtime/codex/.codex/skills/egregore-voice/SKILL.md +0 -29
  59. package/runtime/codex/.codex/skills/emissary/SKILL.md +0 -45
  60. package/runtime/codex/.codex/skills/env/SKILL.md +0 -29
  61. package/runtime/codex/.codex/skills/ingest-google/SKILL.md +0 -29
  62. package/runtime/codex/.codex/skills/minion/SKILL.md +0 -29
  63. package/runtime/codex/.codex/skills/mock/SKILL.md +0 -29
  64. package/runtime/codex/.codex/skills/pr/SKILL.md +0 -29
  65. package/runtime/codex/.codex/skills/product-voice/SKILL.md +0 -29
  66. package/runtime/codex/.codex/skills/project/SKILL.md +0 -45
  67. package/runtime/codex/.codex/skills/pull/SKILL.md +0 -29
  68. package/runtime/codex/.codex/skills/push/SKILL.md +0 -29
  69. package/runtime/codex/.codex/skills/quest-suggest/SKILL.md +0 -29
  70. package/runtime/codex/.codex/skills/review-pr/SKILL.md +0 -45
  71. package/runtime/codex/.codex/skills/scroll/SKILL.md +0 -288
  72. package/runtime/codex/.codex/skills/scroll/agents/openai.yaml +0 -4
  73. package/runtime/codex/.codex/skills/scroll/assets/shell.html +0 -871
  74. package/runtime/codex/.codex/skills/search/SKILL.md +0 -79
  75. package/runtime/codex/.codex/skills/session-view/SKILL.md +0 -29
  76. package/runtime/codex/.codex/skills/setup/SKILL.md +0 -29
  77. package/runtime/codex/.codex/skills/summon/SKILL.md +0 -45
  78. package/runtime/codex/.codex/skills/task/SKILL.md +0 -29
  79. package/runtime/codex/.codex/skills/teams-connect/SKILL.md +0 -29
  80. package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +0 -29
  81. package/runtime/codex/.codex/skills/the-spiral/SKILL.md +0 -84
  82. package/runtime/codex/.codex/skills/thread/SKILL.md +0 -29
  83. package/runtime/codex/.codex/skills/todo/SKILL.md +0 -45
  84. package/runtime/pi/.codex/skills/add/SKILL.md +0 -29
  85. package/runtime/pi/.codex/skills/answer/SKILL.md +0 -29
  86. package/runtime/pi/.codex/skills/ask/SKILL.md +0 -67
  87. package/runtime/pi/.codex/skills/audit/SKILL.md +0 -29
  88. package/runtime/pi/.codex/skills/autosave/SKILL.md +0 -29
  89. package/runtime/pi/.codex/skills/branch/SKILL.md +0 -29
  90. package/runtime/pi/.codex/skills/commit/SKILL.md +0 -29
  91. package/runtime/pi/.codex/skills/demo-mode/SKILL.md +0 -29
  92. package/runtime/pi/.codex/skills/deploy-bridge/SKILL.md +0 -29
  93. package/runtime/pi/.codex/skills/design/SKILL.md +0 -29
  94. package/runtime/pi/.codex/skills/egregore-voice/SKILL.md +0 -29
  95. package/runtime/pi/.codex/skills/emissary/SKILL.md +0 -45
  96. package/runtime/pi/.codex/skills/env/SKILL.md +0 -29
  97. package/runtime/pi/.codex/skills/ingest-google/SKILL.md +0 -29
  98. package/runtime/pi/.codex/skills/minion/SKILL.md +0 -29
  99. package/runtime/pi/.codex/skills/mock/SKILL.md +0 -29
  100. package/runtime/pi/.codex/skills/pr/SKILL.md +0 -29
  101. package/runtime/pi/.codex/skills/product-voice/SKILL.md +0 -29
  102. package/runtime/pi/.codex/skills/project/SKILL.md +0 -45
  103. package/runtime/pi/.codex/skills/pull/SKILL.md +0 -29
  104. package/runtime/pi/.codex/skills/push/SKILL.md +0 -29
  105. package/runtime/pi/.codex/skills/quest-suggest/SKILL.md +0 -29
  106. package/runtime/pi/.codex/skills/review-pr/SKILL.md +0 -45
  107. package/runtime/pi/.codex/skills/scroll/SKILL.md +0 -288
  108. package/runtime/pi/.codex/skills/scroll/agents/openai.yaml +0 -4
  109. package/runtime/pi/.codex/skills/scroll/assets/shell.html +0 -871
  110. package/runtime/pi/.codex/skills/search/SKILL.md +0 -79
  111. package/runtime/pi/.codex/skills/session-view/SKILL.md +0 -29
  112. package/runtime/pi/.codex/skills/setup/SKILL.md +0 -29
  113. package/runtime/pi/.codex/skills/summon/SKILL.md +0 -45
  114. package/runtime/pi/.codex/skills/task/SKILL.md +0 -29
  115. package/runtime/pi/.codex/skills/teams-connect/SKILL.md +0 -29
  116. package/runtime/pi/.codex/skills/telegram-connect/SKILL.md +0 -29
  117. package/runtime/pi/.codex/skills/the-spiral/SKILL.md +0 -84
  118. package/runtime/pi/.codex/skills/thread/SKILL.md +0 -29
  119. package/runtime/pi/.codex/skills/todo/SKILL.md +0 -45
@@ -124,19 +124,119 @@ mkdir -p /tmp
124
124
  # publish-artifact.sh handles HTML generation internally (npx egregore-artifacts)
125
125
  # then uploads. Don't pre-generate HTML — that causes double-wrapping.
126
126
  # https:// URLs are universally cmd+clickable across all terminal emulators.
127
- # publish-artifact.sh may output npx progress lines before the URL — extract just the URL
128
- _DB_PUBLISH_OUT=$(bash "$SCRIPT_DIR/bin/publish-artifact.sh" document "$_DB_MD" \
129
- --title "Session Dashboard" \
130
- --author "${GREETING_NAME:-$AUTHOR}" 2>/dev/null || true)
131
- DASHBOARD_URL=$(echo "$_DB_PUBLISH_OUT" | grep -o 'https://[^ ]*' | tail -1)
132
-
133
- # Fallback: generate HTML locally and use file:// URL (offline, no API key, etc.)
134
- if [ -z "$DASHBOARD_URL" ]; then
135
- _DB_HTML="/tmp/egregore-artifacts/dashboard-${EGREGORE_SESSION_ID}.html"
136
- mkdir -p /tmp/egregore-artifacts
137
- if npx egregore-artifacts@latest document "$_DB_MD" --output "$_DB_HTML" --no-open >/dev/null 2>&1 && [ -f "$_DB_HTML" ]; then
138
- DASHBOARD_URL="file://${_DB_HTML}"
127
+ #
128
+ # Connected mode publishes to a STABLE per-user artifact id and runs the
129
+ # render+upload detached: the URL is known up front, so the greeting prints it
130
+ # without paying the ~2s render/upload on the boot path. Content lands moments
131
+ # later; until then (and through the ~5min edge cache) the URL serves the
132
+ # previous session's dashboard, which is an acceptable trade for a greeting
133
+ # link. This also stops every boot from minting a new artifact URL.
134
+ #
135
+ # The id is RANDOM, generated once and persisted in the user's state file —
136
+ # never derived from the username. Dashboard reads are public and unauth'd,
137
+ # so a predictable id (org slug + github handle) would make every teammate's
138
+ # operational dashboard enumerable; and a name-derived id without an org
139
+ # component collides across orgs for multi-org users (artifact ids upsert
140
+ # globally). Random-per-instance gives unguessable AND collision-free.
141
+ _DB_SLUG=$(jq -r '.slug // empty' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
142
+ _DB_STATE="${STATE_FILE:-$SCRIPT_DIR/.egregore-state.json}"
143
+ _DB_ID=$(jq -r '.dashboard_artifact_id // empty' "$_DB_STATE" 2>/dev/null)
144
+ if [ -z "$_DB_ID" ] && [ -f "$_DB_STATE" ]; then
145
+ # First-id creation is SERIALIZED (mkdir is the portable atomic primitive):
146
+ # without a lock, two first boots can each persist-then-adopt different ids
147
+ # and both publish, stranding an orphan stable artifact. The winner creates
148
+ # exactly one id; the loser re-reads and either adopts it or (readback still
149
+ # empty) falls through to the legacy synchronous publish for this boot.
150
+ # A crashed creator's stale lock breaks after 5 minutes.
151
+ _DB_LOCK="$HOME/.egregore/dashboard-id-$(echo -n "${MAIN_PROJECT_DIR:-$SCRIPT_DIR}" | cksum | cut -d' ' -f1).lock"
152
+ mkdir -p "$HOME/.egregore" 2>/dev/null
153
+ if ! mkdir "$_DB_LOCK" 2>/dev/null; then
154
+ _DB_LOCK_AGE=$(( $(date +%s) - $(stat -f %m "$_DB_LOCK" 2>/dev/null || stat -c %Y "$_DB_LOCK" 2>/dev/null || echo 0) ))
155
+ # Stale reap by atomic rename, never rmdir+mkdir: two reapers doing
156
+ # remove-then-recreate can alternately delete each other's FRESH lock and
157
+ # both proceed. Directory rename is atomic — exactly one reaper's mv
158
+ # succeeds; the loser (and anyone losing the follow-up mkdir to a fresh
159
+ # contender) backs off to the read-only path.
160
+ if [ "$_DB_LOCK_AGE" -gt 300 ] && mv "$_DB_LOCK" "$_DB_LOCK.reap.$$" 2>/dev/null; then
161
+ rm -rf "$_DB_LOCK.reap.$$" 2>/dev/null
162
+ mkdir "$_DB_LOCK" 2>/dev/null || _DB_LOCK=""
163
+ else
164
+ _DB_LOCK=""
165
+ fi
166
+ fi
167
+ if [ -n "$_DB_LOCK" ]; then
168
+ # Re-read under the lock — the racing boot may have created the id already.
169
+ _DB_ID=$(jq -r '.dashboard_artifact_id // empty' "$_DB_STATE" 2>/dev/null)
170
+ if [ -z "$_DB_ID" ]; then
171
+ _DB_RAND=$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom 2>/dev/null | head -c 16)
172
+ [ -n "$_DB_RAND" ] || _DB_RAND=$(openssl rand -hex 8 2>/dev/null)
173
+ if [ -n "$_DB_RAND" ]; then
174
+ jq --arg id "dashboard-${_DB_RAND}" \
175
+ '.dashboard_artifact_id = (.dashboard_artifact_id // $id)' "$_DB_STATE" > "$_DB_STATE.tmp.$$" 2>/dev/null \
176
+ && mv "$_DB_STATE.tmp.$$" "$_DB_STATE" 2>/dev/null || rm -f "$_DB_STATE.tmp.$$" 2>/dev/null
177
+ _DB_ID=$(jq -r '.dashboard_artifact_id // empty' "$_DB_STATE" 2>/dev/null)
178
+ fi
179
+ fi
180
+ rmdir "$_DB_LOCK" 2>/dev/null || true
181
+ else
182
+ # Lost the race — adopt whatever the winner persisted, if it landed yet.
183
+ _DB_ID=$(jq -r '.dashboard_artifact_id // empty' "$_DB_STATE" 2>/dev/null)
139
184
  fi
140
185
  fi
186
+ if [ "${LOCAL_MODE:-false}" != "true" ] && [ -n "$_DB_SLUG" ] && [ -n "$_DB_ID" ]; then
187
+ # Confirmation is BOUND TO THE ID: dashboard_published_id must equal the id
188
+ # we are about to advertise. A bare boolean could be set by a publish of a
189
+ # different id (racing first boots) or by publish-artifact's ephemeral-relay
190
+ # fallback returning some other https URL when the API key is missing —
191
+ # later boots would then advertise a stable URL nothing was published to.
192
+ _DB_EXPECTED_URL="https://egregore.xyz/view/${_DB_SLUG}/${_DB_ID}"
193
+ _DB_CONFIRMED_ID=$(jq -r '.dashboard_published_id // empty' "$_DB_STATE" 2>/dev/null)
194
+ if [ "$_DB_CONFIRMED_ID" = "$_DB_ID" ]; then
195
+ # An artifact is confirmed at this exact id — safe to print the URL
196
+ # immediately and refresh its content detached, off the boot path.
197
+ DASHBOARD_URL="$_DB_EXPECTED_URL"
198
+ ( (
199
+ bash "$SCRIPT_DIR/bin/publish-artifact.sh" document "$_DB_MD" \
200
+ --id "$_DB_ID" \
201
+ --title "Session Dashboard" \
202
+ --author "${GREETING_NAME:-$AUTHOR}" >/dev/null 2>&1 || true
203
+ rm -f "$_DB_MD"
204
+ ) >/dev/null 2>&1 & ) 2>/dev/null
205
+ else
206
+ # First publish for this id: synchronous, so the greeting never advertises
207
+ # a URL with nothing behind it (publishing disabled, missing key, renderer
208
+ # failure, API outage). Confirm only when the returned URL is actually the
209
+ # stable URL for THIS id — host-agnostic suffix match, so a relay/ephemeral
210
+ # URL is shown for this boot but never confirms the stable id.
211
+ _DB_PUBLISH_OUT=$(bash "$SCRIPT_DIR/bin/publish-artifact.sh" document "$_DB_MD" \
212
+ --id "$_DB_ID" \
213
+ --title "Session Dashboard" \
214
+ --author "${GREETING_NAME:-$AUTHOR}" 2>/dev/null || true)
215
+ DASHBOARD_URL=$(echo "$_DB_PUBLISH_OUT" | grep -o 'https://[^ ]*' | tail -1)
216
+ case "$DASHBOARD_URL" in
217
+ */"${_DB_SLUG}/${_DB_ID}")
218
+ jq --arg id "$_DB_ID" '.dashboard_published_id = $id | del(.dashboard_published)' "$_DB_STATE" > "$_DB_STATE.tmp.$$" 2>/dev/null \
219
+ && mv "$_DB_STATE.tmp.$$" "$_DB_STATE" 2>/dev/null || rm -f "$_DB_STATE.tmp.$$" 2>/dev/null
220
+ ;;
221
+ esac
222
+ rm -f "$_DB_MD"
223
+ fi
224
+ else
225
+ # Local/OSS mode: no stable hosted URL to print early — keep the synchronous
226
+ # publish (ephemeral relay URL) with the local file:// render as fallback.
227
+ _DB_PUBLISH_OUT=$(bash "$SCRIPT_DIR/bin/publish-artifact.sh" document "$_DB_MD" \
228
+ --title "Session Dashboard" \
229
+ --author "${GREETING_NAME:-$AUTHOR}" 2>/dev/null || true)
230
+ DASHBOARD_URL=$(echo "$_DB_PUBLISH_OUT" | grep -o 'https://[^ ]*' | tail -1)
231
+
232
+ # Fallback: generate HTML locally and use file:// URL (offline, no API key, etc.)
233
+ if [ -z "$DASHBOARD_URL" ]; then
234
+ _DB_HTML="/tmp/egregore-artifacts/dashboard-${EGREGORE_SESSION_ID}.html"
235
+ mkdir -p /tmp/egregore-artifacts
236
+ if npx egregore-artifacts@latest document "$_DB_MD" --output "$_DB_HTML" --no-open >/dev/null 2>&1 && [ -f "$_DB_HTML" ]; then
237
+ DASHBOARD_URL="file://${_DB_HTML}"
238
+ fi
239
+ fi
141
240
 
142
- rm -f "$_DB_MD"
241
+ rm -f "$_DB_MD"
242
+ fi
@@ -47,10 +47,26 @@ export GIT_HTTP_LOW_SPEED_TIME=10 # for 10 seconds
47
47
  # attendant and stays unconditional below.
48
48
  _REFS_FRESH="false"
49
49
  _ATT_SLUG=$(jq -r '.slug // "egregore"' "$SCRIPT_DIR/egregore.json" 2>/dev/null || echo "egregore")
50
- _ATT_MARKER="$HOME/.egregore/attendant/${_ATT_SLUG}-$(echo -n "$SCRIPT_DIR" | cksum | cut -d' ' -f1).warm-ts"
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"
51
58
  if [ -f "$_ATT_MARKER" ]; then
52
- _ATT_AGE=$(( $(date +%s) - $(cat "$_ATT_MARKER" 2>/dev/null || echo 0) ))
53
- [ "$_ATT_AGE" -lt 360 ] && _REFS_FRESH="true"
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"
54
70
  fi
55
71
 
56
72
  [ "$_REFS_FRESH" != "true" ] && git fetch origin --quiet 2>/dev/null &
@@ -145,6 +161,16 @@ _apply_framework_update() {
145
161
  if ! git show-ref --verify --quiet refs/remotes/upstream/main 2>/dev/null; then
146
162
  return 0
147
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
148
174
  # Apply upstream changes — checkout is idempotent, skip diff check
149
175
  # (git diff with variable path lists breaks in zsh — no word-splitting)
150
176
  for _p in bin/ .claude/commands/ .claude/skills/ .claude/hooks/ .claude/context/ .claude/agents/ loom/ CLAUDE.md skills/; do
@@ -202,9 +228,11 @@ setup_develop() {
202
228
  # Sync the base branch (without checkout — safe for concurrent sessions)
203
229
  CURRENT_BRANCH=$(git branch --show-current 2>/dev/null) || return 1
204
230
 
205
- # Update the local ref from remote without switching branches
206
- # Use fetch + force-update to handle divergence (e.g., from auto-update commits)
207
- git fetch origin "$BASE_BRANCH" --quiet 2>/dev/null || true
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.
208
236
  if [[ "$CURRENT_BRANCH" != "$BASE_BRANCH" ]]; then
209
237
  # Safe to force-update when not checked out
210
238
  git branch -f "$BASE_BRANCH" "origin/$BASE_BRANCH" 2>/dev/null || true
@@ -295,8 +323,16 @@ if [ "$IS_WORKTREE" = "true" ]; then
295
323
  # branch whose commits already landed.
296
324
  WORKTREE_STALE="false"
297
325
  if [ "$BRANCH" != "?" ] && [ "$BRANCH" != "$BASE_BRANCH" ] && [ "$BRANCH" != "main" ]; then
298
- # Check if remote branch still exists
299
- if ! git ls-remote --heads origin "$BRANCH" 2>/dev/null | grep -q "$BRANCH"; 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
300
336
  # Remote branch gone — check if it was merged into the base branch
301
337
  if git branch -r --merged "origin/$BASE_BRANCH" 2>/dev/null | grep -q "origin/$BRANCH" 2>/dev/null || \
302
338
  git merge-base --is-ancestor HEAD "origin/$BASE_BRANCH" 2>/dev/null; then
@@ -219,7 +219,10 @@ else
219
219
  fi
220
220
 
221
221
  if [ -n "${LOOM_DOCTOR_BRIEF:-}" ]; then
222
- printf " %s\n" "$LOOM_DOCTOR_BRIEF"
222
+ # Indent every line — printf ' %s' indents only the first line of a
223
+ # multiline brief, which misaligns the card and hides follow-up warnings
224
+ # from the fast-path signal-line scraper (it keys on "^ ").
225
+ printf '%s\n' "$LOOM_DOCTOR_BRIEF" | sed 's/^/ /'
223
226
  fi
224
227
 
225
228
  # --- Creator review gate: pending turns on owned scrolls/surfaces ---
@@ -361,10 +364,18 @@ echo ""
361
364
 
362
365
  # --- Session context (hidden, for Claude) ---
363
366
  CONTEXT_HANDOFFS=$(cat "$CTX_DIR/handoffs" 2>/dev/null || echo "[]")
367
+ CONTEXT_HANDOFFS=$(printf '%s' "$CONTEXT_HANDOFFS" | jq -c 'map(if .preview then .preview |= .[0:80] else . end)' 2>/dev/null || printf '%s' "$CONTEXT_HANDOFFS")
364
368
  CONTEXT_ADDRESSED=$(cat "$CTX_DIR/addressed" 2>/dev/null || echo "[]")
365
369
  CONTEXT_QUESTS=$(cat "$CTX_DIR/quests" 2>/dev/null || echo "[]")
370
+ CONTEXT_QUESTS=$(printf '%s' "$CONTEXT_QUESTS" | jq -c 'if length > 20 then .[0:20] + ["+\(length - 20) more"] else . end' 2>/dev/null || printf '%s' "$CONTEXT_QUESTS")
366
371
  CONTEXT_ACTIVITY=$(cat "$CTX_DIR/activity" 2>/dev/null || echo "")
367
372
  CONTEXT_TEAM=$(cat "$CTX_DIR/team" 2>/dev/null || echo "[]")
373
+ # Trim the team blob for greeting injection: 8 most-recent people, drop sort
374
+ # keys, one branch each. This trimmed view is all the session ever sees —
375
+ # CTX_DIR is deleted at the end of this script — a deliberate cap to keep
376
+ # hook stdout inline-sized. Untrimmed this is the single largest field in
377
+ # session-context.
378
+ CONTEXT_TEAM=$(printf '%s' "$CONTEXT_TEAM" | jq -c '.[0:8] | map({name, last_seen, working_on: ((.working_on // "")[0:80]), branches: ((.branches // [])[0:1])})' 2>/dev/null || printf '%s' "$CONTEXT_TEAM")
368
379
  CONTEXT_SOUL=$(cat "$CTX_DIR/soul_summary" 2>/dev/null || echo "")
369
380
  CONTEXT_LIFECYCLE=$(cat "$CTX_DIR/lifecycle" 2>/dev/null || echo '{"merged_prs":[],"implemented_handoffs":[]}')
370
381
  CONTEXT_PULSE=$(cat "$CTX_DIR/pulse_brief" 2>/dev/null || echo '{}')
@@ -404,9 +415,10 @@ Memory: memory/ is a symlink to shared knowledge base. Use bin/graph.sh for Neo4
404
415
  SAEOF
405
416
  ) 2>/dev/null || true
406
417
 
407
- cat << CTXEOF
408
-
409
- <!-- session-context
418
+ # Emit the context JSON compacted (jq -c) — nested blobs arrive pretty-printed
419
+ # from CTX_DIR and the whitespace alone is ~1KB of hook stdout. Fall back to
420
+ # the raw heredoc if jq can't parse (a malformed fragment must not eat context).
421
+ _CTX_JSON=$(cat << CTXEOF
410
422
  {
411
423
  "framework_version": "$FRAMEWORK_VERSION",
412
424
  "time_of_day": "$TIME_OF_DAY",
@@ -421,8 +433,10 @@ cat << CTXEOF
421
433
  "momentum": $CONTEXT_METRICS,
422
434
  "pulse": $CONTEXT_PULSE
423
435
  }
424
- -->
425
436
  CTXEOF
437
+ )
438
+ printf '\n<!-- session-context\n%s\n-->\n' \
439
+ "$(printf '%s' "$_CTX_JSON" | jq -c . 2>/dev/null || printf '%s' "$_CTX_JSON")"
426
440
 
427
441
  # Include soul file if present
428
442
  if [ -f "$SCRIPT_DIR/egregore.md" ]; then
@@ -432,13 +446,14 @@ if [ -f "$SCRIPT_DIR/egregore.md" ]; then
432
446
  echo "-->"
433
447
  fi
434
448
 
435
- # Include latest soul reflection if present
449
+ # Point at the latest soul reflection instead of inlining it. The full
450
+ # document is several KB; injected verbatim it pushed hook stdout past the
451
+ # harness inline threshold, which turns the greeting into a file the model
452
+ # must Read back — a whole extra API round-trip on every boot.
436
453
  if [ -d "$SCRIPT_DIR/memory/soul" ]; then
437
454
  LATEST_REFLECTION=$(ls -t "$SCRIPT_DIR/memory/soul/"*.md 2>/dev/null | head -1 || true)
438
455
  if [ -n "$LATEST_REFLECTION" ]; then
439
- echo "<!-- latest-reflection"
440
- cat "$LATEST_REFLECTION"
441
- echo "-->"
456
+ echo "<!-- latest-reflection: memory/soul/$(basename "$LATEST_REFLECTION") — read when org identity, culture, or history context matters -->"
442
457
  fi
443
458
  fi
444
459
 
@@ -480,7 +495,11 @@ if [ "$FIRST_SESSION" = "true" ]; then
480
495
  echo ""
481
496
  echo " Welcome! This is your first session."
482
497
  echo ""
483
- echo "IMPORTANT: Display the above greeting exactly as-is (ASCII art + ornamented status). Then ask the user if they'd like a quick onboarding tour (run /onboarding), or if they want to jump straight in."
498
+ if [ "${EGREGORE_CARD_SHOWN:-}" = "1" ]; then
499
+ echo "IMPORTANT: The greeting card is already on screen (rendered by the launcher) — do NOT re-render it. Reply with the greeting-reply block contents, then ask the user if they'd like a quick onboarding tour (run /onboarding), or if they want to jump straight in."
500
+ else
501
+ echo "IMPORTANT: Display the above greeting exactly as-is (ASCII art + ornamented status). Then ask the user if they'd like a quick onboarding tour (run /onboarding), or if they want to jump straight in."
502
+ fi
484
503
  # Clear the flag so it only shows once
485
504
  jq '.first_session = false' "$STATE_FILE" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
486
505
  else
@@ -495,7 +514,11 @@ else
495
514
  fi
496
515
 
497
516
  echo ""
498
- echo "IMPORTANT: Display the above greeting to the user exactly as-is (preserve the ASCII art formatting and ornamented status) on their first message. Then ask: What are you working on?"
517
+ if [ "${EGREGORE_CARD_SHOWN:-}" = "1" ]; then
518
+ echo "IMPORTANT: The greeting card is already on screen (rendered by the launcher) — do NOT re-render it. Reply with the greeting-reply block contents exactly as-is (it may be empty), then ask: What are you working on?"
519
+ else
520
+ echo "IMPORTANT: Display the above greeting to the user exactly as-is (preserve the ASCII art formatting and ornamented status) on their first message. Then ask: What are you working on?"
521
+ fi
499
522
  echo ""
500
523
  echo "BRANCH RULE: When the user responds with what they're working on, your FIRST action is to create a working branch: git fetch origin develop --quiet && git checkout -b dev/{author}/{topic-slug} origin/develop. Do this BEFORE any other work. Derive the topic slug from their description. If they ask a pure question with no work intent, skip branching."
501
524
  fi
@@ -153,21 +153,39 @@ fi
153
153
  # --- Validate gh CLI identity matches git identity ---
154
154
  # The gh CLI has its own auth (keyring) separate from .env GITHUB_TOKEN.
155
155
  # If they diverge, PRs get created under the wrong account.
156
- if command -v gh &>/dev/null; then
157
- GH_CLI_USER=$(gh api user --jq '.login' 2>/dev/null || true)
158
- if [ -n "$GH_CLI_USER" ] && [ -n "$AUTHOR" ] && [ "$AUTHOR" != "unknown" ]; then
159
- AUTHOR_LC=$(echo "$AUTHOR" | tr '[:upper:]' '[:lower:]')
160
- GH_CLI_LC=$(echo "$GH_CLI_USER" | tr '[:upper:]' '[:lower:]')
161
- if [ "$AUTHOR_LC" != "$GH_CLI_LC" ]; then
162
- echo ""
163
- echo "WARNING: Identity mismatch detected."
164
- echo " Git identity (.env): $AUTHOR"
165
- echo " gh CLI (active): $GH_CLI_USER"
166
- echo " PRs will be created as '$GH_CLI_USER', not '$AUTHOR'."
167
- echo " Fix: gh auth switch --user $AUTHOR"
168
- echo ""
169
- fi
156
+ # The `gh api user` round-trip (~1s) is off the boot path: each start reports
157
+ # the marker the previous background check left, then refreshes it detached.
158
+ # A mismatch therefore surfaces one session late — acceptable for a warning
159
+ # about a rare, persistent condition.
160
+ if command -v gh &>/dev/null && [ -n "$AUTHOR" ] && [ "$AUTHOR" != "unknown" ]; then
161
+ _GH_MISMATCH_FILE="$HOME/.egregore/gh-identity-mismatch-$(echo -n "${MAIN_PROJECT_DIR:-$SCRIPT_DIR}" | cksum | cut -d' ' -f1)"
162
+ # Marker format: line 1 = the AUTHOR the check ran as, line 2 = the gh login
163
+ # observed. Warn only when line 1 matches the current AUTHOR — the same
164
+ # machine/instance can serve different git identities, and Alice's stale
165
+ # marker must not tell Bob that Bob mismatches Bob.
166
+ _GH_MARKER_AUTHOR=$(sed -n '1p' "$_GH_MISMATCH_FILE" 2>/dev/null || true)
167
+ GH_CLI_USER=$(sed -n '2p' "$_GH_MISMATCH_FILE" 2>/dev/null || true)
168
+ if [ -n "$GH_CLI_USER" ] && [ "$_GH_MARKER_AUTHOR" = "$AUTHOR" ]; then
169
+ echo ""
170
+ echo "WARNING: Identity mismatch detected."
171
+ echo " Git identity (.env): $AUTHOR"
172
+ echo " gh CLI (active): $GH_CLI_USER"
173
+ echo " PRs will be created as '$GH_CLI_USER', not '$AUTHOR'."
174
+ echo " Fix: gh auth switch --user $AUTHOR"
175
+ echo ""
170
176
  fi
177
+ ( (
178
+ _GH_LIVE=$(gh api user --jq '.login' 2>/dev/null || true)
179
+ if [ -n "$_GH_LIVE" ]; then
180
+ if [ "$(echo "$_GH_LIVE" | tr '[:upper:]' '[:lower:]')" != "$(echo "$AUTHOR" | tr '[:upper:]' '[:lower:]')" ]; then
181
+ mkdir -p "$HOME/.egregore" 2>/dev/null
182
+ printf '%s\n%s\n' "$AUTHOR" "$_GH_LIVE" > "$_GH_MISMATCH_FILE.tmp" \
183
+ && mv "$_GH_MISMATCH_FILE.tmp" "$_GH_MISMATCH_FILE"
184
+ else
185
+ rm -f "$_GH_MISMATCH_FILE"
186
+ fi
187
+ fi
188
+ ) >/dev/null 2>&1 & ) 2>/dev/null
171
189
  fi
172
190
 
173
191
  # --- Export telemetry identity env vars ---
@@ -394,6 +394,15 @@ def reconcile_profile(args: argparse.Namespace) -> dict[str, Any]:
394
394
  if not github or not re.fullmatch(r"[A-Za-z0-9-]{1,39}", github):
395
395
  raise ValueError("a valid GitHub login is required")
396
396
  github_id = int(args.github_id) if args.github_id not in (None, "") else None
397
+ if github_id is None:
398
+ # Self-heal: adopt the numeric id the canonical profile already
399
+ # carries so a caller without --github-id cannot degrade a
400
+ # github:<id> profile back to github-login form.
401
+ profile_github_id = str(
402
+ parse_profile(PEOPLE_DIR / f"{github}.md")["fields"].get("GitHub-ID") or ""
403
+ ).strip()
404
+ if profile_github_id.isdigit():
405
+ github_id = int(profile_github_id)
397
406
  person_id = (
398
407
  f"github:{github_id}"
399
408
  if github_id is not None
@@ -446,6 +455,17 @@ def reconcile(args: argparse.Namespace) -> dict[str, Any]:
446
455
  raise ValueError("github_username is required")
447
456
  github_id = args.github_id or state.get("github_id")
448
457
  github_id = int(github_id) if github_id not in (None, "") else None
458
+ if github_id is None:
459
+ # Self-heal pre-migration state: a state file written before the
460
+ # numeric-id migration carries github_id null and a github-login
461
+ # person_id, which degraded github:<id> profiles back to login
462
+ # form on every boot. Adopt the numeric id from the canonical
463
+ # profile; state.update() below persists it durably.
464
+ profile_github_id = str(
465
+ parse_profile(PEOPLE_DIR / f"{github}.md")["fields"].get("GitHub-ID") or ""
466
+ ).strip()
467
+ if profile_github_id.isdigit():
468
+ github_id = int(profile_github_id)
449
469
  person_id = (
450
470
  f"github:{github_id}" if github_id is not None
451
471
  else str(state.get("person_id") or f"github-login:{github.lower()}")
@@ -1,67 +1,33 @@
1
1
  {
2
2
  "source": "egregore-runtime",
3
- "policy": "public-safe subset derived from .syncignore; built by scripts/build-runtime-bundle.mjs",
4
- "skillCount": 65,
3
+ "policy": "available OSS skills selected by capability-distribution.json; Connect is an authenticated additive overlay",
4
+ "skillCount": 31,
5
5
  "skills": [
6
6
  "activity",
7
- "add",
8
7
  "announce",
9
- "answer",
10
8
  "archive",
11
- "ask",
12
- "audit",
13
- "autosave",
14
- "branch",
15
9
  "checkup",
16
- "commit",
17
10
  "contribute",
18
11
  "dark-mode",
19
12
  "dashboard",
20
13
  "deep-reflect",
21
14
  "delete-user",
22
- "demo-mode",
23
- "deploy-bridge",
24
- "design",
25
- "egregore-voice",
26
- "emissary",
27
- "env",
28
15
  "handoff",
29
16
  "harvest",
30
17
  "ingest",
31
- "ingest-google",
32
18
  "ingest-user-interview",
33
19
  "invite",
34
20
  "issue",
35
21
  "me",
36
22
  "meeting",
37
- "minion",
38
- "mock",
39
23
  "note",
40
24
  "onboarding",
41
- "pr",
42
- "product-voice",
43
- "project",
44
- "pull",
45
- "push",
46
25
  "quest",
47
- "quest-suggest",
48
26
  "reflect",
49
- "review-pr",
50
27
  "save",
51
- "scroll",
52
- "search",
53
- "session-view",
54
- "setup",
55
- "summon",
56
28
  "sync-repos",
57
- "task",
58
- "teams-connect",
59
- "telegram-connect",
60
29
  "telemetry",
61
30
  "test",
62
- "the-spiral",
63
- "thread",
64
- "todo",
65
31
  "tui-design",
66
32
  "tutorial",
67
33
  "update",
@@ -69,6 +35,7 @@
69
35
  "visual-explain",
70
36
  "wrap"
71
37
  ],
38
+ "availability": "oss",
72
39
  "files": [
73
40
  ".agents/skills",
74
41
  ".claude/context/notification-consent.md",
@@ -77,67 +44,31 @@
77
44
  ".codex/hooks/branch-guard.js",
78
45
  ".codex/hooks/search-hint.js",
79
46
  ".codex/skills/activity/SKILL.md",
80
- ".codex/skills/add/SKILL.md",
81
47
  ".codex/skills/announce/SKILL.md",
82
- ".codex/skills/answer/SKILL.md",
83
48
  ".codex/skills/archive/SKILL.md",
84
- ".codex/skills/ask/SKILL.md",
85
- ".codex/skills/audit/SKILL.md",
86
- ".codex/skills/autosave/SKILL.md",
87
- ".codex/skills/branch/SKILL.md",
88
49
  ".codex/skills/checkup/SKILL.md",
89
- ".codex/skills/commit/SKILL.md",
90
50
  ".codex/skills/contribute/SKILL.md",
91
51
  ".codex/skills/dark-mode/SKILL.md",
92
52
  ".codex/skills/dashboard/SKILL.md",
93
53
  ".codex/skills/deep-reflect/SKILL.md",
94
54
  ".codex/skills/delete-user/SKILL.md",
95
- ".codex/skills/demo-mode/SKILL.md",
96
- ".codex/skills/deploy-bridge/SKILL.md",
97
- ".codex/skills/design/SKILL.md",
98
- ".codex/skills/egregore-voice/SKILL.md",
99
- ".codex/skills/emissary/SKILL.md",
100
- ".codex/skills/env/SKILL.md",
101
55
  ".codex/skills/handoff/SKILL.md",
102
56
  ".codex/skills/harvest/QUESTION_PALETTE.md",
103
57
  ".codex/skills/harvest/SKILL.md",
104
- ".codex/skills/ingest-google/SKILL.md",
105
58
  ".codex/skills/ingest-user-interview/SKILL.md",
106
59
  ".codex/skills/ingest/SKILL.md",
107
60
  ".codex/skills/invite/SKILL.md",
108
61
  ".codex/skills/issue/SKILL.md",
109
62
  ".codex/skills/me/SKILL.md",
110
63
  ".codex/skills/meeting/SKILL.md",
111
- ".codex/skills/minion/SKILL.md",
112
- ".codex/skills/mock/SKILL.md",
113
64
  ".codex/skills/note/SKILL.md",
114
65
  ".codex/skills/onboarding/SKILL.md",
115
- ".codex/skills/pr/SKILL.md",
116
- ".codex/skills/product-voice/SKILL.md",
117
- ".codex/skills/project/SKILL.md",
118
- ".codex/skills/pull/SKILL.md",
119
- ".codex/skills/push/SKILL.md",
120
- ".codex/skills/quest-suggest/SKILL.md",
121
66
  ".codex/skills/quest/SKILL.md",
122
67
  ".codex/skills/reflect/SKILL.md",
123
- ".codex/skills/review-pr/SKILL.md",
124
68
  ".codex/skills/save/SKILL.md",
125
- ".codex/skills/scroll/SKILL.md",
126
- ".codex/skills/scroll/agents/openai.yaml",
127
- ".codex/skills/scroll/assets/shell.html",
128
- ".codex/skills/search/SKILL.md",
129
- ".codex/skills/session-view/SKILL.md",
130
- ".codex/skills/setup/SKILL.md",
131
- ".codex/skills/summon/SKILL.md",
132
69
  ".codex/skills/sync-repos/SKILL.md",
133
- ".codex/skills/task/SKILL.md",
134
- ".codex/skills/teams-connect/SKILL.md",
135
- ".codex/skills/telegram-connect/SKILL.md",
136
70
  ".codex/skills/telemetry/SKILL.md",
137
71
  ".codex/skills/test/SKILL.md",
138
- ".codex/skills/the-spiral/SKILL.md",
139
- ".codex/skills/thread/SKILL.md",
140
- ".codex/skills/todo/SKILL.md",
141
72
  ".codex/skills/tui-design/SKILL.md",
142
73
  ".codex/skills/tutorial/SKILL.md",
143
74
  ".codex/skills/update/SKILL.md",