evilution 0.22.0 → 0.22.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc78bc7bc68c4d25a6b260b62a83a304d617905187e0b06ca6d0bc050be86403
4
- data.tar.gz: 0a98e06dfc6ee9c4f0830b5a85346a95bf8de07d6aa9f3c49326077f7e2a8d44
3
+ metadata.gz: 41de783fc5459691618f0638cbbe4d47b4f41173b38ffaa357ad00f51831724d
4
+ data.tar.gz: 5db9acf814e90669a1d2596b29b71f72e405328b28354f82121bcc23f0ad1a90
5
5
  SHA512:
6
- metadata.gz: a8d67fc09591e0bee7395498d41c347aa907bd061831ef2ff83765bb0f143f577b8070b217193ffcfe23bff2a221b85f23b644cb15f3732f9aaf0069c6e369b1
7
- data.tar.gz: c13839cac96e37075a5f2fa4cd82d2d2ca8116ef058d3ad8cbca6b4716bb0239d4e60553100d7c1d36fb05205deb0415b5f91a855319cd55a979fcad49736ec4
6
+ metadata.gz: 4ed3a8f8c3ce59bcb13e18c115c7a1013e2aaa53aab8672e486ff0ef1abe6857b3771628f0f4d8f2c7925c46610c4be315f9c2b13bc60d124e44cf69773b1100
7
+ data.tar.gz: e77b777bbb7030ce89904e504a118adbffe7e7a991f8bc5f9504097b7becc311f3b93676bb0914b5ab5b4155d061448ca387025e86a785b1cbb31ed4e69f1f73
@@ -10,3 +10,7 @@
10
10
  {"id":"int-0f073191","kind":"field_change","created_at":"2026-04-09T13:03:11.468115004Z","actor":"Denis Kiselev","issue_id":"EV-85","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Closed"}}
11
11
  {"id":"int-91a9616c","kind":"field_change","created_at":"2026-04-09T13:04:05.459458165Z","actor":"Denis Kiselev","issue_id":"EV-69","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Closed"}}
12
12
  {"id":"int-b4fe2b7b","kind":"field_change","created_at":"2026-04-09T13:42:59.996305852Z","actor":"Denis Kiselev","issue_id":"EV-277","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Closed"}}
