sync: auto-sync from GURU-5070 at 2026-06-02 20:40:54

Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-02 20:40:54
This commit is contained in:
2026-06-02 20:40:57 -07:00
parent 8e70d73ece
commit 446a6c1b1c
39 changed files with 1101 additions and 7 deletions

View File

@@ -8,6 +8,8 @@ MODE_FILE="${SCRIPT_DIR}/current-mode"
# Coord API endpoint — per-machine override in identity.json (same lookup as sync.sh).
# Falls back to the on-LAN default so existing machines keep working unchanged.
# This script is invoked by both Claude Code (via .claude/settings.json UserPromptSubmit)
# and Grok (via .grok/hooks/ UserPromptSubmit) for cross-driver coexistence.
IDENTITY_PATH=""
for candidate in "$HOME/.claude/identity.json" "${SCRIPT_DIR}/identity.json"; do
if [ -f "$candidate" ]; then

View File

@@ -0,0 +1,24 @@
# ensure-git-bash.ps1
# Ensures that the 'bash' command in the current PowerShell session resolves to
# Git for Windows / MSYS bash (required by ClaudeTools .sh scripts, vault, hooks, etc.)
# instead of the WSL stub in WindowsApps.
#
# Usage:
# . .claude/scripts/ensure-git-bash.ps1
# Or source in profile.
#
# Idempotent and safe.
$gitBin = "C:\Program Files\Git\bin"
$gitUsrBin = "C:\Program Files\Git\usr\bin"
if (Test-Path $gitBin) {
$current = (Get-Command bash -ErrorAction SilentlyContinue).Source
if ($current -notlike '*Git*bin*bash.exe') {
# Remove any existing Git entries first to avoid duplicates, then prepend
$env:Path = "$gitBin;$gitUsrBin;" + ($env:Path -replace [regex]::Escape("$gitBin;"), '' -replace [regex]::Escape("$gitUsrBin;"), '')
Write-Verbose "[ensure-git-bash] Remapped 'bash' to Git/MSYS bash"
}
} else {
Write-Warning "[ensure-git-bash] Git Bash not found at expected path $gitBin"
}

View File

