@cortexkit/aft-opencode 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/bridge.d.ts +8 -0
  2. package/dist/bridge.d.ts.map +1 -1
  3. package/dist/bridge.js +45 -2
  4. package/dist/bridge.js.map +1 -1
  5. package/dist/config.d.ts +1 -0
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +11 -0
  8. package/dist/config.js.map +1 -1
  9. package/dist/downloader.d.ts.map +1 -1
  10. package/dist/downloader.js +51 -15
  11. package/dist/downloader.js.map +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +56 -16
  14. package/dist/index.js.map +1 -1
  15. package/dist/normalize-schemas.d.ts +16 -0
  16. package/dist/normalize-schemas.d.ts.map +1 -0
  17. package/dist/normalize-schemas.js +45 -0
  18. package/dist/normalize-schemas.js.map +1 -0
  19. package/dist/patch-parser.d.ts.map +1 -1
  20. package/dist/patch-parser.js +10 -0
  21. package/dist/patch-parser.js.map +1 -1
  22. package/dist/platform.d.ts +21 -0
  23. package/dist/platform.d.ts.map +1 -0
  24. package/dist/platform.js +31 -0
  25. package/dist/platform.js.map +1 -0
  26. package/dist/pool.d.ts.map +1 -1
  27. package/dist/pool.js +14 -5
  28. package/dist/pool.js.map +1 -1
  29. package/dist/resolver.d.ts.map +1 -1
  30. package/dist/resolver.js +6 -9
  31. package/dist/resolver.js.map +1 -1
  32. package/dist/tools/ast.d.ts.map +1 -1
  33. package/dist/tools/ast.js +52 -59
  34. package/dist/tools/ast.js.map +1 -1
  35. package/dist/tools/hoisted.d.ts +1 -1
  36. package/dist/tools/hoisted.d.ts.map +1 -1
  37. package/dist/tools/hoisted.js +333 -230
  38. package/dist/tools/hoisted.js.map +1 -1
  39. package/dist/tools/imports.d.ts.map +1 -1
  40. package/dist/tools/imports.js +40 -30
  41. package/dist/tools/imports.js.map +1 -1
  42. package/dist/tools/lsp.d.ts +1 -2
  43. package/dist/tools/lsp.d.ts.map +1 -1
  44. package/dist/tools/lsp.js +31 -13
  45. package/dist/tools/lsp.js.map +1 -1
  46. package/dist/tools/navigation.d.ts.map +1 -1
  47. package/dist/tools/navigation.js +23 -14
  48. package/dist/tools/navigation.js.map +1 -1
  49. package/dist/tools/permissions.d.ts +8 -0
  50. package/dist/tools/permissions.d.ts.map +1 -0
  51. package/dist/tools/permissions.js +50 -0
  52. package/dist/tools/permissions.js.map +1 -0
  53. package/dist/tools/reading.d.ts +1 -2
  54. package/dist/tools/reading.d.ts.map +1 -1
  55. package/dist/tools/reading.js +191 -12
  56. package/dist/tools/reading.js.map +1 -1
  57. package/dist/tools/refactoring.d.ts.map +1 -1
  58. package/dist/tools/refactoring.js +72 -34
  59. package/dist/tools/refactoring.js.map +1 -1
  60. package/dist/tools/safety.d.ts.map +1 -1
  61. package/dist/tools/safety.js +34 -12
  62. package/dist/tools/safety.js.map +1 -1
  63. package/dist/tools/structure.d.ts.map +1 -1
  64. package/dist/tools/structure.js +66 -31
  65. package/dist/tools/structure.js.map +1 -1
  66. package/package.json +7 -7
  67. package/dist/tools/editing.d.ts +0 -8
  68. package/dist/tools/editing.d.ts.map +0 -1
  69. package/dist/tools/editing.js +0 -8
  70. package/dist/tools/editing.js.map +0 -1
@@ -1,40 +1,219 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { extname, join, resolve } from "node:path";
1
3
  import { tool } from "@opencode-ai/plugin";
