From 7172309d44dc05998a6370e83d679a300841df61 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sat, 4 Apr 2026 22:10:12 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20bidirectional=20learnings=20bridge=20?= =?UTF-8?q?=E2=80=94=20gstack=20writes=20OpenClaw=20memory=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When ~/.openclaw/ exists, gstack-learnings-log writes a parallel markdown copy to ~/.openclaw/workspace/memory/gstack-{slug}.md. OpenClaw agents can read these directly as memory entries. gstack-learnings-search reads back from OpenClaw memory files (*.md in ~/.openclaw/workspace/memory/) and includes them in unified search results alongside native JSONL learnings. Cross-pollination. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/gstack-learnings-log | 21 +++++++++++++++++++++ bin/gstack-learnings-search | 27 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/bin/gstack-learnings-log b/bin/gstack-learnings-log index e63c14cb..336db336 100755 --- a/bin/gstack-learnings-log +++ b/bin/gstack-learnings-log @@ -28,3 +28,24 @@ if ! printf '%s' "$INPUT" | bun -e "const j=JSON.parse(await Bun.stdin.text()); fi echo "$INPUT" >> "$GSTACK_HOME/projects/$SLUG/learnings.jsonl" + +# OpenClaw bridge: write a parallel copy as OpenClaw-readable memory +OPENCLAW_MEM="$HOME/.openclaw/workspace/memory" +if [ -d "$HOME/.openclaw" ]; then + mkdir -p "$OPENCLAW_MEM" + MEM_FILE="$OPENCLAW_MEM/gstack-${SLUG}.md" + # Extract fields for the memory entry + ENTRY=$(printf '%s' "$INPUT" | bun -e " + const j = JSON.parse(await Bun.stdin.text()); + const ts = j.ts || new Date().toISOString(); + const date = ts.split('T')[0]; + console.log('- **[' + (j.key||'unknown') + ']** (' + (j.type||'unknown') + ', ' + date + ', confidence: ' + (j.confidence||'?') + '/10): ' + (j.insight||'')); + " 2>/dev/null) || true + if [ -n "$ENTRY" ]; then + # Create file with header if it doesn't exist + if [ ! -f "$MEM_FILE" ]; then + printf '# gstack learnings for %s\n\nAuto-synced from gstack coding sessions.\n\n' "$SLUG" > "$MEM_FILE" + fi + echo "$ENTRY" >> "$MEM_FILE" + fi +fi diff --git a/bin/gstack-learnings-search b/bin/gstack-learnings-search index 4ac187ec..0decebb4 100755 --- a/bin/gstack-learnings-search +++ b/bin/gstack-learnings-search @@ -38,7 +38,31 @@ if [ "$CROSS_PROJECT" = true ]; then done fi +# Also read OpenClaw memory files (bidirectional bridge) +OPENCLAW_MEM="$HOME/.openclaw/workspace/memory" +if [ -d "$OPENCLAW_MEM" ]; then + for f in $(find "$OPENCLAW_MEM" -name "*.md" -not -name "gstack-*" 2>/dev/null | head -10); do + # Convert markdown memory entries to JSONL for unified processing + # Only parse lines that look like learnings (start with "- ") + bun -e " + const text = await Bun.file('$f').text(); + for (const line of text.split('\n')) { + const m = line.match(/^- \\*\\*\\[([^\\]]+)\\]\\*\\*.*?\\(([^,]+),.*?confidence: (\\d+)/); + if (m) { + console.log(JSON.stringify({ + key: m[1], type: m[2].trim(), confidence: parseInt(m[3]), + insight: line.replace(/^- \\*\\*\\[[^\\]]+\\]\\*\\*[^:]*: /, ''), + source: 'openclaw', ts: new Date().toISOString() + })); + } + } + " 2>/dev/null >> /tmp/gstack-openclaw-learnings-$$.jsonl || true + done + [ -f "/tmp/gstack-openclaw-learnings-$$.jsonl" ] && FILES+=("/tmp/gstack-openclaw-learnings-$$.jsonl") +fi + if [ ${#FILES[@]} -eq 0 ]; then + rm -f /tmp/gstack-openclaw-learnings-$$.jsonl 2>/dev/null exit 0 fi @@ -129,3 +153,6 @@ for (const [t, arr] of Object.entries(byType)) { console.log(''); } " 2>/dev/null || exit 0 + +# Cleanup temp files +rm -f /tmp/gstack-openclaw-learnings-$$.jsonl 2>/dev/null || true