feat(cipher): Cipher Memory MCP bridge + Claude config unification - #621
Conversation
Add two new submodules that were created during Supabase enhancement and VPS-related PRs but were missing from .gitmodules: - PMOVES-Headscale: Tailscale control server for VPS coordination (branch: PMOVES.AI-Edition-Hardened) - PMOVES-supabase: Self-hosted Supabase integration components Also updates: - scripts/with-env.sh: Load env.tier-supabase in tier sequence - env.tier-supabase.example: Supabase environment template (copy to env.tier-supabase) - .gitignore: Add negation pattern to allow env.tier-*.example files - docs/SUBMODULE_LIST.md: Document new submodules Users should copy env.tier-supabase.example to env.tier-supabase and fill in their actual values before running bring-up. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add supabase to all tier environment handling targets so bring-up scripts automatically create and populate Supabase credentials: - bootstrap-tier-envs: Creates env.tier-supabase from example - populate-tier-envs: Auto-generates JWT secret, anon key, service role key, realtime secret, and postgres password via openssl rand - check-tier-envs: Validates env.tier-supabase exists before services start - .gitignore: Add negation pattern to allow env.tier-*.example files This ensures Supabase auth credentials are properly configured during automated bring-up without manual intervention. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix ROOT_DIR calculation in with-env.sh to handle sourced/executed cases
- Fix load_env_file variable expansion for ${VAR:-default} syntax
- Remove duplicate /pmoves path from tier env file loading
- Update env.tier-supabase.example with complete Supabase configuration
- Fix POSTGRES_DB in env.shared (cataclysm_pmoves → pmoves)
This resolves environment variable issues that were preventing
Supabase services from starting during incremental bring-up.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix supabase-bootstrap to use correct database credentials (pmoves/pmoves) - Fix supabase-bootstrap to use TCP connection (-h localhost) for auth - Fix seed path from supabase/seeds to supabase/initdb - Add NEO4J_server_config_strict__validation_enabled=false for Neo4j 5.x These fixes address issues encountered during Supabase bring-up on PMOVES.AI-Edition-Hardened branch where: 1. Migrations failed with "peer authentication" errors 2. Seed scripts weren't found (wrong directory path) 3. Neo4j 5.x failed with "Unrecognized setting: PASSWORD" Related: PMOVES-supabase submodule integration
Document common issues encountered during Supabase bring-up on PMOVES.AI-Edition-Hardened branch and their fixes: - PostgreSQL peer authentication errors (need -h localhost) - Missing schemas and roles (manual setup required) - Kong migration bootstrap requirements - Neo4j 5.x config validation issues - Storage service region requirements Includes complete bring-up sequence for fresh deployments.
- PMOVES-Archon: Switched from feat/personas-clean-rebase to Hardened - PMOVES-ToKenism-Multi: Reset to origin/PMOVES.AI-Edition-Hardened - PMOVES-supabase: Re-registered as proper gitlink submodule - PMOVES-DoX: Now on PMOVES.AI-Edition-Hardened (external switch) - PMOVES-BoTZ nested in DoX: On PMOVES.AI-Edition-Hardened-DoX This ensures all submodules track the production hardened branch for consistent deployment and CI/CD alignment. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Document Supabase submodule update procedure - Add critical environment loading fix for docker compose - Document Realtime service requirements (64-byte SECRET_KEY_BASE) - Add submodule branch alignment table (2026-02-06) - Note PMOVES-Headscale lacks Hardened branch (main only) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- PMOVES-Headscale: Now on PMOVES.AI-Edition-Hardened (user created) - Update docs to reflect all submodules on Hardened 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Clarify working directory for environment loading commands - Add branch-only update path in CI/CD procedure - Fix SUPABASE_REALTIME_SECRET variable naming documentation - Add env file creation instruction (cp from example) - Rename section to "Key Submodules" (not complete list) - Add verification commands for submodule alignment - Fix PMOVES-DoX submodule reference update - Note PMOVES-DoX is external submodule 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace exposed Venice API key with placeholder. Users must generate their own key at https://venice.ai Security: P0 - Actual API key was exposed in repository Risk: Unauthorized API usage, cost implications Action Required: - Revoke exposed key: kTBQ__hQVA8zSwilSNF8e5q6oEGtW9f8mlxCFZBqXh - Generate new key for production use if needed Addresses PR-1 from Production Hardening Tracker 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Codex Agent <codex-agent@example.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…parsing issues" This reverts commit 68975ea.
Implements production-ready remote access platform with Headscale (self-hosted Tailscale) + RustDesk + BoTZ MCP integration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CHIT code audit tracking document - all CHIT/GEOMETRY BUS/Evoswarm code verified present on Hardened 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes CI cancellation issue by correcting runner label from [self-hosted, vps] to [self-hosted, ai-lab]
Add pre-flight validation for LLM provider API keys to catch configuration issues before docker compose attempts to start services. Changes: - Add check for core LLM providers (OpenAI, Anthropic, Groq, Gemini, Ollama) - Warn if no LLM providers are configured - Display status of optional API keys (ElevenLabs, Voyage, Cohere, Cloudflare) - Detect empty/placeholder API key values Resolves CROSS_PLATFORM_TASKS.md - Issue K3 (Priority 2). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add platform-specific documentation for bringing up PMOVES.AI on WSL2 with Docker Desktop. Topics covered: - Prerequisites and Docker Desktop configuration - Known WSL2 issues and solutions (bind mounts, network labels, env expansion) - Step-by-step bring-up procedure - Service URLs for WSL2 environment - Troubleshooting common WSL2 problems - Path mapping and firewall considerations - GPU passthrough setup Resolves CROSS_PLATFORM_TASKS.md - Documentation Task D1. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit addresses critical issues found during PR review: **PR #592 - TensorZero Port Consistency** - Fix internal container-to-container communication to use port 3000 (not host port 3030) across all services - Files updated: gateway-agent, deepresearch, agent-zero, n8n flows, tokenism-simulator, mcp configs, docker-compose overrides **PR #593 - ClickHouse Configuration** - Fix ClickHouse URL format to include embedded credentials - Remove unused separate USER/PASSWORD/DB environment variables **PR #595 - Documentation Port Fixes** - Correct Grafana port from 3000 to 3002 in documentation **PR #590 - Docker Compose --project-directory** - Add --project-directory to DC variable for WSL2 compatibility - Convert inline docker compose commands to use $(DC) variable - Fix up-supabase, backup, brand-defaults, and neo4j targets **PR #594 - Network Cleanup with Error Handling** - Add clean-networks target with improved error reporting - Provides warnings instead of silent failures - Integrated into 'up' target as pre-flight check **New Automated Tool** - Add port-consistency-check.sh script for validation - Can detect and auto-fix port inconsistencies Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit incorporates the remaining changes from open PRs that were not included in the initial port consistency fix commit: **From PR #590 (WSL2 --project-directory)** - Add --project-directory to apply_migrations_docker.sh - Add --project-directory to smoke-tests.sh - Add --project-directory to validate-phase1-hardening.sh - Remove unsafe 'cd' pattern, use explicit paths instead **From PR #591 (API Key Validation)** - Add provider API key validation to env_check.sh - Checks for OpenAI, Anthropic, Groq, Gemini, Ollama - Warns about empty/placeholder API keys - Provides helpful configuration tips **From PR #587 (Security Update)** - Update python-multipart to 0.0.22 (CVE-2026-24486 fix) - Applied to agentgym-rl-coordinator and Enhanced Media Stack plans - NOTE: Transformers constraint kept at <4.50.0 for FlagEmbedding All superseded PRs can now be closed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… PMOVES.AI-Edition-Hardened # Conflicts: # pmoves/Makefile
- Created comprehensive production readiness audit for PMOVES.AI-Edition-Hardened - Documented submodule branch alignment issues and PR requirements - Created PMOVES-DoX PR #96 for PostgreSQL 17 compatibility fixes - All critical submodules verified on PMOVES.AI-Edition-Hardened branches
- Migrated e2b components from vendor/ paths to forked submodules - Investigation confirmed no code was lost during migration - Vendor directories were empty with no local git history - Commented out entries preserved for branch transition reference - All code preserved in forked repositories: - PMOVES-E2B-Danger-Room - PMOVES-E2B-Danger-Room-Desktop - pmoves-e2b-mcp-server - PMOVES-Danger-infra - PMOVES-E2b-Spells Fixes git submodule errors when running: git submodule update --init --recursive Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bumps python-multipart from 0.0.20 to 0.0.22 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Created PMOVES-DoX #96 for PostgreSQL 17 compatibility - Created PMOVES-BoTZ #51 for TensorZero 2026 migration - Created PMOVES-Agent-Zero #3 for PMOVES.AI integration patterns - Fixed PMOVES-tensorzero to PMOVES.AI-Edition-Hardened branch - Fixed Pmoves-hyperdimensions to PMOVES.AI-Edition-Hardened branch - Verified PMOVES-Wealth on origin/main (fork with upstream) - Cleaned up orphan e2b submodule reference - Created PMOVES.AI-Edition-Hardened-DoX variant branches where needed
| if line.startswith(f"{label}="): | ||
| lines[i] = f"{label}={value}" | ||
| updated = True | ||
| print(f"Updated: {label}") |
Check failure
Code scanning / CodeQL
Clear-text logging of sensitive information
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 months ago
In general, the problem is that data derived from a secrets source (cgp["points"]) is being logged directly. To fix this without changing core functionality, we should avoid printing sensitive or potentially sensitive identifiers and instead log only non-sensitive, aggregate information (e.g., counts) or use redacted placeholders. The goal is to preserve some feedback to the operator that the script is doing work, while ensuring that no secret-related identifiers from secrets are written to output.
The best minimal change here is to remove or neutralize the per-secret log lines on 31 and 39 so they do not interpolate label. We can replace them with generic messages that do not include label, such as "Updated 1 secret entry" or similar, or even omit those logs entirely and rely on the existing final summary line 45, which already logs only the count of credentials: len(secrets) (a non-sensitive integer). Given the desire to keep progress indications, a safe compromise is to print generic messages without referencing label. Concretely:
- In
pmoves/scripts/update_env_from_cgp.py, change line 31 fromprint(f"Updated: {label}")toprint("Updated one credential entry")(or just remove it). - Similarly change line 39 from
print(f"Updated (empty): {label}")toprint("Updated one previously empty credential entry").
No new imports or methods are required; we simply adjust the string literals passed toprintto avoid using tainted data.
| @@ -28,7 +28,7 @@ | ||
| if line.startswith(f"{label}="): | ||
| lines[i] = f"{label}={value}" | ||
| updated = True | ||
| print(f"Updated: {label}") | ||
| print("Updated one credential entry") | ||
| break | ||
|
|
||
| if not updated and f"{label}=" in env_content: | ||
| @@ -36,7 +36,7 @@ | ||
| for i, line in enumerate(lines): | ||
| if line == f"{label}=": | ||
| lines[i] = f"{label}={value}" | ||
| print(f"Updated (empty): {label}") | ||
| print("Updated one previously empty credential entry") | ||
| break | ||
|
|
||
| # Write back |
| for i, line in enumerate(lines): | ||
| if line == f"{label}=": | ||
| lines[i] = f"{label}={value}" | ||
| print(f"Updated (empty): {label}") |
Check failure
Code scanning / CodeQL
Clear-text logging of sensitive information
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 months ago
Generally, to fix clear-text logging of sensitive information, remove sensitive values (and often identifiers closely tied to them) from logs, or replace them with non-sensitive, non-identifying summaries (counts, generic messages, or redacted forms). Any code path that takes data from a secrets structure and passes it into a logging or printing function should be treated as suspect.
In this script, the label is derived from a secrets file and used in two log messages: print(f"Updated: {label}") and print(f"Updated (empty): {label}"). To preserve existing functionality while improving security, we can change these prints to avoid including the label. Since the primary function of the log is to show progress, we can instead print a generic message like "Updated a secret from CGP" and "Updated an empty secret from CGP", or similar wording, without interpolating label. This requires only small modifications to the two print statements within the loop (lines 31 and 39) in pmoves/scripts/update_env_from_cgp.py; no new imports or helper methods are needed.
Concretely:
- In the first update branch (lines 28–32), replace
print(f"Updated: {label}")with a non-identifying message, e.g.print("Updated a secret from CGP"). - In the second branch for empty values (lines 36–40), replace
print(f"Updated (empty): {label}")with something likeprint("Updated an empty secret from CGP").
No other changes are necessary to maintain behavior: the environment file content is unchanged; only log verbosity/contents are reduced.
| @@ -28,7 +28,7 @@ | ||
| if line.startswith(f"{label}="): | ||
| lines[i] = f"{label}={value}" | ||
| updated = True | ||
| print(f"Updated: {label}") | ||
| print("Updated a secret from CGP") | ||
| break | ||
|
|
||
| if not updated and f"{label}=" in env_content: | ||
| @@ -36,7 +36,7 @@ | ||
| for i, line in enumerate(lines): | ||
| if line == f"{label}=": | ||
| lines[i] = f"{label}={value}" | ||
| print(f"Updated (empty): {label}") | ||
| print("Updated an empty secret from CGP") | ||
| break | ||
|
|
||
| # Write back |
|
|
||
| try: | ||
| # Create cache directory (must be inside try block for error handling) | ||
| cache_dir.mkdir(parents=True, exist_ok=True) |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 months ago
In general, to fix uncontrolled data in path expressions, either (1) constrain user input to a safe subset (e.g., a whitelist of allowed characters or patterns), or (2) build the path under a fixed root and verify the normalized result stays within that root. Here the user-controlled value is not intended to be an arbitrary filesystem path; it is a Hugging Face model ID. The most compatible fix is to sanitize and validate hf_id before using it as a directory name, ensuring we only use safe characters and a reasonable maximum length, while still allowing all typical HF model IDs.
Concretely, we can introduce a small helper function, e.g. def _safe_model_cache_key(model_id: str) -> str, that:
- Replaces
/with--(as currently). - Replaces or removes any characters outside a safe allow‑list (letters, digits,
-,_,.,:). - Optionally truncates the resulting name to a reasonable length to avoid filesystem issues.
Then we use this sanitized key when constructingcache_dir, i.e.safe_key = _safe_model_cache_key(hf_id)andcache_dir = Path(HF_HUB_CACHE) / "models" / safe_key. This preserves existing behavior for normal IDs while ensuring that even malicious values cannot inject problematic path characters.
All changes occur in pmoves/services/hf-mcp-server/main.py. We only need to add the helper function (no new imports beyond the standard library that is already in use) and adjust the cache_dir assignment to use it.
| @@ -44,6 +44,27 @@ | ||
| level=logging.INFO, | ||
| format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", | ||
| ) | ||
|
|
||
|
|
||
| def _safe_model_cache_key(model_id: str) -> str: | ||
| """ | ||
| Sanitize a model identifier so it can be safely used as a single | ||
| directory name under the model cache root. | ||
|
|
||
| This is not meant to restrict which Hugging Face models can be | ||
| requested; it only constrains how we map the ID onto the local | ||
| filesystem to avoid using untrusted strings directly as paths. | ||
| """ | ||
| # Start from the normalized HF ID: replace path separators first. | ||
| key = model_id.replace("/", "--") | ||
|
|
||
| # Allow only a conservative set of characters in directory names. | ||
| allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.:" | ||
| key = "".join(c if c in allowed else "_" for c in key) | ||
|
|
||
| # Avoid excessively long directory names which could cause OS errors. | ||
| # 128 characters should be enough for realistic HF model IDs. | ||
| return key[:128] | ||
| logger = logging.getLogger(__name__) | ||
|
|
||
| # Environment variables | ||
| @@ -498,7 +519,9 @@ | ||
| hf_id = model_id | ||
| model_data = {} | ||
|
|
||
| cache_dir = Path(HF_HUB_CACHE) / "models" / hf_id.replace("/", "--") | ||
| # Sanitize the model identifier before using it as a directory name. | ||
| safe_cache_key = _safe_model_cache_key(hf_id) | ||
| cache_dir = Path(HF_HUB_CACHE) / "models" / safe_cache_key | ||
|
|
||
| try: | ||
| # Create cache directory (must be inside try block for error handling) |
|
|
||
| cache_dir = Path(HF_HUB_CACHE) / "models" / model_id.replace("/", "--") | ||
|
|
||
| if not cache_dir.exists(): |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 months ago
In general, the fix is to sanitize or validate any user-controlled value before using it to construct filesystem paths. For this case, we only need the cache subdirectory derived from model_id to be a safe name under HF_HUB_CACHE / "models". We can achieve this by (1) mapping the model ID to a directory-friendly name (already partially done with .replace("/", "--")), and (2) ensuring that the resulting name is free of problematic characters and does not become an absolute path or contain path separators. One robust approach is to reduce the name to a limited character set (for example, alphanumerics, dash, underscore, and dot), substituting anything else with a safe character like _. This preserves functionality (different model_ids will still map to deterministic directory names) while eliminating potential path traversal or weird-platform edge cases.
The single best fix here without changing existing behavior much is to introduce a small helper to "sanitize" the model-id-derived directory name, and then use that sanitized name instead of raw model_id.replace("/", "--") in hf_model_convert_gguf. Since we can only edit the shown file, we’ll:
- Add an
import reat the top (using only standard library). - Define a helper function, e.g.
_safe_model_cache_dir_name(model_id: str) -> str, that:- Replaces
/with--(keeping existing behavior). - Uses a regex to replace any character not in
[A-Za-z0-9._-]with_. - Optionally strips leading dots to avoid hidden or special names like
... - Ensures we never accidentally introduce path separators.
- Replaces
- Change the construction of
cache_dirinhf_model_convert_gguf(line 611) to use this helper instead of inliningmodel_id.replace("/", "--").
All changes will be within pmoves/services/hf-mcp-server/main.py. No other files are modified.
| @@ -27,6 +27,7 @@ | ||
| from enum import Enum | ||
| from pathlib import Path | ||
| from typing import Any, Dict, List, Optional | ||
| import re | ||
|
|
||
| import aiohttp | ||
| from fastapi import FastAPI, HTTPException | ||
| @@ -590,6 +591,26 @@ | ||
| return results | ||
|
|
||
|
|
||
| def _safe_model_cache_dir_name(model_id: str) -> str: | ||
| """ | ||
| Generate a safe directory name for a model ID under the cache root. | ||
|
|
||
| This preserves the existing convention of replacing "/" with "--", | ||
| and additionally restricts the resulting name to a safe character set. | ||
| """ | ||
| # Preserve existing mapping from "org/model" -> "org--model" | ||
| name = model_id.replace("/", "--") | ||
| # Replace any character that is not alphanumeric, dot, underscore, or dash | ||
| name = re.sub(r"[^A-Za-z0-9._-]", "_", name) | ||
| # Avoid special/hidden names like ".", "..", or those starting with them | ||
| while name.startswith("."): | ||
| name = name[1:] | ||
| # Fallback to a generic name if everything was stripped | ||
| if not name: | ||
| name = "model" | ||
| return name | ||
|
|
||
|
|
||
| async def hf_model_convert_gguf( | ||
| model_id: str, | ||
| quantize: str = "q4_0", | ||
| @@ -608,7 +629,8 @@ | ||
| # This is a placeholder - actual GGUF conversion requires llama.cpp | ||
| # In production, this would spawn a conversion job or call an external service | ||
|
|
||
| cache_dir = Path(HF_HUB_CACHE) / "models" / model_id.replace("/", "--") | ||
| safe_dir_name = _safe_model_cache_dir_name(model_id) | ||
| cache_dir = Path(HF_HUB_CACHE) / "models" / safe_dir_name | ||
|
|
||
| if not cache_dir.exists(): | ||
| raise HTTPException( |
…udit hardened: layered local bring-up + archon/env + monitoring alignment
Docker Hardening ValidationHardening Validation ReportValidated: Mon 16 Feb 2026 12:21:05 AM UTCServices CheckedPMOVES.AI Docker Hardening Validation[INFO] Checking: pmoves/docker-compose.hardened.yml [INFO] Validating: hi-rag-gateway-v2 [INFO] Validating: extract-worker [INFO] Validating: langextract [INFO] Validating: presign [INFO] Validating: render-webhook [INFO] Validating: retrieval-eval [INFO] Validating: pdf-ingest [INFO] Validating: jellyfin-bridge [INFO] Validating: invidious-companion-proxy [INFO] Validating: ffmpeg-whisper [INFO] Validating: media-video [INFO] Validating: media-audio [INFO] Validating: hi-rag-gateway-v2-gpu [INFO] Validating: hi-rag-gateway-gpu [INFO] Validating: deepresearch [INFO] Validating: supaserch [INFO] Validating: publisher-discord [INFO] Validating: mesh-agent [INFO] Validating: nats-echo-req [INFO] Validating: nats-echo-res [INFO] Validating: publisher [INFO] Validating: analysis-echo [INFO] Validating: graph-linker [INFO] Validating: comfy-watcher [INFO] Validating: grayjay-plugin-host [INFO] Validating: agent-zero [INFO] Validating: archon [INFO] Validating: channel-monitor [INFO] Validating: pmoves-yt [INFO] Validating: notebook-sync [INFO] Validating: supabase_service_role_key [INFO] Validating: supabase_jwt_secret ====================================== |
Summary
pmoves-cipher-mcp/): Python MCP server (stdio transport) bridging Claude Code CLI to Cipher Memory via HTTP. 4 tools:store,search,store_reasoning,reasoning_patterns.cipher-api): Added topmoves/docker-compose.ymlon port 8096 (agents profile), sharing existing Neo4j + NATS. Hardened with*tier-agent-hardenedanchor..claude/mcp.json): Registers the MCP server for automatic tool availability..claude/skills/pmoves-cipher-memory/): SKILL.md with usage patterns, categories, memory hints + skill.json manifest..claude/settings.json): Registered existing damage-control PreToolUse hooks (Bash/Edit/Write) that were on disk but unregistered.Architecture
Files Changed (20 files, +1458 lines)
pmoves-cipher-mcp/(11 files: server, tools, client, health, announcer, common, registry, main, pyproject, README).claude/mcp.json.claude/skills/pmoves-cipher-memory/SKILL.md.claude/skills/pmoves-cipher-memory/skill.json.claude/settings.json— hooks wiringpmoves/docker-compose.yml— cipher-api service.gitignore— cipher MCP entries.claude/CLAUDE.md,.claude/context/services-catalog.md,.claude/context/submodules.mdTest plan
git submodule status Pmoves-cipher— shows51eea54(main)from cipher_mcp.server import app— OKdocker compose --profile agents config --services | grep cipher— showscipher-apimcp.json,skill.json,settings.jsoncipher-apicontainer build (requires Neo4j + NATS running)🤖 Generated with Claude Code