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

@@ -71,6 +71,22 @@ This file is gitignored (machine-local). The `UserPromptSubmit` hook reads it to
**Windows/Git Bash:** always use the relative path above (or forward slashes — `/d/claudetools/.claude/current-mode`). NEVER a backslashed Windows path like `D:\claudetools\.claude\current-mode`: Git Bash strips the backslashes and substitutes the illegal `:` with a Unicode PUA char, creating a garbled junk file instead of writing the path. A `PreToolUse(Bash)` hook (`.claude/hooks/block-backslash-winpath.sh`) blocks such redirects; `sync.sh` also strips any that slip through before staging.
**Windows bash command (the `bash` executable):** In PowerShell contexts (including the Grok/Claude tool run_terminal_command), `bash` often resolves to the WSL stub (`WindowsApps\bash.exe`) instead of the required Git for Windows/MSYS bash. This breaks vault.sh, sync.sh, hooks, etc.
Fix (idempotent):
```powershell
$gitBin = "C:\Program Files\Git\bin"
$gitUsrBin = "C:\Program Files\Git\usr\bin"
if ((Test-Path $gitBin) -and ((Get-Command bash -ErrorAction SilentlyContinue).Source -notlike '*Git*bin*bash.exe')) {
$env:Path = "$gitBin;$gitUsrBin;" + ($env:Path -replace [regex]::Escape("$gitBin;"), '' -replace [regex]::Escape("$gitUsrBin;"), '')
}
```
Then plain `bash .claude/scripts/vault.sh ...` works and shows the MSYS version.
Project helper: `. .claude/scripts/ensure-git-bash.ps1` (see that file + `.claude/memory/feedback_windows_bash_mapping.md`).
The user's PowerShell `$PROFILE` auto-applies the remap on new sessions. For critical calls, prefer the full path `"C:\Program Files\Git\bin\bash.exe" .claude/scripts/...` if env is uncertain. Git Bash terminals (direct launch) are already correct. Related: always use system OpenSSH, not Git's.
**Auto-initialization:** If `.claude/current-mode` is missing (e.g., fresh clone), the UserPromptSubmit hook automatically creates it with "general" as the default mode. No manual setup required.
---

View File

@@ -1,6 +1,8 @@
# Claude Code Commands
# Claude Code Commands (also available to Grok)
Custom commands that extend Claude Code's capabilities.
Custom commands that extend the AI's capabilities (Claude Code or Grok).
These live in `.claude/commands/*.md`. For Grok coexistence, thin native wrappers exist in `.grok/skills/<name>/SKILL.md` (with `name:` frontmatter so `/save`, `/rmm` etc. work as first-class Grok slash commands via project skill discovery). The wrappers delegate to these files (read them at runtime + adapt tool names). Single source of truth remains here. See `.grok/README.md` for details.
## Available Commands

View File

@@ -1,6 +1,9 @@
Reconstruct a session log from a Claude Code transcript when a session crashed or was closed before `/save`.
Reconstruct a session log from a Claude Code or Grok transcript when a session crashed or was closed before `/save`.
Claude Code writes every session live to a transcript JSONL under `~/.claude/projects/<slug>/<uuid>.jsonl`. `/recover` distills one of those transcripts back into a normal session log in the `.claude/commands/save.md` format. This is the **manual, reviewed** path; the background detector (`detect_orphaned_sessions.py`) handles unattended auto-recovery.
- Claude Code: `~/.claude/projects/<slug>/<uuid>.jsonl`
- Grok: `~/.grok/sessions/<slug>/<uuid>/` (chat_history.jsonl + events + terminal/call-*.log etc.)
`/recover` distills the transcript back into a normal ClaudeTools session log (the format used by `/save`). This is the **manual, reviewed** path. The driver is auto-detected or can be forced.
---
@@ -82,3 +85,24 @@ Use `/recover` when you know a specific session was lost and want a clean log. L
- `--auto` and `--json` modes on `recover_session.py` exist for the detector and for scripting; `/recover` uses `--print` so Claude always reviews before anything lands on disk.
- The prose is Ollama-drafted from the transcript; the Commands/Config/Reference sections are extracted verbatim by Python. Never trust the prose for exact commands, IPs, credentials, paths, SHAs, or ticket IDs — read those from the verbatim sections.
- Transcripts are per-machine. You can only recover sessions that ran on the machine you are on.
## Grok sessions
Grok stores richer per-session artifacts (separate chat_history, events, per-tool terminal logs with full output, summary, etc.) under `~/.grok/sessions/<slug>/<uuid>/`.
The companion script is `.claude/scripts/recover_grok_session.py`.
Current support (MVP):
- `--list`, `--latest`, `--uuid <id>` work and locate the right dir using the same slug rules Grok uses.
- Extracts terminal command logs (highest value verbatim evidence) + basic metadata.
- `--print` and `--auto` (writes under `session-logs/grok-session-....md`).
Full parity with the Claude recover (conversation reconstruction + Ollama prose for Summary/Decisions + exact file-edit extraction) is planned; the two scripts can share helpers later.
In `/recover` flows, if the chosen orphan or explicit id lives under a Grok sessions tree, the Grok script is used automatically (future enhancement to the dispatcher in recover_session.py or a thin wrapper).
Example (manual):
```bash
py .claude/scripts/recover_grok_session.py --latest --print
py .claude/scripts/recover_grok_session.py --uuid 019e8b67-f97e-7b33-9c45-ec34b342d3eb --auto
```

