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,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module DocOpsLab
6
+ module Dev
7
+ module Initializer
8
+ class << self
9
+ def create_project_manifest
10
+ return if File.exist?(MANIFEST_PATH)
11
+
12
+ puts '📋 Creating docopslab-dev.yml...'
13
+
14
+ FileUtils.mkdir_p('.config')
15
+
16
+ # Copy template from gem
17
+ FileUtils.cp(MANIFEST_DEF_PATH, MANIFEST_PATH)
18
+ puts "✅ Created #{MANIFEST_PATH}"
19
+ end
20
+
21
+ def create_gitignore_stub
22
+ if File.exist?('.gitignore')
23
+ puts '⏭️ .gitignore already exists, skipping'
24
+ return false
25
+ end
26
+
27
+ FileUtils.cp(GITIGNORE_STUB_SOURCE_PATH, '.gitignore')
28
+ puts '✅ Created .gitignore file'
29
+ true
30
+ end
31
+
32
+ def create_gemfile_stub
33
+ if File.exist?('Gemfile')
34
+ puts '⏭️ Gemfile already exists, skipping'
35
+ return false
36
+ end
37
+
38
+ FileUtils.cp(GEMFILE_STUB_SOURCE_PATH, 'Gemfile')
39
+ puts '✅ Created Gemfile'
40
+ true
41
+ end
42
+
43
+ def create_rakefile_stub
44
+ if File.exist?('Rakefile')
45
+ puts '⏭️ Rakefile already exists, skipping'
46
+ return false
47
+ end
48
+
49
+ FileUtils.cp(RAKEFILE_STUB_SOURCE_PATH, 'Rakefile')
50
+ puts '✅ Created Rakefile'
51
+ true
52
+ end
53
+
54
+ def init_git_repository
55
+ if Dir.exist?('.git')
56
+ puts '⏭️ Git repository already initialized, skipping'
57
+ return false
58
+ end
59
+
60
+ system('git', 'init')
61
+ puts '✅ Initialized Git repository'
62
+ true
63
+ end
64
+
65
+ def bootstrap_project
66
+ puts '� Bootstrapping DocOps Lab project...'
67
+ puts ''
68
+
69
+ created = []
70
+
71
+ # Core project files
72
+ created << 'Git repository' if init_git_repository
73
+ created << '.gitignore' if create_gitignore_stub
74
+ # Skip Gemfile; not needed for Docker workflow, template available if needed
75
+ created << 'Rakefile' if create_rakefile_stub
76
+
77
+ # DocOpsLab-specific
78
+ create_project_manifest
79
+ created << '.config/docopslab-dev.yml' unless File.exist?(MANIFEST_PATH)
80
+
81
+ puts ''
82
+ if created.any?
83
+ puts "✅ Bootstrap complete! Created: #{created.join(', ')}"
84
+ puts ''
85
+ puts 'Next steps:'
86
+ puts ' 1. bundle exec rake labdev:sync:all # or: docker run ... labdev:sync:all'
87
+ puts ' 2. Start using labdev tasks!'
88
+ else
89
+ puts '✅ Project already initialized, nothing to create'
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,451 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module DocOpsLab
6
+ module Dev
7
+ module Linters
8
+ class << self
9
+ def run_rubocop context, file_path=nil, opts_string=''
10
+ context.generate_rubocop_config if context.respond_to?(:generate_rubocop_config)
11
+
12
+ rubocop_config_file = CONFIG_PATHS[:rubocop]
13
+ unless File.exist?(rubocop_config_file)
14
+ rubocop_config_file = RUBOCOP_CONFIG_PATH # Fallback to vendor config
15
+ end
16
+
17
+ unless File.exist?(rubocop_config_file)
18
+ puts "❌ No RuboCop config found. Run 'labdev:init' to create one."
19
+ return false
20
+ end
21
+
22
+ puts "📄 Using config: #{rubocop_config_file}"
23
+
24
+ path_config = context.get_path_config('rubocop')
25
+
26
+ if path_config[:skip] && !path_config[:skip].empty?
27
+ puts "⚠️ RuboCop does not support command-line exclusion. Use the 'Exclude' " \
28
+ "property in '.config/rubocop.yml' to ignore files or directories."
29
+ end
30
+
31
+ paths_to_check = if file_path
32
+ [file_path]
33
+ else
34
+ path_config[:lint]
35
+ end
36
+
37
+ cmd = "bundle exec rubocop --config #{rubocop_config_file}"
38
+ if paths_to_check.nil? || paths_to_check.empty?
39
+ puts '📄 No paths configured to check for RuboCop, running on entire project.'
40
+ else
41
+ puts "👮 Running RuboCop on paths: #{paths_to_check.join(' ')}"
42
+ cmd += " #{paths_to_check.join(' ')}"
43
+ end
44
+
45
+ # Append additional options if provided
46
+ cmd += " #{opts_string}" unless opts_string.empty?
47
+
48
+ success = system(cmd)
49
+
50
+ if success
51
+ puts '✅ RuboCop passed'
52
+ else
53
+ puts '❌ RuboCop found issues'
54
+ end
55
+
56
+ success
57
+ end
58
+
59
+ def run_rubocop_with_filter _context, filter_name
60
+ rubocop_config_file = CONFIG_PATHS[:rubocop]
61
+ unless File.exist?(rubocop_config_file)
62
+ rubocop_config_file = RUBOCOP_CONFIG_PATH # Fallback to vendor config
63
+ end
64
+
65
+ unless File.exist?(rubocop_config_file)
66
+ puts "❌ No RuboCop config found. Run 'labdev:init' to create one."
67
+ return false
68
+ end
69
+
70
+ puts "📄 Using config: #{rubocop_config_file}"
71
+ puts "🔍 Running RuboCop with filter: #{filter_name}"
72
+
73
+ cmd = "bundle exec rubocop --config #{rubocop_config_file} --only #{filter_name}"
74
+ success = system(cmd)
75
+
76
+ if success
77
+ puts '✅ RuboCop passed'
78
+ else
79
+ puts '❌ RuboCop found issues'
80
+ end
81
+
82
+ success
83
+ end
84
+
85
+ def run_shellcheck context, file_path=nil, opts_string=''
86
+ scope = file_path ? :file : :project
87
+ running_on = file_path || 'entire project'
88
+ puts "🐚 Running ShellCheck on #{running_on}"
89
+
90
+ shell_scripts = if scope == :file
91
+ File.exist?(file_path) ? [file_path] : []
92
+ else
93
+ context.find_shell_scripts
94
+ end
95
+
96
+ if shell_scripts.empty?
97
+ puts '📄 No shell scripts found to check'
98
+ return true
99
+ end
100
+
101
+ puts "📄 Found #{shell_scripts.length} shell script(s) to check" if scope == :project
102
+ success = true
103
+ shell_scripts.each do |script|
104
+ puts "🔍 Checking #{script}..."
105
+ passed = true
106
+ shebang_status = check_shebang(script)
107
+ unless shebang_status
108
+ puts "❌ Faulty shebang in #{script}; must be: #!/usr/bin/env bash"
109
+ success = false
110
+ passed = false
111
+ end
112
+ cmd = "shellcheck --severity=warning #{opts_string} --rcfile=.config/shellcheckrc #{script}".strip
113
+ shellcheck = context.run_with_fallback('shellcheck', cmd)
114
+ unless shellcheck
115
+ success = false
116
+ passed = false
117
+ puts "❌ ShellCheck found issues in #{script}"
118
+ end
119
+ puts "✅ ShellCheck passed for #{script}" if passed
120
+ end
121
+
122
+ if success
123
+ puts '✅ ShellCheck passed'
124
+ else
125
+ puts '❌ ShellCheck found issues'
126
+ end
127
+ success
128
+ end
129
+
130
+ def run_actionlint context, opts_string=''
131
+ puts '⚙️ Running actionlint...'
132
+ workflows_dir = '.github/workflows'
133
+ unless Dir.exist?(workflows_dir)
134
+ puts '📄 No GitHub Actions workflows found (.github/workflows/ not present)'
135
+ return true
136
+ end
137
+ workflow_files = Dir.glob("#{workflows_dir}/**/*.{yml,yaml}")
138
+ if workflow_files.empty?
139
+ puts '📄 No workflow files found in .github/workflows/'
140
+ return true
141
+ end
142
+ puts "📄 Found #{workflow_files.length} workflow file(s) to check"
143
+ config_file = '.config/actionlint.yml'
144
+ cmd = if File.exist?(config_file)
145
+ puts "📄 Using config: #{config_file}"
146
+ [
147
+ 'actionlint',
148
+ '-config-file', config_file,
149
+ '-shellcheck=', # Disable shellcheck integration (empty value)
150
+ opts_string,
151
+ *workflow_files
152
+ ].reject(&:empty?)
153
+ else
154
+ [
155
+ 'actionlint',
156
+ '-shellcheck=', # Disable shellcheck integration (empty value)
157
+ opts_string,
158
+ *workflow_files
159
+ ].reject(&:empty?)
160
+ end
161
+ success = context.run_with_fallback('actionlint', cmd)
162
+ if success
163
+ puts '✅ actionlint passed'
164
+ else
165
+ puts '❌ actionlint found issues'
166
+ end
167
+ success
168
+ end
169
+
170
+ def run_vale context, file_path=nil, opts_string='', output_format: :cli, filter: nil, style_override: nil
171
+ scope = file_path ? :file : :project
172
+ running_on = file_path ? "file: #{file_path}" : scope.to_s
173
+
174
+ override_label = case style_override
175
+ when :adoc then ' (AsciiDoc syntax)'
176
+ when :text then ' (prose/text)'
177
+ else ''
178
+ end
179
+
180
+ puts "📝 Running Vale on #{running_on}#{override_label}"
181
+
182
+ # Generate runtime config from base + local with optional style override
183
+ puts ' ✅ Vale config up to date' unless context.generate_vale_config(style_override: style_override)
184
+
185
+ # Use the generated config file
186
+ config_file = CONFIG_PATHS[:vale]
187
+
188
+ unless File.exist?(config_file)
189
+ puts "❌ No Vale config found. Run 'labdev:sync:all' to generate one."
190
+ return false
191
+ end
192
+
193
+ puts "📄 Using config: #{config_file}"
194
+
195
+ # Check if Vale is available natively or via Docker
196
+ unless context.tool_available?('vale')
197
+ if context.docker_available?
198
+ puts '⚠️ Vale not found natively, using Docker fallback'
199
+ else
200
+ puts '⚠️ Vale not found. Install options:'
201
+ puts ' • macOS: brew install vale'
202
+ puts ' • Linux: https://vale.sh/docs/vale-cli/installation/'
203
+ puts ' • Docker: docker pull docopslab/dev'
204
+ return false
205
+ end
206
+ end
207
+
208
+ # Find AsciiDoc files to check, excluding vendor/ignored directories
209
+ if scope == :file
210
+ asciidoc_files = [file_path]
211
+ else
212
+ asciidoc_files = context.find_asciidoc_files
213
+ if asciidoc_files.empty?
214
+ puts '📄 No AsciiDoc files found to check'
215
+ return true
216
+ end
217
+ puts "📄 Found #{asciidoc_files.length} AsciiDoc file(s) to check"
218
+ end
219
+
220
+ # Run Vale on specific files instead of scanning everything
221
+ cmd = ['vale', '--config', config_file]
222
+
223
+ # Add output format if not default CLI
224
+ cmd << "--output=#{output_format.to_s.upcase}" unless output_format == :cli
225
+
226
+ # Add filter if specified; Vale requires: --filter='.Name=="RuleName"'
227
+ if filter
228
+ # Accept either 'RuleName' or '.Name==RuleName' or '.Name=="RuleName"'
229
+ # Vale filter syntax: .Name=="RuleName" (expr-lang syntax, needs double quotes)
230
+
231
+ # Extract just the rule name, stripping any existing .Name== prefix and quotes
232
+ filter_expr = if filter.start_with?('.Name==')
233
+ # Strip .Name== prefix and any surrounding quotes
234
+ filter.sub(/^\.Name==/, '').gsub(/^["']|["']$/, '')
235
+ else
236
+ # Just the rule name, remove any quotes if present
237
+ filter.gsub(/^["']|["']$/, '')
238
+ end
239
+
240
+ # Pass as two separate args to avoid shell quoting issues
241
+ cmd << '--filter'
242
+ cmd << ".Name==\"#{filter_expr}\""
243
+ end
244
+
245
+ # Add additional options if provided
246
+ cmd += opts_string.split unless opts_string.empty?
247
+
248
+ # Add files to check
249
+ cmd += asciidoc_files
250
+
251
+ if output_format == :json
252
+ # For JSON output, capture stdout
253
+ # Use array form to preserve argument boundaries (esp. for --filter)
254
+ stdout, stderr, status = Open3.capture3(*cmd)
255
+
256
+ # Vale returns 1 for found issues, >1 for actual problems
257
+ if status.exitstatus > 1
258
+ puts "❌ Vale command failed: #{stderr}"
259
+ return nil
260
+ end
261
+
262
+ stdout
263
+ else
264
+ # Standard execution for CLI output
265
+ success = context.run_with_fallback('vale', cmd)
266
+
267
+ if success
268
+ puts '✅ Vale passed'
269
+ else
270
+ puts '❌ Vale found issues'
271
+ end
272
+
273
+ success
274
+ end
275
+ end
276
+
277
+ def run_htmlproofer context
278
+ require 'html-proofer'
279
+
280
+ context.generate_htmlproofer_config
281
+
282
+ config_options = context.load_htmlproofer_config
283
+ return (puts '⚠️ No HTMLProofer config found; skipping') && true unless config_options.is_a?(Hash)
284
+
285
+ path_config = context.get_path_config('htmlproofer')
286
+ lint_path = path_config[:lint]
287
+ site_dir = lint_path.is_a?(Array) ? lint_path.first : lint_path
288
+
289
+ # Fallback to old check_directory for backward compatibility
290
+ site_dir ||= config_options.delete(:check_directory)
291
+
292
+ unless site_dir
293
+ msg = '⚠️ No directory to check for HTMLProofer specified in manifest or config file; skipping'
294
+ return (puts msg) && true
295
+ end
296
+
297
+ unless Dir.exist?(site_dir)
298
+ return (puts "⚠️ Directory '#{site_dir}' does not exist; skipping HTMLProofer") && true
299
+ end
300
+
301
+ puts "📂 Checking #{site_dir} directory..."
302
+
303
+ # Add ignored files from path config
304
+ ignore_files = path_config[:skip] || []
305
+ if ignore_files.any?
306
+ config_options[:ignore_files] ||= []
307
+ config_options[:ignore_files].concat(ignore_files.map { |p| /#{p}/ })
308
+ end
309
+
310
+ puts "🐛 [DEBUG] Final config_options: #{config_options.inspect}" if ENV['LABDEV_DEBUG'] == 'true'
311
+
312
+ begin
313
+ HTMLProofer.check_directory(site_dir, config_options).run
314
+ puts '✅ HTMLProofer passed'
315
+ true
316
+ rescue StandardError => e
317
+ puts "❌ HTMLProofer failed: #{e.message}"
318
+ false
319
+ end
320
+ end
321
+
322
+ def run_auto_fix context
323
+ puts '🔧 Auto-fixing safe linting issues...'
324
+
325
+ success = true
326
+
327
+ # Auto-fix RuboCop issues
328
+ success &= run_rubocop_auto_fix(context)
329
+
330
+ if success
331
+ puts '✅ Auto-fix complete'
332
+ else
333
+ puts '❌ Some auto-fixes failed'
334
+ end
335
+
336
+ success
337
+ end
338
+
339
+ def run_all_linters context
340
+ puts '🧹 Running all linters...'
341
+
342
+ results = {}
343
+
344
+ results[:rubocop] = run_rubocop(context)
345
+ results[:vale] = run_vale(context)
346
+ results[:shellcheck] = run_shellcheck(context)
347
+ results[:actionlint] = run_actionlint(context)
348
+ results[:htmlproofer] = run_htmlproofer(context)
349
+
350
+ # Summary
351
+ passed = results.values.count(true)
352
+ total = results.size
353
+
354
+ if passed == total
355
+ puts '✅ All linting complete'
356
+ else
357
+ puts "⚠️ #{passed}/#{total} linters passed"
358
+ results.each do |linter, result|
359
+ status = result ? '✅' : '❌'
360
+ puts " #{status} #{linter}"
361
+ end
362
+ end
363
+
364
+ results.values.all?
365
+ end
366
+
367
+ def run_rubocop_auto_fix _context, path: nil
368
+ puts '👮 Running RuboCop auto-correction...'
369
+
370
+ unless File.exist?(RUBOCOP_CONFIG_PATH)
371
+ puts "❌ No RuboCop config found. Run 'labdev:init' to create one."
372
+ return false
373
+ end
374
+
375
+ puts "📄 Using config: #{RUBOCOP_CONFIG_PATH}"
376
+
377
+ # Build command with optional path
378
+ cmd = "bundle exec rubocop --config #{RUBOCOP_CONFIG_PATH} --autocorrect-all"
379
+ if path
380
+ cmd += " #{path}"
381
+ puts "📄 Targeting path: #{path}"
382
+ end
383
+ puts "🔧 Running: #{cmd}"
384
+
385
+ success = system(cmd)
386
+
387
+ if success
388
+ puts '✅ RuboCop auto-correction completed'
389
+ else
390
+ puts '❌ RuboCop auto-correction encountered issues'
391
+ end
392
+
393
+ success
394
+ end
395
+
396
+ def run_linter_group context, group_name, linters
397
+ puts "Running #{group_name} linting..."
398
+
399
+ results = {}
400
+ linters.each do |linter|
401
+ method_name = "run_#{linter}"
402
+ if respond_to?(method_name, true)
403
+ results[linter.to_sym] = send(method_name, context)
404
+ else
405
+ puts "⚠️ Unknown linter: #{linter}"
406
+ results[linter.to_sym] = false
407
+ end
408
+ end
409
+
410
+ passed = results.values.count(true)
411
+ total = results.size
412
+
413
+ if passed == total
414
+ puts "✅ #{group_name} linting complete"
415
+ else
416
+ puts "❌ #{passed}/#{total} #{group_name} linters passed"
417
+ end
418
+
419
+ results.values.all?
420
+ end
421
+
422
+ def lint_file context, file_path
423
+ ext = File.extname(file_path).downcase
424
+ case ext
425
+ when '.adoc', '.asciidoc', '.asc'
426
+ run_vale(context, file_path)
427
+ when '.rb', '.gemspec', ''
428
+ run_rubocop(context, file_path)
429
+ when '.sh'
430
+ run_shellcheck(context, file_path)
431
+ else
432
+ puts "⚠️ No linter configured for file type: #{ext}"
433
+ false
434
+ end
435
+ end
436
+
437
+ def check_shebang file_path
438
+ return false unless File.exist?(file_path)
439
+
440
+ first_line = File.open(file_path, &:readline).strip
441
+ first_line == '#!/usr/bin/env bash'
442
+ rescue EOFError
443
+ false
444
+ rescue StandardError => e
445
+ puts "⚠️ Error checking shebang for #{file_path}: #{e.message}"
446
+ false
447
+ end
448
+ end
449
+ end
450
+ end
451
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'shellwords'
5
+
6
+ module DocOpsLab
7
+ module Dev
8
+ # Log parsing functionality for Jekyll AsciiDoctor build logs
9
+ module LogParser
10
+ class << self
11
+ def parse_jekyll_asciidoc_log log_file, output_dir=nil
12
+ output_dir ||= default_output_dir
13
+
14
+ script_name = 'parse_jekyll_asciidoc_logs.rb'
15
+
16
+ # Execute the parsing script using ScriptManager.run_script
17
+ Dev.run_script(script_name, [log_file, output_dir])
18
+ end
19
+
20
+ private
21
+
22
+ def default_output_dir
23
+ manifest = DocOpsLab::Dev.load_manifest
24
+ log_config = manifest.dig('logs', 'output_dir') || '.agent/reports'
25
+ FileUtils.mkdir_p(log_config)
26
+ log_config
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DocOpsLab
4
+ module Dev
5
+ # Centralized path constants for DocOps Lab Dev
6
+ module Paths
7
+ # Gem root directory (gems/docopslab-dev/)
8
+ def self.gem_root
9
+ File.expand_path('../../..', __dir__)
10
+ end
11
+
12
+ # Asset directories in gem
13
+ def self.gem_assets
14
+ File.join(gem_root, 'assets')
15
+ end
16
+
17
+ def self.gem_config_packs
18
+ File.join(gem_assets, 'config-packs')
19
+ end
20
+
21
+ def self.gem_hooks
22
+ File.join(gem_assets, 'hooks')
23
+ end
24
+
25
+ def self.gem_scripts
26
+ File.join(gem_assets, 'scripts')
27
+ end
28
+
29
+ # Config vendor directory (where config packs are synced to)
30
+ def self.config_vendor_dir
31
+ '.config/.vendor/docopslab'
32
+ end
33
+
34
+ # Generated/managed config files
35
+ CONFIG_FILES = {
36
+ vale: '.config/vale.ini',
37
+ htmlproofer: '.config/htmlproofer.yml',
38
+ rubocop: '.config/rubocop.yml'
39
+ }.freeze
40
+
41
+ def self.config_file name
42
+ CONFIG_FILES[name]
43
+ end
44
+ end
45
+ end
46
+ end