issuer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.vale/config/vocabularies/issuer/accept.txt +63 -0
- data/.vale/config/vocabularies/issuer/reject.txt +21 -0
- data/.vale.ini +42 -0
- data/Dockerfile +43 -0
- data/LICENSE +21 -0
- data/README.adoc +539 -0
- data/Rakefile +70 -0
- data/bin/console +0 -0
- data/bin/issuer +13 -0
- data/bin/setup +0 -0
- data/examples/README.adoc +56 -0
- data/examples/advanced-stub-example.yml +50 -0
- data/examples/basic-example.yml +33 -0
- data/examples/minimal-example.yml +9 -0
- data/examples/new-project-issues.yml +162 -0
- data/examples/validation-test.yml +8 -0
- data/exe/issuer +5 -0
- data/issuer.gemspec +43 -0
- data/lib/issuer/apis/github/client.rb +124 -0
- data/lib/issuer/cache.rb +197 -0
- data/lib/issuer/cli.rb +241 -0
- data/lib/issuer/issue.rb +393 -0
- data/lib/issuer/ops.rb +281 -0
- data/lib/issuer/sites/base.rb +109 -0
- data/lib/issuer/sites/factory.rb +31 -0
- data/lib/issuer/sites/github.rb +248 -0
- data/lib/issuer/version.rb +21 -0
- data/lib/issuer.rb +238 -0
- data/scripts/build.sh +40 -0
- data/scripts/lint-docs.sh +64 -0
- data/scripts/manage-runs.rb +175 -0
- data/scripts/pre-commit-template.sh +54 -0
- data/scripts/publish.sh +92 -0
- data/scripts/setup-vale.sh +59 -0
- data/specs/tests/README.adoc +451 -0
- data/specs/tests/check-github-connectivity.sh +130 -0
- data/specs/tests/cleanup-github-tests.sh +374 -0
- data/specs/tests/github-api/01-auth-connection.yml +21 -0
- data/specs/tests/github-api/02-basic-issues.yml +90 -0
- data/specs/tests/github-api/03-milestone-tests.yml +58 -0
- data/specs/tests/github-api/04-label-tests.yml +98 -0
- data/specs/tests/github-api/05-assignment-tests.yml +55 -0
- data/specs/tests/github-api/06-automation-tests.yml +102 -0
- data/specs/tests/github-api/07-error-tests.yml +29 -0
- data/specs/tests/github-api/08-complex-tests.yml +197 -0
- data/specs/tests/github-api/config.yml.example +17 -0
- data/specs/tests/rspec/cli_spec.rb +127 -0
- data/specs/tests/rspec/issue_spec.rb +184 -0
- data/specs/tests/rspec/issuer_spec.rb +5 -0
- data/specs/tests/rspec/ops_spec.rb +124 -0
- data/specs/tests/rspec/spec_helper.rb +54 -0
- data/specs/tests/run-github-api-tests.sh +424 -0
- metadata +200 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
# Advanced example showing stub functionality and tag logic
|
2
|
+
$meta:
|
3
|
+
proj: docops/advanced-project
|
4
|
+
defaults:
|
5
|
+
vrsn: 2.0.0
|
6
|
+
user: team-lead
|
7
|
+
tags: [needs-review, +auto-generated] # + prefix = append to all
|
8
|
+
stub: true
|
9
|
+
head: |
|
10
|
+
## 🤖 Auto-Generated Issue
|
11
|
+
This issue was created by the issuer CLI tool.
|
12
|
+
|
13
|
+
---
|
14
|
+
body: |
|
15
|
+
This is a placeholder. Please update with specific requirements
|
16
|
+
and assign to the appropriate team member.
|
17
|
+
tail: |
|
18
|
+
---
|
19
|
+
|
20
|
+
**Created by**: issuer CLI
|
21
|
+
**Project**: Advanced Features Demo
|
22
|
+
|
23
|
+
issues:
|
24
|
+
# Scalar string issues (get all defaults + stub processing)
|
25
|
+
- Implement advanced search functionality
|
26
|
+
- Add real-time notifications
|
27
|
+
- Create user dashboard
|
28
|
+
|
29
|
+
# Issue with custom content but stub enabled
|
30
|
+
- summ: Database performance optimization
|
31
|
+
stub: true
|
32
|
+
body: |
|
33
|
+
The current database queries are slow for large datasets.
|
34
|
+
Need to implement proper indexing and query optimization.
|
35
|
+
tags: [performance, database]
|
36
|
+
user: db-specialist
|
37
|
+
|
38
|
+
# Issue with stub disabled (no header/footer added)
|
39
|
+
- summ: Critical security vulnerability fix
|
40
|
+
stub: false
|
41
|
+
body: |
|
42
|
+
URGENT: Security vulnerability discovered in authentication module.
|
43
|
+
Immediate fix required.
|
44
|
+
tags: [critical, security, +urgent] # + prefix = append even with explicit tags
|
45
|
+
user: security-team
|
46
|
+
vrsn: 1.9.1
|
47
|
+
|
48
|
+
# Issue with only append tags (gets default tags too)
|
49
|
+
- summ: Code refactoring initiative
|
50
|
+
tags: [+refactoring, +technical-debt]
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Basic example demonstrating core IMYML features
|
2
|
+
$meta:
|
3
|
+
proj: myorg/myproject
|
4
|
+
defaults:
|
5
|
+
vrsn: 1.0.0
|
6
|
+
user: project-manager
|
7
|
+
tags: [enhancement]
|
8
|
+
|
9
|
+
issues:
|
10
|
+
- summ: Add user authentication
|
11
|
+
body: |
|
12
|
+
Implement secure login and registration functionality.
|
13
|
+
|
14
|
+
Requirements:
|
15
|
+
- Password hashing
|
16
|
+
- Session management
|
17
|
+
- Two-factor authentication support
|
18
|
+
tags: [security, authentication]
|
19
|
+
user: backend-dev
|
20
|
+
|
21
|
+
- summ: Fix responsive design issues
|
22
|
+
body: |
|
23
|
+
The current layout breaks on mobile devices.
|
24
|
+
Need to ensure proper responsive behavior.
|
25
|
+
tags: [bug, ui, mobile]
|
26
|
+
user: frontend-dev
|
27
|
+
|
28
|
+
- summ: Create API documentation
|
29
|
+
body: |
|
30
|
+
Generate comprehensive API documentation for developers.
|
31
|
+
Include examples and authentication details.
|
32
|
+
vrsn: 1.1.0
|
33
|
+
tags: [documentation, api]
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# New Project Bootstrap Issues
|
2
|
+
# Common tasks for setting up a new software project
|
3
|
+
|
4
|
+
$meta:
|
5
|
+
proj: myorg/new-project
|
6
|
+
defaults:
|
7
|
+
vrsn: 0.1.0
|
8
|
+
user: project-lead
|
9
|
+
tags: [project-setup, +new-project]
|
10
|
+
stub: true
|
11
|
+
head: |
|
12
|
+
## 🚀 Project Bootstrap Task
|
13
|
+
This is a foundational task for setting up the new project.
|
14
|
+
|
15
|
+
---
|
16
|
+
body: |
|
17
|
+
Please complete this task as part of the initial project setup.
|
18
|
+
Assign to the appropriate team member and update with specific requirements.
|
19
|
+
tail: |
|
20
|
+
---
|
21
|
+
|
22
|
+
**Priority**: Foundation work - should be completed early
|
23
|
+
**Created by**: Project bootstrap automation
|
24
|
+
|
25
|
+
issues:
|
26
|
+
# Repository and Infrastructure Setup
|
27
|
+
- summ: Initialize repository structure
|
28
|
+
stub: false
|
29
|
+
body: |
|
30
|
+
Set up the basic repository structure and initial files.
|
31
|
+
|
32
|
+
**Tasks:**
|
33
|
+
- Create main branch protection rules
|
34
|
+
- Set up .gitignore file
|
35
|
+
- Add initial directory structure
|
36
|
+
- Configure repository settings and permissions
|
37
|
+
tags: [infrastructure, repository]
|
38
|
+
user: devops-lead
|
39
|
+
|
40
|
+
- summ: Add project README and documentation
|
41
|
+
body: |
|
42
|
+
Create comprehensive project documentation.
|
43
|
+
|
44
|
+
**Requirements:**
|
45
|
+
- Project overview and purpose
|
46
|
+
- Installation instructions
|
47
|
+
- Usage examples
|
48
|
+
- Contributing guidelines
|
49
|
+
- API documentation (if applicable)
|
50
|
+
tags: [documentation, +high-priority]
|
51
|
+
user: tech-writer
|
52
|
+
|
53
|
+
- summ: Set up CI/CD pipeline
|
54
|
+
body: |
|
55
|
+
Configure automated testing and deployment.
|
56
|
+
|
57
|
+
**Components:**
|
58
|
+
- GitHub Actions / Jenkins / CircleCI setup
|
59
|
+
- Automated testing on pull requests
|
60
|
+
- Code quality checks (linting, formatting)
|
61
|
+
- Deployment automation for staging/production
|
62
|
+
tags: [infrastructure, ci-cd, automation]
|
63
|
+
user: devops-lead
|
64
|
+
|
65
|
+
# Development Environment
|
66
|
+
- summ: Configure development environment setup
|
67
|
+
body: |
|
68
|
+
Create reproducible development environment.
|
69
|
+
|
70
|
+
**Deliverables:**
|
71
|
+
- Docker configuration or development containers
|
72
|
+
- Local setup instructions
|
73
|
+
- Environment variable documentation
|
74
|
+
- IDE/editor configuration files
|
75
|
+
tags: [development, environment]
|
76
|
+
user: senior-dev
|
77
|
+
|
78
|
+
- summ: Set up code quality tools
|
79
|
+
body: |
|
80
|
+
Implement code quality and consistency tools.
|
81
|
+
|
82
|
+
**Tools to configure:**
|
83
|
+
- Linters (ESLint, RuboCop, Pylint, etc.)
|
84
|
+
- Code formatters (Prettier, Black, etc.)
|
85
|
+
- Pre-commit hooks
|
86
|
+
- Code coverage reporting
|
87
|
+
tags: [development, code-quality]
|
88
|
+
user: senior-dev
|
89
|
+
|
90
|
+
# Testing Infrastructure
|
91
|
+
- summ: Implement testing framework
|
92
|
+
body: |
|
93
|
+
Set up comprehensive testing infrastructure.
|
94
|
+
|
95
|
+
**Test types:**
|
96
|
+
- Unit tests
|
97
|
+
- Integration tests
|
98
|
+
- End-to-end tests (if applicable)
|
99
|
+
- Performance/load tests (if needed)
|
100
|
+
tags: [testing, framework]
|
101
|
+
user: qa-lead
|
102
|
+
|
103
|
+
# Security and Compliance
|
104
|
+
- summ: Configure security scanning and policies
|
105
|
+
body: |
|
106
|
+
Implement security best practices from the start.
|
107
|
+
|
108
|
+
**Security measures:**
|
109
|
+
- Dependency vulnerability scanning
|
110
|
+
- Static code analysis for security issues
|
111
|
+
- Secrets management setup
|
112
|
+
- Security policy documentation
|
113
|
+
tags: [security, compliance, +critical]
|
114
|
+
user: security-team
|
115
|
+
|
116
|
+
# Project Management
|
117
|
+
- summ: Set up project tracking and issue management
|
118
|
+
body: |
|
119
|
+
Configure project management tools and workflows.
|
120
|
+
|
121
|
+
**Setup tasks:**
|
122
|
+
- Issue templates and labels
|
123
|
+
- Project boards/milestones
|
124
|
+
- Sprint planning tools
|
125
|
+
- Team communication channels
|
126
|
+
tags: [project-management, workflow]
|
127
|
+
user: project-manager
|
128
|
+
|
129
|
+
# Basic Implementation Tasks
|
130
|
+
- Create initial project architecture
|
131
|
+
- Implement basic routing/navigation structure
|
132
|
+
- Set up database schema and migrations
|
133
|
+
- Create user authentication system
|
134
|
+
- Implement basic API endpoints
|
135
|
+
- Add logging and monitoring setup
|
136
|
+
|
137
|
+
# Deployment and Operations
|
138
|
+
- summ: Configure staging environment
|
139
|
+
body: |
|
140
|
+
Set up staging environment for testing.
|
141
|
+
|
142
|
+
**Requirements:**
|
143
|
+
- Mirror production configuration
|
144
|
+
- Automated deployment from main branch
|
145
|
+
- Test data seeding
|
146
|
+
- Monitoring and logging
|
147
|
+
tags: [infrastructure, staging, deployment]
|
148
|
+
user: devops-lead
|
149
|
+
vrsn: 0.2.0
|
150
|
+
|
151
|
+
- summ: Prepare production deployment strategy
|
152
|
+
body: |
|
153
|
+
Plan and document production deployment.
|
154
|
+
|
155
|
+
**Planning items:**
|
156
|
+
- Infrastructure requirements
|
157
|
+
- Deployment rollback procedures
|
158
|
+
- Performance monitoring setup
|
159
|
+
- Disaster recovery plan
|
160
|
+
tags: [infrastructure, production, deployment]
|
161
|
+
user: devops-lead
|
162
|
+
vrsn: 1.0.0
|
@@ -0,0 +1,8 @@
|
|
1
|
+
issues:
|
2
|
+
- summ: Test milestone validation
|
3
|
+
desc: This issue has a milestone that probably doesn't exist
|
4
|
+
vrsn: test-milestone-v1.0
|
5
|
+
tags: [test-label-1, existing-bug]
|
6
|
+
- summ: Test label validation
|
7
|
+
desc: This issue has labels that probably don't exist
|
8
|
+
tags: [test-label-2, test-label-3, enhancement]
|
data/exe/issuer
ADDED
data/issuer.gemspec
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative 'lib/issuer/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "issuer"
|
5
|
+
spec.version = Issuer::VERSION
|
6
|
+
spec.authors = ["DocOps Lab"]
|
7
|
+
spec.email = ["codewriter@protonmail.com"]
|
8
|
+
|
9
|
+
spec.summary = "Bulk GitHub issue creator from YAML definitions"
|
10
|
+
spec.description = "CLI tool for creating multiple GitHub issues from a single YAML file (IMYML format). Define all your issues in one place, apply defaults, and post them to GitHub in bulk."
|
11
|
+
spec.homepage = "https://github.com/DocOps/issuer"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = ">= 2.7.0"
|
14
|
+
|
15
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
16
|
+
spec.metadata["source_code_uri"] = "https://github.com/DocOps/issuer"
|
17
|
+
spec.metadata["changelog_uri"] = "https://github.com/DocOps/issuer/blob/main/CHANGELOG.md"
|
18
|
+
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
20
|
+
spec.files = Dir.chdir(__dir__) do
|
21
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
22
|
+
(File.expand_path(f) == __FILE__) ||
|
23
|
+
f.start_with?(*%w[test/ spec/ features/ .git .circleci appveyor Gemfile pkg/]) ||
|
24
|
+
f.match?(/\.gem$/) ||
|
25
|
+
f.match?(/test_.*\.rb$/)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
# Runtime dependencies
|
34
|
+
spec.add_dependency "octokit", "~> 8.0"
|
35
|
+
spec.add_dependency "thor", "~> 1.0"
|
36
|
+
spec.add_dependency "faraday-retry", "~> 2.0"
|
37
|
+
|
38
|
+
# Development dependencies
|
39
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
40
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
41
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
42
|
+
spec.add_development_dependency "asciidoctor", "~> 2.0"
|
43
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'octokit'
|
4
|
+
|
5
|
+
module Issuer
|
6
|
+
module APIs
|
7
|
+
module GitHub
|
8
|
+
class Client
|
9
|
+
def initialize token: nil, token_env_var: nil
|
10
|
+
@token = token || detect_github_token(token_env_var)
|
11
|
+
|
12
|
+
unless @token
|
13
|
+
env_vars = [token_env_var, *default_token_env_vars].compact.uniq
|
14
|
+
raise Issuer::Error, "GitHub token not found. Set #{env_vars.join(', ')} environment variable."
|
15
|
+
end
|
16
|
+
|
17
|
+
@client = Octokit::Client.new(access_token: @token)
|
18
|
+
@client.auto_paginate = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_issue repo, issue_params
|
22
|
+
# Validate required fields
|
23
|
+
unless issue_params[:title] && !issue_params[:title].strip.empty?
|
24
|
+
raise Issuer::Error, "Issue title is required"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Prepare issue creation parameters
|
28
|
+
params = {
|
29
|
+
title: issue_params[:title],
|
30
|
+
body: issue_params[:body] || ''
|
31
|
+
}
|
32
|
+
|
33
|
+
# Handle labels
|
34
|
+
if issue_params[:labels] && !issue_params[:labels].empty?
|
35
|
+
params[:labels] = issue_params[:labels].map(&:strip).reject(&:empty?)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Handle assignee
|
39
|
+
if issue_params[:assignee] && !issue_params[:assignee].strip.empty?
|
40
|
+
params[:assignee] = issue_params[:assignee].strip
|
41
|
+
end
|
42
|
+
|
43
|
+
# Handle milestone - only if milestone exists
|
44
|
+
if issue_params[:milestone]
|
45
|
+
milestone = find_milestone(repo, issue_params[:milestone])
|
46
|
+
params[:milestone] = milestone.number if milestone
|
47
|
+
end
|
48
|
+
|
49
|
+
@client.create_issue(repo, params[:title], params[:body], params)
|
50
|
+
rescue Octokit::Error => e
|
51
|
+
raise Issuer::Error, "GitHub API error: #{e.message}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_milestone repo, milestone_title
|
55
|
+
milestones = @client.milestones(repo, state: 'all')
|
56
|
+
milestones.find { |m| m.title == milestone_title.to_s }
|
57
|
+
rescue Octokit::Error => e
|
58
|
+
raise Issuer::Error, "Error fetching milestones: #{e.message}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def create_milestone repo, title, description: nil
|
62
|
+
@client.create_milestone(repo, title, description: description)
|
63
|
+
rescue Octokit::Error => e
|
64
|
+
raise Issuer::Error, "Error creating milestone '#{title}': #{e.message}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def find_label repo, label_name
|
68
|
+
labels = @client.labels(repo)
|
69
|
+
labels.find { |l| l.name == label_name.to_s }
|
70
|
+
rescue Octokit::Error => e
|
71
|
+
raise Issuer::Error, "Error fetching labels: #{e.message}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def create_label repo, name, color: 'f29513', description: nil
|
75
|
+
@client.add_label(repo, name, color, description: description)
|
76
|
+
rescue Octokit::Error => e
|
77
|
+
raise Issuer::Error, "Error creating label '#{name}': #{e.message}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def get_milestones repo
|
81
|
+
@client.milestones(repo, state: 'all')
|
82
|
+
rescue Octokit::Error => e
|
83
|
+
raise Issuer::Error, "Error fetching milestones: #{e.message}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_labels repo
|
87
|
+
@client.labels(repo)
|
88
|
+
rescue Octokit::Error => e
|
89
|
+
raise Issuer::Error, "Error fetching labels: #{e.message}"
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_connection
|
93
|
+
@client.user
|
94
|
+
true
|
95
|
+
rescue Octokit::Error => e
|
96
|
+
raise Issuer::Error, "GitHub connection test failed: #{e.message}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def rate_limit
|
100
|
+
@client.rate_limit
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def default_token_env_vars
|
106
|
+
%w[ISSUER_API_TOKEN ISSUER_GITHUB_TOKEN GITHUB_ACCESS_TOKEN GITHUB_TOKEN]
|
107
|
+
end
|
108
|
+
|
109
|
+
def detect_github_token(custom_env_var)
|
110
|
+
# Check custom env var first if provided
|
111
|
+
return ENV[custom_env_var] if custom_env_var && ENV[custom_env_var]
|
112
|
+
|
113
|
+
# Fall back to standard env vars
|
114
|
+
default_token_env_vars.each do |env_var|
|
115
|
+
token = ENV[env_var]
|
116
|
+
return token if token
|
117
|
+
end
|
118
|
+
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/issuer/cache.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'json'
|
5
|
+
require 'digest'
|
6
|
+
require 'securerandom'
|
7
|
+
require 'time'
|
8
|
+
|
9
|
+
module Issuer
|
10
|
+
module Cache
|
11
|
+
module_function
|
12
|
+
|
13
|
+
# Directory and file management
|
14
|
+
def cache_dir
|
15
|
+
if ENV['ISSUER_CONFIG_DIR']
|
16
|
+
File.expand_path(ENV['ISSUER_CONFIG_DIR'])
|
17
|
+
elsif ENV['XDG_CONFIG_HOME']
|
18
|
+
File.join(ENV['XDG_CONFIG_HOME'], 'issuer')
|
19
|
+
else
|
20
|
+
File.expand_path('~/.config/issuer')
|
21
|
+
end
|
22
|
+
rescue ArgumentError
|
23
|
+
# Fallback if home directory issues
|
24
|
+
File.expand_path('.issuer', Dir.pwd)
|
25
|
+
end
|
26
|
+
|
27
|
+
def logs_dir
|
28
|
+
File.join(cache_dir, 'logs')
|
29
|
+
end
|
30
|
+
|
31
|
+
def ensure_cache_directories
|
32
|
+
FileUtils.mkdir_p(logs_dir) unless Dir.exist?(logs_dir)
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_log_file(run_id)
|
36
|
+
File.join(logs_dir, "#{run_id}.json")
|
37
|
+
end
|
38
|
+
|
39
|
+
def generate_run_id
|
40
|
+
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
|
41
|
+
random_suffix = SecureRandom.hex(4)
|
42
|
+
"run_#{timestamp}_#{random_suffix}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Run tracking
|
46
|
+
def start_run(metadata = {})
|
47
|
+
ensure_cache_directories
|
48
|
+
|
49
|
+
run_id = generate_run_id
|
50
|
+
run_data = {
|
51
|
+
run_id: run_id,
|
52
|
+
started_at: Time.now.iso8601,
|
53
|
+
status: 'in_progress',
|
54
|
+
metadata: metadata,
|
55
|
+
artifacts: {
|
56
|
+
issues: [],
|
57
|
+
milestones: [],
|
58
|
+
labels: []
|
59
|
+
},
|
60
|
+
summary: {
|
61
|
+
issues_created: 0,
|
62
|
+
milestones_created: 0,
|
63
|
+
labels_created: 0
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
save_run_log(run_id, run_data)
|
68
|
+
run_id
|
69
|
+
end
|
70
|
+
|
71
|
+
def complete_run(run_id, issues_processed = nil)
|
72
|
+
run_data = load_run_log(run_id)
|
73
|
+
return unless run_data
|
74
|
+
|
75
|
+
# Handle both symbol and string keys
|
76
|
+
completed_key = run_data.key?(:completed_at) ? :completed_at : 'completed_at'
|
77
|
+
status_key = run_data.key?(:status) ? :status : 'status'
|
78
|
+
|
79
|
+
run_data[completed_key] = Time.now.iso8601
|
80
|
+
run_data[status_key] = 'completed'
|
81
|
+
|
82
|
+
# Update issues processed if provided
|
83
|
+
if issues_processed
|
84
|
+
summary_key = run_data.key?(:summary) ? :summary : 'summary'
|
85
|
+
processed_key = 'issues_processed'
|
86
|
+
run_data[summary_key][processed_key] = issues_processed
|
87
|
+
end
|
88
|
+
|
89
|
+
save_run_log(run_id, run_data)
|
90
|
+
end
|
91
|
+
|
92
|
+
def fail_run(run_id, error_message)
|
93
|
+
run_data = load_run_log(run_id)
|
94
|
+
return unless run_data
|
95
|
+
|
96
|
+
# Handle both symbol and string keys
|
97
|
+
failed_key = run_data.key?(:failed_at) ? :failed_at : 'failed_at'
|
98
|
+
status_key = run_data.key?(:status) ? :status : 'status'
|
99
|
+
error_key = run_data.key?(:error) ? :error : 'error'
|
100
|
+
|
101
|
+
run_data[failed_key] = Time.now.iso8601
|
102
|
+
run_data[status_key] = 'failed'
|
103
|
+
run_data[error_key] = error_message
|
104
|
+
save_run_log(run_id, run_data)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Artifact tracking
|
108
|
+
def log_issue_created(run_id, issue_data)
|
109
|
+
log_artifact(run_id, :issues, issue_data)
|
110
|
+
end
|
111
|
+
|
112
|
+
def log_milestone_created(run_id, milestone_data)
|
113
|
+
log_artifact(run_id, :milestones, milestone_data)
|
114
|
+
end
|
115
|
+
|
116
|
+
def log_label_created(run_id, label_data)
|
117
|
+
log_artifact(run_id, :labels, label_data)
|
118
|
+
end
|
119
|
+
|
120
|
+
def log_artifact(run_id, type, artifact_data)
|
121
|
+
run_data = load_run_log(run_id)
|
122
|
+
return unless run_data
|
123
|
+
|
124
|
+
# Handle both symbol and string keys (since JSON loading converts symbols to strings)
|
125
|
+
artifacts_key = run_data.key?(:artifacts) ? :artifacts : 'artifacts'
|
126
|
+
summary_key = run_data.key?(:summary) ? :summary : 'summary'
|
127
|
+
type_key = run_data[artifacts_key].key?(type) ? type : type.to_s
|
128
|
+
|
129
|
+
run_data[artifacts_key][type_key] << artifact_data
|
130
|
+
|
131
|
+
# Increment the appropriate counter (use proper plural-to-singular mapping)
|
132
|
+
counter_key = case type.to_s
|
133
|
+
when 'issues' then 'issues_created'
|
134
|
+
when 'milestones' then 'milestones_created'
|
135
|
+
when 'labels' then 'labels_created'
|
136
|
+
else "#{type}_created"
|
137
|
+
end
|
138
|
+
|
139
|
+
summary_section = run_data[summary_key]
|
140
|
+
if summary_section.key?(counter_key.to_sym)
|
141
|
+
summary_section[counter_key.to_sym] += 1
|
142
|
+
elsif summary_section.key?(counter_key)
|
143
|
+
summary_section[counter_key] += 1
|
144
|
+
else
|
145
|
+
# Fallback - create the key as string
|
146
|
+
summary_section[counter_key] = 1
|
147
|
+
end
|
148
|
+
|
149
|
+
save_run_log(run_id, run_data)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Data persistence
|
153
|
+
def save_run_log(run_id, data)
|
154
|
+
File.write(run_log_file(run_id), JSON.pretty_generate(data))
|
155
|
+
end
|
156
|
+
|
157
|
+
def load_run_log(run_id)
|
158
|
+
log_file = run_log_file(run_id)
|
159
|
+
return nil unless File.exist?(log_file)
|
160
|
+
|
161
|
+
JSON.parse(File.read(log_file), symbolize_names: true)
|
162
|
+
rescue JSON::ParserError => e
|
163
|
+
puts "⚠️ Warning: Could not parse run log file #{log_file}: #{e.message}"
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
|
167
|
+
# Query and listing
|
168
|
+
def list_runs(status: nil, limit: nil)
|
169
|
+
ensure_cache_directories
|
170
|
+
|
171
|
+
log_files = Dir.glob(File.join(logs_dir, '*.json'))
|
172
|
+
.sort_by { |f| File.mtime(f) }
|
173
|
+
.reverse
|
174
|
+
|
175
|
+
runs = log_files.map do |file|
|
176
|
+
begin
|
177
|
+
data = JSON.parse(File.read(file), symbolize_names: true)
|
178
|
+
next if status && data[:status] != status.to_s
|
179
|
+
data
|
180
|
+
rescue JSON::ParserError
|
181
|
+
nil
|
182
|
+
end
|
183
|
+
end.compact
|
184
|
+
|
185
|
+
limit ? runs.take(limit) : runs
|
186
|
+
end
|
187
|
+
|
188
|
+
def get_run(run_id)
|
189
|
+
load_run_log(run_id)
|
190
|
+
end
|
191
|
+
|
192
|
+
def delete_run_log(run_id)
|
193
|
+
log_file = run_log_file(run_id)
|
194
|
+
File.delete(log_file) if File.exist?(log_file)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|