a11y_agent 0.0.5.pre.alpha.4 → 0.0.12

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: 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
  }