diff --git a/.opencode/plugins/sce-agent-trace.ts b/.opencode/plugins/sce-agent-trace.ts index e20bbe0..abfae8f 100644 --- a/.opencode/plugins/sce-agent-trace.ts +++ b/.opencode/plugins/sce-agent-trace.ts @@ -3,7 +3,7 @@ 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; @@ -15,13 +15,14 @@ type DiffTracePayload = { sessionID: string; diff: string; time: number; + model_id: string; }; function extractDiffTracePayload( input: TraceInput, ): DiffTracePayload | undefined { const event = input.event; - if (event === undefined || event.type !== "session.diff") { + if (event === undefined || event.type !== "message.updated") { return undefined; } @@ -30,15 +31,36 @@ function extractDiffTracePayload( return undefined; } - const propertiesObj = properties as Record; + 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 propertiesObj.sessionID === "string" && - propertiesObj.sessionID.trim().length > 0 - ? propertiesObj.sessionID + typeof infoObj.sessionID === "string" && + infoObj.sessionID.trim().length > 0 + ? infoObj.sessionID : "unknown"; - const diffEntries = propertiesObj.diff; + 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; } @@ -48,16 +70,10 @@ function extractDiffTracePayload( 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); - } + const entryObj = entry as {patch?:string}; + const patch = entryObj.patch || ""; + + patches.push(patch); } if (patches.length === 0) { @@ -68,6 +84,7 @@ function extractDiffTracePayload( sessionID, diff: patches.join("\n"), time: Date.now(), + model_id: `${model.providerID}/${model.modelID}`, }; } 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 0000000..6d3e96a --- /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/src/services/agent_trace_db/mod.rs b/cli/src/services/agent_trace_db/mod.rs index 5ce8936..3f711cb 100644 --- a/cli/src/services/agent_trace_db/mod.rs +++ b/cli/src/services/agent_trace_db/mod.rs @@ -20,6 +20,8 @@ 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 AGENT_TRACE_MIGRATIONS: &[(&str, &str)] = &[ ("001_create_diff_traces", CREATE_DIFF_TRACES_MIGRATION), @@ -32,11 +34,15 @@ 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, + ), ]; /// 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 @@ -86,6 +92,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. @@ -188,7 +195,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), ) } @@ -365,6 +372,7 @@ mod tests { time_ms, session_id, patch, + model_id: "test-provider/test-model", }, ) .expect("diff trace insert should succeed"); @@ -510,6 +518,7 @@ 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", ] ); } diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index ce17cef..54793a3 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, }) } diff --git a/config/.opencode/plugins/sce-agent-trace.ts b/config/.opencode/plugins/sce-agent-trace.ts index e20bbe0..abfae8f 100644 --- a/config/.opencode/plugins/sce-agent-trace.ts +++ b/config/.opencode/plugins/sce-agent-trace.ts @@ -3,7 +3,7 @@ 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; @@ -15,13 +15,14 @@ type DiffTracePayload = { sessionID: string; diff: string; time: number; + model_id: string; }; function extractDiffTracePayload( input: TraceInput, ): DiffTracePayload | undefined { const event = input.event; - if (event === undefined || event.type !== "session.diff") { + if (event === undefined || event.type !== "message.updated") { return undefined; } @@ -30,15 +31,36 @@ function extractDiffTracePayload( return undefined; } - const propertiesObj = properties as Record; + 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 propertiesObj.sessionID === "string" && - propertiesObj.sessionID.trim().length > 0 - ? propertiesObj.sessionID + typeof infoObj.sessionID === "string" && + infoObj.sessionID.trim().length > 0 + ? infoObj.sessionID : "unknown"; - const diffEntries = propertiesObj.diff; + 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; } @@ -48,16 +70,10 @@ function extractDiffTracePayload( 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); - } + const entryObj = entry as {patch?:string}; + const patch = entryObj.patch || ""; + + patches.push(patch); } if (patches.length === 0) { @@ -68,6 +84,7 @@ function extractDiffTracePayload( sessionID, diff: patches.join("\n"), time: Date.now(), + model_id: `${model.providerID}/${model.modelID}`, }; } diff --git a/config/automated/.opencode/plugins/sce-agent-trace.ts b/config/automated/.opencode/plugins/sce-agent-trace.ts index e20bbe0..abfae8f 100644 --- a/config/automated/.opencode/plugins/sce-agent-trace.ts +++ b/config/automated/.opencode/plugins/sce-agent-trace.ts @@ -3,7 +3,7 @@ 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; @@ -15,13 +15,14 @@ type DiffTracePayload = { sessionID: string; diff: string; time: number; + model_id: string; }; function extractDiffTracePayload( input: TraceInput, ): DiffTracePayload | undefined { const event = input.event; - if (event === undefined || event.type !== "session.diff") { + if (event === undefined || event.type !== "message.updated") { return undefined; } @@ -30,15 +31,36 @@ function extractDiffTracePayload( return undefined; } - const propertiesObj = properties as Record; + 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 propertiesObj.sessionID === "string" && - propertiesObj.sessionID.trim().length > 0 - ? propertiesObj.sessionID + typeof infoObj.sessionID === "string" && + infoObj.sessionID.trim().length > 0 + ? infoObj.sessionID : "unknown"; - const diffEntries = propertiesObj.diff; + 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; } @@ -48,16 +70,10 @@ function extractDiffTracePayload( 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); - } + const entryObj = entry as {patch?:string}; + const patch = entryObj.patch || ""; + + patches.push(patch); } if (patches.length === 0) { @@ -68,6 +84,7 @@ function extractDiffTracePayload( sessionID, diff: patches.join("\n"), time: Date.now(), + model_id: `${model.providerID}/${model.modelID}`, }; } diff --git a/config/lib/agent-trace-plugin/bun.lock b/config/lib/agent-trace-plugin/bun.lock index b490cef..4569257 100644 --- a/config/lib/agent-trace-plugin/bun.lock +++ b/config/lib/agent-trace-plugin/bun.lock @@ -4,16 +4,30 @@ "workspaces": { "": { "dependencies": { - "@opencode-ai/plugin": "1.3.0", + "@opencode-ai/plugin": "1.14.28", "@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=="], + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], - "@opencode-ai/sdk": ["@opencode-ai/sdk@1.3.0", "", {}, "sha512-5WyYEpcV6Zk9otXOMIrvZRbJm1yxt/c8EXSBn1p6Sw1yagz8HRljkoUTJFxzD0x2+/6vAZItr3OrXDZfE+oA2g=="], + "@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.14.28", "", { "dependencies": { "@opencode-ai/sdk": "1.14.28", "effect": "4.0.0-beta.48", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.1.105", "@opentui/solid": ">=0.1.105" }, "optionalPeers": ["@opentui/core", "@opentui/solid"] }, "sha512-cHJo7t1jwrzbkIVmNgggdWh4cyOVGw5fnbSpuYeL6qwfmH3g/6YLWtw5ZYEP6detUkEebT08mHXDGmsMUpQa+A=="], + + "@opencode-ai/sdk": ["@opencode-ai/sdk@1.14.28", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-qRFJfD+Zdz3jHHSupW4F6Io1ZFrQ6gCRFlG50O6kEU9xRxrBpK0wGvP+Y5VwwvD/gH9WKMHYinlQpDVI9/lgJQ=="], + + "@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=="], @@ -21,8 +35,48 @@ "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.48", "", { "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-MMAM/ZabuNdNmgXiin+BAanQXK7qM8mlt7nfXDoJ/Gn9V8i89JlCq+2N0AiWmqFLXjGLA0u3FjiOjSOYQk5uMw=="], + + "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/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts b/config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts index e20bbe0..abfae8f 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,7 +3,7 @@ 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; @@ -15,13 +15,14 @@ type DiffTracePayload = { sessionID: string; diff: string; time: number; + model_id: string; }; function extractDiffTracePayload( input: TraceInput, ): DiffTracePayload | undefined { const event = input.event; - if (event === undefined || event.type !== "session.diff") { + if (event === undefined || event.type !== "message.updated") { return undefined; } @@ -30,15 +31,36 @@ function extractDiffTracePayload( return undefined; } - const propertiesObj = properties as Record; + 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 propertiesObj.sessionID === "string" && - propertiesObj.sessionID.trim().length > 0 - ? propertiesObj.sessionID + typeof infoObj.sessionID === "string" && + infoObj.sessionID.trim().length > 0 + ? infoObj.sessionID : "unknown"; - const diffEntries = propertiesObj.diff; + 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; } @@ -48,16 +70,10 @@ function extractDiffTracePayload( 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); - } + const entryObj = entry as {patch?:string}; + const patch = entryObj.patch || ""; + + patches.push(patch); } if (patches.length === 0) { @@ -68,6 +84,7 @@ function extractDiffTracePayload( sessionID, diff: patches.join("\n"), time: Date.now(), + model_id: `${model.providerID}/${model.modelID}`, }; } diff --git a/config/lib/agent-trace-plugin/package.json b/config/lib/agent-trace-plugin/package.json index 79a35a9..6d4df72 100644 --- a/config/lib/agent-trace-plugin/package.json +++ b/config/lib/agent-trace-plugin/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "@opencode-ai/plugin": "1.3.0", + "@opencode-ai/plugin": "1.14.28", "@types/bun": "1.3.11", "@types/node": "25.5.0" } diff --git a/context/architecture.md b/context/architecture.md index cc83e96..b735496 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,7 +114,7 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/doctor/mod.rs` owns the current doctor request/report surface while focused submodules (`doctor/inspect.rs`, `doctor/render.rs`, `doctor/fixes.rs`, `doctor/types.rs`) split report fact collection, rendering, manual fix reporting, and doctor-owned domain types into smaller seams; `cli/src/services/doctor/command.rs` owns `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. diff --git a/context/cli/cli-command-surface.md b/context/cli/cli-command-surface.md index bd2bed7..e752eff 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/context-map.md b/context/context-map.md index 636b144..97f52f1 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -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`, and nullable `diff_traces.model_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, 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`, 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-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 `sessionID`/`diff`/`time`/`model_id` validation and 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, `info.model.providerID/modelID` fallback to `unknown/unknown`, empty-diff skip, 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 cf7be2a..d2662ec 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -29,8 +29,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`, and nullable `diff_traces.model_id`, provides typed parameterized insert helpers for diff traces including `model_id`, 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`, `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 +89,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 +107,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. @@ -150,6 +150,6 @@ - `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 currently carries `commit_timestamp`, an RFC 3339 commit-time value required by `build_agent_trace` and used as `AgentTrace.timestamp` - `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. +- `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 non-empty `patch` fields from `info.summary?.diffs[].patch` entries into a single `diff` string, uses `Date.now()` for `time`, and builds `model_id` as `info.model.providerID/info.model.modelID` with missing or empty components falling back to `"unknown"`; returns `undefined` for non-`message.updated` events, non-user messages, messages without `summary.diffs`, or when all diffs have empty patches. +- `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 1b4f568..acf24ac 100644 --- a/context/overview.md +++ b/context/overview.md @@ -23,7 +23,7 @@ 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. @@ -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 e5c1dd1..365eba0 100644 --- a/context/patterns.md +++ b/context/patterns.md @@ -133,7 +133,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-diff-traces-model-id.md b/context/plans/add-diff-traces-model-id.md new file mode 100644 index 0000000..ab6a586 --- /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 0000000..152d175 --- /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/sce/agent-trace-db.md b/context/sce/agent-trace-db.md index b415c5b..3c6a933 100644 --- a/context/sce/agent-trace-db.md +++ b/context/sce/agent-trace-db.md @@ -10,7 +10,7 @@ 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. @@ -37,6 +37,9 @@ 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` + +`005_add_diff_traces_model_id.sql` adds nullable `diff_traces.model_id`. `AgentTraceDbSpec::migrations()` registers it after the `agent_traces` table migration, so setup/doctor initialization applies the model-id column migration 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 +50,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: @@ -81,9 +85,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. diff --git a/context/sce/agent-trace-hooks-command-routing.md b/context/sce/agent-trace-hooks-command-routing.md index 06dce5c..61d9f42 100644 --- a/context/sce/agent-trace-hooks-command-routing.md +++ b/context/sce/agent-trace-hooks-command-routing.md @@ -42,7 +42,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/opencode-agent-trace-plugin-runtime.md b/context/sce/opencode-agent-trace-plugin-runtime.md index 62f74df..80c9972 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; entries without `patch` string content are skipped +7. Non-empty `patch` strings are joined with `\n` to form the `diff` output string (no `diff` field fallback; only `patch` is used) +8. If no entries yield non-empty patch content, the helper returns `undefined` (empty-diff skip) +9. `time` is sourced from `Date.now()` (Unix epoch milliseconds at extraction time) +10. `model_id` is built as `providerID/modelID` from `info.model.providerID` and `info.model.modelID`, with each missing or empty component falling back to `"unknown"` 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 exported from the source module for focused Bun unit coverage and is used by `buildTrace` at runtime. +- `buildTrace` calls `extractDiffTracePayload`; if the result is `undefined` (non-`message.updated` event, non-user role, empty diffs, 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 validates required `sessionID`/`diff`/`model_id` plus `time` and persists those fields through AgentTraceDb `diff_traces` insertion.