etch-loop 0.4.3__tar.gz → 0.4.5__tar.gz

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.
Files changed (25) hide show
  1. {etch_loop-0.4.3 → etch_loop-0.4.5}/PKG-INFO +1 -1
  2. {etch_loop-0.4.3 → etch_loop-0.4.5}/pyproject.toml +1 -1
  3. etch_loop-0.4.5/src/etch/__init__.py +1 -0
  4. {etch_loop-0.4.3 → etch_loop-0.4.5}/src/etch/analyze.py +25 -13
  5. {etch_loop-0.4.3 → etch_loop-0.4.5}/src/etch/signals.py +26 -44
  6. etch_loop-0.4.5/src/etch/templates/RUN.md +32 -0
  7. etch_loop-0.4.3/src/etch/__init__.py +0 -1
  8. etch_loop-0.4.3/src/etch/templates/RUN.md +0 -20
  9. {etch_loop-0.4.3 → etch_loop-0.4.5}/.github/workflows/workflow.yml +0 -0
  10. {etch_loop-0.4.3 → etch_loop-0.4.5}/README.md +0 -0
  11. {etch_loop-0.4.3 → etch_loop-0.4.5}/src/etch/agent.py +0 -0
  12. {etch_loop-0.4.3 → etch_loop-0.4.5}/src/etch/cli.py +0 -0
  13. {etch_loop-0.4.3 → etch_loop-0.4.5}/src/etch/display.py +0 -0
  14. {etch_loop-0.4.3 → etch_loop-0.4.5}/src/etch/git.py +0 -0
  15. {etch_loop-0.4.3 → etch_loop-0.4.5}/src/etch/loop.py +0 -0
  16. {etch_loop-0.4.3 → etch_loop-0.4.5}/src/etch/prompt.py +0 -0
  17. {etch_loop-0.4.3 → etch_loop-0.4.5}/src/etch/report.py +0 -0
  18. {etch_loop-0.4.3 → etch_loop-0.4.5}/src/etch/templates/BREAK.md +0 -0
  19. {etch_loop-0.4.3 → etch_loop-0.4.5}/src/etch/templates/ETCH.md +0 -0
  20. {etch_loop-0.4.3 → etch_loop-0.4.5}/src/etch/templates/SCAN.md +0 -0
  21. {etch_loop-0.4.3 → etch_loop-0.4.5}/tests/__init__.py +0 -0
  22. {etch_loop-0.4.3 → etch_loop-0.4.5}/tests/test_git.py +0 -0
  23. {etch_loop-0.4.3 → etch_loop-0.4.5}/tests/test_loop.py +0 -0
  24. {etch_loop-0.4.3 → etch_loop-0.4.5}/tests/test_prompt.py +0 -0
  25. {etch_loop-0.4.3 → etch_loop-0.4.5}/tests/test_signals.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: etch-loop
3
- Version: 0.4.3
3
+ Version: 0.4.5
4
4
  Summary: Run Claude Code in a fix-break loop until your codebase is clean
5
5
  License: MIT
6
6
  Requires-Python: >=3.11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "etch-loop"
3
- version = "0.4.3"
3
+ version = "0.4.5"
4
4
  requires-python = ">=3.11"
5
5
  description = "Run Claude Code in a fix-break loop until your codebase is clean"
6
6
  readme = "README.md"
@@ -0,0 +1 @@
1
+ __version__ = "0.4.5"
@@ -270,26 +270,38 @@ def build_run_md(info: dict) -> str:
270
270
  else:
271
271
  cmd_list = "- (detect and run the appropriate build/test command for this project)"
272
272
 
273
- return f"""# RUN — build and test validation
273
+ return f"""# RUN — test writer and build validator
274
274
 
275
- You are a build validator. The fixer has made changes. Your job is to run the project's build and test suite to confirm everything still works.
275
+ You are a test engineer. The fixer has just made changes. Your job is to write tests for what was changed, then run the full suite to confirm everything works.
276
276
 