@@ -0,0 +1,230 @@
#!/usr/bin/env python3
"""
recover_grok_session.py -- reconstruct a ClaudeTools session log from a Grok session.
Grok stores per-workspace sessions under:
~/.grok/sessions/<slug>/ (slug = URL-encoded path, e.g. D%3A%5Cclaudetools)
~/.grok/sessions/<slug>/<uuid>/ (one dir per actual session)
- chat_history.jsonl, events.jsonl, updates.jsonl
- terminal/ (call-*.log files with exact command + output for run_terminal_cmd etc.)
- summary.json, system_prompt.txt, prompt_context.json, etc.
This is the Grok counterpart to recover_session.py (which targets Claude Code's
~/.claude/projects/<slug>/<uuid>.jsonl transcripts).
The goal is the same: produce a markdown session log in the format expected by
.claude/commands/save.md (## User, ## Summary, Key Decisions, Commands & Outputs,
File Changes, etc.) so that /save, wiki-compile, memory, etc. work uniformly
regardless of which driver (Claude Code or Grok) was used for the session.
Status: skeleton / MVP. Extracts terminal commands + outputs (the most valuable
verbatim evidence) and basic session metadata. Full chat reconstruction + Ollama
prose drafting (like the Claude recover) can be added by sharing code with
recover_session.py.
Usage (from repo root):
python .claude/scripts/recover_grok_session.py --list
python .claude/scripts/recover_grok_session.py --latest --print
python .claude/scripts/recover_grok_session.py --uuid 019e8b67-f97e-7b33-9c45-ec34b342d3eb --auto
The /recover command will be extended to dispatch to this for Grok sessions.
"""
from __future__ import annotations
import argparse
import json
import os
import re
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Tuple
# --- Resolution of Grok home and current workspace slug (mirrors how Grok does it) ---
def get_grok_home() -> Path:
if os.name == "nt":
return Path(os.environ.get("USERPROFILE", "~")).expanduser() / ".grok"
return Path(os.environ.get("HOME", "~")).expanduser() / ".grok"
def get_workspace_slug(cwd: Optional[Path] = None) -> str:
"""Mimic Grok's slug for the sessions/<slug> directory.
Grok uses URL-style encoding of the absolute path ( : -> %3A, \\ -> %5C, / -> %2F ).
"""
root = (cwd or Path.cwd()).resolve()
# On Windows the path has drive letter + backslashes.
s = str(root)
# Percent-encode unsafe for dir name (Grok appears to use % encoding).
# Simple approach that matched observed dirs: replace special with %HH
encoded = re.sub(r'([^\w\-./])', lambda m: f"%{ord(m.group(1)):02X}", s)
# Grok observed: D%3A%5Cclaudetools (backslashes and colon encoded, no extra for / sometimes)
# But on the FS it was D%3A%5Cclaudetools
return encoded.replace("/", "%2F")
def find_session_dirs(workspace_root: Optional[Path] = None) -> List[Tuple[str, Path]]:
"""Return list of (uuid, dir_path) for sessions under the current workspace slug."""
grok_home = get_grok_home()
slug = get_workspace_slug(workspace_root)
base = grok_home / "sessions" / slug
if not base.exists():
return []
results = []
for p in base.iterdir():
if p.is_dir() and re.match(r'^[0-9a-f]{8}-', p.name): # rough UUID check
results.append((p.name, p))
results.sort(key=lambda t: t[1].stat().st_mtime, reverse=True)
return results
def get_latest_session(workspace_root: Optional[Path] = None) -> Optional[Tuple[str, Path]]:
sessions = find_session_dirs(workspace_root)
return sessions[0] if sessions else None
# --- Extraction ---
def read_jsonl(path: Path) -> List[Dict[str, Any]]:
if not path.exists():
return []
out = []
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
line = line.strip()
if line:
try:
out.append(json.loads(line))
except Exception:
pass
return out
def extract_terminal_commands(session_dir: Path) -> List[Dict[str, str]]:
"""Pull exact commands + outputs from the terminal/ call logs.
These are the highest-fidelity evidence for the session log "Commands & Outputs" section.
"""
term_dir = session_dir / "terminal"
if not term_dir.exists():
return []
results = []
for f in sorted(term_dir.glob("call-*.log")):
try:
content = f.read_text(encoding="utf-8", errors="replace")
# The tool already logs the command in the filename sometimes, or inside.
# From observed usage the file contains the full command + stdout/stderr.
results.append({
"file": f.name,
"content": content[:20000] # cap per call for practicality
})
except Exception:
pass
return results
def extract_basic_metadata(session_dir: Path) -> Dict[str, Any]:
meta: Dict[str, Any] = {"uuid": session_dir.name}
# Try summary.json
sj = session_dir / "summary.json"
if sj.exists():
try:
meta["summary"] = json.loads(sj.read_text(encoding="utf-8", errors="replace"))
except Exception:
pass
# opened time from dir mtime or active_sessions if present
meta["dir_mtime"] = datetime.fromtimestamp(session_dir.stat().st_mtime, tz=timezone.utc).isoformat()
return meta
def build_session_log(uuid: str, session_dir: Path, workspace_root: Path) -> str:
"""Produce markdown in the approximate shape expected by /save and wiki tools."""
lines: List[str] = []
lines.append(f"# Grok Session Log — {uuid}")
lines.append("")
lines.append(f"**Workspace:** {workspace_root}")
lines.append(f"**Grok session dir:** {session_dir}")
lines.append(f"**Recovered at:** {datetime.now(timezone.utc).isoformat()}")
lines.append("**Driver:** Grok")
lines.append("")
meta = extract_basic_metadata(session_dir)
lines.append("## Metadata")
lines.append(json.dumps(meta, indent=2, default=str))
lines.append("")
# Terminal calls (commands + output)
calls = extract_terminal_commands(session_dir)
lines.append("## Commands & Outputs (from terminal call logs)")
if calls:
for c in calls:
lines.append(f"### {c['file']}")
lines.append("```")
lines.append(c["content"].rstrip())
lines.append("```")
lines.append("")
else:
lines.append("(no terminal call logs found)")
lines.append("")
# TODO (future): parse chat_history / events / updates for user/assistant turns,
# file edits (search_replace results), read_file contents, decisions, etc.
# Then feed prose sections (Summary, Key Decisions, Pending Tasks) to Ollama
# exactly like recover_session.py does.
lines.append("## Notes")
lines.append("This is an MVP recovery from Grok native session artifacts.")
lines.append("Full fidelity (conversation turns, structured tool calls, file changes) ")
lines.append("and Ollama-assisted summarization can be added by extending this script ")
lines.append("or sharing logic with recover_session.py.")
lines.append("Run with --auto to write a session-log/ file in the conventional location.")
lines.append("")
return "\n".join(lines)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--uuid", help="Specific Grok session UUID to recover")
ap.add_argument("--latest", action="store_true", help="Recover the most recent session for this workspace")
ap.add_argument("--list", action="store_true", help="List available Grok sessions for this workspace slug")
ap.add_argument("--print", action="store_true", help="Print the reconstructed log to stdout")
ap.add_argument("--auto", action="store_true", help="Write a session log under session-logs/ (or projects/... ) following ClaudeTools conventions")
ap.add_argument("--workspace", type=Path, default=None, help="Override workspace root for slug calculation")
args = ap.parse_args()
ws_root = (args.workspace or Path.cwd()).resolve()
if args.list:
for uuid, d in find_session_dirs(ws_root):
print(f"{uuid} {d} (mtime {datetime.fromtimestamp(d.stat().st_mtime)})")
return 0
target: Optional[Tuple[str, Path]] = None
if args.uuid:
base = get_grok_home() / "sessions" / get_workspace_slug(ws_root)
p = base / args.uuid
if p.exists():
target = (args.uuid, p)
else:
print(f"[ERROR] UUID dir not found: {p}", file=sys.stderr)
return 1
elif args.latest:
target = get_latest_session(ws_root)
if not target:
print("[ERROR] No Grok sessions found for this workspace", file=sys.stderr)
return 1
else:
print("Specify --uuid, --latest, or --list", file=sys.stderr)
return 2
uuid, sdir = target
log = build_session_log(uuid, sdir, ws_root)
if args.print or not args.auto:
print(log)
if args.auto:
# Minimal auto placement: root session-logs/ for now (real /save logic is more sophisticated).
ts = datetime.now(timezone.utc).strftime("%Y-%m-%d")
out_dir = ws_root / "session-logs"
out_dir.mkdir(parents=True, exist_ok=True)
out_path = out_dir / f"grok-session-{ts}-{uuid[:8]}.md"
out_path.write_text(log, encoding="utf-8")
print(f"[OK] Wrote {out_path}")
# In real use we would also update coord, create a checkpoint entry etc.
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -126,9 +126,10 @@ fi
# --- Derive the harness profile memory dir ---------------------------------
# Slug = absolute project path with every run of non-alphanumeric chars -> '-'.
# Must match memory_dream.py's derivation. Prefer CLAUDE_PROJECT_DIR if set.
# Must match memory_dream.py's derivation. Prefer CLAUDE_PROJECT_DIR if set;
# also honor GROK_WORKSPACE_ROOT for Grok driver hooks (coexistence).
HOME_DIR="${HOME:-$(cd ~ 2>/dev/null && pwd)}"
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$ROOT}"
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-${GROK_WORKSPACE_ROOT:-$ROOT}}"
# Absolutize PROJECT_DIR.
case "$PROJECT_DIR" in