docopslab-dev 0.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.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.adoc +904 -0
  4. data/assets/config-packs/actionlint/base.yml +13 -0
  5. data/assets/config-packs/actionlint/project.yml +13 -0
  6. data/assets/config-packs/htmlproofer/base.yml +27 -0
  7. data/assets/config-packs/htmlproofer/project.yml +25 -0
  8. data/assets/config-packs/rubocop/base.yml +130 -0
  9. data/assets/config-packs/rubocop/project.yml +8 -0
  10. data/assets/config-packs/shellcheck/base.shellcheckrc +14 -0
  11. data/assets/config-packs/subtxt/ai-asciidoc-antipatterns.sub.txt +11 -0
  12. data/assets/config-packs/vale/asciidoc/ExplicitSectionIDs.yml +8 -0
  13. data/assets/config-packs/vale/asciidoc/ExtraLineBeforeLevel1.yml +7 -0
  14. data/assets/config-packs/vale/asciidoc/OneSentencePerLine.yml +8 -0
  15. data/assets/config-packs/vale/asciidoc/PreferSourceBlocks.yml +8 -0
  16. data/assets/config-packs/vale/asciidoc/ProperAdmonitions.yml +8 -0
  17. data/assets/config-packs/vale/asciidoc/ProperDLs.yml +7 -0
  18. data/assets/config-packs/vale/asciidoc/UncleanListStart.yml +8 -0
  19. data/assets/config-packs/vale/authoring/ButParagraph.yml +8 -0
  20. data/assets/config-packs/vale/authoring/ExNotEg.yml +8 -0
  21. data/assets/config-packs/vale/authoring/LiteralTerms.yml +20 -0
  22. data/assets/config-packs/vale/authoring/Spelling.yml +679 -0
  23. data/assets/config-packs/vale/base.ini +38 -0
  24. data/assets/config-packs/vale/config/scripts/ExplicitSectionIDs.tengo +56 -0
  25. data/assets/config-packs/vale/config/scripts/ExtraLineBeforeLevel1.tengo +121 -0
  26. data/assets/config-packs/vale/config/scripts/OneSentencePerLine.tengo +53 -0
  27. data/assets/config-packs/vale/project.ini +5 -0
  28. data/assets/hooks/pre-commit +63 -0
  29. data/assets/hooks/pre-push +72 -0
  30. data/assets/scripts/adoc_section_ids.rb +50 -0
  31. data/assets/scripts/build-common.sh +193 -0
  32. data/assets/scripts/build-docker.sh +64 -0
  33. data/assets/scripts/build.sh +56 -0
  34. data/assets/scripts/parse_jekyll_asciidoc_logs.rb +467 -0
  35. data/assets/templates/Gemfile +7 -0
  36. data/assets/templates/Rakefile +3 -0
  37. data/assets/templates/gitignore +69 -0
  38. data/assets/templates/jekyll-asciidoc-fix.prompt.yml +17 -0
  39. data/assets/templates/spellcheck.prompt.yml +16 -0
  40. data/docopslab-dev.gemspec +56 -0
  41. data/docs/agent/AGENTS.md +229 -0
  42. data/docs/agent/index.md +80 -0
  43. data/docs/agent/missions/conduct-release.md +224 -0
  44. data/docs/agent/missions/setup-new-project.md +250 -0
  45. data/docs/agent/roles/devops-release-engineer.md +152 -0
  46. data/docs/agent/roles/docops-engineer.md +193 -0
  47. data/docs/agent/roles/planner-architect.md +74 -0
  48. data/docs/agent/roles/product-engineer.md +153 -0
  49. data/docs/agent/roles/product-manager.md +130 -0
  50. data/docs/agent/roles/project-manager.md +139 -0
  51. data/docs/agent/roles/qa-testing-engineer.md +115 -0
  52. data/docs/agent/roles/tech-docs-manager.md +143 -0
  53. data/docs/agent/roles/tech-writer.md +163 -0
  54. data/docs/agent/skills/asciidoc.md +609 -0
  55. data/docs/agent/skills/code-commenting.md +347 -0
  56. data/docs/agent/skills/fix-broken-links.md +309 -0
  57. data/docs/agent/skills/fix-jekyll-asciidoc-build-errors.md +23 -0
  58. data/docs/agent/skills/fix-spelling-issues.md +13 -0
  59. data/docs/agent/skills/git.md +170 -0
  60. data/docs/agent/skills/github-issues.md +135 -0
  61. data/docs/agent/skills/product-release-rollback-and-patching.md +71 -0
  62. data/docs/agent/skills/rake-cli-dev.md +57 -0
  63. data/docs/agent/skills/readme-driven-dev.md +13 -0
  64. data/docs/agent/skills/release-history.md +29 -0
  65. data/docs/agent/skills/ruby.md +192 -0
  66. data/docs/agent/skills/schemagraphy-sgyml.md +18 -0
  67. data/docs/agent/skills/tests-running.md +25 -0
  68. data/docs/agent/skills/tests-writing.md +45 -0
  69. data/docs/agent/skills/write-the-docs.md +54 -0
  70. data/docs/agent/topics/common-project-paths.md +117 -0
  71. data/docs/agent/topics/dev-tooling-usage.md +202 -0
  72. data/docs/agent/topics/devops-ci-cd.md +55 -0
  73. data/docs/agent/topics/product-docs-deployment.md +25 -0
  74. data/lib/docopslab/dev/auto_fix_asciidoc.rb +46 -0
  75. data/lib/docopslab/dev/checkers.rb +108 -0
  76. data/lib/docopslab/dev/config_manager.rb +241 -0
  77. data/lib/docopslab/dev/file_utils.rb +140 -0
  78. data/lib/docopslab/dev/git_hooks.rb +140 -0
  79. data/lib/docopslab/dev/help.rb +121 -0
  80. data/lib/docopslab/dev/initializer.rb +95 -0
  81. data/lib/docopslab/dev/linters.rb +451 -0
  82. data/lib/docopslab/dev/log_parser.rb +31 -0
  83. data/lib/docopslab/dev/paths.rb +46 -0
  84. data/lib/docopslab/dev/script_manager.rb +136 -0
  85. data/lib/docopslab/dev/spell_check.rb +194 -0
  86. data/lib/docopslab/dev/sync_ops.rb +468 -0
  87. data/lib/docopslab/dev/tasks.rb +440 -0
  88. data/lib/docopslab/dev/tool_execution.rb +68 -0
  89. data/lib/docopslab/dev/version.rb +8 -0
  90. data/lib/docopslab/dev.rb +392 -0
  91. data/specs/data/default-manifest.yml +64 -0
  92. data/specs/data/manifest-schema.yaml +63 -0
  93. data/specs/data/tasks-def.yml +321 -0
  94. data/specs/data/tools.yml +60 -0
  95. metadata +362 -0
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bash
2
+ # Generic release build script for DocOps Lab projects
3
+ # Usage: Set PROJECT_NAME and DOCKER_ORG, then run this script
4
+
5
+ set -e
6
+
7
+ # Load common build functions from centrally managed location
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+
10
+ # Try to find build-common.sh in various locations
11
+ if [ -f "$SCRIPT_DIR/build-common.sh" ]; then
12
+ # shellcheck source=build-common.sh
13
+ source "$SCRIPT_DIR/build-common.sh"
14
+ elif [ -f "$SCRIPT_DIR/lib/build-common.sh" ]; then
15
+ # shellcheck source=build-common.sh
16
+ source "$SCRIPT_DIR/lib/build-common.sh"
17
+ elif [ -f "scripts/.vendor/docopslab/build-common.sh" ]; then
18
+ # shellcheck source=build-common.sh
19
+ source "scripts/.vendor/docopslab/build-common.sh"
20
+ else
21
+ echo "❌ Error: build-common.sh not found. Run 'rake labdev:config:sync' to get centrally managed scripts."
22
+ exit 1
23
+ fi
24
+
25
+ # Project configuration - override these in your calling script or environment
26
+ PROJECT_NAME="${PROJECT_NAME:-$(basename "$(pwd)")}"
27
+ DOCKER_ORG="${DOCKER_ORG:-docopslab}"
28
+
29
+ echo -e "${GREEN}🚀 ${PROJECT_NAME} Release Build Script${NC}"
30
+ echo "=================================="
31
+
32
+ # Validation
33
+ check_project_root
34
+ check_git_clean
35
+ check_main_branch
36
+ check_bundle_installed
37
+ check_docker_available
38
+
39
+ # Run tests
40
+ run_rspec_tests
41
+ test_cli_functionality
42
+
43
+ # Get current version
44
+ current_version=$(get_current_version)
45
+ echo -e "${GREEN}📋 Current version: $current_version${NC}"
46
+
47
+ # Build and test gem
48
+ build_gem
49
+ gem_file=$(test_built_gem)
50
+
51
+ # Build Docker image using the docker-specific script
52
+ echo -e "${YELLOW}🐳 Building Docker image...${NC}"
53
+ "$SCRIPT_DIR/build-docker.sh" 2>&1 | grep -E "(Building|Testing|successfully|Docker image:)" || true
54
+
55
+ # Show final success message
56
+ show_build_success "$current_version" "$gem_file"
@@ -0,0 +1,467 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'yaml'
5
+ require 'time'
6
+ require 'fileutils'
7
+ require 'erb'
8
+
9
+ # Jekyll-AsciiDoc Log Parser
10
+ #
11
+ # Parses Jekyll verbose build logs to extract Asciidoctor warnings and errors.
12
+ # Associates each issue with the source file being processed.
13
+ #
14
+ # Usage:
15
+ # bundle exec jekyll build --verbose > .agent/jekyll-build.log 2>&1
16
+ # bundle exec rake 'labdev:lint:logs[jekyll-asciidoc,.agent/jekyll-build.log]'
17
+
18
+ module JekyllAsciiDocLogParser
19
+ COLORS = {
20
+ red: 31,
21
+ yellow: 33,
22
+ green: 32,
23
+ blue: 34,
24
+ cyan: 36
25
+ }.freeze
26
+
27
+ # Represents a single log issue
28
+ class LogIssue
29
+ attr_accessor :type, :kind, :file, :path, :with, :from, :line, :note, :code, :fix, :attr
30
+
31
+ def initialize type:, kind:, file:, line:, note:, **options
32
+ @type = type
33
+ @kind = kind
34
+ @file = file
35
+ @line = line
36
+ @note = note
37
+ @attr = options[:attr]
38
+
39
+ # Handle optional parameters
40
+ reported_file_path = options[:reported_file_path]
41
+ is_excerpt = options[:is_excerpt] || false
42
+ @fix = nil
43
+
44
+ process_context(reported_file_path, is_excerpt)
45
+ process_error_specifics(reported_file_path, file, line)
46
+ end
47
+
48
+ def to_h
49
+ hash = {
50
+ 'type' => @type,
51
+ 'kind' => @kind,
52
+ 'file' => @file,
53
+ 'line' => @line,
54
+ 'note' => @note,
55
+ 'fix?' => @fix
56
+ }
57
+
58
+ # Add optional fields only if they have values
59
+ hash['path'] = @path if @path
60
+ hash['with'] = @with if @with
61
+ hash['from'] = @from if @from
62
+ hash['code'] = @code if @code && !@code.empty?
63
+ hash['attr'] = @attr if @attr
64
+
65
+ hash
66
+ end
67
+
68
+ private
69
+
70
+ def process_context reported_file_path, is_excerpt
71
+ @from = '#excerpt' if reported_file_path == '#excerpt' || is_excerpt
72
+ end
73
+
74
+ def process_error_specifics reported_file_path, source_file, line_number
75
+ if @kind == 'include_file_not_found'
76
+ extract_missing_path
77
+ else
78
+ set_problem_file(reported_file_path, source_file)
79
+ extract_code_line(line_number)
80
+ end
81
+ end
82
+
83
+ def extract_missing_path
84
+ return unless @note =~ /include file not found: (.+)$/
85
+
86
+ missing_path = Regexp.last_match(1)
87
+
88
+ # Try to convert absolute path back to relative path
89
+ if missing_path =~ %r{/home/[^/]+/[^/]+/work/[^/]+/(.+)$} ||
90
+ missing_path =~ %r{/([^/]+/[^/]+\.adoc)$}
91
+ @path = Regexp.last_match(1)
92
+ end
93
+ end
94
+
95
+ def set_problem_file reported_file_path, source_file
96
+ problem_file = JekyllAsciiDocLogParser.normalize_problem_path(reported_file_path, source_file)
97
+ @with = problem_file unless problem_file == source_file
98
+ end
99
+
100
+ def extract_code_line line_number
101
+ return unless @with
102
+
103
+ code_line = JekyllAsciiDocLogParser.get_code_line_from_problem_file(@with, line_number)
104
+ @code = code_line if code_line && !code_line.empty?
105
+ end
106
+ end
107
+
108
+ class << self
109
+ def parse_log_file log_file, output_dir='.agent/reports'
110
+ unless File.exist?(log_file)
111
+ puts "❌ Log file not found: #{log_file}"
112
+ return false
113
+ end
114
+
115
+ content = File.read(log_file)
116
+ parse_log_content(content, output_dir, log_file)
117
+ end
118
+
119
+ def parse_log_content content, output_dir='.agent/reports', source_name='stdin'
120
+ puts '📝 Parsing Jekyll AsciiDoctor log for warnings and errors...'
121
+
122
+ FileUtils.mkdir_p(output_dir)
123
+
124
+ timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
125
+ output_file = "jekyll-asciidoc-issues-#{timestamp}.yml"
126
+ output_path = File.join(output_dir, output_file)
127
+
128
+ issues = parse_issues(content)
129
+
130
+ if issues.empty?
131
+ puts '✅ No AsciiDoctor issues found!'
132
+ return true
133
+ end
134
+
135
+ generate_yaml_report(issues, output_path, source_name)
136
+
137
+ severity = summarize_severity(issues)
138
+ icon = severity[:has_error] ? '❌' : '⚠️'
139
+ total_files = issues.length
140
+ total_issues = count_total_issues(issues)
141
+ files_text = colorize(total_files, :cyan)
142
+ issues_color = severity[:has_error] ? :red : :yellow
143
+ issues_text = colorize(total_issues, issues_color)
144
+
145
+ puts "📄 Jekyll AsciiDoc issues report generated: #{output_path}"
146
+ puts "#{icon} Found #{issues_text} #{pluralize(total_issues, 'total issue')} across #{files_text} " \
147
+ "#{pluralize(total_files, 'source file')} to review"
148
+ true
149
+ end
150
+
151
+ # Public helper methods accessible to LogIssue class
152
+
153
+ def normalize_source_path source_file
154
+ normalized = source_file.gsub(/#excerpt$/, '').gsub(%r{/$}, '')
155
+ normalized.gsub(%r{^\./}, '')
156
+ end
157
+
158
+ def normalize_problem_path reported_path, source_file
159
+ case reported_path
160
+ when '#excerpt'
161
+ # Special case: excerpt errors are in the source file itself
162
+ source_file
163
+ when %r{^/}
164
+ # Absolute path; try to make relative to project root
165
+ if reported_path.include?('/home/')
166
+ # Extract the project-relative portion
167
+ if reported_path =~ %r{/home/[^/]+/[^/]+/work/([^/]+)/(.+)$}
168
+ project = Regexp.last_match(1)
169
+ path = Regexp.last_match(2)
170
+ "#{project}/#{path}"
171
+ else # Fallback
172
+ File.basename(reported_path)
173
+ end
174
+ else
175
+ reported_path
176
+ end
177
+ when %r{^\.\./}
178
+ # Resolve relative path from source file directory
179
+ source_dir = File.dirname(source_file)
180
+ resolved = File.expand_path(reported_path, source_dir)
181
+ # Make it relative to project root
182
+ resolved.gsub(%r{^/.*?/work/[^/]+/}, '')
183
+ else
184
+ # Already relative or simple filename
185
+ reported_path
186
+ end
187
+ end
188
+
189
+ def categorize_error_type message
190
+ case message
191
+ when /include file not found/
192
+ 'include_file_not_found'
193
+ when /section title out of sequence/
194
+ 'section_title_out_of_sequence'
195
+ when /unterminated listing block/
196
+ 'unterminated_listing_block'
197
+ when /invalid reference/
198
+ 'invalid_reference'
199
+ when /attribute '([^']+)' (?:is|not) defined/
200
+ 'missing_attribute'
201
+ else
202
+ 'other'
203
+ end
204
+ end
205
+
206
+ def get_code_line_from_problem_file problem_file, line_number
207
+ return '' unless problem_file && line_number.positive?
208
+
209
+ # Try various paths where the file might exist
210
+ possible_paths = [
211
+ problem_file,
212
+ "./#{problem_file}",
213
+ File.expand_path(problem_file)
214
+ ]
215
+
216
+ # Also try in common Jekyll source directories
217
+ %w[_docs _blog _pages content].each do |dir|
218
+ unless problem_file.start_with?(dir)
219
+ possible_paths << "#{dir}/#{problem_file}"
220
+ possible_paths << "#{dir}/#{File.basename(problem_file)}"
221
+ end
222
+ end
223
+
224
+ possible_paths.each do |path|
225
+ next unless File.exist?(path)
226
+
227
+ begin
228
+ lines = File.readlines(path)
229
+ line_content = lines[line_number - 1]&.chomp
230
+ return line_content if line_content && !line_content.empty?
231
+ rescue StandardError => e
232
+ puts "⚠️ Could not read line #{line_number} from #{path}: #{e.message}"
233
+ end
234
+ end
235
+
236
+ '' # Return empty string if we can't find/read the file
237
+ end
238
+
239
+ private
240
+
241
+ def parse_issues content
242
+ lines = content.split("\n")
243
+ issues = []
244
+ current_source_file = nil
245
+
246
+ lines.each do |line|
247
+ line = line.strip
248
+
249
+ # Track what file Jekyll is currently rendering (this is our source file)
250
+ current_source_file = Regexp.last_match(1) if line =~ /Rendering Markup: (.+\.adoc.*)/
251
+
252
+ # Extract asciidoctor warnings and errors with explicit file/line
253
+ missing_attr = nil
254
+ if line =~ /^asciidoctor: (WARNING|ERROR): (.+): line (\d+): (.+)$/
255
+ issue_type = Regexp.last_match(1) == 'ERROR' ? 'ERROR' : 'warning'
256
+ reported_file_path = Regexp.last_match(2) # Keep exactly as AsciiDoctor reports it
257
+ line_number = Regexp.last_match(3).to_i
258
+ message = Regexp.last_match(4)
259
+ elsif line =~ /^asciidoctor: (WARNING|ERROR): skipping reference to missing attribute: (.+)$/
260
+ issue_type = Regexp.last_match(1) == 'ERROR' ? 'ERROR' : 'warning'
261
+ reported_file_path = current_source_file
262
+ line_number = 0
263
+ missing_attr = Regexp.last_match(2).strip
264
+ message = "attribute '#{missing_attr}' not defined"
265
+ else
266
+ next
267
+ end
268
+
269
+ next unless current_source_file
270
+
271
+ # Normalize the source file path (relative to project root)
272
+ source_file = normalize_source_path(current_source_file)
273
+ is_excerpt = current_source_file.include?('#excerpt')
274
+
275
+ error_category = categorize_error_type(message)
276
+ attr_name = nil
277
+ if error_category == 'missing_attribute'
278
+ if message =~ /attribute '([^']+)' (?:is|not) defined/
279
+ attr_name = Regexp.last_match(1)
280
+ elsif missing_attr
281
+ attr_name = missing_attr
282
+ end
283
+ end
284
+
285
+ # Create LogIssue object
286
+ log_issue = LogIssue.new(
287
+ type: issue_type,
288
+ kind: error_category,
289
+ file: source_file,
290
+ line: line_number,
291
+ note: message,
292
+ attr: attr_name,
293
+ reported_file_path: reported_file_path,
294
+ is_excerpt: is_excerpt)
295
+
296
+ issues << log_issue.to_h
297
+ end
298
+
299
+ # Group issues by source file for organized output
300
+ issues.group_by { |issue| issue['file'] }.map do |file, file_issues|
301
+ {
302
+ 'source_file' => file,
303
+ 'issues' => file_issues
304
+ }
305
+ end
306
+ end
307
+
308
+ def count_total_issues file_issues
309
+ file_issues.sum { |file_data| file_data['issues'].length }
310
+ end
311
+
312
+ def generate_yaml_report file_issues, output_file, source_name
313
+ template = ERB.new(yaml_template)
314
+ yaml_content = template.result_with_hash(
315
+ file_issues: file_issues,
316
+ source_name: source_name,
317
+ timestamp: Time.now.strftime('%Y-%m-%d %H:%M:%S'),
318
+ total_files: file_issues.length,
319
+ total_issues: count_total_issues(file_issues))
320
+
321
+ # Post-process to remove unwanted blank lines
322
+ cleaned_content = clean_yaml_whitespace(yaml_content)
323
+ File.write(output_file, cleaned_content)
324
+ end
325
+
326
+ def clean_yaml_whitespace yaml_content
327
+ lines = yaml_content.lines
328
+ cleaned_lines = []
329
+
330
+ lines.each_with_index do |line, index|
331
+ if line.strip.empty?
332
+ # Keep empty line only if the next line starts with # or -
333
+ next_line = lines[index + 1]
334
+ cleaned_lines << line if next_line && (next_line.strip.start_with?('#') || next_line.strip.start_with?('-'))
335
+ else
336
+ cleaned_lines << line
337
+ end
338
+ end
339
+
340
+ cleaned_lines.join
341
+ end
342
+
343
+ def ai_prompt agent_prompt=nil
344
+ return agent_prompt if agent_prompt
345
+
346
+ template_path = File.join(TEMPLATES_DIR, 'jekyll-asciidoc-fix.prompt.yml')
347
+ File.read(template_path) if File.exist?(template_path)
348
+ end
349
+
350
+ def yaml_template
351
+ <<~TEMPLATE
352
+ # Jekyll AsciiDoc Issues Report
353
+ #
354
+ # Generated: <%= timestamp %>
355
+ # Source: <%= source_name %>
356
+ # Files with issues: <%= total_files %>
357
+ # Total issues: <%= total_issues %>
358
+ #
359
+ # User Instructions:
360
+ # For each issue, enter a fix?: value of:
361
+ # 'no' or 'skip' to ignore for now
362
+ # 'fix' to mark for correction
363
+ # 'fix("corrected text")' to specify exact correction
364
+ # 'fix(include)' to fix missing include files
365
+ # 'fix(leveloffset)' fix section level issues at the include
366
+ # 'fix(sectionlevel)' fix section level issues in the included file
367
+ # 'ignore' to add to permanent ignore list (not yet implemented)
368
+ #
369
+ # Data Structure:
370
+ # - file: The Jekyll file being rendered (needs fixing)
371
+ # - path: Missing include file path (for include_file_not_found only)
372
+ # - with: The file containing the actual issue (when different from file)
373
+ # - from: Context like "#excerpt" when relevant
374
+ # - kind: Error type classification
375
+ ##{' '}
376
+ # After editing this file, use an AI agent to process the fixes.
377
+ #
378
+ ---
379
+ <% file_issues.each do |file_data| %>
380
+ # <%= file_data['source_file'] %>
381
+ <% file_data['issues'].each do |issue| %>
382
+ - type: <%= issue['type'] %>
383
+ kind: <%= issue['kind'] %>
384
+ file: <%= issue['file'] %>
385
+ <% if issue['path'] %>
386
+ path: <%= issue['path'] %>
387
+ <% end %>
388
+ <% if issue['with'] %>
389
+ with: <%= issue['with'] %>
390
+ <% end %>
391
+ <% if issue['from'] %>
392
+ from: "<%= issue['from'] %>"
393
+ <% end %>
394
+ <% if issue['line'] && issue['line'] > 0 %>
395
+ line: <%= issue['line'] %>
396
+ <% end %>
397
+ note: "<%= issue['note'] %>"
398
+ <% if issue['attr'] %>
399
+ attr: <%= issue['attr'] %>
400
+ <% end %>
401
+ <% if issue['code'] && !issue['code'].empty? %>
402
+ code: |
403
+ <%= issue['code'] %>
404
+ <% end %>
405
+ fix?:#{' '}
406
+ <% end %>
407
+
408
+ <% end %>
409
+ #
410
+ # AI Agent Instructions:
411
+ <%= ai_prompt %>
412
+ TEMPLATE
413
+ end
414
+
415
+ def colorize value, color
416
+ text = value.to_s
417
+ return text unless $stdout.tty?
418
+
419
+ code = COLORS[color]
420
+ return text unless code
421
+
422
+ "\e[#{code}m#{text}\e[0m"
423
+ end
424
+
425
+ def pluralize count, singular, plural=nil
426
+ plural ||= "#{singular}s"
427
+ count == 1 ? singular : plural
428
+ end
429
+
430
+ def summarize_severity file_issues
431
+ has_error = false
432
+ has_warning = false
433
+
434
+ file_issues.each do |file_data|
435
+ file_data['issues'].each do |issue|
436
+ if issue['type'] == 'ERROR'
437
+ has_error = true
438
+ else
439
+ has_warning = true
440
+ end
441
+ end
442
+ end
443
+
444
+ { has_error: has_error, has_warning: has_warning }
445
+ end
446
+ end
447
+ end
448
+
449
+ # CLI usage when run directly
450
+ if $PROGRAM_NAME == __FILE__
451
+ if ARGV.empty?
452
+ puts 'Usage: parse_jekyll_asciidoc_logs.rb <log_file> [output_dir]'
453
+ puts ' or: cat log.txt | parse_jekyll_asciidoc_logs.rb'
454
+ exit 1
455
+ end
456
+
457
+ if ARGV[0] == '-'
458
+ # Read from stdin
459
+ content = $stdin.read
460
+ output_dir = ARGV[1] || '.agent/reports'
461
+ JekyllAsciiDocLogParser.parse_log_content(content, output_dir, 'stdin')
462
+ else
463
+ log_file = ARGV[0]
464
+ output_dir = ARGV[1] || '.agent/reports'
465
+ JekyllAsciiDocLogParser.parse_log_file(log_file, output_dir)
466
+ end
467
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # DocOps Lab development tooling
6
+ # TODO: Once published to RubyGems, replace with: gem 'docopslab-dev'
7
+ gem 'docopslab-dev', path: '../lab/gems/docopslab-dev'
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'docopslab/dev'
@@ -0,0 +1,69 @@
1
+ # Jekyll build output
2
+ _site/
3
+ .jekyll-cache/
4
+
5
+ # Build paths
6
+ built/
7
+ build/
8
+
9
+ # Bundler
10
+ .bundle/
11
+ vendor/
12
+ .vendor/
13
+
14
+ # macOS
15
+ .DS_Store
16
+
17
+ # IDE
18
+ .vscode/
19
+ .idea/
20
+
21
+ # DocOps Lab Dev tooling - vendor configs are synced, not tracked
22
+ .config/.vendor/
23
+ scripts/.vendor/
24
+
25
+ # Temporary paths
26
+ tmp/
27
+ *.tmp
28
+ *.bak
29
+ *~
30
+ *.tmp
31
+ *.bak
32
+ .warp/
33
+ .agent/
34
+ .agents/
35
+
36
+ # Logs
37
+ *.log
38
+
39
+ # Generated config files (merged from base + local)
40
+ .config/vale.ini
41
+ .config/htmlproofer.yml
42
+
43
+ # Gem content paths
44
+ gems/**/pkg/
45
+ *.gem
46
+
47
+ # Build artifacts - generated by CI/scripts
48
+ artifacts/
49
+ tmp/
50
+ # DocOps Lab vendor files
51
+
52
+ # RSpec
53
+ .rspec_status
54
+ specs/tests/results/
55
+
56
+ # Test coverage
57
+ /coverage/
58
+
59
+ # Environment variable files
60
+ .env
61
+ .env.local
62
+ .env.*.local
63
+
64
+ # Logs
65
+ *.log
66
+
67
+ # Ruby version managers
68
+ .ruby-version
69
+ .ruby-gemset
@@ -0,0 +1,17 @@
1
+ # When processing this file, focus on the relationship between
2
+ # the 'file' and 'with' properties:
3
+ #
4
+ # 1. 'file' = The Jekyll file that includes/references the problem
5
+ # 2. 'with' = The file that contains the actual structural issue
6
+ # 3. 'kind' types and typical fixes:
7
+ # - include_file_not_found: Create missing files or fix paths
8
+ # - section_title_out_of_sequence: Adjust heading levels or leveloffset
9
+ # - unterminated_listing_block: Close code blocks with proper delimiters
10
+ # - invalid_reference: Fix broken cross-references
11
+ # - missing_attribute: Define the attribute locally or via included settings
12
+ #
13
+ # Common patterns:
14
+ # - Multiple files referencing the same with file suggest the issue is in the shared file
15
+ # - #excerpt errors usually indicate an include is embedded in the excerpted text
16
+ # - Section title sequence errors often need leveloffset adjustments in include directives
17
+ # - Missing files may need creation or path corrections
@@ -0,0 +1,16 @@
1
+ # When processing user responses in this file:
2
+ # 1. For fix?: 'add' or 'd': Add the term to the filters section in:
3
+ # gems/docopslab-dev/assets/config-packs/vale/authoring/Spelling.yml
4
+ # 1.a. For fix?: 'do' or 'dolab' or 'docopslab': Add the term to a special '# DocOpsLab Terms' section
5
+ # 1.b. For fix?: nontech("word"): Add the term to a special '# Non-Technical Terms' section
6
+ # 2. For fix?: 'fix': Replace the term in the file with inferred corrected spelling
7
+ # 3. For fix?: 'fix("word")': Replace the term with the word value verbatim
8
+ # 4. For fix?: 'no' or 'n': Ignore this issue for now
9
+ # 5. For fix?: 'pass': Wrap the affected text in {vale_off} and {vale_on} comment attributes
10
+ # and make sure the attributes are defined in .adoc file header (offer to user):
11
+ # :vale_off: <!-- vale off -->
12
+ # :vale_on: <!-- vale on -->
13
+ # The spelling dictionary is the 'filters:' sequence in the Spelling.yml file
14
+ # When adding to dictionary, maintain alphabetical order within sections
15
+ # DO NOT TAKE INITIATIVE. STick to the instructed changes except where prompted with `fix?: 'fix'`.
16
+ # DO NOT correct neigboring words in text nor add nor alter words in the dictionary other than those named.
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/docopslab/dev/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'docopslab-dev'
7
+ spec.version = DocOpsLab::Dev::VERSION
8
+ spec.authors = ['DocOps Lab']
9
+ spec.email = ['codewriting@protonmail.com']
10
+
11
+ spec.summary = 'Internal development tooling for DocOps Lab projects'
12
+ spec.description = 'Centralized configuration management, linting, and development ' \
13
+ 'workflows for DocOps Lab repositories'
14
+ spec.homepage = 'https://github.com/DocOps/lab'
15
+ spec.license = 'MIT'
16
+ spec.required_ruby_version = '>= 3.2.0'
17
+
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = 'https://github.com/DocOps/lab/tree/main/gems/docopslab-dev'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/DocOps/lab/blob/main/gems/docopslab-dev/README.adoc'
21
+ spec.metadata['rubygems_mfa_required'] = 'true'
22
+
23
+ spec.files = Dir.glob('{lib,config-packs,hooks,docs,assets}/**/*') +
24
+ %w[README.adoc LICENSE docopslab-dev.gemspec] +
25
+ Dir.glob('specs/data/*')
26
+
27
+ spec.bindir = 'exe'
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ['lib']
30
+
31
+ # Core runtime dependencies
32
+ spec.add_dependency 'asciidoctor', '~> 2.0'
33
+ spec.add_dependency 'rake', '~> 13.0'
34
+ spec.add_dependency 'yaml', '~> 0.2'
35
+
36
+ # Code quality and linting
37
+ spec.add_dependency 'debride', '~> 1.13'
38
+ spec.add_dependency 'fasterer', '~> 0.11'
39
+ spec.add_dependency 'flog', '~> 4.8'
40
+ spec.add_dependency 'reek', '~> 6.5'
41
+ spec.add_dependency 'rubocop', '~> 1.80'
42
+ spec.add_dependency 'rubocop-rake', '~> 0.7'
43
+ spec.add_dependency 'rubocop-rspec', '~> 3.7'
44
+ spec.add_dependency 'subtxt', '~> 0.3'
45
+
46
+ # Security analysis
47
+ spec.add_dependency 'brakeman', '~> 7.1'
48
+ spec.add_dependency 'bundler-audit', '~> 0.9'
49
+
50
+ # Testing and coverage
51
+ spec.add_dependency 'html-proofer', '~> 5.0'
52
+ spec.add_dependency 'inch', '~> 0.8'
53
+ spec.add_dependency 'simplecov', '~> 0.22'
54
+
55
+ # Development dependencies should be in Gemfile, not gemspec
56
+ end