create-egregore 0.19.22 → 0.19.23
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 +2 -2
- package/package.json +1 -1
- package/runtime/codex/.codex/skills/handoff/SKILL.md +69 -7
- package/runtime/codex/bin/codex-skill-render.mjs +5 -0
- package/runtime/codex/bin/handoff-run.sh +65 -81
- package/runtime/codex/bin/index-handoff.sh +2 -1
- package/runtime/codex/bin/publish-artifact.sh +25 -4
- package/runtime/pi/.codex/skills/handoff/SKILL.md +69 -7
- package/runtime/pi/bin/codex-skill-render.mjs +5 -0
- package/runtime/pi/bin/handoff-run.sh +65 -81
- package/runtime/pi/bin/index-handoff.sh +2 -1
- package/runtime/pi/bin/pi-product-workflows.mjs +14 -5
- package/runtime/pi/bin/publish-artifact.sh +25 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// egregore-launcher-version: 0.19.
|
|
2
|
+
// egregore-launcher-version: 0.19.23
|
|
3
3
|
//
|
|
4
4
|
// egregore — launch and browse Egregore instances from your terminal.
|
|
5
5
|
// Installed by create-egregore. Reads ~/.egregore/instances.json.
|
|
@@ -33,7 +33,7 @@ const crypto = require("node:crypto");
|
|
|
33
33
|
const { spawnSync, spawn } = require("node:child_process");
|
|
34
34
|
const readline = require("node:readline");
|
|
35
35
|
|
|
36
|
-
const VERSION = "0.19.
|
|
36
|
+
const VERSION = "0.19.23";
|
|
37
37
|
const REGISTRY = path.join(os.homedir(), ".egregore", "instances.json");
|
|
38
38
|
const DEFAULT_FILE = path.join(os.homedir(), ".egregore", "default");
|
|
39
39
|
const UPDATE_CACHE_FILE = path.join(os.homedir(), ".egregore", "update-check.json");
|
package/package.json
CHANGED
|
@@ -22,25 +22,85 @@ an addressed recipient belongs to `$wrap`.
|
|
|
22
22
|
`name`, then git config.
|
|
23
23
|
3. Parse a short topic and optional recipient from the user request. Match the
|
|
24
24
|
recipient against `memory/people/*.md`; omit the recipient when uncertain.
|
|
25
|
-
4. Draft the markdown
|
|
25
|
+
4. Draft the canonical markdown record with frontmatter and these sections:
|
|
26
26
|
- `## Briefing`
|
|
27
27
|
- `## Key Decisions`
|
|
28
28
|
- `## Current State`
|
|
29
29
|
- `## Open Threads`
|
|
30
30
|
- `## Next Steps`
|
|
31
31
|
- `## Entry Points`
|
|
32
|
-
|
|
32
|
+
Use this metadata contract even when the user supplied only prose:
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
---
|
|
36
|
+
capture_schema: egregore-capture/v1
|
|
37
|
+
capture_mode: addressed
|
|
38
|
+
kind: addressed
|
|
39
|
+
from: <author>
|
|
40
|
+
addressed_to: <recipient, if any>
|
|
41
|
+
date: YYYY-MM-DD
|
|
42
|
+
topic: <topic>
|
|
43
|
+
intent: <action | feedback | fyi>
|
|
44
|
+
claim: <one-line claim>
|
|
45
|
+
ask: <what the receiver should do>
|
|
46
|
+
---
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
5. Compose the HTML view from that record. Markdown is canonical; the JSON is
|
|
50
|
+
only a presentation map. Give every JSON section a stable `id` and add a
|
|
51
|
+
top-level `sourceMap` from every authored `##` heading to the section that
|
|
52
|
+
contains its complete text:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"kind": "handoff",
|
|
57
|
+
"kicker": "Handoff",
|
|
58
|
+
"topic": "<topic>",
|
|
59
|
+
"claim": "<one-line claim>",
|
|
60
|
+
"sourceMap": {
|
|
61
|
+
"Briefing": "briefing",
|
|
62
|
+
"Key Decisions": "decisions",
|
|
63
|
+
"Current State": "current-state",
|
|
64
|
+
"Open Threads": "open-threads",
|
|
65
|
+
"Next Steps": "next-steps",
|
|
66
|
+
"Entry Points": "entry-points"
|
|
67
|
+
},
|
|
68
|
+
"sections": [
|
|
69
|
+
{"id":"briefing","label":"Briefing","component":"prose","body":"<complete Briefing text>"}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Pick richer components when they fit, but copy all authored content into
|
|
75
|
+
the mapped destination. Never independently summarize the Markdown into the
|
|
76
|
+
JSON. For command-heavy sections, keep command headings, explanatory text,
|
|
77
|
+
fenced commands, and expected output verbatim.
|
|
78
|
+
|
|
79
|
+
6. Render the actual HTML with semantic coverage enabled and show it before
|
|
80
|
+
publishing. This is the approval surface—not a list of component names:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
node packages/egregore-artifacts/bin/cli.js composed "$COMPOSED_FILE" \
|
|
84
|
+
--source "$BODY_FILE" --verify-fidelity
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Stop for confirmation after the browser preview. If validation fails, fix
|
|
88
|
+
the mapping/content and render again. Skip confirmation only when the user
|
|
89
|
+
explicitly said to send without preview.
|
|
90
|
+
|
|
91
|
+
7. After confirmation, run one orchestration command:
|
|
33
92
|
|
|
34
93
|
```bash
|
|
35
94
|
TMPDIR="$(mktemp -d -t codex-handoff-XXXXXX)"
|
|
36
|
-
TMPDIR="$TMPDIR" bash bin/capture-run.sh --mode addressed --author "$AUTHOR"
|
|
95
|
+
TMPDIR="$TMPDIR" bash bin/capture-run.sh --mode addressed --author "$AUTHOR" \
|
|
96
|
+
--topic "$TOPIC" ${RECIPIENT_ARG} --composed "$COMPOSED_FILE" < "$BODY_FILE"
|
|
37
97
|
```
|
|
38
98
|
|
|
39
99
|
Use `--recipient "$RECIPIENT"` only when a clear teammate or future-self
|
|
40
100
|
recipient is known. Do not pass `--no-publish` unless the user explicitly
|
|
41
101
|
asked for a dry run. The runner may plan a notification, but never dispatches.
|
|
42
102
|
|
|
43
|
-
|
|
103
|
+
8. Read `$TMPDIR/handoff-run-result.json` and render:
|
|
44
104
|
|
|
45
105
|
```bash
|
|
46
106
|
node bin/codex-skill-render.mjs handoff-card "$TMPDIR/handoff-run-result.json"
|
|
@@ -50,20 +110,20 @@ Paste the rendered card exactly in the visible response, preferably in a
|
|
|
50
110
|
`text` fenced block. Do not paraphrase the card into bullets unless the user
|
|
51
111
|
asks for a summary.
|
|
52
112
|
|
|
53
|
-
|
|
113
|
+
9. If the result has `notifyStatus: "approval_required"`, follow
|
|
54
114
|
`.claude/context/notification-consent.md`. In a separate checkpoint, show
|
|
55
115
|
the exact organization, recipient/group, every channel/delivery, and exact
|
|
56
116
|
message from `notifyPlan`; offer Send / Edit / Cancel and wait. Only a Send
|
|
57
117
|
response to that preview permits one `approve` + `dispatch`. A local direct
|
|
58
118
|
message never falls back to a group.
|
|
59
119
|
|
|
60
|
-
|
|
120
|
+
10. Fire the detached save helper when appropriate:
|
|
61
121
|
|
|
62
122
|
```bash
|
|
63
123
|
( bash bin/handoff-save-egregore.sh "$AUTHOR" "$TOPIC" >/dev/null 2>&1 & ) >/dev/null 2>&1
|
|
64
124
|
```
|
|
65
125
|
|
|
66
|
-
|
|
126
|
+
11. Report the `memory/...` path and hosted link from the result JSON when a
|
|
67
127
|
link exists.
|
|
68
128
|
|
|
69
129
|
## Rules
|
|
@@ -72,6 +132,8 @@ asks for a summary.
|
|
|
72
132
|
- Do not use Claude Code commands.
|
|
73
133
|
- Keep the briefing concise and operational; include exact files, branches,
|
|
74
134
|
commands, open questions, and verification state when relevant.
|
|
135
|
+
- The artifact is a view of the handoff, never a summary of it. Publishing
|
|
136
|
+
must fail when any non-empty authored section is unmapped or loses content.
|
|
75
137
|
- Structured UX parity is required: the rendered card is the acknowledgment.
|
|
76
138
|
Do not add a preamble such as "Handoff created successfully." Preserve the
|
|
77
139
|
72-column TUI/card shape, status footer, links below the box, and warning
|
|
@@ -309,6 +309,7 @@ function renderHandoff(data) {
|
|
|
309
309
|
data.graphStatus ? `graph=${data.graphStatus}` : '',
|
|
310
310
|
data.memoryStatus ? `memory=${data.memoryStatus}` : '',
|
|
311
311
|
data.notifyStatus ? `notify=${data.notifyStatus}` : '',
|
|
312
|
+
data.publishStatus ? `publish=${data.publishStatus}` : '',
|
|
312
313
|
].filter(Boolean).join(' · ');
|
|
313
314
|
|
|
314
315
|
const lines = [topRule(), padLine('⇌ HANDOFF', data.author || ''), rule()];
|
|
@@ -317,6 +318,10 @@ function renderHandoff(data) {
|
|
|
317
318
|
if (file) lines.push(padLine(`Path: ${file}`));
|
|
318
319
|
if (data.artifactUrl) lines.push(padLine(`Link: ${data.artifactUrl}`));
|
|
319
320
|
if (statuses) lines.push(padLine(statuses));
|
|
321
|
+
if (data.publishStatus === 'fidelity-failed') {
|
|
322
|
+
lines.push(rule());
|
|
323
|
+
lines.push(padLine('Artifact not published — restore missing content and preview again.'));
|
|
324
|
+
}
|
|
320
325
|
lines.push(bottomRule());
|
|
321
326
|
return lines.join('\n');
|
|
322
327
|
}
|
|
@@ -122,87 +122,62 @@ if [ ! -s "$ABS_FILE" ]; then
|
|
|
122
122
|
exit 1
|
|
123
123
|
fi
|
|
124
124
|
|
|
125
|
-
# --- Canonical
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
125
|
+
# --- Canonical metadata boundary ----------------------------------------
|
|
126
|
+
# Callers are allowed to send prose-only markdown, but persisted handoffs are
|
|
127
|
+
# not. Normalize every input to the same frontmatter contract before indexing
|
|
128
|
+
# or rendering so title/author/recipient cannot disappear at the adapter seam.
|
|
129
|
+
HAS_FRONTMATTER=0
|
|
130
|
+
[ "$(head -1 "$ABS_FILE")" = "---" ] && HAS_FRONTMATTER=1
|
|
131
|
+
|
|
132
|
+
_has_frontmatter_key() {
|
|
133
|
+
[ "$HAS_FRONTMATTER" = "1" ] && sed -n '2,/^---$/p' "$ABS_FILE" | grep -qiE "^${1}:"
|
|
134
|
+
}
|
|
166
135
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
injected=1
|
|
178
|
-
next
|
|
179
|
-
}
|
|
180
|
-
{ print }
|
|
181
|
-
' "$ABS_FILE" > "$ABS_FILE.new" && mv "$ABS_FILE.new" "$ABS_FILE"
|
|
136
|
+
MISSING_SCHEMA=0; _has_frontmatter_key capture_schema || MISSING_SCHEMA=1
|
|
137
|
+
MISSING_MODE=0; _has_frontmatter_key capture_mode || MISSING_MODE=1
|
|
138
|
+
MISSING_KIND=0; _has_frontmatter_key kind || MISSING_KIND=1
|
|
139
|
+
MISSING_FROM=0; _has_frontmatter_key from || _has_frontmatter_key author || MISSING_FROM=1
|
|
140
|
+
MISSING_DATE=0; _has_frontmatter_key date || MISSING_DATE=1
|
|
141
|
+
MISSING_TOPIC=0; _has_frontmatter_key topic || MISSING_TOPIC=1
|
|
142
|
+
MISSING_INTENT=0; _has_frontmatter_key intent || MISSING_INTENT=1
|
|
143
|
+
MISSING_RECIPIENT=0
|
|
144
|
+
if [ -n "$RECIPIENT" ]; then
|
|
145
|
+
_has_frontmatter_key addressed_to || _has_frontmatter_key to || MISSING_RECIPIENT=1
|
|
182
146
|
fi
|
|
183
147
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
148
|
+
awk \
|
|
149
|
+
-v has_frontmatter="$HAS_FRONTMATTER" \
|
|
150
|
+
-v schema="$MISSING_SCHEMA" -v mode="$MISSING_MODE" -v kind="$MISSING_KIND" \
|
|
151
|
+
-v from="$MISSING_FROM" -v date_missing="$MISSING_DATE" \
|
|
152
|
+
-v topic_missing="$MISSING_TOPIC" -v intent_missing="$MISSING_INTENT" \
|
|
153
|
+
-v recipient_missing="$MISSING_RECIPIENT" \
|
|
154
|
+
-v author="$AUTHOR" -v date="$TODAY" -v topic="$TOPIC" \
|
|
155
|
+
-v intent="$INTENT" -v recipient="$RECIPIENT" '
|
|
156
|
+
function metadata() {
|
|
157
|
+
if (schema) print "capture_schema: egregore-capture/v1"
|
|
158
|
+
if (mode) print "capture_mode: addressed"
|
|
159
|
+
if (kind) print "kind: addressed"
|
|
160
|
+
if (from) print "from: " author
|
|
161
|
+
if (recipient_missing) print "addressed_to: " recipient
|
|
162
|
+
if (date_missing) print "date: " date
|
|
163
|
+
if (topic_missing) print "topic: " topic
|
|
164
|
+
if (intent_missing) print "intent: " intent
|
|
165
|
+
}
|
|
166
|
+
BEGIN {
|
|
167
|
+
if (!has_frontmatter) {
|
|
168
|
+
print "---"
|
|
169
|
+
metadata()
|
|
170
|
+
print "---"
|
|
171
|
+
print ""
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
has_frontmatter && NR == 1 {
|
|
175
|
+
print
|
|
176
|
+
metadata()
|
|
177
|
+
next
|
|
178
|
+
}
|
|
179
|
+
{ print }
|
|
180
|
+
' "$ABS_FILE" > "$ABS_FILE.new" && mv "$ABS_FILE.new" "$ABS_FILE"
|
|
206
181
|
|
|
207
182
|
# --- Append repo state ---------------------------------------------------
|
|
208
183
|
# --no-pr skips `gh pr list` (~400–600ms per repo). A detached PR backfill
|
|
@@ -401,12 +376,16 @@ PID_MEMORY=$!
|
|
|
401
376
|
RENDERED_HTML="$TMPD/handoff.html"
|
|
402
377
|
RENDER_OK=0
|
|
403
378
|
LOCAL_CLI="$SCRIPT_DIR/packages/egregore-artifacts/bin/cli.js"
|
|
379
|
+
FIDELITY_ARGS=(--verify-fidelity)
|
|
380
|
+
if [ "$RENDER_TYPE" = "composed" ]; then
|
|
381
|
+
FIDELITY_ARGS+=(--source "$ABS_FILE")
|
|
382
|
+
fi
|
|
404
383
|
if [ "${EGREGORE_USE_PUBLISHED:-0}" != "1" ] && [ -f "$LOCAL_CLI" ] && [ -d "$SCRIPT_DIR/packages/egregore-artifacts/node_modules/react" ]; then
|
|
405
|
-
if node "$LOCAL_CLI" "$RENDER_TYPE" "${RENDER_SRC:-$ABS_FILE}" --output "$RENDERED_HTML" >/dev/null 2
|
|
384
|
+
if node "$LOCAL_CLI" "$RENDER_TYPE" "${RENDER_SRC:-$ABS_FILE}" "${FIDELITY_ARGS[@]}" --output "$RENDERED_HTML" >/dev/null 2>"$TMPD/render-error"; then
|
|
406
385
|
RENDER_OK=1
|
|
407
386
|
fi
|
|
408
387
|
else
|
|
409
|
-
if npx egregore-artifacts "$RENDER_TYPE" "${RENDER_SRC:-$ABS_FILE}" --output "$RENDERED_HTML" >/dev/null 2
|
|
388
|
+
if npx -y egregore-artifacts@latest "$RENDER_TYPE" "${RENDER_SRC:-$ABS_FILE}" "${FIDELITY_ARGS[@]}" --output "$RENDERED_HTML" >/dev/null 2>"$TMPD/render-error"; then
|
|
410
389
|
RENDER_OK=1
|
|
411
390
|
fi
|
|
412
391
|
fi
|
|
@@ -481,9 +460,11 @@ PID_MEMORY=$!
|
|
|
481
460
|
PUBLISH_RC=0
|
|
482
461
|
if [ -z "$V1URL" ] && [ "$NO_PUBLISH" = "0" ] && [ -x "$SCRIPT_DIR/bin/publish-artifact.sh" ]; then
|
|
483
462
|
AURL=$(bash "$SCRIPT_DIR/bin/publish-artifact.sh" "$RENDER_TYPE" "${RENDER_SRC:-$ABS_FILE}" \
|
|
463
|
+
--verify-fidelity \
|
|
464
|
+
--source "$ABS_FILE" \
|
|
484
465
|
--title "$TOPIC" \
|
|
485
466
|
--author "$AUTHOR" \
|
|
486
|
-
--description "$BRIEFING_LEAD" 2
|
|
467
|
+
--description "$BRIEFING_LEAD" 2>"$TMPD/publish-error") || PUBLISH_RC=$?
|
|
487
468
|
fi
|
|
488
469
|
|
|
489
470
|
PS="published"
|
|
@@ -495,6 +476,8 @@ PID_MEMORY=$!
|
|
|
495
476
|
PS="relay-off"
|
|
496
477
|
elif [ "$PUBLISH_RC" = "3" ]; then
|
|
497
478
|
PS="hosting-off"
|
|
479
|
+
elif grep -q 'Handoff fidelity check failed' "$TMPD/render-error" "$TMPD/publish-error" 2>/dev/null; then
|
|
480
|
+
PS="fidelity-failed"
|
|
498
481
|
else
|
|
499
482
|
PS="failed"
|
|
500
483
|
fi
|
|
@@ -614,6 +597,7 @@ STATUS_BITS=("saved")
|
|
|
614
597
|
[ "$NOTIFY_STATUS" = "approval_required" ] && STATUS_BITS+=("notify approval pending")
|
|
615
598
|
[ -n "$ARTIFACT_URL" ] && STATUS_BITS+=("published")
|
|
616
599
|
[ "$PUBLISH_STATUS" = "relay-off" ] && STATUS_BITS+=("not published")
|
|
600
|
+
[ "$PUBLISH_STATUS" = "fidelity-failed" ] && STATUS_BITS+=("artifact fidelity failed")
|
|
617
601
|
|
|
618
602
|
STATUS_LINE=""
|
|
619
603
|
for bit in "${STATUS_BITS[@]}"; do
|
|
@@ -119,11 +119,12 @@ AUTHOR="$(md_field 'Author')"
|
|
|
119
119
|
AUTHOR_HANDLE="$(echo "$AUTHOR" | sed 's/[[:space:]]*→.*//' | awk '{print tolower($1)}')"
|
|
120
120
|
[ -z "$AUTHOR_HANDLE" ] && AUTHOR_HANDLE="unknown"
|
|
121
121
|
|
|
122
|
-
# Recipients: try markdown header
|
|
122
|
+
# Recipients: try markdown header, then canonical/legacy YAML names.
|
|
123
123
|
RECIPIENTS_RAW="$(md_field 'To')"
|
|
124
124
|
[ -z "$RECIPIENTS_RAW" ] && RECIPIENTS_RAW="$(md_field 'For')"
|
|
125
125
|
[ -z "$RECIPIENTS_RAW" ] && RECIPIENTS_RAW="$(yaml_extract 'to')"
|
|
126
126
|
[ -z "$RECIPIENTS_RAW" ] && RECIPIENTS_RAW="$(yaml_extract 'for')"
|
|
127
|
+
[ -z "$RECIPIENTS_RAW" ] && RECIPIENTS_RAW="$(yaml_extract 'addressed_to')"
|
|
127
128
|
# Also extract from "From: author → recipient" pattern
|
|
128
129
|
if [ -z "$RECIPIENTS_RAW" ] && echo "$AUTHOR" | grep -q '→' 2>/dev/null; then
|
|
129
130
|
RECIPIENTS_RAW="$(echo "$AUTHOR" | sed 's/.*→[[:space:]]*//')"
|
|
@@ -27,6 +27,7 @@ set -euo pipefail
|
|
|
27
27
|
# 3 hosting turned off (features.publishing=false)
|
|
28
28
|
# 4 nothing uploaded — public relay not enabled and no org API key,
|
|
29
29
|
# or egregore.json is missing/unreadable (fail closed)
|
|
30
|
+
# 5 renderer/fidelity validation failed (nothing uploaded)
|
|
30
31
|
|
|
31
32
|
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
32
33
|
CONFIG="$SCRIPT_DIR/egregore.json"
|
|
@@ -41,6 +42,8 @@ ARTIFACT_ID=""
|
|
|
41
42
|
RAW_HTML=0
|
|
42
43
|
SKIP_REFERENCES=0
|
|
43
44
|
PAIR=""
|
|
45
|
+
VERIFY_FIDELITY=0
|
|
46
|
+
FIDELITY_SOURCE=""
|
|
44
47
|
|
|
45
48
|
while [[ $# -gt 0 ]]; do
|
|
46
49
|
case "$1" in
|
|
@@ -49,6 +52,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
49
52
|
--description) DESCRIPTION="$2"; shift 2 ;;
|
|
50
53
|
--id) ARTIFACT_ID="$2"; shift 2 ;;
|
|
51
54
|
--pair) PAIR="$2"; shift 2 ;;
|
|
55
|
+
--verify-fidelity) VERIFY_FIDELITY=1; shift ;;
|
|
56
|
+
--source) FIDELITY_SOURCE="$2"; shift 2 ;;
|
|
52
57
|
--raw-html) RAW_HTML=1; shift ;;
|
|
53
58
|
--no-references) SKIP_REFERENCES=1; shift ;;
|
|
54
59
|
*)
|
|
@@ -165,6 +170,16 @@ else
|
|
|
165
170
|
if [ -n "$PAIR" ]; then
|
|
166
171
|
RENDER_ARGS+=(--pair "$PAIR")
|
|
167
172
|
fi
|
|
173
|
+
if [ "$VERIFY_FIDELITY" -eq 1 ]; then
|
|
174
|
+
RENDER_ARGS+=(--verify-fidelity)
|
|
175
|
+
if [ "$TYPE" = "composed" ]; then
|
|
176
|
+
[ -n "$FIDELITY_SOURCE" ] || {
|
|
177
|
+
echo "Handoff fidelity check requires --source <canonical-handoff.md>" >&2
|
|
178
|
+
exit 1
|
|
179
|
+
}
|
|
180
|
+
RENDER_ARGS+=(--source "$FIDELITY_SOURCE")
|
|
181
|
+
fi
|
|
182
|
+
fi
|
|
168
183
|
|
|
169
184
|
# Prefer the in-repo package copy when it's present AND its dependencies
|
|
170
185
|
# have been installed — that way local edits to packages/egregore-artifacts
|
|
@@ -174,15 +189,21 @@ else
|
|
|
174
189
|
# when a local install is present — useful in the upstream dev repo to
|
|
175
190
|
# dogfood what teammates and OSS users actually run.
|
|
176
191
|
LOCAL_CLI="$SCRIPT_DIR/packages/egregore-artifacts/bin/cli.js"
|
|
192
|
+
RENDER_ERROR="/tmp/egregore-artifacts/render-error-$$.log"
|
|
177
193
|
if [ "${EGREGORE_USE_PUBLISHED:-0}" != "1" ] && [ -f "$LOCAL_CLI" ] && [ -d "$SCRIPT_DIR/packages/egregore-artifacts/node_modules/react" ]; then
|
|
178
|
-
if ! node "$LOCAL_CLI" "${RENDER_ARGS[@]}" >/dev/null 2
|
|
179
|
-
|
|
194
|
+
if ! node "$LOCAL_CLI" "${RENDER_ARGS[@]}" >/dev/null 2>"$RENDER_ERROR"; then
|
|
195
|
+
sed -n '1,4p' "$RENDER_ERROR" >&2
|
|
196
|
+
rm -f "$RENDER_ERROR"
|
|
197
|
+
exit 5
|
|
180
198
|
fi
|
|
181
199
|
else
|
|
182
|
-
if ! npx egregore-artifacts@latest "${RENDER_ARGS[@]}" >/dev/null 2
|
|
183
|
-
|
|
200
|
+
if ! npx -y egregore-artifacts@latest "${RENDER_ARGS[@]}" >/dev/null 2>"$RENDER_ERROR"; then
|
|
201
|
+
sed -n '1,4p' "$RENDER_ERROR" >&2
|
|
202
|
+
rm -f "$RENDER_ERROR"
|
|
203
|
+
exit 5
|
|
184
204
|
fi
|
|
185
205
|
fi
|
|
206
|
+
rm -f "$RENDER_ERROR"
|
|
186
207
|
fi
|
|
187
208
|
|
|
188
209
|
if [ ! -f "$TMP_HTML" ]; then
|
|
@@ -22,25 +22,85 @@ an addressed recipient belongs to `$wrap`.
|
|
|
22
22
|
`name`, then git config.
|
|
23
23
|
3. Parse a short topic and optional recipient from the user request. Match the
|
|
24
24
|
recipient against `memory/people/*.md`; omit the recipient when uncertain.
|
|
25
|
-
4. Draft the markdown
|
|
25
|
+
4. Draft the canonical markdown record with frontmatter and these sections:
|
|
26
26
|
- `## Briefing`
|
|
27
27
|
- `## Key Decisions`
|
|
28
28
|
- `## Current State`
|
|
29
29
|
- `## Open Threads`
|
|
30
30
|
- `## Next Steps`
|
|
31
31
|
- `## Entry Points`
|
|
32
|
-
|
|
32
|
+
Use this metadata contract even when the user supplied only prose:
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
---
|
|
36
|
+
capture_schema: egregore-capture/v1
|
|
37
|
+
capture_mode: addressed
|
|
38
|
+
kind: addressed
|
|
39
|
+
from: <author>
|
|
40
|
+
addressed_to: <recipient, if any>
|
|
41
|
+
date: YYYY-MM-DD
|
|
42
|
+
topic: <topic>
|
|
43
|
+
intent: <action | feedback | fyi>
|
|
44
|
+
claim: <one-line claim>
|
|
45
|
+
ask: <what the receiver should do>
|
|
46
|
+
---
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
5. Compose the HTML view from that record. Markdown is canonical; the JSON is
|
|
50
|
+
only a presentation map. Give every JSON section a stable `id` and add a
|
|
51
|
+
top-level `sourceMap` from every authored `##` heading to the section that
|
|
52
|
+
contains its complete text:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"kind": "handoff",
|
|
57
|
+
"kicker": "Handoff",
|
|
58
|
+
"topic": "<topic>",
|
|
59
|
+
"claim": "<one-line claim>",
|
|
60
|
+
"sourceMap": {
|
|
61
|
+
"Briefing": "briefing",
|
|
62
|
+
"Key Decisions": "decisions",
|
|
63
|
+
"Current State": "current-state",
|
|
64
|
+
"Open Threads": "open-threads",
|
|
65
|
+
"Next Steps": "next-steps",
|
|
66
|
+
"Entry Points": "entry-points"
|
|
67
|
+
},
|
|
68
|
+
"sections": [
|
|
69
|
+
{"id":"briefing","label":"Briefing","component":"prose","body":"<complete Briefing text>"}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Pick richer components when they fit, but copy all authored content into
|
|
75
|
+
the mapped destination. Never independently summarize the Markdown into the
|
|
76
|
+
JSON. For command-heavy sections, keep command headings, explanatory text,
|
|
77
|
+
fenced commands, and expected output verbatim.
|
|
78
|
+
|
|
79
|
+
6. Render the actual HTML with semantic coverage enabled and show it before
|
|
80
|
+
publishing. This is the approval surface—not a list of component names:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
node packages/egregore-artifacts/bin/cli.js composed "$COMPOSED_FILE" \
|
|
84
|
+
--source "$BODY_FILE" --verify-fidelity
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Stop for confirmation after the browser preview. If validation fails, fix
|
|
88
|
+
the mapping/content and render again. Skip confirmation only when the user
|
|
89
|
+
explicitly said to send without preview.
|
|
90
|
+
|
|
91
|
+
7. After confirmation, run one orchestration command:
|
|
33
92
|
|
|
34
93
|
```bash
|
|
35
94
|
TMPDIR="$(mktemp -d -t codex-handoff-XXXXXX)"
|
|
36
|
-
TMPDIR="$TMPDIR" bash bin/capture-run.sh --mode addressed --author "$AUTHOR"
|
|
95
|
+
TMPDIR="$TMPDIR" bash bin/capture-run.sh --mode addressed --author "$AUTHOR" \
|
|
96
|
+
--topic "$TOPIC" ${RECIPIENT_ARG} --composed "$COMPOSED_FILE" < "$BODY_FILE"
|
|
37
97
|
```
|
|
38
98
|
|
|
39
99
|
Use `--recipient "$RECIPIENT"` only when a clear teammate or future-self
|
|
40
100
|
recipient is known. Do not pass `--no-publish` unless the user explicitly
|
|
41
101
|
asked for a dry run. The runner may plan a notification, but never dispatches.
|
|
42
102
|
|
|
43
|
-
|
|
103
|
+
8. Read `$TMPDIR/handoff-run-result.json` and render:
|
|
44
104
|
|
|
45
105
|
```bash
|
|
46
106
|
node bin/codex-skill-render.mjs handoff-card "$TMPDIR/handoff-run-result.json"
|
|
@@ -50,20 +110,20 @@ Paste the rendered card exactly in the visible response, preferably in a
|
|
|
50
110
|
`text` fenced block. Do not paraphrase the card into bullets unless the user
|
|
51
111
|
asks for a summary.
|
|
52
112
|
|
|
53
|
-
|
|
113
|
+
9. If the result has `notifyStatus: "approval_required"`, follow
|
|
54
114
|
`.claude/context/notification-consent.md`. In a separate checkpoint, show
|
|
55
115
|
the exact organization, recipient/group, every channel/delivery, and exact
|
|
56
116
|
message from `notifyPlan`; offer Send / Edit / Cancel and wait. Only a Send
|
|
57
117
|
response to that preview permits one `approve` + `dispatch`. A local direct
|
|
58
118
|
message never falls back to a group.
|
|
59
119
|
|
|
60
|
-
|
|
120
|
+
10. Fire the detached save helper when appropriate:
|
|
61
121
|
|
|
62
122
|
```bash
|
|
63
123
|
( bash bin/handoff-save-egregore.sh "$AUTHOR" "$TOPIC" >/dev/null 2>&1 & ) >/dev/null 2>&1
|
|
64
124
|
```
|
|
65
125
|
|
|
66
|
-
|
|
126
|
+
11. Report the `memory/...` path and hosted link from the result JSON when a
|
|
67
127
|
link exists.
|
|
68
128
|
|
|
69
129
|
## Rules
|
|
@@ -72,6 +132,8 @@ asks for a summary.
|
|
|
72
132
|
- Do not use Claude Code commands.
|
|
73
133
|
- Keep the briefing concise and operational; include exact files, branches,
|
|
74
134
|
commands, open questions, and verification state when relevant.
|
|
135
|
+
- The artifact is a view of the handoff, never a summary of it. Publishing
|
|
136
|
+
must fail when any non-empty authored section is unmapped or loses content.
|
|
75
137
|
- Structured UX parity is required: the rendered card is the acknowledgment.
|
|
76
138
|
Do not add a preamble such as "Handoff created successfully." Preserve the
|
|
77
139
|
72-column TUI/card shape, status footer, links below the box, and warning
|
|
@@ -309,6 +309,7 @@ function renderHandoff(data) {
|
|
|
309
309
|
data.graphStatus ? `graph=${data.graphStatus}` : '',
|
|
310
310
|
data.memoryStatus ? `memory=${data.memoryStatus}` : '',
|
|
311
311
|
data.notifyStatus ? `notify=${data.notifyStatus}` : '',
|
|
312
|
+
data.publishStatus ? `publish=${data.publishStatus}` : '',
|
|
312
313
|
].filter(Boolean).join(' · ');
|
|
313
314
|
|
|
314
315
|
const lines = [topRule(), padLine('⇌ HANDOFF', data.author || ''), rule()];
|
|
@@ -317,6 +318,10 @@ function renderHandoff(data) {
|
|
|
317
318
|
if (file) lines.push(padLine(`Path: ${file}`));
|
|
318
319
|
if (data.artifactUrl) lines.push(padLine(`Link: ${data.artifactUrl}`));
|
|
319
320
|
if (statuses) lines.push(padLine(statuses));
|
|
321
|
+
if (data.publishStatus === 'fidelity-failed') {
|
|
322
|
+
lines.push(rule());
|
|
323
|
+
lines.push(padLine('Artifact not published — restore missing content and preview again.'));
|
|
324
|
+
}
|
|
320
325
|
lines.push(bottomRule());
|
|
321
326
|
return lines.join('\n');
|
|
322
327
|
}
|
|
@@ -122,87 +122,62 @@ if [ ! -s "$ABS_FILE" ]; then
|
|
|
122
122
|
exit 1
|
|
123
123
|
fi
|
|
124
124
|
|
|
125
|
-
# --- Canonical
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
125
|
+
# --- Canonical metadata boundary ----------------------------------------
|
|
126
|
+
# Callers are allowed to send prose-only markdown, but persisted handoffs are
|
|
127
|
+
# not. Normalize every input to the same frontmatter contract before indexing
|
|
128
|
+
# or rendering so title/author/recipient cannot disappear at the adapter seam.
|
|
129
|
+
HAS_FRONTMATTER=0
|
|
130
|
+
[ "$(head -1 "$ABS_FILE")" = "---" ] && HAS_FRONTMATTER=1
|
|
131
|
+
|
|
132
|
+
_has_frontmatter_key() {
|
|
133
|
+
[ "$HAS_FRONTMATTER" = "1" ] && sed -n '2,/^---$/p' "$ABS_FILE" | grep -qiE "^${1}:"
|
|
134
|
+
}
|
|
166
135
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
injected=1
|
|
178
|
-
next
|
|
179
|
-
}
|
|
180
|
-
{ print }
|
|
181
|
-
' "$ABS_FILE" > "$ABS_FILE.new" && mv "$ABS_FILE.new" "$ABS_FILE"
|
|
136
|
+
MISSING_SCHEMA=0; _has_frontmatter_key capture_schema || MISSING_SCHEMA=1
|
|
137
|
+
MISSING_MODE=0; _has_frontmatter_key capture_mode || MISSING_MODE=1
|
|
138
|
+
MISSING_KIND=0; _has_frontmatter_key kind || MISSING_KIND=1
|
|
139
|
+
MISSING_FROM=0; _has_frontmatter_key from || _has_frontmatter_key author || MISSING_FROM=1
|
|
140
|
+
MISSING_DATE=0; _has_frontmatter_key date || MISSING_DATE=1
|
|
141
|
+
MISSING_TOPIC=0; _has_frontmatter_key topic || MISSING_TOPIC=1
|
|
142
|
+
MISSING_INTENT=0; _has_frontmatter_key intent || MISSING_INTENT=1
|
|
143
|
+
MISSING_RECIPIENT=0
|
|
144
|
+
if [ -n "$RECIPIENT" ]; then
|
|
145
|
+
_has_frontmatter_key addressed_to || _has_frontmatter_key to || MISSING_RECIPIENT=1
|
|
182
146
|
fi
|
|
183
147
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
148
|
+
awk \
|
|
149
|
+
-v has_frontmatter="$HAS_FRONTMATTER" \
|
|
150
|
+
-v schema="$MISSING_SCHEMA" -v mode="$MISSING_MODE" -v kind="$MISSING_KIND" \
|
|
151
|
+
-v from="$MISSING_FROM" -v date_missing="$MISSING_DATE" \
|
|
152
|
+
-v topic_missing="$MISSING_TOPIC" -v intent_missing="$MISSING_INTENT" \
|
|
153
|
+
-v recipient_missing="$MISSING_RECIPIENT" \
|
|
154
|
+
-v author="$AUTHOR" -v date="$TODAY" -v topic="$TOPIC" \
|
|
155
|
+
-v intent="$INTENT" -v recipient="$RECIPIENT" '
|
|
156
|
+
function metadata() {
|
|
157
|
+
if (schema) print "capture_schema: egregore-capture/v1"
|
|
158
|
+
if (mode) print "capture_mode: addressed"
|
|
159
|
+
if (kind) print "kind: addressed"
|
|
160
|
+
if (from) print "from: " author
|
|
161
|
+
if (recipient_missing) print "addressed_to: " recipient
|
|
162
|
+
if (date_missing) print "date: " date
|
|
163
|
+
if (topic_missing) print "topic: " topic
|
|
164
|
+
if (intent_missing) print "intent: " intent
|
|
165
|
+
}
|
|
166
|
+
BEGIN {
|
|
167
|
+
if (!has_frontmatter) {
|
|
168
|
+
print "---"
|
|
169
|
+
metadata()
|
|
170
|
+
print "---"
|
|
171
|
+
print ""
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
has_frontmatter && NR == 1 {
|
|
175
|
+
print
|
|
176
|
+
metadata()
|
|
177
|
+
next
|
|
178
|
+
}
|
|
179
|
+
{ print }
|
|
180
|
+
' "$ABS_FILE" > "$ABS_FILE.new" && mv "$ABS_FILE.new" "$ABS_FILE"
|
|
206
181
|
|
|
207
182
|
# --- Append repo state ---------------------------------------------------
|
|
208
183
|
# --no-pr skips `gh pr list` (~400–600ms per repo). A detached PR backfill
|
|
@@ -401,12 +376,16 @@ PID_MEMORY=$!
|
|
|
401
376
|
RENDERED_HTML="$TMPD/handoff.html"
|
|
402
377
|
RENDER_OK=0
|
|
403
378
|
LOCAL_CLI="$SCRIPT_DIR/packages/egregore-artifacts/bin/cli.js"
|
|
379
|
+
FIDELITY_ARGS=(--verify-fidelity)
|
|
380
|
+
if [ "$RENDER_TYPE" = "composed" ]; then
|
|
381
|
+
FIDELITY_ARGS+=(--source "$ABS_FILE")
|
|
382
|
+
fi
|
|
404
383
|
if [ "${EGREGORE_USE_PUBLISHED:-0}" != "1" ] && [ -f "$LOCAL_CLI" ] && [ -d "$SCRIPT_DIR/packages/egregore-artifacts/node_modules/react" ]; then
|
|
405
|
-
if node "$LOCAL_CLI" "$RENDER_TYPE" "${RENDER_SRC:-$ABS_FILE}" --output "$RENDERED_HTML" >/dev/null 2
|
|
384
|
+
if node "$LOCAL_CLI" "$RENDER_TYPE" "${RENDER_SRC:-$ABS_FILE}" "${FIDELITY_ARGS[@]}" --output "$RENDERED_HTML" >/dev/null 2>"$TMPD/render-error"; then
|
|
406
385
|
RENDER_OK=1
|
|
407
386
|
fi
|
|
408
387
|
else
|
|
409
|
-
if npx egregore-artifacts "$RENDER_TYPE" "${RENDER_SRC:-$ABS_FILE}" --output "$RENDERED_HTML" >/dev/null 2
|
|
388
|
+
if npx -y egregore-artifacts@latest "$RENDER_TYPE" "${RENDER_SRC:-$ABS_FILE}" "${FIDELITY_ARGS[@]}" --output "$RENDERED_HTML" >/dev/null 2>"$TMPD/render-error"; then
|
|
410
389
|
RENDER_OK=1
|
|
411
390
|
fi
|
|
412
391
|
fi
|
|
@@ -481,9 +460,11 @@ PID_MEMORY=$!
|
|
|
481
460
|
PUBLISH_RC=0
|
|
482
461
|
if [ -z "$V1URL" ] && [ "$NO_PUBLISH" = "0" ] && [ -x "$SCRIPT_DIR/bin/publish-artifact.sh" ]; then
|
|
483
462
|
AURL=$(bash "$SCRIPT_DIR/bin/publish-artifact.sh" "$RENDER_TYPE" "${RENDER_SRC:-$ABS_FILE}" \
|
|
463
|
+
--verify-fidelity \
|
|
464
|
+
--source "$ABS_FILE" \
|
|
484
465
|
--title "$TOPIC" \
|
|
485
466
|
--author "$AUTHOR" \
|
|
486
|
-
--description "$BRIEFING_LEAD" 2
|
|
467
|
+
--description "$BRIEFING_LEAD" 2>"$TMPD/publish-error") || PUBLISH_RC=$?
|
|
487
468
|
fi
|
|
488
469
|
|
|
489
470
|
PS="published"
|
|
@@ -495,6 +476,8 @@ PID_MEMORY=$!
|
|
|
495
476
|
PS="relay-off"
|
|
496
477
|
elif [ "$PUBLISH_RC" = "3" ]; then
|
|
497
478
|
PS="hosting-off"
|
|
479
|
+
elif grep -q 'Handoff fidelity check failed' "$TMPD/render-error" "$TMPD/publish-error" 2>/dev/null; then
|
|
480
|
+
PS="fidelity-failed"
|
|
498
481
|
else
|
|
499
482
|
PS="failed"
|
|
500
483
|
fi
|
|
@@ -614,6 +597,7 @@ STATUS_BITS=("saved")
|
|
|
614
597
|
[ "$NOTIFY_STATUS" = "approval_required" ] && STATUS_BITS+=("notify approval pending")
|
|
615
598
|
[ -n "$ARTIFACT_URL" ] && STATUS_BITS+=("published")
|
|
616
599
|
[ "$PUBLISH_STATUS" = "relay-off" ] && STATUS_BITS+=("not published")
|
|
600
|
+
[ "$PUBLISH_STATUS" = "fidelity-failed" ] && STATUS_BITS+=("artifact fidelity failed")
|
|
617
601
|
|
|
618
602
|
STATUS_LINE=""
|
|
619
603
|
for bit in "${STATUS_BITS[@]}"; do
|
|
@@ -119,11 +119,12 @@ AUTHOR="$(md_field 'Author')"
|
|
|
119
119
|
AUTHOR_HANDLE="$(echo "$AUTHOR" | sed 's/[[:space:]]*→.*//' | awk '{print tolower($1)}')"
|
|
120
120
|
[ -z "$AUTHOR_HANDLE" ] && AUTHOR_HANDLE="unknown"
|
|
121
121
|
|
|
122
|
-
# Recipients: try markdown header
|
|
122
|
+
# Recipients: try markdown header, then canonical/legacy YAML names.
|
|
123
123
|
RECIPIENTS_RAW="$(md_field 'To')"
|
|
124
124
|
[ -z "$RECIPIENTS_RAW" ] && RECIPIENTS_RAW="$(md_field 'For')"
|
|
125
125
|
[ -z "$RECIPIENTS_RAW" ] && RECIPIENTS_RAW="$(yaml_extract 'to')"
|
|
126
126
|
[ -z "$RECIPIENTS_RAW" ] && RECIPIENTS_RAW="$(yaml_extract 'for')"
|
|
127
|
+
[ -z "$RECIPIENTS_RAW" ] && RECIPIENTS_RAW="$(yaml_extract 'addressed_to')"
|
|
127
128
|
# Also extract from "From: author → recipient" pattern
|
|
128
129
|
if [ -z "$RECIPIENTS_RAW" ] && echo "$AUTHOR" | grep -q '→' 2>/dev/null; then
|
|
129
130
|
RECIPIENTS_RAW="$(echo "$AUTHOR" | sed 's/.*→[[:space:]]*//')"
|
|
@@ -199,14 +199,23 @@ function section(title, body) {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
export function buildHandoffDocument(params, author, date = new Date().toISOString().slice(0, 10)) {
|
|
202
|
+
const topic = cleanArgs(params.topic);
|
|
203
|
+
const recipient = cleanArgs(params.recipient);
|
|
202
204
|
const lines = [
|
|
203
|
-
|
|
204
|
-
"",
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
"---",
|
|
206
|
+
"capture_schema: egregore-capture/v1",
|
|
207
|
+
"capture_mode: addressed",
|
|
208
|
+
"kind: addressed",
|
|
209
|
+
`from: ${author}`,
|
|
207
210
|
];
|
|
208
|
-
if (
|
|
211
|
+
if (recipient) lines.push(`addressed_to: ${recipient}`);
|
|
209
212
|
lines.push(
|
|
213
|
+
`date: ${date}`,
|
|
214
|
+
`topic: ${topic}`,
|
|
215
|
+
"intent: action",
|
|
216
|
+
"---",
|
|
217
|
+
"",
|
|
218
|
+
`# Handoff: ${topic}`,
|
|
210
219
|
"",
|
|
211
220
|
section("Briefing", params.briefing),
|
|
212
221
|
"",
|
|
@@ -27,6 +27,7 @@ set -euo pipefail
|
|
|
27
27
|
# 3 hosting turned off (features.publishing=false)
|
|
28
28
|
# 4 nothing uploaded — public relay not enabled and no org API key,
|
|
29
29
|
# or egregore.json is missing/unreadable (fail closed)
|
|
30
|
+
# 5 renderer/fidelity validation failed (nothing uploaded)
|
|
30
31
|
|
|
31
32
|
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
32
33
|
CONFIG="$SCRIPT_DIR/egregore.json"
|
|
@@ -41,6 +42,8 @@ ARTIFACT_ID=""
|
|
|
41
42
|
RAW_HTML=0
|
|
42
43
|
SKIP_REFERENCES=0
|
|
43
44
|
PAIR=""
|
|
45
|
+
VERIFY_FIDELITY=0
|
|
46
|
+
FIDELITY_SOURCE=""
|
|
44
47
|
|
|
45
48
|
while [[ $# -gt 0 ]]; do
|
|
46
49
|
case "$1" in
|
|
@@ -49,6 +52,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
49
52
|
--description) DESCRIPTION="$2"; shift 2 ;;
|
|
50
53
|
--id) ARTIFACT_ID="$2"; shift 2 ;;
|
|
51
54
|
--pair) PAIR="$2"; shift 2 ;;
|
|
55
|
+
--verify-fidelity) VERIFY_FIDELITY=1; shift ;;
|
|
56
|
+
--source) FIDELITY_SOURCE="$2"; shift 2 ;;
|
|
52
57
|
--raw-html) RAW_HTML=1; shift ;;
|
|
53
58
|
--no-references) SKIP_REFERENCES=1; shift ;;
|
|
54
59
|
*)
|
|
@@ -165,6 +170,16 @@ else
|
|
|
165
170
|
if [ -n "$PAIR" ]; then
|
|
166
171
|
RENDER_ARGS+=(--pair "$PAIR")
|
|
167
172
|
fi
|
|
173
|
+
if [ "$VERIFY_FIDELITY" -eq 1 ]; then
|
|
174
|
+
RENDER_ARGS+=(--verify-fidelity)
|
|
175
|
+
if [ "$TYPE" = "composed" ]; then
|
|
176
|
+
[ -n "$FIDELITY_SOURCE" ] || {
|
|
177
|
+
echo "Handoff fidelity check requires --source <canonical-handoff.md>" >&2
|
|
178
|
+
exit 1
|
|
179
|
+
}
|
|
180
|
+
RENDER_ARGS+=(--source "$FIDELITY_SOURCE")
|
|
181
|
+
fi
|
|
182
|
+
fi
|
|
168
183
|
|
|
169
184
|
# Prefer the in-repo package copy when it's present AND its dependencies
|
|
170
185
|
# have been installed — that way local edits to packages/egregore-artifacts
|
|
@@ -174,15 +189,21 @@ else
|
|
|
174
189
|
# when a local install is present — useful in the upstream dev repo to
|
|
175
190
|
# dogfood what teammates and OSS users actually run.
|
|
176
191
|
LOCAL_CLI="$SCRIPT_DIR/packages/egregore-artifacts/bin/cli.js"
|
|
192
|
+
RENDER_ERROR="/tmp/egregore-artifacts/render-error-$$.log"
|
|
177
193
|
if [ "${EGREGORE_USE_PUBLISHED:-0}" != "1" ] && [ -f "$LOCAL_CLI" ] && [ -d "$SCRIPT_DIR/packages/egregore-artifacts/node_modules/react" ]; then
|
|
178
|
-
if ! node "$LOCAL_CLI" "${RENDER_ARGS[@]}" >/dev/null 2
|
|
179
|
-
|
|
194
|
+
if ! node "$LOCAL_CLI" "${RENDER_ARGS[@]}" >/dev/null 2>"$RENDER_ERROR"; then
|
|
195
|
+
sed -n '1,4p' "$RENDER_ERROR" >&2
|
|
196
|
+
rm -f "$RENDER_ERROR"
|
|
197
|
+
exit 5
|
|
180
198
|
fi
|
|
181
199
|
else
|
|
182
|
-
if ! npx egregore-artifacts@latest "${RENDER_ARGS[@]}" >/dev/null 2
|
|
183
|
-
|
|
200
|
+
if ! npx -y egregore-artifacts@latest "${RENDER_ARGS[@]}" >/dev/null 2>"$RENDER_ERROR"; then
|
|
201
|
+
sed -n '1,4p' "$RENDER_ERROR" >&2
|
|
202
|
+
rm -f "$RENDER_ERROR"
|
|
203
|
+
exit 5
|
|
184
204
|
fi
|
|
185
205
|
fi
|
|
206
|
+
rm -f "$RENDER_ERROR"
|
|
186
207
|
fi
|
|
187
208
|
|
|
188
209
|
if [ ! -f "$TMP_HTML" ]; then
|