Class: RobotLab::To::Guards::ReadBeforeEdit
- Inherits:
-
Hook
- Object
- Hook
- RobotLab::To::Guards::ReadBeforeEdit
- Defined in:
- lib/robot_lab/to/guards/read_before_edit.rb
Overview
Read-before-edit invariant, ported from little-coder read-guard-edit.
Small models fire Edit with an oldText they never actually saw, guessing the file's contents — which either fails the exact-match (wasted turn) or matches the wrong span (silent corruption). We block any Edit whose target wasn't Read this run.
The read-set spans many tool calls within one run, so it lives in the per-run RunStore (see that file for why ctx.local can't hold it). A successful Read, Edit, or Write marks the path as known.
Constant Summary collapse
- KEY =
:robot_lab_to_read_files- TRACKED_READS =
%w[read edit write].freeze
Class Method Summary collapse
-
.after_tool_call(ctx) ⇒ Object
Record successful reads/authored files as "known".
-
.around_tool_call(ctx) ⇒ Object
Block edits to files that were never read.
-
.before_run(_ctx) ⇒ Object
Fresh run = clean slate; a prior iteration's reads say nothing here.
- .edit_before_read_reason(resolved) ⇒ String
- .mark_read(path) ⇒ Object
-
.read?(path) ⇒ Boolean
Whether
pathhas been read this run. -
.read_files ⇒ Array<String>
The per-run read-set (live reference).
Class Method Details
.after_tool_call(ctx) ⇒ Object
Record successful reads/authored files as "known".
32 33 34 35 36 37 38 |
# File 'lib/robot_lab/to/guards/read_before_edit.rb', line 32 def after_tool_call(ctx) return if ctx.tool_error return unless TRACKED_READS.include?(ctx.tool_name.to_s.downcase) path = PathResolution.resolve(ctx.tool_args) mark_read(path) if path end |
.around_tool_call(ctx) ⇒ Object
Block edits to files that were never read.
41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/robot_lab/to/guards/read_before_edit.rb', line 41 def around_tool_call(ctx) return yield unless ctx.tool_name.to_s.casecmp?("edit") path = PathResolution.resolve(ctx.tool_args) if path && !read?(path) ctx.tool_result = edit_before_read_reason(path) return ctx.tool_result end yield end |
.before_run(_ctx) ⇒ Object
Fresh run = clean slate; a prior iteration's reads say nothing here.
27 28 29 |
# File 'lib/robot_lab/to/guards/read_before_edit.rb', line 27 def before_run(_ctx) RunStore.reset(KEY, []) end |
.edit_before_read_reason(resolved) ⇒ String
70 71 72 73 74 75 76 77 78 |
# File 'lib/robot_lab/to/guards/read_before_edit.rb', line 70 def edit_before_read_reason(resolved) <<~MSG File must be read before edit — #{resolved} has not been read in this session. Read #{resolved} first to get the exact current text for oldText (whitespace and indentation must match exactly), then issue the Edit. Include 2-3 surrounding lines so oldText is unique. Do NOT guess the file's contents. MSG end |
.mark_read(path) ⇒ Object
58 59 60 61 |
# File 'lib/robot_lab/to/guards/read_before_edit.rb', line 58 def mark_read(path) files = read_files files << path unless files.include?(path) end |
.read?(path) ⇒ Boolean
Returns whether path has been read this run.
54 55 56 |
# File 'lib/robot_lab/to/guards/read_before_edit.rb', line 54 def read?(path) read_files.include?(path) end |