create-egregore 0.19.8 → 0.19.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/lib/local.js +11 -1
  2. package/lib/setup.js +33 -0
  3. package/package.json +1 -1
  4. package/runtime/codex/.codex/hooks/branch-guard.js +242 -34
  5. package/runtime/codex/.codex/skills/activity/SKILL.md +18 -6
  6. package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +90 -55
  7. package/runtime/codex/.codex/skills/deploy-bridge/SKILL.md +25 -0
  8. package/runtime/codex/.codex/skills/handoff/SKILL.md +6 -2
  9. package/runtime/codex/.codex/skills/save/SKILL.md +21 -3
  10. package/runtime/codex/.codex/skills/scroll/SKILL.md +54 -3
  11. package/runtime/codex/.codex/skills/task/SKILL.md +25 -0
  12. package/runtime/codex/.codex/skills/tui-design/SKILL.md +1 -1
  13. package/runtime/codex/.codex/skills/wrap/SKILL.md +4 -1
  14. package/runtime/codex/.codex/spec-manifest.json +15 -3
  15. package/runtime/codex/AGENTS.md +45 -9
  16. package/runtime/codex/bin/activity-data.sh +1 -1
  17. package/runtime/codex/bin/agent.sh +66 -23
  18. package/runtime/codex/bin/attendant.sh +525 -0
  19. package/runtime/codex/bin/capture-reconcile.sh +67 -0
  20. package/runtime/codex/bin/capture-run.sh +341 -0
  21. package/runtime/codex/bin/codex-render-spec.mjs +15 -4
  22. package/runtime/codex/bin/codex-session-start.sh +45 -3
  23. package/runtime/codex/bin/codex-skill-render.mjs +23 -4
  24. package/runtime/codex/bin/graph-op.sh +719 -49
  25. package/runtime/codex/bin/graph-wal.sh +3 -2
  26. package/runtime/codex/bin/handoff-run.sh +72 -0
  27. package/runtime/codex/bin/handoff-save-egregore.sh +51 -11
  28. package/runtime/codex/bin/index-handoff.sh +26 -17
  29. package/runtime/codex/bin/ingest-graph.sh +80 -0
  30. package/runtime/codex/bin/ingest-surface.html +429 -0
  31. package/runtime/codex/bin/ingest.py +614 -0
  32. package/runtime/codex/bin/ingest.sh +231 -0
  33. package/runtime/codex/bin/ingest_graph.py +776 -0
  34. package/runtime/codex/bin/ingest_surface.py +464 -0
  35. package/runtime/codex/bin/lib/git-sync.sh +2 -6
  36. package/runtime/codex/bin/lib/greeting.sh +157 -22
  37. package/runtime/codex/bin/lib/identity.sh +50 -29
  38. package/runtime/codex/bin/lib/noncode.sh +31 -0
  39. package/runtime/codex/bin/person.py +610 -0
  40. package/runtime/codex/bin/person.sh +448 -0
  41. package/runtime/codex/bin/session-autosave.sh +184 -0
  42. package/runtime/codex/bin/session-log.sh +199 -0
  43. package/runtime/codex/bin/settings.sh +1 -1
  44. package/runtime/codex/manifest.json +19 -1
  45. package/runtime/pi/.codex/hooks/branch-guard.js +242 -34
  46. package/runtime/pi/.codex/skills/activity/SKILL.md +18 -6
  47. package/runtime/pi/.codex/skills/deep-reflect/SKILL.md +90 -55
  48. package/runtime/pi/.codex/skills/deploy-bridge/SKILL.md +25 -0
  49. package/runtime/pi/.codex/skills/handoff/SKILL.md +6 -2
  50. package/runtime/pi/.codex/skills/save/SKILL.md +21 -3
  51. package/runtime/pi/.codex/skills/scroll/SKILL.md +54 -3
  52. package/runtime/pi/.codex/skills/task/SKILL.md +25 -0
  53. package/runtime/pi/.codex/skills/tui-design/SKILL.md +1 -1
  54. package/runtime/pi/.codex/skills/wrap/SKILL.md +4 -1
  55. package/runtime/pi/.codex/spec-manifest.json +15 -3
  56. package/runtime/pi/.pi/APPEND_SYSTEM.md +41 -5
  57. package/runtime/pi/.pi/extensions/egregore.ts +98 -8
  58. package/runtime/pi/.pi/spec-manifest.json +2 -2
  59. package/runtime/pi/AGENTS.md +45 -9
  60. package/runtime/pi/bin/activity-data.sh +1 -1
  61. package/runtime/pi/bin/agent.sh +66 -23
  62. package/runtime/pi/bin/attendant.sh +525 -0
  63. package/runtime/pi/bin/capture-reconcile.sh +67 -0
  64. package/runtime/pi/bin/capture-run.sh +341 -0
  65. package/runtime/pi/bin/codex-render-spec.mjs +15 -4
  66. package/runtime/pi/bin/codex-session-start.sh +45 -3
  67. package/runtime/pi/bin/codex-skill-render.mjs +23 -4
  68. package/runtime/pi/bin/graph-op.sh +719 -49
  69. package/runtime/pi/bin/graph-wal.sh +3 -2
  70. package/runtime/pi/bin/handoff-run.sh +72 -0
  71. package/runtime/pi/bin/handoff-save-egregore.sh +51 -11
  72. package/runtime/pi/bin/index-handoff.sh +26 -17
  73. package/runtime/pi/bin/ingest-graph.sh +80 -0
  74. package/runtime/pi/bin/ingest-surface.html +429 -0
  75. package/runtime/pi/bin/ingest.py +614 -0
  76. package/runtime/pi/bin/ingest.sh +231 -0
  77. package/runtime/pi/bin/ingest_graph.py +776 -0
  78. package/runtime/pi/bin/ingest_surface.py +464 -0
  79. package/runtime/pi/bin/lib/git-sync.sh +2 -6
  80. package/runtime/pi/bin/lib/greeting.sh +157 -22
  81. package/runtime/pi/bin/lib/identity.sh +50 -29
  82. package/runtime/pi/bin/lib/noncode.sh +31 -0
  83. package/runtime/pi/bin/person.py +610 -0
  84. package/runtime/pi/bin/person.sh +448 -0
  85. package/runtime/pi/bin/pi-branch-policy.mjs +164 -4
  86. package/runtime/pi/bin/pi-product-workflows.mjs +31 -2
  87. package/runtime/pi/bin/publish-artifact.sh +14 -0
  88. package/runtime/pi/bin/session-autosave.sh +184 -0
  89. package/runtime/pi/bin/session-log.sh +199 -0
  90. package/runtime/pi/bin/settings.sh +274 -0
  91. package/runtime/pi/manifest.json +20 -1
