Skip to content

Commit a2aa8a0

Browse files
committed
feat(gbrain-sync): preamble block — privacy gate + boundary sync
scripts/resolvers/preamble/generate-brain-sync-block.ts emits bash that runs at every skill invocation: - Detects ~/.gstack-brain-remote.txt on machines without local .git and surfaces a restore-available hint (does NOT auto-run restore). - Runs gstack-brain-sync --once at skill start to drain any pending writes (and at skill end via prose instruction). - Once-per-day auto-pull (cached via .brain-last-pull) for append-only JSONL files. - Emits BRAIN_SYNC: status line every skill run. Also emits prose for the host LLM to fire the one-time privacy stop-gate (full / artifacts-only / off) when gbrain is detected and gbrain_sync_mode_prompted is false. Wired into preamble.ts composition.
1 parent f088fe9 commit a2aa8a0

2 files changed

Lines changed: 128 additions & 0 deletions

File tree

scripts/resolvers/preamble.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ import { generateWritingStyleMigration } from './preamble/generate-writing-style
3737
// Host-specific instructions
3838
import { generateBrainHealthInstruction } from './preamble/generate-brain-health-instruction';
3939

40+
// GBrain cross-machine sync (runs at skill start; end-side handled in completion-status)
41+
import { generateBrainSyncBlock } from './preamble/generate-brain-sync-block';
42+
4043
// Behavioral / voice
4144
import { generateVoiceDirective } from './preamble/generate-voice-directive';
4245

@@ -84,6 +87,7 @@ export function generatePreamble(ctx: TemplateContext): string {
8487
generateVendoringDeprecation(ctx),
8588
generateSpawnedSessionCheck(),
8689
generateBrainHealthInstruction(ctx),
90+
generateBrainSyncBlock(ctx),
8791
generateModelOverlay(ctx),
8892
generateVoiceDirective(tier),
8993
...(tier >= 2 ? [
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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

Comments
 (0)