diff --git a/.claude/hooks/dist/heartbeat.mjs b/.claude/hooks/dist/heartbeat.mjs index 87e5963f..e5506617 100644 --- a/.claude/hooks/dist/heartbeat.mjs +++ b/.claude/hooks/dist/heartbeat.mjs @@ -2,7 +2,7 @@ import { spawnSync } from "child_process"; import { join } from "path"; function getPgConnectionString() { - return process.env.OPC_POSTGRES_URL || "postgresql://opc:opc_dev_password@localhost:5432/opc"; + return process.env.OPC_POSTGRES_URL || "process.env.DATABASE_URL || "postgresql://claude:claude_dev@localhost:5432/continuous_claude""; } function runPgQuery(pythonCode, args = []) { const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); diff --git a/.claude/hooks/dist/shared/db-utils-pg.js b/.claude/hooks/dist/shared/db-utils-pg.js index df9572d3..dec70ca1 100644 --- a/.claude/hooks/dist/shared/db-utils-pg.js +++ b/.claude/hooks/dist/shared/db-utils-pg.js @@ -25,7 +25,7 @@ export { SAFE_ID_PATTERN, isValidId } from './pattern-router.js'; */ export function getPgConnectionString() { return process.env.OPC_POSTGRES_URL || - 'postgresql://opc:opc_dev_password@localhost:5432/opc'; + process.env.DATABASE_URL || 'postgresql://claude:claude_dev@localhost:5432/continuous_claude'; } /** * Execute a PostgreSQL query via coordination_pg.py. @@ -148,7 +148,7 @@ import os pipeline_id = sys.argv[1] current_stage = int(sys.argv[2]) -pg_url = os.environ.get('OPC_POSTGRES_URL', 'postgresql://opc:opc_dev_password@localhost:5432/opc') +pg_url = os.environ.get('OPC_POSTGRES_URL', process.env.DATABASE_URL || 'postgresql://claude:claude_dev@localhost:5432/continuous_claude') async def main(): conn = await asyncpg.connect(pg_url) @@ -329,7 +329,7 @@ from datetime import datetime session_id = sys.argv[1] project = sys.argv[2] working_on = sys.argv[3] if len(sys.argv) > 3 else '' -pg_url = os.environ.get('OPC_POSTGRES_URL', 'postgresql://opc:opc_dev_password@localhost:5432/opc') +pg_url = os.environ.get('OPC_POSTGRES_URL', process.env.DATABASE_URL || 'postgresql://claude:claude_dev@localhost:5432/continuous_claude') async def main(): conn = await asyncpg.connect(pg_url) @@ -383,7 +383,7 @@ import json from datetime import datetime, timedelta project_filter = sys.argv[1] if len(sys.argv) > 1 and sys.argv[1] != 'null' else None -pg_url = os.environ.get('OPC_POSTGRES_URL', 'postgresql://opc:opc_dev_password@localhost:5432/opc') +pg_url = os.environ.get('OPC_POSTGRES_URL', process.env.DATABASE_URL || 'postgresql://claude:claude_dev@localhost:5432/continuous_claude') async def main(): conn = await asyncpg.connect(pg_url) @@ -456,7 +456,7 @@ import json file_path = sys.argv[1] project = sys.argv[2] my_session_id = sys.argv[3] -pg_url = os.environ.get('OPC_POSTGRES_URL', 'postgresql://opc:opc_dev_password@localhost:5432/opc') +pg_url = os.environ.get('OPC_POSTGRES_URL', process.env.DATABASE_URL || 'postgresql://claude:claude_dev@localhost:5432/continuous_claude') async def main(): conn = await asyncpg.connect(pg_url) @@ -516,7 +516,7 @@ import os file_path = sys.argv[1] project = sys.argv[2] session_id = sys.argv[3] -pg_url = os.environ.get('OPC_POSTGRES_URL', 'postgresql://opc:opc_dev_password@localhost:5432/opc') +pg_url = os.environ.get('OPC_POSTGRES_URL', process.env.DATABASE_URL || 'postgresql://claude:claude_dev@localhost:5432/continuous_claude') async def main(): conn = await asyncpg.connect(pg_url) @@ -558,7 +558,7 @@ session_id = sys.argv[1] topic = sys.argv[2] finding = sys.argv[3] relevant_to = json.loads(sys.argv[4]) if len(sys.argv) > 4 else [] -pg_url = os.environ.get('OPC_POSTGRES_URL', 'postgresql://opc:opc_dev_password@localhost:5432/opc') +pg_url = os.environ.get('OPC_POSTGRES_URL', process.env.DATABASE_URL || 'postgresql://claude:claude_dev@localhost:5432/continuous_claude') async def main(): conn = await asyncpg.connect(pg_url) @@ -609,7 +609,7 @@ import json query = sys.argv[1] exclude_session = sys.argv[2] limit = int(sys.argv[3]) -pg_url = os.environ.get('OPC_POSTGRES_URL', 'postgresql://opc:opc_dev_password@localhost:5432/opc') +pg_url = os.environ.get('OPC_POSTGRES_URL', process.env.DATABASE_URL || 'postgresql://claude:claude_dev@localhost:5432/continuous_claude') async def main(): conn = await asyncpg.connect(pg_url) diff --git a/.claude/hooks/dist/subagent-start.mjs b/.claude/hooks/dist/subagent-start.mjs index 54847e31..c8c50c4c 100644 --- a/.claude/hooks/dist/subagent-start.mjs +++ b/.claude/hooks/dist/subagent-start.mjs @@ -16,7 +16,7 @@ function isValidId(id) { import { spawnSync } from "child_process"; import { join } from "path"; function getPgConnectionString() { - return process.env.OPC_POSTGRES_URL || "postgresql://opc:opc_dev_password@localhost:5432/opc"; + return process.env.OPC_POSTGRES_URL || "process.env.DATABASE_URL || "postgresql://claude:claude_dev@localhost:5432/continuous_claude""; } function runPgQuery(pythonCode, args = []) { const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); diff --git a/.claude/hooks/dist/subagent-stop.mjs b/.claude/hooks/dist/subagent-stop.mjs index 450a6b61..f17792b0 100644 --- a/.claude/hooks/dist/subagent-stop.mjs +++ b/.claude/hooks/dist/subagent-stop.mjs @@ -16,7 +16,7 @@ function isValidId(id) { import { spawnSync } from "child_process"; import { join } from "path"; function getPgConnectionString() { - return process.env.OPC_POSTGRES_URL || "postgresql://opc:opc_dev_password@localhost:5432/opc"; + return process.env.OPC_POSTGRES_URL || "process.env.DATABASE_URL || "postgresql://claude:claude_dev@localhost:5432/continuous_claude""; } function runPgQuery(pythonCode, args = []) { const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); diff --git a/.claude/hooks/dist/working-on-sync.mjs b/.claude/hooks/dist/working-on-sync.mjs index f4a0921e..196b8e92 100644 --- a/.claude/hooks/dist/working-on-sync.mjs +++ b/.claude/hooks/dist/working-on-sync.mjs @@ -5,7 +5,7 @@ import { readFileSync } from "fs"; import { spawnSync } from "child_process"; import { join } from "path"; function getPgConnectionString() { - return process.env.OPC_POSTGRES_URL || "postgresql://opc:opc_dev_password@localhost:5432/opc"; + return process.env.OPC_POSTGRES_URL || "process.env.DATABASE_URL || "postgresql://claude:claude_dev@localhost:5432/continuous_claude""; } function runPgQuery(pythonCode, args = []) { const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); diff --git a/.claude/runtime/mcp_client.py b/.claude/runtime/mcp_client.py index 825f331e..5fc57b7b 100644 --- a/.claude/runtime/mcp_client.py +++ b/.claude/runtime/mcp_client.py @@ -147,70 +147,6 @@ def _unwrap_mcp_response(result: Any) -> Any: ] -# =========================================================================== -# Config Loading Helpers - Reduces complexity in initialize -# =========================================================================== - -async def _load_config_from_path(config_path: str) -> McpConfig: - """Load MCP config from explicit path. - - Args: - config_path: Path to config file - - Returns: - Loaded McpConfig - - Raises: - ConfigurationError: If load fails - """ - from pathlib import Path - path = Path(config_path) - if not path.exists(): - raise ConfigurationError(f"Config file not found: {config_path}") - try: - async with aiofiles.open(path) as f: - content = await f.read() - return McpConfig.model_validate_json(content) - except json.JSONDecodeError as e: - raise ConfigurationError(f"Invalid JSON in config file {config_path}: {e}") - except Exception as e: - raise ConfigurationError(f"Failed to load config from {config_path}: {e}") - - -def _find_project_config() -> Path | None: - """Find project config file (.mcp.json or mcp_config.json). - - Returns: - Path to project config if found, None otherwise - """ - project_root = find_project_root(Path.cwd()) - mcp_json = project_root / ".mcp.json" - mcp_config_json = project_root / "mcp_config.json" - - if mcp_json.exists(): - return mcp_json - elif mcp_config_json.exists(): - return mcp_config_json - return None - - -def _merge_configs(global_cfg: McpConfig | None, project_cfg: McpConfig | None) -> McpConfig | None: - """Merge global and project configs. - - Project config takes precedence for servers with same name. - - Args: - global_cfg: Global config (may be None) - project_cfg: Project config (may be None) - - Returns: - Merged config or None if both are None - """ - if global_cfg and project_cfg: - return global_cfg.merge(project_cfg) - return project_cfg or global_cfg - - class ConnectionState(Enum): """Explicit states for the MCP Client Manager lifecycle. diff --git a/CLAUDE.md b/CLAUDE.md index 76707594..d9a3ff72 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -183,7 +183,7 @@ cd opc && docker compose up -d ```bash # Database -DATABASE_URL=postgresql://opc:opc_dev_password@localhost:5432/opc +DATABASE_URL=postgresql://claude:claude_dev@localhost:5432/continuous_claude # Embeddings (optional) VOYAGE_API_KEY=... diff --git a/README.md b/README.md index de03abd7..b251981e 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Claude Code](https://img.shields.io/badge/Claude-Code-orange.svg)](https://claude.ai/code) -[![Skills](https://img.shields.io/badge/Skills-105-green.svg)](#skills-system) -[![Agents](https://img.shields.io/badge/Agents-32-purple.svg)](#agents-system) -[![Hooks](https://img.shields.io/badge/Hooks-65-blue.svg)](#hooks-system) +[![Skills](https://img.shields.io/badge/Skills-111-green.svg)](#skills-system) +[![Agents](https://img.shields.io/badge/Agents-16-purple.svg)](#agents-system) +[![Hooks](https://img.shields.io/badge/Hooks-31-blue.svg)](#hooks-system) **Continuous Claude** transforms Claude Code into a continuously learning system that maintains context across sessions, orchestrates specialized agents, and eliminates wasting tokens through intelligent code analysis. @@ -46,7 +46,7 @@ Claude Code has a **compaction problem**: when context fills up, the system comp | Starting fresh each session | Memory system recalls + daemon auto-extracts learnings | | Reading entire files burns tokens | 5-layer code analysis + semantic index | | Complex tasks need coordination | Meta-skills orchestrate agent workflows | -| Repeating workflows manually | 105 skills with natural language triggers | +| Repeating workflows manually | 111 skills with natural language triggers | **The mantra: Compound, don't compact.** Extract learnings automatically, then start fresh with full context. @@ -184,7 +184,7 @@ uv run python -m scripts.setup.wizard | 2 | Check prerequisites (Docker, Python, uv) | | 3-5 | Database + API key configuration | | 6-7 | Start Docker stack, run migrations | -| 8 | Install Claude Code integration (32 agents, 105 skills, 65 hooks) | +| 8 | Install Claude Code integration (16 agents, 111 skills, 31 hooks) | | 9 | Math features (SymPy, Z3, Pint - optional) | | 10 | TLDR code analysis tool | | 11-12 | Diagnostics tools + Loogle (optional) | @@ -593,7 +593,7 @@ Agents are specialized AI workers spawned via the Task tool. Located in `.claude Hooks intercept Claude Code at lifecycle points. Located in `.claude/hooks/`. -#### Hook Events (65 hooks total) +#### Hook Events (31 hooks total) | Event | Key Hooks | Purpose | |-------|-----------|---------| @@ -614,7 +614,7 @@ Hooks intercept Claude Code at lifecycle points. Located in `.claude/hooks/`. | **post-edit-diagnostics** | Runs pyright/ruff after edits | | **memory-awareness** | Surfaces relevant learnings | -[See all 65 hooks →](docs/hooks/) +[See all 31 hooks →](docs/hooks/) --- diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 430d9cb8..f05b23e4 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -18,5 +18,25 @@ services: retries: 5 restart: unless-stopped + pgbouncer: + image: pgbouncer/pgbouncer:latest + container_name: continuous-claude-pgbouncer + environment: + DATABASES_HOST: postgres + DATABASES_PORT: 5432 + DATABASES_USER: ${POSTGRES_USER:-claude} + DATABASES_PASSWORD: ${POSTGRES_PASSWORD:-claude_dev} + DATABASES_DBNAME: ${POSTGRES_DB:-continuous_claude} + PGBOUNCER_POOL_MODE: transaction + PGBOUNCER_MAX_CLIENT_CONN: 100 + PGBOUNCER_DEFAULT_POOL_SIZE: 20 + PGBOUNCER_IGNORE_STARTUP_PARAMETERS: extra_float_digits + ports: + - "6432:6432" + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + volumes: continuous_claude_data: diff --git a/opc/docker-compose.yml b/opc/docker-compose.yml index 7eab474f..a0477367 100644 --- a/opc/docker-compose.yml +++ b/opc/docker-compose.yml @@ -7,17 +7,16 @@ services: image: pgvector/pgvector:pg16 container_name: opc-postgres environment: - POSTGRES_USER: opc - POSTGRES_PASSWORD: opc_dev_password - POSTGRES_DB: opc + POSTGRES_USER: ${POSTGRES_USER:-claude} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-claude_dev} + POSTGRES_DB: ${POSTGRES_DB:-continuous_claude} ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data - - ./init-db.sql:/docker-entrypoint-initdb.d/01-init.sql:ro - - ../docker/init-schema.sql:/docker-entrypoint-initdb.d/02-memory.sql:ro + - ../docker/init-schema.sql:/docker-entrypoint-initdb.d/init-schema.sql:ro healthcheck: - test: ["CMD-SHELL", "pg_isready -U opc -d opc"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-claude} -d ${POSTGRES_DB:-continuous_claude}"] interval: 5s timeout: 5s retries: 5 @@ -29,9 +28,9 @@ services: environment: DATABASES_HOST: postgres DATABASES_PORT: 5432 - DATABASES_USER: opc - DATABASES_PASSWORD: opc_dev_password - DATABASES_DBNAME: opc + DATABASES_USER: ${POSTGRES_USER:-claude} + DATABASES_PASSWORD: ${POSTGRES_PASSWORD:-claude_dev} + DATABASES_DBNAME: ${POSTGRES_DB:-continuous_claude} PGBOUNCER_POOL_MODE: transaction PGBOUNCER_MAX_CLIENT_CONN: 100 PGBOUNCER_DEFAULT_POOL_SIZE: 20 diff --git a/opc/init-db.sql b/opc/init-db.sql deleted file mode 100644 index 8fb2b05c..00000000 --- a/opc/init-db.sql +++ /dev/null @@ -1,227 +0,0 @@ --- OPC v3 Database Schema --- Auto-run on first postgres startup - --- Enable extensions -CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -CREATE EXTENSION IF NOT EXISTS "vector"; - --- ============================================================================ --- COORDINATION TABLES (from 06-concurrency-coordination.md) --- ============================================================================ - --- Agents table - tracks all spawned agents -CREATE TABLE agents ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - session_id TEXT NOT NULL, - agent_id TEXT UNIQUE NOT NULL, - parent_agent_id UUID REFERENCES agents(id), - premise TEXT, - pattern TEXT, - role TEXT, -- Functional role within pattern (e.g., mapper, reducer, cruncher, validator) - depth_level INTEGER DEFAULT 1, - pid INTEGER, - swarm_id TEXT, -- Optional grouping for kill_swarm; defaults to session_id if not set - status TEXT DEFAULT 'running' CHECK (status IN ('running', 'completed', 'failed', 'orphaned', 'killed')), - spawned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - completed_at TIMESTAMPTZ, - error_message TEXT, - result_summary TEXT, - -- TUI observability columns for live agent monitoring - current_todos JSONB DEFAULT '[]', - last_tool TEXT, - last_tool_at TIMESTAMPTZ, - -- HUD observability columns (Phase 1 of TUI refactor) - handoff_to UUID REFERENCES agents(id), -- Link to continuation agent after context overflow - context_usage FLOAT DEFAULT 0.0 -- Current context % (0.0 to 1.0) -); - -CREATE INDEX idx_agents_session ON agents(session_id); -CREATE INDEX idx_agents_status ON agents(status); -CREATE INDEX idx_agents_pattern ON agents(pattern); -CREATE INDEX idx_agents_role ON agents(role) WHERE role IS NOT NULL; -CREATE INDEX idx_agents_parent ON agents(parent_agent_id); -CREATE INDEX idx_agents_swarm ON agents(swarm_id) WHERE swarm_id IS NOT NULL; -CREATE INDEX idx_agents_handoff ON agents(handoff_to) WHERE handoff_to IS NOT NULL; - --- Blackboard for inter-agent messaging (MESSAGE_PROTOCOL.md) -CREATE TABLE blackboard ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - swarm_id TEXT NOT NULL, - sender_agent TEXT NOT NULL, - message_type TEXT NOT NULL CHECK (message_type IN ('request', 'response', 'status', 'directive', 'checkpoint')), - target_agent TEXT, -- NULL = broadcast to all - priority TEXT DEFAULT 'normal' CHECK (priority IN ('low', 'normal', 'high', 'critical')), - payload JSONB NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - read_by JSONB DEFAULT '[]'::jsonb, -- Array of agent_ids that have read this - archived_at TIMESTAMPTZ -- For cleanup -); - -CREATE INDEX idx_blackboard_swarm ON blackboard(swarm_id, created_at); -CREATE INDEX idx_blackboard_target ON blackboard(target_agent) WHERE target_agent IS NOT NULL; -CREATE INDEX idx_blackboard_priority ON blackboard(priority) WHERE priority = 'critical'; - --- Sandbox computations (shared state between agents) -CREATE TABLE sandbox_computations ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - session_id TEXT NOT NULL, - key TEXT NOT NULL, - value JSONB NOT NULL, - computed_by TEXT NOT NULL, -- agent_id - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - expires_at TIMESTAMPTZ, - UNIQUE(session_id, key) -); - -CREATE INDEX idx_sandbox_session_key ON sandbox_computations(session_id, key); - --- ============================================================================ --- SPAWN QUEUE (from master spec - replaces "spawn blackboard") --- ============================================================================ - -CREATE TABLE spawn_queue ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - requester_agent TEXT NOT NULL, - target_agent_type TEXT NOT NULL, -- e.g. 'kraken', 'scout', 'sleuth' - depth_level INTEGER NOT NULL, - priority TEXT DEFAULT 'normal' CHECK (priority IN ('low', 'normal', 'high', 'critical')), - status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected', 'spawned')), - payload JSONB NOT NULL, -- Task description, context - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - processed_at TIMESTAMPTZ, - spawned_agent_id UUID REFERENCES agents(id), - depends_on UUID[] DEFAULT '{}', -- DAG: IDs of tasks that must complete first - blocked_by_count INTEGER DEFAULT 0 -- Kahn's algorithm: count of unspawned dependencies -); - -CREATE INDEX idx_spawn_queue_status ON spawn_queue(status, priority, created_at); -CREATE INDEX idx_spawn_queue_requester ON spawn_queue(requester_agent); -CREATE INDEX idx_spawn_queue_deps ON spawn_queue USING GIN(depends_on); --- Partial index for O(1) lookup of ready tasks -CREATE INDEX idx_spawn_queue_ready ON spawn_queue(status, blocked_by_count) WHERE status = 'pending' AND blocked_by_count = 0; - --- ============================================================================ --- TEMPORAL MEMORY (from 02-temporal-memory-assessment.md) --- ============================================================================ - -CREATE TABLE temporal_facts ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - session_id TEXT NOT NULL, - fact_type TEXT NOT NULL, -- 'observation', 'decision', 'learning', 'preference' - content TEXT NOT NULL, - embedding vector(1536), -- OpenAI ada-002 dimension - confidence FLOAT DEFAULT 1.0, - source_turn INTEGER, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - expires_at TIMESTAMPTZ, - metadata JSONB DEFAULT '{}'::jsonb -); - -CREATE INDEX idx_temporal_session ON temporal_facts(session_id, created_at DESC); -CREATE INDEX idx_temporal_type ON temporal_facts(fact_type); -CREATE INDEX idx_temporal_embedding ON temporal_facts USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); - --- ============================================================================ --- AGENT LOGS (centralized logging from Round 3 decisions) --- ============================================================================ - -CREATE TABLE agent_logs ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - agent_id TEXT NOT NULL, - session_id TEXT NOT NULL, - level TEXT NOT NULL CHECK (level IN ('debug', 'info', 'warn', 'error')), - message TEXT NOT NULL, - metadata JSONB DEFAULT '{}'::jsonb, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX idx_agent_logs_agent ON agent_logs(agent_id, created_at DESC); -CREATE INDEX idx_agent_logs_session ON agent_logs(session_id, created_at DESC); -CREATE INDEX idx_agent_logs_level ON agent_logs(level) WHERE level IN ('warn', 'error'); - --- ============================================================================ --- CHECKPOINTS (crash recovery from Round 3) --- ============================================================================ - -CREATE TABLE checkpoints ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - agent_id TEXT NOT NULL, - session_id TEXT NOT NULL, - phase TEXT NOT NULL, - context_usage FLOAT, - files_modified JSONB DEFAULT '[]'::jsonb, - unknowns JSONB DEFAULT '[]'::jsonb, - handoff_path TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX idx_checkpoints_agent ON checkpoints(agent_id, created_at DESC); -CREATE INDEX idx_checkpoints_session ON checkpoints(session_id, created_at DESC); - --- ============================================================================ --- USER PREFERENCES (from 07-z3-erotetic.md - personalization) --- ============================================================================ - -CREATE TABLE user_preferences ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id TEXT NOT NULL, - proposition TEXT NOT NULL, - choice TEXT NOT NULL, - context TEXT, - count INTEGER DEFAULT 1, - last_used TIMESTAMPTZ DEFAULT NOW(), - UNIQUE(user_id, proposition, choice) -); - -CREATE INDEX idx_preferences_user ON user_preferences(user_id); -CREATE INDEX idx_preferences_proposition ON user_preferences(user_id, proposition); - --- ============================================================================ --- FINDINGS (research/tracking findings) --- ============================================================================ - -CREATE TABLE IF NOT EXISTS findings ( - id SERIAL PRIMARY KEY, - session_id TEXT NOT NULL, - topic TEXT NOT NULL, - finding TEXT NOT NULL, - relevant_to TEXT[], - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX idx_findings_session ON findings(session_id); -CREATE INDEX idx_findings_topic ON findings(topic); -CREATE INDEX idx_findings_created ON findings(created_at DESC); -CREATE INDEX idx_findings_relevant ON findings USING GIN(relevant_to); - --- ============================================================================ --- HELPER FUNCTIONS --- ============================================================================ - --- Cleanup old blackboard messages (run periodically) -CREATE OR REPLACE FUNCTION archive_old_blackboard_messages(older_than INTERVAL DEFAULT '7 days') -RETURNS INTEGER AS $$ -DECLARE - archived_count INTEGER; -BEGIN - UPDATE blackboard - SET archived_at = NOW() - WHERE archived_at IS NULL - AND created_at < NOW() - older_than; - GET DIAGNOSTICS archived_count = ROW_COUNT; - RETURN archived_count; -END; -$$ LANGUAGE plpgsql; - --- Get active agent count for session (resource limiting) -CREATE OR REPLACE FUNCTION get_active_agent_count(p_session_id TEXT) -RETURNS INTEGER AS $$ -BEGIN - RETURN ( - SELECT COUNT(*) - FROM agents - WHERE session_id = p_session_id - AND status = 'running' - ); -END; -$$ LANGUAGE plpgsql; diff --git a/opc/scripts/claude_spawn.py b/opc/scripts/claude_spawn.py index 867fe271..71c3d272 100644 --- a/opc/scripts/claude_spawn.py +++ b/opc/scripts/claude_spawn.py @@ -196,7 +196,7 @@ async def _update_agent_status( import asyncpg conn = await asyncpg.connect( - "postgresql://opc:opc_dev_password@localhost:5432/opc" + os.environ.get("DATABASE_URL", "postgresql://claude:claude_dev@localhost:5432/continuous_claude") ) await conn.execute( """ diff --git a/opc/src/runtime/mcp_client.py b/opc/src/runtime/mcp_client.py index 584d1eaf..6053080a 100644 --- a/opc/src/runtime/mcp_client.py +++ b/opc/src/runtime/mcp_client.py @@ -156,70 +156,6 @@ def _unwrap_mcp_response(result: Any) -> Any: return unwrapped -# =========================================================================== -# Config Loading Helpers - Reduces complexity in initialize -# =========================================================================== - -async def _load_config_from_path(config_path: str) -> McpConfig: - """Load MCP config from explicit path. - - Args: - config_path: Path to config file - - Returns: - Loaded McpConfig - - Raises: - ConfigurationError: If load fails - """ - from pathlib import Path - path = Path(config_path) - if not path.exists(): - raise ConfigurationError(f"Config file not found: {config_path}") - try: - async with aiofiles.open(path) as f: - content = await f.read() - return McpConfig.model_validate_json(content) - except json.JSONDecodeError as e: - raise ConfigurationError(f"Invalid JSON in config file {config_path}: {e}") - except Exception as e: - raise ConfigurationError(f"Failed to load config from {config_path}: {e}") - - -def _find_project_config() -> Path | None: - """Find project config file (.mcp.json or mcp_config.json). - - Returns: - Path to project config if found, None otherwise - """ - project_root = find_project_root(Path.cwd()) - mcp_json = project_root / ".mcp.json" - mcp_config_json = project_root / "mcp_config.json" - - if mcp_json.exists(): - return mcp_json - elif mcp_config_json.exists(): - return mcp_config_json - return None - - -def _merge_configs(global_cfg: McpConfig | None, project_cfg: McpConfig | None) -> McpConfig | None: - """Merge global and project configs. - - Project config takes precedence for servers with same name. - - Args: - global_cfg: Global config (may be None) - project_cfg: Project config (may be None) - - Returns: - Merged config or None if both are None - """ - if global_cfg and project_cfg: - return global_cfg.merge(project_cfg) - return project_cfg or global_cfg - - class ConnectionState(Enum): """Explicit states for the MCP Client Manager lifecycle. diff --git a/scripts/update-doc-counts.py b/scripts/update-doc-counts.py old mode 100644 new mode 100755