Class: RubynCode::Checkpoint::Manager

Inherits:
Object
  • Object
show all
Defined in:
lib/rubyn_code/checkpoint/manager.rb

Constant Summary collapse

MAX_CHECKPOINTS =
30

Instance Method Summary collapse

Constructor Details

#initialize(project_root:) ⇒ Manager

Returns a new instance of Manager.



23
24
25
26
27
28
# File 'lib/rubyn_code/checkpoint/manager.rb', line 23

def initialize(project_root:)
  @project_root = project_root
  @checkpoints = []
  @seq = 0
  @current = nil
end

Instance Method Details

#checkpoint!(label:, conversation:) ⇒ Integer

Open a new checkpoint for a user turn. Captures the conversation as it stands before the agent acts.

Parameters:

  • label (String)

    short description (usually the user’s message)

  • conversation (Agent::Conversation)

Returns:

  • (Integer)

    the checkpoint id



36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/rubyn_code/checkpoint/manager.rb', line 36

def checkpoint!(label:, conversation:)
  @seq += 1
  @current = {
    id: @seq,
    label: summarize(label),
    messages: Array(conversation.messages).dup,
    files: {}
  }
  @checkpoints << @current
  @checkpoints.shift while @checkpoints.size > MAX_CHECKPOINTS
  @seq
end

#empty?Boolean

Returns:

  • (Boolean)


70
# File 'lib/rubyn_code/checkpoint/manager.rb', line 70

def empty? = @checkpoints.empty?

#latest_idObject



72
# File 'lib/rubyn_code/checkpoint/manager.rb', line 72

def latest_id = @checkpoints.last&.fetch(:id)

#listArray<Hash>

Returns label:, files: newest last.

Returns:

  • (Array<Hash>)

    label:, files: newest last



66
67
68
# File 'lib/rubyn_code/checkpoint/manager.rb', line 66

def list
  @checkpoints.map { |c| { id: c[:id], label: c[:label], files: c[:files].size } }
end

#record_file(path) ⇒ void

This method returns an undefined value.

Record a file’s original contents before it is mutated (once per checkpoint per path). No-op when no checkpoint is open.

Parameters:

  • path (String)

    absolute or project-relative path



54
55
56
57
58
59
60
61
62
63
# File 'lib/rubyn_code/checkpoint/manager.rb', line 54

def record_file(path)
  return unless @current && path

  abs = File.expand_path(path.to_s, @project_root)
  return if @current[:files].key?(abs)

  @current[:files][abs] = File.file?(abs) ? File.read(abs) : ABSENT
rescue StandardError => e
  RubynCode::Debug.warn("Checkpoint capture failed for #{path}: #{e.message}")
end

#restore(id, conversation, scope: :both) ⇒ Hash?

Restore a checkpoint. Scope :both (default), :code, or :chat. Checkpoints newer than the restored one are discarded.

Returns:

  • (Hash, nil)

    summary { id:, files_restored: } or nil if not found



78
79
80
81
82
83
84
85
86
87
88
# File 'lib/rubyn_code/checkpoint/manager.rb', line 78

def restore(id, conversation, scope: :both)
  checkpoint = @checkpoints.find { |c| c[:id] == id }
  return nil unless checkpoint

  restored_files = restore_files(checkpoint) if %i[both code].include?(scope)
  conversation.replace!(checkpoint[:messages].dup) if %i[both chat].include?(scope)

  @checkpoints.reject! { |c| c[:id] > id }
  @current = nil
  { id: id, files_restored: restored_files || 0 }
end