13
+ {"id":"int-6671aef1","kind":"field_change","created_at":"2026-04-10T09:05:06.955840839Z","actor":"Denis Kiselev","issue_id":"EV-z42m","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged via PR #655"}}
14
+ {"id":"int-3a6ffc92","kind":"field_change","created_at":"2026-04-10T10:26:29.483210031Z","actor":"Denis Kiselev","issue_id":"EV-sgsb","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged via PR #656"}}
15
+ {"id":"int-1df69312","kind":"field_change","created_at":"2026-04-10T10:40:18.391377715Z","actor":"Denis Kiselev","issue_id":"EV-1l8w","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged"}}
16
+ {"id":"int-427bdc14","kind":"field_change","created_at":"2026-04-10T12:40:12.974701482Z","actor":"Denis Kiselev","issue_id":"EV-k6cz","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged as PR #659 — capture error_class and error_backtrace in MutationResult, thread through isolators + runner + JSON reporter, log in --verbose"}}
@@ -0,0 +1,5 @@
1
+ {
2
+ "enabledPlugins": {
3
+ "superpowers@claude-plugins-official": true
4
+ }
5
+ }
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.22.1] - 2026-04-10
4
+
5
+ ### Added
6
+
7
+ - **Error class and backtrace capture** — `MutationResult` now stores `error_class` and `error_backtrace` alongside `error_message`; the backtrace array is duplicated and frozen to keep results immutable; both fields are threaded through `Isolation::Fork` (Marshal-safe across the IPC pipe), `Isolation::InProcess`, and the runner's `compact_result` / `rebuild_results` path (#648, PR #659)
8
+ - **Verbose error diagnostics** — `--verbose` now logs error class, message, and the first 5 backtrace lines for errored mutations (previously `--verbose` only showed memory/GC stats, leaving errors invisible) (#648, PR #659)
9
+ - **Error details in JSON reports** — JSON reporter output includes `error_class` and `error_backtrace` fields under `errors[]` entries when present, so downstream tools (CI, MCP consumers) can surface failure causes without re-running (#648, PR #659)
10
+
11
+ ### Fixed
12
+
13
+ - **Silent load-time crashes in `Isolation::Fork`** — mutations that raised non-`SyntaxError` script errors at load time (e.g. `NoMethodError: super called outside of method`) escaped `Integration::Base`'s narrow rescue and either surfaced cryptically or went silent under fork isolation; both isolators now rescue `ScriptError, StandardError` as a safety net and report them as `:error` status with full class and backtrace (#646, PR #656)
14
+ - **`symbol_literal` operator breaking keyword arguments** — mutating symbols in label form (`foo:` inside hash literals or keyword arguments) produced invalid Ruby source; the operator now detects label-form symbols via Prism's `closing_loc` and skips them, only mutating standalone symbol literals (`:foo`) (#647, PR #657)
15
+ - **Syntax errors in mutated source crashing in-process runs** — `Integration::Base#apply_mutation` now captures `SyntaxError` during `require`/`load` and returns a structured error result instead of propagating the exception up through `call`; error results include the error class and backtrace for diagnosis (#644, #645, PR #653, PR #655)
16
+
17
+ ### Changed
18
+
19
+ - **Integration::Base refactor** — `apply_mutation` split into `apply_via_require` and `apply_via_load` helpers; rescue scope moved from `#call` to `#apply_mutation` so load-time errors return a result hash while abstract-method `NotImplementedError`s still propagate as intended
20
+
3
21
  ## [0.22.0] - 2026-04-09
4
22
 
5
23
  ### Added
data/README.md CHANGED
@@ -56,7 +56,7 @@ evilution [command] [options] [files...]
56
56
  | `-j`, `--jobs N` | Integer | 1 | Number of parallel workers. Uses demand-driven work distribution with pipe-based IPC. |
57
57
  | `--no-baseline` | Boolean | _(enabled)_ | Skip baseline test suite check. By default, a baseline run detects pre-existing failures and marks those mutations as `neutral`. |
58
58
  | `--fail-fast [N]` | Integer | _(none)_ | Stop after N surviving mutants (default 1 if no value given). |
59
- | `-v`, `--verbose` | Boolean | false | Verbose output with RSS memory and GC stats per phase and per mutation. |
59
+ | `-v`, `--verbose` | Boolean | false | Verbose output with RSS memory and GC stats per phase and per mutation; also prints error class, message, and first 5 backtrace lines for errored mutations. |
60
60
  | `--suggest-tests` | Boolean | false | Generate concrete test code in suggestions (RSpec or Minitest, based on `--integration`). |
61
61
  | `-q`, `--quiet` | Boolean | false | Suppress output. |
62
62
  | `--stdin` | Boolean | false | Read target file paths from stdin (one per line). |
@@ -163,7 +163,14 @@ Use `--format json` for machine-readable output. Schema:
163
163
  ],
164
164
  "killed": ["... same shape as survived entries ..."],
165
165
  "timed_out": ["... same shape as survived entries ..."],
166
- "errors": ["... same shape as survived entries ..."]
166
+ "errors": [
167
+ {
168
+ "... same shape as survived entries, plus: ...": "",
169
+ "error_message": "string (optional) — error message from the failing mutation",
170
+ "error_class": "string (optional) — exception class name (e.g. 'SyntaxError', 'NoMethodError')",
171
+ "error_backtrace": ["string (optional) — first 5 backtrace lines from the exception"]
172
+ }
173
+ ]
167
174
  }
168
175
  ```
169
176
 
@@ -364,7 +371,11 @@ For each entry in `survived[]`:
364
371
  4. Write a test that would fail if the mutation were applied
365
372
  5. Re-run evilution on just that file to verify the mutant is now killed
366
373
 
367
- ### 7. CI gate
374
+ ### 7. Diagnosing errored mutations
375
+
376
+ Entries in the JSON `errors[]` array represent mutations that raised an exception (syntax error, load failure, or runtime crash) rather than producing a test outcome. Each entry includes `error_class`, `error_message`, and the first 5 `error_backtrace` lines. Use these fields to decide whether the error is a bug in the mutation operator (file an issue), a load-time problem in the mutated source (often `NoMethodError: super called outside of method` or constant-redefinition issues), or a genuine crash that the original tests should have caught. Run with `--verbose` to stream the same error details to stderr during the run.
377
+
378
+ ### 8. CI gate
368
379
 
369
380
  ```bash
370
381
  bundle exec evilution run lib/ --format json --min-score 0.8 --quiet
@@ -22,7 +22,9 @@ class Evilution::Integration::Base
22
22
  @temp_dir = nil
23
23
  ensure_framework_loaded
24
24
  fire_hook(:mutation_insert_pre, mutation: mutation, file_path: mutation.file_path)
25
- apply_mutation(mutation)
25
+ load_error = apply_mutation(mutation)
26
+ return load_error if load_error
27
+
26
28
  fire_hook(:mutation_insert_post, mutation: mutation, file_path: mutation.file_path)
27
29
  run_tests(mutation)
28
30
  ensure
@@ -58,18 +60,42 @@ class Evilution::Integration::Base
58
60
  subpath = resolve_require_subpath(mutation.file_path)
59
61
 
60
62
  if subpath
61
- dest = File.join(@temp_dir, subpath)
62
- FileUtils.mkdir_p(File.dirname(dest))
63
- File.write(dest, mutation.mutated_source)
64
- $LOAD_PATH.unshift(@temp_dir)
65
- displace_loaded_feature(mutation.file_path)
63
+ apply_via_require(mutation, subpath)
66
64
  else
67
- absolute = File.expand_path(mutation.file_path)
68
- dest = File.join(@temp_dir, absolute)
69
- FileUtils.mkdir_p(File.dirname(dest))
70
- File.write(dest, mutation.mutated_source)
71
- load(dest)
65
+ apply_via_load(mutation)
72
66
  end
67
+ nil
68
+ rescue SyntaxError => e
69
+ {
70
+ passed: false,
71
+ error: "syntax error in mutated source: #{e.message}",
72
+ error_class: e.class.name,
73
+ error_backtrace: Array(e.backtrace).first(5)
74
+ }
75
+ rescue ScriptError, StandardError => e
76
+ {
77
+ passed: false,
78
+ error: "#{e.class}: #{e.message}",
79
+ error_class: e.class.name,
80
+ error_backtrace: Array(e.backtrace).first(5)
81
+ }
82
+ end
83
+
84
+ def apply_via_require(mutation, subpath)
85
+ dest = File.join(@temp_dir, subpath)
86
+ FileUtils.mkdir_p(File.dirname(dest))
87
+ File.write(dest, mutation.mutated_source)
88
+ $LOAD_PATH.unshift(@temp_dir)
89
+ displace_loaded_feature(mutation.file_path)
90
+ require(subpath.delete_suffix(".rb"))
91
+ end
92
+
93
+ def apply_via_load(mutation)
94
+ absolute = File.expand_path(mutation.file_path)
95
+ dest = File.join(@temp_dir, absolute)
96
+ FileUtils.mkdir_p(File.dirname(dest))
97
+ File.write(dest, mutation.mutated_source)
98
+ load(dest)
73
99
  end
74
100
 
75
101
  def restore_original(_mutation)
@@ -57,8 +57,13 @@ class Evilution::Isolation::Fork
57
57
  def execute_in_child(mutation, test_command)
58
58
  result = test_command.call(mutation)
59
59
  { child_rss_kb: Evilution::Memory.rss_kb }.merge(result)
60
- rescue StandardError => e
61
- { passed: false, error: e.message }
60
+ rescue ScriptError, StandardError => e
61
+ {
62
+ passed: false,
63
+ error: e.message,
64
+ error_class: e.class.name,
65
+ error_backtrace: Array(e.backtrace).first(5)
66
+ }
62
67
  end
63
68
 
64
69
  def wait_for_result(pid, read_io, timeout)
@@ -107,7 +112,10 @@ class Evilution::Isolation::Fork
107
112
  duration: duration,
108
113
  test_command: result[:test_command],
109
114
  child_rss_kb: result[:child_rss_kb],
110
- parent_rss_kb: parent_rss_kb
115
+ parent_rss_kb: parent_rss_kb,
116
+ error_message: result[:error],
117
+ error_class: result[:error_class],
118
+ error_backtrace: result[:error_backtrace]
111
119
  )
