create-egregore 0.19.23 → 0.19.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/assets/egregore-launcher.js +50 -7
  2. package/assets/egregore-launcher.sh +10 -2
  3. package/bin/cli.js +21 -3
  4. package/lib/codex-runtime.js +25 -3
  5. package/lib/connect-runtime.js +329 -0
  6. package/lib/pi-runtime.js +23 -3
  7. package/lib/setup.js +13 -0
  8. package/package.json +3 -3
  9. package/runtime/codex/.codex/skills/handoff/SKILL.md +35 -11
  10. package/runtime/codex/.codex/skills/save/SKILL.md +39 -4
  11. package/runtime/codex/.codex/spec-manifest.json +2 -2
  12. package/runtime/codex/AGENTS.md +18 -1
  13. package/runtime/codex/bin/agent.sh +28 -14
  14. package/runtime/codex/bin/attendant.sh +169 -6
  15. package/runtime/codex/bin/codex-render-spec.mjs +18 -1
  16. package/runtime/codex/bin/codex-session-start.sh +13 -0
  17. package/runtime/codex/bin/codex-sync-skills.sh +1 -0
  18. package/runtime/codex/bin/graph.sh +21 -1
  19. package/runtime/codex/bin/handoff-run.sh +25 -5
  20. package/runtime/codex/bin/lib/context.sh +220 -172
  21. package/runtime/codex/bin/lib/dashboard-artifact.sh +113 -13
  22. package/runtime/codex/bin/lib/git-sync.sh +44 -8
  23. package/runtime/codex/bin/lib/greeting.sh +34 -11
  24. package/runtime/codex/bin/lib/identity.sh +32 -14
  25. package/runtime/codex/bin/person.py +20 -0
  26. package/runtime/codex/manifest.json +3 -72
  27. package/runtime/pi/.codex/skills/handoff/SKILL.md +35 -11
  28. package/runtime/pi/.codex/skills/save/SKILL.md +39 -4
  29. package/runtime/pi/.codex/spec-manifest.json +2 -2
  30. package/runtime/pi/.pi/APPEND_SYSTEM.md +18 -1
  31. package/runtime/pi/.pi/spec-manifest.json +2 -2
  32. package/runtime/pi/AGENTS.md +18 -1
  33. package/runtime/pi/bin/agent.sh +28 -14
  34. package/runtime/pi/bin/attendant.sh +169 -6
  35. package/runtime/pi/bin/codex-render-spec.mjs +18 -1
  36. package/runtime/pi/bin/codex-session-start.sh +13 -0
  37. package/runtime/pi/bin/codex-sync-skills.sh +1 -0
  38. package/runtime/pi/bin/graph.sh +21 -1
  39. package/runtime/pi/bin/handoff-run.sh +25 -5
  40. package/runtime/pi/bin/lib/context.sh +220 -172
  41. package/runtime/pi/bin/lib/dashboard-artifact.sh +113 -13
  42. package/runtime/pi/bin/lib/git-sync.sh +44 -8
  43. package/runtime/pi/bin/lib/greeting.sh +34 -11
  44. package/runtime/pi/bin/lib/identity.sh +32 -14
  45. package/runtime/pi/bin/person.py +20 -0
  46. package/runtime/pi/bin/pi-product-workflows.mjs +5 -1
  47. package/runtime/pi/manifest.json +3 -72
  48. package/runtime/codex/.codex/skills/add/SKILL.md +0 -29
  49. package/runtime/codex/.codex/skills/answer/SKILL.md +0 -29
  50. package/runtime/codex/.codex/skills/ask/SKILL.md +0 -67
  51. package/runtime/codex/.codex/skills/audit/SKILL.md +0 -29
  52. package/runtime/codex/.codex/skills/autosave/SKILL.md +0 -29
  53. package/runtime/codex/.codex/skills/branch/SKILL.md +0 -29
  54. package/runtime/codex/.codex/skills/commit/SKILL.md +0 -29
  55. package/runtime/codex/.codex/skills/demo-mode/SKILL.md +0 -29
  56. package/runtime/codex/.codex/skills/deploy-bridge/SKILL.md +0 -29
  57. package/runtime/codex/.codex/skills/design/SKILL.md +0 -29
  58. package/runtime/codex/.codex/skills/egregore-voice/SKILL.md +0 -29
  59. package/runtime/codex/.codex/skills/emissary/SKILL.md +0 -45
  60. package/runtime/codex/.codex/skills/env/SKILL.md +0 -29
  61. package/runtime/codex/.codex/skills/ingest-google/SKILL.md +0 -29
  62. package/runtime/codex/.codex/skills/minion/SKILL.md +0 -29
  63. package/runtime/codex/.codex/skills/mock/SKILL.md +0 -29
  64. package/runtime/codex/.codex/skills/pr/SKILL.md +0 -29
  65. package/runtime/codex/.codex/skills/product-voice/SKILL.md +0 -29
  66. package/runtime/codex/.codex/skills/project/SKILL.md +0 -45
  67. package/runtime/codex/.codex/skills/pull/SKILL.md +0 -29
  68. package/runtime/codex/.codex/skills/push/SKILL.md +0 -29
  69. package/runtime/codex/.codex/skills/quest-suggest/SKILL.md +0 -29
  70. package/runtime/codex/.codex/skills/review-pr/SKILL.md +0 -45
  71. package/runtime/codex/.codex/skills/scroll/SKILL.md +0 -288
  72. package/runtime/codex/.codex/skills/scroll/agents/openai.yaml +0 -4
  73. package/runtime/codex/.codex/skills/scroll/assets/shell.html +0 -871
  74. package/runtime/codex/.codex/skills/search/SKILL.md +0 -79
  75. package/runtime/codex/.codex/skills/session-view/SKILL.md +0 -29
  76. package/runtime/codex/.codex/skills/setup/SKILL.md +0 -29
  77. package/runtime/codex/.codex/skills/summon/SKILL.md +0 -45
  78. package/runtime/codex/.codex/skills/task/SKILL.md +0 -29
  79. package/runtime/codex/.codex/skills/teams-connect/SKILL.md +0 -29
  80. package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +0 -29
  81. package/runtime/codex/.codex/skills/the-spiral/SKILL.md +0 -84
  82. package/runtime/codex/.codex/skills/thread/SKILL.md +0 -29
  83. package/runtime/codex/.codex/skills/todo/SKILL.md +0 -45
  84. package/runtime/pi/.codex/skills/add/SKILL.md +0 -29
  85. package/runtime/pi/.codex/skills/answer/SKILL.md +0 -29
  86. package/runtime/pi/.codex/skills/ask/SKILL.md +0 -67
  87. package/runtime/pi/.codex/skills/audit/SKILL.md +0 -29
  88. package/runtime/pi/.codex/skills/autosave/SKILL.md +0 -29
  89. package/runtime/pi/.codex/skills/branch/SKILL.md +0 -29
  90. package/runtime/pi/.codex/skills/commit/SKILL.md +0 -29
  91. package/runtime/pi/.codex/skills/demo-mode/SKILL.md +0 -29
  92. package/runtime/pi/.codex/skills/deploy-bridge/SKILL.md +0 -29
  93. package/runtime/pi/.codex/skills/design/SKILL.md +0 -29
  94. package/runtime/pi/.codex/skills/egregore-voice/SKILL.md +0 -29
  95. package/runtime/pi/.codex/skills/emissary/SKILL.md +0 -45
  96. package/runtime/pi/.codex/skills/env/SKILL.md +0 -29
  97. package/runtime/pi/.codex/skills/ingest-google/SKILL.md +0 -29
  98. package/runtime/pi/.codex/skills/minion/SKILL.md +0 -29
  99. package/runtime/pi/.codex/skills/mock/SKILL.md +0 -29
  100. package/runtime/pi/.codex/skills/pr/SKILL.md +0 -29
  101. package/runtime/pi/.codex/skills/product-voice/SKILL.md +0 -29
  102. package/runtime/pi/.codex/skills/project/SKILL.md +0 -45
  103. package/runtime/pi/.codex/skills/pull/SKILL.md +0 -29
  104. package/runtime/pi/.codex/skills/push/SKILL.md +0 -29
  105. package/runtime/pi/.codex/skills/quest-suggest/SKILL.md +0 -29
  106. package/runtime/pi/.codex/skills/review-pr/SKILL.md +0 -45
  107. package/runtime/pi/.codex/skills/scroll/SKILL.md +0 -288
  108. package/runtime/pi/.codex/skills/scroll/agents/openai.yaml +0 -4
  109. package/runtime/pi/.codex/skills/scroll/assets/shell.html +0 -871
  110. package/runtime/pi/.codex/skills/search/SKILL.md +0 -79
  111. package/runtime/pi/.codex/skills/session-view/SKILL.md +0 -29
  112. package/runtime/pi/.codex/skills/setup/SKILL.md +0 -29
  113. package/runtime/pi/.codex/skills/summon/SKILL.md +0 -45
  114. package/runtime/pi/.codex/skills/task/SKILL.md +0 -29
  115. package/runtime/pi/.codex/skills/teams-connect/SKILL.md +0 -29
  116. package/runtime/pi/.codex/skills/telegram-connect/SKILL.md +0 -29
  117. package/runtime/pi/.codex/skills/the-spiral/SKILL.md +0 -84
  118. package/runtime/pi/.codex/skills/thread/SKILL.md +0 -29
  119. package/runtime/pi/.codex/skills/todo/SKILL.md +0 -45