@@ -0,0 +1,341 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # Shared capture engine for the three ways an Egregore session leaves a trace.
5
+ #
6
+ # addressed /handoff: rich team handoff (delegates to the addressed worker)
7
+ # personal /wrap: explicit personal close
8
+ # baseline SessionEnd: automatic, loss-prevention trace
9
+ #
10
+ # Lifecycle reconciliation is never performed on the caller's critical path.
11
+ # Connected-mode captures append idempotent graph statements to graph-wal.sh
12
+ # and optionally start capture-reconcile.sh as a detached worker.
13
+
14
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
15
+ CONFIG="$SCRIPT_DIR/egregore.json"
16
+ CAPTURE_SCHEMA="egregore-capture/v1"
17
+
18
+ queue_handoff_completion() {
19
+ local session_id="$1"
20
+ [ -n "$session_id" ] || return 0
21
+ # shellcheck disable=SC2016 # $name tokens are Cypher parameters.
22
+ local cypher='
23
+ MATCH (impl:Session {id: $sessionId})-[:IMPLEMENTS]->(ho:Session)
24
+ MATCH (impl)-[:BY]->(recipient:Person)
25
+ MATCH (ho)-[:HANDED_TO]->(recipient)
26
+ WHERE coalesce(ho.handoffStatus, "pending") IN ["pending","read","claimed"]
27
+ AND coalesce(ho.handoffLifecycleVersion, 0) >= 1
28
+ WITH DISTINCT ho, size([(ho)-[:HANDED_TO]->(:Person) | 1]) AS recipientCount
29
+ WHERE recipientCount = 1
30
+ SET ho.handoffStatus = "done",
31
+ ho.handoffDoneAt = coalesce(ho.handoffDoneAt, datetime()),
32
+ ho.handoffUpdatedAt = datetime(),
33
+ ho.handoffLifecycleReason = "single_recipient_implemented",
34
+ ho.handoffLifecycleVersion = 1
35
+ RETURN ho.id AS id'
36
+ local params
37
+ params="$(jq -nc --arg sessionId "$session_id" '{sessionId:$sessionId}')"
38
+ GRAPH_WAL_LOCK_ATTEMPTS=1 \
39
+ bash "$SCRIPT_DIR/bin/graph-wal.sh" append "$cypher" "$params" >/dev/null 2>&1 || true
40
+ }
41
+
42
+ start_reconcile() {
43
+ local session_id="$1"
44
+ local author="$2"
45
+ local summary="$3"
46
+ [ -n "$session_id" ] || return 0
47
+ (
48
+ bash "$SCRIPT_DIR/bin/capture-reconcile.sh" \
49
+ --session-id "$session_id" \
50
+ --author "$author" \
51
+ --summary "$summary" >/dev/null 2>&1 &
52
+ ) >/dev/null 2>&1
53
+ }
54
+
55
+ usage() {
56
+ cat <<'EOF'
57
+ Usage:
58
+ capture-run.sh --mode addressed [handoff-run options] < handoff.md
59
+ capture-run.sh --mode personal|baseline --author HANDLE --topic TOPIC
60
+ --summary SUMMARY [--session-id ID] [--branch BRANCH] [--date ISO]
61
+ [--duration TEXT] [--open-threads-json JSON] [--no-push]
62
+ [--async-push] [--no-reconcile] < details.md
63
+ EOF
64
+ }
65
+
66
+ if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
67
+ usage
68
+ exit 0
69
+ fi
70
+
71
+ CAPTURE_MODE=""
72
+ if [ "${1:-}" = "--mode" ]; then
73
+ CAPTURE_MODE="${2:-}"
74
+ shift 2
75
+ fi
76
+ [ -n "$CAPTURE_MODE" ] || {
77
+ echo "Missing --mode (addressed|personal|baseline)" >&2
78
+ exit 1
79
+ }
80
+
81
+ case "$CAPTURE_MODE" in
82
+ addressed)
83
+ # Keep the mature publishing/notification worker intact behind the shared
84
+ # entry point. It continues writing its compatibility result file.
85
+ ADDRESSED_AUTHOR=""
86
+ ADDRESSED_TOPIC=""
87
+ PREV=""
88
+ for VALUE in "$@"; do
89
+ if [ "$PREV" = "--author" ]; then ADDRESSED_AUTHOR="$VALUE"; fi
90
+ if [ "$PREV" = "--topic" ]; then ADDRESSED_TOPIC="$VALUE"; fi
91
+ PREV="$VALUE"
92
+ done
93
+
94
+ if bash "$SCRIPT_DIR/bin/handoff-run.sh" "$@"; then
95
+ status=0
96
+ else
97
+ status=$?
98
+ fi
99
+ old_result="${TMPDIR:-/tmp}/handoff-run-result.json"
100
+ capture_result="${TMPDIR:-/tmp}/capture-run-result.json"
101
+ if [ -s "$old_result" ]; then
102
+ jq --arg schema "$CAPTURE_SCHEMA" --arg captureMode "$CAPTURE_MODE" \
103
+ '. + {captureSchema:$schema,captureMode:$captureMode}' \
104
+ "$old_result" > "$capture_result" 2>/dev/null || cp "$old_result" "$capture_result"
105
+ fi
106
+
107
+ # Addressed capture is also explicit session-close evidence. Queue the
108
+ # same guarded transition as /wrap after the addressed worker has marked
109
+ # the current Session handed_off.
110
+ ADDRESSED_CONFIG_MODE="$(jq -r '.mode // "connected"' "$CONFIG" 2>/dev/null || echo connected)"
111
+ if [ "$status" = "0" ] && [ "$ADDRESSED_CONFIG_MODE" = "connected" ]; then
112
+ CURRENT_SID="$(cat "$SCRIPT_DIR/.egregore-session-id" 2>/dev/null || true)"
113
+ if [ -z "$CURRENT_SID" ]; then
114
+ PROJ_HASH="$(printf '%s' "$SCRIPT_DIR" | md5 2>/dev/null || printf '%s' "$SCRIPT_DIR" | md5sum 2>/dev/null | cut -d' ' -f1)"
115
+ CURRENT_SID="$(cat "$HOME/.egregore/session-${PROJ_HASH}.id" 2>/dev/null || true)"
116
+ fi
117
+ queue_handoff_completion "$CURRENT_SID"
118
+ start_reconcile "$CURRENT_SID" "$ADDRESSED_AUTHOR" "$ADDRESSED_TOPIC"
119
+ fi
120
+ exit "$status"
121
+ ;;
122
+ personal|baseline) ;;
123
+ *)
124
+ echo "Invalid --mode: $CAPTURE_MODE (addressed|personal|baseline)" >&2
125
+ exit 1
126
+ ;;
127
+ esac
128
+
129
+ AUTHOR=""
130
+ DISPLAY_NAME=""
131
+ TOPIC=""
132
+ SUMMARY=""
133
+ SESSION_ID=""
134
+ BRANCH=""
135
+ CAPTURE_DATE=""
136
+ DURATION=""
137
+ OPEN_THREADS_JSON="[]"
138
+ NO_PUSH=0
139
+ ASYNC_PUSH=0
140
+ NO_RECONCILE=0
141
+
142
+ while [ $# -gt 0 ]; do
143
+ case "$1" in
144
+ --author) AUTHOR="${2:-}"; shift 2 ;;
145
+ --display-name) DISPLAY_NAME="${2:-}"; shift 2 ;;
146
+ --topic) TOPIC="${2:-}"; shift 2 ;;
147
+ --summary) SUMMARY="${2:-}"; shift 2 ;;
148
+ --session-id) SESSION_ID="${2:-}"; shift 2 ;;
149
+ --branch) BRANCH="${2:-}"; shift 2 ;;
150
+ --date) CAPTURE_DATE="${2:-}"; shift 2 ;;
151
+ --duration) DURATION="${2:-}"; shift 2 ;;
152
+ --open-threads-json) OPEN_THREADS_JSON="${2:-[]}"; shift 2 ;;
153
+ --no-push) NO_PUSH=1; shift ;;
154
+ --async-push) ASYNC_PUSH=1; shift ;;
155
+ --no-reconcile) NO_RECONCILE=1; shift ;;
156
+ *) echo "Unknown capture option: $1" >&2; exit 1 ;;
157
+ esac
158
+ done
159
+
160
+ AUTHOR="$(printf '%s' "$AUTHOR" | tr -cd 'a-zA-Z0-9_-')"
161
+ [ -n "$AUTHOR" ] || { echo "Missing --author" >&2; exit 1; }
162
+ [ -n "$TOPIC" ] || { echo "Missing --topic" >&2; exit 1; }
163
+ [ -n "$SUMMARY" ] || SUMMARY="$TOPIC"
164
+ if ! printf '%s' "$OPEN_THREADS_JSON" | jq -e 'type == "array"' >/dev/null 2>&1; then
165
+ echo "Invalid --open-threads-json: expected an array" >&2
166
+ exit 1
167
+ fi
168
+
169
+ DETAILS="$(cat /dev/stdin 2>/dev/null || true)"
170
+ NOW="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
171
+ [ -n "$CAPTURE_DATE" ] || CAPTURE_DATE="$NOW"
172
+ DATE_ONLY="$(printf '%s' "$CAPTURE_DATE" | grep -oE '^[0-9]{4}-[0-9]{2}-[0-9]{2}' || true)"
173
+ [ -n "$DATE_ONLY" ] || DATE_ONLY="$(date +%Y-%m-%d)"
174
+ YYYY_MM="${DATE_ONLY%??}"
175
+ YYYY_MM="${YYYY_MM%-}"
176
+ DD="${DATE_ONLY##*-}"
177
+ [ -n "$BRANCH" ] || BRANCH="$(git -C "$SCRIPT_DIR" branch --show-current 2>/dev/null || true)"
178
+ [ -n "$BRANCH" ] || BRANCH="develop"
179
+
180
+ slugify() {
181
+ printf '%s' "$1" \
182
+ | tr '[:upper:]' '[:lower:]' \
183
+ | sed -E 's/[^a-z0-9]+/-/g; s/^-+|-+$//g' \
184
+ | cut -c1-50
185
+ }
186
+
187
+ SLUG="$(slugify "$TOPIC")"
188
+ [ -n "$SLUG" ] || SLUG="session"
189
+ AUTHOR_LC="$(printf '%s' "$AUTHOR" | tr '[:upper:]' '[:lower:]')"
190
+
191
+ if [ "$CAPTURE_MODE" = "personal" ]; then
192
+ REL_FILE="wraps/${YYYY_MM}/${DD}-${AUTHOR_LC}-${SLUG}.md"
193
+ TITLE_PREFIX="Wrap"
194
+ else
195
+ REL_FILE="sessions/${YYYY_MM}/${DD}-${AUTHOR_LC}-${SLUG}.md"
196
+ TITLE_PREFIX="Session"
197
+ fi
198
+ ABS_FILE="$SCRIPT_DIR/memory/$REL_FILE"
199
+ mkdir -p "$(dirname "$ABS_FILE")"
200
+
201
+ # Never overwrite an existing capture. Session IDs make concurrent same-topic
202
+ # sessions deterministic while retaining the human-readable prefix.
203
+ if [ -e "$ABS_FILE" ]; then
204
+ suffix="$(printf '%s' "${SESSION_ID:-$NOW}" | tr -cd 'a-zA-Z0-9' | tail -c 7)"
205
+ [ -n "$suffix" ] || suffix="2"
206
+ ABS_FILE="${ABS_FILE%.md}-${suffix}.md"
207
+ REL_FILE="${REL_FILE%.md}-${suffix}.md"
208
+ fi
209
+
210
+ {
211
+ printf '# %s: %s\n\n' "$TITLE_PREFIX" "$TOPIC"
212
+ printf '**Capture Schema**: %s\n' "$CAPTURE_SCHEMA"
213
+ printf '**Capture Mode**: %s\n' "$CAPTURE_MODE"
214
+ printf '**Date**: %s\n' "$CAPTURE_DATE"
215
+ printf '**Author**: %s\n' "${DISPLAY_NAME:-$AUTHOR}"
216
+ printf '**To**: %s\n' "$AUTHOR"
217
+ printf '**Branch**: %s\n' "$BRANCH"
218
+ [ -n "$SESSION_ID" ] && printf '**Session**: %s\n' "$SESSION_ID"
219
+ [ -n "$DURATION" ] && printf '**Duration**: %s\n' "$DURATION"
220
+ printf '\n## Summary\n\n%s\n' "$SUMMARY"
221
+ if [ -n "$DETAILS" ]; then
222
+ if [ "$CAPTURE_MODE" = "personal" ]; then
223
+ printf '\n## Notes\n\n%s\n' "$DETAILS"
224
+ else
225
+ printf '\n%s\n' "$DETAILS"
226
+ fi
227
+ fi
228
+ } > "$ABS_FILE"
229
+
230
+ memory_save() {
231
+ local message="$1"
232
+ (
233
+ cd "$SCRIPT_DIR/memory" 2>/dev/null || exit 0
234
+ git add "$REL_FILE" >/dev/null 2>&1 || exit 0
235
+ git commit --only --quiet -m "$message" -- "$REL_FILE" >/dev/null 2>&1 || true
236
+ if git remote get-url origin >/dev/null 2>&1; then
237
+ local _
238
+ for _ in 1 2 3; do
239
+ git pull --rebase origin main --quiet >/dev/null 2>&1 || true
240
+ git push origin main --quiet >/dev/null 2>&1 && exit 0
241
+ sleep 1
242
+ done
243
+ exit 1
244
+ fi
245
+ )
246
+ }
247
+
248
+ MEMORY_STATUS="skipped"
249
+ if [ "$NO_PUSH" = "0" ]; then
250
+ if [ "$ASYNC_PUSH" = "1" ]; then
251
+ ( memory_save "Capture: ${AUTHOR} — ${TOPIC}" >/dev/null 2>&1 & ) >/dev/null 2>&1
252
+ MEMORY_STATUS="queued"
253
+ elif memory_save "Capture: ${AUTHOR} — ${TOPIC}"; then
254
+ MEMORY_STATUS="ok"
255
+ else
256
+ MEMORY_STATUS="failed"
257
+ fi
258
+ fi
259
+
260
+ GRAPH_STATUS="skipped"
261
+ MODE="$(jq -r '.mode // "connected"' "$CONFIG" 2>/dev/null || echo connected)"
262
+ if [ "$MODE" = "connected" ] && [ -n "$SESSION_ID" ]; then
263
+ # shellcheck disable=SC2016 # $name tokens are Cypher parameters.
264
+ CAPTURE_CYPHER='
265
+ MERGE (s:Session {id: $sessionId})
266
+ ON CREATE SET s.date = date($date), s.startedAt = datetime($capturedAt)
267
+ SET s.topic = $topic,
268
+ s.summary = $summary,
269
+ s.filePath = $filePath,
270
+ s.branch = $branch,
271
+ s.captureSchema = $captureSchema,
272
+ s.captureMode = $captureMode,
273
+ s.captureUpdatedAt = datetime()
274
+ FOREACH (_ IN CASE WHEN $captureMode = "personal" THEN [1] ELSE [] END |
275
+ SET s.status = "wrapped",
276
+ s.wrappedAt = coalesce(s.wrappedAt, datetime()),
277
+ s.openThreads = $openThreads)
278
+ FOREACH (_ IN CASE WHEN $captureMode = "baseline" THEN [1] ELSE [] END |
279
+ SET s.status = CASE
280
+ WHEN s.status IN ["wrapped","handed_off","completed"] THEN s.status
281
+ ELSE "captured"
282
+ END,
283
+ s.endedAt = coalesce(s.endedAt, datetime()))
284
+ WITH s
285
+ OPTIONAL MATCH (p:Person)
286
+ WHERE toLower(p.name) = toLower($author)
287
+ OR toLower(coalesce(p.github, "")) = toLower($author)
288
+ FOREACH (_ IN CASE WHEN p IS NOT NULL THEN [1] ELSE [] END |
289
+ MERGE (s)-[:BY]->(p))
290
+ RETURN s.id AS id'
291
+ CAPTURE_PARAMS="$(jq -nc \
292
+ --arg sessionId "$SESSION_ID" \
293
+ --arg date "$DATE_ONLY" \
294
+ --arg capturedAt "$NOW" \
295
+ --arg topic "$TOPIC" \
296
+ --arg summary "$SUMMARY" \
297
+ --arg filePath "$REL_FILE" \
298
+ --arg branch "$BRANCH" \
299
+ --arg captureSchema "$CAPTURE_SCHEMA" \
300
+ --arg captureMode "$CAPTURE_MODE" \
301
+ --arg author "$AUTHOR_LC" \
302
+ --argjson openThreads "$OPEN_THREADS_JSON" \
303
+ '{sessionId:$sessionId,date:$date,capturedAt:$capturedAt,topic:$topic,
304
+ summary:$summary,filePath:$filePath,branch:$branch,
305
+ captureSchema:$captureSchema,captureMode:$captureMode,author:$author,
306
+ openThreads:$openThreads}')"
307
+ GRAPH_WAL_LOCK_ATTEMPTS=1 \
308
+ bash "$SCRIPT_DIR/bin/graph-wal.sh" append "$CAPTURE_CYPHER" "$CAPTURE_PARAMS" >/dev/null 2>&1 || true
309
+ GRAPH_STATUS="queued"
310
+
311
+ # An explicit wrap is strong completion evidence. Queue the transition after
312
+ # the Session update; do not query the graph or wait for reconciliation here.
313
+ if [ "$CAPTURE_MODE" = "personal" ]; then
314
+ queue_handoff_completion "$SESSION_ID"
315
+ fi
316
+
317
+ if [ "$NO_RECONCILE" = "0" ] && [ "$CAPTURE_MODE" = "personal" ]; then
318
+ start_reconcile "$SESSION_ID" "$AUTHOR_LC" "$SUMMARY"
319
+ fi
320
+ fi
321
+
322
+ RESULT_FILE="${TMPDIR:-/tmp}/capture-run-result.json"
323
+ jq -nc \
324
+ --arg schema "$CAPTURE_SCHEMA" \
325
+ --arg captureMode "$CAPTURE_MODE" \
326
+ --arg file "$REL_FILE" \
327
+ --arg absFile "$ABS_FILE" \
328
+ --arg sessionId "$SESSION_ID" \
329
+ --arg memoryStatus "$MEMORY_STATUS" \
330
+ --arg graphStatus "$GRAPH_STATUS" \
331
+ '{
332
+ captureSchema:$schema,
333
+ captureMode:$captureMode,
334
+ file:$file,
335
+ absFile:$absFile,
336
+ sessionId:$sessionId,
337
+ memoryStatus:$memoryStatus,
338
+ graphStatus:$graphStatus
339
+ }' > "$RESULT_FILE"
340
+
341
+ printf 'capture: memory/%s\n' "$REL_FILE"
@@ -218,19 +218,22 @@ If on develop after two messages, create a branch immediately from whatever cont
218
218
 
