Module: Carson::Runtime::Local
- Included in:
- Carson::Runtime
- Defined in:
- lib/carson/runtime/local/sync.rb,
lib/carson/runtime/local/hooks.rb,
lib/carson/runtime/local/prune.rb,
lib/carson/runtime/local/onboard.rb,
lib/carson/runtime/local/template.rb,
lib/carson/runtime/local/worktree.rb,
lib/carson/runtime/local/merge_proof.rb
Constant Summary collapse
- TEMPLATE_SYNC_BRANCH =
"carson/template-sync".freeze
- SUPERSEDED =
[ ".github/carson-instructions.md", ".github/biome.json", ".github/erb-lint.yml", ".github/rubocop.yml", ".github/ruff.toml", ".github/workflows/carson-lint.yml", ".github/.mega-linter.yml", ".github/carson.md", ".github/copilot-instructions.md", ".github/CLAUDE.md", ".github/AGENTS.md", ".github/pull_request_template.md" ].freeze
Instance Method Summary collapse
-
#branch_absorbed_into_main?(branch:) ⇒ Boolean
Returns true when the branch has no unique content relative to main.
-
#cwd_worktree_branch ⇒ Object
Returns the branch checked out in the worktree that contains the process CWD, or nil if CWD is not inside any worktree.
-
#main_worktree_root ⇒ Object
Returns the main (non-worktree) repository root.
- #merge_proof_for_branch(branch:, main_ref: config.main_branch) ⇒ Object
-
#merge_proof_for_remote_ref(branch:, remote: config.git_remote, main_ref: config.main_branch) ⇒ Object
Generates merge proof against the remote tracking ref directly.
-
#offboard! ⇒ Object
Removes Carson-managed repository integration so a host repository can retire Carson cleanly.
-
#onboard! ⇒ Object
One-command onboarding for new repositories: detect remote, install hooks, apply templates, and run initial audit.
- #prune!(json_output: false) ⇒ Object
-
#prune_plan(dry_run: true) ⇒ Object
Returns a plan hash describing what prune! would do, without executing any mutations.
-
#realpath_safe(path) ⇒ Object
Resolves a path to its canonical form, tolerating non-existent paths.
-
#refresh! ⇒ Object
Re-applies hooks, templates, and audit after upgrading Carson.
-
#refresh_all! ⇒ Object
Re-applies hooks, templates, and audit across all governed repositories.
-
#sweep_stale_worktrees! ⇒ Object
Removes agent-owned worktrees whose branch content is already on main.
- #sync!(json_output: false) ⇒ Object
-
#template_apply!(push_prep: false) ⇒ Object
Applies managed template files as full-file writes from Carson sources.
-
#template_check! ⇒ Object
Read-only template drift check; returns block when managed files are output of sync.
-
#worktree_create!(name:, json_output: false) ⇒ Object
Creates a new worktree under .claude/worktrees/<name>.
-
#worktree_list ⇒ Object
Returns all registered worktrees as Carson::Worktree instances.
-
#worktree_list!(json_output: false) ⇒ Object
Human and JSON status surface for all registered worktrees.
-
#worktree_remove!(worktree_path:, force: false, skip_unpushed: false, json_output: false) ⇒ Object
Removes a worktree: directory, git registration, and branch.
Instance Method Details
#branch_absorbed_into_main?(branch:) ⇒ Boolean
Returns true when the branch has no unique content relative to main.
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/carson/runtime/local/prune.rb', line 315 def branch_absorbed_into_main?( branch: ) # Fast path: branch is a strict ancestor of main (fully merged). _, _, is_ancestor, = git_run( "merge-base", "--is-ancestor", branch, config.main_branch ) return true if is_ancestor # Find the merge-base between main and the branch. merge_base_text, _, mb_success, = git_run( "merge-base", config.main_branch, branch ) return false unless mb_success merge_base = merge_base_text.to_s.strip return false if merge_base.empty? # List every file the branch changed relative to the merge-base. changed_text, _, changed_success, = git_run( "diff", "--name-only", merge_base, branch ) return false unless changed_success changed_files = changed_text.to_s.strip.lines.map( &:strip ).reject( &:empty? ) return true if changed_files.empty? # Compare only those files between branch tip and main tip. # If identical, every branch change is already on main. _, _, identical, = git_run( "diff", "--quiet", branch, config.main_branch, "--", *changed_files ) identical end |
#cwd_worktree_branch ⇒ Object
Returns the branch checked out in the worktree that contains the process CWD, or nil if CWD is not inside any worktree. Used by prune to proactively protect the CWD worktree’s branch from deletion. Matches the longest (most specific) path because worktree directories live under the main repo tree (.claude/worktrees/).
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/carson/runtime/local/worktree.rb', line 57 def cwd_worktree_branch cwd = realpath_safe( Dir.pwd ) best_branch = nil best_length = -1 worktree_list.each do |worktree| normalised = File.join( worktree.path, "" ) if ( cwd == worktree.path || cwd.start_with?( normalised ) ) && worktree.path.length > best_length best_branch = worktree.branch best_length = worktree.path.length end end best_branch rescue StandardError nil end |
#main_worktree_root ⇒ Object
Returns the main (non-worktree) repository root. Uses git-common-dir to find the shared .git directory, then takes its parent. Falls back to repo_root if detection fails.
76 77 78 79 80 81 |
# File 'lib/carson/runtime/local/worktree.rb', line 76 def main_worktree_root common_dir, _, success, = git_run( "rev-parse", "--path-format=absolute", "--git-common-dir" ) return File.dirname( common_dir.strip ) if success && !common_dir.strip.empty? repo_root end |
#merge_proof_for_branch(branch:, main_ref: config.main_branch) ⇒ Object
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/carson/runtime/local/merge_proof.rb', line 23 def merge_proof_for_branch( branch:, main_ref: config.main_branch ) return merge_proof_not_applicable( main_ref: main_ref ) if branch.to_s == main_ref.to_s candidate = merge_proof_candidate( branch: branch, main_ref: main_ref ) return candidate if candidate.fetch( :basis ) == "unavailable" trust = merge_proof_main_trust( main_ref: main_ref ) return candidate if trust.fetch( :trusted ) merge_proof_hash( applicable: true, proven: false, basis: "unavailable", summary: trust.fetch( :summary ), main_branch: main_ref, changed_files_count: candidate.fetch( :changed_files_count, 0 ) ) end |
#merge_proof_for_remote_ref(branch:, remote: config.git_remote, main_ref: config.main_branch) ⇒ Object
Generates merge proof against the remote tracking ref directly. Skips the local-main trust check — the caller is responsible for fetching before calling. Used by govern’s post-merge path to avoid mutating the main worktree.
9 10 11 12 13 14 15 16 17 18 19 20 21 |
# File 'lib/carson/runtime/local/merge_proof.rb', line 9 def merge_proof_for_remote_ref( branch:, remote: config.git_remote, main_ref: config.main_branch ) remote_ref = "#{remote}/#{main_ref}" return merge_proof_not_applicable( main_ref: main_ref ) if branch.to_s == main_ref.to_s candidate = merge_proof_candidate( branch: branch, main_ref: remote_ref ) return candidate if candidate.fetch( :basis ) == "unavailable" # Normalise display: show the local branch name, not the remote tracking ref. candidate.merge( main_branch: main_ref, summary: candidate.fetch( :summary ).gsub( remote_ref, main_ref ) ) end |
#offboard! ⇒ Object
Removes Carson-managed repository integration so a host repository can retire Carson cleanly.
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/carson/runtime/local/onboard.rb', line 148 def offboard! puts_verbose "" puts_verbose "[Offboard]" unless inside_git_work_tree? puts_line "#{repo_root} is not a git repository." return EXIT_ERROR end if input_stream.respond_to?( :tty? ) && input_stream.tty? puts_line "" puts_line "This will remove Carson hooks, managed .github/ files," puts_line "and deregister this repository from portfolio governance." puts_line "Continue?" unless prompt_yes_no( default: false ) puts_line "Offboard cancelled." return EXIT_OK end end hooks_status = disable_carson_hooks_path! return hooks_status unless hooks_status == EXIT_OK removed_count = 0 missing_count = 0 offboard_cleanup_targets.each do |relative| absolute = resolve_repo_path!( relative_path: relative, label: "offboard target #{relative}" ) if File.exist?( absolute ) FileUtils.rm_rf( absolute ) puts_verbose "removed_path: #{relative}" removed_count += 1 else puts_verbose "skip_missing_path: #{relative}" missing_count += 1 end end remove_empty_offboard_directories! canonical_root = realpath_safe( main_worktree_root ) remove_govern_repo!( repo_path: canonical_root ) puts_verbose "govern_deregistered: #{canonical_root}" puts_verbose "offboard_summary: removed=#{removed_count} missing=#{missing_count}" if verbose? puts_line "OK: Carson offboard completed for #{repo_root}." else puts_line "Removed #{removed_count} file#{plural_suffix( count: removed_count )}. Offboard complete." end puts_line "" puts_line "Commit the removals and push to finalise offboarding." EXIT_OK end |
#onboard! ⇒ Object
One-command onboarding for new repositories: detect remote, install hooks, apply templates, and run initial audit.
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/carson/runtime/local/onboard.rb', line 10 def onboard! fingerprint_status = block_if_outsider_fingerprints! return fingerprint_status unless fingerprint_status.nil? unless inside_git_work_tree? puts_line "#{repo_root} is not a git repository." return EXIT_ERROR end repo_name = File.basename( repo_root ) puts_line "" puts_line "Onboarding #{repo_name}..." if !global_config_exists? || !git_remote_exists?( remote_name: config.git_remote ) if input_stream.respond_to?( :tty? ) && input_stream.tty? setup_status = setup! return setup_status unless setup_status == EXIT_OK else silent_setup! end end onboard_apply! end |
#prune!(json_output: false) ⇒ Object
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/carson/runtime/local/prune.rb', line 69 def prune!( json_output: false ) fingerprint_status = block_if_outsider_fingerprints! unless fingerprint_status.nil? if json_output output.puts JSON.pretty_generate( { command: "prune", status: "block", error: "Carson-owned artefacts detected in host repository", recovery: "remove Carson-owned files (.carson.yml, bin/carson, .tools/carson) then retry", exit_code: EXIT_BLOCK } ) end return fingerprint_status end prune_git!( "fetch", config.git_remote, "--prune", json_output: json_output ) # Clean stale worktree entries whose directories no longer exist. # Unblocks branch deletion for branches held by dead worktrees. git_run( "worktree", "prune" ) active_branch = current_branch cwd_branch = cwd_worktree_branch counters = { deleted: 0, skipped: 0 } branches = [] stale_branches = stale_local_branches prune_stale_branch_entries( stale_branches: stale_branches, active_branch: active_branch, cwd_branch: cwd_branch, counters: counters, branches: branches ) orphan_branches = orphan_local_branches( active_branch: active_branch, cwd_branch: cwd_branch ) prune_orphan_branch_entries( orphan_branches: orphan_branches, counters: counters, branches: branches ) absorbed_branches = absorbed_local_branches( active_branch: active_branch, cwd_branch: cwd_branch ) prune_absorbed_branch_entries( absorbed_branches: absorbed_branches, counters: counters, branches: branches ) prune_finish( result: { command: "prune", status: "ok", branches: branches, deleted: counters.fetch( :deleted ), skipped: counters.fetch( :skipped ) }, exit_code: EXIT_OK, json_output: json_output, counters: counters ) end |
#prune_plan(dry_run: true) ⇒ Object
Returns a plan hash describing what prune! would do, without executing any mutations. Does NOT fetch — branch staleness reflects whatever the last fetch left behind. Returns: { stale: […], orphan: […], absorbed: […] } Each item: { branch:, action: :delete|:skip, reason:, type: }
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/carson/runtime/local/prune.rb', line 11 def prune_plan( dry_run: true ) # rubocop:disable Lint/UnusedMethodArgument active_branch = current_branch cwd_branch = cwd_worktree_branch stale = stale_local_branches.map do |entry| branch = entry.fetch( :branch ) upstream = entry.fetch( :upstream ) if config.protected_branches.include?( branch ) { action: :skip, branch: branch, upstream: upstream, name: branch, type: "stale", reason: "protected branch" } elsif branch == active_branch { action: :skip, branch: branch, upstream: upstream, name: branch, type: "stale", reason: "current branch" } elsif cwd_branch && branch == cwd_branch { action: :skip, branch: branch, upstream: upstream, name: branch, type: "stale", reason: "checked out in CWD worktree" } else { action: :delete, branch: branch, upstream: upstream, name: branch, type: "stale", reason: "upstream gone" } end end orphan = orphan_local_branches( active_branch: active_branch, cwd_branch: cwd_branch ).map do |branch| if gh_available? tip_sha = begin git_capture!( "rev-parse", "--verify", branch ).strip rescue StandardError nil end if tip_sha merged_pr, = merged_pr_for_branch( branch: branch, branch_tip_sha: tip_sha ) if merged_pr.nil? && branch_absorbed_into_main?( branch: branch ) merged_pr = { url: "absorbed into #{config.main_branch}" } end if merged_pr { action: :delete, branch: branch, upstream: "", name: branch, type: "orphan", reason: "merged — #{merged_pr[ :url ]}" } else { action: :skip, branch: branch, upstream: "", name: branch, type: "orphan", reason: "no merged PR evidence" } end else { action: :skip, branch: branch, upstream: "", name: branch, type: "orphan", reason: "cannot read branch tip SHA" } end else { action: :skip, branch: branch, upstream: "", name: branch, type: "orphan", reason: "gh CLI not available" } end end absorbed = absorbed_local_branches( active_branch: active_branch, cwd_branch: cwd_branch ).map do |entry| branch = entry.fetch( :branch ) upstream = entry.fetch( :upstream ) if gh_available? && branch_has_open_pr?( branch: branch ) { action: :skip, branch: branch, upstream: upstream, name: branch, type: "absorbed", reason: "open PR exists" } else { action: :delete, branch: branch, upstream: upstream, name: branch, type: "absorbed", reason: "content already on main" } end end { stale: stale, orphan: orphan, absorbed: absorbed } end |
#realpath_safe(path) ⇒ Object
Resolves a path to its canonical form, tolerating non-existent paths. Preserves canonical parents for missing paths so deleted worktrees still compare equal to git’s recorded path (for example /tmp vs /private/tmp).
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/carson/runtime/local/worktree.rb', line 86 def realpath_safe( path ) File.realpath( path ) rescue Errno::ENOENT = File.( path ) missing_segments = [] candidate = until File.exist?( candidate ) || Dir.exist?( candidate ) parent = File.dirname( candidate ) break if parent == candidate missing_segments.unshift( File.basename( candidate ) ) candidate = parent end base = if File.exist?( candidate ) || Dir.exist?( candidate ) File.realpath( candidate ) else candidate end missing_segments.empty? ? base : File.join( base, *missing_segments ) end |
#refresh! ⇒ Object
Re-applies hooks, templates, and audit after upgrading Carson.
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/carson/runtime/local/onboard.rb', line 36 def refresh! fingerprint_status = block_if_outsider_fingerprints! return fingerprint_status unless fingerprint_status.nil? unless inside_git_work_tree? puts_line "#{repo_root} is not a git repository." return EXIT_ERROR end if verbose? puts_verbose "" puts_verbose "[Refresh]" hook_status = prepare! return hook_status unless hook_status == EXIT_OK drift_count = template_results.count { |entry| entry.fetch( :status ) != "ok" } stale_count = template_superseded_present.count template_status = template_apply! return template_status unless template_status == EXIT_OK @template_sync_result = template_propagate!( drift_count: drift_count + stale_count ) audit_status = audit! if audit_status == EXIT_OK puts_line "OK: Carson refresh completed for #{repo_root}." elsif audit_status == EXIT_BLOCK puts_line "Refresh complete — some checks need attention. Run carson audit for details." end return audit_status end puts_line "Refresh" hook_status = with_captured_output { prepare! } return hook_status unless hook_status == EXIT_OK puts_line "Hooks installed (#{config.managed_hooks.count} hooks)." template_drift_count = template_results.count { |entry| entry.fetch( :status ) != "ok" } stale_count = template_superseded_present.count template_status = with_captured_output { template_apply! } return template_status unless template_status == EXIT_OK total_drift = template_drift_count + stale_count if total_drift.positive? puts_line "Templates applied (#{template_drift_count} updated, #{stale_count} removed)." else puts_line "Templates in sync." end @template_sync_result = template_propagate!( drift_count: total_drift ) audit_status = audit! puts_line "Refresh complete." audit_status end |
#refresh_all! ⇒ Object
Re-applies hooks, templates, and audit across all governed repositories. Checks each repo for safety (active worktrees, uncommitted changes) and marks unsafe repos as pending to avoid disrupting active work.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/carson/runtime/local/onboard.rb', line 93 def refresh_all! repos = config.govern_repos if repos.empty? puts_line "No governed repositories configured." puts_line " Run carson onboard in each repo to register." return EXIT_ERROR end pending_before = pending_repos_for( command: "refresh" ) if pending_before.any? puts_line "#{pending_before.length} repo#{plural_suffix( count: pending_before.length )} pending from previous run" end puts_line "" puts_line "Refresh all (#{repos.length} repo#{plural_suffix( count: repos.length )})" refreshed = 0 pending = 0 failed = 0 repos.each do |repo_path| repo_name = File.basename( repo_path ) unless Dir.exist?( repo_path ) puts_line "#{repo_name}: not found" record_batch_skip( command: "refresh", repo_path: repo_path, reason: "path not found" ) failed += 1 next end safety = portfolio_repo_safety( repo_path: repo_path ) unless safety.fetch( :safe ) reason = safety.fetch( :reasons ).join( ", " ) puts_line "#{repo_name}: PENDING (#{reason})" record_batch_skip( command: "refresh", repo_path: repo_path, reason: reason ) pending += 1 next end status = refresh_single_repo( repo_path: repo_path, repo_name: repo_name ) if status == EXIT_ERROR failed += 1 else clear_batch_success( command: "refresh", repo_path: repo_path ) refreshed += 1 end end puts_line "" parts = [ "#{refreshed} refreshed" ] parts << "#{pending} still pending (will retry on next run)" if pending.positive? parts << "#{failed} failed" if failed.positive? puts_line "Refresh all complete: #{parts.join( ', ' )}." failed.zero? && pending.zero? ? EXIT_OK : EXIT_ERROR end |
#sweep_stale_worktrees! ⇒ Object
Removes agent-owned worktrees whose branch content is already on main.
22 23 24 |
# File 'lib/carson/runtime/local/worktree.rb', line 22 def sweep_stale_worktrees! Worktree.sweep_stale!( runtime: self ) end |
#sync!(json_output: false) ⇒ Object
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/carson/runtime/local/sync.rb', line 6 def sync!( json_output: false ) # Sync always operates on the main worktree. When called from inside # a worktree, delegate to a runtime rooted at the main tree so # git switch main does not collide with the main tree's checkout. main_root = main_worktree_root if realpath_safe( repo_root ) != realpath_safe( main_root ) main_runtime = Runtime.new( repo_root: main_root, tool_root: tool_root, output: output, error: error, verbose: verbose? ) return main_runtime.sync!( json_output: json_output ) end fingerprint_status = block_if_outsider_fingerprints! return fingerprint_status unless fingerprint_status.nil? unless working_tree_clean? return sync_finish( result: sync_dirty_result, exit_code: EXIT_BLOCK, json_output: json_output ) end = ensure_main_attached! unless .fetch( :ok ) return sync_finish( result: { command: "sync", status: "block", error: .fetch( :error ), recovery: [ :recovery ] }, exit_code: EXIT_BLOCK, json_output: json_output ) end start_branch = current_branch switched = false sync_git!( "fetch", config.git_remote, "--prune", json_output: json_output ) if start_branch != config.main_branch sync_git!( "switch", config.main_branch, json_output: json_output ) switched = true end sync_git!( "pull", "--ff-only", config.git_remote, config.main_branch, json_output: json_output ) ahead_count, behind_count, error_text = main_sync_counts if error_text return sync_finish( result: { command: "sync", status: "block", error: "unable to verify main sync state (#{error_text})" }, exit_code: EXIT_BLOCK, json_output: json_output ) end if ahead_count.zero? && behind_count.zero? return sync_finish( result: { command: "sync", status: "ok", ahead: 0, behind: 0, main_branch: config.main_branch, remote: config.git_remote }, exit_code: EXIT_OK, json_output: json_output ) end sync_finish( result: { command: "sync", status: "block", ahead: ahead_count, behind: behind_count, main_branch: config.main_branch, remote: config.git_remote, error: "local #{config.main_branch} still diverges" }, exit_code: EXIT_BLOCK, json_output: json_output ) ensure git_system!( "switch", start_branch ) if switched && branch_exists?( branch_name: start_branch ) end |
#template_apply!(push_prep: false) ⇒ Object
Applies managed template files as full-file writes from Carson sources. Also removes superseded files that are no longer part of the managed set.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/carson/runtime/local/template.rb', line 58 def template_apply!( push_prep: false ) fingerprint_status = block_if_outsider_fingerprints! return fingerprint_status unless fingerprint_status.nil? puts_verbose "" puts_verbose "[Template Sync Apply]" results = template_results stale = template_superseded_present applied = 0 results.each do |entry| if entry.fetch( :status ) == "error" puts_verbose "template_file: #{entry.fetch( :file )} status=error reason=#{entry.fetch( :reason )}" next end file_path = File.join( repo_root, entry.fetch( :file ) ) if entry.fetch( :status ) == "ok" puts_verbose "template_file: #{entry.fetch( :file )} status=ok reason=in_sync" next end FileUtils.mkdir_p( File.dirname( file_path ) ) File.write( file_path, entry.fetch( :applied_content ) ) puts_verbose "template_file: #{entry.fetch( :file )} status=updated reason=#{entry.fetch( :reason )}" applied += 1 end removed = 0 stale.each do |file| file_path = resolve_repo_path!( relative_path: file, label: "superseded file #{file}" ) File.delete( file_path ) puts_verbose "template_file: #{file} status=removed reason=superseded" removed += 1 end error_count = results.count { |entry| entry.fetch( :status ) == "error" } puts_verbose "template_apply_summary: updated=#{applied} removed=#{removed} error=#{error_count}" unless verbose? if applied.positive? || removed.positive? summary_parts = [] summary_parts << "#{applied} updated" if applied.positive? summary_parts << "#{removed} removed" if removed.positive? puts_line "Templates applied (#{summary_parts.join( ", " )})." else puts_line "Templates in sync." end end return EXIT_ERROR if error_count.positive? return EXIT_BLOCK if push_prep && push_prep_commit! EXIT_OK end |
#template_check! ⇒ Object
Read-only template drift check; returns block when managed files are output of sync.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/carson/runtime/local/template.rb', line 23 def template_check! fingerprint_status = block_if_outsider_fingerprints! return fingerprint_status unless fingerprint_status.nil? puts_verbose "" puts_verbose "[Template Sync Check]" results = template_results stale = template_superseded_present drift_count = results.count { |entry| entry.fetch( :status ) == "drift" } error_count = results.count { |entry| entry.fetch( :status ) == "error" } stale_count = stale.count results.each do |entry| puts_verbose "template_file: #{entry.fetch( :file )} status=#{entry.fetch( :status )} reason=#{entry.fetch( :reason )}" end stale.each { |file| puts_verbose "template_file: #{file} status=stale reason=superseded" } puts_verbose "template_summary: total=#{results.count} drift=#{drift_count} stale=#{stale_count} error=#{error_count}" unless verbose? if ( drift_count + stale_count ).positive? summary_parts = [] summary_parts << "#{drift_count} of #{results.count} drifted" if drift_count.positive? summary_parts << "#{stale_count} stale" if stale_count.positive? puts_line "Templates: #{summary_parts.join( ", " )}" results.select { |entry| entry.fetch( :status ) == "drift" }.each { |entry| puts_line " #{entry.fetch( :file )}" } stale.each { |file| puts_line " #{file} — superseded" } else puts_line "Templates: #{results.count} files in sync" end end return EXIT_ERROR if error_count.positive? ( drift_count + stale_count ).positive? ? EXIT_BLOCK : EXIT_OK end |
#worktree_create!(name:, json_output: false) ⇒ Object
Creates a new worktree under .claude/worktrees/<name>.
12 13 14 |
# File 'lib/carson/runtime/local/worktree.rb', line 12 def worktree_create!( name:, json_output: false ) Worktree.create!( name: name, runtime: self, json_output: json_output ) end |
#worktree_list ⇒ Object
Returns all registered worktrees as Carson::Worktree instances.
27 28 29 |
# File 'lib/carson/runtime/local/worktree.rb', line 27 def worktree_list Worktree.list( runtime: self ) end |
#worktree_list!(json_output: false) ⇒ Object
Human and JSON status surface for all registered worktrees.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/carson/runtime/local/worktree.rb', line 32 def worktree_list!( json_output: false ) entries = worktree_inventory result = { command: "worktree list", status: "ok", worktrees: entries, exit_code: EXIT_OK } if json_output output.puts JSON.pretty_generate( result ) else print_worktree_list( entries: entries ) end EXIT_OK end |
#worktree_remove!(worktree_path:, force: false, skip_unpushed: false, json_output: false) ⇒ Object
Removes a worktree: directory, git registration, and branch.
17 18 19 |
# File 'lib/carson/runtime/local/worktree.rb', line 17 def worktree_remove!( worktree_path:, force: false, skip_unpushed: false, json_output: false ) Worktree.remove!( path: worktree_path, runtime: self, force: force, skip_unpushed: skip_unpushed, json_output: json_output ) end |