Skip to content

Commit e11516f

Browse files
authored
chore(trace): update hooks configuration by running chainloop trace init (#3667)
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
1 parent 6767675 commit e11516f

3 files changed

Lines changed: 44 additions & 7 deletions

File tree

‎.claude/settings.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"type": "command"
1818
}
1919
],
20-
"matcher": "Edit|Write|MultiEdit|Bash"
20+
"matcher": "Edit|Write|MultiEdit|Bash|Skill"
2121
}
2222
],
2323
"PostToolUseFailure": [

‎.cursor/hooks.json‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@
66
"timeout": 30
77
}
88
],
9+
"postToolUse": [
10+
{
11+
"command": "chainloop trace hook cursor post-tool-use",
12+
"matcher": "Shell",
13+
"timeout": 30
14+
}
15+
],
16+
"preToolUse": [
17+
{
18+
"command": "chainloop trace hook cursor pre-tool-use",
19+
"matcher": "Shell",
20+
"timeout": 30
21+
}
22+
],
923
"sessionEnd": [
1024
{
1125
"command": "chainloop trace hook cursor session-end",

‎.opencode/plugins/chainloop-trace.ts‎

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@ import { spawn } from "node:child_process"
22

33
const fileWritingTools = ["edit","write","apply_patch","patch"]
44
const commandTools = ["bash","shell"]
5+
const skillTool = "skill"
6+
7+
// skillDirFrom returns the folder of the skill that a skill tool call loaded:
8+
// the dir field of the result metadata, or else the base directory line of
9+
// the result text. Each OpenCode major gives the result in its own shape, so
10+
// each candidate is tried in turn.
11+
function skillDirFrom(...results: any[]): string {
12+
for (const r of results) {
13+
const dir = r?.metadata?.dir
14+
if (typeof dir === "string" && dir) return dir
15+
const text = typeof r === "string" ? r : typeof r?.output === "string" ? r.output : ""
16+
const m = /^Base directory for this skill:\s*(.+)$/m.exec(text)
17+
if (m) return m[1].trim()
18+
}
19+
return ""
20+
}
521

622
function filePathsFromArgs(args: any): string[] {
723
if (args?.filePath) return [args.filePath]
@@ -117,12 +133,19 @@ async function sessionEvent(directory: string, type: string, sessionID: string,
117133
}
118134
}
119135

120-
async function toolEvent(directory: string, hook: string, hookEventName: string, sessionID: string, tool: string, callID: string, args: any) {
136+
async function toolEvent(directory: string, hook: string, hookEventName: string, sessionID: string, tool: string, callID: string, args: any, skillDir = "") {
121137
const payload = { session_id: sessionID, hook_event_name: hookEventName, tool_name: tool }
138+
if (tool === skillTool) {
139+
// The skill is loaded after the call, and the result names its folder.
140+
// The hook copies the folder for the evidence.
141+
if (skillDir) await fire(directory, hook, { ...payload, skill_dir: skillDir })
142+
return
143+
}
122144
if (commandTools.includes(tool)) {
123145
// The call ID pairs this hook with the other hook of the same call, so
124-
// overlapping commands keep their own snapshots.
125-
await fire(directory, hook, { ...payload, tool_use_id: callID })
146+
// overlapping commands keep their own snapshots. The command line names
147+
// the checkouts that the command works in.
148+
await fire(directory, hook, { ...payload, tool_use_id: callID, command: args?.command ?? "" })
126149
return
127150
}
128151
if (!fileWritingTools.includes(tool)) return
@@ -172,8 +195,8 @@ async function server({ directory, client }: any) {
172195
"tool.execute.before": async (input: any, output: any) => {
173196
await toolEvent(directory, "pre-tool-use", "tool.execute.before", input.sessionID, input.tool, input.callID, output.args)
174197
},
175-
"tool.execute.after": async (input: any) => {
176-
await toolEvent(directory, "post-tool-use", "tool.execute.after", input.sessionID, input.tool, input.callID, input.args)
198+
"tool.execute.after": async (input: any, output: any) => {
199+
await toolEvent(directory, "post-tool-use", "tool.execute.after", input.sessionID, input.tool, input.callID, input.args, input.tool === skillTool ? skillDirFrom(output) : "")
177200
},
178201
}
179202
}
@@ -187,7 +210,7 @@ async function setup(ctx: any) {
187210
await toolEvent(directory, "pre-tool-use", "tool.execute.before", event.sessionID, event.tool, event.id, event.input)
188211
})
189212
await ctx.tool.hook("execute.after", async (event: any) => {
190-
await toolEvent(directory, "post-tool-use", "tool.execute.after", event.sessionID, event.tool, event.id, event.input)
213+
await toolEvent(directory, "post-tool-use", "tool.execute.after", event.sessionID, event.tool, event.id, event.input, event.tool === skillTool ? skillDirFrom(event.output, event.result, event) : "")
191214
})
192215

193216
// resume: false stores the message without asking the model for an answer.

0 commit comments

Comments
 (0)