diff --git a/plugin/scripts/guard-scan.mjs b/plugin/scripts/guard-scan.mjs index 09815c3..0b6996b 100644 --- a/plugin/scripts/guard-scan.mjs +++ b/plugin/scripts/guard-scan.mjs @@ -6090,7 +6090,8 @@ var CURSOR_NOT_ACTIONS = /* @__PURE__ */ new Set([ "Task", "ReadLints", "SemanticSearch", - "WebFetch" + "WebFetch", + "UpdateCurrentStep" ]); var FILE_TOOLS2 = /* @__PURE__ */ new Map([ ["Read", "Read"], diff --git a/src/__tests__/built-artifact.test.ts b/src/__tests__/built-artifact.test.ts index d5e60e1..cdfbaf0 100644 --- a/src/__tests__/built-artifact.test.ts +++ b/src/__tests__/built-artifact.test.ts @@ -1095,7 +1095,8 @@ describe("scan, against the built binary", () => { expect(payload.result.agent).toBe("cursor"); expect(payload.result.sessions).toBe(1); expect(payload.result.projects).toBe(1); - // Seven actions in the session file and two in its sub-agent's; the to-do list is not one. + // Seven actions in the session file and two in its sub-agent's; the to-do list and the two + // progress steps are not actions. expect(payload.result.toolCalls).toBe(9); expect(payload.result.tokens).toBeNull(); expect(payload.result.skipped).toEqual({ @@ -1104,7 +1105,7 @@ describe("scan, against the built binary", () => { unknownRecords: 0, turnsEndedWithError: 1, unreadableFiles: 0, - notActions: 1, + notActions: 3, unmappedTools: [], }); expect(r.stdout).not.toContain("/home/user"); diff --git a/src/__tests__/cursor-scan.test.ts b/src/__tests__/cursor-scan.test.ts index 1caafe6..f94e2c9 100644 --- a/src/__tests__/cursor-scan.test.ts +++ b/src/__tests__/cursor-scan.test.ts @@ -298,13 +298,14 @@ describe("each tool in a session file is evaluated as the table says", () => { "ReadLints", "SemanticSearch", "WebFetch", + "UpdateCurrentStep", ]; it.each(NOT_ACTIONS)("%s is not an action", (name) => { expect(map(name, { anything: true })).toEqual({ kind: "not-action" }); }); - it("the not-action list is exactly those ten", () => { + it("the not-action list is exactly those eleven", () => { expect([...CURSOR_NOT_ACTIONS].sort()).toEqual([...NOT_ACTIONS].sort()); }); @@ -764,7 +765,8 @@ describe("the committed session fixture, read from disk", () => { NO_ALLOWLIST, ); // Session file: three shell commands, a read, a search, an edit and an MCP call. Sub-agent: - // a file search and a shell command. The to-do list is not an action. + // a file search and a shell command. The to-do list and the two progress steps are not + // actions. expect(result.toolCalls).toBe(9); // Three deletes, the `.env` read and the search for `.env` files. expect(result.riskyActions).toBe(5); @@ -774,7 +776,7 @@ describe("the committed session fixture, read from disk", () => { ]); expect(result.recurring.map((r) => [r.text, r.count])).toEqual([["rm -rf ", 3]]); expect(result.skipped).toMatchObject({ - notActions: 1, + notActions: 3, turnsEndedWithError: 1, unmappedTools: [], }); diff --git a/src/__tests__/fixtures/cursor/projects/home-user-project/agent-transcripts/00000000-0000-4000-8000-000000000021/00000000-0000-4000-8000-000000000021.jsonl b/src/__tests__/fixtures/cursor/projects/home-user-project/agent-transcripts/00000000-0000-4000-8000-000000000021/00000000-0000-4000-8000-000000000021.jsonl index e53b486..8bc167b 100644 --- a/src/__tests__/fixtures/cursor/projects/home-user-project/agent-transcripts/00000000-0000-4000-8000-000000000021/00000000-0000-4000-8000-000000000021.jsonl +++ b/src/__tests__/fixtures/cursor/projects/home-user-project/agent-transcripts/00000000-0000-4000-8000-000000000021/00000000-0000-4000-8000-000000000021.jsonl @@ -1,4 +1,5 @@ {"role":"user","message":{"content":[{"type":"text","text":"Tidy the build output and check the settings."}]}} +{"role":"assistant","message":{"content":[{"type":"tool_use","name":"UpdateCurrentStep","input":{"current_step":"Removing build output","final_summary":""}}]}} {"role":"assistant","message":{"content":[{"type":"text","text":"Removing the build folders."},{"type":"tool_use","name":"Shell","input":{"command":"rm -rf /home/user/project/build","description":"Remove build output"}}]}} {"role":"assistant","message":{"content":[{"type":"tool_use","name":"Shell","input":{"command":"rm -rf /home/user/project/dist","description":"Remove dist output"}}]}} {"role":"assistant","message":{"content":[{"type":"tool_use","name":"Read","input":{"path":"/home/user/project/.env"}}]}} @@ -6,6 +7,7 @@ {"role":"assistant","message":{"content":[{"type":"tool_use","name":"StrReplace","input":{"path":"/home/user/project/src/app.ts","old_string":"debug: true","new_string":"debug: false"}}]}} {"role":"assistant","message":{"content":[{"type":"tool_use","name":"TodoWrite","input":{"merge":false,"todos":[]}}]}} {"role":"assistant","message":{"content":[{"type":"tool_use","name":"CallMcpTool","input":{"server":"notes","toolName":"search","arguments":{"query":"release"}}}]}} +{"role":"assistant","message":{"content":[{"type":"tool_use","name":"UpdateCurrentStep","input":{"current_step":"Done","completed_subtitle":"Build output removed","final_summary":"Removed the build folders and turned off debug."}}]}} {"type":"turn_ended","status":"success"} {"role":"user","message":{"content":[{"type":"text","text":"List the files."}]}} {"role":"assistant","message":{"content":[{"type":"tool_use","name":"Shell","input":{"command":"ls -la","description":"List files"}}]}} diff --git a/src/core/cursor-transcript/scan.ts b/src/core/cursor-transcript/scan.ts index 14e19ae..b1a9335 100644 --- a/src/core/cursor-transcript/scan.ts +++ b/src/core/cursor-transcript/scan.ts @@ -40,9 +40,10 @@ * same verdict in both. * * `GetDynamicTools`, `TodoWrite`, `CreatePlan`, `AskQuestion`, `SwitchMode`, `Await`, - * `Task`, `ReadLints`, `SemanticSearch` and `WebFetch` are not actions a guardrail checks, - * and are counted apart. A `Task` starts a sub-agent, whose own tool calls are read from its - * file. Any other tool name is counted as not recognized. + * `Task`, `ReadLints`, `SemanticSearch`, `WebFetch` and `UpdateCurrentStep` (a progress step + * shown in Cursor's UI) are not actions a guardrail checks, and are counted apart. A `Task` + * starts a sub-agent, whose own tool calls are read from its file. Any other tool name is + * counted as not recognized. */ import { join } from "node:path"; @@ -86,6 +87,7 @@ export const CURSOR_NOT_ACTIONS: ReadonlySet = new Set([ "ReadLints", "SemanticSearch", "WebFetch", + "UpdateCurrentStep", ]); /** File tools, by Cursor's name, and the name each is evaluated as. */