create-egregore 0.17.4 → 0.18.1

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 (106) hide show
  1. package/assets/egregore-launcher.js +1965 -0
  2. package/assets/egregore-launcher.sh +385 -0
  3. package/bin/cli.js +472 -81
  4. package/lib/api.js +2 -2
  5. package/lib/auth.js +255 -20
  6. package/lib/codex-runtime.js +268 -0
  7. package/lib/commands.js +31 -0
  8. package/lib/context.js +1 -1
  9. package/lib/local.js +616 -189
  10. package/lib/setup.js +822 -84
  11. package/lib/ui.js +780 -84
  12. package/package.json +12 -3
  13. package/runtime/codex/.codex/config.toml +15 -0
  14. package/runtime/codex/.codex/hooks/branch-guard.js +234 -0
  15. package/runtime/codex/.codex/hooks.json +17 -0
  16. package/runtime/codex/.codex/skills/activity/SKILL.md +70 -0
  17. package/runtime/codex/.codex/skills/add/SKILL.md +25 -0
  18. package/runtime/codex/.codex/skills/announce/SKILL.md +54 -0
  19. package/runtime/codex/.codex/skills/answer/SKILL.md +25 -0
  20. package/runtime/codex/.codex/skills/archive/SKILL.md +41 -0
  21. package/runtime/codex/.codex/skills/ask/SKILL.md +59 -0
  22. package/runtime/codex/.codex/skills/branch/SKILL.md +25 -0
  23. package/runtime/codex/.codex/skills/checkup/SKILL.md +41 -0
  24. package/runtime/codex/.codex/skills/commit/SKILL.md +25 -0
  25. package/runtime/codex/.codex/skills/contribute/SKILL.md +25 -0
  26. package/runtime/codex/.codex/skills/dark-mode/SKILL.md +25 -0
  27. package/runtime/codex/.codex/skills/dashboard/SKILL.md +59 -0
  28. package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +70 -0
  29. package/runtime/codex/.codex/skills/delete-user/SKILL.md +25 -0
  30. package/runtime/codex/.codex/skills/egregore-voice/SKILL.md +25 -0
  31. package/runtime/codex/.codex/skills/emissary/SKILL.md +41 -0
  32. package/runtime/codex/.codex/skills/env/SKILL.md +25 -0
  33. package/runtime/codex/.codex/skills/handoff/SKILL.md +67 -0
  34. package/runtime/codex/.codex/skills/harvest/SKILL.md +107 -0
  35. package/runtime/codex/.codex/skills/infra/SKILL.md +41 -0
  36. package/runtime/codex/.codex/skills/ingest/SKILL.md +25 -0
  37. package/runtime/codex/.codex/skills/ingest-google/SKILL.md +25 -0
  38. package/runtime/codex/.codex/skills/ingest-user-interview/SKILL.md +41 -0
  39. package/runtime/codex/.codex/skills/invite/SKILL.md +40 -0
  40. package/runtime/codex/.codex/skills/issue/SKILL.md +41 -0
  41. package/runtime/codex/.codex/skills/me/SKILL.md +25 -0
  42. package/runtime/codex/.codex/skills/meeting/SKILL.md +41 -0
  43. package/runtime/codex/.codex/skills/note/SKILL.md +25 -0
  44. package/runtime/codex/.codex/skills/onboarding/SKILL.md +41 -0
  45. package/runtime/codex/.codex/skills/pr/SKILL.md +25 -0
  46. package/runtime/codex/.codex/skills/project/SKILL.md +41 -0
  47. package/runtime/codex/.codex/skills/pull/SKILL.md +25 -0
  48. package/runtime/codex/.codex/skills/push/SKILL.md +25 -0
  49. package/runtime/codex/.codex/skills/quest/SKILL.md +77 -0
  50. package/runtime/codex/.codex/skills/quest-suggest/SKILL.md +25 -0
  51. package/runtime/codex/.codex/skills/reflect/SKILL.md +41 -0
  52. package/runtime/codex/.codex/skills/review-pr/SKILL.md +41 -0
  53. package/runtime/codex/.codex/skills/save/SKILL.md +62 -0
  54. package/runtime/codex/.codex/skills/setup/SKILL.md +25 -0
  55. package/runtime/codex/.codex/skills/summon/SKILL.md +41 -0
  56. package/runtime/codex/.codex/skills/sync-repos/SKILL.md +25 -0
  57. package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +25 -0
  58. package/runtime/codex/.codex/skills/telemetry/SKILL.md +25 -0
  59. package/runtime/codex/.codex/skills/test/SKILL.md +41 -0
  60. package/runtime/codex/.codex/skills/the-spiral/SKILL.md +84 -0
  61. package/runtime/codex/.codex/skills/todo/SKILL.md +41 -0
  62. package/runtime/codex/.codex/skills/tui-design/SKILL.md +41 -0
  63. package/runtime/codex/.codex/skills/tutorial/SKILL.md +41 -0
  64. package/runtime/codex/.codex/skills/update/SKILL.md +25 -0
  65. package/runtime/codex/.codex/skills/view/SKILL.md +41 -0
  66. package/runtime/codex/.codex/skills/visual-explain/SKILL.md +41 -0
  67. package/runtime/codex/.codex/skills/wrap/SKILL.md +55 -0
  68. package/runtime/codex/.codex/spec-manifest.json +108 -0
  69. package/runtime/codex/AGENTS.md +40 -0
  70. package/runtime/codex/bin/activity-data.sh +177 -0
  71. package/runtime/codex/bin/agent.sh +669 -0
  72. package/runtime/codex/bin/codex-install-skills.sh +78 -0
  73. package/runtime/codex/bin/codex-render-spec.mjs +308 -0
  74. package/runtime/codex/bin/codex-session-start.sh +238 -0
  75. package/runtime/codex/bin/codex-skill-render.mjs +315 -0
  76. package/runtime/codex/bin/codex-sync-skills.sh +223 -0
  77. package/runtime/codex/bin/dashboard-data.sh +174 -0
  78. package/runtime/codex/bin/graph-batch.sh +60 -0
  79. package/runtime/codex/bin/graph-op.sh +380 -0
  80. package/runtime/codex/bin/graph-wal.sh +228 -0
  81. package/runtime/codex/bin/graph.sh +141 -0
  82. package/runtime/codex/bin/handoff-pr-backfill.sh +86 -0
  83. package/runtime/codex/bin/handoff-run.sh +431 -0
  84. package/runtime/codex/bin/handoff-save-egregore.sh +131 -0
  85. package/runtime/codex/bin/index-handoff.sh +306 -0
  86. package/runtime/codex/bin/lib/artifact-id.sh +46 -0
  87. package/runtime/codex/bin/lib/config.sh +71 -0
  88. package/runtime/codex/bin/lib/context.sh +463 -0
  89. package/runtime/codex/bin/lib/dashboard-artifact.sh +142 -0
  90. package/runtime/codex/bin/lib/ensure-develop.sh +183 -0
  91. package/runtime/codex/bin/lib/git-sync.sh +262 -0
  92. package/runtime/codex/bin/lib/greeting.sh +414 -0
  93. package/runtime/codex/bin/lib/hash.sh +13 -0
  94. package/runtime/codex/bin/lib/identity.sh +164 -0
  95. package/runtime/codex/bin/lib/permissions.sh +106 -0
  96. package/runtime/codex/bin/lib/time.sh +56 -0
  97. package/runtime/codex/bin/lib/worktree-links.sh +54 -0
  98. package/runtime/codex/bin/notify.sh +207 -0
  99. package/runtime/codex/bin/publish-artifact.sh +173 -0
  100. package/runtime/codex/bin/publish-references.sh +144 -0
  101. package/runtime/codex/bin/repo-state.sh +99 -0
  102. package/runtime/codex/bin/telemetry.sh +350 -0
  103. package/runtime/codex/bin/worktree-create.sh +118 -0
  104. package/runtime/codex/bin/worktree-remove.sh +50 -0
  105. package/runtime/codex/bin/worktree.sh +198 -0
  106. package/runtime/codex/manifest.json +155 -0
