chatwerk 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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +90 -0
- data/CHANGELOG.md +1 -0
- data/LICENSE.txt +21 -0
- data/README.md +68 -0
- data/Rakefile +12 -0
- data/config.ru +4 -0
- data/exe/chatwerk +6 -0
- data/lib/chatwerk/api.rb +71 -0
- data/lib/chatwerk/cli.rb +59 -0
- data/lib/chatwerk/error.rb +24 -0
- data/lib/chatwerk/helpers.rb +71 -0
- data/lib/chatwerk/mcp.rb +25 -0
- data/lib/chatwerk/tools/package_todos_tool.rb +50 -0
- data/lib/chatwerk/tools/package_tool.rb +39 -0
- data/lib/chatwerk/tools/package_violations_tool.rb +51 -0
- data/lib/chatwerk/tools/packages_tool.rb +37 -0
- data/lib/chatwerk/tools/print_env_tool.rb +35 -0
- data/lib/chatwerk/version.rb +6 -0
- data/lib/chatwerk/views/base_view.rb +54 -0
- data/lib/chatwerk/views/no_packages_view.rb +28 -0
- data/lib/chatwerk/views/no_violations_view.rb +20 -0
- data/lib/chatwerk/views/package_view.rb +25 -0
- data/lib/chatwerk/views/packages_view.rb +15 -0
- data/lib/chatwerk/views/violations_details_view.rb +46 -0
- data/lib/chatwerk/views/violations_list_view.rb +32 -0
- data/lib/chatwerk/views.rb +10 -0
- data/lib/chatwerk.rb +16 -0
- data/mise.toml +2 -0
- data/sig/chatwerk.rbs +4 -0
- metadata +145 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1f6463014c8b9acb703cc940790b073d41e434b507e7a16cdc5635df1393c9dd
|
|
4
|
+
data.tar.gz: 3e208195bb1b414e677003e973f4b2c225642cc2de780d78fc32c42f6db75f4b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: bc748648854e35f7bb6dab00d22841ea98c18c4d4754ca8b11935a1c58caec6fe8772603d3a1c92fb27c9462c853a2a2d2f4af84e5b32bb666a9f6d3a06c4065
|
|
7
|
+
data.tar.gz: d0f2a2e84fab5ec86d22986d508363818cbb11c49a91afa961b585d21c4cd4b26eeb6aaeb4af61cc86c071ddac8e1f225202ae27f35792c4f1d88611320e2f47
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
plugins:
|
|
2
|
+
- rubocop-sorbet
|
|
3
|
+
- rubocop-performance
|
|
4
|
+
|
|
5
|
+
AllCops:
|
|
6
|
+
TargetRubyVersion: 3.1
|
|
7
|
+
SuggestExtensions: false
|
|
8
|
+
NewCops: enable
|
|
9
|
+
Exclude:
|
|
10
|
+
- bin/**/*
|
|
11
|
+
- vendor/**/*
|
|
12
|
+
|
|
13
|
+
Sorbet/StrictSigil:
|
|
14
|
+
Enabled: false
|
|
15
|
+
|
|
16
|
+
Layout/ExtraSpacing:
|
|
17
|
+
Enabled: true
|
|
18
|
+
Exclude:
|
|
19
|
+
- danger-packwerk.gemspec
|
|
20
|
+
|
|
21
|
+
Layout/SpaceAroundOperators:
|
|
22
|
+
Enabled: true
|
|
23
|
+
Exclude:
|
|
24
|
+
- danger-packwerk.gemspec
|
|
25
|
+
|
|
26
|
+
Sorbet/ValidSigil:
|
|
27
|
+
Enabled: true
|
|
28
|
+
|
|
29
|
+
Gemspec/RequireMFA:
|
|
30
|
+
Enabled: false
|
|
31
|
+
|
|
32
|
+
Layout/LineLength:
|
|
33
|
+
Enabled: false
|
|
34
|
+
|
|
35
|
+
Metrics/BlockLength:
|
|
36
|
+
Enabled: false
|
|
37
|
+
|
|
38
|
+
Style/Documentation:
|
|
39
|
+
Enabled: false
|
|
40
|
+
|
|
41
|
+
Style/SignalException:
|
|
42
|
+
Enabled: false
|
|
43
|
+
|
|
44
|
+
Style/FrozenStringLiteralComment:
|
|
45
|
+
Enabled: false
|
|
46
|
+
|
|
47
|
+
Style/MultilineBlockChain:
|
|
48
|
+
Enabled: false
|
|
49
|
+
|
|
50
|
+
Metrics/MethodLength:
|
|
51
|
+
Enabled: false
|
|
52
|
+
|
|
53
|
+
Metrics/CyclomaticComplexity:
|
|
54
|
+
Enabled: false
|
|
55
|
+
|
|
56
|
+
Metrics/ModuleLength:
|
|
57
|
+
Enabled: false
|
|
58
|
+
|
|
59
|
+
Metrics/ClassLength:
|
|
60
|
+
Enabled: false
|
|
61
|
+
|
|
62
|
+
Sorbet/FalseSigil:
|
|
63
|
+
Enabled: false
|
|
64
|
+
|
|
65
|
+
Lint/UnusedMethodArgument:
|
|
66
|
+
Enabled: false
|
|
67
|
+
|
|
68
|
+
Metrics/AbcSize:
|
|
69
|
+
Enabled: false
|
|
70
|
+
|
|
71
|
+
Style/GuardClause:
|
|
72
|
+
Enabled: false
|
|
73
|
+
|
|
74
|
+
Style/NumericPredicate:
|
|
75
|
+
Enabled: false
|
|
76
|
+
|
|
77
|
+
Metrics/PerceivedComplexity:
|
|
78
|
+
Enabled: false
|
|
79
|
+
|
|
80
|
+
Metrics/ParameterLists:
|
|
81
|
+
Enabled: false
|
|
82
|
+
|
|
83
|
+
Style/SuperArguments:
|
|
84
|
+
Enabled: false
|
|
85
|
+
|
|
86
|
+
Style/ArgumentsForwarding:
|
|
87
|
+
Enabled: false
|
|
88
|
+
|
|
89
|
+
Naming/BlockForwarding:
|
|
90
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
See https://github.com/rubyatscale/chatwerk/releases
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Martin Emde
|
|
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,68 @@
|
|
|
1
|
+
# Chatwerk
|
|
2
|
+
|
|
3
|
+
Chatwerk provides AI tool integration for the [QueryPackwerk](https://github.com/rubyatscale/query_packwerk) gem. It adds a Model Context Protocol (MCP) server that allows AI tools like Cursor IDE to access information about your Packwerk packages, dependencies, and violations.
|
|
4
|
+
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> This is an early prerelease version. We'll continue to update it as we develop. Contributions and feedback are welcome!
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Install the gem, either add in to your packwerk'd application's Gemfile:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
$ bundle add chatwerk
|
|
14
|
+
$ bundle install
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
or install it on its own:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
$ gem install chatwerk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Starting the MCP Server
|
|
26
|
+
|
|
27
|
+
You can test the inspector to see if it's working
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
$ chatwerk inspect
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Connecting with Cursor IDE
|
|
34
|
+
|
|
35
|
+
To use Chatwerk with Cursor:
|
|
36
|
+
|
|
37
|
+
1. In Cursor, open Settings > MCP
|
|
38
|
+
|
|
39
|
+
2. Add a new MCP connection as a command
|
|
40
|
+
Name: `chatwerk`
|
|
41
|
+
Command: `chatwerk mcp`
|
|
42
|
+
|
|
43
|
+
3. Ask Cursor to check all the tools on packwerk. Give it an example pack name (partial strings work)
|
|
44
|
+
|
|
45
|
+
### Example Queries for Cursor
|
|
46
|
+
|
|
47
|
+
Once connected, you can ask Cursor questions about your Packwerk structure:
|
|
48
|
+
|
|
49
|
+
- "What are all the packages in this codebase?"
|
|
50
|
+
- "Tell me about the dependencies of package X"
|
|
51
|
+
- "What packages depend on package Y?"
|
|
52
|
+
- "Show me all the violations for package Z"
|
|
53
|
+
- "How difficult would it be to separate package X from its dependencies?"
|
|
54
|
+
- "What code patterns are used to access Constant on package Y?"
|
|
55
|
+
|
|
56
|
+
## Development
|
|
57
|
+
|
|
58
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
|
59
|
+
|
|
60
|
+
Run `bin/inspect`
|
|
61
|
+
|
|
62
|
+
## Contributing
|
|
63
|
+
|
|
64
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rubyatscale/chatwerk.
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/config.ru
ADDED
data/exe/chatwerk
ADDED
data/lib/chatwerk/api.rb
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'yaml'
|
|
5
|
+
|
|
6
|
+
module Chatwerk
|
|
7
|
+
module Api
|
|
8
|
+
class << self
|
|
9
|
+
def print_env
|
|
10
|
+
msg = <<~MESSAGE
|
|
11
|
+
PWD: #{Dir.pwd}
|
|
12
|
+
PWD from ENV: #{ENV.fetch('PWD', nil)}
|
|
13
|
+
MESSAGE
|
|
14
|
+
Helpers.chdir do
|
|
15
|
+
msg << "Chdir'd to #{Helpers.pwd}\n"
|
|
16
|
+
end
|
|
17
|
+
msg << ENV.to_h.map { |key, value| "#{key}=#{value}" }.join("\n")
|
|
18
|
+
msg
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def packages(package_path: '')
|
|
22
|
+
packages = Helpers.all_packages(package_path)
|
|
23
|
+
|
|
24
|
+
if packages.empty?
|
|
25
|
+
has_packwerk_yml = File.exist?('packwerk.yml')
|
|
26
|
+
Views::NoPackagesView.render(has_packwerk_yml:)
|
|
27
|
+
else
|
|
28
|
+
Views::PackagesView.render(packages:)
|
|
29
|
+
end
|
|
30
|
+
rescue StandardError => e
|
|
31
|
+
raise Chatwerk::Error.new(e, package_path:)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def package(package_path:)
|
|
35
|
+
package = Helpers.find_package(package_path)
|
|
36
|
+
|
|
37
|
+
Views::PackageView.render(package:)
|
|
38
|
+
rescue StandardError => e
|
|
39
|
+
raise Chatwerk::Error.new(e, package_path:)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def package_todos(package_path:, constant_name: '')
|
|
43
|
+
package = Helpers.find_package(package_path)
|
|
44
|
+
constant_name = Helpers.normalize_constant_name(constant_name)
|
|
45
|
+
violations = package.todos
|
|
46
|
+
|
|
47
|
+
if constant_name.empty?
|
|
48
|
+
Views::ViolationsListView.render(package:, violations:)
|
|
49
|
+
else
|
|
50
|
+
Views::ViolationsDetailsView.render(package:, violations:, constant_name:)
|
|
51
|
+
end
|
|
52
|
+
rescue StandardError => e
|
|
53
|
+
raise Chatwerk::Error.new(e, package_path:, constant_name:)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def package_violations(package_path:, constant_name: '')
|
|
57
|
+
package = Helpers.find_package(package_path)
|
|
58
|
+
constant_name = Helpers.normalize_constant_name(constant_name)
|
|
59
|
+
violations = package.violations
|
|
60
|
+
|
|
61
|
+
if constant_name.empty?
|
|
62
|
+
Views::ViolationsListView.render(package:, violations:)
|
|
63
|
+
else
|
|
64
|
+
Views::ViolationsDetailsView.render(package:, violations:, constant_name:)
|
|
65
|
+
end
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
raise Chatwerk::Error.new(e, package_path:, constant_name:)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/chatwerk/cli.rb
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# typed: false
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'thor'
|
|
5
|
+
|
|
6
|
+
module Chatwerk
|
|
7
|
+
# CLI interface for Chatwerk using Thor
|
|
8
|
+
class Cli < Thor
|
|
9
|
+
desc 'mcp', 'Start the Model Context Protocol server in stdio mode'
|
|
10
|
+
def mcp
|
|
11
|
+
require_relative 'mcp'
|
|
12
|
+
require 'mcp/transports/stdio'
|
|
13
|
+
server = Chatwerk::Mcp.server
|
|
14
|
+
transport = MCP::Transports::StdioTransport.new(server)
|
|
15
|
+
transport.open
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
desc 'inspect [WORKING_DIRECTORY]', 'Run the MCP inspector with an optional working directory path (defaults to current directory)'
|
|
19
|
+
def inspect(working_directory = nil)
|
|
20
|
+
pwd = working_directory || Dir.pwd
|
|
21
|
+
system("npx @modelcontextprotocol/inspector -e PWD=#{pwd} bundle exec exe/chatwerk mcp")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc 'print_env', 'Show current environment details'
|
|
25
|
+
def print_env
|
|
26
|
+
puts Api.print_env
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
desc 'packages [PACKAGE_PATH]', 'List all valid packwerk packages, optionally filtered by package path'
|
|
30
|
+
def packages(package_path = nil)
|
|
31
|
+
puts Api.packages(package_path:)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
desc 'package PACKAGE_PATH', 'Show details for a specific package'
|
|
35
|
+
def package(package_path)
|
|
36
|
+
puts Api.package(package_path:)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
desc 'package_todos PACKAGE_PATH [CONSTANT_NAME]', 'Show dependency violations FROM this package TO others'
|
|
40
|
+
def package_todos(package_path, constant_name = nil)
|
|
41
|
+
puts Api.package_todos(package_path:, constant_name:)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
desc 'package_violations PACKAGE_PATH [CONSTANT_NAME]', 'Show dependency violations TO this package FROM others'
|
|
45
|
+
def package_violations(package_path, constant_name = nil)
|
|
46
|
+
puts Api.package_violations(package_path:, constant_name:)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
desc 'version', 'Display Chatwerk version'
|
|
50
|
+
def version
|
|
51
|
+
puts "Chatwerk #{Chatwerk::VERSION}"
|
|
52
|
+
end
|
|
53
|
+
map %w[--version -v] => :version
|
|
54
|
+
|
|
55
|
+
def self.exit_on_failure?
|
|
56
|
+
true
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chatwerk
|
|
4
|
+
class Error < RuntimeError
|
|
5
|
+
def initialize(error, package_path: nil, **args)
|
|
6
|
+
message =
|
|
7
|
+
if package_path && !package_path.empty?
|
|
8
|
+
"There was a problem finding or accessing #{package_path.inspect}"
|
|
9
|
+
else
|
|
10
|
+
'There was a problem accessing package information'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
super(<<~ERROR)
|
|
14
|
+
#{message}
|
|
15
|
+
Error: #{error}
|
|
16
|
+
|
|
17
|
+
* Please ensure that the package path is correct.
|
|
18
|
+
* Check that there is a package.yml file in the given directory.
|
|
19
|
+
* Check that the path is a project root relative path that doesn't start with a slash.
|
|
20
|
+
* Try calling the packages tool with the package_path to see if it is valid.
|
|
21
|
+
ERROR
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Chatwerk
|
|
2
|
+
module Helpers
|
|
3
|
+
def chdir(&)
|
|
4
|
+
Dir.chdir(env_pwd, &)
|
|
5
|
+
end
|
|
6
|
+
module_function :chdir
|
|
7
|
+
|
|
8
|
+
def env_pwd
|
|
9
|
+
path = ENV.fetch('PWD', pwd)
|
|
10
|
+
# Use realpath if the path exists, otherwise fall back to expand_path
|
|
11
|
+
File.directory?(path) ? File.realpath(path) : File.expand_path(path)
|
|
12
|
+
end
|
|
13
|
+
module_function :env_pwd
|
|
14
|
+
|
|
15
|
+
def pwd
|
|
16
|
+
File.realpath(Dir.pwd)
|
|
17
|
+
end
|
|
18
|
+
module_function :pwd
|
|
19
|
+
|
|
20
|
+
def normalize_package_path(package_path)
|
|
21
|
+
package_path = package_path.to_s.strip
|
|
22
|
+
package_path = package_path.delete_prefix('/')
|
|
23
|
+
package_path = package_path.delete_suffix('/package.yml')
|
|
24
|
+
package_path = package_path.delete_suffix('/package_todo.yml')
|
|
25
|
+
package_path.delete_suffix('/')
|
|
26
|
+
end
|
|
27
|
+
module_function :normalize_package_path
|
|
28
|
+
|
|
29
|
+
def normalize_constant_name(constant_name)
|
|
30
|
+
constant_name = constant_name.to_s.strip
|
|
31
|
+
return '' if constant_name.empty?
|
|
32
|
+
|
|
33
|
+
constant_name.sub(/^(::)?/, '::')
|
|
34
|
+
end
|
|
35
|
+
module_function :normalize_constant_name
|
|
36
|
+
|
|
37
|
+
def all_packages(path_pattern = '')
|
|
38
|
+
path_pattern = normalize_package_path(path_pattern)
|
|
39
|
+
if path_pattern.empty?
|
|
40
|
+
QueryPackwerk::Packages.all.to_a
|
|
41
|
+
else
|
|
42
|
+
QueryPackwerk::Packages.where(name: Regexp.new(Regexp.escape(path_pattern))).to_a
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
module_function :all_packages
|
|
46
|
+
|
|
47
|
+
def find_package(path_pattern)
|
|
48
|
+
packages = all_packages(path_pattern)
|
|
49
|
+
|
|
50
|
+
if packages.empty?
|
|
51
|
+
raise "Unable to find a package for #{path_pattern.inspect}."
|
|
52
|
+
elsif packages.size == 1
|
|
53
|
+
packages.first
|
|
54
|
+
else
|
|
55
|
+
# Return an exact match even if it's a subset of another match
|
|
56
|
+
# e.g. packs/payments and packs/payments_api
|
|
57
|
+
exact_match = packages.find { |package| package.name == path_pattern }
|
|
58
|
+
return exact_match if exact_match
|
|
59
|
+
|
|
60
|
+
raise Chatwerk::Error, <<~ERROR
|
|
61
|
+
Found multiple packages for #{path_pattern.inspect}:
|
|
62
|
+
|
|
63
|
+
#{packages.map(&:name).join("\n")}
|
|
64
|
+
|
|
65
|
+
Please use full path to specify the correct package.
|
|
66
|
+
ERROR
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
module_function :find_package
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/chatwerk/mcp.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'mcp'
|
|
2
|
+
|
|
3
|
+
module Chatwerk
|
|
4
|
+
# MCP Server setup
|
|
5
|
+
class Mcp
|
|
6
|
+
def self.server
|
|
7
|
+
# Set up the server with all tools
|
|
8
|
+
MCP::Server.new(
|
|
9
|
+
name: name,
|
|
10
|
+
version: Chatwerk::VERSION,
|
|
11
|
+
tools: [
|
|
12
|
+
Tools::PrintEnvTool,
|
|
13
|
+
Tools::PackagesTool,
|
|
14
|
+
Tools::PackageTool,
|
|
15
|
+
Tools::PackageTodosTool,
|
|
16
|
+
Tools::PackageViolationsTool
|
|
17
|
+
]
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.name
|
|
22
|
+
'chatwerk'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
|
|
5
|
+
module Chatwerk
|
|
6
|
+
module Tools
|
|
7
|
+
# Package todos (violations FROM this package) tool
|
|
8
|
+
class PackageTodosTool < MCP::Tool
|
|
9
|
+
description <<~DESC
|
|
10
|
+
Find code that violates dependency boundaries FROM this package TO other packages.
|
|
11
|
+
|
|
12
|
+
Output formats:
|
|
13
|
+
- Without constant_name: List of violated constants with counts
|
|
14
|
+
Example: "::OtherPackage::SomeClass # 3 violations"
|
|
15
|
+
- With constant_name: Detailed examples and locations
|
|
16
|
+
Example:
|
|
17
|
+
::OtherPackage::SomeClass
|
|
18
|
+
example: OtherPackage::SomeClass.new
|
|
19
|
+
files:
|
|
20
|
+
- app/services/my_service.rb
|
|
21
|
+
DESC
|
|
22
|
+
|
|
23
|
+
input_schema(
|
|
24
|
+
properties: {
|
|
25
|
+
package_path: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: "The relative path of a directory containing a package.yml file (e.g. 'packs/product_services/payments/origination_banks')."
|
|
28
|
+
},
|
|
29
|
+
constant_name: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: "The name of a constant to filter the results by. If provided, a more detailed list of code usage examples will be returned. (e.g. '::OtherPackage::SomeClass')"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
required: ['package_path']
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
class << self
|
|
38
|
+
def call(package_path:, server_context:, constant_name: nil)
|
|
39
|
+
Helpers.chdir
|
|
40
|
+
result = Api.package_todos(package_path: package_path, constant_name: constant_name)
|
|
41
|
+
|
|
42
|
+
MCP::Tool::Response.new([{
|
|
43
|
+
type: 'text',
|
|
44
|
+
text: result
|
|
45
|
+
}])
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
|
|
5
|
+
module Chatwerk
|
|
6
|
+
module Tools
|
|
7
|
+
# Show package details tool
|
|
8
|
+
class PackageTool < MCP::Tool
|
|
9
|
+
description <<~DESC
|
|
10
|
+
Show the details for a specific package.
|
|
11
|
+
|
|
12
|
+
Output format:
|
|
13
|
+
- Package details, including dependencies and configuration
|
|
14
|
+
DESC
|
|
15
|
+
|
|
16
|
+
input_schema(
|
|
17
|
+
properties: {
|
|
18
|
+
package_path: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: "A full relative package path (e.g. 'packs/product_services/payments/banks')."
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
required: ['package_path']
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
def call(package_path:, server_context:)
|
|
28
|
+
Helpers.chdir
|
|
29
|
+
result = Api.package(package_path: package_path)
|
|
30
|
+
|
|
31
|
+
MCP::Tool::Response.new([{
|
|
32
|
+
type: 'text',
|
|
33
|
+
text: result
|
|
34
|
+
}])
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
|
|
5
|
+
module Chatwerk
|
|
6
|
+
module Tools
|
|
7
|
+
# Package violations (violations TO this package) tool
|
|
8
|
+
class PackageViolationsTool < MCP::Tool
|
|
9
|
+
description <<~DESC
|
|
10
|
+
Find code that violates dependency boundaries TO this package FROM other packages.
|
|
11
|
+
|
|
12
|
+
Output formats:
|
|
13
|
+
- Without constant_name: List of violated constants with counts
|
|
14
|
+
Example: "::ThisPackage::SomeClass: 3 violations"
|
|
15
|
+
- With constant_name: Detailed examples and locations
|
|
16
|
+
Example:
|
|
17
|
+
# Constant `::ThisPackage::SomeClass`
|
|
18
|
+
## Example:
|
|
19
|
+
ThisPackage::SomeClass.new
|
|
20
|
+
### Files:
|
|
21
|
+
app/services/other_service.rb
|
|
22
|
+
DESC
|
|
23
|
+
|
|
24
|
+
input_schema(
|
|
25
|
+
properties: {
|
|
26
|
+
package_path: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: "The relative path of a directory containing a package.yml file (e.g. 'packs/product_services/payments/origination_banks'). AKA a 'pack' or 'package'."
|
|
29
|
+
},
|
|
30
|
+
constant_name: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: 'The name of a constant to filter the results by. If provided, a more detailed list of code usage examples will be returned.'
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
required: ['package_path']
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
def call(package_path:, server_context:, constant_name: nil)
|
|
40
|
+
Helpers.chdir
|
|
41
|
+
result = Api.package_violations(package_path: package_path, constant_name: constant_name)
|
|
42
|
+
|
|
43
|
+
MCP::Tool::Response.new([{
|
|
44
|
+
type: 'text',
|
|
45
|
+
text: result
|
|
46
|
+
}])
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
|
|
5
|
+
module Chatwerk
|
|
6
|
+
module Tools
|
|
7
|
+
# List packages tool
|
|
8
|
+
class PackagesTool < MCP::Tool
|
|
9
|
+
description <<~DESC
|
|
10
|
+
List all valid packwerk packages (aka packs) in the project.
|
|
11
|
+
Use this to find or list packages, optionally matching a substring of the package_path.
|
|
12
|
+
DESC
|
|
13
|
+
|
|
14
|
+
input_schema(
|
|
15
|
+
properties: {
|
|
16
|
+
package_path: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
description: "A partial package path name to constrain the results (e.g. 'packs/product_services/payments/banks' or 'payments/banks')."
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
required: []
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
def call(server_context:, package_path: nil)
|
|
26
|
+
Helpers.chdir
|
|
27
|
+
result = Api.packages(package_path: package_path)
|
|
28
|
+
|
|
29
|
+
MCP::Tool::Response.new([{
|
|
30
|
+
type: 'text',
|
|
31
|
+
text: result
|
|
32
|
+
}])
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'mcp'
|
|
2
|
+
require_relative '../helpers'
|
|
3
|
+
|
|
4
|
+
module Chatwerk
|
|
5
|
+
module Tools
|
|
6
|
+
# Print environment information tool
|
|
7
|
+
class PrintEnvTool < MCP::Tool
|
|
8
|
+
description 'Get the current working directory and environment path of the MCP server, ensuring correct directory context'
|
|
9
|
+
|
|
10
|
+
input_schema(
|
|
11
|
+
properties: {},
|
|
12
|
+
required: []
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
def call(server_context:)
|
|
17
|
+
msg = <<~MESSAGE
|
|
18
|
+
Relay these exact details to the user:
|
|
19
|
+
Current Directory: #{Dir.pwd}
|
|
20
|
+
Environment: #{ENV.fetch('PWD', nil)}
|
|
21
|
+
MESSAGE
|
|
22
|
+
Helpers.chdir do
|
|
23
|
+
msg << "Chdir'd to #{Helpers.pwd}\n"
|
|
24
|
+
end
|
|
25
|
+
msg << ENV.to_h.map { |key, value| "#{key}=#{value}" }.join("\n")
|
|
26
|
+
|
|
27
|
+
MCP::Tool::Response.new([{
|
|
28
|
+
type: 'text',
|
|
29
|
+
text: msg
|
|
30
|
+
}])
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chatwerk
|
|
4
|
+
module Views
|
|
5
|
+
class BaseView
|
|
6
|
+
def self.render(**data)
|
|
7
|
+
new(**data).render
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :data, :output
|
|
11
|
+
|
|
12
|
+
def initialize(**data)
|
|
13
|
+
@data = data
|
|
14
|
+
@output = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def render
|
|
18
|
+
@output = nil
|
|
19
|
+
temp = template(**data)
|
|
20
|
+
return temp if @output.nil? # allow templates to return a string instead
|
|
21
|
+
|
|
22
|
+
@output
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def template(**data)
|
|
28
|
+
raise NotImplementedError, 'Subclasses must implement the #template method'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def say(message = '')
|
|
32
|
+
@output ||= +''
|
|
33
|
+
@output << message << "\n"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def method_missing(name, *args, **kwargs, &block)
|
|
37
|
+
@data[name]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def respond_to_missing?(name, include_private = false)
|
|
41
|
+
data.key?(name) || super
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def format_count(count, singular, plural = nil)
|
|
45
|
+
if count == 1
|
|
46
|
+
"1 #{singular}"
|
|
47
|
+
else
|
|
48
|
+
plural ||= "#{singular}s"
|
|
49
|
+
"#{count} #{plural}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chatwerk
|
|
4
|
+
module Views
|
|
5
|
+
class NoPackagesView < BaseView
|
|
6
|
+
def template(has_packwerk_yml: false)
|
|
7
|
+
if has_packwerk_yml
|
|
8
|
+
<<~MESSAGE
|
|
9
|
+
0 packages found.
|
|
10
|
+
`packwerk.yml` file exists in project root: #{Chatwerk::Helpers.pwd}
|
|
11
|
+
|
|
12
|
+
* Check that the project root is correct.
|
|
13
|
+
* Make sure that packwerk is initialized correctly.
|
|
14
|
+
* Make sure at least one package is defined.
|
|
15
|
+
MESSAGE
|
|
16
|
+
else
|
|
17
|
+
<<~MESSAGE
|
|
18
|
+
This project does not appear to be using packwerk.
|
|
19
|
+
`packwerk.yml` file does not exist in project root: #{Chatwerk::Helpers.pwd}
|
|
20
|
+
|
|
21
|
+
* Check that the project root is correct.
|
|
22
|
+
* Check to make sure that packwerk is installed and initialized correctly.
|
|
23
|
+
MESSAGE
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chatwerk
|
|
4
|
+
module Views
|
|
5
|
+
class NoViolationsView < BaseView
|
|
6
|
+
def template(package:, constant_name: nil)
|
|
7
|
+
if constant_name.nil?
|
|
8
|
+
<<~MESSAGE
|
|
9
|
+
No violations found in #{package.name.inspect}.
|
|
10
|
+
MESSAGE
|
|
11
|
+
else
|
|
12
|
+
<<~MESSAGE
|
|
13
|
+
No violations found in #{package.name.inspect} for #{constant_name.inspect}.
|
|
14
|
+
Ensure that constant_name is given in the format of "::ConstantName" or "::ConstantName::NestedConstant".
|
|
15
|
+
MESSAGE
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chatwerk
|
|
4
|
+
module Views
|
|
5
|
+
class PackageView < BaseView
|
|
6
|
+
def template(package:, package_path: nil)
|
|
7
|
+
consumers = QueryPackwerk::Packages.all.to_a.select { |p| p.dependency_names.include?(package.name) }.map(&:name)
|
|
8
|
+
consumers += package.consumer_names
|
|
9
|
+
consumers.uniq.sort!
|
|
10
|
+
|
|
11
|
+
say YAML.dump({
|
|
12
|
+
name: package.name,
|
|
13
|
+
enforce_dependencies: package.enforce_dependencies,
|
|
14
|
+
enforce_privacy: package.enforce_privacy,
|
|
15
|
+
owner: package.owner,
|
|
16
|
+
metadata: package.metadata,
|
|
17
|
+
dependencies: package.dependency_names,
|
|
18
|
+
consumers:,
|
|
19
|
+
todos_count: package.todos.count,
|
|
20
|
+
violations_count: package.violations.count
|
|
21
|
+
}.transform_keys(&:to_s))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chatwerk
|
|
4
|
+
module Views
|
|
5
|
+
class PackagesView < BaseView
|
|
6
|
+
def template(packages:, package_path: nil)
|
|
7
|
+
if packages.empty?
|
|
8
|
+
"No packages found matching #{package_path.inspect}\n"
|
|
9
|
+
else
|
|
10
|
+
packages.map { |p| "#{p.name}\n" }.sort.join
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chatwerk
|
|
4
|
+
module Views
|
|
5
|
+
class ViolationsDetailsView < BaseView
|
|
6
|
+
def template(package:, violations:, constant_name:)
|
|
7
|
+
relevant_violations = violations.anonymous_sources_with_locations.select { |c, _| c.start_with?(constant_name) }
|
|
8
|
+
|
|
9
|
+
if relevant_violations.empty?
|
|
10
|
+
Views::NoViolationsView.render(package:, constant_name:)
|
|
11
|
+
else
|
|
12
|
+
grep_formatted_violations(relevant_violations)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def grep_formatted_violations(relevant_violations)
|
|
19
|
+
# Group violations by file
|
|
20
|
+
files_to_violations = {}
|
|
21
|
+
|
|
22
|
+
relevant_violations.each do |constant, source_info|
|
|
23
|
+
say "No sources found for #{constant}" if source_info.empty?
|
|
24
|
+
source_info.each do |code, files|
|
|
25
|
+
say "No files found for #{constant} with code: #{code}" if files.empty?
|
|
26
|
+
|
|
27
|
+
files.each do |file_with_line|
|
|
28
|
+
file, line = file_with_line.split(':')
|
|
29
|
+
files_to_violations[file] ||= []
|
|
30
|
+
files_to_violations[file] << { constant:, code:, line: line }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Output violations grouped by file
|
|
36
|
+
files_to_violations.sort.map do |file, violations|
|
|
37
|
+
lines = violations.sort_by { |v| v[:line].to_i }.map do |violation|
|
|
38
|
+
"#{violation[:line]}: #{violation[:code]}"
|
|
39
|
+
end.join("\n")
|
|
40
|
+
|
|
41
|
+
"#{file}\n#{lines}\n"
|
|
42
|
+
end.join("\n")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chatwerk
|
|
4
|
+
module Views
|
|
5
|
+
class ViolationsListView < BaseView
|
|
6
|
+
# The anonymous_source_counts method returns a hash as follows:
|
|
7
|
+
# {
|
|
8
|
+
# "::Core::User" => {
|
|
9
|
+
# "User.find(_)" => 2,
|
|
10
|
+
# "User.create(_)" => 1
|
|
11
|
+
# }
|
|
12
|
+
# "::Core::Product" => {
|
|
13
|
+
# "Product.find(_)" => 1,
|
|
14
|
+
# "Product.create(_)" => 1
|
|
15
|
+
# }
|
|
16
|
+
# }
|
|
17
|
+
def template(package:, violations:)
|
|
18
|
+
sums = violations.anonymous_source_counts.transform_values do |source_counts|
|
|
19
|
+
source_counts.values.sum
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if sums.empty?
|
|
23
|
+
NoViolationsView.render(package:)
|
|
24
|
+
else
|
|
25
|
+
sums.sort_by { |_, count| -count }.map do |constant_name, count|
|
|
26
|
+
"#{constant_name} (#{format_count(count, 'violation')})\n"
|
|
27
|
+
end.join
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Chatwerk
|
|
2
|
+
module Views
|
|
3
|
+
autoload :NoPackagesView, 'chatwerk/views/no_packages_view'
|
|
4
|
+
autoload :NoViolationsView, 'chatwerk/views/no_violations_view'
|
|
5
|
+
autoload :PackageView, 'chatwerk/views/package_view'
|
|
6
|
+
autoload :PackagesView, 'chatwerk/views/packages_view'
|
|
7
|
+
autoload :ViolationsDetailsView, 'chatwerk/views/violations_details_view'
|
|
8
|
+
autoload :ViolationsListView, 'chatwerk/views/violations_list_view'
|
|
9
|
+
end
|
|
10
|
+
end
|
data/lib/chatwerk.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'zeitwerk'
|
|
5
|
+
require 'query_packwerk'
|
|
6
|
+
require 'sorbet-runtime'
|
|
7
|
+
require 'chatwerk/version'
|
|
8
|
+
|
|
9
|
+
loader = Zeitwerk::Loader.for_gem
|
|
10
|
+
loader.setup
|
|
11
|
+
|
|
12
|
+
# Chatwerk provides integration between QueryPackwerk and AI tools
|
|
13
|
+
# via the Model Context Protocol (MCP) server.
|
|
14
|
+
module Chatwerk
|
|
15
|
+
class NotFoundError < Error; end
|
|
16
|
+
end
|
data/mise.toml
ADDED
data/sig/chatwerk.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: chatwerk
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Gusto Engineering
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: mcp
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: query_packwerk
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.1'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.1'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: sorbet-runtime
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: thor
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: zeitwerk
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
description: MCP server and API for integrating Packwerk with AI tools like Cursor.
|
|
83
|
+
email:
|
|
84
|
+
- dev@gusto.com
|
|
85
|
+
executables:
|
|
86
|
+
- chatwerk
|
|
87
|
+
extensions: []
|
|
88
|
+
extra_rdoc_files: []
|
|
89
|
+
files:
|
|
90
|
+
- ".rspec"
|
|
91
|
+
- ".rubocop.yml"
|
|
92
|
+
- CHANGELOG.md
|
|
93
|
+
- LICENSE.txt
|
|
94
|
+
- README.md
|
|
95
|
+
- Rakefile
|
|
96
|
+
- config.ru
|
|
97
|
+
- exe/chatwerk
|
|
98
|
+
- lib/chatwerk.rb
|
|
99
|
+
- lib/chatwerk/api.rb
|
|
100
|
+
- lib/chatwerk/cli.rb
|
|
101
|
+
- lib/chatwerk/error.rb
|
|
102
|
+
- lib/chatwerk/helpers.rb
|
|
103
|
+
- lib/chatwerk/mcp.rb
|
|
104
|
+
- lib/chatwerk/tools/package_todos_tool.rb
|
|
105
|
+
- lib/chatwerk/tools/package_tool.rb
|
|
106
|
+
- lib/chatwerk/tools/package_violations_tool.rb
|
|
107
|
+
- lib/chatwerk/tools/packages_tool.rb
|
|
108
|
+
- lib/chatwerk/tools/print_env_tool.rb
|
|
109
|
+
- lib/chatwerk/version.rb
|
|
110
|
+
- lib/chatwerk/views.rb
|
|
111
|
+
- lib/chatwerk/views/base_view.rb
|
|
112
|
+
- lib/chatwerk/views/no_packages_view.rb
|
|
113
|
+
- lib/chatwerk/views/no_violations_view.rb
|
|
114
|
+
- lib/chatwerk/views/package_view.rb
|
|
115
|
+
- lib/chatwerk/views/packages_view.rb
|
|
116
|
+
- lib/chatwerk/views/violations_details_view.rb
|
|
117
|
+
- lib/chatwerk/views/violations_list_view.rb
|
|
118
|
+
- mise.toml
|
|
119
|
+
- sig/chatwerk.rbs
|
|
120
|
+
homepage: https://github.com/rubyatscale/chatwerk
|
|
121
|
+
licenses:
|
|
122
|
+
- MIT
|
|
123
|
+
metadata:
|
|
124
|
+
allowed_push_host: https://rubygems.org
|
|
125
|
+
homepage_uri: https://github.com/rubyatscale/chatwerk
|
|
126
|
+
source_code_uri: https://github.com/rubyatscale/chatwerk
|
|
127
|
+
changelog_uri: https://github.com/rubyatscale/chatwerk/blob/main/CHANGELOG.md
|
|
128
|
+
rdoc_options: []
|
|
129
|
+
require_paths:
|
|
130
|
+
- lib
|
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
132
|
+
requirements:
|
|
133
|
+
- - ">="
|
|
134
|
+
- !ruby/object:Gem::Version
|
|
135
|
+
version: 3.1.0
|
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
137
|
+
requirements:
|
|
138
|
+
- - ">="
|
|
139
|
+
- !ruby/object:Gem::Version
|
|
140
|
+
version: '0'
|
|
141
|
+
requirements: []
|
|
142
|
+
rubygems_version: 3.7.1
|
|
143
|
+
specification_version: 4
|
|
144
|
+
summary: 'Chatwerk: AI integration for Packwerk'
|
|
145
|
+
test_files: []
|