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
|
@@ -6,17 +6,19 @@ set -euo pipefail
|
|
|
6
6
|
# Usage: bash bin/graph-op.sh <operation> [args...]
|
|
7
7
|
#
|
|
8
8
|
# Operations:
|
|
9
|
-
# mark-read <session-id>
|
|
10
|
-
# mark-done <session-id>
|
|
9
|
+
# mark-read <session-id> [user] Mark a handoff as read
|
|
10
|
+
# mark-done <session-id> [user] Mark a handoff as done/resolved
|
|
11
|
+
# mark-expired <session-id> [user] Explicitly expire an open handoff
|
|
12
|
+
# reopen-handoff <session-id> [user] Reopen a terminal handoff
|
|
11
13
|
# answer-question <set-id> Mark a question set as answered
|
|
12
|
-
# resolve-handoffs <user>
|
|
14
|
+
# resolve-handoffs <user> Resolve completed explicit implementation lineage
|
|
13
15
|
# set-topic <session-id> <topic> [branch]
|
|
14
16
|
# Set topic (and optionally branch) on a Session node
|
|
15
17
|
# record-focus <session-id> <shown-json> <selected> [dismissed-json]
|
|
16
18
|
# Track Focus option selection for adaptive options
|
|
17
|
-
# merge-person <keep-name> <
|
|
18
|
-
#
|
|
19
|
-
#
|
|
19
|
+
# merge-person <keep-name> <alias-name>
|
|
20
|
+
# Reconcile two Person nodes — projects relationships
|
|
21
|
+
# to keep-name and retains alias-name append-only
|
|
20
22
|
# create-harvest <id> <topic> <intent> <initiator>
|
|
21
23
|
# Create a Harvest node and link to initiator (WAL-backed)
|
|
22
24
|
# create-harvest-session <harvest-id> <session-id> <person-name> [status]
|
|
@@ -28,6 +30,14 @@ set -euo pipefail
|
|
|
28
30
|
# Record a question-answer turn in a HarvestSession
|
|
29
31
|
# complete-harvest <harvest-id> <artifact-path>
|
|
30
32
|
# Mark harvest complete and link synthesis artifact
|
|
33
|
+
# catalog List bounded read operations and their contracts
|
|
34
|
+
# open-handoffs <user> [limit] Read non-terminal handoffs addressed to a person
|
|
35
|
+
# pending-questions <user> [limit] Read active question sets awaiting a person
|
|
36
|
+
# lineage <topic> [limit] Trace matching sessions, handoffs, implementations,
|
|
37
|
+
# PRs, and canonical evidence files
|
|
38
|
+
# meeting-history <query> [limit]
|
|
39
|
+
# Find a meeting sequence, participants, derived
|
|
40
|
+
# knowledge, and unprojected canonical meeting files
|
|
31
41
|
|
|
32
42
|
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
33
43
|
|
|
@@ -37,50 +47,287 @@ if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
|
|
37
47
|
echo "Named graph operations — clean interface over raw Cypher."
|
|
38
48
|
echo ""
|
|
39
49
|
echo "Operations:"
|
|
40
|
-
echo " mark-read <sid>
|
|
41
|
-
echo " mark-done <sid>
|
|
50
|
+
echo " mark-read <sid> [user] Mark handoff as read"
|
|
51
|
+
echo " mark-done <sid> [user] Mark handoff as done"
|
|
52
|
+
echo " mark-expired <sid> [user] Explicitly expire an open handoff"
|
|
53
|
+
echo " reopen-handoff <sid> [user] Reopen a done/expired handoff"
|
|
42
54
|
echo " answer-question <qid> Mark question set as answered"
|
|
43
|
-
echo " resolve-handoffs <user>
|
|
55
|
+
echo " resolve-handoffs <user> Resolve completed implementation lineage"
|
|
44
56
|
echo " set-topic <sid> <topic> Set topic on a Session node"
|
|
45
|
-
echo " merge-person <keep> <
|
|
57
|
+
echo " merge-person <keep> <alias> Reconcile a Person as a canonical alias"
|
|
46
58
|
echo " claim-handoff <sid> <ho> Link implementing session to handoff"
|
|
47
59
|
echo " create-pr <sid> <num> <repo> <author> [title]"
|
|
48
60
|
echo " register-artifact <id> <url> <title> <type> <author> <topics> <excerpt>"
|
|
49
61
|
echo " create-harvest / complete-harvest / record-harvest-turn"
|
|
62
|
+
echo " catalog List bounded read operation contracts"
|
|
63
|
+
echo " open-handoffs <user> [n] Read recent non-terminal handoffs"
|
|
64
|
+
echo " pending-questions <user> [n] Read recent active question sets"
|
|
65
|
+
echo " lineage <topic> [n] Trace handoff/implementation/PR evidence"
|
|
66
|
+
echo " meeting-history <query> [n] Find meeting continuity and coverage gaps"
|
|
50
67
|
echo " wal-status Show WAL pending count"
|
|
51
68
|
exit 0
|
|
52
69
|
fi
|
|
53
70
|
|
|
71
|
+
GS="$SCRIPT_DIR/bin/graph.sh"
|
|
72
|
+
|
|
73
|
+
OP="${1:-}"
|
|
74
|
+
shift || true
|
|
75
|
+
|
|
54
76
|
# --- Local mode gate: bail immediately ---
|
|
55
77
|
_MODE=$(jq -r '.mode // "connected"' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
|
|
78
|
+
|
|
79
|
+
# The catalog is static runtime metadata, not a graph read. Keep it available
|
|
80
|
+
# in local mode so every harness can route correctly without opening this file
|
|
81
|
+
# or guessing whether an operation exists.
|
|
82
|
+
if [ "$OP" = "catalog" ]; then
|
|
83
|
+
jq -n --arg mode "$_MODE" '{
|
|
84
|
+
schema: "egregore.graph-operation-catalog/v1",
|
|
85
|
+
definitionVersion: 1,
|
|
86
|
+
mode: $mode,
|
|
87
|
+
operations: [
|
|
88
|
+
{
|
|
89
|
+
name: "open-handoffs",
|
|
90
|
+
intent: "current non-terminal handoffs addressed to a person",
|
|
91
|
+
usage: "bash bin/graph-op.sh open-handoffs <user> [limit]",
|
|
92
|
+
availability: "connected",
|
|
93
|
+
maxResults: 25,
|
|
94
|
+
evidence: "live graph state"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "pending-questions",
|
|
98
|
+
intent: "current active question sets awaiting a person",
|
|
99
|
+
usage: "bash bin/graph-op.sh pending-questions <user> [limit]",
|
|
100
|
+
availability: "connected",
|
|
101
|
+
maxResults: 25,
|
|
102
|
+
evidence: "live graph state"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "lineage",
|
|
106
|
+
intent: "handoff to implementation, branch, PR, and superseding direction",
|
|
107
|
+
usage: "bash bin/graph-op.sh lineage <topic> [limit]",
|
|
108
|
+
availability: "connected",
|
|
109
|
+
maxResults: 15,
|
|
110
|
+
evidence: "graph relationships plus canonical file pointers"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "meeting-history",
|
|
114
|
+
intent: "cross-meeting continuity for a company, person, or topic",
|
|
115
|
+
usage: "bash bin/graph-op.sh meeting-history <query> [limit]",
|
|
116
|
+
availability: "connected",
|
|
117
|
+
maxResults: 15,
|
|
118
|
+
evidence: "projected meeting relationships plus canonical file coverage"
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}'
|
|
122
|
+
exit 0
|
|
123
|
+
fi
|
|
124
|
+
|
|
56
125
|
if [ "$_MODE" = "local" ]; then
|
|
57
126
|
echo '{"results":[]}'
|
|
58
127
|
exit 0
|
|
59
128
|
fi
|
|
60
129
|
|
|
61
|
-
|
|
130
|
+
# Return a bounded set of exact canonical paths containing a literal query.
|
|
131
|
+
# This is intentionally a coverage check around graph results, not a second
|
|
132
|
+
# full-text answer corpus: agents still open only the relevant canonical files.
|
|
133
|
+
memory_file_candidates() {
|
|
134
|
+
local query="$1"
|
|
135
|
+
local limit="$2"
|
|
136
|
+
shift 2
|
|
137
|
+
local filename_hits=""
|
|
138
|
+
local content_hits=""
|
|
139
|
+
local hits=""
|
|
62
140
|
|
|
63
|
-
|
|
64
|
-
|
|
141
|
+
if command -v rg >/dev/null 2>&1; then
|
|
142
|
+
filename_hits="$(
|
|
143
|
+
rg --files --glob '*.md' --glob '!index.md' "$@" 2>/dev/null \
|
|
144
|
+
| sort \
|
|
145
|
+
| while IFS= read -r path; do
|
|
146
|
+
printf '%s' "$path" | grep -Fqi -- "$query" \
|
|
147
|
+
&& printf 'filename\t%s\n' "$path"
|
|
148
|
+
done || true
|
|
149
|
+
)"
|
|
150
|
+
content_hits="$(
|
|
151
|
+
rg -i -l -F --glob '*.md' --glob '!index.md' -- "$query" "$@" 2>/dev/null \
|
|
152
|
+
| sort \
|
|
153
|
+
| while IFS= read -r path; do printf 'content\t%s\n' "$path"; done || true
|
|
154
|
+
)"
|
|
155
|
+
else
|
|
156
|
+
content_hits="$(
|
|
157
|
+
find -L "$@" -type f -name '*.md' -print 2>/dev/null \
|
|
158
|
+
| while IFS= read -r path; do
|
|
159
|
+
[ "$(basename "$path")" = "index.md" ] && continue
|
|
160
|
+
printf '%s' "$path" | grep -Fqi -- "$query" \
|
|
161
|
+
&& printf 'filename\t%s\n' "$path"
|
|
162
|
+
grep -Fqi -- "$query" "$path" 2>/dev/null \
|
|
163
|
+
&& printf 'content\t%s\n' "$path"
|
|
164
|
+
done || true
|
|
165
|
+
)"
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
hits="$(
|
|
169
|
+
printf '%s\n%s\n' "$filename_hits" "$content_hits" \
|
|
170
|
+
| awk -F ' ' 'NF >= 2 && !seen[$2]++' \
|
|
171
|
+
| head -n "$limit"
|
|
172
|
+
)"
|
|
173
|
+
|
|
174
|
+
while IFS=$'\t' read -r match_kind path; do
|
|
175
|
+
[ -n "$path" ] || continue
|
|
176
|
+
case "$path" in
|
|
177
|
+
"$SCRIPT_DIR"/memory/*)
|
|
178
|
+
printf 'memory/%s\t%s\n' "${path#"$SCRIPT_DIR"/memory/}" "$match_kind"
|
|
179
|
+
;;
|
|
180
|
+
esac
|
|
181
|
+
done <<< "$hits" | jq -Rsc '
|
|
182
|
+
split("\n")
|
|
183
|
+
| map(select(length > 0))
|
|
184
|
+
| map(split("\t") | {evidencePath: .[0], matchKind: .[1]})
|
|
185
|
+
'
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# Reduce natural-language read queries to a small set of discriminative terms.
|
|
189
|
+
# Named reads should tolerate "42CAP Moritz partner call" without requiring that
|
|
190
|
+
# exact phrase to exist in a graph property or canonical Markdown file.
|
|
191
|
+
meaningful_query_terms() {
|
|
192
|
+
jq -Rn --arg query "$1" '
|
|
193
|
+
[
|
|
194
|
+
$query
|
|
195
|
+
| ascii_downcase
|
|
196
|
+
| scan("[a-z0-9][a-z0-9._+-]*")
|
|
197
|
+
| select(length >= 3)
|
|
198
|
+
| select(. as $term |
|
|
199
|
+
[
|
|
200
|
+
"about","across","after","before","between","call","calls",
|
|
201
|
+
"company","conversation","evolved","first","from","history",
|
|
202
|
+
"later","meeting","meetings","month","partner","partners",
|
|
203
|
+
"person","the","topic","transcript","transcripts","what","with"
|
|
204
|
+
]
|
|
205
|
+
| index($term) == null
|
|
206
|
+
)
|
|
207
|
+
]
|
|
208
|
+
| unique
|
|
209
|
+
| if length == 0 then [$query | ascii_downcase] else .[0:8] end
|
|
210
|
+
'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# Build a bounded union of literal coverage checks for each meaningful term.
|
|
214
|
+
# The result remains a list of exact canonical paths, not a second answer corpus.
|
|
215
|
+
memory_file_candidates_for_terms() {
|
|
216
|
+
local terms_json="$1"
|
|
217
|
+
local limit="$2"
|
|
218
|
+
shift 2
|
|
219
|
+
local combined='[]'
|
|
220
|
+
local term=""
|
|
221
|
+
local term_hits=""
|
|
222
|
+
|
|
223
|
+
while IFS= read -r term; do
|
|
224
|
+
[ -n "$term" ] || continue
|
|
225
|
+
term_hits="$(memory_file_candidates "$term" "$limit" "$@")"
|
|
226
|
+
combined="$(
|
|
227
|
+
jq -n \
|
|
228
|
+
--argjson prior "$combined" \
|
|
229
|
+
--argjson next "$term_hits" \
|
|
230
|
+
--argjson limit "$limit" '
|
|
231
|
+
($prior + $next)
|
|
232
|
+
| unique_by(.evidencePath)
|
|
233
|
+
| .[0:$limit]
|
|
234
|
+
'
|
|
235
|
+
)"
|
|
236
|
+
done < <(printf '%s' "$terms_json" | jq -r '.[]')
|
|
237
|
+
|
|
238
|
+
printf '%s\n' "$combined"
|
|
239
|
+
}
|
|
65
240
|
|
|
66
241
|
case "$OP" in
|
|
67
242
|
|
|
68
243
|
mark-read)
|
|
69
244
|
SID="${1:?missing session-id}"
|
|
245
|
+
USER="${2:-}"
|
|
70
246
|
bash "$GS" query "
|
|
71
247
|
MATCH (s:Session {id: \$sid})
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
248
|
+
OPTIONAL MATCH (s)-[:HANDED_TO]->(recipient:Person)
|
|
249
|
+
WITH s, collect(DISTINCT recipient) AS recipients
|
|
250
|
+
WHERE (\$user = '' OR any(p IN recipients WHERE
|
|
251
|
+
toLower(p.name) = toLower(\$user)
|
|
252
|
+
OR toLower(coalesce(p.github, '')) = toLower(\$user)))
|
|
253
|
+
AND coalesce(s.handoffStatus, 'pending') IN ['pending','read']
|
|
254
|
+
SET s.handoffReadDate = coalesce(s.handoffReadDate, date()),
|
|
255
|
+
s.handoffUpdatedAt = datetime(),
|
|
256
|
+
s.handoffLifecycleVersion = 1
|
|
257
|
+
FOREACH (_ IN CASE WHEN s.handoffIntent = 'fyi' THEN [1] ELSE [] END |
|
|
258
|
+
SET s.handoffStatus = 'done',
|
|
259
|
+
s.handoffDoneAt = coalesce(s.handoffDoneAt, datetime()),
|
|
260
|
+
s.handoffLifecycleReason = 'fyi_read')
|
|
261
|
+
FOREACH (_ IN CASE WHEN coalesce(s.handoffIntent, 'unclassified') <> 'fyi' THEN [1] ELSE [] END |
|
|
262
|
+
SET s.handoffStatus = 'read',
|
|
263
|
+
s.handoffLifecycleReason = 'recipient_read')
|
|
264
|
+
RETURN s.id AS id, s.topic AS topic, s.handoffStatus AS status,
|
|
265
|
+
coalesce(s.handoffIntent, 'unclassified') AS intent
|
|
266
|
+
" "$(jq -n --arg sid "$SID" --arg user "$USER" '{sid: $sid,user: $user}')"
|
|
75
267
|
;;
|
|
76
268
|
|
|
77
269
|
mark-done)
|
|
78
270
|
SID="${1:?missing session-id}"
|
|
271
|
+
USER="${2:-}"
|
|
79
272
|
bash "$GS" query "
|
|
80
273
|
MATCH (s:Session {id: \$sid})
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
274
|
+
OPTIONAL MATCH (s)-[:HANDED_TO]->(recipient:Person)
|
|
275
|
+
WITH s, collect(DISTINCT recipient) AS recipients
|
|
276
|
+
WHERE (\$user = '' OR any(p IN recipients WHERE
|
|
277
|
+
toLower(p.name) = toLower(\$user)
|
|
278
|
+
OR toLower(coalesce(p.github, '')) = toLower(\$user)))
|
|
279
|
+
AND coalesce(s.handoffStatus, 'pending') IN ['pending','read','claimed']
|
|
280
|
+
SET s.handoffStatus = 'done',
|
|
281
|
+
s.handoffDoneAt = datetime(),
|
|
282
|
+
s.handoffUpdatedAt = datetime(),
|
|
283
|
+
s.handoffLifecycleReason = 'explicit_done',
|
|
284
|
+
s.handoffLifecycleVersion = 1
|
|
285
|
+
RETURN s.id AS id, s.topic AS topic, s.handoffStatus AS status
|
|
286
|
+
" "$(jq -n --arg sid "$SID" --arg user "$USER" '{sid: $sid,user: $user}')"
|
|
287
|
+
;;
|
|
288
|
+
|
|
289
|
+
mark-expired)
|
|
290
|
+
SID="${1:?missing session-id}"
|
|
291
|
+
USER="${2:-}"
|
|
292
|
+
bash "$GS" query "
|
|
293
|
+
MATCH (s:Session {id: \$sid})
|
|
294
|
+
OPTIONAL MATCH (s)-[:HANDED_TO]->(recipient:Person)
|
|
295
|
+
WITH s, collect(DISTINCT recipient) AS recipients
|
|
296
|
+
WHERE (\$user = '' OR any(p IN recipients WHERE
|
|
297
|
+
toLower(p.name) = toLower(\$user)
|
|
298
|
+
OR toLower(coalesce(p.github, '')) = toLower(\$user)))
|
|
299
|
+
AND coalesce(s.handoffStatus, 'pending') IN ['pending','read','claimed']
|
|
300
|
+
SET s.handoffStatus = 'expired',
|
|
301
|
+
s.handoffExpiredAt = datetime(),
|
|
302
|
+
s.handoffUpdatedAt = datetime(),
|
|
303
|
+
s.handoffLifecycleReason = 'explicit_expired',
|
|
304
|
+
s.handoffLifecycleVersion = 1
|
|
305
|
+
RETURN s.id AS id, s.topic AS topic, s.handoffStatus AS status
|
|
306
|
+
" "$(jq -n --arg sid "$SID" --arg user "$USER" '{sid: $sid,user: $user}')"
|
|
307
|
+
;;
|
|
308
|
+
|
|
309
|
+
reopen-handoff)
|
|
310
|
+
SID="${1:?missing session-id}"
|
|
311
|
+
USER="${2:-}"
|
|
312
|
+
bash "$GS" query "
|
|
313
|
+
MATCH (s:Session {id: \$sid})
|
|
314
|
+
OPTIONAL MATCH (s)-[:HANDED_TO]->(recipient:Person)
|
|
315
|
+
WITH s, collect(DISTINCT recipient) AS recipients
|
|
316
|
+
WHERE (\$user = '' OR any(p IN recipients WHERE
|
|
317
|
+
toLower(p.name) = toLower(\$user)
|
|
318
|
+
OR toLower(coalesce(p.github, '')) = toLower(\$user)))
|
|
319
|
+
AND s.handoffStatus IN ['done','expired','completed']
|
|
320
|
+
SET s.handoffLastDoneAt = s.handoffDoneAt,
|
|
321
|
+
s.handoffLastExpiredAt = s.handoffExpiredAt,
|
|
322
|
+
s.handoffDoneAt = null,
|
|
323
|
+
s.handoffExpiredAt = null,
|
|
324
|
+
s.handoffStatus = 'pending',
|
|
325
|
+
s.handoffReopenedAt = datetime(),
|
|
326
|
+
s.handoffUpdatedAt = datetime(),
|
|
327
|
+
s.handoffLifecycleReason = 'explicit_reopened',
|
|
328
|
+
s.handoffLifecycleVersion = 1
|
|
329
|
+
RETURN s.id AS id, s.topic AS topic, s.handoffStatus AS status
|
|
330
|
+
" "$(jq -n --arg sid "$SID" --arg user "$USER" '{sid: $sid,user: $user}')"
|
|
84
331
|
;;
|
|
85
332
|
|
|
86
333
|
answer-question)
|
|
@@ -96,13 +343,20 @@ case "$OP" in
|
|
|
96
343
|
USER="${1:?missing username}"
|
|
97
344
|
bash "$GS" query "
|
|
98
345
|
MATCH (s:Session)-[:HANDED_TO]->(p:Person {name: \$user})
|
|
99
|
-
WHERE s.handoffStatus
|
|
100
|
-
WITH s, p,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
346
|
+
WHERE s.handoffStatus IN ['read','claimed']
|
|
347
|
+
WITH DISTINCT s, p, size([(s)-[:HANDED_TO]->(:Person) | 1]) AS recipientCount
|
|
348
|
+
WHERE recipientCount = 1
|
|
349
|
+
MATCH (implementation:Session)-[:IMPLEMENTS]->(s)
|
|
350
|
+
MATCH (implementation)-[:BY]->(p)
|
|
351
|
+
WHERE implementation.wrappedAt IS NOT NULL
|
|
352
|
+
OR implementation.status IN ['completed','handed_off','wrapped']
|
|
353
|
+
WITH DISTINCT s, implementation
|
|
354
|
+
SET s.handoffStatus = 'done',
|
|
355
|
+
s.handoffDoneAt = coalesce(s.handoffDoneAt, datetime()),
|
|
356
|
+
s.handoffUpdatedAt = datetime(),
|
|
357
|
+
s.handoffLifecycleReason = 'single_recipient_implemented',
|
|
358
|
+
s.handoffLifecycleVersion = 1
|
|
359
|
+
RETURN s.id AS resolved, implementation.id AS implementation
|
|
106
360
|
" "$(jq -n --arg user "$USER" '{user: $user}')"
|
|
107
361
|
;;
|
|
108
362
|
|
|
@@ -141,34 +395,110 @@ case "$OP" in
|
|
|
141
395
|
|
|
142
396
|
merge-person)
|
|
143
397
|
KEEP="${1:?missing keep-name (the Person to keep)}"
|
|
144
|
-
|
|
398
|
+
ALIAS="${2:?missing alias-name (the Person to reconcile)}"
|
|
145
399
|
bash "$GS" query "
|
|
146
|
-
MATCH (keep:Person) WHERE toLower(keep.name) = toLower(\$keep) OR keep.github = \$keep
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
400
|
+
MATCH (keep:Person) WHERE (toLower(keep.name) = toLower(\$keep) OR toLower(keep.github) = toLower(\$keep) OR keep.personId = \$keep OR toLower(\$keep) IN [x IN coalesce(keep.githubAliases, []) | toLower(x)] OR toLower(\$keep) IN [x IN coalesce(keep.emails, []) | toLower(x)])
|
|
401
|
+
AND NOT coalesce(keep.kind, '') IN ['external', 'identity_alias']
|
|
402
|
+
AND coalesce(keep.status, 'active') = 'active' AND keep.ingestRef IS NULL
|
|
403
|
+
MATCH (alias:Person) WHERE (toLower(alias.name) = toLower(\$alias) OR toLower(alias.github) = toLower(\$alias) OR alias.personId = \$alias OR toLower(\$alias) IN [x IN coalesce(alias.githubAliases, []) | toLower(x)] OR toLower(\$alias) IN [x IN coalesce(alias.emails, []) | toLower(x)])
|
|
404
|
+
AND NOT coalesce(alias.kind, '') IN ['external', 'identity_alias']
|
|
405
|
+
AND coalesce(alias.status, 'active') = 'active' AND alias.ingestRef IS NULL
|
|
406
|
+
WITH keep, alias WHERE keep <> alias
|
|
407
|
+
AND (
|
|
408
|
+
keep.githubId IS NULL OR alias.githubId IS NULL
|
|
409
|
+
OR toString(keep.githubId) = toString(alias.githubId)
|
|
410
|
+
)
|
|
411
|
+
AND NOT (
|
|
412
|
+
coalesce(keep.personId, '') STARTS WITH 'github:'
|
|
413
|
+
AND coalesce(alias.personId, '') STARTS WITH 'github:'
|
|
414
|
+
AND keep.personId <> alias.personId
|
|
415
|
+
)
|
|
416
|
+
SET keep.previousNames = reduce(
|
|
417
|
+
acc = coalesce(keep.previousNames, []),
|
|
418
|
+
value IN coalesce(alias.previousNames, []) + [alias.name, alias.displayName] |
|
|
419
|
+
CASE WHEN value IS NULL OR toLower(value) = toLower(keep.name) OR value IN acc
|
|
420
|
+
THEN acc ELSE acc + value END
|
|
421
|
+
),
|
|
422
|
+
keep.githubAliases = reduce(
|
|
423
|
+
acc = coalesce(keep.githubAliases, []),
|
|
424
|
+
value IN coalesce(alias.githubAliases, []) + [alias.github] |
|
|
425
|
+
CASE WHEN value IS NULL OR toLower(value) = toLower(keep.github) OR value IN acc
|
|
426
|
+
THEN acc ELSE acc + value END
|
|
427
|
+
),
|
|
428
|
+
keep.emails = reduce(
|
|
429
|
+
acc = coalesce(keep.emails, []),
|
|
430
|
+
value IN coalesce(alias.emails, []) + [alias.email] |
|
|
431
|
+
CASE WHEN value IS NULL OR value IN acc THEN acc ELSE acc + value END
|
|
432
|
+
),
|
|
433
|
+
keep.joined = coalesce(alias.joined, keep.joined),
|
|
434
|
+
keep.invited = coalesce(keep.invited, alias.invited),
|
|
435
|
+
keep.invitedBy = coalesce(keep.invitedBy, alias.invitedBy),
|
|
436
|
+
keep.role = coalesce(keep.role, alias.role),
|
|
437
|
+
keep.domain = coalesce(keep.domain, alias.domain),
|
|
438
|
+
keep.focus = coalesce(keep.focus, alias.focus),
|
|
439
|
+
keep.workStyle = coalesce(keep.workStyle, alias.workStyle),
|
|
440
|
+
keep.telegramId = coalesce(keep.telegramId, alias.telegramId),
|
|
441
|
+
keep.lastBrief = coalesce(keep.lastBrief, alias.lastBrief),
|
|
442
|
+
keep.lastBriefDate = coalesce(keep.lastBriefDate, alias.lastBriefDate),
|
|
443
|
+
keep.lastRecommendations = coalesce(keep.lastRecommendations, alias.lastRecommendations)
|
|
444
|
+
WITH keep, alias
|
|
445
|
+
OPTIONAL MATCH (s1:Session)-[:BY]->(alias)
|
|
152
446
|
FOREACH (_ IN CASE WHEN s1 IS NOT NULL THEN [1] ELSE [] END | MERGE (s1)-[:BY]->(keep))
|
|
153
|
-
WITH keep,
|
|
154
|
-
OPTIONAL MATCH (s2)-[:HANDED_TO]->(
|
|
447
|
+
WITH keep, alias
|
|
448
|
+
OPTIONAL MATCH (s2:Session)-[:HANDED_TO]->(alias)
|
|
155
449
|
FOREACH (_ IN CASE WHEN s2 IS NOT NULL THEN [1] ELSE [] END | MERGE (s2)-[:HANDED_TO]->(keep))
|
|
156
|
-
WITH keep,
|
|
157
|
-
OPTIONAL MATCH (m)-[:INVOLVES]->(
|
|
450
|
+
WITH keep, alias
|
|
451
|
+
OPTIONAL MATCH (m:Meeting)-[:INVOLVES]->(alias)
|
|
158
452
|
FOREACH (_ IN CASE WHEN m IS NOT NULL THEN [1] ELSE [] END | MERGE (m)-[:INVOLVES]->(keep))
|
|
159
|
-
WITH keep,
|
|
160
|
-
OPTIONAL MATCH (a)-[:CONTRIBUTED_BY]->(
|
|
453
|
+
WITH keep, alias
|
|
454
|
+
OPTIONAL MATCH (a:Artifact)-[:CONTRIBUTED_BY]->(alias)
|
|
161
455
|
FOREACH (_ IN CASE WHEN a IS NOT NULL THEN [1] ELSE [] END | MERGE (a)-[:CONTRIBUTED_BY]->(keep))
|
|
162
|
-
WITH keep,
|
|
163
|
-
OPTIONAL MATCH (i)-[:CONDUCTED_BY]->(
|
|
456
|
+
WITH keep, alias
|
|
457
|
+
OPTIONAL MATCH (i:Interview)-[:CONDUCTED_BY]->(alias)
|
|
164
458
|
FOREACH (_ IN CASE WHEN i IS NOT NULL THEN [1] ELSE [] END | MERGE (i)-[:CONDUCTED_BY]->(keep))
|
|
165
|
-
WITH keep,
|
|
166
|
-
OPTIONAL MATCH (
|
|
459
|
+
WITH keep, alias
|
|
460
|
+
OPTIONAL MATCH (t:Todo)-[:BY]->(alias)
|
|
461
|
+
FOREACH (_ IN CASE WHEN t IS NOT NULL THEN [1] ELSE [] END | MERGE (t)-[:BY]->(keep))
|
|
462
|
+
WITH keep, alias
|
|
463
|
+
OPTIONAL MATCH (mentionedArtifact:Artifact)-[:MENTIONS]->(alias)
|
|
464
|
+
FOREACH (_ IN CASE WHEN mentionedArtifact IS NOT NULL THEN [1] ELSE [] END | MERGE (mentionedArtifact)-[:MENTIONS]->(keep))
|
|
465
|
+
WITH keep, alias
|
|
466
|
+
OPTIONAL MATCH (mentionedTodo:Todo)-[:MENTIONS]->(alias)
|
|
467
|
+
FOREACH (_ IN CASE WHEN mentionedTodo IS NOT NULL THEN [1] ELSE [] END | MERGE (mentionedTodo)-[:MENTIONS]->(keep))
|
|
468
|
+
WITH keep, alias
|
|
469
|
+
OPTIONAL MATCH (q:Quest)-[:STARTED_BY]->(alias)
|
|
470
|
+
FOREACH (_ IN CASE WHEN q IS NOT NULL THEN [1] ELSE [] END | MERGE (q)-[:STARTED_BY]->(keep))
|
|
471
|
+
WITH keep, alias
|
|
472
|
+
OPTIONAL MATCH (askedBy:QuestionSet)-[:ASKED_BY]->(alias)
|
|
473
|
+
FOREACH (_ IN CASE WHEN askedBy IS NOT NULL THEN [1] ELSE [] END | MERGE (askedBy)-[:ASKED_BY]->(keep))
|
|
474
|
+
WITH keep, alias
|
|
475
|
+
OPTIONAL MATCH (askedTo:QuestionSet)-[:ASKED_TO]->(alias)
|
|
476
|
+
FOREACH (_ IN CASE WHEN askedTo IS NOT NULL THEN [1] ELSE [] END | MERGE (askedTo)-[:ASKED_TO]->(keep))
|
|
477
|
+
WITH keep, alias
|
|
478
|
+
OPTIONAL MATCH (tu:TelegramUser)-[:IDENTIFIES]->(alias)
|
|
479
|
+
FOREACH (_ IN CASE WHEN tu IS NOT NULL THEN [1] ELSE [] END | MERGE (tu)-[:IDENTIFIES]->(keep))
|
|
480
|
+
WITH keep, alias
|
|
481
|
+
OPTIONAL MATCH (reported:Issue)-[:REPORTED_BY]->(alias)
|
|
482
|
+
FOREACH (_ IN CASE WHEN reported IS NOT NULL THEN [1] ELSE [] END | MERGE (reported)-[:REPORTED_BY]->(keep))
|
|
483
|
+
WITH keep, alias
|
|
484
|
+
OPTIONAL MATCH (initiated:Harvest)-[:INITIATED_BY]->(alias)
|
|
485
|
+
FOREACH (_ IN CASE WHEN initiated IS NOT NULL THEN [1] ELSE [] END | MERGE (initiated)-[:INITIATED_BY]->(keep))
|
|
486
|
+
WITH keep, alias
|
|
487
|
+
OPTIONAL MATCH (withPerson:HarvestSession)-[:WITH]->(alias)
|
|
488
|
+
FOREACH (_ IN CASE WHEN withPerson IS NOT NULL THEN [1] ELSE [] END | MERGE (withPerson)-[:WITH]->(keep))
|
|
489
|
+
WITH keep, alias
|
|
490
|
+
OPTIONAL MATCH (alias)-[:SENT_EMISSARY]->(emissary:Emissary)
|
|
491
|
+
FOREACH (_ IN CASE WHEN emissary IS NOT NULL THEN [1] ELSE [] END | MERGE (keep)-[:SENT_EMISSARY]->(emissary))
|
|
492
|
+
WITH keep, alias
|
|
493
|
+
OPTIONAL MATCH (alias)-[:MEMBER_OF]->(o:Org)
|
|
167
494
|
FOREACH (_ IN CASE WHEN o IS NOT NULL THEN [1] ELSE [] END | MERGE (keep)-[:MEMBER_OF]->(o))
|
|
168
|
-
WITH keep,
|
|
169
|
-
|
|
495
|
+
WITH keep, alias
|
|
496
|
+
MERGE (alias)-[:ALIAS_OF]->(keep)
|
|
497
|
+
SET alias.kind = 'identity_alias',
|
|
498
|
+
alias.canonicalPersonId = keep.personId,
|
|
499
|
+
alias.identityStatus = 'alias'
|
|
170
500
|
RETURN keep.name AS name, keep.github AS github, keep.previousNames AS aliases
|
|
171
|
-
" "$(jq -n --arg keep "$KEEP" --arg
|
|
501
|
+
" "$(jq -n --arg keep "$KEEP" --arg alias "$ALIAS" '{keep: $keep, alias: $alias}')"
|
|
172
502
|
;;
|
|
173
503
|
|
|
174
504
|
claim-handoff)
|
|
@@ -176,15 +506,69 @@ case "$OP" in
|
|
|
176
506
|
HO_SID="${2:?missing handoff-session-id}"
|
|
177
507
|
CYPHER="
|
|
178
508
|
MATCH (impl:Session {id: \$implSid}), (ho:Session {id: \$hoSid})
|
|
179
|
-
|
|
509
|
+
MATCH (impl)-[:BY]->(claimant:Person)
|
|
510
|
+
MATCH (ho)-[:HANDED_TO]->(claimant)
|
|
511
|
+
WHERE ho.handoffStatus IN ['pending','read','claimed']
|
|
180
512
|
MERGE (impl)-[:IMPLEMENTS]->(ho)
|
|
181
|
-
|
|
513
|
+
WITH impl, ho, size([(ho)-[:HANDED_TO]->(:Person) | 1]) AS recipientCount
|
|
514
|
+
FOREACH (_ IN CASE WHEN recipientCount = 1 THEN [1] ELSE [] END |
|
|
515
|
+
SET ho.handoffStatus = 'claimed',
|
|
516
|
+
ho.handoffClaimedAt = coalesce(ho.handoffClaimedAt, datetime()),
|
|
517
|
+
ho.handoffUpdatedAt = datetime(),
|
|
518
|
+
ho.handoffLifecycleReason = 'implementation_claimed',
|
|
519
|
+
ho.handoffLifecycleVersion = 1)
|
|
520
|
+
RETURN impl.id AS implementor, ho.id AS handoff, ho.topic AS topic,
|
|
521
|
+
ho.handoffStatus AS status, recipientCount
|
|
182
522
|
"
|
|
183
523
|
PARAMS="$(jq -n --arg implSid "$IMPL_SID" --arg hoSid "$HO_SID" '{implSid: $implSid, hoSid: $hoSid}')"
|
|
184
524
|
bash "$SCRIPT_DIR/bin/graph-wal.sh" append "$CYPHER" "$PARAMS" 2>/dev/null || true
|
|
185
525
|
bash "$GS" query "$CYPHER" "$PARAMS"
|
|
186
526
|
;;
|
|
187
527
|
|
|
528
|
+
claim-handoff-nudge)
|
|
529
|
+
SID="${1:?missing handoff-session-id}"
|
|
530
|
+
bash "$GS" query "
|
|
531
|
+
MATCH (s:Session {id: \$sid})-[:HANDED_TO]->(recipient:Person)
|
|
532
|
+
WHERE coalesce(s.handoffStatus, 'pending') IN ['pending','read','claimed']
|
|
533
|
+
AND coalesce(s.handoffLifecycleVersion, 0) >= 1
|
|
534
|
+
AND coalesce(s.handoffIntent, 'unclassified') IN ['action','feedback','fyi']
|
|
535
|
+
AND s.handoffNudgedAt IS NULL
|
|
536
|
+
AND (s.handoffNudgeClaimedAt IS NULL
|
|
537
|
+
OR s.handoffNudgeClaimedAt < datetime() - duration('PT1H'))
|
|
538
|
+
WITH s, collect(DISTINCT recipient.name) AS recipients
|
|
539
|
+
WHERE size(recipients) = 1
|
|
540
|
+
SET s.handoffNudgeClaimedAt = datetime(),
|
|
541
|
+
s.handoffNudgeStatus = 'claimed'
|
|
542
|
+
RETURN s.id AS id, s.topic AS topic, recipients
|
|
543
|
+
" "$(jq -n --arg sid "$SID" '{sid:$sid}')"
|
|
544
|
+
;;
|
|
545
|
+
|
|
546
|
+
mark-handoff-nudged)
|
|
547
|
+
SID="${1:?missing handoff-session-id}"
|
|
548
|
+
bash "$GS" query "
|
|
549
|
+
MATCH (s:Session {id: \$sid})
|
|
550
|
+
WHERE s.handoffNudgeStatus = 'claimed' AND s.handoffNudgedAt IS NULL
|
|
551
|
+
SET s.handoffNudgedAt = datetime(),
|
|
552
|
+
s.handoffNudgeStatus = 'sent',
|
|
553
|
+
s.handoffUpdatedAt = datetime(),
|
|
554
|
+
s.handoffLifecycleReason = 'recipient_nudged',
|
|
555
|
+
s.handoffLifecycleVersion = 1
|
|
556
|
+
RETURN s.id AS id, s.handoffNudgeStatus AS status
|
|
557
|
+
" "$(jq -n --arg sid "$SID" '{sid:$sid}')"
|
|
558
|
+
;;
|
|
559
|
+
|
|
560
|
+
release-handoff-nudge)
|
|
561
|
+
SID="${1:?missing handoff-session-id}"
|
|
562
|
+
bash "$GS" query "
|
|
563
|
+
MATCH (s:Session {id: \$sid})
|
|
564
|
+
WHERE s.handoffNudgeStatus = 'claimed' AND s.handoffNudgedAt IS NULL
|
|
565
|
+
SET s.handoffNudgeClaimedAt = null,
|
|
566
|
+
s.handoffNudgeStatus = 'failed',
|
|
567
|
+
s.handoffNudgeFailedAt = datetime()
|
|
568
|
+
RETURN s.id AS id, s.handoffNudgeStatus AS status
|
|
569
|
+
" "$(jq -n --arg sid "$SID" '{sid:$sid}')"
|
|
570
|
+
;;
|
|
571
|
+
|
|
188
572
|
create-pr)
|
|
189
573
|
SID="${1:?missing session-id}"
|
|
190
574
|
PR_NUM="${2:?missing pr-number}"
|
|
@@ -315,6 +699,292 @@ case "$OP" in
|
|
|
315
699
|
fi
|
|
316
700
|
;;
|
|
317
701
|
|
|
702
|
+
open-handoffs)
|
|
703
|
+
USER="${1:?missing username}"
|
|
704
|
+
LIMIT="${2:-10}"
|
|
705
|
+
if ! [[ "$LIMIT" =~ ^[0-9]+$ ]] || [ "$LIMIT" -lt 1 ] || [ "$LIMIT" -gt 25 ]; then
|
|
706
|
+
echo '{"error":"limit must be between 1 and 25"}'
|
|
707
|
+
exit 2
|
|
708
|
+
fi
|
|
709
|
+
bash "$GS" query "
|
|
710
|
+
MATCH (s:Session)-[:HANDED_TO]->(p:Person)
|
|
711
|
+
WHERE (
|
|
712
|
+
toLower(coalesce(p.name, '')) = toLower(\$user)
|
|
713
|
+
OR toLower(coalesce(p.github, '')) = toLower(\$user)
|
|
714
|
+
OR toLower(coalesce(p.githubUsername, '')) = toLower(\$user)
|
|
715
|
+
OR any(alias IN coalesce(p.previousNames, [])
|
|
716
|
+
WHERE toLower(alias) = toLower(\$user))
|
|
717
|
+
)
|
|
718
|
+
AND coalesce(s.handoffStatus, 'pending')
|
|
719
|
+
IN ['pending','unread','read','claimed']
|
|
720
|
+
WITH DISTINCT s
|
|
721
|
+
OPTIONAL MATCH (s)-[:BY]->(author:Person)
|
|
722
|
+
WITH s, author,
|
|
723
|
+
CASE WHEN s.date IS NULL THEN null
|
|
724
|
+
ELSE duration.inDays(date(s.date), date()).days END AS ageDays
|
|
725
|
+
RETURN s.id AS id,
|
|
726
|
+
coalesce(s.topic, s.summary, s.branch) AS topic,
|
|
727
|
+
coalesce(author.name, s.author, 'unknown') AS author,
|
|
728
|
+
toString(s.date) AS date,
|
|
729
|
+
coalesce(s.handoffStatus, 'pending') AS status,
|
|
730
|
+
coalesce(s.handoffIntent, 'unclassified') AS intent,
|
|
731
|
+
ageDays,
|
|
732
|
+
CASE WHEN ageDays IS NULL THEN 'unknown'
|
|
733
|
+
WHEN ageDays <= 14 THEN 'current'
|
|
734
|
+
WHEN ageDays <= 30 THEN 'aging'
|
|
735
|
+
ELSE 'stale' END AS freshness,
|
|
736
|
+
1 AS definitionVersion
|
|
737
|
+
ORDER BY s.date DESC, s.id
|
|
738
|
+
LIMIT \$limit
|
|
739
|
+
" "$(jq -n --arg user "$USER" --argjson limit "$LIMIT" '{user: $user, limit: $limit}')"
|
|
740
|
+
;;
|
|
741
|
+
|
|
742
|
+
pending-questions)
|
|
743
|
+
USER="${1:?missing username}"
|
|
744
|
+
LIMIT="${2:-10}"
|
|
745
|
+
if ! [[ "$LIMIT" =~ ^[0-9]+$ ]] || [ "$LIMIT" -lt 1 ] || [ "$LIMIT" -gt 25 ]; then
|
|
746
|
+
echo '{"error":"limit must be between 1 and 25"}'
|
|
747
|
+
exit 2
|
|
748
|
+
fi
|
|
749
|
+
bash "$GS" query "
|
|
750
|
+
MATCH (qs:QuestionSet)-[:ASKED_TO]->(p:Person)
|
|
751
|
+
WHERE (
|
|
752
|
+
toLower(coalesce(p.name, '')) = toLower(\$user)
|
|
753
|
+
OR toLower(coalesce(p.github, '')) = toLower(\$user)
|
|
754
|
+
OR toLower(coalesce(p.githubUsername, '')) = toLower(\$user)
|
|
755
|
+
OR any(alias IN coalesce(p.previousNames, [])
|
|
756
|
+
WHERE toLower(alias) = toLower(\$user))
|
|
757
|
+
)
|
|
758
|
+
AND coalesce(qs.status, 'pending') IN ['pending','active']
|
|
759
|
+
WITH DISTINCT qs
|
|
760
|
+
OPTIONAL MATCH (q:Question)-[:PART_OF]->(qs)
|
|
761
|
+
OPTIONAL MATCH (qs)-[:ASKED_BY]->(asker:Person)
|
|
762
|
+
WITH qs, asker, count(DISTINCT q) AS questions,
|
|
763
|
+
CASE WHEN qs.created IS NULL THEN null
|
|
764
|
+
ELSE duration.inDays(date(datetime(qs.created)), date()).days END AS ageDays
|
|
765
|
+
RETURN qs.id AS id,
|
|
766
|
+
qs.topic AS topic,
|
|
767
|
+
coalesce(asker.name, qs.askedBy, qs.from, 'unknown') AS askedBy,
|
|
768
|
+
questions,
|
|
769
|
+
toString(qs.created) AS created,
|
|
770
|
+
ageDays,
|
|
771
|
+
CASE WHEN ageDays IS NULL THEN 'unknown'
|
|
772
|
+
WHEN ageDays <= 14 THEN 'current'
|
|
773
|
+
WHEN ageDays <= 30 THEN 'aging'
|
|
774
|
+
ELSE 'stale-open' END AS freshness,
|
|
775
|
+
1 AS definitionVersion
|
|
776
|
+
ORDER BY qs.created DESC, qs.id
|
|
777
|
+
LIMIT \$limit
|
|
778
|
+
" "$(jq -n --arg user "$USER" --argjson limit "$LIMIT" '{user: $user, limit: $limit}')"
|
|
779
|
+
;;
|
|
780
|
+
|
|
781
|
+
lineage)
|
|
782
|
+
QUERY="${1:?missing topic}"
|
|
783
|
+
LIMIT="${2:-10}"
|
|
784
|
+
if ! [[ "$LIMIT" =~ ^[0-9]+$ ]] || [ "$LIMIT" -lt 1 ] || [ "$LIMIT" -gt 15 ]; then
|
|
785
|
+
echo '{"error":"limit must be between 1 and 15"}'
|
|
786
|
+
exit 2
|
|
787
|
+
fi
|
|
788
|
+
GRAPH_RAW="$(bash "$GS" query "
|
|
789
|
+
MATCH (s:Session)
|
|
790
|
+
WHERE toLower(coalesce(s.topic, '')) CONTAINS toLower(\$query)
|
|
791
|
+
OR toLower(coalesce(s.summary, '')) CONTAINS toLower(\$query)
|
|
792
|
+
OR toLower(coalesce(s.branch, '')) CONTAINS toLower(\$query)
|
|
793
|
+
OPTIONAL MATCH (s)-[:BY]->(author:Person)
|
|
794
|
+
OPTIONAL MATCH (s)-[:HANDED_TO]->(recipient:Person)
|
|
795
|
+
OPTIONAL MATCH (implementation:Session)-[:IMPLEMENTS]->(s)
|
|
796
|
+
OPTIONAL MATCH (implementation)-[:BY]->(implementer:Person)
|
|
797
|
+
OPTIONAL MATCH (s)-[:IMPLEMENTS]->(prior:Session)
|
|
798
|
+
OPTIONAL MATCH (prior)-[:BY]->(priorAuthor:Person)
|
|
799
|
+
OPTIONAL MATCH (s)-[:PRODUCED]->(pr:PR)
|
|
800
|
+
OPTIONAL MATCH (implementation)-[:PRODUCED]->(implementationPr:PR)
|
|
801
|
+
WITH s, author,
|
|
802
|
+
collect(DISTINCT coalesce(recipient.name, recipient.github)) AS recipients,
|
|
803
|
+
collect(DISTINCT CASE WHEN implementation IS NULL THEN null ELSE {
|
|
804
|
+
id: implementation.id,
|
|
805
|
+
topic: implementation.topic,
|
|
806
|
+
summary: implementation.summary,
|
|
807
|
+
branch: implementation.branch,
|
|
808
|
+
date: toString(implementation.date),
|
|
809
|
+
evidencePath: CASE
|
|
810
|
+
WHEN implementation.filePath IS NULL THEN null
|
|
811
|
+
WHEN implementation.filePath STARTS WITH 'memory/' THEN implementation.filePath
|
|
812
|
+
ELSE 'memory/' + implementation.filePath END,
|
|
813
|
+
implementedBy: coalesce(implementer.name, implementer.github)
|
|
814
|
+
} END) AS implementations,
|
|
815
|
+
collect(DISTINCT CASE WHEN prior IS NULL THEN null ELSE {
|
|
816
|
+
id: prior.id,
|
|
817
|
+
topic: prior.topic,
|
|
818
|
+
summary: prior.summary,
|
|
819
|
+
branch: prior.branch,
|
|
820
|
+
date: toString(prior.date),
|
|
821
|
+
evidencePath: CASE
|
|
822
|
+
WHEN prior.filePath IS NULL THEN null
|
|
823
|
+
WHEN prior.filePath STARTS WITH 'memory/' THEN prior.filePath
|
|
824
|
+
ELSE 'memory/' + prior.filePath END,
|
|
825
|
+
author: coalesce(priorAuthor.name, priorAuthor.github)
|
|
826
|
+
} END) AS implements,
|
|
827
|
+
collect(DISTINCT CASE WHEN pr IS NULL THEN null ELSE {
|
|
828
|
+
number: pr.number, repo: pr.repo, title: pr.title,
|
|
829
|
+
status: pr.status, mergedAt: toString(pr.mergedAt)
|
|
830
|
+
} END)
|
|
831
|
+
+ collect(DISTINCT CASE WHEN implementationPr IS NULL THEN null ELSE {
|
|
832
|
+
number: implementationPr.number, repo: implementationPr.repo,
|
|
833
|
+
title: implementationPr.title, status: implementationPr.status,
|
|
834
|
+
mergedAt: toString(implementationPr.mergedAt)
|
|
835
|
+
} END) AS prs
|
|
836
|
+
RETURN s.id AS sessionId,
|
|
837
|
+
s.topic AS topic,
|
|
838
|
+
s.summary AS summary,
|
|
839
|
+
s.branch AS branch,
|
|
840
|
+
toString(s.date) AS date,
|
|
841
|
+
CASE WHEN s.filePath IS NULL THEN null
|
|
842
|
+
WHEN s.filePath STARTS WITH 'memory/' THEN s.filePath
|
|
843
|
+
ELSE 'memory/' + s.filePath END AS evidencePath,
|
|
844
|
+
coalesce(author.name, author.github, s.author, 'unknown') AS author,
|
|
845
|
+
recipients[0..20] AS recipients,
|
|
846
|
+
implementations[0..10] AS implementations,
|
|
847
|
+
implements[0..10] AS implements,
|
|
848
|
+
prs[0..10] AS prs
|
|
849
|
+
ORDER BY s.date DESC, s.id
|
|
850
|
+
LIMIT \$limit
|
|
851
|
+
" "$(jq -n --arg query "$QUERY" --argjson limit "$LIMIT" '{query:$query,limit:$limit}')")"
|
|
852
|
+
FILES_RAW="$(memory_file_candidates "$QUERY" "$LIMIT" \
|
|
853
|
+
"$SCRIPT_DIR/memory/handoffs" \
|
|
854
|
+
"$SCRIPT_DIR/memory/wraps" \
|
|
855
|
+
"$SCRIPT_DIR/memory/sessions")"
|
|
856
|
+
jq -n \
|
|
857
|
+
--arg query "$QUERY" \
|
|
858
|
+
--argjson limit "$LIMIT" \
|
|
859
|
+
--argjson graph "$GRAPH_RAW" \
|
|
860
|
+
--argjson files "$FILES_RAW" '
|
|
861
|
+
($graph.values // [] | map({
|
|
862
|
+
sessionId: .[0], topic: .[1], summary: .[2], branch: .[3],
|
|
863
|
+
date: .[4], evidencePath: .[5], author: .[6],
|
|
864
|
+
recipients: (.[7] // []), implementations: (.[8] // []),
|
|
865
|
+
implements: (.[9] // []), prs: (.[10] // [])
|
|
866
|
+
})) as $matches
|
|
867
|
+
| ($matches | map(.evidencePath) | map(select(. != null)) | unique) as $graphPaths
|
|
868
|
+
| ($files | map(.evidencePath) | unique) as $filePaths
|
|
869
|
+
| ($filePaths - $graphPaths) as $unprojected
|
|
870
|
+
| {
|
|
871
|
+
schema: "egregore.graph-read/v1",
|
|
872
|
+
operation: "lineage",
|
|
873
|
+
definitionVersion: 1,
|
|
874
|
+
query: $query,
|
|
875
|
+
limit: $limit,
|
|
876
|
+
coverage:
|
|
877
|
+
(if ($graphPaths | length) == 0 then
|
|
878
|
+
(if ($filePaths | length) == 0 then "none" else "filesystem-only" end)
|
|
879
|
+
elif ($unprojected | length) > 0 then "partial"
|
|
880
|
+
else "complete" end),
|
|
881
|
+
graphMatches: $matches,
|
|
882
|
+
fileCandidates:
|
|
883
|
+
($files | map(
|
|
884
|
+
. as $candidate
|
|
885
|
+
| . + {
|
|
886
|
+
projection:
|
|
887
|
+
(if ($graphPaths | index($candidate.evidencePath)) != null
|
|
888
|
+
then "projected" else "unprojected" end)
|
|
889
|
+
}
|
|
890
|
+
)),
|
|
891
|
+
unprojectedPaths: $unprojected
|
|
892
|
+
}
|
|
893
|
+
'
|
|
894
|
+
;;
|
|
895
|
+
|
|
896
|
+
meeting-history)
|
|
897
|
+
QUERY="${1:?missing company, person, or topic}"
|
|
898
|
+
LIMIT="${2:-10}"
|
|
899
|
+
if ! [[ "$LIMIT" =~ ^[0-9]+$ ]] || [ "$LIMIT" -lt 1 ] || [ "$LIMIT" -gt 15 ]; then
|
|
900
|
+
echo '{"error":"limit must be between 1 and 15"}'
|
|
901
|
+
exit 2
|
|
902
|
+
fi
|
|
903
|
+
QUERY_TERMS="$(meaningful_query_terms "$QUERY")"
|
|
904
|
+
GRAPH_RAW="$(bash "$GS" query "
|
|
905
|
+
MATCH (m:Meeting)
|
|
906
|
+
OPTIONAL MATCH (m)-[:INVOLVES|CONDUCTED_BY]->(participant:Person)
|
|
907
|
+
WITH m, collect(DISTINCT participant) AS participants
|
|
908
|
+
WITH m, participants,
|
|
909
|
+
[term IN \$terms WHERE
|
|
910
|
+
toLower(coalesce(m.title, '')) CONTAINS term
|
|
911
|
+
OR toLower(coalesce(m.name, '')) CONTAINS term
|
|
912
|
+
OR toLower(coalesce(m.id, '')) CONTAINS term
|
|
913
|
+
OR any(p IN participants WHERE
|
|
914
|
+
toLower(coalesce(p.name, '')) CONTAINS term
|
|
915
|
+
OR toLower(coalesce(p.github, '')) CONTAINS term)
|
|
916
|
+
] AS matchedTerms
|
|
917
|
+
WHERE size(matchedTerms) > 0
|
|
918
|
+
OPTIONAL MATCH (artifact:Artifact)-[:FROM_MEETING]->(m)
|
|
919
|
+
RETURN m.id AS meetingId,
|
|
920
|
+
coalesce(m.title, m.name, m.id) AS title,
|
|
921
|
+
toString(m.date) AS date,
|
|
922
|
+
CASE WHEN m.filePath IS NULL THEN null
|
|
923
|
+
WHEN m.filePath STARTS WITH 'memory/' THEN m.filePath
|
|
924
|
+
ELSE 'memory/' + m.filePath END AS evidencePath,
|
|
925
|
+
([p IN participants | coalesce(p.name, p.github)])[0..20] AS participants,
|
|
926
|
+
(collect(DISTINCT CASE WHEN artifact IS NULL THEN null ELSE {
|
|
927
|
+
id: artifact.id,
|
|
928
|
+
type: artifact.type,
|
|
929
|
+
title: artifact.title,
|
|
930
|
+
summary: artifact.summary,
|
|
931
|
+
evidencePath: CASE
|
|
932
|
+
WHEN artifact.filePath IS NULL THEN null
|
|
933
|
+
WHEN artifact.filePath STARTS WITH 'memory/' THEN artifact.filePath
|
|
934
|
+
ELSE 'memory/' + artifact.filePath END
|
|
935
|
+
} END))[0..10] AS derivedKnowledge,
|
|
936
|
+
coalesce(m.verificationStatus, 'legacy') AS verificationStatus,
|
|
937
|
+
m.ingestManifestId AS ingestManifestId,
|
|
938
|
+
matchedTerms,
|
|
939
|
+
size(matchedTerms) AS matchScore
|
|
940
|
+
ORDER BY matchScore DESC, date ASC, meetingId
|
|
941
|
+
LIMIT \$limit
|
|
942
|
+
" "$(jq -n --arg query "$QUERY" --argjson terms "$QUERY_TERMS" --argjson limit "$LIMIT" \
|
|
943
|
+
'{query:$query,terms:$terms,limit:$limit}')")"
|
|
944
|
+
FILES_RAW="$(memory_file_candidates_for_terms \
|
|
945
|
+
"$QUERY_TERMS" "$LIMIT" "$SCRIPT_DIR/memory/meetings")"
|
|
946
|
+
jq -n \
|
|
947
|
+
--arg query "$QUERY" \
|
|
948
|
+
--argjson queryTerms "$QUERY_TERMS" \
|
|
949
|
+
--argjson limit "$LIMIT" \
|
|
950
|
+
--argjson graph "$GRAPH_RAW" \
|
|
951
|
+
--argjson files "$FILES_RAW" '
|
|
952
|
+
($graph.values // [] | map({
|
|
953
|
+
meetingId: .[0], title: .[1], date: .[2], evidencePath: .[3],
|
|
954
|
+
participants: (.[4] // []), derivedKnowledge: (.[5] // []),
|
|
955
|
+
verificationStatus: .[6], ingestManifestId: .[7],
|
|
956
|
+
matchedTerms: (.[8] // []), matchScore: (.[9] // 0)
|
|
957
|
+
})) as $meetings
|
|
958
|
+
| ($meetings | map(.evidencePath) | map(select(. != null)) | unique) as $graphPaths
|
|
959
|
+
| ($files | map(.evidencePath) | unique) as $filePaths
|
|
960
|
+
| ($filePaths - $graphPaths) as $unprojected
|
|
961
|
+
| {
|
|
962
|
+
schema: "egregore.graph-read/v1",
|
|
963
|
+
operation: "meeting-history",
|
|
964
|
+
definitionVersion: 1,
|
|
965
|
+
query: $query,
|
|
966
|
+
queryTerms: $queryTerms,
|
|
967
|
+
limit: $limit,
|
|
968
|
+
coverage:
|
|
969
|
+
(if ($graphPaths | length) == 0 then
|
|
970
|
+
(if ($filePaths | length) == 0 then "none" else "filesystem-only" end)
|
|
971
|
+
elif ($unprojected | length) > 0 then "partial"
|
|
972
|
+
else "complete" end),
|
|
973
|
+
graphMeetings: $meetings,
|
|
974
|
+
fileCandidates:
|
|
975
|
+
($files | map(
|
|
976
|
+
. as $candidate
|
|
977
|
+
| . + {
|
|
978
|
+
projection:
|
|
979
|
+
(if ($graphPaths | index($candidate.evidencePath)) != null
|
|
980
|
+
then "projected" else "unprojected" end)
|
|
981
|
+
}
|
|
982
|
+
)),
|
|
983
|
+
unprojectedPaths: $unprojected
|
|
984
|
+
}
|
|
985
|
+
'
|
|
986
|
+
;;
|
|
987
|
+
|
|
318
988
|
wal-status)
|
|
319
989
|
bash "$SCRIPT_DIR/bin/graph-wal.sh" status
|
|
320
990
|
;;
|
|
@@ -424,7 +1094,7 @@ case "$OP" in
|
|
|
424
1094
|
;;
|
|
425
1095
|
|
|
426
1096
|
*)
|
|
427
|
-
echo '{"error":"unknown operation: '"$OP"'","operations":["mark-read","mark-done","answer-question","resolve-handoffs","set-topic","record-focus","merge-person","claim-handoff","check-implements","create-pr","update-pr","my-merged-prs","my-implemented-handoffs","wal-status","create-harvest","create-harvest-session","record-harvest-turn","complete-harvest"]}'
|
|
1097
|
+
echo '{"error":"unknown operation: '"$OP"'","operations":["catalog","mark-read","mark-done","mark-expired","reopen-handoff","answer-question","resolve-handoffs","set-topic","record-focus","merge-person","claim-handoff","claim-handoff-nudge","mark-handoff-nudged","release-handoff-nudge","check-implements","create-pr","update-pr","my-merged-prs","my-implemented-handoffs","open-handoffs","pending-questions","lineage","meeting-history","wal-status","create-harvest","create-harvest-session","record-harvest-turn","complete-harvest"]}'
|
|
428
1098
|
exit 1
|
|
429
1099
|
;;
|
|
430
1100
|
|