lcode-cli 0.24.0__py3-none-any.whl → 0.25.0__py3-none-any.whl

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.
lcode/__init__.py CHANGED
@@ -3,7 +3,7 @@
3
3
  From a program: `from lcode import Session` (see lcode.api).
4
4
  """
5
5
 
6
- __version__ = "0.24.0"
6
+ __version__ = "0.25.0"
7
7
 
8
8
  API = ("Session", "Result", "Options", "SetupError")
9
9
 
lcode/agent.py CHANGED
@@ -81,14 +81,18 @@ SYSTEM_PROMPT = """You are lcode, an autonomous software-engineering agent runni
81
81
  - Use your tools to gather facts. Never guess file contents, APIs, or command output — read, search, or run them.
82
82
  - To understand a repository: look at the layout, README/docs, entry points and config, then grep and read the relevant files. Cite code as `path:line`.
83
83
  - Read a file before editing it. Use edit_file for targeted changes and write_file for new files or full rewrites. old_string must be copied exactly from the file (without the line-number prefixes).
84
- - After writing or changing code, verify it: run the script, the tests, or at least a syntax check (e.g. `{python} -m py_compile file.py`). Fix what fails.
85
84
  - Match the existing code style, naming and structure. Don't add unrequested features.
86
85
  - For multi-step tasks, plan with todo_write and keep it updated.
87
86
  - Work autonomously until the task is done. Only stop to ask the user when you are genuinely blocked or the decision is theirs.
88
87
  - Don't run destructive or irreversible commands (rm -rf, git reset --hard, git push --force, dropping data) unless the user explicitly asked.
89
- - Never claim something works without having run it. If something fails, say so and show the relevant error.
90
88
  - If the user denies a tool call, don't retry the same thing; adjust or ask.
91
89
 
90
+ # Verifying your work
91
+ - A syntax check or an import only shows that code loads. Run what you changed the way it will be used (the script, the tests, the app) and fix what fails.
92
+ - To fix a bug, first reproduce each problem the user reports with a test, a command or a short script, then run the same checks to show they're gone.
93
+ - For what you can't see, like a GUI, a web page or a layout, check it without a screen where you can: `QT_QPA_PLATFORM=offscreen` for Qt, a headless browser, or a script that prints sizes and positions or renders to an image{look}. Measure instead of assuming.
94
+ - Never claim something works, mark a todo done, or call it tested unless you ran it. If you couldn't check something, say so and tell the user exactly what to look for. If something fails, say so and show the relevant error.
95
+
92
96
  # Communication
93
97
  - Be concise and direct; no filler. Use GitHub-flavored markdown.
94
98
  - When you finish, give a short summary of what you found or changed and anything the user needs to do.
@@ -317,6 +321,7 @@ class Agent:
317
321
  skills=extensions.skills_prompt(self.extensions().skills, self.settings.context),
318
322
  code=self.code_prompt(),
319
323
  map=self.map_prompt(),
324
+ look=" you look at with view_image" if self.vision_model() else "",
320
325
  )
321
326
 
322
327
  def repo_map(self) -> repomap.RepoMap | None:
lcode/bench.py CHANGED
@@ -746,7 +746,7 @@ TASKS = [
746
746
 
747
747
  def extra_tasks() -> list[Task]:
748
748
  """Tasks that only run when asked for with --tasks (they're slower, or test optional features)."""
749
- from lcode import bench_backend
749
+ from lcode import bench_backend, bench_runtime
750
750
 
751
751
  return [
752
752
  Task(
@@ -755,7 +755,14 @@ def extra_tasks() -> list[Task]:
755
755
  bench_backend.PROMPT,
756
756
  bench_backend.FILES,
757
757
  bench_backend.check,
758
- )
758
+ ),
759
+ Task(
760
+ "runtime-bug",
761
+ "Fix a bug that shows only when it runs",
762
+ bench_runtime.PROMPT,
763
+ bench_runtime.FILES,
764
+ bench_runtime.check,
765
+ ),
759
766
  ]
760
767
 
761
768
 