@@ -22,13 +22,20 @@ 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 canonical markdown record with frontmatter and these sections:
26
- - `## Briefing`
27
- - `## Key Decisions`
28
- - `## Current State`
29
- - `## Open Threads`
30
- - `## Next Steps`
31
- - `## Entry Points`
25
+ 4. Classify the content before drafting:
26
+ - **Supplied content**: the user pasted substantial prose, supplied a
27
+ document, or approved exact wording. Preserve wording, order, headings,
28
+ lists, code, and links. Add only capture frontmatter with
29
+ `content_mode: supplied`. Do not create a composed JSON or attach session
30
+ artifacts unless the user asks.
31
+ - **Generated content**: the user supplied only a topic or fragmentary
32
+ notes. Draft the canonical markdown record with these sections:
33
+ - `## Briefing`
34
+ - `## Key Decisions`
35
+ - `## Current State`
36
+ - `## Open Threads`
37
+ - `## Next Steps`
38
+ - `## Entry Points`
32
39
  Use this metadata contract even when the user supplied only prose:
33
40
 
34
41
  ```markdown
@@ -41,12 +48,22 @@ addressed_to: <recipient, if any>
41
48
  date: YYYY-MM-DD
42
49
  topic: <topic>
43
50
  intent: <action | feedback | fyi>
51
+ content_mode: <supplied | generated>
44
52
  claim: <one-line claim>
45
53
  ask: <what the receiver should do>
46
54
  ---
47
55
  ```
48
56
 
49
- 5. Compose the HTML view from that record. Markdown is canonical; the JSON is
57
+ 5. Build the preview. For supplied content, render the Markdown directly
58
+ through the native handoff renderer and verify fidelity:
59
+
60
+ ```bash
61
+ node packages/egregore-artifacts/bin/cli.js handoff "$BODY_FILE" \
62
+ --verify-fidelity
63
+ ```
64
+
65
+ For generated content, compose the HTML view from that record. Markdown is
66
+ canonical; the JSON is
50
67
  only a presentation map. Give every JSON section a stable `id` and add a
51
68
  top-level `sourceMap` from every authored `##` heading to the section that
52
69
  contains its complete text:
@@ -76,14 +93,16 @@ ask: <what the receiver should do>
76
93
  JSON. For command-heavy sections, keep command headings, explanatory text,
77
94
  fenced commands, and expected output verbatim.
78
95
 
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:
96
+ Render generated content with semantic coverage enabled:
81
97
 
82
98
  ```bash
83
99
  node packages/egregore-artifacts/bin/cli.js composed "$COMPOSED_FILE" \
84
100
  --source "$BODY_FILE" --verify-fidelity
85
101
  ```
86
102
 
