From 1ed3061dc50360abb219284bfce3f37ea49a2fa9 Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Tue, 30 Jun 2026 15:19:10 +0200 Subject: [PATCH 1/9] hooks: Remove session-model hook route Remove the session-model hook subcommand from CLI parsing and runtime dispatch, and stop generating the Claude SessionStart hook that invoked it. Keep the existing session_models database fallback in place for later cleanup tasks. Plan: remove-session-models-direct-claude-model-id Task: T01 Co-authored-by: SCE --- cli/src/cli_schema.rs | 6 - cli/src/services/agent_trace_db/mod.rs | 3 + cli/src/services/hooks/mod.rs | 370 +----------------- cli/src/services/parse/command_runtime.rs | 3 - config/.claude/settings.json | 12 +- config/pkl/renderers/claude-content.pkl | 10 - context/architecture.md | 6 +- context/cli/cli-command-surface.md | 7 +- context/context-map.md | 4 +- context/glossary.md | 8 +- context/overview.md | 91 +++++ context/patterns.md | 2 +- ...e-session-models-direct-claude-model-id.md | 87 ++++ context/sce/agent-trace-db.md | 2 +- .../sce/agent-trace-hooks-command-routing.md | 16 +- context/sce/claude-raw-hook-capture.md | 12 +- .../generated-opencode-plugin-registration.md | 2 +- .../opencode-agent-trace-plugin-runtime.md | 7 +- 18 files changed, 208 insertions(+), 440 deletions(-) create mode 100644 context/plans/remove-session-models-direct-claude-model-id.md diff --git a/cli/src/cli_schema.rs b/cli/src/cli_schema.rs index d1b2ff40..e5fc558a 100644 --- a/cli/src/cli_schema.rs +++ b/cli/src/cli_schema.rs @@ -347,12 +347,6 @@ pub enum HooksSubcommand { #[command(about = "Run conversation-trace hook (reads JSON payload from STDIN)")] ConversationTrace, - - #[command( - name = "session-model", - about = "Ingest editor session model attribution (reads JSON payload from STDIN)" - )] - SessionModel, } #[derive(Subcommand, Debug, Clone, PartialEq, Eq)] diff --git a/cli/src/services/agent_trace_db/mod.rs b/cli/src/services/agent_trace_db/mod.rs index ecd38f7a..5faf1487 100644 --- a/cli/src/services/agent_trace_db/mod.rs +++ b/cli/src/services/agent_trace_db/mod.rs @@ -146,6 +146,7 @@ pub struct DiffTraceInsert<'a> { /// Session model attribution payload to upsert into the agent trace database. #[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[allow(dead_code)] pub struct SessionModelUpsert<'a> { pub tool_name: &'a str, pub session_id: &'a str, @@ -339,6 +340,7 @@ impl AgentTraceDb { } /// Upsert editor session model attribution into the `session_models` table. + #[allow(dead_code)] pub fn upsert_session_model(&self, input: SessionModelUpsert<'_>) -> Result { upsert_session_model_with(self, input) } @@ -487,6 +489,7 @@ fn insert_part_with(db: &TursoDb, input: InsertPartInsert) -> Resu ) } +#[allow(dead_code)] fn upsert_session_model_with( db: &TursoDb, input: SessionModelUpsert<'_>, diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index 7842377e..cae8983b 100644 --- a/cli/src/services/hooks/mod.rs +++ b/cli/src/services/hooks/mod.rs @@ -16,7 +16,7 @@ use crate::services::agent_trace::{ use crate::services::agent_trace_db::{ AgentTraceDb, AgentTraceInsert, DiffTraceInsert, InsertMessageInsert, InsertPartInsert, MessageRole, PartType, PostCommitPatchIntersectionInsert, RecentDiffTracePatches, - SessionModelAttribution, SessionModelUpsert, PAYLOAD_TYPE_PATCH, PAYLOAD_TYPE_STRUCTURED, + SessionModelAttribution, PAYLOAD_TYPE_PATCH, PAYLOAD_TYPE_STRUCTURED, }; use crate::services::checkout; use crate::services::config; @@ -34,8 +34,6 @@ pub mod lifecycle; pub const NAME: &str = "hooks"; pub const CANONICAL_SCE_COAUTHOR_TRAILER: &str = "Co-authored-by: SCE "; -const CLAUDE_CLI_BINARY: &str = "claude"; - type PayloadValidationError = fn(&str) -> String; #[derive(Clone, Debug, Eq, PartialEq)] @@ -53,17 +51,6 @@ pub enum HookSubcommand { }, DiffTrace, ConversationTrace, - SessionModel, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -struct SessionModelPayload { - #[serde(rename = "sessionID")] - session_id: String, - time: u64, - model_id: String, - tool_name: String, - tool_version: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize)] @@ -94,14 +81,12 @@ struct ResolvedDiffTraceAttribution { #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum StdinPayloadKind { DiffTrace, - SessionModel, } impl StdinPayloadKind { fn label(self) -> &'static str { match self { Self::DiffTrace => "diff-trace", - Self::SessionModel => "session-model", } } @@ -201,7 +186,6 @@ fn run_hooks_subcommand_in_repo( HookSubcommand::ConversationTrace => { Ok(run_conversation_trace_subcommand(repository_root, logger)) } - HookSubcommand::SessionModel => run_session_model_subcommand(repository_root, logger), } } @@ -804,70 +788,6 @@ where }) } -fn run_session_model_subcommand( - repository_root: &Path, - logger: Option<&dyn Logger>, -) -> Result { - let stdin_payload = read_hook_stdin()?; - let result = run_session_model_subcommand_from_payload(repository_root, &stdin_payload, logger); - if let Err(ref error) = result { - if let Some(log) = logger { - log.error("sce.hooks.session_model.error", &error.to_string(), &[]); - } - } - result -} - -fn run_session_model_subcommand_from_payload( - repository_root: &Path, - stdin_payload: &str, - logger: Option<&dyn Logger>, -) -> Result { - let payload = parse_session_model_payload(stdin_payload)?; - - // Convert the u64 time to i64 for DB storage. - let session_start_time_ms = i64::try_from(payload.time).map_err(|_| { - anyhow!(StdinPayloadKind::SessionModel.validation_error( - "field 'time' must fit in a signed 64-bit Unix epoch millisecond value for Agent Trace DB storage" - )) - })?; - - let upsert_payload = SessionModelUpsert { - tool_name: &payload.tool_name, - session_id: &payload.session_id, - model_id: &payload.model_id, - tool_version: payload.tool_version.as_deref(), - session_start_time_ms, - }; - - let db = open_agent_trace_db_for_hook_runtime( - repository_root, - "Failed to open Agent Trace DB for session-model persistence.", - ) - .context("Failed to open Agent Trace DB for session-model persistence.")?; - let result = db - .upsert_session_model(upsert_payload) - .context("Failed to persist session model attribution to Agent Trace DB."); - - match result { - Ok(_) => Ok(String::from( - "session-model hook intake persisted session model attribution to AgentTraceDb.", - )), - Err(error) => { - if let Some(log) = logger { - log.warn( - "sce.hooks.session_model.agent_trace_db_write_failed", - &error.to_string(), - &[], - ); - } - Ok(String::from( - "session-model hook intake completed; AgentTraceDb persistence failed.", - )) - } - } -} - fn parse_diff_trace_payload(stdin_payload: &str) -> Result { let payload_kind = StdinPayloadKind::DiffTrace; let parsed: Value = serde_json::from_str(stdin_payload) @@ -979,216 +899,6 @@ fn extract_claude_event_time(payload: &serde_json::Map) -> u64 { .map_or(0, |d| d.as_millis() as u64) } -fn parse_session_model_payload(stdin_payload: &str) -> Result { - parse_session_model_payload_with(stdin_payload, capture_claude_cli_version) -} - -fn parse_session_model_payload_with( - stdin_payload: &str, - claude_tool_version: V, -) -> Result -where - V: FnOnce() -> Option, -{ - let payload_kind = StdinPayloadKind::SessionModel; - let parsed: Value = serde_json::from_str(stdin_payload) - .with_context(|| payload_kind.validation_error("expected valid JSON"))?; - let payload = parsed - .as_object() - .ok_or_else(|| anyhow!(payload_kind.validation_error("expected a JSON object")))?; - - // Classify: Claude structured payloads carry hook_event_name. - if payload.contains_key("hook_event_name") { - return parse_claude_session_model_payload(payload, payload_kind, claude_tool_version); - } - - // Original OpenCode/session-model normalized payload — unchanged. - let session_id = required_non_empty_string_field(payload, "sessionID", |d| { - payload_kind.validation_error(d) - })?; - let time = required_u64_millisecond_field(payload, "time", payload_kind)?; - let model_id = - required_non_empty_string_field(payload, "model_id", |d| payload_kind.validation_error(d))?; - let tool_name = required_non_empty_string_field(payload, "tool_name", |d| { - payload_kind.validation_error(d) - })?; - let tool_version = - required_nullable_or_non_empty_string_field(payload, "tool_version", payload_kind)?; - - Ok(SessionModelPayload { - session_id, - time, - model_id, - tool_name, - tool_version, - }) -} - -/// Parse a raw Claude `SessionStart` hook event payload into a session-model intake result. -/// -/// Extracts `session_id`, `model_id`, `time`, and `tool_version` from the raw -/// Claude hook event format (which uses `snake_case` fields and nested model objects) -/// so that Claude settings can pipe hook events directly to `sce hooks session-model`. -const CLAUDE_MODEL_ID_PREFIX: &str = "claude/"; - -fn parse_claude_session_model_payload( - payload: &serde_json::Map, - payload_kind: StdinPayloadKind, - claude_tool_version: impl FnOnce() -> Option, -) -> Result { - let event_name = required_non_empty_string_field(payload, "hook_event_name", |d| { - payload_kind.validation_error(d) - })?; - - if event_name != "SessionStart" { - bail!(payload_kind.validation_error(&format!( - "Claude '{event_name}' event is not supported for session-model intake (expected SessionStart)" - ))); - } - - let session_id = required_claude_session_id(payload, payload_kind)?; - let model_id = required_claude_model_id(payload, payload_kind)?; - let time = extract_claude_event_time(payload); - let tool_name = "claude".to_string(); - let tool_version = extract_claude_tool_version_from_payload(payload).or_else(|| { - claude_tool_version().and_then(|version| normalize_claude_tool_version(&version)) - }); - - Ok(SessionModelPayload { - session_id, - time, - model_id, - tool_name, - tool_version, - }) -} - -fn required_claude_session_id( - payload: &serde_json::Map, - payload_kind: StdinPayloadKind, -) -> Result { - for key in ["session_id", "sessionID"] { - if let Some(value) = payload.get(key) { - if let Some(s) = value.as_str() { - let trimmed = s.trim(); - if !trimmed.is_empty() { - return Ok(trimmed.to_string()); - } - } - } - } - bail!(payload_kind.validation_error( - "missing non-empty 'session_id' or 'sessionID' field for Claude SessionStart" - )) -} - -fn required_claude_model_id( - payload: &serde_json::Map, - payload_kind: StdinPayloadKind, -) -> Result { - // Try direct string fields first. - for key in ["model", "model_id", "modelId"] { - if let Some(value) = payload.get(key) { - if let Some(s) = value.as_str() { - let trimmed = s.trim(); - if !trimmed.is_empty() { - return Ok(normalize_claude_model_id(trimmed)); - } - } - // If model is an object, try nested identifier fields. - if let Some(model_obj) = value.as_object() { - for nested_key in ["id", "model", "name"] { - if let Some(nested_value) = model_obj.get(nested_key) { - if let Some(s) = nested_value.as_str() { - let trimmed = s.trim(); - if !trimmed.is_empty() { - return Ok(normalize_claude_model_id(trimmed)); - } - } - } - } - } - } - } - - bail!(payload_kind.validation_error( - "missing non-empty model identifier (model, model_id, or model.id) for Claude SessionStart" - )) -} - -fn normalize_claude_model_id(model: &str) -> String { - if model.starts_with(CLAUDE_MODEL_ID_PREFIX) { - model.to_string() - } else { - format!("{CLAUDE_MODEL_ID_PREFIX}{model}") - } -} - -fn extract_claude_tool_version_from_payload( - payload: &serde_json::Map, -) -> Option { - for key in ["tool_version", "claude_version", "version"] { - match payload.get(key) { - Some(Value::String(s)) => return normalize_claude_tool_version(s), - Some(Value::Null) => return None, - Some(_) | None => {} // non-string, non-null, or missing → skip - } - } - None -} - -fn capture_claude_cli_version() -> Option { - let output = Command::new(CLAUDE_CLI_BINARY) - .arg("--version") - .output() - .ok()?; - - if !output.status.success() { - return None; - } - - let stdout = String::from_utf8(output.stdout).ok()?; - normalize_claude_tool_version(&stdout) -} - -fn normalize_claude_tool_version(version: &str) -> Option { - let trimmed = version.trim(); - - if trimmed.is_empty() { - return None; - } - - // Extract the first version-like pattern: a contiguous sequence of digits - // and dots that starts with a digit, ends with a digit, and contains at - // least one dot. This handles formats like "2.1.170 (Claude Code)" → - // "2.1.170" or "Claude Code 1.2.3" → "1.2.3". - let mut result = String::new(); - let mut in_version = false; - - for ch in trimmed.chars() { - if ch.is_ascii_digit() { - in_version = true; - result.push(ch); - } else if ch == '.' && in_version { - result.push(ch); - } else if in_version { - // Non-digit, non-dot while in a version sequence — check for match - if result.ends_with(|c: char| c.is_ascii_digit()) && result.contains('.') { - return Some(result); - } - in_version = false; - result.clear(); - } - } - - // Check at end of input - if in_version && result.ends_with(|c: char| c.is_ascii_digit()) && result.contains('.') { - Some(result) - } else { - None - } -} - fn required_nullable_or_non_empty_string_field( payload: &serde_json::Map, field_name: &str, @@ -1893,7 +1603,6 @@ fn hook_runtime_invocation_name(subcommand: &HookSubcommand) -> &'static str { HookSubcommand::PostRewrite { .. } => "post-rewrite runtime invocation", HookSubcommand::DiffTrace => "diff-trace runtime invocation", HookSubcommand::ConversationTrace => "conversation-trace runtime invocation", - HookSubcommand::SessionModel => "session-model runtime invocation", } } @@ -2547,27 +2256,6 @@ mod tests { .contains("field 'type' must be a string")); } - fn claude_session_start_payload(extra_fields: &Value) -> String { - let mut payload = serde_json::json!({ - "hook_event_name": "SessionStart", - "session_id": "session-123", - "model": "sonnet-4", - "time": 1_800_000_000_000_u64, - }); - let payload_object = payload - .as_object_mut() - .expect("base test payload should be a JSON object"); - let extra_object = extra_fields - .as_object() - .expect("extra test payload fields should be a JSON object"); - - for (key, value) in extra_object { - payload_object.insert(key.clone(), value.clone()); - } - - payload.to_string() - } - fn diff_trace_payload(model_id: Option<&str>, tool_version: Option<&str>) -> DiffTracePayload { DiffTracePayload { session_id: String::from("session-123"), @@ -2593,62 +2281,6 @@ mod tests { } } - #[test] - fn claude_session_model_payload_prefers_payload_tool_version_without_cli_probe() { - let payload = claude_session_start_payload(&serde_json::json!({ - "tool_version": " Claude Code 1.2.3\n", - })); - - let output = parse_session_model_payload_with(&payload, || { - panic!("payload tool_version should avoid Claude CLI version probe") - }) - .expect("Claude SessionStart payload should parse"); - - assert_eq!(output.session_id, "session-123"); - assert_eq!(output.model_id, "claude/sonnet-4"); - assert_eq!(output.tool_name, "claude"); - assert_eq!(output.tool_version, Some(String::from("1.2.3"))); - } - - #[test] - fn claude_session_model_payload_prefers_payload_claude_version_without_cli_probe() { - let payload = claude_session_start_payload(&serde_json::json!({ - "claude_version": "Claude Code 1.2.4", - })); - - let output = parse_session_model_payload_with(&payload, || { - panic!("payload claude_version should avoid Claude CLI version probe") - }) - .expect("Claude SessionStart payload should parse"); - - assert_eq!(output.tool_version, Some(String::from("1.2.4"))); - } - - #[test] - fn claude_session_model_payload_uses_cli_version_when_payload_version_missing() { - let payload = claude_session_start_payload(&serde_json::json!({})); - - let output = parse_session_model_payload_with(&payload, || { - Some(String::from("\nClaude Code 2.0.0 ")) - }) - .expect("Claude SessionStart payload should parse"); - - assert_eq!(output.tool_version, Some(String::from("2.0.0"))); - } - - #[test] - fn claude_session_model_payload_keeps_none_for_failed_or_empty_cli_version() { - let payload = claude_session_start_payload(&serde_json::json!({})); - - let unavailable = parse_session_model_payload_with(&payload, || None) - .expect("Claude SessionStart payload should parse with unavailable CLI version"); - assert_eq!(unavailable.tool_version, None); - - let empty = parse_session_model_payload_with(&payload, || Some(String::from("\n \t"))) - .expect("Claude SessionStart payload should parse with empty CLI version"); - assert_eq!(empty.tool_version, None); - } - #[test] fn diff_trace_attribution_uses_session_tool_version_when_payload_missing() { let payload = diff_trace_payload(Some("direct-model"), None); diff --git a/cli/src/services/parse/command_runtime.rs b/cli/src/services/parse/command_runtime.rs index e2ea8b60..01c8d804 100644 --- a/cli/src/services/parse/command_runtime.rs +++ b/cli/src/services/parse/command_runtime.rs @@ -452,9 +452,6 @@ fn convert_hooks_subcommand_request( Ok(services::hooks::HookSubcommand::PostRewrite { rewrite_method }) } cli_schema::HooksSubcommand::DiffTrace => Ok(services::hooks::HookSubcommand::DiffTrace), - cli_schema::HooksSubcommand::SessionModel => { - Ok(services::hooks::HookSubcommand::SessionModel) - } cli_schema::HooksSubcommand::ConversationTrace => { Ok(services::hooks::HookSubcommand::ConversationTrace) } diff --git a/config/.claude/settings.json b/config/.claude/settings.json index 51c102e7..090ced25 100644 --- a/config/.claude/settings.json +++ b/config/.claude/settings.json @@ -1,16 +1,6 @@ { "$schema": "https://json.schemastore.org/claude-code-settings.json", "hooks": { - "SessionStart": [ - { - "hooks": [ - { - "type": "command", - "command": "bash .claude/hooks/run-sce-or-show-install-guidance.sh sce hooks session-model" - } - ] - } - ], "PreToolUse": [ { "matcher": "Bash", @@ -62,4 +52,4 @@ } ] } -} \ No newline at end of file +} diff --git a/config/pkl/renderers/claude-content.pkl b/config/pkl/renderers/claude-content.pkl index bc30e7e0..1bad0626 100644 --- a/config/pkl/renderers/claude-content.pkl +++ b/config/pkl/renderers/claude-content.pkl @@ -62,16 +62,6 @@ settings = new common.RenderedTextFile { { "$schema": "https://json.schemastore.org/claude-code-settings.json", "hooks": { - "SessionStart": [ - { - "hooks": [ - { - "type": "command", - "command": "\(sceHookCommand("sce hooks session-model"))" - } - ] - } - ], "PreToolUse": [ { "matcher": "Bash", diff --git a/context/architecture.md b/context/architecture.md index a2c55518..b0541269 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -124,10 +124,8 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/doctor/mod.rs` owns the current doctor request/report surface while focused submodules (`doctor/inspect.rs`, `doctor/render.rs`, `doctor/fixes.rs`, `doctor/types.rs`) split report fact collection, rendering, manual fix reporting, and doctor-owned domain types into smaller seams; `cli/src/services/doctor/command.rs` owns the `DoctorCommand` payload used by the static `RuntimeCommand` enum and executes against any context implementing repo-root scoping. Runtime doctor execution resolves a repository root, derives a scoped context, requests the shared static lifecycle provider catalog with hooks included for service-owned `diagnose` and `fix` behavior, adapts lifecycle-owned health/fix records into doctor-owned problem/fix records, and then renders stable text/JSON problem records with category/severity/fixability/remediation fields plus deterministic fix-result reporting in fix mode. Checkout DB discovery no longer lives in `doctor`; it moved to the `trace` group (`sce trace db list`) in `cli/src/services/trace/`. Report fact collection preserves environment/repository/hook/integration display data and adds checkout identity plus per-checkout Agent Trace DB status when a checkout ID exists, while service-owned lifecycle providers own config validation, local DB and Agent Trace DB readiness/bootstrap, and hook rollout diagnosis/repair. - `cli/src/services/version/mod.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/version/command.rs` owns the `VersionCommand` payload used by the static `RuntimeCommand` enum. - `cli/src/services/completion/mod.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/completion/command.rs` owns the `CompletionCommand` payload used by the static `RuntimeCommand` enum. -- `cli/src/services/hooks/mod.rs` defines the current 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 enabled-by-default attribution-hooks config/env control is not opted out, `SCE_DISABLED` is false, and the staged-diff AI-overlap preflight confirms AI/editor evidence (`StagedDiffAiOverlapResult::Overlap`); the preflight is wired into `run_commit_msg_subcommand_in_repo` and logs `sce.hooks.commit_msg.ai_overlap_error` on error paths; `cli/src/services/hooks/command.rs` owns the `HooksCommand` payload used by the static `RuntimeCommand` enum. In the current attribution-only baseline, `pre-commit` and `post-rewrite` are deterministic no-op surfaces; `post-commit` requires validated `--remote-url`, threads that URL through the Agent Trace flow, prints it to stderr, and remains an active intersection + Agent Trace persistence entrypoint (captures current commit patch, queries recent `diff_traces` from the bounded past-7-days window, combines valid patches via `patch::combine_patches`, intersects with post-commit patch via `patch::intersect_patches`, persists result to `post_commit_patch_intersections`, then persists built Agent Trace payloads with range-level `content_hash` values to `agent_traces` in AgentTraceDb without post-commit file artifacts); `diff-trace` performs STDIN JSON intake, validates required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent/`null` → `None`, resolved from `session_models` by `tool_name` + `session_id` when absent) and required `tool_version` (present and either `null` or non-empty string) plus required `u64` `time` (Unix epoch milliseconds), rejects values that cannot fit AgentTraceDb signed `time_ms` storage, inserts the parsed payload fields into AgentTraceDb without writing a parsed-payload artifact under `context/tmp`; `session-model` performs STDIN JSON intake, validates required non-empty `sessionID`/`model_id`/`tool_name`, required `u64` `time` (Unix epoch milliseconds), and required nullable/non-empty `tool_version`, then upserts the parsed payload into AgentTraceDb `se... (line truncated to 2000 chars) -- `cli/src/services/hooks/mod.rs` defines the current 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 enabled-by-default attribution-hooks config/env control is not opted out, `SCE_DISABLED` is false, and the staged-diff AI-overlap preflight confirms AI/editor evidence (`StagedDiffAiOverlapResult::Overlap`); the preflight is wired into `run_commit_msg_subcommand_in_repo` and logs `sce.hooks.commit_msg.ai_overlap_error` on error paths; `cli/src/services/hooks/command.rs` owns the `HooksCommand` payload used by the static `RuntimeCommand` enum. In the current attribution-only baseline, `pre-commit` and `post-rewrite` are deterministic no-op surfaces; `post-commit` requires validated `--remote-url`, threads that URL through the Agent Trace flow, prints it to stderr, and remains an active intersection + Agent Trace persistence entrypoint (captures current commit patch, queries recent `diff_traces` from the bounded past-7-days window, combines valid patches via `patch::combine_patches`, intersects with post-commit patch via `patch::intersect_patches`, persists result to `post_commit_patch_intersections`, then persists built Agent Trace payloads with range-level `content_hash` values to `agent_traces` in AgentTraceDb without post-commit file artifacts); `diff-trace` performs STDIN JSON intake, validates required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent/`null` → `None`, resolved from `session_models` by `tool_name` + `session_id` when absent) and required `tool_version` (present and either `null` or non-empty string) plus required `u64` `time` (Unix epoch milliseconds), rejects values that cannot fit AgentTraceDb signed `time_ms` storage, writes one collision-safe parsed-payload `context/tmp/-000000-diff-trace.json` artifact, and inserts the parsed payload fields into AgentTraceDb; `session-model` performs STDIN JSON intake, validates required non-empty `sessionID`/`model_id`/`tool_name`, required `u64` `time` (Unix epoch milliseconds), and required nullable/non-empty `tool_version`, then upserts the parsed payload into AgentTraceDb `se... (line truncated to 2000 chars) -- The external `diff-trace` and `conversation-trace` hook boundaries are fail-open for producer-facing intake failures: `run_diff_trace_subcommand` logs strict-path errors through `sce.hooks.diff_trace.error`, `run_conversation_trace_subcommand` logs strict-path errors through `sce.hooks.conversation_trace.error`, and both return hook success text while preserving valid-payload persistence output plus existing warning/skipped behavior. -- Claude `SessionStart` session-model parsing in `cli/src/services/hooks/mod.rs` uses explicit payload version fields (`tool_version`/`claude_version`/`version`) when present; if no non-empty payload version is available, it best-effort runs `claude --version`, trims stdout, and leaves `tool_version` nullable without failing intake when the command is unavailable, fails, or returns empty output. +- `cli/src/services/hooks/mod.rs` defines the current 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 enabled-by-default attribution-hooks config/env control is not opted out, `SCE_DISABLED` is false, and the staged-diff AI-overlap preflight confirms AI/editor evidence (`StagedDiffAiOverlapResult::Overlap`); the preflight is wired into `run_commit_msg_subcommand_in_repo` and logs `sce.hooks.commit_msg.ai_overlap_error` on error paths; `cli/src/services/hooks/command.rs` owns the `HooksCommand` payload used by the static `RuntimeCommand` enum. In the current attribution-only baseline, `pre-commit` and `post-rewrite` are deterministic no-op surfaces; `post-commit` requires validated `--remote-url`, threads that URL through the Agent Trace flow, prints it to stderr, and remains an active intersection + Agent Trace persistence entrypoint; `diff-trace` performs STDIN JSON intake, validates required fields, rejects values that cannot fit AgentTraceDb signed `time_ms` storage, resolves missing/nullable attribution from existing `session_models` rows when available, and inserts parsed payload fields into AgentTraceDb without writing parsed-payload artifacts under `context/tmp`; `conversation-trace` persists normalized message/part rows. `session-model` is no longer a supported hook command. +- Generated Claude settings no longer register `SessionStart` for Agent Trace model attribution. `session_models` DB APIs and diff-trace fallback lookup remain temporarily in place for later cleanup tasks, but no active route writes new session-model rows. - Diff-trace attribution resolution in `cli/src/services/hooks/mod.rs` looks up `session_models` when `model_id` or `tool_version` is missing/nullable, fills only missing fields from the stored row when available, preserves direct payload precedence, and continues persistence with `None` for unresolved attribution. - `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. - No user-invocable `sce sync` command is wired in the current runtime; local DB bootstrap and setup-time per-checkout Agent Trace DB initialization flow through lifecycle providers aggregated by setup, while checkout/global DB health/repair flow through the doctor surface and checkout DB discovery flows through the `trace` group (`sce trace db list`). diff --git a/context/cli/cli-command-surface.md b/context/cli/cli-command-surface.md index cf84fcf0..a54354ba 100644 --- a/context/cli/cli-command-surface.md +++ b/context/cli/cli-command-surface.md @@ -53,8 +53,7 @@ Operator onboarding currently comes from `sce --help`, command-local `--help` ou - `auth` and `hooks` stay parser-valid and directly invocable, but are hidden from those top-level help surfaces Deferred or gated command surfaces currently avoid claiming unimplemented behavior. -`hooks` routes through implemented subcommand parsing/dispatch for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `session-model`; current behavior remains attribution-only and enabled by default for commit attribution unless explicitly opted out (via `SCE_ATTRIBUTION_HOOKS_DISABLED`, `SCE_DISABLED`, or `policies.attribution_hooks.enabled = false`), gated by the staged-diff AI-overlap preflight so the trailer is appended only when AI/editor evidence is found, while `post-commit` requires validated `--remote-url`, threads that value through Agent Trace flow, prints it to stderr, and remains the active intersection + Agent Trace DB path, `diff-trace` is active STDIN intake with required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent/`null` → `None`), required nullable/non-empty `tool_version`, plus required `u64` `time` (Unix epoch milliseconds) validation, missing/nullable attribution fallback from `session_models` by `tool_name` + `session_id` with direct payload values taking precedence, non-lossy AgentTraceDb `time_ms` conversion, and AgentTraceDb insertion including nullable/resolved `model_id` and `tool_version` without writing parsed-payload artifacts under `context/tmp`, and `session-model` performs STDIN intake for normalized model attribution upsert without raw artifact persistence, with Claude `SessionStart` parsing best-effort filling missing `tool_version` from `claude --version`. -`hooks` routes through implemented subcommand parsing/dispatch for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, `conversation-trace`, and `session-model`; current behavior remains attribution-only and enabled by default for commit attribution unless explicitly opted out (via `SCE_ATTRIBUTION_HOOKS_DISABLED`, `SCE_DISABLED`, or `policies.attribution_hooks.enabled = false`), gated by the staged-diff AI-overlap preflight so the trailer is appended only when AI/editor evidence is found, while `post-commit` requires validated `--remote-url`, threads that value through Agent Trace flow, prints it to stderr, and remains the active intersection + Agent Trace DB path, `diff-trace` is active STDIN intake with required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent/`null` → `None`), required nullable/non-empty `tool_version`, plus required `u64` `time` (Unix epoch milliseconds) validation, missing/nullable attribution fallback from `session_models` by `tool_name` + `session_id` with direct payload values taking precedence, non-lossy AgentTraceDb `time_ms` conversion, collision-safe per-invocation `context/tmp/-000000-diff-trace.json` parsed-payload writes, AgentTraceDb insertion including nullable/resolved `model_id` and `tool_version`, and fail-open logging through `sce.hooks.diff_trace.error` for intake failures, `conversation-trace` is active message/part intake with mixed-batch/raw-Claude-event parsing and fail-open logging through `sce.hooks.conversation_trace.error` for intake failures, and `session-model` performs STDIN intake for normalized model attribution upsert without raw artifact persistence, with Claude `SessionStart` parsing best-effort filling missing `tool_version` from `claude --version`. +`hooks` routes through implemented subcommand parsing/dispatch for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `conversation-trace`; `session-model` is no longer a supported hooks route. Current behavior remains attribution-only and enabled by default for commit attribution unless explicitly opted out (via `SCE_ATTRIBUTION_HOOKS_DISABLED`, `SCE_DISABLED`, or `policies.attribution_hooks.enabled = false`), gated by the staged-diff AI-overlap preflight so the trailer is appended only when AI/editor evidence is found. `post-commit` requires validated `--remote-url`, threads that value through Agent Trace flow, prints it to stderr, and remains the active intersection + Agent Trace DB path. `diff-trace` is active STDIN intake with required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id`, required nullable/non-empty `tool_version`, required `u64` `time` validation, missing/nullable attribution fallback from existing `session_models` rows by `tool_name` + `session_id` with direct payload values taking precedence, non-lossy AgentTraceDb `time_ms` conversion, and AgentTraceDb insertion including nullable/resolved `model_id` and `tool_version` without parsed-payload artifact writes under `context/tmp`. `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 `. @@ -92,9 +91,7 @@ A user-invocable `sync` command is not wired in the current CLI surface; local D - `cli/src/services/doctor/mod.rs` defines the implemented doctor request/report contract (`DoctorRequest`, `DoctorAction`, `DoctorMode`, `run_doctor`) while focused submodules under `cli/src/services/doctor/` handle runtime command dispatch (`command.rs`), diagnosis (`inspect.rs`), rendering (`render.rs`), fix execution (`fixes.rs`), and doctor-owned domain types (`types.rs`). Together they preserve explicit fix-mode parsing, checkout-database discovery, stable text/JSON problem and database-record rendering, deterministic fix-result reporting, and aggregation of `ServiceLifecycle::diagnose`/`ServiceLifecycle::fix` across registered providers (`config`, `local_db`, `auth_db`, `agent_trace_db`, `hooks`). The doctor module coordinates state-root/config/database reporting and validation, 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, and repair-mode delegation to service-owned fix implementations. - `cli/src/services/version/mod.rs` defines the version parser/output contract (`parse_version_request`, `render_version`) with deterministic text/JSON output modes; `cli/src/services/version/command.rs` owns the version runtime command handler. - `cli/src/services/completion/mod.rs` defines the completion output contract (`render_completion`) using clap_complete to generate deterministic shell scripts for Bash, Zsh, and Fish; `cli/src/services/completion/command.rs` owns the completion runtime command handler. -- `cli/src/services/hooks/mod.rs` defines production local hook runtime parsing/dispatch (`HookSubcommand`, `run_hooks_subcommand`) for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `session-model`; `cli/src/services/hooks/command.rs` owns the hook runtime command handler. Current runtime behavior is commit-msg-only attribution behind the enabled-by-default attribution gate with explicit opt-out controls; `pre-commit` and `post-rewrite` are deterministic no-ops; `post-commit` requires validated `--remote-url`, threads that value through Agent Trace flow, prints it to stderr, and remains an active intersection + Agent Trace DB persistence path (captures current commit patch, combines/intersects recent `diff_traces`, persists intersection metadata to `post_commit_patch_intersections`, then persists built Agent Trace payload with range-level `content_hash` values to `agent_traces`); `diff-trace` performs STDIN JSON intake, required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id`, required nullable/non-empty `tool_version`, plus required `u64` `time` (Unix epoch milliseconds) validation, missing/nullable attribution fallback from `session_models` by `tool_name` + `session_id` while preserving direct payload precedence, non-lossy AgentTraceDb `time_ms` conversion, and AgentTraceDb insertion whose failure is logged and reflected in deterministic success text without writing a `context/tmp` artifact fallback; and `session-model` performs STDIN JSON intake for normalized model attribution upsert without raw artifact persistence, with Claude `SessionStart` parsing best-effort filling missing `tool_version` from `claude --version`. `cli/src/services/hooks/lifecycle.rs` implements `ServiceLifecycle` for hook health checks, fix, and setup (hook rollout integrity and required-hook installation). -- `cli/src/services/hooks/mod.rs` defines production local hook runtime parsing/dispatch (`HookSubcommand`, `run_hooks_subcommand`) for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `session-model`; `cli/src/services/hooks/command.rs` owns the hook runtime command handler. Current runtime behavior is commit-msg-only attribution behind the enabled-by-default attribution gate with explicit opt-out controls; `pre-commit` and `post-rewrite` are deterministic no-ops; `post-commit` requires validated `--remote-url`, threads that value through Agent Trace flow, prints it to stderr, and remains an active intersection + Agent Trace DB persistence path (captures current commit patch, combines/intersects recent `diff_traces`, persists intersection metadata to `post_commit_patch_intersections`, then persists built Agent Trace payload with range-level `content_hash` values to `agent_traces`); `diff-trace` performs STDIN JSON intake, required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id`, required nullable/non-empty `tool_version`, plus required `u64` `time` (Unix epoch milliseconds) validation, missing/nullable attribution fallback from `session_models` by `tool_name` + `session_id` while preserving direct payload precedence, non-lossy AgentTraceDb `time_ms` conversion, collision-safe parsed-payload `context/tmp/-000000-diff-trace.json` persistence, best-effort AgentTraceDb insertion whose failure is logged and reflected in success text, and fail-open conversion of intake failures to logged command success through `sce.hooks.diff_trace.error`; and `session-model` performs STDIN JSON intake for normalized model attribution upsert without raw artifact persistence, with Claude `SessionStart` parsing best-effort filling missing `tool_version` from `claude --version`. `cli/src/services/hooks/lifecycle.rs` implements `ServiceLifecycle` for hook health checks, fix, and setup (hook rollout integrity and required-hook installation). -- `conversation-trace` is also part of the production local hook runtime dispatch in `cli/src/services/hooks/mod.rs`; it persists valid mixed-batch/raw-Claude-event message and part payloads, keeps skipped-item and batch-insert warning behavior unchanged, and converts STDIN read, top-level parse/validation, unsupported raw Claude hook event, and AgentTraceDb setup/persistence failures into logged command success through `sce.hooks.conversation_trace.error`. +- `cli/src/services/hooks/mod.rs` defines production local hook runtime parsing/dispatch (`HookSubcommand`, `run_hooks_subcommand`) for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `conversation-trace`; `cli/src/services/hooks/command.rs` owns the hook runtime command handler. Current runtime behavior is commit-msg-only attribution behind the enabled-by-default attribution gate with explicit opt-out controls; `pre-commit` and `post-rewrite` are deterministic no-ops; `post-commit` requires validated `--remote-url`, threads that value through Agent Trace flow, prints it to stderr, and remains an active intersection + Agent Trace DB persistence path; `diff-trace` performs STDIN JSON intake, required-field validation, temporary missing/nullable attribution fallback from existing `session_models` rows, non-lossy AgentTraceDb `time_ms` conversion, and best-effort AgentTraceDb insertion without parsed-payload artifact writes. `session-model` is no longer a supported hooks route. `cli/src/services/hooks/lifecycle.rs` implements `ServiceLifecycle` for hook health checks, fix, and setup (hook rollout integrity and required-hook installation). - `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. - No `cli/src/services/sync.rs` module exists in the current codebase; `sce sync` command wiring is deferred, while local DB initialization and health ownership are split between setup and doctor. - `cli/src/services/default_paths.rs` defines the canonical per-user persisted-location seam for config/state/cache roots plus named default file paths for current persisted artifacts (`global config`, `auth tokens`, `local DB`, `agent trace DB`) used by config discovery, token storage, database adapters, and doctor diagnostics; its internal `roots` seam now owns the platform-aware root-directory resolution so non-test production modules consume shared path accessors instead of resolving owned roots directly. diff --git a/context/context-map.md b/context/context-map.md index 52c3722f..a9b69ad0 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -52,9 +52,7 @@ Feature/domain context: - `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-minimal-generator.md` (implemented a library minimal Agent Trace generator seam at `cli/src/services/agent_trace.rs`, used by the active post-commit hook flow to produce strict `0.1.0` JSON payloads with top-level `version`, UUIDv7 `id` derived from commit-time metadata, caller-provided commit-time `timestamp`, optional top-level `vcs` metadata emitted when present (`type` from enum `git|jj|hg|svn`, `revision` from metadata input; current post-commit flow provides `git`), optional top-level `tool` metadata (`name`/`version`) sourced from builder metadata inputs when overlapping AI content exists, and always-emitted `metadata.sce.version` sourced from the compiled `sce` CLI package version, plus per-file trace data from patch inputs via `intersect_patches(constructed_patch, post_commit_patch)` then `post_commit_patch`-anchored hunk classification into `ai`/`mixed`/`unknown` contributor categories, serialized per conversation with a required lookup `url` derived from top-level `AgentTrace.id`, nested `contributor.type` with optional `contributor.model_id` omitted when provenance is missing, one derived `ranges[{start_line,end_line,content_hash}]` entry per post-commit or embedded-patch hunk, and range `content_hash` values that hash touched-line kind/content independent of positions and metadata) -- `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime behavior: enabled-by-default commit-msg attribution with explicit opt-out controls, no-op `pre-commit`/`post-rewrite` entrypoints, active Agent Trace hook DB paths using no-migration readiness-gated AgentTraceDb access, active `post-commit` intersection entrypoint requiring validated `--remote-url`, threading that URL to the Agent Trace flow, printing it to stderr, capturing current commit patch, querying recent `diff_traces` from past 7 days, combining/intersecting patches via `patch::combine_patches` / `patch::intersect_patches`, persisting results to `post_commit_patch_intersections`, building/schema-validating post-commit Agent Trace payloads enriched with optional top-level `tool` metadata, `metadata.sce.version`, and range `content_hash`, and persisting validated payloads to AgentTraceDb `agent_traces` (DB-only), plus `diff-trace` STDIN intake with required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id`, required nullable/non-empty `tool_version`, required `u64` `time` validation, AgentTraceDb-only persistence with no `context/tmp` parsed-payload artifacts, `session-model` STDIN intake for normalized model attribution and raw Claude `SessionStart` events, and `conversation-trace` STDIN intake that classifies by `hook_event_name` — raw Claude `UserPromptSubmit` events (`transform_claude_user_prompt_submit`) and `Stop` events (`transform_claude_stop`) are transformed into normalized `message` + `message.part` items (user or assistant role, text part) and forwarded through the existing mixed-batch parser, mixed-batch `message.part` accepts `text`/`reasoning`/`patch`/`question` with patch JSON normalization and question JSON-shape validation before persistence, unsupported raw Claude hook events fail deterministically with diagnostics listing supported events, and payloads without `hook_event_name` follow the existing `{ payloads: [{ type, ... }] }` mixed-batch validation/persistence path) -- `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime behavior: enabled-by-default commit-msg attribution with explicit opt-out controls, no-op `pre-commit`/`post-rewrite` entrypoints, active Agent Trace hook DB paths using no-migration readiness-gated AgentTraceDb access, active `post-commit` intersection entrypoint requiring validated `--remote-url`, threading that URL to the Agent Trace flow, printing it to stderr, capturing current commit patch, querying recent `diff_traces` from past 7 days, combining/intersecting patches via `patch::combine_patches` / `patch::intersect_patches`, persisting results to `post_commit_patch_intersections`, building/schema-validating post-commit Agent Trace payloads enriched with optional top-level `tool` metadata, `metadata.sce.version`, and range `content_hash`, and persisting validated payloads to AgentTraceDb `agent_traces` (DB-only), plus `diff-trace` STDIN intake with required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id`, required nullable/non-empty `tool_version`, required `u64` `time` validation, dual persistence to AgentTraceDb, collision-safe `context/tmp/-000000-diff-trace.json` artifacts, `session-model` STDIN intake for normalized model attribution and raw Claude `SessionStart` events, and `conversation-trace` STDIN intake that classifies by `hook_event_name` — raw Claude `UserPromptSubmit` events (`transform_claude_user_prompt_submit`) and `Stop` events (`transform_claude_stop`) are transformed into normalized `message` + `message.part` items (user or assistant role, text part) and forwarded through the existing mixed-batch parser, mixed-batch `message.part` accepts `text`/`reasoning`/`patch`/`question` with patch JSON normalization and question JSON-shape validation before persistence, unsupported raw Claude hook events fail deterministically with diagnostics listing supported events, and payloads without `hook_event_name` follow the existing `{ payloads: [{ type, ... }] }` mixed-batch validation/persistence path) -- `context/sce/agent-trace-hooks-command-routing.md` also owns the current `diff-trace` and `conversation-trace` fail-open intake contracts: diff-trace STDIN read, parse/validation, attribution lookup, artifact persistence, and setup/persistence failures log `sce.hooks.diff_trace.error`; conversation-trace STDIN read, parse/validation, unsupported raw Claude hook event, and AgentTraceDb setup/persistence failures log `sce.hooks.conversation_trace.error`; both return hook success while valid-payload output and existing warning/skipped behavior remain unchanged. Its conversation-trace section is canonical for the current unsupported raw-Claude-event fail-open behavior. +- `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime behavior: enabled-by-default commit-msg attribution with explicit opt-out controls, no-op `pre-commit`/`post-rewrite` entrypoints, active post-commit Agent Trace DB intersection and payload persistence, DB-only `diff-trace` STDIN intake with required payload validation and temporary fallback reads from existing `session_models` rows when attribution is missing, `session-model` removed from the supported hook surface, and active `conversation-trace` intake for normalized and supported raw Claude message/part events; this document also owns the current `diff-trace` and `conversation-trace` fail-open intake contracts.) - `context/sce/automated-profile-contract.md` (deterministic gate policy for automated OpenCode profile, including 10 gate categories, permission mappings, automated `/commit` single-commit execution behavior, and automated profile constraints) - `context/sce/bash-tool-policy-enforcement-contract.md` (approved bash-tool blocking contract plus current Rust evaluator seam and OpenCode/Claude delegation references, including config schema, argv-prefix matching, shell/nix unwrapping, 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 including `sce-bash-policy` + `sce-agent-trace`, TypeScript source ownership, and Claude generated settings boundary including Agent Trace hooks plus `PreToolUse` Bash policy hook registration through the missing-CLI install-guidance helper) diff --git a/context/glossary.md b/context/glossary.md index 52055319..333ffb5f 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -121,8 +121,8 @@ - `auth config baked default`: Optional key-declared fallback in `cli/src/services/config/mod.rs` (with schema/parsing in `schema.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/mod.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 a unified remove-and-replace policy that removes existing targets before swapping staged content. - `setup remove-and-replace`: Replacement choreography in `cli/src/services/setup/mod.rs` where existing install targets are removed before staged content is promoted; on swap failure, the engine cleans temporary staging paths and returns deterministic recovery guidance (recover from version control). No backup artifacts are created. -- `hooks command routing contract`: Current hook command parser/dispatcher plus runtime wiring in `cli/src/services/hooks/mod.rs` (`HookSubcommand`, `run_hooks_subcommand`) that supports `pre-commit`, `commit-msg `, `post-commit`, `post-rewrite `, `diff-trace`, `conversation-trace`, and `session-model` with deterministic invocation validation/usage errors; `commit-msg` is the only active attribution path behind the attribution hooks gate AND the staged-diff AI-overlap preflight (trailer is appended only when `StagedDiffAiOverlapResult::Overlap` is returned; `NoOverlap` and `Error` suppress the trailer, with `Error` logged via `sce.hooks.commit_msg.ai_overlap_error`), `pre-commit`/`post-rewrite` are deterministic no-op entrypoints, `post-commit` requires validated `--remote-url`, threads that value through the Agent Trace flow, prints it to stderr, captures the current commit patch, queries recent `diff_traces` from the past 7 days, combines valid patches via `patch::combine_patches`, intersects with the post-commit patch via `patch::intersect_patches`, persists the intersection result to `post_commit_patch_intersections`, and persists built Agent Trace payloads to AgentTraceDb `agent_traces` (DB-only, no post-commit Agent Trace file artifact), `diff-trace` performs STDIN JSON intake with required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id`, required nullable/non-empty `tool_version`, missing/nullable attribution fallback from `session_models` by `tool_name` + `session_id` while direct payload values keep precedence, required `u64` `time` validation, non-lossy AgentTraceDb `time_ms` conversion, and AgentTraceDb insertion with nullable/resolved attribution without writing parsed-payload artifacts under `context/tmp`, and `session-model` performs STDIN JSON intake for normalized model attribution upsert without raw artifact persistence, with Claude `SessionStart` parsing best-effort filling missing `tool_version` from `claude --version`. -- `Claude raw hook capture (removed)`: Former hidden/internal `sce hooks claude-capture ` intake path removed in T05 of the `claude-typescript-model-cache-remove-rust-capture` plan. Rust now exposes only normalized `session-model` and `diff-trace` intakes for Claude/OpenCode editor runtimes. The removed route previously wrote pretty-printed JSON artifacts under `context/tmp/claude/` without AgentTraceDb writes. See `context/sce/claude-raw-hook-capture.md`. +- `hooks command routing contract`: Current hook command parser/dispatcher plus runtime wiring in `cli/src/services/hooks/mod.rs` (`HookSubcommand`, `run_hooks_subcommand`) supports `pre-commit`, `commit-msg `, `post-commit`, `post-rewrite `, `diff-trace`, and `conversation-trace` with deterministic invocation validation/usage errors; `session-model` is no longer supported. `commit-msg` is the only active attribution path behind the attribution hooks gate and staged-diff AI-overlap preflight; `pre-commit`/`post-rewrite` are no-ops; `post-commit` persists intersections and built Agent Trace payloads; `diff-trace` inserts AgentTraceDb rows with temporary fallback to existing `session_models` rows for missing/nullable attribution and without parsed `context/tmp` artifact writes; `conversation-trace` is the active message/part intake path. +- `Claude raw hook capture (removed)`: Former hidden/internal `sce hooks claude-capture ` intake path removed in T05 of the `claude-typescript-model-cache-remove-rust-capture` plan. Rust now exposes `diff-trace` and `conversation-trace` intakes for active Claude/OpenCode editor runtimes; `session-model` is also removed from the supported hook command surface. The removed route previously wrote pretty-printed JSON artifacts under `context/tmp/claude/` without AgentTraceDb writes. See `context/sce/claude-raw-hook-capture.md`. - `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. @@ -140,8 +140,8 @@ - `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/mod.rs` (`apply_commit_msg_coauthor_policy`) that applies exactly one canonical trailer (`Co-authored-by: SCE `) only when attribution hooks are enabled, SCE is not disabled, and the staged-diff AI-overlap preflight confirms AI/editor evidence (`StagedDiffAiOverlapResult::Overlap`); `NoOverlap` and `Error` both suppress the trailer, with `Error` logged via `sce.hooks.commit_msg.ai_overlap_error`; duplicate canonical trailers are deduped idempotently. - `local DB migration contract`: `cli/src/services/local_db/mod.rs` delegates migration execution to `TursoDb` through the `DbSpec::migrations()` contract. The current `LocalDbSpec` migration list is empty, so `LocalDb::new()` opens/creates the canonical local DB without creating local tables. -- `hook no-op baseline`: Current `cli/src/services/hooks/mod.rs` runtime posture where `pre-commit` and `post-rewrite` return deterministic no-op status text, `commit-msg` is a gated mutating path behind the enabled-by-default attribution-hooks control with explicit opt-out, `post-commit` requires validated `--remote-url`, threads that value through the Agent Trace flow, prints it to stderr, captures current commit patch, queries recent `diff_traces` from past 7 days, combines/intersects patches, persists to `post_commit_patch_intersections`, and persists built Agent Trace payloads to `agent_traces` without post-commit file artifacts, `diff-trace` is an active intake path (validates required STDIN payload fields including `sessionID`/`diff`/`tool_name`, optional `model_id`, required nullable/non-empty `tool_version`, fills missing/nullable attribution from `session_models` when available while preserving direct payload precedence, and inserts parsed payload fields into AgentTraceDb with nullable/resolved attribution without writing `context/tmp` artifacts), and `session-model` is an active intake path (validates required STDIN payload fields including `sessionID`/`model_id`/`tool_name`, best-effort fills missing Claude `tool_version` from `claude --version`, and upserts into `session_models` without raw artifacts). -- `sce doctor` operator-health contract: `cli/src/services/doctor/mod.rs` is the stable doctor entrypoint with focused `doctor/{inspect,render,fixes,types}.rs` submodules. For the full operator-health surface (modes, problem taxonomy, human text layout, JSON shape, fix-mode contracts, and checkout-DB integration), see `context/sce/agent-trace-hook-doctor.md` and `context/sce/doctor-human-text-contract.md`. +- `hook no-op baseline`: Current `cli/src/services/hooks/mod.rs` runtime posture where `pre-commit` and `post-rewrite` return deterministic no-op status text, `commit-msg` is a gated mutating path, `post-commit` persists intersections and built Agent Trace payloads without post-commit file artifacts, `diff-trace` validates STDIN payloads, temporarily fills missing/nullable attribution from existing `session_models` rows when available, and inserts AgentTraceDb rows without parsed `context/tmp` artifact writes, and `conversation-trace` is the active message/part intake path. `session-model` is no longer a supported hook route. +- `sce doctor` operator-health contract: `cli/src/services/doctor/mod.rs` is the stable doctor entrypoint, with focused `doctor/{inspect,render,fixes,types}.rs` submodules implementing 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 checkout/database records and fix-result records, the runtime validates state-root resolution, global and repo-local `sce/config.json` readability/schema health, local DB and checkout/global Agent Trace 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. Checkout DB discovery no longer lives in `doctor`; it moved to the `trace` group (`sce trace db list`). - `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 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 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. diff --git a/context/overview.md b/context/overview.md index 84cd4120..853756db 100644 --- a/context/overview.md +++ b/context/overview.md @@ -14,6 +14,58 @@ It also includes a Rust CLI (`sce`) for Shared Context Engineering workflows: au - **Attribution hooks:** enabled by default, gated by staged-diff AI-overlap preflight; `SCE_ATTRIBUTION_HOOKS_DISABLED` opt-out (see `context/sce/agent-trace-commit-msg-coauthor-policy.md`). - **Install channels:** repo-flake Nix, Cargo, npm, and source-built Flatpak (`dev.crocoder.sce`); Homebrew deferred (see `context/sce/cli-first-install-channels-contract.md`). +The CLI crate currently depends on `anyhow`, `chrono`, `clap`, `clap_complete`, `dirs`, `hmac`, `inquire`, `jsonschema`, `keyring-core`, `murmur3`, `owo-colors`, `rand`, `reqwest`, `serde`, `serde_json`, `sha2`, `tokio`, `tracing`, `turso`, and `uuid`, with target-specific keyring backend dependencies for Linux/FreeBSD, macOS, and Windows. No CLI dev-dependencies are currently declared. +Its command loop is implemented with `clap` derive-based argument parsing and `anyhow` error handling. Top-level help now displays an ASCII art "SCE" banner with a per-column right-to-left color gradient (cyan→magenta when color is enabled, plain ASCII when disabled) above a slim command list without implemented/placeholder labels, and hides `auth` and `hooks` from `sce`, `sce help`, and `sce --help`, while those commands remain directly invocable. The real top-level command catalog/help-visibility contract is now centralized in `cli/src/cli_schema.rs` and consumed by `cli/src/command_surface.rs` for custom banner/help rendering plus known-command classification. 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 and Agent Trace 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 missing canonical DB parent directories, implemented attribution-only `hooks` subcommand routing/validation entrypoints with commit-msg-only behavior behind an enabled-by-default gate with explicit opt-out controls, 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. Parse-time command conversion plus run-time command handling now flow through an internal `RuntimeCommand` seam in `cli/src/app.rs`, so top-level app orchestration no longer owns one monolithic dispatch `match` for every command. +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), stable lifecycle event IDs, stderr-only primary emission so stdout command payloads remain pipe-safe, and `observability::traits` boundaries for logger and telemetry behavior. +The app command dispatcher now enforces a centralized stdout/stderr stream contract in `cli/src/app.rs`: command success payloads are emitted on stdout only, while redacted user-facing diagnostics are emitted on stderr. `cli/src/app.rs` also now runs through explicit startup phases — dependency check, observability config resolution, runtime initialization, command parse/execute, and output rendering — with the app runtime carrying logger/telemetry plus static command-catalog state across those phases while preserving the existing exit-code and degraded-startup contracts. Within that lifecycle, `parse_command_phase` delegates clap-to-runtime conversion to `cli/src/services/parse/command_runtime.rs`, which returns a static `RuntimeCommand` enum, `services::app_support::execute_command_phase` logs around enum-owned `execute(...)` dispatch, and generic `RunOutcome` rendering logs classified errors through the logger trait boundary without coupling render support to the production logger type. Command payload structs for `help`, `version`, `completion`, `auth`, `config`, `setup`, `doctor`, and `hooks` live in service-owned `command.rs` files; `cli/src/services/command_registry.rs` owns the deterministic static command-name catalog and enum variants instead of boxed command trait objects. +The CLI now also enforces a shared output-format parser contract in `cli/src/services/output_format.rs`, with canonical `--format ` parsing and command-specific actionable invalid-value guidance reused by `config` and `version` services. A compile-safe service lifecycle seam also exists in `cli/src/services/lifecycle.rs`: `ServiceLifecycle` exposes default no-op `diagnose`, `fix`, and `setup` methods against the narrow `HasRepoRoot` accessor, uses lifecycle-owned health/fix/setup result types, and owns the shared static `LifecycleProvider` enum catalog/factory with deterministic config → local_db → auth_db → agent_trace_db → hooks ordering and no boxed provider aggregation. Hooks has a `services/hooks/lifecycle.rs` provider for hook rollout diagnosis/fix/setup, config has a `services/config/lifecycle.rs` provider for global/repo-local config validation plus repo-local config bootstrap, local_db has a `services/local_db/lifecycle.rs` provider for canonical local DB path health, parent-directory readiness/bootstrap, and `LocalDb::new()` setup, auth_db has a `services/auth_db/lifecycle.rs` provider for canonical auth DB path health, parent-directory readiness/bootstrap, and `AuthDb::new()` setup, and agent_trace_db has a `services/agent_trace_db/lifecycle.rs` provider for checkout identity setup, per-checkout Agent Trace DB path health/parent readiness when an ID exists, and legacy global fallback outside checkout context. Doctor runtime aggregates the full shared provider catalog for `diagnose` and `fix` and adapts lifecycle records into doctor-owned output records; setup command aggregates the shared provider catalog for `setup` with hooks included only when requested and adapts lifecycle setup outcomes before rendering setup-owned messages. +Agent Trace lifecycle setup now also initializes the per-checkout Agent Trace DB via `AgentTraceDb::open_at(path)`; hook runtime lazy initialization remains a fallback when setup has not prepared the DB or schema metadata is incomplete. +The CLI now also includes a shared text styling service in `cli/src/services/style.rs` that provides deterministic color enablement via `owo-colors`, automatic TTY detection, and `NO_COLOR` compliance for human-facing text output; stdout help/text surfaces, stderr diagnostics, and interactive prompt-adjacent text now reuse that shared styling policy while JSON, completion, and other non-interactive/machine-readable flows remain unstyled. The service exports color-detection, conditional styling, help/diagnostic/label/prompt styling, and `banner_with_gradient()` helpers for use across command surfaces while preserving pipe-safe output for non-interactive environments. +The `setup` command includes an `inquire`-backed target-selection flow: default interactive selection for OpenCode/Claude/both with required-hook installation in the same run, explicit non-interactive target flags (`--opencode`, `--claude`, `--both`), deterministic mutually-exclusive validation, and non-destructive cancellation exits. +The CLI now compiles an embedded setup asset manifest from `config/.opencode/**`, `config/.claude/**`, and `cli/assets/hooks/**` via `cli/build.rs`; `cli/src/services/setup/mod.rs` exposes deterministic normalized relative paths plus file bytes and target-scoped iteration without runtime reads from `config/`. The same build script also discovers `cli/migrations//*.sql` at compile time and writes `cli/src/generated_migrations.rs` constants sorted by numeric filename prefix for database migration consumers. +The setup service also provides repository-root install orchestration: it resolves the repository root, derives a repo-root-scoped `AppContext` from the runtime command context, aggregates `ServiceLifecycle::setup` calls across lifecycle providers (config → local_db → auth_db → agent_trace_db → hooks when requested), handles interactive or flag-based target selection for config asset installation, and reports deterministic completion details (selected target(s) and installed file counts). Setup uses a unified remove-and-replace policy for all write flows — it removes existing targets before swapping staged content and returns deterministic recovery guidance (recover from version control) on swap failure, without creating backup artifacts. The setup command gates all modes on an existing git repository before any writes. Internally, `cli/src/services/setup/mod.rs` now separates install-flow logic from interactive prompt logic through focused support seams. +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`, and nested `policies.bash`, `policies.attribution_hooks.enabled`, plus `policies.database_retry` with per-DB `connection_open`/`query` retry policy specs), 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`), 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/mod.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 flat logging keys through the shared resolver, so env values still override config-file values while config files provide deterministic fallback for file logging; `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`. +Invalid default-discovered config files now also degrade gracefully at startup: `sce` keeps running with degraded observability defaults, logs `sce.config.invalid_config` warnings, and reserves hard failures for explicit `--config` / `SCE_CONFIG_FILE` targets or other truly invalid runtime observability inputs. +`cli/src/services/config/mod.rs` is now a module facade that declares focused config submodules (`types`, `schema`, `policy`, `resolver`, private `render`, `command`, and `lifecycle`), re-exporting `pub use types::*` and `pub(crate) use schema::validate_config_file`. Shared config primitive ownership is delegated to `cli/src/services/config/types.rs`; schema loading and file parsing to `cli/src/services/config/schema.rs`; bash-policy semantic validation and policy-specific formatting to `cli/src/services/config/policy.rs`; runtime discovery/precedence to `cli/src/services/config/resolver.rs`; and `sce config show` / `sce config validate` text+JSON output construction to `cli/src/services/config/render.rs`. Downstream modules continue importing through `services::config` unchanged. The CLI now has a generic borrowed `AppContext` dependency view in `cli/src/app.rs`; `AppRuntime` owns concrete production logger/telemetry/fs/git dependencies, and command execution receives context views that borrow those dependencies plus an optional `repo_root: Option`. `AppContext::with_repo_root(...)` / `ContextWithRepoRoot` derives repo-root-scoped views while preserving the borrowed runtime dependencies, and command execution is generic over associated-type narrow accessor traits where practical. The broad capability seam lives in `cli/src/services/capabilities.rs`, where `FsOps`/`StdFsOps` wrap filesystem operations and `GitOps`/`ProcessGitOps` wrap git process execution plus repository-root/hooks-directory resolution. +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 through a dedicated internal `roots` seam, exposes the current persisted-artifact inventory (global config and auth tokens), and also defines named DB paths (auth DB, local DB, Agent Trace DB) plus the repo-relative, embedded-asset, install, 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 Rust CLI also centralizes SCE-owned web URI construction in `cli/src/services/agent_trace.rs`, with `SCE_WEB_BASE_URL` as the single Rust owner for `https://sce.crocoder.dev` and helpers consumed by Agent Trace conversation URLs, Agent Trace persisted trace URLs, Agent Trace session URLs, and setup-created repo-local config schema URLs. +The same config resolver now also owns the attribution-hooks gate used by local hook runtime: opt-out env `SCE_ATTRIBUTION_HOOKS_DISABLED` overrides `policies.attribution_hooks.enabled` with inverted semantics, and the gate defaults to enabled unless explicitly disabled. +The config service split now includes `cli/src/services/config/resolver.rs` as the focused owner for config-file discovery, file-layer merging, env/flag/default precedence, auth-key resolution, observability resolution, attribution-hooks resolution, and default-discovered invalid-file degradation; `cli/src/services/config/mod.rs` remains the facade/rendering orchestration surface while preserving existing `services::config` imports. +Generated config now includes repo-local plugin assets for both profiles: `sce-bash-policy.ts` plus `sce-agent-trace.ts` are emitted under `config/.opencode/plugins/` and `config/automated/.opencode/plugins/`; the OpenCode agent-trace plugin extracts `{ sessionID, diff, time, model_id }` from user `message.updated` events with diffs, tracks per-session OpenCode client version from `session.created`/`session.updated`, and sends payloads to `sce hooks diff-trace` with `tool_name="opencode"` plus optional `tool_version`. Claude generated config now routes supported `PostToolUse Write|Edit|MultiEdit|NotebookEdit` events directly to `sce hooks diff-trace`; it no longer registers a `SessionStart` hook or calls `sce hooks session-model`. Rust handles extraction, validation, and AgentTraceDb persistence without a TypeScript intermediary, without parsed `context/tmp` diff-trace artifacts, and with temporary missing/nullable attribution fallback reads from existing `session_models` rows until that fallback is removed. Bash-policy delegates OpenCode and Claude enforcement to the Rust `sce policy bash` command. +Claude bash-policy enforcement is also generated through `.claude/settings.json` as a `PreToolUse` `Bash` command hook running `sce policy bash`, so Claude and OpenCode both delegate to the Rust policy evaluator without a Claude TypeScript runtime. +Local database bootstrap is now owned by `LocalDbLifecycle::setup` and `AgentTraceDbLifecycle::setup` aggregated by the setup command. Agent Trace setup creates/reuses the current checkout ID and initializes the per-checkout `agent-trace-{checkout_id}.db` with embedded migrations; hook runtime still lazily creates or upgrades the per-checkout DB when setup has not run or schema metadata is incomplete. Doctor validates checkout/global DB paths/health and can bootstrap missing parent directories; checkout DB discovery now lives in the `trace` group (`sce trace db list`). Wiring a user-invocable `sce sync` command is deferred to `0.4.0`. +The repository-root flake (`flake.nix`) now applies a Rust overlay-backed stable toolchain pinned to `1.95.0` (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. +The CLI Cargo package metadata now includes crates.io publication-ready fields with crate-local install guidance in `cli/README.md`; supported Cargo install paths are `cargo install shared-context-engineering --locked`, `cargo install --git https://github.com/crocoder-dev/shared-context-engineering shared-context-engineering --locked`, and local `cargo install --path cli --locked`. The published crate installs the `sce` binary. The crate also keeps `cargo clippy --manifest-path cli/Cargo.toml` warnings-denied through `cli/Cargo.toml` lint configuration, so an extra `-- -D warnings` flag is redundant. +The repository-root flake is now the single Nix entrypoint for both repo tooling and CLI packaging/checks, so root-level `nix flake check` evaluates the Crane-backed CLI checks (`cli-tests`, `cli-clippy`, `cli-fmt`), the dedicated `integrations/install` runner checks (`integrations-install-tests`, `integrations-install-clippy`, `integrations-install-fmt`), Linux-only `flatpak-static-validation`, `workflow-actionlint` (runs `actionlint` on all `.github/workflows/*.yml`), plus six split JavaScript check derivations: `npm-bun-tests`, `npm-biome-check`, `npm-biome-format`, `config-lib-bun-tests`, `config-lib-biome-check`, and `config-lib-biome-format`, without nested-flake indirection. The config-lib checks now consume `config/lib/` as the shared Bun/TypeScript package root for both `agent-trace-plugin/` and `bash-policy-plugin`, with dependencies resolved from `config/lib/package.json` and `config/lib/bun.lock`. For Cargo packaging/builds, the crate now compiles against a temporary `cli/assets/generated/` mirror prepared from canonical `config/` outputs during Nix builds and crates.io publish runs rather than from a committed crate-local snapshot. +Config-lib JS flake checks execute from `config/lib/`, but the copied Nix check source is repo-shaped when tests require shared repo fixtures; the current Claude agent-trace golden tests are fully Rust-owned in `cli/src/services/structured_patch/fixtures` (Claude TypeScript plugin test removed in T07). +Local developer Nix tuning guidance now lives in `AGENTS.md`, including optional user-level `~/.config/nix/nix.conf` recommendations for `max-jobs` and `cores` plus an explicit system-level-only note for `auto-optimise-store`. +The Pkl authoring layer owns generated OpenCode plugin registration for SCE-managed plugins: `config/pkl/base/opencode.pkl` defines the canonical plugin entries, `config/pkl/renderers/common.pkl` re-exports the shared plugin list for renderer use, and generated `config/.opencode/opencode.json` plus `config/automated/.opencode/opencode.json` register `./plugins/sce-bash-policy.ts` and `./plugins/sce-agent-trace.ts` through OpenCode's `plugin` field. Claude does not use an OpenCode-style plugin manifest; Claude bash-policy enforcement is registered through generated `.claude/settings.json` as a `PreToolUse` `Bash` command hook routed through `.claude/hooks/run-sce-or-show-install-guidance.sh` before running `sce policy bash`. +The current CLI install/distribution contract for `sce` includes repo-flake Nix, Cargo, npm, and source-built Flatpak (`dev.crocoder.sce`) as supported channels, while `Homebrew` remains deferred from the current implementation stage. Nix-managed build/release entrypoints are the source of truth for existing binary rollout surfaces, npm consumes Nix-produced release artifacts, and repo-root `.version` is the canonical checked-in release version source that release packaging and downstream Cargo/npm publication must match. Flatpak is the approved source-built exception to binary artifact reuse: its package must build the Rust CLI from source inside Flatpak, use a Flathub-style release-source manifest plus a Nix-generated local checkout override for local builds, prepare generated CLI assets from checked-in `config/` inputs, and provide Git access through a `/app/bin/git` wrapper delegating to `flatpak-spawn --host git` with the required `org.freedesktop.Flatpak` permission. The active Flatpak release contract approves GitHub Release source-manifest assets (manifest tarball, checksum, and JSON metadata) and source-built `.flatpak` bundle assets (`sce-v-x86_64.flatpak` / `sce-v-aarch64.flatpak` plus `.sha256` / `.json`), with `.github/workflows/release-sce.yml` building/uploading those assets alongside CLI/npm assets, while still excluding automatic Flathub submission, prebuilt (non-source-built) Flatpak binaries/bundles, OSTree repositories, and release-version bumping. The shared release artifact foundation is now implemented through root-flake apps `release-artifacts` and `release-manifest`, which emit canonical `sce-v-.tar.gz` archives, SHA-256 checksum files, merged manifest outputs, and a detached `sce-v-release-manifest.json.sig` produced from a non-repo private signing key; the npm distribution surface is now implemented as a checked-in `npm/` launcher package plus root-flake `release-npm-package`, which packs `sce-v-npm.tgz`, refuses mismatched checked-in package metadata, and installs the native CLI by downloading the release manifest plus detached signature, verifying the manifest with the bundled npm public key, and only then checksum-verifying the matching GitHub release archive at npm `postinstall` time. GitHub Releases remain the canonical publication surface for binary release artifacts and approved Flatpak source-manifest package assets, while crates.io and npm registry publication are separate non-bumping publish stages under the approved release topology. GitHub CLI release automation now lives in dedicated `release-sce*.yml` workflows split by Linux, Linux ARM, and macOS ARM, and `.github/workflows/release-sce.yml` now orchestrates those three reusable platform lanes before assembling the signed release manifest, npm tarball, and GitHub release payload. The orchestrator tags/releases the checked-in `.version` directly and rejects version mismatches instead of generating a new semver during workflow execution; `.github/workflows/publish-crates.yml` and `.github/workflows/publish-npm.yml` own registry publication after release assets exist. +The Linux root flake now also exposes `nix run .#release-flatpak-package -- --version --out-dir `, delegating to `packaging/flatpak/sce-flatpak.sh release-package` to emit deterministic Flatpak source-manifest tarball/checksum/JSON release assets from checked-in packaging source while running the Nix-built version-parity validator script across `.version`, `cli/Cargo.toml`, `npm/package.json`, and AppStream release metadata; `.github/workflows/release-sce.yml` runs that app into `dist/flatpak` and uploads `*.tar.gz`, `*.sha256`, and `*.json` Flatpak assets to the GitHub Release. Linux root flake also exposes `nix run .#release-flatpak-bundle -- --version --arch --out-dir `, delegating to `sce-flatpak.sh release-bundle` to build a source-built `.flatpak` bundle from the checkout using imperative `flatpak-builder` + `flatpak build-bundle` (network + bubblewrap, kept out of pure Nix), emitting per-architecture `.flatpak`/`.sha256`/`.json` files; `.github/workflows/release-sce-linux.yml` and `.github/workflows/release-sce-linux-arm.yml` build and upload x86_64/aarch64 bundles respectively, assembled by `.github/workflows/release-sce.yml`. +The checked-in Flatpak packaging surface lives under `packaging/flatpak/` with Nix-owned generation: `dev.crocoder.sce.yml` is rendered from a Nix expression (`nix/flatpak/manifest.nix`) via the standard nixpkgs YAML formatter (`pkgs.formats.yaml.generate`) and regenerated by `nix run .#regenerate-flatpak-manifest`; `cargo-sources.json` is generated from `cli/Cargo.lock` by a Nix derivation wrapping `flatpak-builder-tools`/`flatpak-cargo-generator.py` and regenerated by `nix run .#regenerate-cargo-sources`; both are guarded by `flatpak-manifest-parity` and `cargo-sources-parity` flake checks. Static manifest validation is Bash-owned (`nix/flatpak/static-validate.sh`), and release-version parity validation is Bash-owned (`nix/flatpak/version-parity.sh`). AppStream metadata and the host-git wrapper source remain checked in, and `sce-flatpak.sh` is a thin imperative orchestrator (no manifest text rewriting, no embedded Python) around `flatpak-builder` and `flatpak build-bundle`, consumed by the reduced flake app surface and by Flatpak source-manifest release packaging. +The current supported automated release target matrix is `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`, and `aarch64-apple-darwin`; npm launcher platform support remains a separate current-state surface documented in the npm distribution contract and launcher code. +The downstream publish-stage implementation is now complete for both registries: `.github/workflows/publish-crates.yml` publishes the checked-in crate version after `.version`/tag/Cargo parity checks, and `.github/workflows/publish-npm.yml` publishes the checked-in npm package after `.version`/tag/npm parity checks plus verification of the canonical `sce-v-npm.tgz` GitHub release asset. +The repository root now also owns the canonical Biome contract for the current JavaScript tooling slice: `biome.json` scopes formatting/linting to `npm/` and the shared `config/lib/` plugin package root while excluding package-local `node_modules/`, and the root Nix dev shell provides the `biome` binary so contributors do not need a host-installed formatter/linter for those areas. +The root flake now also exposes an explicit opt-in install-channel integration-test app, `nix run .#install-channel-integration-tests -- --channel `, which remains outside the default `nix flake check` path while the Rust runner now executes real npm, Bun, and Cargo install-and-verify flows for the existing binary install channels. Flatpak validation/build orchestration is separate and reduced to a minimal app surface: Linux flake apps expose the umbrella `sce-flatpak` (`nix run .#sce-flatpak -- ` for `validate`, `prepare-local-manifest`, etc.) plus `release-flatpak-package`, `release-flatpak-bundle`, and the `regenerate-flatpak-manifest` / `regenerate-cargo-sources` helpers; the previously separate `flatpak-validate`, `flatpak-local-manifest`, and `flatpak-build` wrapper apps are removed. Default `nix flake check` keeps the lightweight Nix-built static/AppStream validator plus the parity checks (`flatpak-manifest-parity`, `cargo-sources-parity`) and does not run a network-heavy Flatpak build. +Shared Context Plan and Shared Context Code remain separate agent roles by design; planning (`/change-to-plan`) and implementation (`/next-task`) stay split while shared baseline guidance is deduplicated via canonical skill-owned contracts. +Their shared baseline doctrine (core principles, `context/` authority, and quality posture) is defined once as canonical snippets in `config/pkl/base/shared-content-common.pkl` and composed into both agent bodies during generation; the aggregation surfaces `config/pkl/base/shared-content.pkl` (manual) and `config/pkl/base/shared-content-automated.pkl` (automated) import from grouped `plan`, `code`, and `commit` modules for downstream renderers. +The `/next-task` command body is intentionally thin orchestration: readiness gating + phase sequencing are command-owned, while detailed implementation/context-sync contracts are skill-owned (`sce-plan-review`, `sce-task-execution`, `sce-context-sync`). The generated OpenCode command doc now also emits machine-readable frontmatter for this chain via `entry-skill: sce-plan-review` and an ordered `skills` list. +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. `/commit` is now split by profile: manual generated commands remain proposal-only and allow split guidance when staged changes mix unrelated goals, while the automated OpenCode `/commit` command generates exactly one commit message and runs `git commit` against the staged diff. The shared `sce-atomic-commit` contract also requires commit bodies to cite affected plan slug(s) and updated task ID(s) when staged changes include `context/plans/*.md`, and to stop for clarification instead of inventing those references when the staged plan diff is ambiguous. +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 the removed Agent Trace schema adaptation, payload building, retry replay, or rewrite handling paths 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, `SCE_DISABLED` is false, and the staged-diff AI-overlap preflight confirms AI/editor evidence (`StagedDiffAiOverlapResult::Overlap`); when the preflight returns `NoOverlap` or `Error` (including DB open failure, schema not ready, query error, staged diff read failure, or zero overlap), the trailer is not appended and errors are logged via `sce.hooks.commit_msg.ai_overlap_error`; `pre-commit` and `post-rewrite` remain deterministic no-op entrypoints; `post-commit` requires validated `--remote-url`, threads that URL through the Agent Trace flow, prints it to stderr, captures current commit patch, queries recent `diff_traces` from past 7 days (dispatching `patch` rows through existing unified-diff parsing and `structured` rows through `structured_patch::derive_claude_structured_patch` at read time), combines/intersects patches, persists intersection metadata to `post_commit_patch_intersections`, and persists the schema-validated built Agent Trace payload, including optional top-level `tool` metadata from recent diff-trace rows, top-level `metadata.sce.version` from the compiled `sce` CLI package version, and range-level `content_hash` values, to AgentTraceDb `agent_traces` (DB-only, no post-commit Agent Trace file artifact); `diff-trace` currently validates/persists required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent or `null` → `None`, present+non-empty → `Some`, present+empty → error), required nullable/non-empty `tool_version`, plus required `u64` millisecond `time`, resolves missing/nullable attribution from `session_models` by `tool_name` + `session_id` when available while direct payload values keep precedence, and continues with `None` for unresolved attribution, with non-lossy AgentTraceDb `time_ms` conversion and AgentTraceDb-only persistence without parsed-payload artifacts under `context/tmp`; and `session-model` performs STDIN intake for normalized model attribution upsert without raw artifact persistence, with Claude `SessionStart` extracting `model_id` from the raw event and best-effort filling missing `tool_version` from `claude --version`. +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 provides `LocalDb` as a thin `TursoDb` alias in `cli/src/services/local_db/mod.rs`; `LocalDbSpec` resolves the canonical local DB path from the shared default-path catalog and currently declares zero migrations. Shared Turso infrastructure lives in `cli/src/services/db/mod.rs`, where `DbSpec` and generic `TursoDb` support dual-mode operation — local mode via `turso::Builder::new_local()` when `SCE_SYNC_URL`+`SCE_SYNC_TOKEN` are absent, or sync (Turso Cloud) mode via `turso::sync::Builder::new_remote()` when both are set. It owns parent-directory creation, connection setup, tokio current-thread runtime bridging, synchronous `execute`/`query`/`query_map`, generic migration execution, sync operations (`push`/`pull`/`checkpoint`/`stats`) that are no-ops in local mode (sync is never triggered automatically from `execute()`), and shared DB lifecycle helpers for service-specific database wrappers. Auth DB persistence now has a thin encrypted wrapper in `cli/src/services/auth_db/mod.rs`: `AuthDb = EncryptedTursoDb` resolves `/sce/auth.db` and embeds ordered `auth_tokens` table/index migrations, with lifecycle registration wired through `AuthDbLifecycle` in `cli/src/services/auth_db/lifecycle.rs`; auth runtime token-storage is now wired through `token_storage.rs`, which persists tokens via the `auth_credentials` table instead of a JSON file. Agent Trace persistence now has its own `cli/src/services/agent_trace_db/mod.rs` wrapper, legacy global `/sce/agent-trace.db` fallback plus active per-checkout `/sce/agent-trace-{checkout_id}.db` hook runtime paths, a split fresh-start baseline migration set (`001..008`) covering `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, nullable `agent_traces.remote_url`, indexes (`idx_diff_traces_time_ms_id`, `idx_agent_traces_agent_trace_id`, `idx_agent_traces_remote_url`), and `session_models` keyed by `(tool_name, session_id)` without `AUTOINCREMENT`, plus `agent_traces.agent_trace_id` as `NOT NULL UNIQUE`; it also provides type... (line truncated to 2000 chars) +The hooks command surface now also supports concrete runtime subcommand routing (`pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `conversation-trace`) with deterministic argument/STDIN validation; `session-model` is no longer supported. Current runtime behavior keeps commit-msg attribution enabled by default unless explicitly opted out; `pre-commit`/`post-rewrite` remain deterministic no-ops; `post-commit` requires validated `--remote-url`, threads that URL into the Agent Trace flow, prints it to stderr, and remains the active bounded recent-diff-trace intersection path; `diff-trace` is the active intake path for parsed STDIN `{ sessionID, diff, time, model_id?, tool_name, tool_version }` payload persistence with temporary missing/nullable attribution fallback from existing `session_models` rows and without parsed-payload artifacts under `context/tmp`; and `conversation-trace` is the active message/part intake path. This behavior is documented in `context/sce/agent-trace-hooks-command-routing.md`. The removed `sce hooks claude-capture` raw capture route is documented in `context/sce/claude-raw-hook-capture.md` as a removed feature. +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`), and uses a unified remove-and-replace policy that removes existing hooks before swapping staged content with deterministic 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`); this behavior is documented in `context/sce/setup-githooks-cli-ux.md`. + ## Repository model - Author once in canonical Pkl content organized by concern: `config/pkl/base/shared-content-{common,plan,code,commit}.pkl` for manual profile and `config/pkl/base/shared-content-automated-{common,plan,code,commit}.pkl` for automated profile; aggregation surfaces `config/pkl/base/shared-content.pkl` and `config/pkl/base/shared-content-automated.pkl` import from these grouped modules for downstream renderers. @@ -42,3 +94,42 @@ Lightweight post-task verification baseline (required after each completed task) - For the full domain file index and discoverability: `context/context-map.md` - For active plan execution state: `context/plans/` - For architecture decisions: `context/decisions/` + +- No dedicated generated-parity workflow is currently checked in; local/generated-output parity is enforced through `nix run .#pkl-check-generated` and the root `nix flake check` `pkl-parity` derivation. GitHub Actions workflow lint is enforced through the root `nix flake check` `workflow-actionlint` derivation (all `.github/workflows/*.yml`). +- PR validation runs through `.github/workflows/pr-ci.yml` on `pull_request`, `push` to `main`, and `workflow_dispatch`, executing on an `ubuntu-latest` + `macos-latest` matrix (`fail-fast: false`); a workflow-level concurrency group (`${{ github.workflow }}-${{ github.ref }}`, `cancel-in-progress: true`) cancels superseded runs on the same ref, and each matrix job has a `timeout-minutes: 90` guard. It installs Nix via `DeterminateSystems/nix-installer-action@v22`, enables `DeterminateSystems/magic-nix-cache-action@v14` immediately after, then runs `nix flake check --print-build-logs`, `nix build .#default --print-build-logs`, and `nix run .#sce -- --help` / `nix run .#sce -- version`. Job name renders as `Nix CI (ubuntu-latest)` / `Nix CI (macos-latest)` for use as required branch protection checks. The Nix store cache is the only cache layer; no separate Cargo/Bun/npm/`node_modules` caches. + +## Cross-target parity + +- OpenCode and Claude are generated from the same canonical content with per-target capability mapping. +- When capabilities differ, parity is implemented by supported target-specific behavior rather than forcing unsupported fields. + +## Context navigation + +- Use `context/architecture.md` for component boundaries and current-state contracts. +- 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`, 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. +- 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, the profile-specific proposal contract (manual allows split guidance; automated enforces single-message), and staged-plan commit-body citation rules. +- 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 commit-msg canonical co-author trailer policy with staged-diff AI-overlap evidence gate, opt-out default, and idempotent dedupe behavior. +- Use `context/sce/agent-trace-post-commit-dual-write.md` only as historical reference for the retired post-commit no-op/dual-write slice; current post-commit behavior is documented in `context/sce/agent-trace-hooks-command-routing.md`. +- 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 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-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/claude-raw-hook-capture.md` (removed feature) for the former hidden/internal Claude raw hook JSON capture intake. The `sce hooks claude-capture` CLI route, `ClaudeCaptureEvent`, `claude_transcript.rs`, and `RepoPaths::claude_capture_tmp_dir()` were removed in T05. Rust now exposes `diff-trace` and `conversation-trace` intakes for active Agent Trace hook data; `session-model` is also removed from the supported hook command surface. +- 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. +- Use `context/sce/setup-githooks-install-flow.md` for the implemented `sce-setup-githooks-any-repo` T03 required-hook install orchestration contract (git-truth hooks-path resolution, per-hook installed/updated/skipped outcomes, and remove-and-replace behavior). +- Use `context/sce/setup-githooks-cli-ux.md` for the implemented `sce-setup-githooks-any-repo` T04 setup command-surface contract (`--hooks`, optional `--repo`), compatibility validation rules, and deterministic hook setup messaging. +- Use `context/sce/automated-profile-contract.md` for the automated OpenCode profile deterministic gate policy (10 gate categories, permission mappings, and automated profile constraints for non-interactive SCE workflows). diff --git a/context/patterns.md b/context/patterns.md index 78371692..214c4bea 100644 --- a/context/patterns.md +++ b/context/patterns.md @@ -133,7 +133,7 @@ - For cross-service CLI dependencies exposed through the borrowed `AppContext` view, prefer shared capability/accessor traits over one-off per-service abstractions; keep production wrappers thin over `std::fs` and `git` process execution until call-site migration tasks approve deeper service refactors, and keep command execution generic over the narrow accessors each command needs where practical. - 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 the current local-hook baseline, keep `pre-commit` and `post-rewrite` as deterministic no-op entrypoints; keep `post-commit` as the active bounded recent-diff-trace intersection entrypoint with validated `--remote-url` plumbed through Agent Trace flow and any direct diagnostics printed to stderr; keep `diff-trace` as an explicit STDIN intake path with deterministic required-field validation for `sessionID`, `diff`, `time`, `tool_name`, optional `model_id` (absent/`null` → `None`, resolved from `session_models` by `tool_name` + `session_id` when absent), and `tool_version` (present and either `null` or non-empty string), non-lossy AgentTraceDb `time_ms` conversion, and AgentTraceDb insertion whose failure is logged and reflected in deterministic success text without creating a `context/tmp` artifact fallback; keep `session-model` as an explicit STDIN intake path for normalized model attribution upsert with no raw artifact persistence. +- For the current local-hook baseline, keep `pre-commit` and `post-rewrite` as deterministic no-op entrypoints; keep `post-commit` as the active bounded recent-diff-trace intersection entrypoint with validated `--remote-url` plumbed through Agent Trace flow and any direct diagnostics printed to stderr; keep `diff-trace` as an explicit STDIN intake path with deterministic required-field validation for `sessionID`, `diff`, `time`, `tool_name`, optional `model_id` (absent/`null` → `None`, resolved from existing `session_models` rows by `tool_name` + `session_id` when absent until the fallback-removal task lands), and `tool_version` (present and either `null` or non-empty string), non-lossy AgentTraceDb `time_ms` conversion, and AgentTraceDb insertion whose failure is logged and reflected in deterministic success text without creating a `context/tmp` artifact fallback; keep `conversation-trace` as the active message/part intake path. `session-model` is no longer a supported hook intake path. - For diff-trace attribution persistence, preserve direct payload `model_id` and `tool_version` values, query `session_models` only when either attribution field is missing/nullable, fill missing fields from the stored row when available, and persist unresolved attribution as `NULL` in AgentTraceDb rather than skipping DB insertion. - For the current local-hook baseline, keep `pre-commit` and `post-rewrite` as deterministic no-op entrypoints; keep `post-commit` as the active bounded recent-diff-trace intersection entrypoint with validated `--remote-url` plumbed through Agent Trace flow and any direct diagnostics printed to stderr; keep `diff-trace` as an explicit STDIN intake path with deterministic required-field validation for `sessionID`, `diff`, `time`, `tool_name`, optional `model_id` (absent/`null` → `None`, resolved from `session_models` by `tool_name` + `session_id` when absent), and `tool_version` (present and either `null` or non-empty string), non-lossy AgentTraceDb `time_ms` conversion, collision-safe `context/tmp/-000000-diff-trace.json` persistence using atomic create-new retry semantics, and valid-path AgentTraceDb insertion whose post-artifact DB failure is logged and reflected in success text while preserving the artifact fallback; keep `conversation-trace` as an explicit STDIN intake path for mixed-batch/raw-Claude message and part payloads with no raw artifact persistence; keep `session-model` as an explicit STDIN intake path for normalized model attribution upsert with no raw artifact persistence. - For `diff-trace` hook intake, keep producer-facing failure behavior fail-open: STDIN read, parse/validation, attribution lookup, artifact persistence, and setup/persistence failures are logged with `sce.hooks.diff_trace.error` and converted into command success; preserve the existing valid-payload success text and the post-artifact AgentTraceDb write-warning success path. diff --git a/context/plans/remove-session-models-direct-claude-model-id.md b/context/plans/remove-session-models-direct-claude-model-id.md new file mode 100644 index 00000000..7763a7df --- /dev/null +++ b/context/plans/remove-session-models-direct-claude-model-id.md @@ -0,0 +1,87 @@ +# Plan: Remove `session_models` and write Claude model IDs directly to `diff_traces` + +## Change summary + +Remove the Agent Trace `session_models` fallback model-attribution path and make `diff_traces.model_id` the only primary persisted model-attribution source for diff traces. For Claude Code, attempt to extract `model_id` directly from each supported `PostToolUse` diff-trace payload and store it in `diff_traces.model_id` through `DiffTracePayload`; if Claude omits model metadata, the value remains `NULL` and generated Agent Trace JSON omits `contributor.model_id` per the current schema/Serde contract. + +This intentionally accepts the known Claude limitation that some events, especially after `/clear`, may not include model metadata. The change favors a simpler data model over session-level fallback enrichment. + +## Success criteria + +- `session_models` is no longer part of the Agent Trace DB schema for fresh databases. +- Rust Agent Trace DB code no longer exposes `SessionModelUpsert`, `SessionModelAttribution`, session-model SQL constants, upsert helpers, or lookup helpers. +- `sce hooks session-model` is removed from CLI parsing, runtime dispatch, generated Claude settings, and generated OpenCode plugin/runtime integration if present. +- `sce hooks diff-trace` no longer looks up missing attribution from `session_models`. +- Claude structured diff-trace parsing attempts to set `DiffTracePayload.model_id` from the raw `PostToolUse` payload using the existing Claude model extraction/normalization rules. +- `diff_traces.model_id` remains nullable and receives only the direct payload-derived model attribution. +- Agent Trace JSON remains valid when `model_id` is absent because `Contributor.model_id: Option` is omitted and Trace Record Schema only requires contributor `type`. + +## Constraints and non-goals + +- Do not invent or persist placeholder model IDs such as `unknown`; absent Claude model metadata remains `NULL`/omitted. +- Do not add a replacement session-level attribution table in this plan. +- Do not change the Agent Trace record schema to require `model_id`. +- Do not change contributor classification semantics (`ai`, `mixed`, `unknown`). +- Preserve OpenCode normalized diff-trace behavior except for removing any now-unused session-model producer path. +- Existing databases may already have `session_models`; the implementation task must choose a safe migration strategy, but runtime code should stop depending on that table. + +## Task stack + +- [x] T01: `Remove session-model command surface and generated producers` (status:done) + - Task ID: T01 + - Goal: Stop producing and routing `sce hooks session-model` events. + - Boundaries (in/out of scope): + - In — Remove `SessionModel` hook subcommand variants/parsing/help references; remove generated Claude `SessionStart -> sce hooks session-model` hook registration; remove OpenCode session-model producer wiring if present; update generated config from canonical Pkl/source as required. + - Out — Agent Trace DB schema/code removal; diff-trace attribution behavior changes; context documentation updates beyond any inline generated output comments required by code. + - Done when: `sce hooks session-model` is no longer a supported runtime route; generated Claude/OpenCode assets no longer call it; generated-output parity can be restored after regeneration. + - Verification notes (commands or checks): `nix run .#pkl-check-generated`; targeted parser/help tests if existing; generated config diff inspection for removed session-model hook commands. + - Completion evidence (2026-06-30 rerun): Removed the `session-model` hooks subcommand from clap parsing/runtime conversion and `HookSubcommand` dispatch, deleted the session-model hook runtime/parser from `hooks/mod.rs`, and removed generated Claude `SessionStart -> sce hooks session-model` registration from canonical Pkl plus regenerated `config/.claude/settings.json`. Left Agent Trace DB `session_models` API/schema and diff-trace fallback in place for later tasks; temporarily marked now-unused session-model DB upsert APIs with `#[allow(dead_code)]` to preserve T02 scope. `nix run .#pkl-check-generated` passed (`Generated outputs are up to date.`). `nix flake check` passed (`all checks passed`). + +- [ ] T02: `Remove session_models database API and schema dependency` (status:todo) + - Task ID: T02 + - Goal: Remove Rust DB adapter support for `session_models` and ensure fresh Agent Trace DB schema no longer creates or requires that table. + - Boundaries (in/out of scope): + - In — Remove session-model SQL constants, structs, upsert/lookup methods, row mappers, and tests; remove or supersede migration `015_create_session_models.sql` from the fresh schema path using the repository's migration policy; update migration readiness expectations accordingly. + - Out — Diff-trace parser behavior; generated hook config; unrelated message/part tables. + - Done when: `agent_trace_db` compiles without any session-model API; fresh Agent Trace DB creation no longer includes `session_models`; migration/readiness tests reflect the new schema contract. + - Verification notes (commands or checks): targeted Agent Trace DB tests if available; `nix develop -c sh -c 'cd cli && cargo test agent_trace_db'` if a narrow module test is practical; otherwise `nix flake check` in final validation. + +- [ ] T03: `Write direct Claude model_id into DiffTracePayload` (status:todo) + - Task ID: T03 + - Goal: Populate `DiffTracePayload.model_id` directly from supported Claude `PostToolUse` payloads when model metadata is present. + - Boundaries (in/out of scope): + - In — Reuse or refactor existing Claude model extraction/normalization logic (`model`, `model_id`, `modelId`, nested model identifiers, `claude/` prefix normalization) for diff-trace parsing; keep `model_id` optional; persist the direct value into `diff_traces.model_id` through the existing `DiffTraceInsert` path. + - Out — Session-level fallback lookup; placeholder model values; Agent Trace schema changes. + - Done when: Claude structured `DiffTracePayload` carries `Some(model_id)` when the raw `PostToolUse` payload includes model metadata and `None` when it does not; persisted `diff_traces.model_id` mirrors that direct value; tests cover present and omitted Claude model metadata. + - Verification notes (commands or checks): targeted hooks tests for Claude diff-trace payload parsing/persistence; inspect `parse_claude_diff_trace_payload` no longer hardcodes `model_id: None` when payload has extractable model info. + +- [ ] T04: `Remove diff-trace session fallback and repair tests` (status:todo) + - Task ID: T04 + - Goal: Make `diff_traces` the sole primary model attribution source by removing fallback resolution from `session_models`. + - Boundaries (in/out of scope): + - In — Delete `ResolvedDiffTraceAttribution`, `resolve_diff_trace_attribution`, and DB lookup closure code if no longer needed; pass `payload.model_id`/`payload.tool_version` directly to DB insert; update tests that previously expected session fallback. + - Out — Changing artifact persistence to `context/tmp`; changing OpenCode required/optional model validation except where compile cleanup requires it. + - Done when: `sce hooks diff-trace` never queries `session_models`; direct payload model/tool metadata is persisted as-is; missing model metadata remains nullable and non-failing. + - Verification notes (commands or checks): targeted hooks tests for direct model persistence and missing-model persistence; grep for `session_model`/`session_models` in Rust hook and DB code should only find historical context/docs before context sync. + +- [ ] T05: `Sync Agent Trace context and docs` (status:todo) + - Task ID: T05 + - Goal: Update durable context/docs to describe the simplified direct `diff_traces.model_id` attribution model. + - Boundaries (in/out of scope): + - In — Update `context/sce/agent-trace-db.md`, `context/sce/agent-trace-hooks-command-routing.md`, `context/context-map.md`, and any focused context files that currently document `session_models` or session-model fallback; update generated command/help docs if owned by code generation; remove stale references from root overview/glossary only if they describe current runtime behavior. + - Out — Adding historical narrative; editing unrelated Agent Trace design artifacts retained as historical references unless they claim current behavior. + - Done when: durable current-state context no longer says active runtime uses `session_models`; docs state Claude model IDs are direct best-effort diff-trace metadata and may be nullable/omitted. + - Verification notes (commands or checks): `nix run .#pkl-check-generated`; focused grep for `session_models`/`session-model` in current-state docs to ensure remaining references are intentional historical references or removed. + +- [ ] T06: `Validation and cleanup` (status:todo) + - Task ID: T06 + - Goal: Run full repository validation and clean up stale artifacts after the session-model removal. + - Boundaries (in/out of scope): + - In — Full checks, generated-output parity, formatting/lint/test validation, removal of dead code/imports/tests, and final plan status/evidence updates. + - Out — New product behavior beyond the planned session-model removal and direct Claude diff-trace model capture. + - Done when: `nix run .#pkl-check-generated` and `nix flake check` pass; no dead `session_models` runtime references remain; plan execution evidence is recorded. + - Verification notes (commands or checks): `nix run .#pkl-check-generated`; `nix flake check`; optional targeted grep for `session_models`, `SessionModel`, and `session-model` to verify only intentional historical/docs references remain. + +## Open questions + +- Should existing user databases with an already-created `session_models` table actively drop it via a new migration, or is it acceptable to leave the unused table in upgraded databases while removing it from the active runtime/fresh schema? This should be decided during T02 before changing migrations. diff --git a/context/sce/agent-trace-db.md b/context/sce/agent-trace-db.md index 3d92f605..c25500b1 100644 --- a/context/sce/agent-trace-db.md +++ b/context/sce/agent-trace-db.md @@ -210,7 +210,7 @@ Post-commit intersection rows are written by the active `post-commit` hook flow - No `context/tmp` artifact is written for conversation traces. - The generated OpenCode agent-trace plugin sends mixed-batch envelopes for conversation traces: regular `message` and `message.part` events each carry one per-item `type`, while diff-backed `message` events send one envelope containing the synthetic parent message item plus patch part items. -The `sce hooks session-model` command route writes session-model attribution payloads into `session_models` via STDIN JSON intake with required `sessionID`/`time`/`model_id`/`tool_name` and nullable `tool_version`; Claude `SessionStart` raw payloads may fill missing version metadata from a best-effort `claude --version` probe before upsert. The stored nullable `session_models.tool_version` is the durable fallback reused by later diff-trace persistence when the incoming payload omits version metadata. `(tool_name, session_id)` is the unique upsert key: subsequent upserts for the same tool/session pair replace `model_id`, `tool_version`, and `session_start_time_ms` while updating `updated_at`. See [agent-trace-hooks-command-routing.md](agent-trace-hooks-command-routing.md). +`sce hooks session-model` is no longer a supported command route, and generated Claude settings no longer produce `SessionStart` model-attribution events. The `session_models` table/API and diff-trace fallback lookup remain temporarily in the DB adapter for the later planned cleanup tasks, so existing rows may still be read by `diff-trace` when direct payload attribution is missing, but no active route writes new rows. See [agent-trace-hooks-command-routing.md](agent-trace-hooks-command-routing.md). ## Recent patch reads diff --git a/context/sce/agent-trace-hooks-command-routing.md b/context/sce/agent-trace-hooks-command-routing.md index cd66b3d5..8c0337b4 100644 --- a/context/sce/agent-trace-hooks-command-routing.md +++ b/context/sce/agent-trace-hooks-command-routing.md @@ -13,7 +13,6 @@ - `sce hooks post-rewrite ` - `sce hooks diff-trace` - `sce hooks conversation-trace` -- `sce hooks session-model` for normalized model attribution intake ## Parser and dispatch behavior @@ -70,16 +69,12 @@ - When `model_id` or `tool_version` is missing/nullable in the parsed payload, Rust looks up AgentTraceDb `session_models` by `(tool_name, session_id)` and uses the stored attribution values for missing fields when available. Direct payload `model_id` and `tool_version` values keep precedence over stored values. - If no matching session row exists, missing attribution fields remain `None`; the hook still attempts the AgentTraceDb insert with nullable attribution. - Persistence: resolves the current per-checkout AgentTraceDb lazily and inserts the parsed payload fields via `DiffTraceInsert` + `insert_diff_trace()` using nullable/resolved `model_id` and `tool_version`. No parsed-payload artifact is written under `context/tmp`. - - If no matching session row exists, missing attribution fields remain `None`; the hook still persists the parsed-payload artifact and attempts the AgentTraceDb insert with nullable attribution. - - Persistence: writes one parsed-payload artifact per invocation to `context/tmp/-000000-diff-trace.json` with atomic create-new retry semantics, resolves the current per-checkout AgentTraceDb lazily, and inserts the parsed payload fields via `DiffTraceInsert` + `insert_diff_trace()` using nullable/resolved `model_id` and `tool_version`. - - Fail-open boundary: STDIN read failures, JSON parse/validation errors, attribution lookup failures, artifact persistence failures, and AgentTraceDb setup/persistence failures are logged through `sce.hooks.diff_trace.error` and converted to command success with `diff-trace hook intake failed open; error logged.` so hook callers do not receive app-level classified errors or non-zero exits for intake failures. - - Valid payload success output is unchanged: full artifact + AgentTraceDb success still returns `diff-trace hook intake persisted payload to AgentTraceDb and context/tmp.`, and the existing AgentTraceDb write-warning path still logs `sce.hooks.diff_trace.agent_trace_db_write_failed` and returns `diff-trace hook intake persisted payload to context/tmp; AgentTraceDb persistence failed.` - - Current TypeScript producers are the OpenCode agent-trace plugin and the generated Claude `sce hooks` command hooks (no TypeScript intermediary). + - Current producers are the OpenCode agent-trace plugin and the generated Claude `sce hooks` command hooks (no TypeScript intermediary). - OpenCode forwards user-message `message` diffs with `tool_name="opencode"`, always including `model_id`, and nullable OpenCode client-version metadata. - - Claude forwards supported `PostToolUse` `Write` structured-update/content-create and `Edit` structured-patch diffs with `tool_name="claude"` and no direct `model_id`; any explicit payload version metadata is preserved, and missing `model_id` / `tool_version` values are resolved from `session_models` when available. + - Claude generated settings no longer register `SessionStart`; supported `PostToolUse` `Write|Edit|MultiEdit|NotebookEdit` events are routed directly to `sce hooks diff-trace`. Missing `model_id` / `tool_version` values are resolved from pre-existing `session_models` rows when available until the later fallback-removal task lands. - Neither TypeScript runtime writes `context/tmp/*-diff-trace.json` artifacts or AgentTraceDb rows directly. - `diff-trace` command success reports AgentTraceDb persistence only. AgentTraceDb open/insert failures are logged through `sce.hooks.diff_trace.agent_trace_db_write_failed` and reflected in deterministic success text as failed DB persistence; no parsed-payload artifact fallback is created. -- `diff-trace` command success no longer requires artifact persistence to succeed on intake failure paths. The strict valid-payload path still writes the parsed-payload artifact before AgentTraceDb insertion; failures before or during that strict path are logged through `sce.hooks.diff_trace.error` and returned as hook success, while the existing post-artifact AgentTraceDb write-warning success path remains unchanged. +- `diff-trace` producer-facing intake failures are logged through `sce.hooks.diff_trace.error` and returned as hook success; the valid-payload path is DB-only and does not write parsed-payload artifacts. - `conversation-trace` is a recognized hook subcommand routed through `HookSubcommand::ConversationTrace`. Rust intake classifies incoming STDIN JSON by the presence of a top-level `hook_event_name` field: raw Claude hook events are routed through `transform_claude_user_prompt_submit`, `transform_claude_stop`, or `transform_claude_post_tool_use` depending on the event name, while payloads without `hook_event_name` follow the existing mixed-batch `{ payloads: [...] }` path. - **Raw Claude `UserPromptSubmit` events** (detected by `hook_event_name = "UserPromptSubmit"`): the raw event payload is validated and transformed by `transform_claude_user_prompt_submit` before being forwarded to `parse_conversation_trace_payloads`. - Validates that `hook_event_name` is exactly `"UserPromptSubmit"` and the required `session_id` and `prompt` fields are present and non-empty. @@ -115,10 +110,7 @@ - Current valid-payload success output reports deterministic mixed-batch accounting: `conversation-trace hook persisted mixed payload batch to AgentTraceDb: attempted=, persisted_messages=, persisted_parts=, skipped=.` The hook does not persist `context/tmp` artifacts. - Fail-open output for conversation-trace intake failures is `conversation-trace hook intake failed open; error logged.` so hook callers do not receive app-level classified errors or non-zero exits for intake failures. - The generated OpenCode agent-trace plugin emits this mixed-batch shape for conversation-trace handoff: ordinary message/part events produce one-item mixed envelopes, completed question-tool parts produce `message.part` items with `part_type: "question"`, and diff-backed message events produce one envelope containing the synthetic parent `message` item plus patch `message.part` items. -- `session-model` reads STDIN JSON and classifies the payload: - - **Claude `SessionStart` payloads** (detected by presence of top-level `hook_event_name`): extracts `session_id` from `session_id`/`sessionID`, `model_id` from `model`/`model_id` (including nested `model.id`/`model.model`/`model.name` with `claude/` prefix normalization), `time` from `time`/`timestamp` (falls back to current system time), `tool_name="claude"`, and `tool_version` from `tool_version`/`claude_version`/`version`; when no non-empty payload version is present, Rust best-effort runs `claude --version`, trims stdout, and uses that value if non-empty, otherwise leaving `tool_version` nullable without failing intake. - - **OpenCode normalized payloads** (no `hook_event_name`): existing `{ sessionID, time, model_id, tool_name, tool_version }` validation applies unchanged. - - Valid payloads are upserted into the per-checkout AgentTraceDb `session_models` via `SessionModelUpsert` using `(tool_name, session_id)` as the unique key. No raw hook artifacts are written. DB open/insert failures are logged through `sce.hooks.session_model.agent_trace_db_write_failed` and reported in the success text as failed persistence. +- `session-model` is no longer a supported `sce hooks` subcommand and generated Claude settings no longer produce `SessionStart` model-attribution events. The `session_models` DB API/table and diff-trace fallback remain temporarily in code for the later planned cleanup tasks, but there is no active runtime route that writes new rows. ## Explicit non-goals in the current baseline diff --git a/context/sce/claude-raw-hook-capture.md b/context/sce/claude-raw-hook-capture.md index 690e1d92..6c3baee7 100644 --- a/context/sce/claude-raw-hook-capture.md +++ b/context/sce/claude-raw-hook-capture.md @@ -4,13 +4,13 @@ The `sce hooks claude-capture ` CLI route, `ClaudeCaptureEvent`, `HookSubcommand::ClaudeCapture`, the `claude_transcript.rs` enrichment module, and `RepoPaths::claude_capture_tmp_dir()` were removed in T05 of the `claude-typescript-model-cache-remove-rust-capture` plan. -Rust now exposes only normalized intakes for Claude/OpenCode editor runtimes: +Rust now exposes these active intakes for Claude/OpenCode editor runtimes: -- `sce hooks session-model` — STDIN JSON intake for normalized model attribution upsert in `session_models`, keyed by `(tool_name, session_id)`. No raw hook artifacts are written. -- `sce hooks diff-trace` — STDIN JSON intake for normalized or Claude structured diff-trace payloads with optional/nullable attribution. When `model_id` or `tool_version` is missing, Rust resolves available values from `session_models` by `(tool_name, session_id)` and otherwise persists nullable attribution to AgentTraceDb without writing raw hook artifacts. -- `sce hooks diff-trace` — STDIN JSON intake for normalized or Claude structured diff-trace payloads with optional/nullable attribution. When `model_id` or `tool_version` is missing, Rust resolves available values from `session_models` by `(tool_name, session_id)` and otherwise persists nullable attribution on the valid path; runtime intake failures log `sce.hooks.diff_trace.error` and fail open to the hook producer. +- `sce hooks diff-trace` — STDIN JSON intake for normalized or Claude structured diff-trace payloads with optional/nullable attribution. When `model_id` or `tool_version` is missing, Rust resolves available values from existing `session_models` rows by `(tool_name, session_id)` and otherwise persists nullable attribution to AgentTraceDb without writing raw hook artifacts. - `sce hooks conversation-trace` — STDIN JSON intake for normalized mixed-batch message/part payloads and supported raw Claude `UserPromptSubmit`, `Stop`, and `PostToolUse` events. Runtime intake failures log `sce.hooks.conversation_trace.error` and fail open to the hook producer. +`sce hooks session-model` is no longer supported, and generated Claude settings no longer register `SessionStart` for Agent Trace attribution. + ## Historical artifact contract Before removal, the raw capture route: @@ -25,9 +25,9 @@ The generated Claude TypeScript runtime at `config/.claude/plugins/sce-agent-tra ## Current state -- Claude settings call the generated Bash helper `.claude/hooks/run-sce-or-show-install-guidance.sh` via generated `.claude/settings.json` command hooks before invoking `sce`: `SessionStart` pipes raw hook event JSON to `sce hooks session-model`, matched `PostToolUse Write|Edit|MultiEdit|NotebookEdit` pipes raw hook event JSON to `sce hooks diff-trace`, supported conversation events pipe raw hook event JSON to `sce hooks conversation-trace`, and `PreToolUse Bash` pipes raw hook event JSON to `sce policy bash`. The helper emits `sce CLI not found. Install it from https://sce.crocoder.dev/docs/getting-started#install-cli` and exits successfully when `sce` is missing; when `sce` exists it `exec`s the original command arguments so Rust receives stdin and owns stdout/stderr/exit behavior. Rust handles extraction, validation, and persistence without a TypeScript intermediary. +- Claude settings call the generated Bash helper `.claude/hooks/run-sce-or-show-install-guidance.sh` via generated `.claude/settings.json` command hooks before invoking `sce`: matched `PostToolUse Write|Edit|MultiEdit|NotebookEdit` pipes raw hook event JSON to `sce hooks diff-trace`, supported conversation events pipe raw hook event JSON to `sce hooks conversation-trace`, and `PreToolUse Bash` pipes raw hook event JSON to `sce policy bash`. The helper emits `sce CLI not found. Install it from https://sce.crocoder.dev/docs/getting-started#install-cli` and exits successfully when `sce` is missing; when `sce` exists it `exec`s the original command arguments so Rust receives stdin and owns stdout/stderr/exit behavior. Rust handles extraction, validation, and persistence without a TypeScript intermediary. `SessionStart` is no longer registered. - The former Claude TypeScript runtime at `config/.claude/plugins/sce-agent-trace.ts` was removed in T07 of the `claude-rust-diff-trace` plan. -- Rust owns normalized persistence: `session-model` upserts into `session_models`, `diff-trace` inserts into `diff_traces` with `payload_type` classification (`"patch"` for OpenCode, `"structured"` for Claude). +- Rust owns normalized persistence: `diff-trace` inserts into `diff_traces` with `payload_type` classification (`"patch"` for OpenCode, `"structured"` for Claude), while `conversation-trace` inserts normalized messages/parts. - Claude `diff-trace` missing `model_id` and `tool_version` values are resolved from `session_models` at persistence time when available, otherwise stored as nullable attribution; OpenCode sends `model_id` directly and may send nullable `tool_version`. - No raw Claude hook payload artifacts are written by TypeScript or Rust. diff --git a/context/sce/generated-opencode-plugin-registration.md b/context/sce/generated-opencode-plugin-registration.md index cb8cab9e..88feab50 100644 --- a/context/sce/generated-opencode-plugin-registration.md +++ b/context/sce/generated-opencode-plugin-registration.md @@ -24,7 +24,7 @@ The generated-config pipeline now has one canonical Pkl-authored source for Open ## Claude boundary - Claude does not consume the OpenCode `plugin` manifest surface. -- Claude agent-trace event handling is registered through generated `.claude/settings.json` command hooks that call `.claude/hooks/run-sce-or-show-install-guidance.sh` before invoking `sce hooks`: `SessionStart` → `sce hooks session-model`, matched `PostToolUse Write|Edit|MultiEdit|NotebookEdit` → `sce hooks diff-trace`, and supported conversation events → `sce hooks conversation-trace`. +- Claude agent-trace event handling is registered through generated `.claude/settings.json` command hooks that call `.claude/hooks/run-sce-or-show-install-guidance.sh` before invoking `sce hooks`: matched `PostToolUse Write|Edit|MultiEdit|NotebookEdit` → `sce hooks diff-trace`, and supported conversation events → `sce hooks conversation-trace`. `SessionStart` is no longer registered and `sce hooks session-model` is no longer supported. - The Rust CLI receives raw Claude hook event JSON on STDIN and handles extraction, validation, and persistence without a TypeScript translation layer. - Claude bash-policy enforcement is registered through generated `.claude/settings.json` as a `PreToolUse` `Bash` command hook that calls the same generated helper before running `sce policy bash` and passing raw hook event JSON on STDIN. - The Claude helper emits `sce CLI not found. Install it from https://sce.crocoder.dev/docs/getting-started#install-cli` and exits successfully when `sce` is missing, preserving fail-open hook behavior; when `sce` exists it `exec`s the original command arguments. diff --git a/context/sce/opencode-agent-trace-plugin-runtime.md b/context/sce/opencode-agent-trace-plugin-runtime.md index 368170c2..39bdba30 100644 --- a/context/sce/opencode-agent-trace-plugin-runtime.md +++ b/context/sce/opencode-agent-trace-plugin-runtime.md @@ -113,11 +113,10 @@ When extraction succeeds, `buildQuestionToolConversationTracePayload(eventPart)` ## Shared boundary with Claude runtime - OpenCode uses a generated TypeScript event runtime as an event-shape adapter before handing normalized diff-trace payloads to the shared Rust hook intake. -- Claude registration uses generated `.claude/settings.json` command hooks that call `.claude/hooks/run-sce-or-show-install-guidance.sh` before `sce hooks` (no TypeScript runtime intermediary): `SessionStart` pipes the raw Claude hook event JSON to `sce hooks session-model`, matched `PostToolUse Write|Edit|MultiEdit|NotebookEdit` pipes the raw hook event to `sce hooks diff-trace`, and supported conversation events pipe raw hook events to `sce hooks conversation-trace`. +- Claude registration uses generated `.claude/settings.json` command hooks that call `.claude/hooks/run-sce-or-show-install-guidance.sh` before `sce hooks` (no TypeScript runtime intermediary): matched `PostToolUse Write|Edit|MultiEdit|NotebookEdit` pipes the raw hook event to `sce hooks diff-trace`, and supported conversation events pipe raw hook events to `sce hooks conversation-trace`; `SessionStart` is no longer registered. - Rust `diff-trace` intake detects Claude payloads via `hook_event_name` and derives structured patches from the raw JSON with `payload_type="structured"`; OpenCode normalized payloads (no `hook_event_name`) are stored as `payload_type="patch"`. -- Rust `session-model` intake detects Claude `SessionStart` payloads via `hook_event_name`, extracts `session_id`/`model_id`/`time` from the raw Claude event format, uses explicit payload version fields (`tool_version`/`claude_version`/`version`) when present, and otherwise best-effort fills `tool_version` from trimmed `claude --version` stdout when available. -- The shared Rust boundary is `sce hooks diff-trace` and `sce hooks session-model`: Rust remains the only writer of AgentTraceDb `diff_traces`/`session_models` rows and no longer writes parsed `context/tmp/*-diff-trace.json` artifacts. -- Claude attribution differs from OpenCode attribution: OpenCode reads provider/model data from the OpenCode event and includes `model_id` in the payload; for Claude `diff-trace`, Rust resolves missing `model_id` and `tool_version` from AgentTraceDb `session_models` at persistence time when available, otherwise persisting nullable attribution; for Claude `session-model`, Rust extracts `model_id` from the raw hook event, normalizes it with a `claude/` prefix, and stores nullable `tool_version` for later diff-trace fallback, including best-effort `claude --version` capture when payload version metadata is absent. +- `sce hooks session-model` is no longer a supported shared Rust boundary. Rust remains the only writer of AgentTraceDb `diff_traces` rows; the old `session_models` fallback API/table remains temporarily until the planned cleanup tasks remove it, but no active hook route writes new session-model rows and no parsed `context/tmp/*-diff-trace.json` artifacts are written. +- Claude attribution differs from OpenCode attribution: OpenCode reads provider/model data from the OpenCode event and includes `model_id` in the payload; for Claude `diff-trace`, Rust currently resolves missing `model_id` and `tool_version` from AgentTraceDb `session_models` at persistence time when available, otherwise persisting nullable attribution. ## Claude derivation golden tests From 20dcd4a702d01d7bc033e4f9d623ce2a91d86711 Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Wed, 1 Jul 2026 10:12:26 +0200 Subject: [PATCH 2/9] agent-trace: Remove session model attribution storage Remove the session_models migration/API surface and stop resolving diff-trace attribution from session-level fallback rows. Keep diff_traces.payload_type as the active 015 migration after retiring the old session_models migration, with direct payload metadata persisted as-is. Plan: remove-session-models-direct-claude-model-id Tasks: T02 Plan: claude-rust-diff-trace Tasks: T03 Co-authored-by: SCE --- ...l => 015_add_diff_traces_payload_type.sql} | 0 .../agent-trace/015_create_session_models.sql | 11 -- cli/src/generated_migrations.rs | 3 +- cli/src/services/agent_trace_db/mod.rs | 126 ++------------- cli/src/services/db/mod.rs | 8 + cli/src/services/hooks/mod.rs | 144 +----------------- context/architecture.md | 7 +- context/glossary.md | 13 +- context/overview.md | 10 +- context/patterns.md | 10 +- context/plans/claude-rust-diff-trace.md | 4 +- ...e-session-models-direct-claude-model-id.md | 20 ++- context/sce/agent-trace-db.md | 7 +- 13 files changed, 72 insertions(+), 291 deletions(-) rename cli/migrations/agent-trace/{016_add_diff_traces_payload_type.sql => 015_add_diff_traces_payload_type.sql} (100%) delete mode 100644 cli/migrations/agent-trace/015_create_session_models.sql diff --git a/cli/migrations/agent-trace/016_add_diff_traces_payload_type.sql b/cli/migrations/agent-trace/015_add_diff_traces_payload_type.sql similarity index 100% rename from cli/migrations/agent-trace/016_add_diff_traces_payload_type.sql rename to cli/migrations/agent-trace/015_add_diff_traces_payload_type.sql diff --git a/cli/migrations/agent-trace/015_create_session_models.sql b/cli/migrations/agent-trace/015_create_session_models.sql deleted file mode 100644 index 4f909c80..00000000 --- a/cli/migrations/agent-trace/015_create_session_models.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE IF NOT EXISTS session_models ( - id INTEGER PRIMARY KEY, - tool_name TEXT NOT NULL, - session_id TEXT NOT NULL, - model_id TEXT NOT NULL, - tool_version TEXT, - session_start_time_ms INTEGER NOT NULL, - 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')), - UNIQUE (tool_name, session_id) -); diff --git a/cli/src/generated_migrations.rs b/cli/src/generated_migrations.rs index 40ee4959..5a855da2 100644 --- a/cli/src/generated_migrations.rs +++ b/cli/src/generated_migrations.rs @@ -17,8 +17,7 @@ pub static AGENT_TRACE_MIGRATIONS: &[(&str, &str)] = &[ ("012_create_parts_session_message_order_index", include_str!("../migrations/agent-trace/012_create_parts_session_message_order_index.sql")), ("013_create_messages_updated_at_trigger", include_str!("../migrations/agent-trace/013_create_messages_updated_at_trigger.sql")), ("014_create_parts_updated_at_trigger", include_str!("../migrations/agent-trace/014_create_parts_updated_at_trigger.sql")), - ("015_create_session_models", include_str!("../migrations/agent-trace/015_create_session_models.sql")), - ("016_add_diff_traces_payload_type", include_str!("../migrations/agent-trace/016_add_diff_traces_payload_type.sql")), + ("015_add_diff_traces_payload_type", include_str!("../migrations/agent-trace/015_add_diff_traces_payload_type.sql")), ]; #[rustfmt::skip] diff --git a/cli/src/services/agent_trace_db/mod.rs b/cli/src/services/agent_trace_db/mod.rs index 5faf1487..8beffcad 100644 --- a/cli/src/services/agent_trace_db/mod.rs +++ b/cli/src/services/agent_trace_db/mod.rs @@ -69,30 +69,7 @@ pub const INSERT_PART_SQL: &str = "INSERT INTO parts (type, text, message_id, session_id, generated_at_unix_ms) VALUES (?1, ?2, ?3, ?4, ?5)"; -/// Parameterized SQL for upserting editor session model attribution. -pub const UPSERT_SESSION_MODEL_SQL: &str = "INSERT INTO session_models ( - tool_name, - session_id, - model_id, - tool_version, - session_start_time_ms -) VALUES (?1, ?2, ?3, ?4, ?5) -ON CONFLICT(tool_name, session_id) DO UPDATE SET - model_id = excluded.model_id, - tool_version = excluded.tool_version, - session_start_time_ms = excluded.session_start_time_ms, - updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')"; - -/// Parameterized SQL for retrieving editor session model attribution. -pub const SELECT_SESSION_MODEL_SQL: &str = "SELECT - tool_name, - session_id, - model_id, - tool_version, - session_start_time_ms -FROM session_models -WHERE tool_name = ?1 AND session_id = ?2 -LIMIT 1"; +const RETIRED_AGENT_TRACE_MIGRATION_IDS: &[&str] = &["015_create_session_models"]; /// Agent trace database configuration. pub struct AgentTraceDbSpec; @@ -110,6 +87,10 @@ impl DbSpec for AgentTraceDbSpec { generated_migrations::AGENT_TRACE_MIGRATIONS } + fn retired_migration_ids() -> &'static [&'static str] { + RETIRED_AGENT_TRACE_MIGRATION_IDS + } + fn db_config_key() -> &'static str { "agent_trace_db" } @@ -144,27 +125,6 @@ pub struct DiffTraceInsert<'a> { pub payload_type: &'a str, } -/// Session model attribution payload to upsert into the agent trace database. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[allow(dead_code)] -pub struct SessionModelUpsert<'a> { - pub tool_name: &'a str, - pub session_id: &'a str, - pub model_id: &'a str, - pub tool_version: Option<&'a str>, - pub session_start_time_ms: i64, -} - -/// Durable session model attribution row read from the agent trace database. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct SessionModelAttribution { - pub tool_name: String, - pub session_id: String, - pub model_id: String, - pub tool_version: Option, - pub session_start_time_ms: i64, -} - /// Raw diff trace row read from the agent trace database. #[derive(Clone, Debug, Eq, PartialEq)] pub struct DiffTracePatchRow { @@ -339,21 +299,6 @@ impl AgentTraceDb { insert_agent_trace_with(self, input) } - /// Upsert editor session model attribution into the `session_models` table. - #[allow(dead_code)] - pub fn upsert_session_model(&self, input: SessionModelUpsert<'_>) -> Result { - upsert_session_model_with(self, input) - } - - /// Retrieve editor session model attribution by `(tool_name, session_id)`. - pub fn session_model_by_tool_and_session( - &self, - tool_name: &str, - session_id: &str, - ) -> Result> { - session_model_by_tool_and_session_with(self, tool_name, session_id) - } - /// Query and parse recent diff trace patches within the inclusive time window. pub fn recent_diff_trace_patches( &self, @@ -489,23 +434,6 @@ fn insert_part_with(db: &TursoDb, input: InsertPartInsert) -> Resu ) } -#[allow(dead_code)] -fn upsert_session_model_with( - db: &TursoDb, - input: SessionModelUpsert<'_>, -) -> Result { - db.execute( - UPSERT_SESSION_MODEL_SQL, - ( - input.tool_name, - input.session_id, - input.model_id, - input.tool_version, - input.session_start_time_ms, - ), - ) -} - fn insert_parts_with(db: &TursoDb, inputs: Vec) -> Result { if inputs.is_empty() { return Ok(0); @@ -541,40 +469,6 @@ fn numbered_placeholders(start: usize, count: usize) -> String { format!("({placeholders})") } -fn session_model_by_tool_and_session_with( - db: &TursoDb, - tool_name: &str, - session_id: &str, -) -> Result> { - let rows = db.query_map( - SELECT_SESSION_MODEL_SQL, - (tool_name, session_id), - session_model_attribution_from_turso, - )?; - - Ok(rows.into_iter().next()) -} - -fn session_model_attribution_from_turso(row: &turso::Row) -> Result { - Ok(SessionModelAttribution { - tool_name: row - .get(0) - .context("failed to read session_models.tool_name")?, - session_id: row - .get(1) - .context("failed to read session_models.session_id")?, - model_id: row - .get(2) - .context("failed to read session_models.model_id")?, - tool_version: row - .get(3) - .context("failed to read session_models.tool_version")?, - session_start_time_ms: row - .get(4) - .context("failed to read session_models.session_start_time_ms")?, - }) -} - fn recent_diff_trace_patches_with( db: &TursoDb, cutoff_time_ms: i64, @@ -700,6 +594,10 @@ mod tests { generated_migrations::AGENT_TRACE_MIGRATIONS } + fn retired_migration_ids() -> &'static [&'static str] { + RETIRED_AGENT_TRACE_MIGRATION_IDS + } + fn db_config_key() -> &'static str { "agent_trace_db" } @@ -723,6 +621,10 @@ mod tests { generated_migrations::AGENT_TRACE_MIGRATIONS } + fn retired_migration_ids() -> &'static [&'static str] { + RETIRED_AGENT_TRACE_MIGRATION_IDS + } + fn db_config_key() -> &'static str { "agent_trace_db" } @@ -921,7 +823,7 @@ mod tests { )); assert!(sqlite_object_exists(&db, "table", "messages")); assert!(sqlite_object_exists(&db, "table", "parts")); - assert!(sqlite_object_exists(&db, "table", "session_models")); + assert!(!sqlite_object_exists(&db, "table", "session_models")); assert!(sqlite_object_exists( &db, "index", diff --git a/cli/src/services/db/mod.rs b/cli/src/services/db/mod.rs index df37e607..1267aa64 100644 --- a/cli/src/services/db/mod.rs +++ b/cli/src/services/db/mod.rs @@ -53,6 +53,12 @@ pub trait DbSpec { /// Ordered embedded migration SQL files as `(id, sql)` pairs. fn migrations() -> &'static [(&'static str, &'static str)]; + /// Migration IDs that are no longer applied to fresh databases but may be + /// present in already-upgraded databases. + fn retired_migration_ids() -> &'static [&'static str] { + &[] + } + /// Config-file lookup key under `policies.database_retry`. /// One of `"local_db"`, `"agent_trace_db"`, `"auth_db"`. fn db_config_key() -> &'static str; @@ -639,9 +645,11 @@ impl TursoDb { problems.push(format!("missing migrations {}", missing_ids.join(", "))); } + let retired_ids = M::retired_migration_ids(); let unexpected_ids = applied_ids .iter() .filter(|applied_id| !expected_ids.iter().any(|id| id == &applied_id.as_str())) + .filter(|applied_id| !retired_ids.iter().any(|id| id == &applied_id.as_str())) .map(String::as_str) .collect::>(); if !unexpected_ids.is_empty() { diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index cae8983b..a6ec260a 100644 --- a/cli/src/services/hooks/mod.rs +++ b/cli/src/services/hooks/mod.rs @@ -16,7 +16,7 @@ use crate::services::agent_trace::{ use crate::services::agent_trace_db::{ AgentTraceDb, AgentTraceInsert, DiffTraceInsert, InsertMessageInsert, InsertPartInsert, MessageRole, PartType, PostCommitPatchIntersectionInsert, RecentDiffTracePatches, - SessionModelAttribution, PAYLOAD_TYPE_PATCH, PAYLOAD_TYPE_STRUCTURED, + PAYLOAD_TYPE_PATCH, PAYLOAD_TYPE_STRUCTURED, }; use crate::services::checkout; use crate::services::config; @@ -72,12 +72,6 @@ enum DiffTraceParseResult { NoOp(String), } -#[derive(Clone, Debug, Eq, PartialEq)] -struct ResolvedDiffTraceAttribution { - model_id: Option, - tool_version: Option, -} - #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum StdinPayloadKind { DiffTrace, @@ -680,25 +674,7 @@ fn run_diff_trace_subcommand_from_payload( DiffTraceParseResult::Persist(payload) => payload, DiffTraceParseResult::NoOp(message) => return Ok(message), }; - let resolve_attribution = - |tool_name: &str, session_id: &str| -> Result> { - let db = open_agent_trace_db_for_hook_runtime( - repository_root, - "Failed to open Agent Trace DB for model resolution.", - ) - .context("Failed to open Agent Trace DB for model resolution.")?; - let attribution = db - .session_model_by_tool_and_session(tool_name, session_id) - .context("Failed to query session model attribution from Agent Trace DB.")?; - Ok(attribution) - }; - - run_diff_trace_subcommand_from_payload_with( - repository_root, - &payload, - logger, - resolve_attribution, - ) + run_diff_trace_subcommand_from_payload_with(repository_root, &payload, logger) } fn log_diff_trace_fail_open(error: &anyhow::Error, logger: Option<&dyn Logger>) -> String { @@ -709,18 +685,11 @@ fn log_diff_trace_fail_open(error: &anyhow::Error, logger: Option<&dyn Logger>) String::from("diff-trace hook intake failed open; error logged.") } -fn run_diff_trace_subcommand_from_payload_with( +fn run_diff_trace_subcommand_from_payload_with( repository_root: &Path, payload: &DiffTracePayload, logger: Option<&dyn Logger>, - resolve_session_attribution: R, -) -> Result -where - R: FnOnce(&str, &str) -> Result>, -{ - let resolved_attribution = - resolve_diff_trace_attribution(payload, resolve_session_attribution)?; - +) -> Result { if let Err(error) = diff_trace_db_time_ms(payload.time) { if let Some(log) = logger { log.warn( @@ -733,8 +702,8 @@ where let agent_trace_db_result = persist_diff_trace_payload_to_agent_trace_db( repository_root, payload, - resolved_attribution.model_id.as_deref(), - resolved_attribution.tool_version.as_deref(), + payload.model_id.as_deref(), + payload.tool_version.as_deref(), ); let agent_trace_db_persisted = match agent_trace_db_result { Ok(()) => true, @@ -761,33 +730,6 @@ where } } -fn resolve_diff_trace_attribution( - payload: &DiffTracePayload, - resolve_attribution: R, -) -> Result -where - R: FnOnce(&str, &str) -> Result>, -{ - let session_attribution = if payload.model_id.is_none() || payload.tool_version.is_none() { - resolve_attribution(&payload.tool_name, &payload.session_id)? - } else { - None - }; - - Ok(ResolvedDiffTraceAttribution { - model_id: payload.model_id.clone().or_else(|| { - session_attribution - .as_ref() - .map(|attribution| attribution.model_id.clone()) - }), - tool_version: payload.tool_version.clone().or_else(|| { - session_attribution - .as_ref() - .and_then(|attribution| attribution.tool_version.clone()) - }), - }) -} - fn parse_diff_trace_payload(stdin_payload: &str) -> Result { let payload_kind = StdinPayloadKind::DiffTrace; let parsed: Value = serde_json::from_str(stdin_payload) @@ -2268,80 +2210,6 @@ mod tests { } } - fn session_model_attribution( - model_id: &str, - tool_version: Option<&str>, - ) -> SessionModelAttribution { - SessionModelAttribution { - tool_name: String::from("claude"), - session_id: String::from("session-123"), - model_id: model_id.to_string(), - tool_version: tool_version.map(String::from), - session_start_time_ms: 1_800_000_000_000_i64, - } - } - - #[test] - fn diff_trace_attribution_uses_session_tool_version_when_payload_missing() { - let payload = diff_trace_payload(Some("direct-model"), None); - - let resolved = resolve_diff_trace_attribution(&payload, |tool_name, session_id| { - assert_eq!(tool_name, "claude"); - assert_eq!(session_id, "session-123"); - Ok(Some(session_model_attribution( - "session-model", - Some("Claude Code 1.2.3"), - ))) - }) - .expect("diff-trace attribution should resolve"); - - assert_eq!(resolved.model_id, Some(String::from("direct-model"))); - assert_eq!( - resolved.tool_version, - Some(String::from("Claude Code 1.2.3")) - ); - } - - #[test] - fn diff_trace_attribution_prefers_payload_tool_version() { - let payload = diff_trace_payload(None, Some("payload-version")); - - let resolved = resolve_diff_trace_attribution(&payload, |_tool_name, _session_id| { - Ok(Some(session_model_attribution( - "session-model", - Some("stored-version"), - ))) - }) - .expect("diff-trace attribution should resolve"); - - assert_eq!(resolved.model_id, Some(String::from("session-model"))); - assert_eq!(resolved.tool_version, Some(String::from("payload-version"))); - } - - #[test] - fn diff_trace_attribution_skips_session_query_when_payload_attribution_complete() { - let payload = diff_trace_payload(Some("direct-model"), Some("payload-version")); - - let resolved = resolve_diff_trace_attribution(&payload, |_tool_name, _session_id| { - panic!("complete payload attribution should avoid session_models lookup") - }) - .expect("complete payload attribution should resolve without session lookup"); - - assert_eq!(resolved.model_id, Some(String::from("direct-model"))); - assert_eq!(resolved.tool_version, Some(String::from("payload-version"))); - } - - #[test] - fn diff_trace_attribution_keeps_none_when_session_row_missing() { - let payload = diff_trace_payload(None, None); - - let resolved = resolve_diff_trace_attribution(&payload, |_tool_name, _session_id| Ok(None)) - .expect("diff-trace attribution should allow missing session row"); - - assert_eq!(resolved.model_id, None); - assert_eq!(resolved.tool_version, None); - } - #[test] fn diff_trace_db_persistence_uses_resolved_tool_version() { let payload = diff_trace_payload(Some("direct-model"), None); diff --git a/context/architecture.md b/context/architecture.md index b0541269..76df6d62 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -116,7 +116,7 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/db/mod.rs` provides the shared generic Turso infrastructure seam: `DbSpec` supplies a service-specific name, path, ordered embedded migrations, and config-file lookup key (`db_config_key()`), while `TursoDb` owns parent-directory creation, `Builder::new_local(...)` initialization (with `experimental_multiprocess_wal(true)` for safe concurrent access), Turso connection setup, tokio current-thread runtime bridging, retry-backed blocking `execute`/`query`/`query_values`/`query_map` wrappers, and generic migration execution with per-database `__sce_migrations` metadata. `TursoDb::new()` and `EncryptedTursoDb::new()` wrap only their local open/connect block in `run_with_retry_sync` using a config-driven connection-open policy resolved from the `DATABASE_RETRY_CONFIG` `OnceLock` with fallback to hardcoded defaults, while operation methods use a config-driven operation policy from the same source. `query_values()` returns fully fetched column names plus raw `turso::Value` rows for deterministic operator-facing rendering; `query_map()` retries the initial query and row-fetch loop, then applies caller row mapping after retry completion. Migration execution is not retried. The same module also provides `EncryptedTursoDb`, a structurally parallel encrypted adapter that resolves the encryption key through `encryption_key::get_or_create_encryption_key()`, enables Turso local encryption with strict `aegis256` cipher selection, and exposes retry-backed synchronous wrappers plus migration execution. `cli/src/services/db/encryption_key.rs` first derives a Turso-compatible 64-character hex key from non-empty `SCE_AUTH_DB_ENCRYPTION_KEY` env-secret text when present, otherwise falls back to keyring-backed credential-store get-or-create behavior; no plaintext auth DB fallback exists. - `cli/src/services/local_db/mod.rs` provides the concrete local DB spec and `LocalDb` type alias over the shared generic `TursoDb` adapter. `LocalDbSpec` resolves the deterministic persistent runtime DB target through the shared default-path seam and declares no local migrations; `TursoDb` supplies retry-backed blocking `execute`/`query`, parent-directory creation, Turso connection setup, tokio current-thread runtime bridging, and generic migration execution. - `cli/src/services/auth_db/mod.rs` provides the encrypted auth DB spec and `AuthDb` type alias over `EncryptedTursoDb`. `AuthDbSpec` resolves `/sce/auth.db` through the shared default-path seam and embeds ordered auth migrations. Auth DB lifecycle setup/doctor integration is wired through `AuthDbLifecycle`; auth command/token-storage reads/writes are directed through `token_storage.rs`. -- `cli/src/services/agent_trace_db/mod.rs` provides the Agent Trace DB spec and `AgentTraceDb` type alias over `TursoDb`. `AgentTraceDbSpec` resolves the legacy `/sce/agent-trace.db` fallback through the shared default-path seam and embeds ordered fresh-start migrations `001..016` for `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `messages`, `parts`, `session_models`, `diff_traces.payload_type`, indexes, and triggers. `agent_traces.agent_trace_id` is `NOT NULL UNIQUE`. The module provides explicit-path `open_at(path)` and `open_for_hooks_without_migrations_at(path)` helpers for per-checkout DB files plus `DiffTraceInsert<'_>`/`insert_diff_trace()` including the `payload_type` discriminator, `PostCommitPatchIntersectionInsert<'_>`/`insert_post_commit_patch_intersection()`, `AgentTraceInsert<'_>`/`insert_agent_trace()`, `InsertMessageInsert`/`insert_message()` plus `insert_messages()` for parent message inserts with duplicate `(session_id, message_id)` writes ignored, `InsertPartInsert`/`insert_part()` plus `insert_parts()` for append-only part text writes, `SessionModelUpsert<'_>`/`upsert_session_model()` plus lookup helpers for durable `session_models` attribution, and `recent_diff_trace_patches(cutoff_time_ms, end_time_ms)` for inclusive chronological `diff_traces` reads that dispatch on `payload_type`, parse valid raw patch or structured payload rows, and return skipped malformed-row reports. `cli/src/services/agent_trace_db/lifecycle.rs` registers Agent Trace DB setup/doctor lifecycle behavior: setup creates/reuses and registers checkout identity, resolves `/sce/agent-trace-{checkout_id}.db`, initializes it with `AgentTraceDb::open_at(path)`, and records `database_path`; active hook runtime still resolves checkout identity and lazily creates or upgrades the per-checkout DB when setup has not run or schema metadata is incomplete. +- `cli/src/services/agent_trace_db/mod.rs` provides the Agent Trace DB spec and `AgentTraceDb` type alias over `TursoDb`. `AgentTraceDbSpec` resolves the legacy `/sce/agent-trace.db` fallback through the shared default-path seam and embeds ordered fresh-start migrations `001..015` (`015_create_session_models` is retired/replaced by active `015_add_diff_traces_payload_type`) for `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `messages`, `parts`, `diff_traces.payload_type`, indexes, and triggers. `agent_traces.agent_trace_id` is `NOT NULL UNIQUE`. The module provides explicit-path `open_at(path)` and `open_for_hooks_without_migrations_at(path)` helpers for per-checkout DB files plus `DiffTraceInsert<'_>`/`insert_diff_trace()` including the `payload_type` discriminator, `PostCommitPatchIntersectionInsert<'_>`/`insert_post_commit_patch_intersection()`, `AgentTraceInsert<'_>`/`insert_agent_trace()`, `InsertMessageInsert`/`insert_message()` plus `insert_messages()` for parent message inserts with duplicate `(session_id, message_id)` writes ignored, `InsertPartInsert`/`insert_part()` plus `insert_parts()` for append-only part text writes, and `recent_diff_trace_patches(cutoff_time_ms, end_time_ms)` for inclusive chronological `diff_traces` reads that dispatch on `payload_type`, parse valid raw patch or structured payload rows, and return skipped malformed-row reports. `cli/src/services/agent_trace_db/lifecycle.rs` registers Agent Trace DB setup/doctor lifecycle behavior: setup creates/reuses and registers checkout identity, resolves `/sce/agent-trace-{checkout_id}.db`, initializes it with `AgentTraceDb::open_at(path)`, and records `database_path`; active hook runtime still resolves checkout identity and lazily creates or upgrades the per-checkout DB when setup has not run or schema metadata is incomplete. - `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/mod.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/**`, and focused internal support seams for install-flow vs prompt-flow logic; `cli/src/services/setup/command.rs` owns the `SetupCommand` payload used by the static `RuntimeCommand` enum and executes against any context implementing repo-root scoping. Its install engine/orchestrator stages embedded files and uses a unified remove-and-replace policy (removing existing targets before swapping staged content, with deterministic recovery guidance on swap failure and no backup artifact creation), and formats deterministic completion messaging; required-hook install orchestration (`install_required_git_hooks`) follows the same remove-and-replace policy (removing existing hooks before swapping staged content, with deterministic recovery guidance on swap failure). The setup command derives a repo-root-scoped context before aggregating static lifecycle provider `setup` dispatch across providers (config → local_db → auth_db → agent_trace_db → hooks when requested), so setup providers consume only repo-root access from the scoped context. - `cli/src/services/setup/mod.rs` keeps those responsibilities inside one file for now, but the current ownership split is explicit: the inline `install` module owns repository-path normalization, staging/swap install behavior, required-hook installation, and filesystem safety guards, while the inline `prompt` module owns interactive target selection and prompt styling. @@ -124,9 +124,8 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/doctor/mod.rs` owns the current doctor request/report surface while focused submodules (`doctor/inspect.rs`, `doctor/render.rs`, `doctor/fixes.rs`, `doctor/types.rs`) split report fact collection, rendering, manual fix reporting, and doctor-owned domain types into smaller seams; `cli/src/services/doctor/command.rs` owns the `DoctorCommand` payload used by the static `RuntimeCommand` enum and executes against any context implementing repo-root scoping. Runtime doctor execution resolves a repository root, derives a scoped context, requests the shared static lifecycle provider catalog with hooks included for service-owned `diagnose` and `fix` behavior, adapts lifecycle-owned health/fix records into doctor-owned problem/fix records, and then renders stable text/JSON problem records with category/severity/fixability/remediation fields plus deterministic fix-result reporting in fix mode. Checkout DB discovery no longer lives in `doctor`; it moved to the `trace` group (`sce trace db list`) in `cli/src/services/trace/`. Report fact collection preserves environment/repository/hook/integration display data and adds checkout identity plus per-checkout Agent Trace DB status when a checkout ID exists, while service-owned lifecycle providers own config validation, local DB and Agent Trace DB readiness/bootstrap, and hook rollout diagnosis/repair. - `cli/src/services/version/mod.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/version/command.rs` owns the `VersionCommand` payload used by the static `RuntimeCommand` enum. - `cli/src/services/completion/mod.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/completion/command.rs` owns the `CompletionCommand` payload used by the static `RuntimeCommand` enum. -- `cli/src/services/hooks/mod.rs` defines the current 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 enabled-by-default attribution-hooks config/env control is not opted out, `SCE_DISABLED` is false, and the staged-diff AI-overlap preflight confirms AI/editor evidence (`StagedDiffAiOverlapResult::Overlap`); the preflight is wired into `run_commit_msg_subcommand_in_repo` and logs `sce.hooks.commit_msg.ai_overlap_error` on error paths; `cli/src/services/hooks/command.rs` owns the `HooksCommand` payload used by the static `RuntimeCommand` enum. In the current attribution-only baseline, `pre-commit` and `post-rewrite` are deterministic no-op surfaces; `post-commit` requires validated `--remote-url`, threads that URL through the Agent Trace flow, prints it to stderr, and remains an active intersection + Agent Trace persistence entrypoint; `diff-trace` performs STDIN JSON intake, validates required fields, rejects values that cannot fit AgentTraceDb signed `time_ms` storage, resolves missing/nullable attribution from existing `session_models` rows when available, and inserts parsed payload fields into AgentTraceDb without writing parsed-payload artifacts under `context/tmp`; `conversation-trace` persists normalized message/part rows. `session-model` is no longer a supported hook command. -- Generated Claude settings no longer register `SessionStart` for Agent Trace model attribution. `session_models` DB APIs and diff-trace fallback lookup remain temporarily in place for later cleanup tasks, but no active route writes new session-model rows. -- Diff-trace attribution resolution in `cli/src/services/hooks/mod.rs` looks up `session_models` when `model_id` or `tool_version` is missing/nullable, fills only missing fields from the stored row when available, preserves direct payload precedence, and continues persistence with `None` for unresolved attribution. +- `cli/src/services/hooks/mod.rs` defines the current 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 enabled-by-default attribution-hooks config/env control is not opted out, `SCE_DISABLED` is false, and the staged-diff AI-overlap preflight confirms AI/editor evidence (`StagedDiffAiOverlapResult::Overlap`); the preflight is wired into `run_commit_msg_subcommand_in_repo` and logs `sce.hooks.commit_msg.ai_overlap_error` on error paths; `cli/src/services/hooks/command.rs` owns the `HooksCommand` payload used by the static `RuntimeCommand` enum. In the current attribution-only baseline, `pre-commit` and `post-rewrite` are deterministic no-op surfaces; `post-commit` requires validated `--remote-url`, threads that URL through the Agent Trace flow, prints it to stderr, and remains an active intersection + Agent Trace persistence entrypoint (captures current commit patch, queries recent `diff_traces` from the bounded past-7-days window, combines valid patches via `patch::combine_patches`, intersects with post-commit patch via `patch::intersect_patches`, persists result to `post_commit_patch_intersections`, then persists built Agent Trace payloads with range-level `content_hash` values to `agent_traces` in AgentTraceDb without post-commit file artifacts); `diff-trace` performs STDIN JSON intake, validates required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent/`null` → `None`), required nullable/non-empty `tool_version` (present and either `null` or non-empty string) plus required `u64` `time` (Unix epoch milliseconds), rejects values that cannot fit AgentTraceDb signed `time_ms` storage, writes one collision-safe parsed-payload `context/tmp/-000000-diff-trace.json` artifact, and inserts the parsed payload fields into AgentTraceDb; `session-model` performs STDIN JSON intake, validat... (line truncated to 2000 chars) +- Generated Claude settings no longer register `SessionStart` for Agent Trace model attribution, and `sce hooks session-model` is no longer a supported hook command. The `session_models` table/API and diff-trace fallback lookup were removed in T02 of the `remove-session-models-direct-claude-model-id` plan; `diff-trace` now uses only direct payload `model_id` and `tool_version` values. - `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. - No user-invocable `sce sync` command is wired in the current runtime; local DB bootstrap and setup-time per-checkout Agent Trace DB initialization flow through lifecycle providers aggregated by setup, while checkout/global DB health/repair flow through the doctor surface and checkout DB discovery flows through the `trace` group (`sce trace db list`). - `cli/src/services/patch.rs` defines the standalone patch domain model (`ParsedPatch`, `PatchFileChange`, `FileChangeKind`, `PatchHunk`, `TouchedLine`, `TouchedLineKind`) for in-memory parsed unified-diff representation, capturing only touched lines (added/removed) plus minimal per-file/per-hunk metadata while excluding non-hunk headers and unchanged context lines. All types are `serde`-serializable/deserializable with `snake_case` JSON field naming. The module also provides `parse_patch`, a public parser function that converts raw unified-diff text (both `Index:` SVN-style and `diff --git` git-style formats) into `ParsedPatch` structs, with `ParseError` for actionable malformed-input diagnostics. Storage-agnostic JSON load helpers (`load_patch_from_json` for string input, `load_patch_from_json_bytes` for byte input) reconstruct `ParsedPatch` from serialized JSON content with `PatchLoadError` for actionable deserialization diagnostics. Its patch-set operations now include deterministic ordered combination plus target-shaped intersection that prefers exact touched-line matches and falls back to historical `kind`+`content` matching when incremental diffs and canonical post-commit diffs have drifted line numbers; `parse_patch`, `combine_patches`, and `intersect_patches` are consumed by the active post-commit hook runtime. diff --git a/context/glossary.md b/context/glossary.md index 333ffb5f..b7a1abaa 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -38,16 +38,17 @@ - `encrypted Turso adapter`: Generic adapter seam in `cli/src/services/db/mod.rs` exposed as `EncryptedTursoDb`, structurally parallel to `TursoDb` (connection, tokio runtime bridge, spec typing). Its constructor resolves the encryption key via `encryption_key::get_or_create_encryption_key(&db_path, db_name)`, which derives a Turso-compatible 64-character hex key from non-empty `SCE_AUTH_DB_ENCRYPTION_KEY` env-secret text before falling back to OS credential-store keyring get-or-create behavior; credential-store default registration is guarded by stable `OnceLock` plus an atomic in-progress flag so errors or panics leave initialization retryable without mutex poisoning. The adapter enables Turso local encryption with strict `aegis256` cipher selection through `turso::EncryptionOpts`, wraps encrypted local open/connect in the default DB connection-open retry policy, and runs embedded migrations after retry has produced a connection; the adapter also exposes retry-backed synchronous `execute`, `query`, `query_map`, and `run_migrations` helpers with `__sce_migrations` tracking parity. - `auth DB adapter`: Module in `cli/src/services/auth_db/mod.rs` that defines `AuthDbSpec` and exposes `AuthDb` as an `EncryptedTursoDb` alias. It resolves the canonical `/sce/auth.db` path with `auth_db_path()`, keeps encryption mandatory with `SCE_AUTH_DB_ENCRYPTION_KEY` env-secret precedence before OS keyring fallback and no plaintext mode, and embeds ordered auth migrations where baseline SQL creates `auth_credentials` without `user_id`, with `updated_at`, and a trigger that auto-refreshes `updated_at` on row updates. Auth runtime token-storage is now wired through `cli/src/services/token_storage.rs`, which persists tokens via the `auth_credentials` table in the encrypted auth DB instead of a JSON file. - `AuthDbLifecycle`: Lifecycle provider in `cli/src/services/auth_db/lifecycle.rs` that implements `ServiceLifecycle` for encrypted auth DB setup/doctor integration. `diagnose` collects auth DB path health problems, `fix` bootstraps missing auth DB parent directory, and `setup` calls `AuthDb::new()`. Registered as `LifecycleProviderId::AuthDb` in the shared lifecycle catalog. -- `agent trace DB adapter`: Module in `cli/src/services/agent_trace_db/mod.rs` that defines `AgentTraceDbSpec`, exposes `AgentTraceDb` as a `TursoDb` alias, keeps `/sce/agent-trace.db` as the legacy/global fallback path, supports explicit per-checkout DB paths through `open_at(path)` and `open_for_hooks_without_migrations_at(path)`, embeds an ordered split fresh-start baseline migration set (`001..016`) covering `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `messages`, `parts`, `session_models`, and `diff_traces.payload_type` plus indexes and triggers; provides typed parameterized insert/upsert helpers for diff traces with `payload_type` discriminator, post-commit intersection rows, built agent-trace rows, message/part batch inserts, and session model attribution; exposes chronological recent `diff_traces` query/parse support with malformed-row skip accounting; has `AgentTraceDbLifecycle` for setup/doctor integration including setup-time per-checkout DB initialization; and is accessed by active Agent Trace hooks through checkout-scoped lazy DB resolution when setup has not prepared the DB. -- `structured patch service`: Pure synchronous Rust service in `cli/src/services/structured_patch.rs` that derives supported structured editor hook payloads into canonical `ParsedPatch` values. The current implemented source is Claude `PostToolUse` payloads for `Write` structured-update hunks, `Write` `tool_input.content` create fallback, and `Edit` structured patches; wired into `sce hooks diff-trace` for Claude payload classification at intake (T04) and into `AgentTraceDb::recent_diff_trace_patches` for post-commit structured payload parsing dispatch at read time (T05). +- `agent trace DB adapter`: Module in `cli/src/services/agent_trace_db/mod.rs` that defines `AgentTraceDbSpec`, exposes `AgentTraceDb` as a `TursoDb` alias, keeps `/sce/agent-trace.db` as the legacy/global fallback path, supports explicit per-checkout DB paths through `open_at(path)` and `open_for_hooks_without_migrations_at(path)`, embeds an ordered split fresh-start baseline migration set (`001..015`; retired `015_create_session_models` is replaced by active `015_add_diff_traces_payload_type`) covering `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `messages`, `parts`, and `diff_traces.payload_type` plus indexes and triggers; provides typed parameterized insert/upsert helpers for diff traces with `payload_type` discriminator, post-commit intersection rows, built agent-trace rows, and message/part batch inserts; exposes chronological recent `diff_traces` query/parse support with malformed-row skip accounting; has `AgentTraceDbLifecycle` for setup/doctor integration including setup-time per-checkout DB initialization; and is accessed by active Agent Trace hooks through checkout-scoped lazy DB resolution when setup has not prepared the DB. +- `structured patch service`: Pure synchronous Rust service in `cli/src/services/structured_patch.rs` that derives supported structured editor hook payloads into canonical `ParsedPatch` values. The current implemented source is Claude `PostToolUse` payloads for `Write` creates and `Edit` structured patches; wired into `sce hooks diff-trace` for Claude payload classification at intake (T04) and into `AgentTraceDb::recent_diff_trace_patches` for post-commit structured payload parsing dispatch at read time (T05). - `Agent Trace SCE metadata`: Implementation-owned top-level metadata emitted by `build_agent_trace(...)` as `metadata.sce.version`; the value is sourced from the compiled `sce` CLI package version via `env!("CARGO_PKG_VERSION")`, is schema-validated with the rest of the payload, and is persisted in AgentTraceDb `agent_traces.trace_json` without changing the top-level Agent Trace payload/schema `version`. - `Agent Trace range content_hash`: Per-range `content_hash` emitted by `build_agent_trace(...)` inside every `ranges[]` entry as `murmur3:`, computed from the touched-line kind/content of the `post_commit_patch` or embedded-patch hunk used to emit that range while excluding positions, paths, metadata, and database IDs. - `DiffTraceInsert`: Insert payload in `cli/src/services/agent_trace_db/mod.rs` carrying `time_ms`, `session_id`, `patch`, `model_id`, `tool_name`, nullable `tool_version`, and `payload_type` for parameterized writes to the `diff_traces` table; `payload_type` uses `PAYLOAD_TYPE_PATCH` (`"patch"`) for `OpenCode` unified-diff payloads and `PAYLOAD_TYPE_STRUCTURED` (`"structured"`) for `Claude` `PostToolUse` structured payloads. -- `diff_traces payload_type discriminator`: `TEXT NOT NULL DEFAULT 'patch'` column in `diff_traces` added by migration `016_add_diff_traces_payload_type`; values are `PAYLOAD_TYPE_PATCH` (`"patch"`) for `OpenCode` unified-diff source payloads and `PAYLOAD_TYPE_STRUCTURED` (`"structured"`) for `Claude` `PostToolUse` structured source payloads; existing rows default to `"patch"` for backward compatibility. +- `diff_traces payload_type discriminator`: `TEXT NOT NULL DEFAULT 'patch'` column in `diff_traces` added by migration `015_add_diff_traces_payload_type`; values are `PAYLOAD_TYPE_PATCH` (`"patch"`) for `OpenCode` unified-diff source payloads and `PAYLOAD_TYPE_STRUCTURED` (`"structured"`) for `Claude` `PostToolUse` structured source payloads; existing rows default to `"patch"` for backward compatibility. - `DbSpec`: Service-specific database metadata trait in `cli/src/services/db/mod.rs` that supplies a diagnostic database name, canonical path resolver, ordered embedded migration list, and config-file lookup key (`db_config_key()`) for `TursoDb`. - `TursoDb`: Generic unencrypted Turso database adapter in `cli/src/services/db/mod.rs`; owns parent-directory creation and Turso local open/connect flow wrapped in config-driven connection-open retry, then delegates synchronous `execute()`/`query()`/`query_map()` wrappers with config-driven query retry and migration execution through the shared internal `TursoConnectionCore` for a `DbSpec` implementation. - `TursoConnectionCore`: Internal shared operation core in `cli/src/services/db/mod.rs` used by both `TursoDb` and `EncryptedTursoDb`; owns the Turso connection and tokio current-thread runtime bridging used by the public adapter methods; generic embedded migration execution with per-database `__sce_migrations` metadata is delegated to `run_embedded_migrations` helpers. - `no-migration DB open path`: `TursoDb::open_without_migrations()` / `TursoDb::open_without_migrations_at(path)` plus the Agent Trace-specific `AgentTraceDb::open_for_hooks_without_migrations()` / `open_for_hooks_without_migrations_at(path)` seams; opens/connects a local Turso database with parent-directory creation and configured connection-open retry but does not create `__sce_migrations` or run embedded schema migrations. Active Agent Trace hook callers first try the explicit per-checkout no-migration path and then fall back to migration-running initialization when readiness fails. +- `retired migration IDs`: Method on `DbSpec` (`retired_migration_ids()`) that returns migration IDs that exist in upgraded databases but are no longer part of the fresh-schema migration list. The `migration_metadata_problems` check excludes retired IDs from the "unexpected migrations" warning, allowing existing databases to retain the unused table without triggering schema-readiness errors. - `TursoDb migration readiness check`: Public methods on `TursoDb` in `cli/src/services/db/mod.rs` for non-mutating schema-readiness verification: `migration_metadata_problems(&self) -> Result>` queries `__sce_migrations` metadata and compares applied IDs against `M::migrations()`, returning problems (missing table, incomplete migrations, unexpected migrations) or an empty list when ready; `ensure_schema_ready(&self, setup_guidance: &str) -> Result<()>` calls `migration_metadata_problems()` and bails with a formatted error including `M::db_name()` and the caller-provided guidance string when problems are found. `AgentTraceDb::ensure_schema_ready_for_hooks()` delegates to `TursoDb::ensure_schema_ready()` with the Agent Trace–specific `AGENT_TRACE_SCHEMA_SETUP_GUIDANCE` constant. - `database_retry config namespace`: Nested config namespace under `policies.database_retry` in `sce/config.json`, authored in `config/pkl/base/sce-config-schema.pkl` and parsed/resolved in `cli/src/services/config/mod.rs`. Supports per-database overrides (`local_db`, `agent_trace_db`, `auth_db`) each with optional `connection_open` and `query` objects containing `max_attempts`, `timeout_ms`, `initial_backoff_ms`, `max_backoff_ms`. Validated against JSON Schema at config load and surfaced in `sce config show`/`validate`. Wired into DB adapter constructors and operation methods via config-aware retry resolution with fallback to hardcoded defaults. - `DatabaseRetryConfig`: Rust type in `cli/src/services/config/mod.rs` holding parsed and validated per-database retry policy overrides (`local_db`/`agent_trace_db`/`auth_db`, each `Option`) from the `policies.database_retry` config namespace. Initialized at app startup via `DATABASE_RETRY_CONFIG` `OnceLock` and consumed by config-aware retry resolution in DB adapters. @@ -121,7 +122,7 @@ - `auth config baked default`: Optional key-declared fallback in `cli/src/services/config/mod.rs` (with schema/parsing in `schema.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/mod.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 a unified remove-and-replace policy that removes existing targets before swapping staged content. - `setup remove-and-replace`: Replacement choreography in `cli/src/services/setup/mod.rs` where existing install targets are removed before staged content is promoted; on swap failure, the engine cleans temporary staging paths and returns deterministic recovery guidance (recover from version control). No backup artifacts are created. -- `hooks command routing contract`: Current hook command parser/dispatcher plus runtime wiring in `cli/src/services/hooks/mod.rs` (`HookSubcommand`, `run_hooks_subcommand`) supports `pre-commit`, `commit-msg `, `post-commit`, `post-rewrite `, `diff-trace`, and `conversation-trace` with deterministic invocation validation/usage errors; `session-model` is no longer supported. `commit-msg` is the only active attribution path behind the attribution hooks gate and staged-diff AI-overlap preflight; `pre-commit`/`post-rewrite` are no-ops; `post-commit` persists intersections and built Agent Trace payloads; `diff-trace` inserts AgentTraceDb rows with temporary fallback to existing `session_models` rows for missing/nullable attribution and without parsed `context/tmp` artifact writes; `conversation-trace` is the active message/part intake path. +- `hooks command routing contract`: Current hook command parser/dispatcher plus runtime wiring in `cli/src/services/hooks/mod.rs` (`HookSubcommand`, `run_hooks_subcommand`) supports `pre-commit`, `commit-msg `, `post-commit`, `post-rewrite `, `diff-trace`, and `conversation-trace` with deterministic invocation validation/usage errors; `session-model` is no longer supported. `commit-msg` is the only active attribution path behind the attribution hooks gate and staged-diff AI-overlap preflight; `pre-commit`/`post-rewrite` are no-ops; `post-commit` persists intersections and built Agent Trace payloads; `diff-trace` persists parsed payload artifacts and AgentTraceDb rows using only direct `model_id` and `tool_version` values (session-model fallback was removed in the `remove-session-models-direct-claude-model-id` plan). - `Claude raw hook capture (removed)`: Former hidden/internal `sce hooks claude-capture ` intake path removed in T05 of the `claude-typescript-model-cache-remove-rust-capture` plan. Rust now exposes `diff-trace` and `conversation-trace` intakes for active Claude/OpenCode editor runtimes; `session-model` is also removed from the supported hook command surface. The removed route previously wrote pretty-printed JSON artifacts under `context/tmp/claude/` without AgentTraceDb writes. See `context/sce/claude-raw-hook-capture.md`. - `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. @@ -140,8 +141,8 @@ - `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/mod.rs` (`apply_commit_msg_coauthor_policy`) that applies exactly one canonical trailer (`Co-authored-by: SCE `) only when attribution hooks are enabled, SCE is not disabled, and the staged-diff AI-overlap preflight confirms AI/editor evidence (`StagedDiffAiOverlapResult::Overlap`); `NoOverlap` and `Error` both suppress the trailer, with `Error` logged via `sce.hooks.commit_msg.ai_overlap_error`; duplicate canonical trailers are deduped idempotently. - `local DB migration contract`: `cli/src/services/local_db/mod.rs` delegates migration execution to `TursoDb` through the `DbSpec::migrations()` contract. The current `LocalDbSpec` migration list is empty, so `LocalDb::new()` opens/creates the canonical local DB without creating local tables. -- `hook no-op baseline`: Current `cli/src/services/hooks/mod.rs` runtime posture where `pre-commit` and `post-rewrite` return deterministic no-op status text, `commit-msg` is a gated mutating path, `post-commit` persists intersections and built Agent Trace payloads without post-commit file artifacts, `diff-trace` validates STDIN payloads, temporarily fills missing/nullable attribution from existing `session_models` rows when available, and inserts AgentTraceDb rows without parsed `context/tmp` artifact writes, and `conversation-trace` is the active message/part intake path. `session-model` is no longer a supported hook route. -- `sce doctor` operator-health contract: `cli/src/services/doctor/mod.rs` is the stable doctor entrypoint, with focused `doctor/{inspect,render,fixes,types}.rs` submodules implementing 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 checkout/database records and fix-result records, the runtime validates state-root resolution, global and repo-local `sce/config.json` readability/schema health, local DB and checkout/global Agent Trace 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. Checkout DB discovery no longer lives in `doctor`; it moved to the `trace` group (`sce trace db list`). +- `hook no-op baseline`: Current `cli/src/services/hooks/mod.rs` runtime posture where `pre-commit` and `post-rewrite` return deterministic no-op status text, `commit-msg` is a gated mutating path, `post-commit` persists intersections and built Agent Trace payloads without post-commit file artifacts, `diff-trace` validates STDIN payloads, uses only direct payload `model_id` and `tool_version` (session-model fallback removed), writes collision-safe parsed-payload artifacts, and inserts AgentTraceDb rows, and `conversation-trace` is the active message/part intake path. `session-model` is no longer a supported hook route. +- `sce doctor` operator-health contract: `cli/src/services/doctor/mod.rs` is the stable doctor entrypoint, with focused `doctor/{inspect,render,fixes,types}.rs` submodules implementing the current approved operator-health surface in `context/sce/agent-trace-hook-doctor.md`: `sce doctor --fix` selects repair intent, `sce doctor dbs` lists registered checkouts, help/output expose deterministic doctor mode/action, JSON includes stable problem taxonomy/fixability fields plus checkout/database records and fix-result records, the runtime validates state-root resolution, global and repo-local `sce/config.json` readability/schema health, local DB and checkout/global Agent Trace 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 uses the approved sectioned layout (`Environment`, `Configuration` with checkout identity + Agent Trace checkout DB rows when available, `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 reuses canonical setup hook installation for missing/stale/non-executable required hooks and missing hooks directories and can bootstrap canonical missing SCE-owned DB parent directories. - `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 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 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. diff --git a/context/overview.md b/context/overview.md index 853756db..805c7b87 100644 --- a/context/overview.md +++ b/context/overview.md @@ -34,7 +34,7 @@ The shared default path service in `cli/src/services/default_paths.rs` is now th The Rust CLI also centralizes SCE-owned web URI construction in `cli/src/services/agent_trace.rs`, with `SCE_WEB_BASE_URL` as the single Rust owner for `https://sce.crocoder.dev` and helpers consumed by Agent Trace conversation URLs, Agent Trace persisted trace URLs, Agent Trace session URLs, and setup-created repo-local config schema URLs. The same config resolver now also owns the attribution-hooks gate used by local hook runtime: opt-out env `SCE_ATTRIBUTION_HOOKS_DISABLED` overrides `policies.attribution_hooks.enabled` with inverted semantics, and the gate defaults to enabled unless explicitly disabled. The config service split now includes `cli/src/services/config/resolver.rs` as the focused owner for config-file discovery, file-layer merging, env/flag/default precedence, auth-key resolution, observability resolution, attribution-hooks resolution, and default-discovered invalid-file degradation; `cli/src/services/config/mod.rs` remains the facade/rendering orchestration surface while preserving existing `services::config` imports. -Generated config now includes repo-local plugin assets for both profiles: `sce-bash-policy.ts` plus `sce-agent-trace.ts` are emitted under `config/.opencode/plugins/` and `config/automated/.opencode/plugins/`; the OpenCode agent-trace plugin extracts `{ sessionID, diff, time, model_id }` from user `message.updated` events with diffs, tracks per-session OpenCode client version from `session.created`/`session.updated`, and sends payloads to `sce hooks diff-trace` with `tool_name="opencode"` plus optional `tool_version`. Claude generated config now routes supported `PostToolUse Write|Edit|MultiEdit|NotebookEdit` events directly to `sce hooks diff-trace`; it no longer registers a `SessionStart` hook or calls `sce hooks session-model`. Rust handles extraction, validation, and AgentTraceDb persistence without a TypeScript intermediary, without parsed `context/tmp` diff-trace artifacts, and with temporary missing/nullable attribution fallback reads from existing `session_models` rows until that fallback is removed. Bash-policy delegates OpenCode and Claude enforcement to the Rust `sce policy bash` command. +Generated config now includes repo-local plugin assets for both profiles: `sce-bash-policy.ts` plus `sce-agent-trace.ts` are emitted under `config/.opencode/plugins/` and `config/automated/.opencode/plugins/`; the OpenCode agent-trace plugin extracts `{ sessionID, diff, time, model_id }` from user `message.updated` events with diffs, tracks per-session OpenCode client version from `session.created`/`session.updated`, and sends payloads to `sce hooks diff-trace` with `tool_name="opencode"` plus optional `tool_version`. Claude generated config now routes supported `PostToolUse Write|Edit|MultiEdit|NotebookEdit` events directly to `sce hooks diff-trace`; it no longer registers a `SessionStart` hook or calls `sce hooks session-model`. Rust handles extraction, validation, and persistence without a TypeScript intermediary; the former `config/.claude/plugins/sce-agent-trace.ts` Bun runtime was removed in T07 of the `claude-rust-diff-trace` plan. The Rust hook validates required fields, uses only direct payload `model_id` and `tool_version` (no longer resolves from `session_models`), and persists `model_id`, `tool_name`, and nullable `tool_version` into `diff_traces` through AgentTraceDb. Bash-policy now delegates OpenCode enforcement to the Rust `sce policy bash` command: the generated OpenCode plugin at `config/.opencode/plugins/sce-bash-policy.ts` (and `config/automated/.opencode/plugins/sce-bash-policy.ts`) is a thin wrapper that calls `sce policy bash --input normalized --output json` via `spawnSync` and throws on deny decisions; it no longer contains independent TypeScript policy logic. The former `bash-policy/runtime.ts` TypeScript runtime has been removed. Preset... Claude bash-policy enforcement is also generated through `.claude/settings.json` as a `PreToolUse` `Bash` command hook running `sce policy bash`, so Claude and OpenCode both delegate to the Rust policy evaluator without a Claude TypeScript runtime. Local database bootstrap is now owned by `LocalDbLifecycle::setup` and `AgentTraceDbLifecycle::setup` aggregated by the setup command. Agent Trace setup creates/reuses the current checkout ID and initializes the per-checkout `agent-trace-{checkout_id}.db` with embedded migrations; hook runtime still lazily creates or upgrades the per-checkout DB when setup has not run or schema metadata is incomplete. Doctor validates checkout/global DB paths/health and can bootstrap missing parent directories; checkout DB discovery now lives in the `trace` group (`sce trace db list`). Wiring a user-invocable `sce sync` command is deferred to `0.4.0`. The repository-root flake (`flake.nix`) now applies a Rust overlay-backed stable toolchain pinned to `1.95.0` (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. @@ -58,10 +58,10 @@ Context sync now uses an important-change gate: cross-cutting/policy/architectur 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. `/commit` is now split by profile: manual generated commands remain proposal-only and allow split guidance when staged changes mix unrelated goals, while the automated OpenCode `/commit` command generates exactly one commit message and runs `git commit` against the staged diff. The shared `sce-atomic-commit` contract also requires commit bodies to cite affected plan slug(s) and updated task ID(s) when staged changes include `context/plans/*.md`, and to stop for clarification instead of inventing those references when the staged plan diff is ambiguous. 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 the removed Agent Trace schema adaptation, payload building, retry replay, or rewrite handling paths 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, `SCE_DISABLED` is false, and the staged-diff AI-overlap preflight confirms AI/editor evidence (`StagedDiffAiOverlapResult::Overlap`); when the preflight returns `NoOverlap` or `Error` (including DB open failure, schema not ready, query error, staged diff read failure, or zero overlap), the trailer is not appended and errors are logged via `sce.hooks.commit_msg.ai_overlap_error`; `pre-commit` and `post-rewrite` remain deterministic no-op entrypoints; `post-commit` requires validated `--remote-url`, threads that URL through the Agent Trace flow, prints it to stderr, captures current commit patch, queries recent `diff_traces` from past 7 days (dispatching `patch` rows through existing unified-diff parsing and `structured` rows through `structured_patch::derive_claude_structured_patch` at read time), combines/intersects patches, persists intersection metadata to `post_commit_patch_intersections`, and persists the schema-validated built Agent Trace payload, including optional top-level `tool` metadata from recent diff-trace rows, top-level `metadata.sce.version` from the compiled `sce` CLI package version, and range-level `content_hash` values, to AgentTraceDb `agent_traces` (DB-only, no post-commit Agent Trace file artifact); `diff-trace` currently validates/persists required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent or `null` → `None`, present+non-empty → `Some`, present+empty → error), required nullable/non-empty `tool_version`, plus required `u64` millisecond `time`, resolves missing/nullable attribution from `session_models` by `tool_name` + `session_id` when available while direct payload values keep precedence, and continues with `None` for unresolved attribution, with non-lossy AgentTraceDb `time_ms` conversion and AgentTraceDb-only persistence without parsed-payload artifacts under `context/tmp`; and `session-model` performs STDIN intake for normalized model attribution upsert without raw artifact persistence, with Claude `SessionStart` extracting `model_id` from the raw event and best-effort filling missing `tool_version` from `claude --version`. -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 provides `LocalDb` as a thin `TursoDb` alias in `cli/src/services/local_db/mod.rs`; `LocalDbSpec` resolves the canonical local DB path from the shared default-path catalog and currently declares zero migrations. Shared Turso infrastructure lives in `cli/src/services/db/mod.rs`, where `DbSpec` and generic `TursoDb` support dual-mode operation — local mode via `turso::Builder::new_local()` when `SCE_SYNC_URL`+`SCE_SYNC_TOKEN` are absent, or sync (Turso Cloud) mode via `turso::sync::Builder::new_remote()` when both are set. It owns parent-directory creation, connection setup, tokio current-thread runtime bridging, synchronous `execute`/`query`/`query_map`, generic migration execution, sync operations (`push`/`pull`/`checkpoint`/`stats`) that are no-ops in local mode (sync is never triggered automatically from `execute()`), and shared DB lifecycle helpers for service-specific database wrappers. Auth DB persistence now has a thin encrypted wrapper in `cli/src/services/auth_db/mod.rs`: `AuthDb = EncryptedTursoDb` resolves `/sce/auth.db` and embeds ordered `auth_tokens` table/index migrations, with lifecycle registration wired through `AuthDbLifecycle` in `cli/src/services/auth_db/lifecycle.rs`; auth runtime token-storage is now wired through `token_storage.rs`, which persists tokens via the `auth_credentials` table instead of a JSON file. Agent Trace persistence now has its own `cli/src/services/agent_trace_db/mod.rs` wrapper, legacy global `/sce/agent-trace.db` fallback plus active per-checkout `/sce/agent-trace-{checkout_id}.db` hook runtime paths, a split fresh-start baseline migration set (`001..008`) covering `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, nullable `agent_traces.remote_url`, indexes (`idx_diff_traces_time_ms_id`, `idx_agent_traces_agent_trace_id`, `idx_agent_traces_remote_url`), and `session_models` keyed by `(tool_name, session_id)` without `AUTOINCREMENT`, plus `agent_traces.agent_trace_id` as `NOT NULL UNIQUE`; it also provides type... (line truncated to 2000 chars) -The hooks command surface now also supports concrete runtime subcommand routing (`pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `conversation-trace`) with deterministic argument/STDIN validation; `session-model` is no longer supported. Current runtime behavior keeps commit-msg attribution enabled by default unless explicitly opted out; `pre-commit`/`post-rewrite` remain deterministic no-ops; `post-commit` requires validated `--remote-url`, threads that URL into the Agent Trace flow, prints it to stderr, and remains the active bounded recent-diff-trace intersection path; `diff-trace` is the active intake path for parsed STDIN `{ sessionID, diff, time, model_id?, tool_name, tool_version }` payload persistence with temporary missing/nullable attribution fallback from existing `session_models` rows and without parsed-payload artifacts under `context/tmp`; and `conversation-trace` is the active message/part intake path. This behavior is documented in `context/sce/agent-trace-hooks-command-routing.md`. The removed `sce hooks claude-capture` raw capture route is documented in `context/sce/claude-raw-hook-capture.md` as a removed feature. +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, `SCE_DISABLED` is false, and the staged-diff AI-overlap preflight confirms AI/editor evidence (`StagedDiffAiOverlapResult::Overlap`); when the preflight returns `NoOverlap` or `Error` (including DB open failure, schema not ready, query error, staged diff read failure, or zero overlap), the trailer is not appended and errors are logged via `sce.hooks.commit_msg.ai_overlap_error`; `pre-commit` and `post-rewrite` remain deterministic no-op entrypoints; `post-commit` requires validated `--remote-url`, threads that URL through the Agent Trace flow, prints it to stderr, captures current commit patch, queries recent `diff_traces` from past 7 days (dispatching `patch` rows through existing unified-diff parsing and `structured` rows through `structured_patch::derive_claude_structured_patch` at read time), combines/intersects patches, persists intersection metadata to `post_commit_patch_intersections`, and persists the schema-validated built Agent Trace payload, including optional top-level `tool` metadata from recent diff-trace rows, top-level `metadata.sce.version` from the compiled `sce` CLI package version, and range-level `content_hash` values, to AgentTraceDb `agent_traces` (DB-only, no post-commit Agent Trace file artifact); `diff-trace` currently validates/persists required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent or `null` → `None`, present+non-empty → `Some`, present+empty → error), required nullable/non-empty `tool_version`, plus required `u64` millisecond `time`, uses only direct payload `model_id` and `tool_version` (no longer resolves from `session_models`), and continues with `None` for missing attribution, with non-lossy AgentTraceDb `time_ms` conversion and collision-safe timestamp+attempt artifact filenames; and `se... (line truncated to 2000 chars) +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 provides`LocalDb`as a thin`TursoDb`alias in`cli/src/services/local_db/mod.rs`; `LocalDbSpec`resolves the canonical local DB path from the shared default-path catalog and currently declares zero migrations. Shared Turso infrastructure lives in`cli/src/services/db/mod.rs`, where `DbSpec`and generic`TursoDb`support dual-mode operation — local mode via`turso::Builder::new_local()`when`SCE_SYNC_URL`+`SCE_SYNC_TOKEN`are absent, or sync (Turso Cloud) mode via`turso::sync::Builder::new_remote()`when both are set. It owns parent-directory creation, connection setup, tokio current-thread runtime bridging, synchronous`execute`/`query`/`query_map`, generic migration execution, sync operations (`push`/`pull`/`checkpoint`/`stats`) that are no-ops in local mode (sync is never triggered automatically from `execute()`), and shared DB lifecycle helpers for service-specific database wrappers. Auth DB persistence now has a thin encrypted wrapper in `cli/src/services/auth_db/mod.rs`: `AuthDb = EncryptedTursoDb`resolves`/sce/auth.db`and embeds ordered`auth_tokens`table/index migrations, with lifecycle registration wired through`AuthDbLifecycle`in`cli/src/services/auth_db/lifecycle.rs`; auth runtime token-storage is now wired through `token_storage.rs`, which persists tokens via the `auth_credentials`table instead of a JSON file. Agent Trace persistence now has its own`cli/src/services/agent_trace_db/mod.rs`wrapper, legacy global`/sce/agent-trace.db`fallback plus active per-checkout`/sce/agent-trace-{checkout_id}.db` hook runtime paths, a split fresh-start baseline migration set (`001..008`) covering `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, nullable `agent_traces.remote_url`, indexes (`idx_diff_traces_time_ms_id`, `idx_agent_traces_agent_trace_id`, `idx_agent_traces_remote_url`), and retired `session_models`migration keyed by`(tool_name... (line truncated to 2000 chars) +The hooks command surface now also supports concrete runtime subcommand routing (`pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `conversation-trace`) with deterministic argument/STDIN validation; `session-model` is no longer supported. Current runtime behavior keeps commit-msg attribution enabled by default unless explicitly opted out: the attribution gate enables canonical trailer insertion in `commit-msg` only when the staged-diff AI-overlap preflight confirms AI/editor evidence (no trailer is appended when the preflight finds no overlap or encounters any error); `pre-commit`/`post-rewrite` remain deterministic no-ops, `post-commit` requires validated `--remote-url`, threads that URL into the Agent Trace flow, prints it to stderr, and remains the active bounded recent-diff-trace intersection path, and `diff-trace` is the active intake path for parsed STDIN `{ sessionID, diff, time, model_id?, tool_name, tool_version }` payload persistence with optional `model_id`, required non-empty `tool_name`, required nullable/non-empty `tool_version`, direct `model_id` and `tool_version` values (no session-model fallback), required `u64` millisecond `time`, non-lossy AgentTraceDb `time_ms` conversion, and collision-safe timestamp+attempt artifact filenames. This behavior is documented in `context/sce/agent-trace-hooks-command-routing.md`. The removed `sce hooks claude-capture` raw capture route is documented in `context/sce/claude-raw-hook-capture.md` as a removed feature. 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`), and uses a unified remove-and-replace policy that removes existing hooks before swapping staged content with deterministic 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`); this behavior is documented in `context/sce/setup-githooks-cli-ux.md`. diff --git a/context/patterns.md b/context/patterns.md index 214c4bea..5037393e 100644 --- a/context/patterns.md +++ b/context/patterns.md @@ -133,16 +133,14 @@ - For cross-service CLI dependencies exposed through the borrowed `AppContext` view, prefer shared capability/accessor traits over one-off per-service abstractions; keep production wrappers thin over `std::fs` and `git` process execution until call-site migration tasks approve deeper service refactors, and keep command execution generic over the narrow accessors each command needs where practical. - 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 the current local-hook baseline, keep `pre-commit` and `post-rewrite` as deterministic no-op entrypoints; keep `post-commit` as the active bounded recent-diff-trace intersection entrypoint with validated `--remote-url` plumbed through Agent Trace flow and any direct diagnostics printed to stderr; keep `diff-trace` as an explicit STDIN intake path with deterministic required-field validation for `sessionID`, `diff`, `time`, `tool_name`, optional `model_id` (absent/`null` → `None`, resolved from existing `session_models` rows by `tool_name` + `session_id` when absent until the fallback-removal task lands), and `tool_version` (present and either `null` or non-empty string), non-lossy AgentTraceDb `time_ms` conversion, and AgentTraceDb insertion whose failure is logged and reflected in deterministic success text without creating a `context/tmp` artifact fallback; keep `conversation-trace` as the active message/part intake path. `session-model` is no longer a supported hook intake path. -- For diff-trace attribution persistence, preserve direct payload `model_id` and `tool_version` values, query `session_models` only when either attribution field is missing/nullable, fill missing fields from the stored row when available, and persist unresolved attribution as `NULL` in AgentTraceDb rather than skipping DB insertion. -- For the current local-hook baseline, keep `pre-commit` and `post-rewrite` as deterministic no-op entrypoints; keep `post-commit` as the active bounded recent-diff-trace intersection entrypoint with validated `--remote-url` plumbed through Agent Trace flow and any direct diagnostics printed to stderr; keep `diff-trace` as an explicit STDIN intake path with deterministic required-field validation for `sessionID`, `diff`, `time`, `tool_name`, optional `model_id` (absent/`null` → `None`, resolved from `session_models` by `tool_name` + `session_id` when absent), and `tool_version` (present and either `null` or non-empty string), non-lossy AgentTraceDb `time_ms` conversion, collision-safe `context/tmp/-000000-diff-trace.json` persistence using atomic create-new retry semantics, and valid-path AgentTraceDb insertion whose post-artifact DB failure is logged and reflected in success text while preserving the artifact fallback; keep `conversation-trace` as an explicit STDIN intake path for mixed-batch/raw-Claude message and part payloads with no raw artifact persistence; keep `session-model` as an explicit STDIN intake path for normalized model attribution upsert with no raw artifact persistence. -- For `diff-trace` hook intake, keep producer-facing failure behavior fail-open: STDIN read, parse/validation, attribution lookup, artifact persistence, and setup/persistence failures are logged with `sce.hooks.diff_trace.error` and converted into command success; preserve the existing valid-payload success text and the post-artifact AgentTraceDb write-warning success path. +- For the current local-hook baseline, keep `pre-commit` and `post-rewrite` as deterministic no-op entrypoints; keep `post-commit` as the active bounded recent-diff-trace intersection entrypoint with validated `--remote-url` plumbed through Agent Trace flow and any direct diagnostics printed to stderr; keep `diff-trace` as an explicit STDIN intake path with deterministic required-field validation for `sessionID`, `diff`, `time`, `tool_name`, optional `model_id` (absent/`null` → `None`), and `tool_version` (present and either `null` or non-empty string), non-lossy AgentTraceDb `time_ms` conversion, and AgentTraceDb insertion whose failure is logged and reflected in deterministic success text without creating a `context/tmp` artifact fallback; keep `conversation-trace` as the active message/part intake path. `session-model` is no longer a supported hook intake path. +- For `diff-trace` hook intake, keep producer-facing failure behavior fail-open: STDIN read, parse/validation, and setup/persistence failures are logged with `sce.hooks.diff_trace.error` and converted into command success; preserve the existing valid-payload success text and the AgentTraceDb write-warning success path. - For `conversation-trace` hook intake, keep producer-facing failure behavior fail-open: STDIN read, top-level parse/validation, unsupported raw Claude hook events, and AgentTraceDb setup/persistence failures are logged with `sce.hooks.conversation_trace.error` and converted into command success; preserve valid-payload mixed-batch accounting, skipped-item logging, and batch-insert warning behavior. -- For diff-trace attribution persistence, preserve direct payload `model_id` and `tool_version` values, query `session_models` only when either attribution field is missing/nullable, fill missing fields from the stored row when available, and persist unresolved attribution as `NULL` rather than skipping the artifact or DB row. +- For diff-trace attribution persistence, persist direct payload `model_id` and `tool_version` values as-is; missing attribution fields are stored as `NULL` in `diff_traces`. The former `session_models` fallback lookup was removed. - For commit-msg co-author policy seams, gate canonical trailer insertion on runtime controls (`SCE_DISABLED` plus the shared attribution-hooks enablement gate) plus the staged-diff AI-overlap evidence gate (`StagedDiffAiOverlapResult::Overlap` maps to `ai_contribution_present = true`; `NoOverlap` and `Error` both map to `false`), 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_DISABLED` opt-out env over `policies.attribution_hooks.enabled`, default `true`) so commit-msg attribution is enabled by default while explicit config `enabled = false` and truthy env opt-out still suppress it without adding hook-specific config parsing. - Do not assume conversation-trace retry/backfill/artifact persistence, retry replay, remap ingestion, or rewrite trace transformation are active in the current local-hook runtime; those paths are removed from or deferred beyond 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 route initialization through `LocalDb::new()`. As database services split, keep path/migration ownership in each `DbSpec`: `LocalDbSpec` owns the neutral local DB path with zero migrations, `AuthDbSpec` owns encrypted `/sce/auth.db` plus ordered auth migrations, `AgentTraceDbSpec` owns `/sce/agent-trace.db` plus ordered Agent Trace migrations for `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `session_models`, `messages`, and `parts` plus supporting indexes and triggers, and shared Turso mechanics plus migration metadata stay in `TursoDb` / `EncryptedTursoDb`. +- 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 route initialization through `LocalDb::new()`. As database services split, keep path/migration ownership in each `DbSpec`: `LocalDbSpec` owns the neutral local DB path with zero migrations, `AuthDbSpec` owns encrypted `/sce/auth.db` plus ordered auth migrations, `AgentTraceDbSpec` owns `/sce/agent-trace.db` plus ordered Agent Trace migrations for `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `messages`, and `parts` plus supporting indexes and triggers (migration `015_create_session_models` is retired from fresh schema), and shared Turso mechanics plus migration metadata stay in `TursoDb` / `EncryptedTursoDb`. - 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/claude-rust-diff-trace.md b/context/plans/claude-rust-diff-trace.md index 45e95350..fd7216fa 100644 --- a/context/plans/claude-rust-diff-trace.md +++ b/context/plans/claude-rust-diff-trace.md @@ -67,7 +67,7 @@ Planning interpretation: the last nine commits created and then refined a Claude - Boundaries (in/out of scope): In - AgentTraceDb migration(s), typed insert/query structs, constants/enums for `patch` and `structured` discriminator values, backward-compatible handling for existing `patch` data if required by current migrations/tests. Out - Claude hook intake, post-commit structured parsing, generated settings changes, OpenCode plugin changes. - Done when: New diff-trace inserts can persist payload text with an explicit type; existing OpenCode patch payloads are stored/read as `patch`; recent-diff-trace query code exposes enough typed information for later parsing into `ParsedPatch`. - Verification notes (commands or checks): Run focused AgentTraceDb tests through Nix, for example `nix develop -c sh -c 'cd cli && cargo test agent_trace_db'`; include migration/backward-compatibility test coverage where the current DB test harness supports it. - - Completion evidence (2026-06-10): Added migration `016_add_diff_traces_payload_type.sql` adding `payload_type TEXT NOT NULL DEFAULT 'patch'` to `diff_traces`. Added `PAYLOAD_TYPE_PATCH` and `PAYLOAD_TYPE_STRUCTURED` constants to `agent_trace_db/mod.rs`. Updated `DiffTraceInsert`, `DiffTracePatchRow`, `ParsedDiffTracePatch` to carry `payload_type`. Updated `INSERT_DIFF_TRACE_SQL` and `SELECT_RECENT_DIFF_TRACE_PATCHES_SQL` to include `payload_type`. Updated `insert_diff_trace_with`, `diff_trace_patch_row_from_turso`, and `parse_recent_diff_trace_patch_rows` for the new column. Updated `hooks/mod.rs` to pass `PAYLOAD_TYPE_PATCH` for existing OpenCode diff-trace flow. Updated baseline migration test to expect 9 migrations. Added `payload_type` assertion to existing diff-trace query test. `nix flake check` passed. `nix run .#pkl-check-generated` passed. + - Completion evidence (2026-06-10): Added migration `015_add_diff_traces_payload_type.sql` adding `payload_type TEXT NOT NULL DEFAULT 'patch'` to `diff_traces`. Added `PAYLOAD_TYPE_PATCH` and `PAYLOAD_TYPE_STRUCTURED` constants to `agent_trace_db/mod.rs`. Updated `DiffTraceInsert`, `DiffTracePatchRow`, `ParsedDiffTracePatch` to carry `payload_type`. Updated `INSERT_DIFF_TRACE_SQL` and `SELECT_RECENT_DIFF_TRACE_PATCHES_SQL` to include `payload_type`. Updated `insert_diff_trace_with`, `diff_trace_patch_row_from_turso`, and `parse_recent_diff_trace_patch_rows` for the new column. Updated `hooks/mod.rs` to pass `PAYLOAD_TYPE_PATCH` for existing OpenCode diff-trace flow. Updated baseline migration test to expect 9 migrations. Added `payload_type` assertion to existing diff-trace query test. `nix flake check` passed. `nix run .#pkl-check-generated` passed. - [x] T04: `Persist Claude structured diff-trace source payloads` (status:done) - Task ID: T04 @@ -148,7 +148,7 @@ None found. `PAYLOAD_TYPE_STRUCTURED` is properly active (no `#[allow(dead_code) - [x] **Generated Claude settings call `sce hooks` directly**: `config/.claude/settings.json` uses `"sce"` command with `"hooks" "session-model"` and `"hooks" "diff-trace"` args; no Bun or `.claude/plugins/sce-agent-trace.ts` references. File verified on disk. -- [x] **AgentTraceDb typed payload storage**: `PAYLOAD_TYPE_PATCH` (`"patch"`) and `PAYLOAD_TYPE_STRUCTURED` (`"structured"`) constants at `cli/src/services/agent_trace_db/mod.rs:73-74`; migration `016_add_diff_traces_payload_type.sql` added `payload_type TEXT NOT NULL DEFAULT 'patch'` column. Code review confirmed. +- [x] **AgentTraceDb typed payload storage**: `PAYLOAD_TYPE_PATCH` (`"patch"`) and `PAYLOAD_TYPE_STRUCTURED` (`"structured"`) constants at `cli/src/services/agent_trace_db/mod.rs:73-74`; migration `015_add_diff_traces_payload_type.sql` added `payload_type TEXT NOT NULL DEFAULT 'patch'` column. Code review confirmed. - [x] **Claude structured payloads stored as raw JSON, derived at post-commit read time**: Intake path (`cli/src/services/hooks/mod.rs:352`) stores `stdin_payload.to_string()` with `payload_type="structured"`. Post-commit read path (`cli/src/services/agent_trace_db/mod.rs:437-443`) dispatches `"structured"` rows through `derive_claude_structured_patch` at read time. Code review confirmed. diff --git a/context/plans/remove-session-models-direct-claude-model-id.md b/context/plans/remove-session-models-direct-claude-model-id.md index 7763a7df..bde6715a 100644 --- a/context/plans/remove-session-models-direct-claude-model-id.md +++ b/context/plans/remove-session-models-direct-claude-model-id.md @@ -37,14 +37,30 @@ This intentionally accepts the known Claude limitation that some events, especia - Verification notes (commands or checks): `nix run .#pkl-check-generated`; targeted parser/help tests if existing; generated config diff inspection for removed session-model hook commands. - Completion evidence (2026-06-30 rerun): Removed the `session-model` hooks subcommand from clap parsing/runtime conversion and `HookSubcommand` dispatch, deleted the session-model hook runtime/parser from `hooks/mod.rs`, and removed generated Claude `SessionStart -> sce hooks session-model` registration from canonical Pkl plus regenerated `config/.claude/settings.json`. Left Agent Trace DB `session_models` API/schema and diff-trace fallback in place for later tasks; temporarily marked now-unused session-model DB upsert APIs with `#[allow(dead_code)]` to preserve T02 scope. `nix run .#pkl-check-generated` passed (`Generated outputs are up to date.`). `nix flake check` passed (`all checks passed`). -- [ ] T02: `Remove session_models database API and schema dependency` (status:todo) +- [x] T02: `Remove session_models database API and schema dependency` (status:done) - Task ID: T02 - Goal: Remove Rust DB adapter support for `session_models` and ensure fresh Agent Trace DB schema no longer creates or requires that table. - Boundaries (in/out of scope): - In — Remove session-model SQL constants, structs, upsert/lookup methods, row mappers, and tests; remove or supersede migration `015_create_session_models.sql` from the fresh schema path using the repository's migration policy; update migration readiness expectations accordingly. - Out — Diff-trace parser behavior; generated hook config; unrelated message/part tables. - Done when: `agent_trace_db` compiles without any session-model API; fresh Agent Trace DB creation no longer includes `session_models`; migration/readiness tests reflect the new schema contract. - - Verification notes (commands or checks): targeted Agent Trace DB tests if available; `nix develop -c sh -c 'cd cli && cargo test agent_trace_db'` if a narrow module test is practical; otherwise `nix flake check` in final validation. + - Verification notes (commands or checks): `nix flake check` (all checks passed). + - Completion evidence (2026-06-30): + - Deleted `cli/migrations/agent-trace/015_create_session_models.sql` from fresh schema path. + - Kept `diff_traces.payload_type` in the fresh schema path as `cli/migrations/agent-trace/015_add_diff_traces_payload_type.sql`; existing development databases with a previous `016_add_diff_traces_payload_type` metadata ID may be deleted/recreated during this migration renumbering. + - Added `DbSpec::retired_migration_ids()` to `cli/src/services/db/mod.rs` so existing upgraded DBs with applied `015_create_session_models` are not flagged as having unexpected migrations. + - Removed `SessionModelUpsert`, `SessionModelAttribution`, `UPSERT_SESSION_MODEL_SQL`, `SELECT_SESSION_MODEL_SQL`, `upsert_session_model`, `session_model_by_tool_and_session`, `upsert_session_model_with`, `session_model_by_tool_and_session_with`, and `session_model_attribution_from_turso` from `cli/src/services/agent_trace_db/mod.rs`. + - Added `AgentTraceDbSpec::retired_migration_ids()` returning `&["015_create_session_models"]` (also added to both test spec impls). + - Updated baseline migration test: `assert!(!sqlite_object_exists(&db, "table", "session_models"))` confirms fresh DB no longer creates the table. + - Removed hook-side session-model fallback attribution from `cli/src/services/hooks/mod.rs`: + - `ResolvedDiffTraceAttribution` struct removed. + - `resolve_diff_trace_attribution` function removed. + - `resolve_attribution` closure in `run_diff_trace_subcommand_from_payload` removed. + - `run_diff_trace_subcommand_from_payload_with` simplified to non-generic, passing `payload.model_id`/`payload.tool_version` directly to persistence. + - Removed `session_model_attribution` test helper and four session-model resolution tests. + - Removed `SessionModelAttribution` import. + - `SessionModelUpsert`, `SessionModelAttribution`, `ResolvedDiffTraceAttribution`, `resolve_diff_trace_attribution`, `session_model_by_tool_and_session`, `upsert_session_model` no longer exist in the codebase. + - `nix flake check` passed: all 84 tests pass, clippy clean, fmt clean, pkl-parity up to date. - [ ] T03: `Write direct Claude model_id into DiffTracePayload` (status:todo) - Task ID: T03 diff --git a/context/sce/agent-trace-db.md b/context/sce/agent-trace-db.md index c25500b1..f17b0b74 100644 --- a/context/sce/agent-trace-db.md +++ b/context/sce/agent-trace-db.md @@ -78,8 +78,9 @@ Active hook runtime resolves per-checkout Agent Trace DB files: - `012_create_parts_session_message_order_index.sql` - `013_create_messages_updated_at_trigger.sql` - `014_create_parts_updated_at_trigger.sql` -- `015_create_session_models.sql` -- `016_add_diff_traces_payload_type.sql` (migration ID `016_add_diff_traces_payload_type`; adds `payload_type TEXT NOT NULL DEFAULT 'patch'` to `diff_traces`) +- `015_add_diff_traces_payload_type.sql` (migration ID `015_add_diff_traces_payload_type`; adds `payload_type TEXT NOT NULL DEFAULT 'patch'` to `diff_traces`) + +The former `015_create_session_models` migration is retired from the fresh schema and listed through `AgentTraceDbSpec::retired_migration_ids()` so already-upgraded databases that recorded that ID are not treated as having an unexpected migration. Current development state does not preserve compatibility with databases that recorded a previous `016_add_diff_traces_payload_type` ID; deleting and recreating the local Agent Trace DB is acceptable for this migration renumbering. The shared `TursoDb` runner records applied IDs in the database-local `__sce_migrations` table. Existing Agent Trace DB files without metadata are brought forward by re-applying the idempotent migration set and recording each ID, so rerunning `sce setup` / `AgentTraceDb::open_at(path)` applies later Agent Trace migrations to an already-created per-checkout DB. @@ -96,7 +97,7 @@ The `diff_traces` baseline migration creates: - `tool_name TEXT` - `tool_version TEXT` -Migration `016_add_diff_traces_payload_type` adds: +Migration `015_add_diff_traces_payload_type` adds: - `payload_type TEXT NOT NULL DEFAULT 'patch'` — discriminator for source payload format; `patch` for `OpenCode` unified-diff payloads, `structured` for `Claude` `PostToolUse` structured payloads. From 8ff1317e6ea1bd710f871df9143fa0b547dda883 Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Wed, 1 Jul 2026 10:36:52 +0200 Subject: [PATCH 3/9] hooks: Capture direct Claude model IDs in diff traces Extract model metadata from supported Claude PostToolUse diff-trace payloads, normalize Claude model IDs with the claude/ prefix, and persist nullable direct attribution through the existing diff_traces insert path. Update tests for present, nested, and omitted Claude model metadata. Plan: remove-session-models-direct-claude-model-id Task: T03 Co-authored-by: SCE --- cli/src/services/hooks/mod.rs | 42 ++++++++++++++++++- context/architecture.md | 2 +- context/cli/cli-command-surface.md | 6 +-- context/context-map.md | 2 +- context/glossary.md | 2 +- context/overview.md | 6 +-- ...e-session-models-direct-claude-model-id.md | 3 +- context/sce/agent-trace-db.md | 24 ++--------- .../sce/agent-trace-hooks-command-routing.md | 11 +++-- .../opencode-agent-trace-plugin-runtime.md | 6 +-- context/sce/shared-turso-db.md | 2 +- 11 files changed, 65 insertions(+), 41 deletions(-) diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index a6ec260a..5f2f08ae 100644 --- a/cli/src/services/hooks/mod.rs +++ b/cli/src/services/hooks/mod.rs @@ -34,6 +34,8 @@ pub mod lifecycle; pub const NAME: &str = "hooks"; pub const CANONICAL_SCE_COAUTHOR_TRAILER: &str = "Co-authored-by: SCE "; +const MAX_TRACE_FILE_CREATE_ATTEMPTS: u64 = 1_000_000; +const CLAUDE_MODEL_ID_PREFIX: &str = "claude/"; type PayloadValidationError = fn(&str) -> String; #[derive(Clone, Debug, Eq, PartialEq)] @@ -795,7 +797,7 @@ fn parse_claude_diff_trace_payload( session_id: patch.session_id, diff: stdin_payload.to_string(), time: patch.time, - model_id: None, + model_id: extract_direct_claude_model_id(payload), tool_name: patch.tool_name, tool_version: patch.tool_version, payload_type: PAYLOAD_TYPE_STRUCTURED.to_string(), @@ -809,6 +811,44 @@ fn parse_claude_diff_trace_payload( } } +fn extract_direct_claude_model_id(payload: &serde_json::Map) -> Option { + direct_claude_model_id_string(payload, &["model", "model_id", "modelId"]) + .or_else(|| { + payload + .get("model") + .and_then(Value::as_object) + .and_then(|model| direct_claude_model_id_string(model, &["id", "model", "name"])) + }) + .and_then(|model| normalize_claude_model_id(&model)) +} + +fn direct_claude_model_id_string( + payload: &serde_json::Map, + keys: &[&str], +) -> Option { + keys.iter().find_map(|key| { + payload + .get(*key) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(str::to_string) + }) +} + +fn normalize_claude_model_id(model: &str) -> Option { + let normalized = model.trim(); + if normalized.is_empty() { + return None; + } + + if normalized.starts_with(CLAUDE_MODEL_ID_PREFIX) { + Some(normalized.to_string()) + } else { + Some(format!("{CLAUDE_MODEL_ID_PREFIX}{normalized}")) + } +} + /// Extract a u64 timestamp from a Claude hook event payload, falling back to the /// current system time when no timestamp field is present. fn extract_claude_event_time(payload: &serde_json::Map) -> u64 { diff --git a/context/architecture.md b/context/architecture.md index 76df6d62..43e72a2f 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -124,7 +124,7 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/doctor/mod.rs` owns the current doctor request/report surface while focused submodules (`doctor/inspect.rs`, `doctor/render.rs`, `doctor/fixes.rs`, `doctor/types.rs`) split report fact collection, rendering, manual fix reporting, and doctor-owned domain types into smaller seams; `cli/src/services/doctor/command.rs` owns the `DoctorCommand` payload used by the static `RuntimeCommand` enum and executes against any context implementing repo-root scoping. Runtime doctor execution resolves a repository root, derives a scoped context, requests the shared static lifecycle provider catalog with hooks included for service-owned `diagnose` and `fix` behavior, adapts lifecycle-owned health/fix records into doctor-owned problem/fix records, and then renders stable text/JSON problem records with category/severity/fixability/remediation fields plus deterministic fix-result reporting in fix mode. Checkout DB discovery no longer lives in `doctor`; it moved to the `trace` group (`sce trace db list`) in `cli/src/services/trace/`. Report fact collection preserves environment/repository/hook/integration display data and adds checkout identity plus per-checkout Agent Trace DB status when a checkout ID exists, while service-owned lifecycle providers own config validation, local DB and Agent Trace DB readiness/bootstrap, and hook rollout diagnosis/repair. - `cli/src/services/version/mod.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/version/command.rs` owns the `VersionCommand` payload used by the static `RuntimeCommand` enum. - `cli/src/services/completion/mod.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/completion/command.rs` owns the `CompletionCommand` payload used by the static `RuntimeCommand` enum. -- `cli/src/services/hooks/mod.rs` defines the current 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 enabled-by-default attribution-hooks config/env control is not opted out, `SCE_DISABLED` is false, and the staged-diff AI-overlap preflight confirms AI/editor evidence (`StagedDiffAiOverlapResult::Overlap`); the preflight is wired into `run_commit_msg_subcommand_in_repo` and logs `sce.hooks.commit_msg.ai_overlap_error` on error paths; `cli/src/services/hooks/command.rs` owns the `HooksCommand` payload used by the static `RuntimeCommand` enum. In the current attribution-only baseline, `pre-commit` and `post-rewrite` are deterministic no-op surfaces; `post-commit` requires validated `--remote-url`, threads that URL through the Agent Trace flow, prints it to stderr, and remains an active intersection + Agent Trace persistence entrypoint (captures current commit patch, queries recent `diff_traces` from the bounded past-7-days window, combines valid patches via `patch::combine_patches`, intersects with post-commit patch via `patch::intersect_patches`, persists result to `post_commit_patch_intersections`, then persists built Agent Trace payloads with range-level `content_hash` values to `agent_traces` in AgentTraceDb without post-commit file artifacts); `diff-trace` performs STDIN JSON intake, validates required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent/`null` → `None`), required nullable/non-empty `tool_version` (present and either `null` or non-empty string) plus required `u64` `time` (Unix epoch milliseconds), rejects values that cannot fit AgentTraceDb signed `time_ms` storage, writes one collision-safe parsed-payload `context/tmp/-000000-diff-trace.json` artifact, and inserts the parsed payload fields into AgentTraceDb; `session-model` performs STDIN JSON intake, validat... (line truncated to 2000 chars) +- `cli/src/services/hooks/mod.rs` defines the current 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 enabled-by-default attribution-hooks config/env control is not opted out, `SCE_DISABLED` is false, and the staged-diff AI-overlap preflight confirms AI/editor evidence (`StagedDiffAiOverlapResult::Overlap`); the preflight is wired into `run_commit_msg_subcommand_in_repo` and logs `sce.hooks.commit_msg.ai_overlap_error` on error paths; `cli/src/services/hooks/command.rs` owns the `HooksCommand` payload used by the static `RuntimeCommand` enum. In the current attribution-only baseline, `pre-commit` and `post-rewrite` are deterministic no-op surfaces; `post-commit` requires validated `--remote-url`, threads that URL through the Agent Trace flow, prints it to stderr, and remains an active intersection + Agent Trace persistence entrypoint (captures current commit patch, queries recent `diff_traces` from the bounded past-7-days window, combines valid patches via `patch::combine_patches`, intersects with post-commit patch via `patch::intersect_patches`, persists result to `post_commit_patch_intersections`, then persists built Agent Trace payloads with range-level `content_hash` values to `agent_traces` in AgentTraceDb without post-commit file artifacts); `diff-trace` performs STDIN JSON intake, validates required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent/`null` → `None`), required nullable/non-empty `tool_version` plus required `u64` `time` (Unix epoch milliseconds), rejects values that cannot fit AgentTraceDb signed `time_ms` storage, writes one collision-safe parsed-payload `context/tmp/-000000-diff-trace.json` artifact, and inserts the parsed payload fields into AgentTraceDb; Claude structured `PostToolUse` diff-trace intake extracts direct model metadata from top-level or nested `model` fields and normalizes it with the `claude/` prefix when present. `session-model` is no longer a supported hook route. - Generated Claude settings no longer register `SessionStart` for Agent Trace model attribution, and `sce hooks session-model` is no longer a supported hook command. The `session_models` table/API and diff-trace fallback lookup were removed in T02 of the `remove-session-models-direct-claude-model-id` plan; `diff-trace` now uses only direct payload `model_id` and `tool_version` values. - `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. - No user-invocable `sce sync` command is wired in the current runtime; local DB bootstrap and setup-time per-checkout Agent Trace DB initialization flow through lifecycle providers aggregated by setup, while checkout/global DB health/repair flow through the doctor surface and checkout DB discovery flows through the `trace` group (`sce trace db list`). diff --git a/context/cli/cli-command-surface.md b/context/cli/cli-command-surface.md index a54354ba..39bcde0e 100644 --- a/context/cli/cli-command-surface.md +++ b/context/cli/cli-command-surface.md @@ -53,7 +53,7 @@ Operator onboarding currently comes from `sce --help`, command-local `--help` ou - `auth` and `hooks` stay parser-valid and directly invocable, but are hidden from those top-level help surfaces Deferred or gated command surfaces currently avoid claiming unimplemented behavior. -`hooks` routes through implemented subcommand parsing/dispatch for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `conversation-trace`; `session-model` is no longer a supported hooks route. Current behavior remains attribution-only and enabled by default for commit attribution unless explicitly opted out (via `SCE_ATTRIBUTION_HOOKS_DISABLED`, `SCE_DISABLED`, or `policies.attribution_hooks.enabled = false`), gated by the staged-diff AI-overlap preflight so the trailer is appended only when AI/editor evidence is found. `post-commit` requires validated `--remote-url`, threads that value through Agent Trace flow, prints it to stderr, and remains the active intersection + Agent Trace DB path. `diff-trace` is active STDIN intake with required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id`, required nullable/non-empty `tool_version`, required `u64` `time` validation, missing/nullable attribution fallback from existing `session_models` rows by `tool_name` + `session_id` with direct payload values taking precedence, non-lossy AgentTraceDb `time_ms` conversion, and AgentTraceDb insertion including nullable/resolved `model_id` and `tool_version` without parsed-payload artifact writes under `context/tmp`. +`hooks` routes through implemented subcommand parsing/dispatch for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `conversation-trace`; `session-model` is no longer a supported hooks route. Current behavior remains attribution-only and enabled by default for commit attribution unless explicitly opted out (via `SCE_ATTRIBUTION_HOOKS_DISABLED`, `SCE_DISABLED`, or `policies.attribution_hooks.enabled = false`), gated by the staged-diff AI-overlap preflight so the trailer is appended only when AI/editor evidence is found. `post-commit` requires validated `--remote-url`, threads that value through Agent Trace flow, prints it to stderr, and remains the active intersection + Agent Trace DB path. `diff-trace` is active STDIN intake with required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id`, required nullable/non-empty `tool_version`, required `u64` `time` validation, direct nullable `model_id` / `tool_version` attribution (no session fallback), non-lossy AgentTraceDb `time_ms` conversion, and AgentTraceDb insertion including nullable `model_id` and `tool_version`; Claude structured `PostToolUse` payloads may derive `model_id` from top-level or nested `model` metadata with `claude/` prefix normalization. `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 `. @@ -91,7 +91,7 @@ A user-invocable `sync` command is not wired in the current CLI surface; local D - `cli/src/services/doctor/mod.rs` defines the implemented doctor request/report contract (`DoctorRequest`, `DoctorAction`, `DoctorMode`, `run_doctor`) while focused submodules under `cli/src/services/doctor/` handle runtime command dispatch (`command.rs`), diagnosis (`inspect.rs`), rendering (`render.rs`), fix execution (`fixes.rs`), and doctor-owned domain types (`types.rs`). Together they preserve explicit fix-mode parsing, checkout-database discovery, stable text/JSON problem and database-record rendering, deterministic fix-result reporting, and aggregation of `ServiceLifecycle::diagnose`/`ServiceLifecycle::fix` across registered providers (`config`, `local_db`, `auth_db`, `agent_trace_db`, `hooks`). The doctor module coordinates state-root/config/database reporting and validation, 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, and repair-mode delegation to service-owned fix implementations. - `cli/src/services/version/mod.rs` defines the version parser/output contract (`parse_version_request`, `render_version`) with deterministic text/JSON output modes; `cli/src/services/version/command.rs` owns the version runtime command handler. - `cli/src/services/completion/mod.rs` defines the completion output contract (`render_completion`) using clap_complete to generate deterministic shell scripts for Bash, Zsh, and Fish; `cli/src/services/completion/command.rs` owns the completion runtime command handler. -- `cli/src/services/hooks/mod.rs` defines production local hook runtime parsing/dispatch (`HookSubcommand`, `run_hooks_subcommand`) for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `conversation-trace`; `cli/src/services/hooks/command.rs` owns the hook runtime command handler. Current runtime behavior is commit-msg-only attribution behind the enabled-by-default attribution gate with explicit opt-out controls; `pre-commit` and `post-rewrite` are deterministic no-ops; `post-commit` requires validated `--remote-url`, threads that value through Agent Trace flow, prints it to stderr, and remains an active intersection + Agent Trace DB persistence path; `diff-trace` performs STDIN JSON intake, required-field validation, temporary missing/nullable attribution fallback from existing `session_models` rows, non-lossy AgentTraceDb `time_ms` conversion, and best-effort AgentTraceDb insertion without parsed-payload artifact writes. `session-model` is no longer a supported hooks route. `cli/src/services/hooks/lifecycle.rs` implements `ServiceLifecycle` for hook health checks, fix, and setup (hook rollout integrity and required-hook installation). +- `cli/src/services/hooks/mod.rs` defines production local hook runtime parsing/dispatch (`HookSubcommand`, `run_hooks_subcommand`) for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `conversation-trace`; `cli/src/services/hooks/command.rs` owns the hook runtime command handler. Current runtime behavior is commit-msg-only attribution behind the enabled-by-default attribution gate with explicit opt-out controls; `pre-commit` and `post-rewrite` are deterministic no-ops; `post-commit` requires validated `--remote-url`, threads that value through Agent Trace flow, prints it to stderr, and remains an active intersection + Agent Trace DB persistence path; `diff-trace` performs STDIN JSON intake, required-field validation, and best-effort AgentTraceDb insertion with direct nullable `model_id` / `tool_version` attribution. Claude structured `PostToolUse` payloads may derive `model_id` from top-level or nested `model` metadata with `claude/` prefix normalization. `session-model` is no longer a supported hooks route. `cli/src/services/hooks/lifecycle.rs` implements `ServiceLifecycle` for hook health checks, fix, and setup (hook rollout integrity and required-hook installation). - `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. - No `cli/src/services/sync.rs` module exists in the current codebase; `sce sync` command wiring is deferred, while local DB initialization and health ownership are split between setup and doctor. - `cli/src/services/default_paths.rs` defines the canonical per-user persisted-location seam for config/state/cache roots plus named default file paths for current persisted artifacts (`global config`, `auth tokens`, `local DB`, `agent trace DB`) used by config discovery, token storage, database adapters, and doctor diagnostics; its internal `roots` seam now owns the platform-aware root-directory resolution so non-test production modules consume shared path accessors instead of resolving owned roots directly. @@ -104,7 +104,7 @@ A user-invocable `sync` command is not wired in the current CLI surface; local D - `cli/src/services/local_db/mod.rs` provides `LocalDb = TursoDb` with retry-backed `new()`, `execute()`, `query()`, and `query_map()` inherited from the shared Turso adapter. - `LocalDb::new()` resolves the canonical per-user DB path through `default_paths::local_db_path()`, creates parent directories, opens the local Turso database, and currently runs zero local migrations. -- `cli/src/services/agent_trace_db/mod.rs` provides `AgentTraceDb = TursoDb` plus `DiffTraceInsert<'_>`/`insert_diff_trace()` for parameterized writes to `diff_traces`, `AgentTraceInsert<'_>`/`insert_agent_trace()` for built `agent_traces`, and `SessionModelUpsert<'_>`/lookup helpers for durable `session_models` attribution keyed by `(tool_name, session_id)`. +- `cli/src/services/agent_trace_db/mod.rs` provides `AgentTraceDb = TursoDb` plus `DiffTraceInsert<'_>`/`insert_diff_trace()` for parameterized writes to `diff_traces`, `AgentTraceInsert<'_>`/`insert_agent_trace()` for built `agent_traces`, and typed message/part insert helpers for conversation traces. - `AgentTraceDb::new()` resolves the legacy/global `/sce/agent-trace.db` through `default_paths::agent_trace_db_path()`; active hook runtime resolves per-checkout DB files through checkout identity and `default_paths::agent_trace_db_path_for_checkout(checkout_id)`. - `cli/src/services/local_db/lifecycle.rs` implements `ServiceLifecycle` for local DB health checks and setup (DB path/health validation and DB bootstrap). - `cli/src/services/agent_trace_db/lifecycle.rs` implements `ServiceLifecycle` for Agent Trace checkout identity setup, setup-time per-checkout DB initialization, and checkout/global DB path-health validation plus parent bootstrap. diff --git a/context/context-map.md b/context/context-map.md index a9b69ad0..fa499218 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -52,7 +52,7 @@ Feature/domain context: - `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-minimal-generator.md` (implemented a library minimal Agent Trace generator seam at `cli/src/services/agent_trace.rs`, used by the active post-commit hook flow to produce strict `0.1.0` JSON payloads with top-level `version`, UUIDv7 `id` derived from commit-time metadata, caller-provided commit-time `timestamp`, optional top-level `vcs` metadata emitted when present (`type` from enum `git|jj|hg|svn`, `revision` from metadata input; current post-commit flow provides `git`), optional top-level `tool` metadata (`name`/`version`) sourced from builder metadata inputs when overlapping AI content exists, and always-emitted `metadata.sce.version` sourced from the compiled `sce` CLI package version, plus per-file trace data from patch inputs via `intersect_patches(constructed_patch, post_commit_patch)` then `post_commit_patch`-anchored hunk classification into `ai`/`mixed`/`unknown` contributor categories, serialized per conversation with a required lookup `url` derived from top-level `AgentTrace.id`, nested `contributor.type` with optional `contributor.model_id` omitted when provenance is missing, one derived `ranges[{start_line,end_line,content_hash}]` entry per post-commit or embedded-patch hunk, and range `content_hash` values that hash touched-line kind/content independent of positions and metadata) -- `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime behavior: enabled-by-default commit-msg attribution with explicit opt-out controls, no-op `pre-commit`/`post-rewrite` entrypoints, active post-commit Agent Trace DB intersection and payload persistence, DB-only `diff-trace` STDIN intake with required payload validation and temporary fallback reads from existing `session_models` rows when attribution is missing, `session-model` removed from the supported hook surface, and active `conversation-trace` intake for normalized and supported raw Claude message/part events; this document also owns the current `diff-trace` and `conversation-trace` fail-open intake contracts.) +- `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime behavior: enabled-by-default commit-msg attribution with explicit opt-out controls, no-op `pre-commit`/`post-rewrite` entrypoints, active `post-commit` intersection and Agent Trace DB persistence, DB-only `diff-trace` STDIN intake with OpenCode normalized payloads and Claude structured `PostToolUse` payload classification, direct nullable `model_id`/`tool_version` persistence without session fallback, Claude direct model metadata extraction from top-level or nested `model` fields with `claude/` prefix normalization, no parsed-payload artifact persistence under `context/tmp`, `session-model` removed from the supported hook surface, and `conversation-trace` STDIN intake for normalized batches plus supported raw Claude events; this document also owns the current `diff-trace` and `conversation-trace` fail-open intake contracts.) - `context/sce/automated-profile-contract.md` (deterministic gate policy for automated OpenCode profile, including 10 gate categories, permission mappings, automated `/commit` single-commit execution behavior, and automated profile constraints) - `context/sce/bash-tool-policy-enforcement-contract.md` (approved bash-tool blocking contract plus current Rust evaluator seam and OpenCode/Claude delegation references, including config schema, argv-prefix matching, shell/nix unwrapping, 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 including `sce-bash-policy` + `sce-agent-trace`, TypeScript source ownership, and Claude generated settings boundary including Agent Trace hooks plus `PreToolUse` Bash policy hook registration through the missing-CLI install-guidance helper) diff --git a/context/glossary.md b/context/glossary.md index b7a1abaa..b894082c 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -38,7 +38,7 @@ - `encrypted Turso adapter`: Generic adapter seam in `cli/src/services/db/mod.rs` exposed as `EncryptedTursoDb`, structurally parallel to `TursoDb` (connection, tokio runtime bridge, spec typing). Its constructor resolves the encryption key via `encryption_key::get_or_create_encryption_key(&db_path, db_name)`, which derives a Turso-compatible 64-character hex key from non-empty `SCE_AUTH_DB_ENCRYPTION_KEY` env-secret text before falling back to OS credential-store keyring get-or-create behavior; credential-store default registration is guarded by stable `OnceLock` plus an atomic in-progress flag so errors or panics leave initialization retryable without mutex poisoning. The adapter enables Turso local encryption with strict `aegis256` cipher selection through `turso::EncryptionOpts`, wraps encrypted local open/connect in the default DB connection-open retry policy, and runs embedded migrations after retry has produced a connection; the adapter also exposes retry-backed synchronous `execute`, `query`, `query_map`, and `run_migrations` helpers with `__sce_migrations` tracking parity. - `auth DB adapter`: Module in `cli/src/services/auth_db/mod.rs` that defines `AuthDbSpec` and exposes `AuthDb` as an `EncryptedTursoDb` alias. It resolves the canonical `/sce/auth.db` path with `auth_db_path()`, keeps encryption mandatory with `SCE_AUTH_DB_ENCRYPTION_KEY` env-secret precedence before OS keyring fallback and no plaintext mode, and embeds ordered auth migrations where baseline SQL creates `auth_credentials` without `user_id`, with `updated_at`, and a trigger that auto-refreshes `updated_at` on row updates. Auth runtime token-storage is now wired through `cli/src/services/token_storage.rs`, which persists tokens via the `auth_credentials` table in the encrypted auth DB instead of a JSON file. - `AuthDbLifecycle`: Lifecycle provider in `cli/src/services/auth_db/lifecycle.rs` that implements `ServiceLifecycle` for encrypted auth DB setup/doctor integration. `diagnose` collects auth DB path health problems, `fix` bootstraps missing auth DB parent directory, and `setup` calls `AuthDb::new()`. Registered as `LifecycleProviderId::AuthDb` in the shared lifecycle catalog. -- `agent trace DB adapter`: Module in `cli/src/services/agent_trace_db/mod.rs` that defines `AgentTraceDbSpec`, exposes `AgentTraceDb` as a `TursoDb` alias, keeps `/sce/agent-trace.db` as the legacy/global fallback path, supports explicit per-checkout DB paths through `open_at(path)` and `open_for_hooks_without_migrations_at(path)`, embeds an ordered split fresh-start baseline migration set (`001..015`; retired `015_create_session_models` is replaced by active `015_add_diff_traces_payload_type`) covering `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `messages`, `parts`, and `diff_traces.payload_type` plus indexes and triggers; provides typed parameterized insert/upsert helpers for diff traces with `payload_type` discriminator, post-commit intersection rows, built agent-trace rows, and message/part batch inserts; exposes chronological recent `diff_traces` query/parse support with malformed-row skip accounting; has `AgentTraceDbLifecycle` for setup/doctor integration including setup-time per-checkout DB initialization; and is accessed by active Agent Trace hooks through checkout-scoped lazy DB resolution when setup has not prepared the DB. +- `agent trace DB adapter`: Module in `cli/src/services/agent_trace_db/mod.rs` that defines `AgentTraceDbSpec`, exposes `AgentTraceDb` as a `TursoDb` alias, keeps `/sce/agent-trace.db` as the legacy/global fallback path, supports explicit per-checkout DB paths through `open_at(path)` and `open_for_hooks_without_migrations_at(path)`, embeds an ordered split fresh-start baseline migration set (`001..015`; retired `015_create_session_models` is replaced by active `015_add_diff_traces_payload_type`) covering `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `messages`, `parts`, and `diff_traces.payload_type` plus indexes and triggers; provides typed parameterized insert helpers for diff traces with `payload_type` discriminator, post-commit intersection rows, built agent-trace rows, and message/part batch inserts; exposes chronological recent `diff_traces` query/parse support with malformed-row skip accounting; has `AgentTraceDbLifecycle` for setup/doctor integration including setup-time per-checkout DB initialization; and is accessed by active Agent Trace hooks through checkout-scoped lazy DB resolution when setup has not prepared the DB. - `structured patch service`: Pure synchronous Rust service in `cli/src/services/structured_patch.rs` that derives supported structured editor hook payloads into canonical `ParsedPatch` values. The current implemented source is Claude `PostToolUse` payloads for `Write` creates and `Edit` structured patches; wired into `sce hooks diff-trace` for Claude payload classification at intake (T04) and into `AgentTraceDb::recent_diff_trace_patches` for post-commit structured payload parsing dispatch at read time (T05). - `Agent Trace SCE metadata`: Implementation-owned top-level metadata emitted by `build_agent_trace(...)` as `metadata.sce.version`; the value is sourced from the compiled `sce` CLI package version via `env!("CARGO_PKG_VERSION")`, is schema-validated with the rest of the payload, and is persisted in AgentTraceDb `agent_traces.trace_json` without changing the top-level Agent Trace payload/schema `version`. - `Agent Trace range content_hash`: Per-range `content_hash` emitted by `build_agent_trace(...)` inside every `ranges[]` entry as `murmur3:`, computed from the touched-line kind/content of the `post_commit_patch` or embedded-patch hunk used to emit that range while excluding positions, paths, metadata, and database IDs. diff --git a/context/overview.md b/context/overview.md index 805c7b87..d70caa57 100644 --- a/context/overview.md +++ b/context/overview.md @@ -58,9 +58,9 @@ Context sync now uses an important-change gate: cross-cutting/policy/architectur 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. `/commit` is now split by profile: manual generated commands remain proposal-only and allow split guidance when staged changes mix unrelated goals, while the automated OpenCode `/commit` command generates exactly one commit message and runs `git commit` against the staged diff. The shared `sce-atomic-commit` contract also requires commit bodies to cite affected plan slug(s) and updated task ID(s) when staged changes include `context/plans/*.md`, and to stop for clarification instead of inventing those references when the staged plan diff is ambiguous. 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 the removed Agent Trace schema adaptation, payload building, retry replay, or rewrite handling paths 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, `SCE_DISABLED` is false, and the staged-diff AI-overlap preflight confirms AI/editor evidence (`StagedDiffAiOverlapResult::Overlap`); when the preflight returns `NoOverlap` or `Error` (including DB open failure, schema not ready, query error, staged diff read failure, or zero overlap), the trailer is not appended and errors are logged via `sce.hooks.commit_msg.ai_overlap_error`; `pre-commit` and `post-rewrite` remain deterministic no-op entrypoints; `post-commit` requires validated `--remote-url`, threads that URL through the Agent Trace flow, prints it to stderr, captures current commit patch, queries recent `diff_traces` from past 7 days (dispatching `patch` rows through existing unified-diff parsing and `structured` rows through `structured_patch::derive_claude_structured_patch` at read time), combines/intersects patches, persists intersection metadata to `post_commit_patch_intersections`, and persists the schema-validated built Agent Trace payload, including optional top-level `tool` metadata from recent diff-trace rows, top-level `metadata.sce.version` from the compiled `sce` CLI package version, and range-level `content_hash` values, to AgentTraceDb `agent_traces` (DB-only, no post-commit Agent Trace file artifact); `diff-trace` currently validates/persists required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent or `null` → `None`, present+non-empty → `Some`, present+empty → error), required nullable/non-empty `tool_version`, plus required `u64` millisecond `time`, uses only direct payload `model_id` and `tool_version` (no longer resolves from `session_models`), and continues with `None` for missing attribution, with non-lossy AgentTraceDb `time_ms` conversion and collision-safe timestamp+attempt artifact filenames; and `se... (line truncated to 2000 chars) -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 provides`LocalDb`as a thin`TursoDb`alias in`cli/src/services/local_db/mod.rs`; `LocalDbSpec`resolves the canonical local DB path from the shared default-path catalog and currently declares zero migrations. Shared Turso infrastructure lives in`cli/src/services/db/mod.rs`, where `DbSpec`and generic`TursoDb`support dual-mode operation — local mode via`turso::Builder::new_local()`when`SCE_SYNC_URL`+`SCE_SYNC_TOKEN`are absent, or sync (Turso Cloud) mode via`turso::sync::Builder::new_remote()`when both are set. It owns parent-directory creation, connection setup, tokio current-thread runtime bridging, synchronous`execute`/`query`/`query_map`, generic migration execution, sync operations (`push`/`pull`/`checkpoint`/`stats`) that are no-ops in local mode (sync is never triggered automatically from `execute()`), and shared DB lifecycle helpers for service-specific database wrappers. Auth DB persistence now has a thin encrypted wrapper in `cli/src/services/auth_db/mod.rs`: `AuthDb = EncryptedTursoDb`resolves`/sce/auth.db`and embeds ordered`auth_tokens`table/index migrations, with lifecycle registration wired through`AuthDbLifecycle`in`cli/src/services/auth_db/lifecycle.rs`; auth runtime token-storage is now wired through `token_storage.rs`, which persists tokens via the `auth_credentials`table instead of a JSON file. Agent Trace persistence now has its own`cli/src/services/agent_trace_db/mod.rs`wrapper, legacy global`/sce/agent-trace.db`fallback plus active per-checkout`/sce/agent-trace-{checkout_id}.db` hook runtime paths, a split fresh-start baseline migration set (`001..008`) covering `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, nullable `agent_traces.remote_url`, indexes (`idx_diff_traces_time_ms_id`, `idx_agent_traces_agent_trace_id`, `idx_agent_traces_remote_url`), and retired `session_models`migration keyed by`(tool_name... (line truncated to 2000 chars) +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, `SCE_DISABLED` is false, and the staged-diff AI-overlap preflight confirms AI/editor evidence (`StagedDiffAiOverlapResult::Overlap`); when the preflight returns `NoOverlap` or `Error` (including DB open failure, schema not ready, query error, staged diff read failure, or zero overlap), the trailer is not appended and errors are logged via `sce.hooks.commit_msg.ai_overlap_error`; `pre-commit` and `post-rewrite` remain deterministic no-op entrypoints; `post-commit` requires validated `--remote-url`, threads that URL through the Agent Trace flow, prints it to stderr, captures current commit patch, queries recent `diff_traces` from past 7 days (dispatching `patch` rows through existing unified-diff parsing and `structured` rows through `structured_patch::derive_claude_structured_patch` at read time), combines/intersects patches, persists intersection metadata to `post_commit_patch_intersections`, and persists the schema-validated built Agent Trace payload, including optional top-level `tool` metadata from recent diff-trace rows, top-level `metadata.sce.version` from the compiled `sce` CLI package version, and range-level `content_hash` values, to AgentTraceDb `agent_traces` (DB-only, no post-commit Agent Trace file artifact); `diff-trace` currently validates/persists required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent or `null` → `None`, present+non-empty → `Some`, present+empty → error), required nullable/non-empty `tool_version`, plus required `u64` millisecond `time`, uses only direct payload `model_id` and `tool_version` (no longer resolves from `session_models`), and continues with `None` for missing attribution, with non-lossy AgentTraceDb `time_ms` conversion and collision-safe timestamp+attempt artifact filenames. Claude structured `PostToolUse` diff-trace intake extracts direct model metadata from top-level or nested `model` fields and normalizes it with the `claude/` prefix when present. +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 provides `LocalDb` as a thin `TursoDb` alias in `cli/src/services/local_db/mod.rs`; `LocalDbSpec` resolves the canonical local DB path from the shared default-path catalog and currently declares zero migrations. Shared Turso infrastructure lives in `cli/src/services/db/mod.rs`, where `DbSpec` and generic `TursoDb` support local or remote sync-mode opens, parent-directory creation, connection setup, synchronous query helpers, embedded migration execution, and shared DB lifecycle helpers. Auth DB persistence uses encrypted `AuthDb = EncryptedTursoDb` and token storage persists credentials through the `auth_credentials` table. Agent Trace persistence uses `AgentTraceDb = TursoDb` with legacy global plus active per-checkout DB paths, fresh-start migrations for `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `messages`, `parts`, indexes/triggers, and retired `015_create_session_models` metadata handling; active hook runtime writes direct nullable diff-trace attribution without a `session_models` API/table dependency. The hooks command surface now also supports concrete runtime subcommand routing (`pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `conversation-trace`) with deterministic argument/STDIN validation; `session-model` is no longer supported. Current runtime behavior keeps commit-msg attribution enabled by default unless explicitly opted out: the attribution gate enables canonical trailer insertion in `commit-msg` only when the staged-diff AI-overlap preflight confirms AI/editor evidence (no trailer is appended when the preflight finds no overlap or encounters any error); `pre-commit`/`post-rewrite` remain deterministic no-ops, `post-commit` requires validated `--remote-url`, threads that URL into the Agent Trace flow, prints it to stderr, and remains the active bounded recent-diff-trace intersection path, and `diff-trace` is the active intake path for parsed STDIN `{ sessionID, diff, time, model_id?, tool_name, tool_version }` payload persistence with optional `model_id`, required non-empty `tool_name`, required nullable/non-empty `tool_version`, direct `model_id` and `tool_version` values (no session-model fallback), required `u64` millisecond `time`, non-lossy AgentTraceDb `time_ms` conversion, and collision-safe timestamp+attempt artifact filenames. This behavior is documented in `context/sce/agent-trace-hooks-command-routing.md`. The removed `sce hooks claude-capture` raw capture route is documented in `context/sce/claude-raw-hook-capture.md` as a removed feature. 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`), and uses a unified remove-and-replace policy that removes existing hooks before swapping staged content with deterministic recovery guidance on swap failures; this behavior is documented in `context/sce/setup-githooks-install-flow.md`. diff --git a/context/plans/remove-session-models-direct-claude-model-id.md b/context/plans/remove-session-models-direct-claude-model-id.md index bde6715a..d33eda9e 100644 --- a/context/plans/remove-session-models-direct-claude-model-id.md +++ b/context/plans/remove-session-models-direct-claude-model-id.md @@ -62,7 +62,7 @@ This intentionally accepts the known Claude limitation that some events, especia - `SessionModelUpsert`, `SessionModelAttribution`, `ResolvedDiffTraceAttribution`, `resolve_diff_trace_attribution`, `session_model_by_tool_and_session`, `upsert_session_model` no longer exist in the codebase. - `nix flake check` passed: all 84 tests pass, clippy clean, fmt clean, pkl-parity up to date. -- [ ] T03: `Write direct Claude model_id into DiffTracePayload` (status:todo) +- [x] T03: `Write direct Claude model_id into DiffTracePayload` (status:done) - Task ID: T03 - Goal: Populate `DiffTracePayload.model_id` directly from supported Claude `PostToolUse` payloads when model metadata is present. - Boundaries (in/out of scope): @@ -70,6 +70,7 @@ This intentionally accepts the known Claude limitation that some events, especia - Out — Session-level fallback lookup; placeholder model values; Agent Trace schema changes. - Done when: Claude structured `DiffTracePayload` carries `Some(model_id)` when the raw `PostToolUse` payload includes model metadata and `None` when it does not; persisted `diff_traces.model_id` mirrors that direct value; tests cover present and omitted Claude model metadata. - Verification notes (commands or checks): targeted hooks tests for Claude diff-trace payload parsing/persistence; inspect `parse_claude_diff_trace_payload` no longer hardcodes `model_id: None` when payload has extractable model info. + - Completion evidence (2026-07-01): Added direct Claude model extraction in `cli/src/services/hooks/mod.rs` for supported structured `PostToolUse` diff-trace payloads. The parser now reads direct `model`/`model_id`/`modelId` strings or nested `model.id`/`model.model`/`model.name`, trims non-empty values, normalizes them with the existing `claude/` prefix rule, and leaves `model_id=None` when absent. Added hooks tests covering direct model extraction plus DB insert mirroring, nested already-prefixed extraction, and omitted model metadata. Context sync classified this as an important localized Agent Trace runtime-contract change and refreshed current-state hook/DB context files plus root summaries. `cargo test claude_diff_trace_payload -- --exact` was attempted but blocked by the repository bash policy requiring `nix flake check`; `nix flake check` passed (`all checks passed`), and `nix run .#pkl-check-generated` passed (`Generated outputs are up to date.`). - [ ] T04: `Remove diff-trace session fallback and repair tests` (status:todo) - Task ID: T04 diff --git a/context/sce/agent-trace-db.md b/context/sce/agent-trace-db.md index f17b0b74..d348315e 100644 --- a/context/sce/agent-trace-db.md +++ b/context/sce/agent-trace-db.md @@ -33,10 +33,6 @@ pub type AgentTraceDb = TursoDb; - `INSERT_PART_SQL`: parameterized single-row append-only INSERT into `parts` (no upsert; multiple rows per `(session_id, message_id)` allowed). - `insert_part(input)`: typed single-row helper that inserts a part row without requiring a matching `messages` row (supports out-of-order writes); retained as part of the adapter surface. - `insert_parts(inputs)`: typed batch helper that generates and executes one parameterized multi-row append-only `parts` insert for valid conversation-trace `message.part` batches. -- `SessionModelUpsert<'a>`: upsert payload with `tool_name`, `session_id`, `model_id`, nullable `tool_version`, and `session_start_time_ms`. -- `upsert_session_model()`: domain-specific upsert helper for `session_models` keyed by `(tool_name, session_id)`. -- `SessionModelAttribution`: durable session model attribution row returned from `session_models` lookups, carrying `model_id` plus nullable `tool_version` for later diff-trace fallback. -- `session_model_by_tool_and_session()`: lookup helper for model/tool-version attribution by `(tool_name, session_id)`. - `lifecycle.rs`: service lifecycle provider for setup/doctor integration. ## Non-goals @@ -147,18 +143,6 @@ The `parts` migration creates: No foreign keys exist between `messages` and `parts`; rows may be written out of order. The data model uses natural identifiers (`session_id`, `message_id`) for joins rather than DB-level referential integrity. -The `session_models` migration creates durable editor session model attribution: - -- `id INTEGER PRIMARY KEY` -- `tool_name TEXT NOT NULL` -- `session_id TEXT NOT NULL` -- `model_id TEXT NOT NULL` -- `tool_version TEXT` (nullable) -- `session_start_time_ms INTEGER NOT NULL` -- `created_at TEXT NOT NULL DEFAULT (...)` -- `updated_at TEXT NOT NULL DEFAULT (...)` -- `UNIQUE (tool_name, session_id)` - Lookup indexes created by the baseline migration set: - `idx_diff_traces_time_ms_id` on `(time_ms, id)` @@ -189,10 +173,10 @@ Both triggers compare `OLD.*` vs `NEW.*` for all mutable columns (excluding `upd `sce hooks diff-trace` is the current runtime writer for `diff_traces`. -- The hook path validates required STDIN `{ sessionID, diff, time, tool_name, tool_version }` before persistence, with `model_id` accepted as optional (absent or `null`) and `tool_version` accepted as nullable. When either attribution field is missing/nullable, Rust queries `session_models` by `(tool_name, session_id)` and fills missing `model_id` / `tool_version` from the stored row when available. -- Direct payload `model_id` and `tool_version` values pass into `DiffTraceInsert` with precedence over stored session values. If no matching session row exists, missing attribution remains `None` and persistence continues with nullable fields. The `payload_type` field is set to `PAYLOAD_TYPE_PATCH` for `OpenCode` normalized diff-trace payloads and `PAYLOAD_TYPE_STRUCTURED` for Claude structured `PostToolUse` payloads. +- The hook path validates required STDIN `{ sessionID, diff, time, tool_name, tool_version }` before persistence, with `model_id` accepted as optional (absent or `null`) and `tool_version` accepted as nullable. Missing attribution remains `None`; there is no session-level fallback lookup. +- Direct payload `model_id` and `tool_version` values pass into `DiffTraceInsert` as-is. The `payload_type` field is set to `PAYLOAD_TYPE_PATCH` for `OpenCode` normalized diff-trace payloads and `PAYLOAD_TYPE_STRUCTURED` for Claude structured `PostToolUse` payloads. Claude structured intake extracts direct `model`/`model_id`/`modelId` metadata, including nested `model.id` / `model.model` / `model.name`, and normalizes values with the `claude/` prefix when present. - `time` is accepted as a `u64` Unix epoch millisecond input and must fit the signed `i64` `time_ms` column before any persistence starts. -- The hook inserts the parsed payload fields plus resolved nullable attribution through `AgentTraceDb::insert_diff_trace()` without writing a parsed-payload artifact under `context/tmp`. +- The hook inserts the parsed payload fields plus nullable direct attribution through `AgentTraceDb::insert_diff_trace()` without writing a parsed-payload artifact under `context/tmp`. - AgentTraceDb open/insert failures are logged and reflected in deterministic success text as failed DB persistence; no artifact fallback is created. - The hook writes the existing collision-safe `context/tmp/-000000-diff-trace.json` parsed-payload artifact, then attempts to insert the parsed payload fields plus resolved nullable attribution through `AgentTraceDb::insert_diff_trace()`. - The strict valid-payload path writes the artifact before AgentTraceDb insertion. `diff-trace` intake failures, including artifact persistence failures and AgentTraceDb setup/persistence failures before the existing post-artifact DB-warning branch, are logged through `sce.hooks.diff_trace.error` and returned as command success; the existing post-artifact AgentTraceDb write-warning path still logs and returns the failed-DB-persistence success text. @@ -211,7 +195,7 @@ Post-commit intersection rows are written by the active `post-commit` hook flow - No `context/tmp` artifact is written for conversation traces. - The generated OpenCode agent-trace plugin sends mixed-batch envelopes for conversation traces: regular `message` and `message.part` events each carry one per-item `type`, while diff-backed `message` events send one envelope containing the synthetic parent message item plus patch part items. -`sce hooks session-model` is no longer a supported command route, and generated Claude settings no longer produce `SessionStart` model-attribution events. The `session_models` table/API and diff-trace fallback lookup remain temporarily in the DB adapter for the later planned cleanup tasks, so existing rows may still be read by `diff-trace` when direct payload attribution is missing, but no active route writes new rows. See [agent-trace-hooks-command-routing.md](agent-trace-hooks-command-routing.md). +`sce hooks session-model` is no longer a supported command route, generated Claude settings no longer produce `SessionStart` model-attribution events, and the Agent Trace DB adapter no longer exposes a `session_models` API or fresh-schema table. See [agent-trace-hooks-command-routing.md](agent-trace-hooks-command-routing.md). ## Recent patch reads diff --git a/context/sce/agent-trace-hooks-command-routing.md b/context/sce/agent-trace-hooks-command-routing.md index 8c0337b4..6f2a2cb9 100644 --- a/context/sce/agent-trace-hooks-command-routing.md +++ b/context/sce/agent-trace-hooks-command-routing.md @@ -63,15 +63,14 @@ - Current command-surface success output is: `post-commit hook processed intersection: commit=, intersection_files=`. - `post-rewrite` is a deterministic no-op entrypoint. - `diff-trace` reads STDIN JSON and classifies the payload: - - **Claude structured payloads** (detected by presence of top-level `hook_event_name`): the STDIN JSON is validated through `derive_claude_structured_patch`. Supported `PostToolUse` `Write` structured-update hunks, `Write` `tool_input.content` create fallback, and `Edit` structured-patch events produce a `DiffTracePayload` with `payload_type="structured"` and the raw event JSON stored as the `diff` column without conversion to unified-diff text. Unsupported Claude events (non-`PostToolUse`, unsupported tools, invalid payloads) produce a deterministic `NoOp` success result. + - **Claude structured payloads** (detected by presence of top-level `hook_event_name`): the STDIN JSON is validated through `derive_claude_structured_patch`. Supported `PostToolUse` `Write` create and `Edit` structured-patch events produce a `DiffTracePayload` with `payload_type="structured"` and the raw event JSON stored as the `diff` column without conversion to unified-diff text. Direct model metadata is extracted from top-level `model`, `model_id`, or `modelId`, or from nested `model.id`, `model.model`, or `model.name`; non-empty values are normalized with the `claude/` prefix. If model metadata is absent, `model_id` remains nullable. Unsupported Claude events (non-`PostToolUse`, unsupported tools, invalid payloads) produce a deterministic `NoOp` success result. - **OpenCode normalized payloads** (no `hook_event_name`): existing flat `{ sessionID, diff, time, model_id?, tool_name, tool_version }` validation applies unchanged, with `payload_type="patch"`. - The `DiffTracePayload` struct carries a `payload_type: String` field consumed by `persist_diff_trace_payload_to_agent_trace_db_with` to pass the correct discriminator to `DiffTraceInsert`. - - When `model_id` or `tool_version` is missing/nullable in the parsed payload, Rust looks up AgentTraceDb `session_models` by `(tool_name, session_id)` and uses the stored attribution values for missing fields when available. Direct payload `model_id` and `tool_version` values keep precedence over stored values. - - If no matching session row exists, missing attribution fields remain `None`; the hook still attempts the AgentTraceDb insert with nullable attribution. - - Persistence: resolves the current per-checkout AgentTraceDb lazily and inserts the parsed payload fields via `DiffTraceInsert` + `insert_diff_trace()` using nullable/resolved `model_id` and `tool_version`. No parsed-payload artifact is written under `context/tmp`. + - Missing `model_id` or `tool_version` stays nullable; Rust does not perform session-level fallback attribution. Direct payload values are persisted as-is after payload-specific validation/normalization. + - Persistence: resolves the current per-checkout AgentTraceDb lazily and inserts the parsed payload fields via `DiffTraceInsert` + `insert_diff_trace()` using nullable direct `model_id` and `tool_version`. No parsed-payload artifact is written under `context/tmp`. - Current producers are the OpenCode agent-trace plugin and the generated Claude `sce hooks` command hooks (no TypeScript intermediary). - OpenCode forwards user-message `message` diffs with `tool_name="opencode"`, always including `model_id`, and nullable OpenCode client-version metadata. - - Claude generated settings no longer register `SessionStart`; supported `PostToolUse` `Write|Edit|MultiEdit|NotebookEdit` events are routed directly to `sce hooks diff-trace`. Missing `model_id` / `tool_version` values are resolved from pre-existing `session_models` rows when available until the later fallback-removal task lands. + - Claude generated settings no longer register `SessionStart`; supported `PostToolUse` `Write|Edit|MultiEdit|NotebookEdit` events are routed directly to `sce hooks diff-trace`. Runtime persistence currently derives structured diff traces for `Write` create and `Edit` structured-patch payloads; unsupported Claude payload shapes are no-ops. - Neither TypeScript runtime writes `context/tmp/*-diff-trace.json` artifacts or AgentTraceDb rows directly. - `diff-trace` command success reports AgentTraceDb persistence only. AgentTraceDb open/insert failures are logged through `sce.hooks.diff_trace.agent_trace_db_write_failed` and reflected in deterministic success text as failed DB persistence; no parsed-payload artifact fallback is created. - `diff-trace` producer-facing intake failures are logged through `sce.hooks.diff_trace.error` and returned as hook success; the valid-payload path is DB-only and does not write parsed-payload artifacts. @@ -110,7 +109,7 @@ - Current valid-payload success output reports deterministic mixed-batch accounting: `conversation-trace hook persisted mixed payload batch to AgentTraceDb: attempted=, persisted_messages=, persisted_parts=, skipped=.` The hook does not persist `context/tmp` artifacts. - Fail-open output for conversation-trace intake failures is `conversation-trace hook intake failed open; error logged.` so hook callers do not receive app-level classified errors or non-zero exits for intake failures. - The generated OpenCode agent-trace plugin emits this mixed-batch shape for conversation-trace handoff: ordinary message/part events produce one-item mixed envelopes, completed question-tool parts produce `message.part` items with `part_type: "question"`, and diff-backed message events produce one envelope containing the synthetic parent `message` item plus patch `message.part` items. -- `session-model` is no longer a supported `sce hooks` subcommand and generated Claude settings no longer produce `SessionStart` model-attribution events. The `session_models` DB API/table and diff-trace fallback remain temporarily in code for the later planned cleanup tasks, but there is no active runtime route that writes new rows. +- `session-model` is no longer a supported `sce hooks` subcommand and generated Claude settings no longer produce `SessionStart` model-attribution events. The `session_models` DB API/table and diff-trace fallback are removed from active code; upgraded databases may still contain the retired table, but runtime paths no longer read or write it. ## Explicit non-goals in the current baseline diff --git a/context/sce/opencode-agent-trace-plugin-runtime.md b/context/sce/opencode-agent-trace-plugin-runtime.md index 39bdba30..1a0f3af3 100644 --- a/context/sce/opencode-agent-trace-plugin-runtime.md +++ b/context/sce/opencode-agent-trace-plugin-runtime.md @@ -108,15 +108,15 @@ When extraction succeeds, `buildQuestionToolConversationTracePayload(eventPart)` - The diff extraction seam is internal to the source module and is used by `buildTrace` at runtime. - `buildTrace` exits early when extraction returns `undefined` (non-user role, empty diffs array, or no usable patch entries), so no diff-trace hook invocation occurs for those events. - The plugin tracks OpenCode client version per session ID from `session.created` / `session.updated` events and forwards it as `tool_version` when available. -- When extraction succeeds, `buildTrace` forwards the extracted payload with required `tool_name="opencode"` and required `tool_version` (nullable when session version is unavailable) to `sce hooks diff-trace` via STDIN JSON; the Rust hook runtime validates required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id`, required nullable/non-empty `tool_version`, plus required `time`, resolves missing/nullable attribution fields from `session_models` when available while preserving direct payload precedence, persists DB-backed diff-trace fields through AgentTraceDb `diff_traces` insertion on the valid path, and fails open for runtime intake failures by logging `sce.hooks.diff_trace.error` while returning hook success to the producer. +- When extraction succeeds, `buildTrace` forwards the extracted payload with required `tool_name="opencode"` and required `tool_version` (nullable when session version is unavailable) to `sce hooks diff-trace` via STDIN JSON; the Rust hook runtime validates required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id`, required nullable/non-empty `tool_version`, plus required `time`, persists nullable `model_id` / `tool_version` attribution without session fallback, and fails open for runtime intake failures by logging `sce.hooks.diff_trace.error` while returning hook success to the producer. ## Shared boundary with Claude runtime - OpenCode uses a generated TypeScript event runtime as an event-shape adapter before handing normalized diff-trace payloads to the shared Rust hook intake. - Claude registration uses generated `.claude/settings.json` command hooks that call `.claude/hooks/run-sce-or-show-install-guidance.sh` before `sce hooks` (no TypeScript runtime intermediary): matched `PostToolUse Write|Edit|MultiEdit|NotebookEdit` pipes the raw hook event to `sce hooks diff-trace`, and supported conversation events pipe raw hook events to `sce hooks conversation-trace`; `SessionStart` is no longer registered. - Rust `diff-trace` intake detects Claude payloads via `hook_event_name` and derives structured patches from the raw JSON with `payload_type="structured"`; OpenCode normalized payloads (no `hook_event_name`) are stored as `payload_type="patch"`. -- `sce hooks session-model` is no longer a supported shared Rust boundary. Rust remains the only writer of AgentTraceDb `diff_traces` rows; the old `session_models` fallback API/table remains temporarily until the planned cleanup tasks remove it, but no active hook route writes new session-model rows and no parsed `context/tmp/*-diff-trace.json` artifacts are written. -- Claude attribution differs from OpenCode attribution: OpenCode reads provider/model data from the OpenCode event and includes `model_id` in the payload; for Claude `diff-trace`, Rust currently resolves missing `model_id` and `tool_version` from AgentTraceDb `session_models` at persistence time when available, otherwise persisting nullable attribution. +- `sce hooks session-model` is no longer a supported shared Rust boundary. Rust remains the only writer of AgentTraceDb `diff_traces` rows; no active runtime path reads or writes `session_models` and no parsed `context/tmp/*-diff-trace.json` artifacts are written by the TypeScript runtime. +- Claude attribution differs from OpenCode attribution: OpenCode reads provider/model data from the OpenCode event and includes `model_id` in the payload, while Claude `diff-trace` best-effort extracts direct model metadata from the raw `PostToolUse` payload and leaves `model_id` nullable when Claude omits it. ## Claude derivation golden tests diff --git a/context/sce/shared-turso-db.md b/context/sce/shared-turso-db.md index 6e0c7ea8..9dd29055 100644 --- a/context/sce/shared-turso-db.md +++ b/context/sce/shared-turso-db.md @@ -57,7 +57,7 @@ the secret value. No plaintext fallback exists. The shared module is exported from `cli/src/services/mod.rs` and compile-checked. Current concrete wrappers: - `cli/src/services/local_db/mod.rs`: `LocalDb = TursoDb`, with `LocalDbSpec` resolving `local_db_path()` and declaring zero migrations. -- `cli/src/services/agent_trace_db/mod.rs`: `AgentTraceDb = TursoDb`, with `AgentTraceDbSpec` resolving the legacy global `agent_trace_db_path()` fallback and loading ordered Agent Trace migrations for `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `session_models`, `messages`, and `parts` plus supporting indexes and triggers. Active hook runtime paths resolve per-checkout DB files through checkout identity and use `AgentTraceDb::open_for_hooks_without_migrations_at(path)` plus `ensure_schema_ready_for_hooks()` first, falling back to migration-running `AgentTraceDb::open_at(path)` when the per-checkout DB is absent or incomplete. +- `cli/src/services/agent_trace_db/mod.rs`: `AgentTraceDb = TursoDb`, with `AgentTraceDbSpec` resolving the legacy global `agent_trace_db_path()` fallback and loading ordered Agent Trace migrations for `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `messages`, and `parts` plus supporting indexes/triggers and the active `diff_traces.payload_type` migration. Active hook runtime paths resolve per-checkout DB files through checkout identity and use `AgentTraceDb::open_for_hooks_without_migrations_at(path)` plus `ensure_schema_ready_for_hooks()` first, falling back to migration-running `AgentTraceDb::open_at(path)` when the per-checkout DB is absent or incomplete. - `cli/src/services/auth_db/mod.rs`: `AuthDb = EncryptedTursoDb`, with `AuthDbSpec` resolving `auth_db_path()` and loading ordered auth migrations where baseline SQL creates `auth_credentials` without `user_id`, with `updated_at`, and a trigger that auto-refreshes `updated_at` on row updates. All three database wrappers (local DB, auth DB, Agent Trace DB) have lifecycle providers. `lifecycle_providers(include_hooks)` registers database providers in order `LocalDbLifecycle` → `AuthDbLifecycle` → `AgentTraceDbLifecycle` before optional hooks. Setup initializes local/auth DBs, establishes Agent Trace checkout identity, initializes the per-checkout Agent Trace DB with migrations, and records its registry path; hook runtime keeps lazy initialization/upgrade as a fallback when setup has not run or schema metadata is incomplete. Doctor diagnoses/fixes DB parent/path readiness through lifecycle providers. From 5be29fd09bd1c8479cae8e18e2b8a14f232c5457 Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Wed, 1 Jul 2026 10:46:40 +0200 Subject: [PATCH 4/9] hooks: Clarify direct diff-trace attribution test Rename the diff-trace persistence test to reflect that model and tool metadata now come directly from the payload, and mark the session-model fallback removal task complete. Plan: remove-session-models-direct-claude-model-id Task: T04 Co-authored-by: SCE --- cli/src/services/hooks/mod.rs | 4 ++-- context/plans/remove-session-models-direct-claude-model-id.md | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index 5f2f08ae..07b58ffb 100644 --- a/cli/src/services/hooks/mod.rs +++ b/cli/src/services/hooks/mod.rs @@ -2251,7 +2251,7 @@ mod tests { } #[test] - fn diff_trace_db_persistence_uses_resolved_tool_version() { + fn diff_trace_db_persistence_uses_direct_payload_model_and_tool_version() { let payload = diff_trace_payload(Some("direct-model"), None); persist_diff_trace_payload_to_agent_trace_db_with( @@ -2269,7 +2269,7 @@ mod tests { Ok(()) }, ) - .expect("resolved diff-trace attribution should be persisted"); + .expect("direct diff-trace attribution should be persisted"); } #[test] diff --git a/context/plans/remove-session-models-direct-claude-model-id.md b/context/plans/remove-session-models-direct-claude-model-id.md index d33eda9e..3e778965 100644 --- a/context/plans/remove-session-models-direct-claude-model-id.md +++ b/context/plans/remove-session-models-direct-claude-model-id.md @@ -72,7 +72,7 @@ This intentionally accepts the known Claude limitation that some events, especia - Verification notes (commands or checks): targeted hooks tests for Claude diff-trace payload parsing/persistence; inspect `parse_claude_diff_trace_payload` no longer hardcodes `model_id: None` when payload has extractable model info. - Completion evidence (2026-07-01): Added direct Claude model extraction in `cli/src/services/hooks/mod.rs` for supported structured `PostToolUse` diff-trace payloads. The parser now reads direct `model`/`model_id`/`modelId` strings or nested `model.id`/`model.model`/`model.name`, trims non-empty values, normalizes them with the existing `claude/` prefix rule, and leaves `model_id=None` when absent. Added hooks tests covering direct model extraction plus DB insert mirroring, nested already-prefixed extraction, and omitted model metadata. Context sync classified this as an important localized Agent Trace runtime-contract change and refreshed current-state hook/DB context files plus root summaries. `cargo test claude_diff_trace_payload -- --exact` was attempted but blocked by the repository bash policy requiring `nix flake check`; `nix flake check` passed (`all checks passed`), and `nix run .#pkl-check-generated` passed (`Generated outputs are up to date.`). -- [ ] T04: `Remove diff-trace session fallback and repair tests` (status:todo) +- [x] T04: `Remove diff-trace session fallback and repair tests` (status:done) - Task ID: T04 - Goal: Make `diff_traces` the sole primary model attribution source by removing fallback resolution from `session_models`. - Boundaries (in/out of scope): @@ -80,6 +80,7 @@ This intentionally accepts the known Claude limitation that some events, especia - Out — Changing artifact persistence to `context/tmp`; changing OpenCode required/optional model validation except where compile cleanup requires it. - Done when: `sce hooks diff-trace` never queries `session_models`; direct payload model/tool metadata is persisted as-is; missing model metadata remains nullable and non-failing. - Verification notes (commands or checks): targeted hooks tests for direct model persistence and missing-model persistence; grep for `session_model`/`session_models` in Rust hook and DB code should only find historical context/docs before context sync. + - Completion evidence (2026-07-01): Verified the active diff-trace runtime no longer contains `ResolvedDiffTraceAttribution`, `resolve_diff_trace_attribution`, or session-model DB lookup logic. `run_diff_trace_subcommand_from_payload_with` now passes `payload.model_id.as_deref()` and `payload.tool_version.as_deref()` directly to Agent Trace DB persistence, and the DB insert helper writes those values as-is. Repaired hook tests by renaming the direct-payload persistence test and adding missing-model coverage that asserts `model_id=None` and `tool_version=None` remain nullable/non-failing. Focused `cargo test diff_trace_db_persistence -- --exact` was attempted through `nix develop` but blocked by repository bash policy requiring `nix flake check`; `nix flake check` passed (`all checks passed`). Rust grep for `session_model|session_models|SessionModel|ResolvedDiffTraceAttribution|resolve_diff_trace_attribution` now finds only the retired migration ID and fresh-schema absence assertion in `agent_trace_db` code. - [ ] T05: `Sync Agent Trace context and docs` (status:todo) - Task ID: T05 From 2eb6df248e5fada3b9a72c90c72bcf4e824208e1 Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Wed, 1 Jul 2026 11:01:15 +0200 Subject: [PATCH 5/9] agent-trace: Document direct diff-trace attribution Clarify that diff_traces.model_id is the only active model-attribution storage for diff traces. Document best-effort Claude model metadata extraction and nullable missing attribution, and remove stale current-state wording about active session-model intake. Plan: remove-session-models-direct-claude-model-id Task: T05 Co-authored-by: SCE --- context/plans/remove-session-models-direct-claude-model-id.md | 3 ++- context/sce/agent-trace-db.md | 4 ++-- context/sce/agent-trace-hooks-command-routing.md | 4 ++-- context/sce/claude-raw-hook-capture.md | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/context/plans/remove-session-models-direct-claude-model-id.md b/context/plans/remove-session-models-direct-claude-model-id.md index 3e778965..cf20ee3d 100644 --- a/context/plans/remove-session-models-direct-claude-model-id.md +++ b/context/plans/remove-session-models-direct-claude-model-id.md @@ -82,7 +82,7 @@ This intentionally accepts the known Claude limitation that some events, especia - Verification notes (commands or checks): targeted hooks tests for direct model persistence and missing-model persistence; grep for `session_model`/`session_models` in Rust hook and DB code should only find historical context/docs before context sync. - Completion evidence (2026-07-01): Verified the active diff-trace runtime no longer contains `ResolvedDiffTraceAttribution`, `resolve_diff_trace_attribution`, or session-model DB lookup logic. `run_diff_trace_subcommand_from_payload_with` now passes `payload.model_id.as_deref()` and `payload.tool_version.as_deref()` directly to Agent Trace DB persistence, and the DB insert helper writes those values as-is. Repaired hook tests by renaming the direct-payload persistence test and adding missing-model coverage that asserts `model_id=None` and `tool_version=None` remain nullable/non-failing. Focused `cargo test diff_trace_db_persistence -- --exact` was attempted through `nix develop` but blocked by repository bash policy requiring `nix flake check`; `nix flake check` passed (`all checks passed`). Rust grep for `session_model|session_models|SessionModel|ResolvedDiffTraceAttribution|resolve_diff_trace_attribution` now finds only the retired migration ID and fresh-schema absence assertion in `agent_trace_db` code. -- [ ] T05: `Sync Agent Trace context and docs` (status:todo) +- [x] T05: `Sync Agent Trace context and docs` (status:done) - Task ID: T05 - Goal: Update durable context/docs to describe the simplified direct `diff_traces.model_id` attribution model. - Boundaries (in/out of scope): @@ -90,6 +90,7 @@ This intentionally accepts the known Claude limitation that some events, especia - Out — Adding historical narrative; editing unrelated Agent Trace design artifacts retained as historical references unless they claim current behavior. - Done when: durable current-state context no longer says active runtime uses `session_models`; docs state Claude model IDs are direct best-effort diff-trace metadata and may be nullable/omitted. - Verification notes (commands or checks): `nix run .#pkl-check-generated`; focused grep for `session_models`/`session-model` in current-state docs to ensure remaining references are intentional historical references or removed. + - Completion evidence (2026-07-01): Refreshed current-state Agent Trace docs to state that `diff_traces.model_id` is the only active model-attribution storage for diff traces, direct payload `model_id`/`tool_version` values are persisted as-is, and Claude structured `PostToolUse` intake performs best-effort direct model metadata extraction while leaving `model_id` nullable when omitted. Updated `context/sce/agent-trace-db.md`, `context/sce/agent-trace-hooks-command-routing.md`, `context/sce/claude-raw-hook-capture.md`, and stale root overview text that still described active `session-model` intake. Context sync classified the task as an important localized Agent Trace runtime-contract documentation update with root overview repair required; architecture/glossary/patterns/context-map already matched current code truth. `nix run .#pkl-check-generated` passed (`Generated outputs are up to date.`). Focused current-state-doc grep for `session_models|session-model|SessionModel` found only intentional removed/retired/fallback-removed references plus historical plan text. - [ ] T06: `Validation and cleanup` (status:todo) - Task ID: T06 diff --git a/context/sce/agent-trace-db.md b/context/sce/agent-trace-db.md index d348315e..e0c13054 100644 --- a/context/sce/agent-trace-db.md +++ b/context/sce/agent-trace-db.md @@ -173,8 +173,8 @@ Both triggers compare `OLD.*` vs `NEW.*` for all mutable columns (excluding `upd `sce hooks diff-trace` is the current runtime writer for `diff_traces`. -- The hook path validates required STDIN `{ sessionID, diff, time, tool_name, tool_version }` before persistence, with `model_id` accepted as optional (absent or `null`) and `tool_version` accepted as nullable. Missing attribution remains `None`; there is no session-level fallback lookup. -- Direct payload `model_id` and `tool_version` values pass into `DiffTraceInsert` as-is. The `payload_type` field is set to `PAYLOAD_TYPE_PATCH` for `OpenCode` normalized diff-trace payloads and `PAYLOAD_TYPE_STRUCTURED` for Claude structured `PostToolUse` payloads. Claude structured intake extracts direct `model`/`model_id`/`modelId` metadata, including nested `model.id` / `model.model` / `model.name`, and normalizes values with the `claude/` prefix when present. +- The hook path validates required STDIN `{ sessionID, diff, time, tool_name, tool_version }` before persistence, with `model_id` accepted as optional (absent or `null`) and `tool_version` accepted as nullable. Missing attribution remains `None`; `diff_traces.model_id` is the only active model-attribution storage for diff traces and there is no session-level fallback lookup. +- Direct payload `model_id` and `tool_version` values pass into `DiffTraceInsert` as-is. The `payload_type` field is set to `PAYLOAD_TYPE_PATCH` for `OpenCode` normalized diff-trace payloads and `PAYLOAD_TYPE_STRUCTURED` for Claude structured `PostToolUse` payloads. Claude structured intake best-effort extracts direct `model`/`model_id`/`modelId` metadata, including nested `model.id` / `model.model` / `model.name`, normalizes values with the `claude/` prefix when present, and leaves `model_id` nullable when metadata is absent. - `time` is accepted as a `u64` Unix epoch millisecond input and must fit the signed `i64` `time_ms` column before any persistence starts. - The hook inserts the parsed payload fields plus nullable direct attribution through `AgentTraceDb::insert_diff_trace()` without writing a parsed-payload artifact under `context/tmp`. - AgentTraceDb open/insert failures are logged and reflected in deterministic success text as failed DB persistence; no artifact fallback is created. diff --git a/context/sce/agent-trace-hooks-command-routing.md b/context/sce/agent-trace-hooks-command-routing.md index 6f2a2cb9..e5e7b748 100644 --- a/context/sce/agent-trace-hooks-command-routing.md +++ b/context/sce/agent-trace-hooks-command-routing.md @@ -63,10 +63,10 @@ - Current command-surface success output is: `post-commit hook processed intersection: commit=, intersection_files=`. - `post-rewrite` is a deterministic no-op entrypoint. - `diff-trace` reads STDIN JSON and classifies the payload: - - **Claude structured payloads** (detected by presence of top-level `hook_event_name`): the STDIN JSON is validated through `derive_claude_structured_patch`. Supported `PostToolUse` `Write` create and `Edit` structured-patch events produce a `DiffTracePayload` with `payload_type="structured"` and the raw event JSON stored as the `diff` column without conversion to unified-diff text. Direct model metadata is extracted from top-level `model`, `model_id`, or `modelId`, or from nested `model.id`, `model.model`, or `model.name`; non-empty values are normalized with the `claude/` prefix. If model metadata is absent, `model_id` remains nullable. Unsupported Claude events (non-`PostToolUse`, unsupported tools, invalid payloads) produce a deterministic `NoOp` success result. + - **Claude structured payloads** (detected by presence of top-level `hook_event_name`): the STDIN JSON is validated through `derive_claude_structured_patch`. Supported `PostToolUse` `Write` create and `Edit` structured-patch events produce a `DiffTracePayload` with `payload_type="structured"` and the raw event JSON stored as the `diff` column without conversion to unified-diff text. Direct model metadata is extracted best-effort from top-level `model`, `model_id`, or `modelId`, or from nested `model.id`, `model.model`, or `model.name`; non-empty values are normalized with the `claude/` prefix. If Claude omits model metadata, `model_id` remains nullable and downstream Agent Trace JSON omits contributor `model_id`. Unsupported Claude events (non-`PostToolUse`, unsupported tools, invalid payloads) produce a deterministic `NoOp` success result. - **OpenCode normalized payloads** (no `hook_event_name`): existing flat `{ sessionID, diff, time, model_id?, tool_name, tool_version }` validation applies unchanged, with `payload_type="patch"`. - The `DiffTracePayload` struct carries a `payload_type: String` field consumed by `persist_diff_trace_payload_to_agent_trace_db_with` to pass the correct discriminator to `DiffTraceInsert`. - - Missing `model_id` or `tool_version` stays nullable; Rust does not perform session-level fallback attribution. Direct payload values are persisted as-is after payload-specific validation/normalization. + - Missing `model_id` or `tool_version` stays nullable; Rust does not perform session-level fallback attribution. Direct payload values are persisted as-is after payload-specific validation/normalization, making `diff_traces.model_id` the only active model-attribution storage for diff traces. - Persistence: resolves the current per-checkout AgentTraceDb lazily and inserts the parsed payload fields via `DiffTraceInsert` + `insert_diff_trace()` using nullable direct `model_id` and `tool_version`. No parsed-payload artifact is written under `context/tmp`. - Current producers are the OpenCode agent-trace plugin and the generated Claude `sce hooks` command hooks (no TypeScript intermediary). - OpenCode forwards user-message `message` diffs with `tool_name="opencode"`, always including `model_id`, and nullable OpenCode client-version metadata. diff --git a/context/sce/claude-raw-hook-capture.md b/context/sce/claude-raw-hook-capture.md index 6c3baee7..7af44a4f 100644 --- a/context/sce/claude-raw-hook-capture.md +++ b/context/sce/claude-raw-hook-capture.md @@ -6,7 +6,7 @@ The `sce hooks claude-capture ` CLI route, `ClaudeCaptureEvent`, `Ho Rust now exposes these active intakes for Claude/OpenCode editor runtimes: -- `sce hooks diff-trace` — STDIN JSON intake for normalized or Claude structured diff-trace payloads with optional/nullable attribution. When `model_id` or `tool_version` is missing, Rust resolves available values from existing `session_models` rows by `(tool_name, session_id)` and otherwise persists nullable attribution to AgentTraceDb without writing raw hook artifacts. +- `sce hooks diff-trace` — STDIN JSON intake for OpenCode normalized or Claude structured diff-trace payloads with optional/nullable direct attribution. Rust persists direct payload-derived `model_id` and `tool_version` values as-is; Claude model metadata is extracted best-effort from the raw `PostToolUse` event and remains nullable when Claude omits it. No session-level fallback lookup is performed. - `sce hooks conversation-trace` — STDIN JSON intake for normalized mixed-batch message/part payloads and supported raw Claude `UserPromptSubmit`, `Stop`, and `PostToolUse` events. Runtime intake failures log `sce.hooks.conversation_trace.error` and fail open to the hook producer. `sce hooks session-model` is no longer supported, and generated Claude settings no longer register `SessionStart` for Agent Trace attribution. @@ -28,7 +28,7 @@ The generated Claude TypeScript runtime at `config/.claude/plugins/sce-agent-tra - Claude settings call the generated Bash helper `.claude/hooks/run-sce-or-show-install-guidance.sh` via generated `.claude/settings.json` command hooks before invoking `sce`: matched `PostToolUse Write|Edit|MultiEdit|NotebookEdit` pipes raw hook event JSON to `sce hooks diff-trace`, supported conversation events pipe raw hook event JSON to `sce hooks conversation-trace`, and `PreToolUse Bash` pipes raw hook event JSON to `sce policy bash`. The helper emits `sce CLI not found. Install it from https://sce.crocoder.dev/docs/getting-started#install-cli` and exits successfully when `sce` is missing; when `sce` exists it `exec`s the original command arguments so Rust receives stdin and owns stdout/stderr/exit behavior. Rust handles extraction, validation, and persistence without a TypeScript intermediary. `SessionStart` is no longer registered. - The former Claude TypeScript runtime at `config/.claude/plugins/sce-agent-trace.ts` was removed in T07 of the `claude-rust-diff-trace` plan. - Rust owns normalized persistence: `diff-trace` inserts into `diff_traces` with `payload_type` classification (`"patch"` for OpenCode, `"structured"` for Claude), while `conversation-trace` inserts normalized messages/parts. -- Claude `diff-trace` missing `model_id` and `tool_version` values are resolved from `session_models` at persistence time when available, otherwise stored as nullable attribution; OpenCode sends `model_id` directly and may send nullable `tool_version`. +- Claude `diff-trace` attempts best-effort direct `model_id` extraction from the raw `PostToolUse` payload and stores nullable direct attribution in `diff_traces`; OpenCode sends `model_id` directly and may send nullable `tool_version`. Missing metadata is persisted as `NULL` and is not resolved from any session-level cache. - No raw Claude hook payload artifacts are written by TypeScript or Rust. See also: [agent-trace-hooks-command-routing.md](./agent-trace-hooks-command-routing.md), [../context-map.md](../context-map.md) From 8150c458c540be1d04d601bae0176df129ef340c Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Wed, 1 Jul 2026 11:35:28 +0200 Subject: [PATCH 6/9] plans: Mark session-model removal validation complete Plan: remove-session-models-direct-claude-model-id Task: T06 Record final validation evidence, success-criteria checks, and residual risk notes for the session-model removal plan. --- ...e-session-models-direct-claude-model-id.md | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/context/plans/remove-session-models-direct-claude-model-id.md b/context/plans/remove-session-models-direct-claude-model-id.md index cf20ee3d..59d83658 100644 --- a/context/plans/remove-session-models-direct-claude-model-id.md +++ b/context/plans/remove-session-models-direct-claude-model-id.md @@ -92,7 +92,7 @@ This intentionally accepts the known Claude limitation that some events, especia - Verification notes (commands or checks): `nix run .#pkl-check-generated`; focused grep for `session_models`/`session-model` in current-state docs to ensure remaining references are intentional historical references or removed. - Completion evidence (2026-07-01): Refreshed current-state Agent Trace docs to state that `diff_traces.model_id` is the only active model-attribution storage for diff traces, direct payload `model_id`/`tool_version` values are persisted as-is, and Claude structured `PostToolUse` intake performs best-effort direct model metadata extraction while leaving `model_id` nullable when omitted. Updated `context/sce/agent-trace-db.md`, `context/sce/agent-trace-hooks-command-routing.md`, `context/sce/claude-raw-hook-capture.md`, and stale root overview text that still described active `session-model` intake. Context sync classified the task as an important localized Agent Trace runtime-contract documentation update with root overview repair required; architecture/glossary/patterns/context-map already matched current code truth. `nix run .#pkl-check-generated` passed (`Generated outputs are up to date.`). Focused current-state-doc grep for `session_models|session-model|SessionModel` found only intentional removed/retired/fallback-removed references plus historical plan text. -- [ ] T06: `Validation and cleanup` (status:todo) +- [x] T06: `Validation and cleanup` (status:done) - Task ID: T06 - Goal: Run full repository validation and clean up stale artifacts after the session-model removal. - Boundaries (in/out of scope): @@ -100,7 +100,34 @@ This intentionally accepts the known Claude limitation that some events, especia - Out — New product behavior beyond the planned session-model removal and direct Claude diff-trace model capture. - Done when: `nix run .#pkl-check-generated` and `nix flake check` pass; no dead `session_models` runtime references remain; plan execution evidence is recorded. - Verification notes (commands or checks): `nix run .#pkl-check-generated`; `nix flake check`; optional targeted grep for `session_models`, `SessionModel`, and `session-model` to verify only intentional historical/docs references remain. + - Completion evidence (2026-07-01): Final validation and cleanup completed. Initial worktree was clean. Focused runtime/current-state search for `session_models|SessionModel|session-model` outside active plan/tmp files found only intentional current-state documentation references plus the runtime-retained retired migration ID (`RETIRED_AGENT_TRACE_MIGRATION_IDS`) and fresh-schema absence assertion in `agent_trace_db` tests; no dead active session-model runtime API/route references were found, so no code cleanup was required. `nix run .#pkl-check-generated` passed (`Generated outputs are up to date.`). `nix flake check` passed (`all checks passed`). + +## Validation Report + +### Commands run + +- `rg -n "session_models|SessionModel|session-model" --glob '!context/plans/**' --glob '!context/tmp/**' --glob '!target/**' --glob '!cli/target/**'` -> exit 0; matches are intentional current-state documentation references plus `RETIRED_AGENT_TRACE_MIGRATION_IDS` and a fresh-schema absence assertion in `agent_trace_db` tests. +- `nix run .#pkl-check-generated` -> exit 0; output included `Generated outputs are up to date.` +- `nix flake check` -> exit 0; output included `all checks passed`. + +### Success-criteria verification + +- [x] `session_models` is no longer part of the Agent Trace DB schema for fresh databases -> confirmed by T02 evidence and final grep showing only retired migration metadata plus fresh-schema absence assertion remain in runtime code. +- [x] Rust Agent Trace DB code no longer exposes session-model API helpers -> confirmed by final grep and prior T02 evidence. +- [x] `sce hooks session-model` is removed from CLI parsing/runtime/generated producers -> confirmed by T01 evidence and final context/code search showing only removed-route documentation references. +- [x] `sce hooks diff-trace` no longer looks up missing attribution from `session_models` -> confirmed by T04 evidence and final grep. +- [x] Claude structured diff-trace parsing attempts direct model extraction -> confirmed by T03 evidence and current context in `context/sce/agent-trace-hooks-command-routing.md`. +- [x] `diff_traces.model_id` remains nullable and receives direct payload-derived model attribution only -> confirmed by T03/T04 evidence and current context in `context/sce/agent-trace-db.md`. +- [x] Agent Trace JSON remains valid when `model_id` is absent -> covered by successful `nix flake check` and T03/T04 missing-model evidence. + +### Failed checks and follow-ups + +- None. + +### Residual risks + +- Existing upgraded user databases may still physically contain the retired `session_models` table; runtime no longer reads or writes it, and `AgentTraceDbSpec::retired_migration_ids()` prevents readiness warnings for that retired migration ID. ## Open questions -- Should existing user databases with an already-created `session_models` table actively drop it via a new migration, or is it acceptable to leave the unused table in upgraded databases while removing it from the active runtime/fresh schema? This should be decided during T02 before changing migrations. +- None remaining. T02 resolved the migration strategy by retiring `015_create_session_models` from the fresh schema while allowing already-upgraded databases to retain the unused table without runtime reads/writes. From ddf99b845046eea4ab03ff40cf4f29cf9aa5cfd6 Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Wed, 1 Jul 2026 13:05:32 +0200 Subject: [PATCH 7/9] db: Remove retired migration IDs mechanism Remove the RETIRED_AGENT_TRACE_MIGRATION_IDS constant, the DbSpec::retired_migration_ids() trait method, all overrides, and the retired-IDs filtering logic in migration_metadata_problems(). This mechanism was introduced to accommodate already-upgraded databases with the old 015_create_session_models migration, but all development databases have since been recreated and no ongoing compatibility is needed. Also remove the unused MAX_TRACE_FILE_CREATE_ATTEMPTS constant from hooks/mod.rs (dead code). Update context files to reflect the removal and add a follow-up note to the predecessor completed plan. --- .claude/settings.json | 12 +----------- cli/src/services/agent_trace_db/mod.rs | 14 -------------- cli/src/services/db/mod.rs | 8 -------- cli/src/services/hooks/mod.rs | 1 - context/architecture.md | 2 +- context/glossary.md | 1 - context/patterns.md | 2 +- context/sce/agent-trace-db.md | 2 +- 8 files changed, 4 insertions(+), 38 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 51c102e7..090ced25 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,16 +1,6 @@ { "$schema": "https://json.schemastore.org/claude-code-settings.json", "hooks": { - "SessionStart": [ - { - "hooks": [ - { - "type": "command", - "command": "bash .claude/hooks/run-sce-or-show-install-guidance.sh sce hooks session-model" - } - ] - } - ], "PreToolUse": [ { "matcher": "Bash", @@ -62,4 +52,4 @@ } ] } -} \ No newline at end of file +} diff --git a/cli/src/services/agent_trace_db/mod.rs b/cli/src/services/agent_trace_db/mod.rs index 8beffcad..6c4f55e7 100644 --- a/cli/src/services/agent_trace_db/mod.rs +++ b/cli/src/services/agent_trace_db/mod.rs @@ -69,8 +69,6 @@ pub const INSERT_PART_SQL: &str = "INSERT INTO parts (type, text, message_id, session_id, generated_at_unix_ms) VALUES (?1, ?2, ?3, ?4, ?5)"; -const RETIRED_AGENT_TRACE_MIGRATION_IDS: &[&str] = &["015_create_session_models"]; - /// Agent trace database configuration. pub struct AgentTraceDbSpec; @@ -87,10 +85,6 @@ impl DbSpec for AgentTraceDbSpec { generated_migrations::AGENT_TRACE_MIGRATIONS } - fn retired_migration_ids() -> &'static [&'static str] { - RETIRED_AGENT_TRACE_MIGRATION_IDS - } - fn db_config_key() -> &'static str { "agent_trace_db" } @@ -594,10 +588,6 @@ mod tests { generated_migrations::AGENT_TRACE_MIGRATIONS } - fn retired_migration_ids() -> &'static [&'static str] { - RETIRED_AGENT_TRACE_MIGRATION_IDS - } - fn db_config_key() -> &'static str { "agent_trace_db" } @@ -621,10 +611,6 @@ mod tests { generated_migrations::AGENT_TRACE_MIGRATIONS } - fn retired_migration_ids() -> &'static [&'static str] { - RETIRED_AGENT_TRACE_MIGRATION_IDS - } - fn db_config_key() -> &'static str { "agent_trace_db" } diff --git a/cli/src/services/db/mod.rs b/cli/src/services/db/mod.rs index 1267aa64..df37e607 100644 --- a/cli/src/services/db/mod.rs +++ b/cli/src/services/db/mod.rs @@ -53,12 +53,6 @@ pub trait DbSpec { /// Ordered embedded migration SQL files as `(id, sql)` pairs. fn migrations() -> &'static [(&'static str, &'static str)]; - /// Migration IDs that are no longer applied to fresh databases but may be - /// present in already-upgraded databases. - fn retired_migration_ids() -> &'static [&'static str] { - &[] - } - /// Config-file lookup key under `policies.database_retry`. /// One of `"local_db"`, `"agent_trace_db"`, `"auth_db"`. fn db_config_key() -> &'static str; @@ -645,11 +639,9 @@ impl TursoDb { problems.push(format!("missing migrations {}", missing_ids.join(", "))); } - let retired_ids = M::retired_migration_ids(); let unexpected_ids = applied_ids .iter() .filter(|applied_id| !expected_ids.iter().any(|id| id == &applied_id.as_str())) - .filter(|applied_id| !retired_ids.iter().any(|id| id == &applied_id.as_str())) .map(String::as_str) .collect::>(); if !unexpected_ids.is_empty() { diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index 07b58ffb..4cac66fe 100644 --- a/cli/src/services/hooks/mod.rs +++ b/cli/src/services/hooks/mod.rs @@ -34,7 +34,6 @@ pub mod lifecycle; pub const NAME: &str = "hooks"; pub const CANONICAL_SCE_COAUTHOR_TRAILER: &str = "Co-authored-by: SCE "; -const MAX_TRACE_FILE_CREATE_ATTEMPTS: u64 = 1_000_000; const CLAUDE_MODEL_ID_PREFIX: &str = "claude/"; type PayloadValidationError = fn(&str) -> String; diff --git a/context/architecture.md b/context/architecture.md index 43e72a2f..367878c1 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -116,7 +116,7 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/db/mod.rs` provides the shared generic Turso infrastructure seam: `DbSpec` supplies a service-specific name, path, ordered embedded migrations, and config-file lookup key (`db_config_key()`), while `TursoDb` owns parent-directory creation, `Builder::new_local(...)` initialization (with `experimental_multiprocess_wal(true)` for safe concurrent access), Turso connection setup, tokio current-thread runtime bridging, retry-backed blocking `execute`/`query`/`query_values`/`query_map` wrappers, and generic migration execution with per-database `__sce_migrations` metadata. `TursoDb::new()` and `EncryptedTursoDb::new()` wrap only their local open/connect block in `run_with_retry_sync` using a config-driven connection-open policy resolved from the `DATABASE_RETRY_CONFIG` `OnceLock` with fallback to hardcoded defaults, while operation methods use a config-driven operation policy from the same source. `query_values()` returns fully fetched column names plus raw `turso::Value` rows for deterministic operator-facing rendering; `query_map()` retries the initial query and row-fetch loop, then applies caller row mapping after retry completion. Migration execution is not retried. The same module also provides `EncryptedTursoDb`, a structurally parallel encrypted adapter that resolves the encryption key through `encryption_key::get_or_create_encryption_key()`, enables Turso local encryption with strict `aegis256` cipher selection, and exposes retry-backed synchronous wrappers plus migration execution. `cli/src/services/db/encryption_key.rs` first derives a Turso-compatible 64-character hex key from non-empty `SCE_AUTH_DB_ENCRYPTION_KEY` env-secret text when present, otherwise falls back to keyring-backed credential-store get-or-create behavior; no plaintext auth DB fallback exists. - `cli/src/services/local_db/mod.rs` provides the concrete local DB spec and `LocalDb` type alias over the shared generic `TursoDb` adapter. `LocalDbSpec` resolves the deterministic persistent runtime DB target through the shared default-path seam and declares no local migrations; `TursoDb` supplies retry-backed blocking `execute`/`query`, parent-directory creation, Turso connection setup, tokio current-thread runtime bridging, and generic migration execution. - `cli/src/services/auth_db/mod.rs` provides the encrypted auth DB spec and `AuthDb` type alias over `EncryptedTursoDb`. `AuthDbSpec` resolves `/sce/auth.db` through the shared default-path seam and embeds ordered auth migrations. Auth DB lifecycle setup/doctor integration is wired through `AuthDbLifecycle`; auth command/token-storage reads/writes are directed through `token_storage.rs`. -- `cli/src/services/agent_trace_db/mod.rs` provides the Agent Trace DB spec and `AgentTraceDb` type alias over `TursoDb`. `AgentTraceDbSpec` resolves the legacy `/sce/agent-trace.db` fallback through the shared default-path seam and embeds ordered fresh-start migrations `001..015` (`015_create_session_models` is retired/replaced by active `015_add_diff_traces_payload_type`) for `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `messages`, `parts`, `diff_traces.payload_type`, indexes, and triggers. `agent_traces.agent_trace_id` is `NOT NULL UNIQUE`. The module provides explicit-path `open_at(path)` and `open_for_hooks_without_migrations_at(path)` helpers for per-checkout DB files plus `DiffTraceInsert<'_>`/`insert_diff_trace()` including the `payload_type` discriminator, `PostCommitPatchIntersectionInsert<'_>`/`insert_post_commit_patch_intersection()`, `AgentTraceInsert<'_>`/`insert_agent_trace()`, `InsertMessageInsert`/`insert_message()` plus `insert_messages()` for parent message inserts with duplicate `(session_id, message_id)` writes ignored, `InsertPartInsert`/`insert_part()` plus `insert_parts()` for append-only part text writes, and `recent_diff_trace_patches(cutoff_time_ms, end_time_ms)` for inclusive chronological `diff_traces` reads that dispatch on `payload_type`, parse valid raw patch or structured payload rows, and return skipped malformed-row reports. `cli/src/services/agent_trace_db/lifecycle.rs` registers Agent Trace DB setup/doctor lifecycle behavior: setup creates/reuses and registers checkout identity, resolves `/sce/agent-trace-{checkout_id}.db`, initializes it with `AgentTraceDb::open_at(path)`, and records `database_path`; active hook runtime still resolves checkout identity and lazily creates or upgrades the per-checkout DB when setup has not run or schema metadata is incomplete. +- `cli/src/services/agent_trace_db/mod.rs` provides the Agent Trace DB spec and `AgentTraceDb` type alias over `TursoDb`. `AgentTraceDbSpec` resolves the legacy `/sce/agent-trace.db` fallback through the shared default-path seam and embeds ordered fresh-start migrations `001..015` (the former `015_create_session_models` was removed from fresh schema; current `015_add_diff_traces_payload_type` replaces it) for `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `messages`, `parts`, `diff_traces.payload_type`, indexes, and triggers. `agent_traces.agent_trace_id` is `NOT NULL UNIQUE`. The module provides explicit-path `open_at(path)` and `open_for_hooks_without_migrations_at(path)` helpers for per-checkout DB files plus `DiffTraceInsert<'_>`/`insert_diff_trace()` including the `payload_type` discriminator, `PostCommitPatchIntersectionInsert<'_>`/`insert_post_commit_patch_intersection()`, `AgentTraceInsert<'_>`/`insert_agent_trace()`, `InsertMessageInsert`/`insert_message()` plus `insert_messages()` for parent message inserts with duplicate `(session_id, message_id)` writes ignored, `InsertPartInsert`/`insert_part()` plus `insert_parts()` for append-only part text writes, and `recent_diff_trace_patches(cutoff_time_ms, end_time_ms)` for inclusive chronological `diff_traces` reads that dispatch on `payload_type`, parse valid raw patch or structured payload rows, and return skipped malformed-row reports. `cli/src/services/agent_trace_db/lifecycle.rs` registers Agent Trace DB setup/doctor lifecycle behavior: setup creates/reuses and registers checkout identity, resolves `/sce/agent-trace-{checkout_id}.db`, initializes it with `AgentTraceDb::open_at(path)`, and records `database_path`; active hook runtime still resolves checkout identity and lazily creates or upgrades the per-checkout DB when setup has not run or schema metadata is incomplete. - `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/mod.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/**`, and focused internal support seams for install-flow vs prompt-flow logic; `cli/src/services/setup/command.rs` owns the `SetupCommand` payload used by the static `RuntimeCommand` enum and executes against any context implementing repo-root scoping. Its install engine/orchestrator stages embedded files and uses a unified remove-and-replace policy (removing existing targets before swapping staged content, with deterministic recovery guidance on swap failure and no backup artifact creation), and formats deterministic completion messaging; required-hook install orchestration (`install_required_git_hooks`) follows the same remove-and-replace policy (removing existing hooks before swapping staged content, with deterministic recovery guidance on swap failure). The setup command derives a repo-root-scoped context before aggregating static lifecycle provider `setup` dispatch across providers (config → local_db → auth_db → agent_trace_db → hooks when requested), so setup providers consume only repo-root access from the scoped context. - `cli/src/services/setup/mod.rs` keeps those responsibilities inside one file for now, but the current ownership split is explicit: the inline `install` module owns repository-path normalization, staging/swap install behavior, required-hook installation, and filesystem safety guards, while the inline `prompt` module owns interactive target selection and prompt styling. diff --git a/context/glossary.md b/context/glossary.md index b894082c..f58a1f7d 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -48,7 +48,6 @@ - `TursoDb`: Generic unencrypted Turso database adapter in `cli/src/services/db/mod.rs`; owns parent-directory creation and Turso local open/connect flow wrapped in config-driven connection-open retry, then delegates synchronous `execute()`/`query()`/`query_map()` wrappers with config-driven query retry and migration execution through the shared internal `TursoConnectionCore` for a `DbSpec` implementation. - `TursoConnectionCore`: Internal shared operation core in `cli/src/services/db/mod.rs` used by both `TursoDb` and `EncryptedTursoDb`; owns the Turso connection and tokio current-thread runtime bridging used by the public adapter methods; generic embedded migration execution with per-database `__sce_migrations` metadata is delegated to `run_embedded_migrations` helpers. - `no-migration DB open path`: `TursoDb::open_without_migrations()` / `TursoDb::open_without_migrations_at(path)` plus the Agent Trace-specific `AgentTraceDb::open_for_hooks_without_migrations()` / `open_for_hooks_without_migrations_at(path)` seams; opens/connects a local Turso database with parent-directory creation and configured connection-open retry but does not create `__sce_migrations` or run embedded schema migrations. Active Agent Trace hook callers first try the explicit per-checkout no-migration path and then fall back to migration-running initialization when readiness fails. -- `retired migration IDs`: Method on `DbSpec` (`retired_migration_ids()`) that returns migration IDs that exist in upgraded databases but are no longer part of the fresh-schema migration list. The `migration_metadata_problems` check excludes retired IDs from the "unexpected migrations" warning, allowing existing databases to retain the unused table without triggering schema-readiness errors. - `TursoDb migration readiness check`: Public methods on `TursoDb` in `cli/src/services/db/mod.rs` for non-mutating schema-readiness verification: `migration_metadata_problems(&self) -> Result>` queries `__sce_migrations` metadata and compares applied IDs against `M::migrations()`, returning problems (missing table, incomplete migrations, unexpected migrations) or an empty list when ready; `ensure_schema_ready(&self, setup_guidance: &str) -> Result<()>` calls `migration_metadata_problems()` and bails with a formatted error including `M::db_name()` and the caller-provided guidance string when problems are found. `AgentTraceDb::ensure_schema_ready_for_hooks()` delegates to `TursoDb::ensure_schema_ready()` with the Agent Trace–specific `AGENT_TRACE_SCHEMA_SETUP_GUIDANCE` constant. - `database_retry config namespace`: Nested config namespace under `policies.database_retry` in `sce/config.json`, authored in `config/pkl/base/sce-config-schema.pkl` and parsed/resolved in `cli/src/services/config/mod.rs`. Supports per-database overrides (`local_db`, `agent_trace_db`, `auth_db`) each with optional `connection_open` and `query` objects containing `max_attempts`, `timeout_ms`, `initial_backoff_ms`, `max_backoff_ms`. Validated against JSON Schema at config load and surfaced in `sce config show`/`validate`. Wired into DB adapter constructors and operation methods via config-aware retry resolution with fallback to hardcoded defaults. - `DatabaseRetryConfig`: Rust type in `cli/src/services/config/mod.rs` holding parsed and validated per-database retry policy overrides (`local_db`/`agent_trace_db`/`auth_db`, each `Option`) from the `policies.database_retry` config namespace. Initialized at app startup via `DATABASE_RETRY_CONFIG` `OnceLock` and consumed by config-aware retry resolution in DB adapters. diff --git a/context/patterns.md b/context/patterns.md index 5037393e..30223225 100644 --- a/context/patterns.md +++ b/context/patterns.md @@ -140,7 +140,7 @@ - For commit-msg co-author policy seams, gate canonical trailer insertion on runtime controls (`SCE_DISABLED` plus the shared attribution-hooks enablement gate) plus the staged-diff AI-overlap evidence gate (`StagedDiffAiOverlapResult::Overlap` maps to `ai_contribution_present = true`; `NoOverlap` and `Error` both map to `false`), 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_DISABLED` opt-out env over `policies.attribution_hooks.enabled`, default `true`) so commit-msg attribution is enabled by default while explicit config `enabled = false` and truthy env opt-out still suppress it without adding hook-specific config parsing. - Do not assume conversation-trace retry/backfill/artifact persistence, retry replay, remap ingestion, or rewrite trace transformation are active in the current local-hook runtime; those paths are removed from or deferred beyond 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 route initialization through `LocalDb::new()`. As database services split, keep path/migration ownership in each `DbSpec`: `LocalDbSpec` owns the neutral local DB path with zero migrations, `AuthDbSpec` owns encrypted `/sce/auth.db` plus ordered auth migrations, `AgentTraceDbSpec` owns `/sce/agent-trace.db` plus ordered Agent Trace migrations for `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `messages`, and `parts` plus supporting indexes and triggers (migration `015_create_session_models` is retired from fresh schema), and shared Turso mechanics plus migration metadata stay in `TursoDb` / `EncryptedTursoDb`. +- 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 route initialization through `LocalDb::new()`. As database services split, keep path/migration ownership in each `DbSpec`: `LocalDbSpec` owns the neutral local DB path with zero migrations, `AuthDbSpec` owns encrypted `/sce/auth.db` plus ordered auth migrations, `AgentTraceDbSpec` owns `/sce/agent-trace.db` plus ordered Agent Trace migrations for `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `messages`, and `parts` plus supporting indexes and triggers (migration `015_create_session_models` was removed from fresh schema; current schema uses `015_add_diff_traces_payload_type`), and shared Turso mechanics plus migration metadata stay in `TursoDb` / `EncryptedTursoDb`. - 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/sce/agent-trace-db.md b/context/sce/agent-trace-db.md index e0c13054..19f151b8 100644 --- a/context/sce/agent-trace-db.md +++ b/context/sce/agent-trace-db.md @@ -76,7 +76,7 @@ Active hook runtime resolves per-checkout Agent Trace DB files: - `014_create_parts_updated_at_trigger.sql` - `015_add_diff_traces_payload_type.sql` (migration ID `015_add_diff_traces_payload_type`; adds `payload_type TEXT NOT NULL DEFAULT 'patch'` to `diff_traces`) -The former `015_create_session_models` migration is retired from the fresh schema and listed through `AgentTraceDbSpec::retired_migration_ids()` so already-upgraded databases that recorded that ID are not treated as having an unexpected migration. Current development state does not preserve compatibility with databases that recorded a previous `016_add_diff_traces_payload_type` ID; deleting and recreating the local Agent Trace DB is acceptable for this migration renumbering. +The former `015_create_session_models` migration was removed from the fresh schema when the `remove-session-models-direct-claude-model-id` plan cleaned up session-models support. The `retired_migration_ids()` compat mechanism and `RETIRED_AGENT_TRACE_MIGRATION_IDS` constant that previously accommodated upgraded databases with that migration ID were subsequently removed in the `remove-retired-migration-ids` plan, since all development databases have been recreated and no ongoing compatibility is needed. Current migration IDs go directly from `014_create_parts_updated_at_trigger` to `015_add_diff_traces_payload_type`. The shared `TursoDb` runner records applied IDs in the database-local `__sce_migrations` table. Existing Agent Trace DB files without metadata are brought forward by re-applying the idempotent migration set and recording each ID, so rerunning `sce setup` / `AgentTraceDb::open_at(path)` applies later Agent Trace migrations to an already-created per-checkout DB. From 4a7695a438792dec1fc5c9b016ce251e9a96863f Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Wed, 1 Jul 2026 17:30:47 +0200 Subject: [PATCH 8/9] trace: Remove session model status reporting Stop requiring the session_models table for trace DB readiness and remove session model counts from trace status statistics, text output, JSON output, aggregation, and related tests. Co-authored-by: SCE --- cli/src/services/trace/discovery.rs | 3 --- cli/src/services/trace/render_status.rs | 5 ----- cli/src/services/trace/render_status_all.rs | 13 +++---------- cli/src/services/trace/stats.rs | 17 +---------------- cli/src/services/trace/status.rs | 1 - cli/src/services/trace/status_all.rs | 2 -- 6 files changed, 4 insertions(+), 37 deletions(-) diff --git a/cli/src/services/trace/discovery.rs b/cli/src/services/trace/discovery.rs index e60ddeee..739ec388 100644 --- a/cli/src/services/trace/discovery.rs +++ b/cli/src/services/trace/discovery.rs @@ -25,7 +25,6 @@ const REQUIRED_TABLES: &[&str] = &[ "agent_traces", "messages", "parts", - "session_models", ]; /// Schema-readiness verdict for a discovered Agent Trace DB. @@ -298,8 +297,6 @@ mod tests { .expect("create messages"); db.execute("CREATE TABLE parts (id INTEGER PRIMARY KEY)", ()) .expect("create parts"); - db.execute("CREATE TABLE session_models (id INTEGER PRIMARY KEY)", ()) - .expect("create session_models"); drop(db); let discovered = discover_agent_trace_dbs_in(&dir).expect("discovery should succeed"); diff --git a/cli/src/services/trace/render_status.rs b/cli/src/services/trace/render_status.rs index b2700c99..a02847e5 100644 --- a/cli/src/services/trace/render_status.rs +++ b/cli/src/services/trace/render_status.rs @@ -31,7 +31,6 @@ fn render_text(report: &StatusReport) -> String { lines.push(format!("Diff traces: {}", stats.diff_traces)); lines.push(format!("Messages: {}", stats.messages)); lines.push(format!("Parts: {}", stats.parts)); - lines.push(format!("Session models: {}", stats.session_models)); lines.push(format!("Agent traces: {}", stats.agent_traces)); lines.push(format!( "Post-commit intersections: {}", @@ -72,7 +71,6 @@ fn render_json(report: &StatusReport) -> Result { "diff_traces": stats.diff_traces, "messages": stats.messages, "parts": stats.parts, - "session_models": stats.session_models, "agent_traces": stats.agent_traces, "post_commit_patch_intersections": stats.post_commit_patch_intersections, }), @@ -116,7 +114,6 @@ mod tests { diff_traces: 7, messages: 4, parts: 11, - session_models: 2, agent_traces: 3, post_commit_patch_intersections: 1, last_activity: Some(last), @@ -146,7 +143,6 @@ mod tests { assert!(rendered.contains("Diff traces: 7")); assert!(rendered.contains("Messages: 4")); assert!(rendered.contains("Parts: 11")); - assert!(rendered.contains("Session models: 2")); assert!(rendered.contains("Agent traces: 3")); assert!(rendered.contains("Post-commit intersections: 1")); assert!(rendered.contains("Last activity: 2026-06-28T")); @@ -186,7 +182,6 @@ mod tests { assert_eq!(value["stats"]["diff_traces"], 7); assert_eq!(value["stats"]["messages"], 4); assert_eq!(value["stats"]["parts"], 11); - assert_eq!(value["stats"]["session_models"], 2); assert_eq!(value["stats"]["agent_traces"], 3); assert_eq!(value["stats"]["post_commit_patch_intersections"], 1); assert!(value["last_activity"].is_string()); diff --git a/cli/src/services/trace/render_status_all.rs b/cli/src/services/trace/render_status_all.rs index b64e4486..7e557777 100644 --- a/cli/src/services/trace/render_status_all.rs +++ b/cli/src/services/trace/render_status_all.rs @@ -17,7 +17,6 @@ const COL_STATUS: &str = "Status"; const COL_DIFFS: &str = "Diffs"; const COL_MESSAGES: &str = "Messages"; const COL_PARTS: &str = "Parts"; -const COL_MODELS: &str = "Models"; const COL_TRACES: &str = "Traces"; const COL_INTERSECTIONS: &str = "Intersections"; const SKIPPED_PLACEHOLDER: &str = "-"; @@ -41,7 +40,6 @@ fn render_text(report: &StatusAllReport) -> String { lines.push(format!("Diff traces: {}", report.totals.diff_traces)); lines.push(format!("Messages: {}", report.totals.messages)); lines.push(format!("Parts: {}", report.totals.parts)); - lines.push(format!("Session models: {}", report.totals.session_models)); lines.push(format!("Agent traces: {}", report.totals.agent_traces)); lines.push(format!( "Post-commit intersections: {}", @@ -65,11 +63,10 @@ fn render_text(report: &StatusAllReport) -> String { COL_DIFFS, COL_MESSAGES, COL_PARTS, - COL_MODELS, COL_TRACES, COL_INTERSECTIONS, ]; - let rows: Vec<[String; 8]> = report.databases.iter().map(format_row).collect(); + let rows: Vec<[String; 7]> = report.databases.iter().map(format_row).collect(); let widths: Vec = (0..headers.len()) .map(|col| { @@ -90,7 +87,7 @@ fn render_text(report: &StatusAllReport) -> String { lines.join("\n") } -fn join_row(cells: &[String; 8], widths: &[usize]) -> String { +fn join_row(cells: &[String; 7], widths: &[usize]) -> String { cells .iter() .enumerate() @@ -101,7 +98,7 @@ fn join_row(cells: &[String; 8], widths: &[usize]) -> String { .to_string() } -fn format_row(row: &DatabaseRow) -> [String; 8] { +fn format_row(row: &DatabaseRow) -> [String; 7] { match &row.status { DatabaseRowStatus::Ready { stats } => [ row.alias.clone(), @@ -109,7 +106,6 @@ fn format_row(row: &DatabaseRow) -> [String; 8] { stats.diff_traces.to_string(), stats.messages.to_string(), stats.parts.to_string(), - stats.session_models.to_string(), stats.agent_traces.to_string(), stats.post_commit_patch_intersections.to_string(), ], @@ -121,7 +117,6 @@ fn format_row(row: &DatabaseRow) -> [String; 8] { SKIPPED_PLACEHOLDER.to_string(), SKIPPED_PLACEHOLDER.to_string(), SKIPPED_PLACEHOLDER.to_string(), - SKIPPED_PLACEHOLDER.to_string(), ], } } @@ -139,7 +134,6 @@ fn render_json(report: &StatusAllReport) -> Result { "diff_traces": stats.diff_traces, "messages": stats.messages, "parts": stats.parts, - "session_models": stats.session_models, "agent_traces": stats.agent_traces, "post_commit_patch_intersections": stats.post_commit_patch_intersections, "last_activity": stats @@ -169,7 +163,6 @@ fn render_json(report: &StatusAllReport) -> Result { "diff_traces": report.totals.diff_traces, "messages": report.totals.messages, "parts": report.totals.parts, - "session_models": report.totals.session_models, "agent_traces": report.totals.agent_traces, "post_commit_patch_intersections": report.totals.post_commit_patch_intersections, "last_activity": report diff --git a/cli/src/services/trace/stats.rs b/cli/src/services/trace/stats.rs index 23f65d77..40610749 100644 --- a/cli/src/services/trace/stats.rs +++ b/cli/src/services/trace/stats.rs @@ -19,7 +19,6 @@ pub struct AgentTraceDbStats { pub diff_traces: u64, pub messages: u64, pub parts: u64, - pub session_models: u64, pub agent_traces: u64, pub post_commit_patch_intersections: u64, pub last_activity: Option>, @@ -39,7 +38,6 @@ pub fn collect_agent_trace_db_stats(path: &Path) -> Result { let diff_traces = count_rows(&db, "diff_traces", path)?; let messages = count_rows(&db, "messages", path)?; let parts = count_rows(&db, "parts", path)?; - let session_models = count_rows(&db, "session_models", path)?; let agent_traces = count_rows(&db, "agent_traces", path)?; let post_commit_patch_intersections = count_rows(&db, "post_commit_patch_intersections", path)?; @@ -70,7 +68,6 @@ pub fn collect_agent_trace_db_stats(path: &Path) -> Result { diff_traces, messages, parts, - session_models, agent_traces, post_commit_patch_intersections, last_activity, @@ -139,7 +136,7 @@ mod tests { use crate::services::agent_trace_db::{ AgentTraceDb, AgentTraceInsert, DiffTraceInsert, InsertMessageInsert, InsertPartInsert, - MessageRole, PartType, PostCommitPatchIntersectionInsert, SessionModelUpsert, + MessageRole, PartType, PostCommitPatchIntersectionInsert, }; fn unique_temp_dir(label: &str) -> PathBuf { @@ -232,16 +229,6 @@ mod tests { .collect(); db.insert_parts(parts).expect("parts"); - // 1 session_model - db.upsert_session_model(SessionModelUpsert { - tool_name: "claude", - session_id: "s1", - model_id: "m1", - tool_version: Some("1"), - session_start_time_ms: 500, - }) - .expect("session model"); - latest_diff_ms } @@ -257,7 +244,6 @@ mod tests { assert_eq!(stats.diff_traces, 2); assert_eq!(stats.messages, 2); assert_eq!(stats.parts, 3); - assert_eq!(stats.session_models, 1); assert_eq!(stats.agent_traces, 1); assert_eq!(stats.post_commit_patch_intersections, 1); @@ -282,7 +268,6 @@ mod tests { assert_eq!(stats.diff_traces, 0); assert_eq!(stats.messages, 0); assert_eq!(stats.parts, 0); - assert_eq!(stats.session_models, 0); assert_eq!(stats.agent_traces, 0); assert_eq!(stats.post_commit_patch_intersections, 0); assert!(stats.last_activity.is_none()); diff --git a/cli/src/services/trace/status.rs b/cli/src/services/trace/status.rs index 84ee479a..30db88b8 100644 --- a/cli/src/services/trace/status.rs +++ b/cli/src/services/trace/status.rs @@ -255,7 +255,6 @@ mod tests { assert_eq!(stats.diff_traces, 0); assert_eq!(stats.messages, 0); assert_eq!(stats.parts, 0); - assert_eq!(stats.session_models, 0); assert_eq!(stats.agent_traces, 0); assert_eq!(stats.post_commit_patch_intersections, 0); assert!(last_activity.is_none()); diff --git a/cli/src/services/trace/status_all.rs b/cli/src/services/trace/status_all.rs index 456390f7..70cc9fb2 100644 --- a/cli/src/services/trace/status_all.rs +++ b/cli/src/services/trace/status_all.rs @@ -71,7 +71,6 @@ pub fn aggregate_status_all_in(sce_dir: &Path) -> Result { totals.diff_traces += stats.diff_traces; totals.messages += stats.messages; totals.parts += stats.parts; - totals.session_models += stats.session_models; totals.agent_traces += stats.agent_traces; totals.post_commit_patch_intersections += stats.post_commit_patch_intersections; if let Some(dt) = stats.last_activity { @@ -224,7 +223,6 @@ mod tests { assert_eq!(report.totals.diff_traces, 4); assert_eq!(report.totals.messages, 3); assert_eq!(report.totals.parts, 5); - assert_eq!(report.totals.session_models, 0); assert_eq!(report.totals.agent_traces, 0); assert_eq!(report.totals.post_commit_patch_intersections, 0); From 1e456bc0fe541ebb3f5afba9bac05689ccb3522d Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Wed, 1 Jul 2026 17:56:20 +0200 Subject: [PATCH 9/9] hooks: Simplify diff-trace payload result handling Avoid returning Result from the internal diff-trace persistence helper since errors are already handled fail-open and converted into user-facing status messages by the caller. --- cli/src/services/hooks/mod.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index 4cac66fe..a8a89347 100644 --- a/cli/src/services/hooks/mod.rs +++ b/cli/src/services/hooks/mod.rs @@ -675,7 +675,11 @@ fn run_diff_trace_subcommand_from_payload( DiffTraceParseResult::Persist(payload) => payload, DiffTraceParseResult::NoOp(message) => return Ok(message), }; - run_diff_trace_subcommand_from_payload_with(repository_root, &payload, logger) + Ok(run_diff_trace_subcommand_from_payload_with( + repository_root, + &payload, + logger, + )) } fn log_diff_trace_fail_open(error: &anyhow::Error, logger: Option<&dyn Logger>) -> String { @@ -690,7 +694,7 @@ fn run_diff_trace_subcommand_from_payload_with( repository_root: &Path, payload: &DiffTracePayload, logger: Option<&dyn Logger>, -) -> Result { +) -> String { if let Err(error) = diff_trace_db_time_ms(payload.time) { if let Some(log) = logger { log.warn( @@ -721,13 +725,9 @@ fn run_diff_trace_subcommand_from_payload_with( }; if agent_trace_db_persisted { - Ok(String::from( - "diff-trace hook intake persisted payload to AgentTraceDb.", - )) + String::from("diff-trace hook intake persisted payload to AgentTraceDb.") } else { - Ok(String::from( - "diff-trace hook intake completed; AgentTraceDb persistence failed.", - )) + String::from("diff-trace hook intake completed; AgentTraceDb persistence failed.") } }