diff --git a/SKILL.md b/SKILL.md index 7e2acdabf..8c1ff4dfb 100644 --- a/SKILL.md +++ b/SKILL.md @@ -377,10 +377,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -415,7 +418,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/autoplan/SKILL.md b/autoplan/SKILL.md index 66c013051..f81dc72a5 100644 --- a/autoplan/SKILL.md +++ b/autoplan/SKILL.md @@ -512,10 +512,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -550,7 +553,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/benchmark-models/SKILL.md b/benchmark-models/SKILL.md index 71608c5a0..75e7e5128 100644 --- a/benchmark-models/SKILL.md +++ b/benchmark-models/SKILL.md @@ -381,10 +381,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -419,7 +422,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/benchmark/SKILL.md b/benchmark/SKILL.md index 1c0e27fba..10a68e818 100644 --- a/benchmark/SKILL.md +++ b/benchmark/SKILL.md @@ -381,10 +381,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -419,7 +422,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/bin/gstack-brain-cache b/bin/gstack-brain-cache index 301483632..abf45a013 100755 --- a/bin/gstack-brain-cache +++ b/bin/gstack-brain-cache @@ -126,13 +126,27 @@ function sha8(input: string): string { * Detects the active brain endpoint (MCP URL or 'local') and returns its * stable identity hash. Used to detect when the user switches brains * (different endpoint → different cache). + * + * Reads BOTH registration scopes in ~/.claude.json (#2499): user scope + * (.mcpServers.gbrain) first, then project scope + * (.projects["/abs/path"].mcpServers.gbrain — what `claude mcp add` + * WITHOUT --scope user writes), preferring the nearest ancestor of cwd + * (longest matching project key) so nested repos resolve to their own + * brain. Before the project-scope read, two different project-scoped + * brains both hashed to 'local', so switching between them never + * invalidated the cache — the exact scenario this function exists to + * catch. + * + * Params exist for tests; production callers use the defaults. */ -export function detectEndpointHash(): string { - const claudeJsonPath = join(homedir(), '.claude.json'); +export function detectEndpointHash( + claudeJsonPath: string = join(homedir(), '.claude.json'), + cwd: string = process.cwd(), +): string { if (existsSync(claudeJsonPath)) { try { const cfg = JSON.parse(readFileSync(claudeJsonPath, 'utf-8')); - const gbrainServer = cfg?.mcpServers?.gbrain; + const gbrainServer = resolveGbrainMcpEntry(cfg, cwd); const url = gbrainServer?.url || gbrainServer?.transport?.url; if (typeof url === 'string' && url.length > 0) { return sha8(url); @@ -143,6 +157,40 @@ export function detectEndpointHash(): string { return 'local'; } +interface McpEntryish { + url?: unknown; + transport?: { url?: unknown }; +} + +/** + * User-scope gbrain entry, else the nearest-ancestor project-scope entry + * for cwd (#2499). Path-boundary-aware: /a/repo never matches /a/repo2. + * Both separators are accepted so Windows project keys resolve. + */ +function resolveGbrainMcpEntry( + cfg: unknown, + cwd: string, +): McpEntryish | undefined { + const root = cfg as { + mcpServers?: Record; + projects?: Record }>; + } | null; + if (root?.mcpServers?.gbrain) return root.mcpServers.gbrain; + const projects = root?.projects; + if (!projects || typeof projects !== 'object') return undefined; + let best: { key: string; entry: McpEntryish } | undefined; + for (const [key, val] of Object.entries(projects)) { + if (!val || typeof val !== 'object') continue; + const entry = val.mcpServers?.gbrain; + if (!entry || typeof entry !== 'object') continue; + const isAncestor = + cwd === key || cwd.startsWith(`${key}/`) || cwd.startsWith(`${key}\\`); + if (!isAncestor) continue; + if (!best || key.length > best.key.length) best = { key, entry }; + } + return best?.entry; +} + // ────────────────────────────────────────────────────────────────────────── // Atomic write (tmp + rename) // ────────────────────────────────────────────────────────────────────────── diff --git a/browse/SKILL.md b/browse/SKILL.md index 5e06c5b39..37b6b1a19 100644 --- a/browse/SKILL.md +++ b/browse/SKILL.md @@ -379,10 +379,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -417,7 +420,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/canary/SKILL.md b/canary/SKILL.md index 5a2e02158..806e1f3b9 100644 --- a/canary/SKILL.md +++ b/canary/SKILL.md @@ -504,10 +504,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -542,7 +545,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/codex/SKILL.md b/codex/SKILL.md index 4470be50d..36eadff5e 100644 --- a/codex/SKILL.md +++ b/codex/SKILL.md @@ -507,10 +507,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -545,7 +548,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/context-restore/SKILL.md b/context-restore/SKILL.md index 0481d71fc..5a3c5b6ef 100644 --- a/context-restore/SKILL.md +++ b/context-restore/SKILL.md @@ -508,10 +508,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -546,7 +549,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/context-save/SKILL.md b/context-save/SKILL.md index 02d3db51b..be9df4146 100644 --- a/context-save/SKILL.md +++ b/context-save/SKILL.md @@ -507,10 +507,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -545,7 +548,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/cso/SKILL.md b/cso/SKILL.md index 06c4b37cf..99e6cb9c0 100644 --- a/cso/SKILL.md +++ b/cso/SKILL.md @@ -510,10 +510,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -548,7 +551,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/design-consultation/SKILL.md b/design-consultation/SKILL.md index c7c248aca..57450909c 100644 --- a/design-consultation/SKILL.md +++ b/design-consultation/SKILL.md @@ -530,10 +530,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -568,7 +571,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/design-html/SKILL.md b/design-html/SKILL.md index 742358e34..a9e1ac1a9 100644 --- a/design-html/SKILL.md +++ b/design-html/SKILL.md @@ -511,10 +511,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -549,7 +552,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/design-review/SKILL.md b/design-review/SKILL.md index efd98527f..786423e76 100644 --- a/design-review/SKILL.md +++ b/design-review/SKILL.md @@ -508,10 +508,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -546,7 +549,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/design-shotgun/SKILL.md b/design-shotgun/SKILL.md index f2fc831bf..4066154c1 100644 --- a/design-shotgun/SKILL.md +++ b/design-shotgun/SKILL.md @@ -525,10 +525,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -563,7 +566,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/devex-review/SKILL.md b/devex-review/SKILL.md index 571816f7d..1450b9bbb 100644 --- a/devex-review/SKILL.md +++ b/devex-review/SKILL.md @@ -510,10 +510,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -548,7 +551,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/diagram/SKILL.md b/diagram/SKILL.md index 0fc2de22d..c2d16b2fb 100644 --- a/diagram/SKILL.md +++ b/diagram/SKILL.md @@ -380,10 +380,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -418,7 +421,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/document-generate/SKILL.md b/document-generate/SKILL.md index 3d7338c99..7d8b34bc0 100644 --- a/document-generate/SKILL.md +++ b/document-generate/SKILL.md @@ -510,10 +510,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -548,7 +551,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/document-release/SKILL.md b/document-release/SKILL.md index e0a2b106e..a87ba0f18 100644 --- a/document-release/SKILL.md +++ b/document-release/SKILL.md @@ -508,10 +508,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -546,7 +549,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/health/SKILL.md b/health/SKILL.md index 352cd3e04..2466a83c5 100644 --- a/health/SKILL.md +++ b/health/SKILL.md @@ -506,10 +506,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -544,7 +547,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/investigate/SKILL.md b/investigate/SKILL.md index ebdc934a2..81b872d0c 100644 --- a/investigate/SKILL.md +++ b/investigate/SKILL.md @@ -545,10 +545,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -583,7 +586,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/ios-clean/SKILL.md b/ios-clean/SKILL.md index c0a36bfd6..f5f3fdf81 100644 --- a/ios-clean/SKILL.md +++ b/ios-clean/SKILL.md @@ -508,10 +508,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -546,7 +549,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/ios-design-review/SKILL.md b/ios-design-review/SKILL.md index f61cf9710..185a24c98 100644 --- a/ios-design-review/SKILL.md +++ b/ios-design-review/SKILL.md @@ -510,10 +510,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -548,7 +551,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/ios-fix/SKILL.md b/ios-fix/SKILL.md index ec12406c2..989b0a6ac 100644 --- a/ios-fix/SKILL.md +++ b/ios-fix/SKILL.md @@ -511,10 +511,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -549,7 +552,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/ios-qa/SKILL.md b/ios-qa/SKILL.md index 6e56ea4ed..72b02a7f6 100644 --- a/ios-qa/SKILL.md +++ b/ios-qa/SKILL.md @@ -514,10 +514,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -552,7 +555,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/ios-sync/SKILL.md b/ios-sync/SKILL.md index 18c709d09..4fdc28866 100644 --- a/ios-sync/SKILL.md +++ b/ios-sync/SKILL.md @@ -508,10 +508,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -546,7 +549,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/land-and-deploy/SKILL.md b/land-and-deploy/SKILL.md index 51bf460f3..67813624a 100644 --- a/land-and-deploy/SKILL.md +++ b/land-and-deploy/SKILL.md @@ -503,10 +503,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -541,7 +544,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/landing-report/SKILL.md b/landing-report/SKILL.md index 49b15e993..114e54210 100644 --- a/landing-report/SKILL.md +++ b/landing-report/SKILL.md @@ -505,10 +505,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -543,7 +546,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/learn/SKILL.md b/learn/SKILL.md index d3b82b52f..eccf8a3fd 100644 --- a/learn/SKILL.md +++ b/learn/SKILL.md @@ -506,10 +506,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -544,7 +547,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/make-pdf/SKILL.md b/make-pdf/SKILL.md index 666bad78c..8ddfe5c51 100644 --- a/make-pdf/SKILL.md +++ b/make-pdf/SKILL.md @@ -416,10 +416,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -454,7 +457,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/office-hours/SKILL.md b/office-hours/SKILL.md index c4b7254c0..2b4eff1f2 100644 --- a/office-hours/SKILL.md +++ b/office-hours/SKILL.md @@ -541,10 +541,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -579,7 +582,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/open-gstack-browser/SKILL.md b/open-gstack-browser/SKILL.md index 559eaa3b6..a1b98c3ca 100644 --- a/open-gstack-browser/SKILL.md +++ b/open-gstack-browser/SKILL.md @@ -379,10 +379,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -417,7 +420,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/pair-agent/SKILL.md b/pair-agent/SKILL.md index af608c243..2aa276d2d 100644 --- a/pair-agent/SKILL.md +++ b/pair-agent/SKILL.md @@ -506,10 +506,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -544,7 +547,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/plan-ceo-review/SKILL.md b/plan-ceo-review/SKILL.md index 3d5e9b911..3c7607e0f 100644 --- a/plan-ceo-review/SKILL.md +++ b/plan-ceo-review/SKILL.md @@ -535,10 +535,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -573,7 +576,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/plan-design-review/SKILL.md b/plan-design-review/SKILL.md index 525e5df68..d7fb3a74a 100644 --- a/plan-design-review/SKILL.md +++ b/plan-design-review/SKILL.md @@ -507,10 +507,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -545,7 +548,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/plan-devex-review/SKILL.md b/plan-devex-review/SKILL.md index 447cd3a1f..d80bfba77 100644 --- a/plan-devex-review/SKILL.md +++ b/plan-devex-review/SKILL.md @@ -513,10 +513,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -551,7 +554,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/plan-eng-review/SKILL.md b/plan-eng-review/SKILL.md index 22afb1c10..d196dba7f 100644 --- a/plan-eng-review/SKILL.md +++ b/plan-eng-review/SKILL.md @@ -511,10 +511,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -549,7 +552,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/plan-tune/SKILL.md b/plan-tune/SKILL.md index de473b9a2..2f1e265fb 100644 --- a/plan-tune/SKILL.md +++ b/plan-tune/SKILL.md @@ -516,10 +516,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -554,7 +557,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/qa-only/SKILL.md b/qa-only/SKILL.md index eafa358de..3209628b7 100644 --- a/qa-only/SKILL.md +++ b/qa-only/SKILL.md @@ -506,10 +506,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -544,7 +547,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/qa/SKILL.md b/qa/SKILL.md index b11981476..4fb7f86c2 100644 --- a/qa/SKILL.md +++ b/qa/SKILL.md @@ -512,10 +512,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -550,7 +553,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/retro/SKILL.md b/retro/SKILL.md index f6fd85d12..ef723d679 100644 --- a/retro/SKILL.md +++ b/retro/SKILL.md @@ -526,10 +526,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -564,7 +567,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/review/SKILL.md b/review/SKILL.md index 263973daf..6099d2b91 100644 --- a/review/SKILL.md +++ b/review/SKILL.md @@ -508,10 +508,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -546,7 +549,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/scrape/SKILL.md b/scrape/SKILL.md index f3ba51a60..87d174fbf 100644 --- a/scrape/SKILL.md +++ b/scrape/SKILL.md @@ -380,10 +380,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -418,7 +421,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/scripts/resolvers/preamble/generate-brain-sync-block.ts b/scripts/resolvers/preamble/generate-brain-sync-block.ts index 0b0a022a1..ae1ab5778 100644 --- a/scripts/resolvers/preamble/generate-brain-sync-block.ts +++ b/scripts/resolvers/preamble/generate-brain-sync-block.ts @@ -30,6 +30,27 @@ import type { TemplateContext } from '../types'; import { quoteSafePath } from '../types'; +/** + * Shared jq sub-expression resolving the gbrain MCP registration entry + * (#2499). Claude Code registers MCP servers at TWO scopes in + * ~/.claude.json: user scope (.mcpServers.gbrain) and project scope + * (.projects["/abs/path"].mcpServers.gbrain — what `claude mcp add` + * WITHOUT --scope user writes). Reading only user scope makes a correctly + * configured project-scoped brain invisible: brain-aware blocks suppress, + * remote-mode sync is never recognised, nothing errors. + * + * Resolution order: user scope first, then the nearest-ancestor project + * entry for $PWD that actually carries a gbrain server (longest matching + * key with a path-boundary check, so nested repos pick their own + * registration, /a/repo never matches /a/repo2, and a nested project + * WITHOUT gbrain doesn't shadow its parent's registration). The emitted + * bash resolves the entry ONCE into _GBRAIN_MCP_ENTRY (compact JSON) and + * extracts fields from that variable — one jq parse of claude.json per + * skill start, and the long expression appears once per rendered SKILL.md. + */ +const GBRAIN_MCP_ENTRY_JQ = + '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty'; + export function generateBrainSyncBlock(ctx: TemplateContext): string { const isBrainHost = ctx.host === 'gbrain' || ctx.host === 'hermes'; return `## Artifacts Sync (skill start) @@ -79,10 +100,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '${GBRAIN_MCP_ENTRY_JQ}' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -117,7 +141,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "\${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server \${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/setup-browser-cookies/SKILL.md b/setup-browser-cookies/SKILL.md index fb6a056ed..79f4d8809 100644 --- a/setup-browser-cookies/SKILL.md +++ b/setup-browser-cookies/SKILL.md @@ -375,10 +375,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -413,7 +416,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/setup-deploy/SKILL.md b/setup-deploy/SKILL.md index d0b72575f..e4cb5b513 100644 --- a/setup-deploy/SKILL.md +++ b/setup-deploy/SKILL.md @@ -507,10 +507,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -545,7 +548,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/setup-gbrain/SKILL.md b/setup-gbrain/SKILL.md index 54b3a987a..93a8cf4e1 100644 --- a/setup-gbrain/SKILL.md +++ b/setup-gbrain/SKILL.md @@ -506,10 +506,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -544,7 +547,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/ship/SKILL.md b/ship/SKILL.md index 593b76f83..3ce2f8641 100644 --- a/ship/SKILL.md +++ b/ship/SKILL.md @@ -508,10 +508,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -546,7 +549,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/skillify/SKILL.md b/skillify/SKILL.md index e22898eb5..e2a73c4d3 100644 --- a/skillify/SKILL.md +++ b/skillify/SKILL.md @@ -505,10 +505,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -543,7 +546,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/spec/SKILL.md b/spec/SKILL.md index f51fa9aad..d0c0c8a3a 100644 --- a/spec/SKILL.md +++ b/spec/SKILL.md @@ -506,10 +506,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -544,7 +547,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/sync-gbrain/SKILL.md b/sync-gbrain/SKILL.md index 48f003b22..89abd243e 100644 --- a/sync-gbrain/SKILL.md +++ b/sync-gbrain/SKILL.md @@ -507,10 +507,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -545,7 +548,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/test/brain-cache-roundtrip.test.ts b/test/brain-cache-roundtrip.test.ts index 972708657..0466bd287 100644 --- a/test/brain-cache-roundtrip.test.ts +++ b/test/brain-cache-roundtrip.test.ts @@ -131,6 +131,59 @@ describe('brain-cache endpoint detection', () => { expect(typeof hash).toBe('string'); expect(hash.length).toBeGreaterThan(0); }); + + // #2499: project-scoped registrations (.projects["/path"].mcpServers.gbrain) + // were never read — two different project-scoped brains both hashed to + // 'local', so switching between them never invalidated the cache. + test('detectEndpointHash resolves a project-scoped gbrain URL for a cwd inside the project (#2499)', async () => { + const mod = await importCache(); + const cj = join(TMP_HOME, 'claude.json'); + writeFileSync(cj, JSON.stringify({ + projects: { + '/w/repo': { mcpServers: { gbrain: { type: 'http', url: 'https://a.example/mcp' } } }, + }, + })); + const inside = mod.detectEndpointHash(cj, '/w/repo/src/deep'); + expect(inside).not.toBe('local'); + expect(inside).toHaveLength(8); + // Path-boundary check: /w/repo2 is NOT inside /w/repo. + expect(mod.detectEndpointHash(cj, '/w/repo2')).toBe('local'); + }); + + test('detectEndpointHash prefers the nearest-ancestor project entry (#2499)', async () => { + const mod = await importCache(); + const cj = join(TMP_HOME, 'claude.json'); + writeFileSync(cj, JSON.stringify({ + projects: { + '/w/repo': { mcpServers: { gbrain: { url: 'https://outer.example/mcp' } } }, + '/w/repo/nested': { mcpServers: { gbrain: { url: 'https://inner.example/mcp' } } }, + }, + })); + const inner = mod.detectEndpointHash(cj, '/w/repo/nested/sub'); + const outer = mod.detectEndpointHash(cj, '/w/repo/other'); + expect(inner).not.toBe(outer); // two brains → two hashes (the docstring scenario) + expect(inner).not.toBe('local'); + expect(outer).not.toBe('local'); + }); + + test('detectEndpointHash still prefers user scope over project scope (#2499)', async () => { + const mod = await importCache(); + const cj = join(TMP_HOME, 'claude.json'); + writeFileSync(cj, JSON.stringify({ + mcpServers: { gbrain: { url: 'https://user.example/mcp' } }, + projects: { + '/w/repo': { mcpServers: { gbrain: { url: 'https://proj.example/mcp' } } }, + }, + })); + const userScoped = mod.detectEndpointHash(cj, '/w/repo'); + // Same file minus the user-scope entry → different hash proves user scope won. + writeFileSync(cj, JSON.stringify({ + projects: { + '/w/repo': { mcpServers: { gbrain: { url: 'https://proj.example/mcp' } } }, + }, + })); + expect(mod.detectEndpointHash(cj, '/w/repo')).not.toBe(userScoped); + }); }); describe('brain-cache schema mismatch behavior', () => { diff --git a/test/fixtures/golden/claude-ship-SKILL.md b/test/fixtures/golden/claude-ship-SKILL.md index 593b76f83..3ce2f8641 100644 --- a/test/fixtures/golden/claude-ship-SKILL.md +++ b/test/fixtures/golden/claude-ship-SKILL.md @@ -508,10 +508,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -546,7 +549,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/test/fixtures/golden/codex-ship-SKILL.md b/test/fixtures/golden/codex-ship-SKILL.md index 08eccf37a..8e4def16c 100644 --- a/test/fixtures/golden/codex-ship-SKILL.md +++ b/test/fixtures/golden/codex-ship-SKILL.md @@ -494,10 +494,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -532,7 +535,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/test/fixtures/golden/factory-ship-SKILL.md b/test/fixtures/golden/factory-ship-SKILL.md index a954fcebf..d42534707 100644 --- a/test/fixtures/golden/factory-ship-SKILL.md +++ b/test/fixtures/golden/factory-ship-SKILL.md @@ -496,10 +496,13 @@ _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || e # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is # a no-op in remote mode; the brain server pulls from GitHub/GitLab on its # own cadence. Read claude.json directly to keep this preamble fast (no -# subprocess to claude CLI on every skill start). +# subprocess to claude CLI on every skill start). Both registration scopes +# are read (#2499): user scope, then the nearest-ancestor project scope. _GBRAIN_MCP_MODE="none" +_GBRAIN_MCP_ENTRY="" if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then - _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_ENTRY=$(jq -c --arg cwd "$PWD" '.mcpServers.gbrain // ((.projects // {}) | to_entries | map(select((.key as $k | $cwd == $k or ($cwd | startswith($k + "/"))) and ((try .value.mcpServers.gbrain catch null) != null))) | sort_by(.key | length) | last | .value.mcpServers.gbrain) // empty' "$HOME/.claude.json" 2>/dev/null) + _GBRAIN_MCP_TYPE=$(printf '%s' "$_GBRAIN_MCP_ENTRY" | jq -r '.type // .transport // empty' 2>/dev/null) case "$_GBRAIN_MCP_TYPE" in url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; stdio) _GBRAIN_MCP_MODE="local-stdio" ;; @@ -534,7 +537,7 @@ fi if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server # pulls from GitHub/GitLab). Show the user this is by design, not broken. - _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') + _GBRAIN_HOST=$(printf '%s' "${_GBRAIN_MCP_ENTRY:-}" | jq -r '.url // empty' 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|' | head -1 | tr -cd 'A-Za-z0-9._-') echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then _BRAIN_QUEUE_DEPTH=0 diff --git a/test/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index b69b104e5..259ce1f1d 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -3604,3 +3604,83 @@ describe('PREAMBLE resolution requires declared preamble-tier', () => { expect(offenders).toEqual([]); }); }); + +// --------------------------------------------------------------------------- +// #2499: gbrain MCP detection must read BOTH ~/.claude.json scopes. +// Claude Code registers MCP servers at user scope (.mcpServers) and project +// scope (.projects["/abs/path"].mcpServers — what `claude mcp add` without +// --scope user writes). The rendered brain-sync block previously read only +// user scope, so a correctly configured project-scoped brain was invisible. +// --------------------------------------------------------------------------- +describe('brain-sync block reads project-scoped MCP registrations (#2499)', () => { + const rendered = fs.readFileSync(path.join(ROOT, 'SKILL.md'), 'utf-8'); + + test('rendered _GBRAIN_MCP_ENTRY jq resolves project scope with nearest-ancestor cwd match', () => { + const line = rendered.split('\n').find((l) => l.includes('_GBRAIN_MCP_ENTRY=$(')); + expect(line).toBeDefined(); + // Project-scope read present, driven by $PWD. + expect(line!).toContain('--arg cwd "$PWD"'); + expect(line!).toContain('.projects'); + // User scope still resolved first. + expect(line!).toContain('.mcpServers.gbrain'); + // The old user-scope-only filter is gone from the rendered output. + expect(rendered).not.toContain('.mcpServers.gbrain.type // .mcpServers.gbrain.transport'); + expect(rendered).not.toContain(".mcpServers.gbrain.url // empty"); + }); + + test('rendered _GBRAIN_MCP_TYPE and _GBRAIN_HOST extract from the resolved entry', () => { + const typeLine = rendered.split('\n').find((l) => l.includes('_GBRAIN_MCP_TYPE=$(')); + const hostLine = rendered.split('\n').find((l) => l.includes('_GBRAIN_HOST=$(')); + expect(typeLine).toBeDefined(); + expect(hostLine).toBeDefined(); + expect(typeLine!).toContain('_GBRAIN_MCP_ENTRY'); + expect(hostLine!).toContain('_GBRAIN_MCP_ENTRY'); + }); + + test('rendered jq lines FUNCTION: project-scoped registration resolves for a cwd inside the project', () => { + // Execute the exact rendered bytes, not a re-derivation: extract the + // _GBRAIN_MCP_ENTRY + _GBRAIN_MCP_TYPE lines from the generated SKILL.md + // and run them in bash against a fixture ~/.claude.json that carries ONLY + // a project-scoped gbrain registration. + const lines = rendered.split('\n'); + const entryLine = lines.find((l) => l.includes('_GBRAIN_MCP_ENTRY=$(')); + const typeLine = lines.find((l) => l.includes('_GBRAIN_MCP_TYPE=$(')); + expect(entryLine).toBeDefined(); + expect(typeLine).toBeDefined(); + + const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-2499-home-')); + const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-2499-proj-')); + const nestedCwd = path.join(projectDir, 'src', 'deep'); + fs.mkdirSync(nestedCwd, { recursive: true }); + try { + fs.writeFileSync( + path.join(tmpHome, '.claude.json'), + JSON.stringify({ + projects: { + [projectDir]: { + mcpServers: { gbrain: { type: 'http', url: 'https://brain.example.com/mcp' } }, + }, + }, + }), + ); + const script = `cd "$1" || exit 1\n${entryLine!.trim()}\n${typeLine!.trim()}\necho "RESOLVED:$_GBRAIN_MCP_TYPE"`; + const r = spawnSync('bash', ['-c', script, 'bash', nestedCwd], { + encoding: 'utf-8', + env: { ...process.env, HOME: tmpHome }, + timeout: 10_000, + }); + expect(r.stdout).toContain('RESOLVED:http'); + + // Discriminator: a cwd OUTSIDE the project must NOT resolve it. + const outside = spawnSync('bash', ['-c', script, 'bash', os.tmpdir()], { + encoding: 'utf-8', + env: { ...process.env, HOME: tmpHome }, + timeout: 10_000, + }); + expect(outside.stdout).toContain('RESOLVED:\n'); + } finally { + fs.rmSync(tmpHome, { recursive: true, force: true }); + fs.rmSync(projectDir, { recursive: true, force: true }); + } + }); +}); diff --git a/test/helpers/carve-guards.ts b/test/helpers/carve-guards.ts index 40664b651..871acf0fb 100644 --- a/test/helpers/carve-guards.ts +++ b/test/helpers/carve-guards.ts @@ -181,7 +181,9 @@ export const CARVE_GUARDS: Record = { // v1.65 merge: provisional larger-of-both-waves budget; re-measured below. // Fork port wave 2 (#703): the repo-doc-preference block in the design // check grew every plan-review skeleton ~0.7KB. Measured values noted. - maxSkeletonBytes: 70_000, // measured 68,780 + // #2499 project-scope MCP jq in the brain-sync block grew every tier-2+ + // skeleton ~1.5KB (entry resolution emitted once per SKILL.md). + maxSkeletonBytes: 70_500, // measured 70,318 minUnionBytes: 70_000, mustContain: ['Architecture', 'Code Quality', 'Test', 'Performance'], // Cross-cutting preamble growth (v1.57.2.0 AUQ-failure prose fallback + the @@ -236,7 +238,9 @@ export const CARVE_GUARDS: Record = { // v1.2.0 activation lift: first-run-guidance section in the shared preamble. // Fork port wave 2 (#703): the repo-doc-preference block in the design // check grew every plan-review skeleton ~0.7KB. Measured values noted. - maxSkeletonBytes: 82_000, // measured 80,493 + // #2499 project-scope MCP jq in the brain-sync block grew every tier-2+ + // skeleton ~1.5KB (entry resolution emitted once per SKILL.md). + maxSkeletonBytes: 82_500, // measured 82,031 minUnionBytes: 70_000, mustContain: ['developer experience', 'Getting Started'], // Default-on Codex outside-voice (codexPreflight block + CODEX_MODE branch @@ -264,7 +268,9 @@ export const CARVE_GUARDS: Record = { // (judgment must be visible before the workflow directs the user to a // vendor site), plus the #703 dual-write + repo-doc-preference block and // the #538 opt-out + D1 evidence directive — ratio 1.104 measured. - maxSkeletonBytes: 101_000, + // #2499 project-scope MCP jq in the brain-sync block grew every tier-2+ + // skeleton ~1.5KB (entry resolution emitted once per SKILL.md). + maxSkeletonBytes: 101_500, // measured 101,314 minUnionBytes: 70_000, mustContain: ['design doc', 'problem statement'], maxSizeRatio: 1.12,