etch-loop 0.4.3__tar.gz → 0.4.4__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 (24) hide show
  1. {etch_loop-0.4.3 → etch_loop-0.4.4}/PKG-INFO +1 -1
  2. {etch_loop-0.4.3 → etch_loop-0.4.4}/pyproject.toml +1 -1
  3. etch_loop-0.4.4/src/etch/__init__.py +1 -0
  4. {etch_loop-0.4.3 → etch_loop-0.4.4}/src/etch/signals.py +26 -44
  5. etch_loop-0.4.3/src/etch/__init__.py +0 -1
  6. {etch_loop-0.4.3 → etch_loop-0.4.4}/.github/workflows/workflow.yml +0 -0
  7. {etch_loop-0.4.3 → etch_loop-0.4.4}/README.md +0 -0
  8. {etch_loop-0.4.3 → etch_loop-0.4.4}/src/etch/agent.py +0 -0
  9. {etch_loop-0.4.3 → etch_loop-0.4.4}/src/etch/analyze.py +0 -0
  10. {etch_loop-0.4.3 → etch_loop-0.4.4}/src/etch/cli.py +0 -0
  11. {etch_loop-0.4.3 → etch_loop-0.4.4}/src/etch/display.py +0 -0
  12. {etch_loop-0.4.3 → etch_loop-0.4.4}/src/etch/git.py +0 -0
  13. {etch_loop-0.4.3 → etch_loop-0.4.4}/src/etch/loop.py +0 -0
  14. {etch_loop-0.4.3 → etch_loop-0.4.4}/src/etch/prompt.py +0 -0
  15. {etch_loop-0.4.3 → etch_loop-0.4.4}/src/etch/report.py +0 -0
  16. {etch_loop-0.4.3 → etch_loop-0.4.4}/src/etch/templates/BREAK.md +0 -0
  17. {etch_loop-0.4.3 → etch_loop-0.4.4}/src/etch/templates/ETCH.md +0 -0
  18. {etch_loop-0.4.3 → etch_loop-0.4.4}/src/etch/templates/RUN.md +0 -0
  19. {etch_loop-0.4.3 → etch_loop-0.4.4}/src/etch/templates/SCAN.md +0 -0
  20. {etch_loop-0.4.3 → etch_loop-0.4.4}/tests/__init__.py +0 -0
  21. {etch_loop-0.4.3 → etch_loop-0.4.4}/tests/test_git.py +0 -0
  22. {etch_loop-0.4.3 → etch_loop-0.4.4}/tests/test_loop.py +0 -0
  23. {etch_loop-0.4.3 → etch_loop-0.4.4}/tests/test_prompt.py +0 -0
  24. {etch_loop-0.4.3 → etch_loop-0.4.4}/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.4
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.4"
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.4"
@@ -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("#"):
@@ -1 +0,0 @@
1
- __version__ = "0.4.3"
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
File without changes