create-egregore 0.19.32 → 0.20.0

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 (173) hide show
  1. package/assets/egregore-launcher.js +118 -13
  2. package/bin/cli.js +48 -3
  3. package/lib/connect-runtime.js +4 -2
  4. package/lib/local.js +15 -1
  5. package/lib/prime-runtime.js +208 -0
  6. package/lib/setup.js +45 -3
  7. package/package.json +5 -4
  8. package/runtime/codex/.codex/skills/ingest-notion/SKILL.md +29 -0
  9. package/runtime/codex/.codex/skills/notion-connect/SKILL.md +29 -0
  10. package/runtime/codex/.codex/skills/teams-connect/SKILL.md +29 -0
  11. package/runtime/codex/.codex/spec-manifest.json +3 -3
  12. package/runtime/codex/AGENTS.md +11 -4
  13. package/runtime/codex/bin/activity-data.sh +4 -8
  14. package/runtime/codex/bin/attendant.sh +13 -0
  15. package/runtime/codex/bin/autosave.sh +27 -1
  16. package/runtime/codex/bin/codex-render-spec.mjs +6 -0
  17. package/runtime/codex/bin/codex-session-start.sh +8 -1
  18. package/runtime/codex/bin/connect-refresh.sh +33 -0
  19. package/runtime/codex/bin/graph-batch.sh +25 -6
  20. package/runtime/codex/bin/graph-op.sh +5 -4
  21. package/runtime/codex/bin/graph.sh +31 -5
  22. package/runtime/codex/bin/ingest.py +81 -5
  23. package/runtime/codex/bin/lib/context.sh +26 -1
  24. package/runtime/codex/bin/lib/greeting.sh +4 -1
  25. package/runtime/codex/bin/lib/worktree-links.sh +25 -0
  26. package/runtime/codex/manifest.json +8 -1
  27. package/runtime/pi/.codex/skills/ingest-notion/SKILL.md +29 -0
  28. package/runtime/pi/.codex/skills/notion-connect/SKILL.md +29 -0
  29. package/runtime/pi/.codex/skills/teams-connect/SKILL.md +29 -0
  30. package/runtime/pi/.codex/spec-manifest.json +3 -3
  31. package/runtime/pi/.pi/APPEND_SYSTEM.md +3 -2
  32. package/runtime/pi/.pi/extensions/egregore.ts +17 -6
  33. package/runtime/pi/.pi/spec-manifest.json +2 -2
  34. package/runtime/pi/AGENTS.md +11 -4
  35. package/runtime/pi/bin/activity-data.sh +4 -8
  36. package/runtime/pi/bin/attendant.sh +13 -0
  37. package/runtime/pi/bin/autosave.sh +27 -1
  38. package/runtime/pi/bin/codex-render-spec.mjs +6 -0
  39. package/runtime/pi/bin/codex-session-start.sh +8 -1
  40. package/runtime/pi/bin/connect-refresh.sh +33 -0
  41. package/runtime/pi/bin/graph-batch.sh +25 -6
  42. package/runtime/pi/bin/graph-op.sh +5 -4
  43. package/runtime/pi/bin/graph.sh +31 -5
  44. package/runtime/pi/bin/ingest.py +81 -5
  45. package/runtime/pi/bin/lib/context.sh +26 -1
  46. package/runtime/pi/bin/lib/greeting.sh +4 -1
  47. package/runtime/pi/bin/lib/worktree-links.sh +25 -0
  48. package/runtime/pi/bin/pi-branch-policy.mjs +138 -1
  49. package/runtime/pi/bin/pi-product-workflows.mjs +35 -7
  50. package/runtime/pi/manifest.json +8 -1
  51. package/runtime/prime/.claude/context/notification-consent.md +66 -0
  52. package/runtime/prime/.codex/config.toml +17 -0
  53. package/runtime/prime/.codex/hooks/branch-guard.js +461 -0
  54. package/runtime/prime/.codex/hooks/search-hint.js +67 -0
  55. package/runtime/prime/.codex/hooks.json +30 -0
  56. package/runtime/prime/.codex/skills/activity/SKILL.md +82 -0
  57. package/runtime/prime/.codex/skills/announce/SKILL.md +67 -0
  58. package/runtime/prime/.codex/skills/archive/SKILL.md +45 -0
  59. package/runtime/prime/.codex/skills/branch/SKILL.md +29 -0
  60. package/runtime/prime/.codex/skills/checkup/SKILL.md +45 -0
  61. package/runtime/prime/.codex/skills/commit/SKILL.md +29 -0
  62. package/runtime/prime/.codex/skills/contribute/SKILL.md +29 -0
  63. package/runtime/prime/.codex/skills/create-skill/SKILL.md +29 -0
  64. package/runtime/prime/.codex/skills/dark-mode/SKILL.md +29 -0
  65. package/runtime/prime/.codex/skills/dashboard/SKILL.md +59 -0
  66. package/runtime/prime/.codex/skills/deep-reflect/SKILL.md +109 -0
  67. package/runtime/prime/.codex/skills/delete-user/SKILL.md +29 -0
  68. package/runtime/prime/.codex/skills/emissary/SKILL.md +45 -0
  69. package/runtime/prime/.codex/skills/handoff/SKILL.md +166 -0
  70. package/runtime/prime/.codex/skills/harvest/QUESTION_PALETTE.md +76 -0
  71. package/runtime/prime/.codex/skills/harvest/SKILL.md +131 -0
  72. package/runtime/prime/.codex/skills/ingest/SKILL.md +29 -0
  73. package/runtime/prime/.codex/skills/ingest-notion/SKILL.md +29 -0
  74. package/runtime/prime/.codex/skills/ingest-user-interview/SKILL.md +45 -0
  75. package/runtime/prime/.codex/skills/invite/SKILL.md +44 -0
  76. package/runtime/prime/.codex/skills/issue/SKILL.md +45 -0
  77. package/runtime/prime/.codex/skills/me/SKILL.md +29 -0
  78. package/runtime/prime/.codex/skills/meeting/SKILL.md +45 -0
  79. package/runtime/prime/.codex/skills/note/SKILL.md +29 -0
  80. package/runtime/prime/.codex/skills/notion-connect/SKILL.md +29 -0
  81. package/runtime/prime/.codex/skills/onboarding/SKILL.md +45 -0
  82. package/runtime/prime/.codex/skills/pr/SKILL.md +29 -0
  83. package/runtime/prime/.codex/skills/pull/SKILL.md +29 -0
  84. package/runtime/prime/.codex/skills/push/SKILL.md +29 -0
  85. package/runtime/prime/.codex/skills/quest/SKILL.md +80 -0
  86. package/runtime/prime/.codex/skills/reflect/SKILL.md +45 -0
  87. package/runtime/prime/.codex/skills/review-pr/SKILL.md +45 -0
  88. package/runtime/prime/.codex/skills/save/SKILL.md +125 -0
  89. package/runtime/prime/.codex/skills/slack-connect/SKILL.md +29 -0
  90. package/runtime/prime/.codex/skills/summon/SKILL.md +45 -0
  91. package/runtime/prime/.codex/skills/sync-repos/SKILL.md +29 -0
  92. package/runtime/prime/.codex/skills/teams-connect/SKILL.md +29 -0
  93. package/runtime/prime/.codex/skills/telegram-connect/SKILL.md +29 -0
  94. package/runtime/prime/.codex/skills/telemetry/SKILL.md +29 -0
  95. package/runtime/prime/.codex/skills/test/SKILL.md +45 -0
  96. package/runtime/prime/.codex/skills/the-spiral/SKILL.md +84 -0
  97. package/runtime/prime/.codex/skills/tui-design/SKILL.md +45 -0
  98. package/runtime/prime/.codex/skills/tutorial/SKILL.md +45 -0
  99. package/runtime/prime/.codex/skills/update/SKILL.md +29 -0
  100. package/runtime/prime/.codex/skills/view/SKILL.md +156 -0
  101. package/runtime/prime/.codex/skills/view/agents/openai.yaml +4 -0
  102. package/runtime/prime/.codex/skills/view/assets/compose-scaffold.html +221 -0
  103. package/runtime/prime/.codex/skills/visual-explain/SKILL.md +45 -0
  104. package/runtime/prime/.codex/skills/wrap/SKILL.md +58 -0
  105. package/runtime/prime/.codex/spec-manifest.json +132 -0
  106. package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +393 -0
  107. package/runtime/prime/.prime/agent/extensions/egregore.ts +460 -0
  108. package/runtime/prime/.prime/agent/settings.json +4 -0
  109. package/runtime/prime/.prime/agent/skills/egregore/SKILL.md +34 -0
  110. package/runtime/prime/.prime/agent/skills/egregore/pyproject.toml +13 -0
  111. package/runtime/prime/.prime/agent/skills/egregore/src/egregore/__init__.py +156 -0
  112. package/runtime/prime/.prime/agent/spec-manifest.json +12 -0
  113. package/runtime/prime/AGENTS.md +489 -0
  114. package/runtime/prime/bin/activity-data.sh +238 -0
  115. package/runtime/prime/bin/agent.sh +775 -0
  116. package/runtime/prime/bin/attendant.sh +707 -0
  117. package/runtime/prime/bin/autosave.sh +112 -0
  118. package/runtime/prime/bin/capture-reconcile.sh +69 -0
  119. package/runtime/prime/bin/capture-run.sh +341 -0
  120. package/runtime/prime/bin/codex-install-skills.sh +78 -0
  121. package/runtime/prime/bin/codex-render-spec.mjs +440 -0
  122. package/runtime/prime/bin/codex-session-start.sh +265 -0
  123. package/runtime/prime/bin/codex-skill-render.mjs +347 -0
  124. package/runtime/prime/bin/codex-sync-skills.sh +232 -0
  125. package/runtime/prime/bin/connect-refresh.sh +33 -0
  126. package/runtime/prime/bin/dashboard-data.sh +178 -0
  127. package/runtime/prime/bin/graph-batch.sh +79 -0
  128. package/runtime/prime/bin/graph-op.sh +1102 -0
  129. package/runtime/prime/bin/graph-wal.sh +229 -0
  130. package/runtime/prime/bin/graph.sh +223 -0
  131. package/runtime/prime/bin/handoff-pr-backfill.sh +86 -0
  132. package/runtime/prime/bin/handoff-run.sh +671 -0
  133. package/runtime/prime/bin/handoff-save-egregore.sh +212 -0
  134. package/runtime/prime/bin/index-handoff.sh +322 -0
  135. package/runtime/prime/bin/ingest-graph.sh +80 -0
  136. package/runtime/prime/bin/ingest-surface.html +495 -0
  137. package/runtime/prime/bin/ingest.py +756 -0
  138. package/runtime/prime/bin/ingest.sh +279 -0
  139. package/runtime/prime/bin/ingest_graph.py +856 -0
  140. package/runtime/prime/bin/ingest_surface.py +544 -0
  141. package/runtime/prime/bin/lib/artifact-id.sh +46 -0
  142. package/runtime/prime/bin/lib/config.sh +116 -0
  143. package/runtime/prime/bin/lib/context.sh +621 -0
  144. package/runtime/prime/bin/lib/dashboard-artifact.sh +242 -0
  145. package/runtime/prime/bin/lib/ensure-develop.sh +183 -0
  146. package/runtime/prime/bin/lib/git-sync.sh +406 -0
  147. package/runtime/prime/bin/lib/greeting.sh +519 -0
  148. package/runtime/prime/bin/lib/hash.sh +13 -0
  149. package/runtime/prime/bin/lib/identity.sh +213 -0
  150. package/runtime/prime/bin/lib/metrics.sh +374 -0
  151. package/runtime/prime/bin/lib/noncode.sh +31 -0
  152. package/runtime/prime/bin/lib/permissions.sh +106 -0
  153. package/runtime/prime/bin/lib/time.sh +56 -0
  154. package/runtime/prime/bin/lib/worktree-links.sh +79 -0
  155. package/runtime/prime/bin/notify.sh +636 -0
  156. package/runtime/prime/bin/person.py +630 -0
  157. package/runtime/prime/bin/person.sh +448 -0
  158. package/runtime/prime/bin/pi-branch-policy.mjs +312 -0
  159. package/runtime/prime/bin/pi-product-workflows.mjs +394 -0
  160. package/runtime/prime/bin/prime-render-spec.mjs +123 -0
  161. package/runtime/prime/bin/prime-session-start.sh +24 -0
  162. package/runtime/prime/bin/publish-artifact.sh +271 -0
  163. package/runtime/prime/bin/publish-references.sh +144 -0
  164. package/runtime/prime/bin/repo-state.sh +96 -0
  165. package/runtime/prime/bin/search.sh +240 -0
  166. package/runtime/prime/bin/session-autosave.sh +209 -0
  167. package/runtime/prime/bin/session-log.sh +199 -0
  168. package/runtime/prime/bin/settings.sh +304 -0
  169. package/runtime/prime/bin/telemetry.sh +391 -0
  170. package/runtime/prime/bin/worktree-create.sh +128 -0
  171. package/runtime/prime/bin/worktree-remove.sh +50 -0
  172. package/runtime/prime/bin/worktree.sh +198 -0
  173. package/runtime/prime/manifest.json +178 -0
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env 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
+ LOCK_ATTEMPTS="${GRAPH_WAL_LOCK_ATTEMPTS:-50}"
36
+
37
+ # --- Cross-platform file locking via mkdir (atomic on all POSIX systems) ---
38
+ _lock() {
39
+ local attempts=0
40
+ while ! mkdir "$LOCK_DIR" 2>/dev/null; do
41
+ attempts=$((attempts + 1))
42
+ if [ "$attempts" -gt "$LOCK_ATTEMPTS" ]; then
43
+ # Stale lock — check if holder PID is alive
44
+ local holder_pid
45
+ holder_pid=$(cat "$LOCK_DIR/pid" 2>/dev/null || echo "")
46
+ if [ -n "$holder_pid" ] && ! kill -0 "$holder_pid" 2>/dev/null; then
47
+ rm -rf "$LOCK_DIR" 2>/dev/null
48
+ continue
49
+ fi
50
+ echo "graph-wal: lock timeout after ${LOCK_ATTEMPTS} attempt(s)" >&2
51
+ return 1
52
+ fi
53
+ sleep 0.1
54
+ done
55
+ echo $$ > "$LOCK_DIR/pid" 2>/dev/null
56
+ }
57
+
58
+ _unlock() {
59
+ rm -rf "$LOCK_DIR" 2>/dev/null
60
+ }
61
+
62
+ trap '_unlock' EXIT
63
+
64
+ # --- Resolve session ID (same pattern as telemetry.sh) ---
65
+ _resolve_session_id() {
66
+ if [ -n "${EGREGORE_SESSION_ID:-}" ]; then
67
+ echo "$EGREGORE_SESSION_ID"
68
+ return
69
+ fi
70
+ local proj_hash
71
+ proj_hash=$(echo -n "$SCRIPT_DIR" | md5 2>/dev/null || echo -n "$SCRIPT_DIR" | md5sum 2>/dev/null | cut -d' ' -f1)
72
+ local sid_file="$WAL_DIR/session-${proj_hash}.id"
73
+ if [ -f "$sid_file" ]; then
74
+ cat "$sid_file" 2>/dev/null || echo "unknown"
75
+ else
76
+ echo "unknown"
77
+ fi
78
+ }
79
+
80
+ # --- append: O(1) local write, no network ---
81
+ cmd_append() {
82
+ local cypher="${1:?Usage: graph-wal.sh append <cypher> <params_json>}"
83
+ local params="${2:-"{}"}"
84
+
85
+ mkdir -p "$WAL_DIR"
86
+
87
+ local ts
88
+ ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
89
+ local sid
90
+ sid=$(_resolve_session_id)
91
+
92
+ # Build JSON line
93
+ local line
94
+ line=$(jq -n -c \
95
+ --arg ts "$ts" \
96
+ --arg sid "$sid" \
97
+ --arg cypher "$cypher" \
98
+ --argjson params "$params" \
99
+ '{ts: $ts, sid: $sid, cypher: $cypher, params: $params}')
100
+
101
+ # Append under lock — if lock fails, append anyway (durability > mutual exclusion)
102
+ local _locked=true
103
+ _lock || _locked=false
104
+ echo "$line" >> "$WAL_FILE"
105
+
106
+ # Buffer guard: truncate to last N entries if file exceeds max size
107
+ # Only safe under lock — tail+mv is not atomic and races with concurrent writers
108
+ if [ "$_locked" = "true" ] && [ -f "$WAL_FILE" ]; then
109
+ local size
110
+ size=$(wc -c < "$WAL_FILE" 2>/dev/null | tr -d ' ')
111
+ if [ "$size" -gt "$MAX_BUFFER_BYTES" ] 2>/dev/null; then
112
+ local total_entries
113
+ total_entries=$(wc -l < "$WAL_FILE" 2>/dev/null | tr -d ' ')
114
+ local dropped=$((total_entries - MAX_ENTRIES_AFTER_TRUNCATE))
115
+ if [ "$dropped" -gt 0 ]; then
116
+ echo "graph-wal: buffer exceeded ${MAX_BUFFER_BYTES} bytes. Dropping $dropped oldest entries (keeping $MAX_ENTRIES_AFTER_TRUNCATE)." >&2
117
+ fi
118
+ local tmp="$WAL_FILE.truncate.$$"
119
+ tail -n "$MAX_ENTRIES_AFTER_TRUNCATE" "$WAL_FILE" > "$tmp" 2>/dev/null \
120
+ && mv "$tmp" "$WAL_FILE" \
121
+ || rm -f "$tmp"
122
+ fi
123
+ fi
124
+ [ "$_locked" = "true" ] && _unlock
125
+ }
126
+
127
+ # --- drain: execute pending entries via graph-batch.sh ---
128
+ cmd_drain() {
129
+ if [ ! -f "$WAL_FILE" ] || [ ! -s "$WAL_FILE" ]; then
130
+ return 0
131
+ fi
132
+
133
+ # Atomic rotate under lock: move to temp file so new appends don't conflict
134
+ _lock || return 0
135
+ local drain_file="$WAL_FILE.drain.$$"
136
+ mv "$WAL_FILE" "$drain_file" 2>/dev/null || { _unlock; return 0; }
137
+ _unlock
138
+
139
+ local total
140
+ total=$(wc -l < "$drain_file" 2>/dev/null | tr -d ' ')
141
+ local batch_size=20
142
+ local offset=0
143
+ local all_ok=true
144
+
145
+ while [ "$offset" -lt "$total" ]; do
146
+ # Extract batch of entries (offset+1 through offset+batch_size)
147
+ local batch_lines
148
+ batch_lines=$(tail -n "+$((offset + 1))" "$drain_file" | head -n "$batch_size")
149
+
150
+ # Build batch JSON array: [{statement, parameters}, ...]
151
+ local batch_json
152
+ batch_json=$(echo "$batch_lines" | jq -s '[.[] | {statement: .cypher, parameters: .params}]' 2>/dev/null)
153
+
154
+ if [ -z "$batch_json" ] || [ "$batch_json" = "[]" ]; then
155
+ offset=$((offset + batch_size))
156
+ continue
157
+ fi
158
+
159
+ # Execute via graph-batch.sh
160
+ if bash "$SCRIPT_DIR/bin/graph-batch.sh" "$batch_json" >/dev/null 2>&1; then
161
+ offset=$((offset + batch_size))
162
+ else
163
+ all_ok=false
164
+ break
165
+ fi
166
+ done
167
+
168
+ if [ "$all_ok" = "true" ]; then
169
+ # All batches succeeded — remove drain file
170
+ rm -f "$drain_file"
171
+ else
172
+ # Failure — merge unprocessed entries back
173
+ # Extract remaining entries (from offset+1 to end)
174
+ local remaining
175
+ remaining=$(tail -n "+$((offset + 1))" "$drain_file" 2>/dev/null)
176
+
177
+ if [ -n "$remaining" ]; then
178
+ _lock || { echo "$remaining" > "$WAL_FILE" 2>/dev/null; rm -f "$drain_file"; return 1; }
179
+ if [ -f "$WAL_FILE" ]; then
180
+ # New entries were written while draining; prepend remaining
181
+ local merge_file="$WAL_FILE.merge.$$"
182
+ { echo "$remaining"; cat "$WAL_FILE"; } > "$merge_file" 2>/dev/null \
183
+ && mv "$merge_file" "$WAL_FILE" \
184
+ && rm -f "$drain_file" \
185
+ || rm -f "$merge_file"
186
+ else
187
+ echo "$remaining" > "$WAL_FILE"
188
+ rm -f "$drain_file"
189
+ fi
190
+ _unlock
191
+ else
192
+ rm -f "$drain_file"
193
+ fi
194
+ fi
195
+ }
196
+
197
+ # --- status: pending count + file size as JSON ---
198
+ cmd_status() {
199
+ if [ -f "$WAL_FILE" ] && [ -s "$WAL_FILE" ]; then
200
+ local count size
201
+ count=$(wc -l < "$WAL_FILE" 2>/dev/null | tr -d ' ')
202
+ size=$(wc -c < "$WAL_FILE" 2>/dev/null | tr -d ' ')
203
+ jq -n --argjson pending "$count" --argjson bytes "$size" '{pending: $pending, bytes: $bytes}'
204
+ else
205
+ echo '{"pending":0,"bytes":0}'
206
+ fi
207
+ }
208
+
209
+ # --- Main dispatch ---
210
+ case "${1:-status}" in
211
+ append)
212
+ shift
213
+ cmd_append "$@"
214
+ ;;
215
+ drain)
216
+ cmd_drain
217
+ ;;
218
+ status)
219
+ cmd_status
220
+ ;;
221
+ *)
222
+ echo "Usage: graph-wal.sh <command>"
223
+ echo ""
224
+ echo "Commands:"
225
+ echo " append <cypher> <params> Append entry to WAL (no network)"
226
+ echo " drain Execute pending entries"
227
+ echo " status Show pending count + file size"
228
+ ;;
229
+ esac
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env 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
+ # A worktree missing its .env symlink resolves through worktree-links.sh, which
26
+ # self-heals the link and falls back to the main checkout's .env.
27
+ _ENV_FILE="$SCRIPT_DIR/.env"
28
+ if [ ! -f "$_ENV_FILE" ] && [ -f "$SCRIPT_DIR/bin/lib/worktree-links.sh" ]; then
29
+ # shellcheck source=bin/lib/worktree-links.sh
30
+ source "$SCRIPT_DIR/bin/lib/worktree-links.sh" 2>/dev/null || true
31
+ _ENV_FILE="$(egregore_env_file "$SCRIPT_DIR" 2>/dev/null || true)"
32
+ fi
33
+ if [ -n "$_ENV_FILE" ] && [ -f "$_ENV_FILE" ]; then
34
+ EGREGORE_API_URL="${EGREGORE_API_URL:-$(grep '^EGREGORE_API_URL=' "$_ENV_FILE" 2>/dev/null | cut -d'=' -f2- || true)}"
35
+ EGREGORE_API_KEY="${EGREGORE_API_KEY:-$(grep '^EGREGORE_API_KEY=' "$_ENV_FILE" 2>/dev/null | cut -d'=' -f2- || true)}"
36
+ fi
37
+
38
+ # api_url comes from egregore.json (committed, non-secret)
39
+ # api_key comes from .env only (EGREGORE_API_KEY) — never from egregore.json
40
+ API_URL="${EGREGORE_API_URL:-$(jq -r '.api_url // empty' "$CONFIG")}"
41
+ API_KEY="${EGREGORE_API_KEY:-}"
42
+
43
+ # --- Millisecond timer (macOS + Linux compatible) ---
44
+ _millis() {
45
+ if command -v gdate &>/dev/null; then
46
+ gdate +%s%3N
47
+ else
48
+ # macOS date doesn't support %N — use python3 fallback
49
+ python3 -c "import time; print(int(time.time() * 1000))" 2>/dev/null || echo "0"
50
+ fi
51
+ }
52
+
53
+ # --- Optional read-through cache (opt-in via EGREGORE_GRAPH_CACHE_TTL) ---
54
+ # Read-only queries can be served from a local cache that bin/attendant.sh
55
+ # keeps warm in the background, so callers on the launch path (session-start
56
+ # greeting) pay zero network round-trips when warm. TTL in seconds; unset/0
57
+ # disables. Mutating queries are never cached.
58
+ _CACHE_TTL="${EGREGORE_GRAPH_CACHE_TTL:-0}"
59
+ # Key the cache by the MAIN checkout so worktree sessions hit the same warm
60
+ # cache the attendant maintains (its replay uses the same derivation).
61
+ # Worktrees have .git as a FILE pointing into the main repo's .git/worktrees/.
62
+ # api_url + slug are part of the key so entries never cross tenants when the
63
+ # same checkout is repointed at a different org/API (cache entries are keyed
64
+ # only by query+params below, so the directory must carry the tenant).
65
+ _CACHE_ROOT="$SCRIPT_DIR"
66
+ if [ -f "$SCRIPT_DIR/.git" ]; then
67
+ _WT_GITDIR=$(sed 's/^gitdir: //' "$SCRIPT_DIR/.git" 2>/dev/null || true)
68
+ if [ -n "$_WT_GITDIR" ]; then
69
+ _CACHE_ROOT=$(cd "$_WT_GITDIR/../../.." 2>/dev/null && pwd || echo "$SCRIPT_DIR")
70
+ fi
71
+ fi
72
+ # The scope is a non-secret fingerprint of the EFFECTIVE endpoint + credential
73
+ # (already resolved above: process env → .env → committed api_url). Committed
74
+ # config alone is not enough — an EGREGORE_API_URL/KEY override or a swapped
75
+ # .env can point this checkout at a different tenant than egregore.json names,
76
+ # and the entry key below carries only query+params. cksum output is a CRC,
77
+ # not a reversible or usable credential.
78
+ _CACHE_SCOPE=$(echo -n "${API_URL:-}|${API_KEY:-}" | cksum | cut -d' ' -f1)
79
+ _CACHE_DIR="$HOME/.egregore/graph-cache/$(echo -n "${_CACHE_ROOT}|${_CACHE_SCOPE}" | cksum | cut -d' ' -f1)"
80
+
81
+ _cache_key() {
82
+ echo -n "$1|${2:-}" | cksum | cut -d' ' -f1
83
+ }
84
+
85
+ _is_read_query() {
86
+ ! echo "$1" | grep -qiE '\b(MERGE|CREATE|SET|DELETE|DETACH|REMOVE)\b'
87
+ }
88
+
89
+ if [ -n "$API_URL" ] && [ -n "$API_KEY" ]; then
90
+ # === API MODE: Call Egregore API gateway ===
91
+
92
+ run_query() {
93
+ local cypher="$1"
94
+ local params
95
+ if [ -n "${2:-}" ]; then
96
+ params="$2"
97
+ else
98
+ params="{}"
99
+ fi
100
+
101
+ local cache_file=""
102
+ if [ "$_CACHE_TTL" != "0" ] && _is_read_query "$cypher"; then
103
+ cache_file="$_CACHE_DIR/$(_cache_key "$cypher" "$params").json"
104
+ if [ -f "$cache_file" ]; then
105
+ local now cts
106
+ now=$(date +%s)
107
+ cts=$(jq -r '.ts // 0' "$cache_file" 2>/dev/null || echo 0)
108
+ if [ $(( now - cts )) -lt "$_CACHE_TTL" ]; then
109
+ jq -c '.result' "$cache_file" 2>/dev/null && return 0
110
+ fi
111
+ fi
112
+ fi
113
+
114
+ local body
115
+ body=$(jq -n --arg stmt "$cypher" --argjson params "$params" \
116
+ '{statement: $stmt, parameters: $params}')
117
+
118
+ local start_ms
119
+ start_ms=$(_millis)
120
+
121
+ local response
122
+ response=$(curl -s -X POST "${API_URL}/api/graph/query" \
123
+ -H "Authorization: Bearer $API_KEY" \
124
+ -H "Content-Type: application/json" \
125
+ -d "$body" \
126
+ --max-time 30)
127
+
128
+ local end_ms
129
+ end_ms=$(_millis)
130
+ local latency_ms=$(( end_ms - start_ms ))
131
+
132
+ # Check for HTTP-level errors
133
+ if echo "$response" | jq -e '.detail' >/dev/null 2>&1; then
134
+ echo "API error:" >&2
135
+ echo "$response" | jq '.detail' >&2
136
+ bash "$SCRIPT_DIR/bin/telemetry.sh" emit "error" \
137
+ "$(jq -n --arg source "graph" --arg error_code "api_error" --argjson latency "$latency_ms" \
138
+ '{source: $source, error_code: $error_code, latency_ms: $latency}')" 2>/dev/null &
139
+ exit 1
140
+ fi
141
+
142
+ # Emit latency telemetry (fire-and-forget)
143
+ bash "$SCRIPT_DIR/bin/telemetry.sh" emit "graph_query" \
144
+ "$(jq -n --argjson latency "$latency_ms" '{latency_ms: $latency}')" 2>/dev/null &
145
+
146
+ # Store for the attendant to keep warm (query+params travel with the result)
147
+ if [ -n "$cache_file" ]; then
148
+ mkdir -p "$_CACHE_DIR" 2>/dev/null
149
+ jq -n --arg q "$cypher" --argjson p "$params" --argjson ts "$(date +%s)" --argjson r "$response" \
150
+ '{query: $q, params: $p, ts: $ts, result: $r}' > "$cache_file" 2>/dev/null || true
151
+ fi
152
+
153
+ echo "$response"
154
+ }
155
+
156
+ get_schema() {
157
+ local response
158
+ response=$(curl -s -X GET "${API_URL}/api/graph/schema" \
159
+ -H "Authorization: Bearer $API_KEY" \
160
+ --max-time 30)
161
+ echo "$response" | jq .
162
+ }
163
+
164
+ test_connection() {
165
+ local response
166
+ response=$(curl -s -X GET "${API_URL}/api/graph/test" \
167
+ -H "Authorization: Bearer $API_KEY" \
168
+ --max-time 10)
169
+
170
+ if echo "$response" | jq -e '.status == "ok"' >/dev/null 2>&1; then
171
+ echo "Connected to Egregore API at $API_URL"
172
+ else
173
+ echo "Failed to connect to Egregore API at $API_URL" >&2
174
+ echo "$response" >&2
175
+ exit 1
176
+ fi
177
+ }
178
+
179
+ elif [ -n "$API_URL" ]; then
180
+ # === CONNECTED CONFIG, MISSING KEY ===
181
+ # api_url is configured, so the org expects a live graph, but no API key was
182
+ # found. Faking '{"results":[]}' here made a missing credential (e.g. a
183
+ # worktree without its .env symlink) indistinguishable from an empty graph —
184
+ # fail loudly instead. `test` keeps its offline JSON contract: dashboards,
185
+ # render-card, and startup-check consume it.
186
+ _missing_credentials() {
187
+ {
188
+ echo "Error: api_url is configured but EGREGORE_API_KEY is empty (looked in: ${_ENV_FILE:-$SCRIPT_DIR/.env})."
189
+ echo " If this is a worktree, its .env symlink may be missing. Run /env to configure."
190
+ } >&2
191
+ exit 1
192
+ }
193
+ run_query() { _missing_credentials; }
194
+ get_schema() { _missing_credentials; }
195
+ test_connection() { echo '{"status":"offline","reason":"no_api_key"}'; }
196
+
197
+ else
198
+ # === OFFLINE MODE: no api_url configured (OSS/unconfigured) ===
199
+ run_query() { echo '{"results":[]}'; }
200
+ get_schema() { echo '{}'; }
201
+ test_connection() { echo '{"status":"offline","reason":"no_api_key"}'; }
202
+ fi
203
+
204
+ case "${1:-help}" in
205
+ query)
206
+ shift
207
+ run_query "$@"
208
+ ;;
209
+ schema)
210
+ get_schema
211
+ ;;
212
+ test)
213
+ test_connection
214
+ ;;
215
+ help|*)
216
+ echo "Usage: graph.sh <command>"
217
+ echo ""
218
+ echo "Commands:"
219
+ echo " query <cypher> [params_json] Run a Cypher query"
220
+ echo " schema Show graph schema"
221
+ echo " test Test connection"
222
+ ;;
223
+ esac
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env 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