kk-git 0.2.6 → 0.2.9

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: ae39abac1e3638de958917ebc2fcbbffdace00c23e13fdd53929cec3e416930f
4
- data.tar.gz: eb7191f382ae86ff2c25aac1ce6f7af7a1288be5a25c503641416bcb321ba234
3
+ metadata.gz: cf707ff92a149936ea729095e66789b1af70a80a5519a1b90f262ac9f9bfeaf5
4
+ data.tar.gz: 16527f8e79675fde491dfad46bc3bf853f43755c837364c905bf7e1839fef542
5
5
  SHA512:
6
- metadata.gz: df09df41cb4383f3654dc161b33703798e069cd16be48dd160162076eca1d03417c7272bb6dc80cdfb7a06c2a46e8a8029597e94c40e6fe5d79e5be9a2f1c6a4
7
- data.tar.gz: a52d189d3bf6606506e9fb06f3ff8289926d9f62864e9a2e3628a0657813a009e32840cfda0b2d4e414f3a8b768e940211566f247c06a998db4c9732d29471a9
6
+ metadata.gz: 47fe99e7a65c3e9eed0f3e6d2fb56ec47dfe2ab77daf775a8706c598e7ed20b7857adaa6256a122c28924fb677954cbf60d3eb6311f319456c914b93d1f69ce2
7
+ data.tar.gz: fb76a7efc708d33ec57b9309db3b79a278b146a47df37159f4acdb39ff7f3920fa14996c9c70b24ed7a68e238d6170953ccd9886b5085b0c5d6cef522cc7d01e
data/exe/kk-git CHANGED
@@ -165,7 +165,7 @@ def main_help
165
165
  KK_GIT_SKIP_PUSH=1 skip push step
166
166
  KK_GIT_AMEND=1 amend last commit instead of creating new one
167
167
  KK_GIT_CONFIRM=1 print message and require KK_GIT_YES=1
168
- KK_GIT_ALLOW_SENSITIVE=1 allow committing .env/credentials paths
168
+ KK_GIT_BLOCK_SENSITIVE=1 block committing .env/credentials paths (off by default)
169
169
  KK_GIT_DEFAULT_TYPE default type for code edits (default: chore)
170
170
  KK_GIT_PUSH_URL push to explicit URL (bypass SSH remote)
171
171
  KK_GIT_PULL_URL pull from explicit URL (bypass SSH remote)
@@ -52,9 +52,18 @@ module KKGit
52
52
  ]
53
53
  ),
