immosquare-cleaner 1.0.1 → 1.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 +4 -4
- data/LICENSE +21 -0
- data/bin/immosquare-cleaner +18 -1
- data/lib/immosquare-cleaner/directory_cleaner.rb +1 -0
- data/lib/immosquare-cleaner/hook.rb +37 -0
- data/lib/immosquare-cleaner/processors/go.rb +31 -0
- data/lib/immosquare-cleaner/version.rb +1 -1
- data/lib/immosquare-cleaner.rb +3 -0
- data/linters/erb_lint/custom_single_line_if_modifier.rb +6 -6
- data/linters/rubocop/cop/custom_cops/style/comment_normalization.rb +15 -4
- data/linters/rubocop-4.0.7.yml +13 -0
- data/linters/rubocop.yml +13 -0
- metadata +16 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 684fefa6f6061c2cfd2a30c10852463e2a8a0605dc8d4078fe9cb8f136bd3f97
|
|
4
|
+
data.tar.gz: 9437ae1b6de815f2b97d5dd10f61fb3ad412e6989969f73a10b481b9560efd8b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d0962e5e3df0bf9493abeed5871e85fa8146787aec41b09ec37a322ccd88fd7d2454d1d08bb97aa03cf153f1e6ff75809b6d7f286fab0bf1f3ad5e4095f15291
|
|
7
|
+
data.tar.gz: 53315a827d4a2030c1ce6e05bd70c24ded051f9259056fd695261c99e3c3d4e969c89543fb46498e5dfd39ff3a8ec076300352c41860491e020255f8b9edd83f
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 immosquare
|
|
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/bin/immosquare-cleaner
CHANGED
|
@@ -31,16 +31,33 @@ OptionParser.new do |parser|
|
|
|
31
31
|
parser.on("-p", "--prevent-concurrent-write", "Prevent concurrent write") do
|
|
32
32
|
options[:prevent_concurrent_write] = true
|
|
33
33
|
end
|
|
34
|
+
parser.on("--hook", "Read the edited file from an AI agent hook payload on stdin") do
|
|
35
|
+
options[:hook] = true
|
|
36
|
+
end
|
|
34
37
|
parser.on("-h", "--help", "Prints this help") do
|
|
35
38
|
puts(parser)
|
|
36
39
|
exit
|
|
37
40
|
end
|
|
38
41
|
end.parse!
|
|
39
42
|
|
|
43
|
+
##============================================================##
|
|
44
|
+
## Hook mode : the edited file comes from the agent's JSON payload.
|
|
45
|
+
## Linters write their reports on stdout, which some agents
|
|
46
|
+
## (Gemini CLI) parse as the hook's JSON answer: everything goes
|
|
47
|
+
## to stderr instead. An unsupported or missing file ends the
|
|
48
|
+
## hook silently with exit 0, so the agent is never blocked.
|
|
49
|
+
##============================================================##
|
|
50
|
+
if options[:hook]
|
|
51
|
+
file_path = ImmosquareCleaner::Hook.file_path($stdin.read)
|
|
52
|
+
exit(0) if file_path.nil?
|
|
53
|
+
$stdout.reopen($stderr)
|
|
54
|
+
else
|
|
55
|
+
file_path = ARGV[0]
|
|
56
|
+
end
|
|
57
|
+
|
|
40
58
|
##============================================================##
|
|
41
59
|
## Check if the file path is provided
|
|
42
60
|
##============================================================##
|
|
43
|
-
file_path = ARGV[0]
|
|
44
61
|
if !file_path
|
|
45
62
|
puts("Error: Please provide a file or directory path.")
|
|
46
63
|
exit(1)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module ImmosquareCleaner
|
|
4
|
+
##============================================================##
|
|
5
|
+
## `immosquare-cleaner --hook` : the file to clean comes from the
|
|
6
|
+
## JSON an AI agent pipes on stdin after an edit, not from ARGV.
|
|
7
|
+
##
|
|
8
|
+
## Each agent nests the edited path differently:
|
|
9
|
+
## - Claude Code, Gemini CLI : tool_input.file_path
|
|
10
|
+
## - Kimi Code : tool_input.path
|
|
11
|
+
## - Grok : toolInput.file_path
|
|
12
|
+
## - Cursor (afterFileEdit) : file_path at the top level
|
|
13
|
+
## A relative path is resolved against the payload's `cwd`.
|
|
14
|
+
##
|
|
15
|
+
## A hook must never block the agent: anything that is not a
|
|
16
|
+
## supported file (no path, deleted file, directory, LICENSE,
|
|
17
|
+
## .env...) is skipped instead of reported.
|
|
18
|
+
##============================================================##
|
|
19
|
+
module Hook
|
|
20
|
+
PAYLOAD_KEYS = ["tool_input", "toolInput"].freeze
|
|
21
|
+
PATH_KEYS = ["file_path", "path", "target_file"].freeze
|
|
22
|
+
|
|
23
|
+
def self.file_path(raw_payload)
|
|
24
|
+
payload = JSON.parse(raw_payload.to_s)
|
|
25
|
+
return if !payload.is_a?(Hash)
|
|
26
|
+
|
|
27
|
+
sources = PAYLOAD_KEYS.map {|key| payload[key] }.grep(Hash) + [payload]
|
|
28
|
+
path = sources.lazy.flat_map {|source| PATH_KEYS.map {|key| source[key] } }.find {|value| value.is_a?(String) && !value.empty? }
|
|
29
|
+
return if path.nil?
|
|
30
|
+
|
|
31
|
+
path = File.expand_path(path, payload["cwd"].is_a?(String) ? payload["cwd"] : Dir.pwd)
|
|
32
|
+
path if File.file?(path) && ImmosquareCleaner.supported?(path)
|
|
33
|
+
rescue JSON::ParserError
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "shellwords"
|
|
2
|
+
|
|
3
|
+
module ImmosquareCleaner
|
|
4
|
+
module Processors
|
|
5
|
+
class Go < Base
|
|
6
|
+
|
|
7
|
+
def self.match?(file_path)
|
|
8
|
+
file_path.end_with?(".go")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def run
|
|
12
|
+
if !system("which gofmt > /dev/null 2>&1")
|
|
13
|
+
warn("ERROR: gofmt is not installed. Please install Go with: brew install go")
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
##============================================================##
|
|
18
|
+
## gofmt flags:
|
|
19
|
+
## -s : apply gofmt's simplifications (redundant composite
|
|
20
|
+
## literal types, `s[a:len(s)]` → `s[a:]`...)
|
|
21
|
+
## -w : write the result back to the file in place
|
|
22
|
+
## Indentation stays in tabs: gofmt has no option for it, and
|
|
23
|
+
## converting to spaces would make every Go tool (gopls,
|
|
24
|
+
## `gofmt -l` in CI) flag the file as unformatted.
|
|
25
|
+
##============================================================##
|
|
26
|
+
launch_cmds(["gofmt -s -w #{Shellwords.escape(file_path)}"])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/immosquare-cleaner.rb
CHANGED
|
@@ -7,6 +7,7 @@ require "fileutils"
|
|
|
7
7
|
require_relative "immosquare-cleaner/configuration"
|
|
8
8
|
require_relative "immosquare-cleaner/markdown"
|
|
9
9
|
require_relative "immosquare-cleaner/directory_cleaner"
|
|
10
|
+
require_relative "immosquare-cleaner/hook"
|
|
10
11
|
|
|
11
12
|
##============================================================##
|
|
12
13
|
## Processors
|
|
@@ -19,6 +20,7 @@ require_relative "immosquare-cleaner/processors/json"
|
|
|
19
20
|
require_relative "immosquare-cleaner/processors/markdown"
|
|
20
21
|
require_relative "immosquare-cleaner/processors/shell"
|
|
21
22
|
require_relative "immosquare-cleaner/processors/rust"
|
|
23
|
+
require_relative "immosquare-cleaner/processors/go"
|
|
22
24
|
require_relative "immosquare-cleaner/processors/toml"
|
|
23
25
|
require_relative "immosquare-cleaner/processors/yaml"
|
|
24
26
|
require_relative "immosquare-cleaner/processors/prettier"
|
|
@@ -123,6 +125,7 @@ module ImmosquareCleaner
|
|
|
123
125
|
Processors::Markdown,
|
|
124
126
|
Processors::Shell,
|
|
125
127
|
Processors::Rust,
|
|
128
|
+
Processors::Go,
|
|
126
129
|
Processors::Toml
|
|
127
130
|
].freeze
|
|
128
131
|
|
|
@@ -74,7 +74,7 @@ module ERBLint
|
|
|
74
74
|
context = {
|
|
75
75
|
:new_code => "<%= #{new_code} %>",
|
|
76
76
|
:output_node => output_node,
|
|
77
|
-
:is_output_node =>
|
|
77
|
+
:is_output_node => output_erb?(output_node)
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
add_offense(full_range, MSG, context)
|
|
@@ -102,9 +102,9 @@ module ERBLint
|
|
|
102
102
|
## output_node should be <%= (output)
|
|
103
103
|
## end_node should be <% (not <%=)
|
|
104
104
|
##============================================================##
|
|
105
|
-
return false unless
|
|
106
|
-
return false unless
|
|
107
|
-
return false unless
|
|
105
|
+
return false unless statement_erb?(if_node)
|
|
106
|
+
return false unless output_erb?(output_node)
|
|
107
|
+
return false unless statement_erb?(end_node)
|
|
108
108
|
|
|
109
109
|
##============================================================##
|
|
110
110
|
## Extract and validate the code
|
|
@@ -133,7 +133,7 @@ module ERBLint
|
|
|
133
133
|
##============================================================##
|
|
134
134
|
## Check if ERB node is a statement (<% ... %>)
|
|
135
135
|
##============================================================##
|
|
136
|
-
def
|
|
136
|
+
def statement_erb?(erb_node)
|
|
137
137
|
indicator = erb_node.children.first
|
|
138
138
|
indicator.nil? || (indicator.respond_to?(:children) && indicator.children.first.nil?)
|
|
139
139
|
end
|
|
@@ -141,7 +141,7 @@ module ERBLint
|
|
|
141
141
|
##============================================================##
|
|
142
142
|
## Check if ERB node is an output (<%= ... %>)
|
|
143
143
|
##============================================================##
|
|
144
|
-
def
|
|
144
|
+
def output_erb?(erb_node)
|
|
145
145
|
indicator = erb_node.children.first
|
|
146
146
|
indicator.respond_to?(:children) && indicator.children.first == "="
|
|
147
147
|
end
|
|
@@ -104,16 +104,13 @@ module RuboCop
|
|
|
104
104
|
in_code = !in_code
|
|
105
105
|
body << text
|
|
106
106
|
content_seen = true
|
|
107
|
-
elsif
|
|
107
|
+
elsif verbatim_line?(text, in_code)
|
|
108
108
|
body << text
|
|
109
109
|
content_seen = true
|
|
110
110
|
elsif text == BLANK_LINE
|
|
111
111
|
body << BLANK_LINE if content_seen
|
|
112
112
|
elsif text.match?(USER_SEPARATOR)
|
|
113
113
|
body << INSIDE_SEPARATOR if content_seen
|
|
114
|
-
elsif text.match?(STRUCTURED_LINE) || text.match?(INDENTED_LINE)
|
|
115
|
-
body << text
|
|
116
|
-
content_seen = true
|
|
117
114
|
else
|
|
118
115
|
body << cleaned_line(text)
|
|
119
116
|
content_seen = true
|
|
@@ -123,6 +120,20 @@ module RuboCop
|
|
|
123
120
|
body
|
|
124
121
|
end
|
|
125
122
|
|
|
123
|
+
##============================================================##
|
|
124
|
+
## Lines kept as written. Precedence matters: inside a code
|
|
125
|
+
## block or on a raw line everything is verbatim, even "##" or
|
|
126
|
+
## a separator; elsewhere a blank line and a user separator win
|
|
127
|
+
## over the structured/indented patterns ("## ---" is a
|
|
128
|
+
## separator, not an indented line).
|
|
129
|
+
##============================================================##
|
|
130
|
+
def verbatim_line?(text, in_code)
|
|
131
|
+
return true if in_code || text.match?(RAW_LINE)
|
|
132
|
+
return false if text == BLANK_LINE || text.match?(USER_SEPARATOR)
|
|
133
|
+
|
|
134
|
+
text.match?(STRUCTURED_LINE) || text.match?(INDENTED_LINE)
|
|
135
|
+
end
|
|
136
|
+
|
|
126
137
|
##============================================================##
|
|
127
138
|
## Cleans up a standard text line:
|
|
128
139
|
## - "### foo" → "## foo" (collapse repeated #)
|
data/linters/rubocop-4.0.7.yml
CHANGED
|
@@ -32,6 +32,14 @@ Naming/FileName:
|
|
|
32
32
|
Enabled: false
|
|
33
33
|
Naming/MethodParameterName:
|
|
34
34
|
MinNameLength: 1
|
|
35
|
+
Naming/PredicateMethod:
|
|
36
|
+
AllowedMethods:
|
|
37
|
+
- call
|
|
38
|
+
- move_to_cache
|
|
39
|
+
- move_to_store
|
|
40
|
+
- save
|
|
41
|
+
- check_health
|
|
42
|
+
- elastic_import_with_progressbar
|
|
35
43
|
Layout/LeadingEmptyLines:
|
|
36
44
|
Enabled: false
|
|
37
45
|
Layout/InitialIndentation:
|
|
@@ -88,6 +96,11 @@ Style/NegatedIf:
|
|
|
88
96
|
Enabled: false
|
|
89
97
|
Style/FormatStringToken:
|
|
90
98
|
EnforcedStyle: template
|
|
99
|
+
Mode: conservative
|
|
100
|
+
Style/OptionalBooleanParameter:
|
|
101
|
+
AllowedMethods:
|
|
102
|
+
- respond_to_missing?
|
|
103
|
+
- perform
|
|
91
104
|
Style/NumericPredicate:
|
|
92
105
|
EnforcedStyle: comparison
|
|
93
106
|
Style/StringLiterals:
|
data/linters/rubocop.yml
CHANGED
|
@@ -37,6 +37,14 @@ Naming/FileName:
|
|
|
37
37
|
Enabled: false # Allow filenames with dashes (e.g. immosquare-cleaner.rb)
|
|
38
38
|
Naming/MethodParameterName:
|
|
39
39
|
MinNameLength: 1 # Allow single-character parameter names
|
|
40
|
+
Naming/PredicateMethod:
|
|
41
|
+
AllowedMethods:
|
|
42
|
+
- call
|
|
43
|
+
- move_to_cache # CarrierWave hooks: the gem calls them by these names and reads their boolean return.
|
|
44
|
+
- move_to_store
|
|
45
|
+
- save # ActiveModel contract: form objects expose `save` returning true/false, like ActiveRecord.
|
|
46
|
+
- check_health # Commands returning their success status (immosquare-railtie PumaManager, Elasticsearch import): a `?` name would hide the side effects.
|
|
47
|
+
- elastic_import_with_progressbar
|
|
40
48
|
|
|
41
49
|
#################### Layout ###########################
|
|
42
50
|
Layout/LeadingEmptyLines:
|
|
@@ -98,6 +106,11 @@ Style/NegatedIf:
|
|
|
98
106
|
Enabled: false # Allow rewriting `if !` as `unless`
|
|
99
107
|
Style/FormatStringToken:
|
|
100
108
|
EnforcedStyle: "template" ## Use %{my_string} interpolation, not %<my_string>s
|
|
109
|
+
Mode: conservative # Only strings passed to format/sprintf/%. In aggressive mode any literal containing `%<x>s` is flagged, including prose that lists placeholders (e.g. an AI prompt telling the model which tokens to preserve), where rewriting the token changes the text.
|
|
110
|
+
Style/OptionalBooleanParameter:
|
|
111
|
+
AllowedMethods:
|
|
112
|
+
- respond_to_missing?
|
|
113
|
+
- perform # Sidekiq and ActiveJob serialize `perform` arguments as JSON: keyword arguments are not reliably supported, and jobs already enqueued keep their positional shape.
|
|
101
114
|
Style/NumericPredicate:
|
|
102
115
|
EnforcedStyle: comparison # No `.zero?` `.negative?` `.positive?`
|
|
103
116
|
Style/StringLiterals:
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: immosquare-cleaner
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- immosquare
|
|
@@ -149,9 +149,11 @@ dependencies:
|
|
|
149
149
|
- - "<="
|
|
150
150
|
- !ruby/object:Gem::Version
|
|
151
151
|
version: '1000.0'
|
|
152
|
-
description:
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
description: immosquare-cleaner formats Ruby, ERB, JS/TS, Rust, Go, TOML, Markdown,
|
|
153
|
+
Shell, JSON, YAML and CSS files with one consistent house style, delegating each
|
|
154
|
+
format to the right tool (RuboCop, erb_lint, ESLint, rustfmt, gofmt, taplo, shfmt,
|
|
155
|
+
Prettier). Run it on a file, a whole repository, on save, or as a Claude Code, Cursor
|
|
156
|
+
or Gemini CLI hook so every file an AI agent writes comes out formatted.
|
|
155
157
|
email:
|
|
156
158
|
- jules@immosquare.com
|
|
157
159
|
executables:
|
|
@@ -160,13 +162,16 @@ extensions: []
|
|
|
160
162
|
extra_rdoc_files: []
|
|
161
163
|
files:
|
|
162
164
|
- ".erb_linters"
|
|
165
|
+
- LICENSE
|
|
163
166
|
- bin/immosquare-cleaner
|
|
164
167
|
- lib/immosquare-cleaner.rb
|
|
165
168
|
- lib/immosquare-cleaner/configuration.rb
|
|
166
169
|
- lib/immosquare-cleaner/directory_cleaner.rb
|
|
170
|
+
- lib/immosquare-cleaner/hook.rb
|
|
167
171
|
- lib/immosquare-cleaner/markdown.rb
|
|
168
172
|
- lib/immosquare-cleaner/processors/base.rb
|
|
169
173
|
- lib/immosquare-cleaner/processors/erb.rb
|
|
174
|
+
- lib/immosquare-cleaner/processors/go.rb
|
|
170
175
|
- lib/immosquare-cleaner/processors/javascript.rb
|
|
171
176
|
- lib/immosquare-cleaner/processors/json.rb
|
|
172
177
|
- lib/immosquare-cleaner/processors/markdown.rb
|
|
@@ -201,10 +206,13 @@ files:
|
|
|
201
206
|
- linters/rubocop/cop/custom_cops/style/kwarg_priority_order.rb
|
|
202
207
|
- linters/rubocop/cop/style/method_call_with_args_parentheses_override.rb
|
|
203
208
|
- package.json
|
|
204
|
-
homepage: https://github.com/immosquare/
|
|
209
|
+
homepage: https://github.com/immosquare/immosquare-cleaner
|
|
205
210
|
licenses:
|
|
206
211
|
- MIT
|
|
207
|
-
metadata:
|
|
212
|
+
metadata:
|
|
213
|
+
source_code_uri: https://github.com/immosquare/immosquare-cleaner
|
|
214
|
+
changelog_uri: https://github.com/immosquare/immosquare-cleaner/blob/main/CHANGELOG.md
|
|
215
|
+
bug_tracker_uri: https://github.com/immosquare/immosquare-cleaner/issues
|
|
208
216
|
rdoc_options: []
|
|
209
217
|
require_paths:
|
|
210
218
|
- lib
|
|
@@ -222,5 +230,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
222
230
|
requirements: []
|
|
223
231
|
rubygems_version: 4.0.22
|
|
224
232
|
specification_version: 4
|
|
225
|
-
summary:
|
|
233
|
+
summary: One command to format every file of a repository, built to run as an AI agent
|
|
234
|
+
hook.
|
|
226
235
|
test_files: []
|