create-egregore 0.19.23 → 0.19.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/egregore-launcher.js +50 -7
- package/assets/egregore-launcher.sh +10 -2
- package/bin/cli.js +21 -3
- package/lib/codex-runtime.js +25 -3
- package/lib/connect-runtime.js +329 -0
- package/lib/pi-runtime.js +23 -3
- package/lib/setup.js +13 -0
- package/package.json +3 -3
- package/runtime/codex/.codex/skills/handoff/SKILL.md +35 -11
- package/runtime/codex/.codex/skills/save/SKILL.md +39 -4
- package/runtime/codex/.codex/spec-manifest.json +2 -2
- package/runtime/codex/AGENTS.md +18 -1
- package/runtime/codex/bin/agent.sh +28 -14
- package/runtime/codex/bin/attendant.sh +169 -6
- package/runtime/codex/bin/codex-render-spec.mjs +18 -1
- package/runtime/codex/bin/codex-session-start.sh +13 -0
- package/runtime/codex/bin/codex-sync-skills.sh +1 -0
- package/runtime/codex/bin/graph.sh +21 -1
- package/runtime/codex/bin/handoff-run.sh +25 -5
- package/runtime/codex/bin/lib/context.sh +220 -172
- package/runtime/codex/bin/lib/dashboard-artifact.sh +113 -13
- package/runtime/codex/bin/lib/git-sync.sh +44 -8
- package/runtime/codex/bin/lib/greeting.sh +34 -11
- package/runtime/codex/bin/lib/identity.sh +32 -14
- package/runtime/codex/bin/person.py +20 -0
- package/runtime/codex/manifest.json +3 -72
- package/runtime/pi/.codex/skills/handoff/SKILL.md +35 -11
- package/runtime/pi/.codex/skills/save/SKILL.md +39 -4
- package/runtime/pi/.codex/spec-manifest.json +2 -2
- package/runtime/pi/.pi/APPEND_SYSTEM.md +18 -1
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +18 -1
- package/runtime/pi/bin/agent.sh +28 -14
- package/runtime/pi/bin/attendant.sh +169 -6
- package/runtime/pi/bin/codex-render-spec.mjs +18 -1
- package/runtime/pi/bin/codex-session-start.sh +13 -0
- package/runtime/pi/bin/codex-sync-skills.sh +1 -0
- package/runtime/pi/bin/graph.sh +21 -1
- package/runtime/pi/bin/handoff-run.sh +25 -5
- package/runtime/pi/bin/lib/context.sh +220 -172
- package/runtime/pi/bin/lib/dashboard-artifact.sh +113 -13
- package/runtime/pi/bin/lib/git-sync.sh +44 -8
- package/runtime/pi/bin/lib/greeting.sh +34 -11
- package/runtime/pi/bin/lib/identity.sh +32 -14
- package/runtime/pi/bin/person.py +20 -0
- package/runtime/pi/bin/pi-product-workflows.mjs +5 -1
- package/runtime/pi/manifest.json +3 -72
- package/runtime/codex/.codex/skills/add/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/answer/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/ask/SKILL.md +0 -67
- package/runtime/codex/.codex/skills/audit/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/autosave/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/branch/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/commit/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/demo-mode/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/deploy-bridge/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/design/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/egregore-voice/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/emissary/SKILL.md +0 -45
- package/runtime/codex/.codex/skills/env/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/ingest-google/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/minion/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/mock/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/pr/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/product-voice/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/project/SKILL.md +0 -45
- package/runtime/codex/.codex/skills/pull/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/push/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/quest-suggest/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/review-pr/SKILL.md +0 -45
- package/runtime/codex/.codex/skills/scroll/SKILL.md +0 -288
- package/runtime/codex/.codex/skills/scroll/agents/openai.yaml +0 -4
- package/runtime/codex/.codex/skills/scroll/assets/shell.html +0 -871
- package/runtime/codex/.codex/skills/search/SKILL.md +0 -79
- package/runtime/codex/.codex/skills/session-view/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/setup/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/summon/SKILL.md +0 -45
- package/runtime/codex/.codex/skills/task/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/teams-connect/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/the-spiral/SKILL.md +0 -84
- package/runtime/codex/.codex/skills/thread/SKILL.md +0 -29
- package/runtime/codex/.codex/skills/todo/SKILL.md +0 -45
- package/runtime/pi/.codex/skills/add/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/answer/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/ask/SKILL.md +0 -67
- package/runtime/pi/.codex/skills/audit/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/autosave/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/branch/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/commit/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/demo-mode/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/deploy-bridge/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/design/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/egregore-voice/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/emissary/SKILL.md +0 -45
- package/runtime/pi/.codex/skills/env/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/ingest-google/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/minion/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/mock/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/pr/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/product-voice/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/project/SKILL.md +0 -45
- package/runtime/pi/.codex/skills/pull/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/push/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/quest-suggest/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/review-pr/SKILL.md +0 -45
- package/runtime/pi/.codex/skills/scroll/SKILL.md +0 -288
- package/runtime/pi/.codex/skills/scroll/agents/openai.yaml +0 -4
- package/runtime/pi/.codex/skills/scroll/assets/shell.html +0 -871
- package/runtime/pi/.codex/skills/search/SKILL.md +0 -79
- package/runtime/pi/.codex/skills/session-view/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/setup/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/summon/SKILL.md +0 -45
- package/runtime/pi/.codex/skills/task/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/teams-connect/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/telegram-connect/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/the-spiral/SKILL.md +0 -84
- package/runtime/pi/.codex/skills/thread/SKILL.md +0 -29
- package/runtime/pi/.codex/skills/todo/SKILL.md +0 -45
|
@@ -33,6 +33,71 @@ elif [ "$HOUR" -lt 17 ]; then TIME_OF_DAY="afternoon"
|
|
|
33
33
|
else TIME_OF_DAY="evening"
|
|
34
34
|
fi
|
|
35
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
|
+
|
|
36
101
|
# 1. Recent handoffs (background)
|
|
37
102
|
(
|
|
38
103
|
JSON="[]"
|
|
@@ -61,7 +126,7 @@ fi
|
|
|
61
126
|
FIRST=true
|
|
62
127
|
for F in "$SCRIPT_DIR/memory/quests/"*.md; do
|
|
63
128
|
[ -e "$F" ] || continue
|
|
64
|
-
|
|
129
|
+
case "$F" in *draft*) continue ;; esac
|
|
65
130
|
NAME=$(basename "$F" .md)
|
|
66
131
|
$FIRST || JSON="$JSON,"
|
|
67
132
|
JSON="$JSON\"$NAME\""
|
|
@@ -77,8 +142,8 @@ fi
|
|
|
77
142
|
git log --author="$AUTHOR" --format="%ar|%s" -1 2>/dev/null > "$CTX_DIR/activity" || echo "" > "$CTX_DIR/activity"
|
|
78
143
|
) &
|
|
79
144
|
|
|
80
|
-
# 3b. Personal todos from graph (background)
|
|
81
|
-
(
|
|
145
|
+
# 3b. Personal todos from graph (background; seeded)
|
|
146
|
+
[ "$CTX_SEED_USED" != "true" ] && (
|
|
82
147
|
_API_URL=$(jq -r '.api_url // empty' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
|
|
83
148
|
_API_KEY=$(grep '^EGREGORE_API_KEY=' "${ENV_FILE:-$SCRIPT_DIR/.env}" 2>/dev/null | cut -d'=' -f2-)
|
|
84
149
|
if [ -n "$_API_URL" ] && [ -n "$_API_KEY" ]; then
|
|
@@ -96,6 +161,9 @@ fi
|
|
|
96
161
|
) > "$CTX_DIR/todos" 2>/dev/null &
|
|
97
162
|
|
|
98
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.
|
|
99
167
|
(
|
|
100
168
|
# --- Read config inside subshell ---
|
|
101
169
|
_API_URL=$(jq -r '.api_url // empty' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
|
|
@@ -104,7 +172,13 @@ fi
|
|
|
104
172
|
# --- Graph: last-seen per person (excluding self) ---
|
|
105
173
|
GRAPH_DATA="[]"
|
|
106
174
|
if [ -n "$_API_URL" ] && [ -n "$_API_KEY" ]; then
|
|
107
|
-
|
|
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"
|
|
108
182
|
GRAPH_RAW=$(bash "$SCRIPT_DIR/bin/graph.sh" query "$CYPHER" "$(jq -n --arg me "$AUTHOR" '{me: $me}')" 2>/dev/null || echo "")
|
|
109
183
|
if [ -n "$GRAPH_RAW" ]; then
|
|
110
184
|
GRAPH_DATA=$(echo "$GRAPH_RAW" | jq '[.values[] | {name: .[0], lastSeen: .[1]}]' 2>/dev/null || echo "[]")
|
|
@@ -112,19 +186,26 @@ fi
|
|
|
112
186
|
fi
|
|
113
187
|
|
|
114
188
|
# --- Git: branch tip commit dates per person (primary recency signal) ---
|
|
115
|
-
# git for-each-ref is fast and gives the actual last commit date per branch
|
|
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.
|
|
116
193
|
GIT_COMMIT_DATA="{}"
|
|
117
194
|
GIT_REF_DATA=$(git -C "$SCRIPT_DIR" for-each-ref --sort=-committerdate \
|
|
118
195
|
--format='%(refname:short)|%(committerdate:unix)' refs/remotes/origin/dev/ 2>/dev/null || echo "")
|
|
119
196
|
if [ -n "$GIT_REF_DATA" ]; then
|
|
120
|
-
GIT_COMMIT_DATA=$(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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 '
|
|
125
205
|
[inputs | split("|") | {(.[0]): .[1]}]
|
|
126
206
|
| add // {}
|
|
127
207
|
' 2>/dev/null || echo "{}")
|
|
208
|
+
[ -z "$GIT_COMMIT_DATA" ] && GIT_COMMIT_DATA="{}"
|
|
128
209
|
fi
|
|
129
210
|
|
|
130
211
|
# --- Git: active dev/* branches (excluding self) ---
|
|
@@ -137,178 +218,142 @@ fi
|
|
|
137
218
|
|
|
138
219
|
BRANCH_MAP="{}"
|
|
139
220
|
if [ -n "$BRANCH_DATA" ]; then
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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("|"))}]
|
|
149
233
|
| group_by(.author)
|
|
150
234
|
| map({(.[0].author): [.[] | .branch]})
|
|
151
235
|
| add // {}
|
|
152
236
|
' 2>/dev/null || echo "{}")
|
|
237
|
+
[ -z "$BRANCH_MAP" ] && BRANCH_MAP="{}"
|
|
153
238
|
fi
|
|
154
239
|
|
|
155
240
|
# --- Local-mode presence: scan session + wrap files for lastSeen ---
|
|
156
|
-
# When graph has no data, these files provide temporal presence information
|
|
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.
|
|
157
244
|
FILE_PRESENCE="{}"
|
|
158
245
|
if [ "$GRAPH_DATA" = "[]" ]; then
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
' 2>/dev/null || echo "{}"
|
|
180
|
-
)
|
|
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
|
|
181
266
|
fi
|
|
182
267
|
|
|
183
268
|
# --- "Working on": latest session/handoff/wrap topic per person (file-based) ---
|
|
184
269
|
# Gives every team surface (greeting "around", dashboard) something legible in
|
|
185
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.
|
|
186
273
|
_WM_TAB=$(printf '\t')
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
NOW_EPOCH=$(date +%s)
|
|
210
|
-
|
|
211
|
-
iso_to_epoch() {
|
|
212
|
-
local iso="$1"
|
|
213
|
-
local clean=$(echo "$iso" | sed 's/Z$//; s/+00:00$//; s/\.[0-9]*//')
|
|
214
|
-
[[ "$clean" != *T* ]] && clean="${clean}T00:00:00"
|
|
215
|
-
TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "$clean" "+%s" 2>/dev/null || \
|
|
216
|
-
TZ=UTC date -d "$clean" +%s 2>/dev/null || echo "0"
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
epoch_to_relative() {
|
|
220
|
-
local epoch="$1"
|
|
221
|
-
[ "$epoch" = "0" ] && echo "--" && return
|
|
222
|
-
local delta=$(( NOW_EPOCH - epoch ))
|
|
223
|
-
if [ "$delta" -lt 60 ]; then echo "just now"
|
|
224
|
-
elif [ "$delta" -lt 3600 ]; then echo "$(( delta / 60 ))m ago"
|
|
225
|
-
elif [ "$delta" -lt 86400 ]; then echo "$(( delta / 3600 ))h ago"
|
|
226
|
-
else
|
|
227
|
-
# Calendar-day based comparison for accurate "yesterday" / "Xd ago"
|
|
228
|
-
local event_date today_date
|
|
229
|
-
event_date=$(date -r "$epoch" +%Y-%m-%d 2>/dev/null || \
|
|
230
|
-
date -d "@$epoch" +%Y-%m-%d 2>/dev/null || echo "")
|
|
231
|
-
today_date=$(date +%Y-%m-%d)
|
|
232
|
-
if [ -z "$event_date" ] || [ "$event_date" = "$today_date" ]; then echo "today"
|
|
233
|
-
else
|
|
234
|
-
local today_e event_e
|
|
235
|
-
today_e=$(date -j -f "%Y-%m-%d" "$today_date" +%s 2>/dev/null || \
|
|
236
|
-
date -d "$today_date" +%s 2>/dev/null || echo "$NOW_EPOCH")
|
|
237
|
-
event_e=$(date -j -f "%Y-%m-%d" "$event_date" +%s 2>/dev/null || \
|
|
238
|
-
date -d "$event_date" +%s 2>/dev/null || echo "$epoch")
|
|
239
|
-
local days=$(( (today_e - event_e) / 86400 ))
|
|
240
|
-
[ "$days" -lt 1 ] && days=1
|
|
241
|
-
if [ "$days" -eq 1 ]; then echo "yesterday"
|
|
242
|
-
else echo "${days}d ago"
|
|
243
|
-
fi
|
|
244
|
-
fi
|
|
245
|
-
fi
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
# --- Merge git commits + graph + file presence + branches into presence array ---
|
|
249
|
-
ALL_NAMES=$(echo "$GRAPH_DATA" | jq -r '.[].name' 2>/dev/null || echo "")
|
|
250
|
-
FILE_NAMES=$(echo "$FILE_PRESENCE" | jq -r 'keys[]' 2>/dev/null || echo "")
|
|
251
|
-
BRANCH_NAMES=$(echo "$BRANCH_MAP" | jq -r 'keys[]' 2>/dev/null || echo "")
|
|
252
|
-
GIT_NAMES=$(echo "$GIT_COMMIT_DATA" | jq -r 'keys[]' 2>/dev/null || echo "")
|
|
253
|
-
|
|
254
|
-
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 "")
|
|
255
|
-
|
|
256
|
-
if [ -z "$UNION_NAMES" ]; then
|
|
257
|
-
echo "[]" > "$CTX_DIR/team"
|
|
258
|
-
exit 0
|
|
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="{}"
|
|
259
296
|
fi
|
|
260
297
|
|
|
261
|
-
# ---
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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"
|
|
312
357
|
) &
|
|
313
358
|
|
|
314
359
|
# 5. Soul self-summary (background)
|
|
@@ -437,8 +482,8 @@ fi
|
|
|
437
482
|
echo "$PENDING_JSON" > "$CTX_DIR/pending_questions"
|
|
438
483
|
) &
|
|
439
484
|
|
|
440
|
-
# 7. Graph health (background — zero added latency, runs in parallel)
|
|
441
|
-
(
|
|
485
|
+
# 7. Graph health (background — zero added latency, runs in parallel; seeded)
|
|
486
|
+
[ "$CTX_SEED_USED" != "true" ] && (
|
|
442
487
|
if [ "$LOCAL_MODE" = "true" ]; then
|
|
443
488
|
echo "skip"
|
|
444
489
|
elif bash "$SCRIPT_DIR/bin/graph.sh" test 2>/dev/null | grep -q "Connected"; then
|
|
@@ -454,7 +499,7 @@ fi
|
|
|
454
499
|
# status field — an earlier grep for the word "connected" read a human sentence
|
|
455
500
|
# that the consent rewrite removed, so the footer showed telegram ✗ while the
|
|
456
501
|
# bot was answering.
|
|
457
|
-
(
|
|
502
|
+
[ "$CTX_SEED_USED" != "true" ] && (
|
|
458
503
|
if [ "$LOCAL_MODE" = "true" ]; then
|
|
459
504
|
echo "skip"
|
|
460
505
|
elif bash "$SCRIPT_DIR/bin/notify.sh" test 2>/dev/null \
|
|
@@ -465,8 +510,8 @@ fi
|
|
|
465
510
|
fi
|
|
466
511
|
) > "$CTX_DIR/telegram_health" 2>/dev/null &
|
|
467
512
|
|
|
468
|
-
# 9. Lifecycle events: merged PRs + implemented handoffs (background)
|
|
469
|
-
(
|
|
513
|
+
# 9. Lifecycle events: merged PRs + implemented handoffs (background; seeded)
|
|
514
|
+
[ "$CTX_SEED_USED" != "true" ] && (
|
|
470
515
|
GH_USER_LC=""
|
|
471
516
|
if [ -f "$STATE_FILE" ]; then
|
|
472
517
|
GH_USER_LC=$(jq -r '.github_username // empty' "$STATE_FILE" 2>/dev/null)
|
|
@@ -501,8 +546,8 @@ fi
|
|
|
501
546
|
'{merged_prs: $merged, implemented_handoffs: $impl}' 2>/dev/null || echo '{"merged_prs":[],"implemented_handoffs":[]}'
|
|
502
547
|
) > "$CTX_DIR/lifecycle" 2>/dev/null &
|
|
503
548
|
|
|
504
|
-
# 10. Last Pulse brief (background) — gated on feature flag + API key
|
|
505
|
-
(
|
|
549
|
+
# 10. Last Pulse brief (background) — gated on feature flag + API key; seeded
|
|
550
|
+
[ "$CTX_SEED_USED" != "true" ] && (
|
|
506
551
|
PULSE_ENABLED=$(jq -r '.features.pulse // "false"' "$CONFIG" 2>/dev/null || echo "false")
|
|
507
552
|
HAS_API_KEY=false
|
|
508
553
|
_env_file="${ENV_FILE:-$SCRIPT_DIR/.env}"
|
|
@@ -533,6 +578,9 @@ fi
|
|
|
533
578
|
) > "$CTX_DIR/pulse_brief" 2>/dev/null &
|
|
534
579
|
|
|
535
580
|
# 11. Momentum metrics (background)
|
|
581
|
+
# Always live, even when seeded: _metrics_gather mixes graph reads (which ride
|
|
582
|
+
# the warm cache) with local git/file scans — sessions, commits, handoffs,
|
|
583
|
+
# knowledge — whose freshness the greeting should reflect this instant.
|
|
536
584
|
(
|
|
537
585
|
source "$SCRIPT_DIR/bin/lib/metrics.sh"
|
|
538
586
|
_metrics_gather
|