git-markdown 0.1.0 → 0.1.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8414cb82426c583ac747480c7fd996b39f35c2758356884881667a57df54efe3
4
- data.tar.gz: ae38f4bf979c40750003cbda6cf65548650c189f929bb33d75e268fe8dcb8e40
3
+ metadata.gz: 06b5aeed8786c57bb26b159375e7dcbb449e12b2ef4068f5aade847e1ffb809b
4
+ data.tar.gz: 3c60e2b038a66ca27183e27c54efba8bc05a0cd0395f3c9ce6f735e401840702
5
5
  SHA512:
6
- metadata.gz: f4e5fe13c671fc6e7934e3bbbfc28f8c3853f0a3c5f04878b8605c1dbf293c01297eda5d8078d966be2fa86b2be6af0a97dd118e78ecd744c031d39b82ca59a2
7
- data.tar.gz: 0ec5b02ec1d5a10fd981ef8a1c8732df476a10942dc132d57cdb2e8792c21af5c4f3c9089d92b53d03b9ff2b237c99e9ac899d527e6d1be6bffc266f1f5f23d5
6
+ metadata.gz: d5156133e179f31b2271f67caed36f9d90dfc0404cb25134097ae6a208861249959f6a3c4aefa7f1f7649d5f1ab25db11fb352de6bd2c11df8962139b2d2c327
7
+ data.tar.gz: cbd01023d035fea4d1f82759e8d829d645ddbcbbf40fa8d5acaf5eff1a777861a9566d2c13dd078efe1abbe3995abd00b2d1e6ba8d9c3f2e364eebf5ae0aae2d
data/CHANGELOG.md CHANGED
@@ -5,27 +5,9 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [0.1.0] - 2026-02-08
8
+ ## [0.1.8] - 2026-03-20
9
9
 
10
10
  ### <!-- 0 -->🚀 Features
11
- - initialize gem structure with gemspec
12
-
13
-
14
- ### <!-- 1 -->🐛 Bug Fixes
15
- - add missing yaml require and improve setup UX
16
- - hide negative flags in Thor CLI and add local install docs
17
- - fix git-cliff configuration
18
-
19
-
20
- ### <!-- 3 -->📚 Documentation
21
- - fix README URLs and expand About section
22
-
23
-
24
- ### <!-- 7 -->⚙️ Miscellaneous Tasks
25
- - update gemspec and README with proper metadata
26
- - streamline release prep workflow
27
- - add git hooks and ignore build artifacts
28
- - update CI ruby matrix and gitignore
29
- - update CI ruby matrix and gitignore
11
+ - filter GitHub-native resolved comments via GraphQL
30
12
 
31
13
 
data/Rakefile CHANGED
@@ -16,33 +16,74 @@ end
16
16
  task default: %i[test standard]
17
17
 
18
18
  namespace :release do
19
- desc "Update changelog, commit, and tag"
20
- task :prep do
21
- version = GitMarkdown::VERSION
22
- branch = `git rev-parse --abbrev-ref HEAD`.strip
19
+ desc "Full release: update changelog, bump version, commit, tag, and push"
20
+ task :bump, [:level] do |_t, args|
21
+ level = args[:level] || "patch"
22
+ valid_levels = %w[major minor patch pre]
23
23
 
24
- if branch == "HEAD"
25
- abort "Release prep requires a branch (not detached HEAD)."
24
+ unless valid_levels.include?(level)
25
+ abort "Invalid level: #{level}. Use: #{valid_levels.join(", ")}"
26
26
  end
27
27
 
28
+ branch = `git rev-parse --abbrev-ref HEAD`.strip
28
29
  unless ["main", "master"].include?(branch)
29
- abort "Release prep must run on main or master. Current: #{branch}."
30
+ abort "Release must run on main or master. Current: #{branch}."
30
31
  end
31
32
 
32
33
  unless system("git diff --quiet") && system("git diff --cached --quiet")
