chocomint 1.0.0

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 (158) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +224 -0
  4. data/THIRD_PARTY_LICENSES.md +45 -0
  5. data/bin/chocomint +37 -0
  6. data/bin/chocomint-console-worker +35 -0
  7. data/bin/chocomint-server +43 -0
  8. data/config/config.yml +37 -0
  9. data/lib/chocomint/config.rb +74 -0
  10. data/lib/chocomint/console/conpty.rb +296 -0
  11. data/lib/chocomint/console/server.rb +164 -0
  12. data/lib/chocomint/console/worker.rb +88 -0
  13. data/lib/chocomint/console/worker_process.rb +254 -0
  14. data/lib/chocomint/edit_git_handlers.rb +397 -0
  15. data/lib/chocomint/edit_handlers.rb +696 -0
  16. data/lib/chocomint/edit_html.rb +9 -0
  17. data/lib/chocomint/errors.rb +18 -0
  18. data/lib/chocomint/factory.rb +145 -0
  19. data/lib/chocomint/llm/base_client.rb +113 -0
  20. data/lib/chocomint/llm/chat_client.rb +206 -0
  21. data/lib/chocomint/llm/primary_client.rb +320 -0
  22. data/lib/chocomint/llm/verifier_client.rb +127 -0
  23. data/lib/chocomint/logger/sqlite_logger.rb +245 -0
  24. data/lib/chocomint/orchestrator.rb +175 -0
  25. data/lib/chocomint/planner.rb +241 -0
  26. data/lib/chocomint/security/path_guard.rb +84 -0
  27. data/lib/chocomint/server.rb +863 -0
  28. data/lib/chocomint/tools/append_file.rb +50 -0
  29. data/lib/chocomint/tools/base.rb +88 -0
  30. data/lib/chocomint/tools/delete_file.rb +42 -0
  31. data/lib/chocomint/tools/edit.rb +65 -0
  32. data/lib/chocomint/tools/fetch_url.rb +156 -0
  33. data/lib/chocomint/tools/file_info.rb +54 -0
  34. data/lib/chocomint/tools/glob.rb +58 -0
  35. data/lib/chocomint/tools/grep.rb +78 -0
  36. data/lib/chocomint/tools/list_dir.rb +41 -0
  37. data/lib/chocomint/tools/ls.rb +51 -0
  38. data/lib/chocomint/tools/make_dir.rb +44 -0
  39. data/lib/chocomint/tools/read_file.rb +45 -0
  40. data/lib/chocomint/tools/registry.rb +29 -0
  41. data/lib/chocomint/tools/run_command.rb +96 -0
  42. data/lib/chocomint/tools/shell.rb +169 -0
  43. data/lib/chocomint/tools/write_file.rb +54 -0
  44. data/lib/chocomint/validator/machine_validator.rb +65 -0
  45. data/lib/chocomint/validator/semantic_validator.rb +52 -0
  46. data/lib/chocomint.rb +51 -0
  47. data/public/edit/edit.css +414 -0
  48. data/public/edit/edit.js +2137 -0
  49. data/public/edit/index.html +251 -0
  50. data/public/vendor/monaco/LICENSE.txt +23 -0
  51. data/public/vendor/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  52. data/public/vendor/monaco/vs/base/worker/workerMain.js +31 -0
  53. data/public/vendor/monaco/vs/basic-languages/abap/abap.js +10 -0
  54. data/public/vendor/monaco/vs/basic-languages/apex/apex.js +10 -0
  55. data/public/vendor/monaco/vs/basic-languages/azcli/azcli.js +10 -0
  56. data/public/vendor/monaco/vs/basic-languages/bat/bat.js +10 -0
  57. data/public/vendor/monaco/vs/basic-languages/bicep/bicep.js +11 -0
  58. data/public/vendor/monaco/vs/basic-languages/cameligo/cameligo.js +10 -0
  59. data/public/vendor/monaco/vs/basic-languages/clojure/clojure.js +10 -0
  60. data/public/vendor/monaco/vs/basic-languages/coffee/coffee.js +10 -0
  61. data/public/vendor/monaco/vs/basic-languages/cpp/cpp.js +10 -0
  62. data/public/vendor/monaco/vs/basic-languages/csharp/csharp.js +10 -0
  63. data/public/vendor/monaco/vs/basic-languages/csp/csp.js +10 -0
  64. data/public/vendor/monaco/vs/basic-languages/css/css.js +12 -0
  65. data/public/vendor/monaco/vs/basic-languages/cypher/cypher.js +10 -0
  66. data/public/vendor/monaco/vs/basic-languages/dart/dart.js +10 -0
  67. data/public/vendor/monaco/vs/basic-languages/dockerfile/dockerfile.js +10 -0
  68. data/public/vendor/monaco/vs/basic-languages/ecl/ecl.js +10 -0
  69. data/public/vendor/monaco/vs/basic-languages/elixir/elixir.js +10 -0
  70. data/public/vendor/monaco/vs/basic-languages/flow9/flow9.js +10 -0
  71. data/public/vendor/monaco/vs/basic-languages/freemarker2/freemarker2.js +12 -0
  72. data/public/vendor/monaco/vs/basic-languages/fsharp/fsharp.js +10 -0
  73. data/public/vendor/monaco/vs/basic-languages/go/go.js +10 -0
  74. data/public/vendor/monaco/vs/basic-languages/graphql/graphql.js +10 -0
  75. data/public/vendor/monaco/vs/basic-languages/handlebars/handlebars.js +10 -0
  76. data/public/vendor/monaco/vs/basic-languages/hcl/hcl.js +10 -0
  77. data/public/vendor/monaco/vs/basic-languages/html/html.js +10 -0
  78. data/public/vendor/monaco/vs/basic-languages/ini/ini.js +10 -0
  79. data/public/vendor/monaco/vs/basic-languages/java/java.js +10 -0
  80. data/public/vendor/monaco/vs/basic-languages/javascript/javascript.js +10 -0
  81. data/public/vendor/monaco/vs/basic-languages/julia/julia.js +10 -0
  82. data/public/vendor/monaco/vs/basic-languages/kotlin/kotlin.js +10 -0
  83. data/public/vendor/monaco/vs/basic-languages/less/less.js +11 -0
  84. data/public/vendor/monaco/vs/basic-languages/lexon/lexon.js +10 -0
  85. data/public/vendor/monaco/vs/basic-languages/liquid/liquid.js +10 -0
  86. data/public/vendor/monaco/vs/basic-languages/lua/lua.js +10 -0
  87. data/public/vendor/monaco/vs/basic-languages/m3/m3.js +10 -0
  88. data/public/vendor/monaco/vs/basic-languages/markdown/markdown.js +10 -0
  89. data/public/vendor/monaco/vs/basic-languages/mdx/mdx.js +10 -0
  90. data/public/vendor/monaco/vs/basic-languages/mips/mips.js +10 -0
  91. data/public/vendor/monaco/vs/basic-languages/msdax/msdax.js +10 -0
  92. data/public/vendor/monaco/vs/basic-languages/mysql/mysql.js +10 -0
  93. data/public/vendor/monaco/vs/basic-languages/objective-c/objective-c.js +10 -0
  94. data/public/vendor/monaco/vs/basic-languages/pascal/pascal.js +10 -0
  95. data/public/vendor/monaco/vs/basic-languages/pascaligo/pascaligo.js +10 -0
  96. data/public/vendor/monaco/vs/basic-languages/perl/perl.js +10 -0
  97. data/public/vendor/monaco/vs/basic-languages/pgsql/pgsql.js +10 -0
  98. data/public/vendor/monaco/vs/basic-languages/php/php.js +10 -0
  99. data/public/vendor/monaco/vs/basic-languages/pla/pla.js +10 -0
  100. data/public/vendor/monaco/vs/basic-languages/postiats/postiats.js +10 -0
  101. data/public/vendor/monaco/vs/basic-languages/powerquery/powerquery.js +10 -0
  102. data/public/vendor/monaco/vs/basic-languages/powershell/powershell.js +10 -0
  103. data/public/vendor/monaco/vs/basic-languages/protobuf/protobuf.js +11 -0
  104. data/public/vendor/monaco/vs/basic-languages/pug/pug.js +10 -0
  105. data/public/vendor/monaco/vs/basic-languages/python/python.js +10 -0
  106. data/public/vendor/monaco/vs/basic-languages/qsharp/qsharp.js +10 -0
  107. data/public/vendor/monaco/vs/basic-languages/r/r.js +10 -0
  108. data/public/vendor/monaco/vs/basic-languages/razor/razor.js +10 -0
  109. data/public/vendor/monaco/vs/basic-languages/redis/redis.js +10 -0
  110. data/public/vendor/monaco/vs/basic-languages/redshift/redshift.js +10 -0
  111. data/public/vendor/monaco/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
  112. data/public/vendor/monaco/vs/basic-languages/ruby/ruby.js +10 -0
  113. data/public/vendor/monaco/vs/basic-languages/rust/rust.js +10 -0
  114. data/public/vendor/monaco/vs/basic-languages/sb/sb.js +10 -0
  115. data/public/vendor/monaco/vs/basic-languages/scala/scala.js +10 -0
  116. data/public/vendor/monaco/vs/basic-languages/scheme/scheme.js +10 -0
  117. data/public/vendor/monaco/vs/basic-languages/scss/scss.js +12 -0
  118. data/public/vendor/monaco/vs/basic-languages/shell/shell.js +10 -0
  119. data/public/vendor/monaco/vs/basic-languages/solidity/solidity.js +10 -0
  120. data/public/vendor/monaco/vs/basic-languages/sophia/sophia.js +10 -0
  121. data/public/vendor/monaco/vs/basic-languages/sparql/sparql.js +10 -0
  122. data/public/vendor/monaco/vs/basic-languages/sql/sql.js +10 -0
  123. data/public/vendor/monaco/vs/basic-languages/st/st.js +10 -0
  124. data/public/vendor/monaco/vs/basic-languages/swift/swift.js +13 -0
  125. data/public/vendor/monaco/vs/basic-languages/systemverilog/systemverilog.js +10 -0
  126. data/public/vendor/monaco/vs/basic-languages/tcl/tcl.js +10 -0
  127. data/public/vendor/monaco/vs/basic-languages/twig/twig.js +10 -0
  128. data/public/vendor/monaco/vs/basic-languages/typescript/typescript.js +10 -0
  129. data/public/vendor/monaco/vs/basic-languages/typespec/typespec.js +10 -0
  130. data/public/vendor/monaco/vs/basic-languages/vb/vb.js +10 -0
  131. data/public/vendor/monaco/vs/basic-languages/wgsl/wgsl.js +307 -0
  132. data/public/vendor/monaco/vs/basic-languages/xml/xml.js +10 -0
  133. data/public/vendor/monaco/vs/basic-languages/yaml/yaml.js +10 -0
  134. data/public/vendor/monaco/vs/editor/editor.main.css +8 -0
  135. data/public/vendor/monaco/vs/editor/editor.main.js +798 -0
  136. data/public/vendor/monaco/vs/language/css/cssMode.js +13 -0
  137. data/public/vendor/monaco/vs/language/css/cssWorker.js +77 -0
  138. data/public/vendor/monaco/vs/language/html/htmlMode.js +13 -0
  139. data/public/vendor/monaco/vs/language/html/htmlWorker.js +454 -0
  140. data/public/vendor/monaco/vs/language/json/jsonMode.js +19 -0
  141. data/public/vendor/monaco/vs/language/json/jsonWorker.js +42 -0
  142. data/public/vendor/monaco/vs/language/typescript/tsMode.js +20 -0
  143. data/public/vendor/monaco/vs/language/typescript/tsWorker.js +51328 -0
  144. data/public/vendor/monaco/vs/loader.js +11 -0
  145. data/public/vendor/monaco/vs/nls.messages.de.js +21 -0
  146. data/public/vendor/monaco/vs/nls.messages.es.js +21 -0
  147. data/public/vendor/monaco/vs/nls.messages.fr.js +19 -0
  148. data/public/vendor/monaco/vs/nls.messages.it.js +19 -0
  149. data/public/vendor/monaco/vs/nls.messages.ja.js +21 -0
  150. data/public/vendor/monaco/vs/nls.messages.ko.js +19 -0
  151. data/public/vendor/monaco/vs/nls.messages.ru.js +21 -0
  152. data/public/vendor/monaco/vs/nls.messages.zh-cn.js +21 -0
  153. data/public/vendor/monaco/vs/nls.messages.zh-tw.js +19 -0
  154. data/public/vendor/xterm/LICENSE +25 -0
  155. data/public/vendor/xterm/addon-fit.js +2 -0
  156. data/public/vendor/xterm/xterm.css +218 -0
  157. data/public/vendor/xterm/xterm.js +2 -0
  158. metadata +318 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cec25dba3c4cbb75c398e6bec8da266b937ffc4e4410ea83ea9b54f186d075c5
