diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index 934fde79..bfbad9b3 100644 --- a/cli/src/services/hooks/mod.rs +++ b/cli/src/services/hooks/mod.rs @@ -1,6 +1,5 @@ -use std::collections::BTreeMap; -use std::fs::{self, OpenOptions}; -use std::io::{self, ErrorKind, Read, Write}; +use std::fs; +use std::io::{self, Read}; use std::path::{Path, PathBuf}; use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; @@ -20,6 +19,7 @@ use crate::services::agent_trace_db::{ SessionModelAttribution, SessionModelUpsert, PAYLOAD_TYPE_PATCH, PAYLOAD_TYPE_STRUCTURED, }; use crate::services::checkout; +use crate::services::config; use crate::services::observability::traits::Logger; use crate::services::patch::{ combine_patches as combine_patches_fn, intersect_patches as intersect_patches_fn, @@ -29,7 +29,6 @@ use crate::services::structured_patch::{ build_claude_post_tool_use_patch, derive_claude_structured_patch, ClaudeStructuredPatchDerivationResult, PatchBuildResult, }; -use crate::services::{config, default_paths::RepoPaths}; pub mod command; pub mod lifecycle; @@ -37,7 +36,6 @@ pub const NAME: &str = "hooks"; pub const CANONICAL_SCE_COAUTHOR_TRAILER: &str = "Co-authored-by: SCE "; const CLAUDE_CLI_BINARY: &str = "claude"; -const MAX_TRACE_FILE_CREATE_ATTEMPTS: u64 = 1_000_000; type PayloadValidationError = fn(&str) -> String; #[derive(Clone, Debug, Eq, PartialEq)] @@ -158,13 +156,6 @@ impl ConversationTracePersistenceSummary { } } -#[allow(dead_code)] -struct TraceArtifactPayload { - trace_directory: PathBuf, - trace_name: String, - serialized: String, - artifact_description: &'static str, -} /// Required `sce hooks diff-trace` STDIN payload shape: /// `{ sessionID, diff, time, model_id?, tool_name, tool_version }`. /// @@ -200,7 +191,9 @@ fn run_hooks_subcommand_in_repo( HookSubcommand::PostCommit { vcs_type, remote_url, - } => run_post_commit_subcommand_with_trace(repository_root, *vcs_type, remote_url.clone()), + } => { + run_post_commit_subcommand_with_trace(repository_root, *vcs_type, remote_url.as_deref()) + } HookSubcommand::PostRewrite { rewrite_method } => { run_post_rewrite_subcommand_with_trace(repository_root, subcommand, rewrite_method) } @@ -739,7 +732,6 @@ where ); } } - persist_diff_trace_payload(repository_root, payload)?; let agent_trace_db_result = persist_diff_trace_payload_to_agent_trace_db( repository_root, payload, @@ -762,11 +754,11 @@ where if agent_trace_db_persisted { Ok(String::from( - "diff-trace hook intake persisted payload to AgentTraceDb and context/tmp.", + "diff-trace hook intake persisted payload to AgentTraceDb.", )) } else { Ok(String::from( - "diff-trace hook intake persisted payload to context/tmp; AgentTraceDb persistence failed.", + "diff-trace hook intake completed; AgentTraceDb persistence failed.", )) } } @@ -1369,25 +1361,6 @@ fn required_field<'a>( }) } -fn persist_diff_trace_payload( - repository_root: &Path, - payload: &DiffTracePayload, -) -> Result { - let trace_directory = RepoPaths::new(repository_root).context_tmp_dir(); - let serialized = format!( - "{}\n", - serde_json::to_string_pretty(payload) - .context("Failed to serialize diff-trace payload for persistence.")? - ); - - persist_serialized_trace_payload( - &trace_directory, - "diff-trace", - &serialized, - "diff-trace payload", - ) -} - fn persist_diff_trace_payload_to_agent_trace_db( repository_root: &Path, payload: &DiffTracePayload, @@ -1436,115 +1409,6 @@ fn diff_trace_db_time_ms(time: u64) -> Result { }) } -fn persist_serialized_trace_payload( - trace_directory: &Path, - trace_name: &str, - serialized: &str, - artifact_description: &str, -) -> Result { - persist_serialized_trace_payload_at( - trace_directory, - trace_name, - serialized, - artifact_description, - Utc::now(), - ) -} - -fn persist_serialized_trace_payload_at( - trace_directory: &Path, - trace_name: &str, - serialized: &str, - artifact_description: &str, - timestamp: DateTime, -) -> Result { - fs::create_dir_all(trace_directory).with_context(|| { - format!( - "Failed to create hook trace directory '{}'.", - trace_directory.display() - ) - })?; - - persist_trace_payload_with_retries( - trace_directory, - trace_name, - serialized, - artifact_description, - timestamp, - persist_trace_payload_to_file, - ) -} - -fn persist_trace_payload_with_retries

( - trace_directory: &Path, - trace_name: &str, - serialized: &str, - artifact_description: &str, - timestamp: DateTime, - mut persist_file: P, -) -> Result -where - P: FnMut(&Path, &str) -> io::Result<()>, -{ - for attempt in 0..MAX_TRACE_FILE_CREATE_ATTEMPTS { - let file_path = trace_directory.join(build_trace_file_name(trace_name, timestamp, attempt)); - - match persist_file(&file_path, serialized) { - Ok(()) => return Ok(file_path), - Err(error) if error.kind() == ErrorKind::AlreadyExists => {} - Err(error) => { - return Err(error).with_context(|| { - format!( - "Failed to write {artifact_description} file '{}'.", - file_path.display() - ) - }); - } - } - } - - bail!( - "Failed to write {artifact_description} file in '{}': exhausted {} collision-safe filename attempts.", - trace_directory.display(), - MAX_TRACE_FILE_CREATE_ATTEMPTS - ) -} - -fn persist_trace_payload_to_file(file_path: &Path, serialized: &str) -> io::Result<()> { - let mut file = OpenOptions::new() - .write(true) - .create_new(true) - .open(file_path)?; - file.write_all(serialized.as_bytes())?; - - Ok(()) -} - -fn format_trace_timestamp(timestamp: DateTime) -> String { - timestamp.format("%Y-%m-%dT%H-%M-%S-%3fZ").to_string() -} - -fn build_trace_file_name(trace_name: &str, timestamp: DateTime, attempt: u64) -> String { - let safe_name = sanitize_trace_name(trace_name); - - format!( - "{}-{:06}-{}.json", - format_trace_timestamp(timestamp), - attempt, - safe_name - ) -} - -fn sanitize_trace_name(trace_name: &str) -> String { - trace_name - .chars() - .map(|character| match character { - 'a'..='z' | 'A'..='Z' | '0'..='9' | '.' | '_' | '-' => character, - _ => '_', - }) - .collect() -} - fn run_pre_commit_subcommand_with_trace(repository_root: &Path) -> Result { run_pre_commit_subcommand(repository_root) } @@ -1983,19 +1847,9 @@ fn current_unix_time_ms() -> Result { fn run_post_commit_subcommand_with_trace( repository_root: &Path, vcs_type: Option, - remote_url: Option, + remote_url: Option<&str>, ) -> Result { - let remote_url_value = remote_url.clone().unwrap_or_default(); - let subcommand = HookSubcommand::PostCommit { - vcs_type, - remote_url, - }; - let input = build_hook_trace_input_for_post_commit(repository_root); - let outcome = run_post_commit_subcommand(repository_root, vcs_type, &remote_url_value); - - let _ = persist_hook_trace(repository_root, &subcommand, &input, &outcome); - - outcome + run_post_commit_subcommand(repository_root, vcs_type, remote_url.unwrap_or_default()) } fn run_post_rewrite_subcommand(repository_root: &Path, rewrite_method: &str) -> Result { @@ -2029,77 +1883,6 @@ fn hook_runtime_invocation_name(subcommand: &HookSubcommand) -> &'static str { } } -fn persist_hook_trace( - repository_root: &Path, - subcommand: &HookSubcommand, - input: &Value, - outcome: &Result, -) -> Result<()> { - let trace_directory = RepoPaths::new(repository_root).context_tmp_dir(); - let body = match outcome { - Ok(output) => json!({ - "input": input, - "output": output, - }), - Err(error) => json!({ - "input": input, - "error": error.to_string(), - }), - }; - - let serialized = format!( - "{}\n", - serde_json::to_string_pretty(&body).context("Failed to serialize hook trace.")? - ); - persist_serialized_trace_payload( - &trace_directory, - hook_trace_name(subcommand), - &serialized, - "hook trace", - )?; - - Ok(()) -} - -fn hook_trace_name(subcommand: &HookSubcommand) -> &'static str { - match subcommand { - HookSubcommand::PreCommit => "pre-commit", - HookSubcommand::CommitMsg { .. } => "commit-msg", - HookSubcommand::PostCommit { .. } => "post-commit", - HookSubcommand::PostRewrite { .. } => "post-rewrite", - HookSubcommand::DiffTrace => "diff-trace", - HookSubcommand::ConversationTrace => "conversation-trace", - HookSubcommand::SessionModel => "session-model", - } -} - -fn build_hook_trace_input_for_post_commit(repository_root: &Path) -> Value { - let mut input = build_base_hook_trace_input("post-commit"); - insert_head_commit_from_git(repository_root, &mut input); - Value::Object(input) -} - -fn build_base_hook_trace_input(hook_name: &str) -> serde_json::Map { - let mut input = serde_json::Map::new(); - input.insert("hook".to_string(), Value::String(hook_name.to_string())); - input.insert( - "git_env".to_string(), - Value::Object( - collect_git_environment() - .into_iter() - .map(|(key, value)| (key, Value::String(value))) - .collect(), - ), - ); - input -} - -fn collect_git_environment() -> BTreeMap { - std::env::vars() - .filter(|(key, _)| key.starts_with("GIT_")) - .collect() -} - fn read_hook_stdin() -> Result { let mut stdin_payload = String::new(); io::stdin() @@ -2108,43 +1891,6 @@ fn read_hook_stdin() -> Result { Ok(stdin_payload) } -fn insert_head_commit_from_git(repository_root: &Path, input: &mut serde_json::Map) { - insert_git_output( - repository_root, - &["rev-parse", "HEAD"], - "Failed to capture HEAD revision from git.", - input, - "head_oid_from_git", - "head_oid_from_git_read_error", - ); - insert_git_output( - repository_root, - &["show", "--format=", "--patch", "--no-ext-diff", "HEAD"], - "Failed to capture HEAD patch from git.", - input, - "head_patch_from_git", - "head_patch_from_git_read_error", - ); -} - -fn insert_git_output( - repository_root: &Path, - args: &[&str], - context_message: &str, - input: &mut serde_json::Map, - output_key: &str, - error_key: &str, -) { - match run_git_command_capture_stdout(repository_root, args, context_message) { - Ok(stdout) => { - input.insert(output_key.to_string(), Value::String(stdout)); - } - Err(error) => { - input.insert(error_key.to_string(), Value::String(error.to_string())); - } - } -} - fn run_git_command_capture_stdout( repository_root: &Path, args: &[&str], diff --git a/context/architecture.md b/context/architecture.md index c0d32a0f..c08ef302 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 either renders `sce doctor dbs` checkout discovery or 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. 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, 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) +- `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) - 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. - 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. diff --git a/context/cli/cli-command-surface.md b/context/cli/cli-command-surface.md index aada356c..91430558 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 `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, and AgentTraceDb insertion including nullable/resolved `model_id` and `tool_version`, 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 `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`. `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 `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, and best-effort AgentTraceDb insertion whose failure is logged and reflected in success text; 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, 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/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/cli/default-path-catalog.md b/context/cli/default-path-catalog.md index 7dd15b7a..a3f55e47 100644 --- a/context/cli/default-path-catalog.md +++ b/context/cli/default-path-catalog.md @@ -43,7 +43,7 @@ - `cli/src/services/doctor/inspect.rs` also resolves OpenCode manifest/plugin/preset locations through shared `RepoPaths` and `InstallTargetPaths` accessors instead of owning those paths locally. - `cli/src/services/setup/mod.rs` now resolves setup target directory names and required hook identifiers through `default_paths.rs` constants/accessors instead of owning those path literals locally. - `cli/src/services/default_paths.rs` includes a regression test that scans non-test Rust source under `cli/src/` and fails when new centralized production path literals appear outside the default-path service. -- `cli/src/services/hooks/mod.rs` resolves the collision-safe `context/tmp/` path shape through shared path accessors. +- Active hook runtime no longer resolves or writes collision-safe JSON artifacts under `context/tmp/`; `context/tmp/` remains a repo-relative scratch/session path owned by the default path catalog. - `cli/src/services/agent_trace_db/lifecycle.rs` and `cli/src/services/checkout/mod.rs` resolve per-checkout Agent Trace DB files through `agent_trace_db_path_for_checkout(checkout_id)` after setup-time DB initialization or hook-runtime lazy initialization. See also: [cli-command-surface.md](./cli-command-surface.md), [../architecture.md](../architecture.md), [../context-map.md](../context-map.md) diff --git a/context/context-map.md b/context/context-map.md index f30c9a52..58992905 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -50,7 +50,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, 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` (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/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) diff --git a/context/glossary.md b/context/glossary.md index d073a996..6125c04d 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -101,7 +101,7 @@ - `CLI capability traits`: Broad capability seam in `cli/src/services/capabilities.rs` consumed by the borrowed, compile-time-typed `AppContext`. `FsOps`/`StdFsOps` wrap filesystem operations and `GitOps`/`ProcessGitOps` wrap git process execution plus repository-root and hooks-directory resolution; current service internals do not consume them directly yet. - `FsOps`: Filesystem capability trait in `cli/src/services/capabilities.rs` with `read_file`, `write_file`, `metadata`, and `exists`, implemented in production by `StdFsOps`. - `GitOps`: Git capability trait in `cli/src/services/capabilities.rs` with `run_command`, `resolve_repository_root`, `resolve_hooks_directory`, and `is_available`, implemented in production by `ProcessGitOps`. -- `SCE default path policy seam`: Canonical path resolver in `cli/src/services/default_paths.rs` that owns config/state/cache root resolution through an internal `roots` helper seam, named default paths, and an explicit inventory for the current default persisted artifacts (`global config`, `auth tokens`); named DB paths include `auth DB`, `local DB`, and `Agent Trace DB`. On Linux those defaults resolve to `$XDG_CONFIG_HOME/sce/config.json`, `$XDG_STATE_HOME/sce/auth/tokens.json`, `$XDG_STATE_HOME/sce/auth.db`, `$XDG_STATE_HOME/sce/local.db`, and `$XDG_STATE_HOME/sce/agent-trace.db` with platform-equivalent `dirs` fallbacks elsewhere. The same module is also the canonical owner for broader production CLI path definitions, including repo-local `context/tmp/claude/` via `RepoPaths::claude_capture_tmp_dir()`, and is protected by a regression test that fails when new non-test production path literals are introduced outside `default_paths.rs`. +- `SCE default path policy seam`: Canonical path resolver in `cli/src/services/default_paths.rs` that owns config/state/cache root resolution through an internal `roots` helper seam, named default paths, and an explicit inventory for the current default persisted artifacts (`global config`, `auth tokens`); named DB paths include `auth DB`, `local DB`, and `Agent Trace DB`. On Linux those defaults resolve to `$XDG_CONFIG_HOME/sce/config.json`, `$XDG_STATE_HOME/sce/auth/tokens.json`, `$XDG_STATE_HOME/sce/auth.db`, `$XDG_STATE_HOME/sce/local.db`, and `$XDG_STATE_HOME/sce/agent-trace.db` with platform-equivalent `dirs` fallbacks elsewhere. The same module is also the canonical owner for broader production CLI path definitions, including repo-local `context/tmp/` scratch/session access, and is protected by a regression test that fails when new non-test production path literals are introduced outside `default_paths.rs`. - `cli config precedence contract`: Deterministic runtime value resolution in `cli/src/services/config/resolver.rs` with precedence `flags > env > config file > defaults` for flag-backed keys (`log_level`, `timeout_ms`) plus shared app-runtime observability keys (`log_format`, `log_file`, `log_file_mode`) consumed by `cli/src/app.rs`; config discovery order is `--config`, `SCE_CONFIG_FILE`, then default discovered global+local paths (`${config_root}/sce/config.json` merged before `.sce/config.json`, with local overriding per key, where `config_root` comes from the shared default path policy seam and resolves to `$XDG_CONFIG_HOME` / `dirs::config_dir()` semantics with platform fallback behavior rather than the old state/data-root default). Runtime startup config loading permits the canonical top-level `"$schema": "https://sce.crocoder.dev/config.json"` declaration anywhere those config files are parsed (parsing delegated to `schema.rs`). - `shared runtime/config primitives seam`: Canonical ownership in `cli/src/services/config/types.rs` for the CLI's shared observability/config enums (`LogLevel`, `LogFormat`, `LogFileMode`), request/response primitives (`ConfigSubcommand`, `ConfigRequest`, `ReportFormat`), source metadata types (`ValueSource`, `ConfigPathSource`, `LoadedConfigPath`, `ResolvedValue`, `ResolvedOptionalValue`), resolved runtime config types (`ResolvedAuthRuntimeConfig`, `ResolvedObservabilityRuntimeConfig`, `ResolvedHookRuntimeConfig`), the `NAME` constant, observability env-key constants, and shared bool parsing helpers; re-exported through `cli/src/services/config/mod.rs` via `pub use types::*` so downstream modules continue importing through `services::config` unchanged. - `config schema and file parsing seam`: Canonical ownership in `cli/src/services/config/schema.rs` for the CLI's JSON Schema embedding (`SCE_CONFIG_SCHEMA_JSON`), `OnceLock` validator (`CONFIG_SCHEMA_VALIDATOR`, `config_schema_validator()`), top-level allowed-key validation (`TOP_LEVEL_CONFIG_KEYS`, `validate_object_keys`), serde DTO definitions (`ParsedFileConfigDocument`, `ParsedPoliciesConfigDocument`, `ParsedBashPolicyConfigDocument`, `ParsedAttributionHooksConfigDocument`, `ParsedCustomBashPolicyEntryDocument`, `ParsedCustomBashPolicyMatchDocument`), file config value wrapper (`FileConfigValue`) and aggregate (`FileConfig`), type aliases (`ParsedBashPolicyConfig`, `ParsedFilePolicies`), and config-file load/parse helpers (`validate_config_file`, `parse_file_config`, `deserialize_typed_config`, `map_policies_config`, `map_attribution_hooks_config`, `map_bash_policy_config`); `validate_config_file` is re-exported `pub(crate)` through `mod.rs` for `lifecycle.rs` and `doctor` consumers. Policy parsing helpers (`parse_bash_policy_presets`, `parse_custom_bash_policies`) and `CustomBashPolicyEntry` are imported from `super::policy` rather than the parent module. @@ -121,7 +121,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`) that supports `pre-commit`, `commit-msg `, `post-commit`, `post-rewrite `, `diff-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, collision-safe per-invocation artifact persistence at `context/tmp/-000000-diff-trace.json`, and AgentTraceDb insertion with nullable/resolved attribution, 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`. +- `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`, 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`. - `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,7 +140,7 @@ - `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, writes collision-safe parsed-payload `context/tmp/-000000-diff-trace.json` artifacts, and inserts parsed payload fields into AgentTraceDb with nullable/resolved attribution), 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). +- `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 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. @@ -201,6 +201,6 @@ - `conversation-trace raw Claude event path`: Classification branch in `parse_conversation_trace_payload` that detects top-level `hook_event_name` and routes to the matching transformer via `match`: `"UserPromptSubmit"` → `transform_claude_user_prompt_submit`, `"Stop"` → `transform_claude_stop`, `"PostToolUse"` → `transform_claude_post_tool_use`, unsupported → error listing supported events. All transformers validate required fields, generate a UUIDv7 `message_id` and parse-time `generated_at_unix_ms`, and produce one parent `message` plus one or more `message.part` items. `UserPromptSubmit` produces `role: "user"` and `text` from `prompt`; `Stop` produces `role: "assistant"` and `text` from `last_assistant_message`; `PostToolUse` only produces items when `tool_name` is `Write` or `Edit` (silently skips other tools like `Read`, `Think`), with `role: "assistant"` and `part_type: "patch"`. Unlike the `UserPromptSubmit`/`Stop` paths, the `PostToolUse` transform delegates to `build_claude_post_tool_use_patch` (from `structured_patch.rs`): on `PatchBuildResult::Built(parsed_patch)` produces one `message.part` with `text` set to JSON-serialized `ParsedPatch`, and on `PatchBuildResult::Skipped(_)` silently returns zero items. - `agent-trace plugin conversation-trace handoff seam`: Helper path in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` where `recordConversationTrace(repoRoot, event)` builds normalized snake_case mixed-batch envelopes for `sce hooks conversation-trace`: ordinary `message` and eligible `message.part` events each send one `payloads[0]` item with its own `type`, diff-backed `message` events send one envelope containing the synthetic parent `message` item plus patch `message.part` items, and completed OpenCode `question` tool parts are mapped to one `message.part` payload with `part_type: "question"` plus JSON-stringified `{ question, answer }[]` text; `message` handoff runs before the plugin's existing diff-trace flow, while part events do not invoke diff-trace. - `agent-trace plugin diff-trace hook handoff seam`: Internal helper `runDiffTraceHook` in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` that invokes `sce hooks diff-trace`, streams `{ sessionID, diff, time, model_id, tool_name, tool_version }` to STDIN JSON (`tool_name` fixed to `opencode`, `tool_version` session-derived when available), and surfaces deterministic invocation failures. -- `agent-trace plugin secondary diff artifact ownership`: Current runtime contract where `buildTrace` no longer writes diff-trace artifacts or database rows directly; extracted diff payloads are forwarded to CLI `diff-trace` intake and the Rust hook runtime owns AgentTraceDb insertion plus collision-safe per-invocation artifact persistence. +- `agent-trace plugin secondary diff persistence ownership`: Current runtime contract where `buildTrace` no longer writes diff-trace artifacts or database rows directly; extracted diff payloads are forwarded to CLI `diff-trace` intake and the Rust hook runtime owns AgentTraceDb insertion without any `context/tmp` artifact fallback. - `messages table (Agent Trace DB)`: Agent Trace DB table created by migration `008_create_messages.sql`; stores session-scoped parent messages with columns `session_id`, `message_id`, `role` (`user`/`assistant` via CHECK constraint), `generated_at_unix_ms`, `created_at`, and `updated_at`. Message body text belongs to `parts.text`, not the parent `messages` row. Has a unique index on `(session_id, message_id)` for duplicate-ignore parent message inserts and a compound index on `(session_id, generated_at_unix_ms, id)` for chronological session message retrieval. No foreign keys to any other table. - `parts table (Agent Trace DB)`: Agent Trace DB table created by migration `009_create_parts.sql`; stores append-only message parts with columns `type` (typed by Rust as `text`/`reasoning`/`patch`/`question` and stored as unconstrained `TEXT NOT NULL`), `text`, `message_id`, `session_id`, `generated_at_unix_ms`, `created_at`, `updated_at`. Uses only the internal `id` for row identity (no upsert/dedup). Multiple parts can exist for the same `(session_id, message_id)`. A compound index on `(session_id, message_id, generated_at_unix_ms, id)` enables ordered joins. No foreign keys to `messages` or any other table, so parts may be inserted before their parent message exists. diff --git a/context/overview.md b/context/overview.md index c53821bb..21e1c33b 100644 --- a/context/overview.md +++ b/context/overview.md @@ -51,10 +51,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 collision-safe timestamp+attempt artifact filenames; 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 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 `session-model`) with deterministic argument/STDIN validation. 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, `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`, missing/nullable attribution fallback from `session_models` by `tool_name` + `session_id` while direct payload values keep precedence, required `u64` millisecond `time`, non-lossy AgentTraceDb `time_ms` conversion, and collision-safe timestamp+attempt artifact filenames; and `session-model` is the active STDIN intake for normalized model attribution upsert, including Claude `SessionStart` best-effort `claude --version` filling for missing version metadata. 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 command surface now also supports concrete runtime subcommand routing (`pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `session-model`) with deterministic argument/STDIN validation. 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, `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`, missing/nullable attribution fallback from `session_models` by `tool_name` + `session_id` while direct payload values keep precedence, required `u64` millisecond `time`, non-lossy AgentTraceDb `time_ms` conversion, and AgentTraceDb-only insertion without parsed-payload artifacts under `context/tmp`; and `session-model` is the active STDIN intake for normalized model attribution upsert, including Claude `SessionStart` best-effort `claude --version` filling for missing version metadata. 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 68849f5d..1a602eb1 100644 --- a/context/patterns.md +++ b/context/patterns.md @@ -152,8 +152,8 @@ - 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, collision-safe `context/tmp/-000000-diff-trace.json` persistence using atomic create-new retry semantics, and best-effort AgentTraceDb insertion whose failure is logged and reflected in success text while preserving the artifact fallback; keep `session-model` as an explicit STDIN intake path for normalized model attribution upsert with no raw artifact persistence. -- 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 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 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 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. diff --git a/context/plans/remove-hook-runtime-context-tmp-artifacts.md b/context/plans/remove-hook-runtime-context-tmp-artifacts.md new file mode 100644 index 00000000..ca262a0f --- /dev/null +++ b/context/plans/remove-hook-runtime-context-tmp-artifacts.md @@ -0,0 +1,111 @@ +# Plan: Remove Hook Runtime Artifacts from `context/tmp` + +## Change summary + +Stop Agent Trace / hook runtime paths from writing debug/fallback artifacts into `context/tmp/` while preserving `context/tmp/` as SCE scratch/session space. + +The current code truth shows `sce hooks diff-trace` still writes parsed payload artifacts to `context/tmp/-000000-diff-trace.json` before attempting AgentTraceDb persistence. Current context also documents historical or current hook artifacts such as `diff-trace` JSON files, post-commit JSON files, and Claude hook capture artifacts under `context/tmp/`. This plan removes those hook-runtime artifact writes and stale current-state references, then cleans existing hook-runtime artifacts from `context/tmp/` without touching SCE log files. + +## Success criteria + +- `sce hooks diff-trace` no longer writes `context/tmp/*-diff-trace.json` artifacts. +- Hook runtime success/error text no longer advertises `context/tmp` artifact fallback persistence. +- Agent Trace persistence remains DB-backed through per-checkout AgentTraceDb paths; DB write failure behavior remains deterministic and test-covered. +- No active post-commit, post-rewrite, Claude, or Agent Trace hook path writes JSON/log artifacts under `context/tmp/`. +- Existing hook-runtime artifacts are removed from `context/tmp/` using narrow patterns only. +- `context/tmp/` remains available for SCE scratch/session files and generated agent guidance/bootstrap remains intact. +- SCE log files are not deleted or modified. +- Durable context no longer describes hook-runtime `context/tmp` artifact writes as current behavior. +- Repository validation passes. + +## Constraints and non-goals + +- In scope: Rust hook runtime, hook-runtime tests, current-state context docs, and cleanup of existing hook-runtime artifacts under `context/tmp/`. +- Out of scope: removing `context/tmp/` itself, changing SCE scratch/session guidance, changing bootstrap creation of `context/tmp/`, changing generated agent guidance that says session-only scraps can use `context/tmp/`, and changing `sce` observability/logging behavior. +- Do not touch `sce.log` or other SCE log files. +- Do not backfill old `context/tmp` artifacts into AgentTraceDb. +- Do not introduce a new fallback artifact location for hook payloads. +- Do not change Agent Trace schema, migrations, or public payload shape unless required by removed artifact success text. + +## Task stack + +- [x] T01: `Remove diff-trace context/tmp artifact persistence` (status:done) + - Task ID: T01 + - Goal: Make `sce hooks diff-trace` persist parsed payloads only to AgentTraceDb, removing the `context/tmp` artifact write path and related success/fallback wording. + - Boundaries (in/out of scope): In — `cli/src/services/hooks/mod.rs` diff-trace persistence flow, helper removal or narrowing for hook artifact file writing, success text updates, and focused tests for DB success/failure behavior. Out — conversation-trace behavior, session-model behavior, DB schema changes, generated config changes, and cleanup of existing files. + - Done when: Valid diff-trace input no longer creates a `context/tmp/*-diff-trace.json` file; success output mentions AgentTraceDb persistence only; DB insert failures retain deterministic logging/error or status behavior per the chosen implementation; no unused hook-artifact helpers remain unless still used by non-hook scratch paths. + - Verification notes (commands or checks): Run a focused Rust test for hooks behavior if available, otherwise `nix develop -c sh -c 'cd cli && cargo test hooks'`; inspect test assertions for absence of `context/tmp` artifact expectations. + - Completed: 2026-06-29 + - Files changed: `cli/src/services/hooks/mod.rs`, `context/plans/remove-hook-runtime-context-tmp-artifacts.md`, `context/sce/agent-trace-hooks-command-routing.md`, `context/sce/agent-trace-db.md`, `context/sce/opencode-agent-trace-plugin-runtime.md`, `context/cli/cli-command-surface.md`, `context/architecture.md`, `context/patterns.md`, `context/glossary.md`, `context/context-map.md` + - Evidence: `nix develop -c sh -c 'cd cli && cargo fmt'`; `nix flake check` (all checks passed); `nix run .#pkl-check-generated` (generated outputs are up to date). Re-run after review feedback removing the temporary injected helper: same command chain passed. A direct focused Cargo test command was attempted first and blocked by repository bash policy in favor of `nix flake check`. + - Notes: Removed the `diff-trace` `context/tmp` artifact write path and updated diff-trace status text to mention AgentTraceDb only; avoided adding extra injected runtime helpers after review feedback; synced durable current-state context for AgentTraceDb-only diff-trace persistence. + +- [x] T02: `Audit and remove remaining hook artifact writers` (status:done) + - Task ID: T02 + - Goal: Confirm no active post-commit, post-rewrite, Claude, or Agent Trace hook runtime writes JSON/log artifacts into `context/tmp/`. + - Boundaries (in/out of scope): In — code/config search for `context/tmp`, `post-commit.json`, `post-rewrite`, Claude hook capture artifact paths, and hook artifact writer helpers; removal of active hook-runtime artifact writes if found. Out — generic SCE scratch/session guidance, `context/tmp` bootstrap, Pkl preview output path under `context/tmp/pkl-generated`, and SCE log files. + - Done when: Search results show no active hook-runtime artifact writes to `context/tmp/`; any remaining `context/tmp` references are either generic scratch/session guidance, non-runtime preview output, historical plan evidence, or explicitly non-hook behavior. + - Verification notes (commands or checks): Search code and generated config for `context/tmp`, `diff-trace.json`, `post-commit.json`, `post-rewrite`, and Claude capture artifact references; run targeted tests for any touched runtime paths. + - Completed: 2026-06-29 + - Files changed: `cli/src/services/hooks/mod.rs`, `context/plans/remove-hook-runtime-context-tmp-artifacts.md` + - Evidence: Targeted searches found and removed the active post-commit hook trace writer that persisted `context/tmp/*-post-commit.json`; after removal, `cli/**/*.rs` search for `persist_serialized_trace_payload`, `persist_hook_trace`, `diff-trace.json`, and `post-commit.json` found no matches, with only the generic `RepoPaths::context_tmp_dir()` accessor remaining. Generated config search for `context/tmp`, `diff-trace.json`, `post-commit.json`, `post-rewrite`, and Claude capture terms found only generic scratch/session guidance, automated session logging guidance, Pkl preview output under `context/tmp/pkl-generated`, or non-artifact hook command references. `nix develop -c sh -c 'cd cli && cargo fmt'` passed. First `nix flake check` exposed a clippy `needless_pass_by_value` issue introduced while removing trace persistence; after fixing it, rerun `nix flake check` passed. `nix run .#pkl-check-generated` passed with generated outputs up to date. + - Notes: Removed the remaining active hook-runtime artifact writer and its now-unused collision-safe JSON artifact helper stack. Existing files under `context/tmp/` were intentionally left for T03. + +- [x] T03: `Remove existing hook runtime artifacts from context/tmp` (status:done) + - Task ID: T03 + - Goal: Delete existing Agent Trace / hook runtime artifacts from `context/tmp/` without deleting SCE logs or generic scratch/session files. + - Boundaries (in/out of scope): In — narrow cleanup of timestamped `*-diff-trace.json`, timestamped `*-post-commit.json`, `post-rewrite/` hook artifacts, and Claude hook JSON artifact directories/files if present. Out — `context/tmp/.gitignore`, `.env`, `sce.log`, `*.log`, unrelated scratch files, and generated agent bootstrap/guidance. + - Done when: `context/tmp/` no longer contains hook-runtime JSON artifact files/directories; SCE log files remain untouched; cleanup is reflected only as deletion of intended ignored/tracked artifacts. + - Verification notes (commands or checks): Before deletion, inspect candidate paths and exclude logs; after deletion, list or glob `context/tmp/` for `*-diff-trace.json`, `*-post-commit.json`, `post-rewrite/`, and Claude hook JSON artifacts; verify `sce.log` still exists if it existed before. + - Completed: 2026-06-29 + - Files changed: `context/plans/remove-hook-runtime-context-tmp-artifacts.md`; user manually removed ignored hook-runtime artifacts under `context/tmp/` + - Evidence: Before cleanup, review found many `context/tmp/*-diff-trace.json` files, many `context/tmp/*-post-commit.json` files, `context/tmp/post-rewrite/`, and `context/tmp/claude/`, with `context/tmp/sce.log` present and explicitly preserved. After user-performed cleanup, targeted globs for `context/tmp/**/*-diff-trace.json`, `context/tmp/**/*-post-commit.json`, and `context/tmp/**/post-rewrite/**` returned no files; `context/tmp/` contained only `.env`, `.gitignore`, `claude-crof.sh`, and `sce.log`; `context/tmp/sce.log` remained present. `git status --short` was clean before the plan evidence update because cleaned artifacts were ignored/untracked. + - Notes: Per user direction, this session did not delete artifacts directly and did not touch `sce.log` or any logic related to its creation. + +- [x] T04: `Sync durable context for DB-only hook persistence` (status:done) + - Task ID: T04 + - Goal: Update current-state context so future sessions understand hook-runtime persistence is DB-backed and no longer writes Agent Trace artifacts into `context/tmp/`. + - Boundaries (in/out of scope): In — `context/sce/agent-trace-hooks-command-routing.md`, `context/overview.md`, `context/context-map.md`, and any directly relevant CLI/domain context files that currently describe `context/tmp` hook artifacts as current behavior. Out — historical completed plan evidence, generic `context/tmp` scratch guidance, and removing `context/tmp/` from the context map as a working area. + - Done when: Current-state context describes `diff-trace` as AgentTraceDb-only persistence; no current-state context claims post-commit/post-rewrite/Claude hook JSON artifacts are written under `context/tmp/`; historical references remain only where clearly historical. + - Verification notes (commands or checks): Search durable context outside `context/plans/` for stale current-state phrases: `context/tmp/-000000-diff-trace.json`, `context/tmp/*-diff-trace.json`, `post-commit.json`, `post-rewrite`, and Claude hook capture artifact wording. + - Completed: 2026-06-29 + - Files changed: `context/overview.md`, `context/patterns.md`, `context/sce/claude-raw-hook-capture.md`, `context/plans/remove-hook-runtime-context-tmp-artifacts.md` + - Evidence: Updated stale current-state wording in `context/overview.md` so `diff-trace` is described as AgentTraceDb-only persistence without `context/tmp` parsed-payload artifacts; clarified `context/patterns.md` diff-trace attribution guidance so unresolved attribution is persisted as `NULL` in AgentTraceDb rather than referring to artifacts; clarified removed Claude raw capture context so current `diff-trace` persists nullable attribution to AgentTraceDb without raw hook artifacts. `nix run .#pkl-check-generated` passed with generated outputs up to date, and passed again after context-sync review. `git diff --check` passed. Targeted non-plan search for obsolete phrases (`collision-safe timestamp+attempt artifact filenames`, `without skipping artifact`, `skipping the artifact`, `context/tmp/-000000-diff-trace.json`, and `Claude hook capture artifact`) returned no matches; remaining non-plan `context/tmp/*-diff-trace.json` references either state no artifact is written or are historical/non-goal notes, and `post-commit.json` appears only in a checked-in fixture reconstruction context, not as current hook-runtime `context/tmp` output. + - Notes: Classified this as an important context-sync task because it corrects root current-state docs. No runtime/code changes were made. + +- [x] T05: `Validate and cleanup` (status:done) + - Task ID: T05 + - Goal: Run full validation, generated-output parity checks, and final cleanup review for the artifact-removal change. + - Boundaries (in/out of scope): In — repository validation, generated-output parity, final stale-reference search, verification that no task-owned temporary scaffolding remains, and plan evidence update. Out — unrelated refactors, deleting SCE logs, deleting generic `context/tmp` scratch/session files, or changing behavior beyond the plan scope. + - Done when: `nix run .#pkl-check-generated` and `nix flake check` pass; stale hook-runtime artifact references are absent from current-state docs/code; `context/tmp/` contains no hook-runtime artifacts targeted by this plan; SCE logs remain untouched. + - Verification notes (commands or checks): `nix run .#pkl-check-generated`; `nix flake check`; targeted search for `context/tmp` hook artifact references; final `context/tmp/` cleanup inspection preserving `sce.log`/`*.log`. + - Completed: 2026-06-29 + - Files changed: `context/plans/remove-hook-runtime-context-tmp-artifacts.md` + - Evidence: `nix run .#pkl-check-generated` passed ("Generated outputs are up to date."); `nix flake check` passed (all 4 flake checks passed including cli-tests, cli-clippy, cli-fmt, and pkl-parity); targeted Rust code search for `context/tmp/*-diff-trace.json` and `context/tmp/*-post-commit.json` patterns found no matches; targeted context doc search for `context/tmp` hook-artifact references found only correct current-state descriptions (context-map.md, overview.md, cli-command-surface.md) stating no artifacts are written; residual hook-runtime artifact files in `context/tmp/` (2 diff-trace, 2 post-commit JSON files from earlier task testing) were removed; `context/tmp/` now contains only `.env`, `.gitignore`, `claude-crof.sh`, and `sce.log`; `sce.log` remains untouched. This was a verify-only context-sync task (no code changes, no durable context drift). + +## Validation Report + +### Commands run +- `nix flake check` → exit 0 (all checks passed: cli-tests, cli-clippy, cli-fmt, pkl-parity, integrations-install-*, npm-*, config-lib-*, workflow-actionlint, flatpak-*) +- `nix run .#pkl-check-generated` → exit 0 ("Generated outputs are up to date.") +- Targeted Rust code search for `context/tmp/*-diff-trace.json` and `context/tmp/*-post-commit.json` → no matches +- Targeted context doc search (outside `context/plans/`) for `context/tmp` hook-artifact references → only correct current-state descriptions found +- Final `context/tmp/` cleanup → removed 4 residual JSON artifacts (2 diff-trace, 2 post-commit); `sce.log` preserved; remaining files: `.env`, `.gitignore`, `claude-crof.sh`, `sce.log` + +### Success-criteria verification +- [x] `sce hooks diff-trace` no longer writes `context/tmp/*-diff-trace.json` artifacts → T01 removed writer; verified by stale search +- [x] Hook runtime success/error text no longer advertises `context/tmp` artifact fallback persistence → T01 updated text; verified by code search +- [x] Agent Trace persistence remains DB-backed → confirmed; post-commit/diff-trace/session-model all use AgentTraceDb +- [x] No active hook path writes JSON/log artifacts under `context/tmp/` → T02 audited and removed all active writers; verified by stale search +- [x] Existing hook-runtime artifacts removed from `context/tmp/` → T03 removed bulk; T05 removed 4 residual artifacts from earlier task testing +- [x] `context/tmp/` remains available for SCE scratch/session files → preserved; `.gitignore`, `.env`, `claude-crof.sh` intact +- [x] SCE log files not deleted or modified → `context/tmp/sce.log` present and untouched +- [x] Durable context no longer describes hook-runtime `context/tmp` artifact writes as current behavior → T04 synced; T05 verified root files match code truth +- [x] Repository validation passes → `nix flake check` and `nix run .#pkl-check-generated` both pass + +### Residual risks +- None identified. All tasks completed, all writers removed, context synced, validation passes. + +## Open questions + +- None. Scope clarified: preserve `context/tmp/` for SCE scratch/session use and do not touch SCE logs. diff --git a/context/sce/agent-trace-db.md b/context/sce/agent-trace-db.md index 678a1889..51470d69 100644 --- a/context/sce/agent-trace-db.md +++ b/context/sce/agent-trace-db.md @@ -191,8 +191,8 @@ Both triggers compare `OLD.*` vs `NEW.*` for all mutable columns (excluding `upd - 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. - `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 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()`. -- Command success requires artifact persistence to succeed; AgentTraceDb open/insert failures are logged and reflected in the success text as failed DB persistence instead of discarding the artifact fallback. +- 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`. +- AgentTraceDb open/insert failures are logged and reflected in deterministic success text as failed DB persistence; no artifact fallback is created. - Existing artifact files are not backfilled into the database. Post-commit intersection rows are written by the active `post-commit` hook flow through per-checkout lazy AgentTraceDb access, and the same flow now also inserts built Agent Trace payloads into `agent_traces` via `AgentTraceDb::insert_agent_trace()` (see [agent-trace-hooks-command-routing.md](agent-trace-hooks-command-routing.md)). The persisted `trace_json` is the schema-validated `build_agent_trace(...)` output and includes top-level `metadata.sce.version` from the compiled `sce` CLI package version plus `content_hash` on every emitted range. Range `content_hash` values are computed from the touched-line kind/content of the post-commit hunk that produced the persisted range, not from DB IDs, paths, line positions, or runtime metadata. diff --git a/context/sce/agent-trace-hooks-command-routing.md b/context/sce/agent-trace-hooks-command-routing.md index 89d1411f..a7e8aa25 100644 --- a/context/sce/agent-trace-hooks-command-routing.md +++ b/context/sce/agent-trace-hooks-command-routing.md @@ -68,13 +68,13 @@ - **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 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`. + - 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`. - Current TypeScript 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` 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. - Neither TypeScript runtime writes `context/tmp/*-diff-trace.json` artifacts or AgentTraceDb rows directly. -- `diff-trace` command success requires artifact persistence to succeed. AgentTraceDb open/insert failures are logged through `sce.hooks.diff_trace.agent_trace_db_write_failed` and reflected in the success text as failed DB persistence, while the parsed-payload artifact remains the durable fallback. +- `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. - `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. diff --git a/context/sce/claude-raw-hook-capture.md b/context/sce/claude-raw-hook-capture.md index 2d1602ca..64b156af 100644 --- a/context/sce/claude-raw-hook-capture.md +++ b/context/sce/claude-raw-hook-capture.md @@ -7,7 +7,7 @@ The `sce hooks claude-capture ` CLI route, `ClaudeCaptureEvent`, `Ho Rust now exposes only normalized 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 without skipping artifact or DB persistence. +- `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. ## Historical artifact contract diff --git a/context/sce/opencode-agent-trace-plugin-runtime.md b/context/sce/opencode-agent-trace-plugin-runtime.md index 8ddfe598..b02742b6 100644 --- a/context/sce/opencode-agent-trace-plugin-runtime.md +++ b/context/sce/opencode-agent-trace-plugin-runtime.md @@ -114,7 +114,7 @@ When extraction succeeds, `buildQuestionToolConversationTracePayload(eventPart)` - Claude registration uses generated `.claude/settings.json` command hooks that call `sce hooks` directly (no TypeScript runtime intermediary): `SessionStart` pipes the raw Claude hook event JSON to `sce hooks session-model`, and matched `PostToolUse Write|Edit|MultiEdit|NotebookEdit` pipes the raw hook event to `sce hooks diff-trace`. - 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 parsed `context/tmp/*-diff-trace.json` artifacts and AgentTraceDb `diff_traces`/`session_models` rows. +- 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. ## Claude derivation golden tests