Class: Rralph::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/rralph/parser.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(plan_path: 'plan.md', learnings_path: 'learnings.md', todo_path: 'todo.md') ⇒ Parser

Returns a new instance of Parser.



5
6
7
8
9
# File 'lib/rralph/parser.rb', line 5

def initialize(plan_path: 'plan.md', learnings_path: 'learnings.md', todo_path: 'todo.md')
  @plan_path = plan_path
  @learnings_path = learnings_path
  @todo_path = todo_path
end

Instance Attribute Details

#learnings_contentObject (readonly)

Returns the value of attribute learnings_content.



3
4
5
# File 'lib/rralph/parser.rb', line 3

def learnings_content
  @learnings_content
end

#plan_contentObject (readonly)

Returns the value of attribute plan_content.



3
4
5
# File 'lib/rralph/parser.rb', line 3

def plan_content
  @plan_content
end

#todo_contentObject (readonly)

Returns the value of attribute todo_content.



3
4
5
# File 'lib/rralph/parser.rb', line 3

def todo_content
  @todo_content
end

Instance Method Details

#all_tasksObject



43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/rralph/parser.rb', line 43

def all_tasks
  return [] unless @todo_content

  @todo_content.lines.map.with_index do |line, index|
    stripped = line.strip
    next unless stripped.match?(/^[-*] \[[ x]\]/i)

    completed = stripped.match?(/^[-*] \[x\]/i)
    task_text = stripped.sub(/^[-*] \[[ x]\] /i, '').strip
    { index: index, line: line, text: task_text, raw: stripped, completed: completed }
  end.compact
end

#build_prompt(current_task: nil) ⇒ Object



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
# File 'lib/rralph/parser.rb', line 112

def build_prompt(current_task: nil)
  <<~PROMPT
    You are in an iterative development loop. There is a todo list with tasks.

    YOUR CURRENT TASK (the first unchecked item in todo.md):
    > #{current_task}

    INSTRUCTIONS - Follow these steps in order:
    1. Implement ONLY the task shown above
    2. Write a unit test for it
    3. Run the test
    4. Respond with exactly one of:
       - "TASK_DONE" if the task is complete and test passes
       - "TASK_FAILURE" if the test fails after your best effort
    5. Optionally add learnings as: "Learning: <insight>"

    IMPORTANT RULES:
    - Work on ONE task only - the one shown above
    - Do NOT implement other tasks from the todo list
    - Do NOT mark tasks as done yourself
    - After you respond "TASK_DONE", the system will mark this task complete
    - Then you will receive the next task

    --- plan.md (context) ---
    #{@plan_content}

    --- learnings.md (prior knowledge) ---
    #{@learnings_content}

    --- todo.md (full list - work on first unchecked only) ---
    #{@todo_content}
  PROMPT
end

#completed_tasksObject



31
32
33
34
35
36
37
38
39
40
41
# File 'lib/rralph/parser.rb', line 31

def completed_tasks
  return [] unless @todo_content

  @todo_content.lines.map.with_index do |line, index|
    stripped = line.strip
    next unless stripped.start_with?('- [x]') || stripped.start_with?('* [x]')

    task_text = stripped.sub(/^[-*] \[x\] /i, '').strip
    { index: index, line: line, text: task_text, raw: stripped }
  end.compact
end

#extract_learnings(response) ⇒ Object



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
110
# File 'lib/rralph/parser.rb', line 68

def extract_learnings(response)
  learnings = []

  # Match various learning patterns like:
  # "Learning: xyz", "**Learning to add:** xyz", "- Learning: xyz", etc.
  response.lines.each do |line|
    learning_pattern = /
      (?:^|\s)                    # Start of line or whitespace
      (?:\*\*)?                   # Optional bold markdown
      (?:Learning|Insight|Note|Tip|Discovered|Found|Realized)  # Learning keywords
      [\s*]*                      # Optional whitespace or asterisks
      [:\s]*                      # Optional colons or whitespace
      (?:to\sadd)?                # Optional "to add"
      [:\s]*                      # Optional colons or whitespace
      (.+?)                       # Capture the actual learning content
      (?:\*\*)?                   # Optional closing bold markdown
      (?:\s*$)                    # Optional whitespace to end of line
    /ix

    unless match = line.match(learning_pattern)
      next
    end

    learning = match[1].strip
    learning = learning.gsub(/^\*\*|\*\*$/, '').strip
    learnings << learning unless learning.empty?
  end

  # Also extract from ## Learnings sections
  if response.match?(/##?\s+Learnings/i)
    section = response.split(/##?\s+Learnings/i)[1]
    section = section.split(/##?\s+/)[0] if section.match?(/##?\s+/)
    section.lines.each do |line|
      stripped = line.strip
      next if stripped.empty?

      stripped = stripped.sub(/^[-*]\s*/, '')
      learnings << stripped unless stripped.empty?
    end
  end

  learnings.uniq
end

#failure_detected?(response) ⇒ Boolean

Returns:

  • (Boolean)


60
61
62
# File 'lib/rralph/parser.rb', line 60

def failure_detected?(response)
  response.match?(/\bTASK_FAILURE\b/)
end

#has_pending_tasks?Boolean

Returns:

  • (Boolean)


56
57
58
# File 'lib/rralph/parser.rb', line 56

def has_pending_tasks?
  pending_tasks.any?
end

#load_filesObject

Raises:



11
12
13
14
15
16
17
# File 'lib/rralph/parser.rb', line 11

def load_files
  raise FileNotFound, 'plan.md not found' unless File.exist?(@plan_path)

  @plan_content = File.read(@plan_path)
  @learnings_content = File.exist?(@learnings_path) ? File.read(@learnings_path) : ''
  @todo_content = File.exist?(@todo_path) ? File.read(@todo_path) : ''
end

#pending_tasksObject



19
20
21
22
23
24
25
26
27
28
29
# File 'lib/rralph/parser.rb', line 19

def pending_tasks
  return [] unless @todo_content

  @todo_content.lines.map.with_index do |line, index|
    stripped = line.strip
    next unless stripped.start_with?('- [ ]') || stripped.start_with?('* [ ]')

    task_text = stripped.sub(/^[-*] \[ \] /, '').strip
    { index: index, line: line, text: task_text, raw: stripped }
  end.compact
end

#task_completed?(response) ⇒ Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/rralph/parser.rb', line 64

def task_completed?(response)
  response.match?(/\bTASK_DONE\b/)
end