@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.
- package/LICENSE +22 -0
- package/README.md +244 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +7 -0
- package/dist/devtools/generate_prompt_factory.d.ts +5 -0
- package/dist/devtools/generate_prompt_factory.js +114 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +34 -0
- package/dist/interprompt/index.d.ts +2 -0
- package/dist/interprompt/index.js +1 -0
- package/dist/interprompt/jinja_template.d.ts +10 -0
- package/dist/interprompt/jinja_template.js +174 -0
- package/dist/interprompt/multilang_prompt.d.ts +54 -0
- package/dist/interprompt/multilang_prompt.js +302 -0
- package/dist/interprompt/prompt_factory.d.ts +16 -0
- package/dist/interprompt/prompt_factory.js +189 -0
- package/dist/interprompt/util/class_decorators.d.ts +1 -0
- package/dist/interprompt/util/class_decorators.js +1 -0
- package/dist/interprompt/util/index.d.ts +1 -0
- package/dist/interprompt/util/index.js +1 -0
- package/dist/serena/agent.d.ts +118 -0
- package/dist/serena/agent.js +675 -0
- package/dist/serena/agno.d.ts +111 -0
- package/dist/serena/agno.js +278 -0
- package/dist/serena/analytics.d.ts +24 -0
- package/dist/serena/analytics.js +119 -0
- package/dist/serena/cli.d.ts +9 -0
- package/dist/serena/cli.js +731 -0
- package/dist/serena/code_editor.d.ts +42 -0
- package/dist/serena/code_editor.js +239 -0
- package/dist/serena/config/context_mode.d.ts +41 -0
- package/dist/serena/config/context_mode.js +239 -0
- package/dist/serena/config/serena_config.d.ts +134 -0
- package/dist/serena/config/serena_config.js +718 -0
- package/dist/serena/constants.d.ts +18 -0
- package/dist/serena/constants.js +27 -0
- package/dist/serena/dashboard.d.ts +55 -0
- package/dist/serena/dashboard.js +472 -0
- package/dist/serena/generated/generated_prompt_factory.d.ts +27 -0
- package/dist/serena/generated/generated_prompt_factory.js +42 -0
- package/dist/serena/gui_log_viewer.d.ts +41 -0
- package/dist/serena/gui_log_viewer.js +436 -0
- package/dist/serena/mcp.d.ts +118 -0
- package/dist/serena/mcp.js +904 -0
- package/dist/serena/project.d.ts +62 -0
- package/dist/serena/project.js +321 -0
- package/dist/serena/prompt_factory.d.ts +20 -0
- package/dist/serena/prompt_factory.js +42 -0
- package/dist/serena/resources/config/contexts/agent.yml +8 -0
- package/dist/serena/resources/config/contexts/chatgpt.yml +28 -0
- package/dist/serena/resources/config/contexts/codex.yml +27 -0
- package/dist/serena/resources/config/contexts/context.template.yml +11 -0
- package/dist/serena/resources/config/contexts/desktop-app.yml +17 -0
- package/dist/serena/resources/config/contexts/ide-assistant.yml +26 -0
- package/dist/serena/resources/config/contexts/oaicompat-agent.yml +8 -0
- package/dist/serena/resources/config/internal_modes/jetbrains.yml +15 -0
- package/dist/serena/resources/config/modes/editing.yml +112 -0
- package/dist/serena/resources/config/modes/interactive.yml +11 -0
- package/dist/serena/resources/config/modes/mode.template.yml +7 -0
- package/dist/serena/resources/config/modes/no-onboarding.yml +8 -0
- package/dist/serena/resources/config/modes/onboarding.yml +16 -0
- package/dist/serena/resources/config/modes/one-shot.yml +15 -0
- package/dist/serena/resources/config/modes/planning.yml +15 -0
- package/dist/serena/resources/config/prompt_templates/simple_tool_outputs.yml +75 -0
- package/dist/serena/resources/config/prompt_templates/system_prompt.yml +66 -0
- package/dist/serena/resources/dashboard/dashboard.js +815 -0
- package/dist/serena/resources/dashboard/index.html +314 -0
- package/dist/serena/resources/dashboard/jquery.min.js +3 -0
- package/dist/serena/resources/dashboard/serena-icon-16.png +0 -0
- package/dist/serena/resources/dashboard/serena-icon-32.png +0 -0
- package/dist/serena/resources/dashboard/serena-icon-48.png +0 -0
- package/dist/serena/resources/dashboard/serena-logs-dark-mode.png +0 -0
- package/dist/serena/resources/dashboard/serena-logs.png +0 -0
- package/dist/serena/resources/project.template.yml +67 -0
- package/dist/serena/resources/serena_config.template.yml +85 -0
- package/dist/serena/symbol.d.ts +199 -0
- package/dist/serena/symbol.js +616 -0
- package/dist/serena/text_utils.d.ts +51 -0
- package/dist/serena/text_utils.js +267 -0
- package/dist/serena/tools/cmd_tools.d.ts +31 -0
- package/dist/serena/tools/cmd_tools.js +48 -0
- package/dist/serena/tools/config_tools.d.ts +53 -0
- package/dist/serena/tools/config_tools.js +176 -0
- package/dist/serena/tools/file_tools.d.ts +231 -0
- package/dist/serena/tools/file_tools.js +511 -0
- package/dist/serena/tools/index.d.ts +7 -0
- package/dist/serena/tools/index.js +7 -0
- package/dist/serena/tools/memory_tools.d.ts +60 -0
- package/dist/serena/tools/memory_tools.js +135 -0
- package/dist/serena/tools/symbol_tools.d.ts +165 -0
- package/dist/serena/tools/symbol_tools.js +362 -0
- package/dist/serena/tools/tools_base.d.ts +162 -0
- package/dist/serena/tools/tools_base.js +378 -0
- package/dist/serena/tools/workflow_tools.d.ts +35 -0
- package/dist/serena/tools/workflow_tools.js +161 -0
- package/dist/serena/util/class_decorators.d.ts +7 -0
- package/dist/serena/util/class_decorators.js +37 -0
- package/dist/serena/util/exception.d.ts +8 -0
- package/dist/serena/util/exception.js +53 -0
- package/dist/serena/util/file_system.d.ts +30 -0
- package/dist/serena/util/file_system.js +352 -0
- package/dist/serena/util/general.d.ts +11 -0
- package/dist/serena/util/general.js +42 -0
- package/dist/serena/util/git.d.ts +11 -0
- package/dist/serena/util/git.js +37 -0
- package/dist/serena/util/inspection.d.ts +45 -0
- package/dist/serena/util/inspection.js +221 -0
- package/dist/serena/util/logging.d.ts +46 -0
- package/dist/serena/util/logging.js +205 -0
- package/dist/serena/util/shell.d.ts +21 -0
- package/dist/serena/util/shell.js +95 -0
- package/dist/serena/util/thread.d.ts +23 -0
- package/dist/serena/util/thread.js +88 -0
- package/dist/serena/version.d.ts +1 -0
- package/dist/serena/version.js +23 -0
- package/dist/solidlsp/language_servers/autoload.d.ts +23 -0
- package/dist/solidlsp/language_servers/autoload.js +25 -0
- package/dist/solidlsp/language_servers/bash_language_server.d.ts +10 -0
- package/dist/solidlsp/language_servers/bash_language_server.js +64 -0
- package/dist/solidlsp/language_servers/clangd_language_server.d.ts +13 -0
- package/dist/solidlsp/language_servers/clangd_language_server.js +110 -0
- package/dist/solidlsp/language_servers/clojure_lsp.d.ts +13 -0
- package/dist/solidlsp/language_servers/clojure_lsp.js +137 -0
- package/dist/solidlsp/language_servers/common.d.ts +41 -0
- package/dist/solidlsp/language_servers/common.js +365 -0
- package/dist/solidlsp/language_servers/csharp_language_server.d.ts +21 -0
- package/dist/solidlsp/language_servers/csharp_language_server.js +694 -0
- package/dist/solidlsp/language_servers/dart_language_server.d.ts +10 -0
- package/dist/solidlsp/language_servers/dart_language_server.js +122 -0
- package/dist/solidlsp/language_servers/eclipse_jdtls.d.ts +24 -0
- package/dist/solidlsp/language_servers/eclipse_jdtls.js +671 -0
- package/dist/solidlsp/language_servers/erlang_language_server.d.ts +22 -0
- package/dist/solidlsp/language_servers/erlang_language_server.js +327 -0
- package/dist/solidlsp/language_servers/gopls.d.ts +12 -0
- package/dist/solidlsp/language_servers/gopls.js +59 -0
- package/dist/solidlsp/language_servers/intelephense.d.ts +13 -0
- package/dist/solidlsp/language_servers/intelephense.js +121 -0
- package/dist/solidlsp/language_servers/jedi_server.d.ts +18 -0
- package/dist/solidlsp/language_servers/jedi_server.js +234 -0
- package/dist/solidlsp/language_servers/kotlin_language_server.d.ts +19 -0
- package/dist/solidlsp/language_servers/kotlin_language_server.js +474 -0
- package/dist/solidlsp/language_servers/lua_ls.d.ts +18 -0
- package/dist/solidlsp/language_servers/lua_ls.js +319 -0
- package/dist/solidlsp/language_servers/nixd_language_server.d.ts +17 -0
- package/dist/solidlsp/language_servers/nixd_language_server.js +341 -0
- package/dist/solidlsp/language_servers/pyright_server.d.ts +19 -0
- package/dist/solidlsp/language_servers/pyright_server.js +180 -0
- package/dist/solidlsp/language_servers/r_language_server.d.ts +19 -0
- package/dist/solidlsp/language_servers/r_language_server.js +184 -0
- package/dist/solidlsp/language_servers/ruby_common.d.ts +10 -0
- package/dist/solidlsp/language_servers/ruby_common.js +136 -0
- package/dist/solidlsp/language_servers/ruby_lsp.d.ts +18 -0
- package/dist/solidlsp/language_servers/ruby_lsp.js +230 -0
- package/dist/solidlsp/language_servers/rust_analyzer.d.ts +13 -0
- package/dist/solidlsp/language_servers/rust_analyzer.js +96 -0
- package/dist/solidlsp/language_servers/solargraph.d.ts +18 -0
- package/dist/solidlsp/language_servers/solargraph.js +208 -0
- package/dist/solidlsp/language_servers/sourcekit_lsp.d.ts +24 -0
- package/dist/solidlsp/language_servers/sourcekit_lsp.js +449 -0
- package/dist/solidlsp/language_servers/terraform_ls.d.ts +13 -0
- package/dist/solidlsp/language_servers/terraform_ls.js +139 -0
- package/dist/solidlsp/language_servers/typescript_language_server.d.ts +20 -0
- package/dist/solidlsp/language_servers/typescript_language_server.js +237 -0
- package/dist/solidlsp/language_servers/vts_language_server.d.ts +13 -0
- package/dist/solidlsp/language_servers/vts_language_server.js +121 -0
- package/dist/solidlsp/language_servers/zls.d.ts +20 -0
- package/dist/solidlsp/language_servers/zls.js +254 -0
- package/dist/solidlsp/ls.d.ts +197 -0
- package/dist/solidlsp/ls.js +507 -0
- package/dist/solidlsp/ls_config.d.ts +43 -0
- package/dist/solidlsp/ls_config.js +157 -0
- package/dist/solidlsp/ls_exceptions.d.ts +5 -0
- package/dist/solidlsp/ls_exceptions.js +14 -0
- package/dist/solidlsp/ls_handler.d.ts +54 -0
- package/dist/solidlsp/ls_handler.js +406 -0
- package/dist/solidlsp/ls_request.d.ts +31 -0
- package/dist/solidlsp/ls_request.js +42 -0
- package/dist/solidlsp/ls_types.d.ts +7 -0
- package/dist/solidlsp/ls_types.js +8 -0
- package/dist/solidlsp/lsp_protocol_handler/server.d.ts +61 -0
- package/dist/solidlsp/lsp_protocol_handler/server.js +68 -0
- package/dist/solidlsp/util/subprocess_util.d.ts +6 -0
- package/dist/solidlsp/util/subprocess_util.js +11 -0
- package/dist/solidlsp/util/zip.d.ts +25 -0
- package/dist/solidlsp/util/zip.js +188 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Oraios AI
|
|
4
|
+
Copyright (c) 2025 smart-edit contributors
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# smart-edit
|
|
2
|
+
|
|
3
|
+
smart-edit は、Python 製のエージェント基盤「Serena」(<https://github.com/oraios/serena>) を TypeScript / Node.js 上で動作するよう全面移植したプロジェクトです。MCP (Model Context Protocol) サーバーとして動作し、SolidLSP ベースの言語サーバー管理や Serena 固有のツール群を TypeScript で再構築しています。移植に際しては Python 版と同じディレクトリ構成・ファイル名(拡張子のみ `.ts` / `.tsx`)を維持しつつ、`@modelcontextprotocol/sdk`、`zod` など Node エコシステムに合わせた依存関係やエラーハンドリングを採用しています。
|
|
4
|
+
|
|
5
|
+
## 主な構成
|
|
6
|
+
|
|
7
|
+
| ディレクトリ / ファイル | 概要 |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `src/serena` | エージェント本体、CLI、各種ツール・コンフィグ周りのロジック |
|
|
10
|
+
| `src/solidlsp` | SolidLSP 連携と言語サーバーごとのランタイム管理実装 |
|
|
11
|
+
| `src/serena/resources` | Serena が自動生成・参照する YAML テンプレート群 |
|
|
12
|
+
| `test/` | Vitest によるユニットテスト・スモークテスト |
|
|
13
|
+
| `docs/` | 移植計画、タスク管理、言語サーバー対応状況などの補足資料 |
|
|
14
|
+
| `tsconfig*.json` | ビルド・テスト・Lint 用 TypeScript 設定 |
|
|
15
|
+
|
|
16
|
+
## 環境要件
|
|
17
|
+
|
|
18
|
+
- Node.js >= 20.11.0
|
|
19
|
+
- pnpm (推奨: v8 以降)
|
|
20
|
+
- macOS / Linux / Windows に対応(Windows では `ensureDefaultSubprocessOptions` により `windowsHide` などを自動付与)
|
|
21
|
+
- 言語サーバーの一部は追加のランタイム(`nix`, `rustup`, `gem`, `dotnet`, `go` など)や外部コマンドを必要とします
|
|
22
|
+
|
|
23
|
+
## セットアップ手順
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm install
|
|
27
|
+
# 必要に応じてビルド
|
|
28
|
+
pnpm build
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`pnpm build` を実行すると TypeScript のトランスパイルに加えて `src/serena/resources/` 以下の YAML テンプレート類も `dist/serena/resources/` にコピーされ、`npx` 経由で配布した際にもコンテキスト定義が参照できるようになります。
|
|
32
|
+
|
|
33
|
+
テストや開発時に外部バイナリのダウンロードを抑止したい場合は、`SERENA_SKIP_RUNTIME_INSTALL=1` を設定してください。
|
|
34
|
+
|
|
35
|
+
## 開発用コマンド
|
|
36
|
+
|
|
37
|
+
| コマンド | 説明 |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| `pnpm lint` | ESLint による静的解析 |
|
|
40
|
+
| `pnpm test` | Vitest 実行(ユニット / スモーク) |
|
|
41
|
+
| `pnpm typecheck` | `tsc --noEmit` での型検証 |
|
|
42
|
+
| `pnpm build` | 生成物を `dist/` に出力 |
|
|
43
|
+
| `pnpm format` / `pnpm format:check` | Prettier による整形 |
|
|
44
|
+
|
|
45
|
+
## ユーザーガイド
|
|
46
|
+
|
|
47
|
+
CLI はまだパッケージ化していないため、開発ツリー上では `pnpm exec tsx src/serena/cli.ts <command>` の形式で呼び出します。長期的には `dist/` から `node dist/index.js` での起動も想定しています。
|
|
48
|
+
|
|
49
|
+
### 1. 初期設定フロー
|
|
50
|
+
|
|
51
|
+
1. **smart-edit 管理ディレクトリの生成**
|
|
52
|
+
```bash
|
|
53
|
+
pnpm exec tsx src/serena/cli.ts config edit
|
|
54
|
+
```
|
|
55
|
+
初回実行時は `~/.smart-edit/serena_config.yml` をテンプレートから生成し、既定エディタで開きます。
|
|
56
|
+
|
|
57
|
+
2. **プロジェクト設定 (project.yml) の生成**
|
|
58
|
+
```bash
|
|
59
|
+
pnpm exec tsx src/serena/cli.ts project generate-yml /path/to/project
|
|
60
|
+
```
|
|
61
|
+
言語を手動指定したい場合は `--language <lang>` を付与します。生成された YAML をプロジェクトルートに配置してください。
|
|
62
|
+
|
|
63
|
+
3. **モード / コンテキストの確認とカスタマイズ**
|
|
64
|
+
- 一覧表示: `mode list` / `context list`
|
|
65
|
+
- テンプレートコピー: `mode create --from-internal default-editor` など
|
|
66
|
+
- 編集: `mode edit <name>` / `context edit <name>`
|
|
67
|
+
- カスタム削除: `mode delete <name>` / `context delete <name>`
|
|
68
|
+
いずれも `pnpm exec tsx src/serena/cli.ts` にサブコマンドを続けて実行します。
|
|
69
|
+
|
|
70
|
+
4. **プロンプトテンプレートの更新**
|
|
71
|
+
独自プロンプトを用意する場合は、`src/serena/resources/prompt_templates/` 以下のテンプレートを `~/.smart-edit/prompt_templates/` にコピーして編集し、`prompts` グループコマンドで管理します。
|
|
72
|
+
|
|
73
|
+
### 2. MCP サーバーの起動
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
SERENA_SKIP_RUNTIME_INSTALL=1 pnpm exec tsx src/serena/cli.ts start-mcp-server \
|
|
77
|
+
--project /path/to/project \
|
|
78
|
+
--context ide-assistant \
|
|
79
|
+
--mode default-editor \
|
|
80
|
+
--transport stdio
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
主なオプション:
|
|
84
|
+
|
|
85
|
+
- `--transport`: `stdio` (既定) / `sse` / `streamable-http` から選択
|
|
86
|
+
- `--enable-web-dashboard`, `--enable-gui-log-window`: Config の設定を一時的に上書き
|
|
87
|
+
- `--log-level`, `--trace-lsp-communication`: ログ詳細度や LSP トレースの制御
|
|
88
|
+
- `--tool-timeout`: ツール実行のタイムアウト秒数
|
|
89
|
+
- `--instructions-override`: MCP クライアントに渡す初期インストラクションをカスタム指定
|
|
90
|
+
|
|
91
|
+
サーバー起動後は、必要に応じてダッシュボード (`--enable-web-dashboard`) や GUI ログビューア (`--enable-gui-log-window`) を利用できます。GUI ログは `GuiLogViewer` 経由でブラウザが自動起動し、`logs/` ディレクトリにも出力されます。
|
|
92
|
+
|
|
93
|
+
#### MCP クライアント(Codex など)からの接続例
|
|
94
|
+
|
|
95
|
+
`npx` で公開パッケージを取得する場合、`mcp_servers.toml` の設定キーを `smart-edit` に合わせてください。
|
|
96
|
+
|
|
97
|
+
```toml
|
|
98
|
+
[mcp_servers.smart-edit]
|
|
99
|
+
command = "npx"
|
|
100
|
+
args = ["-y", "@nogataka/smart-edit@latest", "smart-edit", "start-mcp-server", "--context", "codex", "--transport", "stdio"]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
CLI 側の `--context` や `--mode` は必要に応じて追加してください。`smart-edit` コマンドは `package.json` の `bin.smart-edit` で `./dist/cli.js` にマッピングされています。
|
|
104
|
+
|
|
105
|
+
### 3. プロジェクト / ツール管理
|
|
106
|
+
|
|
107
|
+
- `project generate-yml`: プロジェクト設定 YAML の生成(上記手順参照)
|
|
108
|
+
- `tools list`: 有効化されているツールを確認
|
|
109
|
+
- `tools describe <toolName>`: ツールごとの説明・必須マーカーを参照
|
|
110
|
+
- `project` グループのその他サブコマンド (`index`, `health-check` など) は現時点では未実装であり、実装予定時期を CLI がメッセージとして返します。
|
|
111
|
+
|
|
112
|
+
### 4. 主要な環境変数
|
|
113
|
+
|
|
114
|
+
| 変数 | 用途 |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `SERENA_SKIP_RUNTIME_INSTALL` | `1` の場合、言語サーバーなど外部バイナリの自動インストールをスキップ |
|
|
117
|
+
| `SERENA_ASSUME_<LANG>` | 各言語サーバーで既存ランタイムを仮定(例: `SERENA_ASSUME_GOPLS`, `SERENA_ASSUME_NIXD`, `SERENA_ASSUME_SOURCEKIT`)|
|
|
118
|
+
| `SERENA_<LANG>_PATH` | バイナリパスの明示指定(例: `SERENA_RUBY_BINARY`, `SERENA_ZLS_PATH`)|
|
|
119
|
+
| `EDITOR` | `mode edit` 等で使用する既定エディタ |
|
|
120
|
+
| `SERENA_SKIP_EDITOR` | `1` の場合、CLI が自動でエディタを開かない |
|
|
121
|
+
|
|
122
|
+
環境変数の完全な一覧や詳細は `src/solidlsp/language_servers/*.ts` および `src/serena/config/*.ts` を参照してください。
|
|
123
|
+
|
|
124
|
+
### 5. 言語サーバー対応状況 (2025-10-27 時点)
|
|
125
|
+
|
|
126
|
+
- **対応済み**: Bash, C#, C/C++, Clojure, Dart, Go, Java, Kotlin, Lua, Nix, PHP, Python (Jedi / Pyright), R, Ruby (Ruby LSP / Solargraph), Rust, Swift, Terraform, TypeScript / JavaScript, Vue (VTS), Zig など
|
|
127
|
+
- **除外**: AL Language Server, elixir_tools, OmniSharp(TypeScript 版では対象外)
|
|
128
|
+
- **未対応**: なし(Python 版と同じ範囲をカバー)
|
|
129
|
+
|
|
130
|
+
最新の対応表やサブタスク進捗は `docs/ts_port_task_tracker.md` を参照してください。
|
|
131
|
+
|
|
132
|
+
### Claude Code への接続手順
|
|
133
|
+
|
|
134
|
+
Claude Code ではプロジェクトごとに MCP サーバーを追加します。smart-edit(Serena の TypeScript ポート)を使う場合でも手順はほぼ同じです。
|
|
135
|
+
|
|
136
|
+
1. プロジェクトのルートディレクトリで次のコマンドを実行します。
|
|
137
|
+
```bash
|
|
138
|
+
claude mcp add smart-edit -- npx -y @nogataka/smart-edit@latest smart-edit start-mcp-server --context ide-assistant --project "$(pwd)" --transport stdio
|
|
139
|
+
```
|
|
140
|
+
- `smart-edit` が Claude Code 上でのサーバー識別子になります(任意で変更可)。
|
|
141
|
+
- `--context ide-assistant` は Claude Code 向けに最適化した設定です。用途に応じて `desktop-app` など他のコンテキストへ切り替えても構いません。
|
|
142
|
+
- `--project` にはコードベースのルートパスを指定してください。`$(pwd)` を使うとカレントディレクトリをそのまま渡せます。
|
|
143
|
+
- `--transport stdio` は標準入出力経由での通信を指定します。Claude Code は stdio を用いるため、この指定が必要です。
|
|
144
|
+
|
|
145
|
+
2. 追加が完了したら、`claude mcp list` で登録済みサーバーを確認できます。必要に応じて `claude mcp remove smart-edit` で削除し、再登録してください。
|
|
146
|
+
|
|
147
|
+
3. 会話を開始すると Claude が smart-edit の初期インストラクション(`initial_instructions`)を自動で読み込みます。もし読み込みに失敗した場合は、Claude に「smart-edit の初期インストラクションを読んで」と依頼するか、`/mcp__smart-edit__initial_instructions` を実行してください(初期インストラクションツールを有効化している場合)。
|
|
148
|
+
|
|
149
|
+
4. コンテキストやモードをカスタマイズしたい場合は、`~/.smart-edit/context/` 配下に YAML を配置した上で `--context` / `--mode` オプションで指定できます。
|
|
150
|
+
|
|
151
|
+
### Codex (OpenAI CLI) への接続手順
|
|
152
|
+
|
|
153
|
+
Codex CLI はグローバル設定で MCP サーバーを追加します。`~/.codex/config.toml` に以下のエントリを追加してください。
|
|
154
|
+
|
|
155
|
+
```toml
|
|
156
|
+
[mcp_servers.smart-edit]
|
|
157
|
+
command = "npx"
|
|
158
|
+
args = ["-y", "@nogataka/smart-edit@latest", "smart-edit", "start-mcp-server", "--context", "codex", "--transport", "stdio"]
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- `--context codex` は Codex 特有の I/O や制約に対応するための設定です。Codex では `codex` コンテキストを使うことでツールの動作が最適化されます。
|
|
162
|
+
- Codex 起動後、チャット内で「smart-edit で現在のディレクトリをプロジェクトとしてアクティブ化して」と依頼してください。プロジェクトをアクティブ化しないとツールを利用できません。
|
|
163
|
+
- ダッシュボード (`--enable-web-dashboard`) を利用する場合、Codex のサンドボックスではブラウザが自動起動しないことがあります。その場合は `http://localhost:24282/dashboard/index.html` (ポートは環境により変わります)へブラウザでアクセスしてください。
|
|
164
|
+
- Codex の UI ではツール実行が `failed` と表示されることがありますが、実際には処理が成功しているケースが多いです。ログ (`~/.codex/log/codex-tui.log`) を併せて確認してください。
|
|
165
|
+
|
|
166
|
+
これらの設定を行うことで、Claude Code / Codex ともに `npx -y @nogataka/smart-edit@latest` を通して smart-edit MCP サーバーを利用できます。`npm publish` 後に `@nogataka/smart-edit` のバージョンを最新に保つようご注意ください。
|
|
167
|
+
|
|
168
|
+
### Claude Desktop への接続手順
|
|
169
|
+
|
|
170
|
+
Claude Desktop (Windows/macOS) では `claude_desktop_config.json` に MCP サーバー設定を追加します。メニューの **File → Settings → Developer → MCP Servers → Edit Config** を開き、以下のいずれかの設定を追記してください。識別子は例として `smart-edit` を使用しています。
|
|
171
|
+
|
|
172
|
+
- **npm (npx) 経由で最新版を利用する場合**
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"mcpServers": {
|
|
176
|
+
"smart-edit": {
|
|
177
|
+
"command": "npx",
|
|
178
|
+
"args": ["-y", "@nogataka/smart-edit@latest", "smart-edit", "start-mcp-server", "--context", "desktop-app", "--transport", "stdio"]
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- **ローカルのクローンから開発ビルドを直接利用する場合**
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"mcpServers": {
|
|
188
|
+
"smart-edit": {
|
|
189
|
+
"command": "node",
|
|
190
|
+
"args": ["/absolute/path/to/smart-edit/dist/cli.js", "start-mcp-server", "--context", "desktop-app", "--transport", "stdio", "--project", "/absolute/path/to/project"]
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
- `dist/cli.js` を呼び出す前に `pnpm build` を実行し、`dist/` に成果物を生成しておいてください。
|
|
196
|
+
- `--project` は必要に応じて省略できます(省略時は起動後にチャットからプロジェクトをアクティブ化します)。
|
|
197
|
+
|
|
198
|
+
- **Docker イメージを使う場合(PoC)**
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"mcpServers": {
|
|
202
|
+
"smart-edit": {
|
|
203
|
+
"command": "docker",
|
|
204
|
+
"args": [
|
|
205
|
+
"run", "--rm", "-i",
|
|
206
|
+
"-v", "/path/to/your/projects:/workspace/projects",
|
|
207
|
+
"ghcr.io/nogataka/smart-edit:latest",
|
|
208
|
+
"smart-edit", "start-mcp-server", "--context", "desktop-app", "--transport", "stdio"
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
- 公式イメージを公開する場合はリポジトリ URL やボリューム設定を用途に合わせて変更してください。
|
|
215
|
+
|
|
216
|
+
#### 注意事項
|
|
217
|
+
- Windows でパスを指定する場合はバックスラッシュを二重にする (`\\`) か、スラッシュ (`/`) を利用してください。
|
|
218
|
+
- 設定を保存したら Claude Desktop を完全終了(システムトレイのアイコンも終了)し、再起動すると smart-edit のツールが利用可能になります。
|
|
219
|
+
- `desktop-app` コンテキストは Claude Desktop 向けにチューニングされています。必要に応じて `~/.smart-edit/contexts/` 配下に自作コンテキストを配置し、`--context` で差し替え可能です。
|
|
220
|
+
- ダッシュボードを利用したい場合は Config 側で `web_dashboard: true` を有効にしてください。ブラウザが自動起動しない場合は `http://localhost:24282/dashboard/index.html` へアクセスできます。
|
|
221
|
+
- MCP サーバーを終了するにはチャットを閉じるだけでなく、別コンソールから `smart-edit` プロセスを停止するか、CLI のログを確認しながら Ctrl+C で停止してください。
|
|
222
|
+
|
|
223
|
+
Claude Desktop の MCP 設定については [公式クイックスタート](https://modelcontextprotocol.io/quickstart/user) も参考になります。
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
## トラブルシューティング
|
|
227
|
+
|
|
228
|
+
- 言語サーバーのダウンロードが失敗する場合: ネットワーク設定を確認し、必要に応じて `SERENA_ASSUME_<LANG>` でローカルバイナリを指示します。
|
|
229
|
+
- macOS で `pnpm test` 実行時に `EPERM: operation not permitted, listen` が発生する場合: `sudo` での実行か、環境ポート (`SERENA_DASHBOARD_PORT` など) の明示指定をご検討ください。
|
|
230
|
+
- Windows でコンソールウィンドウが一瞬開く場合: 既に `ensureDefaultSubprocessOptions` が `windowsHide` を設定しますが、`SERENA_VERBOSE_PROCESS=1` を付与すると詳細ログで状況確認ができます。
|
|
231
|
+
|
|
232
|
+
## 参考ドキュメント
|
|
233
|
+
|
|
234
|
+
- `docs/ts_port_implementation_plan.md`: TypeScript 移植全体の方針と決定事項
|
|
235
|
+
- `docs/ts_port_task_tracker.md`: サブタスク進捗・言語サーバー対応表
|
|
236
|
+
- `docs/lsp_server_execution_methods.md` ほか: SolidLSP 周辺仕様の補足資料
|
|
237
|
+
|
|
238
|
+
ドキュメントとコードは常に同期させる方針です。新しい機能やタスクを追加した際は、README と `docs/` の両方を更新してください。
|
|
239
|
+
|
|
240
|
+
## ライセンス
|
|
241
|
+
|
|
242
|
+
- 本リポジトリ(smart-edit)は MIT ライセンスで提供されます。詳細は [`LICENSE`](./LICENSE) を参照してください。
|
|
243
|
+
- Serena の Python 版 (<https://github.com/oraios/serena>) も MIT ライセンスで公開されています。本 TypeScript ポートは Python 版の実装・ドキュメントをベースに再構築しており、オリジナルの著作権表示(Oraios AI)を含めた形で継承しています。
|
|
244
|
+
- smart-edit への追加実装分は「smart-edit contributors」として明記しています。コントリビューションの際は、MIT ライセンスの条件に同意の上で PR を提出してください。
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { autogeneratePromptFactoryModule } from '../interprompt/prompt_factory.js';
|
|
4
|
+
import { PROMPT_TEMPLATES_DIR_INTERNAL, REPO_ROOT } from '../serena/constants.js';
|
|
5
|
+
import { createSerenaLogger } from '../serena/util/logging.js';
|
|
6
|
+
const { logger } = createSerenaLogger({ name: 'devtools.generate_prompt_factory' });
|
|
7
|
+
function resolveTargetPath(targetPath) {
|
|
8
|
+
if (targetPath) {
|
|
9
|
+
return path.resolve(targetPath);
|
|
10
|
+
}
|
|
11
|
+
return path.join(REPO_ROOT, 'src', 'serena', 'generated', 'generated_prompt_factory.ts');
|
|
12
|
+
}
|
|
13
|
+
function resolvePromptsDir(promptsDir) {
|
|
14
|
+
if (promptsDir === undefined) {
|
|
15
|
+
return PROMPT_TEMPLATES_DIR_INTERNAL;
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(promptsDir)) {
|
|
18
|
+
return promptsDir.map((dir) => path.resolve(dir));
|
|
19
|
+
}
|
|
20
|
+
return path.resolve(promptsDir);
|
|
21
|
+
}
|
|
22
|
+
export function generatePromptFactory(options = {}) {
|
|
23
|
+
const promptsDir = resolvePromptsDir(options.promptsDir);
|
|
24
|
+
const targetPath = resolveTargetPath(options.targetPath);
|
|
25
|
+
autogeneratePromptFactoryModule(promptsDir, targetPath);
|
|
26
|
+
logger.info('Prompt factory generated', {
|
|
27
|
+
promptsDir,
|
|
28
|
+
targetPath
|
|
29
|
+
});
|
|
30
|
+
return targetPath;
|
|
31
|
+
}
|
|
32
|
+
function parseCliArguments(argv) {
|
|
33
|
+
const result = {};
|
|
34
|
+
const promptsDirs = [];
|
|
35
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
36
|
+
const arg = argv[index];
|
|
37
|
+
switch (arg) {
|
|
38
|
+
case '--prompts-dir':
|
|
39
|
+
case '-p': {
|
|
40
|
+
const value = argv[index + 1];
|
|
41
|
+
if (!value) {
|
|
42
|
+
throw new Error('Missing value for --prompts-dir option');
|
|
43
|
+
}
|
|
44
|
+
promptsDirs.push(value);
|
|
45
|
+
index += 1;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case '--target':
|
|
49
|
+
case '-t': {
|
|
50
|
+
const value = argv[index + 1];
|
|
51
|
+
if (!value) {
|
|
52
|
+
throw new Error('Missing value for --target option');
|
|
53
|
+
}
|
|
54
|
+
result.targetPath = value;
|
|
55
|
+
index += 1;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case '--help':
|
|
59
|
+
case '-h': {
|
|
60
|
+
result.showHelp = true;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
default: {
|
|
64
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (promptsDirs.length === 1) {
|
|
69
|
+
result.promptsDir = promptsDirs[0];
|
|
70
|
+
}
|
|
71
|
+
else if (promptsDirs.length > 1) {
|
|
72
|
+
result.promptsDir = promptsDirs;
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
function printHelp() {
|
|
77
|
+
const relativeTarget = path.relative(REPO_ROOT, path.join(REPO_ROOT, 'src', 'serena', 'generated', 'generated_prompt_factory.ts'));
|
|
78
|
+
logger.info([
|
|
79
|
+
'Usage: pnpm prompts:generate [options]',
|
|
80
|
+
'',
|
|
81
|
+
'Options:',
|
|
82
|
+
' -p, --prompts-dir <path> 生成に使用するプロンプトテンプレートのディレクトリを追加(複数指定可)',
|
|
83
|
+
' -t, --target <path> 出力先モジュールのパスを明示的に指定',
|
|
84
|
+
' -h, --help このヘルプを表示',
|
|
85
|
+
'',
|
|
86
|
+
`既定値: promptsDir=${PROMPT_TEMPLATES_DIR_INTERNAL}, target=${relativeTarget}`
|
|
87
|
+
].join('\n'));
|
|
88
|
+
}
|
|
89
|
+
function isExecutedAsMainModule(metaUrl) {
|
|
90
|
+
const currentFilePath = fileURLToPath(metaUrl);
|
|
91
|
+
const entryFile = process.argv[1];
|
|
92
|
+
if (!entryFile) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return path.resolve(entryFile) === currentFilePath;
|
|
96
|
+
}
|
|
97
|
+
if (isExecutedAsMainModule(import.meta.url)) {
|
|
98
|
+
const argv = process.argv.slice(2);
|
|
99
|
+
try {
|
|
100
|
+
const parsed = parseCliArguments(argv);
|
|
101
|
+
if (parsed.showHelp) {
|
|
102
|
+
printHelp();
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
generatePromptFactory({
|
|
106
|
+
promptsDir: parsed.promptsDir,
|
|
107
|
+
targetPath: parsed.targetPath
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
logger.error('Failed to generate prompt factory', error);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export * from './serena/constants.js';
|
|
2
|
+
export * from './serena/gui_log_viewer.js';
|
|
3
|
+
export * from './serena/util/exception.js';
|
|
4
|
+
export * from './serena/util/general.js';
|
|
5
|
+
export * from './serena/util/logging.js';
|
|
6
|
+
export * from './serena/util/file_system.js';
|
|
7
|
+
export * from './serena/util/shell.js';
|
|
8
|
+
export * from './serena/util/git.js';
|
|
9
|
+
export * from './serena/util/inspection.js';
|
|
10
|
+
export * from './serena/util/thread.js';
|
|
11
|
+
export * from './serena/util/class_decorators.js';
|
|
12
|
+
export * from './serena/config/serena_config.js';
|
|
13
|
+
export * from './serena/config/context_mode.js';
|
|
14
|
+
export * from './serena/analytics.js';
|
|
15
|
+
export * from './serena/dashboard.js';
|
|
16
|
+
export * from './serena/mcp.js';
|
|
17
|
+
export * from './serena/cli.js';
|
|
18
|
+
export * from './serena/agno.js';
|
|
19
|
+
export * from './serena/prompt_factory.js';
|
|
20
|
+
export * from './serena/code_editor.js';
|
|
21
|
+
export * from './serena/symbol.js';
|
|
22
|
+
export * from './serena/version.js';
|
|
23
|
+
export * from './serena/agent.js';
|
|
24
|
+
export * from './serena/project.js';
|
|
25
|
+
export * from './solidlsp/ls_config.js';
|
|
26
|
+
export * from './solidlsp/ls.js';
|
|
27
|
+
export * from './solidlsp/ls_handler.js';
|
|
28
|
+
export * from './solidlsp/ls_request.js';
|
|
29
|
+
export * from './solidlsp/ls_exceptions.js';
|
|
30
|
+
export * from './solidlsp/lsp_protocol_handler/server.js';
|
|
31
|
+
export * from './solidlsp/ls_types.js';
|
|
32
|
+
export * from './solidlsp/util/subprocess_util.js';
|
|
33
|
+
export * from './solidlsp/util/zip.js';
|
|
34
|
+
import './solidlsp/language_servers/autoload.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export * from './serena/constants.js';
|
|
2
|
+
export * from './serena/gui_log_viewer.js';
|
|
3
|
+
export * from './serena/util/exception.js';
|
|
4
|
+
export * from './serena/util/general.js';
|
|
5
|
+
export * from './serena/util/logging.js';
|
|
6
|
+
export * from './serena/util/file_system.js';
|
|
7
|
+
export * from './serena/util/shell.js';
|
|
8
|
+
export * from './serena/util/git.js';
|
|
9
|
+
export * from './serena/util/inspection.js';
|
|
10
|
+
export * from './serena/util/thread.js';
|
|
11
|
+
export * from './serena/util/class_decorators.js';
|
|
12
|
+
export * from './serena/config/serena_config.js';
|
|
13
|
+
export * from './serena/config/context_mode.js';
|
|
14
|
+
export * from './serena/analytics.js';
|
|
15
|
+
export * from './serena/dashboard.js';
|
|
16
|
+
export * from './serena/mcp.js';
|
|
17
|
+
export * from './serena/cli.js';
|
|
18
|
+
export * from './serena/agno.js';
|
|
19
|
+
export * from './serena/prompt_factory.js';
|
|
20
|
+
export * from './serena/code_editor.js';
|
|
21
|
+
export * from './serena/symbol.js';
|
|
22
|
+
export * from './serena/version.js';
|
|
23
|
+
export * from './serena/agent.js';
|
|
24
|
+
export * from './serena/project.js';
|
|
25
|
+
export * from './solidlsp/ls_config.js';
|
|
26
|
+
export * from './solidlsp/ls.js';
|
|
27
|
+
export * from './solidlsp/ls_handler.js';
|
|
28
|
+
export * from './solidlsp/ls_request.js';
|
|
29
|
+
export * from './solidlsp/ls_exceptions.js';
|
|
30
|
+
export * from './solidlsp/lsp_protocol_handler/server.js';
|
|
31
|
+
export * from './solidlsp/ls_types.js';
|
|
32
|
+
export * from './solidlsp/util/subprocess_util.js';
|
|
33
|
+
export * from './solidlsp/util/zip.js';
|
|
34
|
+
import './solidlsp/language_servers/autoload.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { autogeneratePromptFactoryModule, PromptFactoryBase } from './prompt_factory.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface ParameterizedTemplateInterface {
|
|
2
|
+
getParameters(): string[];
|
|
3
|
+
}
|
|
4
|
+
export declare class JinjaTemplate implements ParameterizedTemplateInterface {
|
|
5
|
+
private readonly template;
|
|
6
|
+
private readonly parameters;
|
|
7
|
+
constructor(templateString: string);
|
|
8
|
+
render(params: Record<string, unknown>): string;
|
|
9
|
+
getParameters(): string[];
|
|
10
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import nunjucks from 'nunjucks';
|
|
2
|
+
const KEYWORDS = new Set([
|
|
3
|
+
'and',
|
|
4
|
+
'as',
|
|
5
|
+
'block',
|
|
6
|
+
'caller',
|
|
7
|
+
'call',
|
|
8
|
+
'do',
|
|
9
|
+
'else',
|
|
10
|
+
'elseif',
|
|
11
|
+
'endblock',
|
|
12
|
+
'endfor',
|
|
13
|
+
'endif',
|
|
14
|
+
'extends',
|
|
15
|
+
'filter',
|
|
16
|
+
'for',
|
|
17
|
+
'from',
|
|
18
|
+
'if',
|
|
19
|
+
'import',
|
|
20
|
+
'in',
|
|
21
|
+
'include',
|
|
22
|
+
'is',
|
|
23
|
+
'macro',
|
|
24
|
+
'not',
|
|
25
|
+
'or',
|
|
26
|
+
'recursive',
|
|
27
|
+
'set',
|
|
28
|
+
'true',
|
|
29
|
+
'false',
|
|
30
|
+
'none',
|
|
31
|
+
'with'
|
|
32
|
+
]);
|
|
33
|
+
const BUILTIN_IDENTIFIERS = new Set(['loop', 'range', 'cycler', 'joiner', 'namespace', 'super']);
|
|
34
|
+
function stripStringLiterals(expression) {
|
|
35
|
+
return expression.replace(/(['"]).*?(?<!\\)\1/gs, ' ');
|
|
36
|
+
}
|
|
37
|
+
function normalizeIdentifier(token) {
|
|
38
|
+
if (token.length === 0) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
if (/^\d/.test(token)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const base = token.split('.')[0] ?? token;
|
|
45
|
+
if (KEYWORDS.has(base) || BUILTIN_IDENTIFIERS.has(base)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return base;
|
|
49
|
+
}
|
|
50
|
+
function collectIdentifiersFromExpression(expression, defined, sink) {
|
|
51
|
+
const sanitized = stripStringLiterals(expression);
|
|
52
|
+
const tokens = sanitized.split(/[^A-Za-z0-9_.]+/).map((token) => token.trim()).filter(Boolean);
|
|
53
|
+
for (const token of tokens) {
|
|
54
|
+
const normalized = normalizeIdentifier(token);
|
|
55
|
+
if (!normalized) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (isIdentifierDefined(normalized, defined)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
sink.add(normalized);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function isIdentifierDefined(identifier, scopes) {
|
|
65
|
+
for (let index = scopes.length - 1; index >= 0; index -= 1) {
|
|
66
|
+
if (scopes[index]?.has(identifier) === true) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
function addIdentifiersToCurrentScope(text, scopes) {
|
|
73
|
+
const scope = scopes[scopes.length - 1];
|
|
74
|
+
if (!scope) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const sanitized = stripStringLiterals(text);
|
|
78
|
+
const tokens = sanitized.split(/[^A-Za-z0-9_.]+/).map((token) => token.trim()).filter(Boolean);
|
|
79
|
+
for (const token of tokens) {
|
|
80
|
+
const normalized = normalizeIdentifier(token);
|
|
81
|
+
if (!normalized) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
scope.add(normalized);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function analyseStatement(statement, scopes, sink) {
|
|
88
|
+
const trimmed = statement.trim();
|
|
89
|
+
if (!trimmed) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (trimmed.startsWith('for ')) {
|
|
93
|
+
const match = /^for\s+(.+?)\s+in\s+(.+?)$/s.exec(trimmed);
|
|
94
|
+
if (match) {
|
|
95
|
+
const targetSection = match[1] ?? '';
|
|
96
|
+
const expression = match[2] ?? '';
|
|
97
|
+
const loopScope = new Set();
|
|
98
|
+
const targets = targetSection.split(',').map((item) => item.trim()).filter(Boolean);
|
|
99
|
+
for (const target of targets) {
|
|
100
|
+
const normalized = normalizeIdentifier(target);
|
|
101
|
+
if (normalized) {
|
|
102
|
+
loopScope.add(normalized);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
scopes.push(loopScope);
|
|
106
|
+
collectIdentifiersFromExpression(expression, scopes, sink);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (trimmed.startsWith('endfor')) {
|
|
111
|
+
if (scopes.length > 1) {
|
|
112
|
+
scopes.pop();
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (trimmed.startsWith('set ')) {
|
|
117
|
+
const match = /^set\s+(.+?)=(.+)$/s.exec(trimmed);
|
|
118
|
+
if (match) {
|
|
119
|
+
const targetSection = match[1] ?? '';
|
|
120
|
+
addIdentifiersToCurrentScope(targetSection, scopes);
|
|
121
|
+
const expression = match[2] ?? '';
|
|
122
|
+
collectIdentifiersFromExpression(expression, scopes, sink);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
collectIdentifiersFromExpression(trimmed, scopes, sink);
|
|
127
|
+
}
|
|
128
|
+
function collectTemplateParameters(templateString) {
|
|
129
|
+
const sink = new Set();
|
|
130
|
+
const scopes = [new Set(BUILTIN_IDENTIFIERS)];
|
|
131
|
+
const tagPattern = /({%[\s\S]*?%}|{{[\s\S]*?}})/g;
|
|
132
|
+
let match;
|
|
133
|
+
while ((match = tagPattern.exec(templateString)) !== null) {
|
|
134
|
+
const token = match[0] ?? '';
|
|
135
|
+
if (token.startsWith('{{')) {
|
|
136
|
+
const expression = token.slice(2, -2);
|
|
137
|
+
collectIdentifiersFromExpression(expression, scopes, sink);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (token.startsWith('{%')) {
|
|
141
|
+
const statement = token.slice(2, -2);
|
|
142
|
+
analyseStatement(statement, scopes, sink);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return Array.from(sink).sort();
|
|
146
|
+
}
|
|
147
|
+
class JinjaEnvProvider {
|
|
148
|
+
static instance = null;
|
|
149
|
+
static getEnv() {
|
|
150
|
+
this.instance ??= new nunjucks.Environment(undefined, {
|
|
151
|
+
autoescape: false,
|
|
152
|
+
throwOnUndefined: false,
|
|
153
|
+
trimBlocks: false,
|
|
154
|
+
lstripBlocks: false
|
|
155
|
+
});
|
|
156
|
+
return this.instance;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export class JinjaTemplate {
|
|
160
|
+
template;
|
|
161
|
+
parameters;
|
|
162
|
+
constructor(templateString) {
|
|
163
|
+
const normalized = templateString.trim();
|
|
164
|
+
const env = JinjaEnvProvider.getEnv();
|
|
165
|
+
this.template = new nunjucks.Template(normalized, env, undefined, true);
|
|
166
|
+
this.parameters = collectTemplateParameters(normalized);
|
|
167
|
+
}
|
|
168
|
+
render(params) {
|
|
169
|
+
return this.template.render(params);
|
|
170
|
+
}
|
|
171
|
+
getParameters() {
|
|
172
|
+
return [...this.parameters];
|
|
173
|
+
}
|
|
174
|
+
}
|