54
54
  Language.new(
55
- id: :prettier,
55
+ id: :js,
56
56
  label: 'JS/TS/Node',
57
- extensions: %w[.js .jsx .mjs .cjs .ts .tsx .json .css .scss .less .html .md .yaml .yml],
57
+ extensions: %w[.js .jsx .mjs .cjs .ts .tsx .json .jsonc .css],
58
+ tools: [
59
+ Tool.new(name: 'biome', bin: 'biome', argv: ['format', '--write', '%{files}']),
60
+ Tool.new(name: 'prettier', bin: 'prettier', argv: ['--write', '%{files}'])
61
+ ]
62
+ ),
63
+ Language.new(
64
+ id: :prettier_docs,
65
+ label: 'Prettier (markup/docs)',
66
+ extensions: %w[.scss .less .html .md .mdx .yaml .yml],
58
67
  tools: [
59
68
  Tool.new(name: 'prettier', bin: 'prettier', argv: ['--write', '%{files}'])
60
69
  ]
@@ -315,8 +315,8 @@ module KKGit
315
315
  return false if path.nil?
316
316
  path.start_with?('openspec/') ||
317
317
  path.match?(/\AREADME(\..+)?\z/i) ||
318
- path.match?(/\.(md|mdx|txt)\z/i)
319
- end
318
+ path.match?(/\.(md|mdx|txt|csv)\z/i)
319
+ end
320
320
 
321
321
  def self.ci_path?(path)
322
322
  return false if path.nil?
@@ -22,6 +22,12 @@ module KKGit
22
22
 
23
23
  SAFE_ENV_TEMPLATE_SUFFIXES = %w[example sample template dist].freeze
24
24
 
25
+ # Application source that manages credentials in-repo (not secret payloads).
26
+ SAFE_CREDENTIALS_CODE_PATTERNS = [
27
+ %r{(?:^|/)[a-z0-9_]+_credentials\.go\z}i,
28
+ %r{(?:^|/)[a-z0-9_]+_credentials\.(ts|tsx|js|jsx)\z}i
29
+ ].freeze
30
+
25
31
  # 仓库同步状态快照
26
32
  Status = Struct.new(
27
33
  :branch, :remote, :clean, :ahead, :behind,
@@ -261,10 +267,15 @@ module KKGit
261
267
 
262
268
  def sensitive_path?(path)
263
269
  return false if safe_env_template?(path)
270
+ return false if safe_credentials_code?(path)
264
271
 
265
272
  SENSITIVE_PATH_PATTERNS.any? { |pattern| path.match?(pattern) }
266
273
  end
267
274
 
275
+ def safe_credentials_code?(path)
276
+ SAFE_CREDENTIALS_CODE_PATTERNS.any? { |pattern| path.match?(pattern) }
277
+ end
278
+
268
279
  def safe_env_template?(path)
269
280
  SAFE_ENV_TEMPLATE_SUFFIXES.any? do |suffix|
270
281
  path.match?(/\A\.env\.#{suffix}\z/i)
@@ -279,16 +290,18 @@ module KKGit
279
290
  end
280
291
 
281
292
  def ensure_no_sensitive_staged!(repo_dir: '.')
293
+ return unless sensitive_check_enabled?
294
+
282
295
  paths = sensitive_staged_paths(repo_dir: repo_dir)
283
296
  return if paths.empty?
284
297
 
285
- if ENV['KK_GIT_ALLOW_SENSITIVE'] == '1'
286
- warn "Warning: committing sensitive paths: #{paths.join(', ')}"
287
- else
288
- raise Error,
289
- "Refusing to commit sensitive paths: #{paths.join(', ')}. " \
290
- 'Set KK_GIT_ALLOW_SENSITIVE=1 to override.'
291
- end
298
+ raise Error,
299
+ "Refusing to commit sensitive paths: #{paths.join(', ')}. " \
300
+ 'Unset KK_GIT_BLOCK_SENSITIVE or unstage these files.'
301
+ end
302
+
303
+ def sensitive_check_enabled?
304
+ ENV['KK_GIT_BLOCK_SENSITIVE'] == '1'
292
305
  end
293
306
 
294
307
  def confirm_commit!(message)
@@ -299,6 +312,18 @@ module KKGit
299
312
  raise Error, 'Set KK_GIT_YES=1 to confirm this commit'
300
313
  end
301
314
 
315
+ def fallback_commit_message(repo_dir: '.')
316
+ inferred = KKGit::CommitMessage.generate(repo_dir: repo_dir, mode: :all).to_s.strip
317
+ return inferred unless inferred.empty?
318
+
319
+ out, _err, ok = run_cmd('git', 'diff', '--cached', '--name-only', chdir: repo_dir)
320
+ names = ok ? out.split("\n").reject(&:empty?) : []
321
+ return 'chore(general): Update project' if names.empty?
322
+
323
+ label = File.basename(names.first)
324
+ names.length == 1 ? "chore(general): Update #{label}" : "chore(general): Update #{names.length} files"
325
+ end
326
+
302
327
  def commit_with_message!(message, repo_dir: '.')
303
328
  commit_args = amend? ? %w[commit --amend -F] : %w[commit -F]
304
329
 
@@ -339,6 +364,17 @@ module KKGit
339
364
  add_all!(repo_dir: repo_dir)
340
365
  ensure_no_sensitive_staged!(repo_dir: repo_dir)
341
366
 
367
+ # add 后可能已干净:假脏(换行归一化等)或 KK_GIT_ADD_PATHS 未覆盖到变更
368
+ if working_tree_clean?(repo_dir: repo_dir)
369
+ if status(remote: remote_name, branch: branch_name, repo_dir: repo_dir).needs_sync?
370
+ sync_with_remote!(remote_name, branch_name, repo_dir: repo_dir)
371
+ return :synced
372
+ end
373
+
374
+ puts 'No changes to commit or push'
375
+ return :noop
376
+ end
377
+
342
378
  message =
343
379
  if commit_message_generator
344
380
  commit_message_generator.call
@@ -346,6 +382,10 @@ module KKGit
346
382
  KKGit::CommitMessage.generate(repo_dir: repo_dir, mode: :all)
347
383
  end
348
384
  message = message.to_s.strip
385
+ if message.empty?
386
+ message = fallback_commit_message(repo_dir: repo_dir)
387
+ warn "Commit message inference empty; using fallback: #{message}"
388
+ end
349
389
  raise Error, 'Could not generate commit message from staged changes' if message.empty?
350
390
 
351
391
  confirm_commit!(message)
@@ -71,6 +71,20 @@ module KKGit
71
71
  warn e.message
72
72
  exit 1
73
73
  end
74
+
75
+ desc 'Auto commit/push, then semver tag (SKIP_TAG=1 or KK_GIT_AUTO_TAG=0 to skip)'
76
+ task :auto_commit_push_tag do
77
+ generator = lambda do
78
+ Rake::Task['git:auto_commit'].reenable
79
+ Rake::Task['git:auto_commit'].invoke
80
+ ENV['KK_GIT_COMMIT_MESSAGE'].to_s
81
+ end
82
+
83
+ KKGit::Release.auto_commit_push_and_tag!(commit_message_generator: generator)
84
+ rescue KKGit::GitOps::Error, ArgumentError => e
85
+ warn e.message
86
+ exit 1
87
+ end
74
88
  end
75
89
  end
76
90
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KKGit
4
- # Semver bump and git tag helpers (maintainer release flow).
4
+ # Semver bump and git tag helpers (maintainer release flow + consumer auto-tag).
5
5
  module Release
6
6
  VERSION_PATH = File.join(__dir__, 'version.rb').freeze
7
7
 
@@ -48,7 +48,7 @@ module KKGit
48
48
  end
49
49
 
50
50
  def semver_max(*versions)
51
- versions.compact.max_by { |v| v }
51
+ versions.compact.max
52
52
  end
53
53
 
54
54
  def tag_exists?(tag, repo_dir: '.')
@@ -74,6 +74,42 @@ module KKGit
74
74
  true
75
75
  end
76
76
 
77
+ # Strict repo tags only: vMAJOR.MINOR.PATCH with MAJOR < max_major (skips date tags like v2026.09.23).
78
+ # @return [Array<Array(Integer)>]
79
+ def list_repo_semver_versions(prefix: 'v', repo_dir: '.', max_major: 1999)
80
+ stdout, _stderr, ok = GitOps.run_cmd('git', 'tag', '--list', "#{prefix}*", chdir: repo_dir)
81
+ return [] unless ok
82
+
83
+ stdout.to_s.split("\n").filter_map do |t|
84
+ t = t.strip
85
+ next unless t.start_with?(prefix)
86
+
87
+ ver = parse_semver(t.delete_prefix(prefix))
88
+ next unless ver
89
+ next if ver[0] > max_major
90
+
91
+ ver
92
+ end
93
+ end
94
+
95
+ # Next tag from git history only (no version.rb / gem VERSION). For product repos.
96
+ # @return [String] e.g. "v1.0.1"
97
+ def next_repo_semver_tag(prefix: nil, bump: nil, repo_dir: '.', default_base: [1, 0, 0], max_major: 1999)
98
+ prefix ||= ENV.fetch('KK_GIT_TAG_PREFIX', 'v')
99
+ bump ||= ENV.fetch('KK_GIT_BUMP', 'patch')
100
+ raise ArgumentError, "bump must be major|minor|patch (got #{bump})" unless %w[major minor patch].include?(bump)
101
+
102
+ base = list_repo_semver_versions(prefix: prefix, repo_dir: repo_dir, max_major: max_major).max || default_base
103
+ version = base
104
+ 50.times do
105
+ version = bump_semver(version, bump)
106
+ candidate = "#{prefix}#{semver_to_s(version)}"
107
+ return candidate unless tag_exists?(candidate, repo_dir: repo_dir)
108
+ end
109
+
110
+ raise 'Failed to generate next repo semver tag: too many attempts'
111
+ end
112
+
77
113
  # @return [Array(String, String)] tag, version
78
114
  def next_tag_and_version(prefix: nil, bump: nil, repo_dir: '.')
79
115
  prefix ||= ENV.fetch('KK_GIT_TAG_PREFIX', 'v')
@@ -81,6 +117,7 @@ module KKGit
81
117
 
82
118
  latest_tag = latest_semver_tag(prefix: prefix, repo_dir: repo_dir)
83
119
  tag_version = latest_tag ? parse_semver(latest_tag.delete_prefix(prefix)) : nil
120
+ # Prefer strict repo tags when gem/file versions would pollute consumer repos.
84
121
  file_version = current_version_in_file || parse_semver(KKGit::VERSION)
85
122
 
86
123
  # version.rb already ahead of tags and tag not yet created → release that version
@@ -109,5 +146,63 @@ module KKGit
109
146
  out, err, ok = GitOps.run_cmd('git', 'push', push_target, tag, chdir: repo_dir)
110
147
  GitOps.ensure_ok!(ok, 'git push tag', stdout: out, stderr: err, repo_dir: repo_dir)
111
148
  end
149
+
150
+ def auto_tag_enabled?
151
+ return false if ENV['SKIP_TAG'] == '1'
152
+
153
+ ENV.fetch('KK_GIT_AUTO_TAG', '1') != '0'
154
+ end
155
+
156
+ # Commit + push, then create/push a semver tag when a new commit was made.
157
+ #
158
+ # - Product repos: leave bump_gem_version false (uses next_repo_semver_tag).
159
+ # - kk-git itself: bump_gem_version true (updates version.rb before commit).
160
+ #
161
+ # Env: KK_GIT_AUTO_TAG (default 1), SKIP_TAG=1, KK_GIT_BUMP, KK_GIT_TAG_PREFIX
162
+ #
163
+ # @return [Hash] keys :result (Symbol), :tag (String or nil)
164
+ def auto_commit_push_and_tag!(commit_message_generator: nil, repo_dir: '.', bump_gem_version: false)
165
+ GitOps.ensure_in_repo!(repo_dir: repo_dir)
166
+
167
+ head_before, _err, ok = GitOps.run_cmd('git', 'rev-parse', 'HEAD', chdir: repo_dir)
168
+ head_before = ok ? head_before.to_s.strip : ''
169
+
170
+ prepared_tag = nil
171
+ if bump_gem_version && auto_tag_enabled? && !GitOps.working_tree_clean?(repo_dir: repo_dir)
172
+ prepared_tag, new_version = next_tag_and_version(repo_dir: repo_dir)
173
+ bumped = update_version!(new_version)
174
+ puts((bumped ? "Version bumped to #{new_version}" : "Version already #{new_version}") +
175
+ ", preparing tag #{prepared_tag}")
176
+ end
177
+
178
+ result = GitOps.auto_commit_push!(
179
+ commit_message_generator: commit_message_generator,
180
+ repo_dir: repo_dir
181
+ )
182
+
183
+ head_after, _err2, ok2 = GitOps.run_cmd('git', 'rev-parse', 'HEAD', chdir: repo_dir)
184
+ head_after = ok2 ? head_after.to_s.strip : ''
185
+ new_commit = result == :committed_and_synced || (head_before != head_after && !head_after.empty?)
186
+
187
+ unless auto_tag_enabled?
188
+ puts 'SKIP_TAG/KK_GIT_AUTO_TAG=0 — not tagging' if ENV['SKIP_TAG'] == '1' || ENV['KK_GIT_AUTO_TAG'] == '0'
189
+ return { result: result, tag: nil }
190
+ end
191
+
192
+ unless new_commit
193
+ puts "No new commit (#{result}) — skip tag"
194
+ return { result: result, tag: nil }
195
+ end
196
+
197
+ tag = prepared_tag || next_repo_semver_tag(repo_dir: repo_dir)
198
+ if tag_exists?(tag, repo_dir: repo_dir)
199
+ puts "Tag #{tag} already exists — skip"
200
+ return { result: result, tag: nil }
201
+ end
202
+
203
+ create_and_push_tag!(tag, repo_dir: repo_dir)
204
+ puts "Tagged and pushed #{tag}"
205
+ { result: result, tag: tag }
206
+ end
112
207
  end
113
208
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KKGit
4
- VERSION = '0.2.6'
4
+ VERSION = '0.2.9'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kk-git
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.2.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - kk
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-07-07 00:00:00.000000000 Z
11
+ date: 2026-11-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Generate Conventional Commits commit messages from current git changes
14
14
  (staged/worktree), designed for Rake/script usage.