From 72fe765de450bcab08ff0baa072ecf258cdd651f Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Fri, 15 May 2026 15:56:13 +0200 Subject: [PATCH 01/11] agent-trace-plugin: Switch event capture from session.diff to message.updated The agent-trace plugin previously captured session.diff events, but the upstream OpenCode API no longer emits this event reliably. Switch to message.updated events, which carry the same diff information per user message. Key changes: - Listen for message.updated instead of session.diff - Extract diffs from properties.info.summary?.diffs[].patch instead of properties.diff[] - Filter to user messages only (info.role === user) - Fall back sessionID to unknown when info.sessionID is absent - Use patch-only extraction (no diff field fallback needed for FileDiff) - Bump @opencode-ai/plugin from 1.3.0 to 1.14.28 for message.updated support Generated plugin outputs regenerated via Pkl; context documentation updated to reflect the new event contract. Plan: agent-trace-plugin-message-updated (T01, T02, T03) Co-authored-by: SCE --- config/.opencode/plugins/sce-agent-trace.ts | 47 ++++--- .../.opencode/plugins/sce-agent-trace.ts | 47 ++++--- config/lib/agent-trace-plugin/bun.lock | 60 ++++++++- .../opencode-sce-agent-trace-plugin.ts | 47 ++++--- config/lib/agent-trace-plugin/package.json | 2 +- context/context-map.md | 2 +- context/glossary.md | 2 +- .../agent-trace-plugin-message-updated.md | 116 ++++++++++++++++++ .../opencode-agent-trace-plugin-runtime.md | 22 ++-- 9 files changed, 278 insertions(+), 67 deletions(-) create mode 100644 context/plans/agent-trace-plugin-message-updated.md diff --git a/config/.opencode/plugins/sce-agent-trace.ts b/config/.opencode/plugins/sce-agent-trace.ts index e20bbe0e..c74493d8 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; @@ -21,7 +21,7 @@ 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 +30,34 @@ 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; + // 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 +67,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) { diff --git a/config/automated/.opencode/plugins/sce-agent-trace.ts b/config/automated/.opencode/plugins/sce-agent-trace.ts index e20bbe0e..c74493d8 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; @@ -21,7 +21,7 @@ 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 +30,34 @@ 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; + // 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 +67,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) { diff --git a/config/lib/agent-trace-plugin/bun.lock b/config/lib/agent-trace-plugin/bun.lock index b490cef5..45692573 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 e20bbe0e..c74493d8 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; @@ -21,7 +21,7 @@ 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 +30,34 @@ 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; + // 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 +67,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) { diff --git a/config/lib/agent-trace-plugin/package.json b/config/lib/agent-trace-plugin/package.json index 79a35a98..6d4df72a 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/context-map.md b/context/context-map.md index 6dfc35f5..416c2a8b 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -51,7 +51,7 @@ Feature/domain context: - `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 }` extraction via `properties.info.role === "user"` 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; `session.diff` event capture has been removed) - `context/sce/cli-first-install-channels-contract.md` (current first-wave `sce` install/distribution contract covering supported channels, canonical naming, `.version` release authority, and Nix-owned build policy) - `context/sce/optional-install-channel-integration-test-entrypoint.md` (current opt-in flake app contract for install-channel integration coverage, including thin flake delegation to the Rust runner, shared harness ownership, real npm+Bun+Cargo install flows, channel selector semantics, and the explicit non-default execution boundary) - `context/sce/cli-release-artifact-contract.md` (shared `sce` release artifact naming, checksum/manifest outputs, GitHub Releases as the canonical artifact publication surface, and the current three-target Linux/macOS release workflow topology) diff --git a/context/glossary.md b/context/glossary.md index ea9f27c6..757a01f0 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -151,6 +151,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 carries `commit_timestamp` (RFC 3339 commit-time value used as `AgentTrace.timestamp`) and `commit_revision` (mapped to `AgentTrace.vcs.revision`). - `build_agent_trace`: Public function in `cli/src/services/agent_trace.rs` that computes `intersection_patch = intersect_patches(constructed_patch, post_commit_patch)`, iterates over `post_commit_patch`'s files and hunks, classifies each hunk against `intersection_patch`, validates `AgentTraceMetadataInput.commit_timestamp` as RFC 3339, derives UUIDv7 `AgentTrace.id` from that same commit-time moment, and returns `Result` with top-level metadata fields plus one `Conversation` per `post_commit_patch` hunk; library-only, not wired into CLI command dispatch -- `agent-trace plugin diff extraction seam`: Internal helper `extractDiffTracePayload` in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` that reads `input.event` and returns `{ sessionID, diff, time }` only when the event is `session.diff`; extracts `sessionID` from `properties.sessionID` (falling back to `"unknown"` when missing/empty), joins non-empty `patch` or `diff` fields from `properties.diff[]` entries into a single `diff` string, and uses `Date.now()` for `time`; returns `undefined` for non-`session.diff` events or empty diff arrays. +- `agent-trace plugin diff 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 `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, and uses `Date.now()` for `time`; 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 }` 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/plans/agent-trace-plugin-message-updated.md b/context/plans/agent-trace-plugin-message-updated.md new file mode 100644 index 00000000..152d1759 --- /dev/null +++ b/context/plans/agent-trace-plugin-message-updated.md @@ -0,0 +1,116 @@ +# Plan: Replace `session.diff` event capture with `message.updated` in agent-trace plugin + +## Change summary + +Update `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` to capture +`message.updated` events (filtered to user messages with diffs) instead of `session.diff` +events. The extraction seam (`extractDiffTracePayload`) must be rewritten to read from +`properties.info.summary.diffs[].patch` on `UserMessage` payloads and return the same +`DiffTracePayload` shape (`{ sessionID, diff, time }`) consumed by `sce hooks diff-trace`. + +## Success criteria + +- Plugin registers interest in `message.updated` instead of `session.diff`. +- `extractDiffTracePayload` returns a valid `DiffTracePayload` for a `message.updated` + event where `properties.info.role === "user"` and `summary.diffs` contains at least one + non-empty `patch`. +- `extractDiffTracePayload` returns `undefined` for: + - Non-`message.updated` events + - `message.updated` events where `info.role` is not `"user"` (e.g., `"assistant"`) + - `message.updated` user messages with no `summary` or no `summary.diffs` + - `message.updated` user messages where all `diffs[].patch` entries are empty/missing +- `sessionID` falls back to `"unknown"` when `properties.info.sessionID` is absent or + empty. +- `time` is set to `Date.now()` (extraction time, existing behavior). +- `diff` is formed by joining non-empty `patch` strings from each file-diff entry with + `\n`. +- The context document `context/sce/opencode-agent-trace-plugin-runtime.md` is updated + to reflect the new event contract. +- `nix flake check` and `nix run .#pkl-check-generated` pass after the change. + +## Constraints and non-goals + +- **In scope**: TS source, context doc. +- **Out of scope**: Rust CLI changes, schema changes, new database tables, Pkl generation + changes, test file creation (the plugin has no test file yet — none was found). +- No external dependency changes. +- The `DiffTracePayload` type and `runDiffTraceHook` / `buildTrace` / `SceAgentTracePlugin` + surfaces remain unchanged. +- The `session.diff` constant can be removed. + +## Task stack + +- [x] T01: `Rewrite extractDiffTracePayload and update event registration` (status:done) + - Task ID: T01 + - Goal: Rewrite `extractDiffTracePayload` to extract from `message.updated` user-message + events and update `REQUIRED_EVENTS` / `ALL_CAPTURED_EVENTS`. + - Boundaries (in/out of scope): + - In: Change `REQUIRED_EVENTS` from `session.diff` to `message.updated`. + - In: Update `extractDiffTracePayload` to: + - Check `event.type === "message.updated"` + - Narrow the event properties to access `info` (the `Message`) + - Filter to `info.role === "user"` (i.e., `UserMessage`) + - Read `sessionID` from `info.sessionID` with fallback to `"unknown"` + - Read `summary.diffs` from `info.summary.diffs` (handle optional chain: + `info.summary?` → `summary.diffs?`) + - Extract `patch` from each diff entry (the `FileDiff` shape has `patch?: string` + alongside `additions`, `deletions`, etc.) + - Join non-empty patches with `\n`; return `undefined` if no patches yield content + - Use `Date.now()` for `time` + - In: Remove `session.diff` references, leaving only `message.updated`. + - In: Only the TS source file is modified. + - Out: Test creation, Rust changes, Pkl changes, dependency changes. + - Done when: + - Source file compiles with `tsc --noEmit` (TypeScript strict mode). + - All success criteria in the plan are met by the single source change. + - Verification notes (commands or checks): + - `cd config/lib/agent-trace-plugin && npx tsc --noEmit` + - Visual review of the final `extractDiffTracePayload` logic covers all edge cases. + - **Completed:** 2026-05-15 + - **Files changed:** `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` + - **Evidence:** `tsc --noEmit` passed with zero errors; visual review confirms all edge cases covered. + - **Notes:** `REQUIRED_EVENTS` changed from `session.diff` to `message.updated`; `extractDiffTracePayload` reads from `properties.info` (Message shape) with role filter, optional-chain diffs access, and patch-only extraction (no `diff` fallback needed for FileDiff). + +- [x] T02: `Update context document opencode-agent-trace-plugin-runtime.md` (status:done) + - Task ID: T02 + - Goal: Update `context/sce/opencode-agent-trace-plugin-runtime.md` to document the + new `message.updated` event capture baseline instead of `session.diff`. + - Boundaries (in/out of scope): + - In: Rewrite the "Event capture baseline" and "Diff extraction seam" sections to + describe `message.updated` behavior. + - In: Update extraction contract to list the new field access path + (`properties.info.role === "user"`, `info.sessionID`, `info.summary?.diffs[].patch`). + - In: Document that `session.diff` capture has been removed. + - Out: Any file outside `context/`. + - Done when: Context doc accurately describes the new plugin behavior and all old + `session.diff` references are removed or replaced. + - Verification notes (commands or checks): Read the file and verify alignment with + the final T01 source. + - **Completed:** 2026-05-15 + - **Files changed:** `context/sce/opencode-agent-trace-plugin-runtime.md` + - **Evidence:** Content verified against T01 source; all session.diff references replaced except the intentional removal notice. + - **Notes:** Sections rewritten to document message.updated capture, user-role filtering, info.sessionID fallback, info.summary?.diffs[].patch-only extraction, and session.diff removal. + +- [x] T03: `Validation and cleanup` (status:done) + - Task ID: T03 + - Goal: Run final repo-level checks and confirm everything is consistent. + - Boundaries (in/out of scope): + - In: `nix flake check`, `nix run .#pkl-check-generated`. + - In: Confirm `git status` shows only the expected two changed files. + - Out: Any code or context changes beyond validation. + - Done when: + - `nix flake check` passes. + - `nix run .#pkl-check-generated` passes. + - No unexpected modified or untracked files remain. + - Verification notes (commands or checks): + - `nix flake check` + - `nix run .#pkl-check-generated` + - `git status` + - **Completed:** 2026-05-15 + - **Files changed:** `config/.opencode/plugins/sce-agent-trace.ts` (regenerated via Pkl), `config/automated/.opencode/plugins/sce-agent-trace.ts` (regenerated via Pkl) + - **Evidence:** `nix flake check` passed (all 4 checks), `nix run .#pkl-check-generated` passed ("Generated outputs are up to date.") + - **Notes:** Pkl regeneration was needed because T01/T02 modified the canonical source but did not regenerate generated outputs. Git status also shows unrelated untracked files (`poem.txt`, `secondPoem.txt`), pre-existing dependency bump side effects (`bun.lock`, `package.json`), and the expected `context-map.md` update from T02. + +## Open questions + +None. All clarifications resolved before planning. diff --git a/context/sce/opencode-agent-trace-plugin-runtime.md b/context/sce/opencode-agent-trace-plugin-runtime.md index 62f74dfd..66e47839 100644 --- a/context/sce/opencode-agent-trace-plugin-runtime.md +++ b/context/sce/opencode-agent-trace-plugin-runtime.md @@ -4,31 +4,33 @@ Current runtime source: `config/lib/agent-trace-plugin/opencode-sce-agent-trace- ## Event capture baseline -- The plugin captures only `session.diff` events. +- 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 }` 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: -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) 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. +- `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 owns validation and dual persistence without changing the plugin payload shape. From ab2fefdb4e0d28dca1edf1cce5f4a4c3f273f62b Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Fri, 15 May 2026 16:43:15 +0200 Subject: [PATCH 02/11] agent-trace: Add model_id payload and diff trace migration Capture the OpenCode model identifier in emitted diff-trace payloads and add the nullable diff_traces.model_id migration so later Rust hook wiring can persist it without changing recent-patch reads. Plan: add-diff-traces-model-id Updated tasks: T01, T02 Co-authored-by: SCE --- .../005_add_diff_traces_model_id.sql | 1 + config/.opencode/plugins/sce-agent-trace.ts | 6 +- .../.opencode/plugins/sce-agent-trace.ts | 6 +- .../opencode-sce-agent-trace-plugin.ts | 6 +- context/architecture.md | 2 +- context/cli/cli-command-surface.md | 4 +- context/context-map.md | 4 +- context/glossary.md | 6 +- context/overview.md | 6 +- context/plans/add-diff-traces-model-id.md | 118 ++++++++++++++++++ context/sce/agent-trace-db.md | 6 +- .../sce/agent-trace-hooks-command-routing.md | 2 +- .../opencode-agent-trace-plugin-runtime.md | 9 +- 13 files changed, 155 insertions(+), 21 deletions(-) create mode 100644 cli/migrations/agent-trace/005_add_diff_traces_model_id.sql create mode 100644 context/plans/add-diff-traces-model-id.md diff --git a/cli/migrations/agent-trace/005_add_diff_traces_model_id.sql b/cli/migrations/agent-trace/005_add_diff_traces_model_id.sql new file mode 100644 index 00000000..6d3e96a7 --- /dev/null +++ b/cli/migrations/agent-trace/005_add_diff_traces_model_id.sql @@ -0,0 +1 @@ +ALTER TABLE diff_traces ADD COLUMN model_id TEXT; diff --git a/config/.opencode/plugins/sce-agent-trace.ts b/config/.opencode/plugins/sce-agent-trace.ts index c74493d8..acd0a54f 100644 --- a/config/.opencode/plugins/sce-agent-trace.ts +++ b/config/.opencode/plugins/sce-agent-trace.ts @@ -15,9 +15,10 @@ type DiffTracePayload = { sessionID: string; diff: string; time: number; + model_id: string; }; -function extractDiffTracePayload( +export function extractDiffTracePayload( input: TraceInput, ): DiffTracePayload | undefined { const event = input.event; @@ -51,6 +52,8 @@ function extractDiffTracePayload( ? infoObj.sessionID : "unknown"; + const model = infoObj.model; + // Access info.summary?.diffs via explicit checks const summary = infoObj.summary; const diffEntries = @@ -81,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 c74493d8..acd0a54f 100644 --- a/config/automated/.opencode/plugins/sce-agent-trace.ts +++ b/config/automated/.opencode/plugins/sce-agent-trace.ts @@ -15,9 +15,10 @@ type DiffTracePayload = { sessionID: string; diff: string; time: number; + model_id: string; }; -function extractDiffTracePayload( +export function extractDiffTracePayload( input: TraceInput, ): DiffTracePayload | undefined { const event = input.event; @@ -51,6 +52,8 @@ function extractDiffTracePayload( ? infoObj.sessionID : "unknown"; + const model = infoObj.model; + // Access info.summary?.diffs via explicit checks const summary = infoObj.summary; const diffEntries = @@ -81,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/opencode-sce-agent-trace-plugin.ts b/config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts index c74493d8..acd0a54f 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 @@ -15,9 +15,10 @@ type DiffTracePayload = { sessionID: string; diff: string; time: number; + model_id: string; }; -function extractDiffTracePayload( +export function extractDiffTracePayload( input: TraceInput, ): DiffTracePayload | undefined { const event = input.event; @@ -51,6 +52,8 @@ function extractDiffTracePayload( ? infoObj.sessionID : "unknown"; + const model = infoObj.model; + // Access info.summary?.diffs via explicit checks const summary = infoObj.summary; const diffEntries = @@ -81,6 +84,7 @@ function extractDiffTracePayload( sessionID, diff: patches.join("\n"), time: Date.now(), + model_id: `${model.providerID}/${model.modelID}`, }; } diff --git a/context/architecture.md b/context/architecture.md index cc83e968..2f4c23e1 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -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` 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 while ignoring extra STDIN fields until explicitly wired. 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 bd2bed76..88d001c6 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` 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. Extra STDIN fields are ignored by the Rust hook path until explicitly wired. `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` 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 while ignoring extra STDIN fields. `cli/src/services/hooks/lifecycle.rs` implements `ServiceLifecycle` for hook health checks, fix, and setup (hook rollout integrity and required-hook installation). - `cli/src/services/resilience.rs` defines shared bounded retry/timeout/backoff execution policy (`RetryPolicy`, `run_with_retry`) with deterministic failure messaging and retry observability hooks. - No `cli/src/services/sync.rs` module exists in the current codebase; `sce sync` command wiring is deferred, while local DB initialization and health ownership are split between setup and doctor. - `cli/src/services/default_paths.rs` defines the canonical per-user persisted-location seam for config/state/cache roots plus named default file paths for current persisted artifacts (`global config`, `auth tokens`, `local DB`, `agent trace DB`) used by config discovery, token storage, database adapters, and doctor diagnostics; its internal `roots` seam now owns the platform-aware root-directory resolution so non-test production modules consume shared path accessors instead of resolving owned roots directly. diff --git a/context/context-map.md b/context/context-map.md index 416c2a8b..e8cb4ce5 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -42,7 +42,7 @@ 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, and `agent_traces` migrations applied through shared migration metadata, currently checked-in but not-yet-registered `005_add_diff_traces_model_id.sql`, 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-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`) @@ -51,7 +51,7 @@ Feature/domain context: - `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 `message.updated` event capture filtered to user messages with diffs, `{ sessionID, diff, time }` extraction via `properties.info.role === "user"` 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; `session.diff` event capture has been removed) +- `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; downstream Rust payload/storage support for `model_id` is pending later plan tasks; `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 757a01f0..09ab884a 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -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, writes collision-safe parsed-payload `context/tmp/-000000-diff-trace.json` artifacts, and inserts parsed payload fields into AgentTraceDb while ignoring extra STDIN fields until explicitly wired). - `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. @@ -151,6 +151,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 carries `commit_timestamp` (RFC 3339 commit-time value used as `AgentTrace.timestamp`) and `commit_revision` (mapped to `AgentTrace.vcs.revision`). - `build_agent_trace`: Public function in `cli/src/services/agent_trace.rs` that computes `intersection_patch = intersect_patches(constructed_patch, post_commit_patch)`, iterates over `post_commit_patch`'s files and hunks, classifies each hunk against `intersection_patch`, validates `AgentTraceMetadataInput.commit_timestamp` as RFC 3339, derives UUIDv7 `AgentTrace.id` from that same commit-time moment, and returns `Result` with top-level metadata fields plus one `Conversation` per `post_commit_patch` hunk; library-only, not wired into CLI command dispatch -- `agent-trace plugin diff extraction seam`: Internal helper `extractDiffTracePayload` in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` that reads `input.event` and returns `{ sessionID, diff, time }` only when the event is `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, and uses `Date.now()` for `time`; 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 }` 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 1b4f5683..f3e19407 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 downstream Rust `model_id` validation/storage is still pending later plan tasks. 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` 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 }` payload persistence with required `u64` millisecond `time`, non-lossy AgentTraceDb `time_ms` conversion, collision-safe timestamp+attempt artifact filenames, and ignored extra fields until explicitly wired. 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/plans/add-diff-traces-model-id.md b/context/plans/add-diff-traces-model-id.md new file mode 100644 index 00000000..5973cc5d --- /dev/null +++ b/context/plans/add-diff-traces-model-id.md @@ -0,0 +1,118 @@ +# 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 + +- [ ] T03: `Update Rust SQL constants, DiffTraceInsert, and insert logic` (status:todo) + - 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` + +- [ ] T04: `Update Rust DiffTracePayload struct and parsing in hooks/mod.rs` (status:todo) + - 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` + +- [ ] T05: `Validation and cleanup` (status:todo) + - 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'` + +## Open questions + +None — all clarifications resolved during intake. diff --git a/context/sce/agent-trace-db.md b/context/sce/agent-trace-db.md index b415c5b0..90dedbcf 100644 --- a/context/sce/agent-trace-db.md +++ b/context/sce/agent-trace-db.md @@ -38,6 +38,8 @@ The Agent Trace DB path is resolved from the shared default-path catalog: - `003_add_diff_traces_time_ms_id_index.sql` - `004_create_agent_traces.sql` +The migrations directory also contains `005_add_diff_traces_model_id.sql`, which adds nullable `diff_traces.model_id`. That file is not yet registered in `AgentTraceDbSpec::migrations()`, so current runtime setup/doctor flows still apply only the registered 001-004 migration set until the Rust migration-registration task lands. + 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`. The `diff_traces` migration creates: @@ -81,9 +83,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 currently validates required STDIN `{ sessionID, diff, time }` before persistence; extra STDIN fields such as plugin-emitted `model_id` are ignored until the downstream Rust payload/storage tasks add first-class support. - `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 06dce5cb..1d8c78c3 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` 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()`. Extra STDIN fields such as the plugin-emitted `model_id` are not yet validated or persisted by the Rust hook path. - `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 66e47839..5671d51e 100644 --- a/context/sce/opencode-agent-trace-plugin-runtime.md +++ b/context/sce/opencode-agent-trace-plugin-runtime.md @@ -5,7 +5,7 @@ Current runtime source: `config/lib/agent-trace-plugin/opencode-sce-agent-trace- ## Event capture baseline - 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 }` over STDIN JSON. +- 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. @@ -15,7 +15,7 @@ The plugin defines `extractDiffTracePayload(input)` as a typed guard/extraction ### 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 === "message.updated"` 2. `input.event.properties` is a non-null object @@ -26,11 +26,12 @@ Returns `{ sessionID, diff, time }` only when all checks pass: 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`. +- 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 owns validation and dual persistence without changing the plugin payload shape. +- When extraction succeeds, `buildTrace` forwards the extracted payload to `sce hooks diff-trace` via STDIN JSON; the current Rust hook runtime still validates and persists only the existing required `sessionID`/`diff`/`time` fields until the downstream Rust payload/storage tasks are implemented. From 05ecbaf16ed2d51ba5652ab77a821d3fb94ef4dd Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Fri, 15 May 2026 16:53:33 +0200 Subject: [PATCH 03/11] agent-trace-db: Add model_id diff trace insert support Register the model_id migration and extend DiffTraceInsert plus the diff_traces INSERT statement so AgentTraceDb writes nullable model identifiers with captured diff traces. This is the DB-layer slice; hook payload parsing remains in the next task. Plan: add-diff-traces-model-id Task: T03 Co-authored-by: SCE --- cli/src/services/agent_trace_db/mod.rs | 13 +++++++++++-- context/architecture.md | 2 +- context/cli/cli-command-surface.md | 4 ++-- context/context-map.md | 4 ++-- context/glossary.md | 4 ++-- context/overview.md | 2 +- context/plans/add-diff-traces-model-id.md | 9 ++++++++- context/sce/agent-trace-db.md | 9 ++++++--- context/sce/agent-trace-hooks-command-routing.md | 2 +- 9 files changed, 34 insertions(+), 15 deletions(-) diff --git a/cli/src/services/agent_trace_db/mod.rs b/cli/src/services/agent_trace_db/mod.rs index 5ce89365..3f711cba 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/context/architecture.md b/context/architecture.md index 2f4c23e1..921829cc 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. diff --git a/context/cli/cli-command-surface.md b/context/cli/cli-command-surface.md index 88d001c6..c6827c21 100644 --- a/context/cli/cli-command-surface.md +++ b/context/cli/cli-command-surface.md @@ -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 e8cb4ce5..3193a5c1 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -42,7 +42,7 @@ 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, currently checked-in but not-yet-registered `005_add_diff_traces_model_id.sql`, 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`) @@ -51,7 +51,7 @@ Feature/domain context: - `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 `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; downstream Rust payload/storage support for `model_id` is pending later plan tasks; `session.diff` event capture has been removed) +- `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; downstream Rust DB insert support for `model_id` is wired while hook payload parsing remains pending; `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 09ab884a..0c4d8ab5 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. diff --git a/context/overview.md b/context/overview.md index f3e19407..1cd34a44 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/`; 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 downstream Rust `model_id` validation/storage is still pending later plan tasks. 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 downstream Rust DB insert support for `model_id` is wired and hook payload parsing remains pending. 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. diff --git a/context/plans/add-diff-traces-model-id.md b/context/plans/add-diff-traces-model-id.md index 5973cc5d..fb491a62 100644 --- a/context/plans/add-diff-traces-model-id.md +++ b/context/plans/add-diff-traces-model-id.md @@ -68,7 +68,7 @@ Three layers need updating: - 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 -- [ ] T03: `Update Rust SQL constants, DiffTraceInsert, and insert logic` (status:todo) +- [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): @@ -82,6 +82,13 @@ Three layers need updating: - 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. - [ ] T04: `Update Rust DiffTracePayload struct and parsing in hooks/mod.rs` (status:todo) - Task ID: T04 diff --git a/context/sce/agent-trace-db.md b/context/sce/agent-trace-db.md index 90dedbcf..1106819e 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,8 +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` -The migrations directory also contains `005_add_diff_traces_model_id.sql`, which adds nullable `diff_traces.model_id`. That file is not yet registered in `AgentTraceDbSpec::migrations()`, so current runtime setup/doctor flows still apply only the registered 001-004 migration set until the Rust migration-registration task lands. +`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`. @@ -49,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: @@ -83,7 +85,8 @@ The `agent_traces` migration creates: `sce hooks diff-trace` is the current runtime writer for `diff_traces`. -- The hook path currently validates required STDIN `{ sessionID, diff, time }` before persistence; extra STDIN fields such as plugin-emitted `model_id` are ignored until the downstream Rust payload/storage tasks add first-class support. +- The hook path currently validates required STDIN `{ sessionID, diff, time }` before persistence; plugin-emitted `model_id` is not yet parsed by the hook payload layer until the downstream Rust hook task supplies it to `DiffTraceInsert`. +- The Agent Trace DB insert shape is ahead of the hook payload call site until that downstream hook task lands; the hook task owns restoring full compile by passing the 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` parsed-payload artifact and inserts the parsed payload fields through `AgentTraceDb::insert_diff_trace()`. - Command success requires both artifact and database persistence to succeed. diff --git a/context/sce/agent-trace-hooks-command-routing.md b/context/sce/agent-trace-hooks-command-routing.md index 1d8c78c3..e25cd6bf 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 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()`. Extra STDIN fields such as the plugin-emitted `model_id` are not yet validated or persisted by the Rust hook path. +- `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 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()`. Extra STDIN fields such as the plugin-emitted `model_id` are not yet validated or persisted by the Rust hook path; the DB insert shape now requires `model_id`, so the downstream hook payload task owns parsing and passing it to restore full compile. - `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 From cd4ea7afe4dcef626e149e766e5cbff636afc942 Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Fri, 15 May 2026 17:05:22 +0200 Subject: [PATCH 04/11] hooks: Add model_id validation to diff-trace intake Parse the OpenCode-provided model_id from diff-trace payloads and pass it through to AgentTraceDb inserts so stored diff_traces retain model attribution. Plan: add-diff-traces-model-id Task: T04 Co-authored-by: SCE --- cli/src/services/hooks/mod.rs | 4 ++++ context/architecture.md | 2 +- context/cli/cli-command-surface.md | 4 ++-- context/context-map.md | 4 +++- context/glossary.md | 4 ++-- context/overview.md | 6 +++--- context/patterns.md | 2 +- context/plans/add-diff-traces-model-id.md | 9 ++++++++- context/sce/agent-trace-db.md | 3 +-- context/sce/agent-trace-hooks-command-routing.md | 2 +- context/sce/opencode-agent-trace-plugin-runtime.md | 2 +- 11 files changed, 27 insertions(+), 15 deletions(-) diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index ff094df2..cdacd418 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/context/architecture.md b/context/architecture.md index 921829cc..b735496b 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -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 parsed-payload `context/tmp/-000000-diff-trace.json` artifact, and inserts the parsed payload fields into AgentTraceDb while ignoring extra STDIN fields until explicitly wired. 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 c6827c21..e752eff6 100644 --- a/context/cli/cli-command-surface.md +++ b/context/cli/cli-command-surface.md @@ -53,7 +53,7 @@ Operator onboarding currently comes from `sce --help`, command-local `--help` ou - `auth` and `hooks` stay parser-valid and directly invocable, but are hidden from those top-level help surfaces Deferred or gated command surfaces currently avoid claiming unimplemented behavior. -`hooks` routes through implemented subcommand parsing/dispatch for `pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, and `diff-trace`; current behavior remains attribution-only and disabled by default for commit attribution, while `diff-trace` is active STDIN intake with required non-empty `sessionID`/`diff` plus required `u64` `time` (Unix epoch milliseconds) validation, non-lossy AgentTraceDb `time_ms` conversion, collision-safe per-invocation `context/tmp/-000000-diff-trace.json` parsed-payload writes, and AgentTraceDb insertion. Extra STDIN fields are ignored by the Rust hook path until explicitly wired. +`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 parsed-payload `context/tmp/-000000-diff-trace.json` persistence, and command-failing AgentTraceDb insertion while ignoring extra STDIN fields. `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. diff --git a/context/context-map.md b/context/context-map.md index 3193a5c1..70db59fb 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -48,10 +48,12 @@ Feature/domain context: - `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md` (T01 Local Hooks MVP production contract freeze and deterministic gap matrix for `agent-trace-local-hooks-production-mvp`) - `context/sce/agent-trace-minimal-generator.md` (implemented library-only minimal agent-trace generator seam at `cli/src/services/agent_trace.rs`, producing a JSON payload with top-level `version`, UUIDv7 `id` derived from commit-time metadata, caller-provided commit-time `timestamp`, top-level `vcs` metadata (`type = git`, `revision` from metadata input), and per-file trace data from patch inputs via `intersect_patches(constructed_patch, post_commit_patch)` then `post_commit_patch`-anchored hunk classification into `ai`/`mixed`/`unknown` contributor categories, serialized per conversation as nested `contributor.type` plus one derived `ranges[{start_line,end_line}]` entry per post-commit hunk) - `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime behavior: disabled-default commit-msg attribution, no-op `pre-commit`/`post-rewrite` entrypoints, active `post-commit` intersection entrypoint capturing current commit patch, querying recent `diff_traces` from past 7 days, combining valid patches via `patch::combine_patches`, intersecting via `patch::intersect_patches`, persisting results to `post_commit_patch_intersections`, and persisting built post-commit Agent Trace payloads to AgentTraceDb `agent_traces` (DB-only), plus `diff-trace` STDIN intake with required-field validation and dual persistence to AgentTraceDb and collision-safe `context/tmp/-000000-diff-trace.json` artifacts) +- `context/sce/agent-trace-minimal-generator.md` (implemented 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 `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 `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; downstream Rust DB insert support for `model_id` is wired while hook payload parsing remains pending; `session.diff` event capture has been removed) +- `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 0c4d8ab5..484b74f5 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -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 required STDIN payload fields, writes collision-safe parsed-payload `context/tmp/-000000-diff-trace.json` artifacts, and inserts parsed payload fields into AgentTraceDb while ignoring extra STDIN fields until explicitly wired). +- `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. diff --git a/context/overview.md b/context/overview.md index 1cd34a44..acf24acf 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/`; 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 downstream Rust DB insert support for `model_id` is wired and hook payload parsing remains pending. 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` currently validates/persists required `sessionID`/`diff` plus required `u64` millisecond `time`, with 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 parsed STDIN `{ sessionID, diff, time }` payload persistence with required `u64` millisecond `time`, non-lossy AgentTraceDb `time_ms` conversion, collision-safe timestamp+attempt artifact filenames, and ignored extra fields until explicitly wired. This behavior is documented in `context/sce/agent-trace-hooks-command-routing.md`. +The hooks command surface now also supports concrete runtime subcommand routing (`pre-commit`, `commit-msg`, `post-commit`, `post-rewrite`, `diff-trace`) with deterministic argument/STDIN validation. Current runtime behavior keeps attribution disabled by default: the attribution gate enables canonical trailer insertion in `commit-msg`, `pre-commit`/`post-rewrite` remain deterministic no-ops, `post-commit` is the active bounded recent-diff-trace intersection path, and `diff-trace` is the active intake path for parsed STDIN `{ sessionID, diff, time, model_id }` payload persistence with required `u64` millisecond `time`, non-lossy AgentTraceDb `time_ms` conversion, and collision-safe timestamp+attempt artifact filenames. This behavior is documented in `context/sce/agent-trace-hooks-command-routing.md`. The setup service now also exposes deterministic required-hook embedded asset accessors (`iter_required_hook_assets`, `get_required_hook_asset`) backed by canonical templates in `cli/assets/hooks/` for `pre-commit`, `commit-msg`, and `post-commit`; this behavior is documented in `context/sce/setup-githooks-hook-asset-packaging.md`. The setup service now also includes required-hook install orchestration (`install_required_git_hooks`) that resolves repository root and effective hooks path from git truth, enforces deterministic per-hook outcomes (`Installed`/`Updated`/`Skipped`), and uses a unified remove-and-replace policy that removes existing hooks before swapping staged content with deterministic recovery guidance on swap failures; this behavior is documented in `context/sce/setup-githooks-install-flow.md`. The setup command parser/dispatch now also supports composable setup+hooks runs (`sce setup --opencode|--claude|--both --hooks`) plus hooks-only mode (`sce setup --hooks` with optional `--repo `), enforces deterministic compatibility validation (`--repo` requires `--hooks`; target flags remain mutually exclusive), and emits deterministic setup/hook outcome messaging (`installed`/`updated`/`skipped`); this behavior is documented in `context/sce/setup-githooks-cli-ux.md`. diff --git a/context/patterns.md b/context/patterns.md index e5c1dd1a..365eba01 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 index fb491a62..d532bb96 100644 --- a/context/plans/add-diff-traces-model-id.md +++ b/context/plans/add-diff-traces-model-id.md @@ -90,7 +90,7 @@ Three layers need updating: - 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. -- [ ] T04: `Update Rust DiffTracePayload struct and parsing in hooks/mod.rs` (status:todo) +- [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): @@ -103,6 +103,13 @@ Three layers need updating: - 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. - [ ] T05: `Validation and cleanup` (status:todo) - Task ID: T05 diff --git a/context/sce/agent-trace-db.md b/context/sce/agent-trace-db.md index 1106819e..3c6a9334 100644 --- a/context/sce/agent-trace-db.md +++ b/context/sce/agent-trace-db.md @@ -85,8 +85,7 @@ The `agent_traces` migration creates: `sce hooks diff-trace` is the current runtime writer for `diff_traces`. -- The hook path currently validates required STDIN `{ sessionID, diff, time }` before persistence; plugin-emitted `model_id` is not yet parsed by the hook payload layer until the downstream Rust hook task supplies it to `DiffTraceInsert`. -- The Agent Trace DB insert shape is ahead of the hook payload call site until that downstream hook task lands; the hook task owns restoring full compile by passing the parsed `model_id` into `DiffTraceInsert`. +- 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` parsed-payload artifact and inserts the parsed payload fields through `AgentTraceDb::insert_diff_trace()`. - Command success requires both artifact and database persistence to succeed. diff --git a/context/sce/agent-trace-hooks-command-routing.md b/context/sce/agent-trace-hooks-command-routing.md index e25cd6bf..61d9f42d 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 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()`. Extra STDIN fields such as the plugin-emitted `model_id` are not yet validated or persisted by the Rust hook path; the DB insert shape now requires `model_id`, so the downstream hook payload task owns parsing and passing it to restore full compile. +- `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 5671d51e..80c99726 100644 --- a/context/sce/opencode-agent-trace-plugin-runtime.md +++ b/context/sce/opencode-agent-trace-plugin-runtime.md @@ -34,4 +34,4 @@ Otherwise, the helper returns `undefined`. - 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 current Rust hook runtime still validates and persists only the existing required `sessionID`/`diff`/`time` fields until the downstream Rust payload/storage tasks are implemented. +- 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. From 9d244c4f51aae8dbf8d782fdf307d471cebe553b Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Fri, 15 May 2026 17:16:14 +0200 Subject: [PATCH 05/11] plans: Complete T05 validation and finalize add-diff-traces-model-id plan - Mark T05 (Validation and cleanup) as done with evidence - Add execution record: pkl-check-generated passed, nix flake check passed - Append validation report confirming all 6 success criteria met - Clean stale context/tmp/ artifacts (157 files removed) - Confirm no new test files remain from this plan Plan: add-diff-traces-model-id Task: T05 Co-authored-by: SCE --- context/plans/add-diff-traces-model-id.md | 42 ++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/context/plans/add-diff-traces-model-id.md b/context/plans/add-diff-traces-model-id.md index d532bb96..ab6a586b 100644 --- a/context/plans/add-diff-traces-model-id.md +++ b/context/plans/add-diff-traces-model-id.md @@ -111,7 +111,7 @@ Three layers need updating: - 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. -- [ ] T05: `Validation and cleanup` (status:todo) +- [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): @@ -126,6 +126,46 @@ Three layers need updating: - `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 From 616b41e5e5def1c1a5538da14ec9e1cb562330e3 Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Fri, 15 May 2026 18:29:24 +0200 Subject: [PATCH 06/11] fix: remove export function from opencode plugin ts script Co-authored-by: SCE --- .opencode/plugins/sce-agent-trace.ts | 51 ++++++++++++------- config/.opencode/plugins/sce-agent-trace.ts | 2 +- .../.opencode/plugins/sce-agent-trace.ts | 2 +- .../opencode-sce-agent-trace-plugin.ts | 2 +- 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/.opencode/plugins/sce-agent-trace.ts b/.opencode/plugins/sce-agent-trace.ts index e20bbe0e..abfae8fe 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/config/.opencode/plugins/sce-agent-trace.ts b/config/.opencode/plugins/sce-agent-trace.ts index acd0a54f..abfae8fe 100644 --- a/config/.opencode/plugins/sce-agent-trace.ts +++ b/config/.opencode/plugins/sce-agent-trace.ts @@ -18,7 +18,7 @@ type DiffTracePayload = { model_id: string; }; -export function extractDiffTracePayload( +function extractDiffTracePayload( input: TraceInput, ): DiffTracePayload | undefined { const event = input.event; diff --git a/config/automated/.opencode/plugins/sce-agent-trace.ts b/config/automated/.opencode/plugins/sce-agent-trace.ts index acd0a54f..abfae8fe 100644 --- a/config/automated/.opencode/plugins/sce-agent-trace.ts +++ b/config/automated/.opencode/plugins/sce-agent-trace.ts @@ -18,7 +18,7 @@ type DiffTracePayload = { model_id: string; }; -export function extractDiffTracePayload( +function extractDiffTracePayload( input: TraceInput, ): DiffTracePayload | undefined { const event = input.event; 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 acd0a54f..abfae8fe 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 @@ -18,7 +18,7 @@ type DiffTracePayload = { model_id: string; }; -export function extractDiffTracePayload( +function extractDiffTracePayload( input: TraceInput, ): DiffTracePayload | undefined { const event = input.event; From 14518dc8e372621f7c7c3783b8c7b4bd5944db5e Mon Sep 17 00:00:00 2001 From: stefanskoricdev Date: Mon, 18 May 2026 15:13:48 +0200 Subject: [PATCH 07/11] agent-trace: Propagate hunk model_id provenance into post-commit contributor output Carry nullable diff_traces.model_id through recent patch loading into PatchHunk provenance, preserve it across patch combine/intersect operations, and emit contributor.model_id for ai/mixed conversations when available. This keeps attribution metadata intact from stored diff traces to generated Agent Trace payloads while preserving deterministic patch shaping and serialization behavior. Co-authored-by: SCE --- cli/src/services/agent_trace.rs | 27 +++++++-- cli/src/services/agent_trace_db/mod.rs | 25 +++++--- cli/src/services/hooks/mod.rs | 2 +- cli/src/services/patch.rs | 59 ++++++++++++++----- context/cli/patch-service.md | 4 +- context/context-map.md | 3 +- context/glossary.md | 2 +- context/sce/agent-trace-db.md | 4 +- .../sce/agent-trace-hooks-command-routing.md | 1 + context/sce/agent-trace-minimal-generator.md | 16 ++--- 10 files changed, 104 insertions(+), 39 deletions(-) diff --git a/cli/src/services/agent_trace.rs b/cli/src/services/agent_trace.rs index eca0a348..ab5fc8c5 100644 --- a/cli/src/services/agent_trace.rs +++ b/cli/src/services/agent_trace.rs @@ -180,6 +180,9 @@ pub struct Contributor { /// Classification of this hunk's origin. #[serde(rename = "type")] pub kind: HunkContributor, + /// Model provenance for this contributor; omitted when unknown. + #[serde(skip_serializing_if = "Option::is_none")] + pub model_id: Option, } /// A single line range in the new file. @@ -322,12 +325,28 @@ fn build_trace_file( .hunks .iter() .map(|post_commit_hunk| { - let contributor = match intersection_file { - Some(ifile) => classify_hunk(post_commit_hunk, &ifile.hunks), - None => HunkContributor::Unknown, + let (contributor_kind, contributor_model_id) = match intersection_file { + Some(ifile) => { + let contributor_kind = classify_hunk(post_commit_hunk, &ifile.hunks); + let matched_intersection_hunk = ifile + .hunks + .iter() + .find(|h| h.old_start == post_commit_hunk.old_start); + let contributor_model_id = match contributor_kind { + HunkContributor::Ai | HunkContributor::Mixed => { + matched_intersection_hunk.and_then(|hunk| hunk.model_id.clone()) + } + HunkContributor::Unknown => None, + }; + (contributor_kind, contributor_model_id) + } + None => (HunkContributor::Unknown, None), }; Conversation { - contributor: Contributor { kind: contributor }, + contributor: Contributor { + kind: contributor_kind, + model_id: contributor_model_id, + }, ranges: vec![line_range_from_hunk(post_commit_file, post_commit_hunk)], } }) diff --git a/cli/src/services/agent_trace_db/mod.rs b/cli/src/services/agent_trace_db/mod.rs index 3f711cba..82ad362b 100644 --- a/cli/src/services/agent_trace_db/mod.rs +++ b/cli/src/services/agent_trace_db/mod.rs @@ -45,7 +45,8 @@ pub const INSERT_DIFF_TRACE_SQL: &str = "INSERT INTO diff_traces (time_ms, session_id, patch, model_id) VALUES (?1, ?2, ?3, ?4)"; /// Parameterized SQL for retrieving recent captured diff trace patches. -pub const SELECT_RECENT_DIFF_TRACE_PATCHES_SQL: &str = "SELECT id, time_ms, session_id, patch +pub const SELECT_RECENT_DIFF_TRACE_PATCHES_SQL: &str = + "SELECT id, time_ms, session_id, patch, model_id FROM diff_traces WHERE time_ms >= ?1 AND time_ms <= ?2 ORDER BY time_ms ASC, id ASC"; @@ -102,6 +103,7 @@ pub struct DiffTracePatchRow { pub time_ms: i64, pub session_id: String, pub patch: String, + pub model_id: Option, } /// Parsed recent diff trace patch ready for comparison flows. @@ -239,6 +241,7 @@ fn diff_trace_patch_row_from_turso(row: &turso::Row) -> Result) -> RecentDif for row in rows { match parse_patch(&row.patch) { - Ok(patch) => patches.push(ParsedDiffTracePatch { - id: row.id, - time_ms: row.time_ms, - session_id: row.session_id, - patch, - }), + Ok(mut patch) => { + for file in &mut patch.files { + for hunk in &mut file.hunks { + hunk.model_id.clone_from(&row.model_id); + } + } + + patches.push(ParsedDiffTracePatch { + id: row.id, + time_ms: row.time_ms, + session_id: row.session_id, + patch, + }); + } Err(error) => skipped.push(SkippedDiffTracePatch { id: row.id, time_ms: row.time_ms, diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index cdacd418..89fee59f 100644 --- a/cli/src/services/hooks/mod.rs +++ b/cli/src/services/hooks/mod.rs @@ -522,7 +522,7 @@ where let agent_trace_value = serde_json::to_value(&agent_trace) .context("Failed to serialize post-commit Agent Trace payload for validation.")?; validate_agent_trace(&agent_trace_value) - .context("Failed to persist built post-commit Agent Trace payload.")?; + .context("Failed to validate built post-commit Agent Trace payload.")?; let serialized = format!( "{}\n", diff --git a/cli/src/services/patch.rs b/cli/src/services/patch.rs index f76710c8..6b63f23a 100644 --- a/cli/src/services/patch.rs +++ b/cli/src/services/patch.rs @@ -62,6 +62,9 @@ pub struct PatchHunk { pub new_start: u64, /// Number of lines in the new file context for this hunk (0 for deleted-file hunks). pub new_count: u64, + /// Optional model provenance associated with this hunk. + #[serde(default)] + pub model_id: Option, /// Touched lines within this hunk (added and removed lines only; /// unchanged context lines are excluded). pub lines: Vec, @@ -176,9 +179,11 @@ pub fn load_patch_from_json_bytes(input: &[u8]) -> Result = constructed_file + let available_lines: Vec> = constructed_file .hunks .iter() - .flat_map(|h| h.lines.iter()) + .flat_map(|hunk| { + hunk.lines.iter().map(move |line| AvailableTouchedLine { + line, + model_id: hunk.model_id.as_deref(), + }) + }) .collect(); let mut used_lines = vec![false; available_lines.len()]; @@ -217,6 +227,7 @@ pub fn intersect_patches( // numbers have drifted. let mut result_hunks: Vec = Vec::new(); for post_commit_hunk in &post_commit_file.hunks { + let mut matched_model_id: Option = None; let overlapping_lines: Vec = post_commit_hunk .lines .iter() @@ -227,6 +238,9 @@ pub fn intersect_patches( line, touched_lines_match_exact, ) { + if matched_model_id.is_none() { + matched_model_id = available_lines[index].model_id.map(str::to_string); + } used_lines[index] = true; return true; } @@ -237,6 +251,9 @@ pub fn intersect_patches( line, touched_lines_match_historical, ) { + if matched_model_id.is_none() { + matched_model_id = available_lines[index].model_id.map(str::to_string); + } used_lines[index] = true; return true; } @@ -255,6 +272,7 @@ pub fn intersect_patches( old_count: post_commit_hunk.old_count, new_start: post_commit_hunk.new_start, new_count: post_commit_hunk.new_count, + model_id: matched_model_id, lines: overlapping_lines, }); } @@ -278,8 +296,13 @@ pub fn intersect_patches( } } +struct AvailableTouchedLine<'a> { + line: &'a TouchedLine, + model_id: Option<&'a str>, +} + fn find_available_line_match( - available_lines: &[&TouchedLine], + available_lines: &[AvailableTouchedLine<'_>], used_lines: &[bool], target: &TouchedLine, matcher: fn(&TouchedLine, &TouchedLine) -> bool, @@ -288,7 +311,7 @@ fn find_available_line_match( .iter() .enumerate() .find_map(|(index, candidate)| { - (!used_lines[index] && matcher(candidate, target)).then_some(index) + (!used_lines[index] && matcher(candidate.line, target)).then_some(index) }) } @@ -368,12 +391,14 @@ pub fn combine_patches(patches: &[ParsedPatch]) -> ParsedPatch { type LineKey = (TouchedLineKind, u64, String); /// Hunk metadata key: (`old_start`, `old_count`, `new_start`, `new_count`). type HunkMeta = (u64, u64, u64, u64); + /// Hunk provenance key: hunk metadata + source `model_id`. + type HunkGroupKey = (HunkMeta, Option); #[allow(clippy::type_complexity)] struct FileAcc { old_path: String, kind: FileChangeKind, - lines: HashMap, + lines: HashMap, } let mut file_order: Vec = Vec::new(); @@ -400,9 +425,11 @@ pub fn combine_patches(patches: &[ParsedPatch]) -> ParsedPatch { hunk.new_start, hunk.new_count, ); + let hunk_group_key: HunkGroupKey = (hunk_meta, hunk.model_id.clone()); for line in &hunk.lines { let line_key = (line.kind, line.line_number, line.content.clone()); - acc.lines.insert(line_key, (line.clone(), hunk_meta)); + acc.lines + .insert(line_key, (line.clone(), hunk_group_key.clone())); } } } @@ -414,17 +441,19 @@ pub fn combine_patches(patches: &[ParsedPatch]) -> ParsedPatch { let acc = files.remove(&path).unwrap(); // Group surviving lines by their hunk metadata. - let mut hunk_groups: HashMap> = HashMap::new(); - for (_line_key, (line, hunk_meta)) in acc.lines { - hunk_groups.entry(hunk_meta).or_default().push(line); + let mut hunk_groups: HashMap> = HashMap::new(); + for (_line_key, (line, hunk_group_key)) in acc.lines { + hunk_groups.entry(hunk_group_key).or_default().push(line); } // Sort hunk groups by old_start for deterministic output. let mut sorted_hunks: Vec<_> = hunk_groups.into_iter().collect(); - sorted_hunks.sort_by_key(|(meta, _)| meta.0); + sorted_hunks.sort_by_key(|((meta, model_id), _)| { + (meta.0, meta.1, meta.2, meta.3, model_id.clone()) + }); let mut hunks = Vec::new(); - for (meta, mut lines) in sorted_hunks { + for ((meta, model_id), mut lines) in sorted_hunks { // Sort lines within each hunk: by line_number, then Removed before // Added, then by content for full determinism. lines.sort_by(|a, b| { @@ -448,6 +477,7 @@ pub fn combine_patches(patches: &[ParsedPatch]) -> ParsedPatch { old_count: meta.1, new_start: meta.2, new_count: meta.3, + model_id, lines, }); } @@ -838,6 +868,7 @@ where old_count, new_start, new_count, + model_id: None, lines: touched_lines, }) } diff --git a/context/cli/patch-service.md b/context/cli/patch-service.md index 710e95bd..7714d532 100644 --- a/context/cli/patch-service.md +++ b/context/cli/patch-service.md @@ -50,7 +50,7 @@ Both functions wrap `serde_json::from_str`/`serde_json::from_slice` and map serd - **File matching**: files are matched by post-change path identity — exact `new_path` equality, or absolute-vs-relative path variants whose normalized path segments share the same relative suffix - **Touched-line matching**: matching prefers exact identity (`kind`, `line_number`, and `content`); when no exact match exists, it falls back to historical reconstruction matching by `kind` and `content` only so canonical post-commit patches can still intersect with earlier incremental diffs whose line numbers drifted -- **Result structure**: only files with at least one overlapping touched line appear in the result; hunks with no overlapping lines are excluded; hunk metadata (`old_start`, `old_count`, `new_start`, `new_count`) is preserved from the second patch (`b`) so the result keeps the target patch shape +- **Result structure**: only files with at least one overlapping touched line appear in the result; hunks with no overlapping lines are excluded; hunk range metadata (`old_start`, `old_count`, `new_start`, `new_count`) is preserved from the second patch (`b`) so the result keeps the target patch shape, while hunk `model_id` provenance is inherited from the matched hunk in the first patch (`a`) when available (and remains `None` when matched constructed provenance is absent) - **Determinism**: the same inputs always produce the same output - **Equivalent-hunk behavior**: semantically identical hunks still intersect when they differ only in surrounding context windows, hunk header ranges, or absolute-vs-relative `Index:` path spelling, as long as their touched-line identities match exactly - **Consumed by**: the post-commit hook runtime combines recent DB diff-trace patches and then intersects with the current commit patch (see `agent-trace-hooks-command-routing.md`). Previously listed as "not yet wired" before T04. @@ -61,7 +61,7 @@ Both functions wrap `serde_json::from_str`/`serde_json::from_slice` and map serd - **File matching**: files are grouped by `new_path`; file metadata (`old_path`, `kind`) is taken from the last patch that contributed to each file - **Touched-line identity and deduplication**: touched lines are deduplicated by identity (`kind`, `line_number`, `content`); when multiple patches describe the same file and logical touched-line slot, the later input's entry is retained -- **Hunk reconstruction**: surviving lines are grouped by their hunk metadata from the last contributing patch; hunks are ordered by `old_start`; lines within each hunk are ordered by `line_number` with `Removed` before `Added` at the same position, then by `content` for full determinism +- **Hunk reconstruction and provenance**: surviving lines are grouped by their hunk metadata from the last contributing patch; each reconstructed hunk preserves that winning hunk's `model_id` provenance; hunks are ordered by `old_start`; lines within each hunk are ordered by `line_number` with `Removed` before `Added` at the same position, then by `content` for full determinism - **File ordering**: files appear in the result in the order they are first encountered across the input patches - **Determinism**: the same inputs in the same order always produce the same output - **Consumed by**: the post-commit hook runtime combines recent DB diff-trace patches before intersecting (see `agent-trace-hooks-command-routing.md`). Previously listed as "not yet wired" before T04. diff --git a/context/context-map.md b/context/context-map.md index 70db59fb..f2bf0c38 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -11,7 +11,7 @@ Feature/domain context: - `context/cli/cli-command-surface.md` (CLI command surface including top-level help with ASCII art banner and gradient rendering, setup install flow, WorkOS device authorization flow + token storage behavior, attribution-only hook routing with DB-backed `diff-trace` dual persistence, setup-owned local DB + Agent Trace DB bootstrap plus doctor DB health coverage, nested flake release package/app installability, and Cargo local install + crates.io readiness policy; `sce sync` command wiring is deferred to `0.4.0`; migrated runtime command structs for help/version/completion/auth/config/setup/doctor/hooks are owned by their respective `services/{name}/command.rs` files, while clap-to-runtime conversion lives in `services/parse/command_runtime.rs`) - `context/cli/default-path-catalog.md` (canonical production CLI path-ownership contract centered on `cli/src/services/default_paths.rs`, including persisted, repo-relative, embedded-asset, install/runtime, hook, and context-path families plus the regression guard that keeps production path ownership centralized) -- `context/cli/patch-service.md` (standalone patch domain model, parser, JSON load helpers, and set operations in `cli/src/services/patch.rs` for in-memory parsed unified-diff representation, capturing only touched lines plus minimal per-file/per-hunk metadata, supporting both `Index:` SVN-style and `diff --git` git-style formats, with `ParseError` for actionable malformed-input diagnostics, `PatchLoadError`/`load_patch_from_json`/`load_patch_from_json_bytes` for storage-agnostic JSON reconstruction, `intersect_patches` for target-shaped overlap with exact-match-first and historical `kind`+`content` fallback semantics, and `combine_patches` for ordered patch combination with later-wins conflict resolution; `parse_patch`, `intersect_patches`, and `combine_patches` are consumed by the active post-commit hook runtime) +- `context/cli/patch-service.md` (standalone patch domain model, parser, JSON load helpers, and set operations in `cli/src/services/patch.rs` for in-memory parsed unified-diff representation, capturing only touched lines plus minimal per-file/per-hunk metadata, supporting both `Index:` SVN-style and `diff --git` git-style formats, with `ParseError` for actionable malformed-input diagnostics, `PatchLoadError`/`load_patch_from_json`/`load_patch_from_json_bytes` for storage-agnostic JSON reconstruction, `intersect_patches` for target-shaped overlap with exact-match-first and historical `kind`+`content` fallback semantics plus matched-constructed-hunk `model_id` provenance inheritance, and `combine_patches` for ordered patch combination with later-wins conflict resolution plus winning-hunk `model_id` provenance inheritance; `parse_patch`, `intersect_patches`, and `combine_patches` are consumed by the active post-commit hook runtime) - `context/cli/styling-service.md` (CLI text-mode output styling with `owo-colors` and `comfy-table`, TTY/`NO_COLOR` policy, shared helper API for human-facing surfaces, and per-column right-to-left RGB gradient banner rendering) - `context/cli/config-precedence-contract.md` (implemented `sce config` show/validate command contract, deterministic `flags > env > config file > defaults` resolution order, canonical `$schema` acceptance for startup-loaded `sce/config.json` files, shared auth-key env/config/optional baked-default support starting with `workos_client_id`, shared runtime resolution for flat logging observability keys, canonical Pkl-generated `sce/config.json` schema ownership plus CLI embedding/reuse contract, config-file selection order, `show` provenance output, trimmed `validate` output contract, and opt-in compiled-binary config-precedence E2E coverage contract) - `context/cli/capability-traits.md` (current broad CLI dependency-injection capability seam in `cli/src/services/capabilities.rs`, including `FsOps`/`StdFsOps`, `GitOps`/`ProcessGitOps`, git root/hooks resolution behavior, AppContext wiring with capability accessors plus repo-root-scoped context derivation, and test-only unimplemented stubs; current service internals do not consume these traits until later lifecycle migration tasks) @@ -49,6 +49,7 @@ Feature/domain context: - `context/sce/agent-trace-minimal-generator.md` (implemented library-only minimal agent-trace generator seam at `cli/src/services/agent_trace.rs`, producing a JSON payload with top-level `version`, UUIDv7 `id` derived from commit-time metadata, caller-provided commit-time `timestamp`, top-level `vcs` metadata (`type = git`, `revision` from metadata input), and per-file trace data from patch inputs via `intersect_patches(constructed_patch, post_commit_patch)` then `post_commit_patch`-anchored hunk classification into `ai`/`mixed`/`unknown` contributor categories, serialized per conversation as nested `contributor.type` plus one derived `ranges[{start_line,end_line}]` entry per post-commit hunk) - `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime behavior: disabled-default commit-msg attribution, no-op `pre-commit`/`post-rewrite` entrypoints, active `post-commit` intersection entrypoint capturing current commit patch, querying recent `diff_traces` from past 7 days, combining valid patches via `patch::combine_patches`, intersecting via `patch::intersect_patches`, persisting results to `post_commit_patch_intersections`, and persisting built post-commit Agent Trace payloads to AgentTraceDb `agent_traces` (DB-only), plus `diff-trace` STDIN intake with required-field validation and dual persistence to AgentTraceDb and collision-safe `context/tmp/-000000-diff-trace.json` artifacts) - `context/sce/agent-trace-minimal-generator.md` (implemented 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-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 optional `contributor.model_id` omitted when provenance is missing and one derived `ranges[{start_line,end_line}]` entry per post-commit hunk) - `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime behavior: disabled-default commit-msg attribution, no-op `pre-commit`/`post-rewrite` entrypoints, active `post-commit` intersection entrypoint capturing current commit patch, querying recent `diff_traces` from past 7 days, combining valid patches via `patch::combine_patches`, intersecting via `patch::intersect_patches`, persisting results to `post_commit_patch_intersections`, 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) diff --git a/context/glossary.md b/context/glossary.md index 484b74f5..1c28395b 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -141,7 +141,7 @@ - `PatchLoadError`: Error type in `cli/src/services/patch.rs` produced when `load_patch_from_json` or `load_patch_from_json_bytes` encounters invalid JSON or a payload that does not match the expected `ParsedPatch` structure; carries an actionable `message` field - `load_patch_from_json`: Public function in `cli/src/services/patch.rs` that reconstructs a `ParsedPatch` from a JSON string; storage-agnostic entrypoint for callers who have already read serialized JSON content from a database, file, or other source - `load_patch_from_json_bytes`: Public function in `cli/src/services/patch.rs` that reconstructs a `ParsedPatch` from JSON bytes; bytes-oriented counterpart to `load_patch_from_json` for callers working with raw byte data -- `intersect_patches`: Public function in `cli/src/services/patch.rs` that computes target-shaped touched-line intersection between two `ParsedPatch` values; takes `constructed_patch` and `post_commit_patch` as inputs, matches files by post-change path identity (exact `new_path` equality or absolute-vs-relative suffix-equivalent path segments), prefers exact touched-line matching by `kind` + `line_number` + `content`, falls back to historical matching by `kind` + `content` when line numbers drift across intermediate edits, and returns a `ParsedPatch` shaped from `post_commit_patch`'s file/hunk metadata; consumed by the active post-commit hook runtime +- `intersect_patches`: Public function in `cli/src/services/patch.rs` that computes target-shaped touched-line intersection between two `ParsedPatch` values; takes `constructed_patch` and `post_commit_patch` as inputs, matches files by post-change path identity (exact `new_path` equality or absolute-vs-relative suffix-equivalent path segments), prefers exact touched-line matching by `kind` + `line_number` + `content`, falls back to historical matching by `kind` + `content` when line numbers drift across intermediate edits, and returns a `ParsedPatch` shaped from `post_commit_patch` file/hunk ranges while inheriting result-hunk `model_id` from matched `constructed_patch` hunk provenance when available; consumed by the active post-commit hook runtime - `combine_patches`: Public function in `cli/src/services/patch.rs` that merges multiple `ParsedPatch` values into one deterministic result with later-input-wins semantics; groups files by `new_path`, deduplicates touched lines by identity (`kind` + `line_number` + `content`) with later patches winning, preserves file metadata and hunk metadata from the last contributing patch, orders files by first encounter and hunks by `old_start`; consumed by the active post-commit hook runtime before intersection - `HunkContributor`: Enum in `cli/src/services/agent_trace.rs` classifying a `post_commit_patch` hunk's origin relative to the intersection patch `intersection_patch = intersect_patches(constructed_patch, post_commit_patch)`: `Ai` (exact line-by-line match), `Mixed` (same slot but different content), `Unknown` (no corresponding slot in `intersection_patch`); serialized as `snake_case` JSON strings - `Conversation`: Struct in `cli/src/services/agent_trace.rs` representing one per-hunk entry in the minimal agent-trace payload, carrying a nested `contributor` object (`{ "type": HunkContributor }`) plus `ranges`, where the current implementation emits exactly one `{ start_line, end_line }` entry derived from the `post_commit_patch` hunk diff --git a/context/sce/agent-trace-db.md b/context/sce/agent-trace-db.md index 3c6a9334..5874fe5d 100644 --- a/context/sce/agent-trace-db.md +++ b/context/sce/agent-trace-db.md @@ -97,9 +97,9 @@ Post-commit intersection rows are written by the active `post-commit` hook flow, `AgentTraceDb::recent_diff_trace_patches(cutoff_time_ms, end_time_ms)` supports the post-commit comparison flow without changing `diff_traces` writes: -- SQL reads `id`, `time_ms`, `session_id`, and `patch` from `diff_traces` where `time_ms >= cutoff_time_ms AND time_ms <= end_time_ms`. +- SQL reads `id`, `time_ms`, `session_id`, `patch`, and nullable `model_id` from `diff_traces` where `time_ms >= cutoff_time_ms AND time_ms <= end_time_ms`. - Rows are ordered by `time_ms ASC, id ASC` for deterministic chronological processing. -- Valid row patches are parsed through `cli/src/services/patch.rs` `parse_patch` and returned as `ParsedDiffTracePatch` records. +- Valid row patches are parsed through `cli/src/services/patch.rs` `parse_patch`, then each produced `PatchHunk` is annotated with the originating row `model_id` (`Some(value)` propagated verbatim, `NULL` propagated as `None`), and returned as `ParsedDiffTracePatch` records. - Malformed recent row patches are returned as `SkippedDiffTracePatch` records with deterministic parse-error reasons; malformed historical rows do not fail the operation. - `RecentDiffTracePatches::loaded_count()` and `skipped_count()` expose accounting for later hook output and persistence metadata. diff --git a/context/sce/agent-trace-hooks-command-routing.md b/context/sce/agent-trace-hooks-command-routing.md index 61d9f42d..66a275ed 100644 --- a/context/sce/agent-trace-hooks-command-routing.md +++ b/context/sce/agent-trace-hooks-command-routing.md @@ -30,6 +30,7 @@ - **`post-commit` is an active intersection entrypoint** (see [agent-trace-db.md](agent-trace-db.md)): - Captures the current commit's patch from git using `capture_post_commit_patch_from_git()`. - Queries recent `diff_traces` patches from the past 7 days via `AgentTraceDb::recent_diff_trace_patches()`. + - Recent-row patch parsing carries nullable row `model_id` into each produced `PatchHunk`, so combined/intersection patch inputs retain per-hunk model provenance for downstream Agent Trace attribution building. - Combines valid recent patches in chronological order via `patch::combine_patches`. - Intersects the combined recent patch with the post-commit patch via `patch::intersect_patches`. - Persists the serialized intersection result to `post_commit_patch_intersections` table with commit metadata (OID, timestamp), window bounds (cutoff_ms, end_ms), and loaded/skipped counts. diff --git a/context/sce/agent-trace-minimal-generator.md b/context/sce/agent-trace-minimal-generator.md index 1eff934d..406222ef 100644 --- a/context/sce/agent-trace-minimal-generator.md +++ b/context/sce/agent-trace-minimal-generator.md @@ -12,27 +12,28 @@ Given a `constructed_patch` (AI candidate) and a `post_commit_patch` (canonical - **`ai`** — `intersection_patch` hunk exists with identical touched lines (same count, kind, `line_number`, content, order). - **`mixed`** — `intersection_patch` hunk exists at the same slot but content differs. - **`unknown`** — no `intersection_patch` hunk at the same `old_start` slot. -4. Emit one `Conversation` per `post_commit_patch` hunk, one `TraceFile` per `post_commit_patch` file. +4. Map `Conversation.contributor.model_id` from the matched `intersection_patch` hunk when contributor type is `ai` or `mixed`; omit `model_id` when provenance is missing (`None`). +5. Emit one `Conversation` per `post_commit_patch` hunk, one `TraceFile` per `post_commit_patch` file. ## Domain types | Type | Purpose | |---|---| | `HunkContributor` | Enum: `Ai`, `Mixed`, `Unknown` | -| `Contributor` | Nested per-conversation object carrying `type: HunkContributor` | +| `Contributor` | Nested per-conversation object carrying `type: HunkContributor` and optional `model_id` omitted when absent | | `LineRange` | New-file line span with `start_line` + `end_line` | | `Conversation` | Per-hunk entry: nested contributor + `ranges` (currently exactly one range derived from `post_commit_patch`) | | `TraceFile` | Per-file entry: path + conversations | | `AgentTraceVcs` | Top-level VCS metadata object carrying `type` + `revision` | | `AgentTrace` | Top-level payload: `version`, `id`, `timestamp`, `vcs`, `files` | -All types are `serde`-serializable with `snake_case` field naming. `Conversation.contributor` serializes as a nested object with a JSON field named `type`. +All types are `serde`-serializable with `snake_case` field naming. `Conversation.contributor` serializes as a nested object with a JSON field named `type`; `model_id` is present only when a concrete value exists. ## Payload shape Current output includes top-level metadata fields with this contract: -- `version` is fixed to `"0.1.0"` and follows strict numeric `x.y.z` +- `version` is fixed to `"0.1"` - `id` is generated per `build_agent_trace(...)` call as a UUIDv7 string derived from the same commit-time moment used for `timestamp` - `timestamp` is sourced from explicit commit metadata input (`AgentTraceMetadataInput.commit_timestamp`) and must be RFC 3339 - `vcs.type` is fixed to `"git"` @@ -40,7 +41,7 @@ Current output includes top-level metadata fields with this contract: ```json { - "version": "0.1.0", + "version": "0.1", "id": "01962f15-2d3d-7c85-9f6b-0a8b4f6b2fd1", "timestamp": "2026-04-23T10:20:30Z", "vcs": { @@ -52,7 +53,7 @@ Current output includes top-level metadata fields with this contract: "path": "src/example.ts", "conversations": [ { - "contributor": { "type": "ai" }, + "contributor": { "type": "ai", "model_id": "model-ai" }, "ranges": [ { "start_line": 10, @@ -74,7 +75,8 @@ Current output includes top-level metadata fields with this contract: ## Test fixture contract - Golden fixtures under `cli/src/services/agent_trace/fixtures/**/golden.json` pin deterministic literal values for top-level `id` and `timestamp`. -- Tests still validate runtime metadata behavior explicitly (`id` parses as UUIDv7 and `timestamp` equals provided commit metadata), then normalize those runtime values to the deterministic fixture literals before whole-payload golden comparison. +- Tests still validate runtime metadata behavior explicitly (`id` parses as UUIDv7 and `timestamp` equals provided commit metadata), then normalize those runtime values to the deterministic fixture literals before payload comparison. +- Because the embedded schema currently expects `contributor.model_id` as a string when present, golden/schema checks operate on a model-id-stripped comparison view, while dedicated assertions validate contributor `model_id` mapping semantics (`ai`/`mixed` populated when provenance exists, omitted when absent). ## Relationship to existing patch service From 7db480937ac961752198af5a6066b15e7910b457 Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Mon, 18 May 2026 16:59:11 +0200 Subject: [PATCH 08/11] agent-trace: Fix builder runtime documentation drift Update Agent Trace docs to reflect that the minimal builder is consumed by the active post-commit hook flow, with contributor model provenance and validated AgentTraceDb persistence documented consistently. Co-authored-by: SCE --- context/context-map.md | 7 ++----- context/glossary.md | 4 ++-- context/sce/agent-trace-minimal-generator.md | 4 ++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/context/context-map.md b/context/context-map.md index f2bf0c38..21ee1bc6 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -46,11 +46,8 @@ Feature/domain context: - `context/sce/agent-trace-core-schema-migrations.md` (historical reference for removed local DB schema bootstrap behavior; T03 now implements the actual local DB with migrations) - `context/sce/agent-trace-retry-queue-observability.md` (inactive local-hook retry path plus historical retry/metrics reference) - `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md` (T01 Local Hooks MVP production contract freeze and deterministic gap matrix for `agent-trace-local-hooks-production-mvp`) -- `context/sce/agent-trace-minimal-generator.md` (implemented library-only minimal agent-trace generator seam at `cli/src/services/agent_trace.rs`, producing a JSON payload with top-level `version`, UUIDv7 `id` derived from commit-time metadata, caller-provided commit-time `timestamp`, top-level `vcs` metadata (`type = git`, `revision` from metadata input), and per-file trace data from patch inputs via `intersect_patches(constructed_patch, post_commit_patch)` then `post_commit_patch`-anchored hunk classification into `ai`/`mixed`/`unknown` contributor categories, serialized per conversation as nested `contributor.type` plus one derived `ranges[{start_line,end_line}]` entry per post-commit hunk) -- `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime behavior: disabled-default commit-msg attribution, no-op `pre-commit`/`post-rewrite` entrypoints, active `post-commit` intersection entrypoint capturing current commit patch, querying recent `diff_traces` from past 7 days, combining valid patches via `patch::combine_patches`, intersecting via `patch::intersect_patches`, persisting results to `post_commit_patch_intersections`, and persisting built post-commit Agent Trace payloads to AgentTraceDb `agent_traces` (DB-only), plus `diff-trace` STDIN intake with required-field validation and dual persistence to AgentTraceDb and collision-safe `context/tmp/-000000-diff-trace.json` artifacts) -- `context/sce/agent-trace-minimal-generator.md` (implemented 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-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 optional `contributor.model_id` omitted when provenance is missing and one derived `ranges[{start_line,end_line}]` entry per post-commit hunk) -- `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime behavior: disabled-default commit-msg attribution, no-op `pre-commit`/`post-rewrite` entrypoints, active `post-commit` intersection entrypoint capturing current commit patch, querying recent `diff_traces` from past 7 days, combining valid patches via `patch::combine_patches`, intersecting via `patch::intersect_patches`, persisting results to `post_commit_patch_intersections`, 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/agent-trace-minimal-generator.md` (implemented minimal Agent Trace builder seam at `cli/src/services/agent_trace.rs`, used by the active post-commit hook flow to produce strict `0.1.0` JSON payloads with UUIDv7 `id` derived from commit-time metadata, caller-provided commit-time `timestamp`, top-level `vcs` metadata (`type = git`, `revision` from metadata input), and per-file trace data from patch inputs via `intersect_patches(constructed_patch, post_commit_patch)` then `post_commit_patch`-anchored hunk classification into `ai`/`mixed`/`unknown` contributor categories, serialized per conversation as nested `contributor.type` plus optional `contributor.model_id` omitted when provenance is missing and one derived `ranges[{start_line,end_line}]` entry per post-commit hunk) +- `context/sce/agent-trace-hooks-command-routing.md` (implemented `sce hooks` command routing plus current runtime behavior: disabled-default commit-msg attribution, no-op `pre-commit`/`post-rewrite` entrypoints, active `post-commit` intersection entrypoint capturing current commit patch, querying recent `diff_traces` from past 7 days, combining valid patches via `patch::combine_patches`, intersecting via `patch::intersect_patches`, persisting results to `post_commit_patch_intersections`, building and schema-validating post-commit Agent Trace payloads, and persisting validated payloads to AgentTraceDb `agent_traces` (DB-only), plus `diff-trace` STDIN intake with required non-empty `sessionID`/`diff`/`model_id` and required `u64` `time` validation, dual persistence to AgentTraceDb, and collision-safe `context/tmp/-000000-diff-trace.json` artifacts) - `context/sce/automated-profile-contract.md` (deterministic gate policy for automated OpenCode profile, including 10 gate categories, permission mappings, automated `/commit` single-commit execution behavior, and automated profile constraints) - `context/sce/bash-tool-policy-enforcement-contract.md` (approved bash-tool blocking contract plus the implementation target for generated OpenCode enforcement, including config schema, argv-prefix matching, fixed preset catalog/messages, and precedence rules) - `context/sce/generated-opencode-plugin-registration.md` (current generated OpenCode plugin-registration contract, canonical Pkl ownership, generated manifest/plugin paths including `sce-bash-policy` + `sce-agent-trace`, and TypeScript source ownership; Claude bash-policy enforcement has been removed from generated outputs) diff --git a/context/glossary.md b/context/glossary.md index 1c28395b..aa2a8df3 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -144,13 +144,13 @@ - `intersect_patches`: Public function in `cli/src/services/patch.rs` that computes target-shaped touched-line intersection between two `ParsedPatch` values; takes `constructed_patch` and `post_commit_patch` as inputs, matches files by post-change path identity (exact `new_path` equality or absolute-vs-relative suffix-equivalent path segments), prefers exact touched-line matching by `kind` + `line_number` + `content`, falls back to historical matching by `kind` + `content` when line numbers drift across intermediate edits, and returns a `ParsedPatch` shaped from `post_commit_patch` file/hunk ranges while inheriting result-hunk `model_id` from matched `constructed_patch` hunk provenance when available; consumed by the active post-commit hook runtime - `combine_patches`: Public function in `cli/src/services/patch.rs` that merges multiple `ParsedPatch` values into one deterministic result with later-input-wins semantics; groups files by `new_path`, deduplicates touched lines by identity (`kind` + `line_number` + `content`) with later patches winning, preserves file metadata and hunk metadata from the last contributing patch, orders files by first encounter and hunks by `old_start`; consumed by the active post-commit hook runtime before intersection - `HunkContributor`: Enum in `cli/src/services/agent_trace.rs` classifying a `post_commit_patch` hunk's origin relative to the intersection patch `intersection_patch = intersect_patches(constructed_patch, post_commit_patch)`: `Ai` (exact line-by-line match), `Mixed` (same slot but different content), `Unknown` (no corresponding slot in `intersection_patch`); serialized as `snake_case` JSON strings -- `Conversation`: Struct in `cli/src/services/agent_trace.rs` representing one per-hunk entry in the minimal agent-trace payload, carrying a nested `contributor` object (`{ "type": HunkContributor }`) plus `ranges`, where the current implementation emits exactly one `{ start_line, end_line }` entry derived from the `post_commit_patch` hunk +- `Conversation`: Struct in `cli/src/services/agent_trace.rs` representing one per-hunk entry in the minimal agent-trace payload, carrying a nested `contributor` object (`type` plus optional `model_id`) plus `ranges`, where the current implementation emits exactly one `{ start_line, end_line }` entry derived from the `post_commit_patch` hunk - `TraceFile`: Struct in `cli/src/services/agent_trace.rs` representing one per-file entry in the minimal agent-trace payload, carrying `path` (from `post_commit_patch`'s `new_path`) plus `conversations` (one per `post_commit_patch` hunk) - `AgentTraceVcs`: Top-level VCS metadata struct in `cli/src/services/agent_trace.rs` carrying `type` and `revision`; current builder behavior fixes `type` to `"git"` and maps revision from caller metadata. - `AgentTrace`: Top-level struct in `cli/src/services/agent_trace.rs` representing the minimal agent-trace payload, carrying top-level `version` (fixed to `0.1.0`, strict numeric `x.y.z`), `id` (UUIDv7 string derived from the same commit-time moment used for `timestamp` in `build_agent_trace(...)`), `timestamp` (caller-provided commit timestamp via `AgentTraceMetadataInput.commit_timestamp`, validated as RFC 3339), `vcs` (`AgentTraceVcs`), and `files` (`Vec`, one per `post_commit_patch` file); `serde`-serializable with `snake_case` field naming - `classify_hunk`: Public function in `cli/src/services/agent_trace.rs` that classifies a single `post_commit_patch` hunk against `intersection_patch` hunks by matching on `old_start` slot, returning `HunkContributor::Ai` for exact line-by-line match, `Mixed` for same-slot-but-different-content, or `Unknown` when no matching slot exists - `AgentTraceMetadataInput`: Metadata input struct in `cli/src/services/agent_trace.rs` that carries `commit_timestamp` (RFC 3339 commit-time value used as `AgentTrace.timestamp`) and `commit_revision` (mapped to `AgentTrace.vcs.revision`). -- `build_agent_trace`: Public function in `cli/src/services/agent_trace.rs` that computes `intersection_patch = intersect_patches(constructed_patch, post_commit_patch)`, iterates over `post_commit_patch`'s files and hunks, classifies each hunk against `intersection_patch`, validates `AgentTraceMetadataInput.commit_timestamp` as RFC 3339, derives UUIDv7 `AgentTrace.id` from that same commit-time moment, and returns `Result` with top-level metadata fields plus one `Conversation` per `post_commit_patch` hunk; library-only, not wired into CLI command dispatch +- `build_agent_trace`: Public function in `cli/src/services/agent_trace.rs` that computes `intersection_patch = intersect_patches(constructed_patch, post_commit_patch)`, iterates over `post_commit_patch`'s files and hunks, classifies each hunk against `intersection_patch`, validates `AgentTraceMetadataInput.commit_timestamp` as RFC 3339, derives UUIDv7 `AgentTrace.id` from that same commit-time moment, and returns `Result` with top-level metadata fields plus one `Conversation` per `post_commit_patch` hunk; consumed by the active post-commit hook flow, with no standalone `sce agent-trace` command surface - `agent-trace plugin diff extraction seam`: Exported helper `extractDiffTracePayload` in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` that reads `input.event` and returns `{ sessionID, diff, time, model_id }` only when the event is `message.updated` with `properties.info.role === "user"`; extracts `sessionID` from `info.sessionID` (falling back to `"unknown"` when absent or empty), joins 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/sce/agent-trace-minimal-generator.md b/context/sce/agent-trace-minimal-generator.md index 406222ef..7b4b38dc 100644 --- a/context/sce/agent-trace-minimal-generator.md +++ b/context/sce/agent-trace-minimal-generator.md @@ -1,6 +1,6 @@ # Minimal agent-trace generator seam -Library-only Rust seam at `cli/src/services/agent_trace.rs` that produces the minimal agent-trace JSON shape from patch data. +Rust library seam at `cli/src/services/agent_trace.rs` that produces the minimal agent-trace JSON shape from patch data and is consumed by the active post-commit hook flow before AgentTraceDb persistence. ## Contract @@ -84,7 +84,7 @@ Consumes `intersect_patches` and `ParsedPatch`/`PatchHunk`/`TouchedLine` types f ## Out of scope -CLI command surface, hook/runtime integration (including post-commit wiring), persistence, OpenCode plugin behavior, non-MVP payload enrichment. +Standalone CLI command surface, OpenCode plugin behavior, non-MVP payload enrichment. Post-commit hook/runtime integration and persistence are owned by [agent-trace-hooks-command-routing.md](agent-trace-hooks-command-routing.md) and [agent-trace-db.md](agent-trace-db.md). ## See also From cddc89da3eee7b0e10550cd3d9e7d26314b0dba1 Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Tue, 19 May 2026 14:11:33 +0200 Subject: [PATCH 09/11] config-lib: Consolidate plugin support under shared package root Move the Bun/TypeScript package root from individual plugin subdirectories to shared config/lib/. Delete duplicate nested package metadata, lockfiles, and tsconfig files under agent-trace-plugin/ and bash-policy-plugin/. Pin @opencode-ai/plugin@1.15.4 once in the shared package.json and provide strict-mode TypeScript coverage for both plugin trees. Co-authored-by: SCE --- .opencode/plugins/sce-agent-trace.ts | 237 +++++++++--------- biome.json | 4 +- config/.opencode/plugins/sce-agent-trace.ts | 237 +++++++++--------- .../.opencode/plugins/sce-agent-trace.ts | 237 +++++++++--------- .../opencode-sce-agent-trace-plugin.ts | 237 +++++++++--------- config/lib/agent-trace-plugin/package.json | 7 - config/lib/agent-trace-plugin/tsconfig.json | 15 -- config/lib/bash-policy-plugin/bun.lock | 28 --- config/lib/{agent-trace-plugin => }/bun.lock | 8 +- .../lib/{bash-policy-plugin => }/package.json | 2 +- config/lib/tsconfig.json | 16 ++ context/architecture.md | 4 +- context/context-map.md | 2 +- context/glossary.md | 9 +- context/overview.md | 4 +- context/patterns.md | 3 +- .../plans/config-lib-shared-plugin-package.md | 162 ++++++++++++ .../bash-tool-policy-enforcement-contract.md | 5 +- .../opencode-agent-trace-plugin-runtime.md | 12 +- flake.nix | 30 +-- 20 files changed, 688 insertions(+), 571 deletions(-) delete mode 100644 config/lib/agent-trace-plugin/package.json delete mode 100644 config/lib/agent-trace-plugin/tsconfig.json delete mode 100644 config/lib/bash-policy-plugin/bun.lock rename config/lib/{agent-trace-plugin => }/bun.lock (88%) rename config/lib/{bash-policy-plugin => }/package.json (69%) create mode 100644 config/lib/tsconfig.json create mode 100644 context/plans/config-lib-shared-plugin-package.md diff --git a/.opencode/plugins/sce-agent-trace.ts b/.opencode/plugins/sce-agent-trace.ts index abfae8fe..aca80ea2 100644 --- a/.opencode/plugins/sce-agent-trace.ts +++ b/.opencode/plugins/sce-agent-trace.ts @@ -8,146 +8,143 @@ const REQUIRED_EVENTS = new Set(["message.updated"]); const ALL_CAPTURED_EVENTS = REQUIRED_EVENTS; type TraceInput = { - event?: OpenCodeEvent; + event?: OpenCodeEvent; }; type DiffTracePayload = { - sessionID: string; - diff: string; - time: number; - model_id: string; + sessionID: string; + diff: string; + time: number; + model_id: string; }; function extractDiffTracePayload( - input: TraceInput, + input: TraceInput, ): DiffTracePayload | undefined { - const event = input.event; - if (event === undefined || event.type !== "message.updated") { - return undefined; - } - - const properties = event.properties; - if (typeof properties !== "object" || properties === null) { - return undefined; - } - - const propertiesObj = properties; - - // Access properties.info (the Message object) - const info = propertiesObj.info; - if (typeof info !== "object" || info === null) { - return undefined; - } - - const infoObj = info; - - // Only capture user messages (filter out assistant, system, etc.) - if (infoObj.role !== "user") { - return undefined; - } - - const sessionID = - typeof infoObj.sessionID === "string" && - infoObj.sessionID.trim().length > 0 - ? infoObj.sessionID - : "unknown"; - - const model = infoObj.model; - - // Access info.summary?.diffs via explicit checks - const summary = infoObj.summary; - const diffEntries = - typeof summary === "object" && summary !== null - ? (summary).diffs - : undefined; - - if (!Array.isArray(diffEntries) || diffEntries.length === 0) { - return undefined; - } - - const patches: string[] = []; - for (const entry of diffEntries) { - if (typeof entry !== "object" || entry === null) { - continue; - } - const entryObj = entry as {patch?:string}; - const patch = entryObj.patch || ""; - - patches.push(patch); - } - - if (patches.length === 0) { - return undefined; - } - - return { - sessionID, - diff: patches.join("\n"), - time: Date.now(), - model_id: `${model.providerID}/${model.modelID}`, - }; + const event = input.event; + if (event === undefined || event.type !== "message.updated") { + return undefined; + } + + const properties = event.properties; + if (typeof properties !== "object" || properties === null) { + return undefined; + } + + const propertiesObj = properties; + + // Access properties.info (the Message object) + const info = propertiesObj.info; + if (typeof info !== "object" || info === null) { + return undefined; + } + + const infoObj = info; + + // Only capture user messages (filter out assistant, system, etc.) + if (infoObj.role !== "user") { + return undefined; + } + + const sessionID = + typeof infoObj.sessionID === "string" && infoObj.sessionID.trim().length > 0 + ? infoObj.sessionID + : "unknown"; + + const model = infoObj.model; + + // Access info.summary?.diffs via explicit checks + const summary = infoObj.summary; + const diffEntries = + typeof summary === "object" && summary !== null ? summary.diffs : undefined; + + if (!Array.isArray(diffEntries) || diffEntries.length === 0) { + return undefined; + } + + const patches: string[] = []; + for (const entry of diffEntries) { + if (typeof entry !== "object" || entry === null) { + continue; + } + const entryObj = entry as { patch?: string }; + const patch = entryObj.patch || ""; + + patches.push(patch); + } + + if (patches.length === 0) { + return undefined; + } + + return { + sessionID, + diff: patches.join("\n"), + time: Date.now(), + model_id: `${model.providerID}/${model.modelID}`, + }; } function shouldCaptureEvent(eventType: string): boolean { - return ALL_CAPTURED_EVENTS.has(eventType); + return ALL_CAPTURED_EVENTS.has(eventType); } async function buildTrace(repoRoot: string, input: TraceInput): Promise { - const diffTracePayload = extractDiffTracePayload(input); + const diffTracePayload = extractDiffTracePayload(input); - if (diffTracePayload === undefined) { - return; - } + if (diffTracePayload === undefined) { + return; + } - await runDiffTraceHook(repoRoot, diffTracePayload); + await runDiffTraceHook(repoRoot, diffTracePayload); } async function runDiffTraceHook( - repoRoot: string, - payload: DiffTracePayload, + repoRoot: string, + payload: DiffTracePayload, ): Promise { - await new Promise((resolve, reject) => { - const child = spawn("sce", ["hooks", "diff-trace"], { - cwd: repoRoot, - stdio: ["pipe", "ignore", "inherit"], - }); - - child.on("error", reject); - - child.on("close", (code, signal) => { - if (code === 0) { - resolve(); - return; - } - - const reason = - signal === null ? `exit code ${String(code)}` : `signal ${signal}`; - reject( - new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), - ); - }); - - child.stdin.end(`${JSON.stringify(payload)}\n`); - }); + await new Promise((resolve, reject) => { + const child = spawn("sce", ["hooks", "diff-trace"], { + cwd: repoRoot, + stdio: ["pipe", "ignore", "inherit"], + }); + + child.on("error", reject); + + child.on("close", (code, signal) => { + if (code === 0) { + resolve(); + return; + } + + const reason = + signal === null ? `exit code ${String(code)}` : `signal ${signal}`; + reject( + new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), + ); + }); + + child.stdin.end(`${JSON.stringify(payload)}\n`); + }); } export const SceAgentTracePlugin: Plugin = async ({ directory, worktree }) => { - const repoRoot = worktree ?? directory ?? process.cwd(); - - return { - event: async (input) => { - const eventType = - typeof input.event === "object" && - input.event !== null && - typeof input.event.type === "string" - ? input.event.type - : undefined; - - if (eventType === undefined || !shouldCaptureEvent(eventType)) { - return; - } - - await buildTrace(repoRoot, input); - }, - }; + const repoRoot = worktree ?? directory ?? process.cwd(); + + return { + event: async (input) => { + const eventType = + typeof input.event === "object" && + input.event !== null && + typeof input.event.type === "string" + ? input.event.type + : undefined; + + if (eventType === undefined || !shouldCaptureEvent(eventType)) { + return; + } + + await buildTrace(repoRoot, input); + }, + }; }; diff --git a/biome.json b/biome.json index 7116d08c..21ce3f57 100644 --- a/biome.json +++ b/biome.json @@ -3,9 +3,9 @@ "files": { "includes": [ "npm/**", - "config/lib/bash-policy-plugin/**", + "config/lib/**", "!npm/node_modules", - "!config/lib/bash-policy-plugin/node_modules" + "!config/lib/node_modules" ] }, "formatter": { diff --git a/config/.opencode/plugins/sce-agent-trace.ts b/config/.opencode/plugins/sce-agent-trace.ts index abfae8fe..aca80ea2 100644 --- a/config/.opencode/plugins/sce-agent-trace.ts +++ b/config/.opencode/plugins/sce-agent-trace.ts @@ -8,146 +8,143 @@ const REQUIRED_EVENTS = new Set(["message.updated"]); const ALL_CAPTURED_EVENTS = REQUIRED_EVENTS; type TraceInput = { - event?: OpenCodeEvent; + event?: OpenCodeEvent; }; type DiffTracePayload = { - sessionID: string; - diff: string; - time: number; - model_id: string; + sessionID: string; + diff: string; + time: number; + model_id: string; }; function extractDiffTracePayload( - input: TraceInput, + input: TraceInput, ): DiffTracePayload | undefined { - const event = input.event; - if (event === undefined || event.type !== "message.updated") { - return undefined; - } - - const properties = event.properties; - if (typeof properties !== "object" || properties === null) { - return undefined; - } - - const propertiesObj = properties; - - // Access properties.info (the Message object) - const info = propertiesObj.info; - if (typeof info !== "object" || info === null) { - return undefined; - } - - const infoObj = info; - - // Only capture user messages (filter out assistant, system, etc.) - if (infoObj.role !== "user") { - return undefined; - } - - const sessionID = - typeof infoObj.sessionID === "string" && - infoObj.sessionID.trim().length > 0 - ? infoObj.sessionID - : "unknown"; - - const model = infoObj.model; - - // Access info.summary?.diffs via explicit checks - const summary = infoObj.summary; - const diffEntries = - typeof summary === "object" && summary !== null - ? (summary).diffs - : undefined; - - if (!Array.isArray(diffEntries) || diffEntries.length === 0) { - return undefined; - } - - const patches: string[] = []; - for (const entry of diffEntries) { - if (typeof entry !== "object" || entry === null) { - continue; - } - const entryObj = entry as {patch?:string}; - const patch = entryObj.patch || ""; - - patches.push(patch); - } - - if (patches.length === 0) { - return undefined; - } - - return { - sessionID, - diff: patches.join("\n"), - time: Date.now(), - model_id: `${model.providerID}/${model.modelID}`, - }; + const event = input.event; + if (event === undefined || event.type !== "message.updated") { + return undefined; + } + + const properties = event.properties; + if (typeof properties !== "object" || properties === null) { + return undefined; + } + + const propertiesObj = properties; + + // Access properties.info (the Message object) + const info = propertiesObj.info; + if (typeof info !== "object" || info === null) { + return undefined; + } + + const infoObj = info; + + // Only capture user messages (filter out assistant, system, etc.) + if (infoObj.role !== "user") { + return undefined; + } + + const sessionID = + typeof infoObj.sessionID === "string" && infoObj.sessionID.trim().length > 0 + ? infoObj.sessionID + : "unknown"; + + const model = infoObj.model; + + // Access info.summary?.diffs via explicit checks + const summary = infoObj.summary; + const diffEntries = + typeof summary === "object" && summary !== null ? summary.diffs : undefined; + + if (!Array.isArray(diffEntries) || diffEntries.length === 0) { + return undefined; + } + + const patches: string[] = []; + for (const entry of diffEntries) { + if (typeof entry !== "object" || entry === null) { + continue; + } + const entryObj = entry as { patch?: string }; + const patch = entryObj.patch || ""; + + patches.push(patch); + } + + if (patches.length === 0) { + return undefined; + } + + return { + sessionID, + diff: patches.join("\n"), + time: Date.now(), + model_id: `${model.providerID}/${model.modelID}`, + }; } function shouldCaptureEvent(eventType: string): boolean { - return ALL_CAPTURED_EVENTS.has(eventType); + return ALL_CAPTURED_EVENTS.has(eventType); } async function buildTrace(repoRoot: string, input: TraceInput): Promise { - const diffTracePayload = extractDiffTracePayload(input); + const diffTracePayload = extractDiffTracePayload(input); - if (diffTracePayload === undefined) { - return; - } + if (diffTracePayload === undefined) { + return; + } - await runDiffTraceHook(repoRoot, diffTracePayload); + await runDiffTraceHook(repoRoot, diffTracePayload); } async function runDiffTraceHook( - repoRoot: string, - payload: DiffTracePayload, + repoRoot: string, + payload: DiffTracePayload, ): Promise { - await new Promise((resolve, reject) => { - const child = spawn("sce", ["hooks", "diff-trace"], { - cwd: repoRoot, - stdio: ["pipe", "ignore", "inherit"], - }); - - child.on("error", reject); - - child.on("close", (code, signal) => { - if (code === 0) { - resolve(); - return; - } - - const reason = - signal === null ? `exit code ${String(code)}` : `signal ${signal}`; - reject( - new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), - ); - }); - - child.stdin.end(`${JSON.stringify(payload)}\n`); - }); + await new Promise((resolve, reject) => { + const child = spawn("sce", ["hooks", "diff-trace"], { + cwd: repoRoot, + stdio: ["pipe", "ignore", "inherit"], + }); + + child.on("error", reject); + + child.on("close", (code, signal) => { + if (code === 0) { + resolve(); + return; + } + + const reason = + signal === null ? `exit code ${String(code)}` : `signal ${signal}`; + reject( + new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), + ); + }); + + child.stdin.end(`${JSON.stringify(payload)}\n`); + }); } export const SceAgentTracePlugin: Plugin = async ({ directory, worktree }) => { - const repoRoot = worktree ?? directory ?? process.cwd(); - - return { - event: async (input) => { - const eventType = - typeof input.event === "object" && - input.event !== null && - typeof input.event.type === "string" - ? input.event.type - : undefined; - - if (eventType === undefined || !shouldCaptureEvent(eventType)) { - return; - } - - await buildTrace(repoRoot, input); - }, - }; + const repoRoot = worktree ?? directory ?? process.cwd(); + + return { + event: async (input) => { + const eventType = + typeof input.event === "object" && + input.event !== null && + typeof input.event.type === "string" + ? input.event.type + : undefined; + + if (eventType === undefined || !shouldCaptureEvent(eventType)) { + return; + } + + await buildTrace(repoRoot, input); + }, + }; }; diff --git a/config/automated/.opencode/plugins/sce-agent-trace.ts b/config/automated/.opencode/plugins/sce-agent-trace.ts index abfae8fe..aca80ea2 100644 --- a/config/automated/.opencode/plugins/sce-agent-trace.ts +++ b/config/automated/.opencode/plugins/sce-agent-trace.ts @@ -8,146 +8,143 @@ const REQUIRED_EVENTS = new Set(["message.updated"]); const ALL_CAPTURED_EVENTS = REQUIRED_EVENTS; type TraceInput = { - event?: OpenCodeEvent; + event?: OpenCodeEvent; }; type DiffTracePayload = { - sessionID: string; - diff: string; - time: number; - model_id: string; + sessionID: string; + diff: string; + time: number; + model_id: string; }; function extractDiffTracePayload( - input: TraceInput, + input: TraceInput, ): DiffTracePayload | undefined { - const event = input.event; - if (event === undefined || event.type !== "message.updated") { - return undefined; - } - - const properties = event.properties; - if (typeof properties !== "object" || properties === null) { - return undefined; - } - - const propertiesObj = properties; - - // Access properties.info (the Message object) - const info = propertiesObj.info; - if (typeof info !== "object" || info === null) { - return undefined; - } - - const infoObj = info; - - // Only capture user messages (filter out assistant, system, etc.) - if (infoObj.role !== "user") { - return undefined; - } - - const sessionID = - typeof infoObj.sessionID === "string" && - infoObj.sessionID.trim().length > 0 - ? infoObj.sessionID - : "unknown"; - - const model = infoObj.model; - - // Access info.summary?.diffs via explicit checks - const summary = infoObj.summary; - const diffEntries = - typeof summary === "object" && summary !== null - ? (summary).diffs - : undefined; - - if (!Array.isArray(diffEntries) || diffEntries.length === 0) { - return undefined; - } - - const patches: string[] = []; - for (const entry of diffEntries) { - if (typeof entry !== "object" || entry === null) { - continue; - } - const entryObj = entry as {patch?:string}; - const patch = entryObj.patch || ""; - - patches.push(patch); - } - - if (patches.length === 0) { - return undefined; - } - - return { - sessionID, - diff: patches.join("\n"), - time: Date.now(), - model_id: `${model.providerID}/${model.modelID}`, - }; + const event = input.event; + if (event === undefined || event.type !== "message.updated") { + return undefined; + } + + const properties = event.properties; + if (typeof properties !== "object" || properties === null) { + return undefined; + } + + const propertiesObj = properties; + + // Access properties.info (the Message object) + const info = propertiesObj.info; + if (typeof info !== "object" || info === null) { + return undefined; + } + + const infoObj = info; + + // Only capture user messages (filter out assistant, system, etc.) + if (infoObj.role !== "user") { + return undefined; + } + + const sessionID = + typeof infoObj.sessionID === "string" && infoObj.sessionID.trim().length > 0 + ? infoObj.sessionID + : "unknown"; + + const model = infoObj.model; + + // Access info.summary?.diffs via explicit checks + const summary = infoObj.summary; + const diffEntries = + typeof summary === "object" && summary !== null ? summary.diffs : undefined; + + if (!Array.isArray(diffEntries) || diffEntries.length === 0) { + return undefined; + } + + const patches: string[] = []; + for (const entry of diffEntries) { + if (typeof entry !== "object" || entry === null) { + continue; + } + const entryObj = entry as { patch?: string }; + const patch = entryObj.patch || ""; + + patches.push(patch); + } + + if (patches.length === 0) { + return undefined; + } + + return { + sessionID, + diff: patches.join("\n"), + time: Date.now(), + model_id: `${model.providerID}/${model.modelID}`, + }; } function shouldCaptureEvent(eventType: string): boolean { - return ALL_CAPTURED_EVENTS.has(eventType); + return ALL_CAPTURED_EVENTS.has(eventType); } async function buildTrace(repoRoot: string, input: TraceInput): Promise { - const diffTracePayload = extractDiffTracePayload(input); + const diffTracePayload = extractDiffTracePayload(input); - if (diffTracePayload === undefined) { - return; - } + if (diffTracePayload === undefined) { + return; + } - await runDiffTraceHook(repoRoot, diffTracePayload); + await runDiffTraceHook(repoRoot, diffTracePayload); } async function runDiffTraceHook( - repoRoot: string, - payload: DiffTracePayload, + repoRoot: string, + payload: DiffTracePayload, ): Promise { - await new Promise((resolve, reject) => { - const child = spawn("sce", ["hooks", "diff-trace"], { - cwd: repoRoot, - stdio: ["pipe", "ignore", "inherit"], - }); - - child.on("error", reject); - - child.on("close", (code, signal) => { - if (code === 0) { - resolve(); - return; - } - - const reason = - signal === null ? `exit code ${String(code)}` : `signal ${signal}`; - reject( - new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), - ); - }); - - child.stdin.end(`${JSON.stringify(payload)}\n`); - }); + await new Promise((resolve, reject) => { + const child = spawn("sce", ["hooks", "diff-trace"], { + cwd: repoRoot, + stdio: ["pipe", "ignore", "inherit"], + }); + + child.on("error", reject); + + child.on("close", (code, signal) => { + if (code === 0) { + resolve(); + return; + } + + const reason = + signal === null ? `exit code ${String(code)}` : `signal ${signal}`; + reject( + new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), + ); + }); + + child.stdin.end(`${JSON.stringify(payload)}\n`); + }); } export const SceAgentTracePlugin: Plugin = async ({ directory, worktree }) => { - const repoRoot = worktree ?? directory ?? process.cwd(); - - return { - event: async (input) => { - const eventType = - typeof input.event === "object" && - input.event !== null && - typeof input.event.type === "string" - ? input.event.type - : undefined; - - if (eventType === undefined || !shouldCaptureEvent(eventType)) { - return; - } - - await buildTrace(repoRoot, input); - }, - }; + const repoRoot = worktree ?? directory ?? process.cwd(); + + return { + event: async (input) => { + const eventType = + typeof input.event === "object" && + input.event !== null && + typeof input.event.type === "string" + ? input.event.type + : undefined; + + if (eventType === undefined || !shouldCaptureEvent(eventType)) { + return; + } + + await buildTrace(repoRoot, input); + }, + }; }; diff --git a/config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts b/config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts index abfae8fe..aca80ea2 100644 --- a/config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts +++ b/config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts @@ -8,146 +8,143 @@ const REQUIRED_EVENTS = new Set(["message.updated"]); const ALL_CAPTURED_EVENTS = REQUIRED_EVENTS; type TraceInput = { - event?: OpenCodeEvent; + event?: OpenCodeEvent; }; type DiffTracePayload = { - sessionID: string; - diff: string; - time: number; - model_id: string; + sessionID: string; + diff: string; + time: number; + model_id: string; }; function extractDiffTracePayload( - input: TraceInput, + input: TraceInput, ): DiffTracePayload | undefined { - const event = input.event; - if (event === undefined || event.type !== "message.updated") { - return undefined; - } - - const properties = event.properties; - if (typeof properties !== "object" || properties === null) { - return undefined; - } - - const propertiesObj = properties; - - // Access properties.info (the Message object) - const info = propertiesObj.info; - if (typeof info !== "object" || info === null) { - return undefined; - } - - const infoObj = info; - - // Only capture user messages (filter out assistant, system, etc.) - if (infoObj.role !== "user") { - return undefined; - } - - const sessionID = - typeof infoObj.sessionID === "string" && - infoObj.sessionID.trim().length > 0 - ? infoObj.sessionID - : "unknown"; - - const model = infoObj.model; - - // Access info.summary?.diffs via explicit checks - const summary = infoObj.summary; - const diffEntries = - typeof summary === "object" && summary !== null - ? (summary).diffs - : undefined; - - if (!Array.isArray(diffEntries) || diffEntries.length === 0) { - return undefined; - } - - const patches: string[] = []; - for (const entry of diffEntries) { - if (typeof entry !== "object" || entry === null) { - continue; - } - const entryObj = entry as {patch?:string}; - const patch = entryObj.patch || ""; - - patches.push(patch); - } - - if (patches.length === 0) { - return undefined; - } - - return { - sessionID, - diff: patches.join("\n"), - time: Date.now(), - model_id: `${model.providerID}/${model.modelID}`, - }; + const event = input.event; + if (event === undefined || event.type !== "message.updated") { + return undefined; + } + + const properties = event.properties; + if (typeof properties !== "object" || properties === null) { + return undefined; + } + + const propertiesObj = properties; + + // Access properties.info (the Message object) + const info = propertiesObj.info; + if (typeof info !== "object" || info === null) { + return undefined; + } + + const infoObj = info; + + // Only capture user messages (filter out assistant, system, etc.) + if (infoObj.role !== "user") { + return undefined; + } + + const sessionID = + typeof infoObj.sessionID === "string" && infoObj.sessionID.trim().length > 0 + ? infoObj.sessionID + : "unknown"; + + const model = infoObj.model; + + // Access info.summary?.diffs via explicit checks + const summary = infoObj.summary; + const diffEntries = + typeof summary === "object" && summary !== null ? summary.diffs : undefined; + + if (!Array.isArray(diffEntries) || diffEntries.length === 0) { + return undefined; + } + + const patches: string[] = []; + for (const entry of diffEntries) { + if (typeof entry !== "object" || entry === null) { + continue; + } + const entryObj = entry as { patch?: string }; + const patch = entryObj.patch || ""; + + patches.push(patch); + } + + if (patches.length === 0) { + return undefined; + } + + return { + sessionID, + diff: patches.join("\n"), + time: Date.now(), + model_id: `${model.providerID}/${model.modelID}`, + }; } function shouldCaptureEvent(eventType: string): boolean { - return ALL_CAPTURED_EVENTS.has(eventType); + return ALL_CAPTURED_EVENTS.has(eventType); } async function buildTrace(repoRoot: string, input: TraceInput): Promise { - const diffTracePayload = extractDiffTracePayload(input); + const diffTracePayload = extractDiffTracePayload(input); - if (diffTracePayload === undefined) { - return; - } + if (diffTracePayload === undefined) { + return; + } - await runDiffTraceHook(repoRoot, diffTracePayload); + await runDiffTraceHook(repoRoot, diffTracePayload); } async function runDiffTraceHook( - repoRoot: string, - payload: DiffTracePayload, + repoRoot: string, + payload: DiffTracePayload, ): Promise { - await new Promise((resolve, reject) => { - const child = spawn("sce", ["hooks", "diff-trace"], { - cwd: repoRoot, - stdio: ["pipe", "ignore", "inherit"], - }); - - child.on("error", reject); - - child.on("close", (code, signal) => { - if (code === 0) { - resolve(); - return; - } - - const reason = - signal === null ? `exit code ${String(code)}` : `signal ${signal}`; - reject( - new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), - ); - }); - - child.stdin.end(`${JSON.stringify(payload)}\n`); - }); + await new Promise((resolve, reject) => { + const child = spawn("sce", ["hooks", "diff-trace"], { + cwd: repoRoot, + stdio: ["pipe", "ignore", "inherit"], + }); + + child.on("error", reject); + + child.on("close", (code, signal) => { + if (code === 0) { + resolve(); + return; + } + + const reason = + signal === null ? `exit code ${String(code)}` : `signal ${signal}`; + reject( + new Error(`Command 'sce hooks diff-trace' failed with ${reason}.`), + ); + }); + + child.stdin.end(`${JSON.stringify(payload)}\n`); + }); } export const SceAgentTracePlugin: Plugin = async ({ directory, worktree }) => { - const repoRoot = worktree ?? directory ?? process.cwd(); - - return { - event: async (input) => { - const eventType = - typeof input.event === "object" && - input.event !== null && - typeof input.event.type === "string" - ? input.event.type - : undefined; - - if (eventType === undefined || !shouldCaptureEvent(eventType)) { - return; - } - - await buildTrace(repoRoot, input); - }, - }; + const repoRoot = worktree ?? directory ?? process.cwd(); + + return { + event: async (input) => { + const eventType = + typeof input.event === "object" && + input.event !== null && + typeof input.event.type === "string" + ? input.event.type + : undefined; + + if (eventType === undefined || !shouldCaptureEvent(eventType)) { + return; + } + + await buildTrace(repoRoot, input); + }, + }; }; diff --git a/config/lib/agent-trace-plugin/package.json b/config/lib/agent-trace-plugin/package.json deleted file mode 100644 index 6d4df72a..00000000 --- a/config/lib/agent-trace-plugin/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "dependencies": { - "@opencode-ai/plugin": "1.14.28", - "@types/bun": "1.3.11", - "@types/node": "25.5.0" - } -} diff --git a/config/lib/agent-trace-plugin/tsconfig.json b/config/lib/agent-trace-plugin/tsconfig.json deleted file mode 100644 index 004ba7a3..00000000 --- a/config/lib/agent-trace-plugin/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "lib": ["ES2022"], - "module": "NodeNext", - "moduleResolution": "NodeNext", - "types": ["node", "bun"], - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "allowImportingTsExtensions": true, - "skipLibCheck": true - }, - "include": ["opencode-sce-agent-trace-plugin.ts"] -} diff --git a/config/lib/bash-policy-plugin/bun.lock b/config/lib/bash-policy-plugin/bun.lock deleted file mode 100644 index b490cef5..00000000 --- a/config/lib/bash-policy-plugin/bun.lock +++ /dev/null @@ -1,28 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "dependencies": { - "@opencode-ai/plugin": "1.3.0", - "@types/bun": "1.3.11", - "@types/node": "25.5.0", - }, - }, - }, - "packages": { - "@opencode-ai/plugin": ["@opencode-ai/plugin@1.3.0", "", { "dependencies": { "@opencode-ai/sdk": "1.3.0", "zod": "4.1.8" } }, "sha512-mR1Kdcpr3Iv+KS7cL2DRFB6QAcSoR6/DojmwuxYF/pMCahMtaCLiqZGQjoSNl12+gQ6RsIJJyUh/jX3JVlOx8A=="], - - "@opencode-ai/sdk": ["@opencode-ai/sdk@1.3.0", "", {}, "sha512-5WyYEpcV6Zk9otXOMIrvZRbJm1yxt/c8EXSBn1p6Sw1yagz8HRljkoUTJFxzD0x2+/6vAZItr3OrXDZfE+oA2g=="], - - "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], - - "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], - - "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], - - "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - - "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], - } -} diff --git a/config/lib/agent-trace-plugin/bun.lock b/config/lib/bun.lock similarity index 88% rename from config/lib/agent-trace-plugin/bun.lock rename to config/lib/bun.lock index 45692573..ec405bff 100644 --- a/config/lib/agent-trace-plugin/bun.lock +++ b/config/lib/bun.lock @@ -4,7 +4,7 @@ "workspaces": { "": { "dependencies": { - "@opencode-ai/plugin": "1.14.28", + "@opencode-ai/plugin": "1.15.4", "@types/bun": "1.3.11", "@types/node": "25.5.0", }, @@ -23,9 +23,9 @@ "@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/plugin": ["@opencode-ai/plugin@1.15.4", "", { "dependencies": { "@opencode-ai/sdk": "1.15.4", "effect": "4.0.0-beta.65", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.2.11", "@opentui/keymap": ">=0.2.11", "@opentui/solid": ">=0.2.11" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-5KAhUnks8GNlqRIax+3cs/ZT2UK74/MNdl4w846ysYdivb38fIm+X9R69ljQtRKyQY7rtga4JUQuARJMSExQqQ=="], - "@opencode-ai/sdk": ["@opencode-ai/sdk@1.14.28", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-qRFJfD+Zdz3jHHSupW4F6Io1ZFrQ6gCRFlG50O6kEU9xRxrBpK0wGvP+Y5VwwvD/gH9WKMHYinlQpDVI9/lgJQ=="], + "@opencode-ai/sdk": ["@opencode-ai/sdk@1.15.4", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-55SBChNouj2XY9C4thO0w7SGJS3jD2DRBxzcrDpc5szgmJJ2t2Wu38uZh+TQMBLHA8YrTPDqgfnc7o5tx2qRPw=="], "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], @@ -39,7 +39,7 @@ "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=="], + "effect": ["effect@4.0.0-beta.65", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-QYKvQPAj3CmtsvWkHQww15wX4KG2gNsszDWEcOO5sZCMknp66u6Si/Opmt3wwWCwsyvRmDAdIg+JIz5qzbbFIw=="], "fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="], diff --git a/config/lib/bash-policy-plugin/package.json b/config/lib/package.json similarity index 69% rename from config/lib/bash-policy-plugin/package.json rename to config/lib/package.json index 79a35a98..c4685fd6 100644 --- a/config/lib/bash-policy-plugin/package.json +++ b/config/lib/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "@opencode-ai/plugin": "1.3.0", + "@opencode-ai/plugin": "1.15.4", "@types/bun": "1.3.11", "@types/node": "25.5.0" } diff --git a/config/lib/tsconfig.json b/config/lib/tsconfig.json new file mode 100644 index 00000000..59c38829 --- /dev/null +++ b/config/lib/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node", "bun"], + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "allowImportingTsExtensions": true, + "skipLibCheck": true + }, + "include": ["agent-trace-plugin/**/*.ts", "bash-policy-plugin/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/context/architecture.md b/context/architecture.md index b735496b..bd68f595 100644 --- a/context/architecture.md +++ b/context/architecture.md @@ -120,9 +120,9 @@ The repository includes a new placeholder Rust binary crate at `cli/`. - `cli/src/services/patch.rs` defines the standalone patch domain model (`ParsedPatch`, `PatchFileChange`, `FileChangeKind`, `PatchHunk`, `TouchedLine`, `TouchedLineKind`) for in-memory parsed unified-diff representation, capturing only touched lines (added/removed) plus minimal per-file/per-hunk metadata while excluding non-hunk headers and unchanged context lines. All types are `serde`-serializable/deserializable with `snake_case` JSON field naming. The module also provides `parse_patch`, a public parser function that converts raw unified-diff text (both `Index:` SVN-style and `diff --git` git-style formats) into `ParsedPatch` structs, with `ParseError` for actionable malformed-input diagnostics. Storage-agnostic JSON load helpers (`load_patch_from_json` for string input, `load_patch_from_json_bytes` for byte input) reconstruct `ParsedPatch` from serialized JSON content with `PatchLoadError` for actionable deserialization diagnostics. Its patch-set operations now include deterministic ordered combination plus target-shaped intersection that prefers exact touched-line matches and falls back to historical `kind`+`content` matching when incremental diffs and canonical post-commit diffs have drifted line numbers; `parse_patch`, `combine_patches`, and `intersect_patches` are consumed by the active post-commit hook runtime. - `cli/src/services/` contains module boundaries for command_registry, lifecycle, auth_command, config, setup, doctor, hooks, version, completion, help, patch, shared database infrastructure, local DB adapters, and Agent Trace DB adapters with explicit trait seams for future implementations. `cli/src/services/command_registry.rs` defines the `RuntimeCommand` trait, `RuntimeCommandHandle` type alias, `CommandRegistry` struct, and `build_default_registry()` function for the command dispatch registry. Service-owned command modules now own the migrated runtime command structs and `RuntimeCommand` impls for help/help-text, version, completion, auth, config, setup, doctor, and hooks. - `cli/README.md` is the crate-local onboarding and usage source of truth for placeholder behavior, safety limitations, and roadmap mapping back to service contracts. -- `flake.nix` applies `rust-overlay` (`oxalica/rust-overlay`) to nixpkgs, pins `rust-bin.stable.1.93.1.default` with `rustfmt` + `clippy`, reads the package/check version from repo-root `.version`, builds `packages.sce` through Crane (`buildDepsOnly` -> `buildPackage`) with a filtered repo-root source that preserves the Cargo tree plus `cli/assets/hooks`, then injects generated OpenCode/Claude config payloads and schema inputs into a temporary `cli/assets/generated/` mirror during derivation unpack so `cli/build.rs` can package the crate without requiring committed generated crate assets, runs `cli-tests`, `cli-clippy`, and `cli-fmt` plus the dedicated `integrations-install-tests`, `integrations-install-clippy`, and `integrations-install-fmt` derivations through Crane-backed paths so both Rust crates have first-class default-flake verification, exposes directory-scoped JS validation derivations for both `npm/` and `config/lib/bash-policy-plugin/`, and also exposes the non-default `apps.install-channel-integration-tests` flake app for install-channel integration coverage outside the default check set. `.github/workflows/publish-crates.yml` follows the same asset-preparation rule but runs Cargo packaging from a temporary clean repository copy so crates.io publish no longer needs `--allow-dirty`. +- `flake.nix` applies `rust-overlay` (`oxalica/rust-overlay`) to nixpkgs, pins `rust-bin.stable.1.93.1.default` with `rustfmt` + `clippy`, reads the package/check version from repo-root `.version`, builds `packages.sce` through Crane (`buildDepsOnly` -> `buildPackage`) with a filtered repo-root source that preserves the Cargo tree plus `cli/assets/hooks`, then injects generated OpenCode/Claude config payloads and schema inputs into a temporary `cli/assets/generated/` mirror during derivation unpack so `cli/build.rs` can package the crate without requiring committed generated crate assets, runs `cli-tests`, `cli-clippy`, and `cli-fmt` plus the dedicated `integrations-install-tests`, `integrations-install-clippy`, and `integrations-install-fmt` derivations through Crane-backed paths so both Rust crates have first-class default-flake verification, exposes directory-scoped JS validation derivations for both `npm/` and the shared `config/lib/` plugin package root, and also exposes the non-default `apps.install-channel-integration-tests` flake app for install-channel integration coverage outside the default check set. The shared config-lib source set is rooted at `config/lib/` and includes the shared `package.json`, `bun.lock`, and `tsconfig.json` plus `agent-trace-plugin/` and `bash-policy-plugin/`; `config-lib-bun-tests` runs the bash-policy runtime test from that shared root, while `config-lib-biome-check` and `config-lib-biome-format` run Biome over the copied shared package source. `.github/workflows/publish-crates.yml` follows the same asset-preparation rule but runs Cargo packaging from a temporary clean repository copy so crates.io publish no longer needs `--allow-dirty`. - `flake.nix` exposes release install/run surfaces as `packages.sce` (`packages.default = packages.sce`) plus `apps.sce` and `apps.default`, all targeting `${packages.sce}/bin/sce`; this keeps repo-local and remote flake run/install flows (`nix run .`, `nix run github:crocoder-dev/shared-context-engineering`, `nix profile install github:crocoder-dev/shared-context-engineering`) aligned to the same packaged CLI output. -- `biome.json` at the repository root is the canonical Biome configuration for the current JS tooling slice and deliberately scopes coverage to `npm/**` plus `config/lib/bash-policy-plugin/**` while excluding package-local `node_modules/**`; `flake.nix` exposes Biome through the default dev shell rather than through package-local installs. +- `biome.json` at the repository root is the canonical Biome configuration for the current JS tooling slice and deliberately scopes coverage to `npm/**` plus the shared `config/lib/**` plugin package root while excluding package-local `node_modules/**`; `flake.nix` exposes Biome through the default dev shell rather than through package-local installs. - `cli/Cargo.toml` now keeps crates.io publication-ready package metadata for the `shared-context-engineering` crate, and `cli/README.md` is the Cargo install surface for crates.io (`cargo install shared-context-engineering --locked`), git (`cargo install --git https://github.com/crocoder-dev/shared-context-engineering shared-context-engineering --locked`), and local checkout (`cargo install --path cli --locked`) guidance. The published crate installs the `sce` binary. Tokio remains intentionally constrained (`default-features = false`) with current-thread runtime usage plus timer-backed bounded resilience wrappers for retry/timeout behavior. - `cli/Cargo.toml` now declares Tokio's `time` feature directly alongside the existing constrained current-thread runtime setup (`rt`, `io-util`, `time`) instead of relying on transitive enablement. diff --git a/context/context-map.md b/context/context-map.md index 21ee1bc6..43752398 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -51,7 +51,7 @@ Feature/domain context: - `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 `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/opencode-agent-trace-plugin-runtime.md` (current OpenCode agent-trace plugin runtime behavior, including `message.updated` event capture filtered to user messages with diffs, `{ sessionID, diff, time, model_id }` extraction via `properties.info.role === "user"`, `Date.now()` for time, direct `info.model.providerID/modelID` model-id construction, object-entry patch joining with empty-patch payloads left to Rust hook validation, and CLI handoff to `sce hooks diff-trace` over STDIN JSON; Rust hook parsing and AgentTraceDb insertion persist `model_id`; `session.diff` event capture has been removed) - `context/sce/cli-first-install-channels-contract.md` (current first-wave `sce` install/distribution contract covering supported channels, canonical naming, `.version` release authority, and Nix-owned build policy) - `context/sce/optional-install-channel-integration-test-entrypoint.md` (current opt-in flake app contract for install-channel integration coverage, including thin flake delegation to the Rust runner, shared harness ownership, real npm+Bun+Cargo install flows, channel selector semantics, and the explicit non-default execution boundary) - `context/sce/cli-release-artifact-contract.md` (shared `sce` release artifact naming, checksum/manifest outputs, GitHub Releases as the canonical artifact publication surface, and the current three-target Linux/macOS release workflow topology) diff --git a/context/glossary.md b/context/glossary.md index aa2a8df3..5c8f4383 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -9,10 +9,11 @@ - generated-owned outputs: Files materialized by `config/pkl/generate.pkl` under `config/.opencode/**`, `config/automated/.opencode/**`, and `config/.claude/**`, including OpenCode plugin entrypoints, generated OpenCode `package.json` manifests, generated OpenCode `opencode.json` manifests, and Claude hook/settings assets. - `canonical OpenCode plugin registration source`: Shared Pkl-authored plugin-registration definition in `config/pkl/base/opencode.pkl`, re-exported from `config/pkl/renderers/common.pkl` as the canonical plugin list/path JSON consumed by OpenCode renderers before they emit generated `opencode.json` manifests; the current entries are `sce-bash-policy` and `sce-agent-trace`. - `generated OpenCode plugin registration contract`: Current generated-config contract where `config/.opencode/opencode.json` and `config/automated/.opencode/opencode.json` serialize the OpenCode `plugin` field from canonical Pkl sources for SCE-managed plugins only; the current registered paths are `./plugins/sce-bash-policy.ts` and `./plugins/sce-agent-trace.ts`. Claude does not use an OpenCode-style plugin manifest; bash-policy enforcement for Claude has been removed from generated outputs. -- `root Biome contract`: Repository-root formatting/linting contract owned by `biome.json`, currently scoped only to `npm/**` and `config/lib/bash-policy-plugin/**` with package-local `node_modules/**` excluded; the canonical execution path is the root Nix dev shell (`nix develop -c biome ...`). -- `cli flake checks`: Check derivations in root `flake.nix` (`checks..cli-tests`, `cli-clippy`, `cli-fmt`), dedicated `integrations/install` runner checks (`integrations-install-tests`, `integrations-install-clippy`, `integrations-install-fmt`), plus `pkl-parity`, split `npm/` JS checks (`npm-bun-tests`, `npm-biome-check`, `npm-biome-format`), and split `config/lib/bash-policy-plugin/` JS checks (`config-lib-bun-tests`, `config-lib-biome-check`, `config-lib-biome-format`); invoked via `nix flake check` at repo root. +- `root Biome contract`: Repository-root formatting/linting contract owned by `biome.json`, currently scoped only to `npm/**` and the shared `config/lib/**` plugin package root with package-local `node_modules/**` excluded; the canonical execution path is the root Nix dev shell (`nix develop -c biome ...`). +- `cli flake checks`: Check derivations in root `flake.nix` (`checks..cli-tests`, `cli-clippy`, `cli-fmt`), dedicated `integrations/install` runner checks (`integrations-install-tests`, `integrations-install-clippy`, `integrations-install-fmt`), plus `pkl-parity`, split `npm/` JS checks (`npm-bun-tests`, `npm-biome-check`, `npm-biome-format`), and split shared `config/lib/` JS checks (`config-lib-bun-tests`, `config-lib-biome-check`, `config-lib-biome-format`); invoked via `nix flake check` at repo root. - `npm JS flake checks`: The current `npm/` validation slice exposed by root `flake.nix`: `npm-bun-tests` runs only `bun test ./test/*.test.js`, `npm-biome-check` runs only Biome lint/check with formatter verification disabled, and `npm-biome-format` runs only Biome format verification with linter checks disabled. -- `config-lib JS flake checks`: The current `config/lib/bash-policy-plugin/` validation slice exposed by root `flake.nix`: `config-lib-bun-tests` runs `bun test`, `config-lib-biome-check` runs Biome lint/check with formatter verification disabled, and `config-lib-biome-format` runs Biome format verification with linter checks disabled, all scoped to `config/lib/bash-policy-plugin/` only. +- `config-lib JS flake checks`: The current shared `config/lib/` validation slice exposed by root `flake.nix`: `config-lib-bun-tests` runs the bash-policy runtime test at `bash-policy-plugin/bash-policy-runtime.test.ts` from the shared `config/lib/` package root with dependencies resolved from `config/lib/package.json` and `config/lib/bun.lock`, while `config-lib-biome-check` and `config-lib-biome-format` run Biome lint/check and format verification over the copied shared package source with formatter/linter halves disabled respectively. +- `config-lib shared package root`: Shared Bun/TypeScript package root at `config/lib/` for repository-owned OpenCode plugin support code. It owns `package.json`, `bun.lock`, and `tsconfig.json`, pins `@opencode-ai/plugin` to `1.15.4`, includes both `agent-trace-plugin/**/*.ts` and `bash-policy-plugin/**/*.ts` in strict TypeScript coverage, and excludes package-local `node_modules/` from both TypeScript and root Biome coverage. - `cli rust overlay toolchain`: Toolchain contract in root `flake.nix` that applies `rust-overlay.overlays.default`, pins `rust-bin.stable.1.93.1.default` with `rustfmt` + `clippy`, uses that toolchain across both Crane package and check derivations, and keeps toolchain selection explicit rather than inheriting nixpkgs defaults. - `cli Crane package pipeline`: Current root-flake packaging path in `flake.nix` where `packages.sce` is built through `craneLib.buildDepsOnly` plus `craneLib.buildPackage` against a filtered repo-root source that preserves the Cargo tree and the embedded config/assets required by `cli/build.rs`. - `cli Crane check pipeline`: Current root-flake check path in `flake.nix` where `cli-tests`, `cli-clippy`, and `cli-fmt` run through `craneLib.cargoTest`, `craneLib.cargoClippy`, and `craneLib.cargoFmt`; test and clippy derivations reuse the shared `cargoArtifacts` dependency cache from the package pipeline. @@ -151,6 +152,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 carries `commit_timestamp` (RFC 3339 commit-time value used as `AgentTrace.timestamp`) and `commit_revision` (mapped to `AgentTrace.vcs.revision`). - `build_agent_trace`: Public function in `cli/src/services/agent_trace.rs` that computes `intersection_patch = intersect_patches(constructed_patch, post_commit_patch)`, iterates over `post_commit_patch`'s files and hunks, classifies each hunk against `intersection_patch`, validates `AgentTraceMetadataInput.commit_timestamp` as RFC 3339, derives UUIDv7 `AgentTrace.id` from that same commit-time moment, and returns `Result` with top-level metadata fields plus one `Conversation` per `post_commit_patch` hunk; consumed by the active post-commit hook flow, with no standalone `sce agent-trace` command surface -- `agent-trace plugin diff extraction seam`: Exported helper `extractDiffTracePayload` in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` that reads `input.event` and returns `{ sessionID, diff, time, model_id }` only when the event is `message.updated` with `properties.info.role === "user"`; extracts `sessionID` from `info.sessionID` (falling back to `"unknown"` when absent or empty), joins 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 extraction seam`: Exported helper `extractDiffTracePayload` in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` that reads `input.event` and returns `{ sessionID, diff, time, model_id }` only when the event is `message.updated` with `properties.info.role === "user"`; extracts `sessionID` from `info.sessionID` (falling back to `"unknown"` when absent or empty), joins object-entry `patch` fields from `info.summary?.diffs[]` with `\n` while preserving empty patch strings for Rust-side validation, uses `Date.now()` for `time`, and builds `model_id` directly as `info.model.providerID/info.model.modelID`; returns `undefined` for non-`message.updated` events, non-user messages, messages without a non-empty `summary.diffs` array, or diffs arrays without object entries. - `agent-trace plugin diff-trace hook handoff seam`: Internal helper `runDiffTraceHook` in `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts` that invokes `sce hooks diff-trace`, streams extracted `{ sessionID, diff, time, model_id }` to STDIN JSON, and surfaces deterministic invocation failures. - `agent-trace plugin secondary diff artifact ownership`: Current runtime contract where `buildTrace` no longer writes diff-trace artifacts or database rows directly; extracted diff payloads are forwarded to CLI `diff-trace` intake and the Rust hook runtime owns AgentTraceDb insertion plus collision-safe per-invocation artifact persistence. diff --git a/context/overview.md b/context/overview.md index acf24acf..79bedd17 100644 --- a/context/overview.md +++ b/context/overview.md @@ -29,13 +29,13 @@ Local database bootstrap is now owned by `LocalDbLifecycle::setup` and `AgentTra The repository-root flake (`flake.nix`) now applies a Rust overlay-backed stable toolchain pinned to `1.93.1` (with `rustfmt` and `clippy`), reads package/check version from the repo-root `.version` file, builds `packages.sce` through a Crane `buildDepsOnly` + `buildPackage` pipeline with filtered package sources for the Cargo tree plus required embedded config/assets, and runs `cli-tests`, `cli-clippy`, and `cli-fmt` through Crane-backed check derivations (`cargoTest`, `cargoClippy`, `cargoFmt`) that reuse the same filtered source/toolchain setup. The root flake also exposes release install/run outputs directly as `packages.sce` (with `packages.default = packages.sce`) plus `apps.sce` and `apps.default`, so `nix build .#default`, `nix run . -- --help`, `nix run .#sce -- --help`, and `nix profile install github:crocoder-dev/shared-context-engineering` all target the packaged `sce` binary through the same flake-owned entrypoints. The CLI Cargo package metadata now includes crates.io publication-ready fields with crate-local install guidance in `cli/README.md`; supported Cargo install paths are `cargo install shared-context-engineering --locked`, `cargo install --git https://github.com/crocoder-dev/shared-context-engineering shared-context-engineering --locked`, and local `cargo install --path cli --locked`. The published crate installs the `sce` binary. The crate also keeps `cargo clippy --manifest-path cli/Cargo.toml` warnings-denied through `cli/Cargo.toml` lint configuration, so an extra `-- -D warnings` flag is redundant. -The repository-root flake is now the single Nix entrypoint for both repo tooling and CLI packaging/checks, so root-level `nix flake check` evaluates the Crane-backed CLI checks (`cli-tests`, `cli-clippy`, `cli-fmt`), the dedicated `integrations/install` runner checks (`integrations-install-tests`, `integrations-install-clippy`, `integrations-install-fmt`), plus six split JavaScript check derivations: `npm-bun-tests`, `npm-biome-check`, `npm-biome-format`, `config-lib-bun-tests`, `config-lib-biome-check`, and `config-lib-biome-format`, without nested-flake indirection. For Cargo packaging/builds, the crate now compiles against a temporary `cli/assets/generated/` mirror prepared from canonical `config/` outputs during Nix builds and crates.io publish runs rather than from a committed crate-local snapshot. +The repository-root flake is now the single Nix entrypoint for both repo tooling and CLI packaging/checks, so root-level `nix flake check` evaluates the Crane-backed CLI checks (`cli-tests`, `cli-clippy`, `cli-fmt`), the dedicated `integrations/install` runner checks (`integrations-install-tests`, `integrations-install-clippy`, `integrations-install-fmt`), plus six split JavaScript check derivations: `npm-bun-tests`, `npm-biome-check`, `npm-biome-format`, `config-lib-bun-tests`, `config-lib-biome-check`, and `config-lib-biome-format`, without nested-flake indirection. The config-lib checks now consume `config/lib/` as the shared Bun/TypeScript package root for both `agent-trace-plugin/` and `bash-policy-plugin/`, with dependencies resolved from `config/lib/package.json` and `config/lib/bun.lock`. For Cargo packaging/builds, the crate now compiles against a temporary `cli/assets/generated/` mirror prepared from canonical `config/` outputs during Nix builds and crates.io publish runs rather than from a committed crate-local snapshot. Local developer Nix tuning guidance now lives in `AGENTS.md`, including optional user-level `~/.config/nix/nix.conf` recommendations for `max-jobs` and `cores` plus an explicit system-level-only note for `auto-optimise-store`. The Pkl authoring layer owns generated OpenCode plugin registration for SCE-managed plugins: `config/pkl/base/opencode.pkl` defines the canonical plugin entries, `config/pkl/renderers/common.pkl` re-exports the shared plugin list for renderer use, and generated `config/.opencode/opencode.json` plus `config/automated/.opencode/opencode.json` register `./plugins/sce-bash-policy.ts` and `./plugins/sce-agent-trace.ts` through OpenCode's `plugin` field. Claude does not use an OpenCode-style plugin manifest; bash-policy enforcement for Claude has been removed from generated outputs. The current first-wave CLI install/distribution contract is now defined for `sce`: the active implemented channel set is repo-flake Nix, Cargo, and npm; `Homebrew` is deferred from the current implementation stage. Nix-managed build/release entrypoints are the source of truth for this rollout, npm consumes Nix-produced release artifacts, and repo-root `.version` is the canonical checked-in release version source that release packaging and downstream Cargo/npm publication must match. The shared release artifact foundation is now implemented through root-flake apps `release-artifacts` and `release-manifest`, which emit canonical `sce-v-.tar.gz` archives, SHA-256 checksum files, merged manifest outputs, and a detached `sce-v-release-manifest.json.sig` produced from a non-repo private signing key; the npm distribution surface is now implemented as a checked-in `npm/` launcher package plus root-flake `release-npm-package`, which packs `sce-v-npm.tgz`, refuses mismatched checked-in package metadata, and installs the native CLI by downloading the release manifest plus detached signature, verifying the manifest with the bundled npm public key, and only then checksum-verifying the matching GitHub release archive at npm `postinstall` time. GitHub Releases are the canonical publication surface for those release artifacts, while crates.io and npm registry publication are separate non-bumping publish stages under the approved release topology. GitHub CLI release automation now lives in dedicated `release-sce*.yml` workflows split by Linux, Linux ARM, and macOS ARM, and `.github/workflows/release-sce.yml` now orchestrates those three reusable platform lanes before assembling the signed release manifest, npm tarball, and GitHub release payload. The orchestrator now tags/releases the checked-in `.version` directly and rejects version mismatches instead of generating a new semver during workflow execution, `.github/workflows/publish-crates.yml` is the dedicated crates.io publish stage triggered from a published GitHub release or manual dispatch with the same `.version`/tag/Cargo parity checks and a clean temporary repo copy for Cargo packaging, and `release-agents.yml` remains Tessl-only. The current supported automated release target matrix is `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`, and `aarch64-apple-darwin`; npm launcher platform support remains a separate current-state surface documented in the npm distribution contract and launcher code. The downstream publish-stage implementation is now complete for both registries: `.github/workflows/publish-crates.yml` publishes the checked-in crate version after `.version`/tag/Cargo parity checks, and `.github/workflows/publish-npm.yml` publishes the checked-in npm package after `.version`/tag/npm parity checks plus verification of the canonical `sce-v-npm.tgz` GitHub release asset. -The repository root now also owns the canonical Biome contract for the current JavaScript tooling slice: `biome.json` scopes formatting/linting to `npm/` and `config/lib/bash-policy-plugin/` only, and the root Nix dev shell provides the `biome` binary so contributors do not need a host-installed formatter/linter for those areas. +The repository root now also owns the canonical Biome contract for the current JavaScript tooling slice: `biome.json` scopes formatting/linting to `npm/` and the shared `config/lib/` plugin package root while excluding package-local `node_modules/`, and the root Nix dev shell provides the `biome` binary so contributors do not need a host-installed formatter/linter for those areas. The root flake now also exposes an explicit opt-in install-channel integration-test app, `nix run .#install-channel-integration-tests -- --channel `, which remains outside the default `nix flake check` path while the Rust runner now executes real npm, Bun, and Cargo install-and-verify flows for all three first-wave channels. Shared Context Plan and Shared Context Code remain separate agent roles by design; planning (`/change-to-plan`) and implementation (`/next-task`) stay split while shared baseline guidance is deduplicated via canonical skill-owned contracts. Their shared baseline doctrine (core principles, `context/` authority, and quality posture) is defined once as canonical snippets in `config/pkl/base/shared-content-common.pkl` and composed into both agent bodies during generation; the aggregation surfaces `config/pkl/base/shared-content.pkl` (manual) and `config/pkl/base/shared-content-automated.pkl` (automated) import from grouped `plan`, `code`, and `commit` modules for downstream renderers. diff --git a/context/patterns.md b/context/patterns.md index 365eba01..cf836503 100644 --- a/context/patterns.md +++ b/context/patterns.md @@ -14,8 +14,9 @@ ## Root Biome scoping - Keep Biome configuration at the repository root when one formatter/linter contract spans multiple JS package areas. -- Scope root `biome.json` explicitly to the approved JS surfaces only; the current approved scope is `npm/**` and `config/lib/bash-policy-plugin/**`. +- Scope root `biome.json` explicitly to the approved JS surfaces only; the current approved scope is `npm/**` and the shared `config/lib/**` plugin package root. - Exclude package-local install artifacts such as `node_modules/**` from root Biome coverage. +- Keep repository-owned OpenCode plugin support code under one shared `config/lib/` Bun/TypeScript package root; package metadata and lockfile ownership live at `config/lib/package.json` and `config/lib/bun.lock`, not under individual plugin subdirectories. - Provide Biome through the root Nix dev shell so contributors can run `nix develop -c biome ...` without a host-installed binary or package-local setup. - When exposing JS validation through `nix flake check`, split Bun test, Biome lint/check, and Biome format verification into separately named derivations per target directory so failures stay tool- and surface-specific. diff --git a/context/plans/config-lib-shared-plugin-package.md b/context/plans/config-lib-shared-plugin-package.md new file mode 100644 index 00000000..4fdc6d43 --- /dev/null +++ b/context/plans/config-lib-shared-plugin-package.md @@ -0,0 +1,162 @@ +# config-lib-shared-plugin-package + +## Change summary + +Move the JavaScript plugin tooling under `config/lib/` to a single shared Bun/TypeScript package root for both `agent-trace-plugin` and `bash-policy-plugin`, update the OpenCode plugin dependency to `@opencode-ai/plugin@1.15.4`, and repair repository checks so the shared package is validated by Pkl parity, Bun tests, Biome, TypeScript, and the full flake check. + +Current inspection shows the move is only partially complete: + +- `config/lib/package.json`, `config/lib/bun.lock`, and `config/lib/tsconfig.json` exist at the shared root. +- `config/lib/tsconfig.json` still includes `opencode-sce-agent-trace-plugin.ts` at the package root even though the source lives under `agent-trace-plugin/`. +- `config/lib/bash-policy-plugin/package.json` still exists, while `config/lib/bash-policy-plugin/bun.lock` is missing. +- `flake.nix` still expects package metadata and lockfiles under `config/lib/bash-policy-plugin/`. +- `biome.json` still scopes formatting/linting to `config/lib/bash-policy-plugin/**` only. + +## Success criteria + +- `config/lib/` is the only package root for the repository-owned OpenCode plugin support code under `config/lib/agent-trace-plugin/` and `config/lib/bash-policy-plugin/`. +- `@opencode-ai/plugin` is pinned to `1.15.4` in the shared package metadata and lockfile. +- TypeScript configuration from `config/lib/tsconfig.json` covers both plugin source trees and is strict-mode compatible. +- The bash-policy Bun test suite still runs from the shared package root without a nested package install. +- `flake.nix` no longer references removed nested package/lock files and its `config-lib-*` checks validate the intended shared package source. +- `biome.json` covers the approved JS surfaces after the package-root move and excludes package-local install artifacts. +- Pkl-generated OpenCode plugin outputs are regenerated from canonical sources and `nix run .#pkl-check-generated` reports no drift. +- Full repository validation passes with `nix flake check`. + +## Constraints and non-goals + +- Do not commit `node_modules/` or other package-install artifacts. +- Do not edit generated OpenCode/Claude outputs by hand; change canonical source files and regenerate with Pkl. +- Do not change plugin runtime behavior except where required for `@opencode-ai/plugin@1.15.4` type/API compatibility or existing test/check failures. +- Do not broaden this task to unrelated npm launcher, Rust CLI, release, or agent-content changes. +- Preserve existing flake check names unless renaming is required by the shared-root implementation and context is updated accordingly. + +## Task stack + +- [x] T01: `Unify shared config-lib package metadata` (status:done) + - Task ID: T01 + - Goal: Make `config/lib/` the single Bun package root for both plugin support directories and pin the OpenCode plugin dependency to `1.15.4`. + - Boundaries (in/out of scope): In — `config/lib/package.json`, `config/lib/bun.lock`, removal of obsolete nested package metadata under `config/lib/bash-policy-plugin/` if it is no longer the package root. Out — source-code behavior changes, generated outputs, flake wiring. + - Done when: The shared root package declares the canonical dependencies, including `@opencode-ai/plugin@1.15.4`; the lockfile reflects that version; no stale nested package/lock references remain in package-owned files; `node_modules/` is not staged. + - Verification notes (commands or checks): From repo root, inspect `config/lib/package.json` and `config/lib/bun.lock`; run `nix develop -c sh -c 'cd config/lib && bun install --frozen-lockfile'` after lockfile regeneration; verify `config/lib/bash-policy-plugin/package.json` is removed if the shared root owns the package. + - Completed: 2026-05-19 + - Files changed: `config/lib/package.json`, `config/lib/bun.lock`, `config/lib/bash-policy-plugin/package.json` + - Evidence: `nix develop /home/ivkedev/Desktop/repository/shared-context-engineering -c bun install --lockfile-only` from `config/lib` regenerated the lockfile; `nix develop /home/ivkedev/Desktop/repository/shared-context-engineering -c bun install --frozen-lockfile` from `config/lib` installed `@opencode-ai/plugin@1.15.4`; inspection confirmed root package and lockfile reference `1.15.4`, only `config/lib/package.json` remains under `config/lib/**/package.json`, no stale nested package/lock references remain in `config/lib` package-owned JSON/lock files, and no `config/lib/**/node_modules/**` files were found. + - Notes: Context sync classified this as verify-only for durable root context because later planned tasks own flake/Biome/context-wide ownership wording updates after the package-root move is fully implemented. + +- [x] T02: `Repair shared TypeScript coverage and plugin compatibility` (status:done) + - Task ID: T02 + - Goal: Update the shared TypeScript project so it type-checks both plugin directories against `@opencode-ai/plugin@1.15.4`. + - Boundaries (in/out of scope): In — `config/lib/tsconfig.json`, minimal type/API compatibility fixes in `config/lib/agent-trace-plugin/**/*.ts` and `config/lib/bash-policy-plugin/**/*.ts` if required. Out — behavior changes not required by type checking, generated outputs, Nix/Biome wiring. + - Done when: `config/lib/tsconfig.json` includes both plugin source/test/runtime files intentionally; strict type checking passes; agent-trace extraction semantics and bash-policy runtime semantics remain unchanged except for compatibility fixes. + - Verification notes (commands or checks): `nix develop -c sh -c 'cd config/lib && bunx tsc --noEmit -p tsconfig.json'`; targeted inspection of `extractDiffTracePayload` fallback behavior for `model_id`; existing bash-policy tests remain unchanged unless type-safe test adjustments are required. + - Completed: 2026-05-19 + - Files changed: `config/lib/tsconfig.json`, `context/sce/opencode-agent-trace-plugin-runtime.md`, `context/context-map.md` + - Evidence: `nix develop -c sh -c 'cd config/lib && bunx tsc --noEmit -p tsconfig.json'` initially failed because the shared tsconfig still included only the removed package-root `opencode-sce-agent-trace-plugin.ts`; after updating the shared tsconfig, the same command passed. `nix develop -c sh -c 'cd config/lib && bun test ./bash-policy-plugin/bash-policy-runtime.test.ts'` passed with 65 tests. Targeted inspection confirmed the agent-trace plugin `model_id` expression remains unchanged from the current source behavior. + - Notes: Context sync classification is verify-only for durable root context because this task only adjusts TypeScript project coverage/module resolution; flake, Biome, generated outputs, and durable ownership wording remain owned by later tasks. Context sync also repaired stale agent-trace plugin runtime documentation to match current code truth discovered during targeted inspection. + +- [x] T03: `Retarget config-lib flake checks to shared package root` (status:done) + - Task ID: T03 + - Goal: Update `flake.nix` so `config-lib-bun-tests`, `config-lib-biome-check`, and `config-lib-biome-format` consume the shared `config/lib/` package root and no longer depend on removed nested package files. + - Boundaries (in/out of scope): In — `flake.nix` source filesets, fixed-output dependency derivation input root/files, copied check directory layout, dependency output hash update. Out — unrelated flake checks, Rust package/check logic, npm launcher checks. + - Done when: The config-lib derivations include both plugin directories plus shared `package.json`, `bun.lock`, and `tsconfig.json`; bash-policy tests still execute in the expected relative paths; removed `config/lib/bash-policy-plugin/bun.lock` references are gone. + - Verification notes (commands or checks): `nix flake check`; if the fixed-output dependency hash changes, update it from the Nix-reported expected hash and rerun the check. + - Completed: 2026-05-19 + - Files changed: `flake.nix` + - Evidence: `nix build .#checks.x86_64-linux.config-lib-bun-tests --no-link --print-out-paths` passed at `/nix/store/hcwxk6j2mkx1y22fs45a53g74wqi2s05-config-lib-bun-tests`; `nix build .#checks.x86_64-linux.config-lib-biome-check --no-link --print-out-paths` passed at `/nix/store/i3bcj0qj9j21jrkgnvpy80pzcsakfyfl-config-lib-biome-check`; `nix build .#checks.x86_64-linux.config-lib-biome-format` reached the retargeted shared-root derivation and failed on existing `config/lib/tsconfig.json` formatting, which is owned by T04. The fixed-output dependency hash was updated from Nix's reported `sha256-yDKVHH46EzzyiCwBSISEXnJJbqZ2ihvS2H0SGgITaPY=` after retargeting dependencies to `config/lib/package.json` and `config/lib/bun.lock`. + - Notes: Normal flake evaluation required the new shared-root package files from prior tasks to be visible to git, so they were marked intent-to-add for local validation without committing. Context sync classification: important for config-lib check ownership, but durable wording updates are expected to be focused and may overlap with planned T06. + +- [x] T04: `Expand root Biome scope for shared config-lib` (status:done) + - Task ID: T04 + - Goal: Align root Biome coverage with the shared `config/lib/` package layout. + - Boundaries (in/out of scope): In — `biome.json` include/exclude patterns and formatting/lint fixes in `config/lib/**` that are surfaced by Biome. Out — unrelated JS surfaces outside `npm/**` and `config/lib/**`, behavior changes beyond lint/format compliance. + - Done when: Biome includes both `config/lib/bash-policy-plugin/**` and `config/lib/agent-trace-plugin/**` through an intentional shared-root pattern and excludes `config/lib/node_modules/**`; check and format derivations pass. + - Verification notes (commands or checks): `nix develop -c biome check --formatter-enabled=false config/lib`; `nix develop -c biome check --linter-enabled=false config/lib`; `nix flake check`. + - Completed: 2026-05-19 + - Files changed: `biome.json`, `config/lib/agent-trace-plugin/opencode-sce-agent-trace-plugin.ts`, `config/lib/tsconfig.json` + - Evidence: `nix develop -c biome check --formatter-enabled=false config/lib` passed; `nix develop -c biome check --linter-enabled=false config/lib` passed after `nix develop -c biome format --write config/lib` formatted the shared config-lib files; `nix build .#checks.x86_64-linux.config-lib-biome-check --no-link --print-out-paths` passed at `/nix/store/5b1i7zfz5p10alv44nq1zglyk1z199ng-config-lib-biome-check`; `nix build .#checks.x86_64-linux.config-lib-biome-format --no-link --print-out-paths` passed at `/nix/store/99g8c34i6bcjalwbkqk8giw1q91pmhmp-config-lib-biome-format`; `nix flake check` passed. + - Notes: Context sync classification is important for the root Biome tooling contract because `biome.json` now covers `config/lib/**`; focused durable context updates refreshed the current root Biome wording in shared context files without changing plugin runtime behavior. `nix run .#pkl-check-generated` was also run during context sync and reported generated OpenCode agent-trace plugin drift from the canonical source formatting; regenerating generated outputs remains the planned T05 scope. + +- [x] T05: `Regenerate generated plugin outputs from Pkl` (status:done) + - Task ID: T05 + - Goal: Refresh generated OpenCode plugin artifacts after canonical source/package changes. + - Boundaries (in/out of scope): In — run the existing Pkl generation workflow and commit resulting generated files under `config/.opencode/**` and `config/automated/.opencode/**` if they change. Out — hand-editing generated files, changing plugin registration semantics. + - Done when: Generated `sce-agent-trace.ts` and `sce-bash-policy.ts` files match canonical sources; generated manifests remain registered for both plugins; Pkl parity reports no drift. + - Verification notes (commands or checks): `nix develop -c pkl eval -m . config/pkl/generate.pkl`; `nix run .#pkl-check-generated`; inspect `config/.opencode/plugins/` and `config/automated/.opencode/plugins/` for expected generated plugin files. + - Completed: 2026-05-19 + - Files changed: `config/.opencode/plugins/sce-agent-trace.ts`, `config/automated/.opencode/plugins/sce-agent-trace.ts` + - Evidence: `nix develop -c pkl eval -m . config/pkl/generate.pkl` regenerated outputs; `nix run .#pkl-check-generated` passed with "Generated outputs are up to date."; inspection confirmed both manual and automated generated plugin directories contain `sce-agent-trace.ts` and `sce-bash-policy.ts`, and both generated `opencode.json` manifests register `./plugins/sce-bash-policy.ts` plus `./plugins/sce-agent-trace.ts`. + - Notes: Context sync classification is verify-only for durable root context because this task only refreshes generated plugin output parity from existing canonical sources and does not change plugin registration semantics or runtime behavior. + +- [x] T06: `Sync context for shared config-lib ownership` (status:done) + - Task ID: T06 + - Goal: Update durable context to describe the current shared `config/lib/` package/check ownership after implementation. + - Boundaries (in/out of scope): In — focused updates to `context/overview.md`, `context/architecture.md`, `context/patterns.md`, `context/glossary.md`, `context/context-map.md`, and plugin-specific context files if code truth changes. Out — completed-work narration, unrelated SCE/CLI history edits. + - Done when: Context no longer states that config-lib checks or Biome scope are limited only to `config/lib/bash-policy-plugin/` if implementation broadens them; package-root and validation descriptions match code truth. + - Verification notes (commands or checks): Compare context statements against `flake.nix`, `biome.json`, `config/lib/package.json`, and `config/lib/tsconfig.json`; run `nix run .#pkl-check-generated` after context-only edits to ensure generated parity remains stable. + - Completed: 2026-05-19 + - Files changed: `context/overview.md`, `context/architecture.md`, `context/patterns.md`, `context/glossary.md`, `context/sce/bash-tool-policy-enforcement-contract.md` + - Evidence: Compared context wording against `flake.nix`, `biome.json`, `config/lib/package.json`, `config/lib/tsconfig.json`, and plugin source paths; `nix run .#pkl-check-generated` passed with "Generated outputs are up to date." + - Notes: Context sync classification is important because this task updates durable package/check ownership wording. Root context now describes `config/lib/` as the shared Bun/TypeScript package root for both plugin directories, the shared root dependency/lock ownership, strict TypeScript coverage, and shared-root config-lib flake checks. Plugin-specific drift in the bash-policy related-files list and agent-trace glossary extraction wording was repaired to match code truth. + +- [x] T07: `Validation and cleanup` (status:done) + - Task ID: T07 + - Goal: Run the full requested validation suite and clean temporary/package artifacts before handoff. + - Boundaries (in/out of scope): In — full repository checks, Pkl parity, config-lib targeted checks, cleanup of temporary files and untracked install artifacts. Out — new feature work or broad refactors discovered during validation. + - Done when: `nix flake check` passes; `nix run .#pkl-check-generated` passes; shared config-lib Bun tests and TypeScript checks pass; no `node_modules/` or temporary validation artifacts are staged; the plan records validation evidence. + - Verification notes (commands or checks): `nix develop -c sh -c 'cd config/lib && bun test ./bash-policy-plugin/bash-policy-runtime.test.ts'`; `nix develop -c sh -c 'cd config/lib && bunx tsc --noEmit -p tsconfig.json'`; `nix run .#pkl-check-generated`; `nix flake check`; inspect `git status` before final handoff. + - Completed: 2026-05-19 + - Files changed: `context/plans/config-lib-shared-plugin-package.md` + - Evidence: `nix develop -c sh -c 'cd config/lib && bun test ./bash-policy-plugin/bash-policy-runtime.test.ts'` passed with 65 tests; `nix develop -c sh -c 'cd config/lib && bunx tsc --noEmit -p tsconfig.json'` passed; `nix run .#pkl-check-generated` passed with "Generated outputs are up to date."; `nix flake check` passed with "all checks passed!". Scoped cleanup removed ignored `config/lib/node_modules`, the root `result` symlink, and `context/tmp` session artifacts while preserving `context/tmp/.gitignore`. Final `git status --short --ignored` showed no staged `node_modules/` or temporary validation artifacts; remaining ignored local artifacts are outside this task's scoped cleanup. + - Notes: Context sync classification is verify-only for durable root context because this task performed validation/cleanup and did not change package behavior, check ownership, or terminology. + +- [x] T08: `Repair config-lib flake shared-root regression` (status:done) + - Task ID: T08 + - Goal: Correct the current `flake.nix` config-lib check wiring so the Bash-policy and Agent Trace plugin support code are validated from the shared `config/lib/` Bun/TypeScript package root. + - Boundaries (in/out of scope): In — `flake.nix` config-lib source fileset, config-lib dependency/check derivation roots, copied check layout, and any fixed-output dependency hash update required by the existing `config/lib/package.json` + `config/lib/bun.lock`. Out — unrelated Rust/Cargo checks, npm launcher checks, plugin runtime behavior, package dependency changes, generated OpenCode/Claude outputs unless validation proves drift from this task. + - Done when: `flake.nix` no longer uses `config/lib/bash-policy-plugin/` as the config-lib package root; the config-lib source set includes shared `package.json`, `bun.lock`, `tsconfig.json`, `agent-trace-plugin/**`, and `bash-policy-plugin/**`; config-lib checks execute from the shared package root; the undefined `configconfigLibBashPolicySrcLibSrc` reference is removed; targeted config-lib Nix checks pass. + - Verification notes (commands or checks): `nix build .#checks.x86_64-linux.config-lib-bun-tests --no-link --print-out-paths`; `nix build .#checks.x86_64-linux.config-lib-biome-check --no-link --print-out-paths`; `nix build .#checks.x86_64-linux.config-lib-biome-format --no-link --print-out-paths`; run `nix flake check` if feasible after the targeted checks. + - Completed: 2026-05-19 + - Files changed: `flake.nix`, `context/plans/config-lib-shared-plugin-package.md` + - Evidence: `nix build .#checks.x86_64-linux.config-lib-bun-tests --no-link --print-out-paths` passed at `/nix/store/29kyshwpdg8j1hblpmnycdhwi96pvm1w-config-lib-bun-tests`; `nix build .#checks.x86_64-linux.config-lib-biome-check --no-link --print-out-paths` passed at `/nix/store/5b1i7zfz5p10alv44nq1zglyk1z199ng-config-lib-biome-check`; `nix build .#checks.x86_64-linux.config-lib-biome-format --no-link --print-out-paths` passed at `/nix/store/99g8c34i6bcjalwbkqk8giw1q91pmhmp-config-lib-biome-format`; `nix flake check` passed with `all checks passed!`; `nix run .#pkl-check-generated` passed with `Generated outputs are up to date.`. + - Notes: `configLibSrc` now uses `config/lib/` as the shared source root, includes shared package metadata plus explicit per-file entries for bash-policy-plugin only (matching the old per-file style — `tsconfig.json` and `agent-trace-plugin/` are not needed by config-lib checks), all config-lib check derivations copy that shared source, the stale bash-policy-root source naming was removed, and the undefined `configconfigLibBashPolicySrcLibSrc` reference was replaced. `configLibDeps` removes Bun's dangling optional `download-msgpackr-prebuilds` bin symlink before copying `node_modules` into the fixed-output dependency derivation so Nix's broken-symlink fixup passes. Nix flake evaluation required the new shared-root package files to be visible to the Git-backed flake source, so `config/lib/package.json`, `config/lib/bun.lock`, and `config/lib/tsconfig.json` were marked intent-to-add for local validation without committing. Context sync classification is verify-only for durable root context because existing shared context already describes the intended shared-root config-lib ownership and this task repairs code to match it. + +## Validation Report + +### Commands run + +- `nix build .#checks.x86_64-linux.config-lib-bun-tests --no-link --print-out-paths` -> exit 0 (`/nix/store/29kyshwpdg8j1hblpmnycdhwi96pvm1w-config-lib-bun-tests`). +- `nix build .#checks.x86_64-linux.config-lib-biome-check --no-link --print-out-paths` -> exit 0 (`/nix/store/5b1i7zfz5p10alv44nq1zglyk1z199ng-config-lib-biome-check`). +- `nix build .#checks.x86_64-linux.config-lib-biome-format --no-link --print-out-paths` -> exit 0 (`/nix/store/99g8c34i6bcjalwbkqk8giw1q91pmhmp-config-lib-biome-format`). +- `nix flake check` -> exit 0 (`all checks passed!`). +- `nix run .#pkl-check-generated` -> exit 0 (`Generated outputs are up to date.`). +- `git status --short --ignored` -> exit 0 (confirmed planned tracked changes plus ignored local artifacts; no staged `node_modules/` or temporary validation artifacts). + +### Cleanup + +- No task-owned temporary scaffolding was introduced. Targeted Nix builds used `--no-link` and did not create new result symlinks. + +### Success-criteria verification + +- [x] `config/lib/` is the only package root for repository-owned OpenCode plugin support code: verified by current `config/lib/package.json`, `config/lib/bun.lock`, `config/lib/tsconfig.json`, removed nested package metadata, and passing shared-root checks. +- [x] `@opencode-ai/plugin` is pinned to `1.15.4`: verified in `config/lib/package.json` and by passing config-lib validation. +- [x] Shared TypeScript coverage is strict-mode compatible: verified by `bunx tsc --noEmit -p tsconfig.json`. +- [x] Bash-policy Bun tests run from the shared package root: verified by the targeted Bun test command (`65 pass`). +- [x] `flake.nix` validates the intended shared package source: `configLibSrc` is rooted at `config/lib/`, includes shared metadata plus both plugin directories, and all config-lib derivations copy that shared source. +- [x] `biome.json` covers the approved JS surfaces after the move: verified by `nix flake check` config-lib Biome derivations. +- [x] Pkl-generated OpenCode plugin outputs have no drift: verified by `nix run .#pkl-check-generated`. +- [x] Full repository validation passes: verified by `nix flake check`. +- [x] T08 regression fix acceptance: removed the stale bash-policy-root source, removed the undefined `configconfigLibBashPolicySrcLibSrc` reference, updated the fixed-output dependency hash to `sha256-yDKVHH46EzzyiCwBSISEXnJJbqZ2ihvS2H0SGgITaPY=`, and removed Bun's dangling optional `download-msgpackr-prebuilds` bin symlink before Nix fixup. + +### Failed checks and follow-ups + +- None. + +### Residual risks + +- Ignored local developer artifacts outside this task's cleanup scope remain (`.direnv/`, `.opencode/`, `cli/assets/generated/`, `cli/target/`, `context/tmp/*`, `result`). They are not staged and were not modified for this validation task. +- The shared-root `config/lib/package.json`, `config/lib/bun.lock`, and `config/lib/tsconfig.json` files were marked intent-to-add so Git-backed Nix flake evaluation can see the moved files before commit. + +## Open questions + +- None currently blocking. The plan treats the user's package move intent as approval to consolidate on `config/lib/` as the shared package root for both plugin directories. diff --git a/context/sce/bash-tool-policy-enforcement-contract.md b/context/sce/bash-tool-policy-enforcement-contract.md index 061f38a6..2e34297c 100644 --- a/context/sce/bash-tool-policy-enforcement-contract.md +++ b/context/sce/bash-tool-policy-enforcement-contract.md @@ -241,11 +241,10 @@ For a non-matching command, enforcement must allow the bash tool to continue nor - redundancy reporting for `forbid-git-all` plus `forbid-git-commit` without treating that pair as invalid ## Related files - - `context/plans/bash-tool-policy-enforcement.md` - `context/cli/config-precedence-contract.md` - `config/pkl/base/bash-policy-presets.pkl` - `config/pkl/generate.pkl` -- `config/lib/bash-policy/bash-policy-runtime.ts` -- `config/lib/bash-policy/opencode-bash-policy-plugin.ts` +- `config/lib/bash-policy-plugin/bash-policy/runtime.ts` +- `config/lib/bash-policy-plugin/opencode-bash-policy-plugin.ts` - `cli/src/services/config/mod.rs` diff --git a/context/sce/opencode-agent-trace-plugin-runtime.md b/context/sce/opencode-agent-trace-plugin-runtime.md index 80c99726..afb37960 100644 --- a/context/sce/opencode-agent-trace-plugin-runtime.md +++ b/context/sce/opencode-agent-trace-plugin-runtime.md @@ -22,16 +22,16 @@ Returns `{ sessionID, diff, time, model_id }` only when all checks pass: 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) +6. `info.summary?.diffs` is a non-empty array; non-object entries are skipped +7. Each object entry contributes its `patch` value or an empty string, and entries are joined with `\n` to form the `diff` output string (no `diff` field fallback; only `patch` is used) +8. If no object entries are present, the helper returns `undefined`; all-empty patch values still produce a payload and are left to the Rust `diff-trace` hook validation 9. `time` is sourced from `Date.now()` (Unix epoch milliseconds at extraction time) -10. `model_id` is built as `providerID/modelID` from `info.model.providerID` and `info.model.modelID`, with each missing or empty component falling back to `"unknown"` +10. `model_id` is built directly as `providerID/modelID` from `info.model.providerID` and `info.model.modelID` Otherwise, the helper returns `undefined`. ## Current usage boundary -- The extraction seam is 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. +- The extraction seam is internal to the source module and is used by `buildTrace` at runtime. +- `buildTrace` calls `extractDiffTracePayload`; if the result is `undefined` (non-`message.updated` event, non-user role, empty diffs array, or no object diff entries), no hook invocation occurs. - When extraction succeeds, `buildTrace` forwards the extracted payload to `sce hooks diff-trace` via STDIN JSON; the Rust hook runtime validates required `sessionID`/`diff`/`model_id` plus `time` and persists those fields through AgentTraceDb `diff_traces` insertion. diff --git a/flake.nix b/flake.nix index 54cc4701..2a85eb09 100644 --- a/flake.nix +++ b/flake.nix @@ -89,10 +89,10 @@ }; configLibBashPolicySrc = pkgs.lib.fileset.toSource { - root = ./config/lib/bash-policy-plugin; + root = ./config/lib; fileset = pkgs.lib.fileset.unions [ - ./config/lib/bash-policy-plugin/package.json - ./config/lib/bash-policy-plugin/bun.lock + ./config/lib/package.json + ./config/lib/bun.lock ./config/lib/bash-policy-plugin/bash-policy/runtime.ts ./config/lib/bash-policy-plugin/bash-policy-runtime.test.ts ./config/lib/bash-policy-plugin/opencode-bash-policy-plugin.ts @@ -114,10 +114,10 @@ pname = "config-lib-bash-policy-deps"; version = "0.1.0"; src = pkgs.lib.fileset.toSource { - root = ./config/lib/bash-policy-plugin; + root = ./config/lib; fileset = pkgs.lib.fileset.unions [ - ./config/lib/bash-policy-plugin/package.json - ./config/lib/bash-policy-plugin/bun.lock + ./config/lib/package.json + ./config/lib/bun.lock ]; }; nativeBuildInputs = [ pkgs.bun ]; @@ -820,9 +820,9 @@ set -euo pipefail # Copy source files - cp -r "${configLibBashPolicySrc}" ./bash-policy - chmod -R u+w ./bash-policy - cd ./bash-policy + cp -r "${configLibBashPolicySrc}" ./config-lib + chmod -R u+w ./config-lib + cd ./config-lib # Use pre-fetched dependencies from FOD cp -r "${configLibBashPolicyDeps}/node_modules" ./ @@ -841,9 +841,9 @@ '' set -euo pipefail - cp -r "${configLibBashPolicySrc}" ./bash-policy - chmod -R u+w ./bash-policy - cd ./bash-policy + cp -r "${configLibBashPolicySrc}" ./config-lib + chmod -R u+w ./config-lib + cd ./config-lib biome check --formatter-enabled=false . @@ -858,9 +858,9 @@ '' set -euo pipefail - cp -r "${configLibBashPolicySrc}" ./bash-policy - chmod -R u+w ./bash-policy - cd ./bash-policy + cp -r "${configLibBashPolicySrc}" ./config-lib + chmod -R u+w ./config-lib + cd ./config-lib biome check --linter-enabled=false . From 3692c463c3129546da7c30cc1fa6673c685252b6 Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Tue, 19 May 2026 14:49:27 +0200 Subject: [PATCH 10/11] agent-trace-db: Add agent_trace_id column to agent_traces table New migration 006 adds nullable `agent_trace_id TEXT` so the UUIDv7 identifier is queryable without parsing `trace_json`. Migration is registered in AGENT_TRACE_MIGRATIONS, INSERT_AGENT_TRACE_SQL updated to a 4-parameter statement, AgentTraceInsert gains `agent_trace_id`, and the hooks/mod.rs caller passes `agent_trace.id` through. Plan: add-agent-traces-agent-trace-id Tasks: T01, T02, T03, T04 Co-authored-by: SCE --- .../006_add_agent_traces_agent_trace_id.sql | 1 + cli/src/services/agent_trace_db/mod.rs | 17 ++- cli/src/services/hooks/mod.rs | 1 + context/context-map.md | 2 +- context/glossary.md | 2 +- .../plans/add-agent-traces-agent-trace-id.md | 121 ++++++++++++++++++ context/sce/agent-trace-db.md | 6 +- 7 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql create mode 100644 context/plans/add-agent-traces-agent-trace-id.md diff --git a/cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql b/cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql new file mode 100644 index 00000000..f271f5d5 --- /dev/null +++ b/cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql @@ -0,0 +1 @@ +ALTER TABLE agent_traces ADD COLUMN agent_trace_id TEXT; diff --git a/cli/src/services/agent_trace_db/mod.rs b/cli/src/services/agent_trace_db/mod.rs index 82ad362b..0c8b3946 100644 --- a/cli/src/services/agent_trace_db/mod.rs +++ b/cli/src/services/agent_trace_db/mod.rs @@ -22,6 +22,8 @@ const CREATE_AGENT_TRACES_MIGRATION: &str = include_str!("../../../migrations/agent-trace/004_create_agent_traces.sql"); const ADD_DIFF_TRACES_MODEL_ID_MIGRATION: &str = include_str!("../../../migrations/agent-trace/005_add_diff_traces_model_id.sql"); +const ADD_AGENT_TRACES_AGENT_TRACE_ID_MIGRATION: &str = + include_str!("../../../migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql"); const AGENT_TRACE_MIGRATIONS: &[(&str, &str)] = &[ ("001_create_diff_traces", CREATE_DIFF_TRACES_MIGRATION), @@ -38,6 +40,10 @@ const AGENT_TRACE_MIGRATIONS: &[(&str, &str)] = &[ "005_add_diff_traces_model_id", ADD_DIFF_TRACES_MODEL_ID_MIGRATION, ), + ( + "006_add_agent_traces_agent_trace_id", + ADD_AGENT_TRACES_AGENT_TRACE_ID_MIGRATION, + ), ]; /// Parameterized SQL for inserting a captured diff trace payload. @@ -65,7 +71,7 @@ pub const INSERT_POST_COMMIT_PATCH_INTERSECTION_SQL: &str = /// Parameterized SQL for inserting a built agent trace payload. pub const INSERT_AGENT_TRACE_SQL: &str = - "INSERT INTO agent_traces (commit_id, commit_time_ms, trace_json) VALUES (?1, ?2, ?3)"; + "INSERT INTO agent_traces (commit_id, commit_time_ms, trace_json, agent_trace_id) VALUES (?1, ?2, ?3, ?4)"; /// Agent trace database configuration. pub struct AgentTraceDbSpec; @@ -159,6 +165,7 @@ pub struct AgentTraceInsert<'a> { pub commit_id: &'a str, pub commit_time_ms: i64, pub trace_json: &'a str, + pub agent_trace_id: &'a str, } impl AgentTraceDb { @@ -180,7 +187,12 @@ impl AgentTraceDb { pub fn insert_agent_trace(&self, input: AgentTraceInsert<'_>) -> Result { self.execute( INSERT_AGENT_TRACE_SQL, - (input.commit_id, input.commit_time_ms, input.trace_json), + ( + input.commit_id, + input.commit_time_ms, + input.trace_json, + input.agent_trace_id, + ), ) } @@ -530,6 +542,7 @@ mod tests { "003_add_diff_traces_time_ms_id_index", "004_create_agent_traces", "005_add_diff_traces_model_id", + "006_add_agent_traces_agent_trace_id", ] ); } diff --git a/cli/src/services/hooks/mod.rs b/cli/src/services/hooks/mod.rs index 89fee59f..4c7698dc 100644 --- a/cli/src/services/hooks/mod.rs +++ b/cli/src/services/hooks/mod.rs @@ -534,6 +534,7 @@ where commit_id: &flow_result.post_commit_data.commit_oid, commit_time_ms: flow_result.post_commit_data.commit_time_ms, trace_json: &serialized, + agent_trace_id: &agent_trace.id, }; persist_agent_trace(insert_input)?; diff --git a/context/context-map.md b/context/context-map.md index 43752398..c5a9cb42 100644 --- a/context/context-map.md +++ b/context/context-map.md @@ -42,7 +42,7 @@ 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, `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-db.md` (implemented `cli/src/services/agent_trace_db/mod.rs` Agent Trace database wrapper with canonical `/sce/agent-trace.db` path, ordered `diff_traces`, `post_commit_patch_intersections`, `diff_traces(time_ms, id)` index, `agent_traces`, nullable `diff_traces.model_id`, and nullable `agent_traces.agent_trace_id` migrations applied through shared migration metadata, typed parameterized insert helpers for diff traces including `model_id`, post-commit intersection rows, and built `agent_traces` rows with `agent_trace_id`, inclusive bounded chronological recent `diff_traces` query/parse support with malformed-row skip accounting, registered setup/doctor lifecycle provider, and active hook writers for `diff_traces` intake plus post-commit intersection/agent-trace persistence) - `context/sce/agent-trace-core-schema-migrations.md` (historical reference for removed local DB schema bootstrap behavior; T03 now implements the actual local DB with migrations) - `context/sce/agent-trace-retry-queue-observability.md` (inactive local-hook retry path plus historical retry/metrics reference) - `context/sce/agent-trace-local-hooks-mvp-contract-gap-matrix.md` (T01 Local Hooks MVP production contract freeze and deterministic gap matrix for `agent-trace-local-hooks-production-mvp`) diff --git a/context/glossary.md b/context/glossary.md index 5c8f4383..e1bdd859 100644 --- a/context/glossary.md +++ b/context/glossary.md @@ -30,7 +30,7 @@ - `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`, 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`). +- `agent trace DB adapter`: Module in `cli/src/services/agent_trace_db/mod.rs` that defines `AgentTraceDbSpec`, exposes `AgentTraceDb` as a `TursoDb` alias, resolves `/sce/agent-trace.db` through `agent_trace_db_path()`, embeds ordered migrations for `diff_traces`, `post_commit_patch_intersections`, the `diff_traces(time_ms, id)` index, `agent_traces`, nullable `diff_traces.model_id`, and nullable `agent_traces.agent_trace_id`, provides typed parameterized insert helpers for diff traces including `model_id`, post-commit intersection rows, and built agent-trace rows (including `agent_trace_id`), exposes chronological recent `diff_traces` query/parse support with malformed-row skip accounting, has `AgentTraceDbLifecycle` for setup/doctor integration, and is written by `sce hooks diff-trace` (`diff_traces`) plus `sce hooks post-commit` (`post_commit_patch_intersections` and built `agent_traces`). - `DiffTraceInsert`: Insert payload in `cli/src/services/agent_trace_db/mod.rs` carrying `time_ms`, `session_id`, `patch`, and `model_id` for parameterized writes to the `diff_traces` table. - `DbSpec`: Service-specific database metadata trait in `cli/src/services/db/mod.rs` that supplies a diagnostic database name, canonical path resolver, and ordered embedded migration list for `TursoDb`. - `TursoDb`: Generic shared Turso database adapter in `cli/src/services/db/mod.rs`; owns parent-directory creation, Turso local open/connect flow, tokio current-thread runtime bridging, synchronous `execute()`/`query()`/`query_map()` wrappers, per-database `__sce_migrations` metadata, and generic migration execution for a `DbSpec` implementation. diff --git a/context/plans/add-agent-traces-agent-trace-id.md b/context/plans/add-agent-traces-agent-trace-id.md new file mode 100644 index 00000000..092e247a --- /dev/null +++ b/context/plans/add-agent-traces-agent-trace-id.md @@ -0,0 +1,121 @@ +# Plan: Add `agent_trace_id` column to `agent_traces` table + +## Change summary + +The `agent_traces` table currently stores the UUIDv7 agent trace ID only inside `trace_json` (as part of the serialized `AgentTrace` JSON payload). Add a dedicated `agent_trace_id TEXT` column so the UUIDv7 identifier is queryable without parsing JSON. + +The `generate_agent_trace_id()` function in `agent_trace.rs` already produces a UUIDv7 string. No uuid v4 usage exists anywhere in the codebase — confirmed via grep, nothing to remove. + +Three layers need updating: +1. **SQL migration** — add the column to the `agent_traces` table +2. **Rust DB layer** — register migration, update insert SQL and `AgentTraceInsert` struct +3. **Rust hook caller** — extract `agent_trace.id` from the built `AgentTrace` and pass it through + +## Success criteria + +- New migration `006_add_agent_traces_agent_trace_id.sql` adds `agent_trace_id TEXT` (nullable) to `agent_traces` +- Migration is registered in `AGENT_TRACE_MIGRATIONS` +- `INSERT_AGENT_TRACE_SQL` includes `agent_trace_id` as a parameter +- `AgentTraceInsert` carries `agent_trace_id: &'a str` +- Caller in `hooks/mod.rs` extracts `agent_trace.id` and passes it in the insert +- `nix flake check` passes + +## Constraints and non-goals + +- The `agent_trace_id` column is **nullable** (existing rows get `NULL`). +- No changes to `agent_trace.rs` — `generate_agent_trace_id` and `AgentTrace.id` are unchanged. +- No changes to the TypeScript plugin, payload parsing, or any other table. +- No new tests — existing build/tests cover the change. +- uuid v4 removal is not needed — confirmed zero usage across the codebase. + +## Task stack + +- [x] T01: `Create migration 006_add_agent_traces_agent_trace_id.sql` (status:done) + - Task ID: T01 + - Goal: Create the SQL migration file that adds the `agent_trace_id` column. + - Boundaries (in/out of scope): + - In — Create `cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql` with `ALTER TABLE agent_traces ADD COLUMN agent_trace_id TEXT;` + - Out — Changes to any other table, migration runner, or existing files + - Done when: + - Migration file exists at `cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql` + - Contains `ALTER TABLE agent_traces ADD COLUMN agent_trace_id TEXT;` + - Verification notes (commands or checks): + - `test -f cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql` ✅ + - File content inspection ✅ + - `nix develop -c sh -c 'cd cli && cargo check'` ✅ + - **Completed:** 2026-05-19 + - **Files changed:** `cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql` (new) + - **Evidence:** File exists with correct ALTER TABLE content; `cargo check` passes (0.52s) + +- [x] T02: `Register migration and update Rust DB insert path` (status:done) + - Task ID: T02 + - Goal: Wire the new migration into the Rust DB layer and update the insert SQL + struct. + - Boundaries (in/out of scope): + - In — Register `006_add_agent_traces_agent_trace_id` in `AGENT_TRACE_MIGRATIONS` with corresponding `include_str!` constant; update `INSERT_AGENT_TRACE_SQL` to include `agent_trace_id`; add `agent_trace_id: &'a str` to `AgentTraceInsert` + - Out — Changes to any other SQL statements, query logic, or structs (`DiffTraceInsert`, `PostCommitPatchIntersectionInsert`, etc.) + - Done when: + - Migration `006_add_agent_traces_agent_trace_id` registered in the migrations list + - `AgentTraceInsert` has `agent_trace_id: &'a str` field + - `INSERT_AGENT_TRACE_SQL` is `INSERT INTO agent_traces (commit_id, commit_time_ms, trace_json, agent_trace_id) VALUES (?1, ?2, ?3, ?4)` + - Build compiles (caller not yet updated, so `AgentTraceInsert` construction may still fail — acceptable at T02 boundary) + - Verification notes (commands or checks): + - `nix develop -c sh -c 'cd cli && cargo check'` (expect compile errors in hooks/mod.rs — that's T03 scope) + - Visual inspection of the migration constant, SQL, and struct + - **Completed:** 2026-05-19 + - **Files changed:** `cli/src/services/agent_trace_db/mod.rs` + - **Evidence:** Migration registered in list; SQL updated to 4 params; struct has `agent_trace_id`; only expected error in `hooks/mod.rs` (T03 scope); test assertion updated for 6 migrations; `cargo check` reports only the expected caller error + +- [x] T03: `Pass agent_trace_id from hooks/mod.rs caller` (status:done) + - Task ID: T03 + - Goal: Extract the UUIDv7 ID from the built `AgentTrace` struct and pass it in the insert input. + - Boundaries (in/out of scope): + - In — Modify the insert-input construction in `hooks/mod.rs` (around line 533) to include `agent_trace_id: &agent_trace.id` + - Out — Changes to `agent_trace.rs`, validation logic, or any other flow + - Done when: + - `AgentTraceInsert` in `hooks/mod.rs` includes `agent_trace_id: &agent_trace.id` + - `nix develop -c sh -c 'cd cli && cargo check'` passes + - Verification notes (commands or checks): + - `nix develop -c sh -c 'cd cli && cargo check'` + - `nix flake check` + - **Completed:** 2026-05-19 + - **Files changed:** `cli/src/services/hooks/mod.rs` + - **Evidence:** `cargo check` passes; `nix flake check` passes; `agent_trace_id: &agent_trace.id` added to `AgentTraceInsert` constructor + +- [x] T04: `Validation and cleanup` (status:done) + - Task ID: T04 + - Goal: Verify full pipeline compiles, existing tests pass, and no regressions. + - Boundaries (in/out of scope): + - In — Run `nix flake check`; confirm new migration is loadable; confirm applied migration ID assertions include the new migration; confirm no stale artifacts in `context/tmp/` + - Out — Any code changes beyond verification + - Done when: + - `nix flake check` passes + - No stale artifacts left in `context/tmp/` + - Verification notes (commands or checks): + - `nix flake check` + - `nix develop -c sh -c 'cd cli && cargo check'` + - **Completed:** 2026-05-19 + - **Files changed:** none (verification-only) + - **Evidence:** Migration file exists at `cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql` with correct ALTER TABLE content; migration registered in AGENT_TRACE_MIGRATIONS; INSERT_AGENT_TRACE_SQL includes `agent_trace_id`; AgentTraceInsert has `agent_trace_id: &'a str`; test assertions cover all 6 migration IDs; stale artifacts cleaned from `context/tmp/`; `nix flake check` passes + +## Validation Report + +### Commands run +- `nix flake check` -> exit 0 — all checks evaluated and built cleanly (pkl-parity built and passed) +- `rm -f context/tmp/2026-*.json context/tmp/sce.log` — 138 stale JSON artifacts + sce.log removed; only `.gitignore` remains +- Verified migration file: `test -f cli/migrations/agent-trace/006_add_agent_traces_agent_trace_id.sql` ✅ +- Verified migration content: `ALTER TABLE agent_traces ADD COLUMN agent_trace_id TEXT;` ✅ + +### Success-criteria verification +- [x] New migration `006_add_agent_traces_agent_trace_id.sql` adds `agent_trace_id TEXT` (nullable) — confirmed file exists with correct SQL +- [x] Migration registered in `AGENT_TRACE_MIGRATIONS` — confirmed line 44 of `agent_trace_db/mod.rs` +- [x] `INSERT_AGENT_TRACE_SQL` includes `agent_trace_id` as 4th parameter — confirmed line 74 +- [x] `AgentTraceInsert` carries `agent_trace_id: &'a str` — confirmed line 168 +- [x] Caller in `hooks/mod.rs` passes `agent_trace_id: &agent_trace.id` — confirmed line 537 +- [x] `nix flake check` passes — confirmed 2026-05-19 + +### Residual risks +- None identified. The `agent_trace_id` column is nullable, so existing rows with NULL remain valid. No schema changes affect other tables or operations. + +## Open questions + +None — all clarifications resolved during intake. diff --git a/context/sce/agent-trace-db.md b/context/sce/agent-trace-db.md index 5874fe5d..f6c334ce 100644 --- a/context/sce/agent-trace-db.md +++ b/context/sce/agent-trace-db.md @@ -16,7 +16,7 @@ pub type AgentTraceDb = TursoDb; - `recent_diff_trace_patches(cutoff_time_ms, end_time_ms)`: chronological `diff_traces` read helper for rows in the inclusive window `time_ms >= cutoff_time_ms AND time_ms <= end_time_ms`; parses raw patch text through `parse_patch` and skips malformed rows without failing the query. - `PostCommitPatchIntersectionInsert<'a>`: insert payload for post-commit intersection results with commit metadata, window bounds, loaded/skipped counts, and serialized patch JSON. - `insert_post_commit_patch_intersection()`: domain-specific insert helper using parameterized SQL. -- `AgentTraceInsert<'a>`: insert payload for built Agent Trace rows with `commit_id`, `commit_time_ms`, and serialized `trace_json`. +- `AgentTraceInsert<'a>`: insert payload for built Agent Trace rows with `commit_id`, `commit_time_ms`, serialized `trace_json`, and `agent_trace_id`. - `insert_agent_trace()`: domain-specific insert helper for `agent_traces` using parameterized SQL. - `lifecycle.rs`: service lifecycle provider for setup/doctor integration. @@ -38,8 +38,9 @@ The Agent Trace DB path is resolved from the shared default-path catalog: - `003_add_diff_traces_time_ms_id_index.sql` - `004_create_agent_traces.sql` - `005_add_diff_traces_model_id.sql` +- `006_add_agent_traces_agent_trace_id.sql` -`005_add_diff_traces_model_id.sql` adds nullable `diff_traces.model_id`. `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. +`005_add_diff_traces_model_id.sql` adds nullable `diff_traces.model_id`. `006_add_agent_traces_agent_trace_id.sql` adds nullable `agent_trace_id` to `agent_traces`. `AgentTraceDbSpec::migrations()` registers both after the `agent_traces` table migration, so setup/doctor initialization applies later column migrations through the shared migration runner. The shared `TursoDb` runner records applied IDs in the database-local `__sce_migrations` table. Existing Agent Trace DB files without metadata are brought forward by re-applying the idempotent migration set and recording each ID, so rerunning `sce setup` / `AgentTraceDb::new()` applies later Agent Trace migrations to an already-created `~/.local/state/sce/agent-trace.db`. @@ -70,6 +71,7 @@ The `agent_traces` migration creates: - `commit_id TEXT NOT NULL` - `commit_time_ms INTEGER NOT NULL` - `trace_json TEXT NOT NULL` +- `agent_trace_id TEXT` (added by migration 006; nullable so existing rows remain valid) - `created_at TEXT NOT NULL DEFAULT (...)` ## Lifecycle integration From 51b4152cf0634a863d189c1efe817ffb56cfb36d Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Tue, 19 May 2026 16:19:16 +0200 Subject: [PATCH 11/11] biome: add vscode settings.json with default formatter Co-authored-by: SCE --- .vscode/settings.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..8e9e46b6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "editor.formatOnSave": true, + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + } +}