create-egregore 0.19.33 → 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.
- package/assets/egregore-launcher.js +118 -13
- package/bin/cli.js +19 -3
- package/lib/connect-runtime.js +4 -2
- package/lib/local.js +15 -1
- package/lib/prime-runtime.js +208 -0
- package/lib/setup.js +45 -3
- package/package.json +4 -3
- package/runtime/codex/.codex/skills/ingest-notion/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/notion-connect/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/codex/.codex/spec-manifest.json +3 -3
- package/runtime/codex/AGENTS.md +11 -4
- package/runtime/codex/bin/activity-data.sh +4 -8
- package/runtime/codex/bin/attendant.sh +13 -0
- package/runtime/codex/bin/autosave.sh +27 -1
- package/runtime/codex/bin/codex-render-spec.mjs +6 -0
- package/runtime/codex/bin/codex-session-start.sh +3 -1
- package/runtime/codex/bin/graph-batch.sh +25 -6
- package/runtime/codex/bin/graph-op.sh +5 -4
- package/runtime/codex/bin/graph.sh +31 -5
- package/runtime/codex/bin/ingest.py +81 -5
- package/runtime/codex/bin/lib/context.sh +26 -1
- package/runtime/codex/bin/lib/greeting.sh +4 -1
- package/runtime/codex/bin/lib/worktree-links.sh +25 -0
- package/runtime/codex/manifest.json +7 -1
- package/runtime/pi/.codex/skills/ingest-notion/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/notion-connect/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/pi/.codex/spec-manifest.json +3 -3
- package/runtime/pi/.pi/APPEND_SYSTEM.md +3 -2
- package/runtime/pi/.pi/extensions/egregore.ts +17 -6
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +11 -4
- package/runtime/pi/bin/activity-data.sh +4 -8
- package/runtime/pi/bin/attendant.sh +13 -0
- package/runtime/pi/bin/autosave.sh +27 -1
- package/runtime/pi/bin/codex-render-spec.mjs +6 -0
- package/runtime/pi/bin/codex-session-start.sh +3 -1
- package/runtime/pi/bin/graph-batch.sh +25 -6
- package/runtime/pi/bin/graph-op.sh +5 -4
- package/runtime/pi/bin/graph.sh +31 -5
- package/runtime/pi/bin/ingest.py +81 -5
- package/runtime/pi/bin/lib/context.sh +26 -1
- package/runtime/pi/bin/lib/greeting.sh +4 -1
- package/runtime/pi/bin/lib/worktree-links.sh +25 -0
- package/runtime/pi/bin/pi-branch-policy.mjs +138 -1
- package/runtime/pi/bin/pi-product-workflows.mjs +35 -7
- package/runtime/pi/manifest.json +7 -1
- package/runtime/prime/.claude/context/notification-consent.md +66 -0
- package/runtime/prime/.codex/config.toml +17 -0
- package/runtime/prime/.codex/hooks/branch-guard.js +461 -0
- package/runtime/prime/.codex/hooks/search-hint.js +67 -0
- package/runtime/prime/.codex/hooks.json +30 -0
- package/runtime/prime/.codex/skills/activity/SKILL.md +82 -0
- package/runtime/prime/.codex/skills/announce/SKILL.md +67 -0
- package/runtime/prime/.codex/skills/archive/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/branch/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/checkup/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/commit/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/contribute/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/create-skill/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/dark-mode/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/dashboard/SKILL.md +59 -0
- package/runtime/prime/.codex/skills/deep-reflect/SKILL.md +109 -0
- package/runtime/prime/.codex/skills/delete-user/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/emissary/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/handoff/SKILL.md +166 -0
- package/runtime/prime/.codex/skills/harvest/QUESTION_PALETTE.md +76 -0
- package/runtime/prime/.codex/skills/harvest/SKILL.md +131 -0
- package/runtime/prime/.codex/skills/ingest/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/ingest-notion/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/ingest-user-interview/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/invite/SKILL.md +44 -0
- package/runtime/prime/.codex/skills/issue/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/me/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/meeting/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/note/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/notion-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/onboarding/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/pr/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/pull/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/push/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/quest/SKILL.md +80 -0
- package/runtime/prime/.codex/skills/reflect/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/review-pr/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/save/SKILL.md +125 -0
- package/runtime/prime/.codex/skills/slack-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/summon/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/sync-repos/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/telegram-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/telemetry/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/test/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/the-spiral/SKILL.md +84 -0
- package/runtime/prime/.codex/skills/tui-design/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/tutorial/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/update/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/view/SKILL.md +156 -0
- package/runtime/prime/.codex/skills/view/agents/openai.yaml +4 -0
- package/runtime/prime/.codex/skills/view/assets/compose-scaffold.html +221 -0
- package/runtime/prime/.codex/skills/visual-explain/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/wrap/SKILL.md +58 -0
- package/runtime/prime/.codex/spec-manifest.json +132 -0
- package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +393 -0
- package/runtime/prime/.prime/agent/extensions/egregore.ts +460 -0
- package/runtime/prime/.prime/agent/settings.json +4 -0
- package/runtime/prime/.prime/agent/skills/egregore/SKILL.md +34 -0
- package/runtime/prime/.prime/agent/skills/egregore/pyproject.toml +13 -0
- package/runtime/prime/.prime/agent/skills/egregore/src/egregore/__init__.py +156 -0
- package/runtime/prime/.prime/agent/spec-manifest.json +12 -0
- package/runtime/prime/AGENTS.md +489 -0
- package/runtime/prime/bin/activity-data.sh +238 -0
- package/runtime/prime/bin/agent.sh +775 -0
- package/runtime/prime/bin/attendant.sh +707 -0
- package/runtime/prime/bin/autosave.sh +112 -0
- package/runtime/prime/bin/capture-reconcile.sh +69 -0
- package/runtime/prime/bin/capture-run.sh +341 -0
- package/runtime/prime/bin/codex-install-skills.sh +78 -0
- package/runtime/prime/bin/codex-render-spec.mjs +440 -0
- package/runtime/prime/bin/codex-session-start.sh +265 -0
- package/runtime/prime/bin/codex-skill-render.mjs +347 -0
- package/runtime/prime/bin/codex-sync-skills.sh +232 -0
- package/runtime/prime/bin/connect-refresh.sh +33 -0
- package/runtime/prime/bin/dashboard-data.sh +178 -0
- package/runtime/prime/bin/graph-batch.sh +79 -0
- package/runtime/prime/bin/graph-op.sh +1102 -0
- package/runtime/prime/bin/graph-wal.sh +229 -0
- package/runtime/prime/bin/graph.sh +223 -0
- package/runtime/prime/bin/handoff-pr-backfill.sh +86 -0
- package/runtime/prime/bin/handoff-run.sh +671 -0
- package/runtime/prime/bin/handoff-save-egregore.sh +212 -0
- package/runtime/prime/bin/index-handoff.sh +322 -0
- package/runtime/prime/bin/ingest-graph.sh +80 -0
- package/runtime/prime/bin/ingest-surface.html +495 -0
- package/runtime/prime/bin/ingest.py +756 -0
- package/runtime/prime/bin/ingest.sh +279 -0
- package/runtime/prime/bin/ingest_graph.py +856 -0
- package/runtime/prime/bin/ingest_surface.py +544 -0
- package/runtime/prime/bin/lib/artifact-id.sh +46 -0
- package/runtime/prime/bin/lib/config.sh +116 -0
- package/runtime/prime/bin/lib/context.sh +621 -0
- package/runtime/prime/bin/lib/dashboard-artifact.sh +242 -0
- package/runtime/prime/bin/lib/ensure-develop.sh +183 -0
- package/runtime/prime/bin/lib/git-sync.sh +406 -0
- package/runtime/prime/bin/lib/greeting.sh +519 -0
- package/runtime/prime/bin/lib/hash.sh +13 -0
- package/runtime/prime/bin/lib/identity.sh +213 -0
- package/runtime/prime/bin/lib/metrics.sh +374 -0
- package/runtime/prime/bin/lib/noncode.sh +31 -0
- package/runtime/prime/bin/lib/permissions.sh +106 -0
- package/runtime/prime/bin/lib/time.sh +56 -0
- package/runtime/prime/bin/lib/worktree-links.sh +79 -0
- package/runtime/prime/bin/notify.sh +636 -0
- package/runtime/prime/bin/person.py +630 -0
- package/runtime/prime/bin/person.sh +448 -0
- package/runtime/prime/bin/pi-branch-policy.mjs +312 -0
- package/runtime/prime/bin/pi-product-workflows.mjs +394 -0
- package/runtime/prime/bin/prime-render-spec.mjs +123 -0
- package/runtime/prime/bin/prime-session-start.sh +24 -0
- package/runtime/prime/bin/publish-artifact.sh +271 -0
- package/runtime/prime/bin/publish-references.sh +144 -0
- package/runtime/prime/bin/repo-state.sh +96 -0
- package/runtime/prime/bin/search.sh +240 -0
- package/runtime/prime/bin/session-autosave.sh +209 -0
- package/runtime/prime/bin/session-log.sh +199 -0
- package/runtime/prime/bin/settings.sh +304 -0
- package/runtime/prime/bin/telemetry.sh +391 -0
- package/runtime/prime/bin/worktree-create.sh +128 -0
- package/runtime/prime/bin/worktree-remove.sh +50 -0
- package/runtime/prime/bin/worktree.sh +198 -0
- package/runtime/prime/manifest.json +178 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
# handoff-run.sh — atomic orchestrator for /handoff.
|
|
4
|
+
#
|
|
5
|
+
# Everything mechanical happens here, in one bash process, so the main
|
|
6
|
+
# session renders a single collapsed Bash tool block instead of 6+.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# bash bin/handoff-run.sh \
|
|
10
|
+
# --author <handle> \
|
|
11
|
+
# --topic "<short topic>" \
|
|
12
|
+
# [--recipient <name>] \
|
|
13
|
+
# [--project <name>] \
|
|
14
|
+
# [--intent <action|feedback|fyi>] \
|
|
15
|
+
# [--content-mode <supplied|generated>] \
|
|
16
|
+
# [--include-session-artifacts] \
|
|
17
|
+
# <<'HANDOFFEOF'
|
|
18
|
+
# ...full markdown body of the handoff...
|
|
19
|
+
# HANDOFFEOF
|
|
20
|
+
#
|
|
21
|
+
# The full file body is read from stdin. The script:
|
|
22
|
+
# 1. Computes the file path: memory/handoffs/YYYY-MM/DD-author-slug.md
|
|
23
|
+
# 2. Writes the file
|
|
24
|
+
# 3. In generated mode, appends ## Repo State when non-empty
|
|
25
|
+
# 4. Prepends memory/handoffs/index.md with a new entry
|
|
26
|
+
# 5. Runs bin/index-handoff.sh (connected mode only) — creates Session node
|
|
27
|
+
# and returns a subgraph snapshot in the result JSON
|
|
28
|
+
# 6. Commits + pushes memory repo (with orphan-stash dance if needed)
|
|
29
|
+
# 7. Publishes HTML artifact via bin/publish-artifact.sh
|
|
30
|
+
# 8. Creates an exact notification proposal via bin/notify.sh (never sends)
|
|
31
|
+
# 9. Emits ONE status line and ONE JSON blob on stdout
|
|
32
|
+
#
|
|
33
|
+
# Exit codes:
|
|
34
|
+
# 0 on success (even if graph/publish/notify individually fail — they are
|
|
35
|
+
# non-blocking and reported in the JSON).
|
|
36
|
+
# 1 on unrecoverable failure (file write, memory push, bad args).
|
|
37
|
+
|
|
38
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
39
|
+
CONFIG="$SCRIPT_DIR/egregore.json"
|
|
40
|
+
|
|
41
|
+
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
|
42
|
+
sed -n '2,30p' "$0"
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# --- Parse args ----------------------------------------------------------
|
|
47
|
+
AUTHOR=""
|
|
48
|
+
TOPIC=""
|
|
49
|
+
RECIPIENT=""
|
|
50
|
+
PROJECT=""
|
|
51
|
+
INTENT="action"
|
|
52
|
+
NO_PUSH=0
|
|
53
|
+
NO_NOTIFY=0
|
|
54
|
+
NO_PUBLISH=0
|
|
55
|
+
COMPOSED="" # optional house-kit JSON — the agent-composed render spec
|
|
56
|
+
CONTENT_MODE="generated"
|
|
57
|
+
INCLUDE_SESSION_ARTIFACTS=0
|
|
58
|
+
|
|
59
|
+
while [ $# -gt 0 ]; do
|
|
60
|
+
case "$1" in
|
|
61
|
+
--author) AUTHOR="${2:?}"; shift 2 ;;
|
|
62
|
+
--topic) TOPIC="${2:?}"; shift 2 ;;
|
|
63
|
+
--recipient) RECIPIENT="${2:-}"; shift 2 ;;
|
|
64
|
+
--project) PROJECT="${2:-}"; shift 2 ;;
|
|
65
|
+
--intent) INTENT="${2:-}"; shift 2 ;;
|
|
66
|
+
--composed) COMPOSED="${2:-}"; shift 2 ;;
|
|
67
|
+
--content-mode) CONTENT_MODE="${2:-}"; shift 2 ;;
|
|
68
|
+
--include-session-artifacts) INCLUDE_SESSION_ARTIFACTS=1; shift ;;
|
|
69
|
+
--no-push) NO_PUSH=1; shift ;;
|
|
70
|
+
--no-notify) NO_NOTIFY=1; shift ;;
|
|
71
|
+
--no-publish) NO_PUBLISH=1; shift ;;
|
|
72
|
+
*) echo "Unknown arg: $1" >&2; exit 1 ;;
|
|
73
|
+
esac
|
|
74
|
+
done
|
|
75
|
+
|
|
76
|
+
# The render source: the agent-composed house-kit JSON when supplied (rich
|
|
77
|
+
# component render), else the markdown handoff (deterministic floor). The
|
|
78
|
+
# markdown file is ALWAYS the memory record + graph index either way.
|
|
79
|
+
RENDER_TYPE="handoff"
|
|
80
|
+
RENDER_SRC="" # resolved to $ABS_FILE after the file is written, unless composed
|
|
81
|
+
if [ "$CONTENT_MODE" = "generated" ] && [ -n "$COMPOSED" ] && [ -f "$COMPOSED" ]; then
|
|
82
|
+
RENDER_TYPE="composed"
|
|
83
|
+
RENDER_SRC="$COMPOSED"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
[ -z "$AUTHOR" ] && { echo "Missing --author" >&2; exit 1; }
|
|
87
|
+
[ -z "$TOPIC" ] && { echo "Missing --topic" >&2; exit 1; }
|
|
88
|
+
case "$INTENT" in
|
|
89
|
+
action|feedback|fyi) ;;
|
|
90
|
+
*) echo "Invalid --intent: $INTENT (action|feedback|fyi)" >&2; exit 1 ;;
|
|
91
|
+
esac
|
|
92
|
+
case "$CONTENT_MODE" in
|
|
93
|
+
supplied|generated) ;;
|
|
94
|
+
*) echo "Invalid --content-mode: $CONTENT_MODE (supplied|generated)" >&2; exit 1 ;;
|
|
95
|
+
esac
|
|
96
|
+
if [ "$CONTENT_MODE" = "supplied" ] && [ -n "$COMPOSED" ]; then
|
|
97
|
+
echo "--composed cannot be used with --content-mode supplied" >&2
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
MODE=$(jq -r '.mode // "connected"' "$CONFIG" 2>/dev/null || echo "connected")
|
|
102
|
+
TODAY=$(date +%Y-%m-%d)
|
|
103
|
+
YYYY_MM=$(date +%Y-%m)
|
|
104
|
+
DD=$(date +%d)
|
|
105
|
+
|
|
106
|
+
# --- Derive slug + file path --------------------------------------------
|
|
107
|
+
_slugify() {
|
|
108
|
+
echo "$1" \
|
|
109
|
+
| tr '[:upper:]' '[:lower:]' \
|
|
110
|
+
| sed -E 's/[^a-z0-9]+/-/g' \
|
|
111
|
+
| sed -E 's/^-+|-+$//g' \
|
|
112
|
+
| cut -c1-50
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
SLUG=$(_slugify "$TOPIC")
|
|
116
|
+
REL_FILE="handoffs/${YYYY_MM}/${DD}-${AUTHOR}-${SLUG}.md"
|
|
117
|
+
ABS_FILE="$SCRIPT_DIR/memory/${REL_FILE}"
|
|
118
|
+
|
|
119
|
+
# Collision avoidance: append -N if file already exists
|
|
120
|
+
N=2
|
|
121
|
+
while [ -e "$ABS_FILE" ]; do
|
|
122
|
+
REL_FILE="handoffs/${YYYY_MM}/${DD}-${AUTHOR}-${SLUG}-${N}.md"
|
|
123
|
+
ABS_FILE="$SCRIPT_DIR/memory/${REL_FILE}"
|
|
124
|
+
N=$((N+1))
|
|
125
|
+
[ $N -gt 99 ] && { echo "Too many collisions for $SLUG" >&2; exit 1; }
|
|
126
|
+
done
|
|
127
|
+
|
|
128
|
+
mkdir -p "$(dirname "$ABS_FILE")"
|
|
129
|
+
|
|
130
|
+
# --- Write the file from stdin ------------------------------------------
|
|
131
|
+
cat > "$ABS_FILE"
|
|
132
|
+
|
|
133
|
+
if [ ! -s "$ABS_FILE" ]; then
|
|
134
|
+
echo "Empty stdin — handoff body is required" >&2
|
|
135
|
+
rm -f "$ABS_FILE"
|
|
136
|
+
exit 1
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# --- Canonical metadata boundary ----------------------------------------
|
|
140
|
+
# Callers are allowed to send prose-only markdown, but persisted handoffs are
|
|
141
|
+
# not. Normalize every input to the same frontmatter contract before indexing
|
|
142
|
+
# or rendering so title/author/recipient cannot disappear at the adapter seam.
|
|
143
|
+
HAS_FRONTMATTER=0
|
|
144
|
+
[ "$(head -1 "$ABS_FILE")" = "---" ] && HAS_FRONTMATTER=1
|
|
145
|
+
|
|
146
|
+
_has_frontmatter_key() {
|
|
147
|
+
[ "$HAS_FRONTMATTER" = "1" ] && sed -n '2,/^---$/p' "$ABS_FILE" | grep -qiE "^${1}:"
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
MISSING_SCHEMA=0; _has_frontmatter_key capture_schema || MISSING_SCHEMA=1
|
|
151
|
+
MISSING_MODE=0; _has_frontmatter_key capture_mode || MISSING_MODE=1
|
|
152
|
+
MISSING_KIND=0; _has_frontmatter_key kind || MISSING_KIND=1
|
|
153
|
+
MISSING_FROM=0; _has_frontmatter_key from || _has_frontmatter_key author || MISSING_FROM=1
|
|
154
|
+
MISSING_DATE=0; _has_frontmatter_key date || MISSING_DATE=1
|
|
155
|
+
MISSING_TOPIC=0; _has_frontmatter_key topic || MISSING_TOPIC=1
|
|
156
|
+
MISSING_INTENT=0; _has_frontmatter_key intent || MISSING_INTENT=1
|
|
157
|
+
MISSING_CONTENT_MODE=0; _has_frontmatter_key content_mode || MISSING_CONTENT_MODE=1
|
|
158
|
+
MISSING_RECIPIENT=0
|
|
159
|
+
if [ -n "$RECIPIENT" ]; then
|
|
160
|
+
_has_frontmatter_key addressed_to || _has_frontmatter_key to || MISSING_RECIPIENT=1
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
awk \
|
|
164
|
+
-v has_frontmatter="$HAS_FRONTMATTER" \
|
|
165
|
+
-v schema="$MISSING_SCHEMA" -v mode="$MISSING_MODE" -v kind="$MISSING_KIND" \
|
|
166
|
+
-v from="$MISSING_FROM" -v date_missing="$MISSING_DATE" \
|
|
167
|
+
-v topic_missing="$MISSING_TOPIC" -v intent_missing="$MISSING_INTENT" \
|
|
168
|
+
-v content_mode_missing="$MISSING_CONTENT_MODE" \
|
|
169
|
+
-v recipient_missing="$MISSING_RECIPIENT" \
|
|
170
|
+
-v author="$AUTHOR" -v date="$TODAY" -v topic="$TOPIC" \
|
|
171
|
+
-v intent="$INTENT" -v recipient="$RECIPIENT" -v content_mode="$CONTENT_MODE" '
|
|
172
|
+
function metadata() {
|
|
173
|
+
if (schema) print "capture_schema: egregore-capture/v1"
|
|
174
|
+
if (mode) print "capture_mode: addressed"
|
|
175
|
+
if (kind) print "kind: addressed"
|
|
176
|
+
if (from) print "from: " author
|
|
177
|
+
if (recipient_missing) print "addressed_to: " recipient
|
|
178
|
+
if (date_missing) print "date: " date
|
|
179
|
+
if (topic_missing) print "topic: " topic
|
|
180
|
+
if (intent_missing) print "intent: " intent
|
|
181
|
+
if (content_mode_missing) print "content_mode: " content_mode
|
|
182
|
+
}
|
|
183
|
+
BEGIN {
|
|
184
|
+
if (!has_frontmatter) {
|
|
185
|
+
print "---"
|
|
186
|
+
metadata()
|
|
187
|
+
print "---"
|
|
188
|
+
if (content_mode != "supplied") print ""
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
has_frontmatter && NR == 1 {
|
|
192
|
+
print
|
|
193
|
+
metadata()
|
|
194
|
+
next
|
|
195
|
+
}
|
|
196
|
+
{ print }
|
|
197
|
+
' "$ABS_FILE" > "$ABS_FILE.new" && mv "$ABS_FILE.new" "$ABS_FILE"
|
|
198
|
+
|
|
199
|
+
# --- Append repo state ---------------------------------------------------
|
|
200
|
+
# --no-pr skips `gh pr list` (~400–600ms per repo). A detached PR backfill
|
|
201
|
+
# after the parallel phase rewrites `—` → `#N` and re-pushes memory.
|
|
202
|
+
REPO_STATE=""
|
|
203
|
+
if [ "$CONTENT_MODE" = "generated" ] || [ "$INCLUDE_SESSION_ARTIFACTS" = "1" ]; then
|
|
204
|
+
REPO_STATE=$(bash "$SCRIPT_DIR/bin/repo-state.sh" --no-pr 2>/dev/null || true)
|
|
205
|
+
fi
|
|
206
|
+
if [ -n "$REPO_STATE" ]; then
|
|
207
|
+
printf '\n%s\n' "$REPO_STATE" >> "$ABS_FILE"
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# --- Prepend memory/handoffs/index.md -----------------------------------
|
|
211
|
+
INDEX="$SCRIPT_DIR/memory/handoffs/index.md"
|
|
212
|
+
mkdir -p "$(dirname "$INDEX")"
|
|
213
|
+
[ -f "$INDEX" ] || printf '# Handoffs\n\n' > "$INDEX"
|
|
214
|
+
|
|
215
|
+
if [ -n "$RECIPIENT" ]; then
|
|
216
|
+
IDX_LINE="- **${TODAY}** — ${AUTHOR}: ${TOPIC} (handoff to ${RECIPIENT})"
|
|
217
|
+
else
|
|
218
|
+
IDX_LINE="- **${TODAY}** — ${AUTHOR}: ${TOPIC} (handoff)"
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
# Insert after the first blank line (keeping the # header intact)
|
|
222
|
+
awk -v line="$IDX_LINE" '
|
|
223
|
+
BEGIN { inserted = 0 }
|
|
224
|
+
/^$/ && !inserted { print; print line; inserted = 1; next }
|
|
225
|
+
{ print }
|
|
226
|
+
END { if (!inserted) print line }
|
|
227
|
+
' "$INDEX" > "$INDEX.new" && mv "$INDEX.new" "$INDEX"
|
|
228
|
+
|
|
229
|
+
# --- Parallel stage: graph + memory + (publish → notification proposal) -
|
|
230
|
+
#
|
|
231
|
+
# Three independent branches fork here and join before we emit the result:
|
|
232
|
+
# A. Graph index — reads ABS_FILE, writes Session node to Neo4j, and
|
|
233
|
+
# returns the handoff's neighborhood as subgraph JSON.
|
|
234
|
+
# B. Memory push — commits + pushes memory repo (orphan-stash dance).
|
|
235
|
+
# C. Publish+plan — publishes HTML artifact, then creates an immutable
|
|
236
|
+
# notification proposal with the artifact URL embedded.
|
|
237
|
+
#
|
|
238
|
+
# Each branch writes its result to a tmpfile. Wall-clock is max(A,B,C).
|
|
239
|
+
#
|
|
240
|
+
# The published artifact runs its own live re-query via bin/graph.sh at
|
|
241
|
+
# render time, so it picks up the subgraph even though Branch A may not
|
|
242
|
+
# have indexed the session yet by the time Branch C fires publish.
|
|
243
|
+
|
|
244
|
+
MEMORY_DIR="$SCRIPT_DIR/memory"
|
|
245
|
+
|
|
246
|
+
TMPD=$(mktemp -d -t handoff-run-XXXXXX)
|
|
247
|
+
trap 'rm -rf "$TMPD"' EXIT
|
|
248
|
+
|
|
249
|
+
# Extract briefing text once (used by publish and the proposal) so we don't
|
|
250
|
+
# re-awk the same file inside multiple workers.
|
|
251
|
+
BRIEFING_LEAD=$(awk '/^## Briefing/{found=1; next} found && /^##/{exit} found && NF{print}' "$ABS_FILE" 2>/dev/null \
|
|
252
|
+
| head -2 | tr '\n' ' ' | cut -c1-200 || true)
|
|
253
|
+
BRIEFING_SHORT=$(awk '/^## Briefing/{found=1; next} found && /^##/{exit} found && NF{print}' "$ABS_FILE" 2>/dev/null \
|
|
254
|
+
| head -3 | tr '\n' ' ' | cut -c1-280 || true)
|
|
255
|
+
|
|
256
|
+
# --- Branch A: Graph index ---
|
|
257
|
+
(
|
|
258
|
+
if [ "$MODE" = "connected" ]; then
|
|
259
|
+
GJ=$(bash "$SCRIPT_DIR/bin/index-handoff.sh" "$ABS_FILE" 2>/dev/null || echo '{}')
|
|
260
|
+
SID=$(echo "$GJ" | jq -r '.sessionId // ""' 2>/dev/null || echo "")
|
|
261
|
+
RES=$(echo "$GJ" | jq -r '.resolved // 0' 2>/dev/null || echo 0)
|
|
262
|
+
SG=$(echo "$GJ" | jq -c '.subgraph // null' 2>/dev/null || echo "null")
|
|
263
|
+
echo "$SG" > "$TMPD/subgraph"
|
|
264
|
+
if [ -n "$SID" ]; then
|
|
265
|
+
printf '%s\n%s\nok\n' "$SID" "$RES" > "$TMPD/graph"
|
|
266
|
+
else
|
|
267
|
+
printf '\n0\noffline\n' > "$TMPD/graph"
|
|
268
|
+
fi
|
|
269
|
+
else
|
|
270
|
+
echo "null" > "$TMPD/subgraph"
|
|
271
|
+
printf '\n0\nskipped\n' > "$TMPD/graph"
|
|
272
|
+
fi
|
|
273
|
+
) &
|
|
274
|
+
PID_GRAPH=$!
|
|
275
|
+
|
|
276
|
+
# --- Branch D: Today's artifacts (connected mode only) ---
|
|
277
|
+
# The graph is the right tool for this: indexed by date + author + excluded
|
|
278
|
+
# tag, returns a small structured list. The filesystem would require a full
|
|
279
|
+
# walk of memory/knowledge/* to answer. Runs in parallel; Branch B waits on
|
|
280
|
+
# this before committing so the file always has the section.
|
|
281
|
+
(
|
|
282
|
+
ARTIFACTS_JSON="[]"
|
|
283
|
+
if [ "$MODE" = "connected" ]; then
|
|
284
|
+
YY=$(date +%Y); MM=$(date +%-m); DD_NUM=$(date +%-d)
|
|
285
|
+
RAW=$(bash "$SCRIPT_DIR/bin/graph.sh" query "
|
|
286
|
+
MATCH (a:Artifact)-[:CONTRIBUTED_BY]->(p:Person {github: \$gh})
|
|
287
|
+
WHERE a.created >= datetime({year: $YY, month: $MM, day: $DD_NUM})
|
|
288
|
+
AND NOT 'tutorial-generated' IN coalesce(a.topics, [])
|
|
289
|
+
RETURN a.title AS title, a.type AS type, a.filePath AS path
|
|
290
|
+
ORDER BY a.created DESC
|
|
291
|
+
LIMIT 5" "$(jq -nc --arg gh "$AUTHOR" '{gh:$gh}')" 2>/dev/null || echo '{}')
|
|
292
|
+
# Response shape: {"fields":[...], "values":[[...], ...]}. Convert to
|
|
293
|
+
# an array of {title, type, path} objects.
|
|
294
|
+
ARTIFACTS_JSON=$(echo "$RAW" | jq -c '
|
|
295
|
+
(.fields // []) as $f
|
|
296
|
+
| (.values // [])
|
|
297
|
+
| map(. as $row | reduce range(0; ($f | length)) as $i ({}; .[$f[$i]] = $row[$i]))
|
|
298
|
+
' 2>/dev/null || echo '[]')
|
|
299
|
+
[ -z "$ARTIFACTS_JSON" ] && ARTIFACTS_JSON="[]"
|
|
300
|
+
fi
|
|
301
|
+
echo "$ARTIFACTS_JSON" > "$TMPD/artifacts"
|
|
302
|
+
) &
|
|
303
|
+
PID_ARTIFACTS=$!
|
|
304
|
+
|
|
305
|
+
# --- Branch B: Memory commit + push ---
|
|
306
|
+
(
|
|
307
|
+
if [ "$NO_PUSH" = "1" ]; then
|
|
308
|
+
echo "skipped (--no-push)" > "$TMPD/memory"
|
|
309
|
+
else
|
|
310
|
+
# Wait for Branch D (artifacts query) so the committed file has the
|
|
311
|
+
# `## Session Artifacts` section. D is fast (~1s) and runs parallel
|
|
312
|
+
# with Branch A; this wait doesn't extend the critical path because
|
|
313
|
+
# Branch C (publish) is the bottleneck.
|
|
314
|
+
wait "$PID_ARTIFACTS" 2>/dev/null || true
|
|
315
|
+
if [ -s "$TMPD/artifacts" ]; then
|
|
316
|
+
ART_COUNT=$(jq 'length' "$TMPD/artifacts" 2>/dev/null || echo 0)
|
|
317
|
+
if [ "${ART_COUNT:-0}" -gt 0 ]; then
|
|
318
|
+
{
|
|
319
|
+
printf '\n## Session Artifacts\n\n'
|
|
320
|
+
jq -r '.[] | "- \(.type // "Artifact"): \(.title) -> \(.path // "")"' "$TMPD/artifacts" 2>/dev/null
|
|
321
|
+
} >> "$ABS_FILE"
|
|
322
|
+
fi
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
cd "$MEMORY_DIR"
|
|
326
|
+
git add "$REL_FILE" handoffs/index.md >/dev/null 2>&1
|
|
327
|
+
|
|
328
|
+
if [ -n "$RECIPIENT" ]; then
|
|
329
|
+
MSG_COMMIT="Handoff: ${TOPIC} (to ${RECIPIENT})"
|
|
330
|
+
else
|
|
331
|
+
MSG_COMMIT="Handoff: ${TOPIC}"
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
git commit -m "$MSG_COMMIT" >/dev/null 2>&1 || true
|
|
335
|
+
|
|
336
|
+
STASHED=0
|
|
337
|
+
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
|
|
338
|
+
git stash push -u -m "handoff-run pre-pull stash" >/dev/null 2>&1 && STASHED=1
|
|
339
|
+
fi
|
|
340
|
+
|
|
341
|
+
MEM_OK=0
|
|
342
|
+
for i in 1 2 3; do
|
|
343
|
+
if git pull --rebase origin main --quiet >/dev/null 2>&1 \
|
|
344
|
+
&& git push origin main --quiet >/dev/null 2>&1; then
|
|
345
|
+
MEM_OK=1
|
|
346
|
+
break
|
|
347
|
+
fi
|
|
348
|
+
sleep 1
|
|
349
|
+
done
|
|
350
|
+
|
|
351
|
+
[ "$STASHED" = "1" ] && git stash pop >/dev/null 2>&1 || true
|
|
352
|
+
|
|
353
|
+
if [ "$MEM_OK" = "1" ]; then
|
|
354
|
+
echo "ok" > "$TMPD/memory"
|
|
355
|
+
else
|
|
356
|
+
echo "failed" > "$TMPD/memory"
|
|
357
|
+
fi
|
|
358
|
+
fi
|
|
359
|
+
) &
|
|
360
|
+
PID_MEMORY=$!
|
|
361
|
+
|
|
362
|
+
# --- Branch C: Publish artifact → Notify ---
|
|
363
|
+
#
|
|
364
|
+
# Strategy: handoffs publish ONCE, through the emissary write API, using
|
|
365
|
+
# `render_mode: "custom"` so the server serves OUR pre-rendered HTML
|
|
366
|
+
# verbatim (the same egregore-artifacts handoff template /view/ has
|
|
367
|
+
# always used). This gives us:
|
|
368
|
+
# - one artifact, one URL (no legacy + new double-publish)
|
|
369
|
+
# - permanent egregore.xyz/emissary/e/<id> URL with /raw endpoint
|
|
370
|
+
# - handoff design fully owned client-side (egregore-artifacts) — the
|
|
371
|
+
# emissary server template is never invoked for handoffs and remains
|
|
372
|
+
# completely untouched
|
|
373
|
+
#
|
|
374
|
+
# Fallback: if emissary publish fails (token missing, network, size cap,
|
|
375
|
+
# rendering failure), fall back to the legacy publish-artifact.sh path so
|
|
376
|
+
# a handoff still gets a Telegram-able URL.
|
|
377
|
+
(
|
|
378
|
+
V1URL=""
|
|
379
|
+
AURL=""
|
|
380
|
+
if [ "$NO_PUBLISH" = "0" ]; then
|
|
381
|
+
EMISSARY_CONFIG="$HOME/.egregore/emissary-config.json"
|
|
382
|
+
EMISSARY_TOKEN=""
|
|
383
|
+
if [ -f "$EMISSARY_CONFIG" ]; then
|
|
384
|
+
EMISSARY_TOKEN=$(jq -r '.auth_token // empty' "$EMISSARY_CONFIG" 2>/dev/null)
|
|
385
|
+
fi
|
|
386
|
+
EMISSARY_API_URL="${EMISSARY_API_URL:-https://egregore-production-55f2.up.railway.app/api/v1/emissary}"
|
|
387
|
+
|
|
388
|
+
# /handoff is an internal team primitive, not an implicit public-emissary
|
|
389
|
+
# composer. Use the emissary route only for an explicit recipient, and make
|
|
390
|
+
# that artifact directed-private. Recipient-less handoffs fall through to
|
|
391
|
+
# the org-scoped publisher (connected) or opt-in relay (local).
|
|
392
|
+
if [ -n "$EMISSARY_TOKEN" ] && [ -n "$RECIPIENT" ]; then
|
|
393
|
+
# Step 1: render the handoff to HTML locally using egregore-artifacts.
|
|
394
|
+
# Same renderer + template publish-artifact.sh uses today — handoffs
|
|
395
|
+
# keep their familiar look.
|
|
396
|
+
RENDERED_HTML="$TMPD/handoff.html"
|
|
397
|
+
RENDER_OK=0
|
|
398
|
+
LOCAL_CLI="$SCRIPT_DIR/packages/egregore-artifacts/bin/cli.js"
|
|
399
|
+
FIDELITY_ARGS=(--verify-fidelity)
|
|
400
|
+
if [ "$RENDER_TYPE" = "composed" ]; then
|
|
401
|
+
FIDELITY_ARGS+=(--source "$ABS_FILE")
|
|
402
|
+
fi
|
|
403
|
+
if [ "${EGREGORE_USE_PUBLISHED:-0}" != "1" ] && [ -f "$LOCAL_CLI" ] && [ -d "$SCRIPT_DIR/packages/egregore-artifacts/node_modules/react" ]; then
|
|
404
|
+
if node "$LOCAL_CLI" "$RENDER_TYPE" "${RENDER_SRC:-$ABS_FILE}" "${FIDELITY_ARGS[@]}" --output "$RENDERED_HTML" >/dev/null 2>"$TMPD/render-error"; then
|
|
405
|
+
RENDER_OK=1
|
|
406
|
+
fi
|
|
407
|
+
else
|
|
408
|
+
if npx -y egregore-artifacts@latest "$RENDER_TYPE" "${RENDER_SRC:-$ABS_FILE}" "${FIDELITY_ARGS[@]}" --output "$RENDERED_HTML" >/dev/null 2>"$TMPD/render-error"; then
|
|
409
|
+
RENDER_OK=1
|
|
410
|
+
fi
|
|
411
|
+
fi
|
|
412
|
+
|
|
413
|
+
# Step 2: build the emissary payload. If rendering succeeded AND the
|
|
414
|
+
# HTML fits the server's 100KB cap, send it with render_mode=custom.
|
|
415
|
+
# If not, fall through to the structured-only path (server-rendered
|
|
416
|
+
# via the emissary template — undesirable for handoffs but still
|
|
417
|
+
# better than no URL).
|
|
418
|
+
if [ "$RENDER_OK" = "1" ] && [ -f "$RENDERED_HTML" ]; then
|
|
419
|
+
RENDER_SIZE=$(wc -c < "$RENDERED_HTML" | tr -d ' ')
|
|
420
|
+
if [ "$RENDER_SIZE" -gt 0 ] && [ "$RENDER_SIZE" -lt 100000 ]; then
|
|
421
|
+
# Author display name
|
|
422
|
+
DISPLAY_NAME="$AUTHOR"
|
|
423
|
+
PERSON_FILE="$SCRIPT_DIR/memory/people/${AUTHOR}.md"
|
|
424
|
+
if [ -f "$PERSON_FILE" ]; then
|
|
425
|
+
DISPLAY_NAME=$(head -1 "$PERSON_FILE" | sed 's/^# //')
|
|
426
|
+
fi
|
|
427
|
+
|
|
428
|
+
RECIP_HANDLE=$(echo "$RECIPIENT" | tr '[:upper:]' '[:lower:]' | awk '{print $1}')
|
|
429
|
+
ADDRESSED_TO=$(jq -nc --arg h "$RECIP_HANDLE" --arg d "$RECIPIENT" '[{"handle":$h,"display":$d}]')
|
|
430
|
+
|
|
431
|
+
CLAIM=$(echo "$BRIEFING_LEAD" | cut -c1-200)
|
|
432
|
+
|
|
433
|
+
# body.prose is schema-required non-empty even when render_mode is
|
|
434
|
+
# "custom" (server validates structurally regardless of render
|
|
435
|
+
# mode). The visible page is the custom HTML; prose just needs to
|
|
436
|
+
# be non-empty plain text for /raw consumers and validation. Use
|
|
437
|
+
# the briefing lead (which the lead/claim is already derived from).
|
|
438
|
+
PROSE_TEXT="${BRIEFING_LEAD:-$TOPIC}"
|
|
439
|
+
|
|
440
|
+
# Use --rawfile so the rendered HTML is read directly from disk
|
|
441
|
+
# into a JSON-string. Faster than shell-var roundtrip and avoids
|
|
442
|
+
# any quoting headaches for HTML.
|
|
443
|
+
V1JSON=$(jq -nc \
|
|
444
|
+
--arg topic "$TOPIC" \
|
|
445
|
+
--arg claim "$CLAIM" \
|
|
446
|
+
--arg summary "$CLAIM" \
|
|
447
|
+
--arg prose "$PROSE_TEXT" \
|
|
448
|
+
--argjson addressed "$ADDRESSED_TO" \
|
|
449
|
+
--rawfile render_html "$RENDERED_HTML" \
|
|
450
|
+
'{
|
|
451
|
+
kind: "continuation",
|
|
452
|
+
topic: $topic,
|
|
453
|
+
claim: $claim,
|
|
454
|
+
summary: $summary,
|
|
455
|
+
body: { prose: $prose },
|
|
456
|
+
audience: {
|
|
457
|
+
addressed_to: $addressed,
|
|
458
|
+
visible_to: $addressed,
|
|
459
|
+
extendable_by: $addressed
|
|
460
|
+
},
|
|
461
|
+
parents: [],
|
|
462
|
+
render_mode: "custom",
|
|
463
|
+
render_html: $render_html
|
|
464
|
+
}')
|
|
465
|
+
|
|
466
|
+
V1RESULT=$(curl -sf -X POST "$EMISSARY_API_URL/emissaries" \
|
|
467
|
+
-H "Content-Type: application/json" \
|
|
468
|
+
-H "Authorization: Bearer $EMISSARY_TOKEN" \
|
|
469
|
+
-d "$V1JSON" 2>/dev/null || echo '{}')
|
|
470
|
+
|
|
471
|
+
V1URL=$(echo "$V1RESULT" | jq -r '.url // ""' 2>/dev/null || echo "")
|
|
472
|
+
fi
|
|
473
|
+
fi
|
|
474
|
+
fi
|
|
475
|
+
fi
|
|
476
|
+
|
|
477
|
+
# Fallback: if a directed emissary publish didn't produce a URL—or this is a
|
|
478
|
+
# recipient-less team handoff—use publish-artifact.sh. Connected mode is
|
|
479
|
+
# authenticated and org-scoped; local mode's public relay is opt-in.
|
|
480
|
+
PUBLISH_RC=0
|
|
481
|
+
if [ -z "$V1URL" ] && [ "$NO_PUBLISH" = "0" ] && [ -x "$SCRIPT_DIR/bin/publish-artifact.sh" ]; then
|
|
482
|
+
AURL=$(bash "$SCRIPT_DIR/bin/publish-artifact.sh" "$RENDER_TYPE" "${RENDER_SRC:-$ABS_FILE}" \
|
|
483
|
+
--verify-fidelity \
|
|
484
|
+
--source "$ABS_FILE" \
|
|
485
|
+
--title "$TOPIC" \
|
|
486
|
+
--author "$AUTHOR" \
|
|
487
|
+
--description "$BRIEFING_LEAD" 2>"$TMPD/publish-error") || PUBLISH_RC=$?
|
|
488
|
+
fi
|
|
489
|
+
|
|
490
|
+
PS="published"
|
|
491
|
+
if [ "$NO_PUBLISH" = "1" ]; then
|
|
492
|
+
PS="disabled"
|
|
493
|
+
elif [ -n "$V1URL" ] || [ -n "$AURL" ]; then
|
|
494
|
+
PS="published"
|
|
495
|
+
elif [ "$PUBLISH_RC" = "4" ]; then
|
|
496
|
+
PS="relay-off"
|
|
497
|
+
elif [ "$PUBLISH_RC" = "3" ]; then
|
|
498
|
+
PS="hosting-off"
|
|
499
|
+
elif grep -q 'Handoff fidelity check failed' "$TMPD/render-error" "$TMPD/publish-error" 2>/dev/null; then
|
|
500
|
+
PS="fidelity-failed"
|
|
501
|
+
else
|
|
502
|
+
PS="failed"
|
|
503
|
+
fi
|
|
504
|
+
echo "$PS" > "$TMPD/publish"
|
|
505
|
+
|
|
506
|
+
# Prefer v1 (emissary-rendered) URL when available; fall back to the
|
|
507
|
+
# legacy egregore-artifacts URL only if the v1 publish failed.
|
|
508
|
+
NOTIFY_URL=""
|
|
509
|
+
if [ -n "$V1URL" ]; then
|
|
510
|
+
echo "$V1URL" > "$TMPD/artifact"
|
|
511
|
+
NOTIFY_URL="$V1URL"
|
|
512
|
+
else
|
|
513
|
+
echo "$AURL" > "$TMPD/artifact"
|
|
514
|
+
NOTIFY_URL="$AURL"
|
|
515
|
+
fi
|
|
516
|
+
|
|
517
|
+
NS="skipped"
|
|
518
|
+
echo '{}' > "$TMPD/notify-plan"
|
|
519
|
+
if [ "$NO_NOTIFY" = "0" ]; then
|
|
520
|
+
if [ -n "$NOTIFY_URL" ]; then
|
|
521
|
+
MSG_NOTIFY="Handoff from ${AUTHOR}: ${TOPIC}
|
|
522
|
+
|
|
523
|
+
\"${BRIEFING_SHORT}\"
|
|
524
|
+
|
|
525
|
+
View: ${NOTIFY_URL}"
|
|
526
|
+
else
|
|
527
|
+
MSG_NOTIFY="Handoff from ${AUTHOR}: ${TOPIC}
|
|
528
|
+
|
|
529
|
+
\"${BRIEFING_SHORT}\""
|
|
530
|
+
fi
|
|
531
|
+
|
|
532
|
+
# Resolve the exact destination, but never approve or dispatch here. The
|
|
533
|
+
# interactive harness must show this proposal in a separate checkpoint.
|
|
534
|
+
if [ -n "$RECIPIENT" ]; then
|
|
535
|
+
NR=$(bash "$SCRIPT_DIR/bin/notify.sh" plan send "$RECIPIENT" "$MSG_NOTIFY" 2>/dev/null || echo '{}')
|
|
536
|
+
else
|
|
537
|
+
NR=$(bash "$SCRIPT_DIR/bin/notify.sh" plan group "$MSG_NOTIFY" 2>/dev/null || echo '{}')
|
|
538
|
+
fi
|
|
539
|
+
|
|
540
|
+
if printf '%s' "$NR" | jq -e '.status == "approval_required"' >/dev/null 2>&1; then
|
|
541
|
+
NS="approval_required"
|
|
542
|
+
printf '%s\n' "$NR" > "$TMPD/notify-plan"
|
|
543
|
+
else
|
|
544
|
+
NS="unavailable"
|
|
545
|
+
fi
|
|
546
|
+
fi
|
|
547
|
+
echo "$NS" > "$TMPD/notify"
|
|
548
|
+
) &
|
|
549
|
+
PID_PUBNOT=$!
|
|
550
|
+
|
|
551
|
+
# Join (never fail parent — each branch reports its own status).
|
|
552
|
+
# Branch D (PID_ARTIFACTS) is already awaited inside Branch B, but we wait
|
|
553
|
+
# again here to be safe if B skipped the wait (--no-push path).
|
|
554
|
+
wait "$PID_GRAPH" 2>/dev/null || true
|
|
555
|
+
wait "$PID_MEMORY" 2>/dev/null || true
|
|
556
|
+
wait "$PID_PUBNOT" 2>/dev/null || true
|
|
557
|
+
wait "$PID_ARTIFACTS" 2>/dev/null || true
|
|
558
|
+
|
|
559
|
+
# --- Collect results from tmpfiles --------------------------------------
|
|
560
|
+
SESSION_ID=""
|
|
561
|
+
RESOLVED=0
|
|
562
|
+
GRAPH_STATUS="skipped"
|
|
563
|
+
if [ -s "$TMPD/graph" ]; then
|
|
564
|
+
SESSION_ID=$(sed -n '1p' "$TMPD/graph")
|
|
565
|
+
RESOLVED=$(sed -n '2p' "$TMPD/graph")
|
|
566
|
+
GRAPH_STATUS=$(sed -n '3p' "$TMPD/graph")
|
|
567
|
+
fi
|
|
568
|
+
[ -z "$RESOLVED" ] && RESOLVED=0
|
|
569
|
+
# Sanitize: --argjson requires a number. If RESOLVED came back as a slug or
|
|
570
|
+
# anything non-numeric (the index-handoff.sh output format has drifted in
|
|
571
|
+
# the past — defensive guard), fall back to 0 so the result file still builds.
|
|
572
|
+
if ! echo "$RESOLVED" | grep -qE '^[0-9]+$'; then
|
|
573
|
+
RESOLVED=0
|
|
574
|
+
fi
|
|
575
|
+
|
|
576
|
+
SUBGRAPH_JSON=$(cat "$TMPD/subgraph" 2>/dev/null || echo "null")
|
|
577
|
+
[ -z "$SUBGRAPH_JSON" ] && SUBGRAPH_JSON="null"
|
|
578
|
+
# Sanitize: --argjson requires valid JSON. Multiple writes to the same
|
|
579
|
+
# tmpfile have produced concatenated values in the past (e.g. "null\n{...}"),
|
|
580
|
+
# which jq rejects on multi-document input. Validate; on failure, take the
|
|
581
|
+
# LAST non-empty line and re-validate; if still bad, fall back to null.
|
|
582
|
+
if ! printf '%s' "$SUBGRAPH_JSON" | jq -s 'length == 1' 2>/dev/null | grep -q '^true$'; then
|
|
583
|
+
LAST_LINE=$(printf '%s\n' "$SUBGRAPH_JSON" | awk 'NF{last=$0} END{print last}')
|
|
584
|
+
if printf '%s' "$LAST_LINE" | jq -e '.' >/dev/null 2>&1; then
|
|
585
|
+
SUBGRAPH_JSON="$LAST_LINE"
|
|
586
|
+
else
|
|
587
|
+
SUBGRAPH_JSON="null"
|
|
588
|
+
fi
|
|
589
|
+
fi
|
|
590
|
+
|
|
591
|
+
MEMORY_STATUS=$(cat "$TMPD/memory" 2>/dev/null || echo "skipped")
|
|
592
|
+
ARTIFACT_URL=$(cat "$TMPD/artifact" 2>/dev/null || echo "")
|
|
593
|
+
NOTIFY_STATUS=$(cat "$TMPD/notify" 2>/dev/null || echo "skipped")
|
|
594
|
+
NOTIFY_PLAN=$(cat "$TMPD/notify-plan" 2>/dev/null || echo "{}")
|
|
595
|
+
PUBLISH_STATUS=$(cat "$TMPD/publish" 2>/dev/null || echo "skipped")
|
|
596
|
+
[ -z "$PUBLISH_STATUS" ] && PUBLISH_STATUS="skipped"
|
|
597
|
+
ARTIFACTS_ARR=$(cat "$TMPD/artifacts" 2>/dev/null || echo "[]")
|
|
598
|
+
[ -z "$ARTIFACTS_ARR" ] && ARTIFACTS_ARR="[]"
|
|
599
|
+
|
|
600
|
+
# --- Detached PR-number backfill ----------------------------------------
|
|
601
|
+
# repo-state.sh was called with --no-pr for speed; now fire the backfill in
|
|
602
|
+
# the background so PR columns get populated without blocking this script.
|
|
603
|
+
# Reparented to init via `( cmd & ) >/dev/null 2>&1` — survives our exit.
|
|
604
|
+
if [ "$MEMORY_STATUS" = "ok" ] && grep -q '^## Repo State' "$ABS_FILE" 2>/dev/null; then
|
|
605
|
+
( bash "$SCRIPT_DIR/bin/handoff-pr-backfill.sh" "$ABS_FILE" "$TOPIC" >/dev/null 2>&1 & ) >/dev/null 2>&1
|
|
606
|
+
fi
|
|
607
|
+
|
|
608
|
+
# --- Emit minimal stdout, stash structured data in a tmpfile ------------
|
|
609
|
+
#
|
|
610
|
+
# The Bash tool block shows stdout verbatim and collapses after ~3 lines.
|
|
611
|
+
# We keep stdout to exactly one compact status line so the block is
|
|
612
|
+
# visually quiet. Full structured data goes to a well-known tmpfile that
|
|
613
|
+
# the caller (skill) reads to render the rich card.
|
|
614
|
+
STATUS_BITS=("saved")
|
|
615
|
+
[ "$GRAPH_STATUS" = "ok" ] && STATUS_BITS+=("graphed")
|
|
616
|
+
[ "$MEMORY_STATUS" = "ok" ] && STATUS_BITS+=("pushed")
|
|
617
|
+
[ "$NOTIFY_STATUS" = "approval_required" ] && STATUS_BITS+=("notify approval pending")
|
|
618
|
+
[ -n "$ARTIFACT_URL" ] && STATUS_BITS+=("published")
|
|
619
|
+
[ "$PUBLISH_STATUS" = "relay-off" ] && STATUS_BITS+=("not published")
|
|
620
|
+
[ "$PUBLISH_STATUS" = "fidelity-failed" ] && STATUS_BITS+=("artifact fidelity failed")
|
|
621
|
+
|
|
622
|
+
STATUS_LINE=""
|
|
623
|
+
for bit in "${STATUS_BITS[@]}"; do
|
|
624
|
+
if [ -z "$STATUS_LINE" ]; then
|
|
625
|
+
STATUS_LINE="$bit"
|
|
626
|
+
else
|
|
627
|
+
STATUS_LINE="${STATUS_LINE} · ${bit}"
|
|
628
|
+
fi
|
|
629
|
+
done
|
|
630
|
+
|
|
631
|
+
# Stash structured result for the caller
|
|
632
|
+
RESULT_FILE="${TMPDIR:-/tmp}/handoff-run-result.json"
|
|
633
|
+
jq -cn \
|
|
634
|
+
--arg mode "$MODE" \
|
|
635
|
+
--arg file "$REL_FILE" \
|
|
636
|
+
--arg absFile "$ABS_FILE" \
|
|
637
|
+
--arg sessionId "$SESSION_ID" \
|
|
638
|
+
--argjson resolved "${RESOLVED:-0}" \
|
|
639
|
+
--arg graphStatus "$GRAPH_STATUS" \
|
|
640
|
+
--arg memoryStatus "$MEMORY_STATUS" \
|
|
641
|
+
--arg notifyStatus "$NOTIFY_STATUS" \
|
|
642
|
+
--argjson notifyPlan "$NOTIFY_PLAN" \
|
|
643
|
+
--arg artifactUrl "$ARTIFACT_URL" \
|
|
644
|
+
--arg publishStatus "$PUBLISH_STATUS" \
|
|
645
|
+
--arg recipient "$RECIPIENT" \
|
|
646
|
+
--arg topic "$TOPIC" \
|
|
647
|
+
--arg author "$AUTHOR" \
|
|
648
|
+
--argjson subgraph "$SUBGRAPH_JSON" \
|
|
649
|
+
--argjson artifacts "$ARTIFACTS_ARR" \
|
|
650
|
+
'{
|
|
651
|
+
mode:$mode,
|
|
652
|
+
file:$file,
|
|
653
|
+
absFile:$absFile,
|
|
654
|
+
sessionId:$sessionId,
|
|
655
|
+
resolved:$resolved,
|
|
656
|
+
graphStatus:$graphStatus,
|
|
657
|
+
memoryStatus:$memoryStatus,
|
|
658
|
+
notifyStatus:$notifyStatus,
|
|
659
|
+
notifyPlan:$notifyPlan,
|
|
660
|
+
artifactUrl:$artifactUrl,
|
|
661
|
+
publishStatus:$publishStatus,
|
|
662
|
+
recipient:$recipient,
|
|
663
|
+
topic:$topic,
|
|
664
|
+
author:$author,
|
|
665
|
+
subgraph:$subgraph,
|
|
666
|
+
artifacts:$artifacts
|
|
667
|
+
}' > "$RESULT_FILE"
|
|
668
|
+
|
|
669
|
+
# One clean line for the Bash tool block.
|
|
670
|
+
# The caller reads structured data from $TMPDIR/handoff-run-result.json.
|
|
671
|
+
echo "⇌ ${STATUS_LINE}"
|