create-egregore 0.19.8 → 0.19.12

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 (92) hide show
  1. package/assets/egregore-launcher.js +266 -4
  2. package/lib/local.js +11 -1
  3. package/lib/setup.js +33 -0
  4. package/package.json +2 -2
  5. package/runtime/codex/.codex/hooks/branch-guard.js +242 -34
  6. package/runtime/codex/.codex/skills/activity/SKILL.md +18 -6
  7. package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +90 -55
  8. package/runtime/codex/.codex/skills/deploy-bridge/SKILL.md +25 -0
  9. package/runtime/codex/.codex/skills/handoff/SKILL.md +6 -2
  10. package/runtime/codex/.codex/skills/save/SKILL.md +29 -5
  11. package/runtime/codex/.codex/skills/scroll/SKILL.md +54 -3
  12. package/runtime/codex/.codex/skills/task/SKILL.md +25 -0
  13. package/runtime/codex/.codex/skills/tui-design/SKILL.md +1 -1
  14. package/runtime/codex/.codex/skills/wrap/SKILL.md +4 -1
  15. package/runtime/codex/.codex/spec-manifest.json +19 -7
  16. package/runtime/codex/AGENTS.md +63 -19
  17. package/runtime/codex/bin/activity-data.sh +1 -1
  18. package/runtime/codex/bin/agent.sh +88 -25
  19. package/runtime/codex/bin/attendant.sh +525 -0
  20. package/runtime/codex/bin/capture-reconcile.sh +67 -0
  21. package/runtime/codex/bin/capture-run.sh +341 -0
  22. package/runtime/codex/bin/codex-render-spec.mjs +29 -14
  23. package/runtime/codex/bin/codex-session-start.sh +49 -4
  24. package/runtime/codex/bin/codex-skill-render.mjs +23 -4
  25. package/runtime/codex/bin/graph-op.sh +719 -49
  26. package/runtime/codex/bin/graph-wal.sh +3 -2
  27. package/runtime/codex/bin/handoff-run.sh +72 -0
  28. package/runtime/codex/bin/handoff-save-egregore.sh +51 -11
  29. package/runtime/codex/bin/index-handoff.sh +26 -17
  30. package/runtime/codex/bin/ingest-graph.sh +80 -0
  31. package/runtime/codex/bin/ingest-surface.html +429 -0
  32. package/runtime/codex/bin/ingest.py +614 -0
  33. package/runtime/codex/bin/ingest.sh +231 -0
  34. package/runtime/codex/bin/ingest_graph.py +776 -0
  35. package/runtime/codex/bin/ingest_surface.py +464 -0
  36. package/runtime/codex/bin/lib/git-sync.sh +2 -6
  37. package/runtime/codex/bin/lib/greeting.sh +157 -22
  38. package/runtime/codex/bin/lib/identity.sh +50 -29
  39. package/runtime/codex/bin/lib/noncode.sh +31 -0
  40. package/runtime/codex/bin/person.py +610 -0
  41. package/runtime/codex/bin/person.sh +448 -0
  42. package/runtime/codex/bin/session-autosave.sh +184 -0
  43. package/runtime/codex/bin/session-log.sh +199 -0
  44. package/runtime/codex/bin/settings.sh +1 -1
  45. package/runtime/codex/manifest.json +19 -1
  46. package/runtime/pi/.codex/hooks/branch-guard.js +242 -34
  47. package/runtime/pi/.codex/skills/activity/SKILL.md +18 -6
  48. package/runtime/pi/.codex/skills/deep-reflect/SKILL.md +90 -55
  49. package/runtime/pi/.codex/skills/deploy-bridge/SKILL.md +25 -0
  50. package/runtime/pi/.codex/skills/handoff/SKILL.md +6 -2
  51. package/runtime/pi/.codex/skills/save/SKILL.md +29 -5
  52. package/runtime/pi/.codex/skills/scroll/SKILL.md +54 -3
  53. package/runtime/pi/.codex/skills/task/SKILL.md +25 -0
  54. package/runtime/pi/.codex/skills/tui-design/SKILL.md +1 -1
  55. package/runtime/pi/.codex/skills/wrap/SKILL.md +4 -1
  56. package/runtime/pi/.codex/spec-manifest.json +19 -7
  57. package/runtime/pi/.pi/APPEND_SYSTEM.md +57 -14
  58. package/runtime/pi/.pi/extensions/egregore.ts +98 -8
  59. package/runtime/pi/.pi/spec-manifest.json +2 -2
  60. package/runtime/pi/AGENTS.md +63 -19
  61. package/runtime/pi/bin/activity-data.sh +1 -1
  62. package/runtime/pi/bin/agent.sh +88 -25
  63. package/runtime/pi/bin/attendant.sh +525 -0
  64. package/runtime/pi/bin/capture-reconcile.sh +67 -0
  65. package/runtime/pi/bin/capture-run.sh +341 -0
  66. package/runtime/pi/bin/codex-render-spec.mjs +29 -14
  67. package/runtime/pi/bin/codex-session-start.sh +49 -4
  68. package/runtime/pi/bin/codex-skill-render.mjs +23 -4
  69. package/runtime/pi/bin/graph-op.sh +719 -49
  70. package/runtime/pi/bin/graph-wal.sh +3 -2
  71. package/runtime/pi/bin/handoff-run.sh +72 -0
  72. package/runtime/pi/bin/handoff-save-egregore.sh +51 -11
  73. package/runtime/pi/bin/index-handoff.sh +26 -17
  74. package/runtime/pi/bin/ingest-graph.sh +80 -0
  75. package/runtime/pi/bin/ingest-surface.html +429 -0
  76. package/runtime/pi/bin/ingest.py +614 -0
  77. package/runtime/pi/bin/ingest.sh +231 -0
  78. package/runtime/pi/bin/ingest_graph.py +776 -0
  79. package/runtime/pi/bin/ingest_surface.py +464 -0
  80. package/runtime/pi/bin/lib/git-sync.sh +2 -6
  81. package/runtime/pi/bin/lib/greeting.sh +157 -22
  82. package/runtime/pi/bin/lib/identity.sh +50 -29
  83. package/runtime/pi/bin/lib/noncode.sh +31 -0
  84. package/runtime/pi/bin/person.py +610 -0
  85. package/runtime/pi/bin/person.sh +448 -0
  86. package/runtime/pi/bin/pi-branch-policy.mjs +164 -4
  87. package/runtime/pi/bin/pi-product-workflows.mjs +31 -2
  88. package/runtime/pi/bin/publish-artifact.sh +14 -0
  89. package/runtime/pi/bin/session-autosave.sh +184 -0
  90. package/runtime/pi/bin/session-log.sh +199 -0
  91. package/runtime/pi/bin/settings.sh +274 -0
  92. 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"
