codeforerunner 0.3.1__py3-none-any.whl → 0.3.2__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.
codeforerunner/check.py CHANGED
@@ -1,9 +1,9 @@
1
- """Drift detection for docs that claim files don't exist when they do."""
1
+ """Drift detection for docs vs repo state."""
2
2
  from __future__ import annotations
3
3
 
4
4
  import fnmatch
5
5
  import re
6
- from dataclasses import dataclass
6
+ from dataclasses import dataclass, field
7
7
  from pathlib import Path
8
8
 
9
9
  from codeforerunner.config import CheckConfig
@@ -17,8 +17,18 @@ class Violation:
17
17
  message: str
18
18
 
19
19
 
20
- _RULES = [
21
- (
20
+ @dataclass(frozen=True)
21
+ class _Rule:
22
+ id: str
23
+ pattern: re.Pattern
24
+ triggers: tuple[str, ...]
25
+ message: str
26
+ invert: bool = False # True = fire when triggers ABSENT (doc claims feature exists but file gone)
27
+
28
+
29
+ _RULES: list[_Rule] = [
30
+ # Normal rules: fire when trigger EXISTS and phrase matches (doc denies a thing that's present)
31
+ _Rule(
22
32
  "R1-no-cli",
23
33
  re.compile(
24
34
  r"(?i)no\s+CLI\s+exists"
@@ -29,56 +39,87 @@ _RULES = [
29
39
  ("src/codeforerunner/cli.py",),
30
40
  "doc claims no CLI exists, but src/codeforerunner/cli.py is present",
31
41
  ),
32
- (
42
+ _Rule(
33
43
  "R2-no-pre-commit",
34
44
  re.compile(r"(?i)no\s+pre[- ]commit(\s+hook)?"),
35
45
  (".pre-commit-hooks.yaml",),
36
46
  "doc claims no pre-commit hook, but .pre-commit-hooks.yaml is present",
37
47
  ),
38
- (
48
+ _Rule(
39
49
  "R3-no-ci",
40
50
  re.compile(r"(?i)no\s+CI(\s+workflow)?"),
41
51
  (".github/workflows/*.yml",),
42
52
  "doc claims no CI workflow, but .github/workflows/*.yml is present",
43
53
  ),
44
- (
54
+ _Rule(
45
55
  "R4-no-installer",
46
56
  re.compile(r"(?i)no\s+installer"),
47
57
  ("src/codeforerunner/installer.py",),
48
58
  "doc claims no installer, but src/codeforerunner/installer.py is present",
49
59
  ),
50
- (
60
+ _Rule(
51
61
  "R5-no-python-package",
52
62
  re.compile(r"(?i)no\s+Python\s+package"),
53
63
  ("pyproject.toml",),
54
64
  "doc claims no Python package, but pyproject.toml is present",
55
65
  ),
56
- (
66
+ _Rule(
57
67
  "R6-no-docker",
58
68
  re.compile(r"(?i)no\s+Docker(\s+image)?|no\s+Dockerfile"),
59
69
  ("Dockerfile", "compose.yml", "docker-compose.yml"),
60
70
  "doc claims no Docker, but Dockerfile/compose file is present",
61
71
  ),
62
- (
72
+ _Rule(
63
73
  "R6b-no-makefile",
64
74
  re.compile(r"(?i)no\s+Makefile"),
65
75
  ("Makefile",),
66
76
  "doc claims no Makefile, but Makefile is present",
67
77
  ),
68
- (
78
+ _Rule(
69
79
  "R7-no-mcp",
70
80
  re.compile(r"(?i)no\s+MCP(\s+server)?"),
71
81
  ("src/codeforerunner/mcp_server.py",),
72
82
  "doc claims no MCP server, but src/codeforerunner/mcp_server.py is present",
73
83
  ),
74
- (
84
+ _Rule(
75
85
  "R8-no-marketplace",
76
86
  re.compile(r"(?i)no\s+marketplace(\s+manifest)?"),
77
87
  ("plugins/codex/marketplace.json",),
78
88
  "doc claims no marketplace, but plugins/codex/marketplace.json is present",
79
89
  ),
90
+ # Inverse rules: fire when trigger ABSENT and phrase matches (doc claims thing exists but file gone)
91
+ _Rule(
92
+ "RI1-missing-cli",
93
+ re.compile(
94
+ r"(?i)\bforerunner\s+(?:init|scan|doc|check|generate|doctor)\b"
95
+ ),
96
+ ("src/codeforerunner/cli.py",),
97
+ "doc references forerunner CLI commands, but src/codeforerunner/cli.py is absent",
98
+ invert=True,
99
+ ),
100
+ _Rule(
101
+ "RI5-missing-python-package",
102
+ re.compile(r"(?i)\bpipx?\s+install\s+codeforerunner\b"),
103
+ ("pyproject.toml",),
104
+ "doc claims package is installable via pip/pipx, but pyproject.toml is absent",
105
+ invert=True,
106
+ ),
107
+ _Rule(
108
+ "RI7-missing-mcp",
109
+ re.compile(r"(?i)\bforerunner\s+mcp-server\b"),
110
+ ("src/codeforerunner/mcp_server.py",),
111
+ "doc references forerunner mcp-server, but src/codeforerunner/mcp_server.py is absent",
112
+ invert=True,
113
+ ),
80
114
  ]
81
115
 
116
+ _VERSION_PIN_RE = re.compile(
117
+ r"(?:codeforerunner==|codeforerunner@v)"
118
+ r"(\d+\.\d+\.\d+)"
119
+ )
120
+ _PYPROJECT_VERSION_RE = re.compile(r'^version\s*=\s*"(\d+\.\d+\.\d+)"', re.MULTILINE)
121
+ _CHANGELOG_FILENAME = "CHANGELOG.md"
122
+
82
123
 
83
124
  def _trigger_exists(repo: Path, patterns: tuple[str, ...]) -> bool:
84
125
  for pat in patterns:
@@ -114,6 +155,54 @@ def _path_ignored(repo: Path, doc: Path, ignore_patterns: tuple[str, ...]) -> bo
114
155
  return any(fnmatch.fnmatch(rel, pat) for pat in ignore_patterns)
115
156
 
116
157
 
158
+ def _current_version(repo: Path) -> str | None:
159
+ pyproject = repo / "pyproject.toml"
160
+ if not pyproject.is_file():
161
+ return None
162
+ try:
163
+ text = pyproject.read_text(encoding="utf-8")
164
+ except OSError:
165
+ return None
166
+ m = _PYPROJECT_VERSION_RE.search(text)
167
+ return m.group(1) if m else None
168
+
169
+
170
+ def _check_version_drift(
171
+ repo: Path,
172
+ docs: list[Path],
173
+ ignore_patterns: tuple[str, ...],
174
+ enabled: set[str] | None,
175
+ ) -> list[Violation]:
176
+ if enabled is not None and "RV1-version-drift" not in enabled:
177
+ return []
178
+ current = _current_version(repo)
179
+ if current is None:
180
+ return []
181
+ violations: list[Violation] = []
182
+ for doc in docs:
183
+ if doc.name == _CHANGELOG_FILENAME:
184
+ continue
185
+ if _path_ignored(repo, doc, ignore_patterns):
186
+ continue
187
+ try:
188
+ text = doc.read_text(encoding="utf-8")
189
+ except (OSError, UnicodeDecodeError):
190
+ continue
191
+ for lineno, line in enumerate(text.splitlines(), start=1):
192
+ for m in _VERSION_PIN_RE.finditer(line):
193
+ pinned = m.group(1)
194
+ if pinned != current:
195
+ violations.append(
196
+ Violation(
197
+ path=doc,
198
+ line=lineno,
199
+ rule_id="RV1-version-drift",
200
+ message=f"version pin {pinned!r} does not match current {current!r}",
201
+ )
202
+ )
203
+ return violations
204
+
205
+
117
206
  def run(repo: Path, config: CheckConfig | None = None) -> list[Violation]:
118
207
  """Scan repo docs for drift; return list of violations.
119
208
 
@@ -124,16 +213,18 @@ def run(repo: Path, config: CheckConfig | None = None) -> list[Violation]:
124
213
  enabled = set(config.enabled_rules) if (config and config.enabled_rules is not None) else None
125
214
  ignore_patterns = config.ignore_paths if config else ()
126
215
 
127
- active_rules = [
128
- (rid, rx, msg)
129
- for rid, rx, triggers, msg in _RULES
130
- if _trigger_exists(repo, triggers) and (enabled is None or rid in enabled)
131
- ]
132
- if not active_rules:
133
- return []
216
+ docs = _scanned_docs(repo)
217
+
218
+ active_rules: list[_Rule] = []
219
+ for rule in _RULES:
220
+ if enabled is not None and rule.id not in enabled:
221
+ continue
222
+ trigger_found = _trigger_exists(repo, rule.triggers)
223
+ if (not rule.invert and trigger_found) or (rule.invert and not trigger_found):
224
+ active_rules.append(rule)
134
225
 
135
226
  violations: list[Violation] = []
136
- for doc in _scanned_docs(repo):
227
+ for doc in docs:
137
228
  if _path_ignored(repo, doc, ignore_patterns):
138
229
  continue
139
230
  try:
@@ -141,11 +232,13 @@ def run(repo: Path, config: CheckConfig | None = None) -> list[Violation]:
141
232
  except (OSError, UnicodeDecodeError):
142
233
  continue
143
234
  for lineno, line in enumerate(text.splitlines(), start=1):
144
- for rid, rx, msg in active_rules:
145
- if rx.search(line):
235
+ for rule in active_rules:
236
+ if rule.pattern.search(line):
146
237
  violations.append(
147
- Violation(path=doc, line=lineno, rule_id=rid, message=msg)
238
+ Violation(path=doc, line=lineno, rule_id=rule.id, message=rule.message)
148
239
  )
240
+
241
+ violations.extend(_check_version_drift(repo, docs, ignore_patterns, enabled))
149
242
  return violations
150
243
 
151
244
 
codeforerunner/cli.py CHANGED
@@ -135,6 +135,17 @@ def cmd_generate(args: argparse.Namespace) -> int:
135
135
  print(f"error: missing API key; set ${env_var}", file=sys.stderr)
136
136
  return 3
137
137
 
138
+ if getattr(args, "stream", False):
139
+ try:
140
+ for chunk in provider.stream(prompt=buf.getvalue(), model=model, api_key=api_key):
141
+ sys.stdout.write(chunk)
142
+ sys.stdout.flush()
143
+ except _providers.ProviderError as e:
144
+ print(f"error: {provider_name} provider failed: {e}", file=sys.stderr)
145
+ return 4
146
+ sys.stdout.write("\n")
147
+ return 0
148
+
138
149
  try:
139
150
  result = provider.complete(prompt=buf.getvalue(), model=model, api_key=api_key)
140
151
  except _providers.ProviderError as e:
@@ -151,7 +162,15 @@ def cmd_generate(args: argparse.Namespace) -> int:
151
162
 
152
163
  def cmd_doctor(args: argparse.Namespace) -> int:
153
164
  from codeforerunner import doctor
165
+ from codeforerunner.config import CONFIG_FILENAME
154
166
  root = Path(args.repo).resolve() if args.repo else Path.cwd()
167
+ if getattr(args, "fix", False):
168
+ cfg_path = root / CONFIG_FILENAME
169
+ if not cfg_path.is_file():
170
+ cfg_path.write_text(doctor.starter_config(), encoding="utf-8")
171
+ print(f"wrote {cfg_path}", file=sys.stderr)
172
+ else:
173
+ print(f"{cfg_path} already exists; skipping --fix", file=sys.stderr)
155
174
  findings = doctor.run(root)
156
175
  sys.stdout.write(doctor.format_report(findings) + "\n")
157
176
  return 1 if any(f.severity == "error" for f in findings) else 0
@@ -200,12 +219,18 @@ def build_parser() -> argparse.ArgumentParser:
200
219
  s_mcp.set_defaults(func=cmd_mcp_server)
201
220
 
202
221
  s_doctor = sub.add_parser("doctor", help="health report: skill parity + marketplace + installed dests")
222
+ s_doctor.add_argument(
223
+ "--fix",
224
+ action="store_true",
225
+ help="write a starter forerunner.config.yaml if absent",
226
+ )
203
227
  s_doctor.set_defaults(func=cmd_doctor)
204
228
 
205
229
  s_gen = sub.add_parser("generate", help="resolve bundle for <task> and call the configured provider")
206
230
  s_gen.add_argument("task", help="task basename under prompts/tasks/")
207
231
  s_gen.add_argument("--provider", help="override config provider")
208
232
  s_gen.add_argument("--model", help="override config model")
233
+ s_gen.add_argument("--stream", action="store_true", help="stream output token-by-token")
209
234
  s_gen.set_defaults(func=cmd_generate)
210
235
 
211
236
  from codeforerunner import installer
codeforerunner/doctor.py CHANGED
@@ -274,6 +274,26 @@ def _check_provider_api_key(repo: Path) -> list[Finding]:
274
274
  ]
275
275
 
276
276
 
277
+ _STARTER_CONFIG = """\
278
+ # forerunner.config.yaml — generated by `forerunner doctor --fix`
279
+ # See https://github.com/derek-palmer/codeforerunner for docs.
280
+
281
+ enabled_rules:
282
+ - R1-no-cli
283
+ - R2-no-pre-commit
284
+ - R3-no-ci
285
+ - R4-no-installer
286
+ - R5-no-python-package
287
+ - R7-no-mcp
288
+ - R8-no-marketplace
289
+ ignore_paths: []
290
+ """
291
+
292
+
293
+ def starter_config() -> str:
294
+ return _STARTER_CONFIG
295
+
296
+
277
297
  def run(repo: Path) -> list[Finding]:
278
298
  repo = repo.resolve()
279
299
  findings: list[Finding] = []
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import json
6
6
  import urllib.error
7
7
  import urllib.request
8
+ from typing import Iterator
8
9
 
9
10
  from codeforerunner.providers.base import CompletionResult, ProviderError
10
11
 
@@ -59,3 +60,59 @@ class AnthropicProvider:
59
60
  return CompletionResult(
60
61
  text=text, model=data.get("model", model), usage=data.get("usage")
61
62
  )
63
+
64
+ def stream(
65
+ self,
66
+ *,
67
+ prompt: str,
68
+ model: str | None = None,
69
+ api_key: str | None = None,
70
+ ) -> Iterator[str]:
71
+ if not api_key:
72
+ raise ProviderError(f"missing API key (set ${self.default_env_var})")
73
+ model = model or self.default_model
74
+ body = json.dumps(
75
+ {
76
+ "model": model,
77
+ "max_tokens": 4096,
78
+ "stream": True,
79
+ "messages": [{"role": "user", "content": prompt}],
80
+ }
81
+ ).encode("utf-8")
82
+ req = urllib.request.Request(
83
+ self.endpoint,
84
+ data=body,
85
+ method="POST",
86
+ headers={
87
+ "x-api-key": api_key,
88
+ "anthropic-version": "2023-06-01",
89
+ "content-type": "application/json",
90
+ },
91
+ )
92
+ try:
93
+ resp = urllib.request.urlopen(req)
94
+ except urllib.error.HTTPError as e:
95
+ snippet = (e.read() or b"")[:500].decode("utf-8", errors="replace")
96
+ raise ProviderError(f"HTTP {e.code}: {snippet}") from e
97
+ except urllib.error.URLError as e:
98
+ raise ProviderError(f"network error: {e.reason}") from e
99
+ try:
100
+ for raw_line in resp:
101
+ line = raw_line.decode("utf-8").rstrip("\n")
102
+ if not line.startswith("data: "):
103
+ continue
104
+ payload = line[6:]
105
+ if payload.strip() == "[DONE]":
106
+ break
107
+ try:
108
+ event = json.loads(payload)
109
+ except json.JSONDecodeError:
110
+ continue
111
+ if event.get("type") == "content_block_delta":
112
+ delta = event.get("delta", {})
113
+ if delta.get("type") == "text_delta":
114
+ text = delta.get("text", "")
115
+ if text:
116
+ yield text
117
+ finally:
118
+ resp.close()
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from dataclasses import dataclass
6
- from typing import Protocol
6
+ from typing import Iterator, Protocol
7
7
 
8
8
 
9
9
  @dataclass(frozen=True)
@@ -26,6 +26,14 @@ class Provider(Protocol):
26
26
  api_key: str | None = None,
27
27
  ) -> CompletionResult: ...
28
28
 
29
+ def stream(
30
+ self,
31
+ *,
32
+ prompt: str,
33
+ model: str | None = None,
34
+ api_key: str | None = None,
35
+ ) -> Iterator[str]: ...
36
+
29
37
 
30
38
  class ProviderError(Exception):
31
39
  """Raised on provider HTTP failures, missing keys, or malformed responses."""
@@ -6,6 +6,7 @@ import json
6
6
  import urllib.error
7
7
  import urllib.parse
8
8
  import urllib.request
9
+ from typing import Iterator
9
10
 
10
11
  from codeforerunner.providers.base import CompletionResult, ProviderError
11
12
 
@@ -18,6 +19,10 @@ class GoogleProvider:
18
19
  endpoint_template = (
19
20
  "https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={key}"
20
21
  )
22
+ stream_endpoint_template = (
23
+ "https://generativelanguage.googleapis.com/v1beta/models/{model}:streamGenerateContent"
24
+ "?key={key}&alt=sse"
25
+ )
21
26
 
22
27
  def complete(
23
28
  self,
@@ -60,3 +65,48 @@ class GoogleProvider:
60
65
  model=data.get("modelVersion", model),
61
66
  usage=data.get("usageMetadata"),
62
67
  )
68
+
69
+ def stream(
70
+ self,
71
+ *,
72
+ prompt: str,
73
+ model: str | None = None,
74
+ api_key: str | None = None,
75
+ ) -> Iterator[str]:
76
+ if not api_key:
77
+ raise ProviderError(f"missing API key (set ${self.default_env_var})")
78
+ model = model or self.default_model
79
+ url = self.stream_endpoint_template.format(
80
+ model=urllib.parse.quote(model, safe=""),
81
+ key=urllib.parse.quote(api_key, safe=""),
82
+ )
83
+ body = json.dumps(
84
+ {"contents": [{"parts": [{"text": prompt}]}]}
85
+ ).encode("utf-8")
86
+ req = urllib.request.Request(
87
+ url,
88
+ data=body,
89
+ method="POST",
90
+ headers={"content-type": "application/json"},
91
+ )
92
+ try:
93
+ resp = urllib.request.urlopen(req)
94
+ except urllib.error.HTTPError as e:
95
+ snippet = (e.read() or b"")[:500].decode("utf-8", errors="replace")
96
+ raise ProviderError(f"HTTP {e.code}: {snippet}") from e
97
+ except urllib.error.URLError as e:
98
+ raise ProviderError(f"network error: {e.reason}") from e
99
+ try:
100
+ for raw_line in resp:
101
+ line = raw_line.decode("utf-8").rstrip("\n")
102
+ if not line.startswith("data: "):
103
+ continue
104
+ try:
105
+ event = json.loads(line[6:])
106
+ text = event["candidates"][0]["content"]["parts"][0]["text"]
107
+ if text:
108
+ yield text
109
+ except (json.JSONDecodeError, KeyError, IndexError, TypeError):
110
+ continue
111
+ finally:
112
+ resp.close()
@@ -6,6 +6,7 @@ import json
6
6
  import os
7
7
  import urllib.error
8
8
  import urllib.request
9
+ from typing import Iterator
9
10
 
10
11
  from codeforerunner.providers.base import CompletionResult, ProviderError
11
12
 
@@ -54,3 +55,47 @@ class OllamaProvider:
54
55
  usage_keys = ("prompt_eval_count", "eval_count", "total_duration")
55
56
  usage = {k: data[k] for k in usage_keys if k in data} or None
56
57
  return CompletionResult(text=text, model=data.get("model", model), usage=usage)
58
+
59
+ def stream(
60
+ self,
61
+ *,
62
+ prompt: str,
63
+ model: str | None = None,
64
+ api_key: str | None = None,
65
+ ) -> Iterator[str]:
66
+ base = api_key or os.environ.get(self.default_env_var) or DEFAULT_HOST
67
+ base = base.rstrip("/")
68
+ model = model or self.default_model
69
+ url = f"{base}/api/generate"
70
+ body = json.dumps(
71
+ {"model": model, "prompt": prompt, "stream": True}
72
+ ).encode("utf-8")
73
+ req = urllib.request.Request(
74
+ url,
75
+ data=body,
76
+ method="POST",
77
+ headers={"content-type": "application/json"},
78
+ )
79
+ try:
80
+ resp = urllib.request.urlopen(req)
81
+ except urllib.error.HTTPError as e:
82
+ snippet = (e.read() or b"")[:500].decode("utf-8", errors="replace")
83
+ raise ProviderError(f"HTTP {e.code}: {snippet}") from e
84
+ except urllib.error.URLError as e:
85
+ raise ProviderError(f"network error: {e.reason}") from e
86
+ try:
87
+ for raw_line in resp:
88
+ line = raw_line.decode("utf-8").strip()
89
+ if not line:
90
+ continue
91
+ try:
92
+ event = json.loads(line)
93
+ except json.JSONDecodeError:
94
+ continue
95
+ text = event.get("response", "")
96
+ if text:
97
+ yield text
98
+ if event.get("done", False):
99
+ break
100
+ finally:
101
+ resp.close()
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import json
6
6
  import urllib.error
7
7
  import urllib.request
8
+ from typing import Iterator
8
9
 
9
10
  from codeforerunner.providers.base import CompletionResult, ProviderError
10
11
 
@@ -57,3 +58,56 @@ class OpenAIProvider:
57
58
  return CompletionResult(
58
59
  text=text, model=data.get("model", model), usage=data.get("usage")
59
60
  )
61
+
62
+ def stream(
63
+ self,
64
+ *,
65
+ prompt: str,
66
+ model: str | None = None,
67
+ api_key: str | None = None,
68
+ ) -> Iterator[str]:
69
+ if not api_key:
70
+ raise ProviderError(f"missing API key (set ${self.default_env_var})")
71
+ model = model or self.default_model
72
+ body = json.dumps(
73
+ {
74
+ "model": model,
75
+ "stream": True,
76
+ "messages": [{"role": "user", "content": prompt}],
77
+ }
78
+ ).encode("utf-8")
79
+ req = urllib.request.Request(
80
+ self.endpoint,
81
+ data=body,
82
+ method="POST",
83
+ headers={
84
+ "Authorization": f"Bearer {api_key}",
85
+ "content-type": "application/json",
86
+ },
87
+ )
88
+ try:
89
+ resp = urllib.request.urlopen(req)
90
+ except urllib.error.HTTPError as e:
91
+ snippet = (e.read() or b"")[:500].decode("utf-8", errors="replace")
92
+ raise ProviderError(f"HTTP {e.code}: {snippet}") from e
93
+ except urllib.error.URLError as e:
94
+ raise ProviderError(f"network error: {e.reason}") from e
95
+ try:
96
+ for raw_line in resp:
97
+ line = raw_line.decode("utf-8").rstrip("\n")
98
+ if not line.startswith("data: "):
99
+ continue
100
+ payload = line[6:]
101
+ if payload.strip() == "[DONE]":
102
+ break
103
+ try:
104
+ event = json.loads(payload)
105
+ except json.JSONDecodeError:
106
+ continue
107
+ choices = event.get("choices", [])
108
+ if choices:
109
+ text = choices[0].get("delta", {}).get("content")
110
+ if text:
111
+ yield text
112
+ finally:
113
+ resp.close()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeforerunner
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Model-agnostic repository documentation tooling (prompt-first; thin CLI).
5
5
  Author: Derek Palmer
6
6
  License-Expression: LicenseRef-Codeforerunner-SAL-0.1
@@ -1,9 +1,9 @@
1
1
  codeforerunner/__init__.py,sha256=4ItVS_FzLddTK77jExpkV3QJ1nHl2Bh-QIujM7Hg_5w,205
2
2
  codeforerunner/bundle.py,sha256=wWlhNaja8HPLzN-9pxiSrFD8X0mGi-c1HM9HRyVhmzk,2020
3
- codeforerunner/check.py,sha256=kSrVoTVb9ALEBHvUerR2nZfroXco_yz6gMgXzdz45Ac,4905
4
- codeforerunner/cli.py,sha256=3eJtUhfOj_ZMPAYPHvlQ7P-eBs2NRIpzewipqvOpy9w,7981
3
+ codeforerunner/check.py,sha256=5sJVwMMSvVCrRmwBIunYbn2pINroUjONrVJAs5ov3O4,8188
4
+ codeforerunner/cli.py,sha256=Rk6T7G3QC78-HpxvGKONQ9zIauZw5LceBHurFechJuU,9032
5
5
  codeforerunner/config.py,sha256=REs4FgmSSn7R2tLIwLJPL8VmSCGkTvw8J2SqBOggzho,6206
6
- codeforerunner/doctor.py,sha256=AFHY8YbqTp726JcxLAYvrdeOLwr6WLPiNUUF2QYOwL8,10076
6
+ codeforerunner/doctor.py,sha256=dsnLfB7ZuYTM7HG9JxzLl63GEmhr_miMRZy9RPq7z_8,10456
7
7
  codeforerunner/installer.py,sha256=9Ze_hyjvow-if-NMovk9CzVDYVZSR3vyLO6s2vhOs6g,10227
8
8
  codeforerunner/mcp_server.py,sha256=oIfuAR7e_rH--B1aLOATVflyWAyGpkyeeXI4SAI4eTg,4657
9
9
  codeforerunner/prompts/partials/context-format.md,sha256=WNfkr4kf2Awj0R8wLOrFotEiYCe6hfKTq5eA3Rt5_Xw,817
@@ -23,14 +23,14 @@ codeforerunner/prompts/tasks/scan.md,sha256=hYXf-IL1kgpBPHJapRrwTu88cLZ7y3bCmAq9
23
23
  codeforerunner/prompts/tasks/stack-docs.md,sha256=Dy-JSXpSmHSyhR5shQBXKa_F0PqnjPcmtljthYZpaiM,1923
24
24
  codeforerunner/prompts/tasks/version-audit.md,sha256=oK-pcoxt_VcvDOlj1Sz9OlEhXlcViLPn54r-qP5WfiA,5833
25
25
  codeforerunner/providers/__init__.py,sha256=ttMAbHWJIO8s-8H6Kb_EWf3LN5oMzlmX1D12RyGSmIg,962
26
- codeforerunner/providers/anthropic.py,sha256=zaJDyzH1vPr7nSqUOv6avlTk3covpkWXLHae2-bS9io,2021
27
- codeforerunner/providers/base.py,sha256=Jk9vBeRNxH9naUC6stN5jY1KHmhrqg2k2kMGOhbQTYk,752
28
- codeforerunner/providers/google.py,sha256=wBpbte8hdX_Jnr_JBHvQarogT6T1hWR4aGF5O2exBCU,2109
29
- codeforerunner/providers/ollama.py,sha256=bKhigdjHs3aw73iB5uQka9_n-XCjv1sh_zZfe1TrWTQ,1975
30
- codeforerunner/providers/openai.py,sha256=OHWeZ11OuHPgQBMwqJnJWxNHyGIP5KL7B06w2ttgxlY,1952
31
- codeforerunner-0.3.1.dist-info/licenses/LICENSE.md,sha256=iIhmJHib6GbdjcwiDMM-npiNRf3XgASom1WsOJivEdc,2915
32
- codeforerunner-0.3.1.dist-info/METADATA,sha256=koodiwqaG0vZYUyvQgkhSy7ggb0s6ldTEJazVtCyrec,7737
33
- codeforerunner-0.3.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
34
- codeforerunner-0.3.1.dist-info/entry_points.txt,sha256=3p8BbPlq-wfcXk42tsweKePRaGlZ1WXho1gOkuZGyIQ,55
35
- codeforerunner-0.3.1.dist-info/top_level.txt,sha256=pV1rt0-NIpNEotKXpL_sF2060DHr-_0F86LWhUlvXis,15
36
- codeforerunner-0.3.1.dist-info/RECORD,,
26
+ codeforerunner/providers/anthropic.py,sha256=oPi4oLHcHEDs8gK-W5Jt06Ef5N23axFTyXhO-2YcCkk,4049
27
+ codeforerunner/providers/base.py,sha256=MMrOUVOXHWP1td-TndxhLhDyDPJZGExZCeFopZUSRCo,923
28
+ codeforerunner/providers/google.py,sha256=OWEE0FNupFWmZCeilIrgYUYDHH1iWvIwHEEsHYQoFFY,3979
29
+ codeforerunner/providers/ollama.py,sha256=k6keO6eN-iwLFLtYETMMMoAyjxJLO1x16p_rM2TGks0,3524
30
+ codeforerunner/providers/openai.py,sha256=999ZzIVh0cqW4xDnzK_NACqfJxNziHwpVjwmw9_jjRw,3825
31
+ codeforerunner-0.3.2.dist-info/licenses/LICENSE.md,sha256=iIhmJHib6GbdjcwiDMM-npiNRf3XgASom1WsOJivEdc,2915
32
+ codeforerunner-0.3.2.dist-info/METADATA,sha256=vCvhbtoSliPMvDFYubsockBIAMLg6JUVkbqsaMBu2zg,7737
33
+ codeforerunner-0.3.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
34
+ codeforerunner-0.3.2.dist-info/entry_points.txt,sha256=3p8BbPlq-wfcXk42tsweKePRaGlZ1WXho1gOkuZGyIQ,55
35
+ codeforerunner-0.3.2.dist-info/top_level.txt,sha256=pV1rt0-NIpNEotKXpL_sF2060DHr-_0F86LWhUlvXis,15
36
+ codeforerunner-0.3.2.dist-info/RECORD,,