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,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,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
|