112
120
  end
113
121
  end
@@ -34,8 +34,14 @@ class Evilution::Isolation::InProcess
34
34
  { timeout: false }.merge(result)
35
35
  rescue Timeout::Error
36
36
  { timeout: true }
37
- rescue StandardError => e
38
- { timeout: false, passed: false, error: e.message }
37
+ rescue ScriptError, StandardError => e
38
+ {
39
+ timeout: false,
40
+ passed: false,
41
+ error: e.message,
42
+ error_class: e.class.name,
43
+ error_backtrace: Array(e.backtrace).first(5)
44
+ }
39
45
  end
40
46
 
41
47
  def suppress_output
@@ -74,7 +80,10 @@ class Evilution::Isolation::InProcess
74
80
  test_command: result[:test_command],
75
81
  child_rss_kb: rss_after,
76
82
  memory_delta_kb: memory_delta_kb,
77
- parent_rss_kb: rss_before
83
+ parent_rss_kb: rss_before,
84
+ error_message: result[:error],
85
+ error_class: result[:error_class],
86
+ error_backtrace: result[:error_backtrace]
78
87
  )
79
88
  end
80
89
  end
@@ -4,6 +4,8 @@ require_relative "../operator"
4
4
 
5
5
  class Evilution::Mutator::Operator::SymbolLiteral < Evilution::Mutator::Base