@@ -79,7 +79,8 @@ Use the runtime-neutral bridge instead of Claude Code slash commands:
79
79
 
80
80
  \`\`\`bash
81
81
  bin/agent.sh branch --topic "auth review"
82
- bin/agent.sh save --message "Save: auth review" --topic "auth review"
82
+ bin/agent.sh save --message "Save: auth review" --topic "auth review" \\
83
+ --pr-body "$PR_BODY" # body per .claude/context/pr-format.md (auto-skeleton if omitted)
83
84
  bin/agent.sh wrap --from alice --topic "auth review" \\
84
85
  --summary "Implemented the OAuth callback parser and documented follow-ups." \\
85
86
  --body "Open threads: review error cases and browser redirects."
@@ -218,19 +219,22 @@ If on develop after two messages, create a branch immediately from whatever cont
218
219
 
219
220
  ### Branch-guard protocol
220
221
 
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:
222
+ 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
223
 
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):
224
+ - **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.
225
+ - **Topic is genuinely ambiguous** → ask only for the topic, using compact numbered options:
225
226
 
226
227
  \`\`\`text
227
- Need a working branch first. Topic?
228
+ What should I call this work?
228
229
  1. <suggested slug from context>
229
230
  2. <alternative slug>
230
231
  3. Other: (name it)
231
232
  \`\`\`
232
233
 
233
234
  Wait for the reply, then branch.
235
+ - **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.
236
+
237
+ 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
238
 
235
239
  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
240
 
@@ -248,20 +252,23 @@ Codex has no structured question tool — render each batch as compact numbered
248
252
 
249
253
  const LOOM_ROUTING_BODY = `Loom routes commands across model tiers on the Claude Code runtime (\`loom/routes.json\` + \`bin/loom.sh\` + a model-pinned executor subagent). Codex has no subagent delegation — every command runs inline in the current session. Ignore "Loom routing" preambles if you encounter them in a skill spec, and skip \`bin/loom.sh\` calls; \`loom/\` and \`.claude/agents/\` are framework files synced for the Claude runtime.`;
250
254
 
251
- const ISOLATION_BODY = `Sessions are confined to this project + memory + managed repos. On Codex this boundary is a standing instruction, not an enforced hook — hold it yourself.
255
+ const ISOLATION_BODY = `Sessions are confined to this project + memory + managed repos, with a **two-tier boundary** — a hard wall between Egregore instances, a consent gate for everything else. On Codex the boundary is a standing instruction, not an enforced hook — hold it yourself.
252
256
 
253
- - **Never modify \`~/.egregore/instances.json\`** — managed by session-start.sh
254
- - **Never access another instance's files** — refuse even if asked
255
- - When a request points outside the boundary, do not improvise a workaround. Ask in plain text with numbered options:
257
+ - **Hard tier — other Egregore instances.** Denied for every tool, always. There is no consent path. Never access another instance's files — refuse even if asked. Never modify \`~/.egregore/instances.json\` (managed by session-start.sh). When a request points at another instance's files there is nothing to ask — refuse and explain.
258
+ - **Soft tier — paths outside the boundary.** Consent-gated. Inbox dirs (\`~/Downloads\`, \`~/Desktop\`) are readable without consent under the default posture; writes outside the project always need consent. Posture (\`strict | standard | open\`) and extra read roots come from \`egregore.json\` -> \`boundary { posture, read[], locked }\` (org, committed) merged with \`.egregore-boundary.local.json\` (personal, gitignored). \`locked: true\` removes the consent path entirely.
259
+ - When a request needs soft-tier consent, do not improvise a workaround. Ask in plain text with numbered options:
256
260
 
257
261
  \`\`\`text
258
262
  That path is outside this Egregore's boundary. How should we proceed?
259
- 1. Paste the contents inline
260
- 2. Move the file into the repo and point me at the new path
261
- 3. Cancel
263
+ 1. Allow its directory for this session (recorded in .egregore-boundary-consent)
264
+ 2. Always allow on this instance (added to read[] in .egregore-boundary.local.json)
265
+ 3. Paste the contents inline
266
+ 4. Cancel
262
267
  \`\`\`
263
268
 
264
- See DEVELOPMENT.md §3 for boundary details.`;
269
+ Never record a consent grant without the user's explicit choice of option 1 or 2 in that exchange. Session grants are cleared on session start; \`locked: true\` removes options 1 and 2.
270
+
271
+ See DEVELOPMENT.md §3 for boundary details and \`memory/knowledge/decisions/2026-07-08-boundary-hook-consent-design.md\` for the design decisions.`;
265
272
 
266
273
  // ── per-section rules ────────────────────────────────────────────────────
267
274
  const RULES = {
@@ -274,6 +281,14 @@ const RULES = {
274
281
  action: 'keep',
275
282
  note: 'Kept; /update, /contribute, /save tokens rendered as $skill tokens.',
276
283
  },
284
+ 'Cross-Runtime Compatibility': {
285
+ action: 'keep',
286
+ note: 'Kept verbatim — framework parity applies equally to Claude Code, Codex, and Pi.',
287
+ },
288
+ 'Voice': {
289
+ action: 'keep',
290
+ note: 'Kept — all runtimes can consult the shared voice rules and register-specific skills.',
291
+ },
277
292
  'On Launch — MANDATORY FIRST ACTION': {
278
293
  action: 'replace',
279
294
  body: ON_LAUNCH_BODY,
@@ -315,7 +330,7 @@ const RULES = {
315
330
  'Environment Isolation': {
316
331
  action: 'replace',
317
332
  body: ISOLATION_BODY,
318
- note: 'boundary-check is a Claude PreToolUse hook with no .codex equivalent — behavior stated as a standing instruction; AskUserQuestion remediation -> numbered options.',
333
+ note: 'boundary-check is a Claude PreToolUse hook with no .codex equivalent — two-tier model stated as a standing instruction; AskUserQuestion remediation -> numbered options, now including the two consent-grant options (session grant -> .egregore-boundary-consent, instance grant -> .egregore-boundary.local.json read[]).',
319
334
  },
320
335
  };
321
336
 
@@ -6,7 +6,7 @@ set -o pipefail
6
6
  # libraries as Claude's SessionStart, then renders a terminal greeting before
7
7
  # Codex itself starts.
8
8
 
9
- FRAMEWORK_VERSION="6"
9
+ FRAMEWORK_VERSION="7"
10
10
  SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
11
11
  cd "$SCRIPT_DIR" || exit 1
12
12
 
@@ -21,6 +21,9 @@ fi
21
21
  # Clear any branch-guard consent from a previous session — consent to write on
22
22
  # a protected branch is asked fresh each session (see AGENTS.md branch-guard protocol).
23
23
  rm -f "$SCRIPT_DIR/.egregore-branch-consent" "$MAIN_PROJECT_DIR/.egregore-branch-consent" 2>/dev/null
24
+ # Same for boundary-crossing consent — grants are session-scoped (AGENTS.md
25
+ # environment-isolation protocol); durable grants belong in .egregore-boundary.local.json.
26
+ rm -f "$SCRIPT_DIR/.egregore-boundary-consent" "$MAIN_PROJECT_DIR/.egregore-boundary-consent" 2>/dev/null
24
27
 
25
28
  if [ -f "$SCRIPT_DIR/bin/lib/worktree-links.sh" ]; then
26
29
  source "$SCRIPT_DIR/bin/lib/worktree-links.sh" >/dev/null 2>/dev/null || true
@@ -43,6 +46,15 @@ source "$SCRIPT_DIR/bin/lib/hash.sh"
43
46
  source "$SCRIPT_DIR/bin/lib/time.sh"
44
47
  source "$SCRIPT_DIR/bin/lib/identity.sh" >/dev/null 2>/dev/null
45
48
 
49
+ # Keep the durable local identity current before gathering context, then project
50
+ # it to Supabase + Neo4j in the background so startup is never network-blocked.
51
+ if [ -f "$STATE_FILE" ] && [ -f "$SCRIPT_DIR/bin/person.py" ]; then
52
+ python3 "$SCRIPT_DIR/bin/person.py" sync-local >/dev/null 2>&1 || true
53
+ fi
54
+ if [ -f "$SCRIPT_DIR/bin/person.sh" ]; then
55
+ ( bash "$SCRIPT_DIR/bin/person.sh" sync >/dev/null 2>&1 & ) 2>/dev/null
56
+ fi
57
+
46
58
  LOCAL_MODE="false"
47
59
  [ "$(_detect_mode)" = "local" ] && LOCAL_MODE="true"
48
60
 
@@ -59,6 +71,10 @@ source "$SCRIPT_DIR/bin/lib/context.sh" >/dev/null 2>/dev/null
59
71
  source "$SCRIPT_DIR/bin/lib/metrics.sh" >/dev/null 2>/dev/null
60
72
  trap 'rm -rf "${CTX_DIR:-}"' EXIT
61
73
 
74
+ # Rescue non-coding leftovers from sessions that died without ceremony
75
+ # (terminal closed). Gated + idle-guarded inside the script; fire-and-forget.
76
+ ( bash "$SCRIPT_DIR/bin/session-autosave.sh" --sweep >/dev/null 2>&1 & ) 2>/dev/null
77
+
62
78
  read_json() {
63
79
  local file="$1"
64
80
  local expr="$2"
@@ -95,8 +111,9 @@ render_pending_line() {
95
111
  else
96
112
  printf ' ◐ %s pending %s - bin/agent.sh answer\n' "$PENDING_Q_COUNT" "$noun"
97
113
  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"
114
+ fi
115
+ if [ "$ADDRESSED_COUNT" -gt 0 ] 2>/dev/null; then
116
+ printf ' ◇ %s handoffs for %s - say "show my handoffs" to review or close\n' "$ADDRESSED_COUNT" "$AUTHOR"
100
117
  fi
101
118
  }
102
119
 
@@ -154,7 +171,35 @@ render_card() {
154
171
  printf '%s%*s%s\n' "$footer_left" "$pad" "" "$footer_right"
155
172
  fi
156
173
 
157
- if [ -n "$SLUG" ]; then
174
+ # Autosave trail — background saves since the last greeting (same ledger
175
+ # the Claude greeting reads; sweep rescues become visible here).
176
+ autosave_log="${EGREGORE_AUTOSAVE_LOG:-$HOME/.egregore/autosave.log}"
177
+ autosave_seen="${autosave_log}.seen"
178
+ if [ -f "$autosave_log" ]; then
179
+ total_as=$(grep -c '|autosave|' "$autosave_log" 2>/dev/null || echo 0)
180
+ seen_as=$(cat "$autosave_seen" 2>/dev/null || echo 0)
181
+ case "$seen_as" in ''|*[!0-9]*) seen_as=0 ;; esac
182
+ if [ "$total_as" -gt "$seen_as" ]; then
183
+ new_as=$((total_as - seen_as))
184
+ merged_as=$(grep '|autosave|' "$autosave_log" 2>/dev/null | tail -n "$new_as" | grep -c '|merged|' || echo 0)
185
+ if [ "$merged_as" -ge "$new_as" ]; then
186
+ printf ' ⟲ auto-saved %s non-coding save(s) → develop while you were away\n' "$new_as"
187
+ else
188
+ printf ' ⟲ auto-saved %s non-coding save(s) — %s merged, rest in open PRs\n' "$new_as" "$merged_as"
189
+ fi
190
+ fi
191
+ printf '%s' "$total_as" > "$autosave_seen" 2>/dev/null || true
192
+ fi
193
+
194
+ # Greeting links. Org config `pinned_links` in egregore.json replaces the
195
+ # default board link — same contract as bin/lib/greeting.sh.
196
+ pinned_links=$(jq -c '.pinned_links // []' "$CONFIG" 2>/dev/null || echo "[]")
197
+ if [ "$(printf '%s' "$pinned_links" | jq 'length' 2>/dev/null || echo 0)" -gt 0 ]; then
198
+ printf '%s' "$pinned_links" | jq -r '.[] |
199
+ if type == "string" then " ◆ \(.)"
200
+ elif (.label // "") != "" then " ◆ \(.label): \(.url)"
201
+ else " ◆ \(.url)" end' 2>/dev/null
202
+ elif [ -n "$SLUG" ]; then
158
203
  board_url="https://egregore.xyz/view/${SLUG}/board"
159
204
  printf ' ◆ %s\n' "$board_url"
160
205
  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
  }