@@ -0,0 +1,174 @@
1
+ #!/bin/bash
2
+ # Fetches personal dashboard data: single API call + parallel git ops.
3
+ # Usage: bash bin/dashboard-data.sh [username] [time_range]
4
+ # Returns a single JSON object with all query results.
5
+ #
6
+ # time_range: P1D (today), P7D (week, default), P30D (month), P365D (all)
7
+ set -euo pipefail
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
10
+ TMPDIR=$(mktemp -d)
11
+ trap "rm -rf $TMPDIR" EXIT
12
+
13
+ # --- Read config ---
14
+ API_URL=$(jq -r '.api_url // empty' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
15
+ API_KEY=$(grep '^EGREGORE_API_KEY=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)
16
+ ORG=$(jq -r '.org_name // "Egregore"' "$SCRIPT_DIR/egregore.json" 2>/dev/null || echo "Egregore")
17
+ DATE=$(date '+%b %d')
18
+
19
+ # --- Detect user ---
20
+ GH_USER="${1:-}"
21
+ if [ -z "$GH_USER" ]; then
22
+ STATE_FILE="$SCRIPT_DIR/.egregore-state.json"
23
+ if [ -f "$STATE_FILE" ]; then
24
+ GH_USER=$(jq -r '.github_username // empty' "$STATE_FILE" 2>/dev/null)
25
+ fi
26
+ if [ -z "$GH_USER" ]; then
27
+ GH_USER=$(git -C "$SCRIPT_DIR" config user.name 2>/dev/null || echo "unknown")
28
+ fi
29
+ fi
30
+
31
+ # --- Time range (default P7D) ---
32
+ TIME_RANGE="${2:-P7D}"
33
+
34
+ # --- Read current session ID (client-side fallback for WAL-not-yet-drained) ---
35
+ PROJ_HASH=$(echo -n "$SCRIPT_DIR" | md5 2>/dev/null || echo -n "$SCRIPT_DIR" | md5sum 2>/dev/null | cut -d' ' -f1)
36
+ SESSION_ID=$(cat "$HOME/.egregore/session-${PROJ_HASH}.id" 2>/dev/null || echo "")
37
+
38
+ # --- Fire API call + git ops in parallel ---
39
+
40
+ # Graph data: single API call (all queries run server-side)
41
+ (
42
+ GRAPH_STATUS="offline"
43
+ GRAPH_REASON=""
44
+ if [ -z "$API_URL" ] || [ -z "$API_KEY" ]; then
45
+ GRAPH_REASON="missing_config"
46
+ else
47
+ HTTP_CODE=$(curl -s -o "$TMPDIR/dashboard_raw.json" -w "%{http_code}" \
48
+ "${API_URL}/api/personal/dashboard?github_username=$(printf '%s' "$GH_USER" | jq -sRr @uri)&time_range=$(printf '%s' "$TIME_RANGE" | jq -sRr @uri)&session_id=$(printf '%s' "$SESSION_ID" | jq -sRr @uri)" \
49
+ -H "Authorization: Bearer $API_KEY" \
50
+ --connect-timeout 5 --max-time 15 2>/dev/null || echo "000")
51
+
52
+ if [ "$HTTP_CODE" = "000" ]; then
53
+ GRAPH_REASON="unreachable"
54
+ elif [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "403" ]; then
55
+ GRAPH_REASON="auth_error"
56
+ elif [ "$HTTP_CODE" -ge 500 ] 2>/dev/null; then
57
+ GRAPH_REASON="server_error"
58
+ elif [ -s "$TMPDIR/dashboard_raw.json" ] && jq -e '.me' "$TMPDIR/dashboard_raw.json" >/dev/null 2>&1; then
59
+ cp "$TMPDIR/dashboard_raw.json" "$TMPDIR/dashboard.json"
60
+ GRAPH_STATUS="connected"
61
+ else
62
+ GRAPH_REASON="invalid_response"
63
+ fi
64
+ fi
65
+
66
+ # If not connected, write status-only response
67
+ if [ "$GRAPH_STATUS" != "connected" ]; then
68
+ jq -n --arg status "$GRAPH_STATUS" --arg reason "$GRAPH_REASON" \
69
+ '{graph_status: $status, graph_reason: $reason}' > "$TMPDIR/dashboard.json"
70
+ else
71
+ jq --arg status "$GRAPH_STATUS" '. + {graph_status: $status}' \
72
+ "$TMPDIR/dashboard.json" > "$TMPDIR/dashboard_merged.json" \
73
+ && mv "$TMPDIR/dashboard_merged.json" "$TMPDIR/dashboard.json"
74
+ fi
75
+ ) &
76
+ API_PID=$!
77
+
78
+ # Git: branch + dirty status (parallel, stays client-side)
79
+ (
80
+ CURRENT=$(git -C "$SCRIPT_DIR" branch --show-current 2>/dev/null || echo "unknown")
81
+ DIRTY="false"
82
+ if [ -n "$(git -C "$SCRIPT_DIR" status --porcelain 2>/dev/null | head -1)" ]; then
83
+ DIRTY="true"
84
+ fi
85
+ jq -n --arg branch "$CURRENT" --argjson dirty "$DIRTY" \
86
+ '{branch: $branch, dirty: $dirty}' > "$TMPDIR/git.json"
87
+ ) &
88
+ GIT_PID=$!
89
+
90
+ # Local sessions: parse memory files when graph is offline (parallel)
91
+ (
92
+ GH_LC=$(echo "$GH_USER" | tr '[:upper:]' '[:lower:]')
93
+ DISPLAY_LC=""
94
+ if [ -f "$SCRIPT_DIR/.egregore-state.json" ]; then
95
+ DISPLAY_LC=$(jq -r '.display_name // empty' "$SCRIPT_DIR/.egregore-state.json" 2>/dev/null | tr '[:upper:]' '[:lower:]')
96
+ fi
97
+
98
+ # Compute cutoff date from time range
99
+ case "$TIME_RANGE" in
100
+ P1D) CUTOFF=$(date -v-1d +%Y-%m-%d 2>/dev/null || date -d '1 day ago' +%Y-%m-%d 2>/dev/null || echo "2000-01-01") ;;
101
+ P7D) CUTOFF=$(date -v-7d +%Y-%m-%d 2>/dev/null || date -d '7 days ago' +%Y-%m-%d 2>/dev/null || echo "2000-01-01") ;;
102
+ P30D) CUTOFF=$(date -v-30d +%Y-%m-%d 2>/dev/null || date -d '30 days ago' +%Y-%m-%d 2>/dev/null || echo "2000-01-01") ;;
103
+ P365D) CUTOFF="2000-01-01" ;;
104
+ *) CUTOFF=$(date -v-7d +%Y-%m-%d 2>/dev/null || date -d '7 days ago' +%Y-%m-%d 2>/dev/null || echo "2000-01-01") ;;
105
+ esac
106
+
107
+ # Scan sessions, wraps, and handoffs for this user
108
+ for DIR in "$SCRIPT_DIR/memory/sessions" "$SCRIPT_DIR/memory/wraps" "$SCRIPT_DIR/memory/handoffs"; do
109
+ [ -d "$DIR" ] || continue
110
+ TYPE=$(basename "$DIR")
111
+ find -L "$DIR" -name '*.md' -not -name 'index*' -type f 2>/dev/null | xargs ls -t 2>/dev/null | head -20 | while read -r MFILE; do
112
+ [ -z "$MFILE" ] && continue
113
+ M_AUTH=$(head -10 "$MFILE" 2>/dev/null | grep -m1 '^\*\*Author\*\*:' | sed 's/^\*\*Author\*\*:[[:space:]]*//' || true)
114
+ [ -z "$M_AUTH" ] && continue
115
+ M_AUTH_LC=$(echo "$M_AUTH" | tr '[:upper:]' '[:lower:]')
116
+ # Filter to this user only
117
+ [ "$M_AUTH_LC" != "$GH_LC" ] && { [ -z "$DISPLAY_LC" ] || [ "$M_AUTH_LC" != "$DISPLAY_LC" ]; } && continue
118
+ M_DATE=$(head -10 "$MFILE" 2>/dev/null | grep -m1 '^\*\*Date\*\*:' | sed 's/^\*\*Date\*\*:[[:space:]]*//' || true)
119
+ M_DAY="${M_DATE%%T*}"
120
+ [ -n "$M_DAY" ] && [ "$M_DAY" \< "$CUTOFF" ] && continue
121
+ M_BRANCH=$(head -10 "$MFILE" 2>/dev/null | grep -m1 '^\*\*Branch\*\*:' | sed 's/^\*\*Branch\*\*:[[:space:]]*//' || true)
122
+ M_DURATION=$(head -10 "$MFILE" 2>/dev/null | grep -m1 '^\*\*Duration\*\*:' | sed 's/^\*\*Duration\*\*:[[:space:]]*//' || true)
123
+ M_TOPIC=$(head -3 "$MFILE" 2>/dev/null | grep -m1 '^# ' | sed 's/^# [^:]*:[[:space:]]*//' || true)
124
+ echo "${M_DATE}|${M_TOPIC}|${M_BRANCH}|${M_DURATION}|${TYPE}"
125
+ done
126
+ done | jq -Rn '
127
+ [inputs | split("|") |
128
+ {date: .[0], topic: .[1], branch: .[2], duration: .[3], type: .[4]}
129
+ ] | sort_by(.date) | reverse | {
130
+ sessions: .,
131
+ session_count: length
132
+ }
133
+ ' 2>/dev/null > "$TMPDIR/local_sessions.json" || echo '{"sessions":[],"session_count":0}' > "$TMPDIR/local_sessions.json"
134
+ ) &
135
+ LOCAL_PID=$!
136
+
137
+ # --- Wait for all ---
138
+ wait $API_PID $GIT_PID $LOCAL_PID 2>/dev/null || true
139
+
140
+ # --- Read results ---
141
+ GIT_INFO=$(cat "$TMPDIR/git.json" 2>/dev/null || echo '{"branch":"unknown","dirty":false}')
142
+ BRANCH=$(echo "$GIT_INFO" | jq -r '.branch' 2>/dev/null || echo "unknown")
143
+ LOCAL_SESSIONS=$(cat "$TMPDIR/local_sessions.json" 2>/dev/null || echo '{"sessions":[],"session_count":0}')
144
+ echo "$LOCAL_SESSIONS" | jq . >/dev/null 2>&1 || LOCAL_SESSIONS='{"sessions":[],"session_count":0}'
145
+
146
+ # --- Client-side current session fallback ---
147
+ # If the graph returned no current_session (WAL hasn't drained yet),
148
+ # inject one from the local session ID file so the TUI always renders it.
149
+ DASHBOARD=$(cat "$TMPDIR/dashboard.json" 2>/dev/null || echo '{"graph_status":"offline"}')
150
+ CS_ID=$(echo "$DASHBOARD" | jq -r '.current_session.id // empty' 2>/dev/null)
151
+ if [ -z "$CS_ID" ] && [ -n "$SESSION_ID" ]; then
152
+ DASHBOARD=$(echo "$DASHBOARD" | jq \
153
+ --arg sid "$SESSION_ID" \
154
+ --arg branch "$BRANCH" \
155
+ '.current_session = {id: $sid, status: "active", topic: null, branch: $branch}' 2>/dev/null || echo "$DASHBOARD")
156
+ fi
157
+
158
+ # --- Derive time range label ---
159
+ case "$TIME_RANGE" in
160
+ P1D) RANGE_LABEL="today" ;;
161
+ P7D) RANGE_LABEL="last 7 days" ;;
162
+ P30D) RANGE_LABEL="last 30 days" ;;
163
+ P365D) RANGE_LABEL="all time" ;;
164
+ *) RANGE_LABEL="last 7 days" ;;
165
+ esac
166
+
167
+ # --- Merge server response + local data ---
168
+ echo "$DASHBOARD" | jq \
169
+ --arg org "$ORG" \
170
+ --arg date "$DATE" \
171
+ --arg range_label "$RANGE_LABEL" \
172
+ --argjson git "$GIT_INFO" \
173
+ --argjson local_sessions "$LOCAL_SESSIONS" \
174
+ '. + {org: $org, date: $date, range_label: $range_label, git: $git, local_sessions: $local_sessions}'
@@ -0,0 +1,60 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # Execute multiple Cypher queries in a single HTTP call.
5
+ # Usage: graph-batch.sh '<json array of {statement, parameters} objects>'
6
+ #
7
+ # Example:
8
+ # graph-batch.sh '[{"statement":"MATCH (p:Person) RETURN p.name"},{"statement":"MATCH (q:Quest) RETURN q.id"}]'
9
+ #
10
+ # Returns: {"results": [{...}, {...}]}
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
13
+ CONFIG="$SCRIPT_DIR/egregore.json"
14
+
15
+ if [ ! -f "$CONFIG" ]; then
16
+ echo "Error: egregore.json not found. Run onboarding first." >&2
17
+ exit 1
18
+ fi
19
+
20
+ # --- Local mode gate: bail immediately ---
21
+ _MODE=$(jq -r '.mode // "connected"' "$CONFIG" 2>/dev/null)
22
+ if [ "$_MODE" = "local" ]; then
23
+ echo '{"results":[]}'
24
+ exit 0
25
+ fi
26
+
27
+ # Load specific variables from .env if it exists (safe extraction, no arbitrary code execution)
28
+ if [ -f "$SCRIPT_DIR/.env" ]; then
29
+ EGREGORE_API_URL="${EGREGORE_API_URL:-$(grep '^EGREGORE_API_URL=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
30
+ EGREGORE_API_KEY="${EGREGORE_API_KEY:-$(grep '^EGREGORE_API_KEY=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
31
+ fi
32
+
33
+ API_URL="${EGREGORE_API_URL:-$(jq -r '.api_url // empty' "$CONFIG")}"
34
+ API_KEY="${EGREGORE_API_KEY:-}"
35
+
36
+ if [ -z "$API_URL" ] || [ -z "$API_KEY" ]; then
37
+ # === OFFLINE MODE: No API key — return empty results (OSS/local) ===
38
+ echo '{"results":[]}'
39
+ exit 0
40
+ fi
41
+
42
+ QUERIES_JSON="${1:?Usage: graph-batch.sh '<json array of queries>'}"
43
+
44
+ # Wrap the array into the batch request format
45
+ BODY=$(jq -n --argjson queries "$QUERIES_JSON" '{queries: $queries}')
46
+
47
+ RESPONSE=$(curl -s -X POST "${API_URL}/api/graph/batch" \
48
+ -H "Authorization: Bearer $API_KEY" \
49
+ -H "Content-Type: application/json" \
50
+ -d "$BODY" \
51
+ --max-time 60)
52
+
53
+ # Check for HTTP-level errors
54
+ if echo "$RESPONSE" | jq -e '.detail' >/dev/null 2>&1; then
55
+ echo "API error:" >&2
56
+ echo "$RESPONSE" | jq '.detail' >&2
57
+ exit 1
58
+ fi
59
+
60
+ echo "$RESPONSE"
@@ -0,0 +1,380 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ # Named graph operations — clean interface over raw Cypher.
4
+ # Keeps implementation details out of the TUI.
5
+ #
6
+ # Usage: bash bin/graph-op.sh <operation> [args...]
7
+ #
8
+ # Operations:
9
+ # mark-read <session-id> Mark a handoff as read
10
+ # mark-done <session-id> Mark a handoff as done/resolved
11
+ # answer-question <set-id> Mark a question set as answered
12
+ # resolve-handoffs <user> Auto-resolve read handoffs with later sessions
13
+ # set-topic <session-id> <topic> [branch]
14
+ # Set topic (and optionally branch) on a Session node
15
+ # record-focus <session-id> <shown-json> <selected> [dismissed-json]
16
+ # Track Focus option selection for adaptive options
17
+ # merge-person <keep-name> <remove-name>
18
+ # Merge two Person nodes — transfers relationships from
19
+ # remove-name to keep-name, stores remove-name as alias
20
+ # create-harvest <id> <topic> <intent> <initiator>
21
+ # Create a Harvest node and link to initiator (WAL-backed)
22
+ # create-harvest-session <harvest-id> <session-id> <person-name>
23
+ # Create a HarvestSession linked to a Harvest and Person
24
+ # record-harvest-turn <session-id> <turn> <question> <intent> [answer] [eval]
25
+ # Record a question-answer turn in a HarvestSession
26
+ # complete-harvest <harvest-id> <artifact-path>
27
+ # Mark harvest complete and link synthesis artifact
28
+
29
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
30
+
31
+ if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
32
+ echo "Usage: graph-op.sh <operation> [args...]"
33
+ echo ""
34
+ echo "Named graph operations — clean interface over raw Cypher."
35
+ echo ""
36
+ echo "Operations:"
37
+ echo " mark-read <sid> Mark handoff as read"
38
+ echo " mark-done <sid> Mark handoff as done"
39
+ echo " answer-question <qid> Mark question set as answered"
40
+ echo " resolve-handoffs <user> Auto-resolve read handoffs"
41
+ echo " set-topic <sid> <topic> Set topic on a Session node"
42
+ echo " merge-person <keep> <rm> Merge two Person nodes"
43
+ echo " claim-handoff <sid> <ho> Link implementing session to handoff"
44
+ echo " create-pr <sid> <num> <repo> <author> [title]"
45
+ echo " create-harvest / complete-harvest / record-harvest-turn"
46
+ echo " wal-status Show WAL pending count"
47
+ exit 0
48
+ fi
49
+
50
+ # --- Local mode gate: bail immediately ---
51
+ _MODE=$(jq -r '.mode // "connected"' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
52
+ if [ "$_MODE" = "local" ]; then
53
+ echo '{"results":[]}'
54
+ exit 0
55
+ fi
56
+
57
+ GS="$SCRIPT_DIR/bin/graph.sh"
58
+
59
+ OP="${1:-}"
60
+ shift || true
61
+
62
+ case "$OP" in
63
+
64
+ mark-read)
65
+ SID="${1:?missing session-id}"
66
+ bash "$GS" query "
67
+ MATCH (s:Session {id: \$sid})
68
+ SET s.handoffStatus = 'read', s.handoffReadDate = date()
69
+ RETURN s.id AS id, s.topic AS topic
70
+ " "$(jq -n --arg sid "$SID" '{sid: $sid}')"
71
+ ;;
72
+
73
+ mark-done)
74
+ SID="${1:?missing session-id}"
75
+ bash "$GS" query "
76
+ MATCH (s:Session {id: \$sid})
77
+ SET s.handoffStatus = 'done'
78
+ RETURN s.id AS id, s.topic AS topic
79
+ " "$(jq -n --arg sid "$SID" '{sid: $sid}')"
80
+ ;;
81
+
82
+ answer-question)
83
+ QID="${1:?missing question-set-id}"
84
+ bash "$GS" query "
85
+ MATCH (qs:QuestionSet {id: \$qid})
86
+ SET qs.status = 'answered'
87
+ RETURN qs.id AS id, qs.topic AS topic
88
+ " "$(jq -n --arg qid "$QID" '{qid: $qid}')"
89
+ ;;
90
+
91
+ resolve-handoffs)
92
+ USER="${1:?missing username}"
93
+ bash "$GS" query "
94
+ MATCH (s:Session)-[:HANDED_TO]->(p:Person {name: \$user})
95
+ WHERE s.handoffStatus = 'read'
96
+ WITH s, p, coalesce(s.handoffReadDate, s.date) AS sinceDate
97
+ MATCH (later:Session)-[:BY]->(p)
98
+ WHERE later.date > sinceDate
99
+ WITH s, count(later) AS laterSessions WHERE laterSessions > 0
100
+ SET s.handoffStatus = 'done'
101
+ RETURN s.id AS resolved
102
+ " "$(jq -n --arg user "$USER" '{user: $user}')"
103
+ ;;
104
+
105
+ set-topic)
106
+ SID="${1:?missing session-id}"
107
+ TOPIC="${2:?missing topic}"
108
+ BRANCH="${3:-}"
109
+ if [ -n "$BRANCH" ]; then
110
+ bash "$GS" query "
111
+ MATCH (s:Session {id: \$sid})
112
+ SET s.topic = \$topic, s.branch = \$branch
113
+ RETURN s.id AS id, s.topic AS topic, s.branch AS branch
114
+ " "$(jq -n --arg sid "$SID" --arg topic "$TOPIC" --arg branch "$BRANCH" '{sid: $sid, topic: $topic, branch: $branch}')"
115
+ else
116
+ bash "$GS" query "
117
+ MATCH (s:Session {id: \$sid})
118
+ SET s.topic = \$topic
119
+ RETURN s.id AS id, s.topic AS topic
120
+ " "$(jq -n --arg sid "$SID" --arg topic "$TOPIC" '{sid: $sid, topic: $topic}')"
121
+ fi
122
+ ;;
123
+
124
+ record-focus)
125
+ SID="${1:?missing session-id}"
126
+ SHOWN="${2:?missing shown options}"
127
+ SELECTED="${3:?missing selected option}"
128
+ DISMISSED="${4:-[]}"
129
+ bash "$GS" query "
130
+ MATCH (s:Session {id: \$sid})
131
+ SET s.focusShown = \$shown,
132
+ s.focusSelected = \$selected,
133
+ s.focusDismissed = \$dismissed
134
+ RETURN s.id AS id
135
+ " "$(jq -n --arg sid "$SID" --argjson shown "$SHOWN" --arg selected "$SELECTED" --argjson dismissed "$DISMISSED" '{sid: $sid, shown: $shown, selected: $selected, dismissed: $dismissed}')"
136
+ ;;
137
+
138
+ merge-person)
139
+ KEEP="${1:?missing keep-name (the Person to keep)}"
140
+ REMOVE="${2:?missing remove-name (the Person to absorb)}"
141
+ bash "$GS" query "
142
+ MATCH (keep:Person) WHERE toLower(keep.name) = toLower(\$keep) OR keep.github = \$keep
143
+ MATCH (remove:Person) WHERE toLower(remove.name) = toLower(\$remove) OR remove.github = \$remove
144
+ WITH keep, remove WHERE keep <> remove
145
+ SET keep.previousNames = coalesce(keep.previousNames, []) + remove.name
146
+ WITH keep, remove
147
+ OPTIONAL MATCH (s1)-[:BY]->(remove)
148
+ FOREACH (_ IN CASE WHEN s1 IS NOT NULL THEN [1] ELSE [] END | MERGE (s1)-[:BY]->(keep))
149
+ WITH keep, remove
150
+ OPTIONAL MATCH (s2)-[:HANDED_TO]->(remove)
151
+ FOREACH (_ IN CASE WHEN s2 IS NOT NULL THEN [1] ELSE [] END | MERGE (s2)-[:HANDED_TO]->(keep))
152
+ WITH keep, remove
153
+ OPTIONAL MATCH (m)-[:INVOLVES]->(remove)
154
+ FOREACH (_ IN CASE WHEN m IS NOT NULL THEN [1] ELSE [] END | MERGE (m)-[:INVOLVES]->(keep))
155
+ WITH keep, remove
156
+ OPTIONAL MATCH (a)-[:CONTRIBUTED_BY]->(remove)
157
+ FOREACH (_ IN CASE WHEN a IS NOT NULL THEN [1] ELSE [] END | MERGE (a)-[:CONTRIBUTED_BY]->(keep))
158
+ WITH keep, remove
159
+ OPTIONAL MATCH (i)-[:CONDUCTED_BY]->(remove)
160
+ FOREACH (_ IN CASE WHEN i IS NOT NULL THEN [1] ELSE [] END | MERGE (i)-[:CONDUCTED_BY]->(keep))
161
+ WITH keep, remove
162
+ OPTIONAL MATCH (remove)-[:MEMBER_OF]->(o:Org)
163
+ FOREACH (_ IN CASE WHEN o IS NOT NULL THEN [1] ELSE [] END | MERGE (keep)-[:MEMBER_OF]->(o))
164
+ WITH keep, remove
165
+ DETACH DELETE remove
166
+ RETURN keep.name AS name, keep.github AS github, keep.previousNames AS aliases
167
+ " "$(jq -n --arg keep "$KEEP" --arg remove "$REMOVE" '{keep: $keep, remove: $remove}')"
168
+ ;;
169
+
170
+ claim-handoff)
171
+ IMPL_SID="${1:?missing implementing-session-id}"
172
+ HO_SID="${2:?missing handoff-session-id}"
173
+ CYPHER="
174
+ MATCH (impl:Session {id: \$implSid}), (ho:Session {id: \$hoSid})
175
+ WHERE ho.handoffStatus IS NOT NULL
176
+ MERGE (impl)-[:IMPLEMENTS]->(ho)
177
+ RETURN impl.id AS implementor, ho.id AS handoff, ho.topic AS topic
178
+ "
179
+ PARAMS="$(jq -n --arg implSid "$IMPL_SID" --arg hoSid "$HO_SID" '{implSid: $implSid, hoSid: $hoSid}')"
180
+ bash "$SCRIPT_DIR/bin/graph-wal.sh" append "$CYPHER" "$PARAMS" 2>/dev/null || true
181
+ bash "$GS" query "$CYPHER" "$PARAMS"
182
+ ;;
183
+
184
+ create-pr)
185
+ SID="${1:?missing session-id}"
186
+ PR_NUM="${2:?missing pr-number}"
187
+ REPO="${3:?missing repo}"
188
+ AUTHOR_GH="${4:?missing author-github}"
189
+ TITLE="${5:-}"
190
+ CYPHER="
191
+ MERGE (pr:PR {number: toInteger(\$num), repo: \$repo})
192
+ ON CREATE SET pr.author = \$author, pr.status = 'open',
193
+ pr.createdAt = datetime(), pr.title = \$title
194
+ WITH pr
195
+ OPTIONAL MATCH (s:Session {id: \$sid})
196
+ FOREACH (_ IN CASE WHEN s IS NOT NULL THEN [1] ELSE [] END |
197
+ MERGE (s)-[:PRODUCED]->(pr))
198
+ RETURN pr.number AS number, pr.repo AS repo
199
+ "
200
+ PARAMS="$(jq -n --arg sid "$SID" --argjson num "$PR_NUM" --arg repo "$REPO" --arg author "$AUTHOR_GH" --arg title "$TITLE" '{sid: $sid, num: $num, repo: $repo, author: $author, title: $title}')"
201
+ bash "$SCRIPT_DIR/bin/graph-wal.sh" append "$CYPHER" "$PARAMS" 2>/dev/null || true
202
+ bash "$GS" query "$CYPHER" "$PARAMS"
203
+ ;;
204
+
205
+ check-implements)
206
+ SID="${1:?missing session-id}"
207
+ bash "$GS" query "
208
+ MATCH (impl:Session {id: \$sid})-[:IMPLEMENTS]->(ho:Session)-[:BY]->(author:Person)
209
+ RETURN ho.id AS handoffId, ho.topic AS topic, author.name AS author,
210
+ author.github AS authorGithub
211
+ " "$(jq -n --arg sid "$SID" '{sid: $sid}')"
212
+ ;;
213
+
214
+ update-pr)
215
+ PR_NUM="${1:?missing pr-number}"
216
+ REPO="${2:?missing repo}"
217
+ STATUS="${3:?missing status}"
218
+ MERGED_AT="${4:-}"
219
+ if [ -n "$MERGED_AT" ]; then
220
+ bash "$GS" query "
221
+ MATCH (pr:PR {number: toInteger(\$num), repo: \$repo})
222
+ SET pr.status = \$status, pr.mergedAt = datetime(\$mergedAt)
223
+ RETURN pr.number AS number, pr.status AS status
224
+ " "$(jq -n --argjson num "$PR_NUM" --arg repo "$REPO" --arg status "$STATUS" --arg mergedAt "$MERGED_AT" '{num: $num, repo: $repo, status: $status, mergedAt: $mergedAt}')"
225
+ else
226
+ bash "$GS" query "
227
+ MATCH (pr:PR {number: toInteger(\$num), repo: \$repo})
228
+ SET pr.status = \$status
229
+ RETURN pr.number AS number, pr.status AS status
230
+ " "$(jq -n --argjson num "$PR_NUM" --arg repo "$REPO" --arg status "$STATUS" '{num: $num, repo: $repo, status: $status}')"
231
+ fi
232
+ ;;
233
+
234
+ my-merged-prs)
235
+ AUTHOR_GH="${1:?missing author-github}"
236
+ SINCE="${2:-}"
237
+ if [ -n "$SINCE" ]; then
238
+ bash "$GS" query "
239
+ MATCH (pr:PR {author: \$author})
240
+ WHERE pr.status = 'merged' AND pr.mergedAt >= datetime(\$since)
241
+ RETURN pr.number AS number, pr.repo AS repo, pr.title AS title,
242
+ toString(pr.mergedAt) AS mergedAt
243
+ ORDER BY pr.mergedAt DESC LIMIT 10
244
+ " "$(jq -n --arg author "$AUTHOR_GH" --arg since "${SINCE}T00:00:00Z" '{author: $author, since: $since}')"
245
+ else
246
+ bash "$GS" query "
247
+ MATCH (pr:PR {author: \$author})
248
+ WHERE pr.status = 'merged'
249
+ RETURN pr.number AS number, pr.repo AS repo, pr.title AS title,
250
+ toString(pr.mergedAt) AS mergedAt
251
+ ORDER BY pr.mergedAt DESC LIMIT 5
252
+ " "$(jq -n --arg author "$AUTHOR_GH" '{author: $author}')"
253
+ fi
254
+ ;;
255
+
256
+ my-implemented-handoffs)
257
+ AUTHOR_NAME="${1:?missing author-name}"
258
+ SINCE="${2:-}"
259
+ if [ -n "$SINCE" ]; then
260
+ bash "$GS" query "
261
+ MATCH (impl:Session)-[:IMPLEMENTS]->(ho:Session)-[:BY]->(author:Person)
262
+ WHERE toLower(author.name) = toLower(\$author)
263
+ AND impl.wrappedAt >= datetime(\$since)
264
+ MATCH (impl)-[:BY]->(implementor:Person)
265
+ RETURN ho.topic AS handoffTopic, implementor.name AS implementedBy,
266
+ toString(impl.wrappedAt) AS completedAt, impl.summary AS summary
267
+ ORDER BY impl.wrappedAt DESC LIMIT 10
268
+ " "$(jq -n --arg author "$AUTHOR_NAME" --arg since "${SINCE}T00:00:00Z" '{author: $author, since: $since}')"
269
+ else
270
+ bash "$GS" query "
271
+ MATCH (impl:Session)-[:IMPLEMENTS]->(ho:Session)-[:BY]->(author:Person)
272
+ WHERE toLower(author.name) = toLower(\$author)
273
+ MATCH (impl)-[:BY]->(implementor:Person)
274
+ RETURN ho.topic AS handoffTopic, implementor.name AS implementedBy,
275
+ toString(impl.wrappedAt) AS completedAt, impl.summary AS summary
276
+ ORDER BY impl.wrappedAt DESC LIMIT 5
277
+ " "$(jq -n --arg author "$AUTHOR_NAME" '{author: $author}')"
278
+ fi
279
+ ;;
280
+
281
+ wal-status)
282
+ bash "$SCRIPT_DIR/bin/graph-wal.sh" status
283
+ ;;
284
+
285
+ create-harvest)
286
+ HID="${1:?missing harvest-id}"
287
+ TOPIC="${2:?missing topic}"
288
+ INTENT="${3:?missing intent}"
289
+ INITIATOR="${4:?missing initiator}"
290
+ CYPHER="
291
+ MERGE (h:Harvest {id: \$hid})
292
+ ON CREATE SET h.topic = \$topic, h.intent = \$intent,
293
+ h.status = 'active', h.created = datetime()
294
+ WITH h
295
+ MATCH (p:Person) WHERE toLower(p.name) = toLower(\$initiator) OR p.github = \$initiator
296
+ MERGE (h)-[:INITIATED_BY]->(p)
297
+ RETURN h.id AS id, h.topic AS topic, h.status AS status
298
+ "
299
+ PARAMS="$(jq -n --arg hid "$HID" --arg topic "$TOPIC" --arg intent "$INTENT" --arg initiator "$INITIATOR" '{hid: $hid, topic: $topic, intent: $intent, initiator: $initiator}')"
300
+ bash "$SCRIPT_DIR/bin/graph-wal.sh" append "$CYPHER" "$PARAMS" 2>/dev/null || true
301
+ bash "$GS" query "$CYPHER" "$PARAMS"
302
+ ;;
303
+
304
+ create-harvest-session)
305
+ HID="${1:?missing harvest-id}"
306
+ HSID="${2:?missing session-id}"
307
+ PERSON="${3:?missing person-name}"
308
+ bash "$GS" query "
309
+ MATCH (h:Harvest {id: \$hid})
310
+ MERGE (hs:HarvestSession {id: \$hsid})
311
+ ON CREATE SET hs.status = 'active', hs.created = datetime()
312
+ MERGE (h)-[:HAS_SESSION]->(hs)
313
+ WITH hs
314
+ MATCH (p:Person) WHERE toLower(p.name) = toLower(\$person) OR p.github = \$person
315
+ MERGE (hs)-[:WITH]->(p)
316
+ RETURN hs.id AS id, hs.status AS status
317
+ " "$(jq -n --arg hid "$HID" --arg hsid "$HSID" --arg person "$PERSON" '{hid: $hid, hsid: $hsid, person: $person}')"
318
+ ;;
319
+
320
+ record-harvest-turn)
321
+ HSID="${1:?missing session-id}"
322
+ TURN="${2:?missing turn-number}"
323
+ QUESTION="${3:?missing question}"
324
+ QINTENT="${4:?missing question-intent}"
325
+ ANSWER="${5:-}"
326
+ EVAL="${6:-}"
327
+ TURN_ID="${HSID}-turn-${TURN}"
328
+ PARAMS=$(jq -n \
329
+ --arg hsid "$HSID" --arg tid "$TURN_ID" --arg turn "$TURN" \
330
+ --arg question "$QUESTION" --arg qintent "$QINTENT" \
331
+ --arg answer "$ANSWER" --arg eval "$EVAL" \
332
+ '{hsid:$hsid, tid:$tid, turn:$turn, question:$question, qintent:$qintent, answer:$answer, eval:$eval}')
333
+ if [ -n "$ANSWER" ]; then
334
+ bash "$GS" query "
335
+ MATCH (hs:HarvestSession {id: \$hsid})
336
+ MERGE (t:HarvestTurn {id: \$tid})
337
+ ON CREATE SET t.turnNumber = toInteger(\$turn), t.question = \$question,
338
+ t.questionIntent = \$qintent, t.created = datetime()
339
+ SET t.answer = \$answer, t.answeredAt = datetime()
340
+ SET t.evaluation = CASE WHEN \$eval <> '' THEN \$eval ELSE t.evaluation END
341
+ MERGE (hs)-[:HAS_TURN]->(t)
342
+ RETURN t.id AS id, t.turnNumber AS turnNumber
343
+ " "$PARAMS"
344
+ else
345
+ bash "$GS" query "
346
+ MATCH (hs:HarvestSession {id: \$hsid})
347
+ MERGE (t:HarvestTurn {id: \$tid})
348
+ ON CREATE SET t.turnNumber = toInteger(\$turn), t.question = \$question,
349
+ t.questionIntent = \$qintent, t.created = datetime()
350
+ MERGE (hs)-[:HAS_TURN]->(t)
351
+ RETURN t.id AS id, t.turnNumber AS turnNumber
352
+ " "$PARAMS"
353
+ fi
354
+ ;;
355
+
356
+ complete-harvest)
357
+ HID="${1:?missing harvest-id}"
358
+ ARTIFACT_PATH="${2:?missing artifact-path}"
359
+ bash "$GS" query "
360
+ MATCH (h:Harvest {id: \$hid})
361
+ SET h.status = 'complete', h.completedAt = datetime(), h.synthesisPath = \$path
362
+ WITH h
363
+ OPTIONAL MATCH (h)-[:HAS_SESSION]->(hs:HarvestSession)
364
+ WHERE hs.status <> 'complete'
365
+ SET hs.status = 'complete', hs.completedAt = datetime()
366
+ WITH h
367
+ MERGE (a:Artifact {id: \$hid + '-synthesis'})
368
+ ON CREATE SET a.type = 'harvest', a.title = h.topic, a.filePath = \$path,
369
+ a.created = datetime(), a.topics = coalesce([h.topic], [])
370
+ MERGE (h)-[:PRODUCED]->(a)
371
+ RETURN h.id AS id, h.status AS status, a.id AS artifact
372
+ " "$(jq -n --arg hid "$HID" --arg path "$ARTIFACT_PATH" '{hid: $hid, path: $path}')"
373
+ ;;
374
+
375
+ *)
376
+ echo '{"error":"unknown operation: '"$OP"'","operations":["mark-read","mark-done","answer-question","resolve-handoffs","set-topic","record-focus","merge-person","claim-handoff","check-implements","create-pr","update-pr","my-merged-prs","my-implemented-handoffs","wal-status","create-harvest","create-harvest-session","record-harvest-turn","complete-harvest"]}'
377
+ exit 1
378
+ ;;
379
+
380
+ esac