create-egregore 0.17.3 → 0.18.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.
- package/assets/egregore-launcher.js +1965 -0
- package/assets/egregore-launcher.sh +385 -0
- package/bin/cli.js +472 -81
- package/lib/api.js +3 -3
- package/lib/auth.js +255 -20
- package/lib/codex-runtime.js +268 -0
- package/lib/commands.js +31 -0
- package/lib/context.js +1 -1
- package/lib/local.js +618 -193
- package/lib/setup.js +837 -85
- package/lib/ui.js +780 -84
- package/package.json +17 -4
- package/runtime/codex/.codex/config.toml +15 -0
- package/runtime/codex/.codex/hooks/branch-guard.js +234 -0
- package/runtime/codex/.codex/hooks.json +17 -0
- package/runtime/codex/.codex/skills/activity/SKILL.md +70 -0
- package/runtime/codex/.codex/skills/add/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/announce/SKILL.md +54 -0
- package/runtime/codex/.codex/skills/answer/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/archive/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/ask/SKILL.md +59 -0
- package/runtime/codex/.codex/skills/branch/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/checkup/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/comment/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/commit/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/contribute/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/dark-mode/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/dashboard/SKILL.md +59 -0
- package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +70 -0
- package/runtime/codex/.codex/skills/delete-user/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/edit/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/edit-process/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/egregore-voice/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/emissary/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/env/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/handoff/SKILL.md +67 -0
- package/runtime/codex/.codex/skills/harvest/SKILL.md +107 -0
- package/runtime/codex/.codex/skills/infra/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/ingest/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/ingest-google/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/ingest-user-interview/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/invite/SKILL.md +40 -0
- package/runtime/codex/.codex/skills/issue/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/me/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/meeting/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/note/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/onboarding/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/pr/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/project/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/pull/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/push/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/quest/SKILL.md +77 -0
- package/runtime/codex/.codex/skills/quest-suggest/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/reflect/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/review-pr/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/save/SKILL.md +62 -0
- package/runtime/codex/.codex/skills/setup/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/summon/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/sync-repos/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/telemetry/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/test/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/the-spiral/SKILL.md +84 -0
- package/runtime/codex/.codex/skills/todo/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/tui-design/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/tutorial/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/update/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/view/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/visual-explain/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/wrap/SKILL.md +55 -0
- package/runtime/codex/.codex/spec-manifest.json +108 -0
- package/runtime/codex/AGENTS.md +328 -0
- package/runtime/codex/bin/activity-data.sh +180 -0
- package/runtime/codex/bin/agent.sh +669 -0
- package/runtime/codex/bin/codex-install-skills.sh +78 -0
- package/runtime/codex/bin/codex-render-spec.mjs +308 -0
- package/runtime/codex/bin/codex-session-start.sh +238 -0
- package/runtime/codex/bin/codex-skill-render.mjs +315 -0
- package/runtime/codex/bin/codex-sync-skills.sh +223 -0
- package/runtime/codex/bin/dashboard-data.sh +178 -0
- package/runtime/codex/bin/graph-batch.sh +60 -0
- package/runtime/codex/bin/graph-op.sh +397 -0
- package/runtime/codex/bin/graph-wal.sh +228 -0
- package/runtime/codex/bin/graph.sh +141 -0
- package/runtime/codex/bin/handoff-pr-backfill.sh +86 -0
- package/runtime/codex/bin/handoff-run.sh +569 -0
- package/runtime/codex/bin/handoff-save-egregore.sh +131 -0
- package/runtime/codex/bin/index-handoff.sh +306 -0
- package/runtime/codex/bin/lib/artifact-id.sh +46 -0
- package/runtime/codex/bin/lib/config.sh +71 -0
- package/runtime/codex/bin/lib/context.sh +511 -0
- package/runtime/codex/bin/lib/dashboard-artifact.sh +142 -0
- package/runtime/codex/bin/lib/edit-loopback.mjs +652 -0
- package/runtime/codex/bin/lib/ensure-develop.sh +183 -0
- package/runtime/codex/bin/lib/git-sync.sh +262 -0
- package/runtime/codex/bin/lib/greeting.sh +432 -0
- package/runtime/codex/bin/lib/hash.sh +13 -0
- package/runtime/codex/bin/lib/identity.sh +164 -0
- package/runtime/codex/bin/lib/permissions.sh +106 -0
- package/runtime/codex/bin/lib/time.sh +56 -0
- package/runtime/codex/bin/lib/worktree-links.sh +54 -0
- package/runtime/codex/bin/notify.sh +207 -0
- package/runtime/codex/bin/publish-artifact.sh +187 -0
- package/runtime/codex/bin/publish-references.sh +144 -0
- package/runtime/codex/bin/repo-state.sh +99 -0
- package/runtime/codex/bin/telemetry.sh +350 -0
- package/runtime/codex/bin/worktree-create.sh +115 -0
- package/runtime/codex/bin/worktree-remove.sh +50 -0
- package/runtime/codex/bin/worktree.sh +199 -0
- package/runtime/codex/manifest.json +162 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# context.sh — Context gathering for session-start.sh
|
|
3
|
+
#
|
|
4
|
+
# Runs 10 parallel background subshells to gather session context:
|
|
5
|
+
# 1. Recent handoffs
|
|
6
|
+
# 2. Active quests
|
|
7
|
+
# 3. User's last git activity
|
|
8
|
+
# 3b. Personal todos from graph
|
|
9
|
+
# 4. Team presence (graph + git branches)
|
|
10
|
+
# 5. Soul self-summary
|
|
11
|
+
# 6. Handoffs addressed to user
|
|
12
|
+
# 7. Graph health check
|
|
13
|
+
# 8. Telegram health check
|
|
14
|
+
# 9. Lifecycle events (merged PRs, implemented handoffs)
|
|
15
|
+
# 10. Last Pulse brief
|
|
16
|
+
#
|
|
17
|
+
# All subshells write to $CTX_DIR (a temp directory).
|
|
18
|
+
# Ends with `wait` + reading health results back.
|
|
19
|
+
#
|
|
20
|
+
# Inputs: SCRIPT_DIR, AUTHOR, LOCAL_MODE, STATE_FILE, CONFIG, ENV_FILE,
|
|
21
|
+
# HEALTH_GRAPH, HEALTH_TELEGRAM
|
|
22
|
+
# Outputs: CTX_DIR (populated), TIME_OF_DAY, HEALTH_GRAPH, HEALTH_TELEGRAM
|
|
23
|
+
|
|
24
|
+
CTX_DIR=$(mktemp -d)
|
|
25
|
+
|
|
26
|
+
# Time of day
|
|
27
|
+
HOUR=$(date +%H)
|
|
28
|
+
if [ "$HOUR" -lt 12 ]; then TIME_OF_DAY="morning"
|
|
29
|
+
elif [ "$HOUR" -lt 17 ]; then TIME_OF_DAY="afternoon"
|
|
30
|
+
else TIME_OF_DAY="evening"
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# 1. Recent handoffs (background)
|
|
34
|
+
(
|
|
35
|
+
JSON="[]"
|
|
36
|
+
if [ -d "$SCRIPT_DIR/memory/handoffs" ]; then
|
|
37
|
+
FILES=$(find -L "$SCRIPT_DIR/memory/handoffs" -name '*.md' -not -name 'index*' -type f 2>/dev/null | xargs ls -t 2>/dev/null | head -3)
|
|
38
|
+
JSON="["
|
|
39
|
+
FIRST=true
|
|
40
|
+
for F in $FILES; do
|
|
41
|
+
[ -z "$F" ] && continue
|
|
42
|
+
NAME=$(basename "$F" .md)
|
|
43
|
+
PREVIEW=$(head -c 120 "$F" 2>/dev/null | tr '\n' ' ' | sed 's/"/\\"/g')
|
|
44
|
+
$FIRST || JSON="$JSON,"
|
|
45
|
+
JSON="$JSON{\"name\":\"$NAME\",\"preview\":\"$PREVIEW\"}"
|
|
46
|
+
FIRST=false
|
|
47
|
+
done
|
|
48
|
+
JSON="$JSON]"
|
|
49
|
+
fi
|
|
50
|
+
echo "$JSON" > "$CTX_DIR/handoffs"
|
|
51
|
+
) &
|
|
52
|
+
|
|
53
|
+
# 2. Quests (background)
|
|
54
|
+
(
|
|
55
|
+
JSON="[]"
|
|
56
|
+
if [ -d "$SCRIPT_DIR/memory/quests" ]; then
|
|
57
|
+
JSON="["
|
|
58
|
+
FIRST=true
|
|
59
|
+
for F in "$SCRIPT_DIR/memory/quests/"*.md; do
|
|
60
|
+
[ -e "$F" ] || continue
|
|
61
|
+
echo "$F" | grep -q draft && continue
|
|
62
|
+
NAME=$(basename "$F" .md)
|
|
63
|
+
$FIRST || JSON="$JSON,"
|
|
64
|
+
JSON="$JSON\"$NAME\""
|
|
65
|
+
FIRST=false
|
|
66
|
+
done
|
|
67
|
+
JSON="$JSON]"
|
|
68
|
+
fi
|
|
69
|
+
echo "$JSON" > "$CTX_DIR/quests"
|
|
70
|
+
) &
|
|
71
|
+
|
|
72
|
+
# 3. User's last activity (background)
|
|
73
|
+
(
|
|
74
|
+
git log --author="$AUTHOR" --format="%ar|%s" -1 2>/dev/null > "$CTX_DIR/activity" || echo "" > "$CTX_DIR/activity"
|
|
75
|
+
) &
|
|
76
|
+
|
|
77
|
+
# 3b. Personal todos from graph (background)
|
|
78
|
+
(
|
|
79
|
+
_API_URL=$(jq -r '.api_url // empty' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
|
|
80
|
+
_API_KEY=$(grep '^EGREGORE_API_KEY=' "${ENV_FILE:-$SCRIPT_DIR/.env}" 2>/dev/null | cut -d'=' -f2-)
|
|
81
|
+
if [ -n "$_API_URL" ] && [ -n "$_API_KEY" ]; then
|
|
82
|
+
TODO_RAW=$(bash "$SCRIPT_DIR/bin/graph.sh" query \
|
|
83
|
+
"MATCH (t:Todo)-[:BY]->(p:Person {github: \$me}) WHERE t.status IN ['open', 'blocked'] OPTIONAL MATCH (t)-[:PART_OF]->(q:Quest) RETURN t.text AS text, t.priority AS priority, t.status AS status, t.created AS created, q.id AS quest ORDER BY t.priority DESC, t.created DESC LIMIT 5" \
|
|
84
|
+
"$(jq -n --arg me "$AUTHOR" '{me: $me}')" 2>/dev/null || echo "")
|
|
85
|
+
if [ -n "$TODO_RAW" ]; then
|
|
86
|
+
echo "$TODO_RAW" | jq '[.values[] | {text: .[0], priority: (.[1] // 0), status: .[2], created: .[3], quest: (.[4] // "")}]' 2>/dev/null || echo "[]"
|
|
87
|
+
else
|
|
88
|
+
echo "[]"
|
|
89
|
+
fi
|
|
90
|
+
else
|
|
91
|
+
echo "[]"
|
|
92
|
+
fi
|
|
93
|
+
) > "$CTX_DIR/todos" 2>/dev/null &
|
|
94
|
+
|
|
95
|
+
# 4. Team presence — last seen + active branches (background)
|
|
96
|
+
(
|
|
97
|
+
# --- Read config inside subshell ---
|
|
98
|
+
_API_URL=$(jq -r '.api_url // empty' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
|
|
99
|
+
_API_KEY=$(grep '^EGREGORE_API_KEY=' "${ENV_FILE:-$SCRIPT_DIR/.env}" 2>/dev/null | cut -d'=' -f2-)
|
|
100
|
+
|
|
101
|
+
# --- Graph: last-seen per person (excluding self) ---
|
|
102
|
+
GRAPH_DATA="[]"
|
|
103
|
+
if [ -n "$_API_URL" ] && [ -n "$_API_KEY" ]; then
|
|
104
|
+
CYPHER="MATCH (s:Session)-[:BY]->(p:Person) WHERE p.github <> \$me RETURN p.name AS name, toString(max(coalesce(s.startedAt, datetime(s.date)))) AS lastSeen ORDER BY lastSeen DESC"
|
|
105
|
+
GRAPH_RAW=$(bash "$SCRIPT_DIR/bin/graph.sh" query "$CYPHER" "$(jq -n --arg me "$AUTHOR" '{me: $me}')" 2>/dev/null || echo "")
|
|
106
|
+
if [ -n "$GRAPH_RAW" ]; then
|
|
107
|
+
GRAPH_DATA=$(echo "$GRAPH_RAW" | jq '[.values[] | {name: .[0], lastSeen: .[1]}]' 2>/dev/null || echo "[]")
|
|
108
|
+
fi
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# --- Git: branch tip commit dates per person (primary recency signal) ---
|
|
112
|
+
# git for-each-ref is fast and gives the actual last commit date per branch
|
|
113
|
+
GIT_COMMIT_DATA="{}"
|
|
114
|
+
GIT_REF_DATA=$(git -C "$SCRIPT_DIR" for-each-ref --sort=-committerdate \
|
|
115
|
+
--format='%(refname:short)|%(committerdate:unix)' refs/remotes/origin/dev/ 2>/dev/null || echo "")
|
|
116
|
+
if [ -n "$GIT_REF_DATA" ]; then
|
|
117
|
+
GIT_COMMIT_DATA=$(echo "$GIT_REF_DATA" | sed 's|^origin/dev/||' | while IFS='|' read -r REF_PATH REF_EPOCH || [ -n "$REF_PATH" ]; do
|
|
118
|
+
[ -z "$REF_PATH" ] && continue
|
|
119
|
+
REF_AUTHOR=$(echo "$REF_PATH" | cut -d'/' -f1 | tr '[:upper:]' '[:lower:]')
|
|
120
|
+
echo "${REF_AUTHOR}|${REF_EPOCH}"
|
|
121
|
+
done | sort -t'|' -k1,1 -k2,2rn | sort -t'|' -k1,1 -u | jq -Rn '
|
|
122
|
+
[inputs | split("|") | {(.[0]): .[1]}]
|
|
123
|
+
| add // {}
|
|
124
|
+
' 2>/dev/null || echo "{}")
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# --- Git: active dev/* branches (excluding self) ---
|
|
128
|
+
SELF_LC=$(echo "$AUTHOR" | tr '[:upper:]' '[:lower:]')
|
|
129
|
+
SELF_DISPLAY_LC=""
|
|
130
|
+
if [ -f "$SCRIPT_DIR/.egregore-state.json" ]; then
|
|
131
|
+
SELF_DISPLAY_LC=$(jq -r '.display_name // empty' "$SCRIPT_DIR/.egregore-state.json" 2>/dev/null | tr '[:upper:]' '[:lower:]')
|
|
132
|
+
fi
|
|
133
|
+
BRANCH_DATA=$(git -C "$SCRIPT_DIR" branch -r --format='%(refname:short)' 2>/dev/null | grep "^origin/dev/" | sed 's|^origin/dev/||' || echo "")
|
|
134
|
+
|
|
135
|
+
BRANCH_MAP="{}"
|
|
136
|
+
if [ -n "$BRANCH_DATA" ]; then
|
|
137
|
+
BRANCH_MAP=$(echo "$BRANCH_DATA" | while IFS='/' read -r B_AUTHOR B_SLUG_REST || [ -n "$B_AUTHOR" ]; do
|
|
138
|
+
[ -z "$B_AUTHOR" ] && continue
|
|
139
|
+
B_AUTHOR_LC=$(echo "$B_AUTHOR" | tr '[:upper:]' '[:lower:]')
|
|
140
|
+
[ "$B_AUTHOR_LC" = "$SELF_LC" ] && continue
|
|
141
|
+
[ -n "$SELF_DISPLAY_LC" ] && [ "$B_AUTHOR_LC" = "$SELF_DISPLAY_LC" ] && continue
|
|
142
|
+
B_HUMAN=$(echo "$B_SLUG_REST" | tr '-' ' ')
|
|
143
|
+
echo "${B_AUTHOR_LC}|${B_HUMAN}"
|
|
144
|
+
done | jq -Rn '
|
|
145
|
+
[inputs | split("|") | {author: .[0], branch: .[1]}]
|
|
146
|
+
| group_by(.author)
|
|
147
|
+
| map({(.[0].author): [.[] | .branch]})
|
|
148
|
+
| add // {}
|
|
149
|
+
' 2>/dev/null || echo "{}")
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# --- Local-mode presence: scan session + wrap files for lastSeen ---
|
|
153
|
+
# When graph has no data, these files provide temporal presence information
|
|
154
|
+
FILE_PRESENCE="{}"
|
|
155
|
+
if [ "$GRAPH_DATA" = "[]" ]; then
|
|
156
|
+
FILE_PRESENCE=$(
|
|
157
|
+
{
|
|
158
|
+
# Scan session logs, wraps, and handoffs (all use **Author**: / **Date**: format)
|
|
159
|
+
for DIR in "$SCRIPT_DIR/memory/sessions" "$SCRIPT_DIR/memory/wraps" "$SCRIPT_DIR/memory/handoffs"; do
|
|
160
|
+
[ -d "$DIR" ] || continue
|
|
161
|
+
find -L "$DIR" -name '*.md' -type f 2>/dev/null | xargs ls -t 2>/dev/null | head -20 | while read -r SFILE; do
|
|
162
|
+
[ -z "$SFILE" ] && continue
|
|
163
|
+
S_AUTH=$(grep -m1 '^\*\*Author\*\*:' "$SFILE" 2>/dev/null | sed 's/^\*\*Author\*\*:[[:space:]]*//')
|
|
164
|
+
S_DATE=$(grep -m1 '^\*\*Date\*\*:' "$SFILE" 2>/dev/null | sed 's/^\*\*Date\*\*:[[:space:]]*//')
|
|
165
|
+
[ -z "$S_AUTH" ] && continue
|
|
166
|
+
echo "${S_AUTH}|${S_DATE}"
|
|
167
|
+
done
|
|
168
|
+
done
|
|
169
|
+
} | sort -t'|' -k1,1 -k2,2r | sort -t'|' -k1,1 -u | while IFS='|' read -r FP_NAME FP_DATE; do
|
|
170
|
+
[ -z "$FP_NAME" ] && continue
|
|
171
|
+
FP_LC=$(echo "$FP_NAME" | tr '[:upper:]' '[:lower:]')
|
|
172
|
+
echo "${FP_LC}|${FP_DATE}"
|
|
173
|
+
done | jq -Rn '
|
|
174
|
+
[inputs | split("|") | {(.[0]): .[1]}]
|
|
175
|
+
| add // {}
|
|
176
|
+
' 2>/dev/null || echo "{}"
|
|
177
|
+
)
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# --- Relative time + epoch helpers (cross-platform) ---
|
|
181
|
+
NOW_EPOCH=$(date +%s)
|
|
182
|
+
|
|
183
|
+
iso_to_epoch() {
|
|
184
|
+
local iso="$1"
|
|
185
|
+
local clean=$(echo "$iso" | sed 's/Z$//; s/+00:00$//; s/\.[0-9]*//')
|
|
186
|
+
[[ "$clean" != *T* ]] && clean="${clean}T00:00:00"
|
|
187
|
+
TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "$clean" "+%s" 2>/dev/null || \
|
|
188
|
+
TZ=UTC date -d "$clean" +%s 2>/dev/null || echo "0"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
epoch_to_relative() {
|
|
192
|
+
local epoch="$1"
|
|
193
|
+
[ "$epoch" = "0" ] && echo "--" && return
|
|
194
|
+
local delta=$(( NOW_EPOCH - epoch ))
|
|
195
|
+
if [ "$delta" -lt 60 ]; then echo "just now"
|
|
196
|
+
elif [ "$delta" -lt 3600 ]; then echo "$(( delta / 60 ))m ago"
|
|
197
|
+
elif [ "$delta" -lt 86400 ]; then echo "$(( delta / 3600 ))h ago"
|
|
198
|
+
else
|
|
199
|
+
# Calendar-day based comparison for accurate "yesterday" / "Xd ago"
|
|
200
|
+
local event_date today_date
|
|
201
|
+
event_date=$(date -r "$epoch" +%Y-%m-%d 2>/dev/null || \
|
|
202
|
+
date -d "@$epoch" +%Y-%m-%d 2>/dev/null || echo "")
|
|
203
|
+
today_date=$(date +%Y-%m-%d)
|
|
204
|
+
if [ -z "$event_date" ] || [ "$event_date" = "$today_date" ]; then echo "today"
|
|
205
|
+
else
|
|
206
|
+
local today_e event_e
|
|
207
|
+
today_e=$(date -j -f "%Y-%m-%d" "$today_date" +%s 2>/dev/null || \
|
|
208
|
+
date -d "$today_date" +%s 2>/dev/null || echo "$NOW_EPOCH")
|
|
209
|
+
event_e=$(date -j -f "%Y-%m-%d" "$event_date" +%s 2>/dev/null || \
|
|
210
|
+
date -d "$event_date" +%s 2>/dev/null || echo "$epoch")
|
|
211
|
+
local days=$(( (today_e - event_e) / 86400 ))
|
|
212
|
+
[ "$days" -lt 1 ] && days=1
|
|
213
|
+
if [ "$days" -eq 1 ]; then echo "yesterday"
|
|
214
|
+
else echo "${days}d ago"
|
|
215
|
+
fi
|
|
216
|
+
fi
|
|
217
|
+
fi
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# --- Merge git commits + graph + file presence + branches into presence array ---
|
|
221
|
+
ALL_NAMES=$(echo "$GRAPH_DATA" | jq -r '.[].name' 2>/dev/null || echo "")
|
|
222
|
+
FILE_NAMES=$(echo "$FILE_PRESENCE" | jq -r 'keys[]' 2>/dev/null || echo "")
|
|
223
|
+
BRANCH_NAMES=$(echo "$BRANCH_MAP" | jq -r 'keys[]' 2>/dev/null || echo "")
|
|
224
|
+
GIT_NAMES=$(echo "$GIT_COMMIT_DATA" | jq -r 'keys[]' 2>/dev/null || echo "")
|
|
225
|
+
|
|
226
|
+
UNION_NAMES=$(printf "%s\n%s\n%s\n%s" "$ALL_NAMES" "$FILE_NAMES" "$BRANCH_NAMES" "$GIT_NAMES" | tr '[:upper:]' '[:lower:]' | sort -u | grep -v '^$' | grep -v "^${SELF_LC}$" | grep -v "^${SELF_DISPLAY_LC}$" || echo "")
|
|
227
|
+
|
|
228
|
+
if [ -z "$UNION_NAMES" ]; then
|
|
229
|
+
echo "[]" > "$CTX_DIR/team"
|
|
230
|
+
exit 0
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
# --- Build presence JSON ---
|
|
234
|
+
PRESENCE="["
|
|
235
|
+
FIRST=true
|
|
236
|
+
for PNAME in $UNION_NAMES; do
|
|
237
|
+
# Git commit date is authoritative (actual work shipped).
|
|
238
|
+
# Graph/file presence are fallbacks only (session opens ≠ real activity).
|
|
239
|
+
GIT_EPOCH=$(echo "$GIT_COMMIT_DATA" | jq -r --arg n "$PNAME" '.[$n] // empty' 2>/dev/null || true)
|
|
240
|
+
|
|
241
|
+
LAST_SEEN_EPOCH="0"
|
|
242
|
+
if [ -n "$GIT_EPOCH" ] && [ "$GIT_EPOCH" -gt 0 ] 2>/dev/null; then
|
|
243
|
+
LAST_SEEN_EPOCH="$GIT_EPOCH"
|
|
244
|
+
else
|
|
245
|
+
# Fallback: graph session startedAt
|
|
246
|
+
GRAPH_ISO=$(echo "$GRAPH_DATA" | jq -r --arg n "$PNAME" '.[] | select((.name | ascii_downcase) == $n) | .lastSeen // empty' 2>/dev/null | head -1)
|
|
247
|
+
if [ -n "$GRAPH_ISO" ]; then
|
|
248
|
+
LAST_SEEN_EPOCH=$(iso_to_epoch "$GRAPH_ISO")
|
|
249
|
+
fi
|
|
250
|
+
# Fallback: file-based presence (local mode)
|
|
251
|
+
if [ "$LAST_SEEN_EPOCH" -le 0 ] 2>/dev/null; then
|
|
252
|
+
FILE_ISO=$(echo "$FILE_PRESENCE" | jq -r --arg n "$PNAME" '.[$n] // empty' 2>/dev/null || true)
|
|
253
|
+
if [ -n "$FILE_ISO" ]; then
|
|
254
|
+
LAST_SEEN_EPOCH=$(iso_to_epoch "$FILE_ISO")
|
|
255
|
+
fi
|
|
256
|
+
fi
|
|
257
|
+
fi
|
|
258
|
+
|
|
259
|
+
if [ "$LAST_SEEN_EPOCH" -gt 0 ] 2>/dev/null; then
|
|
260
|
+
LAST_SEEN_REL=$(epoch_to_relative "$LAST_SEEN_EPOCH")
|
|
261
|
+
else
|
|
262
|
+
LAST_SEEN_EPOCH="0"
|
|
263
|
+
LAST_SEEN_REL="--"
|
|
264
|
+
fi
|
|
265
|
+
|
|
266
|
+
BRANCHES_JSON=$(echo "$BRANCH_MAP" | jq --arg n "$PNAME" '.[$n] // []' 2>/dev/null || echo "[]")
|
|
267
|
+
BR_COUNT=$(echo "$BRANCHES_JSON" | jq 'length' 2>/dev/null || echo "0")
|
|
268
|
+
if [ "$BR_COUNT" -gt 2 ]; then
|
|
269
|
+
EXTRA=$((BR_COUNT - 2))
|
|
270
|
+
BRANCHES_JSON=$(echo "$BRANCHES_JSON" | jq --arg e "+${EXTRA} more" '[.[0:2][], $e]' 2>/dev/null || echo "$BRANCHES_JSON")
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
ENTRY=$(jq -n --arg name "$PNAME" --arg seen "$LAST_SEEN_REL" --argjson sort "$LAST_SEEN_EPOCH" --argjson branches "$BRANCHES_JSON" \
|
|
274
|
+
'{name: $name, last_seen: $seen, last_seen_sort: $sort, branches: $branches}')
|
|
275
|
+
$FIRST || PRESENCE="$PRESENCE,"
|
|
276
|
+
PRESENCE="$PRESENCE$ENTRY"
|
|
277
|
+
FIRST=false
|
|
278
|
+
done
|
|
279
|
+
PRESENCE="$PRESENCE]"
|
|
280
|
+
|
|
281
|
+
PRESENCE=$(echo "$PRESENCE" | jq 'sort_by(-.last_seen_sort)' 2>/dev/null || echo "$PRESENCE")
|
|
282
|
+
echo "$PRESENCE" > "$CTX_DIR/team"
|
|
283
|
+
) &
|
|
284
|
+
|
|
285
|
+
# 5. Soul self-summary (background)
|
|
286
|
+
(
|
|
287
|
+
SUMMARY=""
|
|
288
|
+
if [ -f "$SCRIPT_DIR/egregore.md" ]; then
|
|
289
|
+
SUMMARY=$(sed -n '/^## Self-Summary/,/^##/p' "$SCRIPT_DIR/egregore.md" | sed '1d;/^##/d' | sed 's/^[[:space:]]*//' | tr '\n' ' ' | sed 's/[[:space:]]*$//')
|
|
290
|
+
fi
|
|
291
|
+
echo "$SUMMARY" > "$CTX_DIR/soul_summary"
|
|
292
|
+
) &
|
|
293
|
+
|
|
294
|
+
# 6. Handoffs addressed to user (background, enriched with author/topic)
|
|
295
|
+
(
|
|
296
|
+
JSON="[]"
|
|
297
|
+
RICH="[]"
|
|
298
|
+
if [ -d "$SCRIPT_DIR/memory/handoffs" ]; then
|
|
299
|
+
# Match github username, display name, github_name first word, and people-file name
|
|
300
|
+
# Handoff files may use "to: name", "**To**: name", or "To: name"
|
|
301
|
+
_DISPLAY=$(jq -r '.display_name // empty' "$STATE_FILE" 2>/dev/null)
|
|
302
|
+
_GH_NAME=$(jq -r '.github_name // empty' "$STATE_FILE" 2>/dev/null)
|
|
303
|
+
_FIRST_NAME=$(echo "$_GH_NAME" | awk '{print $1}')
|
|
304
|
+
# Also check people file: memory/people/{author}.md has "# Name" on first line
|
|
305
|
+
_PEOPLE_NAME=""
|
|
306
|
+
_PEOPLE_FILE="$SCRIPT_DIR/memory/people/$(echo "$AUTHOR" | tr '[:upper:]' '[:lower:]').md"
|
|
307
|
+
[ -f "$_PEOPLE_FILE" ] && _PEOPLE_NAME=$(sed -n 's/^# *//p' "$_PEOPLE_FILE" 2>/dev/null | head -1)
|
|
308
|
+
_GREP_PAT="[Tt]o[*]*: *$AUTHOR\|[Tt]o[*]*:$AUTHOR"
|
|
309
|
+
for _VARIANT in "$_DISPLAY" "$_FIRST_NAME" "$_PEOPLE_NAME"; do
|
|
310
|
+
[ -z "$_VARIANT" ] && continue
|
|
311
|
+
[ "$_VARIANT" = "$AUTHOR" ] && continue
|
|
312
|
+
# Skip if already in pattern (avoid duplicates)
|
|
313
|
+
echo "$_GREP_PAT" | grep -qF "$_VARIANT" && continue
|
|
314
|
+
_GREP_PAT="$_GREP_PAT\|[Tt]o[*]*: *$_VARIANT\|[Tt]o[*]*:$_VARIANT"
|
|
315
|
+
done
|
|
316
|
+
ADDRESSED=$(grep -rli "$_GREP_PAT" "$SCRIPT_DIR/memory/handoffs/" 2>/dev/null | sort -r | head -5 || true)
|
|
317
|
+
JSON="["
|
|
318
|
+
RICH="["
|
|
319
|
+
FIRST=true
|
|
320
|
+
for AF in $ADDRESSED; do
|
|
321
|
+
[ -z "$AF" ] && continue
|
|
322
|
+
AF_NAME=$(basename "$AF" .md)
|
|
323
|
+
AF_AUTHOR=$(sed -n 's/^\*\*Author\*\*: *//p' "$AF" 2>/dev/null | head -1)
|
|
324
|
+
if [ -z "$AF_AUTHOR" ]; then
|
|
325
|
+
AF_AUTHOR=$(sed -n 's/^From: *//p' "$AF" 2>/dev/null | head -1)
|
|
326
|
+
fi
|
|
327
|
+
AF_TOPIC=$(sed -n 's/^# Handoff: *//p' "$AF" 2>/dev/null | head -1)
|
|
328
|
+
if [ -z "$AF_TOPIC" ]; then
|
|
329
|
+
AF_TOPIC=$(sed -n 's/^# *//p' "$AF" 2>/dev/null | head -1)
|
|
330
|
+
fi
|
|
331
|
+
AF_DATE=$(sed -n 's/^\*\*Date\*\*: *//p' "$AF" 2>/dev/null | head -1)
|
|
332
|
+
if [ -z "$AF_DATE" ]; then
|
|
333
|
+
AF_DATE=$(sed -n 's/^Date: *//p' "$AF" 2>/dev/null | head -1)
|
|
334
|
+
fi
|
|
335
|
+
# Extract repo state table if present
|
|
336
|
+
AF_REPO_STATE="[]"
|
|
337
|
+
AF_REPO_TABLE=$(awk '/^## Repo State/{found=1; next} found && /^#/{exit} found && /^\|[^-]/ && !/^\| Repo/{print}' "$AF" 2>/dev/null || true)
|
|
338
|
+
if [ -n "$AF_REPO_TABLE" ]; then
|
|
339
|
+
AF_REPO_STATE=$(echo "$AF_REPO_TABLE" | while IFS='|' read -r _ R_REPO R_BRANCH R_PR R_BASE _; do
|
|
340
|
+
R_REPO=$(echo "$R_REPO" | xargs 2>/dev/null || true)
|
|
341
|
+
R_BRANCH=$(echo "$R_BRANCH" | xargs 2>/dev/null || true)
|
|
342
|
+
R_PR=$(echo "$R_PR" | xargs 2>/dev/null | sed 's/^#//' | sed 's/—//' || true)
|
|
343
|
+
R_BASE=$(echo "$R_BASE" | xargs 2>/dev/null || true)
|
|
344
|
+
[ -z "$R_REPO" ] && continue
|
|
345
|
+
if [ -n "$R_PR" ] && [ "$R_PR" -eq "$R_PR" ] 2>/dev/null; then
|
|
346
|
+
printf '{"repo":"%s","branch":"%s","pr":%s,"base":"%s"}\n' "$R_REPO" "$R_BRANCH" "$R_PR" "$R_BASE"
|
|
347
|
+
else
|
|
348
|
+
printf '{"repo":"%s","branch":"%s","pr":null,"base":"%s"}\n' "$R_REPO" "$R_BRANCH" "$R_BASE"
|
|
349
|
+
fi
|
|
350
|
+
done | jq -sc '.' 2>/dev/null || echo "[]")
|
|
351
|
+
fi
|
|
352
|
+
$FIRST || JSON="$JSON,"
|
|
353
|
+
$FIRST || RICH="$RICH,"
|
|
354
|
+
JSON="$JSON\"$AF_NAME\""
|
|
355
|
+
AF_TOPIC_ESC=$(echo "$AF_TOPIC" | sed 's/"/\\"/g')
|
|
356
|
+
AF_AUTHOR_ESC=$(echo "$AF_AUTHOR" | sed 's/"/\\"/g')
|
|
357
|
+
RICH="$RICH{\"name\":\"$AF_NAME\",\"author\":\"$AF_AUTHOR_ESC\",\"topic\":\"$AF_TOPIC_ESC\",\"date\":\"$AF_DATE\",\"repoState\":$AF_REPO_STATE}"
|
|
358
|
+
FIRST=false
|
|
359
|
+
done
|
|
360
|
+
JSON="$JSON]"
|
|
361
|
+
RICH="$RICH]"
|
|
362
|
+
fi
|
|
363
|
+
echo "$JSON" > "$CTX_DIR/addressed"
|
|
364
|
+
echo "$RICH" > "$CTX_DIR/addressed_rich"
|
|
365
|
+
) &
|
|
366
|
+
|
|
367
|
+
# 6.5. Pending questions addressed to user (background)
|
|
368
|
+
(
|
|
369
|
+
PENDING_JSON="[]"
|
|
370
|
+
QDIR="$SCRIPT_DIR/memory/knowledge/questions"
|
|
371
|
+
if [ -d "$QDIR" ]; then
|
|
372
|
+
# Match against the same identity variants used for handoffs (display name,
|
|
373
|
+
# github first-name, people-file name) so "to: oz" matches author "oguzhan".
|
|
374
|
+
_DISPLAY_Q=$(jq -r '.display_name // empty' "$STATE_FILE" 2>/dev/null)
|
|
375
|
+
_GH_NAME_Q=$(jq -r '.github_name // empty' "$STATE_FILE" 2>/dev/null)
|
|
376
|
+
_FIRST_NAME_Q=$(echo "$_GH_NAME_Q" | awk '{print tolower($1)}')
|
|
377
|
+
_PEOPLE_FILE_Q="$SCRIPT_DIR/memory/people/$(echo "$AUTHOR" | tr '[:upper:]' '[:lower:]').md"
|
|
378
|
+
_PEOPLE_NAME_Q=""
|
|
379
|
+
[ -f "$_PEOPLE_FILE_Q" ] && _PEOPLE_NAME_Q=$(sed -n 's/^# *//p' "$_PEOPLE_FILE_Q" 2>/dev/null | head -1 | awk '{print tolower($1)}')
|
|
380
|
+
|
|
381
|
+
LINES=""
|
|
382
|
+
for QF in "$QDIR"/*.md; do
|
|
383
|
+
[ -f "$QF" ] || continue
|
|
384
|
+
Q_TO=$(sed -n 's/^to:[[:space:]]*//p' "$QF" 2>/dev/null | head -1 | tr -d '"' | tr '[:upper:]' '[:lower:]' | awk '{print $1}')
|
|
385
|
+
[ -z "$Q_TO" ] && continue
|
|
386
|
+
_MATCH="false"
|
|
387
|
+
for _V in "$AUTHOR" "$_DISPLAY_Q" "$_FIRST_NAME_Q" "$_PEOPLE_NAME_Q"; do
|
|
388
|
+
[ -z "$_V" ] && continue
|
|
389
|
+
_V_LC=$(echo "$_V" | tr '[:upper:]' '[:lower:]')
|
|
390
|
+
[ "$Q_TO" = "$_V_LC" ] && _MATCH="true" && break
|
|
391
|
+
done
|
|
392
|
+
[ "$_MATCH" = "true" ] || continue
|
|
393
|
+
Q_STATUS=$(sed -n 's/^status:[[:space:]]*//p' "$QF" 2>/dev/null | head -1 | tr -d '"')
|
|
394
|
+
[ "$Q_STATUS" = "pending" ] || continue
|
|
395
|
+
Q_FROM=$(sed -n 's/^from:[[:space:]]*//p' "$QF" 2>/dev/null | head -1 | tr -d '"')
|
|
396
|
+
Q_TOPIC=$(sed -n 's/^topic:[[:space:]]*//p' "$QF" 2>/dev/null | head -1 | tr -d '"')
|
|
397
|
+
Q_HID=$(sed -n 's/^harvest_id:[[:space:]]*//p' "$QF" 2>/dev/null | head -1 | tr -d '"')
|
|
398
|
+
Q_REL="${QF#$SCRIPT_DIR/}"
|
|
399
|
+
LINE=$(jq -nc \
|
|
400
|
+
--arg from "$Q_FROM" \
|
|
401
|
+
--arg topic "$Q_TOPIC" \
|
|
402
|
+
--arg hid "$Q_HID" \
|
|
403
|
+
--arg file "$Q_REL" \
|
|
404
|
+
'{from:$from, topic:$topic, harvest_id:$hid, file:$file}' 2>/dev/null)
|
|
405
|
+
[ -n "$LINE" ] && LINES="$LINES$LINE
|
|
406
|
+
"
|
|
407
|
+
done
|
|
408
|
+
if [ -n "$LINES" ]; then
|
|
409
|
+
PENDING_JSON=$(echo "$LINES" | jq -sc '.' 2>/dev/null || echo "[]")
|
|
410
|
+
fi
|
|
411
|
+
fi
|
|
412
|
+
echo "$PENDING_JSON" > "$CTX_DIR/pending_questions"
|
|
413
|
+
) &
|
|
414
|
+
|
|
415
|
+
# 7. Graph health (background — zero added latency, runs in parallel)
|
|
416
|
+
(
|
|
417
|
+
if [ "$LOCAL_MODE" = "true" ]; then
|
|
418
|
+
echo "skip"
|
|
419
|
+
elif bash "$SCRIPT_DIR/bin/graph.sh" test 2>/dev/null | grep -q "Connected"; then
|
|
420
|
+
echo "ok"
|
|
421
|
+
else
|
|
422
|
+
echo "fail"
|
|
423
|
+
fi
|
|
424
|
+
) > "$CTX_DIR/graph_health" 2>/dev/null &
|
|
425
|
+
|
|
426
|
+
# 8. Telegram health (background)
|
|
427
|
+
(
|
|
428
|
+
if [ "$LOCAL_MODE" = "true" ]; then
|
|
429
|
+
echo "skip"
|
|
430
|
+
elif bash "$SCRIPT_DIR/bin/notify.sh" test 2>/dev/null | grep -q "connected"; then
|
|
431
|
+
echo "ok"
|
|
432
|
+
else
|
|
433
|
+
echo "fail"
|
|
434
|
+
fi
|
|
435
|
+
) > "$CTX_DIR/telegram_health" 2>/dev/null &
|
|
436
|
+
|
|
437
|
+
# 9. Lifecycle events: merged PRs + implemented handoffs (background)
|
|
438
|
+
(
|
|
439
|
+
GH_USER_LC=""
|
|
440
|
+
if [ -f "$STATE_FILE" ]; then
|
|
441
|
+
GH_USER_LC=$(jq -r '.github_username // empty' "$STATE_FILE" 2>/dev/null)
|
|
442
|
+
fi
|
|
443
|
+
DISPLAY_NAME_LC=""
|
|
444
|
+
if [ -f "$STATE_FILE" ]; then
|
|
445
|
+
DISPLAY_NAME_LC=$(jq -r '.display_name // empty' "$STATE_FILE" 2>/dev/null)
|
|
446
|
+
fi
|
|
447
|
+
# Get last session end date (use 7 days ago as fallback)
|
|
448
|
+
LAST_END=$(bash "$SCRIPT_DIR/bin/graph.sh" query "
|
|
449
|
+
MATCH (s:Session)-[:BY]->(p:Person {github: \$gh})
|
|
450
|
+
WHERE s.wrappedAt IS NOT NULL
|
|
451
|
+
RETURN toString(s.wrappedAt) AS t
|
|
452
|
+
ORDER BY s.wrappedAt DESC SKIP 1 LIMIT 1
|
|
453
|
+
" "$(jq -n --arg gh "$GH_USER_LC" '{gh: $gh}')" 2>/dev/null | jq -r '.values[0][0] // empty' 2>/dev/null)
|
|
454
|
+
if [ -z "$LAST_END" ]; then
|
|
455
|
+
LAST_END="$(date -v-7d +%Y-%m-%d 2>/dev/null || date -d '7 days ago' +%Y-%m-%d 2>/dev/null || echo '2026-03-03')T00:00:00Z"
|
|
456
|
+
fi
|
|
457
|
+
|
|
458
|
+
MERGED_JSON="[]"
|
|
459
|
+
if [ -n "$GH_USER_LC" ]; then
|
|
460
|
+
MERGED_JSON=$(bash "$SCRIPT_DIR/bin/graph-op.sh" my-merged-prs "$GH_USER_LC" "$(echo "$LAST_END" | cut -dT -f1)" 2>/dev/null || echo "[]")
|
|
461
|
+
fi
|
|
462
|
+
|
|
463
|
+
IMPL_JSON="[]"
|
|
464
|
+
IMPL_NAME="${DISPLAY_NAME_LC:-$GH_USER_LC}"
|
|
465
|
+
if [ -n "$IMPL_NAME" ]; then
|
|
466
|
+
IMPL_JSON=$(bash "$SCRIPT_DIR/bin/graph-op.sh" my-implemented-handoffs "$IMPL_NAME" "$(echo "$LAST_END" | cut -dT -f1)" 2>/dev/null || echo "[]")
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
jq -n --argjson merged "$MERGED_JSON" --argjson impl "$IMPL_JSON" \
|
|
470
|
+
'{merged_prs: $merged, implemented_handoffs: $impl}' 2>/dev/null || echo '{"merged_prs":[],"implemented_handoffs":[]}'
|
|
471
|
+
) > "$CTX_DIR/lifecycle" 2>/dev/null &
|
|
472
|
+
|
|
473
|
+
# 10. Last Pulse brief (background) — gated on feature flag + API key
|
|
474
|
+
(
|
|
475
|
+
PULSE_ENABLED=$(jq -r '.features.pulse // "false"' "$CONFIG" 2>/dev/null || echo "false")
|
|
476
|
+
HAS_API_KEY=false
|
|
477
|
+
_env_file="${ENV_FILE:-$SCRIPT_DIR/.env}"
|
|
478
|
+
if [ -f "$_env_file" ]; then
|
|
479
|
+
_key=$(grep '^EGREGORE_API_KEY=' "$_env_file" 2>/dev/null | cut -d'=' -f2- || true)
|
|
480
|
+
[ -n "$_key" ] && HAS_API_KEY=true
|
|
481
|
+
fi
|
|
482
|
+
|
|
483
|
+
if [ "$PULSE_ENABLED" = "true" ] && [ "$HAS_API_KEY" = "true" ] && [ -n "$GH_USER_LC" ]; then
|
|
484
|
+
BRIEF_RESULT=$(bash "$SCRIPT_DIR/bin/graph.sh" query "
|
|
485
|
+
MATCH (p:Person {github: \$gh})
|
|
486
|
+
WHERE p.lastBrief IS NOT NULL
|
|
487
|
+
AND p.lastBriefDate >= date() - duration('P7D')
|
|
488
|
+
RETURN p.lastBrief, toString(p.lastBriefDate), p.lastRecommendations
|
|
489
|
+
" "$(jq -n --arg gh "$GH_USER_LC" '{gh: $gh}')" 2>/dev/null || echo '{"values":[]}')
|
|
490
|
+
BRIEF=$(echo "$BRIEF_RESULT" | jq -r '.values[0][0] // empty' 2>/dev/null || true)
|
|
491
|
+
BRIEF_DATE=$(echo "$BRIEF_RESULT" | jq -r '.values[0][1] // empty' 2>/dev/null || true)
|
|
492
|
+
RECS=$(echo "$BRIEF_RESULT" | jq -c '.values[0][2] // []' 2>/dev/null || echo '[]')
|
|
493
|
+
if [ -n "$BRIEF" ]; then
|
|
494
|
+
jq -n --arg brief "$BRIEF" --arg date "$BRIEF_DATE" --argjson recs "$RECS" \
|
|
495
|
+
'{brief: $brief, date: $date, recommendations: $recs}'
|
|
496
|
+
else
|
|
497
|
+
echo '{}'
|
|
498
|
+
fi
|
|
499
|
+
else
|
|
500
|
+
echo '{}'
|
|
501
|
+
fi
|
|
502
|
+
) > "$CTX_DIR/pulse_brief" 2>/dev/null &
|
|
503
|
+
|
|
504
|
+
# Wait for all context gathering + health checks to finish
|
|
505
|
+
wait
|
|
506
|
+
|
|
507
|
+
# --- Read service health results ---
|
|
508
|
+
GRAPH_RESULT=$(cat "$CTX_DIR/graph_health" 2>/dev/null || echo "")
|
|
509
|
+
if [ -n "$GRAPH_RESULT" ]; then HEALTH_GRAPH="$GRAPH_RESULT"; fi
|
|
510
|
+
TELEGRAM_RESULT=$(cat "$CTX_DIR/telegram_health" 2>/dev/null || echo "")
|
|
511
|
+
if [ -n "$TELEGRAM_RESULT" ]; then HEALTH_TELEGRAM="$TELEGRAM_RESULT"; fi
|
|
@@ -0,0 +1,142 @@
|
|
|
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 '.[] | "| \(.name) | \(.last_seen) | \(.branches | join(", ")) |"' 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
|
+
# 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 document "$_DB_MD" --output "$_DB_HTML" --no-open >/dev/null 2>&1 && [ -f "$_DB_HTML" ]; then
|
|
138
|
+
DASHBOARD_URL="file://${_DB_HTML}"
|
|
139
|
+
fi
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
rm -f "$_DB_MD"
|