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,440 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake/tasklib'
4
+ require 'yaml'
5
+
6
+ module DocOpsLab
7
+ module Dev
8
+ # Rake task definitions for DocOps Lab development tools
9
+ # Task structure defined in specs/data/tasks-def.yml
10
+ class Tasks < Rake::TaskLib
11
+ def initialize
12
+ super
13
+ load_task_definitions
14
+ define_tasks
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :task_defs
20
+
21
+ def load_task_definitions
22
+ tasks_def_path = File.join(__dir__, '../../../specs/data/tasks-def.yml')
23
+ @task_defs = YAML.load_file(tasks_def_path)
24
+ end
25
+
26
+ # Look up task description from tasks-def.yml
27
+ # Path format: 'check:env' looks up labdev.check.env._desc
28
+ def desc_for task_path
29
+ parts = task_path.split(':')
30
+ node = task_defs.dig('labdev', *parts)
31
+ return nil unless node.is_a?(Hash)
32
+
33
+ node['_desc']
34
+ end
35
+
36
+ def define_tasks
37
+ namespace :labdev do
38
+ # ============================================================
39
+ # CHECK tasks; Assess local repo and environment
40
+ # ============================================================
41
+
42
+ # Base task; show help for 'check' namespace
43
+ task :check do
44
+ Help.show_task_help('check')
45
+ end
46
+
47
+ namespace :check do
48
+ desc desc_for('check:env')
49
+ task :env do
50
+ puts '🩺 DocOps Lab Environment Diagnostics'
51
+ Dev.check_ruby_version
52
+ puts "Gem version: #{VERSION}"
53
+ Dev.check_config_structure
54
+ Dev.check_standard_rake_tasks
55
+ end
56
+
57
+ desc desc_for('check:updates')
58
+ task :updates do
59
+ Dev.check_hook_updates
60
+ end
61
+
62
+ desc desc_for('check:all')
63
+ task :all do
64
+ Rake::Task['labdev:check:env'].invoke
65
+ Rake::Task['labdev:check:updates'].invoke
66
+ end
67
+ end
68
+
69
+ # ============================================================
70
+ # INIT tasks; Bootstrap development environment
71
+ # ============================================================
72
+
73
+ desc desc_for('init')
74
+ task :init do
75
+ Help.show_task_help('init')
76
+ end
77
+
78
+ namespace :init do
79
+ desc desc_for('init:all')
80
+ task :all do
81
+ Dev.bootstrap_project
82
+ end
83
+
84
+ desc desc_for('init:docs')
85
+ task :docs do
86
+ if Dev.sync_docs
87
+ puts '✅ Agent docs synced'
88
+ else
89
+ puts '⚠️ Agent docs sync skipped or failed'
90
+ end
91
+ end
92
+ end
93
+
94
+ # ============================================================
95
+ # RUN tasks; Execute tools with custom arguments
96
+ # ============================================================
97
+
98
+ namespace :run do
99
+ desc desc_for('run:script')
100
+ task :script, %i[script opts] => [] do |_t, args|
101
+ script = args[:script]
102
+ if script.nil?
103
+ puts '❌ Script name is required.'
104
+ puts 'Usage: bundle exec rake labdev:run:script[script]'
105
+ puts 'Use labdev:show:scripts to see available scripts.'
106
+ return
107
+ end
108
+
109
+ # Parse opts_string if provided
110
+ extra_args = args[:opts] ? args[:opts].split : []
111
+ Dev.run_script(script, extra_args)
112
+ end
113
+
114
+ desc desc_for('run:rubocop')
115
+ task :rubocop, [:opts] => [] do |_t, args|
116
+ opts = args[:opts] || ''
117
+ # Strip quotes that Rake includes when arguments contain spaces
118
+ opts = opts.gsub(/^["']|["']$/, '') if opts.include?(' ')
119
+ success = Dev.run_rubocop(nil, opts)
120
+ exit(1) unless success
121
+ end
122
+
123
+ desc desc_for('run:vale')
124
+ task :vale, [:opts] => [] do |_t, args|
125
+ opts = args[:opts] || ''
126
+ Dev.run_vale(nil, opts)
127
+ end
128
+
129
+ # Removed from 0.1.0 for incongruent options between CLI and API
130
+ # desc desc_for('run:htmlproofer')
131
+ # task :htmlproofer, [:opts] => [] do |_t, args|
132
+ # opts = args[:opts] || ''
133
+ # puts '🔗 Running HTMLProofer...'
134
+ # Dev.run_htmlproofer(opts)
135
+ # end
136
+
137
+ desc desc_for('run:shellcheck')
138
+ task :shellcheck, [:opts] => [] do |_t, args|
139
+ opts = args[:opts] || ''
140
+ Dev.run_shellcheck(nil, opts)
141
+ end
142
+
143
+ desc desc_for('run:actionlint')
144
+ task :actionlint, [:opts] => [] do |_t, args|
145
+ opts = args[:opts] || ''
146
+ Dev.run_actionlint(opts)
147
+ end
148
+ end
149
+
150
+ # ============================================================
151
+ # SYNC tasks; Sync managed files from upstream
152
+ # ============================================================
153
+
154
+ namespace :sync do
155
+ desc desc_for('sync:configs')
156
+ task :configs do
157
+ Dev.sync_config_files
158
+ end
159
+
160
+ desc desc_for('sync:scripts')
161
+ task :scripts do
162
+ Dev.sync_scripts
163
+ end
164
+
165
+ desc desc_for('sync:docs')
166
+ task :docs do
167
+ Dev.sync_docs(force: true)
168
+ end
169
+
170
+ namespace :styles do
171
+ desc desc_for('sync:styles:local')
172
+ task :local do
173
+ Dev.sync_vale_styles(local: true)
174
+ end
175
+
176
+ desc desc_for('sync:styles:all')
177
+ task :all do
178
+ Dev.sync_vale_styles
179
+ end
180
+ end
181
+
182
+ # Base task; show help for 'sync:styles' namespace
183
+ task :styles do
184
+ Help.show_task_help('sync:styles')
185
+ end
186
+
187
+ desc desc_for('sync:hooks')
188
+ task :hooks do
189
+ Dev.update_hooks_interactive
190
+ end
191
+
192
+ namespace :vale do
193
+ desc desc_for('sync:vale:local')
194
+ task :local do
195
+ Dev.sync_config_files(:vale)
196
+ Dev.sync_vale_styles(local: true)
197
+ end
198
+
199
+ desc desc_for('sync:vale:all')
200
+ task :all do
201
+ Dev.sync_config_files(:vale)
202
+ Dev.sync_vale_styles
203
+ end
204
+ end
205
+
206
+ # Base task; show help for 'sync:vale' namespace
207
+ task :vale do
208
+ Help.show_task_help('sync:vale')
209
+ end
210
+
211
+ desc desc_for('sync:all')
212
+ task :all do
213
+ Dev.sync_config_files
214
+ Dev.sync_scripts
215
+ Dev.sync_docs
216
+ Dev.install_missing_hooks
217
+ Dev.sync_vale_styles
218
+ end
219
+ end
220
+
221
+ # Base task; show help for 'sync' namespace
222
+ task :sync do
223
+ Help.show_task_help('sync')
224
+ end
225
+
226
+ # ============================================================
227
+ # LINT tasks; Run linters
228
+ # ============================================================
229
+
230
+ namespace :lint do
231
+ desc desc_for('lint:ruby')
232
+ task :ruby, %i[path rule opts] => [] do |_t, args|
233
+ path = args[:path]
234
+ rule = args[:rule]
235
+ opts = args[:opts]
236
+
237
+ if path || rule || opts
238
+ # Specific file/rule mode
239
+ cmd_opts = []
240
+ cmd_opts << "--only #{rule}" if rule
241
+ cmd_opts << opts if opts
242
+ target = path || nil
243
+ Dev.run_rubocop(target, cmd_opts.join(' '))
244
+ else
245
+ # Default: run on all Ruby files
246
+ Dev.run_linter_group('Ruby', %w[rubocop])
247
+ end
248
+ end
249
+
250
+ desc desc_for('lint:bash')
251
+ task :bash, %i[path rule opts] => [] do |_t, args|
252
+ path = args[:path]
253
+ opts = args[:opts]
254
+
255
+ if path || opts
256
+ target = path || nil
257
+ Dev.run_shellcheck(target, opts || '')
258
+ else
259
+ Dev.run_linter_group('shell script', %w[shellcheck])
260
+ end
261
+ end
262
+
263
+ desc desc_for('lint:docs')
264
+ task :docs, %i[path rule opts] => [] do |_t, args|
265
+ path = args[:path]
266
+ rule = args[:rule]
267
+ opts = args[:opts]
268
+
269
+ if path || rule || opts
270
+ filter = rule ? ".Name==#{rule}" : nil
271
+ target = path || nil
272
+ Dev.run_vale(target, opts || '', filter: filter)
273
+ else
274
+ Dev.run_linter_group('AsciiDoc', %w[vale])
275
+ end
276
+ end
277
+
278
+ desc desc_for('lint:html')
279
+ task :html do
280
+ Dev.run_linter_group('HTML', %w[htmlproofer])
281
+ end
282
+
283
+ desc desc_for('lint:adoc')
284
+ task :adoc, %i[path rule opts] => [] do |_t, args|
285
+ path = args[:path]
286
+ rule = args[:rule]
287
+ opts = args[:opts]
288
+
289
+ if path || rule || opts
290
+ filter = rule ? ".Name==#{rule}" : nil
291
+ target = path || nil
292
+ Dev.run_vale(target, opts || '', filter: filter, style_override: :adoc)
293
+ else
294
+ Dev.run_vale(style_override: :adoc)
295
+ end
296
+ end
297
+
298
+ # Alias: labdev:lint:asciidoc -> labdev:lint:adoc
299
+ task asciidoc: 'adoc'
300
+
301
+ desc desc_for('lint:text')
302
+ task :text, %i[path rule opts] => [] do |_t, args|
303
+ path = args[:path]
304
+ rule = args[:rule]
305
+ opts = args[:opts]
306
+
307
+ if path || rule || opts
308
+ filter = rule ? ".Name==#{rule}" : nil
309
+ target = path || nil
310
+ Dev.run_vale(target, opts || '', filter: filter, style_override: :text)
311
+ else
312
+ Dev.run_vale(style_override: :text)
313
+ end
314
+ end
315
+
316
+ desc desc_for('lint:workflows')
317
+ task :workflows, %i[path opts] => [] do |_t, args|
318
+ # path = args[:path] # TODO: path not yet supported by run_actionlint
319
+ opts = args[:opts]
320
+
321
+ if opts
322
+ Dev.run_actionlint(opts)
323
+ else
324
+ Dev.run_linter_group('GitHub Actions', %w[actionlint])
325
+ end
326
+ end
327
+
328
+ desc desc_for('lint:spellcheck')
329
+ task :spellcheck, %i[path opts] => [] do |_t, args|
330
+ path = args[:path]
331
+ SpellCheck.generate_spellcheck_report(path)
332
+ end
333
+
334
+ desc desc_for('lint:logs')
335
+ task :logs, %i[type path outdir] => [] do |_t, args|
336
+ log_type = args[:type]
337
+ log_file = args[:path]
338
+ output_dir = args[:outdir]
339
+
340
+ unless log_type && log_file
341
+ puts 'Usage: bundle exec rake labdev:lint:logs[type,path]'
342
+ puts 'Example: bundle exec rake labdev:lint:logs[jekyll-asciidoc,.agent/build.log]'
343
+ puts 'Supported log types: jekyll-asciidoc'
344
+ next
345
+ end
346
+
347
+ case log_type.to_s.downcase
348
+ when 'jekyll-asciidoc', 'jekyll_asciidoc', 'jekyll'
349
+ LogParser.parse_jekyll_asciidoc_log(log_file, output_dir)
350
+ else
351
+ puts "❌ Unknown log type: #{log_type}"
352
+ puts 'Supported types: jekyll-asciidoc'
353
+ false
354
+ end
355
+ end
356
+
357
+ desc desc_for('lint:all')
358
+ task :all do
359
+ Dev.run_all_linters
360
+ end
361
+ end
362
+
363
+ # Base task; show help for 'lint' namespace
364
+ task :lint do
365
+ Help.show_task_help('lint')
366
+ end
367
+
368
+ # ============================================================
369
+ # HEAL tasks; Auto-fix issues
370
+ # ============================================================
371
+
372
+ namespace :heal do
373
+ desc desc_for('heal:ruby')
374
+ task :ruby, [:path] => [] do |_t, args|
375
+ Dev.run_rubocop_auto_fix(path: args[:path])
376
+ end
377
+
378
+ desc desc_for('heal:adoc')
379
+ # Add an optional path argument that defaults to nil
380
+ task :adoc, %i[path] => [] do |_t, args|
381
+ Dev.run_adoc_auto_fix(args[:path])
382
+ end
383
+
384
+ desc desc_for('heal:all')
385
+ task :all do
386
+ # if the user passed an argument, we wan to tell them this task does not accept any arguments and we want to peaec out of this operation rather than running it
387
+ if ARGV.any? { |arg| arg.include?('labdev:heal:all') && arg.include?('[') }
388
+ puts '⚠️ labdev:heal:all does not accept any arguments. Exiting.'
389
+ puts 'Use labdev:heal:ruby[path] or labdev:heal:adoc[path] to auto-fix specific files.'
390
+ return
391
+ end
392
+ Dev.run_auto_fix
393
+ end
394
+ end
395
+
396
+ # Base task; show help for 'heal' namespace
397
+ task :heal do
398
+ Help.show_task_help('heal')
399
+ end
400
+
401
+ # ============================================================
402
+ # SHOW and HELP tasks; Display information
403
+ # ============================================================
404
+
405
+ namespace :show do
406
+ desc desc_for('show:scripts')
407
+ task :scripts do
408
+ Dev.list_script_templates
409
+ end
410
+
411
+ desc desc_for('show:hooks')
412
+ task :hooks do
413
+ Dev.list_hook_templates
414
+ end
415
+
416
+ desc desc_for('show:rule')
417
+ task :rule, %i[tool rule] => [] do |_t, args|
418
+ tool = args[:tool]
419
+ rule = args[:rule]
420
+
421
+ if tool.nil? || rule.nil?
422
+ puts '❌ Tool and rule are required parameters.'
423
+ puts 'Usage: bundle exec rake labdev:show:rule[tool,rule]'
424
+ puts 'Example: bundle exec rake labdev:show:rule[vale,*.Spelling]'
425
+ return
426
+ end
427
+
428
+ Dev.show_lint_rule(tool, rule)
429
+ end
430
+ end
431
+
432
+ desc desc_for('help')
433
+ task :help, [:task_string] => [] do |_t, args|
434
+ Help.show_task_help(args[:task_string])
435
+ end
436
+ end
437
+ end
438
+ end
439
+ end
440
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module DocOpsLab
6
+ module Dev
7
+ module ToolExecution
8
+ class << self
9
+ def tool_available? tool_name
10
+ system("which #{tool_name} > /dev/null 2>&1")
11
+ end
12
+
13
+ def docker_available?
14
+ @docker_available ||= system('which docker > /dev/null 2>&1')
15
+ end
16
+
17
+ def image_available?
18
+ unless docker_available?
19
+ @image_available = false
20
+ return @image_available
21
+ end
22
+ @image_available ||= system('docker image inspect docopslab/dev > /dev/null 2>&1')
23
+ end
24
+
25
+ def run_with_fallback tool_name, command, use_docker: false
26
+ # Accept String or Array command; prefer Array for safer execution
27
+ cmd_runner = lambda do |cmd|
28
+ cmd.is_a?(Array) ? system(*cmd) : system(cmd)
29
+ end
30
+
31
+ # if env var LABDEV_DEBUG=true, print the full command
32
+ if ENV['LABDEV_DEBUG'] == 'true'
33
+ if command.is_a?(Array)
34
+ puts "🐛 [DEBUG] Command to run: #{command.map do |c|
35
+ Shellwords.escape(c)
36
+ end.join(' ')}"
37
+ else
38
+ puts "🐛 [DEBUG] Command to run: #{command}"
39
+ end
40
+ end
41
+
42
+ # Run command natively or fall back to Docker
43
+ if use_docker || !tool_available?(tool_name)
44
+ if image_available?
45
+ run_in_docker(command)
46
+ else
47
+ puts "❌ #{tool_name} not available natively and Docker not found"
48
+ puts " Install #{tool_name} or pull Docker image to continue" if docker_available?
49
+ puts " Install #{tool_name} or Docker to continue" unless docker_available?
50
+ false
51
+ end
52
+ else
53
+ cmd_runner.call(command)
54
+ end
55
+ end
56
+
57
+ def run_in_docker command
58
+ # Run command in docopslab/dev container
59
+ # Handle both String and Array command formats
60
+ cmd_str = command.is_a?(Array) ? command.shelljoin : command
61
+ docker_cmd = "docker run -it --rm -v \"$(pwd):/workspace\" -w /workspace docopslab/dev #{cmd_str}"
62
+ puts "🐳 Running in Docker: #{cmd_str}"
63
+ system(docker_cmd)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DocOpsLab
4
+ module Dev
5
+ VERSION = '0.1.0'
6
+ RUBY_TARGET = '3.2.7'
7
+ end
8
+ end