4
+ /** File extensions that aft_outline supports via tree-sitter or markdown parser */
5
+ const OUTLINE_EXTENSIONS = new Set([
6
+ ".ts",
7
+ ".tsx",
8
+ ".js",
9
+ ".jsx",
10
+ ".mjs",
11
+ ".cjs",
12
+ ".rs",
13
+ ".go",
14
+ ".py",
15
+ ".rb",
16
+ ".c",
17
+ ".cpp",
18
+ ".h",
19
+ ".hpp",
20
+ ".cs",
21
+ ".java",
22
+ ".kt",
23
+ ".scala",
24
+ ".swift",
25
+ ".lua",
26
+ ".ex",
27
+ ".exs",
28
+ ".hs",
29
+ ".sol",
30
+ ".nix",
31
+ ".md",
32
+ ".mdx",
33
+ ".css",
34
+ ".html",
35
+ ".json",
36
+ ".yaml",
37
+ ".yml",
38
+ ".sh",
39
+ ".bash",
40
+ ]);
2
41
  const z = tool.schema;
3
42
  /**
4
- * Tool definitions for code reading commands: outline only.
5
- * The zoom/read functionality has been merged into the hoisted `read` tool.
43
+ * Tool definitions for code reading commands: outline + zoom.
6
44
  */
