lazycoder 0.1.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.
argus/llm/client.py ADDED
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import deque
4
+ from collections.abc import Sequence
5
+ from typing import Protocol
6
+
7
+
8
+ class LLMClient(Protocol):
9
+ """Minimal contract for components that produce raw model output."""
10
+
11
+ def generate(self, prompt: str) -> str: ...
12
+
13
+
14
+ class FakeLLMClient:
15
+ """Deterministic LLM test double with queued raw responses."""
16
+
17
+ def __init__(self, responses: Sequence[str]) -> None:
18
+ self._responses = deque(responses)
19
+ self.prompts: list[str] = []
20
+
21
+ def generate(self, prompt: str) -> str:
22
+ self.prompts.append(prompt)
23
+ if not self._responses:
24
+ msg = "FakeLLMClient has no queued responses"
25
+ raise ValueError(msg)
26
+ return self._responses.popleft()
argus/orchestrator.py ADDED
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+
6
+ from argus.config.models import ReviewRulesConfig
7
+ from argus.domain import ReviewReport
8
+ from argus.reviewers import SingleRuleReviewer
9
+
10
+ # Matches a unified-diff hunk header, capturing the new-file start line:
11
+ # @@ -12,3 +45,6 @@ -> 45
12
+ _HUNK = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@")
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class CodeBlock:
17
+ """One reviewable chunk of a diff: a single hunk's post-change content."""
18
+
19
+ file: str
20
+ start_line: int
21
+ code: str
22
+
23
+
24
+ def parse_diff(diff_text: str) -> list[CodeBlock]:
25
+ """Split a unified diff into per-hunk code blocks (added + context lines).
26
+
27
+ ponytail: naive line scanner, no renames/binaries/mode changes. Add when a
28
+ real diff needs them.
29
+ """
30
+ blocks: list[CodeBlock] = []
31
+ current_file: str | None = None
32
+ start_line = 1
33
+ lines: list[str] = []
34
+
35
+ def flush() -> None:
36
+ if current_file and current_file != "/dev/null" and lines:
37
+ blocks.append(CodeBlock(current_file, start_line, "\n".join(lines)))
38
+
39
+ for raw in diff_text.splitlines():
40
+ if raw.startswith("+++ "):
41
+ flush()
42
+ lines = []
43
+ path = raw[4:].strip()
44
+ current_file = path[2:] if path.startswith("b/") else path
45
+ elif match := _HUNK.match(raw):
46
+ flush()
47
+ lines = []
48
+ start_line = int(match.group(1))
49
+ elif raw.startswith("+") and not raw.startswith("+++"):
50
+ lines.append(raw[1:])
51
+ elif raw.startswith(" "):
52
+ lines.append(raw[1:])
53
+ # '-' removals, '\', and file headers are ignored.
54
+
55
+ flush()
56
+ return blocks
57
+
58
+
59
+ def review_diff(
60
+ reviewer: SingleRuleReviewer, diff_text: str, rubric: ReviewRulesConfig
61
+ ) -> ReviewReport:
62
+ """Review every hunk of a diff against the full rubric, one global report."""
63
+ results = [
64
+ result
65
+ for block in parse_diff(diff_text)
66
+ for result in reviewer.review_rubric(block.code, rubric).rule_results
67
+ ]
68
+ return ReviewReport.from_rule_results(results)
@@ -0,0 +1,5 @@
1
+ """Reviewer agents and parsing helpers."""
2
+
3
+ from argus.reviewers.single_rule import LLMReviewerParseError, SingleRuleReviewer
4
+
5
+ __all__ = ["LLMReviewerParseError", "SingleRuleReviewer"]
@@ -0,0 +1,126 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ from pydantic import BaseModel, ConfigDict, ValidationError, model_validator
6
+
7
+ from argus.config.models import ReviewRule, ReviewRulesConfig
8
+ from argus.domain import Finding, ReviewReport, RuleResult
9
+ from argus.llm import LLMClient
10
+
11
+
12
+ class LLMReviewerParseError(Exception):
13
+ """Raised when a reviewer response is invalid or does not match the schema."""
14
+
15
+
16
+ def _normalize_severity(payload: dict[str, object]) -> None:
17
+ """Lowercase severity at the parse boundary so the domain enum stays strict."""
18
+ finding = payload.get("finding")
19
+ if isinstance(finding, dict):
20
+ severity = finding.get("severity")
21
+ if isinstance(severity, str):
22
+ finding["severity"] = severity.strip().lower()
23
+
24
+
25
+ class _ReviewerResponse(BaseModel):
26
+ model_config = ConfigDict(extra="forbid")
27
+
28
+ passed: bool
29
+ finding: Finding | None = None
30
+
31
+ @model_validator(mode="after")
32
+ def finding_matches_passed(self) -> _ReviewerResponse:
33
+ if self.passed and self.finding is not None:
34
+ msg = "passed response must not include a finding"
35
+ raise ValueError(msg)
36
+ if not self.passed and self.finding is None:
37
+ msg = "failed response must include a finding"
38
+ raise ValueError(msg)
39
+ return self
40
+
41
+
42
+ class SingleRuleReviewer:
43
+ """Minimal reviewer that evaluates one rule on one code block."""
44
+
45
+ def __init__(self, client: LLMClient) -> None:
46
+ self._client = client
47
+
48
+ def review(self, code_block: str, rule: ReviewRule) -> RuleResult:
49
+ prompt = self._build_prompt(code_block=code_block, rule=rule)
50
+ raw_response = self._client.generate(prompt)
51
+ parsed = self._parse_response(raw_response)
52
+ finding = parsed.finding
53
+ if finding is not None and finding.rule_id != rule.id:
54
+ msg = "Invalid reviewer response: finding.rule_id must match requested rule"
55
+ raise LLMReviewerParseError(msg)
56
+ return RuleResult(rule_id=rule.id, passed=parsed.passed, finding=finding)
57
+
58
+ def review_all(self, code_block: str, rules: list[ReviewRule]) -> ReviewReport:
59
+ """Evaluate every rule on one block and aggregate into a report."""
60
+ results = [self.review(code_block, rule) for rule in rules]
61
+ return ReviewReport.from_rule_results(results)
62
+
63
+ def review_rubric(self, code_block: str, rubric: ReviewRulesConfig) -> ReviewReport:
64
+ """Evaluate one block against every rule in the configured rubric."""
65
+ return self.review_all(code_block, rubric.rules)
66
+
67
+ def _build_prompt(self, code_block: str, rule: ReviewRule) -> str:
68
+ rule_id = rule.id.value
69
+ return (
70
+ "You are reviewing one code block against one rule.\n"
71
+ f"Rule ID: {rule_id}\n"
72
+ f"Question: {rule.question}\n"
73
+ f"Checks: {rule.checks}\n"
74
+ f"Flag when: {rule.flag_when}\n"
75
+ f"Expected severity if unjustified: {rule.severity_if_unjustified.value}\n"
76
+ "Respond with a single JSON object and nothing else"
77
+ " - no prose, no code fences.\n"
78
+ "Fields:\n"
79
+ "- passed (bool): true if the rule is satisfied,"
80
+ " false if it is violated.\n"
81
+ "- finding (object|null): null when passed is true;"
82
+ " required when passed is false, with fields:\n"
83
+ f' - rule_id (string): must be exactly "{rule_id}".\n'
84
+ " - location (object): file (string, non-empty),"
85
+ " line (int, 1-based), optional end_line (int >= line).\n"
86
+ ' - severity (string): one of "low", "medium", "high".\n'
87
+ " - reason (string, non-empty): why the code violates the rule.\n"
88
+ "Example output when the rule passes:\n"
89
+ '{"passed": true, "finding": null}\n'
90
+ "Example output when the rule is violated:\n"
91
+ f'{{"passed": false, "finding": {{"rule_id": "{rule_id}",'
92
+ ' "location": {"file": "diff.py", "line": 3}, "severity": "high",'
93
+ ' "reason": "unvalidated input reaches the query"}}\n'
94
+ "Code block:\n"
95
+ f"{code_block}\n"
96
+ )
97
+
98
+ def _parse_response(self, raw_response: str) -> _ReviewerResponse:
99
+ payload = self._extract_json_object(raw_response)
100
+ _normalize_severity(payload)
101
+ try:
102
+ return _ReviewerResponse.model_validate(payload)
103
+ except ValidationError as exc:
104
+ msg = f"Invalid reviewer response: {exc}"
105
+ raise LLMReviewerParseError(msg) from exc
106
+
107
+ @staticmethod
108
+ def _extract_json_object(raw_response: str) -> dict[str, object]:
109
+ # Slice from the first '{' to the last '}' so code fences (```json)
110
+ # and surrounding prose are dropped in one move.
111
+ # ponytail: naive brace slice; over-captures if prose after the object
112
+ # also contains a '}'. Switch to a bracket-matching scan if that shows up.
113
+ start = raw_response.find("{")
114
+ end = raw_response.rfind("}")
115
+ if start == -1 or end < start:
116
+ msg = "Invalid reviewer response: no JSON object found"
117
+ raise LLMReviewerParseError(msg)
118
+ try:
119
+ payload = json.loads(raw_response[start : end + 1])
120
+ except json.JSONDecodeError as exc:
121
+ msg = f"Invalid reviewer response: invalid JSON at line {exc.lineno}"
122
+ raise LLMReviewerParseError(msg) from exc
123
+ if not isinstance(payload, dict):
124
+ msg = "Invalid reviewer response: expected a JSON object"
125
+ raise LLMReviewerParseError(msg)
126
+ return payload
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: lazycoder
3
+ Version: 0.1.0
4
+ Summary: Code review agent with senior-level judgement: interrogates every diff hunk against a fixed rubric and returns APPROVE / REQUEST_CHANGES / BLOCK
5
+ Project-URL: Repository, https://github.com/aisona-lab/lazycoder
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.12
9
+ Requires-Dist: anthropic>=0.40
10
+ Requires-Dist: pydantic>=2.10
11
+ Provides-Extra: dev
12
+ Requires-Dist: black>=24.0; extra == 'dev'
13
+ Requires-Dist: mypy>=1.13; extra == 'dev'
14
+ Requires-Dist: pre-commit>=4.0; extra == 'dev'
15
+ Requires-Dist: pytest>=8.0; extra == 'dev'
16
+ Requires-Dist: ruff>=0.8; extra == 'dev'
17
+ Description-Content-Type: text/markdown
18
+
19
+ <h1><img src="assets/logo.png" alt="" height="40" valign="middle">&nbsp;lazycoder</h1>
20
+
21
+ A code review agent with senior-level judgement. It interrogates every changed
22
+ block against a fixed rubric, runs the real checks, and returns a defensible
23
+ verdict — **APPROVE / REQUEST_CHANGES / BLOCK** — before code is trusted or merged.
24
+
25
+ Code gets written fast. The bottleneck is trusting it. lazycoder is the reviewer
26
+ that never gets tired, never skips a rule, and never self-reports green without
27
+ running the checks.
28
+
29
+ ## Manual review vs lazycoder
30
+
31
+ | | Manual review | lazycoder |
32
+ |---|---|---|
33
+ | **Coverage** | Whatever the reviewer remembers to look at | Every rule (R1–R17) evaluated, every time |
34
+ | **Consistency** | Varies by reviewer, mood, time of day | Same rubric, same policy, deterministic |
35
+ | **Verdict** | "LGTM" / gut feel | APPROVE / REQUEST_CHANGES / BLOCK from a severity policy |
36
+ | **Evidence** | Comments, sometimes | Every finding cites `rule_id` + exact file:line |
37
+ | **Green claims** | "tests pass" (trust me) | Real linter/typecheck/test output in a sandbox |
38
+ | **Untrusted code** | Reviewer may run it locally | Reviewed code is data, never executed outside the sandbox |
39
+ | **Speed at scale** | Slows down as diffs grow | Loops the rubric per block, unattended |
40
+ | **Auditability** | Lives in someone's head | Append-only decision log; any verdict is replayable |
41
+
42
+ lazycoder does not replace the human — a person still confirms consequential
43
+ decisions. It removes the parts humans are bad at: remembering all 17 rules,
44
+ staying consistent across 200 files, and proving the checks actually ran.
45
+
46
+ Two structural facts, at a glance. These are not benchmarks — they are
47
+ properties enforced by the schema, so they hold on every single review:
48
+
49
+ ```mermaid
50
+ xychart-beta
51
+ title "Rubric rules guaranteed evaluated per code block"
52
+ x-axis ["manual review", "lazycoder"]
53
+ y-axis "rules (of 17)" 0 --> 17
54
+ bar [0, 17]
55
+ ```
56
+
57
+ Manual review *may* cover all 17 — nothing guarantees it. lazycoder cannot emit
58
+ a verdict until every rule has a recorded pass/fail (`APPROVE` is refused
59
+ otherwise).
60
+
61
+ ```mermaid
62
+ xychart-beta
63
+ title "Findings that cite rule_id + exact file:line (%)"
64
+ x-axis ["manual review", "lazycoder"]
65
+ y-axis "% enforced" 0 --> 100
66
+ bar [0, 100]
67
+ ```
68
+
69
+ A human reviewer *can* cite evidence; the lazycoder domain model makes an
70
+ uncited finding unrepresentable — pydantic rejects it before it exists.
71
+
72
+ ## Status
73
+
74
+ The **full pipeline is live end to end** — deterministic core plus the real
75
+ model. A unified diff flows all the way to an aggregated verdict:
76
+
77
+ ```
78
+ diff → parse_diff → CodeBlock[]
79
+ └─ review_rubric(block, rubric) # every rule, every block
80
+ └─ RuleResult[] → from_rule_results → aggregate → verdict
81
+ ```
82
+
83
+ The same flow runs in two modes, sharing every line of plumbing:
84
+
85
+ - **Fake client** (default, CI): deterministic, network-free. `pytest -q` proves
86
+ the parser, aggregator, and verdict policy on every run.
87
+ - **Real client** (opt-in): `AnthropicClient` hits the live API. The first live
88
+ run of eval E3 already passed — the model caught the SQL injection, flagged
89
+ R7, and the pipeline derived `BLOCK` with zero parse failures.
90
+
91
+ Because the model was the *last* thing plugged in, any failure isolates to the
92
+ prompt or the model — never to the plumbing, which is already proven. The
93
+ response parser is hardened against real LLM output (code fences, surrounding
94
+ prose, severity casing), and the reviewer prompt teaches the model the exact
95
+ `Finding` schema with a literal example, so form errors die at the source.
96
+
97
+ ## Config-driven policy
98
+
99
+ Policy is declarative and lives in `config/`, not buried in code. Each file is
100
+ one part of the setup — reviewable, diffable, swappable:
101
+
102
+ ```
103
+ lazycoder/
104
+ ├── config/
105
+ │ ├── harness.json # project context, stack, hard rules, definition of done
106
+ │ ├── guardrails.json # what the agent may / may not do; injection defense; limits
107
+ │ ├── setup.json # runtime, deps + rationale, env vars, bootstrap
108
+ │ ├── working_loop.json # specify → plan → execute → verify → decide
109
+ │ ├── task_loop.json # orchestrator + review subagents, isolation, aggregation
110
+ │ ├── review_rules.json # R1..R17 — the interrogation rubric (the core)
111
+ │ ├── production_readiness.json # the release gate
112
+ │ ├── evals.json # known-flawed/clean cases that test the reviewer
113
+ │ └── observability.json # append-only decision log, tracing, redaction
114
+ ├── src/argus/ # domain, config loader, reviewers, llm client
115
+ └── tests/ # unit + integration + eval coverage
116
+ ```
117
+
118
+ ## The rubric (R1..R17)
119
+
120
+ Code-level: data structure (R1), control flow (R2), inputs/outputs (R3), failure
121
+ modes (R4), side effects (R5), dependencies (R6). Security: validation, secrets,
122
+ injection (R7). Simplicity: simplest form (R8). System-level: state (R9), sync vs
123
+ async (R10), monolith vs services (R11), invariant (R12). Plus maintainability,
124
+ tests, and compatibility rules through R17.
125
+
126
+ ## Design decisions — the *why*
127
+
128
+ The interesting part of this project is not the review logic; it's the choices
129
+ that make the review logic trustworthy.
130
+
131
+ - **Deterministic core, model last.** Everything that can be pure logic *is* pure
132
+ logic, and the non-deterministic LLM is bolted on at the very end. This is a
133
+ deliberate failure-isolation strategy: when a review goes wrong, the bug is in
134
+ the prompt or the model, because the plumbing has tests proving it isn't there.
135
+
136
+ - **Contracts make invalid state unrepresentable.** The domain types are strict
137
+ pydantic models with validators, not bags of fields. A *passed* rule cannot
138
+ carry a finding; a *failed* one must. Every finding must cite its `rule_id` and
139
+ an exact `file:line`. The verdict is a *computed* field over findings, never a
140
+ value someone can set by hand. You cannot construct a lying `ReviewReport`.
141
+
142
+ - **Normalize at the boundary, keep the core strict.** Untrusted LLM text is
143
+ cleaned up where it enters (`"HIGH"` → `"high"`), but the domain enum stays the
144
+ single source of truth and never loosens. Leniency lives at the edge; the core
145
+ does not bend.
146
+
147
+ - **Debt is executable, not documented.** The one known parser limitation is
148
+ pinned by a `strict` xfail test, not a comment someone can ignore. The day the
149
+ fix lands, that test flips to green and the suite *tells you* the debt is
150
+ closed. Notes rot; tests don't.
151
+
152
+ - **TDD throughout.** Every behavior went RED before GREEN — including the
153
+ garbage-input fixtures that hardened the parser.
154
+
155
+ - **The eval is the product.** `config/evals.json` is a set of known-flawed and
156
+ known-clean cases whose job is to measure *the reviewer itself*. Wired as a CI
157
+ gate, it closes the loop: a code reviewer that has its own reviewer, and knows
158
+ whether it's still good every time it changes.
159
+
160
+ ## Develop
161
+
162
+ ```bash
163
+ uv sync --extra dev
164
+ pre-commit install
165
+
166
+ pytest -q # deterministic suite — no network, no key
167
+ ruff check . && black --check .
168
+ mypy src
169
+ ```
170
+
171
+ To run the live-API suite (opt-in, never part of `pytest -q`):
172
+
173
+ ```bash
174
+ cp .env.example .env # fill in ANTHROPIC_API_KEY — .env is gitignored
175
+ set -a; source .env; set +a
176
+ pytest -m integration
177
+ ```
178
+
179
+ ## Roadmap
180
+
181
+ 1. ~~Multi-file / diff orchestration on top of `review_rubric`.~~ ✓
182
+ 2. ~~Harden the response parser against real LLM output (fixtures).~~ ✓
183
+ 3. ~~Wire `config/evals.json` as a regression gate on the fake client — a missed
184
+ rule fails the gate.~~ ✓
185
+ 4. ~~Wire the real Anthropic client behind the same `LLMClient` protocol, with an
186
+ opt-in integration suite (`pytest -m integration`). First live run: the model
187
+ caught eval E3's SQL injection (R7 → BLOCK).~~ ✓
188
+ 5. **Run the full evals.json set against the live model** and track the score
189
+ over time — the eval stops measuring the plumbing and starts measuring the
190
+ reviewer: does this prompt, on this model, still catch what it must?
191
+ 6. **Distribution:** package for PyPI with a `lazycoder` console entry point, so
192
+ users install with `uvx lazycoder` / `pipx install lazycoder` and review a
193
+ diff with one command. A GitHub Action wrapping the same CLI comes after.
@@ -0,0 +1,31 @@
1
+ argus/__init__.py,sha256=VmlqakiJiEqOnq4w47hWCNjALYKqAwG7j8UFdCfGqTI,65
2
+ argus/cli.py,sha256=lLiPLGoE345JuFSFWpH2Nyld_Pr8fdoIlX--Z9QalsA,3161
3
+ argus/evals.py,sha256=6ez6o1nUHEKg4b90cBtLBhswE4dtkAX6g_bjkBWZLxs,1614
4
+ argus/orchestrator.py,sha256=wVSyN81Ud0V5pN5I8Uo_39iKSS8FILUvCmKAlQ4WBzQ,2099
5
+ argus/config/__init__.py,sha256=LxnIdC58AercgJnzeuhydRpYVHH8y7DWad_MbrLEW_k,211
6
+ argus/config/exceptions.py,sha256=hdHszYmKubDVRq3W4TrTvvRNCYb_l0-CdzljifJPb8Q,369
7
+ argus/config/loader.py,sha256=ofJT-66fWjlqsuKUe5cvDyTZKJnQel-bryFwXn2kKYM,3166
8
+ argus/config/models.py,sha256=5y8vTrhNWmLxfqFvqoTMX7RVsuWBwtjp5LVSvZWe8LM,6586
9
+ argus/domain/__init__.py,sha256=S7Ygw73IKx-fQyL9hru8zhZ44Q7nDrzVY7HMv_Ieohg,403
10
+ argus/domain/aggregator.py,sha256=otrCfhPUp6OSsytgx8L7ra__4N55RItcsXCyDgZhV3Y,443
11
+ argus/domain/enums.py,sha256=XriyNUm7JiBZaFmVf8aiGyk7TZdBwtpq2pQG3MCq_Z0,676
12
+ argus/domain/models.py,sha256=bh-y_Hlfklf_qxIMftEIdqCqEKFSZ3BrW5hxByhibD8,3409
13
+ argus/llm/__init__.py,sha256=KEJZFLctsqs_YbszXe5oJNElDa-k1iaf-z4dfpMJnBM,142
14
+ argus/llm/anthropic_client.py,sha256=5AlSVplZecrM1x6Ut2fnQgLY5sMsoLh04j1sIfOJTME,955
15
+ argus/llm/client.py,sha256=EmMUEe9bbObpsbfHAtOOquoG97iK9CuOoZVWAnY0Bsw,757
16
+ argus/reviewers/__init__.py,sha256=1Y5ZLS8gRlngnGewRvEcGziJuvpEojHPtxzQRVs4d48,185
17
+ argus/reviewers/single_rule.py,sha256=l17RlgB_SQ31ejN5V6MXIpNkfJCjaGs6qXrzu9Djx0o,5599
18
+ argus/config_defaults/evals.json,sha256=qfpjx3HlH9z7Xo1Zv1sxtd5BjiHm50cmi9Qc8fu95AE,4604
19
+ argus/config_defaults/guardrails.json,sha256=UROE8B-QhNaMzxm4x97UahEHo4KyF7f29DEAXz13bBk,1623
20
+ argus/config_defaults/harness.json,sha256=g7-uo0rhk87C3_Gc_TZYCNPwG7i8-F6J1y9snaPmPaw,2406
21
+ argus/config_defaults/observability.json,sha256=tSUkls1Nyz7l9e2RjyG1SbAdnK4YKLQ0OtZoNjwwwkM,887
22
+ argus/config_defaults/production_readiness.json,sha256=4jQxh5Xtq6MU0dCxHyFA_LIOda62HCrfRBFyhgRWuuE,1463
23
+ argus/config_defaults/review_rules.json,sha256=mG7-qBiAlARDzSf2J1I__JopuWlSTBTByqHfFgKsz4k,9413
24
+ argus/config_defaults/setup.json,sha256=t46yW3kzOEynWUHdDSBhaquZ0Q1McZUm6d_-eoPbxTM,1027
25
+ argus/config_defaults/task_loop.json,sha256=zgeK0viq5tKmqy1ARCk2c9Kgli5LnQT-JeSGdYcOS-A,2012
26
+ argus/config_defaults/working_loop.json,sha256=OrFrKLG-qpCiU0RgUWO1JHYEYrodRpQNkpI3jyarYGg,1748
27
+ lazycoder-0.1.0.dist-info/METADATA,sha256=Q7l4TpXjVALzMlgqew3iSCXOF01cy4mr29rIYtXSGUw,9062
28
+ lazycoder-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
29
+ lazycoder-0.1.0.dist-info/entry_points.txt,sha256=69ECUFp2kX5K9K9V0ReQiZvRZ4cWzd4rDQtMI93JZU0,45
30
+ lazycoder-0.1.0.dist-info/licenses/LICENSE,sha256=J_Cg8cr_G5WnKPWYa3bKL9629162E_JMZS8rpte4aHU,1067
31
+ lazycoder-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ lazycoder = argus.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 aisona-lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.