kaizen-loop 0.1.0__tar.gz → 0.2.1__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.
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/.github/workflows/ci.yml +3 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/.github/workflows/release.yml +3 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/PKG-INFO +2 -1
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/pyproject.toml +2 -1
- kaizen_loop-0.2.1/src/kaizen/__init__.py +1 -0
- kaizen_loop-0.2.1/src/kaizen/findings.py +44 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/src/kaizen/loop.py +2 -2
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/src/kaizen/orchestrator.py +18 -12
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/src/kaizen/review_prompt.py +22 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/src/kaizen/steps/__init__.py +2 -3
- kaizen_loop-0.1.0/src/kaizen/__init__.py +0 -1
- kaizen_loop-0.1.0/src/kaizen/findings.py +0 -53
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/.gitignore +0 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/LICENSE +0 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/README.md +0 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/justfile +0 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/src/kaizen/__main__.py +0 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/src/kaizen/agent.py +0 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/src/kaizen/cli.py +0 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/src/kaizen/config.py +0 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/src/kaizen/git.py +0 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/src/kaizen/run.py +0 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/src/kaizen/steps/pr.py +0 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/src/kaizen/steps/push.py +0 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/src/kaizen/steps/review.py +0 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/src/kaizen/work_prompt.py +0 -0
- {kaizen_loop-0.1.0 → kaizen_loop-0.2.1}/tests/test_import.py +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kaizen-loop
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Continuous code improvement: autonomous work → review → fix → ship
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: pydantic>=2.0
|
|
8
9
|
Provides-Extra: dev
|
|
9
10
|
Requires-Dist: pytest; extra == 'dev'
|
|
10
11
|
Requires-Dist: ruff; extra == 'dev'
|
|
@@ -4,10 +4,11 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "kaizen-loop"
|
|
7
|
-
version = "0.1
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "Continuous code improvement: autonomous work → review → fix → ship"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
license = "MIT"
|
|
11
|
+
dependencies = ["pydantic>=2.0"]
|
|
11
12
|
|
|
12
13
|
[project.optional-dependencies]
|
|
13
14
|
dev = ["pytest", "ruff"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.1"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
Action = Literal["no-op", "auto-fix"]
|
|
9
|
+
Severity = Literal["info", "warning", "error"]
|
|
10
|
+
RiskLevel = Literal["low", "medium", "high"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Finding(BaseModel):
|
|
14
|
+
id: str
|
|
15
|
+
severity: Severity
|
|
16
|
+
file: str = ""
|
|
17
|
+
line: int = 0
|
|
18
|
+
description: str = ""
|
|
19
|
+
action: Action = "no-op"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FindingsResult(BaseModel):
|
|
23
|
+
items: list[Finding] = Field(default_factory=list)
|
|
24
|
+
summary: str = ""
|
|
25
|
+
risk_level: RiskLevel = "low"
|
|
26
|
+
risk_rationale: str = ""
|
|
27
|
+
|
|
28
|
+
@cached_property
|
|
29
|
+
def has_auto_fix(self) -> bool:
|
|
30
|
+
return any(f.action == "auto-fix" for f in self.items)
|
|
31
|
+
|
|
32
|
+
@cached_property
|
|
33
|
+
def auto_fix_items(self) -> list[Finding]:
|
|
34
|
+
return [f for f in self.items if f.action == "auto-fix"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def parse_findings(data: dict) -> FindingsResult:
|
|
38
|
+
mapped = {
|
|
39
|
+
"items": data.get("findings", []),
|
|
40
|
+
"summary": data.get("summary", ""),
|
|
41
|
+
"risk_level": data.get("risk_level", "low"),
|
|
42
|
+
"risk_rationale": data.get("risk_rationale", ""),
|
|
43
|
+
}
|
|
44
|
+
return FindingsResult.model_validate(mapped)
|
|
@@ -23,7 +23,7 @@ from kaizen.git import (
|
|
|
23
23
|
slugify_prompt,
|
|
24
24
|
)
|
|
25
25
|
from kaizen.orchestrator import Orchestrator
|
|
26
|
-
from kaizen.review_prompt import build_fix_prompt
|
|
26
|
+
from kaizen.review_prompt import FIX_SCHEMA, build_fix_prompt
|
|
27
27
|
from kaizen.run import RunInfo, setup_run, update_run_head, update_run_pr_url, update_run_status
|
|
28
28
|
from kaizen.steps.pr import PRStep
|
|
29
29
|
from kaizen.steps.push import PushStep
|
|
@@ -178,7 +178,7 @@ def run_loop(
|
|
|
178
178
|
print(f"\n auto-fixing {len(auto_fix_items)} issues...")
|
|
179
179
|
fix_prompt = build_fix_prompt([_finding_to_dict(f) for f in auto_fix_items])
|
|
180
180
|
try:
|
|
181
|
-
agent.run(fix_prompt, ctx.work_dir, repo_dir=cwd)
|
|
181
|
+
agent.run(fix_prompt, ctx.work_dir, schema=FIX_SCHEMA, repo_dir=cwd)
|
|
182
182
|
commit_all(f"kaizen: fix {len(auto_fix_items)} review findings", ctx.work_dir)
|
|
183
183
|
current_head = head_commit(ctx.work_dir)
|
|
184
184
|
update_run_head(ctx.run_info.run_dir, current_head)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import time
|
|
2
2
|
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
3
5
|
from kaizen.agent import OpenCodeAgent
|
|
4
6
|
from kaizen.config import load_config
|
|
5
7
|
from kaizen.git import (
|
|
@@ -25,6 +27,14 @@ WORK_SCHEMA = {
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
|
|
30
|
+
class WorkOutput(BaseModel):
|
|
31
|
+
success: bool
|
|
32
|
+
summary: str
|
|
33
|
+
key_changes_made: list[str] = Field(default_factory=list)
|
|
34
|
+
key_learnings: list[str] = Field(default_factory=list)
|
|
35
|
+
should_fully_stop: bool = False
|
|
36
|
+
|
|
37
|
+
|
|
28
38
|
class Orchestrator:
|
|
29
39
|
def __init__(
|
|
30
40
|
self,
|
|
@@ -93,17 +103,13 @@ class Orchestrator:
|
|
|
93
103
|
break
|
|
94
104
|
continue
|
|
95
105
|
|
|
96
|
-
|
|
97
|
-
summary = str(result.output.get("summary", ""))
|
|
98
|
-
changes = result.output.get("key_changes_made") or []
|
|
99
|
-
learnings = result.output.get("key_learnings") or []
|
|
100
|
-
should_stop = bool(result.output.get("should_fully_stop", False))
|
|
106
|
+
work = WorkOutput.model_validate(result.output)
|
|
101
107
|
|
|
102
108
|
self.total_input_tokens += result.input_tokens
|
|
103
109
|
self.total_output_tokens += result.output_tokens
|
|
104
110
|
|
|
105
|
-
if success:
|
|
106
|
-
commit_msg = f"kaizen {self.iteration}: {summary}"
|
|
111
|
+
if work.success:
|
|
112
|
+
commit_msg = f"kaizen {self.iteration}: {work.summary}"
|
|
107
113
|
try:
|
|
108
114
|
commit_all(commit_msg, self.cwd)
|
|
109
115
|
except RuntimeError as e:
|
|
@@ -117,9 +123,9 @@ class Orchestrator:
|
|
|
117
123
|
self.consecutive_failures = 0
|
|
118
124
|
append_notes(
|
|
119
125
|
self.run_info.run_dir + "/notes.md",
|
|
120
|
-
self.iteration, summary,
|
|
126
|
+
self.iteration, work.summary, work.key_changes_made, work.key_learnings,
|
|
121
127
|
)
|
|
122
|
-
print(f" committed: {summary}")
|
|
128
|
+
print(f" committed: {work.summary}")
|
|
123
129
|
|
|
124
130
|
if self.push_remote:
|
|
125
131
|
try:
|
|
@@ -133,11 +139,11 @@ class Orchestrator:
|
|
|
133
139
|
reset_hard(self.cwd)
|
|
134
140
|
append_notes(
|
|
135
141
|
self.run_info.run_dir + "/notes.md",
|
|
136
|
-
self.iteration, f"[FAIL] {summary}", [],
|
|
142
|
+
self.iteration, f"[FAIL] {work.summary}", [], work.key_learnings,
|
|
137
143
|
)
|
|
138
|
-
print(f" failed: {summary}")
|
|
144
|
+
print(f" failed: {work.summary}")
|
|
139
145
|
|
|
140
|
-
if self.stop_when and
|
|
146
|
+
if self.stop_when and work.should_fully_stop:
|
|
141
147
|
print(f" stop condition met: {self.stop_when}")
|
|
142
148
|
status = "stopped"
|
|
143
149
|
break
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
1
5
|
REVIEW_SCHEMA = {
|
|
2
6
|
"type": "object",
|
|
3
7
|
"additionalProperties": False,
|
|
@@ -25,6 +29,21 @@ REVIEW_SCHEMA = {
|
|
|
25
29
|
"required": ["findings", "summary", "risk_level"],
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
FIX_SCHEMA = {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"additionalProperties": False,
|
|
35
|
+
"properties": {
|
|
36
|
+
"changes_made": {"type": "array", "items": {"type": "string"}},
|
|
37
|
+
"summary": {"type": "string"},
|
|
38
|
+
},
|
|
39
|
+
"required": ["changes_made", "summary"],
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class FixOutput(BaseModel):
|
|
44
|
+
changes_made: list[str] = Field(default_factory=list)
|
|
45
|
+
summary: str = ""
|
|
46
|
+
|
|
28
47
|
|
|
29
48
|
def build_review_prompt(diff: str, intent: str = "") -> str:
|
|
30
49
|
intent_section = ""
|
|
@@ -62,5 +81,8 @@ def build_fix_prompt(findings_items: list[dict]) -> str:
|
|
|
62
81
|
lines.append(f"1. [{f['severity']}]{loc} — {f['description']} ({f['action']})")
|
|
63
82
|
|
|
64
83
|
lines.append("\nAfter fixing, run any available tests or linters to verify.")
|
|
84
|
+
lines.append("\n## Output\n\nReturn structured output with:")
|
|
85
|
+
lines.append("- changes_made: array of descriptions of fixes applied")
|
|
86
|
+
lines.append("- summary: one-sentence summary of fixes")
|
|
65
87
|
|
|
66
88
|
return "\n".join(lines)
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
from
|
|
1
|
+
from pydantic import BaseModel
|
|
2
2
|
|
|
3
3
|
from kaizen.findings import FindingsResult
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
class StepOutcome:
|
|
6
|
+
class StepOutcome(BaseModel):
|
|
8
7
|
findings: FindingsResult | None = None
|
|
9
8
|
skipped: bool = False
|
|
10
9
|
skip_remaining: bool = False
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.0"
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass, field
|
|
2
|
-
|
|
3
|
-
ACTION_NOOP = "no-op"
|
|
4
|
-
ACTION_AUTO_FIX = "auto-fix"
|
|
5
|
-
|
|
6
|
-
SEVERITY_INFO = "info"
|
|
7
|
-
SEVERITY_WARNING = "warning"
|
|
8
|
-
SEVERITY_ERROR = "error"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@dataclass
|
|
12
|
-
class Finding:
|
|
13
|
-
id: str
|
|
14
|
-
severity: str
|
|
15
|
-
file: str = ""
|
|
16
|
-
line: int = 0
|
|
17
|
-
description: str = ""
|
|
18
|
-
action: str = ACTION_NOOP
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@dataclass
|
|
22
|
-
class FindingsResult:
|
|
23
|
-
items: list[Finding] = field(default_factory=list)
|
|
24
|
-
summary: str = ""
|
|
25
|
-
risk_level: str = "low"
|
|
26
|
-
risk_rationale: str = ""
|
|
27
|
-
|
|
28
|
-
@property
|
|
29
|
-
def has_auto_fix(self) -> bool:
|
|
30
|
-
return any(f.action == ACTION_AUTO_FIX for f in self.items)
|
|
31
|
-
|
|
32
|
-
@property
|
|
33
|
-
def auto_fix_items(self) -> list[Finding]:
|
|
34
|
-
return [f for f in self.items if f.action == ACTION_AUTO_FIX]
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def parse_findings(data: dict) -> FindingsResult:
|
|
38
|
-
items = []
|
|
39
|
-
for i, f in enumerate(data.get("findings", [])):
|
|
40
|
-
items.append(Finding(
|
|
41
|
-
id=f.get("id", f"f{i + 1}"),
|
|
42
|
-
severity=f.get("severity", SEVERITY_INFO),
|
|
43
|
-
file=f.get("file", ""),
|
|
44
|
-
line=f.get("line", 0),
|
|
45
|
-
description=f.get("description", ""),
|
|
46
|
-
action=f.get("action", ACTION_NOOP),
|
|
47
|
-
))
|
|
48
|
-
return FindingsResult(
|
|
49
|
-
items=items,
|
|
50
|
-
summary=data.get("summary", ""),
|
|
51
|
-
risk_level=data.get("risk_level", "low"),
|
|
52
|
-
risk_rationale=data.get("risk_rationale", ""),
|
|
53
|
-
)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|