219
219
  ### Branch-guard protocol
220
220
 
221
- The \`.codex/hooks/branch-guard.js\` PreToolUse hook (enabled by the launcher via \`--enable hooks\`) denies writes, patches, and commits on \`develop\`/\`main\`/\`master\`. When it fires, choose between two paths based on how clear the topic is:
221
+ The \`.codex/hooks/branch-guard.js\` PreToolUse hook (enabled by the launcher via \`--enable hooks\`) protects project writes on \`develop\`/\`main\`/\`master\`. Its block message is operational guidance, not a reason to interrupt the user with routine Git choices:
222
222
 
223
- - **Topic is obvious from the last turn** (user said "fix X", "add Y feature") → auto-branch silently with \`bin/agent.sh branch --topic "<topic>"\` (or the git checkout fallback), then continue. Say one sentence ("Creating \`dev/{author}/{slug}\`.") so the user isn't confused about why a new branch appeared.
224
- - **Topic is ambiguous** (open-ended exploration, user didn't specify what we're building yet) → ask **before** branching, in plain text with numbered options (Codex has no structured question tool):
223
+ - **Topic is clear** (the user said "fix X", "add Y feature") → run \`bin/agent.sh branch --topic "<topic>"\` automatically, continue in the printed worktree, and say one short sentence so the branch change is visible. Do not ask the user to approve routine branching.
224
+ - **Topic is genuinely ambiguous** → ask only for the topic, using compact numbered options:
225
225
 
226
226
  \`\`\`text
227
- Need a working branch first. Topic?
227
+ What should I call this work?
228
228
  1. <suggested slug from context>
229
229
  2. <alternative slug>
230
230
  3. Other: (name it)
231
231
  \`\`\`
232
232
 
233
233
  Wait for the reply, then branch.
234
+ - **The user explicitly requested the protected branch** → that request is consent. Record it with \`echo '{branch}' > .egregore-branch-consent\`, then retry. Never create the marker merely to silence the hook.
235
+
236
+ Memory, managed-repo, and runtime-state writes should bypass the project guard. If one triggers it, correct the operation target/context instead of asking for protected-branch consent.
234
237
 
235
238
  If this Codex build does not support hooks, follow the same discipline as a standing instruction: never write or commit on develop/main/master.
236
239
 
@@ -274,6 +277,14 @@ const RULES = {
274
277
  action: 'keep',
275
278
  note: 'Kept; /update, /contribute, /save tokens rendered as $skill tokens.',
276
279
  },
280
+ 'Cross-Runtime Compatibility': {
281
+ action: 'keep',
282
+ note: 'Kept verbatim — framework parity applies equally to Claude Code, Codex, and Pi.',
283
+ },
284
+ 'Voice': {
285
+ action: 'keep',
286
+ note: 'Kept — all runtimes can consult the shared voice rules and register-specific skills.',
287
+ },
277
288
  'On Launch — MANDATORY FIRST ACTION': {
278
289
  action: 'replace',
279
290
  body: ON_LAUNCH_BODY,
@@ -43,6 +43,15 @@ source "$SCRIPT_DIR/bin/lib/hash.sh"
43
43
  source "$SCRIPT_DIR/bin/lib/time.sh"
44
44
  source "$SCRIPT_DIR/bin/lib/identity.sh" >/dev/null 2>/dev/null
45
45
 
46
+ # Keep the durable local identity current before gathering context, then project
47
+ # it to Supabase + Neo4j in the background so startup is never network-blocked.
48
+ if [ -f "$STATE_FILE" ] && [ -f "$SCRIPT_DIR/bin/person.py" ]; then
49
+ python3 "$SCRIPT_DIR/bin/person.py" sync-local >/dev/null 2>&1 || true
50
+ fi
51
+ if [ -f "$SCRIPT_DIR/bin/person.sh" ]; then
52
+ ( bash "$SCRIPT_DIR/bin/person.sh" sync >/dev/null 2>&1 & ) 2>/dev/null
53
+ fi
54
+
46
55
  LOCAL_MODE="false"
47
56
  [ "$(_detect_mode)" = "local" ] && LOCAL_MODE="true"
48
57
 
@@ -59,6 +68,10 @@ source "$SCRIPT_DIR/bin/lib/context.sh" >/dev/null 2>/dev/null
59
68
  source "$SCRIPT_DIR/bin/lib/metrics.sh" >/dev/null 2>/dev/null
60
69
  trap 'rm -rf "${CTX_DIR:-}"' EXIT
61
70
 
71
+ # Rescue non-coding leftovers from sessions that died without ceremony
72
+ # (terminal closed). Gated + idle-guarded inside the script; fire-and-forget.
73
+ ( bash "$SCRIPT_DIR/bin/session-autosave.sh" --sweep >/dev/null 2>&1 & ) 2>/dev/null
74
+
62
75
  read_json() {
63
76
  local file="$1"
64
77
  local expr="$2"
@@ -95,8 +108,9 @@ render_pending_line() {
95
108
  else
96
109
  printf ' ◐ %s pending %s - bin/agent.sh answer\n' "$PENDING_Q_COUNT" "$noun"
97
110
  fi
98
- elif [ "$ADDRESSED_COUNT" -gt 0 ] 2>/dev/null; then
99
- printf ' ◇ %s handoffs for %s - bin/agent.sh activity --for %s\n' "$ADDRESSED_COUNT" "$AUTHOR" "$AUTHOR"
111
+ fi
112
+ if [ "$ADDRESSED_COUNT" -gt 0 ] 2>/dev/null; then
113
+ printf ' ◇ %s handoffs for %s - say "show my handoffs" to review or close\n' "$ADDRESSED_COUNT" "$AUTHOR"
100
114
  fi
101
115
  }
102
116
 
@@ -154,7 +168,35 @@ render_card() {
154
168
  printf '%s%*s%s\n' "$footer_left" "$pad" "" "$footer_right"
155
169
  fi
156
170
 
157
- if [ -n "$SLUG" ]; then
171
+ # Autosave trail — background saves since the last greeting (same ledger
172
+ # the Claude greeting reads; sweep rescues become visible here).
173
+ autosave_log="${EGREGORE_AUTOSAVE_LOG:-$HOME/.egregore/autosave.log}"
174
+ autosave_seen="${autosave_log}.seen"
175
+ if [ -f "$autosave_log" ]; then
176
+ total_as=$(grep -c '|autosave|' "$autosave_log" 2>/dev/null || echo 0)
177
+ seen_as=$(cat "$autosave_seen" 2>/dev/null || echo 0)
178
+ case "$seen_as" in ''|*[!0-9]*) seen_as=0 ;; esac
179
+ if [ "$total_as" -gt "$seen_as" ]; then
180
+ new_as=$((total_as - seen_as))
181
+ merged_as=$(grep '|autosave|' "$autosave_log" 2>/dev/null | tail -n "$new_as" | grep -c '|merged|' || echo 0)
182
+ if [ "$merged_as" -ge "$new_as" ]; then
183
+ printf ' ⟲ auto-saved %s non-coding save(s) → develop while you were away\n' "$new_as"
184
+ else
185
+ printf ' ⟲ auto-saved %s non-coding save(s) — %s merged, rest in open PRs\n' "$new_as" "$merged_as"
186
+ fi
187
+ fi
188
+ printf '%s' "$total_as" > "$autosave_seen" 2>/dev/null || true
189
+ fi
190
+
191
+ # Greeting links. Org config `pinned_links` in egregore.json replaces the
192
+ # default board link — same contract as bin/lib/greeting.sh.
193
+ pinned_links=$(jq -c '.pinned_links // []' "$CONFIG" 2>/dev/null || echo "[]")
194
+ if [ "$(printf '%s' "$pinned_links" | jq 'length' 2>/dev/null || echo 0)" -gt 0 ]; then
195
+ printf '%s' "$pinned_links" | jq -r '.[] |
196
+ if type == "string" then " ◆ \(.)"
197
+ elif (.label // "") != "" then " ◆ \(.label): \(.url)"
198
+ else " ◆ \(.url)" end' 2>/dev/null
199
+ elif [ -n "$SLUG" ]; then
158
200
  board_url="https://egregore.xyz/view/${SLUG}/board"
159
201
  printf ' ◆ %s\n' "$board_url"
160
202
  fi
@@ -143,7 +143,9 @@ function renderActivity(data) {
143
143
  const graph = classifyGraph(data, { mode: data.mode || 'connected', attempt: 'retry' });
144
144
  const org = data.org || data.organization || 'Egregore';
145
145
  const date = data.date || '';
146
- const me = data.me?.github || data.me?.name || data.github_username || '';
146
+ const me = typeof data.me === 'string'
147
+ ? data.me
148
+ : data.me?.github || data.me?.name || data.github_username || '';
147
149
  const handoffs = firstArray(data, ['handoffs_to_me', 'handoffs', 'pending_handoffs', 'me.handoffs', 'addressed_handoffs']);
148
150
  const questions = firstArray(data, ['questions', 'pending_questions', 'me.questions']);
149
151
  const prs = asArray(data.prs);
@@ -169,8 +171,20 @@ function renderActivity(data) {
169
171
  }
170
172
  if (handoffs.length > 0) {
171
173
  lines.push(padLine('HANDOFFS'));
172
- handoffs.slice(0, 4).forEach((h) => {
173
- lines.push(padLine(`○ ${valueOf(h, ['author', 'from'], 'someone')}: ${valueOf(h, ['topic', 'title'], 'handoff')}`));
174
+ const glyph = { pending: '●', read: '◐', claimed: '◆', done: '✓', completed: '✓', expired: '×' };
175
+ handoffs.slice(0, 6).forEach((h, index) => {
176
+ const status = valueOf(h, ['status'], 'pending');
177
+ const age = valueOf(h, ['ageDays', 'age_days'], '');
178
+ const intent = valueOf(h, ['intent'], '');
179
+ const meta = [
180
+ age === '' ? '' : `${age}d`,
181
+ intent && intent !== 'unclassified' ? intent : '',
182
+ status,
183
+ ].filter(Boolean).join(' · ');
184
+ lines.push(padLine(
185
+ `[${index + 1}] ${glyph[status] || '○'} ⇌ ${valueOf(h, ['author', 'from'], 'someone')}: ${valueOf(h, ['topic', 'title'], 'handoff')}`,
186
+ meta,
187
+ ));
174
188
  });
175
189
  }
176
190
  }
@@ -197,7 +211,12 @@ function renderActivity(data) {
197
211
  }
198
212
 
199
213
  lines.push(rule());
200
- lines.push(padLine('Focus choices: handoffs, questions, recent work, dashboard, done'));
214
+ if (handoffs.length > 0) {
215
+ lines.push(padLine('Handoff actions: done N · expire N · reopen N'));
216
+ lines.push(padLine('Type a number to act, or keep working.'));
217
+ } else {
218
+ lines.push(padLine('Focus choices: questions, recent work, dashboard, done'));
219
+ }
201
220
  lines.push(bottomRule());
202
221
  return lines.join('\n');
203
222
  }