buttercut 0.6.0 → 0.7.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/settings.local.json +8 -1
  3. data/.claude/skills +1 -0
  4. data/CLAUDE.md +1 -308
  5. data/LICENSE +106 -21
  6. data/README.md +13 -8
  7. data/lib/buttercut/version.rb +1 -1
  8. data/templates/library_template.yaml +14 -3
  9. data/templates/plan_template.md +1 -1
  10. data/templates/settings_template.yaml +6 -0
  11. metadata +5 -42
  12. data/.claude/scripts/script_extractor.rb +0 -66
  13. data/.claude/skills/analyze-video/SKILL.md +0 -30
  14. data/.claude/skills/analyze-video/agent_prompt.md +0 -84
  15. data/.claude/skills/analyze-video/prepare_visual_script.rb +0 -25
  16. data/.claude/skills/backup-library/SKILL.md +0 -26
  17. data/.claude/skills/backup-library/backup_libraries.rb +0 -46
  18. data/.claude/skills/cut-planner/SKILL.md +0 -74
  19. data/.claude/skills/release/SKILL.md +0 -214
  20. data/.claude/skills/roughcut/SKILL.md +0 -65
  21. data/.claude/skills/roughcut/agent_prompt.md +0 -153
  22. data/.claude/skills/roughcut/export_to_fcpxml.rb +0 -132
  23. data/.claude/skills/setup/SKILL.md +0 -47
  24. data/.claude/skills/setup/advanced-setup.md +0 -141
  25. data/.claude/skills/setup/simple-setup.md +0 -185
  26. data/.claude/skills/setup/verify_install.rb +0 -124
  27. data/.claude/skills/summarize-video/SKILL.md +0 -31
  28. data/.claude/skills/summarize-video/agent_prompt.md +0 -39
  29. data/.claude/skills/summarize-video/summary_skeleton.rb +0 -78
  30. data/.claude/skills/summarize-video/visual_script_extractor.rb +0 -78
  31. data/.claude/skills/transcribe-audio/SKILL.md +0 -36
  32. data/.claude/skills/transcribe-audio/agent_prompt.md +0 -53
  33. data/.claude/skills/transcribe-audio/prepare_audio_script.rb +0 -48
  34. data/.claude/skills/transcribe-audio/refine_instructions.md +0 -114
  35. data/.claude/skills/update-buttercut/SKILL.md +0 -54
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: buttercut
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Ford
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-04 00:00:00.000000000 Z
11
+ date: 2026-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -24,20 +24,6 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.13'
27
- - !ruby/object:Gem::Dependency
28
- name: rubyzip
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '2.3'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '2.3'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: rspec
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -62,32 +48,9 @@ extensions: []
62
48
  extra_rdoc_files: []
63
49
  files:
64
50
  - ".claude/commands/worktree.md"
65
- - ".claude/scripts/script_extractor.rb"
66
51
  - ".claude/settings.json"
67
52
  - ".claude/settings.local.json"
68
- - ".claude/skills/analyze-video/SKILL.md"
69
- - ".claude/skills/analyze-video/agent_prompt.md"
70
- - ".claude/skills/analyze-video/prepare_visual_script.rb"
71
- - ".claude/skills/backup-library/SKILL.md"
72
- - ".claude/skills/backup-library/backup_libraries.rb"
73
- - ".claude/skills/cut-planner/SKILL.md"
74
- - ".claude/skills/release/SKILL.md"
75
- - ".claude/skills/roughcut/SKILL.md"
76
- - ".claude/skills/roughcut/agent_prompt.md"
77
- - ".claude/skills/roughcut/export_to_fcpxml.rb"
78
- - ".claude/skills/setup/SKILL.md"
79
- - ".claude/skills/setup/advanced-setup.md"
80
- - ".claude/skills/setup/simple-setup.md"
81
- - ".claude/skills/setup/verify_install.rb"
82
- - ".claude/skills/summarize-video/SKILL.md"
83
- - ".claude/skills/summarize-video/agent_prompt.md"
84
- - ".claude/skills/summarize-video/summary_skeleton.rb"
85
- - ".claude/skills/summarize-video/visual_script_extractor.rb"
86
- - ".claude/skills/transcribe-audio/SKILL.md"
87
- - ".claude/skills/transcribe-audio/agent_prompt.md"
88
- - ".claude/skills/transcribe-audio/prepare_audio_script.rb"
89
- - ".claude/skills/transcribe-audio/refine_instructions.md"
90
- - ".claude/skills/update-buttercut/SKILL.md"
53
+ - ".claude/skills"
91
54
  - CLAUDE.md
92
55
  - LICENSE
93
56
  - README.md
@@ -103,7 +66,7 @@ files:
103
66
  - templates/settings_template.yaml
104
67
  homepage: https://github.com/andrewford/buttercut
105
68
  licenses:
106
- - MIT
69
+ - Nonstandard
107
70
  metadata: {}
108
71
  post_install_message:
109
72
  rdoc_options: []
@@ -120,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
83
  - !ruby/object:Gem::Version
121
84
  version: '0'
122
85
  requirements: []
123
- rubygems_version: 3.5.23
86
+ rubygems_version: 3.5.22
124
87
  signing_key:
125
88
  specification_version: 4
126
89
  summary: Video Editor XML generator with Agent skills for analyzing video, creating
@@ -1,66 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # Extract the plain-text script from a WhisperX-style transcript JSON.
3
- #
4
- # Usage:
5
- # ruby .claude/scripts/script_extractor.rb <transcript.json> <output.txt>
6
- #
7
- # Output is one segment per paragraph (blank line between), trimmed, suitable
8
- # for proofreading by a human or a sub-agent without the overhead of the full
9
- # transcript JSON (word-level timing, scores, etc.).
10
-
11
- require 'json'
12
-
13
- class ScriptExtractor
14
- def self.extract(transcript_path, output_path)
15
- new(transcript_path, output_path).extract
16
- end
17
-
18
- def initialize(transcript_path, output_path)
19
- raise ArgumentError, "transcript_path is required" if transcript_path.nil? || transcript_path.empty?
20
- raise ArgumentError, "output_path is required" if output_path.nil? || output_path.empty?
21
- @transcript_path = transcript_path
22
- @output_path = output_path
23
- end
24
-
25
- def extract
26
- write_output(format_script)
27
- report
28
- end
29
-
30
- private
31
-
32
- attr_reader :transcript_path, :output_path
33
-
34
- def data
35
- @data ||= JSON.parse(File.read(transcript_path))
36
- end
37
-
38
- def segments
39
- data["segments"] or raise "transcript JSON has no 'segments' key: #{transcript_path}"
40
- end
41
-
42
- def format_script
43
- paragraphs = segments.map { |s| s["text"].to_s.strip }.reject(&:empty?)
44
- paragraphs.join("\n\n") + "\n"
45
- end
46
-
47
- def write_output(text)
48
- File.write(output_path, text)
49
- end
50
-
51
- def report
52
- in_kb = (File.size(transcript_path) / 1024.0).round(1)
53
- out_kb = (File.size(output_path) / 1024.0).round(1)
54
- puts "Extracted script: #{output_path} (#{out_kb} KB from #{in_kb} KB source, #{segments.size} segments)"
55
- end
56
- end
57
-
58
- if __FILE__ == $PROGRAM_NAME
59
- transcript_path, output_path = ARGV
60
- abort("usage: script_extractor.rb <transcript.json> <output.txt>") unless transcript_path && output_path
61
- abort("file not found: #{transcript_path}") unless File.file?(transcript_path)
62
- if File.expand_path(output_path) == File.expand_path(transcript_path)
63
- abort("output path must differ from transcript path: #{transcript_path}")
64
- end
65
- ScriptExtractor.extract(transcript_path, output_path)
66
- end
@@ -1,30 +0,0 @@
1
- ---
2
- name: analyze-video
3
- description: Adds visual descriptions to transcripts by extracting and analyzing video frames with ffmpeg. Creates visual transcript with periodic visual descriptions of the video clip. Use when all files have audio transcripts present (transcript) but don't yet have visual transcripts created (visual_transcript).
4
- ---
5
-
6
- # Skill: Analyze Video (parent brief)
7
-
8
- Adds visual descriptions to a video's audio transcript by extracting JPG frames with ffmpeg and analyzing them.
9
-
10
- `SKILL.md` is the parent's dispatch brief. The sub-agent's working prompt lives in `agent_prompt.md` — inline its contents when launching the Task agent. Don't pass `SKILL.md`.
11
-
12
- ## Prerequisites
13
-
14
- Each video must already have an audio transcript. Run `transcribe-audio` first if any are missing.
15
-
16
- ## Parallelism
17
-
18
- Launch at most **8 in parallel**. ffmpeg frame extraction is a brief CPU burst at the start; the rest of the runtime is LLM API calls. 8 is a comfortable middle ground that won't saturate older machines.
19
-
20
- ## Inputs to gather and pass inline
21
-
22
- - `video_path` — absolute path to the video file
23
- - `audio_transcript_path` — absolute path to the prepared audio transcript JSON
24
- - `visual_transcript_path` — absolute path to write the visual transcript JSON
25
-
26
- After the agent returns, update `library.yaml` with `visual_transcript: <filename>.json`.
27
-
28
- ## Next step
29
-
30
- Once all videos have visual transcripts, dispatch `summarize-video` (Haiku model) to produce summaries.
@@ -1,84 +0,0 @@
1
- # Analyze Video (sub-agent prompt)
2
-
3
- You are a sub-agent. Add visual descriptions to one video's audio transcript by extracting JPG frames with ffmpeg and analyzing them. **Never read the video file directly** — extract frames first.
4
-
5
- ## Inputs (passed inline by the parent)
6
-
7
- - `video_path` — absolute path to the video file
8
- - `audio_transcript_path` — absolute path to the prepared audio transcript JSON
9
- - `visual_transcript_path` — absolute path to write the visual transcript JSON
10
-
11
- Do NOT read `library.yaml` or `settings.yaml`.
12
-
13
- ## 1. Copy & clean audio transcript
14
-
15
- Don't read the audio transcript — just copy it, then prepare it via `prepare_visual_script.rb`. This removes word-level timing data and prettifies the JSON for easier editing:
16
-
17
- ```bash
18
- cp <audio_transcript_path> <visual_transcript_path>
19
- ruby .claude/skills/analyze-video/prepare_visual_script.rb <visual_transcript_path>
20
- ```
21
-
22
- ## 2. Extract frames (binary search)
23
-
24
- Create frame directory: `mkdir -p tmp/frames/[video_name]`
25
-
26
- **Videos ≤30s:** extract one frame at 2s
27
- **Videos >30s:** extract start (2s), middle (duration/2), end (duration-2s)
28
-
29
- ```bash
30
- ffmpeg -ss 00:00:02 -i video.mov -vframes 1 -vf "scale=1280:-1" tmp/frames/[video_name]/start.jpg
31
- ```
32
-
33
- **Subdivide when:** start, middle, and end have different subjects, settings, or angle changes
34
- **Stop when:** the footage no longer seems to be changing or only has minor changes
35
- **Never sample** more frequently than once per 30 seconds
36
-
37
- ## 3. Add visual descriptions
38
-
39
- Read the visual transcript JSON you created in step 1.
40
-
41
- **Read the JPG frames** from `tmp/frames/[video_name]/` using the Read tool, then **Edit** the file at `<visual_transcript_path>`. Do this incrementally — no script needed; just edit the JSON each time you read new frames.
42
-
43
- **Dialogue segments — add `visual` field:**
44
- ```json
45
- {
46
- "start": 2.917,
47
- "end": 7.586,
48
- "text": "Hey, good afternoon everybody.",
49
- "visual": "Man in red shirt speaking to camera in medium shot. Home office with bookshelf. Natural lighting.",
50
- "words": [...]
51
- }
52
- ```
53
-
54
- **B-roll segments — insert new entries:**
55
- ```json
56
- {
57
- "start": 35.474,
58
- "end": 56.162,
59
- "text": "",
60
- "visual": "Green bicycle parked in front of building. Urban street with trees.",
61
- "b_roll": true,
62
- "words": []
63
- }
64
- ```
65
-
66
- **Guidelines:**
67
- - Descriptions: 3 sentences max
68
- - First segment: detailed (subject, setting, shot type, lighting, camera style)
69
- - Continuing shots: brief if similar; up to 3 sentences if drastically different
70
-
71
- ## 4. Cleanup & return
72
-
73
- ```bash
74
- rm -rf tmp/frames/[video_name]
75
- ```
76
-
77
- Return:
78
- ```
79
- ✓ [video_filename.mov] analyzed successfully
80
- Visual transcript: <visual_transcript_path>
81
- Video path: <video_path>
82
- ```
83
-
84
- **Do NOT update library.yaml** — parent handles this to avoid race conditions in parallel execution.
@@ -1,25 +0,0 @@
1
- #!/usr/bin/env ruby
2
- require 'json'
3
-
4
- abort "Usage: ruby prepare_visual_script.rb <json_file>" if ARGV.empty?
5
- abort "Error: File not found: #{ARGV[0]}" unless File.exist?(ARGV[0])
6
-
7
- begin
8
- data = JSON.parse(File.read(ARGV[0]))
9
-
10
- data['segments']&.each { |s| s.delete('words') }
11
- data.delete('word_segments')
12
-
13
- # Reorder keys: language and video_path first, then segments, then everything else
14
- reordered = {}
15
- reordered['language'] = data['language'] if data['language']
16
- reordered['video_path'] = data['video_path'] if data['video_path']
17
- reordered['segments'] = data['segments'] if data['segments']
18
- # Add any other keys that might exist
19
- data.each { |k, v| reordered[k] = v unless reordered.key?(k) }
20
-
21
- File.write(ARGV[0], JSON.pretty_generate(reordered))
22
- puts "Prettified: #{ARGV[0]} (word-level timing removed)"
23
- rescue JSON::ParserError => e
24
- abort "Error: Invalid JSON - #{e.message}"
25
- end
@@ -1,26 +0,0 @@
1
- ---
2
- name: backup-library
3
- description: Backs up user libraries and all their contents (external video excluded). This skill can also be useful when you need to restore a library.
4
- ---
5
-
6
- # Skill: Backup Library
7
-
8
- Verify libraries directory exists:
9
- ```bash
10
- ls -la libraries/
11
- ```
12
-
13
- Run backup:
14
- ```bash
15
- ruby .claude/skills/backup-library/backup_libraries.rb
16
- ```
17
-
18
- Creates `backups/libraries_YYYYMMDD_HHMMSS.zip` containing the entire libraries directory.
19
-
20
- ## Restore Library
21
-
22
- To restore from a backup, extract the ZIP file to the project root.
23
- ```bash
24
- unzip backups/libraries_timestamp.zip -d .
25
- ```
26
- This restores all libraries to their original locations.
@@ -1,46 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Library Backup Utility
5
- # Creates compressed ZIP backups of the entire libraries directory
6
-
7
- require 'fileutils'
8
- require 'time'
9
- require 'zip'
10
-
11
- class LibraryBackup
12
- def initialize(project_root = Dir.pwd)
13
- @libraries_dir = File.join(project_root, 'libraries')
14
- @backups_dir = File.join(project_root, 'backups')
15
- end
16
-
17
- def backup
18
- unless Dir.exist?(@libraries_dir)
19
- puts "❌ No libraries directory found"
20
- return nil
21
- end
22
-
23
- FileUtils.mkdir_p(@backups_dir)
24
-
25
- timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
26
- backup_path = File.join(@backups_dir, "libraries_#{timestamp}.zip")
27
-
28
- puts "📦 Creating backup: #{backup_path}"
29
-
30
- files = Dir.glob(File.join(@libraries_dir, '**', '*')).select { |f| File.file?(f) }
31
-
32
- Zip::File.open(backup_path, create: true) do |zipfile|
33
- files.each do |file|
34
- zipfile.add(file.sub("#{File.dirname(@libraries_dir)}/", ''), file)
35
- end
36
- end
37
-
38
- puts "✅ Backed up #{files.size} files"
39
- backup_path
40
- end
41
- end
42
-
43
- # CLI
44
- if __FILE__ == $PROGRAM_NAME
45
- LibraryBackup.new.backup
46
- end
@@ -1,74 +0,0 @@
1
- ---
2
- name: cut-planner
3
- description: Plans a cut (roughcut, sequence, or scene) from a library's clip summaries. Reads all clip summaries, then talks with the user and iteratively creates a plan markdown file until both agent and user are happy and understand the plan.
4
- ---
5
-
6
- # Skill: Cut Planner
7
-
8
- ## Overview
9
-
10
- In the cut-planner skill, the main thread reads clip summaries from a library to understand footage coverage, then asks the user about the footage to confirm its understanding. It confirms who the characters and locations are, then updates the library.yaml's footage_summary and user_context as it learns more about the footage. If it determines summaries are wrong or missing details, it also updates the summary markdowns.
11
-
12
- After confirming its understanding of the footage, it works with the user to create a narrative plan markdown file.
13
-
14
- This skill runs in the main thread and does not use a sub-agent.
15
-
16
- ## Cut Planner Process
17
-
18
- ### 1. Verify all clips have visual transcripts and summaries
19
- Read `libraries/[library-name]/library.yaml`. Every clip must have `visual_transcript` and `summary` populated. If either is missing for any clip, stop and tell the user which clips still need processing — don't try to plan from incomplete footage. Then ask if they want to resume processing the library.
20
-
21
- ### 2. Read summaries
22
-
23
- #### Sequences
24
- If the user explicitly says they want something short like a short sequence (60 seconds or less), consider asking them about what they want and then grepping through summaries to find the handful of files they might need.
25
-
26
- #### Rough Cuts
27
- If the user wants a full roughcut, read every `libraries/[library-name]/summaries/summary_*.md` file. This will give you full knowledge of the library.
28
-
29
- ### 3. Confirm the footage knowledge and update incorrect summaries
30
- Tell the user what you've learned about the footage, then confirm you understand the Five W's of all the footage: Who, What, When, Where, Why.
31
-
32
- Don't tell them "Five W's" or label out Who, What, When, Where, Why, just talk with them conversationally like an assistant editor getting a grip on the footage.
33
-
34
- If they want a full roughcut, spend more time. If they just want a sequence, be brief.
35
-
36
- Talk with the user until you confirm you understand the footage. Update library.yaml based on the user's responses as you work through questions.
37
-
38
- Update footage_summary (locations, characters, narrative, dialogue, clips) and user_context (preferences, goals, etc.) as you iteratively learn more about the footage.
39
-
40
- Updating user_context and footage_summary helps future agents understand the footage and the user.
41
-
42
- For example, if a summary mentions a generic man or woman but you learn the person is actually the user, replace man/woman with the user's name. Ask the user's name if you don't know it already.
43
-
44
- ### 4. Ask target length
45
- If available, use the `AskUserQuestion` tool or similar to ask the user what length of video they want to create. Use your judgement based on the footage — options like short sequence (30–60s), medium cut (5–8 min), or longer roughcut (9+ min) make good starting points. Podcast footage will likely require a longer option.
46
-
47
- ### 5. If creating a roughcut, propose 2–3 concepts (titles only)
48
- Give the user 2–3 genuinely distinct narrative concepts. Keep this round short — it's about picking a direction, not approving a full plan. For each concept, write only:
49
- - **Title** — short, evocative
50
- - **Concept** — 1–2 sentences explaining the angle, tone, or arc
51
-
52
- Do **not** include beats, footage suggestions, runtime breakdowns, or format notes yet. Those come in step 6 once a direction is chosen.
53
-
54
- Make the options genuinely distinct — different angles, tones, or arcs. End with: "Which feels right, or want me to explore something different?"
55
-
56
- If the user just wants a short sequence, give them information about what the sequence will contain.
57
-
58
- ### 6. Flesh out the chosen concept
59
- Once the user picks a direction for a full roughcut, expand it into a full plan and present that for approval. Now include:
60
- - **Format** — vlog, YouTube Short, long-form, documentary, etc.
61
- - **Beats** — 3–6 beats, each with editorial intent and a rough share of the runtime ("open with ~3 min of X", "montage of Y", "close on Z")
62
- - **Footage suggestions per beat** — name a few videos likely to feed each beat, ie DJI_123, panasonic_1234, etc. Include rough or specific dialogue if you think it will be helpful.
63
- - **Approx. duration**
64
-
65
- Iterate on the fleshed-out plan until the user explicitly signals go.
66
-
67
- If the user wants a short sequence, be brief, just a few sentences, including dialogue if that makes sense.
68
-
69
- ### 7. Save the plan
70
- Copy `templates/plan_template.md` to `libraries/[library-name]/plans/plan_[short-name]_[YYYYMMDD_HHMMSS].md` and fill in every section. The template is the canonical structure — Concept, Format, Target Duration, Beats (with intent / approx. share / footage suggestions), Required Dialogue, Notes for the Build.
71
-
72
- The plan is direction. The build agent confirms specific clips inside each beat.
73
-
74
- Tell the user the plan is ready and confirm they want to move forward, then invoke the `roughcut` skill, passing the full plan path (`libraries/[library-name]/plans/plan_[short-name]_[YYYYMMDD_HHMMSS].md`) as a skill argument — `roughcut` hard-stops if it isn't given one.
@@ -1,214 +0,0 @@
1
- ---
2
- name: release
3
- description: Creates a new ButterCut release with version bump, changelog, git tag, gem build, and GitHub release. Use when publishing a new version.
4
- ---
5
-
6
- # Skill: Release ButterCut
7
-
8
- Guides through the complete release process: version bump, changelog, git operations, gem publishing, and GitHub release creation.
9
-
10
- ## When to Use
11
-
12
- - Publishing a new version of ButterCut
13
- - After merging features or fixes that should be released
14
- - Creating the first v0.1.0 release
15
-
16
- ## Workflow
17
-
18
- ### 1. Run Tests First
19
-
20
- **CRITICAL: Always run tests before releasing. Never release if tests fail.**
21
-
22
- ```bash
23
- bundle exec rspec
24
- ```
25
-
26
- If any tests fail, STOP immediately and ask user to fix before proceeding with release.
27
-
28
- ### 2. Check Current State
29
-
30
- ```bash
31
- # Read current version
32
- cat lib/buttercut/version.rb
33
-
34
- # Check git status (must be clean)
35
- git status
36
-
37
- # Check existing tags
38
- git tag -l
39
- ```
40
-
41
- If git status is not clean, stop and ask user to commit or stash changes before proceeding.
42
-
43
- ### 3. Determine New Version
44
-
45
- Ask user what type of release following [Semantic Versioning](https://semver.org/):
46
- - **MAJOR** (1.0.0): Breaking changes
47
- - **MINOR** (0.2.0): New features, backward compatible
48
- - **PATCH** (0.1.1): Bug fixes, backward compatible
49
-
50
- Calculate new version number based on current version and release type.
51
-
52
- ### 4. Update Version File
53
-
54
- Edit `lib/buttercut/version.rb` with the new version:
55
-
56
- ```ruby
57
- class ButterCut
58
- VERSION = "0.2.0" # Update this
59
- end
60
- ```
61
-
62
- ### 5. Update Gemfile.lock
63
-
64
- Run `bundle install` so `Gemfile.lock` reflects the new version:
65
-
66
- ```bash
67
- bundle install
68
- ```
69
-
70
- Verify the version updated in `Gemfile.lock` before proceeding.
71
-
72
- ### 6. Gather Changelog Notes
73
-
74
- Ask user for release notes. Prompt with:
75
- - What changed in this release?
76
- - Any new features?
77
- - Any bug fixes?
78
- - Any breaking changes?
79
-
80
- ### 7. Update or Create CHANGELOG.md
81
-
82
- If `CHANGELOG.md` exists, prepend new entry. Otherwise create it:
83
-
84
- ```markdown
85
- # Changelog
86
-
87
- All notable changes to ButterCut will be documented in this file.
88
-
89
- ## [0.2.0] - 2025-01-21
90
-
91
- ### Added
92
- - Feature X
93
- - Support for Y
94
-
95
- ### Fixed
96
- - Bug in Z
97
-
98
- ### Changed
99
- - Improved W
100
- ```
101
-
102
- ### 8. Commit Version Bump
103
-
104
- ```bash
105
- git add lib/buttercut/version.rb Gemfile.lock CHANGELOG.md
106
- git commit -m "Bump version to 0.2.0"
107
- ```
108
-
109
- ### 9. Create and Push Git Tag
110
-
111
- ```bash
112
- git tag v0.2.0
113
- git push origin main
114
- git push origin v0.2.0
115
- ```
116
-
117
- ### 10. Build Gem
118
-
119
- ```bash
120
- gem build buttercut.gemspec
121
- ```
122
-
123
- This creates `buttercut-0.2.0.gem` file.
124
-
125
- ### 11. Publish to RubyGems
126
-
127
- **First time setup check:**
128
-
129
- If this is the first release, verify RubyGems authentication:
130
- ```bash
131
- gem signin
132
- ```
133
-
134
- If not authenticated, provide instructions:
135
- 1. Sign up at https://rubygems.org
136
- 2. Run `gem signin` and follow prompts
137
- 3. Store credentials for future releases
138
-
139
- **Publish the gem:**
140
- ```bash
141
- gem push buttercut-0.2.0.gem
142
- ```
143
-
144
- This makes the gem available for `gem install buttercut` worldwide.
145
-
146
- ### 12. Create GitHub Release
147
-
148
- **Using GitHub CLI:**
149
- ```bash
150
- gh release create v0.2.0 \
151
- --title "v0.2.0" \
152
- --notes "[Release notes from changelog]" \
153
- buttercut-0.2.0.gem
154
- ```
155
-
156
- **If `gh` CLI not available:**
157
-
158
- Guide user through manual release creation:
159
- 1. Go to https://github.com/andrewford/buttercut/releases/new
160
- 2. Choose tag: v0.2.0
161
- 3. Set title: v0.2.0
162
- 4. Paste changelog notes in description
163
- 5. Attach buttercut-0.2.0.gem file
164
- 6. Click "Publish release"
165
-
166
- Then wait for user confirmation that release is created before proceeding to cleanup.
167
-
168
- ### 13. Cleanup
169
-
170
- ```bash
171
- # Remove local gem file (it's on RubyGems and GitHub now)
172
- rm buttercut-0.2.0.gem
173
- ```
174
-
175
- ### 14. Verify Release
176
-
177
- Check that everything worked:
178
- - RubyGems page: https://rubygems.org/gems/buttercut
179
- - GitHub releases: https://github.com/andrewford/buttercut/releases
180
- - Git tags: `git tag -l`
181
-
182
- ### 15. Return Success Response
183
-
184
- Provide summary:
185
- ```
186
- ✓ ButterCut 0.2.0 released successfully
187
-
188
- Version: 0.2.0
189
- Git tag: v0.2.0
190
- RubyGems: Published at https://rubygems.org/gems/buttercut
191
- GitHub Release: https://github.com/andrewford/buttercut/releases/tag/v0.2.0
192
-
193
- Installation:
194
- gem install buttercut
195
-
196
- Upgrade:
197
- gem update buttercut
198
- ```
199
-
200
- ## Critical Principles
201
-
202
- **Always run tests first** - Never release if tests fail
203
- **Git must be clean** - No uncommitted changes before release
204
- **Push before publish** - Tags must be pushed before creating GitHub release
205
- **Semantic versioning** - Follow semver strictly for version numbers
206
- **Changelog required** - Every release needs documented changes
207
-
208
- ## Common Issues
209
-
210
- **Tests failing:** Ask user to fix tests before proceeding
211
- **Git not clean:** Ask user to commit or stash changes first
212
- **Tag already exists:** Verify this isn't a duplicate release
213
- **RubyGems authentication:** Guide through `gem signin` process
214
- **GitHub CLI not installed:** Provide manual release instructions