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,136 @@
1
+ #!/bin/bash
2
+ # handoff-save-egregore.sh — background save+auto-merge for the core repo after a handoff.
3
+ #
4
+ # /handoff renders its card first, then fires this detached. The user never waits on git.
5
+ #
6
+ # Behavior:
7
+ # - If nothing to save (clean working tree + up-to-date with origin) → exit silently.
8
+ # - If on develop/main/master → create dev/{author}/handoff-{YYYY-MM-DD} branch from origin/develop.
9
+ # - Commit all changes, rebase onto origin/develop, push.
10
+ # - If push succeeds: create PR to develop (or reuse existing), then:
11
+ # - Markdown-only diff → `gh pr merge --auto --merge` (auto-merges when checks pass).
12
+ # - Code/config present → leave PR open for review (no auto-merge).
13
+ #
14
+ # Usage:
15
+ # handoff-save-egregore.sh <author> <topic> [--repo-dir <path>]
16
+ #
17
+ # --repo-dir overrides the default working directory (normally the script's
18
+ # own repo root, resolved from $0). Tests and sandbox runs MUST pass it;
19
+ # without it, the script operates on the real repo regardless of caller cwd.
20
+ #
21
+ # Detached usage (the skill calls it this way):
22
+ # ( bash bin/handoff-save-egregore.sh "$AUTHOR" "$TOPIC" >/dev/null 2>&1 & ) >/dev/null 2>&1
23
+ #
24
+ # Exit 0 on any non-fatal outcome. The caller never reads stdout/stderr — all status
25
+ # lives in the PR itself and in `git log`.
26
+
27
+ set -uo pipefail
28
+
29
+ AUTHOR="${1:-}"
30
+ TOPIC="${2:-session work}"
31
+ shift 2 2>/dev/null || true
32
+
33
+ REPO_DIR=""
34
+ while [ $# -gt 0 ]; do
35
+ case "$1" in
36
+ --repo-dir) REPO_DIR="${2:-}"; shift 2 ;;
37
+ *) shift ;;
38
+ esac
39
+ done
40
+
41
+ [ -n "$AUTHOR" ] || { echo "handoff-save-egregore: missing author" >&2; exit 1; }
42
+
43
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
44
+ # If --repo-dir wasn't given, default to the script's own repo root.
45
+ [ -z "$REPO_DIR" ] && REPO_DIR="$SCRIPT_DIR"
46
+ cd "$REPO_DIR" || exit 1
47
+
48
+ # Shared safe-staging guard — never auto-commit stray sibling-repo gitlinks.
49
+ # shellcheck source=bin/lib/git-safe.sh
50
+ . "$SCRIPT_DIR/bin/lib/git-safe.sh" 2>/dev/null || true
51
+ type git_add_guarded >/dev/null 2>&1 || git_add_guarded() { git add -A 2>/dev/null || true; }
52
+
53
+ # --- Early exit if nothing to do -----------------------------------------
54
+ DIRTY=0
55
+ [ -n "$(git status --porcelain 2>/dev/null)" ] && DIRTY=1
56
+
57
+ CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
58
+ AHEAD=0
59
+ if [ -n "$CURRENT_BRANCH" ]; then
60
+ # Count commits ahead of origin/develop on the current branch (works from any
61
+ # dev/* branch or from develop itself).
62
+ AHEAD=$(git rev-list --count "origin/develop..HEAD" 2>/dev/null || echo 0)
63
+ fi
64
+
65
+ if [ "$DIRTY" = "0" ] && [ "$AHEAD" = "0" ]; then
66
+ exit 0
67
+ fi
68
+
69
+ # --- Protect the protected branches: create a working branch if needed --
70
+ case "$CURRENT_BRANCH" in
71
+ develop|main|master|"")
72
+ SLUG_DATE=$(date +%Y-%m-%d)
73
+ NEW_BRANCH="dev/${AUTHOR}/handoff-${SLUG_DATE}"
74
+ git fetch origin develop --quiet 2>/dev/null || true
75
+ git checkout -b "$NEW_BRANCH" origin/develop --quiet 2>/dev/null || \
76
+ git checkout "$NEW_BRANCH" --quiet 2>/dev/null || exit 0
77
+ CURRENT_BRANCH="$NEW_BRANCH"
78
+ ;;
79
+ esac
80
+
81
+ # --- Commit whatever is uncommitted --------------------------------------
82
+ if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
83
+ git_add_guarded
84
+ git commit -m "Handoff: ${TOPIC}" --quiet 2>/dev/null || true
85
+ fi
86
+
87
+ # --- Rebase onto develop, fall back to merge if rebase conflicts ---------
88
+ git fetch origin develop --quiet 2>/dev/null || true
89
+ if ! git rebase origin/develop --quiet 2>/dev/null; then
90
+ git rebase --abort 2>/dev/null || true
91
+ if ! git merge origin/develop --quiet -m "Sync with develop" 2>/dev/null; then
92
+ # Conflicts we can't auto-resolve. Leave the branch as-is; the user will see it
93
+ # next session or when they run /save explicitly.
94
+ git merge --abort 2>/dev/null || true
95
+ exit 0
96
+ fi
97
+ fi
98
+
99
+ # --- Push ---------------------------------------------------------------
100
+ if ! git push -u origin "$CURRENT_BRANCH" --quiet 2>/dev/null; then
101
+ # Push failed (network, auth). Commits are safe locally.
102
+ exit 0
103
+ fi
104
+
105
+ # --- PR: reuse open PR on this branch, or create one --------------------
106
+ command -v gh >/dev/null 2>&1 || exit 0
107
+
108
+ PR_NUMBER=""
109
+ EXISTING=$(gh pr list --head "$CURRENT_BRANCH" --state open --json number,baseRefName --jq '.[0]' 2>/dev/null)
110
+ EXISTING_NUMBER=$(echo "$EXISTING" | jq -r '.number // empty' 2>/dev/null)
111
+ EXISTING_BASE=$(echo "$EXISTING" | jq -r '.baseRefName // empty' 2>/dev/null)
112
+
113
+ if [ -n "$EXISTING_NUMBER" ]; then
114
+ PR_NUMBER="$EXISTING_NUMBER"
115
+ if [ "$EXISTING_BASE" != "develop" ]; then
116
+ gh pr edit "$PR_NUMBER" --base develop >/dev/null 2>&1 || true
117
+ fi
118
+ else
119
+ PR_URL=$(gh pr create \
120
+ --base develop \
121
+ --head "$CURRENT_BRANCH" \
122
+ --title "Handoff: ${TOPIC}" \
123
+ --body "Auto-created by /handoff. Session work for: ${TOPIC}" \
124
+ 2>/dev/null || echo "")
125
+ PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$' || echo "")
126
+ fi
127
+
128
+ [ -n "$PR_NUMBER" ] || exit 0
129
+
130
+ # --- Auto-merge iff markdown-only ---------------------------------------
131
+ NON_MD=$(git diff origin/develop --name-only 2>/dev/null | grep -v '\.md$' | head -1 || echo "")
132
+ if [ -z "$NON_MD" ]; then
133
+ gh pr merge "$PR_NUMBER" --auto --merge >/dev/null 2>&1 || true
134
+ fi
135
+
136
+ exit 0
@@ -0,0 +1,312 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # Index a single handoff file into the Neo4j graph.
5
+ # Usage: bash bin/index-handoff.sh <file-path>
6
+ # Returns: {"sessionId":"...","resolved":N} or {"error":"..."}
7
+ #
8
+ # The file-path should be relative to memory/ (e.g. handoffs/2026-02/14-oz-topic.md)
9
+ # or an absolute/relative path that contains "handoffs/" somewhere in it.
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
12
+
13
+ if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
14
+ echo "Usage: index-handoff.sh <handoff-file-path>"
15
+ echo ""
16
+ echo "Index a single handoff file into the Neo4j graph."
17
+ echo "Parses metadata (author, date, topic, recipients) from"
18
+ echo "markdown headers or YAML front matter, creates Session"
19
+ echo "and relationship nodes, and auto-resolves old handoffs."
20
+ echo ""
21
+ echo "Returns: {\"sessionId\":\"...\",\"resolved\":N}"
22
+ exit 0
23
+ fi
24
+
25
+ # --- Local mode gate: bail immediately ---
26
+ _MODE=$(jq -r '.mode // "connected"' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
27
+ if [ "$_MODE" = "local" ]; then
28
+ echo '{"sessionId":"","resolved":0,"mode":"local"}'
29
+ exit 0
30
+ fi
31
+
32
+ # --- Validate input ---
33
+ FILE_ARG="${1:?Usage: index-handoff.sh <handoff-file-path>}"
34
+
35
+ # Resolve to absolute path if needed
36
+ if [[ "$FILE_ARG" = /* ]]; then
37
+ FILE_PATH="$FILE_ARG"
38
+ elif [[ "$FILE_ARG" = memory/* ]]; then
39
+ FILE_PATH="$SCRIPT_DIR/$FILE_ARG"
40
+ else
41
+ FILE_PATH="$SCRIPT_DIR/memory/$FILE_ARG"
42
+ fi
43
+
44
+ if [ ! -f "$FILE_PATH" ]; then
45
+ echo '{"error":"File not found: '"$FILE_ARG"'"}'
46
+ exit 1
47
+ fi
48
+
49
+ # --- Derive filePath (relative to memory/) and sessionId ---
50
+ REL_PATH="${FILE_PATH##*/memory/}"
51
+ if [[ "$REL_PATH" = "$FILE_PATH" ]]; then
52
+ REL_PATH="handoffs/${FILE_PATH##*handoffs/}"
53
+ fi
54
+
55
+ FILENAME="$(basename "$FILE_PATH" .md)"
56
+ DIR_MONTH="$(basename "$(dirname "$FILE_PATH")")"
57
+
58
+ # sessionId = YYYY-MM-DD-author-topic
59
+ # If file is in a YYYY-MM subdirectory, derive from dir + filename
60
+ # If file is in root handoffs/ (no month subdir), extract date from filename
61
+ if [[ "$DIR_MONTH" =~ ^[0-9]{4}-[0-9]{2}$ ]]; then
62
+ DAY="${FILENAME%%-*}"
63
+ REST="${FILENAME#*-}"
64
+ SESSION_ID="${DIR_MONTH}-${DAY}-${REST}"
65
+ else
66
+ # Filename starts with YYYY-MM-DD — use it directly
67
+ SESSION_ID="$FILENAME"
68
+ DAY="$(echo "$FILENAME" | grep -oE '^[0-9]{4}-[0-9]{2}-[0-9]{2}' || true)"
69
+ REST="${FILENAME#*-*-*-}"
70
+ DIR_MONTH="$(echo "$DAY" | grep -oE '^[0-9]{4}-[0-9]{2}' || true)"
71
+ fi
72
+
73
+ # --- Parse handoff metadata from markdown ---
74
+ CONTENT="$(cat "$FILE_PATH")"
75
+
76
+ # Helper: extract from markdown headers, handles both **Key**: Value and **Key:** Value
77
+ md_extract() {
78
+ echo "$CONTENT" | grep -m1 "$1" 2>/dev/null | sed "$2" || true
79
+ }
80
+
81
+ # Helper: extract markdown field by name (handles **Key**: and **Key:** patterns)
82
+ md_field() {
83
+ local key="$1"
84
+ local val
85
+ # Try **Key**: Value (colon outside bold)
86
+ val="$(echo "$CONTENT" | grep -m1 "^\*\*${key}\*\*:" 2>/dev/null | sed "s/\*\*${key}\*\*:[[:space:]]*//" | tr -d '\n' || true)"
87
+ # Try **Key:** Value (colon inside bold)
88
+ if [ -z "$val" ]; then
89
+ val="$(echo "$CONTENT" | grep -m1 "^\*\*${key}:\*\*" 2>/dev/null | sed "s/\*\*${key}:\*\*[[:space:]]*//" | tr -d '\n' || true)"
90
+ fi
91
+ echo "$val"
92
+ }
93
+
94
+ # Helper: extract from YAML front matter (key: value between --- delimiters)
95
+ yaml_extract() {
96
+ local key="$1"
97
+ sed -n '/^---$/,/^---$/p' "$FILE_PATH" 2>/dev/null | grep -m1 "^${key}:" 2>/dev/null | sed "s/^${key}:[[:space:]]*//" | sed 's/^"//' | sed 's/"$//' | xargs 2>/dev/null || true
98
+ }
99
+
100
+ # H1 topic (always from markdown body, not front matter)
101
+ TOPIC="$(md_extract '^# ' 's/^# //; s/^Handoff: //')"
102
+ # Also check YAML front matter topic
103
+ [ -z "$TOPIC" ] && TOPIC="$(yaml_extract 'topic')"
104
+ [ -z "$TOPIC" ] && TOPIC="$REST"
105
+
106
+ # Date: try markdown header, then YAML
107
+ DATE="$(md_field 'Date')"
108
+ [ -z "$DATE" ] && DATE="$(yaml_extract 'date')"
109
+ [ -z "$DATE" ] && DATE="${DIR_MONTH}-${DAY}"
110
+
111
+ # Author: try markdown header (Author or From), then YAML (author or from)
112
+ AUTHOR="$(md_field 'Author')"
113
+ [ -z "$AUTHOR" ] && AUTHOR="$(md_field 'From')"
114
+ [ -z "$AUTHOR" ] && AUTHOR="$(yaml_extract 'author')"
115
+ [ -z "$AUTHOR" ] && AUTHOR="$(yaml_extract 'from')"
116
+ # Strip " → recipient" if present (e.g. "cem → oz" → "cem")
117
+ AUTHOR_HANDLE="$(echo "$AUTHOR" | sed 's/[[:space:]]*→.*//' | awk '{print tolower($1)}')"
118
+ [ -z "$AUTHOR_HANDLE" ] && AUTHOR_HANDLE="unknown"
119
+
120
+ # Recipients: try markdown header (To or For), then YAML (to or for)
121
+ RECIPIENTS_RAW="$(md_field 'To')"
122
+ [ -z "$RECIPIENTS_RAW" ] && RECIPIENTS_RAW="$(md_field 'For')"
123
+ [ -z "$RECIPIENTS_RAW" ] && RECIPIENTS_RAW="$(yaml_extract 'to')"
124
+ [ -z "$RECIPIENTS_RAW" ] && RECIPIENTS_RAW="$(yaml_extract 'for')"
125
+ # Also extract from "From: author → recipient" pattern
126
+ if [ -z "$RECIPIENTS_RAW" ] && echo "$AUTHOR" | grep -q '→' 2>/dev/null; then
127
+ RECIPIENTS_RAW="$(echo "$AUTHOR" | sed 's/.*→[[:space:]]*//')"
128
+ fi
129
+
130
+ # Project: try markdown header, then YAML
131
+ PROJECT="$(md_field 'Project')"
132
+ [ -z "$PROJECT" ] && PROJECT="$(yaml_extract 'project')"
133
+
134
+ # Handoff kind: used by readers to distinguish automatic captures from manual handoffs.
135
+ KIND="$(md_field 'Kind')"
136
+ [ -z "$KIND" ] && KIND="$(yaml_extract 'kind')"
137
+
138
+ # Summary: 5-level precedence chain
139
+ # 1. Frontmatter summary field
140
+ SUMMARY="$(yaml_extract 'summary')"
141
+ # 2. First non-empty paragraph after ## Briefing
142
+ [ -z "$SUMMARY" ] && SUMMARY="$(echo "$CONTENT" | awk '/^## Briefing/{found=1; next} found && /^#/{exit} found && /^[^#]/ && !/^[[:space:]]*$/{print; exit}' || true)"
143
+ # 3. First non-empty paragraph after ## Session Summary (legacy)
144
+ [ -z "$SUMMARY" ] && SUMMARY="$(echo "$CONTENT" | awk '/^## Session Summary/{found=1; next} found && /^#/{exit} found && /^[^#]/ && !/^[[:space:]]*$/{print; exit}' || true)"
145
+ # 4. First non-heading, non-metadata, non-empty line in body
146
+ [ -z "$SUMMARY" ] && SUMMARY="$(echo "$CONTENT" | awk '!/^[[:space:]]*$/ && !/^#/ && !/^\*\*/ && !/^---/ && !/^\|/ && !/^- /{print; exit}' || true)"
147
+ # 5. Topic fallback
148
+ [ -z "$SUMMARY" ] && SUMMARY="$TOPIC"
149
+
150
+ # Repo State: parse markdown table after "## Repo State"
151
+ # Extracts repo, branch, PR number, and base branch from each table row
152
+ REPO_STATE_JSON="[]"
153
+ REPO_TABLE=$(echo "$CONTENT" | awk '/^## Repo State/{found=1; next} found && /^#/{exit} found && /^\|[^-]/ && !/^\| Repo/{print}' || true)
154
+ if [ -n "$REPO_TABLE" ]; then
155
+ REPO_STATE_JSON=$(echo "$REPO_TABLE" | while IFS='|' read -r _ R_REPO R_BRANCH R_PR R_BASE _; do
156
+ R_REPO=$(echo "$R_REPO" | xargs 2>/dev/null || true)
157
+ R_BRANCH=$(echo "$R_BRANCH" | xargs 2>/dev/null || true)
158
+ R_PR=$(echo "$R_PR" | xargs 2>/dev/null | sed 's/^#//' | sed 's/—//' || true)
159
+ R_BASE=$(echo "$R_BASE" | xargs 2>/dev/null || true)
160
+ [ -z "$R_REPO" ] && continue
161
+ if [ -n "$R_PR" ] && [ "$R_PR" -eq "$R_PR" ] 2>/dev/null; then
162
+ jq -n --arg repo "$R_REPO" --arg branch "$R_BRANCH" --argjson pr "$R_PR" --arg base "$R_BASE" \
163
+ '{repo:$repo, branch:$branch, pr:$pr, base:$base}'
164
+ else
165
+ jq -n --arg repo "$R_REPO" --arg branch "$R_BRANCH" --arg base "$R_BASE" \
166
+ '{repo:$repo, branch:$branch, pr:null, base:$base}'
167
+ fi
168
+ done | jq -s '.' 2>/dev/null || echo "[]")
169
+ fi
170
+
171
+ # --- Build Cypher queries ---
172
+
173
+ # Q1: MERGE Session + relationships
174
+ HANDED_TO_CYPHER=""
175
+ _RECIP_ARGS=()
176
+ if [ -n "$RECIPIENTS_RAW" ]; then
177
+ IFS=',' read -ra RECIPS <<< "$RECIPIENTS_RAW"
178
+ for i in "${!RECIPS[@]}"; do
179
+ R="$(echo "${RECIPS[$i]}" | xargs | tr '[:upper:]' '[:lower:]')"
180
+ [ -z "$R" ] && continue
181
+ HANDED_TO_CYPHER="${HANDED_TO_CYPHER}
182
+ WITH s
183
+ OPTIONAL MATCH (r${i}:Person) WHERE toLower(r${i}.name) = \$recipient${i} OR r${i}.github = \$recipient${i} OR toLower(r${i}.fullName) = \$recipient${i}
184
+ FOREACH (_ IN CASE WHEN r${i} IS NOT NULL THEN [1] ELSE [] END |
185
+ MERGE (s)-[:HANDED_TO]->(r${i})
186
+ )"
187
+ _RECIP_ARGS+=("--arg" "recipient${i}" "$R")
188
+ done
189
+ fi
190
+
191
+ PROJECT_CYPHER=""
192
+ _PROJECT_ARGS=()
193
+ if [ -n "$PROJECT" ]; then
194
+ PROJECT_CYPHER="
195
+ WITH s
196
+ OPTIONAL MATCH (proj:Project) WHERE toLower(proj.name) = toLower(\$project)
197
+ FOREACH (_ IN CASE WHEN proj IS NOT NULL THEN [1] ELSE [] END |
198
+ MERGE (s)-[:ABOUT]->(proj)
199
+ )"
200
+ _PROJECT_ARGS=("--arg" "project" "$PROJECT")
201
+ fi
202
+
203
+ Q1_CYPHER="MATCH (p:Person) WHERE toLower(p.name) = \$author OR p.github = \$author OR toLower(p.fullName) = \$author OR \$author IN [x IN coalesce(p.previousNames, []) | toLower(x)]
204
+ MERGE (s:Session {id: \$sessionId})
205
+ ON CREATE SET s.date = date(\$date), s.topic = \$topic, s.summary = \$summary, s.filePath = \$filePath, s.author = \$author, s.handoffKind = CASE WHEN \$kind = '' THEN null ELSE \$kind END, s.handoffStatus = 'pending', s.repoState = \$repoState, s.startedAt = datetime(\$date + 'T00:00:00Z')
206
+ ON MATCH SET s.topic = \$topic, s.summary = \$summary, s.filePath = \$filePath, s.author = \$author, s.handoffKind = CASE WHEN \$kind = '' THEN null ELSE \$kind END, s.repoState = \$repoState
207
+ WITH s, p
208
+ MERGE (s)-[:BY]->(p)${PROJECT_CYPHER}${HANDED_TO_CYPHER}
209
+ RETURN s.id AS sessionId"
210
+
211
+ Q1_PARAMS=$(jq -n \
212
+ --arg sessionId "$SESSION_ID" \
213
+ --arg author "$AUTHOR_HANDLE" \
214
+ --arg date "$DATE" \
215
+ --arg topic "$TOPIC" \
216
+ --arg summary "$SUMMARY" \
217
+ --arg filePath "$REL_PATH" \
218
+ --arg kind "$KIND" \
219
+ --arg repoState "$REPO_STATE_JSON" \
220
+ "${_PROJECT_ARGS[@]:+${_PROJECT_ARGS[@]}}" \
221
+ "${_RECIP_ARGS[@]:+${_RECIP_ARGS[@]}}" \
222
+ '$ARGS.named')
223
+
224
+ # Q2: Auto-resolve old read handoffs from this author
225
+ Q2_CYPHER="MATCH (s:Session)-[:HANDED_TO]->(p:Person) WHERE (toLower(p.name) = \$author OR p.github = \$author OR toLower(p.fullName) = \$author) AND s.handoffStatus = 'read' AND s.id <> \$sessionId
226
+ WITH s, p, coalesce(s.handoffReadDate, s.date) AS sinceDate
227
+ MATCH (later:Session)-[:BY]->(p)
228
+ WHERE later.date > sinceDate
229
+ WITH s, count(later) AS laterSessions WHERE laterSessions > 0
230
+ SET s.handoffStatus = 'done'
231
+ RETURN count(s) AS resolved"
232
+
233
+ Q2_PARAMS=$(jq -n --arg author "$AUTHOR_HANDLE" --arg sessionId "$SESSION_ID" \
234
+ '{author: $author, sessionId: $sessionId}')
235
+
236
+ # Q3: Capture the handoff's knowledge-graph neighborhood for artifact rendering.
237
+ # Only the "graph-only" relations — things not already visible in the markdown
238
+ # header. Author/recipients/project live in the metadata row above the graph,
239
+ # so including them would be noise.
240
+ Q3_CYPHER="MATCH (s:Session {id: \$sessionId})
241
+ OPTIONAL MATCH (s)-[:IMPLEMENTS]->(prior:Session)
242
+ OPTIONAL MATCH (prior)-[:BY]->(priorAuthor:Person)
243
+ OPTIONAL MATCH (later:Session)-[:IMPLEMENTS]->(s)
244
+ OPTIONAL MATCH (later)-[:BY]->(laterAuthor:Person)
245
+ OPTIONAL MATCH (s)-[:CONTINUES]->(cont:Session)
246
+ OPTIONAL MATCH (cont)-[:BY]->(contAuthor:Person)
247
+ OPTIONAL MATCH (s)-[:ADVANCED|INVOLVES]->(quest:Quest)
248
+ OPTIONAL MATCH (s)-[:HAS_ACTIVITY]->(art:Artifact)
249
+ OPTIONAL MATCH (s)-[:PRODUCED]->(pr:PR)
250
+ RETURN
251
+ collect(DISTINCT {id: prior.id, topic: prior.topic, author: priorAuthor.name}) AS implementsHandoff,
252
+ collect(DISTINCT {id: later.id, topic: later.topic, author: laterAuthor.name}) AS implementedBy,
253
+ collect(DISTINCT {id: cont.id, topic: cont.topic, author: contAuthor.name}) AS continues,
254
+ collect(DISTINCT {id: quest.id, title: quest.title}) AS quests,
255
+ collect(DISTINCT {id: art.id, title: art.title, type: art.type, path: art.path}) AS artifacts,
256
+ collect(DISTINCT {id: pr.id, number: pr.number, title: pr.title, repo: pr.repo}) AS prs"
257
+
258
+ Q3_PARAMS=$(jq -n --arg sessionId "$SESSION_ID" '{sessionId: $sessionId}')
259
+
260
+ # --- Build batch request ---
261
+ BATCH_JSON=$(jq -n \
262
+ --arg q1 "$Q1_CYPHER" \
263
+ --argjson p1 "$Q1_PARAMS" \
264
+ --arg q2 "$Q2_CYPHER" \
265
+ --argjson p2 "$Q2_PARAMS" \
266
+ --arg q3 "$Q3_CYPHER" \
267
+ --argjson p3 "$Q3_PARAMS" \
268
+ '[{statement: $q1, parameters: $p1}, {statement: $q2, parameters: $p2}, {statement: $q3, parameters: $p3}]')
269
+
270
+ # --- Execute ---
271
+ RESPONSE=$(bash "$SCRIPT_DIR/bin/graph-batch.sh" "$BATCH_JSON" 2>/dev/null) || {
272
+ echo '{"error":"graph-batch.sh failed"}'
273
+ exit 1
274
+ }
275
+
276
+ # --- Mark auto-captured personal Session as handed_off ---
277
+ PROJ_HASH=$(echo -n "$SCRIPT_DIR" | md5 2>/dev/null || echo -n "$SCRIPT_DIR" | md5sum 2>/dev/null | cut -d' ' -f1)
278
+ CURRENT_SID=$(cat "$HOME/.egregore/session-${PROJ_HASH}.id" 2>/dev/null || echo "")
279
+ if [ -n "$CURRENT_SID" ]; then
280
+ bash "$SCRIPT_DIR/bin/graph.sh" query \
281
+ "MATCH (s:Session {id: \$sid}) SET s.status = 'handed_off' RETURN s.id" \
282
+ "$(jq -n --arg sid "$CURRENT_SID" '{sid: $sid}')" >/dev/null 2>&1 || true
283
+ fi
284
+
285
+ # --- Parse response ---
286
+ RESOLVED=$(echo "$RESPONSE" | jq -r '.results[1].values[0][0] // 0' 2>/dev/null || echo "0")
287
+
288
+ # Parse Q3 neighborhood into a subgraph object. Collect() entries that didn't
289
+ # match anything come back as {name: null, ...} — filter those out.
290
+ EMPTY_SG='{"implementsHandoff":[],"implementedBy":[],"continues":[],"quests":[],"artifacts":[],"prs":[]}'
291
+ SUBGRAPH_JSON=$(echo "$RESPONSE" | jq -c '
292
+ (.results[2] // null) as $r
293
+ | if $r == null or (($r.values // []) | length) == 0 then
294
+ {implementsHandoff:[], implementedBy:[], continues:[], quests:[], artifacts:[], prs:[]}
295
+ else
296
+ ($r.fields // []) as $f
297
+ | ($r.values[0] // []) as $v
298
+ | reduce range(0; ($f | length)) as $i ({}; .[$f[$i]] = $v[$i])
299
+ | {
300
+ implementsHandoff: ((.implementsHandoff // []) | map(select(.id != null))),
301
+ implementedBy: ((.implementedBy // []) | map(select(.id != null))),
302
+ continues: ((.continues // []) | map(select(.id != null))),
303
+ quests: ((.quests // []) | map(select(.id != null))),
304
+ artifacts: ((.artifacts // []) | map(select(.id != null or .path != null))),
305
+ prs: ((.prs // []) | map(select(.number != null or .id != null)))
306
+ }
307
+ end
308
+ ' 2>/dev/null || echo "$EMPTY_SG")
309
+ [ -z "$SUBGRAPH_JSON" ] && SUBGRAPH_JSON="$EMPTY_SG"
310
+
311
+ jq -n --arg sessionId "$SESSION_ID" --argjson resolved "$RESOLVED" --argjson subgraph "$SUBGRAPH_JSON" \
312
+ '{sessionId: $sessionId, resolved: $resolved, subgraph: $subgraph}'
@@ -0,0 +1,46 @@
1
+ #!/bin/bash
2
+ # artifact-id.sh — Deterministic artifact IDs for memory files.
3
+ #
4
+ # Emits `{prefix}-{12 hex}` where prefix is derived from the file extension
5
+ # (m = .md, h = .html) and the hex is the first 12 chars of sha256(canonical_path).
6
+ #
7
+ # Kept in parity with packages/egregore-artifacts/lib/artifact-id.js — both MUST
8
+ # produce identical output for the same input. Any change here requires an
9
+ # equivalent change there (and a passing parity test in test/artifact-id.test.js).
10
+
11
+ _artifact_id_sha256() {
12
+ local input="$1"
13
+ printf '%s' "$input" | shasum -a 256 2>/dev/null | cut -d' ' -f1 \
14
+ || printf '%s' "$input" | sha256sum 2>/dev/null | cut -d' ' -f1
15
+ }
16
+
17
+ # artifact_id_from_path <path>
18
+ # Returns the deterministic artifact id for a repo-relative memory path, or
19
+ # empty (exit 1) if the extension is unsupported.
20
+ artifact_id_from_path() {
21
+ local raw="$1"
22
+ [ -n "$raw" ] || return 1
23
+
24
+ local canonical
25
+ canonical="${raw#./}"
26
+ canonical="$(printf '%s' "$canonical" | sed 's|/\{2,\}|/|g')"
27
+
28
+ # Extension match is case-insensitive to keep parity with the JS renderer
29
+ # (`extension()` in artifact-id.js lowercases before lookup). The canonical
30
+ # path itself is hashed verbatim — only the extension lookup is case-folded.
31
+ local ext_lower
32
+ ext_lower="$(printf '%s' "${canonical##*.}" | tr '[:upper:]' '[:lower:]')"
33
+
34
+ local prefix=""
35
+ case "$ext_lower" in
36
+ md) prefix="m" ;;
37
+ html) prefix="h" ;;
38
+ *) return 1 ;;
39
+ esac
40
+
41
+ local hash
42
+ hash="$(_artifact_id_sha256 "$canonical")"
43
+ [ -n "$hash" ] || return 1
44
+
45
+ printf '%s-%s' "$prefix" "${hash:0:12}"
46
+ }
@@ -0,0 +1,71 @@
1
+ #!/bin/bash
2
+ # Shared config helpers — sourced by session-start.sh and other scripts.
3
+ # Expects SCRIPT_DIR to be set by the caller.
4
+
5
+ # Load specific variables from .env (safe extraction, no code execution)
6
+ _load_env_var() {
7
+ local var_name="$1"
8
+ local env_file="${ENV_FILE:-$SCRIPT_DIR/.env}"
9
+ if [ -f "$env_file" ]; then
10
+ grep "^${var_name}=" "$env_file" 2>/dev/null | cut -d'=' -f2- || true
11
+ fi
12
+ }
13
+
14
+ # Read a value from egregore.json
15
+ _config_val() {
16
+ local key="$1"
17
+ local default="${2:-}"
18
+ local config="${CONFIG:-$SCRIPT_DIR/egregore.json}"
19
+ if [ -f "$config" ]; then
20
+ local val
21
+ val=$(jq -r ".${key} // empty" "$config" 2>/dev/null || true)
22
+ echo "${val:-$default}"
23
+ else
24
+ echo "$default"
25
+ fi
26
+ }
27
+
28
+ # Get base branch for a repo. Reads base_branch from egregore.json repos[] config.
29
+ # Defaults to "develop" for backwards compatibility (existing egregores use develop).
30
+ # Only returns something other than "develop" when base_branch is explicitly set
31
+ # (e.g., for teams who added existing repos with a different default branch).
32
+ # Usage: _get_base_branch "repo-name" (omit for core repo)
33
+ _get_base_branch() {
34
+ local repo_name="${1:-}"
35
+ local config="${CONFIG:-$SCRIPT_DIR/egregore.json}"
36
+
37
+ # Core egregore repo — always develop
38
+ if [ -z "$repo_name" ]; then
39
+ echo "develop"
40
+ return
41
+ fi
42
+
43
+ # Managed repo — check egregore.json for explicit base_branch
44
+ if [ -f "$config" ]; then
45
+ local base
46
+ base=$(jq -r --arg name "$repo_name" \
47
+ '(.repos[]? // empty) | select((if type == "object" then .name else . end) == $name) | if type == "object" then .base_branch // empty else empty end' \
48
+ "$config" 2>/dev/null || true)
49
+ if [ -n "$base" ]; then
50
+ echo "$base"
51
+ return
52
+ fi
53
+ fi
54
+
55
+ # No explicit config — default to develop (backwards compatible)
56
+ echo "develop"
57
+ }
58
+
59
+ # Detect local vs connected mode
60
+ _detect_mode() {
61
+ local config="${CONFIG:-$SCRIPT_DIR/egregore.json}"
62
+ local mode
63
+ mode=$(jq -r '.mode // empty' "$config" 2>/dev/null || true)
64
+ local api_url
65
+ api_url=$(jq -r '.api_url // empty' "$config" 2>/dev/null || true)
66
+ if [ "$mode" = "local" ] || [ -z "$api_url" ]; then
67
+ echo "local"
68
+ else
69
+ echo "connected"
70
+ fi
71
+ }