diff --git a/.opencode/plugins/sce-agent-trace.ts b/.opencode/plugins/sce-agent-trace.ts index e20bbe0e..aca80ea2 100644 --- a/.opencode/plugins/sce-agent-trace.ts +++ b/.opencode/plugins/sce-agent-trace.ts @@ -3,134 +3,148 @@ import type { Hooks, Plugin } from "@opencode-ai/plugin"; type OpenCodeEvent = Parameters>[0]["event"]; -const REQUIRED_EVENTS = new Set(["session.diff"]); +const REQUIRED_EVENTS = new Set(["message.updated"]); const ALL_CAPTURED_EVENTS = REQUIRED_EVENTS; type TraceInput = { - event?: OpenCodeEvent; + event?: OpenCodeEvent; }; type DiffTracePayload = { - sessionID: string; - diff: string; - time: number; + sessionID: string; + diff: string; + time: number; + model_id: string; }; function extractDiffTracePayload( - input: TraceInput, + input: TraceInput, ): DiffTracePayload | undefined { - const event = input.event; - if (event === undefined || event.type !== "session.diff") { - return undefined; - } - - const properties = event.properties; - if (typeof properties !== "object" || properties === null) { - return undefined; - } - - const propertiesObj = properties as Record; - - const sessionID = - typeof propertiesObj.sessionID === "string" && - propertiesObj.sessionID.trim().length > 0 - ? propertiesObj.sessionID - : "unknown"; - - const diffEntries = propertiesObj.diff; - if (!Array.isArray(diffEntries) || diffEntries.length === 0) { - return undefined; - } - - const patches: string[] = []; - for (const entry of diffEntries) { - if (typeof entry !== "object" || entry === null) { - continue; - } - const entryObj = entry as Record; - const patch = - typeof entryObj.patch === "string" - ? entryObj.patch - : typeof entryObj.diff === "string" - ? entryObj.diff - : undefined; - if (patch !== undefined && patch.trim().length > 0) { - patches.push(patch); - } - } - - if (patches.length === 0) { - return undefined; - } - - return { - sessionID, - diff: patches.join("\n"), - time: Date.now(), - }; + const event = input.event; + if (event === undefined || event.type !== "message.updated") { + return undefined; + } + + const properties = event.properties; + if (typeof properties !== "object" || properties === null) { + return undefined; + } + + const propertiesObj = properties; + + // Access properties.info (the Message object) + const info = propertiesObj.info; + if (typeof info !== "object" || info === null) { + return undefined; + } + + const infoObj = info; + + // Only capture user messages (filter out assistant, system, etc.) + if (infoObj.role !== "user") { + return undefined; + } + + const sessionID = + typeof infoObj.sessionID === "string" && infoObj.sessionID.trim().length > 0 + ? infoObj.sessionID + : "unknown"; + + const model = infoObj.model; + + // Access info.summary?.diffs via explicit checks + const summary = infoObj.summary; + const diffEntries = + typeof summary === "object" && summary !== null ? summary.diffs : undefined; + + if (!Array.isArray(diffEntries) || diffEntries.length === 0) { + return undefined; + } + + const patches: string[] = []; + for (const entry of diffEntries) { + if (typeof entry !== "object" || entry === null) { + continue; + } + const entryObj = entry as { patch?: string }; + const patch = entryObj.patch || ""; + + patches.push(patch); + } + + if (patches.length === 0) { + return undefined; + } + + return { + sessionID, + diff: patches.join("\n"), + time: Date.now(), + model_id: `${model.providerID}/${model.modelID}`, + }; } function shouldCaptureEvent(eventType: string): boolean { - return ALL_CAPTURED_EVENTS.has(eventType); + return ALL_CAPTURED_EVENTS.has(eventType); } async function buildTrace(repoRoot: string, input: TraceInput): Promise { - const diffTracePayload = extractDiffTracePayload(input); + const diffTracePayload = extractDiffTracePayload(input); - if (diffTracePayload === undefined) { - return; - } + if (diffTracePayload === undefined) { + return; + } - await runDiffTraceHook(repoRoot, diffTracePayload); + await runDiffTraceHook(repoRoot, diffTracePayload); } async function runDiffTraceHook( - repoRoot: string, - payload: DiffTracePayload, + repoRoot: string, + payload: DiffTracePayload, ): Promise { - await new Promise((resolve, reject) => { - const child = spawn("sce", ["hooks", "diff-trace"], { - cwd: repoRoot, - stdio: ["pipe", "ignore", "inherit"], - }); - - child.on("error", reject); - - child.on("close", (code, signal) => { - if (code === 0) { - resolve(); - return; - } - - const reason = - signal === null ? `exit code ${String(code)}` : `signal ${signal}`; - reject( - new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), - ); - }); - - child.stdin.end(`${JSON.stringify(payload)}\n`); - }); + await new Promise((resolve, reject) => { + const child = spawn("sce", ["hooks", "diff-trace"], { + cwd: repoRoot, + stdio: ["pipe", "ignore", "inherit"], + }); + + child.on("error", reject); + + child.on("close", (code, signal) => { + if (code === 0) { + resolve(); + return; + } + + const reason = + signal === null ? `exit code ${String(code)}` : `signal ${signal}`; + reject( + new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), + ); + }); + + child.stdin.end(`${JSON.stringify(payload)}\n`); + }); } export const SceAgentTracePlugin: Plugin = async ({ directory, worktree }) => { - const repoRoot = worktree ?? directory ?? process.cwd(); - - return { - event: async (input) => { - const eventType = - typeof input.event === "object" && - input.event !== null && - typeof input.event.type === "string" - ? input.event.type - : undefined; - - if (eventType === undefined || !shouldCaptureEvent(eventType)) { - return; - } - - await buildTrace(repoRoot, input); - }, - }; + const repoRoot = worktree ?? directory ?? process.cwd(); + + return { + event: async (input) => { + const eventType = + typeof input.event === "object" && + input.event !== null && + typeof input.event.type === "string" + ? input.event.type + : undefined; + + if (eventType === undefined || !shouldCaptureEvent(eventType)) { + return; + } + + await buildTrace(repoRoot, input); + }, + }; }; diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..8e9e46b6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "editor.formatOnSave": true, + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + } +} diff --git a/biome.json b/biome.json index 7116d08c..21ce3f57 100644 --- a/biome.json +++ b/biome.json @@ -3,9 +3,9 @@ "files": { "includes": [ "npm/**", - "config/lib/bash-policy-plugin/**", + "config/lib/**", "!npm/node_modules", - "!config/lib/bash-policy-plugin/node_modules" + "!config/lib/node_modules" ] }, "formatter": { diff --git a/cli/migrations/agent-trace/005_add_diff_traces_model_id.sql b/cli/migrations/agent-trace/005_add_diff_traces_model_id.sql new file mode 100644 index 00000000..6d3e96a7 --- /dev/null +++ b/cli/migrations/agent-trace/005_add_diff_traces_model_id.sql @@ -0,0 +1 @@ +ALTER TABLE diff_traces ADD COLUMN model_id TEXT; diff --git a/cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql b/cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql new file mode 100644 index 00000000..f271f5d5 --- /dev/null +++ b/cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql @@ -0,0 +1 @@ +ALTER TABLE agent_traces ADD COLUMN agent_trace_id TEXT; diff --git a/cli/src/services/agent_trace.rs b/cli/src/services/agent_trace.rs index eca0a348..ab5fc8c5 100644 --- a/cli/src/services/agent_trace.rs +++ b/cli/src/services/agent_trace.rs @@ -180,6 +180,9 @@ pub struct Contributor { /// Classification of this hunk's origin. #[serde(rename = "type")] pub kind: HunkContributor, + /// Model provenance for this contributor; omitted when unknown. + #[serde(skip_serializing_if = "Option::is_none")] + pub model_id: Option, } /// A single line range in the new file. @@ -322,12 +325,28 @@ fn build_trace_file( .hunks .iter() .map(|post_commit_hunk| { - let contributor = match intersection_file { - Some(ifile) => classify_hunk(post_commit_hunk, &ifile.hunks), - None => HunkContributor::Unknown, + let (contributor_kind, contributor_model_id) = match intersection_file { + Some(ifile) => { + let contributor_kind = classify_hunk(post_commit_hunk, &ifile.hunks); + let matched_intersection_hunk = ifile + .hunks + .iter() + .find(|h| h.old_start == post_commit_hunk.old_start); + let contributor_model_id = match contributor_kind { + HunkContributor::Ai | HunkContributor::Mixed => { + matched_intersection_hunk.and_then(|hunk| hunk.model_id.clone()) + } + HunkContributor::Unknown => None, + }; + (contributor_kind, contributor_model_id) + } + None => (HunkContributor::Unknown, None), }; Conversation { - contributor: Contributor { kind: contributor }, + contributor: Contributor { + kind: contributor_kind, + model_id: contributor_model_id, + }, ranges: vec![line_range_from_hunk(post_commit_file, post_commit_hunk)], } }) diff --git a/cli/src/services/agent_trace_db/mod.rs b/cli/src/services/agent_trace_db/mod.rs index 5ce89365..0c8b3946 100644 --- a/cli/src/services/agent_trace_db/mod.rs +++ b/cli/src/services/agent_trace_db/mod.rs @@ -20,6 +20,10 @@ const ADD_DIFF_TRACES_TIME_MS_ID_INDEX_MIGRATION: &str = include_str!("../../../migrations/agent-trace/003_add_diff_traces_time_ms_id_index.sql"); const CREATE_AGENT_TRACES_MIGRATION: &str = include_str!("../../../migrations/agent-trace/004_create_agent_traces.sql"); +const ADD_DIFF_TRACES_MODEL_ID_MIGRATION: &str = + include_str!("../../../migrations/agent-trace/005_add_diff_traces_model_id.sql"); +const ADD_AGENT_TRACES_AGENT_TRACE_ID_MIGRATION: &str = + include_str!("../../../migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql"); const AGENT_TRACE_MIGRATIONS: &[(&str, &str)] = &[ ("001_create_diff_traces", CREATE_DIFF_TRACES_MIGRATION), @@ -32,14 +36,23 @@ const AGENT_TRACE_MIGRATIONS: &[(&str, &str)] = &[ ADD_DIFF_TRACES_TIME_MS_ID_INDEX_MIGRATION, ), ("004_create_agent_traces", CREATE_AGENT_TRACES_MIGRATION), + ( + "005_add_diff_traces_model_id", + ADD_DIFF_TRACES_MODEL_ID_MIGRATION, + ), + ( + "006_add_agent_traces_agent_trace_id", + ADD_AGENT_TRACES_AGENT_TRACE_ID_MIGRATION, + ), ]; /// Parameterized SQL for inserting a captured diff trace payload. pub const INSERT_DIFF_TRACE_SQL: &str = - "INSERT INTO diff_traces (time_ms, session_id, patch) VALUES (?1, ?2, ?3)"; + "INSERT INTO diff_traces (time_ms, session_id, patch, model_id) VALUES (?1, ?2, ?3, ?4)"; /// Parameterized SQL for retrieving recent captured diff trace patches. -pub const SELECT_RECENT_DIFF_TRACE_PATCHES_SQL: &str = "SELECT id, time_ms, session_id, patch +pub const SELECT_RECENT_DIFF_TRACE_PATCHES_SQL: &str = + "SELECT id, time_ms, session_id, patch, model_id FROM diff_traces WHERE time_ms >= ?1 AND time_ms <= ?2 ORDER BY time_ms ASC, id ASC"; @@ -58,7 +71,7 @@ pub const INSERT_POST_COMMIT_PATCH_INTERSECTION_SQL: &str = /// Parameterized SQL for inserting a built agent trace payload. pub const INSERT_AGENT_TRACE_SQL: &str = - "INSERT INTO agent_traces (commit_id, commit_time_ms, trace_json) VALUES (?1, ?2, ?3)"; + "INSERT INTO agent_traces (commit_id, commit_time_ms, trace_json, agent_trace_id) VALUES (?1, ?2, ?3, ?4)"; /// Agent trace database configuration. pub struct AgentTraceDbSpec; @@ -86,6 +99,7 @@ pub struct DiffTraceInsert<'a> { pub time_ms: i64, pub session_id: &'a str, pub patch: &'a str, + pub model_id: &'a str, } /// Raw diff trace row read from the agent trace database. @@ -95,6 +109,7 @@ pub struct DiffTracePatchRow { pub time_ms: i64, pub session_id: String, pub patch: String, + pub model_id: Option, } /// Parsed recent diff trace patch ready for comparison flows. @@ -150,6 +165,7 @@ pub struct AgentTraceInsert<'a> { pub commit_id: &'a str, pub commit_time_ms: i64, pub trace_json: &'a str, + pub agent_trace_id: &'a str, } impl AgentTraceDb { @@ -171,7 +187,12 @@ impl AgentTraceDb { pub fn insert_agent_trace(&self, input: AgentTraceInsert<'_>) -> Result { self.execute( INSERT_AGENT_TRACE_SQL, - (input.commit_id, input.commit_time_ms, input.trace_json), + ( + input.commit_id, + input.commit_time_ms, + input.trace_json, + input.agent_trace_id, + ), ) } @@ -188,7 +209,7 @@ impl AgentTraceDb { fn insert_diff_trace_with(db: &TursoDb, input: DiffTraceInsert<'_>) -> Result { db.execute( INSERT_DIFF_TRACE_SQL, - (input.time_ms, input.session_id, input.patch), + (input.time_ms, input.session_id, input.patch, input.model_id), ) } @@ -232,6 +253,7 @@ fn diff_trace_patch_row_from_turso(row: &turso::Row) -> Result) -> RecentDif for row in rows { match parse_patch(&row.patch) { - Ok(patch) => patches.push(ParsedDiffTracePatch { - id: row.id, - time_ms: row.time_ms, - session_id: row.session_id, - patch, - }), + Ok(mut patch) => { + for file in &mut patch.files { + for hunk in &mut file.hunks { + hunk.model_id.clone_from(&row.model_id); + } + } + + patches.push(ParsedDiffTracePatch { + id: row.id, + time_ms: row.time_ms, + session_id: row.session_id, + patch, + }); + } Err(error) => skipped.push(SkippedDiffTracePatch { id: row.id, time_ms: row.time_ms, @@ -365,6 +395,7 @@ mod tests { time_ms, session_id, patch, + model_id: "test-provider/test-model", }, ) .expect("diff trace insert should succeed"); @@ -510,6 +541,8 @@ mod tests { "002_create_post_commit_patch_intersections", "003_add_diff_traces_time_ms_id_index", "004_create_agent_traces", + "005_add_diff_traces_model_id", + "006_add_agent_traces_agent_trace_id", ] ); } diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index ff094df2..4c7698dc 100644 --- a/cli/src/services/hooks/mod.rs +++ b/cli/src/services/hooks/mod.rs @@ -47,6 +47,7 @@ struct DiffTracePayload { session_id: String, diff: String, time: u64, + model_id: String, } pub fn run_hooks_subcommand( @@ -147,11 +148,13 @@ fn parse_diff_trace_payload(stdin_payload: &str) -> Result { let session_id = required_non_empty_string_field(payload, "sessionID")?; let diff = required_non_empty_string_field(payload, "diff")?; let time = required_u64_millisecond_field(payload, "time")?; + let model_id = required_non_empty_string_field(payload, "model_id")?; Ok(DiffTracePayload { session_id, diff, time, + model_id, }) } @@ -282,6 +285,7 @@ where time_ms, session_id: &payload.session_id, patch: &payload.diff, + model_id: &payload.model_id, }) } @@ -518,7 +522,7 @@ where let agent_trace_value = serde_json::to_value(&agent_trace) .context("Failed to serialize post-commit Agent Trace payload for validation.")?; validate_agent_trace(&agent_trace_value) - .context("Failed to persist built post-commit Agent Trace payload.")?; + .context("Failed to validate built post-commit Agent Trace payload.")?; let serialized = format!( "{}\n", @@ -530,6 +534,7 @@ where commit_id: &flow_result.post_commit_data.commit_oid, commit_time_ms: flow_result.post_commit_data.commit_time_ms, trace_json: &serialized, + agent_trace_id: &agent_trace.id, }; persist_agent_trace(insert_input)?; diff --git a/cli/src/services/patch.rs b/cli/src/services/patch.rs index f76710c8..6b63f23a 100644 --- a/cli/src/services/patch.rs +++ b/cli/src/services/patch.rs @@ -62,6 +62,9 @@ pub struct PatchHunk { pub new_start: u64, /// Number of lines in the new file context for this hunk (0 for deleted-file hunks). pub new_count: u64, + /// Optional model provenance associated with this hunk. + #[serde(default)] + pub model_id: Option, /// Touched lines within this hunk (added and removed lines only; /// unchanged context lines are excluded). pub lines: Vec, @@ -176,9 +179,11 @@ pub fn load_patch_from_json_bytes(input: &[u8]) -> Result = constructed_file + let available_lines: Vec> = constructed_file .hunks .iter() - .flat_map(|h| h.lines.iter()) + .flat_map(|hunk| { + hunk.lines.iter().map(move |line| AvailableTouchedLine { + line, + model_id: hunk.model_id.as_deref(), + }) + }) .collect(); let mut used_lines = vec![false; available_lines.len()]; @@ -217,6 +227,7 @@ pub fn intersect_patches( // numbers have drifted. let mut result_hunks: Vec = Vec::new(); for post_commit_hunk in &post_commit_file.hunks { + let mut matched_model_id: Option = None; let overlapping_lines: Vec = post_commit_hunk .lines .iter() @@ -227,6 +238,9 @@ pub fn intersect_patches( line, touched_lines_match_exact, ) { + if matched_model_id.is_none() { + matched_model_id = available_lines[index].model_id.map(str::to_string); + } used_lines[index] = true; return true; } @@ -237,6 +251,9 @@ pub fn intersect_patches( line, touched_lines_match_historical, ) { + if matched_model_id.is_none() { + matched_model_id = available_lines[index].model_id.map(str::to_string); + } used_lines[index] = true; return true; } @@ -255,6 +272,7 @@ pub fn intersect_patches( old_count: post_commit_hunk.old_count, new_start: post_commit_hunk.new_start, new_count: post_commit_hunk.new_count, + model_id: matched_model_id, lines: overlapping_lines, }); } @@ -278,8 +296,13 @@ pub fn intersect_patches( } } +struct AvailableTouchedLine<'a> { + line: &'a TouchedLine, + model_id: Option<&'a str>, +} + fn find_available_line_match( - available_lines: &[&TouchedLine], + available_lines: &[AvailableTouchedLine<'_>], used_lines: &[bool], target: &TouchedLine, matcher: fn(&TouchedLine, &TouchedLine) -> bool, @@ -288,7 +311,7 @@ fn find_available_line_match( .iter() .enumerate() .find_map(|(index, candidate)| { - (!used_lines[index] && matcher(candidate, target)).then_some(index) + (!used_lines[index] && matcher(candidate.line, target)).then_some(index) }) } @@ -368,12 +391,14 @@ pub fn combine_patches(patches: &[ParsedPatch]) -> ParsedPatch { type LineKey = (TouchedLineKind, u64, String); /// Hunk metadata key: (`old_start`, `old_count`, `new_start`, `new_count`). type HunkMeta = (u64, u64, u64, u64); + /// Hunk provenance key: hunk metadata + source `model_id`. + type HunkGroupKey = (HunkMeta, Option); #[allow(clippy::type_complexity)] struct FileAcc { old_path: String, kind: FileChangeKind, - lines: HashMap, + lines: HashMap, } let mut file_order: Vec = Vec::new(); @@ -400,9 +425,11 @@ pub fn combine_patches(patches: &[ParsedPatch]) -> ParsedPatch { hunk.new_start, hunk.new_count, ); + let hunk_group_key: HunkGroupKey = (hunk_meta, hunk.model_id.clone()); for line in &hunk.lines { let line_key = (line.kind, line.line_number, line.content.clone()); - acc.lines.insert(line_key, (line.clone(), hunk_meta)); + acc.lines + .insert(line_key, (line.clone(), hunk_group_key.clone())); } } } @@ -414,17 +441,19 @@ pub fn combine_patches(patches: &[ParsedPatch]) -> ParsedPatch { let acc = files.remove(&path).unwrap(); // Group surviving lines by their hunk metadata. - let mut hunk_groups: HashMap> = HashMap::new(); - for (_line_key, (line, hunk_meta)) in acc.lines { - hunk_groups.entry(hunk_meta).or_default().push(line); + let mut hunk_groups: HashMap> = HashMap::new(); + for (_line_key, (line, hunk_group_key)) in acc.lines { + hunk_groups.entry(hunk_group_key).or_default().push(line); } // Sort hunk groups by old_start for deterministic output. let mut sorted_hunks: Vec<_> = hunk_groups.into_iter().collect(); - sorted_hunks.sort_by_key(|(meta, _)| meta.0); + sorted_hunks.sort_by_key(|((meta, model_id), _)| { + (meta.0, meta.1, meta.2, meta.3, model_id.clone()) + }); let mut hunks = Vec::new(); - for (meta, mut lines) in sorted_hunks { + for ((meta, model_id), mut lines) in sorted_hunks { // Sort lines within each hunk: by line_number, then Removed before // Added, then by content for full determinism. lines.sort_by(|a, b| { @@ -448,6 +477,7 @@ pub fn combine_patches(patches: &[ParsedPatch]) -> ParsedPatch { old_count: meta.1, new_start: meta.2, new_count: meta.3, + model_id, lines, }); } @@ -838,6 +868,7 @@ where old_count, new_start, new_count, + model_id: None, lines: touched_lines, }) } diff --git a/config/.opencode/plugins/sce-agent-trace.ts b/config/.opencode/plugins/sce-agent-trace.ts index e20bbe0e..aca80ea2 100644 --- a/config/.opencode/plugins/sce-agent-trace.ts +++ b/config/.opencode/plugins/sce-agent-trace.ts @@ -3,134 +3,148 @@ import type { Hooks, Plugin } from "@opencode-ai/plugin"; type OpenCodeEvent = Parameters>[0]["event"]; -const REQUIRED_EVENTS = new Set(["session.diff"]); +const REQUIRED_EVENTS = new Set(["message.updated"]); const ALL_CAPTURED_EVENTS = REQUIRED_EVENTS; type TraceInput = { - event?: OpenCodeEvent; + event?: OpenCodeEvent; }; type DiffTracePayload = { - sessionID: string; - diff: string; - time: number; + sessionID: string; + diff: string; + time: number; + model_id: string; }; function extractDiffTracePayload( - input: TraceInput, + input: TraceInput, ): DiffTracePayload | undefined { - const event = input.event; - if (event === undefined || event.type !== "session.diff") { - return undefined; - } - - const properties = event.properties; - if (typeof properties !== "object" || properties === null) { - return undefined; - } - - const propertiesObj = properties as Record; - - const sessionID = - typeof propertiesObj.sessionID === "string" && - propertiesObj.sessionID.trim().length > 0 - ? propertiesObj.sessionID - : "unknown"; - - const diffEntries = propertiesObj.diff; - if (!Array.isArray(diffEntries) || diffEntries.length === 0) { - return undefined; - } - - const patches: string[] = []; - for (const entry of diffEntries) { - if (typeof entry !== "object" || entry === null) { - continue; - } - const entryObj = entry as Record; - const patch = - typeof entryObj.patch === "string" - ? entryObj.patch - : typeof entryObj.diff === "string" - ? entryObj.diff - : undefined; - if (patch !== undefined && patch.trim().length > 0) { - patches.push(patch); - } - } - - if (patches.length === 0) { - return undefined; - } - - return { - sessionID, - diff: patches.join("\n"), - time: Date.now(), - }; + const event = input.event; + if (event === undefined || event.type !== "message.updated") { + return undefined; + } + + const properties = event.properties; + if (typeof properties !== "object" || properties === null) { + return undefined; + } + + const propertiesObj = properties; + + // Access properties.info (the Message object) + const info = propertiesObj.info; + if (typeof info !== "object" || info === null) { + return undefined; + } + + const infoObj = info; + + // Only capture user messages (filter out assistant, system, etc.) + if (infoObj.role !== "user") { + return undefined; + } + + const sessionID = + typeof infoObj.sessionID === "string" && infoObj.sessionID.trim().length > 0 + ? infoObj.sessionID + : "unknown"; + + const model = infoObj.model; + + // Access info.summary?.diffs via explicit checks + const summary = infoObj.summary; + const diffEntries = + typeof summary === "object" && summary !== null ? summary.diffs : undefined; + + if (!Array.isArray(diffEntries) || diffEntries.length === 0) { + return undefined; + } + + const patches: string[] = []; + for (const entry of diffEntries) { + if (typeof entry !== "object" || entry === null) { + continue; + } + const entryObj = entry as { patch?: string }; + const patch = entryObj.patch || ""; + + patches.push(patch); + } + + if (patches.length === 0) { + return undefined; + } + + return { + sessionID, + diff: patches.join("\n"), + time: Date.now(), + model_id: `${model.providerID}/${model.modelID}`, + }; } function shouldCaptureEvent(eventType: string): boolean { - return ALL_CAPTURED_EVENTS.has(eventType); + return ALL_CAPTURED_EVENTS.has(eventType); } async function buildTrace(repoRoot: string, input: TraceInput): Promise { - const diffTracePayload = extractDiffTracePayload(input); + const diffTracePayload = extractDiffTracePayload(input); - if (diffTracePayload === undefined) { - return; - } + if (diffTracePayload === undefined) { + return; + } - await runDiffTraceHook(repoRoot, diffTracePayload); + await runDiffTraceHook(repoRoot, diffTracePayload); } async function runDiffTraceHook( - repoRoot: string, - payload: DiffTracePayload, + repoRoot: string, + payload: DiffTracePayload, ): Promise { - await new Promise((resolve, reject) => { - const child = spawn("sce", ["hooks", "diff-trace"], { - cwd: repoRoot, - stdio: ["pipe", "ignore", "inherit"], - }); - - child.on("error", reject); - - child.on("close", (code, signal) => { - if (code === 0) { - resolve(); - return; - } - - const reason = - signal === null ? `exit code ${String(code)}` : `signal ${signal}`; - reject( - new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), - ); - }); - - child.stdin.end(`${JSON.stringify(payload)}\n`); - }); + await new Promise((resolve, reject) => { + const child = spawn("sce", ["hooks", "diff-trace"], { + cwd: repoRoot, + stdio: ["pipe", "ignore", "inherit"], + }); + + child.on("error", reject); + + child.on("close", (code, signal) => { + if (code === 0) { + resolve(); + return; + } + + const reason = + signal === null ? `exit code ${String(code)}` : `signal ${signal}`; + reject( + new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), + ); + }); + + child.stdin.end(`${JSON.stringify(payload)}\n`); + }); } export const SceAgentTracePlugin: Plugin = async ({ directory, worktree }) => { - const repoRoot = worktree ?? directory ?? process.cwd(); - - return { - event: async (input) => { - const eventType = - typeof input.event === "object" && - input.event !== null && - typeof input.event.type === "string" - ? input.event.type - : undefined; - - if (eventType === undefined || !shouldCaptureEvent(eventType)) { - return; - } - - await buildTrace(repoRoot, input); - }, - }; + const repoRoot = worktree ?? directory ?? process.cwd(); + + return { + event: async (input) => { + const eventType = + typeof input.event === "object" && + input.event !== null && + typeof input.event.type === "string" + ? input.event.type + : undefined; + + if (eventType === undefined || !shouldCaptureEvent(eventType)) { + return; + } + + await buildTrace(repoRoot, input); + }, + }; }; diff --git a/config/automated/.opencode/plugins/sce-agent-trace.ts b/config/automated/.opencode/plugins/sce-agent-trace.ts index e20bbe0e..aca80ea2 100644 --- a/config/automated/.opencode/plugins/sce-agent-trace.ts +++ b/config/automated/.opencode/plugins/sce-agent-trace.ts @@ -3,134 +3,148 @@ import type { Hooks, Plugin } from "@opencode-ai/plugin"; type OpenCodeEvent = Parameters>[0]["event"]; -const REQUIRED_EVENTS = new Set(["session.diff"]); +const REQUIRED_EVENTS = new Set(["message.updated"]); const ALL_CAPTURED_EVENTS = REQUIRED_EVENTS; type TraceInput = { - event?: OpenCodeEvent; + event?: OpenCodeEvent; }; type DiffTracePayload = { - sessionID: string; - diff: string; - time: number; + sessionID: string; + diff: string; + time: number; + model_id: string; }; function extractDiffTracePayload( - input: TraceInput, + input: TraceInput, ): DiffTracePayload | undefined { - const event = input.event; - if (event === undefined || event.type !== "session.diff") { - return undefined; - } - - const properties = event.properties; - if (typeof properties !== "object" || properties === null) { - return undefined; - } - - const propertiesObj = properties as Record; - - const sessionID = - typeof propertiesObj.sessionID === "string" && - propertiesObj.sessionID.trim().length > 0 - ? propertiesObj.sessionID - : "unknown"; - - const diffEntries = propertiesObj.diff; - if (!Array.isArray(diffEntries) || diffEntries.length === 0) { - return undefined; - } - - const patches: string[] = []; - for (const entry of diffEntries) { - if (typeof entry !== "object" || entry === null) { - continue; - } - const entryObj = entry as Record; - const patch = - typeof entryObj.patch === "string" - ? entryObj.patch - : typeof entryObj.diff === "string" - ? entryObj.diff - : undefined; - if (patch !== undefined && patch.trim().length > 0) { - patches.push(patch); - } - } - - if (patches.length === 0) { - return undefined; - } - - return { - sessionID, - diff: patches.join("\n"), - time: Date.now(), - }; + const event = input.event; + if (event === undefined || event.type !== "message.updated") { + return undefined; + } + + const properties = event.properties; + if (typeof properties !== "object" || properties === null) { + return undefined; + } + + const propertiesObj = properties; + + // Access properties.info (the Message object) + const info = propertiesObj.info; + if (typeof info !== "object" || info === null) { + return undefined; + } + + const infoObj = info; + + // Only capture user messages (filter out assistant, system, etc.) + if (infoObj.role !== "user") { + return undefined; + } + + const sessionID = + typeof infoObj.sessionID === "string" && infoObj.sessionID.trim().length > 0 + ? infoObj.sessionID + : "unknown"; + + const model = infoObj.model; + + // Access info.summary?.diffs via explicit checks + const summary = infoObj.summary; + const diffEntries = + typeof summary === "object" && summary !== null ? summary.diffs : undefined; + + if (!Array.isArray(diffEntries) || diffEntries.length === 0) { + return undefined; + } + + const patches: string[] = []; + for (const entry of diffEntries) { + if (typeof entry !== "object" || entry === null) { + continue; + } + const entryObj = entry as { patch?: string }; + const patch = entryObj.patch || ""; + + patches.push(patch); + } + + if (patches.length === 0) { + return undefined; + } + + return { + sessionID, + diff: patches.join("\n"), + time: Date.now(), + model_id: `${model.providerID}/${model.modelID}`, + }; } function shouldCaptureEvent(eventType: string): boolean { - return ALL_CAPTURED_EVENTS.has(eventType); + return ALL_CAPTURED_EVENTS.has(eventType); } async function buildTrace(repoRoot: string, input: TraceInput): Promise { - const diffTracePayload = extractDiffTracePayload(input); + const diffTracePayload = extractDiffTracePayload(input); - if (diffTracePayload === undefined) { - return; - } + if (diffTracePayload === undefined) { + return; + } - await runDiffTraceHook(repoRoot, diffTracePayload); + await runDiffTraceHook(repoRoot, diffTracePayload); } async function runDiffTraceHook( - repoRoot: string, - payload: DiffTracePayload, + repoRoot: string, + payload: DiffTracePayload, ): Promise { - await new Promise((resolve, reject) => { - const child = spawn("sce", ["hooks", "diff-trace"], { - cwd: repoRoot, - stdio: ["pipe", "ignore", "inherit"], - }); - - child.on("error", reject); - - child.on("close", (code, signal) => { - if (code === 0) { - resolve(); - return; - } - - const reason = - signal === null ? `exit code ${String(code)}` : `signal ${signal}`; - reject( - new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), - ); - }); - - child.stdin.end(`${JSON.stringify(payload)}\n`); - }); + await new Promise((resolve, reject) => { + const child = spawn("sce", ["hooks", "diff-trace"], { + cwd: repoRoot, + stdio: ["pipe", "ignore", "inherit"], + }); + + child.on("error", reject); + + child.on("close", (code, signal) => { + if (code === 0) { + resolve(); + return; + } + + const reason = + signal === null ? `exit code ${String(code)}` : `signal ${signal}`; + reject( + new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), + ); + }); + + child.stdin.end(`${JSON.stringify(payload)}\n`); + }); } export const SceAgentTracePlugin: Plugin = async ({ directory, worktree }) => { - const repoRoot = worktree ?? directory ?? process.cwd(); - - return { - event: async (input) => { - const eventType = - typeof input.event === "object" && - input.event !== null && - typeof input.event.type === "string" - ? input.event.type - : undefined; - - if (eventType === undefined || !shouldCaptureEvent(eventType)) { - return; - } - - await buildTrace(repoRoot, input); - }, - }; + const repoRoot = worktree ?? directory ?? process.cwd(); + + return { + event: async (input) => { + const eventType = + typeof input.event === "object" && + input.event !== null && + typeof input.event.type === "string" + ? input.event.type + : undefined; + + if (eventType === undefined || !shouldCaptureEvent(eventType)) { + return; + } + + await buildTrace(repoRoot, input); + }, + }; }; diff --git a/config/lib/agent-trace-plugin/bun.lock b/config/lib/agent-trace-plugin/bun.lock deleted file mode 100644 index b490cef5..00000000 --- a/config/lib/agent-trace-plugin/bun.lock +++ /dev/null @@ -1,28 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "dependencies": { - "@opencode-ai/plugin": "1.3.0", - "@types/bun": "1.3.11", - "@types/node": "25.5.0", - }, - }, - }, - "packages": { - "@opencode-ai/plugin": ["@opencode-ai/plugin@1.3.0", "", { "dependencies": { "@opencode-ai/sdk": "1.3.0", "zod": "4.1.8" } }, "sha512-mR1Kdcpr3Iv+KS7cL2DRFB6QAcSoR6/DojmwuxYF/pMCahMtaCLiqZGQjoSNl12+gQ6RsIJJyUh/jX3JVlOx8A=="], - - "@opencode-ai/sdk": ["@opencode-ai/sdk@1.3.0", "", {}, "sha512-5WyYEpcV6Zk9otXOMIrvZRbJm1yxt/c8EXSBn1p6Sw1yagz8HRljkoUTJFxzD0x2+/6vAZItr3OrXDZfE+oA2g=="], - - "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], - - "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], - - "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], - - "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - - "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], - } -} diff --git a/config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts b/config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts index e20bbe0e..aca80ea2 100644 --- a/config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts +++ b/config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts @@ -3,134 +3,148 @@ import type { Hooks, Plugin } from "@opencode-ai/plugin"; type OpenCodeEvent = Parameters>[0]["event"]; -const REQUIRED_EVENTS = new Set(["session.diff"]); +const REQUIRED_EVENTS = new Set(["message.updated"]); const ALL_CAPTURED_EVENTS = REQUIRED_EVENTS; type TraceInput = { - event?: OpenCodeEvent; + event?: OpenCodeEvent; }; type DiffTracePayload = { - sessionID: string; - diff: string; - time: number; + sessionID: string; + diff: string; + time: number; + model_id: string; }; function extractDiffTracePayload( - input: TraceInput, + input: TraceInput, ): DiffTracePayload | undefined { - const event = input.event; - if (event === undefined || event.type !== "session.diff") { - return undefined; - } - - const properties = event.properties; - if (typeof properties !== "object" || properties === null) { - return undefined; - } - - const propertiesObj = properties as Record; - - const sessionID = - typeof propertiesObj.sessionID === "string" && - propertiesObj.sessionID.trim().length > 0 - ? propertiesObj.sessionID - : "unknown"; - - const diffEntries = propertiesObj.diff; - if (!Array.isArray(diffEntries) || diffEntries.length === 0) { - return undefined; - } - - const patches: string[] = []; - for (const entry of diffEntries) { - if (typeof entry !== "object" || entry === null) { - continue; - } - const entryObj = entry as Record; - const patch = - typeof entryObj.patch === "string" - ? entryObj.patch - : typeof entryObj.diff === "string" - ? entryObj.diff - : undefined; - if (patch !== undefined && patch.trim().length > 0) { - patches.push(patch); - } - } - - if (patches.length === 0) { - return undefined; - } - - return { - sessionID, - diff: patches.join("\n"), - time: Date.now(), - }; + const event = input.event; + if (event === undefined || event.type !== "message.updated") { + return undefined; + } + + const properties = event.properties; + if (typeof properties !== "object" || properties === null) { + return undefined; + } + + const propertiesObj = properties; + + // Access properties.info (the Message object) + const info = propertiesObj.info; + if (typeof info !== "object" || info === null) { + return undefined; + } + + const infoObj = info; + + // Only capture user messages (filter out assistant, system, etc.) + if (infoObj.role !== "user") { + return undefined; + } + + const sessionID = + typeof infoObj.sessionID === "string" && infoObj.sessionID.trim().length > 0 + ? infoObj.sessionID + : "unknown"; + + const model = infoObj.model; + + // Access info.summary?.diffs via explicit checks + const summary = infoObj.summary; + const diffEntries = + typeof summary === "object" && summary !== null ? summary.diffs : undefined; + + if (!Array.isArray(diffEntries) || diffEntries.length === 0) { + return undefined; + } + + const patches: string[] = []; + for (const entry of diffEntries) { + if (typeof entry !== "object" || entry === null) { + continue; + } + const entryObj = entry as { patch?: string }; + const patch = entryObj.patch || ""; + + patches.push(patch); + } + + if (patches.length === 0) { + return undefined; + } + + return { + sessionID, + diff: patches.join("\n"), + time: Date.now(), + model_id: `${model.providerID}/${model.modelID}`, + }; } function shouldCaptureEvent(eventType: string): boolean { - return ALL_CAPTURED_EVENTS.has(eventType); + return ALL_CAPTURED_EVENTS.has(eventType); } async function buildTrace(repoRoot: string, input: TraceInput): Promise { - const diffTracePayload = extractDiffTracePayload(input); + const diffTracePayload = extractDiffTracePayload(input); - if (diffTracePayload === undefined) { - return; - } + if (diffTracePayload === undefined) { + return; + } - await runDiffTraceHook(repoRoot, diffTracePayload); + await runDiffTraceHook(repoRoot, diffTracePayload); } async function runDiffTraceHook( - repoRoot: string, - payload: DiffTracePayload, + repoRoot: string, + payload: DiffTracePayload, ): Promise { - await new Promise((resolve, reject) => { - const child = spawn("sce", ["hooks", "diff-trace"], { - cwd: repoRoot, - stdio: ["pipe", "ignore", "inherit"], - }); - - child.on("error", reject); - - child.on("close", (code, signal) => { - if (code === 0) { - resolve(); - return; - } - - const reason = - signal === null ? `exit code ${String(code)}` : `signal ${signal}`; - reject( - new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), - ); - }); - - child.stdin.end(`${JSON.stringify(payload)}\n`); - }); + await new Promise((resolve, reject) => { + const child = spawn("sce", ["hooks", "diff-trace"], { + cwd: repoRoot, + stdio: ["pipe", "ignore", "inherit"], + }); + + child.on("error", reject); + + child.on("close", (code, signal) => { + if (code === 0) { + resolve(); + return; + } + + const reason = + signal === null ? `exit code ${String(code)}` : `signal ${signal}`; + reject( + new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), + ); + }); + + child.stdin.end(`${JSON.stringify(payload)}\n`); + }); } export const SceAgentTracePlugin: Plugin = async ({ directory, worktree }) => { - const repoRoot = worktree ?? directory ?? process.cwd(); - - return { - event: async (input) => { - const eventType = - typeof input.event === "object" && - input.event !== null && - typeof input.event.type === "string" - ? input.event.type - : undefined; - - if (eventType === undefined || !shouldCaptureEvent(eventType)) { - return; - } - - await buildTrace(repoRoot, input); - }, - }; + const repoRoot = worktree ?? directory ?? process.cwd(); + + return { + event: async (input) => { + const eventType = + typeof input.event === "object" && + input.event !== null && + typeof input.event.type === "string" + ? input.event.type + : undefined; + + if (eventType === undefined || !shouldCaptureEvent(eventType)) { + return; + } + + await buildTrace(repoRoot, input); + }, + }; }; diff --git a/config/lib/agent-trace-plugin/package.json b/config/lib/agent-trace-plugin/package.json deleted file mode 100644 index 79a35a98..00000000 --- a/config/lib/agent-trace-plugin/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "dependencies": { - "@opencode-ai/plugin": "1.3.0", - "@types/bun": "1.3.11", - "@types/node": "25.5.0" - } -} diff --git a/config/lib/agent-trace-plugin/tsconfig.json b/config/lib/agent-trace-plugin/tsconfig.json deleted file mode 100644 index 004ba7a3..00000000 --- a/config/lib/agent-trace-plugin/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "lib": ["ES2022"], - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["node", "bun"], - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "allowImportingTsExtensions": true, - "skipLibCheck": true - }, - "include": ["opencode-sce-agent-trace-plugin.ts"] -} diff --git a/config/lib/bash-policy-plugin/bun.lock b/config/lib/bash-policy-plugin/bun.lock deleted file mode 100644 index b490cef5..00000000 --- a/config/lib/bash-policy-plugin/bun.lock +++ /dev/null @@ -1,28 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "dependencies": { - "@opencode-ai/plugin": "1.3.0", - "@types/bun": "1.3.11", - "@types/node": "25.5.0", - }, - }, - }, - "packages": { - "@opencode-ai/plugin": ["@opencode-ai/plugin@1.3.0", "", { "dependencies": { "@opencode-ai/sdk": "1.3.0", "zod": "4.1.8" } }, "sha512-mR1Kdcpr3Iv+KS7cL2DRFB6QAcSoR6/DojmwuxYF/pMCahMtaCLiqZGQjoSNl12+gQ6RsIJJyUh/jX3JVlOx8A=="], - - "@opencode-ai/sdk": ["@opencode-ai/sdk@1.3.0", "", {}, "sha512-5WyYEpcV6Zk9otXOMIrvZRbJm1yxt/c8EXSBn1p6Sw1yagz8HRljkoUTJFxzD0x2+/6vAZItr3OrXDZfE+oA2g=="], - - "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], - - "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], - - "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], - - "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - - "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], - } -} diff --git a/config/lib/bun.lock b/config/lib/bun.lock new file mode 100644 index 00000000..ec405bff --- /dev/null +++ b/config/lib/bun.lock @@ -0,0 +1,82 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "dependencies": { + "@opencode-ai/plugin": "1.15.4", + "@types/bun": "1.3.11", + "@types/node": "25.5.0", + }, + }, + }, + "packages": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], + + "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="], + + "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="], + + "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="], + + "@opencode-ai/plugin": ["@opencode-ai/plugin@1.15.4", "", { "dependencies": { "@opencode-ai/sdk": "1.15.4", "effect": "4.0.0-beta.65", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.2.11", "@opentui/keymap": ">=0.2.11", "@opentui/solid": ">=0.2.11" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-5KAhUnks8GNlqRIax+3cs/ZT2UK74/MNdl4w846ysYdivb38fIm+X9R69ljQtRKyQY7rtga4JUQuARJMSExQqQ=="], + + "@opencode-ai/sdk": ["@opencode-ai/sdk@1.15.4", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-55SBChNouj2XY9C4thO0w7SGJS3jD2DRBxzcrDpc5szgmJJ2t2Wu38uZh+TQMBLHA8YrTPDqgfnc7o5tx2qRPw=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], + + "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "effect": ["effect@4.0.0-beta.65", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-QYKvQPAj3CmtsvWkHQww15wX4KG2gNsszDWEcOO5sZCMknp66u6Si/Opmt3wwWCwsyvRmDAdIg+JIz5qzbbFIw=="], + + "fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="], + + "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], + + "ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], + + "msgpackr": ["msgpackr@1.11.12", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg=="], + + "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="], + + "multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="], + + "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="], + + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "uuid": ["uuid@13.0.2", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + } +} diff --git a/config/lib/bash-policy-plugin/package.json b/config/lib/package.json similarity index 69% rename from config/lib/bash-policy-plugin/package.json rename to config/lib/package.json index 79a35a98..c4685fd6 100644 --- a/config/lib/bash-policy-plugin/package.json +++ b/config/lib/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "@opencode-ai/plugin": "1.3.0", + "@opencode-ai/plugin": "1.15.4", "@types/bun": "1.3.11", "@types/node": "25.5.0" } diff --git a/config/lib/tsconfig.json b/config/lib/tsconfig.json new file mode 100644 index 00000000..59c38829 --- /dev/null +++ b/config/lib/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node", "bun"], + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "allowImportingTsExtensions": true, + "skipLibCheck": true + }, + "include": ["agent-trace-plugin/**/*.ts", "bash-policy-plugin/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/context/architecture.md b/context/architecture.md index cc83e968..bd68f595 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -106,7 +106,7 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/auth_command/mod.rs` defines the implemented auth command surface for `sce auth login|renew|logout|status`, including device-flow login, stored-token renewal (`--force` supported for renew), logout, and status rendering in text/JSON formats; `cli/src/services/auth_command/command.rs` owns the `AuthCommand` struct and its `RuntimeCommand` impl. - `cli/src/services/db/mod.rs` provides the shared generic Turso infrastructure seam: `DbSpec` supplies a service-specific name, path, and ordered embedded migrations, while `TursoDb` owns parent-directory creation, `Builder::new_local(...)` initialization, Turso connection setup, tokio current-thread runtime bridging, blocking `execute`/`query`/`query_map` wrappers, and generic migration execution with per-database `__sce_migrations` metadata. Existing DB files without migration metadata are upgraded by re-applying the current idempotent migration set and recording each migration ID, so setup/lifecycle initialization applies later migrations to already-created databases. The same module owns shared DB lifecycle helpers for path-health problem collection and DB parent-directory bootstrap. - `cli/src/services/local_db/mod.rs` provides the concrete local DB spec and `LocalDb` type alias over the shared generic `TursoDb` adapter. `LocalDbSpec` resolves the deterministic persistent runtime DB target through the shared default-path seam and declares no local migrations; `TursoDb` supplies blocking `execute`/`query`, parent-directory creation, Turso connection setup, tokio current-thread runtime bridging, and generic migration execution. -- `cli/src/services/agent_trace_db/mod.rs` provides the Agent Trace DB spec and `AgentTraceDb` type alias over `TursoDb`. `AgentTraceDbSpec` resolves `/sce/agent-trace.db` through the shared default-path seam and embeds ordered migrations for `diff_traces`, `post_commit_patch_intersections`, the `diff_traces(time_ms, id)` query index, and `agent_traces`; the module adds `DiffTraceInsert<'_>`/`insert_diff_trace()`, `PostCommitPatchIntersectionInsert<'_>`/`insert_post_commit_patch_intersection()`, and `AgentTraceInsert<'_>`/`insert_agent_trace()` for parameterized writes plus `recent_diff_trace_patches(cutoff_time_ms, end_time_ms)` for inclusive chronological `diff_traces` reads that parse valid raw patch text and return skipped malformed-row reports. `cli/src/services/agent_trace_db/lifecycle.rs` registers Agent Trace DB setup/doctor lifecycle behavior; runtime writes come from `sce hooks diff-trace` (`diff_traces`) and `sce hooks post-commit` (`post_commit_patch_intersections` + built `agent_traces`). +- `cli/src/services/agent_trace_db/mod.rs` provides the Agent Trace DB spec and `AgentTraceDb` type alias over `TursoDb`. `AgentTraceDbSpec` resolves `/sce/agent-trace.db` through the shared default-path seam and embeds ordered migrations for `diff_traces`, `post_commit_patch_intersections`, the `diff_traces(time_ms, id)` query index, `agent_traces`, and nullable `diff_traces.model_id`; the module adds `DiffTraceInsert<'_>`/`insert_diff_trace()` (including `model_id` writes), `PostCommitPatchIntersectionInsert<'_>`/`insert_post_commit_patch_intersection()`, and `AgentTraceInsert<'_>`/`insert_agent_trace()` for parameterized writes plus `recent_diff_trace_patches(cutoff_time_ms, end_time_ms)` for inclusive chronological `diff_traces` reads that parse valid raw patch text and return skipped malformed-row reports. `cli/src/services/agent_trace_db/lifecycle.rs` registers Agent Trace DB setup/doctor lifecycle behavior; runtime writes come from `sce hooks diff-trace` (`diff_traces`) and `sce hooks post-commit` (`post_commit_patch_intersections` + built `agent_traces`). - `cli/src/test_support.rs` provides a shared test-only temp-directory helper (`TestTempDir`) used by service tests that need filesystem fixtures. - `cli/src/services/setup/mod.rs` defines the setup command contract (`SetupMode`, `SetupTarget`, `SetupRequest`, CLI flag parser/validator), an `inquire`-backed interactive target prompter (`InquireSetupTargetPrompter`), setup dispatch outcomes (proceed/cancelled), compile-time embedded asset access (`EmbeddedAsset`, target-scoped iterators, required-hook asset iterators/lookups) generated by `cli/build.rs` from the ephemeral crate-local `cli/assets/generated/config/{opencode,claude}/**` mirror plus `cli/assets/hooks/**`, and focused internal support seams for install-flow vs prompt-flow logic; `cli/src/services/setup/command.rs` owns `SetupCommand` and its `RuntimeCommand` impl. Its install engine/orchestrator stages embedded files and uses a unified remove-and-replace policy (removing existing targets before swapping staged content, with deterministic recovery guidance on swap failure and no backup artifact creation), and formats deterministic completion messaging; required-hook install orchestration (`install_required_git_hooks`) follows the same remove-and-replace policy (removing existing hooks before swapping staged content, with deterministic recovery guidance on swap failure). The setup command derives a repo-root-scoped context from the runtime `AppContext` before aggregating `ServiceLifecycle::setup` calls across lifecycle providers (config → local_db → agent_trace_db → hooks when requested), so setup providers receive the runtime logger, telemetry, and capability objects instead of a setup-local replacement context. - `cli/src/services/setup/mod.rs` keeps those responsibilities inside one file for now, but the current ownership split is explicit: the inline `install` module owns repository-path normalization, staging/swap install behavior, required-hook installation, and filesystem safety guards, while the inline `prompt` module owns interactive target selection and prompt styling. @@ -114,15 +114,15 @@ 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 `DoctorCommand` and its `RuntimeCommand` impl. Runtime doctor execution receives `AppContext`, requests the shared lifecycle provider catalog with hooks included for service-owned `diagnose` and `fix` behavior, adapts lifecycle-owned health/fix records into doctor-owned problem/fix records, and then renders stable text/JSON problem records with category/severity/fixability/remediation fields plus deterministic fix-result reporting in fix mode. Report fact collection still preserves current environment/repository/hook/integration display data, while service-owned lifecycle providers now own config validation, local DB and Agent Trace DB readiness/bootstrap, and hook rollout diagnosis/repair. - `cli/src/services/version/mod.rs` defines the version command parser/rendering contract (`parse_version_request`, `render_version`) with deterministic text output and stable JSON runtime-identification fields; `cli/src/services/version/command.rs` owns the `VersionCommand` struct and its `RuntimeCommand` impl. - `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` struct and its `RuntimeCommand` impl. -- `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 disabled-default attribution-hooks config/env control is enabled and `SCE_DISABLED` is false; `cli/src/services/hooks/command.rs` owns `HooksCommand` and its `RuntimeCommand` impl. In the current attribution-only baseline, `pre-commit` and `post-rewrite` are deterministic no-op surfaces; `post-commit` is an active intersection + Agent Trace persistence entrypoint (captures current commit patch, queries recent `diff_traces` from the bounded past-7-days window, combines valid patches via `patch::combine_patches`, intersects with post-commit patch via `patch::intersect_patches`, persists result to `post_commit_patch_intersections`, then persists built Agent Trace payloads to `agent_traces` in AgentTraceDb without post-commit file artifacts); while `diff-trace` performs STDIN JSON intake, validates required non-empty `sessionID`/`diff` plus required `u64` `time` (Unix epoch milliseconds), rejects values that cannot fit AgentTraceDb signed `time_ms` storage, writes one collision-safe `context/tmp/-000000-diff-trace.json` artifact, and inserts the same payload into AgentTraceDb. Success requires both persistence paths to succeed. +- `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 disabled-default attribution-hooks config/env control is enabled and `SCE_DISABLED` is false; `cli/src/services/hooks/command.rs` owns `HooksCommand` and its `RuntimeCommand` impl. In the current attribution-only baseline, `pre-commit` and `post-rewrite` are deterministic no-op surfaces; `post-commit` is an active intersection + Agent Trace persistence entrypoint (captures current commit patch, queries recent `diff_traces` from the bounded past-7-days window, combines valid patches via `patch::combine_patches`, intersects with post-commit patch via `patch::intersect_patches`, persists result to `post_commit_patch_intersections`, then persists built Agent Trace payloads to `agent_traces` in AgentTraceDb without post-commit file artifacts); while `diff-trace` performs STDIN JSON intake, validates required non-empty `sessionID`/`diff`/`model_id` plus required `u64` `time` (Unix epoch milliseconds), rejects values that cannot fit AgentTraceDb signed `time_ms` storage, writes one collision-safe parsed-payload `context/tmp/-000000-diff-trace.json` artifact, and inserts the parsed payload fields into AgentTraceDb. Success requires both persistence paths to succeed. - `cli/src/services/resilience.rs` defines bounded retry/timeout/backoff execution policy (`RetryPolicy`, `run_with_retry`) for transient operation hardening with deterministic failure messaging and retry observability. - No user-invocable `sce sync` command is wired in the current runtime; local DB and Agent Trace DB bootstrap flows through lifecycle providers aggregated by setup, and DB health/repair flows through the doctor surface. - `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. - `cli/src/services/` contains module boundaries for command_registry, lifecycle, auth_command, config, setup, doctor, hooks, version, completion, help, patch, shared database infrastructure, local DB adapters, and Agent Trace DB adapters with explicit trait seams for future implementations. `cli/src/services/command_registry.rs` defines the `RuntimeCommand` trait, `RuntimeCommandHandle` type alias, `CommandRegistry` struct, and `build_default_registry()` function for the command dispatch registry. Service-owned command modules now own the migrated runtime command structs and `RuntimeCommand` impls for help/help-text, version, completion, auth, config, setup, doctor, and hooks. - `cli/README.md` is the crate-local onboarding and usage source of truth for placeholder behavior, safety limitations, and roadmap mapping back to service contracts. -- `flake.nix` applies `rust-overlay` (`oxalica/rust-overlay`) to nixpkgs, pins `rust-bin.stable.1.93.1.default` with `rustfmt` + `clippy`, reads the package/check version from repo-root `.version`, builds `packages.sce` through Crane (`buildDepsOnly` -> `buildPackage`) with a filtered repo-root source that preserves the Cargo tree plus `cli/assets/hooks`, then injects generated OpenCode/Claude config payloads and schema inputs into a temporary `cli/assets/generated/` mirror during derivation unpack so `cli/build.rs` can package the crate without requiring committed generated crate assets, runs `cli-tests`, `cli-clippy`, and `cli-fmt` plus the dedicated `integrations-install-tests`, `integrations-install-clippy`, and `integrations-install-fmt` derivations through Crane-backed paths so both Rust crates have first-class default-flake verification, exposes directory-scoped JS validation derivations for both `npm/` and `config/lib/bash-policy-plugin/`, and also exposes the non-default `apps.install-channel-integration-tests` flake app for install-channel integration coverage outside the default check set. `.github/workflows/publish-crates.yml` follows the same asset-preparation rule but runs Cargo packaging from a temporary clean repository copy so crates.io publish no longer needs `--allow-dirty`. +- `flake.nix` applies `rust-overlay` (`oxalica/rust-overlay`) to nixpkgs, pins `rust-bin.stable.1.93.1.default` with `rustfmt` + `clippy`, reads the package/check version from repo-root `.version`, builds `packages.sce` through Crane (`buildDepsOnly` -> `buildPackage`) with a filtered repo-root source that preserves the Cargo tree plus `cli/assets/hooks`, then injects generated OpenCode/Claude config payloads and schema inputs into a temporary `cli/assets/generated/` mirror during derivation unpack so `cli/build.rs` can package the crate without requiring committed generated crate assets, runs `cli-tests`, `cli-clippy`, and `cli-fmt` plus the dedicated `integrations-install-tests`, `integrations-install-clippy`, and `integrations-install-fmt` derivations through Crane-backed paths so both Rust crates have first-class default-flake verification, exposes directory-scoped JS validation derivations for both `npm/` and the shared `config/lib/` plugin package root, and also exposes the non-default `apps.install-channel-integration-tests` flake app for install-channel integration coverage outside the default check set. The shared config-lib source set is rooted at `config/lib/` and includes the shared `package.json`, `bun.lock`, and `tsconfig.json` plus `agent-trace-plugin/` and `bash-policy-plugin/`; `config-lib-bun-tests` runs the bash-policy runtime test from that shared root, while `config-lib-biome-check` and `config-lib-biome-format` run Biome over the copied shared package source. `.github/workflows/publish-crates.yml` follows the same asset-preparation rule but runs Cargo packaging from a temporary clean repository copy so crates.io publish no longer needs `--allow-dirty`. - `flake.nix` exposes release install/run surfaces as `packages.sce` (`packages.default = packages.sce`) plus `apps.sce` and `apps.default`, all targeting `${packages.sce}/bin/sce`; this keeps repo-local and remote flake run/install flows (`nix run .`, `nix run github:crocoder-dev/shared-context-engineering`, `nix profile install github:crocoder-dev/shared-context-engineering`) aligned to the same packaged CLI output. -- `biome.json` at the repository root is the canonical Biome configuration for the current JS tooling slice and deliberately scopes coverage to `npm/**` plus `config/lib/bash-policy-plugin/**` while excluding package-local `node_modules/**`; `flake.nix` exposes Biome through the default dev shell rather than through package-local installs. +- `biome.json` at the repository root is the canonical Biome configuration for the current JS tooling slice and deliberately scopes coverage to `npm/**` plus the shared `config/lib/**` plugin package root while excluding package-local `node_modules/**`; `flake.nix` exposes Biome through the default dev shell rather than through package-local installs. - `cli/Cargo.toml` now keeps crates.io publication-ready package metadata for the `shared-context-engineering` crate, and `cli/README.md` is the Cargo install surface for crates.io (`cargo install shared-context-engineering --locked`), git (`cargo install --git https://github.com/crocoder-dev/shared-context-engineering shared-context-engineering --locked`), and local checkout (`cargo install --path cli --locked`) guidance. The published crate installs the `sce` binary. Tokio remains intentionally constrained (`default-features = false`) with current-thread runtime usage plus timer-backed bounded resilience wrappers for retry/timeout behavior. - `cli/Cargo.toml` now declares Tokio's `time` feature directly alongside the existing constrained current-thread runtime setup (`rt`, `io-util`, `time`) instead of relying on transitive enablement. diff --git a/context/cli/cli-command-surface.md b/context/cli/cli-command-surface.md index bd2bed76..e752eff6 100644 --- a/context/cli/cli-command-surface.md +++ b/context/cli/cli-command-surface.md @@ -53,7 +53,7 @@ Operator onboarding currently comes from `sce --help`, command-local `--help` ou - `auth` and `hooks` stay parser-valid and directly invocable, but are hidden from those top-level help surfaces Deferred or gated command surfaces currently avoid claiming unimplemented behavior. -`hooks` routes through implemented subcommand parsing/dispatch for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, and `diff-trace`; current behavior remains attribution-only and disabled by default for commit attribution, while `diff-trace` is active STDIN intake with required non-empty `sessionID`/`diff` plus required `u64` `time` (Unix epoch milliseconds) validation, non-lossy AgentTraceDb `time_ms` conversion, collision-safe per-invocation `context/tmp/-000000-diff-trace.json` writes, and AgentTraceDb insertion. +`hooks` routes through implemented subcommand parsing/dispatch for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, and `diff-trace`; current behavior remains attribution-only and disabled by default for commit attribution, while `diff-trace` is active STDIN intake with required non-empty `sessionID`/`diff`/`model_id` plus required `u64` `time` (Unix epoch milliseconds) validation, non-lossy AgentTraceDb `time_ms` conversion, collision-safe per-invocation `context/tmp/-000000-diff-trace.json` parsed-payload writes, and AgentTraceDb insertion including `model_id`. `config` exposes deterministic inspect/validate entrypoints (`sce config show`, `sce config validate`) with explicit precedence (`flags > env > config file > defaults`), a shared auth-runtime resolver for supported keys that declare env/config/optional baked-default inputs starting with `workos_client_id`, first-class `policies.bash` reporting for preset/custom blocked-command rules, and deterministic text/JSON output modes where `show` reports resolved values with provenance while `validate` reports pass/fail plus validation issues and warnings only. `version` exposes deterministic runtime identification output in text mode by default and JSON mode via `--format json`. `completion` exposes deterministic shell completion generation via `sce completion --shell `. @@ -91,7 +91,7 @@ A user-invocable `sync` command is not wired in the current CLI surface; local D - `cli/src/services/doctor/mod.rs` defines the implemented doctor request/report contract (`DoctorRequest`, `DoctorMode`, `run_doctor`) while focused submodules under `cli/src/services/doctor/` handle runtime command dispatch (`command.rs`), diagnosis (`inspect.rs`), rendering (`render.rs`), fix execution (`fixes.rs`), and doctor-owned domain types (`types.rs`). Together they preserve explicit fix-mode parsing, stable text/JSON problem and database-record rendering, deterministic fix-result reporting, and aggregation of `ServiceLifecycle::diagnose`/`ServiceLifecycle::fix` across registered providers (`config`, `local_db`, `agent_trace_db`, `hooks`). The doctor module coordinates state-root/config/database reporting and validation, an empty default repo-scoped database inventory, path-source detection plus required-hook presence/executable/content checks when a repository target is detected, repo-root installed OpenCode integration presence inventory for `plugins`, `agents`, `commands`, and `skills` derived from the embedded OpenCode setup asset catalog, shared-style bracketed human status token rendering (`[PASS]`, `[FAIL]`, `[MISS]`) with simplified `label (path)` text rows, and repair-mode delegation to service-owned fix implementations. - `cli/src/services/version/mod.rs` defines the version parser/output contract (`parse_version_request`, `render_version`) with deterministic text/JSON output modes; `cli/src/services/version/command.rs` owns the version runtime command handler. - `cli/src/services/completion/mod.rs` defines the completion output contract (`render_completion`) using clap_complete to generate deterministic shell scripts for Bash, Zsh, and Fish; `cli/src/services/completion/command.rs` owns the completion runtime command handler. -- `cli/src/services/hooks/mod.rs` defines production local hook runtime parsing/dispatch (`HookSubcommand`, `run_hooks_subcommand`) for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, and `diff-trace`; `cli/src/services/hooks/command.rs` owns the hook runtime command handler. Current runtime behavior is commit-msg-only attribution behind the disabled-default attribution gate; `pre-commit` and `post-rewrite` are deterministic no-ops; `post-commit` is an active intersection + Agent Trace DB persistence path (captures current commit patch, combines/intersects recent `diff_traces`, persists intersection metadata to `post_commit_patch_intersections`, then persists built Agent Trace payload to `agent_traces`); and `diff-trace` performs STDIN JSON intake, required non-empty `sessionID`/`diff` plus required `u64` `time` (Unix epoch milliseconds) validation, non-lossy AgentTraceDb `time_ms` conversion, collision-safe `context/tmp/-000000-diff-trace.json` persistence, and command-failing AgentTraceDb insertion. `cli/src/services/hooks/lifecycle.rs` implements `ServiceLifecycle` for hook health checks, fix, and setup (hook rollout integrity and required-hook installation). +- `cli/src/services/hooks/mod.rs` defines production local hook runtime parsing/dispatch (`HookSubcommand`, `run_hooks_subcommand`) for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, and `diff-trace`; `cli/src/services/hooks/command.rs` owns the hook runtime command handler. Current runtime behavior is commit-msg-only attribution behind the disabled-default attribution gate; `pre-commit` and `post-rewrite` are deterministic no-ops; `post-commit` is an active intersection + Agent Trace DB persistence path (captures current commit patch, combines/intersects recent `diff_traces`, persists intersection metadata to `post_commit_patch_intersections`, then persists built Agent Trace payload to `agent_traces`); and `diff-trace` performs STDIN JSON intake, required non-empty `sessionID`/`diff`/`model_id` plus required `u64` `time` (Unix epoch milliseconds) validation, non-lossy AgentTraceDb `time_ms` conversion, collision-safe parsed-payload `context/tmp/-000000-diff-trace.json` persistence, and command-failing AgentTraceDb insertion. `cli/src/services/hooks/lifecycle.rs` implements `ServiceLifecycle` for hook health checks, fix, and setup (hook rollout integrity and required-hook installation). - `cli/src/services/resilience.rs` defines shared bounded retry/timeout/backoff execution policy (`RetryPolicy`, `run_with_retry`) with deterministic failure messaging and retry observability hooks. - No `cli/src/services/sync.rs` module exists in the current codebase; `sce sync` command wiring is deferred, while local DB initialization and health ownership are split between setup and doctor. - `cli/src/services/default_paths.rs` defines the canonical per-user persisted-location seam for config/state/cache roots plus named default file paths for current persisted artifacts (`global config`, `auth tokens`, `local DB`, `agent trace DB`) used by config discovery, token storage, database adapters, and doctor diagnostics; its internal `roots` seam now owns the platform-aware root-directory resolution so non-test production modules consume shared path accessors instead of resolving owned roots directly. @@ -103,8 +103,8 @@ A user-invocable `sync` command is not wired in the current CLI surface; local D - `cli/src/services/local_db/mod.rs` provides `LocalDb = TursoDb` with `new()`, `execute()`, and `query()` inherited from the shared Turso adapter. - `LocalDb::new()` resolves the canonical per-user DB path through `default_paths::local_db_path()`, creates parent directories, opens the local Turso database, and currently runs zero local migrations. -- `cli/src/services/agent_trace_db/mod.rs` provides `AgentTraceDb = TursoDb` plus `DiffTraceInsert<'_>` and `insert_diff_trace()` for parameterized writes to `diff_traces`. -- `AgentTraceDb::new()` resolves `/sce/agent-trace.db` through `default_paths::agent_trace_db_path()`, creates parent directories through `TursoDb`, opens the Turso database, and runs the embedded `cli/migrations/agent-trace/001_create_diff_traces.sql` migration. +- `cli/src/services/agent_trace_db/mod.rs` provides `AgentTraceDb = TursoDb` plus `DiffTraceInsert<'_>` and `insert_diff_trace()` for parameterized writes to `diff_traces`, including nullable `model_id` storage. +- `AgentTraceDb::new()` resolves `/sce/agent-trace.db` through `default_paths::agent_trace_db_path()`, creates parent directories through `TursoDb`, opens the Turso database, and runs the ordered embedded `cli/migrations/agent-trace/*.sql` migration set. - `cli/src/services/local_db/lifecycle.rs` implements `ServiceLifecycle` for local DB health checks and setup (DB path/health validation and DB bootstrap). - `cli/src/services/agent_trace_db/lifecycle.rs` implements `ServiceLifecycle` for Agent Trace DB health checks and setup (DB path/health validation and DB bootstrap). - `sce setup` aggregates `ServiceLifecycle::setup` calls, which includes `LocalDbLifecycle::setup()` and `AgentTraceDbLifecycle::setup()` for DB initialization as part of local prerequisite bootstrap. diff --git a/context/cli/patch-service.md b/context/cli/patch-service.md index 710e95bd..7714d532 100644 --- a/context/cli/patch-service.md +++ b/context/cli/patch-service.md @@ -50,7 +50,7 @@ Both functions wrap `serde_json::from_str`/`serde_json::from_slice` and map serd - **File matching**: files are matched by post-change path identity — exact `new_path` equality, or absolute-vs-relative path variants whose normalized path segments share the same relative suffix - **Touched-line matching**: matching prefers exact identity (`kind`, `line_number`, and `content`); when no exact match exists, it falls back to historical reconstruction matching by `kind` and `content` only so canonical post-commit patches can still intersect with earlier incremental diffs whose line numbers drifted -- **Result structure**: only files with at least one overlapping touched line appear in the result; hunks with no overlapping lines are excluded; hunk metadata (`old_start`, `old_count`, `new_start`, `new_count`) is preserved from the second patch (`b`) so the result keeps the target patch shape +- **Result structure**: only files with at least one overlapping touched line appear in the result; hunks with no overlapping lines are excluded; hunk range metadata (`old_start`, `old_count`, `new_start`, `new_count`) is preserved from the second patch (`b`) so the result keeps the target patch shape, while hunk `model_id` provenance is inherited from the matched hunk in the first patch (`a`) when available (and remains `None` when matched constructed provenance is absent) - **Determinism**: the same inputs always produce the same output - **Equivalent-hunk behavior**: semantically identical hunks still intersect when they differ only in surrounding context windows, hunk header ranges, or absolute-vs-relative `Index:` path spelling, as long as their touched-line identities match exactly - **Consumed by**: the post-commit hook runtime combines recent DB diff-trace patches and then intersects with the current commit patch (see `agent-trace-hooks-command-routing.md`). Previously listed as "not yet wired" before T04. @@ -61,7 +61,7 @@ Both functions wrap `serde_json::from_str`/`serde_json::from_slice` and map serd - **File matching**: files are grouped by `new_path`; file metadata (`old_path`, `kind`) is taken from the last patch that contributed to each file - **Touched-line identity and deduplication**: touched lines are deduplicated by identity (`kind`, `line_number`, `content`); when multiple patches describe the same file and logical touched-line slot, the later input's entry is retained -- **Hunk reconstruction**: surviving lines are grouped by their hunk metadata from the last contributing patch; hunks are ordered by `old_start`; lines within each hunk are ordered by `line_number` with `Removed` before `Added` at the same position, then by `content` for full determinism +- **Hunk reconstruction and provenance**: surviving lines are grouped by their hunk metadata from the last contributing patch; each reconstructed hunk preserves that winning hunk's `model_id` provenance; hunks are ordered by `old_start`; lines within each hunk are ordered by `line_number` with `Removed` before `Added` at the same position, then by `content` for full determinism - **File ordering**: files appear in the result in the order they are first encountered across the input patches - **Determinism**: the same inputs in the same order always produce the same output - **Consumed by**: the post-commit hook runtime combines recent DB diff-trace patches before intersecting (see `agent-trace-hooks-command-routing.md`). Previously listed as "not yet wired" before T04. diff --git a/context/context-map.md b/context/context-map.md index 6dfc35f5..c5a9cb42 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -11,7 +11,7 @@ Feature/domain context: - `context/cli/cli-command-surface.md` (CLI command surface including top-level help with ASCII art banner and gradient rendering, setup install flow, WorkOS device authorization flow + token storage behavior, attribution-only hook routing with DB-backed `diff-trace` dual persistence, setup-owned local DB + Agent Trace DB bootstrap plus doctor DB health coverage, nested flake release package/app installability, and Cargo local install + crates.io readiness policy; `sce sync` command wiring is deferred to `0.4.0`; migrated runtime command structs for help/version/completion/auth/config/setup/doctor/hooks are owned by their respective `services/{name}/command.rs` files, while clap-to-runtime conversion lives in `services/parse/command_runtime.rs`) - `context/cli/default-path-catalog.md` (canonical production CLI path-ownership contract centered on `cli/src/services/default_paths.rs`, including persisted, repo-relative, embedded-asset, install/runtime, hook, and context-path families plus the regression guard that keeps production path ownership centralized) -- `context/cli/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, and `combine_patches` for ordered patch combination with later-wins conflict resolution; `parse_patch`, `intersect_patches`, and `combine_patches` are consumed by the active post-commit hook runtime) +- `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-hunk `model_id` provenance inheritance, and `combine_patches` for ordered patch combination with later-wins conflict resolution plus winning-hunk `model_id` provenance inheritance; `parse_patch`, `intersect_patches`, and `combine_patches` are consumed by the active post-commit hook runtime) - `context/cli/styling-service.md` (CLI text-mode output styling with `owo-colors` and `comfy-table`, TTY/`NO_COLOR` policy, shared helper API for human-facing surfaces, and per-column right-to-left RGB gradient banner rendering) - `context/cli/config-precedence-contract.md` (implemented `sce config` show/validate command contract, deterministic `flags > env > config file > defaults` resolution order, canonical `$schema` acceptance for startup-loaded `sce/config.json` files, shared auth-key env/config/optional baked-default support starting with `workos_client_id`, shared runtime resolution for flat logging observability keys, canonical Pkl-generated `sce/config.json` schema ownership plus CLI embedding/reuse contract, config-file selection order, `show` provenance output, trimmed `validate` output contract, and opt-in compiled-binary config-precedence E2E coverage contract) - `context/cli/capability-traits.md` (current broad CLI dependency-injection capability seam in `cli/src/services/capabilities.rs`, including `FsOps`/`StdFsOps`, `GitOps`/`ProcessGitOps`, git root/hooks resolution behavior, AppContext wiring with capability accessors plus repo-root-scoped context derivation, and test-only unimplemented stubs; current service internals do not consume these traits until later lifecycle migration tasks) @@ -42,16 +42,16 @@ Feature/domain context: - `context/sce/agent-trace-rewrite-trace-transformation.md` (current post-rewrite no-op baseline plus historical rewrite-transformation reference) - `context/sce/local-db.md` (implemented `cli/src/services/local_db/mod.rs` local database spec with `LocalDb = TursoDb`, canonical local DB path resolution, zero local migrations, and inherited blocking `execute`/`query` methods using the shared Turso adapter) - `context/sce/shared-turso-db.md` (current shared `cli/src/services/db/mod.rs` Turso database infrastructure seam, including `DbSpec`, generic `TursoDb`, sync `execute`/`query`/`query_map` wrappers, per-database `__sce_migrations` tracking, generic embedded migration execution, and current concrete wrappers for `LocalDb` plus `AgentTraceDb`) -- `context/sce/agent-trace-db.md` (implemented `cli/src/services/agent_trace_db/mod.rs` Agent Trace database wrapper with canonical `/sce/agent-trace.db` path, ordered `diff_traces`, `post_commit_patch_intersections`, `diff_traces(time_ms, id)` index, and `agent_traces` migrations applied through shared migration metadata, typed parameterized insert helpers for diff traces, post-commit intersection rows, and built `agent_traces` rows, inclusive bounded chronological recent `diff_traces` query/parse support with malformed-row skip accounting, registered setup/doctor lifecycle provider, and active hook writers for `diff_traces` intake plus post-commit intersection/agent-trace persistence) +- `context/sce/agent-trace-db.md` (implemented `cli/src/services/agent_trace_db/mod.rs` Agent Trace database wrapper with canonical `/sce/agent-trace.db` path, ordered `diff_traces`, `post_commit_patch_intersections`, `diff_traces(time_ms, id)` index, `agent_traces`, nullable `diff_traces.model_id`, and nullable `agent_traces.agent_trace_id` migrations applied through shared migration metadata, typed parameterized insert helpers for diff traces including `model_id`, post-commit intersection rows, and built `agent_traces` rows with `agent_trace_id`, inclusive bounded chronological recent `diff_traces` query/parse support with malformed-row skip accounting, registered setup/doctor lifecycle provider, and active hook writers for `diff_traces` intake plus post-commit intersection/agent-trace persistence) - `context/sce/agent-trace-core-schema-migrations.md` (historical reference for removed local DB schema bootstrap behavior; T03 now implements the actual local DB with migrations) - `context/sce/agent-trace-retry-queue-observability.md` (inactive local-hook retry path plus historical retry/metrics reference) - `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md` (T01 Local Hooks MVP production contract freeze and deterministic gap matrix for `agent-trace-local-hooks-production-mvp`) -- `context/sce/agent-trace-minimal-generator.md` (implemented library-only minimal agent-trace generator seam at `cli/src/services/agent_trace.rs`, producing a JSON payload with top-level `version`, UUIDv7 `id` derived from commit-time metadata, caller-provided commit-time `timestamp`, top-level `vcs` metadata (`type = git`, `revision` from metadata input), and per-file trace data from patch inputs via `intersect_patches(constructed_patch, post_commit_patch)` then `post_commit_patch`-anchored hunk classification into `ai`/`mixed`/`unknown` contributor categories, serialized per conversation as nested `contributor.type` plus one derived `ranges[{start_line,end_line}]` entry per post-commit hunk) -- `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime behavior: disabled-default commit-msg attribution, no-op `pre-commit`/`post-rewrite` entrypoints, active `post-commit` intersection entrypoint capturing current commit patch, querying recent `diff_traces` from past 7 days, combining valid patches via `patch::combine_patches`, intersecting via `patch::intersect_patches`, persisting results to `post_commit_patch_intersections`, and persisting built post-commit Agent Trace payloads to AgentTraceDb `agent_traces` (DB-only), plus `diff-trace` STDIN intake with required-field validation and dual persistence to AgentTraceDb and collision-safe `context/tmp/-000000-diff-trace.json` artifacts) +- `context/sce/agent-trace-minimal-generator.md` (implemented minimal Agent Trace builder seam at `cli/src/services/agent_trace.rs`, used by the active post-commit hook flow to produce strict `0.1.0` JSON payloads with UUIDv7 `id` derived from commit-time metadata, caller-provided commit-time `timestamp`, top-level `vcs` metadata (`type = git`, `revision` from metadata input), and per-file trace data from patch inputs via `intersect_patches(constructed_patch, post_commit_patch)` then `post_commit_patch`-anchored hunk classification into `ai`/`mixed`/`unknown` contributor categories, serialized per conversation as nested `contributor.type` plus optional `contributor.model_id` omitted when provenance is missing and one derived `ranges[{start_line,end_line}]` entry per post-commit hunk) +- `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime behavior: disabled-default commit-msg attribution, no-op `pre-commit`/`post-rewrite` entrypoints, active `post-commit` intersection entrypoint capturing current commit patch, querying recent `diff_traces` from past 7 days, combining valid patches via `patch::combine_patches`, intersecting via `patch::intersect_patches`, persisting results to `post_commit_patch_intersections`, building and schema-validating post-commit Agent Trace payloads, and persisting validated payloads to AgentTraceDb `agent_traces` (DB-only), plus `diff-trace` STDIN intake with required non-empty `sessionID`/`diff`/`model_id` and required `u64` `time` validation, dual persistence to AgentTraceDb, and collision-safe `context/tmp/-000000-diff-trace.json` artifacts) - `context/sce/automated-profile-contract.md` (deterministic gate policy for automated OpenCode profile, including 10 gate categories, permission mappings, automated `/commit` single-commit execution behavior, and automated profile constraints) - `context/sce/bash-tool-policy-enforcement-contract.md` (approved bash-tool blocking contract plus the implementation target for generated OpenCode enforcement, including config schema, argv-prefix matching, fixed preset catalog/messages, and precedence rules) - `context/sce/generated-opencode-plugin-registration.md` (current generated OpenCode plugin-registration contract, canonical Pkl ownership, generated manifest/plugin paths including `sce-bash-policy` + `sce-agent-trace`, and TypeScript source ownership; Claude bash-policy enforcement has been removed from generated outputs) -- `context/sce/opencode-agent-trace-plugin-runtime.md` (current OpenCode agent-trace plugin runtime behavior, including `session.diff` event capture, `{ sessionID, diff, time }` extraction from `session.diff` properties with `Date.now()` for time and empty-diff skip, and CLI handoff to `sce hooks diff-trace` over STDIN JSON so Rust hook runtime owns AgentTraceDb plus collision-safe artifact persistence) +- `context/sce/opencode-agent-trace-plugin-runtime.md` (current OpenCode agent-trace plugin runtime behavior, including `message.updated` event capture filtered to user messages with diffs, `{ sessionID, diff, time, model_id }` extraction via `properties.info.role === "user"`, `Date.now()` for time, direct `info.model.providerID/modelID` model-id construction, object-entry patch joining with empty-patch payloads left to Rust hook validation, and CLI handoff to `sce hooks diff-trace` over STDIN JSON; Rust hook parsing and AgentTraceDb insertion persist `model_id`; `session.diff` event capture has been removed) - `context/sce/cli-first-install-channels-contract.md` (current first-wave `sce` install/distribution contract covering supported channels, canonical naming, `.version` release authority, and Nix-owned build policy) - `context/sce/optional-install-channel-integration-test-entrypoint.md` (current opt-in flake app contract for install-channel integration coverage, including thin flake delegation to the Rust runner, shared harness ownership, real npm+Bun+Cargo install flows, channel selector semantics, and the explicit non-default execution boundary) - `context/sce/cli-release-artifact-contract.md` (shared `sce` release artifact naming, checksum/manifest outputs, GitHub Releases as the canonical artifact publication surface, and the current three-target Linux/macOS release workflow topology) diff --git a/context/glossary.md b/context/glossary.md index ea9f27c6..e1bdd859 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -9,10 +9,11 @@ - generated-owned outputs: Files materialized by `config/pkl/generate.pkl` under `config/.opencode/**`, `config/automated/.opencode/**`, and `config/.claude/**`, including OpenCode plugin entrypoints, generated OpenCode `package.json` manifests, generated OpenCode `opencode.json` manifests, and Claude hook/settings assets. - `canonical OpenCode plugin registration source`: Shared Pkl-authored plugin-registration definition in `config/pkl/base/opencode.pkl`, re-exported from `config/pkl/renderers/common.pkl` as the canonical plugin list/path JSON consumed by OpenCode renderers before they emit generated `opencode.json` manifests; the current entries are `sce-bash-policy` and `sce-agent-trace`. - `generated OpenCode plugin registration contract`: Current generated-config contract where `config/.opencode/opencode.json` and `config/automated/.opencode/opencode.json` serialize the OpenCode `plugin` field from canonical Pkl sources for SCE-managed plugins only; the current registered paths are `./plugins/sce-bash-policy.ts` and `./plugins/sce-agent-trace.ts`. Claude does not use an OpenCode-style plugin manifest; bash-policy enforcement for Claude has been removed from generated outputs. -- `root Biome contract`: Repository-root formatting/linting contract owned by `biome.json`, currently scoped only to `npm/**` and `config/lib/bash-policy-plugin/**` with package-local `node_modules/**` excluded; the canonical execution path is the root Nix dev shell (`nix develop -c biome ...`). -- `cli flake checks`: Check derivations in root `flake.nix` (`checks..cli-tests`, `cli-clippy`, `cli-fmt`), dedicated `integrations/install` runner checks (`integrations-install-tests`, `integrations-install-clippy`, `integrations-install-fmt`), plus `pkl-parity`, split `npm/` JS checks (`npm-bun-tests`, `npm-biome-check`, `npm-biome-format`), and split `config/lib/bash-policy-plugin/` JS checks (`config-lib-bun-tests`, `config-lib-biome-check`, `config-lib-biome-format`); invoked via `nix flake check` at repo root. +- `root Biome contract`: Repository-root formatting/linting contract owned by `biome.json`, currently scoped only to `npm/**` and the shared `config/lib/**` plugin package root with package-local `node_modules/**` excluded; the canonical execution path is the root Nix dev shell (`nix develop -c biome ...`). +- `cli flake checks`: Check derivations in root `flake.nix` (`checks..cli-tests`, `cli-clippy`, `cli-fmt`), dedicated `integrations/install` runner checks (`integrations-install-tests`, `integrations-install-clippy`, `integrations-install-fmt`), plus `pkl-parity`, split `npm/` JS checks (`npm-bun-tests`, `npm-biome-check`, `npm-biome-format`), and split shared `config/lib/` JS checks (`config-lib-bun-tests`, `config-lib-biome-check`, `config-lib-biome-format`); invoked via `nix flake check` at repo root. - `npm JS flake checks`: The current `npm/` validation slice exposed by root `flake.nix`: `npm-bun-tests` runs only `bun test ./test/*.test.js`, `npm-biome-check` runs only Biome lint/check with formatter verification disabled, and `npm-biome-format` runs only Biome format verification with linter checks disabled. -- `config-lib JS flake checks`: The current `config/lib/bash-policy-plugin/` validation slice exposed by root `flake.nix`: `config-lib-bun-tests` runs `bun test`, `config-lib-biome-check` runs Biome lint/check with formatter verification disabled, and `config-lib-biome-format` runs Biome format verification with linter checks disabled, all scoped to `config/lib/bash-policy-plugin/` only. +- `config-lib JS flake checks`: The current shared `config/lib/` validation slice exposed by root `flake.nix`: `config-lib-bun-tests` runs the bash-policy runtime test at `bash-policy-plugin/bash-policy-runtime.test.ts` from the shared `config/lib/` package root with dependencies resolved from `config/lib/package.json` and `config/lib/bun.lock`, while `config-lib-biome-check` and `config-lib-biome-format` run Biome lint/check and format verification over the copied shared package source with formatter/linter halves disabled respectively. +- `config-lib shared package root`: Shared Bun/TypeScript package root at `config/lib/` for repository-owned OpenCode plugin support code. It owns `package.json`, `bun.lock`, and `tsconfig.json`, pins `@opencode-ai/plugin` to `1.15.4`, includes both `agent-trace-plugin/**/*.ts` and `bash-policy-plugin/**/*.ts` in strict TypeScript coverage, and excludes package-local `node_modules/` from both TypeScript and root Biome coverage. - `cli rust overlay toolchain`: Toolchain contract in root `flake.nix` that applies `rust-overlay.overlays.default`, pins `rust-bin.stable.1.93.1.default` with `rustfmt` + `clippy`, uses that toolchain across both Crane package and check derivations, and keeps toolchain selection explicit rather than inheriting nixpkgs defaults. - `cli Crane package pipeline`: Current root-flake packaging path in `flake.nix` where `packages.sce` is built through `craneLib.buildDepsOnly` plus `craneLib.buildPackage` against a filtered repo-root source that preserves the Cargo tree and the embedded config/assets required by `cli/build.rs`. - `cli Crane check pipeline`: Current root-flake check path in `flake.nix` where `cli-tests`, `cli-clippy`, and `cli-fmt` run through `craneLib.cargoTest`, `craneLib.cargoClippy`, and `craneLib.cargoFmt`; test and clippy derivations reuse the shared `cargoArtifacts` dependency cache from the package pipeline. @@ -29,8 +30,8 @@ - `RuntimeCommand seam`: Internal command-execution abstraction where clap-parsed commands are converted into boxed command objects with `name()` and `execute(&AppContext)` methods, allowing app lifecycle orchestration to log and run commands without a single central dispatch `match` covering every command; the `RuntimeCommand` trait and `RuntimeCommandHandle` type alias are defined in `cli/src/services/command_registry.rs`, and the `CommandRegistry` struct maps command names to zero-arg constructor functions for dispatch. Migrated commands (`HelpCommand`, `HelpTextCommand`, `VersionCommand`, `CompletionCommand`, `AuthCommand`, `ConfigCommand`, `SetupCommand`, `DoctorCommand`, `HooksCommand`) live in service-owned `command.rs` files; parsed request construction lives in `cli/src/services/parse/command_runtime.rs` when user-provided options or subcommands are required. - `sce dependency baseline`: Current crate dependency set declared in `cli/Cargo.toml` (`anyhow`, `clap`, `clap_complete`, `dirs`, `hmac`, `inquire`, `reqwest`, `serde`, `serde_json`, `sha2`, `tokio`, `tracing`, `tracing-subscriber`, `turso`) and validated through normal compile/test coverage. - `local Turso adapter`: Module in `cli/src/services/local_db/mod.rs` that defines `LocalDbSpec` and exposes `LocalDb` as a `TursoDb` alias. It resolves the canonical local DB path with `local_db_path()`, currently declares zero migrations, and inherits `new()`, `execute()`, and `query()` from the shared generic adapter. -- `agent trace DB adapter`: Module in `cli/src/services/agent_trace_db/mod.rs` that defines `AgentTraceDbSpec`, exposes `AgentTraceDb` as a `TursoDb` alias, resolves `/sce/agent-trace.db` through `agent_trace_db_path()`, embeds ordered migrations for `diff_traces`, `post_commit_patch_intersections`, and `agent_traces`, provides typed parameterized insert helpers for diff traces, post-commit intersection rows, and built agent-trace rows, exposes chronological recent `diff_traces` query/parse support with malformed-row skip accounting, has `AgentTraceDbLifecycle` for setup/doctor integration, and is written by `sce hooks diff-trace` (`diff_traces`) plus `sce hooks post-commit` (`post_commit_patch_intersections` and built `agent_traces`). -- `DiffTraceInsert`: Insert payload in `cli/src/services/agent_trace_db/mod.rs` carrying `time_ms`, `session_id`, and `patch` for parameterized writes to the `diff_traces` table. +- `agent trace DB adapter`: Module in `cli/src/services/agent_trace_db/mod.rs` that defines `AgentTraceDbSpec`, exposes `AgentTraceDb` as a `TursoDb` alias, resolves `/sce/agent-trace.db` through `agent_trace_db_path()`, embeds ordered migrations for `diff_traces`, `post_commit_patch_intersections`, the `diff_traces(time_ms, id)` index, `agent_traces`, nullable `diff_traces.model_id`, and nullable `agent_traces.agent_trace_id`, provides typed parameterized insert helpers for diff traces including `model_id`, post-commit intersection rows, and built agent-trace rows (including `agent_trace_id`), exposes chronological recent `diff_traces` query/parse support with malformed-row skip accounting, has `AgentTraceDbLifecycle` for setup/doctor integration, and is written by `sce hooks diff-trace` (`diff_traces`) plus `sce hooks post-commit` (`post_commit_patch_intersections` and built `agent_traces`). +- `DiffTraceInsert`: Insert payload in `cli/src/services/agent_trace_db/mod.rs` carrying `time_ms`, `session_id`, `patch`, and `model_id` for parameterized writes to the `diff_traces` table. - `DbSpec`: Service-specific database metadata trait in `cli/src/services/db/mod.rs` that supplies a diagnostic database name, canonical path resolver, and ordered embedded migration list for `TursoDb`. - `TursoDb`: Generic shared Turso database adapter in `cli/src/services/db/mod.rs`; owns parent-directory creation, Turso local open/connect flow, tokio current-thread runtime bridging, synchronous `execute()`/`query()`/`query_map()` wrappers, per-database `__sce_migrations` metadata, and generic migration execution for a `DbSpec` implementation. - `__sce_migrations`: Per-database migration metadata table created by `TursoDb::run_migrations()`; records applied migration IDs after successful execution so later setup/lifecycle initialization applies only migrations not yet recorded, while existing metadata-less DBs are brought forward by re-applying the current idempotent migration set and recording each ID. @@ -89,7 +90,7 @@ - `auth config baked default`: Optional key-declared fallback in `cli/src/services/config/mod.rs` used only after env and config-file inputs are absent; the first implemented case is `workos_client_id`, which currently falls back to `client_sce_default`. - `setup install engine`: Installer in `cli/src/services/setup/mod.rs` (`install_embedded_setup_assets`) that writes embedded setup assets into per-target staging directories and swaps them into repository-root `.opencode/`/`.claude/` destinations, using a unified remove-and-replace policy that removes existing targets before swapping staged content. - `setup remove-and-replace`: Replacement choreography in `cli/src/services/setup/mod.rs` where existing install targets are removed before staged content is promoted; on swap failure, the engine cleans temporary staging paths and returns deterministic recovery guidance (recover from version control). No backup artifacts are created. -- `hooks command routing contract`: Current hook command parser/dispatcher plus runtime wiring in `cli/src/services/hooks/mod.rs` (`HookSubcommand`, `run_hooks_subcommand`) that supports `pre-commit`, `commit-msg `, `post-commit`, `post-rewrite `, and `diff-trace` with deterministic invocation validation/usage errors; `commit-msg` is the only active attribution path behind the attribution hooks gate, `pre-commit`/`post-rewrite` are deterministic no-op entrypoints, `post-commit` actively captures the current commit patch, queries recent `diff_traces` from the past 7 days, combines valid patches via `patch::combine_patches`, intersects with the post-commit patch via `patch::intersect_patches`, persists the intersection result to `post_commit_patch_intersections`, and persists built Agent Trace payloads to AgentTraceDb `agent_traces` (DB-only, no post-commit Agent Trace file artifact), and `diff-trace` performs STDIN JSON intake with required non-empty `sessionID`/`diff` plus required `u64` `time` validation, non-lossy AgentTraceDb `time_ms` conversion, collision-safe per-invocation artifact persistence at `context/tmp/-000000-diff-trace.json`, and AgentTraceDb insertion. +- `hooks command routing contract`: Current hook command parser/dispatcher plus runtime wiring in `cli/src/services/hooks/mod.rs` (`HookSubcommand`, `run_hooks_subcommand`) that supports `pre-commit`, `commit-msg `, `post-commit`, `post-rewrite `, and `diff-trace` with deterministic invocation validation/usage errors; `commit-msg` is the only active attribution path behind the attribution hooks gate, `pre-commit`/`post-rewrite` are deterministic no-op entrypoints, `post-commit` actively captures the current commit patch, queries recent `diff_traces` from the past 7 days, combines valid patches via `patch::combine_patches`, intersects with the post-commit patch via `patch::intersect_patches`, persists the intersection result to `post_commit_patch_intersections`, and persists built Agent Trace payloads to AgentTraceDb `agent_traces` (DB-only, no post-commit Agent Trace file artifact), and `diff-trace` performs STDIN JSON intake with required non-empty `sessionID`/`diff`/`model_id` plus required `u64` `time` validation, non-lossy AgentTraceDb `time_ms` conversion, collision-safe per-invocation artifact persistence at `context/tmp/-000000-diff-trace.json`, and AgentTraceDb insertion. - `cloud sync gateway placeholder`: Abstraction in `cli/src/services/sync.rs` (`CloudSyncGateway`) that returns deferred cloud-sync checkpoints while `sync` remains non-production. - `sce CLI onboarding guide`: Crate-local documentation at `cli/README.md` that defines runnable placeholder commands, non-goals/safety limits, and roadmap mapping to service modules. - `plan/code overlap map`: Context artifact at `context/sce/plan-code-overlap-map.md` that classifies Shared Context Plan/Code, `/change-to-plan`, `/next-task`, `/commit`, and core skills into role-specific vs shared-reusable instruction blocks with explicit dedup targets. @@ -107,7 +108,7 @@ - `agent trace historical reference docs`: Retained `context/sce/agent-trace-*.md` artifacts that describe the removed pre-v0.3 Agent Trace design and task slices; they are reference-only and do not describe the active local-hook runtime. - `agent trace commit-msg co-author policy`: Current contract in `cli/src/services/hooks/mod.rs` (`apply_commit_msg_coauthor_policy`) that applies exactly one canonical trailer (`Co-authored-by: SCE `) only when attribution hooks are enabled and SCE is not disabled; duplicate canonical trailers are deduped idempotently. - `local DB migration contract`: `cli/src/services/local_db/mod.rs` delegates migration execution to `TursoDb` through the `DbSpec::migrations()` contract. The current `LocalDbSpec` migration list is empty, so `LocalDb::new()` opens/creates the canonical local DB without creating local tables. -- `hook no-op baseline`: Current `cli/src/services/hooks/mod.rs` runtime posture where `pre-commit` and `post-rewrite` return deterministic no-op status text, `commit-msg` is a gated mutating path behind the disabled-default attribution-hooks control, `post-commit` is an active intersection + Agent Trace DB path (captures current commit patch, queries recent `diff_traces` from past 7 days, combines/intersects patches, persists to `post_commit_patch_intersections`, and persists built Agent Trace payloads to `agent_traces` without post-commit file artifacts), and `diff-trace` is an active intake path (validates STDIN payload shape, writes collision-safe `context/tmp/-000000-diff-trace.json` artifacts, and inserts the same payload into AgentTraceDb). +- `hook no-op baseline`: Current `cli/src/services/hooks/mod.rs` runtime posture where `pre-commit` and `post-rewrite` return deterministic no-op status text, `commit-msg` is a gated mutating path behind the disabled-default attribution-hooks control, `post-commit` is an active intersection + Agent Trace DB path (captures current commit patch, queries recent `diff_traces` from past 7 days, combines/intersects patches, persists to `post_commit_patch_intersections`, and persists built Agent Trace payloads to `agent_traces` without post-commit file artifacts), and `diff-trace` is an active intake path (validates required STDIN payload fields including `model_id`, writes collision-safe parsed-payload `context/tmp/-000000-diff-trace.json` artifacts, and inserts parsed payload fields into AgentTraceDb). - `sce doctor` operator-health contract: `cli/src/services/doctor/mod.rs` is the stable doctor entrypoint, with focused `doctor/{inspect,render,fixes,types}.rs` submodules implementing the current approved operator-health surface in `context/sce/agent-trace-hook-doctor.md`: `sce doctor --fix` selects repair intent, help/output expose deterministic doctor mode, JSON includes stable problem taxonomy/fixability fields plus database records and fix-result records, the runtime validates state-root resolution, global and repo-local `sce/config.json` readability/schema health, local DB and Agent Trace DB path/health, DB-parent readiness barriers, git availability, non-repo vs bare-repo targeting failures, effective hook-path source resolution, required hook presence/executable/content drift against canonical embedded hook assets, and repo-root installed OpenCode integration presence for `OpenCode plugins`, `OpenCode agents`, `OpenCode commands`, and `OpenCode skills`. Human text mode now uses the approved sectioned layout (`Environment`, `Configuration` (includes Agent Trace DB row), `Repository`, `Git Hooks`, `Integrations`), `SCE doctor diagnose` / `SCE doctor fix` headers, bracketed `[PASS]`/`[FAIL]`/`[MISS]` status tokens with shared-style green/red colorization when enabled, simplified `label (path)` row formatting, top-level-only hook rows, and presence-only integration parent/child rows where missing required files surface as `[MISS]` children and `[FAIL]` parent groups. Fix mode still reuses canonical setup hook installation for missing/stale/non-executable required hooks and missing hooks directories and can bootstrap canonical missing SCE-owned DB parent directories. - `cli warnings-denied lint policy`: `cli/Cargo.toml` sets `warnings = "deny"`, so plain `cargo clippy --manifest-path cli/Cargo.toml` already fails on warnings without needing an extra `-- -D warnings` tail. - `agent trace local DB schema migration contract`: Retired `apply_core_schema_migrations` behavior removed from the current runtime during `agent-trace-removal-and-hook-noop-reset` T01; the local DB baseline is now file open/create only. @@ -141,16 +142,16 @@ - `PatchLoadError`: Error type in `cli/src/services/patch.rs` produced when `load_patch_from_json` or `load_patch_from_json_bytes` encounters invalid JSON or a payload that does not match the expected `ParsedPatch` structure; carries an actionable `message` field - `load_patch_from_json`: Public function in `cli/src/services/patch.rs` that reconstructs a `ParsedPatch` from a JSON string; storage-agnostic entrypoint for callers who have already read serialized JSON content from a database, file, or other source - `load_patch_from_json_bytes`: Public function in `cli/src/services/patch.rs` that reconstructs a `ParsedPatch` from JSON bytes; bytes-oriented counterpart to `load_patch_from_json` for callers working with raw byte data -- `intersect_patches`: Public function in `cli/src/services/patch.rs` that computes target-shaped touched-line intersection between two `ParsedPatch` values; takes `constructed_patch` and `post_commit_patch` as inputs, matches files by post-change path identity (exact `new_path` equality or absolute-vs-relative suffix-equivalent path segments), prefers exact touched-line matching by `kind` + `line_number` + `content`, falls back to historical matching by `kind` + `content` when line numbers drift across intermediate edits, and returns a `ParsedPatch` shaped from `post_commit_patch`'s file/hunk metadata; consumed by the active post-commit hook runtime +- `intersect_patches`: Public function in `cli/src/services/patch.rs` that computes target-shaped touched-line intersection between two `ParsedPatch` values; takes `constructed_patch` and `post_commit_patch` as inputs, matches files by post-change path identity (exact `new_path` equality or absolute-vs-relative suffix-equivalent path segments), prefers exact touched-line matching by `kind` + `line_number` + `content`, falls back to historical matching by `kind` + `content` when line numbers drift across intermediate edits, and returns a `ParsedPatch` shaped from `post_commit_patch` file/hunk ranges while inheriting result-hunk `model_id` from matched `constructed_patch` hunk provenance when available; consumed by the active post-commit hook runtime - `combine_patches`: Public function in `cli/src/services/patch.rs` that merges multiple `ParsedPatch` values into one deterministic result with later-input-wins semantics; groups files by `new_path`, deduplicates touched lines by identity (`kind` + `line_number` + `content`) with later patches winning, preserves file metadata and hunk metadata from the last contributing patch, orders files by first encounter and hunks by `old_start`; consumed by the active post-commit hook runtime before intersection - `HunkContributor`: Enum in `cli/src/services/agent_trace.rs` classifying a `post_commit_patch` hunk's origin relative to the intersection patch `intersection_patch = intersect_patches(constructed_patch, post_commit_patch)`: `Ai` (exact line-by-line match), `Mixed` (same slot but different content), `Unknown` (no corresponding slot in `intersection_patch`); serialized as `snake_case` JSON strings -- `Conversation`: Struct in `cli/src/services/agent_trace.rs` representing one per-hunk entry in the minimal agent-trace payload, carrying a nested `contributor` object (`{ "type": HunkContributor }`) plus `ranges`, where the current implementation emits exactly one `{ start_line, end_line }` entry derived from the `post_commit_patch` hunk +- `Conversation`: Struct in `cli/src/services/agent_trace.rs` representing one per-hunk entry in the minimal agent-trace payload, carrying a nested `contributor` object (`type` plus optional `model_id`) plus `ranges`, where the current implementation emits exactly one `{ start_line, end_line }` entry derived from the `post_commit_patch` hunk - `TraceFile`: Struct in `cli/src/services/agent_trace.rs` representing one per-file entry in the minimal agent-trace payload, carrying `path` (from `post_commit_patch`'s `new_path`) plus `conversations` (one per `post_commit_patch` hunk) - `AgentTraceVcs`: Top-level VCS metadata struct in `cli/src/services/agent_trace.rs` carrying `type` and `revision`; current builder behavior fixes `type` to `"git"` and maps revision from caller metadata. - `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), `vcs` (`AgentTraceVcs`), 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`) and `commit_revision` (mapped to `AgentTrace.vcs.revision`). -- `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`'s 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; library-only, not wired into CLI command dispatch -- `agent-trace plugin diff extraction seam`: Internal helper `extractDiffTracePayload` in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` that reads `input.event` and returns `{ sessionID, diff, time }` only when the event is `session.diff`; extracts `sessionID` from `properties.sessionID` (falling back to `"unknown"` when missing/empty), joins non-empty `patch` or `diff` fields from `properties.diff[]` entries into a single `diff` string, and uses `Date.now()` for `time`; returns `undefined` for non-`session.diff` events or empty diff arrays. -- `agent-trace plugin diff-trace hook handoff seam`: Internal helper `runDiffTraceHook` in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` that invokes `sce hooks diff-trace`, streams extracted `{ sessionID, diff, time }` to STDIN JSON, and surfaces deterministic invocation failures. +- `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`'s 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`: Exported helper `extractDiffTracePayload` in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` that reads `input.event` and returns `{ sessionID, diff, time, model_id }` only when the event is `message.updated` with `properties.info.role === "user"`; extracts `sessionID` from `info.sessionID` (falling back to `"unknown"` when absent or empty), joins object-entry `patch` fields from `info.summary?.diffs[]` with `\n` while preserving empty patch strings for Rust-side validation, uses `Date.now()` for `time`, and builds `model_id` directly as `info.model.providerID/info.model.modelID`; returns `undefined` for non-`message.updated` events, non-user messages, messages without a non-empty `summary.diffs` array, or diffs arrays without object entries. +- `agent-trace plugin diff-trace hook handoff seam`: Internal helper `runDiffTraceHook` in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` that invokes `sce hooks diff-trace`, streams extracted `{ sessionID, diff, time, model_id }` to STDIN JSON, and surfaces deterministic invocation failures. - `agent-trace plugin secondary diff artifact ownership`: Current runtime contract where `buildTrace` no longer writes diff-trace artifacts or database rows directly; extracted diff payloads are forwarded to CLI `diff-trace` intake and the Rust hook runtime owns AgentTraceDb insertion plus collision-safe per-invocation artifact persistence. diff --git a/context/overview.md b/context/overview.md index 1b4f5683..79bedd17 100644 --- a/context/overview.md +++ b/context/overview.md @@ -23,19 +23,19 @@ Invalid default-discovered config files now also degrade gracefully at startup: `cli/src/services/config/mod.rs` is now also the canonical owner for the CLI's shared observability/config primitive seam: `LogLevel`, `LogFormat`, `LogFileMode`, the observability env-key constants, and the shared bool parsing helpers consumed by `cli/src/services/observability.rs`. The CLI now has a minimal `AppContext` dependency-injection container in `cli/src/app.rs` holding `Arc`, `Arc`, `Arc`, `Arc`, and an optional `repo_root: Option`; it can derive repo-root-scoped contexts with `with_repo_root(...)` while preserving runtime dependencies. The broad capability seam lives in `cli/src/services/capabilities.rs`, where `FsOps`/`StdFsOps` wrap filesystem operations and `GitOps`/`ProcessGitOps` wrap git process execution plus repository-root/hooks-directory resolution. Current services have not migrated to consume the filesystem/git traits internally yet. The shared default path service in `cli/src/services/default_paths.rs` is now the canonical owner for production CLI path definitions. It resolves per-user config/state/cache roots through a dedicated internal `roots` seam, exposes the current persisted-artifact inventory (global config, auth tokens, local DB), and also defines the repo-relative, embedded-asset, install/runtime, hook, and context-path accessors consumed across current CLI production code. Non-test production modules should consume this shared catalog instead of hardcoding owned path literals. No default cache-backed persisted artifact currently exists, so cache-root resolution remains available without speculative cache-path features and no legacy default-path fallback is supported. The same config resolver now also owns the attribution-hooks gate used by local hook runtime: `SCE_ATTRIBUTION_HOOKS_ENABLED` overrides `policies.attribution_hooks.enabled`, and the gate defaults to disabled. -Generated config now includes repo-local OpenCode plugin assets for both profiles: `sce-bash-policy.ts` plus `sce-agent-trace.ts` are emitted under `config/.opencode/plugins/` and `config/automated/.opencode/plugins/`; bash-policy also emits shared runtime logic and preset data under `config/.opencode/lib/` (also emitted for `config/automated/.opencode/**`). Claude bash-policy enforcement has been removed from generated outputs. +Generated config now includes repo-local OpenCode plugin assets for both profiles: `sce-bash-policy.ts` plus `sce-agent-trace.ts` are emitted under `config/.opencode/plugins/` and `config/automated/.opencode/plugins/`; the agent-trace plugin extracts `{ sessionID, diff, time, model_id }` from user `message.updated` events with diffs and sends that payload to `sce hooks diff-trace`, while the Rust hook validates and persists `model_id` into `diff_traces` through AgentTraceDb. Bash-policy also emits shared runtime logic and preset data under `config/.opencode/lib/` (also emitted for `config/automated/.opencode/**`). Claude bash-policy enforcement has been removed from generated outputs. The `doctor` command now exposes explicit inspection mode (`sce doctor`) and repair-intent mode (`sce doctor --fix`) at the CLI/help/schema level while keeping diagnosis mode read-only. It now validates both current global operator health and the current repo/hook-integrity slice: state-root resolution, global config path resolution, global and repo-local `sce/config.json` readability/schema validity, local DB and Agent Trace DB path + health, DB parent-directory readiness, git availability, non-repo vs bare-repo targeting failures, effective git hook-path source (default, per-repo `core.hooksPath`, or global `core.hooksPath`), hooks-directory health, required hook presence/executable permissions/content drift against canonical embedded SCE-managed hook assets, and repo-root OpenCode integration presence across the installed `plugins`, `agents`, `commands`, and `skills` inventories with embedded SHA-256 content verification for OpenCode assets. Text mode now renders the approved human-only layout with ordered `Environment` / `Configuration` / `Repository` / `Git Hooks` / `Integrations` sections, `SCE doctor diagnose` / `SCE doctor fix` headers, bracketed `[PASS]`/`[FAIL]`/`[MISS]` status tokens, shared-style green pass plus red fail/miss coloring when color output is enabled, simplified `label (path)` row formatting, top-level-only hook rows, and integration parent/child rows that reflect missing vs content-mismatch states; JSON output now reports Agent Trace DB health under `agent_trace_db` (as a row within the Configuration section in text mode). Repo-scoped database reporting is empty by default because no repo-owned SCE database currently exists. Fix mode reuses the canonical setup hook install flow to repair missing/stale/non-executable required hooks and can also bootstrap missing canonical DB parent directories while preserving manual-only guidance for unsupported issues. Local database bootstrap is now owned by `LocalDbLifecycle::setup` and `AgentTraceDbLifecycle::setup` aggregated by the setup command, while doctor validates both DB paths/health and can bootstrap missing parent directories. Wiring a user-invocable `sce sync` command is deferred to `0.4.0`. The repository-root flake (`flake.nix`) now applies a Rust overlay-backed stable toolchain pinned to `1.93.1` (with `rustfmt` and `clippy`), reads package/check version from the repo-root `.version` file, builds `packages.sce` through a Crane `buildDepsOnly` + `buildPackage` pipeline with filtered package sources for the Cargo tree plus required embedded config/assets, and runs `cli-tests`, `cli-clippy`, and `cli-fmt` through Crane-backed check derivations (`cargoTest`, `cargoClippy`, `cargoFmt`) that reuse the same filtered source/toolchain setup. The root flake also exposes release install/run outputs directly as `packages.sce` (with `packages.default = packages.sce`) plus `apps.sce` and `apps.default`, so `nix build .#default`, `nix run . -- --help`, `nix run .#sce -- --help`, and `nix profile install github:crocoder-dev/shared-context-engineering` all target the packaged `sce` binary through the same flake-owned entrypoints. The CLI Cargo package metadata now includes crates.io publication-ready fields with crate-local install guidance in `cli/README.md`; supported Cargo install paths are `cargo install shared-context-engineering --locked`, `cargo install --git https://github.com/crocoder-dev/shared-context-engineering shared-context-engineering --locked`, and local `cargo install --path cli --locked`. The published crate installs the `sce` binary. The crate also keeps `cargo clippy --manifest-path cli/Cargo.toml` warnings-denied through `cli/Cargo.toml` lint configuration, so an extra `-- -D warnings` flag is redundant. -The repository-root flake is now the single Nix entrypoint for both repo tooling and CLI packaging/checks, so root-level `nix flake check` evaluates the Crane-backed CLI checks (`cli-tests`, `cli-clippy`, `cli-fmt`), the dedicated `integrations/install` runner checks (`integrations-install-tests`, `integrations-install-clippy`, `integrations-install-fmt`), plus six split JavaScript check derivations: `npm-bun-tests`, `npm-biome-check`, `npm-biome-format`, `config-lib-bun-tests`, `config-lib-biome-check`, and `config-lib-biome-format`, without nested-flake indirection. For Cargo packaging/builds, the crate now compiles against a temporary `cli/assets/generated/` mirror prepared from canonical `config/` outputs during Nix builds and crates.io publish runs rather than from a committed crate-local snapshot. +The repository-root flake is now the single Nix entrypoint for both repo tooling and CLI packaging/checks, so root-level `nix flake check` evaluates the Crane-backed CLI checks (`cli-tests`, `cli-clippy`, `cli-fmt`), the dedicated `integrations/install` runner checks (`integrations-install-tests`, `integrations-install-clippy`, `integrations-install-fmt`), plus six split JavaScript check derivations: `npm-bun-tests`, `npm-biome-check`, `npm-biome-format`, `config-lib-bun-tests`, `config-lib-biome-check`, and `config-lib-biome-format`, without nested-flake indirection. The config-lib checks now consume `config/lib/` as the shared Bun/TypeScript package root for both `agent-trace-plugin/` and `bash-policy-plugin/`, with dependencies resolved from `config/lib/package.json` and `config/lib/bun.lock`. For Cargo packaging/builds, the crate now compiles against a temporary `cli/assets/generated/` mirror prepared from canonical `config/` outputs during Nix builds and crates.io publish runs rather than from a committed crate-local snapshot. Local developer Nix tuning guidance now lives in `AGENTS.md`, including optional user-level `~/.config/nix/nix.conf` recommendations for `max-jobs` and `cores` plus an explicit system-level-only note for `auto-optimise-store`. The Pkl authoring layer owns generated OpenCode plugin registration for SCE-managed plugins: `config/pkl/base/opencode.pkl` defines the canonical plugin entries, `config/pkl/renderers/common.pkl` re-exports the shared plugin list for renderer use, and generated `config/.opencode/opencode.json` plus `config/automated/.opencode/opencode.json` register `./plugins/sce-bash-policy.ts` and `./plugins/sce-agent-trace.ts` through OpenCode's `plugin` field. Claude does not use an OpenCode-style plugin manifest; bash-policy enforcement for Claude has been removed from generated outputs. The current first-wave CLI install/distribution contract is now defined for `sce`: the active implemented channel set is repo-flake Nix, Cargo, and npm; `Homebrew` is deferred from the current implementation stage. Nix-managed build/release entrypoints are the source of truth for this rollout, npm consumes Nix-produced release artifacts, and repo-root `.version` is the canonical checked-in release version source that release packaging and downstream Cargo/npm publication must match. The shared release artifact foundation is now implemented through root-flake apps `release-artifacts` and `release-manifest`, which emit canonical `sce-v-.tar.gz` archives, SHA-256 checksum files, merged manifest outputs, and a detached `sce-v-release-manifest.json.sig` produced from a non-repo private signing key; the npm distribution surface is now implemented as a checked-in `npm/` launcher package plus root-flake `release-npm-package`, which packs `sce-v-npm.tgz`, refuses mismatched checked-in package metadata, and installs the native CLI by downloading the release manifest plus detached signature, verifying the manifest with the bundled npm public key, and only then checksum-verifying the matching GitHub release archive at npm `postinstall` time. GitHub Releases are the canonical publication surface for those release artifacts, while crates.io and npm registry publication are separate non-bumping publish stages under the approved release topology. GitHub CLI release automation now lives in dedicated `release-sce*.yml` workflows split by Linux, Linux ARM, and macOS ARM, and `.github/workflows/release-sce.yml` now orchestrates those three reusable platform lanes before assembling the signed release manifest, npm tarball, and GitHub release payload. The orchestrator now tags/releases the checked-in `.version` directly and rejects version mismatches instead of generating a new semver during workflow execution, `.github/workflows/publish-crates.yml` is the dedicated crates.io publish stage triggered from a published GitHub release or manual dispatch with the same `.version`/tag/Cargo parity checks and a clean temporary repo copy for Cargo packaging, and `release-agents.yml` remains Tessl-only. The current supported automated release target matrix is `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`, and `aarch64-apple-darwin`; npm launcher platform support remains a separate current-state surface documented in the npm distribution contract and launcher code. The downstream publish-stage implementation is now complete for both registries: `.github/workflows/publish-crates.yml` publishes the checked-in crate version after `.version`/tag/Cargo parity checks, and `.github/workflows/publish-npm.yml` publishes the checked-in npm package after `.version`/tag/npm parity checks plus verification of the canonical `sce-v-npm.tgz` GitHub release asset. -The repository root now also owns the canonical Biome contract for the current JavaScript tooling slice: `biome.json` scopes formatting/linting to `npm/` and `config/lib/bash-policy-plugin/` only, and the root Nix dev shell provides the `biome` binary so contributors do not need a host-installed formatter/linter for those areas. +The repository root now also owns the canonical Biome contract for the current JavaScript tooling slice: `biome.json` scopes formatting/linting to `npm/` and the shared `config/lib/` plugin package root while excluding package-local `node_modules/`, and the root Nix dev shell provides the `biome` binary so contributors do not need a host-installed formatter/linter for those areas. The root flake now also exposes an explicit opt-in install-channel integration-test app, `nix run .#install-channel-integration-tests -- --channel `, which remains outside the default `nix flake check` path while the Rust runner now executes real npm, Bun, and Cargo install-and-verify flows for all three first-wave channels. Shared Context Plan and Shared Context Code remain separate agent roles by design; planning (`/change-to-plan`) and implementation (`/next-task`) stay split while shared baseline guidance is deduplicated via canonical skill-owned contracts. Their shared baseline doctrine (core principles, `context/` authority, and quality posture) is defined once as canonical snippets in `config/pkl/base/shared-content-common.pkl` and composed into both agent bodies during generation; the aggregation surfaces `config/pkl/base/shared-content.pkl` (manual) and `config/pkl/base/shared-content-automated.pkl` (automated) import from grouped `plan`, `code`, and `commit` modules for downstream renderers. @@ -44,10 +44,10 @@ Context sync now uses an important-change gate: cross-cutting/policy/architectur The `/change-to-plan` command body is also intentionally thin orchestration: it delegates clarification and plan-shape contracts to `sce-plan-authoring` (including one-task/one-atomic-commit task slicing) while keeping wrapper-level plan output and handoff obligations explicit. The generated OpenCode command doc now also emits `entry-skill: sce-plan-authoring` plus an ordered `skills` list. The targeted support commands (`handover`, `commit`, `validate`) keep their thin-wrapper behavior and now also emit machine-readable OpenCode command frontmatter describing their entry skill and ordered skill chain. `/commit` is now split by profile: manual generated commands remain proposal-only and allow split guidance when staged changes mix unrelated goals, while the automated OpenCode `/commit` command generates exactly one commit message and runs `git commit` against the staged diff. The shared `sce-atomic-commit` contract also requires commit bodies to cite affected plan slug(s) and updated task ID(s) when staged changes include `context/plans/*.md`, and to stop for clarification instead of inventing those references when the staged plan diff is ambiguous. The prior no-git-wrapper Agent Trace design artifacts under `context/sce/agent-trace-*.md` are retained only as historical reference; the current CLI runtime no longer wires the removed Agent Trace schema adaptation, payload building, retry replay, or rewrite handling paths into local hook execution. -The hooks service now uses a minimal attribution-only runtime: `commit-msg` is the only hook that mutates behavior, conditionally injecting exactly one canonical SCE trailer when the attribution-hooks gate is enabled and `SCE_DISABLED` is false; `pre-commit` and `post-rewrite` remain deterministic no-op entrypoints; `post-commit` is an active intersection entrypoint that captures current commit patch, queries recent `diff_traces` from past 7 days, combines/intersects patches, persists intersection metadata to `post_commit_patch_intersections`, and persists the built Agent Trace payload to AgentTraceDb `agent_traces` (DB-only, no post-commit Agent Trace file artifact); and `diff-trace` performs STDIN JSON intake with required `u64` millisecond `time`, non-lossy AgentTraceDb `time_ms` conversion, and collision-safe timestamp+attempt artifact filenames. +The hooks service now uses a minimal attribution-only runtime: `commit-msg` is the only hook that mutates behavior, conditionally injecting exactly one canonical SCE trailer when the attribution-hooks gate is enabled and `SCE_DISABLED` is false; `pre-commit` and `post-rewrite` remain deterministic no-op entrypoints; `post-commit` is an active intersection entrypoint that captures current commit patch, queries recent `diff_traces` from past 7 days, combines/intersects patches, persists intersection metadata to `post_commit_patch_intersections`, and persists the built Agent Trace payload to AgentTraceDb `agent_traces` (DB-only, no post-commit Agent Trace file artifact); and `diff-trace` currently validates/persists required `sessionID`/`diff`/`model_id` plus required `u64` millisecond `time`, with non-lossy AgentTraceDb `time_ms` conversion and collision-safe timestamp+attempt artifact filenames. 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` own parent-directory creation, connection setup, tokio current-thread runtime bridging, synchronous `execute`/`query`/`query_map`, generic migration execution, and shared DB lifecycle helpers for service-specific database wrappers. Agent Trace persistence now has its own `cli/src/services/agent_trace_db/mod.rs` wrapper, canonical `/sce/agent-trace.db` path, ordered `diff_traces` plus `post_commit_patch_intersections` plus `agent_traces` migrations, typed parameterized insert helpers for diff traces, post-commit intersection rows, and built agent-trace rows, chronological recent `diff_traces` query/parse support with malformed-row skip accounting, registered setup/doctor lifecycle provider, active `sce hooks diff-trace` writes for `diff_traces`, and active `sce hooks post-commit` writes for built `agent_traces` payloads. -The hooks command surface now also supports concrete runtime subcommand routing (`pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`) with deterministic argument/STDIN validation. Current runtime behavior keeps attribution disabled by default: the attribution gate enables canonical trailer insertion in `commit-msg`, `pre-commit`/`post-rewrite` remain deterministic no-ops, `post-commit` is the active bounded recent-diff-trace intersection path, and `diff-trace` is the active intake path for STDIN `{ sessionID, diff, time }` payload dual persistence with required `u64` millisecond `time`, non-lossy AgentTraceDb `time_ms` conversion, and collision-safe timestamp+attempt artifact filenames. This behavior is documented in `context/sce/agent-trace-hooks-command-routing.md`. +The hooks command surface now also supports concrete runtime subcommand routing (`pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`) with deterministic argument/STDIN validation. Current runtime behavior keeps attribution disabled by default: the attribution gate enables canonical trailer insertion in `commit-msg`, `pre-commit`/`post-rewrite` remain deterministic no-ops, `post-commit` is the active bounded recent-diff-trace intersection path, and `diff-trace` is the active intake path for parsed STDIN `{ sessionID, diff, time, model_id }` payload persistence with required `u64` millisecond `time`, non-lossy AgentTraceDb `time_ms` conversion, and collision-safe timestamp+attempt artifact filenames. This behavior is documented in `context/sce/agent-trace-hooks-command-routing.md`. The setup service now also exposes deterministic required-hook embedded asset accessors (`iter_required_hook_assets`, `get_required_hook_asset`) backed by canonical templates in `cli/assets/hooks/` for `pre-commit`, `commit-msg`, and `post-commit`; this behavior is documented in `context/sce/setup-githooks-hook-asset-packaging.md`. The setup service now also includes required-hook install orchestration (`install_required_git_hooks`) that resolves repository root and effective hooks path from git truth, enforces deterministic per-hook outcomes (`Installed`/`Updated`/`Skipped`), and uses a unified remove-and-replace policy that removes existing hooks before swapping staged content with deterministic recovery guidance on swap failures; this behavior is documented in `context/sce/setup-githooks-install-flow.md`. The setup command parser/dispatch now also supports composable setup+hooks runs (`sce setup --opencode|--claude|--both --hooks`) plus hooks-only mode (`sce setup --hooks` with optional `--repo `), enforces deterministic compatibility validation (`--repo` requires `--hooks`; target flags remain mutually exclusive), and emits deterministic setup/hook outcome messaging (`installed`/`updated`/`skipped`); this behavior is documented in `context/sce/setup-githooks-cli-ux.md`. diff --git a/context/patterns.md b/context/patterns.md index e5c1dd1a..cf836503 100644 --- a/context/patterns.md +++ b/context/patterns.md @@ -14,8 +14,9 @@ ## Root Biome scoping - Keep Biome configuration at the repository root when one formatter/linter contract spans multiple JS package areas. -- Scope root `biome.json` explicitly to the approved JS surfaces only; the current approved scope is `npm/**` and `config/lib/bash-policy-plugin/**`. +- Scope root `biome.json` explicitly to the approved JS surfaces only; the current approved scope is `npm/**` and the shared `config/lib/**` plugin package root. - Exclude package-local install artifacts such as `node_modules/**` from root Biome coverage. +- Keep repository-owned OpenCode plugin support code under one shared `config/lib/` Bun/TypeScript package root; package metadata and lockfile ownership live at `config/lib/package.json` and `config/lib/bun.lock`, not under individual plugin subdirectories. - Provide Biome through the root Nix dev shell so contributors can run `nix develop -c biome ...` without a host-installed binary or package-local setup. - When exposing JS validation through `nix flake check`, split Bun test, Biome lint/check, and Biome format verification into separately named derivations per target directory so failures stay tool- and surface-specific. @@ -133,7 +134,7 @@ - For cross-service CLI dependencies that will be injected through `AppContext`, prefer broad capability traits in `cli/src/services/capabilities.rs` over one-off per-service abstractions; keep production wrappers thin over `std::fs` and `git` process execution until call-site migration tasks approve deeper service refactors. - For future CLI domains, define trait-first service contracts with request/plan models in `cli/src/services/*` and keep placeholder implementations explicitly non-runnable until production behavior is approved. - Model deferred integration boundaries with concrete event/capability data structures (for example hook-runtime attribution snapshots/policies and cloud-sync checkpoints) so later tasks can implement behavior without reshaping public seams. -- For the current local-hook baseline, keep `pre-commit` and `post-rewrite` as deterministic no-op entrypoints; keep `post-commit` as the active bounded recent-diff-trace intersection entrypoint; keep `diff-trace` as an explicit STDIN intake path with deterministic required-field validation, non-lossy AgentTraceDb `time_ms` conversion, collision-safe `context/tmp/-000000-diff-trace.json` persistence using atomic create-new retry semantics, and command-failing AgentTraceDb insertion through the existing database adapter. +- For the current local-hook baseline, keep `pre-commit` and `post-rewrite` as deterministic no-op entrypoints; keep `post-commit` as the active bounded recent-diff-trace intersection entrypoint; keep `diff-trace` as an explicit STDIN intake path with deterministic required-field validation for `sessionID`, `diff`, `time`, and `model_id`, non-lossy AgentTraceDb `time_ms` conversion, collision-safe `context/tmp/-000000-diff-trace.json` persistence using atomic create-new retry semantics, and command-failing AgentTraceDb insertion through the existing database adapter. - For commit-msg co-author policy seams, gate canonical trailer insertion on runtime controls (`SCE_DISABLED` plus the shared attribution-hooks enablement gate), and enforce idempotent dedupe so allowed cases end with exactly one `Co-authored-by: SCE ` trailer. - For local hook attribution flows, resolve the top-level enablement gate through the shared config precedence model (`SCE_ATTRIBUTION_HOOKS_ENABLED` over `policies.attribution_hooks.enabled`, default `false`) so commit-msg attribution stays disabled by default without adding hook-specific config parsing. - Do not assume post-commit persistence, retry replay, remap ingestion, or rewrite trace transformation are active in the current local-hook runtime; those paths are removed from the current baseline. diff --git a/context/plans/add-agent-traces-agent-trace-id.md b/context/plans/add-agent-traces-agent-trace-id.md new file mode 100644 index 00000000..092e247a --- /dev/null +++ b/context/plans/add-agent-traces-agent-trace-id.md @@ -0,0 +1,121 @@ +# Plan: Add `agent_trace_id` column to `agent_traces` table + +## Change summary + +The `agent_traces` table currently stores the UUIDv7 agent trace ID only inside `trace_json` (as part of the serialized `AgentTrace` JSON payload). Add a dedicated `agent_trace_id TEXT` column so the UUIDv7 identifier is queryable without parsing JSON. + +The `generate_agent_trace_id()` function in `agent_trace.rs` already produces a UUIDv7 string. No uuid v4 usage exists anywhere in the codebase — confirmed via grep, nothing to remove. + +Three layers need updating: +1. **SQL migration** — add the column to the `agent_traces` table +2. **Rust DB layer** — register migration, update insert SQL and `AgentTraceInsert` struct +3. **Rust hook caller** — extract `agent_trace.id` from the built `AgentTrace` and pass it through + +## Success criteria + +- New migration `006_add_agent_traces_agent_trace_id.sql` adds `agent_trace_id TEXT` (nullable) to `agent_traces` +- Migration is registered in `AGENT_TRACE_MIGRATIONS` +- `INSERT_AGENT_TRACE_SQL` includes `agent_trace_id` as a parameter +- `AgentTraceInsert` carries `agent_trace_id: &'a str` +- Caller in `hooks/mod.rs` extracts `agent_trace.id` and passes it in the insert +- `nix flake check` passes + +## Constraints and non-goals + +- The `agent_trace_id` column is **nullable** (existing rows get `NULL`). +- No changes to `agent_trace.rs` — `generate_agent_trace_id` and `AgentTrace.id` are unchanged. +- No changes to the TypeScript plugin, payload parsing, or any other table. +- No new tests — existing build/tests cover the change. +- uuid v4 removal is not needed — confirmed zero usage across the codebase. + +## Task stack + +- [x] T01: `Create migration 006_add_agent_traces_agent_trace_id.sql` (status:done) + - Task ID: T01 + - Goal: Create the SQL migration file that adds the `agent_trace_id` column. + - Boundaries (in/out of scope): + - In — Create `cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql` with `ALTER TABLE agent_traces ADD COLUMN agent_trace_id TEXT;` + - Out — Changes to any other table, migration runner, or existing files + - Done when: + - Migration file exists at `cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql` + - Contains `ALTER TABLE agent_traces ADD COLUMN agent_trace_id TEXT;` + - Verification notes (commands or checks): + - `test -f cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql` ✅ + - File content inspection ✅ + - `nix develop -c sh -c 'cd cli && cargo check'` ✅ + - **Completed:** 2026-05-19 + - **Files changed:** `cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql` (new) + - **Evidence:** File exists with correct ALTER TABLE content; `cargo check` passes (0.52s) + +- [x] T02: `Register migration and update Rust DB insert path` (status:done) + - Task ID: T02 + - Goal: Wire the new migration into the Rust DB layer and update the insert SQL + struct. + - Boundaries (in/out of scope): + - In — Register `006_add_agent_traces_agent_trace_id` in `AGENT_TRACE_MIGRATIONS` with corresponding `include_str!` constant; update `INSERT_AGENT_TRACE_SQL` to include `agent_trace_id`; add `agent_trace_id: &'a str` to `AgentTraceInsert` + - Out — Changes to any other SQL statements, query logic, or structs (`DiffTraceInsert`, `PostCommitPatchIntersectionInsert`, etc.) + - Done when: + - Migration `006_add_agent_traces_agent_trace_id` registered in the migrations list + - `AgentTraceInsert` has `agent_trace_id: &'a str` field + - `INSERT_AGENT_TRACE_SQL` is `INSERT INTO agent_traces (commit_id, commit_time_ms, trace_json, agent_trace_id) VALUES (?1, ?2, ?3, ?4)` + - Build compiles (caller not yet updated, so `AgentTraceInsert` construction may still fail — acceptable at T02 boundary) + - Verification notes (commands or checks): + - `nix develop -c sh -c 'cd cli && cargo check'` (expect compile errors in hooks/mod.rs — that's T03 scope) + - Visual inspection of the migration constant, SQL, and struct + - **Completed:** 2026-05-19 + - **Files changed:** `cli/src/services/agent_trace_db/mod.rs` + - **Evidence:** Migration registered in list; SQL updated to 4 params; struct has `agent_trace_id`; only expected error in `hooks/mod.rs` (T03 scope); test assertion updated for 6 migrations; `cargo check` reports only the expected caller error + +- [x] T03: `Pass agent_trace_id from hooks/mod.rs caller` (status:done) + - Task ID: T03 + - Goal: Extract the UUIDv7 ID from the built `AgentTrace` struct and pass it in the insert input. + - Boundaries (in/out of scope): + - In — Modify the insert-input construction in `hooks/mod.rs` (around line 533) to include `agent_trace_id: &agent_trace.id` + - Out — Changes to `agent_trace.rs`, validation logic, or any other flow + - Done when: + - `AgentTraceInsert` in `hooks/mod.rs` includes `agent_trace_id: &agent_trace.id` + - `nix develop -c sh -c 'cd cli && cargo check'` passes + - Verification notes (commands or checks): + - `nix develop -c sh -c 'cd cli && cargo check'` + - `nix flake check` + - **Completed:** 2026-05-19 + - **Files changed:** `cli/src/services/hooks/mod.rs` + - **Evidence:** `cargo check` passes; `nix flake check` passes; `agent_trace_id: &agent_trace.id` added to `AgentTraceInsert` constructor + +- [x] T04: `Validation and cleanup` (status:done) + - Task ID: T04 + - Goal: Verify full pipeline compiles, existing tests pass, and no regressions. + - Boundaries (in/out of scope): + - In — Run `nix flake check`; confirm new migration is loadable; confirm applied migration ID assertions include the new migration; confirm no stale artifacts in `context/tmp/` + - Out — Any code changes beyond verification + - Done when: + - `nix flake check` passes + - No stale artifacts left in `context/tmp/` + - Verification notes (commands or checks): + - `nix flake check` + - `nix develop -c sh -c 'cd cli && cargo check'` + - **Completed:** 2026-05-19 + - **Files changed:** none (verification-only) + - **Evidence:** Migration file exists at `cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql` with correct ALTER TABLE content; migration registered in AGENT_TRACE_MIGRATIONS; INSERT_AGENT_TRACE_SQL includes `agent_trace_id`; AgentTraceInsert has `agent_trace_id: &'a str`; test assertions cover all 6 migration IDs; stale artifacts cleaned from `context/tmp/`; `nix flake check` passes + +## Validation Report + +### Commands run +- `nix flake check` -> exit 0 — all checks evaluated and built cleanly (pkl-parity built and passed) +- `rm -f context/tmp/2026-*.json context/tmp/sce.log` — 138 stale JSON artifacts + sce.log removed; only `.gitignore` remains +- Verified migration file: `test -f cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql` ✅ +- Verified migration content: `ALTER TABLE agent_traces ADD COLUMN agent_trace_id TEXT;` ✅ + +### Success-criteria verification +- [x] New migration `006_add_agent_traces_agent_trace_id.sql` adds `agent_trace_id TEXT` (nullable) — confirmed file exists with correct SQL +- [x] Migration registered in `AGENT_TRACE_MIGRATIONS` — confirmed line 44 of `agent_trace_db/mod.rs` +- [x] `INSERT_AGENT_TRACE_SQL` includes `agent_trace_id` as 4th parameter — confirmed line 74 +- [x] `AgentTraceInsert` carries `agent_trace_id: &'a str` — confirmed line 168 +- [x] Caller in `hooks/mod.rs` passes `agent_trace_id: &agent_trace.id` — confirmed line 537 +- [x] `nix flake check` passes — confirmed 2026-05-19 + +### Residual risks +- None identified. The `agent_trace_id` column is nullable, so existing rows with NULL remain valid. No schema changes affect other tables or operations. + +## Open questions + +None — all clarifications resolved during intake. diff --git a/context/plans/add-diff-traces-model-id.md b/context/plans/add-diff-traces-model-id.md new file mode 100644 index 00000000..ab6a586b --- /dev/null +++ b/context/plans/add-diff-traces-model-id.md @@ -0,0 +1,172 @@ +# Plan: Add `model_id` column to `diff_traces` table + +## Change summary + +Add a `model_id` text column to the `diff_traces` table to track which AI model generated each diff trace. The value is constructed from the OpenCode event's model info as `${providerID}/${modelID}`. + +Three layers need updating: +1. **TypeScript agent-trace plugin** — emit `model_id` in the `DiffTracePayload` sent to the Rust hook +2. **Rust hook handler** — parse `model_id` from the payload and pass it through to the DB layer +3. **Rust DB layer** — migration to add the column, updated insert SQL, and updated insert struct + +## Success criteria + +- New migration `005_add_diff_traces_model_id.sql` adds `model_id TEXT` (nullable) to `diff_traces` +- `extractDiffTracePayload` returns `model_id` constructed as `providerID/modelID` +- `DiffTracePayload` struct in Rust parses `model_id` as a required non-empty string +- `DiffTraceInsert` includes `model_id` and the INSERT SQL writes it +- Existing SELECT queries for recent patches remain unchanged +- `nix flake check` passes + +## Constraints and non-goals + +- The `model_id` column is **nullable** (per user choice); existing rows get `NULL`. +- The SELECT query for recent diff trace patches (`SELECT_RECENT_DIFF_TRACE_PATCHES_SQL`) is **not** updated — the model_id is for storage/audit only. +- `DiffTracePatchRow`, `ParsedDiffTracePatch`, and `SkippedDiffTracePatch` structs are **not** changed. +- No changes to the post-commit intersection or agent_traces flows. +- Do not add new tests for this plan; use existing checks, typechecking/build checks, and inspection. + +## Task stack + +- [x] T01: `Add model_id to TypeScript DiffTracePayload and extractDiffTracePayload` (status:done) + - Task ID: T01 + - Goal: Update the agent-trace plugin to construct and emit `model_id` in the diff trace payload. + - Boundaries (in/out of scope): + - In — `DiffTracePayload` type gains `model_id: string`; `extractDiffTracePayload` extracts `model.providerID` and `model.modelID` from the event info and joins them with `/` + - Out — Changes to the OpenCode event type definitions; changes to how model info is typed; new test files or new test cases + - Done when: + - `extractDiffTracePayload` returns `model_id` field constructed from `input.event.properties.info.model.providerID` + `/` + `input.event.properties.info.model.modelID` + - If `model` object or its sub-fields are missing, falls back to `"unknown/unknown"` + - `buildTrace` passes the payload as before + - Verification notes (commands or checks): + - Visual inspection of the returned payload shape + - `nix develop -c tsc --noEmit -p config/lib/agent-trace-plugin/tsconfig.json` + - `nix run .#pkl-check-generated` + - Execution record: + - Status: done + - Completed: 2026-05-15 + - Files changed: `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts`, generated `config/.opencode/plugins/sce-agent-trace.ts`, generated `config/automated/.opencode/plugins/sce-agent-trace.ts` + - Evidence: `nix develop -c tsc --noEmit -p config/lib/agent-trace-plugin/tsconfig.json` passed; `nix run .#pkl-check-generated` passed; payload shape inspected directly + - Cleanup completed: previously added `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.test.ts` removed; no new tests remain from T01 + - Context sync classification: important localized runtime-contract change; synced `context/sce/opencode-agent-trace-plugin-runtime.md`, discoverability/root summaries, and Rust hook docs to distinguish plugin-emitted `model_id` from pending Rust validation/storage support + +- [x] T02: `Create DB migration 005_add_diff_traces_model_id.sql` (status:done) + - Task ID: T02 + - Goal: Create a new SQL migration file that adds the `model_id` column to the `diff_traces` table. + - Boundaries (in/out of scope): + - In — Create `cli/migrations/agent-trace/005_add_diff_traces_model_id.sql` with `ALTER TABLE diff_traces ADD COLUMN model_id TEXT;` + - Out — Changes to any other table; changes to migration runner + - Done when: + - Migration file exists at `cli/migrations/agent-trace/005_add_diff_traces_model_id.sql` + - Contains `ALTER TABLE diff_traces ADD COLUMN model_id TEXT;` + - Verification notes (commands or checks): + - File exists: `ls cli/migrations/agent-trace/005_add_diff_traces_model_id.sql` + - Execution record: + - Status: done + - Completed: 2026-05-15 + - Files changed: `cli/migrations/agent-trace/005_add_diff_traces_model_id.sql` + - Evidence: migration file read successfully; `test -f "cli/migrations/agent-trace/005_add_diff_traces_model_id.sql" && test "$(tr -d '\n' < "cli/migrations/agent-trace/005_add_diff_traces_model_id.sql")" = "ALTER TABLE diff_traces ADD COLUMN model_id TEXT;"` passed + - Context sync classification: localized migration artifact change; root shared context verify-only; synced `context/sce/agent-trace-db.md` and `context/context-map.md` to document the checked-in but not-yet-registered migration file + +- [x] T03: `Update Rust SQL constants, DiffTraceInsert, and insert logic` (status:done) + - Task ID: T03 + - Goal: Update `agent_trace_db/mod.rs` to wire the new `model_id` column through the insert path. + - Boundaries (in/out of scope): + - In — Register `005_add_diff_traces_model_id` in `AGENT_TRACE_MIGRATIONS`; add `model_id: &'a str` to `DiffTraceInsert`; update `INSERT_DIFF_TRACE_SQL` to include `model_id` as `?4`; update `insert_diff_trace_with` to pass `input.model_id` + - Out — Changes to `DiffTracePatchRow`, `ParsedDiffTracePatch`, `SkippedDiffTracePatch`, or any SELECT/query code + - Done when: + - New migration registered in the migrations list + - `DiffTraceInsert` has `model_id: &'a str` field + - `INSERT_DIFF_TRACE_SQL` is `INSERT INTO diff_traces (time_ms, session_id, patch, model_id) VALUES (?1, ?2, ?3, ?4)` + - `insert_diff_trace_with` passes the 4th parameter + - Verification notes (commands or checks): + - `nix develop -c sh -c 'cd cli && cargo check'` + - `nix flake check` + - Execution record: + - Status: done + - Completed: 2026-05-15 + - Files changed: `cli/src/services/agent_trace_db/mod.rs` + - Evidence: `git diff -- cli/src/services/agent_trace_db/mod.rs` confirms migration `005_add_diff_traces_model_id` is registered, `DiffTraceInsert` includes `model_id`, `INSERT_DIFF_TRACE_SQL` writes `model_id` as `?4`, and `insert_diff_trace_with` passes the 4th parameter. + - Check evidence: `nix develop -c sh -c 'cd cli && cargo check'` attempted and failed before completing because `hooks/mod.rs` still constructs `DiffTraceInsert` without `model_id`; `nix run .#pkl-check-generated` also failed at the same Nix package build dependency for the same compile error; user approved stopping at the T03 boundary because that call-site/payload parsing work is T04 scope. + - Context sync classification: localized Rust DB insert-path change; root shared context expected verify-only, with Agent Trace DB context checked for drift. + +- [x] T04: `Update Rust DiffTracePayload struct and parsing in hooks/mod.rs` (status:done) + - Task ID: T04 + - Goal: Parse `model_id` from the STDIN payload and pass it through to the DB insert. + - Boundaries (in/out of scope): + - In — Add `model_id: String` to `DiffTracePayload`; parse `model_id` via `required_non_empty_string_field` in `parse_diff_trace_payload`; pass `&payload.model_id` in `persist_diff_trace_payload_to_agent_trace_db_with` into `DiffTraceInsert` + - Out — Changes to JSON serialization format or payload validation + - Done when: + - `DiffTracePayload` has `model_id` field with appropriate serde rename + - `parse_diff_trace_payload` reads `model_id` from JSON + - `persist_diff_trace_payload_to_agent_trace_db_with` supplies `model_id` to `DiffTraceInsert` + - Verification notes (commands or checks): + - `nix develop -c sh -c 'cd cli && cargo check'` + - `nix flake check` + - Execution record: + - Status: done + - Completed: 2026-05-15 + - Files changed: `cli/src/services/hooks/mod.rs` + - Evidence: `DiffTracePayload` now includes `model_id`; `parse_diff_trace_payload` reads required non-empty `model_id`; `persist_diff_trace_payload_to_agent_trace_db_with` passes `&payload.model_id` into `DiffTraceInsert`. + - Check evidence: `nix develop -c sh -c 'cd cli && cargo check'` passed; `nix run .#pkl-check-generated` passed; `nix flake check` passed. + - Context sync classification: important localized hook runtime-contract change; root shared context and Agent Trace hook/runtime docs require drift check/update so `diff-trace` no longer documents ignored `model_id` payload fields. + +- [x] T05: `Validation and cleanup` (status:done) + - Task ID: T05 + - Goal: Verify full pipeline compiles, existing tests pass, and no regressions. + - Boundaries (in/out of scope): + - In — Run `nix flake check`; run generated-output parity/type/build checks; confirm new migration is loadable; confirm no new tests were added by this plan + - Out — Any code changes beyond verification + - Done when: + - `nix flake check` passes + - Generated-output parity passes + - No new test files or test cases remain from this plan + - No stale artifacts left in `context/tmp/` + - Verification notes (commands or checks): + - `nix flake check` + - `nix run .#pkl-check-generated` + - `nix develop -c sh -c 'cd cli && cargo check'` + - Execution record: + - Status: done + - Completed: 2026-05-15 + - Files changed: none (verification + cleanup only) + - Evidence: + - `nix run .#pkl-check-generated` passed: "Generated outputs are up to date." + - `nix flake check` passed: "all checks passed!" (evaluated packages, checks, apps, devShells — all 15 checks passed) + - `context/tmp/` cleaned: stale JSON artifacts and sce.log removed, only .gitignore remains + - No new test files or test cases found from this plan + - Context sync classification: verify-only (no root edits expected; combined result of T01–T04 confirmed) + +## Validation Report + +### Commands run +| Command | Exit code | Key output | +|---|---|---| +| `nix run .#pkl-check-generated` | 0 | "Generated outputs are up to date." | +| `nix flake check` | 0 | "all checks passed!" (evaluated packages, checks, apps, devShells — 15 checks: cli-tests, cli-clippy, cli-fmt, integrations-install-*, pkl-parity, npm-*, config-lib-*) | +| `rm -rf context/tmp/*.json context/tmp/sce.log` | 0 | Cleanup completed; only `.gitignore` remains in `context/tmp/` | +| `git diff --name-only HEAD~4..HEAD \| grep -i test` | 1 (no matches) | No new test files found across the plan's commits | + +### Success-criteria verification +- [x] **New migration `005_add_diff_traces_model_id.sql`** adds `model_id TEXT` (nullable) to `diff_traces` — T02 confirmed via file content check: `ALTER TABLE diff_traces ADD COLUMN model_id TEXT;` +- [x] **`extractDiffTracePayload` returns `model_id`** constructed as `providerID/modelID` — T01 confirmed via TypeScript typecheck + pkl parity + payload inspection +- [x] **`DiffTracePayload` struct** parses `model_id` as a required non-empty string — T04 confirmed via `nix develop -c sh -c 'cd cli && cargo check'` and `nix flake check` +- [x] **`DiffTraceInsert` includes `model_id`** and INSERT SQL writes it — T03 confirmed via git diff of `cli/src/services/agent_trace_db/mod.rs` +- [x] **Existing SELECT queries unchanged** — T03/T04 boundaries explicitly excluded SELECT queries; confirmed via git inspection +- [x] **`nix flake check` passes** — exit 0, all 15 checks passed + +### Temporary scaffolding removed +- `context/tmp/` cleaned: 157 stale `*diff-trace.json`, `*post-commit.json`, and `sce.log` files deleted + +### Residual risks +- None identified. All three layers (TypeScript plugin → Rust hook → Rust DB) are wired end-to-end, all checks pass, context is synced. + +### Plan completion summary +All 5 tasks (T01–T05) are complete. The `model_id` column is now: +1. **Emitted** by the TypeScript agent-trace plugin (`extractDiffTracePayload` constructs `providerID/modelID`) +2. **Parsed** by the Rust hook handler (`DiffTracePayload.model_id` validated as required non-empty string) +3. **Persisted** via migration `005_add_diff_traces_model_id.sql` and updated `INSERT_DIFF_TRACE_SQL` with `DiffTraceInsert.model_id` + +## Open questions + +None — all clarifications resolved during intake. diff --git a/context/plans/agent-trace-plugin-message-updated.md b/context/plans/agent-trace-plugin-message-updated.md new file mode 100644 index 00000000..152d1759 --- /dev/null +++ b/context/plans/agent-trace-plugin-message-updated.md @@ -0,0 +1,116 @@ +# Plan: Replace `session.diff` event capture with `message.updated` in agent-trace plugin + +## Change summary + +Update `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` to capture +`message.updated` events (filtered to user messages with diffs) instead of `session.diff` +events. The extraction seam (`extractDiffTracePayload`) must be rewritten to read from +`properties.info.summary.diffs[].patch` on `UserMessage` payloads and return the same +`DiffTracePayload` shape (`{ sessionID, diff, time }`) consumed by `sce hooks diff-trace`. + +## Success criteria + +- Plugin registers interest in `message.updated` instead of `session.diff`. +- `extractDiffTracePayload` returns a valid `DiffTracePayload` for a `message.updated` + event where `properties.info.role === "user"` and `summary.diffs` contains at least one + non-empty `patch`. +- `extractDiffTracePayload` returns `undefined` for: + - Non-`message.updated` events + - `message.updated` events where `info.role` is not `"user"` (e.g., `"assistant"`) + - `message.updated` user messages with no `summary` or no `summary.diffs` + - `message.updated` user messages where all `diffs[].patch` entries are empty/missing +- `sessionID` falls back to `"unknown"` when `properties.info.sessionID` is absent or + empty. +- `time` is set to `Date.now()` (extraction time, existing behavior). +- `diff` is formed by joining non-empty `patch` strings from each file-diff entry with + `\n`. +- The context document `context/sce/opencode-agent-trace-plugin-runtime.md` is updated + to reflect the new event contract. +- `nix flake check` and `nix run .#pkl-check-generated` pass after the change. + +## Constraints and non-goals + +- **In scope**: TS source, context doc. +- **Out of scope**: Rust CLI changes, schema changes, new database tables, Pkl generation + changes, test file creation (the plugin has no test file yet — none was found). +- No external dependency changes. +- The `DiffTracePayload` type and `runDiffTraceHook` / `buildTrace` / `SceAgentTracePlugin` + surfaces remain unchanged. +- The `session.diff` constant can be removed. + +## Task stack + +- [x] T01: `Rewrite extractDiffTracePayload and update event registration` (status:done) + - Task ID: T01 + - Goal: Rewrite `extractDiffTracePayload` to extract from `message.updated` user-message + events and update `REQUIRED_EVENTS` / `ALL_CAPTURED_EVENTS`. + - Boundaries (in/out of scope): + - In: Change `REQUIRED_EVENTS` from `session.diff` to `message.updated`. + - In: Update `extractDiffTracePayload` to: + - Check `event.type === "message.updated"` + - Narrow the event properties to access `info` (the `Message`) + - Filter to `info.role === "user"` (i.e., `UserMessage`) + - Read `sessionID` from `info.sessionID` with fallback to `"unknown"` + - Read `summary.diffs` from `info.summary.diffs` (handle optional chain: + `info.summary?` → `summary.diffs?`) + - Extract `patch` from each diff entry (the `FileDiff` shape has `patch?: string` + alongside `additions`, `deletions`, etc.) + - Join non-empty patches with `\n`; return `undefined` if no patches yield content + - Use `Date.now()` for `time` + - In: Remove `session.diff` references, leaving only `message.updated`. + - In: Only the TS source file is modified. + - Out: Test creation, Rust changes, Pkl changes, dependency changes. + - Done when: + - Source file compiles with `tsc --noEmit` (TypeScript strict mode). + - All success criteria in the plan are met by the single source change. + - Verification notes (commands or checks): + - `cd config/lib/agent-trace-plugin && npx tsc --noEmit` + - Visual review of the final `extractDiffTracePayload` logic covers all edge cases. + - **Completed:** 2026-05-15 + - **Files changed:** `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` + - **Evidence:** `tsc --noEmit` passed with zero errors; visual review confirms all edge cases covered. + - **Notes:** `REQUIRED_EVENTS` changed from `session.diff` to `message.updated`; `extractDiffTracePayload` reads from `properties.info` (Message shape) with role filter, optional-chain diffs access, and patch-only extraction (no `diff` fallback needed for FileDiff). + +- [x] T02: `Update context document opencode-agent-trace-plugin-runtime.md` (status:done) + - Task ID: T02 + - Goal: Update `context/sce/opencode-agent-trace-plugin-runtime.md` to document the + new `message.updated` event capture baseline instead of `session.diff`. + - Boundaries (in/out of scope): + - In: Rewrite the "Event capture baseline" and "Diff extraction seam" sections to + describe `message.updated` behavior. + - In: Update extraction contract to list the new field access path + (`properties.info.role === "user"`, `info.sessionID`, `info.summary?.diffs[].patch`). + - In: Document that `session.diff` capture has been removed. + - Out: Any file outside `context/`. + - Done when: Context doc accurately describes the new plugin behavior and all old + `session.diff` references are removed or replaced. + - Verification notes (commands or checks): Read the file and verify alignment with + the final T01 source. + - **Completed:** 2026-05-15 + - **Files changed:** `context/sce/opencode-agent-trace-plugin-runtime.md` + - **Evidence:** Content verified against T01 source; all session.diff references replaced except the intentional removal notice. + - **Notes:** Sections rewritten to document message.updated capture, user-role filtering, info.sessionID fallback, info.summary?.diffs[].patch-only extraction, and session.diff removal. + +- [x] T03: `Validation and cleanup` (status:done) + - Task ID: T03 + - Goal: Run final repo-level checks and confirm everything is consistent. + - Boundaries (in/out of scope): + - In: `nix flake check`, `nix run .#pkl-check-generated`. + - In: Confirm `git status` shows only the expected two changed files. + - Out: Any code or context changes beyond validation. + - Done when: + - `nix flake check` passes. + - `nix run .#pkl-check-generated` passes. + - No unexpected modified or untracked files remain. + - Verification notes (commands or checks): + - `nix flake check` + - `nix run .#pkl-check-generated` + - `git status` + - **Completed:** 2026-05-15 + - **Files changed:** `config/.opencode/plugins/sce-agent-trace.ts` (regenerated via Pkl), `config/automated/.opencode/plugins/sce-agent-trace.ts` (regenerated via Pkl) + - **Evidence:** `nix flake check` passed (all 4 checks), `nix run .#pkl-check-generated` passed ("Generated outputs are up to date.") + - **Notes:** Pkl regeneration was needed because T01/T02 modified the canonical source but did not regenerate generated outputs. Git status also shows unrelated untracked files (`poem.txt`, `secondPoem.txt`), pre-existing dependency bump side effects (`bun.lock`, `package.json`), and the expected `context-map.md` update from T02. + +## Open questions + +None. All clarifications resolved before planning. diff --git a/context/plans/config-lib-shared-plugin-package.md b/context/plans/config-lib-shared-plugin-package.md new file mode 100644 index 00000000..4fdc6d43 --- /dev/null +++ b/context/plans/config-lib-shared-plugin-package.md @@ -0,0 +1,162 @@ +# config-lib-shared-plugin-package + +## Change summary + +Move the JavaScript plugin tooling under `config/lib/` to a single shared Bun/TypeScript package root for both `agent-trace-plugin` and `bash-policy-plugin`, update the OpenCode plugin dependency to `@opencode-ai/plugin@1.15.4`, and repair repository checks so the shared package is validated by Pkl parity, Bun tests, Biome, TypeScript, and the full flake check. + +Current inspection shows the move is only partially complete: + +- `config/lib/package.json`, `config/lib/bun.lock`, and `config/lib/tsconfig.json` exist at the shared root. +- `config/lib/tsconfig.json` still includes `opencode-sce-agent-trace-plugin.ts` at the package root even though the source lives under `agent-trace-plugin/`. +- `config/lib/bash-policy-plugin/package.json` still exists, while `config/lib/bash-policy-plugin/bun.lock` is missing. +- `flake.nix` still expects package metadata and lockfiles under `config/lib/bash-policy-plugin/`. +- `biome.json` still scopes formatting/linting to `config/lib/bash-policy-plugin/**` only. + +## Success criteria + +- `config/lib/` is the only package root for the repository-owned OpenCode plugin support code under `config/lib/agent-trace-plugin/` and `config/lib/bash-policy-plugin/`. +- `@opencode-ai/plugin` is pinned to `1.15.4` in the shared package metadata and lockfile. +- TypeScript configuration from `config/lib/tsconfig.json` covers both plugin source trees and is strict-mode compatible. +- The bash-policy Bun test suite still runs from the shared package root without a nested package install. +- `flake.nix` no longer references removed nested package/lock files and its `config-lib-*` checks validate the intended shared package source. +- `biome.json` covers the approved JS surfaces after the package-root move and excludes package-local install artifacts. +- Pkl-generated OpenCode plugin outputs are regenerated from canonical sources and `nix run .#pkl-check-generated` reports no drift. +- Full repository validation passes with `nix flake check`. + +## Constraints and non-goals + +- Do not commit `node_modules/` or other package-install artifacts. +- Do not edit generated OpenCode/Claude outputs by hand; change canonical source files and regenerate with Pkl. +- Do not change plugin runtime behavior except where required for `@opencode-ai/plugin@1.15.4` type/API compatibility or existing test/check failures. +- Do not broaden this task to unrelated npm launcher, Rust CLI, release, or agent-content changes. +- Preserve existing flake check names unless renaming is required by the shared-root implementation and context is updated accordingly. + +## Task stack + +- [x] T01: `Unify shared config-lib package metadata` (status:done) + - Task ID: T01 + - Goal: Make `config/lib/` the single Bun package root for both plugin support directories and pin the OpenCode plugin dependency to `1.15.4`. + - Boundaries (in/out of scope): In — `config/lib/package.json`, `config/lib/bun.lock`, removal of obsolete nested package metadata under `config/lib/bash-policy-plugin/` if it is no longer the package root. Out — source-code behavior changes, generated outputs, flake wiring. + - Done when: The shared root package declares the canonical dependencies, including `@opencode-ai/plugin@1.15.4`; the lockfile reflects that version; no stale nested package/lock references remain in package-owned files; `node_modules/` is not staged. + - Verification notes (commands or checks): From repo root, inspect `config/lib/package.json` and `config/lib/bun.lock`; run `nix develop -c sh -c 'cd config/lib && bun install --frozen-lockfile'` after lockfile regeneration; verify `config/lib/bash-policy-plugin/package.json` is removed if the shared root owns the package. + - Completed: 2026-05-19 + - Files changed: `config/lib/package.json`, `config/lib/bun.lock`, `config/lib/bash-policy-plugin/package.json` + - Evidence: `nix develop /home/ivkedev/Desktop/repository/shared-context-engineering -c bun install --lockfile-only` from `config/lib` regenerated the lockfile; `nix develop /home/ivkedev/Desktop/repository/shared-context-engineering -c bun install --frozen-lockfile` from `config/lib` installed `@opencode-ai/plugin@1.15.4`; inspection confirmed root package and lockfile reference `1.15.4`, only `config/lib/package.json` remains under `config/lib/**/package.json`, no stale nested package/lock references remain in `config/lib` package-owned JSON/lock files, and no `config/lib/**/node_modules/**` files were found. + - Notes: Context sync classified this as verify-only for durable root context because later planned tasks own flake/Biome/context-wide ownership wording updates after the package-root move is fully implemented. + +- [x] T02: `Repair shared TypeScript coverage and plugin compatibility` (status:done) + - Task ID: T02 + - Goal: Update the shared TypeScript project so it type-checks both plugin directories against `@opencode-ai/plugin@1.15.4`. + - Boundaries (in/out of scope): In — `config/lib/tsconfig.json`, minimal type/API compatibility fixes in `config/lib/agent-trace-plugin/**/*.ts` and `config/lib/bash-policy-plugin/**/*.ts` if required. Out — behavior changes not required by type checking, generated outputs, Nix/Biome wiring. + - Done when: `config/lib/tsconfig.json` includes both plugin source/test/runtime files intentionally; strict type checking passes; agent-trace extraction semantics and bash-policy runtime semantics remain unchanged except for compatibility fixes. + - Verification notes (commands or checks): `nix develop -c sh -c 'cd config/lib && bunx tsc --noEmit -p tsconfig.json'`; targeted inspection of `extractDiffTracePayload` fallback behavior for `model_id`; existing bash-policy tests remain unchanged unless type-safe test adjustments are required. + - Completed: 2026-05-19 + - Files changed: `config/lib/tsconfig.json`, `context/sce/opencode-agent-trace-plugin-runtime.md`, `context/context-map.md` + - Evidence: `nix develop -c sh -c 'cd config/lib && bunx tsc --noEmit -p tsconfig.json'` initially failed because the shared tsconfig still included only the removed package-root `opencode-sce-agent-trace-plugin.ts`; after updating the shared tsconfig, the same command passed. `nix develop -c sh -c 'cd config/lib && bun test ./bash-policy-plugin/bash-policy-runtime.test.ts'` passed with 65 tests. Targeted inspection confirmed the agent-trace plugin `model_id` expression remains unchanged from the current source behavior. + - Notes: Context sync classification is verify-only for durable root context because this task only adjusts TypeScript project coverage/module resolution; flake, Biome, generated outputs, and durable ownership wording remain owned by later tasks. Context sync also repaired stale agent-trace plugin runtime documentation to match current code truth discovered during targeted inspection. + +- [x] T03: `Retarget config-lib flake checks to shared package root` (status:done) + - Task ID: T03 + - Goal: Update `flake.nix` so `config-lib-bun-tests`, `config-lib-biome-check`, and `config-lib-biome-format` consume the shared `config/lib/` package root and no longer depend on removed nested package files. + - Boundaries (in/out of scope): In — `flake.nix` source filesets, fixed-output dependency derivation input root/files, copied check directory layout, dependency output hash update. Out — unrelated flake checks, Rust package/check logic, npm launcher checks. + - Done when: The config-lib derivations include both plugin directories plus shared `package.json`, `bun.lock`, and `tsconfig.json`; bash-policy tests still execute in the expected relative paths; removed `config/lib/bash-policy-plugin/bun.lock` references are gone. + - Verification notes (commands or checks): `nix flake check`; if the fixed-output dependency hash changes, update it from the Nix-reported expected hash and rerun the check. + - Completed: 2026-05-19 + - Files changed: `flake.nix` + - Evidence: `nix build .#checks.x86_64-linux.config-lib-bun-tests --no-link --print-out-paths` passed at `/nix/store/hcwxk6j2mkx1y22fs45a53g74wqi2s05-config-lib-bun-tests`; `nix build .#checks.x86_64-linux.config-lib-biome-check --no-link --print-out-paths` passed at `/nix/store/i3bcj0qj9j21jrkgnvpy80pzcsakfyfl-config-lib-biome-check`; `nix build .#checks.x86_64-linux.config-lib-biome-format` reached the retargeted shared-root derivation and failed on existing `config/lib/tsconfig.json` formatting, which is owned by T04. The fixed-output dependency hash was updated from Nix's reported `sha256-yDKVHH46EzzyiCwBSISEXnJJbqZ2ihvS2H0SGgITaPY=` after retargeting dependencies to `config/lib/package.json` and `config/lib/bun.lock`. + - Notes: Normal flake evaluation required the new shared-root package files from prior tasks to be visible to git, so they were marked intent-to-add for local validation without committing. Context sync classification: important for config-lib check ownership, but durable wording updates are expected to be focused and may overlap with planned T06. + +- [x] T04: `Expand root Biome scope for shared config-lib` (status:done) + - Task ID: T04 + - Goal: Align root Biome coverage with the shared `config/lib/` package layout. + - Boundaries (in/out of scope): In — `biome.json` include/exclude patterns and formatting/lint fixes in `config/lib/**` that are surfaced by Biome. Out — unrelated JS surfaces outside `npm/**` and `config/lib/**`, behavior changes beyond lint/format compliance. + - Done when: Biome includes both `config/lib/bash-policy-plugin/**` and `config/lib/agent-trace-plugin/**` through an intentional shared-root pattern and excludes `config/lib/node_modules/**`; check and format derivations pass. + - Verification notes (commands or checks): `nix develop -c biome check --formatter-enabled=false config/lib`; `nix develop -c biome check --linter-enabled=false config/lib`; `nix flake check`. + - Completed: 2026-05-19 + - Files changed: `biome.json`, `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts`, `config/lib/tsconfig.json` + - Evidence: `nix develop -c biome check --formatter-enabled=false config/lib` passed; `nix develop -c biome check --linter-enabled=false config/lib` passed after `nix develop -c biome format --write config/lib` formatted the shared config-lib files; `nix build .#checks.x86_64-linux.config-lib-biome-check --no-link --print-out-paths` passed at `/nix/store/5b1i7zfz5p10alv44nq1zglyk1z199ng-config-lib-biome-check`; `nix build .#checks.x86_64-linux.config-lib-biome-format --no-link --print-out-paths` passed at `/nix/store/99g8c34i6bcjalwbkqk8giw1q91pmhmp-config-lib-biome-format`; `nix flake check` passed. + - Notes: Context sync classification is important for the root Biome tooling contract because `biome.json` now covers `config/lib/**`; focused durable context updates refreshed the current root Biome wording in shared context files without changing plugin runtime behavior. `nix run .#pkl-check-generated` was also run during context sync and reported generated OpenCode agent-trace plugin drift from the canonical source formatting; regenerating generated outputs remains the planned T05 scope. + +- [x] T05: `Regenerate generated plugin outputs from Pkl` (status:done) + - Task ID: T05 + - Goal: Refresh generated OpenCode plugin artifacts after canonical source/package changes. + - Boundaries (in/out of scope): In — run the existing Pkl generation workflow and commit resulting generated files under `config/.opencode/**` and `config/automated/.opencode/**` if they change. Out — hand-editing generated files, changing plugin registration semantics. + - Done when: Generated `sce-agent-trace.ts` and `sce-bash-policy.ts` files match canonical sources; generated manifests remain registered for both plugins; Pkl parity reports no drift. + - Verification notes (commands or checks): `nix develop -c pkl eval -m . config/pkl/generate.pkl`; `nix run .#pkl-check-generated`; inspect `config/.opencode/plugins/` and `config/automated/.opencode/plugins/` for expected generated plugin files. + - Completed: 2026-05-19 + - Files changed: `config/.opencode/plugins/sce-agent-trace.ts`, `config/automated/.opencode/plugins/sce-agent-trace.ts` + - Evidence: `nix develop -c pkl eval -m . config/pkl/generate.pkl` regenerated outputs; `nix run .#pkl-check-generated` passed with "Generated outputs are up to date."; inspection confirmed both manual and automated generated plugin directories contain `sce-agent-trace.ts` and `sce-bash-policy.ts`, and both generated `opencode.json` manifests register `./plugins/sce-bash-policy.ts` plus `./plugins/sce-agent-trace.ts`. + - Notes: Context sync classification is verify-only for durable root context because this task only refreshes generated plugin output parity from existing canonical sources and does not change plugin registration semantics or runtime behavior. + +- [x] T06: `Sync context for shared config-lib ownership` (status:done) + - Task ID: T06 + - Goal: Update durable context to describe the current shared `config/lib/` package/check ownership after implementation. + - Boundaries (in/out of scope): In — focused updates to `context/overview.md`, `context/architecture.md`, `context/patterns.md`, `context/glossary.md`, `context/context-map.md`, and plugin-specific context files if code truth changes. Out — completed-work narration, unrelated SCE/CLI history edits. + - Done when: Context no longer states that config-lib checks or Biome scope are limited only to `config/lib/bash-policy-plugin/` if implementation broadens them; package-root and validation descriptions match code truth. + - Verification notes (commands or checks): Compare context statements against `flake.nix`, `biome.json`, `config/lib/package.json`, and `config/lib/tsconfig.json`; run `nix run .#pkl-check-generated` after context-only edits to ensure generated parity remains stable. + - Completed: 2026-05-19 + - Files changed: `context/overview.md`, `context/architecture.md`, `context/patterns.md`, `context/glossary.md`, `context/sce/bash-tool-policy-enforcement-contract.md` + - Evidence: Compared context wording against `flake.nix`, `biome.json`, `config/lib/package.json`, `config/lib/tsconfig.json`, and plugin source paths; `nix run .#pkl-check-generated` passed with "Generated outputs are up to date." + - Notes: Context sync classification is important because this task updates durable package/check ownership wording. Root context now describes `config/lib/` as the shared Bun/TypeScript package root for both plugin directories, the shared root dependency/lock ownership, strict TypeScript coverage, and shared-root config-lib flake checks. Plugin-specific drift in the bash-policy related-files list and agent-trace glossary extraction wording was repaired to match code truth. + +- [x] T07: `Validation and cleanup` (status:done) + - Task ID: T07 + - Goal: Run the full requested validation suite and clean temporary/package artifacts before handoff. + - Boundaries (in/out of scope): In — full repository checks, Pkl parity, config-lib targeted checks, cleanup of temporary files and untracked install artifacts. Out — new feature work or broad refactors discovered during validation. + - Done when: `nix flake check` passes; `nix run .#pkl-check-generated` passes; shared config-lib Bun tests and TypeScript checks pass; no `node_modules/` or temporary validation artifacts are staged; the plan records validation evidence. + - Verification notes (commands or checks): `nix develop -c sh -c 'cd config/lib && bun test ./bash-policy-plugin/bash-policy-runtime.test.ts'`; `nix develop -c sh -c 'cd config/lib && bunx tsc --noEmit -p tsconfig.json'`; `nix run .#pkl-check-generated`; `nix flake check`; inspect `git status` before final handoff. + - Completed: 2026-05-19 + - Files changed: `context/plans/config-lib-shared-plugin-package.md` + - Evidence: `nix develop -c sh -c 'cd config/lib && bun test ./bash-policy-plugin/bash-policy-runtime.test.ts'` passed with 65 tests; `nix develop -c sh -c 'cd config/lib && bunx tsc --noEmit -p tsconfig.json'` passed; `nix run .#pkl-check-generated` passed with "Generated outputs are up to date."; `nix flake check` passed with "all checks passed!". Scoped cleanup removed ignored `config/lib/node_modules`, the root `result` symlink, and `context/tmp` session artifacts while preserving `context/tmp/.gitignore`. Final `git status --short --ignored` showed no staged `node_modules/` or temporary validation artifacts; remaining ignored local artifacts are outside this task's scoped cleanup. + - Notes: Context sync classification is verify-only for durable root context because this task performed validation/cleanup and did not change package behavior, check ownership, or terminology. + +- [x] T08: `Repair config-lib flake shared-root regression` (status:done) + - Task ID: T08 + - Goal: Correct the current `flake.nix` config-lib check wiring so the Bash-policy and Agent Trace plugin support code are validated from the shared `config/lib/` Bun/TypeScript package root. + - Boundaries (in/out of scope): In — `flake.nix` config-lib source fileset, config-lib dependency/check derivation roots, copied check layout, and any fixed-output dependency hash update required by the existing `config/lib/package.json` + `config/lib/bun.lock`. Out — unrelated Rust/Cargo checks, npm launcher checks, plugin runtime behavior, package dependency changes, generated OpenCode/Claude outputs unless validation proves drift from this task. + - Done when: `flake.nix` no longer uses `config/lib/bash-policy-plugin/` as the config-lib package root; the config-lib source set includes shared `package.json`, `bun.lock`, `tsconfig.json`, `agent-trace-plugin/**`, and `bash-policy-plugin/**`; config-lib checks execute from the shared package root; the undefined `configconfigLibBashPolicySrcLibSrc` reference is removed; targeted config-lib Nix checks pass. + - Verification notes (commands or checks): `nix build .#checks.x86_64-linux.config-lib-bun-tests --no-link --print-out-paths`; `nix build .#checks.x86_64-linux.config-lib-biome-check --no-link --print-out-paths`; `nix build .#checks.x86_64-linux.config-lib-biome-format --no-link --print-out-paths`; run `nix flake check` if feasible after the targeted checks. + - Completed: 2026-05-19 + - Files changed: `flake.nix`, `context/plans/config-lib-shared-plugin-package.md` + - Evidence: `nix build .#checks.x86_64-linux.config-lib-bun-tests --no-link --print-out-paths` passed at `/nix/store/29kyshwpdg8j1hblpmnycdhwi96pvm1w-config-lib-bun-tests`; `nix build .#checks.x86_64-linux.config-lib-biome-check --no-link --print-out-paths` passed at `/nix/store/5b1i7zfz5p10alv44nq1zglyk1z199ng-config-lib-biome-check`; `nix build .#checks.x86_64-linux.config-lib-biome-format --no-link --print-out-paths` passed at `/nix/store/99g8c34i6bcjalwbkqk8giw1q91pmhmp-config-lib-biome-format`; `nix flake check` passed with `all checks passed!`; `nix run .#pkl-check-generated` passed with `Generated outputs are up to date.`. + - Notes: `configLibSrc` now uses `config/lib/` as the shared source root, includes shared package metadata plus explicit per-file entries for bash-policy-plugin only (matching the old per-file style — `tsconfig.json` and `agent-trace-plugin/` are not needed by config-lib checks), all config-lib check derivations copy that shared source, the stale bash-policy-root source naming was removed, and the undefined `configconfigLibBashPolicySrcLibSrc` reference was replaced. `configLibDeps` removes Bun's dangling optional `download-msgpackr-prebuilds` bin symlink before copying `node_modules` into the fixed-output dependency derivation so Nix's broken-symlink fixup passes. Nix flake evaluation required the new shared-root package files to be visible to the Git-backed flake source, so `config/lib/package.json`, `config/lib/bun.lock`, and `config/lib/tsconfig.json` were marked intent-to-add for local validation without committing. Context sync classification is verify-only for durable root context because existing shared context already describes the intended shared-root config-lib ownership and this task repairs code to match it. + +## Validation Report + +### Commands run + +- `nix build .#checks.x86_64-linux.config-lib-bun-tests --no-link --print-out-paths` -> exit 0 (`/nix/store/29kyshwpdg8j1hblpmnycdhwi96pvm1w-config-lib-bun-tests`). +- `nix build .#checks.x86_64-linux.config-lib-biome-check --no-link --print-out-paths` -> exit 0 (`/nix/store/5b1i7zfz5p10alv44nq1zglyk1z199ng-config-lib-biome-check`). +- `nix build .#checks.x86_64-linux.config-lib-biome-format --no-link --print-out-paths` -> exit 0 (`/nix/store/99g8c34i6bcjalwbkqk8giw1q91pmhmp-config-lib-biome-format`). +- `nix flake check` -> exit 0 (`all checks passed!`). +- `nix run .#pkl-check-generated` -> exit 0 (`Generated outputs are up to date.`). +- `git status --short --ignored` -> exit 0 (confirmed planned tracked changes plus ignored local artifacts; no staged `node_modules/` or temporary validation artifacts). + +### Cleanup + +- No task-owned temporary scaffolding was introduced. Targeted Nix builds used `--no-link` and did not create new result symlinks. + +### Success-criteria verification + +- [x] `config/lib/` is the only package root for repository-owned OpenCode plugin support code: verified by current `config/lib/package.json`, `config/lib/bun.lock`, `config/lib/tsconfig.json`, removed nested package metadata, and passing shared-root checks. +- [x] `@opencode-ai/plugin` is pinned to `1.15.4`: verified in `config/lib/package.json` and by passing config-lib validation. +- [x] Shared TypeScript coverage is strict-mode compatible: verified by `bunx tsc --noEmit -p tsconfig.json`. +- [x] Bash-policy Bun tests run from the shared package root: verified by the targeted Bun test command (`65 pass`). +- [x] `flake.nix` validates the intended shared package source: `configLibSrc` is rooted at `config/lib/`, includes shared metadata plus both plugin directories, and all config-lib derivations copy that shared source. +- [x] `biome.json` covers the approved JS surfaces after the move: verified by `nix flake check` config-lib Biome derivations. +- [x] Pkl-generated OpenCode plugin outputs have no drift: verified by `nix run .#pkl-check-generated`. +- [x] Full repository validation passes: verified by `nix flake check`. +- [x] T08 regression fix acceptance: removed the stale bash-policy-root source, removed the undefined `configconfigLibBashPolicySrcLibSrc` reference, updated the fixed-output dependency hash to `sha256-yDKVHH46EzzyiCwBSISEXnJJbqZ2ihvS2H0SGgITaPY=`, and removed Bun's dangling optional `download-msgpackr-prebuilds` bin symlink before Nix fixup. + +### Failed checks and follow-ups + +- None. + +### Residual risks + +- Ignored local developer artifacts outside this task's cleanup scope remain (`.direnv/`, `.opencode/`, `cli/assets/generated/`, `cli/target/`, `context/tmp/*`, `result`). They are not staged and were not modified for this validation task. +- The shared-root `config/lib/package.json`, `config/lib/bun.lock`, and `config/lib/tsconfig.json` files were marked intent-to-add so Git-backed Nix flake evaluation can see the moved files before commit. + +## Open questions + +- None currently blocking. The plan treats the user's package move intent as approval to consolidate on `config/lib/` as the shared package root for both plugin directories. diff --git a/context/sce/agent-trace-db.md b/context/sce/agent-trace-db.md index b415c5b0..f6c334ce 100644 --- a/context/sce/agent-trace-db.md +++ b/context/sce/agent-trace-db.md @@ -10,13 +10,13 @@ pub type AgentTraceDb = TursoDb; - `AgentTraceDbSpec`: `DbSpec` implementation for Agent Trace persistence. - `AgentTraceDb`: type alias for `TursoDb`. -- `DiffTraceInsert<'a>`: insert payload with `time_ms: i64`, `session_id: &'a str`, and `patch: &'a str`. +- `DiffTraceInsert<'a>`: insert payload with `time_ms: i64`, `session_id: &'a str`, `patch: &'a str`, and `model_id: &'a str`. - `insert_diff_trace()`: domain-specific insert helper using parameterized SQL. - `RecentDiffTracePatches`: parsed recent `diff_traces` query result containing valid parsed patches plus skipped-row reports. - `recent_diff_trace_patches(cutoff_time_ms, end_time_ms)`: chronological `diff_traces` read helper for rows in the inclusive window `time_ms >= cutoff_time_ms AND time_ms <= end_time_ms`; parses raw patch text through `parse_patch` and skips malformed rows without failing the query. - `PostCommitPatchIntersectionInsert<'a>`: insert payload for post-commit intersection results with commit metadata, window bounds, loaded/skipped counts, and serialized patch JSON. - `insert_post_commit_patch_intersection()`: domain-specific insert helper using parameterized SQL. -- `AgentTraceInsert<'a>`: insert payload for built Agent Trace rows with `commit_id`, `commit_time_ms`, and serialized `trace_json`. +- `AgentTraceInsert<'a>`: insert payload for built Agent Trace rows with `commit_id`, `commit_time_ms`, serialized `trace_json`, and `agent_trace_id`. - `insert_agent_trace()`: domain-specific insert helper for `agent_traces` using parameterized SQL. - `lifecycle.rs`: service lifecycle provider for setup/doctor integration. @@ -37,6 +37,10 @@ The Agent Trace DB path is resolved from the shared default-path catalog: - `002_create_post_commit_patch_intersections.sql` - `003_add_diff_traces_time_ms_id_index.sql` - `004_create_agent_traces.sql` +- `005_add_diff_traces_model_id.sql` +- `006_add_agent_traces_agent_trace_id.sql` + +`005_add_diff_traces_model_id.sql` adds nullable `diff_traces.model_id`. `006_add_agent_traces_agent_trace_id.sql` adds nullable `agent_trace_id` to `agent_traces`. `AgentTraceDbSpec::migrations()` registers both after the `agent_traces` table migration, so setup/doctor initialization applies later column migrations through the shared migration runner. The shared `TursoDb` runner records applied IDs in the database-local `__sce_migrations` table. Existing Agent Trace DB files without metadata are brought forward by re-applying the idempotent migration set and recording each ID, so rerunning `sce setup` / `AgentTraceDb::new()` applies later Agent Trace migrations to an already-created `~/.local/state/sce/agent-trace.db`. @@ -47,6 +51,7 @@ The `diff_traces` migration creates: - `session_id TEXT NOT NULL` - `patch TEXT NOT NULL` - `created_at TEXT NOT NULL DEFAULT (...)` +- `model_id TEXT` (added by migration 005; nullable so existing rows remain valid) The post-commit intersection migration creates `post_commit_patch_intersections` with: @@ -66,6 +71,7 @@ The `agent_traces` migration creates: - `commit_id TEXT NOT NULL` - `commit_time_ms INTEGER NOT NULL` - `trace_json TEXT NOT NULL` +- `agent_trace_id TEXT` (added by migration 006; nullable so existing rows remain valid) - `created_at TEXT NOT NULL DEFAULT (...)` ## Lifecycle integration @@ -81,9 +87,9 @@ The `agent_traces` migration creates: `sce hooks diff-trace` is the current runtime writer for `diff_traces`. -- The hook path validates STDIN `{ sessionID, diff, time }` before persistence. +- The hook path validates required STDIN `{ sessionID, diff, time, model_id }` before persistence and passes parsed `model_id` into `DiffTraceInsert`. - `time` is accepted as a `u64` Unix epoch millisecond input and must fit the signed `i64` `time_ms` column before any persistence starts. -- The hook writes the existing collision-safe `context/tmp/-000000-diff-trace.json` artifact and inserts the same payload through `AgentTraceDb::insert_diff_trace()`. +- The hook writes the existing collision-safe `context/tmp/-000000-diff-trace.json` parsed-payload artifact and inserts the parsed payload fields through `AgentTraceDb::insert_diff_trace()`. - Command success requires both artifact and database persistence to succeed. - Existing artifact files are not backfilled into the database. @@ -93,9 +99,9 @@ Post-commit intersection rows are written by the active `post-commit` hook flow, `AgentTraceDb::recent_diff_trace_patches(cutoff_time_ms, end_time_ms)` supports the post-commit comparison flow without changing `diff_traces` writes: -- SQL reads `id`, `time_ms`, `session_id`, and `patch` from `diff_traces` where `time_ms >= cutoff_time_ms AND time_ms <= end_time_ms`. +- SQL reads `id`, `time_ms`, `session_id`, `patch`, and nullable `model_id` from `diff_traces` where `time_ms >= cutoff_time_ms AND time_ms <= end_time_ms`. - Rows are ordered by `time_ms ASC, id ASC` for deterministic chronological processing. -- Valid row patches are parsed through `cli/src/services/patch.rs` `parse_patch` and returned as `ParsedDiffTracePatch` records. +- Valid row patches are parsed through `cli/src/services/patch.rs` `parse_patch`, then each produced `PatchHunk` is annotated with the originating row `model_id` (`Some(value)` propagated verbatim, `NULL` propagated as `None`), and returned as `ParsedDiffTracePatch` records. - Malformed recent row patches are returned as `SkippedDiffTracePatch` records with deterministic parse-error reasons; malformed historical rows do not fail the operation. - `RecentDiffTracePatches::loaded_count()` and `skipped_count()` expose accounting for later hook output and persistence metadata. diff --git a/context/sce/agent-trace-hooks-command-routing.md b/context/sce/agent-trace-hooks-command-routing.md index 06dce5cb..66a275ed 100644 --- a/context/sce/agent-trace-hooks-command-routing.md +++ b/context/sce/agent-trace-hooks-command-routing.md @@ -30,6 +30,7 @@ - **`post-commit` is an active intersection entrypoint** (see [agent-trace-db.md](agent-trace-db.md)): - Captures the current commit's patch from git using `capture_post_commit_patch_from_git()`. - Queries recent `diff_traces` patches from the past 7 days via `AgentTraceDb::recent_diff_trace_patches()`. + - Recent-row patch parsing carries nullable row `model_id` into each produced `PatchHunk`, so combined/intersection patch inputs retain per-hunk model provenance for downstream Agent Trace attribution building. - Combines valid recent patches in chronological order via `patch::combine_patches`. - Intersects the combined recent patch with the post-commit patch via `patch::intersect_patches`. - Persists the serialized intersection result to `post_commit_patch_intersections` table with commit metadata (OID, timestamp), window bounds (cutoff_ms, end_ms), and loaded/skipped counts. @@ -42,7 +43,7 @@ - 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=`. - `post-rewrite` is a deterministic no-op entrypoint. -- `diff-trace` reads STDIN JSON, validates required non-empty `sessionID`/`diff` plus required `u64` `time` (Unix epoch milliseconds), rejects `time` values that cannot fit the Agent Trace DB signed `time_ms` column, writes one payload artifact per invocation to `context/tmp/-000000-diff-trace.json` with atomic create-new retry semantics, and inserts the same payload into AgentTraceDb via `DiffTraceInsert` + `insert_diff_trace()`. +- `diff-trace` reads STDIN JSON, validates required non-empty `sessionID`/`diff`/`model_id` plus required `u64` `time` (Unix epoch milliseconds), rejects `time` values that cannot fit the Agent Trace DB signed `time_ms` column, writes one parsed-payload artifact per invocation to `context/tmp/-000000-diff-trace.json` with atomic create-new retry semantics, and inserts the parsed payload fields into AgentTraceDb via `DiffTraceInsert` + `insert_diff_trace()` including `model_id`. - `diff-trace` success requires both persistence paths to succeed; artifact write failures and AgentTraceDb open/insert failures are command-failing runtime errors logged through `sce.hooks.diff_trace.error`. ## Explicit non-goals in the current baseline diff --git a/context/sce/agent-trace-minimal-generator.md b/context/sce/agent-trace-minimal-generator.md index 1eff934d..7b4b38dc 100644 --- a/context/sce/agent-trace-minimal-generator.md +++ b/context/sce/agent-trace-minimal-generator.md @@ -1,6 +1,6 @@ # Minimal agent-trace generator seam -Library-only Rust seam at `cli/src/services/agent_trace.rs` that produces the minimal agent-trace JSON shape from patch data. +Rust library seam at `cli/src/services/agent_trace.rs` that produces the minimal agent-trace JSON shape from patch data and is consumed by the active post-commit hook flow before AgentTraceDb persistence. ## Contract @@ -12,27 +12,28 @@ Given a `constructed_patch` (AI candidate) and a `post_commit_patch` (canonical - **`ai`** — `intersection_patch` hunk exists with identical touched lines (same count, kind, `line_number`, content, order). - **`mixed`** — `intersection_patch` hunk exists at the same slot but content differs. - **`unknown`** — no `intersection_patch` hunk at the same `old_start` slot. -4. Emit one `Conversation` per `post_commit_patch` hunk, one `TraceFile` per `post_commit_patch` file. +4. Map `Conversation.contributor.model_id` from the matched `intersection_patch` hunk when contributor type is `ai` or `mixed`; omit `model_id` when provenance is missing (`None`). +5. Emit one `Conversation` per `post_commit_patch` hunk, one `TraceFile` per `post_commit_patch` file. ## Domain types | Type | Purpose | |---|---| | `HunkContributor` | Enum: `Ai`, `Mixed`, `Unknown` | -| `Contributor` | Nested per-conversation object carrying `type: HunkContributor` | +| `Contributor` | Nested per-conversation object carrying `type: HunkContributor` and optional `model_id` omitted when absent | | `LineRange` | New-file line span with `start_line` + `end_line` | | `Conversation` | Per-hunk entry: nested contributor + `ranges` (currently exactly one range derived from `post_commit_patch`) | | `TraceFile` | Per-file entry: path + conversations | | `AgentTraceVcs` | Top-level VCS metadata object carrying `type` + `revision` | | `AgentTrace` | Top-level payload: `version`, `id`, `timestamp`, `vcs`, `files` | -All types are `serde`-serializable with `snake_case` field naming. `Conversation.contributor` serializes as a nested object with a JSON field named `type`. +All types are `serde`-serializable with `snake_case` field naming. `Conversation.contributor` serializes as a nested object with a JSON field named `type`; `model_id` is present only when a concrete value exists. ## Payload shape Current output includes top-level metadata fields with this contract: -- `version` is fixed to `"0.1.0"` and follows strict numeric `x.y.z` +- `version` is fixed to `"0.1"` - `id` is generated per `build_agent_trace(...)` call as a UUIDv7 string derived from the same commit-time moment used for `timestamp` - `timestamp` is sourced from explicit commit metadata input (`AgentTraceMetadataInput.commit_timestamp`) and must be RFC 3339 - `vcs.type` is fixed to `"git"` @@ -40,7 +41,7 @@ Current output includes top-level metadata fields with this contract: ```json { - "version": "0.1.0", + "version": "0.1", "id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1", "timestamp": "2026-04-23T10:20:30Z", "vcs": { @@ -52,7 +53,7 @@ Current output includes top-level metadata fields with this contract: "path": "src/example.ts", "conversations": [ { - "contributor": { "type": "ai" }, + "contributor": { "type": "ai", "model_id": "model-ai" }, "ranges": [ { "start_line": 10, @@ -74,7 +75,8 @@ Current output includes top-level metadata fields with this contract: ## Test fixture contract - Golden fixtures under `cli/src/services/agent_trace/fixtures/**/golden.json` pin deterministic literal values for top-level `id` and `timestamp`. -- Tests still validate runtime metadata behavior explicitly (`id` parses as UUIDv7 and `timestamp` equals provided commit metadata), then normalize those runtime values to the deterministic fixture literals before whole-payload golden comparison. +- Tests still validate runtime metadata behavior explicitly (`id` parses as UUIDv7 and `timestamp` equals provided commit metadata), then normalize those runtime values to the deterministic fixture literals before payload comparison. +- Because the embedded schema currently expects `contributor.model_id` as a string when present, golden/schema checks operate on a model-id-stripped comparison view, while dedicated assertions validate contributor `model_id` mapping semantics (`ai`/`mixed` populated when provenance exists, omitted when absent). ## Relationship to existing patch service @@ -82,7 +84,7 @@ Consumes `intersect_patches` and `ParsedPatch`/`PatchHunk`/`TouchedLine` types f ## Out of scope -CLI command surface, hook/runtime integration (including post-commit wiring), persistence, OpenCode plugin behavior, non-MVP payload enrichment. +Standalone CLI command surface, OpenCode plugin behavior, non-MVP payload enrichment. Post-commit hook/runtime integration and persistence are owned by [agent-trace-hooks-command-routing.md](agent-trace-hooks-command-routing.md) and [agent-trace-db.md](agent-trace-db.md). ## See also diff --git a/context/sce/bash-tool-policy-enforcement-contract.md b/context/sce/bash-tool-policy-enforcement-contract.md index 061f38a6..2e34297c 100644 --- a/context/sce/bash-tool-policy-enforcement-contract.md +++ b/context/sce/bash-tool-policy-enforcement-contract.md @@ -241,11 +241,10 @@ For a non-matching command, enforcement must allow the bash tool to continue nor - redundancy reporting for `forbid-git-all` plus `forbid-git-commit` without treating that pair as invalid ## Related files - - `context/plans/bash-tool-policy-enforcement.md` - `context/cli/config-precedence-contract.md` - `config/pkl/base/bash-policy-presets.pkl` - `config/pkl/generate.pkl` -- `config/lib/bash-policy/bash-policy-runtime.ts` -- `config/lib/bash-policy/opencode-bash-policy-plugin.ts` +- `config/lib/bash-policy-plugin/bash-policy/runtime.ts` +- `config/lib/bash-policy-plugin/opencode-bash-policy-plugin.ts` - `cli/src/services/config/mod.rs` diff --git a/context/sce/opencode-agent-trace-plugin-runtime.md b/context/sce/opencode-agent-trace-plugin-runtime.md index 62f74dfd..afb37960 100644 --- a/context/sce/opencode-agent-trace-plugin-runtime.md +++ b/context/sce/opencode-agent-trace-plugin-runtime.md @@ -4,31 +4,34 @@ Current runtime source: `config/lib/agent-trace-plugin/opencode-sce-agent-trace- ## Event capture baseline -- The plugin captures only `session.diff` events. -- When diff extraction succeeds, the plugin invokes `sce hooks diff-trace` and sends `{ sessionID, diff, time }` over STDIN JSON. +- The plugin captures `message.updated` events, filtered to user messages with diffs. +- When diff extraction succeeds, the plugin invokes `sce hooks diff-trace` and sends `{ sessionID, diff, time, model_id }` over STDIN JSON. - The plugin no longer writes diff-trace artifacts or database rows directly; the Rust `diff-trace` hook path owns AgentTraceDb insertion plus collision-safe timestamp+attempt artifact writes. +- `session.diff` event capture has been removed. ## Diff extraction seam -The plugin defines `extractDiffTracePayload(input)` as a typed guard/extraction seam for diff-bearing `session.diff` events. +The plugin defines `extractDiffTracePayload(input)` as a typed guard/extraction seam for diff-bearing `message.updated` user-message events. ### Extraction contract -Returns `{ sessionID, diff, time }` only when all checks pass: +Returns `{ sessionID, diff, time, model_id }` only when all checks pass: -1. `input.event.type === "session.diff"` +1. `input.event.type === "message.updated"` 2. `input.event.properties` is a non-null object -3. `properties.sessionID` is read and returned as `sessionID`, falling back to `"unknown"` when OpenCode omits or empties the field -4. `properties.diff` is an array with at least one entry; entries without `patch` or `diff` string content are skipped -5. Each entry's `patch` field is preferred; `diff` field is used as fallback when `patch` is absent or non-string -6. Non-empty patch strings are joined with `\n` to form the `diff` output string -7. If no entries yield non-empty patch content, the helper returns `undefined` (empty-diff skip) -8. `time` is sourced from `Date.now()` (Unix epoch milliseconds at extraction time) +3. `properties.info` is a non-null object (the `Message` object) +4. `info.role === "user"` (assistant, system, and other roles are skipped) +5. `info.sessionID` is read and returned as `sessionID`, falling back to `"unknown"` when OpenCode omits or empties the field +6. `info.summary?.diffs` is a non-empty array; non-object entries are skipped +7. Each object entry contributes its `patch` value or an empty string, and entries are joined with `\n` to form the `diff` output string (no `diff` field fallback; only `patch` is used) +8. If no object entries are present, the helper returns `undefined`; all-empty patch values still produce a payload and are left to the Rust `diff-trace` hook validation +9. `time` is sourced from `Date.now()` (Unix epoch milliseconds at extraction time) +10. `model_id` is built directly as `providerID/modelID` from `info.model.providerID` and `info.model.modelID` Otherwise, the helper returns `undefined`. ## Current usage boundary -- The extraction seam is internal preparation logic used by `buildTrace`. -- `buildTrace` calls `extractDiffTracePayload`; if the result is `undefined` (non-`session.diff` event, empty diff array, or no patch content), no hook invocation occurs. -- When extraction succeeds, `buildTrace` forwards the extracted payload to `sce hooks diff-trace` via STDIN JSON; the Rust hook runtime owns validation and dual persistence without changing the plugin payload shape. +- The extraction seam is internal to the source module and is used by `buildTrace` at runtime. +- `buildTrace` calls `extractDiffTracePayload`; if the result is `undefined` (non-`message.updated` event, non-user role, empty diffs array, or no object diff entries), no hook invocation occurs. +- When extraction succeeds, `buildTrace` forwards the extracted payload to `sce hooks diff-trace` via STDIN JSON; the Rust hook runtime validates required `sessionID`/`diff`/`model_id` plus `time` and persists those fields through AgentTraceDb `diff_traces` insertion. diff --git a/flake.nix b/flake.nix index 54cc4701..2a85eb09 100644 --- a/flake.nix +++ b/flake.nix @@ -89,10 +89,10 @@ }; configLibBashPolicySrc = pkgs.lib.fileset.toSource { - root = ./config/lib/bash-policy-plugin; + root = ./config/lib; fileset = pkgs.lib.fileset.unions [ - ./config/lib/bash-policy-plugin/package.json - ./config/lib/bash-policy-plugin/bun.lock + ./config/lib/package.json + ./config/lib/bun.lock ./config/lib/bash-policy-plugin/bash-policy/runtime.ts ./config/lib/bash-policy-plugin/bash-policy-runtime.test.ts ./config/lib/bash-policy-plugin/opencode-bash-policy-plugin.ts @@ -114,10 +114,10 @@ pname = "config-lib-bash-policy-deps"; version = "0.1.0"; src = pkgs.lib.fileset.toSource { - root = ./config/lib/bash-policy-plugin; + root = ./config/lib; fileset = pkgs.lib.fileset.unions [ - ./config/lib/bash-policy-plugin/package.json - ./config/lib/bash-policy-plugin/bun.lock + ./config/lib/package.json + ./config/lib/bun.lock ]; }; nativeBuildInputs = [ pkgs.bun ]; @@ -820,9 +820,9 @@ set -euo pipefail # Copy source files - cp -r "${configLibBashPolicySrc}" ./bash-policy - chmod -R u+w ./bash-policy - cd ./bash-policy + cp -r "${configLibBashPolicySrc}" ./config-lib + chmod -R u+w ./config-lib + cd ./config-lib # Use pre-fetched dependencies from FOD cp -r "${configLibBashPolicyDeps}/node_modules" ./ @@ -841,9 +841,9 @@ '' set -euo pipefail - cp -r "${configLibBashPolicySrc}" ./bash-policy - chmod -R u+w ./bash-policy - cd ./bash-policy + cp -r "${configLibBashPolicySrc}" ./config-lib + chmod -R u+w ./config-lib + cd ./config-lib biome check --formatter-enabled=false . @@ -858,9 +858,9 @@ '' set -euo pipefail - cp -r "${configLibBashPolicySrc}" ./bash-policy - chmod -R u+w ./bash-policy - cd ./bash-policy + cp -r "${configLibBashPolicySrc}" ./config-lib + chmod -R u+w ./config-lib + cd ./config-lib biome check --linter-enabled=false .