diff --git a/.config/commit-conventions.yml b/.config/commit-conventions.yml new file mode 100644 index 00000000..68c67695 --- /dev/null +++ b/.config/commit-conventions.yml @@ -0,0 +1,10 @@ +# Project-local commit convention overrides. +--- +inherit_from: .vendor/docopslab/commit-conventions.yml + +conventions: + scopes: + labdev: + name: docopslab-dev + summ: Changes to the docopslab-dev gem and distributed development tooling + akas: [docopslab-dev, dev, gem] diff --git a/.config/docopslab-dev.yml b/.config/docopslab-dev.yml index 16607d2e..9babd022 100644 --- a/.config/docopslab-dev.yml +++ b/.config/docopslab-dev.yml @@ -85,6 +85,21 @@ tools: target: .config/actionlint.yml synced: false + - tool: git-lint + files: + - source: commit-conventions/base.yml + target: .config/.vendor/docopslab/commit-conventions.yml + synced: true + - source: commit-conventions/project.yml + target: .config/commit-conventions.yml + synced: false + - source: git-lint/base.yml + target: .config/.vendor/docopslab/git-lint.yml + synced: true + - source: git-lint/project.yml + target: .config/git-lint.local.yml + synced: false + # Local library declaration used by lab release and local tests library: enabled: true diff --git a/.config/git-lint.local.yml b/.config/git-lint.local.yml new file mode 100644 index 00000000..7e1c7558 --- /dev/null +++ b/.config/git-lint.local.yml @@ -0,0 +1,6 @@ +# Project-local git-lint overrides. +# +# This file is copied once to `.config/git-lint.local.yml`. +# Leave it empty to use DocOps Lab defaults, or override keys from +# `.config/.vendor/docopslab/git-lint.yml`. +--- diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 34d456a1..e79300d2 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -184,7 +184,7 @@ jobs: else echo "❌ **RuboCop**: Failed" >> $GITHUB_STEP_SUMMARY fi - + # Vale if [ "${{ inputs.skip_vale }}" = "true" ]; then echo "⏭️ **Vale**: Skipped" >> $GITHUB_STEP_SUMMARY @@ -201,4 +201,4 @@ jobs: echo "✅ **HTML-Proofer**: Passed" >> $GITHUB_STEP_SUMMARY else echo "❌ **HTML-Proofer**: Failed" >> $GITHUB_STEP_SUMMARY - fi \ No newline at end of file + fi diff --git a/.gitignore b/.gitignore index 62a911ba..9ddda98a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,12 +33,16 @@ vendor/ # Markdown files (except important docs) *.md ./!AGENTS.md +!gems/docopslab-dev/assets/templates/CLAUDE.md # DocOps Lab vendor files .config/.vendor/ +# tag::universal-docopslab-dev-configs[] # Generated config files (merged from base + local) .config/vale.ini .config/htmlproofer.yml +.config/git-lint.yml +# end::universal-docopslab-dev-configs[] # Gem content paths gems/**/pkg/ diff --git a/Gemfile b/Gemfile index e8ec4337..bbf213e9 100644 --- a/Gemfile +++ b/Gemfile @@ -6,12 +6,13 @@ source 'https://rubygems.org' gem 'docopslab-dev', path: './gems/docopslab-dev' # 3rd Party gem 'colorize', '~> 1.1' -gem 'jekyll', '~> 4.r' +gem 'jekyll', '~> 4.4' gem 'pathspec', '~> 2.1' gem 'reverse_markdown' gem 'rubyzip', '~> 2.3' # For Vale package building gem 'sass' # DocOps Lab +gem 'asciisourcerer' gem 'schemagraphy' group :jekyll_plugins do diff --git a/Gemfile.lock b/Gemfile.lock index b3cc164e..e34d048a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,14 +1,15 @@ PATH remote: gems/docopslab-dev specs: - docopslab-dev (0.3.1) + docopslab-dev (0.4.0) asciidoctor (~> 2.0) - asciisourcerer (~> 0.4) + asciisourcerer (~> 0.5) brakeman (~> 7.1) bundler-audit (~> 0.9) debride (~> 1.13) fasterer (~> 0.11) flog (~> 4.8) + git-lint (~> 6.2) html-proofer (~> 5.2) inch (~> 0.8) rake (~> 13.0) @@ -31,7 +32,7 @@ GEM asciidoctor-html5s (0.5.1) asciidoctor (>= 1.5.7, < 3.0) thread_safe (~> 0.3.4) - asciisourcerer (0.4.0) + asciisourcerer (0.5.0) asciidoctor (~> 2.0) asciidoctor-html5s (~> 0.5) jekyll (~> 4.4) @@ -39,6 +40,7 @@ GEM kramdown-asciidoc (~> 2.1) liquid (~> 4.0) reverse_markdown (~> 2.1) + yard (~> 0.9) ast (2.4.3) async (2.32.0) console (~> 1.29) @@ -55,6 +57,11 @@ GEM bundler (>= 1.2.0, < 3) thor (~> 1.0) coderay (1.1.3) + cogger (0.14.0) + core (~> 0.1) + refinements (~> 11.0) + tone (~> 0.3) + zeitwerk (~> 2.6) colorator (1.1.0) colorize (1.1.0) concurrent-ruby (1.3.5) @@ -62,6 +69,7 @@ GEM fiber-annotation fiber-local (~> 1.1) json + core (0.1.6) crass (1.0.6) csv (3.3.5) date (3.5.1) @@ -73,6 +81,8 @@ GEM dry-configurable (1.3.0) dry-core (~> 1.1) zeitwerk (~> 2.6) + dry-container (0.11.0) + concurrent-ruby (~> 1.0) dry-core (1.1.0) concurrent-ruby (~> 1.0) logger @@ -84,6 +94,10 @@ GEM concurrent-ruby (~> 1.0) dry-core (~> 1.1) zeitwerk (~> 2.6) + dry-monads (1.10.0) + concurrent-ruby (~> 1.0) + dry-core (~> 1.1) + zeitwerk (~> 2.6) dry-schema (1.14.1) concurrent-ruby (~> 1.0) dry-configurable (~> 1.0, >= 1.0.1) @@ -102,6 +116,14 @@ GEM em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) + etcher (0.5.1) + cogger (~> 0.12) + core (~> 0.1) + dry-monads (~> 1.6) + dry-types (~> 1.7) + refinements (~> 11.0) + versionaire (~> 12.1) + zeitwerk (~> 2.6) ethon (0.15.0) ffi (>= 1.15.0) eventmachine (1.2.7) @@ -121,6 +143,26 @@ GEM ruby_parser (~> 3.1, > 3.1.0) sexp_processor (~> 4.8) forwardable-extended (2.6.0) + git-lint (6.2.1) + cogger (~> 0.12) + core (~> 0.1) + dry-container (~> 0.11) + dry-monads (~> 1.6) + dry-schema (~> 1.13) + etcher (~> 0.2) + gitt (~> 2.0) + infusible (~> 2.2) + refinements (~> 11.0) + runcom (~> 10.0) + sod (~> 0.0) + spek (~> 2.0) + tone (~> 0.3) + zeitwerk (~> 2.6) + gitt (2.2.0) + core (~> 0.1) + dry-monads (~> 1.6) + refinements (~> 11.0) + zeitwerk (~> 2.6) google-protobuf (4.32.0-x86_64-linux-gnu) bigdecimal rake (>= 13) @@ -147,6 +189,9 @@ GEM sparkr (>= 0.2.0) term-ansicolor yard (~> 0.9.12) + infusible (2.2.1) + marameters (~> 2.0) + zeitwerk (~> 2.6) io-event (1.14.0) jekyll (4.4.1) addressable (~> 2.4) @@ -198,6 +243,9 @@ GEM loofah (2.25.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) + marameters (2.1.1) + refinements (~> 11.0) + zeitwerk (~> 2.6) mercenary (0.4.0) method_source (1.1.0) metrics (0.15.0) @@ -241,6 +289,7 @@ GEM parser (~> 3.3.0) rainbow (>= 2.0, < 4.0) rexml (~> 3.1) + refinements (11.1.3) regexp_parser (2.11.3) reverse_markdown (2.1.1) nokogiri @@ -274,6 +323,10 @@ GEM racc (~> 1.5) sexp_processor (~> 4.16) rubyzip (2.4.1) + runcom (10.2.1) + refinements (~> 11.0) + xdg (~> 7.1) + zeitwerk (~> 2.6) safe_yaml (1.0.5) sass (3.7.4) sass-listen (~> 4.0.0) @@ -297,7 +350,19 @@ GEM simplecov_json_formatter (~> 0.1) simplecov-html (0.13.2) simplecov_json_formatter (0.1.4) + sod (0.3.1) + cogger (~> 0.12) + dry-container (~> 0.11) + infusible (~> 2.2) + refinements (~> 11.0) + tone (~> 0.3) + zeitwerk (~> 2.6) sparkr (0.4.1) + spek (2.1.1) + dry-monads (~> 1.6) + refinements (~> 11.0) + versionaire (~> 12.0) + zeitwerk (~> 2.6) stringio (3.2.0) subtxt (0.3.0) sync (0.5.0) @@ -313,13 +378,19 @@ GEM mize (~> 0.6) sync to_regexp (0.2.1) + tone (0.4.2) + refinements (~> 11.0) + zeitwerk (~> 2.6) traces (0.18.2) ttfunk (1.8.0) bigdecimal (~> 3.1) typhoeus (1.5.0) ethon (>= 0.9.0, < 0.16.0) unicode-display_width (2.6.0) + versionaire (12.1.1) + refinements (~> 11.0) webrick (1.9.1) + xdg (7.1.3) yaml (0.4.0) yard (0.9.37) yell (2.2.2) @@ -329,12 +400,13 @@ PLATFORMS x86_64-linux DEPENDENCIES + asciisourcerer colorize (~> 1.1) docopslab-dev! feedjira (~> 3.2) http_parser.rb (~> 0.6.0) httparty (~> 0.21) - jekyll (~> 4.r) + jekyll (~> 4.4) jekyll-asciidoc (~> 3.0) jekyll-redirect-from (~> 0.16) jekyll-seo-tag (~> 2.8) diff --git a/README.adoc b/README.adoc index ee871413..677381dd 100644 --- a/README.adoc +++ b/README.adoc @@ -574,6 +574,9 @@ See link:https://vale.sh/docs/styles[Vale's Style documentation] for details on ==== Asset Library Publishing These tasks live in the `DocOps/lab` Rakefile and are used to stage and publish library assets to the `labdev-library` branch. +Generated agent docs follow this chain of truth: +`_docs/agent/` -> `_site/docs/agent/` -> `gems/docopslab-dev/docs/agent/` -> `.library/` -> `~/.cache/docopslab/dev/library/current/`. +Downstream projects running `bundle exec rake labdev:sync:docs` read the local cache first when it is available. .Stage assets from `gems/docopslab-dev/assets/` and generated docs into `.library/` [.prompt] @@ -591,6 +594,13 @@ These tasks live in the `DocOps/lab` Rakefile and are used to stage and publish [.prompt] bundle exec rake gemdo:push:library +.Build the latest agent docs and load them into the local cache for downstream repo testing +[.prompt] + bundle exec rake gemdo:push:library:docs:local + +Use `gemdo:push:library:docs:local` before running `bundle exec rake labdev:sync:docs` in a local downstream checkout such as AsciiSourcerer. +This avoids testing against a stale `~/.cache/docopslab/dev/library/current/` copy. + [[troubleshooting]] == Troubleshooting diff --git a/Rakefile b/Rakefile index 5406b2db..4a7babab 100644 --- a/Rakefile +++ b/Rakefile @@ -230,6 +230,39 @@ task :write_rubocop_styles do puts '✅ RuboCop styles written to _docs/partials/built/_rubocop-styles.adoc' end +def asciisourcerer_doc_partials_dir + candidates = [] + candidates << ENV['ASCIISOURCERER_DOC_PARTIALS_DIR'] if ENV['ASCIISOURCERER_DOC_PARTIALS_DIR'] + candidates << File.expand_path('../asciisourcerer/lib/sourcerer/_docs/partials', __dir__) + + if Gem.loaded_specs['asciisourcerer'] + candidates << File.join(Gem.loaded_specs['asciisourcerer'].full_gem_path, 'lib/sourcerer/_docs/partials') + end + + candidates.find { |path| Dir.exist?(path) } +end + +desc 'Copy AsciiSourcerer documentation partials for site includes' +task :copy_asciisourcerer_doc_partials do + source_dir = asciisourcerer_doc_partials_dir + unless source_dir + abort '❌ AsciiSourcerer documentation partials not found. Expected lib/sourcerer/_docs/partials ' \ + 'in ../asciisourcerer or the bundled asciisourcerer gem.' + end + + partials = Dir.glob(File.join(source_dir, '*.adoc')) + abort "❌ No AsciiSourcerer documentation partials found in #{source_dir}" if partials.empty? + + dest_dir = '_docs/partials/built' + FileUtils.mkdir_p(dest_dir) + partials.each do |source| + dest = File.join(dest_dir, File.basename(source)) + FileUtils.cp(source, dest) + puts " ✓ Copied #{dest}" + end + puts "✅ Copied #{partials.count} AsciiSourcerer documentation partial(s)" +end + desc 'Render an AsciiDoc file of universal attributes' # use _data/docops-lab-projects.yml data and the Liquid template at _includes/docpslab-universal-attributes.asciidoc to produce a file at _docs/partials/built/_docopslab-universal-attributes.adoc task :generate_universal_attributes do @@ -264,11 +297,17 @@ task :build_vale_package do system('bundle exec ruby scripts/build_vale_package.rb') or raise 'Failed to build Vale package' end -desc 'Build the Jekyll site (with single-sourced cards and project pages)' -task build_site: %i[extract_readme_attrs generate_project_pages generate_metadata copy_jekyll_ui_config - write_rubocop_styles generate_universal_attributes gemdo:gen_agent_docs] do +desc 'Build Jekyll HTML used by the site and generated docs' +task build_site_html: %i[extract_readme_attrs generate_project_pages generate_metadata copy_jekyll_ui_config + write_rubocop_styles copy_asciisourcerer_doc_partials generate_universal_attributes] do puts '🔨 Building Jekyll site...' system('bundle exec jekyll build') or raise 'Jekyll build failed' + puts '✅ Jekyll HTML build complete' +end + +desc 'Build the Jekyll site (with single-sourced cards, project pages, and agent docs)' +task build_site: [:build_site_html] do + Rake::Task['gemdo:gen_agent_docs'].invoke puts '✅ Build complete' end @@ -652,11 +691,7 @@ namespace :gemdo do task :gen_agent_docs do require_relative 'scripts/gen_agent_docs' - # Build Jekyll site if not already built (for agent docs HTML) - unless Dir.exist?(BUILD_DIR) && !Dir.empty?(BUILD_DIR) - puts '📄 Building Jekyll site for agent docs HTML...' - system('bundle exec jekyll build') or raise 'Jekyll build failed' - end + Rake::Task['build_site_html'].invoke # Run the generation script GenAgentDocs.run(BUILD_DIR) @@ -713,6 +748,14 @@ namespace :gemdo do puts ' Run `bundle exec rake labdev:show:library` in any downstream project to verify.' end + namespace :docs do + desc 'Build latest agent docs, stage library, and load it into the host cache for downstream sync' + task local: %w[gemdo:push:library:local] do + puts '✅ Latest generated docs are available in the local library cache.' + puts ' Downstream projects can now run `bundle exec rake labdev:sync:docs`.' + end + end + desc "Force-push #{LibraryManager::STAGE_DIR}/ to the #{LIBRARY_BRANCH} branch on GitHub" task :remote do require 'tmpdir' diff --git a/_blog/how-to-train-your-agent.adoc b/_blog/how-to-train-your-agent.adoc index 815acbef..7456d865 100644 --- a/_blog/how-to-train-your-agent.adoc +++ b/_blog/how-to-train-your-agent.adoc @@ -116,7 +116,7 @@ For now, you have to roll up your sleeves and incorporate lessons from all the k These are the ways bots need to be adjusted to work well with _your_ tech stack, _your_ framework, _your_ conventions, _your_ styles, and so forth. You will also need a way to distribute and sync this between projects, and to modify or override some of it in particular projects, when the standard conventions do not translate or apply. -Cross-project orchestration is out of scope for this post, but I touched on it in {xref_blog_single-sourcing-for-ai-agents_link} and I will talk about it more in the future. +Cross-project orchestration is out of scope for this post, but I touched on it link:{xref_blog_single-sourcing-for-ai-agents_url}#distribution[here]. **** Instead, you need to _organize_ your documentation into a taxonomy of resources that the LLM can access as needed. diff --git a/_config.yml b/_config.yml index 1f24723c..a5538350 100644 --- a/_config.yml +++ b/_config.yml @@ -100,12 +100,13 @@ defaults: values: type: procedure indexed: true - - scope: + - scope: path: "_docs/agent/" values: type: agent indexed: false noindex: true + permalink: /docs/agent/:slug/ - scope: path: "_docs/agent/topics" values: @@ -122,6 +123,12 @@ defaults: path: "_docs/agent/missions" values: group: missions + - scope: + path: "_docs/partials/" + values: + published: false + indexed: false + noindex: true # end::defaults-snippet[] highlighting: @@ -202,4 +209,4 @@ doc_types_order: - template xref_attrs: - outfile: _docs/partials/built/xref_attrs.adoc \ No newline at end of file + outfile: _docs/partials/built/xref_attrs.adoc diff --git a/_data/docops-lab-projects.yml b/_data/docops-lab-projects.yml index 8f0070d2..436f132f 100644 --- a/_data/docops-lab-projects.yml +++ b/_data/docops-lab-projects.yml @@ -336,6 +336,24 @@ projects: icon: folder-tree libs: [OpenPathYML] +# FRAMEWORKS (FRONT-END / DESIGN) + +## AsciiDoc FabLab: + - name: AsciiDoc FabLab + slug: asciidoc-fablab + type: framework + desc: | + CSS, JavaScript, and other front-end affecting assets for designing and styling rich-text output from AsciiDoc source files, with a focus on accessibility. + line: Component library and methods for designing AsciiDoc-sourced output + vrsn: 0.1.0 + tags: [design,styling,front-end] + tech: [CSS,Sass,JavaScript,Node.js,HTML,Asciidoctor,Asciidoctor.js] + wave: 1 + done: 90% + icon: palette + deps: [] + libs: [OpenFormYML] + # FRAMEWORKS (DOCUMENT[ATION] OPS) ## Clientele-as-Code: @@ -950,28 +968,40 @@ projects: - name: OpenHXY desc: YAML-based syntax for product release history, notes, changelog slug: open-release-history-sgyml + repo: releasehx - name: OpenSGGY desc: YAML-based syntax for style guide / glossary definition format (Open Style Guides) slug: open-style-guide-glossary-sgyml + repo: schemagraphy - name: OpenVMY desc: YAML-based syntax for version (source and output) divergence mapping system slug: open-version-management-sgyml + repo: versioneer - name: OpenCLY desc: YAML-based syntax for defining & documenting CLIs slug: open-cli-definition-sgyml + repo: schemagraphy - name: OpenFormY desc: YAML-based syntax for defining & documenting Web forms slug: open-form-definition-sgyml + repo: schemagraphy - name: OpenCFGY desc: YAML-based syntax for defining config files slug: open-config-definition-sgyml + repo: schemagraphy - name: OpenPathY desc: | A YAML-based syntax for defining, documenting, validating, & initiating fileset structures slug: open-pathtree-sgyml + repo: openpathyml - name: OpenIMY desc: YAML-based syntax for defining issue tickets slug: open-issues-definition-sgyml + repo: issuer + - name: UserStoryML + desc: YAML-based syntax for defining user stories + slug: open-user-story-definition-sgyml + repo: schemagraphy packs: schemagraphy-specs: desc: | @@ -986,6 +1016,7 @@ projects: - schemagraphy-open-config-spec-ruby - schemagraphy-open-pathy-spec-ruby - schemagraphy-open-issues-spec-ruby + - schemagraphy-open-user-story-spec-ruby schemagraphy-specs-py: desc: | Specification schemas as Python libraries. @@ -999,6 +1030,7 @@ projects: - schemagraphy-open-config-spec-python - schemagraphy-open-pathy-spec-python - schemagraphy-open-issues-spec-python + - schemagraphy-open-user-story-spec-python schemagraphy-specs-npm: desc: | Specification schemas as JavaScript libraries. @@ -1012,6 +1044,7 @@ projects: - schemagraphy-open-config-spec-node - schemagraphy-open-pathy-spec-node - schemagraphy-open-issues-spec-node + - schemagraphy-open-user-story-spec-node tech: [SGYML,YAML,JSON,JSON Schema,JMESPath,JSONPath,Ruby,Python,JavaScript] wave: 2 done: 30% diff --git a/_data/tech-docs-serials.yml b/_data/tech-docs-serials.yml index 41fbcd08..7c6aaf61 100644 --- a/_data/tech-docs-serials.yml +++ b/_data/tech-docs-serials.yml @@ -214,6 +214,14 @@ services: desc: | Sarah Maddox’s technical writing (and occasional fiction) blog offering practical insights on technical communication, documentation community events, and writing in the tech world (with a dash of whimsy). +- name: Geri Reid + site: https://gerireid.com/blog + feed: https://gerireid.com/rss.xml + type: RSS + tags: [accessibility, technical-writing, ai, best-practices] + desc: | + Blog of an accessibility specialist and designer, covering design systems and user experience. + - name: Heroic Technical Writing site: https://heroictechwriting.com feed: https://heroictechwriting.com/feed diff --git a/_docs/agent/skills/git.adoc b/_docs/agent/skills/git.adoc index 9c648aae..73ab78fd 100644 --- a/_docs/agent/skills/git.adoc +++ b/_docs/agent/skills/git.adoc @@ -19,10 +19,12 @@ This document describes protocols for committing and pushing changes to a git Do . Commit messages should be concise and easy for users to edit. + See <> for guidance. -. Always prompt user to approve commits before pushing. +. Always prompt Operator to approve commits before pushing. . Use `gh` for interacting with GitHub whenever possible. + See <> for more information. + +. NEVER sign commits as a co-author. // end::basics-snippet[] include::../../task/development.adoc[tag=repo-state] diff --git a/_docs/agent/skills/liquid-templating.adoc b/_docs/agent/skills/liquid-templating.adoc new file mode 100644 index 00000000..26cc67b8 --- /dev/null +++ b/_docs/agent/skills/liquid-templating.adoc @@ -0,0 +1,8 @@ +--- +permalink: /docs/agent/liquid-templating/ +indexed: false +--- += Liquid Templating in DocOps Lab Projects +include::../_agent_settings.adoc[] + +include::../../reference/liquid-templating.adoc[tags="content"] \ No newline at end of file diff --git a/_docs/agent/skills/yaml-loading.adoc b/_docs/agent/skills/yaml-loading.adoc new file mode 100644 index 00000000..70d17d29 --- /dev/null +++ b/_docs/agent/skills/yaml-loading.adoc @@ -0,0 +1,13 @@ +--- +permalink: /docs/agent/yaml-loading/ +indexed: false +--- += Loading YAML/SGYML in DocOps Lab Projects +include::../_agent_settings.adoc[] + +As an AI agent, you can help DocOps Lab developers write code that properly loads YAML and SGYML data. + +include::../../reference/schemagraphy-sgyml.adoc[tag="loading-yaml"] + +.Detagging loaded data +include::../../reference/schemagraphy-sgyml.adoc[tag="detagging-example"] diff --git a/_docs/agent/topics/liquid-filters-reference.adoc b/_docs/agent/topics/liquid-filters-reference.adoc new file mode 100644 index 00000000..1ea7c919 --- /dev/null +++ b/_docs/agent/topics/liquid-filters-reference.adoc @@ -0,0 +1,10 @@ +--- +permalink: /docs/agent/liquid-filters-reference/ +indexed: false +--- += Liquid Filters Reference for DocOps Lab Projects +include::../_agent_settings.adoc[] + +Custom AsciiSourcerer/DocOps Lab filters are located under `## Filters from AsciiSourcerer` below. + +include::../../partials/built/liquid-filters-by-source.adoc[] diff --git a/_docs/reference/git-commit-styles.adoc b/_docs/reference/git-commit-styles.adoc index 9c5ef3a0..a65a5f8d 100644 --- a/_docs/reference/git-commit-styles.adoc +++ b/_docs/reference/git-commit-styles.adoc @@ -11,11 +11,11 @@ This document outlines the protocols for authoring Git commit messages in DocOps [[general-style]] -== General Style (Conventional Commits) +== General Style DocOps Lab _loosely_ follows the link:https://www.conventionalcommits.org/en/v1.0.0/[Conventional Commits] specification for Git commit messages. -Enforcement is not strict, but using Conventional Commits style is encouraged for consistency and clarity. +Enforcement is not strict, but using modified Conventional Commits style is encouraged for consistency and clarity. [NOTE] Most DocOps Lab projects do not base Changelog/Release Notes generation on commit messages. @@ -23,7 +23,7 @@ Most DocOps Lab projects do not base Changelog/Release Notes generation on commi The basic outline for a Conventional Commit message is: .... -[optional scope]: +[optional scope]: [optional body] @@ -31,27 +31,31 @@ The basic outline for a Conventional Commit message is: .... -[[commit-description]] -== Commit Description +[[commit-subject]] +== Commit Subject -The commit description should be concise and to the point, summarizing the change in 50 characters or less. +The commit subject is the text after `[optional scope]:`. +It should be concise and to the point, summarizing the change in 50 characters or less. -Use the _past tense_ rather than imperative mood (e.g., "Added feature X" instead of "Add feature X"). +Start the subject with a capitalized imperative verb. +For example, use "`feat: Add widget`" instead of "`feat: add widget`" or "`feat: Added widget`". [[commit-types]] == Commit Types + // tag::commit-types[] -* Use present-tense descriptive verbs ("`adds widget`", not "`added`" or "`add`") -* `feat: ...` for new features OR improvements -* `fix: ...` for bugfixes -* `chore: ...` for version bumps and sundry tasks with no product impact -* `docs: ...` for documentation changes -* `test: ...` for test code changes -* `refactor: ...` for code restructuring with no functional changes -* `style: ...` for formatting, missing semi-colons, etc; no functional changes -* `perf: ...` for performance improvements -* `auto: ...` for changes to CI/CD pipelines and build system +* `create: Add installation guide` for new docs or UI content +* `edit: Clarify setup notes` for minor edits to docs or UI content +* `feat: Add widget` for new features OR improvements +* `fix: Handle missing widget config` for bugfixes +* `chore: Bump gem version` for version bumps and sundry tasks with no product impact +* `test: Cover widget validation` for test code changes +* `refactor: Extract widget parser` for code restructuring with no functional changes +* `style: Format widget examples` for formatting, missing semi-colons, etc; no functional changes +* `perf: Cache widget lookup` for performance improvements +* `auto: Update release workflow` for changes to CI/CD pipelines and build system +* `revert: Restore previous widget behavior` for reverting previous changes // end::commit-types[] @@ -66,4 +70,4 @@ Use the _past tense_ rather than imperative mood (e.g., "Added feature X" instea ** keep sentences short // end::body-conventions[] -// end::commit-styles[] \ No newline at end of file +// end::commit-styles[] diff --git a/_docs/reference/liquid-templating.adoc b/_docs/reference/liquid-templating.adoc new file mode 100644 index 00000000..8cb954e8 --- /dev/null +++ b/_docs/reference/liquid-templating.adoc @@ -0,0 +1,163 @@ +--- +tags: ["reference", "liquid", "templating", "development", "styles", "syntax"] +description: "Liquid 4++ templating methods for DocOps Lab projects." +docs-group: technical +order: 66 +permalink: /docs/liquid-templating/ +--- +include::../_local_settings.adoc[] += Liquid Templating Guide + +// tag::content[] +DocOps Lab's Liquid 4 environment is invoked through a special bootstrapping procedure. +It introduces numerous custom Liquid tags and filters. + +Throughout DocOps Lab applications, Jekyll's extended and modified version of Liquid 4 is standard. + +This means all Jekyll's filters and its special `include` tag are available within and outside Jekyll sites. + + +[[custom-tags]] +== Custom Tags + +Not all custom tags are always available, but the following are generally available in DocOps Lab projects: + +.Table of custom tags, their source gems, and usage notes. +[cols="1,1,4",options="header"] +|=== +| Tag Name | Source Gem | Usage Notes + +| `include` +| jekyll +| Transcludes the content of another file, with optional variable arguments going in but no transcendent affect on the context. + +| `embed` +| asciisourcerer +| Transcludes another file, with full pass-through of variables (no arguments needed) with full write access to the context (can add/change variables permanently). + +|=== + + +[[liquid-filters]] +== Liquid Filters + +A canonical listing of custom filters is available in the AsciiSourcerer gem at link:https://github.com/DocOps/asciisourcerer/blob/main/lib/specs/data/liquid-filters.yml[`lib/specs/data/liquid-filters.yml`]. + +The full compendium includes _all_ filters available in Liquid 5 and Jekyll 4, even though officially Jekyll and AsciiSourcerer standardize on Liquid 4. +Shopify's novel Liquid 5 filters are hard-coded in AsciiSourcerer. + +[IMPORTANT] +All other Shopify-sourced filters are conveyed directly from their Liquid 4.0.4 versions. + +Where official Jekyll filters override their Liquid 4 counterparts, the Jekyll versions are used. +Where an AsciiSourcerer filter overrides either upstream counterpart, the AsciiSourcerer version is used. + +[[custom-filters]] +=== Custom Filters + +ifndef::audience-agent[] +include::../partials/built/liquid-filters-by-source.adoc[tags="asciisourcerer"] +endif::[] +ifdef::audience-agent[] +See `.agent/docs/topics/liquid-filters-reference.md` for a listing of filters available to projects with AsciiSourcerer available. +See the section starting at `// tag::asciisourcerer[]` for custom filters available downstream of AsciiSourcerer. +endif::[] + + +[[liquid-syntax-styles]] +== Liquid Syntax Styles + +DocOps Lab contributions should standardize around certain Liquid syntax conventions, namely around indentation and integration with various markup languages we generate from templates. + +[[html-rendering-style]] +=== HTML Rendering Style + +HTML is by far the most common output target for Liquid templates, but DocOps Lab house style prefers a certain form of indentation. + +Our preference is to indent the Liquid tags in cadence with the surrounding HTML tags, so that the Liquid tags are visually aligned with the HTML they are generating. + +[source,html] +---- +{% for item in items %} +
+