7
45
  export function readingTools(ctx) {
8
46
  return {
9
47
  aft_outline: {
10
- description: "Get a structural outline of a source file — lists all top-level symbols with their kind, name, line range, and visibility. Use this to understand file structure before editing. " +
11
- "Supports single file (via 'file') or multiple files in one call (via 'files' array).\n" +
48
+ description: "Get a structural outline of a source file, multiple files, or an entire directory — lists all top-level symbols with their kind, name, line range, and visibility. Use this to understand file structure before editing.\n" +
12
49
  "Each entry includes 'name', 'kind' (function/class/struct/heading/etc), 'range', 'signature', and 'members' (nested children like methods in classes or sub-headings in markdown).\n" +
13
50
  "For Markdown files (.md, .mdx): returns heading hierarchy — h1/h2/h3 as nested symbols with section ranges covering all content until the next same-level heading.\n\n" +
14
- "Parameters:\n" +
15
- "- file (string, optional): Path to a single file to outline (relative to project root or absolute)\n" +
16
- "- files (string[], optional): Array of file paths to outline in one call — returns per-file results\n\n" +
17
- "Provide either 'file' or 'files', not both. Use 'files' to batch multiple outlines in one tool call.",
51
+ "Provide 'filePath', 'files', or 'directory'. Priority: directory > files > filePath. If multiple provided, highest-priority wins.\n" +
52
+ "Supported languages: TypeScript, JavaScript, TSX, Python, Rust, Go, Ruby, C, C++, C#, Java, Kotlin, Scala, Swift, Lua, Elixir, Haskell, Solidity, Nix, Markdown, CSS, HTML, JSON, YAML, Bash.\n" +
53
+ "Directory mode skips commonly ignored directories (node_modules, .git, dist, build, target, __pycache__, venv, vendor, coverage, etc.) and dot-prefixed directories.\n\n" +
54
+ "Returns: Single file { entries: [{ name, kind, range, signature?, exported, members }] }. Multi-file/directory { results: [{ file, ok, entries? }] }.",
18
55
  args: {
19
- file: z
56
+ filePath: z
20
57
  .string()
21
58
  .optional()
22
- .describe("Path to a single source file to outline (relative to project root or absolute)"),
59
+ .describe("Path to a single file to outline (ignored if 'files' or 'directory' is also provided)"),
23
60
  files: z
24
61
  .array(z.string())
25
62
  .optional()
26
- .describe("Array of file paths to outline in one call returns per-file results"),
63
+ .describe("Array of file paths to outline in one call (ignored if 'directory' is also provided)"),
64
+ // The 200-file cap is intentionally only in .describe() — not in the main description —
65
+ // because it's a parameter-level detail. The cap is silent (no warning on truncation).
66
+ directory: z
67
+ .string()
68
+ .optional()
69
+ .describe("Path to a directory — outlines all source files under it recursively (capped at 200 files, takes priority over 'filePath' and 'files')"),
27
70
  },
28
71
  execute: async (args, context) => {
29
72
  const bridge = ctx.pool.getBridge(context.directory);
73
+ const filesArg = Array.isArray(args.files) ? args.files : undefined;
74
+ if (!args.filePath && !filesArg?.length && !args.directory) {
75
+ throw new Error("Provide exactly one of 'filePath', 'files', or 'directory'");
76
+ }
77
+ // Directory mode: discover source files recursively and batch outline
78
+ if (typeof args.directory === "string") {
79
+ const dirPath = resolve(context.directory, args.directory);
80
+ const files = await discoverSourceFiles(dirPath);
81
+ if (files.length === 0) {
82
+ return JSON.stringify({
83
+ success: false,
84
+ message: `No source files found under ${args.directory}`,
85
+ });
86
+ }
87
+ const response = await bridge.send("outline", { files });
88
+ return JSON.stringify(response);
89
+ }
30
90
  if (Array.isArray(args.files) && args.files.length > 0) {
31
91
  const response = await bridge.send("outline", { files: args.files });
32
92
  return JSON.stringify(response);
33
93
  }
34
- const response = await bridge.send("outline", { file: args.file });
94
+ const response = await bridge.send("outline", { file: args.filePath });
35
95
  return JSON.stringify(response);
36
96
  },
37
97
  },
98
+ aft_zoom: {
99
+ description: `Inspect code symbols with call-graph annotations. Returns the full source of named symbols with what they call and what calls them.
100
+
101
+ Use this when you need to understand a specific function, class, or type in detail — not for reading entire files (use read for that).
102
+
103
+ **Modes:**
104
+
105
+ 1. **Inspect symbol** — pass filePath + symbol
106
+ Returns full source + call graph annotations.
107
+ Example: { "filePath": "src/app.ts", "symbol": "handleRequest" }
108
+
109
+ 2. **Inspect multiple symbols** — pass filePath + symbols array
110
+ Returns multiple symbols in one call.
111
+ Example: { "filePath": "src/app.ts", "symbols": ["Config", "createApp"] }
112
+
113
+ 3. **Read line range with context** — pass filePath + startLine + endLine
114
+ Returns lines with context_before and context_after.
115
+ Example: { "filePath": "src/app.ts", "startLine": 50, "endLine": 100 }
116
+
117
+ For Markdown files, use heading text as symbol name (e.g., symbol: "Architecture").
118
+
119
+ Mode priority: symbols array > single symbol > line range.
120
+
121
+ Returns: Symbol mode { name, kind, range, content, context_before, context_after, annotations: { calls_out, called_by } }. Multi-symbol mode returns an array of these. Line-range mode returns { content, context_before, context_after, start_line, end_line }.`,
122
+ args: {
123
+ filePath: z.string().describe("Path to file (absolute or relative to project root)"),
124
+ symbol: z.string().optional().describe("Name of a single symbol to inspect"),
125
+ symbols: z
126
+ .array(z.string())
127
+ .optional()
128
+ .describe("Array of symbol names to inspect in one call"),
129
+ startLine: z.number().optional().describe("1-based start line for line-range mode"),
130
+ endLine: z
131
+ .number()
132
+ .optional()
133
+ .describe("1-based end line for line-range mode (inclusive, required with startLine)"),
134
+ contextLines: z
135
+ .number()
136
+ .optional()
137
+ .describe("Lines of context before/after the requested range or symbol (default: 3)"),
138
+ },
139
+ execute: async (args, context) => {
140
+ const bridge = ctx.pool.getBridge(context.directory);
141
+ const file = args.filePath;
142
+ // Multi-symbol mode: make separate zoom calls in parallel and combine results
143
+ if (Array.isArray(args.symbols) && args.symbols.length > 0) {
144
+ const results = await Promise.all(args.symbols.map((sym) => {
145
+ const params = { file, symbol: sym };
146
+ if (args.contextLines !== undefined)
147
+ params.context_lines = args.contextLines;
148
+ return bridge.send("zoom", params);
149
+ }));
150
+ return JSON.stringify(results);
151
+ }
152
+ // Single symbol or line-range mode
153
+ const params = { file };
154
+ if (typeof args.symbol === "string")
155
+ params.symbol = args.symbol;
156
+ if (args.startLine !== undefined)
157
+ params.start_line = args.startLine;
158
+ if (args.endLine !== undefined)
159
+ params.end_line = args.endLine;
160
+ if (args.contextLines !== undefined)
161
+ params.context_lines = args.contextLines;
162
+ const data = await bridge.send("zoom", params);
163
+ return JSON.stringify(data);
164
+ },
165
+ },
38
166
  };
39
167
  }
