From ca602a90a19373e93e4c81e6a46cf7447859e3b1 Mon Sep 17 00:00:00 2001 From: David Abram Date: Wed, 19 Aug 2026 15:34:37 +0200 Subject: [PATCH 1/7] config: Add opt-in Agent Trace auto-sync resolution Enable the planned post-commit synchronization boundary to resolve a conservative, config-file-only boolean without changing the existing sync engine. Extend the canonical schema and Rust config layers with global-before-local precedence, default-false behavior, provenance-aware inspection, and focused validation/resolution coverage. This keeps automatic synchronization disabled until the later launcher and hook-integration tasks are implemented. Plan: automatic-agent-trace-sync (T01) Co-authored-by: SCE --- cli/src/services/config/render.rs | 9 ++ cli/src/services/config/resolver.rs | 69 +++++++++++++ cli/src/services/config/schema.rs | 40 +++++++- cli/src/services/config/types.rs | 1 + config/pkl/base/sce-config-schema.pkl | 5 + context/architecture.md | 2 +- context/cli/config-precedence-contract.md | 8 +- context/context-map.md | 2 +- context/glossary.md | 4 +- context/overview.md | 2 +- context/patterns.md | 2 +- context/plans/automatic-agent-trace-sync.md | 102 ++++++++++++++++++++ 12 files changed, 232 insertions(+), 14 deletions(-) create mode 100644 context/plans/automatic-agent-trace-sync.md diff --git a/cli/src/services/config/render.rs b/cli/src/services/config/render.rs index a9757683..10a1dae0 100644 --- a/cli/src/services/config/render.rs +++ b/cli/src/services/config/render.rs @@ -48,6 +48,11 @@ pub(super) fn format_show_output(runtime: &RuntimeConfig, report_format: ReportF &runtime.agent_trace_repository_remote.value, runtime.agent_trace_repository_remote.source, ), + format_resolved_value_text( + "agent_trace.auto_sync", + &runtime.agent_trace_auto_sync.value.to_string(), + runtime.agent_trace_auto_sync.source, + ), format_bash_policies_text(&runtime.bash_policies), format_database_retry_text(&runtime.database_retry), format_validation_warnings_text(&warnings), @@ -89,6 +94,10 @@ pub(super) fn format_show_output(runtime: &RuntimeConfig, report_format: ReportF runtime.agent_trace_repository_remote.value.as_str(), runtime.agent_trace_repository_remote.source, ), + "auto_sync": format_resolved_value_json( + runtime.agent_trace_auto_sync.value, + runtime.agent_trace_auto_sync.source, + ), }, "policies": { "bash": format_bash_policies_json(&runtime.bash_policies), diff --git a/cli/src/services/config/resolver.rs b/cli/src/services/config/resolver.rs index f786979f..92fe249b 100644 --- a/cli/src/services/config/resolver.rs +++ b/cli/src/services/config/resolver.rs @@ -78,6 +78,7 @@ pub(super) struct RuntimeConfig { pub(super) control_plane_base_url: ResolvedOptionalValue, pub(super) agent_trace_repository_id: ResolvedOptionalValue, pub(super) agent_trace_repository_remote: ResolvedValue, + pub(super) agent_trace_auto_sync: ResolvedValue, pub(super) bash_policies: ResolvedOptionalValue, pub(super) database_retry: ResolvedOptionalValue, pub(super) validation_errors: Vec, @@ -268,6 +269,7 @@ where Ok(ResolvedHookRuntimeConfig { attribution_hooks_enabled: runtime.attribution_hooks_enabled.value, + agent_trace_auto_sync: runtime.agent_trace_auto_sync.value, }) } @@ -318,6 +320,7 @@ where control_plane_base_url: None, agent_trace_repository_id: None, agent_trace_repository_remote: None, + agent_trace_auto_sync: None, bash_policy_presets: None, bash_policy_custom: None, database_retry: None, @@ -364,6 +367,9 @@ where if let Some(agent_trace_repository_remote) = layer.agent_trace_repository_remote { file_config.agent_trace_repository_remote = Some(agent_trace_repository_remote); } + if let Some(agent_trace_auto_sync) = layer.agent_trace_auto_sync { + file_config.agent_trace_auto_sync = Some(agent_trace_auto_sync); + } if let Some(bash_policy_presets) = layer.bash_policy_presets { file_config.bash_policy_presets = Some(bash_policy_presets); } @@ -522,6 +528,17 @@ where }; } + let resolved_agent_trace_auto_sync = match file_config.agent_trace_auto_sync { + Some(value) => ResolvedValue { + value: value.value, + source: ValueSource::ConfigFile(value.source), + }, + None => ResolvedValue { + value: false, + source: ValueSource::Default, + }, + }; + let resolved_bash_policies = resolve_bash_policy_config( file_config.bash_policy_presets.as_ref(), file_config.bash_policy_custom.as_ref(), @@ -543,6 +560,7 @@ where control_plane_base_url: resolved_control_plane_base_url, agent_trace_repository_id: resolved_agent_trace_repository_id, agent_trace_repository_remote: resolved_agent_trace_repository_remote, + agent_trace_auto_sync: resolved_agent_trace_auto_sync, bash_policies: resolved_bash_policies, database_retry: resolved_database_retry, validation_errors, @@ -765,6 +783,7 @@ mod tests { Ok(ResolvedHookRuntimeConfig { attribution_hooks_enabled: runtime.attribution_hooks_enabled.value, + agent_trace_auto_sync: runtime.agent_trace_auto_sync.value, }) } @@ -805,6 +824,56 @@ mod tests { assert_eq!(runtime.agent_trace_repository_id.source, None); } + #[test] + fn agent_trace_auto_sync_defaults_to_false() { + let runtime = resolve_runtime_with_config(None).unwrap(); + + assert!(!runtime.agent_trace_auto_sync.value); + assert_eq!(runtime.agent_trace_auto_sync.source, ValueSource::Default); + } + + #[test] + fn agent_trace_auto_sync_resolves_from_config_file() { + let runtime = resolve_runtime_with_config(Some( + r#"{"agent_trace":{"auto_sync":true}}"#, + )) + .unwrap(); + + assert!(runtime.agent_trace_auto_sync.value); + assert_eq!( + runtime.agent_trace_auto_sync.source, + ValueSource::ConfigFile(ConfigPathSource::Flag) + ); + } + + #[test] + fn agent_trace_auto_sync_uses_local_config_over_global_config() { + let runtime = resolve_runtime_config_with( + &empty_request(), + Path::new("/tmp/repo"), + |_| None, + |path| { + if path == Path::new("/tmp/global-sce-config.json") { + Ok(r#"{"agent_trace":{"auto_sync":false}}"#.to_string()) + } else { + Ok(r#"{"agent_trace":{"auto_sync":true}}"#.to_string()) + } + }, + |path| { + path == Path::new("/tmp/global-sce-config.json") + || path == Path::new("/tmp/repo/.sce/config.json") + }, + || Ok(PathBuf::from("/tmp/global-sce-config.json")), + ) + .unwrap(); + + assert!(runtime.agent_trace_auto_sync.value); + assert_eq!( + runtime.agent_trace_auto_sync.source, + ValueSource::ConfigFile(ConfigPathSource::DefaultDiscoveredLocal) + ); + } + #[test] fn agent_trace_repository_remote_defaults_to_origin() { let runtime = resolve_runtime_with_config(None).unwrap(); diff --git a/cli/src/services/config/schema.rs b/cli/src/services/config/schema.rs index 4483bbda..92fe41a3 100644 --- a/cli/src/services/config/schema.rs +++ b/cli/src/services/config/schema.rs @@ -85,6 +85,7 @@ pub(crate) struct ParsedFileConfigDocument { pub(crate) struct ParsedAgentTraceConfigDocument { pub(crate) repository_id: Option, pub(crate) repository_remote: Option, + pub(crate) auto_sync: Option, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq)] @@ -165,6 +166,7 @@ pub(crate) struct FileConfig { pub(crate) control_plane_base_url: Option>, pub(crate) agent_trace_repository_id: Option>, pub(crate) agent_trace_repository_remote: Option>, + pub(crate) agent_trace_auto_sync: Option>, pub(crate) bash_policy_presets: Option>>, pub(crate) bash_policy_custom: Option>>, pub(crate) database_retry: Option>, @@ -313,7 +315,7 @@ pub(crate) fn parse_file_config( let control_plane_base_url = typed .control_plane_base_url .map(|value| FileConfigValue { value, source }); - let (agent_trace_repository_id, agent_trace_repository_remote) = + let (agent_trace_repository_id, agent_trace_repository_remote, agent_trace_auto_sync) = map_agent_trace_config(typed.agent_trace.as_ref(), object, path, source)?; let (attribution_hooks_enabled, bash_policy_presets, bash_policy_custom, database_retry) = map_policies_config(typed.policies.as_ref(), object, path, source)?; @@ -330,6 +332,7 @@ pub(crate) fn parse_file_config( control_plane_base_url, agent_trace_repository_id, agent_trace_repository_remote, + agent_trace_auto_sync, bash_policy_presets, bash_policy_custom, database_retry, @@ -610,6 +613,7 @@ pub(crate) fn map_database_retry_config( pub(crate) type ParsedAgentTraceConfig = ( Option>, Option>, + Option>, ); fn map_agent_trace_config( @@ -619,7 +623,7 @@ fn map_agent_trace_config( source: ConfigPathSource, ) -> Result { let Some(agent_trace_value) = object.get("agent_trace") else { - return Ok((None, None)); + return Ok((None, None, None)); }; let agent_trace_object = agent_trace_value.as_object().with_context(|| { @@ -633,8 +637,8 @@ fn map_agent_trace_config( agent_trace_object, path, Some("agent_trace"), - &["repository_id", "repository_remote"], - "repository_id, repository_remote", + &["repository_id", "repository_remote", "auto_sync"], + "repository_id, repository_remote, auto_sync", )?; let repository_id = typed @@ -643,8 +647,11 @@ fn map_agent_trace_config( let repository_remote = typed .and_then(|config| config.repository_remote.clone()) .map(|value| FileConfigValue { value, source }); + let auto_sync = typed + .and_then(|config| config.auto_sync) + .map(|value| FileConfigValue { value, source }); - Ok((repository_id, repository_remote)) + Ok((repository_id, repository_remote, auto_sync)) } fn map_integrations_config( @@ -745,6 +752,10 @@ mod agent_trace_config_tests { .map(|value| value.value.as_str()), Some("upstream") ); + assert_eq!( + config.agent_trace_auto_sync.as_ref().map(|value| value.value), + None + ); } #[test] @@ -790,4 +801,23 @@ mod agent_trace_config_tests { assert!(error.contains("failed schema validation"), "{error}"); } + + #[test] + fn parses_agent_trace_auto_sync() { + let config = parse(r#"{"agent_trace":{"auto_sync":true}}"#).unwrap(); + + assert_eq!( + config.agent_trace_auto_sync.as_ref().map(|value| value.value), + Some(true) + ); + } + + #[test] + fn rejects_non_boolean_agent_trace_auto_sync() { + let error = parse(r#"{"agent_trace":{"auto_sync":"true"}}"#) + .unwrap_err() + .to_string(); + + assert!(error.contains("failed schema validation"), "{error}"); + } } diff --git a/cli/src/services/config/types.rs b/cli/src/services/config/types.rs index 867c63ad..9ef5e1aa 100644 --- a/cli/src/services/config/types.rs +++ b/cli/src/services/config/types.rs @@ -207,6 +207,7 @@ pub(crate) struct ResolvedObservabilityRuntimeConfig { #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct ResolvedHookRuntimeConfig { pub(crate) attribution_hooks_enabled: bool, + pub(crate) agent_trace_auto_sync: bool, } #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/config/pkl/base/sce-config-schema.pkl b/config/pkl/base/sce-config-schema.pkl index 55c71faf..bcd47f12 100644 --- a/config/pkl/base/sce-config-schema.pkl +++ b/config/pkl/base/sce-config-schema.pkl @@ -114,6 +114,11 @@ local sceConfigSchema = new JsonSchema { minLength = 1 default = "origin" } + ["auto_sync"] = new JsonSchema { + type = "boolean" + description = "Launch a detached, best-effort `sce sync` after successful post-commit Agent Trace persistence. Defaults to false." + default = false + } } } ["policies"] = new JsonSchema { diff --git a/context/architecture.md b/context/architecture.md index 19b34c1a..9a71affa 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -112,7 +112,7 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/cli_schema.rs` is now the canonical owner for top-level command metadata for the real clap-backed command set (`auth`, `config`, `setup`, `doctor`, `hooks`, `policy`, `sync`, `version`, `completion`), including the slim top-level help purpose text and per-command visibility on `sce`, `sce help`, and `sce --help`; `cli/src/command_surface.rs` remains the custom top-level help renderer and known-command classifier, adding the synthetic `help` row plus the ASCII banner while consuming that shared metadata instead of maintaining a parallel command catalog. - `cli/src/services/default_paths.rs` is the canonical production path catalog for the CLI: it resolves config/state/cache roots with platform-aware XDG or `dirs` fallbacks through an internal `roots` seam, exposes named default paths for current persisted artifacts and database/log files (global config, auth tokens, auth DB, local DB, default observability log directory, and the sole Agent Trace DB path helper `agent_trace_db_path_for_repository` under `repos//agent-trace.db`; the former global-sentinel and per-checkout Agent Trace path helpers were removed by the `retire-legacy-agent-trace-db` plan), and owns canonical repo-relative, embedded-asset, install, hook, and context-path accessors so non-test production path definitions have one shared owner. Compile-time generated payload paths are owned by `build.rs` under `OUT_DIR`, not by the default-path catalog. Current production consumers such as config discovery, observability config resolution, doctor reporting, setup/install flows, database adapters, checkout identity, Agent Trace storage resolution, and local hook runtime path resolution consume this shared catalog rather than defining owned path literals in their own modules. - `cli/src/services/agent_trace.rs` is the Rust CLI owner for the SCE web base URL (`SCE_WEB_BASE_URL`) and exposes helpers for SCE-owned URL construction: Agent Trace conversation lookup URLs, persisted Agent Trace trace URLs, Agent Trace session URLs, and setup-created config schema URLs. Production Rust code should consume those helpers instead of repeating `sce.crocoder.dev` literals. The config resolver separately owns the `control_plane_base_url` runtime seam, whose baked `sce sync` default is `https://sce.crocoderlab.dev`; this control-plane host is not a web URL or schema owner. -- `cli/src/services/config/mod.rs` is the config service facade and `sce config` orchestration surface (`show`, `validate`, `--help`), with bare `sce config` routed by `cli/src/app.rs` to the same help payload as `sce config --help`. Focused submodules own the implementation slices: `types.rs` owns shared config/runtime primitives, `schema.rs` owns generated schema embedding plus typed file parsing, `policy.rs` owns bash-policy semantic validation plus policy-specific formatting and runtime preset-catalog access for the Rust evaluator, `resolver.rs` owns deterministic config-file discovery, file-layer merging, explicit value precedence (`flags > env > config file > defaults` where flag-backed), shared auth-key resolution, observability-runtime resolution, attribution-hooks runtime gate resolution, database-retry config resolution and `DATABASE_RETRY_CONFIG` `OnceLock` initialization, default-discovered invalid-file degradation, and explicit-path fatal errors for `--config` / `SCE_CONFIG_FILE`, and private `render.rs` owns `sce config show` / `sce config validate` text and JSON output construction plus rendering-specific display-value helpers. The facade preserves existing `services::config` imports for startup/auth/hooks callers while delegating command execution to resolution plus rendering submodules. +- `cli/src/services/config/mod.rs` is the config service facade and `sce config` orchestration surface (`show`, `validate`, `--help`), with bare `sce config` routed by `cli/src/app.rs` to the same help payload as `sce config --help`. Focused submodules own the implementation slices: `types.rs` owns shared config/runtime primitives, `schema.rs` owns generated schema embedding plus typed file parsing, `policy.rs` owns bash-policy semantic validation plus policy-specific formatting and runtime preset-catalog access for the Rust evaluator, `resolver.rs` owns deterministic config-file discovery, file-layer merging, explicit value precedence (`flags > env > config file > defaults` where flag-backed), shared auth-key resolution, observability-runtime resolution, attribution-hooks runtime gate and `agent_trace.auto_sync` resolution, database-retry config resolution and `DATABASE_RETRY_CONFIG` `OnceLock` initialization, default-discovered invalid-file degradation, and explicit-path fatal errors for `--config` / `SCE_CONFIG_FILE`, and private `render.rs` owns `sce config show` / `sce config validate` text and JSON output construction plus rendering-specific display-value helpers. The facade preserves existing `services::config` imports for startup/auth/hooks callers while delegating command execution to resolution plus rendering submodules. - `cli/src/services/output_format.rs` defines the canonical shared CLI output-format contract (`OutputFormat`) for supporting commands, with deterministic `text|json` parsing and command-scoped actionable invalid-value guidance. - `cli/src/services/config/types.rs` is the canonical owner for the shared runtime/config primitive seam used by the CLI: `LogLevel`, `LogFormat`, `SCE_LOG_LEVEL`, `SCE_LOG_FORMAT`, `SCE_LOG_DIR`, `DEFAULT_LOG_FILE_RETENTION_LIMIT`, and the shared bool parsing helpers used by both config resolution and observability bootstrap; `cli/src/services/config/mod.rs` re-exports those primitives through the facade. - `cli/src/services/capabilities.rs` defines the current broad CLI capability traits consumed by the borrowed, compile-time-typed `AppContext`: `FsOps` with `StdFsOps` for filesystem operations and `GitOps` with `ProcessGitOps` for git command execution plus repository-root/hooks-directory resolution. Existing service internals do not consume these traits directly yet; command execution uses narrow accessors and repo-root-scoped context derivation. diff --git a/context/cli/config-precedence-contract.md b/context/cli/config-precedence-contract.md index 887ea693..c03ea0d7 100644 --- a/context/cli/config-precedence-contract.md +++ b/context/cli/config-precedence-contract.md @@ -4,7 +4,7 @@ This contract documents the implemented `sce config` command behavior, runtime resolver, renderer, and canonical Pkl-authored `sce/config.json` schema. The schema is emitted to payload-relative `config/schema/sce-config.schema.json` under Cargo `OUT_DIR` or packaging fallbacks and embedded by `cli/src/services/config/schema.rs` as `SCE_CONFIG_SCHEMA_JSON`; no generated schema is committed. -The current implementation resolves flat logging keys with deterministic precedence and source metadata, exposes resolved-value inspection through `sce config show`, and keeps `sce config validate` focused on validation status plus errors/warnings. Threshold, format, directory, and `log_file_retention_limit` values are consumed by runtime logging; the concrete logger uses the retention value for primary and v2 creation-triggered cleanup. +The current implementation resolves flat logging keys and Agent Trace runtime keys with deterministic precedence and source metadata, exposes resolved-value inspection through `sce config show`, and keeps `sce config validate` focused on validation status plus errors/warnings. Threshold, format, directory, and `log_file_retention_limit` values are consumed by runtime logging; the concrete logger uses the retention value for primary and v2 creation-triggered cleanup. The opt-in `agent_trace.auto_sync` value is currently config plumbing for the post-commit trigger boundary and defaults to disabled until enabled by the later hook integration task. ## Command surface @@ -29,6 +29,7 @@ Agent Trace repository identity keys are also config-file only with per-key `glo - `agent_trace.repository_id` — optional explicit repository identity; resolves as an optional value with no default. - `agent_trace.repository_remote` — Git remote name used to derive repository identity; defaults to `origin` (`DEFAULT_AGENT_TRACE_REPOSITORY_REMOTE` in `cli/src/services/config/resolver.rs`) when no config file sets it. +- `agent_trace.auto_sync` — opt-in boolean for the future post-commit Agent Trace synchronization trigger; config-file only, with no flag or environment layer, and defaults to `false`. Resolved observability values that currently have no CLI flag layer follow the same lower-precedence chain without a flag step: @@ -89,9 +90,10 @@ When a default-discovered global or repo-local config file exists but fails JSON - `workos_client_id` must be a string when present. - `control_plane_base_url` must be a non-empty string when present. -- `agent_trace` must be an object when present and currently allows only `repository_id` and `repository_remote`. +- `agent_trace` must be an object when present and currently allows `repository_id`, `repository_remote`, and `auto_sync`. - `agent_trace.repository_id` must be a non-empty string when present. - `agent_trace.repository_remote` must be a non-empty string when present; the generated schema documents default `origin`. +- `agent_trace.auto_sync` must be a boolean when present; omitted values resolve to `false`. - `integrations` must be an object when present and currently allows `target` and `optional_workflows`; either key alone yields a parsed `IntegrationsConfig` with the other defaulting to empty. - `integrations.target` must be an array of unique canonical target IDs when present. @@ -122,7 +124,7 @@ When a default-discovered global or repo-local config file exists but fails JSON - `show` includes resolved observability values directly in `result.resolved`, preserving flat logging keys (`log_level`, `log_format`, `log_dir`, `log_file_retention_limit`). - `validate` text output is limited to `SCE config validation`, `Validation issues`, and `Validation warnings` lines. - `validate` JSON output is limited to `result.command`, `result.valid`, `result.issues`, and `result.warnings`. -- `show` includes resolved Agent Trace repository identity under `result.resolved.agent_trace` (JSON: `repository_id` optional-value shape, `repository_remote` resolved-value shape) and as `agent_trace.repository_id` / `agent_trace.repository_remote` per-key text lines, reporting `(unset)` for a missing `repository_id` and `source: default` for the `origin` remote fallback. +- `show` includes resolved Agent Trace configuration under `result.resolved.agent_trace` (JSON: `repository_id` optional-value shape, `repository_remote` and `auto_sync` resolved-value shapes) and as per-key text lines, reporting `(unset)` for a missing `repository_id`, `source: default` for the `origin` remote fallback, and `source: default` for omitted `auto_sync`. - `show` includes resolved bash-tool policies under `result.resolved.policies.bash`. - Bash-policy output includes resolved preset IDs, expanded custom entries (`id`, `match.argv_prefix`, `message`), and config-file source metadata when present. - `show` text output renders `policies.bash` as a single deterministic line and reports `(unset)` when no policy config resolves. diff --git a/context/context-map.md b/context/context-map.md index 167420d8..0fb7cf65 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -19,7 +19,7 @@ Feature/domain context: - `context/cli/styling-service.md` (CLI text-mode output styling with `owo-colors`, TTY/`NO_COLOR` policy, shared helper API for human-facing surfaces including sync completion markers, and per-column right-to-left RGB gradient banner rendering) - `context/cli/sync-command.md` (the top-level `sce sync` command: repository-scoped Agent Trace storage resolution, WorkOS-authenticated four-stream control-plane synchronization through the sync-owned consumer-typed `services::sync::progress` reporter contract with sync-owned events, its generic/no-op contract and `indicatif` presentation adapter for aligned stderr progress with independent stream completion, explicit successful finalization, JSON stdout silence, and rejection of the removed `sce trace` command group) - `context/cli/agent-trace-sync-command.md` (composed local-to-control-plane `sce sync` architecture: the `hooks/plugins → repository Agent Trace DB → AgentTraceExportReader → sce sync → HTTPS + WorkOS Bearer → control plane` data flow, the `sce auth login` / `cd ` / `sce sync` user flow, the no-local-cursor/no-`agent-trace-sync.db`/no-Turso-Sync/no-`BridgeLock`/no-local-DWH invariants, and `401`/`409`/ambiguous-batch-failure recovery semantics) -- `context/cli/config-precedence-contract.md` (implemented `sce config` show/validate command contract, deterministic `flags > env > config file > defaults` resolution order, focused `config/resolver.rs` ownership for config discovery/merge/runtime precedence plus default-discovered invalid-file degradation, focused `config/render.rs` ownership for `show`/`validate` text+JSON output construction, canonical `$schema` acceptance for startup-loaded `sce/config.json` files, shared auth-key env/config/optional baked-default support starting with `workos_client_id`, shared runtime resolution for flat logging observability keys including `log_dir` / `SCE_LOG_DIR` with `/sce/logs` defaulting plus config-file/default-only positive `log_file_retention_limit`, config-file-only `agent_trace.repository_id`/`agent_trace.repository_remote` repository-identity keys with default remote `origin`, the catalog-derived `integrations.optional_workflows` optional-workflow selection key, JSON-pointer-prefixed schema-validation errors, canonical Pkl-generated `sce/config.json` schema ownership plus CLI embedding/reuse contract including `policies.attribution_hooks.enabled` default-true/explicit-false opt-out metadata, config-file selection order, `show` provenance output, and trimmed `validate` output contract) +- `context/cli/config-precedence-contract.md` (implemented `sce config` show/validate command contract, deterministic `flags > env > config file > defaults` resolution order, focused `config/resolver.rs` ownership for config discovery/merge/runtime precedence plus default-discovered invalid-file degradation, focused `config/render.rs` ownership for `show`/`validate` text+JSON output construction, canonical `$schema` acceptance for startup-loaded `sce/config.json` files, shared auth-key env/config/optional baked-default support starting with `workos_client_id`, shared runtime resolution for flat logging observability keys including `log_dir` / `SCE_LOG_DIR` with `/sce/logs` defaulting plus config-file/default-only positive `log_file_retention_limit`, config-file-only `agent_trace.repository_id`/`agent_trace.repository_remote` repository-identity keys with default remote `origin`, opt-in `agent_trace.auto_sync` boolean resolution defaulting false for the post-commit trigger boundary, the catalog-derived `integrations.optional_workflows` optional-workflow selection key, JSON-pointer-prefixed schema-validation errors, canonical Pkl-generated `sce/config.json` schema ownership plus CLI embedding/reuse contract including `policies.attribution_hooks.enabled` default-true/explicit-false opt-out metadata, config-file selection order, `show` provenance output, and trimmed `validate` output contract) - `context/cli/capability-traits.md` (current broad CLI capability seam in `cli/src/services/capabilities.rs`, including `FsOps`/`StdFsOps`, `GitOps`/`ProcessGitOps`, git root/hooks resolution behavior, compile-time-typed borrowed AppContext wiring with associated-type narrow capability accessors plus `ContextWithRepoRoot` repo-root-scoped context derivation, generic command execution bounds, and test-only unimplemented stubs; current service internals do not consume fs/git traits until later lifecycle migration tasks) - `context/cli/service-lifecycle.md` (current compile-safe lifecycle seam in `cli/src/services/lifecycle.rs`, including default no-op `ServiceLifecycle` diagnose/fix/setup methods against narrow `HasRepoRoot`, lifecycle-owned health/fix/setup result types with generic setup messages, doctor/setup adapter boundaries, the static `LifecycleProvider` enum catalog/dispatcher, hook/config/local_db/auth_db/agent_trace_db lifecycle providers including setup-time repository-scoped Agent Trace DB initialization plus checkout identity diagnostics, implemented doctor aggregation over diagnose/fix providers, and implemented setup aggregation over `setup` providers in order config → local_db → auth_db → agent_trace_db → hooks when requested) - `context/sce/cli-exit-code-contract.md` (stable class-based `sce` process exit-code contract in `cli/src/app.rs`, so automation can branch on failure category without parsing error text) diff --git a/context/glossary.md b/context/glossary.md index 5661f1d4..704a4409 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -233,8 +233,7 @@ - `AgentTraceVcs`: Top-level VCS metadata struct in `cli/src/services/agent_trace.rs` carrying `type` and `revision`; builder behavior maps `type` from caller metadata (`AgentTraceMetadataInput.vcs_type`, enum-backed) and maps revision from caller metadata when VCS metadata is present. - `AgentTrace`: Top-level struct in `cli/src/services/agent_trace.rs` representing the minimal agent-trace payload, carrying top-level `version` (fixed to `0.1.0`, strict numeric `x.y.z`), `id` (UUIDv7 string derived from the same commit-time moment used for `timestamp` in `build_agent_trace(...)`), `timestamp` (caller-provided commit timestamp via `AgentTraceMetadataInput.commit_timestamp`, validated as RFC 3339), optional `vcs` (`Option`, omitted from serialized JSON when `None`), and `files` (`Vec`, one per `post_commit_patch` file); `serde`-serializable with `snake_case` field naming - `classify_hunk`: Public function in `cli/src/services/agent_trace.rs` that classifies a single `post_commit_patch` hunk against `intersection_patch` hunks by matching on `old_start` slot, returning `HunkContributor::Ai` for exact line-by-line match, `Mixed` for same-slot-but-different-content, or `Unknown` when no matching slot exists -- `AgentTraceMetadataInput`: Metadata input struct in `cli/src/services/agent_trace.rs` that carries `commit_timestamp` (RFC 3339 commit-time value used as `AgentTrace.timestamp`), `commit_revision` (mapped to `AgentTrace.vcs.revision` when VCS metadata is emitted), and optional `vcs_type` (`Option`, mapped to `AgentTrace.vcs.type` and controlling whether top-level `vcs` is emitted). -- `AgentTraceVcsType`: Schema-aligned VCS enum in `cli/src/services/agent_trace.rs` (`Git`, `Jj`, `Hg`, `Svn`) serialized as `snake_case` JSON values (`git`, `jj`, `hg`, `svn`) for `AgentTrace.vcs.type`. +- `AgentTraceMetadataInput`: Metadata input struct in `cli/src/services/agent_trace.rs` that carries `commit_timestamp` (RFC 3339 commit-time value used as `AgentTrace.timestamp`), `commit_revision` (mapped to `AgentTrace.vcs.revision` when VCS metadata is emitted), and optional `vcs_type` (`Option`, mapped to `AgentTrace.vcs.type` and controlling whether top-level `vcs` is emitted); `AgentTraceVcsType` is the schema-aligned `Git`/`Jj`/`Hg`/`Svn` enum serialized as `git`/`jj`/`hg`/`svn`. - `build_agent_trace`: Public function in `cli/src/services/agent_trace.rs` that computes `intersection_patch = intersect_patches(constructed_patch, post_commit_patch)`, iterates over `post_commit_patch` files and hunks, classifies each hunk against `intersection_patch`, validates `AgentTraceMetadataInput.commit_timestamp` as RFC 3339, derives UUIDv7 `AgentTrace.id` from that same commit-time moment, and returns `Result` with top-level metadata fields plus one `Conversation` per `post_commit_patch` hunk; consumed by the active post-commit hook flow, with no standalone `sce agent-trace` command surface. - `agent-trace plugin diff extraction seam`: Helper `extractDiffTracePayload` in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` that accepts a typed `message` event and returns `{ sessionID, diff, time, model_id }` only for user-role messages with non-empty `summary?.diffs`; it joins present object-entry `patch` fields with `\n`, skips entries without `patch`, returns `undefined` when no usable patches remain, uses `Date.now()` for `time`, and builds `model_id` as `providerID/modelID` from `event.properties.info.model`. - `get_or_create_encryption_key`: Public keyring-backed helper in `cli/src/services/db/encryption_key.rs` that retrieves or generates a 64-character hex encryption key from the OS credential store (macOS Keychain, Linux Secret Service via zbus, Windows Credential Store); uses `keyring_core::Entry` with service name `"sce"` and the database name as username. Actively consumed by `EncryptedTursoDb::new()` via the shared adapter constructor. @@ -248,3 +247,4 @@ - `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. - `AgentTraceExportReader`: Read-only incremental export reader in `cli/src/services/agent_trace_export/mod.rs` over one `RepositoryAgentTraceDb`, exposing `read_messages_after`/`read_parts_after`/`read_diff_traces_after`/`read_agent_traces_after`, each `(cursor: i64, limit: usize) -> Result>` over `WHERE id > cursor ORDER BY id ASC LIMIT {limit}`. Holds no local cursor, performs no mutation, makes no network calls, and returns owned camelCase `serde::Serialize` export-row DTOs matching the shipped control-plane ingestion contract. See `context/sce/agent-trace-export-readers.md`. - `context synchronization lifecycle`: Durable task-level state for synchronization after successful `/next-task` execution. The task record is `pending`, `synced`, or `blocked`; blocked records carry a blocker, required action, and retry condition. Missing lifecycle state on a completed task is unresolved debt, not evidence of synchronization. `/validate` does not persist a plan-level synchronization lifecycle. See `context/sce/shared-context-code-workflow.md`. +- `agent_trace.auto_sync`: Config-file-only boolean opt-in resolved by the shared config layers for the post-commit Agent Trace synchronization boundary; omitted values are `false`, and `sce config show` reports its winning source. The asynchronous launcher behavior is owned by the later hook-integration task. diff --git a/context/overview.md b/context/overview.md index 6e524c6e..d7ca01b9 100644 --- a/context/overview.md +++ b/context/overview.md @@ -12,7 +12,7 @@ The generated `/next-task` workflow persists task-level context-synchronization - **Stderr diagnostics:** stable `SCE-ERR-{PARSE,VALIDATION,RUNTIME,DEPENDENCY}` codes with class-default `Try:` remediation (see `context/sce/cli-error-code-taxonomy.md`). - **Stdout/stderr:** command payloads on stdout only; redacted diagnostics and text-mode `sce sync` progress on stderr, while JSON sync remains silent (see `context/sce/cli-stdout-stderr-contract.md`). - **Observability:** config-resolved logging to stderr, optional dated/session-partitioned `log_dir` / `SCE_LOG_DIR` files with retention (see `context/sce/cli-observability-contract.md`). -- **Config precedence:** `flags > env > config file > defaults` (see `context/cli/config-precedence-contract.md`). +- **Config precedence:** `flags > env > config file > defaults` (see `context/cli/config-precedence-contract.md`); the config-file-only `agent_trace.auto_sync` opt-in defaults to `false` and is resolved with source metadata for the post-commit trigger boundary. - **Attribution hooks:** enabled by default, gated by staged-diff AI-overlap preflight; `SCE_ATTRIBUTION_HOOKS_DISABLED` opt-out (see `context/sce/agent-trace-commit-msg-coauthor-policy.md`). - **Install channels:** repo-flake Nix, Cargo, npm, and source-built Flatpak (`dev.crocoder.sce`); Homebrew deferred (see `context/sce/cli-first-install-channels-contract.md`). diff --git a/context/patterns.md b/context/patterns.md index 07dc6ede..0bbdda28 100644 --- a/context/patterns.md +++ b/context/patterns.md @@ -127,7 +127,7 @@ - For observability log-directory configuration, resolve `log_dir` through `SCE_LOG_DIR` > config-file `log_dir` > `default_paths::observability_log_dir()` (`/sce/logs`; Linux `${XDG_STATE_HOME:-~/.local/state}/sce/logs`); select log files per emission from the machine-local date and optional logger session context, append rendered records to the selected file, run retention only after successfully creating a selected file, and keep session IDs out of rendered log schemas unless a caller explicitly passes them as normal fields. - Keep `log_file_retention_limit` flat and config-file/default only: validate it as an integer with minimum `1`, merge global before local, default it to `10`, expose resolved source metadata without adding an environment variable or CLI flag, and pass the resolved value unchanged to primary and v2 creation-triggered logger cleanup. -- For runtime CLI configuration, keep precedence deterministic and explicit (`flags > env > config file > defaults`) and expose inspect/validate command entrypoints with stable text/JSON outputs. +- For runtime CLI configuration, keep precedence deterministic and explicit (`flags > env > config file > defaults`) and expose inspect/validate command entrypoints with stable text/JSON outputs. Config-file-only Agent Trace runtime switches such as `agent_trace.auto_sync` should default conservatively, resolve global before local, and expose winning source metadata without adding an environment variable or CLI flag unless the contract explicitly requires one. - For commands that support text/JSON dual output, centralize `--format ` parsing in one shared contract and pass command-specific `--help` guidance into invalid-value errors instead of duplicating parser logic per command. - For setup-style command contracts, keep interactive mode as the zero-flag default and enforce mutually-exclusive explicit target flags for non-interactive automation. - For durable-context bootstrap, keep create-if-missing additive semantics: ensure baseline paths on every successful setup path, offer a dedicated standalone `--bootstrap-context` mode, and never overwrite existing context content. diff --git a/context/plans/automatic-agent-trace-sync.md b/context/plans/automatic-agent-trace-sync.md new file mode 100644 index 00000000..4d4b974c --- /dev/null +++ b/context/plans/automatic-agent-trace-sync.md @@ -0,0 +1,102 @@ +# Plan: automatic-agent-trace-sync + +## Change summary + +Add opt-in, post-commit Agent Trace synchronization without changing the existing synchronization engine. After the production post-commit flow successfully persists the built Agent Trace in the repository-scoped database, the hook will resolve `agent_trace.auto_sync` through the existing config resolver and, when enabled, launch the current `sce` executable as a detached/best-effort `sync --format json` child in the repository root. The hook will not wait for the child, expose its output, or make child startup/completion/network failures affect a successful commit. + +Extend the canonical Pkl schema and Rust config layers with `agent_trace.auto_sync`, defaulting to `false`, including typed parsing, resolution, inspection/validation output, and focused schema/resolution tests. Add a small sync-owned launcher seam and production tests for command construction, working directory, fail-open spawning, and post-commit ordering. Preserve the existing `services::sync` cursor-authoritative four-stream implementation, avoid all high-frequency trace-hook triggers and persistent background machinery, and document the one-shot asynchronous behavior and retry semantics in durable SCE context. + +## Acceptance criteria + +- [ ] AC1: A config file containing `{ "agent_trace": { "auto_sync": true } }` validates and resolves as enabled, an invalid `auto_sync` type is rejected, and an omitted value resolves to `false`. + - Validate: targeted config schema/resolver tests for valid, invalid-type, and omitted-value cases; `nix run .#pkl-check-generated`. +- [ ] AC2: When `agent_trace.auto_sync` is enabled and post-commit Agent Trace persistence succeeds, the hook launches the current executable with exactly `sync --format json`, uses the repository root as child working directory, discards stdin/stdout/stderr, and returns without waiting for the child. + - Validate: focused launcher and post-commit boundary tests asserting executable/arguments/current directory/stdio configuration and injected launcher invocation ordering. +- [ ] AC3: Disabled auto-sync causes no launch; failed Agent Trace validation or persistence causes no launch; and a launcher/current-executable/spawn failure leaves the otherwise successful post-commit result successful. + - Validate: focused post-commit and launcher failure tests covering each fail-open branch. +- [ ] AC4: Automatic synchronization invokes only the existing `sce sync` command and introduces no daemon, watcher, polling loop, local cursor, synchronization database, persistent service, or high-frequency `conversation-trace`/`diff-trace` trigger. + - Validate: code inspection plus targeted module tests and the existing sync test suite; verify no changes to the existing sync protocol/cursor implementation. +- [ ] AC5: Durable SCE context explains manual `sce sync`, opt-in `agent_trace.auto_sync`, one-shot asynchronous execution, no daemon, fail-open behavior, and local retryability through the control-plane cursor authority. + - Validate: manual review of the updated/new context files against the implemented code. + +### Full validation + +Repository-wide validation after the last task: + +- `nix flake check` +- `nix run .#pkl-check-generated` + +### Context sync + +- `context/overview.md` +- `context/architecture.md` +- `context/glossary.md` +- `context/patterns.md` +- `context/context-map.md` +- `context/cli/sync-command.md` +- `context/cli/config-precedence-contract.md` +- `context/sce/agent-trace-hooks-command-routing.md` +- A new `context/cli/agent-trace-auto-sync.md` documenting the automatic trigger and its non-goals. + +## Task context synchronization lifecycle + +Persist this field in every plan; this is durable plan state, not chat state: + +- **Task context synchronization:** every task carries `pending | synced | blocked`. A completed task must be `synced` before another task can start or the plan can finish. +- For `blocked`, record **Blocker**, **Required action**, and **Retry condition** beside the status. Never infer `synced` from conversation history; write every lifecycle transition to the plan file. + +## Constraints and non-goals + +- **In scope:** canonical Pkl config schema; Rust config DTO, schema, resolver, render/validation plumbing and tests; `cli/src/services/sync/auto_sync.rs` or equivalent launcher; production post-commit boundary integration and injected launcher tests; relevant durable SCE documentation. +- **Out of scope:** changes to synchronization HTTP/API behavior, cursor reconciliation, export readers, Agent Trace DB schema, local sync state, high-frequency trace hooks, plugin/event-triggered sync, retry queues, daemons, watchers, polling loops, schedulers, PID files, locks/leases, and persistent background services. +- **Constraints:** use `std::env::current_exe()` rather than `$PATH`; spawn `sync --format json` with null stdin/stdout/stderr and repository-root `current_dir`; use `Command::spawn()` without status/wait; treat launcher failures and child non-zero completion as fail-open; preserve server-owned cursor retry behavior; use Nix-managed repository tooling and ephemeral Pkl generation rather than editing generated artifacts. +- **Non-goal:** implementing a second synchronization mechanism or making Git wait for network synchronization. + +## Assumptions + +- `agent_trace.auto_sync` is a config-file value in the existing global-then-local config merge, with no new environment variable or CLI flag; its omitted/default value is `false`, matching the requested opt-in rollout and the existing repository-identity config pattern. +- The launcher gets a narrow test seam (an injected process-launch closure or equivalent internal adapter) so tests can assert the child specification and spawn failures without starting a real sync process; production still uses `current_exe()` and `Command::spawn()`. +- Existing `sce config show`/`validate` surfaces are part of “support throughout the existing config system,” so the resolved `auto_sync` value and its default/config-file provenance are exposed consistently with the other Agent Trace config values. + +## Task stack + +- [x] T01: `Add opt-in agent_trace.auto_sync config resolution` (status:done) + - Task ID: T01 + - Scope: In — `config/pkl/base/sce-config-schema.pkl`, `cli/src/services/config/schema.rs`, `types.rs`, `resolver.rs`, config rendering/validation plumbing, and focused config tests for boolean validation, default false, global/local resolution, and inspection output. Out — process spawning and post-commit behavior. + - Dependencies: none + - Done when: the generated schema accepts boolean `agent_trace.auto_sync`, rejects non-boolean values, omitted config resolves to false, configured values resolve through the existing config layers, and config show/validate remain deterministic with the new field. + - Verify: targeted config tests through `nix develop -c ./scripts/run-cli-cargo.sh test --manifest-path cli/Cargo.toml config::`; `nix run .#pkl-check-generated`. + - Context synchronization: synced + - Completed: 2026-08-19 + - Files changed: `cli/src/services/config/render.rs`, `cli/src/services/config/resolver.rs`, `cli/src/services/config/schema.rs`, `cli/src/services/config/types.rs`, `config/pkl/base/sce-config-schema.pkl`, `context/plans/automatic-agent-trace-sync.md` + - Result: Added boolean `agent_trace.auto_sync` schema and typed config plumbing, defaulting to false, with global/local precedence, resolved provenance, deterministic config show output, and focused validation/resolution tests. + - Verify: `nix develop -c ./scripts/run-cli-cargo.sh test --manifest-path cli/Cargo.toml config::` — pass (25 tests); `nix run .#pkl-check-generated` — pass (107 generated files, inventory sha256 `5ebbf7a119a7f79e19f65a7c30ee032681ae749279270735b5fbb87b0e1b2658`). + - Context impact: interface — document the new `agent_trace.auto_sync` config contract, default/provenance behavior, and its future post-commit hook boundary in current-state config and Agent Trace context; review all five root context files for stale configuration summaries. + +- [ ] T02: `Implement fail-open one-shot sync launcher` (status:todo) + - Task ID: T02 + - Scope: In — `cli/src/services/sync/auto_sync.rs` or equivalent, sync module registration, production `current_exe`/`Command::spawn` construction, null stdio/current-directory configuration, and deterministic launcher tests. Out — invoking the launcher from high-frequency hooks or changing sync internals. + - Dependencies: T01 + - Done when: the production trigger constructs the current-executable `sync --format json` child in the supplied repository root, detaches without waiting, suppresses all child streams, and ignores current-executable/spawn failures; tests prove exact command construction and fail-open behavior. + - Verify: targeted Rust tests for `services::sync::auto_sync` through `nix develop -c ./scripts/run-cli-cargo.sh test --manifest-path cli/Cargo.toml auto_sync`. + - Context synchronization: pending + +- [ ] T03: `Trigger auto-sync after successful post-commit persistence` (status:todo) + - Task ID: T03 + - Scope: In — `cli/src/services/hooks/mod.rs` production post-commit boundary, config-gate lookup, launcher injection seam for post-commit tests, and focused tests for disabled/enabled/order/persistence-failure/spawn-failure cases. Out — `pre-commit`, `diff-trace`, `conversation-trace`, plugin event changes, and synchronization algorithm changes. + - Dependencies: T01, T02 + - Done when: a successful post-commit persistence result triggers exactly one best-effort launcher only when resolved auto-sync is enabled; persistence failures do not trigger it; disabled config does not trigger it; launcher failures do not change the successful hook result; the hook path never waits on the child. + - Verify: targeted Rust hook tests through `nix develop -c ./scripts/run-cli-cargo.sh test --manifest-path cli/Cargo.toml hooks::`; inspect the post-commit boundary and confirm no high-frequency hook call sites changed. + - Context synchronization: pending + +- [ ] T04: `Document asynchronous post-commit Agent Trace synchronization` (status:todo) + - Task ID: T04 + - Scope: In — the new auto-sync context document and the listed overview, architecture, glossary, patterns, context-map, sync-command, config-precedence, and hook-routing updates, reflecting the final implemented names and behavior. Out — code changes, generated target trees, generated schema artifacts, and historical decision records. + - Dependencies: T03 + - Done when: durable context distinguishes explicit/manual `sce sync` from opt-in asynchronous post-commit triggering, states that there is no daemon and failures are fail-open, explains that pending rows remain local for later retry, and accurately names the config and hook boundaries. + - Verify: manual code/context review; `nix run .#pkl-check-generated`; `nix flake check`. + - Context synchronization: pending + +## Open questions + +None. The request fixes the trigger boundary, command shape, opt-in default, fail-open semantics, prohibited architectures, test expectations, and documentation requirements; the plan records only local implementation choices that follow existing repository patterns. From cccb8aec5cb00065b4b158a78a63ff4dd2ede4fc Mon Sep 17 00:00:00 2001 From: David Abram Date: Wed, 19 Aug 2026 15:39:22 +0200 Subject: [PATCH 2/7] sync: Add fail-open automatic Agent Trace sync launcher Provide a sync-owned one-shot launcher so callers can start asynchronous Agent Trace synchronization without blocking or inheriting child output. It resolves the current executable, runs `sync --format json` from the repository root with null standard streams, and ignores executable or spawn failures; tests cover exact command construction and fail-open behavior. Ref: context/plans/automatic-agent-trace-sync.md (T02) Co-authored-by: SCE --- cli/src/services/config/resolver.rs | 6 +- cli/src/services/config/schema.rs | 10 +- cli/src/services/sync/auto_sync.rs | 140 ++++++++++++++++++++ cli/src/services/sync/mod.rs | 2 + context/cli/sync-command.md | 6 +- context/plans/automatic-agent-trace-sync.md | 9 +- 6 files changed, 164 insertions(+), 9 deletions(-) create mode 100644 cli/src/services/sync/auto_sync.rs diff --git a/cli/src/services/config/resolver.rs b/cli/src/services/config/resolver.rs index 92fe249b..3ce67d3b 100644 --- a/cli/src/services/config/resolver.rs +++ b/cli/src/services/config/resolver.rs @@ -834,10 +834,8 @@ mod tests { #[test] fn agent_trace_auto_sync_resolves_from_config_file() { - let runtime = resolve_runtime_with_config(Some( - r#"{"agent_trace":{"auto_sync":true}}"#, - )) - .unwrap(); + let runtime = + resolve_runtime_with_config(Some(r#"{"agent_trace":{"auto_sync":true}}"#)).unwrap(); assert!(runtime.agent_trace_auto_sync.value); assert_eq!( diff --git a/cli/src/services/config/schema.rs b/cli/src/services/config/schema.rs index 92fe41a3..15496424 100644 --- a/cli/src/services/config/schema.rs +++ b/cli/src/services/config/schema.rs @@ -753,7 +753,10 @@ mod agent_trace_config_tests { Some("upstream") ); assert_eq!( - config.agent_trace_auto_sync.as_ref().map(|value| value.value), + config + .agent_trace_auto_sync + .as_ref() + .map(|value| value.value), None ); } @@ -807,7 +810,10 @@ mod agent_trace_config_tests { let config = parse(r#"{"agent_trace":{"auto_sync":true}}"#).unwrap(); assert_eq!( - config.agent_trace_auto_sync.as_ref().map(|value| value.value), + config + .agent_trace_auto_sync + .as_ref() + .map(|value| value.value), Some(true) ); } diff --git a/cli/src/services/sync/auto_sync.rs b/cli/src/services/sync/auto_sync.rs new file mode 100644 index 00000000..0b049ace --- /dev/null +++ b/cli/src/services/sync/auto_sync.rs @@ -0,0 +1,140 @@ +//! Best-effort launcher for one-shot automatic Agent Trace synchronization. + +use std::io; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; + +const SYNC_ARGS: &[&str] = &["sync", "--format", "json"]; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum StdioMode { + Null, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct AutoSyncCommand { + executable: PathBuf, + args: Vec, + current_dir: PathBuf, + stdin: StdioMode, + stdout: StdioMode, + stderr: StdioMode, +} + +impl AutoSyncCommand { + fn new(executable: PathBuf, repository_root: &Path) -> Self { + Self { + executable, + args: SYNC_ARGS.iter().map(|arg| (*arg).to_string()).collect(), + current_dir: repository_root.to_path_buf(), + stdin: StdioMode::Null, + stdout: StdioMode::Null, + stderr: StdioMode::Null, + } + } +} + +/// Launches the current executable to synchronize the repository in the +/// background. Launcher failures are intentionally ignored by the caller. +pub fn launch(repository_root: &Path) { + let _ = launch_with(repository_root, std::env::current_exe, spawn_command); +} + +fn launch_with( + repository_root: &Path, + current_exe: FCurrentExe, + spawn: FSpawn, +) -> bool +where + FCurrentExe: FnOnce() -> io::Result, + FSpawn: FnOnce(AutoSyncCommand) -> io::Result<()>, +{ + let executable = match current_exe() { + Ok(executable) => executable, + Err(_) => return false, + }; + + spawn(AutoSyncCommand::new(executable, repository_root)).is_ok() +} + +fn spawn_command(spec: AutoSyncCommand) -> io::Result<()> { + let mut command = Command::new(spec.executable); + command + .args(spec.args) + .current_dir(spec.current_dir) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + + // Dropping Child does not wait for it; the spawned sync continues + // independently of the post-commit caller. + let _child = command.spawn()?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + use std::io; + use std::path::{Path, PathBuf}; + use std::rc::Rc; + + use super::{launch_with, AutoSyncCommand, StdioMode, SYNC_ARGS}; + + #[test] + fn launch_builds_the_expected_detached_command() { + let captured = Rc::new(RefCell::new(None)); + let captured_by_spawn = Rc::clone(&captured); + + let launched = launch_with( + Path::new("/repo/root"), + || Ok(PathBuf::from("/usr/local/bin/sce")), + move |command: AutoSyncCommand| { + *captured_by_spawn.borrow_mut() = Some(command); + Ok(()) + }, + ); + + assert!(launched); + assert_eq!( + captured.borrow().clone(), + Some(AutoSyncCommand { + executable: PathBuf::from("/usr/local/bin/sce"), + args: SYNC_ARGS.iter().map(|arg| (*arg).to_string()).collect(), + current_dir: PathBuf::from("/repo/root"), + stdin: StdioMode::Null, + stdout: StdioMode::Null, + stderr: StdioMode::Null, + }) + ); + } + + #[test] + fn current_executable_failure_is_fail_open() { + let spawn_called = Rc::new(RefCell::new(false)); + let spawn_called_by_spawn = Rc::clone(&spawn_called); + + let launched = launch_with( + Path::new("/repo/root"), + || Err(io::Error::other("current executable unavailable")), + move |_| { + *spawn_called_by_spawn.borrow_mut() = true; + Ok(()) + }, + ); + + assert!(!launched); + assert!(!*spawn_called.borrow()); + } + + #[test] + fn spawn_failure_is_fail_open() { + let launched = launch_with( + Path::new("/repo/root"), + || Ok(PathBuf::from("/usr/local/bin/sce")), + |_| Err(io::Error::other("spawn unavailable")), + ); + + assert!(!launched); + } +} diff --git a/cli/src/services/sync/mod.rs b/cli/src/services/sync/mod.rs index 424107cd..fac25936 100644 --- a/cli/src/services/sync/mod.rs +++ b/cli/src/services/sync/mod.rs @@ -1,5 +1,7 @@ //! Top-level `sce sync` command and Agent Trace synchronization service. +#[allow(dead_code)] +pub mod auto_sync; pub mod command; pub mod progress; pub mod render_sync; diff --git a/context/cli/sync-command.md b/context/cli/sync-command.md index 5f5070cb..dd895ef2 100644 --- a/context/cli/sync-command.md +++ b/context/cli/sync-command.md @@ -9,7 +9,11 @@ longer available; no compatibility alias is retained. The Clap surface is defined in `cli/src/cli_schema.rs` and dispatched through the static `RuntimeCommand::Sync` variant. The sync-owned command boundary lives under `cli/src/services/sync/`; shared storage, export, authentication, and -control-plane protocol infrastructure remains in their existing services. +control-plane protocol infrastructure remains in their existing services. The +same boundary owns a best-effort one-shot launcher for callers that need +asynchronous synchronization: it resolves the current `sce` executable, starts +`sync --format json` in the repository root with null standard streams, and does +not wait for the child; executable and spawn failures are ignored. Sync orchestration owns its `SyncProgressEvent` lifecycle, batch, and stream-completion payloads and publishes them through the consumer-typed, library-independent `services::sync::progress::ProgressReporter` contract. diff --git a/context/plans/automatic-agent-trace-sync.md b/context/plans/automatic-agent-trace-sync.md index 4d4b974c..f4eb36ad 100644 --- a/context/plans/automatic-agent-trace-sync.md +++ b/context/plans/automatic-agent-trace-sync.md @@ -73,13 +73,18 @@ Persist this field in every plan; this is durable plan state, not chat state: - Verify: `nix develop -c ./scripts/run-cli-cargo.sh test --manifest-path cli/Cargo.toml config::` — pass (25 tests); `nix run .#pkl-check-generated` — pass (107 generated files, inventory sha256 `5ebbf7a119a7f79e19f65a7c30ee032681ae749279270735b5fbb87b0e1b2658`). - Context impact: interface — document the new `agent_trace.auto_sync` config contract, default/provenance behavior, and its future post-commit hook boundary in current-state config and Agent Trace context; review all five root context files for stale configuration summaries. -- [ ] T02: `Implement fail-open one-shot sync launcher` (status:todo) +- [x] T02: `Implement fail-open one-shot sync launcher` (status:done) - Task ID: T02 - Scope: In — `cli/src/services/sync/auto_sync.rs` or equivalent, sync module registration, production `current_exe`/`Command::spawn` construction, null stdio/current-directory configuration, and deterministic launcher tests. Out — invoking the launcher from high-frequency hooks or changing sync internals. - Dependencies: T01 - Done when: the production trigger constructs the current-executable `sync --format json` child in the supplied repository root, detaches without waiting, suppresses all child streams, and ignores current-executable/spawn failures; tests prove exact command construction and fail-open behavior. - Verify: targeted Rust tests for `services::sync::auto_sync` through `nix develop -c ./scripts/run-cli-cargo.sh test --manifest-path cli/Cargo.toml auto_sync`. - - Context synchronization: pending + - Completed: 2026-08-19 + - Files changed: `cli/src/services/sync/auto_sync.rs`, `cli/src/services/sync/mod.rs`, `context/cli/sync-command.md`, `context/plans/automatic-agent-trace-sync.md` + - Result: Added a sync-owned best-effort launcher that resolves `current_exe`, starts `sync --format json` in the supplied repository root with null stdin/stdout/stderr, drops the child without waiting, and ignores executable/spawn failures; added exact command-spec and fail-open tests without changing sync internals or hook call sites. + - Verify: `nix develop -c ./scripts/run-cli-cargo.sh test --manifest-path cli/Cargo.toml auto_sync` — pass (8 tests, including existing auto-sync config tests). + - Context impact: domain — documented the sync-owned asynchronous launcher boundary and its fail-open/no-wait behavior in `context/cli/sync-command.md`; root context pass verified with no edits. + - Context synchronization: synced - [ ] T03: `Trigger auto-sync after successful post-commit persistence` (status:todo) - Task ID: T03 From 927447b4e8798b049f639d68d5db0605a86e5d49 Mon Sep 17 00:00:00 2001 From: David Abram Date: Wed, 19 Aug 2026 15:46:04 +0200 Subject: [PATCH 3/7] hooks: Trigger post-commit Agent Trace auto-sync Enable the config-gated post-commit boundary to launch the existing detached sync-owned `sync --format json` command only after Agent Trace persistence succeeds. Preserve fail-open launcher behavior and keep the child asynchronous so the hook does not wait or add high-frequency trigger paths. Plan: automatic-agent-trace-sync (T03) Co-authored-by: SCE --- cli/src/services/hooks/mod.rs | 129 +++++++++++++++++- context/architecture.md | 2 +- context/cli/config-precedence-contract.md | 4 +- context/cli/sync-command.md | 11 +- context/glossary.md | 2 +- context/overview.md | 2 +- context/plans/automatic-agent-trace-sync.md | 7 +- .../sce/agent-trace-hooks-command-routing.md | 1 + 8 files changed, 147 insertions(+), 11 deletions(-) diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index 7e9f172a..c780d7b8 100644 --- a/cli/src/services/hooks/mod.rs +++ b/cli/src/services/hooks/mod.rs @@ -32,6 +32,7 @@ use crate::services::structured_patch::{ build_claude_post_tool_use_patch, derive_claude_structured_patch, ClaudeStructuredPatchDerivationResult, PatchBuildResult, }; +use crate::services::sync::auto_sync; pub mod claude_transcript; pub mod command; pub mod lifecycle; @@ -1397,15 +1398,24 @@ fn run_post_commit_subcommand( remote_url, run_post_commit_intersection_flow, run_post_commit_agent_trace_flow, + |root| { + config::resolve_hook_runtime_config(root).map(|runtime| runtime.agent_trace_auto_sync) + }, + |root| { + auto_sync::launch(root); + Ok(()) + }, ) } -fn run_post_commit_subcommand_with( +fn run_post_commit_subcommand_with( repository_root: &Path, vcs_type: Option, remote_url: &str, run_intersection_flow: F, run_agent_trace_flow: B, + resolve_auto_sync: C, + launch_auto_sync: L, ) -> Result where F: FnOnce(&Path) -> Result, @@ -1415,10 +1425,16 @@ where Option, &str, ) -> Result, + C: FnOnce(&Path) -> Result, + L: FnOnce(&Path) -> Result<()>, { let result = run_intersection_flow(repository_root)?; let _agent_trace = run_agent_trace_flow(repository_root, &result, vcs_type, remote_url)?; + if resolve_auto_sync(repository_root)? { + let _ = launch_auto_sync(repository_root); + } + Ok(format!( "post-commit hook processed intersection: commit={}, intersection_files={}", result.post_commit_data.commit_oid, @@ -2871,4 +2887,115 @@ mod tests { assert_eq!(output.tool_name, Some(String::from("opencode"))); assert_eq!(output.tool_version, Some(String::from("1.2.3"))); } + + fn post_commit_flow_result() -> PostCommitIntersectionFlowResult { + PostCommitIntersectionFlowResult { + combined_recent_patch: valid_patch("src/lib.rs", "shared line"), + post_commit_data: PostCommitPatchData { + commit_oid: String::from("abc123"), + commit_time_ms: 1_800_000_000_000, + parsed_patch: valid_patch("src/lib.rs", "shared line"), + }, + tool_name: None, + tool_version: None, + } + } + + fn minimal_agent_trace() -> AgentTrace { + serde_json::from_value(json!({ "files": [] })) + .expect("minimal Agent Trace should deserialize") + } + + #[test] + fn post_commit_auto_sync_launches_after_successful_persistence_when_enabled() { + let events = RefCell::new(Vec::new()); + + let output = run_post_commit_subcommand_with( + Path::new("/repo"), + None, + "", + |_| { + events.borrow_mut().push("intersection"); + Ok(post_commit_flow_result()) + }, + |_, _, _, _| { + events.borrow_mut().push("persistence"); + Ok(minimal_agent_trace()) + }, + |_| { + events.borrow_mut().push("config"); + Ok(true) + }, + |_| { + events.borrow_mut().push("launch"); + Ok(()) + }, + ) + .expect("successful post-commit should remain successful"); + + assert!(output.contains("post-commit hook processed intersection")); + assert_eq!( + events.into_inner(), + vec!["intersection", "persistence", "config", "launch"] + ); + } + + #[test] + fn post_commit_auto_sync_does_not_launch_when_disabled() { + let launch_called = RefCell::new(false); + + run_post_commit_subcommand_with( + Path::new("/repo"), + None, + "", + |_| Ok(post_commit_flow_result()), + |_, _, _, _| Ok(minimal_agent_trace()), + |_| Ok(false), + |_| { + *launch_called.borrow_mut() = true; + Ok(()) + }, + ) + .expect("disabled auto-sync should not affect post-commit success"); + + assert!(!*launch_called.borrow()); + } + + #[test] + fn post_commit_persistence_failure_does_not_launch_auto_sync() { + let launch_called = RefCell::new(false); + + let error = run_post_commit_subcommand_with( + Path::new("/repo"), + None, + "", + |_| Ok(post_commit_flow_result()), + |_, _, _, _| Err(anyhow!("Agent Trace persistence failed")), + |_| panic!("auto-sync config must not be resolved after persistence failure"), + |_| { + *launch_called.borrow_mut() = true; + Ok(()) + }, + ) + .expect_err("persistence failure should be returned"); + + assert!(error.to_string().contains("persistence failed")); + assert!(!*launch_called.borrow()); + } + + #[test] + fn post_commit_auto_sync_launcher_failure_is_fail_open() { + let output = run_post_commit_subcommand_with( + Path::new("/repo"), + None, + "", + |_| Ok(post_commit_flow_result()), + |_, _, _, _| Ok(minimal_agent_trace()), + |_| Ok(true), + |_| Err(anyhow!("spawn unavailable")), + ) + .expect("launcher failure must not affect post-commit success"); + + assert!(output.contains("post-commit hook processed intersection")); + } } diff --git a/context/architecture.md b/context/architecture.md index 9a71affa..cd2f24e7 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -129,7 +129,7 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/doctor/mod.rs` owns the current doctor request/report surface while focused submodules (`doctor/inspect.rs`, `doctor/render.rs`, `doctor/fixes.rs`, `doctor/types.rs`) split report fact collection, rendering, manual fix reporting, and doctor-owned domain types into smaller seams; `cli/src/services/doctor/command.rs` owns the `DoctorCommand` payload used by the static `RuntimeCommand` enum and executes against any context implementing repo-root scoping. Runtime doctor execution resolves a repository root, derives a scoped context, requests the shared static lifecycle provider catalog with hooks included for service-owned `diagnose` and `fix` behavior, adapts lifecycle-owned health/fix records into doctor-owned problem/fix records, and then renders stable text/JSON problem records with category/severity/fixability/remediation fields plus deterministic fix-result reporting in fix mode. Agent Trace database inspection is no longer a doctor-adjacent command surface; doctor owns repository-scoped DB health and checkout identity facts, while `sce sync` owns control-plane synchronization. Report fact collection preserves environment/repository/hook/integration display data, while service-owned lifecycle providers own config validation, local DB and repository-scoped Agent Trace DB readiness/bootstrap, and hook rollout diagnosis/repair. Integration inspection in `doctor/inspect.rs` is scoped twice over: `resolve_doctor_integration_targets` picks which targets to inspect, and `persisted_optional_workflows` (reused from setup) resolves which optional workflows the repository selected, which the OpenCode/Claude/Pi child collectors apply through `iter_embedded_assets_for_setup_target_with_selection`. An unselected optional workflow therefore contributes no expected children at all, so no row and no missing/mismatch problem can be produced for it, while a selected one keeps the unchanged presence and content-hash checks. - `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 repository-level `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 the repository-scoped Agent Trace DB without post-commit file artifacts); `diff-trace` performs STDIN JSON intake, validates required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent/`null` → `None`), required nullable/non-empty `tool_version` plus required `u64` `time` (Unix epoch milliseconds), rejects values that cannot fit signed `time_ms` storage, prefixes the stored `diff_traces.session_id` before insert construction (`oc_` for OpenCode, `cc_` for Claude, `pi_` for Pi, same-tool idempotent), and inserts the parsed payload fields into `RepositoryAgentTraceDb` without creating a parsed-payload `context/tmp` artifact; Claude structured `PostToolUse` diff-trace intake resolves model attribution event-locally: direct top-level or nested metadata wins, otherwise the event's `transcript_path` is scanned for the assistant envelope whose `tool_use.id` matches `tool_use_id`; either source is normalized once with the `claude/` prefix and lookup failures remain nullable. `session-model` is no longer a supported hook route. +- `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 repository-level `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 the repository-scoped Agent Trace DB without post-commit file artifacts); after successful validation and persistence, the config-file-only `agent_trace.auto_sync` gate can launch one detached sync-owned `sync --format json` child in the repository root, with launcher failures ignored and no high-frequency hook trigger; `diff-trace` performs STDIN JSON intake, validates required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent/`null` → `None`), required nullable/non-empty `tool_version` plus required `u64` `time` (Unix epoch milliseconds), rejects values that cannot fit signed `time_ms` storage, prefixes the stored `diff_traces.session_id` before insert construction (`oc_` for OpenCode, `cc_` for Claude, `pi_` for Pi, same-tool idempotent), and inserts the parsed payload fields into `RepositoryAgentTraceDb` without creating a parsed-payload `context/tmp` artifact; Claude structured `PostToolUse` diff-trace intake resolves model attribution event-locally: direct top-level or nested metadata wins, otherwise the event's `transcript_path` is scanned for the assistant envelope whose `tool_use.id` matches `tool_use_id`; either source is normalized once with the `claude/` prefix and lookup failures remain nullable. `session-model` is no longer a supported hook route. - Generated Claude settings no longer register `SessionStart` for Agent Trace model attribution, and `sce hooks session-model` is no longer a supported hook command. The `session_models` table/API and session-level fallback lookup were removed in T02 of the `remove-session-models-direct-claude-model-id` plan; `diff-trace` now uses direct-first/event-transcript-second Claude `model_id` resolution and direct `tool_version` values, without restoring session-level state. - `cli/src/services/resilience.rs` defines bounded retry/timeout/backoff execution policy (`RetryPolicy`, `run_with_retry`) for transient operation hardening with deterministic failure messaging and retry observability. - `cli/src/services/sync/progress.rs` owns the sync-local, consumer-typed progress seam: generic `ProgressReporter` supports event delivery plus explicit successful finalization, closure-based collectors, and a no-op implementation alongside the fixed `indicatif` stderr presentation adapter. `cli/src/services/sync/sync.rs` owns `SyncProgressEvent` and its four-stream payload semantics, while `sync/command.rs` selects the terminal adapter for text and the no-op reporter for JSON. There is no top-level `cli/src/services/progress/` module; sync orchestration depends only on its sync-owned contract, so terminal-library details stay at the sync presentation boundary. diff --git a/context/cli/config-precedence-contract.md b/context/cli/config-precedence-contract.md index c03ea0d7..2cbdf6f9 100644 --- a/context/cli/config-precedence-contract.md +++ b/context/cli/config-precedence-contract.md @@ -4,7 +4,7 @@ This contract documents the implemented `sce config` command behavior, runtime resolver, renderer, and canonical Pkl-authored `sce/config.json` schema. The schema is emitted to payload-relative `config/schema/sce-config.schema.json` under Cargo `OUT_DIR` or packaging fallbacks and embedded by `cli/src/services/config/schema.rs` as `SCE_CONFIG_SCHEMA_JSON`; no generated schema is committed. -The current implementation resolves flat logging keys and Agent Trace runtime keys with deterministic precedence and source metadata, exposes resolved-value inspection through `sce config show`, and keeps `sce config validate` focused on validation status plus errors/warnings. Threshold, format, directory, and `log_file_retention_limit` values are consumed by runtime logging; the concrete logger uses the retention value for primary and v2 creation-triggered cleanup. The opt-in `agent_trace.auto_sync` value is currently config plumbing for the post-commit trigger boundary and defaults to disabled until enabled by the later hook integration task. +The current implementation resolves flat logging keys and Agent Trace runtime keys with deterministic precedence and source metadata, exposes resolved-value inspection through `sce config show`, and keeps `sce config validate` focused on validation status plus errors/warnings. Threshold, format, directory, and `log_file_retention_limit` values are consumed by runtime logging; the concrete logger uses the retention value for primary and v2 creation-triggered cleanup. The opt-in `agent_trace.auto_sync` value is consumed by the post-commit trigger boundary and defaults to disabled. ## Command surface @@ -29,7 +29,7 @@ Agent Trace repository identity keys are also config-file only with per-key `glo - `agent_trace.repository_id` — optional explicit repository identity; resolves as an optional value with no default. - `agent_trace.repository_remote` — Git remote name used to derive repository identity; defaults to `origin` (`DEFAULT_AGENT_TRACE_REPOSITORY_REMOTE` in `cli/src/services/config/resolver.rs`) when no config file sets it. -- `agent_trace.auto_sync` — opt-in boolean for the future post-commit Agent Trace synchronization trigger; config-file only, with no flag or environment layer, and defaults to `false`. +- `agent_trace.auto_sync` — opt-in boolean for the post-commit Agent Trace synchronization trigger; config-file only, with no flag or environment layer, and defaults to `false`. Resolved observability values that currently have no CLI flag layer follow the same lower-precedence chain without a flag step: diff --git a/context/cli/sync-command.md b/context/cli/sync-command.md index dd895ef2..d7e7e708 100644 --- a/context/cli/sync-command.md +++ b/context/cli/sync-command.md @@ -10,10 +10,13 @@ The Clap surface is defined in `cli/src/cli_schema.rs` and dispatched through the static `RuntimeCommand::Sync` variant. The sync-owned command boundary lives under `cli/src/services/sync/`; shared storage, export, authentication, and control-plane protocol infrastructure remains in their existing services. The -same boundary owns a best-effort one-shot launcher for callers that need -asynchronous synchronization: it resolves the current `sce` executable, starts -`sync --format json` in the repository root with null standard streams, and does -not wait for the child; executable and spawn failures are ignored. +same boundary owns a best-effort one-shot launcher used by the post-commit +hook when `agent_trace.auto_sync` is enabled: it resolves the current `sce` +executable, starts `sync --format json` in the repository root with null standard +streams, and does not wait for the child; executable and spawn failures are +ignored. The launcher is not a daemon or retry queue; local rows remain available +for a later manual or automatic invocation through the control-plane cursor +authority. Sync orchestration owns its `SyncProgressEvent` lifecycle, batch, and stream-completion payloads and publishes them through the consumer-typed, library-independent `services::sync::progress::ProgressReporter` contract. diff --git a/context/glossary.md b/context/glossary.md index 704a4409..85d01dea 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -247,4 +247,4 @@ - `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. - `AgentTraceExportReader`: Read-only incremental export reader in `cli/src/services/agent_trace_export/mod.rs` over one `RepositoryAgentTraceDb`, exposing `read_messages_after`/`read_parts_after`/`read_diff_traces_after`/`read_agent_traces_after`, each `(cursor: i64, limit: usize) -> Result>` over `WHERE id > cursor ORDER BY id ASC LIMIT {limit}`. Holds no local cursor, performs no mutation, makes no network calls, and returns owned camelCase `serde::Serialize` export-row DTOs matching the shipped control-plane ingestion contract. See `context/sce/agent-trace-export-readers.md`. - `context synchronization lifecycle`: Durable task-level state for synchronization after successful `/next-task` execution. The task record is `pending`, `synced`, or `blocked`; blocked records carry a blocker, required action, and retry condition. Missing lifecycle state on a completed task is unresolved debt, not evidence of synchronization. `/validate` does not persist a plan-level synchronization lifecycle. See `context/sce/shared-context-code-workflow.md`. -- `agent_trace.auto_sync`: Config-file-only boolean opt-in resolved by the shared config layers for the post-commit Agent Trace synchronization boundary; omitted values are `false`, and `sce config show` reports its winning source. The asynchronous launcher behavior is owned by the later hook-integration task. +- `agent_trace.auto_sync`: Config-file-only boolean opt-in resolved by the shared config layers for the post-commit Agent Trace synchronization boundary; omitted values are `false`, and `sce config show` reports its winning source. After validation and repository-DB persistence, enabled post-commit runs launch the existing sync-owned `sync --format json` command once through the current executable with detached null-standard-stream child semantics and repository-root working directory; the hook never waits, has no daemon or high-frequency trigger, and treats launcher failures as fail-open. See [Agent Trace hook routing](sce/agent-trace-hooks-command-routing.md). diff --git a/context/overview.md b/context/overview.md index d7ca01b9..ff1c9f6a 100644 --- a/context/overview.md +++ b/context/overview.md @@ -67,7 +67,7 @@ The prior no-git-wrapper Agent Trace design artifacts under `context/sce/agent-t 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, then assigning each structured hunk the persisted row model and every structured touched line the persisted canonical `cc_...` row session), combines/intersects patches, persists intersection metadata to `post_commit_patch_intersections`, and persists the schema-validated built Agent Trace payload, including optional top-level `tool` metadata from recent diff-trace rows, top-level `metadata.sce.version` from the compiled `sce` CLI package version, and range-level `content_hash` values, to AgentTraceDb `agent_traces` (DB-only, no post-commit Agent Trace file artifact); `diff-trace` currently validates/persists required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent or `null` → `None`, present+non-empty → `Some`, present+empty → error), required nullable/non-empty `tool_version`, plus required `u64` millisecond `time`, uses direct-first/event-transcript-second Claude `model_id` resolution plus direct `tool_version` without any `session_models` runtime, and continues with `None` when event-local lookup cannot resolve attribution, with same-tool-idempotent stored `session_id` prefixing (`oc_` for OpenCode, `cc_` for Claude, `pi_` for Pi), non-lossy AgentTraceDb `time_ms` conversion, and no parsed-payload artifact fallback. Claude structured `PostToolUse` diff-trace intake prefers direct top-level or nested model metadata, then matches the event's `tool_use_id` in its JSONL `transcript_path`, normalizes either source once with the `claude/` prefix, and fails open to `None`. The CLI now also includes an approved operator-environment doctor contract documented in `context/sce/agent-trace-hook-doctor.md`; the runtime now matches the implemented T06 slice for `sce doctor --fix` parsing/help, stable problem/fix-result reporting, canonical hook-repair reuse, and bounded doctor-owned local-DB directory bootstrap for the missing SCE-owned DB parent path. The local DB service now provides `LocalDb` as a thin `TursoDb` alias in `cli/src/services/local_db/mod.rs`; `LocalDbSpec` resolves the canonical local DB path from the shared default-path catalog and currently declares zero migrations. Shared Turso infrastructure lives in `cli/src/services/db/mod.rs`, where `DbSpec` and generic `TursoDb` support local or remote sync-mode opens, parent-directory creation, connection setup, synchronous query helpers, embedded migration execution, and shared DB lifecycle helpers. Auth DB persistence uses encrypted `AuthDb = EncryptedTursoDb` and token storage persists credentials through the `auth_credentials` table. Agent Trace persistence uses the sole `RepositoryAgentTraceDb = TursoDb` adapter at `/sce/repos//agent-trace.db`, with a one-file repository schema for `repository_metadata`, `diff_traces`, `post_commit_patch_intersections`, `agent_traces`, `messages`, `parts`, indexes/triggers, and no `checkout_id` columns on trace rows. The checkout-scoped `AgentTraceDb = TursoDb` adapter, its `agent_trace_db_path()`/`agent_trace_db_path_for_checkout()` helpers, and the 15-file `cli/migrations/agent-trace/` chain were removed by the `retire-legacy-agent-trace-db` plan; active hook runtime writes nullable event-local diff-trace attribution without a `session_models` API/table dependency. -The hooks command surface now also supports concrete runtime subcommand routing (`pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`, and `conversation-trace`) with deterministic argument/STDIN validation; `session-model` is no longer supported. Current runtime behavior keeps commit-msg attribution enabled by default unless explicitly opted out: the attribution gate enables canonical trailer insertion in `commit-msg` only when the staged-diff AI-overlap preflight confirms AI/editor evidence (no trailer is appended when the preflight finds no overlap or encounters any error); `pre-commit`/`post-rewrite` remain deterministic no-ops, `post-commit` requires validated `--remote-url`, threads that URL into the Agent Trace flow, prints it to stderr, and remains the active bounded recent-diff-trace intersection path, and `diff-trace` is the active intake path for parsed STDIN `{ sessionID, diff, time, model_id?, tool_name, tool_version }` payload persistence with optional `model_id`, required non-empty `tool_name`, required nullable/non-empty `tool_version`, direct-first/event-transcript-second Claude `model_id` plus direct `tool_version` values (no session-model fallback or cache), required `u64` millisecond `time`, same-tool-idempotent stored `session_id` prefixing (`oc_` for OpenCode, `cc_` for Claude, `pi_` for Pi), non-lossy AgentTraceDb `time_ms` conversion, and no parsed-payload artifact fallback. 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 `conversation-trace`) with deterministic argument/STDIN validation; `session-model` is no longer supported. Current runtime behavior keeps commit-msg attribution enabled by default unless explicitly opted out: the attribution gate enables canonical trailer insertion in `commit-msg` only when the staged-diff AI-overlap preflight confirms AI/editor evidence (no trailer is appended when the preflight finds no overlap or encounters any error); `pre-commit`/`post-rewrite` remain deterministic no-ops, `post-commit` requires validated `--remote-url`, threads that URL into the Agent Trace flow, prints it to stderr, remains the active bounded recent-diff-trace intersection path, and after successful Agent Trace persistence optionally launches the detached sync-owned `sync --format json` child when config-file-only `agent_trace.auto_sync` is true; `diff-trace` is the active intake path for parsed STDIN `{ sessionID, diff, time, model_id?, tool_name, tool_version }` payload persistence with optional `model_id`, required non-empty `tool_name`, required nullable/non-empty `tool_version`, direct-first/event-transcript-second Claude `model_id` plus direct `tool_version` values (no session-model fallback or cache), required `u64` millisecond `time`, same-tool-idempotent stored `session_id` prefixing (`oc_` for OpenCode, `cc_` for Claude, `pi_` for Pi), non-lossy AgentTraceDb `time_ms` conversion, and no parsed-payload artifact fallback. 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, computes the bytes to stage by merging the canonical hook template with any existing hook (preserving a foreign hook's content as an exact prefix with the SCE managed block appended, or bringing an SCE-owned block current in place) rather than writing canonical bytes verbatim, enforces deterministic per-hook outcomes (`Installed`/`Updated`/`Skipped`) against that merged content, surfaces a deterministic advisory when an appended block would be unreachable, and uses a unified atomic-swap policy that renames staged content directly over existing hooks without unlinking them first, 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|--pi|--all --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/plans/automatic-agent-trace-sync.md b/context/plans/automatic-agent-trace-sync.md index f4eb36ad..47ebca72 100644 --- a/context/plans/automatic-agent-trace-sync.md +++ b/context/plans/automatic-agent-trace-sync.md @@ -86,12 +86,17 @@ Persist this field in every plan; this is durable plan state, not chat state: - Context impact: domain — documented the sync-owned asynchronous launcher boundary and its fail-open/no-wait behavior in `context/cli/sync-command.md`; root context pass verified with no edits. - Context synchronization: synced -- [ ] T03: `Trigger auto-sync after successful post-commit persistence` (status:todo) +- [x] T03: `Trigger auto-sync after successful post-commit persistence` (status:done) - Task ID: T03 - Scope: In — `cli/src/services/hooks/mod.rs` production post-commit boundary, config-gate lookup, launcher injection seam for post-commit tests, and focused tests for disabled/enabled/order/persistence-failure/spawn-failure cases. Out — `pre-commit`, `diff-trace`, `conversation-trace`, plugin event changes, and synchronization algorithm changes. - Dependencies: T01, T02 - Done when: a successful post-commit persistence result triggers exactly one best-effort launcher only when resolved auto-sync is enabled; persistence failures do not trigger it; disabled config does not trigger it; launcher failures do not change the successful hook result; the hook path never waits on the child. - Verify: targeted Rust hook tests through `nix develop -c ./scripts/run-cli-cargo.sh test --manifest-path cli/Cargo.toml hooks::`; inspect the post-commit boundary and confirm no high-frequency hook call sites changed. + - Completed: 2026-08-19 + - Files changed: `cli/src/services/hooks/mod.rs` + - Result: Integrated the resolved `agent_trace.auto_sync` gate after successful post-commit Agent Trace persistence, invoking the existing sync-owned launcher through an injected fail-open seam; added tests for enabled ordering, disabled behavior, persistence failure, and launcher failure without changing high-frequency hook paths. + - Verify: `nix develop -c ./scripts/run-cli-cargo.sh test --manifest-path cli/Cargo.toml hooks::` — pass (24 tests); post-commit boundary inspection confirmed launch occurs only after persistence and diff/conversation trace call sites were unchanged. + - Context impact: interface — document the automatic post-commit trigger boundary, resolved config gate, and fail-open launcher behavior in durable SCE context; review all five root context files for stale hook/config descriptions. - Context synchronization: pending - [ ] T04: `Document asynchronous post-commit Agent Trace synchronization` (status:todo) diff --git a/context/sce/agent-trace-hooks-command-routing.md b/context/sce/agent-trace-hooks-command-routing.md index 3310c740..60818d7c 100644 --- a/context/sce/agent-trace-hooks-command-routing.md +++ b/context/sce/agent-trace-hooks-command-routing.md @@ -61,6 +61,7 @@ - When validation passes, the payload is serialized and inserted into Agent Trace DB `agent_traces` using `commit_id` from flow-result commit metadata, `commit_time_ms` from flow-result post-commit timestamp metadata, a derived non-null `url` value formatted as `sce.crocoder.dev/trace/`, and the validated runtime `--remote-url` value persisted to nullable `agent_traces.remote_url`. - Post-commit Agent Trace success requires both schema validation and Agent Trace DB `agent_traces` persistence to succeed. - Current command-surface success output is: `post-commit hook processed intersection: commit=, intersection_files=`. +- After Agent Trace validation and `agent_traces` persistence succeed, post-commit resolves the config-file-only `agent_trace.auto_sync` gate. When it is `true`, the hook invokes the sync-owned one-shot launcher exactly once with the repository root; the launcher starts the current `sce` executable as detached `sync --format json` work and is not awaited. Omitted or `false` configuration does not launch, and validation or persistence failure reaches the existing error path before the gate. Launcher/current-executable/spawn failures are fail-open and do not change the successful post-commit result. No `pre-commit`, `diff-trace`, or `conversation-trace` path invokes automatic synchronization. - `post-rewrite` is a deterministic no-op entrypoint. - `diff-trace` reads STDIN JSON and classifies the payload: - **Claude structured payloads** (detected by presence of top-level `hook_event_name`): the STDIN JSON is validated through `derive_claude_structured_patch`. Supported `PostToolUse` `Write` create and `Edit` structured-patch events produce a `DiffTracePayload` with `payload_type="structured"` and the raw event JSON stored as the `diff` column without conversion to unified-diff text. Model attribution is resolved event-locally and direct-first: top-level `model`, `model_id`, or `modelId`, or nested `model.id`, `model.model`, or `model.name`, wins when present. Otherwise, when the event provides both `transcript_path` and `tool_use_id`, Rust scans that Claude JSONL transcript for the assistant-message envelope whose `tool_use.id` matches, skipping malformed unrelated records. Either source is normalized once with the `claude/` prefix. Missing/unreadable transcripts, unmatched tool calls, missing models, or absent lookup fields leave `model_id` nullable without rejecting the hook, and downstream Agent Trace JSON omits contributor `model_id`. No session-level cache or lookup participates. Unsupported Claude events (non-`PostToolUse`, unsupported tools, invalid payloads) produce a deterministic `NoOp` success result. From 7c2d65c962661de782a3803f0cccba8b040bca35 Mon Sep 17 00:00:00 2001 From: David Abram Date: Wed, 19 Aug 2026 15:52:29 +0200 Subject: [PATCH 4/7] runtime: Add fail-open automatic Agent Trace synchronization Reuse `sce sync --format json` as a detached, one-shot post-commit launch after local Agent Trace persistence, with repository-root execution and ignored launcher failures. Record the config, routing, and retry boundaries while preserving manual synchronization as the recovery path. Plan: automatic-agent-trace-sync (T03) Co-authored-by: SCE --- cli/src/services/sync/auto_sync.rs | 5 +- context/architecture.md | 1 + context/cli/agent-trace-auto-sync.md | 56 +++++++++++++++++++ context/cli/config-precedence-contract.md | 1 + context/cli/sync-command.md | 1 + context/context-map.md | 1 + context/overview.md | 2 +- context/patterns.md | 1 + context/plans/automatic-agent-trace-sync.md | 2 +- .../sce/agent-trace-hooks-command-routing.md | 6 ++ 10 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 context/cli/agent-trace-auto-sync.md diff --git a/cli/src/services/sync/auto_sync.rs b/cli/src/services/sync/auto_sync.rs index 0b049ace..2198f351 100644 --- a/cli/src/services/sync/auto_sync.rs +++ b/cli/src/services/sync/auto_sync.rs @@ -49,9 +49,8 @@ where FCurrentExe: FnOnce() -> io::Result, FSpawn: FnOnce(AutoSyncCommand) -> io::Result<()>, { - let executable = match current_exe() { - Ok(executable) => executable, - Err(_) => return false, + let Ok(executable) = current_exe() else { + return false; }; spawn(AutoSyncCommand::new(executable, repository_root)).is_ok() diff --git a/context/architecture.md b/context/architecture.md index cd2f24e7..45e4cb20 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -132,6 +132,7 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `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 repository-level `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 the repository-scoped Agent Trace DB without post-commit file artifacts); after successful validation and persistence, the config-file-only `agent_trace.auto_sync` gate can launch one detached sync-owned `sync --format json` child in the repository root, with launcher failures ignored and no high-frequency hook trigger; `diff-trace` performs STDIN JSON intake, validates required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent/`null` → `None`), required nullable/non-empty `tool_version` plus required `u64` `time` (Unix epoch milliseconds), rejects values that cannot fit signed `time_ms` storage, prefixes the stored `diff_traces.session_id` before insert construction (`oc_` for OpenCode, `cc_` for Claude, `pi_` for Pi, same-tool idempotent), and inserts the parsed payload fields into `RepositoryAgentTraceDb` without creating a parsed-payload `context/tmp` artifact; Claude structured `PostToolUse` diff-trace intake resolves model attribution event-locally: direct top-level or nested metadata wins, otherwise the event's `transcript_path` is scanned for the assistant envelope whose `tool_use.id` matches `tool_use_id`; either source is normalized once with the `claude/` prefix and lookup failures remain nullable. `session-model` is no longer a supported hook route. - Generated Claude settings no longer register `SessionStart` for Agent Trace model attribution, and `sce hooks session-model` is no longer a supported hook command. The `session_models` table/API and session-level fallback lookup were removed in T02 of the `remove-session-models-direct-claude-model-id` plan; `diff-trace` now uses direct-first/event-transcript-second Claude `model_id` resolution and direct `tool_version` values, without restoring session-level state. - `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. +- `context/cli/agent-trace-auto-sync.md` documents the sync-owned, one-shot post-commit launcher boundary: it reuses `sce sync`, has no daemon or local retry machinery, and fails open when child startup cannot be completed. - `cli/src/services/sync/progress.rs` owns the sync-local, consumer-typed progress seam: generic `ProgressReporter` supports event delivery plus explicit successful finalization, closure-based collectors, and a no-op implementation alongside the fixed `indicatif` stderr presentation adapter. `cli/src/services/sync/sync.rs` owns `SyncProgressEvent` and its four-stream payload semantics, while `sync/command.rs` selects the terminal adapter for text and the no-op reporter for JSON. There is no top-level `cli/src/services/progress/` module; sync orchestration depends only on its sync-owned contract, so terminal-library details stay at the sync presentation boundary. - `sce sync [--format text|json]` is implemented: `cli/src/services/sync/sync.rs` resolves repository-scoped Agent Trace storage, authenticates against the control plane with stored WorkOS credentials, uses the config-resolved `control_plane_base_url` with baked default `https://sce.crocoderlab.dev`, calls the ingestion `/state` endpoint once, then starts the `messages`/`parts`/`diff_traces`/`agent_traces` capture-stream state machines concurrently via `AgentTraceExportReader` and a shared per-stream reconciliation engine. Batches and cursor refreshes remain sequential within each stream, while fixed stream order is retained for final and stream-completion reporting; `cli/src/services/sync/render_sync.rs` renders the converged `AgentTraceSyncReport` as concise per-stream text or `camelCase` JSON without a nested subcommand field (see `context/cli/sync-command.md`). Local DB bootstrap and setup-time repository-scoped Agent Trace DB initialization otherwise still flow through lifecycle providers aggregated by setup, while repository-scoped DB health/repair flows through the doctor surface. The former trace database inspection and nested sync surfaces are unavailable. - `cli/src/services/patch.rs` defines the standalone patch domain model (`ParsedPatch`, `PatchFileChange`, `FileChangeKind`, `PatchHunk`, `TouchedLine`, `TouchedLineKind`) for in-memory parsed unified-diff representation, capturing only touched lines (added/removed) plus minimal per-file/per-hunk metadata while excluding non-hunk headers and unchanged context lines. All types are `serde`-serializable/deserializable with `snake_case` JSON field naming. The module also provides `parse_patch`, a public parser function that converts raw unified-diff text (both `Index:` SVN-style and `diff --git` git-style formats) into `ParsedPatch` structs, with `ParseError` for actionable malformed-input diagnostics. Storage-agnostic JSON load helpers (`load_patch_from_json` for string input, `load_patch_from_json_bytes` for byte input) reconstruct `ParsedPatch` from serialized JSON content with `PatchLoadError` for actionable deserialization diagnostics. Its patch-set operations now include deterministic ordered combination plus target-shaped intersection that prefers exact touched-line matches and falls back to historical `kind`+`content` matching when incremental diffs and canonical post-commit diffs have drifted line numbers; `parse_patch`, `combine_patches`, and `intersect_patches` are consumed by the active post-commit hook runtime. diff --git a/context/cli/agent-trace-auto-sync.md b/context/cli/agent-trace-auto-sync.md new file mode 100644 index 00000000..8951a9a6 --- /dev/null +++ b/context/cli/agent-trace-auto-sync.md @@ -0,0 +1,56 @@ +# Automatic Agent Trace synchronization + +## Purpose + +Automatic synchronization is an opt-in convenience layered on the existing +`sce sync` command. It does not replace explicit synchronization or introduce a +second synchronization engine. + +## Configuration + +`agent_trace.auto_sync` is a config-file-only boolean resolved through the normal +global-then-local config merge. It defaults to `false`, and `sce config show` +reports the resolved value and its source. There is no environment variable or +CLI flag for this opt-in. + +## Trigger boundary + +The post-commit hook first completes its existing Agent Trace validation and +repository-scoped database persistence. Only after both succeed does it inspect +`agent_trace.auto_sync`. When enabled, the hook asks the sync-owned launcher to +start the current `sce` executable with exactly: + +```text +sync --format json +``` + +The child runs with the repository root as its working directory and null +stdin, stdout, and stderr. `Command::spawn()` is used without waiting for a +status; the hook returns its normal successful result immediately. A failure to +resolve the current executable or spawn the child is ignored, so launcher +failures cannot turn a successful post-commit operation into a failure. + +Automatic synchronization is not invoked by `pre-commit`, `diff-trace`, or +`conversation-trace`. It is one post-commit launch, not a high-frequency hook, +watcher, polling loop, scheduler, daemon, retry queue, persistent service, or +second synchronization database. + +## Manual synchronization and retryability + +The explicit operator flow remains: + +```text +sce auth login +cd +sce sync +``` + +Automatic execution uses the same command and therefore the same repository +Agent Trace database, control-plane protocol, authentication, and +control-plane cursor authority. A child startup, completion, or network failure +is fail-open to the commit. Rows that remain local are available to a later +manual `sce sync` or a later successful automatic invocation; no local cursor +or background retry machinery is required. + +See [the sync command contract](sync-command.md), [the config precedence +contract](config-precedence-contract.md), and [the hook routing contract](../sce/agent-trace-hooks-command-routing.md). diff --git a/context/cli/config-precedence-contract.md b/context/cli/config-precedence-contract.md index 2cbdf6f9..673cb62e 100644 --- a/context/cli/config-precedence-contract.md +++ b/context/cli/config-precedence-contract.md @@ -157,3 +157,4 @@ When a default-discovered global or repo-local config file exists but fails JSON - `cli/src/services/config/render.rs` - `cli/src/services/config/schema.rs` - `cli/src/services/config/policy.rs` +- `context/cli/agent-trace-auto-sync.md` diff --git a/context/cli/sync-command.md b/context/cli/sync-command.md index d7e7e708..9941e4b3 100644 --- a/context/cli/sync-command.md +++ b/context/cli/sync-command.md @@ -106,3 +106,4 @@ client. The command change does not alter those semantics. - [Agent Trace export readers](../sce/agent-trace-export-readers.md) - [CLI stdout/stderr contract](../sce/cli-stdout-stderr-contract.md) - [Trace-sync progress stream contract](../decisions/2026-08-13-trace-sync-progress-stream-contract.md) +- [Automatic Agent Trace synchronization](agent-trace-auto-sync.md) diff --git a/context/context-map.md b/context/context-map.md index 0fb7cf65..33353c5f 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -17,6 +17,7 @@ Feature/domain context: - `context/cli/patch-service.md` (standalone patch domain model, parser, JSON load helpers, and set operations in `cli/src/services/patch.rs` for in-memory parsed unified-diff representation, capturing only touched lines plus minimal per-file/per-hunk metadata, supporting both `Index:` SVN-style and `diff --git` git-style formats, with `ParseError` for actionable malformed-input diagnostics, `PatchLoadError`/`load_patch_from_json`/`load_patch_from_json_bytes` for storage-agnostic JSON reconstruction, `intersect_patches` for target-shaped overlap with exact-match-first and historical `kind`+`content` fallback semantics plus matched-constructed-line `session_id` and matched-constructed-hunk `model_id` provenance inheritance, and `combine_patches` for ordered patch combination with later-wins conflict resolution plus winning-hunk `model_id` provenance inheritance; repository structured-row reconstruction supplies persisted hunk-model and canonical touched-line-session provenance before these operations; `parse_patch`, `intersect_patches`, and `combine_patches` are consumed by the active post-commit hook runtime) - `context/cli/structured-patch-service.md` (Claude structured editor-hook derivation in `cli/src/services/structured_patch.rs`, including `Write` structured-update hunks, `Write` `tool_input.content` create fallback, `Edit` structured patches, deterministic skip reasons, `ParsedPatch` output semantics, Rust golden fixture coverage, and repository read-time enrichment that assigns persisted row `model_id` to each hunk and canonical row `session_id` to each touched line) - `context/cli/styling-service.md` (CLI text-mode output styling with `owo-colors`, TTY/`NO_COLOR` policy, shared helper API for human-facing surfaces including sync completion markers, and per-column right-to-left RGB gradient banner rendering) +- `context/cli/agent-trace-auto-sync.md` (opt-in post-commit Agent Trace synchronization: the existing `sce sync` command launched once through the current executable after local persistence, detached null-standard-stream behavior, fail-open startup, no daemon/queue/high-frequency trigger, and retryability through manual sync and control-plane cursor authority) - `context/cli/sync-command.md` (the top-level `sce sync` command: repository-scoped Agent Trace storage resolution, WorkOS-authenticated four-stream control-plane synchronization through the sync-owned consumer-typed `services::sync::progress` reporter contract with sync-owned events, its generic/no-op contract and `indicatif` presentation adapter for aligned stderr progress with independent stream completion, explicit successful finalization, JSON stdout silence, and rejection of the removed `sce trace` command group) - `context/cli/agent-trace-sync-command.md` (composed local-to-control-plane `sce sync` architecture: the `hooks/plugins → repository Agent Trace DB → AgentTraceExportReader → sce sync → HTTPS + WorkOS Bearer → control plane` data flow, the `sce auth login` / `cd ` / `sce sync` user flow, the no-local-cursor/no-`agent-trace-sync.db`/no-Turso-Sync/no-`BridgeLock`/no-local-DWH invariants, and `401`/`409`/ambiguous-batch-failure recovery semantics) - `context/cli/config-precedence-contract.md` (implemented `sce config` show/validate command contract, deterministic `flags > env > config file > defaults` resolution order, focused `config/resolver.rs` ownership for config discovery/merge/runtime precedence plus default-discovered invalid-file degradation, focused `config/render.rs` ownership for `show`/`validate` text+JSON output construction, canonical `$schema` acceptance for startup-loaded `sce/config.json` files, shared auth-key env/config/optional baked-default support starting with `workos_client_id`, shared runtime resolution for flat logging observability keys including `log_dir` / `SCE_LOG_DIR` with `/sce/logs` defaulting plus config-file/default-only positive `log_file_retention_limit`, config-file-only `agent_trace.repository_id`/`agent_trace.repository_remote` repository-identity keys with default remote `origin`, opt-in `agent_trace.auto_sync` boolean resolution defaulting false for the post-commit trigger boundary, the catalog-derived `integrations.optional_workflows` optional-workflow selection key, JSON-pointer-prefixed schema-validation errors, canonical Pkl-generated `sce/config.json` schema ownership plus CLI embedding/reuse contract including `policies.attribution_hooks.enabled` default-true/explicit-false opt-out metadata, config-file selection order, `show` provenance output, and trimmed `validate` output contract) diff --git a/context/overview.md b/context/overview.md index ff1c9f6a..04cf8f09 100644 --- a/context/overview.md +++ b/context/overview.md @@ -12,7 +12,7 @@ The generated `/next-task` workflow persists task-level context-synchronization - **Stderr diagnostics:** stable `SCE-ERR-{PARSE,VALIDATION,RUNTIME,DEPENDENCY}` codes with class-default `Try:` remediation (see `context/sce/cli-error-code-taxonomy.md`). - **Stdout/stderr:** command payloads on stdout only; redacted diagnostics and text-mode `sce sync` progress on stderr, while JSON sync remains silent (see `context/sce/cli-stdout-stderr-contract.md`). - **Observability:** config-resolved logging to stderr, optional dated/session-partitioned `log_dir` / `SCE_LOG_DIR` files with retention (see `context/sce/cli-observability-contract.md`). -- **Config precedence:** `flags > env > config file > defaults` (see `context/cli/config-precedence-contract.md`); the config-file-only `agent_trace.auto_sync` opt-in defaults to `false` and is resolved with source metadata for the post-commit trigger boundary. +- **Config precedence:** `flags > env > config file > defaults` (see `context/cli/config-precedence-contract.md`); the config-file-only `agent_trace.auto_sync` opt-in defaults to `false` and is resolved with source metadata for the post-commit trigger boundary. Its asynchronous post-commit behavior is documented in `context/cli/agent-trace-auto-sync.md`. - **Attribution hooks:** enabled by default, gated by staged-diff AI-overlap preflight; `SCE_ATTRIBUTION_HOOKS_DISABLED` opt-out (see `context/sce/agent-trace-commit-msg-coauthor-policy.md`). - **Install channels:** repo-flake Nix, Cargo, npm, and source-built Flatpak (`dev.crocoder.sce`); Homebrew deferred (see `context/sce/cli-first-install-channels-contract.md`). diff --git a/context/patterns.md b/context/patterns.md index 0bbdda28..ace91231 100644 --- a/context/patterns.md +++ b/context/patterns.md @@ -128,6 +128,7 @@ - Keep `log_file_retention_limit` flat and config-file/default only: validate it as an integer with minimum `1`, merge global before local, default it to `10`, expose resolved source metadata without adding an environment variable or CLI flag, and pass the resolved value unchanged to primary and v2 creation-triggered logger cleanup. - For runtime CLI configuration, keep precedence deterministic and explicit (`flags > env > config file > defaults`) and expose inspect/validate command entrypoints with stable text/JSON outputs. Config-file-only Agent Trace runtime switches such as `agent_trace.auto_sync` should default conservatively, resolve global before local, and expose winning source metadata without adding an environment variable or CLI flag unless the contract explicitly requires one. +- For opt-in automatic Agent Trace synchronization, keep the post-commit launcher as a one-shot reuse of `sce sync`: start the current executable after local Agent Trace persistence, use the repository root and null child streams, do not wait, and fail open on launcher errors. Keep manual sync and the control-plane cursor authority as the retry path; do not add daemons, watchers, polling, queues, or high-frequency hook triggers. - For commands that support text/JSON dual output, centralize `--format ` parsing in one shared contract and pass command-specific `--help` guidance into invalid-value errors instead of duplicating parser logic per command. - For setup-style command contracts, keep interactive mode as the zero-flag default and enforce mutually-exclusive explicit target flags for non-interactive automation. - For durable-context bootstrap, keep create-if-missing additive semantics: ensure baseline paths on every successful setup path, offer a dedicated standalone `--bootstrap-context` mode, and never overwrite existing context content. diff --git a/context/plans/automatic-agent-trace-sync.md b/context/plans/automatic-agent-trace-sync.md index 47ebca72..94db6b0e 100644 --- a/context/plans/automatic-agent-trace-sync.md +++ b/context/plans/automatic-agent-trace-sync.md @@ -97,7 +97,7 @@ Persist this field in every plan; this is durable plan state, not chat state: - Result: Integrated the resolved `agent_trace.auto_sync` gate after successful post-commit Agent Trace persistence, invoking the existing sync-owned launcher through an injected fail-open seam; added tests for enabled ordering, disabled behavior, persistence failure, and launcher failure without changing high-frequency hook paths. - Verify: `nix develop -c ./scripts/run-cli-cargo.sh test --manifest-path cli/Cargo.toml hooks::` — pass (24 tests); post-commit boundary inspection confirmed launch occurs only after persistence and diff/conversation trace call sites were unchanged. - Context impact: interface — document the automatic post-commit trigger boundary, resolved config gate, and fail-open launcher behavior in durable SCE context; review all five root context files for stale hook/config descriptions. - - Context synchronization: pending + - Context synchronization: synced - [ ] T04: `Document asynchronous post-commit Agent Trace synchronization` (status:todo) - Task ID: T04 diff --git a/context/sce/agent-trace-hooks-command-routing.md b/context/sce/agent-trace-hooks-command-routing.md index 60818d7c..391488ef 100644 --- a/context/sce/agent-trace-hooks-command-routing.md +++ b/context/sce/agent-trace-hooks-command-routing.md @@ -124,3 +124,9 @@ - No `conversation-trace` retry/backfill path or `context/tmp` artifact persistence - No runtime Claude diff-trace persistence or AgentTraceDb writes from the removed capture route itself, and no direct artifact/DB writes from the Claude or OpenCode TypeScript runtimes - No checkout-scoped active DB writes, legacy checkout DB migration/import/backfill, or daemon/background Agent Trace service + +## Related context + +- [Automatic Agent Trace synchronization](../cli/agent-trace-auto-sync.md) +- [CLI config precedence contract](../cli/config-precedence-contract.md) +- [SCE sync command](../cli/sync-command.md) From cc966620cf118769f4cc3417539042caaf09faf3 Mon Sep 17 00:00:00 2001 From: David Abram Date: Wed, 19 Aug 2026 16:09:12 +0200 Subject: [PATCH 5/7] hooks: Cover validation failure before automatic sync Add a regression test proving post-commit Agent Trace validation failures return before config resolution or auto-sync launch. Record completion and validation evidence for automatic-agent-trace-sync task T04. Co-authored-by: SCE --- cli/src/services/hooks/mod.rs | 40 +++++++++++++++++ context/plans/automatic-agent-trace-sync.md | 48 ++++++++++++++++++--- 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index c780d7b8..21aee355 100644 --- a/cli/src/services/hooks/mod.rs +++ b/cli/src/services/hooks/mod.rs @@ -2940,6 +2940,46 @@ mod tests { ); } + #[test] + fn post_commit_validation_failure_does_not_resolve_or_launch_auto_sync() { + let validation_called = RefCell::new(false); + let config_called = RefCell::new(false); + let launch_called = RefCell::new(false); + + let error = run_post_commit_subcommand_with( + Path::new("/repo"), + None, + "", + |_| Ok(post_commit_flow_result()), + |_, flow_result, vcs_type, remote_url| { + run_post_commit_agent_trace_flow_with( + flow_result, + vcs_type, + remote_url, + |_| { + *validation_called.borrow_mut() = true; + Err(anyhow!("Agent Trace validation failed")) + }, + |_| panic!("Agent Trace persistence must not run after validation failure"), + ) + }, + |_| { + *config_called.borrow_mut() = true; + Ok(true) + }, + |_| { + *launch_called.borrow_mut() = true; + Ok(()) + }, + ) + .expect_err("validation failure should be returned"); + + assert!(*validation_called.borrow()); + assert!(!error.to_string().is_empty()); + assert!(!*config_called.borrow()); + assert!(!*launch_called.borrow()); + } + #[test] fn post_commit_auto_sync_does_not_launch_when_disabled() { let launch_called = RefCell::new(false); diff --git a/context/plans/automatic-agent-trace-sync.md b/context/plans/automatic-agent-trace-sync.md index 94db6b0e..71ca2906 100644 --- a/context/plans/automatic-agent-trace-sync.md +++ b/context/plans/automatic-agent-trace-sync.md @@ -8,15 +8,15 @@ Extend the canonical Pkl schema and Rust config layers with `agent_trace.auto_sy ## Acceptance criteria -- [ ] AC1: A config file containing `{ "agent_trace": { "auto_sync": true } }` validates and resolves as enabled, an invalid `auto_sync` type is rejected, and an omitted value resolves to `false`. +- [x] AC1: A config file containing `{ "agent_trace": { "auto_sync": true } }` validates and resolves as enabled, an invalid `auto_sync` type is rejected, and an omitted value resolves to `false`. - Validate: targeted config schema/resolver tests for valid, invalid-type, and omitted-value cases; `nix run .#pkl-check-generated`. -- [ ] AC2: When `agent_trace.auto_sync` is enabled and post-commit Agent Trace persistence succeeds, the hook launches the current executable with exactly `sync --format json`, uses the repository root as child working directory, discards stdin/stdout/stderr, and returns without waiting for the child. +- [x] AC2: When `agent_trace.auto_sync` is enabled and post-commit Agent Trace persistence succeeds, the hook launches the current executable with exactly `sync --format json`, uses the repository root as child working directory, discards stdin/stdout/stderr, and returns without waiting for the child. - Validate: focused launcher and post-commit boundary tests asserting executable/arguments/current directory/stdio configuration and injected launcher invocation ordering. -- [ ] AC3: Disabled auto-sync causes no launch; failed Agent Trace validation or persistence causes no launch; and a launcher/current-executable/spawn failure leaves the otherwise successful post-commit result successful. +- [x] AC3: Disabled auto-sync causes no launch; failed Agent Trace validation or persistence causes no launch; and a launcher/current-executable/spawn failure leaves the otherwise successful post-commit result successful. - Validate: focused post-commit and launcher failure tests covering each fail-open branch. -- [ ] AC4: Automatic synchronization invokes only the existing `sce sync` command and introduces no daemon, watcher, polling loop, local cursor, synchronization database, persistent service, or high-frequency `conversation-trace`/`diff-trace` trigger. +- [x] AC4: Automatic synchronization invokes only the existing `sce sync` command and introduces no daemon, watcher, polling loop, local cursor, synchronization database, persistent service, or high-frequency `conversation-trace`/`diff-trace` trigger. - Validate: code inspection plus targeted module tests and the existing sync test suite; verify no changes to the existing sync protocol/cursor implementation. -- [ ] AC5: Durable SCE context explains manual `sce sync`, opt-in `agent_trace.auto_sync`, one-shot asynchronous execution, no daemon, fail-open behavior, and local retryability through the control-plane cursor authority. +- [x] AC5: Durable SCE context explains manual `sce sync`, opt-in `agent_trace.auto_sync`, one-shot asynchronous execution, no daemon, fail-open behavior, and local retryability through the control-plane cursor authority. - Validate: manual review of the updated/new context files against the implemented code. ### Full validation @@ -99,14 +99,48 @@ Persist this field in every plan; this is durable plan state, not chat state: - Context impact: interface — document the automatic post-commit trigger boundary, resolved config gate, and fail-open launcher behavior in durable SCE context; review all five root context files for stale hook/config descriptions. - Context synchronization: synced -- [ ] T04: `Document asynchronous post-commit Agent Trace synchronization` (status:todo) +- [x] T04: `Document asynchronous post-commit Agent Trace synchronization` (status:done) - Task ID: T04 - Scope: In — the new auto-sync context document and the listed overview, architecture, glossary, patterns, context-map, sync-command, config-precedence, and hook-routing updates, reflecting the final implemented names and behavior. Out — code changes, generated target trees, generated schema artifacts, and historical decision records. - Dependencies: T03 - Done when: durable context distinguishes explicit/manual `sce sync` from opt-in asynchronous post-commit triggering, states that there is no daemon and failures are fail-open, explains that pending rows remain local for later retry, and accurately names the config and hook boundaries. - Verify: manual code/context review; `nix run .#pkl-check-generated`; `nix flake check`. - - Context synchronization: pending + - Completed: 2026-08-19 + - Files changed: `context/plans/automatic-agent-trace-sync.md` (lifecycle/evidence record; scoped durable context was already current at the Git baseline) + - Result: Verified the scoped durable context against the implemented config resolver, sync-owned launcher, and post-commit hook boundary; all required auto-sync behavior, fail-open semantics, retryability, and non-goals are accurately documented, so no additional context text changes were necessary. + - Verify: manual code/context review — pass; `nix run .#pkl-check-generated` — pass (107 generated files, inventory sha256 `5ebbf7a119a7f79e19f65a7c30ee032681ae749279270735b5fbb87b0e1b2658`); `nix flake check` — pass (all checks passed; incompatible systems omitted). + - Context impact: interface — verified the new `agent_trace.auto_sync` config contract, asynchronous post-commit trigger boundary, fail-open launcher semantics, manual/cursor-authoritative retry path, and no-daemon/high-frequency-trigger non-goals across the listed durable context files. + - Context synchronization: synced ## Open questions None. The request fixes the trigger boundary, command shape, opt-in default, fail-open semantics, prohibited architectures, test expectations, and documentation requirements; the plan records only local implementation choices that follow existing repository patterns. + +## Validation Report + +**Status:** validated +**Plan:** `context/plans/automatic-agent-trace-sync.md` +**Name:** `automatic-agent-trace-sync` +**Tasks:** `4/4 complete` +**Date:** `2026-08-19` + +## Commands run + +- `nix flake check` -> passed — all flake checks passed; incompatible systems omitted. +- `nix run .#pkl-check-generated` -> passed — ephemeral Pkl generation passed for 107 files. +- `nix develop -c ./scripts/run-cli-cargo.sh test --manifest-path cli/Cargo.toml config::` -> passed — 25 targeted config tests passed. +- `nix develop -c ./scripts/run-cli-cargo.sh test --manifest-path cli/Cargo.toml auto_sync` -> passed — 13 launcher/config/post-commit auto-sync tests passed. +- `nix develop -c ./scripts/run-cli-cargo.sh test --manifest-path cli/Cargo.toml hooks::` -> passed — 25 focused hook tests passed. +- `nix develop -c ./scripts/run-cli-cargo.sh test --manifest-path cli/Cargo.toml sync::` -> passed — 57 existing sync tests passed. + +## Acceptance criteria + +- [x] AC1: A config file containing `{ "agent_trace": { "auto_sync": true } }` validates and resolves as enabled, an invalid `auto_sync` type is rejected, and an omitted value resolves to `false` — targeted config tests and generated Pkl validation passed. +- [x] AC2: When enabled, post-commit launches the exact detached command in the repository root with null stdio — launcher and post-commit ordering tests passed. +- [x] AC3: Disabled, validation-failure, persistence-failure, launcher/current-executable/spawn-failure paths are fail-open — focused tests passed, including `post_commit_validation_failure_does_not_resolve_or_launch_auto_sync`. +- [x] AC4: Automatic synchronization reuses only `sce sync` without prohibited daemon, cursor, persistence, or high-frequency trigger behavior — targeted sync tests and code inspection passed. +- [x] AC5: Durable context accurately documents opt-in asynchronous sync, manual retryability, cursor authority, fail-open behavior, and no daemon — manual review passed. + +## Residual risks + +- None identified. From 40bf5851763f69613b8a937e4c99dcabeb4f22fe Mon Sep 17 00:00:00 2001 From: David Abram Date: Wed, 19 Aug 2026 16:15:48 +0200 Subject: [PATCH 6/7] hooks: Reorder Claude hook configuration keys Preserve the configured matchers and commands while making the JSON hook layout consistent. --- .claude/settings.json | 44 +++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 75cda4c6..87ee9495 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,55 +1,55 @@ { "$schema": "https://json.schemastore.org/claude-code-settings.json", "hooks": { - "PreToolUse": [ + "PostToolUse": [ { - "matcher": "Bash", "hooks": [ { - "type": "command", - "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-sce-or-show-install-guidance.sh\" sce policy bash" + "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-sce-or-show-install-guidance.sh\" sce hooks diff-trace", + "type": "command" } - ] - } - ], - "PostToolUse": [ + ], + "matcher": "Write|Edit|MultiEdit|NotebookEdit" + }, { - "matcher": "Write|Edit|MultiEdit|NotebookEdit", "hooks": [ { - "type": "command", - "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-sce-or-show-install-guidance.sh\" sce hooks diff-trace" + "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-sce-or-show-install-guidance.sh\" sce hooks conversation-trace", + "type": "command" } ] - }, + } + ], + "PreToolUse": [ { "hooks": [ { - "type": "command", - "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-sce-or-show-install-guidance.sh\" sce hooks conversation-trace" + "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-sce-or-show-install-guidance.sh\" sce policy bash", + "type": "command" } - ] + ], + "matcher": "Bash" } ], - "UserPromptSubmit": [ + "Stop": [ { "hooks": [ { - "type": "command", - "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-sce-or-show-install-guidance.sh\" sce hooks conversation-trace" + "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-sce-or-show-install-guidance.sh\" sce hooks conversation-trace", + "type": "command" } ] } ], - "Stop": [ + "UserPromptSubmit": [ { "hooks": [ { - "type": "command", - "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-sce-or-show-install-guidance.sh\" sce hooks conversation-trace" + "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-sce-or-show-install-guidance.sh\" sce hooks conversation-trace", + "type": "command" } ] } ] } -} \ No newline at end of file +} From 07861326a64d54e3819a5b8953ab7a20276f617f Mon Sep 17 00:00:00 2001 From: David Abram Date: Wed, 19 Aug 2026 16:26:23 +0200 Subject: [PATCH 7/7] config: Enable automatic Agent Trace synchronization by default Make agent_trace.auto_sync default to true across runtime resolution and the canonical schema, while preserving explicit false as the opt-out. Align the CLI contracts and shared documentation with the default-enabled post-commit sync behavior. Co-authored-by: SCE --- .sce/config.json | 3 +++ cli/src/services/config/resolver.rs | 6 +++--- config/pkl/base/sce-config-schema.pkl | 4 ++-- context/architecture.md | 2 +- context/cli/agent-trace-auto-sync.md | 8 ++++---- context/cli/config-precedence-contract.md | 6 +++--- context/context-map.md | 4 ++-- context/glossary.md | 2 +- context/overview.md | 2 +- context/patterns.md | 4 ++-- context/sce/agent-trace-hooks-command-routing.md | 2 +- 11 files changed, 23 insertions(+), 20 deletions(-) diff --git a/.sce/config.json b/.sce/config.json index a2bdc0f7..d4cdb171 100644 --- a/.sce/config.json +++ b/.sce/config.json @@ -8,6 +8,9 @@ "pi" ] }, + "agent_trace": { + "auto_sync": true + }, "log_dir": "context/tmp", "log_level": "error", "policies": { diff --git a/cli/src/services/config/resolver.rs b/cli/src/services/config/resolver.rs index 3ce67d3b..5702c259 100644 --- a/cli/src/services/config/resolver.rs +++ b/cli/src/services/config/resolver.rs @@ -534,7 +534,7 @@ where source: ValueSource::ConfigFile(value.source), }, None => ResolvedValue { - value: false, + value: true, source: ValueSource::Default, }, }; @@ -825,10 +825,10 @@ mod tests { } #[test] - fn agent_trace_auto_sync_defaults_to_false() { + fn agent_trace_auto_sync_defaults_to_true() { let runtime = resolve_runtime_with_config(None).unwrap(); - assert!(!runtime.agent_trace_auto_sync.value); + assert!(runtime.agent_trace_auto_sync.value); assert_eq!(runtime.agent_trace_auto_sync.source, ValueSource::Default); } diff --git a/config/pkl/base/sce-config-schema.pkl b/config/pkl/base/sce-config-schema.pkl index bcd47f12..da454f53 100644 --- a/config/pkl/base/sce-config-schema.pkl +++ b/config/pkl/base/sce-config-schema.pkl @@ -116,8 +116,8 @@ local sceConfigSchema = new JsonSchema { } ["auto_sync"] = new JsonSchema { type = "boolean" - description = "Launch a detached, best-effort `sce sync` after successful post-commit Agent Trace persistence. Defaults to false." - default = false + description = "Launch a detached, best-effort `sce sync` after successful post-commit Agent Trace persistence. Defaults to true." + default = true } } } diff --git a/context/architecture.md b/context/architecture.md index 45e4cb20..3c644ddb 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -129,7 +129,7 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/doctor/mod.rs` owns the current doctor request/report surface while focused submodules (`doctor/inspect.rs`, `doctor/render.rs`, `doctor/fixes.rs`, `doctor/types.rs`) split report fact collection, rendering, manual fix reporting, and doctor-owned domain types into smaller seams; `cli/src/services/doctor/command.rs` owns the `DoctorCommand` payload used by the static `RuntimeCommand` enum and executes against any context implementing repo-root scoping. Runtime doctor execution resolves a repository root, derives a scoped context, requests the shared static lifecycle provider catalog with hooks included for service-owned `diagnose` and `fix` behavior, adapts lifecycle-owned health/fix records into doctor-owned problem/fix records, and then renders stable text/JSON problem records with category/severity/fixability/remediation fields plus deterministic fix-result reporting in fix mode. Agent Trace database inspection is no longer a doctor-adjacent command surface; doctor owns repository-scoped DB health and checkout identity facts, while `sce sync` owns control-plane synchronization. Report fact collection preserves environment/repository/hook/integration display data, while service-owned lifecycle providers own config validation, local DB and repository-scoped Agent Trace DB readiness/bootstrap, and hook rollout diagnosis/repair. Integration inspection in `doctor/inspect.rs` is scoped twice over: `resolve_doctor_integration_targets` picks which targets to inspect, and `persisted_optional_workflows` (reused from setup) resolves which optional workflows the repository selected, which the OpenCode/Claude/Pi child collectors apply through `iter_embedded_assets_for_setup_target_with_selection`. An unselected optional workflow therefore contributes no expected children at all, so no row and no missing/mismatch problem can be produced for it, while a selected one keeps the unchanged presence and content-hash checks. - `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 repository-level `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 the repository-scoped Agent Trace DB without post-commit file artifacts); after successful validation and persistence, the config-file-only `agent_trace.auto_sync` gate can launch one detached sync-owned `sync --format json` child in the repository root, with launcher failures ignored and no high-frequency hook trigger; `diff-trace` performs STDIN JSON intake, validates required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent/`null` → `None`), required nullable/non-empty `tool_version` plus required `u64` `time` (Unix epoch milliseconds), rejects values that cannot fit signed `time_ms` storage, prefixes the stored `diff_traces.session_id` before insert construction (`oc_` for OpenCode, `cc_` for Claude, `pi_` for Pi, same-tool idempotent), and inserts the parsed payload fields into `RepositoryAgentTraceDb` without creating a parsed-payload `context/tmp` artifact; Claude structured `PostToolUse` diff-trace intake resolves model attribution event-locally: direct top-level or nested metadata wins, otherwise the event's `transcript_path` is scanned for the assistant envelope whose `tool_use.id` matches `tool_use_id`; either source is normalized once with the `claude/` prefix and lookup failures remain nullable. `session-model` is no longer a supported hook route. +- `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 repository-level `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 the repository-scoped Agent Trace DB without post-commit file artifacts); after successful validation and persistence, the default-enabled config-file-only `agent_trace.auto_sync` gate launches one detached sync-owned `sync --format json` child unless explicitly disabled in config, with launcher failures ignored and no high-frequency hook trigger; `diff-trace` performs STDIN JSON intake, validates required non-empty `sessionID`/`diff`/`tool_name`, optional `model_id` (absent/`null` → `None`), required nullable/non-empty `tool_version` plus required `u64` `time` (Unix epoch milliseconds), rejects values that cannot fit signed `time_ms` storage, prefixes the stored `diff_traces.session_id` before insert construction (`oc_` for OpenCode, `cc_` for Claude, `pi_` for Pi, same-tool idempotent), and inserts the parsed payload fields into `RepositoryAgentTraceDb` without creating a parsed-payload `context/tmp` artifact; Claude structured `PostToolUse` diff-trace intake resolves model attribution event-locally: direct top-level or nested metadata wins, otherwise the event's `transcript_path` is scanned for the assistant envelope whose `tool_use.id` matches `tool_use_id`; either source is normalized once with the `claude/` prefix and lookup failures remain nullable. `session-model` is no longer a supported hook route. - Generated Claude settings no longer register `SessionStart` for Agent Trace model attribution, and `sce hooks session-model` is no longer a supported hook command. The `session_models` table/API and session-level fallback lookup were removed in T02 of the `remove-session-models-direct-claude-model-id` plan; `diff-trace` now uses direct-first/event-transcript-second Claude `model_id` resolution and direct `tool_version` values, without restoring session-level state. - `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. - `context/cli/agent-trace-auto-sync.md` documents the sync-owned, one-shot post-commit launcher boundary: it reuses `sce sync`, has no daemon or local retry machinery, and fails open when child startup cannot be completed. diff --git a/context/cli/agent-trace-auto-sync.md b/context/cli/agent-trace-auto-sync.md index 8951a9a6..a6c6a268 100644 --- a/context/cli/agent-trace-auto-sync.md +++ b/context/cli/agent-trace-auto-sync.md @@ -2,16 +2,16 @@ ## Purpose -Automatic synchronization is an opt-in convenience layered on the existing +Automatic synchronization is a default-enabled convenience layered on the existing `sce sync` command. It does not replace explicit synchronization or introduce a second synchronization engine. ## Configuration `agent_trace.auto_sync` is a config-file-only boolean resolved through the normal -global-then-local config merge. It defaults to `false`, and `sce config show` -reports the resolved value and its source. There is no environment variable or -CLI flag for this opt-in. +global-then-local config merge. It defaults to `true`, and `sce config show` +reports the resolved value and its source. Set it explicitly to `false` to opt +out. There is no environment variable or CLI flag for this setting. ## Trigger boundary diff --git a/context/cli/config-precedence-contract.md b/context/cli/config-precedence-contract.md index 673cb62e..44b4080a 100644 --- a/context/cli/config-precedence-contract.md +++ b/context/cli/config-precedence-contract.md @@ -4,7 +4,7 @@ This contract documents the implemented `sce config` command behavior, runtime resolver, renderer, and canonical Pkl-authored `sce/config.json` schema. The schema is emitted to payload-relative `config/schema/sce-config.schema.json` under Cargo `OUT_DIR` or packaging fallbacks and embedded by `cli/src/services/config/schema.rs` as `SCE_CONFIG_SCHEMA_JSON`; no generated schema is committed. -The current implementation resolves flat logging keys and Agent Trace runtime keys with deterministic precedence and source metadata, exposes resolved-value inspection through `sce config show`, and keeps `sce config validate` focused on validation status plus errors/warnings. Threshold, format, directory, and `log_file_retention_limit` values are consumed by runtime logging; the concrete logger uses the retention value for primary and v2 creation-triggered cleanup. The opt-in `agent_trace.auto_sync` value is consumed by the post-commit trigger boundary and defaults to disabled. +The current implementation resolves flat logging keys and Agent Trace runtime keys with deterministic precedence and source metadata, exposes resolved-value inspection through `sce config show`, and keeps `sce config validate` focused on validation status plus errors/warnings. Threshold, format, directory, and `log_file_retention_limit` values are consumed by runtime logging; the concrete logger uses the retention value for primary and v2 creation-triggered cleanup. The default-enabled `agent_trace.auto_sync` value is consumed by the post-commit trigger boundary and can be disabled explicitly. ## Command surface @@ -29,7 +29,7 @@ Agent Trace repository identity keys are also config-file only with per-key `glo - `agent_trace.repository_id` — optional explicit repository identity; resolves as an optional value with no default. - `agent_trace.repository_remote` — Git remote name used to derive repository identity; defaults to `origin` (`DEFAULT_AGENT_TRACE_REPOSITORY_REMOTE` in `cli/src/services/config/resolver.rs`) when no config file sets it. -- `agent_trace.auto_sync` — opt-in boolean for the post-commit Agent Trace synchronization trigger; config-file only, with no flag or environment layer, and defaults to `false`. +- `agent_trace.auto_sync` — boolean for the post-commit Agent Trace synchronization trigger; config-file only, with no flag or environment layer, and defaults to `true` (set `false` to opt out). Resolved observability values that currently have no CLI flag layer follow the same lower-precedence chain without a flag step: @@ -93,7 +93,7 @@ When a default-discovered global or repo-local config file exists but fails JSON - `agent_trace` must be an object when present and currently allows `repository_id`, `repository_remote`, and `auto_sync`. - `agent_trace.repository_id` must be a non-empty string when present. - `agent_trace.repository_remote` must be a non-empty string when present; the generated schema documents default `origin`. -- `agent_trace.auto_sync` must be a boolean when present; omitted values resolve to `false`. +- `agent_trace.auto_sync` must be a boolean when present; omitted values resolve to `true`. - `integrations` must be an object when present and currently allows `target` and `optional_workflows`; either key alone yields a parsed `IntegrationsConfig` with the other defaulting to empty. - `integrations.target` must be an array of unique canonical target IDs when present. diff --git a/context/context-map.md b/context/context-map.md index 33353c5f..0aaedc35 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -17,10 +17,10 @@ Feature/domain context: - `context/cli/patch-service.md` (standalone patch domain model, parser, JSON load helpers, and set operations in `cli/src/services/patch.rs` for in-memory parsed unified-diff representation, capturing only touched lines plus minimal per-file/per-hunk metadata, supporting both `Index:` SVN-style and `diff --git` git-style formats, with `ParseError` for actionable malformed-input diagnostics, `PatchLoadError`/`load_patch_from_json`/`load_patch_from_json_bytes` for storage-agnostic JSON reconstruction, `intersect_patches` for target-shaped overlap with exact-match-first and historical `kind`+`content` fallback semantics plus matched-constructed-line `session_id` and matched-constructed-hunk `model_id` provenance inheritance, and `combine_patches` for ordered patch combination with later-wins conflict resolution plus winning-hunk `model_id` provenance inheritance; repository structured-row reconstruction supplies persisted hunk-model and canonical touched-line-session provenance before these operations; `parse_patch`, `intersect_patches`, and `combine_patches` are consumed by the active post-commit hook runtime) - `context/cli/structured-patch-service.md` (Claude structured editor-hook derivation in `cli/src/services/structured_patch.rs`, including `Write` structured-update hunks, `Write` `tool_input.content` create fallback, `Edit` structured patches, deterministic skip reasons, `ParsedPatch` output semantics, Rust golden fixture coverage, and repository read-time enrichment that assigns persisted row `model_id` to each hunk and canonical row `session_id` to each touched line) - `context/cli/styling-service.md` (CLI text-mode output styling with `owo-colors`, TTY/`NO_COLOR` policy, shared helper API for human-facing surfaces including sync completion markers, and per-column right-to-left RGB gradient banner rendering) -- `context/cli/agent-trace-auto-sync.md` (opt-in post-commit Agent Trace synchronization: the existing `sce sync` command launched once through the current executable after local persistence, detached null-standard-stream behavior, fail-open startup, no daemon/queue/high-frequency trigger, and retryability through manual sync and control-plane cursor authority) +- `context/cli/agent-trace-auto-sync.md` (default-enabled post-commit Agent Trace synchronization with explicit-false opt-out: the existing `sce sync` command launched once through the current executable after local persistence, detached null-standard-stream behavior, fail-open startup, no daemon/queue/high-frequency trigger, and retryability through manual sync and control-plane cursor authority) - `context/cli/sync-command.md` (the top-level `sce sync` command: repository-scoped Agent Trace storage resolution, WorkOS-authenticated four-stream control-plane synchronization through the sync-owned consumer-typed `services::sync::progress` reporter contract with sync-owned events, its generic/no-op contract and `indicatif` presentation adapter for aligned stderr progress with independent stream completion, explicit successful finalization, JSON stdout silence, and rejection of the removed `sce trace` command group) - `context/cli/agent-trace-sync-command.md` (composed local-to-control-plane `sce sync` architecture: the `hooks/plugins → repository Agent Trace DB → AgentTraceExportReader → sce sync → HTTPS + WorkOS Bearer → control plane` data flow, the `sce auth login` / `cd ` / `sce sync` user flow, the no-local-cursor/no-`agent-trace-sync.db`/no-Turso-Sync/no-`BridgeLock`/no-local-DWH invariants, and `401`/`409`/ambiguous-batch-failure recovery semantics) -- `context/cli/config-precedence-contract.md` (implemented `sce config` show/validate command contract, deterministic `flags > env > config file > defaults` resolution order, focused `config/resolver.rs` ownership for config discovery/merge/runtime precedence plus default-discovered invalid-file degradation, focused `config/render.rs` ownership for `show`/`validate` text+JSON output construction, canonical `$schema` acceptance for startup-loaded `sce/config.json` files, shared auth-key env/config/optional baked-default support starting with `workos_client_id`, shared runtime resolution for flat logging observability keys including `log_dir` / `SCE_LOG_DIR` with `/sce/logs` defaulting plus config-file/default-only positive `log_file_retention_limit`, config-file-only `agent_trace.repository_id`/`agent_trace.repository_remote` repository-identity keys with default remote `origin`, opt-in `agent_trace.auto_sync` boolean resolution defaulting false for the post-commit trigger boundary, the catalog-derived `integrations.optional_workflows` optional-workflow selection key, JSON-pointer-prefixed schema-validation errors, canonical Pkl-generated `sce/config.json` schema ownership plus CLI embedding/reuse contract including `policies.attribution_hooks.enabled` default-true/explicit-false opt-out metadata, config-file selection order, `show` provenance output, and trimmed `validate` output contract) +- `context/cli/config-precedence-contract.md` (implemented `sce config` show/validate command contract, deterministic `flags > env > config file > defaults` resolution order, focused `config/resolver.rs` ownership for config discovery/merge/runtime precedence plus default-discovered invalid-file degradation, focused `config/render.rs` ownership for `show`/`validate` text+JSON output construction, canonical `$schema` acceptance for startup-loaded `sce/config.json` files, shared auth-key env/config/optional baked-default support starting with `workos_client_id`, shared runtime resolution for flat logging observability keys including `log_dir` / `SCE_LOG_DIR` with `/sce/logs` defaulting plus config-file/default-only positive `log_file_retention_limit`, config-file-only `agent_trace.repository_id`/`agent_trace.repository_remote` repository-identity keys with default remote `origin`, default-enabled `agent_trace.auto_sync` boolean resolution with explicit-false opt-out for the post-commit trigger boundary, the catalog-derived `integrations.optional_workflows` optional-workflow selection key, JSON-pointer-prefixed schema-validation errors, canonical Pkl-generated `sce/config.json` schema ownership plus CLI embedding/reuse contract including `policies.attribution_hooks.enabled` default-true/explicit-false opt-out metadata, config-file selection order, `show` provenance output, and trimmed `validate` output contract) - `context/cli/capability-traits.md` (current broad CLI capability seam in `cli/src/services/capabilities.rs`, including `FsOps`/`StdFsOps`, `GitOps`/`ProcessGitOps`, git root/hooks resolution behavior, compile-time-typed borrowed AppContext wiring with associated-type narrow capability accessors plus `ContextWithRepoRoot` repo-root-scoped context derivation, generic command execution bounds, and test-only unimplemented stubs; current service internals do not consume fs/git traits until later lifecycle migration tasks) - `context/cli/service-lifecycle.md` (current compile-safe lifecycle seam in `cli/src/services/lifecycle.rs`, including default no-op `ServiceLifecycle` diagnose/fix/setup methods against narrow `HasRepoRoot`, lifecycle-owned health/fix/setup result types with generic setup messages, doctor/setup adapter boundaries, the static `LifecycleProvider` enum catalog/dispatcher, hook/config/local_db/auth_db/agent_trace_db lifecycle providers including setup-time repository-scoped Agent Trace DB initialization plus checkout identity diagnostics, implemented doctor aggregation over diagnose/fix providers, and implemented setup aggregation over `setup` providers in order config → local_db → auth_db → agent_trace_db → hooks when requested) - `context/sce/cli-exit-code-contract.md` (stable class-based `sce` process exit-code contract in `cli/src/app.rs`, so automation can branch on failure category without parsing error text) diff --git a/context/glossary.md b/context/glossary.md index 85d01dea..1b964bdf 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -247,4 +247,4 @@ - `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. - `AgentTraceExportReader`: Read-only incremental export reader in `cli/src/services/agent_trace_export/mod.rs` over one `RepositoryAgentTraceDb`, exposing `read_messages_after`/`read_parts_after`/`read_diff_traces_after`/`read_agent_traces_after`, each `(cursor: i64, limit: usize) -> Result>` over `WHERE id > cursor ORDER BY id ASC LIMIT {limit}`. Holds no local cursor, performs no mutation, makes no network calls, and returns owned camelCase `serde::Serialize` export-row DTOs matching the shipped control-plane ingestion contract. See `context/sce/agent-trace-export-readers.md`. - `context synchronization lifecycle`: Durable task-level state for synchronization after successful `/next-task` execution. The task record is `pending`, `synced`, or `blocked`; blocked records carry a blocker, required action, and retry condition. Missing lifecycle state on a completed task is unresolved debt, not evidence of synchronization. `/validate` does not persist a plan-level synchronization lifecycle. See `context/sce/shared-context-code-workflow.md`. -- `agent_trace.auto_sync`: Config-file-only boolean opt-in resolved by the shared config layers for the post-commit Agent Trace synchronization boundary; omitted values are `false`, and `sce config show` reports its winning source. After validation and repository-DB persistence, enabled post-commit runs launch the existing sync-owned `sync --format json` command once through the current executable with detached null-standard-stream child semantics and repository-root working directory; the hook never waits, has no daemon or high-frequency trigger, and treats launcher failures as fail-open. See [Agent Trace hook routing](sce/agent-trace-hooks-command-routing.md). +- `agent_trace.auto_sync`: Config-file-only boolean resolved by the shared config layers for the post-commit Agent Trace synchronization boundary; omitted values are `true` and explicit `false` opts out, and `sce config show` reports its winning source. After validation and repository-DB persistence, enabled post-commit runs launch the existing sync-owned `sync --format json` command once through the current executable with detached null-standard-stream child semantics and repository-root working directory; the hook never waits, has no daemon or high-frequency trigger, and treats launcher failures as fail-open. See [Agent Trace hook routing](sce/agent-trace-hooks-command-routing.md). diff --git a/context/overview.md b/context/overview.md index 04cf8f09..337e8f05 100644 --- a/context/overview.md +++ b/context/overview.md @@ -12,7 +12,7 @@ The generated `/next-task` workflow persists task-level context-synchronization - **Stderr diagnostics:** stable `SCE-ERR-{PARSE,VALIDATION,RUNTIME,DEPENDENCY}` codes with class-default `Try:` remediation (see `context/sce/cli-error-code-taxonomy.md`). - **Stdout/stderr:** command payloads on stdout only; redacted diagnostics and text-mode `sce sync` progress on stderr, while JSON sync remains silent (see `context/sce/cli-stdout-stderr-contract.md`). - **Observability:** config-resolved logging to stderr, optional dated/session-partitioned `log_dir` / `SCE_LOG_DIR` files with retention (see `context/sce/cli-observability-contract.md`). -- **Config precedence:** `flags > env > config file > defaults` (see `context/cli/config-precedence-contract.md`); the config-file-only `agent_trace.auto_sync` opt-in defaults to `false` and is resolved with source metadata for the post-commit trigger boundary. Its asynchronous post-commit behavior is documented in `context/cli/agent-trace-auto-sync.md`. +- **Config precedence:** `flags > env > config file > defaults` (see `context/cli/config-precedence-contract.md`); the config-file-only `agent_trace.auto_sync` setting defaults to `true` and is resolved with source metadata for the post-commit trigger boundary. Its asynchronous post-commit behavior is documented in `context/cli/agent-trace-auto-sync.md`. - **Attribution hooks:** enabled by default, gated by staged-diff AI-overlap preflight; `SCE_ATTRIBUTION_HOOKS_DISABLED` opt-out (see `context/sce/agent-trace-commit-msg-coauthor-policy.md`). - **Install channels:** repo-flake Nix, Cargo, npm, and source-built Flatpak (`dev.crocoder.sce`); Homebrew deferred (see `context/sce/cli-first-install-channels-contract.md`). diff --git a/context/patterns.md b/context/patterns.md index ace91231..9aa9ba17 100644 --- a/context/patterns.md +++ b/context/patterns.md @@ -127,8 +127,8 @@ - For observability log-directory configuration, resolve `log_dir` through `SCE_LOG_DIR` > config-file `log_dir` > `default_paths::observability_log_dir()` (`/sce/logs`; Linux `${XDG_STATE_HOME:-~/.local/state}/sce/logs`); select log files per emission from the machine-local date and optional logger session context, append rendered records to the selected file, run retention only after successfully creating a selected file, and keep session IDs out of rendered log schemas unless a caller explicitly passes them as normal fields. - Keep `log_file_retention_limit` flat and config-file/default only: validate it as an integer with minimum `1`, merge global before local, default it to `10`, expose resolved source metadata without adding an environment variable or CLI flag, and pass the resolved value unchanged to primary and v2 creation-triggered logger cleanup. -- For runtime CLI configuration, keep precedence deterministic and explicit (`flags > env > config file > defaults`) and expose inspect/validate command entrypoints with stable text/JSON outputs. Config-file-only Agent Trace runtime switches such as `agent_trace.auto_sync` should default conservatively, resolve global before local, and expose winning source metadata without adding an environment variable or CLI flag unless the contract explicitly requires one. -- For opt-in automatic Agent Trace synchronization, keep the post-commit launcher as a one-shot reuse of `sce sync`: start the current executable after local Agent Trace persistence, use the repository root and null child streams, do not wait, and fail open on launcher errors. Keep manual sync and the control-plane cursor authority as the retry path; do not add daemons, watchers, polling, queues, or high-frequency hook triggers. +- For runtime CLI configuration, keep precedence deterministic and explicit (`flags > env > config file > defaults`) and expose inspect/validate command entrypoints with stable text/JSON outputs. Config-file-only Agent Trace runtime switches such as `agent_trace.auto_sync` should document their default and explicit opt-out, resolve global before local, and expose winning source metadata without adding an environment variable or CLI flag unless the contract explicitly requires one. +- For default-enabled automatic Agent Trace synchronization, keep the post-commit launcher as a one-shot reuse of `sce sync`: start the current executable after local Agent Trace persistence, use the repository root and null child streams, do not wait, and fail open on launcher errors. Keep manual sync and the control-plane cursor authority as the retry path; do not add daemons, watchers, polling, queues, or high-frequency hook triggers. - For commands that support text/JSON dual output, centralize `--format ` parsing in one shared contract and pass command-specific `--help` guidance into invalid-value errors instead of duplicating parser logic per command. - For setup-style command contracts, keep interactive mode as the zero-flag default and enforce mutually-exclusive explicit target flags for non-interactive automation. - For durable-context bootstrap, keep create-if-missing additive semantics: ensure baseline paths on every successful setup path, offer a dedicated standalone `--bootstrap-context` mode, and never overwrite existing context content. diff --git a/context/sce/agent-trace-hooks-command-routing.md b/context/sce/agent-trace-hooks-command-routing.md index 391488ef..18fdeeca 100644 --- a/context/sce/agent-trace-hooks-command-routing.md +++ b/context/sce/agent-trace-hooks-command-routing.md @@ -61,7 +61,7 @@ - When validation passes, the payload is serialized and inserted into Agent Trace DB `agent_traces` using `commit_id` from flow-result commit metadata, `commit_time_ms` from flow-result post-commit timestamp metadata, a derived non-null `url` value formatted as `sce.crocoder.dev/trace/`, and the validated runtime `--remote-url` value persisted to nullable `agent_traces.remote_url`. - Post-commit Agent Trace success requires both schema validation and Agent Trace DB `agent_traces` persistence to succeed. - Current command-surface success output is: `post-commit hook processed intersection: commit=, intersection_files=`. -- After Agent Trace validation and `agent_traces` persistence succeed, post-commit resolves the config-file-only `agent_trace.auto_sync` gate. When it is `true`, the hook invokes the sync-owned one-shot launcher exactly once with the repository root; the launcher starts the current `sce` executable as detached `sync --format json` work and is not awaited. Omitted or `false` configuration does not launch, and validation or persistence failure reaches the existing error path before the gate. Launcher/current-executable/spawn failures are fail-open and do not change the successful post-commit result. No `pre-commit`, `diff-trace`, or `conversation-trace` path invokes automatic synchronization. +- After Agent Trace validation and `agent_traces` persistence succeed, post-commit resolves the config-file-only `agent_trace.auto_sync` gate. When it is `true`, the hook invokes the sync-owned one-shot launcher exactly once with the repository root; the launcher starts the current `sce` executable as detached `sync --format json` work and is not awaited. Explicit `false` configuration does not launch; omitted configuration launches, and validation or persistence failure reaches the existing error path before the gate. Launcher/current-executable/spawn failures are fail-open and do not change the successful post-commit result. No `pre-commit`, `diff-trace`, or `conversation-trace` path invokes automatic synchronization. - `post-rewrite` is a deterministic no-op entrypoint. - `diff-trace` reads STDIN JSON and classifies the payload: - **Claude structured payloads** (detected by presence of top-level `hook_event_name`): the STDIN JSON is validated through `derive_claude_structured_patch`. Supported `PostToolUse` `Write` create and `Edit` structured-patch events produce a `DiffTracePayload` with `payload_type="structured"` and the raw event JSON stored as the `diff` column without conversion to unified-diff text. Model attribution is resolved event-locally and direct-first: top-level `model`, `model_id`, or `modelId`, or nested `model.id`, `model.model`, or `model.name`, wins when present. Otherwise, when the event provides both `transcript_path` and `tool_use_id`, Rust scans that Claude JSONL transcript for the assistant-message envelope whose `tool_use.id` matches, skipping malformed unrelated records. Either source is normalized once with the `claude/` prefix. Missing/unreadable transcripts, unmatched tool calls, missing models, or absent lookup fields leave `model_id` nullable without rejecting the hook, and downstream Agent Trace JSON omits contributor `model_id`. No session-level cache or lookup participates. Unsupported Claude events (non-`PostToolUse`, unsupported tools, invalid payloads) produce a deterministic `NoOp` success result.