{{ item.title }}

+

+ {{ item.description }} + {% if item.link %} +


+ Read more + {% endif %} +

+
+{% endfor %} +---- + +[[asciidoc-rendering-style]] +=== AsciiDoc Rendering Style + +Because AsciiDoc does not involve much indentation, Liquid syntax meant to render AsciiDoc output is a little awkward. + +Maintain left-flush, un-indented Liquid tags, but indent the tag internals to match the intended AsciiDoc output. + +[source,asciidoc] +---- +{%- for item in items %} +{{ item.title }};; +{{ item.description }} +{%- if item.link %} +Link:;;; {{ item.link }} +{%- endif %} +{% endfor %} +---- + +Use left-side whitespace control (`{%-`) to avoid extra blank lines in the output, but skip it on typical `{% endfor %}` tags to ensure a blank line between iterations where it matters. + +[[yaml-rendering-style]] +=== YAML Rendering Style + +YAML pre-processing with Liquid can be fairly straightforward. +YAML is a great target for Liquid, even though indentation and whitespace control matter more than for HTML or AsciiDoc. + +Intertwine Liquid on its own indentation scale and it should map to the YAML fairly well. + +[source,yaml] +---- +{%- for item in items %} +- title: {{ item.title }} + description: {{ item.description }} + {%- if item.link %} + link: {{ item.link }} + {%- endif %} +{% endfor %} +---- + + +[[invocation]] +== Means of Invocation + +If you are developing a Ruby gem or app in the DocOps Lab ecosystem, the basic Jekyll/Liquid engine is invoked via the `asciisourcerer` gem. + +[source,ruby] +---- +require 'asciisourcerer' + +Sourcerer::Rendering.render_outputs([ + { + template: 'templates/release-notes.liquid', + data: 'data/release.yml', + out: 'build/docs/release-notes.md', + key: 'release', + attrs: 'README.adoc', + engine: 'liquid' + }, + { + converter: 'MyProject::JsonRenderer', + data: 'data/release.yml', + out: 'build/api/release.json' + } +]) +---- + +Direct invocation of Liquid rendering is also possible, but the above method is recommended for most use cases in DocOps Lab projects, as it provides a consistent user experience in terms of availability and behavior of tags and filters. + +// end::content[] \ No newline at end of file diff --git a/_docs/reference/schemagraphy-sgyml.adoc b/_docs/reference/schemagraphy-sgyml.adoc new file mode 100644 index 00000000..44b6b8f3 --- /dev/null +++ b/_docs/reference/schemagraphy-sgyml.adoc @@ -0,0 +1,56 @@ +--- +title: SchemaGraphy and SGYML in DocOps Lab Development +docs-group: technical +description: "Using in-house tools to load YAML/SGYML in DocOps Lab projects." +order: 81 +--- +include::../_local_settings.adoc[] += SchemaGraphy and SGYML in DocOps Lab Development + +The SchemaGraphy gem provides enhanced means of loading and working with YAML and the nascent format SGYML (SchemaGraphy YAML-based modeling language). + +The extension of YAML is limited to this day, but the SchemaGraphy gem provides a convenient way to load and work with these files in Ruby. + + +[[loading-yaml-sgyml]] +== Loading YAML/SGYML + +DocOps Lab projects often require loading YAML or SGYML data files for configuration, content, or other purposes. +The `schemagraphy` gem provides a convenient way to load and work with these files in Ruby. + +// tag::loading-yaml[] +There are two types of basic YAML loaders in SchemaGraphy, differing around whether you need to resolve AsciiDoc attributes in the content. + +`SchemaGraphy::Loader.load_yaml_with_tags`:: +This method loads a YAML file while preserving any custom tags (e.g., `!sometag`, `!somenamespace:anothertag`). +Custom tags are attached to the data structure, allowing for later processing based on those tags. ++ +The resulting data structure will have the original string value along with a `__tag__` key that contains the normalized tag name (without `!` or namespace). + +`SchemaGraphy::Loader.load_yaml_with_attributes`:: +This method loads a YAML file and resolves AsciiDoc attribute references like `\{attribute_name}`. +It first loads the YAML with tags and then resolves any attribute references using the provided attributes. +// end::loading-yaml[] + + +[[yaml-tags]] +== De-tagging and Normalization + +When using either loading method, the resulting data structure may include both the original value and a `__tag__` key for any tagged values. +When you want to work with the original value without the tag metadata, you can use the `SchemaGraphy::TagUtils.detag` method to extract just the value. + +This is useful when you want to ignore the tags and work with the raw data. + +To ensure you are working with the original value without the tag metadata, you can use the `SchemaGraphy::TagUtils.detag` method: + +// tag::detagging-example[] +[source,ruby] +---- +require 'schemagraphy' + +yaml_data = SchemaGraphy::Loader.load_yaml_with_tags('path/to/file.yaml') +original_value = SchemaGraphy::TagUtils.detag(yaml_data['some_key']) +puts original_value +---- + +// end::detagging-example[] \ No newline at end of file diff --git a/_docs/task/development.adoc b/_docs/task/development.adoc index 498a9ee9..87c8b42b 100644 --- a/_docs/task/development.adoc +++ b/_docs/task/development.adoc @@ -35,7 +35,7 @@ git checkout main git pull origin main git checkout -b dev/1.2 git checkout -b chore/bump-version-1.2.0 -git commit -am "Bumped version attributes in README" +git commit -am "chore: Bump version attributes" git checkout dev/1.2 git merge chore/bump-version-1.2.0 git push -u origin dev/1.2 @@ -54,9 +54,9 @@ git checkout dev/1.2 git checkout -b feat/add-widget … implement … git add . -git commit -m "feat: add widget" +git commit -m "feat: Add widget" git push -u origin feat/add-widget -gh pr create --base dev/1.2 --title "feat: add widget" --body "Adds a new widget to the dashboard." +gh pr create --base dev/1.2 --title "feat: Add widget" --body "Adds a new widget to the dashboard." .... Branch naming conventions:: @@ -69,7 +69,7 @@ Branch naming conventions:: [[commit-message-conventions]] === Commit Message Conventions -Description (first line) conventions:: +Subject conventions:: include::../reference/git-commit-styles.adoc[tag=commit-types] @@ -91,9 +91,9 @@ git checkout dev/1.2 git checkout -b feat/add-widget … implement … git add . -git commit -m "feat: add widget" +git commit -m "feat: Add widget" git merge --squash feat/add-widget -git commit -m "feat: add widget" +git commit -m "feat: Add widget" git push origin dev/1.2 .... @@ -378,4 +378,4 @@ rake pr_test # Often includes verbose options include::../policy/generative-ai-usage.adoc[tags=tldr] -For the complete policy, see {xref_docs_generative-ai-usage_link}. \ No newline at end of file +For the complete policy, see {xref_docs_generative-ai-usage_link}. diff --git a/_docs/task/lab-dev-setup.adoc b/_docs/task/lab-dev-setup.adoc index 93c69e9c..e40ef73e 100644 --- a/_docs/task/lab-dev-setup.adoc +++ b/_docs/task/lab-dev-setup.adoc @@ -10,7 +10,7 @@ include::../_local_settings.adoc[] include::../partials/_docopslab-dev-context-notice.adoc[] -include::../../gems/docopslab-dev/README.adoc[tags="global-settings,setup",leveloffset="-1""] +include::../../gems/docopslab-dev/README.adoc[tags="global-settings,setup",leveloffset="-1"] [NOTE] For configuration details, see {xref_docs_lab-dev-config_link}. diff --git a/_docs/task/release.adoc b/_docs/task/release.adoc index 818ae823..6b97aeb3 100644 --- a/_docs/task/release.adoc +++ b/_docs/task/release.adoc @@ -187,12 +187,12 @@ git checkout release/1.1 git checkout -b fix/parser-typo # … FIX … git add . -git commit -m "fix: correct parser typo" +git commit -m "fix: Correct parser typo" git push origin fix/parser-typo # … TEST … git checkout release/1.1 git merge --squash fix/parser-typo -git commit -m "fix: correct parser typo" +git commit -m "fix: Correct parser typo" git push origin release/1.1 .... @@ -224,4 +224,4 @@ Perform Steps 1 through 5 of the standard release procedure: . <> . <> -// end::rollback-patching[] \ No newline at end of file +// end::rollback-patching[] diff --git a/gems/docopslab-dev/Gemfile.lock b/gems/docopslab-dev/Gemfile.lock index f57d573c..8634e20b 100644 --- a/gems/docopslab-dev/Gemfile.lock +++ b/gems/docopslab-dev/Gemfile.lock @@ -1,14 +1,15 @@ PATH remote: . specs: - docopslab-dev (0.3.1) + docopslab-dev (0.4.0) asciidoctor (~> 2.0) - asciisourcerer (~> 0.4) + asciisourcerer (~> 0.5) brakeman (~> 7.1) bundler-audit (~> 0.9) debride (~> 1.13) fasterer (~> 0.11) flog (~> 4.8) + git-lint (~> 6.2) html-proofer (~> 5.2) inch (~> 0.8) rake (~> 13.0) @@ -31,7 +32,7 @@ GEM asciidoctor-html5s (0.5.1) asciidoctor (>= 1.5.7, < 3.0) thread_safe (~> 0.3.4) - asciisourcerer (0.4.0) + asciisourcerer (0.5.0) asciidoctor (~> 2.0) asciidoctor-html5s (~> 0.5) jekyll (~> 4.4) @@ -39,6 +40,7 @@ GEM kramdown-asciidoc (~> 2.1) liquid (~> 4.0) reverse_markdown (~> 2.1) + yard (~> 0.9) ast (2.4.3) async (2.32.1) console (~> 1.29) @@ -55,12 +57,18 @@ GEM bundler (>= 1.2.0, < 3) thor (~> 1.0) coderay (1.1.3) + cogger (0.14.0) + core (~> 0.1) + refinements (~> 11.0) + tone (~> 0.3) + zeitwerk (~> 2.6) colorator (1.1.0) concurrent-ruby (1.3.6) console (1.34.0) fiber-annotation fiber-local (~> 1.1) json + core (0.1.6) csv (3.3.5) debride (1.13.0) path_expander (~> 1.0) @@ -71,6 +79,8 @@ GEM dry-configurable (1.3.0) dry-core (~> 1.1) zeitwerk (~> 2.6) + dry-container (0.11.0) + concurrent-ruby (~> 1.0) dry-core (1.1.0) concurrent-ruby (~> 1.0) logger @@ -82,6 +92,10 @@ GEM concurrent-ruby (~> 1.0) dry-core (~> 1.1) zeitwerk (~> 2.6) + dry-monads (1.10.0) + concurrent-ruby (~> 1.0) + dry-core (~> 1.1) + zeitwerk (~> 2.6) dry-schema (1.14.1) concurrent-ruby (~> 1.0) dry-configurable (~> 1.0, >= 1.0.1) @@ -100,6 +114,14 @@ GEM em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) + etcher (0.5.1) + cogger (~> 0.12) + core (~> 0.1) + dry-monads (~> 1.6) + dry-types (~> 1.7) + refinements (~> 11.0) + versionaire (~> 12.1) + zeitwerk (~> 2.6) ethon (0.15.0) ffi (>= 1.15.0) eventmachine (1.2.7) @@ -115,6 +137,26 @@ GEM ruby_parser (~> 3.1, > 3.1.0) sexp_processor (~> 4.8) forwardable-extended (2.6.0) + git-lint (6.2.1) + cogger (~> 0.12) + core (~> 0.1) + dry-container (~> 0.11) + dry-monads (~> 1.6) + dry-schema (~> 1.13) + etcher (~> 0.2) + gitt (~> 2.0) + infusible (~> 2.2) + refinements (~> 11.0) + runcom (~> 10.0) + sod (~> 0.0) + spek (~> 2.0) + tone (~> 0.3) + zeitwerk (~> 2.6) + gitt (2.2.0) + core (~> 0.1) + dry-monads (~> 1.6) + refinements (~> 11.0) + zeitwerk (~> 2.6) google-protobuf (4.34.1-x86_64-linux-gnu) bigdecimal rake (~> 13.3) @@ -137,6 +179,9 @@ GEM sparkr (>= 0.2.0) term-ansicolor yard (~> 0.9.12) + infusible (2.2.1) + marameters (~> 2.0) + zeitwerk (~> 2.6) io-event (1.14.0) jekyll (4.4.1) addressable (~> 2.4) @@ -180,6 +225,9 @@ GEM rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) logger (1.7.0) + marameters (2.1.1) + refinements (~> 11.0) + zeitwerk (~> 2.6) mercenary (0.4.0) method_source (1.1.0) metrics (0.15.0) @@ -216,6 +264,7 @@ GEM parser (~> 3.3.0) rainbow (>= 2.0, < 4.0) rexml (~> 3.1) + refinements (11.1.3) regexp_parser (2.11.3) reverse_markdown (2.1.1) nokogiri @@ -259,6 +308,10 @@ GEM ruby_parser (3.21.1) racc (~> 1.5) sexp_processor (~> 4.16) + runcom (10.2.1) + refinements (~> 11.0) + xdg (~> 7.1) + zeitwerk (~> 2.6) safe_yaml (1.0.5) sass-embedded (1.99.0-x86_64-linux-gnu) google-protobuf (~> 4.31) @@ -269,7 +322,19 @@ GEM simplecov_json_formatter (~> 0.1) simplecov-html (0.13.2) simplecov_json_formatter (0.1.4) + sod (0.3.1) + cogger (~> 0.12) + dry-container (~> 0.11) + infusible (~> 2.2) + refinements (~> 11.0) + tone (~> 0.3) + zeitwerk (~> 2.6) sparkr (0.4.1) + spek (2.1.1) + dry-monads (~> 1.6) + refinements (~> 11.0) + versionaire (~> 12.0) + zeitwerk (~> 2.6) subtxt (0.3.0) sync (0.5.0) term-ansicolor (1.11.3) @@ -282,13 +347,19 @@ GEM bigdecimal mize (~> 0.6) sync + tone (0.4.2) + refinements (~> 11.0) + zeitwerk (~> 2.6) traces (0.18.2) ttfunk (1.8.0) bigdecimal (~> 3.1) typhoeus (1.5.0) ethon (>= 0.9.0, < 0.16.0) unicode-display_width (2.6.0) + versionaire (12.1.1) + refinements (~> 11.0) webrick (1.9.2) + xdg (7.1.3) yaml (0.4.0) yard (0.9.37) yell (2.2.2) diff --git a/gems/docopslab-dev/README.adoc b/gems/docopslab-dev/README.adoc index b554cda0..ef36914c 100644 --- a/gems/docopslab-dev/README.adoc +++ b/gems/docopslab-dev/README.adoc @@ -525,6 +525,34 @@ For full Vale configuration settings ("`keys`") reference, see the link:https:// // end::config-vale[] +[[git-lint]] +=== git-lint + +Git commit message checking for local hooks. + +Convention data:: `.config/.vendor/docopslab/commit-conventions.yml` +Project convention overrides:: `.config/commit-conventions.yml` (inherits via `inherit_from`) +Base config:: `.config/.vendor/docopslab/git-lint.yml` +Project config overrides:: `.config/git-lint.local.yml` +Ephemeral config:: `.config/git-lint.yml` +Sync command:: `bundle exec rake labdev:sync:configs` +Branch lint command:: `bundle exec rake labdev:lint:commits` +Commit-message hook command:: `bundle exec rake labdev:lint:commit[path/to/COMMIT_EDITMSG]` + +The generated git-lint config is derived from shared commit convention data so other commit-message linters can reuse the same types, scopes, and prose rules later. +Commit subjects must follow `[optional scope]: Subject`, use a configured type and scope, start the subject with a capitalized imperative verb, and omit terminal punctuation. + +Commit convention overrides are mapping-based. +Local projects can add or overwrite keys under `rules`, `conventions.types`, and `conventions.scopes`. +Set a key to `null` in the local file to drop an inherited convention. + +Version 6.x of `git-lint` expects an XDG config path named `git-lint/configuration.yml`. +The `labdev` wrappers bridge `.config/git-lint.yml` into that path at runtime, so use the rake tasks instead of invoking `git-lint` directly. + +The distributed `commit-msg` hook runs `labdev:lint:commit` against the unsaved commit message. +This is a local, commit-time check by design, not a CI gate: fixing a violation caught after a push means rewriting shared history, which the local hook avoids entirely. +`labdev:lint:commits` remains available for manually linting a branch or arbitrary commit range. + [[htmlproofer]] === HTMLProofer // tag::config-htmlproofer[] @@ -1024,6 +1052,11 @@ HTMLProofer:: + bundle exec htmlproofer --ignore-urls "/www.github.com/,/foo.com/" ./_site +git-lint:: ++ + bundle exec rake "labdev:lint:commit[.git/COMMIT_EDITMSG]" + bundle exec rake labdev:lint:commits + // end::standard-usage[] // end::usage[] // end::workflow[] @@ -1266,4 +1299,4 @@ Documentation released under Creative Commons Attribution 4.0 International (CC === Bill of Materials No externally sourced content or code is contained in this project. -All third-party dependencies are permissively licensed and are downloaded independently, never provided by DocOps Lab. \ No newline at end of file +All third-party dependencies are permissively licensed and are downloaded independently, never provided by DocOps Lab. diff --git a/gems/docopslab-dev/assets/config-packs/commit-conventions/base.yml b/gems/docopslab-dev/assets/config-packs/commit-conventions/base.yml new file mode 100644 index 00000000..eeb047e2 --- /dev/null +++ b/gems/docopslab-dev/assets/config-packs/commit-conventions/base.yml @@ -0,0 +1,79 @@ +conventions: + types: + edit: + summ: Minor edits to content, documentation, or UI text + create: + summ: New content items, documentation, or UI surfaces + chore: + summ: Routine tasks and maintenance + feat: + summ: New downstream-facing features or functionality + fix: + summ: Bug fixes and error corrections + perf: + summ: Performance improvements + refactor: + summ: Code restructuring without changing behavior + revert: + summ: Reverting previous changes + style: + summ: Code or documentation style and formatting changes + test: + summ: Test code changes + + scopes: + auto: + name: Automation + summ: Changes to automation and CI/CD scripts, workflows, and pipelines + akas: [auto, ci, cd, ci-cd, cicd, pipeline, devops, ops, scripts] + cli: + name: CLI + summ: Changes to a command-line interface + config: + name: Config + summ: Changes to configuration files and settings + content: + name: Content + summ: Changes to content + core: + name: Core + summ: Changes to native API, engine logic, or core behavior + data: + name: Data + summ: Changes to data structures, schemas, and formats + docs: + name: Documentation + summ: Changes to user-facing or internal documentation, including README and inline docs + akas: [doc, documentation] + infra: + name: Infrastructure + summ: Changes to infrastructure, deployment, and CI/CD pipelines + akas: [infrastructure] + lib: + name: Libraries + summ: Changes to libraries, modules, and dependencies + akas: [library, libraries, module, dependency, dependencies, package, packages] + server: + name: Server + summ: Changes to server-side configuration or remote services + specs: + name: Specifications + summ: Changes to specifications, design docs, requirements, and planning artifacts + akas: [spec, specification, definition, design, plan, requirement, requirements] + tests: + name: Tests + summ: Changes to test cases, test suites, and testing frameworks + akas: [test, testing] + +rules: + subject: + maximum: 72 + require_capitalized_imperative: true + reject_terminal_punctuation: true + allowed_scope_separator: "+" + body: + maximum_line_length: 90 + require_one_sentence_per_line: true + text: + reject_em_dash: true + reject_agent_signature: true diff --git a/gems/docopslab-dev/assets/config-packs/commit-conventions/project.yml b/gems/docopslab-dev/assets/config-packs/commit-conventions/project.yml new file mode 100644 index 00000000..050f4d08 --- /dev/null +++ b/gems/docopslab-dev/assets/config-packs/commit-conventions/project.yml @@ -0,0 +1,6 @@ +# Project-local commit convention overrides. +# +# This file is copied once to `.config/commit-conventions.yml`. +# Local keys merge over inherited DocOps Lab defaults. +--- +inherit_from: .vendor/docopslab/commit-conventions.yml diff --git a/gems/docopslab-dev/assets/config-packs/git-lint/base.yml b/gems/docopslab-dev/assets/config-packs/git-lint/base.yml new file mode 100644 index 00000000..9f6ebbae --- /dev/null +++ b/gems/docopslab-dev/assets/config-packs/git-lint/base.yml @@ -0,0 +1,39 @@ +commits: + body: + line_length: + enabled: true + severity: error + maximum: 90 + presence: + enabled: false + severity: warn + minimum: 1 + phrase: + enabled: true + severity: error + excludes: + - "—" + - "Co-authored by Codex" + - "Generated with Claude Code" + signature: + enabled: false + severity: error + includes: + - "" + subject: + length: + enabled: true + severity: error + maximum: 72 + prefix: + enabled: true + severity: error + delimiter: "[[:upper:]]" + includes: [] + suffix: + enabled: true + severity: error + excludes: + - "\\." + - "\\?" + - "\\!" diff --git a/gems/docopslab-dev/assets/config-packs/git-lint/project.yml b/gems/docopslab-dev/assets/config-packs/git-lint/project.yml new file mode 100644 index 00000000..7e1c7558 --- /dev/null +++ b/gems/docopslab-dev/assets/config-packs/git-lint/project.yml @@ -0,0 +1,6 @@ +# Project-local git-lint overrides. +# +# This file is copied once to `.config/git-lint.local.yml`. +# Leave it empty to use DocOps Lab defaults, or override keys from +# `.config/.vendor/docopslab/git-lint.yml`. +--- diff --git a/gems/docopslab-dev/assets/hooks/commit-msg b/gems/docopslab-dev/assets/hooks/commit-msg new file mode 100644 index 00000000..79a78662 --- /dev/null +++ b/gems/docopslab-dev/assets/hooks/commit-msg @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# DocOps Lab commit-msg hook template +# Managed by docopslab-dev gem + +set -e + +MESSAGE_FILE="${1:-}" + +if [[ -z "$MESSAGE_FILE" ]]; then + echo "❌ Missing commit message file path." + exit 1 +fi + +if ! command -v bundle > /dev/null 2>&1; then + echo "⚠️ Bundler not available; skipping commit message lint." + exit 0 +fi + +echo "🪝 Linting commit message..." +bundle exec rake "labdev:lint:commit[$MESSAGE_FILE]" diff --git a/gems/docopslab-dev/assets/templates/CLAUDE.md b/gems/docopslab-dev/assets/templates/CLAUDE.md new file mode 100644 index 00000000..d3dd8171 --- /dev/null +++ b/gems/docopslab-dev/assets/templates/CLAUDE.md @@ -0,0 +1 @@ +Read, respect, and defer to the local `AGENTS.md` file. \ No newline at end of file diff --git a/gems/docopslab-dev/assets/templates/gitignore b/gems/docopslab-dev/assets/templates/gitignore index b7fb617b..1fe4d1c8 100644 --- a/gems/docopslab-dev/assets/templates/gitignore +++ b/gems/docopslab-dev/assets/templates/gitignore @@ -43,6 +43,7 @@ tmp/ # Generated config files (merged from base + local) .config/vale.ini .config/htmlproofer.yml +.config/git-lint.yml # end::universal-docopslab-dev-configs[] # tag::universal-docopslab-agent[] .agent/* diff --git a/gems/docopslab-dev/docopslab-dev.gemspec b/gems/docopslab-dev/docopslab-dev.gemspec index e02d6e9e..dcd45639 100644 --- a/gems/docopslab-dev/docopslab-dev.gemspec +++ b/gems/docopslab-dev/docopslab-dev.gemspec @@ -32,10 +32,11 @@ Gem::Specification.new do |spec| spec.add_dependency 'yaml', '~> 0.2' # Code quality and linting - spec.add_dependency 'asciisourcerer', '~> 0.4' + spec.add_dependency 'asciisourcerer', '~> 0.5' spec.add_dependency 'debride', '~> 1.13' spec.add_dependency 'fasterer', '~> 0.11' spec.add_dependency 'flog', '~> 4.8' + spec.add_dependency 'git-lint', '~> 6.2' spec.add_dependency 'reek', '~> 6.5' spec.add_dependency 'rubocop', '~> 1.80' spec.add_dependency 'rubocop-rake', '~> 0.7' diff --git a/gems/docopslab-dev/lib/docopslab/dev.rb b/gems/docopslab-dev/lib/docopslab/dev.rb index 8865c4d5..13812ac7 100644 --- a/gems/docopslab-dev/lib/docopslab/dev.rb +++ b/gems/docopslab-dev/lib/docopslab/dev.rb @@ -313,6 +313,14 @@ def generate_htmlproofer_config ConfigManager.generate_htmlproofer_config(self) end + def generate_git_lint_config + ConfigManager.generate_git_lint_config(self) + end + + def load_commit_conventions + ConfigManager.load_commit_conventions + end + def load_htmlproofer_config ConfigManager.load_htmlproofer_config end @@ -335,13 +343,21 @@ def run_actionlint opts_string='' Linters.run_actionlint(self, opts_string) end + def run_git_lint target=nil, opts_string='' + Linters.run_git_lint(self, target, opts_string) + end + + def run_git_lint_hook message_file + Linters.run_git_lint_hook(self, message_file) + end + def run_all_linters Linters.run_all_linters(self) end def run_auto_fix - Linters.run_auto_fix - AsciiidocAutoFix.fix_asciidoc_files(self) + Linters.run_auto_fix(self) + AutoFixAsciidoc.fix_asciidoc_files(self) end def run_rubocop_auto_fix path: nil diff --git a/gems/docopslab-dev/lib/docopslab/dev/config_manager.rb b/gems/docopslab-dev/lib/docopslab/dev/config_manager.rb index e4847f7e..96eeb1a6 100644 --- a/gems/docopslab-dev/lib/docopslab/dev/config_manager.rb +++ b/gems/docopslab-dev/lib/docopslab/dev/config_manager.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +require 'fileutils' +require 'yaml' + module DocOpsLab module Dev module ConfigManager @@ -55,6 +58,51 @@ def generate_htmlproofer_config _context end end + def generate_git_lint_config _context + default_config = load_git_lint_defaults + return false unless default_config + + base_config = File.join(Paths.config_vendor_dir, 'git-lint.yml') + project_config = '.config/git-lint.local.yml' + generated_config = Paths::CONFIG_FILES[:git_lint] + + merged_content = default_config + if File.exist?(base_config) + merged_content = deep_merge_configs(merged_content, YAML.load_file(base_config) || {}) + end + if File.exist?(project_config) + merged_content = deep_merge_configs(merged_content, YAML.load_file(project_config) || {}) + end + + conventions = load_commit_conventions + merged_content = apply_commit_conventions_to_git_lint(merged_content, conventions) if conventions + + rendered = YAML.dump(merged_content) + FileUtils.mkdir_p(File.dirname(generated_config)) + + if !File.exist?(generated_config) || File.read(generated_config) != rendered + File.write(generated_config, rendered) + puts " 📝 Generated #{generated_config} from git-lint defaults + DocOps Lab conventions" + true + else + false + end + end + + def load_commit_conventions + base_path = File.join(Paths.config_vendor_dir, 'commit-conventions.yml') + local_path = '.config/commit-conventions.yml' + + if File.exist?(local_path) + load_inheritable_yaml_config(local_path) + elsif File.exist?(base_path) + YAML.load_file(base_path) || {} + end + rescue StandardError => e + warn "⚠️ Failed to load commit conventions: #{e.message}" + nil + end + def load_htmlproofer_config config_path=nil, policy: 'merge' config_paths = if config_path && File.exist?(config_path) [config_path] @@ -99,6 +147,67 @@ def process_htmlproofer_patterns config config.transform_keys(&:to_sym) end + def load_git_lint_defaults + spec = Gem.loaded_specs['git-lint'] || Gem::Specification.find_all_by_name('git-lint').first + unless spec + warn "⚠️ git-lint is not installed. Run 'bundle install'." + return nil + end + + YAML.load_file(File.join(spec.full_gem_path, 'lib/git/lint/configuration/defaults.yml')) || {} + rescue StandardError => e + warn "⚠️ Failed to load git-lint defaults: #{e.message}" + nil + end + + def apply_commit_conventions_to_git_lint config, conventions + types = convention_slugs(conventions, 'types') + scopes = convention_slugs(conventions, 'scopes') + separator = conventions.dig('rules', 'subject', 'allowed_scope_separator') || '+' + max_length = conventions.dig('rules', 'subject', 'maximum') + body_max = conventions.dig('rules', 'body', 'maximum_line_length') + + subject = config.dig('commits', 'subject') + if subject && types.any? + scope_pattern = git_lint_scope_pattern(scopes, separator) + subject['prefix']['includes'] = types.sort.map { |type| "#{Regexp.escape(type)}#{scope_pattern}: " } + end + + subject['length']['maximum'] = max_length if subject && max_length + body = config.dig('commits', 'body') + body['line_length']['maximum'] = body_max if body&.dig('line_length') && body_max + + config + end + + def git_lint_scope_pattern scopes, separator + return '(?:\\([a-z0-9_-]+\\))?' if scopes.empty? + + escaped_scopes = scopes.sort.map { |scope| Regexp.escape(scope) } + escaped_separator = Regexp.escape(separator) + "(?:\\((?:#{escaped_scopes.join('|')})(?:#{escaped_separator}(?:#{escaped_scopes.join('|')}))*\\))?" + end + + def convention_slugs conventions, key + values = conventions.dig('conventions', key) + case values + when Hash + values.keys + else + Array(values).filter_map { |entry| entry['slug'] if entry.is_a?(Hash) } + end + end + + def load_inheritable_yaml_config path + local = YAML.load_file(path) || {} + inherit_from = local.delete('inherit_from') + return local unless inherit_from + + base_path = File.expand_path(inherit_from, File.dirname(path)) + base = File.exist?(base_path) ? YAML.load_file(base_path) || {} : {} + deep_merge_configs(base, local) + end + def merge_yaml_configs base_path, local_path # Implement RuboCop-style inheritance for YAML files require 'yaml' diff --git a/gems/docopslab-dev/lib/docopslab/dev/git_hooks.rb b/gems/docopslab-dev/lib/docopslab/dev/git_hooks.rb index 42f7df99..8d7ae7ab 100644 --- a/gems/docopslab-dev/lib/docopslab/dev/git_hooks.rb +++ b/gems/docopslab-dev/lib/docopslab/dev/git_hooks.rb @@ -119,6 +119,8 @@ def list_hook_templates description = case hook_name when 'pre-commit' 'Advisory checks & syntax validation (non-blocking)' + when 'commit-msg' + 'Commit message style gate (blocking)' when 'pre-push' 'Comprehensive linting & quality gate (blocking)' else diff --git a/gems/docopslab-dev/lib/docopslab/dev/linters.rb b/gems/docopslab-dev/lib/docopslab/dev/linters.rb index 28f3cdf5..beabd7b1 100644 --- a/gems/docopslab-dev/lib/docopslab/dev/linters.rb +++ b/gems/docopslab-dev/lib/docopslab/dev/linters.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true require 'open3' +require 'fileutils' require 'pathname' require 'sourcerer/util/pathifier' +require 'tmpdir' module DocOpsLab module Dev @@ -185,6 +187,162 @@ def run_actionlint context, opts_string='' success end + def run_git_lint context, target=nil, opts_string='' + puts '🧾 Running git-lint...' + context.generate_git_lint_config if context.respond_to?(:generate_git_lint_config) + + unless File.exist?(Paths::CONFIG_FILES[:git_lint]) + puts "❌ No git-lint config found. Run 'labdev:sync:configs' to create one." + return false + end + + cmd = %w[bundle exec git-lint analyze] + if target + commits = target.split(',').reject { |sha| merge_commit?(sha) } + if commits.empty? + puts 'ℹ️ All targeted commits are merge commits (exempt from subject conventions); nothing to check.' + return true + end + cmd += ['--commit', commits.join(',')] + else + cmd << '--branch' + end + cmd += opts_string.split unless opts_string.empty? + + success = with_git_lint_config_env do |env| + system(env, *cmd) + end + + if success + puts '✅ git-lint passed' + else + puts '❌ git-lint found commit message issues' + end + + success + end + + def merge_commit? sha + out, status = Open3.capture2('git', 'rev-list', '--no-walk', '--parents', sha) + return false unless status.success? + + # First token is the commit itself; anything beyond one remaining token is a second parent. + out.strip.split.size > 2 + end + + def run_git_lint_hook context, message_file + puts "🧾 Checking commit message: #{message_file}" + context.generate_git_lint_config if context.respond_to?(:generate_git_lint_config) + + unless File.exist?(message_file) + puts "❌ Commit message file not found: #{message_file}" + return false + end + + convention_success = validate_commit_message_subject(context, message_file) + git_lint_success = with_git_lint_config_env do |env| + system(env, 'bundle', 'exec', 'git-lint', '--hook', message_file) + end + + if convention_success && git_lint_success + puts '✅ Commit message passed' + true + else + puts '❌ Commit message failed' + false + end + end + + def validate_commit_message_subject context, message_file + subject = File.readlines(message_file, chomp: true).find { |line| !line.strip.empty? } + return true if subject.nil? || subject.match?(/\A(?:fixup|squash|amend)!\s/) + + conventions = context.load_commit_conventions if context.respond_to?(:load_commit_conventions) + return true unless conventions + + types = convention_slugs(conventions, 'types').sort + scopes = convention_slugs(conventions, 'scopes').sort + separator = conventions.dig('rules', 'subject', 'allowed_scope_separator') || '+' + reject_punctuation = conventions.dig('rules', 'subject', 'reject_terminal_punctuation') + reject_agent_signature = conventions.dig('rules', 'text', 'reject_agent_signature') + + match = subject.match(/\A(?[a-z]+)(?:\((?[^)]+)\))?: (?.+)\z/) + unless match + puts '❌ Commit subject must match `[optional scope]: Subject`.' + return false + end + + valid = true + valid = false unless validate_commit_type(match[:type], types) + valid = false unless validate_commit_scopes(match[:scope], scopes, separator) + valid = false unless validate_subject_text(match[:subject], reject_punctuation) + valid = false if reject_agent_signature && agent_signature?(File.read(message_file)) + valid + end + + def with_git_lint_config_env + Dir.mktmpdir('docopslab-dev-git-lint') do |dir| + config_dir = File.join(dir, 'git-lint') + FileUtils.mkdir_p(config_dir) + FileUtils.cp(Paths::CONFIG_FILES[:git_lint], File.join(config_dir, 'configuration.yml')) + yield({ 'XDG_CONFIG_HOME' => dir }) + end + end + + def convention_slugs conventions, key + values = conventions.dig('conventions', key) + case values + when Hash + values.keys + else + Array(values).filter_map { |entry| entry['slug'] if entry.is_a?(Hash) } + end + end + + def validate_commit_type type, types + return true if types.include?(type) + + puts "❌ Commit type `#{type}` is not allowed. Use one of: #{types.join(', ')}." + false + end + + def validate_commit_scopes scope_text, scopes, separator + return true if scope_text.nil? || scopes.empty? + + invalid = scope_text.split(separator).reject { |scope| scopes.include?(scope) } + return true if invalid.empty? + + puts "❌ Commit scope(s) not allowed: #{invalid.join(', ')}." + puts " Use configured scopes joined with `#{separator}` when multiple scopes are needed." + false + end + + def validate_subject_text subject_text, reject_punctuation + valid = true + unless subject_text.match?(/\A[[:upper:]]/) + puts '❌ Commit subject must start with a capitalized imperative verb.' + valid = false + end + + if reject_punctuation && subject_text.match?(/[.?!]\z/) + puts '❌ Commit subject must not end with terminal punctuation.' + valid = false + end + + valid + end + + def agent_signature? message + patterns = [ + /Co-authored-by:\s*(?:Codex|Claude|ChatGPT)/i, + /Generated with (?:Claude Code|Codex|ChatGPT)/i + ] + return false unless patterns.any? { |pattern| message.match?(pattern) } + + puts '❌ Commit message must not include agent authorship signatures.' + true + end + def run_vale context, file_path=nil, opts_string='', output_format: :cli, filter: nil, style_override: nil scope = file_path ? :file : :project running_on = file_path ? "file: #{file_path}" : scope.to_s diff --git a/gems/docopslab-dev/lib/docopslab/dev/paths.rb b/gems/docopslab-dev/lib/docopslab/dev/paths.rb index e751f5c0..6cce3e75 100644 --- a/gems/docopslab-dev/lib/docopslab/dev/paths.rb +++ b/gems/docopslab-dev/lib/docopslab/dev/paths.rb @@ -18,6 +18,7 @@ def self.config_vendor_dir CONFIG_FILES = { vale: '.config/vale.ini', htmlproofer: '.config/htmlproofer.yml', + git_lint: '.config/git-lint.yml', rubocop: '.config/rubocop.yml' }.freeze diff --git a/gems/docopslab-dev/lib/docopslab/dev/skim.rb b/gems/docopslab-dev/lib/docopslab/dev/skim.rb index 4f29a14c..ce5ab9f5 100644 --- a/gems/docopslab-dev/lib/docopslab/dev/skim.rb +++ b/gems/docopslab-dev/lib/docopslab/dev/skim.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'json' +require 'pathname' require 'asciisourcerer' require 'sourcerer/util/pathifier' @@ -28,17 +29,24 @@ def run_md path, form: nil, syntax: nil run_with_format(path, exts: MD_EXTS, form: form, syntax: syntax, overlay: true) end + # Skim a Ruby application for its API surface (Modules, Classes, Methods, etc.). Form and syntax are auto-detected. + def run_ruby path, form: nil, syntax: nil + # path is path + lib/ + path = File.join(path, 'lib') + run_with_format(path, exts: ['.rb'], form: form, syntax: syntax) + end + private - def run_with_format path, exts:, form: nil, syntax: nil, default_forms: nil, overlay: false + def run_with_format path, exts:, form: nil, syntax: nil, **opts unless path puts '❌ Path is required.' - puts 'Usage: bundle exec rake labdev:skim[path,form,syntax]' + puts 'Usage: bundle exec rake labdev:skim:[path,form,syntax]' return end - forms = form ? parse_forms(form) : default_forms - file_paths = overlay ? resolve_overlay_paths(path, exts) : resolve_paths(path, exts) + forms = form ? parse_forms(form) : opts[:default_forms] + file_paths = opts[:overlay] ? resolve_overlay_paths(path, exts) : resolve_paths(path, exts) if file_paths.empty? ext_desc = exts.size == 1 ? exts.first : exts.join(', ') @@ -51,7 +59,9 @@ def run_with_format path, exts:, form: nil, syntax: nil, default_forms: nil, ove file_paths.each do |fp| skim_opts = { categories: cats } skim_opts[:forms] = forms if forms - results[fp] = Sourcerer::SourceSkim.skim_file(fp, **skim_opts) + skim_opts[:descriptions] = true if opts[:descriptions] + + results[relative_path(fp)] = Sourcerer::SourceSkim.skim_file(fp, **skim_opts) end portable = JSON.parse(JSON.generate(results)) @@ -85,6 +95,11 @@ def build_relative_map dir_path, exts end end + # Render an absolute file path as relative to the current working directory. + def relative_path fp + Pathname.new(fp).relative_path_from(Pathname.pwd).to_s + end + def parse_forms form form.split(',').map { |f| f.strip.to_sym } end @@ -98,7 +113,7 @@ def resolve_syntax syntax, form return :yaml unless form syntax = 'yaml' if syntax == 'yml' - puts + return syntax.to_sym if syntax :json diff --git a/gems/docopslab-dev/lib/docopslab/dev/sync_ops.rb b/gems/docopslab-dev/lib/docopslab/dev/sync_ops.rb index a31d0151..3f0a9e69 100644 --- a/gems/docopslab-dev/lib/docopslab/dev/sync_ops.rb +++ b/gems/docopslab-dev/lib/docopslab/dev/sync_ops.rb @@ -281,6 +281,15 @@ def sync_config_files context, tool_filter: :all, offline: false return false end + unless offline + begin + Library.ensure_available! + rescue StandardError => e + puts "❌ #{e.message}" + return false + end + end + config_packs_root = Library.resolve('config-packs') unless config_packs_root && Dir.exist?(config_packs_root) puts '❌ config-packs not found in library; run `labdev:sync:library` to fetch.' @@ -383,6 +392,7 @@ def sync_config_files context, tool_filter: :all, offline: false generated_count = 0 generated_count += 1 if context.generate_vale_config generated_count += 1 if context.generate_htmlproofer_config + generated_count += 1 if context.generate_git_lint_config puts ' ✅ All runtime configs up to date' if generated_count.zero? diff --git a/gems/docopslab-dev/lib/docopslab/dev/tasks.rb b/gems/docopslab-dev/lib/docopslab/dev/tasks.rb index 0f3c8f72..9c30b334 100644 --- a/gems/docopslab-dev/lib/docopslab/dev/tasks.rb +++ b/gems/docopslab-dev/lib/docopslab/dev/tasks.rb @@ -146,6 +146,12 @@ def define_tasks opts = args[:opts] || '' Dev.run_actionlint(opts) end + + desc desc_for('run:git_lint') + task :git_lint, [:opts] => [] do |_t, args| + opts = args[:opts] || '' + Dev.run_git_lint(nil, opts) + end end # ============================================================ @@ -339,6 +345,29 @@ def define_tasks end end + desc desc_for('lint:commits') + task :commits, %i[target opts] => [] do |_t, args| + success = Dev.run_git_lint(args[:target], args[:opts] || '') + exit(1) unless success + end + + desc desc_for('lint:commit') + task :commit, [:path] => [] do |_t, args| + unless args[:path] + puts 'Usage: bundle exec rake labdev:lint:commit[path/to/COMMIT_EDITMSG]' + exit(1) + end + + success = Dev.run_git_lint_hook(args[:path]) + exit(1) unless success + end + + desc 'Deprecated alias for labdev:lint:commit' + task :commit_msg, [:path] => [] do |_t, args| + warn '⚠️ labdev:lint:commit_msg is deprecated; use labdev:lint:commit.' + Rake::Task['labdev:lint:commit'].invoke(args[:path]) + end + desc desc_for('lint:spellcheck') task :spellcheck, %i[path opts] => [] do |_t, args| path = args[:path] @@ -390,14 +419,12 @@ def define_tasks end desc desc_for('heal:adoc') - # Add an optional path argument that defaults to nil task :adoc, %i[path] => [] do |_t, args| Dev.run_adoc_auto_fix(args[:path]) end desc desc_for('heal:all') task :all do - # 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 if ARGV.any? { |arg| arg.include?('labdev:heal:all') && arg.include?('[') } puts '⚠️ labdev:heal:all does not accept any arguments. Exiting.' puts 'Use labdev:heal:ruby[path] or labdev:heal:adoc[path] to auto-fix specific files.' @@ -431,6 +458,11 @@ def define_tasks task :md, %i[path form syntax] => [] do |_t, args| Skim.run_md(args[:path], form: args[:form], syntax: args[:syntax]) end + + desc desc_for('skim:ruby') + task :ruby, %i[path form syntax] => [] do |_t, args| + Skim.run_ruby(args[:path], form: args[:form], syntax: args[:syntax]) + end end # ============================================================ diff --git a/gems/docopslab-dev/lib/docopslab/dev/tool_execution.rb b/gems/docopslab-dev/lib/docopslab/dev/tool_execution.rb index 3c7fa1c0..eff44c71 100644 --- a/gems/docopslab-dev/lib/docopslab/dev/tool_execution.rb +++ b/gems/docopslab-dev/lib/docopslab/dev/tool_execution.rb @@ -58,7 +58,7 @@ def run_in_docker command # Run command in docopslab/dev container # Handle both String and Array command formats cmd_str = command.is_a?(Array) ? command.shelljoin : command - docker_cmd = "docker run -it --rm -v \"$(pwd):/workspace\" -w /workspace docopslab/dev #{cmd_str}" + docker_cmd = "docker run --rm -v \"$(pwd):/workspace\" -w /workspace docopslab/dev #{cmd_str}" puts "🐳 Running in Docker: #{cmd_str}" system(docker_cmd) end diff --git a/gems/docopslab-dev/lib/docopslab/dev/version.rb b/gems/docopslab-dev/lib/docopslab/dev/version.rb index 28e21d83..3ee8954f 100644 --- a/gems/docopslab-dev/lib/docopslab/dev/version.rb +++ b/gems/docopslab-dev/lib/docopslab/dev/version.rb @@ -2,7 +2,7 @@ module DocOpsLab module Dev - VERSION = '0.3.1' + VERSION = '0.4.0' RUBY_TARGET = '3.2.7' end end diff --git a/gems/docopslab-dev/specs/data/default-manifest.yml b/gems/docopslab-dev/specs/data/default-manifest.yml index 3e33e28d..240f2d59 100644 --- a/gems/docopslab-dev/specs/data/default-manifest.yml +++ b/gems/docopslab-dev/specs/data/default-manifest.yml @@ -20,6 +20,8 @@ templates: target: .gitignore - source: templates/README.asciidoc target: README.adoc + - source: templates/CLAUDE.md + target: CLAUDE.md tools: - tool: rubocop @@ -75,8 +77,23 @@ tools: target: .config/actionlint.yml synced: false + - tool: git-lint + files: + - source: commit-conventions/base.yml + target: .config/.vendor/docopslab/commit-conventions.yml + synced: true + - source: commit-conventions/project.yml + target: .config/commit-conventions.yml + synced: false + - source: git-lint/base.yml + target: .config/.vendor/docopslab/git-lint.yml + synced: true + - source: git-lint/project.yml + target: .config/git-lint.local.yml + synced: false + library: enabled: true source: repo: DocOps/lab - ref: labdev-library \ No newline at end of file + ref: labdev-library diff --git a/gems/docopslab-dev/specs/data/tasks-def.yml b/gems/docopslab-dev/specs/data/tasks-def.yml index ea3ed4c4..2f791779 100644 --- a/gems/docopslab-dev/specs/data/tasks-def.yml +++ b/gems/docopslab-dev/specs/data/tasks-def.yml @@ -113,6 +113,14 @@ labdev: _test: - bundle exec rake 'labdev:run:actionlint' - bundle exec rake 'labdev:run:actionlint["-oneline -verbose"]' + git_lint: + _desc: Run the base git-lint branch analyzer and options. + _args: + opts: + summ: Additional arguments to pass to the git-lint command. + required: false + _test: + - bundle exec rake 'labdev:run:git_lint' sync: _desc: Sync all managed files (configs, scripts, docs, styles, etc) all: @@ -270,6 +278,23 @@ labdev: opts: summ: Additional options to pass to actionlint required: false + commits: + _desc: Run git-lint on current branch commits or specific commit SHAs + _args: + target: + summ: Optional commit SHA or comma-separated commit SHAs to lint + required: false + opts: + summ: Additional options to pass to git-lint + required: false + commit: + _desc: Run git-lint on an unsaved commit message file + _args: + path: + summ: Path to the commit message file passed by the commit-msg hook + required: true + commit_msg: + _alias: "labdev:lint:commit" spellcheck: _desc: Generate spellcheck report on content files _args: @@ -337,7 +362,7 @@ labdev: - bundle exec rake 'labdev:show:rule[rubocop,Layout/LineLength]' - bundle exec rake 'labdev:show:rule[vale,DocOpsLab-Authoring.ExNotEg]' skim: - _desc: Skim source files for machine-oriented structured outlines (AsciiDoc + Markdown autodetect) + _desc: Skim source files for machine-oriented structured outlines (AsciiDoc, Markdown, Ruby) _args: path: summ: | @@ -402,7 +427,8 @@ labdev: form: summ: | Output shape: tree, flat, or tree,flat (comma-separated). - Defaults to flat for Markdown. When form is explicitly passed, output defaults to JSON. + Defaults to flat for Markdown. + When form is explicitly passed, output defaults to JSON. required: false syntax: summ: | @@ -413,6 +439,23 @@ labdev: - bundle exec rake 'labdev:skim:md[.agent/docs/]' - bundle exec rake 'labdev:skim:md[.agent/docs/,flat,json]' - bundle exec rake 'labdev:skim:md[.agent/docs/:_docs/agent/,flat,json]' + ruby: + _desc: Use ri to detect API surface (Modules, Classes, Methods, etc.). + _args: + path: + summ: | + Optional path to Ruby gem directory to skim. (Default: '.') + required: false + form: + summ: | + Output shape: tree, flat, or tree,flat (comma-separated). + Defaults to tree. When form is explicitly passed, output defaults to JSON. + required: false + syntax: + summ: | + Override output format: json or yaml. + Default: yaml when form is omitted; json when form is explicitly passed. + required: false # ADDING: help: _desc: Show help information about labdev tasks @@ -430,4 +473,3 @@ labdev: When a task_string is passed, shows all docstrings from this file for that task. required: false _test: [] - diff --git a/gems/docopslab-dev/specs/data/tools.yml b/gems/docopslab-dev/specs/data/tools.yml index 1749b77d..da1fc8ee 100644 --- a/gems/docopslab-dev/specs/data/tools.yml +++ b/gems/docopslab-dev/specs/data/tools.yml @@ -57,4 +57,11 @@ config: inheritance: native # Supports standard YAML patterns uses_vendor_base: true # Syncs base config to .vendor/ - format: YAML \ No newline at end of file + format: YAML + +- slug: git-lint + name: git-lint + config: + inheritance: generated_yaml # Builds .config/git-lint.yml from shared conventions + uses_vendor_base: true + format: YAML diff --git a/scripts/gen_agent_docs.rb b/scripts/gen_agent_docs.rb index 5bafc5fd..4d4fae3f 100644 --- a/scripts/gen_agent_docs.rb +++ b/scripts/gen_agent_docs.rb @@ -10,7 +10,10 @@ def self.run build_dir # Setup ReverseMarkdown extensions for better conversion # Strip internal anchor links and disable anchor IDs for LLM consumption - Sourcerer::MarkDownGrade.bootstrap!(strip_internal_links: true, preserve_heading_ids: false) + Sourcerer::MarkDownGrade.bootstrap!( + strip_internal_links: true, + preserve_heading_ids: false, + convert_dls_to_markdown: true) # Manage paths source_dir = File.expand_path(File.join(build_dir, 'docs', 'agent')) @@ -90,7 +93,7 @@ def self.convert_and_write html_file, dest_file body_div = doc.at_css('div.document-body') if body_div - markdown_content = MarkDownGrade.convert(body_div.inner_html, github_flavored: true) + markdown_content = Sourcerer::MarkDownGrade.convert_html(body_div.inner_html, github_flavored: true) title = h1 ? "# #{h1.text.strip}\n\n" : '' File.write(dest_file, title + markdown_content)