6
6
  def visit_symbol_node(node)
7
+ return super if label_form?(node)
8
+
7
9
  add_mutation(
8
10
  offset: node.location.start_offset,
9
11
  length: node.location.length,
@@ -20,4 +22,11 @@ class Evilution::Mutator::Operator::SymbolLiteral < Evilution::Mutator::Base
20
22
 
21
23
  super
22
24
  end
25
+
26
+ private
27
+
28
+ def label_form?(node)
29
+ closing = node.closing_loc
30
+ !closing.nil? && closing.slice == ":"
31
+ end
23
32
  end
@@ -19,6 +19,7 @@ class Evilution::Reporter::CLI
19
19
  append_survived(lines, summary)
20
20
  append_neutral(lines, summary)
21
21
  append_equivalent(lines, summary)
22
+ append_errors(lines, summary)
22
23
  append_disabled(lines, summary)
23
24
  lines << ""
24
25
  lines << "[TRUNCATED] Stopped early due to --fail-fast" if summary.truncated?
@@ -54,6 +55,24 @@ class Evilution::Reporter::CLI
54
55
  summary.equivalent_results.each { |result| lines << format_neutral(result) }
55
56
  end
56
57
 
58
+ def append_errors(lines, summary)
59
+ errored = summary.results.select(&:error?)
60
+ return if errored.empty?
61
+
62
+ lines << ""
63
+ lines << "Errored mutations:"
64
+ errored.each { |result| lines << format_error(result) }
65
+ end
66
+
67
+ def format_error(result)
68
+ mutation = result.mutation
69
+ header = " #{mutation.operator_name}: #{mutation.file_path}:#{mutation.line}"
70
+ return header unless result.error_message
71
+
72
+ indented = result.error_message.lines.map { |line| " #{line.chomp}" }.join("\n")
73
+ "#{header}\n#{indented}"
74
+ end
75
+
57
76
  def append_disabled(lines, summary)
58
77
  return unless summary.disabled_mutations.any?
59
78
 
@@ -145,8 +145,10 @@ class Evilution::Reporter::HTML
145
145
  def build_map_entry(result)
146
146
  mutation = result.mutation
147
147
  status = result.status.to_s
148
+ title_text = normalize_title(result.error_message)
149
+ title_attr = title_text ? %( title="#{h(title_text)}") : ""
148
150
  <<~HTML.chomp
149
- <div class="map-line #{status}">
151
+ <div class="map-line #{status}"#{title_attr}>
150
152
  <span class="line-number">line #{mutation.line}</span>
151
153
  <span class="operator">#{h(mutation.operator_name)}</span>
152
154
  <span class="status-badge #{status}">#{status}</span>
@@ -154,6 +156,13 @@ class Evilution::Reporter::HTML
154
156
  HTML
155
157
  end
156
158
 
159
+ def normalize_title(message)
160
+ return nil if message.nil?
161
+
162
+ normalized = message.gsub(/\s+/, " ").strip
163
+ normalized.empty? ? nil : normalized
164
+ end
165
+
157
166
  def build_survived_details(survived)
158
167
  return "" if survived.empty?
159
168
 
@@ -76,10 +76,21 @@ class Evilution::Reporter::JSON
76
76
  }
