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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.adoc +904 -0
- data/assets/config-packs/actionlint/base.yml +13 -0
- data/assets/config-packs/actionlint/project.yml +13 -0
- data/assets/config-packs/htmlproofer/base.yml +27 -0
- data/assets/config-packs/htmlproofer/project.yml +25 -0
- data/assets/config-packs/rubocop/base.yml +130 -0
- data/assets/config-packs/rubocop/project.yml +8 -0
- data/assets/config-packs/shellcheck/base.shellcheckrc +14 -0
- data/assets/config-packs/subtxt/ai-asciidoc-antipatterns.sub.txt +11 -0
- data/assets/config-packs/vale/asciidoc/ExplicitSectionIDs.yml +8 -0
- data/assets/config-packs/vale/asciidoc/ExtraLineBeforeLevel1.yml +7 -0
- data/assets/config-packs/vale/asciidoc/OneSentencePerLine.yml +8 -0
- data/assets/config-packs/vale/asciidoc/PreferSourceBlocks.yml +8 -0
- data/assets/config-packs/vale/asciidoc/ProperAdmonitions.yml +8 -0
- data/assets/config-packs/vale/asciidoc/ProperDLs.yml +7 -0
- data/assets/config-packs/vale/asciidoc/UncleanListStart.yml +8 -0
- data/assets/config-packs/vale/authoring/ButParagraph.yml +8 -0
- data/assets/config-packs/vale/authoring/ExNotEg.yml +8 -0
- data/assets/config-packs/vale/authoring/LiteralTerms.yml +20 -0
- data/assets/config-packs/vale/authoring/Spelling.yml +679 -0
- data/assets/config-packs/vale/base.ini +38 -0
- data/assets/config-packs/vale/config/scripts/ExplicitSectionIDs.tengo +56 -0
- data/assets/config-packs/vale/config/scripts/ExtraLineBeforeLevel1.tengo +121 -0
- data/assets/config-packs/vale/config/scripts/OneSentencePerLine.tengo +53 -0
- data/assets/config-packs/vale/project.ini +5 -0
- data/assets/hooks/pre-commit +63 -0
- data/assets/hooks/pre-push +72 -0
- data/assets/scripts/adoc_section_ids.rb +50 -0
- data/assets/scripts/build-common.sh +193 -0
- data/assets/scripts/build-docker.sh +64 -0
- data/assets/scripts/build.sh +56 -0
- data/assets/scripts/parse_jekyll_asciidoc_logs.rb +467 -0
- data/assets/templates/Gemfile +7 -0
- data/assets/templates/Rakefile +3 -0
- data/assets/templates/gitignore +69 -0
- data/assets/templates/jekyll-asciidoc-fix.prompt.yml +17 -0
- data/assets/templates/spellcheck.prompt.yml +16 -0
- data/docopslab-dev.gemspec +56 -0
- data/docs/agent/AGENTS.md +229 -0
- data/docs/agent/index.md +80 -0
- data/docs/agent/missions/conduct-release.md +224 -0
- data/docs/agent/missions/setup-new-project.md +250 -0
- data/docs/agent/roles/devops-release-engineer.md +152 -0
- data/docs/agent/roles/docops-engineer.md +193 -0
- data/docs/agent/roles/planner-architect.md +74 -0
- data/docs/agent/roles/product-engineer.md +153 -0
- data/docs/agent/roles/product-manager.md +130 -0
- data/docs/agent/roles/project-manager.md +139 -0
- data/docs/agent/roles/qa-testing-engineer.md +115 -0
- data/docs/agent/roles/tech-docs-manager.md +143 -0
- data/docs/agent/roles/tech-writer.md +163 -0
- data/docs/agent/skills/asciidoc.md +609 -0
- data/docs/agent/skills/code-commenting.md +347 -0
- data/docs/agent/skills/fix-broken-links.md +309 -0
- data/docs/agent/skills/fix-jekyll-asciidoc-build-errors.md +23 -0
- data/docs/agent/skills/fix-spelling-issues.md +13 -0
- data/docs/agent/skills/git.md +170 -0
- data/docs/agent/skills/github-issues.md +135 -0
- data/docs/agent/skills/product-release-rollback-and-patching.md +71 -0
- data/docs/agent/skills/rake-cli-dev.md +57 -0
- data/docs/agent/skills/readme-driven-dev.md +13 -0
- data/docs/agent/skills/release-history.md +29 -0
- data/docs/agent/skills/ruby.md +192 -0
- data/docs/agent/skills/schemagraphy-sgyml.md +18 -0
- data/docs/agent/skills/tests-running.md +25 -0
- data/docs/agent/skills/tests-writing.md +45 -0
- data/docs/agent/skills/write-the-docs.md +54 -0
- data/docs/agent/topics/common-project-paths.md +117 -0
- data/docs/agent/topics/dev-tooling-usage.md +202 -0
- data/docs/agent/topics/devops-ci-cd.md +55 -0
- data/docs/agent/topics/product-docs-deployment.md +25 -0
- data/lib/docopslab/dev/auto_fix_asciidoc.rb +46 -0
- data/lib/docopslab/dev/checkers.rb +108 -0
- data/lib/docopslab/dev/config_manager.rb +241 -0
- data/lib/docopslab/dev/file_utils.rb +140 -0
- data/lib/docopslab/dev/git_hooks.rb +140 -0
- data/lib/docopslab/dev/help.rb +121 -0
- data/lib/docopslab/dev/initializer.rb +95 -0
- data/lib/docopslab/dev/linters.rb +451 -0
- data/lib/docopslab/dev/log_parser.rb +31 -0
- data/lib/docopslab/dev/paths.rb +46 -0
- data/lib/docopslab/dev/script_manager.rb +136 -0
- data/lib/docopslab/dev/spell_check.rb +194 -0
- data/lib/docopslab/dev/sync_ops.rb +468 -0
- data/lib/docopslab/dev/tasks.rb +440 -0
- data/lib/docopslab/dev/tool_execution.rb +68 -0
- data/lib/docopslab/dev/version.rb +8 -0
- data/lib/docopslab/dev.rb +392 -0
- data/specs/data/default-manifest.yml +64 -0
- data/specs/data/manifest-schema.yaml +63 -0
- data/specs/data/tasks-def.yml +321 -0
- data/specs/data/tools.yml +60 -0
- metadata +362 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module DocOpsLab
|
|
6
|
+
module Dev
|
|
7
|
+
module ScriptManager
|
|
8
|
+
class << self
|
|
9
|
+
def sync_scripts
|
|
10
|
+
puts '📜 Syncing common scripts from DocOps Lab...'
|
|
11
|
+
|
|
12
|
+
unless Dir.exist?(SCRIPTS_SOURCE_DIR)
|
|
13
|
+
puts '❌ No scripts directory found in gem'
|
|
14
|
+
return false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Ensure vendor scripts directory exists
|
|
18
|
+
vendor_scripts_dir = File.join('scripts', '.vendor', 'docopslab')
|
|
19
|
+
FileUtils.mkdir_p(vendor_scripts_dir)
|
|
20
|
+
|
|
21
|
+
synced_count = 0
|
|
22
|
+
|
|
23
|
+
Dir.glob("#{SCRIPTS_SOURCE_DIR}/*").each do |script_path|
|
|
24
|
+
next unless File.file?(script_path)
|
|
25
|
+
|
|
26
|
+
script_name = File.basename(script_path)
|
|
27
|
+
dest_path = File.join(vendor_scripts_dir, script_name)
|
|
28
|
+
|
|
29
|
+
# Check if file needs updating
|
|
30
|
+
if !File.exist?(dest_path) || File.read(script_path) != File.read(dest_path)
|
|
31
|
+
FileUtils.cp(script_path, dest_path)
|
|
32
|
+
File.chmod(0o755, dest_path) # Make executable
|
|
33
|
+
puts " 📜 Synced: #{dest_path} (executable)"
|
|
34
|
+
synced_count += 1
|
|
35
|
+
else
|
|
36
|
+
puts " ✅ Up to date: #{dest_path}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if synced_count.positive?
|
|
41
|
+
puts "✅ Script sync complete; #{synced_count} files updated"
|
|
42
|
+
else
|
|
43
|
+
puts '✅ All scripts up to date'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def list_script_templates
|
|
50
|
+
unless Dir.exist?(SCRIPTS_SOURCE_DIR)
|
|
51
|
+
puts '❌ No scripts directory found in gem'
|
|
52
|
+
return false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
puts '📜 Available script templates:'
|
|
56
|
+
|
|
57
|
+
Dir.glob("#{SCRIPTS_SOURCE_DIR}/*").each do |script_path|
|
|
58
|
+
next unless File.file?(script_path)
|
|
59
|
+
|
|
60
|
+
script_name = File.basename(script_path)
|
|
61
|
+
|
|
62
|
+
# Try to extract description from script comments
|
|
63
|
+
description = 'No description available'
|
|
64
|
+
if File.readable?(script_path)
|
|
65
|
+
File.open(script_path, 'r') do |f|
|
|
66
|
+
f.each_line do |line|
|
|
67
|
+
if line.match(/^#\s*(.+)$/) && !line.include?('!/bin/')
|
|
68
|
+
description = line.match(/^#\s*(.+)$/)[1]
|
|
69
|
+
break
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
puts " • #{script_name}: #{description}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def run_script script_name, args=[]
|
|
82
|
+
unless script_name
|
|
83
|
+
puts '❌ Script name is required'
|
|
84
|
+
puts 'Usage: bundle exec rake labdev:run[script_name] -- [args]'
|
|
85
|
+
return false
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Add .sh extension if NO extension provided
|
|
89
|
+
# (valid extensions are .sh, .rb, py, .js)
|
|
90
|
+
script_name += '.sh' unless File.extname(script_name).length.positive?
|
|
91
|
+
|
|
92
|
+
# Look for local script first
|
|
93
|
+
project_script = File.join('scripts', script_name)
|
|
94
|
+
vendor_script = File.join('scripts', '.vendor', 'docopslab', script_name)
|
|
95
|
+
|
|
96
|
+
script_to_run = nil
|
|
97
|
+
if File.exist?(project_script)
|
|
98
|
+
puts "📜 Running local script: #{project_script}"
|
|
99
|
+
script_to_run = project_script
|
|
100
|
+
elsif File.exist?(vendor_script)
|
|
101
|
+
puts "📜 Running vendor script: #{vendor_script}"
|
|
102
|
+
script_to_run = vendor_script
|
|
103
|
+
else
|
|
104
|
+
puts "❌ Script not found: #{script_name}"
|
|
105
|
+
puts 'Searched in:'
|
|
106
|
+
puts " - #{project_script}"
|
|
107
|
+
puts " - #{vendor_script}"
|
|
108
|
+
return false
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Run the script using proper runtime and args
|
|
112
|
+
case File.extname(script_to_run)
|
|
113
|
+
when '.sh', ''
|
|
114
|
+
cmd = ['bash']
|
|
115
|
+
when '.rb'
|
|
116
|
+
cmd = ['ruby']
|
|
117
|
+
when '.py'
|
|
118
|
+
cmd = ['python3']
|
|
119
|
+
when '.js'
|
|
120
|
+
cmd = ['node']
|
|
121
|
+
else
|
|
122
|
+
puts "❌ Unsupported script extension: #{File.extname(script_to_run)}"
|
|
123
|
+
return false
|
|
124
|
+
end
|
|
125
|
+
cmd << script_to_run
|
|
126
|
+
cmd.concat(args) if args.any?
|
|
127
|
+
puts "🚀 Executing: #{cmd.join(' ')}"
|
|
128
|
+
|
|
129
|
+
system(*cmd)
|
|
130
|
+
|
|
131
|
+
$CHILD_STATUS.success?
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'time'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
|
|
7
|
+
module DocOpsLab
|
|
8
|
+
module Dev
|
|
9
|
+
# SpellCheck functionality for parsing Vale logs and managing spelling corrections
|
|
10
|
+
module SpellCheck
|
|
11
|
+
def self.spelling_config_path
|
|
12
|
+
File.join(GEM_ROOT, 'assets', 'config-packs', 'vale', 'authoring', 'Spelling.yml')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
def generate_spellcheck_report file_path=nil
|
|
17
|
+
puts '📝 Generating spellcheck report from Vale output...'
|
|
18
|
+
|
|
19
|
+
manifest = DocOpsLab::Dev.load_manifest
|
|
20
|
+
spellcheck_config = manifest['spellcheck'] || {}
|
|
21
|
+
output_dir = spellcheck_config['output_dir'] || '.agent/reports/'
|
|
22
|
+
FileUtils.mkdir_p(output_dir)
|
|
23
|
+
output_file = spellcheck_config['output_file'] || nil
|
|
24
|
+
prompt = spellcheck_config['prompt'] || nil
|
|
25
|
+
|
|
26
|
+
unless defined?(output_file) && output_file
|
|
27
|
+
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
|
|
28
|
+
output_file = "spellcheck-#{timestamp}.yml"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
output_path = File.join(output_dir, output_file)
|
|
32
|
+
|
|
33
|
+
# Run Vale with JSON output and spelling filter
|
|
34
|
+
# Pass just the rule name; filter builder will add .Name== wrapper
|
|
35
|
+
# If file_path specified, only check that file
|
|
36
|
+
vale_json = DocOpsLab::Dev.run_vale(
|
|
37
|
+
file_path,
|
|
38
|
+
'',
|
|
39
|
+
output_format: :json,
|
|
40
|
+
filter: 'DocOpsLab-Authoring.Spelling')
|
|
41
|
+
return false if vale_json.nil?
|
|
42
|
+
|
|
43
|
+
# Parse JSON output for spelling issues
|
|
44
|
+
spelling_issues = parse_vale_json_output(vale_json)
|
|
45
|
+
|
|
46
|
+
if spelling_issues.empty?
|
|
47
|
+
puts '✅ No spelling issues found!'
|
|
48
|
+
return true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Generate YAML report
|
|
52
|
+
generate_yaml_report(spelling_issues, output_path, prompt)
|
|
53
|
+
|
|
54
|
+
puts "📄 SpellCheck report generated: #{output_path}"
|
|
55
|
+
puts "Found #{spelling_issues.length} spelling issues to review"
|
|
56
|
+
true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def parse_vale_json_output json_output
|
|
62
|
+
require 'json'
|
|
63
|
+
|
|
64
|
+
issues = []
|
|
65
|
+
seen_terms = {}
|
|
66
|
+
|
|
67
|
+
begin
|
|
68
|
+
vale_data = JSON.parse(json_output)
|
|
69
|
+
rescue JSON::ParserError => e
|
|
70
|
+
puts "❌ Failed to parse Vale JSON output: #{e.message}"
|
|
71
|
+
return []
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Vale JSON structure: { "file_path" => [{ "Check" => "rule", "Message" => "message", "Line" => num, "Span" => [start, end], "Severity" => "level", "Match" => "word" }] }
|
|
75
|
+
vale_data.each do |file_path, file_issues|
|
|
76
|
+
file_issues.each do |issue|
|
|
77
|
+
rule = issue['Check']
|
|
78
|
+
issue['Message']
|
|
79
|
+
line_num = issue['Line']
|
|
80
|
+
span = issue['Span']
|
|
81
|
+
misspelled_word = issue['Match']
|
|
82
|
+
|
|
83
|
+
# Only process spelling issues (should be filtered already, but double-check)
|
|
84
|
+
next unless rule&.include?('Spelling')
|
|
85
|
+
|
|
86
|
+
# Skip if no misspelled word found
|
|
87
|
+
next unless misspelled_word && !misspelled_word.empty?
|
|
88
|
+
|
|
89
|
+
term = misspelled_word
|
|
90
|
+
col = span ? span[0] : 1
|
|
91
|
+
|
|
92
|
+
# Get context around the issue
|
|
93
|
+
context_text = get_line_context(file_path, line_num, col, term)
|
|
94
|
+
|
|
95
|
+
issue_entry = {
|
|
96
|
+
'term' => term,
|
|
97
|
+
'path' => file_path,
|
|
98
|
+
'text' => context_text,
|
|
99
|
+
'line' => "#{line_num},#{col}",
|
|
100
|
+
'fix?' => nil
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Add 'all?' field only for the first occurrence of each term
|
|
104
|
+
unless seen_terms[term]
|
|
105
|
+
issue_entry['all?'] = nil
|
|
106
|
+
seen_terms[term] = true
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
issues << issue_entry
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
issues
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def get_line_context file_path, line_num, _col, term
|
|
117
|
+
return '' unless File.exist?(file_path)
|
|
118
|
+
|
|
119
|
+
begin
|
|
120
|
+
lines = File.readlines(file_path)
|
|
121
|
+
target_line = lines[line_num - 1]
|
|
122
|
+
return '' unless target_line
|
|
123
|
+
|
|
124
|
+
# Get some words around the term for context
|
|
125
|
+
# Find the term in the line and extract surrounding words
|
|
126
|
+
words = target_line.split(/\s+/)
|
|
127
|
+
term_index = words.find_index { |word| word.include?(term) }
|
|
128
|
+
|
|
129
|
+
if term_index
|
|
130
|
+
# Get 3 words before and after the term
|
|
131
|
+
start_idx = [term_index - 3, 0].max
|
|
132
|
+
end_idx = [term_index + 3, words.length - 1].min
|
|
133
|
+
context_words = words[start_idx..end_idx]
|
|
134
|
+
context_words.join(' ')
|
|
135
|
+
else
|
|
136
|
+
# Fallback: just return the line trimmed
|
|
137
|
+
target_line.strip[0..80] + (target_line.length > 80 ? '...' : '')
|
|
138
|
+
end
|
|
139
|
+
rescue StandardError => e
|
|
140
|
+
puts "⚠️ Could not read context from #{file_path}: #{e.message}"
|
|
141
|
+
''
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def generate_yaml_report errors, output_file, agent_prompt=nil
|
|
146
|
+
require 'erb'
|
|
147
|
+
|
|
148
|
+
unless agent_prompt
|
|
149
|
+
template_path = File.join(TEMPLATES_DIR, 'spellcheck.prompt.yml')
|
|
150
|
+
agent_prompt = File.read(template_path) if File.exist?(template_path)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Create the YAML content with ERB templating for better formatting
|
|
154
|
+
template = ERB.new(yaml_template)
|
|
155
|
+
|
|
156
|
+
yaml_content = template.result_with_hash(errors: errors, agent_prompt: agent_prompt)
|
|
157
|
+
|
|
158
|
+
File.write(output_file, yaml_content)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def yaml_template
|
|
162
|
+
<<~TEMPLATE
|
|
163
|
+
# SpellCheck Report
|
|
164
|
+
#
|
|
165
|
+
# User Instructions:
|
|
166
|
+
# For each entry, enter a fix?: value of:
|
|
167
|
+
# 'no' or 'n' to skip fixing for now (deleting the entry is more efficient)
|
|
168
|
+
# 'add' or 'd' for add to dictionary
|
|
169
|
+
# use add("[cC]orrected term") to indicate verbatim text to add
|
|
170
|
+
# use docops("term") or nontech("term") to better organize additions
|
|
171
|
+
# 'fix' if it's a typo to be corrected
|
|
172
|
+
# 'fix("corrected")' where corrected is the intended text
|
|
173
|
+
# 'pass' will wrap in <!-- vale off -->', '<!-- vale on -->'
|
|
174
|
+
#
|
|
175
|
+
# After editing this file, use an AI agent to process the fixes.
|
|
176
|
+
#
|
|
177
|
+
---
|
|
178
|
+
<% errors.each do |issue| %>
|
|
179
|
+
- term: "<%= issue['term'] %>"
|
|
180
|
+
path: <%= issue['path'] %>
|
|
181
|
+
text: |
|
|
182
|
+
<%= issue['text'] %>
|
|
183
|
+
line: [<%= issue['line'] %>]
|
|
184
|
+
fix?:#{' '}
|
|
185
|
+
<% end %>
|
|
186
|
+
#
|
|
187
|
+
# AI Agent Instructions:
|
|
188
|
+
<%= agent_prompt %>
|
|
189
|
+
TEMPLATE
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|