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.
- package/assets/egregore-launcher.js +266 -4
- package/lib/local.js +11 -1
- package/lib/setup.js +33 -0
- package/package.json +2 -2
- package/runtime/codex/.codex/hooks/branch-guard.js +242 -34
- package/runtime/codex/.codex/skills/activity/SKILL.md +18 -6
- package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +90 -55
- package/runtime/codex/.codex/skills/deploy-bridge/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/handoff/SKILL.md +6 -2
- package/runtime/codex/.codex/skills/save/SKILL.md +29 -5
- package/runtime/codex/.codex/skills/scroll/SKILL.md +54 -3
- package/runtime/codex/.codex/skills/task/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/tui-design/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/wrap/SKILL.md +4 -1
- package/runtime/codex/.codex/spec-manifest.json +19 -7
- package/runtime/codex/AGENTS.md +63 -19
- package/runtime/codex/bin/activity-data.sh +1 -1
- package/runtime/codex/bin/agent.sh +88 -25
- package/runtime/codex/bin/attendant.sh +525 -0
- package/runtime/codex/bin/capture-reconcile.sh +67 -0
- package/runtime/codex/bin/capture-run.sh +341 -0
- package/runtime/codex/bin/codex-render-spec.mjs +29 -14
- package/runtime/codex/bin/codex-session-start.sh +49 -4
- package/runtime/codex/bin/codex-skill-render.mjs +23 -4
- package/runtime/codex/bin/graph-op.sh +719 -49
- package/runtime/codex/bin/graph-wal.sh +3 -2
- package/runtime/codex/bin/handoff-run.sh +72 -0
- package/runtime/codex/bin/handoff-save-egregore.sh +51 -11
- package/runtime/codex/bin/index-handoff.sh +26 -17
- package/runtime/codex/bin/ingest-graph.sh +80 -0
- package/runtime/codex/bin/ingest-surface.html +429 -0
- package/runtime/codex/bin/ingest.py +614 -0
- package/runtime/codex/bin/ingest.sh +231 -0
- package/runtime/codex/bin/ingest_graph.py +776 -0
- package/runtime/codex/bin/ingest_surface.py +464 -0
- package/runtime/codex/bin/lib/git-sync.sh +2 -6
- package/runtime/codex/bin/lib/greeting.sh +157 -22
- package/runtime/codex/bin/lib/identity.sh +50 -29
- package/runtime/codex/bin/lib/noncode.sh +31 -0
- package/runtime/codex/bin/person.py +610 -0
- package/runtime/codex/bin/person.sh +448 -0
- package/runtime/codex/bin/session-autosave.sh +184 -0
- package/runtime/codex/bin/session-log.sh +199 -0
- package/runtime/codex/bin/settings.sh +1 -1
- package/runtime/codex/manifest.json +19 -1
- package/runtime/pi/.codex/hooks/branch-guard.js +242 -34
- package/runtime/pi/.codex/skills/activity/SKILL.md +18 -6
- package/runtime/pi/.codex/skills/deep-reflect/SKILL.md +90 -55
- package/runtime/pi/.codex/skills/deploy-bridge/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/handoff/SKILL.md +6 -2
- package/runtime/pi/.codex/skills/save/SKILL.md +29 -5
- package/runtime/pi/.codex/skills/scroll/SKILL.md +54 -3
- package/runtime/pi/.codex/skills/task/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/tui-design/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/wrap/SKILL.md +4 -1
- package/runtime/pi/.codex/spec-manifest.json +19 -7
- package/runtime/pi/.pi/APPEND_SYSTEM.md +57 -14
- package/runtime/pi/.pi/extensions/egregore.ts +98 -8
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +63 -19
- package/runtime/pi/bin/activity-data.sh +1 -1
- package/runtime/pi/bin/agent.sh +88 -25
- package/runtime/pi/bin/attendant.sh +525 -0
- package/runtime/pi/bin/capture-reconcile.sh +67 -0
- package/runtime/pi/bin/capture-run.sh +341 -0
- package/runtime/pi/bin/codex-render-spec.mjs +29 -14
- package/runtime/pi/bin/codex-session-start.sh +49 -4
- package/runtime/pi/bin/codex-skill-render.mjs +23 -4
- package/runtime/pi/bin/graph-op.sh +719 -49
- package/runtime/pi/bin/graph-wal.sh +3 -2
- package/runtime/pi/bin/handoff-run.sh +72 -0
- package/runtime/pi/bin/handoff-save-egregore.sh +51 -11
- package/runtime/pi/bin/index-handoff.sh +26 -17
- package/runtime/pi/bin/ingest-graph.sh +80 -0
- package/runtime/pi/bin/ingest-surface.html +429 -0
- package/runtime/pi/bin/ingest.py +614 -0
- package/runtime/pi/bin/ingest.sh +231 -0
- package/runtime/pi/bin/ingest_graph.py +776 -0
- package/runtime/pi/bin/ingest_surface.py +464 -0
- package/runtime/pi/bin/lib/git-sync.sh +2 -6
- package/runtime/pi/bin/lib/greeting.sh +157 -22
- package/runtime/pi/bin/lib/identity.sh +50 -29
- package/runtime/pi/bin/lib/noncode.sh +31 -0
- package/runtime/pi/bin/person.py +610 -0
- package/runtime/pi/bin/person.sh +448 -0
- package/runtime/pi/bin/pi-branch-policy.mjs +164 -4
- package/runtime/pi/bin/pi-product-workflows.mjs +31 -2
- package/runtime/pi/bin/publish-artifact.sh +14 -0
- package/runtime/pi/bin/session-autosave.sh +184 -0
- package/runtime/pi/bin/session-log.sh +199 -0
- package/runtime/pi/bin/settings.sh +274 -0
- 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\`)
|
|
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
|
|
224
|
-
- **Topic is ambiguous**
|
|
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
|
-
|
|
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
|
|
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
|
|
254
|
-
- **
|
|
255
|
-
- When a request
|
|
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.
|
|
260
|
-
2.
|
|
261
|
-
3.
|
|
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
|
-
|
|
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 —
|
|
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="
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
}
|