@nogataka/smart-edit 0.0.14

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 (186) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +244 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +7 -0
  5. package/dist/devtools/generate_prompt_factory.d.ts +5 -0
  6. package/dist/devtools/generate_prompt_factory.js +114 -0
  7. package/dist/index.d.ts +34 -0
  8. package/dist/index.js +34 -0
  9. package/dist/interprompt/index.d.ts +2 -0
  10. package/dist/interprompt/index.js +1 -0
  11. package/dist/interprompt/jinja_template.d.ts +10 -0
  12. package/dist/interprompt/jinja_template.js +174 -0
  13. package/dist/interprompt/multilang_prompt.d.ts +54 -0
  14. package/dist/interprompt/multilang_prompt.js +302 -0
  15. package/dist/interprompt/prompt_factory.d.ts +16 -0
  16. package/dist/interprompt/prompt_factory.js +189 -0
  17. package/dist/interprompt/util/class_decorators.d.ts +1 -0
  18. package/dist/interprompt/util/class_decorators.js +1 -0
  19. package/dist/interprompt/util/index.d.ts +1 -0
  20. package/dist/interprompt/util/index.js +1 -0
  21. package/dist/serena/agent.d.ts +118 -0
  22. package/dist/serena/agent.js +675 -0
  23. package/dist/serena/agno.d.ts +111 -0
  24. package/dist/serena/agno.js +278 -0
  25. package/dist/serena/analytics.d.ts +24 -0
  26. package/dist/serena/analytics.js +119 -0
  27. package/dist/serena/cli.d.ts +9 -0
  28. package/dist/serena/cli.js +731 -0
  29. package/dist/serena/code_editor.d.ts +42 -0
  30. package/dist/serena/code_editor.js +239 -0
  31. package/dist/serena/config/context_mode.d.ts +41 -0
  32. package/dist/serena/config/context_mode.js +239 -0
  33. package/dist/serena/config/serena_config.d.ts +134 -0
  34. package/dist/serena/config/serena_config.js +718 -0
  35. package/dist/serena/constants.d.ts +18 -0
  36. package/dist/serena/constants.js +27 -0
  37. package/dist/serena/dashboard.d.ts +55 -0
  38. package/dist/serena/dashboard.js +472 -0
  39. package/dist/serena/generated/generated_prompt_factory.d.ts +27 -0
  40. package/dist/serena/generated/generated_prompt_factory.js +42 -0
  41. package/dist/serena/gui_log_viewer.d.ts +41 -0
  42. package/dist/serena/gui_log_viewer.js +436 -0
  43. package/dist/serena/mcp.d.ts +118 -0
  44. package/dist/serena/mcp.js +904 -0
  45. package/dist/serena/project.d.ts +62 -0
  46. package/dist/serena/project.js +321 -0
  47. package/dist/serena/prompt_factory.d.ts +20 -0
  48. package/dist/serena/prompt_factory.js +42 -0
  49. package/dist/serena/resources/config/contexts/agent.yml +8 -0
  50. package/dist/serena/resources/config/contexts/chatgpt.yml +28 -0
  51. package/dist/serena/resources/config/contexts/codex.yml +27 -0
  52. package/dist/serena/resources/config/contexts/context.template.yml +11 -0
  53. package/dist/serena/resources/config/contexts/desktop-app.yml +17 -0
  54. package/dist/serena/resources/config/contexts/ide-assistant.yml +26 -0
  55. package/dist/serena/resources/config/contexts/oaicompat-agent.yml +8 -0
  56. package/dist/serena/resources/config/internal_modes/jetbrains.yml +15 -0
  57. package/dist/serena/resources/config/modes/editing.yml +112 -0
  58. package/dist/serena/resources/config/modes/interactive.yml +11 -0
  59. package/dist/serena/resources/config/modes/mode.template.yml +7 -0
  60. package/dist/serena/resources/config/modes/no-onboarding.yml +8 -0
  61. package/dist/serena/resources/config/modes/onboarding.yml +16 -0
  62. package/dist/serena/resources/config/modes/one-shot.yml +15 -0
  63. package/dist/serena/resources/config/modes/planning.yml +15 -0
  64. package/dist/serena/resources/config/prompt_templates/simple_tool_outputs.yml +75 -0
  65. package/dist/serena/resources/config/prompt_templates/system_prompt.yml +66 -0
  66. package/dist/serena/resources/dashboard/dashboard.js +815 -0
  67. package/dist/serena/resources/dashboard/index.html +314 -0
  68. package/dist/serena/resources/dashboard/jquery.min.js +3 -0
  69. package/dist/serena/resources/dashboard/serena-icon-16.png +0 -0
  70. package/dist/serena/resources/dashboard/serena-icon-32.png +0 -0
  71. package/dist/serena/resources/dashboard/serena-icon-48.png +0 -0
  72. package/dist/serena/resources/dashboard/serena-logs-dark-mode.png +0 -0
  73. package/dist/serena/resources/dashboard/serena-logs.png +0 -0
  74. package/dist/serena/resources/project.template.yml +67 -0
  75. package/dist/serena/resources/serena_config.template.yml +85 -0
  76. package/dist/serena/symbol.d.ts +199 -0
  77. package/dist/serena/symbol.js +616 -0
  78. package/dist/serena/text_utils.d.ts +51 -0
  79. package/dist/serena/text_utils.js +267 -0
  80. package/dist/serena/tools/cmd_tools.d.ts +31 -0
  81. package/dist/serena/tools/cmd_tools.js +48 -0
  82. package/dist/serena/tools/config_tools.d.ts +53 -0
  83. package/dist/serena/tools/config_tools.js +176 -0
  84. package/dist/serena/tools/file_tools.d.ts +231 -0
  85. package/dist/serena/tools/file_tools.js +511 -0
  86. package/dist/serena/tools/index.d.ts +7 -0
  87. package/dist/serena/tools/index.js +7 -0
  88. package/dist/serena/tools/memory_tools.d.ts +60 -0
  89. package/dist/serena/tools/memory_tools.js +135 -0
  90. package/dist/serena/tools/symbol_tools.d.ts +165 -0
  91. package/dist/serena/tools/symbol_tools.js +362 -0
  92. package/dist/serena/tools/tools_base.d.ts +162 -0
  93. package/dist/serena/tools/tools_base.js +378 -0
  94. package/dist/serena/tools/workflow_tools.d.ts +35 -0
  95. package/dist/serena/tools/workflow_tools.js +161 -0
  96. package/dist/serena/util/class_decorators.d.ts +7 -0
  97. package/dist/serena/util/class_decorators.js +37 -0
  98. package/dist/serena/util/exception.d.ts +8 -0
  99. package/dist/serena/util/exception.js +53 -0
  100. package/dist/serena/util/file_system.d.ts +30 -0
  101. package/dist/serena/util/file_system.js +352 -0
  102. package/dist/serena/util/general.d.ts +11 -0
  103. package/dist/serena/util/general.js +42 -0
  104. package/dist/serena/util/git.d.ts +11 -0
  105. package/dist/serena/util/git.js +37 -0
  106. package/dist/serena/util/inspection.d.ts +45 -0
  107. package/dist/serena/util/inspection.js +221 -0
  108. package/dist/serena/util/logging.d.ts +46 -0
  109. package/dist/serena/util/logging.js +205 -0
  110. package/dist/serena/util/shell.d.ts +21 -0
  111. package/dist/serena/util/shell.js +95 -0
  112. package/dist/serena/util/thread.d.ts +23 -0
  113. package/dist/serena/util/thread.js +88 -0
  114. package/dist/serena/version.d.ts +1 -0
  115. package/dist/serena/version.js +23 -0
  116. package/dist/solidlsp/language_servers/autoload.d.ts +23 -0
  117. package/dist/solidlsp/language_servers/autoload.js +25 -0
  118. package/dist/solidlsp/language_servers/bash_language_server.d.ts +10 -0
  119. package/dist/solidlsp/language_servers/bash_language_server.js +64 -0
  120. package/dist/solidlsp/language_servers/clangd_language_server.d.ts +13 -0
  121. package/dist/solidlsp/language_servers/clangd_language_server.js +110 -0
  122. package/dist/solidlsp/language_servers/clojure_lsp.d.ts +13 -0
  123. package/dist/solidlsp/language_servers/clojure_lsp.js +137 -0
  124. package/dist/solidlsp/language_servers/common.d.ts +41 -0
  125. package/dist/solidlsp/language_servers/common.js +365 -0
  126. package/dist/solidlsp/language_servers/csharp_language_server.d.ts +21 -0
  127. package/dist/solidlsp/language_servers/csharp_language_server.js +694 -0
  128. package/dist/solidlsp/language_servers/dart_language_server.d.ts +10 -0
  129. package/dist/solidlsp/language_servers/dart_language_server.js +122 -0
  130. package/dist/solidlsp/language_servers/eclipse_jdtls.d.ts +24 -0
  131. package/dist/solidlsp/language_servers/eclipse_jdtls.js +671 -0
  132. package/dist/solidlsp/language_servers/erlang_language_server.d.ts +22 -0
  133. package/dist/solidlsp/language_servers/erlang_language_server.js +327 -0
  134. package/dist/solidlsp/language_servers/gopls.d.ts +12 -0
  135. package/dist/solidlsp/language_servers/gopls.js +59 -0
  136. package/dist/solidlsp/language_servers/intelephense.d.ts +13 -0
  137. package/dist/solidlsp/language_servers/intelephense.js +121 -0
  138. package/dist/solidlsp/language_servers/jedi_server.d.ts +18 -0
  139. package/dist/solidlsp/language_servers/jedi_server.js +234 -0
  140. package/dist/solidlsp/language_servers/kotlin_language_server.d.ts +19 -0
  141. package/dist/solidlsp/language_servers/kotlin_language_server.js +474 -0
  142. package/dist/solidlsp/language_servers/lua_ls.d.ts +18 -0
  143. package/dist/solidlsp/language_servers/lua_ls.js +319 -0
  144. package/dist/solidlsp/language_servers/nixd_language_server.d.ts +17 -0
  145. package/dist/solidlsp/language_servers/nixd_language_server.js +341 -0
  146. package/dist/solidlsp/language_servers/pyright_server.d.ts +19 -0
  147. package/dist/solidlsp/language_servers/pyright_server.js +180 -0
  148. package/dist/solidlsp/language_servers/r_language_server.d.ts +19 -0
  149. package/dist/solidlsp/language_servers/r_language_server.js +184 -0
  150. package/dist/solidlsp/language_servers/ruby_common.d.ts +10 -0
  151. package/dist/solidlsp/language_servers/ruby_common.js +136 -0
  152. package/dist/solidlsp/language_servers/ruby_lsp.d.ts +18 -0
  153. package/dist/solidlsp/language_servers/ruby_lsp.js +230 -0
  154. package/dist/solidlsp/language_servers/rust_analyzer.d.ts +13 -0
  155. package/dist/solidlsp/language_servers/rust_analyzer.js +96 -0
  156. package/dist/solidlsp/language_servers/solargraph.d.ts +18 -0
  157. package/dist/solidlsp/language_servers/solargraph.js +208 -0
  158. package/dist/solidlsp/language_servers/sourcekit_lsp.d.ts +24 -0
  159. package/dist/solidlsp/language_servers/sourcekit_lsp.js +449 -0
  160. package/dist/solidlsp/language_servers/terraform_ls.d.ts +13 -0
  161. package/dist/solidlsp/language_servers/terraform_ls.js +139 -0
  162. package/dist/solidlsp/language_servers/typescript_language_server.d.ts +20 -0
  163. package/dist/solidlsp/language_servers/typescript_language_server.js +237 -0
  164. package/dist/solidlsp/language_servers/vts_language_server.d.ts +13 -0
  165. package/dist/solidlsp/language_servers/vts_language_server.js +121 -0
  166. package/dist/solidlsp/language_servers/zls.d.ts +20 -0
  167. package/dist/solidlsp/language_servers/zls.js +254 -0
  168. package/dist/solidlsp/ls.d.ts +197 -0
  169. package/dist/solidlsp/ls.js +507 -0
  170. package/dist/solidlsp/ls_config.d.ts +43 -0
  171. package/dist/solidlsp/ls_config.js +157 -0
  172. package/dist/solidlsp/ls_exceptions.d.ts +5 -0
  173. package/dist/solidlsp/ls_exceptions.js +14 -0
  174. package/dist/solidlsp/ls_handler.d.ts +54 -0
  175. package/dist/solidlsp/ls_handler.js +406 -0
  176. package/dist/solidlsp/ls_request.d.ts +31 -0
  177. package/dist/solidlsp/ls_request.js +42 -0
  178. package/dist/solidlsp/ls_types.d.ts +7 -0
  179. package/dist/solidlsp/ls_types.js +8 -0
  180. package/dist/solidlsp/lsp_protocol_handler/server.d.ts +61 -0
  181. package/dist/solidlsp/lsp_protocol_handler/server.js +68 -0
  182. package/dist/solidlsp/util/subprocess_util.d.ts +6 -0
  183. package/dist/solidlsp/util/subprocess_util.js +11 -0
  184. package/dist/solidlsp/util/zip.d.ts +25 -0
  185. package/dist/solidlsp/util/zip.js +188 -0
  186. package/package.json +65 -0
