diff --git a/.agent/team/llm-onboarding-improvements.adoc b/.agent/team/llm-onboarding-improvements.adoc new file mode 100644 index 0000000..fabae57 --- /dev/null +++ b/.agent/team/llm-onboarding-improvements.adoc @@ -0,0 +1,360 @@ += LLM Onboarding Improvements for Issuer +:toc: macro +:toclevels: 3 + +[horizontal] +Mission:: Sub-mission for ReleaseHx 0.1.2 release +Agent Role:: Product Engineer + Technical Writer +Date:: 2026-01-24 +Status:: Completed + +toc::[] + +== Executive Summary + +After learning Issuer to create GitHub issues for ReleaseHx 0.1.2 patch release, I documented observations about the tool's LLM-friendliness and propose specific improvements to make Issuer more accessible to AI agents. + +*Key Finding:* Issuer's documentation is comprehensive but could benefit from restructuring specifically for LLM consumption, with clearer hierarchies, machine-readable schemas, and quick-reference patterns. + +== What Worked Well + +=== Comprehensive README.adoc + +The README is thorough and well-structured with: + +* Clear feature list up front +* Installation instructions for both Ruby and Docker users +* Extensive CLI usage documentation +* IMYML format specification with property reference + +=== Rich Example Files + +The `examples/` directory provides excellent learning material: + +* `basic-example.yml` - Simple, clear demonstration +* `advanced-stub-example.yml` - Shows sophisticated features (stub processing, tag prefixes) +* Multiple examples covering different use cases + +=== AsciiDoc Tagged Sections + +The use of `// tag::ai-prompt[]` markers shows awareness of AI agent needs and makes it easier to extract relevant sections. + +=== Dry-Run Mode + +The `--dry` flag is invaluable for validation without API calls - perfect for LLM testing and learning. + +=== Clear Property Naming + +Despite being abbreviated (summ, vrsn, user, tags), the property names are intuitive enough when seen in context. + +== What Was Confusing or Required Extra Effort + +=== IMYML Format Discovery + +*Issue:* The IMYML format specification is embedded deep in the README (lines 160-350). An LLM agent needs to scroll through installation and usage sections first. + +*Impact:* Required reading ~500 lines of documentation before understanding the core data format. + +*What I Did:* Read README sequentially, then jumped to examples to understand format through demonstration. + +=== Property Reference Buried + +*Issue:* The `[[imyml-ref]]` section with complete property documentation starts at line 233, after the format examples. + +*Impact:* Had to read examples first, then circle back to understand all available properties. + +=== No Machine-Readable Schema + +*Issue:* No JSON Schema, SGYML, or other machine-readable format definition. + +*Impact:* LLMs must parse AsciiDoc prose to understand validation rules, types, and constraints. + +=== Tag Prefix Logic Not Immediately Clear + +*Issue:* The `+` and `-` prefix notation for tags is powerful but requires careful reading to understand: + +* Regular tags: `[bug, docs]` +* Append tags: `[+urgent]` +* Removal tags: `[-needs:docs]` + +*Impact:* Could easily misunderstand and create invalid IMYML files. + +=== Authentication Setup + +*Issue:* Authentication section is late in README (line 410+), after CLI usage. + +*Impact:* An agent might try to execute without realizing authentication is required. + +=== Type Property Constraint + +*Issue:* The `type` property "must already be registered" but there's no clear guidance on what types are available or how to check. + +*Impact:* Uncertainty about valid values for the `type` field. + +== Top Priority Documentation Improvements + +=== Create AI Agent Quick Start Section ⭐⭐⭐ + +*Location:* Add near top of README after Overview + +*Proposed Content:* + +[source,asciidoc] +---- +[[ai-agent-quickstart]] +=== Quick Start for AI Agents + +For LLM agents learning to use Issuer: + +. *Core Concept:* IMYML (Issue Management YAML) format defines issues in YAML, Issuer posts them to GitHub +. *Required Reading Order:* + - <> - Understand the data structure + - <> - Complete field documentation + - <> - Command-line options + - <> - Token setup +. *Examples:* Study `examples/basic-example.yml` and `examples/advanced-stub-example.yml` +. *Testing:* Always use `--dry` flag first to validate without posting +. *Authentication:* Set `ISSUER_API_TOKEN` environment variable before posting + +*Minimal Valid IMYML:* + +[source,yaml] +---- +$meta: + proj: org/repo +issues: + - summ: Issue title here +---- + +=== Create IMYML Cheat Sheet ⭐⭐⭐ + +*Location:* New file `docs/IMYML-REFERENCE.adoc` (or section in README): Issue title + + +== Complete Template + +[source,yaml] +---- +$meta: + proj: org/repo # Required: org/repo or user/repo + defaults: + vrsn: 1.0.0 # String: milestone/version + user: username # String: GitHub username + type: Task # String: must exist in repo + tags: [label1, +append] # Array: see tag logic below + stub: false # Boolean: enable stub processing + body: Default text # String: default body text + head: Header text # String: prepend when stub=true + tail: Footer text # String: append when stub=true + +issues: # Array: list of issue records + - summ: Title # Required: one-line summary + body: Description # String: full description + type: Bug # String: issue type + vrsn: 2.0.0 # String: milestone + user: assignee # String: GitHub username + tags: [label, +add] # Array: see tag logic + stub: false # Boolean: override default +---- + +== Tag Logic Rules + +* Regular tags: `[bug, docs]` - Applied based on default logic +* Append tags: `[+urgent]` - Always added to all issues +* Removal tags: `[-needs:docs]` - Remove from defaults +* Can combine: `[feature, +critical, -auto]` + +== Property Types + +summ:: String (required) - Issue title +body:: String (optional) - Issue description (Markdown formatted) +type:: String (optional) - Must already exist in target repo +vrsn:: String (optional) - Milestone name (auto-created if missing) +user:: String (optional) - GitHub username for assignee +tags:: Array[String] (optional) - Labels with prefix notation +stub:: Boolean (optional) - Enable header/footer injection +---- + +=== Add Machine-Readable Schema ⭐⭐ + +*Location:* New file `schemas/imyml-schema.json` or `schemas/imyml-schema.sgyml` + +*Benefit:* Allows LLMs to validate IMYML structure programmatically without parsing prose. + +*Consider:* Since SchemaGraphy exists in the DocOps Lab ecosystem, use SGYML format for the schema definition. + +=== Reorganize README Sections ⭐⭐ + +*Proposed New Order:* + +. Overview (keep as-is) +. *AI Agent Quick Start* (NEW) +. *IMYML Format* (move up from line 160) +. *IMYML Property Reference* (keep with format section) +. *Examples* (link to examples/ directory) +. Installation (keep as-is) +. Authentication (move up before CLI usage) +. CLI Usage (after auth) +. Advanced Usage (keep as-is) +. Development (keep as-is) + +*Rationale:* Put the data format first so agents understand what they're creating before learning how to execute. + +=== Expand Type Property Documentation ⭐ + +*Location:* In IMYML Reference section + +*Add:* + +[source,asciidoc] +---- +type::: +(String) +The type of issue, which must already be registered in the target project or repository. ++ +Common GitHub issue types include: `Bug`, `Feature`, `Task`, `Enhancement`, `Question`, `Documentation`. + +[NOTE] +Issue types vary by repository. Check the target repository's issue type configuration before using this field. If a type doesn't exist, issue creation will fail. + +[TIP] +Use `--dry` mode to test without posting, then check GitHub repo settings for available types if validation fails. +---- + +== Additional Improvement Ideas + +=== Inline Examples in Property Reference + +*Enhancement:* Add mini-examples next to each property definition. + +*Example:* + +[source,asciidoc] +---- +tags::: +(Array of Strings) +Labels to apply to the issue. ++ +.Example +[source,yaml] +---- +tags: [bug, priority:high, +urgent, -needs:review] +---- ++ +Supports prefix notation: ++ +* `bug` - regular label +* `+urgent` - append to all issues +* `-needs:review` - remove from defaults + +=== Visual Hierarchy Markers + +*Enhancement:* Use consistent AsciiDoc markers for LLM parsing: + +* `[[property-name]]` anchors for all properties +* `[.required]` and `[.optional]` role markers +* `[.example]` markers for code blocks + +=== Create docs/agent/ Directory + +*Proposed Structure:* + +.... +docs/ +├── agent/ +│ ├── README.adoc # AI agent overview +│ ├── quickstart.adoc # Fast onboarding +│ ├── imyml-reference.adoc # Complete property docs +│ ├── common-patterns.adoc # Recipe-style examples +│ └── troubleshooting.adoc # FAQ and error handling +└── schemas/ + └── imyml-schema.sgyml # Machine-readable schema +.... + +=== Error Message Improvements + +*Enhancement:* When validation fails, suggest solutions: + +.... +Error: Unknown type 'Improvement' +Available types in DocOps/releasehx: Bug, Feature, Task, Enhancement +Tip: Use --dry mode to validate before posting +.... + +=== Authentication Quick Check Command + +*Enhancement:* Add a test command: + +.... +issuer --test-auth +# Output: +# ✓ GitHub token found (ISSUER_API_TOKEN) +# ✓ Successfully authenticated as: briandominick +# ✓ API rate limit: 4,987/5,000 remaining +.... + +== Mission-Specific Observations + +=== What I Learned About Issuer + +IMYML is intuitive once understood:: The abbreviated property names (summ, vrsn, user, tags) are actually helpful for conciseness + +Dry-run is essential: Perfect safety mechanism for validation + +Tag prefix logic is powerful:: The `+`/`-` notation is elegant for label management + +Stub functionality is sophisticated:: head/body/tail system is well-designed for template-based issue creation + +Docker-first approach is smart:: Lowers barrier to entry for non-Ruby users + +=== Blockers Encountered + +Need to run from issuer directory:: +Had to execute from `/home/brian/Documents/work/issuer/` since issuer wasn't in releasehx bundle + +Type property uncertainty:: +Not sure if "Bug" and "Task" are valid without checking GitHub repo + +=== Successful Outcomes + +* ✅ Created valid IMYML file on first attempt after reading docs +* ✅ Dry-run passed validation +* ✅ Two issues correctly structured with all metadata +* ✅ Ready for actual posting (awaiting Operator approval) + +== Recommendations for Implementation + +=== Immediate Actions (High ROI, Low Effort) + +. *Add AI Agent Quick Start section* to README (30 minutes) +. *Move IMYML format section up* in README (15 minutes) +. *Move Authentication before CLI Usage* (5 minutes) +. *Add inline examples to Property Reference* (1 hour) + +=== Short-Term Actions (High ROI, Medium Effort) + +[start=5] +. *Create IMYML Quick Reference cheat sheet* (2 hours) +. *Create docs/ai-agents/ directory structure* (4 hours) +. *Expand type property documentation* (30 minutes) + +=== Long-Term Actions (High ROI, High Effort) + +[start=8] +. *Create machine-readable schema* (SGYML format) (8 hours) +. *Implement --test-auth command* (4 hours) +. *Enhance error messages with suggestions* (ongoing) + +== Conclusion + +Issuer is already well-documented and functional. The proposed improvements focus on *restructuring and augmenting existing documentation* rather than changing core functionality. + +Key Insight:: +LLM agents benefit most from: ++ +* *Hierarchical navigation* (quick start → detailed reference) +* *Format-first documentation* (show IMYML before CLI) +* *Machine-readable schemas* (programmatic validation) +* *Inline examples* (see-and-understand patterns) + +These improvements would make Issuer significantly more accessible to AI agents while also benefiting human users through clearer documentation structure. \ No newline at end of file diff --git a/.config/.shellcheckrc b/.config/.shellcheckrc new file mode 100644 index 0000000..85edd76 --- /dev/null +++ b/.config/.shellcheckrc @@ -0,0 +1,14 @@ +# ShellCheck configuration for DocOps Lab projects +# This file is synced from docopslab-dev gem + +# Disable some overly strict rules for our use cases +disable=SC2034 # Variable appears unused (common in sourced scripts) +disable=SC2086 # Double quote to prevent globbing (sometimes we want globbing) +disable=SC2181 # Check exit code directly with e.g. 'if mycmd;', not indirectly with $? + +# Set default shell to bash (most of our scripts are bash) +shell=bash + +# Enable additional optional checks +enable=quote-safe-variables +enable=require-variable-braces \ No newline at end of file diff --git a/.config/actionlint.yml b/.config/actionlint.yml new file mode 100644 index 0000000..801b628 --- /dev/null +++ b/.config/actionlint.yml @@ -0,0 +1,13 @@ +# actionlint configuration for this project +# References DocOps Lab base configuration + +# Project-specific ignores (add as needed): +ignore: + # Example: Ignore specific workflow patterns + # - 'workflow "deploy.yml"' + # - '"ubuntu-20.04" is deprecated' + +# Project-specific overrides: +shellcheck: + enable: true + # shell-options: "-e SC2016" # Add project-specific ShellCheck exclusions diff --git a/.config/docopslab-dev.yml b/.config/docopslab-dev.yml new file mode 100644 index 0000000..6e90c54 --- /dev/null +++ b/.config/docopslab-dev.yml @@ -0,0 +1,65 @@ +source: + repo: DocOps/lab + ref: v1 + root: gems/docopslab-dev/config-packs + +docs: + - source: docs/agent/AGENTS.md + target: AGENTS.md + synced: false + - source: docs/agent/skills/*.md + target: .agent/docs/skills/ + synced: true + - source: docs/agent/topics/*.md + target: .agent/docs/topics/ + synced: true + - source: docs/agent/roles/*.md + target: .agent/docs/roles/ + synced: true + - source: docs/agent/missions/*.md + target: .agent/docs/missions/ + synced: true + +tools: + - tool: rubocop + files: + - source: rubocop/base.yml + target: .config/.vendor/docopslab/rubocop.yml + synced: true + - source: rubocop/project.yml + target: .config/rubocop.yml + synced: false + + - tool: vale + files: + - source: vale/base.ini + target: .config/.vendor/docopslab/vale.ini + synced: true + - source: vale/project.ini + target: .config/vale.local.ini + synced: false + + - tool: htmlproofer + enabled: false # Disabled by default, enable per project + files: + - source: htmlproofer/base.yml + target: .config/.vendor/docopslab/htmlproofer.yml + synced: true + - source: htmlproofer/project.yml + target: .config/htmlproofer.yml + synced: false + + - tool: shellcheck + files: + - source: shellcheck/base.shellcheckrc + target: .config/.shellcheckrc + synced: true + + - tool: actionlint + files: + - source: actionlint/base.yml + target: .config/.vendor/docopslab/actionlint.yml + synced: true + - source: actionlint/project.yml + target: .config/actionlint.yml + synced: false \ No newline at end of file diff --git a/.config/releasehx.yml b/.config/releasehx.yml new file mode 100644 index 0000000..e3b488b --- /dev/null +++ b/.config/releasehx.yml @@ -0,0 +1,147 @@ +# ReleaseHx configuration for DocOps/issuer repository + +origin: + source: github + project: DocOps/issuer + version: 3 + label: GitHub Issues API v3 + format: json + +paths: + drafts_dir: docs/releases + payloads_dir: docs/releases/payloads + templates_dir: docs/releases/templates + mappings_dir: docs/releases/mappings + +conversions: + summ: issue_heading + head: issue_heading + note: issue_body + note_source: issue_body + note_pattern: '/^## Release Note\s*\n+(?(.|\n)+)/mi' + markup: asciidoc + engine: asciidoctor + +rhyml: + pasterize_summ: true + pasterize_head: true + chid: "{{ release.code | replace: '.', '_' | upcase }}-{{ change.tick }}" + empty_notes: skip + +parts: + label_prefix: "part:" + cli: + text: CLI + head: CLI + icon: terminal + imyml: + text: IMYML + head: IMYML + icon: file-code-o + documentation: + text: documentation + head: Documentation + icon: book + api_clients: + text: API Clients + head: API Clients + icon: plug + +types: + label_prefix: "type:" + removal: + slug: removal + text: removal + head: Removed + icon: exclamation-triangle + feature: + slug: feature + text: new feature + head: Added + icon: plus-square-o + bug: + slug: bug + text: bug fix + head: Fixed + icon: bug + improvement: + slug: improvement + text: improvement + head: Improved + icon: wrench + deprecation: + slug: deprecation + text: deprecation + head: Deprecated + icon: exclamation-circle + +tags: + _include: ['needs:note', 'changelog', 'breaking', 'deprecation'] + _exclude: ['internal', 'wontfix', 'duplicate'] + + breaking: + head: Breaking Changes + icon: exclamation-triangle + text: Breaking changes + + deprecation: + head: Deprecations + icon: clock-o + text: Deprecated features + +links: + web: https://github.com/DocOps/issuer/issues/{{ tick }} + git: https://github.com/DocOps/issuer/commit/{{ hash }} + usr: https://github.com/{{ vars.lead }} + +modes: + asciidoc_frontmatter: true + markdown_frontmatter: false + remove_excess_lines: 2 + +history: + items: + issue_links: true + git_links: false + metadata_icons: true + +# Enable dynamic labeling for the changelog section +changelog: + head: What's Changed + text: Summary of all changes in this release. + htag: h2 + spot: 2 + sort: + - breaking:grouping1 + - type:grouping1 + items: + frame: unordered + allow_redundant: false + show_issue_links: true + metadata_labels: before + metadata_icons: before + show_parts_label: true + show_tags_label: false + show_type_label: false + show_lead_label: false + +# Enable dynamic labeling for the release notes section +notes: + head: Release Notes + text: Detailed descriptions of notable changes. + htag: h2 + spot: 1 + sort: + - breaking:grouping1 + - deprecation:grouping1 + - part:grouping1 + items: + frame: unordered + allow_redundant: true + show_issue_links: true + metadata_labels: before + metadata_icons: before + show_parts_label: true + show_tags_label: true + show_type_label: true + show_lead_label: true diff --git a/.config/rubocop.yml b/.config/rubocop.yml new file mode 100644 index 0000000..d65e45c --- /dev/null +++ b/.config/rubocop.yml @@ -0,0 +1,8 @@ +# DocOps Lab RuboCop Configuration +# This file inherits from the base configuration and can be customized for your project + +inherit_from: .vendor/docopslab/rubocop.yml + +# Add your project-specific overrides below: +# Style/StringLiterals: +# EnforcedStyle: double_quotes \ No newline at end of file diff --git a/.config/vale.ini b/.config/vale.ini new file mode 100644 index 0000000..dcc8f47 --- /dev/null +++ b/.config/vale.ini @@ -0,0 +1,28 @@ +MinAlertLevel = error +StylesPath = .vendor/vale/styles +Packages = RedHat, AsciiDoc + +[asciidoctor] +attribute-missing = drop +experimental = YES +safe = unsafe + +[*] +BasedOnStyles = RedHat, AsciiDoc, DocOpsLab-AsciiDoc, DocOpsLab-Authoring +RedHat.PascalCamelCase = suggestion +RedHat.Headings = suggestion +RedHat.SentenceLength = suggestion +RedHat.TermsErrors = suggestion +RedHat.TermsWarnings = suggestion +RedHat.Spelling = NO +RedHat.CaseSensitiveTerms = NO +RedHat.GitLinks = NO +RedHat.Using = NO +RedHat.Slash = suggestion +RedHat.DoNotUseTerms = suggestion +RedHat.HeadingPunctuation = NO +RedHat.Hyphens = suggestion + +[#.adoc] +BasedOnStyles = RedHat, AsciiDoc, DocOpsLab-AsciiDoc, DocOpsLab-Authoring +RedHat-AsciiDoc.LinkContainsLinkText = suggestion diff --git a/.config/vale.local.ini b/.config/vale.local.ini new file mode 100644 index 0000000..0c4ce72 --- /dev/null +++ b/.config/vale.local.ini @@ -0,0 +1,5 @@ +# DocOps Lab Vale Configuration +# Combined base and project configuration for consistent Vale linting + +MinAlertLevel = error +StylesPath = .vendor/vale/styles \ No newline at end of file diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index b347e40..4f6830f 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -14,7 +14,7 @@ jobs: timeout-minutes: 15 strategy: matrix: - ruby-version: ['3.0', '3.1', '3.2'] + ruby-version: ['3.2', '3.3'] steps: - uses: actions/checkout@v4 @@ -31,7 +31,7 @@ jobs: bundle list - name: Run tests - run: bundle exec rake spec + run: bundle exec rake rspec - name: Run CLI tests run: | diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index b1f8b54..489bc6e 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -38,7 +38,7 @@ jobs: - name: Setup Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.1' + ruby-version: '3.2' bundler-cache: true - name: Install Vale diff --git a/.gitignore b/.gitignore index 43f292f..e961d78 100644 --- a/.gitignore +++ b/.gitignore @@ -61,7 +61,7 @@ comprehensive-test.yml .ruby-gemset # Bundler -/Gemfile.lock.example +./Gemfile.lock /.bundle # Documentation @@ -98,8 +98,13 @@ specs/data/*-issues.yml # Local scratch space for AI output .warp/ +.agent/* +# DO track team-shared files +!.agent/team/ # Docs and ReleaseHx files -docs/releasehx/*.md -docs/releasehx/*.adoc -releasehx-install.sh \ No newline at end of file +docs/releases/*.md +releasehx-install.sh +# DocOps Lab vendor files +scripts/.vendor/ +.config/.vendor/ diff --git a/.vale.ini b/.vale.ini index 223c993..0287987 100644 --- a/.vale.ini +++ b/.vale.ini @@ -29,6 +29,7 @@ write-good.E-Prime = NO write-good.Passive = NO write-good.TooWordy = NO write-good.Weasel = NO +proselint.Typography = NO # Global ignores for all markup files TokenIgnores = (\$[A-Z_]+), (`[^`]+`), (:[a-z-]+:), (\{[^}]+\}), (https?://[^\s]+) diff --git a/.vale/config/vocabularies/issuer/accept.txt b/.vale/config/vocabularies/issuer/accept.txt deleted file mode 100644 index 4639191..0000000 --- a/.vale/config/vocabularies/issuer/accept.txt +++ /dev/null @@ -1,63 +0,0 @@ -# Project-specific vocabulary for issuer -# These terms are accepted and won't be flagged by spell checkers - -# Project name and variations -issuer -Issuer -IMYML -imyml - -# Technical terms -GitHub -GitLab -Octokit -AsciiDoc -asciidoc -YAML -JSON -APIs -API -CLI -summ -vrsn -proj -metadata -milestones -assignee -repos -repo - -# Common tech abbreviations -dev -config -async -auth -env -vars -bool -bool -bool -timestamp -username -workflow -workflows - -# Ruby/gem specific -Gemfile -gemspec -rspec -RSpec -bundler -Bundler - -# Docker terms -dockerfile -Dockerfile -DocOps -docopslab - -# Proper nouns -Jira -OAuth -README -roadmap diff --git a/.vale/config/vocabularies/issuer/reject.txt b/.vale/config/vocabularies/issuer/reject.txt deleted file mode 100644 index fdf1f5b..0000000 --- a/.vale/config/vocabularies/issuer/reject.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Terms to reject/flag in the issuer project -# These will always be flagged as errors - -# Common misspellings in tech docs -seperately -seperate -recieve -definately -occured -teh -hte -thier -there own -there job - -# Discouraged terms -simply -just -obviously -clearly -easily diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..396f258 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,191 @@ +# AGENTS.md + +AI Agent Guide for Issuer development. + +Table of Contents: + - AI Agency + - Essential Reading Order + - Codebase Architecture + - Agent Development Approach + - Working with Demo Data + - General Agent Responsibilities + - Remember + + +## AI Agency + +As an LLM-backed agent, your primary mission is to assist a human OPerator in the development, documentation, and maintenance of Issuer by following best practices outlined in this document. + +### Philosophy: Documentation-First, Junior/Senior Contributor Mindset + +As an AI agent working on Issuer, approach this codebase like an **inquisitive and opinionated junior engineer with senior coding expertise and experience**. +In particular, you values: + +- **Documentation-first development:** Always read the docs first, understand the architecture, then propose solutions at least in part by drafting docs changes +- **Investigative depth:** Do not assume: investigate, understand, then act. +- **Architectural awareness:** Consider system-wide impacts of changes. +- **Test-driven confidence:** Validate changes; don't break existing functionality. +- **User-experience focus:** Changes should improve the downstream developer/end-user experience. + + +### Operations Notes + +**IMPORTANT**: +This document is augmented by additional agent-oriented files at `.agent/docs/`. +Be sure to `tree .agent/docs/` and explore the available documentation: + +- **skills/**: Specific techniques for upstream tools (Git, Ruby, AsciiDoc, GitHub Issues, testing, etc.) +- **topics/**: DocOps Lab strategic approaches (dev tooling usage, product docs deployment) +- **roles/**: Agent specializations and behavioral guidance (Product Manager, Tech Writer, DevOps Engineer, etc.) +- **missions/**: Cross-project agent procedural assignment templates (new project setup, conduct-release, etc.) + +**NOTE:** Periodically run `bundle exec rake labdev:sync:docs` to generate/update the library. + +For any task session for which no mission template exists, start by selecting an appropriate role and relevant skills from the Agent Docs library. + +**Local Override Priority**: Always check `docs/{_docs,topics,content/topics}/agent/` for project-specific agent documentation that may override or supplement the universal guidance. + +### Ephemeral/Scratch Directory + +There should always be an untracked `.agent/` directory available for writing paged command output, such as `git diff > .agent/tmp/current.diff && cat .agent/tmp/current.diff`. +Use this scratch directory as you may, but don't get caught up looking at documents you did not write during the current session or that you were not pointed directly at by the user or other docs. + +Typical subdirectories include: + +- `docs/`: Generated agent documentation library (skills, roles, topics, missions) +- `tmp/`: Scratch files for current session +- `logs/`: Persistent logs across sessions (e.g., task run history) +- `reports/`: Persistent reports across sessions (e.g., spellcheck reports) +- `team/`: Shared (Git-tracked) files for multi-agent/multi-operator collaboration + +### AsciiDoc, not Markdown + +DocOps Lab is an **AsciiDoc** shop. +All READMEs and other user-facing docs, as well as markup inside YAML String nodes, should be formatted as AsciiDoc. + +Agents have a frustrating tendency to create `.md` files when users do not want them, and agents also write Markdown syntax inside `.adoc` files. +Stick to the AsciiDoc syntax and styles you find in the `README.adoc` files, and you won't go too far wrong. + +ONLY create `.md` files for your own use, unless Operator asks you to. + + + + +## Essential Reading Order (Start Here!) + +Before making any changes, **read these documents in order**: + +### 1. Core Documentation +- **`./README.adoc`** +- Main project overview, features, and workflow examples: + - Pay special attention to any AI prompt sections (`// tag::ai-prompt[]`...`// end::ai-prompt[]`) + - Study the example CLI usage patterns +- Review `Gemfile` and `Dockerfile` for dependencies and environment context + +### 2. Architecture Understanding +- **`./specs/tests/README.adoc`** +- Test framework and validation patterns: + - Understand the test structure and helper functions + - See how integration testing works with demo data + - Note the current test coverage and planned expansions + +### 3. Practical Examples +- See `examples/` directory for example files and demo data. + +### 4. Agent Roles and Skills +- `README.adoc` section: `== Development` +- Use `tree .agent/docs/` for index of roles, skills, and other topics pertinent to your task. + + +## Codebase Architecture + +### Core Components + +``` +lib/issuer/ +├── apis/ # API integration modules +│ └── github/ # GitHub API client implementation +├── cli.rb # Thor-based CLI interface +├── issue.rb # Core Issue data model and validation +├── ops.rb # High-level operations and orchestration +├── sites/ # Site-specific logic (future expansion) +└── version.rb # Version definition +``` + + + + + +## Agent Development Approach + +**Before starting development work:** + +1. **Adopt an Agent Role:** If the Operator has not assigned you a role, review `.agent/docs/roles/` and select the most appropriate role for your task. +2. **Gather Relevant Skills:** Examine `.agent/docs/skills/` for techniques needed: +3. **Understand Strategic Context:** Check `.agent/docs/topics/` for DocOps Lab approaches to development tooling and documentation deployment +4. **Read relevant project documentation** for the area you're changing +5. **For substantial changes, check in with the Operator** - lay out your plan and get approval for risky, innovative, or complex modifications + + + +## Working with Demo Data + +Use the `examples/` directory to validate changes. +You can run `bundle exec issuer examples/basic-example.yml --dry` to test without posting to GitHub. +For comprehensive testing, refer to `specs/tests/README.adoc`. + + + +## General Agent Responsibilities + +1. **Question Requirements:** Ask clarifying questions about specifications. +2. **Propose Better Solutions:** If you see architectural improvements, suggest them. +3. **Consider Edge Cases:** Think about error conditions and unusual inputs. +4. **Maintain Backward Compatibility:** Don't break existing workflows. +5. **Improve Documentation:** Update docs when adding features. +6. **Test Thoroughly:** Use both unit tests and demo validation. +7. **DO NOT assume you know the solution** to anything big. + +### Cross-role Advisories + +During planning stages, be opinionated about: + +- Code architecture and separation of concerns +- User experience, especially: + - CLI ergonomics + - Error handling and messaging + - Configuration usability + - Logging and debug output +- Documentation quality and completeness +- Test coverage and quality + +When troubleshooting or planning, be inquisitive about: + +- Why existing patterns were chosen +- Future proofing and scalability +- What the user experience implications are +- How changes affect different API platforms +- Whether configuration is flexible enough +- What edge cases might exist + + + + +## Remember + +Issuer is a tool for bulk-creating GitHub Issues from a single YAML file (IMYML format). +It is designed to be platform-agnostic in its data model, though currently focused on GitHub. + + + +Your primary mission is to improve Issuer while maintaining operational standards: + +1. **Reliability:** Don't break existing functionality +2. **Usability:** Make interfaces intuitive and helpful +3. **Flexibility:** Support diverse team workflows and preferences +4. **Performance:** Respect system limits and optimize intelligently +5. **Documentation:** Keep the docs current and comprehensive + +**Most importantly**: Read the documentation first, understand the system, then propose thoughtful solutions that improve the overall architecture and user experience. + + diff --git a/Gemfile b/Gemfile index 8a0bba2..fe22a67 100644 --- a/Gemfile +++ b/Gemfile @@ -3,5 +3,15 @@ source 'https://rubygems.org' # Specify your gem's dependencies in issuer.gemspec gemspec -gem 'rake', '~> 13.0' -gem 'rspec', '~> 3.0' +# Development dependencies +# Note: rubocop and rubocop-rspec are provided by docopslab-dev +group :development, :test do + gem 'rspec', '~> 3.0' +end + +group :development do + gem 'rake', '~> 13.0' + gem 'asciidoctor', '~> 2.0' + gem 'docopslab-dev', '~> 0.1.0' + gem 'releasehx', '~> 0.1.2' +end diff --git a/Gemfile.lock b/Gemfile.lock index 1244499..6a819b3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - issuer (0.2.1) + issuer (0.3.0) faraday-retry (~> 2.0) octokit (~> 8.0) thor (~> 1.0) @@ -9,29 +9,288 @@ PATH GEM remote: https://rubygems.org/ specs: - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) - asciidoctor (2.0.23) + Ascii85 (2.0.1) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) + afm (1.0.0) + asciidoctor (2.0.26) + asciidoctor-pdf (2.3.24) + asciidoctor (~> 2.0) + concurrent-ruby (~> 1.3) + matrix (~> 0.4) + prawn (~> 2.4.0) + prawn-icon (~> 3.0.0) + prawn-svg (~> 0.34.0) + prawn-table (~> 0.2.0) + prawn-templates (~> 0.1.0) + treetop (~> 1.6.0) + ttfunk (~> 1.7.0) + ast (2.4.3) + async (2.35.0) + console (~> 1.29) + fiber-annotation + io-event (~> 1.11) + metrics (~> 0.12) + traces (~> 0.18) base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (3.3.1) + brakeman (7.1.1) + racc + bundler-audit (0.9.3) + bundler (>= 1.2.0) + thor (~> 1.0) + coderay (1.1.3) + colorator (1.1.0) + commonmarker (0.23.12) + concurrent-ruby (1.3.6) + console (1.34.2) + fiber-annotation + fiber-local (~> 1.1) + json + css_parser (1.21.1) + addressable + csv (3.3.5) + debride (1.14.0) + path_expander (~> 2.0) + prism (~> 1.5) + sexp_processor (~> 4.17) diff-lcs (1.6.2) - faraday (2.13.2) + docile (1.4.1) + docopslab-dev (0.1.0) + asciidoctor (~> 2.0) + brakeman (~> 7.1) + bundler-audit (~> 0.9) + debride (~> 1.13) + fasterer (~> 0.11) + flog (~> 4.8) + html-proofer (~> 5.0) + inch (~> 0.8) + rake (~> 13.0) + reek (~> 6.5) + rubocop (~> 1.80) + rubocop-rake (~> 0.7) + rubocop-rspec (~> 3.7) + simplecov (~> 0.22) + subtxt (~> 0.3) + yaml (~> 0.2) + dry-configurable (1.3.0) + dry-core (~> 1.1) + zeitwerk (~> 2.6) + dry-core (1.1.0) + concurrent-ruby (~> 1.0) + logger + zeitwerk (~> 2.6) + dry-inflector (1.2.0) + dry-initializer (3.2.0) + dry-logic (1.6.0) + bigdecimal + 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) + dry-core (~> 1.1) + dry-initializer (~> 3.2) + dry-logic (~> 1.5) + dry-types (~> 1.8) + zeitwerk (~> 2.6) + dry-types (1.8.3) + bigdecimal (~> 3.0) + concurrent-ruby (~> 1.0) + dry-core (~> 1.0) + dry-inflector (~> 1.0) + dry-logic (~> 1.4) + zeitwerk (~> 2.6) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + ethon (0.15.0) + ffi (>= 1.15.0) + eventmachine (1.2.7) + faraday (2.14.0) faraday-net_http (>= 2.0, < 3.5) json logger - faraday-net_http (3.4.1) - net-http (>= 0.5.0) + faraday-follow_redirects (0.3.0) + faraday (>= 1, < 3) + faraday-net_http (3.4.2) + net-http (~> 0.5) faraday-retry (2.3.2) faraday (~> 2.0) - json (2.12.2) + fasterer (0.11.0) + ruby_parser (>= 3.19.1) + ffi (1.17.3-x86_64-linux-gnu) + fiber-annotation (0.2.0) + fiber-local (1.1.0) + fiber-storage + fiber-storage (1.0.1) + flog (4.9.1) + path_expander (~> 2.0) + prism (~> 1.7) + sexp_processor (~> 4.8) + forwardable-extended (2.6.0) + google-protobuf (4.33.4-x86_64-linux-gnu) + bigdecimal + rake (>= 13) + hashery (2.1.2) + html-proofer (5.1.1) + addressable (~> 2.3) + async (~> 2.1) + benchmark (~> 0.5) + nokogiri (~> 1.13) + pdf-reader (~> 2.11) + rainbow (~> 3.0) + typhoeus (~> 1.3) + yell (~> 2.0) + zeitwerk (~> 2.5) + http_parser.rb (0.8.1) + i18n (1.14.8) + concurrent-ruby (~> 1.0) + inch (0.8.0) + pry + sparkr (>= 0.2.0) + term-ansicolor + yard (~> 0.9.12) + io-console (0.8.2) + io-event (1.14.2) + jekyll (4.4.1) + addressable (~> 2.4) + base64 (~> 0.2) + colorator (~> 1.0) + csv (~> 3.0) + em-websocket (~> 0.5) + i18n (~> 1.0) + jekyll-sass-converter (>= 2.0, < 4.0) + jekyll-watch (~> 2.0) + json (~> 2.6) + kramdown (~> 2.3, >= 2.3.1) + kramdown-parser-gfm (~> 1.0) + liquid (~> 4.0) + mercenary (~> 0.3, >= 0.3.6) + pathutil (~> 0.9) + rouge (>= 3.0, < 5.0) + safe_yaml (~> 1.0) + terminal-table (>= 1.8, < 4.0) + webrick (~> 1.7) + jekyll-asciidoc (3.0.1) + asciidoctor (>= 1.5.0, < 3.0.0) + jekyll (>= 3.0.0) + jekyll-sass-converter (3.1.0) + sass-embedded (~> 1.75) + jekyll-watch (2.2.1) + listen (~> 3.0) + jmespath (1.6.2) + json (2.18.0) + json-schema (6.1.0) + addressable (~> 2.8) + bigdecimal (>= 3.1, < 5) + jsonpath (1.1.5) + multi_json + kramdown (2.4.0) + rexml + kramdown-asciidoc (2.1.1) + kramdown (~> 2.4.0) + kramdown-parser-gfm (~> 1.1.0) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + liquid (4.0.4) + listen (3.10.0) + logger + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) logger (1.7.0) - net-http (0.6.0) - uri + matrix (0.4.3) + mcp (0.6.0) + json-schema (>= 4.1) + mercenary (0.4.0) + method_source (1.1.0) + metrics (0.15.0) + mize (0.6.1) + multi_json (1.19.1) + net-http (0.9.1) + uri (>= 0.11.1) + nokogiri (1.18.10-x86_64-linux-gnu) + racc (~> 1.4) octokit (8.1.0) base64 faraday (>= 1, < 3) sawyer (~> 0.9) - public_suffix (6.0.2) - rake (13.3.0) + parallel (1.27.0) + parser (3.3.10.0) + ast (~> 2.4.1) + racc + path_expander (2.0.0) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + pdf-core (0.9.0) + pdf-reader (2.15.1) + Ascii85 (>= 1.0, < 3.0, != 2.0.0) + afm (>= 0.2.1, < 2) + hashery (~> 2.0) + ruby-rc4 + ttfunk + polyglot (0.3.5) + prawn (2.4.0) + pdf-core (~> 0.9.0) + ttfunk (~> 1.7) + prawn-icon (3.0.0) + prawn (>= 1.1.0, < 3.0.0) + prawn-svg (0.34.2) + css_parser (~> 1.6) + matrix (~> 0.4.2) + prawn (>= 0.11.1, < 3) + rexml (~> 3.2) + prawn-table (0.2.2) + prawn (>= 1.3.0, < 3.0.0) + prawn-templates (0.1.2) + pdf-reader (~> 2.0) + prawn (~> 2.2) + prism (1.9.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + public_suffix (7.0.2) + racc (1.8.1) + rainbow (3.1.1) + rake (13.3.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + readline (0.0.4) + reline + reek (6.5.0) + dry-schema (~> 1.13) + logger (~> 1.6) + parser (~> 3.3.0) + rainbow (>= 2.0, < 4.0) + rexml (~> 3.1) + regexp_parser (2.11.3) + releasehx (0.1.2) + asciidoctor-pdf (~> 2.3) + commonmarker (~> 0.23) + faraday (~> 2.9) + faraday-follow_redirects (~> 0.3.0) + jekyll (~> 4.4) + jekyll-asciidoc (~> 3.0.0) + jmespath (~> 1.6) + jsonpath (~> 1.1) + kramdown (~> 2.4) + kramdown-asciidoc (~> 2.1) + liquid (~> 4.0) + mcp (~> 0.4) + prism (~> 1.5) + thor (~> 1.3) + tilt (~> 2.3) + to_regexp (= 0.2.1) + yaml (~> 0.4) + reline (0.6.3) + io-console (~> 0.5) + rexml (3.4.4) + rouge (4.7.0) rspec (3.13.1) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) @@ -45,20 +304,82 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.4) + rubocop (1.82.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.48.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.48.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-rake (0.7.1) + lint_roller (~> 1.1) + rubocop (>= 1.72.1) + rubocop-rspec (3.8.0) + lint_roller (~> 1.1) + rubocop (~> 1.81) + ruby-progressbar (1.13.0) + ruby-rc4 (0.1.5) + ruby_parser (3.22.0) + racc (~> 1.5) + sexp_processor (~> 4.16) + safe_yaml (1.0.5) + sass-embedded (1.97.3-x86_64-linux-gnu) + google-protobuf (~> 4.31) sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - thor (1.3.2) - uri (1.0.3) + sexp_processor (4.17.4) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.13.2) + simplecov_json_formatter (0.1.4) + sparkr (0.4.1) + subtxt (0.3.0) + sync (0.5.0) + term-ansicolor (1.11.3) + tins (~> 1) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + thor (1.5.0) + tilt (2.7.0) + tins (1.51.0) + bigdecimal + mize (~> 0.6) + readline + sync + to_regexp (0.2.1) + traces (0.18.2) + treetop (1.6.18) + polyglot (~> 0.3) + ttfunk (1.7.0) + typhoeus (1.5.0) + ethon (>= 0.9.0, < 0.16.0) + unicode-display_width (2.6.0) + uri (1.1.1) + webrick (1.9.2) + yaml (0.4.0) + yard (0.9.38) + yell (2.2.2) + zeitwerk (2.7.4) PLATFORMS x86_64-linux DEPENDENCIES asciidoctor (~> 2.0) - bundler (~> 2.0) + docopslab-dev (~> 0.1.0) issuer! rake (~> 13.0) + releasehx (~> 0.1.2) rspec (~> 3.0) BUNDLED WITH diff --git a/README.adoc b/README.adoc index f8c4469..a754fec 100644 --- a/README.adoc +++ b/README.adoc @@ -1,9 +1,9 @@ = Issuer: Bulk GitHub Issue Creator :toc: macro :toclevels: 3 -:this_prod_vrsn: 0.2.1 -:next_prod_vrsn: 0.3.0 -:docker_base_command: docker run -it --rm -v $(pwd):/workdir -e ISSUER_API_TOKEN=$GITHUB_TOKEN docopslab/issuer +:this_prod_vrsn: 0.3.0 +:next_prod_vrsn: 0.4.0 +:docker_base_command: docker run -it --rm --user $(id -u):$(id -g) -v $(pwd):/workdir -e ISSUER_API_TOKEN=$GITHUB_TOKEN docopslab/issuer :append_or_impose: Prepend items with `+` to indicate they should be appended to existing labels. Items without `+` will only be used for issues with no `tags` designated. ifdef::env-github[] :icons: font @@ -15,6 +15,8 @@ ifdef::env-github[] endif::[] +// tag::ai-prompt[] +[[overview]] == Overview _Issuer_ lets you define all your GitHub Issues work tickets in one place as YAML, apply defaults, and post them to GitHub Issues in bulk. @@ -29,6 +31,8 @@ _Issuer_ lets you define all your GitHub Issues work tickets in one place as YAM * *Issue validation* with helpful error messages * *GitHub API integration* +// end::ai-prompt[] + Future plans include extending this capability to *Jira* (link:https://github.com/DocOps/issuer/issues/9[#9]), *GitLab* Issues (link:https://github.com/DocOps/issuer/issues/33[#33]), GitHub *Projects*, and other services. toc::[] @@ -92,15 +96,18 @@ See <> for more. ==== Alias the Docker Command Optionally alias the base Docker command. -In your shell configuration (usually `~/.bashrc` or `~/.zshrc), add the following: +In your shell configuration (usually `~/.bashrc` or `~/.zshrc`), add the following: [.prompt,subs=+attributes] alias issuer='{docker_base_command}' Reload your shell configuration for the alias to take effect: -[.prompt] - source ~/.bashrc +.... +source ~/.bashrc +# or +source ~/.zshrc +.... === For Ruby Users @@ -151,6 +158,7 @@ For Ruby Bundler usage, prepend `bundle exec ` and for un-aliased Docker usage, [.prompt] issuer example.yml +// tag::user-agent-prompt[] [[imyml-format]] === IMYML File Format @@ -329,8 +337,8 @@ Whether to treat the issue as a stub entry, meaning prepend any `$meta.defaults. A source IMYML file is required and can be specified in two ways: -* *Positional argument* (most common): Place the file path immediately after `issuer` -* *Named option*: Use the `--file` option flag to specify the file path +* *Positional argument* (most common). Place the file path immediately after `issuer` +* *Named option.* Use the `--file` option flag to specify the file path Examples: @@ -367,6 +375,11 @@ Whether to treat all issues as stubs, meaning prepend any `$meta.defaults.head` --dry, --dry-run:: Dry-run: print actions but do not post to GitHub. +--json [_FILE_PATH_]:: +Save GitHub API issue payload as JSON file. +Use `--dry` if you want to skip posting while generating JSON. +If no `FILE_PATH` is specified, saves to time-stamped file in `_payloads/` directory. + --auto-versions, --auto-milestones:: Automatically create missing milestones/versions without prompting for confirmation. @@ -382,6 +395,8 @@ Prints the usage screen. --version:: Prints the version of `issuer`. +// end::user-ai-prompt[] + [[authentication]] === Authentication @@ -423,9 +438,10 @@ Issuer automatically logs all API operations for tracking and potential cleanup. By default, logs are stored in a user-wide directory: -* *Linux/macOS*: `~/.config/issuer/logs/` -* *With XDG Base Directory*: `$XDG_CONFIG_HOME/issuer/logs/` -* *Custom location*: Set `ISSUER_CONFIG_DIR` environment variable +[horizontal] +Linux/macOS:: `~/.config/issuer/logs/` +With XDG Base Directory:: `$XDG_CONFIG_HOME/issuer/logs/` +Custom location:: Set `ISSUER_CONFIG_DIR` environment variable Example: [source,bash] @@ -491,6 +507,8 @@ In the end, the only thing that is mainly untouched by me are the rspec tests, w This also explains why the terminal output contains emojis. I will probably make those togglable or configurable in the future. +// tag::dev-ai-prompt[] +[[tests]] === Tests The `specs/` directory contains all specifications, requirements, and tests for the Issuer CLI tool. @@ -535,10 +553,10 @@ bundle exec rspec --pattern "*ops*" The `pr_test` task runs the exact same tests that GitHub Actions runs for pull requests: -* *RSpec Tests*: All unit tests (`bundle exec rake spec`) -* *CLI Tests*: Command-line interface functionality tests -* *YAML Validation*: Validates all example YAML files -* *Documentation Quality*: Vale linting on all documentation files +RSpec Tests:: All unit tests (`bundle exec rake spec`) +CLI Tests:: Command-line interface functionality tests +YAML Validation:: Validates all example YAML files +Documentation Quality:: Vale linting on all documentation files This ensures you can validate your changes locally before pushing to GitHub. @@ -597,39 +615,36 @@ The API reference includes: This documentation is automatically updated with each gem release. -=== Release History Management +// end::dev-ai-prompt[] -As of version 0.2.0, the Release Notes and Changelog are generated using the _ReleaseHx_ tool, which is still in pre-release. -You can find the release history assets in the `docs/releasehx/` directory, which contains a configuration file and the 0.2.0 "`RHYML`" file that was auto-modified and then manually edited to produce the GitHub link:https://github.com/DocOps/issuer/releases[release announcement]. - -ReleaseHx will be available soon, but for now the following is only usable by DocOps Lab. +[[release-history-management]] +=== Release History Management -. Add local releasehx gem to `Gemfile`: -+ -[source,ruby] -gem 'releasehx', path: '../releasehx' +As of version 0.2.0, Release Notes and Changelog are generated using the link:https://github.com/DocOps/releasehx[_ReleaseHx_] tool. +You can find the release history assets in the `docs/releases/` directory, while the ReleaseHx configuration lives at `.config/releasehx.yml`. +The RHYML files are auto-generated and then manually edited to produce the GitHub link:https://github.com/DocOps/issuer/releases[release announcements]. . Draft RHYML draft file from the online GitHub Issues for the given version: + [.prompt,subs=+attributes] - ./releasehx-install.sh && bundle exec rhx {this_prod_vrsn} --yaml docs/releasehx/release-{this_prod_vrsn}.yml --config docs/releasehx/config.yml -+ -The `releasehx-install.sh` ensures ReleaseHx is up to date. + bundle exec rhx {this_prod_vrsn} --yaml docs/releases/{this_prod_vrsn}.yml --config .config/releasehx.yml . Manually edit the RHYML draft file. . Generate the release history as Markdown. + [.prompt,subs=+attributes] - bundle exec rhx docs/releasehx/release-{this_prod_vrsn}.yml --md docs/releasehx/{this_prod_vrsn}.md --config docs/releasehx/config.yml + bundle exec rhx docs/releases/{this_prod_vrsn}.yml --md docs/releases/{this_prod_vrsn}.md --config .config/releasehx.yml -. Copy and paste the contents of `docs/releasehx/{this_prod_vrsn}.md` into the GitHub release form at https://github.com/DocOps/issuer/releases/new. +. Copy and paste the contents of `docs/releases/{this_prod_vrsn}.md` into the GitHub release form at https://github.com/DocOps/issuer/releases/new. [[contributing]] === Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/DocOps/issuer. + +[[legal]] == Legal The gem is available as open source under the terms of the MIT License. diff --git a/Rakefile b/Rakefile index 41200f2..e4237c5 100644 --- a/Rakefile +++ b/Rakefile @@ -1,12 +1,20 @@ require "bundler/gem_tasks" require "rspec/core/rake_task" require "yaml" +require_relative 'lib/issuer/version' -RSpec::Core::RakeTask.new(:spec) do |t| +# Load DocOps Lab development tasks +begin + require 'docopslab/dev' +rescue LoadError + # Skip if not available (e.g., production environment) +end + +RSpec::Core::RakeTask.new(:rspec) do |t| t.pattern = 'specs/tests/rspec/**/*_spec.rb' end -task :default => :spec +task :default => :rspec desc "Setup Vale styles" task :vale_setup do @@ -46,11 +54,39 @@ task :install do sh "bundle install" end +desc "Draft ReleaseHx document" +task :rhx do + # constructs command like GITHUB_TOKEN=$(gh auth token) && bundle exec rhx 0.3.0 --yaml docs/releases/0.3.0.yml --config .config/releasehx.yml --force + # First looks for GITHUB_TOKEN already set, then invokes gh auth token if gh is available + github_token = ENV['GITHUB_TOKEN'] + version = Issuer::VERSION + unless github_token + begin + # check for a valid token or else use gh to refresh the token with export gh auth refresh -s repo + require 'open3' + stdout, stderr, status = Open3.capture3("gh auth token") + if status.success? && !stdout.strip.empty? + github_token = stdout.strip + else + raise "gh auth token failed" + end + rescue + puts "Error: GITHUB_TOKEN not set and 'gh' CLI not available." + exit 1 + end + end + env = { 'GITHUB_TOKEN' => github_token } + command = "bundle exec rhx #{version} --yaml docs/releases/#{version}.yml --config .config/releasehx.yml --force" + Bundler.with_original_env do + sh env, command, verbose: false + end +end + desc "Run all PR tests locally (same as GitHub Actions)" task :pr_test do puts "🔍 Running all PR tests locally..." puts "\n=== RSpec Tests ===" - Rake::Task[:spec].invoke + Rake::Task[:rspec].invoke puts "\n=== CLI Tests ===" Rake::Task[:cli_test].invoke diff --git a/docs/releasehx/config.yml b/docs/releasehx/config.yml deleted file mode 100644 index e0f51dc..0000000 --- a/docs/releasehx/config.yml +++ /dev/null @@ -1,118 +0,0 @@ -# ReleaseHx configuration for DocOps/issuer repository -# Configuration for testing real GitHub data with dynamic labels enabled - -# API configuration for GitHub -api: - from: github - project: DocOps/issuer - href: https://api.github.com/repos/DocOps/issuer/issues - auth: - key_env: RELEASEHX_API_KEY - -# Sources configuration -sources: - note: issue_body - note_pattern: /^## Release Note.*\n\n(?.+)/ms - summ: issue_heading - head: issue_heading - -rhyml: - pasterize_summ: true - -tags: - deprecation: - text: Deprecation - release_note_needed: - slug: needs:note - internal: - text: Internal - -parts: - label_prefix: "part:" - cli: - text: CLI - head: CLI - icon: terminal - imyml: - text: IMYML - head: IMYML - icon: hacker-news - documentation: - text: documentation - head: Documentation - icon: book - api_clients: - text: API Clients - head: API Clients - icon: plug - -types: - removal: - slug: "type:removal" - text: removal - head: Removed - icon: exclamation-triangle - feature: - slug: "type:feature" - text: new feature - head: Added - icon: plus-square-o - bugfix: - slug: "type:bug" - text: bug fix - head: Fixed - icon: bug - improvement: - slug: "type:improvement" - text: improvement - head: Improved - icon: wrench - deprecation: - slug: "type:deprecation" - text: deprecation - head: Deprecated - icon: exclamation-circle - -# Enable dynamic labeling for the changelog section -changelog: - spot: 1 - sort: - - 'breaking:grouping1' - - 'type:grouping1' - items: - frame: basic - show_parts_label: true - show_tags_label: false - show_type_label: false - show_issue_links: true - -# Enable dynamic labeling for the release notes section -notes: - spot: 2 - sort: - - 'part:grouping1' - items: - show_parts_label: true - show_tags_label: true - show_type_label: true - show_lead_label: true - show_issue_links: true - -# Configure the dynamic labels themselves -history: - labeling: - singularize_labels: true - type_label: "Issue Type" - parts_label: "Components" - part_label: "Component" - tags_label: "Labels" - tag_label: "Label" - lead_label: "Contributed by" - join_string: " | " - items: - show_issue_links: true - -links: - web: - href: "https://github.com/DocOps/issuer/issues/{{ tick }}" - text: "#{{ tick }}" diff --git a/docs/releasehx/release-0.2.0.yml b/docs/releases/0.2.0.yml similarity index 100% rename from docs/releasehx/release-0.2.0.yml rename to docs/releases/0.2.0.yml diff --git a/docs/releases/0.3.0.yml b/docs/releases/0.3.0.yml new file mode 100644 index 0000000..e111375 --- /dev/null +++ b/docs/releases/0.3.0.yml @@ -0,0 +1,21 @@ +code: "0.3.0" +date: 2026-01-30 +memo: +changes: + + - chid: 0_3_0-35 + tick: 35 + type: feature + part: cli + summ: | + Added a `--json [PATH]` option for writing locally instead of to remote API + note: | + Added CLI option to save GitHub API issue payloads as JSON files locally instead of posting to GitHub, enabling payload inspection and workflow automation. + lead: briandominick + + - chid: 0_3_0-34 + tick: 34 + type: bug + part: cli + summ: | + Made `issuer help` commandline return help screen instead of error \ No newline at end of file diff --git a/examples/README.adoc b/examples/README.adoc index 3764d5f..97f7f14 100644 --- a/examples/README.adoc +++ b/examples/README.adoc @@ -49,7 +49,7 @@ Test any example with a dry run: Or with the Docker image: - docker run -it --rm -v $(pwd):/workdir docopslab/issuer issuer examples/basic-example.yml --dry + docker run -it --rm --user $(id -u):$(id -g) -v $(pwd):/workdir docopslab/issuer issuer examples/basic-example.yml --dry == Creating Your Own diff --git a/issuer.gemspec b/issuer.gemspec index f8ec533..ec0eb3a 100644 --- a/issuer.gemspec +++ b/issuer.gemspec @@ -31,10 +31,4 @@ Gem::Specification.new do |spec| spec.add_dependency "octokit", "~> 8.0" spec.add_dependency "thor", "~> 1.0" spec.add_dependency "faraday-retry", "~> 2.0" - - # Development dependencies - spec.add_development_dependency "bundler", "~> 2.0" - spec.add_development_dependency "rake", "~> 13.0" - spec.add_development_dependency "rspec", "~> 3.0" - spec.add_development_dependency "asciidoctor", "~> 2.0" end diff --git a/lib/issuer/apis/github/client.rb b/lib/issuer/apis/github/client.rb index 62afb74..b59c405 100644 --- a/lib/issuer/apis/github/client.rb +++ b/lib/issuer/apis/github/client.rb @@ -56,7 +56,7 @@ def create_issue_rest repo, issue_params params[:assignee] = issue_params[:assignee].strip end - # Handle milestone - only if milestone exists + # Handle milestone; only if milestone exists if issue_params[:milestone] milestone = find_milestone(repo, issue_params[:milestone]) params[:milestone] = milestone.number if milestone @@ -259,7 +259,7 @@ def get_repository_id owner, name def resolve_label_ids repo, label_names # For now, we'll skip complex label ID resolution # GitHub GraphQL API requires label IDs, but REST API uses names - # This is a simplification - in practice, you'd need to fetch and match labels + # This is a simplification; in practice, you'd need to fetch and match labels [] end diff --git a/lib/issuer/cache.rb b/lib/issuer/cache.rb index ab0aa9a..16325dc 100644 --- a/lib/issuer/cache.rb +++ b/lib/issuer/cache.rb @@ -142,7 +142,7 @@ def log_artifact run_id, type, artifact_data elsif summary_section.key?(counter_key) summary_section[counter_key] += 1 else - # Fallback - create the key as string + # Fallback; create the key as string summary_section[counter_key] = 1 end diff --git a/lib/issuer/cli.rb b/lib/issuer/cli.rb index 7fe6729..a7c8b97 100644 --- a/lib/issuer/cli.rb +++ b/lib/issuer/cli.rb @@ -18,6 +18,7 @@ def self.exit_on_failure? class_option :tags, type: :string, desc: 'Comma-separated default or appended (+) labels for all issues' class_option :stub, type: :boolean, desc: 'Enable stub mode for all issues' class_option :dry, type: :boolean, default: false, aliases: ['--dry-run'], desc: 'Print issues, don\'t post' + class_option :json, type: :string, lazy_default: '', desc: 'Save API payloads as JSON to PATH (defaults to _payloads/). Combine with --dry to skip posting.' class_option :tokenv, type: :string, desc: 'Name of environment variable containing GitHub token' # Resource automation options @@ -96,8 +97,9 @@ def main file=nil defaults['stub'] = options[:stub] if !options[:stub].nil? # Determine target repository + dry_mode = options[:dry] repo = options[:proj] || meta['proj'] || ENV['ISSUER_REPO'] || ENV['ISSUER_PROJ'] - if repo.nil? && !options[:dry] + if repo.nil? && !dry_mode abort 'No target repo set. Use --proj, $meta.proj, or ENV[ISSUER_REPO].' end @@ -110,6 +112,9 @@ def main file=nil # Apply stub logic with head/tail/body composition issues = Issuer::Ops.apply_stub_logic(issues, defaults) + json_requested = !options[:json].nil? + json_path = options[:json] + # Separate valid and invalid issues valid_issues = issues.select(&:valid?) invalid_issues = issues.reject(&:valid?) @@ -119,8 +124,11 @@ def main file=nil puts "Skipping issue ##{find_original_index(issues, issue) + 1}: #{issue.validation_errors.join(', ')}" end - if options[:dry] - perform_dry_run(valid_issues, repo) + site = nil + + if dry_mode + site = build_dry_run_site + perform_dry_run(valid_issues, repo, site) else # Use Sites architecture for validation and posting site_options = {} @@ -151,7 +159,9 @@ def main file=nil end end - print_summary(valid_issues.length, invalid_issues.length, options[:dry]) + perform_json_output(valid_issues, repo, json_path, site, dry_run: dry_mode) if json_requested + + print_summary(valid_issues.length, invalid_issues.length, dry_mode) end private @@ -175,6 +185,7 @@ def self.show_help Mode Options: --dry, --dry-run #{self.class_options[:dry].description} + --json [PATH] #{self.class_options[:json].description} --auto-versions #{self.class_options[:auto_versions].description} --auto-milestones (alias for --auto-versions) --auto-tags #{self.class_options[:auto_tags].description} @@ -190,6 +201,7 @@ def self.show_help issuer issues.yml --proj myorg/myrepo issuer --file issues.yml --proj myorg/myrepo --dry issuer issues.yml --vrsn 1.1.2 + issuer issues.yml --json dry-output.json issuer --version issuer --help @@ -203,16 +215,11 @@ def find_original_index issues_array, target_issue issues_array.find_index(target_issue) || 0 end - def perform_dry_run issues, repo - # Create site instance for parameter conversion in dry-run mode - site_options = { token: 'dry-run-token' } - site_options[:token_env_var] = options[:tokenv] if options[:tokenv] - site = Issuer::Sites::Factory.create('github', **site_options) - + def perform_dry_run issues, repo, site issues.each do |issue| print_issue_summary(issue, repo, site) end - + # Add project summary at the end if repo project_term = site.field_mappings[:project_name] || 'project' @@ -220,6 +227,64 @@ def perform_dry_run issues, repo end end + def perform_json_output issues, repo, json_path, site, dry_run: + require 'json' + require 'fileutils' + + if json_path.empty? + output_dir = '_payloads' + timestamp = Time.now.strftime('%Y%m%d_%H%M%S') + output_path = File.join(output_dir, "issues_#{timestamp}.json") + else + output_path = json_path + output_dir = File.dirname(output_path) + end + + FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir) + + # Convert issues to API payloads + payloads = issues.map do |issue| + site.convert_issue_to_site_params(issue, repo, dry_run: dry_run) + end + + # Create JSON structure + json_data = { + metadata: { + generated_at: Time.now.iso8601, + repository: repo, + total_issues: issues.length, + issuer_version: Issuer::VERSION + }, + issues: payloads + } + + File.write(output_path, JSON.pretty_generate(json_data)) + + puts "Saved #{issues.length} issue payloads to: #{output_path}" + + puts "\nIssue preview:" + issues.first(3).each do |issue| + print_issue_summary(issue, repo, site) + end + + if issues.length > 3 + puts "... and #{issues.length - 3} more issues" + puts "------\n" + end + + # Add project summary + if repo + project_term = site.field_mappings[:project_name] || 'project' + puts "JSON contains #{issues.length} issue payloads for #{project_term}: #{repo}" + end + end + + def build_dry_run_site + site_options = { token: 'dry-run-token' } + site_options[:token_env_var] = options[:tokenv] if options[:tokenv] + Issuer::Sites::Factory.create('github', **site_options) + end + def print_summary valid_count, invalid_count, dry_run if dry_run puts "\nDry run complete (use without --dry to actually post)" @@ -228,7 +293,7 @@ def print_summary valid_count, invalid_count, dry_run puts "\n✅ Completed: #{valid_count} issues processed, #{invalid_count} skipped" # Note: Run ID is already displayed in the main flow, no need to repeat it here end - + end def print_issue_summary issue, repo, site diff --git a/lib/issuer/issue.rb b/lib/issuer/issue.rb index 4e44491..d410d5d 100644 --- a/lib/issuer/issue.rb +++ b/lib/issuer/issue.rb @@ -78,7 +78,7 @@ def initialize issue_data, defaults={} @type = @raw_data['type'] || defaults['type'] @stub = @raw_data.key?('stub') ? @raw_data['stub'] : defaults['stub'] - # For tags, we need special handling - combine defaults and issue tags for later processing + # For tags, we need special handling; combine defaults and issue tags for later processing defaults_tags = Array(defaults['tags']) issue_tags = Array(@raw_data['tags']) @tags = defaults_tags + issue_tags @@ -401,7 +401,7 @@ def wrap_line_with_indentation line, indent_size # Get terminal width, default to 80 if not available terminal_width = ENV['COLUMNS']&.to_i || 80 - # Calculate available width for content (terminal width - indentation) + # Calculate available width for content (terminal width; indentation) available_width = terminal_width - indent_size # If line fits within available width, just return it with indentation diff --git a/lib/issuer/ops.rb b/lib/issuer/ops.rb index 8a73623..e55c284 100644 --- a/lib/issuer/ops.rb +++ b/lib/issuer/ops.rb @@ -14,7 +14,7 @@ module Ops # This method converts scalar strings to proper issue hashes and creates Issue objects # with enhanced defaults processing. It handles both string and hash inputs gracefully. # - # @param issues_data [Array] Raw issue data - can be strings or hashes + # @param issues_data [Array] Raw issue data; can be strings or hashes # @param defaults [Hash] Default values to apply to all issues # @return [Array] Array of processed Issue objects # diff --git a/lib/issuer/sites/github.rb b/lib/issuer/sites/github.rb index 4bfc315..ddba14d 100644 --- a/lib/issuer/sites/github.rb +++ b/lib/issuer/sites/github.rb @@ -77,7 +77,7 @@ def create_issue proj, issue_params params[:assignee] = issue_params[:assignee].strip end - # Handle milestone - only if milestone exists + # Handle milestone; only if milestone exists if issue_params[:milestone] # If milestone is already a number (from convert_issue_to_site_params), use it directly if issue_params[:milestone].is_a?(Integer) diff --git a/scripts/build.sh b/scripts/build.sh index 0e28061..345334b 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -19,6 +19,7 @@ check_project_root check_git_clean check_main_branch check_bundle_installed +check_docker_available # Run tests run_rspec_tests diff --git a/scripts/lib/build-common.sh b/scripts/lib/build-common.sh index 46f7b1b..d104618 100644 --- a/scripts/lib/build-common.sh +++ b/scripts/lib/build-common.sh @@ -128,22 +128,36 @@ test_built_gem() { gem_file=$(ls pkg/${PROJECT_NAME}-*.gem | sort -V | tail -n1) echo "Testing gem file: $gem_file" - # Create temporary directory for testing - test_dir=$(mktemp -d) - cd "$test_dir" - gem install "../$gem_file" --user-install + # Get expected version from README.adoc + expected_version=$(get_current_version) + echo "Expected version: $expected_version" - if ! command -v $PROJECT_NAME &> /dev/null; then - echo -e "${YELLOW}⚠️ $PROJECT_NAME command not in PATH. Adding gem bin directory...${NC}" - export PATH="$HOME/.local/share/gem/ruby/$(ruby -e 'puts RUBY_VERSION')/bin:$PATH" - fi + # Test gem installation in clean Docker environment + echo "Testing gem installation in clean environment (Docker)..." + docker run --rm -v $(pwd)/pkg:/gems ruby:3.2 bash -c " + gem install /gems/$(basename $gem_file) --no-document > /dev/null 2>&1 + if [ \$? -ne 0 ]; then + echo 'ERROR: Gem installation failed' + exit 1 + fi + + actual_version=\$(${PROJECT_NAME} --version | grep -o '[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+') + echo \"Installed version: \$actual_version\" + + if [ \"\$actual_version\" != \"$expected_version\" ]; then + echo \"ERROR: Version mismatch! Expected: $expected_version, Got: \$actual_version\" + exit 1 + fi + + echo \"SUCCESS: Gem installed and tested successfully\" + " - # Test installed gem - $PROJECT_NAME --version - cd - > /dev/null - rm -rf "$test_dir" + if [ $? -ne 0 ]; then + echo -e "${RED}❌ Gem installation test failed${NC}" + exit 1 + fi - echo -e "${GREEN}✅ Gem built and tested successfully: $gem_file${NC}" + echo -e "${GREEN}✅ Gem built and tested successfully: $gem_file (version $expected_version)${NC}" echo "$gem_file" } diff --git a/specs/tests/README.adoc b/specs/tests/README.adoc index 5b592ea..41c4445 100644 --- a/specs/tests/README.adoc +++ b/specs/tests/README.adoc @@ -27,10 +27,10 @@ The issuer CLI automatically logs all created artifacts (issues, milestones, lab When you run issuer with live operations (not `--dry`): -1. *Run Tracking*: A unique run ID is generated (example: `run_20250711_143022_abcd`) -2. *Artifact Logging*: All created issues, milestones, and labels are logged with metadata -3. *Status Tracking*: Run status is tracked (`in_progress`, `completed`, `failed`) -4. *JSON Storage*: All data is stored in JSON format in `~/.config/issuer/logs/.json` +. *Run Tracking*: A unique run ID is generated (example: `run_20250711_143022_abcd`) +. *Artifact Logging*: All created issues, milestones, and labels are logged with metadata +. *Status Tracking*: Run status is tracked (`in_progress`, `completed`, `failed`) +. *JSON Storage*: All data is stored in JSON format in `~/.config/issuer/logs/.json` === Log Structure @@ -69,9 +69,10 @@ Each run log contains: During test runs, artifacts are automatically logged. This enables: -* *Verification*: Confirm that all expected artifacts were created -* *Debugging*: Track what was created when tests fail -* *Cleanup Planning*: Prepare for future cleanup script development +Verification:: Confirm that all expected artifacts were created +Debugging:: Track what was created when tests fail +Cleanup Planning:: +Prepare for future cleanup script development The test cleanup scripts can read these logs to understand what needs to be cleaned up. @@ -158,22 +159,36 @@ Or run with options: === Core Functionality Tests -* *`01-auth-connection.yml`*: Basic authentication and API connectivity -* *`02-basic-issues.yml`*: Simple issue creation, markdown support, special characters -* *`03-milestone-tests.yml`*: Milestone creation and assignment -* *`04-label-tests.yml`*: Label creation, default/append logic -* *`05-assignment-tests.yml`*: User assignment functionality +`01-auth-connection.yml`:: +Basic authentication and API connectivity + +`02-basic-issues.yml`:: +Simple issue creation, markdown support, special characters + +`03-milestone-tests.yml`:: +Milestone creation and assignment + +`04-label-tests.yml`:: +Label creation, default/append logic + +`05-assignment-tests.yml`:: +User assignment functionality === Advanced Tests -* *`06-automation-tests.yml`*: Automation flags (`--auto-metadata`, `--auto-versions`, etc.) -* *`07-error-tests.yml`*: Error handling scenarios (invalid repo, etc.) -* *`08-complex-tests.yml`*: Complex integration scenarios with multiple features +`06-automation-tests.yml`:: +Automation flags (`--auto-metadata`, `--auto-versions`, etc.) + +`07-error-tests.yml`:: +Error handling scenarios (invalid repo, etc.) + +`08-complex-tests.yml`:: +Complex integration scenarios with multiple features === Configuration -* *`config.yml.example`*: Template configuration file -* *`config.yml`*: Your customized configuration (create from example) +`config.yml.example`:: Template configuration file +`config.yml`:: Your customized configuration (create from example) == Test Runner Features @@ -194,12 +209,12 @@ Options: === Test Categories -1. *Dry-run tests*: Validate IMYML parsing without API calls -2. *Basic functionality*: Core issue creation features -3. *Advanced features*: Milestones, labels, assignments -4. *Automation*: Testing `--auto-*` flags -5. *Error scenarios*: Invalid inputs and error handling -6. *Edge cases*: Unicode, large content, special characters +. *Dry-run tests:* Validate IMYML parsing without API calls +. *Basic functionality:* Core issue creation features +. *Advanced features:* Milestones, labels, assignments +. *Automation:* Testing `--auto-*` flags +. *Error scenarios:* Invalid inputs and error handling +. *Edge cases:* Unicode, large content, special characters == Results and Logging @@ -219,12 +234,13 @@ specs/tests/results/ Tests are considered successful when: -* ✅ All issues are created without errors -* ✅ Milestones are created when needed -* ✅ Labels are created when needed -* ✅ Assignments work correctly -* ✅ Automation flags work as expected -* ✅ Error scenarios fail gracefully +[%interactive] +- [ ] All issues are created without errors +- [ ] Milestones are created when needed +- [ ] Labels are created when needed +- [ ] Assignments work correctly +- [ ] Automation flags work as expected +- [ ] Error scenarios fail gracefully == Cleanup @@ -279,7 +295,7 @@ Run full test suite before releases: === Common Issues -1. *Authentication Errors* +. *Authentication Errors* + ---- GitHub token not found in environment variables @@ -287,7 +303,7 @@ GitHub token not found in environment variables + Solution: Set `GITHUB_TOKEN` or other token environment variables -2. *Repository Access Errors* +. *Repository Access Errors* + ---- Not Found (HTTP 404) @@ -295,7 +311,7 @@ Not Found (HTTP 404) + Solution: Verify repository name and token permissions -3. *Rate Limiting* +. *Rate Limiting* + ---- API rate limit exceeded @@ -303,7 +319,7 @@ API rate limit exceeded + Solution: Wait or use a token with higher rate limits -4. *Permission Errors* +. *Permission Errors* + ---- Resource not accessible by integration @@ -338,11 +354,11 @@ issuer specs/tests/github-api/01-auth-connection.yml === Adding New Tests -1. Create a new `.yml` file in `specs/tests/github-api/` -2. Follow the naming convention: `NN-description.yml` -3. Use `[TEST]` prefix in issue summaries -4. Update the test runner script to include the new test -5. Document expected behavior in issue bodies +. Create a new `.yml` file in `specs/tests/github-api/` +. Follow the naming convention: `NN-description.yml` +. Use `[TEST]` prefix in issue summaries +. Update the test runner script to include the new test +. Document expected behavior in issue bodies === Test File Template @@ -422,30 +438,30 @@ echo "Running GitHub API integration tests..." == Security Considerations -1. *Token Security*: Never commit GitHub tokens to version control -2. *Test Repository*: Use a dedicated test repository, not production repos -3. *Cleanup*: Always clean up test artifacts to avoid clutter -4. *Rate Limits*: Be mindful of GitHub API rate limits during testing -5. *Permissions*: Use tokens with minimal required permissions +. *Token Security*: Never commit GitHub tokens to version control +. *Test Repository*: Use a dedicated test repository, not production repos +. *Cleanup*: Always clean up test artifacts to avoid clutter +. *Rate Limits*: Be mindful of GitHub API rate limits during testing +. *Permissions*: Use tokens with minimal required permissions == Contributing When contributing to the test suite: -1. *Add tests for new features*: Every new feature should have corresponding tests -2. *Update existing tests*: Modify tests when changing functionality -3. *Test edge cases*: Include tests for error conditions and edge cases -4. *Document expectations*: Clearly document what each test validates -5. *Clean up*: Ensure tests can be cleaned up properly +. *Add tests for new features*: Every new feature should have corresponding tests +. *Update existing tests*: Modify tests when changing functionality +. *Test edge cases*: Include tests for error conditions and edge cases +. *Document expectations*: Clearly document what each test validates +. *Clean up*: Ensure tests can be cleaned up properly == Support For issues with the testing suite: -1. Check the troubleshooting section above -2. Review test logs in `specs/tests/results/` -3. Run individual tests manually for debugging -4. Open an issue in the main repository with: - * Test configuration used - * Error messages - * Expected vs actual behavior +. Check the troubleshooting section above +. Review test logs in `specs/tests/results/` +. Run individual tests manually for debugging +. Open an issue in the main repository with: +* Test configuration used +* Error messages +* Expected vs actual behavior diff --git a/specs/tests/rspec/cli_spec.rb b/specs/tests/rspec/cli_spec.rb index 4b89f69..21dda8f 100644 --- a/specs/tests/rspec/cli_spec.rb +++ b/specs/tests/rspec/cli_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative "spec_helper" +require 'stringio' RSpec.describe Issuer::CLI do # Mock the GitHub site methods to avoid actual API calls during testing @@ -210,6 +211,101 @@ ) end + it 'handles --json option with default path' do + # Mock File.write to avoid actually creating files during test + allow(File).to receive(:write) + # Mock FileUtils.mkdir_p to avoid creating directories + allow(FileUtils).to receive(:mkdir_p) + + expect { + cli = Issuer::CLI.new + cli.invoke(:main, [sample_file], {json: '', dry: true}) + }.to output(/Saved 2 issue payloads to: _payloads/).to_stdout + + # Verify File.write was called with JSON content + expect(File).to have_received(:write) do |path, content| + expect(path).to match(/_payloads\/issues_\d{8}_\d{6}\.json/) + json_data = JSON.parse(content) + expect(json_data['metadata']['repository']).to eq('test/repo') + expect(json_data['issues'].length).to eq(2) + end + end + + it 'handles --json option with custom path' do + # Mock File.write to avoid actually creating files during test + allow(File).to receive(:write) + # Mock FileUtils.mkdir_p to avoid creating directories + allow(FileUtils).to receive(:mkdir_p) + + expect { + cli = Issuer::CLI.new + cli.invoke(:main, [sample_file], {json: 'custom-output.json', dry: true}) + }.to output(/Saved 2 issue payloads to: custom-output.json/).to_stdout + + # Verify File.write was called with the custom path + expect(File).to have_received(:write) do |path, content| + expect(path).to eq('custom-output.json') + json_data = JSON.parse(content) + expect(json_data['metadata']['repository']).to eq('test/repo') + expect(json_data['issues'].length).to eq(2) + end + end + + it 'does not auto-enable dry mode when --json is used' do + # Mock File.write to avoid actually creating files during test + allow(File).to receive(:write) + allow(FileUtils).to receive(:mkdir_p) + allow(Issuer::Ops).to receive(:validate_and_prepare_resources) + allow_any_instance_of(Issuer::Sites::GitHub).to receive(:post_issues).and_return(0) + + stdout = StringIO.new + $stdout = stdout + begin + cli = Issuer::CLI.new + cli.invoke(:main, [sample_file], {json: 'test.json'}) + ensure + $stdout = STDOUT + end + + expect(stdout.string).to include('✅ Completed') + expect(stdout.string).not_to include('Dry run complete') + end + + it 'includes correct JSON structure in --json output' do + json_content = nil + + # Capture the JSON content that would be written + allow(File).to receive(:write) do |path, content| + json_content = content + end + allow(FileUtils).to receive(:mkdir_p) + + cli = Issuer::CLI.new + cli.invoke(:main, [sample_file], {json: 'test.json', dry: true}) + + # Parse and verify the JSON structure + json_data = JSON.parse(json_content) + + # Check metadata structure + expect(json_data['metadata']).to include( + 'generated_at', + 'repository', + 'total_issues', + 'issuer_version' + ) + expect(json_data['metadata']['repository']).to eq('test/repo') + expect(json_data['metadata']['total_issues']).to eq(2) + + # Check issues structure + expect(json_data['issues']).to be_an(Array) + expect(json_data['issues'].length).to eq(2) + + # Verify first issue has expected API payload structure + first_issue = json_data['issues'].first + expect(first_issue).to include('title', 'body', 'labels', 'assignee', 'milestone') + expect(first_issue['title']).to eq('Test issue 1') + end + end describe 'version and help' do