a11y_agent 0.0.5.pre.alpha.4 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7001f5b3ad8390654fd9e2a2255f36e3d9c78730f919696395c9397b0bf92e8
4
- data.tar.gz: 1d1d506ea7dc1f9cb5532b70c2531836aadf92baca3db48aa82b5cced55b3148
3
+ metadata.gz: 2a7493a6bb46ce2e52d663e2b1972ac0664339be3317604fb5fb8957ff209403
4
+ data.tar.gz: 77ad379603264914462dcb118b1082b03a7d88bf6f346d3feac341fc15ef37b1
5
5
  SHA512:
6
- metadata.gz: 29738287670b7bcc1f0e34be727fd4cd775cd810af1375398d2e38817f51f2dec2ffb99761b499d3ac48d685882508f477e98689ff7aa82764c5d908058f112b
7
- data.tar.gz: acbf59aaf97914f9f432dcea8cffd99cf4dd56602cc43b8f968f1c6bbb65227dcb71ba4df3ed26f2dae6c7181993660cc9464d49a748be425a6f62f177513bf1
6
+ metadata.gz: 32fe6b574cc3d27f4e2d4aaa6e3ce7da83974c3a334e0df36e8458ca429bf5547c71a5169a723910bb7dfc2f74052945b9299d9189cda69a13aa92394eb5a644
7
+ data.tar.gz: 7fe424775861271b8bb689a0d4cd425153f8beb0933a9cdfe7cc16d6046ec619b332261742df9120019020351502216db69e29d105c921a4e1082123555a7121
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module A11yAgent
4
- VERSION = '0.0.5-alpha.4'
4
+ VERSION = '0.0.12'
5
5
  end
@@ -1,15 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'axe/core'
4
+ require 'axe/api/run'
3
5
  require 'dotenv/load'
4
6
  require 'diffy'
5
7
  require 'fileutils'
6
8
  require 'json'
7
9
  require 'open3'
8
10
  require 'rainbow/refinement'
11
+ require 'selenium-webdriver'
9
12
  require 'sublayer'
10
13
  require 'tty-prompt'
11
- require_relative '../generators/fix_a11y_generator'
12
- require_relative '../generators/hydrate_document_generator'
14
+ require_relative '../generators/confirmable_fix_generator'
13
15
 
14
16
  Diffy::Diff.default_format = :color
15
17
 
@@ -20,14 +22,7 @@ Diffy::Diff.default_format = :color
20
22
  # Sublayer.configuration.ai_model = "gemini-1.5-flash-latest"
21
23
 
22
24
  Sublayer.configuration.ai_provider = Sublayer::Providers::Claude
23
- Sublayer.configuration.ai_model = 'claude-3-haiku-20240307'
24
-
25
- CHOICES = [
26
- { key: 'y', name: 'approve and continue', value: :yes },
27
- { key: 'n', name: 'skip this change', value: :no },
28
- { key: 'r', name: 'retry with optional instructions', value: :retry },
29
- { key: 'q', name: 'quit; stop making changes', value: :quit }
30
- ].freeze
25
+ Sublayer.configuration.ai_model = 'claude-3-5-haiku-latest'
31
26
 
32
27
  module Sublayer
33
28
  module Agents
@@ -39,6 +34,7 @@ module Sublayer
39
34
  @issue_types = []
40
35
  @file = file
41
36
  @file_contents = File.read(@file)
37
+ @source_code = @file_contents
42
38
  @prompt = TTY::Prompt.new
43
39
  end
44
40
 
@@ -47,7 +43,7 @@ module Sublayer
47
43
  end
48
44
 
49
45
  check_status do
50
- load_issues unless run_axe.empty?
46
+ load_issues
51
47
  end
52
48
 
53
49
  goal_condition do
@@ -56,104 +52,79 @@ module Sublayer
56
52
  end
57
53
 
58
54
  step do
59
- @accessibility_issues.each { |issue| fix_issue_and_save(issue:) }
55
+ @accessibility_issues.each do |issue|
56
+ puts issue.description
57
+ fix_issue_and_save(issue:)
58
+ end
59
+
60
60
  exit 0
61
61
  end
62
62
 
63
63
  private
64
64
 
65
- def run_axe(file: @file)
66
- stdout, _stderr, _status = Open3.capture3("ts-node lib/bin/axe.ts #{file}")
67
- JSON.parse(stdout)
68
- end
69
-
70
65
  def load_issues
71
66
  Tempfile.create(['', File.extname(@file)]) do |tempfile|
72
- tempfile.write(hydrated_file)
67
+ tempfile.write(@file_contents)
73
68
  tempfile.rewind
74
69
 