View File

@@ -2,6 +2,11 @@
# PreToolUse(Bash) hook: block bash commands that REDIRECT/WRITE to a backslashed
# Windows drive path (e.g. `echo x > D:\claudetools\.claude\current-mode`).
#
# Dual-driver: works when invoked from Claude Code (.claude/settings.json) or
# from Grok (.grok/hooks/*.json PreToolUse). The caller normalizes stdin JSON
# shape (tool_input vs toolInput) and the script emits Grok decision JSON when
# it detects a Grok-shaped event and needs to deny.
#
# Why: under Git Bash / MSYS, a backslash is an escape char. `> D:\foo\bar`
# strips the backslashes and substitutes the illegal ':' with the Unicode
# Private-Use char U+F03A, creating a garbled junk file in the CWD instead of
@@ -12,7 +17,25 @@
# `icacls "D:\Homes"` or `pwsh -File C:\x.ps1` are NOT redirects, so they pass.
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null)
# Support both Claude (tool_input / tool_input.command) and Grok (toolInput / toolInput.command + hookEventName) event shapes.
# Prefer jq; fallback to python (avoids repeated "jq: command not found" or parse errors if jq missing in env).
cmd=$(echo "$input" | jq -r '(.toolInput // .tool_input // {}) | .command // ""' 2>/dev/null || python -c "
import sys, json
try:
d = json.load(sys.stdin)
ti = d.get('toolInput') or d.get('tool_input') or {}
print(ti.get('command', ''))
except:
print('')
" 2>/dev/null || echo '')
is_grok=$(echo "$input" | jq -r 'if has("hookEventName") or has("toolInput") then "1" else "0" end' 2>/dev/null || python -c "
import sys, json
try:
d = json.load(sys.stdin)
print('1' if ('hookEventName' in d or 'toolInput' in d) else '0')
except:
print('0')
" 2>/dev/null || echo '0')
# Strip quoted substrings first, so the pattern appearing INSIDE a string or a
# commit message (e.g. git commit -m "... > D:\\path ...") does not false-trigger.
@@ -22,6 +45,7 @@ bare=$(printf '%s' "$cmd" | sed -E "s/'[^']*'//g; s/\"[^\"]*\"//g")
# Block when, after quote-stripping, a redirect/tee writes to a bareword X:\ path.
if printf '%s' "$bare" | grep -qiP '(>>?|tee)\s*[A-Za-z]:\\'; then
reason="Blocked redirect/write to backslashed Windows path in bash (Git Bash would garble it via PUA substitution)."
echo "BLOCKED: do not redirect/write to a backslashed Windows path in bash."
echo ""
echo "Git Bash strips the backslashes and PUA-substitutes ':', creating a"
@@ -31,6 +55,10 @@ if printf '%s' "$bare" | grep -qiP '(>>?|tee)\s*[A-Za-z]:\\'; then
echo " - relative path: echo dev > .claude/current-mode"
echo " - MSYS forward-slash: echo dev > /d/claudetools/.claude/current-mode"
echo " - drive forward-slash: echo dev > D:/claudetools/.claude/current-mode"
if [ "$is_grok" = "1" ]; then
# Emit Grok PreToolUse decision format so the hook runner can deny cleanly.
printf '{"decision":"deny","reason":"%s"}\n' "$reason"
fi
exit 2
fi

View File

@@ -39,6 +39,7 @@
- [1Password — always use service token](feedback_1password_service_token.md) — Source OP_SERVICE_ACCOUNT_TOKEN from SOPS for every `op` call. Desktop-app integration prompts are unacceptable in agent flows.
- [Point vault-access teammates at SOPS path](feedback_vault_pointer_for_teammates.md) — When relaying infra/credential info to Howard or other vault-access teammates, hand over the SOPS path + key anchors; don't transcribe the entry's fields into the message.
- [/tmp path mismatch on Windows](feedback_tmp_path_windows.md) — Write tool and Git Bash resolve `/tmp` to DIFFERENT real dirs. Use heredoc or workspace path for JSON payloads handed to curl.
- [Windows bash command mapping](feedback_windows_bash_mapping.md) — `bash` often resolves to WSL stub instead of Git/MSYS bash required by the harness. Fix by prepending `C:\Program Files\Git\bin` (and usr\bin) to PATH, or source `.claude/scripts/ensure-git-bash.ps1`. Profile has the logic; use plain `bash .claude/scripts/...` after remap. See the helper and this memory file for details.
- [SQL instance role — verify by connections, not name](feedback_sql_instance_role_by_connection.md) — Standard installed under default `SQLEXPRESS` instance name is real. Prove role with `sys.dm_exec_sessions` + `Get-NetTCPConnection -OwningProcess` before recommending stop/uninstall.
- [Clear-RecycleBin fails silently as SYSTEM](feedback_clear_recyclebin_system_context.md) — RMM-dispatched cleanup scripts cannot use `Clear-RecycleBin -Force`; the cmdlet uses Shell COM and silently no-ops without an interactive desktop. Enumerate `C:\$Recycle.Bin\<SID>\*` directly.
- [Graph CA policy reads are eventually consistent](feedback_graph_ca_policy_eventual_consistency.md) — After PATCHing a CA policy (204), wait ~5s before GET-verifying; immediate reads can be stale.

View File

@@ -0,0 +1,54 @@
# Windows Bash Command Mapping (Git Bash vs WSL stub)
## The Issue
On Windows machines (especially when the AI tool runs in PowerShell context), the `bash` command often resolves to the WSL stub at `C:\Users\...\WindowsApps\bash.exe` instead of the required Git for Windows / MSYS bash.
This breaks the entire ClaudeTools harness:
- `.claude/scripts/vault.sh`, `sync.sh`, `check-messages.sh`, etc. expect GNU bash features, `/d/claudetools` style paths, etc.
- Hooks and commands invoke `bash -c '...'`.
- `self-check` expects: `bash present (GNU bash, version 5.2.37(1)-release (x86_64-pc-msys))`
Symptoms: "Windows Subsystem for Linux has no installed distributions." or scripts failing with path/quoting issues.
## The Fix
Prepend Git Bash directories to `$env:Path` so plain `bash` resolves to the correct one:
```powershell
$gitBin = "C:\Program Files\Git\bin"
$gitUsrBin = "C:\Program Files\Git\usr\bin"
if ((Test-Path $gitBin) -and ((Get-Command bash -ErrorAction SilentlyContinue).Source -notlike '*Git*bin*bash.exe')) {
$env:Path = "$gitBin;$gitUsrBin;" + ($env:Path -replace [regex]::Escape("$gitBin;"), '' -replace [regex]::Escape("$gitUsrBin;"), '')
}
```
After this:
- `Get-Command bash` returns `C:\Program Files\Git\bin\bash.exe`
- `bash --version` shows the MSYS version
- Plain invocations like `bash .claude/scripts/vault.sh list` work.
## Project Helper
Use the reusable script:
```powershell
. .claude/scripts/ensure-git-bash.ps1
```
It contains the logic above (idempotent, with verbose/warning).
## Persistence
The user's PowerShell profile (`$PROFILE`) sources/contains the remap logic so it applies on shell startup.
See: `.claude/scripts/ensure-git-bash.ps1` (the helper) and the profile for the exact code.
## For AI Sessions (Grok or Claude)
- At the start of any command involving bash/harness scripts, ensure the mapping (source the ps1 or run the snippet).
- In Grok: the `.grok/skills/windows-bash/SKILL.md` (if present) and this memory entry will be available.
- Always prefer explicit full path ` "C:\Program Files\Git\bin\bash.exe" .claude/scripts/vault.sh ... ` for critical one-off calls if the env isn't trusted yet.
- Git Bash terminals (launched directly) already have the correct bash.
## Related
- See the backslash hook for another Git Bash gotcha: `.claude/hooks/block-backslash-winpath.sh`
- SSH: always use system OpenSSH (`C:\Windows\System32\OpenSSH\ssh.exe`), never Git's ssh.
- Profile and PATH ordering is the standard way; do not remove the WindowsApps stub.
This was fixed during a session on GURU-5070 to make Grok tool calls reliable with the harness. Add similar entries for other machine-specific env quirks.

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

View File

@@ -43,11 +43,17 @@
".claude/scripts/check-messages.sh",
".claude/scripts/migrate-identity.sh"
],
"grok_recovery_scripts": [
".claude/scripts/recover_grok_session.py"
],
"required_hook_files": [
".claude/hooks/block-backslash-winpath.sh",
".claude/hooks/post-commit.template"
],
"grok_hook_files": [
".grok/hooks/claudetools.json"
],
"required_settings_hooks": [
{ "event": "PreToolUse", "matcher": "Bash", "command_contains": "block-backslash-winpath.sh", "why": "blocks garbled backslash Windows-path redirects in Git Bash" },