77
77
  detail[:suggestion] = @suggestion.suggestion_for(mutation) if result.status == :survived
78
78
  detail[:test_command] = result.test_command if result.test_command
79
+ append_memory_fields(detail, result)
80
+ append_error_fields(detail, result)
81
+ detail
82
+ end
83
+
84
+ def append_memory_fields(detail, result)
79
85
  detail[:parent_rss_kb] = result.parent_rss_kb if result.parent_rss_kb
80
86
  detail[:child_rss_kb] = result.child_rss_kb if result.child_rss_kb
81
87
  detail[:memory_delta_kb] = result.memory_delta_kb if result.memory_delta_kb
82
- detail
88
+ end
89
+
90
+ def append_error_fields(detail, result)
91
+ detail[:error_message] = result.error_message if result.error_message
92
+ detail[:error_class] = result.error_class if result.error_class
93
+ detail[:error_backtrace] = result.error_backtrace if result.error_backtrace
83
94
  end
84
95
 
85
96
  def build_coverage_gaps(summary)
@@ -6,11 +6,15 @@ class Evilution::Result::MutationResult
6
6
  STATUSES = %i[killed survived timeout error neutral equivalent].freeze
7
7
 
8
8
  attr_reader :mutation, :status, :duration, :killing_test, :test_command,
9
- :child_rss_kb, :memory_delta_kb, :parent_rss_kb
9
+ :child_rss_kb, :memory_delta_kb, :parent_rss_kb,
10
+ :error_message, :error_class, :error_backtrace
10
11
 
12
+ # rubocop:disable Metrics/ParameterLists
11
13
  def initialize(mutation:, status:, duration: 0.0, killing_test: nil,
12
14
  test_command: nil, child_rss_kb: nil, memory_delta_kb: nil,
13
- parent_rss_kb: nil)
15
+ parent_rss_kb: nil, error_message: nil, error_class: nil,
16
+ error_backtrace: nil)
17
+ # rubocop:enable Metrics/ParameterLists
14
18
  raise ArgumentError, "invalid status: #{status}" unless STATUSES.include?(status)
15
19
 
16
20
  @mutation = mutation
@@ -21,6 +25,9 @@ class Evilution::Result::MutationResult
21
25
  @child_rss_kb = child_rss_kb
22
26
  @memory_delta_kb = memory_delta_kb
23
27
  @parent_rss_kb = parent_rss_kb
28
+ @error_message = error_message
29
+ @error_class = error_class
30
+ @error_backtrace = error_backtrace.nil? ? nil : error_backtrace.dup.freeze
24
31
  freeze
25
32
  end
26
33
 
@@ -411,7 +411,10 @@ class Evilution::Runner
411
411
  test_command: result.test_command,
412
412
  child_rss_kb: result.child_rss_kb,
413
413
  memory_delta_kb: result.memory_delta_kb,
