create-egregore 0.19.33 → 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.
- package/assets/egregore-launcher.js +118 -13
- package/bin/cli.js +19 -3
- package/lib/connect-runtime.js +4 -2
- package/lib/local.js +15 -1
- package/lib/prime-runtime.js +208 -0
- package/lib/setup.js +45 -3
- package/package.json +4 -3
- package/runtime/codex/.codex/skills/ingest-notion/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/notion-connect/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/codex/.codex/spec-manifest.json +3 -3
- package/runtime/codex/AGENTS.md +11 -4
- package/runtime/codex/bin/activity-data.sh +4 -8
- package/runtime/codex/bin/attendant.sh +13 -0
- package/runtime/codex/bin/autosave.sh +27 -1
- package/runtime/codex/bin/codex-render-spec.mjs +6 -0
- package/runtime/codex/bin/codex-session-start.sh +3 -1
- package/runtime/codex/bin/graph-batch.sh +25 -6
- package/runtime/codex/bin/graph-op.sh +5 -4
- package/runtime/codex/bin/graph.sh +31 -5
- package/runtime/codex/bin/ingest.py +81 -5
- package/runtime/codex/bin/lib/context.sh +26 -1
- package/runtime/codex/bin/lib/greeting.sh +4 -1
- package/runtime/codex/bin/lib/worktree-links.sh +25 -0
- package/runtime/codex/manifest.json +7 -1
- package/runtime/pi/.codex/skills/ingest-notion/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/notion-connect/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/pi/.codex/spec-manifest.json +3 -3
- package/runtime/pi/.pi/APPEND_SYSTEM.md +3 -2
- package/runtime/pi/.pi/extensions/egregore.ts +17 -6
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +11 -4
- package/runtime/pi/bin/activity-data.sh +4 -8
- package/runtime/pi/bin/attendant.sh +13 -0
- package/runtime/pi/bin/autosave.sh +27 -1
- package/runtime/pi/bin/codex-render-spec.mjs +6 -0
- package/runtime/pi/bin/codex-session-start.sh +3 -1
- package/runtime/pi/bin/graph-batch.sh +25 -6
- package/runtime/pi/bin/graph-op.sh +5 -4
- package/runtime/pi/bin/graph.sh +31 -5
- package/runtime/pi/bin/ingest.py +81 -5
- package/runtime/pi/bin/lib/context.sh +26 -1
- package/runtime/pi/bin/lib/greeting.sh +4 -1
- package/runtime/pi/bin/lib/worktree-links.sh +25 -0
- package/runtime/pi/bin/pi-branch-policy.mjs +138 -1
- package/runtime/pi/bin/pi-product-workflows.mjs +35 -7
- package/runtime/pi/manifest.json +7 -1
- package/runtime/prime/.claude/context/notification-consent.md +66 -0
- package/runtime/prime/.codex/config.toml +17 -0
- package/runtime/prime/.codex/hooks/branch-guard.js +461 -0
- package/runtime/prime/.codex/hooks/search-hint.js +67 -0
- package/runtime/prime/.codex/hooks.json +30 -0
- package/runtime/prime/.codex/skills/activity/SKILL.md +82 -0
- package/runtime/prime/.codex/skills/announce/SKILL.md +67 -0
- package/runtime/prime/.codex/skills/archive/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/branch/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/checkup/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/commit/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/contribute/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/create-skill/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/dark-mode/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/dashboard/SKILL.md +59 -0
- package/runtime/prime/.codex/skills/deep-reflect/SKILL.md +109 -0
- package/runtime/prime/.codex/skills/delete-user/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/emissary/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/handoff/SKILL.md +166 -0
- package/runtime/prime/.codex/skills/harvest/QUESTION_PALETTE.md +76 -0
- package/runtime/prime/.codex/skills/harvest/SKILL.md +131 -0
- package/runtime/prime/.codex/skills/ingest/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/ingest-notion/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/ingest-user-interview/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/invite/SKILL.md +44 -0
- package/runtime/prime/.codex/skills/issue/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/me/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/meeting/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/note/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/notion-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/onboarding/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/pr/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/pull/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/push/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/quest/SKILL.md +80 -0
- package/runtime/prime/.codex/skills/reflect/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/review-pr/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/save/SKILL.md +125 -0
- package/runtime/prime/.codex/skills/slack-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/summon/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/sync-repos/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/telegram-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/telemetry/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/test/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/the-spiral/SKILL.md +84 -0
- package/runtime/prime/.codex/skills/tui-design/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/tutorial/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/update/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/view/SKILL.md +156 -0
- package/runtime/prime/.codex/skills/view/agents/openai.yaml +4 -0
- package/runtime/prime/.codex/skills/view/assets/compose-scaffold.html +221 -0
- package/runtime/prime/.codex/skills/visual-explain/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/wrap/SKILL.md +58 -0
- package/runtime/prime/.codex/spec-manifest.json +132 -0
- package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +393 -0
- package/runtime/prime/.prime/agent/extensions/egregore.ts +460 -0
- package/runtime/prime/.prime/agent/settings.json +4 -0
- package/runtime/prime/.prime/agent/skills/egregore/SKILL.md +34 -0
- package/runtime/prime/.prime/agent/skills/egregore/pyproject.toml +13 -0
- package/runtime/prime/.prime/agent/skills/egregore/src/egregore/__init__.py +156 -0
- package/runtime/prime/.prime/agent/spec-manifest.json +12 -0
- package/runtime/prime/AGENTS.md +489 -0
- package/runtime/prime/bin/activity-data.sh +238 -0
- package/runtime/prime/bin/agent.sh +775 -0
- package/runtime/prime/bin/attendant.sh +707 -0
- package/runtime/prime/bin/autosave.sh +112 -0
- package/runtime/prime/bin/capture-reconcile.sh +69 -0
- package/runtime/prime/bin/capture-run.sh +341 -0
- package/runtime/prime/bin/codex-install-skills.sh +78 -0
- package/runtime/prime/bin/codex-render-spec.mjs +440 -0
- package/runtime/prime/bin/codex-session-start.sh +265 -0
- package/runtime/prime/bin/codex-skill-render.mjs +347 -0
- package/runtime/prime/bin/codex-sync-skills.sh +232 -0
- package/runtime/prime/bin/connect-refresh.sh +33 -0
- package/runtime/prime/bin/dashboard-data.sh +178 -0
- package/runtime/prime/bin/graph-batch.sh +79 -0
- package/runtime/prime/bin/graph-op.sh +1102 -0
- package/runtime/prime/bin/graph-wal.sh +229 -0
- package/runtime/prime/bin/graph.sh +223 -0
- package/runtime/prime/bin/handoff-pr-backfill.sh +86 -0
- package/runtime/prime/bin/handoff-run.sh +671 -0
- package/runtime/prime/bin/handoff-save-egregore.sh +212 -0
- package/runtime/prime/bin/index-handoff.sh +322 -0
- package/runtime/prime/bin/ingest-graph.sh +80 -0
- package/runtime/prime/bin/ingest-surface.html +495 -0
- package/runtime/prime/bin/ingest.py +756 -0
- package/runtime/prime/bin/ingest.sh +279 -0
- package/runtime/prime/bin/ingest_graph.py +856 -0
- package/runtime/prime/bin/ingest_surface.py +544 -0
- package/runtime/prime/bin/lib/artifact-id.sh +46 -0
- package/runtime/prime/bin/lib/config.sh +116 -0
- package/runtime/prime/bin/lib/context.sh +621 -0
- package/runtime/prime/bin/lib/dashboard-artifact.sh +242 -0
- package/runtime/prime/bin/lib/ensure-develop.sh +183 -0
- package/runtime/prime/bin/lib/git-sync.sh +406 -0
- package/runtime/prime/bin/lib/greeting.sh +519 -0
- package/runtime/prime/bin/lib/hash.sh +13 -0
- package/runtime/prime/bin/lib/identity.sh +213 -0
- package/runtime/prime/bin/lib/metrics.sh +374 -0
- package/runtime/prime/bin/lib/noncode.sh +31 -0
- package/runtime/prime/bin/lib/permissions.sh +106 -0
- package/runtime/prime/bin/lib/time.sh +56 -0
- package/runtime/prime/bin/lib/worktree-links.sh +79 -0
- package/runtime/prime/bin/notify.sh +636 -0
- package/runtime/prime/bin/person.py +630 -0
- package/runtime/prime/bin/person.sh +448 -0
- package/runtime/prime/bin/pi-branch-policy.mjs +312 -0
- package/runtime/prime/bin/pi-product-workflows.mjs +394 -0
- package/runtime/prime/bin/prime-render-spec.mjs +123 -0
- package/runtime/prime/bin/prime-session-start.sh +24 -0
- package/runtime/prime/bin/publish-artifact.sh +271 -0
- package/runtime/prime/bin/publish-references.sh +144 -0
- package/runtime/prime/bin/repo-state.sh +96 -0
- package/runtime/prime/bin/search.sh +240 -0
- package/runtime/prime/bin/session-autosave.sh +209 -0
- package/runtime/prime/bin/session-log.sh +199 -0
- package/runtime/prime/bin/settings.sh +304 -0
- package/runtime/prime/bin/telemetry.sh +391 -0
- package/runtime/prime/bin/worktree-create.sh +128 -0
- package/runtime/prime/bin/worktree-remove.sh +50 -0
- package/runtime/prime/bin/worktree.sh +198 -0
- package/runtime/prime/manifest.json +178 -0
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# context.sh — Context gathering for session-start.sh
|
|
3
|
+
#
|
|
4
|
+
# Runs 11 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
|
+
# 11. Momentum metrics
|
|
17
|
+
#
|
|
18
|
+
# All subshells write to $CTX_DIR (a temp directory).
|
|
19
|
+
# Ends with `wait` + reading health results back.
|
|
20
|
+
#
|
|
21
|
+
# Inputs: SCRIPT_DIR, AUTHOR, LOCAL_MODE, STATE_FILE, CONFIG, ENV_FILE,
|
|
22
|
+
# HEALTH_GRAPH, HEALTH_TELEGRAM
|
|
23
|
+
# Outputs: CTX_DIR (populated), TIME_OF_DAY, HEALTH_GRAPH, HEALTH_TELEGRAM
|
|
24
|
+
|
|
25
|
+
CTX_DIR=$(mktemp -d)
|
|
26
|
+
|
|
27
|
+
source "$SCRIPT_DIR/bin/lib/handoff-meta.sh" >/dev/null 2>/dev/null || true
|
|
28
|
+
|
|
29
|
+
# Time of day
|
|
30
|
+
HOUR=$(date +%H)
|
|
31
|
+
if [ "$HOUR" -lt 12 ]; then TIME_OF_DAY="morning"
|
|
32
|
+
elif [ "$HOUR" -lt 17 ]; then TIME_OF_DAY="afternoon"
|
|
33
|
+
else TIME_OF_DAY="evening"
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# --- Attendant-baked seed ---------------------------------------------------
|
|
37
|
+
# bin/attendant.sh pre-bakes a context snapshot tar every warm cycle
|
|
38
|
+
# (_prebake_context). When the bake is fresh and its provenance matches, reuse
|
|
39
|
+
# its purely graph-bound results — todos, lifecycle, pulse, service health —
|
|
40
|
+
# and run every collector with local git/file inputs live below (handoffs,
|
|
41
|
+
# quests, activity, soul, addressed, questions, team presence, metrics), so
|
|
42
|
+
# everything file/git-derived reflects this instant. Purely graph-derived
|
|
43
|
+
# fields are then at most one warm cycle stale, and the boot path pays
|
|
44
|
+
# near-zero blocking graph round-trips (live collectors ride the warm cache).
|
|
45
|
+
#
|
|
46
|
+
# The seed is trusted only when ALL of these hold — otherwise fall back to a
|
|
47
|
+
# full live gather:
|
|
48
|
+
# - the tar extracts cleanly (atomic rename on the writer side means we see
|
|
49
|
+
# a whole bake or none, never a half-written directory)
|
|
50
|
+
# - .baked-author matches this session's author (shared machines)
|
|
51
|
+
# - .baked-local-mode matches this session's mode (a connected bake must
|
|
52
|
+
# not leak graph data into a session that switched to local)
|
|
53
|
+
# - .baked-config matches this checkout's egregore.json (org/API repoint,
|
|
54
|
+
# or a worktree branch carrying a different config)
|
|
55
|
+
# - .baked-scope matches the EFFECTIVE endpoint/credential fingerprint
|
|
56
|
+
# (EGREGORE_API_URL/KEY overrides can point at a different tenant than
|
|
57
|
+
# the committed config names)
|
|
58
|
+
# - .baked-schema matches this checkout's context.sh revision (a bake from
|
|
59
|
+
# another collector schema may be missing or misformatting files)
|
|
60
|
+
# - .baked-ts is numeric, not in the future, and younger than 15min
|
|
61
|
+
# - every seeded collector's file actually landed in CTX_DIR (a partial
|
|
62
|
+
# copy must not suppress live collectors)
|
|
63
|
+
CTX_SEED_USED="false"
|
|
64
|
+
if [ -n "${CTX_SEED_TAR:-}" ] && [ -f "${CTX_SEED_TAR:-}" ]; then
|
|
65
|
+
_SEED_TMP=$(mktemp -d)
|
|
66
|
+
if tar -xf "$CTX_SEED_TAR" -C "$_SEED_TMP" 2>/dev/null; then
|
|
67
|
+
_SEED_TS=$(cat "$_SEED_TMP/.baked-ts" 2>/dev/null || echo 0)
|
|
68
|
+
case "$_SEED_TS" in ''|*[!0-9]*) _SEED_TS=0 ;; esac
|
|
69
|
+
_SEED_AUTHOR=$(cat "$_SEED_TMP/.baked-author" 2>/dev/null || echo "")
|
|
70
|
+
_SEED_MODE=$(cat "$_SEED_TMP/.baked-local-mode" 2>/dev/null || echo "")
|
|
71
|
+
_SEED_CONFIG=$(cat "$_SEED_TMP/.baked-config" 2>/dev/null || echo "")
|
|
72
|
+
_SEED_SCOPE=$(cat "$_SEED_TMP/.baked-scope" 2>/dev/null || echo "")
|
|
73
|
+
_SEED_SCHEMA=$(cat "$_SEED_TMP/.baked-schema" 2>/dev/null || echo "")
|
|
74
|
+
_NOW_CONFIG=$(cksum "${CONFIG:-$SCRIPT_DIR/egregore.json}" 2>/dev/null | cut -d' ' -f1)
|
|
75
|
+
_NOW_SCHEMA=$(cksum "$SCRIPT_DIR/bin/lib/context.sh" 2>/dev/null | cut -d' ' -f1)
|
|
76
|
+
# Effective endpoint/credential, mirroring bin/graph.sh's resolution order
|
|
77
|
+
# (process env → .env → committed api_url).
|
|
78
|
+
_NOW_URL="${EGREGORE_API_URL:-$(grep '^EGREGORE_API_URL=' "${ENV_FILE:-$SCRIPT_DIR/.env}" 2>/dev/null | cut -d'=' -f2- || true)}"
|
|
79
|
+
_NOW_KEY="${EGREGORE_API_KEY:-$(grep '^EGREGORE_API_KEY=' "${ENV_FILE:-$SCRIPT_DIR/.env}" 2>/dev/null | cut -d'=' -f2- || true)}"
|
|
80
|
+
[ -n "$_NOW_URL" ] || _NOW_URL=$(jq -r '.api_url // empty' "${CONFIG:-$SCRIPT_DIR/egregore.json}" 2>/dev/null)
|
|
81
|
+
_NOW_SCOPE=$(echo -n "${_NOW_URL}|${_NOW_KEY}" | cksum | cut -d' ' -f1)
|
|
82
|
+
_SEED_AGE=$(( $(date +%s) - _SEED_TS ))
|
|
83
|
+
if [ -n "$_SEED_AUTHOR" ] && [ "$_SEED_AUTHOR" = "$AUTHOR" ] \
|
|
84
|
+
&& [ "$_SEED_MODE" = "${LOCAL_MODE:-false}" ] \
|
|
85
|
+
&& [ -n "$_SEED_CONFIG" ] && [ "$_SEED_CONFIG" = "$_NOW_CONFIG" ] \
|
|
86
|
+
&& [ -n "$_SEED_SCOPE" ] && [ "$_SEED_SCOPE" = "$_NOW_SCOPE" ] \
|
|
87
|
+
&& [ -n "$_SEED_SCHEMA" ] && [ "$_SEED_SCHEMA" = "$_NOW_SCHEMA" ] \
|
|
88
|
+
&& [ "$_SEED_AGE" -ge 0 ] && [ "$_SEED_AGE" -lt 900 ]; then
|
|
89
|
+
if cp "$_SEED_TMP"/* "$CTX_DIR/" 2>/dev/null; then
|
|
90
|
+
# Completeness: every collector this seed replaces must be present.
|
|
91
|
+
CTX_SEED_USED="true"
|
|
92
|
+
for _SEED_REQ in todos lifecycle pulse_brief graph_health telegram_health; do
|
|
93
|
+
[ -f "$CTX_DIR/$_SEED_REQ" ] || { CTX_SEED_USED="false"; break; }
|
|
94
|
+
done
|
|
95
|
+
fi
|
|
96
|
+
fi
|
|
97
|
+
fi
|
|
98
|
+
rm -rf "$_SEED_TMP"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# 1. Recent handoffs (background)
|
|
102
|
+
(
|
|
103
|
+
JSON="[]"
|
|
104
|
+
if [ -d "$SCRIPT_DIR/memory/handoffs" ]; then
|
|
105
|
+
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)
|
|
106
|
+
JSON="["
|
|
107
|
+
FIRST=true
|
|
108
|
+
for F in $FILES; do
|
|
109
|
+
[ -z "$F" ] && continue
|
|
110
|
+
NAME=$(basename "$F" .md)
|
|
111
|
+
PREVIEW=$(head -c 120 "$F" 2>/dev/null | tr '\n' ' ' | sed 's/"/\\"/g')
|
|
112
|
+
$FIRST || JSON="$JSON,"
|
|
113
|
+
JSON="$JSON{\"name\":\"$NAME\",\"preview\":\"$PREVIEW\"}"
|
|
114
|
+
FIRST=false
|
|
115
|
+
done
|
|
116
|
+
JSON="$JSON]"
|
|
117
|
+
fi
|
|
118
|
+
echo "$JSON" > "$CTX_DIR/handoffs"
|
|
119
|
+
) &
|
|
120
|
+
|
|
121
|
+
# 2. Quests (background)
|
|
122
|
+
(
|
|
123
|
+
JSON="[]"
|
|
124
|
+
if [ -d "$SCRIPT_DIR/memory/quests" ]; then
|
|
125
|
+
JSON="["
|
|
126
|
+
FIRST=true
|
|
127
|
+
for F in "$SCRIPT_DIR/memory/quests/"*.md; do
|
|
128
|
+
[ -e "$F" ] || continue
|
|
129
|
+
case "$F" in *draft*) continue ;; esac
|
|
130
|
+
NAME=$(basename "$F" .md)
|
|
131
|
+
$FIRST || JSON="$JSON,"
|
|
132
|
+
JSON="$JSON\"$NAME\""
|
|
133
|
+
FIRST=false
|
|
134
|
+
done
|
|
135
|
+
JSON="$JSON]"
|
|
136
|
+
fi
|
|
137
|
+
echo "$JSON" > "$CTX_DIR/quests"
|
|
138
|
+
) &
|
|
139
|
+
|
|
140
|
+
# 3. User's last activity (background)
|
|
141
|
+
(
|
|
142
|
+
git log --author="$AUTHOR" --format="%ar|%s" -1 2>/dev/null > "$CTX_DIR/activity" || echo "" > "$CTX_DIR/activity"
|
|
143
|
+
) &
|
|
144
|
+
|
|
145
|
+
# 3b. Personal todos from graph (background; seeded)
|
|
146
|
+
[ "$CTX_SEED_USED" != "true" ] && (
|
|
147
|
+
_API_URL=$(jq -r '.api_url // empty' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
|
|
148
|
+
_API_KEY=$(grep '^EGREGORE_API_KEY=' "${ENV_FILE:-$SCRIPT_DIR/.env}" 2>/dev/null | cut -d'=' -f2-)
|
|
149
|
+
if [ -n "$_API_URL" ] && [ -n "$_API_KEY" ]; then
|
|
150
|
+
TODO_RAW=$(bash "$SCRIPT_DIR/bin/graph.sh" query \
|
|
151
|
+
"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" \
|
|
152
|
+
"$(jq -n --arg me "$AUTHOR" '{me: $me}')" 2>/dev/null || echo "")
|
|
153
|
+
if [ -n "$TODO_RAW" ]; then
|
|
154
|
+
echo "$TODO_RAW" | jq '[.values[] | {text: .[0], priority: (.[1] // 0), status: .[2], created: .[3], quest: (.[4] // "")}]' 2>/dev/null || echo "[]"
|
|
155
|
+
else
|
|
156
|
+
echo "[]"
|
|
157
|
+
fi
|
|
158
|
+
else
|
|
159
|
+
echo "[]"
|
|
160
|
+
fi
|
|
161
|
+
) > "$CTX_DIR/todos" 2>/dev/null &
|
|
162
|
+
|
|
163
|
+
# 4. Team presence — last seen + active branches (background)
|
|
164
|
+
# Always live, even when seeded: its branch/file inputs are local and cheap
|
|
165
|
+
# (single awk/jq passes), and stale "last seen 2m ago" lines are the most
|
|
166
|
+
# user-visible form of drift. The one graph query inside rides the warm cache.
|
|
167
|
+
(
|
|
168
|
+
# --- Read config inside subshell ---
|
|
169
|
+
_API_URL=$(jq -r '.api_url // empty' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
|
|
170
|
+
_API_KEY=$(grep '^EGREGORE_API_KEY=' "${ENV_FILE:-$SCRIPT_DIR/.env}" 2>/dev/null | cut -d'=' -f2-)
|
|
171
|
+
|
|
172
|
+
# --- Graph: last-seen per person (excluding self) ---
|
|
173
|
+
GRAPH_DATA="[]"
|
|
174
|
+
if [ -n "$_API_URL" ] && [ -n "$_API_KEY" ]; then
|
|
175
|
+
# String-compare the temporals: `datetime(s.date)` is an invalid cast when
|
|
176
|
+
# s.date is a Date (Neo.ClientError.Statement.TypeError), which made this
|
|
177
|
+
# query fail silently on every boot — GRAPH_DATA stayed empty, the greeting
|
|
178
|
+
# fell back to git/file presence, and the FILE_PRESENCE scan below ran in
|
|
179
|
+
# connected mode too. ISO-8601 strings order lexicographically, so max()
|
|
180
|
+
# over toString() is correct across DateTime, Date, and legacy string rows.
|
|
181
|
+
CYPHER="MATCH (s:Session)-[:BY]->(p:Person) WHERE p.github <> \$me RETURN p.name AS name, max(coalesce(toString(s.startedAt), toString(s.date))) AS lastSeen ORDER BY lastSeen DESC"
|
|
182
|
+
GRAPH_RAW=$(bash "$SCRIPT_DIR/bin/graph.sh" query "$CYPHER" "$(jq -n --arg me "$AUTHOR" '{me: $me}')" 2>/dev/null || echo "")
|
|
183
|
+
if [ -n "$GRAPH_RAW" ]; then
|
|
184
|
+
GRAPH_DATA=$(echo "$GRAPH_RAW" | jq '[.values[] | {name: .[0], lastSeen: .[1]}]' 2>/dev/null || echo "[]")
|
|
185
|
+
fi
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# --- Git: branch tip commit dates per person (primary recency signal) ---
|
|
189
|
+
# git for-each-ref is fast and gives the actual last commit date per branch.
|
|
190
|
+
# Single awk pass — the previous per-branch cut+tr loop spawned 3 processes
|
|
191
|
+
# per ref, and busy orgs carry hundreds of origin/dev/* refs. Refs arrive
|
|
192
|
+
# sorted by -committerdate, so the first epoch per author is their newest.
|
|
193
|
+
GIT_COMMIT_DATA="{}"
|
|
194
|
+
GIT_REF_DATA=$(git -C "$SCRIPT_DIR" for-each-ref --sort=-committerdate \
|
|
195
|
+
--format='%(refname:short)|%(committerdate:unix)' refs/remotes/origin/dev/ 2>/dev/null || echo "")
|
|
196
|
+
if [ -n "$GIT_REF_DATA" ]; then
|
|
197
|
+
GIT_COMMIT_DATA=$(printf '%s\n' "$GIT_REF_DATA" | awk -F'|' '
|
|
198
|
+
{
|
|
199
|
+
p = $1; sub(/^origin\/dev\//, "", p)
|
|
200
|
+
split(p, seg, "/"); a = tolower(seg[1])
|
|
201
|
+
if (a != "" && !(a in best)) best[a] = $2
|
|
202
|
+
}
|
|
203
|
+
END { for (a in best) printf "%s|%s\n", a, best[a] }
|
|
204
|
+
' 2>/dev/null | jq -Rn '
|
|
205
|
+
[inputs | split("|") | {(.[0]): .[1]}]
|
|
206
|
+
| add // {}
|
|
207
|
+
' 2>/dev/null || echo "{}")
|
|
208
|
+
[ -z "$GIT_COMMIT_DATA" ] && GIT_COMMIT_DATA="{}"
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# --- Git: active dev/* branches (excluding self) ---
|
|
212
|
+
SELF_LC=$(echo "$AUTHOR" | tr '[:upper:]' '[:lower:]')
|
|
213
|
+
SELF_DISPLAY_LC=""
|
|
214
|
+
if [ -f "$SCRIPT_DIR/.egregore-state.json" ]; then
|
|
215
|
+
SELF_DISPLAY_LC=$(jq -r '.display_name // empty' "$SCRIPT_DIR/.egregore-state.json" 2>/dev/null | tr '[:upper:]' '[:lower:]')
|
|
216
|
+
fi
|
|
217
|
+
BRANCH_DATA=$(git -C "$SCRIPT_DIR" branch -r --format='%(refname:short)' 2>/dev/null | grep "^origin/dev/" | sed 's|^origin/dev/||' || echo "")
|
|
218
|
+
|
|
219
|
+
BRANCH_MAP="{}"
|
|
220
|
+
if [ -n "$BRANCH_DATA" ]; then
|
|
221
|
+
# Single awk pass — the previous per-branch echo+tr loop spawned 2-4
|
|
222
|
+
# processes per branch over hundreds of refs.
|
|
223
|
+
BRANCH_MAP=$(printf '%s\n' "$BRANCH_DATA" | awk -v self="$SELF_LC" -v selfd="$SELF_DISPLAY_LC" '
|
|
224
|
+
{
|
|
225
|
+
if (split($0, seg, "/") < 2) next
|
|
226
|
+
a = tolower(seg[1])
|
|
227
|
+
if (a == "" || a == self || (selfd != "" && a == selfd)) next
|
|
228
|
+
h = $0; sub(/^[^\/]*\//, "", h); gsub(/-/, " ", h)
|
|
229
|
+
printf "%s|%s\n", a, h
|
|
230
|
+
}
|
|
231
|
+
' 2>/dev/null | jq -Rn '
|
|
232
|
+
[inputs | split("|") | {author: .[0], branch: (.[1:] | join("|"))}]
|
|
233
|
+
| group_by(.author)
|
|
234
|
+
| map({(.[0].author): [.[] | .branch]})
|
|
235
|
+
| add // {}
|
|
236
|
+
' 2>/dev/null || echo "{}")
|
|
237
|
+
[ -z "$BRANCH_MAP" ] && BRANCH_MAP="{}"
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
# --- Local-mode presence: scan session + wrap files for lastSeen ---
|
|
241
|
+
# When graph has no data, these files provide temporal presence information.
|
|
242
|
+
# Single awk pass over all candidate files — the previous per-file
|
|
243
|
+
# grep+sed loop spawned 2-3 processes per file (~60 files) on every boot.
|
|
244
|
+
FILE_PRESENCE="{}"
|
|
245
|
+
if [ "$GRAPH_DATA" = "[]" ]; then
|
|
246
|
+
FP_FILES=$(for DIR in "$SCRIPT_DIR/memory/sessions" "$SCRIPT_DIR/memory/wraps" "$SCRIPT_DIR/memory/handoffs"; do
|
|
247
|
+
[ -d "$DIR" ] || continue
|
|
248
|
+
find -L "$DIR" -name '*.md' -type f 2>/dev/null | xargs ls -t 2>/dev/null | head -20
|
|
249
|
+
done)
|
|
250
|
+
if [ -n "$FP_FILES" ]; then
|
|
251
|
+
FILE_PRESENCE=$(printf '%s\n' "$FP_FILES" | tr '\n' '\0' | xargs -0 awk '
|
|
252
|
+
function flush() { if (a != "") printf "%s|%s\n", a, d }
|
|
253
|
+
FNR == 1 { if (NR != 1) flush(); a=""; d="" }
|
|
254
|
+
a == "" && /^\*\*Author\*\*:/ { s = $0; sub(/^\*\*Author\*\*:[ \t]*/, "", s); a = s }
|
|
255
|
+
d == "" && /^\*\*Date\*\*:/ { s = $0; sub(/^\*\*Date\*\*:[ \t]*/, "", s); d = s }
|
|
256
|
+
END { flush() }
|
|
257
|
+
' 2>/dev/null \
|
|
258
|
+
| sort -t'|' -k1,1 -k2,2r | sort -t'|' -k1,1 -u \
|
|
259
|
+
| awk -F'|' '{ print tolower($1) "|" $2 }' \
|
|
260
|
+
| jq -Rn '
|
|
261
|
+
[inputs | split("|") | {(.[0]): .[1]}]
|
|
262
|
+
| add // {}
|
|
263
|
+
' 2>/dev/null || echo "{}")
|
|
264
|
+
[ -z "$FILE_PRESENCE" ] && FILE_PRESENCE="{}"
|
|
265
|
+
fi
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
# --- "Working on": latest session/handoff/wrap topic per person (file-based) ---
|
|
269
|
+
# Gives every team surface (greeting "around", dashboard) something legible in
|
|
270
|
+
# local mode, where git branches are usually empty for teammates.
|
|
271
|
+
# Single awk pass — the previous per-file grep+sed loop spawned ~6 processes
|
|
272
|
+
# per file over ~90 files on every boot.
|
|
273
|
+
_WM_TAB=$(printf '\t')
|
|
274
|
+
WM_FILES=$(for DIR in "$SCRIPT_DIR/memory/sessions" "$SCRIPT_DIR/memory/handoffs" "$SCRIPT_DIR/memory/wraps"; do
|
|
275
|
+
[ -d "$DIR" ] || continue
|
|
276
|
+
find -L "$DIR" -name '*.md' -not -name 'index*' -type f 2>/dev/null | xargs ls -t 2>/dev/null | head -30
|
|
277
|
+
done)
|
|
278
|
+
WORK_MAP="{}"
|
|
279
|
+
if [ -n "$WM_FILES" ]; then
|
|
280
|
+
WORK_MAP=$(printf '%s\n' "$WM_FILES" | tr '\n' '\0' | xargs -0 awk '
|
|
281
|
+
function flush() { if (a != "" && t != "") printf "%s\t%s\t%s\n", a, d, t }
|
|
282
|
+
FNR == 1 { if (NR != 1) flush(); a=""; d=""; t="" }
|
|
283
|
+
a == "" && /^\*\*Author\*\*:/ { s = $0; sub(/^\*\*Author\*\*:[ \t]*/, "", s); a = s }
|
|
284
|
+
d == "" && /^\*\*Date\*\*:/ { s = $0; sub(/^\*\*Date\*\*:[ \t]*/, "", s); d = s }
|
|
285
|
+
t == "" && /^# / {
|
|
286
|
+
s = $0; sub(/^#[ \t]*/, "", s)
|
|
287
|
+
sub(/^Session:[ \t]*/, "", s); sub(/^Handoff:[ \t]*/, "", s); sub(/^Wrap:[ \t]*/, "", s)
|
|
288
|
+
t = s
|
|
289
|
+
}
|
|
290
|
+
END { flush() }
|
|
291
|
+
' 2>/dev/null \
|
|
292
|
+
| sort -t"$_WM_TAB" -k1,1 -k2,2r | sort -t"$_WM_TAB" -k1,1 -u \
|
|
293
|
+
| awk -F"$_WM_TAB" '{ print tolower($1) "\t" $3 }' \
|
|
294
|
+
| jq -Rn '[inputs | split("\t") | select(length >= 2) | {(.[0]): (.[1] // "")}] | add // {}' 2>/dev/null || echo "{}")
|
|
295
|
+
[ -z "$WORK_MAP" ] && WORK_MAP="{}"
|
|
296
|
+
fi
|
|
297
|
+
|
|
298
|
+
# --- Merge git commits + graph + file presence + branches into presence array ---
|
|
299
|
+
# One jq pass. The previous version looped over the name union in bash,
|
|
300
|
+
# spawning ~8 jq/date processes per teammate — at 16+ teammates that was the
|
|
301
|
+
# single largest block of session-start's boot time (~5s). All epoch parsing,
|
|
302
|
+
# relative-time formatting, merging, and sorting now happens inside jq.
|
|
303
|
+
# Git commit date is authoritative (actual work shipped); graph/file presence
|
|
304
|
+
# are fallbacks only (session opens ≠ real activity).
|
|
305
|
+
NOW_EPOCH=$(date +%s)
|
|
306
|
+
PRESENCE=$(jq -nc \
|
|
307
|
+
--argjson graph "$GRAPH_DATA" \
|
|
308
|
+
--argjson git "$GIT_COMMIT_DATA" \
|
|
309
|
+
--argjson branches "$BRANCH_MAP" \
|
|
310
|
+
--argjson files "$FILE_PRESENCE" \
|
|
311
|
+
--argjson work "$WORK_MAP" \
|
|
312
|
+
--arg now "$NOW_EPOCH" --arg self "$SELF_LC" --arg selfdisp "$SELF_DISPLAY_LC" '
|
|
313
|
+
def iso2e:
|
|
314
|
+
if . == null or . == "" then 0
|
|
315
|
+
else (sub("Z$"; "") | sub("\\+00:00$"; "") | sub("\\.[0-9]+"; "")
|
|
316
|
+
| (if test("T") then . else . + "T00:00:00" end)
|
|
317
|
+
| (try (strptime("%Y-%m-%dT%H:%M:%S") | mktime) catch 0))
|
|
318
|
+
end;
|
|
319
|
+
def day: (try strflocaltime("%Y-%m-%d") catch (gmtime | strftime("%Y-%m-%d")));
|
|
320
|
+
def rel($nowe): . as $e |
|
|
321
|
+
if $e <= 0 then "--"
|
|
322
|
+
else ($nowe - $e) as $d |
|
|
323
|
+
if $d < 60 then "just now"
|
|
324
|
+
elif $d < 3600 then "\($d / 60 | floor)m ago"
|
|
325
|
+
elif $d < 86400 then "\($d / 3600 | floor)h ago"
|
|
326
|
+
elif ($e | day) == ($nowe | day) then "today"
|
|
327
|
+
else
|
|
328
|
+
((try (($nowe | day | strptime("%Y-%m-%d") | mktime)
|
|
329
|
+
- ($e | day | strptime("%Y-%m-%d") | mktime)) catch $d) / 86400
|
|
330
|
+
| floor | if . < 1 then 1 else . end) as $days |
|
|
331
|
+
if $days == 1 then "yesterday" else "\($days)d ago" end
|
|
332
|
+
end
|
|
333
|
+
end;
|
|
334
|
+
($now | tonumber) as $nowe |
|
|
335
|
+
($graph
|
|
336
|
+
| map({key: ((.name // "") | ascii_downcase), value: (.lastSeen // "")})
|
|
337
|
+
| group_by(.key)
|
|
338
|
+
| map({key: .[0].key, value: (map(.value) | max)})
|
|
339
|
+
| from_entries) as $g |
|
|
340
|
+
([($g, $files, $branches, $git) | keys[]] | map(ascii_downcase) | unique
|
|
341
|
+
| map(select(. != "" and . != $self and . != $selfdisp))) as $names |
|
|
342
|
+
[ $names[] | . as $n |
|
|
343
|
+
(($git[$n] // "0") | (try tonumber catch 0)) as $ge |
|
|
344
|
+
(if $ge > 0 then $ge
|
|
345
|
+
else ((($g[$n] // "") | iso2e) as $gge |
|
|
346
|
+
if $gge > 0 then $gge else (($files[$n] // "") | iso2e) end)
|
|
347
|
+
end) as $epoch |
|
|
348
|
+
($branches[$n] // []) as $b |
|
|
349
|
+
{ name: $n,
|
|
350
|
+
last_seen: ($epoch | rel($nowe)),
|
|
351
|
+
last_seen_sort: $epoch,
|
|
352
|
+
branches: (if ($b | length) > 2 then ($b[0:2] + ["+\(($b | length) - 2) more"]) else $b end),
|
|
353
|
+
working_on: ($work[$n] // "") }
|
|
354
|
+
] | sort_by(-.last_seen_sort)
|
|
355
|
+
' 2>/dev/null || echo "[]")
|
|
356
|
+
echo "${PRESENCE:-[]}" > "$CTX_DIR/team"
|
|
357
|
+
) &
|
|
358
|
+
|
|
359
|
+
# 5. Soul self-summary (background)
|
|
360
|
+
(
|
|
361
|
+
SUMMARY=""
|
|
362
|
+
if [ -f "$SCRIPT_DIR/egregore.md" ]; then
|
|
363
|
+
SUMMARY=$(sed -n '/^## Self-Summary/,/^##/p' "$SCRIPT_DIR/egregore.md" | sed '1d;/^##/d' | sed 's/^[[:space:]]*//' | tr '\n' ' ' | sed 's/[[:space:]]*$//')
|
|
364
|
+
fi
|
|
365
|
+
echo "$SUMMARY" > "$CTX_DIR/soul_summary"
|
|
366
|
+
) &
|
|
367
|
+
|
|
368
|
+
# 6. Handoffs addressed to user (background, enriched with author/topic)
|
|
369
|
+
(
|
|
370
|
+
JSON="[]"
|
|
371
|
+
RICH="[]"
|
|
372
|
+
if [ -d "$SCRIPT_DIR/memory/handoffs" ]; then
|
|
373
|
+
# One definition everywhere: in connected mode the list comes from the
|
|
374
|
+
# graph's open-handoffs named read (open = pending/unread/read/claimed,
|
|
375
|
+
# matched by name/github/aliases) — the same definition the dashboard
|
|
376
|
+
# uses. The file grep below stays as the local-mode/offline fallback.
|
|
377
|
+
ADDRESSED=""
|
|
378
|
+
if [ "${LOCAL_MODE:-false}" != "true" ]; then
|
|
379
|
+
_GRAPH_LIST=$(bash "$SCRIPT_DIR/bin/graph-op.sh" open-handoffs "$AUTHOR" 5 2>/dev/null || true)
|
|
380
|
+
if echo "$_GRAPH_LIST" | jq -e '.values | length > 0' >/dev/null 2>&1; then
|
|
381
|
+
ADDRESSED=$(echo "$_GRAPH_LIST" | jq -r '.values[][0]' 2>/dev/null | awk '!seen[$0]++' | while IFS= read -r _SID; do
|
|
382
|
+
# Two on-disk layouts: dated subdirs (YYYY-MM/DD-slug.md, the id's
|
|
383
|
+
# own shape) and flat files at the handoffs root. Probe both, else
|
|
384
|
+
# a graph-listed handoff silently vanishes from the greeting.
|
|
385
|
+
_MONTH="${_SID:0:7}"
|
|
386
|
+
_REST="${_SID:8}"
|
|
387
|
+
_F="$SCRIPT_DIR/memory/handoffs/$_MONTH/$_REST.md"
|
|
388
|
+
if [ -f "$_F" ]; then
|
|
389
|
+
echo "$_F"
|
|
390
|
+
elif [ -f "$SCRIPT_DIR/memory/handoffs/$_SID.md" ]; then
|
|
391
|
+
echo "$SCRIPT_DIR/memory/handoffs/$_SID.md"
|
|
392
|
+
fi
|
|
393
|
+
done | head -5)
|
|
394
|
+
fi
|
|
395
|
+
fi
|
|
396
|
+
# Match github username, display name, github_name first word, and people-file name
|
|
397
|
+
# Handoff files may use "to: name", "**To**: name", or "To: name"
|
|
398
|
+
_DISPLAY=$(jq -r '.display_name // empty' "$STATE_FILE" 2>/dev/null)
|
|
399
|
+
_GH_NAME=$(jq -r '.github_name // empty' "$STATE_FILE" 2>/dev/null)
|
|
400
|
+
_FIRST_NAME=$(echo "$_GH_NAME" | awk '{print $1}')
|
|
401
|
+
# Also check people file: memory/people/{author}.md has "# Name" on first line
|
|
402
|
+
_PEOPLE_NAME=""
|
|
403
|
+
_PEOPLE_FILE="$SCRIPT_DIR/memory/people/$(echo "$AUTHOR" | tr '[:upper:]' '[:lower:]').md"
|
|
404
|
+
[ -f "$_PEOPLE_FILE" ] && _PEOPLE_NAME=$(sed -n 's/^# *//p' "$_PEOPLE_FILE" 2>/dev/null | head -1)
|
|
405
|
+
_GREP_PAT="[Tt]o[*]*: *$AUTHOR\|[Tt]o[*]*:$AUTHOR"
|
|
406
|
+
for _VARIANT in "$_DISPLAY" "$_FIRST_NAME" "$_PEOPLE_NAME"; do
|
|
407
|
+
[ -z "$_VARIANT" ] && continue
|
|
408
|
+
[ "$_VARIANT" = "$AUTHOR" ] && continue
|
|
409
|
+
# Skip if already in pattern (avoid duplicates)
|
|
410
|
+
echo "$_GREP_PAT" | grep -qF "$_VARIANT" && continue
|
|
411
|
+
_GREP_PAT="$_GREP_PAT\|[Tt]o[*]*: *$_VARIANT\|[Tt]o[*]*:$_VARIANT"
|
|
412
|
+
done
|
|
413
|
+
if [ -z "$ADDRESSED" ]; then
|
|
414
|
+
ADDRESSED=$(grep -rli "$_GREP_PAT" "$SCRIPT_DIR/memory/handoffs/" 2>/dev/null | sort -r | head -5 || true)
|
|
415
|
+
fi
|
|
416
|
+
JSON="["
|
|
417
|
+
RICH="["
|
|
418
|
+
FIRST=true
|
|
419
|
+
for AF in $ADDRESSED; do
|
|
420
|
+
[ -z "$AF" ] && continue
|
|
421
|
+
AF_NAME=$(basename "$AF" .md)
|
|
422
|
+
AF_AUTHOR=$(eg_handoff_author "$AF")
|
|
423
|
+
AF_TOPIC=$(eg_handoff_topic "$AF")
|
|
424
|
+
AF_DATE=$(eg_handoff_date "$AF")
|
|
425
|
+
# Extract repo state table if present
|
|
426
|
+
AF_REPO_STATE="[]"
|
|
427
|
+
AF_REPO_TABLE=$(awk '/^## Repo State/{found=1; next} found && /^#/{exit} found && /^\|[^-]/ && !/^\| Repo/{print}' "$AF" 2>/dev/null || true)
|
|
428
|
+
if [ -n "$AF_REPO_TABLE" ]; then
|
|
429
|
+
AF_REPO_STATE=$(echo "$AF_REPO_TABLE" | while IFS='|' read -r _ R_REPO R_BRANCH R_PR R_BASE _; do
|
|
430
|
+
R_REPO=$(echo "$R_REPO" | xargs 2>/dev/null || true)
|
|
431
|
+
R_BRANCH=$(echo "$R_BRANCH" | xargs 2>/dev/null || true)
|
|
432
|
+
R_PR=$(echo "$R_PR" | xargs 2>/dev/null | sed 's/^#//' | sed 's/—//' || true)
|
|
433
|
+
R_BASE=$(echo "$R_BASE" | xargs 2>/dev/null || true)
|
|
434
|
+
[ -z "$R_REPO" ] && continue
|
|
435
|
+
if [ -n "$R_PR" ] && [ "$R_PR" -eq "$R_PR" ] 2>/dev/null; then
|
|
436
|
+
printf '{"repo":"%s","branch":"%s","pr":%s,"base":"%s"}\n' "$R_REPO" "$R_BRANCH" "$R_PR" "$R_BASE"
|
|
437
|
+
else
|
|
438
|
+
printf '{"repo":"%s","branch":"%s","pr":null,"base":"%s"}\n' "$R_REPO" "$R_BRANCH" "$R_BASE"
|
|
439
|
+
fi
|
|
440
|
+
done | jq -sc '.' 2>/dev/null || echo "[]")
|
|
441
|
+
fi
|
|
442
|
+
$FIRST || JSON="$JSON,"
|
|
443
|
+
$FIRST || RICH="$RICH,"
|
|
444
|
+
JSON="$JSON\"$AF_NAME\""
|
|
445
|
+
AF_ENTRY=$(jq -cn \
|
|
446
|
+
--arg name "$AF_NAME" \
|
|
447
|
+
--arg author "$AF_AUTHOR" \
|
|
448
|
+
--arg topic "$AF_TOPIC" \
|
|
449
|
+
--arg date "$AF_DATE" \
|
|
450
|
+
--argjson repoState "$AF_REPO_STATE" \
|
|
451
|
+
'{name:$name, author:$author, topic:$topic, date:$date, repoState:$repoState}' 2>/dev/null || echo "{}")
|
|
452
|
+
RICH="$RICH$AF_ENTRY"
|
|
453
|
+
FIRST=false
|
|
454
|
+
done
|
|
455
|
+
JSON="$JSON]"
|
|
456
|
+
RICH="$RICH]"
|
|
457
|
+
fi
|
|
458
|
+
echo "$JSON" > "$CTX_DIR/addressed"
|
|
459
|
+
echo "$RICH" > "$CTX_DIR/addressed_rich"
|
|
460
|
+
) &
|
|
461
|
+
|
|
462
|
+
# 6.5. Pending questions addressed to user (background)
|
|
463
|
+
(
|
|
464
|
+
PENDING_JSON="[]"
|
|
465
|
+
QDIR="$SCRIPT_DIR/memory/knowledge/questions"
|
|
466
|
+
if [ -d "$QDIR" ]; then
|
|
467
|
+
# Match against the same identity variants used for handoffs (display name,
|
|
468
|
+
# github first-name, people-file name) so "to: oz" matches author "oguzhan".
|
|
469
|
+
_DISPLAY_Q=$(jq -r '.display_name // empty' "$STATE_FILE" 2>/dev/null)
|
|
470
|
+
_GH_NAME_Q=$(jq -r '.github_name // empty' "$STATE_FILE" 2>/dev/null)
|
|
471
|
+
_FIRST_NAME_Q=$(echo "$_GH_NAME_Q" | awk '{print tolower($1)}')
|
|
472
|
+
_PEOPLE_FILE_Q="$SCRIPT_DIR/memory/people/$(echo "$AUTHOR" | tr '[:upper:]' '[:lower:]').md"
|
|
473
|
+
_PEOPLE_NAME_Q=""
|
|
474
|
+
[ -f "$_PEOPLE_FILE_Q" ] && _PEOPLE_NAME_Q=$(sed -n 's/^# *//p' "$_PEOPLE_FILE_Q" 2>/dev/null | head -1 | awk '{print tolower($1)}')
|
|
475
|
+
|
|
476
|
+
LINES=""
|
|
477
|
+
for QF in "$QDIR"/*.md; do
|
|
478
|
+
[ -f "$QF" ] || continue
|
|
479
|
+
Q_TO=$(sed -n 's/^to:[[:space:]]*//p' "$QF" 2>/dev/null | head -1 | tr -d '"' | tr '[:upper:]' '[:lower:]' | awk '{print $1}')
|
|
480
|
+
[ -z "$Q_TO" ] && continue
|
|
481
|
+
_MATCH="false"
|
|
482
|
+
for _V in "$AUTHOR" "$_DISPLAY_Q" "$_FIRST_NAME_Q" "$_PEOPLE_NAME_Q"; do
|
|
483
|
+
[ -z "$_V" ] && continue
|
|
484
|
+
_V_LC=$(echo "$_V" | tr '[:upper:]' '[:lower:]')
|
|
485
|
+
[ "$Q_TO" = "$_V_LC" ] && _MATCH="true" && break
|
|
486
|
+
done
|
|
487
|
+
[ "$_MATCH" = "true" ] || continue
|
|
488
|
+
Q_STATUS=$(sed -n 's/^status:[[:space:]]*//p' "$QF" 2>/dev/null | head -1 | tr -d '"')
|
|
489
|
+
[ "$Q_STATUS" = "pending" ] || continue
|
|
490
|
+
Q_FROM=$(sed -n 's/^from:[[:space:]]*//p' "$QF" 2>/dev/null | head -1 | tr -d '"')
|
|
491
|
+
Q_TOPIC=$(sed -n 's/^topic:[[:space:]]*//p' "$QF" 2>/dev/null | head -1 | tr -d '"')
|
|
492
|
+
Q_HID=$(sed -n 's/^harvest_id:[[:space:]]*//p' "$QF" 2>/dev/null | head -1 | tr -d '"')
|
|
493
|
+
Q_REL="${QF#$SCRIPT_DIR/}"
|
|
494
|
+
LINE=$(jq -nc \
|
|
495
|
+
--arg from "$Q_FROM" \
|
|
496
|
+
--arg topic "$Q_TOPIC" \
|
|
497
|
+
--arg hid "$Q_HID" \
|
|
498
|
+
--arg file "$Q_REL" \
|
|
499
|
+
'{from:$from, topic:$topic, harvest_id:$hid, file:$file}' 2>/dev/null)
|
|
500
|
+
[ -n "$LINE" ] && LINES="$LINES$LINE
|
|
501
|
+
"
|
|
502
|
+
done
|
|
503
|
+
if [ -n "$LINES" ]; then
|
|
504
|
+
PENDING_JSON=$(echo "$LINES" | jq -sc '.' 2>/dev/null || echo "[]")
|
|
505
|
+
fi
|
|
506
|
+
fi
|
|
507
|
+
echo "$PENDING_JSON" > "$CTX_DIR/pending_questions"
|
|
508
|
+
) &
|
|
509
|
+
|
|
510
|
+
# 7. Graph health (background — zero added latency, runs in parallel; seeded)
|
|
511
|
+
[ "$CTX_SEED_USED" != "true" ] && (
|
|
512
|
+
if [ "$LOCAL_MODE" = "true" ]; then
|
|
513
|
+
echo "skip"
|
|
514
|
+
elif bash "$SCRIPT_DIR/bin/graph.sh" test 2>/dev/null | grep -q "Connected"; then
|
|
515
|
+
echo "ok"
|
|
516
|
+
else
|
|
517
|
+
echo "fail"
|
|
518
|
+
fi
|
|
519
|
+
) > "$CTX_DIR/graph_health" 2>/dev/null &
|
|
520
|
+
|
|
521
|
+
# 8. Telegram health (background)
|
|
522
|
+
# notify.sh test emits JSON, not prose: {"status":"ok"} connected,
|
|
523
|
+
# {"status":"configured"} local, {"status":"offline"} otherwise. Match the
|
|
524
|
+
# status field — an earlier grep for the word "connected" read a human sentence
|
|
525
|
+
# that the consent rewrite removed, so the footer showed telegram ✗ while the
|
|
526
|
+
# bot was answering.
|
|
527
|
+
[ "$CTX_SEED_USED" != "true" ] && (
|
|
528
|
+
if [ "$LOCAL_MODE" = "true" ]; then
|
|
529
|
+
echo "skip"
|
|
530
|
+
elif bash "$SCRIPT_DIR/bin/notify.sh" test 2>/dev/null \
|
|
531
|
+
| jq -e '.status == "ok" or .status == "configured"' >/dev/null 2>&1; then
|
|
532
|
+
echo "ok"
|
|
533
|
+
else
|
|
534
|
+
echo "fail"
|
|
535
|
+
fi
|
|
536
|
+
) > "$CTX_DIR/telegram_health" 2>/dev/null &
|
|
537
|
+
|
|
538
|
+
# 9. Lifecycle events: merged PRs + implemented handoffs (background; seeded)
|
|
539
|
+
[ "$CTX_SEED_USED" != "true" ] && (
|
|
540
|
+
GH_USER_LC=""
|
|
541
|
+
if [ -f "$STATE_FILE" ]; then
|
|
542
|
+
GH_USER_LC=$(jq -r '.github_username // empty' "$STATE_FILE" 2>/dev/null)
|
|
543
|
+
fi
|
|
544
|
+
DISPLAY_NAME_LC=""
|
|
545
|
+
if [ -f "$STATE_FILE" ]; then
|
|
546
|
+
DISPLAY_NAME_LC=$(jq -r '.display_name // empty' "$STATE_FILE" 2>/dev/null)
|
|
547
|
+
fi
|
|
548
|
+
# Get last session end date (use 7 days ago as fallback)
|
|
549
|
+
LAST_END=$(bash "$SCRIPT_DIR/bin/graph.sh" query "
|
|
550
|
+
MATCH (s:Session)-[:BY]->(p:Person {github: \$gh})
|
|
551
|
+
WHERE s.wrappedAt IS NOT NULL
|
|
552
|
+
RETURN toString(s.wrappedAt) AS t
|
|
553
|
+
ORDER BY s.wrappedAt DESC SKIP 1 LIMIT 1
|
|
554
|
+
" "$(jq -n --arg gh "$GH_USER_LC" '{gh: $gh}')" 2>/dev/null | jq -r '.values[0][0] // empty' 2>/dev/null)
|
|
555
|
+
if [ -z "$LAST_END" ]; then
|
|
556
|
+
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"
|
|
557
|
+
fi
|
|
558
|
+
|
|
559
|
+
MERGED_JSON="[]"
|
|
560
|
+
if [ -n "$GH_USER_LC" ]; then
|
|
561
|
+
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 "[]")
|
|
562
|
+
fi
|
|
563
|
+
|
|
564
|
+
IMPL_JSON="[]"
|
|
565
|
+
IMPL_NAME="${DISPLAY_NAME_LC:-$GH_USER_LC}"
|
|
566
|
+
if [ -n "$IMPL_NAME" ]; then
|
|
567
|
+
IMPL_JSON=$(bash "$SCRIPT_DIR/bin/graph-op.sh" my-implemented-handoffs "$IMPL_NAME" "$(echo "$LAST_END" | cut -dT -f1)" 2>/dev/null || echo "[]")
|
|
568
|
+
fi
|
|
569
|
+
|
|
570
|
+
jq -n --argjson merged "$MERGED_JSON" --argjson impl "$IMPL_JSON" \
|
|
571
|
+
'{merged_prs: $merged, implemented_handoffs: $impl}' 2>/dev/null || echo '{"merged_prs":[],"implemented_handoffs":[]}'
|
|
572
|
+
) > "$CTX_DIR/lifecycle" 2>/dev/null &
|
|
573
|
+
|
|
574
|
+
# 10. Last Pulse brief (background) — gated on feature flag + API key; seeded
|
|
575
|
+
[ "$CTX_SEED_USED" != "true" ] && (
|
|
576
|
+
PULSE_ENABLED=$(jq -r '.features.pulse // "false"' "$CONFIG" 2>/dev/null || echo "false")
|
|
577
|
+
HAS_API_KEY=false
|
|
578
|
+
_env_file="${ENV_FILE:-$SCRIPT_DIR/.env}"
|
|
579
|
+
if [ -f "$_env_file" ]; then
|
|
580
|
+
_key=$(grep '^EGREGORE_API_KEY=' "$_env_file" 2>/dev/null | cut -d'=' -f2- || true)
|
|
581
|
+
[ -n "$_key" ] && HAS_API_KEY=true
|
|
582
|
+
fi
|
|
583
|
+
|
|
584
|
+
if [ "$PULSE_ENABLED" = "true" ] && [ "$HAS_API_KEY" = "true" ] && [ -n "$GH_USER_LC" ]; then
|
|
585
|
+
BRIEF_RESULT=$(bash "$SCRIPT_DIR/bin/graph.sh" query "
|
|
586
|
+
MATCH (p:Person {github: \$gh})
|
|
587
|
+
WHERE p.lastBrief IS NOT NULL
|
|
588
|
+
AND p.lastBriefDate >= date() - duration('P7D')
|
|
589
|
+
RETURN p.lastBrief, toString(p.lastBriefDate), p.lastRecommendations
|
|
590
|
+
" "$(jq -n --arg gh "$GH_USER_LC" '{gh: $gh}')" 2>/dev/null || echo '{"values":[]}')
|
|
591
|
+
BRIEF=$(echo "$BRIEF_RESULT" | jq -r '.values[0][0] // empty' 2>/dev/null || true)
|
|
592
|
+
BRIEF_DATE=$(echo "$BRIEF_RESULT" | jq -r '.values[0][1] // empty' 2>/dev/null || true)
|
|
593
|
+
RECS=$(echo "$BRIEF_RESULT" | jq -c '.values[0][2] // []' 2>/dev/null || echo '[]')
|
|
594
|
+
if [ -n "$BRIEF" ]; then
|
|
595
|
+
jq -n --arg brief "$BRIEF" --arg date "$BRIEF_DATE" --argjson recs "$RECS" \
|
|
596
|
+
'{brief: $brief, date: $date, recommendations: $recs}'
|
|
597
|
+
else
|
|
598
|
+
echo '{}'
|
|
599
|
+
fi
|
|
600
|
+
else
|
|
601
|
+
echo '{}'
|
|
602
|
+
fi
|
|
603
|
+
) > "$CTX_DIR/pulse_brief" 2>/dev/null &
|
|
604
|
+
|
|
605
|
+
# 11. Momentum metrics (background)
|
|
606
|
+
# Always live, even when seeded: _metrics_gather mixes graph reads (which ride
|
|
607
|
+
# the warm cache) with local git/file scans — sessions, commits, handoffs,
|
|
608
|
+
# knowledge — whose freshness the greeting should reflect this instant.
|
|
609
|
+
(
|
|
610
|
+
source "$SCRIPT_DIR/bin/lib/metrics.sh"
|
|
611
|
+
_metrics_gather
|
|
612
|
+
) > "$CTX_DIR/metrics" 2>/dev/null &
|
|
613
|
+
|
|
614
|
+
# Wait for all context gathering + health checks to finish
|
|
615
|
+
wait
|
|
616
|
+
|
|
617
|
+
# --- Read service health results ---
|
|
618
|
+
GRAPH_RESULT=$(cat "$CTX_DIR/graph_health" 2>/dev/null || echo "")
|
|
619
|
+
if [ -n "$GRAPH_RESULT" ]; then HEALTH_GRAPH="$GRAPH_RESULT"; fi
|
|
620
|
+
TELEGRAM_RESULT=$(cat "$CTX_DIR/telegram_health" 2>/dev/null || echo "")
|
|
621
|
+
if [ -n "$TELEGRAM_RESULT" ]; then HEALTH_TELEGRAM="$TELEGRAM_RESULT"; fi
|