create-egregore 0.19.3 → 0.19.6

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 (154) hide show
  1. package/assets/egregore-launcher.js +410 -26
  2. package/bin/cli.js +34 -20
  3. package/lib/local.js +84 -11
  4. package/lib/pi-runtime.js +186 -0
  5. package/lib/setup.js +56 -11
  6. package/lib/ui.js +1 -1
  7. package/package.json +8 -6
  8. package/runtime/codex/.codex/hooks/search-hint.js +65 -0
  9. package/runtime/codex/.codex/hooks.json +13 -0
  10. package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +14 -10
  11. package/runtime/codex/.codex/skills/minion/SKILL.md +25 -0
  12. package/runtime/codex/.codex/skills/scroll/SKILL.md +168 -0
  13. package/runtime/codex/.codex/skills/scroll/agents/openai.yaml +4 -0
  14. package/runtime/codex/.codex/skills/scroll/assets/shell.html +542 -0
  15. package/runtime/codex/.codex/skills/search/SKILL.md +56 -0
  16. package/runtime/codex/.codex/skills/session-view/SKILL.md +25 -0
  17. package/runtime/codex/.codex/skills/thread/SKILL.md +25 -0
  18. package/runtime/codex/.codex/skills/view/SKILL.md +147 -32
  19. package/runtime/codex/.codex/skills/view/agents/openai.yaml +4 -0
  20. package/runtime/codex/.codex/skills/view/assets/compose-scaffold.html +221 -0
  21. package/runtime/codex/.codex/spec-manifest.json +4 -4
  22. package/runtime/codex/AGENTS.md +31 -5
  23. package/runtime/codex/bin/activity-data.sh +46 -4
  24. package/runtime/codex/bin/agent.sh +10 -2
  25. package/runtime/codex/bin/codex-install-skills.sh +1 -1
  26. package/runtime/codex/bin/codex-render-spec.mjs +24 -2
  27. package/runtime/codex/bin/codex-session-start.sh +6 -60
  28. package/runtime/codex/bin/codex-skill-render.mjs +20 -12
  29. package/runtime/codex/bin/codex-sync-skills.sh +4 -0
  30. package/runtime/codex/bin/graph-op.sh +56 -5
  31. package/runtime/codex/bin/graph.sh +36 -0
  32. package/runtime/codex/bin/handoff-run.sh +157 -7
  33. package/runtime/codex/bin/handoff-save-egregore.sh +6 -1
  34. package/runtime/codex/bin/index-handoff.sh +9 -3
  35. package/runtime/codex/bin/lib/context.sh +97 -18
  36. package/runtime/codex/bin/lib/dashboard-artifact.sh +1 -1
  37. package/runtime/codex/bin/lib/git-sync.sh +35 -9
  38. package/runtime/codex/bin/lib/greeting.sh +30 -104
  39. package/runtime/codex/bin/lib/metrics.sh +374 -0
  40. package/runtime/codex/bin/publish-artifact.sh +43 -1
  41. package/runtime/codex/bin/settings.sh +274 -0
  42. package/runtime/codex/manifest.json +18 -1
  43. package/runtime/pi/.codex/config.toml +15 -0
  44. package/runtime/pi/.codex/hooks/branch-guard.js +242 -0
  45. package/runtime/pi/.codex/hooks/search-hint.js +65 -0
  46. package/runtime/pi/.codex/hooks.json +30 -0
  47. package/runtime/pi/.codex/skills/activity/SKILL.md +70 -0
  48. package/runtime/pi/.codex/skills/add/SKILL.md +25 -0
  49. package/runtime/pi/.codex/skills/announce/SKILL.md +54 -0
  50. package/runtime/pi/.codex/skills/answer/SKILL.md +25 -0
  51. package/runtime/pi/.codex/skills/archive/SKILL.md +41 -0
  52. package/runtime/pi/.codex/skills/ask/SKILL.md +59 -0
  53. package/runtime/pi/.codex/skills/branch/SKILL.md +25 -0
  54. package/runtime/pi/.codex/skills/checkup/SKILL.md +41 -0
  55. package/runtime/pi/.codex/skills/commit/SKILL.md +25 -0
  56. package/runtime/pi/.codex/skills/contribute/SKILL.md +25 -0
  57. package/runtime/pi/.codex/skills/dark-mode/SKILL.md +25 -0
  58. package/runtime/pi/.codex/skills/dashboard/SKILL.md +59 -0
  59. package/runtime/pi/.codex/skills/deep-reflect/SKILL.md +74 -0
  60. package/runtime/pi/.codex/skills/delete-user/SKILL.md +25 -0
  61. package/runtime/pi/.codex/skills/egregore-voice/SKILL.md +25 -0
  62. package/runtime/pi/.codex/skills/emissary/SKILL.md +41 -0
  63. package/runtime/pi/.codex/skills/env/SKILL.md +25 -0
  64. package/runtime/pi/.codex/skills/handoff/SKILL.md +67 -0
  65. package/runtime/pi/.codex/skills/harvest/SKILL.md +107 -0
  66. package/runtime/pi/.codex/skills/ingest/SKILL.md +25 -0
  67. package/runtime/pi/.codex/skills/ingest-google/SKILL.md +25 -0
  68. package/runtime/pi/.codex/skills/ingest-user-interview/SKILL.md +41 -0
  69. package/runtime/pi/.codex/skills/invite/SKILL.md +40 -0
  70. package/runtime/pi/.codex/skills/issue/SKILL.md +41 -0
  71. package/runtime/pi/.codex/skills/me/SKILL.md +25 -0
  72. package/runtime/pi/.codex/skills/meeting/SKILL.md +41 -0
  73. package/runtime/pi/.codex/skills/minion/SKILL.md +25 -0
  74. package/runtime/pi/.codex/skills/note/SKILL.md +25 -0
  75. package/runtime/pi/.codex/skills/onboarding/SKILL.md +41 -0
  76. package/runtime/pi/.codex/skills/pr/SKILL.md +25 -0
  77. package/runtime/pi/.codex/skills/project/SKILL.md +41 -0
  78. package/runtime/pi/.codex/skills/pull/SKILL.md +25 -0
  79. package/runtime/pi/.codex/skills/push/SKILL.md +25 -0
  80. package/runtime/pi/.codex/skills/quest/SKILL.md +77 -0
  81. package/runtime/pi/.codex/skills/quest-suggest/SKILL.md +25 -0
  82. package/runtime/pi/.codex/skills/reflect/SKILL.md +41 -0
  83. package/runtime/pi/.codex/skills/review-pr/SKILL.md +41 -0
  84. package/runtime/pi/.codex/skills/save/SKILL.md +62 -0
  85. package/runtime/pi/.codex/skills/scroll/SKILL.md +168 -0
  86. package/runtime/pi/.codex/skills/scroll/agents/openai.yaml +4 -0
  87. package/runtime/pi/.codex/skills/scroll/assets/shell.html +542 -0
  88. package/runtime/pi/.codex/skills/search/SKILL.md +56 -0
  89. package/runtime/pi/.codex/skills/session-view/SKILL.md +25 -0
  90. package/runtime/pi/.codex/skills/setup/SKILL.md +25 -0
  91. package/runtime/pi/.codex/skills/summon/SKILL.md +41 -0
  92. package/runtime/pi/.codex/skills/sync-repos/SKILL.md +25 -0
  93. package/runtime/pi/.codex/skills/telegram-connect/SKILL.md +25 -0
  94. package/runtime/pi/.codex/skills/telemetry/SKILL.md +25 -0
  95. package/runtime/pi/.codex/skills/test/SKILL.md +41 -0
  96. package/runtime/pi/.codex/skills/the-spiral/SKILL.md +84 -0
  97. package/runtime/pi/.codex/skills/thread/SKILL.md +25 -0
  98. package/runtime/pi/.codex/skills/todo/SKILL.md +41 -0
  99. package/runtime/pi/.codex/skills/tui-design/SKILL.md +41 -0
  100. package/runtime/pi/.codex/skills/tutorial/SKILL.md +41 -0
  101. package/runtime/pi/.codex/skills/update/SKILL.md +25 -0
  102. package/runtime/pi/.codex/skills/view/SKILL.md +156 -0
  103. package/runtime/pi/.codex/skills/view/agents/openai.yaml +4 -0
  104. package/runtime/pi/.codex/skills/view/assets/compose-scaffold.html +221 -0
  105. package/runtime/pi/.codex/skills/visual-explain/SKILL.md +41 -0
  106. package/runtime/pi/.codex/skills/wrap/SKILL.md +55 -0
  107. package/runtime/pi/.codex/spec-manifest.json +114 -0
  108. package/runtime/pi/.pi/APPEND_SYSTEM.md +255 -0
  109. package/runtime/pi/.pi/extensions/egregore.ts +324 -0
  110. package/runtime/pi/.pi/settings.json +6 -0
  111. package/runtime/pi/.pi/spec-manifest.json +12 -0
  112. package/runtime/pi/AGENTS.md +350 -0
  113. package/runtime/pi/bin/activity-data.sh +242 -0
  114. package/runtime/pi/bin/agent.sh +677 -0
  115. package/runtime/pi/bin/codex-install-skills.sh +78 -0
  116. package/runtime/pi/bin/codex-render-spec.mjs +394 -0
  117. package/runtime/pi/bin/codex-session-start.sh +188 -0
  118. package/runtime/pi/bin/codex-skill-render.mjs +323 -0
  119. package/runtime/pi/bin/codex-sync-skills.sh +227 -0
  120. package/runtime/pi/bin/dashboard-data.sh +178 -0
  121. package/runtime/pi/bin/graph-batch.sh +60 -0
  122. package/runtime/pi/bin/graph-op.sh +431 -0
  123. package/runtime/pi/bin/graph-wal.sh +228 -0
  124. package/runtime/pi/bin/graph.sh +177 -0
  125. package/runtime/pi/bin/handoff-pr-backfill.sh +86 -0
  126. package/runtime/pi/bin/handoff-run.sh +581 -0
  127. package/runtime/pi/bin/handoff-save-egregore.sh +136 -0
  128. package/runtime/pi/bin/index-handoff.sh +312 -0
  129. package/runtime/pi/bin/lib/artifact-id.sh +46 -0
  130. package/runtime/pi/bin/lib/config.sh +71 -0
  131. package/runtime/pi/bin/lib/context.sh +542 -0
  132. package/runtime/pi/bin/lib/dashboard-artifact.sh +142 -0
  133. package/runtime/pi/bin/lib/ensure-develop.sh +183 -0
  134. package/runtime/pi/bin/lib/git-sync.sh +288 -0
  135. package/runtime/pi/bin/lib/greeting.sh +340 -0
  136. package/runtime/pi/bin/lib/hash.sh +13 -0
  137. package/runtime/pi/bin/lib/identity.sh +174 -0
  138. package/runtime/pi/bin/lib/metrics.sh +374 -0
  139. package/runtime/pi/bin/lib/permissions.sh +106 -0
  140. package/runtime/pi/bin/lib/time.sh +56 -0
  141. package/runtime/pi/bin/lib/worktree-links.sh +54 -0
  142. package/runtime/pi/bin/notify.sh +207 -0
  143. package/runtime/pi/bin/pi-branch-policy.mjs +15 -0
  144. package/runtime/pi/bin/pi-product-workflows.mjs +324 -0
  145. package/runtime/pi/bin/pi-render-spec.mjs +114 -0
  146. package/runtime/pi/bin/pi-session-start.sh +24 -0
  147. package/runtime/pi/bin/publish-artifact.sh +201 -0
  148. package/runtime/pi/bin/publish-references.sh +144 -0
  149. package/runtime/pi/bin/repo-state.sh +99 -0
  150. package/runtime/pi/bin/telemetry.sh +391 -0
  151. package/runtime/pi/bin/worktree-create.sh +118 -0
  152. package/runtime/pi/bin/worktree-remove.sh +50 -0
  153. package/runtime/pi/bin/worktree.sh +198 -0
  154. package/runtime/pi/manifest.json +176 -0
