create-egregore 0.19.8 → 0.19.11
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/lib/local.js +11 -1
- package/lib/setup.js +33 -0
- package/package.json +1 -1
- package/runtime/codex/.codex/hooks/branch-guard.js +242 -34
- package/runtime/codex/.codex/skills/activity/SKILL.md +18 -6
- package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +90 -55
- package/runtime/codex/.codex/skills/deploy-bridge/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/handoff/SKILL.md +6 -2
- package/runtime/codex/.codex/skills/save/SKILL.md +21 -3
- package/runtime/codex/.codex/skills/scroll/SKILL.md +54 -3
- package/runtime/codex/.codex/skills/task/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/tui-design/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/wrap/SKILL.md +4 -1
- package/runtime/codex/.codex/spec-manifest.json +15 -3
- package/runtime/codex/AGENTS.md +45 -9
- package/runtime/codex/bin/activity-data.sh +1 -1
- package/runtime/codex/bin/agent.sh +66 -23
- package/runtime/codex/bin/attendant.sh +525 -0
- package/runtime/codex/bin/capture-reconcile.sh +67 -0
- package/runtime/codex/bin/capture-run.sh +341 -0
- package/runtime/codex/bin/codex-render-spec.mjs +15 -4
- package/runtime/codex/bin/codex-session-start.sh +45 -3
- package/runtime/codex/bin/codex-skill-render.mjs +23 -4
- package/runtime/codex/bin/graph-op.sh +719 -49
- package/runtime/codex/bin/graph-wal.sh +3 -2
- package/runtime/codex/bin/handoff-run.sh +72 -0
- package/runtime/codex/bin/handoff-save-egregore.sh +51 -11
- package/runtime/codex/bin/index-handoff.sh +26 -17
- package/runtime/codex/bin/ingest-graph.sh +80 -0
- package/runtime/codex/bin/ingest-surface.html +429 -0
- package/runtime/codex/bin/ingest.py +614 -0
- package/runtime/codex/bin/ingest.sh +231 -0
- package/runtime/codex/bin/ingest_graph.py +776 -0
- package/runtime/codex/bin/ingest_surface.py +464 -0
- package/runtime/codex/bin/lib/git-sync.sh +2 -6
- package/runtime/codex/bin/lib/greeting.sh +157 -22
- package/runtime/codex/bin/lib/identity.sh +50 -29
- package/runtime/codex/bin/lib/noncode.sh +31 -0
- package/runtime/codex/bin/person.py +610 -0
- package/runtime/codex/bin/person.sh +448 -0
- package/runtime/codex/bin/session-autosave.sh +184 -0
- package/runtime/codex/bin/session-log.sh +199 -0
- package/runtime/codex/bin/settings.sh +1 -1
- package/runtime/codex/manifest.json +19 -1
- package/runtime/pi/.codex/hooks/branch-guard.js +242 -34
- package/runtime/pi/.codex/skills/activity/SKILL.md +18 -6
- package/runtime/pi/.codex/skills/deep-reflect/SKILL.md +90 -55
- package/runtime/pi/.codex/skills/deploy-bridge/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/handoff/SKILL.md +6 -2
- package/runtime/pi/.codex/skills/save/SKILL.md +21 -3
- package/runtime/pi/.codex/skills/scroll/SKILL.md +54 -3
- package/runtime/pi/.codex/skills/task/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/tui-design/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/wrap/SKILL.md +4 -1
- package/runtime/pi/.codex/spec-manifest.json +15 -3
- package/runtime/pi/.pi/APPEND_SYSTEM.md +41 -5
- package/runtime/pi/.pi/extensions/egregore.ts +98 -8
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +45 -9
- package/runtime/pi/bin/activity-data.sh +1 -1
- package/runtime/pi/bin/agent.sh +66 -23
- package/runtime/pi/bin/attendant.sh +525 -0
- package/runtime/pi/bin/capture-reconcile.sh +67 -0
- package/runtime/pi/bin/capture-run.sh +341 -0
- package/runtime/pi/bin/codex-render-spec.mjs +15 -4
- package/runtime/pi/bin/codex-session-start.sh +45 -3
- package/runtime/pi/bin/codex-skill-render.mjs +23 -4
- package/runtime/pi/bin/graph-op.sh +719 -49
- package/runtime/pi/bin/graph-wal.sh +3 -2
- package/runtime/pi/bin/handoff-run.sh +72 -0
- package/runtime/pi/bin/handoff-save-egregore.sh +51 -11
- package/runtime/pi/bin/index-handoff.sh +26 -17
- package/runtime/pi/bin/ingest-graph.sh +80 -0
- package/runtime/pi/bin/ingest-surface.html +429 -0
- package/runtime/pi/bin/ingest.py +614 -0
- package/runtime/pi/bin/ingest.sh +231 -0
- package/runtime/pi/bin/ingest_graph.py +776 -0
- package/runtime/pi/bin/ingest_surface.py +464 -0
- package/runtime/pi/bin/lib/git-sync.sh +2 -6
- package/runtime/pi/bin/lib/greeting.sh +157 -22
- package/runtime/pi/bin/lib/identity.sh +50 -29
- package/runtime/pi/bin/lib/noncode.sh +31 -0
- package/runtime/pi/bin/person.py +610 -0
- package/runtime/pi/bin/person.sh +448 -0
- package/runtime/pi/bin/pi-branch-policy.mjs +164 -4
- package/runtime/pi/bin/pi-product-workflows.mjs +31 -2
- package/runtime/pi/bin/publish-artifact.sh +14 -0
- package/runtime/pi/bin/session-autosave.sh +184 -0
- package/runtime/pi/bin/session-log.sh +199 -0
- package/runtime/pi/bin/settings.sh +274 -0
- package/runtime/pi/manifest.json +20 -1
|
@@ -32,13 +32,14 @@ WAL_FILE="$WAL_DIR/graph-wal-${PROJ_HASH}.jsonl"
|
|
|
32
32
|
MAX_BUFFER_BYTES=2097152 # 2MB
|
|
33
33
|
MAX_ENTRIES_AFTER_TRUNCATE=200
|
|
34
34
|
LOCK_DIR="$WAL_DIR/graph-wal-${PROJ_HASH}.lock"
|
|
35
|
+
LOCK_ATTEMPTS="${GRAPH_WAL_LOCK_ATTEMPTS:-50}"
|
|
35
36
|
|
|
36
37
|
# --- Cross-platform file locking via mkdir (atomic on all POSIX systems) ---
|
|
37
38
|
_lock() {
|
|
38
39
|
local attempts=0
|
|
39
40
|
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
|
|
40
41
|
attempts=$((attempts + 1))
|
|
41
|
-
if [ "$attempts" -gt
|
|
42
|
+
if [ "$attempts" -gt "$LOCK_ATTEMPTS" ]; then
|
|
42
43
|
# Stale lock — check if holder PID is alive
|
|
43
44
|
local holder_pid
|
|
44
45
|
holder_pid=$(cat "$LOCK_DIR/pid" 2>/dev/null || echo "")
|
|
@@ -46,7 +47,7 @@ _lock() {
|
|
|
46
47
|
rm -rf "$LOCK_DIR" 2>/dev/null
|
|
47
48
|
continue
|
|
48
49
|
fi
|
|
49
|
-
echo "graph-wal: lock timeout after
|
|
50
|
+
echo "graph-wal: lock timeout after ${LOCK_ATTEMPTS} attempt(s)" >&2
|
|
50
51
|
return 1
|
|
51
52
|
fi
|
|
52
53
|
sleep 0.1
|
|
@@ -11,6 +11,7 @@ set -euo pipefail
|
|
|
11
11
|
# --topic "<short topic>" \
|
|
12
12
|
# [--recipient <name>] \
|
|
13
13
|
# [--project <name>] \
|
|
14
|
+
# [--intent <action|feedback|fyi>] \
|
|
14
15
|
# <<'HANDOFFEOF'
|
|
15
16
|
# ...full markdown body of the handoff...
|
|
16
17
|
# HANDOFFEOF
|
|
@@ -45,6 +46,7 @@ AUTHOR=""
|
|
|
45
46
|
TOPIC=""
|
|
46
47
|
RECIPIENT=""
|
|
47
48
|
PROJECT=""
|
|
49
|
+
INTENT="action"
|
|
48
50
|
NO_PUSH=0
|
|
49
51
|
NO_NOTIFY=0
|
|
50
52
|
NO_PUBLISH=0
|
|
@@ -56,6 +58,7 @@ while [ $# -gt 0 ]; do
|
|
|
56
58
|
--topic) TOPIC="${2:?}"; shift 2 ;;
|
|
57
59
|
--recipient) RECIPIENT="${2:-}"; shift 2 ;;
|
|
58
60
|
--project) PROJECT="${2:-}"; shift 2 ;;
|
|
61
|
+
--intent) INTENT="${2:-}"; shift 2 ;;
|
|
59
62
|
--composed) COMPOSED="${2:-}"; shift 2 ;;
|
|
60
63
|
--no-push) NO_PUSH=1; shift ;;
|
|
61
64
|
--no-notify) NO_NOTIFY=1; shift ;;
|
|
@@ -76,6 +79,10 @@ fi
|
|
|
76
79
|
|
|
77
80
|
[ -z "$AUTHOR" ] && { echo "Missing --author" >&2; exit 1; }
|
|
78
81
|
[ -z "$TOPIC" ] && { echo "Missing --topic" >&2; exit 1; }
|
|
82
|
+
case "$INTENT" in
|
|
83
|
+
action|feedback|fyi) ;;
|
|
84
|
+
*) echo "Invalid --intent: $INTENT (action|feedback|fyi)" >&2; exit 1 ;;
|
|
85
|
+
esac
|
|
79
86
|
|
|
80
87
|
MODE=$(jq -r '.mode // "connected"' "$CONFIG" 2>/dev/null || echo "connected")
|
|
81
88
|
TODAY=$(date +%Y-%m-%d)
|
|
@@ -115,6 +122,48 @@ if [ ! -s "$ABS_FILE" ]; then
|
|
|
115
122
|
exit 1
|
|
116
123
|
fi
|
|
117
124
|
|
|
125
|
+
# --- Canonical capture contract -----------------------------------------
|
|
126
|
+
# /handoff is the addressed door into the shared capture engine. Persist the
|
|
127
|
+
# same schema/mode fields used by /wrap and SessionEnd so filesystem and graph
|
|
128
|
+
# retrieval can distinguish captures without inferring from directory names.
|
|
129
|
+
if ! grep -qiE '^(capture_schema:|\*\*[Cc]apture [Ss]chema\*\*:)' "$ABS_FILE"; then
|
|
130
|
+
CAPTURE_KIND=""
|
|
131
|
+
if ! grep -qiE '^(kind:|\*\*[Kk]ind\*\*:)' "$ABS_FILE"; then
|
|
132
|
+
CAPTURE_KIND="addressed"
|
|
133
|
+
fi
|
|
134
|
+
if [ "$(head -1 "$ABS_FILE")" = "---" ]; then
|
|
135
|
+
awk -v kind="$CAPTURE_KIND" '
|
|
136
|
+
NR == 1 {
|
|
137
|
+
print
|
|
138
|
+
print "capture_schema: egregore-capture/v1"
|
|
139
|
+
print "capture_mode: addressed"
|
|
140
|
+
if (kind != "") print "kind: " kind
|
|
141
|
+
next
|
|
142
|
+
}
|
|
143
|
+
{ print }
|
|
144
|
+
' "$ABS_FILE" > "$ABS_FILE.new" && mv "$ABS_FILE.new" "$ABS_FILE"
|
|
145
|
+
else
|
|
146
|
+
awk -v kind="$CAPTURE_KIND" '
|
|
147
|
+
!injected && /^\*\*[Aa]uthor\*\*:/ {
|
|
148
|
+
print
|
|
149
|
+
print "**Capture Schema**: egregore-capture/v1"
|
|
150
|
+
print "**Capture Mode**: addressed"
|
|
151
|
+
if (kind != "") print "**Kind**: " kind
|
|
152
|
+
injected=1
|
|
153
|
+
next
|
|
154
|
+
}
|
|
155
|
+
{ print }
|
|
156
|
+
END {
|
|
157
|
+
if (!injected) {
|
|
158
|
+
print "**Capture Schema**: egregore-capture/v1"
|
|
159
|
+
print "**Capture Mode**: addressed"
|
|
160
|
+
if (kind != "") print "**Kind**: " kind
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
' "$ABS_FILE" > "$ABS_FILE.new" && mv "$ABS_FILE.new" "$ABS_FILE"
|
|
164
|
+
fi
|
|
165
|
+
fi
|
|
166
|
+
|
|
118
167
|
# --- Inject **To**: line if --recipient given and body omits it -----
|
|
119
168
|
# index-handoff.sh parses recipients from the file body. Skills
|
|
120
169
|
# normally write the To line themselves, but callers that invoke
|
|
@@ -132,6 +181,29 @@ if [ -n "$RECIPIENT" ] && ! grep -qi "^\*\*[Tt]o\*\*:" "$ABS_FILE"; then
|
|
|
132
181
|
' "$ABS_FILE" > "$ABS_FILE.new" && mv "$ABS_FILE.new" "$ABS_FILE"
|
|
133
182
|
fi
|
|
134
183
|
|
|
184
|
+
# --- Persist lifecycle intent in the canonical handoff file -------------
|
|
185
|
+
if ! grep -qiE '^(intent:|\*\*[Ii]ntent\*\*:)' "$ABS_FILE"; then
|
|
186
|
+
if [ "$(head -1 "$ABS_FILE")" = "---" ]; then
|
|
187
|
+
awk -v intent="$INTENT" '
|
|
188
|
+
NR == 1 { print; print "intent: " intent; next }
|
|
189
|
+
{ print }
|
|
190
|
+
' "$ABS_FILE" > "$ABS_FILE.new" && mv "$ABS_FILE.new" "$ABS_FILE"
|
|
191
|
+
else
|
|
192
|
+
awk -v intent="$INTENT" '
|
|
193
|
+
!injected && /^\*\*[Aa]uthor\*\*:/ {
|
|
194
|
+
print
|
|
195
|
+
print "**Intent**: " intent
|
|
196
|
+
injected=1
|
|
197
|
+
next
|
|
198
|
+
}
|
|
199
|
+
{ print }
|
|
200
|
+
END {
|
|
201
|
+
if (!injected) print "**Intent**: " intent
|
|
202
|
+
}
|
|
203
|
+
' "$ABS_FILE" > "$ABS_FILE.new" && mv "$ABS_FILE.new" "$ABS_FILE"
|
|
204
|
+
fi
|
|
205
|
+
fi
|
|
206
|
+
|
|
135
207
|
# --- Append repo state ---------------------------------------------------
|
|
136
208
|
# --no-pr skips `gh pr list` (~400–600ms per repo). A detached PR backfill
|
|
137
209
|
# after the parallel phase rewrites `—` → `#N` and re-pushes memory.
|
|
@@ -31,13 +31,22 @@ TOPIC="${2:-session work}"
|
|
|
31
31
|
shift 2 2>/dev/null || true
|
|
32
32
|
|
|
33
33
|
REPO_DIR=""
|
|
34
|
+
KIND="handoff"
|
|
35
|
+
BRANCH_SUFFIX=""
|
|
34
36
|
while [ $# -gt 0 ]; do
|
|
35
37
|
case "$1" in
|
|
36
38
|
--repo-dir) REPO_DIR="${2:-}"; shift 2 ;;
|
|
39
|
+
--kind) KIND="${2:-handoff}"; shift 2 ;;
|
|
40
|
+
--branch-suffix) BRANCH_SUFFIX="${2:-}"; shift 2 ;;
|
|
37
41
|
*) shift ;;
|
|
38
42
|
esac
|
|
39
43
|
done
|
|
40
44
|
|
|
45
|
+
case "$KIND" in
|
|
46
|
+
autosave) MSG_PREFIX="Auto-save" ; PR_BODY="Auto-saved session content (non-coding). No explicit /save needed." ;;
|
|
47
|
+
*) MSG_PREFIX="Handoff" ; PR_BODY="Auto-created by /handoff. Session work for:" ;;
|
|
48
|
+
esac
|
|
49
|
+
|
|
41
50
|
[ -n "$AUTHOR" ] || { echo "handoff-save-egregore: missing author" >&2; exit 1; }
|
|
42
51
|
|
|
43
52
|
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
@@ -50,6 +59,13 @@ cd "$REPO_DIR" || exit 1
|
|
|
50
59
|
. "$SCRIPT_DIR/bin/lib/git-safe.sh" 2>/dev/null || true
|
|
51
60
|
type git_add_guarded >/dev/null 2>&1 || git_add_guarded() { git add -A 2>/dev/null || true; }
|
|
52
61
|
|
|
62
|
+
# Shared non-coding classifier (auto-merge policy lives in one place).
|
|
63
|
+
# shellcheck source=bin/lib/noncode.sh
|
|
64
|
+
. "$SCRIPT_DIR/bin/lib/noncode.sh" 2>/dev/null || true
|
|
65
|
+
type noncode_blockers >/dev/null 2>&1 || noncode_blockers() {
|
|
66
|
+
git -C "${2:-.}" diff "${1:-origin/develop}...HEAD" --name-only 2>/dev/null | grep -v '\.md$' | head -1 || true
|
|
67
|
+
}
|
|
68
|
+
|
|
53
69
|
# --- Early exit if nothing to do -----------------------------------------
|
|
54
70
|
DIRTY=0
|
|
55
71
|
[ -n "$(git status --porcelain 2>/dev/null)" ] && DIRTY=1
|
|
@@ -70,7 +86,9 @@ fi
|
|
|
70
86
|
case "$CURRENT_BRANCH" in
|
|
71
87
|
develop|main|master|"")
|
|
72
88
|
SLUG_DATE=$(date +%Y-%m-%d)
|
|
73
|
-
|
|
89
|
+
# --branch-suffix keeps concurrent rescues distinct (e.g. per-checkout
|
|
90
|
+
# hash from the autosave sweep) — same-name pushes wedge on non-FF.
|
|
91
|
+
NEW_BRANCH="dev/${AUTHOR}/${KIND}-${SLUG_DATE}${BRANCH_SUFFIX:+-$BRANCH_SUFFIX}"
|
|
74
92
|
git fetch origin develop --quiet 2>/dev/null || true
|
|
75
93
|
git checkout -b "$NEW_BRANCH" origin/develop --quiet 2>/dev/null || \
|
|
76
94
|
git checkout "$NEW_BRANCH" --quiet 2>/dev/null || exit 0
|
|
@@ -81,7 +99,7 @@ esac
|
|
|
81
99
|
# --- Commit whatever is uncommitted --------------------------------------
|
|
82
100
|
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
|
|
83
101
|
git_add_guarded
|
|
84
|
-
git commit -m "
|
|
102
|
+
git commit -m "${MSG_PREFIX}: ${TOPIC}" --quiet 2>/dev/null || true
|
|
85
103
|
fi
|
|
86
104
|
|
|
87
105
|
# --- Rebase onto develop, fall back to merge if rebase conflicts ---------
|
|
@@ -102,8 +120,21 @@ if ! git push -u origin "$CURRENT_BRANCH" --quiet 2>/dev/null; then
|
|
|
102
120
|
exit 0
|
|
103
121
|
fi
|
|
104
122
|
|
|
123
|
+
# --- Ledger: the user-visible trail of background saves ------------------
|
|
124
|
+
# Read by session-autosave.sh (immediate Stop-hook notice) and the greeting
|
|
125
|
+
# (next-launch summary). Machine-scoped so worktree saves surface everywhere.
|
|
126
|
+
LEDGER="${EGREGORE_AUTOSAVE_LOG:-$HOME/.egregore/autosave.log}"
|
|
127
|
+
ledger_write() { # pr outcome
|
|
128
|
+
local n_files
|
|
129
|
+
n_files=$(git diff origin/develop...HEAD --name-only 2>/dev/null | grep -c . || echo 0)
|
|
130
|
+
mkdir -p "$(dirname "$LEDGER")" 2>/dev/null || true
|
|
131
|
+
printf '%s|%s|%s|%s|%s|%s|%s\n' \
|
|
132
|
+
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$KIND" "$REPO_DIR" "$CURRENT_BRANCH" \
|
|
133
|
+
"$1" "$2" "$n_files" >> "$LEDGER" 2>/dev/null || true
|
|
134
|
+
}
|
|
135
|
+
|
|
105
136
|
# --- PR: reuse open PR on this branch, or create one --------------------
|
|
106
|
-
command -v gh >/dev/null 2>&1 || exit 0
|
|
137
|
+
command -v gh >/dev/null 2>&1 || { ledger_write "-" "pushed"; exit 0; }
|
|
107
138
|
|
|
108
139
|
PR_NUMBER=""
|
|
109
140
|
EXISTING=$(gh pr list --head "$CURRENT_BRANCH" --state open --json number,baseRefName --jq '.[0]' 2>/dev/null)
|
|
@@ -119,18 +150,27 @@ else
|
|
|
119
150
|
PR_URL=$(gh pr create \
|
|
120
151
|
--base develop \
|
|
121
152
|
--head "$CURRENT_BRANCH" \
|
|
122
|
-
--title "
|
|
123
|
-
--body "
|
|
153
|
+
--title "${MSG_PREFIX}: ${TOPIC}" \
|
|
154
|
+
--body "${PR_BODY} ${TOPIC}" \
|
|
124
155
|
2>/dev/null || echo "")
|
|
125
156
|
PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$' || echo "")
|
|
126
157
|
fi
|
|
127
158
|
|
|
128
|
-
[ -n "$PR_NUMBER" ] || exit 0
|
|
129
|
-
|
|
130
|
-
# --- Auto-merge iff
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
159
|
+
[ -n "$PR_NUMBER" ] || { ledger_write "-" "pushed"; exit 0; }
|
|
160
|
+
|
|
161
|
+
# --- Auto-merge iff non-coding -------------------------------------------
|
|
162
|
+
# `--auto` only works when the repo allows auto-merge AND required checks are
|
|
163
|
+
# pending; on a repo with no branch protection it always errors. Fall back to
|
|
164
|
+
# an immediate merge so non-coding PRs never sit open.
|
|
165
|
+
OUTCOME="open"
|
|
166
|
+
NON_CODE_BLOCKER=$(noncode_blockers origin/develop .)
|
|
167
|
+
if [ -z "$NON_CODE_BLOCKER" ]; then
|
|
168
|
+
if gh pr merge "$PR_NUMBER" --auto --merge >/dev/null 2>&1 \
|
|
169
|
+
|| gh pr merge "$PR_NUMBER" --merge >/dev/null 2>&1; then
|
|
170
|
+
OUTCOME="merged"
|
|
171
|
+
fi
|
|
134
172
|
fi
|
|
135
173
|
|
|
174
|
+
ledger_write "$PR_NUMBER" "$OUTCOME"
|
|
175
|
+
|
|
136
176
|
exit 0
|
|
@@ -3,7 +3,9 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
# Index a single handoff file into the Neo4j graph.
|
|
5
5
|
# Usage: bash bin/index-handoff.sh <file-path>
|
|
6
|
-
# Returns: {"sessionId":"...","resolved":
|
|
6
|
+
# Returns: {"sessionId":"...","resolved":0} or {"error":"..."}.
|
|
7
|
+
# `resolved` remains for compatibility; lifecycle reconciliation is queued by
|
|
8
|
+
# capture-run.sh after indexing.
|
|
7
9
|
#
|
|
8
10
|
# The file-path should be relative to memory/ (e.g. handoffs/2026-02/14-oz-topic.md)
|
|
9
11
|
# or an absolute/relative path that contains "handoffs/" somewhere in it.
|
|
@@ -16,7 +18,7 @@ if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
|
|
16
18
|
echo "Index a single handoff file into the Neo4j graph."
|
|
17
19
|
echo "Parses metadata (author, date, topic, recipients) from"
|
|
18
20
|
echo "markdown headers or YAML front matter, creates Session"
|
|
19
|
-
echo "and relationship nodes
|
|
21
|
+
echo "and relationship nodes. Lifecycle completion is queued separately."
|
|
20
22
|
echo ""
|
|
21
23
|
echo "Returns: {\"sessionId\":\"...\",\"resolved\":N}"
|
|
22
24
|
exit 0
|
|
@@ -135,6 +137,15 @@ PROJECT="$(md_field 'Project')"
|
|
|
135
137
|
KIND="$(md_field 'Kind')"
|
|
136
138
|
[ -z "$KIND" ] && KIND="$(yaml_extract 'kind')"
|
|
137
139
|
|
|
140
|
+
# Lifecycle intent determines which automatic transitions are safe.
|
|
141
|
+
INTENT="$(md_field 'Intent')"
|
|
142
|
+
[ -z "$INTENT" ] && INTENT="$(yaml_extract 'intent')"
|
|
143
|
+
INTENT="$(printf '%s' "${INTENT:-unclassified}" | tr '[:upper:]' '[:lower:]')"
|
|
144
|
+
case "$INTENT" in
|
|
145
|
+
action|feedback|fyi|unclassified) ;;
|
|
146
|
+
*) INTENT="unclassified" ;;
|
|
147
|
+
esac
|
|
148
|
+
|
|
138
149
|
# Summary: 5-level precedence chain
|
|
139
150
|
# 1. Frontmatter summary field
|
|
140
151
|
SUMMARY="$(yaml_extract 'summary')"
|
|
@@ -180,7 +191,7 @@ if [ -n "$RECIPIENTS_RAW" ]; then
|
|
|
180
191
|
[ -z "$R" ] && continue
|
|
181
192
|
HANDED_TO_CYPHER="${HANDED_TO_CYPHER}
|
|
182
193
|
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}
|
|
194
|
+
OPTIONAL MATCH (r${i}:Person) WHERE toLower(r${i}.name) = \$recipient${i} OR toLower(r${i}.github) = \$recipient${i} OR toLower(r${i}.fullName) = \$recipient${i} OR \$recipient${i} IN [x IN coalesce(r${i}.previousNames, []) | toLower(x)] OR \$recipient${i} IN [x IN coalesce(r${i}.githubAliases, []) | toLower(x)] OR \$recipient${i} IN [x IN coalesce(r${i}.emails, []) | toLower(x)]
|
|
184
195
|
FOREACH (_ IN CASE WHEN r${i} IS NOT NULL THEN [1] ELSE [] END |
|
|
185
196
|
MERGE (s)-[:HANDED_TO]->(r${i})
|
|
186
197
|
)"
|
|
@@ -200,10 +211,10 @@ FOREACH (_ IN CASE WHEN proj IS NOT NULL THEN [1] ELSE [] END |
|
|
|
200
211
|
_PROJECT_ARGS=("--arg" "project" "$PROJECT")
|
|
201
212
|
fi
|
|
202
213
|
|
|
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)]
|
|
214
|
+
Q1_CYPHER="MATCH (p:Person) WHERE toLower(p.name) = \$author OR toLower(p.github) = \$author OR toLower(p.fullName) = \$author OR \$author IN [x IN coalesce(p.previousNames, []) | toLower(x)] OR \$author IN [x IN coalesce(p.githubAliases, []) | toLower(x)] OR \$author IN [x IN coalesce(p.emails, []) | toLower(x)]
|
|
204
215
|
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
|
|
216
|
+
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.handoffIntent = \$intent, s.handoffLifecycleVersion = 1, s.repoState = \$repoState, s.startedAt = datetime(\$date + 'T00:00:00Z')
|
|
217
|
+
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.handoffIntent = CASE WHEN \$intent = 'unclassified' THEN coalesce(s.handoffIntent, 'unclassified') ELSE \$intent END, s.handoffLifecycleVersion = 1, s.repoState = \$repoState
|
|
207
218
|
WITH s, p
|
|
208
219
|
MERGE (s)-[:BY]->(p)${PROJECT_CYPHER}${HANDED_TO_CYPHER}
|
|
209
220
|
RETURN s.id AS sessionId"
|
|
@@ -216,22 +227,20 @@ Q1_PARAMS=$(jq -n \
|
|
|
216
227
|
--arg summary "$SUMMARY" \
|
|
217
228
|
--arg filePath "$REL_PATH" \
|
|
218
229
|
--arg kind "$KIND" \
|
|
230
|
+
--arg intent "$INTENT" \
|
|
219
231
|
--arg repoState "$REPO_STATE_JSON" \
|
|
220
232
|
"${_PROJECT_ARGS[@]:+${_PROJECT_ARGS[@]}}" \
|
|
221
233
|
"${_RECIP_ARGS[@]:+${_RECIP_ARGS[@]}}" \
|
|
222
234
|
'$ARGS.named')
|
|
223
235
|
|
|
224
|
-
# Q2:
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
Q2_PARAMS=$(jq -n --arg author "$AUTHOR_HANDLE" --arg sessionId "$SESSION_ID" \
|
|
234
|
-
'{author: $author, sessionId: $sessionId}')
|
|
236
|
+
# Q2: Preserve the stable three-result batch shape without mutating lifecycle.
|
|
237
|
+
# Completion used to run inside this foreground indexing request. It now enters
|
|
238
|
+
# through capture-run.sh's local WAL queue and is reconciled off the caller's
|
|
239
|
+
# critical path.
|
|
240
|
+
Q2_CYPHER="MATCH (s:Session {id: \$sessionId})
|
|
241
|
+
RETURN 0 AS resolved"
|
|
242
|
+
|
|
243
|
+
Q2_PARAMS=$(jq -n --arg sessionId "$SESSION_ID" '{sessionId: $sessionId}')
|
|
235
244
|
|
|
236
245
|
# Q3: Capture the handoff's knowledge-graph neighborhood for artifact rendering.
|
|
237
246
|
# Only the "graph-only" relations — things not already visible in the markdown
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Validate, plan, or apply a durable ingestion manifest to the organization graph.
|
|
5
|
+
# The planner owns the source-to-graph contract; this wrapper owns mode detection,
|
|
6
|
+
# batching, and truthful delivery receipts.
|
|
7
|
+
|
|
8
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
9
|
+
SCRIPT_DIR="$ROOT"
|
|
10
|
+
CONFIG="${CONFIG:-$ROOT/egregore.json}"
|
|
11
|
+
PLANNER="${EGREGORE_INGEST_GRAPH_PLANNER:-$ROOT/bin/ingest_graph.py}"
|
|
12
|
+
GRAPH_BATCH="${EGREGORE_INGEST_GRAPH_BATCH:-$ROOT/bin/graph-batch.sh}"
|
|
13
|
+
|
|
14
|
+
# shellcheck source=bin/lib/config.sh
|
|
15
|
+
source "$ROOT/bin/lib/config.sh"
|
|
16
|
+
|
|
17
|
+
usage() {
|
|
18
|
+
echo "usage: bash bin/ingest-graph.sh {validate|plan|apply} MANIFEST" >&2
|
|
19
|
+
exit 2
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
mode="${1:-}"
|
|
23
|
+
manifest="${2:-}"
|
|
24
|
+
[ -n "$mode" ] && [ -n "$manifest" ] || usage
|
|
25
|
+
|
|
26
|
+
case "$mode" in
|
|
27
|
+
validate|plan)
|
|
28
|
+
exec python3 "$PLANNER" "$mode" "$manifest"
|
|
29
|
+
;;
|
|
30
|
+
apply) ;;
|
|
31
|
+
*) usage ;;
|
|
32
|
+
esac
|
|
33
|
+
|
|
34
|
+
plan="$(python3 "$PLANNER" plan "$manifest")"
|
|
35
|
+
manifest_id="$(printf '%s' "$plan" | jq -r '.manifest_id')"
|
|
36
|
+
query_count="$(printf '%s' "$plan" | jq -r '.query_count')"
|
|
37
|
+
|
|
38
|
+
if [ "$(_detect_mode)" = "local" ]; then
|
|
39
|
+
jq -cn \
|
|
40
|
+
--arg manifest_id "$manifest_id" \
|
|
41
|
+
--argjson queries "$query_count" \
|
|
42
|
+
'{status:"stored",graph:"unavailable-in-local-mode",manifest_id:$manifest_id,queries:$queries,batches:0,failed:0,replayable:true}'
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
total=0
|
|
47
|
+
succeeded=0
|
|
48
|
+
failed=0
|
|
49
|
+
while IFS= read -r batch; do
|
|
50
|
+
[ -n "$batch" ] || continue
|
|
51
|
+
total=$((total + 1))
|
|
52
|
+
expected="$(printf '%s' "$batch" | jq 'length')"
|
|
53
|
+
if response="$(bash "$GRAPH_BATCH" "$batch" 2>/dev/null)" \
|
|
54
|
+
&& actual="$(printf '%s' "$response" | jq -er '.results | length' 2>/dev/null)" \
|
|
55
|
+
&& [ "$actual" -eq "$expected" ]; then
|
|
56
|
+
succeeded=$((succeeded + 1))
|
|
57
|
+
else
|
|
58
|
+
failed=$((failed + 1))
|
|
59
|
+
fi
|
|
60
|
+
done < <(
|
|
61
|
+
printf '%s' "$plan" |
|
|
62
|
+
jq -c '.queries | map({statement, parameters}) as $all | range(0; length; 20) as $i | $all[$i:$i+20]'
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if [ "$failed" -gt 0 ]; then
|
|
66
|
+
jq -cn \
|
|
67
|
+
--arg manifest_id "$manifest_id" \
|
|
68
|
+
--argjson queries "$query_count" \
|
|
69
|
+
--argjson batches "$total" \
|
|
70
|
+
--argjson succeeded "$succeeded" \
|
|
71
|
+
--argjson failed "$failed" \
|
|
72
|
+
'{status:"partial",manifest_id:$manifest_id,queries:$queries,batches:$batches,succeeded:$succeeded,failed:$failed,replayable:true}'
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
jq -cn \
|
|
77
|
+
--arg manifest_id "$manifest_id" \
|
|
78
|
+
--argjson queries "$query_count" \
|
|
79
|
+
--argjson batches "$total" \
|
|
80
|
+
'{status:"projected",manifest_id:$manifest_id,queries:$queries,batches:$batches,failed:0,replayable:true}'
|