From 4238fafaa8318731b34ab321e209092dce4cda0a Mon Sep 17 00:00:00 2001 From: David Abram Date: Wed, 8 Apr 2026 15:45:17 +0200 Subject: [PATCH 1/6] feat: Remove Agent Trace persistence and reset hooks to no-op baseline Remove the Agent Trace implementation from the CLI runtime to establish a clean v0.3.0 redesign baseline Co-authored-by: SCE --- cli/src/app.rs | 10 - cli/src/cli_schema.rs | 6 - cli/src/command_surface.rs | 7 - cli/src/services/hooks.rs | 382 +----------------- cli/src/services/local_db.rs | 210 +--------- cli/src/services/mod.rs | 1 - cli/src/services/sync.rs | 242 ----------- context/architecture.md | 6 +- context/context-map.md | 7 +- context/glossary.md | 9 +- context/overview.md | 16 +- context/patterns.md | 8 +- ...agent-trace-removal-and-hook-noop-reset.md | 63 +++ .../sce/agent-trace-core-schema-migrations.md | 36 +- ...ace-local-hooks-mvp-contract-gap-matrix.md | 2 +- .../sce/agent-trace-post-commit-dual-write.md | 17 +- ...t-trace-reconciliation-schema-ingestion.md | 47 --- ...gent-trace-rewrite-trace-transformation.md | 6 +- 18 files changed, 135 insertions(+), 940 deletions(-) delete mode 100644 cli/src/services/sync.rs create mode 100644 context/plans/agent-trace-removal-and-hook-noop-reset.md delete mode 100644 context/sce/agent-trace-reconciliation-schema-ingestion.md diff --git a/cli/src/app.rs b/cli/src/app.rs index 42dd460..b0edf8d 100644 --- a/cli/src/app.rs +++ b/cli/src/app.rs @@ -16,7 +16,6 @@ enum Command { Doctor(services::doctor::DoctorRequest), Hooks(services::hooks::HookSubcommand), Trace(services::trace::TraceRequest), - Sync(services::sync::SyncRequest), Version(services::version::VersionRequest), } @@ -32,7 +31,6 @@ impl Command { Self::Doctor(_) => services::doctor::NAME, Self::Hooks(_) => services::hooks::NAME, Self::Trace(_) => services::trace::NAME, - Self::Sync(_) => services::sync::NAME, Self::Version(_) => services::version::NAME, } } @@ -464,9 +462,6 @@ fn convert_clap_command(command: cli_schema::Commands) -> Result convert_hooks_subcommand(subcommand), cli_schema::Commands::Trace { subcommand } => convert_trace_subcommand(subcommand), - cli_schema::Commands::Sync { format } => Ok(Command::Sync(services::sync::SyncRequest { - format: convert_output_format(format), - })), cli_schema::Commands::Version { format } => { Ok(Command::Version(services::version::VersionRequest { format: convert_output_format(format), @@ -656,7 +651,6 @@ fn dispatch( Command::Auth(request) => services::auth_command::run_auth_subcommand(*request) .map_err(|error| ClassifiedError::runtime(error.to_string())), Command::Completion(request) => Ok(services::completion::render_completion(*request)), - // Clone required: run_config_subcommand takes ownership of ConfigSubcommand Command::Config(subcommand) => services::config::run_config_subcommand(subcommand.clone()) .map_err(|error| ClassifiedError::runtime(error.to_string())), Command::Setup(request) => { @@ -712,14 +706,10 @@ fn dispatch( } Command::Doctor(request) => services::doctor::run_doctor(*request) .map_err(|error| ClassifiedError::runtime(error.to_string())), - // Clone required: run_hooks_subcommand takes ownership of HookSubcommand Command::Hooks(subcommand) => services::hooks::run_hooks_subcommand(subcommand.clone()) .map_err(|error| ClassifiedError::runtime(error.to_string())), - // Clone required: run_trace_subcommand takes ownership of TraceRequest Command::Trace(request) => services::trace::run_trace_subcommand(request.clone()) .map_err(|error| ClassifiedError::runtime(error.to_string())), - Command::Sync(request) => services::sync::run_placeholder_sync(*request) - .map_err(|error| ClassifiedError::runtime(error.to_string())), Command::Version(request) => services::version::render_version(*request) .map_err(|error| ClassifiedError::runtime(error.to_string())), } diff --git a/cli/src/cli_schema.rs b/cli/src/cli_schema.rs index 1d6208d..bfb569a 100644 --- a/cli/src/cli_schema.rs +++ b/cli/src/cli_schema.rs @@ -111,12 +111,6 @@ pub enum Commands { subcommand: TraceSubcommand, }, - #[command(about = "Coordinate future cloud sync workflows (placeholder)")] - Sync { - #[arg(long, value_enum, default_value_t = OutputFormat::Text)] - format: OutputFormat, - }, - #[command(about = "Print deterministic runtime version metadata")] Version { #[arg(long, value_enum, default_value_t = OutputFormat::Text)] diff --git a/cli/src/command_surface.rs b/cli/src/command_surface.rs index 5cb978c..18f99e1 100644 --- a/cli/src/command_surface.rs +++ b/cli/src/command_surface.rs @@ -6,7 +6,6 @@ use services::style::{command_name, heading}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ImplementationStatus { Implemented, - Placeholder, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -60,12 +59,6 @@ pub const COMMANDS: &[CommandContract] = &[ purpose: "Inspect persisted Agent Trace records and captured prompts", show_in_top_level_help: false, }, - CommandContract { - name: services::sync::NAME, - status: ImplementationStatus::Placeholder, - purpose: "Coordinate future cloud sync workflows", - show_in_top_level_help: false, - }, CommandContract { name: services::version::NAME, status: ImplementationStatus::Implemented, diff --git a/cli/src/services/hooks.rs b/cli/src/services/hooks.rs index fe773a3..d6b7ab1 100644 --- a/cli/src/services/hooks.rs +++ b/cli/src/services/hooks.rs @@ -10,8 +10,7 @@ use crate::services::agent_trace::{ build_trace_payload, AgentTraceContributor, AgentTraceConversation, AgentTraceFile, AgentTraceRange, AgentTraceRecord, AgentTraceVcs, ContributorInput, ContributorType, ConversationInput, FileAttributionInput, QualityStatus, RangeInput, RewriteInfo, - TraceAdapterInput, METADATA_IDEMPOTENCY_KEY, METADATA_QUALITY_STATUS, TRACE_CONTENT_TYPE, - TRACE_VERSION, VCS_TYPE_GIT, + TraceAdapterInput, METADATA_IDEMPOTENCY_KEY, TRACE_CONTENT_TYPE, TRACE_VERSION, VCS_TYPE_GIT, }; use crate::services::default_paths; use crate::services::local_db::ensure_agent_trace_local_db_ready_blocking; @@ -694,10 +693,7 @@ fn run_post_commit_subcommand_in_repo(repository_root: &Path) -> Result let mut notes_writer = GitNotesTraceWriter { repository_root: repository_root.to_path_buf(), }; - let mut record_store = LocalDbTraceRecordStore { - repository_root: repository_root.to_path_buf(), - db_path: runtime_paths.local_db_path, - }; + let mut record_store = NoOpTraceRecordStore; let mut retry_queue = JsonFileTraceRetryQueue { path: runtime_paths.retry_queue_path, }; @@ -763,13 +759,12 @@ fn resolve_post_commit_runtime_state(repository_root: &Path) -> PostCommitRuntim #[allow(clippy::struct_field_names)] struct PostCommitRuntimePaths { - local_db_path: PathBuf, retry_queue_path: PathBuf, emission_ledger_path: PathBuf, } fn resolve_post_commit_runtime_paths(repository_root: &Path) -> Result { - let local_db_path = ensure_agent_trace_local_db_ready_blocking()?; + let _ = ensure_agent_trace_local_db_ready_blocking()?; let retry_queue_path = resolve_git_path( repository_root, default_paths::git_relative_path::TRACE_RETRY_QUEUE, @@ -780,7 +775,6 @@ fn resolve_post_commit_runtime_paths(repository_root: &Path) -> Result, } -impl RewriteRemapIngestion for LocalDbRewriteRemapIngestion { +impl RewriteRemapIngestion for NoOpRewriteRemapIngestion { fn ingest(&mut self, request: RewriteRemapRequest) -> Result { - let runtime = tokio::runtime::Builder::new_current_thread().build()?; - let accepted = runtime.block_on(ingest_rewrite_mapping_to_local_db( - &self.repository_root, - &self.db_path, - &request, - ))?; - if accepted { - self.accepted_requests.push(request); - } - Ok(accepted) + self.accepted_requests.push(request); + Ok(true) } } -async fn ingest_rewrite_mapping_to_local_db( - repository_root: &Path, - db_path: &Path, - request: &RewriteRemapRequest, -) -> Result { - let location = db_path.to_str().ok_or_else(|| { - anyhow::anyhow!("Local DB path must be valid UTF-8: {}", db_path.display()) - })?; - let db = turso::Builder::new_local(location).build().await?; - let conn = db.connect()?; - conn.execute("PRAGMA foreign_keys = ON", ()).await?; - - let canonical_root = repository_root - .canonicalize() - .unwrap_or_else(|_| repository_root.to_path_buf()) - .to_string_lossy() - .to_string(); - - conn.execute( - "INSERT OR IGNORE INTO repositories (canonical_root) VALUES (?1)", - [canonical_root.as_str()], - ) - .await?; - - let repository_id = { - let mut rows = conn - .query( - "SELECT id FROM repositories WHERE canonical_root = ?1 LIMIT 1", - [canonical_root.as_str()], - ) - .await?; - let row = rows - .next() - .await? - .ok_or_else(|| anyhow::anyhow!("repository id query returned no rows"))?; - let value = row.get_value(0)?; - value - .as_integer() - .copied() - .ok_or_else(|| anyhow::anyhow!("repository id query returned non-integer"))? - }; - - let existing = { - let mut rows = conn - .query( - "SELECT COUNT(*) FROM rewrite_mappings WHERE repository_id = ?1 AND idempotency_key = ?2", - (repository_id, request.idempotency_key.as_str()), - ) - .await?; - let row = rows - .next() - .await? - .ok_or_else(|| anyhow::anyhow!("rewrite mapping count query returned no rows"))?; - let value = row.get_value(0)?; - value - .as_integer() - .copied() - .ok_or_else(|| anyhow::anyhow!("rewrite mapping count query returned non-integer"))? - }; - if existing > 0 { - return Ok(false); - } - - let reconciliation_key = format!( - "local-post-rewrite:{}:{}", - request.rewrite_method.canonical_label(), - request.new_sha - ); - conn.execute( - "INSERT OR IGNORE INTO reconciliation_runs (repository_id, provider, idempotency_key, status) VALUES (?1, ?2, ?3, ?4)", - (repository_id, "local-hook", reconciliation_key.as_str(), "completed"), - ) - .await?; - - let run_id = { - let mut rows = conn - .query( - "SELECT id FROM reconciliation_runs WHERE repository_id = ?1 AND idempotency_key = ?2 LIMIT 1", - (repository_id, reconciliation_key.as_str()), - ) - .await?; - let row = rows - .next() - .await? - .ok_or_else(|| anyhow::anyhow!("reconciliation run id query returned no rows"))?; - let value = row.get_value(0)?; - value - .as_integer() - .copied() - .ok_or_else(|| anyhow::anyhow!("reconciliation run id query returned non-integer"))? - }; - - conn.execute( - "INSERT INTO rewrite_mappings (reconciliation_run_id, repository_id, old_commit_sha, new_commit_sha, mapping_status, confidence, idempotency_key) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", - ( - run_id, - repository_id, - request.old_sha.as_str(), - request.new_sha.as_str(), - "mapped", - 1.0_f64, - request.idempotency_key.as_str(), - ), - ) - .await?; - - Ok(true) -} - #[derive(Clone, Debug, Eq, PartialEq)] pub struct PreCommitRuntimeState { pub sce_disabled: bool, @@ -1967,225 +1837,12 @@ impl TraceNotesWriter for GitNotesTraceWriter { } } -struct LocalDbTraceRecordStore { - repository_root: PathBuf, - db_path: PathBuf, -} - -impl TraceRecordStore for LocalDbTraceRecordStore { - fn write_trace_record(&mut self, record: PersistedTraceRecord) -> PersistenceWriteResult { - let runtime = match tokio::runtime::Builder::new_current_thread().build() { - Ok(runtime) => runtime, - Err(error) => { - return PersistenceWriteResult::Failed(PersistenceFailure { - class: PersistenceErrorClass::Permanent, - message: format!("failed to initialize local DB runtime: {error}"), - }) - } - }; - - match runtime.block_on(write_trace_record_to_local_db( - &self.db_path, - &self.repository_root, - &record, - )) { - Ok(written) => { - if written { - PersistenceWriteResult::Written - } else { - PersistenceWriteResult::AlreadyExists - } - } - Err(error) => PersistenceWriteResult::Failed(PersistenceFailure { - class: classify_persistence_error_class_from_message(&error.to_string()), - message: format!( - "failed to persist trace record in local DB '{}': {error}", - self.db_path.display() - ), - }), - } - } -} - -#[allow(clippy::too_many_lines)] -async fn write_trace_record_to_local_db( - db_path: &Path, - repository_root: &Path, - record: &PersistedTraceRecord, -) -> Result { - let location = db_path.to_str().ok_or_else(|| { - anyhow::anyhow!("Local DB path must be valid UTF-8: {}", db_path.display()) - })?; - let db = turso::Builder::new_local(location).build().await?; - let conn = db.connect()?; - conn.execute("PRAGMA foreign_keys = ON", ()).await?; - - let canonical_root = repository_root - .canonicalize() - .unwrap_or_else(|_| repository_root.to_path_buf()) - .to_string_lossy() - .to_string(); - - conn.execute( - "INSERT OR IGNORE INTO repositories (canonical_root) VALUES (?1)", - [canonical_root.as_str()], - ) - .await?; - - let repository_id = { - let mut rows = conn - .query( - "SELECT id FROM repositories WHERE canonical_root = ?1 LIMIT 1", - [canonical_root.as_str()], - ) - .await?; - let row = rows - .next() - .await? - .ok_or_else(|| anyhow::anyhow!("repository id query returned no rows"))?; - let value = row.get_value(0)?; - value - .as_integer() - .copied() - .ok_or_else(|| anyhow::anyhow!("repository id query returned non-integer"))? - }; - - conn.execute( - "INSERT OR IGNORE INTO commits (repository_id, commit_sha, idempotency_key) VALUES (?1, ?2, ?3)", - ( - repository_id, - record.commit_sha.as_str(), - record.idempotency_key.as_str(), - ), - ) - .await?; - - let commit_id = { - let mut rows = conn - .query( - "SELECT id FROM commits WHERE repository_id = ?1 AND commit_sha = ?2 LIMIT 1", - (repository_id, record.commit_sha.as_str()), - ) - .await?; - let row = rows - .next() - .await? - .ok_or_else(|| anyhow::anyhow!("commit id query returned no rows"))?; - let value = row.get_value(0)?; - value - .as_integer() - .copied() - .ok_or_else(|| anyhow::anyhow!("commit id query returned non-integer"))? - }; - - let existing = { - let mut rows = conn - .query( - "SELECT COUNT(*) FROM trace_records WHERE repository_id = ?1 AND (commit_id = ?2 OR idempotency_key = ?3)", - (repository_id, commit_id, record.idempotency_key.as_str()), - ) - .await?; - let row = rows - .next() - .await? - .ok_or_else(|| anyhow::anyhow!("existing trace count query returned no rows"))?; - let value = row.get_value(0)?; - value - .as_integer() - .copied() - .ok_or_else(|| anyhow::anyhow!("existing trace count query returned non-integer"))? - }; - if existing > 0 { - return Ok(false); - } - - let payload_json = serde_json::to_string(&trace_record_to_json(&record.record)) - .context("failed to serialize trace record JSON payload")?; - let quality_status = record - .record - .metadata - .get(METADATA_QUALITY_STATUS) - .cloned() - .unwrap_or_else(|| String::from("final")); - - conn.execute( - "INSERT INTO trace_records (repository_id, commit_id, trace_id, version, content_type, notes_ref, payload_json, quality_status, idempotency_key, recorded_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)", - ( - repository_id, - commit_id, - record.record.id.as_str(), - record.record.version.as_str(), - record.content_type.as_str(), - record.notes_ref.as_str(), - payload_json.as_str(), - quality_status.as_str(), - record.idempotency_key.as_str(), - record.record.timestamp.as_str(), - ), - ) - .await?; - - let trace_record_id = { - let mut rows = conn - .query( - "SELECT id FROM trace_records WHERE trace_id = ?1 LIMIT 1", - [record.record.id.as_str()], - ) - .await?; - let row = rows - .next() - .await? - .ok_or_else(|| anyhow::anyhow!("trace record id query returned no rows"))?; - let value = row.get_value(0)?; - value - .as_integer() - .copied() - .ok_or_else(|| anyhow::anyhow!("trace record id query returned non-integer"))? - }; +struct NoOpTraceRecordStore; - for file in &record.record.files { - for conversation in &file.conversations { - for range in &conversation.ranges { - conn.execute( - "INSERT INTO trace_ranges (trace_record_id, file_path, conversation_url, start_line, end_line, contributor_type, contributor_model_id) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", - ( - trace_record_id, - file.path.as_str(), - conversation.url.as_str(), - i64::from(range.start_line), - i64::from(range.end_line), - range.contributor.r#type.as_str(), - range.contributor.model_id.as_deref(), - ), - ) - .await?; - } - } +impl TraceRecordStore for NoOpTraceRecordStore { + fn write_trace_record(&mut self, _record: PersistedTraceRecord) -> PersistenceWriteResult { + PersistenceWriteResult::AlreadyExists } - - for prompt in &record.prompts { - conn.execute( - "INSERT INTO prompts (commit_id, prompt_text, prompt_length, is_truncated, turn_number, harness_type, model_id, cwd, git_branch, tool_call_count, duration_ms, captured_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)", - ( - commit_id, - prompt.prompt_text.as_str(), - i64::try_from(prompt.prompt_length) - .context("Prompt length exceeded supported SQLite integer range")?, - prompt.is_truncated, - i64::from(prompt.turn_number), - prompt.harness_type.as_str(), - prompt.model_id.as_deref(), - prompt.cwd.as_deref(), - prompt.git_branch.as_deref(), - i64::from(prompt.tool_call_count), - prompt.duration_ms, - prompt.captured_at.as_str(), - ), - ) - .await?; - } - - Ok(true) } struct JsonFileTraceRetryQueue { @@ -2371,19 +2028,6 @@ fn classify_persistence_error_class_from_stderr(stderr: &str) -> PersistenceErro PersistenceErrorClass::Permanent } -fn classify_persistence_error_class_from_message(message: &str) -> PersistenceErrorClass { - let lowered = message.to_ascii_lowercase(); - if lowered.contains("locked") - || lowered.contains("timed out") - || lowered.contains("temporar") - || lowered.contains("try again") - { - return PersistenceErrorClass::Transient; - } - - PersistenceErrorClass::Permanent -} - fn persistence_target_label(target: PersistenceTarget) -> &'static str { match target { PersistenceTarget::Notes => "notes", diff --git a/cli/src/services/local_db.rs b/cli/src/services/local_db.rs index 37be456..1f244db 100644 --- a/cli/src/services/local_db.rs +++ b/cli/src/services/local_db.rs @@ -1,155 +1,12 @@ use std::path::{Path, PathBuf}; -use anyhow::{anyhow, ensure, Context, Result}; +use anyhow::{anyhow, Context, Result}; use turso::Builder; use crate::services::default_paths::resolve_sce_default_locations; use crate::services::resilience::{run_with_retry, RetryPolicy}; -const CORE_SCHEMA_STATEMENTS: &[&str] = &[ - "CREATE TABLE IF NOT EXISTS repositories (\ - id INTEGER PRIMARY KEY,\ - vcs_provider TEXT NOT NULL DEFAULT 'git',\ - canonical_root TEXT NOT NULL UNIQUE,\ - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))\ - )", - "CREATE TABLE IF NOT EXISTS commits (\ - id INTEGER PRIMARY KEY,\ - repository_id INTEGER NOT NULL,\ - commit_sha TEXT NOT NULL,\ - parent_sha TEXT,\ - committed_at TEXT,\ - author_name TEXT,\ - author_email TEXT,\ - idempotency_key TEXT,\ - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),\ - FOREIGN KEY(repository_id) REFERENCES repositories(id) ON DELETE CASCADE,\ - UNIQUE(repository_id, commit_sha),\ - UNIQUE(repository_id, idempotency_key)\ - )", - "CREATE TABLE IF NOT EXISTS trace_records (\ - id INTEGER PRIMARY KEY,\ - repository_id INTEGER NOT NULL,\ - commit_id INTEGER NOT NULL,\ - trace_id TEXT NOT NULL UNIQUE,\ - version TEXT NOT NULL,\ - content_type TEXT NOT NULL,\ - notes_ref TEXT NOT NULL,\ - payload_json TEXT NOT NULL,\ - quality_status TEXT NOT NULL,\ - idempotency_key TEXT NOT NULL,\ - recorded_at TEXT NOT NULL,\ - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),\ - FOREIGN KEY(repository_id) REFERENCES repositories(id) ON DELETE CASCADE,\ - FOREIGN KEY(commit_id) REFERENCES commits(id) ON DELETE CASCADE,\ - UNIQUE(repository_id, idempotency_key),\ - UNIQUE(commit_id)\ - )", - "CREATE TABLE IF NOT EXISTS trace_ranges (\ - id INTEGER PRIMARY KEY,\ - trace_record_id INTEGER NOT NULL,\ - file_path TEXT NOT NULL,\ - conversation_url TEXT NOT NULL,\ - start_line INTEGER NOT NULL,\ - end_line INTEGER NOT NULL,\ - contributor_type TEXT NOT NULL,\ - contributor_model_id TEXT,\ - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),\ - FOREIGN KEY(trace_record_id) REFERENCES trace_records(id) ON DELETE CASCADE\ - )", - "CREATE TABLE IF NOT EXISTS reconciliation_runs (\ - id INTEGER PRIMARY KEY,\ - repository_id INTEGER NOT NULL,\ - provider TEXT NOT NULL,\ - idempotency_key TEXT NOT NULL,\ - status TEXT NOT NULL,\ - initiated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),\ - completed_at TEXT,\ - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),\ - FOREIGN KEY(repository_id) REFERENCES repositories(id) ON DELETE CASCADE,\ - UNIQUE(repository_id, idempotency_key)\ - )", - "CREATE TABLE IF NOT EXISTS rewrite_mappings (\ - id INTEGER PRIMARY KEY,\ - reconciliation_run_id INTEGER NOT NULL,\ - repository_id INTEGER NOT NULL,\ - old_commit_sha TEXT NOT NULL,\ - new_commit_sha TEXT,\ - mapping_status TEXT NOT NULL,\ - confidence REAL,\ - idempotency_key TEXT NOT NULL,\ - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),\ - FOREIGN KEY(reconciliation_run_id) REFERENCES reconciliation_runs(id) ON DELETE CASCADE,\ - FOREIGN KEY(repository_id) REFERENCES repositories(id) ON DELETE CASCADE,\ - UNIQUE(repository_id, idempotency_key)\ - )", - "CREATE TABLE IF NOT EXISTS conversations (\ - id INTEGER PRIMARY KEY,\ - repository_id INTEGER NOT NULL,\ - url TEXT NOT NULL,\ - source TEXT NOT NULL,\ - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),\ - FOREIGN KEY(repository_id) REFERENCES repositories(id) ON DELETE CASCADE,\ - UNIQUE(repository_id, url)\ - )", - "CREATE TABLE IF NOT EXISTS trace_retry_queue (\ - id INTEGER PRIMARY KEY,\ - trace_id TEXT NOT NULL UNIQUE,\ - commit_sha TEXT NOT NULL,\ - failed_targets TEXT NOT NULL,\ - content_type TEXT NOT NULL,\ - notes_ref TEXT NOT NULL,\ - payload_json TEXT NOT NULL,\ - attempts INTEGER NOT NULL DEFAULT 0,\ - last_error_class TEXT,\ - last_error_message TEXT,\ - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),\ - updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))\ - )", - "CREATE TABLE IF NOT EXISTS reconciliation_metrics (\ - id INTEGER PRIMARY KEY,\ - run_id INTEGER,\ - mapped_count INTEGER NOT NULL,\ - unmapped_count INTEGER NOT NULL,\ - histogram_high INTEGER NOT NULL,\ - histogram_medium INTEGER NOT NULL,\ - histogram_low INTEGER NOT NULL,\ - runtime_ms INTEGER NOT NULL,\ - error_class TEXT,\ - created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))\ - )", - "CREATE TABLE IF NOT EXISTS prompts (\ - id INTEGER PRIMARY KEY AUTOINCREMENT,\ - commit_id INTEGER NOT NULL,\ - prompt_text TEXT NOT NULL,\ - prompt_length INTEGER,\ - is_truncated BOOLEAN DEFAULT 0,\ - turn_number INTEGER,\ - harness_type TEXT NOT NULL,\ - model_id TEXT,\ - cwd TEXT,\ - git_branch TEXT,\ - tool_call_count INTEGER,\ - duration_ms INTEGER,\ - captured_at TEXT NOT NULL,\ - FOREIGN KEY(commit_id) REFERENCES commits(id) ON DELETE CASCADE\ - )", - "CREATE INDEX IF NOT EXISTS idx_commits_repository_commit_sha ON commits(repository_id, commit_sha)", - "CREATE INDEX IF NOT EXISTS idx_trace_records_repository_commit ON trace_records(repository_id, commit_id)", - "CREATE INDEX IF NOT EXISTS idx_trace_ranges_record_file ON trace_ranges(trace_record_id, file_path)", - "CREATE INDEX IF NOT EXISTS idx_reconciliation_runs_repository_status ON reconciliation_runs(repository_id, status)", - "CREATE INDEX IF NOT EXISTS idx_rewrite_mappings_run_old_sha ON rewrite_mappings(reconciliation_run_id, old_commit_sha)", - "CREATE INDEX IF NOT EXISTS idx_rewrite_mappings_repository_old_sha ON rewrite_mappings(repository_id, old_commit_sha)", - "CREATE INDEX IF NOT EXISTS idx_conversations_repository_source ON conversations(repository_id, source)", - "CREATE INDEX IF NOT EXISTS idx_trace_retry_queue_created_at ON trace_retry_queue(created_at)", - "CREATE INDEX IF NOT EXISTS idx_reconciliation_metrics_created_at ON reconciliation_metrics(created_at)", - "CREATE INDEX IF NOT EXISTS idx_prompts_commit ON prompts(commit_id)", - "CREATE INDEX IF NOT EXISTS idx_prompts_harness ON prompts(harness_type)", - "CREATE INDEX IF NOT EXISTS idx_prompts_captured ON prompts(captured_at)", - "CREATE INDEX IF NOT EXISTS idx_prompts_commit_turn ON prompts(commit_id, turn_number)", -]; - -const CORE_SCHEMA_RETRY_POLICY: RetryPolicy = RetryPolicy { +const LOCAL_DB_OPEN_RETRY_POLICY: RetryPolicy = RetryPolicy { max_attempts: 3, timeout_ms: 5_000, initial_backoff_ms: 150, @@ -157,22 +14,10 @@ const CORE_SCHEMA_RETRY_POLICY: RetryPolicy = RetryPolicy { }; #[derive(Clone, Copy, Debug)] -#[allow(dead_code)] pub enum LocalDatabaseTarget<'a> { - InMemory, Path(&'a Path), } -#[derive(Clone, Copy, Debug)] -pub struct SmokeCheckOutcome { - pub inserted_rows: u64, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct CoreSchemaMigrationOutcome { - pub executed_statements: usize, -} - pub fn resolve_agent_trace_local_db_path() -> Result { Ok(resolve_sce_default_locations()?.agent_trace_local_db()) } @@ -192,10 +37,10 @@ pub fn ensure_agent_trace_local_db_ready_blocking() -> Result { .enable_time() .build()?; runtime.block_on(run_with_retry( - CORE_SCHEMA_RETRY_POLICY, - "local_db.apply_core_schema_migrations", + LOCAL_DB_OPEN_RETRY_POLICY, + "local_db.open_local_database", "retry the command; if it persists, verify state-directory permissions and available disk space.", - |_| apply_core_schema_migrations(LocalDatabaseTarget::Path(&db_path)), + |_| open_local_database(LocalDatabaseTarget::Path(&db_path)), ))?; Ok(db_path) } @@ -224,7 +69,6 @@ async fn connect_local(target: LocalDatabaseTarget<'_>) -> Result) -> Result<&str> { match target { - LocalDatabaseTarget::InMemory => Ok(":memory:"), LocalDatabaseTarget::Path(path) => path .to_str() .ok_or_else(|| anyhow!("Local DB path must be valid UTF-8: {}", path.display())), @@ -238,45 +82,7 @@ pub(crate) fn resolve_state_data_root() -> Result { .to_path_buf()) } -pub async fn apply_core_schema_migrations( - target: LocalDatabaseTarget<'_>, -) -> Result { - let conn = connect_local(target).await?; - for statement in CORE_SCHEMA_STATEMENTS { - conn.execute(statement, ()).await?; - } - - Ok(CoreSchemaMigrationOutcome { - executed_statements: CORE_SCHEMA_STATEMENTS.len(), - }) -} - -pub async fn run_smoke_check(target: LocalDatabaseTarget<'_>) -> Result { - let conn = connect_local(target).await?; - - conn.execute( - "CREATE TABLE IF NOT EXISTS sce_smoke (id INTEGER PRIMARY KEY, label TEXT NOT NULL)", - (), - ) - .await?; - - let inserted_rows = conn - .execute("INSERT INTO sce_smoke (label) VALUES (?1)", ["connected"]) - .await?; - - let mut rows = conn - .query("SELECT label FROM sce_smoke ORDER BY id DESC LIMIT 1", ()) - .await?; - - let row = rows - .next() - .await? - .ok_or_else(|| anyhow!("Turso smoke query returned no rows"))?; - let label = row.get_value(0)?; - ensure!( - label.as_text().is_some(), - "Turso smoke query returned a non-text label" - ); - - Ok(SmokeCheckOutcome { inserted_rows }) +async fn open_local_database(target: LocalDatabaseTarget<'_>) -> Result<()> { + let _ = connect_local(target).await?; + Ok(()) } diff --git a/cli/src/services/mod.rs b/cli/src/services/mod.rs index ac95129..e097ff6 100644 --- a/cli/src/services/mod.rs +++ b/cli/src/services/mod.rs @@ -14,7 +14,6 @@ pub mod resilience; pub mod security; pub mod setup; pub mod style; -pub mod sync; pub mod token_storage; pub mod trace; pub mod version; diff --git a/cli/src/services/sync.rs b/cli/src/services/sync.rs deleted file mode 100644 index 269ba58..0000000 --- a/cli/src/services/sync.rs +++ /dev/null @@ -1,242 +0,0 @@ -use anyhow::{Context, Result}; -use serde_json::json; -use std::sync::OnceLock; - -use crate::services::auth; -use crate::services::config; -use crate::services::local_db::{run_smoke_check, LocalDatabaseTarget}; -use crate::services::output_format::OutputFormat; -use crate::services::resilience::{run_with_retry, RetryPolicy}; -use crate::services::style::{self}; -use crate::services::token_storage; - -pub const NAME: &str = "sync"; - -pub type SyncFormat = OutputFormat; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct SyncRequest { - pub format: SyncFormat, -} - -const SUPPORTED_PHASES: [CloudSyncPhase; 3] = [ - CloudSyncPhase::PlanOnly, - CloudSyncPhase::DryRun, - CloudSyncPhase::Apply, -]; - -static SYNC_RUNTIME: OnceLock = OnceLock::new(); -const SYNC_SMOKE_RETRY_POLICY: RetryPolicy = RetryPolicy { - max_attempts: 3, - timeout_ms: 2_000, - initial_backoff_ms: 100, - max_backoff_ms: 400, -}; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum CloudSyncPhase { - PlanOnly, - DryRun, - Apply, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct CloudSyncRequest { - pub workspace: &'static str, - pub phase: CloudSyncPhase, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct CloudSyncPlan { - pub checkpoints: Vec<&'static str>, - pub can_execute: bool, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -struct SyncPlaceholderReport { - workspace: &'static str, - phase: CloudSyncPhase, - inserted_rows: u64, - checkpoints: Vec<&'static str>, - can_execute: bool, -} - -pub trait CloudSyncGateway { - fn plan(&self, request: &CloudSyncRequest) -> CloudSyncPlan; -} - -#[derive(Clone, Copy, Debug, Default)] -pub struct PlaceholderCloudSyncGateway; - -impl CloudSyncGateway for PlaceholderCloudSyncGateway { - fn plan(&self, request: &CloudSyncRequest) -> CloudSyncPlan { - let mut checkpoints = vec![ - "Resolve local context delta", - "Build upload manifest", - "Persist remote reconciliation state", - ]; - - if request.phase != CloudSyncPhase::PlanOnly { - checkpoints.push("Phase-specific execution remains intentionally disabled"); - } - - if request.phase == CloudSyncPhase::Apply { - checkpoints - .push("Apply execution is intentionally blocked by placeholder safety checks"); - } - - CloudSyncPlan { - checkpoints, - can_execute: false, - } - } -} - -#[derive(Clone, Copy, Debug, Default)] -pub struct PlaceholderSyncService -where - G: CloudSyncGateway, -{ - gateway: G, -} - -impl PlaceholderSyncService -where - G: CloudSyncGateway, -{ - pub fn new(gateway: G) -> Self { - Self { gateway } - } - - fn run(&self, request: &CloudSyncRequest) -> Result { - let runtime = shared_runtime()?; - - let outcome = runtime - .block_on(run_with_retry( - SYNC_SMOKE_RETRY_POLICY, - "sync.local_db_smoke_check", - "rerun 'sce sync'; if the failure persists, verify local runtime health with 'sce doctor'.", - |_| run_smoke_check(LocalDatabaseTarget::InMemory), - )) - .context("local Turso smoke check failed after bounded retries")?; - - let plan = self.gateway.plan(request); - - Ok(SyncPlaceholderReport { - workspace: request.workspace, - phase: request.phase, - inserted_rows: outcome.inserted_rows, - checkpoints: plan.checkpoints, - can_execute: plan.can_execute, - }) - } -} - -fn shared_runtime() -> Result<&'static tokio::runtime::Runtime> { - if let Some(runtime) = SYNC_RUNTIME.get() { - return Ok(runtime); - } - - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_io() - .enable_time() - .build() - .context("failed to create shared tokio runtime for sync placeholder")?; - - Ok(SYNC_RUNTIME.get_or_init(|| runtime)) -} - -pub fn run_placeholder_sync(request: SyncRequest) -> Result { - ensure_sync_auth_if_expired()?; - - let service = PlaceholderSyncService::new(PlaceholderCloudSyncGateway); - let cloud_request = CloudSyncRequest { - workspace: "local", - phase: CloudSyncPhase::PlanOnly, - }; - let report = service.run(&cloud_request)?; - - match request.format { - SyncFormat::Text => Ok(format!( - "{}: '{}' cloud workflows are planned and not implemented yet. {} {} row inserted; cloud sync placeholder enumerates {} phase(s) and plan holds {} checkpoint(s). {}: rerun with '--format json' for machine-readable placeholder checkpoints.", - style::label("TODO"), - style::command_name(NAME), - style::label("Local Turso smoke check succeeded"), - style::value(&report.inserted_rows.to_string()), - style::value(&SUPPORTED_PHASES.len().to_string()), - style::value(&report.checkpoints.len().to_string()), - style::label("Next step") - )), - SyncFormat::Json => { - let payload = json!({ - "status": "ok", - "command": NAME, - "placeholder_state": "planned", - "workspace": report.workspace, - "phase": phase_name(report.phase), - "supported_phases": SUPPORTED_PHASES - .iter() - .map(|phase| phase_name(*phase)) - .collect::>(), - "local_smoke_check": { - "status": "ok", - "target": "in_memory", - "inserted_rows": report.inserted_rows, - "retry_policy": { - "max_attempts": SYNC_SMOKE_RETRY_POLICY.max_attempts, - "timeout_ms": SYNC_SMOKE_RETRY_POLICY.timeout_ms, - "initial_backoff_ms": SYNC_SMOKE_RETRY_POLICY.initial_backoff_ms, - "max_backoff_ms": SYNC_SMOKE_RETRY_POLICY.max_backoff_ms, - }, - }, - "cloud_plan": { - "can_execute": report.can_execute, - "checkpoints": report.checkpoints, - }, - "next_step": "Rerun with '--format json' for machine-readable placeholder checkpoints.", - }); - - serde_json::to_string_pretty(&payload) - .context("failed to serialize sync placeholder report to JSON") - } - } -} - -fn ensure_sync_auth_if_expired() -> Result<()> { - let Some(stored_tokens) = token_storage::load_tokens()? else { - return Ok(()); - }; - - if !auth::is_stored_token_expired(&stored_tokens)? { - return Ok(()); - } - - let cwd = std::env::current_dir() - .context("failed to determine current directory for auth config resolution")?; - let client_id = config::resolve_auth_runtime_config(&cwd)? - .workos_client_id - .value - .unwrap_or_default(); - let client = reqwest::Client::new(); - let runtime = shared_runtime()?; - - runtime - .block_on(auth::ensure_valid_token( - &client, - auth::WORKOS_DEFAULT_BASE_URL, - &client_id, - )) - .map(|_| ()) - .map_err(|error| { - anyhow::anyhow!(error.to_string()) - .context("failed to renew expired authentication before running 'sce sync'") - }) -} - -fn phase_name(phase: CloudSyncPhase) -> &'static str { - match phase { - CloudSyncPhase::PlanOnly => "plan_only", - CloudSyncPhase::DryRun => "dry_run", - CloudSyncPhase::Apply => "apply", - } -} diff --git a/context/architecture.md b/context/architecture.md index 980070d..a459e41 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -88,8 +88,8 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/config.rs` defines `sce config` parser/runtime contracts (`show`, `validate`, `--help`), with bare `sce config` routed by `cli/src/app.rs` to the same help payload as `sce config --help`, deterministic config-file selection, explicit value precedence (`flags > env > config file > defaults`), strict config-file validation (`$schema`, `log_level`, `log_format`, `log_file`, `log_file_mode`, `timeout_ms`, `workos_client_id`, nested `otel`, `policies.bash`), compile-time embedding of the canonical generated schema artifact and bash-policy preset catalog from the ephemeral crate-local `cli/assets/generated/config/**` mirror prepared from canonical `config/` outputs before Cargo packaging/builds, runtime parity between that schema and the Rust-side top-level allowed-key gate so startup config discovery accepts the canonical `"$schema": "https://sce.crocoder.dev/config.json"` declaration, shared auth-key resolution with optional baked defaults starting at `workos_client_id`, repo-configured bash-policy preset/custom validation and merged reporting from discovered config files, shared observability-runtime resolution for logging and OTEL config keys (`log_level`, `log_format`, `log_file`, `log_file_mode`, `otel.enabled`, `otel.exporter_otlp_endpoint`, `otel.exporter_otlp_protocol`) with deterministic source-aware env-over-config precedence reused by `cli/src/app.rs`, and deterministic text/JSON output rendering where `show` includes resolved observability/auth/policy values with provenance while `validate` now returns only validation status plus issues/warnings. - `cli/src/services/output_format.rs` defines the canonical shared CLI output-format contract (`OutputFormat`) for supporting commands, with deterministic `text|json` parsing and command-scoped actionable invalid-value guidance. - `cli/src/services/auth_command.rs` defines the implemented auth command surface for `sce auth login|renew|logout|status`, including device-flow login, stored-token renewal (`--force` supported for renew), logout, and status rendering in text/JSON formats. -- `cli/src/services/trace.rs` defines the prompt-query read path for `sce trace prompts `, including repository-root resolution from git truth, unique prefix/full-SHA commit lookup against persisted Agent Trace data, deterministic text/JSON rendering, legacy `--json` compatibility, and explicit missing/ambiguous commit diagnostics for prompt rows stored in the local `prompts` table. -- `cli/src/services/local_db.rs` provides the local Turso data adapter, including `Builder::new_local(...)` initialization, deterministic persistent runtime DB target resolution/bootstrap (`ensure_agent_trace_local_db_ready_blocking`) through the shared default-path seam, async execute/query smoke checks for in-memory and file-backed targets, and idempotent migration application for Agent Trace persistence foundations (`repositories`, `commits`, `trace_records`, `trace_ranges`), reconciliation ingestion entities (`reconciliation_runs`, `rewrite_mappings`, `conversations`), and T14 retry/observability storage (`trace_retry_queue`, `reconciliation_metrics`) with replay/query indexes. +- `cli/src/services/trace.rs` defines the prompt-query read path for `sce trace prompts `, including repository-root resolution from git truth, unique prefix/full-SHA commit lookup against persisted Agent Trace data, deterministic text/JSON rendering, legacy `--json` compatibility, and explicit missing/ambiguous commit diagnostics for prompt rows stored in the local `prompts` table when those tables exist. +- `cli/src/services/local_db.rs` provides the local Turso data adapter, including `Builder::new_local(...)` initialization, deterministic persistent runtime DB target resolution/bootstrap (`ensure_agent_trace_local_db_ready_blocking`) through the shared default-path seam, creation/opening of the per-user Agent Trace DB file without schema bootstrap, and async execute/query smoke checks for in-memory and file-backed targets. - `cli/src/test_support.rs` provides a shared test-only temp-directory helper (`TestTempDir`) used by service tests that need filesystem fixtures. - `cli/src/services/setup.rs` defines the setup command contract (`SetupMode`, `SetupTarget`, `SetupRequest`, CLI flag parser/validator), an `inquire`-backed interactive target prompter (`InquireSetupTargetPrompter`), setup dispatch outcomes (proceed/cancelled), compile-time embedded asset access (`EmbeddedAsset`, target-scoped iterators, required-hook asset iterators/lookups) generated by `cli/build.rs` from the ephemeral crate-local `cli/assets/generated/config/{opencode,claude}/**` mirror plus `cli/assets/hooks/**`, a target-scoped install engine/orchestrator that stages embedded files and branches backup behavior by repository type (non-git-backed config installs keep backup-and-restore rollback, while git-backed config installs skip backup creation and emit deterministic git-recovery guidance on swap failure), and formats deterministic completion messaging, plus required-hook install orchestration (`install_required_git_hooks`) that now follows the same shared backup-policy seam (non-git-backed hook replacements keep backup-and-restore rollback, while git-backed hook replacements skip backup creation and backup-based rollback and emit deterministic git-recovery guidance on swap failure), along with command-surface setup request resolution helpers (`run_setup_hooks`, `resolve_setup_request`) used by hooks-only and composable target+hooks setup invocations with deterministic option compatibility validation, canonicalized/validated repo targeting, write-permission probes, and stable section-ordered setup/hook outcome messaging. - `cli/src/services/security.rs` provides shared security utilities for deterministic secret redaction (`redact_sensitive_text`) and directory write-permission probes (`ensure_directory_is_writable`) used by app/setup/observability surfaces. @@ -97,7 +97,7 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/agent_trace.rs` defines the Agent Trace schema adapter and builder contracts (`adapt_trace_payload`, `build_trace_payload`), including fixed git VCS identity, reserved reverse-domain metadata keys, and deterministic AI `model_id` normalization before schema-compliance validation. - `cli/src/services/version.rs` defines the version command parser/rendering contract (`parse_version_request`, `render_version`) with deterministic text output and stable JSON runtime-identification fields. - `cli/src/services/completion.rs` defines completion parser/rendering contract (`parse_completion_request`, `render_completion`) with deterministic Bash/Zsh/Fish script output aligned to current parser-valid command/flag surfaces. -- `cli/src/services/hooks.rs` defines production local hook runtime parsing/dispatch (`HookSubcommand`, `parse_hooks_subcommand`, `run_hooks_subcommand`) plus a pre-commit staged-checkpoint finalization seam (`finalize_pre_commit_checkpoint`) that enforces staged-only attribution and carries index/tree anchors with explicit no-op guard states, a commit-msg co-author policy seam (`apply_commit_msg_coauthor_policy`) that injects one canonical SCE trailer only for allowed attributed commits, a post-commit trace finalization seam (`finalize_post_commit_trace`) that performs notes+DB dual writes with idempotency ledger guards and retry-queue fallback capture, a retry replay seam (`process_trace_retry_queue`) that re-attempts only failed persistence targets and emits per-attempt runtime/error-class metrics, bounded operational retry replay invocation from post-commit/post-rewrite flows (`process_runtime_retry_queue`), a post-rewrite remap-ingestion seam (`finalize_post_rewrite_remap`) that parses old->new SHA pairs and derives deterministic replay keys for remap dispatch, and a rewrite trace transformation seam (`finalize_rewrite_trace`) that emits rewritten-SHA Agent Trace records with rewrite metadata plus confidence-based quality status. +- `cli/src/services/hooks.rs` defines production local hook runtime parsing/dispatch (`HookSubcommand`, `parse_hooks_subcommand`, `run_hooks_subcommand`) plus a pre-commit staged-checkpoint finalization seam (`finalize_pre_commit_checkpoint`) that enforces staged-only attribution and carries index/tree anchors with explicit no-op guard states, a commit-msg co-author policy seam (`apply_commit_msg_coauthor_policy`) that injects one canonical SCE trailer only for allowed attributed commits, a post-commit trace finalization seam (`finalize_post_commit_trace`) that currently persists git notes while local DB writes are disconnected through a no-op record-store adapter, a retry replay seam (`process_trace_retry_queue`) that re-attempts only failed persistence targets and emits per-attempt runtime/error-class metrics, bounded operational retry replay invocation from post-commit/post-rewrite flows (`process_runtime_retry_queue`), a post-rewrite remap-ingestion seam (`finalize_post_rewrite_remap`) that parses old->new SHA pairs and records accepted requests in memory without DB ingestion, and a rewrite trace transformation seam (`finalize_rewrite_trace`) that emits rewritten-SHA Agent Trace records with rewrite metadata plus confidence-based quality status. - `cli/src/services/resilience.rs` defines bounded retry/timeout/backoff execution policy (`RetryPolicy`, `run_with_retry`) for transient operation hardening with deterministic failure messaging and retry observability. - `cli/src/services/sync.rs` runs the local adapter through a lazily initialized shared tokio current-thread runtime, applies bounded resilience policy to the local smoke operation, and composes a placeholder cloud-sync abstraction (`CloudSyncGateway`) so local Turso validation and deferred cloud planning remain separated. - `cli/src/services/` contains module boundaries for config, setup, doctor, hooks, sync, version, completion, and local DB adapters with explicit trait seams for future implementations. diff --git a/context/context-map.md b/context/context-map.md index a616bb3..38646bc 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -23,7 +23,7 @@ Feature/domain context: - `context/sce/agent-trace-payload-builder-validation.md` (T03 deterministic payload-builder path, model-id normalization behavior, and Agent Trace schema validation suite) - `context/sce/agent-trace-pre-commit-staged-checkpoint.md` (T04 pre-commit staged-only finalization contract with no-op guards and index/tree anchor capture) - `context/sce/agent-trace-commit-msg-coauthor-policy.md` (T05 commit-msg canonical co-author trailer policy with env-gated injection and idempotent dedupe) -- `context/sce/agent-trace-post-commit-dual-write.md` (T06 post-commit trace finalization contract, persistent local DB bootstrap/path policy, notes+DB dual-write behavior, idempotency ledger guard, and retry-queue fallback semantics) +- `context/sce/agent-trace-post-commit-dual-write.md` (current post-commit persistence baseline with git-notes persistence, no-op local DB record store, local DB file bootstrap, idempotency ledger guard, and retry-queue fallback semantics) - `context/sce/agent-trace-hook-doctor.md` (approved operator-environment contract for broadening `sce doctor` into the canonical health-and-repair entrypoint, including stable problem taxonomy, `--fix` semantics, setup-to-doctor alignment rules, and the approved downstream human text-mode layout/status/integration contract; current implementation baseline is captured inside the file) - `context/sce/setup-githooks-install-contract.md` (T01 canonical `sce setup --hooks` install contract for target-path resolution, idempotent outcomes, backup/rollback, and doctor-readiness alignment) - `context/sce/setup-no-backup-policy-seam.md` (implemented shared `SetupBackupPolicy` seam that classifies git-backed vs non-git-backed setup targets and feeds both config-install and required-hook install flows) @@ -32,9 +32,8 @@ Feature/domain context: - `context/sce/setup-githooks-cli-ux.md` (T04 composable `sce setup` target+`--hooks` / `--repo` command-surface contract, option compatibility validation, and deterministic setup/hook output semantics) - `context/sce/cli-security-hardening-contract.md` (T06 CLI redaction contract, setup `--repo` canonicalization/validation, and setup write-permission probe behavior) - `context/sce/agent-trace-post-rewrite-local-remap-ingestion.md` (T08 `post-rewrite` local remap ingestion contract with strict pair parsing, rewrite-method normalization, and deterministic replay-key derivation) -- `context/sce/agent-trace-rewrite-trace-transformation.md` (T09 rewritten-SHA trace transformation contract with rewrite metadata, confidence-to-quality mapping, and notes+DB persistence parity) -- `context/sce/agent-trace-core-schema-migrations.md` (T10 core local schema migration contract for `repositories`, `commits`, `trace_records`, and `trace_ranges` with upgrade-safe idempotent create semantics) -- `context/sce/agent-trace-reconciliation-schema-ingestion.md` (T11 reconciliation persistence schema for `reconciliation_runs`, `rewrite_mappings`, and `conversations` with replay-safe idempotency and query indexes) +- `context/sce/agent-trace-rewrite-trace-transformation.md` (T09 rewritten-SHA trace transformation contract with rewrite metadata, confidence-to-quality mapping, and current notes-persistence behavior) +- `context/sce/agent-trace-core-schema-migrations.md` (current Agent Trace local DB empty-file baseline with create/open-only runtime behavior and no schema bootstrap) - `context/sce/agent-trace-retry-queue-observability.md` (T14 retry queue recovery contract plus reconciliation/runtime observability metrics and DB-first queue schema additions) - `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md` (T01 Local Hooks MVP production contract freeze and deterministic gap matrix for `agent-trace-local-hooks-production-mvp`) - `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime entrypoint behavior, including commit-msg policy gating/file mutation and post-rewrite remap+rewrite finalization wiring) diff --git a/context/glossary.md b/context/glossary.md index e7d75c7..ae51048 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -94,15 +94,14 @@ - `agent trace schema validation suite`: T03 compliance test slice in `services::agent_trace::tests` that validates payload JSON against the published Agent Trace trace-record schema with draft-2020-12 format checks enabled (`uri`, `date-time`, `uuid`) and a local version-pattern compatibility patch for the current app semver emitted from `CARGO_PKG_VERSION`. - `agent trace pre-commit staged checkpoint finalization`: T04 contract in `cli/src/services/hooks.rs` (`finalize_pre_commit_checkpoint`) that filters pending attribution to staged ranges only, drops unstaged-only files, captures index/head tree anchors, and returns explicit no-op outcomes when SCE is disabled, CLI is unavailable, or the repository is bare. - `agent trace commit-msg co-author policy`: T05 contract in `cli/src/services/hooks.rs` (`apply_commit_msg_coauthor_policy`) that applies exactly one canonical trailer (`Co-authored-by: SCE `) only when SCE is enabled, co-author policy is enabled, and staged SCE attribution exists; duplicate canonical trailers are deduped idempotently. -- `agent trace post-commit dual-write finalization`: T06 contract in `cli/src/services/hooks.rs` (`finalize_post_commit_trace`) that emits one canonical Agent Trace record per commit behind runtime guards, writes to both notes (`refs/notes/agent-trace`) and DB persistence targets, enqueues retry fallback entries when either persistence target fails, and relies on runtime bootstrap of the persistent local DB target before DB writes. -- `agent trace local DB runtime bootstrap`: T06 runtime policy in `cli/src/services/local_db.rs` (`ensure_agent_trace_local_db_ready_blocking`) that resolves the deterministic per-user state path through the shared default path policy seam (`${XDG_STATE_HOME:-~/.local/state}/sce/agent-trace/local.db` on Linux; platform-equivalent user state root elsewhere), creates parent directories, and applies `apply_core_schema_migrations` before post-commit DB persistence. +- `agent trace post-commit persistence baseline`: Current `cli/src/services/hooks.rs` (`finalize_post_commit_trace`) behavior where hook finalization still emits one canonical Agent Trace record per commit behind runtime guards, persists git notes, and keeps local DB persistence disconnected through a no-op record-store adapter while retry bookkeeping remains target-aware. +- `agent trace local DB empty-file baseline`: Current `cli/src/services/local_db.rs` (`ensure_agent_trace_local_db_ready_blocking`) behavior where the deterministic per-user Agent Trace DB path (`${XDG_STATE_HOME:-~/.local/state}/sce/agent-trace/local.db` on Linux; platform-equivalent user state root elsewhere) has its parent directory created and the DB file opened/created without applying schema migrations or installing trace tables. - `agent trace post-commit idempotency ledger`: T06 seam (`TraceEmissionLedger`) in `cli/src/services/hooks.rs` used to prevent duplicate emission for the same commit SHA and to mark successful dual-write completion. - `sce doctor` operator-health contract: `cli/src/services/doctor.rs` now implements the current approved operator-health surface in `context/sce/agent-trace-hook-doctor.md`: `sce doctor --fix` selects repair intent, help/output expose deterministic doctor mode, JSON includes stable problem taxonomy/fixability fields plus database records and fix-result records, the runtime validates state-root resolution, global and repo-local `sce/config.json` readability/schema health, Agent Trace local DB path/health, DB-parent readiness barriers, git availability, non-repo vs bare-repo targeting failures, effective hook-path source resolution, required hook presence/executable/content drift against canonical embedded hook assets, and repo-root installed OpenCode integration presence for `OpenCode plugins`, `OpenCode agents`, `OpenCode commands`, and `OpenCode skills`. Human text mode now uses the approved sectioned layout (`Environment`, `Configuration`, `Repository`, `Git Hooks`, `Integrations`), `SCE doctor diagnose` / `SCE doctor fix` headers, bracketed `[PASS]`/`[FAIL]`/`[MISS]` status tokens with shared-style green/red colorization when enabled, simplified `label (path)` row formatting, top-level-only hook rows, and presence-only integration parent/child rows where missing required files surface as `[MISS]` children and `[FAIL]` parent groups. Fix mode still reuses canonical setup hook installation for missing/stale/non-executable required hooks and missing hooks directories and can bootstrap the canonical missing SCE-owned Agent Trace DB parent directory. - `cli warnings-denied lint policy`: `cli/Cargo.toml` sets `warnings = "deny"`, so plain `cargo clippy --manifest-path cli/Cargo.toml` already fails on warnings without needing an extra `-- -D warnings` tail. - `agent trace post-rewrite local remap ingestion`: T08 contract in `cli/src/services/hooks.rs` (`finalize_post_rewrite_remap`) that parses git `post-rewrite` old/new SHA pairs, captures normalized rewrite method (`amend`, `rebase`, or lowercase passthrough), derives deterministic `post-rewrite:::` idempotency keys, and dispatches replay-safe remap ingestion requests. -- `agent trace rewrite trace transformation`: T09 contract in `cli/src/services/hooks.rs` (`finalize_rewrite_trace`) that materializes rewritten-SHA Agent Trace records with `rewrite_from`/`rewrite_method`/`rewrite_confidence` metadata, enforces confidence range normalization (`0.00`..`1.00`), maps quality status thresholds (`final`/`partial`/`needs_review`), and preserves notes+DB persistence parity with retry fallback. -- `agent trace core schema migrations`: T10 contract in `cli/src/services/local_db.rs` (`apply_core_schema_migrations`) that applies idempotent local DB table/index creation for foundational Agent Trace entities (`repositories`, `commits`, `trace_records`, `trace_ranges`) and supports upgrade-safe reapplication on preexisting database state. -- `agent trace reconciliation schema ingestion`: T11 contract in `cli/src/services/local_db.rs` (`apply_core_schema_migrations`) that extends local DB migrations with hosted rewrite reconciliation entities (`reconciliation_runs`, `rewrite_mappings`, `conversations`), per-repository idempotency uniqueness, and query indexes for run status and old->new mapping lookup. +- `agent trace rewrite trace transformation`: T09 contract in `cli/src/services/hooks.rs` (`finalize_rewrite_trace`) that materializes rewritten-SHA Agent Trace records with `rewrite_from`/`rewrite_method`/`rewrite_confidence` metadata, enforces confidence range normalization (`0.00`..`1.00`), maps quality status thresholds (`final`/`partial`/`needs_review`), and currently preserves notes persistence plus retry fallback while local DB writes remain disconnected. +- `agent trace local DB schema migration contract`: Retired `apply_core_schema_migrations` behavior removed from the current runtime during `agent-trace-removal-and-hook-noop-reset` T01; the local DB baseline is now file open/create only. - `agent trace retry replay processor`: T14/T08 operational contract in `cli/src/services/hooks.rs` where `process_trace_retry_queue` dequeues fallback queue entries, retries only previously failed persistence targets (notes and/or DB), requeues remaining failures, and emits per-attempt runtime/error-class metrics via `RetryMetricsSink`; production local hook runtime invokes bounded replay (`max_items=16`) after post-commit and post-rewrite finalization with deterministic retry summary output. - `agent trace local hooks MVP contract and gap matrix`: T01 context artifact at `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md` that freezes local production boundaries/decisions for `agent-trace-local-hooks-production-mvp` and maps current seam-level code truth to required runtime completion tasks (`T02`..`T10`). - `automated profile`: Non-interactive OpenCode configuration variant generated at `config/automated/.opencode/**` that removes ask/confirm gates and applies deterministic behavior policies defined in `context/sce/automated-profile-contract.md`. diff --git a/context/overview.md b/context/overview.md index 28d247c..25d3703 100644 --- a/context/overview.md +++ b/context/overview.md @@ -45,14 +45,13 @@ The CLI now includes a task-scoped Agent Trace schema adapter contract in `cli/s The Agent Trace service now also provides a deterministic payload-builder path (`build_trace_payload`) with AI `model_id` normalization and schema-compliance validation coverage documented in `context/sce/agent-trace-payload-builder-validation.md`. The hooks service now includes a pre-commit staged checkpoint finalization contract (`finalize_pre_commit_checkpoint`) that enforces staged-only attribution, captures index/tree anchors, and no-ops for disabled/unavailable/bare-repo runtime states; this behavior is documented in `context/sce/agent-trace-pre-commit-staged-checkpoint.md`. The hooks service now also exposes a `commit-msg` co-author trailer policy (`apply_commit_msg_coauthor_policy`) that conditionally injects exactly one canonical SCE trailer based on `SCE_DISABLED`, `SCE_COAUTHOR_ENABLED`, and staged-attribution presence, with idempotent deduplication behavior documented in `context/sce/agent-trace-commit-msg-coauthor-policy.md`. -The hooks service now also includes a post-commit trace finalization seam (`finalize_post_commit_trace`) that builds canonical Agent Trace payloads, enforces commit-level idempotency guards, performs notes + DB dual writes, and enqueues retry fallback metadata when persistence targets fail; post-commit runtime now also enforces persistent local DB readiness (`.../sce/agent-trace/local.db`) with automatic schema bootstrap before DB writes, documented in `context/sce/agent-trace-post-commit-dual-write.md`. +The hooks service now also includes a post-commit trace finalization seam (`finalize_post_commit_trace`) that builds canonical Agent Trace payloads, enforces commit-level idempotency guards, persists git notes, and keeps local DB persistence disconnected through a no-op record-store adapter; post-commit runtime still ensures the per-user Agent Trace DB file exists at `.../sce/agent-trace/local.db`, but no schema bootstrap runs before hook execution. This behavior is documented in `context/sce/agent-trace-post-commit-dual-write.md`. The CLI now also includes an approved operator-environment doctor contract documented in `context/sce/agent-trace-hook-doctor.md`; the runtime now matches the implemented T06 slice for `sce doctor --fix` parsing/help, stable problem/fix-result reporting, canonical hook-repair reuse, and bounded doctor-owned Agent Trace directory bootstrap for the missing SCE-owned DB parent path. The hooks service now also includes a post-rewrite local remap ingestion seam (`finalize_post_rewrite_remap`) that parses `post-rewrite` old->new SHA pairs, normalizes rewrite method capture, and derives deterministic per-pair idempotency keys before remap dispatch; this behavior is documented in `context/sce/agent-trace-post-rewrite-local-remap-ingestion.md`. -The hooks service now also includes rewrite trace transformation finalization (`finalize_rewrite_trace`) that materializes rewritten-SHA Agent Trace records with `rewrite_from`/`rewrite_method`/`rewrite_confidence` metadata, confidence-threshold quality mapping (`final`/`partial`/`needs_review`), and notes+DB persistence parity with retry fallback; this behavior is documented in `context/sce/agent-trace-rewrite-trace-transformation.md`. -The local DB service now includes core Agent Trace persistence schema migrations (`apply_core_schema_migrations`) that install idempotent foundational tables and indexes for `repositories`, `commits`, `trace_records`, and `trace_ranges`; this behavior is documented in `context/sce/agent-trace-core-schema-migrations.md`. -The local DB service now also includes reconciliation persistence schema coverage in the same migration entrypoint for hosted rewrite bookkeeping tables (`reconciliation_runs`, `rewrite_mappings`, `conversations`) and replay/query indexes; this behavior is documented in `context/sce/agent-trace-reconciliation-schema-ingestion.md`. +The hooks service now also includes rewrite trace transformation finalization (`finalize_rewrite_trace`) that materializes rewritten-SHA Agent Trace records with `rewrite_from`/`rewrite_method`/`rewrite_confidence` metadata, confidence-threshold quality mapping (`final`/`partial`/`needs_review`), and current notes persistence with retry fallback while local DB writes remain disconnected; this behavior is documented in `context/sce/agent-trace-rewrite-trace-transformation.md`. +The local DB service now limits the Agent Trace runtime path to creating/opening the configured per-user database file with no schema bootstrap or trace tables; this behavior is documented in `context/sce/agent-trace-core-schema-migrations.md`. The hooks service now also includes operational retry-queue replay processing (`process_trace_retry_queue`) invoked from post-commit and post-rewrite runtime flows with bounded same-pass replay and deterministic retry summary output, plus per-attempt runtime/error-class metric emission; this behavior is documented in `context/sce/agent-trace-retry-queue-observability.md`. -The hooks command surface now also supports concrete runtime subcommand routing (`pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`) with deterministic argument/STDIN validation and production post-rewrite runtime wiring (local remap ingestion plus rewritten-trace finalization through notes+DB adapters) owned by `cli/src/services/hooks.rs`; this behavior is documented in `context/sce/agent-trace-hooks-command-routing.md`. +The hooks command surface now also supports concrete runtime subcommand routing (`pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`) with deterministic argument/STDIN validation and production post-rewrite runtime wiring (local remap ingestion plus rewritten-trace finalization through git-notes and no-op DB adapters) owned by `cli/src/services/hooks.rs`; this behavior is documented in `context/sce/agent-trace-hooks-command-routing.md`. The setup service now also exposes deterministic required-hook embedded asset accessors (`iter_required_hook_assets`, `get_required_hook_asset`) backed by canonical templates in `cli/assets/hooks/` for `pre-commit`, `commit-msg`, and `post-commit`; this behavior is documented in `context/sce/setup-githooks-hook-asset-packaging.md`. The setup service now also includes required-hook install orchestration (`install_required_git_hooks`) that resolves repository root and effective hooks path from git truth, enforces deterministic per-hook outcomes (`Installed`/`Updated`/`Skipped`), preserves backup-and-restore rollback for non-git-backed targets, and skips backup creation plus backup-based rollback in git-backed repositories with deterministic git-recovery guidance on swap failures; this behavior is documented in `context/sce/setup-githooks-install-flow.md`. The setup command parser/dispatch now also supports composable setup+hooks runs (`sce setup --opencode|--claude|--both --hooks`) plus hooks-only mode (`sce setup --hooks` with optional `--repo `), enforces deterministic compatibility validation (`--repo` requires `--hooks`; target flags remain mutually exclusive), and emits deterministic setup/hook outcome messaging (`installed`/`updated`/`skipped` with backup status); this behavior is documented in `context/sce/setup-githooks-cli-ux.md`. @@ -104,12 +103,11 @@ Lightweight post-task verification baseline (required after each completed task) - Use `context/sce/agent-trace-payload-builder-validation.md` for the implemented T03 builder path, normalization policy, and schema-validation behavior. - Use `context/sce/agent-trace-pre-commit-staged-checkpoint.md` for the implemented T04 pre-commit staged-only finalization contract and runtime no-op guards. - Use `context/sce/agent-trace-commit-msg-coauthor-policy.md` for the implemented T05 commit-msg canonical co-author trailer policy and idempotent dedupe behavior. -- Use `context/sce/agent-trace-post-commit-dual-write.md` for the implemented T06 post-commit trace finalization and dual-write + queue-fallback behavior, including persistent local DB path/bootstrap policy for runtime writes. +- Use `context/sce/agent-trace-post-commit-dual-write.md` for the current post-commit persistence baseline, including git-notes persistence, no-op local DB record-store behavior, and persistent local DB file bootstrap. - Use `context/sce/agent-trace-hook-doctor.md` for the approved `sce doctor` operator-environment contract, including the current T02 implementation baseline for `--fix` command-surface/output scaffolding, the stable problem/fixability taxonomy, and the rule that new setup/install surfaces must extend doctor coverage. - Use `context/sce/agent-trace-post-rewrite-local-remap-ingestion.md` for the implemented T08 post-rewrite local remap ingestion pipeline (`post-rewrite` pair parsing, rewrite-method normalization, and deterministic idempotency-key derivation). -- Use `context/sce/agent-trace-rewrite-trace-transformation.md` for the implemented T09 rewritten-SHA trace transformation path (`finalize_rewrite_trace`), confidence-based quality status mapping, and rewrite metadata persistence semantics. -- Use `context/sce/agent-trace-core-schema-migrations.md` for the implemented T10 core local schema migration contract (`apply_core_schema_migrations`) and table/index ownership across foundational Agent Trace persistence entities. -- Use `context/sce/agent-trace-reconciliation-schema-ingestion.md` for the implemented T11 reconciliation schema contract (`reconciliation_runs`, `rewrite_mappings`, `conversations`) and replay-safe idempotency/index coverage. +- Use `context/sce/agent-trace-rewrite-trace-transformation.md` for the implemented T09 rewritten-SHA trace transformation path (`finalize_rewrite_trace`), confidence-based quality status mapping, and current persistence semantics. +- Use `context/sce/agent-trace-core-schema-migrations.md` for the current Agent Trace local DB empty-file baseline and removed schema-bootstrap behavior. - Use `context/sce/agent-trace-hosted-event-intake-orchestration.md` for the implemented T12 hosted intake contract (GitHub/GitLab signature verification, old/new head resolution, deterministic reconciliation-run idempotency keys, and replay-safe run insertion outcomes). - Use `context/sce/agent-trace-rewrite-mapping-engine.md` for the implemented T13 hosted mapping engine contract (patch-id exact matching, range-diff/fuzzy scoring precedence, confidence thresholds, and deterministic unresolved handling). - Use `context/sce/agent-trace-retry-queue-observability.md` for the implemented T14 retry replay contract (notes/DB target-scoped recovery, per-attempt runtime/error-class metrics, reconciliation mapped/unmapped + confidence histogram snapshots, and DB-first retry/metrics schema additions). diff --git a/context/patterns.md b/context/patterns.md index 478943f..3012f80 100644 --- a/context/patterns.md +++ b/context/patterns.md @@ -128,13 +128,11 @@ - Model deferred integration boundaries with concrete event/capability data structures (for example hook-runtime attribution snapshots/policies and cloud-sync checkpoints) so later tasks can implement behavior without reshaping public seams. - For pre-commit attribution finalization seams, keep pending staged and unstaged ranges explicitly separated in input models and finalize from staged ranges only, while carrying index/tree anchors for deterministic commit-time attribution binding. - For commit-msg co-author policy seams, gate canonical trailer insertion on runtime controls (`SCE_DISABLED`, `SCE_COAUTHOR_ENABLED`) plus staged SCE-attribution presence, and enforce idempotent dedupe so allowed cases end with exactly one `Co-authored-by: SCE ` trailer. -- For post-commit trace finalization seams, treat commit SHA as the idempotency identity, perform notes + DB writes in the same finalize pass when available, and enqueue retry-fallback entries that explicitly capture failed persistence targets for replay-safe recovery. +- For post-commit trace finalization seams, treat commit SHA as the idempotency identity, keep git-notes persistence and retry-fallback behavior explicit per target, and avoid assuming local DB writes are active while the local record-store adapter is disconnected. - For retry replay seams, process fallback queue entries in bounded batches, avoid same-pass duplicate trace processing, retry only failed targets, emit per-attempt runtime + persistence error-class metrics for operational visibility, and run a bounded replay pass from production post-commit/post-rewrite hook runtime with deterministic summary output. - For post-rewrite remap ingestion seams, parse ` ` pairs from hook input strictly, ignore empty/no-op self-mapping rows, normalize rewrite method labels to lowercase (`amend`/`rebase` when recognized), and derive deterministic per-pair idempotency keys before dispatching remap requests. -- For rewrite trace transformation seams, materialize rewritten records through the canonical Agent Trace builder path, require finite confidence in `[0.0, 1.0]`, normalize confidence to two-decimal metadata strings, map quality thresholds to `final` (`>= 0.90`), `partial` (`0.60..0.89`), and `needs_review` (`< 0.60`), and preserve notes+DB dual-write plus retry-fallback parity. -- For local persistence rollout, ship core schema changes as idempotent `CREATE TABLE IF NOT EXISTS` and `CREATE INDEX IF NOT EXISTS` statements so migration reapplication is upgrade-safe across empty and preexisting local Turso DB states. -- For local hooks production writes, resolve one deterministic per-user persistent DB target (Linux: `${XDG_STATE_HOME:-~/.local/state}/sce/agent-trace/local.db`; platform-equivalent state roots elsewhere), create parent directories before first use, and run schema bootstrap before DB write attempts. -- For hosted rewrite reconciliation persistence, extend the same migration seam (`apply_core_schema_migrations`) with deterministic schema/index statements and per-repository idempotency uniqueness for run/mapping replay safety. +- For rewrite trace transformation seams, materialize rewritten records through the canonical Agent Trace builder path, require finite confidence in `[0.0, 1.0]`, normalize confidence to two-decimal metadata strings, map quality thresholds to `final` (`>= 0.90`), `partial` (`0.60..0.89`), and `needs_review` (`< 0.60`), and keep persistence behavior aligned with the currently active targets rather than assuming local DB parity. +- For the current local DB baseline, resolve one deterministic per-user persistent DB target (Linux: `${XDG_STATE_HOME:-~/.local/state}/sce/agent-trace/local.db`; platform-equivalent state roots elsewhere), create parent directories before first use, and only create/open the DB file without schema bootstrap or trace-table installation. - For hosted event intake seams, verify provider signatures before payload parsing (GitHub `sha256=` HMAC over body, GitLab token-equality secret check), resolve old/new heads from provider payload fields, and derive deterministic reconciliation run idempotency keys from provider+event+repo+head tuple material. - For hosted rewrite mapping seams, resolve candidates deterministically in strict precedence order (patch-id exact, then range-diff score, then fuzzy score), classify top-score ties as `ambiguous`, enforce low-confidence unresolved behavior below `0.60`, and preserve stable outcome ordering via canonical candidate SHA sorting. - For hosted reconciliation observability, publish run-level mapped/unmapped counts, confidence histogram buckets, runtime timing, and normalized error-class labels so retry/quality drift can be monitored without requiring a full dashboard surface. diff --git a/context/plans/agent-trace-removal-and-hook-noop-reset.md b/context/plans/agent-trace-removal-and-hook-noop-reset.md new file mode 100644 index 0000000..9f77047 --- /dev/null +++ b/context/plans/agent-trace-removal-and-hook-noop-reset.md @@ -0,0 +1,63 @@ +# Plan: Agent trace removal with optional attribution gate + +## Change summary +- Remove the current Agent Trace implementation from the CLI/runtime for this release window so the v0.3.0 redesign can start from a clean baseline. +- Keep the local database path behavior limited to creating/opening an empty database file with no schema bootstrap or trace tables. +- Keep hook-trigger entrypoints present, remove trace/persistence behavior, and preserve attribution-only behavior behind a new config/env gate that defaults to disabled. + +## Success criteria +- Agent Trace persistence, schema migration, trace emission, retry, notes, and rewrite-handling behavior are removed from the current runtime surface. +- `local_db` still creates/opens the configured database file, but no schema tables or trace migrations are applied during setup/bootstrap. +- Hook entrypoints needed for attribution wiring still exist, but trace persistence/rewrite side effects are removed and the disabled-default path is a deterministic no-op. +- Attribution behavior tied to hooks is controlled by a config/env gate that defaults to disabled. +- When the attribution gate is explicitly enabled, hook-triggered attribution behavior still functions without reintroducing Agent Trace persistence, schema bootstrap, retry, notes, or rewrite flows. +- User-facing help, doctor/setup/config/runtime context, and generated/current-state docs no longer describe removed Agent Trace behavior as active. + +## Constraints and non-goals +- Do not redesign the future v0.3.0 attribution/tracing architecture in this plan; this change is a rollback/reset to a minimal safe baseline. +- Do not introduce new tracing/persistence behavior behind the new config/env gate in this release; the gate enables attribution-only behavior while removed trace infrastructure stays absent. +- Keep hook command/installation surfaces stable enough that future work can reuse them without reintroducing removed Agent Trace implementation now. +- Treat code as source of truth if current context artifacts still describe removed tracing behavior; context repair is part of this plan. +- Preserve one-task/one-atomic-commit slicing; each executable task must land as one coherent commit. + +## Task stack +- [x] T01: Remove Agent Trace persistence and local DB schema bootstrap (status:done) + - Task ID: T01 + - Goal: Strip Agent Trace-specific local persistence behavior so the runtime only opens/creates the DB file without applying trace schema migrations or writing trace data. + - Boundaries (in/out of scope): In scope: `cli/src/services/local_db.rs` and directly coupled persistence/bootstrap call sites for trace schema setup and trace-specific DB writes. Out of scope: hook command semantics, config/env gating, and broader doc cleanup beyond code comments tightly coupled to the removed persistence flow. + - Done when: Agent Trace schema/bootstrap and trace-write paths are removed or disconnected; opening the local DB yields an empty database file with no trace tables; no runtime path still expects trace migrations to have run. + - Verification notes (commands or checks): Inspect `local_db` ownership and call sites to confirm no trace schema bootstrap remains; run the narrowest relevant validation covering local DB compile/test paths, then include repo baseline checks during final validation. + - Completed: 2026-04-08 + - Files changed: `cli/src/services/local_db.rs`, `cli/src/services/hooks.rs` + - Evidence: `nix run .#pkl-check-generated`; `nix flake check`; `nix develop -c sh -c 'cd cli && cargo check'` + +- [ ] T02: Keep attribution-only hooks behind a disabled-by-default gate (status:todo) + - Task ID: T02 + - Goal: Keep the hook-trigger surface present while removing trace persistence, retry, rewrite handling, and other Agent Trace side effects; preserve attribution-only behavior behind a config/env gate that defaults to disabled. + - Boundaries (in/out of scope): In scope: hook runtime entrypoints, config/env resolution, disabled-default no-op behavior, and enabled-path attribution behavior that does not depend on trace infrastructure. Out of scope: future v0.3.0 tracing redesign, new persistence behavior, and unrelated CLI command-surface redesign. + - Done when: Hook commands/installed triggers remain invocable; default behavior is disabled/no-op; enabling the new config/env gate restores attribution-only behavior; no trace-related side effect runs in either mode; the new gate resolves through the existing precedence model and is documented in code/tests. + - Verification notes (commands or checks): Inspect hook runtime and config resolution paths for disabled-default no-op behavior plus enabled attribution-only behavior; run targeted hook/config tests where available; include full repo validation in the final task. + +- [ ] T03: Remove Agent Trace-specific command, doctor, setup, and path-surface behavior (status:todo) + - Task ID: T03 + - Goal: Eliminate remaining active runtime references to Agent Trace from command help, doctor/setup readiness checks, default-path inventories, and related operator-facing surfaces while preserving the optional attribution-only hook baseline. + - Boundaries (in/out of scope): In scope: command/help text, doctor checks, setup/install expectations, default-path/service references, and removal of dead trace-only seams exposed to users/operators. Out of scope: broad unrelated CLI polish and the future tracing redesign. + - Done when: User-facing runtime/help/doctor/setup/path surfaces no longer present Agent Trace as an active supported feature; any retained hook trigger surface is described as attribution-only and disabled by default; removed trace-only code paths no longer drive warnings/dead branches. + - Verification notes (commands or checks): Inspect command-surface, doctor, setup, and default-path outputs/contracts for removed Agent Trace references; run targeted checks for affected CLI modules, then defer full repo checks to the final task. + +- [ ] T04: Sync current-state context for the trace-removal baseline (status:todo) + - Task ID: T04 + - Goal: Update shared context to reflect that Agent Trace runtime behavior has been removed, hook attribution is optional and disabled by default, and local DB bootstrap is empty-file only. + - Boundaries (in/out of scope): In scope: affected `context/overview.md`, `context/glossary.md`, `context/context-map.md`, and focused `context/sce/` artifacts describing tracing/hooks/local DB behavior. Out of scope: speculative v0.3.0 redesign docs or historical postmortems. + - Done when: Current-state context no longer documents removed Agent Trace behavior as active, and retained hook attribution/local DB behavior is described accurately for future sessions. + - Verification notes (commands or checks): Review all touched context files against code truth; ensure stale Agent Trace contract files are updated, replaced, or removed as appropriate. + +- [ ] T05: Validation and cleanup (status:todo) + - Task ID: T05 + - Goal: Run full repository validation, confirm removed tracing behavior stays absent while optional attribution behavior still works when enabled, and clean up any leftover dead references or temporary scaffolding. + - Boundaries (in/out of scope): In scope: `nix run .#pkl-check-generated`, `nix flake check`, final targeted spot-checks for disabled-default noop hooks, enabled attribution-only behavior, empty DB expectations, and final context-sync verification. Out of scope: new feature work or redesign follow-ons. + - Done when: Required validation passes, leftover dead/stale trace-removal artifacts are cleaned up, and plan/context state is ready for handoff completion. + - Verification notes (commands or checks): `nix run .#pkl-check-generated`; `nix flake check`; targeted inspection that hooks are no-op by default, attribution works when enabled, and local DB bootstrap remains empty-file only. + +## Open questions +- None. diff --git a/context/sce/agent-trace-core-schema-migrations.md b/context/sce/agent-trace-core-schema-migrations.md index 03dda5a..33ae181 100644 --- a/context/sce/agent-trace-core-schema-migrations.md +++ b/context/sce/agent-trace-core-schema-migrations.md @@ -1,34 +1,34 @@ -# Agent Trace Core Schema Migrations +# Agent Trace local DB empty-file baseline ## Scope -- Implements T10 for plan `agent-trace-attribution-no-git-wrapper`. -- Defines foundational local persistence schema for Agent Trace ingestion. -- Covers only core entities: `repositories`, `commits`, `trace_records`, `trace_ranges`. +- Current state after `agent-trace-removal-and-hook-noop-reset` T01. +- Defines the minimal local DB runtime baseline for the existing Agent Trace path. +- Covers file creation/open behavior only; schema tables and migrations are not active. ## Code ownership -- Migration entrypoint: `cli/src/services/local_db.rs` (`apply_core_schema_migrations`). +- Runtime bootstrap entrypoint: `cli/src/services/local_db.rs` (`ensure_agent_trace_local_db_ready_blocking`). - Shared local DB connection helper: `cli/src/services/local_db.rs` (`connect_local`). -## Migration contract +## Current contract -- Migrations are idempotent and upgrade-safe via `CREATE TABLE IF NOT EXISTS` and `CREATE INDEX IF NOT EXISTS`. -- Reapplying migrations must succeed on both empty and preexisting local DB states. -- Core schema statements are deterministic and owned in one ordered list (`CORE_SCHEMA_STATEMENTS`). +- `ensure_agent_trace_local_db_ready_blocking` resolves the canonical per-user state path and creates parent directories when needed. +- The runtime opens/creates the local Turso file and returns its path. +- No schema bootstrap runs. +- No trace, reconciliation, retry, or prompt tables are created as part of runtime readiness. -## Core tables +## Observable consequences -- `repositories`: repository identity root (`canonical_root`) plus VCS provider marker. -- `commits`: per-repository commit identity (`commit_sha`), optional parent SHA, and idempotency key capture. -- `trace_records`: canonical stored Agent Trace payload envelope per commit (content type, notes ref, payload JSON, quality status, recorded timestamp). -- `trace_ranges`: flattened line-range attribution rows linked to a trace record. +- A newly created DB file is empty until another future task introduces schema creation. +- Hook runtime may still ensure the file exists, but it must not assume DB tables are present. +- Local DB persistence adapters that previously wrote trace or reconciliation rows are disconnected in the current runtime. -## Indexes +## Removed behavior -- `idx_commits_repository_commit_sha` on `commits(repository_id, commit_sha)`. -- `idx_trace_records_repository_commit` on `trace_records(repository_id, commit_id)`. -- `idx_trace_ranges_record_file` on `trace_ranges(trace_record_id, file_path)`. +- `apply_core_schema_migrations` is no longer part of the active runtime surface. +- The ordered schema statement list for Agent Trace persistence is no longer present in `cli/src/services/local_db.rs`. +- Hosted reconciliation schema ingestion is also absent from the current local DB bootstrap path. ## Verification evidence diff --git a/context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md b/context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md index 8908c0a..a2a38e8 100644 --- a/context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md +++ b/context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md @@ -43,7 +43,7 @@ Freeze one implementation-ready contract for Local Hooks MVP productionization a ### 6) Persistence and schema contract - Local production runtime MUST use a deterministic persistent DB path policy (not in-memory). -- Schema bootstrap (`apply_core_schema_migrations`) MUST run before production write paths. +- Current rollback baseline opens/creates the deterministic local DB file only; schema bootstrap is not part of the active runtime. - Notes persistence target MUST remain `refs/notes/agent-trace` with content type `application/vnd.agent-trace.record+json`. ### 7) Retry/observability contract diff --git a/context/sce/agent-trace-post-commit-dual-write.md b/context/sce/agent-trace-post-commit-dual-write.md index 5425662..3040891 100644 --- a/context/sce/agent-trace-post-commit-dual-write.md +++ b/context/sce/agent-trace-post-commit-dual-write.md @@ -1,4 +1,4 @@ -# Agent Trace post-commit dual-write finalization +# Agent Trace post-commit persistence baseline ## Status - Plan: `agent-trace-attribution-no-git-wrapper` @@ -18,11 +18,12 @@ - optional `metadata[dev.crocoder.sce.parent_revision]` when a parent SHA is available - Notes write policy is fixed to `refs/notes/agent-trace` with MIME `application/vnd.agent-trace.record+json`. -## Dual-write and fallback behavior -- Finalization attempts both targets in one pass: +## Current persistence behavior +- Finalization still attempts both persistence targets in one pass: - notes write via `TraceNotesWriter` - - DB persistence via `TraceRecordStore` -- Successful writes (`Written` or `AlreadyExists`) on both targets mark commit emission in `TraceEmissionLedger` and return `Persisted`. + - local DB write via `TraceRecordStore` +- The production local DB adapter is now `NoOpTraceRecordStore`, so the DB target returns `AlreadyExists` without writing trace rows. +- Successful notes persistence plus the no-op DB result mark commit emission in `TraceEmissionLedger` and return `Persisted`. - Any failed target (`PersistenceWriteResult::Failed`) enqueues one retry item via `TraceRetryQueue` with explicit failed target list and returns `QueuedFallback`. - Retry queue entries carry the full trace record, MIME type, notes ref, and failed target list to support replay-safe recovery. @@ -35,12 +36,12 @@ - derives deterministic idempotency (`post-commit:`) and deterministic UUIDv4 trace IDs from commit/timestamp seed - Production adapters currently bound in runtime: - notes adapter: `GitNotesTraceWriter` writes canonical JSON note payloads to `refs/notes/agent-trace` - - local record store adapter: `LocalDbTraceRecordStore` writes trace records and flattened ranges into the persistent Turso target at `.../sce/agent-trace/local.db` + - local record store adapter: `NoOpTraceRecordStore` disconnects local DB trace persistence while keeping the finalize path compile-safe - emission ledger adapter: `FileTraceEmissionLedger` stores emitted commit SHAs at `sce/trace-emission-ledger.txt` - retry queue adapter: `JsonFileTraceRetryQueue` appends failed-target fallback entries to `sce/trace-retry-queue.jsonl` -- Runtime schema bootstrap is mandatory before post-commit persistence: +- Runtime DB-file bootstrap remains mandatory before post-commit execution: - `resolve_post_commit_runtime_paths` calls `ensure_agent_trace_local_db_ready_blocking`. - - `ensure_agent_trace_local_db_ready_blocking` resolves platform state-data DB path (`${XDG_STATE_HOME:-~/.local/state}/sce/agent-trace/local.db` on Linux, platform-equivalent user state root elsewhere), creates parent directories, and applies `apply_core_schema_migrations` before writes. + - `ensure_agent_trace_local_db_ready_blocking` resolves platform state-data DB path (`${XDG_STATE_HOME:-~/.local/state}/sce/agent-trace/local.db` on Linux, platform-equivalent user state root elsewhere), creates parent directories, and opens/creates the DB file without applying schema migrations. - Runtime posture remains fail-open: operational errors return deterministic skip/fallback messages instead of aborting commit progression. ## Verification evidence diff --git a/context/sce/agent-trace-reconciliation-schema-ingestion.md b/context/sce/agent-trace-reconciliation-schema-ingestion.md deleted file mode 100644 index 94a2f24..0000000 --- a/context/sce/agent-trace-reconciliation-schema-ingestion.md +++ /dev/null @@ -1,47 +0,0 @@ -# Agent Trace Reconciliation Schema Ingestion - -## Scope - -- Implements T11 for plan `agent-trace-attribution-no-git-wrapper`. -- Adds hosted-rewrite persistence schema slices for reconciliation bookkeeping and replay-safe mapping ingestion. -- Covers schema/migration behavior only; provider webhook transport remains out of scope. - -## Code ownership - -- Migration entrypoint: `cli/src/services/local_db.rs` (`apply_core_schema_migrations`). -- Schema statement source of truth: `cli/src/services/local_db.rs` (`CORE_SCHEMA_STATEMENTS`). - -## Migration contract - -- New reconciliation entities are installed idempotently with `CREATE TABLE IF NOT EXISTS` and `CREATE INDEX IF NOT EXISTS`. -- Reapplying migrations on preexisting DB state remains upgrade-safe. -- Run-level and mapping-level replay protection is enforced through per-repository idempotency uniqueness. - -## Reconciliation tables - -- `reconciliation_runs`: stores provider, run status lifecycle, run timing, and per-repository idempotency key. -- `rewrite_mappings`: stores old/new commit SHA mapping outcomes, confidence, mapping status, and per-repository idempotency key per mapping row. -- `conversations`: stores canonical conversation URLs per repository/source for hosted reconciliation linkage. - -## Indexes - -- `idx_reconciliation_runs_repository_status` on `reconciliation_runs(repository_id, status)`. -- `idx_rewrite_mappings_run_old_sha` on `rewrite_mappings(reconciliation_run_id, old_commit_sha)`. -- `idx_rewrite_mappings_repository_old_sha` on `rewrite_mappings(repository_id, old_commit_sha)`. -- `idx_conversations_repository_source` on `conversations(repository_id, source)`. - -## Validation coverage - -- Schema existence + index presence checks in `core_schema_migrations_create_required_tables_and_indexes`. -- Upgrade-safe reapplication checks in `core_schema_migrations_are_upgrade_safe_for_preexisting_state`. -- Reconciliation replay/query checks in `reconciliation_schema_supports_replay_safe_runs_and_mapping_queries`. - -## Verification evidence - -- `nix flake check` - -## Related context - -- `context/sce/agent-trace-core-schema-migrations.md` -- `context/sce/agent-trace-rewrite-trace-transformation.md` -- `context/plans/agent-trace-attribution-no-git-wrapper.md` diff --git a/context/sce/agent-trace-rewrite-trace-transformation.md b/context/sce/agent-trace-rewrite-trace-transformation.md index 78fe196..10944f7 100644 --- a/context/sce/agent-trace-rewrite-trace-transformation.md +++ b/context/sce/agent-trace-rewrite-trace-transformation.md @@ -40,8 +40,8 @@ ## Persistence semantics -- Rewritten trace finalization follows the same notes+DB persistence contract as post-commit traces. -- On dual-write success: +- Rewritten trace finalization follows the same current notes-plus-no-op-DB baseline as post-commit traces. +- On success: - commit SHA is marked emitted in `TraceEmissionLedger` - outcome is `RewriteTraceFinalization::Persisted` - On any target failure: @@ -54,7 +54,7 @@ ## Tests added -- Metadata integrity and notes/DB parity for persisted rewrite traces. +- Metadata integrity and current notes/no-op-DB persistence behavior for rewritten traces. - Confidence-threshold quality mapping (`final`, `partial`, `needs_review`). - Confidence range validation errors for out-of-range input. - No-op behavior when rewritten commit was already finalized. From 57f94e2ce0f9eee56d4a3725371cb2c745e57492 Mon Sep 17 00:00:00 2001 From: David Abram Date: Wed, 8 Apr 2026 16:26:23 +0200 Subject: [PATCH 2/6] hooks): Remove agent trace infrastructure and add attribution-only mode Remove the full agent trace pipeline (pre-commit checkpointing, post-commit trace persistence, retry queues, emission ledger) from git hooks. The hooks now operate in a simplified attribution-only mode that only manages the SCE co-author trailer in commit messages. Add `policies.attribution_hooks.enabled` config option and `SCE_ATTRIBUTION_HOOKS_ENABLED` environment variable to control whether attribution hooks are active. Defaults to false. Co-authored-by: SCE --- cli/src/services/config.rs | 138 +- cli/src/services/hooks.rs | 2851 +---------------- config/pkl/base/sce-config-schema.pkl | 9 + config/schema/sce-config.schema.json | 9 + context/architecture.md | 4 +- context/context-map.md | 8 +- context/glossary.md | 9 +- context/overview.md | 10 +- context/patterns.md | 1 + ...agent-trace-removal-and-hook-noop-reset.md | 5 +- .../agent-trace-commit-msg-coauthor-policy.md | 11 +- .../sce/agent-trace-hooks-command-routing.md | 43 +- .../sce/agent-trace-post-commit-dual-write.md | 16 +- ...race-post-rewrite-local-remap-ingestion.md | 8 +- ...gent-trace-pre-commit-staged-checkpoint.md | 8 +- .../agent-trace-retry-queue-observability.md | 10 +- ...gent-trace-rewrite-trace-transformation.md | 7 +- 17 files changed, 315 insertions(+), 2832 deletions(-) diff --git a/cli/src/services/config.rs b/cli/src/services/config.rs index 935b9c6..52ae83c 100644 --- a/cli/src/services/config.rs +++ b/cli/src/services/config.rs @@ -41,6 +41,7 @@ const ENV_LOG_FILE_MODE: &str = "SCE_LOG_FILE_MODE"; const ENV_OTEL_ENABLED: &str = "SCE_OTEL_ENABLED"; const ENV_OTEL_ENDPOINT: &str = "OTEL_EXPORTER_OTLP_ENDPOINT"; const ENV_OTEL_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_PROTOCOL"; +const ENV_ATTRIBUTION_HOOKS_ENABLED: &str = "SCE_ATTRIBUTION_HOOKS_ENABLED"; const WORKOS_CLIENT_ID_ENV: &str = "WORKOS_CLIENT_ID"; const WORKOS_CLIENT_ID_BAKED_DEFAULT: &str = "client_sce_default"; const WORKOS_CLIENT_ID_KEY: AuthConfigKeySpec = AuthConfigKeySpec { @@ -259,6 +260,7 @@ struct RuntimeConfig { otel_endpoint: ResolvedValue, otel_protocol: ResolvedValue, timeout_ms: ResolvedValue, + attribution_hooks_enabled: ResolvedValue, workos_client_id: ResolvedOptionalValue, bash_policies: ResolvedOptionalValue, validation_warnings: Vec, @@ -287,6 +289,11 @@ pub(crate) struct ResolvedObservabilityRuntimeConfig { pub(crate) loaded_config_paths: Vec, } +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct ResolvedHookRuntimeConfig { + pub(crate) attribution_hooks_enabled: bool, +} + #[derive(Clone, Debug, Eq, PartialEq)] struct FileConfig { log_level: Option>, @@ -297,6 +304,7 @@ struct FileConfig { otel_endpoint: Option>, otel_protocol: Option>, timeout_ms: Option>, + attribution_hooks_enabled: Option>, workos_client_id: Option>, bash_policy_presets: Option>>, bash_policy_custom: Option>>, @@ -454,6 +462,19 @@ pub(crate) fn resolve_observability_runtime_config( ) } +pub(crate) fn resolve_hook_runtime_config(cwd: &Path) -> Result { + resolve_hook_runtime_config_with( + cwd, + |key| std::env::var(key).ok(), + |path| { + std::fs::read_to_string(path) + .with_context(|| format!("Failed to read config file '{}'.", path.display())) + }, + Path::exists, + resolve_default_global_config_path, + ) +} + pub(crate) fn resolve_auth_runtime_config_with( cwd: &Path, env_lookup: FEnv, @@ -523,6 +544,37 @@ where }) } +pub(crate) fn resolve_hook_runtime_config_with( + cwd: &Path, + env_lookup: FEnv, + read_file: FRead, + path_exists: fn(&Path) -> bool, + resolve_global_config_path: FGlobalPath, +) -> Result +where + FEnv: Fn(&str) -> Option, + FRead: Fn(&Path) -> Result, + FGlobalPath: Fn() -> Result, +{ + let runtime = resolve_runtime_config_with( + &ConfigRequest { + report_format: ReportFormat::Text, + config_path: None, + log_level: None, + timeout_ms: None, + }, + cwd, + env_lookup, + read_file, + path_exists, + resolve_global_config_path, + )?; + + Ok(ResolvedHookRuntimeConfig { + attribution_hooks_enabled: runtime.attribution_hooks_enabled.value, + }) +} + fn resolve_runtime_config(request: &ConfigRequest, cwd: &Path) -> Result { resolve_runtime_config_with( request, @@ -568,6 +620,7 @@ where otel_endpoint: None, otel_protocol: None, timeout_ms: None, + attribution_hooks_enabled: None, workos_client_id: None, bash_policy_presets: None, bash_policy_custom: None, @@ -599,6 +652,9 @@ where if let Some(timeout_ms) = layer.timeout_ms { file_config.timeout_ms = Some(timeout_ms); } + if let Some(attribution_hooks_enabled) = layer.attribution_hooks_enabled { + file_config.attribution_hooks_enabled = Some(attribution_hooks_enabled); + } if let Some(workos_client_id) = layer.workos_client_id { file_config.workos_client_id = Some(workos_client_id); } @@ -769,6 +825,27 @@ where }; } + let mut resolved_attribution_hooks_enabled = ResolvedValue { + value: false, + source: ValueSource::Default, + }; + if let Some(value) = file_config.attribution_hooks_enabled { + resolved_attribution_hooks_enabled = ResolvedValue { + value: value.value, + source: ValueSource::ConfigFile(value.source), + }; + } + if let Some(raw) = env_lookup(ENV_ATTRIBUTION_HOOKS_ENABLED) { + resolved_attribution_hooks_enabled = ResolvedValue { + value: parse_bool_value( + ENV_ATTRIBUTION_HOOKS_ENABLED, + &raw, + ENV_ATTRIBUTION_HOOKS_ENABLED, + )?, + source: ValueSource::Env, + }; + } + let resolved_workos_client_id = resolve_optional_auth_config_value( WORKOS_CLIENT_ID_KEY, file_config.workos_client_id, @@ -791,6 +868,7 @@ where otel_endpoint: resolved_otel_endpoint, otel_protocol: resolved_otel_protocol, timeout_ms: resolved_timeout_ms, + attribution_hooks_enabled: resolved_attribution_hooks_enabled, workos_client_id: resolved_workos_client_id, bash_policies: resolved_bash_policies, validation_warnings, @@ -1110,6 +1188,7 @@ fn parse_file_config(raw: &str, path: &Path, source: ConfigPathSource) -> Result None => None, }; + let attribution_hooks_enabled = parse_attribution_hooks_config(object, path, source)?; let workos_client_id = parse_optional_string_key(object, path, source, WORKOS_CLIENT_ID_KEY)?; let (bash_policy_presets, bash_policy_custom) = parse_bash_policy_config(object, path, source)?; @@ -1122,6 +1201,7 @@ fn parse_file_config(raw: &str, path: &Path, source: ConfigPathSource) -> Result otel_endpoint, otel_protocol, timeout_ms, + attribution_hooks_enabled, workos_client_id, bash_policy_presets, bash_policy_custom, @@ -1219,9 +1299,9 @@ fn parse_bash_policy_config( })?; for key in policies_object.keys() { - if key != "bash" { + if key != "bash" && key != "attribution_hooks" { bail!( - "Config key 'policies' in '{}' contains unknown key '{}'. Allowed keys: bash.", + "Config key 'policies' in '{}' contains unknown key '{}'. Allowed keys: bash, attribution_hooks.", path.display(), key ); @@ -1268,6 +1348,60 @@ fn parse_bash_policy_config( Ok((presets, custom)) } +fn parse_attribution_hooks_config( + object: &serde_json::Map, + path: &Path, + source: ConfigPathSource, +) -> Result>> { + let Some(policies_value) = object.get("policies") else { + return Ok(None); + }; + + let policies_object = policies_value.as_object().with_context(|| { + format!( + "Config key 'policies' in '{}' must be an object.", + path.display() + ) + })?; + + let Some(attribution_hooks_value) = policies_object.get("attribution_hooks") else { + return Ok(None); + }; + + let attribution_hooks_object = attribution_hooks_value.as_object().with_context(|| { + format!( + "Config key 'policies.attribution_hooks' in '{}' must be an object.", + path.display() + ) + })?; + + for key in attribution_hooks_object.keys() { + if key != "enabled" { + bail!( + "Config key 'policies.attribution_hooks' in '{}' contains unknown key '{}'. Allowed keys: enabled.", + path.display(), + key + ); + } + } + + let Some(enabled) = attribution_hooks_object.get("enabled") else { + return Ok(None); + }; + + let enabled = enabled.as_bool().with_context(|| { + format!( + "Config key 'policies.attribution_hooks.enabled' in '{}' must be a boolean.", + path.display() + ) + })?; + + Ok(Some(FileConfigValue { + value: enabled, + source, + })) +} + fn parse_bash_policy_presets(value: &Value, path: &Path) -> Result> { let items = value.as_array().with_context(|| { format!( diff --git a/cli/src/services/hooks.rs b/cli/src/services/hooks.rs index d6b7ab1..e1831b5 100644 --- a/cli/src/services/hooks.rs +++ b/cli/src/services/hooks.rs @@ -1,33 +1,11 @@ use anyhow::{bail, Context, Result}; -use std::collections::{BTreeMap, HashSet}; use std::fs; -use std::io::Write; use std::path::{Path, PathBuf}; -use std::process::Command; -use std::time::Instant; -use crate::services::agent_trace::{ - build_trace_payload, AgentTraceContributor, AgentTraceConversation, AgentTraceFile, - AgentTraceRange, AgentTraceRecord, AgentTraceVcs, ContributorInput, ContributorType, - ConversationInput, FileAttributionInput, QualityStatus, RangeInput, RewriteInfo, - TraceAdapterInput, METADATA_IDEMPOTENCY_KEY, TRACE_CONTENT_TYPE, TRACE_VERSION, VCS_TYPE_GIT, -}; -use crate::services::default_paths; -use crate::services::local_db::ensure_agent_trace_local_db_ready_blocking; +use crate::services::config; pub const NAME: &str = "hooks"; pub const CANONICAL_SCE_COAUTHOR_TRAILER: &str = "Co-authored-by: SCE "; -pub const POST_COMMIT_PARENT_SHA_METADATA_KEY: &str = "dev.crocoder.sce.parent_revision"; -const CLAUDE_CODE_HARNESS_TYPE: &str = "claude_code"; -const MAX_PROMPT_BYTES: usize = 10 * 1024; -const MODEL_ID_ENV_KEYS: [&str; 5] = [ - "SCE_MODEL_ID", - "CLAUDE_MODEL", - "CLAUDE_CODE_MODEL", - "ANTHROPIC_MODEL", - "MODEL_ID", -]; -const RETRY_QUEUE_MAX_ITEMS_PER_RUN: usize = 16; #[derive(Clone, Debug, Eq, PartialEq)] pub enum HookSubcommand { @@ -51,503 +29,12 @@ pub fn run_hooks_subcommand(subcommand: HookSubcommand) -> Result { fn run_pre_commit_subcommand() -> Result { let repository_root = std::env::current_dir() .context("Failed to determine current directory for pre-commit runtime invocation.")?; - run_pre_commit_subcommand_in_repo(&repository_root) -} - -#[allow(clippy::unnecessary_wraps)] -fn run_pre_commit_subcommand_in_repo(repository_root: &Path) -> Result { - let runtime = resolve_pre_commit_runtime_state(repository_root); - - if runtime.sce_disabled || !runtime.cli_available || runtime.is_bare_repo { - let reason = if runtime.sce_disabled { - PreCommitNoOpReason::Disabled - } else if !runtime.cli_available { - PreCommitNoOpReason::CliUnavailable - } else { - PreCommitNoOpReason::BareRepository - }; - - return Ok(format!( - "pre-commit hook executed with no-op runtime state: {reason:?}" - )); - } - - let anchors = match capture_pre_commit_tree_anchors(repository_root) { - Ok(anchors) => anchors, - Err(error) => { - return Ok(format!( - "pre-commit hook skipped checkpoint finalization: failed to capture git anchors ({error})" - )); - } - }; - - let pending = match collect_pending_checkpoint(repository_root) { - Ok(pending) => pending, - Err(error) => { - return Ok(format!( - "pre-commit hook skipped checkpoint finalization: failed to collect staged attribution ({error})" - )); - } - }; - - let outcome = finalize_pre_commit_checkpoint(&runtime, anchors, pending); - - let message = match outcome { - PreCommitFinalization::NoOp(reason) => { - format!("pre-commit hook executed with no-op runtime state: {reason:?}") - } - PreCommitFinalization::Finalized(checkpoint) => { - if let Err(error) = write_finalized_checkpoint(repository_root, &checkpoint) { - return Ok(format!( - "pre-commit hook finalized staged checkpoint for {} file(s) but failed to persist handoff artifact ({error})", - checkpoint.files.len() - )); - } - format!( - "pre-commit hook executed and finalized staged checkpoint for {} file(s).", - checkpoint.files.len() - ) - } - }; - - Ok(message) -} - -fn resolve_pre_commit_runtime_state(repository_root: &Path) -> PreCommitRuntimeState { - PreCommitRuntimeState { - sce_disabled: env_flag_is_truthy("SCE_DISABLED"), - cli_available: git_command_success(repository_root, &["--version"]), - is_bare_repo: git_command_output(repository_root, &["rev-parse", "--is-bare-repository"]) - .is_some_and(|output| output == "true"), - } -} - -fn env_flag_is_truthy(name: &str) -> bool { - std::env::var(name) - .ok() - .is_some_and(|value| env_value_is_truthy(&value)) -} - -fn env_flag_is_enabled_by_default(name: &str) -> bool { - match std::env::var(name) { - Ok(value) => env_value_is_truthy(&value), - Err(_) => true, - } -} - -fn env_value_is_truthy(value: &str) -> bool { - matches!( - value.trim().to_ascii_lowercase().as_str(), - "1" | "true" | "yes" | "on" - ) -} - -fn git_command_success(repository_root: &Path, args: &[&str]) -> bool { - Command::new("git") - .args(args) - .current_dir(repository_root) - .output() - .map(|output| output.status.success()) - .unwrap_or(false) -} - -fn git_command_output(repository_root: &Path, args: &[&str]) -> Option { - let output = Command::new("git") - .args(args) - .current_dir(repository_root) - .output() - .ok()?; - - if !output.status.success() { - return None; - } - - let stdout = String::from_utf8(output.stdout).ok()?; - Some(stdout.trim().to_string()) -} - -fn run_git_command(repository_root: &Path, args: &[&str], context_message: &str) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(repository_root) - .output() - .with_context(|| { - format!( - "{} (directory: '{}')", - context_message, - repository_root.display() - ) - })?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); - let diagnostic = if stderr.is_empty() { - String::from("git command exited with a non-zero status") - } else { - stderr - }; - bail!("{context_message} {diagnostic}"); - } - - String::from_utf8(output.stdout) - .context("git command output contained invalid UTF-8") - .map(|stdout| stdout.trim().to_string()) -} - -fn run_git_command_allow_empty( - repository_root: &Path, - args: &[&str], - context_message: &str, -) -> Result { - let output = Command::new("git") - .args(args) - .current_dir(repository_root) - .output() - .with_context(|| { - format!( - "{} (directory: '{}')", - context_message, - repository_root.display() - ) - })?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); - let diagnostic = if stderr.is_empty() { - String::from("git command exited with a non-zero status") - } else { - stderr - }; - bail!("{context_message} {diagnostic}"); - } - - String::from_utf8(output.stdout).context("git command output contained invalid UTF-8") -} - -fn capture_pre_commit_tree_anchors(repository_root: &Path) -> Result { - let index_tree = run_git_command( - repository_root, - &["write-tree"], - "Failed to capture index tree anchor for pre-commit checkpoint.", - )?; - let head_tree = git_command_output(repository_root, &["rev-parse", "--verify", "HEAD^{tree}"]); - - Ok(PreCommitTreeAnchors { - index_tree, - head_tree, - }) -} - -fn collect_pending_checkpoint(repository_root: &Path) -> Result { - let staged_diff = run_git_command_allow_empty( - repository_root, - &[ - "diff", - "--cached", - "--unified=0", - "--no-color", - "--no-ext-diff", - ], - "Failed to collect staged diff for pre-commit attribution.", - )?; - let unstaged_diff = run_git_command_allow_empty( - repository_root, - &["diff", "--unified=0", "--no-color", "--no-ext-diff"], - "Failed to collect unstaged diff for pre-commit attribution.", - )?; - - let staged_ranges = parse_unified_zero_diff_ranges(&staged_diff)?; - let unstaged_ranges = parse_unified_zero_diff_ranges(&unstaged_diff)?; - - let mut all_paths = HashSet::new(); - for path in staged_ranges.keys() { - // Clone required: HashSet::insert requires ownership of the key - all_paths.insert(path.clone()); - } - for path in unstaged_ranges.keys() { - // Clone required: HashSet::insert requires ownership of the key - all_paths.insert(path.clone()); - } - - // TODO(0.3.0): Replace with attribution-aware producer. - // Currently defaults to true for all staged files, which means all commits - // will receive the SCE co-author trailer when the policy gate passes. - // Future versions will require explicit attribution marking from a - // separate producer that validates staged ranges came from SCE contributions. - let files = all_paths - .iter() - .map(|path| PendingFileCheckpoint { - path: path.clone(), - has_sce_attribution: true, - staged_ranges: staged_ranges.get(path).cloned().unwrap_or_default(), - unstaged_ranges: unstaged_ranges.get(path).cloned().unwrap_or_default(), - }) - .collect(); - - let git_branch = resolve_pre_commit_git_branch(repository_root)?; - let model_id = resolve_pre_commit_model_id(); - let prompts = load_pending_prompts(repository_root)?; - - Ok(PendingCheckpoint { - files, - harness_type: CLAUDE_CODE_HARNESS_TYPE.to_string(), - git_branch, - model_id, - prompts, - }) -} - -fn resolve_pre_commit_git_branch(repository_root: &Path) -> Result> { - let branch = run_git_command_allow_empty( - repository_root, - &["branch", "--show-current"], - "Failed to resolve git branch for pre-commit checkpoint.", - )?; - - let trimmed = branch.trim(); - if trimmed.is_empty() { - return Ok(None); - } - - Ok(Some(trimmed.to_string())) -} - -fn resolve_pre_commit_model_id() -> Option { - MODEL_ID_ENV_KEYS - .iter() - .filter_map(|name| std::env::var(name).ok()) - .map(|value| value.trim().to_string()) - .find(|value| !value.is_empty()) -} - -fn load_pending_prompts(repository_root: &Path) -> Result> { - let prompt_capture_path = - resolve_git_path(repository_root, default_paths::git_relative_path::PROMPTS)?; - let payload = match fs::read_to_string(&prompt_capture_path) { - Ok(payload) => payload, - Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()), - Err(error) => { - bail!( - "Failed to read prompt capture file '{}': {}", - prompt_capture_path.display(), - error - ) - } - }; - - let mut prompts = Vec::new(); - let mut seen_entries = HashSet::new(); - let mut last_known_cwd: Option = None; - - for line in payload.lines() { - let trimmed = line.trim(); - if trimmed.is_empty() { - continue; - } - - let Ok(json) = serde_json::from_str::(trimmed) else { - continue; - }; - - let Some(prompt_text) = json.get("prompt").and_then(serde_json::Value::as_str) else { - continue; - }; - let Some(captured_at) = json.get("timestamp").and_then(serde_json::Value::as_str) else { - continue; - }; - - let dedupe_key = format!("{prompt_text}\u{001f}{captured_at}"); - if !seen_entries.insert(dedupe_key) { - continue; - } - - let Ok(turn_number) = u32::try_from(prompts.len() + 1) else { - break; - }; - - let cwd = json - .get("cwd") - .and_then(serde_json::Value::as_str) - .map(str::trim) - .filter(|value| !value.is_empty()) - .map(ToOwned::to_owned) - .or_else(|| last_known_cwd.clone()); - - if let Some(cwd_value) = &cwd { - last_known_cwd = Some(cwd_value.clone()); - } - - prompts.push(PendingPromptCheckpoint { - turn_number, - prompt_text: prompt_text.to_string(), - prompt_length: prompt_text.len(), - is_truncated: false, - cwd, - transcript_path: json - .get("transcript_path") - .and_then(serde_json::Value::as_str) - .map(str::trim) - .filter(|value| !value.is_empty()) - .map(ToOwned::to_owned), - captured_at: captured_at.to_string(), - }); - } - - Ok(prompts) -} - -fn parse_unified_zero_diff_ranges( - contents: &str, -) -> Result>> { - let mut ranges_by_path: BTreeMap> = BTreeMap::new(); - let mut current_path: Option = None; - - for line in contents.lines() { - if let Some(path) = line.strip_prefix("+++ b/") { - current_path = Some(path.to_string()); - continue; - } - - if line.starts_with("+++") { - current_path = None; - continue; - } - - if !line.starts_with("@@") { - continue; - } - - let Some(path) = current_path.clone() else { - continue; - }; - - if let Some(range) = parse_hunk_new_range(line)? { - ranges_by_path.entry(path).or_default().push(range); - } - } - - Ok(ranges_by_path) -} - -fn parse_hunk_new_range(header_line: &str) -> Result> { - let mut fields = header_line.split_whitespace(); - let _ = fields.next(); - let _ = fields.next(); - let Some(new_range_field) = fields.next() else { - bail!("Invalid unified diff hunk header '{header_line}': missing new-range field"); - }; - - let Some(range_body) = new_range_field.strip_prefix('+') else { - bail!("Invalid unified diff hunk header '{header_line}': malformed new-range field"); - }; - - let mut parts = range_body.split(','); - let start_line: u32 = parts - .next() - .context("Unified diff hunk is missing start line")? - .parse() - .with_context(|| format!("Invalid hunk start line in '{header_line}': expected integer"))?; - let line_count: u32 = parts - .next() - .map(str::parse) - .transpose() - .with_context(|| format!("Invalid hunk line count in '{header_line}': expected integer"))? - .unwrap_or(1); - - if line_count == 0 { - return Ok(None); - } - - Ok(Some(PendingLineRange { - start_line, - end_line: start_line + line_count - 1, - })) -} - -fn resolve_pre_commit_checkpoint_path(repository_root: &Path) -> Result { - let resolved = run_git_command( - repository_root, - &[ - "rev-parse", - "--git-path", - default_paths::git_relative_path::PRE_COMMIT_CHECKPOINT, - ], - "Failed to resolve pre-commit checkpoint handoff path.", - )?; - let path = PathBuf::from(resolved); - - if path.is_absolute() { - return Ok(path); - } - - Ok(repository_root.join(path)) -} - -fn write_finalized_checkpoint( - repository_root: &Path, - checkpoint: &FinalizedCheckpoint, -) -> Result<()> { - let checkpoint_path = resolve_pre_commit_checkpoint_path(repository_root)?; - let parent = checkpoint_path - .parent() - .context("Resolved pre-commit checkpoint path has no parent directory")?; - fs::create_dir_all(parent).with_context(|| { - format!( - "Failed to create pre-commit checkpoint directory '{}'.", - parent.display() - ) - })?; - - let mut files = Vec::new(); - for file in &checkpoint.files { - let mut ranges = Vec::new(); - for range in &file.ranges { - ranges.push(serde_json::json!({ - "start_line": range.start_line, - "end_line": range.end_line, - })); - } - files.push(serde_json::json!({ - "path": file.path, - "has_sce_attribution": file.has_sce_attribution, - "ranges": ranges, - })); - } - - let mut prompts = Vec::new(); - for prompt in &checkpoint.prompts { - prompts.push(serde_json::json!({ - "turn_number": prompt.turn_number, - "prompt_text": prompt.prompt_text, - "prompt_length": prompt.prompt_length, - "is_truncated": prompt.is_truncated, - "cwd": prompt.cwd, - "transcript_path": prompt.transcript_path, - "captured_at": prompt.captured_at, - })); - } + let runtime = resolve_runtime_state(&repository_root)?; - let payload = serde_json::json!({ - "version": 1, - "anchors": { - "index_tree": checkpoint.anchors.index_tree.clone(), - "head_tree": checkpoint.anchors.head_tree.clone(), - }, - "harness_type": checkpoint.harness_type, - "git_branch": checkpoint.git_branch, - "model_id": checkpoint.model_id, - "files": files, - "prompts": prompts, - }); - - let serialized = serde_json::to_vec_pretty(&payload) - .context("Failed to serialize pre-commit checkpoint artifact")?; - fs::write(&checkpoint_path, serialized).with_context(|| { - format!( - "Failed to persist pre-commit checkpoint artifact '{}'.", - checkpoint_path.display() - ) - }) + Ok(format!( + "pre-commit hook executed with no-op runtime state: {:?}", + pre_commit_no_op_reason(&runtime) + )) } fn run_commit_msg_subcommand(message_file: &Path) -> Result { @@ -574,7 +61,7 @@ fn run_commit_msg_subcommand_in_repo( ); } - let runtime = resolve_commit_msg_runtime_state(repository_root); + let runtime = resolve_runtime_state(repository_root)?; let original = fs::read_to_string(message_file).with_context(|| { format!( "Invalid commit message file '{}': failed to read UTF-8 content.", @@ -582,9 +69,8 @@ fn run_commit_msg_subcommand_in_repo( ) })?; + let gate_passed = commit_msg_policy_gate_passed(&runtime); let transformed = apply_commit_msg_coauthor_policy(&runtime, &original); - let gate_passed = - !runtime.sce_disabled && runtime.sce_coauthor_enabled && runtime.has_staged_sce_attribution; let trailer_applied = gate_passed && transformed != original; if trailer_applied { @@ -604,2293 +90,110 @@ fn run_commit_msg_subcommand_in_repo( )) } -fn resolve_commit_msg_runtime_state(repository_root: &Path) -> CommitMsgRuntimeState { - CommitMsgRuntimeState { - sce_disabled: env_flag_is_truthy("SCE_DISABLED"), - sce_coauthor_enabled: env_flag_is_enabled_by_default("SCE_COAUTHOR_ENABLED"), - has_staged_sce_attribution: staged_sce_attribution_present(repository_root), - } -} - -fn staged_sce_attribution_present(repository_root: &Path) -> bool { - let Ok(checkpoint_path) = resolve_pre_commit_checkpoint_path(repository_root) else { - return false; - }; - - let Ok(payload) = fs::read_to_string(&checkpoint_path) else { - return false; - }; - let Ok(json) = serde_json::from_str::(&payload) else { - return false; - }; - - checkpoint_has_explicit_sce_attribution(&json) -} - -fn checkpoint_has_explicit_sce_attribution(json: &serde_json::Value) -> bool { - json.get("files") - .and_then(serde_json::Value::as_array) - .is_some_and(|files| { - files.iter().any(|file| { - let has_sce_attribution = file - .get("has_sce_attribution") - .and_then(serde_json::Value::as_bool) - .unwrap_or(false); - - if !has_sce_attribution { - return false; - } - - file.get("ranges") - .and_then(serde_json::Value::as_array) - .is_some_and(|ranges| !ranges.is_empty()) - }) - }) -} - fn run_post_commit_subcommand() -> Result { let repository_root = std::env::current_dir() .context("Failed to determine current directory for post-commit runtime invocation.")?; - run_post_commit_subcommand_in_repo(&repository_root) -} - -#[allow(clippy::unnecessary_wraps)] -fn run_post_commit_subcommand_in_repo(repository_root: &Path) -> Result { - let runtime = resolve_post_commit_runtime_state(repository_root); - - if runtime.sce_disabled || !runtime.cli_available || runtime.is_bare_repo { - let reason = if runtime.sce_disabled { - PostCommitNoOpReason::Disabled - } else if !runtime.cli_available { - PostCommitNoOpReason::CliUnavailable - } else { - PostCommitNoOpReason::BareRepository - }; - - return Ok(format!( - "post-commit hook executed with no-op runtime state: {reason:?}" - )); - } - - let runtime_paths = match resolve_post_commit_runtime_paths(repository_root) { - Ok(paths) => paths, - Err(error) => { - return Ok(format!( - "post-commit hook skipped trace finalization: failed to resolve persistence targets ({error})" - )); - } - }; - - let input = match build_post_commit_input(repository_root) { - Ok(input) => input, - Err(error) => { - return Ok(format!( - "post-commit hook skipped trace finalization: failed to collect commit attribution input ({error})" - )); - } - }; - - let mut notes_writer = GitNotesTraceWriter { - repository_root: repository_root.to_path_buf(), - }; - let mut record_store = NoOpTraceRecordStore; - let mut retry_queue = JsonFileTraceRetryQueue { - path: runtime_paths.retry_queue_path, - }; - let mut emission_ledger = FileTraceEmissionLedger { - path: runtime_paths.emission_ledger_path, - }; - - let outcome = match finalize_post_commit_trace( - &runtime, - input, - &mut notes_writer, - &mut record_store, - &mut retry_queue, - &mut emission_ledger, - ) { - Ok(outcome) => outcome, - Err(error) => { - return Ok(format!( - "post-commit hook skipped trace finalization: finalizer execution failed ({error})" - )); - } - }; - - let retry_report = - match process_runtime_retry_queue(&mut retry_queue, &mut notes_writer, &mut record_store) { - Ok(report) => report, - Err(error) => { - return Ok(format!( - "post-commit hook completed trace finalization but retry replay failed ({error})" - )); - } - }; - - let message = match outcome { - PostCommitFinalization::NoOp(reason) => { - format!("post-commit hook executed with no-op runtime state: {reason:?}") - } - PostCommitFinalization::Persisted(persisted) => format!( - "post-commit hook finalized trace for commit '{}' (trace_id='{}', notes={:?}, database={:?}) {}.", - persisted.commit_sha, persisted.trace_id, persisted.notes, persisted.database - , retry_report.summary_text() - ), - PostCommitFinalization::QueuedFallback(queued) => format!( - "post-commit hook enqueued fallback for commit '{}' (trace_id='{}', failed_targets={:?}) {}.", - queued.commit_sha, - queued.trace_id, - queued.failed_targets, - retry_report.summary_text() - ), - }; + let runtime = resolve_runtime_state(&repository_root)?; - Ok(message) + Ok(format!( + "post-commit hook executed with no-op runtime state: {:?}", + post_commit_no_op_reason(&runtime) + )) } -fn resolve_post_commit_runtime_state(repository_root: &Path) -> PostCommitRuntimeState { - PostCommitRuntimeState { - sce_disabled: env_flag_is_truthy("SCE_DISABLED"), - cli_available: git_command_success(repository_root, &["--version"]), - is_bare_repo: git_command_output(repository_root, &["rev-parse", "--is-bare-repository"]) - .is_some_and(|output| output == "true"), - } -} +fn run_post_rewrite_subcommand(rewrite_method: &str) -> Result { + let repository_root = std::env::current_dir() + .context("Failed to determine current directory for post-rewrite runtime invocation.")?; + let runtime = resolve_runtime_state(&repository_root)?; -#[allow(clippy::struct_field_names)] -struct PostCommitRuntimePaths { - retry_queue_path: PathBuf, - emission_ledger_path: PathBuf, + Ok(format!( + "post-rewrite hook executed with no-op runtime state: {:?} (rewrite_method='{}')", + post_rewrite_no_op_reason(&runtime), + rewrite_method.trim() + )) } -fn resolve_post_commit_runtime_paths(repository_root: &Path) -> Result { - let _ = ensure_agent_trace_local_db_ready_blocking()?; - let retry_queue_path = resolve_git_path( - repository_root, - default_paths::git_relative_path::TRACE_RETRY_QUEUE, - )?; - let emission_ledger_path = resolve_git_path( - repository_root, - default_paths::git_relative_path::TRACE_EMISSION_LEDGER, - )?; - - Ok(PostCommitRuntimePaths { - retry_queue_path, - emission_ledger_path, +fn resolve_runtime_state(repository_root: &Path) -> Result { + Ok(HookRuntimeState { + sce_disabled: env_flag_is_truthy("SCE_DISABLED"), + attribution_hooks_enabled: config::resolve_hook_runtime_config(repository_root)? + .attribution_hooks_enabled, }) } -fn resolve_git_path(repository_root: &Path, git_path: &str) -> Result { - let resolved = run_git_command( - repository_root, - &["rev-parse", "--git-path", git_path], - "Failed to resolve git persistence path.", - )?; - let path = PathBuf::from(resolved); - if path.is_absolute() { - return Ok(path); - } - - Ok(repository_root.join(path)) +fn env_flag_is_truthy(name: &str) -> bool { + std::env::var(name) + .ok() + .is_some_and(|value| env_value_is_truthy(&value)) } -fn build_post_commit_input(repository_root: &Path) -> Result { - let commit_sha = run_git_command( - repository_root, - &["rev-parse", "--verify", "HEAD"], - "Failed to resolve post-commit HEAD SHA.", - )?; - let parent_sha = git_command_output(repository_root, &["rev-parse", "--verify", "HEAD^"]); - let timestamp_rfc3339 = run_git_command( - repository_root, - &["show", "-s", "--format=%cI", "HEAD"], - "Failed to resolve post-commit timestamp.", - )?; - let committed_at_unix_ms = run_git_command( - repository_root, - &["show", "-s", "--format=%ct", "HEAD"], - "Failed to resolve post-commit timestamp seconds.", - )? - .parse::() - .context("Failed to parse post-commit timestamp seconds as integer")? - * 1_000; - let files = collect_post_commit_file_attribution(repository_root)?; - let prompts = - load_post_commit_prompt_records(repository_root, committed_at_unix_ms, ×tamp_rfc3339)?; - let idempotency_key = format!("post-commit:{commit_sha}"); - let record_id = deterministic_uuid_v4_from_seed(&format!("{commit_sha}:{timestamp_rfc3339}")); +fn env_value_is_truthy(value: &str) -> bool { + matches!( + value.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) +} - Ok(PostCommitInput { - record_id, - timestamp_rfc3339, - committed_at_unix_ms, - commit_sha, - parent_sha, - idempotency_key, - files, - prompts, - }) +fn commit_msg_policy_gate_passed(runtime: &HookRuntimeState) -> bool { + !runtime.sce_disabled && runtime.attribution_hooks_enabled } -fn collect_post_commit_file_attribution( - repository_root: &Path, -) -> Result> { - let checkpoint_files = load_post_commit_checkpoint_files(repository_root)?; - if !checkpoint_files.is_empty() { - return Ok(checkpoint_files); +fn pre_commit_no_op_reason(runtime: &HookRuntimeState) -> HookNoOpReason { + if runtime.sce_disabled { + HookNoOpReason::Disabled + } else { + HookNoOpReason::AttributionOnlyCommitMsgMode } - - collect_commit_file_attribution( - repository_root, - "HEAD", - "https://crocoder.dev/sce/local-hooks/post-commit", - ) } -fn collect_commit_file_attribution( - repository_root: &Path, - revision: &str, - conversation_url: &str, -) -> Result> { - let changed_paths = run_git_command_allow_empty( - repository_root, - &["show", "--pretty=format:", "--name-only", revision], - "Failed to resolve changed files for commit attribution.", - )?; - - let mut files = Vec::new(); - for line in changed_paths.lines() { - let path = line.trim(); - if path.is_empty() { - continue; - } - - files.push(FileAttributionInput { - path: path.to_string(), - conversations: vec![ConversationInput { - url: conversation_url.to_string(), - related: Vec::new(), - ranges: vec![RangeInput { - start_line: 1, - end_line: 1, - contributor: ContributorInput { - kind: ContributorType::Unknown, - model_id: None, - }, - }], - }], - }); +fn post_commit_no_op_reason(runtime: &HookRuntimeState) -> HookNoOpReason { + if runtime.sce_disabled { + HookNoOpReason::Disabled + } else { + HookNoOpReason::AttributionOnlyCommitMsgMode } - - Ok(files) } -fn load_post_commit_checkpoint_files(repository_root: &Path) -> Result> { - let checkpoint_path = resolve_pre_commit_checkpoint_path(repository_root)?; - let payload = match fs::read_to_string(&checkpoint_path) { - Ok(payload) => payload, - Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()), - Err(error) => { - bail!( - "Failed to read pre-commit checkpoint '{}' for post-commit finalization: {}", - checkpoint_path.display(), - error - ) - } - }; - - let checkpoint = serde_json::from_str::(&payload).with_context(|| { - format!( - "Failed to parse pre-commit checkpoint '{}' as JSON.", - checkpoint_path.display() - ) - })?; - - let Some(files_json) = checkpoint - .get("files") - .and_then(serde_json::Value::as_array) - else { - return Ok(Vec::new()); - }; - - let mut files = Vec::new(); - for file_json in files_json { - let Some(path) = file_json.get("path").and_then(serde_json::Value::as_str) else { - continue; - }; - - let ranges = file_json - .get("ranges") - .and_then(serde_json::Value::as_array) - .map(|ranges| { - ranges - .iter() - .filter_map(|range_json| { - let start_line = range_json - .get("start_line") - .and_then(serde_json::Value::as_u64) - .and_then(|value| u32::try_from(value).ok())?; - let end_line = range_json - .get("end_line") - .and_then(serde_json::Value::as_u64) - .and_then(|value| u32::try_from(value).ok())?; - - Some(RangeInput { - start_line, - end_line, - contributor: ContributorInput { - kind: ContributorType::Unknown, - model_id: None, - }, - }) - }) - .collect::>() - }) - .unwrap_or_default(); - - if ranges.is_empty() { - continue; - } - - files.push(FileAttributionInput { - path: path.to_string(), - conversations: vec![ConversationInput { - url: String::from("https://crocoder.dev/sce/local-hooks/pre-commit-checkpoint"), - related: Vec::new(), - ranges, - }], - }); +fn post_rewrite_no_op_reason(runtime: &HookRuntimeState) -> HookNoOpReason { + if runtime.sce_disabled { + HookNoOpReason::Disabled + } else { + HookNoOpReason::AttributionOnlyCommitMsgMode } - - Ok(files) } -fn load_post_commit_prompt_records( - repository_root: &Path, - committed_at_unix_ms: i64, - committed_at_rfc3339: &str, -) -> Result> { - let checkpoint_path = resolve_pre_commit_checkpoint_path(repository_root)?; - let payload = match fs::read_to_string(&checkpoint_path) { - Ok(payload) => payload, - Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()), - Err(error) => { - bail!( - "Failed to read pre-commit checkpoint '{}' for post-commit prompts: {}", - checkpoint_path.display(), - error - ) - } - }; - - let checkpoint = serde_json::from_str::(&payload).with_context(|| { - format!( - "Failed to parse pre-commit checkpoint '{}' as JSON for prompt persistence.", - checkpoint_path.display() - ) - })?; +pub fn apply_commit_msg_coauthor_policy( + runtime: &HookRuntimeState, + commit_message: &str, +) -> String { + if !commit_msg_policy_gate_passed(runtime) { + return commit_message.to_string(); + } - let harness_type = checkpoint - .get("harness_type") - .and_then(serde_json::Value::as_str) - .unwrap_or(CLAUDE_CODE_HARNESS_TYPE) - .to_string(); - let git_branch = checkpoint - .get("git_branch") - .and_then(serde_json::Value::as_str) - .map(ToOwned::to_owned); - let model_id = checkpoint - .get("model_id") - .and_then(serde_json::Value::as_str) - .map(ToOwned::to_owned); - let Some(prompts_json) = checkpoint - .get("prompts") - .and_then(serde_json::Value::as_array) - else { - return Ok(Vec::new()); - }; + let mut lines: Vec<&str> = commit_message.lines().collect(); + lines.retain(|line| *line != CANONICAL_SCE_COAUTHOR_TRAILER); - let mut prompt_windows = Vec::new(); - for (index, prompt_json) in prompts_json.iter().enumerate() { - let turn_number = prompt_json - .get("turn_number") - .and_then(serde_json::Value::as_u64) - .and_then(|value| u32::try_from(value).ok()) - .unwrap_or_else(|| u32::try_from(index + 1).unwrap_or(u32::MAX)); - let Some(prompt_text) = prompt_json - .get("prompt_text") - .and_then(serde_json::Value::as_str) - .map(ToOwned::to_owned) - else { - continue; - }; - let captured_at = prompt_json - .get("captured_at") - .and_then(serde_json::Value::as_str) - .map_or_else(|| committed_at_rfc3339.to_string(), ToOwned::to_owned); - let Some(captured_at_unix_ms) = parse_utc_rfc3339_to_unix_ms(&captured_at) else { - continue; - }; - let next_captured_at_unix_ms = prompts_json - .get(index + 1) - .and_then(|next| next.get("captured_at")) - .and_then(serde_json::Value::as_str) - .and_then(parse_utc_rfc3339_to_unix_ms) - .unwrap_or(committed_at_unix_ms); - let duration_ms = (next_captured_at_unix_ms - captured_at_unix_ms).max(0); - let transcript_path = prompt_json - .get("transcript_path") - .and_then(serde_json::Value::as_str) - .filter(|value| !value.trim().is_empty()); - let tool_call_count = transcript_path - .map(|path| { - count_tool_uses_in_transcript(path, captured_at_unix_ms, next_captured_at_unix_ms) - }) - .transpose()? - .unwrap_or(0); - let cwd = prompt_json - .get("cwd") - .and_then(serde_json::Value::as_str) - .map(ToOwned::to_owned); - let (prompt_text, is_truncated, prompt_length) = truncate_prompt(&prompt_text); + if !lines.is_empty() && !lines.last().is_some_and(|line| line.is_empty()) { + lines.push(""); + } + lines.push(CANONICAL_SCE_COAUTHOR_TRAILER); - prompt_windows.push(PersistedPromptRecord { - turn_number, - prompt_text, - prompt_length, - is_truncated, - harness_type: harness_type.clone(), - model_id: model_id.clone(), - cwd, - git_branch: git_branch.clone(), - tool_call_count, - duration_ms, - captured_at, - }); + let mut normalized = lines.join("\n"); + if commit_message.ends_with('\n') { + normalized.push('\n'); } - Ok(prompt_windows) + normalized } -fn count_tool_uses_in_transcript( - transcript_path: &str, - captured_at_unix_ms: i64, - next_captured_at_unix_ms: i64, -) -> Result { - let payload = fs::read_to_string(transcript_path).with_context(|| { - format!("Failed to read Claude transcript '{transcript_path}' for prompt metrics.") - })?; - let mut count = 0_u32; - - for line in payload.lines() { - let trimmed = line.trim(); - if trimmed.is_empty() { - continue; - } - - let Ok(json) = serde_json::from_str::(trimmed) else { - continue; - }; - - if json.get("type").and_then(serde_json::Value::as_str) != Some("assistant") { - continue; - } - - let Some(timestamp) = json.get("timestamp").and_then(serde_json::Value::as_str) else { - continue; - }; - let Some(event_unix_ms) = parse_utc_rfc3339_to_unix_ms(timestamp) else { - continue; - }; - if event_unix_ms < captured_at_unix_ms || event_unix_ms >= next_captured_at_unix_ms { - continue; - } - - let Some(contents) = json - .get("message") - .and_then(|message| message.get("content")) - .and_then(serde_json::Value::as_array) - else { - continue; - }; - - for content in contents { - if content.get("type").and_then(serde_json::Value::as_str) == Some("tool_use") { - count = count.saturating_add(1); - } - } - } - - Ok(count) -} - -fn truncate_prompt(text: &str) -> (String, bool, usize) { - let original_length = text.len(); - if original_length <= MAX_PROMPT_BYTES { - return (text.to_string(), false, original_length); - } - - let mut end = 0; - for (index, _) in text.char_indices() { - if index > MAX_PROMPT_BYTES { - break; - } - end = index; - } - if end == 0 { - end = text - .char_indices() - .find(|(index, _)| *index >= MAX_PROMPT_BYTES) - .map_or(text.len(), |(index, _)| index); - } - - (text[..end].to_string(), true, original_length) -} - -fn parse_utc_rfc3339_to_unix_ms(value: &str) -> Option { - let value = value.trim(); - let core = value.strip_suffix('Z')?; - let (date_part, time_part) = core.split_once('T')?; - let mut date_fields = date_part.split('-'); - let year = date_fields.next()?.parse::().ok()?; - let month = date_fields.next()?.parse::().ok()?; - let day = date_fields.next()?.parse::().ok()?; - if date_fields.next().is_some() { - return None; - } - - let (time_main, fractional) = match time_part.split_once('.') { - Some((time_main, fractional)) => (time_main, fractional), - None => (time_part, ""), - }; - let mut time_fields = time_main.split(':'); - let hour = time_fields.next()?.parse::().ok()?; - let minute = time_fields.next()?.parse::().ok()?; - let second = time_fields.next()?.parse::().ok()?; - if time_fields.next().is_some() { - return None; - } - - let millis = parse_fractional_millis(fractional)?; - let days = days_from_civil(year, month, day)?; - let seconds = days - .checked_mul(86_400)? - .checked_add(i64::from(hour) * 3_600)? - .checked_add(i64::from(minute) * 60)? - .checked_add(i64::from(second))?; - seconds.checked_mul(1_000)?.checked_add(i64::from(millis)) -} - -fn parse_fractional_millis(value: &str) -> Option { - if value.is_empty() { - return Some(0); - } - - let digits = value - .chars() - .take_while(char::is_ascii_digit) - .collect::(); - if digits.is_empty() { - return Some(0); - } - - let mut normalized = digits; - while normalized.len() < 3 { - normalized.push('0'); - } - normalized.truncate(3); - normalized.parse::().ok() -} - -fn days_from_civil(year: i32, month: u32, day: u32) -> Option { - if !(1..=12).contains(&month) || day == 0 || day > 31 { - return None; - } - - let year = i64::from(year) - i64::from(month <= 2); - let era = if year >= 0 { year } else { year - 399 } / 400; - let year_of_era = year - era * 400; - let month = i64::from(month); - let day = i64::from(day); - let day_of_year = (153 * (month + if month > 2 { -3 } else { 9 }) + 2) / 5 + day - 1; - let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year; - Some(era * 146_097 + day_of_era - 719_468) -} - -fn prompt_to_json(prompt: &PersistedPromptRecord) -> serde_json::Value { - serde_json::json!({ - "turn_number": prompt.turn_number, - "prompt_text": prompt.prompt_text, - "prompt_length": prompt.prompt_length, - "is_truncated": prompt.is_truncated, - "harness_type": prompt.harness_type, - "model_id": prompt.model_id, - "cwd": prompt.cwd, - "git_branch": prompt.git_branch, - "tool_call_count": prompt.tool_call_count, - "duration_ms": prompt.duration_ms, - "captured_at": prompt.captured_at, - }) -} - -fn prompt_from_json(value: &serde_json::Value) -> Result { - Ok(PersistedPromptRecord { - turn_number: value - .get("turn_number") - .and_then(serde_json::Value::as_u64) - .and_then(|raw| u32::try_from(raw).ok()) - .context("Prompt payload missing 'turn_number' integer")?, - prompt_text: value - .get("prompt_text") - .and_then(serde_json::Value::as_str) - .context("Prompt payload missing 'prompt_text' string")? - .to_string(), - prompt_length: value - .get("prompt_length") - .and_then(serde_json::Value::as_u64) - .and_then(|raw| usize::try_from(raw).ok()) - .context("Prompt payload missing 'prompt_length' integer")?, - is_truncated: value - .get("is_truncated") - .and_then(serde_json::Value::as_bool) - .unwrap_or(false), - harness_type: value - .get("harness_type") - .and_then(serde_json::Value::as_str) - .context("Prompt payload missing 'harness_type' string")? - .to_string(), - model_id: value - .get("model_id") - .and_then(serde_json::Value::as_str) - .map(ToOwned::to_owned), - cwd: value - .get("cwd") - .and_then(serde_json::Value::as_str) - .map(ToOwned::to_owned), - git_branch: value - .get("git_branch") - .and_then(serde_json::Value::as_str) - .map(ToOwned::to_owned), - tool_call_count: value - .get("tool_call_count") - .and_then(serde_json::Value::as_u64) - .and_then(|raw| u32::try_from(raw).ok()) - .unwrap_or(0), - duration_ms: value - .get("duration_ms") - .and_then(serde_json::Value::as_i64) - .unwrap_or(0), - captured_at: value - .get("captured_at") - .and_then(serde_json::Value::as_str) - .context("Prompt payload missing 'captured_at' string")? - .to_string(), - }) -} - -fn deterministic_uuid_v4_from_seed(seed: &str) -> String { - use sha2::{Digest, Sha256}; - - let digest = Sha256::digest(seed.as_bytes()); - let mut bytes = [0_u8; 16]; - bytes.copy_from_slice(&digest[..16]); - - bytes[6] = (bytes[6] & 0x0f) | 0x40; - bytes[8] = (bytes[8] & 0x3f) | 0x80; - - format!( - "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}", - u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), - u16::from_be_bytes([bytes[4], bytes[5]]), - u16::from_be_bytes([bytes[6], bytes[7]]), - u16::from_be_bytes([bytes[8], bytes[9]]), - u64::from_be_bytes([ - 0, 0, bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15] - ]) - ) -} - -fn run_post_rewrite_subcommand(rewrite_method: &str) -> Result { - let repository_root = std::env::current_dir() - .context("Failed to determine current directory for post-rewrite runtime invocation.")?; - let stdin = std::io::read_to_string(std::io::stdin()) - .context("Failed to read post-rewrite pair input from STDIN")?; - - run_post_rewrite_subcommand_in_repo(&repository_root, rewrite_method, &stdin) -} - -#[allow(clippy::unnecessary_wraps)] -fn run_post_rewrite_subcommand_in_repo( - repository_root: &Path, - rewrite_method: &str, - pairs_file_contents: &str, -) -> Result { - let runtime = resolve_post_rewrite_runtime_state(repository_root); - - if runtime.sce_disabled || !runtime.cli_available || runtime.is_bare_repo { - let reason = if runtime.sce_disabled { - PostRewriteNoOpReason::Disabled - } else if !runtime.cli_available { - PostRewriteNoOpReason::CliUnavailable - } else { - PostRewriteNoOpReason::BareRepository - }; - - return Ok(format!( - "post-rewrite hook executed with no-op runtime state: {reason:?}" - )); - } - - let runtime_paths = match resolve_post_commit_runtime_paths(repository_root) { - Ok(paths) => paths, - Err(error) => { - return Ok(format!( - "post-rewrite hook skipped rewrite finalization: failed to resolve persistence targets ({error})" - )); - } - }; - - let mut ingestion = NoOpRewriteRemapIngestion { - accepted_requests: Vec::new(), - }; - - let outcome = match finalize_post_rewrite_remap( - &runtime, - rewrite_method, - pairs_file_contents, - &mut ingestion, - ) { - Ok(outcome) => outcome, - Err(error) => { - return Ok(format!( - "post-rewrite hook skipped rewrite finalization: remap ingestion failed ({error})" - )); - } - }; - - let mut notes_writer = GitNotesTraceWriter { - repository_root: repository_root.to_path_buf(), - }; - let mut record_store = NoOpTraceRecordStore; - let mut retry_queue = JsonFileTraceRetryQueue { - path: runtime_paths.retry_queue_path, - }; - let mut emission_ledger = FileTraceEmissionLedger { - path: runtime_paths.emission_ledger_path, - }; - - let mut rewrite_persisted = 0_usize; - let mut rewrite_queued = 0_usize; - let mut rewrite_noops = 0_usize; - let mut rewrite_failures = 0_usize; - - for request in &ingestion.accepted_requests { - let Ok(input) = build_rewrite_trace_input(repository_root, request) else { - rewrite_failures += 1; - continue; - }; - - match finalize_rewrite_trace( - &runtime, - input, - &mut notes_writer, - &mut record_store, - &mut retry_queue, - &mut emission_ledger, - ) { - Ok(RewriteTraceFinalization::Persisted(_)) => rewrite_persisted += 1, - Ok(RewriteTraceFinalization::QueuedFallback(_)) => rewrite_queued += 1, - Ok(RewriteTraceFinalization::NoOp(_)) => rewrite_noops += 1, - Err(_) => rewrite_failures += 1, - } - } - - let retry_report = - match process_runtime_retry_queue(&mut retry_queue, &mut notes_writer, &mut record_store) { - Ok(report) => report, - Err(error) => { - return Ok(format!( - "post-rewrite hook completed rewrite finalization but retry replay failed ({error})" - )); - } - }; - - match outcome { - PostRewriteFinalization::NoOp(reason) => Ok(format!( - "post-rewrite hook executed with no-op runtime state: {reason:?}" - )), - PostRewriteFinalization::Ingested(ingested) => Ok(format!( - "post-rewrite hook ingested {} pair(s), skipped {} duplicate pair(s), method='{}', rewrite_traces=(persisted={}, queued={}, no_op={}, failed={}) {}.", - ingested.ingested_pairs, - ingested.skipped_pairs, - ingested.rewrite_method.canonical_label(), - rewrite_persisted, - rewrite_queued, - rewrite_noops, - rewrite_failures, - retry_report.summary_text() - )), - } -} - -#[derive(Default)] -struct InMemoryRetryMetricsSink { - events: Vec, -} - -impl RetryMetricsSink for InMemoryRetryMetricsSink { - fn record_retry_metric(&mut self, metric: RetryProcessingMetric) { - self.events.push(metric); - } +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct HookRuntimeState { + pub sce_disabled: bool, + pub attribution_hooks_enabled: bool, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -struct RuntimeRetryReport { - summary: RetryQueueProcessSummary, - transient_failures: usize, - permanent_failures: usize, -} - -impl RuntimeRetryReport { - fn summary_text(&self) -> String { - format!( - "retry_queue=(attempted={}, recovered={}, requeued={}, transient_failures={}, permanent_failures={})", - self.summary.attempted, - self.summary.recovered, - self.summary.requeued, - self.transient_failures, - self.permanent_failures - ) - } -} - -fn process_runtime_retry_queue( - retry_queue: &mut impl TraceRetryQueue, - notes_writer: &mut impl TraceNotesWriter, - record_store: &mut impl TraceRecordStore, -) -> Result { - let mut metrics_sink = InMemoryRetryMetricsSink::default(); - let summary = process_trace_retry_queue( - retry_queue, - notes_writer, - record_store, - &mut metrics_sink, - RETRY_QUEUE_MAX_ITEMS_PER_RUN, - )?; - - let mut transient_failures = 0_usize; - let mut permanent_failures = 0_usize; - - for metric in metrics_sink.events { - match metric.error_class { - Some(PersistenceErrorClass::Transient) => transient_failures += 1, - Some(PersistenceErrorClass::Permanent) => permanent_failures += 1, - None => {} - } - } - - Ok(RuntimeRetryReport { - summary, - transient_failures, - permanent_failures, - }) -} - -fn resolve_post_rewrite_runtime_state(repository_root: &Path) -> PostRewriteRuntimeState { - PostRewriteRuntimeState { - sce_disabled: env_flag_is_truthy("SCE_DISABLED"), - cli_available: git_command_success(repository_root, &["--version"]), - is_bare_repo: git_command_output(repository_root, &["rev-parse", "--is-bare-repository"]) - .is_some_and(|output| output == "true"), - } -} - -fn build_rewrite_trace_input( - repository_root: &Path, - request: &RewriteRemapRequest, -) -> Result { - let timestamp_rfc3339 = run_git_command( - repository_root, - &["show", "-s", "--format=%cI", request.new_sha.as_str()], - "Failed to resolve rewritten commit timestamp.", - )?; - let files = collect_commit_file_attribution( - repository_root, - request.new_sha.as_str(), - "https://crocoder.dev/sce/local-hooks/post-rewrite", - )?; - - Ok(RewriteTraceInput { - record_id: deterministic_uuid_v4_from_seed(&format!( - "{}:{}", - request.idempotency_key, timestamp_rfc3339 - )), - timestamp_rfc3339, - rewritten_commit_sha: request.new_sha.clone(), - rewrite_from_sha: request.old_sha.clone(), - rewrite_method: request.rewrite_method.clone(), - rewrite_confidence: 1.0, - idempotency_key: request.idempotency_key.clone(), - files, - }) -} - -struct NoOpRewriteRemapIngestion { - accepted_requests: Vec, -} - -impl RewriteRemapIngestion for NoOpRewriteRemapIngestion { - fn ingest(&mut self, request: RewriteRemapRequest) -> Result { - self.accepted_requests.push(request); - Ok(true) - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PreCommitRuntimeState { - pub sce_disabled: bool, - pub cli_available: bool, - pub is_bare_repo: bool, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PreCommitTreeAnchors { - pub index_tree: String, - pub head_tree: Option, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PendingLineRange { - pub start_line: u32, - pub end_line: u32, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PendingFileCheckpoint { - pub path: String, - pub has_sce_attribution: bool, - pub staged_ranges: Vec, - pub unstaged_ranges: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PendingCheckpoint { - pub files: Vec, - pub harness_type: String, - pub git_branch: Option, - pub model_id: Option, - pub prompts: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct FinalizedFileCheckpoint { - pub path: String, - pub has_sce_attribution: bool, - pub ranges: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PendingPromptCheckpoint { - pub turn_number: u32, - pub prompt_text: String, - pub prompt_length: usize, - pub is_truncated: bool, - pub cwd: Option, - pub transcript_path: Option, - pub captured_at: String, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct FinalizedPromptCheckpoint { - pub turn_number: u32, - pub prompt_text: String, - pub prompt_length: usize, - pub is_truncated: bool, - pub cwd: Option, - pub transcript_path: Option, - pub captured_at: String, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct FinalizedCheckpoint { - pub anchors: PreCommitTreeAnchors, - pub harness_type: String, - pub git_branch: Option, - pub model_id: Option, - pub files: Vec, - pub prompts: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum PreCommitNoOpReason { - Disabled, - CliUnavailable, - BareRepository, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum PreCommitFinalization { - NoOp(PreCommitNoOpReason), - Finalized(FinalizedCheckpoint), -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct CommitMsgRuntimeState { - pub sce_disabled: bool, - pub sce_coauthor_enabled: bool, - pub has_staged_sce_attribution: bool, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PostCommitRuntimeState { - pub sce_disabled: bool, - pub cli_available: bool, - pub is_bare_repo: bool, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PostCommitInput { - pub record_id: String, - pub timestamp_rfc3339: String, - pub committed_at_unix_ms: i64, - pub commit_sha: String, - pub parent_sha: Option, - pub idempotency_key: String, - pub files: Vec, - pub prompts: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PersistedPromptRecord { - pub turn_number: u32, - pub prompt_text: String, - pub prompt_length: usize, - pub is_truncated: bool, - pub harness_type: String, - pub model_id: Option, - pub cwd: Option, - pub git_branch: Option, - pub tool_call_count: u32, - pub duration_ms: i64, - pub captured_at: String, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct TraceNote { - pub notes_ref: String, - pub commit_sha: String, - pub content_type: String, - pub record: AgentTraceRecord, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PersistedTraceRecord { - pub commit_sha: String, - pub idempotency_key: String, - pub content_type: String, - pub notes_ref: String, - pub record: AgentTraceRecord, - pub prompts: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum PersistenceErrorClass { - Transient, - Permanent, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PersistenceFailure { - pub class: PersistenceErrorClass, - pub message: String, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum PersistenceWriteResult { - Written, - AlreadyExists, - Failed(PersistenceFailure), -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum PersistenceTarget { - Notes, - Database, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct TraceRetryQueueEntry { - pub commit_sha: String, - pub failed_targets: Vec, - pub content_type: String, - pub notes_ref: String, - pub record: AgentTraceRecord, - pub prompts: Vec, -} - -pub trait TraceNotesWriter { - fn write_note(&mut self, note: TraceNote) -> PersistenceWriteResult; -} - -pub trait TraceRecordStore { - fn write_trace_record(&mut self, record: PersistedTraceRecord) -> PersistenceWriteResult; -} - -pub trait TraceRetryQueue { - fn enqueue(&mut self, entry: TraceRetryQueueEntry) -> Result<()>; - fn dequeue_next(&mut self) -> Result>; -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct RetryProcessingMetric { - pub commit_sha: String, - pub trace_id: String, - pub runtime_ms: u128, - pub error_class: Option, - pub failed_targets: Vec, -} - -pub trait RetryMetricsSink { - fn record_retry_metric(&mut self, metric: RetryProcessingMetric); -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct RetryQueueProcessSummary { - pub attempted: usize, - pub recovered: usize, - pub requeued: usize, -} - -pub trait TraceEmissionLedger { - fn has_emitted(&self, commit_sha: &str) -> bool; - fn mark_emitted(&mut self, commit_sha: &str); -} - -struct GitNotesTraceWriter { - repository_root: PathBuf, -} - -impl TraceNotesWriter for GitNotesTraceWriter { - fn write_note(&mut self, note: TraceNote) -> PersistenceWriteResult { - let payload = match serialize_note_payload(¬e) { - Ok(payload) => payload, - Err(error) => { - return PersistenceWriteResult::Failed(PersistenceFailure { - class: PersistenceErrorClass::Permanent, - message: format!("failed to serialize trace note payload: {error}"), - }); - } - }; - - let existing = Command::new("git") - .args([ - "notes", - "--ref", - note.notes_ref.as_str(), - "show", - note.commit_sha.as_str(), - ]) - .current_dir(&self.repository_root) - .output(); - if let Ok(output) = &existing { - if output.status.success() { - let existing_payload = String::from_utf8_lossy(&output.stdout).trim().to_string(); - if existing_payload == payload { - return PersistenceWriteResult::AlreadyExists; - } - } - } - - match Command::new("git") - .args([ - "notes", - "--ref", - note.notes_ref.as_str(), - "add", - "-f", - "-m", - payload.as_str(), - note.commit_sha.as_str(), - ]) - .current_dir(&self.repository_root) - .output() - { - Ok(output) if output.status.success() => PersistenceWriteResult::Written, - Ok(output) => PersistenceWriteResult::Failed(PersistenceFailure { - class: classify_persistence_error_class_from_stderr(&String::from_utf8_lossy( - &output.stderr, - )), - message: format!( - "failed to write git note for commit '{}': {}", - note.commit_sha, - String::from_utf8_lossy(&output.stderr).trim() - ), - }), - Err(error) => PersistenceWriteResult::Failed(PersistenceFailure { - class: classify_persistence_error_class_from_io(&error), - message: format!( - "failed to execute git notes command for commit '{}': {}", - note.commit_sha, error - ), - }), - } - } -} - -struct NoOpTraceRecordStore; - -impl TraceRecordStore for NoOpTraceRecordStore { - fn write_trace_record(&mut self, _record: PersistedTraceRecord) -> PersistenceWriteResult { - PersistenceWriteResult::AlreadyExists - } -} - -struct JsonFileTraceRetryQueue { - path: PathBuf, -} - -impl TraceRetryQueue for JsonFileTraceRetryQueue { - fn enqueue(&mut self, entry: TraceRetryQueueEntry) -> Result<()> { - if let Some(parent) = self.path.parent() { - fs::create_dir_all(parent).with_context(|| { - format!( - "Failed to create retry queue directory '{}'", - parent.display() - ) - })?; - } - - let line = serde_json::json!({ - "commit_sha": entry.commit_sha, - "failed_targets": entry - .failed_targets - .iter() - .copied() - .map(persistence_target_label) - .collect::>(), - "content_type": entry.content_type, - "notes_ref": entry.notes_ref, - "record": trace_record_to_json(&entry.record), - "prompts": entry.prompts.iter().map(prompt_to_json).collect::>(), - }) - .to_string(); - append_jsonl_line(&self.path, &line)?; - - Ok(()) - } - - fn dequeue_next(&mut self) -> Result> { - if !self.path.exists() { - return Ok(None); - } - - let payload = fs::read_to_string(&self.path).with_context(|| { - format!( - "Failed to read retry queue file '{}' for dequeue.", - self.path.display() - ) - })?; - - let mut lines = payload.lines(); - let Some(first_line) = lines.next() else { - return Ok(None); - }; - - let mut remaining = String::new(); - for line in lines { - remaining.push_str(line); - remaining.push('\n'); - } - fs::write(&self.path, remaining).with_context(|| { - format!( - "Failed to rewrite retry queue file '{}' after dequeue.", - self.path.display() - ) - })?; - - let parsed = serde_json::from_str::(first_line) - .context("Failed to parse retry queue entry JSON during dequeue")?; - let commit_sha = parsed - .get("commit_sha") - .and_then(serde_json::Value::as_str) - .context("Retry queue entry missing 'commit_sha' string")? - .to_string(); - let content_type = parsed - .get("content_type") - .and_then(serde_json::Value::as_str) - .context("Retry queue entry missing 'content_type' string")? - .to_string(); - let notes_ref = parsed - .get("notes_ref") - .and_then(serde_json::Value::as_str) - .context("Retry queue entry missing 'notes_ref' string")? - .to_string(); - let record = trace_record_from_json( - parsed - .get("record") - .context("Retry queue entry missing 'record' object")?, - )?; - - let failed_targets = parsed - .get("failed_targets") - .and_then(serde_json::Value::as_array) - .into_iter() - .flatten() - .filter_map(|value| value.as_str()) - .filter_map(persistence_target_from_label) - .collect::>(); - let prompts = parsed - .get("prompts") - .and_then(serde_json::Value::as_array) - .into_iter() - .flatten() - .map(prompt_from_json) - .collect::>>()?; - - Ok(Some(TraceRetryQueueEntry { - commit_sha, - failed_targets, - content_type, - notes_ref, - record, - prompts, - })) - } -} - -struct FileTraceEmissionLedger { - path: PathBuf, -} - -impl TraceEmissionLedger for FileTraceEmissionLedger { - fn has_emitted(&self, commit_sha: &str) -> bool { - fs::read_to_string(&self.path) - .ok() - .is_some_and(|contents| contents.lines().any(|line| line.trim() == commit_sha)) - } - - fn mark_emitted(&mut self, commit_sha: &str) { - if self.has_emitted(commit_sha) { - return; - } - - if let Some(parent) = self.path.parent() { - if fs::create_dir_all(parent).is_err() { - return; - } - } - - if let Ok(mut file) = fs::OpenOptions::new() - .create(true) - .append(true) - .open(&self.path) - { - // Best-effort append to notes; errors are logged but don't fail the commit - if let Err(e) = writeln!(file, "{commit_sha}") { - eprintln!("Warning: Failed to append commit SHA to notes: {e}"); - } - } - } -} - -fn append_jsonl_line(path: &Path, line: &str) -> std::io::Result<()> { - let mut file = fs::OpenOptions::new() - .create(true) - .append(true) - .open(path)?; - writeln!(file, "{line}")?; - Ok(()) -} - -fn classify_persistence_error_class_from_io(error: &std::io::Error) -> PersistenceErrorClass { - match error.kind() { - std::io::ErrorKind::Interrupted - | std::io::ErrorKind::WouldBlock - | std::io::ErrorKind::TimedOut - | std::io::ErrorKind::ConnectionRefused - | std::io::ErrorKind::ConnectionReset - | std::io::ErrorKind::ConnectionAborted - | std::io::ErrorKind::NotConnected => PersistenceErrorClass::Transient, - _ => PersistenceErrorClass::Permanent, - } -} - -fn classify_persistence_error_class_from_stderr(stderr: &str) -> PersistenceErrorClass { - let lowered = stderr.to_ascii_lowercase(); - if lowered.contains("timed out") - || lowered.contains("temporar") - || lowered.contains("try again") - || lowered.contains("index.lock") - { - return PersistenceErrorClass::Transient; - } - - PersistenceErrorClass::Permanent -} - -fn persistence_target_label(target: PersistenceTarget) -> &'static str { - match target { - PersistenceTarget::Notes => "notes", - PersistenceTarget::Database => "database", - } -} - -fn persistence_target_from_label(label: &str) -> Option { - match label { - "notes" => Some(PersistenceTarget::Notes), - "database" => Some(PersistenceTarget::Database), - _ => None, - } -} - -fn serialize_note_payload(note: &TraceNote) -> Result { - serde_json::to_string_pretty(&serde_json::json!({ - "content_type": note.content_type, - "record": trace_record_to_json(¬e.record), - })) - .context("Failed to serialize trace note payload") -} - -fn trace_record_to_json(record: &AgentTraceRecord) -> serde_json::Value { - serde_json::json!({ - "version": record.version, - "id": record.id, - "timestamp": record.timestamp, - "vcs": { - "type": record.vcs.r#type, - "revision": record.vcs.revision, - }, - "files": record.files.iter().map(|file| { - serde_json::json!({ - "path": file.path, - "conversations": file.conversations.iter().map(|conversation| { - serde_json::json!({ - "url": conversation.url, - "related": conversation.related, - "ranges": conversation.ranges.iter().map(|range| { - serde_json::json!({ - "start_line": range.start_line, - "end_line": range.end_line, - "contributor": { - "type": range.contributor.r#type, - "model_id": range.contributor.model_id, - }, - }) - }).collect::>(), - }) - }).collect::>(), - }) - }).collect::>(), - "metadata": record.metadata, - }) -} - -#[allow(clippy::too_many_lines)] -fn trace_record_from_json(value: &serde_json::Value) -> Result { - let version = value - .get("version") - .and_then(serde_json::Value::as_str) - .unwrap_or(TRACE_VERSION) - .to_string(); - let id = value - .get("id") - .and_then(serde_json::Value::as_str) - .context("trace record JSON missing id")? - .to_string(); - let timestamp = value - .get("timestamp") - .and_then(serde_json::Value::as_str) - .context("trace record JSON missing timestamp")? - .to_string(); - - let vcs = value - .get("vcs") - .and_then(serde_json::Value::as_object) - .context("trace record JSON missing vcs object")?; - let vcs_type = vcs - .get("type") - .and_then(serde_json::Value::as_str) - .unwrap_or(VCS_TYPE_GIT) - .to_string(); - let vcs_revision = vcs - .get("revision") - .and_then(serde_json::Value::as_str) - .context("trace record JSON missing vcs.revision")? - .to_string(); - - let files_json = value - .get("files") - .and_then(serde_json::Value::as_array) - .cloned() - .unwrap_or_default(); - let mut files = Vec::new(); - for file in files_json { - let Some(path) = file.get("path").and_then(serde_json::Value::as_str) else { - continue; - }; - let mut conversations = Vec::new(); - for conversation in file - .get("conversations") - .and_then(serde_json::Value::as_array) - .into_iter() - .flatten() - { - let Some(url) = conversation.get("url").and_then(serde_json::Value::as_str) else { - continue; - }; - let related = conversation - .get("related") - .and_then(serde_json::Value::as_array) - .into_iter() - .flatten() - .filter_map(|value| value.as_str().map(ToString::to_string)) - .collect::>(); - let mut ranges = Vec::new(); - for range in conversation - .get("ranges") - .and_then(serde_json::Value::as_array) - .into_iter() - .flatten() - { - let Some(start_line) = range - .get("start_line") - .and_then(serde_json::Value::as_u64) - .and_then(|value| u32::try_from(value).ok()) - else { - continue; - }; - let Some(end_line) = range - .get("end_line") - .and_then(serde_json::Value::as_u64) - .and_then(|value| u32::try_from(value).ok()) - else { - continue; - }; - let contributor = range - .get("contributor") - .and_then(serde_json::Value::as_object) - .cloned() - .unwrap_or_default(); - ranges.push(AgentTraceRange { - start_line, - end_line, - contributor: AgentTraceContributor { - r#type: contributor - .get("type") - .and_then(serde_json::Value::as_str) - .unwrap_or("unknown") - .to_string(), - model_id: contributor - .get("model_id") - .and_then(serde_json::Value::as_str) - .map(ToString::to_string), - }, - }); - } - - conversations.push(AgentTraceConversation { - url: url.to_string(), - related, - ranges, - }); - } - - files.push(AgentTraceFile { - path: path.to_string(), - conversations, - }); - } - - let metadata = value - .get("metadata") - .and_then(serde_json::Value::as_object) - .map(|map| { - map.iter() - .filter_map(|(key, value)| { - value.as_str().map(|value| (key.clone(), value.to_string())) - }) - .collect::>() - }) - .unwrap_or_default(); - - Ok(AgentTraceRecord { - version, - id, - timestamp, - vcs: AgentTraceVcs { - r#type: vcs_type, - revision: vcs_revision, - }, - files, - metadata, - }) -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum PostCommitNoOpReason { - Disabled, - CliUnavailable, - BareRepository, - AlreadyFinalized, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PostCommitPersisted { - pub commit_sha: String, - pub notes: PersistenceWriteResult, - pub database: PersistenceWriteResult, - pub trace_id: String, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PostCommitQueuedFallback { - pub commit_sha: String, - pub failed_targets: Vec, - pub trace_id: String, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum PostCommitFinalization { - NoOp(PostCommitNoOpReason), - Persisted(PostCommitPersisted), - QueuedFallback(PostCommitQueuedFallback), -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PostRewriteRuntimeState { - pub sce_disabled: bool, - pub cli_available: bool, - pub is_bare_repo: bool, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct RewriteTraceInput { - pub record_id: String, - pub timestamp_rfc3339: String, - pub rewritten_commit_sha: String, - pub rewrite_from_sha: String, - pub rewrite_method: RewriteMethod, - pub rewrite_confidence: f32, - pub idempotency_key: String, - pub files: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum RewriteTraceNoOpReason { - Disabled, - CliUnavailable, - BareRepository, - AlreadyFinalized, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct RewriteTracePersisted { - pub commit_sha: String, - pub trace_id: String, - pub quality_status: QualityStatus, - pub notes: PersistenceWriteResult, - pub database: PersistenceWriteResult, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct RewriteTraceQueuedFallback { - pub commit_sha: String, - pub trace_id: String, - pub quality_status: QualityStatus, - pub failed_targets: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum RewriteTraceFinalization { - NoOp(RewriteTraceNoOpReason), - Persisted(RewriteTracePersisted), - QueuedFallback(RewriteTraceQueuedFallback), -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum PostRewriteNoOpReason { +pub enum HookNoOpReason { Disabled, - CliUnavailable, - BareRepository, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum RewriteMethod { - Amend, - Rebase, - Other(String), -} - -impl RewriteMethod { - fn canonical_label(&self) -> &str { - match self { - RewriteMethod::Amend => "amend", - RewriteMethod::Rebase => "rebase", - RewriteMethod::Other(method) => method.as_str(), - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct RewritePair { - pub old_sha: String, - pub new_sha: String, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct RewriteRemapRequest { - pub rewrite_method: RewriteMethod, - pub old_sha: String, - pub new_sha: String, - pub idempotency_key: String, -} - -pub trait RewriteRemapIngestion { - fn ingest(&mut self, request: RewriteRemapRequest) -> Result; -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PostRewriteIngested { - pub rewrite_method: RewriteMethod, - pub total_pairs: usize, - pub ingested_pairs: usize, - pub skipped_pairs: usize, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum PostRewriteFinalization { - NoOp(PostRewriteNoOpReason), - Ingested(PostRewriteIngested), -} - -pub fn finalize_post_rewrite_remap( - runtime: &PostRewriteRuntimeState, - rewrite_method: &str, - pairs_file_contents: &str, - remap_ingestion: &mut impl RewriteRemapIngestion, -) -> Result { - if runtime.sce_disabled { - return Ok(PostRewriteFinalization::NoOp( - PostRewriteNoOpReason::Disabled, - )); - } - - if !runtime.cli_available { - return Ok(PostRewriteFinalization::NoOp( - PostRewriteNoOpReason::CliUnavailable, - )); - } - - if runtime.is_bare_repo { - return Ok(PostRewriteFinalization::NoOp( - PostRewriteNoOpReason::BareRepository, - )); - } - - let method = normalize_rewrite_method(rewrite_method); - let pairs = parse_post_rewrite_pairs(pairs_file_contents)?; - - let mut ingested_pairs = 0_usize; - for pair in &pairs { - let idempotency_key = format!( - "post-rewrite:{}:{}:{}", - method.canonical_label(), - pair.old_sha, - pair.new_sha - ); - let accepted = remap_ingestion.ingest(RewriteRemapRequest { - rewrite_method: method.clone(), - old_sha: pair.old_sha.clone(), - new_sha: pair.new_sha.clone(), - idempotency_key, - })?; - if accepted { - ingested_pairs += 1; - } - } - - let total_pairs = pairs.len(); - Ok(PostRewriteFinalization::Ingested(PostRewriteIngested { - rewrite_method: method, - total_pairs, - ingested_pairs, - skipped_pairs: total_pairs.saturating_sub(ingested_pairs), - })) -} - -pub fn finalize_rewrite_trace( - runtime: &PostRewriteRuntimeState, - input: RewriteTraceInput, - notes_writer: &mut impl TraceNotesWriter, - record_store: &mut impl TraceRecordStore, - retry_queue: &mut impl TraceRetryQueue, - emission_ledger: &mut impl TraceEmissionLedger, -) -> Result { - if runtime.sce_disabled { - return Ok(RewriteTraceFinalization::NoOp( - RewriteTraceNoOpReason::Disabled, - )); - } - - if !runtime.cli_available { - return Ok(RewriteTraceFinalization::NoOp( - RewriteTraceNoOpReason::CliUnavailable, - )); - } - - if runtime.is_bare_repo { - return Ok(RewriteTraceFinalization::NoOp( - RewriteTraceNoOpReason::BareRepository, - )); - } - - if emission_ledger.has_emitted(&input.rewritten_commit_sha) { - return Ok(RewriteTraceFinalization::NoOp( - RewriteTraceNoOpReason::AlreadyFinalized, - )); - } - - let confidence = normalize_rewrite_confidence(input.rewrite_confidence)?; - let quality_status = quality_status_for_confidence(input.rewrite_confidence); - let record = build_trace_payload(TraceAdapterInput { - record_id: input.record_id, - timestamp_rfc3339: input.timestamp_rfc3339, - commit_sha: input.rewritten_commit_sha.clone(), - files: input.files, - quality_status, - rewrite: Some(RewriteInfo { - from_sha: input.rewrite_from_sha, - method: input.rewrite_method.canonical_label().to_string(), - confidence, - }), - idempotency_key: Some(input.idempotency_key.clone()), - }); - - let note = TraceNote { - notes_ref: crate::services::agent_trace::NOTES_REF.to_string(), - commit_sha: input.rewritten_commit_sha.clone(), - content_type: TRACE_CONTENT_TYPE.to_string(), - record: record.clone(), - }; - let persisted = PersistedTraceRecord { - commit_sha: input.rewritten_commit_sha.clone(), - idempotency_key: input.idempotency_key, - content_type: TRACE_CONTENT_TYPE.to_string(), - notes_ref: crate::services::agent_trace::NOTES_REF.to_string(), - record: record.clone(), - prompts: Vec::new(), - }; - - let notes_result = notes_writer.write_note(note); - let database_result = record_store.write_trace_record(persisted); - - let failed_targets = collect_failed_targets(¬es_result, &database_result); - if failed_targets.is_empty() { - emission_ledger.mark_emitted(&input.rewritten_commit_sha); - return Ok(RewriteTraceFinalization::Persisted(RewriteTracePersisted { - commit_sha: input.rewritten_commit_sha, - trace_id: record.id, - quality_status, - notes: notes_result, - database: database_result, - })); - } - - retry_queue.enqueue(TraceRetryQueueEntry { - commit_sha: input.rewritten_commit_sha.clone(), - failed_targets: failed_targets.clone(), - content_type: TRACE_CONTENT_TYPE.to_string(), - notes_ref: crate::services::agent_trace::NOTES_REF.to_string(), - record: record.clone(), - prompts: Vec::new(), - })?; - - Ok(RewriteTraceFinalization::QueuedFallback( - RewriteTraceQueuedFallback { - commit_sha: input.rewritten_commit_sha, - trace_id: record.id, - quality_status, - failed_targets, - }, - )) -} - -fn normalize_rewrite_confidence(confidence: f32) -> Result { - if !confidence.is_finite() { - anyhow::bail!("rewrite confidence must be finite") - } - - if !(0.0..=1.0).contains(&confidence) { - anyhow::bail!("rewrite confidence must be within [0.0, 1.0]") - } - - Ok(format!("{confidence:.2}")) -} - -fn quality_status_for_confidence(confidence: f32) -> QualityStatus { - if confidence >= 0.90 { - return QualityStatus::Final; - } - - if confidence >= 0.60 { - return QualityStatus::Partial; - } - - QualityStatus::NeedsReview -} - -fn parse_post_rewrite_pairs(contents: &str) -> Result> { - let mut pairs = Vec::new(); - - for (line_index, line) in contents.lines().enumerate() { - let trimmed = line.trim(); - if trimmed.is_empty() { - continue; - } - - let mut fields = trimmed.split_whitespace(); - let Some(old_sha) = fields.next() else { - continue; - }; - let Some(new_sha) = fields.next() else { - anyhow::bail!( - "Invalid post-rewrite pair format on line {}: expected ' '", - line_index + 1 - ); - }; - - if fields.next().is_some() { - anyhow::bail!( - "Invalid post-rewrite pair format on line {}: expected exactly two fields", - line_index + 1 - ); - } - - if old_sha == new_sha { - continue; - } - - pairs.push(RewritePair { - old_sha: old_sha.to_string(), - new_sha: new_sha.to_string(), - }); - } - - Ok(pairs) -} - -fn normalize_rewrite_method(method: &str) -> RewriteMethod { - match method.trim().to_ascii_lowercase().as_str() { - "amend" => RewriteMethod::Amend, - "rebase" => RewriteMethod::Rebase, - other => RewriteMethod::Other(other.to_string()), - } -} - -pub fn finalize_post_commit_trace( - runtime: &PostCommitRuntimeState, - input: PostCommitInput, - notes_writer: &mut impl TraceNotesWriter, - record_store: &mut impl TraceRecordStore, - retry_queue: &mut impl TraceRetryQueue, - emission_ledger: &mut impl TraceEmissionLedger, -) -> Result { - if runtime.sce_disabled { - return Ok(PostCommitFinalization::NoOp(PostCommitNoOpReason::Disabled)); - } - - if !runtime.cli_available { - return Ok(PostCommitFinalization::NoOp( - PostCommitNoOpReason::CliUnavailable, - )); - } - - if runtime.is_bare_repo { - return Ok(PostCommitFinalization::NoOp( - PostCommitNoOpReason::BareRepository, - )); - } - - if emission_ledger.has_emitted(&input.commit_sha) { - return Ok(PostCommitFinalization::NoOp( - PostCommitNoOpReason::AlreadyFinalized, - )); - } - - let PostCommitInput { - record_id, - timestamp_rfc3339, - committed_at_unix_ms: _, - commit_sha, - parent_sha, - idempotency_key, - files, - prompts, - } = input; - - let mut record = build_trace_payload(TraceAdapterInput { - record_id, - timestamp_rfc3339, - commit_sha: commit_sha.clone(), - files, - quality_status: QualityStatus::Final, - rewrite: None, - idempotency_key: Some(idempotency_key.clone()), - }); - - if let Some(parent_sha) = parent_sha { - record - .metadata - .insert(POST_COMMIT_PARENT_SHA_METADATA_KEY.to_string(), parent_sha); - } - - let note = TraceNote { - notes_ref: crate::services::agent_trace::NOTES_REF.to_string(), - commit_sha: commit_sha.clone(), - content_type: TRACE_CONTENT_TYPE.to_string(), - record: record.clone(), - }; - let persisted = PersistedTraceRecord { - commit_sha: commit_sha.clone(), - idempotency_key, - content_type: TRACE_CONTENT_TYPE.to_string(), - notes_ref: crate::services::agent_trace::NOTES_REF.to_string(), - record: record.clone(), - prompts: prompts.clone(), - }; - - let notes_result = notes_writer.write_note(note); - let database_result = record_store.write_trace_record(persisted); - - let failed_targets = collect_failed_targets(¬es_result, &database_result); - if failed_targets.is_empty() { - emission_ledger.mark_emitted(&commit_sha); - return Ok(PostCommitFinalization::Persisted(PostCommitPersisted { - commit_sha, - notes: notes_result, - database: database_result, - trace_id: record.id, - })); - } - - retry_queue.enqueue(TraceRetryQueueEntry { - commit_sha: commit_sha.clone(), - failed_targets: failed_targets.clone(), - content_type: TRACE_CONTENT_TYPE.to_string(), - notes_ref: crate::services::agent_trace::NOTES_REF.to_string(), - record: record.clone(), - prompts, - })?; - - Ok(PostCommitFinalization::QueuedFallback( - PostCommitQueuedFallback { - commit_sha, - failed_targets, - trace_id: record.id, - }, - )) -} - -fn collect_failed_targets( - notes_result: &PersistenceWriteResult, - database_result: &PersistenceWriteResult, -) -> Vec { - let mut failed_targets = Vec::new(); - if matches!(notes_result, PersistenceWriteResult::Failed(_)) { - failed_targets.push(PersistenceTarget::Notes); - } - if matches!(database_result, PersistenceWriteResult::Failed(_)) { - failed_targets.push(PersistenceTarget::Database); - } - failed_targets -} - -pub fn process_trace_retry_queue( - retry_queue: &mut impl TraceRetryQueue, - notes_writer: &mut impl TraceNotesWriter, - record_store: &mut impl TraceRecordStore, - metrics_sink: &mut impl RetryMetricsSink, - max_items: usize, -) -> Result { - let mut processed_trace_ids = HashSet::new(); - let mut summary = RetryQueueProcessSummary { - attempted: 0, - recovered: 0, - requeued: 0, - }; - - for _ in 0..max_items { - let Some(entry) = retry_queue.dequeue_next()? else { - break; - }; - - if !processed_trace_ids.insert(entry.record.id.clone()) { - retry_queue.enqueue(entry)?; - break; - } - - summary.attempted += 1; - let started = Instant::now(); - - let notes_result = if entry.failed_targets.contains(&PersistenceTarget::Notes) { - notes_writer.write_note(TraceNote { - notes_ref: entry.notes_ref.clone(), - commit_sha: entry.commit_sha.clone(), - content_type: entry.content_type.clone(), - record: entry.record.clone(), - }) - } else { - PersistenceWriteResult::AlreadyExists - }; - - let database_result = if entry.failed_targets.contains(&PersistenceTarget::Database) { - let idempotency_key = entry - .record - .metadata - .get(METADATA_IDEMPOTENCY_KEY) - .cloned() - .unwrap_or_else(|| format!("retry:{}:{}", entry.commit_sha, entry.record.id)); - record_store.write_trace_record(PersistedTraceRecord { - commit_sha: entry.commit_sha.clone(), - idempotency_key, - content_type: entry.content_type.clone(), - notes_ref: entry.notes_ref.clone(), - record: entry.record.clone(), - prompts: entry.prompts.clone(), - }) - } else { - PersistenceWriteResult::AlreadyExists - }; - - let failed_targets = collect_failed_targets(¬es_result, &database_result); - let error_class = first_failure_class(¬es_result, &database_result); - - metrics_sink.record_retry_metric(RetryProcessingMetric { - commit_sha: entry.commit_sha.clone(), - trace_id: entry.record.id.clone(), - runtime_ms: started.elapsed().as_millis(), - error_class, - failed_targets: failed_targets.clone(), - }); - - if failed_targets.is_empty() { - summary.recovered += 1; - continue; - } - - summary.requeued += 1; - retry_queue.enqueue(TraceRetryQueueEntry { - commit_sha: entry.commit_sha, - failed_targets, - content_type: entry.content_type, - notes_ref: entry.notes_ref, - record: entry.record, - prompts: entry.prompts, - })?; - } - - Ok(summary) -} - -fn first_failure_class( - notes_result: &PersistenceWriteResult, - database_result: &PersistenceWriteResult, -) -> Option { - match notes_result { - PersistenceWriteResult::Failed(failure) => return Some(failure.class.clone()), - PersistenceWriteResult::Written | PersistenceWriteResult::AlreadyExists => {} - } - - match database_result { - PersistenceWriteResult::Failed(failure) => Some(failure.class.clone()), - PersistenceWriteResult::Written | PersistenceWriteResult::AlreadyExists => None, - } -} - -pub fn apply_commit_msg_coauthor_policy( - runtime: &CommitMsgRuntimeState, - commit_message: &str, -) -> String { - if runtime.sce_disabled || !runtime.sce_coauthor_enabled || !runtime.has_staged_sce_attribution - { - return commit_message.to_string(); - } - - let mut lines: Vec<&str> = commit_message.lines().collect(); - lines.retain(|line| *line != CANONICAL_SCE_COAUTHOR_TRAILER); - - if !lines.is_empty() && !lines.last().is_some_and(|line| line.is_empty()) { - lines.push(""); - } - lines.push(CANONICAL_SCE_COAUTHOR_TRAILER); - - let mut normalized = lines.join("\n"); - if commit_message.ends_with('\n') { - normalized.push('\n'); - } - - normalized -} - -pub fn finalize_pre_commit_checkpoint( - runtime: &PreCommitRuntimeState, - anchors: PreCommitTreeAnchors, - pending: PendingCheckpoint, -) -> PreCommitFinalization { - if runtime.sce_disabled { - return PreCommitFinalization::NoOp(PreCommitNoOpReason::Disabled); - } - - if !runtime.cli_available { - return PreCommitFinalization::NoOp(PreCommitNoOpReason::CliUnavailable); - } - - if runtime.is_bare_repo { - return PreCommitFinalization::NoOp(PreCommitNoOpReason::BareRepository); - } - - let PendingCheckpoint { - files: pending_files, - harness_type, - git_branch, - model_id, - prompts: pending_prompts, - } = pending; - - let files = pending_files - .into_iter() - .filter_map(|file| { - if file.staged_ranges.is_empty() { - return None; - } - - Some(FinalizedFileCheckpoint { - path: file.path, - has_sce_attribution: file.has_sce_attribution, - ranges: file.staged_ranges, - }) - }) - .collect(); - - let prompts = pending_prompts - .into_iter() - .map(|prompt| FinalizedPromptCheckpoint { - turn_number: prompt.turn_number, - prompt_text: prompt.prompt_text, - prompt_length: prompt.prompt_length, - is_truncated: prompt.is_truncated, - cwd: prompt.cwd, - transcript_path: prompt.transcript_path, - captured_at: prompt.captured_at, - }) - .collect(); - - PreCommitFinalization::Finalized(FinalizedCheckpoint { - anchors, - harness_type, - git_branch, - model_id, - files, - prompts, - }) + AttributionOnlyCommitMsgMode, } diff --git a/config/pkl/base/sce-config-schema.pkl b/config/pkl/base/sce-config-schema.pkl index 8c49209..3a11ca2 100644 --- a/config/pkl/base/sce-config-schema.pkl +++ b/config/pkl/base/sce-config-schema.pkl @@ -74,6 +74,15 @@ local sceConfigSchema = new JsonSchema { type = "object" additionalProperties = false properties { + ["attribution_hooks"] = new JsonSchema { + type = "object" + additionalProperties = false + properties { + ["enabled"] = new JsonSchema { + type = "boolean" + } + } + } ["bash"] = new JsonSchema { type = "object" additionalProperties = false diff --git a/config/schema/sce-config.schema.json b/config/schema/sce-config.schema.json index eeb10fd..5c21165 100644 --- a/config/schema/sce-config.schema.json +++ b/config/schema/sce-config.schema.json @@ -67,6 +67,15 @@ "policies": { "type": "object", "properties": { + "attribution_hooks": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "additionalProperties": false + }, "bash": { "type": "object", "properties": { diff --git a/context/architecture.md b/context/architecture.md index a459e41..1f13271 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -85,7 +85,7 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/observability.rs` provides deterministic runtime observability controls and rendering for app lifecycle logs, including shared config-resolved threshold/format and OTEL/file-sink inputs with precedence `env > config file > defaults` for non-flag observability keys, optional file sink controls (`SCE_LOG_FILE`, `SCE_LOG_FILE_MODE` with deterministic truncate-or-append policy), optional OTEL export bootstrap (`SCE_OTEL_ENABLED`, `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_PROTOCOL`), stable event identifiers, severity filtering, stderr-only primary emission with optional mirrored file writes, and redaction-safe emission through the shared security helper. - `cli/src/command_surface.rs` is the source of truth for top-level command contract metadata (`help`, `config`, `setup`, `doctor`, `auth`, `hooks`, `trace`, `sync`, `version`, `completion`), including implementation status for command classification and per-command visibility on the slim top-level help surface. - `cli/src/services/default_paths.rs` is the canonical production path catalog for the CLI: it resolves config/state/cache roots with platform-aware XDG or `dirs` fallbacks, exposes named default paths plus an explicit persisted-artifact inventory for the current default artifacts (global config, auth tokens, Agent Trace local DB), and owns canonical repo-relative, embedded-asset, install/runtime, hook, and context-path accessors so non-test production path definitions have one shared owner. Current production consumers such as config discovery, doctor reporting, setup/install flows, and local hook runtime path resolution consume this shared catalog rather than defining owned path literals in their own modules. -- `cli/src/services/config.rs` defines `sce config` parser/runtime contracts (`show`, `validate`, `--help`), with bare `sce config` routed by `cli/src/app.rs` to the same help payload as `sce config --help`, deterministic config-file selection, explicit value precedence (`flags > env > config file > defaults`), strict config-file validation (`$schema`, `log_level`, `log_format`, `log_file`, `log_file_mode`, `timeout_ms`, `workos_client_id`, nested `otel`, `policies.bash`), compile-time embedding of the canonical generated schema artifact and bash-policy preset catalog from the ephemeral crate-local `cli/assets/generated/config/**` mirror prepared from canonical `config/` outputs before Cargo packaging/builds, runtime parity between that schema and the Rust-side top-level allowed-key gate so startup config discovery accepts the canonical `"$schema": "https://sce.crocoder.dev/config.json"` declaration, shared auth-key resolution with optional baked defaults starting at `workos_client_id`, repo-configured bash-policy preset/custom validation and merged reporting from discovered config files, shared observability-runtime resolution for logging and OTEL config keys (`log_level`, `log_format`, `log_file`, `log_file_mode`, `otel.enabled`, `otel.exporter_otlp_endpoint`, `otel.exporter_otlp_protocol`) with deterministic source-aware env-over-config precedence reused by `cli/src/app.rs`, and deterministic text/JSON output rendering where `show` includes resolved observability/auth/policy values with provenance while `validate` now returns only validation status plus issues/warnings. +- `cli/src/services/config.rs` defines `sce config` parser/runtime contracts (`show`, `validate`, `--help`), with bare `sce config` routed by `cli/src/app.rs` to the same help payload as `sce config --help`, deterministic config-file selection, explicit value precedence (`flags > env > config file > defaults`), strict config-file validation (`$schema`, `log_level`, `log_format`, `log_file`, `log_file_mode`, `timeout_ms`, `workos_client_id`, nested `otel`, `policies.bash`, and `policies.attribution_hooks.enabled`), compile-time embedding of the canonical generated schema artifact and bash-policy preset catalog from the ephemeral crate-local `cli/assets/generated/config/**` mirror prepared from canonical `config/` outputs before Cargo packaging/builds, runtime parity between that schema and the Rust-side top-level allowed-key gate so startup config discovery accepts the canonical `"$schema": "https://sce.crocoder.dev/config.json"` declaration, shared auth-key resolution with optional baked defaults starting at `workos_client_id`, repo-configured bash-policy preset/custom validation and merged reporting from discovered config files, a shared attribution-hooks runtime gate resolved from `SCE_ATTRIBUTION_HOOKS_ENABLED` or `policies.attribution_hooks.enabled` with disabled-by-default semantics, shared observability-runtime resolution for logging and OTEL config keys (`log_level`, `log_format`, `log_file`, `log_file_mode`, `otel.enabled`, `otel.exporter_otlp_endpoint`, `otel.exporter_otlp_protocol`) with deterministic source-aware env-over-config precedence reused by `cli/src/app.rs`, and deterministic text/JSON output rendering where `show` includes resolved observability/auth/policy values with provenance while `validate` now returns only validation status plus issues/warnings. - `cli/src/services/output_format.rs` defines the canonical shared CLI output-format contract (`OutputFormat`) for supporting commands, with deterministic `text|json` parsing and command-scoped actionable invalid-value guidance. - `cli/src/services/auth_command.rs` defines the implemented auth command surface for `sce auth login|renew|logout|status`, including device-flow login, stored-token renewal (`--force` supported for renew), logout, and status rendering in text/JSON formats. - `cli/src/services/trace.rs` defines the prompt-query read path for `sce trace prompts `, including repository-root resolution from git truth, unique prefix/full-SHA commit lookup against persisted Agent Trace data, deterministic text/JSON rendering, legacy `--json` compatibility, and explicit missing/ambiguous commit diagnostics for prompt rows stored in the local `prompts` table when those tables exist. @@ -97,7 +97,7 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/agent_trace.rs` defines the Agent Trace schema adapter and builder contracts (`adapt_trace_payload`, `build_trace_payload`), including fixed git VCS identity, reserved reverse-domain metadata keys, and deterministic AI `model_id` normalization before schema-compliance validation. - `cli/src/services/version.rs` defines the version command parser/rendering contract (`parse_version_request`, `render_version`) with deterministic text output and stable JSON runtime-identification fields. - `cli/src/services/completion.rs` defines completion parser/rendering contract (`parse_completion_request`, `render_completion`) with deterministic Bash/Zsh/Fish script output aligned to current parser-valid command/flag surfaces. -- `cli/src/services/hooks.rs` defines production local hook runtime parsing/dispatch (`HookSubcommand`, `parse_hooks_subcommand`, `run_hooks_subcommand`) plus a pre-commit staged-checkpoint finalization seam (`finalize_pre_commit_checkpoint`) that enforces staged-only attribution and carries index/tree anchors with explicit no-op guard states, a commit-msg co-author policy seam (`apply_commit_msg_coauthor_policy`) that injects one canonical SCE trailer only for allowed attributed commits, a post-commit trace finalization seam (`finalize_post_commit_trace`) that currently persists git notes while local DB writes are disconnected through a no-op record-store adapter, a retry replay seam (`process_trace_retry_queue`) that re-attempts only failed persistence targets and emits per-attempt runtime/error-class metrics, bounded operational retry replay invocation from post-commit/post-rewrite flows (`process_runtime_retry_queue`), a post-rewrite remap-ingestion seam (`finalize_post_rewrite_remap`) that parses old->new SHA pairs and records accepted requests in memory without DB ingestion, and a rewrite trace transformation seam (`finalize_rewrite_trace`) that emits rewritten-SHA Agent Trace records with rewrite metadata plus confidence-based quality status. +- `cli/src/services/hooks.rs` defines the current minimal local hook runtime parsing/dispatch (`HookSubcommand`, `run_hooks_subcommand`) plus a commit-msg co-author policy seam (`apply_commit_msg_coauthor_policy`) that injects one canonical SCE trailer only when the disabled-default attribution-hooks config/env control is enabled and `SCE_DISABLED` is false. The other hook entrypoints (`pre-commit`, `post-commit`, `post-rewrite`) are deterministic no-op surfaces in the current attribution-only baseline. - `cli/src/services/resilience.rs` defines bounded retry/timeout/backoff execution policy (`RetryPolicy`, `run_with_retry`) for transient operation hardening with deterministic failure messaging and retry observability. - `cli/src/services/sync.rs` runs the local adapter through a lazily initialized shared tokio current-thread runtime, applies bounded resilience policy to the local smoke operation, and composes a placeholder cloud-sync abstraction (`CloudSyncGateway`) so local Turso validation and deferred cloud planning remain separated. - `cli/src/services/` contains module boundaries for config, setup, doctor, hooks, sync, version, completion, and local DB adapters with explicit trait seams for future implementations. diff --git a/context/context-map.md b/context/context-map.md index 38646bc..7e972a8 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -21,9 +21,9 @@ Feature/domain context: - `context/sce/agent-trace-implementation-contract.md` (normative T01 implementation contract for no-git-wrapper Agent Trace attribution invariants, compliance matrix, and internal-to-Agent-Trace mapping) - `context/sce/agent-trace-schema-adapter.md` (T02 schema adapter contract and code-level mapping surface in `cli/src/services/agent_trace.rs`) - `context/sce/agent-trace-payload-builder-validation.md` (T03 deterministic payload-builder path, model-id normalization behavior, and Agent Trace schema validation suite) -- `context/sce/agent-trace-pre-commit-staged-checkpoint.md` (T04 pre-commit staged-only finalization contract with no-op guards and index/tree anchor capture) -- `context/sce/agent-trace-commit-msg-coauthor-policy.md` (T05 commit-msg canonical co-author trailer policy with env-gated injection and idempotent dedupe) -- `context/sce/agent-trace-post-commit-dual-write.md` (current post-commit persistence baseline with git-notes persistence, no-op local DB record store, local DB file bootstrap, idempotency ledger guard, and retry-queue fallback semantics) +- `context/sce/agent-trace-pre-commit-staged-checkpoint.md` (historical pre-commit staged-checkpoint contract; current runtime baseline has replaced this path with a deterministic no-op) +- `context/sce/agent-trace-commit-msg-coauthor-policy.md` (current commit-msg canonical co-author trailer policy with attribution-hooks + co-author gating and idempotent dedupe) +- `context/sce/agent-trace-post-commit-dual-write.md` (historical post-commit trace finalization baseline; current runtime baseline replaces this path with a deterministic no-op) - `context/sce/agent-trace-hook-doctor.md` (approved operator-environment contract for broadening `sce doctor` into the canonical health-and-repair entrypoint, including stable problem taxonomy, `--fix` semantics, setup-to-doctor alignment rules, and the approved downstream human text-mode layout/status/integration contract; current implementation baseline is captured inside the file) - `context/sce/setup-githooks-install-contract.md` (T01 canonical `sce setup --hooks` install contract for target-path resolution, idempotent outcomes, backup/rollback, and doctor-readiness alignment) - `context/sce/setup-no-backup-policy-seam.md` (implemented shared `SetupBackupPolicy` seam that classifies git-backed vs non-git-backed setup targets and feeds both config-install and required-hook install flows) @@ -36,7 +36,7 @@ Feature/domain context: - `context/sce/agent-trace-core-schema-migrations.md` (current Agent Trace local DB empty-file baseline with create/open-only runtime behavior and no schema bootstrap) - `context/sce/agent-trace-retry-queue-observability.md` (T14 retry queue recovery contract plus reconciliation/runtime observability metrics and DB-first queue schema additions) - `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md` (T01 Local Hooks MVP production contract freeze and deterministic gap matrix for `agent-trace-local-hooks-production-mvp`) -- `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime entrypoint behavior, including commit-msg policy gating/file mutation and post-rewrite remap+rewrite finalization wiring) +- `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus the current minimal runtime behavior: disabled-default commit-msg attribution and no-op `pre-commit`/`post-commit`/`post-rewrite` entrypoints) - `context/sce/automated-profile-contract.md` (deterministic gate policy for automated OpenCode profile, including 10 gate categories, permission mappings, and automated profile constraints) - `context/sce/bash-tool-policy-enforcement-contract.md` (approved bash-tool blocking contract plus the implementation target for generated OpenCode enforcement, including config schema, argv-prefix matching, fixed preset catalog/messages, and precedence rules) - `context/sce/generated-opencode-plugin-registration.md` (current generated OpenCode plugin-registration contract, canonical Pkl ownership, generated manifest/plugin paths, and TypeScript source ownership; Claude bash-policy enforcement has been removed from generated outputs) diff --git a/context/glossary.md b/context/glossary.md index ae51048..5e08873 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -67,13 +67,14 @@ - `cli config precedence contract`: Deterministic runtime value resolution in `cli/src/services/config.rs` with precedence `flags > env > config file > defaults` for flag-backed keys (`log_level`, `timeout_ms`) plus shared app-runtime observability keys (`log_format`, `log_file`, `log_file_mode`, `otel.enabled`, `otel.exporter_otlp_endpoint`, `otel.exporter_otlp_protocol`) consumed by `cli/src/app.rs`; config discovery order is `--config`, `SCE_CONFIG_FILE`, then default discovered global+local paths (`${config_root}/sce/config.json` merged before `.sce/config.json`, with local overriding per key, where `config_root` comes from the shared default path policy seam and resolves to `$XDG_CONFIG_HOME` / `dirs::config_dir()` semantics with platform fallback behavior rather than the old state/data-root default). Runtime startup config loading now also permits the canonical top-level `"$schema": "https://sce.crocoder.dev/config.json"` declaration anywhere those config files are parsed. - `sce config schema artifact`: Canonical JSON Schema for global and repo-local `sce/config.json` files, authored in `config/pkl/base/sce-config-schema.pkl`, generated to `config/schema/sce-config.schema.json`, and embedded by `cli/src/services/config.rs` for shared `sce config validate` and doctor config validation. The current schema accepts the canonical `$schema` declaration, flat logging keys (`log_level`, `log_format`, `log_file`, `log_file_mode`), nested `otel` keys (`enabled`, `exporter_otlp_endpoint`, `exporter_otlp_protocol`), existing auth/config keys, and enforces the schema-level dependency that `log_file_mode` requires `log_file`. - `bash tool policy config surface`: Nested repo config namespace under `.sce/config.json` at `policies.bash`, currently supporting unique built-in `presets` plus repo-owned `custom` argv-prefix rules with deterministic validation, merged global/local resolution, and first-class `sce config show|validate` reporting. +- `attribution hooks gate`: Disabled-default local hook runtime gate resolved through shared config precedence in `cli/src/services/config.rs`: env `SCE_ATTRIBUTION_HOOKS_ENABLED` overrides repo/global config key `policies.attribution_hooks.enabled`, and the current enabled path activates commit-msg-only attribution without re-enabling trace persistence. - `bash policy preset catalog`: Canonical authored preset source at `config/pkl/base/bash-policy-presets.pkl`, rendered to JSON by `config/pkl/generate.pkl` and embedded by the CLI from `config/.opencode/lib/bash-policy-presets.json` so CLI validation and OpenCode enforcement share the same preset IDs, argv-prefix matchers, fixed messages, and conflict metadata. - `OpenCode bash policy plugin`: Generated OpenCode pre-execution hook at `config/.opencode/plugins/sce-bash-policy.ts` with runtime logic at `config/.opencode/lib/bash-policy-runtime.ts` (also emitted under `config/automated/.opencode/**`) that intercepts `bash` tool calls, applies the canonical argv normalization and longest-prefix/custom-over-preset precedence rules, merges `${config_root}/sce/config.json` from the shared default path policy seam with repo-local `.sce/config.json`, and throws a stable `Blocked by SCE bash-tool policy '': ` denial before subprocess launch. - `bash policy redundancy warning`: Non-fatal config validation output emitted when `forbid-git-all` and `forbid-git-commit` are enabled together; the config remains valid, but `sce config show|validate` reports the overlap deterministically as a warning instead of an error. - `auth config baked default`: Optional key-declared fallback in `cli/src/services/config.rs` used only after env and config-file inputs are absent; the first implemented case is `workos_client_id`, which currently falls back to `client_sce_default`. - `setup install engine`: Installer in `cli/src/services/setup.rs` (`install_embedded_setup_assets`) that writes embedded setup assets into per-target staging directories and swaps them into repository-root `.opencode/`/`.claude/` destinations, using backup mode branching from `SetupBackupPolicy`. - `setup backup-and-replace`: Replacement choreography in `cli/src/services/setup.rs` where non-git-backed install targets are renamed to unique `.backup` paths before staged content is promoted; on swap failure, the engine restores the original target from backup and cleans temporary staging paths. Git-backed config installs are excluded from this path and rely on git-state recovery guidance instead. -- `hooks command routing contract` (T02, T07): Implemented hook command parser/dispatcher plus runtime wiring in `cli/src/services/hooks.rs` (`HookSubcommand`, `parse_hooks_subcommand`, `run_hooks_subcommand`) that supports `pre-commit`, `commit-msg `, `post-commit`, and `post-rewrite ` with deterministic invocation validation/usage errors and production post-rewrite remap + rewritten-trace finalization execution. +- `hooks command routing contract`: Current hook command parser/dispatcher plus runtime wiring in `cli/src/services/hooks.rs` (`HookSubcommand`, `run_hooks_subcommand`) that supports `pre-commit`, `commit-msg `, `post-commit`, and `post-rewrite ` with deterministic invocation validation/usage errors; `commit-msg` is the only active attribution path behind the attribution hooks gate, while `pre-commit`, `post-commit`, and `post-rewrite` are deterministic no-op entrypoints. - `cloud sync gateway placeholder`: Abstraction in `cli/src/services/sync.rs` (`CloudSyncGateway`) that returns deferred cloud-sync checkpoints while `sync` remains non-production. - `sce CLI onboarding guide`: Crate-local documentation at `cli/README.md` that defines runnable placeholder commands, non-goals/safety limits, and roadmap mapping to service modules. - `plan/code overlap map`: Context artifact at `context/sce/plan-code-overlap-map.md` that classifies Shared Context Plan/Code, `/change-to-plan`, `/next-task`, `/commit`, and core skills into role-specific vs shared-reusable instruction blocks with explicit dedup targets. @@ -92,9 +93,7 @@ - `agent trace schema adapter`: Task-scoped mapping contract implemented in `cli/src/services/agent_trace.rs` (`adapt_trace_payload`) and documented in `context/sce/agent-trace-schema-adapter.md`; maps internal attribution inputs to Agent Trace-shaped records with fixed `vcs.type = git` and reserved `dev.crocoder.sce.*` metadata placement. - `agent trace payload builder`: Canonical T03 builder contract in `cli/src/services/agent_trace.rs` (`build_trace_payload`) that layers on top of the adapter, preserves deterministic output for identical input, and normalizes AI `model_id` values toward `provider/model` form when inferable. - `agent trace schema validation suite`: T03 compliance test slice in `services::agent_trace::tests` that validates payload JSON against the published Agent Trace trace-record schema with draft-2020-12 format checks enabled (`uri`, `date-time`, `uuid`) and a local version-pattern compatibility patch for the current app semver emitted from `CARGO_PKG_VERSION`. -- `agent trace pre-commit staged checkpoint finalization`: T04 contract in `cli/src/services/hooks.rs` (`finalize_pre_commit_checkpoint`) that filters pending attribution to staged ranges only, drops unstaged-only files, captures index/head tree anchors, and returns explicit no-op outcomes when SCE is disabled, CLI is unavailable, or the repository is bare. -- `agent trace commit-msg co-author policy`: T05 contract in `cli/src/services/hooks.rs` (`apply_commit_msg_coauthor_policy`) that applies exactly one canonical trailer (`Co-authored-by: SCE `) only when SCE is enabled, co-author policy is enabled, and staged SCE attribution exists; duplicate canonical trailers are deduped idempotently. -- `agent trace post-commit persistence baseline`: Current `cli/src/services/hooks.rs` (`finalize_post_commit_trace`) behavior where hook finalization still emits one canonical Agent Trace record per commit behind runtime guards, persists git notes, and keeps local DB persistence disconnected through a no-op record-store adapter while retry bookkeeping remains target-aware. +- `agent trace commit-msg co-author policy`: Current contract in `cli/src/services/hooks.rs` (`apply_commit_msg_coauthor_policy`) that applies exactly one canonical trailer (`Co-authored-by: SCE `) only when attribution hooks are enabled and SCE is not disabled; duplicate canonical trailers are deduped idempotently. - `agent trace local DB empty-file baseline`: Current `cli/src/services/local_db.rs` (`ensure_agent_trace_local_db_ready_blocking`) behavior where the deterministic per-user Agent Trace DB path (`${XDG_STATE_HOME:-~/.local/state}/sce/agent-trace/local.db` on Linux; platform-equivalent user state root elsewhere) has its parent directory created and the DB file opened/created without applying schema migrations or installing trace tables. - `agent trace post-commit idempotency ledger`: T06 seam (`TraceEmissionLedger`) in `cli/src/services/hooks.rs` used to prevent duplicate emission for the same commit SHA and to mark successful dual-write completion. - `sce doctor` operator-health contract: `cli/src/services/doctor.rs` now implements the current approved operator-health surface in `context/sce/agent-trace-hook-doctor.md`: `sce doctor --fix` selects repair intent, help/output expose deterministic doctor mode, JSON includes stable problem taxonomy/fixability fields plus database records and fix-result records, the runtime validates state-root resolution, global and repo-local `sce/config.json` readability/schema health, Agent Trace local DB path/health, DB-parent readiness barriers, git availability, non-repo vs bare-repo targeting failures, effective hook-path source resolution, required hook presence/executable/content drift against canonical embedded hook assets, and repo-root installed OpenCode integration presence for `OpenCode plugins`, `OpenCode agents`, `OpenCode commands`, and `OpenCode skills`. Human text mode now uses the approved sectioned layout (`Environment`, `Configuration`, `Repository`, `Git Hooks`, `Integrations`), `SCE doctor diagnose` / `SCE doctor fix` headers, bracketed `[PASS]`/`[FAIL]`/`[MISS]` status tokens with shared-style green/red colorization when enabled, simplified `label (path)` row formatting, top-level-only hook rows, and presence-only integration parent/child rows where missing required files surface as `[MISS]` children and `[FAIL]` parent groups. Fix mode still reuses canonical setup hook installation for missing/stale/non-executable required hooks and missing hooks directories and can bootstrap the canonical missing SCE-owned Agent Trace DB parent directory. @@ -102,7 +101,7 @@ - `agent trace post-rewrite local remap ingestion`: T08 contract in `cli/src/services/hooks.rs` (`finalize_post_rewrite_remap`) that parses git `post-rewrite` old/new SHA pairs, captures normalized rewrite method (`amend`, `rebase`, or lowercase passthrough), derives deterministic `post-rewrite:::` idempotency keys, and dispatches replay-safe remap ingestion requests. - `agent trace rewrite trace transformation`: T09 contract in `cli/src/services/hooks.rs` (`finalize_rewrite_trace`) that materializes rewritten-SHA Agent Trace records with `rewrite_from`/`rewrite_method`/`rewrite_confidence` metadata, enforces confidence range normalization (`0.00`..`1.00`), maps quality status thresholds (`final`/`partial`/`needs_review`), and currently preserves notes persistence plus retry fallback while local DB writes remain disconnected. - `agent trace local DB schema migration contract`: Retired `apply_core_schema_migrations` behavior removed from the current runtime during `agent-trace-removal-and-hook-noop-reset` T01; the local DB baseline is now file open/create only. -- `agent trace retry replay processor`: T14/T08 operational contract in `cli/src/services/hooks.rs` where `process_trace_retry_queue` dequeues fallback queue entries, retries only previously failed persistence targets (notes and/or DB), requeues remaining failures, and emits per-attempt runtime/error-class metrics via `RetryMetricsSink`; production local hook runtime invokes bounded replay (`max_items=16`) after post-commit and post-rewrite finalization with deterministic retry summary output. +- `agent trace retry replay processor`: Retained operational contract in `cli/src/services/hooks.rs` where `process_trace_retry_queue` dequeues fallback queue entries, retries only previously failed persistence targets (notes and/or DB), requeues remaining failures, and emits per-attempt runtime/error-class metrics via `RetryMetricsSink`; the current trace-removal runtime baseline does not invoke this seam from local hook entrypoints. - `agent trace local hooks MVP contract and gap matrix`: T01 context artifact at `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md` that freezes local production boundaries/decisions for `agent-trace-local-hooks-production-mvp` and maps current seam-level code truth to required runtime completion tasks (`T02`..`T10`). - `automated profile`: Non-interactive OpenCode configuration variant generated at `config/automated/.opencode/**` that removes ask/confirm gates and applies deterministic behavior policies defined in `context/sce/automated-profile-contract.md`. - `automated profile contract`: Context artifact at `context/sce/automated-profile-contract.md` defining deterministic gate policies (10 categories: P1-P10), permission mappings, and automated profile constraints for non-interactive SCE workflows. diff --git a/context/overview.md b/context/overview.md index 25d3703..8928eb3 100644 --- a/context/overview.md +++ b/context/overview.md @@ -20,6 +20,7 @@ The setup service also provides repository-root install orchestration: it resolv The CLI now also applies baseline security hardening for reliability-driven automation: diagnostics/logging paths use deterministic secret redaction, `sce setup --hooks --repo ` canonicalizes and validates repository paths before execution, and setup write flows run explicit directory write-permission probes before staging/swap operations. The config service now provides deterministic runtime config resolution with explicit precedence (`flags > env > config file > defaults`), strict config-file validation (`$schema`, `log_level`, `log_format`, `log_file`, `log_file_mode`, `timeout_ms`, `workos_client_id`, nested `otel`, and nested `policies.bash`), deterministic default discovery/merge of global+local config files (`${config_root}/sce/config.json` then `.sce/config.json` with local override, where `config_root` comes from the shared default-path seam with XDG/`dirs::config_dir()` config-root resolution), defaults for the resolved observability value set (`log_level=error`, `log_format=text`, `log_file_mode=truncate`, `otel.enabled=false`, `otel.exporter_otlp_endpoint=http://127.0.0.1:4317`, `otel.exporter_otlp_protocol=grpc`), shared auth-key resolution with optional baked defaults starting at `workos_client_id`, first-class bash-policy preset/custom parsing with deterministic conflict and duplicate-prefix validation, and a canonical Pkl-authored `sce/config.json` JSON Schema generated to `config/schema/sce-config.schema.json` and embedded by `cli/src/services/config.rs` for both `sce config validate` and doctor-time config checks. Runtime startup config loading now keeps parity with that schema by accepting the canonical `"$schema": "https://sce.crocoder.dev/config.json"` declaration in repo-local and global config files, so startup commands such as `sce version` no longer fail before dispatch on that field. App-runtime observability now consumes that mixed-shape observability config (flat logging keys plus nested `otel`) through the shared resolver, so env values still override config-file values while config files provide deterministic fallback for file logging and OTEL bootstrap; `sce config show` reports resolved observability/auth/policy values with provenance, while `sce config validate` is now a trimmed validation surface that reports only pass/fail plus validation errors or warnings in text and JSON modes. The canonical preset catalog and matching contract live in `config/pkl/data/bash-policy-presets.json` and `context/sce/bash-tool-policy-enforcement-contract.md`. The shared default path service in `cli/src/services/default_paths.rs` is now the canonical owner for production CLI path definitions. It resolves per-user config/state/cache roots, exposes the current persisted-artifact inventory (global config, auth tokens, Agent Trace local DB), and also defines the repo-relative, embedded-asset, install/runtime, hook, and context-path accessors consumed across current CLI production code. Non-test production modules should consume this shared catalog instead of hardcoding owned path literals. No default cache-backed persisted artifact currently exists, so cache-root resolution remains available without speculative cache-path features and no legacy default-path fallback is supported. +The same config resolver now also owns the attribution-hooks gate used by local hook runtime: `SCE_ATTRIBUTION_HOOKS_ENABLED` overrides `policies.attribution_hooks.enabled`, and the gate defaults to disabled. Generated config now includes repo-local bash-policy enforcement assets for OpenCode only: OpenCode blocks `bash` tool calls before subprocess launch via `config/.opencode/plugins/sce-bash-policy.ts` plus shared runtime logic and preset data in `config/.opencode/lib/` (also emitted for `config/automated/.opencode/**`). Claude bash-policy enforcement has been removed from generated outputs. The `doctor` command now exposes explicit inspection mode (`sce doctor`) and repair-intent mode (`sce doctor --fix`) at the CLI/help/schema level while keeping diagnosis mode read-only. It now validates both current global operator health and the current repo/hook-integrity slice: state-root resolution, global config path resolution, global and repo-local `sce/config.json` readability/schema validity, Agent Trace local DB path + health, DB parent-directory readiness, git availability, non-repo vs bare-repo targeting failures, effective git hook-path source (default, per-repo `core.hooksPath`, or global `core.hooksPath`), hooks-directory health, required hook presence/executable permissions/content drift against canonical embedded SCE-managed hook assets, and repo-root OpenCode integration presence across the installed `plugins`, `agents`, `commands`, and `skills` inventories with embedded SHA-256 content verification for OpenCode assets. Text mode now renders the approved human-only layout with ordered `Environment` / `Configuration` / `Repository` / `Git Hooks` / `Integrations` sections, `SCE doctor diagnose` / `SCE doctor fix` headers, bracketed `[PASS]`/`[FAIL]`/`[MISS]` status tokens, shared-style green pass plus red fail/miss coloring when color output is enabled, simplified `label (path)` row formatting, top-level-only hook rows, and integration parent/child rows that reflect missing vs content-mismatch states; JSON output remains unchanged. Repo-scoped database reporting is empty by default because no repo-owned SCE database currently exists, while the Agent Trace local DB is reported in default doctor output. Fix mode reuses the canonical setup hook install flow to repair missing/stale/non-executable required hooks and can also bootstrap the missing SCE-owned Agent Trace DB parent directory while preserving manual-only guidance for unsupported issues. The `sync` placeholder performs a local Turso smoke check through a lazily initialized shared tokio current-thread runtime with bounded retry/timeout/backoff controls, then reports a deferred cloud-sync plan from a placeholder gateway contract; persistent local DB schema bootstrap now uses the same bounded resilience wrapper. @@ -43,15 +44,10 @@ The targeted support commands (`handover`, `commit`, `validate`) keep their thin The no-git-wrapper Agent Trace initiative baseline contract is defined in `context/sce/agent-trace-implementation-contract.md`, including normative invariants, compliance matrix, and canonical internal-to-Agent-Trace mapping for downstream implementation tasks. The CLI now includes a task-scoped Agent Trace schema adapter contract in `cli/src/services/agent_trace.rs`, with deterministic mapping of internal attribution input to Agent Trace-shaped record structures documented in `context/sce/agent-trace-schema-adapter.md`. The Agent Trace service now also provides a deterministic payload-builder path (`build_trace_payload`) with AI `model_id` normalization and schema-compliance validation coverage documented in `context/sce/agent-trace-payload-builder-validation.md`. -The hooks service now includes a pre-commit staged checkpoint finalization contract (`finalize_pre_commit_checkpoint`) that enforces staged-only attribution, captures index/tree anchors, and no-ops for disabled/unavailable/bare-repo runtime states; this behavior is documented in `context/sce/agent-trace-pre-commit-staged-checkpoint.md`. -The hooks service now also exposes a `commit-msg` co-author trailer policy (`apply_commit_msg_coauthor_policy`) that conditionally injects exactly one canonical SCE trailer based on `SCE_DISABLED`, `SCE_COAUTHOR_ENABLED`, and staged-attribution presence, with idempotent deduplication behavior documented in `context/sce/agent-trace-commit-msg-coauthor-policy.md`. -The hooks service now also includes a post-commit trace finalization seam (`finalize_post_commit_trace`) that builds canonical Agent Trace payloads, enforces commit-level idempotency guards, persists git notes, and keeps local DB persistence disconnected through a no-op record-store adapter; post-commit runtime still ensures the per-user Agent Trace DB file exists at `.../sce/agent-trace/local.db`, but no schema bootstrap runs before hook execution. This behavior is documented in `context/sce/agent-trace-post-commit-dual-write.md`. +The hooks service now uses a minimal attribution-only runtime: `commit-msg` is the only hook that mutates behavior, conditionally injecting exactly one canonical SCE trailer when the attribution-hooks gate is enabled and `SCE_DISABLED` is false; `pre-commit`, `post-commit`, and `post-rewrite` are deterministic no-op entrypoints. The CLI now also includes an approved operator-environment doctor contract documented in `context/sce/agent-trace-hook-doctor.md`; the runtime now matches the implemented T06 slice for `sce doctor --fix` parsing/help, stable problem/fix-result reporting, canonical hook-repair reuse, and bounded doctor-owned Agent Trace directory bootstrap for the missing SCE-owned DB parent path. -The hooks service now also includes a post-rewrite local remap ingestion seam (`finalize_post_rewrite_remap`) that parses `post-rewrite` old->new SHA pairs, normalizes rewrite method capture, and derives deterministic per-pair idempotency keys before remap dispatch; this behavior is documented in `context/sce/agent-trace-post-rewrite-local-remap-ingestion.md`. -The hooks service now also includes rewrite trace transformation finalization (`finalize_rewrite_trace`) that materializes rewritten-SHA Agent Trace records with `rewrite_from`/`rewrite_method`/`rewrite_confidence` metadata, confidence-threshold quality mapping (`final`/`partial`/`needs_review`), and current notes persistence with retry fallback while local DB writes remain disconnected; this behavior is documented in `context/sce/agent-trace-rewrite-trace-transformation.md`. The local DB service now limits the Agent Trace runtime path to creating/opening the configured per-user database file with no schema bootstrap or trace tables; this behavior is documented in `context/sce/agent-trace-core-schema-migrations.md`. -The hooks service now also includes operational retry-queue replay processing (`process_trace_retry_queue`) invoked from post-commit and post-rewrite runtime flows with bounded same-pass replay and deterministic retry summary output, plus per-attempt runtime/error-class metric emission; this behavior is documented in `context/sce/agent-trace-retry-queue-observability.md`. -The hooks command surface now also supports concrete runtime subcommand routing (`pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`) with deterministic argument/STDIN validation and production post-rewrite runtime wiring (local remap ingestion plus rewritten-trace finalization through git-notes and no-op DB adapters) owned by `cli/src/services/hooks.rs`; this behavior is documented in `context/sce/agent-trace-hooks-command-routing.md`. +The hooks command surface now also supports concrete runtime subcommand routing (`pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`) with deterministic argument/STDIN validation. Current runtime behavior is commit-msg-only attribution and disabled by default: the attribution gate enables canonical trailer insertion in `commit-msg`, while `pre-commit`, `post-commit`, and `post-rewrite` remain deterministic no-ops. This behavior is documented in `context/sce/agent-trace-hooks-command-routing.md`. The setup service now also exposes deterministic required-hook embedded asset accessors (`iter_required_hook_assets`, `get_required_hook_asset`) backed by canonical templates in `cli/assets/hooks/` for `pre-commit`, `commit-msg`, and `post-commit`; this behavior is documented in `context/sce/setup-githooks-hook-asset-packaging.md`. The setup service now also includes required-hook install orchestration (`install_required_git_hooks`) that resolves repository root and effective hooks path from git truth, enforces deterministic per-hook outcomes (`Installed`/`Updated`/`Skipped`), preserves backup-and-restore rollback for non-git-backed targets, and skips backup creation plus backup-based rollback in git-backed repositories with deterministic git-recovery guidance on swap failures; this behavior is documented in `context/sce/setup-githooks-install-flow.md`. The setup command parser/dispatch now also supports composable setup+hooks runs (`sce setup --opencode|--claude|--both --hooks`) plus hooks-only mode (`sce setup --hooks` with optional `--repo `), enforces deterministic compatibility validation (`--repo` requires `--hooks`; target flags remain mutually exclusive), and emits deterministic setup/hook outcome messaging (`installed`/`updated`/`skipped` with backup status); this behavior is documented in `context/sce/setup-githooks-cli-ux.md`. diff --git a/context/patterns.md b/context/patterns.md index 3012f80..f4ea2fc 100644 --- a/context/patterns.md +++ b/context/patterns.md @@ -128,6 +128,7 @@ - Model deferred integration boundaries with concrete event/capability data structures (for example hook-runtime attribution snapshots/policies and cloud-sync checkpoints) so later tasks can implement behavior without reshaping public seams. - For pre-commit attribution finalization seams, keep pending staged and unstaged ranges explicitly separated in input models and finalize from staged ranges only, while carrying index/tree anchors for deterministic commit-time attribution binding. - For commit-msg co-author policy seams, gate canonical trailer insertion on runtime controls (`SCE_DISABLED`, `SCE_COAUTHOR_ENABLED`) plus staged SCE-attribution presence, and enforce idempotent dedupe so allowed cases end with exactly one `Co-authored-by: SCE ` trailer. +- For local hook attribution flows, resolve the top-level enablement gate through the shared config precedence model (`SCE_ATTRIBUTION_HOOKS_ENABLED` over `policies.attribution_hooks.enabled`, default `false`) so commit-msg attribution stays disabled by default without adding hook-specific config parsing. - For post-commit trace finalization seams, treat commit SHA as the idempotency identity, keep git-notes persistence and retry-fallback behavior explicit per target, and avoid assuming local DB writes are active while the local record-store adapter is disconnected. - For retry replay seams, process fallback queue entries in bounded batches, avoid same-pass duplicate trace processing, retry only failed targets, emit per-attempt runtime + persistence error-class metrics for operational visibility, and run a bounded replay pass from production post-commit/post-rewrite hook runtime with deterministic summary output. - For post-rewrite remap ingestion seams, parse ` ` pairs from hook input strictly, ignore empty/no-op self-mapping rows, normalize rewrite method labels to lowercase (`amend`/`rebase` when recognized), and derive deterministic per-pair idempotency keys before dispatching remap requests. diff --git a/context/plans/agent-trace-removal-and-hook-noop-reset.md b/context/plans/agent-trace-removal-and-hook-noop-reset.md index 9f77047..2146d52 100644 --- a/context/plans/agent-trace-removal-and-hook-noop-reset.md +++ b/context/plans/agent-trace-removal-and-hook-noop-reset.md @@ -31,12 +31,15 @@ - Files changed: `cli/src/services/local_db.rs`, `cli/src/services/hooks.rs` - Evidence: `nix run .#pkl-check-generated`; `nix flake check`; `nix develop -c sh -c 'cd cli && cargo check'` -- [ ] T02: Keep attribution-only hooks behind a disabled-by-default gate (status:todo) +- [x] T02: Keep attribution-only hooks behind a disabled-by-default gate (status:done) - Task ID: T02 - Goal: Keep the hook-trigger surface present while removing trace persistence, retry, rewrite handling, and other Agent Trace side effects; preserve attribution-only behavior behind a config/env gate that defaults to disabled. - Boundaries (in/out of scope): In scope: hook runtime entrypoints, config/env resolution, disabled-default no-op behavior, and enabled-path attribution behavior that does not depend on trace infrastructure. Out of scope: future v0.3.0 tracing redesign, new persistence behavior, and unrelated CLI command-surface redesign. - Done when: Hook commands/installed triggers remain invocable; default behavior is disabled/no-op; enabling the new config/env gate restores attribution-only behavior; no trace-related side effect runs in either mode; the new gate resolves through the existing precedence model and is documented in code/tests. - Verification notes (commands or checks): Inspect hook runtime and config resolution paths for disabled-default no-op behavior plus enabled attribution-only behavior; run targeted hook/config tests where available; include full repo validation in the final task. + - Completed: 2026-04-08 + - Files changed: `cli/src/services/config.rs`, `cli/src/services/hooks.rs`, `config/pkl/base/sce-config-schema.pkl`, `config/schema/sce-config.schema.json` + - Evidence: `nix run .#pkl-check-generated`; `nix flake check`; `nix develop -c sh -c 'cd cli && cargo check'` - [ ] T03: Remove Agent Trace-specific command, doctor, setup, and path-surface behavior (status:todo) - Task ID: T03 diff --git a/context/sce/agent-trace-commit-msg-coauthor-policy.md b/context/sce/agent-trace-commit-msg-coauthor-policy.md index 3128098..4262bd1 100644 --- a/context/sce/agent-trace-commit-msg-coauthor-policy.md +++ b/context/sce/agent-trace-commit-msg-coauthor-policy.md @@ -11,13 +11,11 @@ - Runtime entrypoint: `cli/src/services/hooks.rs` -> `run_commit_msg_subcommand` / `run_commit_msg_subcommand_in_repo`. - Canonical trailer string: `Co-authored-by: SCE `. - Runtime gating conditions: + - `attribution_hooks_enabled = true` - `sce_disabled = false` - - `sce_coauthor_enabled = true` - - `has_staged_sce_attribution = true` - Runtime gate source mapping: + - `attribution_hooks_enabled` resolves from env `SCE_ATTRIBUTION_HOOKS_ENABLED` over config key `policies.attribution_hooks.enabled`, default `false`. - `sce_disabled` resolves from `SCE_DISABLED` truthy evaluation. - - `sce_coauthor_enabled` resolves from `SCE_COAUTHOR_ENABLED` with enabled-by-default semantics. - - `has_staged_sce_attribution` resolves from staged pre-commit checkpoint artifact content only when at least one file has both non-empty `ranges[]` and `has_sce_attribution = true`. - When all gate conditions pass, output commit message MUST contain exactly one canonical SCE trailer. - When any gate condition fails, commit message is returned unchanged. @@ -28,10 +26,7 @@ - Existing trailing newline is preserved when present. - Commit-msg runtime writes the file only when policy gates pass and transformed content differs from original content. - Human author/committer identity is not rewritten; only commit message trailer content is affected. -- Missing or `false` `has_sce_attribution` markers fail the gate even when staged ranges are present, so generic human-only staged diffs do not trigger trailer insertion. - - **TEMPORARY (v0.1.x)**: Currently defaults to `true` for all staged files (see TODO(0.3.0) in `cli/src/services/hooks.rs:collect_pending_checkpoint`). - - **PLANNED (v0.3.0)**: Will default to `false` and require explicit attribution marking. -- The positive path remains explicit-marker driven: commit-msg appends the canonical trailer when an attribution-aware checkpoint producer marks staged ranges as SCE-attributed. +- The current positive path is gate-driven only: when attribution hooks are enabled, `commit-msg` appends the canonical trailer without depending on checkpoint files or other helper state. ## Verification evidence - `nix flake check` diff --git a/context/sce/agent-trace-hooks-command-routing.md b/context/sce/agent-trace-hooks-command-routing.md index ba24005..432134f 100644 --- a/context/sce/agent-trace-hooks-command-routing.md +++ b/context/sce/agent-trace-hooks-command-routing.md @@ -1,32 +1,37 @@ # Agent Trace Hooks Command Routing ## Scope -- Plan: `agent-trace-local-hooks-production-mvp` -- Tasks: `T02`, `T07` -- Focus: implemented `sce hooks` subcommand routing plus production post-rewrite runtime wiring. +- Current trace-removal baseline for `cli/src/services/hooks.rs` +- Focus: concrete `sce hooks` subcommand routing plus current minimal runtime behavior ## Implemented command surface - `sce hooks pre-commit` - `sce hooks commit-msg ` - `sce hooks post-commit` -- `sce hooks post-rewrite ` (reads rewrite pairs from STDIN) +- `sce hooks post-rewrite ` ## Parser and dispatch behavior -- `cli/src/app.rs` routes `hooks` through dedicated hook-subcommand parsing instead of generic no-arg subcommand parsing. -- `cli/src/services/hooks.rs` now owns hook CLI usage text, deterministic parse errors, and runtime dispatch through `HookSubcommand` + `run_hooks_subcommand`. -- Placeholder hook-event/generated-region scaffolding has been removed from production hook modules; local hook runtime behavior is driven only by production entrypoint/finalization seams. +- `cli/src/app.rs` routes `hooks` through dedicated hook-subcommand parsing. +- `cli/src/services/hooks.rs` owns deterministic runtime dispatch through `HookSubcommand` + `run_hooks_subcommand`. - Invalid and ambiguous invocations return deterministic actionable errors pointing to `sce hooks --help`. -## Current runtime entrypoint behavior -- `pre-commit`: executes the pre-commit runtime entrypoint and reports staged-checkpoint finalization outcome. -- `commit-msg`: validates ``, resolves runtime gates (`SCE_DISABLED`, `SCE_COAUTHOR_ENABLED`, staged checkpoint presence), applies canonical co-author policy, and writes back only when trailer mutation is required. -- `post-commit`: resolves runtime guards, builds commit attribution input from git + pre-commit checkpoint artifacts, executes `finalize_post_commit_trace`, writes canonical note payloads to `refs/notes/agent-trace`, ensures persistent local DB readiness (`.../sce/agent-trace/local.db`) with migrations before write attempts, persists trace records to local Turso-backed tables, maintains commit-level emission ledger (`sce/trace-emission-ledger.txt`), and enqueues fallback entries (`sce/trace-retry-queue.jsonl`) when a persistence target fails. -- `post-rewrite`: resolves runtime guards, ensures persistence paths are available, ingests parsed rewrite remap pairs into local DB-backed `rewrite_mappings` with deterministic idempotency, and runs rewritten-trace finalization (`finalize_rewrite_trace`) per accepted remap using canonical notes + DB writers, shared emission ledger, and retry queue adapters. -- `post-rewrite` output now reports both remap ingestion counters and rewrite trace finalization counters (`persisted`, `queued`, `no_op`, `failed`) for deterministic operator diagnostics. +## Current runtime behavior +- Shared enablement gate: + - env `SCE_ATTRIBUTION_HOOKS_ENABLED` + - config `policies.attribution_hooks.enabled` + - precedence: env over config file + - default: disabled +- `commit-msg` is the only active attribution path. + - Reads the message file as UTF-8. + - Applies exactly one canonical trailer: `Co-authored-by: SCE `. + - Writes back only when the attribution gate is enabled, `SCE_DISABLED` is false, and the transformed content differs. +- `pre-commit` is a deterministic no-op entrypoint. +- `post-commit` is a deterministic no-op entrypoint. +- `post-rewrite` is a deterministic no-op entrypoint. -## Notes for next tasks -- T02 established routing and invocation contracts. -- T03 implemented pre-commit staged-checkpoint runtime wiring. -- T04 implemented commit-msg file IO mutation wiring to canonical co-author policy. -- T05 implemented post-commit persistence adapters and runtime wiring. -- T07 implemented production post-rewrite runtime orchestration (remap ingestion + rewritten trace emission). +## Explicit non-goals in the current baseline +- No checkpoint handoff file +- No git-notes persistence +- No local DB persistence +- No retry queue replay +- No rewrite remap ingestion diff --git a/context/sce/agent-trace-post-commit-dual-write.md b/context/sce/agent-trace-post-commit-dual-write.md index 3040891..3210fc2 100644 --- a/context/sce/agent-trace-post-commit-dual-write.md +++ b/context/sce/agent-trace-post-commit-dual-write.md @@ -1,5 +1,9 @@ # Agent Trace post-commit persistence baseline +## Current status +- This contract is no longer active in runtime. +- The current `cli/src/services/hooks.rs` keeps `sce hooks post-commit` as a deterministic no-op. + ## Status - Plan: `agent-trace-attribution-no-git-wrapper` - Task: `T06` @@ -7,8 +11,11 @@ ## Canonical contract - Policy entrypoint: `cli/src/services/hooks.rs` -> `finalize_post_commit_trace`. +- Runtime entrypoint status: retained but not active in the current trace-removal baseline. - Runtime no-op guards: - `sce_disabled = true` -> `NoOp(Disabled)` + - `attribution_hooks_enabled = false` -> `NoOp(AttributionDisabled)` + - `trace_side_effects_enabled = false` -> `NoOp(AttributionOnlyMode)` - `cli_available = false` -> `NoOp(CliUnavailable)` - `is_bare_repo = true` -> `NoOp(BareRepository)` - Idempotency guard: `TraceEmissionLedger::has_emitted(commit_sha)` short-circuits to `NoOp(AlreadyFinalized)`. @@ -27,8 +34,11 @@ - Any failed target (`PersistenceWriteResult::Failed`) enqueues one retry item via `TraceRetryQueue` with explicit failed target list and returns `QueuedFallback`. - Retry queue entries carry the full trace record, MIME type, notes ref, and failed target list to support replay-safe recovery. -## Local hook runtime adapter wiring +## Retained runtime wiring details - Runtime entrypoint: `cli/src/services/hooks.rs` -> `run_post_commit_subcommand_in_repo`. +- Current runtime posture: + - `post-commit` remains invocable but exits through deterministic no-op output before invoking trace persistence behavior. + - Enabling the attribution-hooks gate does not reactivate notes writes, local DB writes, retry replay, or emission-ledger mutation. - Runtime input assembly: - resolves `HEAD` + optional `HEAD^` via git - derives commit timestamp from `git show -s --format=%cI HEAD` @@ -39,9 +49,7 @@ - local record store adapter: `NoOpTraceRecordStore` disconnects local DB trace persistence while keeping the finalize path compile-safe - emission ledger adapter: `FileTraceEmissionLedger` stores emitted commit SHAs at `sce/trace-emission-ledger.txt` - retry queue adapter: `JsonFileTraceRetryQueue` appends failed-target fallback entries to `sce/trace-retry-queue.jsonl` -- Runtime DB-file bootstrap remains mandatory before post-commit execution: - - `resolve_post_commit_runtime_paths` calls `ensure_agent_trace_local_db_ready_blocking`. - - `ensure_agent_trace_local_db_ready_blocking` resolves platform state-data DB path (`${XDG_STATE_HOME:-~/.local/state}/sce/agent-trace/local.db` on Linux, platform-equivalent user state root elsewhere), creates parent directories, and opens/creates the DB file without applying schema migrations. +- If later runtime work reactivates this path, `resolve_post_commit_runtime_paths` still points at the empty-file/open-only Agent Trace DB baseline rather than any schema-backed trace store. - Runtime posture remains fail-open: operational errors return deterministic skip/fallback messages instead of aborting commit progression. ## Verification evidence diff --git a/context/sce/agent-trace-post-rewrite-local-remap-ingestion.md b/context/sce/agent-trace-post-rewrite-local-remap-ingestion.md index fcde428..a8c1ffd 100644 --- a/context/sce/agent-trace-post-rewrite-local-remap-ingestion.md +++ b/context/sce/agent-trace-post-rewrite-local-remap-ingestion.md @@ -1,5 +1,9 @@ # Agent Trace Post-Rewrite Local Remap Ingestion (T08) +## Current status +- This contract is no longer active in runtime. +- The current `cli/src/services/hooks.rs` keeps `sce hooks post-rewrite` as a deterministic no-op. + ## Status - Plan: `agent-trace-attribution-no-git-wrapper` - Task: `T08` @@ -15,6 +19,8 @@ `finalize_post_rewrite_remap` returns `NoOp` and performs no ingestion when any of these guards apply: - `sce_disabled = true` +- `attribution_hooks_enabled = false` +- `trace_side_effects_enabled = false` - `cli_available = false` - `is_bare_repo = true` @@ -55,7 +61,7 @@ ## Tests added -- No-op behavior when SCE is disabled. +- No-op behavior when SCE is disabled, attribution hooks are disabled, or attribution-only mode is active. - Amend-pair ingestion with deterministic idempotency-key derivation. - Rebase duplicate replay behavior (second identical pair skipped). - Strict malformed-line rejection (` ` required). diff --git a/context/sce/agent-trace-pre-commit-staged-checkpoint.md b/context/sce/agent-trace-pre-commit-staged-checkpoint.md index 056c2e8..d124e99 100644 --- a/context/sce/agent-trace-pre-commit-staged-checkpoint.md +++ b/context/sce/agent-trace-pre-commit-staged-checkpoint.md @@ -1,5 +1,9 @@ # Agent Trace Pre-commit Staged Checkpoint +## Current status + +This contract is no longer active. The current `cli/src/services/hooks.rs` runtime keeps `sce hooks pre-commit` as a deterministic no-op and does not persist checkpoint artifacts. + ## Scope Task `agent-trace-attribution-no-git-wrapper` `T04` adds a pre-commit finalization contract that filters pending attribution to staged content only and preserves index/tree anchors for deterministic commit-time binding. @@ -11,10 +15,12 @@ Task `agent-trace-attribution-no-git-wrapper` `T04` adds a pre-commit finalizati - Runtime hook entrypoint: `run_pre_commit_subcommand` -> `run_pre_commit_subcommand_in_repo(repository_root)`. - Runtime no-op guards: - `sce_disabled = true` -> `NoOp(Disabled)`. + - `attribution_hooks_enabled = false` -> `NoOp(AttributionDisabled)`. - `cli_available = false` -> `NoOp(CliUnavailable)`. - `is_bare_repo = true` -> `NoOp(BareRepository)`. - Runtime state resolution: - `SCE_DISABLED` truthy env values (`1`, `true`, `yes`, `on`) set disabled mode. + - `SCE_ATTRIBUTION_HOOKS_ENABLED` overrides config key `policies.attribution_hooks.enabled` and defaults to disabled. - CLI availability checks `git --version` in the repository context. - Bare-repository guard uses `git rev-parse --is-bare-repository`. - Staged-only enforcement: @@ -43,5 +49,5 @@ Task `agent-trace-attribution-no-git-wrapper` `T04` adds a pre-commit finalizati ## Verification coverage - Mixed staged/unstaged fixture test confirms unstaged ranges are excluded and anchor values are preserved. -- Guard-path tests cover disabled, missing CLI, and bare-repository no-op behavior. +- Guard-path tests cover disabled, attribution-disabled, missing CLI, and bare-repository no-op behavior. - Runtime fixture test validates persisted pre-commit checkpoint artifact contains staged-only ranges when both staged and unstaged edits exist for the same file. diff --git a/context/sce/agent-trace-retry-queue-observability.md b/context/sce/agent-trace-retry-queue-observability.md index 812db2e..641c23d 100644 --- a/context/sce/agent-trace-retry-queue-observability.md +++ b/context/sce/agent-trace-retry-queue-observability.md @@ -1,5 +1,9 @@ # Agent Trace retry queue and observability metrics +## Current status +- This contract is no longer active in local hook runtime. +- The current `cli/src/services/hooks.rs` no longer runs retry replay from hook entrypoints. + ## Status - Plan: `agent-trace-attribution-no-git-wrapper` - Task: `T14` @@ -7,10 +11,10 @@ ## Canonical contract - Retry processing entrypoint: `cli/src/services/hooks.rs` -> `process_trace_retry_queue`. -- Production runtime invocation now runs after both `sce hooks post-commit` and `sce hooks post-rewrite` finalization paths through `process_runtime_retry_queue`. +- The retained `process_runtime_retry_queue` wrapper is not invoked by the current trace-removal runtime baseline; `sce hooks post-commit` and `sce hooks post-rewrite` now no-op before retry replay. - Queue contract now supports dequeue + enqueue replay via `TraceRetryQueue::{dequeue_next, enqueue}`. - Retry pass processes up to `max_items` entries per invocation and avoids same-pass duplicate processing for the same trace ID. -- Runtime retry passes currently use a bounded `max_items = 16` per hook invocation. +- If runtime invocation is re-enabled later, the retained wrapper still uses bounded `max_items = 16` replay. - Recovery write behavior is target-scoped: - Failed notes target retries through `TraceNotesWriter`. - Failed DB target retries through `TraceRecordStore` using metadata idempotency key (`dev.crocoder.sce.idempotency_key`) when present. @@ -20,7 +24,7 @@ - runtime histogram input (`runtime_ms`) - `error_class` (from `PersistenceFailure.class` when writes fail) - remaining failed targets. -- Hook command output now includes deterministic retry observability summary text: attempted/recovered/requeued counts plus transient/permanent failure counts. +- The retained runtime wrapper still formats deterministic retry observability summary text, but current hook command output does not surface it because retry replay is not invoked. ## Persistence schema additions - `cli/src/services/local_db.rs` core migrations now include: diff --git a/context/sce/agent-trace-rewrite-trace-transformation.md b/context/sce/agent-trace-rewrite-trace-transformation.md index 10944f7..1c6f47f 100644 --- a/context/sce/agent-trace-rewrite-trace-transformation.md +++ b/context/sce/agent-trace-rewrite-trace-transformation.md @@ -1,5 +1,9 @@ # Agent Trace Rewrite Trace Transformation (T09) +## Current status +- This contract is no longer active in runtime. +- The current `cli/src/services/hooks.rs` keeps `sce hooks post-rewrite` as a deterministic no-op. + ## Status - Plan: `agent-trace-attribution-no-git-wrapper` - Task: `T09` @@ -15,6 +19,7 @@ `finalize_rewrite_trace` returns `NoOp` without persistence when any guard applies: - `sce_disabled = true` +- `trace_side_effects_enabled = false` - `cli_available = false` - `is_bare_repo = true` - rewritten commit SHA is already marked emitted in `TraceEmissionLedger` @@ -57,4 +62,4 @@ - Metadata integrity and current notes/no-op-DB persistence behavior for rewritten traces. - Confidence-threshold quality mapping (`final`, `partial`, `needs_review`). - Confidence range validation errors for out-of-range input. -- No-op behavior when rewritten commit was already finalized. +- No-op behavior when attribution-only mode is active or the rewritten commit was already finalized. From 60605bd850c11128d9d51f4f09a998a4c9f6e3df Mon Sep 17 00:00:00 2001 From: David Abram Date: Wed, 8 Apr 2026 16:53:12 +0200 Subject: [PATCH 3/6] cli: Remove trace command and rename Agent Trace DB to local DB --- cli/src/app.rs | 29 -- cli/src/cli_schema.rs | 22 +- cli/src/command_surface.rs | 8 +- cli/src/services/default_paths.rs | 23 +- cli/src/services/doctor.rs | 137 +++---- cli/src/services/local_db.rs | 46 +-- cli/src/services/mod.rs | 1 - cli/src/services/trace.rs | 358 ------------------ context/architecture.md | 9 +- context/cli/cli-command-surface.md | 28 +- context/cli/default-path-catalog.md | 10 +- context/context-map.md | 6 +- context/glossary.md | 11 +- context/overview.md | 14 +- context/patterns.md | 2 +- ...agent-trace-removal-and-hook-noop-reset.md | 5 +- .../sce/agent-trace-core-schema-migrations.md | 12 +- context/sce/agent-trace-hook-doctor.md | 16 +- 18 files changed, 124 insertions(+), 613 deletions(-) delete mode 100644 cli/src/services/trace.rs diff --git a/cli/src/app.rs b/cli/src/app.rs index b0edf8d..23e0f48 100644 --- a/cli/src/app.rs +++ b/cli/src/app.rs @@ -15,7 +15,6 @@ enum Command { Setup(services::setup::SetupRequest), Doctor(services::doctor::DoctorRequest), Hooks(services::hooks::HookSubcommand), - Trace(services::trace::TraceRequest), Version(services::version::VersionRequest), } @@ -30,7 +29,6 @@ impl Command { Self::Setup(_) => services::setup::NAME, Self::Doctor(_) => services::doctor::NAME, Self::Hooks(_) => services::hooks::NAME, - Self::Trace(_) => services::trace::NAME, Self::Version(_) => services::version::NAME, } } @@ -461,7 +459,6 @@ fn convert_clap_command(command: cli_schema::Commands) -> Result convert_hooks_subcommand(subcommand), - cli_schema::Commands::Trace { subcommand } => convert_trace_subcommand(subcommand), cli_schema::Commands::Version { format } => { Ok(Command::Version(services::version::VersionRequest { format: convert_output_format(format), @@ -617,30 +614,6 @@ fn convert_hooks_subcommand( } } -#[allow(clippy::unnecessary_wraps)] -fn convert_trace_subcommand( - subcommand: cli_schema::TraceSubcommand, -) -> Result { - match subcommand { - cli_schema::TraceSubcommand::Prompts { - commit_sha, - format, - json, - } => Ok(Command::Trace(services::trace::TraceRequest { - subcommand: services::trace::TraceSubcommand::Prompts( - services::trace::TracePromptsRequest { - commit_sha, - format: if json { - services::trace::TraceFormat::Json - } else { - convert_output_format(format) - }, - }, - ), - })), - } -} - fn dispatch( command: &Command, _logger: &services::observability::Logger, @@ -708,8 +681,6 @@ fn dispatch( .map_err(|error| ClassifiedError::runtime(error.to_string())), Command::Hooks(subcommand) => services::hooks::run_hooks_subcommand(subcommand.clone()) .map_err(|error| ClassifiedError::runtime(error.to_string())), - Command::Trace(request) => services::trace::run_trace_subcommand(request.clone()) - .map_err(|error| ClassifiedError::runtime(error.to_string())), Command::Version(request) => services::version::render_version(*request) .map_err(|error| ClassifiedError::runtime(error.to_string())), } diff --git a/cli/src/cli_schema.rs b/cli/src/cli_schema.rs index bfb569a..70db529 100644 --- a/cli/src/cli_schema.rs +++ b/cli/src/cli_schema.rs @@ -99,18 +99,12 @@ pub enum Commands { format: OutputFormat, }, - #[command(about = "Run git-hook runtime entrypoints for local Agent Trace flows")] + #[command(about = "Run attribution-only git hooks (disabled by default)")] Hooks { #[command(subcommand)] subcommand: HooksSubcommand, }, - #[command(about = "Inspect persisted Agent Trace records and prompt captures")] - Trace { - #[command(subcommand)] - subcommand: TraceSubcommand, - }, - #[command(about = "Print deterministic runtime version metadata")] Version { #[arg(long, value_enum, default_value_t = OutputFormat::Text)] @@ -202,20 +196,6 @@ pub enum HooksSubcommand { PostRewrite { rewrite_method: String }, } -#[derive(Subcommand, Debug, Clone, PartialEq, Eq)] -pub enum TraceSubcommand { - #[command(about = "Show captured prompts for a persisted commit trace")] - Prompts { - commit_sha: String, - - #[arg(long, value_enum, default_value_t = OutputFormat::Text, conflicts_with = "json")] - format: OutputFormat, - - #[arg(long, conflicts_with = "format")] - json: bool, - }, -} - #[derive(ValueEnum, Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum OutputFormat { #[default] diff --git a/cli/src/command_surface.rs b/cli/src/command_surface.rs index 18f99e1..86c4c57 100644 --- a/cli/src/command_surface.rs +++ b/cli/src/command_surface.rs @@ -50,13 +50,7 @@ pub const COMMANDS: &[CommandContract] = &[ CommandContract { name: services::hooks::NAME, status: ImplementationStatus::Implemented, - purpose: "Run git-hook runtime entrypoints for local Agent Trace flows", - show_in_top_level_help: false, - }, - CommandContract { - name: services::trace::NAME, - status: ImplementationStatus::Implemented, - purpose: "Inspect persisted Agent Trace records and captured prompts", + purpose: "Run attribution-only git hooks (disabled by default)", show_in_top_level_help: false, }, CommandContract { diff --git a/cli/src/services/default_paths.rs b/cli/src/services/default_paths.rs index 8273120..715ddcd 100644 --- a/cli/src/services/default_paths.rs +++ b/cli/src/services/default_paths.rs @@ -78,7 +78,7 @@ pub(crate) enum PersistedArtifactRootKind { pub(crate) enum PersistedArtifactId { GlobalConfig, AuthTokens, - AgentTraceLocalDb, + LocalDb, } #[cfg_attr(not(test), allow(dead_code))] @@ -106,12 +106,8 @@ impl SceDefaultLocations { .join("tokens.json") } - pub(crate) fn agent_trace_local_db(&self) -> PathBuf { - self.roots - .state_root() - .join("sce") - .join("agent-trace") - .join("local.db") + pub(crate) fn local_db(&self) -> PathBuf { + self.roots.state_root().join("sce").join("local.db") } #[cfg_attr(not(test), allow(dead_code))] @@ -128,9 +124,9 @@ impl SceDefaultLocations { path: self.auth_tokens_file(), }, PersistedArtifactLocation { - id: PersistedArtifactId::AgentTraceLocalDb, + id: PersistedArtifactId::LocalDb, root_kind: PersistedArtifactRootKind::State, - path: self.agent_trace_local_db(), + path: self.local_db(), }, ] } @@ -160,15 +156,6 @@ pub(crate) mod hook_dir { pub const POST_COMMIT: &str = "post-commit"; } -#[cfg_attr(not(test), allow(dead_code))] -pub(crate) mod git_relative_path { - pub const SCE_DIR: &str = "sce"; - pub const PRE_COMMIT_CHECKPOINT: &str = "sce/pre-commit-checkpoint.json"; - pub const PROMPTS: &str = "sce/prompts.jsonl"; - pub const TRACE_RETRY_QUEUE: &str = "sce/trace-retry-queue.jsonl"; - pub const TRACE_EMISSION_LEDGER: &str = "sce/trace-emission-ledger.txt"; -} - #[cfg_attr(not(test), allow(dead_code))] pub(crate) mod embedded_asset_root { pub const GENERATED_CONFIG: &str = "assets/generated/config"; diff --git a/cli/src/services/doctor.rs b/cli/src/services/doctor.rs index 52a29c9..262701d 100644 --- a/cli/src/services/doctor.rs +++ b/cli/src/services/doctor.rs @@ -84,7 +84,7 @@ struct FileLocationHealth { struct GlobalStateHealth { state_root: Option, config_locations: Vec, - agent_trace_local_db: Option, + local_db: Option, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -96,7 +96,7 @@ struct HookDoctorReport { hook_path_source: HookPathSource, hooks_directory: Option, config_locations: Vec, - agent_trace_local_db: Option, + local_db: Option, hooks: Vec, integration_groups: Vec, problems: Vec, @@ -154,13 +154,13 @@ enum ProblemKind { GlobalConfigValidationFailed, UnableToResolveGlobalConfigPath, LocalConfigValidationFailed, - UnableToResolveAgentTraceLocalDbPath, - AgentTraceLocalDbPathHasNoParent, - AgentTraceLocalDbParentMissing, - AgentTraceLocalDbParentNotDirectory, - AgentTraceLocalDbParentNotWritable, - AgentTraceLocalDbParentInspectionFailed, - AgentTraceLocalDbHealthCheckFailed, + UnableToResolveLocalDbPath, + LocalDbPathHasNoParent, + LocalDbParentMissing, + LocalDbParentNotDirectory, + LocalDbParentNotWritable, + LocalDbParentInspectionFailed, + LocalDbHealthCheckFailed, HooksDirectoryMissing, HooksPathNotDirectory, RequiredHookMissing, @@ -205,9 +205,9 @@ struct DoctorDependencies<'a> { check_git_available: &'a dyn Fn() -> bool, resolve_state_root: &'a dyn Fn() -> Result, resolve_global_config_path: &'a dyn Fn() -> Result, - resolve_agent_trace_local_db_path: &'a dyn Fn() -> Result, + resolve_local_db_path: &'a dyn Fn() -> Result, validate_config_file: &'a dyn Fn(&Path) -> Result<()>, - check_agent_trace_local_db_health: &'a dyn Fn(&Path) -> Result<()>, + check_local_db_health: &'a dyn Fn(&Path) -> Result<()>, install_required_git_hooks: &'a dyn Fn(&Path) -> Result, create_directory_all: &'a dyn Fn(&Path) -> Result<()>, } @@ -250,11 +250,9 @@ fn execute_doctor(request: DoctorRequest, repository_root: &Path) -> DoctorExecu resolve_global_config_path: &|| { Ok(resolve_sce_default_locations()?.global_config_file()) }, - resolve_agent_trace_local_db_path: - &crate::services::local_db::resolve_agent_trace_local_db_path, + resolve_local_db_path: &crate::services::local_db::resolve_local_db_path, validate_config_file: &crate::services::config::validate_config_file, - check_agent_trace_local_db_health: - &crate::services::local_db::check_agent_trace_local_db_health_blocking, + check_local_db_health: &crate::services::local_db::check_local_db_health_blocking, install_required_git_hooks: &install_required_git_hooks, create_directory_all: &create_directory_all, }, @@ -425,7 +423,7 @@ fn build_report_with_dependencies( hook_path_source, hooks_directory, config_locations: global_state.config_locations, - agent_trace_local_db: global_state.agent_trace_local_db, + local_db: global_state.local_db, hooks, integration_groups, problems, @@ -528,23 +526,23 @@ fn collect_global_state_health( path: local_path, }); - let agent_trace_local_db = match (dependencies.resolve_agent_trace_local_db_path)() { + let local_db = match (dependencies.resolve_local_db_path)() { Ok(path) => { let health = FileLocationHealth { - label: "Agent Trace local DB", + label: "Local DB", state: if path.exists() { "present" } else { "expected" }, path, }; - inspect_agent_trace_db_health(&health, problems, dependencies); + inspect_local_db_health(&health, problems, dependencies); Some(health) } Err(error) => { problems.push(DoctorProblem { - kind: ProblemKind::UnableToResolveAgentTraceLocalDbPath, + kind: ProblemKind::UnableToResolveLocalDbPath, category: ProblemCategory::GlobalState, severity: ProblemSeverity::Error, fixability: ProblemFixability::ManualOnly, - summary: format!("Unable to resolve expected Agent Trace local DB path: {error}"), + summary: format!("Unable to resolve expected local DB path: {error}"), remediation: String::from("Verify that the SCE state root can be resolved on this machine before rerunning 'sce doctor'."), next_action: "manual_steps", }); @@ -555,25 +553,22 @@ fn collect_global_state_health( GlobalStateHealth { state_root: state_root_health, config_locations, - agent_trace_local_db, + local_db, } } -fn inspect_agent_trace_db_health( +fn inspect_local_db_health( db_health: &FileLocationHealth, problems: &mut Vec, dependencies: &DoctorDependencies<'_>, ) { let Some(parent) = db_health.path.parent() else { problems.push(DoctorProblem { - kind: ProblemKind::AgentTraceLocalDbPathHasNoParent, + kind: ProblemKind::LocalDbPathHasNoParent, category: ProblemCategory::GlobalState, severity: ProblemSeverity::Error, fixability: ProblemFixability::ManualOnly, - summary: format!( - "Agent Trace local DB path '{}' has no parent directory.", - db_health.path.display() - ), + summary: format!("Local DB path '{}' has no parent directory.", db_health.path.display()), remediation: String::from("Verify that the SCE state root resolves to a normal filesystem path before rerunning 'sce doctor'."), next_action: "manual_steps", }); @@ -583,29 +578,23 @@ fn inspect_agent_trace_db_health( match inspect_directory_write_readiness(parent) { DirectoryWriteReadiness::Ready => {} DirectoryWriteReadiness::Missing => problems.push(DoctorProblem { - kind: ProblemKind::AgentTraceLocalDbParentMissing, + kind: ProblemKind::LocalDbParentMissing, category: ProblemCategory::FilesystemPermissions, severity: ProblemSeverity::Error, fixability: ProblemFixability::AutoFixable, - summary: format!( - "Agent Trace local DB parent directory '{}' does not exist.", - parent.display() - ), + summary: format!("Local DB parent directory '{}' does not exist.", parent.display()), remediation: format!( - "Run 'sce doctor --fix' to create the SCE-owned Agent Trace state directory '{}', or create it manually with write access and rerun 'sce doctor'.", + "Run 'sce doctor --fix' to create the SCE-owned state directory '{}', or create it manually with write access and rerun 'sce doctor'.", parent.display() ), next_action: "doctor_fix", }), DirectoryWriteReadiness::NotDirectory => problems.push(DoctorProblem { - kind: ProblemKind::AgentTraceLocalDbParentNotDirectory, + kind: ProblemKind::LocalDbParentNotDirectory, category: ProblemCategory::FilesystemPermissions, severity: ProblemSeverity::Error, fixability: ProblemFixability::ManualOnly, - summary: format!( - "Agent Trace local DB parent path '{}' is not a directory.", - parent.display() - ), + summary: format!("Local DB parent path '{}' is not a directory.", parent.display()), remediation: format!( "Replace '{}' with a writable directory before rerunning 'sce doctor'.", parent.display() @@ -613,14 +602,11 @@ fn inspect_agent_trace_db_health( next_action: "manual_steps", }), DirectoryWriteReadiness::ReadOnly => problems.push(DoctorProblem { - kind: ProblemKind::AgentTraceLocalDbParentNotWritable, + kind: ProblemKind::LocalDbParentNotWritable, category: ProblemCategory::FilesystemPermissions, severity: ProblemSeverity::Error, fixability: ProblemFixability::ManualOnly, - summary: format!( - "Agent Trace local DB parent directory '{}' is not writable.", - parent.display() - ), + summary: format!("Local DB parent directory '{}' is not writable.", parent.display()), remediation: format!( "Grant write access to '{}' before rerunning 'sce doctor'.", parent.display() @@ -628,14 +614,11 @@ fn inspect_agent_trace_db_health( next_action: "manual_steps", }), DirectoryWriteReadiness::Unknown(error) => problems.push(DoctorProblem { - kind: ProblemKind::AgentTraceLocalDbParentInspectionFailed, + kind: ProblemKind::LocalDbParentInspectionFailed, category: ProblemCategory::FilesystemPermissions, severity: ProblemSeverity::Error, fixability: ProblemFixability::ManualOnly, - summary: format!( - "Unable to inspect Agent Trace local DB parent directory '{}': {error}", - parent.display() - ), + summary: format!("Unable to inspect local DB parent directory '{}': {error}", parent.display()), remediation: format!( "Verify that '{}' is accessible and writable before rerunning 'sce doctor'.", parent.display() @@ -645,18 +628,18 @@ fn inspect_agent_trace_db_health( } if db_health.path.exists() { - if let Err(error) = (dependencies.check_agent_trace_local_db_health)(&db_health.path) { + if let Err(error) = (dependencies.check_local_db_health)(&db_health.path) { problems.push(DoctorProblem { - kind: ProblemKind::AgentTraceLocalDbHealthCheckFailed, + kind: ProblemKind::LocalDbHealthCheckFailed, category: ProblemCategory::GlobalState, severity: ProblemSeverity::Error, fixability: ProblemFixability::ManualOnly, summary: format!( - "Agent Trace local DB '{}' failed health checks: {error}", + "Local DB '{}' failed health checks: {error}", db_health.path.display() ), remediation: format!( - "Repair or replace the Agent Trace local DB at '{}' and rerun 'sce doctor'.", + "Repair or replace the local DB at '{}' and rerun 'sce doctor'.", db_health.path.display() ), next_action: "manual_steps", @@ -1253,9 +1236,9 @@ fn format_report_with_color_policy(report: &HookDoctorReport, color_enabled: boo )); lines.push(format_human_text_row( color_enabled, - agent_trace_local_db_status(report), - "Agent Trace local DB", - report.agent_trace_local_db.as_ref().map_or_else( + local_db_status(report), + "Local DB", + report.local_db.as_ref().map_or_else( || String::from("not detected"), |location| location.path.display().to_string(), ), @@ -1410,18 +1393,18 @@ fn state_root_status(report: &HookDoctorReport) -> HumanTextStatus { } } -fn agent_trace_local_db_status(report: &HookDoctorReport) -> HumanTextStatus { - if report.agent_trace_local_db.is_none() +fn local_db_status(report: &HookDoctorReport) -> HumanTextStatus { + if report.local_db.is_none() || report.problems.iter().any(|problem| { matches!( problem.kind, - ProblemKind::UnableToResolveAgentTraceLocalDbPath - | ProblemKind::AgentTraceLocalDbPathHasNoParent - | ProblemKind::AgentTraceLocalDbParentMissing - | ProblemKind::AgentTraceLocalDbParentNotDirectory - | ProblemKind::AgentTraceLocalDbParentNotWritable - | ProblemKind::AgentTraceLocalDbParentInspectionFailed - | ProblemKind::AgentTraceLocalDbHealthCheckFailed + ProblemKind::UnableToResolveLocalDbPath + | ProblemKind::LocalDbPathHasNoParent + | ProblemKind::LocalDbParentMissing + | ProblemKind::LocalDbParentNotDirectory + | ProblemKind::LocalDbParentNotWritable + | ProblemKind::LocalDbParentInspectionFailed + | ProblemKind::LocalDbHealthCheckFailed ) }) { @@ -1656,7 +1639,7 @@ fn render_report_json(execution: &DoctorExecution) -> Result { .as_ref() .map(|path| path.display().to_string()), "config_paths": config_paths, - "agent_trace_local_db": report.agent_trace_local_db.as_ref().map(|location| json!({ + "local_db": report.local_db.as_ref().map(|location| json!({ "label": location.label, "path": location.path.display().to_string(), "state": location.state, @@ -1764,15 +1747,11 @@ fn run_filesystem_auto_fixes( report: &HookDoctorReport, dependencies: &DoctorDependencies<'_>, ) -> Vec { - let Some(db_path) = report - .agent_trace_local_db - .as_ref() - .map(|location| &location.path) - else { + let Some(db_path) = report.local_db.as_ref().map(|location| &location.path) else { return vec![DoctorFixResultRecord { category: ProblemCategory::FilesystemPermissions, outcome: FixResult::Failed, - detail: String::from("Automatic Agent Trace directory repair could not start because the expected local DB path was not resolved during diagnosis."), + detail: String::from("Automatic local DB directory repair could not start because the expected local DB path was not resolved during diagnosis."), }]; }; @@ -1781,20 +1760,20 @@ fn run_filesystem_auto_fixes( category: ProblemCategory::FilesystemPermissions, outcome: FixResult::Failed, detail: format!( - "Automatic Agent Trace directory repair could not start because '{}' has no parent directory.", + "Automatic local DB directory repair could not start because '{}' has no parent directory.", db_path.display() ), }]; }; - let expected_parent = match (dependencies.resolve_agent_trace_local_db_path)() { + let expected_parent = match (dependencies.resolve_local_db_path)() { Ok(path) => path.parent().map(Path::to_path_buf), Err(error) => { return vec![DoctorFixResultRecord { category: ProblemCategory::FilesystemPermissions, outcome: FixResult::Failed, detail: format!( - "Automatic Agent Trace directory repair could not confirm the canonical SCE-owned path: {error}" + "Automatic local DB directory repair could not confirm the canonical SCE-owned path: {error}" ), }]; } @@ -1805,7 +1784,7 @@ fn run_filesystem_auto_fixes( category: ProblemCategory::FilesystemPermissions, outcome: FixResult::Failed, detail: format!( - "Automatic Agent Trace directory repair refused to modify '{}' because it does not match the canonical SCE-owned path.", + "Automatic local DB directory repair refused to modify '{}' because it does not match the canonical SCE-owned path.", parent.display() ), }]; @@ -1816,7 +1795,7 @@ fn run_filesystem_auto_fixes( category: ProblemCategory::FilesystemPermissions, outcome: FixResult::Skipped, detail: format!( - "Agent Trace directory '{}' already exists; no directory bootstrap was needed.", + "Local DB directory '{}' already exists; no directory bootstrap was needed.", parent.display() ), }]; @@ -1827,7 +1806,7 @@ fn run_filesystem_auto_fixes( category: ProblemCategory::FilesystemPermissions, outcome: FixResult::Fixed, detail: format!( - "Created the SCE-owned Agent Trace directory '{}'.", + "Created the SCE-owned local DB directory '{}'.", parent.display() ), }], @@ -1835,7 +1814,7 @@ fn run_filesystem_auto_fixes( category: ProblemCategory::FilesystemPermissions, outcome: FixResult::Failed, detail: format!( - "Automatic Agent Trace directory repair failed for '{}': {error}", + "Automatic local DB directory repair failed for '{}': {error}", parent.display() ), }], diff --git a/cli/src/services/local_db.rs b/cli/src/services/local_db.rs index 1f244db..14fb5e2 100644 --- a/cli/src/services/local_db.rs +++ b/cli/src/services/local_db.rs @@ -1,58 +1,28 @@ use std::path::{Path, PathBuf}; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; use turso::Builder; use crate::services::default_paths::resolve_sce_default_locations; -use crate::services::resilience::{run_with_retry, RetryPolicy}; -const LOCAL_DB_OPEN_RETRY_POLICY: RetryPolicy = RetryPolicy { - max_attempts: 3, - timeout_ms: 5_000, - initial_backoff_ms: 150, - max_backoff_ms: 600, -}; #[derive(Clone, Copy, Debug)] pub enum LocalDatabaseTarget<'a> { Path(&'a Path), } -pub fn resolve_agent_trace_local_db_path() -> Result { - Ok(resolve_sce_default_locations()?.agent_trace_local_db()) +pub fn resolve_local_db_path() -> Result { + Ok(resolve_sce_default_locations()?.local_db()) } -pub fn ensure_agent_trace_local_db_ready_blocking() -> Result { - let db_path = resolve_agent_trace_local_db_path()?; - if let Some(parent) = db_path.parent() { - std::fs::create_dir_all(parent).with_context(|| { - format!( - "Failed to create Agent Trace local DB directory '{}'.", - parent.display() - ) - })?; - } - +pub(crate) fn check_local_db_health_blocking(path: &Path) -> Result<()> { let runtime = tokio::runtime::Builder::new_current_thread() .enable_time() .build()?; - runtime.block_on(run_with_retry( - LOCAL_DB_OPEN_RETRY_POLICY, - "local_db.open_local_database", - "retry the command; if it persists, verify state-directory permissions and available disk space.", - |_| open_local_database(LocalDatabaseTarget::Path(&db_path)), - ))?; - Ok(db_path) + runtime.block_on(check_local_db_health(path)) } -pub(crate) fn check_agent_trace_local_db_health_blocking(path: &Path) -> Result<()> { - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_time() - .build()?; - runtime.block_on(check_agent_trace_local_db_health(path)) -} - -async fn check_agent_trace_local_db_health(path: &Path) -> Result<()> { +async fn check_local_db_health(path: &Path) -> Result<()> { let conn = connect_local(LocalDatabaseTarget::Path(path)).await?; let mut rows = conn.query("PRAGMA schema_version", ()).await?; let _ = rows.next().await?; @@ -82,7 +52,3 @@ pub(crate) fn resolve_state_data_root() -> Result { .to_path_buf()) } -async fn open_local_database(target: LocalDatabaseTarget<'_>) -> Result<()> { - let _ = connect_local(target).await?; - Ok(()) -} diff --git a/cli/src/services/mod.rs b/cli/src/services/mod.rs index e097ff6..7281e52 100644 --- a/cli/src/services/mod.rs +++ b/cli/src/services/mod.rs @@ -15,5 +15,4 @@ pub mod security; pub mod setup; pub mod style; pub mod token_storage; -pub mod trace; pub mod version; diff --git a/cli/src/services/trace.rs b/cli/src/services/trace.rs deleted file mode 100644 index ea70fd8..0000000 --- a/cli/src/services/trace.rs +++ /dev/null @@ -1,358 +0,0 @@ -use std::path::{Path, PathBuf}; -use std::process::Command; - -use anyhow::{anyhow, bail, Context, Result}; -use serde_json::json; -use turso::Builder; - -use crate::services::local_db::ensure_agent_trace_local_db_ready_blocking; -use crate::services::output_format::OutputFormat; -use crate::services::style::{self}; - -pub const NAME: &str = "trace"; - -pub type TraceFormat = OutputFormat; - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct TraceRequest { - pub subcommand: TraceSubcommand, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum TraceSubcommand { - Prompts(TracePromptsRequest), -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct TracePromptsRequest { - pub commit_sha: String, - pub format: TraceFormat, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -struct PromptTraceReport { - commit_sha: String, - harness_type: String, - model_id: Option, - git_branch: Option, - prompts: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -struct PromptTraceEntry { - turn_number: u32, - prompt_text: String, - prompt_length: usize, - is_truncated: bool, - cwd: Option, - tool_call_count: u32, - duration_ms: i64, - captured_at: String, -} - -pub fn run_trace_subcommand(request: TraceRequest) -> Result { - match request.subcommand { - TraceSubcommand::Prompts(request) => run_trace_prompts(&request), - } -} - -fn run_trace_prompts(request: &TracePromptsRequest) -> Result { - let working_dir = std::env::current_dir().context("Failed to determine current directory")?; - let repository_root = resolve_repository_root(&working_dir)?; - let db_path = ensure_agent_trace_local_db_ready_blocking()?; - let report = load_prompt_trace_report(&db_path, &repository_root, &request.commit_sha)?; - - match request.format { - TraceFormat::Text => Ok(render_prompt_trace_text(&report)), - TraceFormat::Json => render_prompt_trace_json(&report), - } -} - -fn resolve_repository_root(working_dir: &Path) -> Result { - let output = Command::new("git") - .args(["rev-parse", "--show-toplevel"]) - .current_dir(working_dir) - .output() - .context("Failed to execute 'git rev-parse --show-toplevel'")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); - let detail = if stderr.is_empty() { - String::new() - } else { - format!("git reported: {stderr}. ") - }; - bail!( - "Failed to resolve the current git repository root. {detail}Try: run 'sce trace prompts ' from inside a non-bare repository with persisted Agent Trace data." - ); - } - - Ok(PathBuf::from( - String::from_utf8_lossy(&output.stdout).trim(), - )) -} - -fn load_prompt_trace_report( - db_path: &Path, - repository_root: &Path, - commit_ref: &str, -) -> Result { - let runtime = tokio::runtime::Builder::new_current_thread().build()?; - runtime.block_on(load_prompt_trace_report_async( - db_path, - repository_root, - commit_ref, - )) -} - -async fn load_prompt_trace_report_async( - db_path: &Path, - repository_root: &Path, - commit_ref: &str, -) -> Result { - let db_location = db_path - .to_str() - .ok_or_else(|| anyhow!("Local DB path must be valid UTF-8: {}", db_path.display()))?; - let db = Builder::new_local(db_location).build().await?; - let conn = db.connect()?; - conn.execute("PRAGMA foreign_keys = ON", ()).await?; - - let canonical_root = repository_root - .canonicalize() - .unwrap_or_else(|_| repository_root.to_path_buf()) - .to_string_lossy() - .to_string(); - - let repository_id = query_repository_id(&conn, &canonical_root).await?; - let (commit_id, resolved_commit_sha) = - resolve_commit_reference(&conn, repository_id, commit_ref).await?; - - let mut rows = conn - .query( - "SELECT prompt_text, prompt_length, is_truncated, turn_number, harness_type, model_id, cwd, git_branch, tool_call_count, duration_ms, captured_at FROM prompts WHERE commit_id = ?1 ORDER BY turn_number ASC, captured_at ASC", - [commit_id], - ) - .await?; - - let mut prompts = Vec::new(); - let mut harness_type = None; - let mut model_id = None; - let mut git_branch = None; - - while let Some(row) = rows.next().await? { - let prompt_harness = required_text_column(&row, 4, "harness_type")?; - if harness_type.is_none() { - harness_type = Some(prompt_harness); - } - if model_id.is_none() { - model_id = optional_text_column(&row, 5)?; - } - if git_branch.is_none() { - git_branch = optional_text_column(&row, 7)?; - } - - prompts.push(PromptTraceEntry { - prompt_text: required_text_column(&row, 0, "prompt_text")?, - prompt_length: usize::try_from(required_integer_column(&row, 1, "prompt_length")?) - .context("prompt_length exceeded supported usize range")?, - is_truncated: required_integer_column(&row, 2, "is_truncated")? != 0, - turn_number: u32::try_from(required_integer_column(&row, 3, "turn_number")?) - .context("turn_number exceeded supported u32 range")?, - cwd: optional_text_column(&row, 6)?, - tool_call_count: u32::try_from(required_integer_column(&row, 8, "tool_call_count")?) - .context("tool_call_count exceeded supported u32 range")?, - duration_ms: required_integer_column(&row, 9, "duration_ms")?, - captured_at: required_text_column(&row, 10, "captured_at")?, - }); - } - - if prompts.is_empty() { - bail!( - "No persisted prompt captures were found for commit '{resolved_commit_sha}'. Try: ensure the commit was created with prompt capture enabled, then rerun the command." - ); - } - - Ok(PromptTraceReport { - commit_sha: resolved_commit_sha, - harness_type: harness_type.expect("prompt rows should set harness_type"), - model_id, - git_branch, - prompts, - }) -} - -async fn query_repository_id(conn: &turso::Connection, canonical_root: &str) -> Result { - let mut rows = conn - .query( - "SELECT id FROM repositories WHERE canonical_root = ?1 LIMIT 1", - [canonical_root], - ) - .await?; - - let Some(row) = rows.next().await? else { - bail!( - "No persisted Agent Trace data was found for repository '{canonical_root}'. Try: create a traced commit in this repository before querying prompts." - ); - }; - - required_integer_column(&row, 0, "repository_id") -} - -async fn resolve_commit_reference( - conn: &turso::Connection, - repository_id: i64, - commit_ref: &str, -) -> Result<(i64, String)> { - let like_pattern = format!("{commit_ref}%"); - let mut rows = conn - .query( - "SELECT id, commit_sha FROM commits WHERE repository_id = ?1 AND (commit_sha = ?2 OR commit_sha LIKE ?3) ORDER BY CASE WHEN commit_sha = ?2 THEN 0 ELSE 1 END, commit_sha ASC LIMIT 3", - (repository_id, commit_ref, like_pattern.as_str()), - ) - .await?; - - let mut matches = Vec::new(); - while let Some(row) = rows.next().await? { - matches.push(( - required_integer_column(&row, 0, "commit_id")?, - required_text_column(&row, 1, "commit_sha")?, - )); - } - - match matches.as_slice() { - [] => bail!( - "No persisted commit matched '{commit_ref}'. Try: pass a full commit SHA or a unique persisted prefix." - ), - [(commit_id, commit_sha)] => Ok((*commit_id, commit_sha.clone())), - [(.., first_sha), (.., second_sha), ..] => bail!( - "Commit reference '{commit_ref}' is ambiguous between '{first_sha}' and '{second_sha}'. Try: rerun with a longer commit SHA prefix." - ), - } -} - -fn render_prompt_trace_text(report: &PromptTraceReport) -> String { - let mut lines = vec![ - format!( - "{}: {}", - style::label("Commit"), - style::value(&report.commit_sha) - ), - format!( - "{}: {}", - style::label("Harness"), - style::value(&report.harness_type) - ), - format!( - "{}: {}", - style::label("Model"), - style::value(report.model_id.as_deref().unwrap_or("unknown")) - ), - format!( - "{}: {}", - style::label("Branch"), - style::value(report.git_branch.as_deref().unwrap_or("unknown")) - ), - format!( - "{}: {}", - style::label("Total prompts"), - style::value(&report.prompts.len().to_string()) - ), - String::new(), - ]; - - for (index, prompt) in report.prompts.iter().enumerate() { - let mut header = format!( - "{} {} {} cwd: {} duration: {} tools: {}", - style::label(&format!("Turn {}", prompt.turn_number)), - style::value(&prompt.captured_at), - style::label("cwd:"), - style::value(prompt.cwd.as_deref().unwrap_or("unknown")), - style::value(&format_duration_ms(prompt.duration_ms)), - style::value(&prompt.tool_call_count.to_string()) - ); - if prompt.is_truncated { - use std::fmt::Write; - // Writing to String buffer cannot fail in practice - write!(&mut header, " {}", style::value("[truncated]")) - .expect("writing to String buffer should never fail"); - } - lines.push(header); - lines.push(format!(" {}", prompt.prompt_text.replace('\n', "\n "))); - if index + 1 != report.prompts.len() { - lines.push(String::new()); - } - } - - lines.join("\n") -} - -fn render_prompt_trace_json(report: &PromptTraceReport) -> Result { - let payload = json!({ - "status": "ok", - "command": NAME, - "subcommand": "prompts", - "commit": report.commit_sha, - "harness": report.harness_type, - "model": report.model_id, - "branch": report.git_branch, - "prompt_count": report.prompts.len(), - "prompts": report.prompts.iter().map(|prompt| { - let mut value = json!({ - "turn_number": prompt.turn_number, - "text": prompt.prompt_text, - "length": prompt.prompt_text.len(), - "cwd": prompt.cwd, - "duration_ms": prompt.duration_ms, - "tool_call_count": prompt.tool_call_count, - "captured_at": prompt.captured_at, - "is_truncated": prompt.is_truncated, - }); - if prompt.is_truncated { - value["original_length"] = json!(prompt.prompt_length); - } - value - }).collect::>(), - }); - - serde_json::to_string_pretty(&payload) - .context("failed to serialize trace prompts report to JSON") -} - -fn format_duration_ms(duration_ms: i64) -> String { - if duration_ms <= 0 { - return String::from("0s"); - } - - let total_seconds = duration_ms / 1_000; - let minutes = total_seconds / 60; - let seconds = total_seconds % 60; - - if minutes > 0 && seconds > 0 { - format!("{minutes}m {seconds}s") - } else if minutes > 0 { - format!("{minutes}m") - } else { - format!("{total_seconds}s") - } -} - -fn required_text_column(row: &turso::Row, index: usize, label: &str) -> Result { - let value = row.get_value(index)?; - value - .as_text() - .map(ToOwned::to_owned) - .ok_or_else(|| anyhow!("{label} column returned a non-text value")) -} - -fn optional_text_column(row: &turso::Row, index: usize) -> Result> { - let value = row.get_value(index)?; - Ok(value.as_text().map(ToOwned::to_owned)) -} - -fn required_integer_column(row: &turso::Row, index: usize, label: &str) -> Result { - let value = row.get_value(index)?; - value - .as_integer() - .copied() - .ok_or_else(|| anyhow!("{label} column returned a non-integer value")) -} diff --git a/context/architecture.md b/context/architecture.md index 1f13271..4a91a38 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -83,17 +83,16 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/cli_schema.rs` defines the clap-based CLI schema using derive macros for all top-level commands and subcommands, and renders command-local help text for the `auth` command tree (`auth`, `auth login`, `auth logout`, `auth status`). - `cli/src/app.rs` provides the clap-based argument dispatch loop with deterministic help/setup execution, bare-command help routing for `sce auth` and `sce config`, centralized stream routing (`stdout` success payloads, `stderr` redacted diagnostics), stable class-based exit-code mapping (`2` parse, `3` validation, `4` runtime, `5` dependency), and stable class-based stderr diagnostic codes (`SCE-ERR-PARSE`, `SCE-ERR-VALIDATION`, `SCE-ERR-RUNTIME`, `SCE-ERR-DEPENDENCY`) with default `Try:` remediation injection when missing. - `cli/src/services/observability.rs` provides deterministic runtime observability controls and rendering for app lifecycle logs, including shared config-resolved threshold/format and OTEL/file-sink inputs with precedence `env > config file > defaults` for non-flag observability keys, optional file sink controls (`SCE_LOG_FILE`, `SCE_LOG_FILE_MODE` with deterministic truncate-or-append policy), optional OTEL export bootstrap (`SCE_OTEL_ENABLED`, `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_PROTOCOL`), stable event identifiers, severity filtering, stderr-only primary emission with optional mirrored file writes, and redaction-safe emission through the shared security helper. -- `cli/src/command_surface.rs` is the source of truth for top-level command contract metadata (`help`, `config`, `setup`, `doctor`, `auth`, `hooks`, `trace`, `sync`, `version`, `completion`), including implementation status for command classification and per-command visibility on the slim top-level help surface. -- `cli/src/services/default_paths.rs` is the canonical production path catalog for the CLI: it resolves config/state/cache roots with platform-aware XDG or `dirs` fallbacks, exposes named default paths plus an explicit persisted-artifact inventory for the current default artifacts (global config, auth tokens, Agent Trace local DB), and owns canonical repo-relative, embedded-asset, install/runtime, hook, and context-path accessors so non-test production path definitions have one shared owner. Current production consumers such as config discovery, doctor reporting, setup/install flows, and local hook runtime path resolution consume this shared catalog rather than defining owned path literals in their own modules. +- `cli/src/command_surface.rs` is the source of truth for top-level command contract metadata (`help`, `config`, `setup`, `doctor`, `auth`, `hooks`, `sync`, `version`, `completion`), including implementation status for command classification and per-command visibility on the slim top-level help surface. +- `cli/src/services/default_paths.rs` is the canonical production path catalog for the CLI: it resolves config/state/cache roots with platform-aware XDG or `dirs` fallbacks, exposes named default paths plus an explicit persisted-artifact inventory for the current default artifacts (global config, auth tokens, local DB), and owns canonical repo-relative, embedded-asset, install/runtime, hook, and context-path accessors so non-test production path definitions have one shared owner. Current production consumers such as config discovery, doctor reporting, setup/install flows, and local hook runtime path resolution consume this shared catalog rather than defining owned path literals in their own modules. - `cli/src/services/config.rs` defines `sce config` parser/runtime contracts (`show`, `validate`, `--help`), with bare `sce config` routed by `cli/src/app.rs` to the same help payload as `sce config --help`, deterministic config-file selection, explicit value precedence (`flags > env > config file > defaults`), strict config-file validation (`$schema`, `log_level`, `log_format`, `log_file`, `log_file_mode`, `timeout_ms`, `workos_client_id`, nested `otel`, `policies.bash`, and `policies.attribution_hooks.enabled`), compile-time embedding of the canonical generated schema artifact and bash-policy preset catalog from the ephemeral crate-local `cli/assets/generated/config/**` mirror prepared from canonical `config/` outputs before Cargo packaging/builds, runtime parity between that schema and the Rust-side top-level allowed-key gate so startup config discovery accepts the canonical `"$schema": "https://sce.crocoder.dev/config.json"` declaration, shared auth-key resolution with optional baked defaults starting at `workos_client_id`, repo-configured bash-policy preset/custom validation and merged reporting from discovered config files, a shared attribution-hooks runtime gate resolved from `SCE_ATTRIBUTION_HOOKS_ENABLED` or `policies.attribution_hooks.enabled` with disabled-by-default semantics, shared observability-runtime resolution for logging and OTEL config keys (`log_level`, `log_format`, `log_file`, `log_file_mode`, `otel.enabled`, `otel.exporter_otlp_endpoint`, `otel.exporter_otlp_protocol`) with deterministic source-aware env-over-config precedence reused by `cli/src/app.rs`, and deterministic text/JSON output rendering where `show` includes resolved observability/auth/policy values with provenance while `validate` now returns only validation status plus issues/warnings. - `cli/src/services/output_format.rs` defines the canonical shared CLI output-format contract (`OutputFormat`) for supporting commands, with deterministic `text|json` parsing and command-scoped actionable invalid-value guidance. - `cli/src/services/auth_command.rs` defines the implemented auth command surface for `sce auth login|renew|logout|status`, including device-flow login, stored-token renewal (`--force` supported for renew), logout, and status rendering in text/JSON formats. -- `cli/src/services/trace.rs` defines the prompt-query read path for `sce trace prompts `, including repository-root resolution from git truth, unique prefix/full-SHA commit lookup against persisted Agent Trace data, deterministic text/JSON rendering, legacy `--json` compatibility, and explicit missing/ambiguous commit diagnostics for prompt rows stored in the local `prompts` table when those tables exist. -- `cli/src/services/local_db.rs` provides the local Turso data adapter, including `Builder::new_local(...)` initialization, deterministic persistent runtime DB target resolution/bootstrap (`ensure_agent_trace_local_db_ready_blocking`) through the shared default-path seam, creation/opening of the per-user Agent Trace DB file without schema bootstrap, and async execute/query smoke checks for in-memory and file-backed targets. +- `cli/src/services/local_db.rs` provides the local Turso data adapter, including `Builder::new_local(...)` initialization, deterministic persistent runtime DB target resolution through the shared default-path seam, a neutral per-user local DB baseline without schema bootstrap, and async execute/query smoke checks for in-memory and file-backed targets. - `cli/src/test_support.rs` provides a shared test-only temp-directory helper (`TestTempDir`) used by service tests that need filesystem fixtures. - `cli/src/services/setup.rs` defines the setup command contract (`SetupMode`, `SetupTarget`, `SetupRequest`, CLI flag parser/validator), an `inquire`-backed interactive target prompter (`InquireSetupTargetPrompter`), setup dispatch outcomes (proceed/cancelled), compile-time embedded asset access (`EmbeddedAsset`, target-scoped iterators, required-hook asset iterators/lookups) generated by `cli/build.rs` from the ephemeral crate-local `cli/assets/generated/config/{opencode,claude}/**` mirror plus `cli/assets/hooks/**`, a target-scoped install engine/orchestrator that stages embedded files and branches backup behavior by repository type (non-git-backed config installs keep backup-and-restore rollback, while git-backed config installs skip backup creation and emit deterministic git-recovery guidance on swap failure), and formats deterministic completion messaging, plus required-hook install orchestration (`install_required_git_hooks`) that now follows the same shared backup-policy seam (non-git-backed hook replacements keep backup-and-restore rollback, while git-backed hook replacements skip backup creation and backup-based rollback and emit deterministic git-recovery guidance on swap failure), along with command-surface setup request resolution helpers (`run_setup_hooks`, `resolve_setup_request`) used by hooks-only and composable target+hooks setup invocations with deterministic option compatibility validation, canonicalized/validated repo targeting, write-permission probes, and stable section-ordered setup/hook outcome messaging. - `cli/src/services/security.rs` provides shared security utilities for deterministic secret redaction (`redact_sensitive_text`) and directory write-permission probes (`ensure_directory_is_writable`) used by app/setup/observability surfaces. -- `cli/src/services/doctor.rs` now defines the current doctor request/report surface: explicit `DoctorMode` (`diagnose` vs `fix`), stable text/JSON problem records with category/severity/fixability/remediation fields, deterministic fix-result reporting in fix mode, current global operator-environment checks (shared state-root resolution, shared global-config-path resolution, repo-local config validation through the generated `sce/config.json` schema contract, Agent Trace local DB path/health, and DB-parent readiness barriers), repo/hook-integrity diagnostics that distinguish git-unavailable vs non-repo vs bare-repo states, an empty default repo-database section reflecting the current state where no repo-owned SCE database exists, Agent Trace local DB reporting in default doctor output, repair-mode reuse of `cli/src/services/setup.rs::install_required_git_hooks` for missing/stale/non-executable required hooks and missing hooks directories, and a bounded doctor-owned repair routine that bootstraps the canonical SCE-owned Agent Trace DB parent directory only when the resolved path matches the expected owned location. +- `cli/src/services/doctor.rs` now defines the current doctor request/report surface: explicit `DoctorMode` (`diagnose` vs `fix`), stable text/JSON problem records with category/severity/fixability/remediation fields, deterministic fix-result reporting in fix mode, current global operator-environment checks (shared state-root resolution, shared global-config-path resolution, repo-local config validation through the generated `sce/config.json` schema contract, local DB path/health, and DB-parent readiness barriers), repo/hook-integrity diagnostics that distinguish git-unavailable vs non-repo vs bare-repo states, an empty default repo-database section reflecting the current state where no repo-owned SCE database exists, repair-mode reuse of `cli/src/services/setup.rs::install_required_git_hooks` for missing/stale/non-executable required hooks and missing hooks directories, and a bounded doctor-owned repair routine that bootstraps the canonical SCE-owned local DB parent directory only when the resolved path matches the expected owned location. - `cli/src/services/agent_trace.rs` defines the Agent Trace schema adapter and builder contracts (`adapt_trace_payload`, `build_trace_payload`), including fixed git VCS identity, reserved reverse-domain metadata keys, and deterministic AI `model_id` normalization before schema-compliance validation. - `cli/src/services/version.rs` defines the version command parser/rendering contract (`parse_version_request`, `render_version`) with deterministic text output and stable JSON runtime-identification fields. - `cli/src/services/completion.rs` defines completion parser/rendering contract (`parse_completion_request`, `render_completion`) with deterministic Bash/Zsh/Fish script output aligned to current parser-valid command/flag surfaces. diff --git a/context/cli/cli-command-surface.md b/context/cli/cli-command-surface.md index f46992d..54772da 100644 --- a/context/cli/cli-command-surface.md +++ b/context/cli/cli-command-surface.md @@ -10,12 +10,12 @@ Operator onboarding currently comes from `sce --help`, command-local `--help` ou - Runtime shell: `cli/src/app.rs` - Command contract catalog: `cli/src/command_surface.rs` - Local Turso adapter: `cli/src/services/local_db.rs` -- Service domains: `cli/src/services/{agent_trace,auth,auth_command,completion,config,default_paths,setup,doctor,hooks,resilience,sync,token_storage,version}.rs` +- Service domains: `cli/src/services/{agent_trace,auth,auth_command,completion,config,default_paths,local_db,setup,doctor,hooks,resilience,sync,token_storage,version}.rs` - Shared test temp-path helper: `cli/src/test_support.rs` (`TestTempDir`, test-only module) ## Onboarding documentation -- `sce --help` includes a slim top-level command list and quick-start examples for `setup`, `doctor`, and `version`; `auth`, `hooks`, `trace`, and `sync` remain implemented in code but are hidden from `sce`, `sce help`, and `sce --help` for this phase. +- `sce --help` includes a slim top-level command list and quick-start examples for `setup`, `doctor`, and `version`; `auth`, `hooks`, and `sync` remain implemented in code but are hidden from `sce`, `sce help`, and `sce --help` for this phase. - Command-local help is available for implemented commands including bare `sce auth`, `sce auth --help`, `sce auth login --help`, `sce setup --help`, `sce doctor --help`, and `sce completion --help`; when stdout color is enabled those help payloads now reuse the shared heading/command/placeholder styling pass while non-TTY and `NO_COLOR` flows stay plain text. Human-readable stderr diagnostics and interactive setup prompt text now follow the same shared styling policy on their respective terminal streams. - Current verification guidance for the CLI slice uses crate-local `cargo test --manifest-path cli/Cargo.toml`, plus release/install commands for installability (`cargo build --manifest-path cli/Cargo.toml --release`, `cargo install --path cli --locked`). @@ -42,21 +42,21 @@ Operator onboarding currently comes from `sce --help`, command-local `--help` ou - the visible command list is `help`, `config`, `setup`, `doctor`, `version`, and `completion` - top-level help omits implemented/placeholder labels - top-level examples cover setup plus doctor/version machine-readable or repair-intent flows (`doctor --format json`, `doctor --fix`, `version --format json`) and use the shared example-command styling when stdout color is enabled -- `auth`, `hooks`, `trace`, and `sync` stay parser-valid and directly invocable, but are hidden from those top-level help surfaces +- `auth`, `hooks`, and `sync` stay parser-valid and directly invocable, but are hidden from those top-level help surfaces Placeholder commands currently acknowledge planned behavior and do not claim production implementation. `sync` routes through an explicit service-contract placeholder. -`hooks` routes through implemented subcommand parsing/dispatch for `pre-commit`, `commit-msg`, `post-commit`, and `post-rewrite`. +`hooks` routes through implemented subcommand parsing/dispatch for `pre-commit`, `commit-msg`, `post-commit`, and `post-rewrite`; current behavior is attribution-only and disabled by default. `config` exposes deterministic inspect/validate entrypoints (`sce config show`, `sce config validate`) with explicit precedence (`flags > env > config file > defaults`), a shared auth-runtime resolver for supported keys that declare env/config/optional baked-default inputs starting with `workos_client_id`, first-class `policies.bash` reporting for preset/custom blocked-command rules, and deterministic text/JSON output modes where `show` reports resolved values with provenance while `validate` reports pass/fail plus validation issues and warnings only. `version` exposes deterministic runtime identification output in text mode by default and JSON mode via `--format json`. `completion` exposes deterministic shell completion generation via `sce completion --shell `. `setup` defaults to an `inquire` interactive target selection (OpenCode, Claude, Both) and accepts mutually-exclusive non-interactive target flags (`--opencode`, `--claude`, `--both`); the interactive prompt title and target labels now reuse shared prompt styling helpers when stdout color is enabled. `auth` now emits auth-local guidance for bare `sce auth` and `sce auth --help`, listing `login`, `logout`, and `status` plus copy-ready next steps. -`setup`, `doctor`, `hooks`, `trace`, `sync`, `version`, and `completion` all support command-local `--help`/`-h` usage output via top-level parser routing in `cli/src/app.rs`. +`setup`, `doctor`, `hooks`, `sync`, `version`, and `completion` all support command-local `--help`/`-h` usage output via top-level parser routing in `cli/src/app.rs`. `setup` now also exposes compile-time embedded config assets for OpenCode/Claude targets, sourced from the generated `config/.opencode/**` and `config/.claude/**` trees via `cli/build.rs` with normalized forward-slash relative paths and target-scoped iteration APIs; the embedded asset set includes the OpenCode bash-policy plugin/runtime files generated from the canonical preset catalog (Claude bash-policy enforcement has been removed from generated outputs). `setup` additionally includes a repository-root install engine (`install_embedded_setup_assets`) that stages embedded files, intentionally leaves generated `skills/*/tile.json` manifests in `config/` only, skips those tile files during repo-root installs, and applies backup-and-replace safety for `.opencode/`/`.claude/` with rollback restoration if staged swap fails while treating bash-policy enforcement files as first-class SCE-managed assets. `setup` now executes end-to-end and prints deterministic completion details including selected target(s), per-target install count, and backup actions. -`doctor` now executes end-to-end with explicit diagnosis and repair-intent surfaces: `sce doctor` stays read-only, `sce doctor --fix` selects repair-intent mode, and text/JSON output expose stable mode/problem/fix-result/database-record scaffolding. The current runtime now covers state-root resolution, global and repo-local `sce/config.json` readability/schema validation, Agent Trace local DB path/health, DB-parent readiness barriers, an intentionally empty repo-scoped SCE database section for the active repository, Agent Trace local DB reporting in default doctor output, the repo hook rollout slice when a repository target is detected, and repo-root installed OpenCode integration presence for `plugins`, `agents`, `commands`, and `skills`; those integration checks are presence-only and fail a group when any required installed file is missing. Fix mode now reuses the canonical setup hook install flow to repair missing/stale/non-executable required hooks and missing hooks directories, and it can bootstrap the missing SCE-owned Agent Trace DB parent directory when the resolved path matches the canonical owned location. +`doctor` now executes end-to-end with explicit diagnosis and repair-intent surfaces: `sce doctor` stays read-only, `sce doctor --fix` selects repair-intent mode, and text/JSON output expose stable mode/problem/fix-result/database-record scaffolding. The current runtime now covers state-root resolution, global and repo-local `sce/config.json` readability/schema validation, local DB path/health, DB-parent readiness barriers, an intentionally empty repo-scoped SCE database section for the active repository, the repo hook rollout slice when a repository target is detected, and repo-root installed OpenCode integration presence for `plugins`, `agents`, `commands`, and `skills`; those integration checks are presence-only and fail a group when any required installed file is missing. Fix mode now reuses the canonical setup hook install flow to repair missing/stale/non-executable required hooks and missing hooks directories, and it can bootstrap the missing canonical local DB parent directory when the resolved path matches the canonical owned location. `sync` includes a local Turso smoke gate backed by a lazily initialized shared tokio current-thread runtime, bounded retry/timeout/backoff policy for the smoke operation, and a placeholder cloud-sync gateway plan; it now supports deterministic `text` output (default) and `--format json` output with stable placeholder fields. ## Command loop and error model @@ -67,11 +67,11 @@ Placeholder commands currently acknowledge planned behavior and do not claim pro - Unknown commands/options and extra positional arguments return deterministic, actionable guidance to run `sce --help`. - `sce setup --help` returns setup-specific usage output with target-flag contract details and deterministic examples, including one-run non-interactive setup+hooks and composable follow-up validation/repair-intent flows (`sce doctor --format json`, `sce doctor --fix`). - `sce auth` and `sce auth --help` return auth-specific usage output with available subcommands and deterministic examples, while `sce auth --help` stays scoped to the selected auth subcommand. -- `sce doctor --help`, `sce hooks --help`, `sce trace --help`, `sce trace prompts --help`, and `sce sync --help` return command-local usage output and deterministic copy-ready examples. +- `sce doctor --help`, `sce hooks --help`, and `sce sync --help` return command-local usage output and deterministic copy-ready examples. - Interactive `sce setup` prompt cancellation/interrupt exits cleanly with: `Setup cancelled. No files were changed.` - Command handlers return deterministic status messaging: - `setup`: `Setup completed successfully.` plus selected targets, per-target install destinations/counts, and policy-aware backup status lines (`existing target moved to ''`, `not created (git-backed repository)`, or `not needed (no existing target)` for config targets; analogous hook status wording for hook setup). -- `doctor`: current runtime emits `SCE doctor diagnose` / `SCE doctor fix` human text headers plus ordered `Environment`, `Configuration`, `Repository`, `Git Hooks`, and `Integrations` sections with bracketed `[PASS]`/`[FAIL]`/`[MISS]` row tokens, shared-style green pass plus red fail/miss colorization when enabled, simplified `label (path)` rows, top-level-only hook rows, and a deterministic summary footer; JSON output remains unchanged and still carries stable problem/fixability records plus deterministic fix-result records in fix mode. The runtime validates global and repo-local `sce/config.json` inputs plus Agent Trace DB health, keeps the repo-scoped database section empty unless a future repo-owned SCE database family is introduced, diagnoses repo hook rollout integrity plus repo-root installed OpenCode integration presence for `OpenCode plugins`, `OpenCode agents`, `OpenCode commands`, and `OpenCode skills`, and in fix mode reuses canonical setup hook installation for supported hook repairs plus bounded bootstrap of the canonical missing SCE-owned Agent Trace DB parent directory while preserving manual-only reporting for unsupported issues. +- `doctor`: current runtime emits `SCE doctor diagnose` / `SCE doctor fix` human text headers plus ordered `Environment`, `Configuration`, `Repository`, `Git Hooks`, and `Integrations` sections with bracketed `[PASS]`/`[FAIL]`/`[MISS]` row tokens, shared-style green pass plus red fail/miss colorization when enabled, simplified `label (path)` rows, top-level-only hook rows, and a deterministic summary footer; JSON output carries stable problem/fixability records plus deterministic fix-result records in fix mode and reports the neutral DB record under `local_db`. The runtime validates global and repo-local `sce/config.json` inputs plus local DB health, keeps the repo-scoped database section empty unless a future repo-owned SCE database family is introduced, diagnoses repo hook rollout integrity plus repo-root installed OpenCode integration presence for `OpenCode plugins`, `OpenCode agents`, `OpenCode commands`, and `OpenCode skills`, and in fix mode reuses canonical setup hook installation for supported hook repairs plus bounded bootstrap of the canonical missing SCE-owned local DB parent directory while preserving manual-only reporting for unsupported issues. - `hooks`: deterministic hook subcommand status messaging for runtime entrypoint invocation and argument/STDIN contract validation. - `TODO: 'sync' cloud workflows are planned and not implemented yet. Local Turso smoke check succeeded (1) row inserted; cloud sync placeholder enumerates 3 phase(s) and plan holds 3 checkpoint(s). Next step: rerun with '--format json' for machine-readable placeholder checkpoints.` @@ -79,17 +79,17 @@ Placeholder commands currently acknowledge planned behavior and do not claim pro - `cli/src/services/setup.rs` defines setup parsing/selection contracts plus runtime install orchestration (`run_setup_for_mode`) over the embedded asset install engine. - `cli/src/services/config.rs` defines config parser/runtime contracts (`show`, `validate`, `--help`), strict config-file key/type validation, deterministic text/JSON rendering, repo-configured bash-policy preset/custom validation and reporting under `policies.bash`, and shared auth-key metadata that declares env key, config-file key, and optional baked-default eligibility for supported auth runtime values starting with `workos_client_id` (`WORKOS_CLIENT_ID` vs `workos_client_id`); auth-key provenance/preference metadata stays on `show`, while `validate` stays trimmed to validation status plus issues/warnings. -- `cli/src/services/doctor.rs` now defines the implemented doctor request/report contract (`DoctorRequest`, `DoctorMode`, `run_doctor`) with explicit fix-mode parsing, stable text/JSON problem and database-record rendering, deterministic fix-result reporting, state-root/config/local-DB reporting and validation, an empty default repo-scoped database inventory, Agent Trace local DB reporting in default doctor output, path-source detection plus required-hook presence/executable/content checks when a repository target is detected, repo-root installed OpenCode integration presence inventory for `plugins`, `agents`, `commands`, and `skills` derived from the embedded OpenCode setup asset catalog, shared-style bracketed human status token rendering (`[PASS]`, `[FAIL]`, `[MISS]`) with simplified `label (path)` text rows, repair-mode reuse of canonical setup hook installation for supported hook repairs, and a bounded doctor-owned Agent Trace directory bootstrap routine for the canonical missing DB parent path. +- `cli/src/services/doctor.rs` now defines the implemented doctor request/report contract (`DoctorRequest`, `DoctorMode`, `run_doctor`) with explicit fix-mode parsing, stable text/JSON problem and database-record rendering, deterministic fix-result reporting, state-root/config/local-DB reporting and validation, an empty default repo-scoped database inventory, path-source detection plus required-hook presence/executable/content checks when a repository target is detected, repo-root installed OpenCode integration presence inventory for `plugins`, `agents`, `commands`, and `skills` derived from the embedded OpenCode setup asset catalog, shared-style bracketed human status token rendering (`[PASS]`, `[FAIL]`, `[MISS]`) with simplified `label (path)` text rows, repair-mode reuse of canonical setup hook installation for supported hook repairs, and a bounded doctor-owned local-DB directory bootstrap routine for the canonical missing DB parent path. - `cli/src/services/agent_trace.rs` defines the task-scoped schema adapter contract (`adapt_trace_payload`) from internal attribution input structs to Agent Trace-shaped record structs, including fixed git `vcs` mapping, contributor type mapping, and reserved `dev.crocoder.sce.*` metadata placement. - `cli/src/services/version.rs` defines the version parser/output contract (`parse_version_request`, `render_version`) with deterministic text/JSON output modes. - `cli/src/services/completion.rs` defines the completion output contract (`render_completion`) using clap_complete to generate deterministic shell scripts for Bash, Zsh, and Fish. -- `cli/src/services/hooks.rs` defines production local hook runtime parsing/dispatch (`HookSubcommand`, `parse_hooks_subcommand`, `run_hooks_subcommand`) for `pre-commit`, `commit-msg`, `post-commit`, and `post-rewrite`, plus checkpoint/persistence/retry finalization seams used by hook entrypoints. +- `cli/src/services/hooks.rs` defines production local hook runtime parsing/dispatch (`HookSubcommand`, `parse_hooks_subcommand`, `run_hooks_subcommand`) for `pre-commit`, `commit-msg`, `post-commit`, and `post-rewrite`; current runtime behavior is commit-msg-only attribution behind the disabled-default attribution gate, while the other entrypoints are deterministic no-ops. - `cli/src/services/resilience.rs` defines shared bounded retry/timeout/backoff execution policy (`RetryPolicy`, `run_with_retry`) with deterministic failure messaging and retry observability hooks. - `cli/src/services/sync.rs` defines cloud-sync abstraction points (`CloudSyncGateway`, `CloudSyncRequest`, `CloudSyncPlan`) layered after the local Turso smoke gate, plus `SyncRequest` parsing/rendering for deterministic text or `--format json` placeholder output and command-local usage text (`sync_usage_text`). -- `cli/src/services/default_paths.rs` defines the canonical per-user persisted-location seam for config/state/cache roots plus named default file paths and an explicit inventory of current default persisted artifacts (`global config`, `auth tokens`, `Agent Trace local DB`) used by config discovery, token storage, local DB bootstrap, and doctor diagnostics; no default cache-backed persisted artifact exists yet. +- `cli/src/services/default_paths.rs` defines the canonical per-user persisted-location seam for config/state/cache roots plus named default file paths and an explicit inventory of current default persisted artifacts (`global config`, `auth tokens`, `local DB`) used by config discovery, token storage, local DB bootstrap, and doctor diagnostics; no default cache-backed persisted artifact exists yet. - `cli/src/services/token_storage.rs` defines WorkOS token persistence (`save_tokens`, `load_tokens`, `delete_tokens`) with shared default-path-seam resolution for the default token file, JSON payload storage including `stored_at_unix_seconds`, graceful missing-file deletion behavior, missing/corrupted-file handling, and restrictive on-disk permissions (`0600` on Unix; Windows best-effort ACL hardening via `icacls`). - `cli/src/services/auth_command.rs` defines the auth command orchestration surface (`AuthRequest`, `AuthSubcommand`, `run_auth_subcommand`) for `login`, `renew`, `logout`, and `status`, including shared text/JSON rendering, token refresh/forced renewal handling for `sce auth renew`, token-storage-backed logout deletion with path-aware remediation guidance, expiry-aware status reporting, canonical credentials-file path reporting sourced from the shared default-path seam, precedence-aware client-ID guidance sourced from the shared auth-runtime resolver instead of env-only assumptions, and a lazily initialized current-thread Tokio runtime with both I/O and time enabled so the auth flows can drive the WorkOS device/refresh paths without the prior I/O-disabled panic. -- `cli/src/app.rs` dispatches `auth`, `config`, `setup`, `doctor`, `hooks`, `trace`, `sync`, `version`, and `completion` through service-level modules so runtime messages are sourced from domain modules instead of inline strings. +- `cli/src/app.rs` dispatches `auth`, `config`, `setup`, `doctor`, `hooks`, `sync`, `version`, and `completion` through service-level modules so runtime messages are sourced from domain modules instead of inline strings. ## Local Turso adapter behavior @@ -99,11 +99,11 @@ Placeholder commands currently acknowledge planned behavior and do not claim pro - The smoke path creates `sce_smoke`, inserts one row, and runs a query round-trip to confirm readable results. - `cli/src/services/sync.rs` wraps this in a lazily initialized shared tokio current-thread runtime and applies bounded retries (3 attempts), operation timeout (2000ms), and capped backoff (100-400ms) before returning placeholder-safe messaging. - The same sync path now derives deferred cloud checkpoint messaging from `PlaceholderCloudSyncGateway`. -- `cli/src/services/local_db.rs` applies the same resilience wrapper when bootstrapping persistent Agent Trace schema migrations (`ensure_agent_trace_local_db_ready_blocking`) with deterministic retries/timeouts/backoff and actionable terminal failure hints. +- `cli/src/services/local_db.rs` retains a neutral empty-file local DB bootstrap seam for future runtime use without schema migrations or trace-table installation. ## Parser-focused tests -- `cli/src/app.rs` unit tests cover default-help behavior, auth/config/setup/hooks/trace routing, auth bare/help/nested-help routing, command-local `--help` routing for `doctor`/`hooks`/`trace`/`sync`, and failure paths for unknown commands/options and extra arguments. +- `cli/src/app.rs` unit tests cover default-help behavior, auth/config/setup/hooks routing, auth bare/help/nested-help routing, command-local `--help` routing for `doctor`/`hooks`/`sync`, and failure paths for unknown commands/options and extra arguments. - `cli/src/app.rs` additionally validates setup contract routing for interactive default, explicit target flags, and mutually-exclusive setup flag failures. - `cli/src/services/local_db.rs` tests cover in-memory and file-backed local Turso initialization plus execute/query smoke checks. - `cli/src/services/resilience.rs` tests lock deterministic retry behavior for transient failures, timeout exhaustion, and actionable terminal error messaging. diff --git a/context/cli/default-path-catalog.md b/context/cli/default-path-catalog.md index 78a8eb8..43909eb 100644 --- a/context/cli/default-path-catalog.md +++ b/context/cli/default-path-catalog.md @@ -16,7 +16,7 @@ - global config: `/sce/config.json` - auth tokens: `/sce/auth/tokens.json` -- Agent Trace local DB: `/sce/agent-trace/local.db` +- local DB: `/sce/local.db` ### Repo-relative paths @@ -33,13 +33,6 @@ - OpenCode plugin/runtime/catalog targets under `.opencode/` - required git hook install targets under `.git/hooks/` -### Git-relative hook runtime paths - -- `sce/pre-commit-checkpoint.json` -- `sce/prompts.jsonl` -- `sce/trace-retry-queue.jsonl` -- `sce/trace-emission-ledger.txt` - ## Contract - Production CLI code should define named path accessors or constants in `default_paths.rs`, not introduce new hardcoded path owners elsewhere. @@ -47,7 +40,6 @@ - `cli/src/services/doctor.rs` now resolves the repo-local config path through `RepoPaths::sce_config_file()` for local-config health reporting and validation. - `cli/src/services/doctor.rs` also resolves OpenCode manifest/plugin/runtime/preset locations through shared `RepoPaths` and `InstallTargetPaths` accessors instead of owning those paths locally. - `cli/src/services/setup.rs` now resolves setup target directory names and required hook identifiers through `default_paths.rs` constants/accessors instead of owning those path literals locally. -- `cli/src/services/hooks.rs` now resolves git-relative runtime artifact paths (`pre-commit` checkpoint, prompt capture, retry queue, emission ledger) through `default_paths.rs` constants before `git rev-parse --git-path` expansion. - `cli/src/services/default_paths.rs` includes a regression test that scans non-test Rust source under `cli/src/` and fails when new centralized production path literals appear outside the default-path service. See also: [cli-command-surface.md](./cli-command-surface.md), [../architecture.md](../architecture.md), [../context-map.md](../context-map.md) diff --git a/context/context-map.md b/context/context-map.md index 7e972a8..100cc1c 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -7,7 +7,7 @@ Primary context files: - `context/glossary.md` Feature/domain context: -- `context/cli/cli-command-surface.md` (CLI command surface including slim top-level help visibility, setup install flow, WorkOS device authorization flow + token storage behavior, bounded resilience-wrapped sync/local-DB smoke and bootstrap behavior, nested flake release package/app installability, and Cargo local install + crates.io readiness policy) +- `context/cli/cli-command-surface.md` (CLI command surface including slim top-level help visibility, setup install flow, WorkOS device authorization flow + token storage behavior, attribution-only hook routing, bounded resilience-wrapped sync/local-DB smoke behavior, nested flake release package/app installability, and Cargo local install + crates.io readiness policy) - `context/cli/default-path-catalog.md` (canonical production CLI path-ownership contract centered on `cli/src/services/default_paths.rs`, including persisted, repo-relative, embedded-asset, install/runtime, hook, and context-path families plus the regression guard that keeps production path ownership centralized) - `context/cli/styling-service.md` (CLI text-mode output styling with `owo-colors` and `comfy-table`, TTY/`NO_COLOR` policy, and shared helper API for human-facing surfaces) - `context/cli/config-precedence-contract.md` (implemented `sce config` show/validate command contract, deterministic `flags > env > config file > defaults` resolution order, canonical `$schema` acceptance for startup-loaded `sce/config.json` files, shared auth-key env/config/optional baked-default support starting with `workos_client_id`, shared runtime resolution for flat logging plus nested `otel` observability keys, canonical Pkl-generated `sce/config.json` schema ownership plus CLI embedding/reuse contract, config-file selection order, `show` provenance output, trimmed `validate` output contract, and opt-in compiled-binary config-precedence E2E coverage contract) @@ -24,7 +24,7 @@ Feature/domain context: - `context/sce/agent-trace-pre-commit-staged-checkpoint.md` (historical pre-commit staged-checkpoint contract; current runtime baseline has replaced this path with a deterministic no-op) - `context/sce/agent-trace-commit-msg-coauthor-policy.md` (current commit-msg canonical co-author trailer policy with attribution-hooks + co-author gating and idempotent dedupe) - `context/sce/agent-trace-post-commit-dual-write.md` (historical post-commit trace finalization baseline; current runtime baseline replaces this path with a deterministic no-op) -- `context/sce/agent-trace-hook-doctor.md` (approved operator-environment contract for broadening `sce doctor` into the canonical health-and-repair entrypoint, including stable problem taxonomy, `--fix` semantics, setup-to-doctor alignment rules, and the approved downstream human text-mode layout/status/integration contract; current implementation baseline is captured inside the file) +- `context/sce/agent-trace-hook-doctor.md` (approved operator-environment contract for broadening `sce doctor` into the canonical health-and-repair entrypoint, including stable problem taxonomy, `--fix` semantics, setup-to-doctor alignment rules, the current neutral local-DB baseline, and the approved downstream human text-mode layout/status/integration contract) - `context/sce/setup-githooks-install-contract.md` (T01 canonical `sce setup --hooks` install contract for target-path resolution, idempotent outcomes, backup/rollback, and doctor-readiness alignment) - `context/sce/setup-no-backup-policy-seam.md` (implemented shared `SetupBackupPolicy` seam that classifies git-backed vs non-git-backed setup targets and feeds both config-install and required-hook install flows) - `context/sce/setup-githooks-hook-asset-packaging.md` (T02 compile-time `sce setup --hooks` required-hook template packaging contract and setup-service accessor surface) @@ -33,7 +33,7 @@ Feature/domain context: - `context/sce/cli-security-hardening-contract.md` (T06 CLI redaction contract, setup `--repo` canonicalization/validation, and setup write-permission probe behavior) - `context/sce/agent-trace-post-rewrite-local-remap-ingestion.md` (T08 `post-rewrite` local remap ingestion contract with strict pair parsing, rewrite-method normalization, and deterministic replay-key derivation) - `context/sce/agent-trace-rewrite-trace-transformation.md` (T09 rewritten-SHA trace transformation contract with rewrite metadata, confidence-to-quality mapping, and current notes-persistence behavior) -- `context/sce/agent-trace-core-schema-migrations.md` (current Agent Trace local DB empty-file baseline with create/open-only runtime behavior and no schema bootstrap) +- `context/sce/agent-trace-core-schema-migrations.md` (current neutral local DB empty-file baseline with create/open-only runtime behavior and no schema bootstrap) - `context/sce/agent-trace-retry-queue-observability.md` (T14 retry queue recovery contract plus reconciliation/runtime observability metrics and DB-first queue schema additions) - `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md` (T01 Local Hooks MVP production contract freeze and deterministic gap matrix for `agent-trace-local-hooks-production-mvp`) - `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus the current minimal runtime behavior: disabled-default commit-msg attribution and no-op `pre-commit`/`post-commit`/`post-rewrite` entrypoints) diff --git a/context/glossary.md b/context/glossary.md index 5e08873..d470190 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -22,10 +22,10 @@ - `cli cargo install contract`: Supported Cargo install surface for `sce`: crates.io (`cargo install sce`), git (`cargo install --git https://github.com/crocoder-dev/shared-context-engineering sce --locked`), and local checkout (`cargo install --path cli --locked`). - `cli crates.io publication posture`: Current Cargo package posture in `cli/Cargo.toml` where crates.io-facing metadata is publication-ready for the `sce` crate, with crate-facing install guidance owned by `cli/README.md`. - `Nix performance recommendations`: Repo-local operator guidance in `AGENTS.md` covering optional user-level `~/.config/nix/nix.conf` tuning (`max-jobs = auto`, `cores = 0`) and the explicit root/admin-only boundary for `/etc/nix/nix.conf` `auto-optimise-store = true`. -- `sce` (CLI foundation): Rust binary crate at `cli/` with implemented auth command flows (`auth login|renew|logout|status`) plus auth-local bare-command guidance (`sce auth`, `sce auth --help`), implemented `trace prompts ` prompt-inspection flow, implemented setup installation flow, implemented `hooks` subcommand routing/validation entrypoints, and placeholder behavior for `sync`. +- `sce` (CLI foundation): Rust binary crate at `cli/` with implemented auth command flows (`auth login|renew|logout|status`) plus auth-local bare-command guidance (`sce auth`, `sce auth --help`), implemented setup installation flow, implemented attribution-only `hooks` subcommand routing/validation entrypoints, and placeholder behavior for `sync`. - `command surface contract`: The static command catalog in `cli/src/command_surface.rs` that marks each top-level command as `implemented` or `placeholder`. - `top-level help visibility metadata`: Per-command `show_in_top_level_help` metadata in `cli/src/command_surface.rs` that controls whether a known command appears in `sce`, `sce help`, and `sce --help` without affecting direct invocation or command classification. -- `command loop`: The `clap` derive-based parser + dispatcher in `cli/src/cli_schema.rs` and `cli/src/app.rs` that routes `help`, `config`, `setup`, `doctor`, `auth`, `hooks`, `trace`, `sync`, `version`, and `completion`, executes implemented command flows, emits command-local help payloads for supported subcommand trees, emits TODO placeholders for deferred commands, and returns deterministic actionable errors for invalid invocation. +- `command loop`: The `clap` derive-based parser + dispatcher in `cli/src/cli_schema.rs` and `cli/src/app.rs` that routes `help`, `config`, `setup`, `doctor`, `auth`, `hooks`, `sync`, `version`, and `completion`, executes implemented command flows, emits command-local help payloads for supported subcommand trees, emits TODO placeholders for deferred commands, and returns deterministic actionable errors for invalid invocation. - `sce dependency baseline`: Current crate dependency set declared in `cli/Cargo.toml` (`anyhow`, `clap`, `clap_complete`, `dirs`, `hmac`, `inquire`, `opentelemetry`, `opentelemetry-otlp`, `opentelemetry_sdk`, `reqwest`, `serde`, `serde_json`, `sha2`, `tokio`, `tracing`, `tracing-opentelemetry`, `tracing-subscriber`, `turso`) and validated through normal compile/test coverage. - `local Turso adapter`: Async data-layer module in `cli/src/services/local_db.rs` that initializes local DB targets with `turso::Builder::new_local(...)` and runs execute/query smoke checks. - `sync Turso smoke gate`: Behavior in `cli/src/services/sync.rs` where the `sync` placeholder command runs an in-memory local Turso smoke check under a lazily initialized shared tokio current-thread runtime before returning placeholder cloud-sync messaging. @@ -61,9 +61,8 @@ - `sce completion command surface`: Implemented top-level CLI command routed by `cli/src/app.rs` to `cli/src/services/completion.rs`, requiring `--shell ` and returning a deterministic shell completion script on stdout. - `sce shared output-format contract`: Canonical parser contract in `cli/src/services/output_format.rs` (`OutputFormat`) that centralizes supported `--format` values (`text`, `json`) and emits command-specific actionable invalid-value guidance (`Run ' --help' ...`) for commands wired to dual-output rendering. - `sce shell completion contract`: Deterministic CLI completion contract where `sce completion --shell ` emits parser-aligned Bash/Zsh/Fish completion scripts for current top-level commands and supported options/subcommands. -- `trace prompts command surface`: Implemented `sce trace prompts ` read/query surface in `cli/src/cli_schema.rs` + `cli/src/services/trace.rs`, supporting `--format ` plus legacy `--json`, unique persisted commit prefix lookup, and deterministic missing/ambiguous commit diagnostics. - `CLI default path catalog`: Canonical production path-ownership contract in `cli/src/services/default_paths.rs`; it is the shared owner for non-test production CLI path definitions, covering per-user persisted paths plus repo-relative, embedded-asset, install/runtime, hook, and context-path definitions used by production CLI code. -- `SCE default path policy seam`: Canonical path resolver in `cli/src/services/default_paths.rs` that owns config/state/cache root resolution, named default paths, and an explicit inventory for the current default persisted artifacts (`global config`, `auth tokens`, `Agent Trace local DB`); on Linux those defaults resolve to `$XDG_CONFIG_HOME/sce/config.json`, `$XDG_STATE_HOME/sce/auth/tokens.json`, and `$XDG_STATE_HOME/sce/agent-trace/local.db` with platform-equivalent `dirs` fallbacks elsewhere. The same module is also the canonical owner for broader production CLI path definitions and is protected by a regression test that fails when new non-test production path literals are introduced outside `default_paths.rs`. +- `SCE default path policy seam`: Canonical path resolver in `cli/src/services/default_paths.rs` that owns config/state/cache root resolution, named default paths, and an explicit inventory for the current default persisted artifacts (`global config`, `auth tokens`, `local DB`); on Linux those defaults resolve to `$XDG_CONFIG_HOME/sce/config.json`, `$XDG_STATE_HOME/sce/auth/tokens.json`, and `$XDG_STATE_HOME/sce/local.db` with platform-equivalent `dirs` fallbacks elsewhere. The same module is also the canonical owner for broader production CLI path definitions and is protected by a regression test that fails when new non-test production path literals are introduced outside `default_paths.rs`. - `cli config precedence contract`: Deterministic runtime value resolution in `cli/src/services/config.rs` with precedence `flags > env > config file > defaults` for flag-backed keys (`log_level`, `timeout_ms`) plus shared app-runtime observability keys (`log_format`, `log_file`, `log_file_mode`, `otel.enabled`, `otel.exporter_otlp_endpoint`, `otel.exporter_otlp_protocol`) consumed by `cli/src/app.rs`; config discovery order is `--config`, `SCE_CONFIG_FILE`, then default discovered global+local paths (`${config_root}/sce/config.json` merged before `.sce/config.json`, with local overriding per key, where `config_root` comes from the shared default path policy seam and resolves to `$XDG_CONFIG_HOME` / `dirs::config_dir()` semantics with platform fallback behavior rather than the old state/data-root default). Runtime startup config loading now also permits the canonical top-level `"$schema": "https://sce.crocoder.dev/config.json"` declaration anywhere those config files are parsed. - `sce config schema artifact`: Canonical JSON Schema for global and repo-local `sce/config.json` files, authored in `config/pkl/base/sce-config-schema.pkl`, generated to `config/schema/sce-config.schema.json`, and embedded by `cli/src/services/config.rs` for shared `sce config validate` and doctor config validation. The current schema accepts the canonical `$schema` declaration, flat logging keys (`log_level`, `log_format`, `log_file`, `log_file_mode`), nested `otel` keys (`enabled`, `exporter_otlp_endpoint`, `exporter_otlp_protocol`), existing auth/config keys, and enforces the schema-level dependency that `log_file_mode` requires `log_file`. - `bash tool policy config surface`: Nested repo config namespace under `.sce/config.json` at `policies.bash`, currently supporting unique built-in `presets` plus repo-owned `custom` argv-prefix rules with deterministic validation, merged global/local resolution, and first-class `sce config show|validate` reporting. @@ -94,9 +93,9 @@ - `agent trace payload builder`: Canonical T03 builder contract in `cli/src/services/agent_trace.rs` (`build_trace_payload`) that layers on top of the adapter, preserves deterministic output for identical input, and normalizes AI `model_id` values toward `provider/model` form when inferable. - `agent trace schema validation suite`: T03 compliance test slice in `services::agent_trace::tests` that validates payload JSON against the published Agent Trace trace-record schema with draft-2020-12 format checks enabled (`uri`, `date-time`, `uuid`) and a local version-pattern compatibility patch for the current app semver emitted from `CARGO_PKG_VERSION`. - `agent trace commit-msg co-author policy`: Current contract in `cli/src/services/hooks.rs` (`apply_commit_msg_coauthor_policy`) that applies exactly one canonical trailer (`Co-authored-by: SCE `) only when attribution hooks are enabled and SCE is not disabled; duplicate canonical trailers are deduped idempotently. -- `agent trace local DB empty-file baseline`: Current `cli/src/services/local_db.rs` (`ensure_agent_trace_local_db_ready_blocking`) behavior where the deterministic per-user Agent Trace DB path (`${XDG_STATE_HOME:-~/.local/state}/sce/agent-trace/local.db` on Linux; platform-equivalent user state root elsewhere) has its parent directory created and the DB file opened/created without applying schema migrations or installing trace tables. +- `local DB empty-file baseline`: Current `cli/src/services/local_db.rs` neutral local DB behavior where the deterministic per-user local DB path (`${XDG_STATE_HOME:-~/.local/state}/sce/local.db` on Linux; platform-equivalent user state root elsewhere) remains an empty-file/open-only baseline without schema migrations or trace tables. - `agent trace post-commit idempotency ledger`: T06 seam (`TraceEmissionLedger`) in `cli/src/services/hooks.rs` used to prevent duplicate emission for the same commit SHA and to mark successful dual-write completion. -- `sce doctor` operator-health contract: `cli/src/services/doctor.rs` now implements the current approved operator-health surface in `context/sce/agent-trace-hook-doctor.md`: `sce doctor --fix` selects repair intent, help/output expose deterministic doctor mode, JSON includes stable problem taxonomy/fixability fields plus database records and fix-result records, the runtime validates state-root resolution, global and repo-local `sce/config.json` readability/schema health, Agent Trace local DB path/health, DB-parent readiness barriers, git availability, non-repo vs bare-repo targeting failures, effective hook-path source resolution, required hook presence/executable/content drift against canonical embedded hook assets, and repo-root installed OpenCode integration presence for `OpenCode plugins`, `OpenCode agents`, `OpenCode commands`, and `OpenCode skills`. Human text mode now uses the approved sectioned layout (`Environment`, `Configuration`, `Repository`, `Git Hooks`, `Integrations`), `SCE doctor diagnose` / `SCE doctor fix` headers, bracketed `[PASS]`/`[FAIL]`/`[MISS]` status tokens with shared-style green/red colorization when enabled, simplified `label (path)` row formatting, top-level-only hook rows, and presence-only integration parent/child rows where missing required files surface as `[MISS]` children and `[FAIL]` parent groups. Fix mode still reuses canonical setup hook installation for missing/stale/non-executable required hooks and missing hooks directories and can bootstrap the canonical missing SCE-owned Agent Trace DB parent directory. +- `sce doctor` operator-health contract: `cli/src/services/doctor.rs` now implements the current approved operator-health surface in `context/sce/agent-trace-hook-doctor.md`: `sce doctor --fix` selects repair intent, help/output expose deterministic doctor mode, JSON includes stable problem taxonomy/fixability fields plus database records and fix-result records, the runtime validates state-root resolution, global and repo-local `sce/config.json` readability/schema health, local DB path/health, DB-parent readiness barriers, git availability, non-repo vs bare-repo targeting failures, effective hook-path source resolution, required hook presence/executable/content drift against canonical embedded hook assets, and repo-root installed OpenCode integration presence for `OpenCode plugins`, `OpenCode agents`, `OpenCode commands`, and `OpenCode skills`. Human text mode now uses the approved sectioned layout (`Environment`, `Configuration`, `Repository`, `Git Hooks`, `Integrations`), `SCE doctor diagnose` / `SCE doctor fix` headers, bracketed `[PASS]`/`[FAIL]`/`[MISS]` status tokens with shared-style green/red colorization when enabled, simplified `label (path)` row formatting, top-level-only hook rows, and presence-only integration parent/child rows where missing required files surface as `[MISS]` children and `[FAIL]` parent groups. Fix mode still reuses canonical setup hook installation for missing/stale/non-executable required hooks and missing hooks directories and can bootstrap the canonical missing SCE-owned local DB parent directory. - `cli warnings-denied lint policy`: `cli/Cargo.toml` sets `warnings = "deny"`, so plain `cargo clippy --manifest-path cli/Cargo.toml` already fails on warnings without needing an extra `-- -D warnings` tail. - `agent trace post-rewrite local remap ingestion`: T08 contract in `cli/src/services/hooks.rs` (`finalize_post_rewrite_remap`) that parses git `post-rewrite` old/new SHA pairs, captures normalized rewrite method (`amend`, `rebase`, or lowercase passthrough), derives deterministic `post-rewrite:::` idempotency keys, and dispatches replay-safe remap ingestion requests. - `agent trace rewrite trace transformation`: T09 contract in `cli/src/services/hooks.rs` (`finalize_rewrite_trace`) that materializes rewritten-SHA Agent Trace records with `rewrite_from`/`rewrite_method`/`rewrite_confidence` metadata, enforces confidence range normalization (`0.00`..`1.00`), maps quality status thresholds (`final`/`partial`/`needs_review`), and currently preserves notes persistence plus retry fallback while local DB writes remain disconnected. diff --git a/context/overview.md b/context/overview.md index 8928eb3..2fb4bf9 100644 --- a/context/overview.md +++ b/context/overview.md @@ -7,7 +7,7 @@ It also includes an early Rust CLI foundation at `cli/` for Shared Context Engin Operator-facing CLI usage currently comes from a slimmed top-level `sce --help` surface, command-local `--help` output, and focused context files under `context/cli/` and `context/sce/`. The CLI crate currently depends on `anyhow`, `clap`, `clap_complete`, `comfy-table`, `dirs`, `hmac`, `inquire`, `opentelemetry`, `opentelemetry-otlp`, `opentelemetry_sdk`, `owo-colors`, `reqwest`, `serde`, `serde_json`, `sha2`, `tokio`, `tracing`, `tracing-opentelemetry`, `tracing-subscriber`, and `turso`. -Its command loop is implemented with `clap` derive-based argument parsing and `anyhow` error handling. Top-level help now shows a slim command list without implemented/placeholder labels and hides `auth`, `hooks`, `trace`, and `sync` from `sce`, `sce help`, and `sce --help`, while those commands remain directly invocable. The runtime includes implemented auth flows (`auth login|logout|status`) plus auth-local guidance for bare `sce auth` / `sce auth --help`, implemented config inspection/validation (`config show`/`config validate`) with bare `sce config` routing to the same help payload as `sce config --help`, real setup orchestration, implemented `doctor` diagnosis-vs-fix CLI surface and stable output-shape scaffolding (`sce doctor`, `sce doctor --fix`, `--format text|json`) plus current installed-CLI/global-state diagnostics for state-root resolution, global config validation, Agent Trace local DB health, writable DB-parent-path checks, git availability/repository targeting, bare-repo refusal, effective hook-path source detection, an intentionally empty repo-scoped SCE database section for the active repository, Agent Trace local DB reporting in default doctor output, required-hook presence/executable/content-drift checks against canonical embedded SCE-managed hook assets, repair-mode reuse of canonical setup hook installation for missing/stale/non-executable required hooks and missing hooks directories, and doctor-owned bootstrap repair for the missing SCE-owned Agent Trace DB parent directory, implemented `hooks` subcommand routing/validation entrypoints, implemented machine-readable runtime identification (`version`), implemented shell completion script generation via `clap_complete` (`completion --shell `), and placeholder dispatch for deferred commands (`sync`) through explicit service contracts. +Its command loop is implemented with `clap` derive-based argument parsing and `anyhow` error handling. Top-level help now shows a slim command list without implemented/placeholder labels and hides `auth`, `hooks`, and `sync` from `sce`, `sce help`, and `sce --help`, while those commands remain directly invocable. The runtime includes implemented auth flows (`auth login|logout|status`) plus auth-local guidance for bare `sce auth` / `sce auth --help`, implemented config inspection/validation (`config show`/`config validate`) with bare `sce config` routing to the same help payload as `sce config --help`, real setup orchestration, implemented `doctor` diagnosis-vs-fix CLI surface and stable output-shape scaffolding (`sce doctor`, `sce doctor --fix`, `--format text|json`) plus current installed-CLI/global-state diagnostics for state-root resolution, global config validation, local DB path + health, writable DB-parent-path checks, git availability/repository targeting, bare-repo refusal, effective hook-path source detection, an intentionally empty repo-scoped SCE database section for the active repository, required-hook presence/executable/content-drift checks against canonical embedded SCE-managed hook assets, repair-mode reuse of canonical setup hook installation for missing/stale/non-executable required hooks and missing hooks directories, and doctor-owned bootstrap repair for the missing canonical local DB parent directory, implemented attribution-only `hooks` subcommand routing/validation entrypoints with commit-msg-only behavior behind a disabled-default gate, implemented machine-readable runtime identification (`version`), implemented shell completion script generation via `clap_complete` (`completion --shell `), and placeholder dispatch for deferred commands (`sync`) through explicit service contracts. The command loop now enforces a stable exit-code contract in `cli/src/app.rs`: `2` parse failures, `3` invocation validation failures, `4` runtime failures, and `5` dependency startup failures. The same runtime also emits stable user-facing stderr error classes (`SCE-ERR-PARSE`, `SCE-ERR-VALIDATION`, `SCE-ERR-RUNTIME`, `SCE-ERR-DEPENDENCY`) using deterministic `Error []: ...` diagnostics with class-default `Try:` remediation appended when missing. The app runtime now also includes a structured observability baseline in `cli/src/services/observability.rs`: deterministic env-controlled log threshold/format (`SCE_LOG_LEVEL` defaults to `error`; `SCE_LOG_FORMAT` defaults to `text`), optional file sink controls (`SCE_LOG_FILE`, `SCE_LOG_FILE_MODE` with deterministic `truncate` default), optional OpenTelemetry export bootstrap (`SCE_OTEL_ENABLED`, `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_PROTOCOL`), stable lifecycle event IDs, and stderr-only primary emission so stdout command payloads remain pipe-safe. @@ -19,10 +19,10 @@ The CLI now compiles an embedded setup asset manifest from `config/.opencode/**` The setup service also provides repository-root install orchestration: it resolves interactive or flag-based target selection, installs embedded assets, reports deterministic completion details (selected target(s), installed file counts, and backup actions), and now skips `.opencode`/`.claude` backup creation in git-backed repositories while surfacing deterministic git-recovery guidance if a git-backed config swap fails. The CLI now also applies baseline security hardening for reliability-driven automation: diagnostics/logging paths use deterministic secret redaction, `sce setup --hooks --repo ` canonicalizes and validates repository paths before execution, and setup write flows run explicit directory write-permission probes before staging/swap operations. The config service now provides deterministic runtime config resolution with explicit precedence (`flags > env > config file > defaults`), strict config-file validation (`$schema`, `log_level`, `log_format`, `log_file`, `log_file_mode`, `timeout_ms`, `workos_client_id`, nested `otel`, and nested `policies.bash`), deterministic default discovery/merge of global+local config files (`${config_root}/sce/config.json` then `.sce/config.json` with local override, where `config_root` comes from the shared default-path seam with XDG/`dirs::config_dir()` config-root resolution), defaults for the resolved observability value set (`log_level=error`, `log_format=text`, `log_file_mode=truncate`, `otel.enabled=false`, `otel.exporter_otlp_endpoint=http://127.0.0.1:4317`, `otel.exporter_otlp_protocol=grpc`), shared auth-key resolution with optional baked defaults starting at `workos_client_id`, first-class bash-policy preset/custom parsing with deterministic conflict and duplicate-prefix validation, and a canonical Pkl-authored `sce/config.json` JSON Schema generated to `config/schema/sce-config.schema.json` and embedded by `cli/src/services/config.rs` for both `sce config validate` and doctor-time config checks. Runtime startup config loading now keeps parity with that schema by accepting the canonical `"$schema": "https://sce.crocoder.dev/config.json"` declaration in repo-local and global config files, so startup commands such as `sce version` no longer fail before dispatch on that field. App-runtime observability now consumes that mixed-shape observability config (flat logging keys plus nested `otel`) through the shared resolver, so env values still override config-file values while config files provide deterministic fallback for file logging and OTEL bootstrap; `sce config show` reports resolved observability/auth/policy values with provenance, while `sce config validate` is now a trimmed validation surface that reports only pass/fail plus validation errors or warnings in text and JSON modes. The canonical preset catalog and matching contract live in `config/pkl/data/bash-policy-presets.json` and `context/sce/bash-tool-policy-enforcement-contract.md`. -The shared default path service in `cli/src/services/default_paths.rs` is now the canonical owner for production CLI path definitions. It resolves per-user config/state/cache roots, exposes the current persisted-artifact inventory (global config, auth tokens, Agent Trace local DB), and also defines the repo-relative, embedded-asset, install/runtime, hook, and context-path accessors consumed across current CLI production code. Non-test production modules should consume this shared catalog instead of hardcoding owned path literals. No default cache-backed persisted artifact currently exists, so cache-root resolution remains available without speculative cache-path features and no legacy default-path fallback is supported. +The shared default path service in `cli/src/services/default_paths.rs` is now the canonical owner for production CLI path definitions. It resolves per-user config/state/cache roots, exposes the current persisted-artifact inventory (global config, auth tokens, local DB), and also defines the repo-relative, embedded-asset, install/runtime, hook, and context-path accessors consumed across current CLI production code. Non-test production modules should consume this shared catalog instead of hardcoding owned path literals. No default cache-backed persisted artifact currently exists, so cache-root resolution remains available without speculative cache-path features and no legacy default-path fallback is supported. The same config resolver now also owns the attribution-hooks gate used by local hook runtime: `SCE_ATTRIBUTION_HOOKS_ENABLED` overrides `policies.attribution_hooks.enabled`, and the gate defaults to disabled. Generated config now includes repo-local bash-policy enforcement assets for OpenCode only: OpenCode blocks `bash` tool calls before subprocess launch via `config/.opencode/plugins/sce-bash-policy.ts` plus shared runtime logic and preset data in `config/.opencode/lib/` (also emitted for `config/automated/.opencode/**`). Claude bash-policy enforcement has been removed from generated outputs. -The `doctor` command now exposes explicit inspection mode (`sce doctor`) and repair-intent mode (`sce doctor --fix`) at the CLI/help/schema level while keeping diagnosis mode read-only. It now validates both current global operator health and the current repo/hook-integrity slice: state-root resolution, global config path resolution, global and repo-local `sce/config.json` readability/schema validity, Agent Trace local DB path + health, DB parent-directory readiness, git availability, non-repo vs bare-repo targeting failures, effective git hook-path source (default, per-repo `core.hooksPath`, or global `core.hooksPath`), hooks-directory health, required hook presence/executable permissions/content drift against canonical embedded SCE-managed hook assets, and repo-root OpenCode integration presence across the installed `plugins`, `agents`, `commands`, and `skills` inventories with embedded SHA-256 content verification for OpenCode assets. Text mode now renders the approved human-only layout with ordered `Environment` / `Configuration` / `Repository` / `Git Hooks` / `Integrations` sections, `SCE doctor diagnose` / `SCE doctor fix` headers, bracketed `[PASS]`/`[FAIL]`/`[MISS]` status tokens, shared-style green pass plus red fail/miss coloring when color output is enabled, simplified `label (path)` row formatting, top-level-only hook rows, and integration parent/child rows that reflect missing vs content-mismatch states; JSON output remains unchanged. Repo-scoped database reporting is empty by default because no repo-owned SCE database currently exists, while the Agent Trace local DB is reported in default doctor output. Fix mode reuses the canonical setup hook install flow to repair missing/stale/non-executable required hooks and can also bootstrap the missing SCE-owned Agent Trace DB parent directory while preserving manual-only guidance for unsupported issues. +The `doctor` command now exposes explicit inspection mode (`sce doctor`) and repair-intent mode (`sce doctor --fix`) at the CLI/help/schema level while keeping diagnosis mode read-only. It now validates both current global operator health and the current repo/hook-integrity slice: state-root resolution, global config path resolution, global and repo-local `sce/config.json` readability/schema validity, local DB path + health, DB parent-directory readiness, git availability, non-repo vs bare-repo targeting failures, effective git hook-path source (default, per-repo `core.hooksPath`, or global `core.hooksPath`), hooks-directory health, required hook presence/executable permissions/content drift against canonical embedded SCE-managed hook assets, and repo-root OpenCode integration presence across the installed `plugins`, `agents`, `commands`, and `skills` inventories with embedded SHA-256 content verification for OpenCode assets. Text mode now renders the approved human-only layout with ordered `Environment` / `Configuration` / `Repository` / `Git Hooks` / `Integrations` sections, `SCE doctor diagnose` / `SCE doctor fix` headers, bracketed `[PASS]`/`[FAIL]`/`[MISS]` status tokens, shared-style green pass plus red fail/miss coloring when color output is enabled, simplified `label (path)` row formatting, top-level-only hook rows, and integration parent/child rows that reflect missing vs content-mismatch states; JSON output now reports the same local DB record under `local_db`. Repo-scoped database reporting is empty by default because no repo-owned SCE database currently exists. Fix mode reuses the canonical setup hook install flow to repair missing/stale/non-executable required hooks and can also bootstrap the missing canonical local DB parent directory while preserving manual-only guidance for unsupported issues. The `sync` placeholder performs a local Turso smoke check through a lazily initialized shared tokio current-thread runtime with bounded retry/timeout/backoff controls, then reports a deferred cloud-sync plan from a placeholder gateway contract; persistent local DB schema bootstrap now uses the same bounded resilience wrapper. The repository-root flake (`flake.nix`) now applies a Rust overlay-backed stable toolchain pinned to `1.93.1` (with `rustfmt` and `clippy`), reads package/check version from the repo-root `.version` file, builds `packages.sce` through a Crane `buildDepsOnly` + `buildPackage` pipeline with filtered package sources for the Cargo tree plus required embedded config/assets, and runs `cli-tests`, `cli-clippy`, and `cli-fmt` through Crane-backed check derivations (`cargoTest`, `cargoClippy`, `cargoFmt`) that reuse the same filtered source/toolchain setup. The root flake also exposes release install/run outputs directly as `packages.sce` (with `packages.default = packages.sce`) plus `apps.sce` and `apps.default`, so `nix build .#default`, `nix run . -- --help`, `nix run .#sce -- --help`, and `nix profile install github:crocoder-dev/shared-context-engineering` all target the packaged `sce` binary through the same flake-owned entrypoints. @@ -45,8 +45,8 @@ The no-git-wrapper Agent Trace initiative baseline contract is defined in `conte The CLI now includes a task-scoped Agent Trace schema adapter contract in `cli/src/services/agent_trace.rs`, with deterministic mapping of internal attribution input to Agent Trace-shaped record structures documented in `context/sce/agent-trace-schema-adapter.md`. The Agent Trace service now also provides a deterministic payload-builder path (`build_trace_payload`) with AI `model_id` normalization and schema-compliance validation coverage documented in `context/sce/agent-trace-payload-builder-validation.md`. The hooks service now uses a minimal attribution-only runtime: `commit-msg` is the only hook that mutates behavior, conditionally injecting exactly one canonical SCE trailer when the attribution-hooks gate is enabled and `SCE_DISABLED` is false; `pre-commit`, `post-commit`, and `post-rewrite` are deterministic no-op entrypoints. -The CLI now also includes an approved operator-environment doctor contract documented in `context/sce/agent-trace-hook-doctor.md`; the runtime now matches the implemented T06 slice for `sce doctor --fix` parsing/help, stable problem/fix-result reporting, canonical hook-repair reuse, and bounded doctor-owned Agent Trace directory bootstrap for the missing SCE-owned DB parent path. -The local DB service now limits the Agent Trace runtime path to creating/opening the configured per-user database file with no schema bootstrap or trace tables; this behavior is documented in `context/sce/agent-trace-core-schema-migrations.md`. +The CLI now also includes an approved operator-environment doctor contract documented in `context/sce/agent-trace-hook-doctor.md`; the runtime now matches the implemented T06 slice for `sce doctor --fix` parsing/help, stable problem/fix-result reporting, canonical hook-repair reuse, and bounded doctor-owned local-DB directory bootstrap for the missing SCE-owned DB parent path. +The local DB service now limits the runtime path to a neutral per-user local DB file with no schema bootstrap or trace tables; this behavior is documented in `context/sce/agent-trace-core-schema-migrations.md`. The hooks command surface now also supports concrete runtime subcommand routing (`pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`) with deterministic argument/STDIN validation. Current runtime behavior is commit-msg-only attribution and disabled by default: the attribution gate enables canonical trailer insertion in `commit-msg`, while `pre-commit`, `post-commit`, and `post-rewrite` remain deterministic no-ops. This behavior is documented in `context/sce/agent-trace-hooks-command-routing.md`. The setup service now also exposes deterministic required-hook embedded asset accessors (`iter_required_hook_assets`, `get_required_hook_asset`) backed by canonical templates in `cli/assets/hooks/` for `pre-commit`, `commit-msg`, and `post-commit`; this behavior is documented in `context/sce/setup-githooks-hook-asset-packaging.md`. The setup service now also includes required-hook install orchestration (`install_required_git_hooks`) that resolves repository root and effective hooks path from git truth, enforces deterministic per-hook outcomes (`Installed`/`Updated`/`Skipped`), preserves backup-and-restore rollback for non-git-backed targets, and skips backup creation plus backup-based rollback in git-backed repositories with deterministic git-recovery guidance on swap failures; this behavior is documented in `context/sce/setup-githooks-install-flow.md`. @@ -88,7 +88,7 @@ Lightweight post-task verification baseline (required after each completed task) - Use `context/patterns.md` for implementation and operational conventions. - Use `context/decisions/` for explicit architecture decisions. - Use `context/plans/` for active plan execution state and task handoff continuity. -- Use `context/cli/cli-command-surface.md` for current command-surface and command-local help coverage, including `auth login|renew|logout|status` and `trace prompts `, plus local Turso adapter behavior and module-boundary details of the `sce` placeholder crate. +- Use `context/cli/cli-command-surface.md` for current command-surface and command-local help coverage, including `auth login|renew|logout|status`, attribution-only `hooks`, and local Turso adapter behavior plus module-boundary details of the `sce` placeholder crate. - Use `context/cli/default-path-catalog.md` for the current canonical CLI path-ownership contract centered on `cli/src/services/default_paths.rs`. - Use `context/sce/shared-context-plan-workflow.md` for the canonical planning-session workflow (`/change-to-plan`) including clarification gating and `/next-task` handoff contract. - Use `context/sce/plan-code-overlap-map.md` for the current overlap/dedup inventory across Shared Context Plan/Code agents, related commands, and core skills. @@ -103,7 +103,7 @@ Lightweight post-task verification baseline (required after each completed task) - Use `context/sce/agent-trace-hook-doctor.md` for the approved `sce doctor` operator-environment contract, including the current T02 implementation baseline for `--fix` command-surface/output scaffolding, the stable problem/fixability taxonomy, and the rule that new setup/install surfaces must extend doctor coverage. - Use `context/sce/agent-trace-post-rewrite-local-remap-ingestion.md` for the implemented T08 post-rewrite local remap ingestion pipeline (`post-rewrite` pair parsing, rewrite-method normalization, and deterministic idempotency-key derivation). - Use `context/sce/agent-trace-rewrite-trace-transformation.md` for the implemented T09 rewritten-SHA trace transformation path (`finalize_rewrite_trace`), confidence-based quality status mapping, and current persistence semantics. -- Use `context/sce/agent-trace-core-schema-migrations.md` for the current Agent Trace local DB empty-file baseline and removed schema-bootstrap behavior. +- Use `context/sce/agent-trace-core-schema-migrations.md` for the current neutral local DB empty-file baseline and removed schema-bootstrap behavior. - Use `context/sce/agent-trace-hosted-event-intake-orchestration.md` for the implemented T12 hosted intake contract (GitHub/GitLab signature verification, old/new head resolution, deterministic reconciliation-run idempotency keys, and replay-safe run insertion outcomes). - Use `context/sce/agent-trace-rewrite-mapping-engine.md` for the implemented T13 hosted mapping engine contract (patch-id exact matching, range-diff/fuzzy scoring precedence, confidence thresholds, and deterministic unresolved handling). - Use `context/sce/agent-trace-retry-queue-observability.md` for the implemented T14 retry replay contract (notes/DB target-scoped recovery, per-attempt runtime/error-class metrics, reconciliation mapped/unmapped + confidence histogram snapshots, and DB-first retry/metrics schema additions). diff --git a/context/patterns.md b/context/patterns.md index f4ea2fc..836efee 100644 --- a/context/patterns.md +++ b/context/patterns.md @@ -133,7 +133,7 @@ - For retry replay seams, process fallback queue entries in bounded batches, avoid same-pass duplicate trace processing, retry only failed targets, emit per-attempt runtime + persistence error-class metrics for operational visibility, and run a bounded replay pass from production post-commit/post-rewrite hook runtime with deterministic summary output. - For post-rewrite remap ingestion seams, parse ` ` pairs from hook input strictly, ignore empty/no-op self-mapping rows, normalize rewrite method labels to lowercase (`amend`/`rebase` when recognized), and derive deterministic per-pair idempotency keys before dispatching remap requests. - For rewrite trace transformation seams, materialize rewritten records through the canonical Agent Trace builder path, require finite confidence in `[0.0, 1.0]`, normalize confidence to two-decimal metadata strings, map quality thresholds to `final` (`>= 0.90`), `partial` (`0.60..0.89`), and `needs_review` (`< 0.60`), and keep persistence behavior aligned with the currently active targets rather than assuming local DB parity. -- For the current local DB baseline, resolve one deterministic per-user persistent DB target (Linux: `${XDG_STATE_HOME:-~/.local/state}/sce/agent-trace/local.db`; platform-equivalent state roots elsewhere), create parent directories before first use, and only create/open the DB file without schema bootstrap or trace-table installation. +- For the current local DB baseline, resolve one deterministic per-user persistent DB target (Linux: `${XDG_STATE_HOME:-~/.local/state}/sce/local.db`; platform-equivalent state roots elsewhere), keep the path neutral rather than Agent Trace-branded, create parent directories before first use, and only create/open the DB file without schema bootstrap or trace-table installation. - For hosted event intake seams, verify provider signatures before payload parsing (GitHub `sha256=` HMAC over body, GitLab token-equality secret check), resolve old/new heads from provider payload fields, and derive deterministic reconciliation run idempotency keys from provider+event+repo+head tuple material. - For hosted rewrite mapping seams, resolve candidates deterministically in strict precedence order (patch-id exact, then range-diff score, then fuzzy score), classify top-score ties as `ambiguous`, enforce low-confidence unresolved behavior below `0.60`, and preserve stable outcome ordering via canonical candidate SHA sorting. - For hosted reconciliation observability, publish run-level mapped/unmapped counts, confidence histogram buckets, runtime timing, and normalized error-class labels so retry/quality drift can be monitored without requiring a full dashboard surface. diff --git a/context/plans/agent-trace-removal-and-hook-noop-reset.md b/context/plans/agent-trace-removal-and-hook-noop-reset.md index 2146d52..7784761 100644 --- a/context/plans/agent-trace-removal-and-hook-noop-reset.md +++ b/context/plans/agent-trace-removal-and-hook-noop-reset.md @@ -41,12 +41,15 @@ - Files changed: `cli/src/services/config.rs`, `cli/src/services/hooks.rs`, `config/pkl/base/sce-config-schema.pkl`, `config/schema/sce-config.schema.json` - Evidence: `nix run .#pkl-check-generated`; `nix flake check`; `nix develop -c sh -c 'cd cli && cargo check'` -- [ ] T03: Remove Agent Trace-specific command, doctor, setup, and path-surface behavior (status:todo) +- [x] T03: Remove Agent Trace-specific command, doctor, setup, and path-surface behavior (status:done) - Task ID: T03 - Goal: Eliminate remaining active runtime references to Agent Trace from command help, doctor/setup readiness checks, default-path inventories, and related operator-facing surfaces while preserving the optional attribution-only hook baseline. - Boundaries (in/out of scope): In scope: command/help text, doctor checks, setup/install expectations, default-path/service references, and removal of dead trace-only seams exposed to users/operators. Out of scope: broad unrelated CLI polish and the future tracing redesign. - Done when: User-facing runtime/help/doctor/setup/path surfaces no longer present Agent Trace as an active supported feature; any retained hook trigger surface is described as attribution-only and disabled by default; removed trace-only code paths no longer drive warnings/dead branches. - Verification notes (commands or checks): Inspect command-surface, doctor, setup, and default-path outputs/contracts for removed Agent Trace references; run targeted checks for affected CLI modules, then defer full repo checks to the final task. + - Completed: 2026-04-08 + - Files changed: `cli/src/app.rs`, `cli/src/cli_schema.rs`, `cli/src/command_surface.rs`, `cli/src/services/default_paths.rs`, `cli/src/services/doctor.rs`, `cli/src/services/local_db.rs`, `cli/src/services/mod.rs` + - Evidence: `nix develop -c sh -c 'cd cli && cargo check'`; `nix run .#pkl-check-generated`; `nix flake check` - [ ] T04: Sync current-state context for the trace-removal baseline (status:todo) - Task ID: T04 diff --git a/context/sce/agent-trace-core-schema-migrations.md b/context/sce/agent-trace-core-schema-migrations.md index 33ae181..a7e227d 100644 --- a/context/sce/agent-trace-core-schema-migrations.md +++ b/context/sce/agent-trace-core-schema-migrations.md @@ -1,27 +1,27 @@ -# Agent Trace local DB empty-file baseline +# Local DB empty-file baseline ## Scope - Current state after `agent-trace-removal-and-hook-noop-reset` T01. -- Defines the minimal local DB runtime baseline for the existing Agent Trace path. +- Defines the minimal local DB runtime baseline for the current neutral local DB path. - Covers file creation/open behavior only; schema tables and migrations are not active. ## Code ownership -- Runtime bootstrap entrypoint: `cli/src/services/local_db.rs` (`ensure_agent_trace_local_db_ready_blocking`). +- Runtime bootstrap seam: `cli/src/services/local_db.rs` (`ensure_local_db_ready_blocking`). - Shared local DB connection helper: `cli/src/services/local_db.rs` (`connect_local`). ## Current contract -- `ensure_agent_trace_local_db_ready_blocking` resolves the canonical per-user state path and creates parent directories when needed. -- The runtime opens/creates the local Turso file and returns its path. +- `resolve_local_db_path` resolves the canonical per-user state path. +- The retained bootstrap seam opens/creates the local Turso file and returns its path when future runtime callers invoke it. - No schema bootstrap runs. - No trace, reconciliation, retry, or prompt tables are created as part of runtime readiness. ## Observable consequences - A newly created DB file is empty until another future task introduces schema creation. -- Hook runtime may still ensure the file exists, but it must not assume DB tables are present. +- Doctor/runtime callers may still ensure the file exists, but they must not assume DB tables are present. - Local DB persistence adapters that previously wrote trace or reconciliation rows are disconnected in the current runtime. ## Removed behavior diff --git a/context/sce/agent-trace-hook-doctor.md b/context/sce/agent-trace-hook-doctor.md index d83e5e1..3e7f505 100644 --- a/context/sce/agent-trace-hook-doctor.md +++ b/context/sce/agent-trace-hook-doctor.md @@ -27,13 +27,13 @@ The runtime in `cli/src/services/doctor.rs` exposes the approved doctor command - human text rendering with `SCE doctor diagnose` / `SCE doctor fix` header + ordered `Environment`, `Configuration`, `Repository`, `Git Hooks`, and `Integrations` sections - exact human text status vocabulary `[PASS]`, `[FAIL]`, and `[MISS]` - text summary footer with blocking-problem and warning counts -- Agent Trace local DB reporting in default doctor output +- local DB reporting in default doctor output - stable problem records with category, severity, fixability, and remediation metadata - deterministic fix-result records in fix mode with `fixed`, `skipped`, `manual`, and `failed` outcomes - simplified `label (path)` human rows for healthy path-backed state/config/repository/hook entries, without redundant `present` / `expected` prose - default global/local config-file location reporting, plus validation of existing global and repo-local `sce/config.json` readability and schema compliance -- Agent Trace local DB location reporting, DB parent-directory readiness checks, and existing-DB health validation -- Agent Trace local DB reporting in default doctor output +- local DB location reporting, DB parent-directory readiness checks, and existing-DB health validation +- local DB reporting in default doctor output - explicit git-unavailable, outside-repo, and bare-repo repository-targeting failures - effective hook-path source (`default`, local `core.hooksPath`, global `core.hooksPath`) - repository root and hooks directory resolution when a repository target is detected @@ -44,7 +44,7 @@ The runtime in `cli/src/services/doctor.rs` exposes the approved doctor command - integration child-row reporting for those four groups now validates file content against embedded SHA-256; missing files render as `[MISS]`, content mismatches render as `[FAIL]`, and any affected parent group renders as `[FAIL]` - repo-root OpenCode plugin inventory includes the installed manifest file plus plugin/runtime/preset artifacts as required presence-only files; generated `config/.opencode/**` trees are not inspected by doctor - repair-mode reuse of `cli/src/services/setup.rs::install_required_git_hooks` for missing hooks directories plus missing, stale, or non-executable required hooks -- doctor-owned bootstrap of the missing canonical SCE-owned Agent Trace DB parent directory, with deterministic refusal when the resolved path does not match the expected owned location +- doctor-owned bootstrap of the missing canonical SCE-owned local DB parent directory, with deterministic refusal when the resolved path does not match the expected owned location ## Approved human text-mode contract @@ -158,7 +158,7 @@ Repo-scoped issues make readiness `not_ready` only when a repository target is r Every detected issue must map to exactly one stable problem category: - `runtime_identity`: installed binary/runtime metadata or command-surface problems -- `global_state`: global state-root, config-path, config-contents, or Agent Trace DB readiness problems +- `global_state`: global state-root, config-path, config-contents, or local DB readiness problems - `repository_targeting`: repository resolution, git availability, bare-repo, or hooks-path discovery problems - `hook_rollout`: missing, partial, stale, non-executable, or otherwise unhealthy required SCE-managed hooks - `repo_assets`: missing or stale repo-local SCE-managed assets outside the hook files themselves @@ -194,9 +194,9 @@ The broadened contract for `sce doctor` must cover the following problem invento - global SCE state root cannot be resolved - expected global config path cannot be resolved - global config file exists but is unreadable, invalid JSON, or fails schema validation -- Agent Trace local DB path cannot be resolved -- Agent Trace DB parent directories are missing or not writable -- Agent Trace DB exists but bootstrap or migration health is broken +- local DB path cannot be resolved +- local DB parent directories are missing or not writable +- local DB exists but bootstrap or migration health is broken ### Repository targeting and git readiness From 7ee2e8005f4ef5c78c0bd29b25c8a5efa8c175d8 Mon Sep 17 00:00:00 2001 From: David Abram Date: Wed, 8 Apr 2026 18:23:24 +0200 Subject: [PATCH 4/6] cli: Remove unused agent_trace module Delete the agent_trace module which contained trace record structures, VCS metadata constants, and payload adapter functions that are no longer referenced by the codebase. --- cli/src/services/agent_trace.rs | 257 -------------------------------- cli/src/services/local_db.rs | 2 - cli/src/services/mod.rs | 1 - 3 files changed, 260 deletions(-) delete mode 100644 cli/src/services/agent_trace.rs diff --git a/cli/src/services/agent_trace.rs b/cli/src/services/agent_trace.rs deleted file mode 100644 index a10ebb0..0000000 --- a/cli/src/services/agent_trace.rs +++ /dev/null @@ -1,257 +0,0 @@ -#![allow(dead_code)] - -use std::collections::BTreeMap; - -pub const TRACE_VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const VCS_TYPE_GIT: &str = "git"; -pub const NOTES_REF: &str = "refs/notes/agent-trace"; -pub const TRACE_CONTENT_TYPE: &str = "application/vnd.agent-trace.record+json"; - -pub const METADATA_QUALITY_STATUS: &str = "dev.crocoder.sce.quality_status"; -pub const METADATA_REWRITE_FROM: &str = "dev.crocoder.sce.rewrite_from"; -pub const METADATA_REWRITE_METHOD: &str = "dev.crocoder.sce.rewrite_method"; -pub const METADATA_REWRITE_CONFIDENCE: &str = "dev.crocoder.sce.rewrite_confidence"; -pub const METADATA_IDEMPOTENCY_KEY: &str = "dev.crocoder.sce.idempotency_key"; -pub const METADATA_NOTES_REF: &str = "dev.crocoder.sce.notes_ref"; -pub const METADATA_CONTENT_TYPE: &str = "dev.crocoder.sce.content_type"; - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct TraceAdapterInput { - pub record_id: String, - pub timestamp_rfc3339: String, - pub commit_sha: String, - pub files: Vec, - pub quality_status: QualityStatus, - pub rewrite: Option, - pub idempotency_key: Option, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct FileAttributionInput { - pub path: String, - pub conversations: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct ConversationInput { - pub url: String, - pub related: Vec, - pub ranges: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct RangeInput { - pub start_line: u32, - pub end_line: u32, - pub contributor: ContributorInput, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct ContributorInput { - pub kind: ContributorType, - pub model_id: Option, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct RewriteInfo { - pub from_sha: String, - pub method: String, - pub confidence: String, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum QualityStatus { - Final, - Partial, - NeedsReview, -} - -impl QualityStatus { - pub fn as_str(self) -> &'static str { - match self { - Self::Final => "final", - Self::Partial => "partial", - Self::NeedsReview => "needs_review", - } - } -} - -#[cfg_attr(not(test), allow(dead_code))] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ContributorType { - Human, - Ai, - Mixed, - Unknown, -} - -impl ContributorType { - pub fn as_str(self) -> &'static str { - match self { - Self::Human => "human", - Self::Ai => "ai", - Self::Mixed => "mixed", - Self::Unknown => "unknown", - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct AgentTraceRecord { - pub version: String, - pub id: String, - pub timestamp: String, - pub vcs: AgentTraceVcs, - pub files: Vec, - pub metadata: BTreeMap, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct AgentTraceVcs { - pub r#type: String, - pub revision: String, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct AgentTraceFile { - pub path: String, - pub conversations: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct AgentTraceConversation { - pub url: String, - pub related: Vec, - pub ranges: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct AgentTraceRange { - pub start_line: u32, - pub end_line: u32, - pub contributor: AgentTraceContributor, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct AgentTraceContributor { - pub r#type: String, - pub model_id: Option, -} - -pub fn build_trace_payload(input: TraceAdapterInput) -> AgentTraceRecord { - let mut record = adapt_trace_payload(input); - normalize_record_model_ids(&mut record); - record -} - -fn normalize_record_model_ids(record: &mut AgentTraceRecord) { - for file in &mut record.files { - for conversation in &mut file.conversations { - for range in &mut conversation.ranges { - if range.contributor.r#type == "ai" { - range.contributor.model_id = - normalize_model_id(range.contributor.model_id.take()); - } - } - } - } -} - -fn normalize_model_id(model_id: Option) -> Option { - let raw = model_id?; - let trimmed = raw.trim(); - if trimmed.is_empty() { - return None; - } - - let canonical = trimmed - .replace(':', "/") - .split_whitespace() - .collect::>() - .join("-"); - - if canonical.is_empty() { - return None; - } - - let mut segments = canonical.split('/'); - let provider = segments.next(); - let model = segments.next(); - let has_more = segments.next().is_some(); - if !has_more { - if let (Some(provider), Some(model)) = (provider, model) { - if !provider.is_empty() && !model.is_empty() { - return Some(format!( - "{}/{}", - provider.to_ascii_lowercase(), - model.to_ascii_lowercase() - )); - } - } - } - - Some(canonical) -} - -pub fn adapt_trace_payload(input: TraceAdapterInput) -> AgentTraceRecord { - let mut metadata = BTreeMap::new(); - metadata.insert( - METADATA_QUALITY_STATUS.to_string(), - input.quality_status.as_str().to_string(), - ); - metadata.insert(METADATA_NOTES_REF.to_string(), NOTES_REF.to_string()); - metadata.insert( - METADATA_CONTENT_TYPE.to_string(), - TRACE_CONTENT_TYPE.to_string(), - ); - - if let Some(rewrite) = input.rewrite { - metadata.insert(METADATA_REWRITE_FROM.to_string(), rewrite.from_sha); - metadata.insert(METADATA_REWRITE_METHOD.to_string(), rewrite.method); - metadata.insert(METADATA_REWRITE_CONFIDENCE.to_string(), rewrite.confidence); - } - - if let Some(idempotency_key) = input.idempotency_key { - metadata.insert(METADATA_IDEMPOTENCY_KEY.to_string(), idempotency_key); - } - - let files = input - .files - .into_iter() - .map(|file| AgentTraceFile { - path: file.path, - conversations: file - .conversations - .into_iter() - .map(|conversation| AgentTraceConversation { - url: conversation.url, - related: conversation.related, - ranges: conversation - .ranges - .into_iter() - .map(|range| AgentTraceRange { - start_line: range.start_line, - end_line: range.end_line, - contributor: AgentTraceContributor { - r#type: range.contributor.kind.as_str().to_string(), - model_id: range.contributor.model_id, - }, - }) - .collect(), - }) - .collect(), - }) - .collect(); - - AgentTraceRecord { - version: TRACE_VERSION.to_string(), - id: input.record_id, - timestamp: input.timestamp_rfc3339, - vcs: AgentTraceVcs { - r#type: VCS_TYPE_GIT.to_string(), - revision: input.commit_sha, - }, - files, - metadata, - } -} diff --git a/cli/src/services/local_db.rs b/cli/src/services/local_db.rs index 14fb5e2..4bf8277 100644 --- a/cli/src/services/local_db.rs +++ b/cli/src/services/local_db.rs @@ -5,7 +5,6 @@ use turso::Builder; use crate::services::default_paths::resolve_sce_default_locations; - #[derive(Clone, Copy, Debug)] pub enum LocalDatabaseTarget<'a> { Path(&'a Path), @@ -51,4 +50,3 @@ pub(crate) fn resolve_state_data_root() -> Result { .state_root() .to_path_buf()) } - diff --git a/cli/src/services/mod.rs b/cli/src/services/mod.rs index 7281e52..3c68298 100644 --- a/cli/src/services/mod.rs +++ b/cli/src/services/mod.rs @@ -1,4 +1,3 @@ -pub mod agent_trace; pub mod auth; pub mod auth_command; pub mod completion; From 01ede24813fca002dc931b2b5a908ad8c161bdb9 Mon Sep 17 00:00:00 2001 From: David Abram Date: Wed, 8 Apr 2026 19:29:05 +0200 Subject: [PATCH 5/6] agent-trace: Mark removed local-hook tracing as historical Clarify that Agent Trace adapter, payload builder, persistence, retry, and rewrite flows are no longer active runtime behavior. Document the current attribution-only hook baseline and empty-file local DB baseline, and mark T04 complete in the removal plan. --- context/architecture.md | 1 - context/cli/cli-command-surface.md | 2 - context/context-map.md | 14 +- context/glossary.md | 11 +- context/overview.md | 20 +- context/patterns.md | 9 +- ...agent-trace-removal-and-hook-noop-reset.md | 5 +- .../agent-trace-implementation-contract.md | 174 ++---------------- ...ace-local-hooks-mvp-contract-gap-matrix.md | 11 +- .../agent-trace-payload-builder-validation.md | 39 +--- .../sce/agent-trace-post-commit-dual-write.md | 60 ++---- ...race-post-rewrite-local-remap-ingestion.md | 71 ++----- ...gent-trace-pre-commit-staged-checkpoint.md | 53 +----- .../agent-trace-retry-queue-observability.md | 39 ++-- ...gent-trace-rewrite-trace-transformation.md | 67 +------ context/sce/agent-trace-schema-adapter.md | 53 ++---- 16 files changed, 123 insertions(+), 506 deletions(-) diff --git a/context/architecture.md b/context/architecture.md index 4a91a38..bb68639 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -93,7 +93,6 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/setup.rs` defines the setup command contract (`SetupMode`, `SetupTarget`, `SetupRequest`, CLI flag parser/validator), an `inquire`-backed interactive target prompter (`InquireSetupTargetPrompter`), setup dispatch outcomes (proceed/cancelled), compile-time embedded asset access (`EmbeddedAsset`, target-scoped iterators, required-hook asset iterators/lookups) generated by `cli/build.rs` from the ephemeral crate-local `cli/assets/generated/config/{opencode,claude}/**` mirror plus `cli/assets/hooks/**`, a target-scoped install engine/orchestrator that stages embedded files and branches backup behavior by repository type (non-git-backed config installs keep backup-and-restore rollback, while git-backed config installs skip backup creation and emit deterministic git-recovery guidance on swap failure), and formats deterministic completion messaging, plus required-hook install orchestration (`install_required_git_hooks`) that now follows the same shared backup-policy seam (non-git-backed hook replacements keep backup-and-restore rollback, while git-backed hook replacements skip backup creation and backup-based rollback and emit deterministic git-recovery guidance on swap failure), along with command-surface setup request resolution helpers (`run_setup_hooks`, `resolve_setup_request`) used by hooks-only and composable target+hooks setup invocations with deterministic option compatibility validation, canonicalized/validated repo targeting, write-permission probes, and stable section-ordered setup/hook outcome messaging. - `cli/src/services/security.rs` provides shared security utilities for deterministic secret redaction (`redact_sensitive_text`) and directory write-permission probes (`ensure_directory_is_writable`) used by app/setup/observability surfaces. - `cli/src/services/doctor.rs` now defines the current doctor request/report surface: explicit `DoctorMode` (`diagnose` vs `fix`), stable text/JSON problem records with category/severity/fixability/remediation fields, deterministic fix-result reporting in fix mode, current global operator-environment checks (shared state-root resolution, shared global-config-path resolution, repo-local config validation through the generated `sce/config.json` schema contract, local DB path/health, and DB-parent readiness barriers), repo/hook-integrity diagnostics that distinguish git-unavailable vs non-repo vs bare-repo states, an empty default repo-database section reflecting the current state where no repo-owned SCE database exists, repair-mode reuse of `cli/src/services/setup.rs::install_required_git_hooks` for missing/stale/non-executable required hooks and missing hooks directories, and a bounded doctor-owned repair routine that bootstraps the canonical SCE-owned local DB parent directory only when the resolved path matches the expected owned location. -- `cli/src/services/agent_trace.rs` defines the Agent Trace schema adapter and builder contracts (`adapt_trace_payload`, `build_trace_payload`), including fixed git VCS identity, reserved reverse-domain metadata keys, and deterministic AI `model_id` normalization before schema-compliance validation. - `cli/src/services/version.rs` defines the version command parser/rendering contract (`parse_version_request`, `render_version`) with deterministic text output and stable JSON runtime-identification fields. - `cli/src/services/completion.rs` defines completion parser/rendering contract (`parse_completion_request`, `render_completion`) with deterministic Bash/Zsh/Fish script output aligned to current parser-valid command/flag surfaces. - `cli/src/services/hooks.rs` defines the current minimal local hook runtime parsing/dispatch (`HookSubcommand`, `run_hooks_subcommand`) plus a commit-msg co-author policy seam (`apply_commit_msg_coauthor_policy`) that injects one canonical SCE trailer only when the disabled-default attribution-hooks config/env control is enabled and `SCE_DISABLED` is false. The other hook entrypoints (`pre-commit`, `post-commit`, `post-rewrite`) are deterministic no-op surfaces in the current attribution-only baseline. diff --git a/context/cli/cli-command-surface.md b/context/cli/cli-command-surface.md index 54772da..29bc91b 100644 --- a/context/cli/cli-command-surface.md +++ b/context/cli/cli-command-surface.md @@ -80,7 +80,6 @@ Placeholder commands currently acknowledge planned behavior and do not claim pro - `cli/src/services/setup.rs` defines setup parsing/selection contracts plus runtime install orchestration (`run_setup_for_mode`) over the embedded asset install engine. - `cli/src/services/config.rs` defines config parser/runtime contracts (`show`, `validate`, `--help`), strict config-file key/type validation, deterministic text/JSON rendering, repo-configured bash-policy preset/custom validation and reporting under `policies.bash`, and shared auth-key metadata that declares env key, config-file key, and optional baked-default eligibility for supported auth runtime values starting with `workos_client_id` (`WORKOS_CLIENT_ID` vs `workos_client_id`); auth-key provenance/preference metadata stays on `show`, while `validate` stays trimmed to validation status plus issues/warnings. - `cli/src/services/doctor.rs` now defines the implemented doctor request/report contract (`DoctorRequest`, `DoctorMode`, `run_doctor`) with explicit fix-mode parsing, stable text/JSON problem and database-record rendering, deterministic fix-result reporting, state-root/config/local-DB reporting and validation, an empty default repo-scoped database inventory, path-source detection plus required-hook presence/executable/content checks when a repository target is detected, repo-root installed OpenCode integration presence inventory for `plugins`, `agents`, `commands`, and `skills` derived from the embedded OpenCode setup asset catalog, shared-style bracketed human status token rendering (`[PASS]`, `[FAIL]`, `[MISS]`) with simplified `label (path)` text rows, repair-mode reuse of canonical setup hook installation for supported hook repairs, and a bounded doctor-owned local-DB directory bootstrap routine for the canonical missing DB parent path. -- `cli/src/services/agent_trace.rs` defines the task-scoped schema adapter contract (`adapt_trace_payload`) from internal attribution input structs to Agent Trace-shaped record structs, including fixed git `vcs` mapping, contributor type mapping, and reserved `dev.crocoder.sce.*` metadata placement. - `cli/src/services/version.rs` defines the version parser/output contract (`parse_version_request`, `render_version`) with deterministic text/JSON output modes. - `cli/src/services/completion.rs` defines the completion output contract (`render_completion`) using clap_complete to generate deterministic shell scripts for Bash, Zsh, and Fish. - `cli/src/services/hooks.rs` defines production local hook runtime parsing/dispatch (`HookSubcommand`, `parse_hooks_subcommand`, `run_hooks_subcommand`) for `pre-commit`, `commit-msg`, `post-commit`, and `post-rewrite`; current runtime behavior is commit-msg-only attribution behind the disabled-default attribution gate, while the other entrypoints are deterministic no-ops. @@ -112,7 +111,6 @@ Placeholder commands currently acknowledge planned behavior and do not claim pro - `cli/src/services/token_storage.rs` tests cover token save/load round-trips, missing-file handling, token deletion outcomes, invalid JSON corruption handling, and Unix `0600` file-permission enforcement. - `cli/src/services/auth.rs` tests cover WorkOS device/token payload shape parsing, RFC 8628 device and refresh grant constant wiring, terminal OAuth error mapping with `Try:` guidance, polling decision handling for `authorization_pending`/`slow_down`/terminal outcomes, token-expiry evaluation, and refresh-token re-login guidance for terminal refresh errors. - `cli/src/services/auth_command.rs` tests cover auth subcommand dispatch, login/logout/status text-or-JSON report shapes (including canonical credentials-file path reporting), `Try:` guidance preservation, and runtime-I/O readiness for the login flow. -- `cli/src/services/agent_trace.rs` includes adapter mapping tests for required field projection, contributor enum/model_id handling, and extension metadata placement under reserved reverse-domain keys. - `cli/src/services/setup.rs` tests also verify embedded-manifest completeness against runtime `config/` trees, deterministic sorted path normalization, target-scoped iterator behavior (`OpenCode`, `Claude`, `Both`), and iterator-level omission of `skills/*/tile.json` while keeping `SKILL.md`; sandbox-sensitive filesystem install coverage has been removed from the unit-test slice for later integration-test coverage. - `cli/src/services/setup.rs` and `cli/src/services/local_db.rs` now share temporary path setup through `crate::test_support::TestTempDir` to keep filesystem test fixtures consistent and cleanup deterministic. - `cli/src/services/doctor.rs` unit coverage is intentionally limited to flake-safe output-shape assertions; filesystem, git, and real repair-flow coverage is deferred to future integration tests so `nix flake check` stays sandbox-safe. diff --git a/context/context-map.md b/context/context-map.md index 100cc1c..c13a7ab 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -18,12 +18,12 @@ Feature/domain context: - `context/sce/dedup-ownership-table.md` (current-state canonical owner-vs-consumer matrix for shared SCE behavior domains and thin-command ownership boundaries) - `context/sce/atomic-commit-workflow.md` (canonical `/commit` command + `sce-atomic-commit` skill contract and naming decision) -- `context/sce/agent-trace-implementation-contract.md` (normative T01 implementation contract for no-git-wrapper Agent Trace attribution invariants, compliance matrix, and internal-to-Agent-Trace mapping) -- `context/sce/agent-trace-schema-adapter.md` (T02 schema adapter contract and code-level mapping surface in `cli/src/services/agent_trace.rs`) -- `context/sce/agent-trace-payload-builder-validation.md` (T03 deterministic payload-builder path, model-id normalization behavior, and Agent Trace schema validation suite) +- `context/sce/agent-trace-implementation-contract.md` (historical no-git-wrapper Agent Trace design contract; not active runtime behavior) +- `context/sce/agent-trace-schema-adapter.md` (historical Agent Trace adapter reference for the removed `cli/src/services/agent_trace.rs` surface) +- `context/sce/agent-trace-payload-builder-validation.md` (historical Agent Trace builder/validation reference for the removed runtime surface) - `context/sce/agent-trace-pre-commit-staged-checkpoint.md` (historical pre-commit staged-checkpoint contract; current runtime baseline has replaced this path with a deterministic no-op) - `context/sce/agent-trace-commit-msg-coauthor-policy.md` (current commit-msg canonical co-author trailer policy with attribution-hooks + co-author gating and idempotent dedupe) -- `context/sce/agent-trace-post-commit-dual-write.md` (historical post-commit trace finalization baseline; current runtime baseline replaces this path with a deterministic no-op) +- `context/sce/agent-trace-post-commit-dual-write.md` (current post-commit no-op baseline plus historical dual-write reference) - `context/sce/agent-trace-hook-doctor.md` (approved operator-environment contract for broadening `sce doctor` into the canonical health-and-repair entrypoint, including stable problem taxonomy, `--fix` semantics, setup-to-doctor alignment rules, the current neutral local-DB baseline, and the approved downstream human text-mode layout/status/integration contract) - `context/sce/setup-githooks-install-contract.md` (T01 canonical `sce setup --hooks` install contract for target-path resolution, idempotent outcomes, backup/rollback, and doctor-readiness alignment) - `context/sce/setup-no-backup-policy-seam.md` (implemented shared `SetupBackupPolicy` seam that classifies git-backed vs non-git-backed setup targets and feeds both config-install and required-hook install flows) @@ -31,10 +31,10 @@ Feature/domain context: - `context/sce/setup-githooks-install-flow.md` (T03 setup-service required-hook install orchestration with git-truth hooks-path resolution, per-hook installed/updated/skipped outcomes, shared git-backed no-backup policy branching, and recovery guidance semantics) - `context/sce/setup-githooks-cli-ux.md` (T04 composable `sce setup` target+`--hooks` / `--repo` command-surface contract, option compatibility validation, and deterministic setup/hook output semantics) - `context/sce/cli-security-hardening-contract.md` (T06 CLI redaction contract, setup `--repo` canonicalization/validation, and setup write-permission probe behavior) -- `context/sce/agent-trace-post-rewrite-local-remap-ingestion.md` (T08 `post-rewrite` local remap ingestion contract with strict pair parsing, rewrite-method normalization, and deterministic replay-key derivation) -- `context/sce/agent-trace-rewrite-trace-transformation.md` (T09 rewritten-SHA trace transformation contract with rewrite metadata, confidence-to-quality mapping, and current notes-persistence behavior) +- `context/sce/agent-trace-post-rewrite-local-remap-ingestion.md` (current post-rewrite no-op baseline plus historical remap-ingestion reference) +- `context/sce/agent-trace-rewrite-trace-transformation.md` (current post-rewrite no-op baseline plus historical rewrite-transformation reference) - `context/sce/agent-trace-core-schema-migrations.md` (current neutral local DB empty-file baseline with create/open-only runtime behavior and no schema bootstrap) -- `context/sce/agent-trace-retry-queue-observability.md` (T14 retry queue recovery contract plus reconciliation/runtime observability metrics and DB-first queue schema additions) +- `context/sce/agent-trace-retry-queue-observability.md` (inactive local-hook retry path plus historical retry/metrics reference) - `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md` (T01 Local Hooks MVP production contract freeze and deterministic gap matrix for `agent-trace-local-hooks-production-mvp`) - `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus the current minimal runtime behavior: disabled-default commit-msg attribution and no-op `pre-commit`/`post-commit`/`post-rewrite` entrypoints) - `context/sce/automated-profile-contract.md` (deterministic gate policy for automated OpenCode profile, including 10 gate categories, permission mappings, and automated profile constraints) diff --git a/context/glossary.md b/context/glossary.md index d470190..18772ce 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -88,19 +88,14 @@ - `OpenCode command skill metadata`: machine-readable frontmatter emitted for targeted generated OpenCode commands in `config/.opencode/command/*.md`, using `entry-skill` for the initial skill and `skills` for the ordered skill chain defined by `config/pkl/renderers/opencode-content.pkl`. - `one-task/one-atomic-commit planning contract`: `sce-plan-authoring` requirement that each executable plan task represents one coherent commit unit; broad multi-commit tasks must be split into sequential atomic tasks before execution handoff. - `commit thin orchestration contract`: `/commit` command-body pattern where the command keeps staged-confirmation and proposal-only constraints, while `sce-atomic-commit` owns commit grammar and atomic split guidance. -- `agent trace implementation contract`: Canonical context artifact at `context/sce/agent-trace-implementation-contract.md` defining no-git-wrapper attribution invariants, hook/workflow contracts, confidence/quality policy, Agent Trace compliance matrix, and normative internal-to-Agent-Trace mapping for `agent-trace-attribution-no-git-wrapper`. -- `agent trace schema adapter`: Task-scoped mapping contract implemented in `cli/src/services/agent_trace.rs` (`adapt_trace_payload`) and documented in `context/sce/agent-trace-schema-adapter.md`; maps internal attribution inputs to Agent Trace-shaped records with fixed `vcs.type = git` and reserved `dev.crocoder.sce.*` metadata placement. -- `agent trace payload builder`: Canonical T03 builder contract in `cli/src/services/agent_trace.rs` (`build_trace_payload`) that layers on top of the adapter, preserves deterministic output for identical input, and normalizes AI `model_id` values toward `provider/model` form when inferable. -- `agent trace schema validation suite`: T03 compliance test slice in `services::agent_trace::tests` that validates payload JSON against the published Agent Trace trace-record schema with draft-2020-12 format checks enabled (`uri`, `date-time`, `uuid`) and a local version-pattern compatibility patch for the current app semver emitted from `CARGO_PKG_VERSION`. +- `agent trace historical reference docs`: Retained `context/sce/agent-trace-*.md` artifacts that describe the removed pre-v0.3 Agent Trace design and task slices; they are reference-only and do not describe the active local-hook runtime. - `agent trace commit-msg co-author policy`: Current contract in `cli/src/services/hooks.rs` (`apply_commit_msg_coauthor_policy`) that applies exactly one canonical trailer (`Co-authored-by: SCE `) only when attribution hooks are enabled and SCE is not disabled; duplicate canonical trailers are deduped idempotently. - `local DB empty-file baseline`: Current `cli/src/services/local_db.rs` neutral local DB behavior where the deterministic per-user local DB path (`${XDG_STATE_HOME:-~/.local/state}/sce/local.db` on Linux; platform-equivalent user state root elsewhere) remains an empty-file/open-only baseline without schema migrations or trace tables. -- `agent trace post-commit idempotency ledger`: T06 seam (`TraceEmissionLedger`) in `cli/src/services/hooks.rs` used to prevent duplicate emission for the same commit SHA and to mark successful dual-write completion. +- `hook no-op baseline`: Current `cli/src/services/hooks.rs` runtime posture where `pre-commit`, `post-commit`, and `post-rewrite` return deterministic no-op status text, while `commit-msg` is the only mutating path and remains gated behind the disabled-default attribution-hooks control. - `sce doctor` operator-health contract: `cli/src/services/doctor.rs` now implements the current approved operator-health surface in `context/sce/agent-trace-hook-doctor.md`: `sce doctor --fix` selects repair intent, help/output expose deterministic doctor mode, JSON includes stable problem taxonomy/fixability fields plus database records and fix-result records, the runtime validates state-root resolution, global and repo-local `sce/config.json` readability/schema health, local DB path/health, DB-parent readiness barriers, git availability, non-repo vs bare-repo targeting failures, effective hook-path source resolution, required hook presence/executable/content drift against canonical embedded hook assets, and repo-root installed OpenCode integration presence for `OpenCode plugins`, `OpenCode agents`, `OpenCode commands`, and `OpenCode skills`. Human text mode now uses the approved sectioned layout (`Environment`, `Configuration`, `Repository`, `Git Hooks`, `Integrations`), `SCE doctor diagnose` / `SCE doctor fix` headers, bracketed `[PASS]`/`[FAIL]`/`[MISS]` status tokens with shared-style green/red colorization when enabled, simplified `label (path)` row formatting, top-level-only hook rows, and presence-only integration parent/child rows where missing required files surface as `[MISS]` children and `[FAIL]` parent groups. Fix mode still reuses canonical setup hook installation for missing/stale/non-executable required hooks and missing hooks directories and can bootstrap the canonical missing SCE-owned local DB parent directory. - `cli warnings-denied lint policy`: `cli/Cargo.toml` sets `warnings = "deny"`, so plain `cargo clippy --manifest-path cli/Cargo.toml` already fails on warnings without needing an extra `-- -D warnings` tail. -- `agent trace post-rewrite local remap ingestion`: T08 contract in `cli/src/services/hooks.rs` (`finalize_post_rewrite_remap`) that parses git `post-rewrite` old/new SHA pairs, captures normalized rewrite method (`amend`, `rebase`, or lowercase passthrough), derives deterministic `post-rewrite:::` idempotency keys, and dispatches replay-safe remap ingestion requests. -- `agent trace rewrite trace transformation`: T09 contract in `cli/src/services/hooks.rs` (`finalize_rewrite_trace`) that materializes rewritten-SHA Agent Trace records with `rewrite_from`/`rewrite_method`/`rewrite_confidence` metadata, enforces confidence range normalization (`0.00`..`1.00`), maps quality status thresholds (`final`/`partial`/`needs_review`), and currently preserves notes persistence plus retry fallback while local DB writes remain disconnected. - `agent trace local DB schema migration contract`: Retired `apply_core_schema_migrations` behavior removed from the current runtime during `agent-trace-removal-and-hook-noop-reset` T01; the local DB baseline is now file open/create only. -- `agent trace retry replay processor`: Retained operational contract in `cli/src/services/hooks.rs` where `process_trace_retry_queue` dequeues fallback queue entries, retries only previously failed persistence targets (notes and/or DB), requeues remaining failures, and emits per-attempt runtime/error-class metrics via `RetryMetricsSink`; the current trace-removal runtime baseline does not invoke this seam from local hook entrypoints. +- `agent trace removed local-hook paths`: Current-state shorthand for the removed local-hook runtime behaviors that are no longer active: staged-checkpoint persistence, post-commit dual-write, post-rewrite remap ingestion, rewrite trace transformation, and retry replay. - `agent trace local hooks MVP contract and gap matrix`: T01 context artifact at `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md` that freezes local production boundaries/decisions for `agent-trace-local-hooks-production-mvp` and maps current seam-level code truth to required runtime completion tasks (`T02`..`T10`). - `automated profile`: Non-interactive OpenCode configuration variant generated at `config/automated/.opencode/**` that removes ask/confirm gates and applies deterministic behavior policies defined in `context/sce/automated-profile-contract.md`. - `automated profile contract`: Context artifact at `context/sce/automated-profile-contract.md` defining deterministic gate policies (10 categories: P1-P10), permission mappings, and automated profile constraints for non-interactive SCE workflows. diff --git a/context/overview.md b/context/overview.md index 2fb4bf9..9354b5d 100644 --- a/context/overview.md +++ b/context/overview.md @@ -41,9 +41,7 @@ The `/next-task` command body is intentionally thin orchestration: readiness gat Context sync now uses an important-change gate: cross-cutting/policy/architecture/terminology changes require root shared-file edits, while localized tasks run verify-only root checks without default churn. The `/change-to-plan` command body is also intentionally thin orchestration: it delegates clarification and plan-shape contracts to `sce-plan-authoring` (including one-task/one-atomic-commit task slicing) while keeping wrapper-level plan output and handoff obligations explicit. The generated OpenCode command doc now also emits `entry-skill: sce-plan-authoring` plus an ordered `skills` list. The targeted support commands (`handover`, `commit`, `validate`) keep their thin-wrapper behavior and now also emit machine-readable OpenCode command frontmatter describing their entry skill and ordered skill chain. -The no-git-wrapper Agent Trace initiative baseline contract is defined in `context/sce/agent-trace-implementation-contract.md`, including normative invariants, compliance matrix, and canonical internal-to-Agent-Trace mapping for downstream implementation tasks. -The CLI now includes a task-scoped Agent Trace schema adapter contract in `cli/src/services/agent_trace.rs`, with deterministic mapping of internal attribution input to Agent Trace-shaped record structures documented in `context/sce/agent-trace-schema-adapter.md`. -The Agent Trace service now also provides a deterministic payload-builder path (`build_trace_payload`) with AI `model_id` normalization and schema-compliance validation coverage documented in `context/sce/agent-trace-payload-builder-validation.md`. +The prior no-git-wrapper Agent Trace design artifacts under `context/sce/agent-trace-*.md` are retained only as historical reference; the current CLI runtime no longer wires Agent Trace schema adaptation, payload building, persistence, retry replay, or rewrite handling into local hook execution. The hooks service now uses a minimal attribution-only runtime: `commit-msg` is the only hook that mutates behavior, conditionally injecting exactly one canonical SCE trailer when the attribution-hooks gate is enabled and `SCE_DISABLED` is false; `pre-commit`, `post-commit`, and `post-rewrite` are deterministic no-op entrypoints. The CLI now also includes an approved operator-environment doctor contract documented in `context/sce/agent-trace-hook-doctor.md`; the runtime now matches the implemented T06 slice for `sce doctor --fix` parsing/help, stable problem/fix-result reporting, canonical hook-repair reuse, and bounded doctor-owned local-DB directory bootstrap for the missing SCE-owned DB parent path. The local DB service now limits the runtime path to a neutral per-user local DB file with no schema bootstrap or trace tables; this behavior is documented in `context/sce/agent-trace-core-schema-migrations.md`. @@ -94,19 +92,19 @@ Lightweight post-task verification baseline (required after each completed task) - Use `context/sce/plan-code-overlap-map.md` for the current overlap/dedup inventory across Shared Context Plan/Code agents, related commands, and core skills. - Use `context/sce/dedup-ownership-table.md` for canonical owner-vs-consumer boundaries and keep-vs-dedup labels used by the dedup implementation plan. - Use `context/sce/atomic-commit-workflow.md` for canonical `/commit` behavior, `sce-atomic-commit` naming, and proposal-only commit planning constraints. -- Use `context/sce/agent-trace-implementation-contract.md` for canonical Agent Trace implementation invariants and field-level mapping guidance (`agent-trace-attribution-no-git-wrapper` T01 baseline). -- Use `context/sce/agent-trace-schema-adapter.md` for the implemented T02 adapter contract and canonical mapping surface in `cli/src/services/agent_trace.rs`. -- Use `context/sce/agent-trace-payload-builder-validation.md` for the implemented T03 builder path, normalization policy, and schema-validation behavior. -- Use `context/sce/agent-trace-pre-commit-staged-checkpoint.md` for the implemented T04 pre-commit staged-only finalization contract and runtime no-op guards. +- Use `context/sce/agent-trace-implementation-contract.md` only as historical reference for the removed Agent Trace design baseline; it does not describe active runtime behavior. +- Use `context/sce/agent-trace-schema-adapter.md` only as historical reference for the removed Agent Trace adapter/builder surface. +- Use `context/sce/agent-trace-payload-builder-validation.md` only as historical reference for the removed Agent Trace payload-builder validation slice. +- Use `context/sce/agent-trace-pre-commit-staged-checkpoint.md` for the current pre-commit no-op baseline and the retired staged-checkpoint history. - Use `context/sce/agent-trace-commit-msg-coauthor-policy.md` for the implemented T05 commit-msg canonical co-author trailer policy and idempotent dedupe behavior. -- Use `context/sce/agent-trace-post-commit-dual-write.md` for the current post-commit persistence baseline, including git-notes persistence, no-op local DB record-store behavior, and persistent local DB file bootstrap. +- Use `context/sce/agent-trace-post-commit-dual-write.md` for the current post-commit no-op baseline and the retired dual-write history. - Use `context/sce/agent-trace-hook-doctor.md` for the approved `sce doctor` operator-environment contract, including the current T02 implementation baseline for `--fix` command-surface/output scaffolding, the stable problem/fixability taxonomy, and the rule that new setup/install surfaces must extend doctor coverage. -- Use `context/sce/agent-trace-post-rewrite-local-remap-ingestion.md` for the implemented T08 post-rewrite local remap ingestion pipeline (`post-rewrite` pair parsing, rewrite-method normalization, and deterministic idempotency-key derivation). -- Use `context/sce/agent-trace-rewrite-trace-transformation.md` for the implemented T09 rewritten-SHA trace transformation path (`finalize_rewrite_trace`), confidence-based quality status mapping, and current persistence semantics. +- Use `context/sce/agent-trace-post-rewrite-local-remap-ingestion.md` for the current post-rewrite no-op baseline and the retired local remap-ingestion history. +- Use `context/sce/agent-trace-rewrite-trace-transformation.md` for the current post-rewrite no-op baseline and the retired rewrite-transformation history. - Use `context/sce/agent-trace-core-schema-migrations.md` for the current neutral local DB empty-file baseline and removed schema-bootstrap behavior. - Use `context/sce/agent-trace-hosted-event-intake-orchestration.md` for the implemented T12 hosted intake contract (GitHub/GitLab signature verification, old/new head resolution, deterministic reconciliation-run idempotency keys, and replay-safe run insertion outcomes). - Use `context/sce/agent-trace-rewrite-mapping-engine.md` for the implemented T13 hosted mapping engine contract (patch-id exact matching, range-diff/fuzzy scoring precedence, confidence thresholds, and deterministic unresolved handling). -- Use `context/sce/agent-trace-retry-queue-observability.md` for the implemented T14 retry replay contract (notes/DB target-scoped recovery, per-attempt runtime/error-class metrics, reconciliation mapped/unmapped + confidence histogram snapshots, and DB-first retry/metrics schema additions). +- Use `context/sce/agent-trace-retry-queue-observability.md` for the current inactive retry-replay status and retained historical notes about the removed local-hook retry path. - Use `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md` for the frozen T01 Local Hooks MVP production contract and deterministic gap matrix that maps current seam-level code truth to the remaining implementation stack (`T02`..`T10`). - Use `context/sce/agent-trace-hooks-command-routing.md` for the implemented T02 `sce hooks` command routing contract (subcommand parsing, deterministic invocation errors, and initial runtime entrypoint behavior). - Use `context/sce/setup-githooks-hook-asset-packaging.md` for the implemented `sce-setup-githooks-any-repo` T02 compile-time hook-template packaging contract and setup-service required-hook embedded accessor surface. diff --git a/context/patterns.md b/context/patterns.md index 836efee..9472fe2 100644 --- a/context/patterns.md +++ b/context/patterns.md @@ -126,13 +126,10 @@ - For repo-scoped hook-health diagnostics, resolve effective hooks location from git truth, distinguish git-unavailable vs outside-repo vs bare-repo failure modes explicitly, and compare required hook payload bytes against the canonical embedded hook assets so stale SCE-managed hook content is reported deterministically (`cli/src/services/doctor.rs`, `cli/src/services/setup.rs`). - For future CLI domains, define trait-first service contracts with request/plan models in `cli/src/services/*` and keep placeholder implementations explicitly non-runnable until production behavior is approved. - Model deferred integration boundaries with concrete event/capability data structures (for example hook-runtime attribution snapshots/policies and cloud-sync checkpoints) so later tasks can implement behavior without reshaping public seams. -- For pre-commit attribution finalization seams, keep pending staged and unstaged ranges explicitly separated in input models and finalize from staged ranges only, while carrying index/tree anchors for deterministic commit-time attribution binding. -- For commit-msg co-author policy seams, gate canonical trailer insertion on runtime controls (`SCE_DISABLED`, `SCE_COAUTHOR_ENABLED`) plus staged SCE-attribution presence, and enforce idempotent dedupe so allowed cases end with exactly one `Co-authored-by: SCE ` trailer. +- For the current local-hook baseline, keep `pre-commit`, `post-commit`, and `post-rewrite` as deterministic no-op entrypoints until an explicitly approved tracing redesign lands. +- For commit-msg co-author policy seams, gate canonical trailer insertion on runtime controls (`SCE_DISABLED` plus the shared attribution-hooks enablement gate), and enforce idempotent dedupe so allowed cases end with exactly one `Co-authored-by: SCE ` trailer. - For local hook attribution flows, resolve the top-level enablement gate through the shared config precedence model (`SCE_ATTRIBUTION_HOOKS_ENABLED` over `policies.attribution_hooks.enabled`, default `false`) so commit-msg attribution stays disabled by default without adding hook-specific config parsing. -- For post-commit trace finalization seams, treat commit SHA as the idempotency identity, keep git-notes persistence and retry-fallback behavior explicit per target, and avoid assuming local DB writes are active while the local record-store adapter is disconnected. -- For retry replay seams, process fallback queue entries in bounded batches, avoid same-pass duplicate trace processing, retry only failed targets, emit per-attempt runtime + persistence error-class metrics for operational visibility, and run a bounded replay pass from production post-commit/post-rewrite hook runtime with deterministic summary output. -- For post-rewrite remap ingestion seams, parse ` ` pairs from hook input strictly, ignore empty/no-op self-mapping rows, normalize rewrite method labels to lowercase (`amend`/`rebase` when recognized), and derive deterministic per-pair idempotency keys before dispatching remap requests. -- For rewrite trace transformation seams, materialize rewritten records through the canonical Agent Trace builder path, require finite confidence in `[0.0, 1.0]`, normalize confidence to two-decimal metadata strings, map quality thresholds to `final` (`>= 0.90`), `partial` (`0.60..0.89`), and `needs_review` (`< 0.60`), and keep persistence behavior aligned with the currently active targets rather than assuming local DB parity. +- Do not assume post-commit persistence, retry replay, remap ingestion, or rewrite trace transformation are active in the current local-hook runtime; those paths are removed from the current baseline. - For the current local DB baseline, resolve one deterministic per-user persistent DB target (Linux: `${XDG_STATE_HOME:-~/.local/state}/sce/local.db`; platform-equivalent state roots elsewhere), keep the path neutral rather than Agent Trace-branded, create parent directories before first use, and only create/open the DB file without schema bootstrap or trace-table installation. - For hosted event intake seams, verify provider signatures before payload parsing (GitHub `sha256=` HMAC over body, GitLab token-equality secret check), resolve old/new heads from provider payload fields, and derive deterministic reconciliation run idempotency keys from provider+event+repo+head tuple material. - For hosted rewrite mapping seams, resolve candidates deterministically in strict precedence order (patch-id exact, then range-diff score, then fuzzy score), classify top-score ties as `ambiguous`, enforce low-confidence unresolved behavior below `0.60`, and preserve stable outcome ordering via canonical candidate SHA sorting. diff --git a/context/plans/agent-trace-removal-and-hook-noop-reset.md b/context/plans/agent-trace-removal-and-hook-noop-reset.md index 7784761..5994b47 100644 --- a/context/plans/agent-trace-removal-and-hook-noop-reset.md +++ b/context/plans/agent-trace-removal-and-hook-noop-reset.md @@ -51,12 +51,15 @@ - Files changed: `cli/src/app.rs`, `cli/src/cli_schema.rs`, `cli/src/command_surface.rs`, `cli/src/services/default_paths.rs`, `cli/src/services/doctor.rs`, `cli/src/services/local_db.rs`, `cli/src/services/mod.rs` - Evidence: `nix develop -c sh -c 'cd cli && cargo check'`; `nix run .#pkl-check-generated`; `nix flake check` -- [ ] T04: Sync current-state context for the trace-removal baseline (status:todo) +- [x] T04: Sync current-state context for the trace-removal baseline (status:done) - Task ID: T04 - Goal: Update shared context to reflect that Agent Trace runtime behavior has been removed, hook attribution is optional and disabled by default, and local DB bootstrap is empty-file only. - Boundaries (in/out of scope): In scope: affected `context/overview.md`, `context/glossary.md`, `context/context-map.md`, and focused `context/sce/` artifacts describing tracing/hooks/local DB behavior. Out of scope: speculative v0.3.0 redesign docs or historical postmortems. - Done when: Current-state context no longer documents removed Agent Trace behavior as active, and retained hook attribution/local DB behavior is described accurately for future sessions. - Verification notes (commands or checks): Review all touched context files against code truth; ensure stale Agent Trace contract files are updated, replaced, or removed as appropriate. + - Completed: 2026-04-08 + - Files changed: `context/architecture.md`, `context/cli/cli-command-surface.md`, `context/context-map.md`, `context/glossary.md`, `context/overview.md`, `context/patterns.md`, `context/sce/agent-trace-implementation-contract.md`, `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md`, `context/sce/agent-trace-payload-builder-validation.md`, `context/sce/agent-trace-post-commit-dual-write.md`, `context/sce/agent-trace-post-rewrite-local-remap-ingestion.md`, `context/sce/agent-trace-pre-commit-staged-checkpoint.md`, `context/sce/agent-trace-retry-queue-observability.md`, `context/sce/agent-trace-rewrite-trace-transformation.md`, `context/sce/agent-trace-schema-adapter.md` + - Evidence: `nix run .#pkl-check-generated`; `nix flake check` - [ ] T05: Validation and cleanup (status:todo) - Task ID: T05 diff --git a/context/sce/agent-trace-implementation-contract.md b/context/sce/agent-trace-implementation-contract.md index a6d57f5..d87e4d0 100644 --- a/context/sce/agent-trace-implementation-contract.md +++ b/context/sce/agent-trace-implementation-contract.md @@ -1,167 +1,23 @@ -# Agent Trace Implementation Contract (No Git Wrapper) +# Agent Trace Implementation Contract (Historical Reference) -## Status -- Plan: `agent-trace-attribution-no-git-wrapper` -- Task: `T01` -- Scope: implementation contract baseline only (no production code changes) -- Normative keywords: `MUST`, `SHOULD`, `MAY` - -## 1. Objective -Define one canonical, implementation-ready contract for Agent Trace attribution in this repository so later tasks (`T02`..`T15`) execute against a single set of invariants. - -## 2. Core invariants -- Native Git workflows are preserved. Developers MUST continue to use normal Git entrypoints (`git commit`, `git rebase`, IDE commit UIs). This system MUST NOT replace `git` on `PATH`. -- Canonical interchange is Agent Trace JSON. Local and hosted flows MUST treat Agent Trace records as the source of truth for line-level attribution. -- Local VCS identity is fixed. Emitted records MUST set `vcs.type = "git"`. -- One canonical finalized trace per commit. Each finalized commit SHA MUST map to one canonical Agent Trace record whose `version` follows the CLI app version (`CARGO_PKG_VERSION`) attached to `refs/notes/agent-trace` and mirrored to backend persistence. -- Co-author behavior is metadata-only UX. Human author/committer identity MUST NOT be rewritten by this system. -- SCE co-author trailer, when applicable, MUST use exactly `Co-authored-by: SCE ` with idempotent insertion. - -## 3. Command and workflow contracts - -### 3.1 Local hook contracts -- `pre-commit` - - MUST finalize attribution checkpoints from staged content only. - - MUST capture index/tree anchors needed for later commit binding. - - MUST no-op safely when disabled, missing CLI, or bare repository conditions apply. -- `commit-msg` - - MUST apply canonical trailer policy only when staged SCE-attributed changes exist. - - MUST honor `SCE_DISABLED` and `SCE_COAUTHOR_ENABLED` controls. - - MUST deduplicate canonical trailer entries. -- `post-commit` - - MUST build and finalize a trace for new `HEAD`. - - MUST dual-write to Git notes (`refs/notes/agent-trace`) and backend storage (or queue fallback on transient failures). - - MUST emit canonical trace media type `application/vnd.agent-trace.record+json`. -- `post-rewrite` - - MUST ingest old->new commit pairs from rewrite events. - - MUST trigger deterministic remap processing with replay-safe idempotency. - -### 3.2 Hosted reconciliation contracts -- Hosted intake (GitHub/GitLab PR/MR updates, force-push) MUST produce deterministic idempotency keys for replay-safe orchestration. -- Reconciliation runs MUST preserve auditable old->new identity mapping and emit explicit confidence and quality outcomes. -- Hosted rewrites MUST NOT mutate canonical attribution semantics beyond declared rewrite metadata fields. - -## 4. Canonical metadata keys - -All extension metadata keys MUST use reverse-domain namespaced keys under `dev.crocoder.sce.*`. - -Reserved key set: -- `dev.crocoder.sce.quality_status` -> one of `final | partial | needs_review` -- `dev.crocoder.sce.rewrite_from` -> previous commit SHA when record is rewritten -- `dev.crocoder.sce.rewrite_method` -> rewrite method enum (for example `amend`, `rebase`, `force_push_reconcile`) -- `dev.crocoder.sce.rewrite_confidence` -> normalized score `0.00`..`1.00` -- `dev.crocoder.sce.idempotency_key` -> deterministic replay key for hosted/local remap orchestration -- `dev.crocoder.sce.notes_ref` -> `refs/notes/agent-trace` when persisted via Git notes -- `dev.crocoder.sce.content_type` -> `application/vnd.agent-trace.record+json` - -Rules: -- Unknown `dev.crocoder.sce.*` keys MAY be added later but MUST be forward-compatible and ignored safely by consumers. -- `quality_status`, `rewrite_*`, and `idempotency_key` fields MUST be preserved end-to-end if present. - -## 5. Confidence and quality policy - -### 5.1 Confidence scoring thresholds -- `>= 0.90`: high confidence; eligible for `final` quality when all required invariants pass. -- `0.60..0.89`: medium confidence; default `partial` unless explicit strict mapping criteria are met. -- `< 0.60`: low confidence; MUST set quality `needs_review`. +## Current status -### 5.2 Quality status contract -- `final` - - Required fields valid. - - Deterministic commit identity resolution complete. - - Attribution ranges structurally valid. -- `partial` - - Required fields valid, but one or more confidence or remap guarantees are incomplete. -- `needs_review` - - Any unresolved/low-confidence mapping, structural anomaly, or policy violation requiring operator inspection. +- This document is retained as historical reference only. +- The current CLI runtime does not implement the Agent Trace contract described by the original no-git-wrapper plan. +- Local hooks are currently attribution-only: `commit-msg` may append the canonical SCE co-author trailer when the attribution gate is enabled, while `pre-commit`, `post-commit`, and `post-rewrite` are deterministic no-ops. -## 6. Failure policy -- Never lose trace intent: - - If notes write fails and DB write succeeds, system MUST enqueue retry for notes sync. - - If DB write fails and notes write succeeds, system MUST enqueue DB ingest retry. - - If both fail, system MUST persist retry intent with deterministic idempotency and emit operational error metrics. -- Commit flow behavior: - - Attribution tooling SHOULD be fail-open for normal developer commit completion unless explicitly configured otherwise. - - Failures MUST be observable and replayable. -- Idempotency: - - All finalize/rewrite pipelines MUST be safe to retry without duplicate canonical records for the same `(repo, commit_sha, trace_version)` tuple. +## Historical scope -## 7. Rollout acceptance gates - -Before enforcement is considered enabled in a repository, the following MUST pass: -- Hook installation and health checks (`sce doctor`) report ready state. -- At least one local commit path demonstrates staged-only attribution and canonical trace creation. -- Notes + backend dual-write path is verified, including one forced transient outage scenario with retry success. -- Local rewrite (`amend` and `rebase`) remap evidence shows deterministic old->new mapping outcomes. -- Hosted replay/idempotency evidence demonstrates duplicate events do not produce duplicate side effects. -- Compliance validation confirms Agent Trace required field presence and structural nesting. - -## 8. Agent Trace field-level compliance matrix - -### 8.1 Required Agent Trace fields - -| Agent Trace field | Requirement | Local contract rule | -| --- | --- | --- | -| `version` | required | MUST emit the CLI app version from `CARGO_PKG_VERSION` | -| `id` | required | MUST be UUID | -| `timestamp` | required | MUST be RFC 3339 date-time | -| `files` | required | MUST be non-empty when attributed file changes exist | - -### 8.2 VCS block - -| Agent Trace field | Requirement | Local contract rule | -| --- | --- | --- | -| `vcs.type` | required (when `vcs` present) | MUST be `git` | -| `vcs.revision` | required (when `vcs` present) | MUST be finalized commit SHA | - -### 8.3 File attribution nesting - -| Agent Trace path | Requirement | Local contract rule | -| --- | --- | --- | -| `files[].conversations[]` | optional but used | SHOULD be present for attributed edits | -| `files[].conversations[].url` | required in conversation object | MUST be URI-formatted | -| `files[].conversations[].ranges[]` | required when conversation carries ranges | MUST exist and be valid | -| `files[].conversations[].ranges[].start_line` | required | MUST be 1-indexed integer >= 1 | -| `files[].conversations[].ranges[].end_line` | required | MUST be integer >= `start_line` | -| `files[].conversations[].ranges[].contributor.type` | required | MUST be one of `human|ai|mixed|unknown` | -| `files[].conversations[].ranges[].contributor.model_id` | conditional | AI entries SHOULD use `provider/model` (models.dev convention) when known | - -### 8.4 Optional links - -| Agent Trace field | Requirement | Local contract rule | -| --- | --- | --- | -| `related[]` | optional | MUST preserve when present | +- Plan: `agent-trace-attribution-no-git-wrapper` +- Task: `T01` +- Scope at the time: implementation-contract baseline only (no production code changes) -## 9. Normative mapping: internal attribution model -> Agent Trace +## Historical objective -| Internal model element | Agent Trace destination | Mapping rule | -| --- | --- | --- | -| `TraceDraft.version` | `version` | Compile-time app version from `CARGO_PKG_VERSION` | -| `TraceDraft.record_uuid` | `id` | UUID v4 string | -| `TraceDraft.emitted_at` | `timestamp` | RFC 3339 UTC timestamp | -| `CommitIdentity.sha` | `vcs.revision` | Finalized commit SHA | -| `CommitIdentity.vcs_kind` | `vcs.type` | Constant `git` | -| `FileAttribution.path` | `files[].path` | Repository-relative normalized path | -| `ConversationRef.url` | `files[].conversations[].url` | Valid URI string | -| `ConversationRef.related_urls[]` | `related[]` (or conversation-level related field if schema supports) | Preserve order and values | -| `LineRange.start` | `files[].conversations[].ranges[].start_line` | 1-indexed integer | -| `LineRange.end` | `files[].conversations[].ranges[].end_line` | Inclusive integer >= start | -| `RangeAttribution.kind` | `files[].conversations[].ranges[].contributor.type` | Enum map: `human|ai|mixed|unknown` | -| `RangeAttribution.model` | `...contributor.model_id` | `provider/model` when available | -| `RewriteInfo.from_sha` | `metadata[dev.crocoder.sce.rewrite_from]` | Only for rewritten commits | -| `RewriteInfo.method` | `metadata[dev.crocoder.sce.rewrite_method]` | Enum string | -| `RewriteInfo.confidence` | `metadata[dev.crocoder.sce.rewrite_confidence]` | Decimal `0.00`..`1.00` | -| `QualityState` | `metadata[dev.crocoder.sce.quality_status]` | `final|partial|needs_review` | -| `TransportInfo.content_type` | `metadata[dev.crocoder.sce.content_type]` | Constant media type | -| `TransportInfo.notes_ref` | `metadata[dev.crocoder.sce.notes_ref]` | Constant `refs/notes/agent-trace` | -| `ReplayInfo.idempotency_key` | `metadata[dev.crocoder.sce.idempotency_key]` | Deterministic key | +Define one canonical, implementation-ready contract for Agent Trace attribution so downstream tasks in that plan could execute against a single set of invariants. -## 10. Version-format interoperability note -- Known ambiguity: public RFC page currently shows a possible pattern/example mismatch for `version` formatting. -- Contract decision: emit the compile-time CLI app version (`CARGO_PKG_VERSION`) canonically, and keep readers tolerant to equivalent semver-like variants where needed. +## Current guidance -## 11. Implementation sequencing implications -- `T02` MUST implement schema adapter outputs matching section 8 and section 9. -- `T03` MUST prove deterministic serialization + compliance validation against Agent Trace schema. -- `T04`..`T09` MUST preserve invariants in sections 2 through 7. -- `T10`..`T14` MUST persist metadata needed to support section 5 and section 6 without semantic loss. +- Do not treat this file as current runtime truth. +- For current local-hook behavior, use `context/sce/agent-trace-hooks-command-routing.md`. +- For the current local DB baseline, use `context/sce/agent-trace-core-schema-migrations.md`. diff --git a/context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md b/context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md index a2a38e8..38b2f4d 100644 --- a/context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md +++ b/context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md @@ -1,13 +1,14 @@ # Agent Trace Local Hooks MVP Contract and Gap Matrix ## Status +- This document is retained as a historical freeze artifact, not as current runtime truth. - Plan: `agent-trace-local-hooks-production-mvp` - Task: `T01` -- Scope: contract and gap freeze only (no production code changes) +- Scope at the time: contract and gap freeze only (no production code changes) - Normative keywords: `MUST`, `SHOULD`, `MAY` -## Objective -Freeze one implementation-ready contract for Local Hooks MVP productionization and map current code-truth seams to missing runtime wiring required by tasks `T02`..`T10`. +## Historical objective +Freeze one implementation-ready contract for Local Hooks MVP productionization and map then-current code-truth seams to missing runtime wiring required by tasks `T02`..`T10`. ## Local MVP boundary - In scope: `sce hooks` runtime command flow for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`; local notes + DB persistence; retry replay; rollout readiness alignment with `sce setup --hooks` and `sce doctor`. @@ -61,7 +62,7 @@ Freeze one implementation-ready contract for Local Hooks MVP productionization a - Runtime failure posture for local hooks: fail-open for commit progression by default, while preserving retry-safe persistence intent and diagnostics. - Idempotency unit for finalized local commit traces: one canonical finalized record per commit SHA. -## Module ownership map (code truth) +## Historical module ownership map (at freeze time) - CLI command parsing/dispatch: `cli/src/app.rs` - Command surface status/help text: `cli/src/command_surface.rs` - Hook-domain contracts/finalizers/retry processor: `cli/src/services/hooks.rs` @@ -70,7 +71,7 @@ Freeze one implementation-ready contract for Local Hooks MVP productionization a - Hook installation orchestration: `cli/src/services/setup.rs` - Hook readiness diagnostics: `cli/src/services/doctor.rs` -## Gap matrix (current code truth -> required runtime completion) +## Gap matrix (historical code truth at freeze time -> required runtime completion) | MVP area | Current state (code truth) | Required completion target | Planned task(s) | | --- | --- | --- | --- | diff --git a/context/sce/agent-trace-payload-builder-validation.md b/context/sce/agent-trace-payload-builder-validation.md index ef84526..219b7cf 100644 --- a/context/sce/agent-trace-payload-builder-validation.md +++ b/context/sce/agent-trace-payload-builder-validation.md @@ -1,35 +1,16 @@ -# Agent Trace Payload Builder And Validation +# Agent Trace Payload Builder And Validation (Historical Reference) -## Scope +## Current status -- Plan/task: `agent-trace-attribution-no-git-wrapper` / `T03`. -- Canonical implementation file: `cli/src/services/agent_trace.rs`. -- Purpose: define one deterministic payload-builder path on top of the adapter and verify Agent Trace schema compliance. +- This document is retained as historical reference only. +- The current CLI runtime does not expose `cli/src/services/agent_trace.rs` or build Agent Trace payloads during local hook execution. -## Current-state contract +## Historical scope -- `build_trace_payload(input)` is the canonical builder entrypoint. -- Builder behavior is deterministic for identical inputs: - - uses adapter output as the single source path - - normalizes AI `model_id` values when provider/model form is inferable (`provider:model` -> `provider/model`, lowercase) - - keeps non-normalizable values intact instead of dropping attribution data -- Record shape remains aligned with Agent Trace-required top-level fields (`version`, `id`, `timestamp`, `files`) and local invariant `vcs.type = "git"`. +- Plan/task: `agent-trace-attribution-no-git-wrapper` / `T03` +- Purpose at the time: define a deterministic payload-builder path and schema-validation coverage for Agent Trace records. -## Validation suite +## Current guidance -- Validation tests compile the published Agent Trace trace-record schema and validate builder output. -- Format validation is enabled (`date-time`, `uri`, `uuid`) via `jsonschema` draft-2020-12 options. -- Schema checks cover: - - required fields + enum constraints - - nested `files[].conversations[].ranges[]` structure - - related-link preservation using schema-compatible related objects in test payload rendering - - negative format tests for invalid URI and RFC3339 timestamp values - -## Published-schema compatibility note - -- The published schema pattern for `version` currently accepts two-segment versions (`x.y`) while RFC examples and this implementation emit the CLI app version from `CARGO_PKG_VERSION` (currently sourced from the repo's centralized app version). -- Test validation applies a local compatibility patch to the version regex (`x.y` or `x.y.z`) to keep compliance tests aligned with the current emitted contract. - -## Verification commands - -- `nix flake check` +- Do not use this file as current runtime guidance. +- For current local-hook behavior, use `context/sce/agent-trace-hooks-command-routing.md`. diff --git a/context/sce/agent-trace-post-commit-dual-write.md b/context/sce/agent-trace-post-commit-dual-write.md index 3210fc2..1baca7e 100644 --- a/context/sce/agent-trace-post-commit-dual-write.md +++ b/context/sce/agent-trace-post-commit-dual-write.md @@ -1,56 +1,22 @@ # Agent Trace post-commit persistence baseline ## Current status + - This contract is no longer active in runtime. - The current `cli/src/services/hooks.rs` keeps `sce hooks post-commit` as a deterministic no-op. -## Status -- Plan: `agent-trace-attribution-no-git-wrapper` -- Task: `T06` -- Implementation state: done - -## Canonical contract -- Policy entrypoint: `cli/src/services/hooks.rs` -> `finalize_post_commit_trace`. -- Runtime entrypoint status: retained but not active in the current trace-removal baseline. -- Runtime no-op guards: - - `sce_disabled = true` -> `NoOp(Disabled)` - - `attribution_hooks_enabled = false` -> `NoOp(AttributionDisabled)` - - `trace_side_effects_enabled = false` -> `NoOp(AttributionOnlyMode)` - - `cli_available = false` -> `NoOp(CliUnavailable)` - - `is_bare_repo = true` -> `NoOp(BareRepository)` -- Idempotency guard: `TraceEmissionLedger::has_emitted(commit_sha)` short-circuits to `NoOp(AlreadyFinalized)`. -- Emitted trace payload path uses `build_trace_payload` from `cli/src/services/agent_trace.rs` with: - - `quality_status = final` - - `metadata[dev.crocoder.sce.idempotency_key]` populated - - optional `metadata[dev.crocoder.sce.parent_revision]` when a parent SHA is available -- Notes write policy is fixed to `refs/notes/agent-trace` with MIME `application/vnd.agent-trace.record+json`. +## Current baseline -## Current persistence behavior -- Finalization still attempts both persistence targets in one pass: - - notes write via `TraceNotesWriter` - - local DB write via `TraceRecordStore` -- The production local DB adapter is now `NoOpTraceRecordStore`, so the DB target returns `AlreadyExists` without writing trace rows. -- Successful notes persistence plus the no-op DB result mark commit emission in `TraceEmissionLedger` and return `Persisted`. -- Any failed target (`PersistenceWriteResult::Failed`) enqueues one retry item via `TraceRetryQueue` with explicit failed target list and returns `QueuedFallback`. -- Retry queue entries carry the full trace record, MIME type, notes ref, and failed target list to support replay-safe recovery. +- Runtime entrypoint: `cli/src/services/hooks.rs` -> `run_post_commit_subcommand` +- Current behavior: `sce hooks post-commit` returns deterministic no-op status text +- No Agent Trace payload is built +- No git-notes write runs +- No local DB trace write runs +- No retry queue entry is produced +- Enabling the attribution-hooks gate does not change `post-commit`; the active gated behavior remains `commit-msg` only -## Retained runtime wiring details -- Runtime entrypoint: `cli/src/services/hooks.rs` -> `run_post_commit_subcommand_in_repo`. -- Current runtime posture: - - `post-commit` remains invocable but exits through deterministic no-op output before invoking trace persistence behavior. - - Enabling the attribution-hooks gate does not reactivate notes writes, local DB writes, retry replay, or emission-ledger mutation. -- Runtime input assembly: - - resolves `HEAD` + optional `HEAD^` via git - - derives commit timestamp from `git show -s --format=%cI HEAD` - - derives file attribution from the pre-commit checkpoint artifact first, then falls back to changed-file discovery (`git show --name-only HEAD`) - - derives deterministic idempotency (`post-commit:`) and deterministic UUIDv4 trace IDs from commit/timestamp seed -- Production adapters currently bound in runtime: - - notes adapter: `GitNotesTraceWriter` writes canonical JSON note payloads to `refs/notes/agent-trace` - - local record store adapter: `NoOpTraceRecordStore` disconnects local DB trace persistence while keeping the finalize path compile-safe - - emission ledger adapter: `FileTraceEmissionLedger` stores emitted commit SHAs at `sce/trace-emission-ledger.txt` - - retry queue adapter: `JsonFileTraceRetryQueue` appends failed-target fallback entries to `sce/trace-retry-queue.jsonl` -- If later runtime work reactivates this path, `resolve_post_commit_runtime_paths` still points at the empty-file/open-only Agent Trace DB baseline rather than any schema-backed trace store. -- Runtime posture remains fail-open: operational errors return deterministic skip/fallback messages instead of aborting commit progression. +## Historical note -## Verification evidence -- `nix flake check` +- Plan: `agent-trace-attribution-no-git-wrapper` +- Task: `T06` +- This file retains the removed dual-write task slice for reference only. diff --git a/context/sce/agent-trace-post-rewrite-local-remap-ingestion.md b/context/sce/agent-trace-post-rewrite-local-remap-ingestion.md index a8c1ffd..ecd3871 100644 --- a/context/sce/agent-trace-post-rewrite-local-remap-ingestion.md +++ b/context/sce/agent-trace-post-rewrite-local-remap-ingestion.md @@ -1,67 +1,20 @@ -# Agent Trace Post-Rewrite Local Remap Ingestion (T08) +# Agent Trace Post-Rewrite Local Remap Ingestion (Historical Reference) ## Current status + - This contract is no longer active in runtime. - The current `cli/src/services/hooks.rs` keeps `sce hooks post-rewrite` as a deterministic no-op. -## Status -- Plan: `agent-trace-attribution-no-git-wrapper` -- Task: `T08` -- Scope: local `post-rewrite` ingestion pipeline only (no hosted webhook processing) - -## Implemented surface -- Code: `cli/src/services/hooks.rs` -- Primary entrypoint: `finalize_post_rewrite_remap` -- Hook intent: consume local git `post-rewrite` input (` ` pairs) and emit deterministic remap-ingestion requests. - -## Runtime gating - -`finalize_post_rewrite_remap` returns `NoOp` and performs no ingestion when any of these guards apply: - -- `sce_disabled = true` -- `attribution_hooks_enabled = false` -- `trace_side_effects_enabled = false` -- `cli_available = false` -- `is_bare_repo = true` - -## Pair parsing contract - -- Input is a newline-delimited payload where each non-empty line must contain exactly two whitespace-separated fields: ` `. -- Empty lines are ignored. -- Self-mapping lines (`old_sha == new_sha`) are ignored as no-op rewrites. -- Any non-empty malformed line fails the call with an error; no partial best-effort parsing for that invocation. +## Current baseline -## Rewrite-method normalization +- Runtime entrypoint: `run_post_rewrite_subcommand` +- Current behavior: returns deterministic no-op status text and includes the provided rewrite-method argument in that status text +- No stdin rewrite-pair parsing runs +- No local remap ingestion runs +- Enabling the attribution-hooks gate does not change `post-rewrite`; it remains a no-op -- Hook argument values are normalized to lowercase. -- Recognized values map to typed methods: - - `amend` -> `RewriteMethod::Amend` - - `rebase` -> `RewriteMethod::Rebase` -- All other values are preserved as lowercase in `RewriteMethod::Other(String)`. +## Historical note -## Idempotency and dispatch - -- For each parsed pair, the ingestion request derives one deterministic key: - - `post-rewrite:::` -- The method token uses normalized labels (`amend`, `rebase`, or lowercase passthrough). -- Requests are dispatched through `RewriteRemapIngestion::ingest`. -- The ingestion response is interpreted as: - - `true`: pair accepted as a new ingestion - - `false`: pair skipped as replay/duplicate -- Finalization returns aggregate counters: total pairs, ingested pairs, and skipped pairs. - -## Current boundaries - -- In scope: local hook-side normalization, strict parsing, deterministic per-pair replay keys, and ingestion dispatch seam. -- Out of scope: rewrite trace transformation semantics (`T09`), hosted intake (`T12`), and mapping engine heuristics (`T13`). - -## Verification evidence - -- `nix flake check` - -## Tests added - -- No-op behavior when SCE is disabled, attribution hooks are disabled, or attribution-only mode is active. -- Amend-pair ingestion with deterministic idempotency-key derivation. -- Rebase duplicate replay behavior (second identical pair skipped). -- Strict malformed-line rejection (` ` required). +- Plan: `agent-trace-attribution-no-git-wrapper` +- Task: `T08` +- The remap-ingestion contract described by the original task is not active in the current runtime. diff --git a/context/sce/agent-trace-pre-commit-staged-checkpoint.md b/context/sce/agent-trace-pre-commit-staged-checkpoint.md index d124e99..b5ed2b3 100644 --- a/context/sce/agent-trace-pre-commit-staged-checkpoint.md +++ b/context/sce/agent-trace-pre-commit-staged-checkpoint.md @@ -4,50 +4,15 @@ This contract is no longer active. The current `cli/src/services/hooks.rs` runtime keeps `sce hooks pre-commit` as a deterministic no-op and does not persist checkpoint artifacts. -## Scope +## Current baseline -Task `agent-trace-attribution-no-git-wrapper` `T04` adds a pre-commit finalization contract that filters pending attribution to staged content only and preserves index/tree anchors for deterministic commit-time binding. +- Code location: `cli/src/services/hooks.rs` +- Runtime entrypoint: `run_pre_commit_subcommand` +- Current behavior: returns deterministic no-op status text +- No checkpoint artifact is written +- No staged/unstaged diff collection runs +- No index/head tree anchors are captured -## Implemented contract +## Historical note -- Code location: `cli/src/services/hooks.rs`. -- Finalization entrypoint: `finalize_pre_commit_checkpoint(runtime, anchors, pending)`. -- Runtime hook entrypoint: `run_pre_commit_subcommand` -> `run_pre_commit_subcommand_in_repo(repository_root)`. -- Runtime no-op guards: - - `sce_disabled = true` -> `NoOp(Disabled)`. - - `attribution_hooks_enabled = false` -> `NoOp(AttributionDisabled)`. - - `cli_available = false` -> `NoOp(CliUnavailable)`. - - `is_bare_repo = true` -> `NoOp(BareRepository)`. -- Runtime state resolution: - - `SCE_DISABLED` truthy env values (`1`, `true`, `yes`, `on`) set disabled mode. - - `SCE_ATTRIBUTION_HOOKS_ENABLED` overrides config key `policies.attribution_hooks.enabled` and defaults to disabled. - - CLI availability checks `git --version` in the repository context. - - Bare-repository guard uses `git rev-parse --is-bare-repository`. -- Staged-only enforcement: - - Input keeps separate `staged_ranges` and `unstaged_ranges` per file. - - Finalized output includes only `staged_ranges`. - - Files with no staged ranges are dropped from finalized attribution. -- Runtime staged/unstaged extraction: - - Staged hunks from `git diff --cached --unified=0 --no-color --no-ext-diff`. - - Unstaged hunks from `git diff --unified=0 --no-color --no-ext-diff`. - - Unified-diff hunks are parsed into deterministic line ranges per file path. -- Anchors captured in finalized output: - - required `index_tree`. - - optional `head_tree`. -- Anchor capture source: - - `index_tree` from `git write-tree`. - - `head_tree` from `git rev-parse --verify HEAD^{tree}` (optional for repos without `HEAD`). -- Finalized checkpoint handoff artifact: - - Persisted as JSON at Git-resolved path `$(git rev-parse --git-path sce/pre-commit-checkpoint.json)`. - - Payload shape: `version`, `anchors`, checkpoint-level `harness_type`, optional `git_branch`, optional `model_id`, and staged-only `files[]`. - - Downstream `commit-msg` gating only treats a file as SCE-attributed when `has_sce_attribution = true` and `ranges[]` is non-empty. - - **TEMPORARY(v0.1.x)**: Generic git-diff collection currently defaults `has_sce_attribution` to `true` for all staged files, which means all commits receive the SCE co-author trailer when the policy gate passes. - - **PLANNED (v0.3.0)**: Will default to `false` and require a separate attribution-aware producer to set the marker when staged ranges are proven to come from SCE contribution. - - See `cli/src/services/hooks.rs:collect_pending_checkpoint` for the TODO(0.3.0) marker. - - Runtime remains fail-open: checkpoint collection/persist failures return deterministic diagnostics without blocking commit flow. - -## Verification coverage - -- Mixed staged/unstaged fixture test confirms unstaged ranges are excluded and anchor values are preserved. -- Guard-path tests cover disabled, attribution-disabled, missing CLI, and bare-repository no-op behavior. -- Runtime fixture test validates persisted pre-commit checkpoint artifact contains staged-only ranges when both staged and unstaged edits exist for the same file. +Task `agent-trace-attribution-no-git-wrapper` `T04` originally defined a staged-checkpoint contract for later commit binding. That contract is not active in the current runtime. diff --git a/context/sce/agent-trace-retry-queue-observability.md b/context/sce/agent-trace-retry-queue-observability.md index 641c23d..f47e860 100644 --- a/context/sce/agent-trace-retry-queue-observability.md +++ b/context/sce/agent-trace-retry-queue-observability.md @@ -1,36 +1,19 @@ -# Agent Trace retry queue and observability metrics +# Agent Trace retry queue and observability metrics (Historical Reference) ## Current status + - This contract is no longer active in local hook runtime. - The current `cli/src/services/hooks.rs` no longer runs retry replay from hook entrypoints. -## Status -- Plan: `agent-trace-attribution-no-git-wrapper` -- Task: `T14` -- Implementation state: done +## Current baseline -## Canonical contract -- Retry processing entrypoint: `cli/src/services/hooks.rs` -> `process_trace_retry_queue`. -- The retained `process_runtime_retry_queue` wrapper is not invoked by the current trace-removal runtime baseline; `sce hooks post-commit` and `sce hooks post-rewrite` now no-op before retry replay. -- Queue contract now supports dequeue + enqueue replay via `TraceRetryQueue::{dequeue_next, enqueue}`. -- Retry pass processes up to `max_items` entries per invocation and avoids same-pass duplicate processing for the same trace ID. -- If runtime invocation is re-enabled later, the retained wrapper still uses bounded `max_items = 16` replay. -- Recovery write behavior is target-scoped: - - Failed notes target retries through `TraceNotesWriter`. - - Failed DB target retries through `TraceRecordStore` using metadata idempotency key (`dev.crocoder.sce.idempotency_key`) when present. -- Retry metrics are emitted per attempted replay through `RetryMetricsSink` with: - - `commit_sha` - - `trace_id` - - runtime histogram input (`runtime_ms`) - - `error_class` (from `PersistenceFailure.class` when writes fail) - - remaining failed targets. -- The retained runtime wrapper still formats deterministic retry observability summary text, but current hook command output does not surface it because retry replay is not invoked. +- The local hook runtime does not replay Agent Trace retries +- `post-commit` and `post-rewrite` no-op before any retry processing +- No local DB retry schema is part of the active runtime baseline +- No local-hook retry metrics are emitted -## Persistence schema additions -- `cli/src/services/local_db.rs` core migrations now include: - - `trace_retry_queue` (DB-first fallback queue storage) -- Added indexes: - - `idx_trace_retry_queue_created_at` +## Historical note -## Verification evidence -- `nix flake check` +- Plan: `agent-trace-attribution-no-git-wrapper` +- Task: `T14` +- This file is retained only as historical reference for the removed retry/metrics design slice. diff --git a/context/sce/agent-trace-rewrite-trace-transformation.md b/context/sce/agent-trace-rewrite-trace-transformation.md index 1c6f47f..559b9e7 100644 --- a/context/sce/agent-trace-rewrite-trace-transformation.md +++ b/context/sce/agent-trace-rewrite-trace-transformation.md @@ -1,65 +1,18 @@ -# Agent Trace Rewrite Trace Transformation (T09) +# Agent Trace Rewrite Trace Transformation (Historical Reference) ## Current status + - This contract is no longer active in runtime. - The current `cli/src/services/hooks.rs` keeps `sce hooks post-rewrite` as a deterministic no-op. -## Status -- Plan: `agent-trace-attribution-no-git-wrapper` -- Task: `T09` -- Scope: rewrite trace transformation semantics for rewritten SHAs - -## Implemented surface -- Code: `cli/src/services/hooks.rs` -- Primary entrypoint: `finalize_rewrite_trace` -- Purpose: materialize rewritten-commit Agent Trace records with explicit rewrite metadata and deterministic quality classification. - -## Runtime gating and idempotency - -`finalize_rewrite_trace` returns `NoOp` without persistence when any guard applies: - -- `sce_disabled = true` -- `trace_side_effects_enabled = false` -- `cli_available = false` -- `is_bare_repo = true` -- rewritten commit SHA is already marked emitted in `TraceEmissionLedger` - -## Rewrite record transformation contract +## Current baseline -- Rewritten traces are emitted through the canonical builder path (`build_trace_payload`) to preserve Agent Trace-required structure. -- The rewritten commit identity maps to `vcs.revision = `. -- Rewrite lineage metadata is always attached via reserved keys: - - `dev.crocoder.sce.rewrite_from` - - `dev.crocoder.sce.rewrite_method` - - `dev.crocoder.sce.rewrite_confidence` -- The method value uses canonical labels from `RewriteMethod` (`amend`, `rebase`, lowercase passthrough for `Other`). +- `sce hooks post-rewrite` does not transform rewritten commits into Agent Trace records +- No rewrite metadata is emitted +- No persistence or retry behavior is attached to `post-rewrite` -## Confidence and quality logic +## Historical note -- Confidence input must be finite and inside `[0.0, 1.0]`; otherwise finalization errors before writes. -- Confidence is normalized to a fixed two-decimal metadata string (`0.00`..`1.00`). -- Quality status mapping: - - `>= 0.90` -> `final` - - `0.60..0.89` -> `partial` - - `< 0.60` -> `needs_review` - -## Persistence semantics - -- Rewritten trace finalization follows the same current notes-plus-no-op-DB baseline as post-commit traces. -- On success: - - commit SHA is marked emitted in `TraceEmissionLedger` - - outcome is `RewriteTraceFinalization::Persisted` -- On any target failure: - - failed targets are captured in a retry queue entry - - outcome is `RewriteTraceFinalization::QueuedFallback` - -## Verification evidence - -- `nix flake check` - -## Tests added - -- Metadata integrity and current notes/no-op-DB persistence behavior for rewritten traces. -- Confidence-threshold quality mapping (`final`, `partial`, `needs_review`). -- Confidence range validation errors for out-of-range input. -- No-op behavior when attribution-only mode is active or the rewritten commit was already finalized. +- Plan: `agent-trace-attribution-no-git-wrapper` +- Task: `T09` +- The rewrite-transformation contract described by the original task is retained only as historical reference. diff --git a/context/sce/agent-trace-schema-adapter.md b/context/sce/agent-trace-schema-adapter.md index 7bd5d8a..2724837 100644 --- a/context/sce/agent-trace-schema-adapter.md +++ b/context/sce/agent-trace-schema-adapter.md @@ -1,48 +1,17 @@ -# Agent Trace Schema Adapter +# Agent Trace Schema Adapter (Historical Reference) -## Scope +## Current status -- Plan/task: `agent-trace-attribution-no-git-wrapper` / `T02`. -- Purpose: define a deterministic adapter contract that maps internal attribution inputs to Agent Trace record shape, without persistence or hook side effects. +- This document is retained as historical reference only. +- `cli/src/services/agent_trace.rs` is not part of the active runtime surface. +- The current local-hook baseline does not build Agent Trace payloads. -## Canonical code location +## Historical scope -- `cli/src/services/agent_trace.rs` +- Plan/task: `agent-trace-attribution-no-git-wrapper` / `T02` +- Purpose at the time: define a deterministic adapter contract that mapped internal attribution inputs to Agent Trace record shape without persistence or hook side effects. -## Adapter contract (current state) +## Current guidance -- Input contract is `TraceAdapterInput` with commit identity, timestamp, record id, file attribution payload, quality status, and optional rewrite/idempotency metadata. -- Output contract is `AgentTraceRecord` with: - - required top-level fields (`version`, `id`, `timestamp`, `files`) - - fixed local VCS block (`vcs.type = "git"`, `vcs.revision = `) - - reverse-domain metadata keys under `dev.crocoder.sce.*` -- Canonical constants are centralized for trace/media/reference values: - - `TRACE_VERSION = env!("CARGO_PKG_VERSION")` so emitted Agent Trace record versions follow the CLI app version at compile time - - `NOTES_REF = "refs/notes/agent-trace"` - - `TRACE_CONTENT_TYPE = "application/vnd.agent-trace.record+json"` - -## Mapping guarantees in this slice - -- Contributor enum mapping is explicit and constrained to `human|ai|mixed|unknown`. -- Conversation links preserve `url` and optional `related` values. -- Extension metadata placement uses reserved keys: - - `dev.crocoder.sce.quality_status` - - `dev.crocoder.sce.rewrite_from` - - `dev.crocoder.sce.rewrite_method` - - `dev.crocoder.sce.rewrite_confidence` - - `dev.crocoder.sce.idempotency_key` - - `dev.crocoder.sce.notes_ref` - - `dev.crocoder.sce.content_type` - -## Verification evidence - -- `nix flake check` includes the current repo-level verification flow for the CLI and related checks. - -## Out of scope (deferred) - -- JSON schema compliance/runtime format validation and deterministic serialization checks (`T03`). -- Hook orchestration, notes/DB writes, and rewrite execution flows (`T04+`). - -## Follow-on coverage - -- `T03` is now implemented in `context/sce/agent-trace-payload-builder-validation.md` with builder-path and schema-validation details layered on this adapter contract. +- Do not use this file as current implementation guidance. +- For current hook behavior, use `context/sce/agent-trace-hooks-command-routing.md`. From bcee76c7c074835407bc9bbfda5dcf3faf212f97 Mon Sep 17 00:00:00 2001 From: David Abram Date: Wed, 8 Apr 2026 20:02:13 +0200 Subject: [PATCH 6/6] cli: Remove Agent Trace local database persistence Remove the turso-based local database implementation and all associated Agent Trace persistence behavior from the CLI. This eliminates the local_db service, schema bootstrap, trace emission, and related doctor checks while keeping hook entrypoints present for optional attribution behavior (now gated and disabled by default). --- cli/Cargo.lock | 1325 +---------------- cli/Cargo.toml | 1 - cli/src/services/default_paths.rs | 17 +- cli/src/services/doctor.rs | 288 +--- cli/src/services/local_db.rs | 52 - cli/src/services/mod.rs | 1 - ...agent-trace-removal-and-hook-noop-reset.md | 33 +- 7 files changed, 51 insertions(+), 1666 deletions(-) delete mode 100644 cli/src/services/local_db.rs diff --git a/cli/Cargo.lock b/cli/Cargo.lock index b69ac59..ab70ebf 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -2,51 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aegis" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78412fa53e6da95324e8902c3641b3ff32ab45258582ea997eb9169c68ffa219" -dependencies = [ - "cc", - "softaes", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - [[package]] name = "ahash" version = "0.8.12" @@ -129,43 +84,12 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "antithesis_sdk" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18dbd97a5b6c21cc9176891cf715f7f0c273caf3959897f43b9bd1231939e675" -dependencies = [ - "libc", - "libloading", - "linkme", - "once_cell", - "rand 0.8.5", - "rustc_version_runtime", - "serde", - "serde_json", -] - [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "arc-swap" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" -dependencies = [ - "rustversion", -] - -[[package]] -name = "assoc" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdc70193dadb9d7287fa4b633f15f90c876915b31f6af17da307fc59c9859a8" - [[package]] name = "async-stream" version = "0.3.6" @@ -264,42 +188,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bigdecimal" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" -dependencies = [ - "autocfg", - "libm", - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "bitflags 2.11.0", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn", - "which", -] - [[package]] name = "bit-set" version = "0.8.0" @@ -327,18 +215,6 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -354,24 +230,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" -[[package]] -name = "branches" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e426eb5cc1900033930ec955317b302e68f19f326cc7bb0c8a86865a826cdf0c" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "built" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" -dependencies = [ - "chrono", -] - [[package]] name = "bumpalo" version = "3.20.2" @@ -384,26 +242,6 @@ version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "byteorder" version = "1.5.0" @@ -426,15 +264,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.4" @@ -447,12 +276,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "cfg_block" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18758054972164c3264f7c8386f5fc6da6114cb46b619fd365d4e3b2dc3ae487" - [[package]] name = "chrono" version = "0.4.44" @@ -466,27 +289,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "4.6.0" @@ -542,15 +344,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -566,49 +359,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc32c" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-skiplist" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df29de440c58ca2cc6e587ec3d22347551a32435fbde9d2bff64e78a9ffa151b" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - [[package]] name = "crossterm" version = "0.25.0" @@ -641,28 +391,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] - [[package]] name = "digest" version = "0.10.7" @@ -727,25 +458,6 @@ dependencies = [ "serde", ] -[[package]] -name = "env_filter" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" -dependencies = [ - "log", -] - -[[package]] -name = "env_logger" -version = "0.11.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" -dependencies = [ - "env_filter", - "log", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -762,12 +474,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - [[package]] name = "fancy-regex" version = "0.16.2" @@ -779,24 +485,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "fastbloom" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" -dependencies = [ - "getrandom 0.3.4", - "libm", - "rand 0.9.2", - "siphasher", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -820,12 +508,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -845,12 +527,6 @@ dependencies = [ "num", ] -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futures-channel" version = "0.3.32" @@ -941,36 +617,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "genawaiter" -version = "0.99.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86bd0361bcbde39b13475e6e36cb24c329964aa2611be285289d1e4b751c1a0" -dependencies = [ - "genawaiter-macro", -] - -[[package]] -name = "genawaiter-macro" -version = "0.99.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b32dfe1fdfc0bbde1f22a5da25355514b5e450c33a6af6770884c8750aedfbc" - -[[package]] -name = "generator" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows-link", - "windows-result", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1003,34 +649,11 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi 5.3.0", + "r-efi", "wasip2", "wasm-bindgen", ] -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "wasip2", - "wasip3", -] - -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - [[package]] name = "glob" version = "0.3.3" @@ -1062,15 +685,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - [[package]] name = "hashbrown" version = "0.16.1" @@ -1089,12 +703,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "hmac" version = "0.12.1" @@ -1104,15 +712,6 @@ dependencies = [ "digest", ] -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "http" version = "1.4.0" @@ -1339,12 +938,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "idna" version = "1.1.0" @@ -1384,17 +977,6 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", - "serde", - "serde_core", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", ] [[package]] @@ -1414,26 +996,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "intrusive-collections" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" -dependencies = [ - "memoffset", -] - -[[package]] -name = "io-uring" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "libc", -] - [[package]] name = "ipnet" version = "2.12.0" @@ -1473,15 +1035,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.14.0" @@ -1542,50 +1095,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link", -] - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "libmimalloc-sys" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "libredox" version = "0.1.15" @@ -1595,38 +1110,6 @@ dependencies = [ "libc", ] -[[package]] -name = "linkme" -version = "0.3.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e3283ed2d0e50c06dd8602e0ab319bb048b6325d0bba739db64ed8205179898" -dependencies = [ - "linkme-impl", -] - -[[package]] -name = "linkme-impl" -version = "0.3.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5cec0ec4228b4853bb129c84dbf093a27e6c7a20526da046defc334a1b017f7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - [[package]] name = "litemap" version = "0.8.2" @@ -1648,34 +1131,12 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] - [[package]] name = "lru-slab" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - [[package]] name = "matchit" version = "0.7.3" @@ -1688,58 +1149,12 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "miette" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" -dependencies = [ - "cfg-if", - "miette-derive", - "unicode-width", -] - -[[package]] -name = "miette-derive" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "mimalloc" -version = "0.1.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" -dependencies = [ - "libmimalloc-sys", -] - [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "mio" version = "0.8.11" @@ -1772,16 +1187,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1830,12 +1235,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-conv" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" - [[package]] name = "num-integer" version = "0.1.46" @@ -1888,12 +1287,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "opentelemetry" version = "0.28.0" @@ -1935,7 +1328,7 @@ dependencies = [ "opentelemetry-http", "opentelemetry-proto", "opentelemetry_sdk", - "prost 0.13.5", + "prost", "reqwest", "thiserror", "tokio", @@ -1951,7 +1344,7 @@ checksum = "56f8870d3024727e99212eb3bb1762ec16e255e3e6f58eeb3dc8db1aa226746d" dependencies = [ "opentelemetry", "opentelemetry_sdk", - "prost 0.13.5", + "prost", "tonic", ] @@ -1988,12 +1381,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - [[package]] name = "owo-colors" version = "4.3.0" @@ -2004,15 +1391,6 @@ dependencies = [ "supports-color 3.0.2", ] -[[package]] -name = "pack1" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6e7cd9bd638dc2c831519a0caa1c006cab771a92b1303403a8322773c5b72d6" -dependencies = [ - "bytemuck", -] - [[package]] name = "parking_lot" version = "0.12.5" @@ -2036,12 +1414,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "percent-encoding" version = "2.3.2" @@ -2074,32 +1446,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix 1.1.4", - "windows-sys 0.61.2", -] - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - [[package]] name = "potential_utf" version = "0.1.5" @@ -2109,12 +1455,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2124,16 +1464,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -2150,17 +1480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" dependencies = [ "bytes", - "prost-derive 0.13.5", -] - -[[package]] -name = "prost" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" -dependencies = [ - "bytes", - "prost-derive 0.14.3", + "prost-derive", ] [[package]] @@ -2170,20 +1490,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.14.0", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "prost-derive" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" -dependencies = [ - "anyhow", - "itertools 0.14.0", + "itertools", "proc-macro2", "quote", "syn", @@ -2200,7 +1507,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.2", + "rustc-hash", "rustls", "socket2 0.6.3", "thiserror", @@ -2220,7 +1527,7 @@ dependencies = [ "lru-slab", "rand 0.9.2", "ring", - "rustc-hash 2.1.2", + "rustc-hash", "rustls", "rustls-pki-types", "slab", @@ -2259,18 +1566,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rand" version = "0.8.5" @@ -2330,24 +1625,6 @@ dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "rand_pcg" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "rapidhash" -version = "4.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" -dependencies = [ - "rustversion", -] - [[package]] name = "redox_syscall" version = "0.5.18" @@ -2485,73 +1762,12 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "roaring" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba9ce64a8f45d7fc86358410bb1a82e8c987504c0d4900e9141d69a9f26c885" -dependencies = [ - "bytemuck", - "byteorder", -] - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustc_version_runtime" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dd18cd2bae1820af0b6ad5e54f4a51d0f3fcc53b05f845675074efcc7af071d" -dependencies = [ - "rustc_version", - "semver", -] - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", -] - [[package]] name = "rustls" version = "0.23.37" @@ -2599,24 +1815,12 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - [[package]] name = "serde" version = "1.0.228" @@ -2672,12 +1876,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - [[package]] name = "sha2" version = "0.10.9" @@ -2713,7 +1911,7 @@ dependencies = [ "opentelemetry", "opentelemetry-otlp", "opentelemetry_sdk", - "owo-colors 4.3.0", + "owo-colors", "reqwest", "serde", "serde_json", @@ -2722,7 +1920,6 @@ dependencies = [ "tracing", "tracing-opentelemetry", "tracing-subscriber", - "turso", ] [[package]] @@ -2731,26 +1928,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "shuttle" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab17edba38d63047f46780cf7360acf7467fec2c048928689a5c1dd1c2b4e31" -dependencies = [ - "assoc", - "bitvec", - "cfg-if", - "generator", - "hex", - "owo-colors 3.5.0", - "rand 0.8.5", - "rand_core 0.6.4", - "rand_pcg", - "scoped-tls", - "smallvec", - "tracing", -] - [[package]] name = "signal-hook" version = "0.3.18" @@ -2782,21 +1959,6 @@ dependencies = [ "libc", ] -[[package]] -name = "simsimd" -version = "6.5.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fb3bc3cdce07a7d7d4caa4c54f8aa967f6be41690482b54b24100a2253fa70" -dependencies = [ - "cc", -] - -[[package]] -name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - [[package]] name = "slab" version = "0.4.12" @@ -2829,12 +1991,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "softaes" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef461faaeb36c340b6c887167a9054a034f6acfc50a014ead26a02b4356b3de" - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -2847,28 +2003,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "subtle" version = "2.6.1" @@ -2925,25 +2059,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.2", - "once_cell", - "rustix 1.1.4", - "windows-sys 0.61.2", -] - [[package]] name = "thiserror" version = "2.0.18" @@ -2973,37 +2088,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tinystr" version = "0.8.3" @@ -3109,7 +2193,7 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "prost 0.13.5", + "prost", "socket2 0.5.10", "tokio", "tokio-stream", @@ -3195,18 +2279,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-appender" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" -dependencies = [ - "crossbeam-channel", - "thiserror", - "time", - "tracing-subscriber", -] - [[package]] name = "tracing-attributes" version = "0.1.31" @@ -3263,14 +2335,10 @@ version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ - "matchers", "nu-ansi-term", - "once_cell", - "regex-automata", "sharded-slab", "smallvec", "thread_local", - "tracing", "tracing-core", "tracing-log", ] @@ -3281,214 +2349,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "turso" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faba49ac70e21ea35cc963341485f3d17822f2cf433f42152a182117da21d29f" -dependencies = [ - "mimalloc", - "thiserror", - "tracing", - "tracing-subscriber", - "turso_sdk_kit", - "turso_sync_sdk_kit", -] - -[[package]] -name = "turso_core" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fac73a12b91b569f4671d63d65912876c11e6312597c996dac40494f9f9b39" -dependencies = [ - "aegis", - "aes", - "aes-gcm", - "antithesis_sdk", - "arc-swap", - "bigdecimal", - "bitflags 2.11.0", - "branches", - "built", - "bumpalo", - "bytemuck", - "cfg_block", - "chrono", - "crc32c", - "crossbeam-skiplist", - "either", - "fallible-iterator", - "fastbloom", - "hex", - "intrusive-collections", - "io-uring", - "libc", - "libloading", - "libm", - "loom", - "miette", - "num-bigint", - "num-traits", - "pack1", - "parking_lot", - "paste", - "polling", - "rand 0.9.2", - "rapidhash", - "regex", - "regex-syntax", - "roaring", - "rustc-hash 2.1.2", - "rustix 1.1.4", - "ryu", - "serde_json", - "shuttle", - "simsimd", - "smallvec", - "strum", - "strum_macros", - "tempfile", - "thiserror", - "tracing", - "tracing-subscriber", - "turso_ext", - "turso_macros", - "turso_parser", - "twox-hash", - "uncased", - "uuid", - "windows-sys 0.61.2", -] - -[[package]] -name = "turso_ext" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd7410a02a3a4cebd48a5bc0db74940d1157dc9c05ad42d48ee5156dd31edd1" -dependencies = [ - "chrono", - "getrandom 0.3.4", - "turso_macros", -] - -[[package]] -name = "turso_macros" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c846c30c3cb085884a8bbaba7760bdcc406ff2176cbde1e51d41b6057171fd4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "turso_parser" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8402ba98c236e3e6d6ed6a43557a9a0b3a682f86a37fcafe02b659b9e6c06b82" -dependencies = [ - "bitflags 2.11.0", - "memchr", - "miette", - "strum", - "strum_macros", - "thiserror", - "turso_macros", -] - -[[package]] -name = "turso_sdk_kit" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b68fee8a6d8515fa6be08ad998d34eba0ac4a8e81dae4b9d0041e21ca01e22" -dependencies = [ - "bindgen", - "env_logger", - "parking_lot", - "tracing", - "tracing-appender", - "tracing-subscriber", - "turso_core", - "turso_sdk_kit_macros", -] - -[[package]] -name = "turso_sdk_kit_macros" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b90fe1bcada9dda8b8e20900f744bdd52f641cccc179f1507e83f8f2ec0b1dc" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "turso_sync_engine" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a94f0d86e6823f63fc52040eb33131ce7fb9cebdb7329a5231443d846e0195a" -dependencies = [ - "base64", - "bytes", - "genawaiter", - "http", - "libc", - "prost 0.14.3", - "roaring", - "serde", - "serde_json", - "thiserror", - "tracing", - "turso_core", - "turso_parser", - "uuid", -] - -[[package]] -name = "turso_sync_sdk_kit" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49fb6c54aaa988f333505a9023fe4985725995b1575eb1557105fa4ac13ea6d" -dependencies = [ - "bindgen", - "env_logger", - "genawaiter", - "parking_lot", - "tracing", - "tracing-appender", - "tracing-subscriber", - "turso_core", - "turso_sdk_kit", - "turso_sdk_kit_macros", - "turso_sync_engine", -] - -[[package]] -name = "twox-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" -dependencies = [ - "rand 0.9.2", -] - [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" -[[package]] -name = "uncased" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-ident" version = "1.0.24" @@ -3507,22 +2373,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - [[package]] name = "untrusted" version = "0.9.0" @@ -3559,9 +2409,7 @@ version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ - "getrandom 0.4.2", "js-sys", - "sha1_smol", "wasm-bindgen", ] @@ -3618,15 +2466,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasm-bindgen" version = "0.2.117" @@ -3682,40 +2521,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.13.0", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap 2.13.0", - "semver", -] - [[package]] name = "web-sys" version = "0.3.94" @@ -3745,18 +2550,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.44", -] - [[package]] name = "winapi" version = "0.3.9" @@ -3856,15 +2649,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" @@ -4074,88 +2858,6 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap 2.13.0", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.13.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] [[package]] name = "writeable" @@ -4163,15 +2865,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "yoke" version = "0.8.2" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 0249d5b..38972b8 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -43,7 +43,6 @@ tokio = { version = "1", default-features = false, features = ["rt", "io-util"] tracing = "0.1" tracing-opentelemetry = "0.29" tracing-subscriber = { version = "0.3", features = ["registry"] } -turso = "0" opentelemetry = { version = "0.28", features = ["trace"] } opentelemetry_sdk = { version = "0.28", features = ["rt-tokio"] } opentelemetry-otlp = { version = "0.28", features = ["grpc-tonic", "http-proto", "trace"] } diff --git a/cli/src/services/default_paths.rs b/cli/src/services/default_paths.rs index 715ddcd..ca9c693 100644 --- a/cli/src/services/default_paths.rs +++ b/cli/src/services/default_paths.rs @@ -78,7 +78,6 @@ pub(crate) enum PersistedArtifactRootKind { pub(crate) enum PersistedArtifactId { GlobalConfig, AuthTokens, - LocalDb, } #[cfg_attr(not(test), allow(dead_code))] @@ -106,10 +105,6 @@ impl SceDefaultLocations { .join("tokens.json") } - pub(crate) fn local_db(&self) -> PathBuf { - self.roots.state_root().join("sce").join("local.db") - } - #[cfg_attr(not(test), allow(dead_code))] pub(crate) fn persisted_artifact_locations(&self) -> Vec { vec![ @@ -123,11 +118,6 @@ impl SceDefaultLocations { root_kind: PersistedArtifactRootKind::State, path: self.auth_tokens_file(), }, - PersistedArtifactLocation { - id: PersistedArtifactId::LocalDb, - root_kind: PersistedArtifactRootKind::State, - path: self.local_db(), - }, ] } } @@ -463,6 +453,13 @@ impl InstallTargetPaths { } } +pub(crate) fn resolve_state_data_root() -> Result { + Ok(resolve_sce_default_locations()? + .roots() + .state_root() + .to_path_buf()) +} + pub(crate) fn resolve_sce_default_locations() -> Result { resolve_sce_default_locations_for( current_platform_family(), diff --git a/cli/src/services/doctor.rs b/cli/src/services/doctor.rs index 262701d..7d40626 100644 --- a/cli/src/services/doctor.rs +++ b/cli/src/services/doctor.rs @@ -7,7 +7,8 @@ use serde_json::json; use sha2::{Digest, Sha256}; use crate::services::default_paths::{ - hook_dir, opencode_asset, resolve_sce_default_locations, InstallTargetPaths, RepoPaths, + hook_dir, opencode_asset, resolve_sce_default_locations, resolve_state_data_root, + InstallTargetPaths, RepoPaths, }; use crate::services::output_format::OutputFormat; use crate::services::setup::{ @@ -84,7 +85,6 @@ struct FileLocationHealth { struct GlobalStateHealth { state_root: Option, config_locations: Vec, - local_db: Option, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -96,7 +96,6 @@ struct HookDoctorReport { hook_path_source: HookPathSource, hooks_directory: Option, config_locations: Vec, - local_db: Option, hooks: Vec, integration_groups: Vec, problems: Vec, @@ -154,13 +153,6 @@ enum ProblemKind { GlobalConfigValidationFailed, UnableToResolveGlobalConfigPath, LocalConfigValidationFailed, - UnableToResolveLocalDbPath, - LocalDbPathHasNoParent, - LocalDbParentMissing, - LocalDbParentNotDirectory, - LocalDbParentNotWritable, - LocalDbParentInspectionFailed, - LocalDbHealthCheckFailed, HooksDirectoryMissing, HooksPathNotDirectory, RequiredHookMissing, @@ -205,11 +197,8 @@ struct DoctorDependencies<'a> { check_git_available: &'a dyn Fn() -> bool, resolve_state_root: &'a dyn Fn() -> Result, resolve_global_config_path: &'a dyn Fn() -> Result, - resolve_local_db_path: &'a dyn Fn() -> Result, validate_config_file: &'a dyn Fn(&Path) -> Result<()>, - check_local_db_health: &'a dyn Fn(&Path) -> Result<()>, install_required_git_hooks: &'a dyn Fn(&Path) -> Result, - create_directory_all: &'a dyn Fn(&Path) -> Result<()>, } struct DoctorExecution { @@ -217,14 +206,6 @@ struct DoctorExecution { fix_results: Vec, } -enum DirectoryWriteReadiness { - Ready, - Missing, - NotDirectory, - ReadOnly, - Unknown(std::io::Error), -} - #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum HumanTextStatus { Pass, @@ -246,15 +227,12 @@ fn execute_doctor(request: DoctorRequest, repository_root: &Path) -> DoctorExecu &DoctorDependencies { run_git_command: &run_git_command, check_git_available: &is_git_available, - resolve_state_root: &crate::services::local_db::resolve_state_data_root, + resolve_state_root: &resolve_state_data_root, resolve_global_config_path: &|| { Ok(resolve_sce_default_locations()?.global_config_file()) }, - resolve_local_db_path: &crate::services::local_db::resolve_local_db_path, validate_config_file: &crate::services::config::validate_config_file, - check_local_db_health: &crate::services::local_db::check_local_db_health_blocking, install_required_git_hooks: &install_required_git_hooks, - create_directory_all: &create_directory_all, }, ) } @@ -423,7 +401,6 @@ fn build_report_with_dependencies( hook_path_source, hooks_directory, config_locations: global_state.config_locations, - local_db: global_state.local_db, hooks, integration_groups, problems, @@ -526,125 +503,9 @@ fn collect_global_state_health( path: local_path, }); - let local_db = match (dependencies.resolve_local_db_path)() { - Ok(path) => { - let health = FileLocationHealth { - label: "Local DB", - state: if path.exists() { "present" } else { "expected" }, - path, - }; - inspect_local_db_health(&health, problems, dependencies); - Some(health) - } - Err(error) => { - problems.push(DoctorProblem { - kind: ProblemKind::UnableToResolveLocalDbPath, - category: ProblemCategory::GlobalState, - severity: ProblemSeverity::Error, - fixability: ProblemFixability::ManualOnly, - summary: format!("Unable to resolve expected local DB path: {error}"), - remediation: String::from("Verify that the SCE state root can be resolved on this machine before rerunning 'sce doctor'."), - next_action: "manual_steps", - }); - None - } - }; - GlobalStateHealth { state_root: state_root_health, config_locations, - local_db, - } -} - -fn inspect_local_db_health( - db_health: &FileLocationHealth, - problems: &mut Vec, - dependencies: &DoctorDependencies<'_>, -) { - let Some(parent) = db_health.path.parent() else { - problems.push(DoctorProblem { - kind: ProblemKind::LocalDbPathHasNoParent, - category: ProblemCategory::GlobalState, - severity: ProblemSeverity::Error, - fixability: ProblemFixability::ManualOnly, - summary: format!("Local DB path '{}' has no parent directory.", db_health.path.display()), - remediation: String::from("Verify that the SCE state root resolves to a normal filesystem path before rerunning 'sce doctor'."), - next_action: "manual_steps", - }); - return; - }; - - match inspect_directory_write_readiness(parent) { - DirectoryWriteReadiness::Ready => {} - DirectoryWriteReadiness::Missing => problems.push(DoctorProblem { - kind: ProblemKind::LocalDbParentMissing, - category: ProblemCategory::FilesystemPermissions, - severity: ProblemSeverity::Error, - fixability: ProblemFixability::AutoFixable, - summary: format!("Local DB parent directory '{}' does not exist.", parent.display()), - remediation: format!( - "Run 'sce doctor --fix' to create the SCE-owned state directory '{}', or create it manually with write access and rerun 'sce doctor'.", - parent.display() - ), - next_action: "doctor_fix", - }), - DirectoryWriteReadiness::NotDirectory => problems.push(DoctorProblem { - kind: ProblemKind::LocalDbParentNotDirectory, - category: ProblemCategory::FilesystemPermissions, - severity: ProblemSeverity::Error, - fixability: ProblemFixability::ManualOnly, - summary: format!("Local DB parent path '{}' is not a directory.", parent.display()), - remediation: format!( - "Replace '{}' with a writable directory before rerunning 'sce doctor'.", - parent.display() - ), - next_action: "manual_steps", - }), - DirectoryWriteReadiness::ReadOnly => problems.push(DoctorProblem { - kind: ProblemKind::LocalDbParentNotWritable, - category: ProblemCategory::FilesystemPermissions, - severity: ProblemSeverity::Error, - fixability: ProblemFixability::ManualOnly, - summary: format!("Local DB parent directory '{}' is not writable.", parent.display()), - remediation: format!( - "Grant write access to '{}' before rerunning 'sce doctor'.", - parent.display() - ), - next_action: "manual_steps", - }), - DirectoryWriteReadiness::Unknown(error) => problems.push(DoctorProblem { - kind: ProblemKind::LocalDbParentInspectionFailed, - category: ProblemCategory::FilesystemPermissions, - severity: ProblemSeverity::Error, - fixability: ProblemFixability::ManualOnly, - summary: format!("Unable to inspect local DB parent directory '{}': {error}", parent.display()), - remediation: format!( - "Verify that '{}' is accessible and writable before rerunning 'sce doctor'.", - parent.display() - ), - next_action: "manual_steps", - }), - } - - if db_health.path.exists() { - if let Err(error) = (dependencies.check_local_db_health)(&db_health.path) { - problems.push(DoctorProblem { - kind: ProblemKind::LocalDbHealthCheckFailed, - category: ProblemCategory::GlobalState, - severity: ProblemSeverity::Error, - fixability: ProblemFixability::ManualOnly, - summary: format!( - "Local DB '{}' failed health checks: {error}", - db_health.path.display() - ), - remediation: format!( - "Repair or replace the local DB at '{}' and rerun 'sce doctor'.", - db_health.path.display() - ), - next_action: "manual_steps", - }); - } } } @@ -1160,24 +1021,6 @@ fn is_executable(metadata: &fs::Metadata) -> bool { metadata.is_file() } -fn inspect_directory_write_readiness(path: &Path) -> DirectoryWriteReadiness { - match fs::metadata(path) { - Ok(metadata) => { - if !metadata.is_dir() { - DirectoryWriteReadiness::NotDirectory - } else if metadata.permissions().readonly() { - DirectoryWriteReadiness::ReadOnly - } else { - DirectoryWriteReadiness::Ready - } - } - Err(error) if error.kind() == std::io::ErrorKind::NotFound => { - DirectoryWriteReadiness::Missing - } - Err(error) => DirectoryWriteReadiness::Unknown(error), - } -} - fn run_git_command(repository_root: &Path, args: &[&str]) -> Option { let output = Command::new("git") .args(args) @@ -1234,15 +1077,6 @@ fn format_report_with_color_policy(report: &HookDoctorReport, color_enabled: boo |location| location.path.display().to_string(), ), )); - lines.push(format_human_text_row( - color_enabled, - local_db_status(report), - "Local DB", - report.local_db.as_ref().map_or_else( - || String::from("not detected"), - |location| location.path.display().to_string(), - ), - )); lines.push(format!("\n{}:", heading("Configuration"))); for location in &report.config_locations { @@ -1393,27 +1227,6 @@ fn state_root_status(report: &HookDoctorReport) -> HumanTextStatus { } } -fn local_db_status(report: &HookDoctorReport) -> HumanTextStatus { - if report.local_db.is_none() - || report.problems.iter().any(|problem| { - matches!( - problem.kind, - ProblemKind::UnableToResolveLocalDbPath - | ProblemKind::LocalDbPathHasNoParent - | ProblemKind::LocalDbParentMissing - | ProblemKind::LocalDbParentNotDirectory - | ProblemKind::LocalDbParentNotWritable - | ProblemKind::LocalDbParentInspectionFailed - | ProblemKind::LocalDbHealthCheckFailed - ) - }) - { - HumanTextStatus::Fail - } else { - HumanTextStatus::Pass - } -} - fn config_location_status( report: &HookDoctorReport, location: &FileLocationHealth, @@ -1639,11 +1452,6 @@ fn render_report_json(execution: &DoctorExecution) -> Result { .as_ref() .map(|path| path.display().to_string()), "config_paths": config_paths, - "local_db": report.local_db.as_ref().map(|location| json!({ - "label": location.label, - "path": location.path.display().to_string(), - "state": location.state, - })), "hooks": hooks, "problems": report.problems.iter().map(|problem| json!({ "category": problem_category(problem.category), @@ -1708,13 +1516,6 @@ fn run_auto_fixes( let mut fix_results = Vec::new(); - if auto_fixable_problems - .iter() - .any(|problem| problem.category == ProblemCategory::FilesystemPermissions) - { - fix_results.extend(run_filesystem_auto_fixes(report, dependencies)); - } - if auto_fixable_problems .iter() .any(|problem| problem.category == ProblemCategory::HookRollout) @@ -1743,89 +1544,6 @@ fn run_auto_fixes( fix_results } -fn run_filesystem_auto_fixes( - report: &HookDoctorReport, - dependencies: &DoctorDependencies<'_>, -) -> Vec { - let Some(db_path) = report.local_db.as_ref().map(|location| &location.path) else { - return vec![DoctorFixResultRecord { - category: ProblemCategory::FilesystemPermissions, - outcome: FixResult::Failed, - detail: String::from("Automatic local DB directory repair could not start because the expected local DB path was not resolved during diagnosis."), - }]; - }; - - let Some(parent) = db_path.parent() else { - return vec![DoctorFixResultRecord { - category: ProblemCategory::FilesystemPermissions, - outcome: FixResult::Failed, - detail: format!( - "Automatic local DB directory repair could not start because '{}' has no parent directory.", - db_path.display() - ), - }]; - }; - - let expected_parent = match (dependencies.resolve_local_db_path)() { - Ok(path) => path.parent().map(Path::to_path_buf), - Err(error) => { - return vec![DoctorFixResultRecord { - category: ProblemCategory::FilesystemPermissions, - outcome: FixResult::Failed, - detail: format!( - "Automatic local DB directory repair could not confirm the canonical SCE-owned path: {error}" - ), - }]; - } - }; - - if expected_parent.as_deref() != Some(parent) { - return vec![DoctorFixResultRecord { - category: ProblemCategory::FilesystemPermissions, - outcome: FixResult::Failed, - detail: format!( - "Automatic local DB directory repair refused to modify '{}' because it does not match the canonical SCE-owned path.", - parent.display() - ), - }]; - } - - if parent.exists() { - return vec![DoctorFixResultRecord { - category: ProblemCategory::FilesystemPermissions, - outcome: FixResult::Skipped, - detail: format!( - "Local DB directory '{}' already exists; no directory bootstrap was needed.", - parent.display() - ), - }]; - } - - match (dependencies.create_directory_all)(parent) { - Ok(()) => vec![DoctorFixResultRecord { - category: ProblemCategory::FilesystemPermissions, - outcome: FixResult::Fixed, - detail: format!( - "Created the SCE-owned local DB directory '{}'.", - parent.display() - ), - }], - Err(error) => vec![DoctorFixResultRecord { - category: ProblemCategory::FilesystemPermissions, - outcome: FixResult::Failed, - detail: format!( - "Automatic local DB directory repair failed for '{}': {error}", - parent.display() - ), - }], - } -} - -fn create_directory_all(path: &Path) -> Result<()> { - fs::create_dir_all(path) - .with_context(|| format!("Failed to create directory '{}'.", path.display())) -} - fn build_hook_fix_results(outcome: &RequiredHooksInstallOutcome) -> Vec { outcome .hook_results diff --git a/cli/src/services/local_db.rs b/cli/src/services/local_db.rs deleted file mode 100644 index 4bf8277..0000000 --- a/cli/src/services/local_db.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::path::{Path, PathBuf}; - -use anyhow::{anyhow, Result}; -use turso::Builder; - -use crate::services::default_paths::resolve_sce_default_locations; - -#[derive(Clone, Copy, Debug)] -pub enum LocalDatabaseTarget<'a> { - Path(&'a Path), -} - -pub fn resolve_local_db_path() -> Result { - Ok(resolve_sce_default_locations()?.local_db()) -} - -pub(crate) fn check_local_db_health_blocking(path: &Path) -> Result<()> { - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_time() - .build()?; - runtime.block_on(check_local_db_health(path)) -} - -async fn check_local_db_health(path: &Path) -> Result<()> { - let conn = connect_local(LocalDatabaseTarget::Path(path)).await?; - let mut rows = conn.query("PRAGMA schema_version", ()).await?; - let _ = rows.next().await?; - Ok(()) -} - -async fn connect_local(target: LocalDatabaseTarget<'_>) -> Result { - let location = target_location(target)?; - let db = Builder::new_local(location).build().await?; - let conn = db.connect()?; - conn.execute("PRAGMA foreign_keys = ON", ()).await?; - Ok(conn) -} - -fn target_location(target: LocalDatabaseTarget<'_>) -> Result<&str> { - match target { - LocalDatabaseTarget::Path(path) => path - .to_str() - .ok_or_else(|| anyhow!("Local DB path must be valid UTF-8: {}", path.display())), - } -} - -pub(crate) fn resolve_state_data_root() -> Result { - Ok(resolve_sce_default_locations()? - .roots() - .state_root() - .to_path_buf()) -} diff --git a/cli/src/services/mod.rs b/cli/src/services/mod.rs index 3c68298..71863ae 100644 --- a/cli/src/services/mod.rs +++ b/cli/src/services/mod.rs @@ -6,7 +6,6 @@ pub mod default_paths; pub mod doctor; pub mod error; pub mod hooks; -pub mod local_db; pub mod observability; pub mod output_format; pub mod resilience; diff --git a/context/plans/agent-trace-removal-and-hook-noop-reset.md b/context/plans/agent-trace-removal-and-hook-noop-reset.md index 5994b47..a365047 100644 --- a/context/plans/agent-trace-removal-and-hook-noop-reset.md +++ b/context/plans/agent-trace-removal-and-hook-noop-reset.md @@ -61,12 +61,43 @@ - Files changed: `context/architecture.md`, `context/cli/cli-command-surface.md`, `context/context-map.md`, `context/glossary.md`, `context/overview.md`, `context/patterns.md`, `context/sce/agent-trace-implementation-contract.md`, `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md`, `context/sce/agent-trace-payload-builder-validation.md`, `context/sce/agent-trace-post-commit-dual-write.md`, `context/sce/agent-trace-post-rewrite-local-remap-ingestion.md`, `context/sce/agent-trace-pre-commit-staged-checkpoint.md`, `context/sce/agent-trace-retry-queue-observability.md`, `context/sce/agent-trace-rewrite-trace-transformation.md`, `context/sce/agent-trace-schema-adapter.md` - Evidence: `nix run .#pkl-check-generated`; `nix flake check` -- [ ] T05: Validation and cleanup (status:todo) +- [x] T05: Validation and cleanup (status:done) - Task ID: T05 - Goal: Run full repository validation, confirm removed tracing behavior stays absent while optional attribution behavior still works when enabled, and clean up any leftover dead references or temporary scaffolding. - Boundaries (in/out of scope): In scope: `nix run .#pkl-check-generated`, `nix flake check`, final targeted spot-checks for disabled-default noop hooks, enabled attribution-only behavior, empty DB expectations, and final context-sync verification. Out of scope: new feature work or redesign follow-ons. - Done when: Required validation passes, leftover dead/stale trace-removal artifacts are cleaned up, and plan/context state is ready for handoff completion. - Verification notes (commands or checks): `nix run .#pkl-check-generated`; `nix flake check`; targeted inspection that hooks are no-op by default, attribution works when enabled, and local DB bootstrap remains empty-file only. + - Completed: 2026-04-08 + - Files changed: `context/plans/agent-trace-removal-and-hook-noop-reset.md` + - Evidence: `nix run .#pkl-check-generated`; `nix flake check`; targeted `hooks` runtime spot-check in a temporary git repo; code inspection of `cli/src/services/local_db.rs` + +## Validation Report + +### Commands run +- `nix run .#pkl-check-generated` -> exit 0 (`Generated outputs are up to date.`) +- `nix flake check` -> exit 0 (all 13 flake checks passed; Nix reported only the standard incompatible-system omission warning) +- Targeted hook spot-check in a temporary git repo via `nix develop -c cargo run --manifest-path /cli/Cargo.toml --quiet -- hooks ...` -> exit 0 + - `hooks pre-commit` reported `AttributionOnlyCommitMsgMode` + - `hooks post-commit` reported `AttributionOnlyCommitMsgMode` + - `hooks post-rewrite amend` reported `AttributionOnlyCommitMsgMode` + - `hooks commit-msg` with default config reported `policy_gate_passed=false, trailer_applied=false` + - `hooks commit-msg` with `SCE_ATTRIBUTION_HOOKS_ENABLED=true` reported `policy_gate_passed=true, trailer_applied=true` and appended the canonical `Co-authored-by: SCE ` trailer + +### Cleanup +- No leftover dead references or temporary scaffolding required removal during this task. + +### Success-criteria verification +- [x] Agent Trace persistence, schema migration, trace emission, retry, notes, and rewrite-handling behavior remain removed from the active runtime surface -> confirmed by `nix flake check`, targeted hook runtime spot-checks, and no active Agent Trace runtime references found in current CLI surfaces during validation. +- [x] `local_db` remains create/open only with no schema bootstrap or trace tables -> confirmed by `cli/src/services/local_db.rs`, which only opens the local DB, enables `PRAGMA foreign_keys`, and checks `PRAGMA schema_version` without any schema DDL or migration calls. +- [x] Hook entrypoints remain present while default behavior is deterministic no-op -> confirmed by successful `hooks pre-commit`, `hooks post-commit`, and `hooks post-rewrite amend` spot-checks reporting `AttributionOnlyCommitMsgMode`. +- [x] Attribution behavior remains disabled by default and still works when explicitly enabled -> confirmed by `hooks commit-msg` spot-checks with default config (`trailer_applied=false`) and `SCE_ATTRIBUTION_HOOKS_ENABLED=true` (`trailer_applied=true` plus canonical trailer insertion). +- [x] Generated/current-state assets remain aligned -> confirmed by `nix run .#pkl-check-generated` and final context verification for this task. + +### Failed checks and follow-ups +- None. + +### Residual risks +- None identified for this task scope. ## Open questions - None.