@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,718 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { z } from 'zod';
4
+ import { DEFAULT_ENCODING, PROJECT_TEMPLATE_FILE, REPO_ROOT, SERENA_CONFIG_TEMPLATE_FILE, SERENA_MANAGED_DIR_IN_HOME, SERENA_MANAGED_DIR_NAME } from '../constants.js';
5
+ import { createSerenaLogger } from '../util/logging.js';
6
+ import { loadYaml, saveYaml } from '../util/general.js';
7
+ import { determineProgrammingLanguageComposition } from '../util/inspection.js';
8
+ import { singleton } from '../util/class_decorators.js';
9
+ import { ToolRegistry } from '../tools/tools_base.js';
10
+ import { Language, coerceLanguage } from '../../solidlsp/ls_config.js';
11
+ const { logger: log } = createSerenaLogger({ name: 'serena.config' });
12
+ export class ToolInclusionDefinition {
13
+ excludedTools;
14
+ includedOptionalTools;
15
+ constructor(options = {}) {
16
+ this.excludedTools = normalizeStringIterable(options.excludedTools);
17
+ this.includedOptionalTools = normalizeStringIterable(options.includedOptionalTools);
18
+ }
19
+ createNext(options) {
20
+ return new ToolInclusionDefinition({
21
+ excludedTools: options.excludedTools ?? this.excludedTools,
22
+ includedOptionalTools: options.includedOptionalTools ?? this.includedOptionalTools
23
+ });
24
+ }
25
+ }
26
+ export const DEFAULT_TOOL_TIMEOUT = 240;
27
+ const STRING_ARRAY_INPUT_SCHEMA = z
28
+ .union([z.array(z.union([z.string(), z.number(), z.boolean()])), z.string(), z.number(), z.boolean(), z.null()])
29
+ .optional()
30
+ .transform((value) => {
31
+ if (value === undefined || value === null) {
32
+ return [];
33
+ }
34
+ if (Array.isArray(value)) {
35
+ return value
36
+ .map((item) => {
37
+ if (typeof item === 'string') {
38
+ return item;
39
+ }
40
+ if (typeof item === 'number' || typeof item === 'boolean') {
41
+ return String(item);
42
+ }
43
+ return null;
44
+ })
45
+ .filter((item) => item !== null);
46
+ }
47
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
48
+ return [String(value)];
49
+ }
50
+ return [];
51
+ });
52
+ const BOOLEAN_INPUT_SCHEMA = z
53
+ .union([z.boolean(), z.string(), z.number(), z.null()])
54
+ .optional()
55
+ .transform((value) => {
56
+ if (value === undefined || value === null) {
57
+ return undefined;
58
+ }
59
+ if (typeof value === 'boolean') {
60
+ return value;
61
+ }
62
+ if (typeof value === 'number') {
63
+ if (value === 1) {
64
+ return true;
65
+ }
66
+ if (value === 0) {
67
+ return false;
68
+ }
69
+ return undefined;
70
+ }
71
+ if (typeof value === 'string') {
72
+ const normalized = value.trim().toLowerCase();
73
+ if (['true', '1', 'yes', 'y', 'on'].includes(normalized)) {
74
+ return true;
75
+ }
76
+ if (['false', '0', 'no', 'n', 'off'].includes(normalized)) {
77
+ return false;
78
+ }
79
+ }
80
+ return undefined;
81
+ });
82
+ const NUMBER_INPUT_SCHEMA = z
83
+ .union([z.number(), z.string(), z.null()])
84
+ .optional()
85
+ .transform((value) => {
86
+ if (value === undefined || value === null) {
87
+ return undefined;
88
+ }
89
+ if (typeof value === 'number') {
90
+ return Number.isFinite(value) ? value : undefined;
91
+ }
92
+ if (typeof value === 'string') {
93
+ const parsed = Number.parseFloat(value);
94
+ return Number.isNaN(parsed) ? undefined : parsed;
95
+ }
96
+ return undefined;
97
+ });
98
+ const OPTIONAL_STRING_SCHEMA = z
99
+ .union([z.string(), z.number()])
100
+ .optional()
101
+ .transform((value) => {
102
+ if (value === undefined) {
103
+ return undefined;
104
+ }
105
+ return String(value);
106
+ });
107
+ const PROJECT_CONFIG_YAML_SCHEMA = z
108
+ .object({
109
+ project_name: z.string({ required_error: 'project_name は必須です' }).min(1, 'project_name は必須です'),
110
+ language: z.union([
111
+ z.nativeEnum(Language),
112
+ z.string({ required_error: 'language は必須です' }).min(1, 'language は必須です')
113
+ ]),
114
+ ignored_paths: STRING_ARRAY_INPUT_SCHEMA,
115
+ excluded_tools: STRING_ARRAY_INPUT_SCHEMA,
116
+ included_optional_tools: STRING_ARRAY_INPUT_SCHEMA,
117
+ read_only: BOOLEAN_INPUT_SCHEMA,
118
+ ignore_all_files_in_gitignore: BOOLEAN_INPUT_SCHEMA,
119
+ initial_prompt: OPTIONAL_STRING_SCHEMA,
120
+ encoding: z.string().optional()
121
+ })
122
+ .passthrough();
123
+ const SERENA_CONFIG_YAML_SCHEMA = z
124
+ .object({
125
+ gui_log_window: BOOLEAN_INPUT_SCHEMA,
126
+ gui_log_window_enabled: BOOLEAN_INPUT_SCHEMA,
127
+ web_dashboard: BOOLEAN_INPUT_SCHEMA,
128
+ web_dashboard_open_on_launch: BOOLEAN_INPUT_SCHEMA,
129
+ log_level: NUMBER_INPUT_SCHEMA,
130
+ gui_log_level: NUMBER_INPUT_SCHEMA,
131
+ trace_lsp_communication: BOOLEAN_INPUT_SCHEMA,
132
+ tool_timeout: NUMBER_INPUT_SCHEMA,
133
+ excluded_tools: STRING_ARRAY_INPUT_SCHEMA,
134
+ included_optional_tools: STRING_ARRAY_INPUT_SCHEMA,
135
+ jetbrains: BOOLEAN_INPUT_SCHEMA,
136
+ record_tool_usage_stats: BOOLEAN_INPUT_SCHEMA,
137
+ token_count_estimator: z.string().optional(),
138
+ default_max_tool_answer_chars: NUMBER_INPUT_SCHEMA,
139
+ ls_specific_settings: z.record(z.unknown()).optional(),
140
+ projects: z.array(z.string(), {
141
+ required_error: '`projects` key not found in Serena configuration.',
142
+ invalid_type_error: '`projects` は文字列の配列である必要があります。'
143
+ })
144
+ })
145
+ .passthrough();
146
+ function datetimeTag() {
147
+ const now = new Date();
148
+ const yyyy = now.getFullYear().toString().padStart(4, '0');
149
+ const mm = (now.getMonth() + 1).toString().padStart(2, '0');
150
+ const dd = now.getDate().toString().padStart(2, '0');
151
+ const hh = now.getHours().toString().padStart(2, '0');
152
+ const mi = now.getMinutes().toString().padStart(2, '0');
153
+ const ss = now.getSeconds().toString().padStart(2, '0');
154
+ return `${yyyy}${mm}${dd}_${hh}${mi}${ss}`;
155
+ }
156
+ class SerenaPathsImpl {
157
+ userConfigDir;
158
+ constructor() {
159
+ this.userConfigDir = SERENA_MANAGED_DIR_IN_HOME;
160
+ }
161
+ getNextLogFilePath(prefix) {
162
+ const dateDir = path.join(this.userConfigDir, 'logs', new Date().toISOString().slice(0, 10));
163
+ fs.mkdirSync(dateDir, { recursive: true });
164
+ return path.join(dateDir, `${prefix}_${datetimeTag()}.txt`);
165
+ }
166
+ }
167
+ const SerenaPathsSingleton = singleton(SerenaPathsImpl);
168
+ export { SerenaPathsSingleton as SerenaPaths };
169
+ export class ToolSet {
170
+ toolNames;
171
+ constructor(toolNames) {
172
+ this.toolNames = new Set(toolNames);
173
+ }
174
+ static default() {
175
+ const registry = new ToolRegistry();
176
+ const defaultTools = registry.getToolNamesDefaultEnabled();
177
+ return new ToolSet(defaultTools);
178
+ }
179
+ apply(...definitions) {
180
+ const registry = new ToolRegistry();
181
+ const next = new Set(this.toolNames);
182
+ for (const definition of definitions) {
183
+ const included = [];
184
+ const excluded = [];
185
+ for (const tool of definition.includedOptionalTools) {
186
+ if (!registry.isValidToolName(tool)) {
187
+ throw new Error(`Invalid tool name '${tool}' provided for inclusion`);
188
+ }
189
+ if (!next.has(tool)) {
190
+ next.add(tool);
191
+ included.push(tool);
192
+ }
193
+ }
194
+ for (const tool of definition.excludedTools) {
195
+ if (!registry.isValidToolName(tool)) {
196
+ throw new Error(`Invalid tool name '${tool}' provided for exclusion`);
197
+ }
198
+ if (next.delete(tool)) {
199
+ excluded.push(tool);
200
+ }
201
+ }
202
+ if (included.length > 0) {
203
+ log.info(`${formatDefinition(definition)} included ${included.length} tools: ${included.join(', ')}`);
204
+ }
205
+ if (excluded.length > 0) {
206
+ log.info(`${formatDefinition(definition)} excluded ${excluded.length} tools: ${excluded.join(', ')}`);
207
+ }
208
+ }
209
+ return new ToolSet(next);
210
+ }
211
+ withoutEditingTools() {
212
+ const registry = new ToolRegistry();
213
+ const next = new Set(this.toolNames);
214
+ for (const toolName of this.toolNames) {
215
+ try {
216
+ const toolClass = registry.getToolClassByName(toolName);
217
+ if (toolClass.canEdit()) {
218
+ next.delete(toolName);
219
+ }
220
+ }
221
+ catch {
222
+ // ToolRegistry は現段階でプレースホルダーのため、未実装メソッドがあれば無視する
223
+ }
224
+ }
225
+ return new ToolSet(next);
226
+ }
227
+ getToolNames() {
228
+ return new Set(this.toolNames);
229
+ }
230
+ includesName(toolName) {
231
+ return this.toolNames.has(toolName);
232
+ }
233
+ }
234
+ export class ProjectConfig extends ToolInclusionDefinition {
235
+ projectName;
236
+ language;
237
+ ignoredPaths;
238
+ readOnly;
239
+ ignoreAllFilesInGitignore;
240
+ initialPrompt;
241
+ encoding;
242
+ static SERENA_DEFAULT_PROJECT_FILE = 'project.yml';
243
+ constructor(init) {
244
+ super(init);
245
+ this.projectName = init.projectName;
246
+ this.language = init.language;
247
+ this.ignoredPaths = [...(init.ignoredPaths ?? [])];
248
+ this.readOnly = init.readOnly ?? false;
249
+ this.ignoreAllFilesInGitignore = init.ignoreAllFilesInGitignore ?? true;
250
+ this.initialPrompt = init.initialPrompt ?? '';
251
+ this.encoding = init.encoding ?? DEFAULT_ENCODING;
252
+ }
253
+ static autogenerate(projectRoot, options = {}) {
254
+ const resolvedRoot = path.resolve(projectRoot);
255
+ if (!fs.existsSync(resolvedRoot)) {
256
+ throw new Error(`Project root not found: ${resolvedRoot}`);
257
+ }
258
+ const saveToDisk = options.saveToDisk ?? true;
259
+ const name = options.projectName ?? path.basename(resolvedRoot);
260
+ let language = options.projectLanguage;
261
+ if (!language) {
262
+ const composition = determineProgrammingLanguageComposition(resolvedRoot);
263
+ const entries = Object.entries(composition);
264
+ if (entries.length === 0) {
265
+ const relativePath = path.join(resolvedRoot, this.relPathToProjectYml());
266
+ throw new Error([
267
+ `No source files found in ${resolvedRoot}`,
268
+ '',
269
+ 'Serena を利用するには以下のいずれかを行ってください:',
270
+ '1. 対応言語のソースファイルを追加 (Python, JavaScript/TypeScript, Java, C#, Rust, Go, Ruby, C++, PHP, Swift, Elixir, Terraform, Bash)',
271
+ `2. 次の場所に project.yml を手動で作成: ${relativePath}`,
272
+ '',
273
+ 'project.yml の例:',
274
+ ` project_name: ${name}`,
275
+ ' language: python # 例: typescript, java, csharp, rust, go, ruby, cpp, php, swift, elixir, terraform, bash'
276
+ ].join('\n'));
277
+ }
278
+ entries.sort((a, b) => b[1] - a[1]);
279
+ language = coerceLanguage(entries[0][0]);
280
+ }
281
+ const template = loadYaml(PROJECT_TEMPLATE_FILE, true);
282
+ assignYamlValue(template, 'project_name', name);
283
+ assignYamlValue(template, 'language', language);
284
+ if (saveToDisk) {
285
+ const destination = path.join(resolvedRoot, this.relPathToProjectYml());
286
+ saveYaml(destination, template, true);
287
+ }
288
+ const plain = yamlToObject(template);
289
+ return this.fromDict(plain);
290
+ }
291
+ static relPathToProjectYml() {
292
+ return path.join(SERENA_MANAGED_DIR_NAME, this.SERENA_DEFAULT_PROJECT_FILE);
293
+ }
294
+ static load(projectRoot, autogenerate = false) {
295
+ const resolvedRoot = path.resolve(projectRoot);
296
+ const yamlPath = path.join(resolvedRoot, this.relPathToProjectYml());
297
+ if (!fs.existsSync(yamlPath)) {
298
+ if (autogenerate) {
299
+ return this.autogenerate(resolvedRoot);
300
+ }
301
+ throw new Error(`Project configuration file not found: ${yamlPath}`);
302
+ }
303
+ const data = loadYaml(yamlPath);
304
+ if (typeof data !== 'object' || data === null) {
305
+ throw new Error(`Invalid project configuration file: ${yamlPath}`);
306
+ }
307
+ const plain = data;
308
+ if (typeof plain.project_name !== 'string' || plain.project_name.length === 0) {
309
+ plain.project_name = path.basename(resolvedRoot);
310
+ }
311
+ return this.fromDict(plain);
312
+ }
313
+ static fromDict(data) {
314
+ const parsedResult = PROJECT_CONFIG_YAML_SCHEMA.safeParse(data);
315
+ if (!parsedResult.success) {
316
+ throw new SerenaConfigError(formatZodIssues('project.yml', parsedResult.error));
317
+ }
318
+ const parsed = parsedResult.data;
319
+ const projectName = parsed.project_name;
320
+ const languageValue = coerceLanguage(String(parsed.language));
321
+ let ignoredPaths = parsed.ignored_paths;
322
+ if (ignoredPaths.length === 0 && 'ignored_dirs' in data) {
323
+ const legacyIgnored = data['ignored_dirs'];
324
+ ignoredPaths = normalizeStringIterable(legacyIgnored);
325
+ }
326
+ return new ProjectConfig({
327
+ projectName,
328
+ language: languageValue,
329
+ ignoredPaths,
330
+ excludedTools: parsed.excluded_tools,
331
+ includedOptionalTools: parsed.included_optional_tools,
332
+ readOnly: parsed.read_only ?? false,
333
+ ignoreAllFilesInGitignore: parsed.ignore_all_files_in_gitignore ?? true,
334
+ initialPrompt: parsed.initial_prompt ?? '',
335
+ encoding: parsed.encoding ?? DEFAULT_ENCODING
336
+ });
337
+ }
338
+ }
339
+ export class RegisteredProject {
340
+ projectRoot;
341
+ projectConfig;
342
+ projectInstance;
343
+ constructor(init) {
344
+ this.projectRoot = path.resolve(init.projectRoot);
345
+ this.projectConfig = init.projectConfig;
346
+ this.projectInstance = init.projectInstance;
347
+ }
348
+ get projectName() {
349
+ return this.projectConfig.projectName;
350
+ }
351
+ static fromProjectInstance(projectInstance) {
352
+ return new RegisteredProject({
353
+ projectRoot: projectInstance.projectRoot,
354
+ projectConfig: projectInstance.projectConfig,
355
+ projectInstance
356
+ });
357
+ }
358
+ matchesRootPath(candidate) {
359
+ return this.projectRoot === path.resolve(candidate);
360
+ }
361
+ hasProjectInstance() {
362
+ return this.projectInstance !== undefined;
363
+ }
364
+ getProjectInstance() {
365
+ if (!this.projectInstance) {
366
+ throw new Error('Project インスタンスはまだ割り当てられていません。Project モジュールの移植完了後に再度呼び出してください。');
367
+ }
368
+ return this.projectInstance;
369
+ }
370
+ attachProjectInstance(instance) {
371
+ this.projectInstance = instance;
372
+ }
373
+ }
374
+ export var RegisteredTokenCountEstimator;
375
+ (function (RegisteredTokenCountEstimator) {
376
+ RegisteredTokenCountEstimator["TIKTOKEN_GPT4O"] = "TIKTOKEN_GPT4O";
377
+ RegisteredTokenCountEstimator["ANTHROPIC_CLAUDE_SONNET_4"] = "ANTHROPIC_CLAUDE_SONNET_4";
378
+ })(RegisteredTokenCountEstimator || (RegisteredTokenCountEstimator = {}));
379
+ export class SerenaConfig extends ToolInclusionDefinition {
380
+ projects;
381
+ guiLogWindowEnabled;
382
+ logLevel;
383
+ traceLspCommunication;
384
+ webDashboard;
385
+ webDashboardOpenOnLaunch;
386
+ toolTimeout;
387
+ loadedCommentedYaml;
388
+ configFilePath;
389
+ jetbrains;
390
+ recordToolUsageStats;
391
+ tokenCountEstimator;
392
+ defaultMaxToolAnswerChars;
393
+ lsSpecificSettings;
394
+ static CONFIG_FILE = 'serena_config.yml';
395
+ static CONFIG_FILE_DOCKER = 'serena_config.docker.yml';
396
+ constructor(init = {}) {
397
+ super(init);
398
+ this.projects = [...(init.projects ?? [])];
399
+ this.guiLogWindowEnabled = init.guiLogWindowEnabled ?? false;
400
+ this.logLevel = init.logLevel ?? 20;
401
+ this.traceLspCommunication = init.traceLspCommunication ?? false;
402
+ this.webDashboard = init.webDashboard ?? true;
403
+ this.webDashboardOpenOnLaunch = init.webDashboardOpenOnLaunch ?? true;
404
+ this.toolTimeout = init.toolTimeout ?? DEFAULT_TOOL_TIMEOUT;
405
+ this.loadedCommentedYaml = init.loadedCommentedYaml;
406
+ this.configFilePath = init.configFilePath ?? null;
407
+ this.jetbrains = init.jetbrains ?? false;
408
+ this.recordToolUsageStats = init.recordToolUsageStats ?? false;
409
+ this.tokenCountEstimator = init.tokenCountEstimator ?? RegisteredTokenCountEstimator.TIKTOKEN_GPT4O;
410
+ this.defaultMaxToolAnswerChars = init.defaultMaxToolAnswerChars ?? 150_000;
411
+ this.lsSpecificSettings = { ...(init.lsSpecificSettings ?? {}) };
412
+ }
413
+ static generateConfigFile(configFilePath) {
414
+ log.info(`Auto-generating Serena configuration file in ${configFilePath}`);
415
+ const template = loadYaml(SERENA_CONFIG_TEMPLATE_FILE, true);
416
+ saveYaml(configFilePath, template, true);
417
+ }
418
+ static determineConfigFilePath() {
419
+ if (isRunningInDocker()) {
420
+ return path.join(REPO_ROOT, this.CONFIG_FILE_DOCKER);
421
+ }
422
+ const configPath = path.join(SERENA_MANAGED_DIR_IN_HOME, this.CONFIG_FILE);
423
+ if (!fs.existsSync(configPath)) {
424
+ const legacy = path.join(REPO_ROOT, this.CONFIG_FILE);
425
+ if (fs.existsSync(legacy)) {
426
+ log.info(`Moving Serena configuration file from ${legacy} to ${configPath}`);
427
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
428
+ fs.renameSync(legacy, configPath);
429
+ }
430
+ }
431
+ return configPath;
432
+ }
433
+ static fromConfigFile(options = {}) {
434
+ const generateIfMissing = options.generateIfMissing ?? true;
435
+ const configFilePath = this.determineConfigFilePath();
436
+ if (!fs.existsSync(configFilePath)) {
437
+ if (!generateIfMissing) {
438
+ throw new Error(`Serena configuration file not found: ${configFilePath}`);
439
+ }
440
+ log.info(`Serena configuration file not found at ${configFilePath}, autogenerating...`);
441
+ this.generateConfigFile(configFilePath);
442
+ }
443
+ log.info(`Loading Serena configuration from ${configFilePath}`);
444
+ const loaded = loadYaml(configFilePath, true);
445
+ const data = yamlToObject(loaded);
446
+ const parsedResult = SERENA_CONFIG_YAML_SCHEMA.safeParse(data);
447
+ if (!parsedResult.success) {
448
+ throw new SerenaConfigError(formatZodIssues(configFilePath, parsedResult.error));
449
+ }
450
+ const parsed = parsedResult.data;
451
+ const projectEntries = parsed.projects;
452
+ const projects = [];
453
+ let numMigrations = 0;
454
+ for (const entry of projectEntries) {
455
+ const rawPath = path.resolve(entry);
456
+ if (!fs.existsSync(rawPath)) {
457
+ log.warn(`Project path ${rawPath} does not exist, skipping.`);
458
+ continue;
459
+ }
460
+ let resolvedPath = rawPath;
461
+ if (fs.statSync(rawPath).isFile()) {
462
+ const migrated = this.migrateOutOfProjectConfigFile(rawPath);
463
+ if (!migrated) {
464
+ continue;
465
+ }
466
+ resolvedPath = migrated;
467
+ numMigrations += 1;
468
+ }
469
+ const configPath = path.join(resolvedPath, ProjectConfig.relPathToProjectYml());
470
+ if (!fs.existsSync(configPath)) {
471
+ log.warn(`Project path ${resolvedPath} does not contain ${ProjectConfig.relPathToProjectYml()}, skipping.`);
472
+ continue;
473
+ }
474
+ try {
475
+ const projectConfig = ProjectConfig.load(resolvedPath);
476
+ projects.push(new RegisteredProject({
477
+ projectRoot: resolvedPath,
478
+ projectConfig
479
+ }));
480
+ }
481
+ catch (error) {
482
+ log.error(`Failed to load project configuration for ${resolvedPath}`, error);
483
+ }
484
+ }
485
+ const config = new SerenaConfig({
486
+ projects,
487
+ guiLogWindowEnabled: isRunningInDocker()
488
+ ? false
489
+ : asBoolean(parsed.gui_log_window ?? parsed.gui_log_window_enabled, false),
490
+ webDashboard: asBoolean(parsed.web_dashboard, true),
491
+ webDashboardOpenOnLaunch: asBoolean(parsed.web_dashboard_open_on_launch, true),
492
+ toolTimeout: parsed.tool_timeout ?? DEFAULT_TOOL_TIMEOUT,
493
+ traceLspCommunication: asBoolean(parsed.trace_lsp_communication, false),
494
+ excludedTools: parsed.excluded_tools,
495
+ includedOptionalTools: parsed.included_optional_tools,
496
+ logLevel: normalizeLogLevel(parsed.log_level ?? parsed.gui_log_level),
497
+ jetbrains: asBoolean(parsed.jetbrains, false),
498
+ recordToolUsageStats: asBoolean(parsed.record_tool_usage_stats, false),
499
+ tokenCountEstimator: normalizeEstimator(parsed.token_count_estimator),
500
+ defaultMaxToolAnswerChars: parsed.default_max_tool_answer_chars ?? 150_000,
501
+ lsSpecificSettings: parsed.ls_specific_settings ?? {},
502
+ loadedCommentedYaml: loaded,
503
+ configFilePath
504
+ });
505
+ if (numMigrations > 0) {
506
+ log.info(`Migrated ${numMigrations} project configurations from legacy format to in-project configuration; re-saving configuration`);
507
+ config.save();
508
+ }
509
+ return config;
510
+ }
511
+ static migrateOutOfProjectConfigFile(filePath) {
512
+ log.info(`Found legacy project configuration file ${filePath}, migrating to in-project configuration.`);
513
+ try {
514
+ const content = loadYaml(filePath);
515
+ if (!content || typeof content !== 'object') {
516
+ throw new Error('Invalid project configuration content');
517
+ }
518
+ const plain = content;
519
+ if (typeof plain.project_name !== 'string' || plain.project_name.length === 0) {
520
+ plain.project_name = path.parse(filePath).name;
521
+ saveYaml(filePath, plain);
522
+ }
523
+ const projectRootValue = plain.project_root;
524
+ if (typeof projectRootValue !== 'string' || projectRootValue.length === 0) {
525
+ throw new Error('Legacy project configuration missing `project_root` field');
526
+ }
527
+ const projectRoot = projectRootValue;
528
+ const destination = path.join(projectRoot, ProjectConfig.relPathToProjectYml());
529
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
530
+ fs.renameSync(filePath, destination);
531
+ return path.resolve(projectRoot);
532
+ }
533
+ catch (error) {
534
+ log.error(`Error migrating configuration file: ${String(error)}`);
535
+ return null;
536
+ }
537
+ }
538
+ get projectPaths() {
539
+ return this.projects.map((project) => project.projectRoot).sort();
540
+ }
541
+ get projectNames() {
542
+ return this.projects.map((project) => project.projectName).sort();
543
+ }
544
+ getProject(projectRootOrName) {
545
+ const byName = this.projects.filter((project) => project.projectName === projectRootOrName);
546
+ if (byName.length === 1 && byName[0].hasProjectInstance()) {
547
+ return byName[0].getProjectInstance();
548
+ }
549
+ if (byName.length > 1) {
550
+ throw new Error(`Multiple projects found with name '${projectRootOrName}'. Please activate it by location instead. Locations: ${byName
551
+ .map((p) => p.projectRoot)
552
+ .join(', ')}`);
553
+ }
554
+ const resolved = path.resolve(projectRootOrName);
555
+ for (const project of this.projects) {
556
+ if (project.matchesRootPath(resolved) && project.hasProjectInstance()) {
557
+ return project.getProjectInstance();
558
+ }
559
+ }
560
+ return undefined;
561
+ }
562
+ addProjectFromPath(projectRoot) {
563
+ const resolved = path.resolve(projectRoot);
564
+ if (!fs.existsSync(resolved)) {
565
+ throw new Error(`Error: Path does not exist: ${resolved}`);
566
+ }
567
+ if (!fs.statSync(resolved).isDirectory()) {
568
+ throw new Error(`Error: Path is not a directory: ${resolved}`);
569
+ }
570
+ for (const project of this.projects) {
571
+ if (project.matchesRootPath(resolved)) {
572
+ throw new Error(`Project with path ${resolved} was already added with name '${project.projectName}'.`);
573
+ }
574
+ }
575
+ const projectConfig = ProjectConfig.load(resolved, true);
576
+ const registered = new RegisteredProject({
577
+ projectRoot: resolved,
578
+ projectConfig
579
+ });
580
+ this.projects.push(registered);
581
+ this.save();
582
+ return registered;
583
+ }
584
+ removeProject(projectName) {
585
+ const index = this.projects.findIndex((project) => project.projectName === projectName);
586
+ if (index === -1) {
587
+ throw new Error(`Project '${projectName}' not found in Serena configuration; valid project names: ${this.projectNames.join(', ')}`);
588
+ }
589
+ this.projects.splice(index, 1);
590
+ this.save();
591
+ }
592
+ save() {
593
+ if (!this.configFilePath) {
594
+ return;
595
+ }
596
+ const yamlData = this.loadedCommentedYaml ?? {};
597
+ const uniqueProjects = Array.from(new Set(this.projects.map((project) => project.projectRoot))).sort();
598
+ assignYamlValue(yamlData, 'projects', uniqueProjects);
599
+ assignYamlValue(yamlData, 'gui_log_window', this.guiLogWindowEnabled);
600
+ assignYamlValue(yamlData, 'web_dashboard', this.webDashboard);
601
+ assignYamlValue(yamlData, 'web_dashboard_open_on_launch', this.webDashboardOpenOnLaunch);
602
+ assignYamlValue(yamlData, 'tool_timeout', this.toolTimeout);
603
+ assignYamlValue(yamlData, 'trace_lsp_communication', this.traceLspCommunication);
604
+ assignYamlValue(yamlData, 'excluded_tools', this.excludedTools);
605
+ assignYamlValue(yamlData, 'included_optional_tools', this.includedOptionalTools);
606
+ assignYamlValue(yamlData, 'jetbrains', this.jetbrains);
607
+ assignYamlValue(yamlData, 'record_tool_usage_stats', this.recordToolUsageStats);
608
+ assignYamlValue(yamlData, 'token_count_estimator', this.tokenCountEstimator);
609
+ assignYamlValue(yamlData, 'default_max_tool_answer_chars', this.defaultMaxToolAnswerChars);
610
+ assignYamlValue(yamlData, 'ls_specific_settings', this.lsSpecificSettings);
611
+ assignYamlValue(yamlData, 'log_level', this.logLevel);
612
+ saveYaml(this.configFilePath, yamlData, isYamlDocument(yamlData));
613
+ }
614
+ }
615
+ export class SerenaConfigError extends Error {
616
+ }
617
+ export function getSerenaManagedInProjectDir(projectRoot) {
618
+ return path.join(projectRoot, SERENA_MANAGED_DIR_NAME);
619
+ }
620
+ export function isRunningInDocker() {
621
+ if (fs.existsSync('/.dockerenv')) {
622
+ return true;
623
+ }
624
+ try {
625
+ const cgroup = fs.readFileSync('/proc/self/cgroup', 'utf-8');
626
+ return cgroup.includes('docker');
627
+ }
628
+ catch {
629
+ return false;
630
+ }
631
+ }
632
+ function formatDefinition(definition) {
633
+ return `ToolInclusionDefinition(excluded=${definition.excludedTools.length}, included=${definition.includedOptionalTools.length})`;
634
+ }
635
+ function normalizeStringIterable(value) {
636
+ if (!value) {
637
+ return [];
638
+ }
639
+ if (Array.isArray(value)) {
640
+ return value.filter((item) => typeof item === 'string');
641
+ }
642
+ if (typeof value === 'string') {
643
+ return [value];
644
+ }
645
+ if (Symbol.iterator in Object(value)) {
646
+ const result = [];
647
+ for (const item of value) {
648
+ if (typeof item === 'string') {
649
+ result.push(item);
650
+ }
651
+ }
652
+ return result;
653
+ }
654
+ return [];
655
+ }
656
+ function formatZodIssues(source, error) {
657
+ const details = error.issues
658
+ .map((issue) => {
659
+ const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
660
+ return `${path}: ${issue.message}`;
661
+ })
662
+ .join('; ');
663
+ return `${source} のスキーマ検証に失敗しました: ${details}`;
664
+ }
665
+ function asBoolean(value, fallback) {
666
+ if (typeof value === 'boolean') {
667
+ return value;
668
+ }
669
+ if (typeof value === 'string') {
670
+ if (value.toLowerCase() === 'true') {
671
+ return true;
672
+ }
673
+ if (value.toLowerCase() === 'false') {
674
+ return false;
675
+ }
676
+ }
677
+ return fallback;
678
+ }
679
+ function normalizeLogLevel(value) {
680
+ if (typeof value === 'number') {
681
+ return value;
682
+ }
683
+ if (typeof value === 'string') {
684
+ const parsed = Number.parseInt(value, 10);
685
+ if (!Number.isNaN(parsed)) {
686
+ return parsed;
687
+ }
688
+ }
689
+ return 20;
690
+ }
691
+ function normalizeEstimator(value) {
692
+ if (typeof value === 'string') {
693
+ const upper = value.toUpperCase();
694
+ if (upper in RegisteredTokenCountEstimator) {
695
+ return RegisteredTokenCountEstimator[upper];
696
+ }
697
+ }
698
+ return RegisteredTokenCountEstimator.TIKTOKEN_GPT4O;
699
+ }
700
+ function isYamlDocument(value) {
701
+ return typeof value === 'object' && value !== null && typeof value.toString === 'function';
702
+ }
703
+ function assignYamlValue(target, key, value) {
704
+ if (isYamlDocument(target) && typeof target.set === 'function') {
705
+ target.set(key, value);
706
+ return;
707
+ }
708
+ target[key] = value;
709
+ }
710
+ function yamlToObject(data) {
711
+ if (isYamlDocument(data) && typeof data.toJSON === 'function') {
712
+ const json = data.toJSON();
713
+ if (json && typeof json === 'object') {
714
+ return { ...json };
715
+ }
716
+ }
717
+ return { ...data };
718
+ }