lcode/bench_runtime.py ADDED
@@ -0,0 +1,156 @@
1
+ """A bug that only shows when the code runs, for `lcode bench --tasks runtime-bug`.
2
+
3
+ A camera wall lays out video panels in a window. While video plays, the window grows taller frame after
4
+ frame, past the screen, and can't be made smaller again: each panel's minimum size is taken from the
5
+ frame it shows, and the frame is scaled to the whole cell, status bar included. Reading the code, the
6
+ obvious fixes are band-aids (ignore the minimum sizes, or only fix the scaling) that fail part of the
7
+ hidden test. Running it, the window sizes show the ratchet. It's the kind of bug a GUI has, without
8
+ needing one, and rewards reproducing a problem before fixing it.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from pathlib import Path
15
+
16
+ WALL = '''\
17
+ """A camera wall: a window with a grid of panels, each showing its camera's latest video frame."""
18
+
19
+ SPACING = 8 # pixels between and around the panels
20
+ STATUS_BAR = 20 # under its video, each panel shows the camera's name and status
21
+
22
+
23
+ class Panel:
24
+ def __init__(self, name):
25
+ self.name = name
26
+ self.min_width = self.min_height = 1 # the panel can't get smaller than this
27
+ self.video = (0, 0) # the size of the frame on screen
28
+
29
+ def show(self, frame_width, frame_height, cell_width, cell_height):
30
+ """Show a frame scaled to fit the panel's cell, keeping its aspect ratio."""
31
+ scale = min(cell_width / frame_width, cell_height / frame_height)
32
+ self.video = (int(frame_width * scale), int(frame_height * scale))
33
+ # The video and the status bar under it must stay visible.
34
+ self.min_width, self.min_height = self.video[0], self.video[1] + STATUS_BAR
35
+
36
+
37
+ class Wall:
38
+ def __init__(self, names, rows, cols):
39
+ self.rows, self.cols = rows, cols
40
+ self.panels = [Panel(name) for name in names]
41
+ self.width = self.height = 0
42
+
43
+ def resize(self, width, height):
44
+ """Ask for a window size. The window can't be smaller than its panels need."""
45
+ need_width = max(p.min_width for p in self.panels) * self.cols + SPACING * (self.cols + 1)
46
+ need_height = max(p.min_height for p in self.panels) * self.rows + SPACING * (self.rows + 1)
47
+ self.width, self.height = max(width, need_width), max(height, need_height)
48
+
49
+ def cell(self):
50
+ """The size of one panel's cell."""
51
+ return (
52
+ (self.width - SPACING * (self.cols + 1)) // self.cols,
53
+ (self.height - SPACING * (self.rows + 1)) // self.rows,
54
+ )
55
+
56
+ def new_frame(self, frame_width, frame_height):
57
+ """Every camera delivered a frame: show it in each panel, then let the layout settle."""
58
+ cell_width, cell_height = self.cell()
59
+ for panel in self.panels:
60
+ panel.show(frame_width, frame_height, cell_width, cell_height)
61
+ self.resize(self.width, self.height)
62
+ '''
63
+
64
+ SIMULATE = '''\
65
+ """Play the camera wall without a screen: python simulate.py [WIDTH HEIGHT [ROWSxCOLS]]"""
66
+
67
+ import sys
68
+
69
+ from wall import Wall
70
+
71
+
72
+ def main():
73
+ width, height = (int(sys.argv[1]), int(sys.argv[2])) if len(sys.argv) > 2 else (2560, 1080)
74
+ rows, cols = map(int, sys.argv[3].split("x")) if len(sys.argv) > 3 else (3, 3)
75
+ wall = Wall([f"camera {n + 1}" for n in range(rows * cols)], rows, cols)
76
+ wall.resize(width * 9 // 10, height * 9 // 10)
77
+ print(f"{rows}x{cols} wall on a {width}x{height} screen, window {wall.width}x{wall.height}")
78
+ for n in range(1, 6):
79
+ wall.new_frame(1920, 1080)
80
+ print(f"after frame {n}: window {wall.width}x{wall.height}, video {wall.panels[0].video}")
81
+
82
+
83
+ if __name__ == "__main__":
84
+ main()
85
+ '''
86
+
87
+ FILES = {"wall.py": WALL, "simulate.py": SIMULATE}
88
+
89
+ PROMPT = (
90
+ "Users say the camera wall in wall.py keeps growing taller while video plays, until it's bigger than "
91
+ "their screen, and after that it can't be made smaller again. Fix it."
92
+ )
93
+
94
+ HIDDEN_TEST = """\
95
+ import unittest
96
+
97
+ from wall import SPACING, STATUS_BAR, Wall
98
+
99
+ SCREENS = [(2560, 1080, 3, 3), (1920, 1080, 2, 3), (1366, 768, 3, 3), (3440, 1440, 2, 4), (1280, 1024, 2, 2)]
100
+
101
+
102
+ def play(width, height, rows, cols, frames=30, frame=(1920, 1080)):
103
+ wall = Wall([f"camera {n}" for n in range(rows * cols)], rows, cols)
104
+ wall.resize(width, height)
105
+ for _ in range(frames):
106
+ wall.new_frame(*frame)
107
+ return wall
108
+
109
+
110
+ class HiddenTest(unittest.TestCase):
111
+ def test_keeps_its_size(self):
112
+ for width, height, rows, cols in SCREENS:
113
+ with self.subTest(screen=(width, height), grid=(rows, cols)):
114
+ wall = play(width * 9 // 10, height * 9 // 10, rows, cols)
115
+ self.assertEqual((wall.width, wall.height), (width * 9 // 10, height * 9 // 10))
116
+
117
+ def test_can_be_made_smaller(self):
118
+ wall = play(2304, 972, 3, 3)
119
+ wall.resize(1280, 720)
120
+ wall.new_frame(1920, 1080)
121
+ self.assertEqual((wall.width, wall.height), (1280, 720))
122
+
123
+ def test_video_fits_and_fills_its_cell(self):
124
+ for width, height, rows, cols in SCREENS:
125
+ for frame in [(1920, 1080), (1280, 960), (720, 1280)]:
126
+ with self.subTest(screen=(width, height), grid=(rows, cols), frame=frame):
127
+ wall = play(width * 9 // 10, height * 9 // 10, rows, cols, frames=3, frame=frame)
128
+ cell_width = (wall.width - SPACING * (cols + 1)) // cols
129
+ cell_height = (wall.height - SPACING * (rows + 1)) // rows
130
+ for panel in wall.panels:
131
+ video_width, video_height = panel.video
132
+ self.assertLessEqual(video_width, cell_width)
133
+ self.assertLessEqual(video_height + STATUS_BAR, cell_height)
134
+ fills = video_width >= cell_width - 2 or video_height + STATUS_BAR >= cell_height - 2
135
+ self.assertTrue(fills, "the video doesn't use the room it has")
136
+ self.assertAlmostEqual(video_width / video_height, frame[0] / frame[1], delta=0.02)
137
+ """
138
+
139
+ PROBLEMS = {
140
+ "test_keeps_its_size": "the window still grows",
141
+ "test_can_be_made_smaller": "the window can't be made smaller",
142
+ "test_video_fits_and_fills_its_cell": "the video doesn't fit its cell above the status bar (or doesn't fill it)",
143
+ }
144
+
145
+
146
+ def check(folder: Path, answer: str):
147
+ from lcode.bench import Check, last_line, run_python
148
+
149
+ if not (folder / "wall.py").is_file():
150
+ return Check(False, "wall.py is missing")
151
+ (folder / "test_hidden_wall.py").write_text(HIDDEN_TEST)
152
+ r = run_python(folder, "-m", "unittest", "-q", "test_hidden_wall")
153
+ if r.returncode == 0:
154
+ return Check(True, "keeps its size, can be made smaller, and the video fits")
155
+ failed = [text for name, text in PROBLEMS.items() if re.search(rf"\b{name}\b", r.stderr)]
156
+ return Check(False, "; ".join(failed) or last_line(r.stderr) or last_line(r.stdout))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: lcode-cli
3
- Version: 0.24.0
3
+ Version: 0.25.0
4
4
  Summary: A local-first terminal coding agent powered by open-weight models on your own GPU or Mac, via Ollama.
5
5
  Project-URL: Homepage, https://nasser1941.github.io/lcode/
6
6
  Project-URL: Documentation, https://nasser1941.github.io/lcode/
@@ -1,12 +1,13 @@
1
- lcode/__init__.py,sha256=TKS4ocYb1I6mIhEHcL41tMRS6veQZlQM9xS4vXe2mCk,473
1
+ lcode/__init__.py,sha256=znAn1x1nESR2keV7zRFdQd7reQE9a4zKFE1_LsWuoas,473
2
2
  lcode/__main__.py,sha256=s5N5pryX2FAu93GGZ3FxUrIXHWGzxz66lKUoaV_h8Ws,35
3
3
  lcode/acp.py,sha256=Tpc8lsvffZSYbTRI4t7X4E2chahqZIPcKeYIcL5bV3Y,29035
4
4
  lcode/action.py,sha256=W-QqdGt4VAVLq7mcIdvL9UrEOwDEWvkZ26gNDv8k1kg,16330
5
- lcode/agent.py,sha256=fXy5sCexEhOjCcdmQJ-_gAYYX0JhNog5uXaGyptUiL4,55568
5
+ lcode/agent.py,sha256=naua7OJW9BXhv8mpF1QLdclnFy6RHZAjw9LYTt_dBy0,56201
6
6
  lcode/api.py,sha256=FQ6aVLB936Cu2XEBmuOoq0u1DfOiExmgap0Y3WxJ2mQ,15376
7
7
  lcode/backends.py,sha256=dtJHrC6ys73bRDg64z0tQz1PavmeAkiODZu1fOO9ExQ,20635
8
- lcode/bench.py,sha256=XVdk0hDxmRcjgROOCgwwOsExcUhIxgh0B2vnxtGnA3o,40742
8
+ lcode/bench.py,sha256=7ICipu_2GXW6vE519XeZucc2GSWA44votT8JvqcNv1k,40964
9
9
  lcode/bench_backend.py,sha256=X2fo-zRX92Xp3KMxSsFlWioc2vRupBTtowj5kLME8mw,14287
10
+ lcode/bench_runtime.py,sha256=EjzNgIvUkdbT8cbSy1TpmlyeIk6e-ipjlDFpM7Dnqx0,6835
10
11
  lcode/catalog.py,sha256=pZ1azX9OH2r4JE2c6E5OfwywpgS39-W2oXzoCHJhmtM,4141
11
12
  lcode/checkpoints.py,sha256=eqPtmPe44-aweWqc2H21ROogwzIt3NpvcUobsaCHoGY,18354
12
13
  lcode/cli.py,sha256=cH5mcYi54Aofcv0PDNgFmFFQX8OQjhwTGdbWMUM_FG8,40289
@@ -55,8 +56,8 @@ lcode/servers/__init__.py,sha256=qrIhxEodEB8cjKEQ4Cicc0GsQhE2OH-9qlVHzr3-tq8,77
55
56
  lcode/servers/encord.py,sha256=WSauawL1UKYZCUAG1oalcKL_An3DVof-JM6xIgFyQSE,12769
56
57
  lcode/servers/valohai.py,sha256=e5xZGIoa4cja4f5i85q1GpL9c7cglUjV8jN2co1CFMY,14430
57
58
  lcode/servers/xai.py,sha256=h2emAvHSnkuB9WmJInlynsDhvxJwNVD2AHFX7vqMXNc,13627
58
- lcode_cli-0.24.0.dist-info/METADATA,sha256=el8BWCJBrgargRIUs2z5O4a5ZKZyVQ9-6vCtuhmFf3k,12620
59
- lcode_cli-0.24.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
60
- lcode_cli-0.24.0.dist-info/entry_points.txt,sha256=Hxsn_itbnbgft3U0FwxWdmEt0yoAV2LB-nJDnL9WT20,172
61
- lcode_cli-0.24.0.dist-info/licenses/LICENSE,sha256=JWjmoL7nNIC1XUa-dZm2hwLUgjUQMFo6k8APb0idNzs,1074
62
- lcode_cli-0.24.0.dist-info/RECORD,,
59
+ lcode_cli-0.25.0.dist-info/METADATA,sha256=j0rf2PzRDAvWLYN-2RizBuLuel-tmvhfgf_m_pvTHkE,12620
60
+ lcode_cli-0.25.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
61
+ lcode_cli-0.25.0.dist-info/entry_points.txt,sha256=Hxsn_itbnbgft3U0FwxWdmEt0yoAV2LB-nJDnL9WT20,172
62
+ lcode_cli-0.25.0.dist-info/licenses/LICENSE,sha256=JWjmoL7nNIC1XUa-dZm2hwLUgjUQMFo6k8APb0idNzs,1074
63
+ lcode_cli-0.25.0.dist-info/RECORD,,