@@ -0,0 +1,228 @@
1
+ #!/bin/bash
2
+ # Graph Write-Ahead Log — resilient graph writes with local buffer.
3
+ # Mirrors bin/telemetry.sh architecture.
4
+ #
5
+ # Subcommands:
6
+ # append <cypher> <params_json> Append entry to local JSONL buffer (no network)
7
+ # drain Execute pending entries via graph-batch.sh
8
+ # status Show pending count + file size as JSON
9
+ #
10
+ # Storage: ~/.egregore/graph-wal.jsonl
11
+ # Buffer guard: 2MB max, auto-truncate to last 200 entries if exceeded.
12
+ set -euo pipefail
13
+
14
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
15
+
16
+ if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
17
+ echo "Usage: graph-wal.sh <command>"
18
+ echo ""
19
+ echo "Graph Write-Ahead Log — resilient graph writes with local buffer."
20
+ echo ""
21
+ echo "Commands:"
22
+ echo " append <cypher> <params> Append entry to WAL (no network)"
23
+ echo " drain Execute pending entries via graph-batch"
24
+ echo " status Show pending count + file size (JSON)"
25
+ exit 0
26
+ fi
27
+
28
+ WAL_DIR="$HOME/.egregore"
29
+ # WAL file is per-instance to prevent cross-org data leakage on multi-instance machines
30
+ PROJ_HASH=$(echo -n "$SCRIPT_DIR" | md5 2>/dev/null || echo -n "$SCRIPT_DIR" | md5sum 2>/dev/null | cut -d' ' -f1)
31
+ WAL_FILE="$WAL_DIR/graph-wal-${PROJ_HASH}.jsonl"
32
+ MAX_BUFFER_BYTES=2097152 # 2MB
33
+ MAX_ENTRIES_AFTER_TRUNCATE=200
34
+ LOCK_DIR="$WAL_DIR/graph-wal-${PROJ_HASH}.lock"
35
+
36
+ # --- Cross-platform file locking via mkdir (atomic on all POSIX systems) ---
37
+ _lock() {
38
+ local attempts=0
39
+ while ! mkdir "$LOCK_DIR" 2>/dev/null; do
40
+ attempts=$((attempts + 1))
41
+ if [ "$attempts" -gt 50 ]; then
42
+ # Stale lock — check if holder PID is alive
43
+ local holder_pid
44
+ holder_pid=$(cat "$LOCK_DIR/pid" 2>/dev/null || echo "")
45
+ if [ -n "$holder_pid" ] && ! kill -0 "$holder_pid" 2>/dev/null; then
46
+ rm -rf "$LOCK_DIR" 2>/dev/null
47
+ continue
48
+ fi
49
+ echo "graph-wal: lock timeout after 5s" >&2
50
+ return 1
51
+ fi
52
+ sleep 0.1
53
+ done
54
+ echo $$ > "$LOCK_DIR/pid" 2>/dev/null
55
+ }
56
+
57
+ _unlock() {
58
+ rm -rf "$LOCK_DIR" 2>/dev/null
59
+ }
60
+
61
+ trap '_unlock' EXIT
62
+
63
+ # --- Resolve session ID (same pattern as telemetry.sh) ---
64
+ _resolve_session_id() {
65
+ if [ -n "${EGREGORE_SESSION_ID:-}" ]; then
66
+ echo "$EGREGORE_SESSION_ID"
67
+ return
68
+ fi
69
+ local proj_hash
70
+ proj_hash=$(echo -n "$SCRIPT_DIR" | md5 2>/dev/null || echo -n "$SCRIPT_DIR" | md5sum 2>/dev/null | cut -d' ' -f1)
71
+ local sid_file="$WAL_DIR/session-${proj_hash}.id"
72
+ if [ -f "$sid_file" ]; then
73
+ cat "$sid_file" 2>/dev/null || echo "unknown"
74
+ else
75
+ echo "unknown"
76
+ fi
77
+ }
78
+
79
+ # --- append: O(1) local write, no network ---
80
+ cmd_append() {
81
+ local cypher="${1:?Usage: graph-wal.sh append <cypher> <params_json>}"
82
+ local params="${2:-"{}"}"
83
+
84
+ mkdir -p "$WAL_DIR"
85
+
86
+ local ts
87
+ ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
88
+ local sid
89
+ sid=$(_resolve_session_id)
90
+
91
+ # Build JSON line
92
+ local line
93
+ line=$(jq -n -c \
94
+ --arg ts "$ts" \
95
+ --arg sid "$sid" \
96
+ --arg cypher "$cypher" \
97
+ --argjson params "$params" \
98
+ '{ts: $ts, sid: $sid, cypher: $cypher, params: $params}')
99
+
100
+ # Append under lock — if lock fails, append anyway (durability > mutual exclusion)
101
+ local _locked=true
102
+ _lock || _locked=false
103
+ echo "$line" >> "$WAL_FILE"
104
+
105
+ # Buffer guard: truncate to last N entries if file exceeds max size
106
+ # Only safe under lock — tail+mv is not atomic and races with concurrent writers
107
+ if [ "$_locked" = "true" ] && [ -f "$WAL_FILE" ]; then
108
+ local size
109
+ size=$(wc -c < "$WAL_FILE" 2>/dev/null | tr -d ' ')
110
+ if [ "$size" -gt "$MAX_BUFFER_BYTES" ] 2>/dev/null; then
111
+ local total_entries
112
+ total_entries=$(wc -l < "$WAL_FILE" 2>/dev/null | tr -d ' ')
113
+ local dropped=$((total_entries - MAX_ENTRIES_AFTER_TRUNCATE))
114
+ if [ "$dropped" -gt 0 ]; then
115
+ echo "graph-wal: buffer exceeded ${MAX_BUFFER_BYTES} bytes. Dropping $dropped oldest entries (keeping $MAX_ENTRIES_AFTER_TRUNCATE)." >&2
116
+ fi
117
+ local tmp="$WAL_FILE.truncate.$$"
118
+ tail -n "$MAX_ENTRIES_AFTER_TRUNCATE" "$WAL_FILE" > "$tmp" 2>/dev/null \
119
+ && mv "$tmp" "$WAL_FILE" \
120
+ || rm -f "$tmp"
121
+ fi
122
+ fi
123
+ [ "$_locked" = "true" ] && _unlock
124
+ }
125
+
126
+ # --- drain: execute pending entries via graph-batch.sh ---
127
+ cmd_drain() {
128
+ if [ ! -f "$WAL_FILE" ] || [ ! -s "$WAL_FILE" ]; then
129
+ return 0
130
+ fi
131
+
132
+ # Atomic rotate under lock: move to temp file so new appends don't conflict
133
+ _lock || return 0
134
+ local drain_file="$WAL_FILE.drain.$$"
135
+ mv "$WAL_FILE" "$drain_file" 2>/dev/null || { _unlock; return 0; }
136
+ _unlock
137
+
138
+ local total
139
+ total=$(wc -l < "$drain_file" 2>/dev/null | tr -d ' ')
140
+ local batch_size=20
141
+ local offset=0
142
+ local all_ok=true
143
+
144
+ while [ "$offset" -lt "$total" ]; do
145
+ # Extract batch of entries (offset+1 through offset+batch_size)
146
+ local batch_lines
147
+ batch_lines=$(tail -n "+$((offset + 1))" "$drain_file" | head -n "$batch_size")
148
+
149
+ # Build batch JSON array: [{statement, parameters}, ...]
150
+ local batch_json
151
+ batch_json=$(echo "$batch_lines" | jq -s '[.[] | {statement: .cypher, parameters: .params}]' 2>/dev/null)
152
+
153
+ if [ -z "$batch_json" ] || [ "$batch_json" = "[]" ]; then
154
+ offset=$((offset + batch_size))
155
+ continue
156
+ fi
157
+
158
+ # Execute via graph-batch.sh
159
+ if bash "$SCRIPT_DIR/bin/graph-batch.sh" "$batch_json" >/dev/null 2>&1; then
160
+ offset=$((offset + batch_size))
161
+ else
162
+ all_ok=false
163
+ break
164
+ fi
165
+ done
166
+
167
+ if [ "$all_ok" = "true" ]; then
168
+ # All batches succeeded — remove drain file
169
+ rm -f "$drain_file"
170
+ else
171
+ # Failure — merge unprocessed entries back
172
+ # Extract remaining entries (from offset+1 to end)
173
+ local remaining
174
+ remaining=$(tail -n "+$((offset + 1))" "$drain_file" 2>/dev/null)
175
+
176
+ if [ -n "$remaining" ]; then
177
+ _lock || { echo "$remaining" > "$WAL_FILE" 2>/dev/null; rm -f "$drain_file"; return 1; }
178
+ if [ -f "$WAL_FILE" ]; then
179
+ # New entries were written while draining; prepend remaining
180
+ local merge_file="$WAL_FILE.merge.$$"
181
+ { echo "$remaining"; cat "$WAL_FILE"; } > "$merge_file" 2>/dev/null \
182
+ && mv "$merge_file" "$WAL_FILE" \
183
+ && rm -f "$drain_file" \
184
+ || rm -f "$merge_file"
185
+ else
186
+ echo "$remaining" > "$WAL_FILE"
187
+ rm -f "$drain_file"
188
+ fi
189
+ _unlock
190
+ else
191
+ rm -f "$drain_file"
192
+ fi
193
+ fi
194
+ }
195
+
196
+ # --- status: pending count + file size as JSON ---
197
+ cmd_status() {
198
+ if [ -f "$WAL_FILE" ] && [ -s "$WAL_FILE" ]; then
199
+ local count size
200
+ count=$(wc -l < "$WAL_FILE" 2>/dev/null | tr -d ' ')
201
+ size=$(wc -c < "$WAL_FILE" 2>/dev/null | tr -d ' ')
202
+ jq -n --argjson pending "$count" --argjson bytes "$size" '{pending: $pending, bytes: $bytes}'
203
+ else
204
+ echo '{"pending":0,"bytes":0}'
205
+ fi
206
+ }
207
+
208
+ # --- Main dispatch ---
209
+ case "${1:-status}" in
210
+ append)
211
+ shift
212
+ cmd_append "$@"
213
+ ;;
214
+ drain)
215
+ cmd_drain
216
+ ;;
217
+ status)
218
+ cmd_status
219
+ ;;
220
+ *)
221
+ echo "Usage: graph-wal.sh <command>"
222
+ echo ""
223
+ echo "Commands:"
224
+ echo " append <cypher> <params> Append entry to WAL (no network)"
225
+ echo " drain Execute pending entries"
226
+ echo " status Show pending count + file size"
227
+ ;;
228
+ esac
@@ -0,0 +1,177 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
5
+ CONFIG="$SCRIPT_DIR/egregore.json"
6
+
7
+ if [ ! -f "$CONFIG" ]; then
8
+ echo "Error: egregore.json not found. Run onboarding first." >&2
9
+ exit 1
10
+ fi
11
+
12
+ # --- Local mode gate: bail immediately, no .env sourcing, no network ---
13
+ _MODE=$(jq -r '.mode // "connected"' "$CONFIG" 2>/dev/null)
14
+ if [ "$_MODE" = "local" ]; then
15
+ case "${1:-}" in
16
+ query) echo '{"results":[]}';;
17
+ schema) echo '{}';;
18
+ test) echo '{"status":"offline","reason":"local_mode"}';;
19
+ *) echo '{"results":[]}';;
20
+ esac
21
+ exit 0
22
+ fi
23
+
24
+ # Load specific variables from .env if it exists (safe extraction, no arbitrary code execution)
25
+ if [ -f "$SCRIPT_DIR/.env" ]; then
26
+ EGREGORE_API_URL="${EGREGORE_API_URL:-$(grep '^EGREGORE_API_URL=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
27
+ EGREGORE_API_KEY="${EGREGORE_API_KEY:-$(grep '^EGREGORE_API_KEY=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
28
+ fi
29
+
30
+ # api_url comes from egregore.json (committed, non-secret)
31
+ # api_key comes from .env only (EGREGORE_API_KEY) — never from egregore.json
32
+ API_URL="${EGREGORE_API_URL:-$(jq -r '.api_url // empty' "$CONFIG")}"
33
+ API_KEY="${EGREGORE_API_KEY:-}"
34
+
35
+ # --- Millisecond timer (macOS + Linux compatible) ---
36
+ _millis() {
37
+ if command -v gdate &>/dev/null; then
38
+ gdate +%s%3N
39
+ else
40
+ # macOS date doesn't support %N — use python3 fallback
41
+ python3 -c "import time; print(int(time.time() * 1000))" 2>/dev/null || echo "0"
42
+ fi
43
+ }
44
+
45
+ # --- Optional read-through cache (opt-in via EGREGORE_GRAPH_CACHE_TTL) ---
46
+ # Read-only queries can be served from a local cache that bin/attendant.sh
47
+ # keeps warm in the background, so callers on the launch path (session-start
48
+ # greeting) pay zero network round-trips when warm. TTL in seconds; unset/0
49
+ # disables. Mutating queries are never cached.
50
+ _CACHE_TTL="${EGREGORE_GRAPH_CACHE_TTL:-0}"
51
+ _CACHE_DIR="$HOME/.egregore/graph-cache/$(echo -n "$SCRIPT_DIR" | cksum | cut -d' ' -f1)"
52
+
53
+ _cache_key() {
54
+ echo -n "$1|${2:-}" | cksum | cut -d' ' -f1
55
+ }
56
+
57
+ _is_read_query() {
58
+ ! echo "$1" | grep -qiE '\b(MERGE|CREATE|SET|DELETE|DETACH|REMOVE)\b'
59
+ }
60
+
61
+ if [ -n "$API_URL" ] && [ -n "$API_KEY" ]; then
62
+ # === API MODE: Call Egregore API gateway ===
63
+
64
+ run_query() {
65
+ local cypher="$1"
66
+ local params
67
+ if [ -n "${2:-}" ]; then
68
+ params="$2"
69
+ else
70
+ params="{}"
71
+ fi
72
+
73
+ local cache_file=""
74
+ if [ "$_CACHE_TTL" != "0" ] && _is_read_query "$cypher"; then
75
+ cache_file="$_CACHE_DIR/$(_cache_key "$cypher" "$params").json"
76
+ if [ -f "$cache_file" ]; then
77
+ local now cts
78
+ now=$(date +%s)
79
+ cts=$(jq -r '.ts // 0' "$cache_file" 2>/dev/null || echo 0)
80
+ if [ $(( now - cts )) -lt "$_CACHE_TTL" ]; then
81
+ jq -c '.result' "$cache_file" 2>/dev/null && return 0
82
+ fi
83
+ fi
84
+ fi
85
+
86
+ local body
87
+ body=$(jq -n --arg stmt "$cypher" --argjson params "$params" \
88
+ '{statement: $stmt, parameters: $params}')
89
+
90
+ local start_ms
91
+ start_ms=$(_millis)
92
+
93
+ local response
94
+ response=$(curl -s -X POST "${API_URL}/api/graph/query" \
95
+ -H "Authorization: Bearer $API_KEY" \
96
+ -H "Content-Type: application/json" \
97
+ -d "$body" \
98
+ --max-time 30)
99
+
100
+ local end_ms
101
+ end_ms=$(_millis)
102
+ local latency_ms=$(( end_ms - start_ms ))
103
+
104
+ # Check for HTTP-level errors
105
+ if echo "$response" | jq -e '.detail' >/dev/null 2>&1; then
106
+ echo "API error:" >&2
107
+ echo "$response" | jq '.detail' >&2
108
+ bash "$SCRIPT_DIR/bin/telemetry.sh" emit "error" \
109
+ "$(jq -n --arg source "graph" --arg error_code "api_error" --argjson latency "$latency_ms" \
110
+ '{source: $source, error_code: $error_code, latency_ms: $latency}')" 2>/dev/null &
111
+ exit 1
112
+ fi
113
+
114
+ # Emit latency telemetry (fire-and-forget)
115
+ bash "$SCRIPT_DIR/bin/telemetry.sh" emit "graph_query" \
116
+ "$(jq -n --argjson latency "$latency_ms" '{latency_ms: $latency}')" 2>/dev/null &
117
+
118
+ # Store for the attendant to keep warm (query+params travel with the result)
119
+ if [ -n "$cache_file" ]; then
120
+ mkdir -p "$_CACHE_DIR" 2>/dev/null
121
+ jq -n --arg q "$cypher" --argjson p "$params" --argjson ts "$(date +%s)" --argjson r "$response" \
122
+ '{query: $q, params: $p, ts: $ts, result: $r}' > "$cache_file" 2>/dev/null || true
123
+ fi
124
+
125
+ echo "$response"
126
+ }
127
+
128
+ get_schema() {
129
+ local response
130
+ response=$(curl -s -X GET "${API_URL}/api/graph/schema" \
131
+ -H "Authorization: Bearer $API_KEY" \
132
+ --max-time 30)
133
+ echo "$response" | jq .
134
+ }
135
+
136
+ test_connection() {
137
+ local response
138
+ response=$(curl -s -X GET "${API_URL}/api/graph/test" \
139
+ -H "Authorization: Bearer $API_KEY" \
140
+ --max-time 10)
141
+
142
+ if echo "$response" | jq -e '.status == "ok"' >/dev/null 2>&1; then
143
+ echo "Connected to Egregore API at $API_URL"
144
+ else
145
+ echo "Failed to connect to Egregore API at $API_URL" >&2
146
+ echo "$response" >&2
147
+ exit 1
148
+ fi
149
+ }
150
+
151
+ else
152
+ # === OFFLINE MODE: No API key — return empty results (OSS/local) ===
153
+ run_query() { echo '{"results":[]}'; }
154
+ get_schema() { echo '{}'; }
155
+ test_connection() { echo '{"status":"offline","reason":"no_api_key"}'; }
156
+ fi
157
+
158
+ case "${1:-help}" in
159
+ query)
160
+ shift
161
+ run_query "$@"
162
+ ;;
163
+ schema)
164
+ get_schema
165
+ ;;
166
+ test)
167
+ test_connection
168
+ ;;
169
+ help|*)
170
+ echo "Usage: graph.sh <command>"
171
+ echo ""
172
+ echo "Commands:"
173
+ echo " query <cypher> [params_json] Run a Cypher query"
174
+ echo " schema Show graph schema"
175
+ echo " test Test connection"
176
+ ;;
177
+ esac
@@ -0,0 +1,86 @@
1
+ #!/bin/bash
2
+ # handoff-pr-backfill.sh — fill in PR numbers in a handoff file's ## Repo State
3
+ # table, then re-commit + push the memory repo. Detached fire-and-forget from
4
+ # /handoff's hot path so user-visible latency doesn't include N × `gh pr list`.
5
+ #
6
+ # Usage:
7
+ # handoff-pr-backfill.sh <abs-handoff-file> <topic>
8
+ #
9
+ # Reads the ## Repo State table, looks up each (repo, branch)'s open PR via
10
+ # `gh pr list`, rewrites the PR column from `—` to `#N`, commits the memory
11
+ # repo, pushes to main. Silent on all failures — this is cosmetic.
12
+ #
13
+ # Detached usage (the skill / orchestrator call it this way):
14
+ # ( bash bin/handoff-pr-backfill.sh "$ABS_FILE" "$TOPIC" >/dev/null 2>&1 & ) >/dev/null 2>&1
15
+
16
+ set -uo pipefail
17
+
18
+ ABS_FILE="${1:-}"
19
+ TOPIC="${2:-handoff}"
20
+
21
+ [ -n "$ABS_FILE" ] && [ -f "$ABS_FILE" ] || exit 0
22
+
23
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
24
+ CONFIG="$SCRIPT_DIR/egregore.json"
25
+
26
+ command -v gh >/dev/null 2>&1 || exit 0
27
+ command -v jq >/dev/null 2>&1 || exit 0
28
+
29
+ GITHUB_ORG=$(jq -r '.github_org // empty' "$CONFIG" 2>/dev/null)
30
+ [ -n "$GITHUB_ORG" ] || exit 0
31
+
32
+ # Extract the ## Repo State table rows: format is `| repo | branch | — | base |`.
33
+ # Skip header and separator lines (first two rows of the markdown table).
34
+ ROWS=$(awk '
35
+ /^## Repo State/ { in_section=1; next }
36
+ in_section && /^## / { in_section=0 }
37
+ in_section && /^\|/ && !/^\|[-\s]/ && !/^\| Repo/ { print }
38
+ ' "$ABS_FILE" 2>/dev/null)
39
+
40
+ [ -n "$ROWS" ] || exit 0
41
+
42
+ UPDATED=0
43
+ while IFS= read -r row; do
44
+ [ -z "$row" ] && continue
45
+ # Parse: | repo | branch | pr | base |
46
+ repo=$(echo "$row" | awk -F'|' '{gsub(/^ +| +$/,"",$2); print $2}')
47
+ branch=$(echo "$row" | awk -F'|' '{gsub(/^ +| +$/,"",$3); print $3}')
48
+ pr_col=$(echo "$row" | awk -F'|' '{gsub(/^ +| +$/,"",$4); print $4}')
49
+
50
+ # Only backfill `—` cells (leave `#N` alone)
51
+ [ "$pr_col" = "—" ] || continue
52
+ [ -n "$repo" ] && [ -n "$branch" ] || continue
53
+
54
+ pr_num=$(gh pr list --repo "$GITHUB_ORG/$repo" --head "$branch" --json number --jq '.[0].number // empty' 2>/dev/null || echo "")
55
+ [ -n "$pr_num" ] || continue
56
+
57
+ # Rewrite the `—` in this branch's row to `#N`. Match by branch name to
58
+ # avoid cross-repo collisions when repos share branch names.
59
+ awk -v branch="$branch" -v pr="#$pr_num" '
60
+ BEGIN { in_section=0 }
61
+ /^## Repo State/ { in_section=1; print; next }
62
+ in_section && /^## / { in_section=0 }
63
+ in_section && $0 ~ "\\| " branch " " && /—/ {
64
+ sub(/—/, pr)
65
+ print; next
66
+ }
67
+ { print }
68
+ ' "$ABS_FILE" > "$ABS_FILE.tmp" && mv "$ABS_FILE.tmp" "$ABS_FILE"
69
+ UPDATED=1
70
+ done <<< "$ROWS"
71
+
72
+ [ "$UPDATED" = "1" ] || exit 0
73
+
74
+ # Re-commit + push the memory repo with the backfilled PR numbers.
75
+ MEMORY_DIR="$SCRIPT_DIR/memory"
76
+ if [ -d "$MEMORY_DIR" ]; then
77
+ (
78
+ cd "$MEMORY_DIR" || exit 0
79
+ git add -A >/dev/null 2>&1
80
+ git commit -m "Backfill PR numbers for handoff: $TOPIC" >/dev/null 2>&1 || exit 0
81
+ git pull --rebase origin main --quiet >/dev/null 2>&1
82
+ git push origin main --quiet >/dev/null 2>&1
83
+ ) || true
84
+ fi
85
+
86
+ exit 0