75
- @accessibility_issues = run_axe(file: tempfile.path).map do |issue|
76
- %w[id impact tags helpUrl].each { |key| issue.delete(key) }
77
- issue
70
+ command = %(yarn --silent biome lint --reporter=json --only=a11y #{tempfile.path})
71
+ stdout, _stderr, _status = Open3.capture3(command, stdin_data: @file_contents)
72
+
73
+ @accessibility_issues = JSON.parse(stdout).fetch('diagnostics').map do |d|
74
+ OpenStruct.new(
75
+ description: d.fetch('description'),
76
+ location: d.fetch('location').fetch('span'),
77
+ snippet: d.fetch('location').fetch('sourceCode')[d.fetch('location').fetch('span')[0]..d.fetch('location').fetch('span')[1] - 1],
78
+ advice: d.fetch('advices').fetch('advices').map do |a|
79
+ a.fetch('log')[1][0].fetch('content') unless a.fetch('log', nil).nil?
80
+ end
81
+ )
78
82
  end
79
83
  end
80
84
 
81
85
  puts "🚨 Found #{@accessibility_issues.length} accessibility issues" unless @accessibility_issues.empty?
82
86
  end
83
87
 
84
- def hydrated_file
85
- puts "Loading fake data into #{@file}"
86
- hydrated = HydrateDocumentGenerator.new(contents: @file_contents, extension: File.extname(@file)).generate
87
- hydrated << "\n" until hydrated.end_with?("\n")
88
-
89
- print_diff(contents: @file_contents, fixed: hydrated, message: '📊 Changes made:')
90
- hydration_approved = @prompt.yes? 'Continue with updates?'
91
- hydrated = @file_contents unless hydration_approved
92
- hydrated
93
- end
94
-
95
88
  def fix_issue_and_save(issue:)
96
- updated_contents = File.read(@file)
97
-
98
- issue['nodes'].each do |node|
99
- user_input = nil
100
- fixed = nil
101
- additional_prompt = nil
102
- summary = node['failureSummary']
103
- node_issue = [summary, issue['help'], node['html']].join("\n\n")
104
-
105
- puts "🔍 #{issue['help']}"
106
- attempt = @prompt.yes? "Attempt to fix these issues in #{@file}?"
107
- next unless attempt
108
-
109
- until %i[yes no].include?(user_input)
110
- puts '🔧 Attempting a fix...'
111
- result = FixA11yGenerator.new(contents: updated_contents, issue: node_issue, extension: File.extname(@file),
112
- additional_prompt:).generate
113
- result << "\n" unless result.end_with?("\n")
114
-
115
- print_chunks(contents: updated_contents, fixed: result)
116
-
117
- user_input = @prompt.expand('Approve changes?', CHOICES)
118
-
119
- case user_input
120
- when :yes
121
- fixed = result
122
- when :no
123
- fixed = updated_contents
124
- when :retry
125
- additional_prompt = @prompt.ask('Additional instructions:')
126
- fixed = nil
127
- when :quit
128
- puts 'Quitting...'
129
- exit 0
130
- end
89
+ additional_instructions = nil
90
+ input = nil
91
+
92
+ until %i[fix skip].include?(input)
93
+ exit 0 if input == :exit
94
+ puts additional_instructions if additional_instructions
95
+
96
+ result = ConfirmableFixGenerator.new(lint_failure: issue, source_code: @source_code, additional_instructions:).generate
97
+ fixed = result.fixed + "\n"
98
+ puts Diffy::Diff.new(@source_code, fixed, context: 2).to_s(:color)
99
+
100
+ input = @prompt.expand('Apply the fix?', [
101
+ { key: 'y', name: 'Apply the fix', value: :fix },
102
+ { key: 'n', name: 'Skip the fix', value: :skip },
103
+ { key: 'e', name: 'Explain why', value: :explain },
104
+ { key: 'r', name: 'Retry with optional instructions', value: :retry },
105
+ { key: 'q', name: 'Exit', value: :exit }
106
+ ])
107
+
108
+ if input == :retry
109
+ additional_instructions = @prompt.ask('Provide additional instructions:')
110
+ next
111
+ elsif input == :explain
112
+ puts
113
+ puts 'Explanation'.bright
114
+ puts result.description
115
+ puts
116
+
117
+ continue = @prompt.yes? 'Continue with the fix?'
118
+ next unless continue
119
+ input = :fix
131
120
  end
132
-
133
- puts '📝 Saving changes...'
134
- File.write(@file, fixed) if fixed
135
121
  end
136
- end
137
122
 
138
- def print_diff(contents:, fixed:, message: '')
139
- puts message
140
- puts Diffy::Diff.new(contents, fixed)
141
- end
142
123
 
143
- def print_chunks(contents:, fixed:)
144
- Diffy::Diff.new(contents, fixed).each_chunk do |chunk|
145
- case chunk
146
- when /^\+/
147
- print chunk.to_s.green
148
- when /^-/
149
- print chunk.to_s.red
150
- else
151
- lines = chunk.to_s.split("\n")
152
- puts lines[0..2].join("\n")
153
- puts '...'
154
- puts lines[-3..].join("\n") if lines.length > 5
155
- end
156
- end
124
+ return unless input == :fix
125
+ puts '📝 Saving changes...'
126
+ File.write(@file, fixed)
127
+ @source_code = fixed
157
128
  end
158
129
  end
159
130
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dotenv/load'
4
+ require 'sublayer'
5
+
6
+ class ConfirmableFixGenerator < Sublayer::Generators::Base
7
+ llm_output_adapter type: :named_strings,
8
+ name: 'lint_fix',
9
+ description: 'A fix for lint failures',
10
+ item_name: 'lint_fix',
11
+ attributes: [
12
+ {
13
+ name: 'description',
14
+ description: 'A brief description of the fix and why it is important'
15
+ },
16
+ {
17
+ name: 'impact',
18
+ description: 'A brief explanation of how the fix impacts assistive technologies'
19
+ },
20
+ {
21
+ name: 'fixed',
22
+ description: 'The complete source code with the fix applied'
23
+ }
24
+ ]
25
+
26
+ def initialize(lint_failure:, source_code:, additional_instructions: nil)
27
+ super()
28
+ @source_code = source_code
29
+ @additional_instructions = additional_instructions
30
+ @failure_line = %(#{lint_failure.description} at span #{lint_failure.location[0]}:#{lint_failure.location[1]})
31
+ end
32
+
33
+ def prompt
34
+ <<-PROMPT
35
+ You are an expert at remediating lint errors in source code.
36
+ Generate a fix for the following lint failure in the provided source code.
37
+ Only fix one specified lint failure at a time, at the given position.
38
+
39
+ Source code:
40
+ #{@source_code}
41
+
42
+ Lint failure:
43
+ #{@failure_line}
44
+
45
+ Additional instructions (if any):
46
+ #{@additional_instructions}
47
+
48
+ For the fix provide:
49
+ - description: A brief description of the change and why it is important.
50
+ - fixed: the fixed source code with the issue resolved.
51
+ - impact: A description of how the fix impacts assistive technologies.
52
+
53
+ Provide your response is an object containing the above attributes.
54
+ PROMPT
55
+ end
56
+ end
@@ -54,8 +54,8 @@ namespace :release do
54
54
  # Extract all files to libexec, which is a common Homebrew practice for third-party tools
55
55
  libexec.install Dir["*"]
56
56
 
57
- system "bundle", "install", "--without", "development"
58
- system "gem", "build", "#{spec.name}.gemspec"
57
+ system "bundle", "install", "--gemfile", libexec/"Gemfile"
58
+ system "gem", "build", libexec/"#{spec.name}.gemspec"
59
59
  system "gem", "install", "--ignore-dependencies", "#{spec.name}-#{version}.gem"
60
60
 
61
61
  bin.install libexec/"exe/#{spec.name}"
@@ -64,7 +64,7 @@ namespace :release do
64
64
 
65
65
  test do
66
66
  # Simple test to check the version or a help command
67
- system "\#{bin}/a11y_agent", "--help"
67
+ system "\#{bin}/#{spec.name}", "--help"
68
68
  end
69
69
  end
70
70
  RUBY
data/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
- "devDependencies": {
3
- "@types/jsdom": "^21.1.7",
4
- "eslint": "^8.57.0",
5
- "eslint-plugin-import": "^2.29.1",
6
- "eslint-plugin-jsx-a11y": "^6.9.0",
7
- "globals": "^15.8.0",
8
- "typescript": "^5.5.4",
9
- "typescript-eslint": "^7.17.0"
10
- },
2
+ "name": "a11y-agent",
3
+ "version": "0.0.0",
4
+ "main": "index.js",
5
+ "repository": "git@github.com:AccessLint/a11y-agent.git",
6
+ "author": "Cameron Cundiff <github@ckundo.com>",
7
+ "license": "AGPL-3.0",
8
+ "private": true,
11
9
  "dependencies": {
12
- "axe-core": "^4.10.0",
13
- "canvas": "^2.11.2",
14
- "jsdom": "^24.1.1"
10
+ "@biomejs/biome": "1.9.4",
11
+ "eslint-plugin-vuejs-accessibility": "^2.4.1"
12
+ },
13
+ "devDependencies": {
14
+ "globals": "^15.9.0"
15
15
  }
16
16
  }