33
- abort "Release prep requires a clean working tree."
34
+ abort "Release requires a clean working tree."
35
+ end
36
+
37
+ current = GitMarkdown::VERSION
38
+ parts = current.split(".").map(&:to_i)
39
+
40
+ next_version = case level
41
+ when "major"
42
+ "#{parts[0] + 1}.0.0"
43
+ when "minor"
44
+ "#{parts[0]}.#{parts[1] + 1}.0"
45
+ when "patch"
46
+ "#{parts[0]}.#{parts[1]}.#{parts[2] + 1}"
47
+ when "pre"
48
+ "#{parts[0]}.#{parts[1]}.#{parts[2]}.pre.1"
34
49
  end
35
50
 
36
- sh "git cliff -c cliff.toml --unreleased --tag v#{version} -o CHANGELOG.md"
51
+ puts "=== Step 1: Updating CHANGELOG.md for v#{next_version} ==="
52
+ sh "git cliff -c cliff.toml --unreleased --tag v#{next_version} -o CHANGELOG.md"
53
+
37
54
  if system("git diff --quiet -- CHANGELOG.md")
38
- puts "No changelog changes. Skipping release prep."
39
- next
55
+ puts "Warning: No changelog changes detected"
56
+ else
57
+ puts "✓ Changelog updated"
40
58
  end
41
59
 
42
- sh "git add CHANGELOG.md"
43
- sh "git commit -m \"docs: update changelog for v#{version}\""
44
- sh "bundle exec gem tag -v #{version}"
45
- sh "git push"
46
- sh "git push origin v#{version}"
60
+ puts "\n=== Step 2: Bumping version to #{next_version} ==="
61
+
62
+ File.write(
63
+ "lib/git/markdown/version.rb",
64
+ <<~RUBY
65
+ # frozen_string_literal: true
66
+
67
+ module GitMarkdown
68
+ VERSION = "#{next_version}"
69
+ end
70
+ RUBY
71
+ )
72
+
73
+ puts "✓ Version updated in lib/git/markdown/version.rb"
74
+
75
+ puts "\n=== Step 3: Committing changes ==="
76
+ sh "git add CHANGELOG.md lib/git/markdown/version.rb"
77
+ sh "git commit -m \"chore(release): bump version to #{next_version}\""
78
+ puts "✓ Changes committed"
79
+
80
+ puts "\n=== Step 4: Creating and pushing tag ==="
81
+ sh "git tag -a v#{next_version} -m \"Release v#{next_version}\""
82
+ sh "git push origin master"
83
+ sh "git push origin v#{next_version}"
84
+ puts "✓ Tag v#{next_version} created and pushed"
85
+
86
+ puts "\n✅ Release v#{next_version} prepared successfully!"
87
+ puts "GitHub Actions will now build and publish the release."
47
88
  end
48
89
  end
data/git-markdown.gemspec CHANGED
@@ -1,19 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "lib/git/markdown/version"
4
+
3
5
  Gem::Specification.new do |spec|
4
6
  spec.name = "git-markdown"
5
- spec.version = "0.1.0"
6
- spec.authors = ["ethos-link"]
7
+ spec.version = GitMarkdown::VERSION
8
+ spec.authors = ["Paulo Fidalgo", "Ethos Link"]
7
9
 
8
10
  spec.summary = "Convert GitHub PRs to Markdown for local AI code review"
9
11
  spec.description = "A CLI tool that fetches GitHub pull requests and converts them to Markdown format, perfect for local AI assistants like opencode and codex."
10
- spec.homepage = "https://github.com/ethos-link/git-markdown"
12
+ spec.homepage = "https://www.ethos-link.com/opensource/git-markdown"
11
13
  spec.license = "MIT"
12
14
  spec.required_ruby_version = ">= 3.0.0"
13
15
 
14
- spec.metadata["homepage_uri"] = spec.homepage
15
- spec.metadata["source_code_uri"] = spec.homepage
16
- spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
16
+ repo = "https://github.com/ethos-link/git-markdown"
17
+ branch = "master"
18
+
19
+ spec.metadata = {
20
+ "homepage_uri" => spec.homepage,
21
+ "source_code_uri" => repo,
22
+ "bug_tracker_uri" => "#{repo}/issues",
23
+ "changelog_uri" => "#{repo}/blob/#{branch}/CHANGELOG.md",
24
+ "documentation_uri" => "#{repo}/blob/#{branch}/README.md",
25
+ "funding_uri" => "https://www.reviato.com/",
26
+ "rubygems_mfa_required" => "true"
27
+ }
17
28
 