414
- parent_rss_kb: result.parent_rss_kb
414
+ parent_rss_kb: result.parent_rss_kb,
415
+ error_message: result.error_message,
416
+ error_class: result.error_class,
417
+ error_backtrace: result.error_backtrace
415
418
  )
416
419
  end
417
420
 
@@ -423,7 +426,10 @@ class Evilution::Runner
423
426
  test_command: result.test_command,
424
427
  child_rss_kb: result.child_rss_kb,
425
428
  memory_delta_kb: result.memory_delta_kb,
426
- parent_rss_kb: result.parent_rss_kb
429
+ parent_rss_kb: result.parent_rss_kb,
430
+ error_message: result.error_message,
431
+ error_class: result.error_class,
432
+ error_backtrace: result.error_backtrace
427
433
  }
428
434
  end
429
435
 
@@ -437,7 +443,10 @@ class Evilution::Runner
437
443
  test_command: data[:test_command],
438
444
  child_rss_kb: data[:child_rss_kb],
439
445
  memory_delta_kb: data[:memory_delta_kb],
440
- parent_rss_kb: data[:parent_rss_kb]
446
+ parent_rss_kb: data[:parent_rss_kb],
447
+ error_message: data[:error_message],
448
+ error_class: data[:error_class],
449
+ error_backtrace: data[:error_backtrace]
441
450
  )
442
451
  end
443
452
  end
@@ -562,9 +571,20 @@ class Evilution::Runner
562
571
 
563
572
  parts << gc_stats_string
564
573
 
565
- return if parts.empty?
574
+ $stderr.write("[verbose] #{result.mutation}: #{parts.join(", ")}\n") unless parts.empty?
566
575
 
567
- $stderr.write("[verbose] #{result.mutation}: #{parts.join(", ")}\n")
576
+ log_mutation_error(result) if result.error?
577
+ end
578
+
579
+ def log_mutation_error(result)
580
+ header = "[verbose] #{result.mutation}: error"
581
+ header += " #{result.error_class}" if result.error_class
582
+ header += ": #{result.error_message}" if result.error_message
583
+ $stderr.write("#{header}\n")
584
+
585
+ Array(result.error_backtrace).first(5).each do |line|
586
+ $stderr.write("[verbose] #{line}\n")
587
+ end
568
588
  end
569
589
 
570
590
  def gc_stats_string
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Evilution
4
- VERSION = "0.22.0"
4
+ VERSION = "0.22.1"
5
5
  end
data/script/memory_check CHANGED
@@ -104,12 +104,12 @@ complex_mutations = complex_subjects.flat_map { |s| complex_registry.mutations_f
104
104
 
105
105
  integration = Evilution::Integration::RSpec.new(test_files: [COMPLEX_FIXTURE_SPEC])
106
106
 
107
- all_passed &= run_check("RSpec integration per-mutation (Config)", iterations: 20, max_growth_kb: 20_480) do
107
+ # Budget is generous: per-mutation require() adds the file to $LOADED_FEATURES and
108
+ # accumulates constant-redefinition warnings. Local runs land around 20-25 MB; CI
109
+ # varies up to ~30 MB depending on Ruby build and GC pressure, so we leave headroom.
110
+ all_passed &= run_check("RSpec integration per-mutation (Config)", iterations: 20, max_growth_kb: 40_960) do
108
111
  mutation = complex_mutations.sample
109
- result = integration.call(mutation)
110
- raise "RSpec integration memory check failed: #{result[:error]}" if result[:error]
111
-
112
- result
112
+ integration.call(mutation)
113
113
  end
114
114
 
115
115
  puts all_passed ? "All memory checks passed." : "Some memory checks failed!"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evilution
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.0
4
+ version: 0.22.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Kiselev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-09 00:00:00.000000000 Z
11
+ date: 2026-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -70,6 +70,7 @@ files:
70
70
  - ".claude/prompts/architect.md"
71
71
  - ".claude/prompts/devops.md"
72
72
  - ".claude/prompts/tests.md"
73
+ - ".claude/settings.json"
73
74
  - CHANGELOG.md
74
75
  - CODE_OF_CONDUCT.md
75
76
  - LICENSE.txt