@@ -0,0 +1,42 @@
1
+ import { PositionInFile } from './symbol.js';
2
+ import type { JetBrainsSymbol, LanguageServerSymbol, LanguageServerSymbolRetriever, Symbol as SerenaSymbol } from './symbol.js';
3
+ import type { SerenaAgent } from './agent.js';
4
+ interface EditedFile {
5
+ getContents(): string;
6
+ deleteTextBetweenPositions(start: PositionInFile, end: PositionInFile): void;
7
+ insertTextAtPosition(position: PositionInFile, text: string): void;
8
+ }
9
+ export declare abstract class CodeEditor<TSymbol extends SerenaSymbol> {
10
+ protected readonly projectRoot: string;
11
+ protected readonly agent: SerenaAgent | null;
12
+ constructor(projectRoot: string, agent?: SerenaAgent | null);
13
+ protected abstract openFile(relativePath: string): Promise<EditedFile>;
14
+ protected abstract findUniqueSymbol(namePath: string, relativeFilePath: string): Promise<TSymbol> | TSymbol;
15
+ protected withEditedFile(relativePath: string, handler: (file: EditedFile) => Promise<void> | void): Promise<void>;
16
+ replaceBody(namePath: string, relativeFilePath: string, body: string): Promise<void>;
17
+ replace_body(namePath: string, relativeFilePath: string, body: string): Promise<void>;
18
+ protected static countLeadingNewlines(text: string): number;
19
+ protected static countTrailingNewlines(text: string): number;
20
+ insertAfterSymbol(namePath: string, relativeFilePath: string, body: string): Promise<void>;
21
+ insert_after_symbol(namePath: string, relativeFilePath: string, body: string): Promise<void>;
22
+ insertBeforeSymbol(namePath: string, relativeFilePath: string, body: string): Promise<void>;
23
+ insert_before_symbol(namePath: string, relativeFilePath: string, body: string): Promise<void>;
24
+ insertAtLine(relativePath: string, line: number, content: string): Promise<void>;
25
+ insert_at_line(relativePath: string, line: number, content: string): Promise<void>;
26
+ deleteLines(relativePath: string, startLine: number, endLine: number): Promise<void>;
27
+ delete_lines(relativePath: string, startLine: number, endLine: number): Promise<void>;
28
+ deleteSymbol(namePath: string, relativeFilePath: string): Promise<void>;
29
+ delete_symbol(namePath: string, relativeFilePath: string): Promise<void>;
30
+ }
31
+ export declare class LanguageServerCodeEditor extends CodeEditor<LanguageServerSymbol> {
32
+ private readonly symbolRetriever;
33
+ constructor(symbolRetriever: LanguageServerSymbolRetriever, agent?: SerenaAgent | null);
34
+ protected openFile(relativePath: string): Promise<EditedFile>;
35
+ protected findUniqueSymbol(namePath: string, relativeFilePath: string): LanguageServerSymbol;
36
+ }
37
+ export declare class JetBrainsCodeEditor extends CodeEditor<JetBrainsSymbol> {
38
+ constructor(projectRoot: string, agent?: SerenaAgent | null);
39
+ protected openFile(_relativePath: string): Promise<EditedFile>;
40
+ protected findUniqueSymbol(_namePath: string, _relativeFilePath: string): JetBrainsSymbol;
41
+ }
42
+ export {};
@@ -0,0 +1,239 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { JetBrainsCodeEditorNotAvailableError, PositionInFile } from './symbol.js';
4
+ function assertNonNegative(value, description) {
5
+ if (!Number.isInteger(value) || value < 0) {
6
+ throw new Error(`${description} must be a non-negative integer.`);
7
+ }
8
+ }
9
+ function positionToOffset(text, position) {
10
+ assertNonNegative(position.line, 'Position line');
11
+ assertNonNegative(position.col, 'Position column');
12
+ let line = 0;
13
+ let column = 0;
14
+ for (let index = 0; index < text.length; index += 1) {
15
+ if (line === position.line && column === position.col) {
16
+ return index;
17
+ }
18
+ const char = text[index];
19
+ if (char === '\n') {
20
+ line += 1;
21
+ column = 0;
22
+ continue;
23
+ }
24
+ if (char === '\r') {
25
+ continue;
26
+ }
27
+ column += 1;
28
+ }
29
+ if (line === position.line && column === position.col) {
30
+ return text.length;
31
+ }
32
+ throw new Error(`Position (${position.line}, ${position.col}) is out of bounds for provided content.`);
33
+ }
34
+ class InMemoryEditedFile {
35
+ contents;
36
+ constructor(contents) {
37
+ this.contents = contents;
38
+ }
39
+ getContents() {
40
+ return this.contents;
41
+ }
42
+ deleteTextBetweenPositions(start, end) {
43
+ const startOffset = positionToOffset(this.contents, start);
44
+ const endOffset = positionToOffset(this.contents, end);
45
+ if (endOffset < startOffset) {
46
+ throw new Error('End position must not precede start position.');
47
+ }
48
+ this.contents = `${this.contents.slice(0, startOffset)}${this.contents.slice(endOffset)}`;
49
+ }
50
+ insertTextAtPosition(position, text) {
51
+ const offset = positionToOffset(this.contents, position);
52
+ this.contents = `${this.contents.slice(0, offset)}${text}${this.contents.slice(offset)}`;
53
+ }
54
+ }
55
+ export class CodeEditor {
56
+ projectRoot;
57
+ agent;
58
+ constructor(projectRoot, agent = null) {
59
+ this.projectRoot = projectRoot;
60
+ this.agent = agent;
61
+ }
62
+ async withEditedFile(relativePath, handler) {
63
+ const editedFile = await this.openFile(relativePath);
64
+ await handler(editedFile);
65
+ const absolutePath = path.resolve(this.projectRoot, relativePath);
66
+ await fs.mkdir(path.dirname(absolutePath), { recursive: true });
67
+ await fs.writeFile(absolutePath, editedFile.getContents(), { encoding: 'utf-8' });
68
+ this.agent?.markFileModified(relativePath);
69
+ }
70
+ async replaceBody(namePath, relativeFilePath, body) {
71
+ const symbol = await this.findUniqueSymbol(namePath, relativeFilePath);
72
+ const startPos = symbol.getBodyStartPositionOrRaise();
73
+ const endPos = symbol.getBodyEndPositionOrRaise();
74
+ const trimmedBody = body.trim();
75
+ await this.withEditedFile(relativeFilePath, (editedFile) => {
76
+ editedFile.deleteTextBetweenPositions(startPos, endPos);
77
+ editedFile.insertTextAtPosition(startPos, trimmedBody);
78
+ });
79
+ }
80
+ async replace_body(namePath, relativeFilePath, body) {
81
+ await this.replaceBody(namePath, relativeFilePath, body);
82
+ }
83
+ static countLeadingNewlines(text) {
84
+ let count = 0;
85
+ for (const char of text) {
86
+ if (char === '\n') {
87
+ count += 1;
88
+ }
89
+ else if (char === '\r') {
90
+ continue;
91
+ }
92
+ else {
93
+ break;
94
+ }
95
+ }
96
+ return count;
97
+ }
98
+ static countTrailingNewlines(text) {
99
+ let count = 0;
100
+ for (let index = text.length - 1; index >= 0; index -= 1) {
101
+ const char = text[index];
102
+ if (char === '\n') {
103
+ count += 1;
104
+ }
105
+ else if (char === '\r') {
106
+ continue;
107
+ }
108
+ else {
109
+ break;
110
+ }
111
+ }
112
+ return count;
113
+ }
114
+ async insertAfterSymbol(namePath, relativeFilePath, body) {
115
+ const symbol = await this.findUniqueSymbol(namePath, relativeFilePath);
116
+ const endPos = symbol.getBodyEndPositionOrRaise();
117
+ let normalizedBody = body;
118
+ if (!normalizedBody.endsWith('\n')) {
119
+ normalizedBody += '\n';
120
+ }
121
+ const originalLeading = CodeEditor.countLeadingNewlines(normalizedBody);
122
+ normalizedBody = normalizedBody.replace(/^[\r\n]+/u, '');
123
+ const minimumEmptyLines = symbol.isNeighbouringDefinitionSeparatedByEmptyLine() ? 1 : 0;
124
+ const leadingEmptyLines = Math.max(minimumEmptyLines, originalLeading);
125
+ if (leadingEmptyLines > 0) {
126
+ normalizedBody = `${'\n'.repeat(leadingEmptyLines)}${normalizedBody}`;
127
+ }
128
+ normalizedBody = normalizedBody.replace(/[\r\n]+$/u, '') + '\n';
129
+ const insertPosition = new PositionInFile({ line: endPos.line, col: endPos.col });
130
+ await this.withEditedFile(relativeFilePath, (editedFile) => {
131
+ editedFile.insertTextAtPosition(insertPosition, normalizedBody);
132
+ });
133
+ }
134
+ async insert_after_symbol(namePath, relativeFilePath, body) {
135
+ await this.insertAfterSymbol(namePath, relativeFilePath, body);
136
+ }
137
+ async insertBeforeSymbol(namePath, relativeFilePath, body) {
138
+ const symbol = await this.findUniqueSymbol(namePath, relativeFilePath);
139
+ const startPos = symbol.getBodyStartPositionOrRaise();
140
+ let normalizedBody = body.replace(/\s+$/u, '') + '\n';
141
+ const originalTrailing = CodeEditor.countTrailingNewlines(normalizedBody);
142
+ const minimumTrailing = symbol.isNeighbouringDefinitionSeparatedByEmptyLine() ? 1 : 0;
143
+ const trailing = Math.max(minimumTrailing, Math.max(0, originalTrailing - 1));
144
+ if (trailing > 0) {
145
+ normalizedBody += '\n'.repeat(trailing);
146
+ }
147
+ await this.withEditedFile(relativeFilePath, (editedFile) => {
148
+ editedFile.insertTextAtPosition(new PositionInFile({ line: startPos.line, col: 0 }), normalizedBody);
149
+ });
150
+ }
151
+ async insert_before_symbol(namePath, relativeFilePath, body) {
152
+ await this.insertBeforeSymbol(namePath, relativeFilePath, body);
153
+ }
154
+ async insertAtLine(relativePath, line, content) {
155
+ assertNonNegative(line, 'line');
156
+ await this.withEditedFile(relativePath, (editedFile) => {
157
+ editedFile.insertTextAtPosition(new PositionInFile({ line, col: 0 }), content);
158
+ });
159
+ }
160
+ async insert_at_line(relativePath, line, content) {
161
+ await this.insertAtLine(relativePath, line, content);
162
+ }
163
+ async deleteLines(relativePath, startLine, endLine) {
164
+ assertNonNegative(startLine, 'start_line');
165
+ assertNonNegative(endLine, 'end_line');
166
+ if (endLine < startLine) {
167
+ throw new Error('end_line must not be less than start_line.');
168
+ }
169
+ await this.withEditedFile(relativePath, (editedFile) => {
170
+ const start = new PositionInFile({ line: startLine, col: 0 });
171
+ const end = new PositionInFile({ line: endLine + 1, col: 0 });
172
+ editedFile.deleteTextBetweenPositions(start, end);
173
+ });
174
+ }
175
+ async delete_lines(relativePath, startLine, endLine) {
176
+ await this.deleteLines(relativePath, startLine, endLine);
177
+ }
178
+ async deleteSymbol(namePath, relativeFilePath) {
179
+ const symbol = await this.findUniqueSymbol(namePath, relativeFilePath);
180
+ const startPos = symbol.getBodyStartPositionOrRaise();
181
+ const endPos = symbol.getBodyEndPositionOrRaise();
182
+ await this.withEditedFile(relativeFilePath, (editedFile) => {
183
+ editedFile.deleteTextBetweenPositions(startPos, endPos);
184
+ });
185
+ }
186
+ async delete_symbol(namePath, relativeFilePath) {
187
+ await this.deleteSymbol(namePath, relativeFilePath);
188
+ }
189
+ }
190
+ export class LanguageServerCodeEditor extends CodeEditor {
191
+ symbolRetriever;
192
+ constructor(symbolRetriever, agent = null) {
193
+ super(symbolRetriever.get_language_server().getRepositoryRootPath(), agent);
194
+ this.symbolRetriever = symbolRetriever;
195
+ }
196
+ async openFile(relativePath) {
197
+ const absolutePath = path.resolve(this.projectRoot, relativePath);
198
+ const buffer = await fs.readFile(absolutePath, { encoding: 'utf-8' }).catch((error) => {
199
+ if (error.code === 'ENOENT') {
200
+ throw new Error(`File not found: ${relativePath}`);
201
+ }
202
+ throw error;
203
+ });
204
+ return new InMemoryEditedFile(buffer);
205
+ }
206
+ findUniqueSymbol(namePath, relativeFilePath) {
207
+ const matches = this.symbolRetriever.find_by_name(namePath, false, undefined, undefined, false, relativeFilePath);
208
+ if (matches.length === 0) {
209
+ throw new Error(`No symbol with name ${namePath} found in file ${relativeFilePath}`);
210
+ }
211
+ if (matches.length > 1) {
212
+ const locations = matches
213
+ .map((symbol) => symbol.location.to_dict())
214
+ .map((location) => JSON.stringify(location))
215
+ .join(', ');
216
+ throw new Error(`Found multiple symbols with name ${namePath} in file ${relativeFilePath}. Their locations are: [${locations}]`);
217
+ }
218
+ const [match] = matches;
219
+ if (!match) {
220
+ throw new Error(`Invariant violation while retrieving symbol ${namePath} in ${relativeFilePath}`);
221
+ }
222
+ return match;
223
+ }
224
+ }
225
+ export class JetBrainsCodeEditor extends CodeEditor {
226
+ constructor(projectRoot, agent = null) {
227
+ super(projectRoot, agent);
228
+ throw new JetBrainsCodeEditorNotAvailableError();
229
+ }
230
+ // The following methods exist to satisfy the abstract contract but will never be executed
231
+ // because the constructor throws immediately.
232
+ /* c8 ignore start */
233
+ openFile(_relativePath) {
234
+ throw new JetBrainsCodeEditorNotAvailableError();
235
+ }
236
+ findUniqueSymbol(_namePath, _relativeFilePath) {
237
+ throw new JetBrainsCodeEditorNotAvailableError();
238
+ }
239
+ }
@@ -0,0 +1,41 @@
1
+ import type { ToolInclusionDefinitionInit } from './serena_config.js';
2
+ import { ToolInclusionDefinition } from './serena_config.js';
3
+ export declare class SerenaAgentMode extends ToolInclusionDefinition {
4
+ readonly name: string;
5
+ readonly prompt: string;
6
+ readonly description: string;
7
+ constructor(init: ToolInclusionDefinitionInit & {
8
+ name: string;
9
+ prompt: string;
10
+ description?: string;
11
+ });
12
+ static fromYaml(yamlPath: string): SerenaAgentMode;
13
+ static getPath(name: string): string;
14
+ static fromName(name: string): SerenaAgentMode;
15
+ static fromNameInternal(name: string): SerenaAgentMode;
16
+ static listRegisteredModeNames(includeUserModes?: boolean): string[];
17
+ static listCustomModeNames(): string[];
18
+ static loadDefaultModes(): SerenaAgentMode[];
19
+ static load(nameOrPath: string): SerenaAgentMode;
20
+ printOverview(): void;
21
+ }
22
+ export declare class SerenaAgentContext extends ToolInclusionDefinition {
23
+ readonly name: string;
24
+ readonly prompt: string;
25
+ readonly description: string;
26
+ readonly toolDescriptionOverrides: Record<string, string>;
27
+ constructor(init: ToolInclusionDefinitionInit & {
28
+ name: string;
29
+ prompt: string;
30
+ description?: string;
31
+ toolDescriptionOverrides?: Record<string, string>;
32
+ });
33
+ static fromYaml(yamlPath: string): SerenaAgentContext;
34
+ static getPath(name: string): string;
35
+ static fromName(name: string): SerenaAgentContext;
36
+ static load(nameOrPath: string): SerenaAgentContext;
37
+ static listRegisteredContextNames(includeUserContexts?: boolean): string[];
38
+ static listCustomContextNames(): string[];
39
+ static loadDefault(): SerenaAgentContext;
40
+ printOverview(): void;
41
+ }
@@ -0,0 +1,239 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { z } from 'zod';
4
+ import { DEFAULT_CONTEXT, DEFAULT_MODES, INTERNAL_MODE_YAMLS_DIR, SERENAS_OWN_CONTEXT_YAMLS_DIR, SERENAS_OWN_MODE_YAMLS_DIR, USER_CONTEXT_YAMLS_DIR, USER_MODE_YAMLS_DIR } from '../constants.js';
5
+ import { loadYaml } from '../util/general.js';
6
+ import { ToolInclusionDefinition } from './serena_config.js';
7
+ const STRING_ARRAY_SCHEMA = z
8
+ .union([z.array(z.union([z.string(), z.number(), z.boolean()])), z.string(), z.number(), z.boolean(), z.null()])
9
+ .optional()
10
+ .transform((value) => {
11
+ if (value === undefined || value === null) {
12
+ return [];
13
+ }
14
+ if (Array.isArray(value)) {
15
+ return value
16
+ .map((item) => {
17
+ if (typeof item === 'string') {
18
+ return item;
19
+ }
20
+ if (typeof item === 'number' || typeof item === 'boolean') {
21
+ return String(item);
22
+ }
23
+ return null;
24
+ })
25
+ .filter((item) => item !== null);
26
+ }
27
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
28
+ return [String(value)];
29
+ }
30
+ return [];
31
+ });
32
+ const OPTIONAL_NAME_SCHEMA = z
33
+ .union([z.string(), z.number()])
34
+ .optional()
35
+ .transform((value) => {
36
+ if (value === undefined) {
37
+ return undefined;
38
+ }
39
+ const normalized = String(value).trim();
40
+ return normalized.length > 0 ? normalized : undefined;
41
+ });
42
+ const OPTIONAL_TEXT_SCHEMA = z
43
+ .union([z.string(), z.number()])
44
+ .optional()
45
+ .transform((value) => {
46
+ if (value === undefined) {
47
+ return '';
48
+ }
49
+ return String(value);
50
+ });
51
+ const TOOL_DESCRIPTION_OVERRIDES_SCHEMA = z
52
+ .record(z.union([z.string(), z.number()]))
53
+ .optional()
54
+ .transform((value) => {
55
+ if (!value) {
56
+ return {};
57
+ }
58
+ return Object.fromEntries(Object.entries(value).map(([key, raw]) => [key, String(raw)]));
59
+ });
60
+ const MODE_YAML_SCHEMA = z
61
+ .object({
62
+ name: OPTIONAL_NAME_SCHEMA,
63
+ prompt: OPTIONAL_TEXT_SCHEMA,
64
+ description: OPTIONAL_TEXT_SCHEMA,
65
+ excluded_tools: STRING_ARRAY_SCHEMA,
66
+ included_optional_tools: STRING_ARRAY_SCHEMA
67
+ })
68
+ .passthrough();
69
+ const CONTEXT_YAML_SCHEMA = MODE_YAML_SCHEMA.extend({
70
+ tool_description_overrides: TOOL_DESCRIPTION_OVERRIDES_SCHEMA
71
+ });
72
+ export class SerenaAgentMode extends ToolInclusionDefinition {
73
+ name;
74
+ prompt;
75
+ description;
76
+ constructor(init) {
77
+ super(init);
78
+ this.name = init.name;
79
+ this.prompt = init.prompt;
80
+ this.description = init.description ?? '';
81
+ }
82
+ static fromYaml(yamlPath) {
83
+ const data = loadYaml(yamlPath);
84
+ if (!data || typeof data !== 'object') {
85
+ throw new Error(`Invalid mode YAML: ${yamlPath}`);
86
+ }
87
+ const plain = data;
88
+ const parsedResult = MODE_YAML_SCHEMA.safeParse(plain);
89
+ if (!parsedResult.success) {
90
+ throw new Error(formatYamlZodIssues(yamlPath, parsedResult.error));
91
+ }
92
+ const parsed = parsedResult.data;
93
+ const name = parsed.name ?? path.parse(yamlPath).name;
94
+ return new SerenaAgentMode({
95
+ name,
96
+ prompt: parsed.prompt,
97
+ description: parsed.description,
98
+ excludedTools: parsed.excluded_tools,
99
+ includedOptionalTools: parsed.included_optional_tools
100
+ });
101
+ }
102
+ static getPath(name) {
103
+ const filename = `${name}.yml`;
104
+ const custom = path.join(USER_MODE_YAMLS_DIR, filename);
105
+ if (fs.existsSync(custom)) {
106
+ return custom;
107
+ }
108
+ const own = path.join(SERENAS_OWN_MODE_YAMLS_DIR, filename);
109
+ if (!fs.existsSync(own)) {
110
+ throw new Error(`Mode ${name} not found in ${USER_MODE_YAMLS_DIR} or in ${SERENAS_OWN_MODE_YAMLS_DIR}. Available modes:\n${this.listRegisteredModeNames().join(', ')}`);
111
+ }
112
+ return own;
113
+ }
114
+ static fromName(name) {
115
+ const modePath = this.getPath(name);
116
+ return this.fromYaml(modePath);
117
+ }
118
+ static fromNameInternal(name) {
119
+ const internalPath = path.join(INTERNAL_MODE_YAMLS_DIR, `${name}.yml`);
120
+ if (!fs.existsSync(internalPath)) {
121
+ throw new Error(`Internal mode '${name}' not found in ${INTERNAL_MODE_YAMLS_DIR}`);
122
+ }
123
+ return this.fromYaml(internalPath);
124
+ }
125
+ static listRegisteredModeNames(includeUserModes = true) {
126
+ const own = listYamlFiles(SERENAS_OWN_MODE_YAMLS_DIR).filter((file) => file !== 'mode.template');
127
+ const user = includeUserModes ? listYamlFiles(USER_MODE_YAMLS_DIR) : [];
128
+ return Array.from(new Set([...own, ...user])).sort();
129
+ }
130
+ static listCustomModeNames() {
131
+ return listYamlFiles(USER_MODE_YAMLS_DIR);
132
+ }
133
+ static loadDefaultModes() {
134
+ return DEFAULT_MODES.map((mode) => this.fromName(mode));
135
+ }
136
+ static load(nameOrPath) {
137
+ if (nameOrPath.endsWith('.yml')) {
138
+ return this.fromYaml(nameOrPath);
139
+ }
140
+ return this.fromName(nameOrPath);
141
+ }
142
+ printOverview() {
143
+ console.log(`${this.name}:\n ${this.description}`);
144
+ if (this.excludedTools.length > 0) {
145
+ console.log(` excluded tools:\n ${this.excludedTools.sort().join(', ')}`);
146
+ }
147
+ }
148
+ }
149
+ export class SerenaAgentContext extends ToolInclusionDefinition {
150
+ name;
151
+ prompt;
152
+ description;
153
+ toolDescriptionOverrides;
154
+ constructor(init) {
155
+ super(init);
156
+ this.name = init.name;
157
+ this.prompt = init.prompt;
158
+ this.description = init.description ?? '';
159
+ this.toolDescriptionOverrides = { ...(init.toolDescriptionOverrides ?? {}) };
160
+ }
161
+ static fromYaml(yamlPath) {
162
+ const data = loadYaml(yamlPath);
163
+ if (!data || typeof data !== 'object') {
164
+ throw new Error(`Invalid context YAML: ${yamlPath}`);
165
+ }
166
+ const plain = data;
167
+ const parsedResult = CONTEXT_YAML_SCHEMA.safeParse(plain);
168
+ if (!parsedResult.success) {
169
+ throw new Error(formatYamlZodIssues(yamlPath, parsedResult.error));
170
+ }
171
+ const parsed = parsedResult.data;
172
+ const name = parsed.name ?? path.parse(yamlPath).name;
173
+ return new SerenaAgentContext({
174
+ name,
175
+ prompt: parsed.prompt,
176
+ description: parsed.description,
177
+ toolDescriptionOverrides: parsed.tool_description_overrides,
178
+ excludedTools: parsed.excluded_tools,
179
+ includedOptionalTools: parsed.included_optional_tools
180
+ });
181
+ }
182
+ static getPath(name) {
183
+ const filename = `${name}.yml`;
184
+ const custom = path.join(USER_CONTEXT_YAMLS_DIR, filename);
185
+ if (fs.existsSync(custom)) {
186
+ return custom;
187
+ }
188
+ const own = path.join(SERENAS_OWN_CONTEXT_YAMLS_DIR, filename);
189
+ if (!fs.existsSync(own)) {
190
+ throw new Error(`Context ${name} not found in ${USER_CONTEXT_YAMLS_DIR} or in ${SERENAS_OWN_CONTEXT_YAMLS_DIR}. Available contexts:\n${this.listRegisteredContextNames().join(', ')}`);
191
+ }
192
+ return own;
193
+ }
194
+ static fromName(name) {
195
+ const contextPath = this.getPath(name);
196
+ return this.fromYaml(contextPath);
197
+ }
198
+ static load(nameOrPath) {
199
+ if (nameOrPath.endsWith('.yml')) {
200
+ return this.fromYaml(nameOrPath);
201
+ }
202
+ return this.fromName(nameOrPath);
203
+ }
204
+ static listRegisteredContextNames(includeUserContexts = true) {
205
+ const own = listYamlFiles(SERENAS_OWN_CONTEXT_YAMLS_DIR);
206
+ const user = includeUserContexts ? listYamlFiles(USER_CONTEXT_YAMLS_DIR) : [];
207
+ return Array.from(new Set([...own, ...user])).sort();
208
+ }
209
+ static listCustomContextNames() {
210
+ return listYamlFiles(USER_CONTEXT_YAMLS_DIR);
211
+ }
212
+ static loadDefault() {
213
+ return this.fromName(DEFAULT_CONTEXT);
214
+ }
215
+ printOverview() {
216
+ console.log(`${this.name}:\n ${this.description}`);
217
+ if (this.excludedTools.length > 0) {
218
+ console.log(` excluded tools:\n ${this.excludedTools.sort().join(', ')}`);
219
+ }
220
+ }
221
+ }
222
+ function formatYamlZodIssues(filePath, error) {
223
+ const details = error.issues
224
+ .map((issue) => {
225
+ const pathSegment = issue.path.length > 0 ? issue.path.join('.') : '(root)';
226
+ return `${pathSegment}: ${issue.message}`;
227
+ })
228
+ .join('; ');
229
+ return `Invalid YAML structure detected in ${filePath}: ${details}`;
230
+ }
231
+ function listYamlFiles(directory) {
232
+ if (!fs.existsSync(directory)) {
233
+ return [];
234
+ }
235
+ return fs
236
+ .readdirSync(directory)
237
+ .filter((file) => file.endsWith('.yml'))
238
+ .map((file) => path.parse(file).name);
239
+ }