277
- ## Commands to run
277
+ ## Your mission
278
+
279
+ 1. **Write or update tests** — look at what the fixer changed and write targeted tests covering:
280
+ - The specific edge cases that were fixed
281
+ - Boundary conditions around the changed code
282
+ - Any regression paths that could break silently
283
+ Write tests in the project's existing test style and location.
284
+
285
+ 2. **Run the test suite** — run the full build and test commands to confirm everything passes.
286
+
287
+ ## Build and test commands
278
288
 
279
289
  {cmd_list}
280
290
 
281
291
  ## Rules
282
292
 
283
- 1. Run each command and observe the output
284
- 2. If ALL commands pass:
285
- - Write `ETCH_SUMMARY: <e.g. "all 47 tests passed">`
286
- - Write `ETCH_ALL_CLEAR`
287
- 3. If ANY command fails:
288
- - Write `ETCH_SUMMARY: <what failed, e.g. "3 tests failed in test_auth.py — TypeError on line 42">`
289
- - Include the relevant error output so the fixer can diagnose it
290
- - Write `ETCH_ISSUES_FOUND`
291
-
292
- Do not fix anything — only run and report.
293
+ 1. You MAY edit test files that is your job
294
+ 2. Do NOT touch production code — only tests
295
+ 3. After running, report clearly:
296
+ - If ALL tests pass:
297
+ - `ETCH_SUMMARY: <e.g. "wrote 4 tests, all 51 passed">`
298
+ - `ETCH_ALL_CLEAR`
299
+ - If ANY test fails due to a bug in the production code:
300
+ - `ETCH_SUMMARY: <what failed and why>`
301
+ - Include the relevant error output
302
+ - `ETCH_ISSUES_FOUND`
303
+ - If tests fail because the tests themselves are wrong (flawed test logic):
304
+ - Fix the test and re-run before reporting
293
305
  """
294
306
 
295
307
 
@@ -6,36 +6,30 @@ _PUNCTUATION_ONLY = set("-=*_`~><|")
6
6
 
7
7
 
8
8
  def parse(output: str) -> str:
9
- """Parse breaker agent output for control tokens.
9
+ """Parse agent output for control tokens.
10
10
 
11
- Returns the signal corresponding to whichever token appears first.
12
- If both appear, the earlier one wins. If neither appears, returns
13
- "issues" as a fail-safe.
11
+ Tokens must appear alone on their own line (after stripping whitespace
12
+ and backtick wrappers). This prevents false matches when agents quote the
13
+ token strings in explanations like `ETCH_ALL_CLEAR`.
14
+
15
+ The first matching line wins. If neither token is found, returns "issues"
16
+ as a fail-safe.
14
17
 
15
18
  Returns:
16
- "clear" — ETCH_ALL_CLEAR found (and appears before ETCH_ISSUES_FOUND)
17
- "issues" — ETCH_ISSUES_FOUND found, appears first, or neither found
19
+ "clear" — ETCH_ALL_CLEAR found on its own line first
20
+ "issues" — ETCH_ISSUES_FOUND found on its own line first, or no token found
18
21
  """
19
22
  if not isinstance(output, str):
20
23
  return "issues"
21
24
 
22
- clear_pos = output.find(_TOKEN_CLEAR)
23
- issues_pos = output.find(_TOKEN_ISSUES)
24
-
25
- if clear_pos == -1 and issues_pos == -1:
26
- # Fail-safe: no token found → assume issues
27
- return "issues"
28
-
29
- if clear_pos == -1:
30
- return "issues"
31
-
32
- if issues_pos == -1:
33
- return "clear"
25
+ for line in output.splitlines():
26
+ stripped = line.strip().strip("`").strip()
27
+ if stripped == _TOKEN_CLEAR:
28
+ return "clear"
29
+ if stripped == _TOKEN_ISSUES:
30
+ return "issues"
34
31
 
35
- # Both found whichever appears first wins
36
- if clear_pos < issues_pos:
37
- return "clear"
38
- return "issues"
32
+ return "issues" # fail-safe: no token found
39
33
 
40
34
 
41
35
  def extract_commit_message(output: str, fallback: str) -> str:
@@ -93,35 +87,23 @@ def extract_summary(output: str) -> str:
93
87
 
94
88
 
95
89
  def extract_finding(output: str) -> str:
96
- """Extract first meaningful line before the signal token.
90
+ """Extract the last meaningful line before the signal token line.
97
91
 
98
- Returns the first non-empty, non-header line that appears before
99
- the signal token, or an empty string if nothing useful is found.
92
+ Scans line by line, stops at the first line that IS a token (exact match).
93
+ Returns the last non-empty, non-header line before that point.
100
94
  """
101
95
  if not isinstance(output, str) or not output.strip():
102
96
  return ""
103
97
 
104
- # Find the position of either token
105
- clear_pos = output.find(_TOKEN_CLEAR)
106
- issues_pos = output.find(_TOKEN_ISSUES)
107
-
108
- # Determine the cutoff point (use whichever token appears first)
109
- cutoff = len(output)
110
- if clear_pos >= 0 and issues_pos >= 0:
111
- cutoff = min(clear_pos, issues_pos)
112
- elif clear_pos >= 0:
113
- cutoff = clear_pos
114
- elif issues_pos >= 0:
115
- cutoff = issues_pos
116
-
117
- text_before = output[:cutoff].strip()
118
- if not text_before:
119
- return ""
98
+ lines_before: list[str] = []
99
+ for line in output.splitlines():
100
+ stripped = line.strip().strip("`").strip()
101
+ if stripped in (_TOKEN_CLEAR, _TOKEN_ISSUES):
102
+ break
103
+ lines_before.append(line)
120
104
 
121
- lines = text_before.splitlines()
122
- for line in reversed(lines):
105
+ for line in reversed(lines_before):
123
106
  stripped = line.strip().strip("`").strip()
124
- # Skip empty lines, markdown headers, separator lines, and bare punctuation
125
107
  if not stripped:
126
108
  continue
127
109
  if stripped.startswith("#"):
@@ -0,0 +1,32 @@
1
+ # RUN — test writer and build validator
2
+
3
+ You are a test engineer. The fixer has just made changes. Your job is to write tests for what was changed, then run the full suite to confirm everything works.
4
+
5
+ ## Your mission
6
+
7
+ 1. **Write or update tests** — look at what the fixer changed and write targeted tests covering:
8
+ - The specific edge cases that were fixed
9
+ - Boundary conditions around the changed code
10
+ - Any regression paths that could break silently
11
+ Write tests in the project's existing test style and location.
12
+
13
+ 2. **Run the test suite** — run the full build and test commands to confirm everything passes.
14
+
15
+ ## Build and test commands
16
+
17
+ [configured by etch init]
18
+
19
+ ## Rules
20
+
21
+ 1. You MAY edit test files — that is your job
22
+ 2. Do NOT touch production code — only tests
23
+ 3. After running, report clearly:
24
+ - If ALL tests pass:
25
+ - `ETCH_SUMMARY: <e.g. "wrote 4 tests, all 51 passed">`
26
+ - `ETCH_ALL_CLEAR`
27
+ - If ANY test fails due to a bug in the production code:
28
+ - `ETCH_SUMMARY: <what failed and why>`
29
+ - Include the relevant error output
30
+ - `ETCH_ISSUES_FOUND`
31
+ - If tests fail because the tests themselves are wrong (flawed test logic):
32
+ - Fix the test and re-run before reporting
@@ -1 +0,0 @@
1
- __version__ = "0.4.3"
@@ -1,20 +0,0 @@
1
- # RUN — build and test validation
2
-
3
- You are a build validator. The fixer has made changes. Your job is to run the project's build and test suite to confirm everything still works.
4
-
5
- ## Commands to run
6
-
7
- [configured by etch init]
8
-
9
- ## Rules
10
-
11
- 1. Run each command and observe the output
12
- 2. If ALL commands pass:
13
- - Write `ETCH_SUMMARY: <e.g. "all 47 tests passed">`
14
- - Write `ETCH_ALL_CLEAR`
15
- 3. If ANY command fails:
16
- - Write `ETCH_SUMMARY: <what failed, e.g. "3 tests failed in test_auth.py — TypeError on line 42">`
17
- - Include the relevant error output so the fixer can diagnose it
18
- - Write `ETCH_ISSUES_FOUND`
19
-
20
- Do not fix anything — only run and report.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes