loopgate 0.1.0__tar.gz → 0.1.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.
- {loopgate-0.1.0 → loopgate-0.1.1}/PKG-INFO +2 -3
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/cli.py +36 -33
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/config.py +15 -24
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/temp.pyproject.toml +1 -1
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/tests/test_cli.py +181 -101
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/tests/test_gate.py +1 -1
- {loopgate-0.1.0 → loopgate-0.1.1}/pyproject.toml +6 -6
- {loopgate-0.1.0 → loopgate-0.1.1}/tests/preferences/test_preferences.py +234 -131
- loopgate-0.1.0/tests/preferences/test_preferences_properties.py +0 -245
- {loopgate-0.1.0 → loopgate-0.1.1}/.githooks/_resolve +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/.githooks/hoist +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/.githooks/pre-commit +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/.githooks/pre-push +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/.githooks/prepare-commit-msg +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/.gitignore +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/LICENSE +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/README.md +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/docs/PROJECT_STATUS.md +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/docs/PROMPT.md +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/docs/plan.md +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/docs/specs/another_spec.md +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/docs/specs/base.md +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/__init__.py +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/gate.py +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/js-scaffold/PROMPT.md +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/js-scaffold/README.md +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/js-scaffold/index.html +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/js-scaffold/package-lock.json +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/js-scaffold/package.json +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/js-scaffold/quiz.js +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/js-scaffold/specs/quiz.md +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/js-scaffold/test.js +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/ralph.ps1 +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/ralph.sh +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/tests/conftest.py +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/tests/test_init_fixtures.py +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/tests/test_properties.py +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/tests/test_ralph.py +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/harness/tests/test_ralph_ps1.py +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/mutation/check_mutmut.py +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/preferences/__init__.py +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/preferences/preferences.py +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/tests/mutation/mutmut-cicd-stats.json +0 -0
- {loopgate-0.1.0 → loopgate-0.1.1}/tests/mutation/test_check_mutmut.py +0 -0
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: loopgate
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Gated, harnessed, autonomy loop with quality checks.
|
|
5
5
|
Author: rxdt
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Keywords: agent,autonomy,claude,codex,harness,llm,loop,scaffold
|
|
9
9
|
Classifier: Development Status :: 4 - Beta
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
11
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -161,8 +161,9 @@ def info() -> None:
|
|
|
161
161
|
def status() -> None:
|
|
162
162
|
"""Count run logs and point at the newest one."""
|
|
163
163
|
runs = REPO_ROOT / "scratchpad" / "runs"
|
|
164
|
-
logs = sorted(runs.glob("*.jsonl")) if runs.is_dir() else [
|
|
165
|
-
|
|
164
|
+
logs = sorted(runs.glob("*.jsonl"), reverse=True) if runs.is_dir() else []
|
|
165
|
+
newest = "\n".join(str(log) for log in logs[:3]) if logs else ""
|
|
166
|
+
secho(f"{len(logs)} run log(s) in {runs}\nnewest:\n{newest}", fg=30)
|
|
166
167
|
|
|
167
168
|
|
|
168
169
|
def cleanup(cwd: Path) -> bool:
|
|
@@ -195,7 +196,7 @@ def cleanup(cwd: Path) -> bool:
|
|
|
195
196
|
)
|
|
196
197
|
def install() -> None:
|
|
197
198
|
"""Used by template cloned from Github. Syncs dependencies, and activates the git hooks."""
|
|
198
|
-
rprint("\n[cyan2]installing dependencies[/
|
|
199
|
+
rprint("\n[cyan2]installing dependencies[/]")
|
|
199
200
|
env_bin = infer_env_manager_and_install()
|
|
200
201
|
cleanup(REPO_ROOT)
|
|
201
202
|
setup_git_hooks(env_bin)
|
|
@@ -340,21 +341,21 @@ def init() -> None:
|
|
|
340
341
|
):
|
|
341
342
|
secho("Run `harness init` to configure loopgate", fg=colors.MAGENTA, bold=True)
|
|
342
343
|
raise Exit(code=0)
|
|
343
|
-
|
|
344
|
-
confirm(style("\n2. Can we wire githooks so quality checks run?", fg=10), default=True, abort=True)
|
|
344
|
+
wired = write_harness_config()
|
|
345
345
|
hoisted = hoist()
|
|
346
346
|
hooks = setup_git_hooks(Path(sys.executable).parent)
|
|
347
347
|
timeout = check_for_timeout_and_prompt() or IS_WINDOWS
|
|
348
348
|
agents = configure_agents()
|
|
349
349
|
rprint(
|
|
350
|
-
f"\n[bold cyan2]RESULT:[/]\
|
|
351
|
-
f"\
|
|
352
|
-
f"\
|
|
353
|
-
"\n[
|
|
350
|
+
f"\n[bold cyan2]RESULT:[/]\ngit hooks available via path: {hooks}\ntimeout-ready: {timeout}"
|
|
351
|
+
f"\nfiles added: {hoisted}\nagents configured: {agents}"
|
|
352
|
+
f"\nHarness commands in `pyproject.toml to run as checks`:\n{set(wired)}\n"
|
|
353
|
+
f"\n[bold cyan2]Can likely run loops with checks: [/]{bool(wired and hoisted and hooks and timeout)}\n"
|
|
354
|
+
"\n[italic]To loop with checks using `harness run` command, ensure your environemnt is activated[/]\n"
|
|
354
355
|
)
|
|
355
356
|
|
|
356
357
|
|
|
357
|
-
def write_harness_config() ->
|
|
358
|
+
def write_harness_config() -> set[str]:
|
|
358
359
|
"""Takes user's configs and creates a pyproject.toml or appends to an existing pyproject.toml."""
|
|
359
360
|
pyproject_path = REPO_ROOT / "pyproject.toml"
|
|
360
361
|
user_pyproject: TOMLDocument = document()
|
|
@@ -362,21 +363,21 @@ def write_harness_config() -> bool:
|
|
|
362
363
|
if pyproject_path.is_file():
|
|
363
364
|
user_pyproject: TOMLDocument = parse(pyproject_path.read_text(encoding="utf-8"))
|
|
364
365
|
user_pyproject_tools: dict[str, Any] = user_pyproject.setdefault("tool", table())
|
|
365
|
-
user_harness = user_pyproject_tools.get("harness", {})
|
|
366
|
-
checks = user_harness.get("gate", {})
|
|
367
|
-
if checks and checks.get("test"):
|
|
368
|
-
confirm(style("Seems loopgate may be wired already. Continue?", fg=10), default=True, abort=True)
|
|
369
366
|
template: Path = Path(__file__).resolve().with_name("temp.pyproject.toml")
|
|
370
367
|
template_contents: TOMLDocument = parse(template.read_text(encoding="utf-8"))
|
|
371
|
-
|
|
368
|
+
template_tools = template_contents["tool"]
|
|
369
|
+
for t in template_tools:
|
|
372
370
|
if t in user_pyproject_tools:
|
|
373
|
-
|
|
371
|
+
template_tools[t] = deepcopy(user_pyproject_tools[t])
|
|
374
372
|
contents = ConfigParser(interpolation=None)
|
|
375
373
|
contents.read([(REPO_ROOT / "tox.ini"), (REPO_ROOT / "setup.cfg")], encoding="utf-8")
|
|
376
374
|
section_names = contents.sections()
|
|
377
375
|
inspect_configs(section_names, user_pyproject_tools, deepcopy(CATEGORIES), template_contents["tool"])
|
|
378
|
-
user_pyproject.setdefault("tool", {}).update(
|
|
379
|
-
|
|
376
|
+
user_pyproject.setdefault("tool", {}).update(template_tools)
|
|
377
|
+
pyproject_path.write_text(dumps(user_pyproject), encoding="utf-8")
|
|
378
|
+
updated_pyproject: TOMLDocument = parse(pyproject_path.read_text(encoding="utf-8"))
|
|
379
|
+
harness: dict[str, Any] = updated_pyproject.setdefault("tool", table()).get("harness", {})
|
|
380
|
+
return set(harness.get("preflight", {}) | harness.get("gate", {}))
|
|
380
381
|
|
|
381
382
|
|
|
382
383
|
def find_configured_command(
|
|
@@ -412,10 +413,7 @@ def find_configured_command(
|
|
|
412
413
|
|
|
413
414
|
|
|
414
415
|
def inspect_configs(
|
|
415
|
-
section_names: list[str],
|
|
416
|
-
user_pyproject_tools: dict[str, Any],
|
|
417
|
-
categories: dict[str, str],
|
|
418
|
-
template: dict[str, Any],
|
|
416
|
+
section_names: list[str], user_pyproject_tools: dict[str, Any], categories: dict[str, str], template: dict[str, Any]
|
|
419
417
|
) -> None:
|
|
420
418
|
"""For each tool in a pre-built internal map, see if user has the tool installed and configured.
|
|
421
419
|
Args:
|
|
@@ -453,35 +451,40 @@ def hoist() -> bool:
|
|
|
453
451
|
if not (ASSETS["docs"][0].is_dir() and ASSETS["githooks"][0].is_dir()):
|
|
454
452
|
rprint("Harness is missing required assets: `docs/` and `githooks/`")
|
|
455
453
|
return False
|
|
454
|
+
confirm(style("\n2. Can we wire githooks so quality checks run?", fg=10), default=True, abort=True)
|
|
455
|
+
repo_root = REPO_ROOT
|
|
456
|
+
ASSETS["githooks"][1].mkdir(parents=True, exist_ok=True)
|
|
457
|
+
hoist_script: str = (ASSETS["githooks"][0] / "hoist").as_posix()
|
|
458
|
+
run_git(["-c", 'alias.loopgate-hoist=!f() { sh "$1"; }; f', "loopgate-hoist", hoist_script], REPO_ROOT)
|
|
459
|
+
console.print("[green]\n3. Ran script to make `.githooks` executable[/]\n")
|
|
456
460
|
rprint(
|
|
457
|
-
"\n[bold yellow]We will need to add these files[/]\n*
|
|
458
|
-
"\n* Mutation tests promote
|
|
459
|
-
"
|
|
461
|
+
"\n[bold yellow]We will need to add these files[/]\n* `.githooks` are what ensure quality checks run"
|
|
462
|
+
"\n* Mutation tests promote better tests. Docs and code for example test at `mutation/` and `tests/mutation`"
|
|
463
|
+
"\n* `preferences/` allows for checks beyond what tooling catches"
|
|
464
|
+
"and demonstrates Hypothesis property tests\n* `docs` contain the instructions and memory for loops "
|
|
460
465
|
"\n*`scratchpad/` allows local agent use and contains a `runs/` directory for logs."
|
|
461
466
|
)
|
|
462
467
|
confirm(
|
|
463
468
|
style(
|
|
464
|
-
"
|
|
469
|
+
"\n4. Confirm, loopgate can add those files? Pre-existing files in the expected paths will remain "
|
|
465
470
|
"and loopgate will skip adding them.",
|
|
466
471
|
fg=10,
|
|
467
472
|
),
|
|
468
473
|
default=True,
|
|
469
474
|
abort=True,
|
|
470
475
|
)
|
|
471
|
-
repo_root = REPO_ROOT
|
|
472
|
-
ASSETS["githooks"][1].mkdir(parents=True, exist_ok=True)
|
|
473
|
-
hoist_script: str = (ASSETS["githooks"][0] / "hoist").as_posix()
|
|
474
|
-
run_git(["-c", 'alias.loopgate-hoist=!f() { sh "$1"; }; f', "loopgate-hoist", hoist_script], REPO_ROOT)
|
|
475
|
-
console.print(f"[green]\n4. Ran {hoist_script}[/]\n")
|
|
476
476
|
for key, paths in ASSETS.items():
|
|
477
477
|
source, destination = paths
|
|
478
|
-
|
|
479
|
-
|
|
478
|
+
source_paths = source.rglob("*") if source.is_dir() else (source,)
|
|
479
|
+
for source_path in source_paths:
|
|
480
|
+
destination_path = destination / source_path.relative_to(source) if source.is_dir() else destination
|
|
480
481
|
if source_path.is_dir():
|
|
481
482
|
destination_path.mkdir(parents=True, exist_ok=True)
|
|
482
483
|
elif not destination_path.exists():
|
|
483
484
|
destination_path.parent.mkdir(parents=True, exist_ok=True)
|
|
484
485
|
copy2(source_path, destination_path)
|
|
486
|
+
else:
|
|
487
|
+
rprint(f"Skipped existing `{destination_path}` during copy")
|
|
485
488
|
rprint(f"`{key}/` exists {destination.exists()}")
|
|
486
489
|
(repo_root / "scratchpad" / "runs").mkdir(parents=True, exist_ok=True)
|
|
487
490
|
(repo_root / "scratchpad" / "runs" / ".gitkeep").touch()
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from importlib.metadata import distribution
|
|
5
|
+
from importlib.metadata import distribution
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
9
|
from harness.gate import gates
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
site_packages = Path(str(distribution(distribution_name).locate_file("")))
|
|
11
|
+
site_packages = Path(str(distribution("loopgate").locate_file("")))
|
|
13
12
|
package_root = site_packages / "harness"
|
|
14
13
|
repo_root = gates().repo_root
|
|
15
14
|
|
|
@@ -78,11 +77,7 @@ TOOLS: dict[str, dict[str, Any]] = {
|
|
|
78
77
|
"args": gates().commit_checks["ruff_lint"],
|
|
79
78
|
},
|
|
80
79
|
"black": {"category": "format", "pyproject": ["black"], "args": ["black", "--check", "."]},
|
|
81
|
-
"coverage": {
|
|
82
|
-
"category": "test",
|
|
83
|
-
"filenames": [".coveragerc", ".coveragerc.toml"],
|
|
84
|
-
"pyproject": ["coverage"],
|
|
85
|
-
},
|
|
80
|
+
"coverage": {"category": "test", "filenames": [".coveragerc", ".coveragerc.toml"], "pyproject": ["coverage"]},
|
|
86
81
|
"flake8": {"category": "lint", "filenames": [".flake8"], "args": ["flake8", "."]},
|
|
87
82
|
"hypothesis": {"category": "test", "pyproject": ["hypothesis"]},
|
|
88
83
|
"lint": {"category": "lint", "pyproject": ["lint"]},
|
|
@@ -104,12 +99,7 @@ TOOLS: dict[str, dict[str, Any]] = {
|
|
|
104
99
|
"pyproject": ["pylint"],
|
|
105
100
|
"args": gates().commit_checks["pylint"],
|
|
106
101
|
},
|
|
107
|
-
"mypy": {
|
|
108
|
-
"category": "types",
|
|
109
|
-
"filenames": ["mypy.ini", ".mypy.ini"],
|
|
110
|
-
"pyproject": ["mypy"],
|
|
111
|
-
"args": ["mypy", "."],
|
|
112
|
-
},
|
|
102
|
+
"mypy": {"category": "types", "filenames": ["mypy.ini", ".mypy.ini"], "pyproject": ["mypy"], "args": ["mypy", "."]},
|
|
113
103
|
"mutmut": {"category": "test", "pyproject": ["mutmut"]},
|
|
114
104
|
"radon": {
|
|
115
105
|
"category": "complexity",
|
|
@@ -173,12 +163,7 @@ TOOLS: dict[str, dict[str, Any]] = {
|
|
|
173
163
|
"filenames": [".xenon.yml"],
|
|
174
164
|
"args": ["xenon", "--max-absolute", "B", "--max-modules", "A", "--max-average", "A", "."],
|
|
175
165
|
},
|
|
176
|
-
"zuban": {
|
|
177
|
-
"category": "types",
|
|
178
|
-
"filenames": [".zuban.toml"],
|
|
179
|
-
"pyproject": ["zuban"],
|
|
180
|
-
"args": ["zuban", "check"],
|
|
181
|
-
},
|
|
166
|
+
"zuban": {"category": "types", "filenames": [".zuban.toml"], "pyproject": ["zuban"], "args": ["zuban", "check"]},
|
|
182
167
|
}
|
|
183
168
|
|
|
184
169
|
CLAUDE_RULES: set[str] = {
|
|
@@ -196,8 +181,14 @@ CLAUDE_RULES: set[str] = {
|
|
|
196
181
|
ASSETS: dict[str, tuple[Path, Path]] = {
|
|
197
182
|
"docs": (package_root / "docs", repo_root / "docs"),
|
|
198
183
|
"githooks": (package_root / ".githooks", repo_root / ".githooks"),
|
|
199
|
-
"preferences": (site_packages / "preferences", repo_root / "preferences"),
|
|
200
|
-
"mutation": (site_packages / "mutation", repo_root / "mutation"),
|
|
201
|
-
"
|
|
202
|
-
|
|
184
|
+
"preferences": (site_packages / "preferences/preferences.py", repo_root / "preferences/preferences.py"),
|
|
185
|
+
"mutation": (site_packages / "mutation/check_mutmut.py", repo_root / "mutation/check_mutmut.py"),
|
|
186
|
+
"tests/preferences": (
|
|
187
|
+
package_root / "tests/preferences/test_preferences.py",
|
|
188
|
+
repo_root / "tests/preferences/test_preferences.py",
|
|
189
|
+
),
|
|
190
|
+
"tests/mutation": (
|
|
191
|
+
package_root / "tests/mutation/test_check_mutmut.py",
|
|
192
|
+
repo_root / "tests/mutation/test_check_mutmut.py",
|
|
193
|
+
),
|
|
203
194
|
}
|