103
+ 6. Show the actual browser HTML before publishing. This is the approval
104
+ surface—not a list of component names.
105
+
87
106
  Stop for confirmation after the browser preview. If validation fails, fix
88
107
  the mapping/content and render again. Skip confirmation only when the user
89
108
  explicitly said to send without preview.
@@ -93,9 +112,12 @@ node packages/egregore-artifacts/bin/cli.js composed "$COMPOSED_FILE" \
93
112
  ```bash
94
113
  TMPDIR="$(mktemp -d -t codex-handoff-XXXXXX)"
95
114
  TMPDIR="$TMPDIR" bash bin/capture-run.sh --mode addressed --author "$AUTHOR" \
96
- --topic "$TOPIC" ${RECIPIENT_ARG} --composed "$COMPOSED_FILE" < "$BODY_FILE"
115
+ --topic "$TOPIC" ${RECIPIENT_ARG} --content-mode "$CONTENT_MODE" \
116
+ ${COMPOSED_ARG} < "$BODY_FILE"
97
117
  ```
98
118
 
119
+ Use `COMPOSED_ARG=(--composed "$COMPOSED_FILE")` only for generated content.
120
+
99
121
  Use `--recipient "$RECIPIENT"` only when a clear teammate or future-self
100
122
  recipient is known. Do not pass `--no-publish` unless the user explicitly
101
123
  asked for a dry run. The runner may plan a notification, but never dispatches.
@@ -132,6 +154,8 @@ asks for a summary.
132
154
  - Do not use Claude Code commands.
133
155
  - Keep the briefing concise and operational; include exact files, branches,
134
156
  commands, open questions, and verification state when relevant.
157
+ - Supplied content is authoritative. Do not replace it with a session summary,
158
+ presentation JSON, related artifacts, or repo state.
135
159
  - The artifact is a view of the handoff, never a summary of it. Publishing
136
160
  must fail when any non-empty authored section is unmapped or loses content.
137
161
  - Structured UX parity is required: the rendered card is the acknowledgment.
@@ -42,7 +42,42 @@ esac
42
42
  - Stop with "everything is already saved" only when there are no repo or
43
43
  memory changes and either the branch has no commits to integrate
44
44
  (`AHEAD=0`) or an open integration PR already exists.
45
- 4. Synthesize:
45
+ 4. Run the skill distribution checkpoint before any commit or push when
46
+ `capability-distribution.json` exists:
47
+
48
+ ```bash
49
+ node bin/capability-distribution.mjs validate --root .
50
+ node bin/capability-distribution.mjs changes --root . --base "origin/$BASE" --json
51
+ ```
52
+
53
+ Inspect the diff as well as the receipt. Ask only when this work introduces
54
+ a new or unclassified Egregore skill, or changes a skill's placement without
55
+ an explicit user decision in the current conversation. Ordinary edits to an
56
+ already classified skill do not prompt. Packages, APIs, sites, workers,
57
+ infrastructure, and schemas remain outside this checkpoint.
58
+
59
+ Show the tracker before asking:
60
+
61
+ `artifacts/capability-pipeline.html`
62
+
63
+ Ask once using structured Codex question tooling when available. Otherwise
64
+ render the compact numbered question and wait:
65
+
66
+ ```text
67
+ Where should {name} live?
68
+ 1. OSS + Connect — part of the open runtime; Connect inherits it
69
+ 2. Connect only — delivered only to authenticated Connect users
70
+ 3. Curve Labs / Egregore — used only in this development instance
71
+ ```
72
+
73
+ Record every new skill as `queued` under the selected placement. Availability
74
+ is changed separately through the tracker after review. Group skills into one
75
+ checkpoint only when they share the same placement. Apply the answer to
76
+ `capability-distribution.json`, regenerate `skill-distribution.json` and both
77
+ artifacts, then validate again. Rebuild runtime packs only when an available
78
+ skill changes placement. If the user already made this exact decision during
79
+ the current work, do not ask twice.
80
+ 5. Synthesize:
46
81
  - a short topic from the work,
47
82
  - a clear commit message,
48
83
  - a one-line user-facing scope summary,
@@ -50,7 +85,7 @@ esac
50
85
  `## What` (1–4 bullets), `## Why` (1–3 sentences), and
51
86
  `## Verification` (how the change was checked — required when the
52
87
  diff touches non-markdown files; be honest if unverified).
53
- 5. If the scope is ambiguous or includes unrelated changes, ask for one compact
88
+ 6. If the scope is ambiguous or includes unrelated changes, ask for one compact
54
89
  confirmation. Use structured Codex question tooling when available;
55
90
  otherwise render:
56
91
 
@@ -61,7 +96,7 @@ Save these changes?
61
96
  Other:
62
97
  ```
63
98
 
64
- 6. Run the bridge command:
99
+ 7. Run the bridge command:
65
100
 
66
101
  ```bash
67
102
  bin/agent.sh save --message "$MESSAGE" --topic "$TOPIC" --pr-body "$PR_BODY"
@@ -72,7 +107,7 @@ commit repo changes, push, and create or reuse a pull request when available.
72
107
  Always pass the synthesized `--pr-body` — the bridge only auto-generates a
73
108
  skeleton body as a last resort, and the CI `pr-format` check gates every PR.
74
109
 
75
- 7. Parse the output and report only useful user information:
110
+ 8. Parse the output and report only useful user information:
76
111
  - branch name,
77
112
  - whether a commit was created,
78
113
  - whether push succeeded,
@@ -29,13 +29,13 @@
29
29
  {
30
30
  "section": "On Launch — MANDATORY FIRST ACTION",
31
31
  "action": "replace",
32
- "sourceHash": "d2f6595756c8",
32
+ "sourceHash": "3def4421213a",
33
33
  "note": "Claude SessionStart hook does not run on Codex; the egregore launcher renders the card via bin/codex-session-start.sh before the session. Greeting-replay instruction dropped (card is already on screen)."
34
34
  },