4
+ data.tar.gz: 1745a506a4bd94df8f67ff8eadc9ab313b3bacc7b9daa6b06094f438cdc866a8
5
+ SHA512:
6
+ metadata.gz: 95cbbce72c2151674bc93f73ab4c9fe77bbd2d44cf90b2d03a5eccfc8186bdb8cb0ebcf0f524bd3535316edb6fe5e8c68bf809644ce684743853a5dd1c6b621c
7
+ data.tar.gz: d8ad80506000c46f26b312eea3353af2684d978c3935bc00fe7a56cb1a1922fb80c43d9eb95f13ce7be4e85b4a58c4665bf0144c761b80c416d09c76bfa518e8
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ひかり (Himeyama)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,224 @@
1
+ # chocomint
2
+
3
+ <img width="600" alt="image" src="https://github.com/user-attachments/assets/93c03bde-eab0-432f-8e72-26540e183320" />
4
+
5
+ **Tool Execution Supervisor for Ruby** — LLM のツール呼び出しをソフトウェアで厳格に監督し、
6
+ 機械的検証・意味的検証・自動再試行・監査ログを行うオーケストレータ。
7
+
8
+ 設計仕様は [`DESIGN.md`](./DESIGN.md) を参照。設計原則:
9
+
10
+ > LLM は提案者と監査者に限定し、実行・判定・再試行・記録はソフトウェアで管理する。
11
+
12
+ ## 必要環境
13
+
14
+ - Ruby 3.4 (RubyInstaller **with MSYS2/DevKit**)
15
+ - LLM backend: Anthropic Messages API 互換エンドポイント
16
+ (Ollama が `/v1/messages` に対応。Claude API / OpenRouter でも `base_url` 差し替えで動作)
17
+
18
+ ## セットアップ
19
+
20
+ ```powershell
21
+ # Ruby 3.4 (未導入の場合)
22
+ winget install --id RubyInstallerTeam.RubyWithDevKit.3.4 -e
23
+
24
+ # 依存 gem
25
+ bundle install
26
+ ```
27
+
28
+ ## 設定
29
+
30
+ `config/config.yml` を編集する(DESIGN §11)。実機確認済みの構成:
31
+
32
+ ```yaml
33
+ llm:
34
+ base_url: http://localhost:11434/v1 # Ollama
35
+ model: gemma4:e4b # 提案者(primary)。tool_use 対応が必須
36
+ verifier:
37
+ enabled: true
38
+ # モデルは指定しない。verifier は llm.model を共有する(下記「モデルは 1 つ」を参照)
39
+ ```
40
+
41
+ 環境変数でオーバーライド可能: `CHOCOMINT_LLM_BASE_URL`, `CHOCOMINT_LLM_MODEL`。
42
+
43
+ > **モデルは 1 つ(primary と verifier で共有)**: VRAM 節約のため verifier は
44
+ > primary と同じモデルを使う。`factory.rb` が verifier に `llm.model` を渡すため、
45
+ > `config.yml` に `verifier.model` を書く必要はない(書いても無視される)。
46
+ > 単一モデルなら Ollama へのロードも 1 つで済み、モデルスワップが起きない。
47
+
48
+ > **モデル選定の注意**: verifier は出力を必ず `1`/`0` のみにする必要があるため、
49
+ > primary/verifier を兼ねるモデルは **tool_use 対応**かつ **指示追従性が高い**
50
+ > ものを選ぶ。`qwen3` 等の thinking 系は余計な出力をして verifier が
51
+ > 不正判定 (RETRY) になりやすいので注意。
52
+
53
+ ## 実行
54
+
55
+ ```powershell
56
+ # ollama serve を起動しておく
57
+ ruby bin/chocomint "<ユーザー要求>" ["<期待条件>"]
58
+
59
+ # 例
60
+ ruby bin/chocomint "workspace/hello.txt に hello と書いて" "内容が hello であること"
61
+ ```
62
+
63
+ 出力例:
64
+
65
+ ```
66
+ status: PASS
67
+ trace_id: 8af69708-...
68
+ attempts: 1
69
+ proposal: {"tool":"write_file","arguments":{"path":"workspace/hello.txt","content":"hello"}}
70
+ result: {"exit_code":0,"stdout":"wrote 5 bytes to workspace/hello.txt", ...}
71
+ ```
72
+
73
+ ## 監督プロキシサーバー
74
+
75
+ chocomint を **Anthropic Messages API 互換の HTTP サーバー**として起動できる。
76
+ `POST /v1/messages` で受けた要求を内部で `Orchestrator#run` に委譲し、
77
+ ツール実行・機械検証・意味検証・再試行・監査ログまで**完結させてから**結果を返す
78
+ (「監督完結型」)。通常の LLM API と違い、返るのは提案ではなく**実行が保証された結果**。
79
+
80
+ ```powershell
81
+ # 起動 (Ollama backend が別途必要)
82
+ ruby bin/chocomint-server # 既定 127.0.0.1:9210 (使用中なら 9211, 9212... と自動で空きポートを探す)
83
+ # CHOCOMINT_HOST / CHOCOMINT_PORT または config.yml の server: で変更可
84
+ ```
85
+
86
+ リクエスト例:
87
+
88
+ ```powershell
89
+ $body = @{
90
+ messages = @(@{ role="user"; content="a.txt に hello と書いて" })
91
+ expectations = "a.txt が存在し内容が hello であること"
92
+ } | ConvertTo-Json -Depth 6
93
+ Invoke-RestMethod -Uri http://127.0.0.1:9210/v1/messages -Method Post `
94
+ -ContentType "application/json" -Body $body
95
+ ```
96
+
97
+ レスポンス(Anthropic 形式 + `chocomint` 監督メタ情報):
98
+
99
+ ```json
100
+ {
101
+ "type": "message", "role": "assistant", "model": "chocomint-supervisor",
102
+ "chocomint": { "status": "PASS", "trace_id": "...", "attempts": 1 },
103
+ "content": [
104
+ { "type": "tool_use", "name": "write_file", "input": { "path": "...", "content": "hello" } },
105
+ { "type": "tool_result", "content": [{ "type": "text", "text": "{\"exit_code\":0,...}" }] }
106
+ ]
107
+ }
108
+ ```
109
+
110
+ | エンドポイント | 内容 |
111
+ |----------------|------|
112
+ | `POST /v1/messages` | 監督実行。200=PASS / 422=提案不正 / 502=リトライ上限 / 400=リクエスト不正 |
113
+ | `GET /health` | ヘルスチェック |
114
+
115
+ ## テスト
116
+
117
+ ```powershell
118
+ bundle exec rspec
119
+ ```
120
+
121
+ ## 監査ログ
122
+
123
+ 全試行が `logs/execution.db` (SQLite) の `executions` テーブルに記録される(DESIGN §12)。
124
+
125
+ ```powershell
126
+ ruby -e "require 'sqlite3'; SQLite3::Database.new('./logs/execution.db').execute('SELECT attempt,status,verifier_output FROM executions ORDER BY id').each{|r| p r}"
127
+ ```
128
+
129
+ ## セキュリティ
130
+
131
+ - ファイルアクセスは `security.allowed_dirs`(既定 `.` = サーバーを起動した場所)配下のみ。
132
+ パストラバーサル (`../`) は `PathGuard` が拒否する(DESIGN §13)。
133
+ - 出力・ファイルサイズ制限、実行タイムアウトを config で制御。
134
+
135
+ ## 対応ツール
136
+
137
+ 初期スコープ (DESIGN §15) に加え、基本的なファイル/コマンドツールを実装。
138
+
139
+ ### ファイル系(すべて `PathGuard` で許可ディレクトリ内に制限)
140
+
141
+ | ツール | 引数 | 概要 |
142
+ |--------|------|------|
143
+ | `write_file` | `path`, `content` | ファイルを書き込む(親ディレクトリ自動作成) |
144
+ | `read_file` | `path` | ファイル内容を stdout に返す |
145
+ | `list_dir` | `path` | ディレクトリのエントリ一覧を返す |
146
+ | `append_file` | `path`, `content` | ファイル末尾に追記(無ければ新規作成) |
147
+ | `delete_file` | `path` | ファイルを削除(ディレクトリは対象外) |
148
+ | `make_dir` | `path` | ディレクトリを作成(親も作成) |
149
+ | `file_info` | `path` | 存在・種別・サイズを JSON で返す |
150
+
151
+ ### 検索・編集系(`PathGuard` で許可ディレクトリ内に制限)
152
+
153
+ | ツール | 引数 | 概要 |
154
+ |--------|------|------|
155
+ | `edit` | `path`, `old_string`, `new_string`, `replace_all?` | 文字列置換。既定では `old_string` が一意である必要あり |
156
+ | `grep` | `pattern`, `base?`, `glob?`, `ignore_case?` | 正規表現検索。`file:line:text` 形式で返す |
157
+ | `glob` | `pattern`, `base?` | glob パターン(例 `**/*.rb`)でファイル列挙 |
158
+ | `ls` | `path` | エントリを `name`/`type`/`size` 付き JSON 配列で返す |
159
+
160
+ ### コマンド系
161
+
162
+ | ツール | 引数 | 概要 |
163
+ |--------|------|------|
164
+ | `run_command` | `command`, `args[]` | **whitelist** のコマンドのみ実行。`shell` を介さず引数配列で spawn(DESIGN §13) |
165
+ | `bash` | `script` | bash スクリプトを実行 ⚠️ |
166
+ | `powershell` | `script` | PowerShell (`pwsh`) スクリプトを実行 ⚠️ |
167
+
168
+ `run_command` の許可コマンドは `config.yml` の `security.allowed_commands` で指定する。
169
+ `security.allow_all_commands: true` にすると whitelist を無効化し任意コマンドを実行できる
170
+ (⚠️ 信頼できるローカル環境でのみ使うこと)。
171
+
172
+ > ⚠️ **`bash` / `powershell` について**: shell を介すため **任意コマンド実行が可能**で、
173
+ > DESIGN §13 の「shell 展開禁止」原則からは外れる強力なツール。cwd 固定・timeout・
174
+ > 出力サイズ制限は適用されるが、信頼できる環境でのみ使うこと。
175
+
176
+ ## AI エディタ (`/edit`)
177
+
178
+ `chocomint-server` は **ブラウザ上の AI エディタ**を `/edit` で提供する。
179
+ Monaco Editor でのファイル編集、AI チャットによる編集(内部で Planner が
180
+ 提案→実行→検証→再試行を完結)、本物の対話コンソール(xterm.js)を 1 画面に統合する。
181
+
182
+ ```powershell
183
+ ruby bin/chocomint-server
184
+ # → http://127.0.0.1:9210/edit をブラウザで開く
185
+ ```
186
+
187
+ 構成: メニュー / ファイル一覧(`workspace/` 配下)/ Monaco エディタ / AI チャット /
188
+ コンソール(ConPTY による本物の tty)。
189
+
190
+ | エンドポイント | 内容 |
191
+ |----------------|------|
192
+ | `GET /edit` | エディタ画面 |
193
+ | `GET /edit/files` | `workspace/` のファイル一覧 (JSON) |
194
+ | `GET /edit/file?path=` | ファイル内容 (JSON) |
195
+ | `POST /edit/save` | 人手編集の保存(`PathGuard` で `workspace/` 内に限定) |
196
+ | `POST /edit/chat` | 自然言語指示で AI 編集(`Planner#run`。`expectations` に指示を渡すため意味的検証が効く) |
197
+ | `GET /edit/assets/*` | Monaco / xterm.js のローカル同梱アセット |
198
+
199
+ ### 追加依存
200
+
201
+ - `ffi` — Windows の擬似コンソール (ConPTY) を FFI で叩く(プリビルド gem、DevKit 不要)。
202
+ - `websocket-driver` — コンソール (xterm.js) の WebSocket。
203
+ - Monaco / xterm.js は `public/vendor/` に同梱(CDN 不要・オフライン動作)。
204
+
205
+ ### コンソール(本物の対話 tty)
206
+
207
+ コンソールは Windows の **ConPTY** で `pwsh` を対話起動し、WebSocket 経由で
208
+ xterm.js と双方向に繋ぐ。tty ワーカーは新規コンソール付きで spawn される
209
+ (ConPTY はコンソールを持つプロセスからしか正しく動かないため)。
210
+
211
+ WebSocket は既定で HTTP ポート +1(`8081`)。ポート・トークンは以下で変更可能:
212
+
213
+ ```powershell
214
+ $env:CHOCOMINT_WS_PORT = "8081"
215
+ $env:CHOCOMINT_WS_TOKEN = "<共有トークン>" # 未設定ならローカル限定運用として認証なし
216
+ ```
217
+
218
+ > ⚠️ **コンソールのセキュリティ**: 生の対話 tty は `PathGuard` /
219
+ > `allowed_commands` の **監督外**で任意コマンドを実行できる(DESIGN §13 の
220
+ > shell 展開禁止原則の例外)。bind は `127.0.0.1` 固定。信頼できるローカル環境で、
221
+ > 必要なら `CHOCOMINT_WS_TOKEN` を設定して使うこと。
222
+ >
223
+ > ConPTY 非対応環境(非 Windows / `ffi` 未導入)ではコンソールのみ無効化され、
224
+ > エディタの他機能は動作する。
@@ -0,0 +1,45 @@
1
+ # Third-Party Licenses
2
+
3
+ chocomint は以下のサードパーティソフトウェアを利用・同梱している。各ソフトウェアの
4
+ 著作権はそれぞれの権利者に帰属し、以下のライセンスのもとで配布される。
5
+
6
+ ---
7
+
8
+ ## 同梱アセット (この gem に含めて再配布するもの)
9
+
10
+ AI エディタ (`/edit`) をオフラインで動作させるため、以下を `public/vendor/` に
11
+ バンドルしている。いずれも MIT ライセンスであり、原著作権表示とライセンス全文を
12
+ 各ディレクトリに同梱している。
13
+
14
+ ### Monaco Editor — v0.52.2
15
+
16
+ - Copyright (c) Microsoft Corporation. All rights reserved.
17
+ - License: MIT
18
+ - Homepage: https://github.com/microsoft/monaco-editor
19
+ - 同梱ライセンス: `public/vendor/monaco/LICENSE.txt`
20
+
21
+ ### xterm.js (@xterm) — addon-fit を含む
22
+
23
+ - Copyright (c) 2017-2022, The xterm.js authors
24
+ - Copyright (c) 2014-2016, SourceLair Private Company
25
+ - Copyright (c) 2012-2013, Christopher Jeffrey
26
+ - License: MIT
27
+ - Homepage: https://github.com/xtermjs/xterm.js
28
+ - 同梱ライセンス: `public/vendor/xterm/LICENSE`
29
+
30
+ ---
31
+
32
+ ## 実行時依存 gem (RubyGems 経由でインストールされ、この gem には同梱しないもの)
33
+
34
+ これらは `gem install` 時に依存として取得される。ここでは参考として列挙する。
35
+
36
+ | gem | ライセンス | ホームページ |
37
+ |-----|-----------|-------------|
38
+ | faraday | MIT | https://lostisland.github.io/faraday |
39
+ | sqlite3 | BSD-3-Clause | https://github.com/sparklemotion/sqlite3-ruby |
40
+ | json_schemer | MIT | https://github.com/davishmcclurg/json_schemer |
41
+ | webrick | Ruby / BSD-2-Clause | https://github.com/ruby/webrick |
42
+ | ffi | BSD-3-Clause | https://github.com/ffi/ffi |
43
+ | websocket-driver | Apache-2.0 | https://github.com/faye/websocket-driver-ruby |
44
+
45
+ 上記はいずれも再配布・商用利用を許容する寛容なライセンスである。
data/bin/chocomint ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require_relative "../lib/chocomint"
6
+
7
+ # 使い方:
8
+ # ruby bin/chocomint "<ユーザー要求>" ["期待条件"]
9
+ def main(argv)
10
+ if argv.empty?
11
+ warn "usage: chocomint <request> [expectations]"
12
+ return 2
13
+ end
14
+
15
+ request = argv[0]
16
+ expectations = argv[1]
17
+
18
+ config = Chocomint::Config.load
19
+ planner = Chocomint::Factory.build(config)
20
+
21
+ result = planner.run(request, expectations: expectations)
22
+
23
+ puts "status: #{result.status}"
24
+ puts "trace_id: #{result.trace_id}"
25
+ puts "attempts: #{result.attempts}"
26
+ puts "steps: #{result.steps.size}"
27
+ result.steps.each_with_index do |step, i|
28
+ puts " [#{i + 1}] #{step[:tool]} #{JSON.generate(step[:arguments])}"
29
+ puts " -> #{JSON.generate(step[:result])}"
30
+ end
31
+ 0
32
+ rescue Chocomint::Error => e
33
+ warn "FAIL: #{e.class.name.split('::').last}: #{e.message}"
34
+ 1
35
+ end
36
+
37
+ exit(main(ARGV)) if $PROGRAM_NAME == __FILE__
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # ConPTY コンソールワーカー。WorkerProcess から新規コンソール付きで spawn され、
5
+ # 名前付きパイプ経由で tty を橋渡しする。
6
+ # (ConPTY はコンソール付きプロセスからしか正しく動かないため、この分離が必要。)
7
+ #
8
+ # 使い方 (通常は WorkerProcess が spawn する):
9
+ # ruby bin/chocomint-console-worker --pipe \\.\pipe\... --cols 80 --rows 25 \
10
+ # --cwd <dir> -- <shell command...>
11
+ require "optparse"
12
+ require_relative "../lib/chocomint/console/worker"
13
+
14
+ options = { cols: 80, rows: 25, cwd: nil, c2w: nil, w2c: nil,
15
+ command: Chocomint::Console::ConPTY::DEFAULT_SHELL }
16
+ OptionParser.new do |o|
17
+ o.on("--c2w NAME") { |v| options[:c2w] = v }
18
+ o.on("--w2c NAME") { |v| options[:w2c] = v }
19
+ o.on("--cols N", Integer) { |v| options[:cols] = v }
20
+ o.on("--rows N", Integer) { |v| options[:rows] = v }
21
+ o.on("--cwd DIR") { |v| options[:cwd] = v }
22
+ o.on("--command CMD") { |v| options[:command] = v }
23
+ end.parse!(ARGV)
24
+ options[:command] = ARGV.join(" ") unless ARGV.empty?
25
+
26
+ abort("--c2w and --w2c are required") unless options[:c2w] && options[:w2c]
27
+
28
+ Chocomint::Console::Worker.new(
29
+ command: options[:command],
30
+ cols: options[:cols],
31
+ rows: options[:rows],
32
+ cwd: options[:cwd],
33
+ c2w_name: options[:c2w],
34
+ w2c_name: options[:w2c]
35
+ ).run
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/chocomint"
5
+
6
+ # 監督完結型の Anthropic 互換プロキシ + AI エディタ (/edit) サーバーを起動する。
7
+ # ruby bin/chocomint-server
8
+ # 環境変数 CHOCOMINT_HOST / CHOCOMINT_PORT / CHOCOMINT_WS_PORT / CHOCOMINT_WS_TOKEN で上書き可。
9
+ config = Chocomint::Config.load
10
+ base_dir = Dir.pwd
11
+ planner = Chocomint::Factory.build(config, base_dir: base_dir)
12
+ path_guard = Chocomint::Factory.build_path_guard(config, base_dir: base_dir)
13
+ chat_client = Chocomint::Factory.build_chat_client(config, base_dir: base_dir)
14
+
15
+ # AI エディタのコンソール (WebSocket + ConPTY) を別スレッドで起動する。
16
+ # ConPTY (Windows 擬似コンソール) が使える環境でのみ有効化する。
17
+ ws_port = nil
18
+ ws_token = config.console_ws_token
19
+ if Chocomint::CONSOLE_AVAILABLE
20
+ ws_port = config.console_ws_port
21
+ Chocomint::Console::Server.new(
22
+ host: config.server_host,
23
+ port: ws_port,
24
+ cwd: path_guard.allowed_roots.first, # WORKING DIRECTORY (ファイル一覧) の起点と揃える
25
+ token: ws_token,
26
+ logger: $stderr
27
+ ).start_in_thread
28
+ else
29
+ warn "console (ConPTY) unavailable on this platform; /edit terminal disabled"
30
+ end
31
+
32
+ Chocomint::Server.new(
33
+ planner,
34
+ host: config.server_host,
35
+ port: config.server_port,
36
+ sqlite_path: config.sqlite_path,
37
+ path_guard: path_guard,
38
+ base_dir: base_dir,
39
+ max_file_bytes: config.max_file_bytes,
40
+ ws_port: ws_port,
41
+ ws_token: ws_token,
42
+ chat_client: chat_client
43
+ ).start
data/config/config.yml ADDED
@@ -0,0 +1,37 @@
1
+ # chocomint 設定 (DESIGN.md §11)
2
+ llm:
3
+ base_url: http://localhost:11434/v1
4
+ model: gemma4:e4b
5
+ api_key_env: OLLAMA_API_KEY # 参照する環境変数名 (無ければ空)
6
+ max_tokens: 4096 # thinking モデルは思考だけで使い切ることがあるため余裕を持たせる
7
+
8
+ verifier:
9
+ enabled: true
10
+ # モデルは指定しない。VRAM 節約のため llm.model を共有する (factory.rb 参照)。
11
+ max_tokens: 1024 # thinking モデルは思考だけで使い切ることがあるため余裕を持たせる
12
+
13
+ execution:
14
+ max_retry: 10
15
+ max_steps: 20 # マルチステップ要求で実行を許すステップ数の上限 (暴走防止)
16
+ timeout_sec: 30
17
+ max_output_bytes: 1048576 # stdout/stderr サイズ制限 (1 MiB)
18
+
19
+ security:
20
+ allowed_dirs:
21
+ - . # サーバーを起動した場所 (Dir.pwd) を作業ディレクトリとして許可
22
+ max_file_bytes: 10485760 # ファイルサイズ制限 (10 MiB)
23
+ allowed_commands: # run_command で実行を許可するコマンド (whitelist)
24
+ - git
25
+ - ruby
26
+ - node
27
+ - uv # Python 環境構築 (uv init / uv add / venv)
28
+ - ping # ネットワーク疎通確認
29
+ allow_all_commands: true # true にすると run_command が allowed_commands を無視し任意コマンドを実行する
30
+ # (⚠️ whitelist を無効化する。信頼できるローカル環境でのみ true にすること)
31
+
32
+ logging:
33
+ sqlite_path: ./logs/execution.db
34
+
35
+ server:
36
+ host: 127.0.0.1
37
+ port: 9210
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Chocomint
6
+ # config/config.yml を読み込み、環境変数オーバーライドを適用する。
7
+ class Config
8
+ DEFAULT_PATH = File.expand_path("../../config/config.yml", __dir__)
9
+
10
+ attr_reader :data
11
+
12
+ def self.load(path = DEFAULT_PATH)
13
+ raw = File.exist?(path) ? YAML.safe_load(File.read(path)) : {}
14
+ new(raw || {})
15
+ end
16
+
17
+ def initialize(data)
18
+ @data = deep_stringify(data)
19
+ apply_env_overrides!
20
+ end
21
+
22
+ def llm = @data.fetch("llm", {})
23
+ def verifier = @data.fetch("verifier", {})
24
+ def execution = @data.fetch("execution", {})
25
+ def security = @data.fetch("security", {})
26
+ def logging = @data.fetch("logging", {})
27
+ def server = @data.fetch("server", {})
28
+
29
+ def server_host = ENV.fetch("CHOCOMINT_HOST", server.fetch("host", "127.0.0.1"))
30
+ def server_port = ENV.fetch("CHOCOMINT_PORT", server.fetch("port", 9210)).to_i
31
+ # AI エディタのコンソール (WebSocket) 用ポート。既定は HTTP ポート+1。
32
+ def console_ws_port = ENV.fetch("CHOCOMINT_WS_PORT", server.fetch("ws_port", server_port + 1)).to_i
33
+ # コンソール接続に要求する共有トークン。未設定なら nil (ローカル限定運用向け)。
34
+ def console_ws_token = ENV.fetch("CHOCOMINT_WS_TOKEN", server.fetch("ws_token", nil))
35
+
36
+ def max_retry = execution.fetch("max_retry", 10)
37
+ # マルチステップ実行のステップ数上限。未設定なら max_retry を流用する (暴走防止)。
38
+ def max_steps = execution.fetch("max_steps", max_retry)
39
+ def timeout_sec = execution.fetch("timeout_sec", 30)
40
+ def max_output_bytes = execution.fetch("max_output_bytes", 1_048_576)
41
+
42
+ def allowed_dirs = security.fetch("allowed_dirs", ["."])
43
+ def max_file_bytes = security.fetch("max_file_bytes", 10_485_760)
44
+ def allowed_commands = security.fetch("allowed_commands", [])
45
+ # true なら run_command で allowed_commands の whitelist チェックを行わず任意コマンドを実行する。
46
+ def allow_all_commands? = security.fetch("allow_all_commands", false)
47
+
48
+ def sqlite_path = logging.fetch("sqlite_path", "./logs/execution.db")
49
+
50
+ def verifier_enabled? = verifier.fetch("enabled", false)
51
+
52
+ private
53
+
54
+ def apply_env_overrides!
55
+ if (url = ENV["CHOCOMINT_LLM_BASE_URL"])
56
+ (@data["llm"] ||= {})["base_url"] = url
57
+ end
58
+ if (model = ENV["CHOCOMINT_LLM_MODEL"])
59
+ (@data["llm"] ||= {})["model"] = model
60
+ end
61
+ end
62
+
63
+ def deep_stringify(obj)
64
+ case obj
65
+ when Hash
66
+ obj.each_with_object({}) { |(k, v), h| h[k.to_s] = deep_stringify(v) }
67
+ when Array
68
+ obj.map { |v| deep_stringify(v) }
69
+ else
70
+ obj
71
+ end
72
+ end
73
+ end
74
+ end