168
+ /** Recursively discover source files under a directory, skipping common noise directories */
169
+ const SKIP_DIRS = new Set([
170
+ "node_modules",
171
+ ".git",
172
+ "dist",
173
+ "build",
174
+ "out",
175
+ ".next",
176
+ ".nuxt",
177
+ "target",
178
+ "__pycache__",
179
+ ".venv",
180
+ "venv",
181
+ "vendor",
182
+ ".turbo",
183
+ "coverage",
184
+ ".nyc_output",
185
+ ".cache",
186
+ ]);
187
+ async function discoverSourceFiles(dir, maxFiles = 200) {
188
+ const files = [];
189
+ async function walk(current) {
190
+ if (files.length >= maxFiles)
191
+ return;
192
+ let entries;
193
+ try {
194
+ entries = await readdir(current, { withFileTypes: true });
195
+ }
196
+ catch {
197
+ return; // permission denied, not a directory, etc.
198
+ }
199
+ for (const entry of entries) {
200
+ if (files.length >= maxFiles)
201
+ return;
202
+ if (entry.isDirectory()) {
203
+ if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
204
+ await walk(join(current, entry.name));
205
+ }
206
+ }
207
+ else if (entry.isFile()) {
208
+ const ext = extname(entry.name).toLowerCase();
209
+ if (OUTLINE_EXTENSIONS.has(ext)) {
210
+ files.push(join(current, entry.name));
211
+ }
212
+ }
213
+ }
214
+ }
215
+ await walk(dir);
216
+ files.sort();
217
+ return files;
218
+ }
40
219
  //# sourceMappingURL=reading.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reading.js","sourceRoot":"","sources":["../../src/tools/reading.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAG3C,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;AAEtB;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,GAAkB;IAC7C,OAAO;QACL,WAAW,EAAE;YACX,WAAW,EACT,mLAAmL;gBACnL,wFAAwF;gBACxF,sLAAsL;gBACtL,wKAAwK;gBACxK,eAAe;gBACf,sGAAsG;gBACtG,yGAAyG;gBACzG,sGAAsG;YACxG,IAAI,EAAE;gBACJ,IAAI,EAAE,CAAC;qBACJ,MAAM,EAAE;qBACR,QAAQ,EAAE;qBACV,QAAQ,CACP,gFAAgF,CACjF;gBACH,KAAK,EAAE,CAAC;qBACL,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;qBACjB,QAAQ,EAAE;qBACV,QAAQ,CAAC,uEAAuE,CAAC;aACrF;YACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAmB,EAAE;gBAChD,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gBACrD,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACvD,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;oBACrE,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;gBAClC,CAAC;gBACD,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;gBACnE,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YAClC,CAAC;SACF;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"reading.js","sourceRoot":"","sources":["../../src/tools/reading.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"refactoring.d.ts","sourceRoot":"","sources":["../../src/tools/refactoring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAoFnF"}