35
35
  {
36
36
  "section": "After Greeting — BRANCH ON FIRST RESPONSE",
37
37
  "action": "replace",
38
- "sourceHash": "f3bce52b9ade",
38
+ "sourceHash": "88410d7c6c00",
39
39
  "note": "EnterWorktree -> bin/agent.sh branch with git checkout fallback; AskUserQuestion -> numbered options in plain text; branch-guard mapped to .codex/hooks/branch-guard.js with instruction fallback; plan-mode note dropped (no plan mode on Codex); /branch + /onboarding -> skill tokens. Handoff claiming and repoState auto-checkout kept verbatim (pure git)."
40
40
  },
41
41
  {
@@ -58,7 +58,13 @@ integration branch.
58
58
 
59
59
  1. Derive a topic slug from what the user said (kebab-case, 2–4 words)
60
60
  2. Run `bin/agent.sh branch --topic "<topic>"` — it resolves the configured base and creates a work branch from `origin/{base}` in a task worktree (`dev/{author}/{slug}`, or `feature/{slug}` / `bugfix/{slug}` when the topic reads as a feature or fix). Continue all file work from the printed path.
61
- 3. Confirm: `On {branch} (worktree).`
61
+ 3. Give a value-first workspace receipt. Do not lead with Git terminology:
62
+
63
+ `Your stable project is protected. I’m working in a separate workspace for **{topic}**, where changes stay isolated, reviewable, and reversible.`
64
+
65
+ Then expose the implementation as secondary detail:
66
+
67
+ `Workspace: {branch} (worktree).`
62
68
 
63
69
  **Fallback:** If `bin/agent.sh branch` fails, resolve `{base}` with
64
70
  `_get_base_branch`, then use
@@ -66,6 +72,17 @@ integration branch.
66
72
 
67
73
  4. Update graph (fire-and-forget): `bash bin/graph-op.sh set-topic "$(cat .egregore-session-id 2>/dev/null)" "topic from slug" "dev/author/slug" 2>/dev/null &`
68
74
 
75
+ ### Starting-work UX contract
76
+
77
+ Before execution begins, make Egregore's useful structure legible without turning every task start into a tutorial:
78
+
79
+ - **Workspace** — use the value-first receipt above for a newly created workspace or topic pivot. If already on the appropriate working branch, do not repeat it.
80
+ - **Context** — when organizational retrieval materially informs the work, keep the required Egregore Retrieval Beat and then add one compact result receipt: `↳ Context restored: {decision, handoff, or prior work} · {source/date}`. Never claim context was restored when retrieval found nothing useful.
81
+ - **Assumptions** — surface only consequential assumptions that could change the implementation. Use `Assumption: {assumption} — based on {evidence}.` Add a correction path when cheap; do not narrate obvious operational choices.
82
+ - **Transition** — once workspace, context, and assumptions are settled, state the outcome you are starting toward in one short sentence and begin.
83
+
84
+ The intended sequence is: **intent → safe workspace → relevant context → consequential assumptions → execution**. Keep technical identifiers available but subordinate them to user value.
85
+
69
86
  ### Handoff claiming
70
87
 
71
88
  If `addressed_to_user` handoffs exist and the user is picking one up, create the IMPLEMENTS link after branch creation:
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "generatedBy": "bin/pi-render-spec.mjs",
3
3
  "source": "AGENTS.md generated Codex spec",
4
- "sourceHash": "dc445470f616",
5
- "outputHash": "147fd1b54289",
4
+ "sourceHash": "6247d8070669",
5
+ "outputHash": "117a6f3cafc3",
6
6
  "adaptations": [
7
7
  "Pi project trust and APPEND_SYSTEM delivery",
8
8
  "Pi tool_call branch gate",
@@ -132,7 +132,13 @@ integration branch.
132
132
 
133
133
  1. Derive a topic slug from what the user said (kebab-case, 2–4 words)
134
134
  2. Run `bin/agent.sh branch --topic "<topic>"` — it resolves the configured base and creates a work branch from `origin/{base}` in a task worktree (`dev/{author}/{slug}`, or `feature/{slug}` / `bugfix/{slug}` when the topic reads as a feature or fix). Continue all file work from the printed path.
135
- 3. Confirm: `On {branch} (worktree).`
135
+ 3. Give a value-first workspace receipt. Do not lead with Git terminology:
136
+
137
+ `Your stable project is protected. I’m working in a separate workspace for **{topic}**, where changes stay isolated, reviewable, and reversible.`
138
+
139
+ Then expose the implementation as secondary detail:
140
+
141
+ `Workspace: {branch} (worktree).`
136
142
 
137
143
  **Fallback:** If `bin/agent.sh branch` fails, resolve `{base}` with
138
144
  `_get_base_branch`, then use
@@ -140,6 +146,17 @@ integration branch.
140
146
 
141
147
  4. Update graph (fire-and-forget): `bash bin/graph-op.sh set-topic "$(cat .egregore-session-id 2>/dev/null)" "topic from slug" "dev/author/slug" 2>/dev/null &`
142
148
 
149
+ ### Starting-work UX contract
150
+
151
+ Before execution begins, make Egregore's useful structure legible without turning every task start into a tutorial:
152
+
153
+ - **Workspace** — use the value-first receipt above for a newly created workspace or topic pivot. If already on the appropriate working branch, do not repeat it.
154
+ - **Context** — when organizational retrieval materially informs the work, keep the required Egregore Retrieval Beat and then add one compact result receipt: `↳ Context restored: {decision, handoff, or prior work} · {source/date}`. Never claim context was restored when retrieval found nothing useful.
155
+ - **Assumptions** — surface only consequential assumptions that could change the implementation. Use `Assumption: {assumption} — based on {evidence}.` Add a correction path when cheap; do not narrate obvious operational choices.
156
+ - **Transition** — once workspace, context, and assumptions are settled, state the outcome you are starting toward in one short sentence and begin.
157
+
158
+ The intended sequence is: **intent → safe workspace → relevant context → consequential assumptions → execution**. Keep technical identifiers available but subordinate them to user value.
159
+
143
160
  ### Handoff claiming
144
161
 
145
162
  If `addressed_to_user` handoffs exist and the user is picking one up, create the IMPLEMENTS link after branch creation:
@@ -506,7 +506,7 @@ cmd_wrap() {
506
506
  cmd_handoff() {
507
507
  ensure_memory
508
508
  local from="" to="" topic="" body="" body_file="" project="" intent="action"
509
- local no_publish=0 no_notify=0 json=0
509
+ local no_publish=0 no_notify=0 json=0 body_supplied=0 content_mode="generated"
510
510
  NO_PUSH=0
511
511
 
512
512
  while [ $# -gt 0 ]; do
@@ -536,12 +536,16 @@ cmd_handoff() {
536
536
  if [ -n "$body_file" ]; then
537
537
  [ -f "$body_file" ] || die "body file not found: $body_file"
538
538
  body="$(cat "$body_file")"
539
+ body_supplied=1
539
540
  elif [ -z "$body" ] && [ ! -t 0 ]; then
540
541
  body="$(cat)"
542
+ [ -n "$body" ] && body_supplied=1
543
+ elif [ -n "$body" ]; then
544
+ body_supplied=1
541
545
  fi
542
- [ -n "$body" ] || body="No briefing provided."
546
+ [ "$body_supplied" = "1" ] && content_mode="supplied"
543
547
 
544
- local today tmpd result result_out rel project_line stdout_file
548
+ local today tmpd result result_out rel stdout_file
545
549
  local handoff_args=()
546
550
  today="$(date +%Y-%m-%d)"
547
551
  tmpd="$(mktemp -d -t egregore-agent-handoff-XXXXXX)"
@@ -549,24 +553,34 @@ cmd_handoff() {
549
553
  result_out="$(mktemp -t egregore-agent-handoff-result-XXXXXX.json)"
550
554
  stdout_file="$tmpd/stdout"
551
555
 
552
- project_line=""
553
- [ -n "$project" ] && project_line="**Project**: $project"
554
556
  [ -n "$to" ] && handoff_args+=(--recipient "$to")
555
557
  [ -n "$project" ] && handoff_args+=(--project "$project")
556
- handoff_args+=(--intent "$intent")
558
+ handoff_args+=(--intent "$intent" --content-mode "$content_mode")
559
+ [ "$content_mode" = "generated" ] && handoff_args+=(--include-session-artifacts)
557
560
  [ "$NO_PUSH" = "1" ] && handoff_args+=(--no-push)
558
561
  [ "$no_publish" = "1" ] && handoff_args+=(--no-publish)
559
562
  [ "$no_notify" = "1" ] && handoff_args+=(--no-notify)
560
563
 
561
564
  {
562
- printf '# Handoff: %s\n\n' "$topic"
563
- printf '**Date**: %s\n' "$today"
564
- printf '**Author**: %s\n' "$from"
565
- [ -n "$to" ] && printf '**To**: %s\n' "$to"
566
- printf '**Intent**: %s\n' "$intent"
567
- [ -n "$project_line" ] && printf '%s\n' "$project_line"
568
- printf '\n## Briefing\n\n%s\n\n' "$body"
569
- printf '## Next Steps\n\n- Continue from this handoff using the shared Egregore memory protocol.\n'
565
+ printf '%s\n' '---'
566
+ printf 'capture_schema: egregore-capture/v1\n'
567
+ printf 'capture_mode: addressed\n'
568
+ printf 'kind: addressed\n'
569
+ printf 'content_mode: %s\n' "$content_mode"
570
+ printf 'from: %s\n' "$(yaml_str "$from")"
571
+ [ -n "$to" ] && printf 'addressed_to: %s\n' "$(yaml_str "$to")"
572
+ printf 'date: %s\n' "$today"
573
+ printf 'topic: %s\n' "$(yaml_str "$topic")"
574
+ printf 'intent: %s\n' "$intent"
575
+ [ -n "$project" ] && printf 'project: %s\n' "$(yaml_str "$project")"
576
+ printf '%s\n\n' '---'
577
+ if [ "$content_mode" = "supplied" ]; then
578
+ printf '%s\n' "$body"
579
+ else
580
+ printf '# Handoff: %s\n\n' "$topic"
581
+ printf '## Briefing\n\nNo briefing was supplied.\n\n'
582
+ printf '## Next Steps\n\n1. Continue from this handoff using the shared Egregore memory protocol.\n'
583
+ fi
570
584
  } | TMPDIR="$tmpd" bash "$SCRIPT_DIR/bin/capture-run.sh" \
571
585
  --mode addressed \
572
586
  --author "$from" \
@@ -387,26 +387,57 @@ Co-Authored-By: Egregore Attendant <noreply@egregore.xyz>" 2>/dev/null
387
387
  WARM_MIN="${ATTENDANT_WARM_MIN:-5}"
388
388
  WARM_MARKER="$STATE_DIR/$KEY.warm-ts"
389
389
 
390
+ # Non-secret fingerprint of the effective graph endpoint + credential, matching
391
+ # bin/graph.sh's resolution order exactly (process env → .env → committed
392
+ # api_url). Scopes the graph cache and the snapshot provenance to the tenant
393
+ # answers actually come from, not just the tenant the committed config names.
394
+ _effective_scope() {
395
+ local url key
396
+ url="${EGREGORE_API_URL:-$(grep '^EGREGORE_API_URL=' "$MAIN_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
397
+ key="${EGREGORE_API_KEY:-$(grep '^EGREGORE_API_KEY=' "$MAIN_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
398
+ [ -n "$url" ] || url=$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)
399
+ echo -n "${url}|${key}" | cksum | cut -d' ' -f1
400
+ }
401
+
390
402
  _warm() {
391
403
  local now last
392
404
  now=$(date +%s)
393
405
  last=$(cat "$WARM_MARKER" 2>/dev/null || echo 0)
406
+ # Numeric + not-future guard: a malformed legacy marker must not abort
407
+ # warming, and a future timestamp must not suppress it indefinitely.
408
+ case "$last" in ''|*[!0-9]*) last=0 ;; esac
409
+ [ "$last" -gt "$now" ] && last=0
394
410
  [ $(( now - last )) -lt $(( WARM_MIN * 60 )) ] && return 0
395
411
  [ "$DRY_RUN" = "1" ] && { log "DRY warm"; return 0; }
396
412
 
397
- git -C "$MAIN_DIR" fetch origin --quiet 2>/dev/null &
413
+ # --prune keeps remote-tracking refs honest so the launch path can answer
414
+ # "does my branch still exist on origin?" from local refs (worktree stale
415
+ # check in git-sync.sh) instead of a ~2s ls-remote round-trip.
416
+ local _origin_pid _origin_ok="false"
417
+ git -C "$MAIN_DIR" fetch origin --prune --quiet 2>/dev/null &
418
+ _origin_pid=$!
398
419
  [ -d "$MEMORY_DIR/.git" ] || [ -f "$MEMORY_DIR/.git" ] && git -C "$MEMORY_DIR" fetch origin --quiet 2>/dev/null &
399
420
  local repo
400
421
  jq -r '(.repos[]? // empty) | if type == "object" then .name else . end' "$CONFIG" 2>/dev/null | while read -r repo; do
401
422
  [ -d "$MAIN_DIR/../$repo/.git" ] && git -C "$MAIN_DIR/../$repo" fetch origin --quiet 2>/dev/null
402
423
  done &
424
+ wait "$_origin_pid" 2>/dev/null && _origin_ok="true"
403
425
  wait
404
- date +%s > "$WARM_MARKER"
426
+ # Stamp the marker ONLY when the origin fetch actually succeeded, and stamp
427
+ # it atomically (tmp+mv): the launch path treats a fresh marker as "local
428
+ # refs are authoritative", so a failed fetch must not silence live fetches
429
+ # for ten minutes, and a reader must never catch a half-written timestamp.
430
+ if [ "$_origin_ok" = "true" ]; then
431
+ date +%s > "$WARM_MARKER.tmp" && mv "$WARM_MARKER.tmp" "$WARM_MARKER"
432
+ fi
405
433
 
406
434
  # Replay graph cache entries touched in the last 24h (connected mode only —
407
435
  # in local mode graph.sh exits instantly and no cache exists).
408
436
  local cache_dir f q p
409
- cache_dir="$HOME/.egregore/graph-cache/$(echo -n "$MAIN_DIR" | cksum | cut -d' ' -f1)"
437
+ # Key must match graph.sh's _CACHE_DIR derivation exactly: repo root plus
438
+ # the effective endpoint/credential fingerprint, so entries never cross
439
+ # tenants when the same checkout is repointed at a different org/API.
440
+ cache_dir="$HOME/.egregore/graph-cache/$(echo -n "${MAIN_DIR}|$(_effective_scope)" | cksum | cut -d' ' -f1)"
410
441
  if [ -d "$cache_dir" ]; then
411
442
  for f in $(find "$cache_dir" -name '*.json' -mmin -1440 2>/dev/null); do
412
443
  q=$(jq -r '.query // empty' "$f" 2>/dev/null)
@@ -414,7 +445,90 @@ _warm() {
414
445
  [ -n "$q" ] && EGREGORE_GRAPH_CACHE_TTL=1 bash "$MAIN_DIR/bin/graph.sh" query "$q" "$p" >/dev/null 2>&1 || true
415
446
  done
416
447
  fi
417
- log "warm: refs fetched, graph cache replayed"
448
+ _prebake_context
449
+ log "warm: refs fetched, graph cache replayed, context baked"
450
+ }
451
+
452
+ # --- Pre-bake the session-start context snapshot -----------------------------
453
+ # Runs the same context gather session-start would (bin/lib/context.sh) and
454
+ # stores the result as a single tar at $STATE_DIR/$KEY.ctx.tar. On boot,
455
+ # context.sh reuses the baked graph-bound results (todos, lifecycle, pulse,
456
+ # metrics, service health) when the bake is <15min old and its provenance
457
+ # matches (author, mode, config), re-running the local collectors live —
458
+ # taking the greeting's graph work off the launch path entirely.
459
+ #
460
+ # One tar + atomic rename, not a directory swap: readers open either the whole
461
+ # old bake or the whole new one, never a half-replaced directory. The tmp name
462
+ # carries $$ so two attendants racing (possible before the run-loop self-check
463
+ # converges them) never write through each other's file.
464
+ _prebake_context() {
465
+ [ -f "$MAIN_DIR/bin/lib/context.sh" ] || return 0
466
+ local snap="$STATE_DIR/$KEY.ctx.tar"
467
+ (
468
+ set +u +e
469
+ unset CTX_SEED_TAR CTX_SEED_DIR # never let a bake seed itself from an old bake
470
+ SCRIPT_DIR="$MAIN_DIR"
471
+ CONFIG="$MAIN_DIR/egregore.json"
472
+ STATE_FILE="$MAIN_DIR/.egregore-state.json"
473
+ ENV_FILE="$MAIN_DIR/.env"
474
+ AUTHOR=$(jq -r '.github_username // empty' "$STATE_FILE" 2>/dev/null)
475
+ [ -n "$AUTHOR" ] || exit 0
476
+ # Canonical mode semantics (config.sh _detect_mode): local when the config
477
+ # says so OR api_url is absent — legacy OSS configs carry no mode key, and
478
+ # a bare '.mode == local' test would bake failed connected health for them.
479
+ LOCAL_MODE="false"
480
+ source "$MAIN_DIR/bin/lib/config.sh" 2>/dev/null || true
481
+ if type _detect_mode >/dev/null 2>&1; then
482
+ [ "$(_detect_mode)" = "local" ] && LOCAL_MODE="true"
483
+ elif ! jq -e '(.mode // "") != "local" and ((.api_url // "") | length > 0)' "$CONFIG" >/dev/null 2>&1; then
484
+ LOCAL_MODE="true"
485
+ fi
486
+ # Capture ALL provenance BEFORE gathering, then publish only if it is
487
+ # unchanged AFTER — a user, config, or credential switch mid-gather would
488
+ # otherwise produce mixed-tenant/mixed-user data labeled with whatever the
489
+ # final values happened to be (TOCTOU).
490
+ _PRE_CONFIG=$(cksum "$CONFIG" 2>/dev/null | cut -d' ' -f1)
491
+ _PRE_SCOPE=$(_effective_scope)
492
+ _PRE_SCHEMA=$(cksum "$MAIN_DIR/bin/lib/context.sh" 2>/dev/null | cut -d' ' -f1)
493
+ # PIN the effective credentials for the entire gather: graph.sh re-reads
494
+ # env/.env in every subprocess, so without pinning, an A→B→A .env flip
495
+ # during the gather could fetch B-tenant data that the pre/post scope
496
+ # samples cannot see. With the pin, every graph call in this bake uses
497
+ # the credentials the provenance stamp names. Empty values stay empty
498
+ # (local mode) — graph.sh treats them as unset.
499
+ export EGREGORE_API_URL="${EGREGORE_API_URL:-$(grep '^EGREGORE_API_URL=' "$MAIN_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
500
+ export EGREGORE_API_KEY="${EGREGORE_API_KEY:-$(grep '^EGREGORE_API_KEY=' "$MAIN_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
501
+ export EGREGORE_GRAPH_CACHE_TTL=600
502
+ source "$MAIN_DIR/bin/lib/context.sh"
503
+ [ -n "$CTX_DIR" ] && [ -d "$CTX_DIR" ] || exit 0
504
+ _POST_AUTHOR=$(jq -r '.github_username // empty' "$STATE_FILE" 2>/dev/null)
505
+ _POST_CONFIG=$(cksum "$CONFIG" 2>/dev/null | cut -d' ' -f1)
506
+ _POST_SCOPE=$(_effective_scope)
507
+ _POST_SCHEMA=$(cksum "$MAIN_DIR/bin/lib/context.sh" 2>/dev/null | cut -d' ' -f1)
508
+ if [ "$_POST_AUTHOR" != "$AUTHOR" ] || [ "$_POST_CONFIG" != "$_PRE_CONFIG" ] \
509
+ || [ "$_POST_SCOPE" != "$_PRE_SCOPE" ] || [ "$_POST_SCHEMA" != "$_PRE_SCHEMA" ]; then
510
+ rm -rf "$CTX_DIR"
511
+ exit 0
512
+ fi
513
+ # Provenance the reader validates before trusting the bake.
514
+ date +%s > "$CTX_DIR/.baked-ts"
515
+ printf '%s\n' "$AUTHOR" > "$CTX_DIR/.baked-author"
516
+ printf '%s\n' "$LOCAL_MODE" > "$CTX_DIR/.baked-local-mode"
517
+ printf '%s\n' "$_PRE_CONFIG" > "$CTX_DIR/.baked-config"
518
+ # Effective endpoint/credential + the context.sh revision that produced
519
+ # this bake: a seed from a different tenant or a different collector
520
+ # schema must never be trusted.
521
+ printf '%s\n' "$_PRE_SCOPE" > "$CTX_DIR/.baked-scope"
522
+ printf '%s\n' "$_PRE_SCHEMA" > "$CTX_DIR/.baked-schema"
523
+ if (cd "$CTX_DIR" && tar -cf "$snap.tmp.$$" .) 2>/dev/null; then
524
+ mv -f "$snap.tmp.$$" "$snap" 2>/dev/null || rm -f "$snap.tmp.$$" 2>/dev/null
525
+ else
526
+ rm -f "$snap.tmp.$$" 2>/dev/null
527
+ fi
528
+ rm -rf "$CTX_DIR"
529
+ ) >/dev/null 2>&1 || true
530
+ # Clean up the directory-format snapshot from the previous iteration
531
+ rm -rf "$STATE_DIR/$KEY.ctx" 2>/dev/null || true
418
532
  }
419
533
 
420
534
  # --- Handoff lifecycle: recurring close / nudge / expiry --------------------
@@ -482,6 +596,11 @@ cmd_run() {
482
596
  log "attendant up (pid $$, stale=${STALE_MIN}m, $MAIN_DIR)"
483
597
  while true; do
484
598
  [ -d "$MAIN_DIR" ] || { log "project gone — exiting"; break; }
599
+ # Duplicate-daemon convergence: cmd_ensure's check-then-spawn is lockless,
600
+ # so two concurrent cold starts can both launch a run loop. Whoever owns
601
+ # the pidfile keeps running; the other notices within one cycle and exits
602
+ # without touching shared state.
603
+ [ "$(cat "$PIDFILE" 2>/dev/null)" = "$$" ] || { log "superseded (pid $$) — exiting"; exit 0; }
485
604
  cmd_sweep
486
605
  sleep 60
487
606
  done
@@ -498,8 +617,52 @@ cmd_ensure() {
498
617
  kill "$(cat "$PIDFILE")" 2>/dev/null || true
499
618
  rm -f "$PIDFILE"
500
619
  fi
501
- nohup bash "$MAIN_DIR/bin/attendant.sh" run >> "$LOGFILE" 2>&1 &
502
- disown 2>/dev/null || true
620
+ # Atomic spawn gate: the pidfile check above is check-then-spawn, so two
621
+ # concurrent cold starts could both reach here and launch two daemons that
622
+ # overlap for a full cycle (lifecycle jobs, ripcord, auto-push). mkdir is
623
+ # the portable atomic primitive; a crashed spawner's stale lock breaks
624
+ # after 5 minutes. Losing the race means someone else is spawning — done.
625
+ local _spawn_lock="$STATE_DIR/$KEY.spawn-lock"
626
+ if ! mkdir "$_spawn_lock" 2>/dev/null; then
627
+ local _lock_age
628
+ _lock_age=$(( $(date +%s) - $(stat -f %m "$_spawn_lock" 2>/dev/null || stat -c %Y "$_spawn_lock" 2>/dev/null || echo 0) ))
629
+ # Stale reap by atomic rename, never rmdir+mkdir: two reapers doing
630
+ # remove-then-recreate can alternately delete each other's FRESH lock and
631
+ # both spawn. Exactly one mv succeeds; losing the mv or the follow-up
632
+ # mkdir means someone else owns a fresh lock — back off.
633
+ if [ "$_lock_age" -gt 300 ] && mv "$_spawn_lock" "$_spawn_lock.reap.$$" 2>/dev/null; then
634
+ rm -rf "$_spawn_lock.reap.$$" 2>/dev/null
635
+ mkdir "$_spawn_lock" 2>/dev/null || exit 0
636
+ else
637
+ exit 0
638
+ fi
639
+ fi
640
+ # Re-check under the lock — the racing ensure may already have spawned.
641
+ if ! { [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null; }; then
642
+ nohup bash "$MAIN_DIR/bin/attendant.sh" run >> "$LOGFILE" 2>&1 &
643
+ disown 2>/dev/null || true
644
+ # Hold the lock until the child's ownership is durably visible (it writes
645
+ # its pidfile first thing in cmd_run). Releasing earlier reopens the race:
646
+ # another ensure passes the pidfile re-check before the child registers
647
+ # and spawns a second daemon whose first sweep overlaps this one.
648
+ local _i _spawned="false"
649
+ for _i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25; do
650
+ if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null; then
651
+ _spawned="true"
652
+ break
653
+ fi
654
+ sleep 0.2
655
+ done
656
+ if [ "$_spawned" != "true" ]; then
657
+ # Child still hasn't registered: KEEP the lock rather than reopen the
658
+ # spawn window — a merely-delayed child may yet come up, and the stale
659
+ # reaper above frees the lock in 5 minutes if it never does. Fail
660
+ # closed: no attendant for a few minutes beats two attendants sweeping.
661
+ log "ensure: spawned child slow to register — holding spawn lock (pid $$)"
662
+ exit 0
663
+ fi
664
+ fi
665
+ rmdir "$_spawn_lock" 2>/dev/null || true
503
666
  }
504
667
 
505
668
  cmd_status() {
@@ -186,7 +186,13 @@ integration branch.
186
186
 
187
187
  1. Derive a topic slug from what the user said (kebab-case, 2–4 words)
188
188
  2. Run \`bin/agent.sh branch --topic "<topic>"\` — it resolves the configured base and creates a work branch from \`origin/{base}\` in a task worktree (\`dev/{author}/{slug}\`, or \`feature/{slug}\` / \`bugfix/{slug}\` when the topic reads as a feature or fix). Continue all file work from the printed path.
189
- 3. Confirm: \`On {branch} (worktree).\`
189
+ 3. Give a value-first workspace receipt. Do not lead with Git terminology:
190
+
191
+ \`Your stable project is protected. I’m working in a separate workspace for **{topic}**, where changes stay isolated, reviewable, and reversible.\`
192
+
193
+ Then expose the implementation as secondary detail:
194
+
195
+ \`Workspace: {branch} (worktree).\`
190
196
 
191
197
  **Fallback:** If \`bin/agent.sh branch\` fails, resolve \`{base}\` with
192
198
  \`_get_base_branch\`, then use
@@ -194,6 +200,17 @@ integration branch.
194
200
 
195
201
  4. Update graph (fire-and-forget): \`bash bin/graph-op.sh set-topic "$(cat .egregore-session-id 2>/dev/null)" "topic from slug" "dev/author/slug" 2>/dev/null &\`
196
202
 
203
+ ### Starting-work UX contract
204
+
205
+ Before execution begins, make Egregore's useful structure legible without turning every task start into a tutorial:
206
+
207
+ - **Workspace** — use the value-first receipt above for a newly created workspace or topic pivot. If already on the appropriate working branch, do not repeat it.
208
+ - **Context** — when organizational retrieval materially informs the work, keep the required Egregore Retrieval Beat and then add one compact result receipt: \`↳ Context restored: {decision, handoff, or prior work} · {source/date}\`. Never claim context was restored when retrieval found nothing useful.
209
+ - **Assumptions** — surface only consequential assumptions that could change the implementation. Use \`Assumption: {assumption} — based on {evidence}.\` Add a correction path when cheap; do not narrate obvious operational choices.
210
+ - **Transition** — once workspace, context, and assumptions are settled, state the outcome you are starting toward in one short sentence and begin.
211
+
212
+ The intended sequence is: **intent → safe workspace → relevant context → consequential assumptions → execution**. Keep technical identifiers available but subordinate them to user value.
213
+
197
214
  ### Handoff claiming
198
215
 
199
216
  If \`addressed_to_user\` handoffs exist and the user is picking one up, create the IMPLEMENTS link after branch creation:
@@ -68,7 +68,20 @@ if [ "$LOCAL_MODE" != "true" ]; then
68
68
  fi
69
69
 
70
70
  source "$SCRIPT_DIR/bin/lib/git-sync.sh" >/dev/null 2>/dev/null
71
+ # Warm-state parity with the Claude SessionStart path: hand context.sh the
72
+ # pre-baked snapshot and route greeting reads through the warm graph cache.
73
+ # The attendant daemon is deliberately NOT started from the Codex/Pi path —
74
+ # its dead-session detection reads Claude transcripts only, so a live Codex
75
+ # or Pi session with 12 quiet minutes would look dead and could have its
76
+ # unfinished work ripcorded/auto-pushed. Codex sessions still benefit from
77
+ # any snapshot a Claude session's attendant maintains; runtime-neutral
78
+ # liveness tracking is the prerequisite for starting the daemon here.
79
+ if [ -n "${_ATT_KEY:-}" ]; then
80
+ export CTX_SEED_TAR="${ATTENDANT_HOME:-$HOME/.egregore/attendant}/${_ATT_KEY}.ctx.tar"
81
+ fi
82
+ export EGREGORE_GRAPH_CACHE_TTL=600
71
83
  source "$SCRIPT_DIR/bin/lib/context.sh" >/dev/null 2>/dev/null
84
+ unset EGREGORE_GRAPH_CACHE_TTL CTX_SEED_TAR
72
85
  source "$SCRIPT_DIR/bin/lib/metrics.sh" >/dev/null 2>/dev/null
73
86
  trap 'rm -rf "${CTX_DIR:-}"' EXIT
74
87
 
@@ -22,6 +22,7 @@ NATIVE_SKILLS=(
22
22
  search
23
23
  view
24
24
  scroll
25
+ plate
25
26
  )
26
27
 
27
28
  STRUCTURED_UX_SKILLS=(