brainchat 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '09e175edc9c5ee8494be30f5ae64384d38e493c029e212e363cb6ffea74eb435'
4
+ data.tar.gz: 75d5889ecf72f543c622585c49406b8066d566747aff824a565a4f0cb3762348
5
+ SHA512:
6
+ metadata.gz: '09e02f48285304b6e142e0f25b41f238874bb183e3dbd202485332a1d8a9343b8e0a6bbca750612df660d994329acf4edc32fdaf402b8cb94f664688a29342b1'
7
+ data.tar.gz: ac64f712e98f66a072c5a133880f0a54296ce754f95d16a41d288e683268a554e9dc32f5ed29502fb72bc448c8634fa0d1fa788095ba07ce006b7e0792e0519b
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.10
data/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2026-07-27
4
+
5
+ - Initial release: `brainchat ask QUESTION` answers from a knowledge-brain
6
+ vault with citations. Retrieval shells out to the hitgate `rag-query` CLI in
7
+ JSON mode (argv-array via `Open3.capture3`, no shell interpolation); the chat
8
+ is RubyLLM with retrieved chunks injected as numbered context and cited as
9
+ `[n]` in the reply, with a `[n] -> path:lines` sources list printed after the
10
+ answer.
11
+ - Streaming by default; `--no-stream` prints the complete answer at once.
12
+ - Provider configuration via `--model` / `--provider` / `--assume-model-exists`
13
+ and the `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `OLLAMA_API_BASE`
14
+ environment variables. `--provider ollama` needs no setup.
15
+ - Retriever honors `BRAINCHAT_RAG_QUERY_BIN` and passes `RAG_*` index
16
+ configuration through to `rag-query`.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Lucas Santana
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # brainchat
2
+
3
+ [![CI](https://github.com/LucasSantana-Dev/brainchat/actions/workflows/main.yml/badge.svg)](https://github.com/LucasSantana-Dev/brainchat/actions/workflows/main.yml)
4
+ [![Ruby >= 3.2](https://img.shields.io/badge/ruby-%3E%3D%203.2-red)](https://www.ruby-lang.org)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.txt)
6
+
7
+ **Chat with your knowledge base from the terminal, with citations that resolve to real files.**
8
+ brainchat is a Ruby AI CLI that answers questions over a "knowledge-brain" vault (notes, ADRs,
9
+ plans, commit history, docs) and labels every claim with the exact chunk it came from.
10
+
11
+ ```
12
+ $ brainchat ask "what did we decide about hitgate caveat timing"
13
+
14
+ Ship the two-channel honesty check first, then the self-indexed caveat [1][2]. The
15
+ rationale: publishing the caveat first would anchor the evaluation story on a limitation
16
+ with no third-party evidence behind it [1].
17
+
18
+ Sources:
19
+ [1] /vault/knowledge-brain/docs/adr/0012-caveat-investment-timing.md:1-34 (repo-docs/hitgate)
20
+ [2] /vault/knowledge-brain/memory/caveat-timing-note.md:3-9 (memory/knowledge-brain)
21
+ ```
22
+
23
+ ## How it works
24
+
25
+ brainchat deliberately does **not** reimplement retrieval. It composes two tools that each do
26
+ one thing well:
27
+
28
+ 1. Your question goes to [`rag-query`](https://github.com/LucasSantana-Dev/hitgate), the
29
+ hybrid BM25 + cosine retrieval CLI that hitgate's regression gate benchmarks. brainchat
30
+ shells out with `Open3.capture3` and an argv-array (never a shell string) and treats the
31
+ JSON output as the retrieval contract.
32
+ 2. The retrieved chunks are injected into a [RubyLLM](https://rubyllm.com) chat as numbered
33
+ context. The system prompt requires `[n]` citations, and the CLI prints the
34
+ `[n] -> path:lines` mapping after the answer, so every citation resolves to a file and
35
+ line range you can open.
36
+
37
+ Provider-agnostic: Anthropic, OpenAI, or fully local with Ollama.
38
+
39
+ ## Requirements
40
+
41
+ - Ruby >= 3.2
42
+ - The `rag-query` executable from [hitgate](https://github.com/LucasSantana-Dev/hitgate) on
43
+ your `PATH` (or point `BRAINCHAT_RAG_QUERY_BIN` at it), plus an indexed vault. `rag-query`
44
+ is configured through its own `RAG_*` environment variables (e.g. `RAG_INDEX_DIR`), which
45
+ brainchat passes straight through.
46
+ - **`--scope-repo all` needs hitgate at or after
47
+ [#134](https://github.com/LucasSantana-Dev/hitgate/pull/134).** Earlier versions turned the
48
+ `all` sentinel into "no preference", which re-enabled cwd auto-scoping; since hitgate's
49
+ source roots default to the working directory, every query got scoped to a repo named after
50
+ whatever directory you were standing in and came back empty with exit 0. If `brainchat ask`
51
+ finds nothing in an index you know is populated, check this first.
52
+ - A provider: `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`, or a local Ollama.
53
+
54
+ ## Install
55
+
56
+ Not on RubyGems yet. Build from source:
57
+
58
+ ```bash
59
+ git clone https://github.com/LucasSantana-Dev/brainchat
60
+ cd brainchat && bin/setup
61
+ gem build brainchat.gemspec && gem install ./brainchat-0.1.0.gem
62
+ ```
63
+
64
+ Or run it without installing: `bundle exec exe/brainchat ask "..."`.
65
+
66
+ ## Usage
67
+
68
+ ```bash
69
+ brainchat ask "what did we decide about hitgate caveat timing"
70
+ brainchat ask "how does the reranker fall back" --top 8 --scope-repo all
71
+ brainchat ask "..." --provider ollama --model qwen3:4b --assume-model-exists # fully local
72
+ brainchat ask "..." --no-stream # print the answer only when complete
73
+ brainchat ask "..." --rerank # cross-encoder reranker (slower, better ranking)
74
+ ```
75
+
76
+ | Flag | Purpose |
77
+ |---|---|
78
+ | `--top N` | number of chunks to retrieve (default 5) |
79
+ | `--scope-repo a,b` | restrict retrieval to named repos, or `all` to disable cwd scoping |
80
+ | `--rerank` | enable the cross-encoder reranker (default: fast mode) |
81
+ | `--provider NAME` | `anthropic`, `openai`, `ollama` |
82
+ | `--model ID` | model id (default: RubyLLM's configured default) |
83
+ | `--assume-model-exists` | skip model-registry validation, needed for local Ollama models |
84
+ | `--no-stream` | print the complete answer at once instead of streaming |
85
+
86
+ ## Configuration
87
+
88
+ | Variable | Purpose |
89
+ |---|---|
90
+ | `ANTHROPIC_API_KEY` | Anthropic provider key |
91
+ | `OPENAI_API_KEY` | OpenAI provider key |
92
+ | `OLLAMA_API_BASE` | Ollama endpoint (default `http://localhost:11434/v1`) |
93
+ | `BRAINCHAT_RAG_QUERY_BIN` | path to the `rag-query` executable (default: `rag-query` on `PATH`) |
94
+ | `RAG_INDEX_DIR` and other `RAG_*` | hitgate index configuration, passed through to `rag-query` |
95
+
96
+ ## Under the hood
97
+
98
+ Built around Ruby idioms, not ported line-for-line from anything:
99
+
100
+ - **`Open3.capture3` with an argv-array, never a shell string.** The question travels as one
101
+ bare argv entry, so a query containing `$(...)`, backticks or `;` is inert data. The spec
102
+ suite proves this with a shell-injection marker test that fails if the query is ever
103
+ interpolated into a shell.
104
+ - **`Data.define` for the `Chunk` value object**: immutable, keyword-constructed, with a
105
+ `#location` helper that renders `path:start-end` (or bare `path` for line-less chunks like
106
+ git commits).
107
+ - **RubyLLM wired explicitly.** RubyLLM 1.16 reads no provider env vars on its own, so
108
+ `Chat.configure` feeds it `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `OLLAMA_API_BASE` itself.
109
+ - **Streaming by default** via `chat.ask(prompt) { |chunk| ... }`; `--no-stream` prints the
110
+ complete answer at once.
111
+ - **Thor** for CLI dispatch.
112
+
113
+ ## Development
114
+
115
+ ```bash
116
+ bin/setup
117
+ bundle exec rake # rspec + rubocop
118
+ ```
119
+
120
+ Specs stub both boundaries: the retriever is tested against a fixture of real `rag-query`
121
+ JSON output (no subprocess), and the chat against a stubbed RubyLLM (no network).
122
+
123
+ ## Related projects
124
+
125
+ - [hitgate](https://github.com/LucasSantana-Dev/hitgate): the retrieval engine and regression
126
+ gate brainchat queries.
127
+ - [leakless](https://github.com/LucasSantana-Dev/leakless): rule-driven secret scanner with
128
+ redact-always AI triage, same Ruby-idiom-first approach.
129
+
130
+ ## License
131
+
132
+ MIT. See [LICENSE.txt](LICENSE.txt).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/exe/brainchat ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "brainchat"
5
+
6
+ Brainchat::CLI.start(ARGV)
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby_llm"
4
+
5
+ module Brainchat
6
+ # Answers a question with an LLM, using retrieved knowledge-brain chunks as
7
+ # the only context. Chunks are numbered in the prompt and the model is asked
8
+ # to cite them as [n]; the CLI prints the [n] -> path:lines mapping after the
9
+ # answer, so every citation is resolvable to a file.
10
+ module Chat
11
+ # Ollama's conventional local endpoint, so --provider ollama needs no setup.
12
+ OLLAMA_DEFAULT_BASE = "http://localhost:11434/v1"
13
+
14
+ # A missing API key or an unknown model id raise off a different branch of
15
+ # RubyLLM's hierarchy than provider/HTTP faults, and they are the two most
16
+ # likely failures here, so both branches are caught.
17
+ FAILURES = [RubyLLM::Error, RubyLLM::ConfigurationError,
18
+ RubyLLM::ModelNotFoundError, Faraday::Error].freeze
19
+
20
+ SYSTEM_PROMPT = <<~PROMPT
21
+ You answer questions about the user's knowledge-brain: a vault of notes,
22
+ ADRs, plans, commit history and docs, retrieved for you by a hybrid
23
+ BM25 + cosine search. The retrieved chunks are numbered and included in
24
+ the user's message.
25
+
26
+ Answer only from those chunks. When the chunks do not contain the answer,
27
+ say so plainly instead of guessing. Cite every claim with the chunk
28
+ number in square brackets, e.g. [1] or [2][3]. Keep the answer tight;
29
+ quote file paths only via the citation numbers.
30
+ PROMPT
31
+
32
+ module_function
33
+
34
+ # Asks `question` with `chunks` as context. Streams each content delta to
35
+ # `on_chunk` when given. Returns the final RubyLLM::Message.
36
+ def call(question, chunks, on_chunk: nil, **chat_options)
37
+ configure
38
+ chat = RubyLLM.chat(**chat_options.compact)
39
+ .with_instructions(SYSTEM_PROMPT)
40
+ if on_chunk
41
+ chat.ask(prompt_for(question, chunks)) { |chunk| on_chunk.call(chunk.content) }
42
+ else
43
+ chat.ask(prompt_for(question, chunks))
44
+ end
45
+ rescue *FAILURES => e
46
+ raise Error, "chat failed: #{e.class}: #{e.message}"
47
+ end
48
+
49
+ # RubyLLM reads no provider credentials from the environment on its own,
50
+ # and a CLI has nowhere else to get them.
51
+ def configure
52
+ RubyLLM.configure do |config|
53
+ config.anthropic_api_key = ENV.fetch("ANTHROPIC_API_KEY", nil)
54
+ config.openai_api_key = ENV.fetch("OPENAI_API_KEY", nil)
55
+ config.ollama_api_base = ENV.fetch("OLLAMA_API_BASE", OLLAMA_DEFAULT_BASE)
56
+ end
57
+ end
58
+
59
+ # Chunks go over as a numbered list; the citation number the model emits is
60
+ # the position in this list, and the CLI prints the same numbering.
61
+ def prompt_for(question, chunks)
62
+ body = chunks.each_with_index.map do |chunk, index|
63
+ <<~CHUNK
64
+ [#{index + 1}] #{chunk.location} (#{chunk.source_type}/#{chunk.repo})
65
+ #{chunk.text}
66
+ CHUNK
67
+ end.join("\n")
68
+
69
+ <<~PROMPT
70
+ Retrieved chunks:
71
+
72
+ #{body}
73
+
74
+ Question: #{question}
75
+ PROMPT
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require_relative "retriever"
5
+ require_relative "chat"
6
+
7
+ module Brainchat
8
+ # Thor command-line interface. `ask` retrieves chunks via rag-query and
9
+ # streams the cited answer to stdout.
10
+ class CLI < Thor
11
+ def self.exit_on_failure? = true
12
+
13
+ desc "ask QUESTION", "answer a question from the knowledge-brain, citing retrieved chunks"
14
+ method_option :top,
15
+ type: :numeric,
16
+ default: Retriever::DEFAULT_TOP,
17
+ desc: "number of chunks to retrieve"
18
+ method_option :scope_repo,
19
+ type: :string,
20
+ desc: "comma-separated repo names to search, or 'all' to disable cwd scoping"
21
+ method_option :rerank,
22
+ type: :boolean,
23
+ default: false,
24
+ desc: "enable the cross-encoder reranker (slower, better ranking)"
25
+ method_option :model,
26
+ type: :string,
27
+ desc: "model id (default: RubyLLM's configured default)"
28
+ method_option :provider,
29
+ type: :string,
30
+ desc: "provider, e.g. anthropic, openai, ollama"
31
+ method_option :assume_model_exists,
32
+ type: :boolean,
33
+ default: false,
34
+ desc: "skip model-registry validation, needed for local Ollama models"
35
+ method_option :no_stream,
36
+ type: :boolean,
37
+ default: false,
38
+ desc: "print the answer only once it is complete"
39
+ def ask(question)
40
+ chunks = retrieve(question)
41
+ answer(question, chunks)
42
+ print_sources(chunks)
43
+ rescue Error => e
44
+ warn e.message
45
+ exit 1
46
+ end
47
+
48
+ no_commands do
49
+ def retrieve(question)
50
+ chunks = Retriever.call(question,
51
+ top: options[:top],
52
+ scope_repo: options[:scope_repo],
53
+ fast: !options[:rerank])
54
+ return chunks unless chunks.empty?
55
+
56
+ raise Error, "No chunks retrieved for this question. Is the knowledge-brain indexed " \
57
+ "(RAG_INDEX_DIR) and is `rag-query` working?"
58
+ end
59
+
60
+ def answer(question, chunks)
61
+ on_chunk = options[:no_stream] ? nil : ->(content) { print content }
62
+ message = Chat.call(question, chunks,
63
+ on_chunk:,
64
+ model: options[:model],
65
+ provider: options[:provider]&.to_sym,
66
+ assume_model_exists: options[:assume_model_exists])
67
+ # In streaming mode the deltas already went to stdout; in --no-stream
68
+ # mode nothing has been printed yet.
69
+ puts(options[:no_stream] ? message.content : "")
70
+ end
71
+
72
+ def print_sources(chunks)
73
+ puts "\nSources:"
74
+ chunks.each_with_index do |chunk, index|
75
+ puts " [#{index + 1}] #{chunk.location} (#{chunk.source_type}/#{chunk.repo})"
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "open3"
5
+
6
+ module Brainchat
7
+ # Retrieves knowledge-brain chunks by shelling out to the hitgate `rag-query`
8
+ # CLI in its JSON output mode:
9
+ #
10
+ # rag-query --top N --format json [--scope-repo all] [--fast] "<query>"
11
+ #
12
+ # stdout is a pure JSON array of chunks (warnings and model-load progress go
13
+ # to stderr), each shaped:
14
+ #
15
+ # { "rank", "rrf", "cos", "bm25", "reranked", "source_type", "repo",
16
+ # "language", "symbol", "path", "start_line", "end_line", "text" }
17
+ #
18
+ # The query and every flag travel as separate argv entries through
19
+ # Open3.capture3 — no shell is ever involved, so a query containing
20
+ # metacharacters (`$(...)`, backticks, `;`) is inert data, not a command.
21
+ module Retriever
22
+ # One retrieved chunk. `Data.define` gives an immutable value object with
23
+ # free keyword construction, matching the Finding idiom in leakless.
24
+ Chunk = Data.define(:rank, :source_type, :repo, :symbol, :path, :start_line, :end_line, :text) do
25
+ # "path:12-40", or just "path" for chunks without line information
26
+ # (git-commit chunks carry start_line/end_line of 0).
27
+ def location
28
+ return path.to_s if start_line.to_i.zero? && end_line.to_i.zero?
29
+
30
+ "#{path}:#{start_line}-#{end_line}"
31
+ end
32
+ end
33
+
34
+ DEFAULT_TOP = 5
35
+
36
+ module_function
37
+
38
+ # Returns an array of Chunk, empty when the index has no matches.
39
+ def call(query, top: DEFAULT_TOP, scope_repo: nil, fast: true, bin: default_bin)
40
+ stdout, stderr, status = Open3.capture3(*argv(bin, query, top:, scope_repo:, fast:))
41
+ raise Error, "rag-query failed (exit #{status.exitstatus}): #{stderr.strip}" unless status.success?
42
+
43
+ parse(stdout)
44
+ rescue Errno::ENOENT
45
+ raise Error, "`#{bin}` was not found on PATH. Install hitgate " \
46
+ "(https://github.com/LucasSantana-Dev/hitgate) or point " \
47
+ "BRAINCHAT_RAG_QUERY_BIN at a rag-query executable."
48
+ end
49
+
50
+ def argv(bin, query, top:, scope_repo:, fast:)
51
+ args = [bin, "--top", top.to_s, "--format", "json"]
52
+ args += ["--scope-repo", scope_repo] if scope_repo
53
+ args << "--fast" if fast
54
+ args << query
55
+ end
56
+
57
+ def parse(stdout)
58
+ rows = JSON.parse(stdout)
59
+ unless rows.is_a?(Array) && rows.all?(Hash)
60
+ raise Error, "rag-query returned unexpected JSON (expected an array of chunks)"
61
+ end
62
+
63
+ rows.map { |row| chunk_from(row) }
64
+ rescue JSON::ParserError => e
65
+ raise Error, "rag-query did not return valid JSON: #{e.message}"
66
+ end
67
+
68
+ def chunk_from(row)
69
+ Chunk.new(rank: row["rank"],
70
+ source_type: row["source_type"],
71
+ repo: row["repo"],
72
+ symbol: row["symbol"],
73
+ path: row["path"],
74
+ start_line: row["start_line"],
75
+ end_line: row["end_line"],
76
+ text: row["text"])
77
+ end
78
+
79
+ def default_bin
80
+ ENV.fetch("BRAINCHAT_RAG_QUERY_BIN", "rag-query")
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brainchat
4
+ VERSION = "0.1.0"
5
+ end
data/lib/brainchat.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "brainchat/version"
4
+
5
+ module Brainchat
6
+ class Error < StandardError; end
7
+ end
8
+
9
+ require_relative "brainchat/cli"
data/sig/brainchat.rbs ADDED
@@ -0,0 +1,14 @@
1
+ module Brainchat
2
+ VERSION: String
3
+
4
+ class Error < StandardError
5
+ end
6
+
7
+ module Retriever
8
+ def self.call: (String query, ?top: Integer, ?scope_repo: String?, ?fast: bool, ?bin: String) -> Array[Chunk]
9
+ end
10
+
11
+ module Chat
12
+ def self.call: (String question, Array[Retriever::Chunk] chunks, ?on_chunk: ^(String) -> void, **untyped chat_options) -> untyped
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: brainchat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Lucas Santana
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-07-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ruby_llm
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ description: A small Ruby CLI that answers questions about a knowledge-brain vault.
42
+ Retrieval shells out to the hitgate `rag-query` CLI (JSON mode, argv-array, no shell
43
+ interpolation); the chat is RubyLLM, with retrieved chunks injected as context and
44
+ cited in the reply.
45
+ email:
46
+ - lucas.diassantana@gmail.com
47
+ executables:
48
+ - brainchat
49
+ extensions: []
50
+ extra_rdoc_files: []
51
+ files:
52
+ - ".ruby-version"
53
+ - CHANGELOG.md
54
+ - LICENSE.txt
55
+ - README.md
56
+ - Rakefile
57
+ - exe/brainchat
58
+ - lib/brainchat.rb
59
+ - lib/brainchat/chat.rb
60
+ - lib/brainchat/cli.rb
61
+ - lib/brainchat/retriever.rb
62
+ - lib/brainchat/version.rb
63
+ - sig/brainchat.rbs
64
+ homepage: https://github.com/LucasSantana-Dev/brainchat
65
+ licenses:
66
+ - MIT
67
+ metadata:
68
+ allowed_push_host: https://rubygems.org
69
+ homepage_uri: https://github.com/LucasSantana-Dev/brainchat
70
+ source_code_uri: https://github.com/LucasSantana-Dev/brainchat/tree/main
71
+ changelog_uri: https://github.com/LucasSantana-Dev/brainchat/blob/main/CHANGELOG.md
72
+ rubygems_mfa_required: 'true'
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 3.2.0
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubygems_version: 3.0.3.1
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: Chat over a knowledge-brain vault from the terminal.
92
+ test_files: []