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,242 @@
1
+ # shellcheck shell=bash
2
+ # dashboard-artifact.sh — Generate a session dashboard artifact with a shareable URL
3
+ #
4
+ # Runs after context.sh (all $CTX_DIR/* files populated), before greeting.sh.
5
+ # Compiles gathered context into a markdown document, generates HTML via
6
+ # egregore-artifacts, publishes it, and exports DASHBOARD_URL for greeting.sh.
7
+ #
8
+ # Inputs: SCRIPT_DIR, CTX_DIR, AUTHOR, GREETING_NAME, LOCAL_MODE,
9
+ # EGREGORE_SESSION_ID, FIRST_SESSION
10
+ # Outputs: DASHBOARD_URL (exported for greeting.sh)
11
+
12
+ DASHBOARD_URL=""
13
+
14
+ # Skip conditions: first session, onboarding incomplete, no session ID
15
+ if [ "${FIRST_SESSION:-false}" = "true" ]; then
16
+ return 0 2>/dev/null || exit 0
17
+ fi
18
+ if [ -z "${EGREGORE_SESSION_ID:-}" ]; then
19
+ return 0 2>/dev/null || exit 0
20
+ fi
21
+
22
+ # --- Read gathered context ---
23
+ _DB_ADDRESSED=$(cat "$CTX_DIR/addressed_rich" 2>/dev/null || echo "[]")
24
+ _DB_ADDR_COUNT=$(echo "$_DB_ADDRESSED" | jq 'length' 2>/dev/null || echo "0")
25
+ _DB_QUESTS=$(cat "$CTX_DIR/quests" 2>/dev/null || echo "[]")
26
+ _DB_QUEST_COUNT=$(echo "$_DB_QUESTS" | jq 'length' 2>/dev/null || echo "0")
27
+ _DB_TEAM=$(cat "$CTX_DIR/team" 2>/dev/null || echo "[]")
28
+ _DB_TEAM_COUNT=$(echo "$_DB_TEAM" | jq 'length' 2>/dev/null || echo "0")
29
+ _DB_LIFECYCLE=$(cat "$CTX_DIR/lifecycle" 2>/dev/null || echo '{}')
30
+ _DB_MERGED_COUNT=$(echo "$_DB_LIFECYCLE" | jq '.merged_prs.values // [] | length' 2>/dev/null || echo "0")
31
+ _DB_IMPL_COUNT=$(echo "$_DB_LIFECYCLE" | jq '.implemented_handoffs.values // [] | length' 2>/dev/null || echo "0")
32
+ _DB_PULSE=$(cat "$CTX_DIR/pulse_brief" 2>/dev/null || echo '{}')
33
+ _DB_PULSE_BRIEF=$(echo "$_DB_PULSE" | jq -r '.brief // empty' 2>/dev/null || true)
34
+
35
+ # Skip if all context is empty — nothing worth showing
36
+ _DB_HAS_CONTENT="false"
37
+ if [ "$_DB_ADDR_COUNT" -gt 0 ] 2>/dev/null; then _DB_HAS_CONTENT="true"; fi
38
+ if [ "$_DB_QUEST_COUNT" -gt 0 ] 2>/dev/null; then _DB_HAS_CONTENT="true"; fi
39
+ if [ "$_DB_TEAM_COUNT" -gt 0 ] 2>/dev/null; then _DB_HAS_CONTENT="true"; fi
40
+ if [ "$_DB_MERGED_COUNT" -gt 0 ] 2>/dev/null; then _DB_HAS_CONTENT="true"; fi
41
+ if [ "$_DB_IMPL_COUNT" -gt 0 ] 2>/dev/null; then _DB_HAS_CONTENT="true"; fi
42
+
43
+ if [ "$_DB_HAS_CONTENT" = "false" ]; then
44
+ return 0 2>/dev/null || exit 0
45
+ fi
46
+
47
+ # --- Compile markdown ---
48
+ _DB_DATE=$(date +%Y-%m-%d)
49
+ _DB_ORG=$(jq -r '.org_name // "Egregore"' "$SCRIPT_DIR/egregore.json" 2>/dev/null || echo "Egregore")
50
+ _DB_MD="/tmp/egregore-dashboard-${EGREGORE_SESSION_ID}.md"
51
+ mkdir -p /tmp
52
+
53
+ {
54
+ printf '# Session Dashboard — %s\n\n' "$_DB_ORG"
55
+
56
+ # --- Pulse Brief (first — sets the tone) ---
57
+ if [ -n "$_DB_PULSE_BRIEF" ]; then
58
+ printf '## Pulse\n\n'
59
+ printf '✦ %s\n\n' "$_DB_PULSE_BRIEF"
60
+ fi
61
+
62
+ # --- Handoffs & Asks (dense format matching terminal TUI) ---
63
+ if [ "$_DB_ADDR_COUNT" -gt 0 ] 2>/dev/null; then
64
+ printf '## Handoffs & Asks\n\n'
65
+ _DB_NOW_SEC=$(date +%s)
66
+ echo "$_DB_ADDRESSED" | jq -r '.[] | "\(.author // "unknown")\t\(.topic // "Untitled")\t\(.date // "--")"' 2>/dev/null | while IFS=$'\t' read -r _HF_AUTHOR _HF_TOPIC _HF_DATE; do
67
+ [ -z "$_HF_AUTHOR" ] && continue
68
+ # Compute relative time
69
+ _HF_DATE_ONLY="${_HF_DATE%%T*}"
70
+ _HF_EPOCH=$(date -j -f "%Y-%m-%d" "$_HF_DATE_ONLY" +%s 2>/dev/null || \
71
+ date -d "$_HF_DATE_ONLY" +%s 2>/dev/null || echo "0")
72
+ _HF_DAYS=$(( (_DB_NOW_SEC - _HF_EPOCH) / 86400 ))
73
+ if [ "$_HF_DAYS" -le 0 ] 2>/dev/null; then _HF_AGO="today"
74
+ elif [ "$_HF_DAYS" -eq 1 ] 2>/dev/null; then _HF_AGO="yesterday"
75
+ else _HF_AGO="${_HF_DAYS}d ago"
76
+ fi
77
+ _HF_NAME_LC=$(printf '%s' "$_HF_AUTHOR" | awk '{print tolower($1)}')
78
+ printf '● %s → you: %s (%s)\n\n' "$_HF_NAME_LC" "$_HF_TOPIC" "$_HF_AGO"
79
+ done
80
+ fi
81
+
82
+ # --- Team Activity (right after handoffs, recent only) ---
83
+ if [ "$_DB_TEAM_COUNT" -gt 0 ] 2>/dev/null; then
84
+ _DB_NOW=$(date +%s)
85
+ _DB_RECENCY=$((7 * 86400))
86
+ _DB_TEAM_FILTERED=$(echo "$_DB_TEAM" | jq --argjson now "$_DB_NOW" --argjson window "$_DB_RECENCY" \
87
+ '[.[] | select(.last_seen_sort > 0 and ($now - .last_seen_sort) < $window)]' 2>/dev/null || echo "[]")
88
+ _DB_TEAM_FCOUNT=$(echo "$_DB_TEAM_FILTERED" | jq 'length' 2>/dev/null || echo "0")
89
+ if [ "$_DB_TEAM_FCOUNT" -gt 0 ] 2>/dev/null; then
90
+ printf '## Team Activity\n\n'
91
+ printf '| Name | Last Seen | Working On |\n'
92
+ printf '|------|-----------|------------|\n'
93
+ echo "$_DB_TEAM_FILTERED" | jq -r '.[] | ((.working_on // "")) as $w | (.branches // []) as $b | "| \(.name) | \(.last_seen) | \(if $w != "" then $w else ($b | join(", ")) end) |"' 2>/dev/null || true
94
+ printf '\n'
95
+ fi
96
+ fi
97
+
98
+ # --- What Changed ---
99
+ if [ "$_DB_MERGED_COUNT" -gt 0 ] 2>/dev/null || [ "$_DB_IMPL_COUNT" -gt 0 ] 2>/dev/null; then
100
+ printf '## What Changed\n\n'
101
+ if [ "$_DB_MERGED_COUNT" -gt 0 ] 2>/dev/null; then
102
+ echo "$_DB_LIFECYCLE" | jq -r '.merged_prs.values[]? | "- ✓ PR #\(.[0]) \(.[2] // .[1] // "")"' 2>/dev/null || true
103
+ fi
104
+ if [ "$_DB_IMPL_COUNT" -gt 0 ] 2>/dev/null; then
105
+ echo "$_DB_LIFECYCLE" | jq -r '.implemented_handoffs.values[]? | "- ✓ \(.[1] // "someone") worked on: \(.[0] // "handoff")"' 2>/dev/null || true
106
+ fi
107
+ printf '\n'
108
+ fi
109
+
110
+ # --- Active Quests (bottom — reference, exclude meta entries) ---
111
+ if [ "$_DB_QUEST_COUNT" -gt 0 ] 2>/dev/null; then
112
+ _DB_QUESTS_FILTERED=$(echo "$_DB_QUESTS" | jq '[.[] | select(. != "_template" and . != "index")][:10]' 2>/dev/null || echo "[]")
113
+ _DB_QF_COUNT=$(echo "$_DB_QUESTS_FILTERED" | jq 'length' 2>/dev/null || echo "0")
114
+ if [ "$_DB_QF_COUNT" -gt 0 ] 2>/dev/null; then
115
+ printf '## Active Quests\n\n'
116
+ echo "$_DB_QUESTS_FILTERED" | jq -r '.[] | "- \(.)"' 2>/dev/null || true
117
+ printf '\n'
118
+ fi
119
+ fi
120
+
121
+ } > "$_DB_MD" 2>/dev/null
122
+
123
+ # --- Publish markdown → HTML → hosted URL ---
124
+ # publish-artifact.sh handles HTML generation internally (npx egregore-artifacts)
125
+ # then uploads. Don't pre-generate HTML — that causes double-wrapping.
126
+ # https:// URLs are universally cmd+clickable across all terminal emulators.
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)
184
+ fi
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
240
+
241
+ rm -f "$_DB_MD"
242
+ fi
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env bash
2
+ # ensure-develop.sh — Ensure a repository has a develop branch.
3
+ #
4
+ # Usage:
5
+ # bash bin/lib/ensure-develop.sh <repo-path>
6
+ # bash bin/lib/ensure-develop.sh <repo-path> --report
7
+ #
8
+ # Behavior:
9
+ # 1. Detects the repo's default branch (main, master, or HEAD)
10
+ # 2. Checks if develop exists locally and/or on origin
11
+ # 3. If missing, creates develop from the default branch and pushes it
12
+ #
13
+ # Flags:
14
+ # --report Print a JSON report of the repo's branching model instead of
15
+ # creating develop. Used by create-egregore to analyze existing repos.
16
+ #
17
+ # Exit codes:
18
+ # 0 — develop exists (already existed or was just created)
19
+ # 1 — could not create develop (no default branch, not a git repo, push failed)
20
+ #
21
+ # Output (on success, without --report):
22
+ # created:<default_branch> — develop was created from <default_branch>
23
+ # exists — develop already existed
24
+ #
25
+ # Output (with --report):
26
+ # JSON object with branching analysis
27
+
28
+ set -o pipefail
29
+
30
+ REPO_PATH="${1:-.}"
31
+ FLAG="${2:-}"
32
+
33
+ if [ ! -d "$REPO_PATH/.git" ] && [ ! -f "$REPO_PATH/.git" ]; then
34
+ echo "error:not_a_git_repo" >&2
35
+ exit 1
36
+ fi
37
+
38
+ # ── Detect default branch ──────────────────────────────────────────
39
+ # Priority: what origin/HEAD points to > main > master > first branch found
40
+ detect_default_branch() {
41
+ local repo="$1"
42
+
43
+ # 1. Check origin/HEAD (set by GitHub on clone)
44
+ local origin_head
45
+ origin_head=$(git -C "$repo" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||')
46
+ if [ -n "$origin_head" ] && git -C "$repo" show-ref --verify --quiet "refs/remotes/origin/$origin_head" 2>/dev/null; then
47
+ echo "$origin_head"
48
+ return 0
49
+ fi
50
+
51
+ # 2. Check common names on remote
52
+ for branch in main master; do
53
+ if git -C "$repo" show-ref --verify --quiet "refs/remotes/origin/$branch" 2>/dev/null; then
54
+ echo "$branch"
55
+ return 0
56
+ fi
57
+ done
58
+
59
+ # 3. Check local branches (repo might not have a remote yet)
60
+ for branch in main master; do
61
+ if git -C "$repo" show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then
62
+ echo "$branch"
63
+ return 0
64
+ fi
65
+ done
66
+
67
+ # 4. Fall back to whatever HEAD points to
68
+ local head_branch
69
+ head_branch=$(git -C "$repo" branch --show-current 2>/dev/null)
70
+ if [ -n "$head_branch" ] && [ "$head_branch" != "develop" ]; then
71
+ echo "$head_branch"
72
+ return 0
73
+ fi
74
+
75
+ return 1
76
+ }
77
+
78
+ # ── Report mode ─────────────────────────────────────────────────────
79
+ if [ "$FLAG" = "--report" ]; then
80
+ default_branch=$(detect_default_branch "$REPO_PATH" 2>/dev/null || echo "")
81
+ has_develop_local="false"
82
+ has_develop_remote="false"
83
+ git -C "$REPO_PATH" show-ref --verify --quiet refs/heads/develop 2>/dev/null && has_develop_local="true"
84
+ git -C "$REPO_PATH" show-ref --verify --quiet refs/remotes/origin/develop 2>/dev/null && has_develop_remote="true"
85
+
86
+ # Count remote branches (excluding HEAD)
87
+ branch_count=$(git -C "$REPO_PATH" branch -r 2>/dev/null | grep -cv 'HEAD' 2>/dev/null || echo "0")
88
+ [ -z "$branch_count" ] && branch_count=0
89
+
90
+ # List remote branch names (excluding origin/HEAD, max 20)
91
+ _branch_list=$(git -C "$REPO_PATH" branch -r --format='%(refname:short)' 2>/dev/null \
92
+ | grep -v 'origin/HEAD' \
93
+ | sed 's|^origin/||' \
94
+ | head -20)
95
+ if [ -n "$_branch_list" ]; then
96
+ branches=$(echo "$_branch_list" | jq -R . | jq -s .)
97
+ else
98
+ branches="[]"
99
+ fi
100
+
101
+ # Detect active contributors from branch names (dev/author/... pattern)
102
+ _contrib_list=$(git -C "$REPO_PATH" branch -r --format='%(refname:short)' 2>/dev/null \
103
+ | grep -E '^origin/dev/' \
104
+ | sed 's|^origin/dev/||; s|/.*||' \
105
+ | sort -u)
106
+ if [ -n "$_contrib_list" ]; then
107
+ contributors=$(echo "$_contrib_list" | jq -R . | jq -s .)
108
+ else
109
+ contributors="[]"
110
+ fi
111
+
112
+ jq -n \
113
+ --arg default_branch "$default_branch" \
114
+ --argjson has_develop_local "$has_develop_local" \
115
+ --argjson has_develop_remote "$has_develop_remote" \
116
+ --argjson branch_count "$branch_count" \
117
+ --argjson branches "$branches" \
118
+ --argjson contributors "$contributors" \
119
+ '{
120
+ default_branch: $default_branch,
121
+ has_develop_local: $has_develop_local,
122
+ has_develop_remote: $has_develop_remote,
123
+ branch_count: $branch_count,
124
+ branches: $branches,
125
+ contributors: $contributors,
126
+ needs_develop: (($has_develop_remote | not) and ($has_develop_local | not))
127
+ }'
128
+ exit 0
129
+ fi
130
+
131
+ # ── Ensure develop exists ──────────────────────────────────────────
132
+
133
+ # Already exists on remote?
134
+ if git -C "$REPO_PATH" show-ref --verify --quiet refs/remotes/origin/develop 2>/dev/null; then
135
+ # Ensure local tracking branch exists too
136
+ if ! git -C "$REPO_PATH" show-ref --verify --quiet refs/heads/develop 2>/dev/null; then
137
+ git -C "$REPO_PATH" branch develop origin/develop --quiet 2>/dev/null || true
138
+ fi
139
+ echo "exists"
140
+ exit 0
141
+ fi
142
+
143
+ # Already exists locally but not on remote? Push it.
144
+ if git -C "$REPO_PATH" show-ref --verify --quiet refs/heads/develop 2>/dev/null; then
145
+ if git -C "$REPO_PATH" push -u origin develop --quiet 2>/dev/null; then
146
+ echo "exists"
147
+ exit 0
148
+ fi
149
+ # Push failed but local exists — still usable
150
+ echo "exists:local_only"
151
+ exit 0
152
+ fi
153
+
154
+ # Need to create develop from default branch
155
+ DEFAULT_BRANCH=$(detect_default_branch "$REPO_PATH")
156
+ if [ -z "$DEFAULT_BRANCH" ]; then
157
+ echo "error:no_default_branch" >&2
158
+ exit 1
159
+ fi
160
+
161
+ # Create develop from the default branch
162
+ # Prefer remote ref if available (more up-to-date after fetch)
163
+ if git -C "$REPO_PATH" show-ref --verify --quiet "refs/remotes/origin/$DEFAULT_BRANCH" 2>/dev/null; then
164
+ git -C "$REPO_PATH" branch develop "origin/$DEFAULT_BRANCH" --quiet 2>/dev/null
165
+ else
166
+ git -C "$REPO_PATH" branch develop "$DEFAULT_BRANCH" --quiet 2>/dev/null
167
+ fi
168
+
169
+ if [ $? -ne 0 ]; then
170
+ echo "error:branch_create_failed" >&2
171
+ exit 1
172
+ fi
173
+
174
+ # Push to remote
175
+ if git -C "$REPO_PATH" push -u origin develop --quiet 2>/dev/null; then
176
+ echo "created:$DEFAULT_BRANCH"
177
+ exit 0
178
+ else
179
+ # Push failed — develop exists locally but not on remote
180
+ # This is still usable; session-start.sh will retry the push
181
+ echo "created:$DEFAULT_BRANCH:local_only"
182
+ exit 0
183
+ fi