1
+ {"version":3,"file":"refactoring.d.ts","sourceRoot":"","sources":["../../src/tools/refactoring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAYjD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA4HnF"}
@@ -1,5 +1,6 @@
1
1
  import { tool } from "@opencode-ai/plugin";
2
2
  import { queryLspHints } from "../lsp.js";
3
+ import { askEditPermission, permissionDeniedResponse, resolveAbsolutePath, resolveRelativePattern, resolveRelativePatterns, workspacePattern, } from "./permissions.js";
3
4
  const z = tool.schema;
4
5
  /**
5
6
  * Tool definitions for refactoring commands: move_symbol, extract_function, inline_symbol.
@@ -9,59 +10,96 @@ export function refactoringTools(ctx) {
9
10
  aft_refactor: {
10
11
  description: "Workspace-wide refactoring operations that update imports and references across files.\n\n" +
11
12
  "Ops:\n" +
12
- "- 'move': Move a symbol to another file, updating all imports workspace-wide. Requires 'symbol', 'destination'. Creates a checkpoint before mutating.\n" +
13
- "- 'extract': Extract a line range into a new function with auto-detected parameters. Requires 'name', 'start_line', 'end_line' (all 1-based). Supports TS/JS/TSX and Python.\n" +
14
- "- 'inline': Replace a function call with the function's body, substituting args for params. Requires 'symbol', 'call_site_line' (1-based). Validates single-return constraint.\n\n" +
15
- "Parameters:\n" +
16
- "- op (enum, required): 'move' | 'extract' | 'inline'\n" +
17
- "- file (string, required): Path to the source file\n" +
18
- "- symbol (string, optional): Symbol name required for 'move' (symbol to move) and 'inline' (function to inline)\n" +
19
- "- destination (string, optional): Target file path for 'move' op — created if it doesn't exist\n" +
20
- "- scope (string, optional): Disambiguation scope for 'move' when multiple symbols share the same name\n" +
21
- "- name (string, optional): New function name for 'extract' op\n" +
22
- "- start_line (number, optional): 1-based start line of the range to extract ('extract' op)\n" +
23
- "- end_line (number, optional): 1-based end line of the range to extract, exclusive ('extract' op)\n" +
24
- "- call_site_line (number, optional): 1-based line where the call expression is located ('inline' op)\n" +
25
- "- dry_run (boolean, optional): Preview changes as diff without modifying files\n\n" +
26
- "All ops need 'file'. Use dry_run to preview before applying.",
13
+ "- 'move': Move a top-level symbol to another file, updating all imports workspace-wide. Requires 'symbol', 'destination'. Creates a checkpoint before mutating. Only works on top-level exports (not nested functions or class methods).\n" +
14
+ " Note: This moves code symbols between files. To rename/move an entire file, use aft_move instead.\n" +
15
+ "- 'extract': Extract a line range into a new function with auto-detected parameters. Requires 'name', 'startLine', 'endLine' (1-based, both inclusive). Supports TS/JS/TSX and Python.\n" +
16
+ "- 'inline': Replace a function call with the function's body, substituting args for params. Requires 'symbol', 'callSiteLine' (1-based). Validates single-return constraint.\n\n" +
17
+ "Each op requires specific parameters see parameter descriptions for requirements.\n\n" +
18
+ "All ops need 'filePath'. Use dryRun to preview before applying.\n\n" +
19
+ "Returns: move dry-run { ok, dry_run, diffs }; move apply { ok, files_modified, consumers_updated, checkpoint_name, results }. extract returns { file, name, parameters, return_type, syntax_valid, formatted, ... }. inline returns { file, symbol, call_context, substitutions, conflicts, syntax_valid, formatted, ... }.",
20
+ // Parameters are Zod-optional because different ops need different subsets.
21
+ // Runtime guards below validate per-op requirements and give clear errors.
27
22
  args: {
28
23
  op: z.enum(["move", "extract", "inline"]).describe("Refactoring operation"),
29
- file: z.string().describe("Path to the source file"),
24
+ filePath: z
25
+ .string()
26
+ .describe("Path to the source file (absolute or relative to project root)"),
30
27
  symbol: z
31
28
  .string()
32
29
  .optional()
33
- .describe("Symbol name (move: symbol to move, inline: function to inline)"),
30
+ .describe("Symbol name required for 'move' and 'inline' ops"),
34
31
  // move
35
- destination: z
36
- .string()
37
- .optional()
38
- .describe("Destination file path (move op will be created if needed)"),
32
+ destination: z.string().optional().describe("Target file path — required for 'move' op"),
33
+ // scope disambiguates overloaded top-level names, NOT nested symbols.
34
+ // "Only works on top-level exports" in the description is correct — scope selects
35
+ // among multiple top-level symbols that share a name, not class methods.
39
36
  scope: z
40
37
  .string()
41
38
  .optional()
42
- .describe("Disambiguation scope when multiple symbols share the same name (move op)"),
39
+ .describe("Disambiguation scope for 'move' op — when multiple top-level symbols share the same name, specify the containing scope to disambiguate (e.g. 'MyClass'). Does NOT enable access to nested symbols or class methods."),
43
40
  // extract
44
- name: z.string().optional().describe("Name for the new extracted function (extract op)"),
45
- start_line: z.number().describe("First line of range to extract, 1-based (extract op)"),
46
- end_line: z.number().describe("Last line of range, exclusive, 1-based (extract op)"),
41
+ name: z.string().optional().describe("New function name required for 'extract' op"),
42
+ startLine: z.number().optional().describe("1-based start line required for 'extract' op"),
43
+ // endLine is inclusive from the agent's perspective; the execute function adds +1
44
+ // because the Rust backend expects exclusive end. This is intentional — do not document.
45
+ endLine: z
46
+ .number()
47
+ .optional()
48
+ .describe("1-based end line (inclusive) — required for 'extract' op"),
47
49
  // inline
48
- call_site_line: z
50
+ callSiteLine: z
49
51
  .number()
50
- .describe("Line where the call expression is located, 1-based (inline op)"),
52
+ .optional()
53
+ .describe("1-based call site line — required for 'inline' op"),
51
54
  // common
52
- dry_run: z.boolean().optional().describe("Preview as diff without modifying files"),
55
+ dryRun: z
56
+ .boolean()
57
+ .optional()
58
+ .describe("Preview changes as diff without modifying files (default: false)"),
53
59
  },
54
60
  execute: async (args, context) => {
55
61
  const bridge = ctx.pool.getBridge(context.directory);
56
62
  const op = args.op;
63
+ const isDryRun = args.dryRun === true;
64
+ if ((op === "move" || op === "inline") && typeof args.symbol !== "string") {
65
+ throw new Error(`'symbol' is required for '${op}' op`);
66
+ }
67
+ if (op === "move" && typeof args.destination !== "string") {
68
+ throw new Error("'destination' is required for 'move' op");
69
+ }
70
+ if (op === "extract") {
71
+ if (typeof args.name !== "string")
72
+ throw new Error("'name' is required for 'extract' op");
73
+ if (args.startLine === undefined)
74
+ throw new Error("'startLine' is required for 'extract' op");
75
+ if (args.endLine === undefined)
76
+ throw new Error("'endLine' is required for 'extract' op");
77
+ }
78
+ if (op === "inline" && args.callSiteLine === undefined) {
79
+ throw new Error("'callSiteLine' is required for 'inline' op");
80
+ }
81
+ if (!isDryRun) {
82
+ const filePath = resolveAbsolutePath(context, args.filePath);
83
+ const patterns = op === "move"
84
+ ? resolveRelativePatterns(context, [
85
+ workspacePattern(context),
86
+ args.filePath,
87
+ ...(typeof args.destination === "string" ? [args.destination] : []),
88
+ ])
89
+ : [resolveRelativePattern(context, args.filePath)];
90
+ const metadata = patterns.length === 1 ? { filepath: filePath } : {};
91
+ const permissionError = await askEditPermission(context, patterns, metadata);
92
+ if (permissionError)
93
+ return permissionDeniedResponse(permissionError);
94
+ }
57
95
  const commandMap = {
58
96
  move: "move_symbol",
59
97
  extract: "extract_function",
60
98
  inline: "inline_symbol",
61
99
  };
62
- const params = { file: args.file };
63
- if (args.dry_run !== undefined)
64
- params.dry_run = args.dry_run;
100
+ const params = { file: args.filePath };
101
+ if (args.dryRun !== undefined)
102
+ params.dry_run = args.dryRun;
65
103
  switch (op) {
66
104
  case "move":
67
105
  params.symbol = args.symbol;
@@ -71,12 +109,12 @@ export function refactoringTools(ctx) {
71
109
  break;
72
110
  case "extract":
73
111
  params.name = args.name;
74
- params.start_line = Number(args.start_line);
75
- params.end_line = Number(args.end_line);
112
+ params.start_line = Number(args.startLine);
113
+ params.end_line = Number(args.endLine) + 1; // Agent uses inclusive, Rust expects exclusive
76
114
  break;
77
115
  case "inline":
78
116
  params.symbol = args.symbol;
79
- params.call_site_line = Number(args.call_site_line);
117
+ params.call_site_line = Number(args.callSiteLine);
80
118
  break;
81
119
  }
82
120
  const hints = await queryLspHints(ctx.client, (args.symbol ?? args.name));
@@ -1 +1 @@
1
- {"version":3,"file":"refactoring.js","sourceRoot":"","sources":["../../src/tools/refactoring.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"refactoring.js","sourceRoot":"","sources":["../../src/tools/refactoring.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../src/tools/safety.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAkD9E"}
1
+ {"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../src/tools/safety.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAWjD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA6E9E"}
@@ -1,4 +1,5 @@
1
1
  import { tool } from "@opencode-ai/plugin";
2
+ import { askEditPermission, permissionDeniedResponse, resolveAbsolutePath, resolveRelativePattern, workspacePattern, } from "./permissions.js";
2
3
  const z = tool.schema;
3
4
  /**
4
5
  * Tool definitions for safety & recovery commands: undo, edit_history,
@@ -8,24 +9,26 @@ export function safetyTools(ctx) {
8
9
  return {
9
10
  aft_safety: {
10
11
  description: "File safety and recovery operations.\n\n" +
12
+ "IMPORTANT: All backups are in-memory only — lost if the AFT process restarts. Per-file undo stack is capped at 20 entries (oldest evicted).\n\n" +
11
13
  "Ops:\n" +
12
- "- 'undo': Undo the last edit to a file. Requires 'file'.\n" +
13
- "- 'history': List all edit snapshots for a file. Requires 'file'.\n" +
14
+ "- 'undo': Undo the last edit to a file. Requires 'filePath'. Note: pops from the undo stack (irreversible, no redo). Use 'history' to inspect before undoing.\n" +
15
+ "- 'history': List all edit snapshots for a file. Requires 'filePath'.\n" +
14
16
  "- 'checkpoint': Save a named snapshot of tracked files. Requires 'name'. Optional 'files' to snapshot specific files only.\n" +
15
17
  "- 'restore': Restore files to a previously saved checkpoint. Requires 'name'.\n" +
16
18
  "- 'list': List all available named checkpoints. No extra params needed.\n\n" +
17
- "Parameters:\n" +
18
- "- op (enum, required): 'undo' | 'history' | 'checkpoint' | 'restore' | 'list'\n" +
19
- "- file (string, optional): File path required for 'undo' and 'history' ops\n" +
20
- "- name (string, optional): Checkpoint name required for 'checkpoint' and 'restore' ops\n" +
21
- "- files (string[], optional): Specific files to include in checkpoint (defaults to all tracked files)\n\n" +
22
- "Use checkpoint before risky multi-file changes. Use undo for quick single-file rollback.\n" +
23
- "Note: backups are in-memory (lost on restart). Per-file undo stack is capped at 20 entries (oldest evicted).",
19
+ "Each op requires specific parameters — see parameter descriptions for requirements.\n\n" +
20
+ "Use checkpoint before risky multi-file changes. Use undo for quick single-file rollback.\n\n" +
21
+ "Returns: undo { path, backup_id }. history { file, entries }. checkpoint { ok, name }. restore { ok, name }. list { checkpoints }.",
22
+ // Parameters are Zod-optional because different ops need different subsets.
23
+ // Runtime guards below validate per-op requirements and give clear errors.
24
24
  args: {
25
25
  op: z
26
26
  .enum(["undo", "history", "checkpoint", "restore", "list"])
27
27
  .describe("Safety operation"),
28
- file: z.string().optional().describe("File path (required for undo, history)"),
28
+ filePath: z
29
+ .string()
30
+ .optional()
31
+ .describe("File path (required for undo, history). Absolute or relative to project root"),
29
32
  name: z.string().optional().describe("Checkpoint name (required for checkpoint, restore)"),
30
33
  files: z
31
34
  .array(z.string())
@@ -35,6 +38,25 @@ export function safetyTools(ctx) {
35
38
  execute: async (args, context) => {
36
39
  const bridge = ctx.pool.getBridge(context.directory);
37
40
  const op = args.op;
41
+ if ((op === "undo" || op === "history") && typeof args.filePath !== "string") {
42
+ throw new Error(`'filePath' is required for '${op}' op`);
43
+ }
44
+ if ((op === "checkpoint" || op === "restore") && typeof args.name !== "string") {
45
+ throw new Error(`'name' is required for '${op}' op`);
46
+ }
47
+ if (op === "undo" && typeof args.filePath === "string") {
48
+ const filePath = resolveAbsolutePath(context, args.filePath);
49
+ const permissionError = await askEditPermission(context, [resolveRelativePattern(context, args.filePath)], { filepath: filePath });
50
+ if (permissionError)
51
+ return permissionDeniedResponse(permissionError);
52
+ }
53
+ if (op === "restore") {
54
+ const permissionError = await askEditPermission(context, [workspacePattern(context)], {
55
+ checkpoint: args.name,
56
+ });
57
+ if (permissionError)
58
+ return permissionDeniedResponse(permissionError);
59
+ }
38
60
  const commandMap = {
39
61
  undo: "undo",
40
62
  history: "edit_history",
@@ -43,8 +65,8 @@ export function safetyTools(ctx) {
43
65
  list: "list_checkpoints",
44
66
  };
45
67
  const params = {};
46
- if (args.file !== undefined)
47
- params.file = args.file;
68
+ if (args.filePath !== undefined)
69
+ params.file = args.filePath;
48
70
  if (args.name !== undefined)
49
71
  params.name = args.name;
50
72
  if (args.files !== undefined)
@@ -1 +1 @@
1
- {"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/tools/safety.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAG3C,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;AAEtB;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,GAAkB;IAC5C,OAAO;QACL,UAAU,EAAE;YACV,WAAW,EACT,0CAA0C;gBAC1C,QAAQ;gBACR,4DAA4D;gBAC5D,qEAAqE;gBACrE,8HAA8H;gBAC9H,iFAAiF;gBACjF,6EAA6E;gBAC7E,eAAe;gBACf,iFAAiF;gBACjF,gFAAgF;gBAChF,4FAA4F;gBAC5F,2GAA2G;gBAC3G,4FAA4F;gBAC5F,8GAA8G;YAChH,IAAI,EAAE;gBACJ,EAAE,EAAE,CAAC;qBACF,IAAI,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;qBAC1D,QAAQ,CAAC,kBAAkB,CAAC;gBAC/B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wCAAwC,CAAC;gBAC9E,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oDAAoD,CAAC;gBAC1F,KAAK,EAAE,CAAC;qBACL,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;qBACjB,QAAQ,EAAE;qBACV,QAAQ,CACP,mFAAmF,CACpF;aACJ;YACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAmB,EAAE;gBAChD,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gBACrD,MAAM,EAAE,GAAG,IAAI,CAAC,EAAY,CAAC;gBAC7B,MAAM,UAAU,GAA2B;oBACzC,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,cAAc;oBACvB,UAAU,EAAE,YAAY;oBACxB,OAAO,EAAE,oBAAoB;oBAC7B,IAAI,EAAE,kBAAkB;iBACzB,CAAC;gBACF,MAAM,MAAM,GAA4B,EAAE,CAAC;gBAC3C,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;oBAAE,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;gBACrD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;oBAAE,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;gBACrD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;oBAAE,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;gBACxD,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;gBAC3D,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YAClC,CAAC;SACF;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/tools/safety.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"structure.d.ts","sourceRoot":"","sources":["../../src/tools/structure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAoHjF"}
1
+ {"version":3,"file":"structure.d.ts","sourceRoot":"","sources":["../../src/tools/structure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAUjD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAkKjF"}