create-egregore 0.17.4 → 0.18.1
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 +2 -2
- 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 +616 -189
- package/lib/setup.js +822 -84
- package/lib/ui.js +780 -84
- package/package.json +12 -3
- 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/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/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 +40 -0
- package/runtime/codex/bin/activity-data.sh +177 -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 +174 -0
- package/runtime/codex/bin/graph-batch.sh +60 -0
- package/runtime/codex/bin/graph-op.sh +380 -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 +431 -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 +463 -0
- package/runtime/codex/bin/lib/dashboard-artifact.sh +142 -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 +414 -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 +173 -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 +118 -0
- package/runtime/codex/bin/worktree-remove.sh +50 -0
- package/runtime/codex/bin/worktree.sh +198 -0
- package/runtime/codex/manifest.json +155 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
# handoff-run.sh — atomic orchestrator for /handoff.
|
|
4
|
+
#
|
|
5
|
+
# Everything mechanical happens here, in one bash process, so the main
|
|
6
|
+
# session renders a single collapsed Bash tool block instead of 6+.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# bash bin/handoff-run.sh \
|
|
10
|
+
# --author <handle> \
|
|
11
|
+
# --topic "<short topic>" \
|
|
12
|
+
# [--recipient <name>] \
|
|
13
|
+
# [--project <name>] \
|
|
14
|
+
# <<'HANDOFFEOF'
|
|
15
|
+
# ...full markdown body of the handoff...
|
|
16
|
+
# HANDOFFEOF
|
|
17
|
+
#
|
|
18
|
+
# The full file body is read from stdin. The script:
|
|
19
|
+
# 1. Computes the file path: memory/handoffs/YYYY-MM/DD-author-slug.md
|
|
20
|
+
# 2. Writes the file
|
|
21
|
+
# 3. Appends ## Repo State section (from bin/repo-state.sh) if non-empty
|
|
22
|
+
# 4. Prepends memory/handoffs/index.md with a new entry
|
|
23
|
+
# 5. Runs bin/index-handoff.sh (connected mode only) — creates Session node
|
|
24
|
+
# and returns a subgraph snapshot in the result JSON
|
|
25
|
+
# 6. Commits + pushes memory repo (with orphan-stash dance if needed)
|
|
26
|
+
# 7. Publishes HTML artifact via bin/publish-artifact.sh
|
|
27
|
+
# 8. Sends Telegram notification via bin/notify.sh (if recipient)
|
|
28
|
+
# 9. Emits ONE status line and ONE JSON blob on stdout
|
|
29
|
+
#
|
|
30
|
+
# Exit codes:
|
|
31
|
+
# 0 on success (even if graph/publish/notify individually fail — they are
|
|
32
|
+
# non-blocking and reported in the JSON).
|
|
33
|
+
# 1 on unrecoverable failure (file write, memory push, bad args).
|
|
34
|
+
|
|
35
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
36
|
+
CONFIG="$SCRIPT_DIR/egregore.json"
|
|
37
|
+
|
|
38
|
+
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
|
39
|
+
sed -n '2,30p' "$0"
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# --- Parse args ----------------------------------------------------------
|
|
44
|
+
AUTHOR=""
|
|
45
|
+
TOPIC=""
|
|
46
|
+
RECIPIENT=""
|
|
47
|
+
PROJECT=""
|
|
48
|
+
NO_PUSH=0
|
|
49
|
+
NO_NOTIFY=0
|
|
50
|
+
NO_PUBLISH=0
|
|
51
|
+
|
|
52
|
+
while [ $# -gt 0 ]; do
|
|
53
|
+
case "$1" in
|
|
54
|
+
--author) AUTHOR="${2:?}"; shift 2 ;;
|
|
55
|
+
--topic) TOPIC="${2:?}"; shift 2 ;;
|
|
56
|
+
--recipient) RECIPIENT="${2:-}"; shift 2 ;;
|
|
57
|
+
--project) PROJECT="${2:-}"; shift 2 ;;
|
|
58
|
+
--no-push) NO_PUSH=1; shift ;;
|
|
59
|
+
--no-notify) NO_NOTIFY=1; shift ;;
|
|
60
|
+
--no-publish) NO_PUBLISH=1; shift ;;
|
|
61
|
+
*) echo "Unknown arg: $1" >&2; exit 1 ;;
|
|
62
|
+
esac
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
[ -z "$AUTHOR" ] && { echo "Missing --author" >&2; exit 1; }
|
|
66
|
+
[ -z "$TOPIC" ] && { echo "Missing --topic" >&2; exit 1; }
|
|
67
|
+
|
|
68
|
+
MODE=$(jq -r '.mode // "connected"' "$CONFIG" 2>/dev/null || echo "connected")
|
|
69
|
+
TODAY=$(date +%Y-%m-%d)
|
|
70
|
+
YYYY_MM=$(date +%Y-%m)
|
|
71
|
+
DD=$(date +%d)
|
|
72
|
+
|
|
73
|
+
# --- Derive slug + file path --------------------------------------------
|
|
74
|
+
_slugify() {
|
|
75
|
+
echo "$1" \
|
|
76
|
+
| tr '[:upper:]' '[:lower:]' \
|
|
77
|
+
| sed -E 's/[^a-z0-9]+/-/g' \
|
|
78
|
+
| sed -E 's/^-+|-+$//g' \
|
|
79
|
+
| cut -c1-50
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
SLUG=$(_slugify "$TOPIC")
|
|
83
|
+
REL_FILE="handoffs/${YYYY_MM}/${DD}-${AUTHOR}-${SLUG}.md"
|
|
84
|
+
ABS_FILE="$SCRIPT_DIR/memory/${REL_FILE}"
|
|
85
|
+
|
|
86
|
+
# Collision avoidance: append -N if file already exists
|
|
87
|
+
N=2
|
|
88
|
+
while [ -e "$ABS_FILE" ]; do
|
|
89
|
+
REL_FILE="handoffs/${YYYY_MM}/${DD}-${AUTHOR}-${SLUG}-${N}.md"
|
|
90
|
+
ABS_FILE="$SCRIPT_DIR/memory/${REL_FILE}"
|
|
91
|
+
N=$((N+1))
|
|
92
|
+
[ $N -gt 99 ] && { echo "Too many collisions for $SLUG" >&2; exit 1; }
|
|
93
|
+
done
|
|
94
|
+
|
|
95
|
+
mkdir -p "$(dirname "$ABS_FILE")"
|
|
96
|
+
|
|
97
|
+
# --- Write the file from stdin ------------------------------------------
|
|
98
|
+
cat > "$ABS_FILE"
|
|
99
|
+
|
|
100
|
+
if [ ! -s "$ABS_FILE" ]; then
|
|
101
|
+
echo "Empty stdin — handoff body is required" >&2
|
|
102
|
+
rm -f "$ABS_FILE"
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# --- Inject **To**: line if --recipient given and body omits it -----
|
|
107
|
+
# index-handoff.sh parses recipients from the file body. Skills
|
|
108
|
+
# normally write the To line themselves, but callers that invoke
|
|
109
|
+
# handoff-run.sh directly often skip it. Inject after the Author
|
|
110
|
+
# line so the graph gets the HANDED_TO edge.
|
|
111
|
+
if [ -n "$RECIPIENT" ] && ! grep -qi "^\*\*[Tt]o\*\*:" "$ABS_FILE"; then
|
|
112
|
+
awk -v r="$RECIPIENT" '
|
|
113
|
+
!injected && /^\*\*[Aa]uthor\*\*:/ {
|
|
114
|
+
print
|
|
115
|
+
print "**To**: " r
|
|
116
|
+
injected=1
|
|
117
|
+
next
|
|
118
|
+
}
|
|
119
|
+
{ print }
|
|
120
|
+
' "$ABS_FILE" > "$ABS_FILE.new" && mv "$ABS_FILE.new" "$ABS_FILE"
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
# --- Append repo state ---------------------------------------------------
|
|
124
|
+
# --no-pr skips `gh pr list` (~400–600ms per repo). A detached PR backfill
|
|
125
|
+
# after the parallel phase rewrites `—` → `#N` and re-pushes memory.
|
|
126
|
+
REPO_STATE=$(bash "$SCRIPT_DIR/bin/repo-state.sh" --no-pr 2>/dev/null || true)
|
|
127
|
+
if [ -n "$REPO_STATE" ]; then
|
|
128
|
+
printf '\n%s\n' "$REPO_STATE" >> "$ABS_FILE"
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# --- Prepend memory/handoffs/index.md -----------------------------------
|
|
132
|
+
INDEX="$SCRIPT_DIR/memory/handoffs/index.md"
|
|
133
|
+
mkdir -p "$(dirname "$INDEX")"
|
|
134
|
+
[ -f "$INDEX" ] || printf '# Handoffs\n\n' > "$INDEX"
|
|
135
|
+
|
|
136
|
+
if [ -n "$RECIPIENT" ]; then
|
|
137
|
+
IDX_LINE="- **${TODAY}** — ${AUTHOR}: ${TOPIC} (handoff to ${RECIPIENT})"
|
|
138
|
+
else
|
|
139
|
+
IDX_LINE="- **${TODAY}** — ${AUTHOR}: ${TOPIC} (handoff)"
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# Insert after the first blank line (keeping the # header intact)
|
|
143
|
+
awk -v line="$IDX_LINE" '
|
|
144
|
+
BEGIN { inserted = 0 }
|
|
145
|
+
/^$/ && !inserted { print; print line; inserted = 1; next }
|
|
146
|
+
{ print }
|
|
147
|
+
END { if (!inserted) print line }
|
|
148
|
+
' "$INDEX" > "$INDEX.new" && mv "$INDEX.new" "$INDEX"
|
|
149
|
+
|
|
150
|
+
# --- Parallel stage: graph + memory + (publish → notify) ----------------
|
|
151
|
+
#
|
|
152
|
+
# Three independent branches fork here and join before we emit the result:
|
|
153
|
+
# A. Graph index — reads ABS_FILE, writes Session node to Neo4j, and
|
|
154
|
+
# returns the handoff's neighborhood as subgraph JSON.
|
|
155
|
+
# B. Memory push — commits + pushes memory repo (orphan-stash dance).
|
|
156
|
+
# C. Publish+notify — publishes HTML artifact, then (if recipient is set)
|
|
157
|
+
# sends Telegram with the artifact URL embedded.
|
|
158
|
+
#
|
|
159
|
+
# Each branch writes its result to a tmpfile. Wall-clock is max(A,B,C).
|
|
160
|
+
#
|
|
161
|
+
# The published artifact runs its own live re-query via bin/graph.sh at
|
|
162
|
+
# render time, so it picks up the subgraph even though Branch A may not
|
|
163
|
+
# have indexed the session yet by the time Branch C fires publish.
|
|
164
|
+
|
|
165
|
+
MEMORY_DIR="$SCRIPT_DIR/memory"
|
|
166
|
+
|
|
167
|
+
TMPD=$(mktemp -d -t handoff-run-XXXXXX)
|
|
168
|
+
trap 'rm -rf "$TMPD"' EXIT
|
|
169
|
+
|
|
170
|
+
# Extract briefing text once (used by publish and notify) so we don't
|
|
171
|
+
# re-awk the same file inside multiple workers.
|
|
172
|
+
BRIEFING_LEAD=$(awk '/^## Briefing/{found=1; next} found && /^##/{exit} found && NF{print}' "$ABS_FILE" 2>/dev/null \
|
|
173
|
+
| head -2 | tr '\n' ' ' | cut -c1-200 || true)
|
|
174
|
+
BRIEFING_SHORT=$(awk '/^## Briefing/{found=1; next} found && /^##/{exit} found && NF{print}' "$ABS_FILE" 2>/dev/null \
|
|
175
|
+
| head -3 | tr '\n' ' ' | cut -c1-280 || true)
|
|
176
|
+
|
|
177
|
+
# --- Branch A: Graph index ---
|
|
178
|
+
(
|
|
179
|
+
if [ "$MODE" = "connected" ]; then
|
|
180
|
+
GJ=$(bash "$SCRIPT_DIR/bin/index-handoff.sh" "$ABS_FILE" 2>/dev/null || echo '{}')
|
|
181
|
+
SID=$(echo "$GJ" | jq -r '.sessionId // ""' 2>/dev/null || echo "")
|
|
182
|
+
RES=$(echo "$GJ" | jq -r '.resolved // 0' 2>/dev/null || echo 0)
|
|
183
|
+
SG=$(echo "$GJ" | jq -c '.subgraph // null' 2>/dev/null || echo "null")
|
|
184
|
+
echo "$SG" > "$TMPD/subgraph"
|
|
185
|
+
if [ -n "$SID" ]; then
|
|
186
|
+
printf '%s\n%s\nok\n' "$SID" "$RES" > "$TMPD/graph"
|
|
187
|
+
else
|
|
188
|
+
printf '\n0\noffline\n' > "$TMPD/graph"
|
|
189
|
+
fi
|
|
190
|
+
else
|
|
191
|
+
echo "null" > "$TMPD/subgraph"
|
|
192
|
+
printf '\n0\nskipped\n' > "$TMPD/graph"
|
|
193
|
+
fi
|
|
194
|
+
) &
|
|
195
|
+
PID_GRAPH=$!
|
|
196
|
+
|
|
197
|
+
# --- Branch D: Today's artifacts (connected mode only) ---
|
|
198
|
+
# The graph is the right tool for this: indexed by date + author + excluded
|
|
199
|
+
# tag, returns a small structured list. The filesystem would require a full
|
|
200
|
+
# walk of memory/knowledge/* to answer. Runs in parallel; Branch B waits on
|
|
201
|
+
# this before committing so the file always has the section.
|
|
202
|
+
(
|
|
203
|
+
ARTIFACTS_JSON="[]"
|
|
204
|
+
if [ "$MODE" = "connected" ]; then
|
|
205
|
+
YY=$(date +%Y); MM=$(date +%-m); DD_NUM=$(date +%-d)
|
|
206
|
+
PARAMS=$(jq -nc --arg gh "$AUTHOR" '{gh:$gh}')
|
|
207
|
+
RAW=$(bash "$SCRIPT_DIR/bin/graph.sh" query "
|
|
208
|
+
MATCH (a:Artifact)-[:CONTRIBUTED_BY]->(p:Person {github: \$gh})
|
|
209
|
+
WHERE a.created >= datetime({year: $YY, month: $MM, day: $DD_NUM})
|
|
210
|
+
AND NOT 'tutorial-generated' IN coalesce(a.topics, [])
|
|
211
|
+
RETURN a.title AS title, a.type AS type, a.filePath AS path
|
|
212
|
+
ORDER BY a.created DESC
|
|
213
|
+
LIMIT 5" "$PARAMS" 2>/dev/null || echo '{}')
|
|
214
|
+
# Response shape: {"fields":[...], "values":[[...], ...]}. Convert to
|
|
215
|
+
# an array of {title, type, path} objects.
|
|
216
|
+
ARTIFACTS_JSON=$(echo "$RAW" | jq -c '
|
|
217
|
+
(.fields // []) as $f
|
|
218
|
+
| (.values // [])
|
|
219
|
+
| map(. as $row | reduce range(0; ($f | length)) as $i ({}; .[$f[$i]] = $row[$i]))
|
|
220
|
+
' 2>/dev/null || echo '[]')
|
|
221
|
+
[ -z "$ARTIFACTS_JSON" ] && ARTIFACTS_JSON="[]"
|
|
222
|
+
fi
|
|
223
|
+
echo "$ARTIFACTS_JSON" > "$TMPD/artifacts"
|
|
224
|
+
) &
|
|
225
|
+
PID_ARTIFACTS=$!
|
|
226
|
+
|
|
227
|
+
# --- Branch B: Memory commit + push ---
|
|
228
|
+
(
|
|
229
|
+
if [ "$NO_PUSH" = "1" ]; then
|
|
230
|
+
echo "skipped (--no-push)" > "$TMPD/memory"
|
|
231
|
+
else
|
|
232
|
+
# Wait for Branch D (artifacts query) so the committed file has the
|
|
233
|
+
# `## Session Artifacts` section. D is fast (~1s) and runs parallel
|
|
234
|
+
# with Branch A; this wait doesn't extend the critical path because
|
|
235
|
+
# Branch C (publish) is the bottleneck.
|
|
236
|
+
wait "$PID_ARTIFACTS" 2>/dev/null || true
|
|
237
|
+
if [ -s "$TMPD/artifacts" ]; then
|
|
238
|
+
ART_COUNT=$(jq 'length' "$TMPD/artifacts" 2>/dev/null || echo 0)
|
|
239
|
+
if [ "${ART_COUNT:-0}" -gt 0 ]; then
|
|
240
|
+
{
|
|
241
|
+
printf '\n## Session Artifacts\n\n'
|
|
242
|
+
jq -r '.[] | "- \(.type // "Artifact"): \(.title) -> \(.path // "")"' "$TMPD/artifacts" 2>/dev/null
|
|
243
|
+
} >> "$ABS_FILE"
|
|
244
|
+
fi
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
cd "$MEMORY_DIR"
|
|
248
|
+
git add "$REL_FILE" handoffs/index.md >/dev/null 2>&1
|
|
249
|
+
|
|
250
|
+
if [ -n "$RECIPIENT" ]; then
|
|
251
|
+
MSG_COMMIT="Handoff: ${TOPIC} (to ${RECIPIENT})"
|
|
252
|
+
else
|
|
253
|
+
MSG_COMMIT="Handoff: ${TOPIC}"
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
git commit -m "$MSG_COMMIT" >/dev/null 2>&1 || true
|
|
257
|
+
|
|
258
|
+
STASHED=0
|
|
259
|
+
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
|
|
260
|
+
git stash push -u -m "handoff-run pre-pull stash" >/dev/null 2>&1 && STASHED=1
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
MEM_OK=0
|
|
264
|
+
for i in 1 2 3; do
|
|
265
|
+
if git pull --rebase origin main --quiet >/dev/null 2>&1 \
|
|
266
|
+
&& git push origin main --quiet >/dev/null 2>&1; then
|
|
267
|
+
MEM_OK=1
|
|
268
|
+
break
|
|
269
|
+
fi
|
|
270
|
+
sleep 1
|
|
271
|
+
done
|
|
272
|
+
|
|
273
|
+
[ "$STASHED" = "1" ] && git stash pop >/dev/null 2>&1 || true
|
|
274
|
+
|
|
275
|
+
if [ "$MEM_OK" = "1" ]; then
|
|
276
|
+
echo "ok" > "$TMPD/memory"
|
|
277
|
+
else
|
|
278
|
+
echo "failed" > "$TMPD/memory"
|
|
279
|
+
fi
|
|
280
|
+
fi
|
|
281
|
+
) &
|
|
282
|
+
PID_MEMORY=$!
|
|
283
|
+
|
|
284
|
+
# --- Branch C: Publish artifact → Notify ---
|
|
285
|
+
(
|
|
286
|
+
AURL=""
|
|
287
|
+
if [ "$NO_PUBLISH" = "0" ] && [ -x "$SCRIPT_DIR/bin/publish-artifact.sh" ]; then
|
|
288
|
+
AURL=$(bash "$SCRIPT_DIR/bin/publish-artifact.sh" handoff "$ABS_FILE" \
|
|
289
|
+
--title "$TOPIC" \
|
|
290
|
+
--author "$AUTHOR" \
|
|
291
|
+
--description "$BRIEFING_LEAD" 2>/dev/null || echo "")
|
|
292
|
+
fi
|
|
293
|
+
echo "$AURL" > "$TMPD/artifact"
|
|
294
|
+
|
|
295
|
+
NS="skipped"
|
|
296
|
+
if [ "$NO_NOTIFY" = "0" ]; then
|
|
297
|
+
if [ -n "$AURL" ]; then
|
|
298
|
+
MSG_NOTIFY="Handoff from ${AUTHOR}: ${TOPIC}
|
|
299
|
+
|
|
300
|
+
\"${BRIEFING_SHORT}\"
|
|
301
|
+
|
|
302
|
+
View: ${AURL}"
|
|
303
|
+
else
|
|
304
|
+
MSG_NOTIFY="Handoff from ${AUTHOR}: ${TOPIC}
|
|
305
|
+
|
|
306
|
+
\"${BRIEFING_SHORT}\""
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
# Always notify. With a recipient (connected mode) → DM. Otherwise → group.
|
|
310
|
+
# Self-handoffs still post to the group so they're visible on /activity
|
|
311
|
+
# and in the Telegram feed — a handoff without a Telegram beat is invisible.
|
|
312
|
+
if [ -n "$RECIPIENT" ] && [ "$MODE" = "connected" ]; then
|
|
313
|
+
NR=$(bash "$SCRIPT_DIR/bin/notify.sh" send "$RECIPIENT" "$MSG_NOTIFY" 2>/dev/null || echo "Failed")
|
|
314
|
+
else
|
|
315
|
+
NR=$(bash "$SCRIPT_DIR/bin/notify.sh" group "$MSG_NOTIFY" 2>/dev/null || echo "Failed")
|
|
316
|
+
fi
|
|
317
|
+
|
|
318
|
+
if echo "$NR" | grep -qi "^Sent"; then
|
|
319
|
+
NS="sent"
|
|
320
|
+
elif echo "$NR" | grep -qi "^Failed"; then
|
|
321
|
+
NS="failed"
|
|
322
|
+
else
|
|
323
|
+
NS="unknown"
|
|
324
|
+
fi
|
|
325
|
+
fi
|
|
326
|
+
echo "$NS" > "$TMPD/notify"
|
|
327
|
+
) &
|
|
328
|
+
PID_PUBNOT=$!
|
|
329
|
+
|
|
330
|
+
# Join (never fail parent — each branch reports its own status).
|
|
331
|
+
# Branch D (PID_ARTIFACTS) is already awaited inside Branch B, but we wait
|
|
332
|
+
# again here to be safe if B skipped the wait (--no-push path).
|
|
333
|
+
wait "$PID_GRAPH" 2>/dev/null || true
|
|
334
|
+
wait "$PID_MEMORY" 2>/dev/null || true
|
|
335
|
+
wait "$PID_PUBNOT" 2>/dev/null || true
|
|
336
|
+
wait "$PID_ARTIFACTS" 2>/dev/null || true
|
|
337
|
+
|
|
338
|
+
# --- Collect results from tmpfiles --------------------------------------
|
|
339
|
+
SESSION_ID=""
|
|
340
|
+
RESOLVED=0
|
|
341
|
+
GRAPH_STATUS="skipped"
|
|
342
|
+
if [ -s "$TMPD/graph" ]; then
|
|
343
|
+
SESSION_ID=$(sed -n '1p' "$TMPD/graph")
|
|
344
|
+
RESOLVED=$(sed -n '2p' "$TMPD/graph")
|
|
345
|
+
GRAPH_STATUS=$(sed -n '3p' "$TMPD/graph")
|
|
346
|
+
fi
|
|
347
|
+
[ -z "$RESOLVED" ] && RESOLVED=0
|
|
348
|
+
|
|
349
|
+
SUBGRAPH_JSON=$(cat "$TMPD/subgraph" 2>/dev/null || echo "null")
|
|
350
|
+
[ -z "$SUBGRAPH_JSON" ] && SUBGRAPH_JSON="null"
|
|
351
|
+
|
|
352
|
+
MEMORY_STATUS=$(cat "$TMPD/memory" 2>/dev/null || echo "skipped")
|
|
353
|
+
ARTIFACT_URL=$(cat "$TMPD/artifact" 2>/dev/null || echo "")
|
|
354
|
+
NOTIFY_STATUS=$(cat "$TMPD/notify" 2>/dev/null || echo "skipped")
|
|
355
|
+
ARTIFACTS_ARR=$(cat "$TMPD/artifacts" 2>/dev/null || echo "[]")
|
|
356
|
+
[ -z "$ARTIFACTS_ARR" ] && ARTIFACTS_ARR="[]"
|
|
357
|
+
|
|
358
|
+
# --- Detached PR-number backfill ----------------------------------------
|
|
359
|
+
# repo-state.sh was called with --no-pr for speed; now fire the backfill in
|
|
360
|
+
# the background so PR columns get populated without blocking this script.
|
|
361
|
+
# Reparented to init via `( cmd & ) >/dev/null 2>&1` — survives our exit.
|
|
362
|
+
if [ "$MEMORY_STATUS" = "ok" ] && grep -q '^## Repo State' "$ABS_FILE" 2>/dev/null; then
|
|
363
|
+
( bash "$SCRIPT_DIR/bin/handoff-pr-backfill.sh" "$ABS_FILE" "$TOPIC" >/dev/null 2>&1 & ) >/dev/null 2>&1
|
|
364
|
+
fi
|
|
365
|
+
|
|
366
|
+
# --- Emit minimal stdout, stash structured data in a tmpfile ------------
|
|
367
|
+
#
|
|
368
|
+
# The Bash tool block shows stdout verbatim and collapses after ~3 lines.
|
|
369
|
+
# We keep stdout to exactly one compact status line so the block is
|
|
370
|
+
# visually quiet. Full structured data goes to a well-known tmpfile that
|
|
371
|
+
# the caller (skill) reads to render the rich card.
|
|
372
|
+
STATUS_BITS=("saved")
|
|
373
|
+
[ "$GRAPH_STATUS" = "ok" ] && STATUS_BITS+=("graphed")
|
|
374
|
+
[ "$MEMORY_STATUS" = "ok" ] && STATUS_BITS+=("pushed")
|
|
375
|
+
if [ "$NOTIFY_STATUS" = "sent" ]; then
|
|
376
|
+
if [ -n "$RECIPIENT" ]; then
|
|
377
|
+
STATUS_BITS+=("${RECIPIENT} notified")
|
|
378
|
+
else
|
|
379
|
+
STATUS_BITS+=("group notified")
|
|
380
|
+
fi
|
|
381
|
+
elif [ "$NOTIFY_STATUS" = "unknown" ] && [ -n "$RECIPIENT" ]; then
|
|
382
|
+
STATUS_BITS+=("${RECIPIENT} relayed to group")
|
|
383
|
+
fi
|
|
384
|
+
[ -n "$ARTIFACT_URL" ] && STATUS_BITS+=("published")
|
|
385
|
+
|
|
386
|
+
STATUS_LINE=""
|
|
387
|
+
for bit in "${STATUS_BITS[@]}"; do
|
|
388
|
+
if [ -z "$STATUS_LINE" ]; then
|
|
389
|
+
STATUS_LINE="$bit"
|
|
390
|
+
else
|
|
391
|
+
STATUS_LINE="${STATUS_LINE} · ${bit}"
|
|
392
|
+
fi
|
|
393
|
+
done
|
|
394
|
+
|
|
395
|
+
# Stash structured result for the caller
|
|
396
|
+
RESULT_FILE="${TMPDIR:-/tmp}/handoff-run-result.json"
|
|
397
|
+
jq -cn \
|
|
398
|
+
--arg mode "$MODE" \
|
|
399
|
+
--arg file "$REL_FILE" \
|
|
400
|
+
--arg absFile "$ABS_FILE" \
|
|
401
|
+
--arg sessionId "$SESSION_ID" \
|
|
402
|
+
--argjson resolved "${RESOLVED:-0}" \
|
|
403
|
+
--arg graphStatus "$GRAPH_STATUS" \
|
|
404
|
+
--arg memoryStatus "$MEMORY_STATUS" \
|
|
405
|
+
--arg notifyStatus "$NOTIFY_STATUS" \
|
|
406
|
+
--arg artifactUrl "$ARTIFACT_URL" \
|
|
407
|
+
--arg recipient "$RECIPIENT" \
|
|
408
|
+
--arg topic "$TOPIC" \
|
|
409
|
+
--arg author "$AUTHOR" \
|
|
410
|
+
--argjson subgraph "$SUBGRAPH_JSON" \
|
|
411
|
+
--argjson artifacts "$ARTIFACTS_ARR" \
|
|
412
|
+
'{
|
|
413
|
+
mode:$mode,
|
|
414
|
+
file:$file,
|
|
415
|
+
absFile:$absFile,
|
|
416
|
+
sessionId:$sessionId,
|
|
417
|
+
resolved:$resolved,
|
|
418
|
+
graphStatus:$graphStatus,
|
|
419
|
+
memoryStatus:$memoryStatus,
|
|
420
|
+
notifyStatus:$notifyStatus,
|
|
421
|
+
artifactUrl:$artifactUrl,
|
|
422
|
+
recipient:$recipient,
|
|
423
|
+
topic:$topic,
|
|
424
|
+
author:$author,
|
|
425
|
+
subgraph:$subgraph,
|
|
426
|
+
artifacts:$artifacts
|
|
427
|
+
}' > "$RESULT_FILE"
|
|
428
|
+
|
|
429
|
+
# One clean line for the Bash tool block.
|
|
430
|
+
# The caller reads structured data from $TMPDIR/handoff-run-result.json.
|
|
431
|
+
echo "⇌ ${STATUS_LINE}"
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# handoff-save-egregore.sh — background save+auto-merge for the core repo after a handoff.
|
|
3
|
+
#
|
|
4
|
+
# /handoff renders its card first, then fires this detached. The user never waits on git.
|
|
5
|
+
#
|
|
6
|
+
# Behavior:
|
|
7
|
+
# - If nothing to save (clean working tree + up-to-date with origin) → exit silently.
|
|
8
|
+
# - If on develop/main/master → create dev/{author}/handoff-{YYYY-MM-DD} branch from origin/develop.
|
|
9
|
+
# - Commit all changes, rebase onto origin/develop, push.
|
|
10
|
+
# - If push succeeds: create PR to develop (or reuse existing), then:
|
|
11
|
+
# - Markdown-only diff → `gh pr merge --auto --merge` (auto-merges when checks pass).
|
|
12
|
+
# - Code/config present → leave PR open for review (no auto-merge).
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# handoff-save-egregore.sh <author> <topic> [--repo-dir <path>]
|
|
16
|
+
#
|
|
17
|
+
# --repo-dir overrides the default working directory (normally the script's
|
|
18
|
+
# own repo root, resolved from $0). Tests and sandbox runs MUST pass it;
|
|
19
|
+
# without it, the script operates on the real repo regardless of caller cwd.
|
|
20
|
+
#
|
|
21
|
+
# Detached usage (the skill calls it this way):
|
|
22
|
+
# ( bash bin/handoff-save-egregore.sh "$AUTHOR" "$TOPIC" >/dev/null 2>&1 & ) >/dev/null 2>&1
|
|
23
|
+
#
|
|
24
|
+
# Exit 0 on any non-fatal outcome. The caller never reads stdout/stderr — all status
|
|
25
|
+
# lives in the PR itself and in `git log`.
|
|
26
|
+
|
|
27
|
+
set -uo pipefail
|
|
28
|
+
|
|
29
|
+
AUTHOR="${1:-}"
|
|
30
|
+
TOPIC="${2:-session work}"
|
|
31
|
+
shift 2 2>/dev/null || true
|
|
32
|
+
|
|
33
|
+
REPO_DIR=""
|
|
34
|
+
while [ $# -gt 0 ]; do
|
|
35
|
+
case "$1" in
|
|
36
|
+
--repo-dir) REPO_DIR="${2:-}"; shift 2 ;;
|
|
37
|
+
*) shift ;;
|
|
38
|
+
esac
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
[ -n "$AUTHOR" ] || { echo "handoff-save-egregore: missing author" >&2; exit 1; }
|
|
42
|
+
|
|
43
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
44
|
+
# If --repo-dir wasn't given, default to the script's own repo root.
|
|
45
|
+
[ -z "$REPO_DIR" ] && REPO_DIR="$SCRIPT_DIR"
|
|
46
|
+
cd "$REPO_DIR" || exit 1
|
|
47
|
+
|
|
48
|
+
# --- Early exit if nothing to do -----------------------------------------
|
|
49
|
+
DIRTY=0
|
|
50
|
+
[ -n "$(git status --porcelain 2>/dev/null)" ] && DIRTY=1
|
|
51
|
+
|
|
52
|
+
CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
|
|
53
|
+
AHEAD=0
|
|
54
|
+
if [ -n "$CURRENT_BRANCH" ]; then
|
|
55
|
+
# Count commits ahead of origin/develop on the current branch (works from any
|
|
56
|
+
# dev/* branch or from develop itself).
|
|
57
|
+
AHEAD=$(git rev-list --count "origin/develop..HEAD" 2>/dev/null || echo 0)
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
if [ "$DIRTY" = "0" ] && [ "$AHEAD" = "0" ]; then
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# --- Protect the protected branches: create a working branch if needed --
|
|
65
|
+
case "$CURRENT_BRANCH" in
|
|
66
|
+
develop|main|master|"")
|
|
67
|
+
SLUG_DATE=$(date +%Y-%m-%d)
|
|
68
|
+
NEW_BRANCH="dev/${AUTHOR}/handoff-${SLUG_DATE}"
|
|
69
|
+
git fetch origin develop --quiet 2>/dev/null || true
|
|
70
|
+
git checkout -b "$NEW_BRANCH" origin/develop --quiet 2>/dev/null || \
|
|
71
|
+
git checkout "$NEW_BRANCH" --quiet 2>/dev/null || exit 0
|
|
72
|
+
CURRENT_BRANCH="$NEW_BRANCH"
|
|
73
|
+
;;
|
|
74
|
+
esac
|
|
75
|
+
|
|
76
|
+
# --- Commit whatever is uncommitted --------------------------------------
|
|
77
|
+
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
|
|
78
|
+
git add -A 2>/dev/null || true
|
|
79
|
+
git commit -m "Handoff: ${TOPIC}" --quiet 2>/dev/null || true
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# --- Rebase onto develop, fall back to merge if rebase conflicts ---------
|
|
83
|
+
git fetch origin develop --quiet 2>/dev/null || true
|
|
84
|
+
if ! git rebase origin/develop --quiet 2>/dev/null; then
|
|
85
|
+
git rebase --abort 2>/dev/null || true
|
|
86
|
+
if ! git merge origin/develop --quiet -m "Sync with develop" 2>/dev/null; then
|
|
87
|
+
# Conflicts we can't auto-resolve. Leave the branch as-is; the user will see it
|
|
88
|
+
# next session or when they run /save explicitly.
|
|
89
|
+
git merge --abort 2>/dev/null || true
|
|
90
|
+
exit 0
|
|
91
|
+
fi
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# --- Push ---------------------------------------------------------------
|
|
95
|
+
if ! git push -u origin "$CURRENT_BRANCH" --quiet 2>/dev/null; then
|
|
96
|
+
# Push failed (network, auth). Commits are safe locally.
|
|
97
|
+
exit 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# --- PR: reuse open PR on this branch, or create one --------------------
|
|
101
|
+
command -v gh >/dev/null 2>&1 || exit 0
|
|
102
|
+
|
|
103
|
+
PR_NUMBER=""
|
|
104
|
+
EXISTING=$(gh pr list --head "$CURRENT_BRANCH" --state open --json number,baseRefName --jq '.[0]' 2>/dev/null)
|
|
105
|
+
EXISTING_NUMBER=$(echo "$EXISTING" | jq -r '.number // empty' 2>/dev/null)
|
|
106
|
+
EXISTING_BASE=$(echo "$EXISTING" | jq -r '.baseRefName // empty' 2>/dev/null)
|
|
107
|
+
|
|
108
|
+
if [ -n "$EXISTING_NUMBER" ]; then
|
|
109
|
+
PR_NUMBER="$EXISTING_NUMBER"
|
|
110
|
+
if [ "$EXISTING_BASE" != "develop" ]; then
|
|
111
|
+
gh pr edit "$PR_NUMBER" --base develop >/dev/null 2>&1 || true
|
|
112
|
+
fi
|
|
113
|
+
else
|
|
114
|
+
PR_URL=$(gh pr create \
|
|
115
|
+
--base develop \
|
|
116
|
+
--head "$CURRENT_BRANCH" \
|
|
117
|
+
--title "Handoff: ${TOPIC}" \
|
|
118
|
+
--body "Auto-created by /handoff. Session work for: ${TOPIC}" \
|
|
119
|
+
2>/dev/null || echo "")
|
|
120
|
+
PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$' || echo "")
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
[ -n "$PR_NUMBER" ] || exit 0
|
|
124
|
+
|
|
125
|
+
# --- Auto-merge iff markdown-only ---------------------------------------
|
|
126
|
+
NON_MD=$(git diff origin/develop --name-only 2>/dev/null | grep -v '\.md$' | head -1 || echo "")
|
|
127
|
+
if [ -z "$NON_MD" ]; then
|
|
128
|
+
gh pr merge "$PR_NUMBER" --auto --merge >/dev/null 2>&1 || true
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
exit 0
|