immosquare-cleaner 1.1.0 → 1.2.1
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/bin/immosquare-cleaner +12 -8
- data/lib/immosquare-cleaner/directory_cleaner.rb +6 -2
- data/lib/immosquare-cleaner/hook.rb +36 -13
- data/lib/immosquare-cleaner/markdown.rb +29 -2
- data/lib/immosquare-cleaner/version.rb +1 -1
- data/linters/eslint.config.mjs +14 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ce05c959aa1bf3f812d6f676b2864037f59668a97da382b9bd1d7c7b13b6bde6
|
|
4
|
+
data.tar.gz: f6d6650dbbea335a474030d9808043820de93d25c6829f0423aaea1758a8d034
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7d261eb4d38439c234c8836c5e04f56c3172277ea330722d352288e4b55ae2432d17e14ccb9db350ba6686d2016b9aeebe3c9eec98e61f0f7b85fe686ebf5a45
|
|
7
|
+
data.tar.gz: 6214f6c06cc2b1155a4272b12fd95d6a0531245b456eb904ac1c04b63a2e4b642745399e26004ddbbdff6301c0d0ba6257618bdc5d041fe00440bafbebe0d1f7
|
data/bin/immosquare-cleaner
CHANGED
|
@@ -41,16 +41,18 @@ OptionParser.new do |parser|
|
|
|
41
41
|
end.parse!
|
|
42
42
|
|
|
43
43
|
##============================================================##
|
|
44
|
-
## Hook mode : the edited
|
|
45
|
-
##
|
|
46
|
-
##
|
|
47
|
-
##
|
|
48
|
-
## hook silently with exit 0, so the
|
|
44
|
+
## Hook mode : the edited files come from the agent's JSON payload
|
|
45
|
+
## (one file, or several for a Codex patch). Linters write their
|
|
46
|
+
## reports on stdout, which some agents (Gemini CLI, Codex) parse
|
|
47
|
+
## as the hook's JSON answer: everything goes to stderr instead.
|
|
48
|
+
## No supported file ends the hook silently with exit 0, so the
|
|
49
|
+
## agent is never blocked.
|
|
49
50
|
##============================================================##
|
|
50
51
|
if options[:hook]
|
|
51
|
-
|
|
52
|
-
exit(0) if
|
|
52
|
+
hook_files = ImmosquareCleaner::Hook.file_paths($stdin.read)
|
|
53
|
+
exit(0) if hook_files.empty?
|
|
53
54
|
$stdout.reopen($stderr)
|
|
55
|
+
file_path = hook_files.first
|
|
54
56
|
else
|
|
55
57
|
file_path = ARGV[0]
|
|
56
58
|
end
|
|
@@ -95,7 +97,9 @@ end
|
|
|
95
97
|
## We can now call the clean method, passing the file absolute path and options
|
|
96
98
|
##============================================================##
|
|
97
99
|
file_path = File.expand_path(file_path)
|
|
98
|
-
if
|
|
100
|
+
if options[:hook]
|
|
101
|
+
hook_files.each {|path| ImmosquareCleaner.clean(path) }
|
|
102
|
+
elsif File.directory?(file_path)
|
|
99
103
|
##============================================================##
|
|
100
104
|
## Run from the cleaned directory: the project's
|
|
101
105
|
## config/initializers/immosquare-cleaner.rb (exclude_files) is
|
|
@@ -27,7 +27,10 @@ module ImmosquareCleaner
|
|
|
27
27
|
## EXCLUDED_PATHS : paths relative to the root — generated or
|
|
28
28
|
## vendored content that repositories commit
|
|
29
29
|
## (db/schema.rb, Tauri schemas...) and test
|
|
30
|
-
## fixtures that must stay as written
|
|
30
|
+
## fixtures that must stay as written: YAML
|
|
31
|
+
## fixtures hold ERB tags Prettier would break,
|
|
32
|
+
## sample files are compared byte for byte.
|
|
33
|
+
## Test code itself is cleaned like any source.
|
|
31
34
|
##============================================================##
|
|
32
35
|
EXCLUDED_DIRS = [
|
|
33
36
|
".git",
|
|
@@ -47,9 +50,10 @@ module ImmosquareCleaner
|
|
|
47
50
|
"app/assets/images",
|
|
48
51
|
"db",
|
|
49
52
|
"public",
|
|
53
|
+
"spec/fixtures",
|
|
50
54
|
"src-tauri/gen",
|
|
51
55
|
"src-tauri/permissions/autogenerated",
|
|
52
|
-
"test"
|
|
56
|
+
"test/fixtures"
|
|
53
57
|
].freeze
|
|
54
58
|
EXCLUDED_FILES = [
|
|
55
59
|
"npm-shrinkwrap.json",
|
|
@@ -2,36 +2,59 @@ require "json"
|
|
|
2
2
|
|
|
3
3
|
module ImmosquareCleaner
|
|
4
4
|
##============================================================##
|
|
5
|
-
## `immosquare-cleaner --hook` : the
|
|
5
|
+
## `immosquare-cleaner --hook` : the files to clean come from the
|
|
6
6
|
## JSON an AI agent pipes on stdin after an edit, not from ARGV.
|
|
7
7
|
##
|
|
8
|
-
## Each agent
|
|
8
|
+
## Each agent describes the edit differently:
|
|
9
9
|
## - Claude Code, Gemini CLI : tool_input.file_path
|
|
10
10
|
## - Kimi Code : tool_input.path
|
|
11
11
|
## - Grok : toolInput.file_path
|
|
12
12
|
## - Cursor (afterFileEdit) : file_path at the top level
|
|
13
|
-
##
|
|
13
|
+
## - Codex (apply_patch) : tool_input.command holds the patch,
|
|
14
|
+
## one `*** Update File: <path>` (or
|
|
15
|
+
## Add File / Move to) line per file
|
|
16
|
+
## Relative paths are resolved against tool_input.workdir (Codex),
|
|
17
|
+
## then the payload's `cwd`.
|
|
14
18
|
##
|
|
15
19
|
## A hook must never block the agent: anything that is not a
|
|
16
|
-
## supported file (
|
|
17
|
-
## .env...) is
|
|
20
|
+
## supported file (deleted file, directory, symlink, LICENSE,
|
|
21
|
+
## .env...) is dropped instead of reported. A symlink is never
|
|
22
|
+
## followed, so an edit cannot reformat a file outside the project.
|
|
18
23
|
##============================================================##
|
|
19
24
|
module Hook
|
|
20
25
|
PAYLOAD_KEYS = ["tool_input", "toolInput"].freeze
|
|
21
26
|
PATH_KEYS = ["file_path", "path", "target_file"].freeze
|
|
27
|
+
PATCH_LINE = /^\*\*\* (?:Add File|Update File|Move to): (.+)$/
|
|
22
28
|
|
|
23
|
-
def self.
|
|
29
|
+
def self.file_paths(raw_payload)
|
|
24
30
|
payload = JSON.parse(raw_payload.to_s)
|
|
25
|
-
return if !payload.is_a?(Hash)
|
|
31
|
+
return [] if !payload.is_a?(Hash)
|
|
26
32
|
|
|
27
|
-
sources
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
sources = PAYLOAD_KEYS.map {|key| payload[key] }.grep(Hash)
|
|
34
|
+
cwd = payload["cwd"].is_a?(String) && !payload["cwd"].empty? ? payload["cwd"] : Dir.pwd
|
|
35
|
+
workdir = sources.map {|source| source["workdir"] }.find {|value| value.is_a?(String) && !value.empty? }
|
|
36
|
+
base_dir = workdir ? File.expand_path(workdir, cwd) : cwd
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
paths = patch_paths(sources)
|
|
39
|
+
paths = [direct_path(sources + [payload])].compact if paths.empty?
|
|
40
|
+
paths.map {|path| File.expand_path(path, base_dir) }.uniq.select {|path| File.file?(path) && !File.symlink?(path) && ImmosquareCleaner.supported?(path) }
|
|
33
41
|
rescue JSON::ParserError
|
|
34
|
-
|
|
42
|
+
[]
|
|
35
43
|
end
|
|
44
|
+
|
|
45
|
+
def self.direct_path(sources)
|
|
46
|
+
sources.lazy.flat_map {|source| PATH_KEYS.map {|key| source[key] } }.find {|value| value.is_a?(String) && !value.empty? }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
##============================================================##
|
|
50
|
+
## Codex sends the patch as a string, older versions as an argv
|
|
51
|
+
## array (`["apply_patch", "*** Begin Patch..."]`)
|
|
52
|
+
##============================================================##
|
|
53
|
+
def self.patch_paths(sources)
|
|
54
|
+
patch = sources.map {|source| source["command"] }.find {|value| value.is_a?(String) || value.is_a?(Array) }
|
|
55
|
+
Array(patch).grep(String).join("\n").scan(PATCH_LINE).flatten.map(&:strip)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private_class_method(:direct_path, :patch_paths)
|
|
36
59
|
end
|
|
37
60
|
end
|
|
@@ -144,6 +144,33 @@ module ImmosquareCleaner
|
|
|
144
144
|
nil
|
|
145
145
|
end
|
|
146
146
|
|
|
147
|
+
##============================================================##
|
|
148
|
+
## Splits a table row on its delimiting pipes. A pipe escaped as
|
|
149
|
+
## `\|` is cell content (GFM), not a delimiter: splitting on it
|
|
150
|
+
## would add a column to the whole table. A trailing empty field
|
|
151
|
+
## only exists when the row ends with a delimiting pipe, which
|
|
152
|
+
## closes the row rather than opening an extra cell.
|
|
153
|
+
##============================================================##
|
|
154
|
+
def table_cells(line)
|
|
155
|
+
cells = [+""]
|
|
156
|
+
escaped = false
|
|
157
|
+
line.strip.delete_prefix("|").each_char do |char|
|
|
158
|
+
if escaped
|
|
159
|
+
cells.last << char
|
|
160
|
+
escaped = false
|
|
161
|
+
elsif char == "\\"
|
|
162
|
+
cells.last << char
|
|
163
|
+
escaped = true
|
|
164
|
+
elsif char == "|"
|
|
165
|
+
cells << +""
|
|
166
|
+
else
|
|
167
|
+
cells.last << char
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
cells.pop if cells.size > 1 && cells.last.empty?
|
|
171
|
+
cells.map(&:strip)
|
|
172
|
+
end
|
|
173
|
+
|
|
147
174
|
##============================================================##
|
|
148
175
|
## we want to clean the markdown files to have a uniform style
|
|
149
176
|
## for the tables.
|
|
@@ -161,9 +188,9 @@ module ImmosquareCleaner
|
|
|
161
188
|
## above a column of row labels, a value that does not apply).
|
|
162
189
|
## Rejecting empty cells would shift every following cell one
|
|
163
190
|
## column to the left and silently file values under the wrong
|
|
164
|
-
## header.
|
|
191
|
+
## header.
|
|
165
192
|
##============================================================##
|
|
166
|
-
cells = line
|
|
193
|
+
cells = table_cells(line)
|
|
167
194
|
|
|
168
195
|
##============================================================##
|
|
169
196
|
## We increase the size of the array if needed
|
data/linters/eslint.config.mjs
CHANGED
|
@@ -134,6 +134,20 @@ export default [
|
|
|
134
134
|
"no-undef": 0
|
|
135
135
|
}
|
|
136
136
|
},
|
|
137
|
+
//============================================================//
|
|
138
|
+
// JSX files: ESLint only lints .js/.mjs/.cjs by default, so a
|
|
139
|
+
// .jsx file matched by no `files` pattern is skipped with
|
|
140
|
+
// "File ignored because no matching configuration was supplied".
|
|
141
|
+
// The common rules above carry no `files` key and apply here too.
|
|
142
|
+
//============================================================//
|
|
143
|
+
{
|
|
144
|
+
files: ["**/*.jsx"],
|
|
145
|
+
languageOptions: {
|
|
146
|
+
parserOptions: {
|
|
147
|
+
ecmaFeatures: { jsx: true }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
137
151
|
erb.configs.recommended,
|
|
138
152
|
{
|
|
139
153
|
linterOptions: {
|