18
29
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
19
30
  `git ls-files -z`.split("\x0").reject do |f|
@@ -17,6 +17,17 @@ module GitMarkdown
17
17
  Response.new(response)
18
18
  end
19
19
 
20
+ def post(path, body = {})
21
+ uri = build_uri(path, {})
22
+ request = Net::HTTP::Post.new(uri)
23
+ set_headers(request)
24
+ request["Content-Type"] = "application/json"
25
+ request.body = JSON.generate(body)
26
+
27
+ response = http_request(uri, request)
28
+ Response.new(response)
29
+ end
30
+
20
31
  private
21
32
 
22
33
  def build_uri(path, params)
@@ -7,13 +7,14 @@ module GitMarkdown
7
7
  XDG_CONFIG_HOME = ENV.fetch("XDG_CONFIG_HOME", File.expand_path("~/.config"))
8
8
  DEFAULT_PROVIDER = :github
9
9
 
10
- attr_accessor :token, :provider, :api_url, :output_dir, :default_status
10
+ attr_accessor :token, :provider, :api_url, :graphql_url, :output_dir, :default_status
11
11
 
12
12
  def initialize
13
13
  @provider = DEFAULT_PROVIDER
14
14
  @output_dir = Dir.pwd
15
15
  @default_status = :unresolved
16
16
  @api_url = nil
17
+ @graphql_url = nil
17
18
  end
18
19
 
19
20
  def self.load
@@ -24,6 +25,7 @@ module GitMarkdown
24
25
  load_from_file if config_file_exist?
25
26
  resolve_credentials
26
27
  resolve_api_url if api_url.nil?
28
+ resolve_graphql_url if graphql_url.nil?
27
29
  self
28
30
  end
29
31
 
@@ -67,6 +69,7 @@ module GitMarkdown
67
69
  )
68
70
  @provider = config[:provider] if config[:provider]
69
71
  @api_url = config[:api_url] if config[:api_url]
72
+ @graphql_url = config[:graphql_url] if config[:graphql_url]
70
73
  @output_dir = config[:output_dir] if config[:output_dir]
71
74
  @default_status = config[:default_status].to_sym if config[:default_status]
72
75
  end
@@ -81,10 +84,17 @@ module GitMarkdown
81
84
  end
82
85
  end
83
86
 
87
+ def resolve_graphql_url
88
+ @graphql_url = ENV.fetch("GITHUB_GRAPHQL_URL") do
89
+ (@provider == :github) ? "https://api.github.com/graphql" : nil
90
+ end
91
+ end
92
+
84
93
  def config_to_yaml
85
94
  {
86
95
  provider: @provider,
87
96
  api_url: @api_url,
97
+ graphql_url: @graphql_url,
88
98
  output_dir: @output_dir,
89
99
  default_status: @default_status
90
100
  }.to_yaml
@@ -50,9 +50,9 @@ module GitMarkdown
50
50
  def include_comment?(comment)
51
51
  case @status_filter
52
52
  when :unresolved
53
- !comment.body.include?("[resolved]") && !comment.body.include?("[done]")
53
+ !comment.resolved? && !comment.body.include?("[resolved]") && !comment.body.include?("[done]")
54
54
  when :resolved
55
- comment.body.include?("[resolved]") || comment.body.include?("[done]")
55
+ comment.resolved? || comment.body.include?("[resolved]") || comment.body.include?("[done]")
56
56
  else
57
57
  true
58
58
  end
@@ -4,6 +4,7 @@ module GitMarkdown
4
4
  module Models
5
5
  class Comment
6
6
  attr_reader :id, :body, :author, :path, :line, :html_url, :created_at, :updated_at, :in_reply_to_id
7
+ attr_accessor :resolved
7
8
 
8
9
  def initialize(attrs = {})
9
10
  @id = attrs[:id]
@@ -15,6 +16,7 @@ module GitMarkdown
15
16
  @created_at = attrs[:created_at]
16
17
  @updated_at = attrs[:updated_at]
17
18
  @in_reply_to_id = attrs[:in_reply_to_id]
