|
| 1 | +/** |
| 2 | + * gbrain-sync preamble block. |
| 3 | + * |
| 4 | + * Emits bash that runs at every skill invocation: |
| 5 | + * 1. If ~/.gstack-brain-remote.txt exists AND ~/.gstack/.git is missing, |
| 6 | + * surface a restore-available hint (does NOT auto-run restore). |
| 7 | + * 2. If sync is on, run `gstack-brain-sync --once` (drain + push). |
| 8 | + * 3. On first skill of the day (24h cache via .brain-last-pull): |
| 9 | + * `git fetch` + ff-only merge (JSONL merge driver handles conflicts). |
| 10 | + * 4. Emit a `BRAIN_SYNC:` status line so every skill surfaces health. |
| 11 | + * |
| 12 | + * Also emits prose instructions for the host LLM to fire a one-time privacy |
| 13 | + * stop-gate via AskUserQuestion when gbrain_sync_mode is unset and gbrain |
| 14 | + * is available on the host. |
| 15 | + * |
| 16 | + * Block emitted across all tiers. Internal bash short-circuits when feature |
| 17 | + * is disabled; cost is <5ms. |
| 18 | + * |
| 19 | + * Skill-end sync is handled by the completion-status generator via a call |
| 20 | + * to `gstack-brain-sync --discover-new` + `--once`. |
| 21 | + */ |
| 22 | +import type { TemplateContext } from '../types'; |
| 23 | + |
| 24 | +export function generateBrainSyncBlock(ctx: TemplateContext): string { |
| 25 | + const isBrainHost = ctx.host === 'gbrain' || ctx.host === 'hermes'; |
| 26 | + return `## GBrain Sync (skill start) |
| 27 | +
|
| 28 | +\`\`\`bash |
| 29 | +# gbrain-sync: drain pending writes, pull once per day. Silent no-op when |
| 30 | +# the feature isn't initialized or gbrain_sync_mode is "off". See |
| 31 | +# docs/gbrain-sync.md. |
| 32 | +
|
| 33 | +_GSTACK_HOME="\${GSTACK_HOME:-$HOME/.gstack}" |
| 34 | +_BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" |
| 35 | +_BRAIN_SYNC_BIN="${ctx.paths.binDir}/gstack-brain-sync" |
| 36 | +_BRAIN_CONFIG_BIN="${ctx.paths.binDir}/gstack-config" |
| 37 | +
|
| 38 | +_BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) |
| 39 | +
|
| 40 | +# New-machine hint: URL file present, local .git missing, sync not yet enabled. |
| 41 | +if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then |
| 42 | + _BRAIN_NEW_URL=$(head -1 "$_BRAIN_REMOTE_FILE" 2>/dev/null | tr -d '[:space:]') |
| 43 | + if [ -n "$_BRAIN_NEW_URL" ]; then |
| 44 | + echo "BRAIN_SYNC: brain repo detected: $_BRAIN_NEW_URL" |
| 45 | + echo "BRAIN_SYNC: run 'gstack-brain-restore' to pull your cross-machine memory (or 'gstack-config set gbrain_sync_mode off' to dismiss forever)" |
| 46 | + fi |
| 47 | +fi |
| 48 | +
|
| 49 | +# Active-sync path. |
| 50 | +if [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then |
| 51 | + # Once-per-day pull. |
| 52 | + _BRAIN_LAST_PULL_FILE="$_GSTACK_HOME/.brain-last-pull" |
| 53 | + _BRAIN_NOW=$(date +%s) |
| 54 | + _BRAIN_DO_PULL=1 |
| 55 | + if [ -f "$_BRAIN_LAST_PULL_FILE" ]; then |
| 56 | + _BRAIN_LAST=$(cat "$_BRAIN_LAST_PULL_FILE" 2>/dev/null || echo 0) |
| 57 | + _BRAIN_AGE=$(( _BRAIN_NOW - _BRAIN_LAST )) |
| 58 | + [ "$_BRAIN_AGE" -lt 86400 ] && _BRAIN_DO_PULL=0 |
| 59 | + fi |
| 60 | + if [ "$_BRAIN_DO_PULL" = "1" ]; then |
| 61 | + ( cd "$_GSTACK_HOME" && git fetch origin >/dev/null 2>&1 && git merge --ff-only "origin/$(git rev-parse --abbrev-ref HEAD)" >/dev/null 2>&1 ) || true |
| 62 | + echo "$_BRAIN_NOW" > "$_BRAIN_LAST_PULL_FILE" |
| 63 | + fi |
| 64 | + # Drain pending queue, push. |
| 65 | + "$_BRAIN_SYNC_BIN" --once 2>/dev/null || true |
| 66 | +fi |
| 67 | +
|
| 68 | +# Status line — always emitted, easy to grep. |
| 69 | +if [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then |
| 70 | + _BRAIN_QUEUE_DEPTH=0 |
| 71 | + [ -f "$_GSTACK_HOME/.brain-queue.jsonl" ] && _BRAIN_QUEUE_DEPTH=$(wc -l < "$_GSTACK_HOME/.brain-queue.jsonl" | tr -d ' ') |
| 72 | + _BRAIN_LAST_PUSH="never" |
| 73 | + [ -f "$_GSTACK_HOME/.brain-last-push" ] && _BRAIN_LAST_PUSH=$(cat "$_GSTACK_HOME/.brain-last-push" 2>/dev/null || echo never) |
| 74 | + echo "BRAIN_SYNC: mode=$_BRAIN_SYNC_MODE | last_push=$_BRAIN_LAST_PUSH | queue=$_BRAIN_QUEUE_DEPTH" |
| 75 | +else |
| 76 | + echo "BRAIN_SYNC: off" |
| 77 | +fi |
| 78 | +\`\`\` |
| 79 | +
|
| 80 | +${isBrainHost ? `If the bash output shows \`BRAIN_SYNC: brain repo detected\`, the user copied their remote URL file to this machine but hasn't restored yet. Offer to run \`gstack-brain-restore\` via AskUserQuestion. If the user agrees, run the command; otherwise continue without sync.` : ''} |
| 81 | +
|
| 82 | +**Privacy stop-gate (fires ONCE per machine).** |
| 83 | +
|
| 84 | +If the bash output shows \`BRAIN_SYNC: off\` AND the config value |
| 85 | +\`gbrain_sync_mode_prompted\` is \`false\` AND gbrain is detected on this host |
| 86 | +(either \`gbrain doctor --fast --json\` succeeds or the \`gbrain\` binary is in PATH), |
| 87 | +fire a one-time privacy gate via AskUserQuestion: |
| 88 | +
|
| 89 | +> gstack can publish your session memory (learnings, plans, designs, retros) to a |
| 90 | +> private GitHub repo that GBrain indexes across your machines. Higher tiers |
| 91 | +> include behavioral data (session timelines, developer profile). How much do you |
| 92 | +> want to sync? |
| 93 | +
|
| 94 | +Options: |
| 95 | +- A) Everything allowlisted (recommended — maximum cross-machine memory) |
| 96 | +- B) Only artifacts (plans, designs, retros, learnings) — skip timelines and profile |
| 97 | +- C) Decline — keep everything local |
| 98 | +
|
| 99 | +After the user answers, run (substituting the chosen value): |
| 100 | +
|
| 101 | +\`\`\`bash |
| 102 | +# Chosen mode: full | artifacts-only | off |
| 103 | +"$_BRAIN_CONFIG_BIN" set gbrain_sync_mode <choice> |
| 104 | +"$_BRAIN_CONFIG_BIN" set gbrain_sync_mode_prompted true |
| 105 | +\`\`\` |
| 106 | +
|
| 107 | +If A or B was chosen AND \`~/.gstack/.git\` doesn't exist, ask a follow-up: |
| 108 | +"Set up the GBrain sync repo now? (runs \`gstack-brain-init\`)" |
| 109 | +- A) Yes, run it now |
| 110 | +- B) Show me the command, I'll run it myself |
| 111 | +
|
| 112 | +Do not block the skill. Emit the question, continue the skill workflow. The |
| 113 | +next skill run picks up wherever this left off. |
| 114 | +
|
| 115 | +**At skill END (before the telemetry block),** run these bash commands to |
| 116 | +catch artifact writes (design docs, plans, retros) that skipped the writer |
| 117 | +shims, plus drain any still-pending queue entries: |
| 118 | +
|
| 119 | +\`\`\`bash |
| 120 | +"${ctx.paths.binDir}/gstack-brain-sync" --discover-new 2>/dev/null || true |
| 121 | +"${ctx.paths.binDir}/gstack-brain-sync" --once 2>/dev/null || true |
| 122 | +\`\`\` |
| 123 | +`; |
| 124 | +} |
0 commit comments