19
+ @resolved = attrs.fetch(:resolved, false)
18
20
  end
19
21
 
20
22
  def self.from_api(data)
@@ -38,6 +40,10 @@ module GitMarkdown
38
40
  def reply?
39
41
  !@in_reply_to_id.nil?
40
42
  end
43
+
44
+ def resolved?
45
+ @resolved == true
46
+ end
41
47
  end
42
48
  end
43
49
  end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitMarkdown
4
+ module Providers
5
+ class GitHub
6
+ class Graphql
7
+ RESOLVED_STATE_QUERY = <<~GRAPHQL
8
+ query($owner: String!, $repo: String!, $number: Int!, $threadsAfter: String, $commentsAfter: String) {
9
+ repository(owner: $owner, name: $repo) {
10
+ pullRequest(number: $number) {
11
+ reviewThreads(first: 100, after: $threadsAfter) {
12
+ pageInfo {
13
+ hasNextPage
14
+ endCursor
15
+ }
16
+ nodes {
17
+ isResolved
18
+ comments(first: 100, after: $commentsAfter) {
19
+ pageInfo {
20
+ hasNextPage
21
+ endCursor
22
+ }
23
+ nodes {
24
+ databaseId
25
+ body
26
+ path
27
+ line
28
+ createdAt
29
+ updatedAt
30
+ author {
31
+ login
32
+ }
33
+ replyTo {
34
+ databaseId
35
+ }
36
+ url
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
44
+ GRAPHQL
45
+
46
+ def initialize(config)
47
+ @config = config
48
+ end
49
+
50
+ def fetch_resolved_states(owner, repo, number)
51
+ resolved_ids = Set.new
52
+ threads_after = nil
53
+
54
+ loop do
55
+ response = client.post("", {
56
+ query: RESOLVED_STATE_QUERY,
57
+ variables: {owner: owner, repo: repo, number: number,
58
+ threadsAfter: threads_after}
59
+ })
60
+
61
+ raise ApiError, "GraphQL request failed: #{response.error_message}" unless response.success?
62
+
63
+ data = response.data
64
+ threads_data = data.dig("data", "repository", "pullRequest", "reviewThreads")
65
+ break unless threads_data
66
+
67
+ threads_data["nodes"].each do |thread|
68
+ next unless thread["isResolved"]
69
+
70
+ thread["comments"]["nodes"].each do |comment|
71
+ resolved_ids.add(comment["databaseId"]) if comment["databaseId"]
72
+ end
73
+ end
74
+
75
+ threads_page = threads_data["pageInfo"]
76
+ break unless threads_page["hasNextPage"]
77
+
78
+ threads_after = threads_page["endCursor"]
79
+ end
80
+
81
+ resolved_ids
82
+ end
83
+
84
+ private
85
+
86
+ def client
87
+ @client ||= Api::Client.new(
88
+ base_url: @config.graphql_url,
89
+ token: @config.token
90
+ )
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -21,23 +21,36 @@ module GitMarkdown
21
21
  end
22
22
 
23
23
  def fetch_reviews(owner, repo, number)
24
+ resolved_ids = fetch_resolved_comment_ids(owner, repo, number)
25
+
24
26
  path = "/repos/#{owner}/#{repo}/pulls/#{number}/reviews"
25
27
  reviews = fetch_all_pages(path)
26
28
 
29
+ all_pr_comments = fetch_all_pr_comments(owner, repo, number)
30
+
27
31
  reviews.map do |review_data|
28
32
  review = Models::Review.from_api(review_data)
29
- review.comments = fetch_review_comments(owner, repo, review.id)
33
+ review.comments = all_pr_comments
34
+ .select { |c| c["pull_request_review_id"] == review.id }
35
+ .map do |data|
36
+ comment = Models::Comment.from_api(data)
37
+ comment.resolved = resolved_ids.include?(comment.id)
38
+ comment
39
+ end
30
40
  review
31
41
  end
32
42
  end
33
43
 
34
- def fetch_review_comments(owner, repo, review_id)
35
- path = "/repos/#{owner}/#{repo}/pulls/comments"
36
- all_comments = fetch_all_pages(path)
44
+ def fetch_review_comments(owner, repo, review_id, resolved_ids = Set.new)
45
+ all_pr_comments = fetch_all_pr_comments(owner, repo)
37
46
 
38
- all_comments
47
+ all_pr_comments
39
48
  .select { |c| c["pull_request_review_id"] == review_id }
40
- .map { |data| Models::Comment.from_api(data) }
49
+ .map do |data|
50
+ comment = Models::Comment.from_api(data)
51
+ comment.resolved = resolved_ids.include?(comment.id)
52
+ comment
53
+ end
41
54
  end
42
55
 
43
56
  private
@@ -49,6 +62,22 @@ module GitMarkdown
49
62
  )
50
63
  end
51
64
 
65
+ def graphql_client
66
+ @graphql_client ||= Graphql.new(@config)
67
+ end
68
+
69
+ def fetch_resolved_comment_ids(owner, repo, number)
70
+ graphql_client.fetch_resolved_states(owner, repo, number)
71
+ rescue ApiError
72
+ Set.new
73
+ end
74
+
75
+ def fetch_all_pr_comments(owner, repo, _number = nil)
76
+ @all_pr_comments ||= {}
77
+ key = "#{owner}/#{repo}"
78
+ @all_pr_comments[key] ||= fetch_all_pages("/repos/#{owner}/#{repo}/pulls/comments")
79
+ end
80
+
52
81
  def fetch_all_pages(path, params = {})
53
82
  results = []
54
83
  page = 1
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GitMarkdown
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.8"
5
5
  end
data/lib/git_markdown.rb CHANGED
@@ -15,6 +15,7 @@ require_relative "git/markdown/credentials"
15
15
  require_relative "git/markdown/remote_parser"
16
16
  require_relative "git/markdown/providers/base"
17
17
  require_relative "git/markdown/providers/github"
18
+ require_relative "git/markdown/providers/github/graphql"
18
19
  require_relative "git/markdown/api/client"
19
20
  require_relative "git/markdown/api/response"
20
21
  require_relative "git/markdown/models/pull_request"
metadata CHANGED
@@ -1,10 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-markdown
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
- - ethos-link
7
+ - Paulo Fidalgo
8
+ - Ethos Link
8
9
  bindir: bin
9
10
  cert_chain: []
10
11
  date: 1980-01-02 00:00:00.000000000 Z
@@ -122,16 +123,21 @@ files:
122
123
  - lib/git/markdown/models/review.rb
123
124
  - lib/git/markdown/providers/base.rb
124
125
  - lib/git/markdown/providers/github.rb
126
+ - lib/git/markdown/providers/github/graphql.rb
125
127
  - lib/git/markdown/remote_parser.rb
126
128
  - lib/git/markdown/version.rb
127
129
  - lib/git_markdown.rb
128
- homepage: https://github.com/ethos-link/git-markdown
130
+ homepage: https://www.ethos-link.com/opensource/git-markdown
129
131
  licenses:
130
132
  - MIT
131
133
  metadata:
132
- homepage_uri: https://github.com/ethos-link/git-markdown
134
+ homepage_uri: https://www.ethos-link.com/opensource/git-markdown
133
135
  source_code_uri: https://github.com/ethos-link/git-markdown
134
- changelog_uri: https://github.com/ethos-link/git-markdown/blob/main/CHANGELOG.md
136
+ bug_tracker_uri: https://github.com/ethos-link/git-markdown/issues
137
+ changelog_uri: https://github.com/ethos-link/git-markdown/blob/master/CHANGELOG.md
138
+ documentation_uri: https://github.com/ethos-link/git-markdown/blob/master/README.md
139
+ funding_uri: https://www.reviato.com/
140
+ rubygems_mfa_required: 'true'
135
141
  rdoc_options: []
136
142
  require_paths:
137
143
  - lib
@@ -146,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
146
152
  - !ruby/object:Gem::Version
147
153
  version: '0'
148
154
  requirements: []
149
- rubygems_version: 3.6.9
155
+ rubygems_version: 4.0.6
150
156
  specification_version: 4
151
157
  summary: Convert GitHub PRs to Markdown for local AI code review
152
158
  test_files: []